mirror of
https://github.com/thegeeklab/ansible-doctor.git
synced 2024-06-29 13:40:54 +02:00
Compare commits
540 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
434f79e9f2 | ||
|
8a027828bf | ||
f1bd734498 | |||
951f4394da | |||
|
41e5ec806b | ||
|
65723bae22 | ||
8e22e87a31 | |||
|
ed113e37ea | ||
|
c33738c8a1 | ||
|
d5e4e6ba33 | ||
08e2178333 | |||
|
42892daa74 | ||
|
ab6c988869 | ||
|
eb2cf69468 | ||
|
abee343056 | ||
|
29320b6b96 | ||
8e042c739e | |||
9b20c11660 | |||
|
5760ee0832 | ||
|
db94c07396 | ||
|
73bbd746d3 | ||
ab0372bef5 | |||
3df7e465db | |||
89c6a11be4 | |||
|
4051d2915d | ||
|
fe4e4e5f7a | ||
|
172e4f4380 | ||
|
fada900568 | ||
|
013f760c8a | ||
|
00adc389a2 | ||
|
84fdc06315 | ||
|
db68e80372 | ||
|
af702628eb | ||
|
81d4e97af6 | ||
|
a33f3c53bb | ||
|
ccc2d249f8 | ||
|
f7ff5fd624 | ||
|
acee6e1285 | ||
|
2375ad118d | ||
|
a2f02527d9 | ||
|
0bf59ac34f | ||
|
94ec1a632b | ||
|
075e1f91ca | ||
4cf63bf2fe | |||
|
e3f797d5e3 | ||
|
3a0c5ae35f | ||
|
9f7f943c93 | ||
|
b38c4aa2b8 | ||
|
ed167d1443 | ||
|
da6fd26c6d | ||
|
522c21f8fc | ||
|
28e39055e3 | ||
|
894965286b | ||
|
c2e0f787ce | ||
|
d524537fd3 | ||
|
a559b654ca | ||
|
aa78adf912 | ||
6e88c18375 | |||
7b9ba09f1d | |||
|
08883952c1 | ||
|
e1ef4937dd | ||
|
571222f6f5 | ||
|
6d50525021 | ||
|
3ade4698e7 | ||
|
ee81c8ee73 | ||
|
03df5bd79b | ||
|
1e32a8f87a | ||
|
dab9bc8691 | ||
e2eaa81c4f | |||
|
732f588aa9 | ||
6619351fbd | |||
|
8f6f444931 | ||
|
49b861cabc | ||
|
59b497d745 | ||
b2f4fd2bd8 | |||
|
864af95606 | ||
|
758c87ee80 | ||
|
9b0edda70a | ||
|
1d32f7548a | ||
|
704cdb9d7c | ||
|
c7f3fe57a0 | ||
|
1270d7cb7d | ||
|
461eeb2d74 | ||
|
0817646004 | ||
|
5242fd882a | ||
|
052c668d92 | ||
1e8a8beef7 | |||
|
c124460c11 | ||
|
38bd53f7bc | ||
|
dbf9c979ac | ||
|
fe12548387 | ||
ae14704b74 | |||
|
2232a12bc8 | ||
78d4c5f44b | |||
c3068a573f | |||
|
505f9b58cc | ||
|
593df92d32 | ||
|
568b91654d | ||
|
4afabc4284 | ||
|
10ff283ec2 | ||
|
1d0ff92bf3 | ||
|
7245a4149c | ||
|
df155dcf8a | ||
2ce29b2bff | |||
1dc53d1970 | |||
2f1f42318b | |||
|
69d682df79 | ||
223b1d8814 | |||
|
70539cc9a2 | ||
|
982e2db2df | ||
|
787c09a741 | ||
|
95c2a6aeaf | ||
|
855f48894a | ||
2270051d0d | |||
|
c0f100b70e | ||
|
41ed10270d | ||
|
eef09f4a42 | ||
|
0460f09627 | ||
6a7ae3011d | |||
0db500b0a8 | |||
dfa10dd209 | |||
|
82398da75d | ||
|
0257b874e9 | ||
|
40b96ae285 | ||
0f65f50e06 | |||
|
9476810896 | ||
|
2eb9aad213 | ||
cacc92f831 | |||
9536cd400d | |||
593a90ff10 | |||
|
a1e3e669d4 | ||
|
3e1eb79a5b | ||
9aba240985 | |||
|
9ba01f9b99 | ||
|
fd30e47e11 | ||
|
8f3053d739 | ||
|
4f42f5e133 | ||
|
0a23dd3539 | ||
|
a4a29c1598 | ||
|
b33a4b0f60 | ||
|
2727ecf6c2 | ||
|
a44b7789e5 | ||
|
972abb8d9e | ||
|
0456c5b870 | ||
|
4d016fcae5 | ||
|
6362c1cbcf | ||
4fc8c8d923 | |||
536c74eac1 | |||
315fc2b521 | |||
|
92fed951b0 | ||
d6ce15fc00 | |||
66f7738139 | |||
|
6e0a07f260 | ||
|
250df17565 | ||
|
9a9f6986b2 | ||
|
b4e06adc2c | ||
|
e78494d258 | ||
|
c7169539e8 | ||
|
0a166c1d39 | ||
|
e1044ecc5e | ||
9862372841 | |||
|
0668d31878 | ||
|
4c608330dc | ||
|
94bbce7bbe | ||
|
66523c1c15 | ||
|
b340dabf24 | ||
c5bbdbc764 | |||
|
9d227aa7c3 | ||
|
eb0a2f2dcd | ||
|
05884eddac | ||
|
7eb4f91a5d | ||
|
036126ee1e | ||
|
4d9366cf7a | ||
|
3d2ba7593d | ||
8e3dd49970 | |||
|
33caaf78c9 | ||
|
2b55025150 | ||
|
7f0bc4ad57 | ||
|
89639f82f9 | ||
|
4e81bceaa0 | ||
|
b979852f71 | ||
0cf7fc9ce2 | |||
|
69afdf56ed | ||
|
2817668e0a | ||
|
220a55e524 | ||
a4a497ca4e | |||
|
aa29881542 | ||
|
54709b26b7 | ||
|
4a3241d0e6 | ||
|
415aabf64a | ||
|
dbb3a89f5f | ||
|
de993d2b8c | ||
|
7e5b22ae03 | ||
|
825f749898 | ||
|
18ca26f7ea | ||
|
fff4fd7d4b | ||
301de5c654 | |||
|
114f87306f | ||
|
ad49089c3c | ||
|
8fcc5ea92b | ||
0b031bcec1 | |||
|
9b913d4032 | ||
|
45547d718b | ||
|
836f13ffa6 | ||
|
49e4a7183a | ||
|
4888d01fae | ||
1144c6f733 | |||
8280a62fb9 | |||
|
584e212f52 | ||
|
8faa599a98 | ||
|
b84e0569a4 | ||
458f9fc577 | |||
|
ea5760fa29 | ||
|
b67896d238 | ||
588f41a3ea | |||
|
b6001fe81d | ||
|
7d1478e3c1 | ||
|
98660389a5 | ||
|
eed89ca661 | ||
|
bfd8c8e549 | ||
|
b14b2b5153 | ||
|
8acb86a6da | ||
|
70f458dce6 | ||
|
6d57d2df59 | ||
34cc4ca062 | |||
|
0057baabca | ||
|
af85c0ec05 | ||
|
4206f228db | ||
|
ed7f28bcfa | ||
|
af340128ba | ||
|
d5237a7b7f | ||
|
370713a178 | ||
9ab2fd1c0a | |||
|
9667ff2773 | ||
4fbaa27668 | |||
|
f72b9573ec | ||
|
0ccec85234 | ||
|
adf9461953 | ||
596b27e696 | |||
|
e4acc05fd9 | ||
|
bc32109897 | ||
|
26d7a3cb8d | ||
d47427e128 | |||
|
3d544b3beb | ||
|
17a448d416 | ||
|
693fcc1359 | ||
|
88fc60af80 | ||
|
c0f674cbe8 | ||
|
578dde6e9a | ||
|
d77fa10686 | ||
|
bc2f11ae0b | ||
71c1679d6c | |||
|
6d293f8b2b | ||
|
7b721dcfa3 | ||
|
997959eda2 | ||
|
152e4e6adb | ||
|
a1ea9e9a6c | ||
|
f86ab8b187 | ||
|
daf94a971c | ||
a95add9fbe | |||
|
bad8c7e7a1 | ||
|
641c51a248 | ||
|
ea9c5b2dfb | ||
|
6b9690646e | ||
8a8647512c | |||
|
c92a50a9fc | ||
a50242e345 | |||
ffe18075c5 | |||
fe62ee77b4 | |||
3aad08646f | |||
|
a761b3c874 | ||
1eed0129ed | |||
|
7382318cf6 | ||
|
158bf214d9 | ||
|
73ed2d1de2 | ||
|
edd40fde71 | ||
|
a5fad3b9ff | ||
|
f7530e9bad | ||
|
37a887af40 | ||
|
6232a5f60f | ||
878cce1791 | |||
|
457e1fdcf9 | ||
|
9df909e8a1 | ||
194a4e1d82 | |||
|
52c0c2859c | ||
4ad870e5c8 | |||
|
32ab32d067 | ||
|
de0630bd93 | ||
|
6501a4bf25 | ||
|
11d7424902 | ||
|
eddab86b52 | ||
dfb4ed99ed | |||
|
d4a7557919 | ||
|
1eb4c478d1 | ||
|
4ee33538a1 | ||
|
dcdf3ab727 | ||
|
2ecede4a1d | ||
|
c901597f5f | ||
|
089cde9ce5 | ||
|
1de2750875 | ||
|
145807ebd4 | ||
|
d26e830420 | ||
|
4fc1e52c4a | ||
e43b33c96d | |||
|
e706eff80c | ||
|
515723f5b5 | ||
|
fa26e18d83 | ||
|
bb89eac39b | ||
|
a117896ae4 | ||
|
40e69fd64c | ||
|
843dff9d78 | ||
|
f0097a6404 | ||
|
71f32898ec | ||
|
f8d496cd9b | ||
96b9304f01 | |||
b0ac8e6ef2 | |||
|
cd78c948f4 | ||
00ac5a2e04 | |||
|
3efac27a0f | ||
|
63da70ced5 | ||
|
1c803d3003 | ||
|
fd38b3d28e | ||
|
d6849bda3a | ||
|
de572ab86e | ||
|
f2e3e3624d | ||
|
067cb3e377 | ||
|
020f9df485 | ||
|
ca06777abc | ||
|
f4f85fee36 | ||
|
a6948d69e2 | ||
|
a03bf5aec6 | ||
|
763389a669 | ||
|
5f48819435 | ||
|
4678e576ef | ||
|
1b6d29cb39 | ||
|
dc56ed8651 | ||
|
8fd547686e | ||
|
0c9ba221de | ||
|
097360f3c1 | ||
|
cf385791c1 | ||
|
438df45cdd | ||
|
74ae444e40 | ||
|
faa851e8db | ||
|
ab3b0d21e8 | ||
|
ced9cce6a8 | ||
|
a3692d3af9 | ||
|
129ac12b23 | ||
|
347ee67204 | ||
|
56411bd4dc | ||
|
406c6ff82b | ||
|
c8312d3aa0 | ||
|
364d1cb506 | ||
|
0f707b70a6 | ||
|
4bfe75d991 | ||
|
53867fcae4 | ||
|
3c17e21524 | ||
|
f09a096329 | ||
|
3b7d4f6501 | ||
|
d184e8bfb3 | ||
|
b37a1d02f6 | ||
|
7ed670a570 | ||
|
a531725380 | ||
|
a100976800 | ||
|
1d579f41d4 | ||
|
66109806d1 | ||
|
dd6f263fb0 | ||
|
57b4e62825 | ||
|
125d2e2782 | ||
|
ca876e0ef2 | ||
|
c4daca384e | ||
|
b0f149e178 | ||
94fcf0c896 | |||
5613be2dca | |||
d51fad44a6 | |||
|
c6c6e4089b | ||
|
36083a20d9 | ||
|
dd10cbed00 | ||
|
e3b88c84e7 | ||
ea2e33993f | |||
|
b396fb40fe | ||
afd4bd2c26 | |||
|
98c1f18377 | ||
|
fd7ead2b5a | ||
|
116a01a5b2 | ||
|
5dcf169171 | ||
|
05561c5b4f | ||
|
ed4c39b5b9 | ||
|
f2f42b3534 | ||
|
27910095a4 | ||
|
5852e0432b | ||
|
0bc72713e9 | ||
|
747c5b0dc6 | ||
|
bc9d9ffdc5 | ||
|
e489f5487e | ||
|
ce9eedb527 | ||
|
aa51eefab0 | ||
|
75119bc460 | ||
89be30578c | |||
|
4e2801d1ce | ||
|
5e270f544a | ||
|
14e7911874 | ||
|
877ae3d3d8 | ||
|
2eb75d6c67 | ||
|
b54937b142 | ||
|
003a699fcd | ||
|
7db37a4ed9 | ||
|
ddb186322c | ||
|
8f81b0dcfd | ||
|
0141cd610c | ||
|
fbd0d349c2 | ||
1b4556dc0d | |||
|
44239e3d86 | ||
b6a2813114 | |||
|
9d26ba0238 | ||
|
2c0b405ecc | ||
|
aee2f9bf8f | ||
|
058cdd5c07 | ||
|
404ff27309 | ||
0de2faf445 | |||
3ceef4365d | |||
|
8e87db349d | ||
|
0b0a890b03 | ||
ce0cd8a7e6 | |||
|
c43c876057 | ||
|
7da5b7596e | ||
|
e9e741b71b | ||
|
18e30cfa9d | ||
|
bcbfdeed17 | ||
|
5322dd2ffd | ||
|
eee5286179 | ||
f6b1f6bae9 | |||
|
4e67f8d912 | ||
|
a0195d945f | ||
|
e145e02351 | ||
|
36effa32a6 | ||
|
785baf8183 | ||
b1a1d16bcc | |||
|
904645cbc9 | ||
|
2088a9d17c | ||
|
11fb666161 | ||
|
8e8938ebf8 | ||
|
00bae94ed8 | ||
|
a6ace7a257 | ||
|
6e91fbf598 | ||
|
a431656b79 | ||
|
6fc4af76b4 | ||
|
c029ac30df | ||
|
06f7275866 | ||
|
f403a53264 | ||
|
28922c0b30 | ||
|
b3d21791a8 | ||
|
f8f769b157 | ||
|
29c95ff12a | ||
924753ab70 | |||
307abeb3cb | |||
|
d9b0178095 | ||
6a4328d6d5 | |||
|
ba2383d894 | ||
|
fad6ab5e39 | ||
15321e9fe9 | |||
|
17563b5ab8 | ||
|
cc24614ea7 | ||
|
426077cece | ||
a3f240ef02 | |||
|
5081356781 | ||
c5106144ad | |||
aac0ef5f20 | |||
|
9bb31ca293 | ||
|
9f9620fd3e | ||
|
d9098c3ae7 | ||
|
3dcf26f72a | ||
|
fee80dcbd0 | ||
|
bc7b427848 | ||
|
83291c1752 | ||
|
2fd00ce2e7 | ||
|
004e6b6434 | ||
b987f978cd | |||
|
2107ee917a | ||
|
34336b313b | ||
|
732a21c470 | ||
|
db8989c4c0 | ||
|
eec2489c06 | ||
|
ede8060806 | ||
|
35e47377a5 | ||
|
4cf2682878 | ||
|
5b0b20bef6 | ||
|
d4cc58bb3e | ||
|
f158a8e30b | ||
|
b9db54d9a5 | ||
|
c8bcfccbec | ||
|
6c02607ebb | ||
02e22f9b03 | |||
|
010492c2c6 | ||
|
3a93c33406 | ||
|
04c4ddf5f7 | ||
|
61e5a44a41 | ||
|
d5f426b9a0 | ||
|
f1fa2e70e2 | ||
|
384d1d10ea | ||
|
e24a025dd5 | ||
015ac120e8 | |||
c6b8b1394e | |||
e41a6d1a78 | |||
|
f44d1164a0 | ||
|
a20e4a64f4 | ||
|
d85c933442 | ||
|
dfef88419d | ||
888b49848c | |||
|
077ae56ad0 | ||
|
73e4812f27 | ||
|
839262c112 | ||
|
35700bc952 | ||
|
16ae0bd156 | ||
|
edba8d9127 | ||
|
ea7e572956 | ||
|
d3e0a7399d | ||
|
e2d045022d | ||
|
a7dc6e6190 | ||
|
383608762f | ||
|
d8e4147e4c | ||
|
5060eaa734 | ||
|
505fc2adf5 | ||
|
075811ab24 | ||
|
b746f40a5c | ||
|
8fc7bd1a42 | ||
|
b492c15250 | ||
|
a96baf8354 | ||
|
8c9929f782 | ||
|
406fd38e3b | ||
|
779c7bde60 | ||
|
c66f4337e4 | ||
8678ab4c00 | |||
e70b5368ab | |||
|
3a39b62d70 | ||
|
17e13654dc | ||
|
cfdaf55a75 | ||
|
24e8146913 | ||
|
c1ae67a02f | ||
|
7284c0459a | ||
|
a80452cb21 |
|
@ -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 "(Co-\\w*-by.*)" .Subject "") | trim }}
|
|
||||||
{{ end }}
|
|
||||||
{{- end -}}
|
|
||||||
|
|
||||||
{{- if .NoteGroups -}}
|
|
||||||
{{ range .NoteGroups -}}
|
|
||||||
### {{ .Title }}
|
|
||||||
|
|
||||||
{{ range .Notes }}
|
|
||||||
{{ .Body }}
|
|
||||||
{{ end }}
|
|
||||||
{{ end -}}
|
|
||||||
{{ end -}}
|
|
||||||
{{ end -}}
|
|
|
@ -1,25 +0,0 @@
|
||||||
style: github
|
|
||||||
template: CHANGELOG.tpl.md
|
|
||||||
info:
|
|
||||||
title: CHANGELOG
|
|
||||||
repository_url: https://github.com/thegeeklab/ansible-doctor
|
|
||||||
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
|
|
|
@ -7,3 +7,7 @@ PyPI
|
||||||
SELinux
|
SELinux
|
||||||
xoxys
|
xoxys
|
||||||
ansible-.+
|
ansible-.+
|
||||||
|
toc
|
||||||
|
GPL-3.0
|
||||||
|
(P|p)re-(C|c)ommit
|
||||||
|
JSON
|
||||||
|
|
492
.drone.jsonnet
492
.drone.jsonnet
|
@ -1,492 +0,0 @@
|
||||||
local PythonVersion(pyversion='3.7') = {
|
|
||||||
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',
|
|
||||||
'poetry version',
|
|
||||||
'poetry run ansible-doctor --help',
|
|
||||||
],
|
|
||||||
depends_on: [
|
|
||||||
'fetch',
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
local PipelineLint = {
|
|
||||||
kind: 'pipeline',
|
|
||||||
name: 'lint',
|
|
||||||
platform: {
|
|
||||||
os: 'linux',
|
|
||||||
arch: 'amd64',
|
|
||||||
},
|
|
||||||
steps: [
|
|
||||||
{
|
|
||||||
name: 'yapf',
|
|
||||||
image: 'python:3.9',
|
|
||||||
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 ./ansibledoctor',
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'flake8',
|
|
||||||
image: 'python:3.9',
|
|
||||||
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 flake8 ./ansibledoctor',
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
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.9',
|
|
||||||
commands: [
|
|
||||||
'git fetch -tq',
|
|
||||||
],
|
|
||||||
},
|
|
||||||
PythonVersion(pyversion='3.7'),
|
|
||||||
PythonVersion(pyversion='3.8'),
|
|
||||||
PythonVersion(pyversion='3.9'),
|
|
||||||
],
|
|
||||||
depends_on: [
|
|
||||||
'lint',
|
|
||||||
],
|
|
||||||
trigger: {
|
|
||||||
ref: ['refs/heads/main', 'refs/tags/**', 'refs/pull/**'],
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
local PipelineSecurity = {
|
|
||||||
kind: 'pipeline',
|
|
||||||
name: 'security',
|
|
||||||
platform: {
|
|
||||||
os: 'linux',
|
|
||||||
arch: 'amd64',
|
|
||||||
},
|
|
||||||
steps: [
|
|
||||||
{
|
|
||||||
name: 'bandit',
|
|
||||||
image: 'python:3.9',
|
|
||||||
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 bandit -r ./ansibledoctor -x ./ansibledoctor/test',
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
depends_on: [
|
|
||||||
'test',
|
|
||||||
],
|
|
||||||
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.9',
|
|
||||||
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.9',
|
|
||||||
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: [
|
|
||||||
'security',
|
|
||||||
],
|
|
||||||
trigger: {
|
|
||||||
ref: ['refs/heads/main', 'refs/tags/**', 'refs/pull/**'],
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
local PipelineBuildContainer(arch='amd64') = {
|
|
||||||
local build = if arch == 'arm' then [{
|
|
||||||
name: 'build',
|
|
||||||
image: 'python:3.9-alpine',
|
|
||||||
commands: [
|
|
||||||
'apk add -Uq --no-cache build-base openssl-dev libffi-dev musl-dev python3-dev git cargo',
|
|
||||||
'git fetch -tq',
|
|
||||||
'pip install poetry poetry-dynamic-versioning -qq',
|
|
||||||
'poetry build',
|
|
||||||
],
|
|
||||||
environment: {
|
|
||||||
CARGO_NET_GIT_FETCH_WITH_CLI: true,
|
|
||||||
},
|
|
||||||
}] else [{
|
|
||||||
name: 'build',
|
|
||||||
image: 'python:3.9',
|
|
||||||
commands: [
|
|
||||||
'git fetch -tq',
|
|
||||||
'pip install poetry poetry-dynamic-versioning -qq',
|
|
||||||
'poetry build',
|
|
||||||
],
|
|
||||||
}],
|
|
||||||
|
|
||||||
kind: 'pipeline',
|
|
||||||
name: 'build-container-' + arch,
|
|
||||||
platform: {
|
|
||||||
os: 'linux',
|
|
||||||
arch: arch,
|
|
||||||
},
|
|
||||||
steps: build + [
|
|
||||||
{
|
|
||||||
name: 'dryrun',
|
|
||||||
image: 'thegeeklab/drone-docker:19',
|
|
||||||
settings: {
|
|
||||||
dry_run: true,
|
|
||||||
dockerfile: 'docker/Dockerfile.' + arch,
|
|
||||||
repo: 'thegeeklab/${DRONE_REPO_NAME}',
|
|
||||||
username: { from_secret: 'docker_username' },
|
|
||||||
password: { from_secret: 'docker_password' },
|
|
||||||
},
|
|
||||||
depends_on: ['build'],
|
|
||||||
when: {
|
|
||||||
ref: ['refs/pull/**'],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'publish-dockerhub',
|
|
||||||
image: 'thegeeklab/drone-docker:19',
|
|
||||||
settings: {
|
|
||||||
auto_tag: true,
|
|
||||||
auto_tag_suffix: arch,
|
|
||||||
dockerfile: 'docker/Dockerfile.' + arch,
|
|
||||||
repo: 'thegeeklab/${DRONE_REPO_NAME}',
|
|
||||||
username: { from_secret: 'docker_username' },
|
|
||||||
password: { from_secret: 'docker_password' },
|
|
||||||
},
|
|
||||||
when: {
|
|
||||||
ref: ['refs/heads/main', 'refs/tags/**'],
|
|
||||||
},
|
|
||||||
depends_on: ['dryrun'],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'publish-quay',
|
|
||||||
image: 'thegeeklab/drone-docker:19',
|
|
||||||
settings: {
|
|
||||||
auto_tag: true,
|
|
||||||
auto_tag_suffix: arch,
|
|
||||||
dockerfile: 'docker/Dockerfile.' + arch,
|
|
||||||
registry: 'quay.io',
|
|
||||||
repo: 'quay.io/thegeeklab/${DRONE_REPO_NAME}',
|
|
||||||
username: { from_secret: 'quay_username' },
|
|
||||||
password: { from_secret: 'quay_password' },
|
|
||||||
},
|
|
||||||
when: {
|
|
||||||
ref: ['refs/heads/main', 'refs/tags/**'],
|
|
||||||
},
|
|
||||||
depends_on: ['dryrun'],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
depends_on: [
|
|
||||||
'security',
|
|
||||||
],
|
|
||||||
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: 'node:lts-alpine',
|
|
||||||
commands: [
|
|
||||||
'npm install -g spellchecker-cli',
|
|
||||||
"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.83.1',
|
|
||||||
commands: [
|
|
||||||
'hugo -s docs/ -b http://localhost/',
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'link-validation',
|
|
||||||
image: 'thegeeklab/link-validator',
|
|
||||||
commands: [
|
|
||||||
'link-validator -ro',
|
|
||||||
],
|
|
||||||
environment: {
|
|
||||||
LINK_VALIDATOR_BASE_DIR: 'docs/public',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'build',
|
|
||||||
image: 'thegeeklab/hugo:0.83.1',
|
|
||||||
commands: [
|
|
||||||
'hugo -s docs/',
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'beautify',
|
|
||||||
image: 'node:lts-alpine',
|
|
||||||
commands: [
|
|
||||||
'npm install -g js-beautify',
|
|
||||||
"html-beautify -r -f 'docs/public/**/*.html'",
|
|
||||||
],
|
|
||||||
environment: {
|
|
||||||
FORCE_COLOR: true,
|
|
||||||
NPM_CONFIG_LOGLEVEL: 'error',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'publish',
|
|
||||||
image: 'plugins/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-amd64',
|
|
||||||
'build-container-arm64',
|
|
||||||
'build-container-arm',
|
|
||||||
],
|
|
||||||
trigger: {
|
|
||||||
ref: ['refs/heads/main', 'refs/tags/**', 'refs/pull/**'],
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
local PipelineNotifications = {
|
|
||||||
kind: 'pipeline',
|
|
||||||
name: 'notifications',
|
|
||||||
platform: {
|
|
||||||
os: 'linux',
|
|
||||||
arch: 'amd64',
|
|
||||||
},
|
|
||||||
steps: [
|
|
||||||
{
|
|
||||||
image: 'plugins/manifest',
|
|
||||||
name: 'manifest-dockerhub',
|
|
||||||
settings: {
|
|
||||||
ignore_missing: true,
|
|
||||||
auto_tag: true,
|
|
||||||
username: { from_secret: 'docker_username' },
|
|
||||||
password: { from_secret: 'docker_password' },
|
|
||||||
spec: 'docker/manifest.tmpl',
|
|
||||||
},
|
|
||||||
when: {
|
|
||||||
status: ['success'],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
image: 'plugins/manifest',
|
|
||||||
name: 'manifest-quay',
|
|
||||||
settings: {
|
|
||||||
ignore_missing: true,
|
|
||||||
auto_tag: true,
|
|
||||||
username: { from_secret: 'quay_username' },
|
|
||||||
password: { from_secret: 'quay_password' },
|
|
||||||
spec: 'docker/manifest-quay.tmpl',
|
|
||||||
},
|
|
||||||
when: {
|
|
||||||
status: ['success'],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'pushrm-dockerhub',
|
|
||||||
pull: 'always',
|
|
||||||
image: 'chko/docker-pushrm:1',
|
|
||||||
environment: {
|
|
||||||
DOCKER_PASS: {
|
|
||||||
from_secret: 'docker_password',
|
|
||||||
},
|
|
||||||
DOCKER_USER: {
|
|
||||||
from_secret: 'docker_username',
|
|
||||||
},
|
|
||||||
PUSHRM_FILE: 'README.md',
|
|
||||||
PUSHRM_SHORT: 'Annotation based documentation for your Ansible roles',
|
|
||||||
PUSHRM_TARGET: 'thegeeklab/${DRONE_REPO_NAME}',
|
|
||||||
},
|
|
||||||
when: {
|
|
||||||
status: ['success'],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'pushrm-quay',
|
|
||||||
pull: 'always',
|
|
||||||
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 }}){{/if}} 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,
|
|
||||||
PipelineSecurity,
|
|
||||||
PipelineBuildPackage,
|
|
||||||
PipelineBuildContainer(arch='amd64'),
|
|
||||||
PipelineBuildContainer(arch='arm64'),
|
|
||||||
PipelineBuildContainer(arch='arm'),
|
|
||||||
PipelineDocs,
|
|
||||||
PipelineNotifications,
|
|
||||||
]
|
|
622
.drone.yml
622
.drone.yml
|
@ -1,622 +0,0 @@
|
||||||
---
|
|
||||||
kind: pipeline
|
|
||||||
name: lint
|
|
||||||
|
|
||||||
platform:
|
|
||||||
os: linux
|
|
||||||
arch: amd64
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: yapf
|
|
||||||
image: python:3.9
|
|
||||||
commands:
|
|
||||||
- git fetch -tq
|
|
||||||
- pip install poetry poetry-dynamic-versioning -qq
|
|
||||||
- poetry config experimental.new-installer false
|
|
||||||
- poetry install
|
|
||||||
- poetry run yapf -dr ./ansibledoctor
|
|
||||||
environment:
|
|
||||||
PY_COLORS: 1
|
|
||||||
|
|
||||||
- name: flake8
|
|
||||||
image: python:3.9
|
|
||||||
commands:
|
|
||||||
- git fetch -tq
|
|
||||||
- pip install poetry poetry-dynamic-versioning -qq
|
|
||||||
- poetry config experimental.new-installer false
|
|
||||||
- poetry install
|
|
||||||
- poetry run flake8 ./ansibledoctor
|
|
||||||
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.9
|
|
||||||
commands:
|
|
||||||
- git fetch -tq
|
|
||||||
|
|
||||||
- name: python37-pytest
|
|
||||||
image: python:3.7
|
|
||||||
commands:
|
|
||||||
- pip install poetry poetry-dynamic-versioning -qq
|
|
||||||
- poetry config experimental.new-installer false
|
|
||||||
- poetry install
|
|
||||||
- poetry version
|
|
||||||
- poetry run ansible-doctor --help
|
|
||||||
environment:
|
|
||||||
PY_COLORS: 1
|
|
||||||
depends_on:
|
|
||||||
- fetch
|
|
||||||
|
|
||||||
- name: python38-pytest
|
|
||||||
image: python:3.8
|
|
||||||
commands:
|
|
||||||
- pip install poetry poetry-dynamic-versioning -qq
|
|
||||||
- poetry config experimental.new-installer false
|
|
||||||
- poetry install
|
|
||||||
- poetry version
|
|
||||||
- poetry run ansible-doctor --help
|
|
||||||
environment:
|
|
||||||
PY_COLORS: 1
|
|
||||||
depends_on:
|
|
||||||
- fetch
|
|
||||||
|
|
||||||
- name: python39-pytest
|
|
||||||
image: python:3.9
|
|
||||||
commands:
|
|
||||||
- pip install poetry poetry-dynamic-versioning -qq
|
|
||||||
- poetry config experimental.new-installer false
|
|
||||||
- poetry install
|
|
||||||
- poetry version
|
|
||||||
- poetry run ansible-doctor --help
|
|
||||||
environment:
|
|
||||||
PY_COLORS: 1
|
|
||||||
depends_on:
|
|
||||||
- fetch
|
|
||||||
|
|
||||||
trigger:
|
|
||||||
ref:
|
|
||||||
- refs/heads/main
|
|
||||||
- refs/tags/**
|
|
||||||
- refs/pull/**
|
|
||||||
|
|
||||||
depends_on:
|
|
||||||
- lint
|
|
||||||
|
|
||||||
---
|
|
||||||
kind: pipeline
|
|
||||||
name: security
|
|
||||||
|
|
||||||
platform:
|
|
||||||
os: linux
|
|
||||||
arch: amd64
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: bandit
|
|
||||||
image: python:3.9
|
|
||||||
commands:
|
|
||||||
- git fetch -tq
|
|
||||||
- pip install poetry poetry-dynamic-versioning -qq
|
|
||||||
- poetry config experimental.new-installer false
|
|
||||||
- poetry install
|
|
||||||
- poetry run bandit -r ./ansibledoctor -x ./ansibledoctor/test
|
|
||||||
environment:
|
|
||||||
PY_COLORS: 1
|
|
||||||
|
|
||||||
trigger:
|
|
||||||
ref:
|
|
||||||
- refs/heads/main
|
|
||||||
- refs/tags/**
|
|
||||||
- refs/pull/**
|
|
||||||
|
|
||||||
depends_on:
|
|
||||||
- test
|
|
||||||
|
|
||||||
---
|
|
||||||
kind: pipeline
|
|
||||||
name: build-package
|
|
||||||
|
|
||||||
platform:
|
|
||||||
os: linux
|
|
||||||
arch: amd64
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: build
|
|
||||||
image: python:3.9
|
|
||||||
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.9
|
|
||||||
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:
|
|
||||||
- security
|
|
||||||
|
|
||||||
---
|
|
||||||
kind: pipeline
|
|
||||||
name: build-container-amd64
|
|
||||||
|
|
||||||
platform:
|
|
||||||
os: linux
|
|
||||||
arch: amd64
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: build
|
|
||||||
image: python:3.9
|
|
||||||
commands:
|
|
||||||
- git fetch -tq
|
|
||||||
- pip install poetry poetry-dynamic-versioning -qq
|
|
||||||
- poetry build
|
|
||||||
|
|
||||||
- name: dryrun
|
|
||||||
image: thegeeklab/drone-docker:19
|
|
||||||
settings:
|
|
||||||
dockerfile: docker/Dockerfile.amd64
|
|
||||||
dry_run: true
|
|
||||||
password:
|
|
||||||
from_secret: docker_password
|
|
||||||
repo: thegeeklab/${DRONE_REPO_NAME}
|
|
||||||
username:
|
|
||||||
from_secret: docker_username
|
|
||||||
when:
|
|
||||||
ref:
|
|
||||||
- refs/pull/**
|
|
||||||
depends_on:
|
|
||||||
- build
|
|
||||||
|
|
||||||
- name: publish-dockerhub
|
|
||||||
image: thegeeklab/drone-docker:19
|
|
||||||
settings:
|
|
||||||
auto_tag: true
|
|
||||||
auto_tag_suffix: amd64
|
|
||||||
dockerfile: docker/Dockerfile.amd64
|
|
||||||
password:
|
|
||||||
from_secret: docker_password
|
|
||||||
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:19
|
|
||||||
settings:
|
|
||||||
auto_tag: true
|
|
||||||
auto_tag_suffix: amd64
|
|
||||||
dockerfile: docker/Dockerfile.amd64
|
|
||||||
password:
|
|
||||||
from_secret: quay_password
|
|
||||||
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:
|
|
||||||
- security
|
|
||||||
|
|
||||||
---
|
|
||||||
kind: pipeline
|
|
||||||
name: build-container-arm64
|
|
||||||
|
|
||||||
platform:
|
|
||||||
os: linux
|
|
||||||
arch: arm64
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: build
|
|
||||||
image: python:3.9
|
|
||||||
commands:
|
|
||||||
- git fetch -tq
|
|
||||||
- pip install poetry poetry-dynamic-versioning -qq
|
|
||||||
- poetry build
|
|
||||||
|
|
||||||
- name: dryrun
|
|
||||||
image: thegeeklab/drone-docker:19
|
|
||||||
settings:
|
|
||||||
dockerfile: docker/Dockerfile.arm64
|
|
||||||
dry_run: true
|
|
||||||
password:
|
|
||||||
from_secret: docker_password
|
|
||||||
repo: thegeeklab/${DRONE_REPO_NAME}
|
|
||||||
username:
|
|
||||||
from_secret: docker_username
|
|
||||||
when:
|
|
||||||
ref:
|
|
||||||
- refs/pull/**
|
|
||||||
depends_on:
|
|
||||||
- build
|
|
||||||
|
|
||||||
- name: publish-dockerhub
|
|
||||||
image: thegeeklab/drone-docker:19
|
|
||||||
settings:
|
|
||||||
auto_tag: true
|
|
||||||
auto_tag_suffix: arm64
|
|
||||||
dockerfile: docker/Dockerfile.arm64
|
|
||||||
password:
|
|
||||||
from_secret: docker_password
|
|
||||||
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:19
|
|
||||||
settings:
|
|
||||||
auto_tag: true
|
|
||||||
auto_tag_suffix: arm64
|
|
||||||
dockerfile: docker/Dockerfile.arm64
|
|
||||||
password:
|
|
||||||
from_secret: quay_password
|
|
||||||
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:
|
|
||||||
- security
|
|
||||||
|
|
||||||
---
|
|
||||||
kind: pipeline
|
|
||||||
name: build-container-arm
|
|
||||||
|
|
||||||
platform:
|
|
||||||
os: linux
|
|
||||||
arch: arm
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: build
|
|
||||||
image: python:3.9-alpine
|
|
||||||
commands:
|
|
||||||
- apk add -Uq --no-cache build-base openssl-dev libffi-dev musl-dev python3-dev git cargo
|
|
||||||
- git fetch -tq
|
|
||||||
- pip install poetry poetry-dynamic-versioning -qq
|
|
||||||
- poetry build
|
|
||||||
environment:
|
|
||||||
CARGO_NET_GIT_FETCH_WITH_CLI: true
|
|
||||||
|
|
||||||
- name: dryrun
|
|
||||||
image: thegeeklab/drone-docker:19
|
|
||||||
settings:
|
|
||||||
dockerfile: docker/Dockerfile.arm
|
|
||||||
dry_run: true
|
|
||||||
password:
|
|
||||||
from_secret: docker_password
|
|
||||||
repo: thegeeklab/${DRONE_REPO_NAME}
|
|
||||||
username:
|
|
||||||
from_secret: docker_username
|
|
||||||
when:
|
|
||||||
ref:
|
|
||||||
- refs/pull/**
|
|
||||||
depends_on:
|
|
||||||
- build
|
|
||||||
|
|
||||||
- name: publish-dockerhub
|
|
||||||
image: thegeeklab/drone-docker:19
|
|
||||||
settings:
|
|
||||||
auto_tag: true
|
|
||||||
auto_tag_suffix: arm
|
|
||||||
dockerfile: docker/Dockerfile.arm
|
|
||||||
password:
|
|
||||||
from_secret: docker_password
|
|
||||||
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:19
|
|
||||||
settings:
|
|
||||||
auto_tag: true
|
|
||||||
auto_tag_suffix: arm
|
|
||||||
dockerfile: docker/Dockerfile.arm
|
|
||||||
password:
|
|
||||||
from_secret: quay_password
|
|
||||||
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:
|
|
||||||
- security
|
|
||||||
|
|
||||||
---
|
|
||||||
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: node:lts-alpine
|
|
||||||
commands:
|
|
||||||
- npm install -g spellchecker-cli
|
|
||||||
- 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.83.1
|
|
||||||
commands:
|
|
||||||
- hugo -s docs/ -b http://localhost/
|
|
||||||
|
|
||||||
- name: link-validation
|
|
||||||
image: thegeeklab/link-validator
|
|
||||||
commands:
|
|
||||||
- link-validator -ro
|
|
||||||
environment:
|
|
||||||
LINK_VALIDATOR_BASE_DIR: docs/public
|
|
||||||
|
|
||||||
- name: build
|
|
||||||
image: thegeeklab/hugo:0.83.1
|
|
||||||
commands:
|
|
||||||
- hugo -s docs/
|
|
||||||
|
|
||||||
- name: beautify
|
|
||||||
image: node:lts-alpine
|
|
||||||
commands:
|
|
||||||
- npm install -g js-beautify
|
|
||||||
- html-beautify -r -f 'docs/public/**/*.html'
|
|
||||||
environment:
|
|
||||||
FORCE_COLOR: true
|
|
||||||
NPM_CONFIG_LOGLEVEL: error
|
|
||||||
|
|
||||||
- name: publish
|
|
||||||
image: plugins/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-amd64
|
|
||||||
- build-container-arm64
|
|
||||||
- build-container-arm
|
|
||||||
|
|
||||||
---
|
|
||||||
kind: pipeline
|
|
||||||
name: notifications
|
|
||||||
|
|
||||||
platform:
|
|
||||||
os: linux
|
|
||||||
arch: amd64
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: manifest-dockerhub
|
|
||||||
image: plugins/manifest
|
|
||||||
settings:
|
|
||||||
auto_tag: true
|
|
||||||
ignore_missing: true
|
|
||||||
password:
|
|
||||||
from_secret: docker_password
|
|
||||||
spec: docker/manifest.tmpl
|
|
||||||
username:
|
|
||||||
from_secret: docker_username
|
|
||||||
when:
|
|
||||||
status:
|
|
||||||
- success
|
|
||||||
|
|
||||||
- name: manifest-quay
|
|
||||||
image: plugins/manifest
|
|
||||||
settings:
|
|
||||||
auto_tag: true
|
|
||||||
ignore_missing: true
|
|
||||||
password:
|
|
||||||
from_secret: quay_password
|
|
||||||
spec: docker/manifest-quay.tmpl
|
|
||||||
username:
|
|
||||||
from_secret: quay_username
|
|
||||||
when:
|
|
||||||
status:
|
|
||||||
- success
|
|
||||||
|
|
||||||
- name: pushrm-dockerhub
|
|
||||||
pull: always
|
|
||||||
image: chko/docker-pushrm:1
|
|
||||||
environment:
|
|
||||||
DOCKER_PASS:
|
|
||||||
from_secret: docker_password
|
|
||||||
DOCKER_USER:
|
|
||||||
from_secret: docker_username
|
|
||||||
PUSHRM_FILE: README.md
|
|
||||||
PUSHRM_SHORT: Annotation based documentation for your Ansible roles
|
|
||||||
PUSHRM_TARGET: thegeeklab/${DRONE_REPO_NAME}
|
|
||||||
when:
|
|
||||||
status:
|
|
||||||
- success
|
|
||||||
|
|
||||||
- name: pushrm-quay
|
|
||||||
pull: always
|
|
||||||
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 }}){{/if}} 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: 580d5097f554e354260dcc12ecb4294851ea90d4956c3d0c48b1415b0e9667c8
|
|
||||||
|
|
||||||
...
|
|
9
.github/settings.yml
vendored
9
.github/settings.yml
vendored
|
@ -52,6 +52,11 @@ branches:
|
||||||
required_status_checks:
|
required_status_checks:
|
||||||
strict: false
|
strict: false
|
||||||
contexts:
|
contexts:
|
||||||
- continuous-integration/drone/pr
|
- ci/woodpecker/pr/lint
|
||||||
enforce_admins: null
|
- 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
|
restrictions: null
|
||||||
|
|
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -106,6 +106,8 @@ pip-wheel-metadata
|
||||||
docs/themes/
|
docs/themes/
|
||||||
docs/public/
|
docs/public/
|
||||||
resources/_gen/
|
resources/_gen/
|
||||||
|
.hugo_build.lock
|
||||||
|
|
||||||
# Misc
|
# Misc
|
||||||
CHANGELOG.md
|
CHANGELOG.md
|
||||||
|
.ruff_cache
|
||||||
|
|
47
.gitsv/config.yml
Normal file
47
.gitsv/config.yml
Normal 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
1
.lycheeignore
Normal file
|
@ -0,0 +1 @@
|
||||||
|
https://hub.docker.com/r/thegeeklab/*
|
|
@ -2,5 +2,9 @@
|
||||||
default: True
|
default: True
|
||||||
MD013: False
|
MD013: False
|
||||||
MD041: False
|
MD041: False
|
||||||
|
MD024: False
|
||||||
MD004:
|
MD004:
|
||||||
style: dash
|
style: dash
|
||||||
|
MD033:
|
||||||
|
allowed_elements:
|
||||||
|
- "br"
|
||||||
|
|
10
.pre-commit-hooks.yaml
Normal file
10
.pre-commit-hooks.yaml
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
---
|
||||||
|
- id: ansible-doctor
|
||||||
|
name: ansible-doctor
|
||||||
|
description: Create annotation based documentation for your Ansible roles.
|
||||||
|
entry: ansible-doctor -f -qqq
|
||||||
|
language: python
|
||||||
|
pass_filenames: False
|
||||||
|
always_run: True
|
||||||
|
additional_dependencies:
|
||||||
|
- .[ansible-core]
|
|
@ -1,2 +1,2 @@
|
||||||
.drone.yml
|
|
||||||
*.tpl.md
|
*.tpl.md
|
||||||
|
LICENSE
|
||||||
|
|
73
.woodpecker/build-container.yml
Normal file
73
.woodpecker/build-container.yml
Normal 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
|
56
.woodpecker/build-package.yml
Normal file
56
.woodpecker/build-package.yml
Normal 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
100
.woodpecker/docs.yml
Normal 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.127.0
|
||||||
|
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: Annotation based documentation for your Ansible roles
|
||||||
|
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
|
34
.woodpecker/lint.yml
Normal file
34
.woodpecker/lint.yml
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
---
|
||||||
|
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 -E ansible-core
|
||||||
|
- 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"
|
||||||
|
|
||||||
|
- name: check-jinja
|
||||||
|
image: docker.io/library/python:3.12
|
||||||
|
commands:
|
||||||
|
- pip install poetry poetry-dynamic-versioning -qq
|
||||||
|
- poetry install -E ansible-core
|
||||||
|
- poetry run j2lint ansibledoctor/templates/ -i jinja-statements-indentation jinja-statements-delimiter
|
||||||
|
environment:
|
||||||
|
PY_COLORS: "1"
|
26
.woodpecker/notify.yml
Normal file
26
.woodpecker/notify.yml
Normal 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
|
34
.woodpecker/test.yml
Normal file
34
.woodpecker/test.yml
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
---
|
||||||
|
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 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
|
|
@ -3,7 +3,7 @@
|
||||||
## Security
|
## Security
|
||||||
|
|
||||||
If you think you have found a **security issue**, please do not mention it in this repository.
|
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
|
## Bug Reports and Feature Requests
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
FROM python:3.9-alpine@sha256:55fbe1ef2c62a1607ab702ac14111d9981d6b4f2a62351b75ffe34622f18dc3f
|
FROM python:3.12-alpine@sha256:ff870bf7c2bb546419aaea570f0a1c28c8103b78743a2b8030e9e97391ddf81b
|
||||||
|
|
||||||
LABEL maintainer="Robert Kaussow <mail@thegeeklab.de>"
|
LABEL maintainer="Robert Kaussow <mail@thegeeklab.de>"
|
||||||
LABEL org.opencontainers.image.authors="Robert Kaussow <mail@thegeeklab.de>"
|
LABEL org.opencontainers.image.authors="Robert Kaussow <mail@thegeeklab.de>"
|
||||||
|
@ -8,12 +8,13 @@ LABEL org.opencontainers.image.source="https://github.com/thegeeklab/ansible-doc
|
||||||
LABEL org.opencontainers.image.documentation="https://ansible-doctor.geekdocs.de/"
|
LABEL org.opencontainers.image.documentation="https://ansible-doctor.geekdocs.de/"
|
||||||
|
|
||||||
ENV PY_COLORS=1
|
ENV PY_COLORS=1
|
||||||
|
ENV TZ=UTC
|
||||||
|
|
||||||
ADD dist/ansible_doctor-*.whl /
|
ADD dist/ansible_doctor-*.whl /
|
||||||
|
|
||||||
RUN apk update && \
|
RUN apk --update add --virtual .build-deps build-base libffi-dev openssl-dev git && \
|
||||||
pip install --upgrade --no-cache-dir pip && \
|
pip install --upgrade --no-cache-dir pip && \
|
||||||
pip install --no-cache-dir $(find / -name "ansible_doctor-*.whl") && \
|
pip install --no-cache-dir $(find / -name "ansible_doctor-*.whl")[ansible-core] && \
|
||||||
rm -f ansible_doctor-*.whl && \
|
rm -f ansible_doctor-*.whl && \
|
||||||
rm -rf /var/cache/apk/* && \
|
rm -rf /var/cache/apk/* && \
|
||||||
rm -rf /root/.cache/
|
rm -rf /root/.cache/
|
2
LICENSE
2
LICENSE
|
@ -1,7 +1,7 @@
|
||||||
GNU GENERAL PUBLIC LICENSE
|
GNU GENERAL PUBLIC LICENSE
|
||||||
Version 3, 29 June 2007
|
Version 3, 29 June 2007
|
||||||
|
|
||||||
Copyright (C) 2021 Robert Kaussow <mail@thegeeklab.de>
|
Copyright (C) 2022 Robert Kaussow <mail@thegeeklab.de>
|
||||||
Everyone is permitted to copy and distribute verbatim copies
|
Everyone is permitted to copy and distribute verbatim copies
|
||||||
of this license document, but changing it is not allowed.
|
of this license document, but changing it is not allowed.
|
||||||
|
|
||||||
|
|
4
Makefile
4
Makefile
|
@ -1,5 +1,5 @@
|
||||||
# renovate: datasource=github-releases depName=thegeeklab/hugo-geekdoc
|
# renovate: datasource=github-releases depName=thegeeklab/hugo-geekdoc
|
||||||
THEME_VERSION := v0.19.1
|
THEME_VERSION := v0.46.0
|
||||||
THEME := hugo-geekdoc
|
THEME := hugo-geekdoc
|
||||||
BASEDIR := docs
|
BASEDIR := docs
|
||||||
THEMEDIR := $(BASEDIR)/themes
|
THEMEDIR := $(BASEDIR)/themes
|
||||||
|
@ -17,4 +17,4 @@ doc-assets:
|
||||||
|
|
||||||
.PHONY: clean
|
.PHONY: clean
|
||||||
clean:
|
clean:
|
||||||
rm -rf $(THEMEDIR) && \
|
rm -rf $(THEMEDIR)
|
||||||
|
|
10
README.md
10
README.md
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
Annotation based documentation for your Ansible roles
|
Annotation based documentation for your Ansible roles
|
||||||
|
|
||||||
[![Build Status](https://img.shields.io/drone/build/thegeeklab/ansible-doctor?logo=drone&server=https%3A%2F%2Fdrone.thegeeklab.de)](https://drone.thegeeklab.de/thegeeklab/ansible-doctor)
|
[![Build Status](https://ci.thegeeklab.de/api/badges/thegeeklab/ansible-doctor/status.svg)](https://ci.thegeeklab.de/repos/thegeeklab/ansible-doctor)
|
||||||
[![Docker Hub](https://img.shields.io/badge/dockerhub-latest-blue.svg?logo=docker&logoColor=white)](https://hub.docker.com/r/thegeeklab/ansible-doctor)
|
[![Docker Hub](https://img.shields.io/badge/dockerhub-latest-blue.svg?logo=docker&logoColor=white)](https://hub.docker.com/r/thegeeklab/ansible-doctor)
|
||||||
[![Quay.io](https://img.shields.io/badge/quay-latest-blue.svg?logo=docker&logoColor=white)](https://quay.io/repository/thegeeklab/ansible-doctor)
|
[![Quay.io](https://img.shields.io/badge/quay-latest-blue.svg?logo=docker&logoColor=white)](https://quay.io/repository/thegeeklab/ansible-doctor)
|
||||||
[![Python Version](https://img.shields.io/pypi/pyversions/ansible-doctor.svg)](https://pypi.org/project/ansible-doctor/)
|
[![Python Version](https://img.shields.io/pypi/pyversions/ansible-doctor.svg)](https://pypi.org/project/ansible-doctor/)
|
||||||
|
@ -14,15 +14,15 @@ Annotation based documentation for your Ansible roles
|
||||||
|
|
||||||
This project is based on the idea (and at some parts on the code) of [ansible-autodoc](https://github.com/AndresBott/ansible-autodoc) by Andres Bott so credits goes to him for his work.
|
This project is based on the idea (and at some parts on the code) of [ansible-autodoc](https://github.com/AndresBott/ansible-autodoc) by Andres Bott so credits goes to him for his work.
|
||||||
|
|
||||||
_ansible-doctor_ is a simple annotation like documentation generator based on Jinja2 templates. While _ansible-doctor_ comes with a default template called `readme`, it is also possible to write your own templates. This gives you the ability to customize the output and render the data to every format you like (e.g. HTML or XML).
|
_ansible-doctor_ is a simple annotation like documentation generator based on Jinja2 templates. While _ansible-doctor_ comes with a default template called `readme`, it is also possible to write custom templates to customize the output or render the data to other formats like HTML or XML as well.
|
||||||
|
|
||||||
_ansible-doctor_ is designed to work within your CI pipeline to complete your testing and deployment workflow. Releases are available as Python Packages on [GitHub](https://github.com/thegeeklab/ansible-doctor/releases) or [PyPI](https://pypi.org/project/ansible-doctor/) and as Docker Image on [Docker Hub](https://hub.docker.com/r/thegeeklab/ansible-doctor).
|
_ansible-doctor_ is designed to work within a CI pipeline to complete the existing testing and deployment workflow. Releases are available as Python Packages on [GitHub](https://github.com/thegeeklab/ansible-doctor/releases) or [PyPI](https://pypi.org/project/ansible-doctor/) and as Docker Image on [Docker Hub](https://hub.docker.com/r/thegeeklab/ansible-doctor).
|
||||||
|
|
||||||
You can find the full documentation at [https://ansible-doctor.geekdocs.de](https://ansible-doctor.geekdocs.de/).
|
The full documentation is available at [https://ansible-doctor.geekdocs.de](https://ansible-doctor.geekdocs.de/).
|
||||||
|
|
||||||
## Contributors
|
## Contributors
|
||||||
|
|
||||||
Special thanks goes to all [contributors](https://github.com/thegeeklab/ansible-doctor/graphs/contributors). If you would like to contribute,
|
Special thanks to all [contributors](https://github.com/thegeeklab/ansible-doctor/graphs/contributors). If you would like to contribute,
|
||||||
please see the [instructions](https://github.com/thegeeklab/ansible-doctor/blob/main/CONTRIBUTING.md).
|
please see the [instructions](https://github.com/thegeeklab/ansible-doctor/blob/main/CONTRIBUTING.md).
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
|
@ -1,3 +1,10 @@
|
||||||
"""Default package."""
|
"""Provide version information."""
|
||||||
|
|
||||||
__version__ = "0.0.0"
|
__version__ = "0.0.0"
|
||||||
|
|
||||||
|
import sys
|
||||||
|
|
||||||
|
try:
|
||||||
|
import ansible # noqa
|
||||||
|
except ImportError:
|
||||||
|
sys.exit("ERROR: Python requirements are missing: 'ansible-core' not found.")
|
||||||
|
|
|
@ -6,9 +6,10 @@ import re
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
|
|
||||||
import anyconfig
|
import anyconfig
|
||||||
|
import structlog
|
||||||
|
|
||||||
from ansibledoctor.config import SingleConfig
|
from ansibledoctor.config import SingleConfig
|
||||||
from ansibledoctor.utils import SingleLog
|
from ansibledoctor.utils import _split_string, sysexit_with_message
|
||||||
|
|
||||||
|
|
||||||
class AnnotationItem:
|
class AnnotationItem:
|
||||||
|
@ -20,9 +21,11 @@ class AnnotationItem:
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
"""Beautify object string output."""
|
"""Beautify object string output."""
|
||||||
for key in self.data.keys():
|
for key in self.data:
|
||||||
for sub in self.data.get(key):
|
for sub in self.data.get(key):
|
||||||
return "AnnotationItem({}: {})".format(key, sub)
|
return f"AnnotationItem({key}: {sub})"
|
||||||
|
|
||||||
|
return "None"
|
||||||
|
|
||||||
def get_obj(self):
|
def get_obj(self):
|
||||||
return self.data
|
return self.data
|
||||||
|
@ -35,13 +38,12 @@ class Annotation:
|
||||||
self._all_items = defaultdict(dict)
|
self._all_items = defaultdict(dict)
|
||||||
self._file_handler = None
|
self._file_handler = None
|
||||||
self.config = SingleConfig()
|
self.config = SingleConfig()
|
||||||
self.log = SingleLog()
|
self.log = structlog.get_logger()
|
||||||
self.logger = self.log.logger
|
|
||||||
self._files_registry = files_registry
|
self._files_registry = files_registry
|
||||||
|
|
||||||
self._all_annotations = self.config.get_annotations_definition()
|
self._all_annotations = self.config.get_annotations_definition()
|
||||||
|
|
||||||
if name in self._all_annotations.keys():
|
if name in self._all_annotations:
|
||||||
self._annotation_definition = self._all_annotations[name]
|
self._annotation_definition = self._all_annotations[name]
|
||||||
|
|
||||||
if self._annotation_definition is not None:
|
if self._annotation_definition is not None:
|
||||||
|
@ -53,8 +55,7 @@ class Annotation:
|
||||||
def _find_annotation(self):
|
def _find_annotation(self):
|
||||||
regex = r"(\#\ *\@" + self._annotation_definition["name"] + r"\ +.*)"
|
regex = r"(\#\ *\@" + self._annotation_definition["name"] + r"\ +.*)"
|
||||||
for rfile in self._files_registry.get_files():
|
for rfile in self._files_registry.get_files():
|
||||||
self._file_handler = open(rfile, encoding="utf8")
|
with open(rfile, encoding="utf8") as self._file_handler:
|
||||||
|
|
||||||
num = 1
|
num = 1
|
||||||
while True:
|
while True:
|
||||||
line = self._file_handler.readline()
|
line = self._file_handler.readline()
|
||||||
|
@ -66,18 +67,25 @@ class Annotation:
|
||||||
num, line, self._annotation_definition["name"], rfile
|
num, line, self._annotation_definition["name"], rfile
|
||||||
)
|
)
|
||||||
if item:
|
if item:
|
||||||
self.logger.info(str(item))
|
self.log.info(f"Found {item!s}")
|
||||||
self._populate_item(item.get_obj().items())
|
self._populate_item(
|
||||||
|
item.get_obj().items(), self._annotation_definition["name"]
|
||||||
|
)
|
||||||
num += 1
|
num += 1
|
||||||
|
|
||||||
self._file_handler.close()
|
def _populate_item(self, item, name):
|
||||||
|
allow_multiple = self.config.ANNOTATIONS.get(name)["allow_multiple"]
|
||||||
|
|
||||||
def _populate_item(self, item):
|
|
||||||
for key, value in item:
|
for key, value in item:
|
||||||
|
if allow_multiple:
|
||||||
|
if key not in self._all_items:
|
||||||
|
self._all_items[key] = []
|
||||||
|
self._all_items[key].append(value)
|
||||||
|
else:
|
||||||
try:
|
try:
|
||||||
anyconfig.merge(self._all_items[key], value, ac_merge=anyconfig.MS_DICTS)
|
anyconfig.merge(self._all_items[key], value, ac_merge=anyconfig.MS_DICTS)
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
self.log.sysexit_with_message("Unable to merge annotation values:\n{}".format(e))
|
sysexit_with_message("Failed to merge annotation values", error=e)
|
||||||
|
|
||||||
def _get_annotation_data(self, num, line, name, rfile):
|
def _get_annotation_data(self, num, line, name, rfile):
|
||||||
"""
|
"""
|
||||||
|
@ -92,26 +100,26 @@ class Annotation:
|
||||||
line1 = re.sub(reg1, "", line).strip()
|
line1 = re.sub(reg1, "", line).strip()
|
||||||
|
|
||||||
# step3 take the main key value from the annotation
|
# step3 take the main key value from the annotation
|
||||||
parts = [part.strip() for part in line1.split(":", 2)]
|
parts = [part.strip() for part in _split_string(line1, ":", "\\", 2)]
|
||||||
key = str(parts[0])
|
key = str(parts[0])
|
||||||
item.data[key] = {}
|
item.data[key] = {}
|
||||||
multiline_char = [">", "$>"]
|
multiline_char = [">", "$>"]
|
||||||
|
|
||||||
if len(parts) < 2:
|
if len(parts) < 2:
|
||||||
return
|
return None
|
||||||
|
|
||||||
if len(parts) == 2:
|
if len(parts) == 2:
|
||||||
parts = parts[:1] + ["value"] + parts[1:]
|
parts = parts[:1] + ["value"] + parts[1:]
|
||||||
|
|
||||||
subtypes = self.config.ANNOTATIONS.get(name)["subtypes"]
|
subtypes = self.config.ANNOTATIONS.get(name)["subtypes"]
|
||||||
if subtypes and parts[1] not in subtypes:
|
if subtypes and parts[1] not in subtypes:
|
||||||
return
|
return None
|
||||||
|
|
||||||
content = [parts[2]]
|
content = [parts[2]]
|
||||||
|
|
||||||
if parts[2] not in multiline_char and parts[2].startswith("$"):
|
if parts[2] not in multiline_char and parts[2].startswith("$"):
|
||||||
source = parts[2].replace("$", "").strip()
|
source = parts[2].replace("$", "").strip()
|
||||||
content = self._str_to_json(key, source, rfile, num, line)
|
content = self._str_to_json(key, source, rfile, num)
|
||||||
|
|
||||||
item.data[key][parts[1]] = content
|
item.data[key][parts[1]] = content
|
||||||
|
|
||||||
|
@ -120,7 +128,8 @@ class Annotation:
|
||||||
multiline = []
|
multiline = []
|
||||||
stars_with_annotation = r"(\#\ *[\@][\w]+)"
|
stars_with_annotation = r"(\#\ *[\@][\w]+)"
|
||||||
current_file_position = self._file_handler.tell()
|
current_file_position = self._file_handler.tell()
|
||||||
newline = ""
|
before = ""
|
||||||
|
after = ""
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
next_line = self._file_handler.readline().lstrip()
|
next_line = self._file_handler.readline().lstrip()
|
||||||
|
@ -143,31 +152,34 @@ class Annotation:
|
||||||
final = re.findall(r"\#(.*)", next_line)[0].rstrip()
|
final = re.findall(r"\#(.*)", next_line)[0].rstrip()
|
||||||
if final[:1] == " ":
|
if final[:1] == " ":
|
||||||
final = final[1:]
|
final = final[1:]
|
||||||
final = newline + final
|
final = before + final
|
||||||
|
|
||||||
# match if empty line or commented empty line
|
# match if empty line or commented empty line
|
||||||
test_line = next_line.replace("#", "").strip()
|
test_line = next_line.replace("#", "").strip()
|
||||||
if len(test_line) == 0:
|
if len(test_line) == 0:
|
||||||
newline = "\n\n"
|
before = "\n\n"
|
||||||
continue
|
continue
|
||||||
else:
|
before = ""
|
||||||
newline = ""
|
|
||||||
|
|
||||||
multiline.append(newline + final)
|
if test_line.endswith("\\"):
|
||||||
|
final = final.rstrip("\\").strip()
|
||||||
|
after = "\n"
|
||||||
|
else:
|
||||||
|
after = ""
|
||||||
|
|
||||||
|
multiline.append(before + final + after)
|
||||||
|
|
||||||
if parts[2].startswith("$"):
|
if parts[2].startswith("$"):
|
||||||
source = "".join([x.strip() for x in multiline])
|
source = "".join([x.strip() for x in multiline])
|
||||||
multiline = self._str_to_json(key, source, rfile, num, line)
|
multiline = self._str_to_json(key, source, rfile, num)
|
||||||
|
|
||||||
item.data[key][parts[1]] = multiline
|
item.data[key][parts[1]] = multiline
|
||||||
return item
|
return item
|
||||||
|
|
||||||
def _str_to_json(self, key, string, rfile, num, line):
|
def _str_to_json(self, key, string, rfile, num):
|
||||||
try:
|
try:
|
||||||
return {key: json.loads(string)}
|
return {key: json.loads(string)}
|
||||||
except ValueError:
|
except ValueError:
|
||||||
self.log.sysexit_with_message(
|
sysexit_with_message(
|
||||||
"Json value error: Can't parse json in {}:{}:\n{}".format(
|
f"ValueError: Failed to parse json in {rfile}:{num!s}", file=rfile
|
||||||
rfile, str(num), line.strip()
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|
|
@ -2,29 +2,36 @@
|
||||||
"""Entrypoint and CLI handler."""
|
"""Entrypoint and CLI handler."""
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
|
import os
|
||||||
|
|
||||||
|
import structlog
|
||||||
|
|
||||||
import ansibledoctor.exception
|
import ansibledoctor.exception
|
||||||
from ansibledoctor import __version__
|
from ansibledoctor import __version__
|
||||||
from ansibledoctor.config import SingleConfig
|
from ansibledoctor.config import SingleConfig
|
||||||
from ansibledoctor.doc_generator import Generator
|
from ansibledoctor.doc_generator import Generator
|
||||||
from ansibledoctor.doc_parser import Parser
|
from ansibledoctor.doc_parser import Parser
|
||||||
from ansibledoctor.utils import SingleLog
|
from ansibledoctor.utils import sysexit_with_message
|
||||||
|
|
||||||
|
|
||||||
class AnsibleDoctor:
|
class AnsibleDoctor:
|
||||||
"""Main doctor object."""
|
"""Create main object."""
|
||||||
|
|
||||||
|
log = structlog.get_logger()
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.log = SingleLog()
|
try:
|
||||||
self.logger = self.log.logger
|
self.config = SingleConfig()
|
||||||
self.args = self._cli_args()
|
self.config.load(args=self._parse_args())
|
||||||
self.config = self._get_config()
|
self._execute()
|
||||||
|
except ansibledoctor.exception.DoctorError as e:
|
||||||
|
sysexit_with_message(e)
|
||||||
|
except FileNotFoundError as e:
|
||||||
|
sysexit_with_message("Base directory not found", path=e.filename)
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
sysexit_with_message("Aborted...")
|
||||||
|
|
||||||
doc_parser = Parser()
|
def _parse_args(self):
|
||||||
doc_generator = Generator(doc_parser)
|
|
||||||
doc_generator.render()
|
|
||||||
|
|
||||||
def _cli_args(self):
|
|
||||||
"""
|
"""
|
||||||
Use argparse for parsing CLI arguments.
|
Use argparse for parsing CLI arguments.
|
||||||
|
|
||||||
|
@ -35,72 +42,106 @@ class AnsibleDoctor:
|
||||||
description="Generate documentation from annotated Ansible roles using templates"
|
description="Generate documentation from annotated Ansible roles using templates"
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"role_dir", nargs="?", help="role directory (default: current working dir)"
|
"base_dir",
|
||||||
|
nargs="?",
|
||||||
|
default=self.config.config.base_dir,
|
||||||
|
help="base directory (default: current working directory)",
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"-c", "--config", dest="config_file", help="location of configuration file"
|
"-c",
|
||||||
|
"--config",
|
||||||
|
dest="config_file",
|
||||||
|
help="path to configuration file",
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"-o", "--output", dest="output_dir", action="store", help="output base dir"
|
"-o",
|
||||||
|
"--output",
|
||||||
|
dest="renderer__dest",
|
||||||
|
action="store",
|
||||||
|
default=self.config.config.renderer.dest,
|
||||||
|
help="output directory",
|
||||||
|
metavar="OUTPUT_DIR",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"-r",
|
||||||
|
"--recursive",
|
||||||
|
dest="recursive",
|
||||||
|
action="store_true",
|
||||||
|
default=self.config.config.recursive,
|
||||||
|
help="run recursively over the base directory subfolders",
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"-f",
|
"-f",
|
||||||
"--force",
|
"--force",
|
||||||
dest="force_overwrite",
|
dest="renderer.force_overwrite",
|
||||||
action="store_true",
|
action="store_true",
|
||||||
default=None,
|
default=self.config.config.renderer.force_overwrite,
|
||||||
help="force overwrite output file"
|
help="force overwrite output file",
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"-d",
|
"-d",
|
||||||
"--dry-run",
|
"--dry-run",
|
||||||
dest="dry_run",
|
dest="dry_run",
|
||||||
action="store_true",
|
action="store_true",
|
||||||
default=None,
|
default=self.config.config.dry_run,
|
||||||
help="dry run without writing"
|
help="dry run without writing",
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"-n",
|
"-n",
|
||||||
"--no-role-detection",
|
"--no-role-detection",
|
||||||
dest="role_detection",
|
dest="role_detection",
|
||||||
action="store_false",
|
action="store_false",
|
||||||
default=None,
|
default=self.config.config.role.autodetect,
|
||||||
help="disable automatic role detection"
|
help="disable automatic role detection",
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"-v", dest="logging.level", action="append_const", const=-1, help="increase log level"
|
"-v",
|
||||||
|
dest="logging.level",
|
||||||
|
action="append_const",
|
||||||
|
const=-1,
|
||||||
|
help="increase log level",
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"-q", dest="logging.level", action="append_const", const=1, help="decrease log level"
|
"-q",
|
||||||
|
dest="logging.level",
|
||||||
|
action="append_const",
|
||||||
|
const=1,
|
||||||
|
help="decrease log level",
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--version", action="version", version="%(prog)s {}".format(__version__)
|
"--version",
|
||||||
|
action="version",
|
||||||
|
version=f"%(prog)s {__version__}",
|
||||||
)
|
)
|
||||||
|
|
||||||
return parser.parse_args().__dict__
|
return parser.parse_args().__dict__
|
||||||
|
|
||||||
def _get_config(self):
|
def _execute(self):
|
||||||
try:
|
cwd = os.path.abspath(self.config.config.base_dir)
|
||||||
config = SingleConfig(args=self.args)
|
walkdirs = [cwd]
|
||||||
except ansibledoctor.exception.ConfigError as e:
|
|
||||||
self.log.sysexit_with_message(e)
|
|
||||||
|
|
||||||
try:
|
if self.config.config.recursive:
|
||||||
self.log.set_level(config.config["logging"]["level"])
|
walkdirs = [f.path for f in os.scandir(cwd) if f.is_dir()]
|
||||||
except ValueError as e:
|
|
||||||
self.log.sysexit_with_message("Can not set log level.\n{}".format(str(e)))
|
|
||||||
|
|
||||||
if config.config["role_detection"]:
|
for item in walkdirs:
|
||||||
if config.is_role:
|
os.chdir(item)
|
||||||
self.logger.info("Ansible role detected")
|
self.config.load(root_path=os.getcwd())
|
||||||
|
|
||||||
|
self.log.debug("Switch working directory", path=item)
|
||||||
|
self.log.info("Lookup config file", path=self.config.config_files)
|
||||||
|
|
||||||
|
if self.config.config.role.autodetect:
|
||||||
|
if self.config.is_role():
|
||||||
|
structlog.contextvars.bind_contextvars(role=self.config.config.role_name)
|
||||||
|
self.log.info("Ansible role detected")
|
||||||
else:
|
else:
|
||||||
self.log.sysexit_with_message("No Ansible role detected")
|
sysexit_with_message("No Ansible role detected")
|
||||||
else:
|
else:
|
||||||
self.logger.info("Ansible role detection disabled")
|
self.log.info("Ansible role detection disabled")
|
||||||
|
|
||||||
self.logger.info("Using config file {}".format(config.config_file))
|
doc_parser = Parser()
|
||||||
|
doc_generator = Generator(doc_parser)
|
||||||
return config
|
doc_generator.render()
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
|
|
|
@ -1,298 +1,226 @@
|
||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
"""Global settings definition."""
|
"""Global settings definition."""
|
||||||
|
|
||||||
|
import logging
|
||||||
import os
|
import os
|
||||||
|
import re
|
||||||
|
from io import StringIO
|
||||||
|
|
||||||
import anyconfig
|
import colorama
|
||||||
import environs
|
import structlog
|
||||||
import jsonschema.exceptions
|
|
||||||
import ruamel.yaml
|
|
||||||
from appdirs import AppDirs
|
from appdirs import AppDirs
|
||||||
from jsonschema._utils import format_as_index
|
from dynaconf import Dynaconf, ValidationError, Validator
|
||||||
|
|
||||||
import ansibledoctor.exception
|
import ansibledoctor.exception
|
||||||
from ansibledoctor.utils import Singleton
|
from ansibledoctor.utils import Singleton
|
||||||
|
|
||||||
config_dir = AppDirs("ansible-doctor").user_config_dir
|
|
||||||
default_config_file = os.path.join(config_dir, "config.yml")
|
|
||||||
|
|
||||||
|
class Config:
|
||||||
class Config():
|
"""Create configuration object."""
|
||||||
"""
|
|
||||||
Create an object with all necessary settings.
|
|
||||||
|
|
||||||
Settings are loade from multiple locations in defined order (last wins):
|
|
||||||
- default settings defined by `self._get_defaults()`
|
|
||||||
- yaml config file, defaults to OS specific user config dir (https://pypi.org/project/appdirs/)
|
|
||||||
- provides cli parameters
|
|
||||||
"""
|
|
||||||
|
|
||||||
SETTINGS = {
|
|
||||||
"config_file": {
|
|
||||||
"default": "",
|
|
||||||
"env": "CONFIG_FILE",
|
|
||||||
"type": environs.Env().str
|
|
||||||
},
|
|
||||||
"role_dir": {
|
|
||||||
"default": "",
|
|
||||||
"env": "ROLE_DIR",
|
|
||||||
"type": environs.Env().str
|
|
||||||
},
|
|
||||||
"role_name": {
|
|
||||||
"default": "",
|
|
||||||
"env": "ROLE_NAME",
|
|
||||||
"type": environs.Env().str
|
|
||||||
},
|
|
||||||
"dry_run": {
|
|
||||||
"default": False,
|
|
||||||
"env": "DRY_RUN",
|
|
||||||
"file": True,
|
|
||||||
"type": environs.Env().bool
|
|
||||||
},
|
|
||||||
"logging.level": {
|
|
||||||
"default": "WARNING",
|
|
||||||
"env": "LOG_LEVEL",
|
|
||||||
"file": True,
|
|
||||||
"type": environs.Env().str
|
|
||||||
},
|
|
||||||
"logging.json": {
|
|
||||||
"default": False,
|
|
||||||
"env": "LOG_JSON",
|
|
||||||
"file": True,
|
|
||||||
"type": environs.Env().bool
|
|
||||||
},
|
|
||||||
"output_dir": {
|
|
||||||
"default": os.getcwd(),
|
|
||||||
"env": "OUTPUT_DIR",
|
|
||||||
"file": True,
|
|
||||||
"type": environs.Env().str
|
|
||||||
},
|
|
||||||
"template_dir": {
|
|
||||||
"default": os.path.join(os.path.dirname(os.path.realpath(__file__)), "templates"),
|
|
||||||
"env": "TEMPLATE_DIR",
|
|
||||||
"file": True,
|
|
||||||
"type": environs.Env().str
|
|
||||||
},
|
|
||||||
"template": {
|
|
||||||
"default": "readme",
|
|
||||||
"env": "TEMPLATE",
|
|
||||||
"file": True,
|
|
||||||
"type": environs.Env().str
|
|
||||||
},
|
|
||||||
"force_overwrite": {
|
|
||||||
"default": False,
|
|
||||||
"env": "FORCE_OVERWRITE",
|
|
||||||
"file": True,
|
|
||||||
"type": environs.Env().bool
|
|
||||||
},
|
|
||||||
"custom_header": {
|
|
||||||
"default": "",
|
|
||||||
"env": "CUSTOM_HEADER",
|
|
||||||
"file": True,
|
|
||||||
"type": environs.Env().str
|
|
||||||
},
|
|
||||||
"exclude_files": {
|
|
||||||
"default": [],
|
|
||||||
"env": "EXCLUDE_FILES",
|
|
||||||
"file": True,
|
|
||||||
"type": environs.Env().list
|
|
||||||
},
|
|
||||||
"role_detection": {
|
|
||||||
"default": True,
|
|
||||||
"env": "ROLE_DETECTION",
|
|
||||||
"file": True,
|
|
||||||
"type": environs.Env().bool
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
ANNOTATIONS = {
|
ANNOTATIONS = {
|
||||||
"meta": {
|
"meta": {
|
||||||
"name": "meta",
|
"name": "meta",
|
||||||
"automatic": True,
|
"automatic": True,
|
||||||
"subtypes": []
|
"subtypes": ["value"],
|
||||||
|
"allow_multiple": False,
|
||||||
},
|
},
|
||||||
"todo": {
|
"todo": {
|
||||||
"name": "todo",
|
"name": "todo",
|
||||||
"automatic": True,
|
"automatic": True,
|
||||||
"subtypes": []
|
"subtypes": ["value"],
|
||||||
|
"allow_multiple": True,
|
||||||
},
|
},
|
||||||
"var": {
|
"var": {
|
||||||
"name": "var",
|
"name": "var",
|
||||||
"automatic": True,
|
"automatic": True,
|
||||||
"subtypes": ["value", "example", "description"]
|
"subtypes": ["value", "example", "description", "type", "deprecated"],
|
||||||
|
"allow_multiple": False,
|
||||||
},
|
},
|
||||||
"example": {
|
"example": {
|
||||||
"name": "example",
|
"name": "example",
|
||||||
"automatic": True,
|
"automatic": True,
|
||||||
"subtypes": []
|
"subtypes": [],
|
||||||
|
"allow_multiple": False,
|
||||||
},
|
},
|
||||||
"tag": {
|
"tag": {
|
||||||
"name": "tag",
|
"name": "tag",
|
||||||
"automatic": True,
|
"automatic": True,
|
||||||
"subtypes": []
|
"subtypes": ["value", "description"],
|
||||||
|
"allow_multiple": False,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
def __init__(self, args={}):
|
def __init__(self):
|
||||||
"""
|
self.config_files = [
|
||||||
Initialize a new settings class.
|
os.path.join(AppDirs("ansible-doctor").user_config_dir, "config.yml"),
|
||||||
|
".ansibledoctor",
|
||||||
|
".ansibledoctor.yml",
|
||||||
|
".ansibledoctor.yaml",
|
||||||
|
]
|
||||||
|
self.config_merge = True
|
||||||
|
self.args = {}
|
||||||
|
self.load()
|
||||||
|
|
||||||
:param args: An optional dict of options, arguments and commands from the CLI.
|
def load(self, root_path=None, args=None):
|
||||||
:param config_file: An optional path to a yaml config file.
|
tmpl_src = os.path.join(os.path.dirname(os.path.realpath(__file__)), "templates")
|
||||||
:returns: None
|
tmpl_provider = ["local", "git"]
|
||||||
|
|
||||||
"""
|
if args:
|
||||||
self._args = args
|
if args.get("config_file"):
|
||||||
self._schema = None
|
self.config_merge = False
|
||||||
self.config_file = default_config_file
|
self.config_files = [os.path.abspath(args.get("config_file"))]
|
||||||
self.role_dir = os.getcwd()
|
args.pop("config_file")
|
||||||
self.config = None
|
|
||||||
self._set_config()
|
|
||||||
self.is_role = self._set_is_role() or False
|
|
||||||
|
|
||||||
def _get_args(self, args):
|
self.args = args
|
||||||
cleaned = dict(filter(lambda item: item[1] is not None, args.items()))
|
|
||||||
|
|
||||||
normalized = {}
|
self.config = Dynaconf(
|
||||||
for key, value in cleaned.items():
|
envvar_prefix="ANSIBLE_DOCTOR",
|
||||||
normalized = self._add_dict_branch(normalized, key.split("."), value)
|
merge_enabled=self.config_merge,
|
||||||
|
core_loaders=["YAML"],
|
||||||
|
root_path=root_path,
|
||||||
|
settings_files=self.config_files,
|
||||||
|
fresh_vars=["base_dir", "output_dir"],
|
||||||
|
validators=[
|
||||||
|
Validator(
|
||||||
|
"base_dir",
|
||||||
|
default=os.getcwd(),
|
||||||
|
apply_default_on_none=True,
|
||||||
|
is_type_of=str,
|
||||||
|
),
|
||||||
|
Validator(
|
||||||
|
"dry_run",
|
||||||
|
default=False,
|
||||||
|
is_type_of=bool,
|
||||||
|
),
|
||||||
|
Validator(
|
||||||
|
"recursive",
|
||||||
|
default=False,
|
||||||
|
is_type_of=bool,
|
||||||
|
),
|
||||||
|
Validator(
|
||||||
|
"exclude_files",
|
||||||
|
default=[],
|
||||||
|
is_type_of=list,
|
||||||
|
),
|
||||||
|
Validator(
|
||||||
|
"exclude_tags",
|
||||||
|
default=[],
|
||||||
|
is_type_of=list,
|
||||||
|
),
|
||||||
|
Validator(
|
||||||
|
"role.name",
|
||||||
|
is_type_of=str,
|
||||||
|
),
|
||||||
|
Validator(
|
||||||
|
"role.autodetect",
|
||||||
|
default=True,
|
||||||
|
is_type_of=bool,
|
||||||
|
),
|
||||||
|
Validator(
|
||||||
|
"logging.level",
|
||||||
|
default="WARNING",
|
||||||
|
is_in=[
|
||||||
|
"DEBUG",
|
||||||
|
"INFO",
|
||||||
|
"WARNING",
|
||||||
|
"ERROR",
|
||||||
|
"CRITICAL",
|
||||||
|
"debug",
|
||||||
|
"info",
|
||||||
|
"warning",
|
||||||
|
"error",
|
||||||
|
"critical",
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Validator(
|
||||||
|
"logging.json",
|
||||||
|
default=False,
|
||||||
|
is_type_of=bool,
|
||||||
|
),
|
||||||
|
Validator(
|
||||||
|
"recursive",
|
||||||
|
default=False,
|
||||||
|
is_type_of=bool,
|
||||||
|
),
|
||||||
|
Validator(
|
||||||
|
"template.src",
|
||||||
|
default=f"local>{tmpl_src}",
|
||||||
|
is_type_of=str,
|
||||||
|
condition=lambda x: re.match(r"^(local|git)\s*>\s*", x),
|
||||||
|
messages={
|
||||||
|
"condition": f"Template provider must be one of {tmpl_provider}.",
|
||||||
|
},
|
||||||
|
),
|
||||||
|
Validator(
|
||||||
|
"template.name",
|
||||||
|
default="readme",
|
||||||
|
is_type_of=str,
|
||||||
|
),
|
||||||
|
Validator(
|
||||||
|
"template.options.tabulate_variables",
|
||||||
|
default=False,
|
||||||
|
is_type_of=bool,
|
||||||
|
),
|
||||||
|
Validator(
|
||||||
|
"renderer.autotrim",
|
||||||
|
default=True,
|
||||||
|
is_type_of=bool,
|
||||||
|
),
|
||||||
|
Validator(
|
||||||
|
"renderer.include_header",
|
||||||
|
default="",
|
||||||
|
is_type_of=str,
|
||||||
|
),
|
||||||
|
Validator(
|
||||||
|
"renderer.dest",
|
||||||
|
default=os.path.relpath(os.getcwd()),
|
||||||
|
is_type_of=str,
|
||||||
|
),
|
||||||
|
Validator(
|
||||||
|
"renderer.force_overwrite",
|
||||||
|
default=False,
|
||||||
|
is_type_of=bool,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
self.validate()
|
||||||
|
|
||||||
# Override correct log level from argparse
|
# Override correct log level from argparse
|
||||||
levels = ["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"]
|
levels = ["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"]
|
||||||
log_level = levels.index(self.SETTINGS["logging.level"]["default"])
|
log_level = levels.index(self.config.logging.level.upper())
|
||||||
if normalized.get("logging"):
|
if self.args.get("logging.level") and isinstance(self.args["logging.level"], list):
|
||||||
for adjustment in normalized["logging"]["level"]:
|
for lvl in self.args["logging.level"]:
|
||||||
log_level = min(len(levels) - 1, max(log_level + adjustment, 0))
|
log_level = min(len(levels) - 1, max(log_level + lvl, 0))
|
||||||
normalized["logging"]["level"] = levels[log_level]
|
|
||||||
|
|
||||||
return normalized
|
self.args["logging__level"] = levels[log_level]
|
||||||
|
|
||||||
def _get_defaults(self):
|
if root_path:
|
||||||
normalized = {}
|
self.args["base_dir"] = root_path
|
||||||
for key, item in self.SETTINGS.items():
|
|
||||||
normalized = self._add_dict_branch(normalized, key.split("."), item["default"])
|
|
||||||
|
|
||||||
# compute role_name default
|
self.config.update(self.args)
|
||||||
normalized["role_name"] = os.path.basename(self.role_dir)
|
self.validate()
|
||||||
|
|
||||||
self.schema = anyconfig.gen_schema(normalized)
|
self._init_logger()
|
||||||
return normalized
|
|
||||||
|
|
||||||
def _get_envs(self):
|
def validate(self):
|
||||||
normalized = {}
|
|
||||||
for key, item in self.SETTINGS.items():
|
|
||||||
if item.get("env"):
|
|
||||||
prefix = "ANSIBLE_DOCTOR_"
|
|
||||||
envname = prefix + item["env"]
|
|
||||||
try:
|
try:
|
||||||
value = item["type"](envname)
|
self.config.validators.validate_all()
|
||||||
normalized = self._add_dict_branch(normalized, key.split("."), value)
|
except ValidationError as e:
|
||||||
except environs.EnvError as e:
|
raise ansibledoctor.exception.ConfigError("Configuration error", e.message) from e
|
||||||
if '"{}" not set'.format(envname) in str(e):
|
|
||||||
pass
|
def is_role(self):
|
||||||
else:
|
self.config.role_name = self.config.get(
|
||||||
raise ansibledoctor.exception.ConfigError(
|
"role_name", os.path.basename(self.config.base_dir)
|
||||||
"Unable to read environment variable", str(e)
|
|
||||||
)
|
)
|
||||||
|
return os.path.isdir(os.path.join(self.config.base_dir, "tasks"))
|
||||||
return normalized
|
|
||||||
|
|
||||||
def _set_config(self):
|
|
||||||
args = self._get_args(self._args)
|
|
||||||
envs = self._get_envs()
|
|
||||||
defaults = self._get_defaults()
|
|
||||||
|
|
||||||
# preset config file path
|
|
||||||
if envs.get("config_file"):
|
|
||||||
self.config_file = self._normalize_path(envs.get("config_file"))
|
|
||||||
if envs.get("role_dir"):
|
|
||||||
self.role_dir = self._normalize_path(envs.get("role_dir"))
|
|
||||||
|
|
||||||
if args.get("config_file"):
|
|
||||||
self.config_file = self._normalize_path(args.get("config_file"))
|
|
||||||
if args.get("role_dir"):
|
|
||||||
self.role_dir = self._normalize_path(args.get("role_dir"))
|
|
||||||
|
|
||||||
source_files = []
|
|
||||||
source_files.append(self.config_file)
|
|
||||||
source_files.append(os.path.join(os.getcwd(), ".ansibledoctor"))
|
|
||||||
source_files.append(os.path.join(os.getcwd(), ".ansibledoctor.yml"))
|
|
||||||
source_files.append(os.path.join(os.getcwd(), ".ansibledoctor.yaml"))
|
|
||||||
|
|
||||||
for config in source_files:
|
|
||||||
if config and os.path.exists(config):
|
|
||||||
with open(config, "r", encoding="utf8") as stream:
|
|
||||||
s = stream.read()
|
|
||||||
try:
|
|
||||||
file_dict = ruamel.yaml.safe_load(s)
|
|
||||||
except (
|
|
||||||
ruamel.yaml.composer.ComposerError, ruamel.yaml.scanner.ScannerError
|
|
||||||
) as e:
|
|
||||||
message = "{} {}".format(e.context, e.problem)
|
|
||||||
raise ansibledoctor.exception.ConfigError(
|
|
||||||
"Unable to read config file {}".format(config), message
|
|
||||||
)
|
|
||||||
|
|
||||||
if self._validate(file_dict):
|
|
||||||
anyconfig.merge(defaults, file_dict, ac_merge=anyconfig.MS_DICTS)
|
|
||||||
defaults["logging"]["level"] = defaults["logging"]["level"].upper()
|
|
||||||
|
|
||||||
if self._validate(envs):
|
|
||||||
anyconfig.merge(defaults, envs, ac_merge=anyconfig.MS_DICTS)
|
|
||||||
|
|
||||||
if self._validate(args):
|
|
||||||
anyconfig.merge(defaults, args, ac_merge=anyconfig.MS_DICTS)
|
|
||||||
|
|
||||||
fix_files = ["output_dir", "template_dir", "custom_header"]
|
|
||||||
for file in fix_files:
|
|
||||||
if defaults[file] and defaults[file] != "":
|
|
||||||
defaults[file] = self._normalize_path(defaults[file])
|
|
||||||
|
|
||||||
if "config_file" in defaults:
|
|
||||||
defaults.pop("config_file")
|
|
||||||
if "role_dir" in defaults:
|
|
||||||
defaults.pop("role_dir")
|
|
||||||
|
|
||||||
defaults["logging"]["level"] = defaults["logging"]["level"].upper()
|
|
||||||
|
|
||||||
self.config = defaults
|
|
||||||
|
|
||||||
def _normalize_path(self, path):
|
|
||||||
if not os.path.isabs(path):
|
|
||||||
base = os.path.join(os.getcwd(), path)
|
|
||||||
return os.path.abspath(os.path.expanduser(os.path.expandvars(base)))
|
|
||||||
else:
|
|
||||||
return path
|
|
||||||
|
|
||||||
def _set_is_role(self):
|
|
||||||
if os.path.isdir(os.path.join(self.role_dir, "tasks")):
|
|
||||||
return True
|
|
||||||
|
|
||||||
def _validate(self, config):
|
|
||||||
try:
|
|
||||||
anyconfig.validate(config, self.schema, ac_schema_safe=False)
|
|
||||||
except jsonschema.exceptions.ValidationError as e:
|
|
||||||
schema_error = "Failed validating '{validator}' in schema{schema}\n{message}".format(
|
|
||||||
validator=e.validator,
|
|
||||||
schema=format_as_index(list(e.relative_schema_path)[:-1]),
|
|
||||||
message=e.message
|
|
||||||
)
|
|
||||||
raise ansibledoctor.exception.ConfigError("Configuration error", schema_error)
|
|
||||||
|
|
||||||
return True
|
|
||||||
|
|
||||||
def _add_dict_branch(self, tree, vector, value):
|
|
||||||
key = vector[0]
|
|
||||||
tree[key] = value \
|
|
||||||
if len(vector) == 1 \
|
|
||||||
else self._add_dict_branch(tree[key] if key in tree else {}, vector[1:], value)
|
|
||||||
return tree
|
|
||||||
|
|
||||||
def get_annotations_definition(self, automatic=True):
|
def get_annotations_definition(self, automatic=True):
|
||||||
annotations = {}
|
annotations = {}
|
||||||
if automatic:
|
if automatic:
|
||||||
for k, item in self.ANNOTATIONS.items():
|
for k, item in self.ANNOTATIONS.items():
|
||||||
if "automatic" in item.keys() and item["automatic"]:
|
if item.get("automatic"):
|
||||||
annotations[k] = item
|
annotations[k] = item
|
||||||
return annotations
|
return annotations
|
||||||
|
|
||||||
|
@ -300,19 +228,84 @@ class Config():
|
||||||
annotations = []
|
annotations = []
|
||||||
if automatic:
|
if automatic:
|
||||||
for k, item in self.ANNOTATIONS.items():
|
for k, item in self.ANNOTATIONS.items():
|
||||||
if "automatic" in item.keys() and item["automatic"]:
|
if item.get("automatic"):
|
||||||
annotations.append(k)
|
annotations.append(k)
|
||||||
return annotations
|
return annotations
|
||||||
|
|
||||||
def get_template(self):
|
def _init_logger(self):
|
||||||
"""
|
styles = structlog.dev.ConsoleRenderer.get_default_level_styles()
|
||||||
Get the base dir for the template to use.
|
styles["debug"] = colorama.Fore.BLUE
|
||||||
|
|
||||||
:return: str abs path
|
processors = [
|
||||||
"""
|
structlog.contextvars.merge_contextvars,
|
||||||
template_dir = self.config.get("template_dir")
|
structlog.processors.add_log_level,
|
||||||
template = self.config.get("template")
|
structlog.processors.StackInfoRenderer(),
|
||||||
return os.path.realpath(os.path.join(template_dir, template))
|
structlog.dev.set_exc_info,
|
||||||
|
structlog.processors.TimeStamper(fmt="%Y-%m-%d %H:%M:%S", utc=False),
|
||||||
|
]
|
||||||
|
|
||||||
|
if self.config.logging.json:
|
||||||
|
processors.append(ErrorStringifier())
|
||||||
|
processors.append(structlog.processors.JSONRenderer())
|
||||||
|
else:
|
||||||
|
processors.append(MultilineConsoleRenderer(level_styles=styles))
|
||||||
|
|
||||||
|
try:
|
||||||
|
structlog.configure(
|
||||||
|
processors=processors,
|
||||||
|
wrapper_class=structlog.make_filtering_bound_logger(
|
||||||
|
logging.getLevelName(self.config.get("logging.level")),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
structlog.contextvars.unbind_contextvars()
|
||||||
|
except KeyError as e:
|
||||||
|
raise ansibledoctor.exception.ConfigError(f"Can not set log level: {e!s}") from e
|
||||||
|
|
||||||
|
|
||||||
|
class ErrorStringifier:
|
||||||
|
"""A processor that converts exceptions to a string representation."""
|
||||||
|
|
||||||
|
def __call__(self, _, __, event_dict):
|
||||||
|
if "error" not in event_dict:
|
||||||
|
return event_dict
|
||||||
|
|
||||||
|
err = event_dict.get("error")
|
||||||
|
|
||||||
|
if isinstance(err, Exception):
|
||||||
|
event_dict["error"] = f"{err.__class__.__name__}: {err}"
|
||||||
|
|
||||||
|
return event_dict
|
||||||
|
|
||||||
|
|
||||||
|
class MultilineConsoleRenderer(structlog.dev.ConsoleRenderer):
|
||||||
|
"""A processor for printing multiline strings."""
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
def __call__(self, _, __, event_dict):
|
||||||
|
err = None
|
||||||
|
|
||||||
|
if "error" in event_dict:
|
||||||
|
err = event_dict.pop("error")
|
||||||
|
|
||||||
|
event_dict = super().__call__(_, __, event_dict)
|
||||||
|
|
||||||
|
if not err:
|
||||||
|
return event_dict
|
||||||
|
|
||||||
|
sio = StringIO()
|
||||||
|
sio.write(event_dict)
|
||||||
|
|
||||||
|
if isinstance(err, Exception):
|
||||||
|
sio.write(
|
||||||
|
f"\n{colorama.Fore.RED}{err.__class__.__name__}:"
|
||||||
|
f"{colorama.Style.RESET_ALL} {str(err).strip()}"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
sio.write(f"\n{err.strip()}")
|
||||||
|
|
||||||
|
return sio.getvalue()
|
||||||
|
|
||||||
|
|
||||||
class SingleConfig(Config, metaclass=Singleton):
|
class SingleConfig(Config, metaclass=Singleton):
|
||||||
|
|
|
@ -1,145 +1,125 @@
|
||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
"""Prepare output and write compiled jinja2 templates."""
|
"""Prepare output and write compiled jinja2 templates."""
|
||||||
|
|
||||||
import glob
|
|
||||||
import ntpath
|
|
||||||
import os
|
import os
|
||||||
|
import re
|
||||||
from functools import reduce
|
from functools import reduce
|
||||||
|
|
||||||
import jinja2.exceptions
|
import jinja2.exceptions
|
||||||
import ruamel.yaml
|
import ruamel.yaml
|
||||||
from jinja2 import Environment
|
import structlog
|
||||||
from jinja2 import FileSystemLoader
|
from jinja2 import Environment, FileSystemLoader
|
||||||
from jinja2.filters import evalcontextfilter
|
from jinja2.filters import pass_eval_context
|
||||||
|
|
||||||
import ansibledoctor.exception
|
|
||||||
from ansibledoctor.config import SingleConfig
|
from ansibledoctor.config import SingleConfig
|
||||||
from ansibledoctor.utils import FileUtils
|
from ansibledoctor.template import Template
|
||||||
from ansibledoctor.utils import SingleLog
|
from ansibledoctor.utils import FileUtils, sysexit_with_message
|
||||||
|
|
||||||
|
|
||||||
class Generator:
|
class Generator:
|
||||||
"""Generate documentation from jinja2 templates."""
|
"""Generate documentation from jinja2 templates."""
|
||||||
|
|
||||||
def __init__(self, doc_parser):
|
def __init__(self, doc_parser):
|
||||||
self.template_files = []
|
self.log = structlog.get_logger()
|
||||||
self.extension = "j2"
|
|
||||||
self._parser = None
|
|
||||||
self.config = SingleConfig()
|
self.config = SingleConfig()
|
||||||
self.log = SingleLog()
|
self.template = Template(
|
||||||
self.logger = self.log.logger
|
self.config.config.get("template.name"),
|
||||||
|
self.config.config.get("template.src"),
|
||||||
|
)
|
||||||
self._parser = doc_parser
|
self._parser = doc_parser
|
||||||
self._scan_template()
|
|
||||||
|
|
||||||
def _scan_template(self):
|
|
||||||
"""
|
|
||||||
Search for Jinja2 (.j2) files to apply to the destination.
|
|
||||||
|
|
||||||
:return: None
|
|
||||||
"""
|
|
||||||
template_dir = self.config.get_template()
|
|
||||||
if os.path.isdir(template_dir):
|
|
||||||
self.logger.info("Using template dir: {}".format(template_dir))
|
|
||||||
else:
|
|
||||||
self.log.sysexit_with_message("Can not open template dir {}".format(template_dir))
|
|
||||||
|
|
||||||
for file in glob.iglob(template_dir + "/**/*." + self.extension, recursive=True):
|
|
||||||
relative_file = file[len(template_dir) + 1:]
|
|
||||||
if ntpath.basename(file)[:1] != "_":
|
|
||||||
self.logger.debug("Found template file: " + relative_file)
|
|
||||||
self.template_files.append(relative_file)
|
|
||||||
else:
|
|
||||||
self.logger.debug("Ignoring template file: " + relative_file)
|
|
||||||
|
|
||||||
def _create_dir(self, directory):
|
def _create_dir(self, directory):
|
||||||
if not self.config.config["dry_run"] and not os.path.isdir(directory):
|
if not self.config.config["dry_run"] and not os.path.isdir(directory):
|
||||||
try:
|
try:
|
||||||
os.makedirs(directory, exist_ok=True)
|
os.makedirs(directory, exist_ok=True)
|
||||||
self.logger.info("Creating dir: " + directory)
|
self.log.info(f"Creating dir: {directory}")
|
||||||
except FileExistsError as e:
|
except FileExistsError as e:
|
||||||
self.log.sysexit_with_message(str(e))
|
sysexit_with_message(e)
|
||||||
|
|
||||||
def _write_doc(self):
|
def _write_doc(self):
|
||||||
files_to_overwite = []
|
files_to_overwite = []
|
||||||
|
|
||||||
for file in self.template_files:
|
for tf in self.template.files:
|
||||||
doc_file = os.path.join(
|
doc_file = os.path.join(
|
||||||
self.config.config.get("output_dir"),
|
self.config.config.get("renderer.dest"), os.path.splitext(tf)[0]
|
||||||
os.path.splitext(file)[0]
|
|
||||||
)
|
)
|
||||||
if os.path.isfile(doc_file):
|
if os.path.isfile(doc_file):
|
||||||
files_to_overwite.append(doc_file)
|
files_to_overwite.append(doc_file)
|
||||||
|
|
||||||
header_file = self.config.config.get("custom_header")
|
header_file = self.config.config.get("renderer.include_header")
|
||||||
role_data = self._parser.get_data()
|
role_data = self._parser.get_data()
|
||||||
header_content = ""
|
header_content = ""
|
||||||
if bool(header_file):
|
if bool(header_file):
|
||||||
role_data["internal"]["append"] = True
|
role_data["internal"]["append"] = True
|
||||||
try:
|
try:
|
||||||
with open(header_file, "r") as a:
|
with open(header_file) as a:
|
||||||
header_content = a.read()
|
header_content = a.read()
|
||||||
except FileNotFoundError as e:
|
except FileNotFoundError as e:
|
||||||
self.log.sysexit_with_message("Can not open custom header file\n{}".format(str(e)))
|
sysexit_with_message("Can not open custom header file", path=header_file, error=e)
|
||||||
|
|
||||||
if len(files_to_overwite) > 0 and self.config.config.get("force_overwrite") is False:
|
if (
|
||||||
if not self.config.config["dry_run"]:
|
len(files_to_overwite) > 0
|
||||||
self.logger.warn("This files will be overwritten:")
|
and self.config.config.get("renderer.force_overwrite") is False
|
||||||
print(*files_to_overwite, sep="\n")
|
and not self.config.config["dry_run"]
|
||||||
|
):
|
||||||
|
files_to_overwite_string = "\n".join(files_to_overwite)
|
||||||
|
prompt = f"These files will be overwritten:\n{files_to_overwite_string}".replace(
|
||||||
|
"\n", "\n... "
|
||||||
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if not FileUtils.query_yes_no("Do you want to continue?"):
|
if not FileUtils.query_yes_no(f"{prompt}\nDo you want to continue?"):
|
||||||
self.log.sysexit_with_message("Aborted...")
|
sysexit_with_message("Aborted...")
|
||||||
except ansibledoctor.exception.InputError as e:
|
except KeyboardInterrupt:
|
||||||
self.logger.debug(str(e))
|
sysexit_with_message("Aborted...")
|
||||||
self.log.sysexit_with_message("Aborted...")
|
|
||||||
|
|
||||||
for file in self.template_files:
|
for tf in self.template.files:
|
||||||
doc_file = os.path.join(
|
doc_file = os.path.join(
|
||||||
self.config.config.get("output_dir"),
|
self.config.config.get("renderer.dest"), os.path.splitext(tf)[0]
|
||||||
os.path.splitext(file)[0]
|
|
||||||
)
|
)
|
||||||
source_file = self.config.get_template() + "/" + file
|
template = os.path.join(self.template.path, tf)
|
||||||
|
|
||||||
self.logger.debug("Writing doc output to: " + doc_file + " from: " + source_file)
|
self.log.debug("Writing renderer output", path=doc_file, src=os.path.dirname(template))
|
||||||
|
|
||||||
# make sure the directory exists
|
# make sure the directory exists
|
||||||
self._create_dir(os.path.dirname(doc_file))
|
self._create_dir(os.path.dirname(doc_file))
|
||||||
|
|
||||||
if os.path.exists(source_file) and os.path.isfile(source_file):
|
if os.path.exists(template) and os.path.isfile(template):
|
||||||
with open(source_file, "r") as template:
|
with open(template) as template:
|
||||||
data = template.read()
|
data = template.read()
|
||||||
if data is not None:
|
if data is not None:
|
||||||
try:
|
try:
|
||||||
jenv = Environment( # nosec
|
jenv = Environment( # nosec
|
||||||
loader=FileSystemLoader(self.config.get_template()),
|
loader=FileSystemLoader(self.template.path),
|
||||||
lstrip_blocks=True,
|
lstrip_blocks=True,
|
||||||
trim_blocks=True
|
trim_blocks=True,
|
||||||
|
autoescape=jinja2.select_autoescape(),
|
||||||
)
|
)
|
||||||
jenv.filters["to_nice_yaml"] = self._to_nice_yaml
|
jenv.filters["to_nice_yaml"] = self._to_nice_yaml
|
||||||
jenv.filters["deep_get"] = self._deep_get
|
jenv.filters["deep_get"] = self._deep_get
|
||||||
jenv.filters["save_join"] = self._save_join
|
jenv.filters["safe_join"] = self._safe_join
|
||||||
data = jenv.from_string(data).render(role_data, role=role_data)
|
# keep the old name of the function to not break custom templates.
|
||||||
|
jenv.filters["save_join"] = self._safe_join
|
||||||
|
template_options = self.config.config.get("template.options")
|
||||||
|
data = jenv.from_string(data).render(
|
||||||
|
role_data, role=role_data, options=template_options
|
||||||
|
)
|
||||||
if not self.config.config["dry_run"]:
|
if not self.config.config["dry_run"]:
|
||||||
with open(doc_file, "wb") as outfile:
|
with open(doc_file, "wb") as outfile:
|
||||||
outfile.write(header_content.encode("utf-8"))
|
outfile.write(header_content.encode("utf-8"))
|
||||||
outfile.write(data.encode("utf-8"))
|
outfile.write(data.encode("utf-8"))
|
||||||
self.logger.info("Writing to: " + doc_file)
|
|
||||||
else:
|
|
||||||
self.logger.info("Writing to: " + doc_file)
|
|
||||||
except (
|
except (
|
||||||
jinja2.exceptions.UndefinedError, jinja2.exceptions.TemplateSyntaxError
|
jinja2.exceptions.UndefinedError,
|
||||||
|
jinja2.exceptions.TemplateSyntaxError,
|
||||||
|
jinja2.exceptions.TemplateRuntimeError,
|
||||||
) as e:
|
) as e:
|
||||||
self.log.sysexit_with_message(
|
sysexit_with_message(
|
||||||
"Jinja2 templating error while loading file: '{}'\n{}".format(
|
"Jinja2 template error while loading file", path=tf, error=e
|
||||||
file, str(e)
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
except UnicodeEncodeError as e:
|
except UnicodeEncodeError as e:
|
||||||
self.log.sysexit_with_message(
|
sysexit_with_message("Failed to print special characters", error=e)
|
||||||
"Unable to print special characters\n{}".format(str(e))
|
|
||||||
)
|
|
||||||
|
|
||||||
def _to_nice_yaml(self, a, indent=4, *args, **kw):
|
def _to_nice_yaml(self, a, indent=4, **kw):
|
||||||
"""Make verbose, human readable yaml."""
|
"""Make verbose, human readable yaml."""
|
||||||
yaml = ruamel.yaml.YAML()
|
yaml = ruamel.yaml.YAML()
|
||||||
yaml.indent(mapping=indent, sequence=(indent * 2), offset=indent)
|
yaml.indent(mapping=indent, sequence=(indent * 2), offset=indent)
|
||||||
|
@ -147,19 +127,26 @@ class Generator:
|
||||||
yaml.dump(a, stream, **kw)
|
yaml.dump(a, stream, **kw)
|
||||||
return stream.getvalue().rstrip()
|
return stream.getvalue().rstrip()
|
||||||
|
|
||||||
def _deep_get(self, _, dictionary, keys, *args, **kw):
|
def _deep_get(self, _, dictionary, keys):
|
||||||
default = None
|
default = None
|
||||||
return reduce(
|
return reduce(
|
||||||
lambda d, key: d.get(key, default)
|
lambda d, key: d.get(key, default) if isinstance(d, dict) else default,
|
||||||
if isinstance(d, dict) else default, keys.split("."), dictionary
|
keys.split("."),
|
||||||
|
dictionary,
|
||||||
)
|
)
|
||||||
|
|
||||||
@evalcontextfilter
|
@pass_eval_context
|
||||||
def _save_join(self, eval_ctx, value, d=u"", attribute=None):
|
def _safe_join(self, eval_ctx, value, d=""):
|
||||||
if isinstance(value, str):
|
if isinstance(value, str):
|
||||||
value = [value]
|
value = [value]
|
||||||
return jinja2.filters.do_join(eval_ctx, value, d, attribute=None)
|
|
||||||
|
normalized = jinja2.filters.do_join(eval_ctx, value, d, attribute=None)
|
||||||
|
|
||||||
|
if self.config.config.renderer.autotrim:
|
||||||
|
for s in [r" +(\n|\t| )", r"(\n|\t) +"]:
|
||||||
|
normalized = re.sub(s, "\\1", normalized)
|
||||||
|
|
||||||
|
return jinja2.filters.do_mark_safe(normalized)
|
||||||
|
|
||||||
def render(self):
|
def render(self):
|
||||||
self.logger.info("Using output dir: " + self.config.config.get("output_dir"))
|
|
||||||
self._write_doc()
|
self._write_doc()
|
||||||
|
|
|
@ -5,15 +5,15 @@ import fnmatch
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
|
|
||||||
import anyconfig
|
import anyconfig
|
||||||
import ruamel.yaml
|
import structlog
|
||||||
from nested_lookup import nested_lookup
|
|
||||||
|
|
||||||
from ansibledoctor.annotation import Annotation
|
from ansibledoctor.annotation import Annotation
|
||||||
from ansibledoctor.config import SingleConfig
|
from ansibledoctor.config import SingleConfig
|
||||||
from ansibledoctor.contstants import YAML_EXTENSIONS
|
from ansibledoctor.contstants import YAML_EXTENSIONS
|
||||||
|
from ansibledoctor.exception import YAMLError
|
||||||
from ansibledoctor.file_registry import Registry
|
from ansibledoctor.file_registry import Registry
|
||||||
from ansibledoctor.utils import SingleLog
|
from ansibledoctor.utils import flatten, sysexit_with_message
|
||||||
from ansibledoctor.utils import UnsafeTag
|
from ansibledoctor.utils.yamlhelper import parse_yaml, parse_yaml_ansible
|
||||||
|
|
||||||
|
|
||||||
class Parser:
|
class Parser:
|
||||||
|
@ -23,90 +23,82 @@ class Parser:
|
||||||
self._annotation_objs = {}
|
self._annotation_objs = {}
|
||||||
self._data = defaultdict(dict)
|
self._data = defaultdict(dict)
|
||||||
self.config = SingleConfig()
|
self.config = SingleConfig()
|
||||||
self.log = SingleLog()
|
self.log = structlog.get_logger()
|
||||||
self.logger = SingleLog().logger
|
|
||||||
self._files_registry = Registry()
|
self._files_registry = Registry()
|
||||||
self._parse_meta_file()
|
self._parse_meta_file()
|
||||||
self._parse_var_files()
|
self._parse_var_files()
|
||||||
|
self._parse_task_tags()
|
||||||
self._populate_doc_data()
|
self._populate_doc_data()
|
||||||
|
|
||||||
def _parse_var_files(self):
|
def _parse_var_files(self):
|
||||||
for rfile in self._files_registry.get_files():
|
for rfile in self._files_registry.get_files():
|
||||||
if any(fnmatch.fnmatch(rfile, "*/defaults/*." + ext) for ext in YAML_EXTENSIONS):
|
if any(fnmatch.fnmatch(rfile, "*/defaults/*." + ext) for ext in YAML_EXTENSIONS):
|
||||||
with open(rfile, "r", encoding="utf8") as yaml_file:
|
with open(rfile, encoding="utf8") as yamlfile:
|
||||||
try:
|
try:
|
||||||
ruamel.yaml.add_constructor(
|
raw = parse_yaml(yamlfile)
|
||||||
UnsafeTag.yaml_tag,
|
except YAMLError as e:
|
||||||
UnsafeTag.yaml_constructor,
|
sysexit_with_message("Failed to read yaml file", path=rfile, error=e)
|
||||||
constructor=ruamel.yaml.SafeConstructor
|
|
||||||
)
|
data = defaultdict(dict, raw or {})
|
||||||
data = defaultdict(dict, (ruamel.yaml.safe_load(yaml_file) or {}))
|
|
||||||
for key, value in data.items():
|
for key, value in data.items():
|
||||||
self._data["var"][key] = {"value": {key: value}}
|
self._data["var"][key] = {"value": {key: value}}
|
||||||
except (
|
|
||||||
ruamel.yaml.composer.ComposerError, ruamel.yaml.scanner.ScannerError
|
|
||||||
) as e:
|
|
||||||
message = "{} {}".format(e.context, e.problem)
|
|
||||||
self.log.sysexit_with_message(
|
|
||||||
"Unable to read yaml file {}\n{}".format(rfile, message)
|
|
||||||
)
|
|
||||||
|
|
||||||
def _parse_meta_file(self):
|
def _parse_meta_file(self):
|
||||||
|
self._data["meta"]["name"] = {"value": self.config.config["role_name"]}
|
||||||
|
|
||||||
for rfile in self._files_registry.get_files():
|
for rfile in self._files_registry.get_files():
|
||||||
if any("meta/main." + ext in rfile for ext in YAML_EXTENSIONS):
|
if any("meta/main." + ext in rfile for ext in YAML_EXTENSIONS):
|
||||||
with open(rfile, "r", encoding="utf8") as yaml_file:
|
with open(rfile, encoding="utf8") as yamlfile:
|
||||||
try:
|
try:
|
||||||
data = defaultdict(dict, ruamel.yaml.safe_load(yaml_file))
|
raw = parse_yaml(yamlfile)
|
||||||
|
except YAMLError as e:
|
||||||
|
sysexit_with_message("Failed to read yaml file", path=rfile, error=e)
|
||||||
|
|
||||||
|
data = defaultdict(dict, raw)
|
||||||
if data.get("galaxy_info"):
|
if data.get("galaxy_info"):
|
||||||
for key, value in data.get("galaxy_info").items():
|
for key, value in data.get("galaxy_info").items():
|
||||||
self._data["meta"][key] = {"value": value}
|
self._data["meta"][key] = {"value": value}
|
||||||
|
|
||||||
if data.get("dependencies") is not None:
|
if data.get("dependencies") is not None:
|
||||||
self._data["meta"]["dependencies"] = {
|
self._data["meta"]["dependencies"] = {"value": data.get("dependencies")}
|
||||||
"value": data.get("dependencies")
|
|
||||||
}
|
|
||||||
|
|
||||||
self._data["meta"]["name"] = {"value": self.config.config["role_name"]}
|
|
||||||
except (
|
|
||||||
ruamel.yaml.composer.ComposerError, ruamel.yaml.scanner.ScannerError
|
|
||||||
) as e:
|
|
||||||
message = "{} {}".format(e.context, e.problem)
|
|
||||||
self.log.sysexit_with_message(
|
|
||||||
"Unable to read yaml file {}\n{}".format(rfile, message)
|
|
||||||
)
|
|
||||||
|
|
||||||
def _parse_task_tags(self):
|
def _parse_task_tags(self):
|
||||||
for rfile in self._files_registry.get_files():
|
for rfile in self._files_registry.get_files():
|
||||||
if any(fnmatch.fnmatch(rfile, "*/tasks/*." + ext) for ext in YAML_EXTENSIONS):
|
if any(fnmatch.fnmatch(rfile, "*/tasks/*." + ext) for ext in YAML_EXTENSIONS):
|
||||||
with open(rfile, "r", encoding="utf8") as yaml_file:
|
with open(rfile, encoding="utf8") as yamlfile:
|
||||||
try:
|
try:
|
||||||
data = ruamel.yaml.safe_load(yaml_file)
|
raw = parse_yaml_ansible(yamlfile)
|
||||||
except (
|
except YAMLError as e:
|
||||||
ruamel.yaml.composer.ComposerError, ruamel.yaml.scanner.ScannerError
|
sysexit_with_message("Failed to read yaml file", path=rfile, error=e)
|
||||||
) as e:
|
|
||||||
message = "{} {}".format(e.context, e.problem)
|
|
||||||
self.log.sysexit_with_message(
|
|
||||||
"Unable to read yaml file {}\n{}".format(rfile, message)
|
|
||||||
)
|
|
||||||
|
|
||||||
tags_found = nested_lookup("tags", data)
|
tags = []
|
||||||
for tag in tags_found:
|
for task in raw:
|
||||||
self._data["tags"][tag] = {}
|
task_tags = task.get("tags", [])
|
||||||
|
if isinstance(task_tags, str):
|
||||||
|
task_tags = [task_tags]
|
||||||
|
|
||||||
|
for tag in task_tags:
|
||||||
|
if tag not in self.config.config["exclude_tags"]:
|
||||||
|
tags.append(tag)
|
||||||
|
|
||||||
|
for tag in flatten(tags):
|
||||||
|
self._data["tag"][tag] = {"value": tag}
|
||||||
|
|
||||||
def _populate_doc_data(self):
|
def _populate_doc_data(self):
|
||||||
"""Generate the documentation data object."""
|
"""Generate the documentation data object."""
|
||||||
tags = defaultdict(dict)
|
tags = defaultdict(dict)
|
||||||
for annotaion in self.config.get_annotations_names(automatic=True):
|
for annotation in self.config.get_annotations_names(automatic=True):
|
||||||
self.logger.info("Finding annotations for: @" + annotaion)
|
self.log.info(f"Lookup annotation @{annotation}")
|
||||||
self._annotation_objs[annotaion] = Annotation(
|
self._annotation_objs[annotation] = Annotation(
|
||||||
name=annotaion, files_registry=self._files_registry
|
name=annotation, files_registry=self._files_registry
|
||||||
)
|
)
|
||||||
tags[annotaion] = self._annotation_objs[annotaion].get_details()
|
tags[annotation] = self._annotation_objs[annotation].get_details()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
anyconfig.merge(self._data, tags, ac_merge=anyconfig.MS_DICTS)
|
anyconfig.merge(self._data, tags, ac_merge=anyconfig.MS_DICTS)
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
self.log.sysexit_with_message("Unable to merge annotation values:\n{}".format(e))
|
sysexit_with_message("Failed to merge annotation values", error=e)
|
||||||
|
|
||||||
def get_data(self):
|
def get_data(self):
|
||||||
return self._data
|
return self._data
|
||||||
|
|
|
@ -1,22 +1,26 @@
|
||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
"""Custom exceptions."""
|
"""Doctor exception module."""
|
||||||
|
|
||||||
|
|
||||||
class DoctorError(Exception):
|
class DoctorError(Exception):
|
||||||
"""Generic exception class for ansible-doctor."""
|
"""Define generic exception."""
|
||||||
|
|
||||||
def __init__(self, msg, original_exception=""):
|
def __init__(self, msg, original_exception=""):
|
||||||
super(DoctorError, self).__init__("{msg}\n{org}".format(msg=msg, org=original_exception))
|
super().__init__(f"{msg}\n{original_exception}")
|
||||||
self.original_exception = original_exception
|
self.original_exception = original_exception
|
||||||
|
|
||||||
|
|
||||||
|
class YAMLError(DoctorError):
|
||||||
|
"""Errors while reading a yaml file."""
|
||||||
|
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class ConfigError(DoctorError):
|
class ConfigError(DoctorError):
|
||||||
"""Errors related to config file handling."""
|
"""Errors related to config file handling."""
|
||||||
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class InputError(DoctorError):
|
class TemplateError(DoctorError):
|
||||||
"""Errors related to config file handling."""
|
"""Errors related to template file handling."""
|
||||||
|
|
||||||
pass
|
|
||||||
|
|
|
@ -5,10 +5,10 @@ import glob
|
||||||
import os
|
import os
|
||||||
|
|
||||||
import pathspec
|
import pathspec
|
||||||
|
import structlog
|
||||||
|
|
||||||
from ansibledoctor.config import SingleConfig
|
from ansibledoctor.config import SingleConfig
|
||||||
from ansibledoctor.contstants import YAML_EXTENSIONS
|
from ansibledoctor.contstants import YAML_EXTENSIONS
|
||||||
from ansibledoctor.utils import SingleLog
|
|
||||||
|
|
||||||
|
|
||||||
class Registry:
|
class Registry:
|
||||||
|
@ -21,7 +21,7 @@ class Registry:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self._doc = []
|
self._doc = []
|
||||||
self.config = SingleConfig()
|
self.config = SingleConfig()
|
||||||
self.log = SingleLog().logger
|
self.log = structlog.get_logger()
|
||||||
self._scan_for_yamls()
|
self._scan_for_yamls()
|
||||||
|
|
||||||
def get_files(self):
|
def get_files(self):
|
||||||
|
@ -35,24 +35,17 @@ class Registry:
|
||||||
:return: None
|
:return: None
|
||||||
"""
|
"""
|
||||||
extensions = YAML_EXTENSIONS
|
extensions = YAML_EXTENSIONS
|
||||||
role_dir = self.config.role_dir
|
base_dir = self.config.config.base_dir
|
||||||
role_name = os.path.basename(role_dir)
|
|
||||||
excludes = self.config.config.get("exclude_files")
|
excludes = self.config.config.get("exclude_files")
|
||||||
excludespec = pathspec.PathSpec.from_lines("gitwildmatch", excludes)
|
excludespec = pathspec.PathSpec.from_lines("gitwildmatch", excludes)
|
||||||
|
|
||||||
self.log.debug("Scan for files: " + role_dir)
|
self.log.debug("Lookup role files", path=base_dir)
|
||||||
|
|
||||||
for extension in extensions:
|
for extension in extensions:
|
||||||
pattern = os.path.join(role_dir, "**/*." + extension)
|
pattern = os.path.join(base_dir, "**/*." + extension)
|
||||||
for filename in glob.iglob(pattern, recursive=True):
|
for filename in glob.iglob(pattern, recursive=True):
|
||||||
if not excludespec.match_file(filename):
|
if not excludespec.match_file(filename):
|
||||||
self.log.debug(
|
self.log.debug("Found role file", path=os.path.relpath(filename, base_dir))
|
||||||
"Adding file to '{}': {}".format(
|
|
||||||
role_name, os.path.relpath(filename, role_dir)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
self._doc.append(filename)
|
self._doc.append(filename)
|
||||||
else:
|
else:
|
||||||
self.log.debug(
|
self.log.debug("Skippped role file", path=os.path.relpath(filename, base_dir))
|
||||||
"Excluding file: {}".format(os.path.relpath(filename, role_dir))
|
|
||||||
)
|
|
||||||
|
|
113
ansibledoctor/template.py
Normal file
113
ansibledoctor/template.py
Normal file
|
@ -0,0 +1,113 @@
|
||||||
|
"""Module for handling templates."""
|
||||||
|
|
||||||
|
import atexit
|
||||||
|
import glob
|
||||||
|
import ntpath
|
||||||
|
import os
|
||||||
|
import shutil
|
||||||
|
import tempfile
|
||||||
|
|
||||||
|
import structlog
|
||||||
|
from git import GitCommandError, Repo
|
||||||
|
|
||||||
|
import ansibledoctor.exception
|
||||||
|
from ansibledoctor.utils import sysexit_with_message
|
||||||
|
|
||||||
|
|
||||||
|
class Template:
|
||||||
|
"""
|
||||||
|
Represents a template that can be used to generate content.
|
||||||
|
|
||||||
|
Templates can be sourced from a local file or a Git repository. The `Template` class handles
|
||||||
|
the initialization and setup of a template, including cloning a Git repository if necessary.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
----
|
||||||
|
name (str): The name of the template.
|
||||||
|
src (str): The source of the template, in the format `<provider>><path>`.
|
||||||
|
Supported providers are `local` and `git`.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
------
|
||||||
|
ansibledoctor.exception.TemplateError
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, name, src):
|
||||||
|
self.log = structlog.get_logger()
|
||||||
|
self.name = name
|
||||||
|
self.src = src
|
||||||
|
|
||||||
|
try:
|
||||||
|
provider, path = self.src.split(">", 1)
|
||||||
|
except ValueError as e:
|
||||||
|
raise ansibledoctor.exception.TemplateError(
|
||||||
|
"Error reading template src", str(e)
|
||||||
|
) from e
|
||||||
|
|
||||||
|
self.provider = provider.strip().lower()
|
||||||
|
self.path = path.strip()
|
||||||
|
|
||||||
|
if self.provider == "local":
|
||||||
|
self.path = os.path.realpath(os.path.join(self.path, self.name))
|
||||||
|
elif self.provider == "git":
|
||||||
|
repo_url, branch_or_tag = (
|
||||||
|
self.path.split("#", 1) if "#" in self.path else (self.path, None)
|
||||||
|
)
|
||||||
|
temp_dir = self._clone_repo(repo_url, branch_or_tag)
|
||||||
|
self.path = os.path.join(temp_dir, self.name)
|
||||||
|
else:
|
||||||
|
raise ansibledoctor.exception.TemplateError(
|
||||||
|
f"Unsupported template provider: {provider}"
|
||||||
|
)
|
||||||
|
|
||||||
|
self.files = self._scan_files()
|
||||||
|
|
||||||
|
def _clone_repo(self, repo_url, branch_or_tag=None):
|
||||||
|
temp_dir = tempfile.mkdtemp(prefix="ansibledoctor-")
|
||||||
|
atexit.register(self._cleanup_temp_dir, temp_dir)
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.log.debug("Cloning template repo", src=repo_url)
|
||||||
|
repo = Repo.clone_from(repo_url, temp_dir)
|
||||||
|
if branch_or_tag:
|
||||||
|
self.log.debug(f"Checking out branch or tag: {branch_or_tag}")
|
||||||
|
try:
|
||||||
|
repo.git.checkout(branch_or_tag)
|
||||||
|
except GitCommandError as e:
|
||||||
|
raise ansibledoctor.exception.TemplateError(
|
||||||
|
f"Error checking out branch or tag: {branch_or_tag}: {e}"
|
||||||
|
) from e
|
||||||
|
|
||||||
|
return temp_dir
|
||||||
|
except GitCommandError as e:
|
||||||
|
msg = e.stderr.strip("'").strip()
|
||||||
|
msg = msg.removeprefix("stderr: ")
|
||||||
|
|
||||||
|
raise ansibledoctor.exception.TemplateError(
|
||||||
|
f"Error cloning Git repository: {msg}"
|
||||||
|
) from e
|
||||||
|
|
||||||
|
def _scan_files(self):
|
||||||
|
"""Search for Jinja2 (.j2) files to apply to the destination."""
|
||||||
|
template_files = []
|
||||||
|
|
||||||
|
if os.path.isdir(self.path):
|
||||||
|
self.log.info("Lookup template files", src=self.src)
|
||||||
|
else:
|
||||||
|
sysexit_with_message("Can not open template directory", path=self.path)
|
||||||
|
|
||||||
|
for file in glob.iglob(self.path + "/**/*.j2", recursive=True):
|
||||||
|
relative_file = file[len(self.path) + 1 :]
|
||||||
|
if ntpath.basename(file)[:1] != "_":
|
||||||
|
self.log.debug("Found template file", path=relative_file)
|
||||||
|
template_files.append(relative_file)
|
||||||
|
else:
|
||||||
|
self.log.debug("Skipped template file", path=relative_file)
|
||||||
|
|
||||||
|
return template_files
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _cleanup_temp_dir(temp_dir):
|
||||||
|
if temp_dir and os.path.exists(temp_dir):
|
||||||
|
shutil.rmtree(temp_dir)
|
|
@ -9,7 +9,7 @@
|
||||||
{% set deps = meta.dependencies.value %}
|
{% set deps = meta.dependencies.value %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% for item in deps %}
|
{% for item in deps %}
|
||||||
* {{ item }}
|
- {{ item }}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% else %}
|
{% else %}
|
||||||
None.
|
None.
|
||||||
|
|
7
ansibledoctor/templates/hugo-book/_requirements.j2
Normal file
7
ansibledoctor/templates/hugo-book/_requirements.j2
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
{% if meta | deep_get(meta, "min_ansible_version.value") %}
|
||||||
|
- Minimum Ansible version: `{{ meta.min_ansible_version.value }}`
|
||||||
|
{% else %}
|
||||||
|
None.
|
||||||
|
{% endif %}
|
13
ansibledoctor/templates/hugo-book/_tag.j2
Normal file
13
ansibledoctor/templates/hugo-book/_tag.j2
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
{% set tag = role.tag | default({}) %}
|
||||||
|
{% if tag %}
|
||||||
|
## Discovered Tags
|
||||||
|
{% for key, item in tag | dictsort %}
|
||||||
|
|
||||||
|
{{ key }}
|
||||||
|
{% if item.description is defined and item.description | safe_join(" ") | striptags %}
|
||||||
|
: {{ item.description | safe_join(" ") | striptags }}
|
||||||
|
{% else %}
|
||||||
|
:
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
|
@ -1,10 +1,19 @@
|
||||||
|
- [Requirements](#requirements)
|
||||||
{% set var = role.var | default({}) %}
|
{% set var = role.var | default({}) %}
|
||||||
{% if var %}
|
{% if var %}
|
||||||
* [Default Variables](#default-variables)
|
- [Default Variables](#default-variables)
|
||||||
|
{% if not options.tabulate_vars %}
|
||||||
{% for key, item in var | dictsort %}
|
{% for key, item in var | dictsort %}
|
||||||
* [{{ key }}](#{{ key }})
|
- [{{ key }}](#{{ key }})
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
* [Dependencies](#dependencies)
|
{% endif %}
|
||||||
|
{% if tag %}
|
||||||
|
- [Discovered Tags](#discovered-tags)
|
||||||
|
{% endif %}
|
||||||
|
{% if todo %}
|
||||||
|
- [Open Tasks](#open-tasks)
|
||||||
|
{% endif %}
|
||||||
|
- [Dependencies](#dependencies)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
19
ansibledoctor/templates/hugo-book/_todo.j2
Normal file
19
ansibledoctor/templates/hugo-book/_todo.j2
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
{% set todo = role.todo | default({}) %}
|
||||||
|
{% if todo %}
|
||||||
|
## Open Tasks
|
||||||
|
|
||||||
|
{% for key, item in todo | dictsort %}
|
||||||
|
{% for line in item %}
|
||||||
|
{% if line.value is defined and line.value | safe_join(" ") | striptags and key == "default" %}
|
||||||
|
- {{ line.value | safe_join(" ") | striptags }}
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
{% endfor %}
|
||||||
|
{% for key, item in todo | dictsort %}
|
||||||
|
{% for line in item %}
|
||||||
|
{% if line.value is defined and line.value | safe_join(" ") | striptags and key != "default" %}
|
||||||
|
- ({{ key }}): {{ line.value | safe_join(" ") | striptags }}
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
|
@ -1,13 +1,29 @@
|
||||||
{% set var = role.var | default({}) %}
|
{% set var = role.var | default({}) %}
|
||||||
{% if var %}
|
{% if var %}
|
||||||
|
|
||||||
## Default Variables
|
## Default Variables
|
||||||
{% for key, item in var | dictsort %}
|
{% for key, item in var | dictsort %}
|
||||||
|
|
||||||
### {{ key }}
|
### {{ key }}
|
||||||
{% if item.description is defined and item.description %}
|
{% if item.description is defined and item.description %}
|
||||||
|
{% set description = [item.description] if item.description is string else item.description %}
|
||||||
|
|
||||||
{{ item.description | save_join(" ") | striptags }}
|
{{ description | map("replace", "\n\n", "\n") | safe_join("\n") }}
|
||||||
|
{% endif %}
|
||||||
|
{% if item.deprecated is defined or item.type is defined %}
|
||||||
|
|
||||||
|
{% if item.deprecated is defined %}
|
||||||
|
{% set deprecated = [item.deprecated] if item.deprecated is string else item.deprecated %}
|
||||||
|
{% set deprecated_string = deprecated | map("replace", "\n\n", "\n") | safe_join("\n") %}
|
||||||
|
{% if deprecated_string %}
|
||||||
|
**_Deprecated:_** {{ deprecated_string }}<br />
|
||||||
|
{% else %}
|
||||||
|
**_Deprecated_**<br />
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
|
{% if item.type is defined and item.type %}
|
||||||
|
{% set type = [item.type] if item.type is string else item.type %}
|
||||||
|
**_Type:_** {{ type | map("replace", "\n\n", "\n") | safe_join("\n") }}<br />
|
||||||
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if item.value is defined and item.value %}
|
{% if item.value is defined and item.value %}
|
||||||
|
|
||||||
|
@ -18,7 +34,7 @@
|
||||||
{{ item.value | to_nice_yaml(indent=2) }}
|
{{ item.value | to_nice_yaml(indent=2) }}
|
||||||
{% else %}
|
{% else %}
|
||||||
{% for ve_line in item.value %}
|
{% for ve_line in item.value %}
|
||||||
{{ ve_line }}
|
{{ ve_line | replace("\n\n", "\n") }}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
```
|
```
|
||||||
|
@ -32,7 +48,7 @@
|
||||||
{{ item.example | to_nice_yaml(indent=2) }}
|
{{ item.example | to_nice_yaml(indent=2) }}
|
||||||
{% else %}
|
{% else %}
|
||||||
{% for ex_line in item.example %}
|
{% for ex_line in item.example %}
|
||||||
{{ ex_line }}
|
{{ ex_line | replace("\n\n", "\n") }}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
```
|
```
|
||||||
|
|
49
ansibledoctor/templates/hugo-book/_vars_tabulated.j2
Normal file
49
ansibledoctor/templates/hugo-book/_vars_tabulated.j2
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
{% set var = role.var | default({}) %}
|
||||||
|
{% if var %}
|
||||||
|
## Default Variables
|
||||||
|
|
||||||
|
{% set columns = ["variable", "default", "description", "type", "deprecated", "example"] %}
|
||||||
|
{% set found_columns = ["variable", "default"] + var.values() | map("list") | sum(start=["key"]) | unique | list %}
|
||||||
|
{% for c in columns %}
|
||||||
|
{% if c in found_columns %}
|
||||||
|
|{{ c | capitalize -}}
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
|
|
||||||
|
{% for c in columns %}
|
||||||
|
{% if c in found_columns %}
|
||||||
|
|{{ "-" * (c | length) -}}
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
|
|
||||||
|
{% for key, item in var | dictsort %}
|
||||||
|
|{{ key -}}
|
||||||
|
|{{ (item.value | default({}))[key] | default -}}
|
||||||
|
{% if "description" in found_columns %}
|
||||||
|
|{{ item.description | default([]) | safe_join("<br />") | replace("\n", "<br />") | replace("|", "\|") -}}
|
||||||
|
{% endif %}
|
||||||
|
{% if "type" in found_columns %}
|
||||||
|
|{{ item.type | default([]) | join("<br />") -}}
|
||||||
|
{% endif %}
|
||||||
|
{% if "deprecated" in found_columns %}
|
||||||
|
|
|
||||||
|
{%- if "deprecated" in found_columns %}
|
||||||
|
{% if item.deprecated is defined %}
|
||||||
|
{% set deprecated = [item.deprecated] if item.deprecated is string else item.deprecated %}
|
||||||
|
{% set deprecated_string = deprecated | map("replace", "\n", "<br />") | safe_join("<br />") %}
|
||||||
|
{% if deprecated_string -%}
|
||||||
|
{{ deprecated_string }}
|
||||||
|
{%- else -%}
|
||||||
|
True
|
||||||
|
{%- endif %}
|
||||||
|
{%- else -%}
|
||||||
|
False
|
||||||
|
{%- endif %}
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
|
{% if "example" in found_columns %}
|
||||||
|
|{{ item.example | default([]) | safe_join("<br />") | replace("\n", "<br />") | replace("|", "\|") -}}
|
||||||
|
{% endif %}
|
||||||
|
|
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
|
@ -1,20 +1,39 @@
|
||||||
{% if not append | deep_get(role, "internal.append") %}
|
{% if not append | deep_get(role, "internal.append") %}
|
||||||
{% set meta = role.meta | default({}) %}
|
{% set meta = role.meta | default({}) %}
|
||||||
---
|
---
|
||||||
title: {{ meta.name.value | save_join(" ") }}
|
title: {{ meta.name.value | safe_join(" ") }}
|
||||||
type: docs
|
type: docs
|
||||||
|
{% if summary | deep_get(meta, "summary.value") %}
|
||||||
|
summary: {{ meta.summary.value | safe_join(" ") }}
|
||||||
|
{% endif %}
|
||||||
---
|
---
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if description | deep_get(meta, "description.value") %}
|
{% if description | deep_get(meta, "description.value") %}
|
||||||
|
{% set description = [meta.description.value] if meta.description.value is string else meta.description.value %}
|
||||||
|
|
||||||
{{ meta.description.value | save_join(" ") }}
|
{{ description | map("replace", "\n\n", "\n") | safe_join("\n") }}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
<!--more-->
|
||||||
|
|
||||||
{# TOC #}
|
{# TOC #}
|
||||||
{% include '_toc.j2' %}
|
{% include '_toc.j2' +%}
|
||||||
|
|
||||||
|
{# Requirements #}
|
||||||
|
{% include '_requirements.j2' %}
|
||||||
|
|
||||||
{# Vars #}
|
{# Vars #}
|
||||||
|
{% if options.tabulate_vars %}
|
||||||
|
{% include '_vars_tabulated.j2' %}
|
||||||
|
{% else %}
|
||||||
{% include '_vars.j2' %}
|
{% include '_vars.j2' %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{# Tag #}
|
||||||
|
{% include '_tag.j2' %}
|
||||||
|
|
||||||
|
{# Todo #}
|
||||||
|
{% include '_todo.j2' %}
|
||||||
|
|
||||||
{# Meta #}
|
{# Meta #}
|
||||||
{% include '_meta.j2' %}
|
{% include '_meta.j2' %}
|
||||||
|
|
|
@ -1,17 +1,31 @@
|
||||||
{% if not append | deep_get(role, "internal.append") %}
|
{% if not append | deep_get(role, "internal.append") %}
|
||||||
{% set meta = role.meta | default({}) %}
|
{% set meta = role.meta | default({}) %}
|
||||||
# {{ meta.name.value | save_join(" ") }}
|
# {{ meta.name.value | safe_join(" ") }}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if description | deep_get(meta, "description.value") %}
|
{% if description | deep_get(meta, "description.value") %}
|
||||||
|
{% set description = [meta.description.value] if meta.description.value is string else meta.description.value %}
|
||||||
|
|
||||||
{{ meta.description.value | save_join(" ") }}
|
{{ description | map("replace", "\n\n", "\n") | safe_join("\n") }}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{# TOC #}
|
{# TOC #}
|
||||||
{% include '_toc.j2' %}
|
{% include '_toc.j2' +%}
|
||||||
|
|
||||||
|
{# Requirements #}
|
||||||
|
{% include '_requirements.j2' %}
|
||||||
|
|
||||||
{# Vars #}
|
{# Vars #}
|
||||||
|
{% if options.tabulate_vars %}
|
||||||
|
{% include '_vars_tabulated.j2' %}
|
||||||
|
{% else %}
|
||||||
{% include '_vars.j2' %}
|
{% include '_vars.j2' %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{# Tag #}
|
||||||
|
{% include '_tag.j2' %}
|
||||||
|
|
||||||
|
{# Todo #}
|
||||||
|
{% include '_todo.j2' %}
|
||||||
|
|
||||||
{# Meta #}
|
{# Meta #}
|
||||||
{% include '_meta.j2' %}
|
{% include '_meta.j2' %}
|
||||||
|
|
|
@ -9,7 +9,9 @@
|
||||||
{% set deps = meta.dependencies.value %}
|
{% set deps = meta.dependencies.value %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% for item in deps %}
|
{% for item in deps %}
|
||||||
* {{ item }}
|
{% if item is string or item.role %}
|
||||||
|
- {{ item if item is string else item.role }}
|
||||||
|
{% endif %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% else %}
|
{% else %}
|
||||||
None.
|
None.
|
||||||
|
@ -18,12 +20,12 @@ None.
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
{{ meta.license.value }}
|
{{ meta.license.value | safe_join(" ") }}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if author | deep_get(meta, "author.value") %}
|
{% if author | deep_get(meta, "author.value") %}
|
||||||
|
|
||||||
## Author
|
## Author
|
||||||
|
|
||||||
{{ meta.author.value | save_join(" ") }}
|
{{ meta.author.value | safe_join(" ") }}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
7
ansibledoctor/templates/readme/_requirements.j2
Normal file
7
ansibledoctor/templates/readme/_requirements.j2
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
{% if meta | deep_get(meta, "min_ansible_version.value") %}
|
||||||
|
- Minimum Ansible version: `{{ meta.min_ansible_version.value }}`
|
||||||
|
{% else %}
|
||||||
|
None.
|
||||||
|
{% endif %}
|
12
ansibledoctor/templates/readme/_tag.j2
Normal file
12
ansibledoctor/templates/readme/_tag.j2
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
{% set tag = role.tag | default({}) %}
|
||||||
|
{% if tag %}
|
||||||
|
## Discovered Tags
|
||||||
|
{% for key, item in tag | dictsort %}
|
||||||
|
{% set is_desc = item.description is defined and item.description | safe_join(" ") | striptags %}
|
||||||
|
|
||||||
|
**_{{ key }}_**{{ "\\" if is_desc else "" }}
|
||||||
|
{% if is_desc %}
|
||||||
|
 {{ item.description | safe_join(" ") | striptags }}
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
|
@ -1,14 +1,23 @@
|
||||||
## Table of content
|
## Table of content
|
||||||
|
|
||||||
|
- [Requirements](#requirements)
|
||||||
{% set var = role.var | default({}) %}
|
{% set var = role.var | default({}) %}
|
||||||
{% if var %}
|
{% if var %}
|
||||||
* [Default Variables](#default-variables)
|
- [Default Variables](#default-variables)
|
||||||
|
{% if not options.tabulate_vars %}
|
||||||
{% for key, item in var | dictsort %}
|
{% for key, item in var | dictsort %}
|
||||||
* [{{ key }}](#{{ key }})
|
- [{{ key }}](#{{ key }})
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
* [Dependencies](#dependencies)
|
{% endif %}
|
||||||
* [License](#license)
|
{% if tag %}
|
||||||
* [Author](#author)
|
- [Discovered Tags](#discovered-tags)
|
||||||
|
{% endif %}
|
||||||
|
{% if todo %}
|
||||||
|
- [Open Tasks](#open-tasks)
|
||||||
|
{% endif %}
|
||||||
|
- [Dependencies](#dependencies)
|
||||||
|
- [License](#license)
|
||||||
|
- [Author](#author)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
19
ansibledoctor/templates/readme/_todo.j2
Normal file
19
ansibledoctor/templates/readme/_todo.j2
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
{% set todo = role.todo | default({}) %}
|
||||||
|
{% if todo %}
|
||||||
|
## Open Tasks
|
||||||
|
|
||||||
|
{% for key, item in todo | dictsort %}
|
||||||
|
{% for line in item %}
|
||||||
|
{% if line.value is defined and line.value | safe_join(" ") | striptags and key == "default" %}
|
||||||
|
- {{ line.value | safe_join(" ") | striptags }}
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
{% endfor %}
|
||||||
|
{% for key, item in todo | dictsort %}
|
||||||
|
{% for line in item %}
|
||||||
|
{% if line.value is defined and line.value | safe_join(" ") | striptags and key != "default" %}
|
||||||
|
- ({{ key }}): {{ line.value | safe_join(" ") | striptags }}
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
|
@ -1,13 +1,29 @@
|
||||||
{% set var = role.var | default({}) %}
|
{% set var = role.var | default({}) %}
|
||||||
{% if var %}
|
{% if var %}
|
||||||
|
|
||||||
## Default Variables
|
## Default Variables
|
||||||
{% for key, item in var | dictsort %}
|
{% for key, item in var | dictsort %}
|
||||||
|
|
||||||
### {{ key }}
|
### {{ key }}
|
||||||
{% if item.description is defined and item.description %}
|
{% if item.description is defined and item.description %}
|
||||||
|
{% set description = [item.description] if item.description is string else item.description %}
|
||||||
|
|
||||||
{{ item.description | save_join(" ") | striptags }}
|
{{ description | map("replace", "\n\n", "\n") | safe_join("\n") }}
|
||||||
|
{% endif %}
|
||||||
|
{% if item.deprecated is defined or item.type is defined %}
|
||||||
|
|
||||||
|
{% if item.deprecated is defined %}
|
||||||
|
{% set deprecated = [item.deprecated] if item.deprecated is string else item.deprecated %}
|
||||||
|
{% set deprecated_string = deprecated | map("replace", "\n\n", "\n") | safe_join("\n") %}
|
||||||
|
{% if deprecated_string %}
|
||||||
|
**_Deprecated:_** {{ deprecated_string }}<br />
|
||||||
|
{% else %}
|
||||||
|
**_Deprecated_**<br />
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
|
{% if item.type is defined and item.type %}
|
||||||
|
{% set type = [item.type] if item.type is string else item.type %}
|
||||||
|
**_Type:_** {{ type | map("replace", "\n\n", "\n") | safe_join("\n") }}<br />
|
||||||
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if item.value is defined and item.value %}
|
{% if item.value is defined and item.value %}
|
||||||
|
|
||||||
|
@ -18,7 +34,7 @@
|
||||||
{{ item.value | to_nice_yaml(indent=2) }}
|
{{ item.value | to_nice_yaml(indent=2) }}
|
||||||
{% else %}
|
{% else %}
|
||||||
{% for ve_line in item.value %}
|
{% for ve_line in item.value %}
|
||||||
{{ ve_line }}
|
{{ ve_line | replace("\n\n", "\n") }}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
```
|
```
|
||||||
|
@ -32,7 +48,7 @@
|
||||||
{{ item.example | to_nice_yaml(indent=2) }}
|
{{ item.example | to_nice_yaml(indent=2) }}
|
||||||
{% else %}
|
{% else %}
|
||||||
{% for ex_line in item.example %}
|
{% for ex_line in item.example %}
|
||||||
{{ ex_line }}
|
{{ ex_line | replace("\n\n", "\n") }}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
```
|
```
|
||||||
|
|
49
ansibledoctor/templates/readme/_vars_tabulated.j2
Normal file
49
ansibledoctor/templates/readme/_vars_tabulated.j2
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
{% set var = role.var | default({}) %}
|
||||||
|
{% if var %}
|
||||||
|
## Default Variables
|
||||||
|
|
||||||
|
{% set columns = ["variable", "default", "description", "type", "deprecated", "example"] %}
|
||||||
|
{% set found_columns = ["variable", "default"] + var.values() | map("list") | sum(start=["key"]) | unique | list %}
|
||||||
|
{% for c in columns %}
|
||||||
|
{% if c in found_columns %}
|
||||||
|
|{{ c | capitalize -}}
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
|
|
||||||
|
{% for c in columns %}
|
||||||
|
{% if c in found_columns %}
|
||||||
|
|{{ "-" * (c | length) -}}
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
|
|
||||||
|
{% for key, item in var | dictsort %}
|
||||||
|
|{{ key -}}
|
||||||
|
|{{ (item.value | default({}))[key] | default -}}
|
||||||
|
{% if "description" in found_columns %}
|
||||||
|
|{{ item.description | default([]) | safe_join("<br />") | replace("\n", "<br />") | replace("|", "\|") -}}
|
||||||
|
{% endif %}
|
||||||
|
{% if "type" in found_columns %}
|
||||||
|
|{{ item.type | default([]) | join("<br />") -}}
|
||||||
|
{% endif %}
|
||||||
|
{% if "deprecated" in found_columns %}
|
||||||
|
|
|
||||||
|
{%- if "deprecated" in found_columns %}
|
||||||
|
{% if item.deprecated is defined %}
|
||||||
|
{% set deprecated = [item.deprecated] if item.deprecated is string else item.deprecated %}
|
||||||
|
{% set deprecated_string = deprecated | map("replace", "\n", "<br />") | safe_join("<br />") %}
|
||||||
|
{% if deprecated_string -%}
|
||||||
|
{{ deprecated_string }}
|
||||||
|
{%- else -%}
|
||||||
|
True
|
||||||
|
{%- endif %}
|
||||||
|
{%- else -%}
|
||||||
|
False
|
||||||
|
{%- endif %}
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
|
{% if "example" in found_columns %}
|
||||||
|
|{{ item.example | default([]) | safe_join("<br />") | replace("\n", "<br />") | replace("|", "\|") -}}
|
||||||
|
{% endif %}
|
||||||
|
|
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
|
@ -1,275 +0,0 @@
|
||||||
#!/usr/bin/env python3
|
|
||||||
"""Global utility methods and classes."""
|
|
||||||
|
|
||||||
import logging
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
from distutils.util import strtobool
|
|
||||||
|
|
||||||
import colorama
|
|
||||||
from pythonjsonlogger import jsonlogger
|
|
||||||
|
|
||||||
import ansibledoctor.exception
|
|
||||||
|
|
||||||
CONSOLE_FORMAT = "{}{}[%(levelname)s]{} %(message)s"
|
|
||||||
JSON_FORMAT = "(asctime) (levelname) (message)"
|
|
||||||
|
|
||||||
|
|
||||||
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)
|
|
||||||
|
|
||||||
return sys.stdout.isatty() and os.environ.get("TERM") != "dumb"
|
|
||||||
|
|
||||||
|
|
||||||
colorama.init(autoreset=True, strip=not _should_do_markup())
|
|
||||||
|
|
||||||
|
|
||||||
class Singleton(type):
|
|
||||||
"""Meta singleton class."""
|
|
||||||
|
|
||||||
_instances = {}
|
|
||||||
|
|
||||||
def __call__(cls, *args, **kwargs):
|
|
||||||
if cls not in cls._instances:
|
|
||||||
cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs)
|
|
||||||
return cls._instances[cls]
|
|
||||||
|
|
||||||
|
|
||||||
class LogFilter(object):
|
|
||||||
"""A custom log filter which excludes log messages above the logged level."""
|
|
||||||
|
|
||||||
def __init__(self, level):
|
|
||||||
"""
|
|
||||||
Initialize a new custom log filter.
|
|
||||||
|
|
||||||
:param level: Log level limit
|
|
||||||
:returns: None
|
|
||||||
|
|
||||||
"""
|
|
||||||
self.__level = level
|
|
||||||
|
|
||||||
def filter(self, logRecord): # noqa
|
|
||||||
# https://docs.python.org/3/library/logging.html#logrecord-attributes
|
|
||||||
return logRecord.levelno <= self.__level
|
|
||||||
|
|
||||||
|
|
||||||
class MultilineFormatter(logging.Formatter):
|
|
||||||
"""Logging Formatter to reset color after newline characters."""
|
|
||||||
|
|
||||||
def format(self, record): # noqa
|
|
||||||
record.msg = record.msg.replace("\n", "\n{}... ".format(colorama.Style.RESET_ALL))
|
|
||||||
return logging.Formatter.format(self, record)
|
|
||||||
|
|
||||||
|
|
||||||
class MultilineJsonFormatter(jsonlogger.JsonFormatter):
|
|
||||||
"""Logging Formatter to remove newline characters."""
|
|
||||||
|
|
||||||
def format(self, record): # noqa
|
|
||||||
record.msg = record.msg.replace("\n", " ")
|
|
||||||
return jsonlogger.JsonFormatter.format(self, record)
|
|
||||||
|
|
||||||
|
|
||||||
class Log:
|
|
||||||
"""Handle logging."""
|
|
||||||
|
|
||||||
def __init__(self, level=logging.WARN, name="ansibledoctor", json=False):
|
|
||||||
self.logger = logging.getLogger(name)
|
|
||||||
self.logger.setLevel(level)
|
|
||||||
self.logger.addHandler(self._get_error_handler(json=json))
|
|
||||||
self.logger.addHandler(self._get_warn_handler(json=json))
|
|
||||||
self.logger.addHandler(self._get_info_handler(json=json))
|
|
||||||
self.logger.addHandler(self._get_critical_handler(json=json))
|
|
||||||
self.logger.addHandler(self._get_debug_handler(json=json))
|
|
||||||
self.logger.propagate = False
|
|
||||||
|
|
||||||
def _get_error_handler(self, json=False):
|
|
||||||
handler = logging.StreamHandler(sys.stderr)
|
|
||||||
handler.setLevel(logging.ERROR)
|
|
||||||
handler.addFilter(LogFilter(logging.ERROR))
|
|
||||||
handler.setFormatter(
|
|
||||||
MultilineFormatter(
|
|
||||||
self.error(
|
|
||||||
CONSOLE_FORMAT.format(
|
|
||||||
colorama.Fore.RED, colorama.Style.BRIGHT, colorama.Style.RESET_ALL
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
if json:
|
|
||||||
handler.setFormatter(MultilineJsonFormatter(JSON_FORMAT))
|
|
||||||
|
|
||||||
return handler
|
|
||||||
|
|
||||||
def _get_warn_handler(self, json=False):
|
|
||||||
handler = logging.StreamHandler(sys.stdout)
|
|
||||||
handler.setLevel(logging.WARN)
|
|
||||||
handler.addFilter(LogFilter(logging.WARN))
|
|
||||||
handler.setFormatter(
|
|
||||||
MultilineFormatter(
|
|
||||||
self.warn(
|
|
||||||
CONSOLE_FORMAT.format(
|
|
||||||
colorama.Fore.YELLOW, colorama.Style.BRIGHT, colorama.Style.RESET_ALL
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
if json:
|
|
||||||
handler.setFormatter(MultilineJsonFormatter(JSON_FORMAT))
|
|
||||||
|
|
||||||
return handler
|
|
||||||
|
|
||||||
def _get_info_handler(self, json=False):
|
|
||||||
handler = logging.StreamHandler(sys.stdout)
|
|
||||||
handler.setLevel(logging.INFO)
|
|
||||||
handler.addFilter(LogFilter(logging.INFO))
|
|
||||||
handler.setFormatter(
|
|
||||||
MultilineFormatter(
|
|
||||||
self.info(
|
|
||||||
CONSOLE_FORMAT.format(
|
|
||||||
colorama.Fore.CYAN, colorama.Style.BRIGHT, colorama.Style.RESET_ALL
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
if json:
|
|
||||||
handler.setFormatter(MultilineJsonFormatter(JSON_FORMAT))
|
|
||||||
|
|
||||||
return handler
|
|
||||||
|
|
||||||
def _get_critical_handler(self, json=False):
|
|
||||||
handler = logging.StreamHandler(sys.stderr)
|
|
||||||
handler.setLevel(logging.CRITICAL)
|
|
||||||
handler.addFilter(LogFilter(logging.CRITICAL))
|
|
||||||
handler.setFormatter(
|
|
||||||
MultilineFormatter(
|
|
||||||
self.critical(
|
|
||||||
CONSOLE_FORMAT.format(
|
|
||||||
colorama.Fore.RED, colorama.Style.BRIGHT, colorama.Style.RESET_ALL
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
if json:
|
|
||||||
handler.setFormatter(MultilineJsonFormatter(JSON_FORMAT))
|
|
||||||
|
|
||||||
return handler
|
|
||||||
|
|
||||||
def _get_debug_handler(self, json=False):
|
|
||||||
handler = logging.StreamHandler(sys.stderr)
|
|
||||||
handler.setLevel(logging.DEBUG)
|
|
||||||
handler.addFilter(LogFilter(logging.DEBUG))
|
|
||||||
handler.setFormatter(
|
|
||||||
MultilineFormatter(
|
|
||||||
self.critical(
|
|
||||||
CONSOLE_FORMAT.format(
|
|
||||||
colorama.Fore.BLUE, colorama.Style.BRIGHT, colorama.Style.RESET_ALL
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
if json:
|
|
||||||
handler.setFormatter(MultilineJsonFormatter(JSON_FORMAT))
|
|
||||||
|
|
||||||
return handler
|
|
||||||
|
|
||||||
def set_level(self, s):
|
|
||||||
self.logger.setLevel(s)
|
|
||||||
|
|
||||||
def debug(self, msg):
|
|
||||||
"""Format info messages and return string."""
|
|
||||||
return msg
|
|
||||||
|
|
||||||
def critical(self, msg):
|
|
||||||
"""Format critical messages and return string."""
|
|
||||||
return msg
|
|
||||||
|
|
||||||
def error(self, msg):
|
|
||||||
"""Format error messages and return string."""
|
|
||||||
return msg
|
|
||||||
|
|
||||||
def warn(self, msg):
|
|
||||||
"""Format warn messages and return string."""
|
|
||||||
return msg
|
|
||||||
|
|
||||||
def info(self, msg):
|
|
||||||
"""Format info messages and return string."""
|
|
||||||
return msg
|
|
||||||
|
|
||||||
def _color_text(self, color, msg):
|
|
||||||
"""
|
|
||||||
Colorize strings.
|
|
||||||
|
|
||||||
:param color: colorama color settings
|
|
||||||
:param msg: string to colorize
|
|
||||||
:returns: string
|
|
||||||
|
|
||||||
"""
|
|
||||||
return "{}{}{}".format(color, msg, colorama.Style.RESET_ALL)
|
|
||||||
|
|
||||||
def sysexit(self, code=1):
|
|
||||||
sys.exit(code)
|
|
||||||
|
|
||||||
def sysexit_with_message(self, msg, code=1):
|
|
||||||
self.logger.critical(str(msg))
|
|
||||||
self.sysexit(code)
|
|
||||||
|
|
||||||
|
|
||||||
class SingleLog(Log, metaclass=Singleton):
|
|
||||||
"""Singleton logging class."""
|
|
||||||
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class UnsafeTag:
|
|
||||||
"""Handle custom yaml unsafe tag."""
|
|
||||||
|
|
||||||
yaml_tag = u"!unsafe"
|
|
||||||
|
|
||||||
def __init__(self, value):
|
|
||||||
self.unsafe = value
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def yaml_constructor(loader, node):
|
|
||||||
return loader.construct_scalar(node)
|
|
||||||
|
|
||||||
|
|
||||||
class FileUtils:
|
|
||||||
"""Mics static methods for file handling."""
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def create_path(path):
|
|
||||||
os.makedirs(path, exist_ok=True)
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def query_yes_no(question, default=True):
|
|
||||||
"""Ask a yes/no question via input() and return their answer.
|
|
||||||
|
|
||||||
"question" is a string that is presented to the user.
|
|
||||||
"default" is the presumed answer if the user just hits <Enter>.
|
|
||||||
It must be "yes" (the default), "no" or None (meaning
|
|
||||||
an answer is required of the user).
|
|
||||||
|
|
||||||
The "answer" return value is one of "yes" or "no".
|
|
||||||
"""
|
|
||||||
if default:
|
|
||||||
prompt = "[Y/n]"
|
|
||||||
else:
|
|
||||||
prompt = "[N/y]"
|
|
||||||
|
|
||||||
try:
|
|
||||||
# input method is safe in python3
|
|
||||||
choice = input("{} {} ".format(question, prompt)) or default # nosec
|
|
||||||
return to_bool(choice)
|
|
||||||
except (KeyboardInterrupt, ValueError) as e:
|
|
||||||
raise ansibledoctor.exception.InputError("Error while reading input", e)
|
|
130
ansibledoctor/utils/__init__.py
Normal file
130
ansibledoctor/utils/__init__.py
Normal file
|
@ -0,0 +1,130 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Global utility methods and classes."""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
from collections.abc import Iterable
|
||||||
|
|
||||||
|
import structlog
|
||||||
|
|
||||||
|
|
||||||
|
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 flatten(items):
|
||||||
|
for x in items:
|
||||||
|
if isinstance(x, Iterable) and not isinstance(x, (str, bytes)):
|
||||||
|
yield from flatten(x)
|
||||||
|
else:
|
||||||
|
yield x
|
||||||
|
|
||||||
|
|
||||||
|
def _split_string(string, delimiter, escape, maxsplit=None):
|
||||||
|
result = []
|
||||||
|
current_element = []
|
||||||
|
iterator = iter(string)
|
||||||
|
count_split = 0
|
||||||
|
skip_split = False
|
||||||
|
|
||||||
|
for character in iterator:
|
||||||
|
if maxsplit and count_split >= maxsplit:
|
||||||
|
skip_split = True
|
||||||
|
|
||||||
|
if character == escape and not skip_split:
|
||||||
|
try:
|
||||||
|
next_character = next(iterator)
|
||||||
|
if next_character != delimiter and next_character != escape:
|
||||||
|
# Do not copy the escape character if it is intended to escape either the
|
||||||
|
# delimiter or the escape character itself. Copy the escape character
|
||||||
|
# if it is not used to escape either of these characters.
|
||||||
|
current_element.append(escape)
|
||||||
|
current_element.append(next_character)
|
||||||
|
count_split += 1
|
||||||
|
except StopIteration:
|
||||||
|
current_element.append(escape)
|
||||||
|
elif character == delimiter and not skip_split:
|
||||||
|
result.append("".join(current_element))
|
||||||
|
current_element = []
|
||||||
|
count_split += 1
|
||||||
|
else:
|
||||||
|
current_element.append(character)
|
||||||
|
|
||||||
|
result.append("".join(current_element))
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def sysexit(code=1):
|
||||||
|
sys.exit(code)
|
||||||
|
|
||||||
|
|
||||||
|
def sysexit_with_message(msg, code=1, **kwargs):
|
||||||
|
structlog.get_logger().critical(str(msg).strip(), **kwargs)
|
||||||
|
sysexit(code)
|
||||||
|
|
||||||
|
|
||||||
|
class Singleton(type):
|
||||||
|
"""Meta singleton class."""
|
||||||
|
|
||||||
|
_instances = {}
|
||||||
|
|
||||||
|
def __call__(cls, *args, **kwargs):
|
||||||
|
if cls not in cls._instances:
|
||||||
|
cls._instances[cls] = super().__call__(*args, **kwargs)
|
||||||
|
return cls._instances[cls]
|
||||||
|
|
||||||
|
|
||||||
|
class FileUtils:
|
||||||
|
"""Mics static methods for file handling."""
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def create_path(path):
|
||||||
|
os.makedirs(path, exist_ok=True)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def query_yes_no(question, default=True):
|
||||||
|
"""
|
||||||
|
Ask a yes/no question via input() and return their answer.
|
||||||
|
|
||||||
|
"question" is a string that is presented to the user.
|
||||||
|
"default" is the presumed answer if the user just hits <Enter>.
|
||||||
|
It must be "yes" (the default), "no" or None (meaning
|
||||||
|
an answer is required of the user).
|
||||||
|
|
||||||
|
The "answer" return value is one of "yes" or "no".
|
||||||
|
"""
|
||||||
|
prompt = "[Y/n]" if default else "[N/y]"
|
||||||
|
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
# input method is safe in python3
|
||||||
|
choice = input(f"{question} {prompt} ") or default # nosec
|
||||||
|
return to_bool(choice)
|
||||||
|
except ValueError:
|
||||||
|
print("Invalid input. Please enter 'y' or 'n'.") # noqa: T201
|
||||||
|
except KeyboardInterrupt as e:
|
||||||
|
raise e
|
78
ansibledoctor/utils/yamlhelper.py
Normal file
78
ansibledoctor/utils/yamlhelper.py
Normal file
|
@ -0,0 +1,78 @@
|
||||||
|
"""Utils for YAML file operations."""
|
||||||
|
|
||||||
|
from collections import defaultdict
|
||||||
|
from contextlib import suppress
|
||||||
|
|
||||||
|
import ruamel.yaml
|
||||||
|
import yaml
|
||||||
|
from ansible.parsing.yaml.loader import AnsibleLoader
|
||||||
|
|
||||||
|
import ansibledoctor.exception
|
||||||
|
|
||||||
|
|
||||||
|
class UnsafeTag:
|
||||||
|
"""Handle custom yaml unsafe tag."""
|
||||||
|
|
||||||
|
yaml_tag = "!unsafe"
|
||||||
|
|
||||||
|
def __init__(self, value):
|
||||||
|
self.unsafe = value
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def yaml_constructor(loader, node):
|
||||||
|
return loader.construct_scalar(node)
|
||||||
|
|
||||||
|
|
||||||
|
def parse_yaml_ansible(yamlfile):
|
||||||
|
try:
|
||||||
|
loader = AnsibleLoader(yamlfile)
|
||||||
|
data = loader.get_single_data() or []
|
||||||
|
except (
|
||||||
|
yaml.parser.ParserError,
|
||||||
|
yaml.scanner.ScannerError,
|
||||||
|
yaml.constructor.ConstructorError,
|
||||||
|
yaml.composer.ComposerError,
|
||||||
|
) as e:
|
||||||
|
raise ansibledoctor.exception.YAMLError(e) from e
|
||||||
|
|
||||||
|
return data
|
||||||
|
|
||||||
|
|
||||||
|
def parse_yaml(yamlfile):
|
||||||
|
try:
|
||||||
|
ruamel.yaml.add_constructor(
|
||||||
|
UnsafeTag.yaml_tag,
|
||||||
|
UnsafeTag.yaml_constructor,
|
||||||
|
constructor=ruamel.yaml.SafeConstructor,
|
||||||
|
)
|
||||||
|
|
||||||
|
data = ruamel.yaml.YAML(typ="rt").load(yamlfile)
|
||||||
|
_yaml_remove_comments(data)
|
||||||
|
data = defaultdict(dict, data or {})
|
||||||
|
except (
|
||||||
|
ruamel.yaml.parser.ParserError,
|
||||||
|
ruamel.yaml.scanner.ScannerError,
|
||||||
|
ruamel.yaml.constructor.ConstructorError,
|
||||||
|
ruamel.yaml.composer.ComposerError,
|
||||||
|
) as e:
|
||||||
|
raise ansibledoctor.exception.YAMLError(e) from e
|
||||||
|
|
||||||
|
return data
|
||||||
|
|
||||||
|
|
||||||
|
def _yaml_remove_comments(d):
|
||||||
|
if isinstance(d, dict):
|
||||||
|
for k, v in d.items():
|
||||||
|
_yaml_remove_comments(k)
|
||||||
|
_yaml_remove_comments(v)
|
||||||
|
elif isinstance(d, list):
|
||||||
|
for elem in d:
|
||||||
|
_yaml_remove_comments(elem)
|
||||||
|
|
||||||
|
with suppress(AttributeError):
|
||||||
|
attr = (
|
||||||
|
"comment"
|
||||||
|
if isinstance(d, ruamel.yaml.scalarstring.ScalarString)
|
||||||
|
else ruamel.yaml.comments.Comment.attrib
|
||||||
|
)
|
||||||
|
delattr(d, attr)
|
|
@ -1,23 +0,0 @@
|
||||||
FROM arm32v7/python:3.9-alpine@sha256:4b7733264c4cc10afe9159a41dadeb9638057cfb0f9a0cb3f26e81266f51a568
|
|
||||||
|
|
||||||
LABEL maintainer="Robert Kaussow <mail@thegeeklab.de>"
|
|
||||||
LABEL org.opencontainers.image.authors="Robert Kaussow <mail@thegeeklab.de>"
|
|
||||||
LABEL org.opencontainers.image.title="ansible-doctor"
|
|
||||||
LABEL org.opencontainers.image.url="https://ansible-doctor.geekdocs.de/"
|
|
||||||
LABEL org.opencontainers.image.source="https://github.com/thegeeklab/ansible-doctor"
|
|
||||||
LABEL org.opencontainers.image.documentation="https://ansible-doctor.geekdocs.de/"
|
|
||||||
|
|
||||||
ENV PY_COLORS=1
|
|
||||||
|
|
||||||
ADD dist/ansible_doctor-*.whl /
|
|
||||||
|
|
||||||
RUN apk update && \
|
|
||||||
pip install --upgrade --no-cache-dir pip && \
|
|
||||||
pip install --no-cache-dir $(find / -name "ansible_doctor-*.whl") && \
|
|
||||||
rm -f ansible_doctor-*.whl && \
|
|
||||||
rm -rf /var/cache/apk/* && \
|
|
||||||
rm -rf /root/.cache/
|
|
||||||
|
|
||||||
USER root
|
|
||||||
CMD []
|
|
||||||
ENTRYPOINT ["/usr/local/bin/ansible-doctor"]
|
|
|
@ -1,23 +0,0 @@
|
||||||
FROM arm64v8/python:3.9-alpine@sha256:a9b413e4519b7bde9ddcd3a1faeb4f67eb0bcf044bb036b1fd2d52d5f6758246
|
|
||||||
|
|
||||||
LABEL maintainer="Robert Kaussow <mail@thegeeklab.de>"
|
|
||||||
LABEL org.opencontainers.image.authors="Robert Kaussow <mail@thegeeklab.de>"
|
|
||||||
LABEL org.opencontainers.image.title="ansible-doctor"
|
|
||||||
LABEL org.opencontainers.image.url="https://ansible-doctor.geekdocs.de/"
|
|
||||||
LABEL org.opencontainers.image.source="https://github.com/thegeeklab/ansible-doctor"
|
|
||||||
LABEL org.opencontainers.image.documentation="https://ansible-doctor.geekdocs.de/"
|
|
||||||
|
|
||||||
ENV PY_COLORS=1
|
|
||||||
|
|
||||||
ADD dist/ansible_doctor-*.whl /
|
|
||||||
|
|
||||||
RUN apk update && \
|
|
||||||
pip install --upgrade --no-cache-dir pip && \
|
|
||||||
pip install --no-cache-dir $(find / -name "ansible_doctor-*.whl") && \
|
|
||||||
rm -f ansible_doctor-*.whl && \
|
|
||||||
rm -rf /var/cache/apk/* && \
|
|
||||||
rm -rf /root/.cache/
|
|
||||||
|
|
||||||
USER root
|
|
||||||
CMD []
|
|
||||||
ENTRYPOINT ["/usr/local/bin/ansible-doctor"]
|
|
|
@ -1,24 +0,0 @@
|
||||||
image: quay.io/thegeeklab/ansible-doctor:{{#if build.tag}}{{trimPrefix "v" build.tag}}{{else}}latest{{/if}}
|
|
||||||
{{#if build.tags}}
|
|
||||||
tags:
|
|
||||||
{{#each build.tags}}
|
|
||||||
- {{this}}
|
|
||||||
{{/each}}
|
|
||||||
{{/if}}
|
|
||||||
manifests:
|
|
||||||
- image: quay.io/thegeeklab/ansible-doctor:{{#if build.tag}}{{trimPrefix "v" build.tag}}-{{/if}}amd64
|
|
||||||
platform:
|
|
||||||
architecture: amd64
|
|
||||||
os: linux
|
|
||||||
|
|
||||||
- image: quay.io/thegeeklab/ansible-doctor:{{#if build.tag}}{{trimPrefix "v" build.tag}}-{{/if}}arm64
|
|
||||||
platform:
|
|
||||||
architecture: arm64
|
|
||||||
os: linux
|
|
||||||
variant: v8
|
|
||||||
|
|
||||||
- image: quay.io/thegeeklab/ansible-doctor:{{#if build.tag}}{{trimPrefix "v" build.tag}}-{{/if}}arm
|
|
||||||
platform:
|
|
||||||
architecture: arm
|
|
||||||
os: linux
|
|
||||||
variant: v7
|
|
|
@ -1,24 +0,0 @@
|
||||||
image: thegeeklab/ansible-doctor:{{#if build.tag}}{{trimPrefix "v" build.tag}}{{else}}latest{{/if}}
|
|
||||||
{{#if build.tags}}
|
|
||||||
tags:
|
|
||||||
{{#each build.tags}}
|
|
||||||
- {{this}}
|
|
||||||
{{/each}}
|
|
||||||
{{/if}}
|
|
||||||
manifests:
|
|
||||||
- image: thegeeklab/ansible-doctor:{{#if build.tag}}{{trimPrefix "v" build.tag}}-{{/if}}amd64
|
|
||||||
platform:
|
|
||||||
architecture: amd64
|
|
||||||
os: linux
|
|
||||||
|
|
||||||
- image: thegeeklab/ansible-doctor:{{#if build.tag}}{{trimPrefix "v" build.tag}}-{{/if}}arm64
|
|
||||||
platform:
|
|
||||||
architecture: arm64
|
|
||||||
os: linux
|
|
||||||
variant: v8
|
|
||||||
|
|
||||||
- image: thegeeklab/ansible-doctor:{{#if build.tag}}{{trimPrefix "v" build.tag}}-{{/if}}arm
|
|
||||||
platform:
|
|
||||||
architecture: arm
|
|
||||||
os: linux
|
|
||||||
variant: v7
|
|
|
@ -18,11 +18,17 @@ markup:
|
||||||
startLevel: 1
|
startLevel: 1
|
||||||
|
|
||||||
params:
|
params:
|
||||||
|
description: >
|
||||||
|
ansible-doctor is a simple annotation like documentation generator based on Jinja2 templates
|
||||||
|
to create documentation of Ansible roles in various formats.
|
||||||
|
images:
|
||||||
|
- "socialmedia2.png"
|
||||||
|
|
||||||
geekdocMenuBundle: true
|
geekdocMenuBundle: true
|
||||||
geekdocToC: 3
|
geekdocToC: 3
|
||||||
|
|
||||||
geekdocRepo: https://github.com/thegeeklab/ansible-doctor
|
geekdocRepo: https://github.com/thegeeklab/ansible-doctor
|
||||||
geekdocEditPath: edit/main/docs/content
|
geekdocEditPath: edit/main/docs
|
||||||
|
|
||||||
geekdocDateFormat: "Jan 2, 2006"
|
geekdocDateFormat: "Jan 2, 2006"
|
||||||
geekdocSearch: true
|
geekdocSearch: true
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
title: Documentation
|
title: Documentation
|
||||||
---
|
---
|
||||||
|
|
||||||
[![Build Status](https://img.shields.io/drone/build/thegeeklab/ansible-doctor?logo=drone&server=https%3A%2F%2Fdrone.thegeeklab.de)](https://drone.thegeeklab.de/thegeeklab/ansible-doctor)
|
[![Build Status](https://ci.thegeeklab.de/api/badges/thegeeklab/ansible-doctor/status.svg)](https://ci.thegeeklab.de/repos/thegeeklab/ansible-doctor)
|
||||||
[![Docker Hub](https://img.shields.io/badge/dockerhub-latest-blue.svg?logo=docker&logoColor=white)](https://hub.docker.com/r/thegeeklab/ansible-doctor)
|
[![Docker Hub](https://img.shields.io/badge/dockerhub-latest-blue.svg?logo=docker&logoColor=white)](https://hub.docker.com/r/thegeeklab/ansible-doctor)
|
||||||
[![Quay.io](https://img.shields.io/badge/quay-latest-blue.svg?logo=docker&logoColor=white)](https://quay.io/repository/thegeeklab/ansible-doctor)
|
[![Quay.io](https://img.shields.io/badge/quay-latest-blue.svg?logo=docker&logoColor=white)](https://quay.io/repository/thegeeklab/ansible-doctor)
|
||||||
[![Python Version](https://img.shields.io/pypi/pyversions/ansible-doctor.svg)](https://pypi.org/project/ansible-doctor/)
|
[![Python Version](https://img.shields.io/pypi/pyversions/ansible-doctor.svg)](https://pypi.org/project/ansible-doctor/)
|
||||||
|
@ -14,6 +14,6 @@ title: Documentation
|
||||||
|
|
||||||
This project is based on the idea (and at some parts on the code) of [ansible-autodoc](https://github.com/AndresBott/ansible-autodoc) by Andres Bott so credits goes to him for his work.
|
This project is based on the idea (and at some parts on the code) of [ansible-autodoc](https://github.com/AndresBott/ansible-autodoc) by Andres Bott so credits goes to him for his work.
|
||||||
|
|
||||||
_ansible-doctor_ is a simple annotation like documentation generator based on Jinja2 templates. While _ansible-doctor_ comes with a default template called `readme`, it is also possible to write your own templates. This gives you the ability to customize the output and render the data to every format you like (e.g. HTML or XML).
|
_ansible-doctor_ is a simple annotation like documentation generator based on Jinja2 templates. While _ansible-doctor_ comes with a default template called `readme`, it is also possible to write custom templates to customize the output or render the data to other formats like HTML or XML as well.
|
||||||
|
|
||||||
_ansible-doctor_ is designed to work within your CI pipeline to complete your testing and deployment workflow. Releases are available as Python Packages at [GitHub](https://github.com/thegeeklab/ansible-doctor/releases) or [PyPI](https://pypi.org/project/ansible-doctor/) and as Docker Image at [Docker Hub](https://hub.docker.com/r/thegeeklab/ansible-doctor).
|
_ansible-doctor_ is designed to work within a CI pipeline to complete the existing testing and deployment workflow. Releases are available as Python Packages on [GitHub](https://github.com/thegeeklab/ansible-doctor/releases) or [PyPI](https://pypi.org/project/ansible-doctor/) and as Docker Image on [Docker Hub](https://hub.docker.com/r/thegeeklab/ansible-doctor).
|
||||||
|
|
|
@ -1,13 +0,0 @@
|
||||||
---
|
|
||||||
title: Configuration
|
|
||||||
---
|
|
||||||
|
|
||||||
_ansible-doctor_ comes with default settings which should be sufficient for most users to start, but you can adjust most settings to your needs.
|
|
||||||
|
|
||||||
Changes can be made on different locations which will be processed in the following order (last wins):
|
|
||||||
|
|
||||||
- default configuration (build-in)
|
|
||||||
- global configuration file (path depends on your operating system)
|
|
||||||
- folder-based configuration file (.ansibledoctor.yml|.ansibledoctor.yaml|.ansibledoctor) in current working directory
|
|
||||||
- environment variables
|
|
||||||
- CLI options
|
|
|
@ -1,33 +0,0 @@
|
||||||
---
|
|
||||||
title: CLI options
|
|
||||||
---
|
|
||||||
|
|
||||||
You can get all available CLI options by running `ansible-doctor --help`:
|
|
||||||
|
|
||||||
<!-- prettier-ignore-start -->
|
|
||||||
<!-- spellchecker-disable -->
|
|
||||||
{{< highlight Shell "linenos=table" >}}
|
|
||||||
$ ansible-doctor --help
|
|
||||||
usage: ansible-doctor [-h] [-c CONFIG_FILE] [-o OUTPUT_DIR] [-f] [-d] [-n] [-v] [-q] [--version] [role_dir]
|
|
||||||
|
|
||||||
Generate documentation from annotated Ansible roles using templates
|
|
||||||
|
|
||||||
positional arguments:
|
|
||||||
role_dir role directory (default: current working dir)
|
|
||||||
|
|
||||||
optional arguments:
|
|
||||||
-h, --help show this help message and exit
|
|
||||||
-c CONFIG_FILE, --config CONFIG_FILE
|
|
||||||
location of configuration file
|
|
||||||
-o OUTPUT_DIR, --output OUTPUT_DIR
|
|
||||||
output base dir
|
|
||||||
-f, --force force overwrite output file
|
|
||||||
-d, --dry-run dry run without writing
|
|
||||||
-n, --no-role-detection
|
|
||||||
disable automatic role detection
|
|
||||||
-v increase log level
|
|
||||||
-q decrease log level
|
|
||||||
--version show program's version number and exit
|
|
||||||
{{< /highlight >}}
|
|
||||||
<!-- spellchecker-enable -->
|
|
||||||
<!-- prettier-ignore-end -->
|
|
|
@ -1,47 +0,0 @@
|
||||||
---
|
|
||||||
title: Default settings
|
|
||||||
---
|
|
||||||
|
|
||||||
<!-- prettier-ignore-start -->
|
|
||||||
<!-- markdownlint-disable -->
|
|
||||||
<!-- spellchecker-disable -->
|
|
||||||
{{< highlight YAML "linenos=table" >}}
|
|
||||||
---
|
|
||||||
# default is your current working dir
|
|
||||||
role_dir:
|
|
||||||
# default is the basename of 'role_name'
|
|
||||||
role_name:
|
|
||||||
# Auto-detect if the given directory is a role, can be disabled
|
|
||||||
# to parse loose files instead.
|
|
||||||
role_detection: True
|
|
||||||
# don't write anything to file system
|
|
||||||
dry_run: False
|
|
||||||
|
|
||||||
logging:
|
|
||||||
# possible options debug | info | warning | error | critical
|
|
||||||
level: "warning"
|
|
||||||
# you can enable json logging if a parsable output is required
|
|
||||||
json: False
|
|
||||||
|
|
||||||
# path to write rendered template file
|
|
||||||
# default is your current working dir
|
|
||||||
output_dir:
|
|
||||||
# default is in-build templates dir
|
|
||||||
template_dir:
|
|
||||||
template: readme
|
|
||||||
|
|
||||||
# don't ask to overwrite if output file exists
|
|
||||||
force_overwrite: False
|
|
||||||
# load custom header from given file and append template output
|
|
||||||
# to it before write.
|
|
||||||
custom_header: ""
|
|
||||||
|
|
||||||
exclude_files: []
|
|
||||||
# Examples
|
|
||||||
# exclude_files:
|
|
||||||
# - molecule/
|
|
||||||
# - files/**/*.py
|
|
||||||
{{< /highlight >}}
|
|
||||||
<!-- spellchecker-enable -->
|
|
||||||
<!-- markdownlint-restore -->
|
|
||||||
<!-- prettier-ignore-end -->
|
|
|
@ -1,24 +0,0 @@
|
||||||
---
|
|
||||||
title: Environment Variables
|
|
||||||
---
|
|
||||||
|
|
||||||
<!-- prettier-ignore-start -->
|
|
||||||
<!-- spellchecker-disable -->
|
|
||||||
{{< highlight Shell "linenos=table" >}}
|
|
||||||
ANSIBLE_DOCTOR_CONFIG_FILE=
|
|
||||||
ANSIBLE_DOCTOR_ROLE_DETECTION=true
|
|
||||||
ANSIBLE_DOCTOR_ROLE_DIR=
|
|
||||||
ANSIBLE_DOCTOR_ROLE_NAME=
|
|
||||||
ANSIBLE_DOCTOR_DRY_RUN=false
|
|
||||||
ANSIBLE_DOCTOR_LOG_LEVEL=warning
|
|
||||||
ANSIBLE_DOCTOR_LOG_JSON=false
|
|
||||||
ANSIBLE_DOCTOR_OUTPUT_DIR=
|
|
||||||
ANSIBLE_DOCTOR_TEMPLATE_DIR=
|
|
||||||
ANSIBLE_DOCTOR_TEMPLATE=readme
|
|
||||||
ANSIBLE_DOCTOR_FORCE_OVERWRITE=false
|
|
||||||
ANSIBLE_DOCTOR_CUSTOM_HEADER=
|
|
||||||
ANSIBLE_DOCTOR_EXCLUDE_FILES=
|
|
||||||
ANSIBLE_DOCTOR_EXCLUDE_FILES=molecule/,files/**/*.py
|
|
||||||
{{< /highlight >}}
|
|
||||||
<!-- spellchecker-enable -->
|
|
||||||
<!-- prettier-ignore-end -->
|
|
|
@ -2,24 +2,18 @@
|
||||||
title: Using docker
|
title: Using docker
|
||||||
---
|
---
|
||||||
|
|
||||||
<!-- prettier-ignore-start -->
|
```Shell
|
||||||
<!-- spellchecker-disable -->
|
|
||||||
{{< highlight Shell "linenos=table" >}}
|
|
||||||
docker run \
|
docker run \
|
||||||
-e ANSIBLE_DOCTOR_ROLE_DIR=example/demo-role/ \
|
-e ANSIBLE_DOCTOR_BASE_DIR=example/demo-role/ \
|
||||||
-e ANSIBLE_DOCTOR_OUTPUT_DIR=example/ \
|
|
||||||
-e ANSIBLE_DOCTOR_FORCE_OVERWRITE=true \
|
-e ANSIBLE_DOCTOR_FORCE_OVERWRITE=true \
|
||||||
-e ANSIBLE_DOCTOR_CUSTOM_HEADER=example/demo-role/HEADER.md \
|
-e ANSIBLE_DOCTOR_CUSTOM_HEADER=HEADER.md \
|
||||||
-e ANSIBLE_DOCTOR_LOG_LEVEL=info \
|
-e ANSIBLE_DOCTOR_LOG_LEVEL=info \
|
||||||
-e PY_COLORS=1 \
|
-e PY_COLORS=1 \
|
||||||
-v $(pwd):/doctor \
|
-v $(pwd):/doctor \
|
||||||
-w /doctor \
|
-w /doctor \
|
||||||
thegeeklab/ansible-doctor
|
thegeeklab/ansible-doctor
|
||||||
{{< /highlight >}}
|
```
|
||||||
<!-- spellchecker-enable -->
|
|
||||||
<!-- prettier-ignore-end -->
|
|
||||||
|
|
||||||
{{< hint info >}}
|
{{< hint type=note >}}
|
||||||
**Info**\
|
Keep in mind, that SELinux labels (`:Z` or `:z`) need to be passed as mount option on SELinux enabled systems.
|
||||||
Keep in mind, that you have to pass SELinux labels (:Z or :z) to your mount option if you are working on SELinux enabled systems.
|
|
||||||
{{< /hint >}}
|
{{< /hint >}}
|
||||||
|
|
|
@ -2,19 +2,14 @@
|
||||||
title: Using pip
|
title: Using pip
|
||||||
---
|
---
|
||||||
|
|
||||||
<!-- prettier-ignore-start -->
|
```Shell
|
||||||
<!-- markdownlint-disable -->
|
|
||||||
<!-- spellchecker-disable -->
|
|
||||||
{{< highlight Shell "linenos=table" >}}
|
|
||||||
# From PyPI as unprivileged user
|
# From PyPI as unprivileged user
|
||||||
$ pip install ansible-doctor --user
|
$ pip install ansible-doctor[ansible-core] --user
|
||||||
|
|
||||||
# .. or as root
|
# .. or as root
|
||||||
$ sudo pip install ansible-doctor
|
$ sudo pip install ansible-doctor[ansible-core]
|
||||||
|
|
||||||
# From Wheel file
|
# From Wheel file
|
||||||
$ pip install https://github.com/thegeeklab/ansible-doctor/releases/download/v0.1.1/ansible_doctor-0.1.1-py2.py3-none-any.whl
|
# Please check first whether a newer version is available.
|
||||||
{{< /highlight >}}
|
$ pip install https://github.com/thegeeklab/ansible-doctor/releases/download/v3.1.4/ansible_doctor-3.1.4-py2.py3-none-any.whl[ansible-core]
|
||||||
<!-- spellchecker-enable -->
|
```
|
||||||
<!-- markdownlint-restore -->
|
|
||||||
<!-- prettier-ignore-end -->
|
|
||||||
|
|
|
@ -1,41 +1,3 @@
|
||||||
---
|
---
|
||||||
title: Usage
|
title: Usage
|
||||||
---
|
---
|
||||||
|
|
||||||
```Shell
|
|
||||||
ansible-doctor FOLDER
|
|
||||||
```
|
|
||||||
|
|
||||||
If you don't pass a folder to _ansible-doctor_ your current working directory will be used. The first step is to identify if the given folder is an Ansible role. This check is very simple, if the folder contains a sub-directory called `tasks` is MUST be an Ansible role! :)
|
|
||||||
|
|
||||||
After the successful check, _ansible-doctor_ will try to read some static files into a dictionary:
|
|
||||||
|
|
||||||
- defaults/main.yml
|
|
||||||
- meta/main.yml
|
|
||||||
|
|
||||||
This will be the base result set which is used as data source for every output template. Without any work, you will get at least a documentation about available variables and some meta information. Theses basic information can be expanded with a set of available annotations. In general, an annotation is a comment with an identifier. This identifier is followed by colon separated options and ends with a value.
|
|
||||||
|
|
||||||
<!-- prettier-ignore-start -->
|
|
||||||
<!-- markdownlint-disable -->
|
|
||||||
<!-- spellchecker-disable -->
|
|
||||||
{{< highlight Yaml "linenos=table" >}}
|
|
||||||
# @identifier option1:option2: <value>
|
|
||||||
|
|
||||||
# @var docker_registry_password:example: "%8gv_5GA?"
|
|
||||||
# @var docker_registry_password:description: Very secure password to login to the docker registry
|
|
||||||
# @var docker_registry_password:description: >
|
|
||||||
# You can also write it as multi line description
|
|
||||||
# Very secure password to login to the docker registry.
|
|
||||||
# @end
|
|
||||||
docker_registry_password: "secret"
|
|
||||||
{{< /highlight >}}
|
|
||||||
<!-- spellchecker-enable -->
|
|
||||||
<!-- markdownlint-restore -->
|
|
||||||
<!-- prettier-ignore-end -->
|
|
||||||
|
|
||||||
These list of predefined identifiers is currently available:
|
|
||||||
|
|
||||||
- `@meta`
|
|
||||||
- `@todo`
|
|
||||||
- `@var`
|
|
||||||
- `@tag`
|
|
||||||
|
|
174
docs/content/usage/configuration.md
Normal file
174
docs/content/usage/configuration.md
Normal file
|
@ -0,0 +1,174 @@
|
||||||
|
---
|
||||||
|
title: Configuration
|
||||||
|
---
|
||||||
|
|
||||||
|
_ansible-doctor_ comes with default settings which should be sufficient for most users to start, but most of the settings can be adjusted.
|
||||||
|
|
||||||
|
{{< toc >}}
|
||||||
|
|
||||||
|
Configuration options can be set in different places, which are processed in the following order (last wins):
|
||||||
|
|
||||||
|
- Standard configuration (built-in)
|
||||||
|
- Global configuration file (the path depends on the operating system)
|
||||||
|
- Folder-based configuration file (`.ansibledoctor.yml|.ansibledoctor.yaml|.ansibledoctor`) in the current working directory
|
||||||
|
- Environment Variables
|
||||||
|
- CLI options
|
||||||
|
|
||||||
|
## Defaults
|
||||||
|
|
||||||
|
```YAML
|
||||||
|
---
|
||||||
|
# Default is the current working directory.
|
||||||
|
base_dir:
|
||||||
|
|
||||||
|
role:
|
||||||
|
# Default is the basename of 'role_name'.
|
||||||
|
name:
|
||||||
|
# Auto-detect if the given directory is a role, can be disabled
|
||||||
|
# to parse loose files instead.
|
||||||
|
autodetect: True
|
||||||
|
|
||||||
|
# Don't write anything to file system.
|
||||||
|
dry_run: False
|
||||||
|
|
||||||
|
exclude_files: []
|
||||||
|
# Examples
|
||||||
|
# exclude_files:
|
||||||
|
# - molecule/
|
||||||
|
# - files/**/*.py
|
||||||
|
|
||||||
|
# Exclude tags from automatic detection. Configured tags are only skipped
|
||||||
|
# if the tag is not used in an annotation.
|
||||||
|
exclude_tags: []
|
||||||
|
|
||||||
|
logging:
|
||||||
|
# Possible options: debug|info|warning| error|critical
|
||||||
|
level: "warning"
|
||||||
|
# JSON logging can be enabled if a parsable output is required.
|
||||||
|
json: False
|
||||||
|
|
||||||
|
template:
|
||||||
|
# Name of the template to be used. In most cases, this is the name of a directory that is attached to the
|
||||||
|
# the `src` path or Git repo (see example below).
|
||||||
|
name: readme
|
||||||
|
|
||||||
|
# Template provider source. Currently supported providers are `local|git`.
|
||||||
|
# The `local` provider loads templates from the local file system. This provider
|
||||||
|
# is used by default and uses the built-in templates.
|
||||||
|
#
|
||||||
|
# Examples:
|
||||||
|
# template:
|
||||||
|
# name: readme
|
||||||
|
# src: local>/tmp/custom_templates/
|
||||||
|
#
|
||||||
|
# The `git` provider allows templates to be loaded from a git repository. At the moment
|
||||||
|
# the functions of this provider are limited and only public repositories are supported.
|
||||||
|
#
|
||||||
|
# Examples:
|
||||||
|
# template:
|
||||||
|
# src: git>https://github.com/thegeeklab/ansible-doctor
|
||||||
|
# name: ansibledoctor/templates/readme
|
||||||
|
#
|
||||||
|
# template:
|
||||||
|
# src: git>git@github.com:thegeeklab/ansible-doctor.git
|
||||||
|
# name: ansibledoctor/templates/readme
|
||||||
|
#
|
||||||
|
# template:
|
||||||
|
# src: git>git@github.com:thegeeklab/ansible-doctor.git#branch-or-tag
|
||||||
|
# name: ansibledoctor/templates/readme
|
||||||
|
src:
|
||||||
|
|
||||||
|
options:
|
||||||
|
# Configures whether to tabulate variables in the output. When set to `True`,
|
||||||
|
# variables will be displayed in a tabular format intsead of plain marktdown sections.
|
||||||
|
# NOTE: This option does not support rendering multiline code blocks.
|
||||||
|
tabulate_vars: False
|
||||||
|
|
||||||
|
renderer:
|
||||||
|
# By default, double spaces, spaces before and after line breaks or tab characters, etc.
|
||||||
|
# are automatically removed before the template is rendered. As a result, indenting
|
||||||
|
# with spaces does not work. If you want to use spaces to indent text, you must disable
|
||||||
|
# this option.
|
||||||
|
autotrim: True
|
||||||
|
# Load custom header from given file and append template output to it before write.
|
||||||
|
include_header: ""
|
||||||
|
# Path to write rendered template file. Default is the current working directory.
|
||||||
|
dest:
|
||||||
|
# Don't ask to overwrite if output file exists.
|
||||||
|
force_overwrite: False
|
||||||
|
```
|
||||||
|
|
||||||
|
## CLI
|
||||||
|
|
||||||
|
```Shell
|
||||||
|
$ ansible-doctor --help
|
||||||
|
usage: ansible-doctor [-h] [-c CONFIG_FILE] [-o OUTPUT_DIR] [-r] [-f] [-d] [-n] [-v] [-q] [--version] [base_dir]
|
||||||
|
|
||||||
|
Generate documentation from annotated Ansible roles using templates
|
||||||
|
|
||||||
|
positional arguments:
|
||||||
|
base_dir base directory (default: current working directory)
|
||||||
|
|
||||||
|
options:
|
||||||
|
-h, --help show this help message and exit
|
||||||
|
-c CONFIG_FILE, --config CONFIG_FILE
|
||||||
|
path to configuration file
|
||||||
|
-o OUTPUT_DIR, --output OUTPUT_DIR
|
||||||
|
output directory
|
||||||
|
-r, --recursive run recursively over the base directory subfolders
|
||||||
|
-f, --force force overwrite output file
|
||||||
|
-d, --dry-run dry run without writing
|
||||||
|
-n, --no-role-detection
|
||||||
|
disable automatic role detection
|
||||||
|
-v increase log level
|
||||||
|
-q decrease log level
|
||||||
|
--version show program's version number and exit
|
||||||
|
```
|
||||||
|
|
||||||
|
## Environment Variables
|
||||||
|
|
||||||
|
{{< hint type=note >}}
|
||||||
|
List configuration options need to be passed as JSON strings.
|
||||||
|
{{< /hint >}}
|
||||||
|
|
||||||
|
```Shell
|
||||||
|
ANSIBLE_DOCTOR_BASE_DIR=
|
||||||
|
ANSIBLE_DOCTOR_DRY_RUN=False
|
||||||
|
ANSIBLE_DOCTOR_EXCLUDE_FILES="['molecule/']"
|
||||||
|
ANSIBLE_DOCTOR_EXCLUDE_TAGS="[]"
|
||||||
|
|
||||||
|
ANSIBLE_DOCTOR_ROLE__NAME=
|
||||||
|
ANSIBLE_DOCTOR_ROLE__AUTODETECT=True
|
||||||
|
|
||||||
|
ANSIBLE_DOCTOR_LOGGING__LEVEL="warning"
|
||||||
|
ANSIBLE_DOCTOR_LOGGING__JSON=False
|
||||||
|
|
||||||
|
ANSIBLE_DOCTOR_TEMPLATE__NAME=readme
|
||||||
|
ANSIBLE_DOCTOR_TEMPLATE__SRC=
|
||||||
|
ANSIBLE_DOCTOR_TEMPLATE__OPTIONS__TABULATE_VARS=False
|
||||||
|
|
||||||
|
ANSIBLE_DOCTOR_RENDERER__AUTOTRIM=True
|
||||||
|
ANSIBLE_DOCTOR_RENDERER__INCLUDE_HEADER=
|
||||||
|
ANSIBLE_DOCTOR_RENDERER__DEST=
|
||||||
|
ANSIBLE_DOCTOR_RENDERER__FORCE_OVERWRITE=False
|
||||||
|
```
|
||||||
|
|
||||||
|
## Pre-Commit setup
|
||||||
|
|
||||||
|
To use _ansible-doctor_ with the [pre-commit](https://pre-commit.com/) framework, add the following to the `.pre-commit-config.yaml` file in your local repository.
|
||||||
|
|
||||||
|
<!-- prettier-ignore-start -->
|
||||||
|
<!-- markdownlint-disable -->
|
||||||
|
<!-- spellchecker-disable -->
|
||||||
|
|
||||||
|
{{< highlight yaml "linenos=table" >}}
|
||||||
|
- repo: https://github.com/thegeeklab/ansible-doctor
|
||||||
|
# update version with `pre-commit autoupdate`
|
||||||
|
rev: v4.0.4
|
||||||
|
hooks:
|
||||||
|
- id: ansible-doctor
|
||||||
|
{{< /highlight >}}
|
||||||
|
|
||||||
|
<!-- spellchecker-enable -->
|
||||||
|
<!-- markdownlint-restore -->
|
||||||
|
<!-- prettier-ignore-end -->
|
138
docs/content/usage/getting-started.md
Normal file
138
docs/content/usage/getting-started.md
Normal file
|
@ -0,0 +1,138 @@
|
||||||
|
---
|
||||||
|
title: Getting Started
|
||||||
|
---
|
||||||
|
|
||||||
|
{{< toc >}}
|
||||||
|
|
||||||
|
## Getting Started
|
||||||
|
|
||||||
|
```Shell
|
||||||
|
ansible-doctor FOLDER
|
||||||
|
```
|
||||||
|
|
||||||
|
If no folder is passed to _ansible-doctor_, the current working directory is used. The first step is to determine if the specified folder is an Ansible role. This check is very simple and only verifies if there is a sub-directory named `tasks` in the specified folder. After a successful check, _ansible-doctor_ registers all files of the role to search them for annotations.
|
||||||
|
|
||||||
|
Without any further work _ansible-doctor_ can already create a documentation of the available variables and some meta information if the role contains. This basic information can be extended with a set of available annotations. If you want to see it in action you can find a [demo role](https://github.com/thegeeklab/ansible-doctor/tree/main/example) with a lot of examples in the repository.
|
||||||
|
|
||||||
|
## Annotations
|
||||||
|
|
||||||
|
In general, an annotation is a comment with an identifier followed by colon separated options and a value. A [complex example](https://github.com/thegeeklab/ansible-doctor/tree/main/example) is available on the GitHub repository.
|
||||||
|
|
||||||
|
### `@meta`
|
||||||
|
|
||||||
|
Identifier to add role metadata information. The general structure for this identifier is `# @identifier option1: <value>`.
|
||||||
|
|
||||||
|
option1
|
||||||
|
: scope that can be chosen freely, but the built-in template only handles a few scopes `["dependencies", "license", "author"]`
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
|
||||||
|
```YAML
|
||||||
|
# @meta description: >
|
||||||
|
# Role to demonstrate ansible-doctor. It is also possible to overwrite
|
||||||
|
# the default description with an annotation.
|
||||||
|
# @end
|
||||||
|
|
||||||
|
# @meta author:value: [John Doe](https://blog.example.com)
|
||||||
|
```
|
||||||
|
|
||||||
|
### `@var`
|
||||||
|
|
||||||
|
Identifier to add extra documentation to Ansible variables. The general structure for this identifier is `# @identifier option1:option2: <value>`.
|
||||||
|
|
||||||
|
option1
|
||||||
|
: the name of the variable to which additional information should be added
|
||||||
|
|
||||||
|
option2
|
||||||
|
: supports `["value", "example", "description", "type", "deprecated"]` as information scopes
|
||||||
|
|
||||||
|
#### `value`
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# @var docker_registry_password:value: $ "secret"
|
||||||
|
docker_registry_password: "secret"
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `example`
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# @var docker_registry_password:example: $ "randomPassw0rd"
|
||||||
|
# @var docker_registry_password:example: >
|
||||||
|
# docker_registry_password: "randomPassw0rd"
|
||||||
|
# @end
|
||||||
|
docker_registry_password: "secret"
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `description`
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# @var docker_registry_password:description: Very secure password to login to the docker registry.
|
||||||
|
# @var docker_registry_password:description: >
|
||||||
|
# Multi line description are possible as well.
|
||||||
|
# Very secure password to login to the docker registry.
|
||||||
|
# @end
|
||||||
|
docker_registry_password: "secret"
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `type`
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# @var docker_registry_password:type: string
|
||||||
|
docker_registry_password: "secret"
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `deprecated`
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# @var docker_registry_password:deprecated: true
|
||||||
|
# @var docker_registry_password:deprecated: since v1.0.0
|
||||||
|
docker_registry_password: "secret"
|
||||||
|
```
|
||||||
|
|
||||||
|
### `@tag`
|
||||||
|
|
||||||
|
Used tags within the Ansible task files will be auto-discovered. This identifier can be used to define tags manually or add extended information to discovered tags.
|
||||||
|
|
||||||
|
option1
|
||||||
|
: the name of the tag to which additional information should be added
|
||||||
|
|
||||||
|
option2
|
||||||
|
: supports `["value", "description"]` as information scopes
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
|
||||||
|
```YAML
|
||||||
|
- name: Demo task with a tag list
|
||||||
|
debug:
|
||||||
|
msg: "Demo message"
|
||||||
|
tags:
|
||||||
|
- role-tag1
|
||||||
|
- role-tag2
|
||||||
|
|
||||||
|
# @tag single-tag:description: Example description of tag `single-tag`
|
||||||
|
- name: Demo task with a single tag
|
||||||
|
debug:
|
||||||
|
msg: "Demo message"
|
||||||
|
tags: single-tag
|
||||||
|
```
|
||||||
|
|
||||||
|
### `@todo`
|
||||||
|
|
||||||
|
Identifier to open tasks that need to be addressed. The general structure for this identifier is `# @identifier option1: <value>`.
|
||||||
|
|
||||||
|
option1
|
||||||
|
: scope that can be chosen freely, e.g. `bug`, `improvement`
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
|
||||||
|
```YAML
|
||||||
|
# @todo bug: Some bug that is known and need to be fixed.
|
||||||
|
# @todo bug: >
|
||||||
|
# Multi line description are possible as well.
|
||||||
|
# Some bug that is known and need to be fixed.
|
||||||
|
# @end
|
||||||
|
|
||||||
|
# @todo improvement: Some things that need to be improved.
|
||||||
|
|
||||||
|
# @todo default: Unscoped general todo.
|
||||||
|
```
|
|
@ -6,14 +6,9 @@ main:
|
||||||
ref: "/setup/pip"
|
ref: "/setup/pip"
|
||||||
- name: Using docker
|
- name: Using docker
|
||||||
ref: "/setup/docker"
|
ref: "/setup/docker"
|
||||||
- name: Configuration
|
|
||||||
ref: "/configuration"
|
|
||||||
sub:
|
|
||||||
- name: Default settings
|
|
||||||
ref: "/configuration/defaults"
|
|
||||||
- name: CLI options
|
|
||||||
ref: "/configuration/cli"
|
|
||||||
- name: Environment variables
|
|
||||||
ref: "/configuration/env"
|
|
||||||
- name: Usage
|
- name: Usage
|
||||||
ref: "/usage"
|
sub:
|
||||||
|
- name: Getting Started
|
||||||
|
ref: "/usage/getting-started"
|
||||||
|
- name: Configuration
|
||||||
|
ref: "/usage/configuration"
|
||||||
|
|
162
docs/static/socialmedia.svg
vendored
Normal file
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 |
BIN
docs/static/socialmedia2.png
vendored
Normal file
BIN
docs/static/socialmedia2.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 30 KiB |
|
@ -1,140 +0,0 @@
|
||||||
# demo-role-custom-header
|
|
||||||
|
|
||||||
[![Build Status](https://img.shields.io/drone/build/thegeeklab/ansible-doctor?logo=drone&server=https%3A%2F%2Fdrone.thegeeklab.de)](https://drone.thegeeklab.de/thegeeklab/ansible-doctor)
|
|
||||||
[![License: GPL-3.0](https://img.shields.io/github/license/thegeeklab/ansible-doctor)](https://github.com/thegeeklab/ansible-doctor/blob/main/LICENSE)
|
|
||||||
|
|
||||||
Role to demonstrate ansible-doctor. It is also possible to overwrite the default description with an annotation.
|
|
||||||
|
|
||||||
## Table of content
|
|
||||||
|
|
||||||
- [Default Variables](#default-variables)
|
|
||||||
- [demo_role_dict](#demo_role_dict)
|
|
||||||
- [demo_role_empty](#demo_role_empty)
|
|
||||||
- [demo_role_empty_dict](#demo_role_empty_dict)
|
|
||||||
- [demo_role_other_tags](#demo_role_other_tags)
|
|
||||||
- [demo_role_single](#demo_role_single)
|
|
||||||
- [demo_role_undefined_var](#demo_role_undefined_var)
|
|
||||||
- [demo_role_unset](#demo_role_unset)
|
|
||||||
- [Dependencies](#dependencies)
|
|
||||||
- [License](#license)
|
|
||||||
- [Author](#author)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Default Variables
|
|
||||||
|
|
||||||
### demo_role_dict
|
|
||||||
|
|
||||||
#### Default value
|
|
||||||
|
|
||||||
```YAML
|
|
||||||
demo_role_dict:
|
|
||||||
key1:
|
|
||||||
sub: some value
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Example usage
|
|
||||||
|
|
||||||
```YAML
|
|
||||||
demo_role_dict:
|
|
||||||
key1:
|
|
||||||
sub: some value
|
|
||||||
key2:
|
|
||||||
sublist:
|
|
||||||
- subval1
|
|
||||||
- subval2
|
|
||||||
```
|
|
||||||
|
|
||||||
### demo_role_empty
|
|
||||||
|
|
||||||
#### Default value
|
|
||||||
|
|
||||||
```YAML
|
|
||||||
demo_role_empty: ''
|
|
||||||
```
|
|
||||||
|
|
||||||
### demo_role_empty_dict
|
|
||||||
|
|
||||||
... or you can use a valid json. In this case, the json will be automatically prefixed with the annotation key and you can use e.g. `to_nice_yaml` filter in your templates. To get this working, you have to prefix your json with a `$` char.
|
|
||||||
|
|
||||||
#### Default value
|
|
||||||
|
|
||||||
```YAML
|
|
||||||
demo_role_empty_dict: {}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Example usage
|
|
||||||
|
|
||||||
```YAML
|
|
||||||
demo_role_empty_dict:
|
|
||||||
key1:
|
|
||||||
sub: some value
|
|
||||||
key2:
|
|
||||||
sublist:
|
|
||||||
- subval1
|
|
||||||
- subval2
|
|
||||||
```
|
|
||||||
|
|
||||||
### demo_role_other_tags
|
|
||||||
|
|
||||||
If a variable need some more explanation, this is a good place to do so.
|
|
||||||
|
|
||||||
#### Default value
|
|
||||||
|
|
||||||
```YAML
|
|
||||||
demo_role_other_tags: []
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Example usage
|
|
||||||
|
|
||||||
```YAML
|
|
||||||
demo_role_other_tags:
|
|
||||||
- package1
|
|
||||||
- package2
|
|
||||||
```
|
|
||||||
|
|
||||||
### demo_role_single
|
|
||||||
|
|
||||||
#### Default value
|
|
||||||
|
|
||||||
```YAML
|
|
||||||
demo_role_single: b
|
|
||||||
```
|
|
||||||
|
|
||||||
### demo_role_undefined_var
|
|
||||||
|
|
||||||
If you want to add an explicit notice, that a var is not set by default, this is one option. Make sure to flag it as json value: `@var demo_role_undefined_var: $ "_unset_"`
|
|
||||||
|
|
||||||
#### Default value
|
|
||||||
|
|
||||||
```YAML
|
|
||||||
demo_role_undefined_var: _unset_
|
|
||||||
```
|
|
||||||
|
|
||||||
### demo_role_unset
|
|
||||||
|
|
||||||
You can set values as string, but there is no magic or autoformatting...
|
|
||||||
|
|
||||||
#### Default value
|
|
||||||
|
|
||||||
```YAML
|
|
||||||
demo_role_unset:
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Example usage
|
|
||||||
|
|
||||||
```YAML
|
|
||||||
demo_role_unset: some_value
|
|
||||||
```
|
|
||||||
|
|
||||||
## Dependencies
|
|
||||||
|
|
||||||
None.
|
|
||||||
|
|
||||||
## License
|
|
||||||
|
|
||||||
MIT
|
|
||||||
|
|
||||||
## Author
|
|
||||||
|
|
||||||
Robert Kaussow <mail@example.com>
|
|
|
@ -1,5 +1,10 @@
|
||||||
---
|
---
|
||||||
custom_header: HEADER.md
|
|
||||||
logging:
|
logging:
|
||||||
level: debug
|
level: debug
|
||||||
template: readme
|
|
||||||
|
template:
|
||||||
|
src: git>https://github.com/thegeeklab/ansible-doctor
|
||||||
|
name: ansibledoctor/templates/readme
|
||||||
|
|
||||||
|
renderer:
|
||||||
|
include_header: HEADER.md
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
# demo-role-custom-header
|
# demo-role-custom-header
|
||||||
|
|
||||||
[![Build Status](https://img.shields.io/drone/build/thegeeklab/ansible-doctor?logo=drone&server=https%3A%2F%2Fdrone.thegeeklab.de)](https://drone.thegeeklab.de/thegeeklab/ansible-doctor)
|
[![Build Status](https://ci.thegeeklab.de/api/badges/thegeeklab/ansible-doctor/status.svg)](https://ci.thegeeklab.de/repos/thegeeklab/ansible-doctor)
|
||||||
[![License: GPL-3.0](https://img.shields.io/github/license/thegeeklab/ansible-doctor)](https://github.com/thegeeklab/ansible-doctor/blob/main/LICENSE)
|
[![License: GPL-3.0](https://img.shields.io/github/license/thegeeklab/ansible-doctor)](https://github.com/thegeeklab/ansible-doctor/blob/main/LICENSE)
|
||||||
|
|
215
example/demo-role/README.md
Normal file
215
example/demo-role/README.md
Normal file
|
@ -0,0 +1,215 @@
|
||||||
|
# demo-role-custom-header
|
||||||
|
|
||||||
|
[![Build Status](https://ci.thegeeklab.de/api/badges/thegeeklab/ansible-doctor/status.svg)](https://ci.thegeeklab.de/repos/thegeeklab/ansible-doctor)
|
||||||
|
[![License: GPL-3.0](https://img.shields.io/github/license/thegeeklab/ansible-doctor)](https://github.com/thegeeklab/ansible-doctor/blob/main/LICENSE)
|
||||||
|
|
||||||
|
Role to demonstrate ansible-doctor. It is also possible to overwrite
|
||||||
|
the default description with an annotation.
|
||||||
|
|
||||||
|
## Table of content
|
||||||
|
|
||||||
|
- [Requirements](#requirements)
|
||||||
|
- [Default Variables](#default-variables)
|
||||||
|
- [demo_role_deprecated](#demo_role_deprecated)
|
||||||
|
- [demo_role_deprecated_info](#demo_role_deprecated_info)
|
||||||
|
- [demo_role_dict](#demo_role_dict)
|
||||||
|
- [demo_role_empty](#demo_role_empty)
|
||||||
|
- [demo_role_empty_dict](#demo_role_empty_dict)
|
||||||
|
- [demo_role_other_tags](#demo_role_other_tags)
|
||||||
|
- [demo_role_override](#demo_role_override)
|
||||||
|
- [demo_role_override_complex](#demo_role_override_complex)
|
||||||
|
- [demo_role_single](#demo_role_single)
|
||||||
|
- [demo_role_undefined_var](#demo_role_undefined_var)
|
||||||
|
- [demo_role_unset](#demo_role_unset)
|
||||||
|
- [Discovered Tags](#discovered-tags)
|
||||||
|
- [Open Tasks](#open-tasks)
|
||||||
|
- [Dependencies](#dependencies)
|
||||||
|
- [License](#license)
|
||||||
|
- [Author](#author)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
- Minimum Ansible version: `2.10`
|
||||||
|
|
||||||
|
## Default Variables
|
||||||
|
|
||||||
|
### demo_role_deprecated
|
||||||
|
|
||||||
|
**_Deprecated_**<br />
|
||||||
|
|
||||||
|
#### Default value
|
||||||
|
|
||||||
|
```YAML
|
||||||
|
demo_role_deprecated: b
|
||||||
|
```
|
||||||
|
|
||||||
|
### demo_role_deprecated_info
|
||||||
|
|
||||||
|
**_Deprecated:_** This variable is deprected since `v2.0.0` and will be removed in a future release.<br />
|
||||||
|
**_Type:_** string<br />
|
||||||
|
|
||||||
|
#### Default value
|
||||||
|
|
||||||
|
```YAML
|
||||||
|
demo_role_deprecated_info: a
|
||||||
|
```
|
||||||
|
|
||||||
|
### demo_role_dict
|
||||||
|
|
||||||
|
#### Default value
|
||||||
|
|
||||||
|
```YAML
|
||||||
|
demo_role_dict:
|
||||||
|
key1:
|
||||||
|
sub: some value
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Example usage
|
||||||
|
|
||||||
|
```YAML
|
||||||
|
demo_role_dict:
|
||||||
|
key1:
|
||||||
|
sub: some value
|
||||||
|
|
||||||
|
# Inline description
|
||||||
|
key2:
|
||||||
|
sublist:
|
||||||
|
- subval1
|
||||||
|
- subval2
|
||||||
|
```
|
||||||
|
|
||||||
|
### demo_role_empty
|
||||||
|
|
||||||
|
#### Default value
|
||||||
|
|
||||||
|
```YAML
|
||||||
|
demo_role_empty: ''
|
||||||
|
```
|
||||||
|
|
||||||
|
### demo_role_empty_dict
|
||||||
|
|
||||||
|
... or valid json can be used. In this case, the json will be automatically prefixed with the annotation key
|
||||||
|
and filters like `to_nice_yaml` can be used in templates. To get it working, the json need to be prefixed with a `$`.
|
||||||
|
|
||||||
|
#### Default value
|
||||||
|
|
||||||
|
```YAML
|
||||||
|
demo_role_empty_dict: {}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Example usage
|
||||||
|
|
||||||
|
```YAML
|
||||||
|
demo_role_empty_dict:
|
||||||
|
key1:
|
||||||
|
sub: some value
|
||||||
|
key2:
|
||||||
|
sublist:
|
||||||
|
- subval1
|
||||||
|
- subval2
|
||||||
|
```
|
||||||
|
|
||||||
|
### demo_role_other_tags
|
||||||
|
|
||||||
|
If a variable need some more explanation, this is a good place to do so.
|
||||||
|
|
||||||
|
#### Default value
|
||||||
|
|
||||||
|
```YAML
|
||||||
|
demo_role_other_tags: []
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Example usage
|
||||||
|
|
||||||
|
```YAML
|
||||||
|
demo_role_other_tags:
|
||||||
|
- package1
|
||||||
|
- package2
|
||||||
|
```
|
||||||
|
|
||||||
|
### demo_role_override
|
||||||
|
|
||||||
|
#### Default value
|
||||||
|
|
||||||
|
```YAML
|
||||||
|
demo_role_override: test
|
||||||
|
```
|
||||||
|
|
||||||
|
### demo_role_override_complex
|
||||||
|
|
||||||
|
#### Default value
|
||||||
|
|
||||||
|
```YAML
|
||||||
|
demo_role_override_complex:
|
||||||
|
foo: bar
|
||||||
|
second: value
|
||||||
|
```
|
||||||
|
|
||||||
|
### demo_role_single
|
||||||
|
|
||||||
|
#### Default value
|
||||||
|
|
||||||
|
```YAML
|
||||||
|
demo_role_single: b
|
||||||
|
```
|
||||||
|
|
||||||
|
### demo_role_undefined_var
|
||||||
|
|
||||||
|
To highlight a variable that has not set a value by default, this is one way to achieve it.
|
||||||
|
Make sure to flag it as json value: `@var demo_role_undefined_var: $ "_unset_"`
|
||||||
|
|
||||||
|
| Attribute | Description |
|
||||||
|
| --- | --- |
|
||||||
|
| value1 | desc1 |
|
||||||
|
|
||||||
|
#### Default value
|
||||||
|
|
||||||
|
```YAML
|
||||||
|
demo_role_undefined_var: _unset_
|
||||||
|
```
|
||||||
|
|
||||||
|
### demo_role_unset
|
||||||
|
|
||||||
|
Values can be plain strings, but there is no magic or autoformatting...
|
||||||
|
|
||||||
|
#### Default value
|
||||||
|
|
||||||
|
```YAML
|
||||||
|
demo_role_unset:
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Example usage
|
||||||
|
|
||||||
|
```YAML
|
||||||
|
demo_role_unset: some_value
|
||||||
|
```
|
||||||
|
|
||||||
|
## Discovered Tags
|
||||||
|
|
||||||
|
**_role-tag1_**
|
||||||
|
|
||||||
|
**_role-tag2_**
|
||||||
|
|
||||||
|
**_single-tag_**\
|
||||||
|
 Example description of tag `single-tag`
|
||||||
|
|
||||||
|
## Open Tasks
|
||||||
|
|
||||||
|
- Unscoped general todo.
|
||||||
|
- (bug): Some bug that is known and need to be fixed.
|
||||||
|
- (bug): Multi line description are possible as well. Some bug that is known and need to be fixed.
|
||||||
|
- (improvement): Some things that need to be improved.
|
||||||
|
|
||||||
|
## Dependencies
|
||||||
|
|
||||||
|
- role2
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
MIT
|
||||||
|
|
||||||
|
## Author
|
||||||
|
|
||||||
|
[John Doe](https://blog.example.com)
|
|
@ -1,16 +1,14 @@
|
||||||
---
|
---
|
||||||
# @var demo_role_unset:description: You can set values as string, but there is no magic or autoformatting...
|
# @var demo_role_unset:description: Values can be plain strings, but there is no magic or autoformatting...
|
||||||
# @var demo_role_unset:example: demo_role_unset: some_value
|
# @var demo_role_unset:example: demo_role_unset: some_value
|
||||||
demo_role_unset:
|
demo_role_unset:
|
||||||
|
|
||||||
demo_role_empty: ""
|
demo_role_empty: ""
|
||||||
demo_role_single: 'b'
|
demo_role_single: "b"
|
||||||
|
|
||||||
# @var demo_role_empty_dict:description: >
|
# @var demo_role_empty_dict:description: >
|
||||||
# ... or you can use a valid json. In this case,
|
# ... or valid json can be used. In this case, the json will be automatically prefixed with the annotation key
|
||||||
# the json will be automatically prefixed with the annotation key
|
# and filters like `to_nice_yaml` can be used in templates. To get it working, the json need to be prefixed with a `$`.
|
||||||
# and you can use e.g. `to_nice_yaml` filter in your templates.
|
|
||||||
# To get this working, you have to prefix your json with a `$` char.
|
|
||||||
# @end
|
# @end
|
||||||
# @var demo_role_empty_dict:example: $ {"key1": {"sub": "some value"}, "key2": {"sublist": ["subval1", "subval2"]}}
|
# @var demo_role_empty_dict:example: $ {"key1": {"sub": "some value"}, "key2": {"sublist": ["subval1", "subval2"]}}
|
||||||
demo_role_empty_dict: {}
|
demo_role_empty_dict: {}
|
||||||
|
@ -19,6 +17,8 @@ demo_role_empty_dict: {}
|
||||||
# demo_role_dict:
|
# demo_role_dict:
|
||||||
# key1:
|
# key1:
|
||||||
# sub: some value
|
# sub: some value
|
||||||
|
#
|
||||||
|
# # Inline description
|
||||||
# key2:
|
# key2:
|
||||||
# sublist:
|
# sublist:
|
||||||
# - subval1
|
# - subval1
|
||||||
|
@ -28,9 +28,19 @@ demo_role_dict:
|
||||||
key1:
|
key1:
|
||||||
sub: some value
|
sub: some value
|
||||||
|
|
||||||
|
# @var demo_role_undefined_var:description: >
|
||||||
|
# To highlight a variable that has not set a value by default, this is one way to achieve it.
|
||||||
|
# Make sure to flag it as json value: `@var demo_role_undefined_var: $ "_unset_"`
|
||||||
|
#
|
||||||
|
# | Attribute | Description |
|
||||||
|
# | --- | --- |
|
||||||
|
# | value1 | desc1 |
|
||||||
|
#
|
||||||
|
# @end
|
||||||
|
# @var demo_role_undefined_var: $ "_unset_"
|
||||||
|
|
||||||
# @var demo_role_other_tags:description: >
|
# @var demo_role_other_tags:description: >
|
||||||
# If a variable need some more explanation,
|
# If a variable need some more explanation, this is a good place to do so.
|
||||||
# this is a good place to do so.
|
|
||||||
# @end
|
# @end
|
||||||
# @var demo_role_other_tags:example: $>
|
# @var demo_role_other_tags:example: $>
|
||||||
# [
|
# [
|
||||||
|
@ -40,8 +50,18 @@ demo_role_dict:
|
||||||
# @end
|
# @end
|
||||||
demo_role_other_tags: []
|
demo_role_other_tags: []
|
||||||
|
|
||||||
# @var demo_role_undefined_var:description: >
|
## Simple value
|
||||||
# If you want to add an explicit notice, that a var is not set by default, this is one option.
|
# @var demo_role_override: $ "test"
|
||||||
# Make sure to flag it as json value: `@var demo_role_undefined_var: $ "_unset_"`
|
demo_role_override: original
|
||||||
# @end
|
|
||||||
# @var demo_role_undefined_var: $ "_unset_"
|
## Complex value
|
||||||
|
# @var demo_role_override_complex:value: $ {"foo":"bar", "second":"value"}
|
||||||
|
demo_role_override_complex: {}
|
||||||
|
|
||||||
|
# @var demo_role_deprecated:deprecated:
|
||||||
|
demo_role_deprecated: "b"
|
||||||
|
|
||||||
|
# @var demo_role_deprecated_info:deprecated: >
|
||||||
|
# This variable is deprected since `v2.0.0` and will be removed in a future release.
|
||||||
|
# @var demo_role_deprecated_info:type: string
|
||||||
|
demo_role_deprecated_info: "a"
|
||||||
|
|
|
@ -3,16 +3,20 @@
|
||||||
# Role to demonstrate ansible-doctor. It is also possible to overwrite
|
# Role to demonstrate ansible-doctor. It is also possible to overwrite
|
||||||
# the default description with an annotation.
|
# the default description with an annotation.
|
||||||
# @end
|
# @end
|
||||||
|
# @meta author: [John Doe](https\://blog.example.com)
|
||||||
galaxy_info:
|
galaxy_info:
|
||||||
description: Role to demonstrate ansible-doctor.
|
description: Role to demonstrate ansible-doctor.
|
||||||
author: Robert Kaussow <mail@example.com>
|
author: John Doe
|
||||||
license: MIT
|
license: MIT
|
||||||
min_ansible_version: 2.4
|
min_ansible_version: "2.10"
|
||||||
platforms:
|
platforms:
|
||||||
- name: EL
|
- name: EL
|
||||||
versions:
|
versions:
|
||||||
- 7
|
- "9"
|
||||||
galaxy_tags:
|
galaxy_tags:
|
||||||
- demo
|
- demo
|
||||||
- documentation
|
- documentation
|
||||||
dependencies: []
|
|
||||||
|
dependencies:
|
||||||
|
- role: role2
|
||||||
|
- name: namespace.role3
|
||||||
|
|
|
@ -1 +1,24 @@
|
||||||
---
|
---
|
||||||
|
# @todo bug: Some bug that is known and need to be fixed.
|
||||||
|
# @todo bug: >
|
||||||
|
# Multi line description are possible as well.
|
||||||
|
# Some bug that is known and need to be fixed.
|
||||||
|
# @end
|
||||||
|
|
||||||
|
# @todo improvement: Some things that need to be improved.
|
||||||
|
# @todo default: Unscoped general todo.
|
||||||
|
|
||||||
|
- name: Demo task with a tag list
|
||||||
|
debug:
|
||||||
|
msg: "Demo message"
|
||||||
|
tags:
|
||||||
|
- module-tag
|
||||||
|
tags:
|
||||||
|
- role-tag1
|
||||||
|
- role-tag2
|
||||||
|
|
||||||
|
# @tag single-tag:description: Example description of tag `single-tag`
|
||||||
|
- name: Demo task with a single tag
|
||||||
|
debug:
|
||||||
|
msg: "Demo message"
|
||||||
|
tags: single-tag
|
||||||
|
|
9
example/other-role/.ansibledoctor.yml
Normal file
9
example/other-role/.ansibledoctor.yml
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
---
|
||||||
|
logging:
|
||||||
|
level: debug
|
||||||
|
|
||||||
|
template:
|
||||||
|
name: readme
|
||||||
|
|
||||||
|
renderer:
|
||||||
|
include_header: HEADER.md
|
4
example/other-role/HEADER.md
Normal file
4
example/other-role/HEADER.md
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
# other-role-custom-header
|
||||||
|
|
||||||
|
[![Build Status](https://ci.thegeeklab.de/api/badges/thegeeklab/ansible-doctor/status.svg)](https://ci.thegeeklab.de/repos/thegeeklab/ansible-doctor)
|
||||||
|
[![License: GPL-3.0](https://img.shields.io/github/license/thegeeklab/ansible-doctor)](https://github.com/thegeeklab/ansible-doctor/blob/main/LICENSE)
|
67
example/other-role/README.md
Normal file
67
example/other-role/README.md
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
# other-role-custom-header
|
||||||
|
|
||||||
|
[![Build Status](https://ci.thegeeklab.de/api/badges/thegeeklab/ansible-doctor/status.svg)](https://ci.thegeeklab.de/repos/thegeeklab/ansible-doctor)
|
||||||
|
[![License: GPL-3.0](https://img.shields.io/github/license/thegeeklab/ansible-doctor)](https://github.com/thegeeklab/ansible-doctor/blob/main/LICENSE)
|
||||||
|
|
||||||
|
Role to demonstrate ansible-doctor.
|
||||||
|
|
||||||
|
## Table of content
|
||||||
|
|
||||||
|
- [Requirements](#requirements)
|
||||||
|
- [Default Variables](#default-variables)
|
||||||
|
- [demo_role_unset](#demo_role_unset)
|
||||||
|
- [Discovered Tags](#discovered-tags)
|
||||||
|
- [Open Tasks](#open-tasks)
|
||||||
|
- [Dependencies](#dependencies)
|
||||||
|
- [License](#license)
|
||||||
|
- [Author](#author)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
- Minimum Ansible version: `2.10`
|
||||||
|
|
||||||
|
## Default Variables
|
||||||
|
|
||||||
|
### demo_role_unset
|
||||||
|
|
||||||
|
Values can be plain strings, but there is no magic or autoformatting...
|
||||||
|
|
||||||
|
#### Default value
|
||||||
|
|
||||||
|
```YAML
|
||||||
|
demo_role_unset:
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Example usage
|
||||||
|
|
||||||
|
```YAML
|
||||||
|
demo_role_unset: some_value
|
||||||
|
```
|
||||||
|
|
||||||
|
## Discovered Tags
|
||||||
|
|
||||||
|
**_role-tag1_**
|
||||||
|
|
||||||
|
**_role-tag2_**
|
||||||
|
|
||||||
|
## Open Tasks
|
||||||
|
|
||||||
|
- Unscoped general todo.
|
||||||
|
- (bug): Some bug that is known and need to be fixed.
|
||||||
|
- (bug): Multi line description are possible as well. Some bug that is known and need to be fixed.
|
||||||
|
- (improvement): Some things that need to be improved.
|
||||||
|
|
||||||
|
## Dependencies
|
||||||
|
|
||||||
|
- role1
|
||||||
|
- role2
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
MIT
|
||||||
|
|
||||||
|
## Author
|
||||||
|
|
||||||
|
[John Doe](https://blog.example.com)
|
4
example/other-role/defaults/main.yml
Normal file
4
example/other-role/defaults/main.yml
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
---
|
||||||
|
# @var demo_role_unset:description: Values can be plain strings, but there is no magic or autoformatting...
|
||||||
|
# @var demo_role_unset:example: demo_role_unset: some_value
|
||||||
|
demo_role_unset:
|
19
example/other-role/meta/main.yml
Normal file
19
example/other-role/meta/main.yml
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
---
|
||||||
|
# @meta author: [John Doe](https\://blog.example.com)
|
||||||
|
galaxy_info:
|
||||||
|
description: Role to demonstrate ansible-doctor.
|
||||||
|
author: John Doe
|
||||||
|
license: MIT
|
||||||
|
min_ansible_version: "2.10"
|
||||||
|
platforms:
|
||||||
|
- name: EL
|
||||||
|
versions:
|
||||||
|
- "9"
|
||||||
|
galaxy_tags:
|
||||||
|
- demo
|
||||||
|
- documentation
|
||||||
|
|
||||||
|
dependencies:
|
||||||
|
- role1
|
||||||
|
- role: role2
|
||||||
|
- name: namespace.role3
|
16
example/other-role/tasks/main.yml
Normal file
16
example/other-role/tasks/main.yml
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
---
|
||||||
|
# @todo bug: Some bug that is known and need to be fixed.
|
||||||
|
# @todo bug: >
|
||||||
|
# Multi line description are possible as well.
|
||||||
|
# Some bug that is known and need to be fixed.
|
||||||
|
# @end
|
||||||
|
|
||||||
|
# @todo improvement: Some things that need to be improved.
|
||||||
|
# @todo default: Unscoped general todo.
|
||||||
|
|
||||||
|
- name: Demo task with a tag list
|
||||||
|
debug:
|
||||||
|
msg: "Demo message"
|
||||||
|
tags:
|
||||||
|
- role-tag1
|
||||||
|
- role-tag2
|
1494
poetry.lock
generated
1494
poetry.lock
generated
File diff suppressed because it is too large
Load Diff
140
pyproject.toml
140
pyproject.toml
|
@ -10,9 +10,10 @@ classifiers = [
|
||||||
"Natural Language :: English",
|
"Natural Language :: English",
|
||||||
"Operating System :: POSIX",
|
"Operating System :: POSIX",
|
||||||
"Programming Language :: Python :: 3",
|
"Programming Language :: Python :: 3",
|
||||||
"Programming Language :: Python :: 3.7",
|
|
||||||
"Programming Language :: Python :: 3.8",
|
|
||||||
"Programming Language :: Python :: 3.9",
|
"Programming Language :: Python :: 3.9",
|
||||||
|
"Programming Language :: Python :: 3.10",
|
||||||
|
"Programming Language :: Python :: 3.11",
|
||||||
|
"Programming Language :: Python :: 3.12",
|
||||||
"Topic :: Utilities",
|
"Topic :: Utilities",
|
||||||
"Topic :: Software Development",
|
"Topic :: Software Development",
|
||||||
"Topic :: Software Development :: Documentation",
|
"Topic :: Software Development :: Documentation",
|
||||||
|
@ -20,71 +21,53 @@ classifiers = [
|
||||||
description = "Generate documentation from annotated Ansible roles using templates."
|
description = "Generate documentation from annotated Ansible roles using templates."
|
||||||
documentation = "https://ansible-doctor.geekdocs.de/"
|
documentation = "https://ansible-doctor.geekdocs.de/"
|
||||||
homepage = "https://ansible-doctor.geekdocs.de/"
|
homepage = "https://ansible-doctor.geekdocs.de/"
|
||||||
include = [
|
include = ["LICENSE"]
|
||||||
"LICENSE",
|
|
||||||
]
|
|
||||||
keywords = ["ansible", "role", "documentation"]
|
keywords = ["ansible", "role", "documentation"]
|
||||||
license = "GPL-3.0-only"
|
license = "GPL-3.0-only"
|
||||||
name = "ansible-doctor"
|
name = "ansible-doctor"
|
||||||
packages = [
|
packages = [{ include = "ansibledoctor" }]
|
||||||
{include = "ansibledoctor"},
|
|
||||||
]
|
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
repository = "https://github.com/thegeeklab/ansible-doctor/"
|
repository = "https://github.com/thegeeklab/ansible-doctor/"
|
||||||
version = "0.0.0"
|
version = "0.0.0"
|
||||||
|
|
||||||
[tool.poetry.dependencies]
|
[tool.poetry.dependencies]
|
||||||
Jinja2 = "3.0.1"
|
Jinja2 = "3.1.4"
|
||||||
anyconfig = "0.12.0"
|
anyconfig = "0.14.0"
|
||||||
appdirs = "1.4.4"
|
appdirs = "1.4.4"
|
||||||
colorama = "0.4.4"
|
colorama = "0.4.6"
|
||||||
environs = "9.3.4"
|
pathspec = "0.12.1"
|
||||||
jsonschema = "4.0.1"
|
python = "^3.9.0"
|
||||||
nested-lookup = "0.2.22"
|
"ruamel.yaml" = "0.18.6"
|
||||||
pathspec = "0.9.0"
|
dynaconf = "3.2.5"
|
||||||
python = "^3.7.0"
|
gitpython = "3.1.43"
|
||||||
python-json-logger = "2.0.2"
|
ansible-core = { version = "2.14.17", optional = true }
|
||||||
"ruamel.yaml" = "0.17.16"
|
structlog = "24.2.0"
|
||||||
|
|
||||||
[tool.poetry.dev-dependencies]
|
[tool.poetry.extras]
|
||||||
bandit = "1.7.0"
|
ansible-core = ["ansible-core"]
|
||||||
flake8 = "3.9.2"
|
|
||||||
flake8-blind-except = "0.2.0"
|
|
||||||
flake8-builtins = "1.5.3"
|
|
||||||
flake8-docstrings = "1.6.0"
|
|
||||||
flake8-eradicate = "1.1.0"
|
|
||||||
flake8-isort = "4.0.0"
|
|
||||||
flake8-logging-format = "0.6.0"
|
|
||||||
flake8-pep3101 = "1.3.0"
|
|
||||||
flake8-polyfill = "1.0.2"
|
|
||||||
flake8-quotes = "3.3.0"
|
|
||||||
pep8-naming = "0.12.1"
|
|
||||||
pydocstyle = "6.1.1"
|
|
||||||
pytest = "6.2.5"
|
|
||||||
pytest-cov = "3.0.0"
|
|
||||||
pytest-mock = "3.6.1"
|
|
||||||
yapf = "0.31.0"
|
|
||||||
|
|
||||||
[tool.poetry.scripts]
|
[tool.poetry.scripts]
|
||||||
ansible-doctor = "ansibledoctor.cli:main"
|
ansible-doctor = "ansibledoctor.cli:main"
|
||||||
|
|
||||||
|
[tool.poetry.group.dev.dependencies]
|
||||||
|
ruff = "0.4.10"
|
||||||
|
pytest = "8.2.2"
|
||||||
|
pytest-mock = "3.14.0"
|
||||||
|
pytest-cov = "5.0.0"
|
||||||
|
toml = "0.10.2"
|
||||||
|
j2lint = "1.1.0"
|
||||||
|
|
||||||
|
|
||||||
[tool.poetry-dynamic-versioning]
|
[tool.poetry-dynamic-versioning]
|
||||||
enable = true
|
enable = true
|
||||||
style = "semver"
|
style = "semver"
|
||||||
vcs = "git"
|
vcs = "git"
|
||||||
|
|
||||||
[tool.isort]
|
|
||||||
default_section = "THIRDPARTY"
|
|
||||||
force_single_line = true
|
|
||||||
line_length = 99
|
|
||||||
sections = ["FUTURE", "STDLIB", "THIRDPARTY", "FIRSTPARTY", "LOCALFOLDER"]
|
|
||||||
skip_glob = ["**/.env*", "**/env/*", "**/.venv/*", "**/docs/*"]
|
|
||||||
|
|
||||||
[tool.pytest.ini_options]
|
[tool.pytest.ini_options]
|
||||||
addopts = "ansibledoctor --cov=ansibledoctor --cov-report=xml:coverage.xml --cov-report=term --cov-append --no-cov-on-fail"
|
addopts = "ansibledoctor --cov=ansibledoctor --cov-report=xml:coverage.xml --cov-report=term --no-cov-on-fail"
|
||||||
filterwarnings = [
|
filterwarnings = [
|
||||||
"ignore::FutureWarning",
|
"ignore::FutureWarning",
|
||||||
"ignore:.*collections.*:DeprecationWarning",
|
"ignore::DeprecationWarning",
|
||||||
"ignore:.*pep8.*:FutureWarning",
|
"ignore:.*pep8.*:FutureWarning",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -92,5 +75,70 @@ filterwarnings = [
|
||||||
omit = ["**/test/*"]
|
omit = ["**/test/*"]
|
||||||
|
|
||||||
[build-system]
|
[build-system]
|
||||||
build-backend = "poetry.core.masonry.api"
|
build-backend = "poetry_dynamic_versioning.backend"
|
||||||
requires = ["poetry-core>=1.0.0", "poetry-dynamic-versioning"]
|
requires = ["poetry-core>=1.0.0", "poetry-dynamic-versioning"]
|
||||||
|
|
||||||
|
[tool.ruff]
|
||||||
|
exclude = [
|
||||||
|
".git",
|
||||||
|
"__pycache__",
|
||||||
|
"build",
|
||||||
|
"dist",
|
||||||
|
"test",
|
||||||
|
"*.pyc",
|
||||||
|
"*.egg-info",
|
||||||
|
".cache",
|
||||||
|
".eggs",
|
||||||
|
"env*",
|
||||||
|
]
|
||||||
|
|
||||||
|
line-length = 99
|
||||||
|
indent-width = 4
|
||||||
|
|
||||||
|
[tool.ruff.lint]
|
||||||
|
# Explanation of errors
|
||||||
|
#
|
||||||
|
# D102: Missing docstring in public method
|
||||||
|
# D103: Missing docstring in public function
|
||||||
|
# D105: Missing docstring in magic method
|
||||||
|
# D107: Missing docstring in __init__
|
||||||
|
# D202: No blank lines allowed after function docstring
|
||||||
|
# D203: One blank line required before class docstring
|
||||||
|
# D212: Multi-line docstring summary should start at the first line
|
||||||
|
ignore = [
|
||||||
|
"D102",
|
||||||
|
"D103",
|
||||||
|
"D105",
|
||||||
|
"D107",
|
||||||
|
"D202",
|
||||||
|
"D203",
|
||||||
|
"D212",
|
||||||
|
"UP038",
|
||||||
|
"RUF012",
|
||||||
|
]
|
||||||
|
select = [
|
||||||
|
"D",
|
||||||
|
"E",
|
||||||
|
"F",
|
||||||
|
"Q",
|
||||||
|
"W",
|
||||||
|
"I",
|
||||||
|
"S",
|
||||||
|
"BLE",
|
||||||
|
"N",
|
||||||
|
"UP",
|
||||||
|
"B",
|
||||||
|
"A",
|
||||||
|
"C4",
|
||||||
|
"T20",
|
||||||
|
"SIM",
|
||||||
|
"RET",
|
||||||
|
"ARG",
|
||||||
|
"ERA",
|
||||||
|
"RUF",
|
||||||
|
]
|
||||||
|
|
||||||
|
[tool.ruff.format]
|
||||||
|
quote-style = "double"
|
||||||
|
indent-style = "space"
|
||||||
|
line-ending = "lf"
|
||||||
|
|
|
@ -1,4 +1,17 @@
|
||||||
{
|
{
|
||||||
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
||||||
"extends": ["github>thegeeklab/renovate-presets"]
|
"extends": ["github>thegeeklab/renovate-presets"],
|
||||||
|
"packageRules": [
|
||||||
|
{
|
||||||
|
"description": "Ansible base dependencies",
|
||||||
|
"matchPackageNames": ["ansible-core"],
|
||||||
|
"separateMinorPatch": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"matchManagers": ["woodpecker"],
|
||||||
|
"matchFileNames": [".woodpecker/test.yml"],
|
||||||
|
"matchPackageNames": ["docker.io/library/python"],
|
||||||
|
"enabled": false
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|
20
setup.cfg
20
setup.cfg
|
@ -1,20 +0,0 @@
|
||||||
[flake8]
|
|
||||||
# Explanation of errors
|
|
||||||
#
|
|
||||||
# D102: Missing docstring in public method
|
|
||||||
# D103: Missing docstring in public function
|
|
||||||
# D105: Missing docstring in magic method
|
|
||||||
# D107: Missing docstring in __init__
|
|
||||||
# D202: No blank lines allowed after function docstring
|
|
||||||
# W503:Line break occurred before a binary operator
|
|
||||||
ignore = D102, D103, D105, D107, D202, W503
|
|
||||||
max-line-length = 99
|
|
||||||
inline-quotes = double
|
|
||||||
exclude = .git, __pycache__, build, dist, test, *.pyc, *.egg-info, .cache, .eggs, env*
|
|
||||||
|
|
||||||
[yapf]
|
|
||||||
based_on_style = google
|
|
||||||
column_limit = 99
|
|
||||||
dedent_closing_brackets = true
|
|
||||||
coalesce_brackets = true
|
|
||||||
split_before_logical_operator = true
|
|
Loading…
Reference in New Issue
Block a user