Недавно, на работе появилась нужда в том, чтобы иметь один конфигурационный YAML-файл со всеми типами ENV и переменными для них. При этом, чтобы скрипт умел парсить его и подставлять во все вхождения во всех входящих файлах. Это очень удобно, когда у вас много типов машин с разными настройками. Я решил проблему, написал скрипт, все очень даже довольны.
Python3 скрипт для конструктора конфиг-файлов с Jinja в Unix/Linux
Начнем с того, что у становим питон и другие полезности:
Установка python в Unix/Linux — в процессе написания (в черновиках)
Установка pip/setuptools/wheel в Unix/Linux
Другие полезные темы:
Установка/Использование pexpect и python в Unix/Linux
Установка virtualenv + virtualenvwrapper в Unix/Linux
Pip3 имееться, теперь поставим пакеты:
$ pip3 install -U pyyaml argparse
Открываем файл, например, я его вот так назвал:
$ vim templater_py3.py
Содержание будет следующим:
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import argparse
import time
import os
import pathlib
import glob
import yaml
from jinja2 import Environment, StrictUndefined
from jinja2.loaders import FileSystemLoader
from jinja2.exceptions import UndefinedError, TemplateRuntimeError, TemplateNotFound
class Bgcolors:
def __init__(self):
self.colors = {
'PURPURE': '\033[95m',
'BLUE': '\033[94m',
'GREEN': '\033[92m',
'YELLOW': '\033[93m',
'RED': '\033[91m',
'ENDC': '\033[0m',
'BOLD': '\033[1m',
'UNDERLINE': '\033[4m'
}
def direcroty(in_put, output):
array_files = []
if in_put is not None:
if os.path.exists(os.path.dirname(in_put)):
if os.path.isdir(os.path.dirname(in_put)):
print(Bgcolors().colors['GREEN'] + 'Input is directory: {}'.format(pathlib.Path(in_put.strip())),
Bgcolors().colors['ENDC'])
for file in glob.glob(os.path.join(in_put, '*')):
if os.path.isfile(file):
array_files.append(pathlib.Path(file.strip()))
else:
print('Input is file: {}'.format(pathlib.Path(in_put.strip())))
if os.path.isfile(pathlib.Path(in_put.strip())):
array_files.append(pathlib.Path(in_put.strip()))
else:
print(Bgcolors().colors['RED'] +
'Use correct PATH for configs or check your file: {}'.format(in_put), '\nIt doesnt exist!',
Bgcolors().colors['ENDC'])
exit(0)
else:
print(Bgcolors().colors['RED'] +
'Use correct PATH for configs or check your file: {}'.format(in_put), '\nIt doesnt exist!',
Bgcolors().colors['ENDC'])
exit(0)
else:
print(Bgcolors().colors['RED'], 'Please set [--in_put-file] or [--in_put-dir] or use [--help] to get a help',
Bgcolors().colors['ENDC'])
exit(0)
if output is not None:
if os.path.exists(os.path.dirname(output)):
print(Bgcolors().colors['GREEN'] + 'Output folder: ', pathlib.Path(output.strip()), '\n',
Bgcolors().colors['ENDC'])
else:
try:
os.stat(pathlib.Path(os.path.dirname(output)))
print(Bgcolors().colors['GREEN'] + 'Output folder: ', pathlib.Path(output.strip()), '\n',
Bgcolors().colors['ENDC'])
except Exception:
os.mkdir(output)
print(Bgcolors().colors['GREEN'] + 'Output folder: ', pathlib.Path(output.strip()), '\n',
Bgcolors().colors['ENDC'])
else:
print(Bgcolors().colors['RED'], 'Please set [--output-dir] or use [--help] to get a help',
Bgcolors().colors['ENDC'])
exit(0)
# print(array_files)
return array_files
def jinja_render(in_put, output, env, env_type, yml_files):
array_files = direcroty(in_put, output)
sections = ['default', env, env_type]
# add properties that we know about
config = {'ENVIRONMENT_TYPE': env_type, 'ENVIRONMENT': env}
# Create the jinja2 environment.
# Notice the use of trim_blocks, which greatly helps control whitespace.
j2_env = Environment(
loader=FileSystemLoader(''),
trim_blocks=True,
autoescape=False,
undefined=StrictUndefined)
if yml_files is not None:
for yml_file in yml_files:
with open(yml_file) as stream:
try:
# print(yaml.safe_load(stream))
cfg_yaml = yaml.safe_load(stream.read())
for key in sections:
props = cfg_yaml.get(key)
# print('{0} : {1}'.format(key,props), len(props))
if props is not None:
# print(yaml.dump(props, default_flow_style=False))
config.update(props)
except yaml.YAMLError as exc:
print(exc)
for envriron in os.environ:
config.update({envriron: os.environ.get(envriron)})
# print(envriron, os.environ.get(envriron))
else:
print(Bgcolors().colors['RED'], 'Please set [--yml] or use [--help] to get a help',
Bgcolors().colors['ENDC'])
exit(0)
print(yaml.dump(config, default_flow_style=False))
errs = []
for file in array_files:
# file_name = os.path.basename(file)
head, file_name = os.path.split(file.as_posix())
template = j2_env.get_template(file.as_posix())
try:
out_content = template.render(env=os.environ, **config)
out_file = output + '/' + file_name
try:
f = open(out_file, 'wb')
f.write(out_content.encode('utf-8'))
f.close()
print(Bgcolors().colors['GREEN'] + 'The [{}] file has been written.'.format(out_file)+
Bgcolors().colors['ENDC'])
except ValueError:
print('I cant write to file: ', ValueError)
# print('file:', file,'\n', out_content)
except UndefinedError as e:
if e not in errs:
errs.append(str(e))
# errs.append(str(e).split("'")[1])
if len(errs) > 0:
print('~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~')
print('Total errors: [{}]'.format(len(errs)))
print(errs)
print('~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~')
return jinja_render
def main():
start__time = time.time()
parser = argparse.ArgumentParser(prog='python3 script_name.py -h',
usage='python3 script_name.py {ARGS}',
add_help=True,
prefix_chars='--/',
epilog='''created by Vitalii Natarov''')
parser.add_argument('--version', action='version', version='v0.2.0')
parser.add_argument('--input-file', '--input-dir', dest='input', help='Set input file or folder to find configs',
default='./templates')
parser.add_argument('--output-dir', dest='output', help='Set output a folder for outgoing files',
default='./output')
parser.add_argument('--env', dest='env', help='Environment: tst, sit01, prd, etc', default=None)
parser.add_argument('--env-type', dest='env_type', help='Environment Type: prod or nonprod', default=None)
parser.add_argument('--yml', dest='yml_files', nargs="+", help='Config property yml files', default=None)
results = parser.parse_args()
in_put = os.path.expanduser(results.input)
output = os.path.expanduser(results.output)
env = results.env
env_type = results.env_type
yml_files = results.yml_files
jinja_render(in_put, output, env, env_type, yml_files)
end__time = round(time.time() - start__time, 2)
print("--- %s seconds ---" % end__time)
print(Bgcolors().colors['GREEN'], "============================================================",
Bgcolors().colors['ENDC'])
print(Bgcolors().colors['GREEN'], "==========================FINISHED==========================",
Bgcolors().colors['ENDC'])
print(Bgcolors().colors['GREEN'], "============================================================",
Bgcolors().colors['ENDC'])
if __name__ == '__main__':
main()
Чтобы получить помощь по скрипты, выполните:
$ python3 templater_py3.py -h
usage: python3 script_name.py {ARGS}
optional arguments:
-h, --help show this help message and exit
--version show program's version number and exit
--input-file INPUT, --input-dir INPUT
Set input file or folder to find configs
--output-dir OUTPUT Set output a folder for outgoing files
--env ENV Environment: tst, sit01, prd, etc
--env-type ENV_TYPE Environment Type: prod or nonprod
--yml YML_FILES [YML_FILES ...]
Config property yml files
created by Vitalii Natarov
Где:
- —input-file или —input-dir — Входные данные (папка или файл) для обработки jinja-ей. Т.е чтобы джинжа заменила все вхождения в конфиге или других файлах.
- —output-dir — Выходные данные будут складываться в данную папку.
- —env — Имя окружающей среды (service1, service2).
- —env-type — Тип окружающей среды (prod, nonprod, dev).
- —yml — Ямл файл со всеми настройками для всех типов ENV и ENV в целом.
Я приведу пример использования данного скрипта:
$ python3 templater_py3.py --input-dir=templates --yml configProps.yml --env=test --env-type=nonprod --ouput-dir=output
При необходимости, скрипт понимает что нужно создать output папку, по этому, ее не обязательно создавать.
Данный скрипт тестировался на:
- MacOS с python 3.7.
- CentOS 7 с python 3.4, 3.5.
Немного позже, выложу данный скрипт на Github и обновлю статью.
Вот и все, статья «Python3 скрипт для конструктора конфиг-файлов с Jinja в Unix/Linux» завершена.