Passed
Branch v4.0-dev (7b0e25)
by Emmanuel
05:10
created

stakkr.cli   A

Complexity

Total Complexity 33

Size/Duplication

Total Lines 277
Duplicated Lines 0 %

Test Coverage

Coverage 74.39%

Importance

Changes 0
Metric Value
eloc 173
dl 0
loc 277
ccs 122
cts 164
cp 0.7439
rs 9.76
c 0
b 0
f 0
wmc 33

17 Functions

Rating   Name   Duplication   Size   Complexity  
A stakkr() 0 19 1
A console() 0 15 2
A exec_cmd() 0 20 2
A refresh_plugins() 0 14 2
A mysql() 0 17 1
A _get_project_dir() 0 9 2
A services_update() 0 10 1
A stop() 0 8 1
A status() 0 5 1
A services() 0 21 3
A _show_status() 0 9 2
A restart() 0 15 2
A _get_cmd_user() 0 8 4
A main() 0 20 3
A start() 0 12 1
A debug_mode() 0 6 3
A services_add() 0 16 2
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