Skip to content

Commit 2560795

Browse files
committed
Add ldo, the Libre DevOps Helpers CLI and library
Importable Python helpers and the ldo command for day-to-day DevOps and security work, starting with Microsoft: Entra ID, Defender for Endpoint, Intune, Azure (Resource Graph, RBAC, Defender for Cloud), Key Vault, Log Analytics and PIM, with device checks and watches across them. - A vendor-neutral core (config, token cache, HTTP client, command runner, polling, inputs and text, JSON or OTLP logging) and a microsoft layer with its features, with the layering enforced by a test. - Azure CLI, interactive, device code, client secret, workload identity and managed identity credentials. Everything reads, apart from 'az use'. - Every name comes from core/brand.py, and 'just rebrand' renames the project by parameter for use under another organisation's name. - CI with a gitleaks scan of every commit, pip-audit, tests on Python 3.11 to 3.14 and on Windows and macOS, and a single build; a tag-driven release workflow; CodeQL, Dependency Review and Dependabot.
1 parent ceaf94e commit 2560795

127 files changed

Lines changed: 16793 additions & 2 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
---
2+
name: Bug report
3+
about: Report something in ldo that is broken or not working as expected
4+
title: "[Bug]: "
5+
labels: bug
6+
assignees: ""
7+
---
8+
9+
<!-- Never paste an access token, tenant id, subscription id or real device name here.
10+
Replace them with placeholders such as <tenant-id> or web01.example.com. -->
11+
12+
## Description
13+
14+
A clear description of what is broken or not working as expected.
15+
16+
## Steps to Reproduce
17+
18+
1. Run `ldo ...`
19+
2. Observe the issue
20+
21+
## Expected Behaviour
22+
23+
What should have happened?
24+
25+
## Actual Behaviour
26+
27+
What actually happened? Include the error and hint lines, and the exit code.
28+
29+
## Environment
30+
31+
- ldo version (`ldo --version`):
32+
- Azure CLI version (`az version`):
33+
- Python version:
34+
- OS:
35+
36+
## Debug Output
37+
38+
If useful, run the command again with `-vv` and paste the relevant lines, with any ids redacted.
39+
40+
## Additional Context
41+
42+
Anything else useful.

‎.github/ISSUE_TEMPLATE/config.yml‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
blank_issues_enabled: false
2+
contact_links:
3+
- name: Report a security vulnerability
4+
url: https://github.com/libre-devops/python-helpers/security/advisories/new
5+
about: Please report vulnerabilities privately, not as a public issue.
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
---
2+
name: Feature request
3+
about: Suggest an enhancement or new command for ldo
4+
title: "[Feature]: "
5+
labels: enhancement
6+
assignees: ""
7+
---
8+
9+
## Summary
10+
11+
Describe the feature or improvement.
12+
13+
## Problem Statement
14+
15+
What problem does this solve, or what need does it address?
16+
17+
## Proposed Solution
18+
19+
What would you like to see implemented? If it calls a new Microsoft API, say which one and
20+
which permissions it needs.
21+
22+
## Alternatives Considered
23+
24+
Any other approaches you have thought of?
25+
26+
## Additional Context
27+
28+
Links, example output, or other relevant information.

‎.github/dependabot.yml‎

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
version: 2
2+
updates:
3+
# uv - pyproject.toml and uv.lock at the repository root. Minor and patch are
4+
# grouped into one PR per week; majors get individual PRs so they can be
5+
# reviewed separately.
6+
- package-ecosystem: uv
7+
directory: /
8+
schedule:
9+
interval: weekly
10+
day: monday
11+
time: "06:00"
12+
timezone: Europe/London
13+
groups:
14+
minor-and-patch:
15+
patterns: ["*"]
16+
update-types: [minor, patch]
17+
open-pull-requests-limit: 5
18+
labels:
19+
- dependencies
20+
21+
# GitHub Actions - group all updates together. Dependabot keeps the SHA pin
22+
# and its version comment in step for setup-uv.
23+
- package-ecosystem: github-actions
24+
directory: /
25+
schedule:
26+
interval: weekly
27+
day: monday
28+
time: "06:00"
29+
timezone: Europe/London
30+
groups:
31+
actions:
32+
patterns: ["*"]
33+
open-pull-requests-limit: 3
34+
labels:
35+
- dependencies
36+
- github-actions

‎.github/pull_request_template.md‎

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
## Summary
2+
3+
Brief description of the change.
4+
5+
## Type of Change
6+
7+
- [ ] Bug fix
8+
- [ ] New feature or command
9+
- [ ] Enhancement / improvement
10+
- [ ] Documentation update
11+
- [ ] Refactor
12+
- [ ] CI / pipeline change
13+
- [ ] Dependency update
14+
15+
## Testing
16+
17+
Describe how this was tested.
18+
19+
- [ ] `just check` passes locally (ruff lint, format check and pytest)
20+
- [ ] `just test-311` passes (the oldest supported Python)
21+
- [ ] New behaviour is covered by tests that use the fakes, not the network or a real `az`
22+
- [ ] Manually run against a real tenant (say which commands)
23+
- [ ] GitHub Actions pipeline passed
24+
25+
## Checklist
26+
27+
- [ ] Code follows project conventions (see CONTRIBUTING.md)
28+
- [ ] README updated for any new command, option or exit code
29+
- [ ] CHANGELOG.md updated
30+
- [ ] No tokens, tenant ids, subscription ids or real host names included (gitleaks checks)
31+
- [ ] No hard-coded tool names; they come from `core/brand.py` (the rebrand test checks)
32+
- [ ] Changes are scoped appropriately

‎.github/workflows/ci.yml‎

Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
name: Lint and Test
2+
3+
on:
4+
push:
5+
branches: [main]
6+
pull_request:
7+
branches: [main]
8+
workflow_dispatch:
9+
# The release workflow calls this one, so the gate a release passes is this exact job
10+
# set rather than a copy of it that can drift.
11+
workflow_call:
12+
13+
permissions:
14+
contents: read
15+
16+
concurrency:
17+
# github.workflow is the calling workflow's name under workflow_call, so a release and a
18+
# push to main land in their own groups and do not cancel each other.
19+
group: ci-${{ github.workflow }}-${{ github.ref }}
20+
cancel-in-progress: true
21+
22+
env:
23+
GITLEAKS_VERSION: 8.30.1
24+
# From the release's checksums file. Dependabot cannot update a pinned download, so bump
25+
# both together when moving to a new gitleaks release.
26+
GITLEAKS_SHA256: 551f6fc83ea457d62a0d98237cbad105af8d557003051f41f3e7ca7b3f2470eb
27+
PIP_AUDIT_VERSION: 2.10.1
28+
29+
jobs:
30+
secrets:
31+
name: Secret scan (gitleaks)
32+
runs-on: ubuntu-latest
33+
timeout-minutes: 10
34+
35+
steps:
36+
- name: Checkout (full history)
37+
uses: actions/checkout@v7
38+
with:
39+
# Every commit, so a secret added and later removed is still found.
40+
fetch-depth: 0
41+
42+
- name: Install gitleaks
43+
run: |
44+
archive="gitleaks_${GITLEAKS_VERSION}_linux_x64.tar.gz"
45+
curl -sSfL -o "$RUNNER_TEMP/$archive" \
46+
"https://github.com/gitleaks/gitleaks/releases/download/v${GITLEAKS_VERSION}/$archive"
47+
echo "${GITLEAKS_SHA256} $RUNNER_TEMP/$archive" | sha256sum -c -
48+
tar -xzf "$RUNNER_TEMP/$archive" -C "$RUNNER_TEMP" gitleaks
49+
50+
- name: Scan every commit
51+
run: '"$RUNNER_TEMP/gitleaks" git --config .gitleaks.toml --redact --verbose .'
52+
53+
lint:
54+
name: Lint and format check
55+
runs-on: ubuntu-latest
56+
timeout-minutes: 10
57+
58+
steps:
59+
- name: Checkout
60+
uses: actions/checkout@v7
61+
62+
- name: Set up uv
63+
uses: astral-sh/setup-uv@c18668ad3cf93ea998bef934396af7bb5c839dc7 # v10.2.0
64+
with:
65+
enable-cache: true
66+
67+
# --locked fails the job when uv.lock is out of step with pyproject.toml.
68+
- name: Install
69+
run: uv sync --locked
70+
71+
- name: Lint
72+
run: uv run ruff check --output-format=github src tests scripts
73+
74+
# Check only: formatting is fixed locally with 'just fmt', never in CI.
75+
- name: Format check
76+
run: uv run ruff format --check src tests scripts
77+
78+
audit:
79+
name: Dependency audit (pip-audit)
80+
runs-on: ubuntu-latest
81+
timeout-minutes: 10
82+
83+
steps:
84+
- name: Checkout
85+
uses: actions/checkout@v7
86+
87+
- name: Set up uv
88+
uses: astral-sh/setup-uv@c18668ad3cf93ea998bef934396af7bb5c839dc7 # v10.2.0
89+
90+
# The locked, hashed dependency tree, runtime and dev, exactly as CI installs it.
91+
- name: Audit the locked dependencies for known vulnerabilities
92+
run: |
93+
uv export --frozen --format requirements-txt --no-emit-project \
94+
--output-file "$RUNNER_TEMP/requirements.txt"
95+
uvx "pip-audit==${PIP_AUDIT_VERSION}" --strict --disable-pip --require-hashes \
96+
--requirement "$RUNNER_TEMP/requirements.txt"
97+
98+
test:
99+
name: Test (Python ${{ matrix.python-version }}, ${{ matrix.os }})
100+
runs-on: ${{ matrix.os }}
101+
timeout-minutes: 15
102+
103+
strategy:
104+
fail-fast: false
105+
matrix:
106+
os: [ubuntu-latest]
107+
python-version: ["3.11", "3.12", "3.13", "3.14"]
108+
# The config path and file permission handling differ on Windows and macOS,
109+
# so run the suite there too, on one Python version.
110+
include:
111+
- os: windows-latest
112+
python-version: "3.13"
113+
- os: macos-latest
114+
python-version: "3.13"
115+
116+
env:
117+
UV_PYTHON: ${{ matrix.python-version }}
118+
119+
steps:
120+
- name: Checkout
121+
uses: actions/checkout@v7
122+
123+
- name: Set up uv
124+
uses: astral-sh/setup-uv@c18668ad3cf93ea998bef934396af7bb5c839dc7 # v10.2.0
125+
with:
126+
enable-cache: true
127+
128+
- name: Install
129+
run: uv sync --locked
130+
131+
# Includes the rebrand test, which renames a copy of the repository and runs its
132+
# whole suite, so 'just rebrand' is proven on every platform.
133+
- name: Test
134+
run: uv run pytest
135+
136+
build:
137+
name: Build
138+
runs-on: ubuntu-latest
139+
timeout-minutes: 10
140+
needs: [secrets, lint, audit, test]
141+
142+
steps:
143+
- name: Checkout
144+
uses: actions/checkout@v7
145+
146+
- name: Set up uv
147+
uses: astral-sh/setup-uv@c18668ad3cf93ea998bef934396af7bb5c839dc7 # v10.2.0
148+
149+
- name: Build the sdist and wheel
150+
run: uv build
151+
152+
# Proves the wheel installs cleanly and the entry point resolves outside the source tree.
153+
- name: Smoke test the wheel
154+
shell: bash
155+
run: |
156+
uv venv "$RUNNER_TEMP/smoke"
157+
uv pip install --python "$RUNNER_TEMP/smoke" dist/*.whl
158+
"$RUNNER_TEMP/smoke/bin/ldo" --version
159+
"$RUNNER_TEMP/smoke/bin/ldo" --help > /dev/null
160+
161+
# Built once here; a release publishes these exact files rather than rebuilding.
162+
- name: Keep the build
163+
uses: actions/upload-artifact@v7
164+
with:
165+
name: dist
166+
path: dist/
167+
if-no-files-found: error
168+
retention-days: 14

‎.github/workflows/codeql.yml‎

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
name: CodeQL
2+
3+
on:
4+
push:
5+
branches: [main]
6+
pull_request:
7+
branches: [main]
8+
schedule:
9+
# Weekly full scan, Monday 03:00 UTC
10+
- cron: '0 3 * * 1'
11+
12+
permissions:
13+
contents: read
14+
security-events: write
15+
actions: read
16+
17+
jobs:
18+
analyse:
19+
name: Analyse (${{ matrix.language }})
20+
runs-on: ubuntu-latest
21+
timeout-minutes: 20
22+
23+
strategy:
24+
fail-fast: false
25+
matrix:
26+
# python: the package under src/ and the tests
27+
# actions: the workflows themselves
28+
language: [python, actions]
29+
30+
steps:
31+
- name: Checkout
32+
uses: actions/checkout@v7
33+
34+
# Neither language is compiled, so there is nothing to build before analysis.
35+
- name: Initialise CodeQL
36+
uses: github/codeql-action/init@v4
37+
with:
38+
languages: ${{ matrix.language }}
39+
build-mode: none
40+
# security-and-quality catches both vulnerability patterns and bad practices
41+
queries: security-and-quality
42+
43+
- name: Perform CodeQL analysis
44+
uses: github/codeql-action/analyze@v4
45+
with:
46+
category: /language:${{ matrix.language }}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
name: Dependency Review
2+
3+
on:
4+
pull_request:
5+
branches: [main]
6+
7+
permissions:
8+
contents: read
9+
pull-requests: write
10+
11+
jobs:
12+
dependency-review:
13+
name: Dependency Review
14+
runs-on: ubuntu-latest
15+
timeout-minutes: 5
16+
17+
steps:
18+
- name: Checkout
19+
uses: actions/checkout@v7
20+
21+
- name: Dependency Review
22+
uses: actions/dependency-review-action@v5
23+
# continue-on-error so findings comment on the PR but never block merge
24+
continue-on-error: true
25+
with:
26+
comment-summary-in-pr: always
27+
fail-on-severity: moderate

0 commit comments

Comments
 (0)