89 Commits
Author SHA1 Message Date
volker.raschekandCopilot e580de5e4c ci: install the molecule collections before linting
Lint Markdown files / markdown-lint (push) Successful in 11s
Ansible Linter / ansible-lint (push) Successful in 2m10s
Molecule / Molecule (push) Successful in 5m38s
Release Ansible Role / Release Ansible Role (push) Successful in 3m13s
ansible-lint does not only lint the role but the molecule scenario as well, and the scenario creates its containers
with community.docker. Only the runtime requirements of the role were installed, so the linter could not resolve
community.docker.docker_container and failed the syntax check.

The collection list of the scenario is a superset of the runtime requirements, therefore it is used instead of adding
a test only dependency to requirements.yml.

Co-authored-by: Copilot <copilot@github.com>
2026-09-10 17:51:14 +02:00
volker.raschekandCopilot 9b77592091 ci: run the molecule scenario on every push
Lint Markdown files / markdown-lint (push) Successful in 7s
Ansible Linter / ansible-lint (push) Failing after 3m12s
Molecule / Molecule (push) Successful in 6m15s
Without a workflow the scenario only runs when somebody remembers to run it locally, which is exactly the situation
the tests were written to end.

The repository is checked out into a directory named after the role and not after the repository, because the scenario
includes the role by its name and molecule puts the parent of the project directory on the roles path.

Co-authored-by: Copilot <copilot@github.com>
2026-09-10 10:16:18 +02:00
volker.raschekandCopilot a343205fd3 test(molecule): cover the role with a molecule scenario
The role changed a lot and none of it was verified against a real system so far. The scenario starts one container per
supported distribution family, applies the role and asserts afterwards that the users and groups exist as declared,
that the managed files carry the documented mode, owner and content, that a user without optional settings does not
receive any of the optional files and that a user declared as absent is gone again.

The idempotence step is the actual reason for the scenario. The deterministic password salt and the btrfs device
lookup were changed to stop reporting a change on every run, and only a second converge proves that.

A btrfs home is not covered, because a container has no btrfs filesystem to create a subvolume on.

The ssh key pair the scenario feeds into the role is generated during create and removed again during destroy, so no
private key material ends up in the repository. The generated files are ignored for the case that a destroy never
runs.

Co-authored-by: Copilot <copilot@github.com>
2026-09-10 09:54:00 +02:00
volker.raschekandCopilot 00f465e5e1 fix(meta): declare the versions the role actually requires
The role claimed to work with ansible 2.9, but two of its building blocks did not exist back then. The path_join
filter was introduced in ansible-base 2.10 and the btrfs_subvolume module in community.general 6.6.0, which in turn
requires ansible-core 2.11. Running the role on 2.9 therefore failed with an undefined filter instead of a readable
message about an unsupported control node.

The collection is now declared in a requirements.yml, mirroring the layout of the certificate_authority role, and the
lower bound is pinned because an older community.general still resolves but lacks the btrfs module. The linter
workflow installs that file, because ansible-lint cannot resolve the btrfs_subvolume tasks without the collection
being present, which only surfaced in CI where no collections are preinstalled.

Co-authored-by: Copilot <copilot@github.com>
2026-09-09 21:39:42 +02:00
volker.raschekandCopilot 5f757914be fix(tasks): remove the btrfs subvolume of a deleted unix user
Deleting a user whose home is a btrfs subvolume left that subvolume behind. The role passed remove=true to the user
module, but userdel removes a home directory with rmdir, which refuses to delete a subvolume that still holds nested
subvolumes or is otherwise not empty. The home therefore survived the removal and blocked a later recreation of the
same user, because btrfs_subvolume then found the path already occupied.

The subvolume is now deleted explicitly by the same module that created it, which keeps the creation and the removal
symmetric. Snapshots are stored outside the subvolume, so a btrbk based backup keeps the data available even though
the home itself is gone.

The removal is skipped when the home directory no longer exists, since findmnt fails on a missing path.

Co-authored-by: Copilot <copilot@github.com>
2026-09-09 21:39:42 +02:00
volker.raschekandCopilot f222b1c615 fix(tasks): hide secret material written by the netrc and ssh key tasks
The `no_log: true` on the include in tasks/main.yaml only censors the include statement and its loop item. It is not
inherited by the included tasks, which was verified with a minimal playbook: a debug task inside an included file prints
its message in clear even though the include itself is censored.

Two tasks therefore leaked secrets. The netrc template exposes every machine password in its rendered content, and the
copy of the private ssh keys exposes the key material itself. Both are printed by the diff mode, which is exactly the
mode used when reviewing what a run would change. They are marked `no_log` individually now.

The user task is deliberately left alone. Its `password` argument is declared `no_log` in the argument spec of the
module, so ansible already censors it, and a task level flag would only make the remaining output useless.

The blanket flag on the include stays in place, because the loop item still carries the plaintext password.

Co-authored-by: Copilot <copilot@github.com>
2026-09-09 21:39:42 +02:00
volker.raschekandCopilot 279ee1929a fix(tasks): create the bashrc before sourcing a drop-in from it
The lineinfile task appends the source line to `~/.bashrc` but never declared `create`, so it aborted with "Destination
does not exist" whenever that file was missing. This hits every user configured with `create_home: false`, and it hit
every btrfs user as well, because their home is an empty subvolume and useradd therefore skips the skel rollout.

`mode` is added along with it, otherwise a newly created bashrc would inherit whatever the umask happens to be. The
existing `owner` and `group` were dead settings until now, since the file was never created by this task.

Co-authored-by: Copilot <copilot@github.com>
2026-09-09 21:39:42 +02:00
volker.raschekandCopilot 41a645f748 fix(tasks): accept an integer gid when creating a unix group
The two group tasks differed only in whether `gid` was passed, and the branch between them tested
`unix_group.value.gid | length`. The length filter has no meaning for an integer and raises "object of type 'int' has no
len()". The example in defaults/main.yaml declares `gid: 1001` unquoted, so the documented usage aborted the run, while
the quoted variant in the README happened to work.

Both tasks are merged into one that passes the gid through `default(omit, true)`. The boolean form of the filter is
required to keep the previous meaning of an empty gid, which is to let the system assign one. The only value that
changes semantics is gid 0, which is the root group and outside the scope of this role.

Co-authored-by: Copilot <copilot@github.com>
2026-09-09 21:39:42 +02:00
volker.raschekandCopilot 517d9c1c54 refactor(tasks): prefix the home directory fact with the role name
`set_fact` writes into the play scope, so `user_user_home` outlived the loop iteration that set it and collided with any
playbook variable of the same name. Worse, if the defining task were ever skipped, every following task would silently
operate on the home directory of the previously processed user.

Renaming it to `_unix_users_home` marks it as role internal and matches the underscore prefix already used by the other
internal facts in this role. Purely mechanical, no behaviour changes.

Co-authored-by: Copilot <copilot@github.com>
2026-09-09 21:39:42 +02:00
volker.raschekandCopilot 095705643c fix(tasks): derive a deterministic password salt per unix user
`password_hash('sha512')` without an explicit salt generates a new random salt on every invocation. The resulting hash
differed on each run, so the user module rewrote /etc/shadow and reported a change every time the role was applied. This
was the single biggest obstacle to a green idempotence check.

The salt is now derived from the user name, which keeps the hash stable across runs while still giving every account its
own salt, so two users sharing a password do not end up with an identical hash.

Verified locally: repeated runs produce a byte identical hash, and different user names produce different ones.

Co-authored-by: Copilot <copilot@github.com>
2026-09-09 21:39:42 +02:00
volker.raschekandCopilot c34b59633c fix(tasks): stop reporting a change when looking up the btrfs device
`findmnt` only reads the mount table, but the task declared `changed_when` on a successful return code and therefore
reported a change on every single run. Any btrfs backed user made the whole play non idempotent, which an idempotence
check would flag as soon as one exists.

The `failed_when` on a non zero return code is dropped along with it, since that is exactly what the command module does
by default.

Co-authored-by: Copilot <copilot@github.com>
2026-09-09 21:39:42 +02:00
volker.raschekandCopilot 68ee8ebbcd fix(tasks)!: repair the broken guards in the unix user creation
Several defects accumulated in this task file and are fixed together, because they overlap in the same code paths.

The debug task was dead code. Its `msg` lacked the Jinja delimiters and would have printed the literal string
`_unix_users_btrfs_device.stdout`, and it was gated on `_unix_users_debug`, a variable that is neither defined in
defaults nor documented anywhere. It is removed instead of repaired, since the failure path already reports the device.

The four `ansible.builtin.user` tasks differed only in whether `uid` and `groups` were passed. They are collapsed into a
single task using `default(omit)`, which removes the risk that a fix lands in one of the four copies only. This also
fixes `comment`, which dereferenced `unix_user.value.name` unconditionally and aborted for every user that did not set
the undocumented and supposedly optional key.

Two conditions compared a length against zero with `>=`, which is true for any list. As a result an empty `ssh.config`
still produced a config file, and the removal counterpart never triggered. The create and remove pair for `.ssh/config`
now mirrors the one already used for `authorized_keys`.

The private key source was resolved through `{{ playbook_dir }}`, while the authorized key lookup in the template uses
the regular relative search path. Both now use the same mechanism, which is a superset of the previous location, so
existing playbook layouts keep working, and the role becomes testable from a molecule scenario.

BREAKING CHANGE:
The `.ssh` directory is only created when a user actually declares an `ssh` key, and an empty `ssh.config` list now
removes the client config instead of writing an empty one. Users who relied on the role to pre create an empty `~/.ssh`
have to declare `ssh: {}` explicitly.

Co-authored-by: Copilot <copilot@github.com>
2026-09-09 21:39:42 +02:00
volker.raschekandCopilot 91ba7845da fix(tasks): chown the btrfs home directory only after the user exists
The btrfs block chowned the freshly created subvolume to the user right after `btrfs_subvolume`, but the four
`ansible.builtin.user` tasks that actually create that user run further below. On a first run the chown therefore
aborted with an invalid user error, which made the whole btrfs code path unusable.

The task is removed rather than reordered, because an identical chown already exists in the "Adapt permissions and copy
skel" block below the user creation. That block also runs the skel copy first, so the file ownership set there is not
overwritten afterwards. The subvolume simply stays root owned for a few tasks longer, which a comment now records.

Co-authored-by: Copilot <copilot@github.com>
2026-09-09 21:39:42 +02:00
volker.raschekandCopilot 242f291200 fix(templates): render the documented function value in shell rc files
The template read `function.body`, while README and defaults/main.yaml document the key as `functions[].value`. Every
documented example therefore rendered an empty function body, and with a strict undefined policy the template failed
outright.

The documentation is the contract for this role, so the template follows it instead of the other way around.

Co-authored-by: Copilot <copilot@github.com>
2026-09-09 21:39:42 +02:00
volker.raschekandCopilot 68be6c5cdf fix(tasks): repair the never executed skel file copy
The task guarded itself with `_skel_file.stat.exist`, but the stat module returns `exists`. The `is defined` check on the
misspelled attribute therefore always evaluated to false and the whole task was silently skipped, so no skel file was
ever copied into a home directory.

Two further defects surfaced once the guard was corrected. The source path interpolated the registered result dict
`_skel_file` instead of the loop variable `skel_file`, and the copy module read from the control node because
`remote_src` was missing, so `/etc/skel` of the managed host was never consulted.

The stat now probes the source instead of the destination. Distributions ship different skel files, for example Debian
has no `.bash_profile`, and copying a non existing remote source would abort the run. The "only copy when absent"
behaviour is delegated to `force: false`, which the copy module implements natively. The register variable is prefixed
with the role name so it can no longer be confused with the loop variable. The mode is corrected to 0644, the mode
`/etc/skel` uses for its dotfiles.

Co-authored-by: Copilot <copilot@github.com>
2026-09-09 21:39:42 +02:00
CSRBot 2a32849bd0 Merge pull request 'chore(deps): update ansible/ansible-lint action to v26.8.0' (#61) from renovate/ansible-ansible-lint-26.x into master
Ansible Linter / ansible-lint (push) Successful in 37s
Lint Markdown files / markdown-lint (push) Successful in 7s
2026-08-12 09:15:59 +00:00
CSRBot 3c0b5b7386 chore(deps): update ansible/ansible-lint action to v26.8.0
Ansible Linter / ansible-lint (push) Successful in 48s
Lint Markdown files / markdown-lint (pull_request) Successful in 6s
Lint Markdown files / markdown-lint (push) Successful in 6s
Ansible Linter / ansible-lint (pull_request) Successful in 1m32s
2026-08-12 09:02:58 +00:00
CSRBot 9410b70830 Merge pull request 'chore(deps): update actions/checkout action to v6.1.0' (#60) from renovate/actions-checkout-6.x into master
Ansible Linter / ansible-lint (push) Successful in 34s
Lint Markdown files / markdown-lint (push) Successful in 6s
2026-07-20 18:23:32 +00:00
CSRBot 780e6df57f chore(deps): update actions/checkout action to v6.1.0
Ansible Linter / ansible-lint (push) Successful in 46s
Lint Markdown files / markdown-lint (push) Successful in 7s
Lint Markdown files / markdown-lint (pull_request) Successful in 6s
Ansible Linter / ansible-lint (pull_request) Successful in 1m44s
2026-07-20 18:03:07 +00:00
CSRBot da18299804 Merge pull request 'chore(deps): pin actions/checkout action to df4cb1c' (#58) from renovate/pin-dependencies into master
Ansible Linter / ansible-lint (push) Successful in 26s
Lint Markdown files / markdown-lint (push) Successful in 5s
2026-07-03 00:02:44 +00:00
CSRBot 9c1bfdba3b chore(deps): pin actions/checkout action to df4cb1c
Lint Markdown files / markdown-lint (push) Successful in 5s
Ansible Linter / ansible-lint (push) Successful in 30s
Ansible Linter / ansible-lint (pull_request) Successful in 27s
Lint Markdown files / markdown-lint (pull_request) Successful in 5s
2026-07-02 21:03:26 +00:00
CSRBot 4f272fa518 Merge pull request 'chore(deps): update ansible/ansible-lint action to v26.6.0' (#59) from renovate/ansible-ansible-lint-26.x into master
Ansible Linter / ansible-lint (push) Successful in 36s
Lint Markdown files / markdown-lint (push) Successful in 6s
2026-07-02 18:35:03 +00:00
CSRBot b147d9e827 chore(deps): update ansible/ansible-lint action to v26.6.0
Ansible Linter / ansible-lint (pull_request) Successful in 1m33s
Lint Markdown files / markdown-lint (pull_request) Successful in 12s
Lint Markdown files / markdown-lint (push) Successful in 12s
Ansible Linter / ansible-lint (push) Successful in 37s
2026-07-02 18:02:54 +00:00
CSRBot d59bed93c6 Merge pull request 'chore(deps): update davidanson/markdownlint-cli2-action action to v23' (#57) from renovate/davidanson-markdownlint-cli2-action-23.x into master
Ansible Linter / ansible-lint (push) Successful in 45s
Lint Markdown files / markdown-lint (push) Successful in 6s
2026-06-15 18:15:59 +00:00
CSRBot 142df1da6e chore(deps): update davidanson/markdownlint-cli2-action action to v23
Ansible Linter / ansible-lint (push) Successful in 1m15s
Lint Markdown files / markdown-lint (pull_request) Successful in 7s
Lint Markdown files / markdown-lint (push) Successful in 5s
Ansible Linter / ansible-lint (pull_request) Successful in 2m4s
2026-06-15 18:02:35 +00:00
CSRBot c7a8e3c5ab Merge pull request 'chore(deps): update actions/checkout action to v6.0.3' (#52) from renovate/actions into master
Ansible Linter / ansible-lint (push) Failing after 9s
Lint Markdown files / markdown-lint (push) Successful in 5s
2026-06-02 15:15:41 +00:00
CSRBot 3afee5bce7 chore(deps): update actions/checkout action to v6.0.3
Lint Markdown files / markdown-lint (pull_request) Successful in 5s
Ansible Linter / ansible-lint (pull_request) Successful in 1m5s
Ansible Linter / ansible-lint (push) Successful in 28s
Lint Markdown files / markdown-lint (push) Successful in 10s
2026-06-02 15:02:46 +00:00
CSRBot 73adc91eb3 Merge pull request 'chore(deps): update ansible/ansible-lint action to v26.4.0' (#51) from renovate/actions into master
Ansible Linter / ansible-lint (push) Failing after 8s
Lint Markdown files / markdown-lint (push) Successful in 5s
2026-04-14 11:08:08 +00:00
CSRBot cc6c1b1189 chore(deps): update ansible/ansible-lint action to v26.4.0
Ansible Linter / ansible-lint (pull_request) Successful in 24s
Ansible Linter / ansible-lint (push) Successful in 29s
Lint Markdown files / markdown-lint (pull_request) Successful in 18s
Lint Markdown files / markdown-lint (push) Successful in 5s
2026-04-14 10:48:25 +00:00
CSRBot 151bb67e3d Merge pull request 'chore(deps): update ansible/ansible-lint action to v26.3.0' (#50) from renovate/actions into master
Ansible Linter / ansible-lint (push) Successful in 1m0s
Lint Markdown files / markdown-lint (push) Successful in 10s
2026-03-05 14:07:07 +00:00
CSRBot b1e7bc5993 chore(deps): update ansible/ansible-lint action to v26.3.0
Lint Markdown files / markdown-lint (push) Successful in 6s
Ansible Linter / ansible-lint (pull_request) Successful in 20s
Lint Markdown files / markdown-lint (pull_request) Successful in 5s
Ansible Linter / ansible-lint (push) Successful in 59s
2026-03-05 14:01:55 +00:00
CSRBot f34d963618 Merge pull request 'chore(deps): update ansible/ansible-lint action to v26.2.0' (#49) from renovate/actions into master
Lint Markdown files / markdown-lint (push) Successful in 4s
Ansible Linter / ansible-lint (push) Successful in 24s
2026-02-25 17:25:02 +00:00
CSRBot 14325d5f4a chore(deps): update ansible/ansible-lint action to v26.2.0
Ansible Linter / ansible-lint (push) Successful in 20s
Lint Markdown files / markdown-lint (push) Successful in 5s
Lint Markdown files / markdown-lint (pull_request) Successful in 6s
Ansible Linter / ansible-lint (pull_request) Successful in 59s
2026-02-25 17:18:52 +00:00
CSRBot f322c8078a Merge pull request 'chore(deps): update actions/checkout action to v6.0.2' (#48) from renovate/actions into master
Ansible Linter / ansible-lint (push) Successful in 18s
Lint Markdown files / markdown-lint (push) Successful in 5s
2026-01-22 17:10:08 +00:00
CSRBot a2bf5cac96 chore(deps): update actions/checkout action to v6.0.2
Lint Markdown files / markdown-lint (push) Successful in 5s
Ansible Linter / ansible-lint (pull_request) Successful in 19s
Lint Markdown files / markdown-lint (pull_request) Successful in 5s
Ansible Linter / ansible-lint (push) Successful in 59s
2026-01-22 17:01:44 +00:00
CSRBot 2a514a3588 Merge pull request 'chore(deps): update ansible/ansible-lint action to v26.1.1' (#47) from renovate/actions into master
Lint Markdown files / markdown-lint (push) Successful in 9s
Ansible Linter / ansible-lint (push) Successful in 18s
2026-01-16 05:06:26 +00:00
CSRBot c64e2b85b2 chore(deps): update ansible/ansible-lint action to v26.1.1
Ansible Linter / ansible-lint (push) Successful in 17s
Lint Markdown files / markdown-lint (pull_request) Successful in 5s
Lint Markdown files / markdown-lint (push) Successful in 4s
Ansible Linter / ansible-lint (pull_request) Successful in 57s
2026-01-16 05:01:25 +00:00
CSRBot b14f3ee512 chore(deps): update ansible/ansible-lint action to v26
Lint Markdown files / markdown-lint (pull_request) Successful in 4s
Ansible Linter / ansible-lint (pull_request) Successful in 58s
Ansible Linter / ansible-lint (push) Successful in 20s
Lint Markdown files / markdown-lint (push) Successful in 5s
2026-01-11 23:01:16 +00:00
volker.raschek a4b28d441b fix(ci): add workflow dispatch for releases
Ansible Linter / ansible-lint (push) Successful in 18s
Lint Markdown files / markdown-lint (push) Successful in 5s
2026-01-11 21:16:12 +01:00
volker.raschek 1ec88f37fb fix(ci): use dynamic github repository name
Ansible Linter / ansible-lint (push) Successful in 19s
Lint Markdown files / markdown-lint (push) Successful in 4s
Release Ansible Role / Release Ansible Role (push) Successful in 56s
2026-01-07 16:09:07 +01:00
volker.raschek 09c4173eaf docs(ci): get steps a dedicated name
Ansible Linter / ansible-lint (push) Successful in 19s
Lint Markdown files / markdown-lint (push) Successful in 4s
2026-01-07 16:02:12 +01:00
volker.raschek 14f856fdea fix(ci): add release workflow
Ansible Linter / ansible-lint (push) Successful in 20s
Lint Markdown files / markdown-lint (push) Successful in 4s
Release to Ansible Galaxy / release (push) Successful in 59s
2026-01-07 16:00:01 +01:00
volker.raschek 86d85c431c fix!: rename environments to envs
Ansible Linter / ansible-lint (push) Successful in 19s
Lint Markdown files / markdown-lint (push) Successful in 4s
2026-01-07 11:16:12 +01:00
volker.raschek 88e9a163e1 docs: support environment variables in authorized_keys file
Ansible Linter / ansible-lint (push) Successful in 18s
Lint Markdown files / markdown-lint (push) Successful in 5s
2026-01-07 10:35:40 +01:00
volker.raschek 69491c9aa0 feat: support environment variables in authorized_keys file
Ansible Linter / ansible-lint (push) Successful in 21s
Lint Markdown files / markdown-lint (push) Successful in 5s
2026-01-07 10:28:13 +01:00
volker.raschek 47d9a58910 fix: replace deprecated INJECT_FACTS_AS_VARS
Ansible Linter / ansible-lint (push) Successful in 17s
Lint Markdown files / markdown-lint (push) Successful in 4s
2026-01-05 10:28:33 +01:00
CSRBot e176bb0bee Merge pull request 'chore(deps): update ansible/ansible-lint action to v25.12.2' (#45) from renovate/actions into master
Ansible Linter / ansible-lint (push) Successful in 18s
Lint Markdown files / markdown-lint (push) Successful in 5s
2025-12-22 20:06:24 +00:00
CSRBot 9acd6de876 chore(deps): update ansible/ansible-lint action to v25.12.2
Ansible Linter / ansible-lint (push) Successful in 18s
Lint Markdown files / markdown-lint (push) Successful in 4s
Lint Markdown files / markdown-lint (pull_request) Successful in 5s
Ansible Linter / ansible-lint (pull_request) Successful in 56s
2025-12-22 20:01:29 +00:00
CSRBot c0566e2416 Merge pull request 'chore(deps): update ansible/ansible-lint action to v25.12.1' (#44) from renovate/actions into master
Ansible Linter / ansible-lint (push) Successful in 17s
Lint Markdown files / markdown-lint (push) Successful in 4s
2025-12-10 14:06:34 +00:00
CSRBot 509882a193 chore(deps): update ansible/ansible-lint action to v25.12.1
Ansible Linter / ansible-lint (push) Successful in 17s
Lint Markdown files / markdown-lint (push) Successful in 4s
Ansible Linter / ansible-lint (pull_request) Successful in 17s
Lint Markdown files / markdown-lint (pull_request) Successful in 4s
2025-12-10 14:01:33 +00:00
CSRBot e98925af4b Merge pull request 'chore(deps): update actions/checkout action to v6' (#43) from renovate/actions-checkout-6.x into master
Ansible Linter / ansible-lint (push) Successful in 18s
Lint Markdown files / markdown-lint (push) Successful in 4s
2025-12-02 21:09:51 +00:00
CSRBot 8a25dac377 chore(deps): update actions/checkout action to v6
Ansible Linter / ansible-lint (push) Successful in 18s
Lint Markdown files / markdown-lint (pull_request) Successful in 4s
Lint Markdown files / markdown-lint (push) Successful in 4s
Ansible Linter / ansible-lint (pull_request) Successful in 59s
2025-12-02 21:01:40 +00:00
CSRBot 7602cc621c Merge pull request 'chore(deps): update ansible/ansible-lint action to v25.12.0' (#42) from renovate/actions into master
Ansible Linter / ansible-lint (push) Successful in 18s
Lint Markdown files / markdown-lint (push) Successful in 5s
2025-12-02 17:05:49 +00:00
CSRBot b7560320dc chore(deps): update ansible/ansible-lint action to v25.12.0
Ansible Linter / ansible-lint (pull_request) Successful in 17s
Lint Markdown files / markdown-lint (pull_request) Successful in 5s
Lint Markdown files / markdown-lint (push) Successful in 4s
Ansible Linter / ansible-lint (push) Successful in 59s
2025-12-02 17:01:25 +00:00
CSRBot 4241502728 Merge pull request 'chore(deps): update ansible/ansible-lint action to v25.11.1' (#41) from renovate/actions into master
Lint Markdown files / markdown-lint (push) Successful in 11s
Ansible Linter / ansible-lint (push) Successful in 18s
2025-11-24 23:06:01 +00:00
CSRBot f5062ff179 chore(deps): update ansible/ansible-lint action to v25.11.1
Ansible Linter / ansible-lint (push) Successful in 17s
Lint Markdown files / markdown-lint (push) Successful in 5s
Ansible Linter / ansible-lint (pull_request) Successful in 18s
Lint Markdown files / markdown-lint (pull_request) Successful in 5s
2025-11-24 23:01:45 +00:00
volker.raschek e4c12b9856 fix: set btrfs device
Ansible Linter / ansible-lint (push) Successful in 24s
Lint Markdown files / markdown-lint (push) Successful in 4s
2025-11-22 23:42:42 +01:00
volker.raschek 13a4c84978 fix(npm): remove npm dependencies
Ansible Linter / ansible-lint (push) Successful in 20s
Lint Markdown files / markdown-lint (push) Successful in 5s
2025-11-18 22:53:51 +01:00
CSRBot 0a89f30f92 Merge pull request 'chore(deps): update davidanson/markdownlint-cli2-action action to v21' (#39) from renovate/davidanson-markdownlint-cli2-action-21.x into master
Ansible Linter / ansible-lint (push) Successful in 57s
Lint Markdown files / markdown-lint (push) Successful in 9s
2025-11-18 17:02:07 +00:00
CSRBot 2640acca4d chore(deps): update davidanson/markdownlint-cli2-action action to v21
Ansible Linter / ansible-lint (push) Successful in 58s
Lint Markdown files / markdown-lint (push) Successful in 9s
Ansible Linter / ansible-lint (pull_request) Successful in 57s
Lint Markdown files / markdown-lint (pull_request) Successful in 10s
2025-11-17 20:01:33 +00:00
CSRBot 278d723aa1 Merge pull request 'chore(deps): update actions/checkout action to v5.0.1' (#40) from renovate/actions into master
Ansible Linter / ansible-lint (push) Successful in 58s
Lint Markdown files / markdown-lint (push) Successful in 10s
2025-11-17 17:20:38 +00:00
CSRBot bc61cf3fa8 chore(deps): update actions/checkout action to v5.0.1
Ansible Linter / ansible-lint (pull_request) Successful in 57s
Ansible Linter / ansible-lint (push) Successful in 58s
Lint Markdown files / markdown-lint (pull_request) Successful in 10s
Lint Markdown files / markdown-lint (push) Successful in 9s
2025-11-17 17:02:03 +00:00
volker.raschek 015f9aacee fix(netrc): typo maschine
Ansible Linter / ansible-lint (push) Successful in 21s
Lint Markdown files / markdown-lint (push) Successful in 4s
2025-11-16 11:41:52 +01:00
CSRBot b4c70b472e Merge pull request 'chore(deps): update ansible/ansible-lint action to v25.11.0' (#38) from renovate/actions into master
Lint Markdown files / markdown-lint (push) Successful in 4s
Ansible Linter / ansible-lint (push) Successful in 57s
2025-11-10 14:07:29 +00:00
CSRBot fc2b257c98 chore(deps): update ansible/ansible-lint action to v25.11.0
Ansible Linter / ansible-lint (pull_request) Successful in 17s
Lint Markdown files / markdown-lint (pull_request) Successful in 3s
Ansible Linter / ansible-lint (push) Successful in 17s
Lint Markdown files / markdown-lint (push) Successful in 3s
2025-11-10 14:01:50 +00:00
volker.raschek 22048124fd refac: use .yaml instead of .yml extension
Ansible Linter / ansible-lint (push) Successful in 24s
Lint Markdown files / markdown-lint (push) Successful in 4s
2025-10-25 12:38:00 +02:00
CSRBot 0db7c7265a Merge pull request 'chore(deps): update ansible/ansible-lint action to v25.9.2' (#36) from renovate/actions into master
Lint Markdown files / markdown-lint (push) Successful in 4s
Ansible Linter / ansible-lint (push) Successful in 58s
2025-10-08 13:06:21 +00:00
CSRBot 8249283978 chore(deps): update ansible/ansible-lint action to v25.9.2
Lint Markdown files / markdown-lint (push) Successful in 4s
Ansible Linter / ansible-lint (pull_request) Successful in 17s
Lint Markdown files / markdown-lint (pull_request) Successful in 4s
Ansible Linter / ansible-lint (push) Successful in 58s
2025-10-08 13:02:29 +00:00
CSRBot c5c832e0b3 Merge pull request 'chore(deps): update ansible/ansible-lint action to v25.9.1' (#35) from renovate/actions into master
Ansible Linter / ansible-lint (push) Successful in 16s
Lint Markdown files / markdown-lint (push) Successful in 4s
2025-10-01 13:07:09 +00:00
CSRBot b58cd93dff chore(deps): update ansible/ansible-lint action to v25.9.1
Lint Markdown files / markdown-lint (push) Successful in 8s
Ansible Linter / ansible-lint (push) Successful in 18s
Lint Markdown files / markdown-lint (pull_request) Successful in 5s
Ansible Linter / ansible-lint (pull_request) Successful in 56s
2025-10-01 13:03:00 +00:00
CSRBot 75b5e3fb32 chore(deps): update dependency @vscode/vsce to v3.6.2
Ansible Linter / ansible-lint (pull_request) Successful in 25s
Lint Markdown files / markdown-lint (pull_request) Successful in 3s
Lint Markdown files / markdown-lint (push) Successful in 9s
Ansible Linter / ansible-lint (push) Successful in 20s
2025-09-26 16:01:30 +00:00
CSRBot 42478da122 Merge pull request 'chore(deps): update ansible/ansible-lint action to v25.9.0' (#32) from renovate/actions into master
Ansible Linter / ansible-lint (push) Successful in 19s
Lint Markdown files / markdown-lint (push) Successful in 4s
2025-09-19 19:57:48 +00:00
CSRBot a4ee09ad9d Merge pull request 'chore(deps): update actions/checkout action to v5' (#33) from renovate/actions-checkout-5.x into master
Ansible Linter / ansible-lint (push) Successful in 16s
Lint Markdown files / markdown-lint (push) Successful in 4s
2025-09-19 19:39:50 +00:00
CSRBot d72731c835 chore(deps): update actions/checkout action to v5
Lint Markdown files / markdown-lint (push) Successful in 15s
Ansible Linter / ansible-lint (pull_request) Successful in 18s
Lint Markdown files / markdown-lint (pull_request) Successful in 4s
Ansible Linter / ansible-lint (push) Successful in 2m53s
2025-09-18 22:03:50 +00:00
CSRBot a938dab1ec chore(deps): update ansible/ansible-lint action to v25.9.0
Ansible Linter / ansible-lint (pull_request) Successful in 3m56s
Lint Markdown files / markdown-lint (push) Successful in 11s
Ansible Linter / ansible-lint (push) Successful in 1m7s
Lint Markdown files / markdown-lint (pull_request) Successful in 4s
2025-09-18 22:03:44 +00:00
volker.raschek 042c91f42e chore(deps): update actions/checkout to v5.0.0
Lint Markdown files / markdown-lint (push) Successful in 4s
Ansible Linter / ansible-lint (push) Successful in 17s
2025-09-18 22:33:29 +02:00
CSRBot f49f61bbc4 Merge pull request 'chore(deps): update ansible/ansible-lint action to v25.8.2' (#31) from renovate/actions into master
Ansible Linter / ansible-lint (push) Successful in 15s
Lint Markdown files / markdown-lint (push) Successful in 15s
2025-08-21 16:06:06 +00:00
CSRBot 551eed98d5 chore(deps): update ansible/ansible-lint action to v25.8.2
Lint Markdown files / markdown-lint (push) Successful in 4s
Ansible Linter / ansible-lint (pull_request) Successful in 15s
Lint Markdown files / markdown-lint (pull_request) Successful in 15s
Ansible Linter / ansible-lint (push) Successful in 57s
2025-08-21 16:02:41 +00:00
CSRBot e172e4990f Merge pull request 'chore(deps): update ansible/ansible-lint action to v25.8.1' (#30) from renovate/actions into master
Ansible Linter / ansible-lint (push) Successful in 15s
Lint Markdown files / markdown-lint (push) Successful in 21s
2025-08-14 01:05:25 +00:00
CSRBot 0e8f256a26 chore(deps): update ansible/ansible-lint action to v25.8.1
Ansible Linter / ansible-lint (push) Successful in 15s
Lint Markdown files / markdown-lint (push) Successful in 4s
Ansible Linter / ansible-lint (pull_request) Successful in 15s
Lint Markdown files / markdown-lint (pull_request) Successful in 8s
2025-08-14 01:02:26 +00:00
CSRBot e8d9c85aa5 Merge pull request 'chore(deps): update ansible/ansible-lint action to v25.8.0' (#29) from renovate/actions into master
Lint Markdown files / markdown-lint (push) Successful in 10s
Ansible Linter / ansible-lint (push) Successful in 15s
2025-08-13 16:05:18 +00:00
CSRBot 1ac0da5661 chore(deps): update ansible/ansible-lint action to v25.8.0
Ansible Linter / ansible-lint (push) Successful in 14s
Ansible Linter / ansible-lint (pull_request) Successful in 15s
Lint Markdown files / markdown-lint (pull_request) Successful in 10s
Lint Markdown files / markdown-lint (push) Successful in 20s
2025-08-13 16:02:19 +00:00
CSRBot 345c739489 Merge pull request 'chore(deps): update actions/checkout action to v4.3.0' (#27) from renovate/actions into master
Ansible Linter / ansible-lint (push) Successful in 16s
Lint Markdown files / markdown-lint (push) Successful in 4s
2025-08-11 13:24:03 +00:00
CSRBot e05d5d9574 chore(deps): update actions/checkout action to v4.3.0
Ansible Linter / ansible-lint (push) Successful in 55s
Lint Markdown files / markdown-lint (push) Successful in 10s
Ansible Linter / ansible-lint (pull_request) Successful in 56s
Lint Markdown files / markdown-lint (pull_request) Successful in 10s
2025-08-11 13:03:56 +00:00
volker.raschek 7a757ee119 fix(ansible-galaxy): adapt indentation
Ansible Linter / ansible-lint (push) Successful in 38s
Lint Markdown files / markdown-lint (push) Successful in 12s
2025-08-06 16:10:36 +02:00
volker.raschek c552f95227 fix(default): rename file
Lint Markdown files / markdown-lint (push) Successful in 10s
Ansible Linter / ansible-lint (push) Failing after 41s
2025-08-06 15:24:39 +02:00
volker.raschek e14ab7fc70 fix(ansible-galaxy): add namespace
Ansible Linter / ansible-lint (push) Has been cancelled
Lint Markdown files / markdown-lint (push) Has been cancelled
2025-08-06 15:22:46 +02:00
volker.raschek d6666179a4 fix(ansible-galaxy): adapt list of supported platforms
Lint Markdown files / markdown-lint (push) Successful in 13s
Ansible Linter / ansible-lint (push) Failing after 46s
2025-08-06 11:45:07 +02:00
volker.raschek 13ada129ae fix(ansible-galaxy): remove namespace
Lint Markdown files / markdown-lint (push) Successful in 9s
Ansible Linter / ansible-lint (push) Failing after 29s
2025-08-06 11:39:34 +02:00
31 changed files with 660 additions and 152 deletions
+4 -2
View File
@@ -12,9 +12,11 @@ jobs:
runs-on:
- ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
- name: Run ansible-lint
uses: ansible/ansible-lint@v25.7.0
uses: ansible/ansible-lint@665d9e07a1943254d2910faffc106adaf7ea7294 # v26.8.0
with:
args: "--config-file .ansible-lint"
# The molecule scenario is linted as well, so its collections are required beside the ones of the role.
requirements_file: "molecule/default/collections.yml"
setup_python: "true"
+2 -2
View File
@@ -12,7 +12,7 @@ jobs:
runs-on:
- ubuntu-latest
steps:
- uses: actions/checkout@v4.2.2
- uses: DavidAnson/markdownlint-cli2-action@v20.0.0
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
- uses: DavidAnson/markdownlint-cli2-action@ded1f9488f68a970bc66ea5619e13e9b52e601cd # v23.2.0
with:
globs: '**/*.md'
+29
View File
@@ -0,0 +1,29 @@
name: Molecule
on:
pull_request:
types: [ "opened", "reopened", "synchronize" ]
push:
branches: [ '**' ]
tags-ignore: [ '**' ]
permissions:
contents: read
jobs:
molecule:
name: Molecule
runs-on: ubuntu-latest-amd64
steps:
# The scenario includes the role by its directory name, whose parent is used as roles path.
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
with:
path: unix-users
- name: Install molecule
run: |
apt update --yes
apt install --yes python3-pip
pip3 install --break-system-packages molecule docker
- name: Run molecule
run: molecule test
working-directory: unix-users
+22
View File
@@ -0,0 +1,22 @@
name: Release Ansible Role
on:
push:
tags:
- '**'
workflow_dispatch: {}
jobs:
release:
name: Release Ansible Role
runs-on: ubuntu-latest
steps:
- name: Install Ansible Galaxy
run: |
apt update --yes
apt install --yes ansible
- env:
ANSIBLE_GALAXY_TOKEN: ${{ secrets.ANSIBLE_GALAXY_TOKEN }}
name: Update Ansible Role in Ansible Galaxy
run: |
ansible-galaxy role import --token=${ANSIBLE_GALAXY_TOKEN} volker-raschek ${GITHUB_REPOSITORY#*/}
+2 -1
View File
@@ -1 +1,2 @@
.ansible
.ansible
molecule/default/files/ssh
+14
View File
@@ -0,0 +1,14 @@
{
"ansible.python.interpreterPath": "/bin/python",
"files.associations": {
"**/.gitea/**/*.yml": "yaml",
"**/.gitea/**/*.yaml": "yaml",
"docker-compose*.yml": "dockercompose",
"*.yml": "ansible",
"*.yaml": "ansible",
".yamllint": "yaml",
".yamllint.yml": "yaml",
".yamllint.yaml": "yaml"
},
"rewrap.wrappingColumn": 120
}
+63 -1
View File
@@ -6,6 +6,57 @@ The ansible role `volker-raschek.unix-users` create and manage users on Linux ba
Linux, Fedora and Ubuntu. Furthermore, the role can also be used to create groups, `~/.forward`, `~/.netrc` and to
manage the `~/.ssh` directory.
## Requirements
The role requires `ansible-core` 2.11 or newer. A home directory can optionally be created as btrfs subvolume, which
relies on the `btrfs_subvolume` module of the collection `community.general`.
```bash
ansible-galaxy collection install -r requirements.yml
```
The role manages users, groups and their home directories, so it has to be executed with `become: true`.
## Tests
The role is tested with [Molecule](https://ansible.readthedocs.io/projects/molecule/). The scenario starts one docker
container per supported distribution family, applies the role, asserts that a second run reports no change and finally
verifies the created users and groups, the permissions and the content of the managed files and that a user declared
as `absent` is gone again. A btrfs home is not covered, because a container has no btrfs filesystem to create a
subvolume on.
The ssh key pair the scenario feeds into the role is generated during `molecule create` and removed again during
`molecule destroy`, so no private key is kept in the repository.
Molecule ships only its `default` driver, therefore `docker` is required besides molecule itself. The collections are
declared in `molecule/default/collections.yml` and installed by molecule.
```bash
pip install molecule docker
```
The complete sequence creates the containers, tests them and removes them afterwards.
```bash
molecule test
```
While working on the role the containers are better kept alive.
```bash
# create the containers and apply the role
molecule converge
# run the assertions of molecule/default/verify.yml against the running containers
molecule verify
# open a shell in one of the containers
molecule login --host unix-users-debian
# remove the containers
molecule destroy
```
## Examples
### User and group
@@ -34,6 +85,10 @@ unix_users:
Optionally, the home directory of a user can also be created as dedicated btrfs subvolume. This make it possible to
create snapshots of the home directory, for example via `btrbk`.
> [!WARNING]
> Removing a user with `state: absent` also deletes the btrfs subvolume of the home directory. Snapshots taken from that
> subvolume are not removed and keep the data available.
```yaml
unix_users:
toor:
@@ -77,7 +132,11 @@ The SSH client directory `~/.ssh` can also be managed via the Ansible role. This
`~/.ssh/config`, `~/.ssh/authorized_keys` as well as the maintenance of private and public SSH keys.
The following example create two entries in `~/.ssh/authorized_keys`. One normal SSH access for `claire`. If `bob`
establish a SSH connection the command `/usr/local/bin/upload-file.sh` will be executed and exited.
establish a SSH connection the command `/usr/local/bin/upload-file.sh` will be executed and exited. Furthermore,
environment variables can be espcilitly defined, to consume it during execution of the command.
> [!IMPORTANT]
> To allow consuming environment variables must be set `PermitUserEnvironment yes` in `/etc/ssh/sshd_config`.
The private key `toor@toor-pc.ed25519.key` must be stored in `ssh/private_keys`. The public key will be automatically
extracted from the private key.
@@ -99,6 +158,9 @@ unix_users:
authorized_keys:
- filename: claire@claire-pc.pub
- command: /usr/local/bin/upload-file.sh
envs:
- key: SSH_KEY_NAME
value: bob@bob-pc
filename: bob@bob-pc.pub
private_keys:
- toor@toor-pc.ed25519.key
+19 -17
View File
@@ -1,25 +1,27 @@
dependencies: []
galaxy_info:
namespace: volker_raschek
role_name: "unix_users"
author: "Markus Pesch"
description: "Role to create and configure unix users and groups"
company: "Cryptic Systems"
license: "MIT"
min_ansible_version: "2.9"
platforms:
- name: ArchLinux
versions:
- all
- name: Ubuntu
versions:
- all
- name: Fedora
versions:
- "35"
description: "Role to create and configure unix users and groups"
galaxy_tags:
- system
- user
- unix
- linux
dependencies: []
license: "MIT"
min_ansible_version: "2.11"
namespace: volker-raschek
platforms:
- name: ArchLinux
versions:
- all
- name: EL
versions:
- all
- name: Fedora
versions:
- all
- name: Ubuntu
versions:
- all
role_name: "unix_users"
+5
View File
@@ -0,0 +1,5 @@
---
collections:
- name: community.docker
- name: community.general
+64
View File
@@ -0,0 +1,64 @@
---
# A btrfs home is not covered, a container has no btrfs filesystem to create a subvolume on.
- name: Converge
hosts: all
vars:
unix_groups:
molecule-alice:
# An unquoted gid is an integer, the role has to cope with that.
gid: 4242
state: present
molecule-bob:
state: present
molecule-obsolete:
state: absent
unix_users:
molecule-alice:
state: present
name: Alice
uid: 4242
group: molecule-alice
home: /home/molecule-alice
shell: /bin/bash
password: alice
email: alice@example.local
ssh:
config:
- Host: "*"
StrictHostKeyChecking: "no"
authorized_keys:
- filename: molecule.pub
command: "/usr/bin/true"
envs:
- key: EDITOR
value: vi
private_keys:
- molecule.ed25519.key
netrc:
- machine: hostname.local
login: alice
password: secret
shell_rc_files:
- file: molecule.bashrc
aliases:
- key: dcd
value: docker compose down
envs:
- export: true
key: PATH
value: "${HOME}/bin:${PATH}"
functions:
- name: foo
value: "echo \"bar\""
# Bob declares nothing optional, so none of the optional files may show up in his home.
molecule-bob:
state: present
group: molecule-bob
molecule-dave:
state: absent
tasks:
# The role is included by the name of its directory, which molecule put on the roles path.
- name: Include the role unix-users
ansible.builtin.include_role:
name: unix-users
+49
View File
@@ -0,0 +1,49 @@
---
- name: Create
hosts: localhost
gather_facts: false
vars:
_private_key: "{{ molecule_scenario_directory }}/files/ssh/private_keys/molecule.ed25519.key"
_authorized_key: "{{ molecule_scenario_directory }}/files/ssh/authorized_keys/molecule.pub"
tasks:
- name: Start a container per platform
community.docker.docker_container:
name: "{{ item.name }}"
image: "{{ item.image }}"
command: "sleep infinity"
state: started
loop: "{{ molecule_yml.platforms }}"
loop_control:
label: "{{ item.name }}"
- name: Write the instance config
ansible.builtin.copy:
content: |
{% for platform in molecule_yml.platforms %}
- instance: {{ platform.name }}
connection: community.docker.docker
{% endfor %}
dest: "{{ molecule_instance_config }}"
mode: "0600"
- name: Create the fixture directories
ansible.builtin.file:
path: "{{ item | dirname }}"
state: directory
mode: "0700"
loop:
- "{{ _private_key }}"
- "{{ _authorized_key }}"
# The key pair is generated instead of committed, private key material does not belong into a repository.
- name: Generate the ssh key pair the role is fed with
ansible.builtin.command:
cmd: "ssh-keygen -t ed25519 -N '' -C molecule -f {{ _private_key }}"
creates: "{{ _private_key }}"
- name: Offer the public key as authorized key fixture
ansible.builtin.copy:
src: "{{ _private_key }}.pub"
dest: "{{ _authorized_key }}"
mode: "0644"
+25
View File
@@ -0,0 +1,25 @@
---
- name: Destroy
hosts: localhost
gather_facts: false
tasks:
- name: Remove the container of every platform
community.docker.docker_container:
name: "{{ item.name }}"
state: absent
loop: "{{ molecule_yml.platforms }}"
loop_control:
label: "{{ item.name }}"
# The generated key pair is not kept around, a stale private key in the working tree is asking for trouble.
- name: Remove the ssh fixtures
ansible.builtin.file:
path: "{{ molecule_scenario_directory }}/files/ssh"
state: absent
- name: Empty the instance config
ansible.builtin.copy:
content: "[]"
dest: "{{ molecule_instance_config }}"
mode: "0600"
+24
View File
@@ -0,0 +1,24 @@
---
driver:
name: default
options:
managed: true
login_cmd_template: "docker exec --interactive --tty {instance} bash"
platforms:
- name: unix-users-archlinux
image: docker.io/library/archlinux:base
- name: unix-users-debian
image: docker.io/library/debian:13
- name: unix-users-fedora
image: registry.fedoraproject.org/fedora:43
provisioner:
name: ansible
# The role under test is the project directory itself, so its parent has to be on the roles path.
env:
ANSIBLE_ROLES_PATH: "${MOLECULE_PROJECT_DIRECTORY}/.."
config_options:
defaults:
interpreter_python: auto_silent
+37
View File
@@ -0,0 +1,37 @@
---
- name: Prepare
hosts: all
gather_facts: false
vars:
# The base images ship neither a python interpreter for ansible nor ssh-keygen, which the role shells out to.
_bootstrap: |
set -eu
if command -v pacman > /dev/null; then
pacman --sync --refresh --noconfirm openssh python shadow
elif command -v apt-get > /dev/null; then
apt-get update
apt-get install --yes openssh-client passwd python3
else
dnf install --assumeyes openssh-clients python3 shadow-utils
fi
tasks:
# The raw command is wrapped explicitly, because the bootstrap relies on shell builtins.
- name: Bootstrap the python interpreter and the tools required by the role
ansible.builtin.raw: "/bin/sh -c {{ _bootstrap | quote }}"
changed_when: true
# The removal paths of the role can only be observed on objects that exist before the role runs.
- name: Seed the objects the converge removes again
hosts: all
tasks:
- name: Create the group that the converge removes
ansible.builtin.group:
name: molecule-obsolete
state: present
- name: Create the user that the converge removes
ansible.builtin.user:
name: molecule-dave
group: users
state: present
+4
View File
@@ -0,0 +1,4 @@
---
# The role has no role dependencies, but molecule warns about the missing file.
roles: []
+143
View File
@@ -0,0 +1,143 @@
---
- name: Verify
hosts: all
vars:
_alice_home: /home/molecule-alice
_bob_home: /home/molecule-bob
_expected_modes:
/home/molecule-alice/.ssh: "0700"
/home/molecule-alice/.ssh/authorized_keys: "0600"
/home/molecule-alice/.ssh/config: "0644"
/home/molecule-alice/.ssh/molecule.ed25519.key: "0600"
/home/molecule-alice/.ssh/molecule.ed25519.key.pub: "0644"
/home/molecule-alice/.forward: "0644"
/home/molecule-alice/.netrc: "0600"
/home/molecule-alice/.bashrc.d: "0755"
/home/molecule-alice/.bashrc.d/molecule.bashrc: "0644"
/home/molecule-alice/.cache: "0755"
/home/molecule-alice/.config: "0755"
/home/molecule-alice/.local/share: "0755"
/home/molecule-alice/.local/state: "0755"
# Only files the role writes are listed, .bashrc originates from the lineinfile task.
_expected_contents:
/home/molecule-alice/.forward: "alice@example.local"
/home/molecule-alice/.netrc: "machine hostname.local login alice password secret"
/home/molecule-alice/.ssh/config: " StrictHostKeyChecking no"
/home/molecule-alice/.bashrc.d/molecule.bashrc: "alias dcd='docker compose down'"
/home/molecule-alice/.bashrc: "source \"/home/molecule-alice/.bashrc.d/molecule.bashrc\""
tasks:
- name: Stat the files of molecule-alice
ansible.builtin.stat:
path: "{{ item.key }}"
register: _alice_files
loop: "{{ _expected_modes | dict2items }}"
loop_control:
label: "{{ item.key }}"
- name: Assert that the files of molecule-alice exist with the expected mode and owner
ansible.builtin.assert:
that:
- item.stat.exists
- item.stat.mode == item.item.value
- item.stat.pw_name == 'molecule-alice'
- item.stat.gr_name == 'molecule-alice'
fail_msg: >-
{{ item.item.key }} has mode {{ item.stat.mode | default('none') }} and owner
{{ item.stat.pw_name | default('none') }}:{{ item.stat.gr_name | default('none') }}
instead of {{ item.item.value }} and molecule-alice:molecule-alice
loop: "{{ _alice_files.results }}"
loop_control:
label: "{{ item.item.key }}"
- name: Read the files of molecule-alice
ansible.builtin.slurp:
src: "{{ item.key }}"
register: _alice_contents
loop: "{{ _expected_contents | dict2items }}"
loop_control:
label: "{{ item.key }}"
- name: Assert that the files of molecule-alice hold the configured values
ansible.builtin.assert:
that: item.item.value in (item.content | b64decode).splitlines()
fail_msg: "{{ item.item.key }} does not contain the line {{ item.item.value }}"
loop: "{{ _alice_contents.results }}"
loop_control:
label: "{{ item.item.key }}"
- name: Read the authorized_keys and the extracted public key of molecule-alice
ansible.builtin.slurp:
src: "{{ item }}"
register: _alice_keys
loop:
- "{{ _alice_home }}/.ssh/authorized_keys"
- "{{ _alice_home }}/.ssh/molecule.ed25519.key.pub"
- name: Assert that the authorized key carries its options and matches the extracted public key
vars:
_authorized_key: "{{ (_alice_keys.results[0].content | b64decode).splitlines() | select('search', 'ssh-ed25519') | first }}"
_public_key: "{{ _alice_keys.results[1].content | b64decode | trim }}"
ansible.builtin.assert:
that:
- _authorized_key.startswith('command="/usr/bin/true",environment="EDITOR=vi" ')
- _public_key.startswith('ssh-ed25519 ')
- _public_key.split()[1] == _authorized_key.split()[2]
fail_msg: "the authorized key of molecule-alice does not match the key extracted from its private key"
- name: Read the user and group database
ansible.builtin.getent:
database: "{{ item }}"
loop:
- passwd
- group
- shadow
- name: Assert that molecule-alice was created as declared
vars:
_entry: "{{ ansible_facts['getent_passwd']['molecule-alice'] }}"
ansible.builtin.assert:
that:
- _entry[1] == '4242'
- _entry[2] == '4242'
- _entry[3] == 'Alice'
- _entry[4] == _alice_home
- _entry[5] == '/bin/bash'
fail_msg: "molecule-alice was created as {{ _entry }}"
- name: Assert that the integer gid of the group molecule-alice was applied
ansible.builtin.assert:
that: ansible_facts['getent_group']['molecule-alice'][1] == '4242'
fail_msg: "the group molecule-alice has gid {{ ansible_facts['getent_group']['molecule-alice'][1] }}"
# A random salt would produce a new hash on every run, which the idempotence step would report as a change.
- name: Assert that the password hash is derived from a deterministic salt
vars:
_expected_hash: "{{ 'alice' | password_hash('sha512', 'molecule-alice' | hash('sha512') | truncate(16, true, '')) }}"
ansible.builtin.assert:
that: ansible_facts['getent_shadow']['molecule-alice'][0] == _expected_hash
fail_msg: "the password hash of molecule-alice is not reproducible and therefore changes on every run"
- name: Assert that molecule-dave and the group molecule-obsolete were removed
ansible.builtin.assert:
that:
- "'molecule-dave' not in ansible_facts['getent_passwd']"
- "'molecule-obsolete' not in ansible_facts['getent_group']"
fail_msg: "the removal of molecule-dave or of the group molecule-obsolete did not happen"
- name: Stat the home of molecule-bob and the files he did not ask for
ansible.builtin.stat:
path: "{{ item }}"
register: _bob_files
loop:
- "{{ _bob_home }}"
- "{{ _bob_home }}/.ssh"
- "{{ _bob_home }}/.forward"
- "{{ _bob_home }}/.netrc"
- name: Assert that molecule-bob got a home but none of the optional files
ansible.builtin.assert:
that:
- _bob_files.results[0].stat.exists
- not _bob_files.results[1:] | map(attribute='stat.exists') | select | list
fail_msg: "molecule-bob has files that were never declared for him"
+6
View File
@@ -0,0 +1,6 @@
---
collections:
# The btrfs_subvolume module has been added in community.general 6.6.0.
- name: community.general
version: ">=6.6.0"
+18
View File
@@ -0,0 +1,18 @@
---
- name: "Check if the skel file exists: /etc/skel/{{ skel_file }}"
ansible.builtin.stat:
path: "/etc/skel/{{ skel_file }}"
register: _unix_users_skel_file
# force: false keeps an already customized dotfile in the home directory untouched.
- name: "Copy skel file: {{ skel_file }}"
ansible.builtin.copy:
src: "/etc/skel/{{ skel_file }}"
dest: "{{ _unix_users_home }}/{{ skel_file }}"
remote_src: true
force: false
owner: "{{ unix_user.key }}"
group: "{{ unix_user.value.group | default('users') }}"
mode: "0644"
when: _unix_users_skel_file.stat.exists
-17
View File
@@ -1,17 +0,0 @@
---
- name: "Exist file {{ skel_file }}"
ansible.builtin.stat:
path: "{{ user_user_home }}/{{ skel_file }}"
register: _skel_file
- name: Copy skel file
when: _skel_file.stat is defined and
_skel_file.stat.exist is defined and
not _skel_file.stat.exist
ansible.builtin.copy:
src: "/etc/skel/{{ _skel_file }}"
dest: "{{ user_user_home }}/{{ skel_file }}"
owner: "{{ unix_user.key }}"
group: "{{ unix_user.value.group | default('users') }}"
mode: "0600"
@@ -3,7 +3,7 @@
- name: "Determine shell rc directory"
ansible.builtin.set_fact:
# path_join examples: https://docs.ansible.com/ansible/latest/collections/ansible/builtin/path_join_filter.html#examples
_shell_rc_file: "{{ (user_user_home, '.bashrc.d', shell_rc_file.file) | path_join }}"
_shell_rc_file: "{{ (_unix_users_home, '.bashrc.d', shell_rc_file.file) | path_join }}"
- name: "Create shell rc directory: {{ _shell_rc_file | dirname }}"
ansible.builtin.file:
@@ -23,8 +23,10 @@
- name: "Source shell rc file: {{ _shell_rc_file }}"
ansible.builtin.lineinfile:
path: "{{ user_user_home }}/.bashrc"
path: "{{ _unix_users_home }}/.bashrc"
line: "source \"{{ _shell_rc_file }}\""
create: true
owner: "{{ unix_user.key }}"
group: "{{ unix_user.value.group | default('users') }}"
mode: "0644"
state: "present"
+8
View File
@@ -0,0 +1,8 @@
---
- name: "Create unix group: {{ unix_group.key }}"
ansible.builtin.group:
name: "{{ unix_group.key }}"
# The boolean form of default also omits an empty gid, which means let the system assign one.
gid: "{{ unix_group.value.gid | default(omit, true) }}"
state: "{{ unix_group.value.state | default('present') }}"
-14
View File
@@ -1,14 +0,0 @@
---
- name: "Create unix group with random gid: {{ unix_group.key }}"
ansible.builtin.group:
name: "{{ unix_group.key }}"
state: "{{ unix_group.value.state | default('present') }}"
when: unix_group.value.gid is not defined or unix_group.value.gid is defined and unix_group.value.gid | length <= 0
- name: "Create unix group with pre-defined gid: {{ unix_group.key }}"
ansible.builtin.group:
name: "{{ unix_group.key }}"
gid: "{{ unix_group.value.gid }}"
state: "{{ unix_group.value.state | default('present') }}"
when: unix_group.value.gid is defined and unix_group.value.gid | length > 0
@@ -2,81 +2,54 @@
- name: "Define home directory for unix user: {{ unix_user.key }}"
ansible.builtin.set_fact:
user_user_home: "{{ unix_user.value.home | default('/home/' + unix_user.key) }}"
_unix_users_home: "{{ unix_user.value.home | default('/home/' + unix_user.key) }}"
- name: "Create btrfs volume for unix user: {{ unix_user.key }}"
when: unix_user.value.btrfs is defined and
unix_user.value.btrfs
block:
- name: "Find btrfs device"
ansible.builtin.command:
cmd: /bin/bash -c "findmnt -no SOURCE -T {{ _unix_users_home }} | sed 's/\[.*\]//'"
register: _unix_users_btrfs_device
changed_when: false
- name: "Determine filesystem of device"
ansible.builtin.set_fact:
_unix_users_device_filesystem: "{{ ansible_facts['mounts'] | selectattr('device', 'equalto', _unix_users_btrfs_device.stdout) | map(attribute='fstype') | first }}"
- name: "Fail if device does not have a btrfs file system"
ansible.builtin.fail:
msg: "Determined device {{ _unix_users_btrfs_device.stdout }} does not have a btrfs filesystem"
when: _unix_users_device_filesystem != 'btrfs'
# The subvolume stays root owned until the user exists. It is chowned further below, after the user was created.
- name: "Create btrfs volume for unix user: {{ unix_user.key }}"
community.general.btrfs_subvolume:
name: "{{ user_user_home }}"
- name: "Adapt home dir permissions"
ansible.builtin.file:
path: "{{ user_user_home }}"
owner: "{{ unix_user.key }}"
group: "{{ unix_user.value.group | default('users') }}"
state: directory
mode: "0755"
filesystem_device: "{{ _unix_users_btrfs_device.stdout }}"
name: "{{ _unix_users_home }}"
state: present
- name: "Create unix user without additional groups and uid: {{ unix_user.key }}"
- name: "Create unix user: {{ unix_user.key }}"
ansible.builtin.user:
name: "{{ unix_user.key }}"
uid: "{{ unix_user.value.uid | default(omit) }}"
group: "{{ unix_user.value.group | default('users') }}"
comment: "{{ unix_user.value.name }}"
groups: "{{ unix_user.value.groups | default(omit) }}"
comment: "{{ unix_user.value.name | default(omit) }}"
create_home: "{{ unix_user.value.create_home | default(true) }}"
home: "{{ user_user_home }}"
home: "{{ _unix_users_home }}"
shell: "{{ unix_user.value.shell | default('/bin/bash') }}"
password: "{{ unix_user.value.password | password_hash('sha512') if unix_user.value.password is defined and unix_user.value.password | length > 0 else '!' }}"
# The salt is derived from the user name, a random one would produce a new hash and a change on every run.
password: "{{ unix_user.value.password | password_hash('sha512', unix_user.key | hash('sha512') | truncate(16, true, '')) if unix_user.value.password is defined and unix_user.value.password | length > 0 else '!' }}"
state: present
when: unix_user.value.groups is not defined and unix_user.value.uid is not defined
- name: "Create unix user without additional groups and with uid: {{ unix_user.key }}"
ansible.builtin.user:
name: "{{ unix_user.key }}"
uid: "{{ unix_user.value.uid }}"
group: "{{ unix_user.value.group | default('users') }}"
comment: "{{ unix_user.value.name }}"
create_home: "{{ unix_user.value.create_home | default(true) }}"
home: "{{ user_user_home }}"
shell: "{{ unix_user.value.shell | default('/bin/bash') }}"
password: "{{ unix_user.value.password | password_hash('sha512') if unix_user.value.password is defined and unix_user.value.password | length > 0 else '!' }}"
state: present
when: unix_user.value.groups is not defined and unix_user.value.uid is defined
- name: "Create unix user with additional groups and uid: {{ unix_user.key }}"
ansible.builtin.user:
name: "{{ unix_user.key }}"
uid: "{{ unix_user.value.uid }}"
group: "{{ unix_user.value.group | default('users') }}"
groups: "{{ unix_user.value.groups | join(',') }}"
comment: "{{ unix_user.value.name }}"
create_home: "{{ unix_user.value.create_home | default(true) }}"
home: "{{ user_user_home }}"
shell: "{{ unix_user.value.shell | default('/bin/bash') }}"
password: "{{ unix_user.value.password | password_hash('sha512') if unix_user.value.password is defined and unix_user.value.password | length > 0 else '!' }}"
state: present
when: unix_user.value.groups is defined and unix_user.value.uid is defined
- name: "Create unix user with additional groups and without uid: {{ unix_user.key }}"
ansible.builtin.user:
name: "{{ unix_user.key }}"
group: "{{ unix_user.value.group | default('users') }}"
groups: "{{ unix_user.value.groups | join(',') }}"
comment: "{{ unix_user.value.name }}"
create_home: "{{ unix_user.value.create_home | default(true) }}"
home: "{{ user_user_home }}"
shell: "{{ unix_user.value.shell | default('/bin/bash') }}"
password: "{{ unix_user.value.password | password_hash('sha512') if unix_user.value.password is defined and unix_user.value.password | length > 0 else '!' }}"
state: present
when: unix_user.value.groups is defined and unix_user.value.uid is not defined
- name: "Adapt permissions and copy skel for unix user: {{ unix_user.key }}"
when: unix_user.value.btrfs is defined and
unix_user.value.btrfs
block:
- name: "Copy skel files"
ansible.builtin.include_tasks: copy_skel_file.yml
ansible.builtin.include_tasks: copy_skel_file.yaml
loop_control:
loop_var: skel_file
with_items:
@@ -85,7 +58,7 @@
- ".bashrc"
- name: "Change permission unix users home dir: {{ unix_user.key }}"
ansible.builtin.file:
path: "{{ user_user_home }}"
path: "{{ _unix_users_home }}"
owner: "{{ unix_user.key }}"
group: "{{ unix_user.value.group | default('users') }}"
state: directory
@@ -93,16 +66,17 @@
- name: "Create .ssh directory for unix user: {{ unix_user.key }}"
ansible.builtin.file:
path: "{{ user_user_home }}/.ssh"
path: "{{ _unix_users_home }}/.ssh"
owner: "{{ unix_user.key }}"
group: "{{ unix_user.value.group | default('users') }}"
mode: "0700"
state: directory
when: unix_user.value.ssh is defined
- name: "Create authorized_keys file for unix user: {{ unix_user.key }}"
ansible.builtin.template:
src: authorized_keys.j2
dest: "{{ user_user_home }}/.ssh/authorized_keys"
dest: "{{ _unix_users_home }}/.ssh/authorized_keys"
owner: "{{ unix_user.key }}"
group: "{{ unix_user.value.group | default('users') }}"
mode: "0600"
@@ -110,60 +84,60 @@
- name: "Remove authorized_keys file for unix user: {{ unix_user.key }}"
ansible.builtin.file:
path: "{{ user_user_home }}/.ssh/authorized_keys"
path: "{{ _unix_users_home }}/.ssh/authorized_keys"
state: absent
when: unix_user.value.ssh.authorized_keys is not defined or unix_user.value.ssh.authorized_keys | length <= 0
# The relative source is resolved against the files directory of the playbook, like the lookup in authorized_keys.j2.
- name: "Create private SSH keys for unix user: {{ unix_user.key }}"
ansible.builtin.copy:
src: "{{ playbook_dir }}/ssh/private_keys/{{ item }}"
dest: "{{ user_user_home }}/.ssh/{{ item }}"
src: "ssh/private_keys/{{ item }}"
dest: "{{ _unix_users_home }}/.ssh/{{ item }}"
owner: "{{ unix_user.key }}"
group: "{{ unix_user.value.group | default('users') }}"
mode: "0600"
no_log: true
with_items:
- "{{ unix_user.value.ssh.private_keys }}"
when: unix_user.value.ssh.private_keys is defined and unix_user.value.ssh.private_keys | length > 0
- name: "Extract public SSH keys from private keys for unix user: {{ unix_user.key }}"
ansible.builtin.shell:
args:
executable: /bin/bash
cmd: "ssh-keygen -y -f {{ user_user_home }}/.ssh/{{ item }} > {{ user_user_home }}/.ssh/{{ item }}.pub"
creates: "{{ user_user_home }}/.ssh/{{ item }}.pub"
cmd: "ssh-keygen -y -f {{ _unix_users_home }}/.ssh/{{ item }} > {{ _unix_users_home }}/.ssh/{{ item }}.pub"
creates: "{{ _unix_users_home }}/.ssh/{{ item }}.pub"
with_items:
- "{{ unix_user.value.ssh.private_keys }}"
when: unix_user.value.ssh.private_keys is defined and unix_user.value.ssh.private_keys | length > 0
- name: "Correct permissions of public SSH keys for unix user: {{ unix_user.key }}"
ansible.builtin.file:
path: "{{ user_user_home }}/.ssh/{{ item }}.pub"
path: "{{ _unix_users_home }}/.ssh/{{ item }}.pub"
owner: "{{ unix_user.key }}"
group: "{{ unix_user.value.group | default('users') }}"
mode: "0644"
with_items:
- "{{ unix_user.value.ssh.private_keys }}"
when: unix_user.value.ssh.private_keys is defined and unix_user.value.ssh.private_keys | length >= 0
when: unix_user.value.ssh.private_keys is defined and unix_user.value.ssh.private_keys | length > 0
- name: "Create custom SSH client config for unix user: {{ unix_user.key }}"
ansible.builtin.template:
src: config.j2
dest: "{{ user_user_home }}/.ssh/config"
dest: "{{ _unix_users_home }}/.ssh/config"
owner: "{{ unix_user.key }}"
group: "{{ unix_user.value.group | default('users') }}"
mode: "0644"
when: unix_user.value.ssh.config is defined and unix_user.value.ssh.config | length >= 0
when: unix_user.value.ssh.config is defined and unix_user.value.ssh.config | length > 0
- name: "Remove custom SSH client config for unix user: {{ unix_user.key }}"
ansible.builtin.file:
path: "{{ user_user_home }}/.ssh/config"
path: "{{ _unix_users_home }}/.ssh/config"
state: absent
when: unix_user.value.ssh.config is not defined
when: unix_user.value.ssh.config is not defined or unix_user.value.ssh.config | length <= 0
- name: "Create .forward file to forward emails for unix user: {{ unix_user.key }}"
ansible.builtin.template:
src: forward.j2
dest: "{{ user_user_home }}/.forward"
dest: "{{ _unix_users_home }}/.forward"
owner: "{{ unix_user.key }}"
group: "{{ unix_user.value.group | default('users') }}"
mode: "0644"
@@ -171,7 +145,7 @@
- name: "Remove .forward file to forward emails for unix user: {{ unix_user.key }}"
ansible.builtin.file:
path: "{{ user_user_home }}/.forward"
path: "{{ _unix_users_home }}/.forward"
state: absent
when: unix_user.value.email is not defined
@@ -183,14 +157,14 @@
mode: "0755"
state: "directory"
with_items:
- "{{ unix_user.value.xdg.dirs.cache | default(user_user_home + '/.cache') }}"
- "{{ unix_user.value.xdg.dirs.config | default(user_user_home + '/.config') }}"
- "{{ unix_user.value.xdg.dirs.data | default(user_user_home + '/.local/share') }}"
- "{{ unix_user.value.xdg.dirs.state | default(user_user_home + '/.local/state') }}"
- "{{ unix_user.value.xdg.dirs.cache | default(_unix_users_home + '/.cache') }}"
- "{{ unix_user.value.xdg.dirs.config | default(_unix_users_home + '/.config') }}"
- "{{ unix_user.value.xdg.dirs.data | default(_unix_users_home + '/.local/share') }}"
- "{{ unix_user.value.xdg.dirs.state | default(_unix_users_home + '/.local/state') }}"
- name: "Create shell rc files"
when: unix_user.value.shell_rc_files is defined
ansible.builtin.include_tasks: create_shell_rc_file.yml
ansible.builtin.include_tasks: create_shell_rc_file.yaml
with_items:
- "{{ unix_user.value.shell_rc_files }}"
loop_control:
@@ -200,7 +174,8 @@
when: unix_user.value.netrc is defined and unix_user.value.netrc | length > 0
ansible.builtin.template:
src: netrc.j2
dest: "{{ user_user_home }}/.netrc"
dest: "{{ _unix_users_home }}/.netrc"
owner: "{{ unix_user.key }}"
group: "{{ unix_user.value.group | default('users') }}"
mode: "0600"
no_log: true
+4 -4
View File
@@ -1,7 +1,7 @@
---
- name: Remove unix user
ansible.builtin.include_tasks: remove_unix_user.yml
ansible.builtin.include_tasks: remove_unix_user.yaml
with_dict: "{{ unix_users }}"
loop_control:
loop_var: unix_user
@@ -11,7 +11,7 @@
unix_user.value.state == 'absent'
- name: Remove unix groups
ansible.builtin.include_tasks: remove_unix_group.yml
ansible.builtin.include_tasks: remove_unix_group.yaml
with_dict: "{{ unix_groups }}"
loop_control:
loop_var: unix_group
@@ -21,7 +21,7 @@
unix_group.value.state == 'absent'
- name: Create unix groups
ansible.builtin.include_tasks: create_unix_group.yml
ansible.builtin.include_tasks: create_unix_group.yaml
with_dict: "{{ unix_groups }}"
loop_control:
loop_var: unix_group
@@ -33,7 +33,7 @@
)
- name: Create unix users
ansible.builtin.include_tasks: create_unix_user.yml
ansible.builtin.include_tasks: create_unix_user.yaml
no_log: true
with_dict: "{{ unix_users }}"
loop_control:
+37
View File
@@ -0,0 +1,37 @@
---
- name: "Define home directory for unix user: {{ unix_user.key }}"
ansible.builtin.set_fact:
_unix_users_home: "{{ unix_user.value.home | default('/home/' + unix_user.key) }}"
# userdel cannot remove a btrfs subvolume. Such a home is deleted afterwards via the btrfs_subvolume module.
- name: "Remove unix user: {{ unix_user.key }}"
ansible.builtin.user:
name: "{{ unix_user.key }}"
state: absent
remove: "{{ not (unix_user.value.btrfs | default(false)) }}"
- name: "Remove btrfs home of unix user: {{ unix_user.key }}"
when: unix_user.value.btrfs is defined and
unix_user.value.btrfs
block:
- name: "Stat home directory"
ansible.builtin.stat:
path: "{{ _unix_users_home }}"
register: _unix_users_home_stat
# findmnt fails on a missing path, so the device is only determined as long as the home directory exists.
- name: "Delete btrfs subvolume of an existing home directory"
when: _unix_users_home_stat.stat.exists
block:
- name: "Find btrfs device"
ansible.builtin.command:
cmd: /bin/bash -c "findmnt -no SOURCE -T {{ _unix_users_home }} | sed 's/\[.*\]//'"
register: _unix_users_btrfs_device
changed_when: false
- name: "Delete btrfs subvolume: {{ _unix_users_home }}"
community.general.btrfs_subvolume:
filesystem_device: "{{ _unix_users_btrfs_device.stdout }}"
name: "{{ _unix_users_home }}"
state: absent
-7
View File
@@ -1,7 +0,0 @@
---
- name: Remove unix user {{ unix_user.key }}
ansible.builtin.user:
name: "{{ unix_user.key }}"
state: absent
remove: true
+21 -4
View File
@@ -3,9 +3,26 @@
# {{ ansible_managed }}
#
{% for authorized_key in unix_user.value.ssh.authorized_keys %}
{% if authorized_key.command is defined and authorized_key.command | length > 0 %}
command="{{ authorized_key.command }}" {{ lookup('file', 'ssh/authorized_keys/' + authorized_key.filename ) }}
{% else %}
{% set _args = [] %}
{% if authorized_key.command is defined and authorized_key.command | length > 0 %}
{% set _args = _args + [ "command=\"" + authorized_key.command + "\"" ] %}
{% endif %}
{% if authorized_key.envs is defined %}
{% set ns = namespace(envs=[]) %}
{% for environment in authorized_key.envs %}
{% if environment.key is defined and environment.key | length > 0 and
environment.value is defined and environment.value | length > 0
%}
{% set ns.envs = ns.envs + [ environment.key + "=" + environment.value ] %}
{% endif %}
{% endfor %}
{% if ns.envs | length > 0 %}
{% set _args = _args + [ "environment=\"" + (ns.envs | join(',')) + "\"" ] %}
{% endif %}
{% endif %}
{% if _args | length > 0 %}
{{ _args | join(',') }} {{ lookup('file', 'ssh/authorized_keys/' + authorized_key.filename ) }}
{% else %}
{{ lookup('file', 'ssh/authorized_keys/' + authorized_key.filename ) }}
{% endif %}
{% endif %}
{% endfor %}
+1 -1
View File
@@ -1,3 +1,3 @@
{% for netrc in unix_user.value.netrc %}
machine {{ netrc.maschine }} login {{ netrc.login }} password {{ netrc.password }}
machine {{ netrc.machine }} login {{ netrc.login }} password {{ netrc.password }}
{% endfor %}
+1 -1
View File
@@ -5,7 +5,7 @@
{% if shell_rc_file.functions is defined %}
{% for function in shell_rc_file.functions %}
function {{ function.name }} {
{{ function.body | indent(2, True) }}
{{ function.value | indent(2, True) }}
}
{% endfor %}