-
Notifications
You must be signed in to change notification settings - Fork 3
/
devlab
executable file
·248 lines (214 loc) · 14.1 KB
/
devlab
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
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
#!/usr/bin/env python
# vim: set syntax=python et ts=4 sw=4 sts=4:
"""
Main script for managing the devlab environment stack
"""
import argparse
import logging
import os
import sys
import devlab_bench.actions
import devlab_bench
from devlab_bench.helpers.command import Command
from devlab_bench.helpers.common import get_components, get_config, get_runtime_images, get_shell_components, logging_init
from devlab_bench.helpers.docker import DockerHelper
from devlab_bench.exceptions import DevlabComponentError
##- Variables -##
ARGS = None
PARSER = None
LOGGER = None
__VERSION__ = 'master'
##- Functions -##
def action_default(**kwargs):
"""
A default action that doesn't really do anything
"""
ignored_args = kwargs
PARSER.parse_args(['-h'])
def set_default_action(args, subparser):
"""
Look at the args passed and determine if there is a subparser action set for
it. If there is, then return the normal set of args. If NOT then append the
default 'none' action and return it.
This is primarily to get around a shortcoming in python2 :-|
Args:
args: list, of the args passed to the script
Returns:
list
"""
action_exists = False
args_passed = list(args)
for action in subparser.choices:
if action in args_passed:
action_exists = True
break
if not action_exists:
args_passed.append('none')
return args_passed
##- Main -##
if __name__ == '__main__':
CUR_COMPONENTS = get_components()
#Top level parser
PARSER = argparse.ArgumentParser(description='Main interface for devlab')
PARSER.add_argument('--log-level', '-l', choices=list(devlab_bench.LOGGING_LEVELS.keys()), default='info', help='Set the log-level output')
PARSER.add_argument('--version', '-v', action='store_true', help='Display the version of devlab and exit')
PARSER.add_argument('--project-root', '-P', default=None, help='Force project root to a specific path instead of searching for DevlabConfig.json/DevlabConfig.yaml etc...')
SUBPARSERS = PARSER.add_subparsers(help='Actions')
#Add Subparser for dummy default action
PARSER_DEFAULT = SUBPARSERS.add_parser('none')
PARSER_DEFAULT.set_defaults(func=action_default)
#Add Subparser for build action
PARSER_BUILD = SUBPARSERS.add_parser('build', help='Build docker images')
PARSER_BUILD.add_argument('images', nargs='*', choices=list(devlab_bench.IMAGES.keys()) + get_runtime_images() + ['*'], default='*', help='Build the specific image or images. Leave empty for all(*)')
PARSER_BUILD.add_argument('--clean', '-c', action='store_true', help='Do a clean build, which will remove all images and then rebuild them')
PARSER_BUILD.add_argument('--no-cache', '-C', action='store_true', help='Don\'t use docker\'s cache when building')
PARSER_BUILD.add_argument('--pull', '-p', action='store_true', help='Try to pull the latest version of images during build')
PARSER_BUILD.set_defaults(func=devlab_bench.actions.build.action)
#Add Subparser for down action
PARSER_DOWN = SUBPARSERS.add_parser('down', help='Bring down components')
PARSER_DOWN.add_argument('components', nargs='*', default='*', type=get_components, help='Bring down the specific component(s) or glob matches to bring down. COMPONENTS: {}'.format(', '.join(CUR_COMPONENTS)))
PARSER_DOWN.add_argument('--rm', '-r', action='store_true', help="Don't just bring the component down, but also delete the container")
PARSER_DOWN.set_defaults(func=devlab_bench.actions.down.action)
#Add Subparser for shell action
PARSER_SHELL = SUBPARSERS.add_parser('sh', help='Execute a shell command inside of a component/container')
PARSER_SHELL.add_argument('components', nargs='*', default='*', type=get_shell_components, help='The component(s) or globs where the shell/command should be run. If more than one component is specified the command will be run sequentially across the components. COMPONENTS: {}'.format(', '.join(CUR_COMPONENTS + ['adhoc'])))
PARSER_SHELL.add_argument('--adhoc-image', '-i', default='devlab_helper', help='When using the \'adhoc\' component, use this image. [NOTE] This is overridden if --command is specified with \'helper_container|IMAGENAME: /bin/bash\' etc... DEFAULT: \'devlab_helper\'')
PARSER_SHELL.add_argument('--adhoc-name', '-n', default=None, help='When using the \'adhoc\' component, use this name for the container.')
PARSER_SHELL.add_argument('--command', '-c', nargs=argparse.REMAINDER, help='Optional command to run instead of an interactive shell')
PARSER_SHELL.add_argument('--user', '-u', default=None, help='Optional user to run the command/shell as')
PARSER_SHELL.set_defaults(func=devlab_bench.actions.shell.action)
#Add Subparser for reset action
PARSER_RESET = SUBPARSERS.add_parser('reset', help='Reset a specific component, getting rid of all data including persistent data. This is useful if you want to have a component start from scratch without re-running the wizard')
PARSER_RESET.add_argument('targets', nargs='*', default='default', type=devlab_bench.actions.reset.get_reset_components, help='Reset the specific target(s) or glob matches. * means all components, but this does NOT inlcude other targets like \'devlab\'. TARGETS: {}'.format(', '.join(CUR_COMPONENTS + ['devlab'])))
PARSER_RESET.add_argument('--reset-wizard', '-r', action='store_true', help='Also remove wizard related files so that the wizard will run again for the specified component')
PARSER_RESET.add_argument('--full', '-f', action='store_true', help='Remove all component specific files, wizard files, as well as devlab files AND potentially files you\'re working on. BE CAREFUL IF YOU HAVE MANUAL CHANGES IN PATHS DEFINED IN YOUR \'paths.reset_full\'!!')
PARSER_RESET.set_defaults(func=devlab_bench.actions.reset.action)
#Add Subparser for global_restart action
PARSER_GLOBAL_RESTART = SUBPARSERS.add_parser('global-restart', help='Restart components across all environments managed by devlab')
PARSER_GLOBAL_RESTART.add_argument('--update-images', '-u', action='store_true', help='Look for images that components are using, and try to either build new versions, or pull new ones')
PARSER_GLOBAL_RESTART.set_defaults(func=devlab_bench.actions.global_restart.action)
#Add Subparser for global_status action
PARSER_GLOBAL_STATUS = SUBPARSERS.add_parser('global-status', help='Get a global status of all environments where devlab has created containers')
PARSER_GLOBAL_STATUS.set_defaults(func=devlab_bench.actions.global_status.action)
#Add Subparser for status action
PARSER_STATUS = SUBPARSERS.add_parser('status', help='Get a status of the environment')
PARSER_STATUS.set_defaults(func=devlab_bench.actions.status.action)
#Add Subparser for up action
PARSER_UP = SUBPARSERS.add_parser('up', help='Bring up components')
PARSER_UP.add_argument('components', nargs='*', default='*', type=get_components, help='Bring up the specific component(s) based on name or glob match. COMPONENTS: {}'.format(', '.join(CUR_COMPONENTS)))
PARSER_UP.add_argument('--bind-to-host', '-b', action='store_true', help='Whether or not we should spin things up so that other systems on your host\'s network will be able to easily reach and work with the spun up components. This generally means if your host\'s IP changes, components will have to be reprovisioned')
PARSER_UP.add_argument('--skip-provision', '-k', action='store_true', help='Bring up the components but don\'t run any scripts')
PARSER_UP.add_argument('--keep-up-on-error', '-K', action='store_true', help='Whether to keep a component container running even if it encounters errors during provisioning scripts etc...')
PARSER_UP.add_argument('--update-images', '-u', action='store_true', help='Look for images that components are using, and try to either build new versions, or pull new ones when bringing them "up"')
PARSER_UP.set_defaults(func=devlab_bench.actions.up.action)
# Add subparser for update action
PARSER_UPDATE = SUBPARSERS.add_parser('update', help='Update images used by component using combination of docker pull and/or docker build')
PARSER_UPDATE.add_argument('components', nargs='*', default='*', type=get_components, help='Update the image(s) used by one or more components based on name of glob match. COMPONENTS: {}'.format(', '.join(CUR_COMPONENTS)))
PARSER_UPDATE.add_argument('--skip-base-images', '-B', action='store_true', help='Skip updating built-in base devlab images')
PARSER_UPDATE.set_defaults(func=devlab_bench.actions.update.action)
# Add subparser for upgrade action
PARSER_UPGRADE = SUBPARSERS.add_parser('upgrade', help='Upgrade devlab to the latest released version')
PARSER_UPGRADE.add_argument('--uninstall', '-U', action='store_true', help='Instead of updating using the installer, uninstall it')
PARSER_UPGRADE.add_argument('--set-version', '-V', default=None, help='Update/Downgrade to a specific version of devlab')
PARSER_UPGRADE.set_defaults(func=devlab_bench.actions.upgrade.action)
# Add subparser for restart
PARSER_RESTART = SUBPARSERS.add_parser('restart', help='Restart components')
PARSER_RESTART.add_argument('components', nargs='*', default='*', type=get_components, help='Stop and start a specific component(s) or glob match. COMPONENTS: {}'.format(', '.join(CUR_COMPONENTS)))
PARSER_RESTART.add_argument('--update-images', '-u', action='store_true', help='Look for images that components are using, and try to either build new versions, or pull new ones')
PARSER_RESTART.set_defaults(func=devlab_bench.actions.restart.action)
#Parse our args
try:
ARGS = PARSER.parse_args(set_default_action(args=sys.argv[1:], subparser=SUBPARSERS))
except DevlabComponentError as exc:
print('ERROR during parsing of aguments: {}'.format(exc))
sys.exit(1)
if ARGS.version:
print('Version: {}'.format(__VERSION__))
sys.exit(0)
#Initialize logging:
logging_init(level=ARGS.log_level)
LOGGER = logging.getLogger("Main")
#The 'update' action is special and doesn't need all of the checks or a devlab_bench.PROJ_ROOT etc..
#it also will exit after executing
if ARGS.func in [
devlab_bench.actions.upgrade.action,
devlab_bench.actions.global_restart.action,
devlab_bench.actions.global_status.action,
action_default
]:
ARGS.func(**vars(ARGS))
if ARGS.project_root:
devlab_bench.PROJ_ROOT = ARGS.project_root
if not devlab_bench.PROJ_ROOT:
#Running Adhoc without a project won't get us a DockerHelper object.
#this will create one if none has been set.
if ARGS.func == devlab_bench.actions.shell.action and ARGS.components == ['adhoc']: #pylint: disable=bad-option-value,comparison-with-callable
devlab_bench.PROJ_ROOT = os.path.abspath('.')
devlab_bench.helpers.docker.DOCKER = DockerHelper(
labels=[
'com.lab.type=devlab',
'com.lab.project={}'.format(devlab_bench.PROJ_ROOT)
]
)
devlab_bench.CONFIG = {
'network': {
'name': None
}
}
# Run the adhoc function
ARGS.func(**vars(ARGS))
sys.exit(0)
else:
LOGGER.error("Aborting... could not determine project root. Please create a DevlabConfig.json or DevlabConfig.yaml etc...")
sys.exit(1)
#Load config
devlab_bench.CONFIG = get_config()
#If we're doing an 'up' action, check for and run wizard
if ARGS.func == devlab_bench.actions.up.action: #pylint: disable=bad-option-value,comparison-with-callable
if devlab_bench.CONFIG['wizard_enabled']:
if os.path.isfile('{}/wizard'.format(devlab_bench.PROJ_ROOT)):
LOGGER.debug("Running wizard, in case it needs to be run")
WIZ_OUT = Command('{}/wizard'.format(devlab_bench.PROJ_ROOT), interactive=True).run()
if WIZ_OUT[0] != 0:
LOGGER.error("Wizard did not exit successfully... Aborting!")
sys.exit(1)
devlab_bench.CONFIG = get_config(force_reload=True)
else:
LOGGER.warning("WARNING!!!!WARNING!!! No wizard found!!!")
#See if we have enough details in our config
if not devlab_bench.CONFIG['components'] and 'foreground_component' not in devlab_bench.CONFIG:
LOGGER.warning("No devlab configuration was found yet.")
LOGGER.info("Trying to load from 'defaults/'")
devlab_bench.CONFIG = get_config(force_reload=True, fallback_default=True)
if not devlab_bench.CONFIG['components']:
LOGGER.error("No configured components found!... aborting")
sys.exit(1)
#Check min devlab version if set
if devlab_bench.CONFIG.get('min_devlab_version', None):
MIN_DEVLAB_VERSION = devlab_bench.CONFIG['min_devlab_version']
#Assume that "master" version is newer that min version and only if the version doesn't match
if __VERSION__ not in ["master", MIN_DEVLAB_VERSION]:
VERS_SORT = sorted(
[
__VERSION__,
MIN_DEVLAB_VERSION
],
key=devlab_bench.helpers.common.human_keys
)
if VERS_SORT[-1] != __VERSION__:
LOGGER.error("This devlab project requuires a minimum version of: '%s' Found: '%s' installed. Please upgrade", MIN_DEVLAB_VERSION, __VERSION__)
sys.exit(1)
LOGGER.debug("Current version of devlab: '%s' matches or excedes required minimum version: '%s'", __VERSION__, MIN_DEVLAB_VERSION)
#Create our DockerHelper Object
devlab_bench.helpers.docker.DOCKER = DockerHelper(
filter_label=devlab_bench.CONFIG['project_filter'],
labels=[
'com.lab.type=devlab',
'com.lab.project={}'.format(devlab_bench.PROJ_ROOT)
],
common_domain=devlab_bench.CONFIG['domain']
)
#Change directory to the root of the project
os.chdir(devlab_bench.PROJ_ROOT)
#Run the action function
ARGS.func(**vars(ARGS))