1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
|
# -*- coding: utf-8 -*-
"""CLI parser setup and helpers."""
import argparse
import logging
import os
import sys
from pathlib import Path
from typing import List, NamedTuple
import yaml
from ansiblelint.constants import DEFAULT_RULESDIR, INVALID_CONFIG_RC
from ansiblelint.utils import expand_path_vars
from ansiblelint.version import __version__
_logger = logging.getLogger(__name__)
_PATH_VARS = ['exclude_paths', 'rulesdir', ]
def abspath(path: str, base_dir: str) -> str:
"""Make relative path absolute relative to given directory.
Args:
path (str): the path to make absolute
base_dir (str): the directory from which make relative paths
absolute
default_drive: Windows drive to use to make the path
absolute if none is given.
"""
if not os.path.isabs(path):
# Don't use abspath as it assumes path is relative to cwd.
# We want it relative to base_dir.
path = os.path.join(base_dir, path)
return os.path.normpath(path)
def expand_to_normalized_paths(config: dict, base_dir: str = None) -> None:
# config can be None (-c /dev/null)
if not config:
return
base_dir = base_dir or os.getcwd()
for paths_var in _PATH_VARS:
if paths_var not in config:
continue # Cause we don't want to add a variable not present
normalized_paths = []
for path in config.pop(paths_var):
normalized_path = abspath(expand_path_vars(path), base_dir=base_dir)
normalized_paths.append(normalized_path)
config[paths_var] = normalized_paths
def load_config(config_file: str) -> dict:
config_path = os.path.abspath(config_file or '.ansible-lint')
if config_file:
if not os.path.exists(config_path):
_logger.error("Config file not found '%s'", config_path)
sys.exit(INVALID_CONFIG_RC)
elif not os.path.exists(config_path):
# a missing default config file should not trigger an error
return {}
try:
with open(config_path, "r") as stream:
config = yaml.safe_load(stream)
except yaml.YAMLError as e:
_logger.error(e)
sys.exit(INVALID_CONFIG_RC)
# TODO(ssbarnea): implement schema validation for config file
if isinstance(config, list):
_logger.error(
"Invalid configuration '%s', expected YAML mapping in the config file.",
config_path)
sys.exit(INVALID_CONFIG_RC)
config_dir = os.path.dirname(config_path)
expand_to_normalized_paths(config, config_dir)
return config
class AbspathArgAction(argparse.Action):
def __call__(self, parser, namespace, values, option_string=None):
if isinstance(values, (str, Path)):
values = [values]
normalized_values = [Path(expand_path_vars(path)).resolve() for path in values]
previous_values = getattr(namespace, self.dest, [])
setattr(namespace, self.dest, previous_values + normalized_values)
def get_cli_parser() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser()
parser.add_argument('-L', dest='listrules', default=False,
action='store_true', help="list all the rules")
parser.add_argument('-f', dest='format', default='rich',
choices=['rich', 'plain', 'rst'],
help="Format used rules output, (default: %(default)s)")
parser.add_argument('-q', dest='quiet',
default=False,
action='store_true',
help="quieter, although not silent output")
parser.add_argument('-p', dest='parseable',
default=False,
action='store_true',
help="parseable output in the format of pep8")
parser.add_argument('--parseable-severity', dest='parseable_severity',
default=False,
action='store_true',
help="parseable output including severity of rule")
parser.add_argument('--progressive', dest='progressive',
default=False,
action='store_true',
help="Return success if it detects a reduction in number"
" of violations compared with previous git commit. This "
"feature works only in git repositories.")
parser.add_argument('-r', action=AbspathArgAction, dest='rulesdir',
default=[], type=Path,
help="Specify custom rule directories. Add -R "
f"to keep using embedded rules from {DEFAULT_RULESDIR}")
parser.add_argument('-R', action='store_true',
default=False,
dest='use_default_rules',
help="Keep default rules when using -r")
parser.add_argument('--show-relpath', dest='display_relative_path', action='store_false',
default=True,
help="Display path relative to CWD")
parser.add_argument('-t', dest='tags',
action='append',
default=[],
help="only check rules whose id/tags match these values")
parser.add_argument('-T', dest='listtags', action='store_true',
help="list all the tags")
parser.add_argument('-v', dest='verbosity', action='count',
help="Increase verbosity level",
default=0)
parser.add_argument('-x', dest='skip_list', default=[], action='append',
help="only check rules whose id/tags do not "
"match these values")
parser.add_argument('-w', dest='warn_list', default=[], action='append',
help="only warn about these rules, unless overridden in "
"config file defaults to 'experimental'")
parser.add_argument('--nocolor', dest='colored',
default=hasattr(sys.stdout, 'isatty') and sys.stdout.isatty(),
action='store_false',
help="disable colored output")
parser.add_argument('--force-color', dest='colored',
action='store_true',
help="Try force colored output (relying on ansible's code)")
parser.add_argument('--exclude', dest='exclude_paths',
action=AbspathArgAction,
type=Path, default=[],
help='path to directories or files to skip. '
'This option is repeatable.',
)
parser.add_argument('-c', dest='config_file',
help='Specify configuration file to use. '
'Defaults to ".ansible-lint"')
parser.add_argument('--version', action='version',
version='%(prog)s {ver!s}'.format(ver=__version__),
)
parser.add_argument(dest='playbook', nargs='*',
help="One or more files or paths. When missing it will "
" enable auto-detection mode.")
return parser
def merge_config(file_config, cli_config) -> NamedTuple:
bools = (
'display_relative_path',
'parseable',
'parseable_severity',
'quiet',
'use_default_rules',
)
# maps lists to their default config values
lists_map = {
'exclude_paths': [],
'rulesdir': [],
'skip_list': [],
'tags': [],
'warn_list': ['experimental'],
}
if not file_config:
return cli_config
for entry in bools:
x = getattr(cli_config, entry) or file_config.get(entry, False)
setattr(cli_config, entry, x)
for entry, default in lists_map.items():
getattr(cli_config, entry).extend(file_config.get(entry, default))
if 'verbosity' in file_config:
cli_config.verbosity = (cli_config.verbosity +
file_config['verbosity'])
return cli_config
def get_config(arguments: List[str]):
parser = get_cli_parser()
options = parser.parse_args(arguments)
config = load_config(options.config_file)
return merge_config(config, options)
def print_help(file=sys.stdout):
get_cli_parser().print_help(file=file)
# vim: et:sw=4:syntax=python:ts=4:
|