1
1
import copy
2
+ import functools
2
3
import json
4
+ import os
3
5
import pathlib
4
- from pkg_resources import resource_filename
5
6
import socket
6
7
import subprocess as sp
7
8
import uuid
8
9
9
10
from dcor_shared .paths import get_ckan_config_option , get_ckan_config_path
10
11
from dcor_shared .parse import ConfigOptionNotFoundError , parse_ini_config
11
12
13
+ from ..resources import resource_location
12
14
from .. import util
13
15
14
16
from . import common
@@ -33,25 +35,21 @@ def check_ckan_beaker_session_cookie_secret(autocorrect=False):
33
35
return did_something
34
36
35
37
36
- def check_ckan_ini (autocorrect = False ):
38
+ def check_ckan_ini (dcor_site_config_dir = None , autocorrect = False ):
37
39
"""Check custom ckan.ini server options
38
40
39
41
This includes the contributions from
40
- - general options from resources/dcor_options .ini
42
+ - general options from resources/dcor_defaults .ini
41
43
- as well as custom options in resources/server_options.json
42
44
43
45
Custom options override general options.
44
46
"""
45
47
did_something = 0
46
- custom_opts = get_expected_ckan_options ()["ckan.ini" ]
47
- general_opts = parse_ini_config (
48
- resource_filename ("dcor_control.resources" , "dcor_options.ini" ))
48
+ dcor_opts = get_expected_site_options (dcor_site_config_dir )["ckan.ini" ]
49
49
50
- general_opts .update (custom_opts )
51
-
52
- for key in general_opts :
50
+ for key in dcor_opts :
53
51
did_something += check_ckan_ini_option (
54
- key , general_opts [key ], autocorrect = autocorrect )
52
+ key , dcor_opts [key ], autocorrect = autocorrect )
55
53
56
54
return did_something
57
55
@@ -147,7 +145,7 @@ def check_dcor_theme_main_css(autocorrect):
147
145
did_something = 0
148
146
ckan_ini = get_ckan_config_path ()
149
147
opt = get_actual_ckan_option ("ckan.theme" )
150
- # TODO: Check whether the paths created by this script are setup correctly
148
+ # TODO: Check whether the paths created by this script are set up correctly
151
149
if opt != "dcor_theme_main/dcor_theme_main" :
152
150
if autocorrect :
153
151
print ("Applying DCOR theme main css" )
@@ -175,107 +173,103 @@ def get_actual_ckan_option(key):
175
173
return opt
176
174
177
175
178
- def get_expected_ckan_options ():
179
- """Return expected ckan.ini options for the current host"""
180
- # Load the json data
181
- opt_path = resource_filename ("dcor_control.resources" ,
182
- "server_options.json" )
183
- with open (opt_path ) as fd :
184
- opt_dict = json .load (fd )
185
- # Determine which server we are on
186
- my_hostname = socket .gethostname ()
187
- my_ip = get_ip ()
176
+ def get_dcor_site_config_dir (dcor_site_config_dir = None ):
177
+ """Return a local directory on disk containing the site's configuration
188
178
189
- cands = []
190
- for setup in opt_dict ["setups" ]:
191
- req = setup ["requirements" ]
192
- ip = req .get ("ip" , "" )
193
- if ip == "unknown" :
194
- # The IP is unknown for this server.
195
- ip = my_ip
196
- hostname = req .get ("hostname" , "" )
197
- if ip == my_ip and hostname == my_hostname :
198
- # perfect match
199
- cands = [setup ]
200
- break
201
- elif ip or hostname :
202
- # no match
203
- continue
204
- else :
205
- # fallback setup
206
- cands .append (setup )
207
- if len (cands ) == 0 :
208
- raise ValueError ("No fallback setups?" )
209
- if len (cands ) != 1 :
210
- names = [setup ["name" ] for setup in cands ]
211
- custom_message = "Valid setup-identifiers: {}" .format (
212
- ", " .join (names ))
213
- for _ in range (3 ):
214
- sn = util .get_dcor_control_config ("setup-identifier" ,
215
- custom_message )
216
- if sn is not None :
179
+ The configuration directory is searched for in the following order:
180
+
181
+ 1. Path passed in dcor_site_config_dir
182
+ 2. Environment variable `DCOR_SITE_CONFIG_DIR`
183
+ 3. Matching sites in the `dcor_control.resources` directory
184
+ """
185
+ if dcor_site_config_dir is not None :
186
+ # passed via argument
187
+ pass
188
+ elif (env_cfg_dir := os .environ .get ("DCOR_SITE_CONFIG_DIR" )) is not None :
189
+ # environment variable
190
+ dcor_site_config_dir = env_cfg_dir
191
+ else :
192
+ # search registered sites
193
+ for site_dir in sorted (resource_location .glob ("site_dcor-*" )):
194
+ if is_site_config_dir_applicable (site_dir ):
195
+ dcor_site_config_dir = site_dir
217
196
break
218
197
else :
219
- raise ValueError ("Could not get setup-identifier (tried 3 times)!" )
220
- setup = cands [names .index (sn )]
221
- else :
222
- setup = cands [0 ]
198
+ raise ValueError (
199
+ "Could not determine the DCOR site configuration. Please "
200
+ "specify the `dcor_site_config_dir` keyword argument or "
201
+ "set the `DCOR_SITE_CONFIG_DIR` environment variable." )
202
+ if not is_site_config_dir_applicable (dcor_site_config_dir ):
203
+ raise ValueError (
204
+ f"The site configuration directory '{ dcor_site_config_dir } ' is "
205
+ f"not applicable. Please check hostname and IP address." )
206
+
207
+ return dcor_site_config_dir
208
+
209
+
210
+ def get_expected_site_options (dcor_site_config_dir ):
211
+ """Return expected site config options for the specified site
212
+
213
+ Returns a dictionary with "name", "requirements", and "ckan.ini".
214
+ """
215
+ dcor_site_config_dir = get_dcor_site_config_dir (dcor_site_config_dir )
216
+ cfg = json .loads ((dcor_site_config_dir / "dcor_config.json" ).read_text ())
217
+ cfg ["dcor_site_config_dir" ] = dcor_site_config_dir
218
+ # Store the information into permanent storage. We might reuse it.
219
+ util .set_dcor_control_config ("setup-identifier" , cfg ["name" ])
220
+ util .set_dcor_control_config ("dcor-site-config-dir" ,
221
+ str (dcor_site_config_dir ))
222
+
223
+ # Import DCOR default ckan.ini variables
224
+ cfg_d = parse_ini_config (resource_location / "dcor_defaults.ini.template" )
225
+ for key , value in cfg_d .items ():
226
+ cfg ["ckan.ini" ].setdefault (key , value )
223
227
224
- # Populate with includes
225
- for inc_key in setup ["include" ]:
226
- common .recursive_update_dict (setup , opt_dict ["includes" ][inc_key ])
227
228
# Fill in template variables
228
- update_expected_ckan_options_templates (setup )
229
- # Fill in branding variables
230
- update_expected_ckan_options_branding (setup )
231
- return setup
232
-
233
-
234
- def update_expected_ckan_options_branding (ini_dict ):
235
- """Update dict with templates and public paths according to branding"""
236
- brands = ini_dict ["branding" ]
237
- # Please not the dcor_control must be an installed package for
238
- # this to work (no egg or somesuch).
239
- templt_paths = []
240
- public_paths = []
241
- for brand in brands :
242
- template_dir = resource_filename ("dcor_control.resources.branding" ,
243
- "templates_{}" .format (brand ))
244
- if pathlib .Path (template_dir ).exists ():
245
- templt_paths .append (template_dir )
246
- public_dir = resource_filename ("dcor_control.resources.branding" ,
247
- "public_{}" .format (brand ))
248
- if pathlib .Path (public_dir ).exists ():
249
- public_paths .append (public_dir )
250
- if templt_paths :
251
- ini_dict ["ckan.ini" ]["extra_template_paths" ] = ", " .join (templt_paths )
252
- if public_paths :
253
- ini_dict ["ckan.ini" ]["extra_public_paths" ] = ", " .join (public_paths )
254
-
255
-
256
- def update_expected_ckan_options_templates (ini_dict ):
229
+ update_expected_ckan_options_templates (cfg )
230
+
231
+ return cfg
232
+
233
+
234
+ @functools .lru_cache ()
235
+ def is_site_config_dir_applicable (dcor_site_config_dir ):
236
+ cfg = json .loads ((dcor_site_config_dir / "dcor_config.json" ).read_text ())
237
+ # Determine which server we are on
238
+ my_hostname = socket .gethostname ()
239
+ my_ip = get_ip ()
240
+
241
+ req = cfg ["requirements" ]
242
+ ip = req .get ("ip" , "" )
243
+ if ip == "unknown" :
244
+ # The IP is unknown for this server.
245
+ ip = my_ip
246
+ hostname = req .get ("hostname" , "" )
247
+ return ip == my_ip and hostname == my_hostname
248
+
249
+
250
+ def update_expected_ckan_options_templates (cfg_dict , templates = None ):
257
251
"""Update dict with templates in server_options.json"""
258
- templates = {
259
- "IP" : [ get_ip , []],
260
- "EMAIL " : [util . get_dcor_control_config , ["email" ]],
261
- "PGSQLPASS " : [util .get_dcor_control_config , ["pgsqlpass " ]],
262
- "HOSTNAME " : [socket . gethostname , []],
263
- "PATH_BRANDING " : [resource_filename , ["dcor_control.resources" ,
264
- "branding " ]],
265
- }
266
-
267
- for key in sorted (ini_dict .keys ()):
268
- item = ini_dict [key ]
252
+ if templates is None :
253
+ templates = {
254
+ "IP " : [get_ip , []],
255
+ "EMAIL " : [util .get_dcor_control_config , ["email " ]],
256
+ "PGSQLPASS " : [util . get_dcor_control_config , ["pgsqlpass" ]],
257
+ "HOSTNAME " : [socket . gethostname , []] ,
258
+ "DCOR_SITE_CONFIG_DIR" : [ cfg_dict . get , [ "dcor_site_config_dir " ]],
259
+ }
260
+
261
+ for key in sorted (cfg_dict .keys ()):
262
+ item = cfg_dict [key ]
269
263
if isinstance (item , str ):
270
264
for tk in templates :
271
265
tstr = "<TEMPLATE:{}>" .format (tk )
272
266
if item .count (tstr ):
273
267
func , args = templates [tk ]
274
- item = item .replace (tstr , func (* args ))
275
- ini_dict [key ] = item
268
+ item = item .replace (tstr , str ( func (* args ) ))
269
+ cfg_dict [key ] = item
276
270
elif isinstance (item , dict ):
277
271
# recurse into nested dicts
278
- update_expected_ckan_options_templates (item )
272
+ update_expected_ckan_options_templates (item , templates = templates )
279
273
280
274
281
275
def get_ip ():
0 commit comments