mirror of
https://github.com/thegeeklab/ansible-doctor.git
synced 2024-06-10 20:30:53 +02:00
Compare commits
114 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
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 |
1
.lycheeignore
Normal file
1
.lycheeignore
Normal file
|
@ -0,0 +1 @@
|
|||
https://hub.docker.com/r/thegeeklab/*
|
|
@ -5,3 +5,6 @@ MD041: False
|
|||
MD024: False
|
||||
MD004:
|
||||
style: dash
|
||||
MD033:
|
||||
allowed_elements:
|
||||
- "br"
|
||||
|
|
|
@ -2,7 +2,9 @@
|
|||
- id: ansible-doctor
|
||||
name: ansible-doctor
|
||||
description: Create annotation based documentation for your Ansible roles.
|
||||
entry: ansible-doctor -f
|
||||
entry: ansible-doctor -f -qqq
|
||||
language: python
|
||||
pass_filenames: False
|
||||
always_run: True
|
||||
additional_dependencies:
|
||||
- .[ansible-core]
|
||||
|
|
|
@ -6,15 +6,14 @@ when:
|
|||
- ${CI_REPO_DEFAULT_BRANCH}
|
||||
|
||||
steps:
|
||||
build:
|
||||
- name: build
|
||||
image: docker.io/library/python:3.12
|
||||
commands:
|
||||
- git fetch --depth=2147483647
|
||||
- pip install poetry poetry-dynamic-versioning -qq
|
||||
- poetry build
|
||||
|
||||
dryrun:
|
||||
image: quay.io/thegeeklab/wp-docker-buildx:1
|
||||
- name: dryrun
|
||||
image: quay.io/thegeeklab/wp-docker-buildx:4
|
||||
settings:
|
||||
containerfile: Containerfile.multiarch
|
||||
dry_run: true
|
||||
|
@ -26,9 +25,9 @@ steps:
|
|||
when:
|
||||
- event: [pull_request]
|
||||
|
||||
publish-dockerhub:
|
||||
- name: publish-dockerhub
|
||||
image: quay.io/thegeeklab/wp-docker-buildx:4
|
||||
group: container
|
||||
image: quay.io/thegeeklab/wp-docker-buildx:1
|
||||
settings:
|
||||
auto_tag: true
|
||||
containerfile: Containerfile.multiarch
|
||||
|
@ -47,9 +46,9 @@ steps:
|
|||
branch:
|
||||
- ${CI_REPO_DEFAULT_BRANCH}
|
||||
|
||||
publish-quay:
|
||||
- name: publish-quay
|
||||
image: quay.io/thegeeklab/wp-docker-buildx:4
|
||||
group: container
|
||||
image: quay.io/thegeeklab/wp-docker-buildx:1
|
||||
settings:
|
||||
auto_tag: true
|
||||
containerfile: Containerfile.multiarch
|
||||
|
|
|
@ -6,27 +6,25 @@ when:
|
|||
- ${CI_REPO_DEFAULT_BRANCH}
|
||||
|
||||
steps:
|
||||
build:
|
||||
- name: build
|
||||
image: docker.io/library/python:3.12
|
||||
commands:
|
||||
- git fetch --depth=2147483647
|
||||
- pip install poetry poetry-dynamic-versioning -qq
|
||||
- poetry build
|
||||
|
||||
checksum:
|
||||
- name: checksum
|
||||
image: quay.io/thegeeklab/alpine-tools
|
||||
commands:
|
||||
- cd dist/ && sha256sum * > ../sha256sum.txt
|
||||
|
||||
changelog:
|
||||
- name: changelog
|
||||
image: quay.io/thegeeklab/git-sv
|
||||
commands:
|
||||
- git fetch --depth=2147483647
|
||||
- git sv current-version
|
||||
- git sv release-notes -t ${CI_COMMIT_TAG:-next} -o CHANGELOG.md
|
||||
- cat CHANGELOG.md
|
||||
|
||||
publish-github:
|
||||
- name: publish-github
|
||||
image: docker.io/plugins/github-release
|
||||
settings:
|
||||
api_key:
|
||||
|
@ -40,7 +38,7 @@ steps:
|
|||
when:
|
||||
- event: [tag]
|
||||
|
||||
publish-pypi:
|
||||
- name: publish-pypi
|
||||
image: docker.io/library/python:3.12
|
||||
secrets:
|
||||
- source: pypi_password
|
||||
|
@ -48,7 +46,6 @@ steps:
|
|||
- source: pypi_username
|
||||
target: POETRY_HTTP_BASIC_PYPI_USERNAME
|
||||
commands:
|
||||
- git fetch --depth=2147483647
|
||||
- pip install poetry poetry-dynamic-versioning -qq
|
||||
- poetry publish -n
|
||||
when:
|
||||
|
|
|
@ -6,51 +6,44 @@ when:
|
|||
- ${CI_REPO_DEFAULT_BRANCH}
|
||||
|
||||
steps:
|
||||
assets:
|
||||
- name: assets
|
||||
image: quay.io/thegeeklab/alpine-tools
|
||||
commands:
|
||||
- make doc
|
||||
|
||||
markdownlint:
|
||||
- name: markdownlint
|
||||
image: quay.io/thegeeklab/markdownlint-cli
|
||||
group: test
|
||||
commands:
|
||||
- markdownlint 'README.md' 'CONTRIBUTING.md'
|
||||
|
||||
spellcheck:
|
||||
- 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
|
||||
- spellchecker --files 'docs/**/*.md' 'README.md' 'CONTRIBUTING.md' -d .dictionary -p spell indefinite-article syntax-urls
|
||||
environment:
|
||||
FORCE_COLOR: "true"
|
||||
NPM_CONFIG_LOGLEVEL: "error"
|
||||
|
||||
link-validation:
|
||||
- name: link-validation
|
||||
image: docker.io/lycheeverse/lychee
|
||||
group: test
|
||||
commands:
|
||||
- lychee --no-progress --format detailed docs/content README.md
|
||||
|
||||
testbuild:
|
||||
image: quay.io/thegeeklab/hugo:0.115.2
|
||||
commands:
|
||||
- hugo --panicOnWarning -s docs/ -b http://localhost:8000/
|
||||
|
||||
build:
|
||||
image: quay.io/thegeeklab/hugo:0.115.2
|
||||
- name: build
|
||||
image: quay.io/thegeeklab/hugo:0.127.0
|
||||
commands:
|
||||
- hugo --panicOnWarning -s docs/
|
||||
|
||||
beautify:
|
||||
- name: beautify
|
||||
image: quay.io/thegeeklab/alpine-tools
|
||||
commands:
|
||||
- html-beautify -r -f 'docs/public/**/*.html'
|
||||
environment:
|
||||
FORCE_COLOR: "true"
|
||||
NPM_CONFIG_LOGLEVEL: error
|
||||
|
||||
publish:
|
||||
- name: publish
|
||||
image: quay.io/thegeeklab/wp-s3-action
|
||||
settings:
|
||||
access_key:
|
||||
|
@ -69,9 +62,9 @@ steps:
|
|||
- event: [push, manual]
|
||||
branch:
|
||||
- ${CI_REPO_DEFAULT_BRANCH}
|
||||
status: [success]
|
||||
status: [success, failure]
|
||||
|
||||
pushrm-dockerhub:
|
||||
- name: pushrm-dockerhub
|
||||
image: docker.io/chko/docker-pushrm:1
|
||||
secrets:
|
||||
- source: docker_password
|
||||
|
@ -88,7 +81,7 @@ steps:
|
|||
- ${CI_REPO_DEFAULT_BRANCH}
|
||||
status: [success]
|
||||
|
||||
pushrm-quay:
|
||||
- name: pushrm-quay
|
||||
image: docker.io/chko/docker-pushrm:1
|
||||
secrets:
|
||||
- source: quay_token
|
||||
|
|
|
@ -6,22 +6,29 @@ when:
|
|||
- ${CI_REPO_DEFAULT_BRANCH}
|
||||
|
||||
steps:
|
||||
check-format:
|
||||
- name: check-format
|
||||
image: docker.io/library/python:3.12
|
||||
commands:
|
||||
- git fetch --depth=2147483647
|
||||
- pip install poetry poetry-dynamic-versioning -qq
|
||||
- poetry install
|
||||
- poetry install -E ansible-core
|
||||
- poetry run ruff format --check --diff ./${CI_REPO_NAME//-/}
|
||||
environment:
|
||||
PY_COLORS: "1"
|
||||
|
||||
check-coding:
|
||||
- name: check-coding
|
||||
image: docker.io/library/python:3.12
|
||||
commands:
|
||||
- git fetch --depth=2147483647
|
||||
- pip install poetry poetry-dynamic-versioning -qq
|
||||
- poetry install
|
||||
- poetry run ruff ./${CI_REPO_NAME//-/}
|
||||
- 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"
|
||||
|
|
|
@ -8,7 +8,7 @@ when:
|
|||
runs_on: [success, failure]
|
||||
|
||||
steps:
|
||||
matrix:
|
||||
- name: matrix
|
||||
image: quay.io/thegeeklab/wp-matrix
|
||||
settings:
|
||||
homeserver:
|
||||
|
|
|
@ -5,22 +5,30 @@ when:
|
|||
branch:
|
||||
- ${CI_REPO_DEFAULT_BRANCH}
|
||||
|
||||
matrix:
|
||||
PYTHON_VERSION:
|
||||
- "3.8"
|
||||
- "3.9"
|
||||
- "3.10"
|
||||
- "3.11"
|
||||
- "3.12"
|
||||
|
||||
steps:
|
||||
pytest:
|
||||
image: docker.io/library/python:${PYTHON_VERSION}
|
||||
variables:
|
||||
- &pytest_base
|
||||
group: pytest
|
||||
commands:
|
||||
- git fetch --depth=2147483647
|
||||
- pip install poetry poetry-dynamic-versioning -qq
|
||||
- poetry install
|
||||
- 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
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
FROM python:3.12-alpine@sha256:a5d1738d6abbdff3e81c10b7f86923ebcb340ca536e21e8c5ee7d938d263dba1
|
||||
FROM python:3.12-alpine@sha256:d24ed567ee3b972478a232ceff84b0d002e18ee9f5d38234ecbffece23dfa084
|
||||
|
||||
LABEL maintainer="Robert Kaussow <mail@thegeeklab.de>"
|
||||
LABEL org.opencontainers.image.authors="Robert Kaussow <mail@thegeeklab.de>"
|
||||
|
@ -12,9 +12,9 @@ ENV TZ=UTC
|
|||
|
||||
ADD dist/ansible_doctor-*.whl /
|
||||
|
||||
RUN apk --update add --virtual .build-deps build-base libffi-dev openssl-dev && \
|
||||
RUN apk --update add --virtual .build-deps build-base libffi-dev openssl-dev git && \
|
||||
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 -rf /var/cache/apk/* && \
|
||||
rm -rf /root/.cache/
|
||||
|
|
2
Makefile
2
Makefile
|
@ -1,5 +1,5 @@
|
|||
# renovate: datasource=github-releases depName=thegeeklab/hugo-geekdoc
|
||||
THEME_VERSION := v0.44.0
|
||||
THEME_VERSION := v0.46.0
|
||||
THEME := hugo-geekdoc
|
||||
BASEDIR := docs
|
||||
THEMEDIR := $(BASEDIR)/themes
|
||||
|
|
|
@ -1,3 +1,10 @@
|
|||
"""Provide version information."""
|
||||
|
||||
__version__ = "0.0.0"
|
||||
|
||||
import sys
|
||||
|
||||
try:
|
||||
import ansible # noqa
|
||||
except ImportError:
|
||||
sys.exit("ERROR: Python requirements are missing: 'ansible-core' not found.")
|
||||
|
|
|
@ -18,11 +18,17 @@ class AnsibleDoctor:
|
|||
def __init__(self):
|
||||
self.log = SingleLog()
|
||||
self.logger = self.log.logger
|
||||
self.args = self._cli_args()
|
||||
self.config = self._get_config()
|
||||
|
||||
try:
|
||||
self.config = SingleConfig()
|
||||
self.config.load(args=self._parse_args())
|
||||
self.log.register_hanlers(json=self.config.config.logging.json)
|
||||
except ansibledoctor.exception.ConfigError as e:
|
||||
self.log.sysexit_with_message(e)
|
||||
|
||||
self._execute()
|
||||
|
||||
def _cli_args(self):
|
||||
def _parse_args(self):
|
||||
"""
|
||||
Use argparse for parsing CLI arguments.
|
||||
|
||||
|
@ -33,28 +39,40 @@ class AnsibleDoctor:
|
|||
description="Generate documentation from annotated Ansible roles using templates"
|
||||
)
|
||||
parser.add_argument(
|
||||
"base_dir", nargs="?", help="base directory (default: current working directory)"
|
||||
"base_dir",
|
||||
nargs="?",
|
||||
default=self.config.config.base_dir,
|
||||
help="base directory (default: current working directory)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-c", "--config", dest="config_file", help="path to configuration file"
|
||||
"-c",
|
||||
"--config",
|
||||
dest="config_file",
|
||||
help="path to configuration file",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-o", "--output", dest="output_dir", action="store", help="output directory"
|
||||
"-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=None,
|
||||
default=self.config.config.recursive,
|
||||
help="run recursively over the base directory subfolders",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-f",
|
||||
"--force",
|
||||
dest="force_overwrite",
|
||||
dest="renderer.force_overwrite",
|
||||
action="store_true",
|
||||
default=None,
|
||||
default=self.config.config.renderer.force_overwrite,
|
||||
help="force overwrite output file",
|
||||
)
|
||||
parser.add_argument(
|
||||
|
@ -62,7 +80,7 @@ class AnsibleDoctor:
|
|||
"--dry-run",
|
||||
dest="dry_run",
|
||||
action="store_true",
|
||||
default=None,
|
||||
default=self.config.config.dry_run,
|
||||
help="dry run without writing",
|
||||
)
|
||||
parser.add_argument(
|
||||
|
@ -70,49 +88,58 @@ class AnsibleDoctor:
|
|||
"--no-role-detection",
|
||||
dest="role_detection",
|
||||
action="store_false",
|
||||
default=None,
|
||||
default=self.config.config.role.autodetect,
|
||||
help="disable automatic role detection",
|
||||
)
|
||||
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(
|
||||
"-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(
|
||||
"--version",
|
||||
action="version",
|
||||
version=f"%(prog)s {__version__}",
|
||||
)
|
||||
parser.add_argument("--version", action="version", version=f"%(prog)s {__version__}")
|
||||
|
||||
return parser.parse_args().__dict__
|
||||
|
||||
def _get_config(self):
|
||||
try:
|
||||
config = SingleConfig(args=self.args)
|
||||
except ansibledoctor.exception.ConfigError as e:
|
||||
self.log.sysexit_with_message(e)
|
||||
|
||||
return config
|
||||
|
||||
def _execute(self):
|
||||
cwd = self.config.base_dir
|
||||
cwd = os.path.abspath(self.config.config.base_dir)
|
||||
walkdirs = [cwd]
|
||||
|
||||
if self.config.recursive:
|
||||
if self.config.config.recursive:
|
||||
walkdirs = [f.path for f in os.scandir(cwd) if f.is_dir()]
|
||||
|
||||
for item in walkdirs:
|
||||
os.chdir(item)
|
||||
|
||||
self.config.set_config(base_dir=os.getcwd())
|
||||
try:
|
||||
self.log.set_level(self.config.config["logging"]["level"])
|
||||
self.config.load(root_path=os.getcwd())
|
||||
self.log.register_hanlers(json=self.config.config.logging.json)
|
||||
except ansibledoctor.exception.ConfigError as e:
|
||||
self.log.sysexit_with_message(e)
|
||||
|
||||
try:
|
||||
self.log.set_level(self.config.config.logging.level)
|
||||
except ValueError as e:
|
||||
self.log.sysexit_with_message(f"Can not set log level.\n{e!s}")
|
||||
self.logger.info(f"Using config file: {self.config.config_file}")
|
||||
self.logger.info(f"Using config file: {self.config.config_files}")
|
||||
|
||||
self.logger.debug(f"Using working dir: {item}")
|
||||
self.logger.debug(f"Using working directory: {os.path.relpath(item, self.log.ctx)}")
|
||||
|
||||
if self.config.config["role_detection"]:
|
||||
if self.config.is_role:
|
||||
self.logger.info(f"Ansible role detected: {self.config.config['role_name']}")
|
||||
if self.config.config.role.autodetect:
|
||||
if self.config.is_role():
|
||||
self.logger.info(f"Ansible role detected: {self.config.config.role_name}")
|
||||
else:
|
||||
self.log.sysexit_with_message("No Ansible role detected")
|
||||
else:
|
||||
|
|
|
@ -3,127 +3,15 @@
|
|||
|
||||
import os
|
||||
|
||||
import anyconfig
|
||||
import environs
|
||||
import jsonschema.exceptions
|
||||
import ruamel.yaml
|
||||
from appdirs import AppDirs
|
||||
from jsonschema._utils import format_as_index
|
||||
from dynaconf import Dynaconf, ValidationError, Validator
|
||||
|
||||
import ansibledoctor.exception
|
||||
from ansibledoctor.utils import Singleton
|
||||
|
||||
config_dir = AppDirs("ansible-doctor").user_config_dir
|
||||
default_config_file = os.path.join(config_dir, "config.yml")
|
||||
default_envs_prefix = "ANSIBLE_DOCTOR_"
|
||||
|
||||
|
||||
class Config:
|
||||
"""
|
||||
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": default_config_file,
|
||||
"env": "CONFIG_FILE",
|
||||
"type": environs.Env().str,
|
||||
},
|
||||
"base_dir": {
|
||||
"default": os.getcwd(),
|
||||
"refresh": os.getcwd,
|
||||
"env": "BASE_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(),
|
||||
"refresh": os.getcwd,
|
||||
"env": "OUTPUT_DIR",
|
||||
"file": True,
|
||||
"type": environs.Env().str,
|
||||
},
|
||||
"recursive": {
|
||||
"default": False,
|
||||
"env": "RECURSIVE",
|
||||
"type": environs.Env().bool,
|
||||
},
|
||||
"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,
|
||||
},
|
||||
"template_autotrim": {
|
||||
"default": True,
|
||||
"env": "TEMPLATE_AUTOTRIM",
|
||||
"file": True,
|
||||
"type": environs.Env().bool,
|
||||
},
|
||||
"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,
|
||||
},
|
||||
"exclude_tags": {
|
||||
"default": [],
|
||||
"env": "EXCLUDE_TAGS",
|
||||
"file": True,
|
||||
"type": environs.Env().list,
|
||||
},
|
||||
"role_detection": {
|
||||
"default": True,
|
||||
"env": "ROLE_DETECTION",
|
||||
"file": True,
|
||||
"type": environs.Env().bool,
|
||||
},
|
||||
}
|
||||
"""Create configuration object."""
|
||||
|
||||
ANNOTATIONS = {
|
||||
"meta": {
|
||||
|
@ -158,183 +46,167 @@ class Config:
|
|||
},
|
||||
}
|
||||
|
||||
def __init__(self, args=None):
|
||||
"""
|
||||
Initialize a new settings class.
|
||||
def __init__(self):
|
||||
self.config_files = [
|
||||
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.
|
||||
:param config_file: An optional path to a yaml config file.
|
||||
:returns: None
|
||||
def load(self, root_path=None, args=None):
|
||||
if args:
|
||||
if args.get("config_file"):
|
||||
self.config_merge = False
|
||||
self.config_files = [os.path.abspath(args.get("config_file"))]
|
||||
args.pop("config_file")
|
||||
|
||||
"""
|
||||
if args is None:
|
||||
self._args = {}
|
||||
else:
|
||||
self._args = args
|
||||
self._schema = None
|
||||
self.config = None
|
||||
self.is_role = False
|
||||
self.set_config()
|
||||
self.args = args
|
||||
|
||||
def _get_args(self, args):
|
||||
cleaned = dict(filter(lambda item: item[1] is not None, args.items()))
|
||||
self.config = Dynaconf(
|
||||
envvar_prefix="ANSIBLE_DOCTOR",
|
||||
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=os.path.join(os.path.dirname(os.path.realpath(__file__)), "templates"),
|
||||
is_type_of=str,
|
||||
),
|
||||
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,
|
||||
),
|
||||
],
|
||||
)
|
||||
|
||||
normalized = {}
|
||||
for key, value in cleaned.items():
|
||||
normalized = self._add_dict_branch(normalized, key.split("."), value)
|
||||
self.validate()
|
||||
|
||||
# Override correct log level from argparse
|
||||
levels = ["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"]
|
||||
log_level = levels.index(self.SETTINGS["logging.level"]["default"])
|
||||
if normalized.get("logging"):
|
||||
for adjustment in normalized["logging"]["level"]:
|
||||
log_level = min(len(levels) - 1, max(log_level + adjustment, 0))
|
||||
normalized["logging"]["level"] = levels[log_level]
|
||||
log_level = levels.index(self.config.logging.level.upper())
|
||||
if self.args.get("logging.level") and isinstance(self.args["logging.level"], list):
|
||||
for lvl in self.args["logging.level"]:
|
||||
log_level = min(len(levels) - 1, max(log_level + lvl, 0))
|
||||
|
||||
return normalized
|
||||
self.args["logging__level"] = levels[log_level]
|
||||
|
||||
def _get_defaults(self):
|
||||
normalized = {}
|
||||
for key, item in self.SETTINGS.items():
|
||||
if item.get("refresh"):
|
||||
item["default"] = item["refresh"]()
|
||||
normalized = self._add_dict_branch(normalized, key.split("."), item["default"])
|
||||
if root_path:
|
||||
self.args["base_dir"] = root_path
|
||||
|
||||
self.schema = anyconfig.gen_schema(normalized)
|
||||
return normalized
|
||||
self.config.update(self.args)
|
||||
self.validate()
|
||||
|
||||
def _get_envs(self):
|
||||
normalized = {}
|
||||
for key, item in self.SETTINGS.items():
|
||||
if item.get("env"):
|
||||
envname = f"{default_envs_prefix}{item['env']}"
|
||||
def validate(self):
|
||||
try:
|
||||
value = item["type"](envname)
|
||||
normalized = self._add_dict_branch(normalized, key.split("."), value)
|
||||
except environs.EnvError as e:
|
||||
if f'"{envname}" not set' in str(e):
|
||||
pass
|
||||
else:
|
||||
raise ansibledoctor.exception.ConfigError(
|
||||
"Unable to read environment variable", str(e)
|
||||
) from e
|
||||
self.config.validators.validate_all()
|
||||
except ValidationError as e:
|
||||
raise ansibledoctor.exception.ConfigError("Configuration error", e.message) from e
|
||||
|
||||
return normalized
|
||||
|
||||
def set_config(self, base_dir=None):
|
||||
args = self._get_args(self._args)
|
||||
envs = self._get_envs()
|
||||
defaults = self._get_defaults()
|
||||
|
||||
self.recursive = defaults.get("recursive")
|
||||
if envs.get("recursive"):
|
||||
self.recursive = envs.get("recursive")
|
||||
if args.get("recursive"):
|
||||
self.recursive = args.get("recursive")
|
||||
if "recursive" in defaults:
|
||||
defaults.pop("recursive")
|
||||
|
||||
self.config_file = defaults.get("config_file")
|
||||
if envs.get("config_file"):
|
||||
self.config_file = self._normalize_path(envs.get("config_file"))
|
||||
if args.get("config_file"):
|
||||
self.config_file = self._normalize_path(args.get("config_file"))
|
||||
if "config_file" in defaults:
|
||||
defaults.pop("config_file")
|
||||
|
||||
self.base_dir = defaults.get("base_dir")
|
||||
if envs.get("base_dir"):
|
||||
self.base_dir = self._normalize_path(envs.get("base_dir"))
|
||||
if args.get("base_dir"):
|
||||
self.base_dir = self._normalize_path(args.get("base_dir"))
|
||||
if base_dir:
|
||||
self.base_dir = base_dir
|
||||
if "base_dir" in defaults:
|
||||
defaults.pop("base_dir")
|
||||
|
||||
self.is_role = os.path.isdir(os.path.join(self.base_dir, "tasks"))
|
||||
|
||||
# compute role_name default
|
||||
defaults["role_name"] = os.path.basename(self.base_dir)
|
||||
|
||||
source_files = []
|
||||
source_files.append((self.config_file, False))
|
||||
source_files.append((os.path.join(os.getcwd(), ".ansibledoctor"), True))
|
||||
source_files.append((os.path.join(os.getcwd(), ".ansibledoctor.yml"), True))
|
||||
source_files.append((os.path.join(os.getcwd(), ".ansibledoctor.yaml"), True))
|
||||
|
||||
for config, first_found in source_files:
|
||||
if config and os.path.exists(config):
|
||||
with open(config, encoding="utf8") as stream:
|
||||
s = stream.read()
|
||||
try:
|
||||
file_dict = ruamel.yaml.YAML(typ="safe", pure=True).load(s)
|
||||
except (
|
||||
ruamel.yaml.composer.ComposerError,
|
||||
ruamel.yaml.scanner.ScannerError,
|
||||
) as e:
|
||||
message = f"{e.context} {e.problem}"
|
||||
raise ansibledoctor.exception.ConfigError(
|
||||
f"Unable to read config file: {config}", message
|
||||
) from e
|
||||
|
||||
if self._validate(file_dict):
|
||||
anyconfig.merge(defaults, file_dict, ac_merge=anyconfig.MS_DICTS)
|
||||
defaults["logging"]["level"] = defaults["logging"]["level"].upper()
|
||||
|
||||
self.config_file = config
|
||||
if first_found:
|
||||
break
|
||||
|
||||
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 filename in fix_files:
|
||||
if defaults[filename] and defaults[filename] != "":
|
||||
defaults[filename] = self._normalize_path(defaults[filename])
|
||||
|
||||
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)))
|
||||
|
||||
return path
|
||||
|
||||
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,
|
||||
def is_role(self):
|
||||
self.config.role_name = self.config.get(
|
||||
"role_name", os.path.basename(self.config.base_dir)
|
||||
)
|
||||
raise ansibledoctor.exception.ConfigError("Configuration error", schema_error) from e
|
||||
|
||||
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
|
||||
return os.path.isdir(os.path.join(self.config.base_dir, "tasks"))
|
||||
|
||||
def get_annotations_definition(self, automatic=True):
|
||||
annotations = {}
|
||||
if automatic:
|
||||
for k, item in self.ANNOTATIONS.items():
|
||||
if "automatic" in item and item["automatic"]:
|
||||
if item.get("automatic"):
|
||||
annotations[k] = item
|
||||
return annotations
|
||||
|
||||
|
@ -342,7 +214,7 @@ class Config:
|
|||
annotations = []
|
||||
if automatic:
|
||||
for k, item in self.ANNOTATIONS.items():
|
||||
if "automatic" in item and item["automatic"]:
|
||||
if item.get("automatic"):
|
||||
annotations.append(k)
|
||||
return annotations
|
||||
|
||||
|
@ -352,9 +224,9 @@ class Config:
|
|||
|
||||
:return: str abs path
|
||||
"""
|
||||
template_dir = self.config.get("template_dir")
|
||||
template = self.config.get("template")
|
||||
return os.path.realpath(os.path.join(template_dir, template))
|
||||
template_base = self.config.get("template.src")
|
||||
template_name = self.config.get("template.name")
|
||||
return os.path.realpath(os.path.join(template_base, template_name))
|
||||
|
||||
|
||||
class SingleConfig(Config, metaclass=Singleton):
|
||||
|
|
|
@ -36,14 +36,14 @@ class Generator:
|
|||
|
||||
:return: None
|
||||
"""
|
||||
template_dir = self.config.get_template()
|
||||
if os.path.isdir(template_dir):
|
||||
self.logger.info(f"Using template dir: {template_dir}")
|
||||
template = self.config.get_template()
|
||||
if os.path.isdir(template):
|
||||
self.logger.info(f"Using template: {os.path.relpath(template, self.log.ctx)}")
|
||||
else:
|
||||
self.log.sysexit_with_message(f"Can not open template dir {template_dir}")
|
||||
self.log.sysexit_with_message(f"Can not open template directory {template}")
|
||||
|
||||
for file in glob.iglob(template_dir + "/**/*." + self.extension, recursive=True):
|
||||
relative_file = file[len(template_dir) + 1 :]
|
||||
for file in glob.iglob(template + "/**/*." + self.extension, recursive=True):
|
||||
relative_file = file[len(template) + 1 :]
|
||||
if ntpath.basename(file)[:1] != "_":
|
||||
self.logger.debug(f"Found template file: {relative_file}")
|
||||
self.template_files.append(relative_file)
|
||||
|
@ -56,19 +56,19 @@ class Generator:
|
|||
os.makedirs(directory, exist_ok=True)
|
||||
self.logger.info(f"Creating dir: {directory}")
|
||||
except FileExistsError as e:
|
||||
self.log.sysexit_with_message(str(e))
|
||||
self.log.sysexit_with_message(e)
|
||||
|
||||
def _write_doc(self):
|
||||
files_to_overwite = []
|
||||
|
||||
for file in self.template_files:
|
||||
doc_file = os.path.join(
|
||||
self.config.config.get("output_dir"), os.path.splitext(file)[0]
|
||||
self.config.config.get("renderer.dest"), os.path.splitext(file)[0]
|
||||
)
|
||||
if os.path.isfile(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()
|
||||
header_content = ""
|
||||
if bool(header_file):
|
||||
|
@ -81,14 +81,16 @@ class Generator:
|
|||
|
||||
if (
|
||||
len(files_to_overwite) > 0
|
||||
and self.config.config.get("force_overwrite") is False
|
||||
and self.config.config.get("renderer.force_overwrite") is False
|
||||
and not self.config.config["dry_run"]
|
||||
):
|
||||
files_to_overwite_string = "\n".join(files_to_overwite)
|
||||
self.logger.warning(f"This files will be overwritten:\n{files_to_overwite_string}")
|
||||
prompt = f"These files will be overwritten:\n{files_to_overwite_string}".replace(
|
||||
"\n", "\n... "
|
||||
)
|
||||
|
||||
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...")
|
||||
except ansibledoctor.exception.InputError as e:
|
||||
self.logger.debug(str(e))
|
||||
|
@ -96,11 +98,14 @@ class Generator:
|
|||
|
||||
for file in self.template_files:
|
||||
doc_file = os.path.join(
|
||||
self.config.config.get("output_dir"), os.path.splitext(file)[0]
|
||||
self.config.config.get("renderer.dest"), os.path.splitext(file)[0]
|
||||
)
|
||||
source_file = self.config.get_template() + "/" + file
|
||||
|
||||
self.logger.debug(f"Writing doc output to: {doc_file} from: {source_file}")
|
||||
self.logger.debug(
|
||||
f"Writing renderer output to: {os.path.relpath(doc_file, self.log.ctx)} "
|
||||
f"from: {os.path.dirname(os.path.relpath(source_file, self.log.ctx))}"
|
||||
)
|
||||
|
||||
# make sure the directory exists
|
||||
self._create_dir(os.path.dirname(doc_file))
|
||||
|
@ -121,7 +126,10 @@ class Generator:
|
|||
jenv.filters["safe_join"] = self._safe_join
|
||||
# keep the old name of the function to not break custom templates.
|
||||
jenv.filters["save_join"] = self._safe_join
|
||||
data = jenv.from_string(data).render(role_data, role=role_data)
|
||||
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"]:
|
||||
with open(doc_file, "wb") as outfile:
|
||||
outfile.write(header_content.encode("utf-8"))
|
||||
|
@ -165,12 +173,12 @@ class Generator:
|
|||
|
||||
normalized = jinja2.filters.do_join(eval_ctx, value, d, attribute=None)
|
||||
|
||||
if self.config.config["template_autotrim"]:
|
||||
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):
|
||||
self.logger.info(f"Using output dir: {self.config.config.get('output_dir')}")
|
||||
self.logger.info(f"Using renderer destination: {self.config.config.get('renderer.dest')}")
|
||||
self._write_doc()
|
||||
|
|
|
@ -72,12 +72,15 @@ class Parser:
|
|||
except YAMLError as e:
|
||||
self.log.sysexit_with_message(f"Unable to read yaml file {rfile}\n{e}")
|
||||
|
||||
tags = [
|
||||
task.get("tags")
|
||||
for task in raw
|
||||
if task.get("tags")
|
||||
and task.get("tags") not in self.config.config["exclude_tags"]
|
||||
]
|
||||
tags = []
|
||||
for task in raw:
|
||||
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}
|
||||
|
|
|
@ -21,7 +21,8 @@ class Registry:
|
|||
def __init__(self):
|
||||
self._doc = []
|
||||
self.config = SingleConfig()
|
||||
self.log = SingleLog().logger
|
||||
self.log = SingleLog()
|
||||
self.logger = self.log.logger
|
||||
self._scan_for_yamls()
|
||||
|
||||
def get_files(self):
|
||||
|
@ -35,19 +36,19 @@ class Registry:
|
|||
:return: None
|
||||
"""
|
||||
extensions = YAML_EXTENSIONS
|
||||
base_dir = self.config.base_dir
|
||||
role_name = os.path.basename(base_dir)
|
||||
base_dir = self.config.config.base_dir
|
||||
role_name = self.config.config.role_name
|
||||
excludes = self.config.config.get("exclude_files")
|
||||
excludespec = pathspec.PathSpec.from_lines("gitwildmatch", excludes)
|
||||
|
||||
self.log.debug(f"Scan for files: {base_dir}")
|
||||
self.logger.debug(f"Scan for files: {os.path.relpath(base_dir,self.log.ctx)}")
|
||||
|
||||
for extension in extensions:
|
||||
pattern = os.path.join(base_dir, "**/*." + extension)
|
||||
for filename in glob.iglob(pattern, recursive=True):
|
||||
if not excludespec.match_file(filename):
|
||||
self.log.debug(
|
||||
f"Adding file to '{role_name}': {os.path.relpath(filename, base_dir)}"
|
||||
f"Adding file to role '{role_name}': {os.path.relpath(filename, base_dir)}"
|
||||
)
|
||||
self._doc.append(filename)
|
||||
else:
|
||||
|
|
|
@ -2,10 +2,12 @@
|
|||
{% set var = role.var | default({}) %}
|
||||
{% if var %}
|
||||
- [Default Variables](#default-variables)
|
||||
{% if not options.tabulate_vars %}
|
||||
{% for key, item in var | dictsort %}
|
||||
- [{{ key }}](#{{ key }})
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% if tag %}
|
||||
- [Discovered Tags](#discovered-tags)
|
||||
{% 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 %}
|
|
@ -23,7 +23,11 @@ summary: {{ meta.summary.value | safe_join(" ") }}
|
|||
{% include '_requirements.j2' %}
|
||||
|
||||
{# Vars #}
|
||||
{% if options.tabulate_vars %}
|
||||
{% include '_vars_tabulated.j2' %}
|
||||
{% else %}
|
||||
{% include '_vars.j2' %}
|
||||
{% endif %}
|
||||
|
||||
{# Tag #}
|
||||
{% include '_tag.j2' %}
|
||||
|
|
|
@ -15,7 +15,11 @@
|
|||
{% include '_requirements.j2' %}
|
||||
|
||||
{# Vars #}
|
||||
{% if options.tabulate_vars %}
|
||||
{% include '_vars_tabulated.j2' %}
|
||||
{% else %}
|
||||
{% include '_vars.j2' %}
|
||||
{% endif %}
|
||||
|
||||
{# Tag #}
|
||||
{% include '_tag.j2' %}
|
||||
|
|
|
@ -4,10 +4,12 @@
|
|||
{% set var = role.var | default({}) %}
|
||||
{% if var %}
|
||||
- [Default Variables](#default-variables)
|
||||
{% if not options.tabulate_vars %}
|
||||
{% for key, item in var | dictsort %}
|
||||
- [{{ key }}](#{{ key }})
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% if tag %}
|
||||
- [Discovered Tags](#discovered-tags)
|
||||
{% 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 %}
|
|
@ -128,15 +128,15 @@ class LogFilter:
|
|||
class MultilineFormatter(logging.Formatter):
|
||||
"""Reset color after newline characters."""
|
||||
|
||||
def format(self, record): # noqa
|
||||
record.msg = record.msg.replace("\n", f"\n{colorama.Style.RESET_ALL}... ")
|
||||
def format(self, record):
|
||||
record.msg = record.msg.strip().replace("\n", f"\n{colorama.Style.RESET_ALL}... ")
|
||||
return logging.Formatter.format(self, record)
|
||||
|
||||
|
||||
class MultilineJsonFormatter(jsonlogger.JsonFormatter):
|
||||
"""Remove newline characters."""
|
||||
|
||||
def format(self, record): # noqa
|
||||
def format(self, record):
|
||||
record.msg = record.msg.replace("\n", " ")
|
||||
return jsonlogger.JsonFormatter.format(self, record)
|
||||
|
||||
|
@ -145,13 +145,10 @@ class Log:
|
|||
"""Handle logging."""
|
||||
|
||||
def __init__(self, level=logging.WARNING, name="ansibledoctor", json=False):
|
||||
self.ctx = os.getcwd()
|
||||
self.logger = logging.getLogger(name)
|
||||
self.logger.setLevel(level)
|
||||
self.logger.addHandler(self._get_error_handler(json=json))
|
||||
self.logger.addHandler(self._get_warning_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.register_hanlers(json=json)
|
||||
self.logger.propagate = False
|
||||
|
||||
def _get_error_handler(self, json=False):
|
||||
|
@ -236,7 +233,7 @@ class Log:
|
|||
handler.addFilter(LogFilter(logging.DEBUG))
|
||||
handler.setFormatter(
|
||||
MultilineFormatter(
|
||||
self.critical(
|
||||
self.debug(
|
||||
CONSOLE_FORMAT.format(
|
||||
colorama.Fore.BLUE, colorama.Style.BRIGHT, colorama.Style.RESET_ALL
|
||||
)
|
||||
|
@ -249,8 +246,24 @@ class Log:
|
|||
|
||||
return handler
|
||||
|
||||
def register_hanlers(self, json=False):
|
||||
"""
|
||||
Enable or disable JSON logging.
|
||||
|
||||
:param enable: True to enable JSON logging, False to disable
|
||||
"""
|
||||
# Remove all existing handlers
|
||||
for handler in self.logger.handlers[:]:
|
||||
self.logger.removeHandler(handler)
|
||||
|
||||
self.logger.addHandler(self._get_error_handler(json=json))
|
||||
self.logger.addHandler(self._get_warning_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))
|
||||
|
||||
def set_level(self, s):
|
||||
self.logger.setLevel(s)
|
||||
self.logger.setLevel(s.upper())
|
||||
|
||||
def debug(self, msg):
|
||||
"""Format info messages and return string."""
|
||||
|
@ -287,7 +300,7 @@ class Log:
|
|||
sys.exit(code)
|
||||
|
||||
def sysexit_with_message(self, msg, code=1):
|
||||
self.logger.critical(str(msg.strip()))
|
||||
self.logger.critical(str(msg).strip())
|
||||
self.sysexit(code)
|
||||
|
||||
|
||||
|
|
|
@ -4,7 +4,6 @@ from collections import defaultdict
|
|||
from contextlib import suppress
|
||||
|
||||
import ruamel.yaml
|
||||
import yaml
|
||||
from ansible.parsing.yaml.loader import AnsibleLoader
|
||||
|
||||
import ansibledoctor.exception
|
||||
|
@ -28,10 +27,10 @@ def parse_yaml_ansible(yamlfile):
|
|||
loader = AnsibleLoader(yamlfile)
|
||||
data = loader.get_single_data() or []
|
||||
except (
|
||||
yaml.parser.ParserError,
|
||||
yaml.scanner.ScannerError,
|
||||
yaml.constructor.ConstructorError,
|
||||
yaml.composer.ComposerError,
|
||||
ruamel.yaml.parser.ParserError,
|
||||
ruamel.yaml.scanner.ScannerError,
|
||||
ruamel.yaml.constructor.ConstructorError,
|
||||
ruamel.yaml.composer.ComposerError,
|
||||
) as e:
|
||||
message = (
|
||||
f"{e.context} in line {e.context_mark.line}, column {e.context_mark.line}\n"
|
||||
|
@ -52,7 +51,7 @@ def parse_yaml(yamlfile):
|
|||
|
||||
data = ruamel.yaml.YAML(typ="rt").load(yamlfile)
|
||||
_yaml_remove_comments(data)
|
||||
data = defaultdict(dict, data)
|
||||
data = defaultdict(dict, data or {})
|
||||
except (
|
||||
ruamel.yaml.parser.ParserError,
|
||||
ruamel.yaml.scanner.ScannerError,
|
||||
|
|
|
@ -4,11 +4,12 @@ title: Using pip
|
|||
|
||||
```Shell
|
||||
# From PyPI as unprivileged user
|
||||
$ pip install ansible-doctor --user
|
||||
$ pip install ansible-doctor[ansible-core] --user
|
||||
|
||||
# .. or as root
|
||||
$ sudo pip install ansible-doctor
|
||||
$ sudo pip install ansible-doctor[ansible-core]
|
||||
|
||||
# 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.
|
||||
$ 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]
|
||||
```
|
||||
|
|
|
@ -20,36 +20,17 @@ Configuration options can be set in different places, which are processed in the
|
|||
---
|
||||
# Default is the current working directory.
|
||||
base_dir:
|
||||
|
||||
role:
|
||||
# Default is the basename of 'role_name'.
|
||||
role_name:
|
||||
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
|
||||
autodetect: True
|
||||
|
||||
# Don't write anything to file system.
|
||||
dry_run: False
|
||||
|
||||
logging:
|
||||
# Possible options debug | info | warning | error | critical
|
||||
level: "warning"
|
||||
# Json logging can be enabled if a parsable output is required
|
||||
json: False
|
||||
|
||||
# Path to write rendered template file. Default is the current working directory.
|
||||
output_dir:
|
||||
# Default is built-in templates directory.
|
||||
template_dir:
|
||||
template: readme
|
||||
# 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.
|
||||
template_autotrim: True
|
||||
|
||||
# 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:
|
||||
|
@ -59,6 +40,36 @@ exclude_files: []
|
|||
# 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: readme
|
||||
# Default is built-in templates directory.
|
||||
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
|
||||
|
@ -91,22 +102,25 @@ options:
|
|||
## Environment Variables
|
||||
|
||||
```Shell
|
||||
ANSIBLE_DOCTOR_CONFIG_FILE=
|
||||
ANSIBLE_DOCTOR_ROLE_DETECTION=true
|
||||
ANSIBLE_DOCTOR_BASE_DIR=
|
||||
ANSIBLE_DOCTOR_RECURSIVE=false
|
||||
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_TEMPLATE_AUTOTRIM=true
|
||||
ANSIBLE_DOCTOR_FORCE_OVERWRITE=false
|
||||
ANSIBLE_DOCTOR_CUSTOM_HEADER=
|
||||
ANSIBLE_DOCTOR_DRY_RUN=False
|
||||
ANSIBLE_DOCTOR_EXCLUDE_FILES=
|
||||
ANSIBLE_DOCTOR_EXCLUDE_FILES=molecule/,files/**/*.py
|
||||
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
|
||||
|
@ -119,8 +133,8 @@ To use _ansible-doctor_ with the [pre-commit](https://pre-commit.com/) framework
|
|||
|
||||
{{< highlight yaml "linenos=table" >}}
|
||||
- repo: https://github.com/thegeeklab/ansible-doctor
|
||||
# change ref to the latest release from https://github.com/thegeeklab/ansible-doctor/releases
|
||||
rev: v1.4.8
|
||||
# update version with `pre-commit autoupdate`
|
||||
rev: v4.0.4
|
||||
hooks:
|
||||
- id: ansible-doctor
|
||||
{{< /highlight >}}
|
||||
|
|
|
@ -1,5 +1,9 @@
|
|||
---
|
||||
custom_header: HEADER.md
|
||||
logging:
|
||||
level: debug
|
||||
template: readme
|
||||
|
||||
template:
|
||||
name: readme
|
||||
|
||||
renderer:
|
||||
include_header: HEADER.md
|
||||
|
|
|
@ -204,7 +204,6 @@ demo_role_unset: some_value
|
|||
|
||||
## Dependencies
|
||||
|
||||
- role1
|
||||
- role2
|
||||
|
||||
## License
|
||||
|
|
|
@ -18,6 +18,5 @@ galaxy_info:
|
|||
- documentation
|
||||
|
||||
dependencies:
|
||||
- role1
|
||||
- role: role2
|
||||
- name: namespace.role3
|
||||
|
|
|
@ -1,5 +1,9 @@
|
|||
---
|
||||
custom_header: HEADER.md
|
||||
logging:
|
||||
level: debug
|
||||
template: readme
|
||||
|
||||
template:
|
||||
name: readme
|
||||
|
||||
renderer:
|
||||
include_header: HEADER.md
|
||||
|
|
917
poetry.lock
generated
917
poetry.lock
generated
File diff suppressed because it is too large
Load Diff
|
@ -10,7 +10,6 @@ classifiers = [
|
|||
"Natural Language :: English",
|
||||
"Operating System :: POSIX",
|
||||
"Programming Language :: Python :: 3",
|
||||
"Programming Language :: Python :: 3.8",
|
||||
"Programming Language :: Python :: 3.9",
|
||||
"Programming Language :: Python :: 3.10",
|
||||
"Programming Language :: Python :: 3.11",
|
||||
|
@ -22,41 +21,43 @@ classifiers = [
|
|||
description = "Generate documentation from annotated Ansible roles using templates."
|
||||
documentation = "https://ansible-doctor.geekdocs.de/"
|
||||
homepage = "https://ansible-doctor.geekdocs.de/"
|
||||
include = [
|
||||
"LICENSE",
|
||||
]
|
||||
include = ["LICENSE"]
|
||||
keywords = ["ansible", "role", "documentation"]
|
||||
license = "GPL-3.0-only"
|
||||
name = "ansible-doctor"
|
||||
packages = [
|
||||
{include = "ansibledoctor"},
|
||||
]
|
||||
packages = [{ include = "ansibledoctor" }]
|
||||
readme = "README.md"
|
||||
repository = "https://github.com/thegeeklab/ansible-doctor/"
|
||||
version = "0.0.0"
|
||||
|
||||
[tool.poetry.dependencies]
|
||||
Jinja2 = "3.1.2"
|
||||
anyconfig = "0.13.0"
|
||||
Jinja2 = "3.1.4"
|
||||
anyconfig = "0.14.0"
|
||||
appdirs = "1.4.4"
|
||||
colorama = "0.4.6"
|
||||
environs = "9.5.0"
|
||||
jsonschema = "4.19.2"
|
||||
pathspec = "0.11.2"
|
||||
python = "^3.8.0"
|
||||
python-json-logger = "2.0.7"
|
||||
"ruamel.yaml" = "0.18.5"
|
||||
ansible-core = "2.13"
|
||||
environs = "11.0.0"
|
||||
jsonschema = "4.22.0"
|
||||
pathspec = "0.12.1"
|
||||
python = "^3.9.0"
|
||||
python-json-logger = { git = "https://github.com/nhairs/python-json-logger.git", tag = "v3.1.0" }
|
||||
"ruamel.yaml" = "0.18.6"
|
||||
dynaconf = "3.2.5"
|
||||
ansible-core = { version = "2.14.17", optional = true }
|
||||
|
||||
[tool.poetry.extras]
|
||||
ansible-core = ["ansible-core"]
|
||||
|
||||
[tool.poetry.scripts]
|
||||
ansible-doctor = "ansibledoctor.cli:main"
|
||||
|
||||
[tool.poetry.group.dev.dependencies]
|
||||
ruff = "0.1.4"
|
||||
pytest = "7.4.3"
|
||||
pytest-mock = "3.12.0"
|
||||
pytest-cov = "4.1.0"
|
||||
ruff = "0.4.8"
|
||||
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]
|
||||
enable = true
|
||||
|
@ -95,6 +96,7 @@ exclude = [
|
|||
line-length = 99
|
||||
indent-width = 4
|
||||
|
||||
[tool.ruff.lint]
|
||||
# Explanation of errors
|
||||
#
|
||||
# D102: Missing docstring in public method
|
||||
|
|
|
@ -1,4 +1,17 @@
|
|||
{
|
||||
"$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
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user