Compare commits

...

248 Commits
v3.2.1 ... main

Author SHA1 Message Date
renovate[bot] e6ca4cfdb0
chore(deps): lock file maintenance (#809)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-17 09:37:20 +02:00
renovate[bot] e1b535af8a
chore(deps): update quay.io/thegeeklab/hugo docker tag to v0.125.7 (#810)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-14 14:08:53 +02:00
renovate[bot] b7401677dc chore(deps): update dependency ruff to v0.4.4 2024-05-13 02:46:59 +02:00
renovate[bot] 8213c46e05
chore(deps): lock file maintenance (#807)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-07 12:15:03 +02:00
renovate[bot] e2549dfc77 chore(deps): update dependency ruff to v0.4.3 2024-05-06 03:30:21 +02:00
renovate[bot] 4c3b8f2e82
fix(deps): update dependency jsonschema to v4.22.0 (#805)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-01 12:36:49 +02:00
renovate[bot] 5e64c9d79d
chore(deps): lock file maintenance (#802)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-04-29 10:29:29 +02:00
renovate[bot] cfcd804959 chore(deps): update devdeps non-major 2024-04-29 03:04:16 +02:00
renovate[bot] 13ac29da22
fix(deps): update dependency ansible-core to v2.14.16 (#803)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-04-23 21:18:16 +02:00
renovate[bot] eeae482b31 chore(deps): update dependency ruff to v0.4.1 2024-04-22 04:20:01 +02:00
renovate[bot] 0a53eee7f3 chore(deps): update dependency ruff to v0.3.7 2024-04-15 03:26:31 +02:00
renovate[bot] 73e960f0b7
chore(deps): lock file maintenance (#794)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-04-12 09:18:14 +02:00
renovate[bot] 5d40978fa3 chore(docker): update python:3.12-alpine docker digest to ef09762 2024-04-11 02:22:02 +02:00
renovate[bot] 9dcaf5e488 chore(deps): update dependency thegeeklab/hugo-geekdoc to v0.45.0 2024-04-08 02:48:45 +02:00
renovate[bot] fa65be587b chore(deps): update dependency ruff to v0.3.5 2024-04-08 02:47:25 +02:00
renovate[bot] dcc2e08d2c chore(deps): update dependency thegeeklab/hugo-geekdoc to v0.44.3 2024-04-01 02:17:59 +02:00
renovate[bot] ab4f8b209f
chore(deps): update quay.io/thegeeklab/wp-docker-buildx docker tag to v4 (#791)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-03-27 08:56:19 +01:00
renovate[bot] 82e09a5726 chore(docker): update python:3.12-alpine docker digest to c7eb5c9 2024-03-27 02:34:21 +01:00
renovate[bot] cef217826e
chore(deps): lock file maintenance (#785)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-03-26 08:33:14 +01:00
renovate[bot] 64e1b4dadb
fix(deps): update dependency ansible-core to v2.14.15 (#789)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-03-26 08:33:06 +01:00
renovate[bot] 0f00036d7c
chore(deps): update dependency pytest-cov to v5 (#787)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-03-25 08:34:14 +01:00
renovate[bot] 2209fabe28 chore(deps): update devdeps non-major 2024-03-25 01:36:01 +01:00
renovate[bot] c7b9e492d3
chore(deps): update quay.io/thegeeklab/hugo docker tag to v0.124.1 (#786)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-03-20 22:54:15 +01:00
renovate[bot] ab9b9c6ae9 chore(deps): update dependency ruff to v0.3.3 2024-03-18 01:32:06 +01:00
renovate[bot] de4825ff70 chore(docker): update python:3.12-alpine docker digest to 25a82f6 2024-03-17 01:46:53 +01:00
Robert Kaussow e285bf3351
ci: fix deprecated ruff command 2024-03-12 20:52:53 +01:00
renovate[bot] 58beb822ae chore(deps): update dependency thegeeklab/hugo-geekdoc to v0.44.2 2024-03-11 02:26:27 +01:00
renovate[bot] afcbd3d356 chore(deps): update devdeps non-major 2024-03-11 02:26:10 +01:00
renovate[bot] a5832c553a chore(deps): update devdeps non-major 2024-03-04 01:40:41 +01:00
renovate[bot] fc4a6944d6
chore(deps): lock file maintenance (#778)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-02-26 10:41:38 +01:00
renovate[bot] d6ac162764
chore(deps): lock file maintenance (#776)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-02-26 10:14:12 +01:00
renovate[bot] a7623f7a5a chore(deps): update dependency pytest to v8.0.2 2024-02-26 02:55:16 +01:00
renovate[bot] f0baf76e24 chore(deps): update devdeps non-major 2024-02-19 01:13:55 +01:00
renovate[bot] f754a7f292
fix(deps): update dependency yamllint to v1.35.1 (#774)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-02-16 13:56:52 +01:00
renovate[bot] d92da10f13
fix(deps): update dependency yamllint to v1.35.0 (#773)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-02-15 12:29:39 +01:00
Robert Kaussow e6027445c8
[skip ci] revert renovate automerge config 2024-02-15 12:23:08 +01:00
renovate[bot] 1d7321e3dc
chore(deps): update quay.io/thegeeklab/hugo docker tag to v0.122.0 (#772)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-02-11 21:50:13 +01:00
Robert Kaussow 252af19cb3
enable renovate on automerge branches 2024-02-09 23:08:30 +01:00
renovate[bot] 2803c86816 chore(docker): update python:3.12-alpine docker digest to 1a05012 2024-02-09 04:20:27 +01:00
renovate[bot] 359db66db2
fix(deps): update dependency yamllint to v1.34.0 (#770)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-02-06 11:20:31 +01:00
Robert Kaussow 0e74461b58
chore: bump ruff to v0.2.1 (#769) 2024-02-06 09:34:20 +01:00
renovate[bot] 942420db34 chore(docker): update python:3.12-alpine docker digest to 14cfc61 2024-02-02 14:35:26 +01:00
Robert Kaussow b90b030054
docs: remove ref to unmaintained github action 2024-02-01 12:37:12 +01:00
Robert Kaussow 8dba488747
docs: add document-end to default config 2024-02-01 12:30:50 +01:00
Robert Kaussow a89d6d5336
fix: fix handling of blocks (#767) 2024-01-31 22:49:19 +01:00
renovate[bot] 37e701217e
fix(deps): update dependency anyconfig to v0.14.0 (#742)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-01-31 10:50:12 +01:00
Robert Kaussow 48baa2f338
fix: pkg_resources with importlib.resources (#766) 2024-01-31 10:28:46 +01:00
Robert Kaussow ce0d895fc4
feat: add new rule CheckKeyOrder (#765) 2024-01-30 22:35:45 +01:00
renovate[bot] 9d6dd16c1c
fix(deps): update dependency ansible-core to v2.14.14 (#763)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-01-30 08:42:23 +01:00
Robert Kaussow 0e197fd585
fix: ensure yaml document end marker is not set (#762)
BREAKING CHANGE: The default setting for the yamllint rule `docuent-end` has been changed to `false`.
2024-01-29 21:28:23 +01:00
renovate[bot] 9797533a14
chore(deps): update dependency pytest to v8 (#760)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-01-29 08:34:31 +01:00
Robert Kaussow 674ffe6445
feat: add new rule CheckFQCN (#761) 2024-01-29 08:34:08 +01:00
Robert Kaussow 6fd39b62a4
revert candidate filetype handling 2024-01-28 21:38:56 +01:00
Robert Kaussow 7121b2ce6f
use candiate filetype 2024-01-28 14:54:27 +01:00
Robert Kaussow 74f58c59c9
fix: strip builtin fqc prefix from task action (#759) 2024-01-27 20:56:00 +01:00
Robert Kaussow da5d3c21c2
feat: add rule CheckYamlOctalValues (#758) 2024-01-27 20:31:15 +01:00
Robert Kaussow 7b44647bec
chore: improve desc and help for CheckFilePermissionOctal 2024-01-27 19:59:45 +01:00
Robert Kaussow 2f4e35d83c
refactor: use lint-like rule identifier (#757) 2024-01-27 19:56:35 +01:00
Robert Kaussow 80ac8ec34d
fix toml formatting 2024-01-27 18:20:15 +01:00
Robert Kaussow 8861dcc705
add back optional dependency group ansible 2024-01-26 10:20:48 +01:00
renovate[bot] b365b32638
fix(deps): update dependency toolz to v0.12.1 (#751)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-01-25 22:40:01 +01:00
renovate[bot] 087df4848e
fix(deps): update dependency ansible-core to v2.14.13 (#755)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-01-25 22:33:50 +01:00
Robert Kaussow 39a5e90e78
chore: cleanup optional ansible dependency (#754) 2024-01-25 22:27:08 +01:00
Robert Kaussow 2df48598ec
refactor: drop default standards version and rename to rules (#752)
BREAKING CHANGE: The option to define a `Standards` version has been removed. Every new rule that is added on upcoming releases is activated by default and will also create errors if triggered. The behavior of rules can be controlled by the existing `rules.exclude_filter` or `rules.warning_filter` options.

BREAKING CHANGE: The option `rules.buildin` has been renamed to `rules.builtin`.

BREAKING CHANGE: The option `rules.standards` has been renamed to `rules.dir`.

BREAKING CHANGE: The option `rules.filter` has been renamed to `rules.include_filter`.
2024-01-25 21:40:15 +01:00
renovate[bot] d360de2125
chore(deps): update quay.io/thegeeklab/wp-docker-buildx docker tag to v3 (#750)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-01-22 11:29:41 +01:00
renovate[bot] 0137b863fa chore(deps): update dependency ruff to v0.1.14 2024-01-22 01:34:45 +01:00
renovate[bot] d4db45ba05
fix(deps): update dependency jsonschema to v4.21.1 (#748)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-01-20 15:48:13 +01:00
renovate[bot] e9a082b2fa chore(docker): update python:3.12-alpine docker digest to 801b54e 2024-01-19 23:33:30 +01:00
renovate[bot] 975a7bd661 chore(docker): update python:3.12-alpine docker digest to 4a156f7 2024-01-19 05:57:59 +01:00
renovate[bot] f907222162
fix(deps): update dependency jsonschema to v4.21.0 (#745)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-01-18 10:21:58 +01:00
renovate[bot] 7f5c2a51b1 chore(deps): update dependency thegeeklab/hugo-geekdoc to v0.44.1 2024-01-16 02:57:53 +01:00
renovate[bot] c51a4111ec
chore(deps): lock file maintenance (#735)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-01-15 21:58:56 +01:00
Robert Kaussow 8c9420b445 fix linting 2024-01-15 15:26:56 +01:00
renovate[bot] 87ef3539cb chore(deps): update dependency ruff to v0.1.13 2024-01-15 15:26:56 +01:00
renovate[bot] 952cbb265b chore(deps): update dependency ruff to v0.1.11 2024-01-08 02:43:41 +01:00
renovate[bot] c68679535c
chore(deps): update quay.io/thegeeklab/hugo docker tag to v0.121.2 (#740)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-01-07 20:42:37 +01:00
renovate[bot] ff428aaa21 chore(deps): update dependency pytest to v7.4.4 2024-01-01 03:15:34 +01:00
renovate[bot] 969118c90f chore(deps): update dependency ruff to v0.1.9 2023-12-27 11:12:30 +01:00
Robert Kaussow 8250ecb577
disable renovate for python test matrix in ci 2023-12-24 00:03:56 +01:00
Robert Kaussow 33089cd159
use list style synatx and cleanup (#736) 2023-12-23 23:26:44 +01:00
renovate[bot] b9755de618 chore(deps): update dependency ruff to v0.1.8 2023-12-18 01:21:31 +01:00
Robert Kaussow 4f1e3c6551
cleanup unused env vars in ci 2023-12-17 14:07:59 +01:00
renovate[bot] adfe297f92
fix(deps): update dependency pathspec to v0.12.1 (#730)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-12-12 09:22:14 +01:00
renovate[bot] 1de67de11e
chore(deps): lock file maintenance (#732)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-12-12 08:54:17 +01:00
renovate[bot] 41e7bd1e4d
fix(deps): update dependency ansible-core to v2.15.8 (#733)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-12-12 08:53:59 +01:00
renovate[bot] d24b5caa7e chore(deps): update dependency ruff to v0.1.7 2023-12-11 02:37:32 +01:00
renovate[bot] ae8968ce6e chore(docker): update python:3.12-alpine docker digest to c793b92 2023-12-09 08:00:48 +01:00
renovate[bot] 771a6feea3 chore(docker): update python:3.12-alpine docker digest to 401aa10 2023-12-09 04:30:56 +01:00
Robert Kaussow 2967885c9b
ci: exclude dockerhub from linkcheck due to rate limiting 2023-12-07 09:08:26 +01:00
renovate[bot] f8036e12a1
chore(deps): update quay.io/thegeeklab/wp-docker-buildx docker tag to v2 (#727)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-12-07 08:40:23 +01:00
renovate[bot] 2a03723047
fix(deps): update dependency ansible to v8.7.0 (#726)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-12-06 21:48:36 +01:00
renovate[bot] 422df4d050
fix(deps): update dependency ansible-core to v2.15.7 (#725)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-12-05 08:09:49 +01:00
renovate[bot] 7de980eb31
fix(deps): update dependency jsonschema to v4.20.0 (#720)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-12-04 21:36:58 +01:00
dependabot[bot] 6c1ae48cc2
chore(deps): bump cryptography from 41.0.5 to 41.0.6 (#724)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-12-04 21:36:43 +01:00
renovate[bot] 5e32636dbe chore(docker): update python:3.12-alpine docker digest to 09f18c1 2023-12-04 21:19:35 +01:00
renovate[bot] c78ed5fb9e
chore(deps): lock file maintenance (#718)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-12-04 21:09:03 +01:00
renovate[bot] c1e8ceb7da
fix(deps): update dependency yamllint to v1.33.0 (#714)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-12-04 21:08:39 +01:00
Robert Kaussow 62c137a130
fix settings for required status checks 2023-12-04 21:01:50 +01:00
renovate[bot] 71d5ca9942 chore(deps): update dependency ruff to v0.1.6 2023-11-20 03:21:59 +01:00
Thorsten B 2f2d687f49
docs: fix minor typo (#719) 2023-11-15 21:07:55 +01:00
renovate[bot] 400141599b chore(deps): update dependency ruff to v0.1.5 2023-11-13 02:07:45 +01:00
Robert Kaussow 8266f92b47
chore: drop yapf and favor of the ruff formatter (#716) 2023-11-10 14:50:48 +01:00
renovate[bot] 511728afdf
fix(deps): update dependency ansible to v8.6.1 (#715)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-11-10 08:36:45 +01:00
renovate[bot] d226cdd07f
fix(deps): update dependency ansible to v8.6.0 (#713)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-11-08 09:38:29 +01:00
renovate[bot] 274d19a379
chore(deps): lock file maintenance (#710)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-11-07 11:16:06 +01:00
renovate[bot] ce7932be7d
fix(deps): update dependency ansible-core to v2.15.6 (#711)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-11-07 11:15:55 +01:00
renovate[bot] ab7e2d7ea9 chore(deps): update dependency ruff to v0.1.4 2023-11-06 01:44:20 +01:00
Robert Kaussow d687e263cc
ci: cleanup matrix build name (#708) 2023-11-04 16:24:24 +01:00
renovate[bot] b554557440
chore(deps): lock file maintenance (#706)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-10-31 20:43:41 +01:00
renovate[bot] 894c3ce107
fix(deps): update dependency jsonschema to v4.19.2 (#707)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-10-31 20:08:05 +01:00
renovate[bot] 77cd620449 chore(deps): update devdeps non-major 2023-10-30 02:05:19 +01:00
renovate[bot] b1491413ff chore(deps): update dependency thegeeklab/hugo-geekdoc to v0.44.0 2023-10-29 00:52:47 +02:00
renovate[bot] 5a86410a59 chore(deps): update dependency thegeeklab/hugo-geekdoc to v0.43.0 2023-10-27 15:45:37 +02:00
renovate[bot] 237aaf8d09 chore(deps): update devdeps non-major 2023-10-23 02:27:10 +02:00
renovate[bot] 547e9d2148 chore(deps): update dependency thegeeklab/hugo-geekdoc to v0.42.0 2023-10-21 18:08:40 +02:00
renovate[bot] e000e49e16 chore(deps): update dependency thegeeklab/hugo-geekdoc to v0.41.3 2023-10-20 12:08:26 +02:00
renovate[bot] 0d59c5de12 chore(docker): update python:3.12-alpine docker digest to a5d1738 2023-10-19 12:07:34 +02:00
renovate[bot] 3ef12f0cc5 chore(docker): update python:3.12-alpine docker digest to a4029bd 2023-10-19 08:20:08 +02:00
Robert Kaussow dc6146fc46
ci: fix changelog generation 2023-10-18 13:59:24 +02:00
Robert Kaussow c4a96aad71
chore: replace linkcheck by lychee (#697) 2023-10-16 21:34:15 +02:00
Robert Kaussow 80fe102901
chore: replace git-chglog by git-sv (#696) 2023-10-16 15:43:00 +02:00
renovate[bot] 3f838836a7
chore(deps): lock file maintenance (#693)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-10-16 14:29:22 +02:00
Robert Kaussow a35f5030ec
feat: add support for python 3.12 (#695)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-10-16 14:15:26 +02:00
Robert Kaussow 0ebb855bc5
fix: remove deprecated distutils (#694) 2023-10-16 12:16:44 +02:00
renovate[bot] f7592ce839
fix(deps): update dependency ansible to v8.5.0 (#692)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-10-11 20:32:56 +02:00
renovate[bot] 1425d99934 chore(deps): update dependency thegeeklab/hugo-geekdoc to v0.41.2 2023-10-10 23:56:43 +02:00
renovate[bot] 40a47aebf8
fix(deps): update dependency ansible-core to v2.15.5 (#690)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-10-10 09:10:39 +02:00
renovate[bot] 5d8c1b6104
chore(deps): lock file maintenance (#689)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-10-09 14:58:26 +02:00
renovate[bot] da209c6bf7 chore(deps): update dependency ruff to v0.0.292 2023-10-09 02:21:08 +02:00
renovate[bot] 0f8a2c0cdb chore(docker): update python:3.11-alpine docker digest to 3e73c0b 2023-10-03 11:17:25 +02:00
renovate[bot] 166c8aece3 chore(docker): update python:3.11-alpine docker digest to 29a71a3 2023-10-03 10:09:00 +02:00
renovate[bot] 77a92f31f4 chore(docker): update python:3.11-alpine docker digest to ab2c3f7 2023-10-03 05:23:31 +02:00
renovate[bot] 3a181f2043
chore(deps): lock file maintenance (#682)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-10-02 13:29:32 +02:00
renovate[bot] 59fe984f03 chore(docker): update python:3.11-alpine docker digest to cd311c6 2023-09-29 18:40:12 +02:00
renovate[bot] 05131edbc7 chore(docker): update python:3.11-alpine docker digest to ca1736e 2023-09-29 06:40:13 +02:00
renovate[bot] 41ee34a95d chore(docker): update python:3.11-alpine docker digest to ab19fb3 2023-09-29 03:26:02 +02:00
Robert Kaussow 1ca9588968
ci: use secret for s3 endpoint 2023-09-26 21:40:31 +02:00
renovate[bot] bf4b5914fd
chore(deps): lock file maintenance (#676)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-09-26 20:52:11 +02:00
renovate[bot] b9717bced2
fix(deps): update dependency jsonschema to v4.19.1 (#674)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-09-26 20:50:49 +02:00
Robert Kaussow e588c9469f
fix: fix exclude_files while ignore_dotfiles is disabled (#678) 2023-09-26 20:50:42 +02:00
Robert Kaussow 024af07c65
docs: drop codecov badge 2023-09-25 09:26:58 +02:00
renovate[bot] 981383274f chore(deps): update devdeps non-major 2023-09-25 03:46:57 +02:00
renovate[bot] 1c1f33cbb2
fix(deps): update dependency ansible to v8.4.0 (#672)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-09-18 08:59:33 +02:00
renovate[bot] c0788df9e3
chore(deps): lock file maintenance (#666)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-09-18 08:45:02 +02:00
renovate[bot] 1951a354ea
fix(deps): update dependency ansible-core to v2.15.4 (#671)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-09-18 08:44:53 +02:00
renovate[bot] ad14cf1a9f chore(deps): update dependency ruff to v0.0.290 2023-09-18 02:12:31 +02:00
renovate[bot] 13abe0ef92 chore(deps): update dependency pytest to v7.4.2 2023-09-11 03:58:32 +02:00
renovate[bot] 4933296bca chore(deps): update devdeps non-major 2023-09-04 02:47:27 +02:00
renovate[bot] f890714fa5 chore(deps): update dependency thegeeklab/hugo-geekdoc to v0.41.1 2023-09-03 23:31:18 +02:00
renovate[bot] f214552ae1 chore(deps): update dependency thegeeklab/hugo-geekdoc to v0.41.0 2023-09-01 00:54:41 +02:00
renovate[bot] 873318916c chore(deps): update dependency ruff to v0.0.286 2023-08-28 03:21:40 +02:00
renovate[bot] 5d1450b044 chore(docker): update python:3.11-alpine docker digest to 5d769f9 2023-08-26 09:51:47 +02:00
renovate[bot] 38eb46c3cd chore(docker): update python:3.11-alpine docker digest to 7e8b581 2023-08-26 05:47:05 +02:00
renovate[bot] f85bf41b57 chore(docker): update python:3.11-alpine docker digest to 5324fad 2023-08-26 03:04:29 +02:00
Theodore Ni bb526ed483
fix: use poetry-dynamic-versioning for PEP 517 builds (#658) 2023-08-25 00:27:05 +02:00
renovate[bot] 50ea5608d3
chore(deps): lock file maintenance (#660)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-08-25 00:23:20 +02:00
renovate[bot] 5ea41e66dd
chore(deps): update dependency ruff to v0.0.285 (#659)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-08-25 00:23:07 +02:00
Robert Kaussow 78cdf8cd93
ci: migrate to woodpecker (#661) 2023-08-25 00:07:23 +02:00
renovate[bot] bd9810fb69
fix(deps): update dependency ansible to v8.3.0 (#657)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-08-17 15:42:23 +02:00
renovate[bot] 5e7a8ebfe3
chore(deps): lock file maintenance (#655)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-08-17 15:17:35 +02:00
renovate[bot] 9102934c3c
fix(deps): update dependency ansible-core to v2.15.3 (#656)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-08-17 15:17:25 +02:00
renovate[bot] 279e7c0c3c chore(deps): update dependency ruff to v0.0.284 2023-08-14 03:39:44 +00:00
renovate[bot] bdddc6196d chore(docker): update python:3.11-alpine docker digest to 603975e 2023-08-09 18:16:31 +00:00
renovate[bot] f0a1372dcb chore(docker): update python:3.11-alpine docker digest to 4352dc7 2023-08-09 06:38:12 +00:00
renovate[bot] 291a02d426
fix(deps): update dependency jsonschema to v4.19.0 (#650)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-08-09 07:34:47 +02:00
renovate[bot] 1e4bf15b1d
chore(deps): lock file maintenance (#649)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-08-09 07:33:31 +02:00
renovate[bot] 523522d86d chore(docker): update python:3.11-alpine docker digest to bd16cc5 2023-08-08 10:03:33 +00:00
renovate[bot] 52c300d3f2
chore(deps): update dependency ruff to v0.0.282 (#648)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Robert Kaussow <mail@thegeeklab.de>
2023-08-07 10:35:35 +02:00
renovate[bot] 9b1bc48741
fix(deps): update dependency jsonschema to v4.18.6 (#646)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-08-05 15:50:21 +02:00
renovate[bot] a18731d80f
fix(deps): update dependency jsonschema to v4.18.5 (#645)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-08-02 22:00:20 +02:00
renovate[bot] 968a7a2f63
chore(deps): lock file maintenance (#644)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-08-02 10:47:19 +02:00
renovate[bot] 57116fbb15
fix(deps): update dependency pathspec to v0.11.2 (#643)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-08-02 10:44:23 +02:00
renovate[bot] dd6d1e203e
chore(deps): update dependency ruff to v0.0.280 (#641)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Robert Kaussow <mail@thegeeklab.de>
2023-07-24 09:01:54 +02:00
renovate[bot] e1184ce4c6
chore(deps): lock file maintenance (#642)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-07-24 08:44:50 +02:00
renovate[bot] c049bf44d5
fix(deps): update dependency jsonschema to v4.18.4 (#631)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-07-19 13:05:03 +02:00
renovate[bot] fde78ad9d2
fix(deps): update dependency ansible to v8.2.0 (#640)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-07-19 10:57:17 +02:00
renovate[bot] 7c80ae1e3f
fix(deps): update dependency ansible-core to v2.15.2 (#638)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-07-19 10:36:12 +02:00
renovate[bot] 1b244bf1e4
chore(deps): lock file maintenance (#633)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-07-19 10:07:36 +02:00
renovate[bot] ce28c25add
fix(deps): update dependency pyyaml to v6.0.1 (#639)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-07-19 10:07:21 +02:00
renovate[bot] 4684e3024e chore(deps): update dependency ruff to v0.0.278 2023-07-17 04:37:06 +00:00
renovate[bot] 8b0a186a23 chore(deps): update dependency thegeeklab/hugo-geekdoc to v0.40.1 2023-07-12 13:22:35 +00:00
renovate[bot] 8d4d7f8655 chore(deps): update dependency thegeeklab/hugo-geekdoc to v0.40.0 2023-07-11 21:36:30 +00:00
Robert Kaussow f772ae7c4d
ci: bump hugo to v0.115.2 (#635) 2023-07-11 21:35:21 +02:00
renovate[bot] 5423cf9580 chore(deps): update dependency ruff to v0.0.277 2023-07-10 04:23:48 +00:00
renovate[bot] 2fe0ab3696 chore(deps): update dependency thegeeklab/hugo-geekdoc to v0.39.7 2023-07-03 10:41:28 +00:00
renovate[bot] 3545561d76 chore(deps): update dependency thegeeklab/hugo-geekdoc to v0.39.6 2023-06-28 21:08:34 +00:00
renovate[bot] 22aaf73f64
chore(deps): lock file maintenance (#627)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-06-28 15:57:48 +02:00
Robert Kaussow 26ce453fc4
fix: ignore complex changed when checks in rule CheckChangedInWhen (#628) 2023-06-28 15:57:40 +02:00
renovate[bot] a38a9aa11b
chore(deps): update devdeps non-major (#626)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Robert Kaussow <mail@thegeeklab.de>
2023-06-28 09:55:18 +02:00
renovate[bot] c55733f0eb chore(deps): update dependency thegeeklab/hugo-geekdoc to v0.39.5 2023-06-23 13:03:35 +00:00
Robert Kaussow 2f09ef6c31
ci: bump hugo to v0.114.0 (#625) 2023-06-23 11:19:01 +02:00
renovate[bot] 512dc44a89
fix(deps): update dependency ansible to v8.1.0 (#623)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-06-23 09:13:28 +02:00
renovate[bot] 237b1a2748
chore(deps): lock file maintenance (#620)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-06-22 10:05:10 +02:00
renovate[bot] 8cc4d0dc12
fix(deps): update dependency ansible-core to v2.15.1 (#622)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-06-22 10:05:02 +02:00
Robert Kaussow 0fabe9c7b4
docs: replace socialmedia image (#621) 2023-06-20 14:23:39 +02:00
renovate[bot] bb4f42ea8b chore(deps): update dependency pytest-mock to v3.11.1 2023-06-19 03:43:45 +00:00
renovate[bot] 202ba187bb chore(docker): update python:3.11-alpine docker digest to 25df32b 2023-06-15 16:11:48 +00:00
renovate[bot] 26a06f2eb7
chore(deps): lock file maintenance (#613)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-06-15 10:38:41 +02:00
renovate[bot] 4409b1c8e9
chore(deps): update devdeps non-major (#617)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Robert Kaussow <mail@thegeeklab.de>
2023-06-15 09:30:03 +02:00
renovate[bot] e3aae7b231 chore(deps): update dependency thegeeklab/hugo-geekdoc to v0.39.4 2023-06-09 10:25:42 +00:00
renovate[bot] e5c187504b chore(docker): update python:3.11-alpine docker digest to 995c7fc 2023-06-08 03:35:47 +00:00
renovate[bot] 8f22a26d26 chore(deps): update dependency thegeeklab/hugo-geekdoc to v0.39.3 2023-06-05 13:50:04 +00:00
Robert Kaussow c4d406f138
fix: call init_plugin_loader to fix module loading during module arg parse (#612) 2023-05-31 10:19:45 +02:00
renovate[bot] 6bde8f1437
fix(deps): update dependency ansible to v8 (#611)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-05-31 08:28:21 +02:00
renovate[bot] 43675d771d
fix(deps): update dependency ansible-core to v2.15.0 (#602)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-05-31 08:11:16 +02:00
renovate[bot] 3f2d169354 chore(deps): update dependency thegeeklab/hugo-geekdoc to v0.39.2 2023-05-29 14:03:25 +00:00
Robert Kaussow 15e3909660
fix: regression in CheckScmInSrc rule (#610) 2023-05-29 14:51:52 +02:00
Robert Kaussow adc1801724
fix: fix error on empty or none string src in rule CheckScmInSrc (#608) 2023-05-29 13:47:46 +02:00
renovate[bot] a77609197f
chore(deps): lock file maintenance (#595)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-05-29 12:09:28 +02:00
renovate[bot] 55b4f2a091
fix(deps): update dependency ansible to v7.6.0 (#606)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-05-29 12:09:17 +02:00
renovate[bot] 065984e123
fix(deps): update dependency yamllint to v1.32.0 (#605)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-05-29 11:35:49 +02:00
renovate[bot] ccc1fe999d
chore(deps): update devdeps non-major (#603) 2023-05-28 23:13:08 +02:00
Robert Kaussow 34cf06c9c1
chore: remove poetry experimental.new-installer flag (#607) 2023-05-28 15:38:12 +02:00
renovate[bot] f567ec2ab2
fix(deps): update dependency ansible-core to v2.14.6 (#604)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-05-28 15:30:01 +02:00
renovate[bot] ef10fb8ae1 chore(docker): update python:3.11-alpine docker digest to 4e8e9a5 2023-05-12 21:09:54 +00:00
renovate[bot] 0bc487f8c4 chore(docker): update python:3.11-alpine docker digest to 2f2dadb 2023-05-12 05:39:23 +00:00
renovate[bot] 9bbd1ee81b chore(deps): update dependency ruff to v0.0.265 2023-05-08 04:41:30 +00:00
renovate[bot] fe6c190745 chore(deps): update dependency thegeeklab/hugo-geekdoc to v0.39.1 2023-05-04 13:32:14 +00:00
renovate[bot] fa4223a319 chore(docker): update python:3.11-alpine docker digest to 7210235 2023-05-03 23:54:18 +00:00
Robert Kaussow b43ab2915e
fix bare url in contribution file (#597) 2023-05-03 09:37:17 +02:00
renovate[bot] 96d58b6ad3 chore(deps): update dependency ruff to v0.0.263 2023-05-01 04:43:46 +00:00
Robert Kaussow 9dad9cdb5f
fix: remove with_items from CheckDeprecatedBareVars rule (#593) 2023-04-27 09:22:07 +02:00
renovate[bot] 58cf4cb09e
fix(deps): update dependency ansible to v7.5.0 (#592)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-04-27 08:47:18 +02:00
Robert Kaussow ce47455b0f
fix: check for non-empty loop_type in CheckDeprecatedBareVars (#591) 2023-04-26 09:41:50 +02:00
Robert Kaussow 9a9bf37702
feat: add role for deprecated loop bare vars (#586) 2023-04-26 08:58:34 +02:00
renovate[bot] 151741f70a
fix(deps): update dependency yamllint to v1.31.0 (#587)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-04-25 16:30:44 +02:00
renovate[bot] 4ba87b90fe
chore(deps): lock file maintenance (#589)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-04-25 09:50:18 +02:00
renovate[bot] d3a1fb816a
fix(deps): update dependency ansible-core to v2.14.5 (#590)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-04-25 09:50:08 +02:00
renovate[bot] bf30fc60a6 chore(deps): update devdeps non-major 2023-04-24 06:09:15 +00:00
renovate[bot] 67c7ac8f70
chore(deps): lock file maintenance (#581)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-04-20 09:33:39 +02:00
Robert Kaussow 4ec8954ab5
fix: improve url and jinja string escapes (#585) 2023-04-20 08:23:12 +02:00
renovate[bot] 5daceac699
chore(deps): update dependency thegeeklab/hugo-geekdoc to v0.39.0 (#584)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-04-19 22:24:11 +02:00
Robert Kaussow fb806086a7
ci: switch to new codecov uploader (#583) 2023-04-19 20:53:08 +02:00
renovate[bot] 82c448ca2b chore(deps): update devdeps non-major 2023-04-18 18:44:54 +00:00
renovate[bot] eba0094410 chore(docker): update python:3.11-alpine docker digest to 507818d 2023-04-08 02:39:28 +00:00
renovate[bot] 653c7ba384 chore(docker): update python:3.11-alpine docker digest to 5405826 2023-04-06 09:05:24 +00:00
renovate[bot] 60861897fc
chore(deps): lock file maintenance (#576)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-04-03 10:46:42 +02:00
renovate[bot] 4da5ccd8cf
chore(deps): update dependency ruff to v0.0.260 (#575) 2023-04-03 10:26:04 +02:00
renovate[bot] 0776af4c92 chore(docker): update python:3.11-alpine docker digest to 4b4078a 2023-03-30 06:40:40 +00:00
renovate[bot] d505795239 chore(docker): update python:3.11-alpine docker digest to 8b90b63 2023-03-30 00:52:44 +00:00
renovate[bot] 50adfc9476
fix(deps): update dependency ansible to v7.4.0 (#572) 2023-03-29 10:53:07 +02:00
renovate[bot] 8b7011689f
fix(deps): update dependency yamllint to v1.30.0 (#565) 2023-03-28 08:46:29 +02:00
renovate[bot] 6190e9a132 chore(deps): update dependency thegeeklab/hugo-geekdoc to v0.38.1 2023-03-27 23:01:17 +00:00
renovate[bot] 31cd5b6f82
chore(deps): lock file maintenance (#569) 2023-03-27 21:07:33 +02:00
renovate[bot] 1843140b76
fix(deps): update dependency ansible-core to v2.14.4 (#570) 2023-03-27 21:07:14 +02:00
renovate[bot] 6d18b52e78 chore(docker): update python:3.11-alpine docker digest to 8af856d 2023-03-27 15:07:31 +00:00
renovate[bot] 9e5f939098
chore(deps): update dependency ruff to v0.0.259 (#568) 2023-03-27 13:18:25 +02:00
renovate[bot] 2811408df3 chore(docker): update python:3.11-alpine docker digest to 506eed4 2023-03-23 19:52:10 +00:00
79 changed files with 2075 additions and 2077 deletions

View File

@ -1,23 +0,0 @@
# Changelog
{{ range .Versions -}}
## {{ if .Tag.Previous }}[{{ .Tag.Name }}]({{ $.Info.RepositoryURL }}/compare/{{ .Tag.Previous.Name }}...{{ .Tag.Name }}){{ else }}{{ .Tag.Name }}{{ end }} ({{ datetime "2006-01-02" .Tag.Date }})
{{ range .CommitGroups -}}
### {{ .Title }}
{{ range .Commits -}}
- {{ if .Scope }}**{{ .Scope }}:** {{ end }}{{ (regexReplaceAll "(.*)/issues/(.*)" (regexReplaceAll "(Co-\\w*-by.*)" .Subject "") "${1}/pull/${2}") | trim }}
{{ end }}
{{- end -}}
{{- if .NoteGroups -}}
{{ range .NoteGroups -}}
### {{ .Title }}
{{ range .Notes }}
{{ .Body }}
{{ end }}
{{ end -}}
{{ end -}}
{{ end -}}

View File

@ -1,25 +0,0 @@
style: github
template: CHANGELOG.tpl.md
info:
title: CHANGELOG
repository_url: https://github.com/thegeeklab/ansible-later
options:
commit_groups:
title_maps:
feat: Features
fix: Bug Fixes
perf: Performance Improvements
refactor: Code Refactoring
chore: Others
test: Testing
ci: CI Pipeline
docs: Documentation
header:
pattern: "^(\\w*)(?:\\(([\\w\\$\\.\\-\\*\\s]*)\\))?\\:\\s(.*)$"
pattern_maps:
- Type
- Scope
- Subject
notes:
keywords:
- BREAKING CHANGE

View File

@ -18,8 +18,8 @@ HostVars
Rolesfile
Makefile
Jinja2
ANSIBLE([0-9]{4})
LINT([0-9]{4})
ANS([0-9]{3})
YML([0-9]{3})
SCM
bools
Check[A-Z].+

View File

@ -1,440 +0,0 @@
local PythonVersion(pyversion='3.9') = {
name: 'python' + std.strReplace(pyversion, '.', '') + '-pytest',
image: 'python:' + pyversion,
environment: {
PY_COLORS: 1,
},
commands: [
'pip install poetry poetry-dynamic-versioning -qq',
'poetry config experimental.new-installer false',
'poetry install -E ansible-core',
'poetry run pytest',
'poetry version',
'poetry run ansible-later --help',
],
depends_on: [
'fetch',
],
};
local PipelineLint = {
kind: 'pipeline',
name: 'lint',
platform: {
os: 'linux',
arch: 'amd64',
},
steps: [
{
name: 'check-format',
image: 'python:3.11',
environment: {
PY_COLORS: 1,
},
commands: [
'git fetch -tq',
'pip install poetry poetry-dynamic-versioning -qq',
'poetry config experimental.new-installer false',
'poetry install',
'poetry run yapf -dr ./ansiblelater',
],
},
{
name: 'check-coding',
image: 'python:3.11',
environment: {
PY_COLORS: 1,
},
commands: [
'git fetch -tq',
'pip install poetry poetry-dynamic-versioning -qq',
'poetry install -E ansible-core',
'poetry run ruff ./ansiblelater',
],
},
],
trigger: {
ref: ['refs/heads/main', 'refs/tags/**', 'refs/pull/**'],
},
};
local PipelineTest = {
kind: 'pipeline',
name: 'test',
platform: {
os: 'linux',
arch: 'amd64',
},
steps: [
{
name: 'fetch',
image: 'python:3.11',
commands: [
'git fetch -tq',
],
},
PythonVersion(pyversion='3.9'),
PythonVersion(pyversion='3.10'),
PythonVersion(pyversion='3.11'),
{
name: 'codecov',
image: 'python:3.11',
environment: {
PY_COLORS: 1,
CODECOV_TOKEN: { from_secret: 'codecov_token' },
},
commands: [
'pip install codecov -qq',
'codecov --required -X gcov',
],
depends_on: [
'python39-pytest',
'python310-pytest',
'python311-pytest',
],
},
],
depends_on: [
'lint',
],
trigger: {
ref: ['refs/heads/main', 'refs/tags/**', 'refs/pull/**'],
},
};
local PipelineBuildPackage = {
kind: 'pipeline',
name: 'build-package',
platform: {
os: 'linux',
arch: 'amd64',
},
steps: [
{
name: 'build',
image: 'python:3.11',
commands: [
'git fetch -tq',
'pip install poetry poetry-dynamic-versioning -qq',
'poetry build',
],
},
{
name: 'checksum',
image: 'alpine',
commands: [
'cd dist/ && sha256sum * > ../sha256sum.txt',
],
},
{
name: 'changelog-generate',
image: 'thegeeklab/git-chglog',
commands: [
'git fetch -tq',
'git-chglog --no-color --no-emoji -o CHANGELOG.md ${DRONE_TAG:---next-tag unreleased unreleased}',
],
},
{
name: 'changelog-format',
image: 'thegeeklab/alpine-tools',
commands: [
'prettier CHANGELOG.md',
'prettier -w CHANGELOG.md',
],
},
{
name: 'publish-github',
image: 'plugins/github-release',
settings: {
overwrite: true,
api_key: { from_secret: 'github_token' },
files: ['dist/*', 'sha256sum.txt'],
title: '${DRONE_TAG}',
note: 'CHANGELOG.md',
},
when: {
ref: ['refs/tags/**'],
},
},
{
name: 'publish-pypi',
image: 'python:3.11',
commands: [
'git fetch -tq',
'pip install poetry poetry-dynamic-versioning -qq',
'poetry publish -n',
],
environment: {
POETRY_HTTP_BASIC_PYPI_USERNAME: { from_secret: 'pypi_username' },
POETRY_HTTP_BASIC_PYPI_PASSWORD: { from_secret: 'pypi_password' },
},
when: {
ref: ['refs/tags/**'],
},
},
],
depends_on: [
'test',
],
trigger: {
ref: ['refs/heads/main', 'refs/tags/**', 'refs/pull/**'],
},
};
local PipelineBuildContainer = {
kind: 'pipeline',
name: 'build-container',
platform: {
os: 'linux',
arch: 'amd64',
},
steps: [
{
name: 'build',
image: 'python:3.11',
commands: [
'git fetch -tq',
'pip install poetry poetry-dynamic-versioning -qq',
'poetry build',
],
},
{
name: 'dryrun',
image: 'thegeeklab/drone-docker-buildx:23',
settings: {
dry_run: true,
dockerfile: 'Dockerfile.multiarch',
repo: 'thegeeklab/${DRONE_REPO_NAME}',
platforms: [
'linux/amd64',
'linux/arm64',
],
provenance: false,
},
depends_on: ['build'],
when: {
ref: ['refs/pull/**'],
},
},
{
name: 'publish-dockerhub',
image: 'thegeeklab/drone-docker-buildx:23',
settings: {
auto_tag: true,
dockerfile: 'Dockerfile.multiarch',
repo: 'thegeeklab/${DRONE_REPO_NAME}',
username: { from_secret: 'docker_username' },
password: { from_secret: 'docker_password' },
platforms: [
'linux/amd64',
'linux/arm64',
],
provenance: false,
},
when: {
ref: ['refs/heads/main', 'refs/tags/**'],
},
depends_on: ['dryrun'],
},
{
name: 'publish-quay',
image: 'thegeeklab/drone-docker-buildx:23',
settings: {
auto_tag: true,
dockerfile: 'Dockerfile.multiarch',
registry: 'quay.io',
repo: 'quay.io/thegeeklab/${DRONE_REPO_NAME}',
username: { from_secret: 'quay_username' },
password: { from_secret: 'quay_password' },
platforms: [
'linux/amd64',
'linux/arm64',
],
provenance: false,
},
when: {
ref: ['refs/heads/main', 'refs/tags/**'],
},
depends_on: ['dryrun'],
},
],
depends_on: [
'test',
],
trigger: {
ref: ['refs/heads/main', 'refs/tags/**', 'refs/pull/**'],
},
};
local PipelineDocs = {
kind: 'pipeline',
name: 'docs',
platform: {
os: 'linux',
arch: 'amd64',
},
concurrency: {
limit: 1,
},
steps: [
{
name: 'assets',
image: 'thegeeklab/alpine-tools',
commands: [
'make doc',
],
},
{
name: 'markdownlint',
image: 'thegeeklab/markdownlint-cli',
commands: [
"markdownlint 'docs/content/**/*.md' 'README.md' 'CONTRIBUTING.md'",
],
},
{
name: 'spellcheck',
image: 'thegeeklab/alpine-tools',
commands: [
"spellchecker --files 'docs/content/**/*.md' 'README.md' -d .dictionary -p spell indefinite-article syntax-urls --no-suggestions",
],
environment: {
FORCE_COLOR: true,
NPM_CONFIG_LOGLEVEL: 'error',
},
},
{
name: 'testbuild',
image: 'thegeeklab/hugo:0.105.0',
commands: [
'hugo --panicOnWarning -s docs/ -b http://localhost:8000/',
],
},
{
name: 'link-validation',
image: 'thegeeklab/link-validator',
commands: [
'link-validator --color=always --rate-limit 10',
],
environment: {
LINK_VALIDATOR_BASE_DIR: 'docs/public',
LINK_VALIDATOR_RETRIES: '3',
},
},
{
name: 'build',
image: 'thegeeklab/hugo:0.105.0',
commands: [
'hugo --panicOnWarning -s docs/',
],
},
{
name: 'beautify',
image: 'thegeeklab/alpine-tools',
commands: [
"html-beautify -r -f 'docs/public/**/*.html'",
],
environment: {
FORCE_COLOR: true,
NPM_CONFIG_LOGLEVEL: 'error',
},
},
{
name: 'publish',
image: 'thegeeklab/drone-s3-sync',
settings: {
access_key: { from_secret: 's3_access_key' },
bucket: 'geekdocs',
delete: true,
endpoint: 'https://sp.rknet.org',
path_style: true,
secret_key: { from_secret: 's3_secret_access_key' },
source: 'docs/public/',
strip_prefix: 'docs/public/',
target: '/${DRONE_REPO_NAME}',
},
when: {
ref: ['refs/heads/main', 'refs/tags/**'],
},
},
],
depends_on: [
'build-package',
'build-container',
],
trigger: {
ref: ['refs/heads/main', 'refs/tags/**', 'refs/pull/**'],
},
};
local PipelineNotifications = {
kind: 'pipeline',
name: 'notifications',
platform: {
os: 'linux',
arch: 'amd64',
},
steps: [
{
name: 'pushrm-dockerhub',
image: 'chko/docker-pushrm:1',
environment: {
DOCKER_PASS: {
from_secret: 'docker_password',
},
DOCKER_USER: {
from_secret: 'docker_username',
},
PUSHRM_FILE: 'README.md',
PUSHRM_SHORT: 'Another best practice scanner for Ansible roles and playbooks',
PUSHRM_TARGET: 'thegeeklab/${DRONE_REPO_NAME}',
},
when: {
status: ['success'],
},
},
{
name: 'pushrm-quay',
image: 'chko/docker-pushrm:1',
environment: {
APIKEY__QUAY_IO: {
from_secret: 'quay_token',
},
PUSHRM_FILE: 'README.md',
PUSHRM_TARGET: 'quay.io/thegeeklab/${DRONE_REPO_NAME}',
},
when: {
status: ['success'],
},
},
{
name: 'matrix',
image: 'thegeeklab/drone-matrix',
settings: {
homeserver: { from_secret: 'matrix_homeserver' },
roomid: { from_secret: 'matrix_roomid' },
template: 'Status: **{{ .Build.Status }}**<br/> Build: [{{ .Repo.Owner }}/{{ .Repo.Name }}]({{ .Build.Link }}){{ if .Build.Branch }} ({{ .Build.Branch }}){{ end }} by {{ .Commit.Author }}<br/> Message: {{ .Commit.Message.Title }}',
username: { from_secret: 'matrix_username' },
password: { from_secret: 'matrix_password' },
},
when: {
status: ['success', 'failure'],
},
},
],
depends_on: [
'docs',
],
trigger: {
ref: ['refs/heads/main', 'refs/tags/**'],
status: ['success', 'failure'],
},
};
[
PipelineLint,
PipelineTest,
PipelineBuildPackage,
PipelineBuildContainer,
PipelineDocs,
PipelineNotifications,
]

View File

@ -1,423 +0,0 @@
---
kind: pipeline
name: lint
platform:
os: linux
arch: amd64
steps:
- name: check-format
image: python:3.11
commands:
- git fetch -tq
- pip install poetry poetry-dynamic-versioning -qq
- poetry config experimental.new-installer false
- poetry install
- poetry run yapf -dr ./ansiblelater
environment:
PY_COLORS: 1
- name: check-coding
image: python:3.11
commands:
- git fetch -tq
- pip install poetry poetry-dynamic-versioning -qq
- poetry install -E ansible-core
- poetry run ruff ./ansiblelater
environment:
PY_COLORS: 1
trigger:
ref:
- refs/heads/main
- refs/tags/**
- refs/pull/**
---
kind: pipeline
name: test
platform:
os: linux
arch: amd64
steps:
- name: fetch
image: python:3.11
commands:
- git fetch -tq
- name: python39-pytest
image: python:3.9
commands:
- pip install poetry poetry-dynamic-versioning -qq
- poetry config experimental.new-installer false
- poetry install -E ansible-core
- poetry run pytest
- poetry version
- poetry run ansible-later --help
environment:
PY_COLORS: 1
depends_on:
- fetch
- name: python310-pytest
image: python:3.10
commands:
- pip install poetry poetry-dynamic-versioning -qq
- poetry config experimental.new-installer false
- poetry install -E ansible-core
- poetry run pytest
- poetry version
- poetry run ansible-later --help
environment:
PY_COLORS: 1
depends_on:
- fetch
- name: python311-pytest
image: python:3.11
commands:
- pip install poetry poetry-dynamic-versioning -qq
- poetry config experimental.new-installer false
- poetry install -E ansible-core
- poetry run pytest
- poetry version
- poetry run ansible-later --help
environment:
PY_COLORS: 1
depends_on:
- fetch
- name: codecov
image: python:3.11
commands:
- pip install codecov -qq
- codecov --required -X gcov
environment:
CODECOV_TOKEN:
from_secret: codecov_token
PY_COLORS: 1
depends_on:
- python39-pytest
- python310-pytest
- python311-pytest
trigger:
ref:
- refs/heads/main
- refs/tags/**
- refs/pull/**
depends_on:
- lint
---
kind: pipeline
name: build-package
platform:
os: linux
arch: amd64
steps:
- name: build
image: python:3.11
commands:
- git fetch -tq
- pip install poetry poetry-dynamic-versioning -qq
- poetry build
- name: checksum
image: alpine
commands:
- cd dist/ && sha256sum * > ../sha256sum.txt
- name: changelog-generate
image: thegeeklab/git-chglog
commands:
- git fetch -tq
- git-chglog --no-color --no-emoji -o CHANGELOG.md ${DRONE_TAG:---next-tag unreleased unreleased}
- name: changelog-format
image: thegeeklab/alpine-tools
commands:
- prettier CHANGELOG.md
- prettier -w CHANGELOG.md
- name: publish-github
image: plugins/github-release
settings:
api_key:
from_secret: github_token
files:
- dist/*
- sha256sum.txt
note: CHANGELOG.md
overwrite: true
title: ${DRONE_TAG}
when:
ref:
- refs/tags/**
- name: publish-pypi
image: python:3.11
commands:
- git fetch -tq
- pip install poetry poetry-dynamic-versioning -qq
- poetry publish -n
environment:
POETRY_HTTP_BASIC_PYPI_PASSWORD:
from_secret: pypi_password
POETRY_HTTP_BASIC_PYPI_USERNAME:
from_secret: pypi_username
when:
ref:
- refs/tags/**
trigger:
ref:
- refs/heads/main
- refs/tags/**
- refs/pull/**
depends_on:
- test
---
kind: pipeline
name: build-container
platform:
os: linux
arch: amd64
steps:
- name: build
image: python:3.11
commands:
- git fetch -tq
- pip install poetry poetry-dynamic-versioning -qq
- poetry build
- name: dryrun
image: thegeeklab/drone-docker-buildx:23
settings:
dockerfile: Dockerfile.multiarch
dry_run: true
platforms:
- linux/amd64
- linux/arm64
provenance: false
repo: thegeeklab/${DRONE_REPO_NAME}
when:
ref:
- refs/pull/**
depends_on:
- build
- name: publish-dockerhub
image: thegeeklab/drone-docker-buildx:23
settings:
auto_tag: true
dockerfile: Dockerfile.multiarch
password:
from_secret: docker_password
platforms:
- linux/amd64
- linux/arm64
provenance: false
repo: thegeeklab/${DRONE_REPO_NAME}
username:
from_secret: docker_username
when:
ref:
- refs/heads/main
- refs/tags/**
depends_on:
- dryrun
- name: publish-quay
image: thegeeklab/drone-docker-buildx:23
settings:
auto_tag: true
dockerfile: Dockerfile.multiarch
password:
from_secret: quay_password
platforms:
- linux/amd64
- linux/arm64
provenance: false
registry: quay.io
repo: quay.io/thegeeklab/${DRONE_REPO_NAME}
username:
from_secret: quay_username
when:
ref:
- refs/heads/main
- refs/tags/**
depends_on:
- dryrun
trigger:
ref:
- refs/heads/main
- refs/tags/**
- refs/pull/**
depends_on:
- test
---
kind: pipeline
name: docs
platform:
os: linux
arch: amd64
concurrency:
limit: 1
steps:
- name: assets
image: thegeeklab/alpine-tools
commands:
- make doc
- name: markdownlint
image: thegeeklab/markdownlint-cli
commands:
- markdownlint 'docs/content/**/*.md' 'README.md' 'CONTRIBUTING.md'
- name: spellcheck
image: thegeeklab/alpine-tools
commands:
- spellchecker --files 'docs/content/**/*.md' 'README.md' -d .dictionary -p spell indefinite-article syntax-urls --no-suggestions
environment:
FORCE_COLOR: true
NPM_CONFIG_LOGLEVEL: error
- name: testbuild
image: thegeeklab/hugo:0.105.0
commands:
- hugo --panicOnWarning -s docs/ -b http://localhost:8000/
- name: link-validation
image: thegeeklab/link-validator
commands:
- link-validator --color=always --rate-limit 10
environment:
LINK_VALIDATOR_BASE_DIR: docs/public
LINK_VALIDATOR_RETRIES: 3
- name: build
image: thegeeklab/hugo:0.105.0
commands:
- hugo --panicOnWarning -s docs/
- name: beautify
image: thegeeklab/alpine-tools
commands:
- html-beautify -r -f 'docs/public/**/*.html'
environment:
FORCE_COLOR: true
NPM_CONFIG_LOGLEVEL: error
- name: publish
image: thegeeklab/drone-s3-sync
settings:
access_key:
from_secret: s3_access_key
bucket: geekdocs
delete: true
endpoint: https://sp.rknet.org
path_style: true
secret_key:
from_secret: s3_secret_access_key
source: docs/public/
strip_prefix: docs/public/
target: /${DRONE_REPO_NAME}
when:
ref:
- refs/heads/main
- refs/tags/**
trigger:
ref:
- refs/heads/main
- refs/tags/**
- refs/pull/**
depends_on:
- build-package
- build-container
---
kind: pipeline
name: notifications
platform:
os: linux
arch: amd64
steps:
- name: pushrm-dockerhub
image: chko/docker-pushrm:1
environment:
DOCKER_PASS:
from_secret: docker_password
DOCKER_USER:
from_secret: docker_username
PUSHRM_FILE: README.md
PUSHRM_SHORT: Another best practice scanner for Ansible roles and playbooks
PUSHRM_TARGET: thegeeklab/${DRONE_REPO_NAME}
when:
status:
- success
- name: pushrm-quay
image: chko/docker-pushrm:1
environment:
APIKEY__QUAY_IO:
from_secret: quay_token
PUSHRM_FILE: README.md
PUSHRM_TARGET: quay.io/thegeeklab/${DRONE_REPO_NAME}
when:
status:
- success
- name: matrix
image: thegeeklab/drone-matrix
settings:
homeserver:
from_secret: matrix_homeserver
password:
from_secret: matrix_password
roomid:
from_secret: matrix_roomid
template: "Status: **{{ .Build.Status }}**<br/> Build: [{{ .Repo.Owner }}/{{ .Repo.Name }}]({{ .Build.Link }}){{ if .Build.Branch }} ({{ .Build.Branch }}){{ end }} by {{ .Commit.Author }}<br/> Message: {{ .Commit.Message.Title }}"
username:
from_secret: matrix_username
when:
status:
- success
- failure
trigger:
ref:
- refs/heads/main
- refs/tags/**
status:
- success
- failure
depends_on:
- docs
---
kind: signature
hmac: 1bc7f62d74ce0afa031770f617ffda20b9719ed4489061c470476ca707d1275f
...

View File

@ -52,7 +52,11 @@ branches:
required_status_checks:
strict: false
contexts:
- continuous-integration/drone/pr
enforce_admins: true
- ci/woodpecker/pr/lint
- ci/woodpecker/pr/test
- ci/woodpecker/pr/build-package
- ci/woodpecker/pr/build-container
- ci/woodpecker/pr/docs
enforce_admins: false
required_linear_history: true
restrictions: null

47
.gitsv/config.yml Normal file
View File

@ -0,0 +1,47 @@
---
version: "1.1"
versioning:
update-major: []
update-minor: [feat]
update-patch: [fix, perf, refactor, chore, test, ci, docs]
tag:
pattern: "v%d.%d.%d"
release-notes:
sections:
- name: Features
commit-types: [feat]
section-type: commits
- name: Bug Fixes
commit-types: [fix]
section-type: commits
- name: Performance Improvements
commit-types: [perf]
section-type: commits
- name: Code Refactoring
commit-types: [refactor]
section-type: commits
- name: Others
commit-types: [chore]
section-type: commits
- name: Testing
commit-types: [test]
section-type: commits
- name: CI Pipeline
commit-types: [ci]
section-type: commits
- name: Documentation
commit-types: [docs]
section-type: commits
- name: Breaking Changes
section-type: breaking-changes
commit-message:
footer:
issue:
key: issue
add-value-prefix: "#"
issue:
regex: "#?[0-9]+"

1
.lycheeignore Normal file
View File

@ -0,0 +1 @@
https://hub.docker.com/r/thegeeklab/*

View File

@ -1,3 +1,2 @@
.drone.yml
*.tpl.md
LICENSE

View File

@ -0,0 +1,73 @@
---
when:
- event: [pull_request, tag]
- event: [push, manual]
branch:
- ${CI_REPO_DEFAULT_BRANCH}
steps:
- name: build
image: docker.io/library/python:3.12
commands:
- pip install poetry poetry-dynamic-versioning -qq
- poetry build
- name: dryrun
image: quay.io/thegeeklab/wp-docker-buildx:4
settings:
containerfile: Containerfile.multiarch
dry_run: true
platforms:
- linux/amd64
- linux/arm64
provenance: false
repo: ${CI_REPO}
when:
- event: [pull_request]
- name: publish-dockerhub
image: quay.io/thegeeklab/wp-docker-buildx:4
group: container
settings:
auto_tag: true
containerfile: Containerfile.multiarch
password:
from_secret: docker_password
platforms:
- linux/amd64
- linux/arm64
provenance: false
repo: ${CI_REPO}
username:
from_secret: docker_username
when:
- event: [tag]
- event: [push, manual]
branch:
- ${CI_REPO_DEFAULT_BRANCH}
- name: publish-quay
image: quay.io/thegeeklab/wp-docker-buildx:4
group: container
settings:
auto_tag: true
containerfile: Containerfile.multiarch
password:
from_secret: quay_password
platforms:
- linux/amd64
- linux/arm64
provenance: false
registry: quay.io
repo: quay.io/${CI_REPO}
username:
from_secret: quay_username
when:
- event: [tag]
- event: [push, manual]
branch:
- ${CI_REPO_DEFAULT_BRANCH}
depends_on:
- lint
- test

View File

@ -0,0 +1,56 @@
---
when:
- event: [pull_request, tag]
- event: [push, manual]
branch:
- ${CI_REPO_DEFAULT_BRANCH}
steps:
- name: build
image: docker.io/library/python:3.12
commands:
- pip install poetry poetry-dynamic-versioning -qq
- poetry build
- name: checksum
image: quay.io/thegeeklab/alpine-tools
commands:
- cd dist/ && sha256sum * > ../sha256sum.txt
- name: changelog
image: quay.io/thegeeklab/git-sv
commands:
- git sv current-version
- git sv release-notes -t ${CI_COMMIT_TAG:-next} -o CHANGELOG.md
- cat CHANGELOG.md
- name: publish-github
image: docker.io/plugins/github-release
settings:
api_key:
from_secret: github_token
files:
- dist/*
- sha256sum.txt
note: CHANGELOG.md
overwrite: true
title: ${CI_COMMIT_TAG}
when:
- event: [tag]
- name: publish-pypi
image: docker.io/library/python:3.12
secrets:
- source: pypi_password
target: POETRY_HTTP_BASIC_PYPI_PASSWORD
- source: pypi_username
target: POETRY_HTTP_BASIC_PYPI_USERNAME
commands:
- pip install poetry poetry-dynamic-versioning -qq
- poetry publish -n
when:
- event: [tag]
depends_on:
- lint
- test

100
.woodpecker/docs.yml Normal file
View File

@ -0,0 +1,100 @@
---
when:
- event: [pull_request, tag]
- event: [push, manual]
branch:
- ${CI_REPO_DEFAULT_BRANCH}
steps:
- name: assets
image: quay.io/thegeeklab/alpine-tools
commands:
- make doc
- name: markdownlint
image: quay.io/thegeeklab/markdownlint-cli
group: test
commands:
- markdownlint 'README.md' 'CONTRIBUTING.md'
- name: spellcheck
image: quay.io/thegeeklab/alpine-tools
group: test
commands:
- spellchecker --files 'docs/**/*.md' 'README.md' 'CONTRIBUTING.md' -d .dictionary -p spell indefinite-article syntax-urls
environment:
FORCE_COLOR: "true"
- name: link-validation
image: docker.io/lycheeverse/lychee
group: test
commands:
- lychee --no-progress --format detailed docs/content README.md
- name: build
image: quay.io/thegeeklab/hugo:0.125.7
commands:
- hugo --panicOnWarning -s docs/
- name: beautify
image: quay.io/thegeeklab/alpine-tools
commands:
- html-beautify -r -f 'docs/public/**/*.html'
environment:
FORCE_COLOR: "true"
- name: publish
image: quay.io/thegeeklab/wp-s3-action
settings:
access_key:
from_secret: s3_access_key
bucket: geekdocs
delete: true
endpoint:
from_secret: s3_endpoint
path_style: true
secret_key:
from_secret: s3_secret_access_key
source: docs/public/
strip_prefix: docs/public/
target: /${CI_REPO_NAME}
when:
- event: [push, manual]
branch:
- ${CI_REPO_DEFAULT_BRANCH}
status: [success, failure]
- name: pushrm-dockerhub
image: docker.io/chko/docker-pushrm:1
secrets:
- source: docker_password
target: DOCKER_PASS
- source: docker_username
target: DOCKER_USER
environment:
PUSHRM_FILE: README.md
PUSHRM_SHORT: Another best practice scanner for Ansible roles and playbooks
PUSHRM_TARGET: ${CI_REPO}
when:
- event: [push, manual]
branch:
- ${CI_REPO_DEFAULT_BRANCH}
status: [success]
- name: pushrm-quay
image: docker.io/chko/docker-pushrm:1
secrets:
- source: quay_token
target: APIKEY__QUAY_IO
environment:
PUSHRM_FILE: README.md
PUSHRM_TARGET: quay.io/${CI_REPO}
when:
- event: [push, manual]
branch:
- ${CI_REPO_DEFAULT_BRANCH}
status: [success]
depends_on:
- build-package
- build-container

25
.woodpecker/lint.yml Normal file
View File

@ -0,0 +1,25 @@
---
when:
- event: [pull_request, tag]
- event: [push, manual]
branch:
- ${CI_REPO_DEFAULT_BRANCH}
steps:
- name: check-format
image: docker.io/library/python:3.12
commands:
- pip install poetry poetry-dynamic-versioning -qq
- poetry install
- poetry run ruff format --check --diff ./${CI_REPO_NAME//-/}
environment:
PY_COLORS: "1"
- name: check-coding
image: docker.io/library/python:3.12
commands:
- pip install poetry poetry-dynamic-versioning -qq
- poetry install -E ansible-core
- poetry run ruff check ./${CI_REPO_NAME//-/}
environment:
PY_COLORS: "1"

26
.woodpecker/notify.yml Normal file
View File

@ -0,0 +1,26 @@
---
when:
- event: [tag]
- event: [push, manual]
branch:
- ${CI_REPO_DEFAULT_BRANCH}
runs_on: [success, failure]
steps:
- name: matrix
image: quay.io/thegeeklab/wp-matrix
settings:
homeserver:
from_secret: matrix_homeserver
password:
from_secret: matrix_password
roomid:
from_secret: matrix_roomid
username:
from_secret: matrix_username
when:
- status: [success, failure]
depends_on:
- docs

35
.woodpecker/test.yml Normal file
View File

@ -0,0 +1,35 @@
---
when:
- event: [pull_request, tag]
- event: [push, manual]
branch:
- ${CI_REPO_DEFAULT_BRANCH}
variables:
- &pytest_base
group: pytest
commands:
- pip install poetry poetry-dynamic-versioning -qq
- poetry install -E ansible-core
- poetry run pytest --cov-append
- poetry version
- poetry run ${CI_REPO_NAME} --help
environment:
PY_COLORS: "1"
steps:
- name: python-312
image: docker.io/library/python:3.12
<<: *pytest_base
- name: python-311
image: docker.io/library/python:3.11
<<: *pytest_base
- name: python-310
image: docker.io/library/python:3.10
<<: *pytest_base
- name: python-39
image: docker.io/library/python:3.9
<<: *pytest_base

View File

@ -3,7 +3,7 @@
## Security
If you think you have found a **security issue**, please do not mention it in this repository.
Instead, send an email to security@thegeeklab.de with as many details as possible so it can be handled confidential.
Instead, send an email to `security@thegeeklab.de` with as many details as possible so it can be handled confidential.
## Bug Reports and Feature Requests

View File

@ -1,4 +1,4 @@
FROM python:3.11-alpine@sha256:741e650697a506f0991ef88490320dee59f9e68de61734e034aee11d2f3baedf
FROM python:3.12-alpine@sha256:ef097620baf1272e38264207003b0982285da3236a20ed829bf6bbf1e85fe3cb
LABEL maintainer="Robert Kaussow <mail@thegeeklab.de>"
LABEL org.opencontainers.image.authors="Robert Kaussow <mail@thegeeklab.de>"

View File

@ -1,5 +1,5 @@
# renovate: datasource=github-releases depName=thegeeklab/hugo-geekdoc
THEME_VERSION := v0.38.0
THEME_VERSION := v0.45.0
THEME := hugo-geekdoc
BASEDIR := docs
THEMEDIR := $(BASEDIR)/themes

View File

@ -2,13 +2,12 @@
Another best practice scanner for Ansible roles and playbooks
[![Build Status](https://img.shields.io/drone/build/thegeeklab/ansible-later?logo=drone&server=https%3A%2F%2Fdrone.thegeeklab.de)](https://drone.thegeeklab.de/thegeeklab/ansible-later)
[![Build Status](https://ci.thegeeklab.de/api/badges/thegeeklab/ansible-later/status.svg)](https://ci.thegeeklab.de/repos/thegeeklab/ansible-later)
[![Docker Hub](https://img.shields.io/badge/dockerhub-latest-blue.svg?logo=docker&logoColor=white)](https://hub.docker.com/r/thegeeklab/ansible-later)
[![Quay.io](https://img.shields.io/badge/quay-latest-blue.svg?logo=docker&logoColor=white)](https://quay.io/repository/thegeeklab/ansible-later)
[![Python Version](https://img.shields.io/pypi/pyversions/ansible-later.svg)](https://pypi.org/project/ansible-later/)
[![PyPI Status](https://img.shields.io/pypi/status/ansible-later.svg)](https://pypi.org/project/ansible-later/)
[![PyPI Release](https://img.shields.io/pypi/v/ansible-later.svg)](https://pypi.org/project/ansible-later/)
[![Codecov](https://img.shields.io/codecov/c/github/thegeeklab/ansible-later)](https://codecov.io/gh/thegeeklab/ansible-later)
[![GitHub contributors](https://img.shields.io/github/contributors/thegeeklab/ansible-later)](https://github.com/thegeeklab/ansible-later/graphs/contributors)
[![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)
@ -19,16 +18,6 @@ ansible-later does **not** ensure that your role will work as expected. For depl
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
Special thanks to all [contributors](https://github.com/thegeeklab/ansible-later/graphs/contributors). If you would like to contribute,

View File

@ -7,8 +7,8 @@ import sys
from ansiblelater import LOG, __version__, logger
from ansiblelater.candidate import Candidate
from ansiblelater.rule import SingleRules
from ansiblelater.settings import Settings
from ansiblelater.standard import SingleStandards
def main():
@ -22,33 +22,33 @@ def main():
parser.add_argument(
"-r",
"--rules-dir",
dest="rules.standards",
metavar="RULES",
dest="rules.dir",
metavar="DIR",
action="append",
help="directory of standard rules"
help="directory of rules",
)
parser.add_argument(
"-B",
"--no-buildin",
dest="rules.buildin",
"--no-builtin",
dest="rules.builtin",
action="store_false",
help="disables build-in standard rules"
help="disables built-in rules",
)
parser.add_argument(
"-s",
"--standards",
dest="rules.filter",
metavar="FILTER",
"-i",
"--include-rules",
dest="rules.include_filter",
metavar="TAGS",
action="append",
help="limit standards to given ID's"
help="limit rules to given id/tags",
)
parser.add_argument(
"-x",
"--exclude-standards",
"--exclude-rules",
dest="rules.exclude_filter",
metavar="EXCLUDE_FILTER",
metavar="TAGS",
action="append",
help="exclude standards by given ID's"
help="exclude rules by given it/tags",
)
parser.add_argument(
"-v", dest="logging.level", action="append_const", const=-1, help="increase log level"
@ -65,7 +65,7 @@ def main():
config = settings.config
logger.update_logger(LOG, config["logging"]["level"], config["logging"]["json"])
SingleStandards(config["rules"]["standards"]).rules
SingleRules(config["rules"]["dir"])
workers = max(multiprocessing.cpu_count() - 2, 2)
p = multiprocessing.Pool(workers)
@ -85,7 +85,7 @@ def main():
else:
LOG.info(f"Couldn't classify file {filename}")
errors = (sum(p.map(_review_wrapper, tasks)))
errors = sum(p.map(_review_wrapper, tasks))
p.close()
p.join()

View File

@ -3,14 +3,12 @@
import codecs
import copy
import os
import re
from distutils.version import LooseVersion
from ansible.plugins.loader import module_loader
from ansiblelater import LOG, utils
from ansiblelater import LOG
from ansiblelater.logger import flag_extra
from ansiblelater.standard import SingleStandards, StandardBase
from ansiblelater.rule import RuleBase, SingleRules
class Candidate:
@ -21,11 +19,12 @@ class Candidate:
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.binary = 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.config = settings.config
self.settings = settings
@ -37,164 +36,117 @@ class Candidate:
except UnicodeDecodeError:
self.binary = True
def _get_version(self):
name = type(self).__name__
path = self.path
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"]
def _filter_rules(self):
target_rules = []
includes = self.config["rules"]["include_filter"]
excludes = self.config["rules"]["exclude_filter"]
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:
if standard.sid in includes and standard.sid not in excludes:
target_standards.append(standard)
for rule in self.rules:
if rule.rid in includes and rule.rid not in excludes:
target_rules.append(rule)
return target_standards
return target_rules
def review(self):
errors = 0
self.standards = SingleStandards(self.config["rules"]["standards"]).rules
self.version_config = self._get_version()
self.version = self.version_config or utils.standards_latest(self.standards)
self.rules = SingleRules(self.config["rules"]["dir"]).rules
for standard in self._filter_standards():
if type(self).__name__.lower() not in standard.types:
for rule in self._filter_rules():
if self.kind not in rule.types:
continue
result = standard.check(self, self.config)
result = rule.check(self, self.config)
if not result:
LOG.error(
f"Standard '{standard.sid}' returns an empty result object. Check failed!"
)
LOG.error(f"rule '{rule.rid}' returns an empty result object. Check failed!")
continue
labels = {
"tag": "review",
"standard": standard.description,
"rule": rule.description,
"file": self.path,
"passed": True
"passed": True,
}
if standard.sid and standard.sid.strip():
labels["sid"] = standard.sid
if rule.rid and rule.rid.strip():
labels["rid"] = rule.rid
for err in result.errors:
err_labels = copy.copy(labels)
err_labels["passed"] = False
sid = self._format_id(standard.sid)
rid = self._format_id(rule.rid)
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())
if not standard.version:
LOG.warning(
f"{sid}Best practice '{description}' not met:\n{path}:{err}",
extra=flag_extra(err_labels)
)
elif LooseVersion(standard.version) > LooseVersion(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}"
msg = f"{rid}rule '{description}' not met:\n{path}:{err}"
if standard.sid not in self.config["rules"]["warning_filter"]:
LOG.error(msg, extra=flag_extra(err_labels))
errors = errors + 1
else:
LOG.warning(msg, extra=flag_extra(err_labels))
if rule.rid not in self.config["rules"]["warning_filter"]:
LOG.error(msg, extra=flag_extra(err_labels))
errors = errors + 1
else:
LOG.warning(msg, extra=flag_extra(err_labels))
return errors
@staticmethod
def classify(filename, settings={}, standards=[]): # noqa
def classify(filename, settings={}, rules=[]): # noqa
parentdir = os.path.basename(os.path.dirname(filename))
basename = os.path.basename(filename)
ext = os.path.splitext(filename)[1][1:]
if parentdir in ["tasks"]:
return Task(filename, settings, standards)
return Task(filename, settings, rules)
if parentdir in ["handlers"]:
return Handler(filename, settings, standards)
return Handler(filename, settings, rules)
if parentdir in ["vars", "defaults"]:
return RoleVars(filename, settings, standards)
return RoleVars(filename, settings, rules)
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):
return HostVars(filename, settings, standards)
return HostVars(filename, settings, rules)
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:
return ArgumentSpecs(filename, settings, standards)
if (
parentdir in ["library", "lookup_plugins", "callback_plugins", "filter_plugins"]
or filename.endswith(".py")
):
return Code(filename, settings, standards)
return ArgumentSpecs(filename, settings, rules)
if parentdir in [
"library",
"lookup_plugins",
"callback_plugins",
"filter_plugins",
] or filename.endswith(".py"):
return Code(filename, settings, rules)
if basename == "inventory" or basename == "hosts" or parentdir in ["inventories"]:
return Inventory(filename, settings, standards)
if ("rolesfile" in basename or ("requirements" in basename and ext in ["yaml", "yml"])):
return Rolesfile(filename, settings, standards)
return Inventory(filename, settings, rules)
if "rolesfile" in basename or ("requirements" in basename and ext in ["yaml", "yml"]):
return Rolesfile(filename, settings, rules)
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"):
return Template(filename, settings, standards)
return Template(filename, settings, rules)
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"):
return Playbook(filename, settings, standards)
return Playbook(filename, settings, rules)
if "README" in basename:
return Doc(filename, settings, standards)
return Doc(filename, settings, rules)
return None
def _format_id(self, standard_id):
sid = standard_id.strip()
if sid:
standard_id = f"[{sid}] "
def _format_id(self, rule_id):
rid = rule_id.strip()
if rid:
rule_id = f"[{rid}] "
return standard_id # noqa
return rule_id
def __repr__(self):
return f"{type(self).__name__} ({self.path})"
return f"{self.kind} ({self.path})"
def __getitem__(self, item):
return self.__dict__.get(item)
@ -203,8 +155,8 @@ class Candidate:
class RoleFile(Candidate):
"""Object classified as Ansible role file."""
def __init__(self, filename, settings={}, standards=[]): # noqa
super().__init__(filename, settings, standards)
def __init__(self, filename, settings={}, rules=[]): # noqa
super().__init__(filename, settings, rules)
parentdir = os.path.dirname(os.path.abspath(filename))
while parentdir != os.path.dirname(parentdir):
@ -224,17 +176,17 @@ class Playbook(Candidate):
class Task(RoleFile):
"""Object classified as Ansible task file."""
def __init__(self, filename, settings={}, standards=[]): # noqa
super().__init__(filename, settings, standards)
self.filetype = "tasks"
def __init__(self, filename, settings={}, rules=[]): # noqa
super().__init__(filename, settings, rules)
self.filemeta = "tasks"
class Handler(RoleFile):
"""Object classified as Ansible handler file."""
def __init__(self, filename, settings={}, standards=[]): # noqa
super().__init__(filename, settings, standards)
self.filetype = "handlers"
def __init__(self, filename, settings={}, rules=[]): # noqa
super().__init__(filename, settings, rules)
self.filemeta = "handlers"
class Vars(Candidate):

View File

@ -3,7 +3,6 @@
import logging
import os
import sys
from distutils.util import strtobool
import colorama
from pythonjsonlogger import jsonlogger
@ -12,12 +11,35 @@ CONSOLE_FORMAT = "{}%(levelname)s:{} %(message)s"
JSON_FORMAT = "%(asctime)s %(levelname)s %(message)s"
def strtobool(value):
"""Convert a string representation of truth to true or false."""
_map = {
"y": True,
"yes": True,
"t": True,
"true": True,
"on": True,
"1": True,
"n": False,
"no": False,
"f": False,
"false": False,
"off": False,
"0": False,
}
try:
return _map[str(value).lower()]
except KeyError as err:
raise ValueError(f'"{value}" is not a valid bool value') from err
def to_bool(string):
return bool(strtobool(str(string)))
def _should_do_markup():
py_colors = os.environ.get("PY_COLORS", None)
if py_colors is not None:
return to_bool(py_colors)
@ -60,7 +82,7 @@ class LogFilter:
class MultilineFormatter(logging.Formatter):
"""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 + "\n"
return logging.Formatter.format(self, record)
@ -69,7 +91,7 @@ class MultilineFormatter(logging.Formatter):
class MultilineJsonFormatter(jsonlogger.JsonFormatter):
"""Logging Formatter to remove newline characters."""
def format(self, record): # noqa
def format(self, record):
record.msg = record.msg.replace("\n", " ")
return jsonlogger.JsonFormatter.format(self, record)

View File

@ -1,4 +1,4 @@
"""Standard definition."""
"""Rule definition."""
import copy
import importlib
@ -8,6 +8,7 @@ import pathlib
import re
from abc import ABCMeta, abstractmethod
from collections import defaultdict
from urllib.parse import urlparse
import toolz
import yaml
@ -26,27 +27,26 @@ from ansiblelater.utils.yamlhelper import (
)
class StandardMeta(type):
class RuleMeta(type):
def __call__(cls, *args):
mcls = type.__call__(cls, *args)
mcls.sid = cls.sid
mcls.rid = cls.rid
mcls.description = getattr(cls, "description", "__unknown__")
mcls.helptext = getattr(cls, "helptext", "")
mcls.version = getattr(cls, "version", None)
mcls.types = getattr(cls, "types", [])
return mcls
class StandardExtendedMeta(StandardMeta, ABCMeta):
class RuleExtendedMeta(RuleMeta, ABCMeta):
pass
class StandardBase(metaclass=StandardExtendedMeta):
class RuleBase(metaclass=RuleExtendedMeta):
SHELL_PIPE_CHARS = "&|<>;$\n*[]{}?"
@property
@abstractmethod
def sid(self):
def rid(self):
pass
@abstractmethod
@ -54,7 +54,7 @@ class StandardBase(metaclass=StandardExtendedMeta):
pass
def __repr__(self):
return f"Standard: {self.description} (version: {self.version}, types: {self.types})"
return f"Rule: {self.description} (types: {self.types})"
@staticmethod
def get_tasks(candidate, settings): # noqa
@ -68,11 +68,11 @@ class StandardBase(metaclass=StandardExtendedMeta):
except LaterError as ex:
e = ex.original
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
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
return yamllines, errors
@ -92,11 +92,11 @@ class StandardBase(metaclass=StandardExtendedMeta):
except LaterError as ex:
e = ex.original
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
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
return tasks, errors
@ -114,11 +114,11 @@ class StandardBase(metaclass=StandardExtendedMeta):
except LaterError as ex:
e = ex.original
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
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
return normalized, errors
@ -149,20 +149,21 @@ class StandardBase(metaclass=StandardExtendedMeta):
# No need to normalize_task if we are skipping it.
continue
normalized.append(
normalize_task(
task, candidate.path, settings["ansible"]["custom_modules"]
)
normalized_task = normalize_task(
task, candidate.path, settings["ansible"]["custom_modules"]
)
normalized_task["__raw_task__"] = task
normalized.append(normalized_task)
except LaterError as ex:
e = ex.original
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
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
return normalized, errors
@ -183,11 +184,11 @@ class StandardBase(metaclass=StandardExtendedMeta):
except LaterError as ex:
e = ex.original
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
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
return yamllines, errors
@ -209,7 +210,7 @@ class StandardBase(metaclass=StandardExtendedMeta):
content = yaml.safe_load(f)
except yaml.YAMLError as e:
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
@ -223,14 +224,14 @@ class StandardBase(metaclass=StandardExtendedMeta):
try:
with open(candidate.path, encoding="utf-8") as f:
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:
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
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
return errors
@ -244,7 +245,23 @@ class StandardBase(metaclass=StandardExtendedMeta):
else:
first_cmd_arg = task["action"]["__ansible_arguments__"][0]
return first_cmd_arg # noqa
return first_cmd_arg
@staticmethod
def get_safe_cmd(task):
if "cmd" in task["action"]:
cmd = task["action"].get("cmd", "")
else:
cmd = " ".join(task["action"].get("__ansible_arguments__", []))
cmd = re.sub(r"{{.+?}}", "JINJA_EXPRESSION", cmd)
cmd = re.sub(r"{%.+?%}", "JINJA_STATEMENT", cmd)
cmd = re.sub(r"{#.+?#}", "JINJA_COMMENT", cmd)
parts = cmd.split()
parts = [p if not urlparse(p.strip('"').strip("'")).scheme else "URL" for p in parts]
return " ".join(parts)
class Error:
"""Default error object created if a rule failed."""
@ -260,7 +277,7 @@ class StandardBase(metaclass=StandardExtendedMeta):
self.lineno = lineno
self.message = message
self.kwargs = kwargs
for (key, value) in kwargs.items():
for key, value in kwargs.items():
setattr(self, key, value)
def __repr__(self):
@ -270,7 +287,7 @@ class StandardBase(metaclass=StandardExtendedMeta):
def to_dict(self):
result = {"lineno": self.lineno, "message": self.message}
for (key, value) in self.kwargs.items():
for key, value in self.kwargs.items():
result[key] = value
return result
@ -285,8 +302,7 @@ class StandardBase(metaclass=StandardExtendedMeta):
return "\n".join([f"{self.candidate}:{error}" for error in self.errors])
class StandardLoader():
class RulesLoader:
def __init__(self, source):
self.rules = []
@ -302,33 +318,33 @@ class StandardLoader():
try:
spec.loader.exec_module(module)
except (ImportError, NameError) as e:
sysexit_with_message(f"Failed to load roles file {filename}: \n {str(e)}")
sysexit_with_message(f"Failed to load roles file {filename}: \n {e!s}")
try:
for _name, obj in inspect.getmembers(module):
if self._is_plugin(obj):
self.rules.append(obj())
except TypeError as e:
sysexit_with_message(f"Failed to load roles file: \n {str(e)}")
sysexit_with_message(f"Failed to load roles file: \n {e!s}")
self.validate()
def _is_plugin(self, obj):
return inspect.isclass(obj) and issubclass(
obj, StandardBase
) and obj is not StandardBase and not None
return (
inspect.isclass(obj) and issubclass(obj, RuleBase) and obj is not RuleBase and not None
)
def validate(self):
normalized_std = (list(toolz.remove(lambda x: x.sid == "", self.rules)))
unique_std = len(list(toolz.unique(normalized_std, key=lambda x: x.sid)))
all_std = len(normalized_std)
if all_std != unique_std:
normalize_rule = list(toolz.remove(lambda x: x.rid == "", self.rules))
unique_rule = len(list(toolz.unique(normalize_rule, key=lambda x: x.rid)))
all_rules = len(normalize_rule)
if all_rules != unique_rule:
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."""
pass

View File

@ -1,12 +1,10 @@
from ansiblelater.standard import StandardBase
from ansiblelater.rule import RuleBase
class CheckBecomeUser(StandardBase):
sid = "ANSIBLE0015"
class CheckBecomeUser(RuleBase):
rid = "ANS115"
description = "Become should be combined with become_user"
helptext = "the task has `become` enabled but `become_user` is missing"
version = "0.1"
types = ["playbook", "task", "handler"]
def check(self, candidate, settings):
@ -16,7 +14,7 @@ class CheckBecomeUser(StandardBase):
if not errors:
gen = (task for task in tasks if "become" in task)
for task in gen:
if task["become"] in true_value and "become_user" not in task.keys():
if task["become"] in true_value and "become_user" not in task:
errors.append(self.Error(task["__line__"], self.helptext))
return self.Result(candidate.path, errors)

View File

@ -1,15 +1,13 @@
import re
from ansiblelater.standard import StandardBase
from ansiblelater.rule import RuleBase
from ansiblelater.utils import count_spaces
class CheckBracesSpaces(StandardBase):
sid = "ANSIBLE0004"
class CheckBracesSpaces(RuleBase):
rid = "ANS104"
description = "YAML should use consistent number of spaces around variables"
helptext = "no suitable numbers of spaces (min: {min} max: {max})"
version = "0.1"
types = ["playbook", "task", "handler", "rolevars", "hostvars", "groupvars", "meta"]
def check(self, candidate, settings):
@ -41,7 +39,7 @@ class CheckBracesSpaces(StandardBase):
i,
self.helptext.format(
min=conf["min-spaces-inside"], max=conf["max-spaces-inside"]
)
),
)
)
return self.Result(candidate.path, errors)

View File

@ -17,15 +17,14 @@
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
from ansiblelater.standard import StandardBase
from ansiblelater.rule import RuleBase
class CheckChangedInWhen(StandardBase):
sid = "ANSIBLE0026"
class CheckChangedInWhen(RuleBase):
rid = "ANS126"
description = "Use handlers instead of `when: changed`"
helptext = "tasks using `when: result.changed` setting are effectively acting as a handler"
version = "0.2"
types = ["playbook", "task", "handler"]
def check(self, candidate, settings):
@ -35,7 +34,7 @@ class CheckChangedInWhen(StandardBase):
for task in tasks:
when = None
if task["__ansible_action_type__"] == "task":
if task["__ansible_action_type__"] in ["task", "meta"]:
when = task.get("when")
if isinstance(when, str):
@ -53,6 +52,16 @@ class CheckChangedInWhen(StandardBase):
if not isinstance(item, str):
return False
if not {"and", "or", "not"}.isdisjoint(item.split()):
return False
return any(
changed in item for changed in [".changed", "|changed", '["changed"]', "['changed']"]
changed in item
for changed in [
".changed",
"|changed",
'["changed"]',
"['changed']",
"is changed",
]
)

View File

@ -1,15 +1,13 @@
from ansiblelater.standard import StandardBase
from ansiblelater.rule import RuleBase
class CheckCommandHasChanges(StandardBase):
sid = "ANSIBLE0011"
class CheckCommandHasChanges(RuleBase):
rid = "ANS111"
description = "Commands should be idempotent"
helptext = (
"commands should only read while using `changed_when` or try to be "
"idempotent while using controls like `creates`, `removes` or `when`"
)
version = "0.1"
types = ["playbook", "task"]
def check(self, candidate, settings):
@ -19,9 +17,11 @@ class CheckCommandHasChanges(StandardBase):
if not errors:
for task in tasks:
if task["action"]["__ansible_module__"] in commands and (
"changed_when" not in task and "when" not in task
"changed_when" not in task
and "when" not in task
and "when" not in task.get("__ansible_action_meta__", [])
and "creates" not in task["action"] and "removes" not in task["action"]
and "creates" not in task["action"]
and "removes" not in task["action"]
):
errors.append(self.Error(task["__line__"], self.helptext))

View File

@ -20,15 +20,13 @@
import os
from ansiblelater.standard import StandardBase
from ansiblelater.rule import RuleBase
class CheckCommandInsteadOfArgument(StandardBase):
sid = "ANSIBLE0017"
class CheckCommandInsteadOfArgument(RuleBase):
rid = "ANS117"
description = "Commands should not be used in place of module arguments"
helptext = "{exec} used in place of file modules argument {arg}"
version = "0.2"
types = ["playbook", "task", "handler"]
def check(self, candidate, settings):
@ -41,7 +39,7 @@ class CheckCommandInsteadOfArgument(StandardBase):
"ln": "state=link",
"mkdir": "state=directory",
"rmdir": "state=absent",
"rm": "state=absent"
"rm": "state=absent",
}
if not errors:
@ -51,13 +49,14 @@ class CheckCommandInsteadOfArgument(StandardBase):
executable = os.path.basename(first_cmd_arg)
if (
first_cmd_arg and executable in arguments
first_cmd_arg
and executable in arguments
and task["action"].get("warn", True)
):
errors.append(
self.Error(
task["__line__"],
self.helptext.format(exec=executable, arg=arguments[executable])
self.helptext.format(exec=executable, arg=arguments[executable]),
)
)

View File

@ -1,14 +1,12 @@
import os
from ansiblelater.standard import StandardBase
from ansiblelater.rule import RuleBase
class CheckCommandInsteadOfModule(StandardBase):
sid = "ANSIBLE0008"
class CheckCommandInsteadOfModule(RuleBase):
rid = "ANS108"
description = "Commands should not be used in place of modules"
helptext = "{exec} command used in place of {module} module"
version = "0.1"
types = ["playbook", "task", "handler"]
def check(self, candidate, settings):
@ -31,7 +29,7 @@ class CheckCommandInsteadOfModule(StandardBase):
"rsync": "synchronize",
"supervisorctl": "supervisorctl",
"systemctl": "systemd",
"sed": "template or lineinfile"
"sed": "template or lineinfile",
}
if not errors:
@ -39,14 +37,19 @@ class CheckCommandInsteadOfModule(StandardBase):
if task["action"]["__ansible_module__"] in commands:
first_cmd_arg = self.get_first_cmd_arg(task)
executable = os.path.basename(first_cmd_arg)
cmd = cmd = self.get_safe_cmd(task)
if (
first_cmd_arg and executable in modules
and task["action"].get("warn", True) and "register" not in task
first_cmd_arg
and executable in modules
and task["action"].get("warn", True)
and "register" not in task
and not any(ch in cmd for ch in self.SHELL_PIPE_CHARS)
):
errors.append(
self.Error(
task["__line__"],
self.helptext.format(exec=executable, module=modules[executable])
self.helptext.format(exec=executable, module=modules[executable]),
)
)

View File

@ -1,15 +1,13 @@
import re
from ansiblelater.candidate import Template
from ansiblelater.standard import StandardBase
from ansiblelater.rule import RuleBase
class CheckCompareToEmptyString(StandardBase):
sid = "ANSIBLE0012"
description = "Don't compare to empty string \"\""
helptext = ("use `when: var` rather than `when: var !=` (or conversely `when: not var`)")
version = "0.1"
class CheckCompareToEmptyString(RuleBase):
rid = "ANS112"
description = 'Don\'t compare to empty string ""'
helptext = "use `when: var` rather than `when: var !=` (or conversely `when: not var`)"
types = ["playbook", "task", "handler", "template"]
def check(self, candidate, settings):

View File

@ -1,15 +1,13 @@
import re
from ansiblelater.candidate import Template
from ansiblelater.standard import StandardBase
from ansiblelater.rule import RuleBase
class CheckCompareToLiteralBool(StandardBase):
sid = "ANSIBLE0013"
class CheckCompareToLiteralBool(RuleBase):
rid = "ANS113"
description = "Don't compare to True or False"
helptext = ("use `when: var` rather than `when: var == True` (or conversely `when: not var`)")
version = "0.1"
helptext = "use `when: var` rather than `when: var == True` (or conversely `when: not var`)"
types = ["playbook", "task", "handler"]
def check(self, candidate, settings):

View File

@ -1,12 +1,10 @@
from ansiblelater.standard import StandardBase
from ansiblelater.rule import RuleBase
class CheckDeprecated(StandardBase):
sid = "ANSIBLE9999"
class CheckDeprecated(RuleBase):
rid = "ANS999"
description = "Deprecated features should not be used"
helptext = "'{old}' is deprecated and should not be used anymore. Use '{new}' instead."
version = "0.1"
helptext = "`{old}` is deprecated and should not be used anymore. Use `{new}` instead."
types = ["playbook", "task", "handler"]
def check(self, candidate, settings):
@ -20,7 +18,7 @@ class CheckDeprecated(StandardBase):
task["__line__"],
self.helptext.format(
old="skip_ansible_lint", new="skip_ansible_later"
)
),
)
)
return self.Result(candidate.path, errors)

View File

@ -0,0 +1,87 @@
# Copyright (c) 2013-2014 Will Thames <will@thames.id.au>
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
import os
from ansiblelater.rule import RuleBase
from ansiblelater.utils import has_glob, has_jinja
class CheckDeprecatedBareVars(RuleBase):
rid = "ANS127"
description = "Deprecated bare variables in loops must not be used"
helptext = (
"bare var '{barevar}' in '{loop_type}' must use full var syntax '{{{{ {barevar} }}}}' "
"or be converted to a list"
)
types = ["playbook", "task", "handler"]
def check(self, candidate, settings):
tasks, self.errors = self.get_normalized_tasks(candidate, settings)
if not self.errors:
for task in tasks:
loop_type = next((key for key in task if key.startswith("with_")), None)
if not loop_type:
continue
if loop_type in [
"with_nested",
"with_together",
"with_flattened",
"with_filetree",
"with_community.general.filetree",
]:
# These loops can either take a list defined directly in the task
# or a variable that is a list itself. When a single variable is used
# we just need to check that one variable, and not iterate over it like
# it's a list. Otherwise, loop through and check all items.
items = task[loop_type]
if not isinstance(items, (list, tuple)):
items = [items]
for var in items:
self._matchvar(var, task, loop_type)
elif loop_type == "with_subelements":
self._matchvar(task[loop_type][0], task, loop_type)
elif loop_type in ["with_sequence", "with_ini", "with_inventory_hostnames"]:
pass
else:
self._matchvar(task[loop_type], task, loop_type)
return self.Result(candidate.path, self.errors)
def _matchvar(self, varstring, task, loop_type):
if isinstance(varstring, str) and not has_jinja(varstring):
valid = loop_type == "with_fileglob" and bool(
has_jinja(varstring) or has_glob(varstring),
)
valid |= loop_type == "with_filetree" and bool(
has_jinja(varstring) or varstring.endswith(os.sep),
)
if not valid:
self.errors.append(
self.Error(
task["__line__"],
self.helptext.format(barevar=varstring, loop_type=loop_type),
)
)

View 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)

View File

@ -19,18 +19,16 @@
# THE SOFTWARE.
import re
from ansiblelater.standard import StandardBase
from ansiblelater.rule import RuleBase
class CheckFilePermissionMissing(StandardBase):
sid = "ANSIBLE0018"
class CheckFilePermissionMissing(RuleBase):
rid = "ANS118"
description = "File permissions unset or incorrect"
helptext = (
"`mode` parameter should set permissions explicitly (e.g. `mode: 0644`) "
"to avoid unexpected file permissions"
)
version = "0.2"
types = ["playbook", "task", "handler"]
_modules = {
@ -67,8 +65,7 @@ class CheckFilePermissionMissing(StandardBase):
mode = task["action"].get("mode", None)
state = task["action"].get("state", "file")
if module not in self._modules and \
module not in self._create_modules:
if module not in self._modules and module not in self._create_modules:
return False
if mode == "preserve" and module not in self._preserve_modules:

View File

@ -17,22 +17,28 @@
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
from ansiblelater.standard import StandardBase
from ansiblelater.rule import RuleBase
class CheckFilePermissionOctal(StandardBase):
sid = "ANSIBLE0019"
description = "Octal file permissions must contain leading zero or be a string"
helptext = "numeric file permissions without leading zero can behave in unexpected ways"
version = "0.2"
class CheckFilePermissionOctal(RuleBase):
rid = "ANS119"
description = "Numeric file permissions without a leading zero can behave unexpectedly"
helptext = '`mode: {mode}` should be strings with a leading zero `mode: "0{mode}"`'
types = ["playbook", "task", "handler"]
def check(self, candidate, settings):
tasks, errors = self.get_normalized_tasks(candidate, settings)
modules = [
"assemble", "copy", "file", "ini_file", "lineinfile", "replace", "synchronize",
"template", "unarchive"
"assemble",
"copy",
"file",
"ini_file",
"lineinfile",
"replace",
"synchronize",
"template",
"unarchive",
]
if not errors:
@ -41,26 +47,32 @@ class CheckFilePermissionOctal(StandardBase):
mode = task["action"].get("mode", None)
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)
@staticmethod
def _is_invalid_permission(mode):
other_write_without_read = (
mode % 8 and mode % 8 < 4 and not (mode % 8 == 1 and (mode >> 6) % 2 == 1)
)
group_write_without_read = ((mode >> 3) % 8 and (mode >> 3) % 8 < 4
and not ((mode >> 3) % 8 == 1 and (mode >> 6) % 2 == 1))
user_write_without_read = ((mode >> 6) % 8 and (mode >> 6) % 8 < 4
and (mode >> 6) % 8 != 1)
group_write_without_read = (
(mode >> 3) % 8
and (mode >> 3) % 8 < 4
and not ((mode >> 3) % 8 == 1 and (mode >> 6) % 2 == 1)
)
user_write_without_read = (mode >> 6) % 8 and (mode >> 6) % 8 < 4 and (mode >> 6) % 8 != 1
other_more_generous_than_group = mode % 8 > (mode >> 3) % 8
other_more_generous_than_user = mode % 8 > (mode >> 6) % 8
group_more_generous_than_user = (mode >> 3) % 8 > (mode >> 6) % 8
return bool(
other_write_without_read or group_write_without_read or user_write_without_read
or other_more_generous_than_group or other_more_generous_than_user
other_write_without_read
or group_write_without_read
or user_write_without_read
or other_more_generous_than_group
or other_more_generous_than_user
or group_more_generous_than_user
)

View File

@ -1,14 +1,12 @@
import re
from ansiblelater.standard import StandardBase
from ansiblelater.rule import RuleBase
class CheckFilterSeparation(StandardBase):
sid = "ANSIBLE0016"
class CheckFilterSeparation(RuleBase):
rid = "ANS116"
description = "Jinja2 filters should be separated with spaces"
helptext = "no suitable numbers of spaces (required: 1)"
version = "0.1"
types = ["playbook", "task", "handler", "rolevars", "hostvars", "groupvars"]
def check(self, candidate, settings):

View File

@ -17,15 +17,14 @@
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
from ansiblelater.standard import StandardBase
from ansiblelater.rule import RuleBase
class CheckGitHasVersion(StandardBase):
sid = "ANSIBLE0020"
class CheckGitHasVersion(RuleBase):
rid = "ANS120"
description = "Git checkouts should use explicit version"
helptext = "git checkouts should point to an explicit commit or tag, not `latest`"
version = "0.2"
types = ["playbook", "task", "handler"]
def check(self, candidate, settings):

View File

@ -1,21 +1,41 @@
from ansiblelater.standard import StandardBase
from ansiblelater.rule import RuleBase
class CheckInstallUseLatest(StandardBase):
sid = "ANSIBLE0009"
class CheckInstallUseLatest(RuleBase):
rid = "ANS109"
description = "Package installs should use present, not latest"
helptext = "package installs should use `state=present` with or without a version"
version = "0.1"
types = ["playbook", "task", "handler"]
def check(self, candidate, settings):
tasks, errors = self.get_normalized_tasks(candidate, settings)
package_managers = [
"yum", "apt", "dnf", "homebrew", "pacman", "openbsd_package", "pkg5", "portage",
"pkgutil", "slackpkg", "swdepot", "zypper", "bundler", "pip", "pear", "npm", "yarn",
"gem", "easy_install", "bower", "package", "apk", "openbsd_pkg", "pkgng", "sorcery",
"xbps"
"yum",
"apt",
"dnf",
"homebrew",
"pacman",
"openbsd_package",
"pkg5",
"portage",
"pkgutil",
"slackpkg",
"swdepot",
"zypper",
"bundler",
"pip",
"pear",
"npm",
"yarn",
"gem",
"easy_install",
"bower",
"package",
"apk",
"openbsd_pkg",
"pkgng",
"sorcery",
"xbps",
]
if not errors:

View 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

View File

@ -1,14 +1,12 @@
import re
from ansiblelater.standard import StandardBase
from ansiblelater.rule import RuleBase
class CheckLiteralBoolFormat(StandardBase):
sid = "ANSIBLE0014"
class CheckLiteralBoolFormat(RuleBase):
rid = "ANS114"
description = "Literal bools should be consistent"
helptext = "literal bools should be written as `{bools}`"
version = "0.1"
types = ["playbook", "task", "handler", "rolevars", "hostvars", "groupvars"]
def check(self, candidate, settings):

View File

@ -1,14 +1,12 @@
# Copyright (c) 2016, Tsukinowa Inc. <info@tsukinowa.jp>
# Copyright (c) 2018, Ansible Project
from ansiblelater.standard import StandardBase
from ansiblelater.rule import RuleBase
class CheckLocalAction(StandardBase):
sid = "ANSIBLE0024"
class CheckLocalAction(RuleBase):
rid = "ANS124"
description = "Don't use local_action"
helptext = ("`delegate_to: localhost` should be used instead of `local_action`")
version = "0.2"
helptext = "`delegate_to: localhost` should be used instead of `local_action`"
types = ["playbook", "task", "handler"]
def check(self, candidate, settings):

View File

@ -1,15 +1,13 @@
# Copyright (c) 2018, Ansible Project
from nested_lookup import nested_lookup
from ansiblelater.standard import StandardBase
from ansiblelater.rule import RuleBase
class CheckMetaChangeFromDefault(StandardBase):
sid = "ANSIBLE0021"
class CheckMetaChangeFromDefault(RuleBase):
rid = "ANS121"
description = "Roles meta/main.yml default values should be changed"
helptext = "meta/main.yml default values should be changed for: `{field}`"
version = "0.2"
types = ["meta"]
def check(self, candidate, settings):

View File

@ -1,14 +1,12 @@
from nested_lookup import nested_lookup
from ansiblelater.standard import StandardBase
from ansiblelater.rule import RuleBase
class CheckMetaMain(StandardBase):
sid = "ANSIBLE0002"
class CheckMetaMain(RuleBase):
rid = "ANS102"
description = "Roles must contain suitable meta/main.yml"
helptext = "file should contain `{key}` key"
version = "0.1"
types = ["meta"]
def check(self, candidate, settings):
@ -16,8 +14,8 @@ class CheckMetaMain(StandardBase):
keys = ["author", "description", "min_ansible_version", "platforms"]
if not errors:
has_galaxy_info = (isinstance(content, dict) and "galaxy_info" in content)
has_dependencies = (isinstance(content, dict) and "dependencies" in content)
has_galaxy_info = isinstance(content, dict) and "galaxy_info" in content
has_dependencies = isinstance(content, dict) and "dependencies" in content
if not has_galaxy_info:
errors.append(self.Error(None, self.helptext.format(key="galaxy_info")))

View File

@ -1,14 +1,12 @@
from collections import defaultdict
from ansiblelater.standard import StandardBase
from ansiblelater.rule import RuleBase
class CheckNameFormat(StandardBase):
sid = "ANSIBLE0007"
class CheckNameFormat(RuleBase):
rid = "ANS107"
description = "Name of tasks and handlers must be formatted"
helptext = "name '{name}' should start with uppercase"
version = "0.1"
helptext = "name `{name}` should start with uppercase"
types = ["playbook", "task", "handler"]
def check(self, candidate, settings):
@ -19,7 +17,7 @@ class CheckNameFormat(StandardBase):
for task in tasks:
if "name" in task:
namelines[task["name"]].append(task["__line__"])
for (name, lines) in namelines.items():
for name, lines in namelines.items():
if name and not name[0].isupper():
errors.append(self.Error(lines[-1], self.helptext.format(name=name)))

View File

@ -1,12 +1,10 @@
from ansiblelater.standard import StandardBase
from ansiblelater.rule import RuleBase
class CheckNamedTask(StandardBase):
sid = "ANSIBLE0006"
class CheckNamedTask(RuleBase):
rid = "ANS106"
description = "Tasks and handlers must be named"
helptext = "module '{module}' used without or empty `name` attribute"
version = "0.1"
helptext = "module `{module}` used without or empty `name` attribute"
types = ["playbook", "task", "handler"]
def check(self, candidate, settings):

View File

@ -1,12 +1,10 @@
from ansiblelater.standard import StandardBase
from ansiblelater.rule import RuleBase
class CheckNativeYaml(StandardBase):
sid = "LINT0008"
class CheckNativeYaml(RuleBase):
rid = "YML108"
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"
version = "0.1"
types = ["playbook", "task", "handler"]
def check(self, candidate, settings):

View File

@ -21,18 +21,16 @@
# THE SOFTWARE.
import re
from ansiblelater.standard import StandardBase
from ansiblelater.rule import RuleBase
class CheckNestedJinja(StandardBase):
sid = "ANSIBLE0023"
class CheckNestedJinja(RuleBase):
rid = "ANS123"
description = "Don't use nested Jinja2 pattern"
helptext = (
"there should not be any nested jinja pattern "
"like `{{ list_one + {{ list_two | max }} }}`"
)
version = "0.2"
types = ["playbook", "task", "handler", "rolevars", "hostvars", "groupvars"]
def check(self, candidate, settings):

View File

@ -1,14 +1,12 @@
# Copyright (c) 2016, Tsukinowa Inc. <info@tsukinowa.jp>
# Copyright (c) 2018, Ansible Project
from ansiblelater.standard import StandardBase
from ansiblelater.rule import RuleBase
class CheckRelativeRolePaths(StandardBase):
sid = "ANSIBLE0025"
class CheckRelativeRolePaths(RuleBase):
rid = "ANS125"
description = "Don't use a relative path in a role"
helptext = "`copy` and `template` modules don't need relative path for `src`"
version = "0.2"
types = ["playbook", "task", "handler"]
def check(self, candidate, settings):

View File

@ -1,14 +1,12 @@
from ansible.parsing.yaml.objects import AnsibleMapping
from ansiblelater.standard import StandardBase
from ansiblelater.rule import RuleBase
class CheckScmInSrc(StandardBase):
sid = "ANSIBLE0005"
class CheckScmInSrc(RuleBase):
rid = "ANS105"
description = "Use `scm:` key rather than `src: scm+url`"
helptext = "usage of `src: scm+url` not recommended"
version = "0.1"
types = ["rolesfile"]
def check(self, candidate, settings):
@ -16,7 +14,11 @@ class CheckScmInSrc(StandardBase):
if not errors:
for role in roles:
if isinstance(role, AnsibleMapping) and "+" in role.get("src"):
if (
isinstance(role, AnsibleMapping)
and bool(role.get("src"))
and "+" in role.get("src")
):
errors.append(self.Error(role["__line__"], self.helptext))
return self.Result(candidate.path, errors)

View File

@ -1,14 +1,10 @@
import re
from ansiblelater.standard import StandardBase
from ansiblelater.rule import RuleBase
class CheckShellInsteadCommand(StandardBase):
sid = "ANSIBLE0010"
class CheckShellInsteadCommand(RuleBase):
rid = "ANS110"
description = "Shell should only be used when essential"
helptext = "shell should only be used when piping, redirecting or chaining commands"
version = "0.1"
types = ["playbook", "task", "handler"]
def check(self, candidate, settings):
@ -22,13 +18,8 @@ class CheckShellInsteadCommand(StandardBase):
if "executable" in task["action"]:
continue
if "cmd" in task["action"]:
cmd = task["action"].get("cmd", [])
else:
cmd = " ".join(task["action"].get("__ansible_arguments__", []))
unjinja = re.sub(r"\{\{[^\}]*\}\}", "JINJA_VAR", cmd)
if not any(ch in unjinja for ch in "&|<>;$\n*[]{}?"):
cmd = self.get_safe_cmd(task)
if not any(ch in cmd for ch in self.SHELL_PIPE_CHARS):
errors.append(self.Error(task["__line__"], self.helptext))
return self.Result(candidate.path, errors)

View File

@ -1,15 +1,13 @@
import re
from collections import defaultdict
from ansiblelater.standard import StandardBase
from ansiblelater.rule import RuleBase
class CheckTaskSeparation(StandardBase):
sid = "ANSIBLE0001"
class CheckTaskSeparation(RuleBase):
rid = "ANS101"
description = "Single tasks should be separated by empty line"
helptext = "missing task separation (required: 1 empty line)"
version = "0.1"
types = ["playbook", "task", "handler"]
def check(self, candidate, settings):

View File

@ -1,14 +1,12 @@
from collections import defaultdict
from ansiblelater.standard import StandardBase
from ansiblelater.rule import RuleBase
class CheckUniqueNamedTask(StandardBase):
sid = "ANSIBLE0003"
class CheckUniqueNamedTask(RuleBase):
rid = "ANS103"
description = "Tasks and handlers must be uniquely named within a single file"
helptext = "name '{name}' appears multiple times"
version = "0.1"
helptext = "name `{name}` appears multiple times"
types = ["playbook", "task", "handler"]
def check(self, candidate, settings):
@ -20,7 +18,7 @@ class CheckUniqueNamedTask(StandardBase):
for task in tasks:
if "name" in task:
namelines[task["name"]].append(task["__line__"])
for (name, lines) in namelines.items():
for name, lines in namelines.items():
if name and len(lines) > 1:
errors.append(self.Error(lines[-1], self.helptext.format(name=name)))

View File

@ -1,17 +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)

View File

@ -1,15 +1,13 @@
from ansiblelater.standard import StandardBase
from ansiblelater.rule import RuleBase
class CheckWhenFormat(StandardBase):
sid = "ANSIBLE0022"
class CheckWhenFormat(RuleBase):
rid = "ANS122"
description = "Don't use Jinja2 in when"
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"]
def check(self, candidate, settings):

View File

@ -1,11 +1,9 @@
from ansiblelater.standard import StandardBase
from ansiblelater.rule import RuleBase
class CheckYamlColons(StandardBase):
sid = "LINT0005"
class CheckYamlColons(RuleBase):
rid = "YML105"
description = "YAML should use consistent number of spaces around colons"
version = "0.1"
types = ["playbook", "task", "handler", "rolevars", "hostvars", "groupvars", "meta"]
def check(self, candidate, settings):

View File

@ -1,11 +1,9 @@
from ansiblelater.standard import StandardBase
from ansiblelater.rule import RuleBase
class CheckYamlDocumentEnd(StandardBase):
sid = "LINT0009"
description = "YAML should contain document end marker"
version = "0.1"
class CheckYamlDocumentEnd(RuleBase):
rid = "YML109"
description = "YAML document end marker should match configuration"
types = ["playbook", "task", "handler", "rolevars", "hostvars", "groupvars", "meta"]
def check(self, candidate, settings):

View File

@ -1,11 +1,9 @@
from ansiblelater.standard import StandardBase
from ansiblelater.rule import RuleBase
class CheckYamlDocumentStart(StandardBase):
sid = "LINT0004"
description = "YAML should contain document start marker"
version = "0.1"
class CheckYamlDocumentStart(RuleBase):
rid = "YML104"
description = "YAML document start marker should match configuration"
types = ["playbook", "task", "handler", "rolevars", "hostvars", "groupvars", "meta"]
def check(self, candidate, settings):

View File

@ -1,11 +1,9 @@
from ansiblelater.standard import StandardBase
from ansiblelater.rule import RuleBase
class CheckYamlEmptyLines(StandardBase):
sid = "LINT0001"
class CheckYamlEmptyLines(RuleBase):
rid = "YML101"
description = "YAML should not contain unnecessarily empty lines"
version = "0.1"
types = ["playbook", "task", "handler", "rolevars", "hostvars", "groupvars", "meta"]
def check(self, candidate, settings):

View File

@ -1,14 +1,12 @@
import os
from ansiblelater.standard import StandardBase
from ansiblelater.rule import RuleBase
class CheckYamlFile(StandardBase):
sid = "LINT0006"
class CheckYamlFile(RuleBase):
rid = "YML106"
description = "Roles file should be in yaml format"
helptext = "file does not have a .yml extension"
version = "0.1"
types = ["playbook", "task", "handler"]
def check(self, candidate, settings):

View File

@ -1,12 +1,10 @@
from ansiblelater.standard import StandardBase
from ansiblelater.rule import RuleBase
class CheckYamlHasContent(StandardBase):
sid = "LINT0007"
class CheckYamlHasContent(RuleBase):
rid = "YML107"
description = "Files should contain useful content"
helptext = "the file appears to have no useful content"
version = "0.1"
types = ["playbook", "task", "handler", "rolevars", "defaults", "meta"]
def check(self, candidate, settings):

View File

@ -1,11 +1,9 @@
from ansiblelater.standard import StandardBase
from ansiblelater.rule import RuleBase
class CheckYamlHyphens(StandardBase):
sid = "LINT0003"
class CheckYamlHyphens(RuleBase):
rid = "YML103"
description = "YAML should use consistent number of spaces after hyphens"
version = "0.1"
types = ["playbook", "task", "handler", "rolevars", "hostvars", "groupvars", "meta"]
def check(self, candidate, settings):

View File

@ -1,11 +1,9 @@
from ansiblelater.standard import StandardBase
from ansiblelater.rule import RuleBase
class CheckYamlIndent(StandardBase):
sid = "LINT0002"
class CheckYamlIndent(RuleBase):
rid = "YML102"
description = "YAML should not contain unnecessarily empty lines"
version = "0.1"
types = ["playbook", "task", "handler", "rolevars", "hostvars", "groupvars", "meta"]
def check(self, candidate, settings):

View 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)

View File

@ -1,5 +1,6 @@
"""Global settings object definition."""
import importlib.resources
import os
import anyconfig
@ -7,7 +8,6 @@ import jsonschema.exceptions
import pathspec
from appdirs import AppDirs
from jsonschema._utils import format_as_index
from pkg_resources import resource_filename
from ansiblelater import utils
@ -104,13 +104,13 @@ class Settings:
if f not in defaults["ansible"]["custom_modules"]:
defaults["ansible"]["custom_modules"].append(f)
if defaults["rules"]["buildin"]:
defaults["rules"]["standards"].append(
os.path.join(resource_filename("ansiblelater", "rules"))
)
if defaults["rules"]["builtin"]:
ref = importlib.resources.files("ansiblelater") / "rules"
with importlib.resources.as_file(ref) as path:
defaults["rules"]["dir"].append(path)
defaults["rules"]["standards"] = [
os.path.relpath(os.path.normpath(p)) for p in defaults["rules"]["standards"]
defaults["rules"]["dir"] = [
os.path.relpath(os.path.normpath(p)) for p in defaults["rules"]["dir"]
]
return defaults
@ -118,21 +118,20 @@ class Settings:
def _get_defaults(self):
defaults = {
"rules": {
"buildin": True,
"standards": [],
"filter": [],
"builtin": True,
"dir": [],
"include_filter": [],
"exclude_filter": [],
"warning_filter": [
"ANSIBLE9999",
"ANSIBLE9998",
"ANS128",
"ANS999",
],
"ignore_dotfiles": True,
"exclude_files": [],
"version": ""
},
"logging": {
"level": "WARNING",
"json": False
"json": False,
},
"ansible": {
"custom_modules": [],
@ -145,7 +144,7 @@ class Settings:
"exclude": [
"meta",
"debug",
"block",
"block/always/rescue",
"include_role",
"import_role",
"include_tasks",
@ -169,17 +168,21 @@ class Settings:
"indent-sequences": True,
},
"hyphens": {
"max-spaces-after": 1
"max-spaces-after": 1,
},
"document-start": {
"present": True
"present": True,
},
"document-end": {
"present": True
"present": False,
},
"colons": {
"max-spaces-before": 0,
"max-spaces-after": 1
"max-spaces-after": 1,
},
"octal-values": {
"forbid-implicit-octal": True,
"forbid-explicit-octal": True,
},
},
}
@ -195,7 +198,7 @@ class Settings:
except jsonschema.exceptions.ValidationError as e:
validator = e.validator
path = format_as_index(
list(e.absolute_path)[0],
next(iter(e.absolute_path)),
list(e.absolute_path)[1:],
)
msg = e.message
@ -210,9 +213,10 @@ class Settings:
excludes = self.config["rules"]["exclude_files"]
ignore_dotfiles = self.config["rules"]["ignore_dotfiles"]
if ignore_dotfiles and not self.args_files:
if ignore_dotfiles:
excludes.append(".*")
else:
if self.args_files:
del excludes[:]
filelist = []

View File

@ -1,11 +1,13 @@
"""Global utils collection."""
import contextlib
import re
import sys
from contextlib import suppress
from distutils.version import LooseVersion
from functools import lru_cache
import yaml
from ansible.plugins.loader import module_loader
from ansiblelater import logger
@ -31,12 +33,7 @@ def count_spaces(c_string):
break
trailing_spaces += 1
return ((leading_spaces, trailing_spaces))
def standards_latest(standards):
return max([standard.version for standard in standards if standard.version] or ["0.1"],
key=LooseVersion)
return (leading_spaces, trailing_spaces)
def lines_ranges(lines_spec):
@ -81,14 +78,24 @@ def open_file(filename, mode="r"):
def add_dict_branch(tree, vector, value):
key = vector[0]
tree[key] = value \
if len(vector) == 1 \
else add_dict_branch(tree[key] if key in tree else {},
vector[1:],
value)
tree[key] = (
value if len(vector) == 1 else add_dict_branch(tree.get(key, {}), vector[1:], value)
)
return tree
def has_jinja(value):
"""Return true if a string seems to contain jinja templating."""
re_has_jinja = re.compile(r"{[{%#].*[%#}]}", re.DOTALL)
return bool(isinstance(value, str) and re_has_jinja.search(value))
def has_glob(value):
"""Return true if a string looks like having a glob pattern."""
re_has_glob = re.compile("[][*?]")
return bool(isinstance(value, str) and re_has_glob.search(value))
def sysexit(code=1):
sys.exit(code)
@ -107,3 +114,21 @@ class Singleton(type):
if cls not in cls._instances:
cls._instances[cls] = super().__call__(*args, **kwargs)
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

View File

@ -21,8 +21,6 @@
# THE SOFTWARE.
import codecs
import glob
import imp
import os
from contextlib import suppress
@ -67,7 +65,9 @@ def ansible_template(basedir, varname, templatevars, **kwargs):
try:
from ansible.plugins import module_loader
from ansible.plugins.loader import init_plugin_loader, module_loader
init_plugin_loader()
except ImportError:
from ansible.plugins.loader import module_loader
@ -128,24 +128,6 @@ BLOCK_NAME_TO_ACTION_TYPE_MAP = {
}
def load_plugins(directory):
result = []
fh = None
for pluginfile in glob.glob(os.path.join(directory, "[A-Za-z]*.py")):
pluginname = os.path.basename(pluginfile.replace(".py", ""))
try:
fh, filename, desc = imp.find_module(pluginname, [directory])
mod = imp.load_module(pluginname, fh, filename, desc)
obj = getattr(mod, pluginname)()
result.append(obj)
finally:
if fh:
fh.close()
return result
def tokenize(line):
tokens = line.lstrip().split(" ")
if tokens[0] == "-":
@ -208,11 +190,12 @@ def template(basedir, value, variables, fail_on_undefined=False, **kwargs):
# Hack to skip the following exception when using to_json filter on a variable.
# I guess the filter doesn't like empty vars...
with suppress(AnsibleError, ValueError):
value = ansible_template(
os.path.abspath(basedir), value, variables,
**dict(kwargs, fail_on_undefined=fail_on_undefined)
return ansible_template(
os.path.abspath(basedir),
value,
variables,
**dict(kwargs, fail_on_undefined=fail_on_undefined),
)
return value
def play_children(basedir, item, parent_type):
@ -234,8 +217,9 @@ def play_children(basedir, item, parent_type):
if k in delegate_map and v:
v = template(
os.path.abspath(basedir),
v, {"playbook_dir": os.path.abspath(basedir)},
fail_on_undefined=False
v,
{"playbook_dir": os.path.abspath(basedir)},
fail_on_undefined=False,
)
return delegate_map[k](basedir, k, v, parent_type)
return []
@ -266,18 +250,20 @@ def _taskshandlers_children(basedir, k, v, parent_type):
results.extend(
_roles_children(
basedir,
k, [th["import_role"].get("name")],
k,
[th["import_role"].get("name")],
parent_type,
main=th["import_role"].get("tasks_from", "main")
main=th["import_role"].get("tasks_from", "main"),
)
)
elif "include_role" in th:
results.extend(
_roles_children(
basedir,
k, [th["include_role"].get("name")],
k,
[th["include_role"].get("name")],
parent_type,
main=th["include_role"].get("tasks_from", "main")
main=th["include_role"].get("tasks_from", "main"),
)
)
elif "block" in th:
@ -329,7 +315,7 @@ def _rolepath(basedir, role):
path_dwim(basedir, role),
# if included from roles/[role]/meta/main.yml
path_dwim(basedir, os.path.join("..", "..", "..", "roles", role)),
path_dwim(basedir, os.path.join("..", "..", role))
path_dwim(basedir, os.path.join("..", "..", role)),
]
if constants.DEFAULT_ROLES_PATH:
@ -371,25 +357,75 @@ def rolename(filepath):
idx = filepath.find("roles/")
if idx < 0:
return ""
role = filepath[idx + 6:]
role = role[:role.find("/")]
return role # noqa
role = filepath[idx + 6 :]
return role[: role.find("/")]
def _kv_to_dict(v):
(command, args, kwargs) = tokenize(v)
return (dict(__ansible_module__=command, __ansible_arguments__=args, **kwargs))
return dict(__ansible_module__=command, __ansible_arguments__=args, **kwargs)
def normalize_task(task, filename, custom_modules=None):
"""Ensure tasks have an action key and strings are converted to python objects."""
if custom_modules is None:
custom_modules = []
def _normalize(task, custom_modules):
if custom_modules is None:
custom_modules = []
ansible_action_type = task.get("__ansible_action_type__", "task")
if "__ansible_action_type__" in task:
del (task["__ansible_action_type__"])
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(set(builtin + custom_modules))
ansible.parsing.mod_args.BUILTIN_TASKS = frozenset(builtin)
mod_arg_parser = ModuleArgsParser(task)
try:
action, arguments, normalized["delegate_to"] = mod_arg_parser.parse()
except AnsibleParserError as e:
raise LaterAnsibleError(e) from e
# denormalize shell -> command conversion
if "_uses_shell" in arguments:
action = "shell"
del arguments["_uses_shell"]
for k, v in list(task.items()):
if k in ansible_parsed_keys or k == action:
# we don"t want to re-assign these values, which were
# determined by the ModuleArgsParser() above
continue
normalized[k] = v
# 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:
normalized["action"]["__ansible_arguments__"] = (
arguments["_raw_params"].strip().split()
)
del arguments["_raw_params"]
else:
normalized["action"]["__ansible_arguments__"] = []
normalized["action"].update(arguments)
return normalized
# temp. extract metadata
ansible_meta = {}
@ -401,67 +437,34 @@ def normalize_task(task, filename, custom_modules=None):
ansible_meta[key] = task.pop(key, default)
normalized = {}
ansible_action_type = task.get("__ansible_action_type__", "task")
if "__ansible_action_type__" in task:
del task["__ansible_action_type__"]
builtin = list(ansible.parsing.mod_args.BUILTIN_TASKS)
builtin = list(set(builtin + custom_modules))
ansible.parsing.mod_args.BUILTIN_TASKS = frozenset(builtin)
mod_arg_parser = ModuleArgsParser(task)
try:
action, arguments, normalized["delegate_to"] = mod_arg_parser.parse()
except AnsibleParserError as e:
raise LaterAnsibleError("syntax error", e) from e
# denormalize shell -> command conversion
if "_uses_shell" in arguments:
action = "shell"
del (arguments["_uses_shell"])
for (k, v) in list(task.items()):
if k in ("action", "local_action", "args", "delegate_to") or k == action:
# we don"t want to re-assign these values, which were
# determined by the ModuleArgsParser() above
continue
normalized[k] = v
normalized["action"] = {"__ansible_module__": action}
if "_raw_params" in arguments:
normalized["action"]["__ansible_arguments__"] = arguments["_raw_params"].strip().split()
del (arguments["_raw_params"])
else:
normalized["action"]["__ansible_arguments__"] = []
normalized["action"].update(arguments)
normalized = _normalize(task, custom_modules)
normalized[FILENAME_KEY] = filename
normalized["__ansible_action_type__"] = ansible_action_type
# add back extracted metadata
for (k, v) in ansible_meta.items():
for k, v in ansible_meta.items():
if v:
normalized[k] = v
return normalized
def action_tasks(yaml, file):
def action_tasks(yaml, candidate):
tasks = []
if file["filetype"] in ["tasks", "handlers"]:
tasks = add_action_type(yaml, file["filetype"])
if candidate.filemeta in ["tasks", "handlers"]:
tasks = add_action_type(yaml, candidate.filemeta)
else:
tasks.extend(extract_from_list(yaml, ["tasks", "handlers", "pre_tasks", "post_tasks"]))
# Add sub-elements of block/rescue/always to tasks list
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 [task for task in tasks if set(allowed).isdisjoint(task.keys())]
return tasks
def task_to_str(task):
@ -469,10 +472,14 @@ def task_to_str(task):
if name:
return name
action = task.get("action")
args = " ".join([
f"{k}={v}" for (k, v) in action.items()
if k not in ["__ansible_module__", "__ansible_arguments__"]
] + action.get("__ansible_arguments__"))
args = " ".join(
[
f"{k}={v}"
for (k, v) in action.items()
if k not in ["__ansible_module__", "__ansible_arguments__"]
]
+ action.get("__ansible_arguments__")
)
return "{} {}".format(action["__ansible_module__"], args)
@ -486,10 +493,13 @@ def extract_from_list(blocks, candidates):
meta_data = dict(block)
for key in delete_meta_keys:
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:
raise RuntimeError(
f"Key '{candidate}' defined, but bad value: '{str(block[candidate])}'"
f"Key '{candidate}' defined, but bad value: '{block[candidate]!s}'"
)
return results
@ -538,9 +548,13 @@ def parse_yaml_linenumbers(data, filename):
loader.compose_node = compose_node
loader.construct_mapping = construct_mapping
data = loader.get_single_data() or []
except (yaml.parser.ParserError, yaml.scanner.ScannerError) as e:
except (
yaml.parser.ParserError,
yaml.scanner.ScannerError,
yaml.constructor.ConstructorError,
) as e:
raise LaterError("syntax error", e) from e
except (yaml.composer.ComposerError) as e:
except yaml.composer.ComposerError as e:
e.problem = f"{e.context} {e.problem}"
raise LaterError("syntax error", e) from e
return data
@ -571,6 +585,26 @@ def normalized_yaml(file, options):
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:
"""Handle custom yaml unsafe tag."""

View File

@ -2,13 +2,12 @@
title: Documentation
---
[![Build Status](https://img.shields.io/drone/build/thegeeklab/ansible-later?logo=drone&server=https%3A%2F%2Fdrone.thegeeklab.de)](https://drone.thegeeklab.de/thegeeklab/ansible-later)
[![Build Status](https://ci.thegeeklab.de/api/badges/thegeeklab/ansible-later/status.svg)](https://ci.thegeeklab.de/repos/thegeeklab/ansible-later)
[![Docker Hub](https://img.shields.io/badge/dockerhub-latest-blue.svg?logo=docker&logoColor=white)](https://hub.docker.com/r/thegeeklab/ansible-later)
[![Quay.io](https://img.shields.io/badge/quay-latest-blue.svg?logo=docker&logoColor=white)](https://quay.io/repository/thegeeklab/ansible-later)
[![Python Version](https://img.shields.io/pypi/pyversions/ansible-later.svg)](https://pypi.org/project/ansible-later/)
[![PyPI Status](https://img.shields.io/pypi/status/ansible-later.svg)](https://pypi.org/project/ansible-later/)
[![PyPI Release](https://img.shields.io/pypi/v/ansible-later.svg)](https://pypi.org/project/ansible-later/)
[![Codecov](https://img.shields.io/codecov/c/github/thegeeklab/ansible-later)](https://codecov.io/gh/thegeeklab/ansible-later)
[![GitHub contributors](https://img.shields.io/github/contributors/thegeeklab/ansible-later)](https://github.com/thegeeklab/ansible-later/graphs/contributors)
[![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)

View File

@ -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 -->
<!-- spellchecker-disable -->
{{< highlight Python "linenos=table" >}}
class CheckBecomeUser(StandardBase):
class CheckBecomeUser(RuleBase):
sid = "ANSIBLE0015"
rid = "ANS115"
description = "Become should be combined with become_user"
helptext = "the task has `become` enabled but `become_user` is missing"
version = "0.1"
types = ["playbook", "task", "handler"]
def check(self, candidate, settings):

View File

@ -13,4 +13,4 @@ Changes can be made in a YAML configuration file or via CLI options, which are p
Please note that YAML attributes are overwritten while YAML lists are merged in any configuration files.
To simplify single file linting, e.g. for debugging purposes, ansible-later ignores the `exclude_files` and `ignore_dotfiles` options when only one file is passed to the CLI.
To simplify the linting of individual files, e.g. for debugging purposes, ansible-later ignores the `exclude_files` and `ignore_dotfiles` options when files are passed to the CLI.

View File

@ -8,28 +8,27 @@ You can get all available CLI options by running `ansible-later --help`:
<!-- spellchecker-disable -->
{{< highlight Shell "linenos=table" >}}
$ ansible-later --help
usage: ansible-later [-h] [-c CONFIG_FILE] [-r RULES.STANDARDS]
[-s RULES.FILTER] [-v] [-q] [--version]
[rules.files [rules.files ...]]
usage: ansible-later [-h] [-c CONFIG] [-r DIR] [-B] [-i TAGS] [-x TAGS] [-v] [-q] [-V] [rules.files ...]
Validate Ansible files against best practice guideline
positional arguments:
rules.files
optional arguments:
options:
-h, --help show this help message and exit
-c CONFIG_FILE, --config CONFIG_FILE
location of configuration file
-r RULES.STANDARDS, --rules RULES.STANDARDS
location of standards rules
-s RULES.FILTER, --standards RULES.FILTER
limit standards to given ID's
-x RULES.EXCLUDE_FILTER, --exclude-standards RULES.EXCLUDE_FILTER
exclude standards by given ID's
-c CONFIG, --config CONFIG
path to configuration file
-r DIR, --rules-dir DIR
directory of rules
-B, --no-builtin disables built-in rules
-i TAGS, --include-rules TAGS
limit rules to given id/tags
-x TAGS, --exclude-rules TAGS
exclude rules by given it/tags
-v increase log level
-q decrease log level
--version show program's version number and exit
-V, --version show program's version number and exit
{{< /highlight >}}
<!-- spellchecker-enable -->
<!-- prettier-ignore-end -->

View File

@ -11,37 +11,37 @@ The default configuration is used if no other value is specified. Each option ca
---
ansible:
# Add the name of used custom Ansible modules. Otherwise ansible-later
# can't detect unknown modules and will through an error.
# can't detect unknown modules and will throw an error.
# Modules which are bundled with the role and placed in a './library'
# directory will be auto-detected and don't need to be added to this list.
custom_modules: []
# Settings for variable formatting rule (ANSIBLE0004)
# Settings for variable formatting rule (ANS104)
double-braces:
max-spaces-inside: 1
min-spaces-inside: 1
# List of allowed literal bools (ANSIBLE0014)
# List of allowed literal bools (ANS114)
literal-bools:
- "True"
- "False"
- "yes"
- "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!
named-task:
exclude:
- "meta"
- "debug"
- "block"
- "block/always/rescue"
- "include_role"
- "include_tasks"
- "include_vars"
- "import_role"
- "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!
native-yaml:
exclude: []
@ -58,8 +58,8 @@ logging:
# Global settings for all defined rules
rules:
# Disable build-in rules if required
buildin: True
# Disable built-in rules if required
builtin: True
# List of files to exclude
exclude_files: []
@ -75,22 +75,17 @@ rules:
exclude_filter: []
# 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.
# This list allows to degrade errors to warnings for each rule.
# no rules are marked as warnings. This list allows to degrade errors to warnings for each rule.
warning_filter:
- "ANSIBLE9999"
- "ANSIBLE9998"
- "ANS128"
- "ANS999"
# All dotfiles (including hidden folders) are excluded by default.
# You can disable this setting and handle dotfiles by yourself with `exclude_files`.
ignore_dotfiles: True
# List of directories to load standard rules from (defaults to build-in)
standards: []
# Standard version to use. Standard version set in a roles meta file
# or playbook will takes precedence.
version:
# List of directories to load rules from (defaults to built-in)
dir: []
# Block to control included yamllint rules.
# See https://yamllint.readthedocs.io/en/stable/rules.html
@ -100,6 +95,8 @@ yamllint:
max-spaces-before: 0
document-start:
present: True
document-end:
present: True
empty-lines:
max: 1
max-end: 1

View File

@ -2,44 +2,47 @@
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 |
| ----------------------------- | ----------- | ----------------------------------------------------------------- | ---------------------------------------------------------------------- |
| CheckYamlEmptyLines | LINT0001 | 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} |
| CheckYamlHyphens | LINT0003 | 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}} |
| CheckYamlColons | LINT0005 | 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. | |
| CheckYamlHasContent | LINT0007 | Files should contain useful content. | |
| CheckNativeYaml | LINT0008 | 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}} |
| CheckTaskSeparation | ANSIBLE0001 | Single tasks should be separated by an empty line. | |
| CheckMetaMain | ANSIBLE0002 | Meta file should contain a basic subset of parameters. | author, description, min_ansible_version, platforms, dependencies |
| CheckUniqueNamedTask | ANSIBLE0003 | Tasks and handlers must be uniquely named within a file. | |
| CheckBraces | ANSIBLE0004 | YAML should use consistent number of spaces around variables. | {double-braces: max-spaces-inside: 1, min-spaces-inside: 1} |
| CheckScmInSrc | ANSIBLE0005 | Use SCM key rather than `src: scm+url` in requirements file. | |
| CheckNamedTask | ANSIBLE0006 | Tasks and handlers must be named. | {named-task: {exclude: [meta, debug, block, include\_\*, import\_\*]}} |
| CheckNameFormat | ANSIBLE0007 | Name of tasks and handlers must be formatted. | formats: first letter capital |
| CheckCommandInsteadofModule | ANSIBLE0008 | Commands should not be used in place of modules. | |
| CheckInstallUseLatest | ANSIBLE0009 | Package managers should not install with state=latest. | |
| CheckShellInsteadCommand | ANSIBLE0010 | Use Shell only when piping, redirecting or chaining commands. | |
| CheckCommandHasChanges | ANSIBLE0011 | Commands should be idempotent and only used with some checks. | |
| CheckCompareToEmptyString | ANSIBLE0012 | Don't compare to "" - use `when: var` or `when: not var`. | |
| CheckCompareToLiteralBool | ANSIBLE0013 | Don't compare to True/False - use `when: var` or `when: not var`. | |
| CheckLiteralBoolFormat | ANSIBLE0014 | Literal bools should be consistent. | {literal-bools: [True, False, yes, no]} |
| CheckBecomeUser | ANSIBLE0015 | Become should be combined with become_user. | |
| CheckFilterSeparation | ANSIBLE0016 | Jinja2 filters should be separated with spaces. | |
| CheckCommandInsteadOfArgument | ANSIBLE0017 | Commands should not be used in place of module arguments. | |
| CheckFilePermissionMissing | ANSIBLE0018 | File permissions unset or incorrect. | |
| CheckFilePermissionOctal | ANSIBLE0019 | Octal file permissions must contain leading zero or be a string. | |
| CheckGitHasVersion | ANSIBLE0020 | Git checkouts should use explicit version. | |
| CheckMetaChangeFromDefault | ANSIBLE0021 | Roles meta/main.yml default values should be changed. | |
| CheckWhenFormat | ANSIBLE0022 | Don't use Jinja2 in `when`. | |
| CheckNestedJinja | ANSIBLE0023 | Don't use nested Jinja2 pattern. | |
| CheckLocalAction | ANSIBLE0024 | Don't use local_action. | |
| CheckRelativeRolePaths | ANSIBLE0025 | Don't use a relative path in a role. | |
| CheckChangedInWhen | ANSIBLE0026 | Use handlers instead of `when: changed`. | |
| CheckVersion | ANSIBLE9998 | Standards version should be pinned. | |
| CheckDeprecated | ANSIBLE9999 | Deprecated features of `ansible-later` should not be used. | |
| Rule | ID | Description | Parameter |
| ----------------------------- | ------ | ----------------------------------------------------------------- | -------------------------------------------------------------------------- |
| CheckYamlEmptyLines | YML101 | YAML should not contain unnecessarily empty lines. | {max: 1, max-start: 0, max-end: 1} |
| CheckYamlIndent | YML102 | YAML should be correctly indented. | {spaces: 2, check-multi-line-strings: false, indent-sequences: true} |
| CheckYamlHyphens | YML103 | YAML should use consistent number of spaces after hyphens (-). | {max-spaces-after: 1} |
| CheckYamlDocumentStart | YML104 | YAML should contain document start marker. | {document-start: {present: true}} |
| CheckYamlColons | YML105 | YAML should use consistent number of spaces around colons. | {colons: {max-spaces-before: 0, max-spaces-after: 1}} |
| CheckYamlFile | YML106 | Roles file should be in YAML format. | |
| CheckYamlHasContent | YML107 | Files should contain useful content. | |
| CheckNativeYaml | YML108 | Use YAML format for tasks and handlers rather than key=value. | {native-yaml: {exclude: []}} |
| CheckYamlDocumentEnd | YML109 | YAML should contain document end marker. | {document-end: {present: true}} |
| CheckYamlOctalValues | YML110 | YAML should not use forbidden implicit or explicit octal value. | {octal-values: {forbid-implicit-octal: true, forbid-explicit-octal: true}} |
| CheckTaskSeparation | ANS101 | Single tasks should be separated by an empty line. | |
| CheckMetaMain | ANS102 | Meta file should contain a basic subset of parameters. | author, description, min_ansible_version, platforms, dependencies |
| CheckUniqueNamedTask | ANS103 | Tasks and handlers must be uniquely named within a file. | |
| CheckBraces | ANS104 | YAML should use consistent number of spaces around variables. | {double-braces: max-spaces-inside: 1, min-spaces-inside: 1} |
| CheckScmInSrc | ANS105 | Use SCM key rather than `src: scm+url` in requirements file. | |
| CheckNamedTask | ANS106 | Tasks and handlers must be named. | {named-task: {exclude: [meta, debug, block, include\_\*, import\_\*]}} |
| CheckNameFormat | ANS107 | Name of tasks and handlers must be formatted. | formats: first letter capital |
| CheckCommandInsteadofModule | ANS108 | Commands should not be used in place of modules. | |
| CheckInstallUseLatest | ANS109 | Package managers should not install with state=latest. | |
| CheckShellInsteadCommand | ANS110 | Use Shell only when piping, redirecting or chaining commands. | |
| CheckCommandHasChanges | ANS111 | Commands should be idempotent and only used with some checks. | |
| CheckCompareToEmptyString | ANS112 | Don't compare to "" - use `when: var` or `when: not var`. | |
| CheckCompareToLiteralBool | ANS113 | Don't compare to True/False - use `when: var` or `when: not var`. | |
| CheckLiteralBoolFormat | ANS114 | Literal bools should be consistent. | {literal-bools: [True, False, yes, no]} |
| CheckBecomeUser | ANS115 | Become should be combined with become_user. | |
| CheckFilterSeparation | ANS116 | Jinja2 filters should be separated with spaces. | |
| CheckCommandInsteadOfArgument | ANS117 | Commands should not be used in place of module arguments. | |
| CheckFilePermissionMissing | ANS118 | File permissions unset or incorrect. | |
| CheckFilePermissionOctal | ANS119 | Octal file permissions must contain leading zero or be a string. | |
| CheckGitHasVersion | ANS120 | Git checkouts should use explicit version. | |
| CheckMetaChangeFromDefault | ANS121 | Roles meta/main.yml default values should be changed. | |
| CheckWhenFormat | ANS122 | Don't use Jinja2 in `when`. | |
| CheckNestedJinja | ANS123 | Don't use nested Jinja2 pattern. | |
| CheckLocalAction | ANS124 | Don't use local_action. | |
| CheckRelativeRolePaths | ANS125 | Don't use a relative path in a role. | |
| CheckChangedInWhen | ANS126 | Use handlers instead of `when: changed`. | |
| CheckChangedInWhen | ANS127 | Deprecated bare variables in loops must 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. | |

View File

@ -23,5 +23,5 @@ main:
sub:
- name: Candidates
ref: "/build_rules/candidates"
- name: Standards checks
ref: "/build_rules/standards_check"
- name: Rules
ref: "/build_rules/rule"

162
docs/static/socialmedia.svg vendored Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

After

Width:  |  Height:  |  Size: 28 KiB

924
poetry.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -13,40 +13,37 @@ classifiers = [
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Topic :: Utilities",
"Topic :: Software Development",
]
description = "Reviews ansible playbooks, roles and inventories and suggests improvements."
documentation = "https://ansible-later.geekdocs.de/"
homepage = "https://ansible-later.geekdocs.de/"
include = [
"LICENSE",
]
include = ["LICENSE"]
keywords = ["ansible", "code", "review"]
license = "MIT"
name = "ansible-later"
packages = [
{include = "ansiblelater"},
]
packages = [{ include = "ansiblelater" }]
readme = "README.md"
repository = "https://github.com/thegeeklab/ansible-later/"
version = "0.0.0"
[tool.poetry.dependencies]
PyYAML = "6.0"
ansible = {version = "7.3.0", optional = true}
ansible-core = {version = "2.14.3", optional = true}
anyconfig = "0.13.0"
PyYAML = "6.0.1"
ansible-core = { version = "2.14.16", optional = true }
ansible = { version = "7.7.0", optional = true }
anyconfig = "0.14.0"
appdirs = "1.4.4"
colorama = "0.4.6"
jsonschema = "4.17.3"
jsonschema = "4.22.0"
nested-lookup = "0.2.25"
pathspec = "0.11.1"
pathspec = "0.12.1"
python = "^3.9.0"
python-json-logger = "2.0.7"
toolz = "0.12.0"
toolz = "0.12.1"
unidiff = "0.7.5"
yamllint = "1.29.0"
yamllint = "1.35.1"
[tool.poetry.extras]
ansible = ["ansible"]
@ -56,12 +53,11 @@ ansible-core = ["ansible-core"]
ansible-later = "ansiblelater.__main__:main"
[tool.poetry.group.dev.dependencies]
ruff = "0.0.254"
pytest = "7.2.2"
pytest-mock = "3.10.0"
pytest-cov = "4.0.0"
ruff = "0.4.4"
pytest = "8.2.0"
pytest-mock = "3.14.0"
pytest-cov = "5.0.0"
toml = "0.10.2"
yapf = "0.32.0"
[tool.poetry-dynamic-versioning]
enable = true
@ -69,7 +65,7 @@ style = "semver"
vcs = "git"
[tool.pytest.ini_options]
addopts = "ansiblelater --cov=ansiblelater --cov-report=xml:coverage.xml --cov-report=term --cov-append --no-cov-on-fail"
addopts = "ansiblelater --cov=ansiblelater --cov-report=xml:coverage.xml --cov-report=term --no-cov-on-fail"
filterwarnings = [
"ignore::FutureWarning",
"ignore::DeprecationWarning",
@ -80,22 +76,27 @@ filterwarnings = [
omit = ["**/test/*"]
[build-system]
build-backend = "poetry.core.masonry.api"
build-backend = "poetry_dynamic_versioning.backend"
requires = ["poetry-core>=1.0.0", "poetry-dynamic-versioning"]
[tool.ruff]
exclude = [
".git",
"__pycache__",
"build",
"dist",
"test",
"*.pyc",
"*.egg-info",
".cache",
".eggs",
"env*",
".git",
"__pycache__",
"build",
"dist",
"test",
"*.pyc",
"*.egg-info",
".cache",
".eggs",
"env*",
]
line-length = 99
indent-width = 4
[tool.ruff.lint]
# Explanation of errors
#
# D100: Missing docstring in public module
@ -108,48 +109,41 @@ exclude = [
# D203: One blank line required before class docstring
# D212: Multi-line docstring summary should start at the first line
ignore = [
"D100",
"D101",
"D102",
"D103",
"D105",
"D107",
"D202",
"D203",
"D212",
"UP038",
"D100",
"D101",
"D102",
"D103",
"D105",
"D107",
"D202",
"D203",
"D212",
"UP038",
"RUF012",
]
line-length = 99
select = [
"D",
"E",
"F",
"Q",
"W",
"I",
"S",
"BLE",
"N",
"UP",
"B",
"A",
"C4",
"T20",
"SIM",
"RET",
"ARG",
"ERA",
"RUF",
"D",
"E",
"F",
"Q",
"W",
"I",
"S",
"BLE",
"N",
"UP",
"B",
"A",
"C4",
"T20",
"SIM",
"RET",
"ARG",
"ERA",
"RUF",
]
[tool.ruff.flake8-quotes]
inline-quotes = "double"
[tool.yapf]
based_on_style = "google"
column_limit = 99
dedent_closing_brackets = true
coalesce_brackets = true
split_before_logical_operator = true
indent_dictionary_value = true
allow_split_before_dict_value = false
[tool.ruff.format]
quote-style = "double"
indent-style = "space"
line-ending = "lf"

View File

@ -6,6 +6,12 @@
"description": "Ansible base dependencies",
"matchPackageNames": ["ansible", "ansible-core"],
"separateMinorPatch": true
},
{
"matchManagers": ["woodpecker"],
"matchFileNames": [".woodpecker/test.yml"],
"matchPackageNames": ["docker.io/library/python"],
"enabled": false
}
]
}