From 4fbb2dff3b1e2be33ded955f3aea55d1c228ea1d Mon Sep 17 00:00:00 2001 From: Eduardo Fraga Date: Wed, 29 Jul 2026 08:03:37 -0300 Subject: [PATCH 1/5] feat(tests): add smoke tests and CI automation --- .github/workflows/test.yaml | 66 ++++++++++++++++++++++++++++--------- AGENTS.md | 30 +++++++++++++++++ tests/smoke_ansible.sh | 17 ++++++++++ tests/smoke_build.sh | 11 +++++++ tests/smoke_playbook.sh | 17 ++++++++++ tests/smoke_python.sh | 14 ++++++++ tests/smoke_shell.sh | 21 ++++++++++++ tests/test.yml | 7 ++++ 8 files changed, 168 insertions(+), 15 deletions(-) create mode 100644 AGENTS.md create mode 100755 tests/smoke_ansible.sh create mode 100755 tests/smoke_build.sh create mode 100755 tests/smoke_playbook.sh create mode 100755 tests/smoke_python.sh create mode 100755 tests/smoke_shell.sh create mode 100644 tests/test.yml diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 8d00f7c..8b9af20 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -1,18 +1,54 @@ -name: Run +name: Smoke tests on: - workflow_dispatch: - inputs: - command: - description: "Command to execute" - required: true - default: "ansible-playbook" - -jobs: - run: + push: + branches: ["**"] + pull_request: + branches: ["master"] + workflow_dispatch: + +jobs: + smoke: runs-on: ubuntu-latest - steps: - - uses: eftechcombr/ansible-cli-github-action@master - with: - command: "ansible --version" - + steps: + - uses: actions/checkout@v4 + + - name: Build Docker image + run: docker build -t ansible-cli-smoke:test . + + - name: smoke_build — image built + run: docker image inspect ansible-cli-smoke:test > /dev/null + + - name: smoke_ansible — CLI version checks + run: | + docker run --rm ansible-cli-smoke:test "ansible --version" + docker run --rm ansible-cli-smoke:test "ansible-playbook --version" + docker run --rm ansible-cli-smoke:test "ansible-inventory --help" + + - name: smoke_python — dependency imports + run: | + docker run --rm ansible-cli-smoke:test "python -c 'import winrm; print(\"winrm ok\")'" + docker run --rm ansible-cli-smoke:test "python -c 'import ansible; print(\"ansible ok\")'" + + - name: smoke_shell — eval passthrough + run: | + OUTPUT=$(docker run --rm ansible-cli-smoke:test "echo hello from ansible") + [ "$OUTPUT" = "hello from ansible" ] || { echo "FAIL: $OUTPUT"; exit 1; } + + - name: smoke_shell — exit code propagation + run: | + set +e + docker run --rm ansible-cli-smoke:test "exit 42" + RC=$? + set -e + [ "$RC" -eq 42 ] || { echo "FAIL: expected 42, got $RC"; exit 1; } + + - name: smoke_playbook — syntax check + run: | + docker run --rm -v ${{ github.workspace }}/tests:/tests:ro ansible-cli-smoke:test \ + "ansible-playbook --syntax-check /tests/test.yml" + + - name: smoke_playbook — ping localhost + run: | + docker run --rm -v ${{ github.workspace }}/tests:/tests:ro ansible-cli-smoke:test \ + "ansible-playbook -i 'localhost,' -c local /tests/test.yml" diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..87f18a4 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,30 @@ +# AGENTS.md + +## Dev environment tips +- Build the Docker image with `docker build -t ansible-cli-smoke:test .` before running tests. +- Use `docker compose build` for the Compose-based build. + +## Testing instructions +- Run all smoke tests: `tests/smoke_build.sh && tests/smoke_ansible.sh && tests/smoke_python.sh && tests/smoke_shell.sh && tests/smoke_playbook.sh` +- Or run individual tests: `tests/smoke_build.sh` (must run first — sets image tag), then any of `tests/smoke_ansible.sh`, `tests/smoke_python.sh`, `tests/smoke_shell.sh`, `tests/smoke_playbook.sh` +- CI runs all smoke tests via `.github/workflows/test.yaml` on every push and PR. + +## PR instructions +- Follow Conventional Commits (for example, `feat(scaffolding): add doc links`). +- Cross-link new scaffolds in `docs/README.md` and `agents/README.md` so future agents can find them. +- Attach sample CLI output or generated markdown when behaviour shifts. +- Confirm the built artefacts in `dist/` match the new source changes. + +## Repository map +- `action.yml` — GitHub Action metadata (inputs, branding, Docker runtime config) +- `docker-compose.yaml` — local dev convenience for building and tagging the image +- `Dockerfile` — builds the container from `python:3.11-slim` +- `entrypoint.sh` — container entrypoint, executes `eval $1` +- `README.md` — user-facing documentation +- `requirements.txt` — Python dependencies (`ansible`, `pywinrm[credssp]`) +- `tests/` — smoke test scripts and test fixtures + +## AI Context References +- Documentation index: `.context/docs/README.md` +- Agent playbooks: `.context/agents/README.md` +- Contributor guide: `CONTRIBUTING.md` diff --git a/tests/smoke_ansible.sh b/tests/smoke_ansible.sh new file mode 100755 index 0000000..a7d4ea3 --- /dev/null +++ b/tests/smoke_ansible.sh @@ -0,0 +1,17 @@ +#!/bin/sh +set -e + +. /tmp/ansible_smoke_image 2>/dev/null || IMG_LABEL="ansible-cli-smoke:test" + +echo "=== smoke_ansible: Ansible CLI tools ===" + +echo " -> ansible --version" +docker run --rm "$IMG_LABEL" "ansible --version" + +echo " -> ansible-playbook --version" +docker run --rm "$IMG_LABEL" "ansible-playbook --version" + +echo " -> ansible-inventory --help" +docker run --rm "$IMG_LABEL" "ansible-inventory --help" + +echo "PASS: smoke_ansible" diff --git a/tests/smoke_build.sh b/tests/smoke_build.sh new file mode 100755 index 0000000..abff463 --- /dev/null +++ b/tests/smoke_build.sh @@ -0,0 +1,11 @@ +#!/bin/sh +set -e + +echo "=== smoke_build: Docker image build ===" + +IMG_LABEL="ansible-cli-smoke:test" + +docker build -t "$IMG_LABEL" . + +echo "PASS: smoke_build" +echo "IMG_LABEL=$IMG_LABEL" > /tmp/ansible_smoke_image diff --git a/tests/smoke_playbook.sh b/tests/smoke_playbook.sh new file mode 100755 index 0000000..9ba6703 --- /dev/null +++ b/tests/smoke_playbook.sh @@ -0,0 +1,17 @@ +#!/bin/sh +set -e + +. /tmp/ansible_smoke_image 2>/dev/null || IMG_LABEL="ansible-cli-smoke:test" +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" + +echo "=== smoke_playbook: Ansible playbook parsing and local run ===" + +echo " -> syntax check" +docker run --rm -v "$SCRIPT_DIR:/tests:ro" "$IMG_LABEL" \ + "ansible-playbook --syntax-check /tests/test.yml" + +echo " -> ping localhost" +docker run --rm -v "$SCRIPT_DIR:/tests:ro" "$IMG_LABEL" \ + "ansible-playbook -i 'localhost,' -c local /tests/test.yml" + +echo "PASS: smoke_playbook" diff --git a/tests/smoke_python.sh b/tests/smoke_python.sh new file mode 100755 index 0000000..63afb66 --- /dev/null +++ b/tests/smoke_python.sh @@ -0,0 +1,14 @@ +#!/bin/sh +set -e + +. /tmp/ansible_smoke_image 2>/dev/null || IMG_LABEL="ansible-cli-smoke:test" + +echo "=== smoke_python: Python dependency imports ===" + +echo " -> import winrm" +docker run --rm "$IMG_LABEL" "python -c \"import winrm; print('winrm version:', winrm.__version__)\"" + +echo " -> import ansible" +docker run --rm "$IMG_LABEL" "python -c \"import ansible; print('ansible loaded ok')\"" + +echo "PASS: smoke_python" diff --git a/tests/smoke_shell.sh b/tests/smoke_shell.sh new file mode 100755 index 0000000..a6ef121 --- /dev/null +++ b/tests/smoke_shell.sh @@ -0,0 +1,21 @@ +#!/bin/sh +set -e + +. /tmp/ansible_smoke_image 2>/dev/null || IMG_LABEL="ansible-cli-smoke:test" + +echo "=== smoke_shell: eval passthrough + exit codes ===" + +echo " -> echo passthrough" +OUTPUT=$(docker run --rm "$IMG_LABEL" "echo hello from ansible") +echo " output: $OUTPUT" +[ "$OUTPUT" = "hello from ansible" ] || { echo "FAIL: unexpected output"; exit 1; } + +echo " -> exit code propagation (expect exit 42)" +set +e +docker run --rm "$IMG_LABEL" "exit 42" +RC=$? +set -e +echo " exit code: $RC" +[ "$RC" -eq 42 ] || { echo "FAIL: expected 42, got $RC"; exit 1; } + +echo "PASS: smoke_shell" diff --git a/tests/test.yml b/tests/test.yml new file mode 100644 index 0000000..232ac4f --- /dev/null +++ b/tests/test.yml @@ -0,0 +1,7 @@ +--- +- name: Smoke test connectivity + hosts: localhost + gather_facts: false + tasks: + - name: Verify reachability + ping: From 3b756fb8e37f0a70c693f19bf210638f34db7f71 Mon Sep 17 00:00:00 2001 From: Eduardo Fraga Date: Wed, 29 Jul 2026 08:28:28 -0300 Subject: [PATCH 2/5] docs: update documentation for smoke tests and CI automation --- .context/docs/development-workflow.md | 52 ++++++++++++++++++ .context/docs/testing-strategy.md | 78 +++++++++++++++++++++++++++ .context/docs/tooling.md | 75 ++++++++++++++++++++++++++ README.md | 28 ++++++++-- 4 files changed, 228 insertions(+), 5 deletions(-) create mode 100644 .context/docs/development-workflow.md create mode 100644 .context/docs/testing-strategy.md create mode 100644 .context/docs/tooling.md diff --git a/.context/docs/development-workflow.md b/.context/docs/development-workflow.md new file mode 100644 index 0000000..bb5d89d --- /dev/null +++ b/.context/docs/development-workflow.md @@ -0,0 +1,52 @@ +--- +type: doc +name: development-workflow +description: Day-to-day engineering processes, branching, and contribution guidelines +category: workflow +generated: 2026-07-29 +status: filled +--- + +# Development Workflow + +## Prerequisites + +- Docker +- Git + +## Local Development + +1. Clone the repository +2. Build the Docker image: + ```bash + docker compose build + ``` +3. Run the full smoke test suite: + ```bash + tests/smoke_build.sh && tests/smoke_ansible.sh && tests/smoke_python.sh && tests/smoke_shell.sh && tests/smoke_playbook.sh + ``` +4. Or test a single command: + ```bash + docker run --rm eftechcombr/ansible-cli-github-action "ansible --version" + ``` + +## Making Changes + +- Edit the appropriate file (`Dockerfile`, `entrypoint.sh`, `requirements.txt`, `action.yml`) +- Rebuild and run the smoke test suite locally +- Update `README.md` if the interface changes +- Verify all five smoke tests pass before committing + +## CI/CD + +The repository has two workflows (see `.github/workflows/`): + +- **test.yaml** — Runs automatically on every push (any branch) and pull request (targeting master). Executes all five smoke tests: build, ansible CLI, Python imports, shell passthrough, and playbook execution. Can also be triggered manually via `workflow_dispatch`. +- **build.yaml** — Manually triggered workflow that builds the Docker image using `docker/build-push-action` (no push). + +## Contribution Guidelines + +- Follow conventional commits for PR titles +- Test changes locally with the full smoke test suite +- Ensure all five smoke tests pass +- Update `README.md` for any interface or input changes diff --git a/.context/docs/testing-strategy.md b/.context/docs/testing-strategy.md new file mode 100644 index 0000000..3e05b21 --- /dev/null +++ b/.context/docs/testing-strategy.md @@ -0,0 +1,78 @@ +--- +type: doc +name: testing-strategy +description: Test frameworks, patterns, coverage requirements, and quality gates +category: testing +generated: 2026-07-29 +status: filled +--- + +# Testing Strategy + +## Approach + +This project is a thin Docker-based GitHub Action. Testing focuses on three areas: + +1. **Container build** — ensure the Docker image builds successfully +2. **Command execution** — verify that Ansible is installed and runnable inside the container +3. **Runtime behavior** — verify shell passthrough, exit code propagation, and playbook execution + +Smoke tests are implemented as standalone shell scripts in `tests/` that can be run locally or in CI. + +## Tests + +### Local Testing + +Build the image first, then run the full smoke test suite: + +```bash +tests/smoke_build.sh && tests/smoke_ansible.sh && tests/smoke_python.sh && tests/smoke_shell.sh && tests/smoke_playbook.sh +``` + +Individual smoke tests can also be run independently: + +```bash +tests/smoke_build.sh # must run first — sets image tag +tests/smoke_ansible.sh # checks ansible, ansible-playbook, ansible-inventory +tests/smoke_python.sh # checks Python dependency imports (winrm, ansible) +tests/smoke_shell.sh # checks eval passthrough and exit code propagation +tests/smoke_playbook.sh # checks playbook syntax checking and localhost ping +``` + +Alternatively, build and test with Docker Compose: + +```bash +docker compose build +docker run --rm eftechcombr/ansible-cli-github-action "ansible --version" +``` + +### CI Testing (GitHub Actions) + +The workflow in `.github/workflows/test.yaml` runs all five smoke tests automatically on every push (any branch) and pull request (targeting master). It can also be triggered manually via `workflow_dispatch`. + +The CI pipeline executes the same checks as the local smoke scripts: + +- **smoke_build** — verifies the Docker image is built +- **smoke_ansible** — runs `ansible --version`, `ansible-playbook --version`, `ansible-inventory --help` +- **smoke_python** — imports `winrm` and `ansible` Python packages +- **smoke_shell** — verifies command output passthrough and exit code propagation +- **smoke_playbook** — runs `ansible-playbook --syntax-check` and a ping playbook against localhost + +## Quality Gates + +- Docker image must build without errors +- `ansible --version` must exit with code 0 +- `ansible-playbook --version` must exit with code 0 +- `ansible-inventory --help` must exit with code 0 +- Python `winrm` and `ansible` module imports must succeed +- Shell command output must pass through correctly +- Shell exit codes must propagate correctly +- Playbook syntax checking must pass +- Playbook must run successfully against localhost with the `local` connection plugin + +## What We Don't Test + +- Ansible playbook correctness (the action is a runner, not a linter) +- Specific Ansible modules or features (upstream Ansible tests those) +- Remote host connectivity or SSH configuration +- Multi-node playbook orchestration diff --git a/.context/docs/tooling.md b/.context/docs/tooling.md new file mode 100644 index 0000000..a47f419 --- /dev/null +++ b/.context/docs/tooling.md @@ -0,0 +1,75 @@ +--- +type: doc +name: tooling +description: Scripts, IDE settings, automation, and developer productivity tips +category: tooling +generated: 2026-07-29 +status: filled +--- + +# Tooling + +## Docker Compose + +`docker-compose.yaml` provides a quick way to build and tag the image: + +```bash +docker compose build +``` + +## VS Code Settings + +`.vscode/settings.json` contains: + +```json +{ + "kiroAgent.configureMCP": "Enabled" +} +``` + +## GitHub Actions + +The CI workflow runs automatically: + +- **Test** (`.github/workflows/test.yaml`) — triggered on every push (any branch) and pull request (targeting master). Runs all five smoke tests: build, ansible CLI, Python imports, shell passthrough, and playbook execution. Also supports manual trigger via `workflow_dispatch`. +- **Build** (`.github/workflows/build.yaml`) — manually triggered workflow that builds the Docker image using `docker/build-push-action` (no push to registry). + +## Smoke Tests + +Five standalone shell scripts live in `tests/`: + +| Script | What It Tests | +|---|---| +| `smoke_build.sh` | Docker image builds successfully | +| `smoke_ansible.sh` | Ansible CLI tools are present and runnable | +| `smoke_python.sh` | Python dependencies (`winrm`, `ansible`) are importable | +| `smoke_shell.sh` | Command passthrough and exit code propagation | +| `smoke_playbook.sh` | Playbook syntax checking and localhost execution | + +Run the full suite: + +```bash +tests/smoke_build.sh && tests/smoke_ansible.sh && tests/smoke_python.sh && tests/smoke_shell.sh && tests/smoke_playbook.sh +``` + +## Quick Reference + +```bash +# Build locally +docker compose build +# or +docker build -t ansible-cli-smoke:test . + +# Run all smoke tests +tests/smoke_build.sh && tests/smoke_ansible.sh && tests/smoke_python.sh && tests/smoke_shell.sh && tests/smoke_playbook.sh + +# Run a command +docker run --rm eftechcombr/ansible-cli-github-action "ansible --version" + +# Interactive shell (for debugging) +docker run --rm -it --entrypoint /bin/bash eftechcombr/ansible-cli-github-action +``` + +## No Package Manager + +This project has no `package.json` — all dependencies are Python-based and managed via `requirements.txt`. diff --git a/README.md b/README.md index 9d65861..86840f5 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ # Ansible CLI Github Action -This action is based on `ubuntu:bionic` image. -You can execute all `Ansible` related actions i.e, `ansible` or `ansible-playbook`. -You can also execute standard `Linux` commands as base `Docker` image is `ubuntu`. +This action is based on the `python:3.11-slim` image. +You can execute all Ansible-related commands: `ansible`, `ansible-playbook`, `ansible-galaxy`, and others. +You can also execute standard Linux commands as the base image includes a shell environment. ## Inputs @@ -12,8 +12,26 @@ You can also execute standard `Linux` commands as base `Docker` image is `ubuntu ## Example usage -``` -uses: actions/ansible-cli-action@latest +```yaml +uses: eftechcombr/ansible-cli-github-action@latest with: command: "ansible-playbook main.yml" ``` + +## Testing + +Run the smoke test suite locally to verify the Docker image builds and all Ansible tools work correctly: + +```bash +tests/smoke_build.sh && tests/smoke_ansible.sh && tests/smoke_python.sh && tests/smoke_shell.sh && tests/smoke_playbook.sh +``` + +The smoke tests cover: + +- Docker image build +- Ansible CLI version checks (`ansible`, `ansible-playbook`, `ansible-inventory`) +- Python dependency imports (`winrm`, `ansible`) +- Shell command passthrough and exit code propagation +- Playbook syntax checking and localhost execution + +CI runs these tests automatically on every push and pull request via GitHub Actions. From e83152cb83eb1a7edec336c80ed7feac852004c1 Mon Sep 17 00:00:00 2001 From: Eduardo Fraga Date: Wed, 29 Jul 2026 09:03:43 -0300 Subject: [PATCH 3/5] docs: add connection examples for Windows, Linux, and self-hosted runners --- .context/docs/examples.md | 199 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 199 insertions(+) create mode 100644 .context/docs/examples.md diff --git a/.context/docs/examples.md b/.context/docs/examples.md new file mode 100644 index 0000000..5173a27 --- /dev/null +++ b/.context/docs/examples.md @@ -0,0 +1,199 @@ +## Examples + +### Example 1: Windows connection via WinRM + +This example runs an Ansible playbook against a Windows target over WinRM. The `pywinrm[credssp]` package is pre-installed in the action's Docker image, so no additional setup is required. + +**Inventory (`inventory.ini`):** + +```ini +[windows] +windows-server-01.example.com + +[windows:vars] +ansible_connection=winrm +ansible_winrm_transport=ntlm +ansible_user=Administrator +ansible_password={{ windows_admin_password }} +ansible_winrm_server_cert_validation=ignore +``` + +**Playbook (`win-ping.yml`):** + +```yaml +--- +- name: Verify Windows host connectivity + hosts: windows + tasks: + - name: Test WinRM connection + ansible.windows.win_ping: +``` + +**Workflow (`.github/workflows/windows-check.yml`):** + +```yaml +name: Windows Connectivity Check + +on: + schedule: + - cron: "0 6 * * *" + workflow_dispatch: + +jobs: + winrm-ping: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Run WinRM ping playbook + uses: eftechcombr/ansible-cli-github-action@master + with: + command: > + ansible-playbook win-ping.yml + -i inventory.ini + -e windows_admin_password=${{ secrets.WINDOWS_ADMIN_PASSWORD }} +``` + +The `ansible-playbook` command passes the inventory file and injects the Windows administrator password via the `-e` flag, keeping the secret out of the repository. Because `pywinrm[credssp]` is bundled in the image, WinRM connections work without any additional pip install steps. + +--- + +### Example 2: Linux connection via SSH + +This example covers both playbook and ad-hoc command execution against Linux targets over SSH. + +**Inventory (`inventory.yml`):** + +```yaml +all: + hosts: + web-01.example.com: + web-02.example.com: + vars: + ansible_connection: ssh + ansible_user: ubuntu + ansible_ssh_private_key_file: /tmp/ssh_key +``` + +**Playbook (`ping.yml`):** + +```yaml +--- +- name: Verify Linux host connectivity + hosts: all + tasks: + - name: Test SSH connection + ansible.builtin.ping: +``` + +**Workflow (`.github/workflows/linux-check.yml`):** + +```yaml +name: Linux Connectivity Check + +on: + push: + branches: [main] + workflow_dispatch: + +jobs: + ssh-ping: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Install SSH key + run: | + mkdir -p /tmp + echo "${{ secrets.SSH_PRIVATE_KEY }}" > /tmp/ssh_key + chmod 600 /tmp/ssh_key + + - name: Run playbook + uses: eftechcombr/ansible-cli-github-action@master + with: + command: > + ansible-playbook ping.yml + -i inventory.yml + + - name: Ad-hoc ping test + uses: eftechcombr/ansible-cli-github-action@master + with: + command: ansible all -i inventory.yml -m ping +``` + +The first step writes the SSH private key to disk (sourced from a GitHub secret), then sets restrictive permissions so that Ansible's SSH client accepts it. The playbook step executes `ansible-playbook` against all hosts, and the ad-hoc step demonstrates how to run a single module directly with `ansible ... -m ping`. + +--- + +### Example 3: Connection via GitHub Actions self-hosted runner + +A self-hosted runner is a machine you deploy and manage yourself to run GitHub Actions workflows. It connects to GitHub and picks up jobs intended for its runner group. Self-hosted runners are useful when you need access to internal networks, on-premises infrastructure, or resources not exposed to the public internet. For more details, see the [GitHub documentation on self-hosted runners](https://docs.github.com/en/actions/hosting-your-own-runners/managing-self-hosted-runners/about-self-hosted-runners). + +When a job targets a self-hosted runner, the `eftechcombr/ansible-cli-github-action` container runs directly on that machine. Because this action is Docker-based, the runner only needs Docker installed -- it does not need Ansible or Python installed on the host itself. The action's image provides everything required. + +Use a self-hosted runner label in the `runs-on` field to target your runner fleet: + +```yaml +runs-on: [self-hosted, linux, production] +``` + +**Inventory (`inventory.yml`):** + +```yaml +all: + hosts: + db-primary.internal.example.com: + db-replica.internal.example.com: + vars: + ansible_connection: ssh + ansible_user: admin + ansible_ssh_private_key_file: /etc/runner/keys/ansible_rsa +``` + +**Playbook (`health-check.yml`):** + +```yaml +--- +- name: Database infrastructure health check + hosts: all + tasks: + - name: Ensure PostgreSQL is accepting connections + ansible.builtin.wait_for: + port: 5432 + host: "{{ inventory_hostname }}" + timeout: 10 + + - name: Collect uptime + ansible.builtin.shell: uptime + register: result + + - name: Report uptime + ansible.builtin.debug: + msg: "{{ inventory_hostname }} uptime: {{ result.stdout }}" +``` + +**Workflow (`.github/workflows/internal-health.yml`):** + +```yaml +name: Internal Infrastructure Health Check + +on: + schedule: + - cron: "*/15 * * * *" + workflow_dispatch: + +jobs: + health-check: + runs-on: [self-hosted, linux, production] + steps: + - uses: actions/checkout@v4 + + - name: Run health check playbook against internal hosts + uses: eftechcombr/ansible-cli-github-action@master + with: + command: > + ansible-playbook health-check.yml + -i inventory.yml +``` + +Because the workflow runs on a self-hosted runner inside the internal network, it can resolve `db-primary.internal.example.com` and `db-replica.internal.example.com` -- hosts that would not be reachable from GitHub-hosted runners. The action's Docker image supplies Ansible and all dependencies, so no additional setup is needed on the runner host. From 4e9eb38b1290ba56a74c25e6e5ab65e4112b5d17 Mon Sep 17 00:00:00 2001 From: Eduardo Fraga Date: Wed, 29 Jul 2026 13:30:59 -0300 Subject: [PATCH 4/5] docs: add Windows, Linux, and self-hosted runner examples to README --- README.md | 184 ++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 180 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 86840f5..78f3f7d 100644 --- a/README.md +++ b/README.md @@ -10,14 +10,190 @@ You can also execute standard Linux commands as the base image includes a shell **Required** Command to execute. Default `"ansible-playbook"`. -## Example usage +## Examples +### 1. Windows connection via WinRM + +Run a playbook against a Windows target over WinRM. The `pywinrm[credssp]` package is pre-installed in the action's Docker image. + +**Inventory (`inventory.ini`):** +```ini +[windows] +windows-server-01.example.com + +[windows:vars] +ansible_connection=winrm +ansible_winrm_transport=ntlm +ansible_user=Administrator +ansible_password={{ windows_admin_password }} +ansible_winrm_server_cert_validation=ignore +``` + +**Playbook (`win-ping.yml`):** ```yaml -uses: eftechcombr/ansible-cli-github-action@latest -with: - command: "ansible-playbook main.yml" +--- +- name: Verify Windows host connectivity + hosts: windows + tasks: + - name: Test WinRM connection + ansible.windows.win_ping: ``` +**Workflow (`.github/workflows/windows-check.yml`):** +```yaml +name: Windows Connectivity Check + +on: + schedule: + - cron: "0 6 * * *" + workflow_dispatch: + +jobs: + winrm-ping: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Run WinRM ping playbook + uses: eftechcombr/ansible-cli-github-action@master + with: + command: > + ansible-playbook win-ping.yml + -i inventory.ini + -e windows_admin_password=${{ secrets.WINDOWS_ADMIN_PASSWORD }} +``` + +--- + +### 2. Linux connection via SSH + +Connect to Linux targets over SSH using a playbook or ad-hoc commands. + +**Inventory (`inventory.yml`):** +```yaml +all: + hosts: + web-01.example.com: + web-02.example.com: + vars: + ansible_connection: ssh + ansible_user: ubuntu + ansible_ssh_private_key_file: /tmp/ssh_key +``` + +**Playbook (`ping.yml`):** +```yaml +--- +- name: Verify Linux host connectivity + hosts: all + tasks: + - name: Test SSH connection + ansible.builtin.ping: +``` + +**Workflow (`.github/workflows/linux-check.yml`):** +```yaml +name: Linux Connectivity Check + +on: + push: + branches: [main] + workflow_dispatch: + +jobs: + ssh-ping: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Install SSH key + run: | + mkdir -p /tmp + echo "${{ secrets.SSH_PRIVATE_KEY }}" > /tmp/ssh_key + chmod 600 /tmp/ssh_key + + - name: Run playbook + uses: eftechcombr/ansible-cli-github-action@master + with: + command: > + ansible-playbook ping.yml + -i inventory.yml + + - name: Ad-hoc ping test + uses: eftechcombr/ansible-cli-github-action@master + with: + command: ansible all -i inventory.yml -m ping +``` + +--- + +### 3. Connection via GitHub Actions self-hosted runner + +A self-hosted runner is a machine you deploy and manage yourself to run GitHub Actions workflows. Use one when you need access to internal networks or resources not exposed to the public internet. Because this action is Docker-based, the runner only needs Docker installed -- not Ansible or Python. + +Target your runner with a label in `runs-on`: +```yaml +runs-on: [self-hosted, linux, production] +``` + +**Inventory (`inventory.yml`):** +```yaml +all: + hosts: + db-primary.internal.example.com: + db-replica.internal.example.com: + vars: + ansible_connection: ssh + ansible_user: admin + ansible_ssh_private_key_file: /etc/runner/keys/ansible_rsa +``` + +**Playbook (`health-check.yml`):** +```yaml +--- +- name: Database infrastructure health check + hosts: all + tasks: + - name: Ensure PostgreSQL is accepting connections + ansible.builtin.wait_for: + port: 5432 + host: "{{ inventory_hostname }}" + timeout: 10 + + - name: Collect uptime + ansible.builtin.shell: uptime + register: result + + - name: Report uptime + ansible.builtin.debug: + msg: "{{ inventory_hostname }} uptime: {{ result.stdout }}" +``` + +**Workflow (`.github/workflows/internal-health.yml`):** +```yaml +name: Internal Infrastructure Health Check + +on: + schedule: + - cron: "*/15 * * * *" + workflow_dispatch: + +jobs: + health-check: + runs-on: [self-hosted, linux, production] + steps: + - uses: actions/checkout@v4 + + - name: Run health check playbook against internal hosts + uses: eftechcombr/ansible-cli-github-action@master + with: + command: > + ansible-playbook health-check.yml + -i inventory.yml +``` + +Because the workflow runs on a self-hosted runner inside the internal network, it can resolve `db-primary.internal.example.com` -- hosts that would not be reachable from GitHub-hosted runners. The action's Docker image supplies Ansible and all dependencies, so no additional setup is needed on the runner host. + ## Testing Run the smoke test suite locally to verify the Docker image builds and all Ansible tools work correctly: From 513ccb1d53b4209d5c774edb06761382b9e59b16 Mon Sep 17 00:00:00 2001 From: Eduardo Fraga Date: Wed, 29 Jul 2026 14:19:01 -0300 Subject: [PATCH 5/5] docs(readme): add WinRM setup section for Windows nodes --- README.md | 148 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 148 insertions(+) diff --git a/README.md b/README.md index 755cb45..2304074 100644 --- a/README.md +++ b/README.md @@ -193,6 +193,154 @@ jobs: ``` Because the workflow runs on a self-hosted runner inside the internal network, it can resolve `db-primary.internal.example.com` -- hosts that would not be reachable from GitHub-hosted runners. The action's Docker image supplies Ansible and all dependencies, so no additional setup is needed on the runner host. + +## Enabling WinRM on Windows Nodes for Ansible + +WinRM (Windows Remote Management) is Microsoft's implementation of the WS-Management protocol. Ansible uses WinRM to communicate with Windows hosts because Windows does not ship with an SSH server by default. Before you can manage a Windows node with Ansible, WinRM must be enabled and configured on that node. + +### 1. Enable WinRM on the Windows node + +Run the following commands in an **elevated PowerShell** session (Run as Administrator) on each Windows target: + +```powershell +# Quick configuration of WinRM listener +winrm quickconfig -q + +# Enable WSMan CredSSP (required for credential delegation) +Enable-WSManCredSSP -Role Server -Force +``` + +`winrm quickconfig` does the following: +- Starts the WinRM service and sets it to auto-start. +- Creates an HTTP listener on port `5985`. +- Adds a firewall exception for WS-Management traffic. + +### 2. Configure the firewall + +If the firewall rules are not created automatically, or if you need HTTPS access on port `5986`, add them manually: + +```powershell +New-NetFirewallRule -DisplayName "Allow WinRM TCP 5985 (HTTP)" -Direction Inbound -Protocol TCP -LocalPort 5985 -Action Allow +New-NetFirewallRule -DisplayName "Allow WinRM TCP 5986 (HTTPS)" -Direction Inbound -Protocol TCP -LocalPort 5986 -Action Allow +``` + +| Port | Protocol | Use | +|------|----------|-----| +| 5985 | HTTP | Default WinRM listener (can be encrypted via Kerberos/NTLM) | +| 5986 | HTTPS | WinRM over SSL (requires a certificate) | + +### 3. Set up HTTPS with a self-signed certificate (optional but recommended) + +For production environments, use HTTPS with a valid certificate signed by a trusted CA. For testing, you can create a self-signed certificate: + +```powershell +# Create a self-signed certificate +$cert = New-SelfSignedCertificate -CertStoreLocation Cert:\LocalMachine\My ` + -DnsName "$env:COMPUTERNAME" -FriendlyName "WinRM HTTPS" + +# Create the HTTPS listener +New-Item -Path WSMan:\Localhost\Listener -Transport HTTPS -Address * ` + -CertificateThumbprint $cert.Thumbprint -Force +``` + +When using a self-signed certificate, set `ansible_winrm_server_cert_validation=ignore` in your Ansible inventory to bypass certificate validation. + +### 4. Verify WinRM is listening + +On the Windows node itself, confirm the listeners are active: + +```powershell +winrm enumerate winrm/config/Listener +``` + +You should see output similar to: + +``` +Listener + Address = * + Transport = HTTP + Port = 5985 + Enabled = true + ... + +Listener + Address = * + Transport = HTTPS + Port = 5986 + Enabled = true + ... +``` + +### 5. Test WinRM connectivity from the control node + +From the Ansible control node (or the GitHub Actions runner), verify you can reach the Windows host over WinRM **before** running playbooks: + +```bash +# Test HTTP (port 5985) +ansible windows -i inventory.ini -m ansible.windows.win_ping + +# Or use the ansible-cli action locally via Docker +docker run --rm eftechcombr/ansible-cli-github-action \ + ansible windows -i inventory.ini -m ansible.windows.win_ping +``` + +If connectivity fails, check that: +- The Windows node is reachable from the runner (no firewall blocking). +- The WinRM service is running (`Get-Service WinRM`). +- The `ansible_user` has the necessary permissions. +- CredSSP or the chosen transport (NTLM, Kerberos) is correctly configured. + +### 6. Inventory variables for Windows nodes + +When using the `ansible-cli` action with Windows targets, your inventory must include the following variables: + +```ini +[windows] +windows-server-01.example.com + +[windows:vars] +ansible_connection=winrm +ansible_winrm_transport=ntlm +ansible_user=Administrator +ansible_password={{ windows_admin_password }} +ansible_winrm_server_cert_validation=ignore +``` + +| Variable | Value | Purpose | +|----------|-------|---------| +| `ansible_connection` | `winrm` | Tells Ansible to use the WinRM connection plugin instead of SSH. | +| `ansible_winrm_transport` | `ntlm` / `credssp` / `kerberos` | Authentication protocol. NTLM works for most environments; CredSSP is needed for credential delegation (e.g., when the playbook needs to pass credentials to a second host). | +| `ansible_user` | `Administrator` | Local or domain user with admin privileges. | +| `ansible_password` | `{{ password_variable }}` | The user's password (use secrets in CI). | +| `ansible_winrm_server_cert_validation` | `ignore` | Required when using a self-signed certificate. Omit or set to `validate` for CA-signed certs. | + +### 7. Common issues and tips + +**Python fork-safety on macOS** +If you are developing or testing locally on macOS with zsh, you may encounter crashes when Ansible spawns subprocesses. Add this to your `.zshrc`: + +```bash +export OBJC_DISABLE_INITIALIZE_FORK_SAFETY=YES +``` + +**pywinrm is pre-installed** +The `ansible-cli-github-action` Docker image includes `pywinrm[credssp]`, so no additional Python packages are needed when running inside GitHub Actions. For local testing, install it with: + +```bash +python3 -m venv env +source env/bin/activate +pip install pywinrm[credssp] ansible +``` + +**Transport selection** +- **NTLM** — Simplest; works across domains; does not support double-hop (passing credentials to a second machine). +- **CredSSP** — Enables double-hop; requires `Enable-WSManCredSSP -Role Server -Force` on the Windows host. +- **Kerberos** — Best for domain-joined hosts; provides the strongest security but requires additional setup (keytab or `kinit`). + +**Reference links** +- [Ansible Windows WinRM guide](https://docs.ansible.com/ansible/latest/os_guide/windows_winrm.html) +- [Homebrew and Python PEP 668](https://docs.brew.sh/Homebrew-and-Python#pep-668-python312-and-virtual-environments) + ## Testing Run the smoke test suite locally to verify the Docker image builds and all Ansible tools work correctly: