From 2a560d85cb44aeb6c7a1f74d1b2989670b860ae8 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 29 Oct 2026 17:49:16 +0000 Subject: [PATCH 1/7] =?UTF-8?q?feat(devcontainer-features-common-utils):?= =?UTF-8?q?=20=E2=9C=A8=20add=20GitLab=20CI=20fragments=20for=20every=20Gi?= =?UTF-8?q?tHub=20workflow?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit One `_...gitlab-ci.yml` stub per workflow, merged into the dotted `.gitlab-ci.yml`. tomgrv/actions steps run through `zz-use -x`; the ones that call the GitHub API are pending manual jobs. Vendors the GitLab github-actions-to-gitlab-ci skill, and adds a repo-wide bats guard. Co-Authored-By: Claude Sonnet 5.5 Claude-Session: https://claude.ai/code/session_01XWCx6ifQFkyqUFNvkGPo4o --- .../github-actions-to-gitlab-ci/LICENSE | 21 + .../github-actions-to-gitlab-ci/SKILL.md | 370 ++++++ .../references/marketplace-actions.md | 1042 +++++++++++++++++ .../references/syntax-mapping.md | 906 ++++++++++++++ .../references/validator.md | 91 ++ .../scripts/validate.sh | 206 ++++ .claude/skills/github-actions-to-gitlab-ci | 1 + .github/skills/github-actions-to-gitlab-ci | 1 + src/ai-coding/README.md | 7 + .../github-actions-to-gitlab-ci/LICENSE | 21 + .../github-actions-to-gitlab-ci/SKILL.md | 370 ++++++ .../references/marketplace-actions.md | 1042 +++++++++++++++++ .../references/syntax-mapping.md | 906 ++++++++++++++ .../references/validator.md | 91 ++ .../scripts/validate.sh | 206 ++++ .../skills/github-actions-to-gitlab-ci | 1 + .../stubs/_review-deepseek...gitlab-ci.yml | 23 + src/common-utils/README.md | 8 + .../stubs/_test-workspaces...gitlab-ci.yml | 54 + .../stubs/_validate-pr-lock...gitlab-ci.yml | 20 + .../stubs/_zz-use...gitlab-ci.yml | 21 + src/githooks/README.md | 7 + .../stubs/_clean-branches...gitlab-ci.yml | 19 + .../stubs/_configure-github...gitlab-ci.yml | 21 + .../stubs/_update-labels...gitlab-ci.yml | 20 + .../stubs/_validate-pr-format...gitlab-ci.yml | 37 + .../stubs/_validate-pr-secret...gitlab-ci.yml | 20 + ...alidate-pr-vulnerabilities...gitlab-ci.yml | 21 + src/gitutils/README.md | 6 + .../_manage-monorepo-merge...gitlab-ci.yml | 43 + .../_manage-monorepo-split...gitlab-ci.yml | 45 + .../_manage-orphan-branches...gitlab-ci.yml | 23 + .../stubs/_release-prod...gitlab-ci.yml | 22 + src/larasets/README.md | 8 + src/larasets/stubs/_fix-php...gitlab-ci.yml | 34 + .../stubs/_validate-pr-php...gitlab-ci.yml | 59 + .../_validate-pr-security...gitlab-ci.yml | 12 + .../stubs/_zz-use-php...gitlab-ci.yml | 23 + src/minikube/README.md | 6 + .../stubs/_validate-pr-docker...gitlab-ci.yml | 15 + stubs/_update-features...gitlab-ci.yml | 38 + tests/README.md | 4 + tests/gitlab-fragments.bats | 75 ++ 43 files changed, 5966 insertions(+) create mode 100644 .agents/skills/github-actions-to-gitlab-ci/LICENSE create mode 100644 .agents/skills/github-actions-to-gitlab-ci/SKILL.md create mode 100644 .agents/skills/github-actions-to-gitlab-ci/references/marketplace-actions.md create mode 100644 .agents/skills/github-actions-to-gitlab-ci/references/syntax-mapping.md create mode 100644 .agents/skills/github-actions-to-gitlab-ci/references/validator.md create mode 100755 .agents/skills/github-actions-to-gitlab-ci/scripts/validate.sh create mode 120000 .claude/skills/github-actions-to-gitlab-ci create mode 120000 .github/skills/github-actions-to-gitlab-ci create mode 100644 src/ai-coding/stubs/.agents/skills/github-actions-to-gitlab-ci/LICENSE create mode 100644 src/ai-coding/stubs/.agents/skills/github-actions-to-gitlab-ci/SKILL.md create mode 100644 src/ai-coding/stubs/.agents/skills/github-actions-to-gitlab-ci/references/marketplace-actions.md create mode 100644 src/ai-coding/stubs/.agents/skills/github-actions-to-gitlab-ci/references/syntax-mapping.md create mode 100644 src/ai-coding/stubs/.agents/skills/github-actions-to-gitlab-ci/references/validator.md create mode 100755 src/ai-coding/stubs/.agents/skills/github-actions-to-gitlab-ci/scripts/validate.sh create mode 120000 src/ai-coding/stubs/.github/skills/github-actions-to-gitlab-ci create mode 100644 src/ai-coding/stubs/_review-deepseek...gitlab-ci.yml create mode 100644 src/common-utils/stubs/_test-workspaces...gitlab-ci.yml create mode 100644 src/common-utils/stubs/_validate-pr-lock...gitlab-ci.yml create mode 100644 src/common-utils/stubs/_zz-use...gitlab-ci.yml create mode 100644 src/githooks/stubs/_clean-branches...gitlab-ci.yml create mode 100644 src/githooks/stubs/_configure-github...gitlab-ci.yml create mode 100644 src/githooks/stubs/_update-labels...gitlab-ci.yml create mode 100644 src/githooks/stubs/_validate-pr-format...gitlab-ci.yml create mode 100644 src/githooks/stubs/_validate-pr-secret...gitlab-ci.yml create mode 100644 src/githooks/stubs/_validate-pr-vulnerabilities...gitlab-ci.yml create mode 100644 src/gitutils/stubs/_manage-monorepo-merge...gitlab-ci.yml create mode 100644 src/gitutils/stubs/_manage-monorepo-split...gitlab-ci.yml create mode 100644 src/gitutils/stubs/_manage-orphan-branches...gitlab-ci.yml create mode 100644 src/gitutils/stubs/_release-prod...gitlab-ci.yml create mode 100644 src/larasets/stubs/_fix-php...gitlab-ci.yml create mode 100644 src/larasets/stubs/_validate-pr-php...gitlab-ci.yml create mode 100644 src/larasets/stubs/_validate-pr-security...gitlab-ci.yml create mode 100644 src/larasets/stubs/_zz-use-php...gitlab-ci.yml create mode 100644 src/minikube/stubs/_validate-pr-docker...gitlab-ci.yml create mode 100644 stubs/_update-features...gitlab-ci.yml create mode 100644 tests/gitlab-fragments.bats diff --git a/.agents/skills/github-actions-to-gitlab-ci/LICENSE b/.agents/skills/github-actions-to-gitlab-ci/LICENSE new file mode 100644 index 00000000..a724ed88 --- /dev/null +++ b/.agents/skills/github-actions-to-gitlab-ci/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026-present GitLab Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/.agents/skills/github-actions-to-gitlab-ci/SKILL.md b/.agents/skills/github-actions-to-gitlab-ci/SKILL.md new file mode 100644 index 00000000..c7f4493e --- /dev/null +++ b/.agents/skills/github-actions-to-gitlab-ci/SKILL.md @@ -0,0 +1,370 @@ +--- +name: github-actions-to-gitlab-ci +description: Use this skill whenever the user wants to migrate, translate, or convert GitHub Actions workflows to GitLab CI/CD pipelines. This includes converting `.github/workflows/*.yml` files to `.gitlab-ci.yml`, comparing GitHub Actions and GitLab CI/CD syntax, mapping GitHub Actions marketplace actions to GitLab equivalents, or evaluating what a workflow would look like in GitLab. Use this skill if the user mentions both GitHub Actions and GitLab CI in the same context, asks about migrating from GitHub to GitLab, or shows you a workflow YAML and asks how it would work in GitLab. Even if the user does not explicitly say "migrate", if they are exploring GitLab CI/CD coming from a GitHub Actions background, use this skill. +--- + +# GitHub Actions to GitLab CI/CD Migration + +This skill translates GitHub Actions workflows to GitLab CI/CD pipelines. It handles single-file conversions, maps marketplace actions to GitLab equivalents, and produces a migration report categorizing what translates cleanly, what needs approximation, and what requires human review. Use this skill when a user shows you a GitHub workflow and asks how it would work in GitLab, or when they are evaluating GitLab CI/CD coming from a GitHub Actions background. + +## When to use this skill + +Use this skill whenever the user mentions GitHub Actions and GitLab CI/CD in the same context. Concrete triggers include: + +- User shows you a `.github/workflows/*.yml` file and asks "how would this work in GitLab?" +- User asks to migrate, convert, or translate a GitHub Actions workflow +- User compares GitHub Actions syntax to GitLab CI/CD syntax +- User asks about GitLab equivalents for specific GitHub Actions (e.g., "what replaces actions/checkout?") +- User is evaluating GitLab CI/CD as an alternative to GitHub Actions +- User mentions both platforms in a single question, even if they don't explicitly say "migrate" +- User pastes a GitHub workflow and asks for the GitLab equivalent +- User asks how to set up a CI/CD pipeline in GitLab after using GitHub Actions + +Even if the user does not explicitly say "migrate" or "convert", if they are exploring GitLab CI/CD coming from a GitHub Actions background, use this skill. + +## What this skill does well + +- **Real CI/CD workflows**: build, test, deploy, lint, release workflows translate cleanly with high confidence +- **Single-file translations**: one `.github/workflows/*.yml` to one `.gitlab-ci.yml` +- **Marketplace action mapping**: top 50 GitHub Actions have curated GitLab equivalents +- **Caching and artifacts**: native GitLab features map directly from GitHub Actions equivalents +- **Matrix builds**: GitHub's `matrix:` translates to GitLab's `parallel:matrix:` with cleaner syntax +- **Conditional execution**: `if:` conditions translate to `rules:if:` with semantic preservation +- **Migration reporting**: every source line categorized as Translated, Approximated, Needs Review, or Lost +- **Docker-based workflows**: docker/build-push-action and related actions have clear GitLab equivalents +- **Secrets and variables**: GitHub secrets map to GitLab CI/CD variables with clear setup instructions + +## Known limitations + +- **Repo automation workflows**: GitHub Actions like `actions/stale`, `actions/first-interaction`, and `actions/labeler` have no GitLab CI/CD equivalent because they respond to issue/MR/comment events. GitLab CI/CD runs only on git events (push, tag, MR, schedule, manual, API). For these workflows, recommend `gitlab-triage` on a schedule or webhooks + external service. +- **Composite actions**: not supported in v1. Composite actions (`.github/actions/*/action.yml`) require manual translation. See "Composite actions" in `references/syntax-mapping.md` for how to pick the target. Full composite action translation is a candidate for a future version. +- **Reusable workflows with inputs/outputs**: GitHub's `inputs:` and `outputs:` in reusable workflows have no direct GitLab equivalent. Recommend `include:` with variable substitution or child pipelines. +- **Permissions**: GitHub's `permissions:` keyword has no per-job equivalent in GitLab. Workaround: configure at project level (CI/CD token access, project access tokens stored as masked variables). +- **GitHub Enterprise specifics**: this skill targets github.com. GitHub Enterprise Server may have different API endpoints or features. +- **Workflow dispatch inputs**: GitHub's `workflow_dispatch.inputs` translates to GitLab's `spec:inputs` (pipeline-level). The translation requires three pieces: `spec:inputs` for the typed input schema, `workflow:rules:` to gate the pipeline to manual/API triggers only, and a default value on every input (auto-triggered pipelines cannot prompt for inputs and will fail without defaults). See `references/syntax-mapping.md` for a full before/after example. +- **Step outputs**: GitHub's `steps.*.outputs` have no direct GitLab equivalent. Workaround: use artifacts or environment files. + +## High-level workflow + +When invoked, follow this sequence: + +### 1. Read the source workflow + +Read the user's `.github/workflows/*.yml` file. If they paste it inline, work from the paste. If they reference a file path, read it from their repository. Confirm you have the complete workflow before proceeding. Note the workflow name, triggers, and overall structure. + +### 2. Classify the workflow + +Determine which category the workflow falls into: + +- **Real CI/CD workflow** (build, test, deploy, lint, release, publish): high-confidence translation path. Use the references to translate keyword by keyword. These workflows typically have `jobs:` that run `script:` commands and produce artifacts. Examples: Node.js test suite, Docker image build and push, Python package release. +- **Repo automation workflow** (stale, greetings, labeler, dependabot-style, issue triage): limited GitLab CI/CD support. Document the constraint upfront. Suggest alternatives: `gitlab-triage` on a schedule, webhooks + external service, or project access tokens. + +### 3. Translate + +Walk through the workflow keyword by keyword. For each line: + +1. Look up the keyword in `references/syntax-mapping.md` (e.g., `env:`, `jobs:`, `steps:`, `uses:`, `if:`, `runs-on:`, `needs:`) +2. For `uses:` lines, look up the action in `references/marketplace-actions.md` +3. If the lookup fails, apply the "Pattern: actions not in this list" decision tree from marketplace-actions.md +4. Note items that need human review (e.g., complex `if:` conditions, custom actions, permissions) +5. Preserve the logical flow and intent of the original workflow +6. Maintain the same job dependencies and execution order + +### 4. Produce a migration report + +The migration report is the trust mechanism. Categorize every line of the source workflow as: + +- **Translated**: clean 1:1 mapping, no semantic loss +- **Approximated**: GitLab equivalent exists but with caveats (note the caveat) +- **Needs review**: no equivalent or significant semantic differences (the user must decide) +- **Lost**: GitHub feature with no GitLab analogue (e.g., step `name:` labels, `defaults:run:` shell selection) + +Format the report as a markdown table the user can copy into their migration tracking spreadsheet. Include columns: Source Line, Category, GitLab Equivalent, Notes. + +### 4.5. Validate the translated `.gitlab-ci.yml` + +Before presenting the final translation, run the bundled validator. It catches mechanical errors before they reach the user: unknown keywords, malformed YAML, invalid `include:` / `extends:` / `rules:` expressions. + +Invoke it via `bash` from the skill bundle directory: + +```bash +bash /scripts/validate.sh /path/to/translated.gitlab-ci.yml +``` + +The script prefers `glci lint` (offline, no token, deeper parse that resolves `include:`, `extends:`, `!reference`, `default:`, and `workflow:`) and falls back to `glab ci lint`. The validator does not catch semantic or runtime errors; those remain the user's responsibility, surfaced via the migration report. + +Read the script's stdout, not just its exit code: it announces which backend ran. If you see "falling back to glab ci lint" and the user's remote is on GitHub, that fallback will likely produce a 404. Verify `glci` is genuinely absent with `ls -l /usr/local/bin/glci /opt/homebrew/bin/glci ~/bin/glci ~/.local/bin/glci 2>/dev/null` before treating the glab failure as authoritative. + +If anything goes wrong (script not found, permission denied, neither backend installed, no shell access, no network, validation fails, or the `glab` backend needs a project), see [`references/validator.md`](./references/validator.md) for the full handling guide. + +### 5. Suggest GitLab improvements (the upgrade moment) + +After producing the basic translation, opportunistically suggest GitLab features that would improve on the GitHub original. Only suggest these when they actually improve the user's specific workflow: + +- **DAG with `needs:`**: eliminate stage barriers for faster pipelines (jobs run as soon as dependencies complete, not waiting for stage boundaries) +- **`parallel:matrix:`**: cleaner matrix syntax than GitHub's matrix, with better variable scoping +- **`rules:changes:`**: monorepo path-based job filtering (run jobs only when specific files change) +- **Child pipelines**: break very large workflows into smaller, reusable pipelines with `trigger:` +- **`include:`**: share config across projects and workflows +- **GitLab CI templates**: Auto DevOps, Security Scanning, Terraform, SAST, DAST, etc. (no GitHub Actions equivalent) +- **Resource groups**: serialize job execution to prevent concurrent deployments to the same environment +- **Protected environments**: gate deployments by approval rules and environment-specific variables + +## Reference files + +Load these references when translating: + +- 📋 [`references/syntax-mapping.md`](./references/syntax-mapping.md) — Load when translating workflow keywords (jobs, stages, steps, rules, variables, artifacts, caching, matrix, if conditions, runs-on, needs, etc.) +- 🧩 [`references/marketplace-actions.md`](./references/marketplace-actions.md) — Load when you encounter a `uses:` line referencing a marketplace action (actions/checkout, actions/setup-node, docker/build-push-action, codecov/codecov-action, etc.) +- 🛠️ [`references/validator.md`](./references/validator.md) — Load when running `scripts/validate.sh` produces an error, when neither backend is installed, when the agent has no shell or network access, or when you need to handle a validation failure. + +## Self-check rubric + +Before producing the final translation, verify: + +- [ ] Did I remove `actions/checkout` (no equivalent, GitLab clones automatically)? +- [ ] Did I translate `runs-on:` to either an `image:` choice or `tags:` for runner selection? +- [ ] Did I translate `env:` and secrets to `variables:` (with masked variables noted as a manual setup step)? +- [ ] Did I handle every `uses:` line (looked up in marketplace-actions.md or applied the fallback pattern)? +- [ ] Did I translate `if:` conditions to `rules:if:` (NOT a string substitution; semantics differ)? +- [ ] Did I handle artifacts (`upload-artifact` -> `artifacts:`, `download-artifact` -> automatic between stages or `needs:artifacts:`)? +- [ ] Did I document `permissions:` items as manual project-level setup (no per-job equivalent)? +- [ ] Did I translate `concurrency:` to `resource_group:` and `interruptible:` where applicable? +- [ ] Did I produce a migration report categorizing every source item? +- [ ] Did I flag items that need human review explicitly? +- [ ] Did I suggest GitLab-only features that would improve the workflow (only where they apply)? + +## Output format for the user + +When the translation is complete, structure your response as: + +1. **Brief overview** (1-2 sentences: workflow name, complexity, overall verdict) +2. **The translated `.gitlab-ci.yml`** in a fenced code block, ready to copy +3. **Migration report** (markdown table with source line, category, GitLab equivalent, notes) +4. **Manual setup steps** (numbered list, e.g., "Configure CI/CD variable X", "Set schedule in CI/CD > Schedules") +5. **Suggested GitLab improvements** (bulleted, optional, only if applicable) +6. **Honest caveats** (any "needs review" items called out plainly) + +## Common translation patterns + +### Environment variables and secrets + +GitHub: +```yaml +env: + REGISTRY: ghcr.io + IMAGE_NAME: ${{ github.repository }} +jobs: + build: + env: + BUILD_ENV: production + steps: + - run: echo $REGISTRY +``` + +GitLab: +```yaml +variables: + REGISTRY: ghcr.io + IMAGE_NAME: $CI_PROJECT_PATH +build: + variables: + BUILD_ENV: production + script: + - echo $REGISTRY +``` + +Key differences: GitLab uses `$VAR_NAME` (not `${{ }}`), and secrets are CI/CD variables (configured in UI, not in YAML). Masked variables are set in Project > Settings > CI/CD > Variables. + +### Conditional execution + +GitHub: +```yaml +jobs: + deploy: + if: github.ref == 'refs/heads/main' + runs-on: ubuntu-latest +``` + +GitLab: +```yaml +deploy: + rules: + - if: $CI_COMMIT_BRANCH == "main" + image: ubuntu:latest +``` + +Key differences: `if:` becomes `rules:if:`, and the condition syntax changes from `${{ }}` expressions to shell-like expressions. Common variables: `$CI_COMMIT_BRANCH`, `$CI_COMMIT_TAG`, `$CI_PIPELINE_SOURCE`. When translating GitHub context expressions (`${{ github.X }}`, `${{ runner.X }}`, `${{ inputs.X }}`, etc.), look up the GitLab predefined variable equivalent and substitute it directly. Use the mapping table in `references/syntax-mapping.md` (`### github.* context expressions` section) for common expressions. For expressions not in the table, consult the canonical reference at https://docs.gitlab.com/ci/variables/predefined_variables/ and pick the closest match. Flag mappings with no GitLab equivalent (e.g., `github.run_attempt`, `runner.arch`) as Needs Review in the migration report. + +### Matrix builds + +GitHub: +```yaml +jobs: + test: + strategy: + matrix: + node-version: [14, 16, 18] + os: [ubuntu-latest, macos-latest] +``` + +GitLab: +```yaml +test: + parallel: + matrix: + - NODE_VERSION: ["14", "16", "18"] + OS: ["ubuntu-latest", "macos-latest"] +``` + +Key differences: GitLab's `parallel:matrix:` is cleaner and uses environment variables directly. Variables are automatically available in the job. + +### Artifacts and caching + +GitHub: +```yaml +- uses: actions/upload-artifact@v3 + with: + name: build-output + path: dist/ +- uses: actions/cache@v3 + with: + path: node_modules + key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }} +``` + +GitLab: +```yaml +artifacts: + name: build-output + paths: + - dist/ +cache: + key: $CI_COMMIT_REF_SLUG + paths: + - node_modules/ +``` + +Key differences: GitLab artifacts are automatic between stages. Cache keys use GitLab variables. No explicit download step needed. + +### Docker image building + +GitHub: +```yaml +- uses: docker/build-push-action@v4 + with: + push: true + tags: ghcr.io/user/image:latest + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} +``` + +GitLab: +```yaml +script: + - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY + - docker build -t $CI_REGISTRY_IMAGE:latest . + - docker push $CI_REGISTRY_IMAGE:latest +``` + +Key differences: GitLab provides built-in registry variables (`$CI_REGISTRY`, `$CI_REGISTRY_IMAGE`, `$CI_REGISTRY_USER`, `$CI_REGISTRY_PASSWORD`). Use `services: docker:dind` for Docker-in-Docker. + +## Style guide + +- Imperative, direct, factual +- No em dashes (use commas, periods, colons instead) +- Tone neutral, not GitLab-evangelical +- Acknowledge GitHub Actions as a legitimate prior choice +- When something cannot translate, say so plainly and suggest alternatives +- Avoid "MUST" and "NEVER" in all caps; use imperative form instead + +## Critical facts (do not get wrong) + +- `actions/checkout` has NO equivalent. GitLab clones the repository automatically. +- GitLab CI/CD pipelines run on git events only (push, tag, MR, schedule, manual, API). They do NOT run on issue/MR/comment/discussion events. +- `permissions:` has no per-job equivalent in GitLab. Use project-level CI/CD token access settings or project access tokens stored as masked variables. +- Schedules are configured in the GitLab UI under CI/CD > Schedules, not in YAML. Use `rules:if: $CI_PIPELINE_SOURCE == "schedule"` to gate jobs. +- Secrets in GitLab are CI/CD variables (masked, protected). Access syntax is `$VAR_NAME`, not `${{ secrets.NAME }}`. +- `if:` syntax differs significantly. NOT a direct string substitution. GitHub uses `${{ }}` expressions; GitLab uses shell-like expressions in `rules:if:`. +- For workflows that handle issues/MRs/comments (stale, greetings, labelers): there is no GitLab CI/CD equivalent. Recommend `gitlab-triage` on a schedule, or webhooks + an external service. +- `needs:` in GitLab has stricter rules around stages than GitHub's `needs:`. A job can only depend on jobs in earlier stages unless you use DAG mode. +- `services:` in GitLab is equivalent to GitHub's `services:` for running sidecar containers (databases, registries, etc.). +- `artifacts:` in GitLab is equivalent to GitHub's `upload-artifact` and `download-artifact` combined. +- `before_script:` and `after_script:` are GitLab equivalents for setup and cleanup steps. +- `tags:` in GitLab selects runners by label, equivalent to GitHub's `runs-on:` for self-hosted runners. + +## Workflow structure comparison + +### GitHub Actions structure + +```yaml +name: CI +on: + push: + branches: [main] + pull_request: + branches: [main] + +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 18 + - run: npm install + - run: npm test +``` + +### GitLab CI/CD equivalent + +```yaml +stages: + - test + +test: + stage: test + image: node:18 + script: + - npm install + - npm test +``` + +Key structural differences: GitLab uses `stages:` to group jobs, not `jobs:` with triggers. Triggers are implicit (every push runs the pipeline). Use `rules:` to filter which jobs run. + +## Troubleshooting common issues + +**Issue: "My workflow uses `actions/checkout` but I don't see it in the GitLab version"** +Solution: GitLab clones the repository automatically. Remove the checkout step entirely. The working directory is already populated with the repository contents. + +**Issue: "My `if:` condition doesn't work in GitLab"** +Solution: GitHub's `if:` uses `${{ }}` expression syntax. GitLab's `rules:if:` uses shell-like expressions. Common conversions: +- `github.ref == 'refs/heads/main'` -> `$CI_COMMIT_BRANCH == "main"` +- `github.event_name == 'pull_request'` -> `$CI_PIPELINE_SOURCE == "merge_request_event"` +- `contains(github.event.head_commit.message, '[skip ci]')` -> `$CI_COMMIT_MESSAGE =~ /\[skip ci\]/` + +**Issue: "My secrets aren't available in GitLab"** +Solution: GitLab CI/CD variables are configured in the UI (Project > Settings > CI/CD > Variables), not in YAML. Create masked variables for sensitive data. Access them with `$VAR_NAME` in scripts. + +**Issue: "My matrix build isn't working"** +Solution: GitLab's `parallel:matrix:` syntax differs from GitHub's. Use environment variable names (uppercase) and quote all values. Variables are automatically available in the job. + +**Issue: "I need to run a job only on schedule"** +Solution: Use `rules:if: $CI_PIPELINE_SOURCE == "schedule"`. Configure the schedule in Project > CI/CD > Schedules. + +## Next steps after translation + +1. **Validate the YAML**: Use `gitlab-ci-lint` or the GitLab UI (CI/CD > Pipelines > CI/CD Lint) to check syntax. +2. **Set up CI/CD variables**: Configure secrets and environment variables in Project > Settings > CI/CD > Variables. +3. **Configure runners**: Ensure you have runners available (shared runners or self-hosted). Check Project > Settings > CI/CD > Runners. +4. **Test the pipeline**: Push a commit to trigger the pipeline. Monitor the pipeline in CI/CD > Pipelines. +5. **Iterate**: Adjust the `.gitlab-ci.yml` based on pipeline results. Common issues: missing dependencies, incorrect image, permission errors. + +## When to ask for help + +- If a GitHub Actions marketplace action has no clear GitLab equivalent, check the "Pattern: actions not in this list" section of `references/marketplace-actions.md`. +- If a workflow uses composite actions or reusable workflows, manual translation may be needed. See "Composite actions" in `references/syntax-mapping.md` for how to pick the target. +- If a workflow responds to issue/MR/comment events, recommend `gitlab-triage` or webhooks + external service. +- If you're unsure about a translation, flag it as "Needs review" in the migration report and let the user decide. diff --git a/.agents/skills/github-actions-to-gitlab-ci/references/marketplace-actions.md b/.agents/skills/github-actions-to-gitlab-ci/references/marketplace-actions.md new file mode 100644 index 00000000..a0cba551 --- /dev/null +++ b/.agents/skills/github-actions-to-gitlab-ci/references/marketplace-actions.md @@ -0,0 +1,1042 @@ +# Marketplace Actions Mapping + +This reference maps popular GitHub Actions marketplace actions to their GitLab CI/CD equivalents. Use this guide when migrating workflows from GitHub Actions to GitLab CI/CD to quickly identify the right approach for each action your workflow depends on. + +## How to use this reference + +1. Find your action in the list below, organized by tier (frequency of use). +2. Read the "What it does" section to confirm it matches your use case. +3. Follow the "Before/After" example to translate the step. +4. Check "Notes" for any gotchas or special handling required. +5. For actions not listed, see the "Pattern: actions not in this list" section. + +## At a glance (table) + +| Action | Tier | GitLab Equivalent | Notes | +|--------|------|-------------------|-------| +| actions/checkout | 1 | Remove (auto-cloned) | GitLab clones repo automatically | +| actions/setup-node | 1 | `image: node:` | Use Docker image or asdf | +| actions/setup-python | 1 | `image: python:` | Use Docker image or asdf | +| actions/setup-java | 1 | `image: eclipse-temurin:` | Use Docker image or asdf | +| actions/setup-go | 1 | `image: golang:` | Use Docker image or asdf | +| actions/setup-ruby | 1 | `image: ruby:` | Use Docker image or asdf | +| actions/cache | 1 | `cache:` keyword | Native GitLab feature | +| actions/upload-artifact | 1 | `artifacts:` keyword | Native GitLab feature | +| actions/download-artifact | 1 | `needs:artifacts:` or `artifacts:` | Automatic between stages | +| docker/build-push-action | 1 | `docker build` + `docker push` | Use `services: docker:dind` | +| docker/login-action | 1 | `docker login` in script | Use CI/CD variables | +| docker/setup-buildx-action | 1 | `docker buildx` in script | Use `services: docker:dind` | +| docker/setup-qemu-action | 1 | `docker run --rm --privileged tonistiigi/binfmt` | Multi-arch builds | +| actions/github-script | 1 | GitLab API calls or glab | Needs review, no direct equivalent | +| actions/setup-dotnet | 2 | `image: mcr.microsoft.com/dotnet/sdk:` | Use Docker image | +| shivammathur/setup-php | 2 | `image: php:` | Use Docker image or custom | +| aws-actions/configure-aws-credentials | 2 | CI/CD variables or OIDC | Use GitLab OIDC to AWS | +| google-github-actions/auth | 2 | CI/CD variables or OIDC | Use GitLab OIDC to GCP | +| azure/login | 2 | CI/CD variables or OIDC | Use GitLab OIDC to Azure | +| hashicorp/setup-terraform | 2 | `include: 'Terraform/Base.latest.gitlab-ci.yml'` | GitLab template | +| codecov/codecov-action | 2 | `codecov-cli` in script | Use codecov CLI directly | +| softprops/action-gh-release | 2 | `glab release create` or API | Use glab or REST API | +| peter-evans/create-pull-request | 2 | `glab mr create` or API | Needs review, use glab | +| JamesIves/github-pages-deploy-action | 2 | GitLab Pages native | Use `pages:` job | +| pnpm/action-setup | 2 | `image: node:` + pnpm script | Install pnpm in script | +| oven-sh/setup-bun | 2 | `image: oven/bun:` | Use Docker image | +| gradle/gradle-build-action | 2 | `image: gradle:` | Use Docker image | +| cypress-io/github-action | 2 | `image: cypress/browsers:` | Use Docker image | +| tj-actions/changed-files | 3 | `git diff` in script | Use git commands directly | +| dorny/paths-filter | 3 | `rules:changes:` keyword | Native GitLab feature | +| nick-invision/retry | 3 | `retry:` keyword | Native GitLab feature | +| pre-commit/action | 3 | `pre-commit run` in script | Run pre-commit in script | +| slackapi/slack-github-action | 3 | Slack webhook or API | Use curl or Slack SDK | + +## Tier 1: Used in 80%+ of workflows + +### actions/checkout + +**What it does**: Clones the repository into the runner's workspace. + +**GitLab equivalent**: Remove this step entirely. GitLab CI/CD automatically clones the repository before running each job. + +**Before** (GitHub Actions): +```yaml +- uses: actions/checkout@v4 + with: + fetch-depth: 0 +``` + +**After** (GitLab CI/CD): +```yaml +# No step needed. GitLab clones automatically. +# To fetch full history, use: +variables: + GIT_DEPTH: 0 +``` + +**Notes**: +- GitLab clones the repository automatically for every job. There is no equivalent action to add. +- Use `GIT_DEPTH: 0` to fetch full history instead of shallow clones. +- Use `GIT_SUBMODULE_STRATEGY: recursive` to clone submodules. + +### actions/setup-node + +**What it does**: Installs Node.js and optionally configures npm/yarn/pnpm caching. + +**GitLab equivalent**: Use a Node.js Docker image, optionally combined with `cache:` for package manager caching. + +**Before** (GitHub Actions): +```yaml +- uses: actions/setup-node@v4 + with: + node-version: '20' + cache: 'npm' +``` + +**After** (GitLab CI/CD): +```yaml +image: node:20 + +cache: + paths: + - node_modules/ +``` + +**Notes**: +- Docker images are the preferred approach in GitLab CI/CD. Use `node:` for LTS versions or `node:latest` for the latest version. +- For caching, use the `cache:` keyword with paths like `node_modules/` or `.npm/`. +- If you need multiple Node.js versions in one pipeline, use `image:` at the job level instead of globally. + +### actions/setup-python + +**What it does**: Installs Python and optionally configures pip caching. + +**GitLab equivalent**: Use a Python Docker image, optionally combined with `cache:` for pip caching. + +**Before** (GitHub Actions): +```yaml +- uses: actions/setup-python@v4 + with: + python-version: '3.11' + cache: 'pip' +``` + +**After** (GitLab CI/CD): +```yaml +image: python:3.11 + +cache: + paths: + - .cache/pip/ +``` + +**Notes**: +- Use `python:` Docker images. For specific patch versions, use `python:3.11.5`. +- Configure pip to use a cache directory with `pip install --cache-dir .cache/pip`. +- For Poetry or other package managers, adjust the cache path accordingly. + +### actions/setup-java + +**What it does**: Installs Java and optionally configures Maven/Gradle caching. + +**GitLab equivalent**: Use an Eclipse Temurin or OpenJDK Docker image, optionally combined with `cache:`. + +**Before** (GitHub Actions): +```yaml +- uses: actions/setup-java@v3 + with: + java-version: '17' + distribution: 'temurin' + cache: 'maven' +``` + +**After** (GitLab CI/CD): +```yaml +image: eclipse-temurin:17 + +cache: + paths: + - .m2/repository/ +``` + +**Notes**: +- Use `eclipse-temurin:` for the official Eclipse Temurin images. +- For Maven, cache `.m2/repository/`. For Gradle, cache `.gradle/`. +- Ensure your build tool is configured to use the cache directory. + +### actions/setup-go + +**What it does**: Installs Go and optionally configures module caching. + +**GitLab equivalent**: Use a Go Docker image, optionally combined with `cache:`. + +**Before** (GitHub Actions): +```yaml +- uses: actions/setup-go@v4 + with: + go-version: '1.21' + cache: true +``` + +**After** (GitLab CI/CD): +```yaml +image: golang:1.21 + +cache: + paths: + - .cache/go-build/ + - go/pkg/mod/ +``` + +**Notes**: +- Use `golang:` Docker images. +- Go modules are cached automatically in `$GOPATH/pkg/mod/`. Set `GOMODCACHE` to customize the location. +- Build cache is stored in `$GOCACHE`, typically `.cache/go-build/`. + +### actions/setup-ruby + +**What it does**: Installs Ruby and optionally configures Bundler caching. + +**GitLab equivalent**: Use a Ruby Docker image, optionally combined with `cache:`. + +**Before** (GitHub Actions): +```yaml +- uses: actions/setup-ruby@v1 + with: + ruby-version: '3.2' + bundler-cache: true +``` + +**After** (GitLab CI/CD): +```yaml +image: ruby:3.2 + +cache: + paths: + - vendor/bundle/ + +before_script: + - bundle install --jobs $(nproc) --retry 3 --path vendor/bundle +``` + +**Notes**: +- Use `ruby:` Docker images. +- Bundler caches gems in `vendor/bundle/` by default when using `bundle install --path vendor/bundle`. +- The `ruby/setup-ruby` action is similar; use the same Docker image approach. + +### actions/cache + +**What it does**: Caches dependencies or build artifacts between workflow runs. + +**GitLab equivalent**: Use the native `cache:` keyword in your job definition. + +**Before** (GitHub Actions): +```yaml +- uses: actions/cache@v3 + with: + path: ~/.npm + key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }} + restore-keys: | + ${{ runner.os }}-npm- +``` + +**After** (GitLab CI/CD): +```yaml +cache: + key: + files: + - package-lock.json + paths: + - .npm/ +``` + +**Notes**: +- GitLab's `cache:` keyword is simpler and more integrated. Keys are automatically generated from file hashes. +- Use `cache:key:files:` to generate cache keys based on file contents. +- Cache is shared across all jobs in a pipeline by default. Use `cache:key:` to create separate caches. + +### actions/upload-artifact + +**What it does**: Uploads build artifacts or test results for later download or retention. + +**GitLab equivalent**: Use the native `artifacts:` keyword in your job definition. + +**Before** (GitHub Actions): +```yaml +- uses: actions/upload-artifact@v3 + with: + name: test-results + path: test-results/ + retention-days: 30 +``` + +**After** (GitLab CI/CD): +```yaml +artifacts: + name: test-results + paths: + - test-results/ + expire_in: 30 days +``` + +**Notes**: +- GitLab's `artifacts:` keyword is native and automatically available to downstream jobs. +- Use `expire_in:` to set retention time (e.g., `30 days`, `1 week`). +- Artifacts are automatically passed to jobs that depend on this job via `needs:`. + +### actions/download-artifact + +**What it does**: Downloads artifacts uploaded by a previous job in the workflow. + +**GitLab equivalent**: Use `needs:artifacts:` or rely on automatic artifact passing between stages. + +**Before** (GitHub Actions): +```yaml +- uses: actions/download-artifact@v3 + with: + name: test-results + path: ./test-results/ +``` + +**After** (GitLab CI/CD): +```yaml +# Option 1: Automatic (if job is in previous stage) +# Artifacts are automatically available + +# Option 2: Explicit dependency +needs: + - job: build-job + artifacts: true +``` + +**Notes**: +- In GitLab CI/CD, artifacts from jobs in previous stages are automatically available. +- Use `needs:artifacts:` only when you need artifacts from a job outside the normal stage dependency. +- Artifacts are extracted to the job's working directory automatically. + +### docker/build-push-action + +**What it does**: Builds a Docker image and pushes it to a registry. + +**GitLab equivalent**: Use `docker build` and `docker push` commands in a script, with `services: docker:dind`. + +**Before** (GitHub Actions): +```yaml +- uses: docker/build-push-action@v5 + with: + context: . + push: true + tags: myregistry.azurecr.io/myimage:latest + username: ${{ secrets.REGISTRY_USERNAME }} + password: ${{ secrets.REGISTRY_PASSWORD }} +``` + +**After** (GitLab CI/CD): +```yaml +image: docker:latest +services: + - docker:dind + +script: + - docker login -u $REGISTRY_USERNAME -p $REGISTRY_PASSWORD myregistry.azurecr.io + - docker build -t myregistry.azurecr.io/myimage:latest . + - docker push myregistry.azurecr.io/myimage:latest +``` + +**Notes**: +- Use `services: docker:dind` to enable Docker-in-Docker for building images. +- Credentials should be stored as CI/CD variables (e.g., `REGISTRY_USERNAME`, `REGISTRY_PASSWORD`). +- For advanced builds, consider using GitLab's `Docker.latest.gitlab-ci.yml` template. + +### docker/login-action + +**What it does**: Authenticates with a Docker registry. + +**GitLab equivalent**: Use `docker login` command in a script with CI/CD variables. + +**Before** (GitHub Actions): +```yaml +- uses: docker/login-action@v2 + with: + registry: myregistry.azurecr.io + username: ${{ secrets.REGISTRY_USERNAME }} + password: ${{ secrets.REGISTRY_PASSWORD }} +``` + +**After** (GitLab CI/CD): +```yaml +script: + - docker login -u $REGISTRY_USERNAME -p $REGISTRY_PASSWORD myregistry.azurecr.io +``` + +**Notes**: +- Store registry credentials as CI/CD variables (masked for security). +- For Docker Hub, omit the registry URL or use `docker.io`. +- For AWS ECR, use `aws ecr get-login-password` instead. + +### docker/setup-buildx-action + +**What it does**: Sets up Docker Buildx for advanced multi-platform builds. + +**GitLab equivalent**: Use `docker buildx` commands in a script with `services: docker:dind`. + +**Before** (GitHub Actions): +```yaml +- uses: docker/setup-buildx-action@v2 +``` + +**After** (GitLab CI/CD): +```yaml +image: docker:latest +services: + - docker:dind + +script: + - docker run --rm --privileged docker/binfmt:latest + - docker buildx create --use + - docker buildx build --platform linux/amd64,linux/arm64 -t myimage:latest . +``` + +**Notes**: +- Buildx is included in recent Docker images. Enable it with `docker buildx create --use`. +- For multi-platform builds, use `docker run --rm --privileged docker/binfmt:latest` to register additional architectures. + +### docker/setup-qemu-action + +**What it does**: Sets up QEMU for multi-architecture Docker builds. + +**GitLab equivalent**: Run `docker run --rm --privileged tonistiigi/binfmt` to register additional architectures. + +**Before** (GitHub Actions): +```yaml +- uses: docker/setup-qemu-action@v2 + with: + platforms: linux/amd64,linux/arm64 +``` + +**After** (GitLab CI/CD): +```yaml +image: docker:latest +services: + - docker:dind + +script: + - docker run --rm --privileged tonistiigi/binfmt --install linux/amd64,linux/arm64 + - docker buildx build --platform linux/amd64,linux/arm64 -t myimage:latest . +``` + +**Notes**: +- QEMU is required for building images for architectures different from the host. +- Use `tonistiigi/binfmt` to register additional architectures. + +### actions/github-script + +**What it does**: Runs JavaScript code with access to the GitHub API. + +**GitLab equivalent**: Use GitLab API calls via `curl` or the `glab` CLI, or use a scripting language like Python or Node.js. + +**Before** (GitHub Actions): +```yaml +- uses: actions/github-script@v7 + with: + script: | + const issues = await github.rest.issues.listForRepo({ + owner: context.repo.owner, + repo: context.repo.repo, + }); + console.log(issues.data); +``` + +**After** (GitLab CI/CD): +```yaml +image: curlimages/curl:latest + +script: + - curl --header "PRIVATE-TOKEN: $CI_JOB_TOKEN" \ + "https://gitlab.com/api/v4/projects/$CI_PROJECT_ID/issues" \ + | jq . +``` + +**Notes**: +- There is no direct equivalent to `actions/github-script`. Use the GitLab API directly via `curl` or `glab`. +- Use `$CI_JOB_TOKEN` for authentication within the same GitLab instance. +- For complex logic, use a scripting language like Python or Node.js. +- This translation often requires review to ensure API calls are correct. + +## Tier 2: Common (20-80%) + +### actions/setup-dotnet + +**What it does**: Installs .NET SDK and optionally configures NuGet caching. + +**GitLab equivalent**: Use a .NET Docker image, optionally combined with `cache:`. + +**Before** (GitHub Actions): +```yaml +- uses: actions/setup-dotnet@v3 + with: + dotnet-version: '7.0' +``` + +**After** (GitLab CI/CD): +```yaml +image: mcr.microsoft.com/dotnet/sdk:7.0 + +cache: + paths: + - ~/.nuget/packages/ +``` + +**Notes**: +- Use `mcr.microsoft.com/dotnet/sdk:` for the official Microsoft .NET images. +- NuGet packages are cached in `~/.nuget/packages/` by default. + +### shivammathur/setup-php + +**What it does**: Installs PHP with extensions and optionally configures Composer caching. + +**GitLab equivalent**: Use a PHP Docker image with extensions, optionally combined with `cache:`. + +**Before** (GitHub Actions): +```yaml +- uses: shivammathur/setup-php@v2 + with: + php-version: '8.2' + extensions: mbstring, intl + tools: composer:v2 +``` + +**After** (GitLab CI/CD): +```yaml +image: php:8.2 + +before_script: + - apt-get update && apt-get install -y libicu-dev + - docker-php-ext-install mbstring intl + - curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer + +cache: + paths: + - vendor/ +``` + +**Notes**: +- PHP Docker images require manual extension installation via `docker-php-ext-install`. +- Install system dependencies before PHP extensions. +- Composer is not included by default; install it in `before_script:`. + +### aws-actions/configure-aws-credentials + +**What it does**: Configures AWS credentials for use in subsequent steps. + +**GitLab equivalent**: Set AWS environment variables via CI/CD variables, or use GitLab's native OIDC to AWS. + +**Before** (GitHub Actions): +```yaml +- uses: aws-actions/configure-aws-credentials@v2 + with: + aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} + aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + aws-region: us-east-1 +``` + +**After** (GitLab CI/CD): +```yaml +variables: + AWS_ACCESS_KEY_ID: $AWS_ACCESS_KEY_ID + AWS_SECRET_ACCESS_KEY: $AWS_SECRET_ACCESS_KEY + AWS_DEFAULT_REGION: us-east-1 + +script: + - aws s3 ls +``` + +**Notes**: +- Store AWS credentials as CI/CD variables (masked for security). +- For better security, use GitLab's native OIDC to AWS instead of long-lived credentials. +- Set `AWS_DEFAULT_REGION` to specify the region. + +### google-github-actions/auth + +**What it does**: Authenticates with Google Cloud Platform. + +**GitLab equivalent**: Use CI/CD variables or GitLab's native OIDC to GCP. + +**Before** (GitHub Actions): +```yaml +- uses: google-github-actions/auth@v1 + with: + credentials_json: ${{ secrets.GCP_SA_KEY }} +``` + +**After** (GitLab CI/CD): +```yaml +variables: + GOOGLE_APPLICATION_CREDENTIALS: /tmp/gcp-key.json + +before_script: + - echo $GCP_SA_KEY | base64 -d > $GOOGLE_APPLICATION_CREDENTIALS + +script: + - gcloud auth activate-service-account --key-file=$GOOGLE_APPLICATION_CREDENTIALS +``` + +**Notes**: +- Store the GCP service account key as a CI/CD variable (base64-encoded). +- For better security, use GitLab's native OIDC to GCP. +- Decode the key before using it with `gcloud` or other tools. + +### azure/login + +**What it does**: Authenticates with Microsoft Azure. + +**GitLab equivalent**: Use CI/CD variables or GitLab's native OIDC to Azure. + +**Before** (GitHub Actions): +```yaml +- uses: azure/login@v1 + with: + creds: ${{ secrets.AZURE_CREDENTIALS }} +``` + +**After** (GitLab CI/CD): +```yaml +variables: + AZURE_SUBSCRIPTION_ID: $AZURE_SUBSCRIPTION_ID + AZURE_TENANT_ID: $AZURE_TENANT_ID + AZURE_CLIENT_ID: $AZURE_CLIENT_ID + AZURE_CLIENT_SECRET: $AZURE_CLIENT_SECRET + +before_script: + - az login --service-principal -u $AZURE_CLIENT_ID -p $AZURE_CLIENT_SECRET --tenant $AZURE_TENANT_ID + +script: + - az vm list +``` + +**Notes**: +- Store Azure credentials as CI/CD variables (masked for security). +- For better security, use GitLab's native OIDC to Azure. +- Use `az login` with service principal credentials. + +### hashicorp/setup-terraform + +**What it does**: Installs Terraform and optionally configures caching. + +**GitLab equivalent**: Use GitLab's `Terraform/Base.latest.gitlab-ci.yml` template or a Terraform Docker image. + +**Before** (GitHub Actions): +```yaml +- uses: hashicorp/setup-terraform@v2 + with: + terraform_version: 1.5.0 +``` + +**After** (GitLab CI/CD): +```yaml +include: + - template: Terraform/Base.latest.gitlab-ci.yml + +variables: + TF_VERSION: 1.5.0 +``` + +**Notes**: +- GitLab provides a built-in Terraform template that handles setup and caching. +- Alternatively, use `image: hashicorp/terraform:` for a Docker-based approach. +- The template includes best practices for Terraform in CI/CD. + +### codecov/codecov-action + +**What it does**: Uploads code coverage reports to Codecov. + +**GitLab equivalent**: Use the `codecov-cli` tool directly in a script. + +**Before** (GitHub Actions): +```yaml +- uses: codecov/codecov-action@v3 + with: + files: ./coverage.xml + token: ${{ secrets.CODECOV_TOKEN }} +``` + +**After** (GitLab CI/CD): +```yaml +image: python:3.11 + +script: + - pip install codecov-cli + - codecov upload-file --file coverage.xml --token $CODECOV_TOKEN +``` + +**Notes**: +- Install `codecov-cli` via pip or use a Docker image that includes it. +- Store the Codecov token as a CI/CD variable (masked for security). +- Ensure your test runner generates coverage reports in a format Codecov supports. + +### softprops/action-gh-release + +**What it does**: Creates a GitHub release with assets. + +**GitLab equivalent**: Use `glab release create` or the GitLab REST API. + +**Before** (GitHub Actions): +```yaml +- uses: softprops/action-gh-release@v1 + with: + files: dist/* + token: ${{ secrets.GITHUB_TOKEN }} +``` + +**After** (GitLab CI/CD): +```yaml +image: alpine:latest + +before_script: + - apk add --no-cache curl + +script: + - glab release create v1.0.0 --assets dist/* +``` + +**Notes**: +- Use `glab release create` for GitLab releases. +- For GitHub releases, use the GitHub REST API via `curl`. +- Store authentication tokens as CI/CD variables (masked for security). + +### peter-evans/create-pull-request + +**What it does**: Creates a pull request (or merge request) with changes. + +**GitLab equivalent**: Use `glab mr create` or the GitLab REST API. + +**Before** (GitHub Actions): +```yaml +- uses: peter-evans/create-pull-request@v5 + with: + commit-message: 'Update dependencies' + title: 'Automated dependency update' + body: 'This PR updates dependencies.' +``` + +**After** (GitLab CI/CD): +```yaml +image: alpine:latest + +before_script: + - apk add --no-cache git curl + +script: + - git config user.email "ci@example.com" + - git config user.name "CI Bot" + - git checkout -b update-deps + - git add . + - git commit -m "Update dependencies" + - glab mr create --title "Automated dependency update" --description "This MR updates dependencies." +``` + +**Notes**: +- Use `glab mr create` for GitLab merge requests. +- Configure git user before committing. +- This translation often requires review to ensure the MR is created correctly. + +### JamesIves/github-pages-deploy-action + +**What it does**: Deploys a static site to GitHub Pages. + +**GitLab equivalent**: Use GitLab Pages native feature with the `pages:` job. + +**Before** (GitHub Actions): +```yaml +- uses: JamesIves/github-pages-deploy-action@v4 + with: + folder: build +``` + +**After** (GitLab CI/CD): +```yaml +pages: + stage: deploy + script: + - mkdir -p public + - cp -r build/* public/ + artifacts: + paths: + - public + only: + - master +``` + +**Notes**: +- GitLab Pages is a native feature. Use the `pages:` job to deploy. +- Artifacts must be in the `public/` directory. +- The `pages:` job automatically deploys to GitLab Pages. + +### pnpm/action-setup + +**What it does**: Installs pnpm package manager. + +**GitLab equivalent**: Install pnpm in a script using npm or curl. + +**Before** (GitHub Actions): +```yaml +- uses: pnpm/action-setup@v2 + with: + version: 8 +``` + +**After** (GitLab CI/CD): +```yaml +image: node:20 + +before_script: + - npm install -g pnpm@8 + - pnpm install +``` + +**Notes**: +- Install pnpm globally via npm: `npm install -g pnpm@`. +- Alternatively, use `curl -fsSL https://get.pnpm.io/install.sh | sh -`. +- Cache pnpm store with `cache:paths: - .pnpm-store/`. + +### oven-sh/setup-bun + +**What it does**: Installs Bun JavaScript runtime. + +**GitLab equivalent**: Use a Bun Docker image. + +**Before** (GitHub Actions): +```yaml +- uses: oven-sh/setup-bun@v1 + with: + bun-version: latest +``` + +**After** (GitLab CI/CD): +```yaml +image: oven/bun:latest + +cache: + paths: + - .bun/install/cache/ +``` + +**Notes**: +- Use `oven/bun:` Docker images. +- Bun caches dependencies in `.bun/install/cache/`. + +### gradle/gradle-build-action + +**What it does**: Installs Gradle and configures caching. + +**GitLab equivalent**: Use a Gradle Docker image, optionally combined with `cache:`. + +**Before** (GitHub Actions): +```yaml +- uses: gradle/gradle-build-action@v2 + with: + gradle-version: 8.0 +``` + +**After** (GitLab CI/CD): +```yaml +image: gradle:8.0 + +cache: + paths: + - .gradle/ +``` + +**Notes**: +- Use `gradle:` Docker images. +- Gradle caches in `.gradle/` by default. + +### cypress-io/github-action + +**What it does**: Installs Cypress and runs end-to-end tests. + +**GitLab equivalent**: Use a Cypress Docker image and run tests in a script. + +**Before** (GitHub Actions): +```yaml +- uses: cypress-io/github-action@v5 + with: + browser: chrome + start: npm start +``` + +**After** (GitLab CI/CD): +```yaml +image: cypress/browsers:latest + +script: + - npm install + - npm start & + - npx cypress run --browser chrome +``` + +**Notes**: +- Use `cypress/browsers:` Docker images which include Cypress and browsers. +- Start your application before running Cypress tests. +- Use `npx cypress run` to run tests in CI mode. + +## Tier 3: Specialized + +### tj-actions/changed-files + +**What it does**: Detects which files changed in the current commit or pull request. + +**GitLab equivalent**: Use `git diff` commands in a script. + +**Before** (GitHub Actions): +```yaml +- uses: tj-actions/changed-files@v40 + with: + files: src/** +``` + +**After** (GitLab CI/CD): +```yaml +script: + - git diff --name-only $CI_COMMIT_BEFORE_SHA $CI_COMMIT_SHA | grep "^src/" +``` + +**Notes**: +- Use `git diff` to detect changed files. +- Use `$CI_COMMIT_BEFORE_SHA` and `$CI_COMMIT_SHA` for commit ranges. + +### dorny/paths-filter + +**What it does**: Filters jobs based on changed file paths. + +**GitLab equivalent**: Use the native `rules:changes:` keyword. + +**Before** (GitHub Actions): +```yaml +- uses: dorny/paths-filter@v2 + with: + filters: | + backend: + - 'src/backend/**' +``` + +**After** (GitLab CI/CD): +```yaml +test-backend: + script: + - npm test + rules: + - if: '$CI_PIPELINE_SOURCE == "merge_request_event"' + changes: + - src/backend/** +``` + +**Notes**: +- Use `rules:changes:` to conditionally run jobs based on file changes. +- This is a native GitLab feature and simpler than the GitHub Actions equivalent. + +### nick-invision/retry + +**What it does**: Retries a step if it fails. + +**GitLab equivalent**: Use the native `retry:` keyword. + +**Before** (GitHub Actions): +```yaml +- uses: nick-invision/retry@v2 + with: + timeout_minutes: 10 + max_attempts: 3 + command: npm test +``` + +**After** (GitLab CI/CD): +```yaml +test: + script: + - npm test + retry: + max: 3 + when: + - script_failure +``` + +**Notes**: +- Use `retry:` to automatically retry failed jobs. +- Specify `max:` for the number of retries and `when:` for the failure type. + +### pre-commit/action + +**What it does**: Runs pre-commit hooks. + +**GitLab equivalent**: Run `pre-commit run` in a script. + +**Before** (GitHub Actions): +```yaml +- uses: pre-commit/action@v3 +``` + +**After** (GitLab CI/CD): +```yaml +image: python:3.11 + +before_script: + - pip install pre-commit + +script: + - pre-commit run --all-files +``` + +**Notes**: +- Install `pre-commit` via pip. +- Run `pre-commit run --all-files` to check all files. + +### slackapi/slack-github-action + +**What it does**: Sends notifications to Slack. + +**GitLab equivalent**: Use a Slack webhook or the Slack SDK via curl or a scripting language. + +**Before** (GitHub Actions): +```yaml +- uses: slackapi/slack-github-action@v1 + with: + webhook-url: ${{ secrets.SLACK_WEBHOOK }} + payload: | + { + "text": "Build completed" + } +``` + +**After** (GitLab CI/CD): +```yaml +image: curlimages/curl:latest + +script: + - curl -X POST -H 'Content-type: application/json' \ + --data '{"text":"Build completed"}' \ + $SLACK_WEBHOOK_URL +``` + +**Notes**: +- Use a Slack webhook URL stored as a CI/CD variable. +- Send JSON payloads via `curl` to the webhook. +- For more complex messages, use the Slack SDK in Python or Node.js. + +## Pattern: actions not in this list + +If an action is not listed above, follow this decision tree: + +1. **Does it set up a tool or language?** Use a Docker image instead. + - Example: `actions/setup-rust` -> `image: rust:latest` + +2. **Does it interact with an external service?** Use the service's CLI or API. + - Example: `aws-actions/s3-cp` -> `aws s3 cp` in a script + +3. **Does it perform a simple file operation?** Use shell commands. + - Example: `actions/upload-release-asset` -> `curl` to upload via API + +4. **Does it require complex logic?** Use a scripting language. + - Example: Custom action with JavaScript -> Python or Node.js script + +5. **Is there no clear equivalent?** Flag for review. + - Document the action, its purpose, and ask for guidance. + +For any action not covered, search the GitLab CI/CD documentation for equivalent features or consider using a Docker image that includes the tool. diff --git a/.agents/skills/github-actions-to-gitlab-ci/references/syntax-mapping.md b/.agents/skills/github-actions-to-gitlab-ci/references/syntax-mapping.md new file mode 100644 index 00000000..f07d99c2 --- /dev/null +++ b/.agents/skills/github-actions-to-gitlab-ci/references/syntax-mapping.md @@ -0,0 +1,906 @@ +# Syntax Mapping: GitHub Actions to GitLab CI/CD + +> Comprehensive keyword and concept mapping for translating `.github/workflows/*.yml` to `.gitlab-ci.yml`. + +## At a glance + +GitHub Actions and GitLab CI/CD share the same goals (automate build, test, deploy) but use different +vocabulary and mental models. GitHub Actions organizes work as workflows containing jobs containing steps. +GitLab CI/CD organizes work as pipelines containing jobs grouped into stages. The table below is the +official GitLab keyword mapping; the sections that follow expand each row with examples and gotchas. + +| GitHub Actions | GitLab CI/CD | Notes | +|---|---|---| +| `env` | `variables` | Global or per-job variable definitions | +| `name` (workflow) | `workflow:name` | Sets the pipeline name displayed in the UI | +| `jobs` | `stages` | Grouping mechanism for parallel/sequential work | +| `on` | Not applicable | GitLab is git-integrated; triggers are implicit; use `rules:` for conditions | +| `run` | Not applicable | Use the `script:` array, one entry per command | +| `runs-on` | `tags` | Runner selection by label | +| `steps` | `script` | Ordered list of commands within a job | +| `uses` | `include` | Only true for reusable workflows; marketplace actions need different treatment | + +Source: https://docs.gitlab.com/ci/migration/github_actions/ + +--- + +## Triggers (`on:` -> `workflow:` and `rules:`) + +`on:` declares which events cause a GitHub Actions workflow to run (push, pull_request, schedule, etc.). +GitLab pipelines run automatically on every push; use `workflow:rules:` to restrict at the pipeline level +and `rules:` at the job level. + +**GitHub example:** + +```yaml +on: + push: + branches: + - main + - 'release/**' + pull_request: + branches: + - main + schedule: + - cron: '0 6 * * 1' +``` + +**GitLab equivalent:** + +```yaml +workflow: + rules: + - if: $CI_COMMIT_BRANCH == "main" + - if: $CI_COMMIT_BRANCH =~ /^release\// + - if: $CI_PIPELINE_SOURCE == "merge_request_event" + - if: $CI_PIPELINE_SOURCE == "schedule" + +stages: + - build + - test +``` + +**Notes:** + +- GitLab has no `on:` equivalent at the file level. Every push triggers a pipeline unless `workflow:rules:` suppresses it. +- `pull_request` maps to `$CI_PIPELINE_SOURCE == "merge_request_event"` (requires merge request pipelines to be enabled). +- Scheduled pipelines are configured in the GitLab UI under CI/CD > Schedules, not in the YAML. The `schedule` source is available as `$CI_PIPELINE_SOURCE == "schedule"` for conditional logic. + +Source: https://docs.gitlab.com/ci/yaml/workflow/ + +--- + +### Workflow name (`name:` -> `workflow:name:`) + +GitHub: +```yaml +name: CI +``` + +GitLab: +```yaml +workflow: + name: CI +``` + +Sets the pipeline name displayed in the GitLab Pipelines UI. Supports CI/CD variable interpolation, for example `name: 'Pipeline for $CI_COMMIT_REF_NAME'`. Available since GitLab 15.8. + +See: https://docs.gitlab.com/ci/yaml/#workflowname + +--- + +## Jobs and stages (`jobs:` -> `stages:` + jobs) + +`jobs:` in GitHub Actions is a map of named jobs that run in parallel by default; `needs:` creates +dependencies. GitLab separates the concept: `stages:` defines the execution order, and jobs declare +which stage they belong to. + +**GitHub example:** + +```yaml +jobs: + build: + runs-on: ubuntu-latest + steps: + - run: make build + + test: + runs-on: ubuntu-latest + needs: build + steps: + - run: make test + + deploy: + runs-on: ubuntu-latest + needs: test + steps: + - run: make deploy +``` + +**GitLab equivalent:** + +```yaml +stages: + - build + - test + - deploy + +build: + stage: build + script: + - make build + +test: + stage: test + script: + - make test + +deploy: + stage: deploy + script: + - make deploy +``` + +**Notes:** + +- Jobs in the same stage run in parallel. Jobs in later stages wait for all jobs in earlier stages to succeed. +- `needs:` exists in GitLab too and enables a DAG (directed acyclic graph) that bypasses stage ordering. It is more powerful than GitHub's `needs:` because it can reference jobs in earlier or same stages. +- Job names in GitLab are top-level YAML keys (not nested under `jobs:`). + +Source: https://docs.gitlab.com/ci/yaml/#stages + +--- + +## Steps and scripts (`steps:` -> `script:`) + +`steps:` is an ordered list of actions and shell commands within a GitHub Actions job. GitLab's `script:` +is a YAML array of shell commands that runs in sequence. + +**GitHub example:** + +```yaml +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Install dependencies + run: npm ci + - name: Build + run: npm run build + - name: Test + run: npm test +``` + +**GitLab equivalent:** + +```yaml +build: + image: node:20 + script: + - npm ci + - npm run build + - npm test +``` + +**Notes:** + +- `actions/checkout@v4` has no GitLab equivalent and should be removed. GitLab clones the repository automatically before every job. +- `before_script:` runs before `script:` and is useful for setup commands (install tools, authenticate). It can be defined globally (applies to all jobs) or per-job. +- `after_script:` runs after `script:` even if the job fails, similar to a `finally` block. + +--- + +## Runners (`runs-on:` -> `tags:`) + +`runs-on:` selects the runner environment by label (e.g., `ubuntu-latest`, `windows-latest`, `self-hosted`). +GitLab uses `tags:` to match jobs to runners that have been registered with those tags. + +**GitHub example:** + +```yaml +jobs: + linux-job: + runs-on: ubuntu-latest + steps: + - run: echo "Running on Linux" + + windows-job: + runs-on: windows-latest + steps: + - run: echo "Running on Windows" +``` + +**GitLab equivalent:** + +```yaml +linux-job: + tags: + - linux + script: + - echo "Running on Linux" + +windows-job: + tags: + - windows + script: + - echo "Running on Windows" +``` + +**Notes:** + +- GitLab.com shared runners have tags like `saas-linux-small-amd64`. Check your project's available runners under Settings > CI/CD > Runners. +- If no `tags:` are specified, the job runs on any available runner. This is fine for GitLab.com but may cause issues on self-managed instances with specialized runners. +- `runs-on: self-hosted` with additional labels (e.g., `runs-on: [self-hosted, gpu]`) maps to multiple tags in GitLab: `tags: [self-hosted, gpu]`. + +Source: https://docs.gitlab.com/ci/runners/configure_runners/#control-jobs-that-a-runner-can-run + +--- + +## Matrix builds (`matrix:` -> `parallel:matrix:`) + +`strategy.matrix:` in GitHub Actions generates multiple job instances with different variable combinations. +GitLab's `parallel:matrix:` provides the same capability with different syntax. + +**GitHub example:** + +```yaml +jobs: + test: + runs-on: ubuntu-latest + strategy: + matrix: + os: [ubuntu, windows] + node: [18, 20, 22] + steps: + - run: echo "Testing on ${{ matrix.os }} with Node ${{ matrix.node }}" +``` + +**GitLab equivalent:** + +```yaml +test: + image: node:${NODE_VERSION} + parallel: + matrix: + - OS: [ubuntu, windows] + NODE_VERSION: [18, 20, 22] + script: + - echo "Testing on $OS with Node $NODE_VERSION" +``` + +**Notes:** + +- GitHub matrix variables are accessed as `${{ matrix.key }}`; GitLab matrix variables are injected as regular CI/CD variables accessed as `$KEY`. +- GitLab generates `N x M` jobs (one per combination), same as GitHub. Job names are suffixed with the variable values. +- `strategy.fail-fast: false` (continue running other matrix instances when one fails) is the **default behaviour** in GitLab. GitLab's `parallel:matrix` instances are independent: one failing does not cancel the others. No translation needed; remove the keyword and the workflow behaves the same. +- GitLab does not have a built-in equivalent for `strategy.fail-fast: true` (cancel other matrix instances when one fails). If users rely on this behaviour, document it as a "needs review" item in the migration report. + +Source: https://docs.gitlab.com/ci/yaml/#parallelmatrix + +--- + +## Variables and secrets (`env:` and secrets -> `variables:`) + +`env:` defines environment variables at the workflow, job, or step level. GitHub secrets are accessed +as `${{ secrets.NAME }}`. In GitLab, both plain variables and secrets are CI/CD variables, defined in +the YAML or in the project/group settings UI. + +**GitHub example:** + +```yaml +env: + GREETING: Hello + +jobs: + greet: + runs-on: ubuntu-latest + env: + NAME: World + steps: + - run: echo "$GREETING $NAME" + - run: echo "Token is ${{ secrets.API_TOKEN }}" + env: + API_TOKEN: ${{ secrets.API_TOKEN }} +``` + +**GitLab equivalent:** + +```yaml +variables: + GREETING: Hello + +greet: + variables: + NAME: World + script: + - echo "$GREETING $NAME" + - echo "Token is $API_TOKEN" +``` + +**Notes:** + +- Secrets in GitLab are CI/CD variables marked as "Masked" and optionally "Protected". Define them in Settings > CI/CD > Variables. They are accessed as `$VAR_NAME`, not `${{ secrets.NAME }}`. +- Protected variables are only available in pipelines for protected branches or tags. +- Step-level `env:` (scoped to a single `run:` step) has no direct equivalent. Use job-level `variables:` or inline shell assignment (`export FOO=bar && command`). + +Source: https://docs.gitlab.com/ci/variables/ + +--- + +## Containers and services (`container:` -> `image:`, `services:` -> `services:`) + +`container:` specifies the Docker image a GitHub Actions job runs inside. `services:` defines sidecar +containers (databases, caches). GitLab uses `image:` and `services:` with nearly identical semantics. + +**GitHub example:** + +```yaml +jobs: + test: + runs-on: ubuntu-latest + container: node:20-alpine + services: + postgres: + image: postgres:15 + env: + POSTGRES_PASSWORD: secret + ports: + - 5432:5432 + steps: + - run: npm test +``` + +**GitLab equivalent:** + +```yaml +test: + image: node:20-alpine + services: + - name: postgres:15 + alias: postgres + variables: + POSTGRES_PASSWORD: secret + script: + - npm test +``` + +**Notes:** + +- Service hostnames in GitLab default to the image name with slashes and colons replaced by double underscores. Use `alias:` to set a predictable hostname (e.g., `alias: postgres` makes the service reachable at `postgres`). +- Port mapping (`ports:`) is not needed in GitLab. Services are accessible on their default ports via the alias hostname. +- The `default:` keyword can set a global `image:` for all jobs, equivalent to setting `container:` at the workflow level. + +Source: https://docs.gitlab.com/ci/services/ + +--- + +## Artifacts (upload/download -> `artifacts:`) + +`actions/upload-artifact` and `actions/download-artifact` pass files between jobs in GitHub Actions. +GitLab's `artifacts:` keyword declares files to persist, and they are automatically available to +downstream jobs. + +**GitHub example:** + +```yaml +jobs: + build: + runs-on: ubuntu-latest + steps: + - run: make build + - uses: actions/upload-artifact@v4 + with: + name: binaries + path: dist/ + + test: + needs: build + runs-on: ubuntu-latest + steps: + - uses: actions/download-artifact@v4 + with: + name: binaries + path: dist/ + - run: ./dist/app --test +``` + +**GitLab equivalent:** + +```yaml +stages: + - build + - test + +build: + stage: build + script: + - make build + artifacts: + paths: + - dist/ + expire_in: 1 hour + +test: + stage: test + script: + - ./dist/app --test +``` + +**Notes:** + +- GitLab artifacts from earlier stages are automatically downloaded by later-stage jobs. No explicit download step is needed. +- Use `artifacts:expire_in:` to control storage duration. Default retention is set at the instance level. +- `artifacts:reports:` is a GitLab-specific feature for structured data (JUnit XML, coverage, SAST results) that integrates with merge request UI. + +Source: https://docs.gitlab.com/ci/yaml/#artifacts + +--- + +## Caching (`actions/cache` -> `cache:`) + +`actions/cache` saves and restores directories keyed by a cache key, typically used for dependency +directories. GitLab's `cache:` keyword provides the same functionality natively. + +**GitHub example:** + +```yaml +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/cache@v4 + with: + path: ~/.npm + key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }} + restore-keys: | + ${{ runner.os }}-node- + - run: npm ci +``` + +**GitLab equivalent:** + +```yaml +build: + script: + - npm ci + cache: + key: + files: + - package-lock.json + paths: + - .npm/ + variables: + npm_config_cache: .npm +``` + +**Notes:** + +- `cache:key:files:` computes a hash of the listed files, equivalent to `hashFiles()` in GitHub Actions. +- GitLab cache is per-runner by default. For shared cache across runners, configure a distributed cache backend (S3 or GCS) in the runner configuration. +- Unlike artifacts, cache is not guaranteed to be available. Jobs must handle a cold cache gracefully. + +Source: https://docs.gitlab.com/ci/caching/ + +--- + +## Conditional execution (`if:` -> `rules:`) + +`if:` in GitHub Actions uses `${{ }}` template expressions to conditionally run a job or step. +GitLab's `rules:` keyword evaluates conditions using shell-style expressions with predefined CI/CD variables. + +**GitHub example:** + +```yaml +jobs: + deploy: + runs-on: ubuntu-latest + if: github.ref == 'refs/heads/main' && github.event_name == 'push' + steps: + - run: make deploy + + notify: + runs-on: ubuntu-latest + if: failure() + steps: + - run: ./notify-slack.sh +``` + +**GitLab equivalent:** + +```yaml +deploy: + script: + - make deploy + rules: + - if: $CI_COMMIT_BRANCH == "main" && $CI_PIPELINE_SOURCE == "push" + +notify: + script: + - ./notify-slack.sh + rules: + - when: on_failure +``` + +**Notes:** + +- GitHub `${{ github.ref }}` maps to `$CI_COMMIT_REF_NAME` or `$CI_COMMIT_BRANCH`. See the full predefined variable list at https://docs.gitlab.com/ci/variables/predefined_variables/. +- `rules:` is evaluated top-to-bottom; the first matching rule wins. This differs from GitHub's single `if:` expression. +- `rules:changes:` triggers a job only when specific files change, a capability with no direct GitHub Actions equivalent (GitHub requires path filters on `on:push:paths:`). + +Source: https://docs.gitlab.com/ci/jobs/job_rules/ + +--- + +## Reusable configuration (`uses:` for workflows -> `include:`) + +`uses:` in GitHub Actions serves two purposes: referencing marketplace actions and calling reusable +workflows. Only the reusable workflow case maps to GitLab's `include:`. Marketplace actions are a +separate concern (see `references/marketplace-actions.md`). + +**GitHub example (reusable workflow call):** + +```yaml +jobs: + call-workflow: + uses: ./.github/workflows/shared-build.yml + with: + environment: staging + secrets: inherit +``` + +**GitLab equivalent:** + +```yaml +include: + - local: .gitlab/ci/shared-build.yml + +# Or include from another project: +include: + - project: mygroup/shared-ci + file: /templates/build.yml + ref: main +``` + +**Notes:** + +- `uses: actions/checkout@v4` (marketplace action) does NOT map to `include:`. Remove it; GitLab clones automatically. +- `uses: owner/repo/.github/workflows/file.yml@ref` (cross-repo reusable workflow) maps to `include: project:` with `ref:`. +- GitLab `include:` merges configuration at parse time. It does not support passing inputs the way GitHub reusable workflows do. Use `variables:` to parameterize included jobs. + +Source: https://docs.gitlab.com/ci/yaml/includes/ + +--- + +## Job control (`timeout-minutes:`, `continue-on-error:`, `concurrency:`, `permissions:`) + +GitHub Actions provides several job-level control keywords that have GitLab equivalents, though the +semantics differ in important ways. + +**GitHub example:** + +```yaml +jobs: + flaky-test: + runs-on: ubuntu-latest + timeout-minutes: 10 + continue-on-error: true + concurrency: + group: deploy-${{ github.ref }} + cancel-in-progress: true + permissions: + contents: read + packages: write + steps: + - run: make test +``` + +**GitLab equivalent:** + +```yaml +flaky-test: + script: + - make test + timeout: 10 minutes + allow_failure: true + resource_group: deploy-$CI_COMMIT_REF_SLUG + interruptible: true +``` + +**Notes:** + +- `permissions:` has no job-level equivalent in GitLab. Token permissions are configured at the project level under Settings > CI/CD > Token Access. Document this as a manual step in your migration report. +- `concurrency.cancel-in-progress: true` maps to `interruptible: true`. GitLab cancels interruptible jobs when a newer pipeline starts for the same ref. +- `resource_group:` serializes jobs with the same group key, equivalent to GitHub's `concurrency.group:` without cancellation. + +Source: https://docs.gitlab.com/ci/yaml/#resource_group + +--- + +## Outputs (`outputs:` -> dotenv pattern) + +GitHub Actions jobs can declare `outputs:` that downstream jobs read via `${{ needs.job.outputs.key }}`. +GitLab does not have a native job output mechanism, but the dotenv artifact pattern achieves the same result. + +**GitHub example:** + +```yaml +jobs: + build: + runs-on: ubuntu-latest + outputs: + version: ${{ steps.get-version.outputs.version }} + steps: + - id: get-version + run: echo "version=$(cat VERSION)" >> $GITHUB_OUTPUT + + deploy: + needs: build + runs-on: ubuntu-latest + steps: + - run: echo "Deploying version ${{ needs.build.outputs.version }}" +``` + +**GitLab equivalent:** + +```yaml +stages: + - build + - deploy + +build: + stage: build + script: + - echo "VERSION=$(cat VERSION)" > build.env + artifacts: + reports: + dotenv: build.env + +deploy: + stage: deploy + script: + - echo "Deploying version $VERSION" + needs: + - job: build + artifacts: true +``` + +**Notes:** + +- The `artifacts:reports:dotenv:` file is sourced as environment variables in downstream jobs that declare `needs: [job: build, artifacts: true]`. +- Variable names in the dotenv file must be valid shell identifiers (no hyphens). +- This pattern only works across jobs with an explicit `needs:` relationship, not across all downstream jobs automatically. + +Source: https://docs.gitlab.com/ci/yaml/#artifactsreportsdotenv + +--- + +## Things that don't translate cleanly + +Some GitHub Actions concepts have no direct GitLab CI/CD equivalent. These require manual decisions +during migration. + +### Composite actions (`uses: ./.github/actions/my-action`) + +Composite actions are reusable step sequences defined in a local directory (`.github/actions/*/action.yml`). GitLab has no single equivalent abstraction, so the right target depends on how the composite is used: + +- **Steps inside a job** (the usual case): inline them with a `!reference [.my_steps, script]` tag, or extract them into a shell script and call it from `script:`. +- **CI/CD components (GitLab 17.0+)**: when the composite maps to one or more whole jobs. Use typed `spec:inputs` to match the composite's inputs, and the dotenv artifact pattern (`artifacts:reports:dotenv` + `needs:`) for outputs. +- **`extends:` patterns or `include:` snippets**: For simpler job-level configuration reuse within the same project or across projects. +- **Child pipelines (`trigger:include:`)**: An escape hatch for composite actions that represent self-contained workflows and benefit from isolated execution context. + +### `actions/setup-*` actions + +`actions/setup-node`, `actions/setup-python`, `actions/setup-java`, etc. configure a language runtime +on the runner. In GitLab, use `image:` to select a pre-built Docker image with the runtime already +installed. This is cleaner and more reproducible. + +```yaml +# GitHub +- uses: actions/setup-node@v4 + with: + node-version: '20' + +# GitLab: just set the image +image: node:20 +``` + +### `github.*` and other context expressions + +GitHub Actions exposes context objects (`github.*`, `runner.*`, `env.*`, `vars.*`, `secrets.*`, `steps.*`, `needs.*`, `inputs.*`, `matrix.*`, `job.*`, `strategy.*`). When translating, treat them in two categories: + +**Category A: GitHub data exposed as predefined variables.** GitLab provides predefined variables for most `github.*` and some `runner.*` expressions. Use the mapping below. + +**Category B: User-defined or runtime data.** `env.*`, `vars.*`, `secrets.*`, `inputs.*`, `matrix.*`, and `steps/needs.*.outputs.*` are not predefined variables. They translate to plain CI/CD variables (`$NAME`), masked variables, or dotenv artifact patterns depending on the source. + +#### `github.*` context + +| GitHub expression | GitLab equivalent | Notes | +|---|---|---| +| `github.sha` | `$CI_COMMIT_SHA` | | +| `github.ref` | `$CI_COMMIT_REF_NAME` | GitHub keeps `refs/heads/` prefix; GitLab strips it for branches but keeps it for tags | +| `github.ref_name` | `$CI_COMMIT_REF_NAME` | Branch or tag name without prefix | +| `github.repository` | `$CI_PROJECT_PATH` | | +| `github.repository_owner` | `$CI_PROJECT_NAMESPACE` | | +| `github.actor` | `$GITLAB_USER_LOGIN` | | +| `github.triggering_actor` | `$GITLAB_USER_LOGIN` | GitLab does not distinguish triggering actor from actor | +| `github.event_name` | `$CI_PIPELINE_SOURCE` | Values differ: `pull_request` -> `merge_request_event`, `schedule` -> `schedule`, `workflow_dispatch` -> `web` or `api` | +| `github.workflow` | `$CI_PIPELINE_NAME` | Requires GitLab 16.3+ and `workflow:name:` set | +| `github.run_id` | `$CI_PIPELINE_ID` | | +| `github.run_number` | `$CI_PIPELINE_IID` | Project-scoped incrementing number | +| `github.run_attempt` | (no equivalent) | Flag as Needs Review | +| `github.job` | `$CI_JOB_NAME` | | +| `github.head_ref` | `$CI_MERGE_REQUEST_SOURCE_BRANCH_NAME` | MR pipelines only | +| `github.base_ref` | `$CI_MERGE_REQUEST_TARGET_BRANCH_NAME` | MR pipelines only | +| `github.workspace` | `$CI_PROJECT_DIR` | | + +#### `runner.*` context + +| GitHub expression | GitLab equivalent | Notes | +|---|---|---| +| `runner.os` | (no predefined variable) | Use `image:` selection or set a custom variable based on the runner tag | +| `runner.arch` | (no predefined variable) | Flag as Needs Review | +| `runner.temp` | (no predefined variable) | Use `/tmp` or a path under `$CI_PROJECT_DIR` | +| `runner.workspace` | `$CI_PROJECT_DIR` | Closest equivalent | + +#### `env.*`, `vars.*`, `secrets.*` (user-defined data) + +| GitHub expression | GitLab equivalent | Notes | +|---|---|---| +| `env.` | `$NAME` | Plain CI/CD variable defined in `variables:` | +| `vars.` | `$NAME` | GitHub's non-secret repo/org variable; same mechanism as `env` in GitLab | +| `secrets.` | `$NAME` | Masked CI/CD variable defined in Project > Settings > CI/CD > Variables | + +#### `inputs.*` context + +| GitHub expression | GitLab equivalent | Notes | +|---|---|---| +| `inputs.` | `$[[ inputs. ]]` (`spec:inputs`) or `$NAME` (variable form) | See the `workflow_dispatch` inputs section for the full pattern | + +#### `steps.*` and `needs.*` context + +| GitHub expression | GitLab equivalent | Notes | +|---|---|---| +| `steps..outputs.` | `$NAME` (via dotenv artifact) | Requires `artifacts:reports:dotenv` and downstream `needs:` relationship | +| `steps..outcome` | (no equivalent) | Flag as Needs Review | +| `steps..conclusion` | (no equivalent) | Flag as Needs Review | +| `needs..outputs.` | `$NAME` (via dotenv artifact) | Same pattern as `steps.outputs` | +| `needs..result` | (no equivalent) | Flag as Needs Review | + +#### `job.*` and `strategy.*` and `matrix.*` context + +| GitHub expression | GitLab equivalent | Notes | +|---|---|---| +| `job.status` | `$CI_JOB_STATUS` | Available only in `after_script:` | +| `job.container.id` | (no equivalent) | Flag as Needs Review | +| `matrix.` | `$NAME` | Injected automatically by `parallel:matrix:` | +| `strategy.fail-fast` | (no keyword equivalent for `true`) | GitLab default behavior is `false` | +| `strategy.job-index` | `$CI_NODE_INDEX` | | +| `strategy.job-total` | `$CI_NODE_TOTAL` | | + +**Agent instruction:** When translating a GitHub workflow, scan for `${{ ... }}` expressions and substitute the GitLab equivalent from the tables above. For expressions not listed, consult https://docs.gitlab.com/ci/variables/predefined_variables/ and pick the closest match. If no GitLab equivalent exists, leave the expression replaced with a clearly marked placeholder (e.g., `# TODO: no GitLab equivalent for github.run_attempt`) and flag it as Needs Review in the migration report. + +Full predefined variable reference: https://docs.gitlab.com/ci/variables/predefined_variables/ + +### `workflow_dispatch` inputs + +GitHub's `on: workflow_dispatch` combines two things: a manual-only trigger and an optional typed input schema. GitLab separates these concerns: `workflow:rules:` controls when the pipeline runs, and `spec:inputs` declares the input schema. + +**Critical behavioral difference:** GitHub `workflow_dispatch` workflows run only on manual trigger. GitLab pipelines run on every push by default. A naive translation breaks immediately if any input lacks a default, because auto-triggered pipelines cannot prompt for values. The full translation pattern requires three pieces: + +1. `spec:inputs` at the top of `.gitlab-ci.yml` for the typed input schema (pipeline-level, GitLab 17.0+) +2. `workflow:rules:` to restrict the pipeline to manual and API triggers only +3. A `default:` on every input so that if the pipeline IS triggered automatically (e.g., by a schedule or API call without explicit inputs), it does not fail + +**GitHub example:** + +```yaml +on: + workflow_dispatch: + inputs: + environment: + description: 'Target environment' + required: true + default: 'staging' + type: choice + options: + - staging + - production + dry_run: + description: 'Run without making changes' + required: false + default: true + type: boolean + version: + description: 'Version to deploy' + required: true + type: string + +jobs: + deploy: + runs-on: ubuntu-latest + steps: + - run: echo "Deploying ${{ inputs.version }} to ${{ inputs.environment }}" +``` + +**GitLab equivalent:** + +```yaml +spec: + inputs: + environment: + description: 'Target environment' + type: string + default: 'staging' + options: + - staging + - production + dry_run: + description: 'Run without making changes' + type: boolean + default: true + version: + description: 'Version to deploy' + type: string + default: '' # Required in GitHub, but GitLab needs a default for auto-triggered safety + +--- + +workflow: + rules: + - if: $CI_PIPELINE_SOURCE == "web" # "Run pipeline" button + - if: $CI_PIPELINE_SOURCE == "api" # Pipeline API / trigger token + - if: $CI_PIPELINE_SOURCE == "trigger" # Trigger API + +deploy: + stage: deploy + script: + - echo "Deploying $[[ inputs.version ]] to $[[ inputs.environment ]]" +``` + +**Input type mapping:** + +| GitHub `workflow_dispatch` type | GitLab `spec:inputs` type | Notes | +|---|---|---| +| `string` | `type: string` | Direct mapping. Use `regex:` for validation. | +| `boolean` | `type: boolean` | Direct mapping. | +| `number` | `type: number` | Direct mapping. | +| `choice` | `type: string` + `options:` | GitLab uses `options:` on a string input instead of a separate `choice` type. | +| `environment` | `type: string` + `options:` | No native environment type. List environment names in `options:` manually. | + +**Notes:** + +- `spec:inputs` uses `$[[ inputs.name ]]` interpolation syntax (double brackets), not `${{ }}`. This is resolved at pipeline creation time, before any job runs. +- `required: true` in GitHub has no direct equivalent. In GitLab, omitting `default:` makes the input mandatory, but mandatory inputs break auto-triggered pipelines. Prefer setting a sensible default and validating in the script. +- The `workflow:rules:` block above restricts the pipeline to manual/API sources. Without this, every push creates a pipeline that uses the default input values, which is rarely the intended behavior for a `workflow_dispatch` translation. +- For GitLab versions before 17.0, use top-level `variables:` with `description:`, `value:`, and `options:` as a simpler alternative. This renders a form in the "Run pipeline" UI but provides no type enforcement (all values are strings). + +Source: https://docs.gitlab.com/ci/inputs/ + +### `actions/github-script` + +`actions/github-script` runs JavaScript with access to the GitHub API client. The GitLab equivalent +is using `glab` CLI or the GitLab REST/GraphQL API directly in a `script:` block with `$CI_JOB_TOKEN` +for authentication. + +### Environment protection rules + +GitHub environments (`environment:`) support required reviewers and wait timers before deployment. +GitLab has equivalent functionality via protected environments (Settings > CI/CD > Environments), +but the configuration is in the UI, not the YAML. The `environment:` keyword in GitLab YAML only +declares which environment a job deploys to. + +### `permissions:` (GITHUB_TOKEN scopes) + +GitHub's `permissions:` restricts the automatically-provided `GITHUB_TOKEN`. GitLab's `CI_JOB_TOKEN` +has a fixed scope controlled at the project level. There is no per-job permission narrowing in GitLab CI/CD YAML. + +### Reusable workflow inputs and outputs + +GitHub reusable workflows support typed `inputs:` and `outputs:` with schema validation. GitLab `include:` +merges YAML at parse time with no input/output contract. Parameterize included jobs via `variables:` and +document the expected variable names in comments. + +--- + +*Source: https://docs.gitlab.com/ci/migration/github_actions/* diff --git a/.agents/skills/github-actions-to-gitlab-ci/references/validator.md b/.agents/skills/github-actions-to-gitlab-ci/references/validator.md new file mode 100644 index 00000000..dc0bb93f --- /dev/null +++ b/.agents/skills/github-actions-to-gitlab-ci/references/validator.md @@ -0,0 +1,91 @@ +# Validator reference + +Detailed handling for `scripts/validate.sh`. Load when something other than the happy path occurs: script not found, permission denied, neither backend installed, no shell or network access, validation fails, or the `glab` backend needs a project. + +The happy path itself stays in `SKILL.md` step 4.5: `bash /scripts/validate.sh /path/to/translated.gitlab-ci.yml`. + +## Finding the script + +The script is at `scripts/validate.sh` inside the skill bundle. The path depends on the host tool: + +- **Claude Code**: use `${CLAUDE_SKILL_DIR}/scripts/validate.sh` +- **opencode and other tools**: the skill is at one of the discovery paths (`~/.claude/skills/github-actions-to-gitlab-ci/`, `~/.config/opencode/skills/github-actions-to-gitlab-ci/`, `~/.agents/skills/github-actions-to-gitlab-ci/`, or a project-local equivalent). Locate the directory you loaded this skill from and append `/scripts/validate.sh`. +- **Fallback**: if you cannot locate the bundled script, run the underlying tool directly: `glci lint -f ` (preferred) or `glab ci lint ` (fallback). The bundled script is a thin wrapper around exactly those commands. + +## Backend selection + +Set `VALIDATE_BACKEND=glci|glab` to force a specific backend. The default is `auto`, which prefers `glci` when installed and falls back to `glab`. + +## `glab` backend project resolution + +The `glab` backend needs a GitLab project context (the lint API is per-project). The script resolves the project from, in order: + +1. A `--project ` flag passed to the script. +2. The `VALIDATE_PROJECT` environment variable. +3. The git remote of the current directory (when the user is inside a GitLab checkout). +4. The user's `glab` default. + +The skill does not hardcode any project. If none of these resolves, the script exits 2 with a clear error explaining the three options the user has: install `glci` instead, set `VALIDATE_PROJECT`, or pass `--project`. For users migrating from GitHub Actions who do not have a GitLab project they can use, the simplest path is to install `glci`, which validates entirely offline and needs no project. + +## Migrating from a GitHub-hosted repo + +Users invoking this skill are almost always sitting in a repo whose origin is on GitHub. That has two consequences: + +- The git-remote step of the `glab` project resolver will never produce a GitLab path. The script's resolver rejects non-GitLab hosts. +- Unless the user has set `VALIDATE_PROJECT` or `--project`, the `glab` path can only fail. + +When you detect (or anticipate) a non-GitLab origin, lead with `glci` from the start: recommend installing it before invoking the validator. This avoids the failed `glab` fallback entirely. + +## Permission denied / missing executable bit + +Two binaries can hit this: the bundled `scripts/validate.sh` itself, and the `glci` binary when installed manually. + +**`validate.sh`**: the executable bit is lost when the skill is copied via tarball or zip (not via `git clone`). Fix once with `chmod +x /scripts/validate.sh`, or always invoke via `bash` as shown in SKILL.md. + +**`glci`**: manual installs via `sudo mv` often preserve whatever mode the downloaded file had and silently produce a binary without the `x` bit. `validate.sh` detects this in standard locations and exits 2 with a pointer. If the script reports glci as missing despite a prior install, check the standard paths: + +```bash +ls -l /usr/local/bin/glci /opt/homebrew/bin/glci ~/bin/glci ~/.local/bin/glci 2>/dev/null +``` + +If a file is present without `x` in its mode, fix it with `sudo chmod +x ` (or without sudo if the file is under `$HOME`). + +## Neither backend installed (exit code 2) + +The script prints install commands for both tools. The agent should not run any install command itself without explicit user consent, and even with consent, prefer asking the user to run it themselves. This sidesteps environments where the agent is sandboxed (no shell access, `curl` blocked, or otherwise restricted) and keeps the user in control of what gets installed on their machine. + +Surface both options with their tradeoffs and let the user pick: + +- **glci** (recommended when the user does not yet have GitLab tooling). Runs offline, no GitLab account or token required, deeper parse, unlocks future structural validation. The bundled `scripts/validate.sh` prints the install one-liner in its error output, pointing at `main/install.sh` which always installs the latest tag. If `curl` is restricted in the user's environment or they prefer a manual install, point them to the releases page: https://gitlab.com/gitlab-org/ci-cd/runner-tools/glci/-/releases. They can download the binary matching their OS/arch (`glci-darwin-arm64`, `glci-linux-amd64`, etc.), mark it executable with `chmod +x`, and move it to a directory on PATH. +- **glab** (recommended if the user already has a GitLab account). Calls the GitLab CI Lint API. Install via the user's preferred method (Homebrew, apt, dnf, winget, manual download). See https://gitlab.com/gitlab-org/cli#installation for the full list. After install, the user runs `glab auth login` once. + +When the user is migrating from GitHub Actions and does not yet have GitLab tooling, lead with `glci` because it does not assume an existing GitLab account. If the user already mentioned a GitLab account or `glab` set up, lead with `glab`. Hand the user the relevant install command (or the releases URL for a manual download), ask them to run it themselves, and once they confirm, re-run `validate.sh` and continue. + +If the user declines both, skip validation in this run and clearly note in the migration report that the translated `.gitlab-ci.yml` was not validated locally. Recommend they validate in the GitLab UI Pipeline editor (**Build > Pipeline editor > Validate**, or **CI/CD > Pipelines > CI/CD Lint** on older versions) after pushing. + +## No shell access + +Some hosts (web-only chat surfaces, read-only review agents, sandboxed environments without shell access) cannot invoke `bash` or run the bundled validator. In that case: + +- Skip the script invocation; do not pretend it ran. +- Mark "local validation skipped (agent has no shell access)" in the migration report so the user knows they are receiving an unvalidated translation. +- Tell the user to validate the YAML in the GitLab UI: open the project on GitLab and go to **Build > Pipeline editor > Validate** (or **CI/CD > Pipelines > CI/CD Lint** on older versions). Paste the YAML, click Validate. Zero local tooling required. +- For future runs, recommend the user install `glci` (preferred) or `glab` locally so subsequent translations can be validated end-to-end. The bundled `scripts/validate.sh` will work on hosts that support bash. + +## No network access + +Two paths to be aware of: + +- `glci lint` is offline by design. If `glci` is installed locally, validation still works without any network. +- `glab ci lint` calls the GitLab CI Lint API and will fail without network. If only `glab` is installed, validation will exit non-zero with a network error; document this in the migration report as "could not validate (no network)" and recommend the user retry when network is available, install `glci` for offline validation, or use the GitLab UI Pipeline editor. + +## Validation failures + +The output lists errors with line numbers. Two responses: + +- **Syntax error**: fix it and re-run the validator. +- **Translation gap**: document the error as a "Needs review" item in the migration report rather than silently shipping a broken file. + +## What the validator does not catch + +The validator does not catch semantic errors (does the pipeline do what the user intended?) or runtime errors (will the scripts succeed?). Those remain the user's responsibility, surfaced via the migration report. diff --git a/.agents/skills/github-actions-to-gitlab-ci/scripts/validate.sh b/.agents/skills/github-actions-to-gitlab-ci/scripts/validate.sh new file mode 100755 index 00000000..6775f928 --- /dev/null +++ b/.agents/skills/github-actions-to-gitlab-ci/scripts/validate.sh @@ -0,0 +1,206 @@ +#!/usr/bin/env bash +# Validate a .gitlab-ci.yml file using glci (preferred) or glab (fallback). +# Usage: ./validate.sh [--project ] [path-to-file] +# Default file: .gitlab-ci.yml in the current directory. +# +# Backend selection (env var VALIDATE_BACKEND): +# auto (default) - prefer glci if installed, fall back to glab +# glci - require glci, do not fall back +# glab - require glab, do not fall back +# +# glci lint runs fully offline. It resolves include:, extends:, !reference, +# default:, and workflow: without a GitLab token. No GitLab project context +# required. Deeper than glab ci lint. +# +# glab ci lint calls the GitLab CI Lint API. Requires glab to be installed and +# authenticated, AND a GitLab project context the user can read. The script +# resolves the project from (in order): +# 1. --project flag +# 2. VALIDATE_PROJECT environment variable +# 3. git remote origin URL (when run inside a GitLab checkout) +# 4. glab's default host project via `glab config get` +# If none of those resolve, the script exits 2 with a clear error. +# +# Exit codes: +# 0 - file is syntactically valid +# 1 - file is invalid (errors printed) +# 2 - file not found, no backend available, no project for glab backend, +# or unknown VALIDATE_BACKEND + +set -uo pipefail + +# Parse args: --project , then positional FILE. +PROJECT_FLAG="" +FILE="" +while [ $# -gt 0 ]; do + case "$1" in + --project) + shift + PROJECT_FLAG="${1:-}" + shift + ;; + --project=*) + PROJECT_FLAG="${1#--project=}" + shift + ;; + -h|--help) + sed -n '1,28p' "$0" | sed 's/^# \{0,1\}//' + exit 0 + ;; + *) + if [ -z "$FILE" ]; then + FILE="$1" + fi + shift + ;; + esac +done +FILE="${FILE:-.gitlab-ci.yml}" +BACKEND="${VALIDATE_BACKEND:-auto}" + +if [ ! -f "$FILE" ]; then + echo "ERROR: $FILE not found" >&2 + exit 2 +fi + +have_glci=0 +have_glab=0 +command -v glci >/dev/null 2>&1 && have_glci=1 +command -v glab >/dev/null 2>&1 && have_glab=1 + +# If glci is not on PATH, check common install paths for an existing file that +# is not executable. Manual installs via `sudo mv` often preserve whatever mode +# the downloaded file had and silently produce an inert binary. +broken_glci_path="" +if [ "$have_glci" = 0 ]; then + for candidate in /usr/local/bin/glci /opt/homebrew/bin/glci "$HOME/bin/glci" "$HOME/.local/bin/glci"; do + if [ -f "$candidate" ] && [ ! -x "$candidate" ]; then + broken_glci_path="$candidate" + break + fi + done +fi + +print_install_pointers() { + # The install script at main/install.sh always pins to the latest tag. + echo "Install glci (preferred, offline, no token):" >&2 + echo " curl -fsSL https://gitlab.com/gitlab-org/ci-cd/runner-tools/glci/-/raw/main/install.sh | bash" >&2 + echo " (or browse releases: https://gitlab.com/gitlab-org/ci-cd/runner-tools/glci/-/releases)" >&2 + echo "Install glab (fallback, requires authenticated GitLab account):" >&2 + echo " https://gitlab.com/gitlab-org/cli#installation" >&2 +} + +# Resolution chain for the glab backend's project context. +# Echoes the resolved "group/project" path on stdout, empty if none found. +resolve_glab_project() { + # 1. Explicit --project flag + if [ -n "$PROJECT_FLAG" ]; then + echo "$PROJECT_FLAG" + return 0 + fi + # 2. VALIDATE_PROJECT env var + if [ -n "${VALIDATE_PROJECT:-}" ]; then + echo "$VALIDATE_PROJECT" + return 0 + fi + # 3. git remote origin URL, parsed for gitlab host + local remote_url + remote_url="$(git config --get remote.origin.url 2>/dev/null || true)" + if [ -n "$remote_url" ]; then + # Strip protocol and host for both SSH and HTTPS remotes. Only matches + # gitlab.com or any *.gitlab.* host; non-GitLab hosts (github.com, etc.) + # are left intact and rejected by the equality check below. The .git + # suffix is stripped separately so a github.com URL with .git does not + # masquerade as a "stripped" GitLab path. + local stripped + stripped="$(echo "$remote_url" \ + | sed -E 's#^(https?://|git@)[^/:]*gitlab[^/:]*[:/]##')" + if [ -n "$stripped" ] && [ "$stripped" != "$remote_url" ]; then + echo "${stripped%.git}" + return 0 + fi + fi + # 4. glab's default host project, if glab is installed + if [ "$have_glab" = 1 ]; then + local glab_default + glab_default="$(glab config get -g host 2>/dev/null || true)" + # glab config does not expose a "default project" directly; users must + # set one explicitly. Skip this step rather than guessing. + : "${glab_default:=}" + fi + echo "" +} + +print_no_project_error() { + echo "ERROR: glab backend requires a GitLab project context." >&2 + echo " No project found via --project flag, VALIDATE_PROJECT env var, git remote, or glab config." >&2 + echo "" >&2 + echo "You have three options:" >&2 + echo " 1. Install glci (preferred; works offline, no GitLab project required):" >&2 + echo " curl -fsSL https://gitlab.com/gitlab-org/ci-cd/runner-tools/glci/-/raw/main/install.sh | bash" >&2 + echo " (or browse releases: https://gitlab.com/gitlab-org/ci-cd/runner-tools/glci/-/releases)" >&2 + echo " 2. Set VALIDATE_PROJECT to a GitLab project you own (or can read):" >&2 + echo " VALIDATE_PROJECT=your-group/your-project bash scripts/validate.sh " >&2 + echo " 3. Pass --project explicitly per invocation:" >&2 + echo " bash scripts/validate.sh --project your-group/your-project " >&2 +} + +case "$BACKEND" in + auto) + if [ "$have_glci" = 1 ]; then + BACKEND=glci + elif [ -n "$broken_glci_path" ]; then + echo "ERROR: found glci at $broken_glci_path but it is not executable." >&2 + echo " Fix it and re-run:" >&2 + echo " chmod +x $broken_glci_path" >&2 + exit 2 + elif [ "$have_glab" = 1 ]; then + BACKEND=glab + echo "Note: glci not detected on PATH; falling back to glab ci lint." >&2 + else + echo "ERROR: neither glci nor glab is installed." >&2 + print_install_pointers + exit 2 + fi + ;; + glci) + if [ "$have_glci" != 1 ]; then + if [ -n "$broken_glci_path" ]; then + echo "ERROR: VALIDATE_BACKEND=glci but glci at $broken_glci_path is not executable." >&2 + echo " Fix it and re-run:" >&2 + echo " chmod +x $broken_glci_path" >&2 + else + echo "ERROR: VALIDATE_BACKEND=glci but glci is not installed." >&2 + print_install_pointers + fi + exit 2 + fi + ;; + glab) + if [ "$have_glab" != 1 ]; then + echo "ERROR: VALIDATE_BACKEND=glab but glab is not installed." >&2 + print_install_pointers + exit 2 + fi + ;; + *) + echo "ERROR: unknown VALIDATE_BACKEND: $BACKEND (expected auto, glci, or glab)" >&2 + exit 2 + ;; +esac + +case "$BACKEND" in + glci) + echo "Validating $FILE via glci lint..." + exec glci lint --quiet -f "$FILE" + ;; + glab) + PROJECT="$(resolve_glab_project)" + if [ -z "$PROJECT" ]; then + print_no_project_error + exit 2 + fi + echo "Validating $FILE via glab ci lint (project: $PROJECT)..." + exec glab ci lint --repo "$PROJECT" "$FILE" + ;; +esac diff --git a/.claude/skills/github-actions-to-gitlab-ci b/.claude/skills/github-actions-to-gitlab-ci new file mode 120000 index 00000000..3d4a8407 --- /dev/null +++ b/.claude/skills/github-actions-to-gitlab-ci @@ -0,0 +1 @@ +../../.agents/skills/github-actions-to-gitlab-ci \ No newline at end of file diff --git a/.github/skills/github-actions-to-gitlab-ci b/.github/skills/github-actions-to-gitlab-ci new file mode 120000 index 00000000..3d4a8407 --- /dev/null +++ b/.github/skills/github-actions-to-gitlab-ci @@ -0,0 +1 @@ +../../.agents/skills/github-actions-to-gitlab-ci \ No newline at end of file diff --git a/src/ai-coding/README.md b/src/ai-coding/README.md index 02f105be..4654e43d 100644 --- a/src/ai-coding/README.md +++ b/src/ai-coding/README.md @@ -34,6 +34,7 @@ npm install --save-dev @tomgrv/devcontainer-features-ai-coding - `.github/workflows/deepseek-review.yml` — runs [DeepSeek Code Review](https://github.com/hustcer/deepseek-review) on every PR open/reopen/sync, and on demand via an `@deepseek` mention in a PR comment, posting an automated review. - `.agents/skills/caveman*` — ultra-compressed communication mode skills (terse mode, commit messages, PR reviews, markdown compression, help reference). Symlinked from `.github/skills/`; installed live for Claude Code and GitHub Copilot. - `.agents/skills/pm/*` — product-management workflow skills: research synthesis, roadmap prioritization brief, PRD drafting, metrics digest, release notes generation. Symlinked from `.github/skills/`; installed live for Claude Code and GitHub Copilot. +- `.agents/skills/github-actions-to-gitlab-ci/` — [GitLab's](https://gitlab.com/gitlab-org/ci-cd/github-actions-to-gitlab-ci) (MIT) GitHub Actions to GitLab CI/CD translator, vendored unchanged. Symlinked from `.github/skills/`. - `.agents/skills/feature-ai-coding/` — self-doc skill for this feature. - `ai-coding.json` (repo root) — the dedicated manifest listing everything installed: `skills` (names under `.agents/skills/`), `agents` (`npx skills` target ids — `claude-code`, `github-copilot`), and `plugins` (Claude Code plugin-marketplace entries — name, marketplace, git URL). Add or drop a skill/agent/plugin here, not in the scripts. Other features that `dependsOn` `ai-coding` can ship their own `stubs/ai-coding.json` fragment — `feature-configure`'s generic JSON-stub merge unions it into this same root file. - `.claude/hooks/configure-skills.sh` — the `npx skills` wrapper. `configure-skills.sh sync` reads root `ai-coding.json` and, for each skill, runs `npx skills add tomgrv/devcontainer-features/.agents/skills/ -a ... -y` for every listed agent, then merges each declared plugin into `.claude/settings.json`'s `enabledPlugins`/`extraKnownMarketplaces` (the settings file Claude Code reads to enable plugins). Runs on devcontainer `postCreate` (`configure-skills.sh`) and on every Claude Code `SessionStart` — so it also covers claude.ai/code web/cloud sessions, which never run devcontainer `postCreate`. @@ -59,3 +60,9 @@ Likewise, add a repository secret named `DEEPSEEK_CHAT_TOKEN` containing a valid Skills already work with DeepSeek and any other file-reading coding assistant with no extra setup: `.agents/skills/` is deployed as plain files (the `universal` agent target in `ai-coding.json`), so any assistant that reads repo files — including DeepSeek-based ones — picks them up without needing a dedicated `npx skills` agent id. `.claude/hooks/configure-skills.sh` requires `jq` on `PATH` (and `npx`/Node.js for the skills-install step); if either is missing, or root `ai-coding.json` isn't found, it logs a warning and exits cleanly rather than failing the session. + +## GitLab CI + +Each workflow above has a GitLab counterpart, deployed as a fragment merged into the repository's `.gitlab-ci.yml` (`stubs/_...gitlab-ci.yml`). `tomgrv/actions` steps run through `zz-use -x tomgrv/actions/`, with the action inputs set as environment variables. + +- `review-deepseek`: pending, manual merge request job (GitLab pipelines do not run on comments); `confirm-pr-comment` and the DeepSeek review still target GitHub. diff --git a/src/ai-coding/stubs/.agents/skills/github-actions-to-gitlab-ci/LICENSE b/src/ai-coding/stubs/.agents/skills/github-actions-to-gitlab-ci/LICENSE new file mode 100644 index 00000000..a724ed88 --- /dev/null +++ b/src/ai-coding/stubs/.agents/skills/github-actions-to-gitlab-ci/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026-present GitLab Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/src/ai-coding/stubs/.agents/skills/github-actions-to-gitlab-ci/SKILL.md b/src/ai-coding/stubs/.agents/skills/github-actions-to-gitlab-ci/SKILL.md new file mode 100644 index 00000000..c7f4493e --- /dev/null +++ b/src/ai-coding/stubs/.agents/skills/github-actions-to-gitlab-ci/SKILL.md @@ -0,0 +1,370 @@ +--- +name: github-actions-to-gitlab-ci +description: Use this skill whenever the user wants to migrate, translate, or convert GitHub Actions workflows to GitLab CI/CD pipelines. This includes converting `.github/workflows/*.yml` files to `.gitlab-ci.yml`, comparing GitHub Actions and GitLab CI/CD syntax, mapping GitHub Actions marketplace actions to GitLab equivalents, or evaluating what a workflow would look like in GitLab. Use this skill if the user mentions both GitHub Actions and GitLab CI in the same context, asks about migrating from GitHub to GitLab, or shows you a workflow YAML and asks how it would work in GitLab. Even if the user does not explicitly say "migrate", if they are exploring GitLab CI/CD coming from a GitHub Actions background, use this skill. +--- + +# GitHub Actions to GitLab CI/CD Migration + +This skill translates GitHub Actions workflows to GitLab CI/CD pipelines. It handles single-file conversions, maps marketplace actions to GitLab equivalents, and produces a migration report categorizing what translates cleanly, what needs approximation, and what requires human review. Use this skill when a user shows you a GitHub workflow and asks how it would work in GitLab, or when they are evaluating GitLab CI/CD coming from a GitHub Actions background. + +## When to use this skill + +Use this skill whenever the user mentions GitHub Actions and GitLab CI/CD in the same context. Concrete triggers include: + +- User shows you a `.github/workflows/*.yml` file and asks "how would this work in GitLab?" +- User asks to migrate, convert, or translate a GitHub Actions workflow +- User compares GitHub Actions syntax to GitLab CI/CD syntax +- User asks about GitLab equivalents for specific GitHub Actions (e.g., "what replaces actions/checkout?") +- User is evaluating GitLab CI/CD as an alternative to GitHub Actions +- User mentions both platforms in a single question, even if they don't explicitly say "migrate" +- User pastes a GitHub workflow and asks for the GitLab equivalent +- User asks how to set up a CI/CD pipeline in GitLab after using GitHub Actions + +Even if the user does not explicitly say "migrate" or "convert", if they are exploring GitLab CI/CD coming from a GitHub Actions background, use this skill. + +## What this skill does well + +- **Real CI/CD workflows**: build, test, deploy, lint, release workflows translate cleanly with high confidence +- **Single-file translations**: one `.github/workflows/*.yml` to one `.gitlab-ci.yml` +- **Marketplace action mapping**: top 50 GitHub Actions have curated GitLab equivalents +- **Caching and artifacts**: native GitLab features map directly from GitHub Actions equivalents +- **Matrix builds**: GitHub's `matrix:` translates to GitLab's `parallel:matrix:` with cleaner syntax +- **Conditional execution**: `if:` conditions translate to `rules:if:` with semantic preservation +- **Migration reporting**: every source line categorized as Translated, Approximated, Needs Review, or Lost +- **Docker-based workflows**: docker/build-push-action and related actions have clear GitLab equivalents +- **Secrets and variables**: GitHub secrets map to GitLab CI/CD variables with clear setup instructions + +## Known limitations + +- **Repo automation workflows**: GitHub Actions like `actions/stale`, `actions/first-interaction`, and `actions/labeler` have no GitLab CI/CD equivalent because they respond to issue/MR/comment events. GitLab CI/CD runs only on git events (push, tag, MR, schedule, manual, API). For these workflows, recommend `gitlab-triage` on a schedule or webhooks + external service. +- **Composite actions**: not supported in v1. Composite actions (`.github/actions/*/action.yml`) require manual translation. See "Composite actions" in `references/syntax-mapping.md` for how to pick the target. Full composite action translation is a candidate for a future version. +- **Reusable workflows with inputs/outputs**: GitHub's `inputs:` and `outputs:` in reusable workflows have no direct GitLab equivalent. Recommend `include:` with variable substitution or child pipelines. +- **Permissions**: GitHub's `permissions:` keyword has no per-job equivalent in GitLab. Workaround: configure at project level (CI/CD token access, project access tokens stored as masked variables). +- **GitHub Enterprise specifics**: this skill targets github.com. GitHub Enterprise Server may have different API endpoints or features. +- **Workflow dispatch inputs**: GitHub's `workflow_dispatch.inputs` translates to GitLab's `spec:inputs` (pipeline-level). The translation requires three pieces: `spec:inputs` for the typed input schema, `workflow:rules:` to gate the pipeline to manual/API triggers only, and a default value on every input (auto-triggered pipelines cannot prompt for inputs and will fail without defaults). See `references/syntax-mapping.md` for a full before/after example. +- **Step outputs**: GitHub's `steps.*.outputs` have no direct GitLab equivalent. Workaround: use artifacts or environment files. + +## High-level workflow + +When invoked, follow this sequence: + +### 1. Read the source workflow + +Read the user's `.github/workflows/*.yml` file. If they paste it inline, work from the paste. If they reference a file path, read it from their repository. Confirm you have the complete workflow before proceeding. Note the workflow name, triggers, and overall structure. + +### 2. Classify the workflow + +Determine which category the workflow falls into: + +- **Real CI/CD workflow** (build, test, deploy, lint, release, publish): high-confidence translation path. Use the references to translate keyword by keyword. These workflows typically have `jobs:` that run `script:` commands and produce artifacts. Examples: Node.js test suite, Docker image build and push, Python package release. +- **Repo automation workflow** (stale, greetings, labeler, dependabot-style, issue triage): limited GitLab CI/CD support. Document the constraint upfront. Suggest alternatives: `gitlab-triage` on a schedule, webhooks + external service, or project access tokens. + +### 3. Translate + +Walk through the workflow keyword by keyword. For each line: + +1. Look up the keyword in `references/syntax-mapping.md` (e.g., `env:`, `jobs:`, `steps:`, `uses:`, `if:`, `runs-on:`, `needs:`) +2. For `uses:` lines, look up the action in `references/marketplace-actions.md` +3. If the lookup fails, apply the "Pattern: actions not in this list" decision tree from marketplace-actions.md +4. Note items that need human review (e.g., complex `if:` conditions, custom actions, permissions) +5. Preserve the logical flow and intent of the original workflow +6. Maintain the same job dependencies and execution order + +### 4. Produce a migration report + +The migration report is the trust mechanism. Categorize every line of the source workflow as: + +- **Translated**: clean 1:1 mapping, no semantic loss +- **Approximated**: GitLab equivalent exists but with caveats (note the caveat) +- **Needs review**: no equivalent or significant semantic differences (the user must decide) +- **Lost**: GitHub feature with no GitLab analogue (e.g., step `name:` labels, `defaults:run:` shell selection) + +Format the report as a markdown table the user can copy into their migration tracking spreadsheet. Include columns: Source Line, Category, GitLab Equivalent, Notes. + +### 4.5. Validate the translated `.gitlab-ci.yml` + +Before presenting the final translation, run the bundled validator. It catches mechanical errors before they reach the user: unknown keywords, malformed YAML, invalid `include:` / `extends:` / `rules:` expressions. + +Invoke it via `bash` from the skill bundle directory: + +```bash +bash /scripts/validate.sh /path/to/translated.gitlab-ci.yml +``` + +The script prefers `glci lint` (offline, no token, deeper parse that resolves `include:`, `extends:`, `!reference`, `default:`, and `workflow:`) and falls back to `glab ci lint`. The validator does not catch semantic or runtime errors; those remain the user's responsibility, surfaced via the migration report. + +Read the script's stdout, not just its exit code: it announces which backend ran. If you see "falling back to glab ci lint" and the user's remote is on GitHub, that fallback will likely produce a 404. Verify `glci` is genuinely absent with `ls -l /usr/local/bin/glci /opt/homebrew/bin/glci ~/bin/glci ~/.local/bin/glci 2>/dev/null` before treating the glab failure as authoritative. + +If anything goes wrong (script not found, permission denied, neither backend installed, no shell access, no network, validation fails, or the `glab` backend needs a project), see [`references/validator.md`](./references/validator.md) for the full handling guide. + +### 5. Suggest GitLab improvements (the upgrade moment) + +After producing the basic translation, opportunistically suggest GitLab features that would improve on the GitHub original. Only suggest these when they actually improve the user's specific workflow: + +- **DAG with `needs:`**: eliminate stage barriers for faster pipelines (jobs run as soon as dependencies complete, not waiting for stage boundaries) +- **`parallel:matrix:`**: cleaner matrix syntax than GitHub's matrix, with better variable scoping +- **`rules:changes:`**: monorepo path-based job filtering (run jobs only when specific files change) +- **Child pipelines**: break very large workflows into smaller, reusable pipelines with `trigger:` +- **`include:`**: share config across projects and workflows +- **GitLab CI templates**: Auto DevOps, Security Scanning, Terraform, SAST, DAST, etc. (no GitHub Actions equivalent) +- **Resource groups**: serialize job execution to prevent concurrent deployments to the same environment +- **Protected environments**: gate deployments by approval rules and environment-specific variables + +## Reference files + +Load these references when translating: + +- 📋 [`references/syntax-mapping.md`](./references/syntax-mapping.md) — Load when translating workflow keywords (jobs, stages, steps, rules, variables, artifacts, caching, matrix, if conditions, runs-on, needs, etc.) +- 🧩 [`references/marketplace-actions.md`](./references/marketplace-actions.md) — Load when you encounter a `uses:` line referencing a marketplace action (actions/checkout, actions/setup-node, docker/build-push-action, codecov/codecov-action, etc.) +- 🛠️ [`references/validator.md`](./references/validator.md) — Load when running `scripts/validate.sh` produces an error, when neither backend is installed, when the agent has no shell or network access, or when you need to handle a validation failure. + +## Self-check rubric + +Before producing the final translation, verify: + +- [ ] Did I remove `actions/checkout` (no equivalent, GitLab clones automatically)? +- [ ] Did I translate `runs-on:` to either an `image:` choice or `tags:` for runner selection? +- [ ] Did I translate `env:` and secrets to `variables:` (with masked variables noted as a manual setup step)? +- [ ] Did I handle every `uses:` line (looked up in marketplace-actions.md or applied the fallback pattern)? +- [ ] Did I translate `if:` conditions to `rules:if:` (NOT a string substitution; semantics differ)? +- [ ] Did I handle artifacts (`upload-artifact` -> `artifacts:`, `download-artifact` -> automatic between stages or `needs:artifacts:`)? +- [ ] Did I document `permissions:` items as manual project-level setup (no per-job equivalent)? +- [ ] Did I translate `concurrency:` to `resource_group:` and `interruptible:` where applicable? +- [ ] Did I produce a migration report categorizing every source item? +- [ ] Did I flag items that need human review explicitly? +- [ ] Did I suggest GitLab-only features that would improve the workflow (only where they apply)? + +## Output format for the user + +When the translation is complete, structure your response as: + +1. **Brief overview** (1-2 sentences: workflow name, complexity, overall verdict) +2. **The translated `.gitlab-ci.yml`** in a fenced code block, ready to copy +3. **Migration report** (markdown table with source line, category, GitLab equivalent, notes) +4. **Manual setup steps** (numbered list, e.g., "Configure CI/CD variable X", "Set schedule in CI/CD > Schedules") +5. **Suggested GitLab improvements** (bulleted, optional, only if applicable) +6. **Honest caveats** (any "needs review" items called out plainly) + +## Common translation patterns + +### Environment variables and secrets + +GitHub: +```yaml +env: + REGISTRY: ghcr.io + IMAGE_NAME: ${{ github.repository }} +jobs: + build: + env: + BUILD_ENV: production + steps: + - run: echo $REGISTRY +``` + +GitLab: +```yaml +variables: + REGISTRY: ghcr.io + IMAGE_NAME: $CI_PROJECT_PATH +build: + variables: + BUILD_ENV: production + script: + - echo $REGISTRY +``` + +Key differences: GitLab uses `$VAR_NAME` (not `${{ }}`), and secrets are CI/CD variables (configured in UI, not in YAML). Masked variables are set in Project > Settings > CI/CD > Variables. + +### Conditional execution + +GitHub: +```yaml +jobs: + deploy: + if: github.ref == 'refs/heads/main' + runs-on: ubuntu-latest +``` + +GitLab: +```yaml +deploy: + rules: + - if: $CI_COMMIT_BRANCH == "main" + image: ubuntu:latest +``` + +Key differences: `if:` becomes `rules:if:`, and the condition syntax changes from `${{ }}` expressions to shell-like expressions. Common variables: `$CI_COMMIT_BRANCH`, `$CI_COMMIT_TAG`, `$CI_PIPELINE_SOURCE`. When translating GitHub context expressions (`${{ github.X }}`, `${{ runner.X }}`, `${{ inputs.X }}`, etc.), look up the GitLab predefined variable equivalent and substitute it directly. Use the mapping table in `references/syntax-mapping.md` (`### github.* context expressions` section) for common expressions. For expressions not in the table, consult the canonical reference at https://docs.gitlab.com/ci/variables/predefined_variables/ and pick the closest match. Flag mappings with no GitLab equivalent (e.g., `github.run_attempt`, `runner.arch`) as Needs Review in the migration report. + +### Matrix builds + +GitHub: +```yaml +jobs: + test: + strategy: + matrix: + node-version: [14, 16, 18] + os: [ubuntu-latest, macos-latest] +``` + +GitLab: +```yaml +test: + parallel: + matrix: + - NODE_VERSION: ["14", "16", "18"] + OS: ["ubuntu-latest", "macos-latest"] +``` + +Key differences: GitLab's `parallel:matrix:` is cleaner and uses environment variables directly. Variables are automatically available in the job. + +### Artifacts and caching + +GitHub: +```yaml +- uses: actions/upload-artifact@v3 + with: + name: build-output + path: dist/ +- uses: actions/cache@v3 + with: + path: node_modules + key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }} +``` + +GitLab: +```yaml +artifacts: + name: build-output + paths: + - dist/ +cache: + key: $CI_COMMIT_REF_SLUG + paths: + - node_modules/ +``` + +Key differences: GitLab artifacts are automatic between stages. Cache keys use GitLab variables. No explicit download step needed. + +### Docker image building + +GitHub: +```yaml +- uses: docker/build-push-action@v4 + with: + push: true + tags: ghcr.io/user/image:latest + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} +``` + +GitLab: +```yaml +script: + - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY + - docker build -t $CI_REGISTRY_IMAGE:latest . + - docker push $CI_REGISTRY_IMAGE:latest +``` + +Key differences: GitLab provides built-in registry variables (`$CI_REGISTRY`, `$CI_REGISTRY_IMAGE`, `$CI_REGISTRY_USER`, `$CI_REGISTRY_PASSWORD`). Use `services: docker:dind` for Docker-in-Docker. + +## Style guide + +- Imperative, direct, factual +- No em dashes (use commas, periods, colons instead) +- Tone neutral, not GitLab-evangelical +- Acknowledge GitHub Actions as a legitimate prior choice +- When something cannot translate, say so plainly and suggest alternatives +- Avoid "MUST" and "NEVER" in all caps; use imperative form instead + +## Critical facts (do not get wrong) + +- `actions/checkout` has NO equivalent. GitLab clones the repository automatically. +- GitLab CI/CD pipelines run on git events only (push, tag, MR, schedule, manual, API). They do NOT run on issue/MR/comment/discussion events. +- `permissions:` has no per-job equivalent in GitLab. Use project-level CI/CD token access settings or project access tokens stored as masked variables. +- Schedules are configured in the GitLab UI under CI/CD > Schedules, not in YAML. Use `rules:if: $CI_PIPELINE_SOURCE == "schedule"` to gate jobs. +- Secrets in GitLab are CI/CD variables (masked, protected). Access syntax is `$VAR_NAME`, not `${{ secrets.NAME }}`. +- `if:` syntax differs significantly. NOT a direct string substitution. GitHub uses `${{ }}` expressions; GitLab uses shell-like expressions in `rules:if:`. +- For workflows that handle issues/MRs/comments (stale, greetings, labelers): there is no GitLab CI/CD equivalent. Recommend `gitlab-triage` on a schedule, or webhooks + an external service. +- `needs:` in GitLab has stricter rules around stages than GitHub's `needs:`. A job can only depend on jobs in earlier stages unless you use DAG mode. +- `services:` in GitLab is equivalent to GitHub's `services:` for running sidecar containers (databases, registries, etc.). +- `artifacts:` in GitLab is equivalent to GitHub's `upload-artifact` and `download-artifact` combined. +- `before_script:` and `after_script:` are GitLab equivalents for setup and cleanup steps. +- `tags:` in GitLab selects runners by label, equivalent to GitHub's `runs-on:` for self-hosted runners. + +## Workflow structure comparison + +### GitHub Actions structure + +```yaml +name: CI +on: + push: + branches: [main] + pull_request: + branches: [main] + +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 18 + - run: npm install + - run: npm test +``` + +### GitLab CI/CD equivalent + +```yaml +stages: + - test + +test: + stage: test + image: node:18 + script: + - npm install + - npm test +``` + +Key structural differences: GitLab uses `stages:` to group jobs, not `jobs:` with triggers. Triggers are implicit (every push runs the pipeline). Use `rules:` to filter which jobs run. + +## Troubleshooting common issues + +**Issue: "My workflow uses `actions/checkout` but I don't see it in the GitLab version"** +Solution: GitLab clones the repository automatically. Remove the checkout step entirely. The working directory is already populated with the repository contents. + +**Issue: "My `if:` condition doesn't work in GitLab"** +Solution: GitHub's `if:` uses `${{ }}` expression syntax. GitLab's `rules:if:` uses shell-like expressions. Common conversions: +- `github.ref == 'refs/heads/main'` -> `$CI_COMMIT_BRANCH == "main"` +- `github.event_name == 'pull_request'` -> `$CI_PIPELINE_SOURCE == "merge_request_event"` +- `contains(github.event.head_commit.message, '[skip ci]')` -> `$CI_COMMIT_MESSAGE =~ /\[skip ci\]/` + +**Issue: "My secrets aren't available in GitLab"** +Solution: GitLab CI/CD variables are configured in the UI (Project > Settings > CI/CD > Variables), not in YAML. Create masked variables for sensitive data. Access them with `$VAR_NAME` in scripts. + +**Issue: "My matrix build isn't working"** +Solution: GitLab's `parallel:matrix:` syntax differs from GitHub's. Use environment variable names (uppercase) and quote all values. Variables are automatically available in the job. + +**Issue: "I need to run a job only on schedule"** +Solution: Use `rules:if: $CI_PIPELINE_SOURCE == "schedule"`. Configure the schedule in Project > CI/CD > Schedules. + +## Next steps after translation + +1. **Validate the YAML**: Use `gitlab-ci-lint` or the GitLab UI (CI/CD > Pipelines > CI/CD Lint) to check syntax. +2. **Set up CI/CD variables**: Configure secrets and environment variables in Project > Settings > CI/CD > Variables. +3. **Configure runners**: Ensure you have runners available (shared runners or self-hosted). Check Project > Settings > CI/CD > Runners. +4. **Test the pipeline**: Push a commit to trigger the pipeline. Monitor the pipeline in CI/CD > Pipelines. +5. **Iterate**: Adjust the `.gitlab-ci.yml` based on pipeline results. Common issues: missing dependencies, incorrect image, permission errors. + +## When to ask for help + +- If a GitHub Actions marketplace action has no clear GitLab equivalent, check the "Pattern: actions not in this list" section of `references/marketplace-actions.md`. +- If a workflow uses composite actions or reusable workflows, manual translation may be needed. See "Composite actions" in `references/syntax-mapping.md` for how to pick the target. +- If a workflow responds to issue/MR/comment events, recommend `gitlab-triage` or webhooks + external service. +- If you're unsure about a translation, flag it as "Needs review" in the migration report and let the user decide. diff --git a/src/ai-coding/stubs/.agents/skills/github-actions-to-gitlab-ci/references/marketplace-actions.md b/src/ai-coding/stubs/.agents/skills/github-actions-to-gitlab-ci/references/marketplace-actions.md new file mode 100644 index 00000000..a0cba551 --- /dev/null +++ b/src/ai-coding/stubs/.agents/skills/github-actions-to-gitlab-ci/references/marketplace-actions.md @@ -0,0 +1,1042 @@ +# Marketplace Actions Mapping + +This reference maps popular GitHub Actions marketplace actions to their GitLab CI/CD equivalents. Use this guide when migrating workflows from GitHub Actions to GitLab CI/CD to quickly identify the right approach for each action your workflow depends on. + +## How to use this reference + +1. Find your action in the list below, organized by tier (frequency of use). +2. Read the "What it does" section to confirm it matches your use case. +3. Follow the "Before/After" example to translate the step. +4. Check "Notes" for any gotchas or special handling required. +5. For actions not listed, see the "Pattern: actions not in this list" section. + +## At a glance (table) + +| Action | Tier | GitLab Equivalent | Notes | +|--------|------|-------------------|-------| +| actions/checkout | 1 | Remove (auto-cloned) | GitLab clones repo automatically | +| actions/setup-node | 1 | `image: node:` | Use Docker image or asdf | +| actions/setup-python | 1 | `image: python:` | Use Docker image or asdf | +| actions/setup-java | 1 | `image: eclipse-temurin:` | Use Docker image or asdf | +| actions/setup-go | 1 | `image: golang:` | Use Docker image or asdf | +| actions/setup-ruby | 1 | `image: ruby:` | Use Docker image or asdf | +| actions/cache | 1 | `cache:` keyword | Native GitLab feature | +| actions/upload-artifact | 1 | `artifacts:` keyword | Native GitLab feature | +| actions/download-artifact | 1 | `needs:artifacts:` or `artifacts:` | Automatic between stages | +| docker/build-push-action | 1 | `docker build` + `docker push` | Use `services: docker:dind` | +| docker/login-action | 1 | `docker login` in script | Use CI/CD variables | +| docker/setup-buildx-action | 1 | `docker buildx` in script | Use `services: docker:dind` | +| docker/setup-qemu-action | 1 | `docker run --rm --privileged tonistiigi/binfmt` | Multi-arch builds | +| actions/github-script | 1 | GitLab API calls or glab | Needs review, no direct equivalent | +| actions/setup-dotnet | 2 | `image: mcr.microsoft.com/dotnet/sdk:` | Use Docker image | +| shivammathur/setup-php | 2 | `image: php:` | Use Docker image or custom | +| aws-actions/configure-aws-credentials | 2 | CI/CD variables or OIDC | Use GitLab OIDC to AWS | +| google-github-actions/auth | 2 | CI/CD variables or OIDC | Use GitLab OIDC to GCP | +| azure/login | 2 | CI/CD variables or OIDC | Use GitLab OIDC to Azure | +| hashicorp/setup-terraform | 2 | `include: 'Terraform/Base.latest.gitlab-ci.yml'` | GitLab template | +| codecov/codecov-action | 2 | `codecov-cli` in script | Use codecov CLI directly | +| softprops/action-gh-release | 2 | `glab release create` or API | Use glab or REST API | +| peter-evans/create-pull-request | 2 | `glab mr create` or API | Needs review, use glab | +| JamesIves/github-pages-deploy-action | 2 | GitLab Pages native | Use `pages:` job | +| pnpm/action-setup | 2 | `image: node:` + pnpm script | Install pnpm in script | +| oven-sh/setup-bun | 2 | `image: oven/bun:` | Use Docker image | +| gradle/gradle-build-action | 2 | `image: gradle:` | Use Docker image | +| cypress-io/github-action | 2 | `image: cypress/browsers:` | Use Docker image | +| tj-actions/changed-files | 3 | `git diff` in script | Use git commands directly | +| dorny/paths-filter | 3 | `rules:changes:` keyword | Native GitLab feature | +| nick-invision/retry | 3 | `retry:` keyword | Native GitLab feature | +| pre-commit/action | 3 | `pre-commit run` in script | Run pre-commit in script | +| slackapi/slack-github-action | 3 | Slack webhook or API | Use curl or Slack SDK | + +## Tier 1: Used in 80%+ of workflows + +### actions/checkout + +**What it does**: Clones the repository into the runner's workspace. + +**GitLab equivalent**: Remove this step entirely. GitLab CI/CD automatically clones the repository before running each job. + +**Before** (GitHub Actions): +```yaml +- uses: actions/checkout@v4 + with: + fetch-depth: 0 +``` + +**After** (GitLab CI/CD): +```yaml +# No step needed. GitLab clones automatically. +# To fetch full history, use: +variables: + GIT_DEPTH: 0 +``` + +**Notes**: +- GitLab clones the repository automatically for every job. There is no equivalent action to add. +- Use `GIT_DEPTH: 0` to fetch full history instead of shallow clones. +- Use `GIT_SUBMODULE_STRATEGY: recursive` to clone submodules. + +### actions/setup-node + +**What it does**: Installs Node.js and optionally configures npm/yarn/pnpm caching. + +**GitLab equivalent**: Use a Node.js Docker image, optionally combined with `cache:` for package manager caching. + +**Before** (GitHub Actions): +```yaml +- uses: actions/setup-node@v4 + with: + node-version: '20' + cache: 'npm' +``` + +**After** (GitLab CI/CD): +```yaml +image: node:20 + +cache: + paths: + - node_modules/ +``` + +**Notes**: +- Docker images are the preferred approach in GitLab CI/CD. Use `node:` for LTS versions or `node:latest` for the latest version. +- For caching, use the `cache:` keyword with paths like `node_modules/` or `.npm/`. +- If you need multiple Node.js versions in one pipeline, use `image:` at the job level instead of globally. + +### actions/setup-python + +**What it does**: Installs Python and optionally configures pip caching. + +**GitLab equivalent**: Use a Python Docker image, optionally combined with `cache:` for pip caching. + +**Before** (GitHub Actions): +```yaml +- uses: actions/setup-python@v4 + with: + python-version: '3.11' + cache: 'pip' +``` + +**After** (GitLab CI/CD): +```yaml +image: python:3.11 + +cache: + paths: + - .cache/pip/ +``` + +**Notes**: +- Use `python:` Docker images. For specific patch versions, use `python:3.11.5`. +- Configure pip to use a cache directory with `pip install --cache-dir .cache/pip`. +- For Poetry or other package managers, adjust the cache path accordingly. + +### actions/setup-java + +**What it does**: Installs Java and optionally configures Maven/Gradle caching. + +**GitLab equivalent**: Use an Eclipse Temurin or OpenJDK Docker image, optionally combined with `cache:`. + +**Before** (GitHub Actions): +```yaml +- uses: actions/setup-java@v3 + with: + java-version: '17' + distribution: 'temurin' + cache: 'maven' +``` + +**After** (GitLab CI/CD): +```yaml +image: eclipse-temurin:17 + +cache: + paths: + - .m2/repository/ +``` + +**Notes**: +- Use `eclipse-temurin:` for the official Eclipse Temurin images. +- For Maven, cache `.m2/repository/`. For Gradle, cache `.gradle/`. +- Ensure your build tool is configured to use the cache directory. + +### actions/setup-go + +**What it does**: Installs Go and optionally configures module caching. + +**GitLab equivalent**: Use a Go Docker image, optionally combined with `cache:`. + +**Before** (GitHub Actions): +```yaml +- uses: actions/setup-go@v4 + with: + go-version: '1.21' + cache: true +``` + +**After** (GitLab CI/CD): +```yaml +image: golang:1.21 + +cache: + paths: + - .cache/go-build/ + - go/pkg/mod/ +``` + +**Notes**: +- Use `golang:` Docker images. +- Go modules are cached automatically in `$GOPATH/pkg/mod/`. Set `GOMODCACHE` to customize the location. +- Build cache is stored in `$GOCACHE`, typically `.cache/go-build/`. + +### actions/setup-ruby + +**What it does**: Installs Ruby and optionally configures Bundler caching. + +**GitLab equivalent**: Use a Ruby Docker image, optionally combined with `cache:`. + +**Before** (GitHub Actions): +```yaml +- uses: actions/setup-ruby@v1 + with: + ruby-version: '3.2' + bundler-cache: true +``` + +**After** (GitLab CI/CD): +```yaml +image: ruby:3.2 + +cache: + paths: + - vendor/bundle/ + +before_script: + - bundle install --jobs $(nproc) --retry 3 --path vendor/bundle +``` + +**Notes**: +- Use `ruby:` Docker images. +- Bundler caches gems in `vendor/bundle/` by default when using `bundle install --path vendor/bundle`. +- The `ruby/setup-ruby` action is similar; use the same Docker image approach. + +### actions/cache + +**What it does**: Caches dependencies or build artifacts between workflow runs. + +**GitLab equivalent**: Use the native `cache:` keyword in your job definition. + +**Before** (GitHub Actions): +```yaml +- uses: actions/cache@v3 + with: + path: ~/.npm + key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }} + restore-keys: | + ${{ runner.os }}-npm- +``` + +**After** (GitLab CI/CD): +```yaml +cache: + key: + files: + - package-lock.json + paths: + - .npm/ +``` + +**Notes**: +- GitLab's `cache:` keyword is simpler and more integrated. Keys are automatically generated from file hashes. +- Use `cache:key:files:` to generate cache keys based on file contents. +- Cache is shared across all jobs in a pipeline by default. Use `cache:key:` to create separate caches. + +### actions/upload-artifact + +**What it does**: Uploads build artifacts or test results for later download or retention. + +**GitLab equivalent**: Use the native `artifacts:` keyword in your job definition. + +**Before** (GitHub Actions): +```yaml +- uses: actions/upload-artifact@v3 + with: + name: test-results + path: test-results/ + retention-days: 30 +``` + +**After** (GitLab CI/CD): +```yaml +artifacts: + name: test-results + paths: + - test-results/ + expire_in: 30 days +``` + +**Notes**: +- GitLab's `artifacts:` keyword is native and automatically available to downstream jobs. +- Use `expire_in:` to set retention time (e.g., `30 days`, `1 week`). +- Artifacts are automatically passed to jobs that depend on this job via `needs:`. + +### actions/download-artifact + +**What it does**: Downloads artifacts uploaded by a previous job in the workflow. + +**GitLab equivalent**: Use `needs:artifacts:` or rely on automatic artifact passing between stages. + +**Before** (GitHub Actions): +```yaml +- uses: actions/download-artifact@v3 + with: + name: test-results + path: ./test-results/ +``` + +**After** (GitLab CI/CD): +```yaml +# Option 1: Automatic (if job is in previous stage) +# Artifacts are automatically available + +# Option 2: Explicit dependency +needs: + - job: build-job + artifacts: true +``` + +**Notes**: +- In GitLab CI/CD, artifacts from jobs in previous stages are automatically available. +- Use `needs:artifacts:` only when you need artifacts from a job outside the normal stage dependency. +- Artifacts are extracted to the job's working directory automatically. + +### docker/build-push-action + +**What it does**: Builds a Docker image and pushes it to a registry. + +**GitLab equivalent**: Use `docker build` and `docker push` commands in a script, with `services: docker:dind`. + +**Before** (GitHub Actions): +```yaml +- uses: docker/build-push-action@v5 + with: + context: . + push: true + tags: myregistry.azurecr.io/myimage:latest + username: ${{ secrets.REGISTRY_USERNAME }} + password: ${{ secrets.REGISTRY_PASSWORD }} +``` + +**After** (GitLab CI/CD): +```yaml +image: docker:latest +services: + - docker:dind + +script: + - docker login -u $REGISTRY_USERNAME -p $REGISTRY_PASSWORD myregistry.azurecr.io + - docker build -t myregistry.azurecr.io/myimage:latest . + - docker push myregistry.azurecr.io/myimage:latest +``` + +**Notes**: +- Use `services: docker:dind` to enable Docker-in-Docker for building images. +- Credentials should be stored as CI/CD variables (e.g., `REGISTRY_USERNAME`, `REGISTRY_PASSWORD`). +- For advanced builds, consider using GitLab's `Docker.latest.gitlab-ci.yml` template. + +### docker/login-action + +**What it does**: Authenticates with a Docker registry. + +**GitLab equivalent**: Use `docker login` command in a script with CI/CD variables. + +**Before** (GitHub Actions): +```yaml +- uses: docker/login-action@v2 + with: + registry: myregistry.azurecr.io + username: ${{ secrets.REGISTRY_USERNAME }} + password: ${{ secrets.REGISTRY_PASSWORD }} +``` + +**After** (GitLab CI/CD): +```yaml +script: + - docker login -u $REGISTRY_USERNAME -p $REGISTRY_PASSWORD myregistry.azurecr.io +``` + +**Notes**: +- Store registry credentials as CI/CD variables (masked for security). +- For Docker Hub, omit the registry URL or use `docker.io`. +- For AWS ECR, use `aws ecr get-login-password` instead. + +### docker/setup-buildx-action + +**What it does**: Sets up Docker Buildx for advanced multi-platform builds. + +**GitLab equivalent**: Use `docker buildx` commands in a script with `services: docker:dind`. + +**Before** (GitHub Actions): +```yaml +- uses: docker/setup-buildx-action@v2 +``` + +**After** (GitLab CI/CD): +```yaml +image: docker:latest +services: + - docker:dind + +script: + - docker run --rm --privileged docker/binfmt:latest + - docker buildx create --use + - docker buildx build --platform linux/amd64,linux/arm64 -t myimage:latest . +``` + +**Notes**: +- Buildx is included in recent Docker images. Enable it with `docker buildx create --use`. +- For multi-platform builds, use `docker run --rm --privileged docker/binfmt:latest` to register additional architectures. + +### docker/setup-qemu-action + +**What it does**: Sets up QEMU for multi-architecture Docker builds. + +**GitLab equivalent**: Run `docker run --rm --privileged tonistiigi/binfmt` to register additional architectures. + +**Before** (GitHub Actions): +```yaml +- uses: docker/setup-qemu-action@v2 + with: + platforms: linux/amd64,linux/arm64 +``` + +**After** (GitLab CI/CD): +```yaml +image: docker:latest +services: + - docker:dind + +script: + - docker run --rm --privileged tonistiigi/binfmt --install linux/amd64,linux/arm64 + - docker buildx build --platform linux/amd64,linux/arm64 -t myimage:latest . +``` + +**Notes**: +- QEMU is required for building images for architectures different from the host. +- Use `tonistiigi/binfmt` to register additional architectures. + +### actions/github-script + +**What it does**: Runs JavaScript code with access to the GitHub API. + +**GitLab equivalent**: Use GitLab API calls via `curl` or the `glab` CLI, or use a scripting language like Python or Node.js. + +**Before** (GitHub Actions): +```yaml +- uses: actions/github-script@v7 + with: + script: | + const issues = await github.rest.issues.listForRepo({ + owner: context.repo.owner, + repo: context.repo.repo, + }); + console.log(issues.data); +``` + +**After** (GitLab CI/CD): +```yaml +image: curlimages/curl:latest + +script: + - curl --header "PRIVATE-TOKEN: $CI_JOB_TOKEN" \ + "https://gitlab.com/api/v4/projects/$CI_PROJECT_ID/issues" \ + | jq . +``` + +**Notes**: +- There is no direct equivalent to `actions/github-script`. Use the GitLab API directly via `curl` or `glab`. +- Use `$CI_JOB_TOKEN` for authentication within the same GitLab instance. +- For complex logic, use a scripting language like Python or Node.js. +- This translation often requires review to ensure API calls are correct. + +## Tier 2: Common (20-80%) + +### actions/setup-dotnet + +**What it does**: Installs .NET SDK and optionally configures NuGet caching. + +**GitLab equivalent**: Use a .NET Docker image, optionally combined with `cache:`. + +**Before** (GitHub Actions): +```yaml +- uses: actions/setup-dotnet@v3 + with: + dotnet-version: '7.0' +``` + +**After** (GitLab CI/CD): +```yaml +image: mcr.microsoft.com/dotnet/sdk:7.0 + +cache: + paths: + - ~/.nuget/packages/ +``` + +**Notes**: +- Use `mcr.microsoft.com/dotnet/sdk:` for the official Microsoft .NET images. +- NuGet packages are cached in `~/.nuget/packages/` by default. + +### shivammathur/setup-php + +**What it does**: Installs PHP with extensions and optionally configures Composer caching. + +**GitLab equivalent**: Use a PHP Docker image with extensions, optionally combined with `cache:`. + +**Before** (GitHub Actions): +```yaml +- uses: shivammathur/setup-php@v2 + with: + php-version: '8.2' + extensions: mbstring, intl + tools: composer:v2 +``` + +**After** (GitLab CI/CD): +```yaml +image: php:8.2 + +before_script: + - apt-get update && apt-get install -y libicu-dev + - docker-php-ext-install mbstring intl + - curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer + +cache: + paths: + - vendor/ +``` + +**Notes**: +- PHP Docker images require manual extension installation via `docker-php-ext-install`. +- Install system dependencies before PHP extensions. +- Composer is not included by default; install it in `before_script:`. + +### aws-actions/configure-aws-credentials + +**What it does**: Configures AWS credentials for use in subsequent steps. + +**GitLab equivalent**: Set AWS environment variables via CI/CD variables, or use GitLab's native OIDC to AWS. + +**Before** (GitHub Actions): +```yaml +- uses: aws-actions/configure-aws-credentials@v2 + with: + aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} + aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + aws-region: us-east-1 +``` + +**After** (GitLab CI/CD): +```yaml +variables: + AWS_ACCESS_KEY_ID: $AWS_ACCESS_KEY_ID + AWS_SECRET_ACCESS_KEY: $AWS_SECRET_ACCESS_KEY + AWS_DEFAULT_REGION: us-east-1 + +script: + - aws s3 ls +``` + +**Notes**: +- Store AWS credentials as CI/CD variables (masked for security). +- For better security, use GitLab's native OIDC to AWS instead of long-lived credentials. +- Set `AWS_DEFAULT_REGION` to specify the region. + +### google-github-actions/auth + +**What it does**: Authenticates with Google Cloud Platform. + +**GitLab equivalent**: Use CI/CD variables or GitLab's native OIDC to GCP. + +**Before** (GitHub Actions): +```yaml +- uses: google-github-actions/auth@v1 + with: + credentials_json: ${{ secrets.GCP_SA_KEY }} +``` + +**After** (GitLab CI/CD): +```yaml +variables: + GOOGLE_APPLICATION_CREDENTIALS: /tmp/gcp-key.json + +before_script: + - echo $GCP_SA_KEY | base64 -d > $GOOGLE_APPLICATION_CREDENTIALS + +script: + - gcloud auth activate-service-account --key-file=$GOOGLE_APPLICATION_CREDENTIALS +``` + +**Notes**: +- Store the GCP service account key as a CI/CD variable (base64-encoded). +- For better security, use GitLab's native OIDC to GCP. +- Decode the key before using it with `gcloud` or other tools. + +### azure/login + +**What it does**: Authenticates with Microsoft Azure. + +**GitLab equivalent**: Use CI/CD variables or GitLab's native OIDC to Azure. + +**Before** (GitHub Actions): +```yaml +- uses: azure/login@v1 + with: + creds: ${{ secrets.AZURE_CREDENTIALS }} +``` + +**After** (GitLab CI/CD): +```yaml +variables: + AZURE_SUBSCRIPTION_ID: $AZURE_SUBSCRIPTION_ID + AZURE_TENANT_ID: $AZURE_TENANT_ID + AZURE_CLIENT_ID: $AZURE_CLIENT_ID + AZURE_CLIENT_SECRET: $AZURE_CLIENT_SECRET + +before_script: + - az login --service-principal -u $AZURE_CLIENT_ID -p $AZURE_CLIENT_SECRET --tenant $AZURE_TENANT_ID + +script: + - az vm list +``` + +**Notes**: +- Store Azure credentials as CI/CD variables (masked for security). +- For better security, use GitLab's native OIDC to Azure. +- Use `az login` with service principal credentials. + +### hashicorp/setup-terraform + +**What it does**: Installs Terraform and optionally configures caching. + +**GitLab equivalent**: Use GitLab's `Terraform/Base.latest.gitlab-ci.yml` template or a Terraform Docker image. + +**Before** (GitHub Actions): +```yaml +- uses: hashicorp/setup-terraform@v2 + with: + terraform_version: 1.5.0 +``` + +**After** (GitLab CI/CD): +```yaml +include: + - template: Terraform/Base.latest.gitlab-ci.yml + +variables: + TF_VERSION: 1.5.0 +``` + +**Notes**: +- GitLab provides a built-in Terraform template that handles setup and caching. +- Alternatively, use `image: hashicorp/terraform:` for a Docker-based approach. +- The template includes best practices for Terraform in CI/CD. + +### codecov/codecov-action + +**What it does**: Uploads code coverage reports to Codecov. + +**GitLab equivalent**: Use the `codecov-cli` tool directly in a script. + +**Before** (GitHub Actions): +```yaml +- uses: codecov/codecov-action@v3 + with: + files: ./coverage.xml + token: ${{ secrets.CODECOV_TOKEN }} +``` + +**After** (GitLab CI/CD): +```yaml +image: python:3.11 + +script: + - pip install codecov-cli + - codecov upload-file --file coverage.xml --token $CODECOV_TOKEN +``` + +**Notes**: +- Install `codecov-cli` via pip or use a Docker image that includes it. +- Store the Codecov token as a CI/CD variable (masked for security). +- Ensure your test runner generates coverage reports in a format Codecov supports. + +### softprops/action-gh-release + +**What it does**: Creates a GitHub release with assets. + +**GitLab equivalent**: Use `glab release create` or the GitLab REST API. + +**Before** (GitHub Actions): +```yaml +- uses: softprops/action-gh-release@v1 + with: + files: dist/* + token: ${{ secrets.GITHUB_TOKEN }} +``` + +**After** (GitLab CI/CD): +```yaml +image: alpine:latest + +before_script: + - apk add --no-cache curl + +script: + - glab release create v1.0.0 --assets dist/* +``` + +**Notes**: +- Use `glab release create` for GitLab releases. +- For GitHub releases, use the GitHub REST API via `curl`. +- Store authentication tokens as CI/CD variables (masked for security). + +### peter-evans/create-pull-request + +**What it does**: Creates a pull request (or merge request) with changes. + +**GitLab equivalent**: Use `glab mr create` or the GitLab REST API. + +**Before** (GitHub Actions): +```yaml +- uses: peter-evans/create-pull-request@v5 + with: + commit-message: 'Update dependencies' + title: 'Automated dependency update' + body: 'This PR updates dependencies.' +``` + +**After** (GitLab CI/CD): +```yaml +image: alpine:latest + +before_script: + - apk add --no-cache git curl + +script: + - git config user.email "ci@example.com" + - git config user.name "CI Bot" + - git checkout -b update-deps + - git add . + - git commit -m "Update dependencies" + - glab mr create --title "Automated dependency update" --description "This MR updates dependencies." +``` + +**Notes**: +- Use `glab mr create` for GitLab merge requests. +- Configure git user before committing. +- This translation often requires review to ensure the MR is created correctly. + +### JamesIves/github-pages-deploy-action + +**What it does**: Deploys a static site to GitHub Pages. + +**GitLab equivalent**: Use GitLab Pages native feature with the `pages:` job. + +**Before** (GitHub Actions): +```yaml +- uses: JamesIves/github-pages-deploy-action@v4 + with: + folder: build +``` + +**After** (GitLab CI/CD): +```yaml +pages: + stage: deploy + script: + - mkdir -p public + - cp -r build/* public/ + artifacts: + paths: + - public + only: + - master +``` + +**Notes**: +- GitLab Pages is a native feature. Use the `pages:` job to deploy. +- Artifacts must be in the `public/` directory. +- The `pages:` job automatically deploys to GitLab Pages. + +### pnpm/action-setup + +**What it does**: Installs pnpm package manager. + +**GitLab equivalent**: Install pnpm in a script using npm or curl. + +**Before** (GitHub Actions): +```yaml +- uses: pnpm/action-setup@v2 + with: + version: 8 +``` + +**After** (GitLab CI/CD): +```yaml +image: node:20 + +before_script: + - npm install -g pnpm@8 + - pnpm install +``` + +**Notes**: +- Install pnpm globally via npm: `npm install -g pnpm@`. +- Alternatively, use `curl -fsSL https://get.pnpm.io/install.sh | sh -`. +- Cache pnpm store with `cache:paths: - .pnpm-store/`. + +### oven-sh/setup-bun + +**What it does**: Installs Bun JavaScript runtime. + +**GitLab equivalent**: Use a Bun Docker image. + +**Before** (GitHub Actions): +```yaml +- uses: oven-sh/setup-bun@v1 + with: + bun-version: latest +``` + +**After** (GitLab CI/CD): +```yaml +image: oven/bun:latest + +cache: + paths: + - .bun/install/cache/ +``` + +**Notes**: +- Use `oven/bun:` Docker images. +- Bun caches dependencies in `.bun/install/cache/`. + +### gradle/gradle-build-action + +**What it does**: Installs Gradle and configures caching. + +**GitLab equivalent**: Use a Gradle Docker image, optionally combined with `cache:`. + +**Before** (GitHub Actions): +```yaml +- uses: gradle/gradle-build-action@v2 + with: + gradle-version: 8.0 +``` + +**After** (GitLab CI/CD): +```yaml +image: gradle:8.0 + +cache: + paths: + - .gradle/ +``` + +**Notes**: +- Use `gradle:` Docker images. +- Gradle caches in `.gradle/` by default. + +### cypress-io/github-action + +**What it does**: Installs Cypress and runs end-to-end tests. + +**GitLab equivalent**: Use a Cypress Docker image and run tests in a script. + +**Before** (GitHub Actions): +```yaml +- uses: cypress-io/github-action@v5 + with: + browser: chrome + start: npm start +``` + +**After** (GitLab CI/CD): +```yaml +image: cypress/browsers:latest + +script: + - npm install + - npm start & + - npx cypress run --browser chrome +``` + +**Notes**: +- Use `cypress/browsers:` Docker images which include Cypress and browsers. +- Start your application before running Cypress tests. +- Use `npx cypress run` to run tests in CI mode. + +## Tier 3: Specialized + +### tj-actions/changed-files + +**What it does**: Detects which files changed in the current commit or pull request. + +**GitLab equivalent**: Use `git diff` commands in a script. + +**Before** (GitHub Actions): +```yaml +- uses: tj-actions/changed-files@v40 + with: + files: src/** +``` + +**After** (GitLab CI/CD): +```yaml +script: + - git diff --name-only $CI_COMMIT_BEFORE_SHA $CI_COMMIT_SHA | grep "^src/" +``` + +**Notes**: +- Use `git diff` to detect changed files. +- Use `$CI_COMMIT_BEFORE_SHA` and `$CI_COMMIT_SHA` for commit ranges. + +### dorny/paths-filter + +**What it does**: Filters jobs based on changed file paths. + +**GitLab equivalent**: Use the native `rules:changes:` keyword. + +**Before** (GitHub Actions): +```yaml +- uses: dorny/paths-filter@v2 + with: + filters: | + backend: + - 'src/backend/**' +``` + +**After** (GitLab CI/CD): +```yaml +test-backend: + script: + - npm test + rules: + - if: '$CI_PIPELINE_SOURCE == "merge_request_event"' + changes: + - src/backend/** +``` + +**Notes**: +- Use `rules:changes:` to conditionally run jobs based on file changes. +- This is a native GitLab feature and simpler than the GitHub Actions equivalent. + +### nick-invision/retry + +**What it does**: Retries a step if it fails. + +**GitLab equivalent**: Use the native `retry:` keyword. + +**Before** (GitHub Actions): +```yaml +- uses: nick-invision/retry@v2 + with: + timeout_minutes: 10 + max_attempts: 3 + command: npm test +``` + +**After** (GitLab CI/CD): +```yaml +test: + script: + - npm test + retry: + max: 3 + when: + - script_failure +``` + +**Notes**: +- Use `retry:` to automatically retry failed jobs. +- Specify `max:` for the number of retries and `when:` for the failure type. + +### pre-commit/action + +**What it does**: Runs pre-commit hooks. + +**GitLab equivalent**: Run `pre-commit run` in a script. + +**Before** (GitHub Actions): +```yaml +- uses: pre-commit/action@v3 +``` + +**After** (GitLab CI/CD): +```yaml +image: python:3.11 + +before_script: + - pip install pre-commit + +script: + - pre-commit run --all-files +``` + +**Notes**: +- Install `pre-commit` via pip. +- Run `pre-commit run --all-files` to check all files. + +### slackapi/slack-github-action + +**What it does**: Sends notifications to Slack. + +**GitLab equivalent**: Use a Slack webhook or the Slack SDK via curl or a scripting language. + +**Before** (GitHub Actions): +```yaml +- uses: slackapi/slack-github-action@v1 + with: + webhook-url: ${{ secrets.SLACK_WEBHOOK }} + payload: | + { + "text": "Build completed" + } +``` + +**After** (GitLab CI/CD): +```yaml +image: curlimages/curl:latest + +script: + - curl -X POST -H 'Content-type: application/json' \ + --data '{"text":"Build completed"}' \ + $SLACK_WEBHOOK_URL +``` + +**Notes**: +- Use a Slack webhook URL stored as a CI/CD variable. +- Send JSON payloads via `curl` to the webhook. +- For more complex messages, use the Slack SDK in Python or Node.js. + +## Pattern: actions not in this list + +If an action is not listed above, follow this decision tree: + +1. **Does it set up a tool or language?** Use a Docker image instead. + - Example: `actions/setup-rust` -> `image: rust:latest` + +2. **Does it interact with an external service?** Use the service's CLI or API. + - Example: `aws-actions/s3-cp` -> `aws s3 cp` in a script + +3. **Does it perform a simple file operation?** Use shell commands. + - Example: `actions/upload-release-asset` -> `curl` to upload via API + +4. **Does it require complex logic?** Use a scripting language. + - Example: Custom action with JavaScript -> Python or Node.js script + +5. **Is there no clear equivalent?** Flag for review. + - Document the action, its purpose, and ask for guidance. + +For any action not covered, search the GitLab CI/CD documentation for equivalent features or consider using a Docker image that includes the tool. diff --git a/src/ai-coding/stubs/.agents/skills/github-actions-to-gitlab-ci/references/syntax-mapping.md b/src/ai-coding/stubs/.agents/skills/github-actions-to-gitlab-ci/references/syntax-mapping.md new file mode 100644 index 00000000..f07d99c2 --- /dev/null +++ b/src/ai-coding/stubs/.agents/skills/github-actions-to-gitlab-ci/references/syntax-mapping.md @@ -0,0 +1,906 @@ +# Syntax Mapping: GitHub Actions to GitLab CI/CD + +> Comprehensive keyword and concept mapping for translating `.github/workflows/*.yml` to `.gitlab-ci.yml`. + +## At a glance + +GitHub Actions and GitLab CI/CD share the same goals (automate build, test, deploy) but use different +vocabulary and mental models. GitHub Actions organizes work as workflows containing jobs containing steps. +GitLab CI/CD organizes work as pipelines containing jobs grouped into stages. The table below is the +official GitLab keyword mapping; the sections that follow expand each row with examples and gotchas. + +| GitHub Actions | GitLab CI/CD | Notes | +|---|---|---| +| `env` | `variables` | Global or per-job variable definitions | +| `name` (workflow) | `workflow:name` | Sets the pipeline name displayed in the UI | +| `jobs` | `stages` | Grouping mechanism for parallel/sequential work | +| `on` | Not applicable | GitLab is git-integrated; triggers are implicit; use `rules:` for conditions | +| `run` | Not applicable | Use the `script:` array, one entry per command | +| `runs-on` | `tags` | Runner selection by label | +| `steps` | `script` | Ordered list of commands within a job | +| `uses` | `include` | Only true for reusable workflows; marketplace actions need different treatment | + +Source: https://docs.gitlab.com/ci/migration/github_actions/ + +--- + +## Triggers (`on:` -> `workflow:` and `rules:`) + +`on:` declares which events cause a GitHub Actions workflow to run (push, pull_request, schedule, etc.). +GitLab pipelines run automatically on every push; use `workflow:rules:` to restrict at the pipeline level +and `rules:` at the job level. + +**GitHub example:** + +```yaml +on: + push: + branches: + - main + - 'release/**' + pull_request: + branches: + - main + schedule: + - cron: '0 6 * * 1' +``` + +**GitLab equivalent:** + +```yaml +workflow: + rules: + - if: $CI_COMMIT_BRANCH == "main" + - if: $CI_COMMIT_BRANCH =~ /^release\// + - if: $CI_PIPELINE_SOURCE == "merge_request_event" + - if: $CI_PIPELINE_SOURCE == "schedule" + +stages: + - build + - test +``` + +**Notes:** + +- GitLab has no `on:` equivalent at the file level. Every push triggers a pipeline unless `workflow:rules:` suppresses it. +- `pull_request` maps to `$CI_PIPELINE_SOURCE == "merge_request_event"` (requires merge request pipelines to be enabled). +- Scheduled pipelines are configured in the GitLab UI under CI/CD > Schedules, not in the YAML. The `schedule` source is available as `$CI_PIPELINE_SOURCE == "schedule"` for conditional logic. + +Source: https://docs.gitlab.com/ci/yaml/workflow/ + +--- + +### Workflow name (`name:` -> `workflow:name:`) + +GitHub: +```yaml +name: CI +``` + +GitLab: +```yaml +workflow: + name: CI +``` + +Sets the pipeline name displayed in the GitLab Pipelines UI. Supports CI/CD variable interpolation, for example `name: 'Pipeline for $CI_COMMIT_REF_NAME'`. Available since GitLab 15.8. + +See: https://docs.gitlab.com/ci/yaml/#workflowname + +--- + +## Jobs and stages (`jobs:` -> `stages:` + jobs) + +`jobs:` in GitHub Actions is a map of named jobs that run in parallel by default; `needs:` creates +dependencies. GitLab separates the concept: `stages:` defines the execution order, and jobs declare +which stage they belong to. + +**GitHub example:** + +```yaml +jobs: + build: + runs-on: ubuntu-latest + steps: + - run: make build + + test: + runs-on: ubuntu-latest + needs: build + steps: + - run: make test + + deploy: + runs-on: ubuntu-latest + needs: test + steps: + - run: make deploy +``` + +**GitLab equivalent:** + +```yaml +stages: + - build + - test + - deploy + +build: + stage: build + script: + - make build + +test: + stage: test + script: + - make test + +deploy: + stage: deploy + script: + - make deploy +``` + +**Notes:** + +- Jobs in the same stage run in parallel. Jobs in later stages wait for all jobs in earlier stages to succeed. +- `needs:` exists in GitLab too and enables a DAG (directed acyclic graph) that bypasses stage ordering. It is more powerful than GitHub's `needs:` because it can reference jobs in earlier or same stages. +- Job names in GitLab are top-level YAML keys (not nested under `jobs:`). + +Source: https://docs.gitlab.com/ci/yaml/#stages + +--- + +## Steps and scripts (`steps:` -> `script:`) + +`steps:` is an ordered list of actions and shell commands within a GitHub Actions job. GitLab's `script:` +is a YAML array of shell commands that runs in sequence. + +**GitHub example:** + +```yaml +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Install dependencies + run: npm ci + - name: Build + run: npm run build + - name: Test + run: npm test +``` + +**GitLab equivalent:** + +```yaml +build: + image: node:20 + script: + - npm ci + - npm run build + - npm test +``` + +**Notes:** + +- `actions/checkout@v4` has no GitLab equivalent and should be removed. GitLab clones the repository automatically before every job. +- `before_script:` runs before `script:` and is useful for setup commands (install tools, authenticate). It can be defined globally (applies to all jobs) or per-job. +- `after_script:` runs after `script:` even if the job fails, similar to a `finally` block. + +--- + +## Runners (`runs-on:` -> `tags:`) + +`runs-on:` selects the runner environment by label (e.g., `ubuntu-latest`, `windows-latest`, `self-hosted`). +GitLab uses `tags:` to match jobs to runners that have been registered with those tags. + +**GitHub example:** + +```yaml +jobs: + linux-job: + runs-on: ubuntu-latest + steps: + - run: echo "Running on Linux" + + windows-job: + runs-on: windows-latest + steps: + - run: echo "Running on Windows" +``` + +**GitLab equivalent:** + +```yaml +linux-job: + tags: + - linux + script: + - echo "Running on Linux" + +windows-job: + tags: + - windows + script: + - echo "Running on Windows" +``` + +**Notes:** + +- GitLab.com shared runners have tags like `saas-linux-small-amd64`. Check your project's available runners under Settings > CI/CD > Runners. +- If no `tags:` are specified, the job runs on any available runner. This is fine for GitLab.com but may cause issues on self-managed instances with specialized runners. +- `runs-on: self-hosted` with additional labels (e.g., `runs-on: [self-hosted, gpu]`) maps to multiple tags in GitLab: `tags: [self-hosted, gpu]`. + +Source: https://docs.gitlab.com/ci/runners/configure_runners/#control-jobs-that-a-runner-can-run + +--- + +## Matrix builds (`matrix:` -> `parallel:matrix:`) + +`strategy.matrix:` in GitHub Actions generates multiple job instances with different variable combinations. +GitLab's `parallel:matrix:` provides the same capability with different syntax. + +**GitHub example:** + +```yaml +jobs: + test: + runs-on: ubuntu-latest + strategy: + matrix: + os: [ubuntu, windows] + node: [18, 20, 22] + steps: + - run: echo "Testing on ${{ matrix.os }} with Node ${{ matrix.node }}" +``` + +**GitLab equivalent:** + +```yaml +test: + image: node:${NODE_VERSION} + parallel: + matrix: + - OS: [ubuntu, windows] + NODE_VERSION: [18, 20, 22] + script: + - echo "Testing on $OS with Node $NODE_VERSION" +``` + +**Notes:** + +- GitHub matrix variables are accessed as `${{ matrix.key }}`; GitLab matrix variables are injected as regular CI/CD variables accessed as `$KEY`. +- GitLab generates `N x M` jobs (one per combination), same as GitHub. Job names are suffixed with the variable values. +- `strategy.fail-fast: false` (continue running other matrix instances when one fails) is the **default behaviour** in GitLab. GitLab's `parallel:matrix` instances are independent: one failing does not cancel the others. No translation needed; remove the keyword and the workflow behaves the same. +- GitLab does not have a built-in equivalent for `strategy.fail-fast: true` (cancel other matrix instances when one fails). If users rely on this behaviour, document it as a "needs review" item in the migration report. + +Source: https://docs.gitlab.com/ci/yaml/#parallelmatrix + +--- + +## Variables and secrets (`env:` and secrets -> `variables:`) + +`env:` defines environment variables at the workflow, job, or step level. GitHub secrets are accessed +as `${{ secrets.NAME }}`. In GitLab, both plain variables and secrets are CI/CD variables, defined in +the YAML or in the project/group settings UI. + +**GitHub example:** + +```yaml +env: + GREETING: Hello + +jobs: + greet: + runs-on: ubuntu-latest + env: + NAME: World + steps: + - run: echo "$GREETING $NAME" + - run: echo "Token is ${{ secrets.API_TOKEN }}" + env: + API_TOKEN: ${{ secrets.API_TOKEN }} +``` + +**GitLab equivalent:** + +```yaml +variables: + GREETING: Hello + +greet: + variables: + NAME: World + script: + - echo "$GREETING $NAME" + - echo "Token is $API_TOKEN" +``` + +**Notes:** + +- Secrets in GitLab are CI/CD variables marked as "Masked" and optionally "Protected". Define them in Settings > CI/CD > Variables. They are accessed as `$VAR_NAME`, not `${{ secrets.NAME }}`. +- Protected variables are only available in pipelines for protected branches or tags. +- Step-level `env:` (scoped to a single `run:` step) has no direct equivalent. Use job-level `variables:` or inline shell assignment (`export FOO=bar && command`). + +Source: https://docs.gitlab.com/ci/variables/ + +--- + +## Containers and services (`container:` -> `image:`, `services:` -> `services:`) + +`container:` specifies the Docker image a GitHub Actions job runs inside. `services:` defines sidecar +containers (databases, caches). GitLab uses `image:` and `services:` with nearly identical semantics. + +**GitHub example:** + +```yaml +jobs: + test: + runs-on: ubuntu-latest + container: node:20-alpine + services: + postgres: + image: postgres:15 + env: + POSTGRES_PASSWORD: secret + ports: + - 5432:5432 + steps: + - run: npm test +``` + +**GitLab equivalent:** + +```yaml +test: + image: node:20-alpine + services: + - name: postgres:15 + alias: postgres + variables: + POSTGRES_PASSWORD: secret + script: + - npm test +``` + +**Notes:** + +- Service hostnames in GitLab default to the image name with slashes and colons replaced by double underscores. Use `alias:` to set a predictable hostname (e.g., `alias: postgres` makes the service reachable at `postgres`). +- Port mapping (`ports:`) is not needed in GitLab. Services are accessible on their default ports via the alias hostname. +- The `default:` keyword can set a global `image:` for all jobs, equivalent to setting `container:` at the workflow level. + +Source: https://docs.gitlab.com/ci/services/ + +--- + +## Artifacts (upload/download -> `artifacts:`) + +`actions/upload-artifact` and `actions/download-artifact` pass files between jobs in GitHub Actions. +GitLab's `artifacts:` keyword declares files to persist, and they are automatically available to +downstream jobs. + +**GitHub example:** + +```yaml +jobs: + build: + runs-on: ubuntu-latest + steps: + - run: make build + - uses: actions/upload-artifact@v4 + with: + name: binaries + path: dist/ + + test: + needs: build + runs-on: ubuntu-latest + steps: + - uses: actions/download-artifact@v4 + with: + name: binaries + path: dist/ + - run: ./dist/app --test +``` + +**GitLab equivalent:** + +```yaml +stages: + - build + - test + +build: + stage: build + script: + - make build + artifacts: + paths: + - dist/ + expire_in: 1 hour + +test: + stage: test + script: + - ./dist/app --test +``` + +**Notes:** + +- GitLab artifacts from earlier stages are automatically downloaded by later-stage jobs. No explicit download step is needed. +- Use `artifacts:expire_in:` to control storage duration. Default retention is set at the instance level. +- `artifacts:reports:` is a GitLab-specific feature for structured data (JUnit XML, coverage, SAST results) that integrates with merge request UI. + +Source: https://docs.gitlab.com/ci/yaml/#artifacts + +--- + +## Caching (`actions/cache` -> `cache:`) + +`actions/cache` saves and restores directories keyed by a cache key, typically used for dependency +directories. GitLab's `cache:` keyword provides the same functionality natively. + +**GitHub example:** + +```yaml +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/cache@v4 + with: + path: ~/.npm + key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }} + restore-keys: | + ${{ runner.os }}-node- + - run: npm ci +``` + +**GitLab equivalent:** + +```yaml +build: + script: + - npm ci + cache: + key: + files: + - package-lock.json + paths: + - .npm/ + variables: + npm_config_cache: .npm +``` + +**Notes:** + +- `cache:key:files:` computes a hash of the listed files, equivalent to `hashFiles()` in GitHub Actions. +- GitLab cache is per-runner by default. For shared cache across runners, configure a distributed cache backend (S3 or GCS) in the runner configuration. +- Unlike artifacts, cache is not guaranteed to be available. Jobs must handle a cold cache gracefully. + +Source: https://docs.gitlab.com/ci/caching/ + +--- + +## Conditional execution (`if:` -> `rules:`) + +`if:` in GitHub Actions uses `${{ }}` template expressions to conditionally run a job or step. +GitLab's `rules:` keyword evaluates conditions using shell-style expressions with predefined CI/CD variables. + +**GitHub example:** + +```yaml +jobs: + deploy: + runs-on: ubuntu-latest + if: github.ref == 'refs/heads/main' && github.event_name == 'push' + steps: + - run: make deploy + + notify: + runs-on: ubuntu-latest + if: failure() + steps: + - run: ./notify-slack.sh +``` + +**GitLab equivalent:** + +```yaml +deploy: + script: + - make deploy + rules: + - if: $CI_COMMIT_BRANCH == "main" && $CI_PIPELINE_SOURCE == "push" + +notify: + script: + - ./notify-slack.sh + rules: + - when: on_failure +``` + +**Notes:** + +- GitHub `${{ github.ref }}` maps to `$CI_COMMIT_REF_NAME` or `$CI_COMMIT_BRANCH`. See the full predefined variable list at https://docs.gitlab.com/ci/variables/predefined_variables/. +- `rules:` is evaluated top-to-bottom; the first matching rule wins. This differs from GitHub's single `if:` expression. +- `rules:changes:` triggers a job only when specific files change, a capability with no direct GitHub Actions equivalent (GitHub requires path filters on `on:push:paths:`). + +Source: https://docs.gitlab.com/ci/jobs/job_rules/ + +--- + +## Reusable configuration (`uses:` for workflows -> `include:`) + +`uses:` in GitHub Actions serves two purposes: referencing marketplace actions and calling reusable +workflows. Only the reusable workflow case maps to GitLab's `include:`. Marketplace actions are a +separate concern (see `references/marketplace-actions.md`). + +**GitHub example (reusable workflow call):** + +```yaml +jobs: + call-workflow: + uses: ./.github/workflows/shared-build.yml + with: + environment: staging + secrets: inherit +``` + +**GitLab equivalent:** + +```yaml +include: + - local: .gitlab/ci/shared-build.yml + +# Or include from another project: +include: + - project: mygroup/shared-ci + file: /templates/build.yml + ref: main +``` + +**Notes:** + +- `uses: actions/checkout@v4` (marketplace action) does NOT map to `include:`. Remove it; GitLab clones automatically. +- `uses: owner/repo/.github/workflows/file.yml@ref` (cross-repo reusable workflow) maps to `include: project:` with `ref:`. +- GitLab `include:` merges configuration at parse time. It does not support passing inputs the way GitHub reusable workflows do. Use `variables:` to parameterize included jobs. + +Source: https://docs.gitlab.com/ci/yaml/includes/ + +--- + +## Job control (`timeout-minutes:`, `continue-on-error:`, `concurrency:`, `permissions:`) + +GitHub Actions provides several job-level control keywords that have GitLab equivalents, though the +semantics differ in important ways. + +**GitHub example:** + +```yaml +jobs: + flaky-test: + runs-on: ubuntu-latest + timeout-minutes: 10 + continue-on-error: true + concurrency: + group: deploy-${{ github.ref }} + cancel-in-progress: true + permissions: + contents: read + packages: write + steps: + - run: make test +``` + +**GitLab equivalent:** + +```yaml +flaky-test: + script: + - make test + timeout: 10 minutes + allow_failure: true + resource_group: deploy-$CI_COMMIT_REF_SLUG + interruptible: true +``` + +**Notes:** + +- `permissions:` has no job-level equivalent in GitLab. Token permissions are configured at the project level under Settings > CI/CD > Token Access. Document this as a manual step in your migration report. +- `concurrency.cancel-in-progress: true` maps to `interruptible: true`. GitLab cancels interruptible jobs when a newer pipeline starts for the same ref. +- `resource_group:` serializes jobs with the same group key, equivalent to GitHub's `concurrency.group:` without cancellation. + +Source: https://docs.gitlab.com/ci/yaml/#resource_group + +--- + +## Outputs (`outputs:` -> dotenv pattern) + +GitHub Actions jobs can declare `outputs:` that downstream jobs read via `${{ needs.job.outputs.key }}`. +GitLab does not have a native job output mechanism, but the dotenv artifact pattern achieves the same result. + +**GitHub example:** + +```yaml +jobs: + build: + runs-on: ubuntu-latest + outputs: + version: ${{ steps.get-version.outputs.version }} + steps: + - id: get-version + run: echo "version=$(cat VERSION)" >> $GITHUB_OUTPUT + + deploy: + needs: build + runs-on: ubuntu-latest + steps: + - run: echo "Deploying version ${{ needs.build.outputs.version }}" +``` + +**GitLab equivalent:** + +```yaml +stages: + - build + - deploy + +build: + stage: build + script: + - echo "VERSION=$(cat VERSION)" > build.env + artifacts: + reports: + dotenv: build.env + +deploy: + stage: deploy + script: + - echo "Deploying version $VERSION" + needs: + - job: build + artifacts: true +``` + +**Notes:** + +- The `artifacts:reports:dotenv:` file is sourced as environment variables in downstream jobs that declare `needs: [job: build, artifacts: true]`. +- Variable names in the dotenv file must be valid shell identifiers (no hyphens). +- This pattern only works across jobs with an explicit `needs:` relationship, not across all downstream jobs automatically. + +Source: https://docs.gitlab.com/ci/yaml/#artifactsreportsdotenv + +--- + +## Things that don't translate cleanly + +Some GitHub Actions concepts have no direct GitLab CI/CD equivalent. These require manual decisions +during migration. + +### Composite actions (`uses: ./.github/actions/my-action`) + +Composite actions are reusable step sequences defined in a local directory (`.github/actions/*/action.yml`). GitLab has no single equivalent abstraction, so the right target depends on how the composite is used: + +- **Steps inside a job** (the usual case): inline them with a `!reference [.my_steps, script]` tag, or extract them into a shell script and call it from `script:`. +- **CI/CD components (GitLab 17.0+)**: when the composite maps to one or more whole jobs. Use typed `spec:inputs` to match the composite's inputs, and the dotenv artifact pattern (`artifacts:reports:dotenv` + `needs:`) for outputs. +- **`extends:` patterns or `include:` snippets**: For simpler job-level configuration reuse within the same project or across projects. +- **Child pipelines (`trigger:include:`)**: An escape hatch for composite actions that represent self-contained workflows and benefit from isolated execution context. + +### `actions/setup-*` actions + +`actions/setup-node`, `actions/setup-python`, `actions/setup-java`, etc. configure a language runtime +on the runner. In GitLab, use `image:` to select a pre-built Docker image with the runtime already +installed. This is cleaner and more reproducible. + +```yaml +# GitHub +- uses: actions/setup-node@v4 + with: + node-version: '20' + +# GitLab: just set the image +image: node:20 +``` + +### `github.*` and other context expressions + +GitHub Actions exposes context objects (`github.*`, `runner.*`, `env.*`, `vars.*`, `secrets.*`, `steps.*`, `needs.*`, `inputs.*`, `matrix.*`, `job.*`, `strategy.*`). When translating, treat them in two categories: + +**Category A: GitHub data exposed as predefined variables.** GitLab provides predefined variables for most `github.*` and some `runner.*` expressions. Use the mapping below. + +**Category B: User-defined or runtime data.** `env.*`, `vars.*`, `secrets.*`, `inputs.*`, `matrix.*`, and `steps/needs.*.outputs.*` are not predefined variables. They translate to plain CI/CD variables (`$NAME`), masked variables, or dotenv artifact patterns depending on the source. + +#### `github.*` context + +| GitHub expression | GitLab equivalent | Notes | +|---|---|---| +| `github.sha` | `$CI_COMMIT_SHA` | | +| `github.ref` | `$CI_COMMIT_REF_NAME` | GitHub keeps `refs/heads/` prefix; GitLab strips it for branches but keeps it for tags | +| `github.ref_name` | `$CI_COMMIT_REF_NAME` | Branch or tag name without prefix | +| `github.repository` | `$CI_PROJECT_PATH` | | +| `github.repository_owner` | `$CI_PROJECT_NAMESPACE` | | +| `github.actor` | `$GITLAB_USER_LOGIN` | | +| `github.triggering_actor` | `$GITLAB_USER_LOGIN` | GitLab does not distinguish triggering actor from actor | +| `github.event_name` | `$CI_PIPELINE_SOURCE` | Values differ: `pull_request` -> `merge_request_event`, `schedule` -> `schedule`, `workflow_dispatch` -> `web` or `api` | +| `github.workflow` | `$CI_PIPELINE_NAME` | Requires GitLab 16.3+ and `workflow:name:` set | +| `github.run_id` | `$CI_PIPELINE_ID` | | +| `github.run_number` | `$CI_PIPELINE_IID` | Project-scoped incrementing number | +| `github.run_attempt` | (no equivalent) | Flag as Needs Review | +| `github.job` | `$CI_JOB_NAME` | | +| `github.head_ref` | `$CI_MERGE_REQUEST_SOURCE_BRANCH_NAME` | MR pipelines only | +| `github.base_ref` | `$CI_MERGE_REQUEST_TARGET_BRANCH_NAME` | MR pipelines only | +| `github.workspace` | `$CI_PROJECT_DIR` | | + +#### `runner.*` context + +| GitHub expression | GitLab equivalent | Notes | +|---|---|---| +| `runner.os` | (no predefined variable) | Use `image:` selection or set a custom variable based on the runner tag | +| `runner.arch` | (no predefined variable) | Flag as Needs Review | +| `runner.temp` | (no predefined variable) | Use `/tmp` or a path under `$CI_PROJECT_DIR` | +| `runner.workspace` | `$CI_PROJECT_DIR` | Closest equivalent | + +#### `env.*`, `vars.*`, `secrets.*` (user-defined data) + +| GitHub expression | GitLab equivalent | Notes | +|---|---|---| +| `env.` | `$NAME` | Plain CI/CD variable defined in `variables:` | +| `vars.` | `$NAME` | GitHub's non-secret repo/org variable; same mechanism as `env` in GitLab | +| `secrets.` | `$NAME` | Masked CI/CD variable defined in Project > Settings > CI/CD > Variables | + +#### `inputs.*` context + +| GitHub expression | GitLab equivalent | Notes | +|---|---|---| +| `inputs.` | `$[[ inputs. ]]` (`spec:inputs`) or `$NAME` (variable form) | See the `workflow_dispatch` inputs section for the full pattern | + +#### `steps.*` and `needs.*` context + +| GitHub expression | GitLab equivalent | Notes | +|---|---|---| +| `steps..outputs.` | `$NAME` (via dotenv artifact) | Requires `artifacts:reports:dotenv` and downstream `needs:` relationship | +| `steps..outcome` | (no equivalent) | Flag as Needs Review | +| `steps..conclusion` | (no equivalent) | Flag as Needs Review | +| `needs..outputs.` | `$NAME` (via dotenv artifact) | Same pattern as `steps.outputs` | +| `needs..result` | (no equivalent) | Flag as Needs Review | + +#### `job.*` and `strategy.*` and `matrix.*` context + +| GitHub expression | GitLab equivalent | Notes | +|---|---|---| +| `job.status` | `$CI_JOB_STATUS` | Available only in `after_script:` | +| `job.container.id` | (no equivalent) | Flag as Needs Review | +| `matrix.` | `$NAME` | Injected automatically by `parallel:matrix:` | +| `strategy.fail-fast` | (no keyword equivalent for `true`) | GitLab default behavior is `false` | +| `strategy.job-index` | `$CI_NODE_INDEX` | | +| `strategy.job-total` | `$CI_NODE_TOTAL` | | + +**Agent instruction:** When translating a GitHub workflow, scan for `${{ ... }}` expressions and substitute the GitLab equivalent from the tables above. For expressions not listed, consult https://docs.gitlab.com/ci/variables/predefined_variables/ and pick the closest match. If no GitLab equivalent exists, leave the expression replaced with a clearly marked placeholder (e.g., `# TODO: no GitLab equivalent for github.run_attempt`) and flag it as Needs Review in the migration report. + +Full predefined variable reference: https://docs.gitlab.com/ci/variables/predefined_variables/ + +### `workflow_dispatch` inputs + +GitHub's `on: workflow_dispatch` combines two things: a manual-only trigger and an optional typed input schema. GitLab separates these concerns: `workflow:rules:` controls when the pipeline runs, and `spec:inputs` declares the input schema. + +**Critical behavioral difference:** GitHub `workflow_dispatch` workflows run only on manual trigger. GitLab pipelines run on every push by default. A naive translation breaks immediately if any input lacks a default, because auto-triggered pipelines cannot prompt for values. The full translation pattern requires three pieces: + +1. `spec:inputs` at the top of `.gitlab-ci.yml` for the typed input schema (pipeline-level, GitLab 17.0+) +2. `workflow:rules:` to restrict the pipeline to manual and API triggers only +3. A `default:` on every input so that if the pipeline IS triggered automatically (e.g., by a schedule or API call without explicit inputs), it does not fail + +**GitHub example:** + +```yaml +on: + workflow_dispatch: + inputs: + environment: + description: 'Target environment' + required: true + default: 'staging' + type: choice + options: + - staging + - production + dry_run: + description: 'Run without making changes' + required: false + default: true + type: boolean + version: + description: 'Version to deploy' + required: true + type: string + +jobs: + deploy: + runs-on: ubuntu-latest + steps: + - run: echo "Deploying ${{ inputs.version }} to ${{ inputs.environment }}" +``` + +**GitLab equivalent:** + +```yaml +spec: + inputs: + environment: + description: 'Target environment' + type: string + default: 'staging' + options: + - staging + - production + dry_run: + description: 'Run without making changes' + type: boolean + default: true + version: + description: 'Version to deploy' + type: string + default: '' # Required in GitHub, but GitLab needs a default for auto-triggered safety + +--- + +workflow: + rules: + - if: $CI_PIPELINE_SOURCE == "web" # "Run pipeline" button + - if: $CI_PIPELINE_SOURCE == "api" # Pipeline API / trigger token + - if: $CI_PIPELINE_SOURCE == "trigger" # Trigger API + +deploy: + stage: deploy + script: + - echo "Deploying $[[ inputs.version ]] to $[[ inputs.environment ]]" +``` + +**Input type mapping:** + +| GitHub `workflow_dispatch` type | GitLab `spec:inputs` type | Notes | +|---|---|---| +| `string` | `type: string` | Direct mapping. Use `regex:` for validation. | +| `boolean` | `type: boolean` | Direct mapping. | +| `number` | `type: number` | Direct mapping. | +| `choice` | `type: string` + `options:` | GitLab uses `options:` on a string input instead of a separate `choice` type. | +| `environment` | `type: string` + `options:` | No native environment type. List environment names in `options:` manually. | + +**Notes:** + +- `spec:inputs` uses `$[[ inputs.name ]]` interpolation syntax (double brackets), not `${{ }}`. This is resolved at pipeline creation time, before any job runs. +- `required: true` in GitHub has no direct equivalent. In GitLab, omitting `default:` makes the input mandatory, but mandatory inputs break auto-triggered pipelines. Prefer setting a sensible default and validating in the script. +- The `workflow:rules:` block above restricts the pipeline to manual/API sources. Without this, every push creates a pipeline that uses the default input values, which is rarely the intended behavior for a `workflow_dispatch` translation. +- For GitLab versions before 17.0, use top-level `variables:` with `description:`, `value:`, and `options:` as a simpler alternative. This renders a form in the "Run pipeline" UI but provides no type enforcement (all values are strings). + +Source: https://docs.gitlab.com/ci/inputs/ + +### `actions/github-script` + +`actions/github-script` runs JavaScript with access to the GitHub API client. The GitLab equivalent +is using `glab` CLI or the GitLab REST/GraphQL API directly in a `script:` block with `$CI_JOB_TOKEN` +for authentication. + +### Environment protection rules + +GitHub environments (`environment:`) support required reviewers and wait timers before deployment. +GitLab has equivalent functionality via protected environments (Settings > CI/CD > Environments), +but the configuration is in the UI, not the YAML. The `environment:` keyword in GitLab YAML only +declares which environment a job deploys to. + +### `permissions:` (GITHUB_TOKEN scopes) + +GitHub's `permissions:` restricts the automatically-provided `GITHUB_TOKEN`. GitLab's `CI_JOB_TOKEN` +has a fixed scope controlled at the project level. There is no per-job permission narrowing in GitLab CI/CD YAML. + +### Reusable workflow inputs and outputs + +GitHub reusable workflows support typed `inputs:` and `outputs:` with schema validation. GitLab `include:` +merges YAML at parse time with no input/output contract. Parameterize included jobs via `variables:` and +document the expected variable names in comments. + +--- + +*Source: https://docs.gitlab.com/ci/migration/github_actions/* diff --git a/src/ai-coding/stubs/.agents/skills/github-actions-to-gitlab-ci/references/validator.md b/src/ai-coding/stubs/.agents/skills/github-actions-to-gitlab-ci/references/validator.md new file mode 100644 index 00000000..dc0bb93f --- /dev/null +++ b/src/ai-coding/stubs/.agents/skills/github-actions-to-gitlab-ci/references/validator.md @@ -0,0 +1,91 @@ +# Validator reference + +Detailed handling for `scripts/validate.sh`. Load when something other than the happy path occurs: script not found, permission denied, neither backend installed, no shell or network access, validation fails, or the `glab` backend needs a project. + +The happy path itself stays in `SKILL.md` step 4.5: `bash /scripts/validate.sh /path/to/translated.gitlab-ci.yml`. + +## Finding the script + +The script is at `scripts/validate.sh` inside the skill bundle. The path depends on the host tool: + +- **Claude Code**: use `${CLAUDE_SKILL_DIR}/scripts/validate.sh` +- **opencode and other tools**: the skill is at one of the discovery paths (`~/.claude/skills/github-actions-to-gitlab-ci/`, `~/.config/opencode/skills/github-actions-to-gitlab-ci/`, `~/.agents/skills/github-actions-to-gitlab-ci/`, or a project-local equivalent). Locate the directory you loaded this skill from and append `/scripts/validate.sh`. +- **Fallback**: if you cannot locate the bundled script, run the underlying tool directly: `glci lint -f ` (preferred) or `glab ci lint ` (fallback). The bundled script is a thin wrapper around exactly those commands. + +## Backend selection + +Set `VALIDATE_BACKEND=glci|glab` to force a specific backend. The default is `auto`, which prefers `glci` when installed and falls back to `glab`. + +## `glab` backend project resolution + +The `glab` backend needs a GitLab project context (the lint API is per-project). The script resolves the project from, in order: + +1. A `--project ` flag passed to the script. +2. The `VALIDATE_PROJECT` environment variable. +3. The git remote of the current directory (when the user is inside a GitLab checkout). +4. The user's `glab` default. + +The skill does not hardcode any project. If none of these resolves, the script exits 2 with a clear error explaining the three options the user has: install `glci` instead, set `VALIDATE_PROJECT`, or pass `--project`. For users migrating from GitHub Actions who do not have a GitLab project they can use, the simplest path is to install `glci`, which validates entirely offline and needs no project. + +## Migrating from a GitHub-hosted repo + +Users invoking this skill are almost always sitting in a repo whose origin is on GitHub. That has two consequences: + +- The git-remote step of the `glab` project resolver will never produce a GitLab path. The script's resolver rejects non-GitLab hosts. +- Unless the user has set `VALIDATE_PROJECT` or `--project`, the `glab` path can only fail. + +When you detect (or anticipate) a non-GitLab origin, lead with `glci` from the start: recommend installing it before invoking the validator. This avoids the failed `glab` fallback entirely. + +## Permission denied / missing executable bit + +Two binaries can hit this: the bundled `scripts/validate.sh` itself, and the `glci` binary when installed manually. + +**`validate.sh`**: the executable bit is lost when the skill is copied via tarball or zip (not via `git clone`). Fix once with `chmod +x /scripts/validate.sh`, or always invoke via `bash` as shown in SKILL.md. + +**`glci`**: manual installs via `sudo mv` often preserve whatever mode the downloaded file had and silently produce a binary without the `x` bit. `validate.sh` detects this in standard locations and exits 2 with a pointer. If the script reports glci as missing despite a prior install, check the standard paths: + +```bash +ls -l /usr/local/bin/glci /opt/homebrew/bin/glci ~/bin/glci ~/.local/bin/glci 2>/dev/null +``` + +If a file is present without `x` in its mode, fix it with `sudo chmod +x ` (or without sudo if the file is under `$HOME`). + +## Neither backend installed (exit code 2) + +The script prints install commands for both tools. The agent should not run any install command itself without explicit user consent, and even with consent, prefer asking the user to run it themselves. This sidesteps environments where the agent is sandboxed (no shell access, `curl` blocked, or otherwise restricted) and keeps the user in control of what gets installed on their machine. + +Surface both options with their tradeoffs and let the user pick: + +- **glci** (recommended when the user does not yet have GitLab tooling). Runs offline, no GitLab account or token required, deeper parse, unlocks future structural validation. The bundled `scripts/validate.sh` prints the install one-liner in its error output, pointing at `main/install.sh` which always installs the latest tag. If `curl` is restricted in the user's environment or they prefer a manual install, point them to the releases page: https://gitlab.com/gitlab-org/ci-cd/runner-tools/glci/-/releases. They can download the binary matching their OS/arch (`glci-darwin-arm64`, `glci-linux-amd64`, etc.), mark it executable with `chmod +x`, and move it to a directory on PATH. +- **glab** (recommended if the user already has a GitLab account). Calls the GitLab CI Lint API. Install via the user's preferred method (Homebrew, apt, dnf, winget, manual download). See https://gitlab.com/gitlab-org/cli#installation for the full list. After install, the user runs `glab auth login` once. + +When the user is migrating from GitHub Actions and does not yet have GitLab tooling, lead with `glci` because it does not assume an existing GitLab account. If the user already mentioned a GitLab account or `glab` set up, lead with `glab`. Hand the user the relevant install command (or the releases URL for a manual download), ask them to run it themselves, and once they confirm, re-run `validate.sh` and continue. + +If the user declines both, skip validation in this run and clearly note in the migration report that the translated `.gitlab-ci.yml` was not validated locally. Recommend they validate in the GitLab UI Pipeline editor (**Build > Pipeline editor > Validate**, or **CI/CD > Pipelines > CI/CD Lint** on older versions) after pushing. + +## No shell access + +Some hosts (web-only chat surfaces, read-only review agents, sandboxed environments without shell access) cannot invoke `bash` or run the bundled validator. In that case: + +- Skip the script invocation; do not pretend it ran. +- Mark "local validation skipped (agent has no shell access)" in the migration report so the user knows they are receiving an unvalidated translation. +- Tell the user to validate the YAML in the GitLab UI: open the project on GitLab and go to **Build > Pipeline editor > Validate** (or **CI/CD > Pipelines > CI/CD Lint** on older versions). Paste the YAML, click Validate. Zero local tooling required. +- For future runs, recommend the user install `glci` (preferred) or `glab` locally so subsequent translations can be validated end-to-end. The bundled `scripts/validate.sh` will work on hosts that support bash. + +## No network access + +Two paths to be aware of: + +- `glci lint` is offline by design. If `glci` is installed locally, validation still works without any network. +- `glab ci lint` calls the GitLab CI Lint API and will fail without network. If only `glab` is installed, validation will exit non-zero with a network error; document this in the migration report as "could not validate (no network)" and recommend the user retry when network is available, install `glci` for offline validation, or use the GitLab UI Pipeline editor. + +## Validation failures + +The output lists errors with line numbers. Two responses: + +- **Syntax error**: fix it and re-run the validator. +- **Translation gap**: document the error as a "Needs review" item in the migration report rather than silently shipping a broken file. + +## What the validator does not catch + +The validator does not catch semantic errors (does the pipeline do what the user intended?) or runtime errors (will the scripts succeed?). Those remain the user's responsibility, surfaced via the migration report. diff --git a/src/ai-coding/stubs/.agents/skills/github-actions-to-gitlab-ci/scripts/validate.sh b/src/ai-coding/stubs/.agents/skills/github-actions-to-gitlab-ci/scripts/validate.sh new file mode 100755 index 00000000..6775f928 --- /dev/null +++ b/src/ai-coding/stubs/.agents/skills/github-actions-to-gitlab-ci/scripts/validate.sh @@ -0,0 +1,206 @@ +#!/usr/bin/env bash +# Validate a .gitlab-ci.yml file using glci (preferred) or glab (fallback). +# Usage: ./validate.sh [--project ] [path-to-file] +# Default file: .gitlab-ci.yml in the current directory. +# +# Backend selection (env var VALIDATE_BACKEND): +# auto (default) - prefer glci if installed, fall back to glab +# glci - require glci, do not fall back +# glab - require glab, do not fall back +# +# glci lint runs fully offline. It resolves include:, extends:, !reference, +# default:, and workflow: without a GitLab token. No GitLab project context +# required. Deeper than glab ci lint. +# +# glab ci lint calls the GitLab CI Lint API. Requires glab to be installed and +# authenticated, AND a GitLab project context the user can read. The script +# resolves the project from (in order): +# 1. --project flag +# 2. VALIDATE_PROJECT environment variable +# 3. git remote origin URL (when run inside a GitLab checkout) +# 4. glab's default host project via `glab config get` +# If none of those resolve, the script exits 2 with a clear error. +# +# Exit codes: +# 0 - file is syntactically valid +# 1 - file is invalid (errors printed) +# 2 - file not found, no backend available, no project for glab backend, +# or unknown VALIDATE_BACKEND + +set -uo pipefail + +# Parse args: --project , then positional FILE. +PROJECT_FLAG="" +FILE="" +while [ $# -gt 0 ]; do + case "$1" in + --project) + shift + PROJECT_FLAG="${1:-}" + shift + ;; + --project=*) + PROJECT_FLAG="${1#--project=}" + shift + ;; + -h|--help) + sed -n '1,28p' "$0" | sed 's/^# \{0,1\}//' + exit 0 + ;; + *) + if [ -z "$FILE" ]; then + FILE="$1" + fi + shift + ;; + esac +done +FILE="${FILE:-.gitlab-ci.yml}" +BACKEND="${VALIDATE_BACKEND:-auto}" + +if [ ! -f "$FILE" ]; then + echo "ERROR: $FILE not found" >&2 + exit 2 +fi + +have_glci=0 +have_glab=0 +command -v glci >/dev/null 2>&1 && have_glci=1 +command -v glab >/dev/null 2>&1 && have_glab=1 + +# If glci is not on PATH, check common install paths for an existing file that +# is not executable. Manual installs via `sudo mv` often preserve whatever mode +# the downloaded file had and silently produce an inert binary. +broken_glci_path="" +if [ "$have_glci" = 0 ]; then + for candidate in /usr/local/bin/glci /opt/homebrew/bin/glci "$HOME/bin/glci" "$HOME/.local/bin/glci"; do + if [ -f "$candidate" ] && [ ! -x "$candidate" ]; then + broken_glci_path="$candidate" + break + fi + done +fi + +print_install_pointers() { + # The install script at main/install.sh always pins to the latest tag. + echo "Install glci (preferred, offline, no token):" >&2 + echo " curl -fsSL https://gitlab.com/gitlab-org/ci-cd/runner-tools/glci/-/raw/main/install.sh | bash" >&2 + echo " (or browse releases: https://gitlab.com/gitlab-org/ci-cd/runner-tools/glci/-/releases)" >&2 + echo "Install glab (fallback, requires authenticated GitLab account):" >&2 + echo " https://gitlab.com/gitlab-org/cli#installation" >&2 +} + +# Resolution chain for the glab backend's project context. +# Echoes the resolved "group/project" path on stdout, empty if none found. +resolve_glab_project() { + # 1. Explicit --project flag + if [ -n "$PROJECT_FLAG" ]; then + echo "$PROJECT_FLAG" + return 0 + fi + # 2. VALIDATE_PROJECT env var + if [ -n "${VALIDATE_PROJECT:-}" ]; then + echo "$VALIDATE_PROJECT" + return 0 + fi + # 3. git remote origin URL, parsed for gitlab host + local remote_url + remote_url="$(git config --get remote.origin.url 2>/dev/null || true)" + if [ -n "$remote_url" ]; then + # Strip protocol and host for both SSH and HTTPS remotes. Only matches + # gitlab.com or any *.gitlab.* host; non-GitLab hosts (github.com, etc.) + # are left intact and rejected by the equality check below. The .git + # suffix is stripped separately so a github.com URL with .git does not + # masquerade as a "stripped" GitLab path. + local stripped + stripped="$(echo "$remote_url" \ + | sed -E 's#^(https?://|git@)[^/:]*gitlab[^/:]*[:/]##')" + if [ -n "$stripped" ] && [ "$stripped" != "$remote_url" ]; then + echo "${stripped%.git}" + return 0 + fi + fi + # 4. glab's default host project, if glab is installed + if [ "$have_glab" = 1 ]; then + local glab_default + glab_default="$(glab config get -g host 2>/dev/null || true)" + # glab config does not expose a "default project" directly; users must + # set one explicitly. Skip this step rather than guessing. + : "${glab_default:=}" + fi + echo "" +} + +print_no_project_error() { + echo "ERROR: glab backend requires a GitLab project context." >&2 + echo " No project found via --project flag, VALIDATE_PROJECT env var, git remote, or glab config." >&2 + echo "" >&2 + echo "You have three options:" >&2 + echo " 1. Install glci (preferred; works offline, no GitLab project required):" >&2 + echo " curl -fsSL https://gitlab.com/gitlab-org/ci-cd/runner-tools/glci/-/raw/main/install.sh | bash" >&2 + echo " (or browse releases: https://gitlab.com/gitlab-org/ci-cd/runner-tools/glci/-/releases)" >&2 + echo " 2. Set VALIDATE_PROJECT to a GitLab project you own (or can read):" >&2 + echo " VALIDATE_PROJECT=your-group/your-project bash scripts/validate.sh " >&2 + echo " 3. Pass --project explicitly per invocation:" >&2 + echo " bash scripts/validate.sh --project your-group/your-project " >&2 +} + +case "$BACKEND" in + auto) + if [ "$have_glci" = 1 ]; then + BACKEND=glci + elif [ -n "$broken_glci_path" ]; then + echo "ERROR: found glci at $broken_glci_path but it is not executable." >&2 + echo " Fix it and re-run:" >&2 + echo " chmod +x $broken_glci_path" >&2 + exit 2 + elif [ "$have_glab" = 1 ]; then + BACKEND=glab + echo "Note: glci not detected on PATH; falling back to glab ci lint." >&2 + else + echo "ERROR: neither glci nor glab is installed." >&2 + print_install_pointers + exit 2 + fi + ;; + glci) + if [ "$have_glci" != 1 ]; then + if [ -n "$broken_glci_path" ]; then + echo "ERROR: VALIDATE_BACKEND=glci but glci at $broken_glci_path is not executable." >&2 + echo " Fix it and re-run:" >&2 + echo " chmod +x $broken_glci_path" >&2 + else + echo "ERROR: VALIDATE_BACKEND=glci but glci is not installed." >&2 + print_install_pointers + fi + exit 2 + fi + ;; + glab) + if [ "$have_glab" != 1 ]; then + echo "ERROR: VALIDATE_BACKEND=glab but glab is not installed." >&2 + print_install_pointers + exit 2 + fi + ;; + *) + echo "ERROR: unknown VALIDATE_BACKEND: $BACKEND (expected auto, glci, or glab)" >&2 + exit 2 + ;; +esac + +case "$BACKEND" in + glci) + echo "Validating $FILE via glci lint..." + exec glci lint --quiet -f "$FILE" + ;; + glab) + PROJECT="$(resolve_glab_project)" + if [ -z "$PROJECT" ]; then + print_no_project_error + exit 2 + fi + echo "Validating $FILE via glab ci lint (project: $PROJECT)..." + exec glab ci lint --repo "$PROJECT" "$FILE" + ;; +esac diff --git a/src/ai-coding/stubs/.github/skills/github-actions-to-gitlab-ci b/src/ai-coding/stubs/.github/skills/github-actions-to-gitlab-ci new file mode 120000 index 00000000..3d4a8407 --- /dev/null +++ b/src/ai-coding/stubs/.github/skills/github-actions-to-gitlab-ci @@ -0,0 +1 @@ +../../.agents/skills/github-actions-to-gitlab-ci \ No newline at end of file diff --git a/src/ai-coding/stubs/_review-deepseek...gitlab-ci.yml b/src/ai-coding/stubs/_review-deepseek...gitlab-ci.yml new file mode 100644 index 00000000..cc2f1b49 --- /dev/null +++ b/src/ai-coding/stubs/_review-deepseek...gitlab-ci.yml @@ -0,0 +1,23 @@ +# @format + +# Counterpart of .github/workflows/review-deepseek.yml +# +# Pending: GitLab pipelines do not run on comment events, so the review is a +# manual merge request job instead of an `@deepseek` mention. +review-deepseek: + extends: .zz-use + stage: test + timeout: 30m + rules: + - if: $CI_PIPELINE_SOURCE == "merge_request_event" + when: manual + allow_failure: true + variables: + REACTION: eyes + script: + # TODO(gitlab): confirm-pr-comment reacts to a GitHub comment (COMMENT_URL or + # COMMENT_ID); it needs a GitLab emoji-award variant before it is useful here. + - zz-use -x tomgrv/actions/confirm-pr-comment + # TODO(gitlab): run hustcer/deepseek-review against the merge request, with the + # DEEPSEEK_CHAT_TOKEN CI/CD variable. + - echo "Pending - deepseek-review is not wired to GitLab yet." diff --git a/src/common-utils/README.md b/src/common-utils/README.md index 7eea62c8..3ce7831c 100644 --- a/src/common-utils/README.md +++ b/src/common-utils/README.md @@ -159,3 +159,11 @@ Installed from [`tomgrv/scripts`](https://github.com/tomgrv/scripts); each has i ## Contributing If you have a feature that you would like to add to this repository, please open an issue or submit a pull request. + +## GitLab CI + +Each workflow above has a GitLab counterpart, deployed as a fragment merged into the repository's `.gitlab-ci.yml` (`stubs/_...gitlab-ci.yml`). `tomgrv/actions` steps run through `zz-use -x tomgrv/actions/`, with the action inputs set as environment variables. + +- `.zz-use`: shared base image and `zz-use` bootstrap, extended by the jobs of every feature. +- `test-workspaces` / `repo-tests`: the workspace matrix is a loop; drafts are detected from the merge request title. +- `validate-pr-lock`: `check-lock`, reporting through reviewdog (`REVIEWDOG_GITLAB_API_TOKEN` variable required). diff --git a/src/common-utils/stubs/_test-workspaces...gitlab-ci.yml b/src/common-utils/stubs/_test-workspaces...gitlab-ci.yml new file mode 100644 index 00000000..426ddfc1 --- /dev/null +++ b/src/common-utils/stubs/_test-workspaces...gitlab-ci.yml @@ -0,0 +1,54 @@ +# @format + +# Counterpart of .github/workflows/test-workspaces.yaml +# +# A draft merge request only runs the workspaces its changes impact (see +# ws-impacted); marking it ready and any push to a non-draft merge request run +# every workspace. The workspace matrix is a loop (GitLab has no dynamic +# `parallel:matrix`), and the "Tests green" aggregate job is the pipeline status. +.test-workspaces: + extends: .zz-use + stage: test + interruptible: true + before_script: + - !reference [.zz-use, before_script] + - apt-get update -qq && apt-get install -y -qq bats + +test-workspaces: + extends: .test-workspaces + timeout: 20m + rules: + - if: $CI_PIPELINE_SOURCE == "merge_request_event" + variables: + GIT_DEPTH: '0' + REQUIRE_REPOSITORY: 'false' + script: + - | + FILTER= + if echo "$CI_MERGE_REQUEST_TITLE" | grep -qiE '^(draft:|\[draft\]|\(draft\))'; then + zz-use ws-impacted + FILTER=$(ws-impacted -r "$CI_MERGE_REQUEST_DIFF_BASE_SHA..HEAD") + fi + export FILTER + - zz-use -x tomgrv/actions/list-packages + - sed -n 's/^packages=//p' "$GITHUB_OUTPUT" | tail -1 | jq -r '.[].path' > .packages + - | + failed=0 + while read -r path; do + WORKSPACE=$path zz-use -x tomgrv/actions/run-workspace-tests || failed=1 + done < .packages + exit $failed + +# Repo-wide tests are not workspace-scoped: skipped on drafts, run on ready. +repo-tests: + extends: .test-workspaces + timeout: 20m + rules: + - if: $CI_PIPELINE_SOURCE == "merge_request_event" && $CI_MERGE_REQUEST_TITLE !~ /^(Draft:|\[Draft\]|\(Draft\))/i + script: + - | + if ls tests/*.bats > /dev/null 2>&1; then + bats tests/ + else + echo "No repo-wide tests under tests/, skipping." + fi diff --git a/src/common-utils/stubs/_validate-pr-lock...gitlab-ci.yml b/src/common-utils/stubs/_validate-pr-lock...gitlab-ci.yml new file mode 100644 index 00000000..c1bf7974 --- /dev/null +++ b/src/common-utils/stubs/_validate-pr-lock...gitlab-ci.yml @@ -0,0 +1,20 @@ +# @format + +# Counterpart of .github/workflows/validate-pr-lock.yml +validate-pr-lock: + extends: .zz-use + stage: test + timeout: 10m + interruptible: true + rules: + - if: $CI_PIPELINE_SOURCE == "merge_request_event" + variables: + GIT_DEPTH: '0' + # '.' checks the repo root; pass a comma-separated list (e.g. '.,packages/foo') + # if manifests live in subdirectories. + TARGET_PATHS: . + # TODO(gitlab): reviewdog reports to the merge request with this reporter, + # and needs a masked REVIEWDOG_GITLAB_API_TOKEN CI/CD variable. + REVIEWDOG_REPORTER: gitlab-mr-discussion + script: + - zz-use -x tomgrv/actions/check-lock diff --git a/src/common-utils/stubs/_zz-use...gitlab-ci.yml b/src/common-utils/stubs/_zz-use...gitlab-ci.yml new file mode 100644 index 00000000..0051c1cf --- /dev/null +++ b/src/common-utils/stubs/_zz-use...gitlab-ci.yml @@ -0,0 +1,21 @@ +# @format + +# Shared base of the GitLab fragments of every feature (counterpart of the +# `setup-scripts` GitHub action): installs the zz-* scripts, so a job can run +# `zz-use -x tomgrv/actions/`. The actions read their inputs from +# environment variables, set under `variables:` of each job. +.zz-use: + image: node:lts + variables: + ZZ_CACHE_DIR: $CI_PROJECT_DIR/.cache/zz_scripts + # Same versioned ref as common-utils/install.sh, never main (see UPGRADING.md) + ZZ_ORIGIN_REF: v3 + # Where the actions write their outputs (name=value lines), read back by the jobs + GITHUB_OUTPUT: $CI_PROJECT_DIR/.zz-output + cache: + key: zz-use + paths: + - .cache/zz_scripts/ + before_script: + - curl -fsSL https://raw.githubusercontent.com/tomgrv/scripts/$ZZ_ORIGIN_REF/setup.sh | sh + - ': > "$GITHUB_OUTPUT"' diff --git a/src/githooks/README.md b/src/githooks/README.md index 062cd431..e5d7666e 100644 --- a/src/githooks/README.md +++ b/src/githooks/README.md @@ -116,3 +116,10 @@ The feature also includes the following VS Code customizations: ## Contributing If you have a feature that you would like to add to this repository, please open an issue or submit a pull request. + +## GitLab CI + +Each workflow above has a GitLab counterpart, deployed as a fragment merged into the repository's `.gitlab-ci.yml` (`stubs/_...gitlab-ci.yml`). `tomgrv/actions` steps run through `zz-use -x tomgrv/actions/`, with the action inputs set as environment variables. + +- `check-pr-source-branch`, `validate-pr-secret` (gitleaks CLI), `validate-pr-vulnerabilities` (trivy CLI): run on merge requests. +- `validate-pr-title`, `clean-branches`, `update-labels`, `configure-gitlab`: pending, manual jobs - the actions behind them use the GitHub API (`gh`) and need a GitHub token in `GITHUB_TOKEN`. diff --git a/src/githooks/stubs/_clean-branches...gitlab-ci.yml b/src/githooks/stubs/_clean-branches...gitlab-ci.yml new file mode 100644 index 00000000..084c6647 --- /dev/null +++ b/src/githooks/stubs/_clean-branches...gitlab-ci.yml @@ -0,0 +1,19 @@ +# @format + +# Counterpart of .github/workflows/clean-branches.yml +clean-branches: + extends: .zz-use + stage: deploy + timeout: 10m + resource_group: clean-branches + # Pending: clean-branches deletes branches through `gh` (GitHub API). Run it on + # demand until a GitLab variant exists, then switch the rule to the weekly + # schedule (CI/CD > Schedules, `0 5 * * 1`): $CI_PIPELINE_SOURCE == "schedule". + rules: + - if: $CI_PIPELINE_SOURCE == "web" + when: manual + allow_failure: true + variables: + REPO: $CI_PROJECT_PATH + script: + - zz-use -x tomgrv/actions/clean-branches diff --git a/src/githooks/stubs/_configure-github...gitlab-ci.yml b/src/githooks/stubs/_configure-github...gitlab-ci.yml new file mode 100644 index 00000000..68e9808d --- /dev/null +++ b/src/githooks/stubs/_configure-github...gitlab-ci.yml @@ -0,0 +1,21 @@ +# @format + +# Counterpart of .github/workflows/configure-github.yml +# +# Pending: the workflow sets GitHub branch protection (`main` restricted to the +# bot, "Tests green" required on `develop`) with a GitHub App token. GitLab +# keeps this in protected branches, set through the project API. No +# tomgrv/actions step is involved. +configure-gitlab: + stage: deploy + timeout: 10m + resource_group: configure-gitlab + image: alpine:latest + rules: + - if: $CI_PIPELINE_SOURCE == "web" + when: manual + allow_failure: true + script: + # TODO(gitlab): PATCH/POST $CI_API_V4_URL/projects/$CI_PROJECT_ID/protected_branches + # for `main` (push_access_level: 0) and `develop`, with a project access token. + - echo "Pending - protected branches are not configured by this job yet." diff --git a/src/githooks/stubs/_update-labels...gitlab-ci.yml b/src/githooks/stubs/_update-labels...gitlab-ci.yml new file mode 100644 index 00000000..5987408b --- /dev/null +++ b/src/githooks/stubs/_update-labels...gitlab-ci.yml @@ -0,0 +1,20 @@ +# @format + +# Counterpart of .github/workflows/update-labels.yml +update-labels: + extends: .zz-use + stage: deploy + timeout: 10m + resource_group: update-labels + # Pending: update-labels syncs GitHub issue labels through `gh`. Run it on demand + # until a GitLab variant exists, then switch the rule to the weekly schedule + # (CI/CD > Schedules, `0 4 * * 1`): $CI_PIPELINE_SOURCE == "schedule". + rules: + - if: $CI_PIPELINE_SOURCE == "web" + when: manual + allow_failure: true + variables: + REPOSITORY: $CI_PROJECT_PATH + LABELS_FILE: .github/labels.json + script: + - zz-use -x tomgrv/actions/update-labels diff --git a/src/githooks/stubs/_validate-pr-format...gitlab-ci.yml b/src/githooks/stubs/_validate-pr-format...gitlab-ci.yml new file mode 100644 index 00000000..247b50a0 --- /dev/null +++ b/src/githooks/stubs/_validate-pr-format...gitlab-ci.yml @@ -0,0 +1,37 @@ +# @format + +# Counterpart of .github/workflows/validate-pr-format.yml +validate-pr-title: + extends: .zz-use + stage: test + timeout: 10m + interruptible: true + # Pending: check-pr-format reads and fixes the title through `gh pr view/edit` + # (GitHub API), so it only runs on demand until it supports GitLab merge requests. + rules: + - if: $CI_PIPELINE_SOURCE == "merge_request_event" + when: manual + allow_failure: true + variables: + GIT_DEPTH: '0' + FIX: 'true' + PR_TITLE: $CI_MERGE_REQUEST_TITLE + PR_NUMBER: $CI_MERGE_REQUEST_IID + REPO: $CI_PROJECT_PATH + HEAD_REPO_FULL_NAME: $CI_MERGE_REQUEST_SOURCE_PROJECT_PATH + # TODO(gitlab): GITHUB_TOKEN must be a CI/CD variable until a GitLab API variant exists. + script: + - zz-use -x tomgrv/actions/check-pr-format + +check-pr-source-branch: + extends: .zz-use + stage: test + timeout: 10m + interruptible: true + rules: + - if: $CI_PIPELINE_SOURCE == "merge_request_event" + variables: + TARGET_BRANCH: $CI_MERGE_REQUEST_TARGET_BRANCH_NAME + PR_TITLE: $CI_MERGE_REQUEST_TITLE + script: + - zz-use -x tomgrv/actions/check-pr-source-branch diff --git a/src/githooks/stubs/_validate-pr-secret...gitlab-ci.yml b/src/githooks/stubs/_validate-pr-secret...gitlab-ci.yml new file mode 100644 index 00000000..a0d6e473 --- /dev/null +++ b/src/githooks/stubs/_validate-pr-secret...gitlab-ci.yml @@ -0,0 +1,20 @@ +# @format + +# Counterpart of .github/workflows/validate-pr-secret.yml +# +# tomgrv/actions/check-secret wraps gitleaks/gitleaks-action (no run.sh, so it +# cannot run through zz-use): the gitleaks CLI is run directly instead. The CLI +# needs no license, and reports in the job log rather than on the merge request. +validate-pr-secret: + stage: test + timeout: 10m + interruptible: true + image: + name: zricethezav/gitleaks:latest + entrypoint: [''] + rules: + - if: $CI_PIPELINE_SOURCE == "merge_request_event" + variables: + GIT_DEPTH: '0' + script: + - gitleaks detect --source . --redact --verbose --log-opts "$CI_MERGE_REQUEST_DIFF_BASE_SHA..HEAD" diff --git a/src/githooks/stubs/_validate-pr-vulnerabilities...gitlab-ci.yml b/src/githooks/stubs/_validate-pr-vulnerabilities...gitlab-ci.yml new file mode 100644 index 00000000..b62a5750 --- /dev/null +++ b/src/githooks/stubs/_validate-pr-vulnerabilities...gitlab-ci.yml @@ -0,0 +1,21 @@ +# @format + +# Counterpart of .github/workflows/validate-pr-vulnerabilities.yml +# +# aquasecurity/trivy-action becomes the trivy CLI; the SARIF upload to the +# GitHub Security tab has no equivalent here, the report is a job artifact. +validate-pr-vulnerabilities: + stage: test + timeout: 10m + interruptible: true + image: + name: aquasec/trivy:latest + entrypoint: [''] + rules: + - if: $CI_PIPELINE_SOURCE == "merge_request_event" && $CI_MERGE_REQUEST_TITLE !~ /^(Draft:|\[Draft\]|\(Draft\))/i + script: + - trivy fs --scanners vuln,misconfig,secret --severity HIGH,CRITICAL --ignore-unfixed --exit-code 1 --format sarif --output trivy.sarif . + artifacts: + when: always + paths: + - trivy.sarif diff --git a/src/gitutils/README.md b/src/gitutils/README.md index d1e521ee..0df60dfd 100644 --- a/src/gitutils/README.md +++ b/src/gitutils/README.md @@ -185,3 +185,9 @@ Both rewrite history: use `-f` carefully, it allows overwriting pushed history. ## Contributing If you have a feature that you would like to add to this repository, please open an issue or submit a pull request. + +## GitLab CI + +Each workflow above has a GitLab counterpart, deployed as a fragment merged into the repository's `.gitlab-ci.yml` (`stubs/_...gitlab-ci.yml`). `tomgrv/actions` steps run through `zz-use -x tomgrv/actions/`, with the action inputs set as environment variables. + +- `release-prod`, `manage-orphan-branches`, `manage-monorepo-merge`, `manage-monorepo-split`: pending, manual jobs - the actions behind them use the GitHub API. `DRY_RUN`, `SOURCE_BRANCH` and `EXCLUDE_PATHS` replace the workflow inputs. diff --git a/src/gitutils/stubs/_manage-monorepo-merge...gitlab-ci.yml b/src/gitutils/stubs/_manage-monorepo-merge...gitlab-ci.yml new file mode 100644 index 00000000..78ca4712 --- /dev/null +++ b/src/gitutils/stubs/_manage-monorepo-merge...gitlab-ci.yml @@ -0,0 +1,43 @@ +# @format + +# Counterpart of .github/workflows/manage-monorepo-merge.yml +# +# Pending: degit-package and create-pr push to and open pull requests on the +# GitHub package repositories (GitHub API). The GitHub App token is replaced by +# a GITHUB_TOKEN CI/CD variable that can write to those repositories. The +# package matrix is a loop, and the PHP/Node setup steps are the job image. +# Set SOURCE_BRANCH / EXCLUDE_PATHS (the workflow inputs) when running manually. +manage-monorepo-merge: + extends: .zz-use + stage: deploy + timeout: 30m + resource_group: manage-monorepo-merge + rules: + - if: $CI_PIPELINE_SOURCE == "web" + when: manual + allow_failure: true + variables: + SOURCE_BRANCH: '' + EXCLUDE_PATHS: .github,.devcontainer,.git + script: + - zz-use -x tomgrv/actions/list-packages + - sed -n 's/^packages=//p' "$GITHUB_OUTPUT" | tail -1 | jq -c '.[]' > .packages + - zz-use -x tomgrv/actions/config-bot + - | + out() { sed -n "s/^$1=//p" "$GITHUB_OUTPUT" | tail -1; } + failed=0 + while read -r pkg; do + org=$(echo "$pkg" | jq -r .org) + name=$(echo "$pkg" | jq -r .name) + : > "$GITHUB_OUTPUT" + SOURCE_ORG=$org SOURCE_NAME=$name TARGET_SUBDIR=$(echo "$pkg" | jq -r .location) \ + zz-use -x tomgrv/actions/degit-package || { failed=1; continue; } + [ "$(out has-changes)" = true ] || continue + branch=$(out source-branch) + REPOSITORY=$org/$name HEAD_BRANCH=$(out degit-branch) WORKING_DIRECTORY=$(out degit-workdir) \ + COMMIT_MESSAGE="sync: degit from $org/$name@$branch" \ + PR_TITLE="sync: degit from $org/$name" \ + PR_BODY="Automated degit sync from $org/$name. Source branch: $branch. Source commit: $(out source-sha)" \ + zz-use -x tomgrv/actions/create-pr || failed=1 + done < .packages + exit $failed diff --git a/src/gitutils/stubs/_manage-monorepo-split...gitlab-ci.yml b/src/gitutils/stubs/_manage-monorepo-split...gitlab-ci.yml new file mode 100644 index 00000000..94b43047 --- /dev/null +++ b/src/gitutils/stubs/_manage-monorepo-split...gitlab-ci.yml @@ -0,0 +1,45 @@ +# @format + +# Counterpart of .github/workflows/manage-monorepo-split.yml +# +# Pending: create-pr opens pull requests on the GitHub package repositories +# (GitHub API). The GitHub App token is replaced by a GITHUB_TOKEN CI/CD +# variable that can write to those repositories. The package matrix is a loop, +# and the PHP/Node setup steps are the job image. The `workflow_run` trigger +# (after "Release Prod") has no counterpart: add `needs:`/`trigger:` from the +# release pipeline if the split must follow a release. +manage-monorepo-split: + extends: .zz-use + stage: deploy + timeout: 30m + resource_group: manage-monorepo-split + rules: + - if: $CI_COMMIT_BRANCH == "main" && $CI_PIPELINE_SOURCE == "push" + when: manual + allow_failure: true + - if: $CI_PIPELINE_SOURCE == "web" + when: manual + allow_failure: true + variables: + GIT_DEPTH: '0' + script: + - zz-use -x tomgrv/actions/list-packages + - sed -n 's/^packages=//p' "$GITHUB_OUTPUT" | tail -1 | jq -c '.[]' > .packages + - zz-use -x tomgrv/actions/config-bot + - | + out() { sed -n "s/^$1=//p" "$GITHUB_OUTPUT" | tail -1; } + failed=0 + while read -r pkg; do + org=$(echo "$pkg" | jq -r .org) + name=$(echo "$pkg" | jq -r .name) + : > "$GITHUB_OUTPUT" + PACKAGE_DIR=$(echo "$pkg" | jq -r .path) REPO_ORG=$org REPO_NAME=$name \ + zz-use -x tomgrv/actions/split-package || { failed=1; continue; } + [ -n "$(out split-branch)" ] || continue + REPOSITORY=$org/$name HEAD_BRANCH=$(out split-branch) WORKING_DIRECTORY=$(out split-workdir) \ + COMMIT_MESSAGE="sync: import from monorepo $CI_PROJECT_PATH" \ + PR_TITLE="sync: import from monorepo $CI_PROJECT_PATH" \ + PR_BODY="Automated sync from monorepo $CI_PROJECT_URL, at commit $CI_COMMIT_SHA ($CI_PROJECT_URL/-/commit/$CI_COMMIT_SHA)." \ + zz-use -x tomgrv/actions/create-pr || failed=1 + done < .packages + exit $failed diff --git a/src/gitutils/stubs/_manage-orphan-branches...gitlab-ci.yml b/src/gitutils/stubs/_manage-orphan-branches...gitlab-ci.yml new file mode 100644 index 00000000..bbbc3cf4 --- /dev/null +++ b/src/gitutils/stubs/_manage-orphan-branches...gitlab-ci.yml @@ -0,0 +1,23 @@ +# @format + +# Counterpart of .github/workflows/manage-orphan-branches.yml +# +# Pending: prefix-orphan-branches renames branches through `gh` (GitHub API). Run +# it on demand until a GitLab variant exists, then switch the rule to the weekly +# schedule (CI/CD > Schedules, `0 4 * * 1`): $CI_PIPELINE_SOURCE == "schedule". +# `DRY_RUN` is the `dry_run` input: set it when running the pipeline manually. +prefix-orphan-branches: + extends: .zz-use + stage: deploy + timeout: 10m + resource_group: prefix-orphan-branches + rules: + - if: $CI_PIPELINE_SOURCE == "web" + when: manual + allow_failure: true + variables: + GIT_DEPTH: '0' + DRY_RUN: 'false' + GH_REPO: $CI_PROJECT_PATH + script: + - zz-use -x tomgrv/actions/prefix-orphan-branches diff --git a/src/gitutils/stubs/_release-prod...gitlab-ci.yml b/src/gitutils/stubs/_release-prod...gitlab-ci.yml new file mode 100644 index 00000000..a3a72bed --- /dev/null +++ b/src/gitutils/stubs/_release-prod...gitlab-ci.yml @@ -0,0 +1,22 @@ +# @format + +# Counterpart of .github/workflows/release-prod.yml +# +# Pending: release-promote pushes and opens releases through `gh` (GitHub API). +# `DRY_RUN` is the `dry_run` input: set it when running the pipeline manually. +release-prod: + extends: .zz-use + stage: deploy + timeout: 30m + resource_group: release-prod + rules: + - if: $CI_PIPELINE_SOURCE == "web" + when: manual + allow_failure: true + variables: + GIT_DEPTH: '0' + DRY_RUN: 'false' + SCRIPTS_REF: v3 + script: + - git checkout -B develop origin/develop + - zz-use -x tomgrv/actions/release-promote diff --git a/src/larasets/README.md b/src/larasets/README.md index d9c51bd4..1c9dc175 100644 --- a/src/larasets/README.md +++ b/src/larasets/README.md @@ -136,3 +136,11 @@ The deployed `validate-pr-php.yml` runs `tomgrv/actions/list-php-parts` to build ## Contributing If you have a feature that you would like to add to this repository, please open an issue or submit a pull request. + +## GitLab CI + +Each workflow above has a GitLab counterpart, deployed as a fragment merged into the repository's `.gitlab-ci.yml` (`stubs/_...gitlab-ci.yml`). `tomgrv/actions` steps run through `zz-use -x tomgrv/actions/`, with the action inputs set as environment variables. + +- `.zz-use-php`: shared base (PHP, Composer, reviewdog) of the jobs below. +- `validate-php-code`, `run-php-tests`, `security-audit`: run on merge requests (`check-laravel` has no script to run through `zz-use`, only its pint step is ported). +- `fix-phpinsights`: pending, manual job - `create-pr` uses the GitHub API. diff --git a/src/larasets/stubs/_fix-php...gitlab-ci.yml b/src/larasets/stubs/_fix-php...gitlab-ci.yml new file mode 100644 index 00000000..c62d60b6 --- /dev/null +++ b/src/larasets/stubs/_fix-php...gitlab-ci.yml @@ -0,0 +1,34 @@ +# @format + +# Counterpart of .github/workflows/fix-php.yml +# +# Pending: create-pr opens a GitHub pull request through `gh` (GitHub API). Run +# it on demand until a GitLab variant exists. `TARGET_PATHS` and `BASE_BRANCH` +# are the `paths` / `base_branch` inputs: set them when running manually. +fix-phpinsights: + extends: .zz-use-php + stage: deploy + resource_group: fix-phpinsights + rules: + - if: $CI_PIPELINE_SOURCE == "web" + when: manual + allow_failure: true + variables: + TARGET_PATHS: app,config,database,resources,routes,tests,modules,packages + BASE_BRANCH: develop + script: + - zz-use -x tomgrv/actions/config-bot + - zz-use -x tomgrv/actions/run-phpstan + - | + if [ -d app/Filament ]; then + FILACHECK_PATH=app/Filament zz-use -x tomgrv/actions/run-filacheck + fi + - BLADE=true zz-use -x tomgrv/actions/run-pint + - | + if ! git diff --quiet; then + REPOSITORY=$CI_PROJECT_PATH HEAD_BRANCH="chore/phpinsights-fix-$CI_PIPELINE_ID" \ + WORKING_DIRECTORY=$CI_PROJECT_DIR COMMIT_MESSAGE='chore: apply PHP Insights auto-fixes' \ + PR_TITLE='chore: apply PHP Insights auto-fixes' \ + PR_BODY='Automated PHP Insights fixes generated by CI.' \ + zz-use -x tomgrv/actions/create-pr + fi diff --git a/src/larasets/stubs/_validate-pr-php...gitlab-ci.yml b/src/larasets/stubs/_validate-pr-php...gitlab-ci.yml new file mode 100644 index 00000000..2cfeed60 --- /dev/null +++ b/src/larasets/stubs/_validate-pr-php...gitlab-ci.yml @@ -0,0 +1,59 @@ +# @format + +# Counterpart of .github/workflows/validate-pr-php.yml +# +# tomgrv/actions/check-laravel has no run.sh (it is a composite of other +# actions), so zz-use cannot run it: its pint step is run here, the rest of its +# checks are pending. The `part` matrix is a loop over list-php-parts. +validate-php-code: + extends: .zz-use-php + stage: test + interruptible: true + rules: + - if: $CI_PIPELINE_SOURCE == "merge_request_event" + variables: + GIT_DEPTH: '0' + BLADE: 'true' + script: + # TODO(gitlab): add the remaining check-laravel steps (phpstan, phpinsights, phpmd, composer validate). + - zz-use -x tomgrv/actions/run-pint + - | + if [ -d app/Filament ]; then + FILACHECK_PATH=app/Filament zz-use -x tomgrv/actions/run-filacheck + fi + +run-php-tests: + extends: .zz-use-php + stage: test + needs: + - validate-php-code + interruptible: true + rules: + - if: $CI_PIPELINE_SOURCE == "merge_request_event" + services: + - name: mysql:8.0 + alias: mysql + variables: + GIT_DEPTH: '0' + MYSQL_ALLOW_EMPTY_PASSWORD: 'yes' + MYSQL_DATABASE: testing + DB_CONNECTION: mysql + DB_HOST: mysql + DB_PORT: '3306' + DB_DATABASE: testing + DB_USERNAME: root + DB_PASSWORD: '' + script: + - zz-use -x tomgrv/actions/list-php-parts + - | + out() { sed -n "s/^$1=//p" "$GITHUB_OUTPUT" | tail -1; } + [ "$(out has-parts)" = true ] || { echo "No PHP parts affected, skipping."; exit 0; } + out matrix | jq -c '.[]' > .parts + - '[ ! -d node_modules/playwright ] || npx playwright install --with-deps chromium' + - | + failed=0 + while read -r part; do + REVIEWDOG_NAME="phpunit ($(echo "$part" | jq -r .name))" TEST_PATHS=$(echo "$part" | jq -r .path) \ + zz-use -x tomgrv/actions/run-phptests || failed=1 + done < .parts + exit $failed diff --git a/src/larasets/stubs/_validate-pr-security...gitlab-ci.yml b/src/larasets/stubs/_validate-pr-security...gitlab-ci.yml new file mode 100644 index 00000000..72c1072e --- /dev/null +++ b/src/larasets/stubs/_validate-pr-security...gitlab-ci.yml @@ -0,0 +1,12 @@ +# @format + +# Counterpart of .github/workflows/validate-pr-security.yml +security-audit: + extends: .zz-use-php + stage: test + interruptible: true + rules: + - if: $CI_PIPELINE_SOURCE == "merge_request_event" + script: + - zz-use -x tomgrv/actions/check-security-npm + - zz-use -x tomgrv/actions/check-security-composer diff --git a/src/larasets/stubs/_zz-use-php...gitlab-ci.yml b/src/larasets/stubs/_zz-use-php...gitlab-ci.yml new file mode 100644 index 00000000..4bef0fba --- /dev/null +++ b/src/larasets/stubs/_zz-use-php...gitlab-ci.yml @@ -0,0 +1,23 @@ +# @format + +# Base of the larasets GitLab jobs (counterpart of the `setup-php`, +# `setup-node` and `setup-reviewdog` GitHub actions): the zz-use base image +# (Node) plus PHP, Composer and reviewdog. +.zz-use-php: + extends: .zz-use + variables: + COMPOSER_CACHE_DIR: $CI_PROJECT_DIR/.cache/composer + NODE_ENV: test + # TODO(gitlab): reviewdog reports to the merge request with this reporter, + # and needs a masked REVIEWDOG_GITLAB_API_TOKEN CI/CD variable. + REVIEWDOG_REPORTER: gitlab-mr-discussion + cache: + key: zz-use-php + paths: + - .cache/zz_scripts/ + - .cache/composer/ + before_script: + - !reference [.zz-use, before_script] + # shortcut: the distribution PHP version, pin one (e.g. sury.org) when it matters + - apt-get update -qq && apt-get install -y -qq php-cli php-mbstring php-xml php-curl php-zip php-mysql composer unzip + - curl -sfL https://raw.githubusercontent.com/reviewdog/reviewdog/master/install.sh | sh -s -- -b /usr/local/bin v0.20.3 diff --git a/src/minikube/README.md b/src/minikube/README.md index 72ce86db..eab5481d 100644 --- a/src/minikube/README.md +++ b/src/minikube/README.md @@ -29,3 +29,9 @@ curl -fsSL https://raw.githubusercontent.com/tomgrv/devcontainer-features/develo ```sh npm install --save-dev @tomgrv/devcontainer-features-minikube ``` + +## GitLab CI + +Each workflow above has a GitLab counterpart, deployed as a fragment merged into the repository's `.gitlab-ci.yml` (`stubs/_...gitlab-ci.yml`). `tomgrv/actions` steps run through `zz-use -x tomgrv/actions/`, with the action inputs set as environment variables. + +- `validate-pr-docker`: `check-dockerfile` on merge requests. diff --git a/src/minikube/stubs/_validate-pr-docker...gitlab-ci.yml b/src/minikube/stubs/_validate-pr-docker...gitlab-ci.yml new file mode 100644 index 00000000..be3a56bb --- /dev/null +++ b/src/minikube/stubs/_validate-pr-docker...gitlab-ci.yml @@ -0,0 +1,15 @@ +# @format + +# Counterpart of .github/workflows/validate-pr-docker.yml +validate-pr-docker: + extends: .zz-use + stage: test + interruptible: true + rules: + - if: $CI_PIPELINE_SOURCE == "merge_request_event" && $CI_MERGE_REQUEST_TITLE !~ /^(Draft:|\[Draft\]|\(Draft\))/i + variables: + # TODO(gitlab): reviewdog reports to the merge request with this reporter, + # and needs a masked REVIEWDOG_GITLAB_API_TOKEN CI/CD variable. + REVIEWDOG_REPORTER: gitlab-mr-discussion + script: + - zz-use -x tomgrv/actions/check-dockerfile diff --git a/stubs/_update-features...gitlab-ci.yml b/stubs/_update-features...gitlab-ci.yml new file mode 100644 index 00000000..7e4a1989 --- /dev/null +++ b/stubs/_update-features...gitlab-ci.yml @@ -0,0 +1,38 @@ +# @format + +# Counterpart of .github/workflows/update-features.yml +update-features: + extends: .zz-use + stage: deploy + timeout: 15m + resource_group: update-features + # Pending: create-pr opens a GitHub pull request through `gh`. Run it on demand + # until a GitLab variant exists, then switch the rule to the nightly schedule + # (CI/CD > Schedules, `0 3 * * *`): $CI_PIPELINE_SOURCE == "schedule". + rules: + - if: $CI_PIPELINE_SOURCE == "web" + when: manual + allow_failure: true + variables: + GIT_DEPTH: '0' + HEAD_BRANCH: chore/update-features + WORKING_DIRECTORY: $CI_PROJECT_DIR + COMMIT_MESSAGE: 'chore: update features' + PR_TITLE: 'chore: update features' + PR_BODY: | + Automated nightly feature updates. + + Command run: + - npm exec --yes -- tomgrv/devcontainer-features update -a + REPOSITORY: $CI_PROJECT_PATH + script: + - zz-use -x tomgrv/actions/config-bot + - npm exec --yes -- tomgrv/devcontainer-features update -a + - npm install --package-lock-only --ignore-scripts --no-audit --no-fund + # A CI job token cannot push .github/workflows changes: drop them, like the + # GitHub workflow does without UPDATE_FEATURES_TOKEN. + - git checkout -- .github/workflows && git clean -fdq -- .github/workflows + - | + if ! git diff --quiet -- .; then + zz-use -x tomgrv/actions/create-pr + fi diff --git a/tests/README.md b/tests/README.md index ed156e5c..b330509a 100644 --- a/tests/README.md +++ b/tests/README.md @@ -19,3 +19,7 @@ bats tests/ CI runs this automatically via `.github/workflows/test-workspaces.yaml`'s `repo-tests` job, silently skipped when this directory has no `*.bats` files. + +## GitLab fragments + +`gitlab-fragments.bats` guards the `_...gitlab-ci.yml` stubs: each deploys to the dotted `.gitlab-ci.yml` (the three dots survive `feature-configure`'s `..` collapse and `_` qualifier strip), is present for every GitHub workflow, defines unique jobs, and calls `tomgrv/actions` through `zz-use -x` only. diff --git a/tests/gitlab-fragments.bats b/tests/gitlab-fragments.bats new file mode 100644 index 00000000..94ffe9ec --- /dev/null +++ b/tests/gitlab-fragments.bats @@ -0,0 +1,75 @@ +#!/usr/bin/env bats +# @format + +# Guards the GitLab CI fragments (stubs/**/_...gitlab-ci.yml), the +# counterparts of the GitHub workflows: each must deploy to the dotted +# `.gitlab-ci.yml`, merge with the others without clashing jobs, extend +# templates that exist, and call tomgrv/actions only through `zz-use -x`. + +setup() { + ROOT="${BATS_TEST_DIRNAME}/.." + FRAGMENTS=$(ls "$ROOT"/src/*/stubs/_*gitlab-ci.yml "$ROOT"/stubs/_*gitlab-ci.yml) +} + +# feature-configure's stub naming: `..` collapses to `.`, then a `_` qualifier +# is stripped up to the first `.`. +deployed_name() { + base=$(basename "$1" | sed 's/\.\./\./g') + base=${base#_} + echo "${base#*.}" +} + +@test "every fragment deploys to the dotted .gitlab-ci.yml" { + for f in $FRAGMENTS; do + [ "$(deployed_name "$f")" = ".gitlab-ci.yml" ] || { + echo "$f deploys as $(deployed_name "$f")" + return 1 + } + done +} + +@test "every GitHub workflow stub has a GitLab fragment of the same name" { + for w in "$ROOT"/src/*/stubs/.github/workflows/*.y*ml "$ROOT"/stubs/.github/workflows/*.y*ml; do + name=$(basename "$w") + name=${name%.*} + ls "$(dirname "$w")"/../../_"$name"...gitlab-ci.yml >/dev/null 2>&1 || { + echo "no GitLab fragment for $w" + return 1 + } + done +} + +# Top-level keys sit at column 0 in the fragments (no flow-style mappings). +top_keys() { + grep -hoE '^[^ #-][^:]*' "$@" +} + +@test "fragments are valid YAML" { + python3 -c 'import yaml' 2>/dev/null || skip "PyYAML is not installed" + # `!reference` is a GitLab tag PyYAML does not know: ignore it. + load='import sys, yaml; yaml.SafeLoader.add_multi_constructor("!", lambda l, s, n: None); [yaml.load(open(f), Loader=yaml.SafeLoader) for f in sys.argv[1:]]' + python3 -c "$load" $FRAGMENTS +} + +@test "no job is defined in two fragments" { + dups=$(top_keys $FRAGMENTS | sort | uniq -d) + [ -z "$dups" ] || { + echo "defined in several fragments: $dups" + return 1 + } +} + +@test "every extends targets a template defined in a fragment" { + defined=$(top_keys $FRAGMENTS) + for t in $(grep -hoE '^ extends: .*' $FRAGMENTS | sed 's/^ extends: //' | sort -u); do + echo "$defined" | grep -qx -- "$t" || { + echo "extends $t, defined nowhere" + return 1 + } + done +} + +@test "tomgrv/actions are called through zz-use -x, without a GitHub-style pin" { + run grep -rhE 'tomgrv/actions/[A-Za-z0-9_-]+@|uses:' $FRAGMENTS + [ "$status" -eq 1 ] +} From b161b38899faad534b7fe455181750a2aa4ee744 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 29 Oct 2026 17:58:46 +0000 Subject: [PATCH 2/7] =?UTF-8?q?feat(devcontainer-features-githooks):=20?= =?UTF-8?q?=E2=9C=A8=20run=20validate-pr-title=20on=20GitLab=20merge=20req?= =?UTF-8?q?uests?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit check-pr-format now reads and fixes the title through the GitLab API (tomgrv/actions#152), so the job is no longer pending. Co-Authored-By: Claude Sonnet 5.5 Claude-Session: https://claude.ai/code/session_01XWCx6ifQFkyqUFNvkGPo4o --- src/githooks/README.md | 3 ++- src/githooks/stubs/_validate-pr-format...gitlab-ci.yml | 8 +++----- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/githooks/README.md b/src/githooks/README.md index e5d7666e..8d40273a 100644 --- a/src/githooks/README.md +++ b/src/githooks/README.md @@ -122,4 +122,5 @@ If you have a feature that you would like to add to this repository, please open Each workflow above has a GitLab counterpart, deployed as a fragment merged into the repository's `.gitlab-ci.yml` (`stubs/_...gitlab-ci.yml`). `tomgrv/actions` steps run through `zz-use -x tomgrv/actions/`, with the action inputs set as environment variables. - `check-pr-source-branch`, `validate-pr-secret` (gitleaks CLI), `validate-pr-vulnerabilities` (trivy CLI): run on merge requests. -- `validate-pr-title`, `clean-branches`, `update-labels`, `configure-gitlab`: pending, manual jobs - the actions behind them use the GitHub API (`gh`) and need a GitHub token in `GITHUB_TOKEN`. +- `validate-pr-title`: `check-pr-format` through the GitLab API; set a `GITLAB_TOKEN` variable (project access token, `api` scope) to let it fix the title. +- `clean-branches`, `update-labels`, `configure-gitlab`: pending, manual jobs - the actions behind them use the GitHub API (`gh`) and need a GitHub token in `GITHUB_TOKEN`. diff --git a/src/githooks/stubs/_validate-pr-format...gitlab-ci.yml b/src/githooks/stubs/_validate-pr-format...gitlab-ci.yml index 247b50a0..d21ce7d5 100644 --- a/src/githooks/stubs/_validate-pr-format...gitlab-ci.yml +++ b/src/githooks/stubs/_validate-pr-format...gitlab-ci.yml @@ -6,12 +6,11 @@ validate-pr-title: stage: test timeout: 10m interruptible: true - # Pending: check-pr-format reads and fixes the title through `gh pr view/edit` - # (GitHub API), so it only runs on demand until it supports GitLab merge requests. + # Needs tomgrv/actions#152 (GitLab API support in check-pr-format). Without a + # GITLAB_TOKEN CI/CD variable (project access token, `api` scope) the title is + # only validated, not fixed. rules: - if: $CI_PIPELINE_SOURCE == "merge_request_event" - when: manual - allow_failure: true variables: GIT_DEPTH: '0' FIX: 'true' @@ -19,7 +18,6 @@ validate-pr-title: PR_NUMBER: $CI_MERGE_REQUEST_IID REPO: $CI_PROJECT_PATH HEAD_REPO_FULL_NAME: $CI_MERGE_REQUEST_SOURCE_PROJECT_PATH - # TODO(gitlab): GITHUB_TOKEN must be a CI/CD variable until a GitLab API variant exists. script: - zz-use -x tomgrv/actions/check-pr-format From c8c4cc06efb8c9afa2ee7b26d8dcd122376cceed Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 29 Oct 2026 18:06:34 +0000 Subject: [PATCH 3/7] =?UTF-8?q?feat(devcontainer-features-gitutils):=20?= =?UTF-8?q?=E2=9C=A8=20run=20GitLab=20jobs=20through=20the=20new=20GitLab?= =?UTF-8?q?=20API=20support?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit clean-branches, update-labels, manage-orphan-branches and update-features run on schedules; fix-phpinsights opens a merge request; the monorepo jobs keep targeting GitHub repositories with FORGE=github. Co-Authored-By: Claude Sonnet 5.5 Claude-Session: https://claude.ai/code/session_01XWCx6ifQFkyqUFNvkGPo4o --- src/githooks/README.md | 3 ++- src/githooks/stubs/_clean-branches...gitlab-ci.yml | 8 +++++--- src/githooks/stubs/_update-labels...gitlab-ci.yml | 8 +++++--- src/gitutils/README.md | 3 ++- src/gitutils/stubs/_manage-monorepo-merge...gitlab-ci.yml | 2 +- src/gitutils/stubs/_manage-monorepo-split...gitlab-ci.yml | 2 +- .../stubs/_manage-orphan-branches...gitlab-ci.yml | 8 +++++--- src/larasets/README.md | 2 +- src/larasets/stubs/_fix-php...gitlab-ci.yml | 6 +++--- stubs/_update-features...gitlab-ci.yml | 8 +++++--- 10 files changed, 30 insertions(+), 20 deletions(-) diff --git a/src/githooks/README.md b/src/githooks/README.md index 8d40273a..8e0420ff 100644 --- a/src/githooks/README.md +++ b/src/githooks/README.md @@ -123,4 +123,5 @@ Each workflow above has a GitLab counterpart, deployed as a fragment merged into - `check-pr-source-branch`, `validate-pr-secret` (gitleaks CLI), `validate-pr-vulnerabilities` (trivy CLI): run on merge requests. - `validate-pr-title`: `check-pr-format` through the GitLab API; set a `GITLAB_TOKEN` variable (project access token, `api` scope) to let it fix the title. -- `clean-branches`, `update-labels`, `configure-gitlab`: pending, manual jobs - the actions behind them use the GitHub API (`gh`) and need a GitHub token in `GITHUB_TOKEN`. +- `clean-branches`, `update-labels`: through the GitLab API (tomgrv/actions#152), weekly once a pipeline schedule is created, with a `GITLAB_TOKEN` variable. +- `configure-gitlab`: pending, manual job - the branch protection is not configured by this job yet. diff --git a/src/githooks/stubs/_clean-branches...gitlab-ci.yml b/src/githooks/stubs/_clean-branches...gitlab-ci.yml index 084c6647..5fa3beeb 100644 --- a/src/githooks/stubs/_clean-branches...gitlab-ci.yml +++ b/src/githooks/stubs/_clean-branches...gitlab-ci.yml @@ -6,10 +6,12 @@ clean-branches: stage: deploy timeout: 10m resource_group: clean-branches - # Pending: clean-branches deletes branches through `gh` (GitHub API). Run it on - # demand until a GitLab variant exists, then switch the rule to the weekly - # schedule (CI/CD > Schedules, `0 5 * * 1`): $CI_PIPELINE_SOURCE == "schedule". + # Needs tomgrv/actions#152. Weekly: create the schedule under CI/CD > Schedules + # (`0 5 * * 1`); it can also be started by hand. + # GITLAB_TOKEN: CI/CD variable, project access token with the `api` scope + # (plus `write_repository` where the job pushes). rules: + - if: $CI_PIPELINE_SOURCE == "schedule" - if: $CI_PIPELINE_SOURCE == "web" when: manual allow_failure: true diff --git a/src/githooks/stubs/_update-labels...gitlab-ci.yml b/src/githooks/stubs/_update-labels...gitlab-ci.yml index 5987408b..3a71fad1 100644 --- a/src/githooks/stubs/_update-labels...gitlab-ci.yml +++ b/src/githooks/stubs/_update-labels...gitlab-ci.yml @@ -6,10 +6,12 @@ update-labels: stage: deploy timeout: 10m resource_group: update-labels - # Pending: update-labels syncs GitHub issue labels through `gh`. Run it on demand - # until a GitLab variant exists, then switch the rule to the weekly schedule - # (CI/CD > Schedules, `0 4 * * 1`): $CI_PIPELINE_SOURCE == "schedule". + # Needs tomgrv/actions#152. Weekly: create the schedule under CI/CD > Schedules + # (`0 4 * * 1`); it can also be started by hand. + # GITLAB_TOKEN: CI/CD variable, project access token with the `api` scope + # (plus `write_repository` where the job pushes). rules: + - if: $CI_PIPELINE_SOURCE == "schedule" - if: $CI_PIPELINE_SOURCE == "web" when: manual allow_failure: true diff --git a/src/gitutils/README.md b/src/gitutils/README.md index 0df60dfd..c6e0f595 100644 --- a/src/gitutils/README.md +++ b/src/gitutils/README.md @@ -190,4 +190,5 @@ If you have a feature that you would like to add to this repository, please open Each workflow above has a GitLab counterpart, deployed as a fragment merged into the repository's `.gitlab-ci.yml` (`stubs/_...gitlab-ci.yml`). `tomgrv/actions` steps run through `zz-use -x tomgrv/actions/`, with the action inputs set as environment variables. -- `release-prod`, `manage-orphan-branches`, `manage-monorepo-merge`, `manage-monorepo-split`: pending, manual jobs - the actions behind them use the GitHub API. `DRY_RUN`, `SOURCE_BRANCH` and `EXCLUDE_PATHS` replace the workflow inputs. +- `manage-orphan-branches`: through the GitLab API (tomgrv/scripts#107), weekly once a pipeline schedule is created, with a `GITLAB_TOKEN` variable. +- `release-prod`, `manage-monorepo-merge`, `manage-monorepo-split`: pending, manual jobs - the actions behind them use the GitHub API (the monorepo jobs open pull requests on the GitHub package repositories with `FORGE=github`). `DRY_RUN`, `SOURCE_BRANCH` and `EXCLUDE_PATHS` replace the workflow inputs. diff --git a/src/gitutils/stubs/_manage-monorepo-merge...gitlab-ci.yml b/src/gitutils/stubs/_manage-monorepo-merge...gitlab-ci.yml index 78ca4712..68ec81dd 100644 --- a/src/gitutils/stubs/_manage-monorepo-merge...gitlab-ci.yml +++ b/src/gitutils/stubs/_manage-monorepo-merge...gitlab-ci.yml @@ -34,7 +34,7 @@ manage-monorepo-merge: zz-use -x tomgrv/actions/degit-package || { failed=1; continue; } [ "$(out has-changes)" = true ] || continue branch=$(out source-branch) - REPOSITORY=$org/$name HEAD_BRANCH=$(out degit-branch) WORKING_DIRECTORY=$(out degit-workdir) \ + FORGE=github REPOSITORY=$org/$name HEAD_BRANCH=$(out degit-branch) WORKING_DIRECTORY=$(out degit-workdir) \ COMMIT_MESSAGE="sync: degit from $org/$name@$branch" \ PR_TITLE="sync: degit from $org/$name" \ PR_BODY="Automated degit sync from $org/$name. Source branch: $branch. Source commit: $(out source-sha)" \ diff --git a/src/gitutils/stubs/_manage-monorepo-split...gitlab-ci.yml b/src/gitutils/stubs/_manage-monorepo-split...gitlab-ci.yml index 94b43047..1d693595 100644 --- a/src/gitutils/stubs/_manage-monorepo-split...gitlab-ci.yml +++ b/src/gitutils/stubs/_manage-monorepo-split...gitlab-ci.yml @@ -36,7 +36,7 @@ manage-monorepo-split: PACKAGE_DIR=$(echo "$pkg" | jq -r .path) REPO_ORG=$org REPO_NAME=$name \ zz-use -x tomgrv/actions/split-package || { failed=1; continue; } [ -n "$(out split-branch)" ] || continue - REPOSITORY=$org/$name HEAD_BRANCH=$(out split-branch) WORKING_DIRECTORY=$(out split-workdir) \ + FORGE=github REPOSITORY=$org/$name HEAD_BRANCH=$(out split-branch) WORKING_DIRECTORY=$(out split-workdir) \ COMMIT_MESSAGE="sync: import from monorepo $CI_PROJECT_PATH" \ PR_TITLE="sync: import from monorepo $CI_PROJECT_PATH" \ PR_BODY="Automated sync from monorepo $CI_PROJECT_URL, at commit $CI_COMMIT_SHA ($CI_PROJECT_URL/-/commit/$CI_COMMIT_SHA)." \ diff --git a/src/gitutils/stubs/_manage-orphan-branches...gitlab-ci.yml b/src/gitutils/stubs/_manage-orphan-branches...gitlab-ci.yml index bbbc3cf4..dad7b828 100644 --- a/src/gitutils/stubs/_manage-orphan-branches...gitlab-ci.yml +++ b/src/gitutils/stubs/_manage-orphan-branches...gitlab-ci.yml @@ -2,16 +2,18 @@ # Counterpart of .github/workflows/manage-orphan-branches.yml # -# Pending: prefix-orphan-branches renames branches through `gh` (GitHub API). Run -# it on demand until a GitLab variant exists, then switch the rule to the weekly -# schedule (CI/CD > Schedules, `0 4 * * 1`): $CI_PIPELINE_SOURCE == "schedule". +# Needs tomgrv/scripts#107 (git-fix-orphans on GitLab). Weekly: create the schedule +# under CI/CD > Schedules (`0 4 * * 1`); it can also be started by hand. # `DRY_RUN` is the `dry_run` input: set it when running the pipeline manually. prefix-orphan-branches: extends: .zz-use stage: deploy timeout: 10m resource_group: prefix-orphan-branches + # GITLAB_TOKEN: CI/CD variable, project access token with the `api` scope + # (plus `write_repository` where the job pushes). rules: + - if: $CI_PIPELINE_SOURCE == "schedule" - if: $CI_PIPELINE_SOURCE == "web" when: manual allow_failure: true diff --git a/src/larasets/README.md b/src/larasets/README.md index 1c9dc175..ec12a2c9 100644 --- a/src/larasets/README.md +++ b/src/larasets/README.md @@ -143,4 +143,4 @@ Each workflow above has a GitLab counterpart, deployed as a fragment merged into - `.zz-use-php`: shared base (PHP, Composer, reviewdog) of the jobs below. - `validate-php-code`, `run-php-tests`, `security-audit`: run on merge requests (`check-laravel` has no script to run through `zz-use`, only its pint step is ported). -- `fix-phpinsights`: pending, manual job - `create-pr` uses the GitHub API. +- `fix-phpinsights`: manual job, opens a merge request through the GitLab API (tomgrv/actions#152) with a `GITLAB_TOKEN` variable. diff --git a/src/larasets/stubs/_fix-php...gitlab-ci.yml b/src/larasets/stubs/_fix-php...gitlab-ci.yml index c62d60b6..d15216ea 100644 --- a/src/larasets/stubs/_fix-php...gitlab-ci.yml +++ b/src/larasets/stubs/_fix-php...gitlab-ci.yml @@ -2,9 +2,9 @@ # Counterpart of .github/workflows/fix-php.yml # -# Pending: create-pr opens a GitHub pull request through `gh` (GitHub API). Run -# it on demand until a GitLab variant exists. `TARGET_PATHS` and `BASE_BRANCH` -# are the `paths` / `base_branch` inputs: set them when running manually. +# Needs tomgrv/actions#152 (create-pr on GitLab); GITLAB_TOKEN needs the `api` and +# `write_repository` scopes. `TARGET_PATHS` and `BASE_BRANCH` are the `paths` / +# `base_branch` inputs: set them when running manually. fix-phpinsights: extends: .zz-use-php stage: deploy diff --git a/stubs/_update-features...gitlab-ci.yml b/stubs/_update-features...gitlab-ci.yml index 7e4a1989..4472d40b 100644 --- a/stubs/_update-features...gitlab-ci.yml +++ b/stubs/_update-features...gitlab-ci.yml @@ -6,10 +6,12 @@ update-features: stage: deploy timeout: 15m resource_group: update-features - # Pending: create-pr opens a GitHub pull request through `gh`. Run it on demand - # until a GitLab variant exists, then switch the rule to the nightly schedule - # (CI/CD > Schedules, `0 3 * * *`): $CI_PIPELINE_SOURCE == "schedule". + # Needs tomgrv/actions#152 (create-pr on GitLab). Nightly: create the schedule under + # CI/CD > Schedules (`0 3 * * *`); it can also be started by hand. + # GITLAB_TOKEN: CI/CD variable, project access token with the `api` scope + # (plus `write_repository` where the job pushes). rules: + - if: $CI_PIPELINE_SOURCE == "schedule" - if: $CI_PIPELINE_SOURCE == "web" when: manual allow_failure: true From 07b0795657cce14e347988c7a70c5aaa56ff502b Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 29 Oct 2026 18:14:31 +0000 Subject: [PATCH 4/7] =?UTF-8?q?feat(devcontainer-features-gitutils):=20?= =?UTF-8?q?=E2=9C=A8=20run=20release-prod=20through=20the=20GitLab=20API?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Sonnet 5.5 Claude-Session: https://claude.ai/code/session_01XWCx6ifQFkyqUFNvkGPo4o --- src/gitutils/README.md | 3 ++- src/gitutils/stubs/_release-prod...gitlab-ci.yml | 12 +++++++++--- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/gitutils/README.md b/src/gitutils/README.md index c6e0f595..21ca834d 100644 --- a/src/gitutils/README.md +++ b/src/gitutils/README.md @@ -191,4 +191,5 @@ If you have a feature that you would like to add to this repository, please open Each workflow above has a GitLab counterpart, deployed as a fragment merged into the repository's `.gitlab-ci.yml` (`stubs/_...gitlab-ci.yml`). `tomgrv/actions` steps run through `zz-use -x tomgrv/actions/`, with the action inputs set as environment variables. - `manage-orphan-branches`: through the GitLab API (tomgrv/scripts#107), weekly once a pipeline schedule is created, with a `GITLAB_TOKEN` variable. -- `release-prod`, `manage-monorepo-merge`, `manage-monorepo-split`: pending, manual jobs - the actions behind them use the GitHub API (the monorepo jobs open pull requests on the GitHub package repositories with `FORGE=github`). `DRY_RUN`, `SOURCE_BRANCH` and `EXCLUDE_PATHS` replace the workflow inputs. +- `release-prod`: manual job, pushes the release and comments the merge requests through the GitLab API (tomgrv/actions#152), with a `GITLAB_TOKEN` variable allowed to push to `main` and create tags. +- `manage-monorepo-merge`, `manage-monorepo-split`: pending, manual jobs - the actions behind them use the GitHub API (the monorepo jobs open pull requests on the GitHub package repositories with `FORGE=github`). `DRY_RUN`, `SOURCE_BRANCH` and `EXCLUDE_PATHS` replace the workflow inputs. diff --git a/src/gitutils/stubs/_release-prod...gitlab-ci.yml b/src/gitutils/stubs/_release-prod...gitlab-ci.yml index a3a72bed..3a6a95d2 100644 --- a/src/gitutils/stubs/_release-prod...gitlab-ci.yml +++ b/src/gitutils/stubs/_release-prod...gitlab-ci.yml @@ -2,8 +2,10 @@ # Counterpart of .github/workflows/release-prod.yml # -# Pending: release-promote pushes and opens releases through `gh` (GitHub API). -# `DRY_RUN` is the `dry_run` input: set it when running the pipeline manually. +# Needs tomgrv/actions#152 (release-promote on GitLab). GITLAB_TOKEN: CI/CD variable, +# project access token with the `api` and `write_repository` scopes, allowed to push +# to `main` and to create tags. `DRY_RUN` is the `dry_run` input: set it when running +# the pipeline manually. release-prod: extends: .zz-use stage: deploy @@ -16,7 +18,11 @@ release-prod: variables: GIT_DEPTH: '0' DRY_RUN: 'false' - SCRIPTS_REF: v3 script: + - git config user.name "$GITLAB_USER_NAME" + - git config user.email "$GITLAB_USER_EMAIL" - git checkout -B develop origin/develop + - zz-use git-release-beta git-release-prod ws-list + - zz-use -x tomgrv/actions/setup-gitversion + - zz-use -x tomgrv/actions/setup-gitflow - zz-use -x tomgrv/actions/release-promote From 9c975f859246884eee4d8a0e1e076e01f2e2d60e Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 29 Oct 2026 18:19:57 +0000 Subject: [PATCH 5/7] =?UTF-8?q?fix(devcontainer-features-gitutils):=20?= =?UTF-8?q?=F0=9F=90=9B=20capture=20the=20stdout=20outputs=20of=20the=20ac?= =?UTF-8?q?tions=20in=20GitLab=20jobs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The GitHub action wrappers append the actions' stdout to GITHUB_OUTPUT; do the same in the GitLab jobs that read list-packages, list-php-parts, degit-package and split-package outputs. Co-Authored-By: Claude Sonnet 5.5 Claude-Session: https://claude.ai/code/session_01XWCx6ifQFkyqUFNvkGPo4o --- src/common-utils/stubs/_test-workspaces...gitlab-ci.yml | 3 ++- src/gitutils/stubs/_manage-monorepo-merge...gitlab-ci.yml | 5 +++-- src/gitutils/stubs/_manage-monorepo-split...gitlab-ci.yml | 5 +++-- src/larasets/stubs/_validate-pr-php...gitlab-ci.yml | 3 ++- 4 files changed, 10 insertions(+), 6 deletions(-) diff --git a/src/common-utils/stubs/_test-workspaces...gitlab-ci.yml b/src/common-utils/stubs/_test-workspaces...gitlab-ci.yml index 426ddfc1..8d0b78fa 100644 --- a/src/common-utils/stubs/_test-workspaces...gitlab-ci.yml +++ b/src/common-utils/stubs/_test-workspaces...gitlab-ci.yml @@ -30,7 +30,8 @@ test-workspaces: FILTER=$(ws-impacted -r "$CI_MERGE_REQUEST_DIFF_BASE_SHA..HEAD") fi export FILTER - - zz-use -x tomgrv/actions/list-packages + # The actions print their outputs on stdout; the GitHub action wrapper appends them to GITHUB_OUTPUT + - zz-use -x tomgrv/actions/list-packages >> "$GITHUB_OUTPUT" - sed -n 's/^packages=//p' "$GITHUB_OUTPUT" | tail -1 | jq -r '.[].path' > .packages - | failed=0 diff --git a/src/gitutils/stubs/_manage-monorepo-merge...gitlab-ci.yml b/src/gitutils/stubs/_manage-monorepo-merge...gitlab-ci.yml index 68ec81dd..6789c502 100644 --- a/src/gitutils/stubs/_manage-monorepo-merge...gitlab-ci.yml +++ b/src/gitutils/stubs/_manage-monorepo-merge...gitlab-ci.yml @@ -20,7 +20,8 @@ manage-monorepo-merge: SOURCE_BRANCH: '' EXCLUDE_PATHS: .github,.devcontainer,.git script: - - zz-use -x tomgrv/actions/list-packages + # The actions print their outputs on stdout; the GitHub action wrapper appends them to GITHUB_OUTPUT + - zz-use -x tomgrv/actions/list-packages >> "$GITHUB_OUTPUT" - sed -n 's/^packages=//p' "$GITHUB_OUTPUT" | tail -1 | jq -c '.[]' > .packages - zz-use -x tomgrv/actions/config-bot - | @@ -31,7 +32,7 @@ manage-monorepo-merge: name=$(echo "$pkg" | jq -r .name) : > "$GITHUB_OUTPUT" SOURCE_ORG=$org SOURCE_NAME=$name TARGET_SUBDIR=$(echo "$pkg" | jq -r .location) \ - zz-use -x tomgrv/actions/degit-package || { failed=1; continue; } + zz-use -x tomgrv/actions/degit-package >> "$GITHUB_OUTPUT" || { failed=1; continue; } [ "$(out has-changes)" = true ] || continue branch=$(out source-branch) FORGE=github REPOSITORY=$org/$name HEAD_BRANCH=$(out degit-branch) WORKING_DIRECTORY=$(out degit-workdir) \ diff --git a/src/gitutils/stubs/_manage-monorepo-split...gitlab-ci.yml b/src/gitutils/stubs/_manage-monorepo-split...gitlab-ci.yml index 1d693595..625f6cf9 100644 --- a/src/gitutils/stubs/_manage-monorepo-split...gitlab-ci.yml +++ b/src/gitutils/stubs/_manage-monorepo-split...gitlab-ci.yml @@ -23,7 +23,8 @@ manage-monorepo-split: variables: GIT_DEPTH: '0' script: - - zz-use -x tomgrv/actions/list-packages + # The actions print their outputs on stdout; the GitHub action wrapper appends them to GITHUB_OUTPUT + - zz-use -x tomgrv/actions/list-packages >> "$GITHUB_OUTPUT" - sed -n 's/^packages=//p' "$GITHUB_OUTPUT" | tail -1 | jq -c '.[]' > .packages - zz-use -x tomgrv/actions/config-bot - | @@ -34,7 +35,7 @@ manage-monorepo-split: name=$(echo "$pkg" | jq -r .name) : > "$GITHUB_OUTPUT" PACKAGE_DIR=$(echo "$pkg" | jq -r .path) REPO_ORG=$org REPO_NAME=$name \ - zz-use -x tomgrv/actions/split-package || { failed=1; continue; } + zz-use -x tomgrv/actions/split-package >> "$GITHUB_OUTPUT" || { failed=1; continue; } [ -n "$(out split-branch)" ] || continue FORGE=github REPOSITORY=$org/$name HEAD_BRANCH=$(out split-branch) WORKING_DIRECTORY=$(out split-workdir) \ COMMIT_MESSAGE="sync: import from monorepo $CI_PROJECT_PATH" \ diff --git a/src/larasets/stubs/_validate-pr-php...gitlab-ci.yml b/src/larasets/stubs/_validate-pr-php...gitlab-ci.yml index 2cfeed60..dbe9f3ef 100644 --- a/src/larasets/stubs/_validate-pr-php...gitlab-ci.yml +++ b/src/larasets/stubs/_validate-pr-php...gitlab-ci.yml @@ -44,7 +44,8 @@ run-php-tests: DB_USERNAME: root DB_PASSWORD: '' script: - - zz-use -x tomgrv/actions/list-php-parts + # The actions print their outputs on stdout; the GitHub action wrapper appends them to GITHUB_OUTPUT + - zz-use -x tomgrv/actions/list-php-parts >> "$GITHUB_OUTPUT" - | out() { sed -n "s/^$1=//p" "$GITHUB_OUTPUT" | tail -1; } [ "$(out has-parts)" = true ] || { echo "No PHP parts affected, skipping."; exit 0; } From aa7da48909ed4b51666d433eec6d2000f18e6c96 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 29 Oct 2026 18:21:04 +0000 Subject: [PATCH 6/7] =?UTF-8?q?feat(devcontainer-features-githooks):=20?= =?UTF-8?q?=E2=9C=A8=20configure=20GitLab=20protected=20branches=20through?= =?UTF-8?q?=20the=20project=20API?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replaces the placeholder configure-gitlab job: protects main and develop and requires successful pipelines for merge requests. Co-Authored-By: Claude Sonnet 5.5 Claude-Session: https://claude.ai/code/session_01XWCx6ifQFkyqUFNvkGPo4o --- src/githooks/README.md | 2 +- .../stubs/_configure-github...gitlab-ci.yml | 41 +++++++++++++++---- 2 files changed, 35 insertions(+), 8 deletions(-) diff --git a/src/githooks/README.md b/src/githooks/README.md index 8e0420ff..0db7e353 100644 --- a/src/githooks/README.md +++ b/src/githooks/README.md @@ -124,4 +124,4 @@ Each workflow above has a GitLab counterpart, deployed as a fragment merged into - `check-pr-source-branch`, `validate-pr-secret` (gitleaks CLI), `validate-pr-vulnerabilities` (trivy CLI): run on merge requests. - `validate-pr-title`: `check-pr-format` through the GitLab API; set a `GITLAB_TOKEN` variable (project access token, `api` scope) to let it fix the title. - `clean-branches`, `update-labels`: through the GitLab API (tomgrv/actions#152), weekly once a pipeline schedule is created, with a `GITLAB_TOKEN` variable. -- `configure-gitlab`: pending, manual job - the branch protection is not configured by this job yet. +- `configure-gitlab`: protects `main` (maintainers only) and `develop`, and requires successful pipelines for merge requests, through the project API. Needs a `GITLAB_TOKEN` with the `api` scope and the Owner role; weekly once a pipeline schedule is created. diff --git a/src/githooks/stubs/_configure-github...gitlab-ci.yml b/src/githooks/stubs/_configure-github...gitlab-ci.yml index 68e9808d..a791796f 100644 --- a/src/githooks/stubs/_configure-github...gitlab-ci.yml +++ b/src/githooks/stubs/_configure-github...gitlab-ci.yml @@ -2,20 +2,47 @@ # Counterpart of .github/workflows/configure-github.yml # -# Pending: the workflow sets GitHub branch protection (`main` restricted to the -# bot, "Tests green" required on `develop`) with a GitHub App token. GitLab -# keeps this in protected branches, set through the project API. No -# tomgrv/actions step is involved. +# GitLab has no GitHub App: protected branches and the merge request setting are +# set through the project API. GITLAB_TOKEN: CI/CD variable, project access token +# with the `api` scope and the Owner role (or a maintainer's personal token). +# Weekly: create the schedule under CI/CD > Schedules (`0 4 * * 1`); it can also +# be started by hand. Without GITLAB_TOKEN the job only prints a notice. configure-gitlab: stage: deploy timeout: 10m resource_group: configure-gitlab image: alpine:latest rules: + - if: $CI_PIPELINE_SOURCE == "schedule" - if: $CI_PIPELINE_SOURCE == "web" when: manual allow_failure: true + before_script: + - apk add --no-cache curl script: - # TODO(gitlab): PATCH/POST $CI_API_V4_URL/projects/$CI_PROJECT_ID/protected_branches - # for `main` (push_access_level: 0) and `develop`, with a project access token. - - echo "Pending - protected branches are not configured by this job yet." + - | + if [ -z "$GITLAB_TOKEN" ]; then + echo "GITLAB_TOKEN is not set, skipping the GitLab configuration." + exit 0 + fi + api() { + method=$1; path=$2; shift 2 + curl -sS -o /dev/null -w '%{http_code}' --request "$method" --header "PRIVATE-TOKEN: $GITLAB_TOKEN" \ + "$CI_API_V4_URL/projects/$CI_PROJECT_ID/$path" "$@" + } + expect() { # expect