{"id":15055454,"url":"https://github.com/tedspinks/gitlab-codeowners","last_synced_at":"2026-02-25T05:37:05.126Z","repository":{"id":243399034,"uuid":"805632568","full_name":"TedSpinks/gitlab-codeowners","owner":"TedSpinks","description":"Validate your GitLab CODEOWNERS file. Make sure it's enforcing MR approvals the way you expect!","archived":false,"fork":false,"pushed_at":"2024-07-14T17:00:28.000Z","size":8166,"stargazers_count":2,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-03-02T22:42:44.287Z","etag":null,"topics":["codeowners","gitlab"],"latest_commit_sha":null,"homepage":"https://gitlab.com/explore/catalog/tedspinks/validate-codeowners","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/TedSpinks.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":"CODEOWNERS","security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-05-25T03:12:22.000Z","updated_at":"2025-01-06T20:52:32.000Z","dependencies_parsed_at":"2024-06-08T16:30:04.962Z","dependency_job_id":"a506a020-3ebe-4e9c-a9f5-3074d2be627c","html_url":"https://github.com/TedSpinks/gitlab-codeowners","commit_stats":null,"previous_names":["tedspinks/gitlab-codeowners"],"tags_count":1,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/TedSpinks%2Fgitlab-codeowners","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/TedSpinks%2Fgitlab-codeowners/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/TedSpinks%2Fgitlab-codeowners/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/TedSpinks%2Fgitlab-codeowners/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/TedSpinks","download_url":"https://codeload.github.com/TedSpinks/gitlab-codeowners/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":241582521,"owners_count":19985846,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["codeowners","gitlab"],"created_at":"2024-09-24T21:42:16.209Z","updated_at":"2025-10-26T16:36:44.185Z","avatar_url":"https://github.com/TedSpinks.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# [validate-codeowners](https://gitlab.com/tedspinks/validate-codeowners)\n\nThis CI/CD component provides a job to validate your GitLab project's CODEOWNERS file. It also includes a Linux binary and Docker image, which you can run from your desktop or from your own CI/CD job.\n\nIt performs the following validation checks:\n\n- CODEOWNERS file resides in one of the three [supported locations](https://docs.gitlab.com/ee/user/project/codeowners/#codeowners-file).\n- [Syntax](https://docs.gitlab.com/ee/user/project/codeowners/reference.html) is valid.\n- All owners are valid GitLab @groups, @users, or user@emails.\n- All @groups are **direct** members of the project.\n- All @users are **direct** members of the project.\n- All user@emails are **direct** members of the project.\n\n\n## About direct memberships\n\nWhat's all the fuss about checking [direct](https://docs.gitlab.com/ee/user/project/members/) memberships? While you can add CODEOWNERS entries for both direct and inherited members of a project, CODEOWNERS entries for inherited members are only ***optional***, not required. Moreover, when reading a CODEOWNERS file, it is not obvious which entries will be optional vs. required - one would need to manually cross-check each CODEOWNERS entry against the project's direct memberships. Since the point of having a CODEOWNERS file is almost always to ***enforce*** MR approvals of specific files/directories, this job makes sure that all CODEOWNERS entries are for ***direct members***, and are therefore ***required*** approvals. \n\nFrom the [GitLab documentation](https://docs.gitlab.com/ee/user/project/codeowners/#group-inheritance-and-eligibility):\n\u003e For approval to be *required*, groups as Code Owners must have a direct membership (not inherited membership) in the project. Approval can only be *optional* for groups that inherit membership. Members in the Code Owners group also must be direct members, and not inherit membership from any parent groups.\n\n\n## Example CI/CD Component Usage\n\n.gitlab-ci.yml\n```yaml\ninclude:\n  - component: gitlab.com/tedspinks/validate-codeowners/validate-codeowners@1.0.0\n    inputs:\n      GITLAB_TOKEN: ${GITLAB_TOKEN}\n\nvalidate-codeowners:\n  extends: .validate-codeowners\n  stage: test\n  # Example rule will only run this job in MRs, when the CODEOWNERS file has changed\n  rules:\n    - if: $CI_PIPELINE_SOURCE == 'merge_request_event'\n      changes:\n        - CODEOWNERS\n        - docs/CODEOWNERS\n        - .gitlab/CODEOWNERS\n```\n\n\n## Example CLI Usage\n\n```bash\nsudo curl -L -o /usr/local/bin/validate-codeowners \\\n https://gitlab.com/tedspinks/validate-codeowners/-/releases/1.0.0/downloads/linux-amd64/validate-codeowners\n\nsudo chmod +x /usr/local/bin/validate-codeowners\n\nexport GITLAB_TOKEN=glpat-blahblah12345\nexport CI_PROJECT_PATH=my-group/my-project-with-codeowners-file\nexport CI_COMMIT_REF_NAME=my-branch\nexport CI_API_GRAPHQL_URL=https://gitlab.com/api/graphql\nexport CI_API_V4_URL=https://gitlab.com/api/v4\n\ncd my-git-clone-directory\n\nvalidate-codeowners\n```\n\n## Inputs\n\n#### CI/CD Component Inputs\n\n- `GITLAB_TOKEN` - **Required**. GitLab token with \"read_api\" scope. This is usually an Admin token. See token [permission details](https://docs.gitlab.com/ee/api/members.html). Summary of required permissions:\n  1. Owner of the target project.\n  2. Member of ALL groups that might be listed as Codeowners (or that might contain users listed as Codeowners).\n  3. To validate emails: group owners for enterprise users, or admin for self-hosted.\n- `GITLAB_TIMEOUT_SECS` - Optional. Timeout in seconds for communication with the GitLab APIs. Default is \"30\".\n\n#### Pipeline Variables\n\n- `CODEOWNERS_DEBUG` - Optional. Set to \"true\" for debug logging (it's VERY verbose). Handy for manual pipeline runs in the web UI.\n\n#### GitLab [Predefined variables](https://docs.gitlab.com/ee/ci/variables/predefined_variables.html)\n\n- `CI_PROJECT_PATH` - The namespace/project path of your project with the CODEOWNERS file you want to validate.\n- `CI_COMMIT_REF_NAME` - The branch or tag name of your project.\n- `CI_API_GRAPHQL_URL` - The GitLab API GraphQL root URL. For SaaS GitLab this will be https://gitlab.com/api/graphql.\n- `CI_API_V4_URL` - The GitLab REST API v4 root URL. For SaaS GitLab this will be https://gitlab.com/api/v4.\n\n\n## Design Considerations\n\nThe GitLab GraphQL API includes a very nice [CODEOWNERS syntax validator](https://docs.gitlab.com/ee/api/graphql/reference/#repositoryvalidatecodeownerfile). I believe this is the same validator that runs when you edit a CODEWONERS file from the GitLab web UI. Rather than reinvent the wheel and write a complete parser, I decided to take advantage of this API function. And, with syntax validation taken care of, I was able to write a *much simpler* `splitCodeownersLine()` function, which just grabs the file patterns and owners from each line.\n\nTo do the actual validations, I tried to use GitLab's newer GraphQL API as mush as possible. However, it wasn't apparent to me how to get a project's `shared_with_groups` field from the GraphQL queries, so I ended up using the REST `projects/` endpoint for that piece.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftedspinks%2Fgitlab-codeowners","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftedspinks%2Fgitlab-codeowners","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftedspinks%2Fgitlab-codeowners/lists"}