Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
102 changes: 102 additions & 0 deletions .github/workflows/branch-protection-rule.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
name: Apply Branch Protection Rules

on:
workflow_dispatch:
inputs:
mode:
description: "single = one repo | bulk = all repos in branch-protection.txt"
required: true
type: choice
options:
- single
- bulk
default: single

target_repo:
description: "[single mode] Repo to protect e.g. mosip/<repo-name>"
required: false
type: string
default: ""

branch_pattern:
description: "[single mode] Branch to protect e.g. release-1.2.x"
required: false
type: string
default: ""

rules:
description: "Choose which branch protection rules to apply"
required: true
type: choice
options:
- default
- custom
default: default

jobs:
resolve-config:
name: "Resolve config repo details"
runs-on: ubuntu-latest
outputs:
config_repo: ${{ steps.info.outputs.config_repo }}
config_repo_ref: ${{ steps.info.outputs.config_repo_ref }}
steps:
- name: Capture repo and branch
id: info
run: |
echo "config_repo=${{ github.repository }}" >> $GITHUB_OUTPUT
echo "config_repo_ref=${{ github.ref_name }}" >> $GITHUB_OUTPUT

single-repo:

Check warning

Code scanning / CodeQL

Workflow does not contain permissions Medium

Actions job or workflow does not limit the permissions of the GITHUB_TOKEN. Consider setting an explicit permissions block, using the following as a minimal starting point: {}
Comment on lines +38 to +50
name: "Single -> ${{ inputs.target_repo }} @ ${{ inputs.branch_pattern }}"
needs: resolve-config
if: inputs.mode == 'single' && inputs.target_repo != '' && inputs.branch_pattern != ''
uses: mosip/kattu/.github/workflows/branch-protection-rule.yaml@master-MOSIP-30370-branching-rule
with:
target_repo: ${{ inputs.target_repo }}
branch_pattern: ${{ inputs.branch_pattern }}
use_custom_rules: ${{ inputs.rules == 'custom' }}
config_repo: ${{ needs.resolve-config.outputs.config_repo }}
config_repo_ref: ${{ needs.resolve-config.outputs.config_repo_ref }}
secrets:
ACTION_PAT: ${{ secrets.ACTION_PAT }}
ALLOWED_TEAM: ${{ secrets.ALLOWED_TEAM }}

prepare-matrix:

Check warning

Code scanning / CodeQL

Workflow does not contain permissions Medium

Actions job or workflow does not limit the permissions of the GITHUB_TOKEN. Consider setting an explicit permissions block, using the following as a minimal starting point: {}
Comment on lines +51 to +65
name: "Parse branch-protection.txt"
needs: resolve-config
if: inputs.mode == 'bulk'
runs-on: ubuntu-latest
outputs:
matrix: ${{ steps.parse.outputs.matrix }}
steps:
- name: Checkout release-script repo
uses: actions/checkout@v4
with:
repository: ${{ needs.resolve-config.outputs.config_repo }}
ref: ${{ needs.resolve-config.outputs.config_repo_ref }}
token: ${{ secrets.ACTION_PAT }}

- name: Parse branch-protection.txt
id: parse
run: |
echo "aW1wb3J0IGpzb24sIHN5cywgb3MKCmZpbGVwYXRoID0gInJlbGVhc2UvYnJhbmNoaW5nLXJ1bGUvYnJhbmNoLXByb3RlY3Rpb24udHh0IgoKaWYgbm90IG9zLnBhdGguZXhpc3RzKGZpbGVwYXRoKToKICAgIHByaW50KCI6OmVycm9yOjoiICsgZmlsZXBhdGggKyAiIG5vdCBmb3VuZCEiKQogICAgc3lzLmV4aXQoMSkKCmVudHJpZXMgPSBbXQpjdXJyZW50X3JlcG8gPSBOb25lCmN1cnJlbnRfYnJhbmNoID0gTm9uZQoKd2l0aCBvcGVuKGZpbGVwYXRoKSBhcyBmOgogICAgZm9yIGxpbmUgaW4gZjoKICAgICAgICBzdHJpcHBlZCA9IGxpbmUuc3RyaXAoKQogICAgICAgIGlmIHN0cmlwcGVkLnN0YXJ0c3dpdGgoIi0gcmVwbzoiKToKICAgICAgICAgICAgaWYgY3VycmVudF9yZXBvIGFuZCBjdXJyZW50X2JyYW5jaDoKICAgICAgICAgICAgICAgIGVudHJpZXMuYXBwZW5kKHsidGFyZ2V0X3JlcG8iOiBjdXJyZW50X3JlcG8sICJicmFuY2hfcGF0dGVybiI6IGN1cnJlbnRfYnJhbmNofSkKICAgICAgICAgICAgY3VycmVudF9yZXBvID0gc3RyaXBwZWQuc3BsaXQoIi0gcmVwbzoiKVsxXS5zdHJpcCgpCiAgICAgICAgICAgIGN1cnJlbnRfYnJhbmNoID0gTm9uZQogICAgICAgIGVsaWYgc3RyaXBwZWQuc3RhcnRzd2l0aCgiYnJhbmNoOiIpOgogICAgICAgICAgICBjdXJyZW50X2JyYW5jaCA9IHN0cmlwcGVkLnNwbGl0KCJicmFuY2g6IilbMV0uc3RyaXAoKQoKaWYgY3VycmVudF9yZXBvIGFuZCBjdXJyZW50X2JyYW5jaDoKICAgIGVudHJpZXMuYXBwZW5kKHsidGFyZ2V0X3JlcG8iOiBjdXJyZW50X3JlcG8sICJicmFuY2hfcGF0dGVybiI6IGN1cnJlbnRfYnJhbmNofSkKCmlmIG5vdCBlbnRyaWVzOgogICAgcHJpbnQoIjo6ZXJyb3I6Ok5vIHZhbGlkIGVudHJpZXMgZm91bmQgaW4gYnJhbmNoLXByb3RlY3Rpb24udHh0ISIpCiAgICBzeXMuZXhpdCgxKQoKcHJpbnQoIkZvdW5kICIgKyBzdHIobGVuKGVudHJpZXMpKSArICIgcmVwbyhzKSB0byBwcm90ZWN0OiIpCmZvciBlIGluIGVudHJpZXM6CiAgICBwcmludCgiICAiICsgZVsidGFyZ2V0X3JlcG8iXSArICIgIC0+ICAiICsgZVsiYnJhbmNoX3BhdHRlcm4iXSkKCndpdGggb3BlbigiL3RtcC9tYXRyaXguanNvbiIsICJ3IikgYXMgZjoKICAgIGYud3JpdGUoanNvbi5kdW1wcyh7ImluY2x1ZGUiOiBlbnRyaWVzfSkpCg==" | base64 -d > /tmp/parse_repos.py
python3 /tmp/parse_repos.py
echo "matrix=$(cat /tmp/matrix.json)" >> $GITHUB_OUTPUT

bulk-apply:

Check warning

Code scanning / CodeQL

Workflow does not contain permissions Medium

Actions job or workflow does not limit the permissions of the GITHUB_TOKEN. Consider setting an explicit permissions block, using the following as a minimal starting point: {contents: read}
Comment on lines +66 to +87
name: "Bulk -> ${{ matrix.target_repo }} @ ${{ matrix.branch_pattern }}"
needs: [resolve-config, prepare-matrix]
strategy:
fail-fast: false
matrix: ${{ fromJson(needs.prepare-matrix.outputs.matrix) }}
uses: mosip/kattu/.github/workflows/branch-protection-rule.yaml@master
with:
target_repo: ${{ matrix.target_repo }}
branch_pattern: ${{ matrix.branch_pattern }}
use_custom_rules: ${{ inputs.rules == 'custom' }}
config_repo: ${{ needs.resolve-config.outputs.config_repo }}
config_repo_ref: ${{ needs.resolve-config.outputs.config_repo_ref }}
secrets:
ACTION_PAT: ${{ secrets.ACTION_PAT }}
ALLOWED_TEAM: ${{ secrets.ALLOWED_TEAM }}

Check warning

Code scanning / CodeQL

Workflow does not contain permissions Medium

Actions job or workflow does not limit the permissions of the GITHUB_TOKEN. Consider setting an explicit permissions block, using the following as a minimal starting point: {}
Comment on lines +88 to +102
217 changes: 217 additions & 0 deletions release/branching-rule/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,217 @@
# Branch Protection Rules

Automates applying GitHub branch protection rules across personal and organization repositories using a reusable GitHub Actions workflow.

---

## Repository Structure

```
release-script/ ← Caller repo (trigger workflow here)
├── .github/
│ └── workflows/
│ └── branch-protection.yaml ← Trigger this workflow
└── release/
└── branching-rule/
├── branch-protection.txt ← Repo + branch list (bulk mode)
└── custom-branch-protection-rule.yaml ← Custom rules (optional)

kattu/ ← Reusable workflow repo
└── .github/
└── workflows/
└── branch-protection-rule.yaml ← Central logic (do not trigger directly)
```

---

## How to Add Repos for Bulk Mode

Edit `release/branching-rule/branch-protection.txt`:

```
repositories:
- repo: mosip/admin-services
branch: release-1.2.x

- repo: inji/vc-verifier
branch: release-1.8.x

- repo: Prafulrakhade/admin-services
branch: develop
```

- `repo` — full name in `org/repo-name` or `user/repo-name` format
- `branch` — exact branch name to protect
- Lines starting with `#` are ignored

---

## How to Run the Workflow

Go to `release-script` → **Actions** → **Apply Branch Protection Rules** → **Run workflow**

### Inputs

| Input | Options | Description |
|---|---|---|
| `mode` | `single` / `bulk` | Single repo or all repos in `branch-protection.txt` |
| `target_repo` | e.g. `mosip/admin-services` | Single mode only — leave blank for bulk |
| `branch_pattern` | e.g. `release-1.2.x` | Single mode only — leave blank for bulk |
| `rules` | `default` / `custom` | Which rules to apply |

### Single Mode
```
mode → single
target_repo → mosip/admin-services
branch_pattern → release-1.2.x
rules → default
```

### Bulk Mode
```
mode → bulk
target_repo → (leave blank)
branch_pattern → (leave blank)
rules → default
```

---

## Access Control

Only users with **Admin** or **Maintain** role on the target repository can trigger the workflow. The check runs in 3 levels:

| Check | Condition | Result |
|---|---|---|
| Check 0 | Actor is the repo owner | Authorized |
| Check 1 | Actor is a direct collaborator with `admin` or `maintain` role | Authorized |
| Check 2 | Actor is a member of any team that has `admin` or `maintain` on the repo | Authorized |

**Blocked roles:**

| Role | Can trigger? |
|---|---|
| Admin | ✅ Yes |
| Maintain | ✅ Yes |
| Write | ❌ No |
| Triage | ❌ No |
| Read | ❌ No |
| Outside collaborator (non-admin/maintain) | ❌ No |

If access is denied, the workflow prints the user's actual role:
```
[DENIED] abhishek8shankar has 'write' role on mosip/admin-services
[DENIED] Only 'admin' and 'maintain' roles can create branch protection rules
```

---

## Existing Branch Protection

If branch protection rules **already exist** on the target branch, the workflow **fails** with a clear error:

```
[FAIL] Branch 'develop' on 'mosip/admin-services'
already has branch protection rules.
Please remove the existing rules first if you want to reapply.
```

> Remove the existing rules from GitHub Settings → Branches → Edit, then re-run the workflow.

---

## Default Branch Protection Rules

Applied when `rules → default`:

| Rule | Value |
|---|---|
| Required PR approvals | 1 |
| Dismiss stale reviews | false |
| Require CODEOWNER review | false |
| Require status checks | Auto-discovered |
| Require conversation resolution | true |
| Enforce admins (no bypassing) | true |
| Allow force pushes | false |
| Allow branch deletion | false |
| Require linear history | false |
| Push restrictions (users) | gsasikumar, vishwa-vyom, ckm007 *(org repos only)* |

---

## Custom Branch Protection Rules

Edit `release/branching-rule/custom-branch-protection-rule.yaml` and change only what you need:

```yaml
rules:
required_approving_review_count: 2 # default: 1
dismiss_stale_reviews: false
require_code_owner_reviews: false
require_last_push_approval: false
require_status_checks: false
strict_status_checks: false
status_check_contexts: ""
required_conversation_resolution: true
required_linear_history: false
enforce_admins: true
allow_force_pushes: false
allow_deletions: false
push_users: "gsasikumar,vishwa-vyom,ckm007"
push_teams: ""
```

Then run workflow with `rules → custom`.

> Inline comments (`# ...`) are supported and ignored by the parser.

---

## Status Checks — Auto Discovery

For `default` mode, the workflow automatically reads workflow files in the **target repo** and registers:

- Jobs using `maven-build.yml` → `<job-id> / maven-build`
- Jobs using `docker-build.yml` → `<SERVICE_NAME> / build-dockers`

If no matching jobs found → branch protection is still applied without status checks.

---

## Personal vs Organization Repos

| Feature | Personal repo | Org repo |
|---|---|---|
| All protection rules | ✅ | ✅ |
| Push restrictions (users/teams) | ❌ Skipped automatically | ✅ |

Push restrictions are silently skipped for personal repos — GitHub API does not support them for personal accounts.

---

## Required Secrets

Configure in `release-script` → **Settings → Secrets → Actions**:

| Secret | Required | Description |
|---|---|---|
| `ACTION_PAT` | ✅ Yes | PAT with `repo` + `admin:org` scopes |
| `ALLOWED_TEAM` | ❌ No | Legacy — no longer required, team check is automatic |

### Creating the PAT
1. GitHub → **Settings → Developer settings → Personal access tokens → Tokens (classic)**
2. Select scopes: `repo` (full) + `admin:org`
3. Save as `ACTION_PAT` secret in `release-script`

---

## Troubleshooting

| Error | Cause | Fix |
|---|---|---|
| `branch-protection.txt not found` | File not at `release/branching-rule/branch-protection.txt` | Check file path and branch |
| `Access denied` | User does not have Admin or Maintain role | Grant correct role on target repo |
| `Already has branch protection rules` | Rules exist on target branch | Remove existing rules first |
| `HTTP 422 Validation Failed` | Push restrictions on personal repo | Expected — handled automatically |
| `HTTP 404 on branch` | Branch does not exist | Create the branch first |
| `Custom rules file not found` | File missing | Ensure file exists at `release/branching-rule/custom-branch-protection-rule.yaml` |
5 changes: 5 additions & 0 deletions release/branching-rule/branch-protection.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
repositories:
- repo: abhishek8shankar/id-authentication
branch: master
- repo: Prafulrakhade/admin-services
branch: release-1.3.x
44 changes: 44 additions & 0 deletions release/branching-rule/custom-branch-protection-rule.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# ============================================================
# FILE: branching-rule/branch-protection-rules.yaml
#
# Edit this file to define your own branch protection rules.
# Trigger the workflow with "Use custom rules file" = true
# to apply these instead of the default hardcoded rules.
#
# DEFAULT values below match the standard hardcoded defaults
# in the workflow — change only what you need.
# ============================================================

rules:

# ── Pull Request Reviews ───────────────────────────────────
required_approving_review_count: 2 # Number of approvals required (0–6)
dismiss_stale_reviews: false # Dismiss approvals when new commits are pushed
require_code_owner_reviews: false # Require review from a CODEOWNER
require_last_push_approval: false # Last pusher cannot approve their own PR

# ── Status Checks ─────────────────────────────────────────
require_status_checks: false # Require CI checks to pass before merging
strict_status_checks: false # Branch must be up-to-date before merging
status_check_contexts: "" # Comma-separated check names e.g. "build,test,lint"
# Leave empty if require_status_checks is false

# ── Conversation & History ─────────────────────────────────
required_conversation_resolution: true # All comments must be resolved before merging
required_linear_history: false # Prevent merge commits (require squash or rebase)

# ── Admin & Bypass ────────────────────────────────────────
enforce_admins: true # Apply rules to admins too (no bypassing)

# ── Push & Deletion ───────────────────────────────────────
allow_force_pushes: false # Allow force pushes to the branch
allow_deletions: false # Allow the branch to be deleted

# ── Push Restrictions ─────────────────────────────────────
# Comma-separated GitHub usernames allowed to push.
# Must already be collaborators on the repo.
push_users: "gsasikumar,vishwa-vyom,ckm007"

# Comma-separated team slugs allowed to push.
# Leave empty if no team restriction needed.
push_teams: ""