codecov-io: # [Codecov](https://codecov.io/gh/2gis/k8s-handle/pull/73?src=pr&el=h1) Report
> Merging [#73](https://codecov.io/gh/2gis/k8s-handle/pull/73?src=pr&el=desc) into [master](https://codecov.io/gh/2gis/k8s-handle/commit/e727854bd0493af8f836abf496582f53c2d9c711?src=pr&el=desc) will **decrease** coverage by `0.06%`.
> The diff coverage is `100%`.

[![Impacted file tree graph](https://codecov.io/gh/2gis/k8s-handle/pull/73/graphs/tree.svg?width=650&token=tv8Ij8dA6k&height=150&src=pr)](https://codecov.io/gh/2gis/k8s-handle/pull/73?src=pr&el=tree)

```diff
@@            Coverage Diff             @@
##           master      #73      +/-   ##
==========================================
- Coverage    73.9%   73.84%   -0.07%     
==========================================
  Files           8        8              
  Lines         801      799       -2     
==========================================
- Hits          592      590       -2     
  Misses        209      209
```


| [Impacted Files](https://codecov.io/gh/2gis/k8s-handle/pull/73?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [config.py](https://codecov.io/gh/2gis/k8s-handle/pull/73/diff?src=pr&el=tree#diff-Y29uZmlnLnB5) | `100% <100%> (ø)` | :arrow_up: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/2gis/k8s-handle/pull/73?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/2gis/k8s-handle/pull/73?src=pr&el=footer). Last update [e727854...cdb67cc](https://codecov.io/gh/2gis/k8s-handle/pull/73?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).

otherNoscript: ууупс, не в ту ветку
dekhtyarev: @otherNoscript , привет! 

Обнови PR с мастера, пжлста. У нас произошли некоторые изменения в рамках #70 
diff --git a/.dockerignore b/.dockerignore
index d3f33a2..fea1e67 100644
--- a/.dockerignore
+++ b/.dockerignore
@@ -6,5 +6,10 @@ Dockerfile*
 .gitignore
 .idea/
 .tox/
+.travis.yml
+tox.ini
 __pychache__
 htmlcov/
+tests/
+*.png
+
diff --git a/k8s_handle/config.py b/k8s_handle/config.py
index acbf34f..6c2564f 100644
--- a/k8s_handle/config.py
+++ b/k8s_handle/config.py
@@ -13,7 +13,7 @@ from k8s_handle.templating import b64decode
 log = logging.getLogger(__name__)
 
 INCLUDE_RE = re.compile(r'{{\s?file\s?=\s?\'(?P<file>[^\']*)\'\s?}}')
-CUSTOM_ENV_RE = re.compile(r'^(?P<prefix>.*){{\s*env\s*=\s*\'(?P<env>[^\']*)\'\s*}}(?P<postfix>.*)$')  # noqa
+CUSTOM_ENV_RE = r'{{\s*env\s*=\s*\'([^\']*)\'\s*}}'
 
 KEY_USE_KUBECONFIG = 'use_kubeconfig'
 KEY_K8S_MASTER_URI = 'k8s_master_uri'
@@ -113,19 +113,15 @@ def _process_variable(variable):
     if matches:
         return load_yaml(matches.groupdict().get('file'))
 
-    matches = CUSTOM_ENV_RE.match(variable)
+    try:
+        return re.sub(CUSTOM_ENV_RE, lambda m: os.environ[m.group(1)], variable)
 
-    if matches:
-        prefix = matches.groupdict().get('prefix')
-        env_var_name = matches.groupdict().get('env')
-        postfix = matches.groupdict().get('postfix')
-
-        if os.environ.get(env_var_name) is None and settings.GET_ENVIRON_STRICT:
-            raise RuntimeError('Environment variable "{}" is not set'.format(env_var_name))
-
-        return prefix + os.environ.get(env_var_name, '') + postfix
+    except KeyError as err:
+        log.debug('Environment variable "{}" is not set'.format(err.args[0]))
+        if settings.GET_ENVIRON_STRICT:
+            raise RuntimeError('Environment variable "{}" is not set'.format(err.args[0]))
 
-    return variable
+    return re.sub(CUSTOM_ENV_RE, lambda m: os.environ.get(m.group(1), ''), variable)
 
 
 def _update_single_variable(value, include_history):
diff --git a/k8s_handle/filesystem.py b/k8s_handle/filesystem.py
index 5d200eb..7993290 100644
--- a/k8s_handle/filesystem.py
+++ b/k8s_handle/filesystem.py
@@ -17,7 +17,7 @@ class InvalidYamlError(Exception):
 def load_yaml(path):
     try:
         with open(path) as f:
-            return yaml.load(f.read())
+            return yaml.safe_load(f.read())
     except Exception as e:
         raise InvalidYamlError("file '{}' doesn't contain valid yaml: {}".format(
             path, e))
diff --git a/k8s_handle/templating.py b/k8s_handle/templating.py
index 2a5c441..7476f07 100644
--- a/k8s_handle/templating.py
+++ b/k8s_handle/templating.py
@@ -19,7 +19,7 @@ log = logging.getLogger(__name__)
 def get_template_contexts(file_path):
     with open(file_path) as f:
         try:
-            contexts = yaml.load_all(f.read())
+            contexts = yaml.safe_load_all(f.read())
         except Exception as e:
             raise RuntimeError('Unable to load yaml file: {}, {}'.format(file_path, e))
 

It is not possible to concatenate several environment variables into one value
Привет.
Столкнулся с невозможностью одновременного использования нескольких переменных окружения при объявлении переменной в config.yaml. Небольшой пример:
Мы при развертывании сервиса иногда создаем более одного деплоя, registry используем как приватные так и публичные, да еще и registry приватных у нас несколько. Так же при каждом деплое у нас выбирается образ по тэгу в git. Для того, что бы не создавать множество шаблонов деплоя, мы сделали один универсальный, в цикле которого добавляем все необходимые образы. По этому все образы объявляются в config.yaml
```yaml
common:
  deployments:
    service_name:
      containers:
        app:
          image: "{{ env='CI_REGISTRY' }}/service-image:{{ env='TAG' }}"
        nginx:
          image: "{{ env='CI_REGISTRY' }}/custom-nginx:configmap"
```
Так вот при такой записи в случае с образом nginx все ОК, в случае с контейнером app после создания манифеста имеем "{{ env='CI_REGISTRY' }}/service-image:1.0.0". Заглянул в код и понял, что в текущей реализации не получится из нескольких переменных окружения получить нужное нам имя образа.
diff --git a/tests/test_config.py b/tests/test_config.py
index 2f2e2b3..ce3e228 100644
--- a/tests/test_config.py
+++ b/tests/test_config.py
@@ -118,13 +118,17 @@ class TestContextGeneration(unittest.TestCase):
                     'section1-key4': [0, 1, 2, 3],
                     'section1-key5': "{{ env='CUSTOM_ENV' }}",
                     'section1-key6': "{{ file='tests/fixtures/include.yaml' }}",
+                    'section1-key7': "{{ env='CUSTOM_ENV'}} = {{ env='CUSTOM_ENV' }}",
+                    'section1-key8': "{{ env='NULL_VAR' }}-{{ env='CUSTOM_ENV' }}"
                 }
             },
             'section2': [
                 {},
                 'var2',
                 'var3',
-                '{{ env=\'CUSTOM_ENV\' }}'
+                '{{ env=\'CUSTOM_ENV\' }}',
+                '{{ env=\'CUSTOM_ENV\' }} = {{ env=\'CUSTOM_ENV\' }}',
+                '{{ env=\'NULL_VAR\' }}-{{ env=\'CUSTOM_ENV\' }}'
             ],
             'section3': [0, 1, 2, 3, 4]
         }
@@ -137,13 +141,17 @@ class TestContextGeneration(unittest.TestCase):
                     'section1-key4': [0, 1, 2, 3],
                     'section1-key5': 'My value',
                     'section1-key6': {'ha_ha': 'included_var'},
+                    'section1-key7': 'My value = My value',
+                    'section1-key8': "-My value"
                 }
             },
             'section2': [
                 {},
                 'var2',
                 'var3',
-                'My value'
+                'My value',
+                'My value = My value',
+                '-My value'
             ],
             'section3': [0, 1, 2, 3, 4]
         }

pip install -e .
parse_log_pytest
requirements.txt
pytest pytest-cov pytest-xdist pytest-mock pytest-asyncio
apt-get update apt-get install -y gcc
3.6
requirements.txt
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
adal==1.2.7
attrs==22.2.0
cachetools==4.2.4
certifi==2021.5.30
cffi==1.15.1
chardet==3.0.4
coverage==6.2
cryptography==40.0.2
execnet==1.9.0
google-auth==2.22.0
idna==2.7
importlib-metadata==4.8.3
iniconfig==1.1.1
Jinja2==2.10
-e git+https://github.com/2gis/k8s-handle.git@dec5c73ec1bcd694bd45651901d68cd933721b3e#egg=k8s_handle
kubernetes==7.0.0
MarkupSafe==2.0.1
oauthlib==3.2.2
packaging==21.3
pluggy==1.0.0
py==1.11.0
pyasn1==0.5.1
pyasn1-modules==0.3.0
pycparser==2.21
PyJWT==2.4.0
pyparsing==3.1.4
pytest==7.0.1
pytest-asyncio==0.16.0
pytest-cov==4.0.0
pytest-mock==3.6.1
pytest-xdist==3.0.2
python-dateutil==2.9.0.post0
PyYAML==3.13
requests==2.20.1
requests-oauthlib==2.0.0
rsa==4.9
semver==2.8.1
six==1.17.0
tomli==1.2.3
typing_extensions==4.1.1
urllib3==1.24.3
websocket-client==1.3.1
zipp==3.6.0

name: k8s-handle
channels:
  - defaults
  - https://repo.anaconda.com/pkgs/main
  - https://repo.anaconda.com/pkgs/r
  - conda-forge
dependencies:
  - _libgcc_mutex=0.1=main
  - _openmp_mutex=5.1=1_gnu
  - ca-certificates=2025.2.25=h06a4308_0
  - certifi=2021.5.30=py36h06a4308_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.3=he6710b0_2
  - libgcc-ng=11.2.0=h1234567_1
  - libgomp=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - ncurses=6.4=h6a678d5_0
  - openssl=1.1.1w=h7f8727e_0
  - pip=21.2.2=py36h06a4308_0
  - python=3.6.13=h12debd9_1
  - readline=8.2=h5eee18b_0
  - setuptools=58.0.4=py36h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - wheel=0.37.1=pyhd3eb1b0_0
  - xz=5.6.4=h5eee18b_1
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - adal==1.2.7
      - attrs==22.2.0
      - cachetools==4.2.4
      - cffi==1.15.1
      - chardet==3.0.4
      - coverage==6.2
      - cryptography==40.0.2
      - execnet==1.9.0
      - google-auth==2.22.0
      - idna==2.7
      - importlib-metadata==4.8.3
      - iniconfig==1.1.1
      - jinja2==2.10
      - kubernetes==7.0.0
      - markupsafe==2.0.1
      - oauthlib==3.2.2
      - packaging==21.3
      - pluggy==1.0.0
      - py==1.11.0
      - pyasn1==0.5.1
      - pyasn1-modules==0.3.0
      - pycparser==2.21
      - pyjwt==2.4.0
      - pyparsing==3.1.4
      - pytest==7.0.1
      - pytest-asyncio==0.16.0
      - pytest-cov==4.0.0
      - pytest-mock==3.6.1
      - pytest-xdist==3.0.2
      - python-dateutil==2.9.0.post0
      - pyyaml==3.13
      - requests==2.20.1
      - requests-oauthlib==2.0.0
      - rsa==4.9
      - semver==2.8.1
      - six==1.17.0
      - tomli==1.2.3
      - typing-extensions==4.1.1
      - urllib3==1.24.3
      - websocket-client==1.3.1
      - zipp==3.6.0
prefix: /opt/conda/envs/k8s-handle

tests/test_config.py::TestContextGeneration::test_context_update_recursion
tests/test_config.py::TestContextGeneration::test_check_empty_var tests/test_config.py::TestContextGeneration::test_common_section tests/test_config.py::TestContextGeneration::test_concatination_with_env tests/test_config.py::TestContextGeneration::test_config_incorrect tests/test_config.py::TestContextGeneration::test_config_is_empty tests/test_config.py::TestContextGeneration::test_config_not_exist tests/test_config.py::TestContextGeneration::test_context_update_section tests/test_config.py::TestContextGeneration::test_dashes_in_var_names tests/test_config.py::TestContextGeneration::test_env_var_in_include_2_levels_dont_set tests/test_config.py::TestContextGeneration::test_env_var_in_include_dont_set tests/test_config.py::TestContextGeneration::test_env_var_in_section1_dont_set tests/test_config.py::TestContextGeneration::test_env_var_in_section2_dont_set tests/test_config.py::TestContextGeneration::test_infinite_recursion_loop tests/test_config.py::TestContextGeneration::test_merge_section_options tests/test_config.py::TestContextGeneration::test_no_templates tests/test_config.py::TestContextGeneration::test_not_existed_section tests/test_config.py::TestContextGeneration::test_recursive_vars tests/test_config.py::TestPriorityEvaluation::test_environment_deprecated tests/test_config.py::TestPriorityEvaluation::test_first_none_argument tests/test_config.py::TestPriorityEvaluation::test_first_priority tests/test_config.py::TestPriorityEvaluation::test_k8s_client_configuration_missing_k8s_ca tests/test_config.py::TestPriorityEvaluation::test_k8s_client_configuration_missing_token tests/test_config.py::TestPriorityEvaluation::test_k8s_client_configuration_missing_uri tests/test_config.py::TestPriorityEvaluation::test_k8s_client_configuration_success tests/test_config.py::TestPriorityEvaluation::test_k8s_namespace_default
k8s_handle/filesystem.py .dockerignore k8s_handle/templating.py k8s_handle/config.py
k8s_handle/filesystem.py .dockerignore k8s_handle/templating.py k8s_handle/config.py