From 1da0c69b15572d93b987e999afaba32f1e0ab6b0 Mon Sep 17 00:00:00 2001 From: caballeto Date: Fri, 10 Apr 2026 15:58:09 +0200 Subject: [PATCH] feat: implement composite GitHub Action for DevHelm CLI setup Adds action.yml (composite), README with usage examples and security guidance, and a self-test workflow covering install, version pinning, env var export, macOS compat, Node skip, and cache behavior. Made-with: Cursor --- .github/workflows/test.yml | 153 +++++++++++++++++++++++++++++++++++++ README.md | 110 ++++++++++++++++++++++++++ action.yml | 104 +++++++++++++++++++++++++ 3 files changed, 367 insertions(+) create mode 100644 .github/workflows/test.yml create mode 100644 README.md create mode 100644 action.yml diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..ddcfdac --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,153 @@ +name: Test Action + +on: + push: + branches: [main] + pull_request: + +jobs: + test-latest: + name: Install latest + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Run setup-devhelm + id: setup + uses: ./ + with: + devhelm-version: latest + + - name: Verify devhelm is on PATH + run: which devhelm + + - name: Verify version output + run: | + VERSION="${{ steps.setup.outputs.devhelm-version }}" + echo "Reported version: $VERSION" + [[ "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+ ]] || { echo "::error::Invalid version format: $VERSION"; exit 1; } + + - name: Verify CLI runs + run: devhelm --help + + - name: Verify validate works without API + run: | + devhelm init --force + devhelm validate devhelm.yml + + test-pinned: + name: Install pinned version + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Run setup-devhelm + id: setup + uses: ./ + with: + devhelm-version: '0.1.4' + + - name: Verify exact version + run: | + EXPECTED="0.1.4" + ACTUAL="${{ steps.setup.outputs.devhelm-version }}" + echo "Expected: $EXPECTED, Got: $ACTUAL" + [[ "$ACTUAL" == "$EXPECTED" ]] || { echo "::error::Version mismatch: expected $EXPECTED, got $ACTUAL"; exit 1; } + + test-env-export: + name: Environment variables + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Run setup-devhelm with env inputs + uses: ./ + with: + api-token: test-token-value + api-url: https://api.staging.devhelm.io + org-id: '42' + workspace-id: '7' + + - name: Verify DEVHELM_API_TOKEN + run: | + [[ "$DEVHELM_API_TOKEN" == "test-token-value" ]] || { echo "::error::DEVHELM_API_TOKEN not set"; exit 1; } + + - name: Verify DEVHELM_API_URL + run: | + [[ "$DEVHELM_API_URL" == "https://api.staging.devhelm.io" ]] || { echo "::error::DEVHELM_API_URL not set"; exit 1; } + + - name: Verify DEVHELM_ORG_ID + run: | + [[ "$DEVHELM_ORG_ID" == "42" ]] || { echo "::error::DEVHELM_ORG_ID not set"; exit 1; } + + - name: Verify DEVHELM_WORKSPACE_ID + run: | + [[ "$DEVHELM_WORKSPACE_ID" == "7" ]] || { echo "::error::DEVHELM_WORKSPACE_ID not set"; exit 1; } + + test-no-env-when-empty: + name: No env vars when inputs are empty + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Run setup-devhelm without auth inputs + uses: ./ + + - name: Verify no DEVHELM_API_TOKEN + run: | + [[ -z "$DEVHELM_API_TOKEN" ]] || { echo "::error::DEVHELM_API_TOKEN should not be set"; exit 1; } + + - name: Verify no DEVHELM_ORG_ID + run: | + [[ -z "$DEVHELM_ORG_ID" ]] || { echo "::error::DEVHELM_ORG_ID should not be set"; exit 1; } + + test-skip-node-setup: + name: Skip built-in Node setup + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-node@v4 + with: + node-version: 22 + + - name: Run setup-devhelm without auto Node + id: setup + uses: ./ + with: + node-version: '' + + - name: Verify Node version unchanged + run: | + NODE_V=$(node -v) + echo "Node version: $NODE_V" + [[ "$NODE_V" == v22.* ]] || { echo "::error::Expected Node 22.x, got $NODE_V"; exit 1; } + + - name: Verify devhelm works + run: devhelm --help + + test-macos: + name: Install on macOS + runs-on: macos-latest + steps: + - uses: actions/checkout@v4 + + - name: Run setup-devhelm + id: setup + uses: ./ + + - name: Verify CLI runs + run: devhelm --help + + test-cache: + name: Cache behavior + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Run setup-devhelm + id: setup + uses: ./ + + - name: Report cache status + run: echo "Cache hit = ${{ steps.setup.outputs.cache-hit }}" diff --git a/README.md b/README.md new file mode 100644 index 0000000..cb2e984 --- /dev/null +++ b/README.md @@ -0,0 +1,110 @@ +# setup-devhelm + +GitHub Action to install and configure the [DevHelm CLI](https://www.npmjs.com/package/devhelm) for monitoring-as-code workflows. + +## Usage + +### Basic — validate config on PRs + +```yaml +- uses: actions/checkout@v4 +- uses: devhelmhq/setup-devhelm@v1 +- run: devhelm validate +``` + +### Pin a specific version + +```yaml +- uses: devhelmhq/setup-devhelm@v1 + with: + devhelm-version: '0.1.4' +``` + +### Authenticate and verify + +```yaml +- uses: devhelmhq/setup-devhelm@v1 + with: + api-token: ${{ secrets.DEVHELM_API_TOKEN }} + org-id: ${{ vars.DEVHELM_ORG_ID }} + workspace-id: ${{ vars.DEVHELM_WORKSPACE_ID }} + verify-connection: true +``` + +### Full CI/CD example + +```yaml +name: DevHelm Monitoring as Code + +on: + push: + branches: [main] + paths: ['devhelm.yml'] + pull_request: + paths: ['devhelm.yml'] + +jobs: + validate: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: devhelmhq/setup-devhelm@v1 + - run: devhelm validate + + deploy: + if: github.ref == 'refs/heads/main' + needs: validate + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: devhelmhq/setup-devhelm@v1 + with: + api-token: ${{ secrets.DEVHELM_API_TOKEN }} + org-id: ${{ vars.DEVHELM_ORG_ID }} + workspace-id: ${{ vars.DEVHELM_WORKSPACE_ID }} + verify-connection: true + - run: devhelm deploy devhelm.yml + - run: devhelm status --output json +``` + +## Inputs + +| Input | Required | Default | Description | +|-------|----------|---------|-------------| +| `devhelm-version` | No | `latest` | CLI version — exact semver (e.g. `0.1.4`) or `latest` | +| `api-token` | No | — | DevHelm API token. Pass via `secrets.DEVHELM_API_TOKEN` | +| `api-url` | No | — | API base URL (default: `https://api.devhelm.io`) | +| `org-id` | No | — | Organization ID for multi-org accounts | +| `workspace-id` | No | — | Workspace ID for multi-workspace accounts | +| `verify-connection` | No | `false` | Run `devhelm auth me` after setup to verify credentials | +| `node-version` | No | `20` | Node.js version. Set to `''` to skip if you manage Node yourself | + +## Outputs + +| Output | Description | +|--------|-------------| +| `devhelm-version` | Installed CLI version (e.g. `0.1.4`) | +| `cache-hit` | `true` if the npm cache was restored | + +## How it works + +1. **Node.js** — ensures Node >= 18 is available (auto-installs via `actions/setup-node` unless you set `node-version: ''`) +2. **Cache** — restores `~/.npm` cache keyed on OS + CLI version +3. **Install** — runs `npm install -g devhelm@` +4. **Environment** — exports `DEVHELM_API_TOKEN`, `DEVHELM_API_URL`, `DEVHELM_ORG_ID`, `DEVHELM_WORKSPACE_ID` for all subsequent steps +5. **Verify** — optionally runs `devhelm auth me` to fail fast on bad credentials + +## Security + +- **Never hardcode tokens** in workflow files. Use [GitHub Secrets](https://docs.github.com/en/actions/security-for-github-actions/security-guides/using-secrets-in-github-actions). +- The `api-token` input is passed through environment variables, never logged. +- For org/workspace IDs (non-sensitive), use [GitHub Variables](https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/store-information-in-variables). + +## Requirements + +- **Node.js >= 18** — the action auto-installs Node 20 by default. Set `node-version: ''` if your workflow already provides Node. +- **npm** — ships with Node.js. + +## License + +MIT — see [LICENSE](LICENSE). diff --git a/action.yml b/action.yml new file mode 100644 index 0000000..7858dee --- /dev/null +++ b/action.yml @@ -0,0 +1,104 @@ +name: 'Setup DevHelm CLI' +description: 'Install and configure the DevHelm CLI for monitoring-as-code workflows' +author: 'DevHelm' + +branding: + icon: terminal + color: blue + +inputs: + devhelm-version: + description: 'CLI version to install — exact semver (e.g. "0.1.4") or "latest"' + required: false + default: 'latest' + api-token: + description: 'DevHelm API token (recommended: pass via secrets.DEVHELM_API_TOKEN)' + required: false + api-url: + description: 'DevHelm API base URL (default: https://api.devhelm.io)' + required: false + org-id: + description: 'Organization ID for multi-org accounts' + required: false + workspace-id: + description: 'Workspace ID for multi-workspace accounts' + required: false + verify-connection: + description: 'Run "devhelm auth me" after setup to verify credentials work' + required: false + default: 'false' + node-version: + description: 'Node.js version to use if actions/setup-node was not called before this action. Set to empty string to skip auto-setup.' + required: false + default: '20' + +outputs: + devhelm-version: + description: 'Installed CLI version (semver only, e.g. "0.1.4")' + value: ${{ steps.install.outputs.version }} + cache-hit: + description: 'Whether the npm cache was restored' + value: ${{ steps.cache.outputs.cache-hit }} + +runs: + using: composite + steps: + - name: Ensure Node.js is available + if: inputs.node-version != '' + uses: actions/setup-node@v4 + with: + node-version: ${{ inputs.node-version }} + + - name: Validate Node.js + shell: bash + run: | + if ! command -v node &>/dev/null; then + echo "::error::Node.js is required. Either set input 'node-version' or run actions/setup-node before this action." + exit 1 + fi + NODE_MAJOR=$(node -v | sed 's/v\([0-9]*\).*/\1/') + if (( NODE_MAJOR < 18 )); then + echo "::error::DevHelm CLI requires Node.js >= 18 (found $(node -v))" + exit 1 + fi + + - name: Restore npm cache + id: cache + uses: actions/cache@v4 + with: + path: ~/.npm + key: ${{ runner.os }}-devhelm-${{ inputs.devhelm-version }} + + - name: Install DevHelm CLI + id: install + shell: bash + run: | + echo "::group::Installing devhelm@${{ inputs.devhelm-version }}" + npm install -g devhelm@${{ inputs.devhelm-version }} 2>&1 + echo "::endgroup::" + + RAW=$(devhelm --version) + VERSION=$(echo "$RAW" | awk '{print $1}' | sed 's|.*/||') + echo "version=$VERSION" >> "$GITHUB_OUTPUT" + echo "Installed devhelm $VERSION" + + - name: Export environment variables + shell: bash + env: + INPUT_TOKEN: ${{ inputs.api-token }} + INPUT_URL: ${{ inputs.api-url }} + INPUT_ORG: ${{ inputs.org-id }} + INPUT_WS: ${{ inputs.workspace-id }} + run: | + [[ -n "$INPUT_TOKEN" ]] && echo "DEVHELM_API_TOKEN=$INPUT_TOKEN" >> "$GITHUB_ENV" + [[ -n "$INPUT_URL" ]] && echo "DEVHELM_API_URL=$INPUT_URL" >> "$GITHUB_ENV" + [[ -n "$INPUT_ORG" ]] && echo "DEVHELM_ORG_ID=$INPUT_ORG" >> "$GITHUB_ENV" + [[ -n "$INPUT_WS" ]] && echo "DEVHELM_WORKSPACE_ID=$INPUT_WS" >> "$GITHUB_ENV" + exit 0 + + - name: Verify connection + if: inputs.verify-connection == 'true' && inputs.api-token != '' + shell: bash + run: | + echo "Verifying DevHelm API connection..." + devhelm auth me --output json