Skip to content

Commit f0d5edf

Browse files
committed
feat: add flag to allow cross-organization teams during org transitions
This allows teams from different organizations to be valid owners, which is useful during repository transitions between organizations (e.g., from altana-poc to altana-tech). When OWNER_CHECKER_ALLOW_CROSS_ORG_TEAMS is set to true, the validator will skip the check that ensures teams belong to the same organization as the repository. Updates: - Add AllowCrossOrgTeams field to ValidOwnerConfig - Skip org validation in validateTeam when flag is enabled - Add documentation for the new environment variable - Update GitHub Action definition with new input parameter - Add unit tests for the new configuration flag
1 parent f3651e3 commit f0d5edf

File tree

5 files changed

+71
-1
lines changed

5 files changed

+71
-1
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,7 @@ Use the following environment variables to configure the application:
143143
| <tt>OWNER_CHECKER_IGNORED_OWNERS</tt> | `@ghost` | The comma-separated list of owners that should not be validated. Example: `"@owner1,@owner2,@org/team1,[email protected]"`. |
144144
| <tt>OWNER_CHECKER_ALLOW_UNOWNED_PATTERNS</tt> | `true` | Specifies whether CODEOWNERS may have unowned files. For example: <br> <br> `/infra/oncall-rotator/ @sre-team` <br> `/infra/oncall-rotator/oncall-config.yml` <br> <br> The `/infra/oncall-rotator/oncall-config.yml` file is not owned by anyone. |
145145
| <tt>OWNER_CHECKER_OWNERS_MUST_BE_TEAMS</tt> | `false` | Specifies whether only teams are allowed as owners of files. |
146+
| <tt>OWNER_CHECKER_ALLOW_CROSS_ORG_TEAMS</tt> | `false` | Specifies whether teams from different organizations are allowed. When set to `true`, the check that validates if a team belongs to the repository's organization will be skipped. This is useful during transitions between organizations. |
146147
| <tt>NOT_OWNED_CHECKER_SKIP_PATTERNS</tt> | | The comma-separated list of patterns that should be ignored by `not-owned-checker`. For example, you can specify `*` and as a result, the `*` pattern from the **CODEOWNERS** file will be ignored and files owned by this pattern will be reported as unowned unless a later specific pattern will match that path. It's useful because often we have default owners entry at the begging of the CODOEWNERS file, e.g. `* @global-owner1 @global-owner2` |
147148
| <tt>NOT_OWNED_CHECKER_SUBDIRECTORIES</tt> | | The comma-separated list of subdirectories to check in `not-owned-checker`. When specified, only files in the listed subdirectories will be checked if they do not have specified owners in CODEOWNERS. |
148149
| <tt>NOT_OWNED_CHECKER_TRUST_WORKSPACE</tt> | `false` | Specifies whether the repository path should be marked as safe. See: https://github.com/actions/checkout/issues/766. |

action.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,11 @@ inputs:
6969
default: "false"
7070
required: false
7171

72+
owner_checker_allow_cross_org_teams:
73+
description: "Specifies whether teams from different organizations are allowed. When set to true, the check that validates if a team belongs to the repository's organization will be skipped. This is useful during transitions between organizations."
74+
default: "false"
75+
required: false
76+
7277
not_owned_checker_subdirectories:
7378
description: "Only check listed subdirectories for CODEOWNERS ownership that don't have owners."
7479
required: false

docs/gh-action.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,11 @@ jobs:
8989
# Specifies whether only teams are allowed as owners of files.
9090
owner_checker_owners_must_be_teams: "false"
9191

92+
# Specifies whether teams from different organizations are allowed. When set to true,
93+
# the check that validates if a team belongs to the repository's organization will be
94+
# skipped. This is useful during transitions between organizations.
95+
owner_checker_allow_cross_org_teams: "false"
96+
9297
# Only check listed subdirectories for CODEOWNERS ownership that don't have owners.
9398
not_owned_checker_subdirectories: ""
9499
```

internal/check/valid_owner.go

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,10 @@ type ValidOwnerConfig struct {
3838
AllowUnownedPatterns bool `envconfig:"default=true"`
3939
// OwnersMustBeTeams specifies whether owners must be teams in the same org as the repository
4040
OwnersMustBeTeams bool `envconfig:"default=false"`
41+
// AllowCrossOrgTeams specifies whether teams from different organizations are allowed.
42+
// When set to true, the check that validates if a team belongs to the repository's
43+
// organization will be skipped. This is useful during transitions between organizations.
44+
AllowCrossOrgTeams bool `envconfig:"default=false"`
4145
}
4246

4347
// ValidOwner validates each owner
@@ -51,6 +55,7 @@ type ValidOwner struct {
5155
ignOwners map[string]struct{}
5256
allowUnownedPatterns bool
5357
ownersMustBeTeams bool
58+
allowCrossOrgTeams bool
5459
}
5560

5661
// NewValidOwner returns new instance of the ValidOwner
@@ -73,6 +78,7 @@ func NewValidOwner(cfg ValidOwnerConfig, ghClient *github.Client, checkScopes bo
7378
ignOwners: ignOwners,
7479
allowUnownedPatterns: cfg.AllowUnownedPatterns,
7580
ownersMustBeTeams: cfg.OwnersMustBeTeams,
81+
allowCrossOrgTeams: cfg.AllowCrossOrgTeams,
7682
}, nil
7783
}
7884

@@ -216,7 +222,8 @@ func (v *ValidOwner) validateTeam(ctx context.Context, name string) *validateErr
216222
team := parts[1]
217223

218224
// GitHub normalizes name before comparison
219-
if !strings.EqualFold(org, v.orgName) {
225+
// Skip org check if cross-org teams are allowed
226+
if !v.allowCrossOrgTeams && !strings.EqualFold(org, v.orgName) {
220227
return newValidateError("Team %q does not belong to %q organization.", name, v.orgName)
221228
}
222229

internal/check/valid_owner_test.go

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,3 +159,55 @@ func TestValidOwnerCheckerOwnersMustBeTeams(t *testing.T) {
159159
})
160160
}
161161
}
162+
163+
func TestValidOwnerCheckerAllowCrossOrgTeams(t *testing.T) {
164+
// This test validates the AllowCrossOrgTeams config flag behavior
165+
// Note: This test only validates the configuration parsing and basic team syntax validation
166+
// The actual org validation requires a GitHub client and is tested in integration tests
167+
168+
t.Run("Config with AllowCrossOrgTeams enabled", func(t *testing.T) {
169+
// given
170+
ownerCheck, err := check.NewValidOwner(check.ValidOwnerConfig{
171+
Repository: "org/repo",
172+
AllowUnownedPatterns: true,
173+
AllowCrossOrgTeams: true, // Enable cross-org teams
174+
}, nil, true)
175+
require.NoError(t, err)
176+
177+
// Verify the config was properly set
178+
assert.NotNil(t, ownerCheck)
179+
})
180+
181+
t.Run("Config with AllowCrossOrgTeams disabled", func(t *testing.T) {
182+
// given
183+
ownerCheck, err := check.NewValidOwner(check.ValidOwnerConfig{
184+
Repository: "org/repo",
185+
AllowUnownedPatterns: true,
186+
AllowCrossOrgTeams: false, // Disable cross-org teams (default)
187+
}, nil, true)
188+
require.NoError(t, err)
189+
190+
// Verify the config was properly set
191+
assert.NotNil(t, ownerCheck)
192+
})
193+
194+
t.Run("Team syntax validation", func(t *testing.T) {
195+
// Test that team syntax is recognized correctly regardless of org
196+
tests := []struct {
197+
owner string
198+
isValid bool
199+
}{
200+
{"@org/team", true},
201+
{"@different-org/team", true},
202+
{"@altana-poc/team", true},
203+
{"@altana-tech/team", true},
204+
{"@org/", false},
205+
{"org/team", false},
206+
}
207+
208+
for _, tc := range tests {
209+
result := check.IsValidOwner(tc.owner)
210+
assert.Equal(t, tc.isValid, result, "Owner %s validation failed", tc.owner)
211+
}
212+
})
213+
}

0 commit comments

Comments
 (0)