mirror of
https://github.com/thegeeklab/ansible-later.git
synced 2024-11-16 18:10:38 +00:00
Compare commits
158 Commits
Author | SHA1 | Date | |
---|---|---|---|
8571c4841e | |||
7099f6d3dc | |||
|
d0070d4816 | ||
|
aa69db12cc | ||
|
d5cb46f3c4 | ||
|
523867c885 | ||
|
bdb093047c | ||
|
38d11aae44 | ||
92d3fad445 | |||
|
7b95de429b | ||
64531c1271 | |||
|
d5682968ab | ||
|
62b7096f3a | ||
|
68bb9953de | ||
|
34c879dcc6 | ||
|
ff03f9c933 | ||
|
20425d54b5 | ||
|
3b1c285f2d | ||
|
fa4ebcdb67 | ||
|
49b4182fda | ||
|
26d89019de | ||
|
70d2e2b6e3 | ||
|
b8fc1f80f5 | ||
|
3ecaf2c83c | ||
|
ae339853fc | ||
|
7d6ff809ae | ||
|
fff6c09a3d | ||
|
72c1e8e778 | ||
|
6d84a16819 | ||
|
4880f9e999 | ||
|
855fdb701f | ||
|
9a447b168a | ||
|
253b54410d | ||
|
6c6fbe2646 | ||
|
dd2d6e415b | ||
|
a1a2b57546 | ||
|
1a1ecbd695 | ||
|
6f0a9fa628 | ||
|
c09b67088f | ||
|
fa49768ded | ||
|
25b52b9e18 | ||
|
f0ceba29ed | ||
|
c75029d26f | ||
|
bf4fa1f9e8 | ||
ccaa936f9a | |||
|
a8ae213b42 | ||
|
f17f833e6d | ||
|
b1a92ee164 | ||
|
e22fd04a04 | ||
|
c9bebc3fc6 | ||
|
7d0f611982 | ||
|
1f15548f69 | ||
|
70b31ff632 | ||
|
dce5137deb | ||
|
414ae921b8 | ||
|
97b6c688c6 | ||
|
f81a2e2f79 | ||
|
776896eb10 | ||
|
77ac5003a2 | ||
|
4decb201b7 | ||
|
35e26084fa | ||
|
ab9edb7618 | ||
|
7224113ec9 | ||
|
bd8a413c4b | ||
|
d03b522bbc | ||
|
516b78a871 | ||
|
0decedea9c | ||
|
68187e8765 | ||
|
70b40cee63 | ||
|
0fd035828b | ||
|
94591e4435 | ||
|
efa2529ae0 | ||
|
a17ddf8a52 | ||
|
9af2d042e0 | ||
|
d7b6775516 | ||
|
6abed0ab00 | ||
|
893eb69cc6 | ||
|
47b55b27b6 | ||
|
f9a214f622 | ||
|
5971c9679c | ||
|
f87922a662 | ||
|
aed6a5f420 | ||
|
21d6316dd0 | ||
|
b0f3dca7c7 | ||
|
e6ca4cfdb0 | ||
|
e1b535af8a | ||
|
b7401677dc | ||
|
8213c46e05 | ||
|
e2549dfc77 | ||
|
4c3b8f2e82 | ||
|
5e64c9d79d | ||
|
cfcd804959 | ||
|
13ac29da22 | ||
|
eeae482b31 | ||
|
0a53eee7f3 | ||
|
73e960f0b7 | ||
|
5d40978fa3 | ||
|
9dcaf5e488 | ||
|
fa65be587b | ||
|
dcc2e08d2c | ||
|
ab4f8b209f | ||
|
82e09a5726 | ||
|
cef217826e | ||
|
64e1b4dadb | ||
|
0f00036d7c | ||
|
2209fabe28 | ||
|
c7b9e492d3 | ||
|
ab9b9c6ae9 | ||
|
de4825ff70 | ||
e285bf3351 | |||
|
58beb822ae | ||
|
afcbd3d356 | ||
|
a5832c553a | ||
|
fc4a6944d6 | ||
|
d6ac162764 | ||
|
a7623f7a5a | ||
|
f0baf76e24 | ||
|
f754a7f292 | ||
|
d92da10f13 | ||
e6027445c8 | |||
|
1d7321e3dc | ||
252af19cb3 | |||
|
2803c86816 | ||
|
359db66db2 | ||
0e74461b58 | |||
|
942420db34 | ||
b90b030054 | |||
8dba488747 | |||
a89d6d5336 | |||
|
37e701217e | ||
48baa2f338 | |||
ce0d895fc4 | |||
|
9d6dd16c1c | ||
0e197fd585 | |||
|
9797533a14 | ||
674ffe6445 | |||
6fd39b62a4 | |||
7121b2ce6f | |||
74f58c59c9 | |||
da5d3c21c2 | |||
7b44647bec | |||
2f4e35d83c | |||
80ac8ec34d | |||
8861dcc705 | |||
|
b365b32638 | ||
|
087df4848e | ||
39a5e90e78 | |||
2df48598ec | |||
|
d360de2125 | ||
|
0137b863fa | ||
|
d4db45ba05 | ||
|
e9a082b2fa | ||
|
975a7bd661 | ||
|
f907222162 | ||
|
7f5c2a51b1 | ||
|
c51a4111ec | ||
8c9420b445 | |||
|
87ef3539cb |
@ -18,8 +18,8 @@ HostVars
|
|||||||
Rolesfile
|
Rolesfile
|
||||||
Makefile
|
Makefile
|
||||||
Jinja2
|
Jinja2
|
||||||
ANSIBLE([0-9]{4})
|
ANS([0-9]{3})
|
||||||
LINT([0-9]{4})
|
YML([0-9]{3})
|
||||||
SCM
|
SCM
|
||||||
bools
|
bools
|
||||||
Check[A-Z].+
|
Check[A-Z].+
|
||||||
|
1
.github/settings.yml
vendored
1
.github/settings.yml
vendored
@ -1,7 +1,6 @@
|
|||||||
repository:
|
repository:
|
||||||
name: ansible-later
|
name: ansible-later
|
||||||
description: Another best practice scanner for Ansible roles and playbooks
|
description: Another best practice scanner for Ansible roles and playbooks
|
||||||
homepage: https://ansible-later.geekdocs.de
|
|
||||||
topics: ansible, ansible-later, ansible-review, best practice
|
topics: ansible, ansible-later, ansible-review, best practice
|
||||||
|
|
||||||
private: false
|
private: false
|
||||||
|
@ -12,22 +12,31 @@ steps:
|
|||||||
- pip install poetry poetry-dynamic-versioning -qq
|
- pip install poetry poetry-dynamic-versioning -qq
|
||||||
- poetry build
|
- poetry build
|
||||||
|
|
||||||
- name: dryrun
|
- name: security-build
|
||||||
image: quay.io/thegeeklab/wp-docker-buildx:2
|
image: quay.io/thegeeklab/wp-docker-buildx:5
|
||||||
|
depends_on: [build]
|
||||||
settings:
|
settings:
|
||||||
containerfile: Containerfile.multiarch
|
containerfile: Containerfile.multiarch
|
||||||
dry_run: true
|
output: type=oci,dest=oci/${CI_REPO_NAME},tar=false
|
||||||
platforms:
|
|
||||||
- linux/amd64
|
|
||||||
- linux/arm64
|
|
||||||
provenance: false
|
|
||||||
repo: ${CI_REPO}
|
repo: ${CI_REPO}
|
||||||
when:
|
|
||||||
- event: [pull_request]
|
- name: security-scan
|
||||||
|
image: docker.io/aquasec/trivy
|
||||||
|
depends_on: [security-build]
|
||||||
|
commands:
|
||||||
|
- trivy -v
|
||||||
|
- trivy image --input oci/${CI_REPO_NAME}
|
||||||
|
environment:
|
||||||
|
TRIVY_EXIT_CODE: "1"
|
||||||
|
TRIVY_IGNORE_UNFIXED: "true"
|
||||||
|
TRIVY_NO_PROGRESS: "true"
|
||||||
|
TRIVY_SEVERITY: HIGH,CRITICAL
|
||||||
|
TRIVY_TIMEOUT: 1m
|
||||||
|
TRIVY_DB_REPOSITORY: docker.io/aquasec/trivy-db:2
|
||||||
|
|
||||||
- name: publish-dockerhub
|
- name: publish-dockerhub
|
||||||
image: quay.io/thegeeklab/wp-docker-buildx:2
|
image: quay.io/thegeeklab/wp-docker-buildx:5
|
||||||
group: container
|
depends_on: [security-scan]
|
||||||
settings:
|
settings:
|
||||||
auto_tag: true
|
auto_tag: true
|
||||||
containerfile: Containerfile.multiarch
|
containerfile: Containerfile.multiarch
|
||||||
@ -47,8 +56,8 @@ steps:
|
|||||||
- ${CI_REPO_DEFAULT_BRANCH}
|
- ${CI_REPO_DEFAULT_BRANCH}
|
||||||
|
|
||||||
- name: publish-quay
|
- name: publish-quay
|
||||||
image: quay.io/thegeeklab/wp-docker-buildx:2
|
image: quay.io/thegeeklab/wp-docker-buildx:5
|
||||||
group: container
|
depends_on: security-scan
|
||||||
settings:
|
settings:
|
||||||
auto_tag: true
|
auto_tag: true
|
||||||
containerfile: Containerfile.multiarch
|
containerfile: Containerfile.multiarch
|
||||||
|
@ -40,11 +40,11 @@ steps:
|
|||||||
|
|
||||||
- name: publish-pypi
|
- name: publish-pypi
|
||||||
image: docker.io/library/python:3.12
|
image: docker.io/library/python:3.12
|
||||||
secrets:
|
environment:
|
||||||
- source: pypi_password
|
POETRY_HTTP_BASIC_PYPI_PASSWORD:
|
||||||
target: POETRY_HTTP_BASIC_PYPI_PASSWORD
|
from_secret: pypi_password
|
||||||
- source: pypi_username
|
POETRY_HTTP_BASIC_PYPI_USERNAME:
|
||||||
target: POETRY_HTTP_BASIC_PYPI_USERNAME
|
from_secret: pypi_username
|
||||||
commands:
|
commands:
|
||||||
- pip install poetry poetry-dynamic-versioning -qq
|
- pip install poetry poetry-dynamic-versioning -qq
|
||||||
- poetry publish -n
|
- poetry publish -n
|
||||||
|
@ -13,13 +13,13 @@ steps:
|
|||||||
|
|
||||||
- name: markdownlint
|
- name: markdownlint
|
||||||
image: quay.io/thegeeklab/markdownlint-cli
|
image: quay.io/thegeeklab/markdownlint-cli
|
||||||
group: test
|
depends_on: [assets]
|
||||||
commands:
|
commands:
|
||||||
- markdownlint 'README.md' 'CONTRIBUTING.md'
|
- markdownlint 'README.md' 'CONTRIBUTING.md'
|
||||||
|
|
||||||
- name: spellcheck
|
- name: spellcheck
|
||||||
image: quay.io/thegeeklab/alpine-tools
|
image: quay.io/thegeeklab/alpine-tools
|
||||||
group: test
|
depends_on: [assets]
|
||||||
commands:
|
commands:
|
||||||
- spellchecker --files 'docs/**/*.md' 'README.md' 'CONTRIBUTING.md' -d .dictionary -p spell indefinite-article syntax-urls
|
- spellchecker --files 'docs/**/*.md' 'README.md' 'CONTRIBUTING.md' -d .dictionary -p spell indefinite-article syntax-urls
|
||||||
environment:
|
environment:
|
||||||
@ -27,24 +27,25 @@ steps:
|
|||||||
|
|
||||||
- name: link-validation
|
- name: link-validation
|
||||||
image: docker.io/lycheeverse/lychee
|
image: docker.io/lycheeverse/lychee
|
||||||
group: test
|
depends_on: [assets]
|
||||||
commands:
|
commands:
|
||||||
- lychee --no-progress --format detailed docs/content README.md
|
- lychee --no-progress --format detailed docs/content README.md
|
||||||
|
|
||||||
- name: build
|
- name: build
|
||||||
image: quay.io/thegeeklab/hugo:0.121.2
|
image: quay.io/thegeeklab/hugo:0.136.5
|
||||||
|
depends_on: [link-validation]
|
||||||
commands:
|
commands:
|
||||||
- hugo --panicOnWarning -s docs/
|
- hugo --panicOnWarning -s docs/
|
||||||
|
|
||||||
- name: beautify
|
- name: beautify
|
||||||
image: quay.io/thegeeklab/alpine-tools
|
image: quay.io/thegeeklab/alpine-tools
|
||||||
|
depends_on: [build]
|
||||||
commands:
|
commands:
|
||||||
- html-beautify -r -f 'docs/public/**/*.html'
|
- html-beautify -r -f 'docs/public/**/*.html'
|
||||||
environment:
|
|
||||||
FORCE_COLOR: "true"
|
|
||||||
|
|
||||||
- name: publish
|
- name: publish
|
||||||
image: quay.io/thegeeklab/wp-s3-action
|
image: quay.io/thegeeklab/wp-s3-action
|
||||||
|
depends_on: [beautify]
|
||||||
settings:
|
settings:
|
||||||
access_key:
|
access_key:
|
||||||
from_secret: s3_access_key
|
from_secret: s3_access_key
|
||||||
@ -66,12 +67,12 @@ steps:
|
|||||||
|
|
||||||
- name: pushrm-dockerhub
|
- name: pushrm-dockerhub
|
||||||
image: docker.io/chko/docker-pushrm:1
|
image: docker.io/chko/docker-pushrm:1
|
||||||
secrets:
|
depends_on: [publish]
|
||||||
- source: docker_password
|
|
||||||
target: DOCKER_PASS
|
|
||||||
- source: docker_username
|
|
||||||
target: DOCKER_USER
|
|
||||||
environment:
|
environment:
|
||||||
|
DOCKER_PASS:
|
||||||
|
from_secret: docker_password
|
||||||
|
DOCKER_USER:
|
||||||
|
from_secret: docker_username
|
||||||
PUSHRM_FILE: README.md
|
PUSHRM_FILE: README.md
|
||||||
PUSHRM_SHORT: Another best practice scanner for Ansible roles and playbooks
|
PUSHRM_SHORT: Another best practice scanner for Ansible roles and playbooks
|
||||||
PUSHRM_TARGET: ${CI_REPO}
|
PUSHRM_TARGET: ${CI_REPO}
|
||||||
@ -83,10 +84,10 @@ steps:
|
|||||||
|
|
||||||
- name: pushrm-quay
|
- name: pushrm-quay
|
||||||
image: docker.io/chko/docker-pushrm:1
|
image: docker.io/chko/docker-pushrm:1
|
||||||
secrets:
|
depends_on: [publish]
|
||||||
- source: quay_token
|
|
||||||
target: APIKEY__QUAY_IO
|
|
||||||
environment:
|
environment:
|
||||||
|
APIKEY__QUAY_IO:
|
||||||
|
from_secret: quay_token
|
||||||
PUSHRM_FILE: README.md
|
PUSHRM_FILE: README.md
|
||||||
PUSHRM_TARGET: quay.io/${CI_REPO}
|
PUSHRM_TARGET: quay.io/${CI_REPO}
|
||||||
when:
|
when:
|
||||||
|
@ -8,6 +8,7 @@ when:
|
|||||||
steps:
|
steps:
|
||||||
- name: check-format
|
- name: check-format
|
||||||
image: docker.io/library/python:3.12
|
image: docker.io/library/python:3.12
|
||||||
|
depends_on: []
|
||||||
commands:
|
commands:
|
||||||
- pip install poetry poetry-dynamic-versioning -qq
|
- pip install poetry poetry-dynamic-versioning -qq
|
||||||
- poetry install
|
- poetry install
|
||||||
@ -17,9 +18,10 @@ steps:
|
|||||||
|
|
||||||
- name: check-coding
|
- name: check-coding
|
||||||
image: docker.io/library/python:3.12
|
image: docker.io/library/python:3.12
|
||||||
|
depends_on: []
|
||||||
commands:
|
commands:
|
||||||
- pip install poetry poetry-dynamic-versioning -qq
|
- pip install poetry poetry-dynamic-versioning -qq
|
||||||
- poetry install -E ansible-core
|
- poetry install -E ansible-core
|
||||||
- poetry run ruff ./${CI_REPO_NAME//-/}
|
- poetry run ruff check ./${CI_REPO_NAME//-/}
|
||||||
environment:
|
environment:
|
||||||
PY_COLORS: "1"
|
PY_COLORS: "1"
|
||||||
|
@ -13,12 +13,12 @@ steps:
|
|||||||
settings:
|
settings:
|
||||||
homeserver:
|
homeserver:
|
||||||
from_secret: matrix_homeserver
|
from_secret: matrix_homeserver
|
||||||
password:
|
room_id:
|
||||||
from_secret: matrix_password
|
from_secret: matrix_room_id
|
||||||
roomid:
|
user_id:
|
||||||
from_secret: matrix_roomid
|
from_secret: matrix_user_id
|
||||||
username:
|
access_token:
|
||||||
from_secret: matrix_username
|
from_secret: matrix_access_token
|
||||||
when:
|
when:
|
||||||
- status: [success, failure]
|
- status: [success, failure]
|
||||||
|
|
||||||
|
@ -7,7 +7,7 @@ when:
|
|||||||
|
|
||||||
variables:
|
variables:
|
||||||
- &pytest_base
|
- &pytest_base
|
||||||
group: pytest
|
depends_on: []
|
||||||
commands:
|
commands:
|
||||||
- pip install poetry poetry-dynamic-versioning -qq
|
- pip install poetry poetry-dynamic-versioning -qq
|
||||||
- poetry install -E ansible-core
|
- poetry install -E ansible-core
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
FROM python:3.12-alpine@sha256:c793b92fd9e0e2a0b611756788a033d569ca864b733461c8fb30cfd14847dbcf
|
FROM python:3.12-alpine@sha256:38e179a0f0436c97ecc76bcd378d7293ab3ee79e4b8c440fdc7113670cb6e204
|
||||||
|
|
||||||
LABEL maintainer="Robert Kaussow <mail@thegeeklab.de>"
|
LABEL maintainer="Robert Kaussow <mail@thegeeklab.de>"
|
||||||
LABEL org.opencontainers.image.authors="Robert Kaussow <mail@thegeeklab.de>"
|
LABEL org.opencontainers.image.authors="Robert Kaussow <mail@thegeeklab.de>"
|
||||||
|
2
Makefile
2
Makefile
@ -1,5 +1,5 @@
|
|||||||
# renovate: datasource=github-releases depName=thegeeklab/hugo-geekdoc
|
# renovate: datasource=github-releases depName=thegeeklab/hugo-geekdoc
|
||||||
THEME_VERSION := v0.44.0
|
THEME_VERSION := v1.2.1
|
||||||
THEME := hugo-geekdoc
|
THEME := hugo-geekdoc
|
||||||
BASEDIR := docs
|
BASEDIR := docs
|
||||||
THEMEDIR := $(BASEDIR)/themes
|
THEMEDIR := $(BASEDIR)/themes
|
||||||
|
14
README.md
14
README.md
@ -12,22 +12,12 @@ Another best practice scanner for Ansible roles and playbooks
|
|||||||
[![Source: GitHub](https://img.shields.io/badge/source-github-blue.svg?logo=github&logoColor=white)](https://github.com/thegeeklab/ansible-later)
|
[![Source: GitHub](https://img.shields.io/badge/source-github-blue.svg?logo=github&logoColor=white)](https://github.com/thegeeklab/ansible-later)
|
||||||
[![License: MIT](https://img.shields.io/github/license/thegeeklab/ansible-later)](https://github.com/thegeeklab/ansible-later/blob/main/LICENSE)
|
[![License: MIT](https://img.shields.io/github/license/thegeeklab/ansible-later)](https://github.com/thegeeklab/ansible-later/blob/main/LICENSE)
|
||||||
|
|
||||||
|
> **Discontinued:** This project is no longer maintained. Please use [ansible-lint](https://github.com/ansible-community/ansible-lint) instead.
|
||||||
|
|
||||||
ansible-later is a best practice scanner and linting tool. In most cases, if you write Ansible roles in a team, it helps to have a coding or best practice guideline in place. This will make Ansible roles more readable for all maintainers and can reduce the troubleshooting time. While ansible-later aims to be a fast and easy to use linting tool for your Ansible resources, it might not be that feature completed as required in some situations. If you need a more in-depth analysis you can take a look at [ansible-lint](https://github.com/ansible-community/ansible-lint).
|
ansible-later is a best practice scanner and linting tool. In most cases, if you write Ansible roles in a team, it helps to have a coding or best practice guideline in place. This will make Ansible roles more readable for all maintainers and can reduce the troubleshooting time. While ansible-later aims to be a fast and easy to use linting tool for your Ansible resources, it might not be that feature completed as required in some situations. If you need a more in-depth analysis you can take a look at [ansible-lint](https://github.com/ansible-community/ansible-lint).
|
||||||
|
|
||||||
ansible-later does **not** ensure that your role will work as expected. For deployment tests you can use other tools like [molecule](https://github.com/ansible/molecule).
|
ansible-later does **not** ensure that your role will work as expected. For deployment tests you can use other tools like [molecule](https://github.com/ansible/molecule).
|
||||||
|
|
||||||
You can find the full documentation at [https://ansible-later.geekdocs.de](https://ansible-later.geekdocs.de/).
|
|
||||||
|
|
||||||
## Community
|
|
||||||
|
|
||||||
<!-- prettier-ignore-start -->
|
|
||||||
<!-- spellchecker-disable -->
|
|
||||||
|
|
||||||
- [GitHub Action](https://github.com/patrickjahns/ansible-later-action) by [@patrickjahns](https://github.com/patrickjahns)
|
|
||||||
|
|
||||||
<!-- spellchecker-enable -->
|
|
||||||
<!-- prettier-ignore-end -->
|
|
||||||
|
|
||||||
## Contributors
|
## Contributors
|
||||||
|
|
||||||
Special thanks to all [contributors](https://github.com/thegeeklab/ansible-later/graphs/contributors). If you would like to contribute,
|
Special thanks to all [contributors](https://github.com/thegeeklab/ansible-later/graphs/contributors). If you would like to contribute,
|
||||||
|
@ -7,8 +7,8 @@ import sys
|
|||||||
|
|
||||||
from ansiblelater import LOG, __version__, logger
|
from ansiblelater import LOG, __version__, logger
|
||||||
from ansiblelater.candidate import Candidate
|
from ansiblelater.candidate import Candidate
|
||||||
|
from ansiblelater.rule import SingleRules
|
||||||
from ansiblelater.settings import Settings
|
from ansiblelater.settings import Settings
|
||||||
from ansiblelater.standard import SingleStandards
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
@ -22,33 +22,33 @@ def main():
|
|||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"-r",
|
"-r",
|
||||||
"--rules-dir",
|
"--rules-dir",
|
||||||
dest="rules.standards",
|
dest="rules.dir",
|
||||||
metavar="RULES",
|
metavar="DIR",
|
||||||
action="append",
|
action="append",
|
||||||
help="directory of standard rules",
|
help="directory of rules",
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"-B",
|
"-B",
|
||||||
"--no-buildin",
|
"--no-builtin",
|
||||||
dest="rules.buildin",
|
dest="rules.builtin",
|
||||||
action="store_false",
|
action="store_false",
|
||||||
help="disables build-in standard rules",
|
help="disables built-in rules",
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"-s",
|
"-i",
|
||||||
"--standards",
|
"--include-rules",
|
||||||
dest="rules.filter",
|
dest="rules.include_filter",
|
||||||
metavar="FILTER",
|
metavar="TAGS",
|
||||||
action="append",
|
action="append",
|
||||||
help="limit standards to given ID's",
|
help="limit rules to given id/tags",
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"-x",
|
"-x",
|
||||||
"--exclude-standards",
|
"--exclude-rules",
|
||||||
dest="rules.exclude_filter",
|
dest="rules.exclude_filter",
|
||||||
metavar="EXCLUDE_FILTER",
|
metavar="TAGS",
|
||||||
action="append",
|
action="append",
|
||||||
help="exclude standards by given ID's",
|
help="exclude rules by given it/tags",
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"-v", dest="logging.level", action="append_const", const=-1, help="increase log level"
|
"-v", dest="logging.level", action="append_const", const=-1, help="increase log level"
|
||||||
@ -65,7 +65,7 @@ def main():
|
|||||||
config = settings.config
|
config = settings.config
|
||||||
|
|
||||||
logger.update_logger(LOG, config["logging"]["level"], config["logging"]["json"])
|
logger.update_logger(LOG, config["logging"]["level"], config["logging"]["json"])
|
||||||
SingleStandards(config["rules"]["standards"])
|
SingleRules(config["rules"]["dir"])
|
||||||
|
|
||||||
workers = max(multiprocessing.cpu_count() - 2, 2)
|
workers = max(multiprocessing.cpu_count() - 2, 2)
|
||||||
p = multiprocessing.Pool(workers)
|
p = multiprocessing.Pool(workers)
|
||||||
|
@ -3,14 +3,12 @@
|
|||||||
import codecs
|
import codecs
|
||||||
import copy
|
import copy
|
||||||
import os
|
import os
|
||||||
import re
|
|
||||||
|
|
||||||
from ansible.plugins.loader import module_loader
|
from ansible.plugins.loader import module_loader
|
||||||
from packaging.version import Version
|
|
||||||
|
|
||||||
from ansiblelater import LOG, utils
|
from ansiblelater import LOG
|
||||||
from ansiblelater.logger import flag_extra
|
from ansiblelater.logger import flag_extra
|
||||||
from ansiblelater.standard import SingleStandards, StandardBase
|
from ansiblelater.rule import RuleBase, SingleRules
|
||||||
|
|
||||||
|
|
||||||
class Candidate:
|
class Candidate:
|
||||||
@ -21,11 +19,12 @@ class Candidate:
|
|||||||
bundled with necessary meta informations for rule processing.
|
bundled with necessary meta informations for rule processing.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, filename, settings={}, standards=[]): # noqa
|
def __init__(self, filename, settings={}, rules=[]): # noqa
|
||||||
self.path = filename
|
self.path = filename
|
||||||
self.binary = False
|
self.binary = False
|
||||||
self.vault = False
|
self.vault = False
|
||||||
self.filetype = type(self).__name__.lower()
|
self.filemeta = type(self).__name__.lower()
|
||||||
|
self.kind = type(self).__name__.lower()
|
||||||
self.faulty = False
|
self.faulty = False
|
||||||
self.config = settings.config
|
self.config = settings.config
|
||||||
self.settings = settings
|
self.settings = settings
|
||||||
@ -37,107 +36,58 @@ class Candidate:
|
|||||||
except UnicodeDecodeError:
|
except UnicodeDecodeError:
|
||||||
self.binary = True
|
self.binary = True
|
||||||
|
|
||||||
def _get_version(self):
|
def _filter_rules(self):
|
||||||
name = type(self).__name__
|
target_rules = []
|
||||||
path = self.path
|
includes = self.config["rules"]["include_filter"]
|
||||||
version = None
|
|
||||||
config_version = self.config["rules"]["version"].strip()
|
|
||||||
|
|
||||||
if config_version:
|
|
||||||
version_config_re = re.compile(r"([\d.]+)")
|
|
||||||
match = version_config_re.match(config_version)
|
|
||||||
if match:
|
|
||||||
version = match.group(1)
|
|
||||||
|
|
||||||
if not self.binary:
|
|
||||||
if isinstance(self, RoleFile):
|
|
||||||
parentdir = os.path.dirname(os.path.abspath(self.path))
|
|
||||||
while parentdir != os.path.dirname(parentdir):
|
|
||||||
meta_file = os.path.join(parentdir, "meta", "main.yml")
|
|
||||||
if os.path.exists(meta_file):
|
|
||||||
path = meta_file
|
|
||||||
break
|
|
||||||
parentdir = os.path.dirname(parentdir)
|
|
||||||
|
|
||||||
version_file_re = re.compile(r"^# Standards:\s*([\d.]+)")
|
|
||||||
with codecs.open(path, mode="rb", encoding="utf-8") as f:
|
|
||||||
for line in f:
|
|
||||||
match = version_file_re.match(line)
|
|
||||||
if match:
|
|
||||||
version = match.group(1)
|
|
||||||
|
|
||||||
if version:
|
|
||||||
LOG.info(f"{name} {path} declares standards version {version}")
|
|
||||||
|
|
||||||
return version
|
|
||||||
|
|
||||||
def _filter_standards(self):
|
|
||||||
target_standards = []
|
|
||||||
includes = self.config["rules"]["filter"]
|
|
||||||
excludes = self.config["rules"]["exclude_filter"]
|
excludes = self.config["rules"]["exclude_filter"]
|
||||||
|
|
||||||
if len(includes) == 0:
|
if len(includes) == 0:
|
||||||
includes = [s.sid for s in self.standards]
|
includes = [s.rid for s in self.rules]
|
||||||
|
|
||||||
for standard in self.standards:
|
for rule in self.rules:
|
||||||
if standard.sid in includes and standard.sid not in excludes:
|
if rule.rid in includes and rule.rid not in excludes:
|
||||||
target_standards.append(standard)
|
target_rules.append(rule)
|
||||||
|
|
||||||
return target_standards
|
return target_rules
|
||||||
|
|
||||||
def review(self):
|
def review(self):
|
||||||
errors = 0
|
errors = 0
|
||||||
self.standards = SingleStandards(self.config["rules"]["standards"]).rules
|
self.rules = SingleRules(self.config["rules"]["dir"]).rules
|
||||||
self.version_config = self._get_version()
|
|
||||||
self.version = self.version_config or utils.standards_latest(self.standards)
|
|
||||||
|
|
||||||
for standard in self._filter_standards():
|
for rule in self._filter_rules():
|
||||||
if type(self).__name__.lower() not in standard.types:
|
if self.kind not in rule.types:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
result = standard.check(self, self.config)
|
result = rule.check(self, self.config)
|
||||||
|
|
||||||
if not result:
|
if not result:
|
||||||
LOG.error(
|
LOG.error(f"rule '{rule.rid}' returns an empty result object. Check failed!")
|
||||||
f"Standard '{standard.sid}' returns an empty result object. Check failed!"
|
|
||||||
)
|
|
||||||
continue
|
continue
|
||||||
|
|
||||||
labels = {
|
labels = {
|
||||||
"tag": "review",
|
"tag": "review",
|
||||||
"standard": standard.description,
|
"rule": rule.description,
|
||||||
"file": self.path,
|
"file": self.path,
|
||||||
"passed": True,
|
"passed": True,
|
||||||
}
|
}
|
||||||
|
|
||||||
if standard.sid and standard.sid.strip():
|
if rule.rid and rule.rid.strip():
|
||||||
labels["sid"] = standard.sid
|
labels["rid"] = rule.rid
|
||||||
|
|
||||||
for err in result.errors:
|
for err in result.errors:
|
||||||
err_labels = copy.copy(labels)
|
err_labels = copy.copy(labels)
|
||||||
err_labels["passed"] = False
|
err_labels["passed"] = False
|
||||||
|
|
||||||
sid = self._format_id(standard.sid)
|
rid = self._format_id(rule.rid)
|
||||||
path = self.path
|
path = self.path
|
||||||
description = standard.description
|
description = rule.description
|
||||||
|
|
||||||
if isinstance(err, StandardBase.Error):
|
if isinstance(err, RuleBase.Error):
|
||||||
err_labels.update(err.to_dict())
|
err_labels.update(err.to_dict())
|
||||||
|
|
||||||
if not standard.version:
|
msg = f"{rid}rule '{description}' not met:\n{path}:{err}"
|
||||||
LOG.warning(
|
|
||||||
f"{sid}Best practice '{description}' not met:\n{path}:{err}",
|
|
||||||
extra=flag_extra(err_labels),
|
|
||||||
)
|
|
||||||
elif Version(standard.version) > Version(self.version):
|
|
||||||
LOG.warning(
|
|
||||||
f"{sid}Future standard '{description}' not met:\n{path}:{err}",
|
|
||||||
extra=flag_extra(err_labels),
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
msg = f"{sid}Standard '{description}' not met:\n{path}:{err}"
|
|
||||||
|
|
||||||
if standard.sid not in self.config["rules"]["warning_filter"]:
|
if rule.rid not in self.config["rules"]["warning_filter"]:
|
||||||
LOG.error(msg, extra=flag_extra(err_labels))
|
LOG.error(msg, extra=flag_extra(err_labels))
|
||||||
errors = errors + 1
|
errors = errors + 1
|
||||||
else:
|
else:
|
||||||
@ -146,57 +96,57 @@ class Candidate:
|
|||||||
return errors
|
return errors
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def classify(filename, settings={}, standards=[]): # noqa
|
def classify(filename, settings={}, rules=[]): # noqa
|
||||||
parentdir = os.path.basename(os.path.dirname(filename))
|
parentdir = os.path.basename(os.path.dirname(filename))
|
||||||
basename = os.path.basename(filename)
|
basename = os.path.basename(filename)
|
||||||
ext = os.path.splitext(filename)[1][1:]
|
ext = os.path.splitext(filename)[1][1:]
|
||||||
|
|
||||||
if parentdir in ["tasks"]:
|
if parentdir in ["tasks"]:
|
||||||
return Task(filename, settings, standards)
|
return Task(filename, settings, rules)
|
||||||
if parentdir in ["handlers"]:
|
if parentdir in ["handlers"]:
|
||||||
return Handler(filename, settings, standards)
|
return Handler(filename, settings, rules)
|
||||||
if parentdir in ["vars", "defaults"]:
|
if parentdir in ["vars", "defaults"]:
|
||||||
return RoleVars(filename, settings, standards)
|
return RoleVars(filename, settings, rules)
|
||||||
if "group_vars" in filename.split(os.sep):
|
if "group_vars" in filename.split(os.sep):
|
||||||
return GroupVars(filename, settings, standards)
|
return GroupVars(filename, settings, rules)
|
||||||
if "host_vars" in filename.split(os.sep):
|
if "host_vars" in filename.split(os.sep):
|
||||||
return HostVars(filename, settings, standards)
|
return HostVars(filename, settings, rules)
|
||||||
if parentdir in ["meta"] and "main" in basename:
|
if parentdir in ["meta"] and "main" in basename:
|
||||||
return Meta(filename, settings, standards)
|
return Meta(filename, settings, rules)
|
||||||
if parentdir in ["meta"] and "argument_specs" in basename:
|
if parentdir in ["meta"] and "argument_specs" in basename:
|
||||||
return ArgumentSpecs(filename, settings, standards)
|
return ArgumentSpecs(filename, settings, rules)
|
||||||
if parentdir in [
|
if parentdir in [
|
||||||
"library",
|
"library",
|
||||||
"lookup_plugins",
|
"lookup_plugins",
|
||||||
"callback_plugins",
|
"callback_plugins",
|
||||||
"filter_plugins",
|
"filter_plugins",
|
||||||
] or filename.endswith(".py"):
|
] or filename.endswith(".py"):
|
||||||
return Code(filename, settings, standards)
|
return Code(filename, settings, rules)
|
||||||
if basename == "inventory" or basename == "hosts" or parentdir in ["inventories"]:
|
if basename == "inventory" or basename == "hosts" or parentdir in ["inventories"]:
|
||||||
return Inventory(filename, settings, standards)
|
return Inventory(filename, settings, rules)
|
||||||
if "rolesfile" in basename or ("requirements" in basename and ext in ["yaml", "yml"]):
|
if "rolesfile" in basename or ("requirements" in basename and ext in ["yaml", "yml"]):
|
||||||
return Rolesfile(filename, settings, standards)
|
return Rolesfile(filename, settings, rules)
|
||||||
if "Makefile" in basename:
|
if "Makefile" in basename:
|
||||||
return Makefile(filename, settings, standards)
|
return Makefile(filename, settings, rules)
|
||||||
if "templates" in filename.split(os.sep) or basename.endswith(".j2"):
|
if "templates" in filename.split(os.sep) or basename.endswith(".j2"):
|
||||||
return Template(filename, settings, standards)
|
return Template(filename, settings, rules)
|
||||||
if "files" in filename.split(os.sep):
|
if "files" in filename.split(os.sep):
|
||||||
return File(filename, settings, standards)
|
return File(filename, settings, rules)
|
||||||
if basename.endswith(".yml") or basename.endswith(".yaml"):
|
if basename.endswith(".yml") or basename.endswith(".yaml"):
|
||||||
return Playbook(filename, settings, standards)
|
return Playbook(filename, settings, rules)
|
||||||
if "README" in basename:
|
if "README" in basename:
|
||||||
return Doc(filename, settings, standards)
|
return Doc(filename, settings, rules)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def _format_id(self, standard_id):
|
def _format_id(self, rule_id):
|
||||||
sid = standard_id.strip()
|
rid = rule_id.strip()
|
||||||
if sid:
|
if rid:
|
||||||
standard_id = f"[{sid}] "
|
rule_id = f"[{rid}] "
|
||||||
|
|
||||||
return standard_id
|
return rule_id
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return f"{type(self).__name__} ({self.path})"
|
return f"{self.kind} ({self.path})"
|
||||||
|
|
||||||
def __getitem__(self, item):
|
def __getitem__(self, item):
|
||||||
return self.__dict__.get(item)
|
return self.__dict__.get(item)
|
||||||
@ -205,8 +155,8 @@ class Candidate:
|
|||||||
class RoleFile(Candidate):
|
class RoleFile(Candidate):
|
||||||
"""Object classified as Ansible role file."""
|
"""Object classified as Ansible role file."""
|
||||||
|
|
||||||
def __init__(self, filename, settings={}, standards=[]): # noqa
|
def __init__(self, filename, settings={}, rules=[]): # noqa
|
||||||
super().__init__(filename, settings, standards)
|
super().__init__(filename, settings, rules)
|
||||||
|
|
||||||
parentdir = os.path.dirname(os.path.abspath(filename))
|
parentdir = os.path.dirname(os.path.abspath(filename))
|
||||||
while parentdir != os.path.dirname(parentdir):
|
while parentdir != os.path.dirname(parentdir):
|
||||||
@ -226,17 +176,17 @@ class Playbook(Candidate):
|
|||||||
class Task(RoleFile):
|
class Task(RoleFile):
|
||||||
"""Object classified as Ansible task file."""
|
"""Object classified as Ansible task file."""
|
||||||
|
|
||||||
def __init__(self, filename, settings={}, standards=[]): # noqa
|
def __init__(self, filename, settings={}, rules=[]): # noqa
|
||||||
super().__init__(filename, settings, standards)
|
super().__init__(filename, settings, rules)
|
||||||
self.filetype = "tasks"
|
self.filemeta = "tasks"
|
||||||
|
|
||||||
|
|
||||||
class Handler(RoleFile):
|
class Handler(RoleFile):
|
||||||
"""Object classified as Ansible handler file."""
|
"""Object classified as Ansible handler file."""
|
||||||
|
|
||||||
def __init__(self, filename, settings={}, standards=[]): # noqa
|
def __init__(self, filename, settings={}, rules=[]): # noqa
|
||||||
super().__init__(filename, settings, standards)
|
super().__init__(filename, settings, rules)
|
||||||
self.filetype = "handlers"
|
self.filemeta = "handlers"
|
||||||
|
|
||||||
|
|
||||||
class Vars(Candidate):
|
class Vars(Candidate):
|
||||||
|
@ -82,7 +82,7 @@ class LogFilter:
|
|||||||
class MultilineFormatter(logging.Formatter):
|
class MultilineFormatter(logging.Formatter):
|
||||||
"""Logging Formatter to reset color after newline characters."""
|
"""Logging Formatter to reset color after newline characters."""
|
||||||
|
|
||||||
def format(self, record): # noqa
|
def format(self, record):
|
||||||
record.msg = record.msg.replace("\n", f"\n{colorama.Style.RESET_ALL}... ")
|
record.msg = record.msg.replace("\n", f"\n{colorama.Style.RESET_ALL}... ")
|
||||||
record.msg = record.msg + "\n"
|
record.msg = record.msg + "\n"
|
||||||
return logging.Formatter.format(self, record)
|
return logging.Formatter.format(self, record)
|
||||||
@ -91,7 +91,7 @@ class MultilineFormatter(logging.Formatter):
|
|||||||
class MultilineJsonFormatter(jsonlogger.JsonFormatter):
|
class MultilineJsonFormatter(jsonlogger.JsonFormatter):
|
||||||
"""Logging Formatter to remove newline characters."""
|
"""Logging Formatter to remove newline characters."""
|
||||||
|
|
||||||
def format(self, record): # noqa
|
def format(self, record):
|
||||||
record.msg = record.msg.replace("\n", " ")
|
record.msg = record.msg.replace("\n", " ")
|
||||||
return jsonlogger.JsonFormatter.format(self, record)
|
return jsonlogger.JsonFormatter.format(self, record)
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
"""Standard definition."""
|
"""Rule definition."""
|
||||||
|
|
||||||
import copy
|
import copy
|
||||||
import importlib
|
import importlib
|
||||||
@ -27,27 +27,26 @@ from ansiblelater.utils.yamlhelper import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class StandardMeta(type):
|
class RuleMeta(type):
|
||||||
def __call__(cls, *args):
|
def __call__(cls, *args):
|
||||||
mcls = type.__call__(cls, *args)
|
mcls = type.__call__(cls, *args)
|
||||||
mcls.sid = cls.sid
|
mcls.rid = cls.rid
|
||||||
mcls.description = getattr(cls, "description", "__unknown__")
|
mcls.description = getattr(cls, "description", "__unknown__")
|
||||||
mcls.helptext = getattr(cls, "helptext", "")
|
mcls.helptext = getattr(cls, "helptext", "")
|
||||||
mcls.version = getattr(cls, "version", None)
|
|
||||||
mcls.types = getattr(cls, "types", [])
|
mcls.types = getattr(cls, "types", [])
|
||||||
return mcls
|
return mcls
|
||||||
|
|
||||||
|
|
||||||
class StandardExtendedMeta(StandardMeta, ABCMeta):
|
class RuleExtendedMeta(RuleMeta, ABCMeta):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class StandardBase(metaclass=StandardExtendedMeta):
|
class RuleBase(metaclass=RuleExtendedMeta):
|
||||||
SHELL_PIPE_CHARS = "&|<>;$\n*[]{}?"
|
SHELL_PIPE_CHARS = "&|<>;$\n*[]{}?"
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def sid(self):
|
def rid(self):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
@ -55,7 +54,7 @@ class StandardBase(metaclass=StandardExtendedMeta):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return f"Standard: {self.description} (version: {self.version}, types: {self.types})"
|
return f"Rule: {self.description} (types: {self.types})"
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_tasks(candidate, settings): # noqa
|
def get_tasks(candidate, settings): # noqa
|
||||||
@ -69,11 +68,11 @@ class StandardBase(metaclass=StandardExtendedMeta):
|
|||||||
except LaterError as ex:
|
except LaterError as ex:
|
||||||
e = ex.original
|
e = ex.original
|
||||||
errors.append(
|
errors.append(
|
||||||
StandardBase.Error(e.problem_mark.line + 1, f"syntax error: {e.problem}")
|
RuleBase.Error(e.problem_mark.line + 1, f"syntax error: {e.problem}")
|
||||||
)
|
)
|
||||||
candidate.faulty = True
|
candidate.faulty = True
|
||||||
except LaterAnsibleError as e:
|
except LaterAnsibleError as e:
|
||||||
errors.append(StandardBase.Error(e.line, f"syntax error: {e.message}"))
|
errors.append(RuleBase.Error(e.line, f"syntax error: {e.message}"))
|
||||||
candidate.faulty = True
|
candidate.faulty = True
|
||||||
|
|
||||||
return yamllines, errors
|
return yamllines, errors
|
||||||
@ -93,11 +92,11 @@ class StandardBase(metaclass=StandardExtendedMeta):
|
|||||||
except LaterError as ex:
|
except LaterError as ex:
|
||||||
e = ex.original
|
e = ex.original
|
||||||
errors.append(
|
errors.append(
|
||||||
StandardBase.Error(e.problem_mark.line + 1, f"syntax error: {e.problem}")
|
RuleBase.Error(e.problem_mark.line + 1, f"syntax error: {e.problem}")
|
||||||
)
|
)
|
||||||
candidate.faulty = True
|
candidate.faulty = True
|
||||||
except LaterAnsibleError as e:
|
except LaterAnsibleError as e:
|
||||||
errors.append(StandardBase.Error(e.line, f"syntax error: {e.message}"))
|
errors.append(RuleBase.Error(e.line, f"syntax error: {e.message}"))
|
||||||
candidate.faulty = True
|
candidate.faulty = True
|
||||||
|
|
||||||
return tasks, errors
|
return tasks, errors
|
||||||
@ -115,11 +114,11 @@ class StandardBase(metaclass=StandardExtendedMeta):
|
|||||||
except LaterError as ex:
|
except LaterError as ex:
|
||||||
e = ex.original
|
e = ex.original
|
||||||
errors.append(
|
errors.append(
|
||||||
StandardBase.Error(e.problem_mark.line + 1, f"syntax error: {e.problem}")
|
RuleBase.Error(e.problem_mark.line + 1, f"syntax error: {e.problem}")
|
||||||
)
|
)
|
||||||
candidate.faulty = True
|
candidate.faulty = True
|
||||||
except LaterAnsibleError as e:
|
except LaterAnsibleError as e:
|
||||||
errors.append(StandardBase.Error(e.line, f"syntax error: {e.message}"))
|
errors.append(RuleBase.Error(e.line, f"syntax error: {e.message}"))
|
||||||
candidate.faulty = True
|
candidate.faulty = True
|
||||||
|
|
||||||
return normalized, errors
|
return normalized, errors
|
||||||
@ -150,20 +149,21 @@ class StandardBase(metaclass=StandardExtendedMeta):
|
|||||||
# No need to normalize_task if we are skipping it.
|
# No need to normalize_task if we are skipping it.
|
||||||
continue
|
continue
|
||||||
|
|
||||||
normalized.append(
|
normalized_task = normalize_task(
|
||||||
normalize_task(
|
|
||||||
task, candidate.path, settings["ansible"]["custom_modules"]
|
task, candidate.path, settings["ansible"]["custom_modules"]
|
||||||
)
|
)
|
||||||
)
|
normalized_task["__raw_task__"] = task
|
||||||
|
|
||||||
|
normalized.append(normalized_task)
|
||||||
|
|
||||||
except LaterError as ex:
|
except LaterError as ex:
|
||||||
e = ex.original
|
e = ex.original
|
||||||
errors.append(
|
errors.append(
|
||||||
StandardBase.Error(e.problem_mark.line + 1, f"syntax error: {e.problem}")
|
RuleBase.Error(e.problem_mark.line + 1, f"syntax error: {e.problem}")
|
||||||
)
|
)
|
||||||
candidate.faulty = True
|
candidate.faulty = True
|
||||||
except LaterAnsibleError as e:
|
except LaterAnsibleError as e:
|
||||||
errors.append(StandardBase.Error(e.line, f"syntax error: {e.message}"))
|
errors.append(RuleBase.Error(e.line, f"syntax error: {e.message}"))
|
||||||
candidate.faulty = True
|
candidate.faulty = True
|
||||||
|
|
||||||
return normalized, errors
|
return normalized, errors
|
||||||
@ -184,11 +184,11 @@ class StandardBase(metaclass=StandardExtendedMeta):
|
|||||||
except LaterError as ex:
|
except LaterError as ex:
|
||||||
e = ex.original
|
e = ex.original
|
||||||
errors.append(
|
errors.append(
|
||||||
StandardBase.Error(e.problem_mark.line + 1, f"syntax error: {e.problem}")
|
RuleBase.Error(e.problem_mark.line + 1, f"syntax error: {e.problem}")
|
||||||
)
|
)
|
||||||
candidate.faulty = True
|
candidate.faulty = True
|
||||||
except LaterAnsibleError as e:
|
except LaterAnsibleError as e:
|
||||||
errors.append(StandardBase.Error(e.line, f"syntax error: {e.message}"))
|
errors.append(RuleBase.Error(e.line, f"syntax error: {e.message}"))
|
||||||
candidate.faulty = True
|
candidate.faulty = True
|
||||||
|
|
||||||
return yamllines, errors
|
return yamllines, errors
|
||||||
@ -210,7 +210,7 @@ class StandardBase(metaclass=StandardExtendedMeta):
|
|||||||
content = yaml.safe_load(f)
|
content = yaml.safe_load(f)
|
||||||
except yaml.YAMLError as e:
|
except yaml.YAMLError as e:
|
||||||
errors.append(
|
errors.append(
|
||||||
StandardBase.Error(e.problem_mark.line + 1, f"syntax error: {e.problem}")
|
RuleBase.Error(e.problem_mark.line + 1, f"syntax error: {e.problem}")
|
||||||
)
|
)
|
||||||
candidate.faulty = True
|
candidate.faulty = True
|
||||||
|
|
||||||
@ -224,14 +224,14 @@ class StandardBase(metaclass=StandardExtendedMeta):
|
|||||||
try:
|
try:
|
||||||
with open(candidate.path, encoding="utf-8") as f:
|
with open(candidate.path, encoding="utf-8") as f:
|
||||||
for problem in linter.run(f, YamlLintConfig(options)):
|
for problem in linter.run(f, YamlLintConfig(options)):
|
||||||
errors.append(StandardBase.Error(problem.line, problem.desc))
|
errors.append(RuleBase.Error(problem.line, problem.desc))
|
||||||
except yaml.YAMLError as e:
|
except yaml.YAMLError as e:
|
||||||
errors.append(
|
errors.append(
|
||||||
StandardBase.Error(e.problem_mark.line + 1, f"syntax error: {e.problem}")
|
RuleBase.Error(e.problem_mark.line + 1, f"syntax error: {e.problem}")
|
||||||
)
|
)
|
||||||
candidate.faulty = True
|
candidate.faulty = True
|
||||||
except (TypeError, ValueError) as e:
|
except (TypeError, ValueError) as e:
|
||||||
errors.append(StandardBase.Error(None, f"yamllint error: {e}"))
|
errors.append(RuleBase.Error(None, f"yamllint error: {e}"))
|
||||||
candidate.faulty = True
|
candidate.faulty = True
|
||||||
|
|
||||||
return errors
|
return errors
|
||||||
@ -302,7 +302,7 @@ class StandardBase(metaclass=StandardExtendedMeta):
|
|||||||
return "\n".join([f"{self.candidate}:{error}" for error in self.errors])
|
return "\n".join([f"{self.candidate}:{error}" for error in self.errors])
|
||||||
|
|
||||||
|
|
||||||
class StandardLoader:
|
class RulesLoader:
|
||||||
def __init__(self, source):
|
def __init__(self, source):
|
||||||
self.rules = []
|
self.rules = []
|
||||||
|
|
||||||
@ -331,23 +331,20 @@ class StandardLoader:
|
|||||||
|
|
||||||
def _is_plugin(self, obj):
|
def _is_plugin(self, obj):
|
||||||
return (
|
return (
|
||||||
inspect.isclass(obj)
|
inspect.isclass(obj) and issubclass(obj, RuleBase) and obj is not RuleBase and not None
|
||||||
and issubclass(obj, StandardBase)
|
|
||||||
and obj is not StandardBase
|
|
||||||
and not None
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def validate(self):
|
def validate(self):
|
||||||
normalized_std = list(toolz.remove(lambda x: x.sid == "", self.rules))
|
normalize_rule = list(toolz.remove(lambda x: x.rid == "", self.rules))
|
||||||
unique_std = len(list(toolz.unique(normalized_std, key=lambda x: x.sid)))
|
unique_rule = len(list(toolz.unique(normalize_rule, key=lambda x: x.rid)))
|
||||||
all_std = len(normalized_std)
|
all_rules = len(normalize_rule)
|
||||||
if all_std != unique_std:
|
if all_rules != unique_rule:
|
||||||
sysexit_with_message(
|
sysexit_with_message(
|
||||||
"Detect duplicate ID's in standards definition. Please use unique ID's only."
|
"Found duplicate tags in rules definition. Please use unique tags only."
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class SingleStandards(StandardLoader, metaclass=Singleton):
|
class SingleRules(RulesLoader, metaclass=Singleton):
|
||||||
"""Singleton config class."""
|
"""Singleton config class."""
|
||||||
|
|
||||||
pass
|
pass
|
@ -1,11 +1,10 @@
|
|||||||
from ansiblelater.standard import StandardBase
|
from ansiblelater.rule import RuleBase
|
||||||
|
|
||||||
|
|
||||||
class CheckBecomeUser(StandardBase):
|
class CheckBecomeUser(RuleBase):
|
||||||
sid = "ANSIBLE0015"
|
rid = "ANS115"
|
||||||
description = "Become should be combined with become_user"
|
description = "Become should be combined with become_user"
|
||||||
helptext = "the task has `become` enabled but `become_user` is missing"
|
helptext = "the task has `become` enabled but `become_user` is missing"
|
||||||
version = "0.1"
|
|
||||||
types = ["playbook", "task", "handler"]
|
types = ["playbook", "task", "handler"]
|
||||||
|
|
||||||
def check(self, candidate, settings):
|
def check(self, candidate, settings):
|
||||||
|
@ -1,14 +1,13 @@
|
|||||||
import re
|
import re
|
||||||
|
|
||||||
from ansiblelater.standard import StandardBase
|
from ansiblelater.rule import RuleBase
|
||||||
from ansiblelater.utils import count_spaces
|
from ansiblelater.utils import count_spaces
|
||||||
|
|
||||||
|
|
||||||
class CheckBracesSpaces(StandardBase):
|
class CheckBracesSpaces(RuleBase):
|
||||||
sid = "ANSIBLE0004"
|
rid = "ANS104"
|
||||||
description = "YAML should use consistent number of spaces around variables"
|
description = "YAML should use consistent number of spaces around variables"
|
||||||
helptext = "no suitable numbers of spaces (min: {min} max: {max})"
|
helptext = "no suitable numbers of spaces (min: {min} max: {max})"
|
||||||
version = "0.1"
|
|
||||||
types = ["playbook", "task", "handler", "rolevars", "hostvars", "groupvars", "meta"]
|
types = ["playbook", "task", "handler", "rolevars", "hostvars", "groupvars", "meta"]
|
||||||
|
|
||||||
def check(self, candidate, settings):
|
def check(self, candidate, settings):
|
||||||
|
@ -18,14 +18,13 @@
|
|||||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
# THE SOFTWARE.
|
# THE SOFTWARE.
|
||||||
|
|
||||||
from ansiblelater.standard import StandardBase
|
from ansiblelater.rule import RuleBase
|
||||||
|
|
||||||
|
|
||||||
class CheckChangedInWhen(StandardBase):
|
class CheckChangedInWhen(RuleBase):
|
||||||
sid = "ANSIBLE0026"
|
rid = "ANS126"
|
||||||
description = "Use handlers instead of `when: changed`"
|
description = "Use handlers instead of `when: changed`"
|
||||||
helptext = "tasks using `when: result.changed` setting are effectively acting as a handler"
|
helptext = "tasks using `when: result.changed` setting are effectively acting as a handler"
|
||||||
version = "0.2"
|
|
||||||
types = ["playbook", "task", "handler"]
|
types = ["playbook", "task", "handler"]
|
||||||
|
|
||||||
def check(self, candidate, settings):
|
def check(self, candidate, settings):
|
||||||
|
@ -1,14 +1,13 @@
|
|||||||
from ansiblelater.standard import StandardBase
|
from ansiblelater.rule import RuleBase
|
||||||
|
|
||||||
|
|
||||||
class CheckCommandHasChanges(StandardBase):
|
class CheckCommandHasChanges(RuleBase):
|
||||||
sid = "ANSIBLE0011"
|
rid = "ANS111"
|
||||||
description = "Commands should be idempotent"
|
description = "Commands should be idempotent"
|
||||||
helptext = (
|
helptext = (
|
||||||
"commands should only read while using `changed_when` or try to be "
|
"commands should only read while using `changed_when` or try to be "
|
||||||
"idempotent while using controls like `creates`, `removes` or `when`"
|
"idempotent while using controls like `creates`, `removes` or `when`"
|
||||||
)
|
)
|
||||||
version = "0.1"
|
|
||||||
types = ["playbook", "task"]
|
types = ["playbook", "task"]
|
||||||
|
|
||||||
def check(self, candidate, settings):
|
def check(self, candidate, settings):
|
||||||
|
@ -20,14 +20,13 @@
|
|||||||
|
|
||||||
import os
|
import os
|
||||||
|
|
||||||
from ansiblelater.standard import StandardBase
|
from ansiblelater.rule import RuleBase
|
||||||
|
|
||||||
|
|
||||||
class CheckCommandInsteadOfArgument(StandardBase):
|
class CheckCommandInsteadOfArgument(RuleBase):
|
||||||
sid = "ANSIBLE0017"
|
rid = "ANS117"
|
||||||
description = "Commands should not be used in place of module arguments"
|
description = "Commands should not be used in place of module arguments"
|
||||||
helptext = "{exec} used in place of file modules argument {arg}"
|
helptext = "{exec} used in place of file modules argument {arg}"
|
||||||
version = "0.2"
|
|
||||||
types = ["playbook", "task", "handler"]
|
types = ["playbook", "task", "handler"]
|
||||||
|
|
||||||
def check(self, candidate, settings):
|
def check(self, candidate, settings):
|
||||||
|
@ -1,13 +1,12 @@
|
|||||||
import os
|
import os
|
||||||
|
|
||||||
from ansiblelater.standard import StandardBase
|
from ansiblelater.rule import RuleBase
|
||||||
|
|
||||||
|
|
||||||
class CheckCommandInsteadOfModule(StandardBase):
|
class CheckCommandInsteadOfModule(RuleBase):
|
||||||
sid = "ANSIBLE0008"
|
rid = "ANS108"
|
||||||
description = "Commands should not be used in place of modules"
|
description = "Commands should not be used in place of modules"
|
||||||
helptext = "{exec} command used in place of {module} module"
|
helptext = "{exec} command used in place of {module} module"
|
||||||
version = "0.1"
|
|
||||||
types = ["playbook", "task", "handler"]
|
types = ["playbook", "task", "handler"]
|
||||||
|
|
||||||
def check(self, candidate, settings):
|
def check(self, candidate, settings):
|
||||||
|
@ -1,14 +1,13 @@
|
|||||||
import re
|
import re
|
||||||
|
|
||||||
from ansiblelater.candidate import Template
|
from ansiblelater.candidate import Template
|
||||||
from ansiblelater.standard import StandardBase
|
from ansiblelater.rule import RuleBase
|
||||||
|
|
||||||
|
|
||||||
class CheckCompareToEmptyString(StandardBase):
|
class CheckCompareToEmptyString(RuleBase):
|
||||||
sid = "ANSIBLE0012"
|
rid = "ANS112"
|
||||||
description = 'Don\'t compare to empty string ""'
|
description = 'Don\'t compare to empty string ""'
|
||||||
helptext = "use `when: var` rather than `when: var !=` (or conversely `when: not var`)"
|
helptext = "use `when: var` rather than `when: var !=` (or conversely `when: not var`)"
|
||||||
version = "0.1"
|
|
||||||
types = ["playbook", "task", "handler", "template"]
|
types = ["playbook", "task", "handler", "template"]
|
||||||
|
|
||||||
def check(self, candidate, settings):
|
def check(self, candidate, settings):
|
||||||
|
@ -1,14 +1,13 @@
|
|||||||
import re
|
import re
|
||||||
|
|
||||||
from ansiblelater.candidate import Template
|
from ansiblelater.candidate import Template
|
||||||
from ansiblelater.standard import StandardBase
|
from ansiblelater.rule import RuleBase
|
||||||
|
|
||||||
|
|
||||||
class CheckCompareToLiteralBool(StandardBase):
|
class CheckCompareToLiteralBool(RuleBase):
|
||||||
sid = "ANSIBLE0013"
|
rid = "ANS113"
|
||||||
description = "Don't compare to True or False"
|
description = "Don't compare to True or False"
|
||||||
helptext = "use `when: var` rather than `when: var == True` (or conversely `when: not var`)"
|
helptext = "use `when: var` rather than `when: var == True` (or conversely `when: not var`)"
|
||||||
version = "0.1"
|
|
||||||
types = ["playbook", "task", "handler"]
|
types = ["playbook", "task", "handler"]
|
||||||
|
|
||||||
def check(self, candidate, settings):
|
def check(self, candidate, settings):
|
||||||
|
@ -1,11 +1,10 @@
|
|||||||
from ansiblelater.standard import StandardBase
|
from ansiblelater.rule import RuleBase
|
||||||
|
|
||||||
|
|
||||||
class CheckDeprecated(StandardBase):
|
class CheckDeprecated(RuleBase):
|
||||||
sid = "ANSIBLE9999"
|
rid = "ANS999"
|
||||||
description = "Deprecated features should not be used"
|
description = "Deprecated features should not be used"
|
||||||
helptext = "'{old}' is deprecated and should not be used anymore. Use '{new}' instead."
|
helptext = "`{old}` is deprecated and should not be used anymore. Use `{new}` instead."
|
||||||
version = "0.1"
|
|
||||||
types = ["playbook", "task", "handler"]
|
types = ["playbook", "task", "handler"]
|
||||||
|
|
||||||
def check(self, candidate, settings):
|
def check(self, candidate, settings):
|
||||||
|
@ -20,18 +20,17 @@
|
|||||||
|
|
||||||
import os
|
import os
|
||||||
|
|
||||||
from ansiblelater.standard import StandardBase
|
from ansiblelater.rule import RuleBase
|
||||||
from ansiblelater.utils import has_glob, has_jinja
|
from ansiblelater.utils import has_glob, has_jinja
|
||||||
|
|
||||||
|
|
||||||
class CheckDeprecatedBareVars(StandardBase):
|
class CheckDeprecatedBareVars(RuleBase):
|
||||||
sid = "ANSIBLE0027"
|
rid = "ANS127"
|
||||||
description = "Deprecated bare variables in loops must not be used"
|
description = "Deprecated bare variables in loops must not be used"
|
||||||
helptext = (
|
helptext = (
|
||||||
"bare var '{barevar}' in '{loop_type}' must use full var syntax '{{{{ {barevar} }}}}' "
|
"bare var '{barevar}' in '{loop_type}' must use full var syntax '{{{{ {barevar} }}}}' "
|
||||||
"or be converted to a list"
|
"or be converted to a list"
|
||||||
)
|
)
|
||||||
version = "0.3"
|
|
||||||
types = ["playbook", "task", "handler"]
|
types = ["playbook", "task", "handler"]
|
||||||
|
|
||||||
def check(self, candidate, settings):
|
def check(self, candidate, settings):
|
||||||
|
132
ansiblelater/rules/CheckFQCNBuiltin.py
Normal file
132
ansiblelater/rules/CheckFQCNBuiltin.py
Normal file
@ -0,0 +1,132 @@
|
|||||||
|
# Original code written by the authors of ansible-lint
|
||||||
|
|
||||||
|
from ansiblelater.rule import RuleBase
|
||||||
|
from ansiblelater.utils import load_plugin
|
||||||
|
|
||||||
|
|
||||||
|
class CheckFQCNBuiltin(RuleBase):
|
||||||
|
rid = "ANS128"
|
||||||
|
helptext = "use FQCN `{module_alias}` for module action `{module}`"
|
||||||
|
description = "Module actions should use full qualified collection names"
|
||||||
|
types = ["playbook", "task", "handler", "rolevars", "hostvars", "groupvars"]
|
||||||
|
module_aliases = {"block/always/rescue": "block/always/rescue"}
|
||||||
|
|
||||||
|
def check(self, candidate, settings):
|
||||||
|
tasks, errors = self.get_normalized_tasks(candidate, settings)
|
||||||
|
|
||||||
|
_builtins = [
|
||||||
|
"add_host",
|
||||||
|
"apt",
|
||||||
|
"apt_key",
|
||||||
|
"apt_repository",
|
||||||
|
"assemble",
|
||||||
|
"assert",
|
||||||
|
"async_status",
|
||||||
|
"blockinfile",
|
||||||
|
"command",
|
||||||
|
"copy",
|
||||||
|
"cron",
|
||||||
|
"debconf",
|
||||||
|
"debug",
|
||||||
|
"dnf",
|
||||||
|
"dpkg_selections",
|
||||||
|
"expect",
|
||||||
|
"fail",
|
||||||
|
"fetch",
|
||||||
|
"file",
|
||||||
|
"find",
|
||||||
|
"gather_facts",
|
||||||
|
"get_url",
|
||||||
|
"getent",
|
||||||
|
"git",
|
||||||
|
"group",
|
||||||
|
"group_by",
|
||||||
|
"hostname",
|
||||||
|
"import_playbook",
|
||||||
|
"import_role",
|
||||||
|
"import_tasks",
|
||||||
|
"include",
|
||||||
|
"include_role",
|
||||||
|
"include_tasks",
|
||||||
|
"include_vars",
|
||||||
|
"iptables",
|
||||||
|
"known_hosts",
|
||||||
|
"lineinfile",
|
||||||
|
"meta",
|
||||||
|
"package",
|
||||||
|
"package_facts",
|
||||||
|
"pause",
|
||||||
|
"ping",
|
||||||
|
"pip",
|
||||||
|
"raw",
|
||||||
|
"reboot",
|
||||||
|
"replace",
|
||||||
|
"rpm_key",
|
||||||
|
"script",
|
||||||
|
"service",
|
||||||
|
"service_facts",
|
||||||
|
"set_fact",
|
||||||
|
"set_stats",
|
||||||
|
"setup",
|
||||||
|
"shell",
|
||||||
|
"slurp",
|
||||||
|
"stat",
|
||||||
|
"subversion",
|
||||||
|
"systemd",
|
||||||
|
"sysvinit",
|
||||||
|
"tempfile",
|
||||||
|
"template",
|
||||||
|
"unarchive",
|
||||||
|
"uri",
|
||||||
|
"user",
|
||||||
|
"wait_for",
|
||||||
|
"wait_for_connection",
|
||||||
|
"yum",
|
||||||
|
"yum_repository",
|
||||||
|
]
|
||||||
|
|
||||||
|
if errors:
|
||||||
|
return self.Result(candidate.path, errors)
|
||||||
|
|
||||||
|
for task in tasks:
|
||||||
|
module = task["action"]["__ansible_module_original__"]
|
||||||
|
|
||||||
|
if module not in self.module_aliases:
|
||||||
|
loaded_module = load_plugin(module)
|
||||||
|
target = loaded_module.resolved_fqcn
|
||||||
|
self.module_aliases[module] = target
|
||||||
|
|
||||||
|
if target is None:
|
||||||
|
self.module_aliases[module] = module
|
||||||
|
continue
|
||||||
|
|
||||||
|
if target not in self.module_aliases:
|
||||||
|
self.module_aliases[target] = target
|
||||||
|
|
||||||
|
if module != self.module_aliases[module]:
|
||||||
|
module_alias = self.module_aliases[module]
|
||||||
|
if module_alias.startswith("ansible.builtin"):
|
||||||
|
legacy_module = module_alias.replace(
|
||||||
|
"ansible.builtin.",
|
||||||
|
"ansible.legacy.",
|
||||||
|
1,
|
||||||
|
)
|
||||||
|
if module != legacy_module:
|
||||||
|
helptext = self.helptext.format(module_alias=module_alias, module=module)
|
||||||
|
if module == "ansible.builtin.include":
|
||||||
|
helptext = (
|
||||||
|
"`ansible.builtin.include_task` or `ansible.builtin.import_tasks` "
|
||||||
|
f"should be used instead of deprecated `{module}`",
|
||||||
|
)
|
||||||
|
|
||||||
|
errors.append(self.Error(task["__line__"], helptext))
|
||||||
|
else:
|
||||||
|
if module.count(".") < 2:
|
||||||
|
errors.append(
|
||||||
|
self.Error(
|
||||||
|
task["__line__"],
|
||||||
|
self.helptext.format(module_alias=module_alias, module=module),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
return self.Result(candidate.path, errors)
|
@ -19,17 +19,16 @@
|
|||||||
# THE SOFTWARE.
|
# THE SOFTWARE.
|
||||||
import re
|
import re
|
||||||
|
|
||||||
from ansiblelater.standard import StandardBase
|
from ansiblelater.rule import RuleBase
|
||||||
|
|
||||||
|
|
||||||
class CheckFilePermissionMissing(StandardBase):
|
class CheckFilePermissionMissing(RuleBase):
|
||||||
sid = "ANSIBLE0018"
|
rid = "ANS118"
|
||||||
description = "File permissions unset or incorrect"
|
description = "File permissions unset or incorrect"
|
||||||
helptext = (
|
helptext = (
|
||||||
"`mode` parameter should set permissions explicitly (e.g. `mode: 0644`) "
|
"`mode` parameter should set permissions explicitly (e.g. `mode: 0644`) "
|
||||||
"to avoid unexpected file permissions"
|
"to avoid unexpected file permissions"
|
||||||
)
|
)
|
||||||
version = "0.2"
|
|
||||||
types = ["playbook", "task", "handler"]
|
types = ["playbook", "task", "handler"]
|
||||||
|
|
||||||
_modules = {
|
_modules = {
|
||||||
|
@ -18,14 +18,13 @@
|
|||||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
# THE SOFTWARE.
|
# THE SOFTWARE.
|
||||||
|
|
||||||
from ansiblelater.standard import StandardBase
|
from ansiblelater.rule import RuleBase
|
||||||
|
|
||||||
|
|
||||||
class CheckFilePermissionOctal(StandardBase):
|
class CheckFilePermissionOctal(RuleBase):
|
||||||
sid = "ANSIBLE0019"
|
rid = "ANS119"
|
||||||
description = "Octal file permissions must contain leading zero or be a string"
|
description = "Numeric file permissions without a leading zero can behave unexpectedly"
|
||||||
helptext = "numeric file permissions without leading zero can behave in unexpected ways"
|
helptext = '`mode: {mode}` should be strings with a leading zero `mode: "0{mode}"`'
|
||||||
version = "0.2"
|
|
||||||
types = ["playbook", "task", "handler"]
|
types = ["playbook", "task", "handler"]
|
||||||
|
|
||||||
def check(self, candidate, settings):
|
def check(self, candidate, settings):
|
||||||
@ -48,7 +47,9 @@ class CheckFilePermissionOctal(StandardBase):
|
|||||||
mode = task["action"].get("mode", None)
|
mode = task["action"].get("mode", None)
|
||||||
|
|
||||||
if isinstance(mode, int) and self._is_invalid_permission(mode):
|
if isinstance(mode, int) and self._is_invalid_permission(mode):
|
||||||
errors.append(self.Error(task["__line__"], self.helptext))
|
errors.append(
|
||||||
|
self.Error(task["__line__"], self.helptext.format(mode=mode))
|
||||||
|
)
|
||||||
|
|
||||||
return self.Result(candidate.path, errors)
|
return self.Result(candidate.path, errors)
|
||||||
|
|
||||||
|
@ -1,13 +1,12 @@
|
|||||||
import re
|
import re
|
||||||
|
|
||||||
from ansiblelater.standard import StandardBase
|
from ansiblelater.rule import RuleBase
|
||||||
|
|
||||||
|
|
||||||
class CheckFilterSeparation(StandardBase):
|
class CheckFilterSeparation(RuleBase):
|
||||||
sid = "ANSIBLE0016"
|
rid = "ANS116"
|
||||||
description = "Jinja2 filters should be separated with spaces"
|
description = "Jinja2 filters should be separated with spaces"
|
||||||
helptext = "no suitable numbers of spaces (required: 1)"
|
helptext = "no suitable numbers of spaces (required: 1)"
|
||||||
version = "0.1"
|
|
||||||
types = ["playbook", "task", "handler", "rolevars", "hostvars", "groupvars"]
|
types = ["playbook", "task", "handler", "rolevars", "hostvars", "groupvars"]
|
||||||
|
|
||||||
def check(self, candidate, settings):
|
def check(self, candidate, settings):
|
||||||
|
@ -18,14 +18,13 @@
|
|||||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
# THE SOFTWARE.
|
# THE SOFTWARE.
|
||||||
|
|
||||||
from ansiblelater.standard import StandardBase
|
from ansiblelater.rule import RuleBase
|
||||||
|
|
||||||
|
|
||||||
class CheckGitHasVersion(StandardBase):
|
class CheckGitHasVersion(RuleBase):
|
||||||
sid = "ANSIBLE0020"
|
rid = "ANS120"
|
||||||
description = "Git checkouts should use explicit version"
|
description = "Git checkouts should use explicit version"
|
||||||
helptext = "git checkouts should point to an explicit commit or tag, not `latest`"
|
helptext = "git checkouts should point to an explicit commit or tag, not `latest`"
|
||||||
version = "0.2"
|
|
||||||
types = ["playbook", "task", "handler"]
|
types = ["playbook", "task", "handler"]
|
||||||
|
|
||||||
def check(self, candidate, settings):
|
def check(self, candidate, settings):
|
||||||
|
@ -1,11 +1,10 @@
|
|||||||
from ansiblelater.standard import StandardBase
|
from ansiblelater.rule import RuleBase
|
||||||
|
|
||||||
|
|
||||||
class CheckInstallUseLatest(StandardBase):
|
class CheckInstallUseLatest(RuleBase):
|
||||||
sid = "ANSIBLE0009"
|
rid = "ANS109"
|
||||||
description = "Package installs should use present, not latest"
|
description = "Package installs should use present, not latest"
|
||||||
helptext = "package installs should use `state=present` with or without a version"
|
helptext = "package installs should use `state=present` with or without a version"
|
||||||
version = "0.1"
|
|
||||||
types = ["playbook", "task", "handler"]
|
types = ["playbook", "task", "handler"]
|
||||||
|
|
||||||
def check(self, candidate, settings):
|
def check(self, candidate, settings):
|
||||||
|
89
ansiblelater/rules/CheckKeyOrder.py
Normal file
89
ansiblelater/rules/CheckKeyOrder.py
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
# Original code written by the authors of ansible-lint
|
||||||
|
|
||||||
|
import functools
|
||||||
|
|
||||||
|
from ansiblelater.rule import RuleBase
|
||||||
|
|
||||||
|
SORTER_TASKS = (
|
||||||
|
"name",
|
||||||
|
# "__module__",
|
||||||
|
# "action",
|
||||||
|
# "args",
|
||||||
|
None, # <-- None include all modules that not using action and *
|
||||||
|
# "when",
|
||||||
|
# "notify",
|
||||||
|
# "tags",
|
||||||
|
"block",
|
||||||
|
"rescue",
|
||||||
|
"always",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class CheckKeyOrder(RuleBase):
|
||||||
|
rid = "ANS129"
|
||||||
|
description = "Check for recommended key order"
|
||||||
|
helptext = "{type} key order can be improved to `{sorted_keys}`"
|
||||||
|
types = ["playbook", "task", "handler"]
|
||||||
|
|
||||||
|
def check(self, candidate, settings):
|
||||||
|
errors = []
|
||||||
|
tasks, err = self.get_normalized_tasks(candidate, settings)
|
||||||
|
|
||||||
|
if err:
|
||||||
|
return self.Result(candidate.path, err)
|
||||||
|
|
||||||
|
for task in tasks:
|
||||||
|
is_sorted, keys = self._sort_keys(task.get("__raw_task__"))
|
||||||
|
if not is_sorted:
|
||||||
|
errors.append(
|
||||||
|
self.Error(
|
||||||
|
task["__line__"],
|
||||||
|
self.helptext.format(type="task", sorted_keys=", ".join(keys)),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
if candidate.kind == "playbook":
|
||||||
|
tasks, err = self.get_tasks(candidate, settings)
|
||||||
|
|
||||||
|
if err:
|
||||||
|
return self.Result(candidate.path, err)
|
||||||
|
|
||||||
|
for task in tasks:
|
||||||
|
is_sorted, keys = self._sort_keys(task)
|
||||||
|
if not is_sorted:
|
||||||
|
errors.append(
|
||||||
|
self.Error(
|
||||||
|
task["__line__"],
|
||||||
|
self.helptext.format(type="play", sorted_keys=", ".join(keys)),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
return self.Result(candidate.path, errors)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _sort_keys(task):
|
||||||
|
if not task:
|
||||||
|
return True, []
|
||||||
|
|
||||||
|
keys = [str(key) for key in task if not key.startswith("_")]
|
||||||
|
sorted_keys = sorted(keys, key=functools.cmp_to_key(_task_property_sorter))
|
||||||
|
|
||||||
|
return (keys == sorted_keys), sorted_keys
|
||||||
|
|
||||||
|
|
||||||
|
def _task_property_sorter(property1, property2):
|
||||||
|
"""Sort task properties based on SORTER."""
|
||||||
|
v_1 = _get_property_sort_index(property1)
|
||||||
|
v_2 = _get_property_sort_index(property2)
|
||||||
|
return (v_1 > v_2) - (v_1 < v_2)
|
||||||
|
|
||||||
|
|
||||||
|
def _get_property_sort_index(name):
|
||||||
|
"""Return the index of the property in the sorter."""
|
||||||
|
a_index = -1
|
||||||
|
for i, v in enumerate(SORTER_TASKS):
|
||||||
|
if v == name:
|
||||||
|
return i
|
||||||
|
if v is None:
|
||||||
|
a_index = i
|
||||||
|
return a_index
|
@ -1,13 +1,12 @@
|
|||||||
import re
|
import re
|
||||||
|
|
||||||
from ansiblelater.standard import StandardBase
|
from ansiblelater.rule import RuleBase
|
||||||
|
|
||||||
|
|
||||||
class CheckLiteralBoolFormat(StandardBase):
|
class CheckLiteralBoolFormat(RuleBase):
|
||||||
sid = "ANSIBLE0014"
|
rid = "ANS114"
|
||||||
description = "Literal bools should be consistent"
|
description = "Literal bools should be consistent"
|
||||||
helptext = "literal bools should be written as `{bools}`"
|
helptext = "literal bools should be written as `{bools}`"
|
||||||
version = "0.1"
|
|
||||||
types = ["playbook", "task", "handler", "rolevars", "hostvars", "groupvars"]
|
types = ["playbook", "task", "handler", "rolevars", "hostvars", "groupvars"]
|
||||||
|
|
||||||
def check(self, candidate, settings):
|
def check(self, candidate, settings):
|
||||||
|
@ -1,13 +1,12 @@
|
|||||||
# Copyright (c) 2016, Tsukinowa Inc. <info@tsukinowa.jp>
|
# Copyright (c) 2016, Tsukinowa Inc. <info@tsukinowa.jp>
|
||||||
# Copyright (c) 2018, Ansible Project
|
# Copyright (c) 2018, Ansible Project
|
||||||
from ansiblelater.standard import StandardBase
|
from ansiblelater.rule import RuleBase
|
||||||
|
|
||||||
|
|
||||||
class CheckLocalAction(StandardBase):
|
class CheckLocalAction(RuleBase):
|
||||||
sid = "ANSIBLE0024"
|
rid = "ANS124"
|
||||||
description = "Don't use local_action"
|
description = "Don't use local_action"
|
||||||
helptext = "`delegate_to: localhost` should be used instead of `local_action`"
|
helptext = "`delegate_to: localhost` should be used instead of `local_action`"
|
||||||
version = "0.2"
|
|
||||||
types = ["playbook", "task", "handler"]
|
types = ["playbook", "task", "handler"]
|
||||||
|
|
||||||
def check(self, candidate, settings):
|
def check(self, candidate, settings):
|
||||||
|
@ -1,14 +1,13 @@
|
|||||||
# Copyright (c) 2018, Ansible Project
|
# Copyright (c) 2018, Ansible Project
|
||||||
from nested_lookup import nested_lookup
|
from nested_lookup import nested_lookup
|
||||||
|
|
||||||
from ansiblelater.standard import StandardBase
|
from ansiblelater.rule import RuleBase
|
||||||
|
|
||||||
|
|
||||||
class CheckMetaChangeFromDefault(StandardBase):
|
class CheckMetaChangeFromDefault(RuleBase):
|
||||||
sid = "ANSIBLE0021"
|
rid = "ANS121"
|
||||||
description = "Roles meta/main.yml default values should be changed"
|
description = "Roles meta/main.yml default values should be changed"
|
||||||
helptext = "meta/main.yml default values should be changed for: `{field}`"
|
helptext = "meta/main.yml default values should be changed for: `{field}`"
|
||||||
version = "0.2"
|
|
||||||
types = ["meta"]
|
types = ["meta"]
|
||||||
|
|
||||||
def check(self, candidate, settings):
|
def check(self, candidate, settings):
|
||||||
|
@ -1,13 +1,12 @@
|
|||||||
from nested_lookup import nested_lookup
|
from nested_lookup import nested_lookup
|
||||||
|
|
||||||
from ansiblelater.standard import StandardBase
|
from ansiblelater.rule import RuleBase
|
||||||
|
|
||||||
|
|
||||||
class CheckMetaMain(StandardBase):
|
class CheckMetaMain(RuleBase):
|
||||||
sid = "ANSIBLE0002"
|
rid = "ANS102"
|
||||||
description = "Roles must contain suitable meta/main.yml"
|
description = "Roles must contain suitable meta/main.yml"
|
||||||
helptext = "file should contain `{key}` key"
|
helptext = "file should contain `{key}` key"
|
||||||
version = "0.1"
|
|
||||||
types = ["meta"]
|
types = ["meta"]
|
||||||
|
|
||||||
def check(self, candidate, settings):
|
def check(self, candidate, settings):
|
||||||
|
@ -1,13 +1,12 @@
|
|||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
|
|
||||||
from ansiblelater.standard import StandardBase
|
from ansiblelater.rule import RuleBase
|
||||||
|
|
||||||
|
|
||||||
class CheckNameFormat(StandardBase):
|
class CheckNameFormat(RuleBase):
|
||||||
sid = "ANSIBLE0007"
|
rid = "ANS107"
|
||||||
description = "Name of tasks and handlers must be formatted"
|
description = "Name of tasks and handlers must be formatted"
|
||||||
helptext = "name '{name}' should start with uppercase"
|
helptext = "name `{name}` should start with uppercase"
|
||||||
version = "0.1"
|
|
||||||
types = ["playbook", "task", "handler"]
|
types = ["playbook", "task", "handler"]
|
||||||
|
|
||||||
def check(self, candidate, settings):
|
def check(self, candidate, settings):
|
||||||
|
@ -1,11 +1,10 @@
|
|||||||
from ansiblelater.standard import StandardBase
|
from ansiblelater.rule import RuleBase
|
||||||
|
|
||||||
|
|
||||||
class CheckNamedTask(StandardBase):
|
class CheckNamedTask(RuleBase):
|
||||||
sid = "ANSIBLE0006"
|
rid = "ANS106"
|
||||||
description = "Tasks and handlers must be named"
|
description = "Tasks and handlers must be named"
|
||||||
helptext = "module '{module}' used without or empty `name` attribute"
|
helptext = "module `{module}` used without or empty `name` attribute"
|
||||||
version = "0.1"
|
|
||||||
types = ["playbook", "task", "handler"]
|
types = ["playbook", "task", "handler"]
|
||||||
|
|
||||||
def check(self, candidate, settings):
|
def check(self, candidate, settings):
|
||||||
|
@ -1,11 +1,10 @@
|
|||||||
from ansiblelater.standard import StandardBase
|
from ansiblelater.rule import RuleBase
|
||||||
|
|
||||||
|
|
||||||
class CheckNativeYaml(StandardBase):
|
class CheckNativeYaml(RuleBase):
|
||||||
sid = "LINT0008"
|
rid = "YML108"
|
||||||
description = "Use YAML format for tasks and handlers rather than key=value"
|
description = "Use YAML format for tasks and handlers rather than key=value"
|
||||||
helptext = "task arguments appear to be in key value rather than YAML format"
|
helptext = "task arguments appear to be in key value rather than YAML format"
|
||||||
version = "0.1"
|
|
||||||
types = ["playbook", "task", "handler"]
|
types = ["playbook", "task", "handler"]
|
||||||
|
|
||||||
def check(self, candidate, settings):
|
def check(self, candidate, settings):
|
||||||
|
@ -21,17 +21,16 @@
|
|||||||
# THE SOFTWARE.
|
# THE SOFTWARE.
|
||||||
import re
|
import re
|
||||||
|
|
||||||
from ansiblelater.standard import StandardBase
|
from ansiblelater.rule import RuleBase
|
||||||
|
|
||||||
|
|
||||||
class CheckNestedJinja(StandardBase):
|
class CheckNestedJinja(RuleBase):
|
||||||
sid = "ANSIBLE0023"
|
rid = "ANS123"
|
||||||
description = "Don't use nested Jinja2 pattern"
|
description = "Don't use nested Jinja2 pattern"
|
||||||
helptext = (
|
helptext = (
|
||||||
"there should not be any nested jinja pattern "
|
"there should not be any nested jinja pattern "
|
||||||
"like `{{ list_one + {{ list_two | max }} }}`"
|
"like `{{ list_one + {{ list_two | max }} }}`"
|
||||||
)
|
)
|
||||||
version = "0.2"
|
|
||||||
types = ["playbook", "task", "handler", "rolevars", "hostvars", "groupvars"]
|
types = ["playbook", "task", "handler", "rolevars", "hostvars", "groupvars"]
|
||||||
|
|
||||||
def check(self, candidate, settings):
|
def check(self, candidate, settings):
|
||||||
|
@ -1,13 +1,12 @@
|
|||||||
# Copyright (c) 2016, Tsukinowa Inc. <info@tsukinowa.jp>
|
# Copyright (c) 2016, Tsukinowa Inc. <info@tsukinowa.jp>
|
||||||
# Copyright (c) 2018, Ansible Project
|
# Copyright (c) 2018, Ansible Project
|
||||||
from ansiblelater.standard import StandardBase
|
from ansiblelater.rule import RuleBase
|
||||||
|
|
||||||
|
|
||||||
class CheckRelativeRolePaths(StandardBase):
|
class CheckRelativeRolePaths(RuleBase):
|
||||||
sid = "ANSIBLE0025"
|
rid = "ANS125"
|
||||||
description = "Don't use a relative path in a role"
|
description = "Don't use a relative path in a role"
|
||||||
helptext = "`copy` and `template` modules don't need relative path for `src`"
|
helptext = "`copy` and `template` modules don't need relative path for `src`"
|
||||||
version = "0.2"
|
|
||||||
types = ["playbook", "task", "handler"]
|
types = ["playbook", "task", "handler"]
|
||||||
|
|
||||||
def check(self, candidate, settings):
|
def check(self, candidate, settings):
|
||||||
|
@ -1,13 +1,12 @@
|
|||||||
from ansible.parsing.yaml.objects import AnsibleMapping
|
from ansible.parsing.yaml.objects import AnsibleMapping
|
||||||
|
|
||||||
from ansiblelater.standard import StandardBase
|
from ansiblelater.rule import RuleBase
|
||||||
|
|
||||||
|
|
||||||
class CheckScmInSrc(StandardBase):
|
class CheckScmInSrc(RuleBase):
|
||||||
sid = "ANSIBLE0005"
|
rid = "ANS105"
|
||||||
description = "Use `scm:` key rather than `src: scm+url`"
|
description = "Use `scm:` key rather than `src: scm+url`"
|
||||||
helptext = "usage of `src: scm+url` not recommended"
|
helptext = "usage of `src: scm+url` not recommended"
|
||||||
version = "0.1"
|
|
||||||
types = ["rolesfile"]
|
types = ["rolesfile"]
|
||||||
|
|
||||||
def check(self, candidate, settings):
|
def check(self, candidate, settings):
|
||||||
|
@ -1,11 +1,10 @@
|
|||||||
from ansiblelater.standard import StandardBase
|
from ansiblelater.rule import RuleBase
|
||||||
|
|
||||||
|
|
||||||
class CheckShellInsteadCommand(StandardBase):
|
class CheckShellInsteadCommand(RuleBase):
|
||||||
sid = "ANSIBLE0010"
|
rid = "ANS110"
|
||||||
description = "Shell should only be used when essential"
|
description = "Shell should only be used when essential"
|
||||||
helptext = "shell should only be used when piping, redirecting or chaining commands"
|
helptext = "shell should only be used when piping, redirecting or chaining commands"
|
||||||
version = "0.1"
|
|
||||||
types = ["playbook", "task", "handler"]
|
types = ["playbook", "task", "handler"]
|
||||||
|
|
||||||
def check(self, candidate, settings):
|
def check(self, candidate, settings):
|
||||||
|
@ -1,14 +1,13 @@
|
|||||||
import re
|
import re
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
|
|
||||||
from ansiblelater.standard import StandardBase
|
from ansiblelater.rule import RuleBase
|
||||||
|
|
||||||
|
|
||||||
class CheckTaskSeparation(StandardBase):
|
class CheckTaskSeparation(RuleBase):
|
||||||
sid = "ANSIBLE0001"
|
rid = "ANS101"
|
||||||
description = "Single tasks should be separated by empty line"
|
description = "Single tasks should be separated by empty line"
|
||||||
helptext = "missing task separation (required: 1 empty line)"
|
helptext = "missing task separation (required: 1 empty line)"
|
||||||
version = "0.1"
|
|
||||||
types = ["playbook", "task", "handler"]
|
types = ["playbook", "task", "handler"]
|
||||||
|
|
||||||
def check(self, candidate, settings):
|
def check(self, candidate, settings):
|
||||||
|
@ -1,13 +1,12 @@
|
|||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
|
|
||||||
from ansiblelater.standard import StandardBase
|
from ansiblelater.rule import RuleBase
|
||||||
|
|
||||||
|
|
||||||
class CheckUniqueNamedTask(StandardBase):
|
class CheckUniqueNamedTask(RuleBase):
|
||||||
sid = "ANSIBLE0003"
|
rid = "ANS103"
|
||||||
description = "Tasks and handlers must be uniquely named within a single file"
|
description = "Tasks and handlers must be uniquely named within a single file"
|
||||||
helptext = "name '{name}' appears multiple times"
|
helptext = "name `{name}` appears multiple times"
|
||||||
version = "0.1"
|
|
||||||
types = ["playbook", "task", "handler"]
|
types = ["playbook", "task", "handler"]
|
||||||
|
|
||||||
def check(self, candidate, settings):
|
def check(self, candidate, settings):
|
||||||
|
@ -1,16 +0,0 @@
|
|||||||
from ansiblelater.standard import StandardBase
|
|
||||||
|
|
||||||
|
|
||||||
class CheckVersion(StandardBase):
|
|
||||||
sid = "ANSIBLE9998"
|
|
||||||
description = "Standards version should be pinned"
|
|
||||||
helptext = "Standards version not set. Using latest standards version {version}"
|
|
||||||
types = ["task", "handler", "rolevars", "meta", "template", "file", "playbook"]
|
|
||||||
|
|
||||||
def check(self, candidate, settings): # noqa
|
|
||||||
errors = []
|
|
||||||
|
|
||||||
if not candidate.version_config:
|
|
||||||
errors.append(self.Error(None, self.helptext.format(version=candidate.version)))
|
|
||||||
|
|
||||||
return self.Result(candidate.path, errors)
|
|
@ -1,13 +1,13 @@
|
|||||||
from ansiblelater.standard import StandardBase
|
from ansiblelater.rule import RuleBase
|
||||||
|
|
||||||
|
|
||||||
class CheckWhenFormat(StandardBase):
|
class CheckWhenFormat(RuleBase):
|
||||||
sid = "ANSIBLE0022"
|
rid = "ANS122"
|
||||||
description = "Don't use Jinja2 in when"
|
description = "Don't use Jinja2 in when"
|
||||||
helptext = (
|
helptext = (
|
||||||
"`when` is a raw Jinja2 expression, redundant {{ }} " "should be removed from variable(s)"
|
"`when` is a raw Jinja2 expression, redundant `{{ }}` should be removed from variable(s)"
|
||||||
)
|
)
|
||||||
version = "0.2"
|
|
||||||
types = ["playbook", "task", "handler"]
|
types = ["playbook", "task", "handler"]
|
||||||
|
|
||||||
def check(self, candidate, settings):
|
def check(self, candidate, settings):
|
||||||
|
@ -1,10 +1,9 @@
|
|||||||
from ansiblelater.standard import StandardBase
|
from ansiblelater.rule import RuleBase
|
||||||
|
|
||||||
|
|
||||||
class CheckYamlColons(StandardBase):
|
class CheckYamlColons(RuleBase):
|
||||||
sid = "LINT0005"
|
rid = "YML105"
|
||||||
description = "YAML should use consistent number of spaces around colons"
|
description = "YAML should use consistent number of spaces around colons"
|
||||||
version = "0.1"
|
|
||||||
types = ["playbook", "task", "handler", "rolevars", "hostvars", "groupvars", "meta"]
|
types = ["playbook", "task", "handler", "rolevars", "hostvars", "groupvars", "meta"]
|
||||||
|
|
||||||
def check(self, candidate, settings):
|
def check(self, candidate, settings):
|
||||||
|
@ -1,10 +1,9 @@
|
|||||||
from ansiblelater.standard import StandardBase
|
from ansiblelater.rule import RuleBase
|
||||||
|
|
||||||
|
|
||||||
class CheckYamlDocumentEnd(StandardBase):
|
class CheckYamlDocumentEnd(RuleBase):
|
||||||
sid = "LINT0009"
|
rid = "YML109"
|
||||||
description = "YAML should contain document end marker"
|
description = "YAML document end marker should match configuration"
|
||||||
version = "0.1"
|
|
||||||
types = ["playbook", "task", "handler", "rolevars", "hostvars", "groupvars", "meta"]
|
types = ["playbook", "task", "handler", "rolevars", "hostvars", "groupvars", "meta"]
|
||||||
|
|
||||||
def check(self, candidate, settings):
|
def check(self, candidate, settings):
|
||||||
|
@ -1,10 +1,9 @@
|
|||||||
from ansiblelater.standard import StandardBase
|
from ansiblelater.rule import RuleBase
|
||||||
|
|
||||||
|
|
||||||
class CheckYamlDocumentStart(StandardBase):
|
class CheckYamlDocumentStart(RuleBase):
|
||||||
sid = "LINT0004"
|
rid = "YML104"
|
||||||
description = "YAML should contain document start marker"
|
description = "YAML document start marker should match configuration"
|
||||||
version = "0.1"
|
|
||||||
types = ["playbook", "task", "handler", "rolevars", "hostvars", "groupvars", "meta"]
|
types = ["playbook", "task", "handler", "rolevars", "hostvars", "groupvars", "meta"]
|
||||||
|
|
||||||
def check(self, candidate, settings):
|
def check(self, candidate, settings):
|
||||||
|
@ -1,10 +1,9 @@
|
|||||||
from ansiblelater.standard import StandardBase
|
from ansiblelater.rule import RuleBase
|
||||||
|
|
||||||
|
|
||||||
class CheckYamlEmptyLines(StandardBase):
|
class CheckYamlEmptyLines(RuleBase):
|
||||||
sid = "LINT0001"
|
rid = "YML101"
|
||||||
description = "YAML should not contain unnecessarily empty lines"
|
description = "YAML should not contain unnecessarily empty lines"
|
||||||
version = "0.1"
|
|
||||||
types = ["playbook", "task", "handler", "rolevars", "hostvars", "groupvars", "meta"]
|
types = ["playbook", "task", "handler", "rolevars", "hostvars", "groupvars", "meta"]
|
||||||
|
|
||||||
def check(self, candidate, settings):
|
def check(self, candidate, settings):
|
||||||
|
@ -1,13 +1,12 @@
|
|||||||
import os
|
import os
|
||||||
|
|
||||||
from ansiblelater.standard import StandardBase
|
from ansiblelater.rule import RuleBase
|
||||||
|
|
||||||
|
|
||||||
class CheckYamlFile(StandardBase):
|
class CheckYamlFile(RuleBase):
|
||||||
sid = "LINT0006"
|
rid = "YML106"
|
||||||
description = "Roles file should be in yaml format"
|
description = "Roles file should be in yaml format"
|
||||||
helptext = "file does not have a .yml extension"
|
helptext = "file does not have a .yml extension"
|
||||||
version = "0.1"
|
|
||||||
types = ["playbook", "task", "handler"]
|
types = ["playbook", "task", "handler"]
|
||||||
|
|
||||||
def check(self, candidate, settings):
|
def check(self, candidate, settings):
|
||||||
|
@ -1,11 +1,10 @@
|
|||||||
from ansiblelater.standard import StandardBase
|
from ansiblelater.rule import RuleBase
|
||||||
|
|
||||||
|
|
||||||
class CheckYamlHasContent(StandardBase):
|
class CheckYamlHasContent(RuleBase):
|
||||||
sid = "LINT0007"
|
rid = "YML107"
|
||||||
description = "Files should contain useful content"
|
description = "Files should contain useful content"
|
||||||
helptext = "the file appears to have no useful content"
|
helptext = "the file appears to have no useful content"
|
||||||
version = "0.1"
|
|
||||||
types = ["playbook", "task", "handler", "rolevars", "defaults", "meta"]
|
types = ["playbook", "task", "handler", "rolevars", "defaults", "meta"]
|
||||||
|
|
||||||
def check(self, candidate, settings):
|
def check(self, candidate, settings):
|
||||||
|
@ -1,10 +1,9 @@
|
|||||||
from ansiblelater.standard import StandardBase
|
from ansiblelater.rule import RuleBase
|
||||||
|
|
||||||
|
|
||||||
class CheckYamlHyphens(StandardBase):
|
class CheckYamlHyphens(RuleBase):
|
||||||
sid = "LINT0003"
|
rid = "YML103"
|
||||||
description = "YAML should use consistent number of spaces after hyphens"
|
description = "YAML should use consistent number of spaces after hyphens"
|
||||||
version = "0.1"
|
|
||||||
types = ["playbook", "task", "handler", "rolevars", "hostvars", "groupvars", "meta"]
|
types = ["playbook", "task", "handler", "rolevars", "hostvars", "groupvars", "meta"]
|
||||||
|
|
||||||
def check(self, candidate, settings):
|
def check(self, candidate, settings):
|
||||||
|
@ -1,10 +1,9 @@
|
|||||||
from ansiblelater.standard import StandardBase
|
from ansiblelater.rule import RuleBase
|
||||||
|
|
||||||
|
|
||||||
class CheckYamlIndent(StandardBase):
|
class CheckYamlIndent(RuleBase):
|
||||||
sid = "LINT0002"
|
rid = "YML102"
|
||||||
description = "YAML should not contain unnecessarily empty lines"
|
description = "YAML should not contain unnecessarily empty lines"
|
||||||
version = "0.1"
|
|
||||||
types = ["playbook", "task", "handler", "rolevars", "hostvars", "groupvars", "meta"]
|
types = ["playbook", "task", "handler", "rolevars", "hostvars", "groupvars", "meta"]
|
||||||
|
|
||||||
def check(self, candidate, settings):
|
def check(self, candidate, settings):
|
||||||
|
13
ansiblelater/rules/CheckYamlOctalValues.py
Normal file
13
ansiblelater/rules/CheckYamlOctalValues.py
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
from ansiblelater.rule import RuleBase
|
||||||
|
|
||||||
|
|
||||||
|
class CheckYamlOctalValues(RuleBase):
|
||||||
|
rid = "YML110"
|
||||||
|
description = "YAML implicit/explicit octal value should match configuration"
|
||||||
|
types = ["playbook", "task", "handler", "rolevars", "hostvars", "groupvars", "meta"]
|
||||||
|
|
||||||
|
def check(self, candidate, settings):
|
||||||
|
options = f"rules: {{octal-values: {settings['yamllint']['octal-values']}}}"
|
||||||
|
errors = self.run_yamllint(candidate, options)
|
||||||
|
|
||||||
|
return self.Result(candidate.path, errors)
|
@ -1,5 +1,6 @@
|
|||||||
"""Global settings object definition."""
|
"""Global settings object definition."""
|
||||||
|
|
||||||
|
import importlib.resources
|
||||||
import os
|
import os
|
||||||
|
|
||||||
import anyconfig
|
import anyconfig
|
||||||
@ -7,7 +8,6 @@ import jsonschema.exceptions
|
|||||||
import pathspec
|
import pathspec
|
||||||
from appdirs import AppDirs
|
from appdirs import AppDirs
|
||||||
from jsonschema._utils import format_as_index
|
from jsonschema._utils import format_as_index
|
||||||
from pkg_resources import resource_filename
|
|
||||||
|
|
||||||
from ansiblelater import utils
|
from ansiblelater import utils
|
||||||
|
|
||||||
@ -104,13 +104,13 @@ class Settings:
|
|||||||
if f not in defaults["ansible"]["custom_modules"]:
|
if f not in defaults["ansible"]["custom_modules"]:
|
||||||
defaults["ansible"]["custom_modules"].append(f)
|
defaults["ansible"]["custom_modules"].append(f)
|
||||||
|
|
||||||
if defaults["rules"]["buildin"]:
|
if defaults["rules"]["builtin"]:
|
||||||
defaults["rules"]["standards"].append(
|
ref = importlib.resources.files("ansiblelater") / "rules"
|
||||||
os.path.join(resource_filename("ansiblelater", "rules"))
|
with importlib.resources.as_file(ref) as path:
|
||||||
)
|
defaults["rules"]["dir"].append(path)
|
||||||
|
|
||||||
defaults["rules"]["standards"] = [
|
defaults["rules"]["dir"] = [
|
||||||
os.path.relpath(os.path.normpath(p)) for p in defaults["rules"]["standards"]
|
os.path.relpath(os.path.normpath(p)) for p in defaults["rules"]["dir"]
|
||||||
]
|
]
|
||||||
|
|
||||||
return defaults
|
return defaults
|
||||||
@ -118,17 +118,16 @@ class Settings:
|
|||||||
def _get_defaults(self):
|
def _get_defaults(self):
|
||||||
defaults = {
|
defaults = {
|
||||||
"rules": {
|
"rules": {
|
||||||
"buildin": True,
|
"builtin": True,
|
||||||
"standards": [],
|
"dir": [],
|
||||||
"filter": [],
|
"include_filter": [],
|
||||||
"exclude_filter": [],
|
"exclude_filter": [],
|
||||||
"warning_filter": [
|
"warning_filter": [
|
||||||
"ANSIBLE9999",
|
"ANS128",
|
||||||
"ANSIBLE9998",
|
"ANS999",
|
||||||
],
|
],
|
||||||
"ignore_dotfiles": True,
|
"ignore_dotfiles": True,
|
||||||
"exclude_files": [],
|
"exclude_files": [],
|
||||||
"version": "",
|
|
||||||
},
|
},
|
||||||
"logging": {
|
"logging": {
|
||||||
"level": "WARNING",
|
"level": "WARNING",
|
||||||
@ -145,7 +144,7 @@ class Settings:
|
|||||||
"exclude": [
|
"exclude": [
|
||||||
"meta",
|
"meta",
|
||||||
"debug",
|
"debug",
|
||||||
"block",
|
"block/always/rescue",
|
||||||
"include_role",
|
"include_role",
|
||||||
"import_role",
|
"import_role",
|
||||||
"include_tasks",
|
"include_tasks",
|
||||||
@ -175,12 +174,16 @@ class Settings:
|
|||||||
"present": True,
|
"present": True,
|
||||||
},
|
},
|
||||||
"document-end": {
|
"document-end": {
|
||||||
"present": True,
|
"present": False,
|
||||||
},
|
},
|
||||||
"colons": {
|
"colons": {
|
||||||
"max-spaces-before": 0,
|
"max-spaces-before": 0,
|
||||||
"max-spaces-after": 1,
|
"max-spaces-after": 1,
|
||||||
},
|
},
|
||||||
|
"octal-values": {
|
||||||
|
"forbid-implicit-octal": True,
|
||||||
|
"forbid-explicit-octal": True,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,9 +4,10 @@ import contextlib
|
|||||||
import re
|
import re
|
||||||
import sys
|
import sys
|
||||||
from contextlib import suppress
|
from contextlib import suppress
|
||||||
|
from functools import lru_cache
|
||||||
|
|
||||||
import yaml
|
import yaml
|
||||||
from packaging.version import Version
|
from ansible.plugins.loader import module_loader
|
||||||
|
|
||||||
from ansiblelater import logger
|
from ansiblelater import logger
|
||||||
|
|
||||||
@ -35,12 +36,6 @@ def count_spaces(c_string):
|
|||||||
return (leading_spaces, trailing_spaces)
|
return (leading_spaces, trailing_spaces)
|
||||||
|
|
||||||
|
|
||||||
def standards_latest(standards):
|
|
||||||
return max(
|
|
||||||
[standard.version for standard in standards if standard.version] or ["0.1"], key=Version
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def lines_ranges(lines_spec):
|
def lines_ranges(lines_spec):
|
||||||
if not lines_spec:
|
if not lines_spec:
|
||||||
return None
|
return None
|
||||||
@ -84,9 +79,7 @@ def open_file(filename, mode="r"):
|
|||||||
def add_dict_branch(tree, vector, value):
|
def add_dict_branch(tree, vector, value):
|
||||||
key = vector[0]
|
key = vector[0]
|
||||||
tree[key] = (
|
tree[key] = (
|
||||||
value
|
value if len(vector) == 1 else add_dict_branch(tree.get(key, {}), vector[1:], value)
|
||||||
if len(vector) == 1
|
|
||||||
else add_dict_branch(tree[key] if key in tree else {}, vector[1:], value)
|
|
||||||
)
|
)
|
||||||
return tree
|
return tree
|
||||||
|
|
||||||
@ -121,3 +114,21 @@ class Singleton(type):
|
|||||||
if cls not in cls._instances:
|
if cls not in cls._instances:
|
||||||
cls._instances[cls] = super().__call__(*args, **kwargs)
|
cls._instances[cls] = super().__call__(*args, **kwargs)
|
||||||
return cls._instances[cls]
|
return cls._instances[cls]
|
||||||
|
|
||||||
|
|
||||||
|
@lru_cache
|
||||||
|
def load_plugin(name):
|
||||||
|
"""Return loaded ansible plugin/module."""
|
||||||
|
loaded_module = module_loader.find_plugin_with_context(
|
||||||
|
name,
|
||||||
|
ignore_deprecated=True,
|
||||||
|
check_aliases=True,
|
||||||
|
)
|
||||||
|
if not loaded_module.resolved and name.startswith("ansible.builtin."):
|
||||||
|
# fallback to core behavior of using legacy
|
||||||
|
loaded_module = module_loader.find_plugin_with_context(
|
||||||
|
name.replace("ansible.builtin.", "ansible.legacy."),
|
||||||
|
ignore_deprecated=True,
|
||||||
|
check_aliases=True,
|
||||||
|
)
|
||||||
|
return loaded_module
|
||||||
|
@ -65,11 +65,11 @@ def ansible_template(basedir, varname, templatevars, **kwargs):
|
|||||||
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from ansible.plugins import module_loader
|
|
||||||
except ImportError:
|
|
||||||
from ansible.plugins.loader import init_plugin_loader, module_loader
|
from ansible.plugins.loader import init_plugin_loader, module_loader
|
||||||
|
|
||||||
init_plugin_loader()
|
init_plugin_loader()
|
||||||
|
except ImportError:
|
||||||
|
from ansible.plugins.loader import module_loader
|
||||||
|
|
||||||
LINE_NUMBER_KEY = "__line__"
|
LINE_NUMBER_KEY = "__line__"
|
||||||
FILENAME_KEY = "__file__"
|
FILENAME_KEY = "__file__"
|
||||||
@ -369,24 +369,22 @@ def _kv_to_dict(v):
|
|||||||
def normalize_task(task, filename, custom_modules=None):
|
def normalize_task(task, filename, custom_modules=None):
|
||||||
"""Ensure tasks have an action key and strings are converted to python objects."""
|
"""Ensure tasks have an action key and strings are converted to python objects."""
|
||||||
|
|
||||||
|
def _normalize(task, custom_modules):
|
||||||
if custom_modules is None:
|
if custom_modules is None:
|
||||||
custom_modules = []
|
custom_modules = []
|
||||||
|
|
||||||
ansible_action_type = task.get("__ansible_action_type__", "task")
|
|
||||||
if "__ansible_action_type__" in task:
|
|
||||||
del task["__ansible_action_type__"]
|
|
||||||
|
|
||||||
# temp. extract metadata
|
|
||||||
ansible_meta = {}
|
|
||||||
for key in ["__line__", "__file__", "__ansible_action_meta__"]:
|
|
||||||
default = None
|
|
||||||
|
|
||||||
if key == "__ansible_action_meta__":
|
|
||||||
default = {}
|
|
||||||
|
|
||||||
ansible_meta[key] = task.pop(key, default)
|
|
||||||
|
|
||||||
normalized = {}
|
normalized = {}
|
||||||
|
ansible_parsed_keys = ("action", "local_action", "args", "delegate_to")
|
||||||
|
|
||||||
|
if is_nested_task(task):
|
||||||
|
_extract_ansible_parsed_keys_from_task(normalized, task, ansible_parsed_keys)
|
||||||
|
# Add dummy action for block/always/rescue statements
|
||||||
|
normalized["action"] = {
|
||||||
|
"__ansible_module__": "block/always/rescue",
|
||||||
|
"__ansible_module_original__": "block/always/rescue",
|
||||||
|
"__ansible_arguments__": "block/always/rescue",
|
||||||
|
}
|
||||||
|
return normalized
|
||||||
|
|
||||||
builtin = list(ansible.parsing.mod_args.BUILTIN_TASKS)
|
builtin = list(ansible.parsing.mod_args.BUILTIN_TASKS)
|
||||||
builtin = list(set(builtin + custom_modules))
|
builtin = list(set(builtin + custom_modules))
|
||||||
@ -404,22 +402,47 @@ def normalize_task(task, filename, custom_modules=None):
|
|||||||
del arguments["_uses_shell"]
|
del arguments["_uses_shell"]
|
||||||
|
|
||||||
for k, v in list(task.items()):
|
for k, v in list(task.items()):
|
||||||
if k in ("action", "local_action", "args", "delegate_to") or k == action:
|
if k in ansible_parsed_keys or k == action:
|
||||||
# we don"t want to re-assign these values, which were
|
# we don"t want to re-assign these values, which were
|
||||||
# determined by the ModuleArgsParser() above
|
# determined by the ModuleArgsParser() above
|
||||||
continue
|
continue
|
||||||
|
|
||||||
normalized[k] = v
|
normalized[k] = v
|
||||||
|
|
||||||
normalized["action"] = {"__ansible_module__": action}
|
# convert builtin fqn calls to short forms because most rules know only
|
||||||
|
# about short calls
|
||||||
|
normalized["action"] = {
|
||||||
|
"__ansible_module__": action.removeprefix("ansible.builtin."),
|
||||||
|
"__ansible_module_original__": action,
|
||||||
|
}
|
||||||
|
|
||||||
if "_raw_params" in arguments:
|
if "_raw_params" in arguments:
|
||||||
normalized["action"]["__ansible_arguments__"] = arguments["_raw_params"].strip().split()
|
normalized["action"]["__ansible_arguments__"] = (
|
||||||
|
arguments["_raw_params"].strip().split()
|
||||||
|
)
|
||||||
del arguments["_raw_params"]
|
del arguments["_raw_params"]
|
||||||
else:
|
else:
|
||||||
normalized["action"]["__ansible_arguments__"] = []
|
normalized["action"]["__ansible_arguments__"] = []
|
||||||
normalized["action"].update(arguments)
|
normalized["action"].update(arguments)
|
||||||
|
|
||||||
|
return normalized
|
||||||
|
|
||||||
|
# temp. extract metadata
|
||||||
|
ansible_meta = {}
|
||||||
|
for key in ["__line__", "__file__", "__ansible_action_meta__"]:
|
||||||
|
default = None
|
||||||
|
|
||||||
|
if key == "__ansible_action_meta__":
|
||||||
|
default = {}
|
||||||
|
|
||||||
|
ansible_meta[key] = task.pop(key, default)
|
||||||
|
|
||||||
|
ansible_action_type = task.get("__ansible_action_type__", "task")
|
||||||
|
if "__ansible_action_type__" in task:
|
||||||
|
del task["__ansible_action_type__"]
|
||||||
|
|
||||||
|
normalized = _normalize(task, custom_modules)
|
||||||
|
|
||||||
normalized[FILENAME_KEY] = filename
|
normalized[FILENAME_KEY] = filename
|
||||||
normalized["__ansible_action_type__"] = ansible_action_type
|
normalized["__ansible_action_type__"] = ansible_action_type
|
||||||
|
|
||||||
@ -431,22 +454,17 @@ def normalize_task(task, filename, custom_modules=None):
|
|||||||
return normalized
|
return normalized
|
||||||
|
|
||||||
|
|
||||||
def action_tasks(yaml, file):
|
def action_tasks(yaml, candidate):
|
||||||
tasks = []
|
tasks = []
|
||||||
if file["filetype"] in ["tasks", "handlers"]:
|
if candidate.filemeta in ["tasks", "handlers"]:
|
||||||
tasks = add_action_type(yaml, file["filetype"])
|
tasks = add_action_type(yaml, candidate.filemeta)
|
||||||
else:
|
else:
|
||||||
tasks.extend(extract_from_list(yaml, ["tasks", "handlers", "pre_tasks", "post_tasks"]))
|
tasks.extend(extract_from_list(yaml, ["tasks", "handlers", "pre_tasks", "post_tasks"]))
|
||||||
|
|
||||||
# Add sub-elements of block/rescue/always to tasks list
|
# Add sub-elements of block/rescue/always to tasks list
|
||||||
tasks.extend(extract_from_list(tasks, ["block", "rescue", "always"]))
|
tasks.extend(extract_from_list(tasks, ["block", "rescue", "always"]))
|
||||||
# Remove block/rescue/always elements from tasks list
|
|
||||||
block_rescue_always = ("block", "rescue", "always")
|
|
||||||
tasks[:] = [task for task in tasks if all(k not in task for k in block_rescue_always)]
|
|
||||||
|
|
||||||
allowed = ["include", "include_tasks", "import_playbook", "import_tasks"]
|
return tasks
|
||||||
|
|
||||||
return [task for task in tasks if set(allowed).isdisjoint(task.keys())]
|
|
||||||
|
|
||||||
|
|
||||||
def task_to_str(task):
|
def task_to_str(task):
|
||||||
@ -475,7 +493,10 @@ def extract_from_list(blocks, candidates):
|
|||||||
meta_data = dict(block)
|
meta_data = dict(block)
|
||||||
for key in delete_meta_keys:
|
for key in delete_meta_keys:
|
||||||
meta_data.pop(key, None)
|
meta_data.pop(key, None)
|
||||||
results.extend(add_action_type(block[candidate], candidate, meta_data))
|
|
||||||
|
actions = add_action_type(block[candidate], candidate, meta_data)
|
||||||
|
|
||||||
|
results.extend(actions)
|
||||||
elif block[candidate] is not None:
|
elif block[candidate] is not None:
|
||||||
raise RuntimeError(
|
raise RuntimeError(
|
||||||
f"Key '{candidate}' defined, but bad value: '{block[candidate]!s}'"
|
f"Key '{candidate}' defined, but bad value: '{block[candidate]!s}'"
|
||||||
@ -564,6 +585,26 @@ def normalized_yaml(file, options):
|
|||||||
return lines
|
return lines
|
||||||
|
|
||||||
|
|
||||||
|
def is_nested_task(task):
|
||||||
|
"""Check if task includes block/always/rescue."""
|
||||||
|
# Cannot really trust the input
|
||||||
|
if isinstance(task, str):
|
||||||
|
return False
|
||||||
|
|
||||||
|
return any(task.get(key) for key in ["block", "rescue", "always"])
|
||||||
|
|
||||||
|
|
||||||
|
def _extract_ansible_parsed_keys_from_task(result, task, keys):
|
||||||
|
"""Return a dict with existing key in task."""
|
||||||
|
for k, v in list(task.items()):
|
||||||
|
if k in keys:
|
||||||
|
# we don't want to re-assign these values, which were
|
||||||
|
# determined by the ModuleArgsParser() above
|
||||||
|
continue
|
||||||
|
result[k] = v
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
class UnsafeTag:
|
class UnsafeTag:
|
||||||
"""Handle custom yaml unsafe tag."""
|
"""Handle custom yaml unsafe tag."""
|
||||||
|
|
||||||
|
@ -1,18 +1,17 @@
|
|||||||
---
|
---
|
||||||
title: Minimal standard checks
|
title: Write a rule
|
||||||
---
|
---
|
||||||
|
|
||||||
A typical standards check will look like:
|
A typical rule check will look like:
|
||||||
|
|
||||||
<!-- prettier-ignore-start -->
|
<!-- prettier-ignore-start -->
|
||||||
<!-- spellchecker-disable -->
|
<!-- spellchecker-disable -->
|
||||||
{{< highlight Python "linenos=table" >}}
|
{{< highlight Python "linenos=table" >}}
|
||||||
class CheckBecomeUser(StandardBase):
|
class CheckBecomeUser(RuleBase):
|
||||||
|
|
||||||
sid = "ANSIBLE0015"
|
rid = "ANS115"
|
||||||
description = "Become should be combined with become_user"
|
description = "Become should be combined with become_user"
|
||||||
helptext = "the task has `become` enabled but `become_user` is missing"
|
helptext = "the task has `become` enabled but `become_user` is missing"
|
||||||
version = "0.1"
|
|
||||||
types = ["playbook", "task", "handler"]
|
types = ["playbook", "task", "handler"]
|
||||||
|
|
||||||
def check(self, candidate, settings):
|
def check(self, candidate, settings):
|
@ -8,28 +8,27 @@ You can get all available CLI options by running `ansible-later --help`:
|
|||||||
<!-- spellchecker-disable -->
|
<!-- spellchecker-disable -->
|
||||||
{{< highlight Shell "linenos=table" >}}
|
{{< highlight Shell "linenos=table" >}}
|
||||||
$ ansible-later --help
|
$ ansible-later --help
|
||||||
usage: ansible-later [-h] [-c CONFIG_FILE] [-r RULES.STANDARDS]
|
usage: ansible-later [-h] [-c CONFIG] [-r DIR] [-B] [-i TAGS] [-x TAGS] [-v] [-q] [-V] [rules.files ...]
|
||||||
[-s RULES.FILTER] [-v] [-q] [--version]
|
|
||||||
[rules.files [rules.files ...]]
|
|
||||||
|
|
||||||
Validate Ansible files against best practice guideline
|
Validate Ansible files against best practice guideline
|
||||||
|
|
||||||
positional arguments:
|
positional arguments:
|
||||||
rules.files
|
rules.files
|
||||||
|
|
||||||
optional arguments:
|
options:
|
||||||
-h, --help show this help message and exit
|
-h, --help show this help message and exit
|
||||||
-c CONFIG_FILE, --config CONFIG_FILE
|
-c CONFIG, --config CONFIG
|
||||||
location of configuration file
|
path to configuration file
|
||||||
-r RULES.STANDARDS, --rules RULES.STANDARDS
|
-r DIR, --rules-dir DIR
|
||||||
location of standards rules
|
directory of rules
|
||||||
-s RULES.FILTER, --standards RULES.FILTER
|
-B, --no-builtin disables built-in rules
|
||||||
limit standards to given ID's
|
-i TAGS, --include-rules TAGS
|
||||||
-x RULES.EXCLUDE_FILTER, --exclude-standards RULES.EXCLUDE_FILTER
|
limit rules to given id/tags
|
||||||
exclude standards by given ID's
|
-x TAGS, --exclude-rules TAGS
|
||||||
|
exclude rules by given it/tags
|
||||||
-v increase log level
|
-v increase log level
|
||||||
-q decrease log level
|
-q decrease log level
|
||||||
--version show program's version number and exit
|
-V, --version show program's version number and exit
|
||||||
{{< /highlight >}}
|
{{< /highlight >}}
|
||||||
<!-- spellchecker-enable -->
|
<!-- spellchecker-enable -->
|
||||||
<!-- prettier-ignore-end -->
|
<!-- prettier-ignore-end -->
|
||||||
|
@ -16,32 +16,32 @@ ansible:
|
|||||||
# directory will be auto-detected and don't need to be added to this list.
|
# directory will be auto-detected and don't need to be added to this list.
|
||||||
custom_modules: []
|
custom_modules: []
|
||||||
|
|
||||||
# Settings for variable formatting rule (ANSIBLE0004)
|
# Settings for variable formatting rule (ANS104)
|
||||||
double-braces:
|
double-braces:
|
||||||
max-spaces-inside: 1
|
max-spaces-inside: 1
|
||||||
min-spaces-inside: 1
|
min-spaces-inside: 1
|
||||||
|
|
||||||
# List of allowed literal bools (ANSIBLE0014)
|
# List of allowed literal bools (ANS114)
|
||||||
literal-bools:
|
literal-bools:
|
||||||
- "True"
|
- "True"
|
||||||
- "False"
|
- "False"
|
||||||
- "yes"
|
- "yes"
|
||||||
- "no"
|
- "no"
|
||||||
|
|
||||||
# List of modules that don't need to be named (ANSIBLE0006).
|
# List of modules that don't need to be named (ANS106).
|
||||||
# You must specify each individual module name, globs or wildcards do not work!
|
# You must specify each individual module name, globs or wildcards do not work!
|
||||||
named-task:
|
named-task:
|
||||||
exclude:
|
exclude:
|
||||||
- "meta"
|
- "meta"
|
||||||
- "debug"
|
- "debug"
|
||||||
- "block"
|
- "block/always/rescue"
|
||||||
- "include_role"
|
- "include_role"
|
||||||
- "include_tasks"
|
- "include_tasks"
|
||||||
- "include_vars"
|
- "include_vars"
|
||||||
- "import_role"
|
- "import_role"
|
||||||
- "import_tasks"
|
- "import_tasks"
|
||||||
|
|
||||||
# List of modules that are allowed to use the key=value format instead of the native YAML format (LINT0008).
|
# List of modules that are allowed to use the key=value format instead of the native YAML format (YML108).
|
||||||
# You must specify each individual module name, globs or wildcards do not work!
|
# You must specify each individual module name, globs or wildcards do not work!
|
||||||
native-yaml:
|
native-yaml:
|
||||||
exclude: []
|
exclude: []
|
||||||
@ -58,8 +58,8 @@ logging:
|
|||||||
|
|
||||||
# Global settings for all defined rules
|
# Global settings for all defined rules
|
||||||
rules:
|
rules:
|
||||||
# Disable build-in rules if required
|
# Disable built-in rules if required
|
||||||
buildin: True
|
builtin: True
|
||||||
|
|
||||||
# List of files to exclude
|
# List of files to exclude
|
||||||
exclude_files: []
|
exclude_files: []
|
||||||
@ -75,22 +75,17 @@ rules:
|
|||||||
exclude_filter: []
|
exclude_filter: []
|
||||||
|
|
||||||
# List of rule ID's that should be displayed as a warning instead of an error. By default,
|
# List of rule ID's that should be displayed as a warning instead of an error. By default,
|
||||||
# only rules whose version is higher than the current default version are marked as warnings.
|
# no rules are marked as warnings. This list allows to degrade errors to warnings for each rule.
|
||||||
# This list allows to degrade errors to warnings for each rule.
|
|
||||||
warning_filter:
|
warning_filter:
|
||||||
- "ANSIBLE9999"
|
- "ANS128"
|
||||||
- "ANSIBLE9998"
|
- "ANS999"
|
||||||
|
|
||||||
# All dotfiles (including hidden folders) are excluded by default.
|
# All dotfiles (including hidden folders) are excluded by default.
|
||||||
# You can disable this setting and handle dotfiles by yourself with `exclude_files`.
|
# You can disable this setting and handle dotfiles by yourself with `exclude_files`.
|
||||||
ignore_dotfiles: True
|
ignore_dotfiles: True
|
||||||
|
|
||||||
# List of directories to load standard rules from (defaults to build-in)
|
# List of directories to load rules from (defaults to built-in)
|
||||||
standards: []
|
dir: []
|
||||||
|
|
||||||
# Standard version to use. Standard version set in a roles meta file
|
|
||||||
# or playbook will takes precedence.
|
|
||||||
version:
|
|
||||||
|
|
||||||
# Block to control included yamllint rules.
|
# Block to control included yamllint rules.
|
||||||
# See https://yamllint.readthedocs.io/en/stable/rules.html
|
# See https://yamllint.readthedocs.io/en/stable/rules.html
|
||||||
@ -100,6 +95,8 @@ yamllint:
|
|||||||
max-spaces-before: 0
|
max-spaces-before: 0
|
||||||
document-start:
|
document-start:
|
||||||
present: True
|
present: True
|
||||||
|
document-end:
|
||||||
|
present: True
|
||||||
empty-lines:
|
empty-lines:
|
||||||
max: 1
|
max: 1
|
||||||
max-end: 1
|
max-end: 1
|
||||||
|
@ -2,45 +2,47 @@
|
|||||||
title: Included rules
|
title: Included rules
|
||||||
---
|
---
|
||||||
|
|
||||||
Reviews are useless without some rules or standards to check against. ansible-later comes with a set of built-in checks, which are explained in the following table.
|
Reviews are useless without some rules to check against. `ansible-later` comes with a set of built-in checks, which are explained in the following table.
|
||||||
|
|
||||||
| Rule | ID | Description | Parameter |
|
| Rule | ID | Description | Parameter |
|
||||||
| ----------------------------- | ----------- | ----------------------------------------------------------------- | ---------------------------------------------------------------------- |
|
| ----------------------------- | ------ | ----------------------------------------------------------------- | -------------------------------------------------------------------------- |
|
||||||
| CheckYamlEmptyLines | LINT0001 | YAML should not contain unnecessarily empty lines. | {max: 1, max-start: 0, max-end: 1} |
|
| CheckYamlEmptyLines | YML101 | YAML should not contain unnecessarily empty lines. | {max: 1, max-start: 0, max-end: 1} |
|
||||||
| CheckYamlIndent | LINT0002 | YAML should be correctly indented. | {spaces: 2, check-multi-line-strings: false, indent-sequences: true} |
|
| CheckYamlIndent | YML102 | YAML should be correctly indented. | {spaces: 2, check-multi-line-strings: false, indent-sequences: true} |
|
||||||
| CheckYamlHyphens | LINT0003 | YAML should use consistent number of spaces after hyphens (-). | {max-spaces-after: 1} |
|
| CheckYamlHyphens | YML103 | YAML should use consistent number of spaces after hyphens (-). | {max-spaces-after: 1} |
|
||||||
| CheckYamlDocumentStart | LINT0004 | YAML should contain document start marker. | {document-start: {present: true}} |
|
| CheckYamlDocumentStart | YML104 | YAML should contain document start marker. | {document-start: {present: true}} |
|
||||||
| CheckYamlColons | LINT0005 | YAML should use consistent number of spaces around colons. | {colons: {max-spaces-before: 0, max-spaces-after: 1}} |
|
| CheckYamlColons | YML105 | YAML should use consistent number of spaces around colons. | {colons: {max-spaces-before: 0, max-spaces-after: 1}} |
|
||||||
| CheckYamlFile | LINT0006 | Roles file should be in YAML format. | |
|
| CheckYamlFile | YML106 | Roles file should be in YAML format. | |
|
||||||
| CheckYamlHasContent | LINT0007 | Files should contain useful content. | |
|
| CheckYamlHasContent | YML107 | Files should contain useful content. | |
|
||||||
| CheckNativeYaml | LINT0008 | Use YAML format for tasks and handlers rather than key=value. | {native-yaml: {exclude: []}} |
|
| CheckNativeYaml | YML108 | Use YAML format for tasks and handlers rather than key=value. | {native-yaml: {exclude: []}} |
|
||||||
| CheckYamlDocumentEnd | LINT0009 | YAML should contain document end marker. | {document-end: {present: true}} |
|
| CheckYamlDocumentEnd | YML109 | YAML should contain document end marker. | {document-end: {present: true}} |
|
||||||
| CheckTaskSeparation | ANSIBLE0001 | Single tasks should be separated by an empty line. | |
|
| CheckYamlOctalValues | YML110 | YAML should not use forbidden implicit or explicit octal value. | {octal-values: {forbid-implicit-octal: true, forbid-explicit-octal: true}} |
|
||||||
| CheckMetaMain | ANSIBLE0002 | Meta file should contain a basic subset of parameters. | author, description, min_ansible_version, platforms, dependencies |
|
| CheckTaskSeparation | ANS101 | Single tasks should be separated by an empty line. | |
|
||||||
| CheckUniqueNamedTask | ANSIBLE0003 | Tasks and handlers must be uniquely named within a file. | |
|
| CheckMetaMain | ANS102 | Meta file should contain a basic subset of parameters. | author, description, min_ansible_version, platforms, dependencies |
|
||||||
| CheckBraces | ANSIBLE0004 | YAML should use consistent number of spaces around variables. | {double-braces: max-spaces-inside: 1, min-spaces-inside: 1} |
|
| CheckUniqueNamedTask | ANS103 | Tasks and handlers must be uniquely named within a file. | |
|
||||||
| CheckScmInSrc | ANSIBLE0005 | Use SCM key rather than `src: scm+url` in requirements file. | |
|
| CheckBraces | ANS104 | YAML should use consistent number of spaces around variables. | {double-braces: max-spaces-inside: 1, min-spaces-inside: 1} |
|
||||||
| CheckNamedTask | ANSIBLE0006 | Tasks and handlers must be named. | {named-task: {exclude: [meta, debug, block, include\_\*, import\_\*]}} |
|
| CheckScmInSrc | ANS105 | Use SCM key rather than `src: scm+url` in requirements file. | |
|
||||||
| CheckNameFormat | ANSIBLE0007 | Name of tasks and handlers must be formatted. | formats: first letter capital |
|
| CheckNamedTask | ANS106 | Tasks and handlers must be named. | {named-task: {exclude: [meta, debug, block, include\_\*, import\_\*]}} |
|
||||||
| CheckCommandInsteadofModule | ANSIBLE0008 | Commands should not be used in place of modules. | |
|
| CheckNameFormat | ANS107 | Name of tasks and handlers must be formatted. | formats: first letter capital |
|
||||||
| CheckInstallUseLatest | ANSIBLE0009 | Package managers should not install with state=latest. | |
|
| CheckCommandInsteadofModule | ANS108 | Commands should not be used in place of modules. | |
|
||||||
| CheckShellInsteadCommand | ANSIBLE0010 | Use Shell only when piping, redirecting or chaining commands. | |
|
| CheckInstallUseLatest | ANS109 | Package managers should not install with state=latest. | |
|
||||||
| CheckCommandHasChanges | ANSIBLE0011 | Commands should be idempotent and only used with some checks. | |
|
| CheckShellInsteadCommand | ANS110 | Use Shell only when piping, redirecting or chaining commands. | |
|
||||||
| CheckCompareToEmptyString | ANSIBLE0012 | Don't compare to "" - use `when: var` or `when: not var`. | |
|
| CheckCommandHasChanges | ANS111 | Commands should be idempotent and only used with some checks. | |
|
||||||
| CheckCompareToLiteralBool | ANSIBLE0013 | Don't compare to True/False - use `when: var` or `when: not var`. | |
|
| CheckCompareToEmptyString | ANS112 | Don't compare to "" - use `when: var` or `when: not var`. | |
|
||||||
| CheckLiteralBoolFormat | ANSIBLE0014 | Literal bools should be consistent. | {literal-bools: [True, False, yes, no]} |
|
| CheckCompareToLiteralBool | ANS113 | Don't compare to True/False - use `when: var` or `when: not var`. | |
|
||||||
| CheckBecomeUser | ANSIBLE0015 | Become should be combined with become_user. | |
|
| CheckLiteralBoolFormat | ANS114 | Literal bools should be consistent. | {literal-bools: [True, False, yes, no]} |
|
||||||
| CheckFilterSeparation | ANSIBLE0016 | Jinja2 filters should be separated with spaces. | |
|
| CheckBecomeUser | ANS115 | Become should be combined with become_user. | |
|
||||||
| CheckCommandInsteadOfArgument | ANSIBLE0017 | Commands should not be used in place of module arguments. | |
|
| CheckFilterSeparation | ANS116 | Jinja2 filters should be separated with spaces. | |
|
||||||
| CheckFilePermissionMissing | ANSIBLE0018 | File permissions unset or incorrect. | |
|
| CheckCommandInsteadOfArgument | ANS117 | Commands should not be used in place of module arguments. | |
|
||||||
| CheckFilePermissionOctal | ANSIBLE0019 | Octal file permissions must contain leading zero or be a string. | |
|
| CheckFilePermissionMissing | ANS118 | File permissions unset or incorrect. | |
|
||||||
| CheckGitHasVersion | ANSIBLE0020 | Git checkouts should use explicit version. | |
|
| CheckFilePermissionOctal | ANS119 | Octal file permissions must contain leading zero or be a string. | |
|
||||||
| CheckMetaChangeFromDefault | ANSIBLE0021 | Roles meta/main.yml default values should be changed. | |
|
| CheckGitHasVersion | ANS120 | Git checkouts should use explicit version. | |
|
||||||
| CheckWhenFormat | ANSIBLE0022 | Don't use Jinja2 in `when`. | |
|
| CheckMetaChangeFromDefault | ANS121 | Roles meta/main.yml default values should be changed. | |
|
||||||
| CheckNestedJinja | ANSIBLE0023 | Don't use nested Jinja2 pattern. | |
|
| CheckWhenFormat | ANS122 | Don't use Jinja2 in `when`. | |
|
||||||
| CheckLocalAction | ANSIBLE0024 | Don't use local_action. | |
|
| CheckNestedJinja | ANS123 | Don't use nested Jinja2 pattern. | |
|
||||||
| CheckRelativeRolePaths | ANSIBLE0025 | Don't use a relative path in a role. | |
|
| CheckLocalAction | ANS124 | Don't use local_action. | |
|
||||||
| CheckChangedInWhen | ANSIBLE0026 | Use handlers instead of `when: changed`. | |
|
| CheckRelativeRolePaths | ANS125 | Don't use a relative path in a role. | |
|
||||||
| CheckChangedInWhen | ANSIBLE0027 | Deprecated bare variables in loops must not be used. | |
|
| CheckChangedInWhen | ANS126 | Use handlers instead of `when: changed`. | |
|
||||||
| CheckVersion | ANSIBLE9998 | Standards version should be pinned. | |
|
| CheckChangedInWhen | ANS127 | Deprecated bare variables in loops must not be used. | |
|
||||||
| CheckDeprecated | ANSIBLE9999 | Deprecated features of `ansible-later` should not be used. | |
|
| CheckFQCNBuiltin | ANS128 | Module actions should use full qualified collection names. | |
|
||||||
|
| CheckFQCNBuiltin | ANS129 | Check optimized playbook/tasks key order. | |
|
||||||
|
| CheckDeprecated | ANS999 | Deprecated features of `ansible-later` should not be used. | |
|
||||||
|
@ -23,5 +23,5 @@ main:
|
|||||||
sub:
|
sub:
|
||||||
- name: Candidates
|
- name: Candidates
|
||||||
ref: "/build_rules/candidates"
|
ref: "/build_rules/candidates"
|
||||||
- name: Standards checks
|
- name: Rules
|
||||||
ref: "/build_rules/standards_check"
|
ref: "/build_rules/rule"
|
||||||
|
964
poetry.lock
generated
964
poetry.lock
generated
File diff suppressed because it is too large
Load Diff
@ -20,34 +20,30 @@ classifiers = [
|
|||||||
description = "Reviews ansible playbooks, roles and inventories and suggests improvements."
|
description = "Reviews ansible playbooks, roles and inventories and suggests improvements."
|
||||||
documentation = "https://ansible-later.geekdocs.de/"
|
documentation = "https://ansible-later.geekdocs.de/"
|
||||||
homepage = "https://ansible-later.geekdocs.de/"
|
homepage = "https://ansible-later.geekdocs.de/"
|
||||||
include = [
|
include = ["LICENSE"]
|
||||||
"LICENSE",
|
|
||||||
]
|
|
||||||
keywords = ["ansible", "code", "review"]
|
keywords = ["ansible", "code", "review"]
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
name = "ansible-later"
|
name = "ansible-later"
|
||||||
packages = [
|
packages = [{ include = "ansiblelater" }]
|
||||||
{include = "ansiblelater"},
|
|
||||||
]
|
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
repository = "https://github.com/thegeeklab/ansible-later/"
|
repository = "https://github.com/thegeeklab/ansible-later/"
|
||||||
version = "0.0.0"
|
version = "0.0.0"
|
||||||
|
|
||||||
[tool.poetry.dependencies]
|
[tool.poetry.dependencies]
|
||||||
PyYAML = "6.0.1"
|
PyYAML = "6.0.2"
|
||||||
ansible = {version = "8.7.0", optional = true}
|
ansible-core = { version = "2.14.17", optional = true }
|
||||||
ansible-core = {version = "2.15.8", optional = true}
|
ansible = { version = "7.7.0", optional = true }
|
||||||
anyconfig = "0.13.0"
|
anyconfig = "0.14.0"
|
||||||
appdirs = "1.4.4"
|
appdirs = "1.4.4"
|
||||||
colorama = "0.4.6"
|
colorama = "0.4.6"
|
||||||
jsonschema = "4.20.0"
|
jsonschema = "4.23.0"
|
||||||
nested-lookup = "0.2.25"
|
nested-lookup = "0.2.25"
|
||||||
pathspec = "0.12.1"
|
pathspec = "0.12.1"
|
||||||
python = "^3.9.0"
|
python = "^3.9.0"
|
||||||
python-json-logger = "2.0.7"
|
python-json-logger = "2.0.7"
|
||||||
toolz = "0.12.0"
|
toolz = "1.0.0"
|
||||||
unidiff = "0.7.5"
|
unidiff = "0.7.5"
|
||||||
yamllint = "1.33.0"
|
yamllint = "1.35.1"
|
||||||
|
|
||||||
[tool.poetry.extras]
|
[tool.poetry.extras]
|
||||||
ansible = ["ansible"]
|
ansible = ["ansible"]
|
||||||
@ -57,10 +53,10 @@ ansible-core = ["ansible-core"]
|
|||||||
ansible-later = "ansiblelater.__main__:main"
|
ansible-later = "ansiblelater.__main__:main"
|
||||||
|
|
||||||
[tool.poetry.group.dev.dependencies]
|
[tool.poetry.group.dev.dependencies]
|
||||||
ruff = "0.1.11"
|
ruff = "0.7.2"
|
||||||
pytest = "7.4.4"
|
pytest = "8.3.3"
|
||||||
pytest-mock = "3.12.0"
|
pytest-mock = "3.14.0"
|
||||||
pytest-cov = "4.1.0"
|
pytest-cov = "6.0.0"
|
||||||
toml = "0.10.2"
|
toml = "0.10.2"
|
||||||
|
|
||||||
[tool.poetry-dynamic-versioning]
|
[tool.poetry-dynamic-versioning]
|
||||||
@ -100,6 +96,7 @@ exclude = [
|
|||||||
line-length = 99
|
line-length = 99
|
||||||
indent-width = 4
|
indent-width = 4
|
||||||
|
|
||||||
|
[tool.ruff.lint]
|
||||||
# Explanation of errors
|
# Explanation of errors
|
||||||
#
|
#
|
||||||
# D100: Missing docstring in public module
|
# D100: Missing docstring in public module
|
||||||
|
Loading…
Reference in New Issue
Block a user