-
Notifications
You must be signed in to change notification settings - Fork 1
/
rtpl
executable file
·151 lines (127 loc) · 5.42 KB
/
rtpl
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
#!/bin/env python3
import glob
import logging
import json
import retemplate
import sys
import yaml
from argparse import ArgumentParser
from threading import Thread
'''
This file serves as an entry point to the module's features, and is the recommended method of
executing templates.
'''
all_configs = list()
def parseargs():
'''
Interpets command line arguments
'''
parser = ArgumentParser(description='Schedule file templating')
parser.add_argument('-c', '--config',
help='Config file to read in',
default='config.yml')
parser.add_argument('-l', '--logfile',
help='The file to direct log output to (see readme for details)',
default=None)
parser.add_argument('-v', '--values',
help='Log all values retrieved from data stores',
default=False,
action='store_true')
return parser.parse_args()
def read_config(path):
try:
with open(path, 'r') as fh:
return yaml.load(fh.read(), Loader=yaml.FullLoader)
except IOError:
logging.error('Could not read config file: {}'.format(args.config))
return None
def get_configs(includes):
if len(includes) > 0:
for include in includes:
files = glob.glob(include)
for file in files:
config = read_config(file)
if config:
all_configs.append(config)
if 'retemplate' in config and 'include' in config['retemplate']:
get_configs(config['retemplate']['include'])
def deep_merge(d, u):
for k, v in u.items():
if isinstance(v, dict):
d[k] = deep_merge(d.get(k, {}), v)
else:
d[k] = v
return d
def merge_configs(base_config):
# logging.info('Base config: {}'.format(json.dumps(base_config, indent=2)))
for config in all_configs:
# logging.info('Merging in config: {}'.format(json.dumps(config, indent=2)))
base_config = deep_merge(base_config, config)
# logging.info('Base config after merge: {}'.format(json.dumps(base_config, indent=2)))
if 'retemplate' in base_config and 'include' in base_config['retemplate']:
del(base_config['retemplate']['include'])
return base_config
def main():
'''
Main entry point to the rtpl utility. This handles configuration of the application before
launching Retemplate objects as simultaneous threads.
'''
args = parseargs()
# First set up basic logging so we can emit events before the user's custom configs can be read
logcfg = {
'level': 'INFO',
'filename': args.logfile,
'format': '%(levelname)s %(asctime)s %(message)s',
'style': '%'
}
# Forcible reconfiguration of live logging is only available after Python 3.8
if sys.version_info.minor >= 8:
logcfg['force'] = True
logging.basicConfig(**logcfg)
logging.info('Starting up Retemplate')
stores = dict()
templates = list()
threads = list()
# Parse config files
base_config = read_config(args.config)
if base_config and 'retemplate' in base_config and 'include' in base_config['retemplate']:
get_configs(base_config['retemplate']['include'])
config = merge_configs(base_config)
# Update logging config based on user configs if Python allows it
if 'retemplate' in config and 'logging' in config['retemplate'] and sys.version_info.minor >= 8:
logcfg.update(config['retemplate']['logging'])
logging.basicConfig(**logcfg)
logging.debug('Logging configured')
logging.info('Retemplate loaded with this configuration:\n{}'.format(json.dumps(config, indent=2)))
# Configure DataStores
for store in config['stores']:
if config['stores'][store]['type'] == 'aws-local-meta':
stores[store] = retemplate.AwsLocalMetadataServer(store, **config['stores'][store])
if config['stores'][store]['type'] == 'aws-secrets-manager':
stores[store] = retemplate.AwsSecretsManagerStore(store, **config['stores'][store])
if config['stores'][store]['type'] == 'aws-systems-manager':
stores[store] = retemplate.AwsSystemsManagerStore(store, **config['stores'][store])
if config['stores'][store]['type'] == 'local-exec':
stores[store] = retemplate.LocalExecutionStore(store, **config['stores'][store])
if config['stores'][store]['type'] == 'redis':
stores[store] = retemplate.RedisStore(store, **config['stores'][store])
logging.debug('Configured data store {}'.format(store))
# Configure templates
for target in config['templates']:
template = config['templates'][target]['template']
del(config['templates'][target]['template'])
tpl = retemplate.Retemplate(target, template, stores, args.values, **config['templates'][target])
templates.append(tpl)
logging.debug('Configured template for target {}'.format(target))
# Run each template as a thread
for tpl in templates:
thread = Thread(target=tpl.run)
threads.append(thread)
thread.start()
# Wait for all threads to finish before exiting
# (They should only ever finish if they error out, since they run in infinite loops)
for thread in threads:
thread.join()
logging.info('Shutting down Retemplate')
if __name__ == '__main__':
main()