diff --git a/.gitea/workflows/ansible.yml b/.gitea/workflows/ansible.yml new file mode 100644 index 0000000..894115d --- /dev/null +++ b/.gitea/workflows/ansible.yml @@ -0,0 +1,57 @@ +--- +name: ansible + +on: + push: + branches: [main] + paths: + - 'infrastructure/**/ansible/**' + - '.ansible-lint' + - '.gitea/workflows/ansible.yml' + pull_request: + paths: + - 'infrastructure/**/ansible/**' + - '.ansible-lint' + - '.gitea/workflows/ansible.yml' + +env: + ANSIBLE_COLLECTIONS_PATH: /root/.ansible/collections + +jobs: + lint: + runs-on: self-hosted + steps: + - uses: actions/checkout@v4 + + - name: Bootstrap uv + run: | + python3 -m pip install --user --break-system-packages \ + --index-url https://pypi.org/simple --quiet uv==0.11.7 + echo "$HOME/.local/bin" >> "$GITHUB_PATH" + + - name: Install ansible-lint and collections + run: | + for i in 1 2 3 4 5; do + uv tool install ansible-core --with paramiko --with pywinrm --quiet && break + echo "attempt $i failed"; sleep 10 + done + for i in 1 2 3 4 5; do + uv tool install ansible-lint --quiet && break + echo "attempt $i failed"; sleep 10 + done + export PATH="$HOME/.local/bin:$PATH" + for p in infrastructure/proxmox infrastructure/samba-ad infrastructure/openbao; do + ansible-galaxy collection install \ + -r "$p/ansible/requirements.yml" -p "$ANSIBLE_COLLECTIONS_PATH" + done + + - name: ansible-lint + run: | + export PATH="$HOME/.local/bin:$PATH" + rc=0 + for p in infrastructure/openbao infrastructure/samba-ad infrastructure/proxmox; do + echo "::group::$p" + (cd "$p/ansible" && ansible-lint -c ../../../.ansible-lint --nocolor -f pep8 .) || rc=1 + echo "::endgroup::" + done + exit $rc diff --git a/.gitea/workflows/lint.yml b/.gitea/workflows/lint.yml index 72eb610..fdbbd0c 100644 --- a/.gitea/workflows/lint.yml +++ b/.gitea/workflows/lint.yml @@ -1,20 +1,26 @@ --- # Stage 1 of the infra pipeline: static checks only. No cluster access, no -# credentials, no mutation — so this is safe to run on every push from day one. +# credentials or mutation. It runs only when YAML-related paths change. # # Stages 2 (kubectl --dry-run=server) and 3 (k3d / molecule) come later and DO # need cluster access; keep them in separate workflows so a credential problem # there can never block this one. -name: lint +name: yaml on: push: + branches: [main] + paths: + - '**/*.yaml' + - '**/*.yml' + - '.yamllint.yml' + - '.gitea/workflows/lint.yml' pull_request: - -env: - # ansible-lint and ansible-core install as separate uv tools. Install Galaxy - # collections into this shared path so both isolated environments can see them. - ANSIBLE_COLLECTIONS_PATH: /root/.ansible/collections + paths: + - '**/*.yaml' + - '**/*.yml' + - '.yamllint.yml' + - '.gitea/workflows/lint.yml' jobs: yaml: @@ -23,8 +29,7 @@ jobs: - uses: actions/checkout@v4 - name: Bootstrap uv - # setup-uv queries api.github.com, which is unreachable from the nested - # job network. Official PyPI is reachable; pin the tool for reproducibility. + # Pin the tool for reproducibility; PyPI also avoids another setup action. run: | python3 -m pip install --user --break-system-packages \ --index-url https://pypi.org/simple --quiet uv==0.11.7 @@ -47,71 +52,3 @@ jobs: export PATH="$HOME/.local/bin:$PATH" files=$(git ls-files '*.yaml' '*.yml' | grep -vE '^apps/netboot/') yamllint -c .yamllint.yml --no-warnings -f parsable $files - - ansible: - runs-on: self-hosted - steps: - - uses: actions/checkout@v4 - - - name: Bootstrap uv - run: | - python3 -m pip install --user --break-system-packages \ - --index-url https://pypi.org/simple --quiet uv==0.11.7 - echo "$HOME/.local/bin" >> "$GITHUB_PATH" - - - name: Install ansible-lint and collections - # pywinrm is not optional — without it every ansible.windows.* task dies - # with "No module named 'winrm'" (CLAUDE.md documents this trap). - run: | - for i in 1 2 3 4 5; do - # Do not add the `ansible` meta-package here: it bundles collections - # inside this uv venv, making Galaxy skip the shared path below while - # ansible-lint's separate venv still cannot resolve the modules. - uv tool install ansible-core --with paramiko --with pywinrm --quiet && break - echo "attempt $i failed"; sleep 10 - done - for i in 1 2 3 4 5; do - uv tool install ansible-lint --quiet && break - echo "attempt $i failed"; sleep 10 - done - export PATH="$HOME/.local/bin:$PATH" - for p in infrastructure/proxmox infrastructure/samba-ad infrastructure/openbao; do - ansible-galaxy collection install \ - -r "$p/ansible/requirements.yml" -p "$ANSIBLE_COLLECTIONS_PATH" - done - - - name: ansible-lint - # Each project has its own ansible.cfg and relative roles_path, so lint - # must run from inside each one — a single run at the repo root resolves - # roles_path incorrectly and reports spurious missing-role errors. - run: | - export PATH="$HOME/.local/bin:$PATH" - rc=0 - for p in infrastructure/openbao infrastructure/samba-ad infrastructure/proxmox; do - echo "::group::$p" - (cd "$p/ansible" && ansible-lint -c ../../../.ansible-lint --nocolor -f pep8 .) || rc=1 - echo "::endgroup::" - done - exit $rc - - terraform: - runs-on: self-hosted - steps: - - uses: actions/checkout@v4 - - uses: hashicorp/setup-terraform@v3 - - - name: fmt and validate - # -backend=false so validate never touches real state or needs credentials. - # These roots deliberately use different providers AND different interactive - # auth (bao login -method=oidc, az login), which is exactly why they are not - # merged — so validate is as far as static checking can go here. - run: | - rc=0 - for d in $(git ls-files '*.tf' | xargs -n1 dirname | sort -u); do - echo "::group::$d" - terraform -chdir="$d" fmt -check -diff || rc=1 - terraform -chdir="$d" init -backend=false -input=false || rc=1 - terraform -chdir="$d" validate || rc=1 - echo "::endgroup::" - done - exit $rc diff --git a/.gitea/workflows/terraform.yml b/.gitea/workflows/terraform.yml new file mode 100644 index 0000000..d768b37 --- /dev/null +++ b/.gitea/workflows/terraform.yml @@ -0,0 +1,35 @@ +--- +name: terraform + +on: + push: + branches: [main] + paths: + - '**/*.tf' + - '**/.terraform.lock.hcl' + - '.gitea/workflows/terraform.yml' + pull_request: + paths: + - '**/*.tf' + - '**/.terraform.lock.hcl' + - '.gitea/workflows/terraform.yml' + +jobs: + validate: + runs-on: self-hosted + steps: + - uses: actions/checkout@v4 + - uses: hashicorp/setup-terraform@v3 + + - name: fmt and validate + # -backend=false so validate never touches real state or needs credentials. + run: | + rc=0 + for d in $(git ls-files '*.tf' | xargs -n1 dirname | sort -u); do + echo "::group::$d" + terraform -chdir="$d" fmt -check -diff || rc=1 + terraform -chdir="$d" init -backend=false -input=false || rc=1 + terraform -chdir="$d" validate || rc=1 + echo "::endgroup::" + done + exit $rc diff --git a/docs/cicd.md b/docs/cicd.md index 73d1a42..2853c29 100644 --- a/docs/cicd.md +++ b/docs/cicd.md @@ -1,7 +1,7 @@ # CI/CD — what we are building -Status: **partly built.** Stage 1 and the Kubernetes runner are live; Flux and -credentialed stages are not yet installed. +Status: **partly built.** Stage 1、Kubernetes runner 与 Flux 已上线;credentialed +stages 尚未实现。 Started 2026-07-28. ## Goal @@ -24,18 +24,20 @@ to make drift between this repo and reality visible when it happens. | | | |---|---| | git | `homelab-infra` is hosted on the local Gitea; an independent off-site mirror is still missing | -| stage 1 | Live and green on the Gitea runner — `yamllint`, `ansible-lint`, `terraform fmt`/`validate` | -| gitea | 1.25.5, Actions enabled, `DEFAULT_ACTIONS_URL=github`; one instance-scoped Kubernetes runner is deployed with capacity four | +| stage 1 | Gitea runner 上的 `yamllint`、`ansible-lint`、Terraform fmt/validate 已上线;三个 workflow 按路径触发,feature push 不再与 PR 事件重复运行 | +| gitea | 1.27.3,Actions 已启用;一个 instance-scoped Kubernetes runner 以 capacity 4 运行 | | ansible | 33 roles across `infrastructure/proxmox/`, `infrastructure/samba-ad/`, `infrastructure/openbao/` | | terraform | 4 roots, **local state**, each with **different interactive auth** (`bao login -method=oidc`, `az login`) | -| k8s | ~13 Helm releases, all deployed by hand | +| k8s | Flux 已接管 Gitea、Gitea Actions 与 http-echo canary;其余 brownfield release 逐项迁移 | | secrets | 4 config files are gitignored because they embed live secrets, so their contents are **not** version controlled | ## The four stages **Stage 1 — static. Built.** `yamllint`, `ansible-lint`, `terraform fmt -check` / `validate -backend=false`. -No cluster, no credentials, no mutation, so it is safe on every push. It already +No cluster, no credentials, no mutation. YAML、Ansible 和 Terraform 各自按相关路径 +触发;feature branch 只由 `pull_request` 检查,合并后再由 `main` push 检查,避免 +同一 revision 因 branch push 和 PR 各跑一遍。它已经 found a real defect: `infrastructure/proxmox/ansible/` had no `requirements.yml` at all, so a fresh checkout could not reproduce its collections.