Completed
Push — master ( bc517a...3bce3a )
by Konrad
02:08
created

db_sync_tool.utility.system   B

Complexity

Total Complexity 50

Size/Duplication

Total Lines 288
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 50
eloc 160
dl 0
loc 288
rs 8.4
c 0
b 0
f 0

8 Functions

Rating   Name   Duplication   Size   Complexity  
B get_configuration() 0 41 7
A get_password_by_user() 0 30 2
A check_authorizations() 0 7 1
A check_options() 0 17 4
C link_configuration_with_hosts() 0 40 11
A check_target_configuration() 0 6 1
F check_authorization() 0 33 14
C check_args_options() 0 56 10

How to fix   Complexity   

Complexity

Complex classes like db_sync_tool.utility.system often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1
#!/usr/bin/env python3
0 ignored issues
show
introduced by
Missing module docstring
Loading history...
2
# -*- coding: future_fstrings -*-
3
4
import sys
5
import json
6
import os
7
import getpass
8
from db_sync_tool.utility import log, parser, mode, helper, output
9
10
#
11
# GLOBALS
12
#
13
14
config = {
15
    'verbose': False,
16
    'mute': False,
17
    'keep_dump': False,
18
    'dump_name': '',
19
    'import': '',
20
    'link_hosts': '',
21
    'default_origin_dump_dir': True,
22
    'default_target_dump_dir': True,
23
    'check_dump': True,
24
    'is_same_client': False,
25
    'config_file_path': None,
26
    'ssh_password': {
27
        'origin': None,
28
        'target': None
29
    }
30
}
31
32
#
33
# DEFAULTS
34
#
35
36
default_local_sync_path = '/tmp/'
0 ignored issues
show
Coding Style Naming introduced by
Constant name "default_local_sync_path" doesn't conform to UPPER_CASE naming style ('([^\\W\\da-z][^\\Wa-z]*|__.*__)$' pattern)

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
37
38
39
#
40
# FUNCTIONS
41
#
42
43
def check_target_configuration():
44
    """
45
    Checking target database configuration
46
    :return:
47
    """
48
    parser.get_database_configuration(mode.Client.TARGET)
49
50
51
def get_configuration(host_config):
52
    """
53
    Checking configuration information by file or dictionary
54
    :param host_config: Dictionary
55
    :return:
56
    """
57
    global config
0 ignored issues
show
Coding Style introduced by
Usage of the global statement should be avoided.

Usage of global can make code hard to read and test, its usage is generally not recommended unless you are dealing with legacy code.

Loading history...
Coding Style Naming introduced by
Constant name "config" doesn't conform to UPPER_CASE naming style ('([^\\W\\da-z][^\\Wa-z]*|__.*__)$' pattern)

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
58
59
    if config['config_file_path'] is None and host_config == {}:
60
        sys.exit(
61
            output.message(
62
                output.Subject.ERROR,
63
                f'Configuration is missing, use a separate file or provide host parameter',
0 ignored issues
show
introduced by
Using an f-string that does not have any interpolated variables
Loading history...
64
                False
65
            )
66
        )
67
68
    if host_config:
69
        config.update(host_config)
70
71
    _config_file_path = config['config_file_path']
72
    if not _config_file_path is None:
73
        if os.path.isfile(_config_file_path):
74
            with open(_config_file_path, 'r') as read_file:
75
                config.update(json.load(read_file))
76
                output.message(
77
                    output.Subject.LOCAL,
78
                    f'Loading host configuration {output.CliFormat.BLACK}{_config_file_path}{output.CliFormat.ENDC}',
0 ignored issues
show
Coding Style introduced by
This line is too long as per the coding-style (117/100).

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

Loading history...
79
                    True
80
                )
81
        else:
82
            sys.exit(
83
                output.message(
84
                    output.Subject.ERROR,
85
                    f'Local configuration not found: {config["config_file_path"]}',
86
                    False
87
                )
88
            )
89
90
    check_options()
91
    log.get_logger().info('Starting db_sync_tool')
92
93
94
def check_options():
95
    """
96
    Checking configuration provided file
97
    :return:
98
    """
99
    global config
0 ignored issues
show
Coding Style introduced by
Usage of the global statement should be avoided.

Usage of global can make code hard to read and test, its usage is generally not recommended unless you are dealing with legacy code.

Loading history...
Coding Style Naming introduced by
Constant name "config" doesn't conform to UPPER_CASE naming style ('([^\\W\\da-z][^\\Wa-z]*|__.*__)$' pattern)

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
100
    if 'dump_dir' in config['origin']:
101
        config['default_origin_dump_dir'] = False
102
103
    if 'dump_dir' in config['target']:
104
        config['default_target_dump_dir'] = False
105
106
    if 'check_dump' in config:
107
        config['check_dump'] = config['check_dump']
108
109
    link_configuration_with_hosts()
110
    mode.check_sync_mode()
111
112
113
def check_authorizations():
114
    """
115
    Checking authorization for clients
116
    :return:
117
    """
118
    check_authorization(mode.Client.ORIGIN)
119
    check_authorization(mode.Client.TARGET)
120
121
122
def check_authorization(client):
123
    """
124
    Checking arguments and fill options array
125
    :param client: String
126
    :return:
127
    """
128
    # only need authorization if client is remote
129
    if mode.is_remote(client):
130
        # Workaround if no authorization is needed
131
        if (mode.get_sync_mode() == mode.SyncMode.DUMP_REMOTE and client == mode.Client.TARGET) or (
0 ignored issues
show
best-practice introduced by
Too many boolean expressions in if statement (6/5)
Loading history...
132
                mode.get_sync_mode() == mode.SyncMode.DUMP_LOCAL and client == mode.Client.ORIGIN) or (
0 ignored issues
show
Coding Style introduced by
This line is too long as per the coding-style (103/100).

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

Loading history...
133
                mode.get_sync_mode() == mode.SyncMode.IMPORT_REMOTE and client == mode.Client.ORIGIN):
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation (add 4 spaces).
Loading history...
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...
134
            return
135
136
        # ssh key authorization
137
        if 'ssh_key' in config[client]:
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable config does not seem to be defined.
Loading history...
138
            _ssh_key = config[client]['ssh_key']
139
            if not os.path.isfile(_ssh_key):
140
                sys.exit(
141
                    output.message(
142
                        output.Subject.ERROR,
143
                        f'SSH {client} private key not found: {_ssh_key}',
144
                        False
145
                    )
146
                )
147
        elif 'password' in config[client]:
148
            config[client]['password'] = config[client]['password']
149
        else:
150
            # user input authorization
151
            config[client]['password'] = get_password_by_user(client)
152
153
        if mode.get_sync_mode() == mode.SyncMode.DUMP_REMOTE and client == mode.Client.ORIGIN and 'password' in config[mode.Client.ORIGIN]:
0 ignored issues
show
Coding Style introduced by
This line is too long as per the coding-style (139/100).

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

Loading history...
154
            config[mode.Client.TARGET]['password'] = config[mode.Client.ORIGIN]['password']
155
156
157
def get_password_by_user(client):
158
    """
159
    Getting password by user input
160
    :param client: String
161
    :return: String password
162
    """
163
    _password = getpass.getpass(
164
        output.message(
165
            output.Subject.INFO,
166
            'SSH password ' + helper.get_ssh_host_name(client, True) + ': ',
167
            False
168
        )
169
    )
170
171
    while _password.strip() == '':
172
        output.message(
173
            output.Subject.WARNING,
174
            'Password seems to be empty. Please enter a valid password.',
175
            True
176
        )
177
178
        _password = getpass.getpass(
179
            output.message(
180
                output.Subject.INFO,
181
                'SSH password ' + helper.get_ssh_host_name(client, True) + ': ',
182
                False
183
            )
184
        )
185
186
    return _password
187
188
189
def check_args_options(config_file=None,
0 ignored issues
show
best-practice introduced by
Too many arguments (8/5)
Loading history...
190
                       verbose=False,
191
                       yes=False,
192
                       mute=False,
193
                       import_file=None,
194
                       dump_name=None,
195
                       keep_dump=None,
196
                       host_file=None):
197
    """
198
    Checking arguments and fill options array
199
    :param config_file:
200
    :param verbose:
201
    :param yes:
202
    :param mute:
203
    :param import_file:
204
    :param dump_name:
205
    :param keep_dump:
206
    :param host_file:
207
    :return:
208
    """
209
    global config
0 ignored issues
show
Coding Style introduced by
Usage of the global statement should be avoided.

Usage of global can make code hard to read and test, its usage is generally not recommended unless you are dealing with legacy code.

Loading history...
Coding Style Naming introduced by
Constant name "config" doesn't conform to UPPER_CASE naming style ('([^\\W\\da-z][^\\Wa-z]*|__.*__)$' pattern)

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
210
    global default_local_sync_path
0 ignored issues
show
Coding Style introduced by
Usage of the global statement should be avoided.

Usage of global can make code hard to read and test, its usage is generally not recommended unless you are dealing with legacy code.

Loading history...
Coding Style Naming introduced by
Constant name "default_local_sync_path" doesn't conform to UPPER_CASE naming style ('([^\\W\\da-z][^\\Wa-z]*|__.*__)$' pattern)

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
211
212
    if not config_file is None:
213
        config['config_file_path'] = config_file
214
215
    if not verbose is None:
216
        config['verbose'] = verbose
217
218
    if not yes is None:
219
        config['yes'] = yes
220
221
    if not mute is None:
222
        config['mute'] = mute
223
224
    if not import_file is None:
225
        config['import'] = import_file
226
227
    if not dump_name is None:
228
        config['dump_name'] = dump_name
229
230
    if not host_file is None:
231
        config['link_hosts'] = host_file
232
233
    if not keep_dump is None:
234
        default_local_sync_path = keep_dump
235
236
        # Adding trailing slash if necessary
237
        if default_local_sync_path[-1] != '/':
238
            default_local_sync_path += '/'
239
240
        config['keep_dump'] = True
241
        output.message(
242
            output.Subject.INFO,
243
            '"Keep dump" option chosen',
244
            True
245
        )
246
247
248
def link_configuration_with_hosts():
249
    """
250
    Merging the hosts definition with the given configuration file
251
    :return:
252
    """
253
    if ('link' in config['origin'] or 'link' in config['target']) and config['link_hosts'] == '':
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable config does not seem to be defined.
Loading history...
254
        # Try to find default hosts.json file in same directory
255
        sys.exit(
256
            output.message(
257
                output.Subject.ERROR,
258
                f'Missing hosts file for linking hosts with configuration. '
0 ignored issues
show
introduced by
Using an f-string that does not have any interpolated variables
Loading history...
259
                f'Use the "-o" / "--hosts" argument to define the filepath for the hosts file, when using a link parameter within the configuration.',
0 ignored issues
show
Coding Style introduced by
This line is too long as per the coding-style (150/100).

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

Loading history...
260
                False
261
            )
262
        )
263
264
    if config['link_hosts'] != '':
265
        if os.path.isfile(config['link_hosts']):
266
            with open(config['link_hosts'], 'r') as read_file:
267
                _hosts = json.load(read_file)
268
                output.message(
269
                    output.Subject.INFO,
270
                    'Linking configuration with hosts',
271
                    True
272
                )
273
                if 'link' in config['origin']:
274
                    _host_name = str(config['origin']['link']).replace('@','')
0 ignored issues
show
Coding Style introduced by
Exactly one space required after comma
Loading history...
275
                    if _host_name in _hosts:
276
                        config['origin'] = {**config['origin'], **_hosts[_host_name]}
277
278
                if 'link' in config['target']:
279
                    _host_name = str(config['target']['link']).replace('@','')
0 ignored issues
show
Coding Style introduced by
Exactly one space required after comma
Loading history...
280
                    if _host_name in _hosts:
281
                        config['target'] = {**config['target'], **_hosts[_host_name]}
282
        else:
283
            sys.exit(
284
                output.message(
285
                    output.Subject.ERROR,
286
                    f'Local host file not found: {config["link_hosts"]}',
287
                    False
288
                )
289
            )
290
291
292
0 ignored issues
show
coding-style introduced by
Trailing newlines
Loading history...
293