-
Notifications
You must be signed in to change notification settings - Fork 4
/
amp_hook_config.py
executable file
·207 lines (186 loc) · 10.4 KB
/
amp_hook_config.py
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
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
#!/bin/env python3
# This script will be run when the AMP system is reconfigured. It will
# write the configuration files that amppd needs, driven by the amp
# configuration file.
#
# No arguments, but the AMP_ROOT and AMP_DATA_ROOT environment variables
# will be set by the caller so it can find all things AMP.
import argparse
import logging
from pathlib import Path
import os
from amp.config import load_amp_config
from amp.logging import setup_logging
import random
import yaml
def main():
parser = argparse.ArgumentParser()
parser.add_argument('--debug', default=False, action='store_true', help="Turn on debugging")
args = parser.parse_args()
# set up the standard logging
setup_logging(None, args.debug)
# grab the configuration file
config = load_amp_config()
# set amp_root
amp_root = Path(os.environ['AMP_ROOT'])
# These two things used to get carried over when the config was running
# from within the amp_control.py script. Need to recompute these...
#"galaxy.port": (['amp', 'galaxy_port'], None), # set during galaxy config generation
#"galaxy.userId": (['galaxy', "user_id"], None), # set during galaxy config generation
if 'galaxy_port' not in config['amp']:
config['amp']['galaxy_port'] = int(config['amp']['port']) + 2
if 'user_id' not in config['galaxy']:
pass
# If the encryption_secret or the jwt_secret isn't set, set it to a sufficiently
# random value and then reload the configuration.
for sec in ('encryption_secret', 'jwt_secret'):
if config['rest'].get(sec, None) in (None, "CHANGE ME"):
logging.info(f"Generating a new {sec}")
secret = "".join(random.choices("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789", k=16))
rdata = {'rest': {sec: secret}}
with open(amp_root / f"data/package_config/rest__{sec}.yaml", "w") as x:
yaml.safe_dump(rdata, x)
config = load_amp_config()
# inject the amp.data_root value into the config since it is now passed via AMP_DATA_ROOT
data_root = config['amp']['data_root'] = os.environ['AMP_DATA_ROOT']
config['amp']['amp_root'] = amp_root
"""Create the configuration file for the AMP REST service"""
# make sure the configuration file is specified in the tomcat startup env stuff:
# JAVA_OPTS: -Dspring.config.location=/path/to/config.properties
if not (amp_root / "tomcat/bin/setenv.sh").exists():
with open(amp_root / "tomcat/bin/setenv.sh", "w") as o:
o.write(f'JAVA_OPTS="$JAVA_OPTS -Dspring.config.location={amp_root / "data/config/application.properties"!s}"\n')
else:
(amp_root / "tomcat/bin/setenv.sh").rename(amp_root / "tomcat/bin/setenv.sh.bak")
with open(amp_root / "tomcat/bin/setenv.sh.bak") as i:
with open(amp_root / "tomcat/bin/setenv.sh", "w") as o:
for l in i.readlines():
if 'spring.config.location' in l:
pass
elif l == '':
pass
else:
o.write(l)
o.write('\n')
o.write(f'JAVA_OPTS="$JAVA_OPTS -Dspring.config.location={amp_root / "data/config/application.properties"!s}"\n')
# create the configuration file, based on config data...
# in the property map, the key is the name of the key in the properties file
# the value is a tuple with these fields:
# - a list of keys to walk in the configuration to find the value (req)
# - the default value (req)
# - the transformation function (opt) I think only path rel is implemented
# - for path_rel, the list of keys to walk for the relative directory value (opt)
# the list of keys is equivalent to dot-notation in javascript using the
# amp configuration as the data structure. Computed configuration can be
# obtained by running ./amp_control.py configure --dump
with open(amp_root / "data/config/application.properties", "w") as f:
# simple property map
property_map = {
# server port and root
'server.port': (['amp', 'port'], None),
# database creds (host/db/port is handled elsewhere)
'spring.datasource.username': (['rest', 'db_user'], None),
'spring.datasource.password': (['rest', 'db_pass'], None),
# initial user
'amppd.username': (['rest', 'admin_username'], None),
'amppd.password': (['rest', 'admin_password'], None),
'amppd.adminEmail': (['rest', 'admin_email'], None),
# galaxy integration
"galaxy.host": (['galaxy', 'host'], 'localhost'),
"galaxy.root": (['galaxy', 'root'], None),
"galaxy.username": (['galaxy', 'admin_username'], None),
"galaxy.password": (['galaxy', 'admin_password'], None),
"galaxy.port": (['amp', 'galaxy_port'], None), # set during galaxy config generation
"galaxy.userId": (['galaxy', "user_id"], None), # set during galaxy config generation
# AMPUI properties
'amppdui.hmgmSecretKey': (['mgms', 'hmgm', 'auth_key'], None),
# Directories
'logging.path': (['rest', 'logging_path'], 'logs', 'path_rel', ['amp', 'data_root']),
'amppd.fileStorageRoot': (['rest', 'storage_path'], 'media', 'path_rel', ['amp', 'data_root']),
'amppd.dropboxRoot': (['rest', 'dropbox_path'], 'dropbox', 'path_rel', ['amp', 'data_root']),
'amppd.mediaprobeDir': (['rest', 'mediaprobe_dir'], 'MediaProbe', 'path_rel', ['amp', 'data_root']),
'amppd.mgmEvaluationResultsRoot': (['rest', 'mgm_evaluation_results_root'], 'evaluation', 'path_rel', ['amp', 'data_root']),
'amppd.mgmEvaluationScriptsRoot': (['rest', 'mgm_evaluation_scripts_root'], 'mgm_scoring_tools', 'path_rel', ['amp', 'amp_root']),
# Avalon integration
"avalon.url": (['rest', 'avalon_url'], 'https://avalon.example.edu'),
"avalon.token": (['rest', 'avalon_token'], 'dummytoken'),
# secrets
'amppd.encryptionSecret': (['rest', 'encryption_secret'], None),
'amppd.jwtSecret': (['rest', 'jwt_secret'], None),
}
def resolve_list(data, path, default=None):
# given a data structure and a path, walk it and return the value
if len(path) == 1:
logging.debug(f"Base case: {data}, {path}, {default}")
return data.get(path[0], default)
else:
v = data.get(path[0], None)
logging.debug(f"Lookup: {data}, {path}, {default} = {v}")
if v is None or not isinstance(v, dict):
logging.debug("Returning the default")
return default
else:
logging.debug(f"Recurse: {v}, {path[1:]}, {default}")
return resolve_list(v, path[1:], default)
# create the configuration
for key, val in property_map.items():
if isinstance(val, str):
# this is a constant, just write it.
f.write(f"{key} = {val}\n")
elif isinstance(val, tuple):
# every section starts with a reference list
logging.debug(f"Looking up {key} {val}")
v = resolve_list(config, val[0], val[1])
if v is None:
logging.error(f"Error setting {key}: Section {val[0]} doesn't exist in the configuration")
continue
if len(val) < 3:
# write it.
if isinstance(v, bool):
f.write(f"{key} = {'true' if v else 'false'}\n")
else:
f.write(f"{key} = {v}\n")
else:
# there's a function to be called.
if val[2] == 'path_rel':
if Path(v).is_absolute():
f.write(f"{key} = {v}\n")
else:
r = resolve_list(config, val[3], None)
if r is None:
logging.error(f"Error setting {key}: Section {val[3]} doesn't exist in the configuration")
continue
this_path = None
if Path(r).is_absolute():
this_path = Path(r, v)
else:
this_path = Path(amp_root, r, v)
f.write(f"{key} = {this_path!s}\n")
# create the directory if we need to (need the check because it may be symlink)
if not this_path.exists():
this_path.mkdir(exist_ok=True)
else:
logging.error(f"Error handling {key}: special action {val[2]} not supported")
# these are things which are "hard" and can't be done through the generic mechanism.
# datasource configuration
f.write(f"spring.datasource.url = jdbc:postgresql://{config['rest']['db_host']}:{config['rest'].get('db_port', 5432)}/{config['rest']['db_name']}\n")
# amppdui.url and amppd.url -- where we can find the UI and ourselves.
if config['amp'].get('https', False):
f.write(f"amppdui.url = https://{config['amp']['host']}/#\n")
f.write(f"amppd.url = https://{config['amp']['host']}/rest\n")
else:
f.write(f"amppdui.url = http://{config['amp']['host']}:{config['amp']['port']}/#\n")
f.write(f"amppd.url = http://{config['amp']['host']}:{config['amp']['port']}/rest\n")
# amppdui.documentRoot -- this should be somewhere in the tomcat tree.
f.write(f"amppdui.documentRoot = {amp_root}/tomcat/webapps/ROOT\n")
# amppd.dataRoot is data_root, and synlinkDir is symlinks, as defined in tomcat config
f.write(f"amppd.dataRoot = {data_root}\n")
f.write(f"amppd.symlinkDir = symlinks\n")
f.write("# boilerplate properties\n")
for k,v in config['rest']['properties'].items():
if isinstance(v, bool):
f.write(f"{k} = {'true' if v else 'false'}\n")
else:
f.write(f"{k} = {v}\n")
if __name__ == "__main__":
main()