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