1
|
|
|
#!/usr/bin/env python |
2
|
|
|
# coding: utf-8 |
3
|
1 |
|
""" |
4
|
|
|
CLI Entry Point. |
5
|
|
|
|
6
|
|
|
From click, build stakkr. |
7
|
|
|
Give all options to manage services to be launched, stopped, etc. |
8
|
|
|
""" |
9
|
|
|
|
10
|
1 |
|
import click |
11
|
1 |
|
from os import getcwd |
12
|
1 |
|
from sys import argv, exit, stdout, stderr |
13
|
1 |
|
from click.core import Context |
14
|
1 |
|
from stakkr.docker_actions import get_running_containers_names |
15
|
1 |
|
from stakkr.aliases import get_aliases |
16
|
|
|
|
17
|
|
|
|
18
|
1 |
|
@click.group(help="""Main CLI Tool that easily create / maintain |
19
|
|
|
a stack of services, for example for web development. |
20
|
|
|
|
21
|
|
|
Read the configuration file and setup the required services by |
22
|
|
|
linking and managing everything for you.""") |
23
|
1 |
|
@click.version_option('5.1') |
24
|
1 |
|
@click.option('--config', '-c', help='Set the configuration filename (stakkr.yml by default)') |
25
|
1 |
|
@click.option('--debug/--no-debug', '-d', default=False) |
26
|
1 |
|
@click.option('--verbose', '-v', is_flag=True) |
27
|
1 |
|
@click.pass_context |
28
|
1 |
|
def stakkr(ctx: Context, config=None, debug=False, verbose=True): |
29
|
|
|
"""Click group, set context and main object.""" |
30
|
1 |
|
from stakkr.actions import StakkrActions |
31
|
|
|
|
32
|
1 |
|
if config is not None and config[0] != '/': |
33
|
|
|
config = '{}/{}'.format(getcwd(), config) |
34
|
|
|
|
35
|
1 |
|
ctx.obj['CONFIG'] = config |
36
|
1 |
|
ctx.obj['DEBUG'] = debug |
37
|
1 |
|
ctx.obj['VERBOSE'] = verbose |
38
|
1 |
|
ctx.obj['STAKKR'] = StakkrActions(ctx.obj) |
39
|
|
|
|
40
|
|
|
|
41
|
1 |
|
@stakkr.command(help="""Enter a container to perform direct actions such as |
42
|
|
|
install packages, run commands, etc.""") |
43
|
1 |
|
@click.argument('container', required=True) |
44
|
1 |
|
@click.option('--user', '-u', help="User's name. Valid choices : www-data or root") |
45
|
1 |
|
@click.option('--tty/--no-tty', '-t/ ', is_flag=True, default=True, help="Use a TTY") |
46
|
1 |
|
@click.pass_context |
47
|
1 |
|
def console(ctx: Context, container: str, user: str, tty: bool): |
48
|
|
|
"""See command Help.""" |
49
|
1 |
|
ctx.obj['STAKKR'].init_project() |
50
|
1 |
|
ctx.obj['CTS'] = get_running_containers_names(ctx.obj['STAKKR'].project_name) |
51
|
1 |
|
if ctx.obj['CTS']: |
52
|
1 |
|
ct_choice = click.Choice(ctx.obj['CTS']) |
53
|
1 |
|
ct_choice.convert(container, None, ctx) |
54
|
|
|
|
55
|
1 |
|
ctx.obj['STAKKR'].console(container, _get_cmd_user(user, container), tty) |
56
|
|
|
|
57
|
|
|
|
58
|
1 |
|
@stakkr.command(help="""Execute a command into a container. |
59
|
|
|
|
60
|
|
|
Examples:\n |
61
|
|
|
- ``stakkr -v exec mysql mysqldump -p'$MYSQL_ROOT_PASSWORD' mydb > /tmp/backup.sql``\n |
62
|
|
|
- ``stakkr exec php php -v`` : Execute the php binary in the php container with option -v\n |
63
|
|
|
- ``stakkr exec apache service apache2 restart``\n |
64
|
|
|
""", name='exec', context_settings=dict(ignore_unknown_options=True, allow_interspersed_args=False)) |
65
|
1 |
|
@click.pass_context |
66
|
1 |
|
@click.option('--user', '-u', help="User's name. Be careful, each container have its own users.") |
67
|
1 |
|
@click.option('--tty/--no-tty', '-t/ ', is_flag=True, default=True, help="Use a TTY") |
68
|
1 |
|
@click.option('--workdir', '-w', help="Working directory") |
69
|
1 |
|
@click.argument('container', required=True) |
70
|
1 |
|
@click.argument('command', required=True, nargs=-1, type=click.UNPROCESSED) |
71
|
1 |
|
def exec_cmd(ctx: Context, user: str, container: str, command: tuple, tty: bool, workdir: str): |
72
|
|
|
"""See command Help.""" |
73
|
1 |
|
ctx.obj['STAKKR'].init_project() |
74
|
1 |
|
ctx.obj['CTS'] = get_running_containers_names(ctx.obj['STAKKR'].project_name) |
75
|
1 |
|
if ctx.obj['CTS']: |
76
|
1 |
|
click.Choice(ctx.obj['CTS']).convert(container, None, ctx) |
77
|
|
|
|
78
|
1 |
|
ctx.obj['STAKKR'].exec_cmd(container, _get_cmd_user(user, container), command, tty, workdir) |
79
|
|
|
|
80
|
|
|
|
81
|
1 |
|
@stakkr.command(help="Restart all (or a single as CONTAINER) container(s)") |
82
|
1 |
|
@click.argument('container', required=False) |
83
|
1 |
|
@click.option('--pull', '-p', help="Force a pull of the latest images versions", is_flag=True) |
84
|
1 |
|
@click.option('--recreate', '-r', help="Recreate all containers", is_flag=True) |
85
|
1 |
|
@click.option('--proxy/--no-proxy', '-P', help="Restart the proxy", default=True) |
86
|
1 |
|
@click.pass_context |
87
|
1 |
|
def restart(ctx: Context, container: str, pull: bool, recreate: bool, proxy: bool): |
88
|
|
|
"""See command Help.""" |
89
|
1 |
|
stdout.write(click.style('[RESTARTING]', fg='green') + ' your stakkr services\n') |
90
|
1 |
|
try: |
91
|
1 |
|
ctx.invoke(stop, container=container, proxy=proxy) |
92
|
1 |
|
except Exception: |
93
|
1 |
|
pass |
94
|
|
|
|
95
|
1 |
|
ctx.invoke(start, container=container, pull=pull, recreate=recreate, proxy=proxy) |
96
|
|
|
|
97
|
|
|
|
98
|
1 |
|
@stakkr.command(help="""List available services available for stakkr.yml |
99
|
|
|
(with info if the service is enabled)""") |
100
|
1 |
|
@click.pass_context |
101
|
|
|
def services(ctx): |
102
|
|
|
"""See command Help.""" |
103
|
1 |
|
ctx.obj['STAKKR'].init_project() |
104
|
|
|
|
105
|
1 |
|
from stakkr.stakkr_compose import get_available_services |
106
|
|
|
|
107
|
1 |
|
stdout.write('Available services usable in stakkr.yml ') |
108
|
1 |
|
stdout.write('({} = disabled) : \n'.format(click.style('✘', fg='red'))) |
109
|
|
|
|
110
|
1 |
|
svcs = ctx.obj['STAKKR'].get_config()['services'] |
111
|
1 |
|
enabled_svcs = [svc for svc, opts in svcs.items() if opts['enabled'] is True] |
112
|
1 |
|
available_svcs = get_available_services(ctx.obj['STAKKR'].project_dir) |
113
|
1 |
|
for available_svc in sorted(list(available_svcs.keys())): |
114
|
1 |
|
sign = click.style('✘', fg='red') |
115
|
1 |
|
if available_svc in enabled_svcs: |
116
|
1 |
|
version = svcs[available_svc]['version'] |
117
|
1 |
|
sign = click.style(str(version), fg='green') |
118
|
|
|
|
119
|
1 |
|
stdout.write(' - {} ({})\n'.format(available_svc, sign)) |
120
|
|
|
|
121
|
|
|
|
122
|
1 |
|
@stakkr.command(help="""Download a pack of services from github (see github) containing services. |
123
|
|
|
PACKAGE is the git url or package name. |
124
|
|
|
NAME is the directory name to clone the repo. |
125
|
|
|
""") |
126
|
1 |
|
@click.argument('package', required=True) |
127
|
1 |
|
@click.argument('name', required=False) |
128
|
1 |
|
@click.pass_context |
129
|
1 |
|
def services_add(ctx: Context, package: str, name: str): |
130
|
|
|
"""See command Help.""" |
131
|
1 |
|
from stakkr.services import install |
132
|
|
|
|
133
|
1 |
|
project_dir = _get_project_dir(ctx.obj['CONFIG']) |
134
|
1 |
|
services_dir = '{}/services'.format(project_dir) |
135
|
1 |
|
name = package if name is None else name |
136
|
1 |
|
success, message = install(services_dir, package, name) |
137
|
1 |
|
if success is False: |
138
|
1 |
|
click.echo(click.style(message, fg='red')) |
139
|
1 |
|
exit(1) |
140
|
|
|
|
141
|
1 |
|
stdout_msg = 'Package "' + click.style(package, fg='green') + '" installed successfully' |
142
|
1 |
|
if message is not None: |
143
|
1 |
|
stdout_msg = click.style(message, fg='yellow') |
144
|
|
|
|
145
|
1 |
|
click.echo(stdout_msg) |
146
|
1 |
|
click.echo('Try ' + click.style('stakkr services', fg='green') + ' to see new available services') |
147
|
|
|
|
148
|
|
|
|
149
|
1 |
|
@stakkr.command(help="Update all services packs in services/") |
150
|
1 |
|
@click.pass_context |
151
|
|
|
def services_update(ctx): |
152
|
|
|
"""See command Help.""" |
153
|
1 |
|
from stakkr.services import update_all |
154
|
|
|
|
155
|
1 |
|
project_dir = _get_project_dir(ctx.obj['CONFIG']) |
156
|
1 |
|
services_dir = '{}/services'.format(project_dir) |
157
|
1 |
|
update_all(services_dir) |
158
|
1 |
|
stdout.write(click.style('Packages updated\n', fg='green')) |
159
|
|
|
|
160
|
|
|
|
161
|
1 |
|
@stakkr.command(help="Start all (or a single as CONTAINER) container(s) defined in compose.ini") |
162
|
1 |
|
@click.argument('container', required=False) |
163
|
1 |
|
@click.option('--pull', '-p', help="Force a pull of the latest images versions", is_flag=True) |
164
|
1 |
|
@click.option('--recreate', '-r', help="Recreate all containers", is_flag=True) |
165
|
1 |
|
@click.option('--proxy/--no-proxy', '-P', help="Start proxy", default=True) |
166
|
1 |
|
@click.pass_context |
167
|
1 |
|
def start(ctx: Context, container: str, pull: bool, recreate: bool, proxy: bool): |
168
|
|
|
"""See command Help.""" |
169
|
1 |
|
stdout.write(click.style('[STARTING]', fg='green') + ' your stakkr services\n') |
170
|
|
|
|
171
|
1 |
|
ctx.obj['STAKKR'].start(container, pull, recreate, proxy) |
172
|
1 |
|
_show_status(ctx) |
173
|
|
|
|
174
|
|
|
|
175
|
1 |
|
@stakkr.command(help="Display a list of running containers") |
176
|
1 |
|
@click.pass_context |
177
|
|
|
def status(ctx): |
178
|
|
|
"""See command Help.""" |
179
|
1 |
|
ctx.obj['STAKKR'].status() |
180
|
|
|
|
181
|
|
|
|
182
|
1 |
|
@stakkr.command(help="Stop all (or a single as CONTAINER) container(s)") |
183
|
1 |
|
@click.argument('container', required=False) |
184
|
1 |
|
@click.option('--proxy/--no-proxy', '-P', help="Stop the proxy", default=True) |
185
|
1 |
|
@click.pass_context |
186
|
1 |
|
def stop(ctx: Context, container: str, proxy: bool): |
187
|
|
|
"""See command Help.""" |
188
|
1 |
|
stdout.write(click.style('[STOPPING]', fg='yellow') + ' your stakkr services\n') |
189
|
1 |
|
ctx.obj['STAKKR'].stop(container, proxy) |
190
|
|
|
|
191
|
|
|
|
192
|
1 |
|
def _get_cmd_user(user: str, container: str): |
193
|
1 |
|
users = {'apache': 'www-data', 'nginx': 'www-data', 'php': 'www-data'} |
194
|
|
|
|
195
|
1 |
|
cmd_user = 'root' if user is None else user |
196
|
1 |
|
if container in users and user is None: |
197
|
1 |
|
cmd_user = users[container] |
198
|
|
|
|
199
|
1 |
|
return cmd_user |
200
|
|
|
|
201
|
|
|
|
202
|
1 |
|
def _show_status(ctx): |
203
|
1 |
|
services_ports = ctx.obj['STAKKR'].get_services_urls() |
204
|
1 |
|
if services_ports == '': |
205
|
1 |
|
stdout.write('\nServices Status:\n') |
206
|
1 |
|
ctx.invoke(status) |
207
|
1 |
|
return |
208
|
|
|
|
209
|
1 |
|
stdout.write('\nServices URLs :\n') |
210
|
1 |
|
stdout.write(services_ports) |
211
|
|
|
|
212
|
|
|
|
213
|
1 |
|
def _get_project_dir(config: str): |
214
|
1 |
|
from os.path import abspath, dirname |
215
|
|
|
|
216
|
1 |
|
if config is not None: |
217
|
1 |
|
config = abspath(config) |
218
|
1 |
|
return dirname(config) |
219
|
|
|
|
220
|
|
|
from stakkr.file_utils import find_project_dir |
221
|
|
|
return find_project_dir() |
222
|
|
|
|
223
|
|
|
|
224
|
1 |
|
def debug_mode(): |
225
|
|
|
"""Guess if we are in debug mode, useful to display runtime errors.""" |
226
|
1 |
|
if '--debug' in argv or '-d' in argv: |
227
|
1 |
|
return True |
228
|
|
|
|
229
|
1 |
|
return False |
230
|
|
|
|
231
|
|
|
|
232
|
1 |
|
def run_commands(ctx: Context, extra_args: tuple, tty: bool): |
233
|
|
|
"""Run commands for a specific alias""" |
234
|
1 |
|
commands = ctx.obj['STAKKR'].get_config()['aliases'][ctx.command.name]['exec'] |
235
|
1 |
|
for command in commands: |
236
|
1 |
|
user = command['user'] if 'user' in command else 'root' |
237
|
1 |
|
workdir = command['workdir'] if 'workdir' in command else None |
238
|
1 |
|
container = command['container'] |
239
|
1 |
|
args = command['args'] + list(extra_args) if extra_args is not None else [] |
240
|
|
|
|
241
|
1 |
|
ctx.invoke(exec_cmd, user=user, container=container, command=args, tty=tty, workdir=workdir) |
242
|
|
|
|
243
|
|
|
|
244
|
1 |
|
def main(): |
245
|
|
|
"""Call the CLI Script.""" |
246
|
1 |
|
try: |
247
|
1 |
|
for alias, conf in get_aliases().items(): |
248
|
1 |
|
if conf is None: |
249
|
|
|
continue |
250
|
|
|
|
251
|
1 |
|
cmd_help = conf['description'] if 'description' in conf else 'No description' |
252
|
|
|
|
253
|
1 |
|
@stakkr.command(help=cmd_help, name=alias) |
254
|
1 |
|
@click.option('--tty/--no-tty', '-t/ ', is_flag=True, default=True, help="Use a TTY") |
255
|
1 |
|
@click.argument('extra_args', required=False, nargs=-1, type=click.UNPROCESSED) |
256
|
1 |
|
@click.pass_context |
257
|
1 |
|
def _f(ctx: Context, extra_args: tuple, tty: bool): |
258
|
|
|
"""See command Help.""" |
259
|
1 |
|
run_commands(ctx, extra_args, tty) |
260
|
|
|
|
261
|
1 |
|
stakkr(obj={}) |
262
|
1 |
|
except Exception as error: |
263
|
1 |
|
msg = click.style(r""" ______ _____ _____ ____ _____ |
264
|
|
|
| ____| __ \| __ \ / __ \| __ \ |
265
|
|
|
| |__ | |__) | |__) | | | | |__) | |
266
|
|
|
| __| | _ /| _ /| | | | _ / |
267
|
|
|
| |____| | \ \| | \ \| |__| | | \ \ |
268
|
|
|
|______|_| \_\_| \_\\____/|_| \_\ |
269
|
|
|
|
270
|
|
|
""", fg='yellow') |
271
|
1 |
|
msg += click.style('{}\n'.format(error), fg='red') |
272
|
1 |
|
stderr.write(msg) |
273
|
|
|
|
274
|
1 |
|
if debug_mode() is True: |
275
|
1 |
|
raise error |
276
|
|
|
|
277
|
1 |
|
exit(1) |
278
|
|
|
|
279
|
|
|
|
280
|
1 |
|
if __name__ == '__main__': |
281
|
|
|
main() |
282
|
|
|
|