瀏覽代碼

First SLIM version

Able to :
- create/delete instances
- list instances
- run make %target% in all instances
- edit configuration given an instance name

refs #145
Yann Weber 8 年之前
父節點
當前提交
4037a0c75f

+ 1
- 0
progs/instances.json 查看文件

@@ -0,0 +1 @@
1
+{"test1": {"path": "/tmp/lodel2_instances/test1"}}

+ 219
- 0
progs/slim.py 查看文件

@@ -0,0 +1,219 @@
1
+#!/usr/bin/env python3
2
+#-*- coding: utf-8 -*-
3
+
4
+
5
+INSTANCES_ABSPATH="/tmp/lodel2_instances"
6
+STORE_FILE='./instances.json'
7
+CREATION_SCRIPT='../scripts/create_instance.sh'
8
+INSTALL_TPL = './slim_ressources/slim_install_model'
9
+EMFILE = './slim_ressources/emfile.pickle'
10
+
11
+import os, os.path
12
+import sys
13
+import shutil
14
+import argparse
15
+import logging
16
+import json
17
+
18
+logging.basicConfig(level=logging.INFO)
19
+
20
+CREATION_SCRIPT=os.path.join(os.path.dirname(__file__), CREATION_SCRIPT)
21
+STORE_FILE=os.path.join(os.path.dirname(__file__), STORE_FILE)
22
+INSTALL_TPL=os.path.join(os.path.dirname(__file__), INSTALL_TPL)
23
+EMFILE=os.path.join(os.path.dirname(__file__), EMFILE)
24
+
25
+
26
+#STORE_FILE syntax :
27
+#
28
+#First level keys are instances names, their values are dict with following
29
+#informations : 
30
+# - path
31
+#
32
+
33
+##@brief Run 'make %target%' for each instances given in names
34
+#@param target str : make target
35
+#@param names list : list of instance name
36
+def run_make(target, names):
37
+    validate_names(names)
38
+    store_datas = get_store_datas()
39
+    cwd = os.getcwd()
40
+    for name in [n for n in store_datas if n in names]:
41
+        datas = store_datas[name]
42
+        logging.info("Running 'make %s' for '%s' in %s" % (
43
+            target, name, datas['path']))
44
+        os.chdir(datas['path'])
45
+        os.system('make %s' % target)
46
+    os.chdir(cwd)
47
+            
48
+
49
+##@brief If the name is not valid raise
50
+def name_is_valid(name):
51
+    allowed_chars = [chr(i) for i in range(ord('a'), ord('z')+1)]
52
+    allowed_chars += [chr(i) for i in range(ord('A'), ord('Z')+1)]
53
+    allowed_chars += [chr(i) for i in range(ord('0'), ord('9')+1)]
54
+    allowed_chars += ['_']
55
+    for c in name:
56
+        if c not in allowed_chars:
57
+            raise RuntimeError("Allowed characters for instance name are \
58
+lower&upper alphanum and '_'. Name '%s' is invalid" % name)
59
+
60
+##@brief Create a new instance
61
+#@param name str : the instance name
62
+def new_instance(name):
63
+    name_is_valid(name)
64
+    store_datas = get_store_datas()
65
+    if name in store_datas:
66
+        logging.error("An instance named '%s' already exists" % name)
67
+        exit(1)
68
+    if not os.path.isdir(INSTANCES_ABSPATH):
69
+        logging.info("Instances directory '%s' don't exists, creating it")
70
+        os.mkdir(INSTANCES_ABSPATH)
71
+    instance_path = os.path.join(INSTANCES_ABSPATH, name)
72
+    creation_cmd = '{script} "{name}" "{path}" "{install_tpl}" \
73
+"{emfile}"'.format(
74
+        script = CREATION_SCRIPT,
75
+        name = name,
76
+        path = instance_path,
77
+        install_tpl = INSTALL_TPL,
78
+        emfile = EMFILE)
79
+    res = os.system(creation_cmd)
80
+    if res != 0:
81
+        logging.error("Creation script fails")
82
+        exit(res)
83
+    #storing new instance
84
+    store_datas[name] = {'path': instance_path}
85
+    save_datas(store_datas)
86
+
87
+##@brief Delete an instance
88
+#@param name str : the instance name
89
+def delete_instance(name):
90
+    store_datas = get_store_datas()
91
+    logging.warning("Deleting instance %s" % name)
92
+    logging.info("Deleting instance folder %s" % store_datas[name]['path'])
93
+    shutil.rmtree(store_datas[name]['path'])
94
+    logging.info("Deleting instance from json store file")
95
+    del(store_datas[name])
96
+    save_datas(store_datas)
97
+
98
+##@brief returns stored datas
99
+def get_store_datas():
100
+    if not os.path.isfile(STORE_FILE):
101
+        return dict()
102
+    else:
103
+        with open(STORE_FILE, 'r') as sfp:
104
+            datas = json.load(sfp)
105
+    return datas
106
+
107
+##@brief Checks names validity and exit if fails
108
+def validate_names(names):
109
+    store_datas = get_store_datas()
110
+    invalid = [ n for n in names if n not in store_datas]
111
+    if len(invalid) > 0:
112
+        print("Following names are not existing instance :", file=sys.stderr)
113
+        for name in invalid:
114
+            print("\t%s" % name, file=sys.stderr)
115
+        exit(1)
116
+
117
+##@brief Check if instance are specified
118
+def get_specified(args):
119
+    if args.all:
120
+        names = list(get_store_datas().keys())
121
+    elif args.name is not None:
122
+        names = args.name
123
+    else:
124
+        names = None
125
+    return names
126
+
127
+##@brief Saves store datas
128
+def save_datas(datas):
129
+    with open(STORE_FILE, 'w+') as sfp:
130
+        json.dump(datas, sfp)
131
+
132
+##@brief Print the list of instances and exit
133
+def list_instances(verbosity):
134
+    if not os.path.isfile(STORE_FILE):
135
+        print("No store file, no instances are existing. Exiting...",
136
+            file=sys.stderr)
137
+        exit(0)
138
+    print('Instances list :')
139
+    exit(0)
140
+
141
+##@brief Returns instanciated parser
142
+def get_parser():
143
+    parser = argparse.ArgumentParser(
144
+        description='SLIM (Simple Lodel Instance Manager.)')
145
+    selector = parser.add_argument_group('Instances selectors')
146
+    actions = parser.add_argument_group('Instances actions')
147
+    parser.add_argument('-l', '--list', 
148
+        help='list existing instances and exit', action='store_const',
149
+        const=True, default=False)
150
+    parser.add_argument('-v', '--verbose', action='count')
151
+    actions.add_argument('-c', '--create', action='store_const',
152
+        default=False, const=True,
153
+        help="Create a new instance with given name (see -n --name)")
154
+    actions.add_argument('-d', '--delete', action='store_const',
155
+        default=False, const=True,
156
+        help="Delete an instance with given name (see -n --name)")
157
+    actions.add_argument('-e', '--edit-config', action='store_const',
158
+        default=False, const=True,
159
+        help='Edit configuration of specified instance')
160
+    selector.add_argument('-a', '--all', action='store_const',
161
+        default=False, const=True,
162
+        help='Select all instances')
163
+    selector.add_argument('-n', '--name', metavar='NAME', type=str, nargs='*',
164
+        help="Specify an instance name")
165
+    actions.add_argument('--stop', action='store_const', 
166
+        default=False, const=True, help="Stop instances")
167
+    actions.add_argument('--start', action='store_const', 
168
+        default=False, const=True, help="Start instances")
169
+    actions.add_argument('-m', '--make', metavar='TARGET', type=str,
170
+        nargs="?", default='not',
171
+        help='Run make for selected instances')
172
+    return parser
173
+
174
+if __name__ == '__main__':
175
+    parser = get_parser()
176
+    args = parser.parse_args()
177
+    if args.list:
178
+        list_instances(args.verbose)
179
+    elif args.create:
180
+        if args.name is None:
181
+            parser.print_help()
182
+            print("\nAn instance name expected when creating an instance !",
183
+                file=sys.stderr)
184
+            exit(1)
185
+        for name in args.name:
186
+            new_instance(name)
187
+    elif args.delete:
188
+        if args.name is None:
189
+            parser.print_help()
190
+            print("\nAn instance name expected when creating an instance !",
191
+                file=sys.stderr)
192
+            exit(1)
193
+        validate_names(args.name)
194
+        for name in args.name:
195
+            delete_instance(name)
196
+    elif args.make != 'not':
197
+        if args.make is None:
198
+            target = 'all'
199
+        else:
200
+            target = args.make
201
+        names = get_specified(args)
202
+        if names is None:
203
+            parser.print_help()
204
+            print("\nWhen using -m --make options you have to select \
205
+instances, either by name using -n or all using -a")
206
+            exit(1)
207
+        run_make(target, names)
208
+    elif args.edit_config:
209
+        names = get_specified(args)
210
+        if len(names) > 1:
211
+            print("\n-e --edit-config option works only when 1 instance is \
212
+specified")
213
+        validate_names(names)
214
+        name = names[0]
215
+        store_datas = get_store_datas()
216
+        conffile = os.path.join(store_datas[name]['path'], 'conf.d')
217
+        conffile = os.path.join(conffile, 'lodel2.ini')
218
+        os.system('editor "%s"' % conffile)
219
+        exit(0)

二進制
progs/slim_ressources/emfile.pickle 查看文件


+ 15
- 0
progs/slim_ressources/slim_install_model/Makefile 查看文件

@@ -0,0 +1,15 @@
1
+python=python3
2
+
3
+all: dyncode
4
+
5
+dyncode:
6
+	$(python) -c 'import lodel_admin; lodel_admin.refresh_dyncode()'
7
+
8
+init_db: 
9
+	$(python) -c 'import lodel_admin; lodel_admin.init_all_dbs()'
10
+
11
+list_hooks: dyncode
12
+	$(python) -c 'import lodel_admin; lodel_admin.list_registered_hooks()'
13
+
14
+refresh_plugins:
15
+	$(python) -c 'import lodel_admin; lodel_admin.update_plugin_discover_cache()'

+ 34
- 0
progs/slim_ressources/slim_install_model/conf.d/lodel2.ini 查看文件

@@ -0,0 +1,34 @@
1
+[lodel2.datasources.default]
2
+identifier = dummy_datasource.example
3
+
4
+[lodel2.datasources.dummy2]
5
+identifier = dummy_datasource.dummy2
6
+
7
+[lodel2.datasource.dummy_datasource.example]
8
+dummy =
9
+[lodel2.datasource.dummy_datasource.dummy2]
10
+dummy =
11
+[lodel2.section1]
12
+key1 = 42
13
+[lodel2]
14
+debug = False
15
+sitename = noname
16
+
17
+[lodel2.logging.stderr]
18
+level = DEBUG
19
+filename = -
20
+context = True
21
+
22
+[lodel2.editorialmodel]
23
+groups = base_group, editorial_abstract, editorial_person
24
+emfile = editorial_model.pickle
25
+dyncode = leapi_dyncode.py
26
+
27
+[lodel2.webui]
28
+standalone=False
29
+#listen_address=127.0.0.1
30
+#listen_port=9090
31
+[lodel2.webui.sessions]
32
+directory=./sessions
33
+expiration=900
34
+file_template=lodel2_%s.sess

+ 77
- 0
progs/slim_ressources/slim_install_model/loader.py 查看文件

@@ -0,0 +1,77 @@
1
+#-*- coding: utf-8 -*-
2
+
3
+##@brief Lodel2 loader script
4
+#
5
+#@note If you want to avoid settings loading you can set the environment
6
+#variable LODEL2_NO_SETTINGS_LOAD (see @ref install.lodel_admin.update_plugin_discover_cache()
7
+#
8
+
9
+import sys, os, os.path
10
+#
11
+# Bootstraping
12
+#
13
+LODEL2_LIB_ABS_PATH = None
14
+if LODEL2_LIB_ABS_PATH is not None:
15
+    if not os.path.isdir(LODEL2_LIB_ABS_PATH):
16
+        print("FATAL ERROR : the LODEL2_LIB_ABS_PATH variable in loader.py is \
17
+not correct : '%s'" % LODEL2_LIB_ABS_PATH, file=sys.stderr)
18
+    sys.path.append(os.path.dirname(LODEL2_LIB_ABS_PATH))
19
+
20
+try:
21
+    import lodel
22
+except ImportError as e:
23
+    print("Unable to load lodel module. exiting...")
24
+    print(e)
25
+    exit(1)
26
+
27
+if 'LODEL2_NO_SETTINGS_LOAD' not in os.environ:
28
+    #
29
+    # Loading settings
30
+    #
31
+    from lodel.settings.settings import Settings as settings
32
+    if not settings.started():
33
+        settings('conf.d')
34
+    from lodel.settings import Settings
35
+    
36
+    #Starts hooks
37
+    from lodel.plugin import LodelHook
38
+    from lodel.plugin import core_hooks
39
+    from lodel.plugin import core_scripts
40
+
41
+def start():
42
+    #Load plugins
43
+    from lodel import logger
44
+    from lodel.plugin import Plugin
45
+    logger.debug("Loader.start() called")
46
+    Plugin.load_all()
47
+    LodelHook.call_hook('lodel2_bootstraped', '__main__', None)
48
+
49
+
50
+if __name__ == '__main__':
51
+
52
+    start()
53
+    if Settings.runtest:
54
+        import unittest
55
+        import tests
56
+        loader = unittest.TestLoader()
57
+        test_dir = os.path.join(LODEL2_LIB_ABS_PATH, 'tests')
58
+        suite = loader.discover(test_dir)
59
+        runner = unittest.TextTestRunner(
60
+            failfast = '-f' in sys.argv,
61
+            verbosity = 2 if '-v' in sys.argv else 1)
62
+        runner.run(suite)
63
+        exit()
64
+
65
+    import lodel
66
+    import leapi_dyncode as dyncode
67
+    lodel.dyncode = dyncode
68
+    LodelHook.call_hook('lodel2_dyncode_bootstraped', '__main__', None)
69
+    LodelHook.call_hook('lodel2_loader_main', '__main__', None)
70
+
71
+    #Run interative python
72
+    import code
73
+    print("""
74
+     Running interactive python in Lodel2 %s instance environment
75
+
76
+"""%Settings.sitename)
77
+    code.interact(local=locals())

+ 132
- 0
progs/slim_ressources/slim_install_model/lodel_admin.py 查看文件

@@ -0,0 +1,132 @@
1
+#-*- coding: utf-8 -*-
2
+
3
+import sys
4
+import os, os.path
5
+import argparse
6
+
7
+##@brief Dirty hack to avoid problems with simlink to lodel2 lib folder
8
+#
9
+#In instance folder we got a loader.py (the one we want to import here when
10
+#writing "import loader". The problem is that lodel_admin.py is a simlink to
11
+#LODEL2LIB_FOLDER/install/lodel_admin.py . In this folder there is the 
12
+#generic loader.py template. And when writing "import loader" its 
13
+#LODEL2LIB_FOLDER/install/loader.py that gets imported.
14
+#
15
+#In order to solve this problem the _simlink_hack() function delete the
16
+#LODEL2LIB_FOLDER/install entry from sys.path
17
+#@note This problem will be solved once lodel lib dir will be in 
18
+#/usr/lib/python3/lodel
19
+def _simlink_hack():
20
+    sys.path[0] = os.getcwd()
21
+
22
+## @brief Utility method to generate python code given an emfile and a
23
+# translator
24
+# @param model_file str : An em file
25
+# @param translator str : a translator name
26
+# @return python code as string
27
+def generate_dyncode(model_file, translator):
28
+    from lodel.editorial_model.model import EditorialModel
29
+    from lodel.leapi import lefactory
30
+
31
+    model = EditorialModel.load(translator, filename  = model_file)
32
+    dyncode = lefactory.dyncode_from_em(model)
33
+    return dyncode
34
+
35
+## @brief Utility method to generate a python file representing leapi dyncode
36
+# given an em file and the associated translator name
37
+#
38
+# @param model_file str : An em file
39
+# @param translator str : a translator name
40
+# @param output_filename str : the output file
41
+def create_dyncode(model_file, translator, output_filename):
42
+    from lodel import logger
43
+    dyncode = generate_dyncode(model_file, translator)
44
+    with open(output_filename, 'w+') as out_fd:
45
+        out_fd.write(dyncode)
46
+    out_fd.close()
47
+    logger.info("Dynamic leapi code written in %s", output_filename)
48
+
49
+
50
+## @brief Refresh dynamic leapi code from settings
51
+def refresh_dyncode():
52
+    import loader
53
+    from lodel.settings import Settings
54
+    # EditorialModel update/refresh
55
+    
56
+    # TODO
57
+
58
+    # Dyncode refresh
59
+    create_dyncode( Settings.editorialmodel.emfile,
60
+                    Settings.editorialmodel.emtranslator,
61
+                    Settings.editorialmodel.dyncode)
62
+
63
+
64
+def init_all_dbs():
65
+    import loader
66
+    loader.start()
67
+    import leapi_dyncode as dyncode
68
+    from lodel.plugin.datasource_plugin import DatasourcePlugin
69
+    from lodel.settings.utils import SettingsError
70
+    from lodel.leapi.leobject import LeObject
71
+    from lodel.plugin import Plugin
72
+    from lodel import logger
73
+
74
+    ds_cls = dict() # EmClass indexed by rw_datasource
75
+    for cls in dyncode.dynclasses:
76
+        ds = cls._datasource_name
77
+        if ds not in ds_cls:
78
+            ds_cls[ds] = [cls]
79
+        else:
80
+            ds_cls[ds].append(cls)
81
+    
82
+    for ds_name in ds_cls:
83
+        mh = DatasourcePlugin.init_migration_handler(ds_name)
84
+        #Retrieve plugin_name & ds_identifier for logging purpose
85
+        plugin_name, ds_identifier = DatasourcePlugin.plugin_name(
86
+            ds_name, False)
87
+        try:
88
+            mh.init_db(ds_cls[ds_name])
89
+        except Exception as e:
90
+            msg = "Migration failed for datasource %s(%s.%s) when running \
91
+init_db method: %s"
92
+            msg %= (ds_name, plugin_name, ds_identifier, e)
93
+        logger.info("Database initialisation done for %s(%s.%s)" % (
94
+            ds_name, plugin_name, ds_identifier))
95
+
96
+def list_registered_hooks():
97
+    import loader
98
+    loader.start()
99
+    from lodel.plugin.hooks import LodelHook
100
+    hlist = LodelHook.hook_list()
101
+    print("Registered hooks are : ")
102
+    for name in sorted(hlist.keys()):
103
+        print("\t- %s is registered by : " % name)
104
+        for reg_hook in hlist[name]:
105
+            hook, priority = reg_hook
106
+            msg = "\t\t- {modname}.{funname} with priority : {priority}"
107
+            msg = msg.format(
108
+                modname = hook.__module__,
109
+                funname = hook.__name__,
110
+                priority = priority)
111
+            print(msg)
112
+        print("\n")
113
+
114
+##@brief update plugin's discover cache
115
+#@note impossible to give arguments from a Makefile...
116
+#@todo write a __main__ to be able to run ./lodel_admin
117
+def update_plugin_discover_cache(path_list = None):
118
+    os.environ['LODEL2_NO_SETTINGS_LOAD'] = 'True'
119
+    import loader
120
+    from lodel.plugin.plugins import Plugin
121
+    res = Plugin.discover(path_list)
122
+    print("Plugin discover result in %s :\n" % res['path_list'])
123
+    for pname, pinfos in res['plugins'].items():
124
+        print("\t- %s %s -> %s" % (
125
+            pname, pinfos['version'], pinfos['path']))
126
+
127
+if __name__ == '__main__':
128
+    _simlink_hack()
129
+    import loader
130
+    loader.start()
131
+    from lodel.plugin.scripts import main_run
132
+    main_run()

Loading…
取消
儲存