Hi M***n,
Below you’ll find:
1. A real custom ansible-lint rule in Python + its pytest module
2. Our full MR template markdown
3. How we handle changelog entries and version bumps once a rule is merged
,---
1. Example custom ansible-lint rule (rulesdir/custom_rules/no_raw_passwords.py)
```python
from ansiblelint.rules import AnsibleLintRule
class NoRawPasswordsRule(AnsibleLintRule):
id = 'NW100'
shortdesc = 'Avoid raw passwords in playbooks'
description = (
'Passwords should come from vault or vars_prompt, '
'not hard-coded strings.'
)
severity = 'HIGH'
tags = ['security', 'passwords']
def matchtask(self, file, task):
args = task.get('args', {})
pwd = args.get('password')
if isinstance(pwd, str) and not pwd.startswith('{{'):
# raw string, not a vault/ref
return True
return False
```
tests for that rule (tests/rules/test_no_raw_passwords.py)
```python
import pytest
from ansiblelint.runner import Runner
from ansiblelint.rules import RulesCollection
from rules.no_raw_passwords import NoRawPasswordsRule
@pytest.fixture
def collection():
coll = RulesCollection()
coll.register(NoRawPasswordsRule())
return coll
def test_password_from_vault_pass(collection, tmp_path):
play = tmp_path / "vault_pass.yml"
play.write_text("""
- hosts: all
tasks:
- name: set secure password
user:
name: alice
password: "{{ vault_user_password }}"
""")
matches = Runner(collection, [str(play)], [], [], {}, []).run()
assert not matches
def test_raw_password_fail(collection, tmp_path):
play = tmp_path / "raw_pass.yml"
play.write_text("""
- hosts: all
tasks:
- name: set insecure password
user:
name: bob
password: "SuperSecret123"
""")
matches = Runner(collection, [str(play)], [], [], {}, []).run()
# expect our NW100 rule to fire
assert any(m.rule.id == 'NW100' for m in matches)
```
,---
2. Full MR template markdown (.gitlab/merge_request_templates/network_change.md)
```markdown
### Title
[FEATURE/BUGFIX] Brief description of the change
### Jira / Issue
- Closes: PROJ-1234
### Description
- What does this MR do?
- Why is it needed?
### Files Changed
- List of key files (e.g., `rulesdir/...`, `tests/...`, `CHANGELOG.md`, `VERSION`)
### Changelog & Version Bump
- Added under `## [Unreleased]` in CHANGELOG.md:
- `- NW100: Avoid raw passwords in playbooks (no_raw_passwords rule)`
- Updated `automation/VERSION` from `1.4.2` → `1.4.3`
### Checklist
- [ ] CI pipeline passed (ansible-lint, syntax-check, Molecule, pytest)
- [ ] Two technical approvals obtained
- [ ] Runbooks/docs updated under `/docs` or `/runbooks`
- [ ] Rollback plan documented (if required)
### Testing & Validation
- Staging run output / dry-run logs
- Screenshots or log excerpts (if applicable)
### Impact & Rollout
- Affected devices/services
- Scheduled window (if any)
### Reviewer Notes
- Points requiring special attention
- Known limitations or follow-up tasks
```
,---
3. Changelog & version bump process
• We keep a `CHANGELOG.md` with an “Unreleased” section at the top.
• Upon merging a rule MR, the author adds a bullet under Unreleased describing the rule ID and purpose.
• We maintain a simple `automation/VERSION` file for the current package version. In the same MR, we bump that version (e.g. 1.4.2 → 1.4.3).
• Our GitLab CI then runs a “release” job on merges to `main` that:
, Tags the repo with the new version
, Moves the Unreleased block under the new version header with date
, Publishes release notes to our internal feed
You’re welcome to review these files directly in the repo. If you’d like to walk through anything live or see additional examples, let me know and we can jump on a 15-minute call.
Looking forward to your thoughts!
Best,
J***e
HR Recruiter, StrategyBrain