From d1262aa3fffa637d92268714b512628b8d0d11af Mon Sep 17 00:00:00 2001 From: Eugen Nicolae Cojan Date: Tue, 13 Jul 2021 19:23:18 +0300 Subject: [PATCH] Added env variable support for himl --- README.md | 5 ++ .../cluster=cluster1/cluster.yaml | 1 + himl/config_generator.py | 10 +++- himl/inject_env.py | 50 +++++++++++++++++++ himl/interpolation.py | 18 +++++++ 5 files changed, 83 insertions(+), 1 deletion(-) create mode 100644 himl/inject_env.py diff --git a/README.md b/README.md index 5807d7c..c1b9323 100644 --- a/README.md +++ b/README.md @@ -267,6 +267,11 @@ remote_states: endpoint: "{{outputs.cluster_composition.output.value.redis_endpoint}}" ``` +### Merge with env variables +```yaml +kubeconfig_location: {{env(KUBECONFIG)}} +``` + ## himl config merger diff --git a/examples/complex/env=dev/region=us-west-2/cluster=cluster1/cluster.yaml b/examples/complex/env=dev/region=us-west-2/cluster=cluster1/cluster.yaml index e364015..cdbf029 100644 --- a/examples/complex/env=dev/region=us-west-2/cluster=cluster1/cluster.yaml +++ b/examples/complex/env=dev/region=us-west-2/cluster=cluster1/cluster.yaml @@ -1 +1,2 @@ cluster: cluster1 +home: {{env(HOME)}} diff --git a/himl/config_generator.py b/himl/config_generator.py index b204f86..4ced82e 100755 --- a/himl/config_generator.py +++ b/himl/config_generator.py @@ -17,7 +17,8 @@ import yaml from deepmerge import Merger -from .interpolation import InterpolationResolver, EscapingResolver, InterpolationValidator, SecretResolver, DictIterator, replace_parent_working_directory +from .interpolation import InterpolationResolver, EscapingResolver, InterpolationValidator, SecretResolver, \ + DictIterator, replace_parent_working_directory, EnvVarResolver from .python_compat import iteritems, primitive_types, PY3 from .remote_state import S3TerraformRemoteStateRetriever @@ -68,6 +69,9 @@ def process(self, cwd=None, path=None, filters=(), exclude_keys=(), enclosing_ke # value2: "something-{{value1}} <--- this will be resolved at this step generator.resolve_interpolations() + generator.resolve_env() + generator.resolve_interpolations() + if len(filters) > 0: generator.filter_data(filters) @@ -279,6 +283,10 @@ def resolve_secrets(self, default_aws_profile): resolver = SecretResolver() self.generated_data = resolver.resolve_secrets(self.generated_data, default_aws_profile) + def resolve_env(self): + resolver = EnvVarResolver() + self.generated_data = resolver.resolve_env_vars(self.generated_data) + def validate_interpolations(self): self.interpolation_validator.check_all_interpolations_resolved(self.generated_data) diff --git a/himl/inject_env.py b/himl/inject_env.py new file mode 100644 index 0000000..cf4a3b1 --- /dev/null +++ b/himl/inject_env.py @@ -0,0 +1,50 @@ +# Copyright 2021 Adobe. All rights reserved. +# This file is licensed to you 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 REPRESENTATIONS +# OF ANY KIND, either express or implied. See the License for the specific language +# governing permissions and limitations under the License. + +from os import getenv + + +class EnvVarInjector(object): + """ + Resolve variables in the form: + {{env(HOME)}} + """ + + def __init__(self): + return + + def is_interpolation(self, value): + return value.startswith('{{') and value.endswith('}}') + + def inject_env_var(self, line): + """ + Check if value is an interpolation and try to resolve it. + """ + if not self.is_interpolation(line): + return line + + # remove {{ and }} + updated_line = line[2:-2] + + # check supported function to ensure the proper format is used + if not self.check_supported_function(updated_line): + return line + + # remove env( and ) to extract the env Variable + updated_line = updated_line[4:-1] + + # If env variable is missing or not set, the output will be None + return getenv(updated_line) + + def check_supported_function(self, value): + return value.startswith('env(') and value.endswith(')') + + def split_function_items(self, line): + line.split() diff --git a/himl/interpolation.py b/himl/interpolation.py index 75e4fc9..b517753 100644 --- a/himl/interpolation.py +++ b/himl/interpolation.py @@ -10,6 +10,7 @@ import re +from .inject_env import EnvVarInjector from .inject_secrets import SecretInjector from .python_compat import iteritems, string_types, primitive_types @@ -83,6 +84,14 @@ def resolve_secrets(self, data, default_aws_profile): return data +class EnvVarResolver(object): + def resolve_env_vars(self, data): + injector = EnvVarInjector() + env_resolver = EnvVarInterpolationsResolver(injector) + env_resolver.resolve_interpolations(data) + return data + + class DictIterator(object): def loop_all_items(self, data, process_func): @@ -168,6 +177,15 @@ def do_resolve_interpolation(self, line): return self.secrets_injector.inject_secret(line) +class EnvVarInterpolationsResolver(AbstractInterpolationResolver): + def __init__(self, env_vars_injector): + AbstractInterpolationResolver.__init__(self) + self.env_vars_injector = env_vars_injector + + def do_resolve_interpolation(self, line): + return self.env_vars_injector.inject_env_var(line) + + class InterpolationValidator(DictIterator): def check_all_interpolations_resolved(self, data):