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