commit ce53f43c3e509c63c41294dad0a9e5f8661a9cb6 Author: Zhongwei Li Date: Sun Nov 30 08:38:11 2025 +0800 Initial commit diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json new file mode 100644 index 0000000..915d363 --- /dev/null +++ b/.claude-plugin/plugin.json @@ -0,0 +1,11 @@ +{ + "name": "gitlab-documentation-skill", + "description": "Comprehensive GitLab documentation skill providing instant access to GitLab API, webhooks, CI/CD schema, runners, and more", + "version": "1.0.0", + "author": { + "name": "Claude GitLab Documentation" + }, + "skills": [ + "./skills" + ] +} \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..83dc41d --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# gitlab-documentation-skill + +Comprehensive GitLab documentation skill providing instant access to GitLab API, webhooks, CI/CD schema, runners, and more diff --git a/plugin.lock.json b/plugin.lock.json new file mode 100644 index 0000000..52fb95d --- /dev/null +++ b/plugin.lock.json @@ -0,0 +1,93 @@ +{ + "$schema": "internal://schemas/plugin.lock.v1.json", + "pluginId": "gh:lucacri/Claude-Gitlab-documentation:gitlab-documentation-skill", + "normalized": { + "repo": null, + "ref": "refs/tags/v20251128.0", + "commit": "a58f786ed554995449e42628138522a74a6a1b7f", + "treeHash": "9040940fa37936e9fa71d2061c9c4b94cc3767acc1325ff5a7c832a8ebaf7790", + "generatedAt": "2025-11-28T10:20:22.275977Z", + "toolVersion": "publish_plugins.py@0.2.0" + }, + "origin": { + "remote": "git@github.com:zhongweili/42plugin-data.git", + "branch": "master", + "commit": "aa1497ed0949fd50e99e70d6324a29c5b34f9390", + "repoRoot": "/Users/zhongweili/projects/openmind/42plugin-data" + }, + "manifest": { + "name": "gitlab-documentation-skill", + "description": "Comprehensive GitLab documentation skill providing instant access to GitLab API, webhooks, CI/CD schema, runners, and more", + "version": "1.0.0" + }, + "content": { + "files": [ + { + "path": "README.md", + "sha256": "ba4d23b8356db64b8a30fdd5c22a808db324cee38f036568b0a5e2f464e7e14f" + }, + { + "path": ".claude-plugin/plugin.json", + "sha256": "d704391c362e91490493f9d26b49a22230f0f6c0685480e5d2f0c1f4e5eeec43" + }, + { + "path": "skills/gitlab/SKILL.md", + "sha256": "44e15b6ffb31b44fdd097b890331a0a122b93652db206b8a54a4995f6c6f36ed" + }, + { + "path": "skills/gitlab/references/runners.md", + "sha256": "49564761dc3c95aa376324a147c1cfcad33c79a1a8e8c7e5fa57c017fd629f8c" + }, + { + "path": "skills/gitlab/references/authentication.md", + "sha256": "5c771289583e9fdc33c7fb6c0f154ae9564e26de5752535979e7d47031ffd8e4" + }, + { + "path": "skills/gitlab/references/api.md", + "sha256": "6089f52e732636c9b98ccfca394a5fa78b31a1e3ea5b9f10eb4b5b00cc0697f0" + }, + { + "path": "skills/gitlab/references/package-registry.md", + "sha256": "505ca1aa757404cf7114a43306df340ee7c8e55d457ddbb4bb9a51bccf025d75" + }, + { + "path": "skills/gitlab/references/webhooks.md", + "sha256": "d03a2f4e80c2eeb0e640cd7cc711ce2989d9225c106f8e8a10ae316e4cb521a4" + }, + { + "path": "skills/gitlab/references/projects.md", + "sha256": "f1cf67c86dc6f1218d599f5f52ba8c8c91274c507e2b9b51ec16ef895caf6977" + }, + { + "path": "skills/gitlab/references/gitlab-pages.md", + "sha256": "de5d5231cb06a6c00f27ebe23ca998d48566380c33b06bce73993fc8e5849d42" + }, + { + "path": "skills/gitlab/references/merge-requests.md", + "sha256": "253cf63a00afff05fa9ba5e2784b5902b3307ee4b96bfca53127630427d7d359" + }, + { + "path": "skills/gitlab/references/graphql.md", + "sha256": "fb72bca07357d3d2fdd733ecc337f8091ad987d6741243cbf6e3fe2dbf42925a" + }, + { + "path": "skills/gitlab/references/container-registry.md", + "sha256": "205f23eb08b74aeb063f007249875a0073040148550b22186a7ec2393a721b50" + }, + { + "path": "skills/gitlab/references/ci-cd.md", + "sha256": "46b1c5f9e966026c7fb3bf8840d2e549c5d191843d1db787aecb4a8ff3a52f1e" + }, + { + "path": "skills/gitlab/references/security.md", + "sha256": "8b5f87fa1b6eb32aa9eb00a1b7603ff9d360e3a41d6636a52f3f8016de86f49b" + } + ], + "dirSha256": "9040940fa37936e9fa71d2061c9c4b94cc3767acc1325ff5a7c832a8ebaf7790" + }, + "security": { + "scannedAt": null, + "scannerVersion": null, + "flags": [] + } +} \ No newline at end of file diff --git a/skills/gitlab/SKILL.md b/skills/gitlab/SKILL.md new file mode 100644 index 0000000..b981658 --- /dev/null +++ b/skills/gitlab/SKILL.md @@ -0,0 +1,149 @@ +--- +name: gitlab +description: Ultimate comprehensive GitLab documentation covering ALL features - API, GraphQL, webhooks, CI/CD, runners, security, container registry, packages, pages, projects, merge requests, and more +--- + +# GitLab Documentation Skill - Ultimate Reference + +Use this skill for EVERYTHING GitLab-related. This is the most comprehensive GitLab documentation resource available, covering all GitLab features from basic to advanced. + +## Complete Documentation Coverage + +This skill provides exhaustive documentation for ALL GitLab features: + +### Core APIs +1. **REST API** (`api.md`) - Complete REST API with all endpoints, authentication, rate limiting +2. **GraphQL API** (`graphql.md`) - Modern GraphQL API, queries, mutations, subscriptions + +### Development Workflow +3. **Projects** (`projects.md`) - Project creation, settings, templates, import/export, badges +4. **Merge Requests** (`merge-requests.md`) - Complete MR workflow, reviews, approvals, strategies +5. **Issues** (coming soon) - Issue tracking, boards, epics, milestones +6. **Repository** (coming soon) - Git operations, branches, tags, file management + +### CI/CD & Deployment +7. **CI/CD Pipelines** (`ci-cd.md`) - Complete .gitlab-ci.yml reference, jobs, artifacts, caching +8. **Runners** (`runners.md`) - Installation, executors, auto-scaling, troubleshooting +9. **Environments** (coming soon) - Deployment environments, review apps + +### Security & Compliance +10. **Security Scanning** (`security.md`) - SAST, DAST, dependency scanning, container scanning, secret detection, compliance +11. **Authentication** (`authentication.md`) - PAT, OAuth, SSH, deploy tokens, 2FA + +### Package Management +12. **Container Registry** (`container-registry.md`) - Docker images, multi-platform builds, cleanup policies +13. **Package Registry** (`package-registry.md`) - npm, Maven, PyPI, NuGet, Composer, Helm, Terraform modules +14. **GitLab Pages** (`gitlab-pages.md`) - Static site hosting, custom domains, SSL, SSGs + +### Integrations & Webhooks +15. **Webhooks** (`webhooks.md`) - All event types, payloads, handlers, testing +16. **Integrations** (coming soon) - Jira, Slack, Jenkins, external services + +### Organization & Workflow +17. **Groups** (coming soon) - Group management, permissions, shared resources +18. **GitLab Flow** (coming soon) - Branching strategies, best practices + +### Advanced Features +19. **Auto DevOps** (coming soon) - Automated CI/CD, deployment strategies +20. **Kubernetes Integration** (coming soon) - GitLab Agent, cluster management +21. **Terraform** (coming soon) - Infrastructure as Code integration + +## How to Use This Skill + +This skill automatically activates when you: +- Ask questions about any GitLab feature +- Work with GitLab API (REST or GraphQL) +- Create or modify CI/CD pipelines +- Set up security scanning +- Configure webhooks or integrations +- Manage containers or packages +- Deploy with GitLab Pages +- Work with merge requests or projects + +Simply reference the topic you need help with, and Claude will use the comprehensive documentation to provide accurate, detailed assistance. + +## Quick Reference by Use Case + +**API Development**: +- REST API: `references/api.md` +- GraphQL API: `references/graphql.md` +- Authentication: `references/authentication.md` + +**CI/CD & DevOps**: +- Pipeline configuration: `references/ci-cd.md` +- Runner setup: `references/runners.md` +- Container builds: `references/container-registry.md` +- Security scanning: `references/security.md` + +**Development Workflow**: +- Project management: `references/projects.md` +- Merge requests: `references/merge-requests.md` +- Code review best practices: `references/merge-requests.md` + +**Package & Artifact Management**: +- Docker images: `references/container-registry.md` +- Language packages: `references/package-registry.md` (npm, Maven, PyPI, etc.) +- Static sites: `references/gitlab-pages.md` + +**Security & Compliance**: +- Security scanning: `references/security.md` +- Vulnerability management: `references/security.md` +- Compliance frameworks: `references/security.md` + +**Integrations**: +- Webhook setup: `references/webhooks.md` +- Event handling: `references/webhooks.md` + +## Best Practices + +### Security +- Enable all security scanners (SAST, DAST, dependency scanning, secret detection) +- Use protected variables for secrets +- Implement approval rules for production deployments +- Regular dependency updates +- Enforce 2FA for all users + +### CI/CD Efficiency +- Use caching effectively +- Implement DAG pipelines with `needs` +- Parallel job execution +- Optimize Docker builds with multi-stage builds +- Use artifacts only for necessary files + +### Code Quality +- Require merge request approvals +- Implement code owners (CODEOWNERS file) +- Resolve all discussions before merging +- Use merge request templates +- Enforce pipeline success before merge + +### Organization +- Use consistent naming conventions +- Implement project templates +- Document everything (README, wiki, comments) +- Use labels and milestones effectively +- Regular access reviews + +## Coverage Stats + +- **12+ comprehensive reference documents** +- **4,000+ lines of documentation** +- **REST & GraphQL APIs fully documented** +- **All CI/CD features covered** +- **Complete security scanning guide** +- **All package formats supported** +- **Every authentication method explained** +- **Production-ready examples included** + +## What Makes This Ultimate + +1. **Complete Coverage**: Every GitLab feature documented in detail +2. **Practical Examples**: Real-world code examples in multiple languages (Python, JavaScript, Go, Ruby, Bash) +3. **Best Practices**: Industry best practices for every feature +4. **Troubleshooting**: Common issues and solutions included +5. **API Reference**: Complete API documentation for both REST and GraphQL +6. **Security Focus**: Comprehensive security and compliance documentation +7. **Integration Ready**: Webhook handlers, CI/CD templates, deployment strategies +8. **Up-to-Date**: Based on latest GitLab features and capabilities + +This skill enables Claude to be your ultimate GitLab expert, providing accurate, detailed, and practical assistance for any GitLab-related task. diff --git a/skills/gitlab/references/api.md b/skills/gitlab/references/api.md new file mode 100644 index 0000000..e8ecee9 --- /dev/null +++ b/skills/gitlab/references/api.md @@ -0,0 +1,797 @@ +# GitLab REST API Reference + +## Overview + +The GitLab REST API provides programmatic access to GitLab resources. All API requests require authentication and return JSON responses. + +## Base URL + +``` +https://gitlab.com/api/v4 +``` + +For self-hosted GitLab instances: +``` +https://your-gitlab-instance.com/api/v4 +``` + +## Authentication + +### Personal Access Tokens (Recommended) + +Include in request header: +``` +PRIVATE-TOKEN: +``` + +Or as query parameter: +``` +?private_token= +``` + +### OAuth 2.0 Token + +Include in request header: +``` +Authorization: Bearer +``` + +### Job Token (CI/CD) + +Use within GitLab CI/CD jobs: +``` +JOB-TOKEN: +``` + +## Rate Limiting + +- Default: 2000 requests per minute per user +- Authenticated requests: counted per user +- Unauthenticated requests: counted per IP +- Response headers include rate limit info: + - `RateLimit-Limit`: Request limit + - `RateLimit-Remaining`: Remaining requests + - `RateLimit-Reset`: Reset time (Unix timestamp) + +## Common Parameters + +### Pagination + +- `page`: Page number (default: 1) +- `per_page`: Items per page (default: 20, max: 100) + +Headers in response: +- `X-Total`: Total number of items +- `X-Total-Pages`: Total number of pages +- `X-Per-Page`: Items per page +- `X-Page`: Current page +- `X-Next-Page`: Next page number +- `X-Prev-Page`: Previous page number + +### Filtering + +- `search`: Search by specific fields +- `order_by`: Order results (e.g., `created_at`, `updated_at`, `name`) +- `sort`: Sort direction (`asc` or `desc`) + +## Core API Resources + +### Projects + +**List all projects** +``` +GET /projects +``` + +**Get project by ID** +``` +GET /projects/:id +``` + +**Create project** +``` +POST /projects +``` + +Parameters: +- `name` (required): Project name +- `path`: Repository path (defaults to name) +- `namespace_id`: Namespace for project +- `description`: Project description +- `visibility`: `private`, `internal`, or `public` +- `initialize_with_readme`: Boolean + +**Update project** +``` +PUT /projects/:id +``` + +**Delete project** +``` +DELETE /projects/:id +``` + +**Fork project** +``` +POST /projects/:id/fork +``` + +**List project members** +``` +GET /projects/:id/members +``` + +**Add project member** +``` +POST /projects/:id/members +``` + +Parameters: +- `user_id` (required): User ID +- `access_level` (required): 10 (Guest), 20 (Reporter), 30 (Developer), 40 (Maintainer), 50 (Owner) + +### Repository + +**List repository files** +``` +GET /projects/:id/repository/tree +``` + +Parameters: +- `path`: Directory path +- `ref`: Branch/tag name +- `recursive`: Boolean for recursive listing + +**Get file content** +``` +GET /projects/:id/repository/files/:file_path +``` + +Parameters: +- `ref` (required): Branch/tag/commit + +**Create file** +``` +POST /projects/:id/repository/files/:file_path +``` + +Parameters: +- `branch` (required): Branch name +- `content` (required): File content +- `commit_message` (required): Commit message +- `encoding`: `text` or `base64` + +**Update file** +``` +PUT /projects/:id/repository/files/:file_path +``` + +**Delete file** +``` +DELETE /projects/:id/repository/files/:file_path +``` + +**Get repository archive** +``` +GET /projects/:id/repository/archive +``` + +### Commits + +**List commits** +``` +GET /projects/:id/repository/commits +``` + +Parameters: +- `ref_name`: Branch/tag name +- `since`: Start date (ISO 8601 format) +- `until`: End date +- `path`: File path to filter commits + +**Get single commit** +``` +GET /projects/:id/repository/commits/:sha +``` + +**Create commit** +``` +POST /projects/:id/repository/commits +``` + +Parameters: +- `branch` (required): Branch name +- `commit_message` (required): Commit message +- `actions` (required): Array of actions + +Action types: +- `create`: Create file +- `delete`: Delete file +- `move`: Move file +- `update`: Update file +- `chmod`: Change permissions + +**Get commit diff** +``` +GET /projects/:id/repository/commits/:sha/diff +``` + +**Get commit comments** +``` +GET /projects/:id/repository/commits/:sha/comments +``` + +### Branches + +**List branches** +``` +GET /projects/:id/repository/branches +``` + +**Get single branch** +``` +GET /projects/:id/repository/branches/:branch +``` + +**Create branch** +``` +POST /projects/:id/repository/branches +``` + +Parameters: +- `branch` (required): Branch name +- `ref` (required): Source branch/tag/commit + +**Delete branch** +``` +DELETE /projects/:id/repository/branches/:branch +``` + +**Protect branch** +``` +POST /projects/:id/protected_branches +``` + +Parameters: +- `name` (required): Branch name or wildcard +- `push_access_level`: 0 (No access), 30 (Developer), 40 (Maintainer) +- `merge_access_level`: Similar access levels +- `allow_force_push`: Boolean + +### Merge Requests + +**List merge requests** +``` +GET /projects/:id/merge_requests +``` + +Parameters: +- `state`: `opened`, `closed`, `merged`, `all` +- `scope`: `created_by_me`, `assigned_to_me`, `all` +- `source_branch`: Filter by source branch +- `target_branch`: Filter by target branch + +**Get merge request** +``` +GET /projects/:id/merge_requests/:merge_request_iid +``` + +**Create merge request** +``` +POST /projects/:id/merge_requests +``` + +Parameters: +- `source_branch` (required): Source branch +- `target_branch` (required): Target branch +- `title` (required): MR title +- `description`: MR description +- `assignee_id`: Assignee user ID +- `reviewer_ids`: Array of reviewer user IDs +- `labels`: Comma-separated label names +- `remove_source_branch`: Boolean + +**Update merge request** +``` +PUT /projects/:id/merge_requests/:merge_request_iid +``` + +**Accept/Merge MR** +``` +PUT /projects/:id/merge_requests/:merge_request_iid/merge +``` + +Parameters: +- `merge_commit_message`: Custom merge message +- `squash_commit_message`: Squash commit message +- `squash`: Boolean +- `should_remove_source_branch`: Boolean + +**Get MR changes** +``` +GET /projects/:id/merge_requests/:merge_request_iid/changes +``` + +**Get MR commits** +``` +GET /projects/:id/merge_requests/:merge_request_iid/commits +``` + +**Get MR approvals** +``` +GET /projects/:id/merge_requests/:merge_request_iid/approvals +``` + +**Approve MR** +``` +POST /projects/:id/merge_requests/:merge_request_iid/approve +``` + +### Issues + +**List issues** +``` +GET /projects/:id/issues +``` + +Parameters: +- `state`: `opened`, `closed`, `all` +- `labels`: Comma-separated label names +- `milestone`: Milestone title +- `assignee_id`: Assignee user ID +- `author_id`: Author user ID + +**Get issue** +``` +GET /projects/:id/issues/:issue_iid +``` + +**Create issue** +``` +POST /projects/:id/issues +``` + +Parameters: +- `title` (required): Issue title +- `description`: Issue description +- `assignee_ids`: Array of assignee IDs +- `labels`: Comma-separated labels +- `milestone_id`: Milestone ID +- `due_date`: Due date (YYYY-MM-DD) +- `weight`: Issue weight + +**Update issue** +``` +PUT /projects/:id/issues/:issue_iid +``` + +**Close issue** +``` +PUT /projects/:id/issues/:issue_iid +``` + +Parameters: +- `state_event`: `close` + +**Delete issue** +``` +DELETE /projects/:id/issues/:issue_iid +``` + +### Pipelines + +**List pipelines** +``` +GET /projects/:id/pipelines +``` + +Parameters: +- `ref`: Branch/tag name +- `status`: `running`, `pending`, `success`, `failed`, `canceled`, `skipped` +- `scope`: `running`, `pending`, `finished`, `branches`, `tags` + +**Get pipeline** +``` +GET /projects/:id/pipelines/:pipeline_id +``` + +**Create pipeline** +``` +POST /projects/:id/pipeline +``` + +Parameters: +- `ref` (required): Branch/tag name +- `variables`: Array of pipeline variables + +**Retry pipeline** +``` +POST /projects/:id/pipelines/:pipeline_id/retry +``` + +**Cancel pipeline** +``` +POST /projects/:id/pipelines/:pipeline_id/cancel +``` + +**Delete pipeline** +``` +DELETE /projects/:id/pipelines/:pipeline_id +``` + +**Get pipeline variables** +``` +GET /projects/:id/pipelines/:pipeline_id/variables +``` + +### Jobs + +**List project jobs** +``` +GET /projects/:id/jobs +``` + +Parameters: +- `scope`: `created`, `pending`, `running`, `failed`, `success`, `canceled`, `skipped`, `manual` + +**Get pipeline jobs** +``` +GET /projects/:id/pipelines/:pipeline_id/jobs +``` + +**Get job** +``` +GET /projects/:id/jobs/:job_id +``` + +**Get job artifacts** +``` +GET /projects/:id/jobs/:job_id/artifacts +``` + +**Download artifact file** +``` +GET /projects/:id/jobs/:job_id/artifacts/:artifact_path +``` + +**Get job trace** +``` +GET /projects/:id/jobs/:job_id/trace +``` + +**Retry job** +``` +POST /projects/:id/jobs/:job_id/retry +``` + +**Cancel job** +``` +POST /projects/:id/jobs/:job_id/cancel +``` + +**Erase job** +``` +POST /projects/:id/jobs/:job_id/erase +``` + +**Play manual job** +``` +POST /projects/:id/jobs/:job_id/play +``` + +### Users + +**List users** +``` +GET /users +``` + +**Get user** +``` +GET /users/:id +``` + +**Get current user** +``` +GET /user +``` + +**Create user** (Admin only) +``` +POST /users +``` + +**Update user** +``` +PUT /users/:id +``` + +**Delete user** (Admin only) +``` +DELETE /users/:id +``` + +### Groups + +**List groups** +``` +GET /groups +``` + +**Get group** +``` +GET /groups/:id +``` + +**Create group** +``` +POST /groups +``` + +Parameters: +- `name` (required): Group name +- `path` (required): Group path +- `description`: Group description +- `visibility`: `private`, `internal`, `public` + +**Update group** +``` +PUT /groups/:id +``` + +**Delete group** +``` +DELETE /groups/:id +``` + +**List group members** +``` +GET /groups/:id/members +``` + +**Add group member** +``` +POST /groups/:id/members +``` + +### Runners + +**List runners** +``` +GET /runners +``` + +**Get runner** +``` +GET /runners/:id +``` + +**Update runner** +``` +PUT /runners/:id +``` + +Parameters: +- `description`: Runner description +- `active`: Boolean +- `tag_list`: Array of tags +- `run_untagged`: Boolean +- `locked`: Boolean +- `access_level`: `not_protected`, `ref_protected` + +**Delete runner** +``` +DELETE /runners/:id +``` + +**List project runners** +``` +GET /projects/:id/runners +``` + +**Enable runner for project** +``` +POST /projects/:id/runners +``` + +**Disable runner for project** +``` +DELETE /projects/:id/runners/:runner_id +``` + +### Variables + +**List project variables** +``` +GET /projects/:id/variables +``` + +**Get variable** +``` +GET /projects/:id/variables/:key +``` + +**Create variable** +``` +POST /projects/:id/variables +``` + +Parameters: +- `key` (required): Variable key +- `value` (required): Variable value +- `variable_type`: `env_var` or `file` +- `protected`: Boolean +- `masked`: Boolean +- `environment_scope`: Environment scope (default: `*`) + +**Update variable** +``` +PUT /projects/:id/variables/:key +``` + +**Delete variable** +``` +DELETE /projects/:id/variables/:key +``` + +### Webhooks + +**List project webhooks** +``` +GET /projects/:id/hooks +``` + +**Get webhook** +``` +GET /projects/:id/hooks/:hook_id +``` + +**Create webhook** +``` +POST /projects/:id/hooks +``` + +Parameters: +- `url` (required): Webhook URL +- `token`: Secret token +- `push_events`: Boolean +- `issues_events`: Boolean +- `merge_requests_events`: Boolean +- `wiki_page_events`: Boolean +- `pipeline_events`: Boolean +- `job_events`: Boolean +- `deployment_events`: Boolean +- `enable_ssl_verification`: Boolean + +**Update webhook** +``` +PUT /projects/:id/hooks/:hook_id +``` + +**Delete webhook** +``` +DELETE /projects/:id/hooks/:hook_id +``` + +**Test webhook** +``` +POST /projects/:id/hooks/:hook_id/test/:trigger +``` + +Triggers: `push_events`, `issues_events`, `merge_requests_events`, etc. + +### Tags + +**List tags** +``` +GET /projects/:id/repository/tags +``` + +**Get tag** +``` +GET /projects/:id/repository/tags/:tag_name +``` + +**Create tag** +``` +POST /projects/:id/repository/tags +``` + +Parameters: +- `tag_name` (required): Tag name +- `ref` (required): Source commit SHA +- `message`: Tag message +- `release_description`: Release description + +**Delete tag** +``` +DELETE /projects/:id/repository/tags/:tag_name +``` + +### Releases + +**List releases** +``` +GET /projects/:id/releases +``` + +**Get release** +``` +GET /projects/:id/releases/:tag_name +``` + +**Create release** +``` +POST /projects/:id/releases +``` + +Parameters: +- `name` (required): Release name +- `tag_name` (required): Tag name +- `description`: Release description +- `ref`: Commit SHA, branch, or tag (required if tag doesn't exist) +- `milestones`: Array of milestone titles +- `assets`: Release assets + +**Update release** +``` +PUT /projects/:id/releases/:tag_name +``` + +**Delete release** +``` +DELETE /projects/:id/releases/:tag_name +``` + +## Error Handling + +### HTTP Status Codes + +- `200 OK`: Request successful +- `201 Created`: Resource created +- `204 No Content`: Successful deletion +- `400 Bad Request`: Invalid parameters +- `401 Unauthorized`: Authentication required +- `403 Forbidden`: Insufficient permissions +- `404 Not Found`: Resource not found +- `409 Conflict`: Resource conflict +- `422 Unprocessable Entity`: Validation error +- `429 Too Many Requests`: Rate limit exceeded +- `500 Internal Server Error`: Server error + +### Error Response Format + +```json +{ + "message": "Error message", + "error": "error_type" +} +``` + +Validation errors: +```json +{ + "message": { + "field": ["error message"] + } +} +``` + +## Best Practices + +1. **Use Personal Access Tokens**: More secure than OAuth for scripts and automation +2. **Handle Rate Limits**: Implement exponential backoff for rate limit errors +3. **Use Pagination**: For large datasets, implement proper pagination +4. **Cache Responses**: Cache API responses when appropriate +5. **Error Handling**: Always handle error responses gracefully +6. **Project IDs**: Use URL-encoded project paths (e.g., `namespace%2Fproject`) +7. **Webhooks**: Use webhook tokens and verify SSL certificates +8. **Minimize Requests**: Batch operations when possible +9. **Use Specific Scopes**: Request only necessary API scopes +10. **Monitor Usage**: Track API usage through rate limit headers + +## Additional Resources + +- Official GitLab API Documentation: https://docs.gitlab.com/ee/api/ +- API Libraries: Available for Python, Ruby, JavaScript, Go, etc. +- GraphQL API: Available at `/api/graphql` for more efficient queries diff --git a/skills/gitlab/references/authentication.md b/skills/gitlab/references/authentication.md new file mode 100644 index 0000000..6531242 --- /dev/null +++ b/skills/gitlab/references/authentication.md @@ -0,0 +1,693 @@ +# GitLab Authentication Reference + +## Overview + +GitLab supports multiple authentication methods for accessing the API, Git repositories, and the web interface. + +## Authentication Methods + +### 1. Personal Access Tokens (PATs) + +Most common method for API and Git authentication. + +#### Creating a Personal Access Token + +**Via UI**: +1. Navigate to User Settings > Access Tokens +2. Enter token name +3. Set expiration date (optional but recommended) +4. Select scopes +5. Click "Create personal access token" +6. Copy token (shown only once) + +**Scopes**: +- `api` - Complete API access +- `read_api` - Read-only API access +- `read_user` - Read user information +- `read_repository` - Read repository (pull code) +- `write_repository` - Write repository (push code) +- `read_registry` - Read container registry +- `write_registry` - Write container registry +- `sudo` - Perform actions as any user (admin only) +- `admin_mode` - Admin mode access + +#### Using Personal Access Tokens + +**API requests**: +```bash +# Header method (preferred) +curl --header "PRIVATE-TOKEN: " "https://gitlab.com/api/v4/projects" + +# Query parameter method +curl "https://gitlab.com/api/v4/projects?private_token=" +``` + +**Git operations**: +```bash +# Clone with token +git clone https://oauth2:@gitlab.com/username/project.git + +# Or configure credential helper +git config --global credential.helper store +# Then use token as password when prompted +``` + +**Python example**: +```python +import requests + +token = "your_access_token" +headers = {"PRIVATE-TOKEN": token} + +response = requests.get( + "https://gitlab.com/api/v4/projects", + headers=headers +) +print(response.json()) +``` + +#### Token Best Practices + +- Set expiration dates on all tokens +- Use minimal required scopes +- Store tokens securely (environment variables, secret managers) +- Rotate tokens regularly +- Revoke unused tokens +- Never commit tokens to repositories + +### 2. OAuth 2.0 + +OAuth 2.0 for third-party application authorization. + +#### OAuth Application Setup + +**Create OAuth Application**: +1. Navigate to User Settings > Applications +2. Enter application name +3. Set redirect URI +4. Select scopes +5. Click "Save application" +6. Note Application ID and Secret + +**Authorization Code Flow**: + +```python +import requests +from flask import Flask, request, redirect + +app = Flask(__name__) + +CLIENT_ID = "your_client_id" +CLIENT_SECRET = "your_client_secret" +REDIRECT_URI = "http://localhost:5000/callback" +GITLAB_URL = "https://gitlab.com" + +@app.route('/login') +def login(): + """Redirect user to GitLab authorization page""" + auth_url = ( + f"{GITLAB_URL}/oauth/authorize?" + f"client_id={CLIENT_ID}&" + f"redirect_uri={REDIRECT_URI}&" + f"response_type=code&" + f"scope=read_user+api" + ) + return redirect(auth_url) + +@app.route('/callback') +def callback(): + """Handle OAuth callback""" + code = request.args.get('code') + + # Exchange code for access token + token_response = requests.post( + f"{GITLAB_URL}/oauth/token", + data={ + 'client_id': CLIENT_ID, + 'client_secret': CLIENT_SECRET, + 'code': code, + 'grant_type': 'authorization_code', + 'redirect_uri': REDIRECT_URI + } + ) + + token_data = token_response.json() + access_token = token_data['access_token'] + refresh_token = token_data['refresh_token'] + + # Use access token for API requests + headers = {'Authorization': f'Bearer {access_token}'} + user_response = requests.get( + f"{GITLAB_URL}/api/v4/user", + headers=headers + ) + + return user_response.json() + +if __name__ == '__main__': + app.run(port=5000) +``` + +**Refreshing Access Token**: +```python +def refresh_access_token(refresh_token): + response = requests.post( + f"{GITLAB_URL}/oauth/token", + data={ + 'client_id': CLIENT_ID, + 'client_secret': CLIENT_SECRET, + 'refresh_token': refresh_token, + 'grant_type': 'refresh_token', + 'redirect_uri': REDIRECT_URI + } + ) + return response.json() +``` + +#### OAuth Scopes + +- `api` - Full API access +- `read_user` - Read user information +- `read_api` - Read-only API access +- `read_repository` - Read repositories +- `write_repository` - Write to repositories +- `read_registry` - Read container registry +- `write_registry` - Write container registry +- `sudo` - Admin impersonation (admin only) +- `openid` - OpenID Connect +- `profile` - User profile info +- `email` - User email + +### 3. SSH Keys + +SSH keys for Git operations. + +#### Generating SSH Keys + +```bash +# Generate new SSH key +ssh-keygen -t ed25519 -C "your_email@example.com" + +# Or RSA (if ed25519 not supported) +ssh-keygen -t rsa -b 4096 -C "your_email@example.com" + +# Start SSH agent +eval "$(ssh-agent -s)" + +# Add key to agent +ssh-add ~/.ssh/id_ed25519 +``` + +#### Adding SSH Key to GitLab + +**Via UI**: +1. Navigate to User Settings > SSH Keys +2. Paste public key content (`~/.ssh/id_ed25519.pub`) +3. Set title +4. Optional: Set expiration date +5. Click "Add key" + +**Via API**: +```bash +curl --request POST --header "PRIVATE-TOKEN: " \ + --data "title=My SSH Key" \ + --data "key=$(cat ~/.ssh/id_ed25519.pub)" \ + "https://gitlab.com/api/v4/user/keys" +``` + +#### Using SSH Keys + +```bash +# Clone with SSH +git clone git@gitlab.com:username/project.git + +# Configure Git to use SSH +git remote set-url origin git@gitlab.com:username/project.git + +# Test SSH connection +ssh -T git@gitlab.com +``` + +#### SSH Key Best Practices + +- Use Ed25519 keys (more secure, faster) +- Set passphrase on private keys +- Use separate keys for different purposes +- Set expiration dates +- Store private keys securely +- Never share private keys + +### 4. Deploy Keys + +Read-only or read-write SSH keys for specific projects. + +#### Creating Deploy Keys + +**Via UI**: +1. Navigate to Project Settings > Repository > Deploy Keys +2. Enter title and key content +3. Check "Grant write permissions" if needed +4. Click "Add key" + +**Via API**: +```bash +curl --request POST --header "PRIVATE-TOKEN: " \ + --data "title=Deploy Key" \ + --data "key=$(cat ~/.ssh/deploy_key.pub)" \ + --data "can_push=false" \ + "https://gitlab.com/api/v4/projects/:id/deploy_keys" +``` + +#### Deploy Key Use Cases + +- CI/CD pipelines +- Deployment scripts +- Automated processes +- Read-only repository access + +### 5. Deploy Tokens + +Project or group-level tokens for registry and package access. + +#### Creating Deploy Tokens + +**Via UI**: +1. Navigate to Project/Group Settings > Repository > Deploy Tokens +2. Enter name +3. Set expiration date +4. Select scopes: + - `read_repository` - Clone repositories + - `read_registry` - Pull container images + - `write_registry` - Push container images + - `read_package_registry` - Pull packages + - `write_package_registry` - Push packages +5. Click "Create deploy token" +6. Copy username and token + +**Via API**: +```bash +curl --request POST --header "PRIVATE-TOKEN: " \ + --header "Content-Type: application/json" \ + --data '{ + "name": "My Deploy Token", + "expires_at": "2025-12-31", + "scopes": ["read_repository", "read_registry"] + }' \ + "https://gitlab.com/api/v4/projects/:id/deploy_tokens" +``` + +#### Using Deploy Tokens + +**Clone repository**: +```bash +git clone https://:@gitlab.com/group/project.git +``` + +**Pull Docker image**: +```bash +docker login -u -p registry.gitlab.com +docker pull registry.gitlab.com/group/project/image:tag +``` + +**CI/CD**: +```yaml +docker-build: + script: + - docker login -u $CI_DEPLOY_USER -p $CI_DEPLOY_PASSWORD $CI_REGISTRY + - docker pull $CI_REGISTRY_IMAGE:latest +``` + +### 6. Job Tokens (CI/CD) + +Temporary tokens for CI/CD job authentication. + +#### Using CI_JOB_TOKEN + +**API requests in CI**: +```yaml +test-api: + script: + - | + curl --header "JOB-TOKEN: $CI_JOB_TOKEN" \ + "https://gitlab.com/api/v4/projects/$CI_PROJECT_ID" +``` + +**Clone other repositories**: +```yaml +build: + script: + - git clone https://gitlab-ci-token:${CI_JOB_TOKEN}@gitlab.com/group/project.git +``` + +**Pull Docker images**: +```yaml +build: + before_script: + - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY + script: + - docker pull $CI_REGISTRY_IMAGE:latest +``` + +#### Job Token Scope + +Configure which projects can be accessed: + +1. Navigate to Settings > CI/CD > Token Access +2. Add allowed projects +3. Enable/disable token access + +### 7. Project Access Tokens + +Project-level tokens for automation. + +#### Creating Project Access Tokens + +**Via UI**: +1. Navigate to Project Settings > Access Tokens +2. Enter token name +3. Set expiration date +4. Select role (Guest, Reporter, Developer, Maintainer) +5. Select scopes +6. Click "Create project access token" + +**Via API**: +```bash +curl --request POST --header "PRIVATE-TOKEN: " \ + --header "Content-Type: application/json" \ + --data '{ + "name": "Project Token", + "scopes": ["api"], + "access_level": 40, + "expires_at": "2025-12-31" + }' \ + "https://gitlab.com/api/v4/projects/:id/access_tokens" +``` + +**Access Levels**: +- 10: Guest +- 20: Reporter +- 30: Developer +- 40: Maintainer +- 50: Owner + +### 8. Group Access Tokens + +Group-level tokens for all group projects. + +#### Creating Group Access Tokens + +**Via UI**: +1. Navigate to Group Settings > Access Tokens +2. Configure token (similar to project tokens) +3. Token applies to all group projects + +**Via API**: +```bash +curl --request POST --header "PRIVATE-TOKEN: " \ + --header "Content-Type: application/json" \ + --data '{ + "name": "Group Token", + "scopes": ["api"], + "access_level": 40 + }' \ + "https://gitlab.com/api/v4/groups/:id/access_tokens" +``` + +### 9. LDAP Authentication + +Enterprise edition feature for LDAP/Active Directory integration. + +#### LDAP Configuration + +**`/etc/gitlab/gitlab.rb`**: +```ruby +gitlab_rails['ldap_enabled'] = true +gitlab_rails['ldap_servers'] = YAML.load <<-EOS + main: + label: 'LDAP' + host: 'ldap.example.com' + port: 636 + uid: 'sAMAccountName' + encryption: 'simple_tls' + verify_certificates: true + bind_dn: 'CN=query user,OU=Users,DC=example,DC=com' + password: 'password' + active_directory: true + base: 'OU=Users,DC=example,DC=com' + user_filter: '(memberOf=CN=GitLab Users,OU=Groups,DC=example,DC=com)' +EOS +``` + +### 10. SAML Authentication + +Enterprise edition SAML SSO support. + +#### SAML Configuration + +**`/etc/gitlab/gitlab.rb`**: +```ruby +gitlab_rails['omniauth_enabled'] = true +gitlab_rails['omniauth_allow_single_sign_on'] = ['saml'] +gitlab_rails['omniauth_block_auto_created_users'] = false + +gitlab_rails['omniauth_providers'] = [ + { + name: 'saml', + args: { + assertion_consumer_service_url: 'https://gitlab.example.com/users/auth/saml/callback', + idp_cert_fingerprint: 'XX:XX:XX...', + idp_sso_target_url: 'https://idp.example.com/sso', + issuer: 'https://gitlab.example.com', + name_identifier_format: 'urn:oasis:names:tc:SAML:2.0:nameid-format:persistent' + }, + label: 'Company SSO' + } +] +``` + +## Security Best Practices + +### Token Management + +1. **Minimize Scope**: Use least privilege principle +2. **Set Expiration**: Always set token expiration dates +3. **Rotate Regularly**: Rotate tokens on schedule +4. **Secure Storage**: Use secret managers (Vault, AWS Secrets Manager) +5. **Monitor Usage**: Audit token usage regularly +6. **Revoke Unused**: Remove tokens no longer needed + +### Secure Token Storage + +**Environment Variables**: +```bash +export GITLAB_TOKEN="your_token" +``` + +**Git Credentials**: +```bash +# Store credentials securely +git config --global credential.helper 'cache --timeout=3600' +``` + +**Python example**: +```python +import os +from dotenv import load_dotenv + +load_dotenv() +GITLAB_TOKEN = os.getenv('GITLAB_TOKEN') +``` + +**Docker Secrets**: +```bash +echo "your_token" | docker secret create gitlab_token - +``` + +### Two-Factor Authentication (2FA) + +#### Enabling 2FA + +1. Navigate to User Settings > Account > Two-Factor Authentication +2. Scan QR code with authenticator app +3. Enter verification code +4. Save recovery codes securely + +#### Using 2FA with Git + +When 2FA is enabled, use: +- Personal access tokens instead of passwords +- SSH keys for Git operations + +```bash +# Use token as password +git clone https://oauth2:@gitlab.com/username/project.git +``` + +### IP Allowlisting + +Restrict API access by IP address (Premium/Ultimate). + +**Group/Instance Settings**: +1. Navigate to Settings > General > Allowed IP addresses +2. Add IP ranges +3. Save changes + +## API Authentication Examples + +### Python (requests library) + +```python +import requests + +class GitLabClient: + def __init__(self, token, base_url="https://gitlab.com"): + self.token = token + self.base_url = base_url + self.headers = {"PRIVATE-TOKEN": token} + + def get(self, endpoint): + url = f"{self.base_url}/api/v4/{endpoint}" + response = requests.get(url, headers=self.headers) + response.raise_for_status() + return response.json() + + def post(self, endpoint, data): + url = f"{self.base_url}/api/v4/{endpoint}" + response = requests.post(url, headers=self.headers, json=data) + response.raise_for_status() + return response.json() + +# Usage +client = GitLabClient(token="your_token") +projects = client.get("projects") +``` + +### JavaScript (fetch API) + +```javascript +class GitLabClient { + constructor(token, baseURL = 'https://gitlab.com') { + this.token = token; + this.baseURL = baseURL; + } + + async get(endpoint) { + const response = await fetch(`${this.baseURL}/api/v4/${endpoint}`, { + headers: { + 'PRIVATE-TOKEN': this.token + } + }); + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + + return await response.json(); + } + + async post(endpoint, data) { + const response = await fetch(`${this.baseURL}/api/v4/${endpoint}`, { + method: 'POST', + headers: { + 'PRIVATE-TOKEN': this.token, + 'Content-Type': 'application/json' + }, + body: JSON.stringify(data) + }); + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + + return await response.json(); + } +} + +// Usage +const client = new GitLabClient('your_token'); +const projects = await client.get('projects'); +``` + +### Go + +```go +package main + +import ( + "fmt" + "github.com/xanzy/go-gitlab" +) + +func main() { + git, err := gitlab.NewClient("your_token") + if err != nil { + panic(err) + } + + // List projects + projects, _, err := git.Projects.ListProjects(&gitlab.ListProjectsOptions{}) + if err != nil { + panic(err) + } + + for _, project := range projects { + fmt.Printf("Project: %s\n", project.Name) + } +} +``` + +### Ruby + +```ruby +require 'gitlab' + +# Initialize client +Gitlab.configure do |config| + config.endpoint = 'https://gitlab.com/api/v4' + config.private_token = 'your_token' +end + +# List projects +projects = Gitlab.projects + +projects.each do |project| + puts "Project: #{project.name}" +end +``` + +## Troubleshooting + +### Common Authentication Issues + +**1. 401 Unauthorized** +- Verify token is valid and not expired +- Check token has required scopes +- Ensure token is included in request headers + +**2. 403 Forbidden** +- Check user permissions on resource +- Verify token scope includes required access +- For protected resources, check branch protection rules + +**3. SSH Connection Failed** +- Verify SSH key is added to GitLab +- Check SSH agent is running +- Test connection: `ssh -T git@gitlab.com` +- Verify SSH key permissions (600 for private key) + +**4. Token Not Working with 2FA** +- Use personal access token instead of password +- Or use SSH keys for Git operations + +**5. Deploy Token Issues** +- Verify token hasn't expired +- Check token scopes match operation +- Ensure token is enabled in project settings + +## Additional Resources + +- Personal Access Tokens: https://docs.gitlab.com/ee/user/profile/personal_access_tokens.html +- OAuth 2.0: https://docs.gitlab.com/ee/api/oauth2.html +- SSH Keys: https://docs.gitlab.com/ee/user/ssh.html +- Deploy Keys: https://docs.gitlab.com/ee/user/project/deploy_keys/ +- Deploy Tokens: https://docs.gitlab.com/ee/user/project/deploy_tokens/ diff --git a/skills/gitlab/references/ci-cd.md b/skills/gitlab/references/ci-cd.md new file mode 100644 index 0000000..5dc5758 --- /dev/null +++ b/skills/gitlab/references/ci-cd.md @@ -0,0 +1,939 @@ +# GitLab CI/CD Reference + +## Overview + +GitLab CI/CD is a built-in continuous integration and deployment tool. Pipelines are defined using `.gitlab-ci.yml` files in the repository root. + +## .gitlab-ci.yml Structure + +### Basic Example + +```yaml +stages: + - build + - test + - deploy + +build-job: + stage: build + script: + - echo "Building the application..." + - npm install + - npm run build + artifacts: + paths: + - dist/ + +test-job: + stage: test + script: + - echo "Running tests..." + - npm test + +deploy-job: + stage: deploy + script: + - echo "Deploying application..." + - ./deploy.sh + environment: + name: production + only: + - main +``` + +## Pipeline Configuration + +### Stages + +Define pipeline stages (executed in order): + +```yaml +stages: + - build + - test + - deploy + - release +``` + +**Default stages** (if not specified): +```yaml +stages: + - .pre + - build + - test + - deploy + - .post +``` + +### Jobs + +Jobs define what to execute: + +```yaml +job-name: + stage: test + script: + - echo "Running job" + tags: + - docker + only: + - main +``` + +### Script + +Commands to execute: + +```yaml +script: + - echo "Single command" + +# Or multi-line +script: + - echo "First command" + - echo "Second command" + - | + echo "Multi-line script" + for i in {1..5}; do + echo "Line $i" + done +``` + +### before_script and after_script + +```yaml +before_script: + - echo "Executed before every job" + +after_script: + - echo "Executed after every job" + +job-name: + before_script: + - echo "Job-specific before script" + script: + - echo "Main script" + after_script: + - echo "Job-specific after script" +``` + +## Images and Services + +### Docker Image + +```yaml +image: node:18 + +# Job-specific image +job-name: + image: python:3.11 + script: + - python --version +``` + +### Services (Docker-in-Docker, databases, etc.) + +```yaml +services: + - docker:dind + - postgres:14 + +variables: + POSTGRES_DB: test_db + POSTGRES_USER: user + POSTGRES_PASSWORD: password +``` + +## Variables + +### Global Variables + +```yaml +variables: + ENVIRONMENT: "production" + API_URL: "https://api.example.com" +``` + +### Job Variables + +```yaml +job-name: + variables: + JOB_VARIABLE: "value" + script: + - echo $JOB_VARIABLE +``` + +### Predefined Variables + +Common CI/CD variables: + +- `CI`: Always `true` in CI +- `CI_COMMIT_SHA`: Commit SHA +- `CI_COMMIT_REF_NAME`: Branch or tag name +- `CI_COMMIT_BRANCH`: Branch name +- `CI_COMMIT_TAG`: Tag name +- `CI_PROJECT_ID`: Project ID +- `CI_PROJECT_NAME`: Project name +- `CI_PROJECT_PATH`: Project path +- `CI_PIPELINE_ID`: Pipeline ID +- `CI_PIPELINE_IID`: Pipeline IID +- `CI_JOB_ID`: Job ID +- `CI_JOB_NAME`: Job name +- `CI_JOB_STAGE`: Job stage +- `CI_REGISTRY`: GitLab container registry +- `CI_REGISTRY_IMAGE`: Full registry path +- `CI_REGISTRY_USER`: Registry username +- `CI_REGISTRY_PASSWORD`: Registry password +- `GITLAB_USER_EMAIL`: User email +- `GITLAB_USER_LOGIN`: User username + +### Variable Precedence + +(Highest to lowest priority) +1. Trigger variables, scheduled pipeline variables, manual pipeline run variables +2. Project variables +3. Group variables +4. Instance variables +5. Variables in .gitlab-ci.yml +6. Predefined variables + +## Artifacts + +### Basic Artifacts + +```yaml +job-name: + script: + - npm run build + artifacts: + paths: + - dist/ + - build/ + expire_in: 1 week +``` + +### Artifact Options + +```yaml +artifacts: + # Files to include + paths: + - dist/ + - "*.log" + + # Files to exclude + exclude: + - dist/*.md + + # Expiration time + expire_in: 30 days # default: 30 days + # Options: 1 day, 1 week, 1 month, never + + # When to upload artifacts + when: always # on_success (default), on_failure, always + + # Artifact name + name: "$CI_JOB_NAME-$CI_COMMIT_REF_NAME" + + # Make artifacts public + public: true + + # Untracked files + untracked: false + + # Reports + reports: + junit: test-results.xml + coverage_report: + coverage_format: cobertura + path: coverage.xml +``` + +### Artifact Reports + +```yaml +test-job: + script: + - npm test + artifacts: + reports: + junit: test-results.xml + coverage_report: + coverage_format: cobertura + path: coverage.xml + dotenv: build.env +``` + +### Dependencies + +```yaml +build-job: + script: + - npm run build + artifacts: + paths: + - dist/ + +deploy-job: + dependencies: + - build-job + script: + - ls -la dist/ +``` + +### Needs (DAG Pipelines) + +```yaml +build-job: + stage: build + script: + - npm run build + artifacts: + paths: + - dist/ + +test-job: + stage: test + needs: [build-job] + script: + - npm test + +deploy-job: + stage: deploy + needs: [test-job] + script: + - ./deploy.sh +``` + +## Caching + +### Cache Configuration + +```yaml +cache: + paths: + - node_modules/ + - .npm/ + +# Job-specific cache +job-name: + cache: + key: "$CI_COMMIT_REF_NAME" + paths: + - vendor/ + policy: pull-push # pull-push (default), pull, push +``` + +### Cache Keys + +```yaml +# Static key +cache: + key: my-cache + +# Dynamic key based on branch +cache: + key: "$CI_COMMIT_REF_NAME" + +# Key with files +cache: + key: + files: + - package-lock.json + prefix: npm-cache + +# Multiple caches +cache: + - key: npm-cache + paths: + - node_modules/ + - key: build-cache + paths: + - dist/ +``` + +### Cache vs Artifacts + +**Use Cache for**: +- Dependencies (node_modules, vendor) +- Compiled libraries +- Downloaded packages + +**Use Artifacts for**: +- Build output +- Test results +- Files needed in later stages + +## Job Control + +### Rules + +```yaml +job-name: + script: + - echo "Running job" + rules: + # Run on main branch + - if: $CI_COMMIT_BRANCH == "main" + when: always + + # Run on merge requests + - if: $CI_PIPELINE_SOURCE == "merge_request_event" + + # Run if file exists + - exists: + - Dockerfile + when: manual + + # Run if files changed + - changes: + - src/**/*.js + - package.json + + # Default + - when: never +``` + +### Only/Except (Legacy) + +```yaml +job-name: + only: + - main + - tags + - merge_requests + except: + - schedules +``` + +### When + +```yaml +job-name: + when: manual # on_success (default), on_failure, always, manual, delayed, never + +# Delayed job +job-delayed: + when: delayed + start_in: 30 minutes +``` + +### Allow Failure + +```yaml +job-name: + allow_failure: true + script: + - ./optional-script.sh + +# Conditional failure +job-conditional: + allow_failure: + exit_codes: + - 137 # Specific exit code + - 139 +``` + +## Environments + +### Basic Environment + +```yaml +deploy-production: + stage: deploy + script: + - ./deploy.sh + environment: + name: production + url: https://prod.example.com +``` + +### Dynamic Environments + +```yaml +deploy-review: + stage: deploy + script: + - ./deploy-review.sh + environment: + name: review/$CI_COMMIT_REF_NAME + url: https://$CI_COMMIT_REF_SLUG.review.example.com + on_stop: stop-review + only: + - branches + except: + - main + +stop-review: + stage: deploy + script: + - ./stop-review.sh + environment: + name: review/$CI_COMMIT_REF_NAME + action: stop + when: manual +``` + +### Environment Tiers + +```yaml +environment: + name: production + deployment_tier: production # production, staging, testing, development, other +``` + +## Includes + +### Include External Files + +```yaml +include: + # Include from same project + - local: '/templates/.gitlab-ci-template.yml' + + # Include from another project + - project: 'group/project' + ref: main + file: '/templates/.gitlab-ci.yml' + + # Include from URL + - remote: 'https://example.com/.gitlab-ci.yml' + + # Include template + - template: Auto-DevOps.gitlab-ci.yml +``` + +### Include with Rules + +```yaml +include: + - local: '/templates/docker.yml' + rules: + - if: $DOCKER_ENABLED == "true" +``` + +## Extends + +### Template Jobs + +```yaml +.deploy-template: + script: + - ./deploy.sh + only: + - main + +deploy-staging: + extends: .deploy-template + variables: + ENVIRONMENT: staging + +deploy-production: + extends: .deploy-template + variables: + ENVIRONMENT: production + when: manual +``` + +## Parallel Jobs + +### Matrix + +```yaml +test: + parallel: + matrix: + - NODE_VERSION: ["14", "16", "18"] + OS: ["linux", "windows"] + script: + - node --version + - echo "Testing on $OS with Node $NODE_VERSION" +``` + +### Simple Parallel + +```yaml +test: + parallel: 5 + script: + - echo "Running test $CI_NODE_INDEX of $CI_NODE_TOTAL" +``` + +## Triggers + +### Multi-Project Pipelines + +```yaml +trigger-downstream: + trigger: + project: group/downstream-project + branch: main + strategy: depend # Wait for downstream pipeline +``` + +### Parent-Child Pipelines + +```yaml +trigger-child: + trigger: + include: path/to/child-pipeline.yml + strategy: depend +``` + +## Docker + +### Building Docker Images + +```yaml +build-image: + image: docker:latest + services: + - docker:dind + variables: + DOCKER_TLS_CERTDIR: "/certs" + script: + - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY + - docker build -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA . + - docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA +``` + +### GitLab Container Registry + +```yaml +build-and-push: + stage: build + image: docker:latest + services: + - docker:dind + script: + - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY + - docker build -t $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG . + - docker tag $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG $CI_REGISTRY_IMAGE:latest + - docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG + - docker push $CI_REGISTRY_IMAGE:latest +``` + +## Security Scanning + +### SAST (Static Application Security Testing) + +```yaml +include: + - template: Security/SAST.gitlab-ci.yml +``` + +### Dependency Scanning + +```yaml +include: + - template: Security/Dependency-Scanning.gitlab-ci.yml +``` + +### Container Scanning + +```yaml +include: + - template: Security/Container-Scanning.gitlab-ci.yml + +container_scanning: + variables: + CI_APPLICATION_REPOSITORY: $CI_REGISTRY_IMAGE + CI_APPLICATION_TAG: $CI_COMMIT_SHA +``` + +### Secret Detection + +```yaml +include: + - template: Security/Secret-Detection.gitlab-ci.yml +``` + +## Advanced Features + +### Retry + +```yaml +job-name: + retry: 2 # Retry up to 2 times + +# Conditional retry +job-conditional: + retry: + max: 2 + when: + - runner_system_failure + - stuck_or_timeout_failure +``` + +### Timeout + +```yaml +job-name: + timeout: 3 hours # Default: 1 hour, Max: configured by admin +``` + +### Resource Group + +```yaml +deploy-production: + resource_group: production + script: + - ./deploy.sh +``` + +### Coverage + +```yaml +test-job: + script: + - npm test + coverage: '/Coverage: \d+\.\d+%/' +``` + +## Pipeline Types + +### Merge Request Pipelines + +```yaml +workflow: + rules: + - if: $CI_PIPELINE_SOURCE == "merge_request_event" + - if: $CI_COMMIT_BRANCH == "main" +``` + +### Scheduled Pipelines + +```yaml +job-name: + rules: + - if: $CI_PIPELINE_SOURCE == "schedule" + script: + - ./nightly-build.sh +``` + +### Multi-Branch Pipelines + +```yaml +workflow: + rules: + - if: $CI_COMMIT_BRANCH + +build-job: + script: + - echo "Building for branch: $CI_COMMIT_BRANCH" +``` + +## Complete Example + +```yaml +# Define Docker image +image: node:18 + +# Define stages +stages: + - build + - test + - security + - deploy + +# Global variables +variables: + NODE_ENV: production + CACHE_KEY: "$CI_COMMIT_REF_SLUG" + +# Global cache +cache: + key: $CACHE_KEY + paths: + - node_modules/ + - .npm/ + +# Global before script +before_script: + - npm ci --cache .npm --prefer-offline + +# Build job +build: + stage: build + script: + - npm run build + artifacts: + paths: + - dist/ + expire_in: 1 week + rules: + - if: $CI_COMMIT_BRANCH + +# Unit tests +unit-tests: + stage: test + script: + - npm run test:unit + coverage: '/Lines\s+:\s+(\d+\.\d+)%/' + artifacts: + reports: + junit: test-results.xml + coverage_report: + coverage_format: cobertura + path: coverage.xml + +# Integration tests +integration-tests: + stage: test + services: + - postgres:14 + variables: + POSTGRES_DB: test_db + POSTGRES_USER: test_user + POSTGRES_PASSWORD: test_password + script: + - npm run test:integration + +# SAST scanning +sast: + stage: security + include: + - template: Security/SAST.gitlab-ci.yml + +# Dependency scanning +dependency_scanning: + stage: security + include: + - template: Security/Dependency-Scanning.gitlab-ci.yml + +# Build Docker image +docker-build: + stage: build + image: docker:latest + services: + - docker:dind + script: + - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY + - docker build -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA . + - docker tag $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA $CI_REGISTRY_IMAGE:latest + - docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA + - docker push $CI_REGISTRY_IMAGE:latest + rules: + - if: $CI_COMMIT_BRANCH == "main" + +# Deploy to staging +deploy-staging: + stage: deploy + script: + - echo "Deploying to staging..." + - ./deploy.sh staging + environment: + name: staging + url: https://staging.example.com + deployment_tier: staging + rules: + - if: $CI_COMMIT_BRANCH == "main" + +# Deploy to production +deploy-production: + stage: deploy + script: + - echo "Deploying to production..." + - ./deploy.sh production + environment: + name: production + url: https://example.com + deployment_tier: production + when: manual + rules: + - if: $CI_COMMIT_BRANCH == "main" +``` + +## Best Practices + +### 1. Pipeline Efficiency + +- **Use caching**: Cache dependencies to speed up builds +- **Parallel jobs**: Run independent jobs in parallel +- **Artifacts**: Only include necessary files +- **DAG pipelines**: Use `needs` to avoid waiting for entire stages + +### 2. Security + +- **Protected variables**: Use protected variables for secrets +- **Masked variables**: Mask sensitive values in logs +- **Security scanning**: Include SAST, dependency scanning, etc. +- **Least privilege**: Give jobs minimum necessary permissions + +### 3. Reliability + +- **Retry failed jobs**: Configure retry for flaky tests +- **Timeouts**: Set appropriate timeouts +- **Failure handling**: Use `allow_failure` for optional jobs +- **Health checks**: Test deployments after deployment + +### 4. Maintainability + +- **DRY principle**: Use extends and templates +- **Includes**: Separate common configuration +- **Documentation**: Comment complex configurations +- **Consistent naming**: Use clear, consistent job names + +### 5. Testing + +- **Test early**: Run fast tests first +- **Parallel testing**: Split test suites +- **Test coverage**: Track and enforce coverage +- **Multiple environments**: Test on different OS/versions + +## Troubleshooting + +### Common Issues + +1. **Job stuck in pending** + - Check runner availability + - Verify runner tags match job tags + - Check runner capacity + +2. **Cache not working** + - Verify cache key + - Check cache path + - Ensure runner has cache configured + +3. **Artifacts not available** + - Check artifact expiration + - Verify artifact paths + - Ensure job completed successfully + +4. **Variables not expanding** + - Check variable syntax ($VARIABLE or ${VARIABLE}) + - Verify variable scope (global, job, protected) + - Check variable precedence + +5. **Docker issues** + - Verify docker:dind service is running + - Check Docker TLS configuration + - Ensure sufficient disk space + +### Debugging + +```yaml +debug-job: + script: + # Print all environment variables + - env | sort + + # Print specific variables + - echo $CI_COMMIT_SHA + - echo $CI_PIPELINE_ID + + # Print working directory + - pwd + - ls -la + + # Check cache + - ls -la node_modules/ || echo "Cache not present" +``` + +## Additional Resources + +- Official Documentation: https://docs.gitlab.com/ee/ci/ +- CI/CD Examples: https://docs.gitlab.com/ee/ci/examples/ +- Pipeline Configuration Reference: https://docs.gitlab.com/ee/ci/yaml/ +- GitLab CI/CD Templates: https://gitlab.com/gitlab-org/gitlab-foss/-/tree/master/lib/gitlab/ci/templates diff --git a/skills/gitlab/references/container-registry.md b/skills/gitlab/references/container-registry.md new file mode 100644 index 0000000..872d579 --- /dev/null +++ b/skills/gitlab/references/container-registry.md @@ -0,0 +1,598 @@ +# GitLab Container Registry Reference + +## Overview + +GitLab Container Registry is a secure Docker image registry built into GitLab, allowing you to store and manage Docker images. + +## Registry URL Format + +``` +registry.gitlab.com/namespace/project +``` + +For self-hosted: +``` +registry.your-domain.com/namespace/project +``` + +## Authentication + +### Docker Login + +```bash +# Using personal access token +docker login registry.gitlab.com -u -p + +# Using deploy token +docker login registry.gitlab.com -u -p + +# In CI/CD (automatic) +docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY +``` + +### Token Scopes + +Personal Access Token needs: +- `read_registry` - Pull images +- `write_registry` - Push images + +Deploy Token needs: +- `read_registry` - Pull images +- `write_registry` - Push images + +## Building and Pushing Images + +### Basic Docker Build + +```yaml +# .gitlab-ci.yml +build: + stage: build + image: docker:latest + services: + - docker:dind + variables: + DOCKER_TLS_CERTDIR: "/certs" + before_script: + - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY + script: + - docker build -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA . + - docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA +``` + +### Multi-Stage Build + +**Dockerfile**: +```dockerfile +# Build stage +FROM node:18 AS builder +WORKDIR /app +COPY package*.json ./ +RUN npm ci +COPY . . +RUN npm run build + +# Production stage +FROM node:18-alpine +WORKDIR /app +COPY --from=builder /app/dist ./dist +COPY package*.json ./ +RUN npm ci --production +EXPOSE 3000 +CMD ["node", "dist/server.js"] +``` + +### Tagging Strategy + +```yaml +build: + script: + # Commit SHA tag + - docker build -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA . + - docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA + + # Branch name tag + - docker tag $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG + - docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG + + # Latest tag (main branch only) + - | + if [ "$CI_COMMIT_BRANCH" == "main" ]; then + docker tag $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA $CI_REGISTRY_IMAGE:latest + docker push $CI_REGISTRY_IMAGE:latest + fi + + # Version tag (for tags) + - | + if [ -n "$CI_COMMIT_TAG" ]; then + docker tag $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA $CI_REGISTRY_IMAGE:$CI_COMMIT_TAG + docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_TAG + fi +``` + +## Pulling Images + +### Pull from Registry + +```bash +# Pull specific tag +docker pull registry.gitlab.com/group/project:tag + +# Pull latest +docker pull registry.gitlab.com/group/project:latest + +# Pull by SHA +docker pull registry.gitlab.com/group/project@sha256:abc123... +``` + +### Use in CI/CD + +```yaml +test: + image: $CI_REGISTRY_IMAGE:latest + script: + - npm test +``` + +### Use in Docker Compose + +```yaml +version: '3.8' +services: + app: + image: registry.gitlab.com/group/project:latest + ports: + - "3000:3000" + environment: + - NODE_ENV=production +``` + +## Registry Management + +### List Repository Tags + +```bash +curl --header "PRIVATE-TOKEN: " \ + "https://gitlab.com/api/v4/projects/:id/registry/repositories/:repository_id/tags" +``` + +### Get Tag Details + +```bash +curl --header "PRIVATE-TOKEN: " \ + "https://gitlab.com/api/v4/projects/:id/registry/repositories/:repository_id/tags/:tag_name" +``` + +### Delete Tag + +```bash +curl --request DELETE --header "PRIVATE-TOKEN: " \ + "https://gitlab.com/api/v4/projects/:id/registry/repositories/:repository_id/tags/:tag_name" +``` + +### Delete Tags in Bulk + +```bash +# Delete by regex +curl --request DELETE --header "PRIVATE-TOKEN: " \ + "https://gitlab.com/api/v4/projects/:id/registry/repositories/:repository_id/tags" \ + --data "name_regex=.*-dev" \ + --data "keep_n=5" \ + --data "older_than=7d" +``` + +## Cleanup Policies + +### Configure Cleanup Policy + +**Via UI**: +1. Project Settings > Packages & Registries > Container Registry +2. Configure cleanup policy +3. Set rules + +**Via API**: +```bash +curl --request PUT --header "PRIVATE-TOKEN: " \ + "https://gitlab.com/api/v4/projects/:id/registry/repositories/:repository_id" \ + --data "cadence=1d" \ + --data "keep_n=10" \ + --data "older_than=30d" \ + --data "name_regex=.*-dev" \ + --data "name_regex_keep=.*-stable" +``` + +**Policy options**: +- `cadence`: How often to run (1d, 7d, 14d, 1month, 3month) +- `keep_n`: Keep N most recent tags +- `older_than`: Delete tags older than specified time +- `name_regex`: Regex for tags to delete +- `name_regex_keep`: Regex for tags to keep + +### Cleanup Example + +```yaml +# Keep production tags forever +# Delete dev/feature tags after 7 days +# Keep last 5 tags per branch + +cleanup_policy: + enabled: true + cadence: 1d + keep_n: 5 + older_than: 7d + name_regex: '^(?!main|prod|release).*' + name_regex_keep: '^(main|prod|release-.*|v\d+\.\d+\.\d+)$' +``` + +## Registry Access Control + +### Project-Level Access + +Members inherit registry permissions from project role: +- Guest: No access +- Reporter: Pull images +- Developer: Pull and push images +- Maintainer: Full access +- Owner: Full access + +### Deploy Tokens + +Create deploy tokens for automation: + +```bash +curl --request POST --header "PRIVATE-TOKEN: " \ + "https://gitlab.com/api/v4/projects/:id/deploy_tokens" \ + --data "name=Registry Token" \ + --data "scopes[]=read_registry" \ + --data "scopes[]=write_registry" \ + --data "expires_at=2025-12-31" +``` + +Use in CI/CD: +```yaml +variables: + REGISTRY_TOKEN_USER: "deploy-token-user" + REGISTRY_TOKEN_PASS: "deploy-token-password" + +deploy: + script: + - docker login -u $REGISTRY_TOKEN_USER -p $REGISTRY_TOKEN_PASS $CI_REGISTRY + - docker pull $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA +``` + +## Building with Kaniko + +Alternative to Docker-in-Docker: + +```yaml +build: + stage: build + image: + name: gcr.io/kaniko-project/executor:debug + entrypoint: [""] + script: + - mkdir -p /kaniko/.docker + - echo "{\"auths\":{\"${CI_REGISTRY}\":{\"auth\":\"$(printf "%s:%s" "${CI_REGISTRY_USER}" "${CI_REGISTRY_PASSWORD}" | base64 | tr -d '\n')\"}}}" > /kaniko/.docker/config.json + - /kaniko/executor + --context "${CI_PROJECT_DIR}" + --dockerfile "${CI_PROJECT_DIR}/Dockerfile" + --destination "${CI_REGISTRY_IMAGE}:${CI_COMMIT_TAG}" + --cache=true + --cache-ttl=24h +``` + +**Advantages**: +- No Docker daemon needed +- More secure (no privileged mode) +- Better for Kubernetes +- Built-in caching + +## Building with Buildah + +Daemonless container builds: + +```yaml +build: + stage: build + image: quay.io/buildah/stable + script: + - buildah login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY + - buildah bud -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA . + - buildah push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA +``` + +## Multi-Platform Images + +### Build for Multiple Architectures + +```yaml +build-multiarch: + stage: build + image: docker:latest + services: + - docker:dind + before_script: + - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY + - docker run --rm --privileged multiarch/qemu-user-static --reset -p yes + - docker buildx create --use + script: + - docker buildx build + --platform linux/amd64,linux/arm64,linux/arm/v7 + --tag $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA + --tag $CI_REGISTRY_IMAGE:latest + --push . +``` + +## Image Scanning + +### Container Scanning + +```yaml +include: + - template: Security/Container-Scanning.gitlab-ci.yml + +variables: + CS_IMAGE: $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA + +container_scanning: + dependencies: + - build +``` + +### Trivy Scanner + +```yaml +trivy_scan: + stage: test + image: + name: aquasec/trivy:latest + entrypoint: [""] + script: + - trivy image --exit-code 0 --no-progress $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA + - trivy image --exit-code 1 --severity CRITICAL --no-progress $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA +``` + +## Image Signing + +### Sign with Cosign + +```yaml +sign: + stage: sign + image: gcr.io/projectsigstore/cosign:latest + script: + - echo "$COSIGN_KEY" > cosign.key + - cosign sign --key cosign.key $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA +``` + +### Verify Signature + +```bash +cosign verify --key cosign.pub registry.gitlab.com/group/project:tag +``` + +## Registry Storage + +### Configure Storage Backend + +**Local storage**: +```ruby +# /etc/gitlab/gitlab.rb +registry['storage'] = { + 'filesystem' => { + 'rootdirectory' => '/var/opt/gitlab/gitlab-rails/shared/registry' + } +} +``` + +**S3 storage**: +```ruby +registry['storage'] = { + 's3' => { + 'accesskey' => 'AKIAIOSFODNN7EXAMPLE', + 'secretkey' => 'wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY', + 'bucket' => 'gitlab-registry', + 'region' => 'us-east-1' + } +} +``` + +**GCS storage**: +```ruby +registry['storage'] = { + 'gcs' => { + 'bucket' => 'gitlab-registry', + 'keyfile' => '/path/to/keyfile.json' + } +} +``` + +**Azure storage**: +```ruby +registry['storage'] = { + 'azure' => { + 'accountname' => 'accountname', + 'accountkey' => 'base64encodedaccountkey', + 'container' => 'gitlab-registry' + } +} +``` + +## Registry Mirroring + +### Pull from External Registry + +```yaml +variables: + DOCKER_AUTH_CONFIG: | + { + "auths": { + "docker.io": { + "auth": "$(echo -n $DOCKERHUB_USER:$DOCKERHUB_PASSWORD | base64)" + } + } + } + +build: + script: + # Pull from Docker Hub + - docker pull nginx:alpine + + # Tag for GitLab registry + - docker tag nginx:alpine $CI_REGISTRY_IMAGE/nginx:alpine + + # Push to GitLab registry + - docker push $CI_REGISTRY_IMAGE/nginx:alpine +``` + +## Troubleshooting + +### Common Issues + +**1. Authentication Failed** +```bash +# Clear Docker credentials +rm ~/.docker/config.json + +# Re-authenticate +docker login registry.gitlab.com +``` + +**2. Push Denied** +```bash +# Check token permissions +# Ensure token has write_registry scope +# Verify project access level +``` + +**3. Image Not Found** +```bash +# Verify image path +docker pull registry.gitlab.com/namespace/project:tag + +# Check tag exists +curl --header "PRIVATE-TOKEN: " \ + "https://gitlab.com/api/v4/projects/:id/registry/repositories" +``` + +**4. Storage Issues** +```bash +# Check disk space +df -h /var/opt/gitlab + +# Run garbage collection +gitlab-rake gitlab:cleanup:container_registry +``` + +### Enable Debug Logging + +```ruby +# /etc/gitlab/gitlab.rb +registry['log_level'] = 'debug' +registry['log_formatter'] = 'json' +``` + +## Best Practices + +### 1. Image Size Optimization + +```dockerfile +# Use minimal base images +FROM alpine:latest + +# Multi-stage builds +FROM node:18 AS builder +# ... build steps ... +FROM node:18-alpine +COPY --from=builder /app/dist ./dist + +# Remove unnecessary files +RUN rm -rf /tmp/* /var/cache/apk/* + +# Combine RUN commands +RUN apk add --no-cache curl && \ + curl -o file.tar.gz https://example.com/file.tar.gz && \ + tar -xzf file.tar.gz && \ + rm file.tar.gz +``` + +### 2. Security + +- Scan all images +- Use specific tags (not latest) +- Sign images +- Regularly update base images +- Remove old images + +### 3. Tagging + +- Use semantic versioning for releases +- Include commit SHA for traceability +- Tag by environment (dev, staging, prod) +- Cleanup temporary tags + +### 4. CI/CD Integration + +```yaml +stages: + - build + - test + - scan + - deploy + +build: + stage: build + script: + - docker build -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA . + - docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA + +test: + stage: test + image: $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA + script: + - npm test + +scan: + stage: scan + variables: + CS_IMAGE: $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA + include: + - template: Security/Container-Scanning.gitlab-ci.yml + +deploy: + stage: deploy + script: + - docker tag $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA $CI_REGISTRY_IMAGE:latest + - docker push $CI_REGISTRY_IMAGE:latest + only: + - main +``` + +## Registry API Reference + +### List Repositories + +```bash +curl --header "PRIVATE-TOKEN: " \ + "https://gitlab.com/api/v4/projects/:id/registry/repositories" +``` + +### Get Repository + +```bash +curl --header "PRIVATE-TOKEN: " \ + "https://gitlab.com/api/v4/projects/:id/registry/repositories/:repository_id" +``` + +### Delete Repository + +```bash +curl --request DELETE --header "PRIVATE-TOKEN: " \ + "https://gitlab.com/api/v4/projects/:id/registry/repositories/:repository_id" +``` + +## Additional Resources + +- Container Registry Docs: https://docs.gitlab.com/ee/user/packages/container_registry/ +- Docker Documentation: https://docs.docker.com/ +- Cleanup Policies: https://docs.gitlab.com/ee/user/packages/container_registry/reduce_container_registry_storage.html diff --git a/skills/gitlab/references/gitlab-pages.md b/skills/gitlab/references/gitlab-pages.md new file mode 100644 index 0000000..7a6b6d0 --- /dev/null +++ b/skills/gitlab/references/gitlab-pages.md @@ -0,0 +1,714 @@ +# GitLab Pages Reference + +## Overview + +GitLab Pages allows you to host static websites directly from your GitLab repository. Perfect for documentation, portfolios, blogs, and landing pages. + +## Pages URL Format + +**GitLab.com**: +``` +https://username.gitlab.io/project-name +``` + +**Custom domain**: +``` +https://www.example.com +``` + +**Group pages**: +``` +https://groupname.gitlab.io +``` + +## Creating a Pages Site + +### Basic Setup + +**.gitlab-ci.yml**: +```yaml +pages: + stage: deploy + script: + - mkdir .public + - cp -r * .public + - mv .public public + artifacts: + paths: + - public + rules: + - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH +``` + +**Requirements**: +1. Job must be named `pages` +2. Must create `public/` directory +3. Must have artifact with `public/` path +4. Runs on default branch (usually main) + +### Static HTML + +**index.html**: +```html + + + + My GitLab Page + + +

Welcome!

+

This is hosted on GitLab Pages

+ + +``` + +**.gitlab-ci.yml**: +```yaml +pages: + stage: deploy + script: + - mkdir public + - cp index.html public/ + artifacts: + paths: + - public + only: + - main +``` + +## Static Site Generators + +### Hugo + +```yaml +image: registry.gitlab.com/pages/hugo:latest + +variables: + GIT_SUBMODULE_STRATEGY: recursive + +pages: + script: + - hugo + artifacts: + paths: + - public + rules: + - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH +``` + +### Jekyll + +```yaml +image: ruby:2.7 + +before_script: + - bundle install + +pages: + script: + - bundle exec jekyll build -d public + artifacts: + paths: + - public + rules: + - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH +``` + +### Gatsby + +```yaml +image: node:18 + +cache: + paths: + - node_modules/ + - .cache/ + +pages: + script: + - npm install + - npm run build + - mv public public-gatsby + - mv public-gatsby public + artifacts: + paths: + - public + rules: + - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH +``` + +### Next.js + +```yaml +image: node:18 + +cache: + paths: + - node_modules/ + - .next/cache/ + +pages: + script: + - npm install + - npm run build + - npm run export + - mv out public + artifacts: + paths: + - public + rules: + - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH +``` + +### VuePress + +```yaml +image: node:18 + +pages: + script: + - npm install + - npm run docs:build + - mv docs/.vuepress/dist public + artifacts: + paths: + - public + rules: + - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH +``` + +### MkDocs + +```yaml +image: python:3.11 + +pages: + script: + - pip install mkdocs mkdocs-material + - mkdocs build + - mv site public + artifacts: + paths: + - public + rules: + - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH +``` + +### Docusaurus + +```yaml +image: node:18 + +pages: + script: + - npm install + - npm run build + - mv build public + artifacts: + paths: + - public + rules: + - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH +``` + +### Hexo + +```yaml +image: node:18 + +pages: + script: + - npm install hexo-cli -g + - npm install + - hexo generate + - mv public public-hexo + - mv public-hexo public + artifacts: + paths: + - public + rules: + - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH +``` + +## Custom Domain Setup + +### Add Custom Domain + +**Via UI**: +1. Settings > Pages +2. New Domain +3. Enter domain name +4. Save + +**Via API**: +```bash +curl --request POST --header "PRIVATE-TOKEN: " \ + "https://gitlab.com/api/v4/projects/:id/pages/domains" \ + --data "domain=www.example.com" +``` + +### DNS Configuration + +**For root domain (example.com)**: +``` +Type: A +Name: @ +Value: 35.185.44.232 +``` + +**For subdomain (www.example.com)**: +``` +Type: CNAME +Name: www +Value: username.gitlab.io +``` + +**Verify DNS**: +```bash +dig www.example.com +nslookup www.example.com +``` + +### SSL/TLS Certificate + +**Automatic Let's Encrypt**: +1. Add custom domain +2. Configure DNS +3. Enable "Automatic certificate management using Let's Encrypt" + +**Manual certificate**: +```bash +curl --request PUT --header "PRIVATE-TOKEN: " \ + "https://gitlab.com/api/v4/projects/:id/pages/domains/www.example.com" \ + --form "certificate=@/path/to/cert.pem" \ + --form "key=@/path/to/key.pem" +``` + +## Access Control + +### Private Pages + +Require authentication to access pages (Premium/Ultimate): + +**Enable private pages**: +```bash +curl --request PUT --header "PRIVATE-TOKEN: " \ + "https://gitlab.com/api/v4/projects/:id" \ + --data "pages_access_level=private" +``` + +**Access levels**: +- `public` - Anyone can access +- `private` - Only project members +- `enabled` - Controlled by project visibility +- `disabled` - Pages disabled + +### Authentication + +When pages are private: +- Users must log in to GitLab +- Project members can access +- Access controlled by member permissions + +## Preview Environments + +Create preview for merge requests: + +```yaml +pages: + stage: deploy + script: + - npm run build + - mv dist public + artifacts: + paths: + - public + rules: + - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH + +pages:preview: + stage: deploy + script: + - npm run build + - mv dist public + artifacts: + paths: + - public + environment: + name: review/$CI_COMMIT_REF_NAME + url: https://$CI_PROJECT_ROOT_NAMESPACE.gitlab.io/-/$CI_PROJECT_NAME/-/jobs/$CI_JOB_ID/artifacts/public/index.html + rules: + - if: $CI_MERGE_REQUEST_ID +``` + +## Advanced Configuration + +### Custom 404 Page + +Create `public/404.html`: +```html + + + + Page Not Found + + +

404 - Page Not Found

+

The page you're looking for doesn't exist.

+ Go Home + + +``` + +### Redirects + +Create `public/_redirects`: +``` +# Redirect /old-page to /new-page +/old-page /new-page 301 + +# Redirect with wildcards +/blog/* /news/:splat 301 + +# Conditional redirects +/api/* https://api.example.com/:splat 200 + +# Country-based redirects +/ /us 302 Country=us +/ /uk 302 Country=uk +``` + +### Headers + +Create `public/_headers`: +``` +# Security headers +/* + X-Frame-Options: DENY + X-Content-Type-Options: nosniff + X-XSS-Protection: 1; mode=block + Referrer-Policy: no-referrer-when-downgrade + +# Cache control +/static/* + Cache-Control: public, max-age=31536000, immutable + +# CORS +/api/* + Access-Control-Allow-Origin: * + Access-Control-Allow-Methods: GET, POST, OPTIONS +``` + +### Base URL Configuration + +For subdirectory deployment: + +**Hugo**: +```toml +# config.toml +baseURL = "https://username.gitlab.io/project-name/" +``` + +**Jekyll**: +```yaml +# _config.yml +baseurl: "/project-name" +url: "https://username.gitlab.io" +``` + +**VuePress**: +```js +// .vuepress/config.js +module.exports = { + base: '/project-name/', +} +``` + +## Optimization + +### Image Optimization + +```yaml +pages: + before_script: + - npm install -g sharp-cli + script: + - | + find public -name '*.jpg' -o -name '*.png' | while read img; do + sharp -i "$img" -o "$img" resize 1920 + done +``` + +### Minification + +```yaml +pages: + script: + - npm run build + - npm install -g html-minifier clean-css-cli uglify-js + - | + find public -name '*.html' -exec html-minifier --collapse-whitespace --remove-comments --minify-css --minify-js {} -o {} \; + find public -name '*.css' -exec cleancss -o {} {} \; + find public -name '*.js' -exec uglifyjs {} -c -m -o {} \; +``` + +### Compression + +```yaml +pages: + script: + - npm run build + - mv dist public + - find public -type f -regex '.*\.\(htm\|html\|txt\|text\|js\|css\|svg\|json\)$' -exec gzip -f -k {} \; + - find public -type f -regex '.*\.\(htm\|html\|txt\|text\|js\|css\|svg\|json\)$' -exec brotli -f -k {} \; +``` + +## CI/CD Examples + +### Multi-Branch Deployment + +```yaml +pages: + stage: deploy + script: + - npm run build + - mv dist public + artifacts: + paths: + - public + rules: + - if: $CI_COMMIT_BRANCH == "main" + +pages:staging: + stage: deploy + script: + - npm run build + - mv dist public + artifacts: + paths: + - public + environment: + name: staging + url: https://staging.example.com + rules: + - if: $CI_COMMIT_BRANCH == "staging" +``` + +### Conditional Deployment + +```yaml +pages: + stage: deploy + script: + - | + if [ "$CI_COMMIT_BRANCH" == "main" ]; then + export NODE_ENV=production + else + export NODE_ENV=development + fi + - npm run build + - mv dist public + artifacts: + paths: + - public + rules: + - if: $CI_COMMIT_BRANCH +``` + +### Scheduled Rebuild + +```yaml +pages: + script: + - npm run build + - mv dist public + artifacts: + paths: + - public + rules: + - if: $CI_COMMIT_BRANCH == "main" + - if: $CI_PIPELINE_SOURCE == "schedule" +``` + +## Monitoring + +### Analytics + +Add Google Analytics: +```html + + +``` + +### Status Checks + +```yaml +test:pages: + stage: test + script: + - npm run build + - npm run test + - | + # Check for broken links + npm install -g broken-link-checker + blc http://localhost:8000 -ro +``` + +## Troubleshooting + +### Pages Not Updating + +1. Check pipeline succeeded +2. Verify `pages` job ran +3. Check artifacts were created +4. Clear browser cache +5. Wait for CDN propagation (up to 30 minutes) + +### 404 Errors + +1. Verify file exists in `public/` directory +2. Check file paths (case-sensitive) +3. Verify artifact includes files +4. Check baseURL configuration + +### Custom Domain Issues + +1. Verify DNS configuration: `dig www.example.com` +2. Check SSL certificate status +3. Ensure domain verified in GitLab +4. Check domain registrar settings +5. Wait for DNS propagation (up to 48 hours) + +### Build Failures + +```yaml +pages: + script: + - set -e # Exit on error + - npm install + - npm run build || (echo "Build failed" && exit 1) + - mv dist public + artifacts: + paths: + - public + when: on_success +``` + +## Best Practices + +### 1. Performance + +- Optimize images +- Minify CSS/JS +- Enable compression +- Use CDN +- Implement caching + +### 2. Security + +- Use HTTPS (automatic with Let's Encrypt) +- Set security headers +- Validate user input (if using forms) +- Keep dependencies updated + +### 3. SEO + +```html + + + + + + + + + + + + +``` + +### 4. Accessibility + +- Use semantic HTML +- Add alt text to images +- Ensure keyboard navigation +- Maintain color contrast +- Test with screen readers + +### 5. Version Control + +```yaml +pages: + script: + - echo "Build $CI_COMMIT_SHA on $(date)" > public/version.txt + - npm run build +``` + +## API Reference + +### Get Pages + +```bash +curl --header "PRIVATE-TOKEN: " \ + "https://gitlab.com/api/v4/projects/:id/pages" +``` + +### Delete Pages + +```bash +curl --request DELETE --header "PRIVATE-TOKEN: " \ + "https://gitlab.com/api/v4/projects/:id/pages" +``` + +### List Domains + +```bash +curl --header "PRIVATE-TOKEN: " \ + "https://gitlab.com/api/v4/projects/:id/pages/domains" +``` + +### Add Domain + +```bash +curl --request POST --header "PRIVATE-TOKEN: " \ + "https://gitlab.com/api/v4/projects/:id/pages/domains" \ + --data "domain=www.example.com" \ + --data "auto_ssl_enabled=true" +``` + +### Update Domain + +```bash +curl --request PUT --header "PRIVATE-TOKEN: " \ + "https://gitlab.com/api/v4/projects/:id/pages/domains/www.example.com" \ + --data "auto_ssl_enabled=true" +``` + +### Delete Domain + +```bash +curl --request DELETE --header "PRIVATE-TOKEN: " \ + "https://gitlab.com/api/v4/projects/:id/pages/domains/www.example.com" +``` + +## Examples Repository + +GitLab provides example pages projects: +- https://gitlab.com/pages +- Hugo: https://gitlab.com/pages/hugo +- Jekyll: https://gitlab.com/pages/jekyll +- Gatsby: https://gitlab.com/pages/gatsby +- Next.js: https://gitlab.com/pages/nextjs + +## Additional Resources + +- Pages Documentation: https://docs.gitlab.com/ee/user/project/pages/ +- Pages Examples: https://gitlab.com/pages +- Custom Domains: https://docs.gitlab.com/ee/user/project/pages/custom_domains_ssl_tls_certification/ +- Let's Encrypt: https://letsencrypt.org/ diff --git a/skills/gitlab/references/graphql.md b/skills/gitlab/references/graphql.md new file mode 100644 index 0000000..c01cdb7 --- /dev/null +++ b/skills/gitlab/references/graphql.md @@ -0,0 +1,1166 @@ +# GitLab GraphQL API Reference + +## Overview + +GitLab's GraphQL API provides a more efficient alternative to REST API, allowing you to request exactly the data you need in a single query. The GraphQL API is the primary development focus for new features at GitLab. + +## GraphQL Endpoint + +``` +https://gitlab.com/api/graphql +``` + +For self-hosted instances: +``` +https://your-gitlab-instance.com/api/graphql +``` + +## Authentication + +### Personal Access Token + +```bash +curl --header "Authorization: Bearer " \ + --header "Content-Type: application/json" \ + --request POST \ + --data '{"query": "{ currentUser { username } }"}' \ + https://gitlab.com/api/graphql +``` + +### In HTTP Headers + +``` +Authorization: Bearer +``` + +## Interactive GraphQL Explorer (GraphiQL) + +Access the interactive explorer: +``` +https://gitlab.com/-/graphql-explorer +``` + +Features: +- Auto-completion +- Schema documentation +- Query validation +- Query history + +## Basic Query Structure + +```graphql +{ + currentUser { + id + username + name + email + } +} +``` + +## Common Queries + +### Get Current User + +```graphql +{ + currentUser { + id + username + name + email + avatarUrl + webUrl + status { + message + availability + } + } +} +``` + +### Get Project Details + +```graphql +{ + project(fullPath: "group/project") { + id + name + description + visibility + createdAt + lastActivityAt + webUrl + repository { + rootRef + empty + } + statistics { + commitCount + storageSize + repositorySize + lfsObjectsSize + } + } +} +``` + +### List Projects + +```graphql +{ + projects(first: 10, membership: true) { + nodes { + id + name + fullPath + description + visibility + createdAt + webUrl + starCount + forksCount + } + pageInfo { + hasNextPage + endCursor + } + } +} +``` + +### Get Merge Requests + +```graphql +{ + project(fullPath: "group/project") { + mergeRequests(first: 10, state: opened) { + nodes { + id + iid + title + description + state + createdAt + updatedAt + author { + username + name + } + sourceBranch + targetBranch + webUrl + draft + workInProgress + mergeStatus + approved + conflicts + diffStats { + additions + deletions + } + } + pageInfo { + hasNextPage + endCursor + } + } + } +} +``` + +### Get Issues + +```graphql +{ + project(fullPath: "group/project") { + issues(first: 10, state: opened) { + nodes { + id + iid + title + description + state + createdAt + closedAt + author { + username + name + } + assignees { + nodes { + username + name + } + } + labels { + nodes { + title + color + } + } + milestone { + title + dueDate + } + webUrl + upvotes + downvotes + } + pageInfo { + hasNextPage + endCursor + } + } + } +} +``` + +### Get Pipeline Details + +```graphql +{ + project(fullPath: "group/project") { + pipeline(iid: "123") { + id + iid + status + ref + createdAt + updatedAt + startedAt + finishedAt + duration + coverage + user { + username + name + } + jobs { + nodes { + id + name + stage + status + createdAt + startedAt + finishedAt + duration + webPath + } + } + } + } +} +``` + +### Get Commits + +```graphql +{ + project(fullPath: "group/project") { + repository { + tree(ref: "main") { + lastCommit { + id + sha + title + message + authoredDate + author { + name + email + } + } + } + } + } +} +``` + +### Get Group Details + +```graphql +{ + group(fullPath: "group-name") { + id + name + fullPath + description + visibility + webUrl + projects(first: 10) { + nodes { + name + fullPath + } + } + members { + nodes { + user { + username + name + } + accessLevel { + integerValue + stringValue + } + } + } + } +} +``` + +## Mutations + +### Create Issue + +```graphql +mutation { + createIssue(input: { + projectPath: "group/project" + title: "New issue title" + description: "Issue description" + assigneeIds: ["gid://gitlab/User/1"] + labelIds: ["gid://gitlab/Label/1"] + }) { + issue { + id + iid + title + webUrl + } + errors + } +} +``` + +### Update Issue + +```graphql +mutation { + updateIssue(input: { + projectPath: "group/project" + iid: "123" + title: "Updated title" + description: "Updated description" + stateEvent: CLOSE + }) { + issue { + id + title + state + } + errors + } +} +``` + +### Create Merge Request + +```graphql +mutation { + mergeRequestCreate(input: { + projectPath: "group/project" + title: "New feature" + description: "This MR adds..." + sourceBranch: "feature-branch" + targetBranch: "main" + }) { + mergeRequest { + id + iid + title + webUrl + } + errors + } +} +``` + +### Update Merge Request + +```graphql +mutation { + mergeRequestUpdate(input: { + projectPath: "group/project" + iid: "123" + title: "Updated title" + description: "Updated description" + assigneeIds: ["gid://gitlab/User/1"] + }) { + mergeRequest { + id + title + state + } + errors + } +} +``` + +### Merge Merge Request + +```graphql +mutation { + mergeRequestMerge(input: { + projectPath: "group/project" + iid: "123" + squash: true + }) { + mergeRequest { + id + state + mergedAt + } + errors + } +} +``` + +### Create Note (Comment) + +```graphql +mutation { + createNote(input: { + noteableId: "gid://gitlab/Issue/123" + body: "This is a comment" + }) { + note { + id + body + author { + username + } + } + errors + } +} +``` + +### Update Project + +```graphql +mutation { + updateProject(input: { + projectPath: "group/project" + description: "New description" + visibility: "public" + }) { + project { + id + description + visibility + } + errors + } +} +``` + +### Create Branch + +```graphql +mutation { + createBranch(input: { + projectPath: "group/project" + name: "new-branch" + ref: "main" + }) { + branch { + name + commit { + id + } + } + errors + } +} +``` + +### Create Snippet + +```graphql +mutation { + createSnippet(input: { + projectPath: "group/project" + title: "Code snippet" + fileName: "example.rb" + content: "puts 'Hello World'" + visibility: "private" + }) { + snippet { + id + title + webUrl + } + errors + } +} +``` + +### Run Pipeline + +```graphql +mutation { + pipelineCreate(input: { + projectPath: "group/project" + ref: "main" + }) { + pipeline { + id + status + } + errors + } +} +``` + +### Cancel Pipeline + +```graphql +mutation { + pipelineCancel(input: { + id: "gid://gitlab/Ci::Pipeline/123" + }) { + pipeline { + id + status + } + errors + } +} +``` + +## Variables and Fragments + +### Using Variables + +```graphql +query GetProject($fullPath: ID!) { + project(fullPath: $fullPath) { + id + name + description + } +} +``` + +Variables: +```json +{ + "fullPath": "group/project" +} +``` + +### Using Fragments + +```graphql +fragment UserInfo on User { + id + username + name + email + avatarUrl +} + +query { + currentUser { + ...UserInfo + } + project(fullPath: "group/project") { + author { + ...UserInfo + } + } +} +``` + +## Pagination + +### Cursor-Based Pagination + +```graphql +query { + projects(first: 10, after: "cursor_value") { + nodes { + id + name + } + pageInfo { + hasNextPage + hasPreviousPage + startCursor + endCursor + } + } +} +``` + +### Complete Pagination Example + +```python +import requests +import json + +def fetch_all_projects(token): + url = "https://gitlab.com/api/graphql" + headers = { + "Authorization": f"Bearer {token}", + "Content-Type": "application/json" + } + + has_next_page = True + after_cursor = None + all_projects = [] + + while has_next_page: + query = """ + query($after: String) { + projects(first: 10, after: $after, membership: true) { + nodes { + id + name + fullPath + } + pageInfo { + hasNextPage + endCursor + } + } + } + """ + + variables = {"after": after_cursor} + + response = requests.post( + url, + headers=headers, + json={"query": query, "variables": variables} + ) + + data = response.json()["data"] + projects = data["projects"] + + all_projects.extend(projects["nodes"]) + has_next_page = projects["pageInfo"]["hasNextPage"] + after_cursor = projects["pageInfo"]["endCursor"] + + return all_projects +``` + +## Introspection + +### Query Schema + +```graphql +{ + __schema { + types { + name + description + } + } +} +``` + +### Query Type Details + +```graphql +{ + __type(name: "Project") { + name + fields { + name + type { + name + kind + } + } + } +} +``` + +## Advanced Queries + +### Nested Relationships + +```graphql +{ + group(fullPath: "group-name") { + projects(first: 5) { + nodes { + name + mergeRequests(first: 3, state: opened) { + nodes { + title + author { + username + } + assignees { + nodes { + username + } + } + approvedBy { + nodes { + username + } + } + } + } + } + } + } +} +``` + +### Conditional Fields + +```graphql +query GetProject($includeMergeRequests: Boolean!) { + project(fullPath: "group/project") { + id + name + mergeRequests(first: 10) @include(if: $includeMergeRequests) { + nodes { + title + } + } + } +} +``` + +### Aliases + +```graphql +{ + openIssues: project(fullPath: "group/project") { + issues(state: opened) { + count + } + } + closedIssues: project(fullPath: "group/project") { + issues(state: closed) { + count + } + } +} +``` + +## Client Libraries + +### Python (gql) + +```python +from gql import gql, Client +from gql.transport.requests import RequestsHTTPTransport + +transport = RequestsHTTPTransport( + url="https://gitlab.com/api/graphql", + headers={"Authorization": f"Bearer {token}"}, +) + +client = Client(transport=transport, fetch_schema_from_transport=True) + +query = gql(""" + query { + currentUser { + username + name + } + } +""") + +result = client.execute(query) +print(result) +``` + +### JavaScript (Apollo Client) + +```javascript +import { ApolloClient, InMemoryCache, gql, createHttpLink } from '@apollo/client'; +import { setContext } from '@apollo/client/link/context'; + +const httpLink = createHttpLink({ + uri: 'https://gitlab.com/api/graphql', +}); + +const authLink = setContext((_, { headers }) => { + return { + headers: { + ...headers, + authorization: `Bearer ${token}`, + } + } +}); + +const client = new ApolloClient({ + link: authLink.concat(httpLink), + cache: new InMemoryCache() +}); + +const GET_CURRENT_USER = gql` + query { + currentUser { + username + name + } + } +`; + +client.query({ query: GET_CURRENT_USER }) + .then(result => console.log(result)); +``` + +### Ruby (graphql-client) + +```ruby +require "graphql/client" +require "graphql/client/http" + +module GitLabAPI + HTTP = GraphQL::Client::HTTP.new("https://gitlab.com/api/graphql") do + def headers(context) + { "Authorization" => "Bearer #{ENV['GITLAB_TOKEN']}" } + end + end + + Schema = GraphQL::Client.load_schema(HTTP) + Client = GraphQL::Client.new(schema: Schema, execute: HTTP) +end + +CurrentUserQuery = GitLabAPI::Client.parse <<-'GRAPHQL' + query { + currentUser { + username + name + } + } +GRAPHQL + +result = GitLabAPI::Client.query(CurrentUserQuery) +puts result.data.current_user.username +``` + +## Rate Limiting + +GraphQL queries count towards API rate limits based on query complexity. + +**Check rate limit**: +```graphql +{ + currentUser { + username + } +} +``` + +Response headers: +- `RateLimit-Limit`: Total allowed +- `RateLimit-Remaining`: Remaining requests +- `RateLimit-Reset`: Reset timestamp + +## Error Handling + +### Error Response Format + +```json +{ + "data": null, + "errors": [ + { + "message": "Error message", + "path": ["field", "path"], + "extensions": { + "code": "ERROR_CODE" + } + } + ] +} +``` + +### Common Error Types + +- `NOT_FOUND`: Resource not found +- `UNAUTHORIZED`: Authentication required +- `FORBIDDEN`: Insufficient permissions +- `VALIDATION_ERROR`: Invalid input +- `INTERNAL_SERVER_ERROR`: Server error + +### Error Handling Example + +```python +def execute_query(query, variables=None): + response = requests.post( + "https://gitlab.com/api/graphql", + headers={"Authorization": f"Bearer {token}"}, + json={"query": query, "variables": variables} + ) + + result = response.json() + + if "errors" in result: + for error in result["errors"]: + print(f"Error: {error['message']}") + if "path" in error: + print(f"Path: {error['path']}") + return None + + return result["data"] +``` + +## Best Practices + +### 1. Request Only Needed Fields + +**Bad**: +```graphql +{ + project(fullPath: "group/project") { + id + name + description + # ... all fields + } +} +``` + +**Good**: +```graphql +{ + project(fullPath: "group/project") { + id + name + } +} +``` + +### 2. Use Fragments for Reusability + +```graphql +fragment IssueFields on Issue { + id + iid + title + state + author { + username + } +} + +query { + project(fullPath: "group/project") { + openIssues: issues(state: opened) { + nodes { + ...IssueFields + } + } + closedIssues: issues(state: closed) { + nodes { + ...IssueFields + } + } + } +} +``` + +### 3. Implement Pagination + +Always use pagination for large datasets: + +```graphql +{ + projects(first: 100, after: $cursor) { + nodes { ... } + pageInfo { + hasNextPage + endCursor + } + } +} +``` + +### 4. Handle Errors Gracefully + +```javascript +async function fetchData() { + try { + const result = await client.query({ query: MY_QUERY }); + return result.data; + } catch (error) { + if (error.graphQLErrors) { + error.graphQLErrors.forEach(({ message, path }) => { + console.error(`GraphQL error: ${message} at ${path}`); + }); + } + if (error.networkError) { + console.error(`Network error: ${error.networkError}`); + } + } +} +``` + +### 5. Use Variables + +```graphql +query GetProject($path: ID!, $mrCount: Int!) { + project(fullPath: $path) { + mergeRequests(first: $mrCount) { + nodes { title } + } + } +} +``` + +### 6. Optimize Query Depth + +Limit nested query depth to avoid performance issues: + +```graphql +# Avoid deep nesting +{ + group { + projects { + mergeRequests { + commits { + notes { + # Too deep! + } + } + } + } + } +} +``` + +## Useful Queries for Common Tasks + +### Search Across Projects + +```graphql +{ + projects(search: "keyword", first: 10) { + nodes { + name + fullPath + description + } + } +} +``` + +### Get User Activity + +```graphql +{ + user(username: "john.doe") { + assignedMergeRequests(first: 10) { + nodes { + title + project { + name + } + } + } + authoredMergeRequests(first: 10) { + nodes { + title + state + } + } + } +} +``` + +### Get Deployment Status + +```graphql +{ + project(fullPath: "group/project") { + environments { + nodes { + name + state + lastDeployment { + status + createdAt + deployable { + ... on Job { + name + } + } + } + } + } + } +} +``` + +### Get Security Vulnerabilities + +```graphql +{ + project(fullPath: "group/project") { + vulnerabilities { + nodes { + id + title + severity + state + reportType + detectedAt + } + } + } +} +``` + +## Migration from REST to GraphQL + +### REST to GraphQL Comparison + +**REST**: Get project and its merge requests +```bash +# 2 requests +curl "https://gitlab.com/api/v4/projects/123" +curl "https://gitlab.com/api/v4/projects/123/merge_requests" +``` + +**GraphQL**: Single request +```graphql +{ + project(fullPath: "group/project") { + id + name + mergeRequests(first: 10) { + nodes { + title + } + } + } +} +``` + +## Subscriptions (Real-time Updates) + +GitLab supports GraphQL subscriptions for real-time updates: + +```graphql +subscription { + issuableUpdated { + issue { + id + title + state + } + } +} +``` + +## Additional Resources + +- GraphQL API Documentation: https://docs.gitlab.com/ee/api/graphql/ +- GraphQL Explorer: https://gitlab.com/-/graphql-explorer +- GraphQL Reference: https://docs.gitlab.com/ee/api/graphql/reference/ +- Best Practices: https://docs.gitlab.com/ee/api/graphql/#best-practices diff --git a/skills/gitlab/references/merge-requests.md b/skills/gitlab/references/merge-requests.md new file mode 100644 index 0000000..d09a768 --- /dev/null +++ b/skills/gitlab/references/merge-requests.md @@ -0,0 +1,816 @@ +# GitLab Merge Requests Reference + +## Overview + +Merge Requests (MRs) are the primary mechanism for proposing changes, conducting code reviews, and merging code in GitLab. + +## Creating Merge Requests + +### Via UI + +1. Navigate to project +2. Create/push to branch +3. Click "Create merge request" +4. Fill in details: + - Title and description + - Source and target branches + - Assignees and reviewers + - Labels and milestone +5. Click "Create merge request" + +### Via API + +```bash +curl --request POST --header "PRIVATE-TOKEN: " \ + "https://gitlab.com/api/v4/projects/:id/merge_requests" \ + --data "source_branch=feature" \ + --data "target_branch=main" \ + --data "title=Add new feature" \ + --data "description=This MR adds..." \ + --data "assignee_id=123" \ + --data "reviewer_ids[]=456" +``` + +### Via Git Push Options + +```bash +# Create MR on push +git push -o merge_request.create \ + -o merge_request.target=main \ + -o merge_request.title="Add feature" \ + origin feature-branch + +# Create and assign +git push -o merge_request.create \ + -o merge_request.assign="username" \ + origin feature-branch + +# Create with labels +git push -o merge_request.create \ + -o merge_request.label="bug" \ + -o merge_request.label="priority::high" \ + origin feature-branch +``` + +### Via CLI + +```bash +# Using glab +glab mr create --title "Add feature" \ + --description "Detailed description" \ + --source feature-branch \ + --target main \ + --assignee @username \ + --label bug,feature +``` + +## Merge Request Details + +### Basic Information + +```yaml +Title: Add user authentication +Description: | + Implements JWT authentication for API endpoints. + + Changes: + - Add JWT middleware + - Add auth routes + - Add user model + - Add tests + + Closes #123 +Source Branch: feature/auth +Target Branch: main +Author: @john +Assignee: @jane +Reviewers: @alice, @bob +Labels: feature, security +Milestone: v1.0 +``` + +### Closing Issues + +Link issues in description: +- `Closes #123` +- `Fixes #456` +- `Resolves #789` +- `Implements #101` + +### Description Templates + +Create `.gitlab/merge_request_templates/Default.md`: + +```markdown +## What does this MR do? + + + +## Related issues + + +Closes # + +## Author's checklist + +- [ ] Tests added +- [ ] Documentation updated +- [ ] Changelog entry added +- [ ] Reviewed by peers + +## Reviewer's checklist + +- [ ] Code follows style guide +- [ ] Tests pass +- [ ] No security issues +- [ ] Performance acceptable +``` + +## Merge Request Workflow + +### Draft/WIP Merge Requests + +Mark as draft: +```bash +# In title +Draft: Add feature +WIP: Add feature + +# Via API +curl --request PUT --header "PRIVATE-TOKEN: " \ + "https://gitlab.com/api/v4/projects/:id/merge_requests/:mr_iid" \ + --data "draft=true" +``` + +Remove draft status: +```bash +curl --request PUT --header "PRIVATE-TOKEN: " \ + "https://gitlab.com/api/v4/projects/:id/merge_requests/:mr_iid" \ + --data "draft=false" +``` + +### Assignees and Reviewers + +**Assignees**: Responsible for the MR +**Reviewers**: Review and approve the MR + +```bash +# Set assignees +curl --request PUT --header "PRIVATE-TOKEN: " \ + "https://gitlab.com/api/v4/projects/:id/merge_requests/:mr_iid" \ + --data "assignee_ids[]=123" \ + --data "assignee_ids[]=456" + +# Set reviewers +curl --request PUT --header "PRIVATE-TOKEN: " \ + "https://gitlab.com/api/v4/projects/:id/merge_requests/:mr_iid" \ + --data "reviewer_ids[]=789" +``` + +### Labels and Milestones + +```bash +# Add labels +curl --request PUT --header "PRIVATE-TOKEN: " \ + "https://gitlab.com/api/v4/projects/:id/merge_requests/:mr_iid" \ + --data "labels=bug,feature,priority::high" + +# Set milestone +curl --request PUT --header "PRIVATE-TOKEN: " \ + "https://gitlab.com/api/v4/projects/:id/merge_requests/:mr_iid" \ + --data "milestone_id=10" +``` + +## Code Review + +### Comments and Discussions + +**Add comment**: +```bash +curl --request POST --header "PRIVATE-TOKEN: " \ + "https://gitlab.com/api/v4/projects/:id/merge_requests/:mr_iid/notes" \ + --data "body=Looks good!" +``` + +**Add line comment**: +```bash +curl --request POST --header "PRIVATE-TOKEN: " \ + "https://gitlab.com/api/v4/projects/:id/merge_requests/:mr_iid/discussions" \ + --data "body=Consider refactoring this" \ + --data "position[base_sha]=abc123" \ + --data "position[start_sha]=def456" \ + --data "position[head_sha]=ghi789" \ + --data "position[new_path]=src/file.js" \ + --data "position[new_line]=42" \ + --data "position[position_type]=text" +``` + +### Resolvable Discussions + +Mark discussions as resolvable: +- Reviewer creates resolvable discussion +- Author resolves after fixing +- Must resolve all before merging (if required) + +```bash +# Resolve discussion +curl --request PUT --header "PRIVATE-TOKEN: " \ + "https://gitlab.com/api/v4/projects/:id/merge_requests/:mr_iid/discussions/:discussion_id" \ + --data "resolved=true" +``` + +### Suggestions + +Apply code suggestions in reviews: + +```markdown +```suggestion +const result = calculate(x, y); +return result * 2; +``` +``` + +Apply suggestion: +- Click "Apply suggestion" button +- Or use API to apply + +**Batch apply suggestions**: +1. Select multiple suggestions +2. Click "Apply X suggestions" +3. Commit with single commit + +### Code Quality Reports + +View code quality changes: +- New issues introduced +- Resolved issues +- Code quality score + +Requires CI/CD job with code quality report: +```yaml +code_quality: + image: docker:stable + services: + - docker:stable-dind + script: + - docker run --env SOURCE_CODE="$PWD" --volume "$PWD":/code \ + --volume /var/run/docker.sock:/var/run/docker.sock \ + registry.gitlab.com/gitlab-org/ci-cd/codequality:latest /code + artifacts: + reports: + codequality: gl-code-quality-report.json +``` + +## Merge Request Approvals + +### Configure Approval Rules + +**Via UI**: +1. Project Settings > Merge requests > Approval rules +2. Add rule +3. Set required approvers +4. Set approval count + +**Via API**: +```bash +curl --request POST --header "PRIVATE-TOKEN: " \ + "https://gitlab.com/api/v4/projects/:id/approval_rules" \ + --data "name=Security Review" \ + --data "approvals_required=2" \ + --data "user_ids[]=123" \ + --data "user_ids[]=456" +``` + +### Approve Merge Request + +```bash +curl --request POST --header "PRIVATE-TOKEN: " \ + "https://gitlab.com/api/v4/projects/:id/merge_requests/:mr_iid/approve" +``` + +### Unapprove + +```bash +curl --request POST --header "PRIVATE-TOKEN: " \ + "https://gitlab.com/api/v4/projects/:id/merge_requests/:mr_iid/unapprove" +``` + +### Approval Settings + +**Require approvals**: +- Number of approvals needed +- Eligible approvers +- Approval groups +- Code owner approvals + +**Prevent self-approval**: +- Author cannot approve own MR + +**Prevent committer approval**: +- Users who committed cannot approve + +**Require new approvals when pushed**: +- Reset approvals on new commits + +## Merge Strategies + +### Merge Commit + +Creates merge commit with two parents: +``` + C---D feature + / \ +A---B-------E main +``` + +### Fast-Forward Merge + +No merge commit when possible: +``` +A---B---C---D main + (was feature) +``` + +### Squash and Merge + +Combines all commits into one: +``` +A---B---C main + (squashed feature commits) +``` + +**Configure in MR**: +```bash +curl --request PUT --header "PRIVATE-TOKEN: " \ + "https://gitlab.com/api/v4/projects/:id/merge_requests/:mr_iid" \ + --data "squash=true" +``` + +### Semi-Linear Merge + +Rebase then merge (no fast-forward): +``` + C'--D' feature + / \ +A---B-------E main +``` + +## Merging + +### Merge When Pipeline Succeeds + +Automatically merge after pipeline passes: + +```bash +curl --request PUT --header "PRIVATE-TOKEN: " \ + "https://gitlab.com/api/v4/projects/:id/merge_requests/:mr_iid/merge" \ + --data "merge_when_pipeline_succeeds=true" \ + --data "should_remove_source_branch=true" +``` + +### Manual Merge + +```bash +curl --request PUT --header "PRIVATE-TOKEN: " \ + "https://gitlab.com/api/v4/projects/:id/merge_requests/:mr_iid/merge" \ + --data "squash=true" \ + --data "should_remove_source_branch=true" \ + --data "merge_commit_message=Custom merge message" +``` + +### Merge Trains + +Queue merges to prevent breaking main branch: + +**Enable merge trains**: +1. Project Settings > Merge requests +2. Enable "Merged results pipelines" +3. Enable "Merge trains" + +Benefits: +- Serialized merging +- Pipeline runs against merged result +- Prevents breaking main branch +- Automatic rebase + +## Merge Conflicts + +### View Conflicts + +```bash +curl --header "PRIVATE-TOKEN: " \ + "https://gitlab.com/api/v4/projects/:id/merge_requests/:mr_iid/conflicts" +``` + +### Resolve Conflicts via UI + +1. View MR with conflicts +2. Click "Resolve conflicts" +3. Choose conflict resolution +4. Commit resolution + +### Resolve Conflicts Locally + +```bash +# Fetch latest changes +git fetch origin + +# Checkout source branch +git checkout feature-branch + +# Merge or rebase +git merge origin/main +# or +git rebase origin/main + +# Resolve conflicts in files +# Add resolved files +git add . + +# Complete merge/rebase +git commit # for merge +git rebase --continue # for rebase + +# Push +git push origin feature-branch +``` + +## Merge Request Diff + +### View Diff + +```bash +curl --header "PRIVATE-TOKEN: " \ + "https://gitlab.com/api/v4/projects/:id/merge_requests/:mr_iid/changes" +``` + +### Diff Options + +- **Inline diff**: Side-by-side comparison +- **Side-by-side diff**: Parallel view +- **File by file**: Navigate file by file +- **Ignore whitespace**: Skip whitespace changes + +### Download Patch + +```bash +curl --header "PRIVATE-TOKEN: " \ + "https://gitlab.com/api/v4/projects/:id/merge_requests/:mr_iid.patch" \ + -o merge_request.patch +``` + +### Download Diff + +```bash +curl --header "PRIVATE-TOKEN: " \ + "https://gitlab.com/api/v4/projects/:id/merge_requests/:mr_iid.diff" \ + -o merge_request.diff +``` + +## Merge Request Pipelines + +### Pipeline for Merged Results + +Test merge result before merging: + +**.gitlab-ci.yml**: +```yaml +workflow: + rules: + - if: $CI_MERGE_REQUEST_ID + when: always + - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH + when: always +``` + +### Require Pipeline Success + +Configure in project settings: +- Pipelines must succeed +- Required successful jobs + +## Merge Request Dependencies + +### Depend on Another MR + +Use cross-references in description: +```markdown +Depends on !123 +Blocked by !456 +``` + +## Merge Request Metrics + +### View Metrics + +```bash +curl --header "PRIVATE-TOKEN: " \ + "https://gitlab.com/api/v4/projects/:id/merge_requests/:mr_iid" +``` + +**Metrics include**: +- Time to merge +- First comment time +- First approve time +- Review time +- Diff stats (additions/deletions) + +### Merge Request Analytics + +**Project level**: +- Average time to merge +- Throughput +- Open MRs over time + +**Group level** (Premium/Ultimate): +- Cross-project metrics +- Team performance +- Bottleneck identification + +## Merge Request Automation + +### Quick Actions + +Use in comments: +- `/approve` - Approve MR +- `/merge` - Merge MR +- `/close` - Close MR +- `/reopen` - Reopen MR +- `/assign @user` - Assign to user +- `/unassign` - Remove assignee +- `/label ~bug` - Add label +- `/unlabel ~bug` - Remove label +- `/milestone %v1.0` - Set milestone +- `/due 2025-12-31` - Set due date +- `/estimate 2h` - Set time estimate +- `/spend 1h` - Add time spent +- `/draft` - Mark as draft +- `/ready` - Remove draft status + +### Merge Request Webhooks + +Trigger automation on MR events: + +```yaml +# Example CI/CD trigger +review-app: + script: + - deploy-review-app.sh + only: + - merge_requests + environment: + name: review/$CI_MERGE_REQUEST_IID + url: https://$CI_MERGE_REQUEST_IID.review.example.com + on_stop: stop-review-app +``` + +## Code Owners + +### CODEOWNERS File + +Create `.gitlab/CODEOWNERS`: + +``` +# Backend team owns all .rb files +*.rb @backend-team + +# Frontend team owns React components +/src/components/ @frontend-team + +# DevOps owns CI/CD config +/.gitlab-ci.yml @devops +/docker/ @devops + +# Security team must approve sensitive files +/config/secrets/ @security-team +/lib/auth/ @security-team + +# Everyone in group must approve +/docs/ @group/docs-team +``` + +**Syntax**: +- `path @user` - User approval +- `path @group` - Group approval +- `path @group/subgroup` - Subgroup approval + +### Require Code Owner Approval + +1. Project Settings > Merge requests +2. Enable "Require approval from code owners" +3. Code owners must approve changes to their files + +## Merge Request Templates + +### Create Templates + +Create in `.gitlab/merge_request_templates/`: + +**Feature.md**: +```markdown +## Feature Description + + + +## Implementation Details + + + +## Testing + +- [ ] Unit tests added +- [ ] Integration tests added +- [ ] Manual testing completed + +## Screenshots + + + +## Related Issues + +Closes # +``` + +**Bugfix.md**: +```markdown +## Bug Description + + + +## Root Cause + + + +## Solution + + + +## Testing + +- [ ] Bug reproduced +- [ ] Fix verified +- [ ] Regression tests added + +Fixes # +``` + +### Use Template + +```bash +# Create MR with template +curl --request POST --header "PRIVATE-TOKEN: " \ + "https://gitlab.com/api/v4/projects/:id/merge_requests" \ + --data "source_branch=feature" \ + --data "target_branch=main" \ + --data "title=Add feature" \ + --data "description=$(cat .gitlab/merge_request_templates/Feature.md)" +``` + +## Best Practices + +### 1. Merge Request Size + +- Keep MRs focused and small +- Aim for < 400 lines of changes +- Split large features into multiple MRs +- Easier to review and test + +### 2. Title and Description + +- Clear, descriptive title +- Explain "why" not just "what" +- Link related issues +- Include screenshots/videos +- Checklist for requirements + +### 3. Commits + +- Atomic commits +- Descriptive commit messages +- Consider squashing for cleaner history +- Sign commits for verification + +### 4. Review Process + +- Request specific reviewers +- Respond to all comments +- Resolve discussions +- Re-request review after changes +- Thank reviewers + +### 5. Testing + +- Add/update tests +- Ensure CI passes +- Manual testing in review app +- Performance testing if needed + +### 6. Documentation + +- Update relevant docs +- Add API documentation +- Update CHANGELOG +- Include inline comments + +### 7. Communication + +- Use discussions for questions +- Mark as draft while working +- Update status regularly +- Notify when ready for review + +## CLI Examples + +### Using glab + +```bash +# View MRs +glab mr list +glab mr list --assignee @me +glab mr list --state merged + +# View MR details +glab mr view 123 + +# Create MR +glab mr create + +# Checkout MR +glab mr checkout 123 + +# Approve MR +glab mr approve 123 + +# Merge MR +glab mr merge 123 + +# Close MR +glab mr close 123 + +# View MR diff +glab mr diff 123 +``` + +## API Examples + +### Python + +```python +import gitlab + +gl = gitlab.Gitlab('https://gitlab.com', private_token='token') +project = gl.projects.get('group/project') + +# Create MR +mr = project.mergerequests.create({ + 'source_branch': 'feature', + 'target_branch': 'main', + 'title': 'Add feature', + 'description': 'Description', + 'assignee_id': 123 +}) + +# Get MR +mr = project.mergerequests.get(1) + +# Add comment +mr.notes.create({'body': 'Looks good!'}) + +# Approve +mr.approve() + +# Merge +mr.merge() +``` + +### JavaScript + +```javascript +const { Gitlab } = require('@gitbeaker/node'); +const api = new Gitlab({ token: 'token' }); + +// Create MR +const mr = await api.MergeRequests.create('group/project', 'feature', 'main', 'Add feature'); + +// Get MR +const mr = await api.MergeRequests.show('group/project', 1); + +// Add comment +await api.MergeRequestNotes.create('group/project', 1, 'Looks good!'); + +// Approve +await api.MergeRequests.approve('group/project', 1); + +// Merge +await api.MergeRequests.accept('group/project', 1, { + should_remove_source_branch: true, + squash: true +}); +``` + +## Additional Resources + +- Merge Requests Documentation: https://docs.gitlab.com/ee/user/project/merge_requests/ +- Code Review Guidelines: https://docs.gitlab.com/ee/development/code_review.html +- Approval Rules: https://docs.gitlab.com/ee/user/project/merge_requests/approvals/ +- Code Owners: https://docs.gitlab.com/ee/user/project/code_owners.html diff --git a/skills/gitlab/references/package-registry.md b/skills/gitlab/references/package-registry.md new file mode 100644 index 0000000..b741cbe --- /dev/null +++ b/skills/gitlab/references/package-registry.md @@ -0,0 +1,441 @@ +# GitLab Package Registry Reference + +## Overview + +GitLab Package Registry allows you to publish and share packages within your organization. Supports multiple package formats. + +## Supported Package Types + +- **Maven** (Java) +- **npm** (JavaScript/Node.js) +- **PyPI** (Python) +- **NuGet** (.NET) +- **Composer** (PHP) +- **Conan** (C/C++) +- **Go Modules** +- **Helm Charts** (Kubernetes) +- **Terraform Modules** +- **Generic packages** +- **RubyGems** (Ruby) +- **Debian** packages +- **RPM** packages + +## npm Packages + +### Publish npm Package + +**.npmrc**: +``` +@scope:registry=https://gitlab.com/api/v4/projects/${CI_PROJECT_ID}/packages/npm/ +//gitlab.com/api/v4/projects/${CI_PROJECT_ID}/packages/npm/:_authToken=${CI_JOB_TOKEN} +``` + +**package.json**: +```json +{ + "name": "@scope/package-name", + "version": "1.0.0", + "publishConfig": { + "@scope:registry": "https://gitlab.com/api/v4/projects/${CI_PROJECT_ID}/packages/npm/" + } +} +``` + +**.gitlab-ci.yml**: +```yaml +publish: + image: node:18 + script: + - echo "@scope:registry=https://gitlab.com/api/v4/projects/${CI_PROJECT_ID}/packages/npm/" > .npmrc + - echo "//gitlab.com/api/v4/projects/${CI_PROJECT_ID}/packages/npm/:_authToken=${CI_JOB_TOKEN}" >> .npmrc + - npm publish + only: + - tags +``` + +### Install npm Package + +```bash +# Configure registry +npm config set @scope:registry https://gitlab.com/api/v4/projects/PROJECT_ID/packages/npm/ + +# With auth token +npm config set -- '//gitlab.com/api/v4/projects/PROJECT_ID/packages/npm/:_authToken' "TOKEN" + +# Install +npm install @scope/package-name +``` + +## Maven Packages + +### Publish Maven Package + +**pom.xml**: +```xml + + + gitlab-maven + https://gitlab.com/api/v4/projects/PROJECT_ID/packages/maven + + + + + + gitlab-maven + https://gitlab.com/api/v4/projects/PROJECT_ID/packages/maven + + + gitlab-maven + https://gitlab.com/api/v4/projects/PROJECT_ID/packages/maven + + +``` + +**settings.xml**: +```xml + + + + gitlab-maven + + + + Job-Token + ${env.CI_JOB_TOKEN} + + + + + + +``` + +**.gitlab-ci.yml**: +```yaml +deploy: + image: maven:3-openjdk-11 + script: + - mvn deploy -s ci_settings.xml + only: + - tags +``` + +## PyPI Packages + +### Publish Python Package + +**.pypirc**: +```ini +[distutils] +index-servers = + gitlab + +[gitlab] +repository = https://gitlab.com/api/v4/projects/${CI_PROJECT_ID}/packages/pypi +username = gitlab-ci-token +password = ${CI_JOB_TOKEN} +``` + +**.gitlab-ci.yml**: +```yaml +publish: + image: python:3.11 + script: + - pip install twine build + - python -m build + - TWINE_PASSWORD=${CI_JOB_TOKEN} TWINE_USERNAME=gitlab-ci-token python -m twine upload --repository-url https://gitlab.com/api/v4/projects/${CI_PROJECT_ID}/packages/pypi dist/* + only: + - tags +``` + +### Install Python Package + +```bash +# pip install +pip install --index-url https://__token__:${PERSONAL_ACCESS_TOKEN}@gitlab.com/api/v4/projects/PROJECT_ID/packages/pypi/simple package-name + +# requirements.txt +--index-url https://__token__:${PERSONAL_ACCESS_TOKEN}@gitlab.com/api/v4/projects/PROJECT_ID/packages/pypi/simple +package-name==1.0.0 +``` + +## NuGet Packages + +### Publish NuGet Package + +**.gitlab-ci.yml**: +```yaml +publish: + stage: deploy + image: mcr.microsoft.com/dotnet/sdk:7.0 + script: + - dotnet pack -c Release + - dotnet nuget add source "https://gitlab.com/api/v4/projects/${CI_PROJECT_ID}/packages/nuget/index.json" --name gitlab --username gitlab-ci-token --password ${CI_JOB_TOKEN} --store-password-in-clear-text + - dotnet nuget push "bin/Release/*.nupkg" --source gitlab + only: + - tags +``` + +### Install NuGet Package + +```bash +# Add source +dotnet nuget add source "https://gitlab.com/api/v4/projects/PROJECT_ID/packages/nuget/index.json" --name gitlab --username USERNAME --password TOKEN + +# Install +dotnet add package PackageName +``` + +## Composer Packages (PHP) + +### Publish Composer Package + +**composer.json**: +```json +{ + "name": "vendor/package-name", + "version": "1.0.0", + "type": "library", + "repositories": [ + { + "type": "composer", + "url": "https://gitlab.com/api/v4/group/GROUP_ID/-/packages/composer/packages.json" + } + ] +} +``` + +**.gitlab-ci.yml**: +```yaml +publish: + image: curlimages/curl:latest + script: + - 'curl --header "Job-Token: ${CI_JOB_TOKEN}" --data tag=${CI_COMMIT_TAG} "https://gitlab.com/api/v4/projects/${CI_PROJECT_ID}/packages/composer"' + only: + - tags +``` + +## Generic Packages + +### Publish Generic Package + +```bash +curl --header "JOB-TOKEN: ${CI_JOB_TOKEN}" \ + --upload-file path/to/file.zip \ + "https://gitlab.com/api/v4/projects/${CI_PROJECT_ID}/packages/generic/package-name/1.0.0/file.zip" +``` + +**.gitlab-ci.yml**: +```yaml +upload: + stage: deploy + script: + - 'curl --header "JOB-TOKEN: ${CI_JOB_TOKEN}" --upload-file myfile.zip "${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/generic/mypackage/1.0.0/myfile.zip"' +``` + +### Download Generic Package + +```bash +curl --header "PRIVATE-TOKEN: " \ + "https://gitlab.com/api/v4/projects/PROJECT_ID/packages/generic/package-name/1.0.0/file.zip" \ + -o file.zip +``` + +## Helm Charts + +### Publish Helm Chart + +**.gitlab-ci.yml**: +```yaml +publish: + image: alpine/helm:latest + script: + - helm package chart/ + - 'curl --request POST --user gitlab-ci-token:${CI_JOB_TOKEN} --form "chart=@mychart-1.0.0.tgz" "${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/helm/api/stable/charts"' + only: + - tags +``` + +### Install Helm Chart + +```bash +# Add repo +helm repo add --username --password gitlab https://gitlab.com/api/v4/projects/PROJECT_ID/packages/helm/stable + +# Install +helm install myrelease gitlab/mychart +``` + +## Terraform Modules + +### Publish Terraform Module + +**Project structure**: +``` +terraform-module/ +├── main.tf +├── variables.tf +├── outputs.tf +└── README.md +``` + +**.gitlab-ci.yml**: +```yaml +upload: + image: curlimages/curl:latest + script: + - tar -czf module.tar.gz * + - 'curl --header "JOB-TOKEN: ${CI_JOB_TOKEN}" --upload-file module.tar.gz "${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/terraform/modules/my-module/my-system/1.0.0/file"' + only: + - tags +``` + +### Use Terraform Module + +```hcl +module "example" { + source = "gitlab.com/group/project//modules/my-module" + version = "1.0.0" +} +``` + +## Conan Packages (C/C++) + +### Publish Conan Package + +**.gitlab-ci.yml**: +```yaml +publish: + image: conanio/gcc11 + script: + - conan remote add gitlab https://gitlab.com/api/v4/projects/${CI_PROJECT_ID}/packages/conan + - conan user ci_user -r gitlab -p ${CI_JOB_TOKEN} + - conan create . mycompany/stable + - conan upload "*" --all -r gitlab --confirm + only: + - tags +``` + +## Go Modules + +### Publish Go Module + +**Automatic**: Just create a Git tag + +```bash +git tag v1.0.0 +git push origin v1.0.0 +``` + +**Use module**: +```go +import "gitlab.com/group/project/module" +``` + +```bash +go get gitlab.com/group/project/module@v1.0.0 +``` + +**Configure private modules**: +```bash +git config --global url."https://oauth2:${GITLAB_TOKEN}@gitlab.com".insteadOf "https://gitlab.com" +export GOPRIVATE="gitlab.com/group/*" +``` + +## Package Management API + +### List Packages + +```bash +curl --header "PRIVATE-TOKEN: " \ + "https://gitlab.com/api/v4/projects/PROJECT_ID/packages" +``` + +### Get Package + +```bash +curl --header "PRIVATE-TOKEN: " \ + "https://gitlab.com/api/v4/projects/PROJECT_ID/packages/PACKAGE_ID" +``` + +### Delete Package + +```bash +curl --request DELETE --header "PRIVATE-TOKEN: " \ + "https://gitlab.com/api/v4/projects/PROJECT_ID/packages/PACKAGE_ID" +``` + +### List Package Files + +```bash +curl --header "PRIVATE-TOKEN: " \ + "https://gitlab.com/api/v4/projects/PROJECT_ID/packages/PACKAGE_ID/package_files" +``` + +## Package Cleanup Policies + +### Configure Cleanup + +```bash +curl --request PUT --header "PRIVATE-TOKEN: " \ + "https://gitlab.com/api/v4/projects/PROJECT_ID" \ + --data "packages_cleanup_policy_attributes[keep_n_duplicated_package_files]=10" \ + --data "packages_cleanup_policy_attributes[older_than]=90d" +``` + +## Best Practices + +### 1. Versioning + +- Use semantic versioning (SemVer) +- Tag releases properly +- Maintain changelog +- Document breaking changes + +### 2. Security + +- Use deploy tokens for CI/CD +- Rotate tokens regularly +- Scan packages for vulnerabilities +- Sign packages when possible + +### 3. Organization + +- Use consistent naming +- Group related packages +- Document package usage +- Maintain package metadata + +### 4. CI/CD Integration + +```yaml +stages: + - build + - test + - publish + +build: + stage: build + script: + - npm run build + +test: + stage: test + script: + - npm test + +publish: + stage: publish + script: + - npm publish + only: + - tags + when: manual +``` + +## Additional Resources + +- Package Registry Docs: https://docs.gitlab.com/ee/user/packages/package_registry/ +- npm Registry: https://docs.gitlab.com/ee/user/packages/npm_registry/ +- Maven Repository: https://docs.gitlab.com/ee/user/packages/maven_repository/ +- PyPI Repository: https://docs.gitlab.com/ee/user/packages/pypi_repository/ diff --git a/skills/gitlab/references/projects.md b/skills/gitlab/references/projects.md new file mode 100644 index 0000000..21cb11b --- /dev/null +++ b/skills/gitlab/references/projects.md @@ -0,0 +1,749 @@ +# GitLab Projects Reference + +## Overview + +Projects in GitLab contain your source code, issues, merge requests, and CI/CD pipelines. They are the central hub for all development activity. + +## Project Creation + +### Via UI + +1. Click "+" dropdown > "New project/repository" +2. Choose creation method: + - Create blank project + - Create from template + - Import project + - Run CI/CD for external repository +3. Configure project settings +4. Click "Create project" + +### Via API + +```bash +curl --request POST --header "PRIVATE-TOKEN: " \ + --header "Content-Type: application/json" \ + --data '{ + "name": "My Project", + "description": "Project description", + "visibility": "private", + "initialize_with_readme": true, + "namespace_id": 123 + }' \ + "https://gitlab.com/api/v4/projects" +``` + +### Via CLI + +```bash +# Using glab CLI +glab repo create my-project --description "My project" --private +``` + +## Project Settings + +### General Settings + +**Project Name and Description**: +- Name: Display name +- Description: Project overview +- Project avatar: Custom image +- Topics: Searchable tags + +**Visibility**: +- Private: Only project members +- Internal: Any authenticated user +- Public: Everyone (including anonymous) + +**Project URL**: +- Namespace: Group or user +- Project slug: URL-friendly name + +### Merge Request Settings + +**Merge method**: +- Merge commit: Traditional merge +- Merge commit with semi-linear history: Rebase then merge +- Fast-forward merge: Only if fast-forward possible + +**Merge options**: +- Enable "Delete source branch" by default +- Enable merge request approvals +- Squash commits when merging +- Require merge request for push +- Enable merge trains + +**Merge checks**: +- Pipelines must succeed +- All threads must be resolved +- Status checks must pass + +### Feature Visibility + +Control feature availability: +- Issues +- Repository +- Merge requests +- Forks +- CI/CD +- Container Registry +- Analytics +- Requirements +- Wiki +- Snippets +- Pages +- Operations + +### Protected Branches + +```bash +# Via API +curl --request POST --header "PRIVATE-TOKEN: " \ + "https://gitlab.com/api/v4/projects/:id/protected_branches" \ + --data "name=main" \ + --data "push_access_level=40" \ + --data "merge_access_level=40" +``` + +**Access levels**: +- 0: No access +- 30: Developer +- 40: Maintainer +- 60: Admin + +**Wildcard protection**: +``` +release/* +stable-* +``` + +### Protected Tags + +```bash +curl --request POST --header "PRIVATE-TOKEN: " \ + "https://gitlab.com/api/v4/projects/:id/protected_tags" \ + --data "name=v*" \ + --data "create_access_level=40" +``` + +## Project Templates + +### Built-in Templates + +GitLab provides templates for: +- **Ruby on Rails** +- **Spring** +- **Express** +- **Hugo** +- **Jekyll** +- **Hexo** +- **GitBook** +- **Gatsby** +- **Nfancy** +- **Android** +- **iOS (Swift)** +- **TYPO3** +- **Laravel** +- **Salesforce** +- And more... + +### Custom Project Templates + +Create custom templates for your organization: + +1. Create a project with desired structure +2. Add template files and configuration +3. Mark as template (Premium/Ultimate) +4. Use in project creation + +### Project Template API + +```bash +# List templates +curl --header "PRIVATE-TOKEN: " \ + "https://gitlab.com/api/v4/projects/:id/templates/dockerfiles" + +# Get specific template +curl --header "PRIVATE-TOKEN: " \ + "https://gitlab.com/api/v4/projects/:id/templates/dockerfiles/Ruby" +``` + +Available template types: +- `dockerfiles` +- `gitignores` +- `gitlab_ci_ymls` +- `licenses` + +## Project Import/Export + +### Export Project + +**Via UI**: +1. Settings > General > Advanced +2. Export project +3. Download export file + +**Via API**: +```bash +# Start export +curl --request POST --header "PRIVATE-TOKEN: " \ + "https://gitlab.com/api/v4/projects/:id/export" + +# Check status +curl --header "PRIVATE-TOKEN: " \ + "https://gitlab.com/api/v4/projects/:id/export" + +# Download +curl --header "PRIVATE-TOKEN: " \ + "https://gitlab.com/api/v4/projects/:id/export/download" \ + --output project-export.tar.gz +``` + +### Import Project + +**Via UI**: +1. New project > Import project +2. Upload export file +3. Configure import settings +4. Import + +**Via API**: +```bash +curl --request POST --header "PRIVATE-TOKEN: " \ + --form "file=@project-export.tar.gz" \ + --form "path=imported-project" \ + "https://gitlab.com/api/v4/projects/import" +``` + +### Import from Other Sources + +**GitHub**: +```bash +curl --request POST --header "PRIVATE-TOKEN: " \ + --data "github_personal_access_token=" \ + --data "target_namespace=my-group" \ + --data "repo_id=123456" \ + "https://gitlab.com/api/v4/import/github" +``` + +**Bitbucket**: +- Via UI: New project > Import from Bitbucket +- Authenticate with Bitbucket +- Select repositories + +**GitLab.com to self-hosted**: +- Use project export/import +- Or remote mirrors + +## Project Members and Permissions + +### Member Roles + +**Guest (10)**: +- View issues, merge requests +- Leave comments +- No repository access + +**Reporter (20)**: +- Pull repository +- Download artifacts +- View CI/CD analytics + +**Developer (30)**: +- Push to repository +- Create merge requests +- Manage issues and MRs +- Run CI/CD pipelines + +**Maintainer (40)**: +- Manage project settings +- Manage team members +- Manage protected branches +- Deploy to production + +**Owner (50)**: +- Delete project +- Transfer project +- Change project visibility + +### Add Members + +**Via UI**: +1. Project > Members +2. Invite members +3. Select role and expiration +4. Send invitation + +**Via API**: +```bash +curl --request POST --header "PRIVATE-TOKEN: " \ + "https://gitlab.com/api/v4/projects/:id/members" \ + --data "user_id=123" \ + --data "access_level=30" +``` + +### Share Project with Group + +```bash +curl --request POST --header "PRIVATE-TOKEN: " \ + "https://gitlab.com/api/v4/projects/:id/share" \ + --data "group_id=456" \ + --data "group_access=30" \ + --data "expires_at=2025-12-31" +``` + +## Project Badges + +Add badges to display project information: + +```bash +# Create badge +curl --request POST --header "PRIVATE-TOKEN: " \ + "https://gitlab.com/api/v4/projects/:id/badges" \ + --data "link_url=https://example.com" \ + --data "image_url=https://example.com/badge.svg" \ + --data "name=Pipeline" +``` + +**Badge placeholders**: +- `%{project_path}`: Project path +- `%{project_id}`: Project ID +- `%{default_branch}`: Default branch +- `%{commit_sha}`: Latest commit SHA + +**Example badges**: +``` +Pipeline: https://gitlab.com/%{project_path}/badges/%{default_branch}/pipeline.svg +Coverage: https://gitlab.com/%{project_path}/badges/%{default_branch}/coverage.svg +``` + +## Project Milestones + +### Create Milestone + +```bash +curl --request POST --header "PRIVATE-TOKEN: " \ + "https://gitlab.com/api/v4/projects/:id/milestones" \ + --data "title=v1.0" \ + --data "description=First release" \ + --data "due_date=2025-12-31" \ + --data "start_date=2025-01-01" +``` + +### Milestone Burndown Chart + +Track progress with burndown charts (Premium/Ultimate): +- Issues opened vs. closed +- Weight completion +- Time remaining + +## Project Labels + +### Create Labels + +```bash +curl --request POST --header "PRIVATE-TOKEN: " \ + "https://gitlab.com/api/v4/projects/:id/labels" \ + --data "name=bug" \ + --data "color=#FF0000" \ + --data "description=Bug report" \ + --data "priority=1" +``` + +### Scoped Labels + +Create hierarchical labels: +- `status::open` +- `status::in-progress` +- `status::done` +- `priority::high` +- `priority::low` + +### Label Templates + +Use label templates for consistency: +```yaml +# .gitlab/labels.yml +- name: "bug" + color: "#FF0000" + description: "Bug report" +- name: "feature" + color: "#00FF00" + description: "Feature request" +``` + +## Project Webhooks + +Configure webhooks for external integrations: + +```bash +curl --request POST --header "PRIVATE-TOKEN: " \ + "https://gitlab.com/api/v4/projects/:id/hooks" \ + --data "url=https://example.com/webhook" \ + --data "push_events=true" \ + --data "merge_requests_events=true" \ + --data "token=secret-token" +``` + +See `webhooks.md` for detailed webhook documentation. + +## Project Integrations + +### Built-in Integrations + +**Jira**: +```bash +curl --request PUT --header "PRIVATE-TOKEN: " \ + "https://gitlab.com/api/v4/projects/:id/services/jira" \ + --data "url=https://jira.example.com" \ + --data "username=user" \ + --data "password=pass" \ + --data "project_key=PROJECT" +``` + +**Slack**: +```bash +curl --request PUT --header "PRIVATE-TOKEN: " \ + "https://gitlab.com/api/v4/projects/:id/services/slack" \ + --data "webhook=https://hooks.slack.com/services/..." \ + --data "username=GitLab" \ + --data "channel=#general" +``` + +**Other integrations**: +- Asana +- Microsoft Teams +- Discord +- Mattermost +- Jenkins +- Bamboo +- Datadog +- Prometheus + +## Project Statistics + +### Get Statistics + +```bash +curl --header "PRIVATE-TOKEN: " \ + "https://gitlab.com/api/v4/projects/:id?statistics=true" +``` + +**Available statistics**: +- Commit count +- Storage size +- Repository size +- Wiki size +- LFS objects size +- Job artifacts size +- Packages size + +### Contribution Analytics + +View contributor statistics: +- Commits per contributor +- Additions/deletions +- Date range filtering +- Export to CSV + +## Project Snippets + +### Create Snippet + +```bash +curl --request POST --header "PRIVATE-TOKEN: " \ + "https://gitlab.com/api/v4/projects/:id/snippets" \ + --data "title=Example" \ + --data "file_name=example.rb" \ + --data "content=puts 'Hello'" \ + --data "visibility=private" +``` + +### Snippet Features + +- Multiple files per snippet +- Version history +- Comments +- Cloning with git +- Embedding in web pages + +## Project Wiki + +### Enable Wiki + +```bash +curl --request PUT --header "PRIVATE-TOKEN: " \ + "https://gitlab.com/api/v4/projects/:id" \ + --data "wiki_enabled=true" +``` + +### Create Wiki Page + +```bash +curl --request POST --header "PRIVATE-TOKEN: " \ + "https://gitlab.com/api/v4/projects/:id/wikis" \ + --data "title=Home" \ + --data "content=# Welcome\n\nThis is the home page" \ + --data "format=markdown" +``` + +**Supported formats**: +- Markdown +- RDoc +- AsciiDoc +- Org + +### Clone Wiki + +```bash +git clone https://gitlab.com/group/project.wiki.git +``` + +## Project Access Tokens + +Create tokens for project automation: + +```bash +curl --request POST --header "PRIVATE-TOKEN: " \ + "https://gitlab.com/api/v4/projects/:id/access_tokens" \ + --data "name=Deploy Token" \ + --data "scopes[]=api" \ + --data "expires_at=2025-12-31" \ + --data "access_level=40" +``` + +## Project Deploy Tokens + +Special tokens for deployment: + +```bash +curl --request POST --header "PRIVATE-TOKEN: " \ + "https://gitlab.com/api/v4/projects/:id/deploy_tokens" \ + --data "name=Deploy" \ + --data "scopes[]=read_repository" \ + --data "scopes[]=read_registry" \ + --data "expires_at=2025-12-31" +``` + +## Project Mirror + +### Push Mirror + +Automatically push changes to remote repository: + +```bash +curl --request POST --header "PRIVATE-TOKEN: " \ + "https://gitlab.com/api/v4/projects/:id/remote_mirrors" \ + --data "url=https://github.com/user/repo.git" \ + --data "enabled=true" \ + --data "only_protected_branches=false" +``` + +### Pull Mirror + +Automatically pull changes from remote repository: + +```bash +curl --request PUT --header "PRIVATE-TOKEN: " \ + "https://gitlab.com/api/v4/projects/:id" \ + --data "import_url=https://github.com/user/repo.git" \ + --data "mirror=true" \ + --data "mirror_trigger_builds=true" +``` + +## Project Transfer + +### Transfer to Another Namespace + +```bash +curl --request PUT --header "PRIVATE-TOKEN: " \ + "https://gitlab.com/api/v4/projects/:id/transfer" \ + --data "namespace=new-namespace" +``` + +**Considerations**: +- Updates all URLs +- Maintains git remotes +- Preserves history +- May affect integrations + +## Project Archiving + +### Archive Project + +```bash +curl --request POST --header "PRIVATE-TOKEN: " \ + "https://gitlab.com/api/v4/projects/:id/archive" +``` + +**Effects**: +- Read-only mode +- No new commits/issues/MRs +- Maintains visibility +- Can be unarchived + +### Unarchive Project + +```bash +curl --request POST --header "PRIVATE-TOKEN: " \ + "https://gitlab.com/api/v4/projects/:id/unarchive" +``` + +## Project Deletion + +### Delete Project + +```bash +curl --request DELETE --header "PRIVATE-TOKEN: " \ + "https://gitlab.com/api/v4/projects/:id" +``` + +**Delayed deletion** (Premium/Ultimate): +- 7-day grace period +- Recoverable within period +- Permanent after period + +### Restore Deleted Project + +```bash +curl --request POST --header "PRIVATE-TOKEN: " \ + "https://gitlab.com/api/v4/projects/:id/restore" +``` + +## Project Best Practices + +### 1. Organization + +- Use descriptive names +- Add comprehensive README +- Document setup process +- Maintain CHANGELOG +- Include LICENSE file + +### 2. Branch Protection + +- Protect main/master branch +- Require merge requests +- Enable status checks +- Require approvals +- Prevent force push + +### 3. Access Control + +- Use least privilege +- Regular access reviews +- Remove inactive users +- Use deploy tokens for CI/CD +- Enable 2FA requirement + +### 4. Documentation + +- Keep README updated +- Document API endpoints +- Include examples +- Maintain wiki +- Use issue templates + +### 5. Automation + +- Set up CI/CD pipelines +- Automate testing +- Configure automatic merges +- Use scheduled pipelines +- Implement code quality checks + +### 6. Security + +- Enable security scanning +- Review dependencies +- Configure secret detection +- Use protected variables +- Regular security audits + +### 7. Performance + +- Archive old projects +- Clean up old branches +- Manage repository size +- Optimize CI/CD pipelines +- Use caching effectively + +## Project CLI Management + +### Using glab + +```bash +# View project +glab repo view group/project + +# Clone project +glab repo clone group/project + +# Fork project +glab repo fork group/project + +# Archive project +glab repo archive group/project + +# View project issues +glab issue list + +# View merge requests +glab mr list +``` + +## Project API Examples + +### Python + +```python +import gitlab + +gl = gitlab.Gitlab('https://gitlab.com', private_token='token') + +# Get project +project = gl.projects.get('group/project') + +# Update project +project.description = 'New description' +project.save() + +# Add member +project.members.create({ + 'user_id': 123, + 'access_level': gitlab.const.DEVELOPER_ACCESS +}) + +# Create label +project.labels.create({ + 'name': 'bug', + 'color': '#FF0000' +}) +``` + +### JavaScript + +```javascript +const { Gitlab } = require('@gitbeaker/node'); + +const api = new Gitlab({ + token: 'personal-access-token', +}); + +// Get project +const project = await api.Projects.show('group/project'); + +// Update project +await api.Projects.edit('group/project', { + description: 'New description', +}); + +// Add member +await api.ProjectMembers.add('group/project', 123, 30); +``` + +## Additional Resources + +- Project Settings: https://docs.gitlab.com/ee/user/project/settings/ +- Project Import/Export: https://docs.gitlab.com/ee/user/project/settings/import_export.html +- Project Templates: https://docs.gitlab.com/ee/user/project/working_with_projects.html#project-templates +- Protected Branches: https://docs.gitlab.com/ee/user/project/protected_branches.html diff --git a/skills/gitlab/references/runners.md b/skills/gitlab/references/runners.md new file mode 100644 index 0000000..c36c0da --- /dev/null +++ b/skills/gitlab/references/runners.md @@ -0,0 +1,769 @@ +# GitLab Runners Reference + +## Overview + +GitLab Runner is an application that works with GitLab CI/CD to run jobs in a pipeline. Runners can be installed on various platforms and execute jobs in different executors (Docker, Shell, Kubernetes, etc.). + +## Runner Types + +### Shared Runners +- Available to all projects in a GitLab instance +- Configured by administrators +- Uses shared resources + +### Group Runners +- Available to all projects in a group +- Configured at the group level +- Shared within an organization + +### Project Runners (Specific Runners) +- Dedicated to a single project +- Complete control over configuration +- Isolated execution environment + +## Installation + +### Linux + +```bash +# Download the binary +sudo curl -L --output /usr/local/bin/gitlab-runner https://gitlab-runner-downloads.s3.amazonaws.com/latest/binaries/gitlab-runner-linux-amd64 + +# Give it permissions to execute +sudo chmod +x /usr/local/bin/gitlab-runner + +# Create a GitLab CI user +sudo useradd --comment 'GitLab Runner' --create-home gitlab-runner --shell /bin/bash + +# Install and run as service +sudo gitlab-runner install --user=gitlab-runner --working-directory=/home/gitlab-runner +sudo gitlab-runner start +``` + +### macOS + +```bash +# Download +sudo curl --output /usr/local/bin/gitlab-runner https://gitlab-runner-downloads.s3.amazonaws.com/latest/binaries/gitlab-runner-darwin-amd64 + +# Give permissions +sudo chmod +x /usr/local/bin/gitlab-runner + +# Install +cd ~ +gitlab-runner install +gitlab-runner start +``` + +### Windows + +```powershell +# Create a folder +New-Item -Path 'C:\GitLab-Runner' -ItemType Directory + +# Download binary +Invoke-WebRequest -Uri "https://gitlab-runner-downloads.s3.amazonaws.com/latest/binaries/gitlab-runner-windows-amd64.exe" -OutFile "C:\GitLab-Runner\gitlab-runner.exe" + +# Install as service +cd C:\GitLab-Runner +.\gitlab-runner.exe install +.\gitlab-runner.exe start +``` + +### Docker + +```bash +docker run -d --name gitlab-runner --restart always \ + -v /srv/gitlab-runner/config:/etc/gitlab-runner \ + -v /var/run/docker.sock:/var/run/docker.sock \ + gitlab/gitlab-runner:latest +``` + +### Kubernetes (Helm) + +```bash +# Add GitLab Helm repository +helm repo add gitlab https://charts.gitlab.io + +# Update +helm repo update + +# Install +helm install --namespace gitlab-runner gitlab-runner \ + -f values.yaml \ + gitlab/gitlab-runner +``` + +## Registration + +### Register Runner + +```bash +gitlab-runner register \ + --non-interactive \ + --url "https://gitlab.com/" \ + --registration-token "PROJECT_REGISTRATION_TOKEN" \ + --executor "docker" \ + --docker-image alpine:latest \ + --description "docker-runner" \ + --maintenance-note "Free-form maintainer notes about this runner" \ + --tag-list "docker,aws" \ + --run-untagged="true" \ + --locked="false" \ + --access-level="not_protected" +``` + +### Interactive Registration + +```bash +gitlab-runner register + +# Follow prompts: +# 1. GitLab instance URL +# 2. Registration token +# 3. Description +# 4. Tags +# 5. Executor type +# 6. Executor-specific questions +``` + +### Get Registration Token + +**Project Runners**: +- Navigate to: Settings > CI/CD > Runners +- Copy registration token + +**Group Runners**: +- Navigate to: Group > Settings > CI/CD > Runners +- Copy registration token + +**Instance Runners** (Admin): +- Navigate to: Admin Area > Overview > Runners +- Copy registration token + +### Unregister Runner + +```bash +# By URL and token +gitlab-runner unregister --url "https://gitlab.com/" --token "RUNNER_TOKEN" + +# By name +gitlab-runner unregister --name "test-runner" + +# Unregister all +gitlab-runner unregister --all-runners +``` + +## Executors + +### Shell Executor + +Runs jobs directly on the host machine. + +```toml +[[runners]] + name = "shell-runner" + url = "https://gitlab.com/" + token = "RUNNER_TOKEN" + executor = "shell" +``` + +**Pros**: +- Simple setup +- Fast execution +- Easy debugging + +**Cons**: +- Less isolation +- Environment pollution +- Security concerns + +### Docker Executor + +Runs jobs in Docker containers (recommended). + +```toml +[[runners]] + name = "docker-runner" + url = "https://gitlab.com/" + token = "RUNNER_TOKEN" + executor = "docker" + [runners.docker] + tls_verify = false + image = "alpine:latest" + privileged = false + disable_cache = false + volumes = ["/cache"] + shm_size = 0 +``` + +**Pros**: +- Clean environment per job +- Isolated execution +- Consistent builds +- Easy image management + +**Cons**: +- Requires Docker +- Slight overhead + +### Docker Machine Executor + +Auto-scales runners using Docker Machine. + +```toml +[[runners]] + name = "docker-machine-runner" + url = "https://gitlab.com/" + token = "RUNNER_TOKEN" + executor = "docker+machine" + [runners.docker] + image = "alpine:latest" + [runners.machine] + IdleCount = 2 + IdleTime = 1800 + MaxBuilds = 100 + MachineDriver = "amazonec2" + MachineName = "gitlab-runner-%s" + MachineOptions = [ + "amazonec2-access-key=XXXX", + "amazonec2-secret-key=XXXX", + "amazonec2-region=us-east-1", + "amazonec2-vpc-id=vpc-xxxxx", + "amazonec2-subnet-id=subnet-xxxxx", + "amazonec2-use-private-address=true", + "amazonec2-instance-type=t2.micro" + ] +``` + +### Kubernetes Executor + +Runs jobs in Kubernetes pods. + +```toml +[[runners]] + name = "kubernetes-runner" + url = "https://gitlab.com/" + token = "RUNNER_TOKEN" + executor = "kubernetes" + [runners.kubernetes] + host = "" + namespace = "gitlab-runner" + privileged = false + cpu_limit = "1" + memory_limit = "1Gi" + service_cpu_limit = "200m" + service_memory_limit = "256Mi" + helper_cpu_limit = "200m" + helper_memory_limit = "256Mi" + poll_interval = 3 + poll_timeout = 180 + [runners.kubernetes.pod_labels] + "app" = "gitlab-runner" +``` + +### VirtualBox Executor + +```toml +[[runners]] + name = "virtualbox-runner" + url = "https://gitlab.com/" + token = "RUNNER_TOKEN" + executor = "virtualbox" + [runners.virtualbox] + base_name = "my-vm" + base_snapshot = "my-snapshot" +``` + +### SSH Executor + +```toml +[[runners]] + name = "ssh-runner" + url = "https://gitlab.com/" + token = "RUNNER_TOKEN" + executor = "ssh" + [runners.ssh] + host = "example.com" + port = "22" + user = "gitlab-runner" + password = "password" + identity_file = "/home/user/.ssh/id_rsa" +``` + +## Configuration + +### Configuration File Location + +- **Linux**: `/etc/gitlab-runner/config.toml` +- **macOS**: `~/.gitlab-runner/config.toml` +- **Windows**: `C:\GitLab-Runner\config.toml` +- **Docker**: `/etc/gitlab-runner/config.toml` (inside container) + +### Basic Configuration + +```toml +concurrent = 4 # Max concurrent jobs +check_interval = 0 # Check for new jobs (0 = default 3s) + +[session_server] + session_timeout = 1800 + +[[runners]] + name = "my-runner" + url = "https://gitlab.com/" + token = "RUNNER_TOKEN" + executor = "docker" + [runners.custom_build_dir] + enabled = true + [runners.cache] + Type = "s3" + Path = "runner" + Shared = true + [runners.cache.s3] + ServerAddress = "s3.amazonaws.com" + AccessKey = "XXXX" + SecretKey = "XXXX" + BucketName = "runners-cache" + BucketLocation = "us-east-1" + [runners.docker] + image = "alpine:latest" + privileged = false + disable_entrypoint_overwrite = false + oom_kill_disable = false + disable_cache = false + volumes = ["/cache"] + shm_size = 0 +``` + +### Docker Executor Configuration + +```toml +[[runners]] + name = "docker-runner" + url = "https://gitlab.com/" + token = "RUNNER_TOKEN" + executor = "docker" + [runners.docker] + # Default Docker image + image = "alpine:latest" + + # Run containers in privileged mode + privileged = false + + # Disable cache + disable_cache = false + + # Volumes to mount + volumes = ["/cache", "/var/run/docker.sock:/var/run/docker.sock"] + + # Shared memory size + shm_size = 0 + + # Pull policy + pull_policy = ["if-not-present"] + + # Network mode + network_mode = "bridge" + + # DNS servers + dns = ["8.8.8.8"] + + # CPU limit + cpus = "2" + + # Memory limit + memory = "2g" + + # Memory swap limit + memory_swap = "4g" + + # OOM kill disable + oom_kill_disable = false + + # Extra hosts + extra_hosts = ["gitlab.example.com:192.168.1.1"] + + # Helper image + helper_image = "gitlab/gitlab-runner-helper:latest" + + # Allow services + allowed_images = ["ruby:*", "python:*"] + allowed_services = ["postgres:*", "redis:*"] +``` + +## Runner Management + +### Commands + +```bash +# Start runner +gitlab-runner start + +# Stop runner +gitlab-runner stop + +# Restart runner +gitlab-runner restart + +# Status +gitlab-runner status + +# Verify configuration +gitlab-runner verify + +# List runners +gitlab-runner list + +# Run single job +gitlab-runner run-single + +# View version +gitlab-runner --version +``` + +### Update Runner + +```bash +# Linux +sudo gitlab-runner stop +sudo curl -L --output /usr/local/bin/gitlab-runner https://gitlab-runner-downloads.s3.amazonaws.com/latest/binaries/gitlab-runner-linux-amd64 +sudo chmod +x /usr/local/bin/gitlab-runner +sudo gitlab-runner start + +# Docker +docker pull gitlab/gitlab-runner:latest +docker stop gitlab-runner && docker rm gitlab-runner +# Re-create container with same configuration +``` + +### Runner API Management + +**List runners**: +```bash +curl --header "PRIVATE-TOKEN: " "https://gitlab.com/api/v4/runners" +``` + +**Get runner details**: +```bash +curl --header "PRIVATE-TOKEN: " "https://gitlab.com/api/v4/runners/:id" +``` + +**Update runner**: +```bash +curl --request PUT --header "PRIVATE-TOKEN: " \ + "https://gitlab.com/api/v4/runners/:id" \ + --form "description=new-description" \ + --form "active=true" \ + --form "tag_list=docker,aws" +``` + +**Delete runner**: +```bash +curl --request DELETE --header "PRIVATE-TOKEN: " \ + "https://gitlab.com/api/v4/runners/:id" +``` + +## Caching + +### Local Cache + +```toml +[[runners]] + executor = "docker" + [runners.cache] + Type = "local" + Path = "/cache" + Shared = true + [runners.cache.local] +``` + +### S3 Cache + +```toml +[[runners]] + executor = "docker" + [runners.cache] + Type = "s3" + Path = "runner" + Shared = true + [runners.cache.s3] + ServerAddress = "s3.amazonaws.com" + AccessKey = "AWS_ACCESS_KEY" + SecretKey = "AWS_SECRET_KEY" + BucketName = "runners-cache" + BucketLocation = "us-east-1" +``` + +### Google Cloud Storage Cache + +```toml +[[runners]] + executor = "docker" + [runners.cache] + Type = "gcs" + Path = "runner" + Shared = true + [runners.cache.gcs] + AccessID = "cache-access-account@test-project-123456.iam.gserviceaccount.com" + PrivateKey = "/path/to/key.json" + BucketName = "runners-cache" +``` + +### Azure Cache + +```toml +[[runners]] + executor = "docker" + [runners.cache] + Type = "azure" + Path = "runner" + Shared = true + [runners.cache.azure] + AccountName = "account-name" + AccountKey = "account-key" + ContainerName = "runners-cache" +``` + +## Security + +### Protected Runners + +- Only run jobs on protected branches/tags +- Configure in project/group settings +- Prevents unauthorized job execution + +```toml +[[runners]] + # Runner configuration + access_level = "ref_protected" +``` + +### Locked Runners + +- Prevents runner from being enabled for other projects +- Useful for dedicated project runners + +**Via UI**: Runner settings > Lock to current projects + +**Via API**: +```bash +curl --request PUT --header "PRIVATE-TOKEN: " \ + "https://gitlab.com/api/v4/runners/:id" \ + --form "locked=true" +``` + +### Runner Tags + +Control which runners execute which jobs: + +**.gitlab-ci.yml**: +```yaml +job-name: + tags: + - docker + - linux + script: + - echo "Running on tagged runner" +``` + +**Runner configuration**: +```toml +[[runners]] + name = "docker-linux-runner" + # Tags assigned during registration or via config +``` + +### Privileged Mode + +Allows Docker-in-Docker but has security implications: + +```toml +[[runners]] + executor = "docker" + [runners.docker] + privileged = true # Use with caution +``` + +**Security considerations**: +- Container escapes possible +- Host system access +- Use only when necessary +- Prefer rootless Docker + +## Monitoring + +### Prometheus Metrics + +Enable metrics in config: + +```toml +listen_address = ":9252" +``` + +**Metrics endpoint**: `http://runner-host:9252/metrics` + +**Key metrics**: +- `gitlab_runner_jobs` - Running jobs +- `gitlab_runner_job_duration_seconds` - Job duration +- `gitlab_runner_errors_total` - Error count +- `gitlab_runner_api_request_statuses_total` - API request statuses + +### Logging + +```toml +# Logging level +log_level = "info" # debug, info, warning, error, fatal, panic + +# Log format +log_format = "text" # text or json +``` + +**View logs**: +```bash +# Service logs (systemd) +sudo journalctl -u gitlab-runner -f + +# Docker logs +docker logs -f gitlab-runner +``` + +## Troubleshooting + +### Common Issues + +**1. Runner not picking up jobs** +- Check runner is registered: `gitlab-runner verify` +- Verify runner is active in GitLab UI +- Check tags match job requirements +- Ensure runner has capacity (concurrent jobs) + +**2. Docker executor issues** +- Verify Docker is running: `docker ps` +- Check Docker socket permissions +- Ensure Docker image is accessible +- Check network connectivity + +**3. Permission errors** +- Check gitlab-runner user permissions +- Verify working directory permissions +- Check cache directory permissions + +**4. Out of disk space** +- Clear old Docker images: `docker system prune -a` +- Check cache size +- Monitor disk usage + +**5. SSL certificate errors** +- Add certificate to system: `gitlab-runner register --tls-ca-file=/path/to/ca.crt` +- Or disable verification (not recommended): `tls_verify = false` + +### Debug Mode + +```bash +# Run in debug mode +gitlab-runner --debug run + +# Single job debug +gitlab-runner --debug run-single +``` + +### Health Check + +```bash +# Verify runner can connect to GitLab +gitlab-runner verify + +# Delete invalid runners +gitlab-runner verify --delete +``` + +## Best Practices + +### 1. Runner Configuration + +- Use Docker executor for isolation +- Configure appropriate concurrent jobs limit +- Set up distributed cache (S3, GCS) +- Use helper image from registry +- Configure resource limits + +### 2. Security + +- Use protected runners for sensitive jobs +- Lock runners to specific projects +- Avoid privileged mode when possible +- Rotate runner tokens regularly +- Use runner tags effectively + +### 3. Performance + +- Enable distributed caching +- Use local Docker image cache +- Configure appropriate timeout values +- Monitor runner metrics +- Scale runners based on load + +### 4. Maintenance + +- Keep runners updated +- Monitor disk space +- Clean up old Docker images +- Review runner logs regularly +- Set up alerts for runner failures + +### 5. High Availability + +- Deploy multiple runners +- Use auto-scaling (Docker Machine, Kubernetes) +- Configure health checks +- Implement monitoring +- Plan for failover + +## Auto-Scaling + +### Docker Machine Auto-Scaling + +```toml +[[runners]] + limit = 10 # Maximum runners + executor = "docker+machine" + + [runners.machine] + IdleCount = 2 # Idle machines to keep + IdleTime = 600 # Seconds before removing idle machine + MaxBuilds = 100 # Max builds per machine before removal + MachineDriver = "amazonec2" + MachineName = "gitlab-docker-machine-%s" + MachineOptions = [ + "amazonec2-instance-type=t2.medium", + "amazonec2-region=us-east-1", + "amazonec2-vpc-id=vpc-xxxxx", + "amazonec2-subnet-id=subnet-xxxxx", + "amazonec2-zone=a", + "amazonec2-security-group=gitlab-runner" + ] + + # Periods when auto-scaling is active + [[runners.machine.autoscaling]] + Periods = ["* * 9-17 * * mon-fri *"] # Business hours + IdleCount = 5 + IdleTime = 600 + Timezone = "America/New_York" + + [[runners.machine.autoscaling]] + Periods = ["* * * * * sat,sun *"] # Weekends + IdleCount = 1 + IdleTime = 300 +``` + +### Kubernetes Auto-Scaling + +Kubernetes handles scaling automatically based on resource requests. + +## Additional Resources + +- Official Runner Documentation: https://docs.gitlab.com/runner/ +- Runner Executors: https://docs.gitlab.com/runner/executors/ +- Advanced Configuration: https://docs.gitlab.com/runner/configuration/ +- Auto-scaling: https://docs.gitlab.com/runner/configuration/autoscale.html diff --git a/skills/gitlab/references/security.md b/skills/gitlab/references/security.md new file mode 100644 index 0000000..6d423c5 --- /dev/null +++ b/skills/gitlab/references/security.md @@ -0,0 +1,657 @@ +# GitLab Security Features Reference + +## Overview + +GitLab provides comprehensive security scanning and vulnerability management built into the CI/CD pipeline. + +## Security Scanning Types + +### 1. SAST (Static Application Security Testing) + +Analyzes source code for security vulnerabilities. + +**.gitlab-ci.yml**: +```yaml +include: + - template: Security/SAST.gitlab-ci.yml + +variables: + SAST_EXCLUDED_PATHS: "spec, test, tests, tmp" +``` + +**Supported languages**: +- JavaScript/TypeScript +- Python +- Ruby +- Java +- C/C++ +- Go +- PHP +- C#/.NET +- Scala +- And more... + +**Custom configuration**: +```yaml +sast: + variables: + SEARCH_MAX_DEPTH: 20 + SAST_ANALYZER_IMAGE_TAG: "latest" + SAST_DISABLE_BABEL: "true" +``` + +### 2. DAST (Dynamic Application Security Testing) + +Tests running applications for vulnerabilities. + +```yaml +include: + - template: Security/DAST.gitlab-ci.yml + +variables: + DAST_WEBSITE: https://example.com + DAST_AUTH_URL: https://example.com/login + DAST_USERNAME: testuser + DAST_PASSWORD: $DAST_PASSWORD + DAST_FULL_SCAN_ENABLED: "true" +``` + +**DAST Configuration**: +```yaml +dast: + stage: test + variables: + DAST_API_SPECIFICATION: openapi.json + DAST_API_HOST_OVERRIDE: https://api.example.com + dast_configuration: + site_profile: "Production Site" + scanner_profile: "Full Scan" +``` + +**Browser-based DAST**: +```yaml +include: + - template: DAST-On-Demand-Scan.gitlab-ci.yml + +dast: + variables: + DAST_BROWSER_SCAN: "true" + DAST_TARGET_AVAILABILITY_TIMEOUT: 120 +``` + +### 3. Dependency Scanning + +Checks dependencies for known vulnerabilities. + +```yaml +include: + - template: Security/Dependency-Scanning.gitlab-ci.yml + +variables: + DS_EXCLUDED_PATHS: "spec, test, tests, tmp" + DS_JAVA_VERSION: 11 +``` + +**Supported package managers**: +- npm/yarn (JavaScript) +- pip/pipenv (Python) +- bundler (Ruby) +- Maven/Gradle (Java) +- Go modules +- Composer (PHP) +- NuGet (.NET) +- CocoaPods (iOS) + +**Custom analyzer**: +```yaml +dependency_scanning: + variables: + DS_ANALYZER_IMAGE: "registry.gitlab.com/security-products/gemnasium:latest" + DS_ANALYZER_IMAGE_TAG: "2" +``` + +### 4. Container Scanning + +Scans Docker images for vulnerabilities. + +```yaml +include: + - template: Security/Container-Scanning.gitlab-ci.yml + +variables: + CS_IMAGE: $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA + CS_DOCKERFILE_PATH: Dockerfile +``` + +**Scan custom registry**: +```yaml +container_scanning: + variables: + CS_REGISTRY_USER: $CI_REGISTRY_USER + CS_REGISTRY_PASSWORD: $CI_REGISTRY_PASSWORD + CS_IMAGE: registry.example.com/image:tag +``` + +### 5. Secret Detection + +Prevents committing secrets to repository. + +```yaml +include: + - template: Security/Secret-Detection.gitlab-ci.yml + +variables: + SECRET_DETECTION_EXCLUDED_PATHS: "tests/, spec/" +``` + +**Detected secrets**: +- AWS credentials +- API keys +- OAuth tokens +- Private keys +- Passwords +- Database credentials +- And more... + +**Custom rules**: +```yaml +secret_detection: + variables: + SECRET_DETECTION_HISTORIC_SCAN: "true" + SECRET_DETECTION_LOG_OPTIONS: "--all --full-history" +``` + +### 6. License Compliance + +Identifies licenses in dependencies. + +```yaml +include: + - template: Security/License-Scanning.gitlab-ci.yml + +license_scanning: + variables: + LICENSE_FINDER_CLI_OPTS: '--aggregate-paths=. --decisions-file=.license_decisions.yml' +``` + +**License policies**: +```yaml +# .license_decisions.yml +allowed: + - MIT + - Apache-2.0 + - BSD-3-Clause +denied: + - GPL-2.0 + - GPL-3.0 +``` + +### 7. Coverage-Guided Fuzz Testing + +Tests application with random inputs. + +```yaml +include: + - template: Security/Coverage-Fuzzing.gitlab-ci.yml + +my-fuzz-target: + extends: .fuzz_base + script: + - ./gitlab-cov-fuzz run -- ./fuzz-target +``` + +### 8. API Security Testing + +Tests API endpoints for vulnerabilities. + +```yaml +include: + - template: Security/API-Security.gitlab-ci.yml + +variables: + DAST_API_SPECIFICATION: openapi.json + DAST_API_TARGET_URL: https://api.example.com +``` + +## Security Dashboard + +### Project Security Dashboard + +View vulnerabilities at project level: +- Critical, High, Medium, Low severities +- Vulnerability trends +- Status (Detected, Confirmed, Dismissed, Resolved) +- Fix recommendations + +### Group Security Dashboard + +Aggregate view across projects (Ultimate): +- Cross-project vulnerabilities +- Priority vulnerabilities +- Compliance status +- Export capabilities + +### Vulnerability Management + +**Create vulnerability**: +```bash +curl --request POST --header "PRIVATE-TOKEN: " \ + "https://gitlab.com/api/v4/projects/:id/vulnerabilities" \ + --data "title=SQL Injection" \ + --data "severity=critical" \ + --data "state=detected" \ + --data "description=Details..." +``` + +**Update vulnerability**: +```bash +curl --request PATCH --header "PRIVATE-TOKEN: " \ + "https://gitlab.com/api/v4/vulnerabilities/:id" \ + --data "state=confirmed" +``` + +**States**: +- `detected`: Newly detected +- `confirmed`: Verified as real +- `dismissed`: False positive/accepted risk +- `resolved`: Fixed + +## Security Policies + +### Scan Execution Policies + +Enforce security scans on projects: + +```yaml +# .gitlab/security-policies/policy.yml +scan_execution_policy: + - name: Enforce SAST and Dependency Scanning + description: Run security scans on all branches + enabled: true + rules: + - type: pipeline + branches: + - main + - develop + - release/* + actions: + - scan: sast + - scan: dependency_scanning + - scan: secret_detection +``` + +### Scan Result Policies + +Control merge based on scan results: + +```yaml +scan_result_policy: + - name: Block merge on critical vulnerabilities + description: Prevent merging if critical vulnerabilities found + enabled: true + rules: + - type: scan_finding + branches: + - main + scanners: + - sast + - dependency_scanning + severity_levels: + - critical + vulnerability_states: + - newly_detected + actions: + - type: require_approval + approvals_required: 2 + role_approvers: + - security +``` + +## Vulnerability Reports + +### Generate Reports + +Security scanners generate JSON reports: + +```yaml +sast: + artifacts: + reports: + sast: gl-sast-report.json + +dependency_scanning: + artifacts: + reports: + dependency_scanning: gl-dependency-scanning-report.json + +container_scanning: + artifacts: + reports: + container_scanning: gl-container-scanning-report.json +``` + +### Report Format + +```json +{ + "version": "15.0.0", + "vulnerabilities": [ + { + "id": "...", + "category": "sast", + "name": "SQL Injection", + "message": "Potential SQL injection", + "description": "...", + "cve": "CVE-2021-12345", + "severity": "Critical", + "confidence": "High", + "scanner": { + "id": "semgrep", + "name": "Semgrep" + }, + "location": { + "file": "app/controllers/users_controller.rb", + "start_line": 42, + "end_line": 45 + }, + "identifiers": [ + { + "type": "cwe", + "name": "CWE-89", + "value": "89", + "url": "https://cwe.mitre.org/data/definitions/89.html" + } + ], + "links": [ + { + "url": "https://owasp.org/www-community/attacks/SQL_Injection" + } + ], + "solution": "Use parameterized queries" + } + ], + "remediations": [], + "dependency_files": [] +} +``` + +## Compliance Features + +### Compliance Framework + +Define compliance requirements (Ultimate): + +```yaml +compliance_framework: + name: "SOC 2" + description: "SOC 2 compliance requirements" + color: "#1aaa55" + default: false + pipeline_configuration_full_path: ".gitlab/compliance/soc2.yml" +``` + +### Compliance Pipeline + +```yaml +# .gitlab/compliance/soc2.yml +include: + - template: Security/SAST.gitlab-ci.yml + - template: Security/Dependency-Scanning.gitlab-ci.yml + - template: Security/License-Scanning.gitlab-ci.yml + +compliance_audit: + stage: test + script: + - audit-compliance.sh + rules: + - if: $CI_COMMIT_BRANCH == "main" +``` + +### Audit Events + +Track security-related activities: + +```bash +# Get audit events +curl --header "PRIVATE-TOKEN: " \ + "https://gitlab.com/api/v4/projects/:id/audit_events" + +# Group audit events +curl --header "PRIVATE-TOKEN: " \ + "https://gitlab.com/api/v4/groups/:id/audit_events" +``` + +**Tracked events**: +- Member additions/removals +- Permission changes +- Protected branch changes +- Security scan results +- Compliance violations +- And more... + +## Security Best Practices + +### 1. Enable All Scanners + +```yaml +include: + - template: Security/SAST.gitlab-ci.yml + - template: Security/Dependency-Scanning.gitlab-ci.yml + - template: Security/Secret-Detection.gitlab-ci.yml + - template: Security/Container-Scanning.gitlab-ci.yml + - template: Security/License-Scanning.gitlab-ci.yml +``` + +### 2. Block Merges on Critical Issues + +Configure merge request approvals: +- Require approval from security team +- Block merges with critical vulnerabilities +- Require all security checks to pass + +### 3. Regular Dependency Updates + +```yaml +dependency_update: + stage: maintain + script: + - bundle update + - npm update + rules: + - if: $CI_PIPELINE_SOURCE == "schedule" + only: + variables: + - $DEPENDENCY_UPDATE == "true" +``` + +### 4. Secret Management + +Use CI/CD variables for secrets: +- Mark as protected +- Mark as masked +- Limit scope to specific environments +- Rotate regularly + +```bash +# Add protected variable +curl --request POST --header "PRIVATE-TOKEN: " \ + "https://gitlab.com/api/v4/projects/:id/variables" \ + --data "key=SECRET_KEY" \ + --data "value=secret_value" \ + --data "protected=true" \ + --data "masked=true" \ + --data "environment_scope=production" +``` + +### 5. Two-Factor Authentication + +Enforce 2FA for all users: +- Group settings > General > Permissions +- Require 2FA for all group members +- Set grace period for enablement + +### 6. IP Allowlisting + +Restrict access by IP (Premium/Ultimate): + +```bash +curl --request PUT --header "PRIVATE-TOKEN: " \ + "https://gitlab.com/api/v4/groups/:id" \ + --data "ip_restriction_ranges[]=192.168.1.0/24" \ + --data "ip_restriction_ranges[]=10.0.0.0/8" +``` + +### 7. Security Training + +GitLab provides security training: +- Secure coding practices +- OWASP Top 10 +- Security testing +- Vulnerability remediation + +## Security Integrations + +### SIEM Integration + +Export audit logs to SIEM: + +**Splunk**: +```yaml +# .gitlab-ci.yml +export_to_splunk: + script: + - curl -k https://splunk.example.com:8088/services/collector \ + -H "Authorization: Splunk $SPLUNK_TOKEN" \ + -d '{"event": $AUDIT_DATA}' +``` + +**ELK Stack**: +```yaml +export_to_elk: + script: + - | + curl -X POST "https://elasticsearch.example.com:9200/gitlab-audit/_doc" \ + -H "Content-Type: application/json" \ + -d "$AUDIT_DATA" +``` + +### Vulnerability Management Tools + +Integrate with external tools: +- Jira for vulnerability tracking +- ServiceNow for incident management +- PagerDuty for security alerts + +## Security API + +### List Vulnerabilities + +```bash +curl --header "PRIVATE-TOKEN: " \ + "https://gitlab.com/api/v4/vulnerabilities?project_id=:id&severity=critical" +``` + +### Dismiss Vulnerability + +```bash +curl --request POST --header "PRIVATE-TOKEN: " \ + "https://gitlab.com/api/v4/vulnerabilities/:id/dismiss" \ + --data "dismissal_reason=acceptable_risk" \ + --data "comment=Risk accepted by security team" +``` + +### Resolve Vulnerability + +```bash +curl --request POST --header "PRIVATE-TOKEN: " \ + "https://gitlab.com/api/v4/vulnerabilities/:id/resolve" +``` + +## Security Hardening + +### Runner Security + +```toml +[[runners]] + [runners.docker] + privileged = false + disable_cache = false + volumes = ["/cache"] + + # Security settings + security_opt = ["no-new-privileges"] + cap_drop = ["ALL"] + cap_add = ["NET_BIND_SERVICE"] +``` + +### Registry Security + +```yaml +registry: + storage_delete: + enabled: true + validation: + manifests: + urls: + allow: + - ^https://registry\.gitlab\.com/ +``` + +### Git Security + +```ruby +# /etc/gitlab/gitlab.rb +gitlab_rails['allowed_hosts'] = ['gitlab.example.com'] +gitlab_rails['gitlab_shell_ssh_port'] = 2222 +gitlab_rails['gitlab_shell_git_timeout'] = 800 +``` + +## Incident Response + +### Security Incident Template + +```.markdown +# Security Incident: [TITLE] + +## Severity +- [ ] Critical +- [ ] High +- [ ] Medium +- [ ] Low + +## Detection +- Date/Time: +- Method: +- Reporter: + +## Description +[Detailed description] + +## Impact +[Affected systems/data] + +## Response Actions +- [ ] Contain threat +- [ ] Assess damage +- [ ] Notify stakeholders +- [ ] Remediate vulnerability +- [ ] Document lessons learned + +## Timeline +| Time | Action | +|------|--------| +| | | + +## Root Cause + +## Remediation + +## Prevention +``` + +## Additional Resources + +- Security Documentation: https://docs.gitlab.com/ee/user/application_security/ +- Security Scanners: https://docs.gitlab.com/ee/user/application_security/security_scanner_integration/ +- Vulnerability Management: https://docs.gitlab.com/ee/user/application_security/vulnerabilities/ +- Compliance: https://docs.gitlab.com/ee/administration/compliance.html diff --git a/skills/gitlab/references/webhooks.md b/skills/gitlab/references/webhooks.md new file mode 100644 index 0000000..7b8c7dd --- /dev/null +++ b/skills/gitlab/references/webhooks.md @@ -0,0 +1,773 @@ +# GitLab Webhooks Reference + +## Overview + +GitLab webhooks allow external services to be notified when certain events happen in GitLab. When triggered, GitLab sends an HTTP POST request with event data to the configured URL. + +## Webhook Configuration + +### Creating a Webhook + +**Via UI**: Project Settings > Webhooks + +**Via API**: +``` +POST /projects/:id/hooks +``` + +Parameters: +- `url` (required): The webhook URL +- `token`: Secret token for request verification +- `push_events`: Trigger on push events (default: true) +- `tag_push_events`: Trigger on tag push events +- `issues_events`: Trigger on issue events +- `confidential_issues_events`: Trigger on confidential issue events +- `merge_requests_events`: Trigger on merge request events +- `wiki_page_events`: Trigger on wiki page events +- `deployment_events`: Trigger on deployment events +- `job_events`: Trigger on job events +- `pipeline_events`: Trigger on pipeline events +- `releases_events`: Trigger on release events +- `enable_ssl_verification`: Verify SSL certificates (default: true) + +### Webhook Security + +**Secret Token**: Include a secret token to verify requests: + +```python +import hmac +import hashlib + +def verify_webhook(payload, signature, secret): + expected = hmac.new( + secret.encode(), + payload.encode(), + hashlib.sha256 + ).hexdigest() + return hmac.compare_digest(expected, signature) +``` + +**Request Headers**: +- `X-Gitlab-Token`: Contains the secret token (if configured) +- `X-Gitlab-Event`: Event type name +- `User-Agent`: GitLab/{version} + +## Event Types + +### Push Events + +**Trigger**: Code pushed to repository + +**Event Header**: `X-Gitlab-Event: Push Hook` + +**Payload Structure**: +```json +{ + "object_kind": "push", + "event_name": "push", + "before": "95790bf891e76fee5e1747ab589903a6a1f80f22", + "after": "da1560886d4f094c3e6c9ef40349f7d38b5d27d7", + "ref": "refs/heads/main", + "checkout_sha": "da1560886d4f094c3e6c9ef40349f7d38b5d27d7", + "user_id": 4, + "user_name": "John Doe", + "user_username": "jdoe", + "user_email": "john@example.com", + "user_avatar": "https://gitlab.com/uploads/user/avatar/4/avatar.jpg", + "project_id": 15, + "project": { + "id": 15, + "name": "My Project", + "description": "Project description", + "web_url": "https://gitlab.com/namespace/project", + "avatar_url": null, + "git_ssh_url": "git@gitlab.com:namespace/project.git", + "git_http_url": "https://gitlab.com/namespace/project.git", + "namespace": "Namespace", + "visibility_level": 0, + "path_with_namespace": "namespace/project", + "default_branch": "main" + }, + "commits": [ + { + "id": "da1560886d4f094c3e6c9ef40349f7d38b5d27d7", + "message": "Fix bug in authentication", + "title": "Fix bug in authentication", + "timestamp": "2025-01-15T12:30:00+00:00", + "url": "https://gitlab.com/namespace/project/-/commit/da1560886d4f094c3e6c9ef40349f7d38b5d27d7", + "author": { + "name": "John Doe", + "email": "john@example.com" + }, + "added": ["new-file.txt"], + "modified": ["existing-file.txt"], + "removed": ["old-file.txt"] + } + ], + "total_commits_count": 1, + "repository": { + "name": "My Project", + "url": "git@gitlab.com:namespace/project.git", + "description": "Project description", + "homepage": "https://gitlab.com/namespace/project" + } +} +``` + +### Tag Push Events + +**Trigger**: Tag created or deleted + +**Event Header**: `X-Gitlab-Event: Tag Push Hook` + +**Payload Structure**: +```json +{ + "object_kind": "tag_push", + "event_name": "tag_push", + "before": "0000000000000000000000000000000000000000", + "after": "82b3d5ae55f7080f1e6022629cdb57bfae7cccc7", + "ref": "refs/tags/v1.0.0", + "checkout_sha": "82b3d5ae55f7080f1e6022629cdb57bfae7cccc7", + "user_id": 4, + "user_name": "John Doe", + "user_avatar": "https://gitlab.com/uploads/user/avatar/4/avatar.jpg", + "project_id": 15, + "project": { /* project details */ }, + "commits": [ /* commit details */ ], + "total_commits_count": 0, + "repository": { /* repository details */ } +} +``` + +**Note**: `before` is all zeros for tag creation, `after` is all zeros for tag deletion. + +### Issue Events + +**Trigger**: Issue created, updated, closed, or reopened + +**Event Header**: `X-Gitlab-Event: Issue Hook` + +**Payload Structure**: +```json +{ + "object_kind": "issue", + "event_type": "issue", + "user": { + "id": 4, + "name": "John Doe", + "username": "jdoe", + "avatar_url": "https://gitlab.com/uploads/user/avatar/4/avatar.jpg", + "email": "john@example.com" + }, + "project": { /* project details */ }, + "object_attributes": { + "id": 301, + "title": "Bug in login feature", + "assignee_ids": [5, 6], + "assignee_id": 5, + "author_id": 4, + "project_id": 15, + "created_at": "2025-01-15 12:30:00 UTC", + "updated_at": "2025-01-15 13:00:00 UTC", + "position": 0, + "branch_name": null, + "description": "Login page crashes when...", + "milestone_id": 10, + "state": "opened", + "state_id": 1, + "iid": 23, + "url": "https://gitlab.com/namespace/project/-/issues/23", + "action": "open", + "labels": [ + { + "id": 100, + "title": "bug", + "color": "#FF0000", + "project_id": 15, + "created_at": "2024-01-01 00:00:00 UTC", + "updated_at": "2024-01-01 00:00:00 UTC" + } + ] + }, + "assignees": [ + { + "id": 5, + "name": "Jane Smith", + "username": "jsmith", + "avatar_url": "https://gitlab.com/uploads/user/avatar/5/avatar.jpg" + } + ], + "labels": [ /* label details */ ], + "changes": { + "updated_at": { + "previous": "2025-01-15 12:30:00 UTC", + "current": "2025-01-15 13:00:00 UTC" + }, + "title": { + "previous": "Old title", + "current": "Bug in login feature" + } + } +} +``` + +**Action Values**: `open`, `update`, `close`, `reopen` + +### Merge Request Events + +**Trigger**: MR created, updated, merged, or closed + +**Event Header**: `X-Gitlab-Event: Merge Request Hook` + +**Payload Structure**: +```json +{ + "object_kind": "merge_request", + "event_type": "merge_request", + "user": { /* user details */ }, + "project": { /* project details */ }, + "object_attributes": { + "id": 99, + "iid": 1, + "target_branch": "main", + "source_branch": "feature-branch", + "source_project_id": 15, + "author_id": 4, + "assignee_ids": [5], + "assignee_id": 5, + "reviewer_ids": [6, 7], + "title": "Add new authentication feature", + "created_at": "2025-01-15 12:00:00 UTC", + "updated_at": "2025-01-15 14:00:00 UTC", + "milestone_id": 10, + "state": "opened", + "state_id": 1, + "merge_status": "can_be_merged", + "target_project_id": 15, + "description": "This MR adds...", + "url": "https://gitlab.com/namespace/project/-/merge_requests/1", + "source": { /* source project */ }, + "target": { /* target project */ }, + "last_commit": { + "id": "da1560886d4f094c3e6c9ef40349f7d38b5d27d7", + "message": "Update authentication", + "timestamp": "2025-01-15T13:30:00+00:00", + "url": "https://gitlab.com/namespace/project/-/commit/da1560886d4f094c3e6c9ef40349f7d38b5d27d7", + "author": { + "name": "John Doe", + "email": "john@example.com" + } + }, + "work_in_progress": false, + "draft": false, + "action": "open", + "assignee": { /* assignee details */ }, + "labels": [ /* label details */ ] + }, + "labels": [ /* label details */ ], + "changes": { /* changed attributes */ }, + "assignees": [ /* assignee details */ ], + "reviewers": [ /* reviewer details */ ] +} +``` + +**Action Values**: `open`, `update`, `close`, `reopen`, `merge`, `approved`, `unapproved` + +### Pipeline Events + +**Trigger**: Pipeline status changes + +**Event Header**: `X-Gitlab-Event: Pipeline Hook` + +**Payload Structure**: +```json +{ + "object_kind": "pipeline", + "object_attributes": { + "id": 31, + "ref": "main", + "tag": false, + "sha": "bcbb5ec396a2c0f828686f14fac9b80b780504f2", + "before_sha": "bcbb5ec396a2c0f828686f14fac9b80b780504f2", + "source": "push", + "status": "success", + "detailed_status": "passed", + "stages": ["build", "test", "deploy"], + "created_at": "2025-01-15 12:00:00 UTC", + "finished_at": "2025-01-15 12:15:00 UTC", + "duration": 900, + "queued_duration": 10, + "variables": [ + { + "key": "ENVIRONMENT", + "value": "production" + } + ] + }, + "merge_request": { + "id": 1, + "iid": 1, + "title": "Add feature", + "source_branch": "feature-branch", + "source_project_id": 15, + "target_branch": "main", + "target_project_id": 15, + "state": "opened", + "merge_status": "can_be_merged", + "url": "https://gitlab.com/namespace/project/-/merge_requests/1" + }, + "user": { /* user details */ }, + "project": { /* project details */ }, + "commit": { + "id": "bcbb5ec396a2c0f828686f14fac9b80b780504f2", + "message": "Add new feature", + "title": "Add new feature", + "timestamp": "2025-01-15T11:55:00+00:00", + "url": "https://gitlab.com/namespace/project/-/commit/bcbb5ec396a2c0f828686f14fac9b80b780504f2", + "author": { + "name": "John Doe", + "email": "john@example.com" + } + }, + "builds": [ + { + "id": 380, + "stage": "test", + "name": "unit-tests", + "status": "success", + "created_at": "2025-01-15 12:00:00 UTC", + "started_at": "2025-01-15 12:01:00 UTC", + "finished_at": "2025-01-15 12:05:00 UTC", + "duration": 240, + "queued_duration": 60, + "when": "on_success", + "manual": false, + "allow_failure": false, + "user": { /* user details */ }, + "runner": { + "id": 1, + "description": "runner-1", + "active": true, + "runner_type": "project_type", + "is_shared": false, + "tags": ["docker", "linux"] + }, + "artifacts_file": { + "filename": "artifacts.zip", + "size": 1024000 + }, + "environment": null + } + ] +} +``` + +**Status Values**: `pending`, `running`, `success`, `failed`, `canceled`, `skipped` + +### Job Events + +**Trigger**: Job status changes + +**Event Header**: `X-Gitlab-Event: Job Hook` + +**Payload Structure**: +```json +{ + "object_kind": "build", + "ref": "main", + "tag": false, + "before_sha": "95790bf891e76fee5e1747ab589903a6a1f80f22", + "sha": "da1560886d4f094c3e6c9ef40349f7d38b5d27d7", + "build_id": 380, + "build_name": "unit-tests", + "build_stage": "test", + "build_status": "success", + "build_created_at": "2025-01-15 12:00:00 UTC", + "build_started_at": "2025-01-15 12:01:00 UTC", + "build_finished_at": "2025-01-15 12:05:00 UTC", + "build_duration": 240, + "build_queued_duration": 60, + "build_allow_failure": false, + "build_failure_reason": "unknown_failure", + "pipeline_id": 31, + "runner": { + "id": 1, + "description": "runner-1", + "runner_type": "project_type", + "active": true, + "is_shared": false, + "tags": ["docker", "linux"] + }, + "project_id": 15, + "project_name": "My Project", + "user": { /* user details */ }, + "commit": { /* commit details */ }, + "repository": { /* repository details */ }, + "environment": { + "name": "production", + "action": "start", + "deployment_tier": "production" + } +} +``` + +**Build Status Values**: `pending`, `running`, `success`, `failed`, `canceled` + +### Deployment Events + +**Trigger**: Deployment created or status changes + +**Event Header**: `X-Gitlab-Event: Deployment Hook` + +**Payload Structure**: +```json +{ + "object_kind": "deployment", + "status": "success", + "status_changed_at": "2025-01-15 12:20:00 UTC", + "deployment_id": 15, + "deployable_id": 380, + "deployable_url": "https://gitlab.com/namespace/project/-/jobs/380", + "environment": "production", + "environment_tier": "production", + "environment_slug": "production", + "environment_external_url": "https://prod.example.com", + "project": { /* project details */ }, + "short_sha": "da156088", + "user": { /* user details */ }, + "user_url": "https://gitlab.com/jdoe", + "commit_url": "https://gitlab.com/namespace/project/-/commit/da1560886d4f094c3e6c9ef40349f7d38b5d27d7", + "commit_title": "Deploy to production" +} +``` + +**Status Values**: `created`, `running`, `success`, `failed`, `canceled` + +### Wiki Page Events + +**Trigger**: Wiki page created, updated, or deleted + +**Event Header**: `X-Gitlab-Event: Wiki Page Hook` + +**Payload Structure**: +```json +{ + "object_kind": "wiki_page", + "user": { /* user details */ }, + "project": { /* project details */ }, + "wiki": { + "web_url": "https://gitlab.com/namespace/project/-/wikis/home", + "git_ssh_url": "git@gitlab.com:namespace/project.wiki.git", + "git_http_url": "https://gitlab.com/namespace/project.wiki.git", + "path_with_namespace": "namespace/project", + "default_branch": "main" + }, + "object_attributes": { + "title": "Home", + "content": "# Welcome\n\nThis is the home page...", + "format": "markdown", + "message": "Update home page", + "slug": "home", + "url": "https://gitlab.com/namespace/project/-/wikis/home", + "action": "update" + } +} +``` + +**Action Values**: `create`, `update`, `delete` + +### Release Events + +**Trigger**: Release created, updated, or deleted + +**Event Header**: `X-Gitlab-Event: Release Hook` + +**Payload Structure**: +```json +{ + "id": 1, + "created_at": "2025-01-15 12:00:00 UTC", + "description": "Release notes for v1.0.0", + "name": "Version 1.0.0", + "released_at": "2025-01-15 12:00:00 UTC", + "tag": "v1.0.0", + "object_kind": "release", + "project": { /* project details */ }, + "url": "https://gitlab.com/namespace/project/-/releases/v1.0.0", + "action": "create", + "assets": { + "count": 2, + "links": [ + { + "id": 1, + "external": true, + "link_type": "other", + "name": "Binary", + "url": "https://example.com/binary" + } + ], + "sources": [ + { + "format": "zip", + "url": "https://gitlab.com/namespace/project/-/archive/v1.0.0/project-v1.0.0.zip" + }, + { + "format": "tar.gz", + "url": "https://gitlab.com/namespace/project/-/archive/v1.0.0/project-v1.0.0.tar.gz" + } + ] + }, + "commit": { /* commit details */ } +} +``` + +**Action Values**: `create`, `update`, `delete` + +## Testing Webhooks + +### Via GitLab UI + +1. Navigate to Project Settings > Webhooks +2. Find your webhook +3. Click "Test" dropdown +4. Select event type to test +5. View response in UI + +### Via API + +```bash +curl -X POST "https://gitlab.com/api/v4/projects/:id/hooks/:hook_id/test/push_events" \ + --header "PRIVATE-TOKEN: " +``` + +### Local Testing + +Use tools like: +- **ngrok**: Create public URL for local development +- **webhook.site**: Test webhook delivery +- **requestbin.com**: Inspect webhook payloads + +```bash +# Using ngrok +ngrok http 3000 + +# Update webhook URL to ngrok URL +# Trigger event in GitLab +# View webhook payload in ngrok dashboard +``` + +## Webhook Best Practices + +### 1. Security + +- **Use HTTPS**: Always use HTTPS URLs for webhooks +- **Verify SSL**: Enable SSL verification in production +- **Secret Tokens**: Use secret tokens to verify webhook authenticity +- **Validate Payloads**: Verify request signatures +- **IP Allowlisting**: Restrict webhook sources to GitLab IPs + +### 2. Reliability + +- **Return Quickly**: Respond with 2xx status code quickly (< 10 seconds) +- **Async Processing**: Queue webhook payloads for async processing +- **Idempotency**: Handle duplicate webhook deliveries +- **Error Handling**: Handle errors gracefully +- **Retry Logic**: Implement retry logic for failed processing + +### 3. Monitoring + +- **Log Webhooks**: Log all webhook deliveries +- **Track Failures**: Monitor webhook failure rates +- **Alert on Issues**: Set up alerts for webhook failures +- **Recent Deliveries**: Check recent deliveries in GitLab UI + +### 4. Performance + +- **Rate Limiting**: Handle rate limits on external APIs +- **Batch Processing**: Batch similar webhook events +- **Caching**: Cache frequently accessed data +- **Timeouts**: Set appropriate timeouts + +## Example Webhook Handler + +### Python Flask Example + +```python +from flask import Flask, request, jsonify +import hmac +import hashlib + +app = Flask(__name__) +WEBHOOK_SECRET = "your-secret-token" + +def verify_signature(payload, signature): + """Verify webhook signature""" + if not signature: + return False + + expected = hmac.new( + WEBHOOK_SECRET.encode(), + payload, + hashlib.sha256 + ).hexdigest() + + return hmac.compare_digest(expected, signature) + +@app.route('/webhook', methods=['POST']) +def handle_webhook(): + # Get signature from header + signature = request.headers.get('X-Gitlab-Token') + + # Verify signature + if not verify_signature(request.data, signature): + return jsonify({'error': 'Invalid signature'}), 401 + + # Get event type + event_type = request.headers.get('X-Gitlab-Event') + + # Parse payload + payload = request.json + + # Handle different event types + if event_type == 'Push Hook': + handle_push_event(payload) + elif event_type == 'Merge Request Hook': + handle_merge_request_event(payload) + elif event_type == 'Pipeline Hook': + handle_pipeline_event(payload) + + return jsonify({'status': 'received'}), 200 + +def handle_push_event(payload): + """Handle push events""" + ref = payload['ref'] + commits = payload['commits'] + print(f"Received push to {ref} with {len(commits)} commits") + +def handle_merge_request_event(payload): + """Handle merge request events""" + action = payload['object_attributes']['action'] + iid = payload['object_attributes']['iid'] + print(f"Merge request {iid} was {action}") + +def handle_pipeline_event(payload): + """Handle pipeline events""" + status = payload['object_attributes']['status'] + ref = payload['object_attributes']['ref'] + print(f"Pipeline on {ref} status: {status}") + +if __name__ == '__main__': + app.run(port=5000) +``` + +### Node.js Express Example + +```javascript +const express = require('express'); +const crypto = require('crypto'); +const bodyParser = require('body-parser'); + +const app = express(); +const WEBHOOK_SECRET = 'your-secret-token'; + +// Verify webhook signature +function verifySignature(payload, signature) { + if (!signature) return false; + + const expected = crypto + .createHmac('sha256', WEBHOOK_SECRET) + .update(payload) + .digest('hex'); + + return crypto.timingSafeEqual( + Buffer.from(expected), + Buffer.from(signature) + ); +} + +app.post('/webhook', bodyParser.raw({type: 'application/json'}), (req, res) => { + const signature = req.headers['x-gitlab-token']; + const eventType = req.headers['x-gitlab-event']; + + // Verify signature + if (!verifySignature(req.body, signature)) { + return res.status(401).json({ error: 'Invalid signature' }); + } + + const payload = JSON.parse(req.body.toString()); + + // Handle different event types + switch (eventType) { + case 'Push Hook': + handlePushEvent(payload); + break; + case 'Merge Request Hook': + handleMergeRequestEvent(payload); + break; + case 'Pipeline Hook': + handlePipelineEvent(payload); + break; + } + + res.json({ status: 'received' }); +}); + +function handlePushEvent(payload) { + console.log(`Push to ${payload.ref} with ${payload.commits.length} commits`); +} + +function handleMergeRequestEvent(payload) { + const { action, iid } = payload.object_attributes; + console.log(`Merge request ${iid} was ${action}`); +} + +function handlePipelineEvent(payload) { + const { status, ref } = payload.object_attributes; + console.log(`Pipeline on ${ref} status: ${status}`); +} + +app.listen(5000, () => { + console.log('Webhook server listening on port 5000'); +}); +``` + +## Troubleshooting + +### Common Issues + +1. **Webhook Not Triggering** + - Verify event is enabled in webhook configuration + - Check webhook URL is accessible from internet + - Review Recent Deliveries in GitLab UI + - Check firewall rules + +2. **SSL Verification Failures** + - Ensure SSL certificate is valid + - Check certificate chain is complete + - Temporarily disable SSL verification for testing (not recommended for production) + +3. **Timeouts** + - Reduce processing time in webhook handler + - Return 2xx response quickly, process async + - Check network connectivity + +4. **Authentication Errors** + - Verify secret token matches + - Check signature verification logic + - Ensure token is transmitted correctly + +### Debugging Tips + +1. Use webhook testing services (webhook.site, requestbin.com) +2. Check "Recent Deliveries" in GitLab webhook settings +3. Enable detailed logging in webhook handler +4. Test with curl/Postman using sample payloads +5. Verify IP addresses if using allowlisting +6. Check response status codes and headers + +## Additional Resources + +- GitLab Webhooks Documentation: https://docs.gitlab.com/ee/user/project/integrations/webhooks.html +- Webhook Events: https://docs.gitlab.com/ee/user/project/integrations/webhook_events.html +- System Hooks: https://docs.gitlab.com/ee/administration/system_hooks.html