Passed
Push — master ( 868477...1b1f92 )
by Emmanuel
06:17
created

stakkr.cli.services()   A

Complexity

Conditions 3

Size

Total Lines 22
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 15
CRAP Score 3

Importance

Changes 0
Metric Value
cc 3
eloc 16
nop 1
dl 0
loc 22
ccs 15
cts 15
cp 1
crap 3
rs 9.6
c 0
b 0
f 0
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.core import Context
13 1
from stakkr.docker_actions import get_running_containers_names
14 1
from stakkr.aliases import get_aliases
15
16
17 1
@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 1
@click.version_option('4.0.5')
23 1
@click.option('--config', '-c', help='Set the configuration filename (stakkr.yml by default)')
24 1
@click.option('--debug/--no-debug', '-d', default=False)
25 1
@click.option('--verbose', '-v', is_flag=True)
26 1
@click.pass_context
27 1
def stakkr(ctx: Context, config=None, debug=False, verbose=True):
28
    """Click group, set context and main object."""
29 1
    from stakkr.actions import StakkrActions
30
31 1
    ctx.obj['CONFIG'] = config
32 1
    ctx.obj['DEBUG'] = debug
33 1
    ctx.obj['VERBOSE'] = verbose
34 1
    ctx.obj['STAKKR'] = StakkrActions(ctx.obj)
35
36
37 1
@stakkr.command(help="""Enter a container to perform direct actions such as
38
install packages, run commands, etc.""")
39 1
@click.argument('container', required=True)
40 1
@click.option('--user', '-u', help="User's name. Valid choices : www-data or root")
41 1
@click.option('--tty/--no-tty', '-t/ ', is_flag=True, default=True, help="Use a TTY")
42 1
@click.pass_context
43 1
def console(ctx: Context, container: str, user: str, tty: bool):
44
    """See command Help."""
45 1
    ctx.obj['STAKKR'].init_project()
46 1
    ctx.obj['CTS'] = get_running_containers_names(ctx.obj['STAKKR'].project_name)
47 1
    if ctx.obj['CTS']:
48 1
        ct_choice = click.Choice(ctx.obj['CTS'])
49 1
        ct_choice.convert(container, None, ctx)
50
51 1
    ctx.obj['STAKKR'].console(container, _get_cmd_user(user, container), tty)
52
53
54 1
@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 1
@click.pass_context
62 1
@click.option('--user', '-u', help="User's name. Be careful, each container have its own users.")
63 1
@click.option('--tty/--no-tty', '-t/ ', is_flag=True, default=True, help="Use a TTY")
64 1
@click.argument('container', required=True)
65 1
@click.argument('command', required=True, nargs=-1, type=click.UNPROCESSED)
66 1
def exec_cmd(ctx: Context, user: str, container: str, command: tuple, tty: bool):
67
    """See command Help."""
68 1
    ctx.obj['STAKKR'].init_project()
69 1
    ctx.obj['CTS'] = get_running_containers_names(ctx.obj['STAKKR'].project_name)
70 1
    if ctx.obj['CTS']:
71 1
        click.Choice(ctx.obj['CTS']).convert(container, None, ctx)
72
73 1
    ctx.obj['STAKKR'].exec_cmd(container, _get_cmd_user(user, container), command, tty)
74
75
76 1
@stakkr.command(help="Restart all (or a single as CONTAINER) container(s)")
77 1
@click.argument('container', required=False)
78 1
@click.option('--pull', '-p', help="Force a pull of the latest images versions", is_flag=True)
79 1
@click.option('--recreate', '-r', help="Recreate all containers", is_flag=True)
80 1
@click.option('--proxy/--no-proxy', '-P', help="Restart the proxy", default=True)
81 1
@click.pass_context
82 1
def restart(ctx: Context, container: str, pull: bool, recreate: bool, proxy: bool):
83
    """See command Help."""
84 1
    print(click.style('[RESTARTING]', fg='green') + ' your stakkr services')
85 1
    try:
86 1
        ctx.invoke(stop, container=container, proxy=proxy)
87 1
    except Exception:
0 ignored issues
show
Best Practice introduced by
Catching very general exceptions such as Exception is usually not recommended.

Generally, you would want to handle very specific errors in the exception handler. This ensure that you do not hide other types of errors which should be fixed.

So, unless you specifically plan to handle any error, consider adding a more specific exception.

Loading history...
88 1
        pass
89
90 1
    ctx.invoke(start, container=container, pull=pull, recreate=recreate, proxy=proxy)
91
92
93 1
@stakkr.command(help="""List available services available for stakkr.yml
94
(with info if the service is enabled)""")
95 1
@click.pass_context
96
def services(ctx):
97
    """See command Help."""
98 1
    ctx.obj['STAKKR'].init_project()
99
100 1
    from stakkr.stakkr_compose import get_available_services
101
102 1
    print('Available services usable in stakkr.yml ', end='')
103 1
    print('({} = disabled) : '.format(click.style('✘', fg='red')))
104
105 1
    svcs = ctx.obj['STAKKR'].get_config()['services']
106 1
    enabled_svcs = [svc for svc, opts in svcs.items() if opts['enabled'] is True]
107 1
    available_svcs = get_available_services(ctx.obj['STAKKR'].project_dir)
108 1
    for available_svc in sorted(list(available_svcs.keys())):
109 1
        sign = click.style('✘', fg='red')
110 1
        if available_svc in enabled_svcs:
111 1
            version = svcs[available_svc]['version']
112 1
            sign = click.style(str(version), fg='green')
113
114 1
        print('  - {} ({})'.format(available_svc, sign))
115
116
117 1
@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 1
@click.argument('package', required=True)
122 1
@click.argument('name', required=False)
123 1
@click.pass_context
124 1
def services_add(ctx: Context, package: str, name: str):
125
    """See command Help."""
126 1
    from stakkr.services import install
127
128 1
    project_dir = _get_project_dir(ctx.obj['CONFIG'])
129 1
    services_dir = '{}/services'.format(project_dir)
130 1
    name = package if name is None else name
131 1
    success, message = install(services_dir, package, name)
132 1
    if success is False:
133 1
        click.echo(click.style(message, fg='red'))
134 1
        sys.exit(1)
135
136 1
    stdout_msg = 'Package "' + click.style(package, fg='green') + '" installed successfully'
137 1
    if message is not None:
138 1
        stdout_msg = click.style(message, fg='yellow')
139
140 1
    click.echo(stdout_msg)
141 1
    click.echo('Try ' + click.style('stakkr services', fg='green') + ' to see new available services')
0 ignored issues
show
Coding Style introduced by
This line is too long as per the coding-style (102/100).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
142
143
144 1
@stakkr.command(help="Update all services packs in services/")
145 1
@click.pass_context
146
def services_update(ctx):
147
    """See command Help."""
148 1
    from stakkr.services import update_all
149
150 1
    project_dir = _get_project_dir(ctx.obj['CONFIG'])
151 1
    services_dir = '{}/services'.format(project_dir)
152 1
    update_all(services_dir)
153 1
    print(click.style('Packages updated', fg='green'))
154
155
156 1
@stakkr.command(help="Start all (or a single as CONTAINER) container(s) defined in compose.ini")
157 1
@click.argument('container', required=False)
158 1
@click.option('--pull', '-p', help="Force a pull of the latest images versions", is_flag=True)
159 1
@click.option('--recreate', '-r', help="Recreate all containers", is_flag=True)
160 1
@click.option('--proxy/--no-proxy', '-P', help="Start proxy", default=True)
161 1
@click.pass_context
162 1
def start(ctx: Context, container: str, pull: bool, recreate: bool, proxy: bool):
163
    """See command Help."""
164 1
    print(click.style('[STARTING]', fg='green') + ' your stakkr services')
165
166 1
    ctx.obj['STAKKR'].start(container, pull, recreate, proxy)
167 1
    _show_status(ctx)
168
169
170 1
@stakkr.command(help="Display a list of running containers")
171 1
@click.pass_context
172
def status(ctx):
173
    """See command Help."""
174 1
    ctx.obj['STAKKR'].status()
175
176
177 1
@stakkr.command(help="Stop all (or a single as CONTAINER) container(s)")
178 1
@click.argument('container', required=False)
179 1
@click.option('--proxy/--no-proxy', '-P', help="Stop the proxy", default=True)
180 1
@click.pass_context
181 1
def stop(ctx: Context, container: str, proxy: bool):
182
    """See command Help."""
183 1
    print(click.style('[STOPPING]', fg='yellow') + ' your stakkr services')
184 1
    ctx.obj['STAKKR'].stop(container, proxy)
185
186
187 1
def _get_cmd_user(user: str, container: str):
188 1
    users = {'apache': 'www-data', 'nginx': 'www-data', 'php': 'www-data'}
189
190 1
    cmd_user = 'root' if user is None else user
191 1
    if container in users and user is None:
192 1
        cmd_user = users[container]
193
194 1
    return cmd_user
195
196
197 1
def _show_status(ctx):
198 1
    services_ports = ctx.obj['STAKKR'].get_services_urls()
199 1
    if services_ports == '':
200 1
        print('\nServices Status:')
201 1
        ctx.invoke(status)
202 1
        return
203
204 1
    print('\nServices URLs :')
205 1
    print(services_ports)
206
207
208 1
def _get_project_dir(config: str):
209 1
    from os.path import abspath, dirname
210
211 1
    if config is not None:
212 1
        config = abspath(config)
213 1
        return dirname(config)
214
215
    from stakkr.file_utils import find_project_dir
216
    return find_project_dir()
217
218
219 1
def debug_mode():
220
    """Guess if we are in debug mode, useful to display runtime errors."""
221 1
    if '--debug' in sys.argv or '-d' in sys.argv:
222 1
        return True
223
224 1
    return False
225
226
227 1
def run_commands(ctx: Context, extra_args: tuple, tty: bool):
228
    """Run commands for a specific alias"""
229 1
    commands = ctx.obj['STAKKR'].get_config()['aliases'][ctx.command.name]['exec']
230 1
    for command in commands:
231 1
        user = command['user'] if 'user' in command else 'root'
232 1
        container = command['container']
233 1
        args = command['args'] + list(extra_args) if extra_args is not None else []
234
235 1
        ctx.invoke(exec_cmd, user=user, container=container, command=args, tty=tty)
236
237
238 1
def main():
239
    """Call the CLI Script."""
240 1
    try:
241 1
        for alias, conf in get_aliases().items():
242 1
            if conf is None:
243
                continue
244
245 1
            cmd_help = conf['description'] if 'description' in conf else 'No description'
246
247 1
            @stakkr.command(help=cmd_help, name=alias)
248 1
            @click.option('--tty/--no-tty', '-t/ ', is_flag=True, default=True, help="Use a TTY")
249 1
            @click.argument('extra_args', required=False, nargs=-1, type=click.UNPROCESSED)
250 1
            @click.pass_context
251 1
            def _f(ctx: Context, extra_args: tuple, tty: bool):
252
                """See command Help."""
253 1
                run_commands(ctx, extra_args, tty)
254
255 1
        stakkr(obj={})
0 ignored issues
show
Bug introduced by
The keyword obj does not seem to exist for the function call.
Loading history...
Bug introduced by
It seems like a value for argument ctx is missing in the function call.
Loading history...
256 1
    except Exception as error:
0 ignored issues
show
Best Practice introduced by
Catching very general exceptions such as Exception is usually not recommended.

Generally, you would want to handle very specific errors in the exception handler. This ensure that you do not hide other types of errors which should be fixed.

So, unless you specifically plan to handle any error, consider adding a more specific exception.

Loading history...
257 1
        msg = click.style(r""" ______ _____  _____   ____  _____
258
|  ____|  __ \|  __ \ / __ \|  __ \
259
| |__  | |__) | |__) | |  | | |__) |
260
|  __| |  _  /|  _  /| |  | |  _  /
261
| |____| | \ \| | \ \| |__| | | \ \
262
|______|_|  \_\_|  \_\\____/|_|  \_\
263
264
""", fg='yellow')
265 1
        msg += click.style('{}'.format(error), fg='red')
266 1
        print(msg + '\n', file=sys.stderr)
267
268 1
        if debug_mode() is True:
269 1
            raise error
270
271 1
        sys.exit(1)
272
273
274 1
if __name__ == '__main__':
275
    main()
276