Skip to content

Commit c778270

Browse files
committed
labels_sync: allow setting org-level labels
In OpenShift we cargo-culted us into thinking the tool actually supports org-level labels but it doesn't. The *proper* structure for doing this is probably more hierarchic (like some of the Prow plugins, e.g. branch protector), but I wanted to avoid larger changes.
1 parent 6c4950a commit c778270

File tree

4 files changed

+141
-20
lines changed

4 files changed

+141
-20
lines changed

label_sync/BUILD.bazel

+4
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,10 @@ go_test(
5757
"//label_sync:test_examples",
5858
],
5959
embed = [":go_default_library"],
60+
deps = [
61+
"@com_github_google_go_cmp//cmp:go_default_library",
62+
"@com_github_google_go_cmp//cmp/cmpopts:go_default_library",
63+
],
6064
)
6165

6266
filegroup(

label_sync/labels_example.yaml

+12
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,16 @@ default:
1515
- name: dead-label
1616
description: Delete Me :)
1717
deleteAfter: 2017-01-01T13:00:00Z
18+
orgs:
19+
org:
20+
labels:
21+
- color: green
22+
name: sgtm
23+
description: Sounds Good To Me
24+
repos:
25+
org/repo:
26+
labels:
27+
- color: blue
28+
name: tgtm
29+
description: Tastes Good To Me
1830
...

label_sync/main.go

+55-12
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ type Label struct {
8484
// There is also a Default list of labels applied to every Repo
8585
type Configuration struct {
8686
Repos map[string]RepoConfig `json:"repos,omitempty"`
87+
Orgs map[string]RepoConfig `json:"orgs,omitempty"`
8788
Default RepoConfig `json:"default"`
8889
}
8990

@@ -239,6 +240,9 @@ func stringInSortedSlice(a string, list []string) bool {
239240
func (c Configuration) Labels() []Label {
240241
var labelarrays [][]Label
241242
labelarrays = append(labelarrays, c.Default.Labels)
243+
for _, org := range c.Orgs {
244+
labelarrays = append(labelarrays, org.Labels)
245+
}
242246
for _, repo := range c.Repos {
243247
labelarrays = append(labelarrays, repo.Labels)
244248
}
@@ -265,29 +269,42 @@ func (c Configuration) Labels() []Label {
265269
// Ensures the config does not duplicate label names between default and repo
266270
func (c Configuration) validate(orgs string) error {
267271
// Check default labels
268-
seen, err := validate(c.Default.Labels, "default", make(map[string]string))
272+
defaultSeen, err := validate(c.Default.Labels, "default", make(map[string]string))
269273
if err != nil {
270274
return fmt.Errorf("invalid config: %v", err)
271275
}
272276

273277
// Generate list of orgs
274278
sortedOrgs := strings.Split(orgs, ",")
275279
sort.Strings(sortedOrgs)
276-
// Check other repos labels
280+
281+
// Check org-level labels for duplicities with default labels
282+
orgSeen := map[string]map[string]string{}
283+
for org, orgConfig := range c.Orgs {
284+
if orgSeen[org], err = validate(orgConfig.Labels, org, defaultSeen); err != nil {
285+
return fmt.Errorf("invalid config: %v", err)
286+
}
287+
}
288+
277289
for repo, repoconfig := range c.Repos {
278-
// Will complain if a label is both in default and repo
279-
if _, err := validate(repoconfig.Labels, repo, seen); err != nil {
290+
data := strings.Split(repo, "/")
291+
if len(data) != 2 {
292+
return fmt.Errorf("invalid repo name '%s', expected org/repo form", repo)
293+
}
294+
org := data[0]
295+
if _, ok := orgSeen[org]; !ok {
296+
orgSeen[org] = defaultSeen
297+
}
298+
299+
// Check repo labels for duplicities with default and org-level labels
300+
if _, err := validate(repoconfig.Labels, repo, orgSeen[org]); err != nil {
280301
return fmt.Errorf("invalid config: %v", err)
281302
}
282303
// If orgs have been specified, warn if repo isn't under orgs
283-
if len(orgs) != 0 {
284-
data := strings.Split(repo, "/")
285-
if len(data) == 2 {
286-
if !stringInSortedSlice(data[0], sortedOrgs) {
287-
logrus.WithField("orgs", orgs).WithField("org", data[0]).WithField("repo", repo).Warn("Repo isn't inside orgs")
288-
}
289-
}
304+
if len(orgs) > 0 && !stringInSortedSlice(org, sortedOrgs) {
305+
logrus.WithField("orgs", orgs).WithField("org", org).WithField("repo", repo).Warn("Repo isn't inside orgs")
290306
}
307+
291308
}
292309
return nil
293310
}
@@ -472,6 +489,9 @@ func copyLabelMap(originalMap map[string]Label) map[string]Label {
472489
func syncLabels(config Configuration, org string, repos RepoLabels) (RepoUpdates, error) {
473490
// Find required, dead and archaic labels
474491
defaultRequired, defaultArchaic, defaultDead := classifyLabels(config.Default.Labels, make(map[string]Label), make(map[string]Label), make(map[string]Label), time.Now(), nil)
492+
if orgLabels, ok := config.Orgs[org]; ok {
493+
defaultRequired, defaultArchaic, defaultDead = classifyLabels(orgLabels.Labels, defaultRequired, defaultArchaic, defaultDead, time.Now(), nil)
494+
}
475495

476496
var validationErrors []error
477497
var actions []Update
@@ -811,8 +831,31 @@ func writeDocs(template string, output string, config Configuration) error {
811831
data = append(data, labelData{desc, linkify(desc), LabelsForTarget(config.Default.Labels, issueTarget)})
812832
desc = "all repos, only for PRs"
813833
data = append(data, labelData{desc, linkify(desc), LabelsForTarget(config.Default.Labels, prTarget)})
834+
// Let's sort orgs
835+
var orgs []string
836+
for org := range config.Orgs {
837+
orgs = append(orgs, org)
838+
}
839+
sort.Strings(orgs)
840+
// And append their labels
841+
for _, org := range orgs {
842+
lead := fmt.Sprintf("all repos in %s", org)
843+
if l := LabelsForTarget(config.Orgs[org].Labels, bothTarget); len(l) > 0 {
844+
desc = lead + ", for both issues and PRs"
845+
data = append(data, labelData{desc, linkify(desc), l})
846+
}
847+
if l := LabelsForTarget(config.Orgs[org].Labels, issueTarget); len(l) > 0 {
848+
desc = lead + ", only for issues"
849+
data = append(data, labelData{desc, linkify(desc), l})
850+
}
851+
if l := LabelsForTarget(config.Orgs[org].Labels, prTarget); len(l) > 0 {
852+
desc = lead + ", only for PRs"
853+
data = append(data, labelData{desc, linkify(desc), l})
854+
}
855+
}
856+
814857
// Let's sort repos
815-
repos := make([]string, 0)
858+
var repos []string
816859
for repo := range config.Repos {
817860
repos = append(repos, repo)
818861
}

label_sync/main_test.go

+70-8
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,12 @@ package main
1818

1919
import (
2020
"encoding/json"
21-
"reflect"
2221
"strings"
2322
"testing"
2423
"time"
24+
25+
"github.com/google/go-cmp/cmp"
26+
"github.com/google/go-cmp/cmp/cmpopts"
2527
)
2628

2729
// Tests for getting data from GitHub are not needed:
@@ -82,6 +84,36 @@ func TestValidate(t *testing.T) {
8284
},
8385
expectedError: false,
8486
},
87+
{
88+
name: "Required label defined in default and org",
89+
config: Configuration{
90+
Default: RepoConfig{Labels: []Label{
91+
{Name: "lab1", Description: "Test Label 1", Color: "deadbe"},
92+
}},
93+
Orgs: map[string]RepoConfig{
94+
"org": {Labels: []Label{
95+
{Name: "lab1", Description: "Test Label 1", Color: "deadbe"},
96+
}},
97+
},
98+
},
99+
expectedError: true,
100+
},
101+
{
102+
name: "Required label defined in org and repo",
103+
config: Configuration{
104+
Orgs: map[string]RepoConfig{
105+
"org": {Labels: []Label{
106+
{Name: "lab1", Description: "Test Label 1", Color: "deadbe"},
107+
}},
108+
},
109+
Repos: map[string]RepoConfig{
110+
"org/repo1": {Labels: []Label{
111+
{Name: "lab1", Description: "Test Label 1", Color: "deadbe"},
112+
}},
113+
},
114+
},
115+
expectedError: true,
116+
},
85117
}
86118
// Do tests
87119
for _, tc := range testcases {
@@ -162,6 +194,32 @@ func TestSyncLabels(t *testing.T) {
162194
{repo: "repo1", Why: "missing", Wanted: &Label{Name: "lab1", Description: "Test Label 1", Color: "deadbe"}}},
163195
},
164196
},
197+
{
198+
name: "Required label defined on org-level - update required on one repo",
199+
config: Configuration{
200+
Default: RepoConfig{Labels: []Label{
201+
{Name: "lab1", Description: "Test Label 1", Color: "deadbe"},
202+
}},
203+
Orgs: map[string]RepoConfig{
204+
"org": {Labels: []Label{
205+
{Name: "lab2", Description: "Test Label 2", Color: "deadbe"},
206+
}},
207+
},
208+
},
209+
current: RepoLabels{
210+
"repo1": {
211+
{Name: "lab1", Description: "Test Label 1", Color: "deadbe"},
212+
{Name: "lab2", Description: "Test Label 2", Color: "deadbe"},
213+
},
214+
"repo2": {
215+
{Name: "lab1", Description: "Test Label 1", Color: "deadbe"},
216+
},
217+
},
218+
expectedUpdates: RepoUpdates{
219+
"repo2": {
220+
{repo: "repo2", Why: "missing", Wanted: &Label{Name: "lab2", Description: "Test Label 2", Color: "deadbe"}}},
221+
},
222+
},
165223
{
166224
name: "Duplicate label on repo1",
167225
current: RepoLabels{
@@ -423,11 +481,15 @@ func TestLoadYAML(t *testing.T) {
423481
}{
424482
{
425483
path: "labels_example.yaml",
426-
expected: Configuration{Default: RepoConfig{Labels: []Label{
427-
{Name: "lgtm", Description: "LGTM", Color: "green"},
428-
{Name: "priority/P0", Description: "P0 Priority", Color: "red", Previously: []Label{{Name: "P0", Description: "P0 Priority", Color: "blue"}}},
429-
{Name: "dead-label", Description: "Delete Me :)", DeleteAfter: &d},
430-
}}},
484+
expected: Configuration{
485+
Default: RepoConfig{Labels: []Label{
486+
{Name: "lgtm", Description: "LGTM", Color: "green"},
487+
{Name: "priority/P0", Description: "P0 Priority", Color: "red", Previously: []Label{{Name: "P0", Description: "P0 Priority", Color: "blue"}}},
488+
{Name: "dead-label", Description: "Delete Me :)", DeleteAfter: &d},
489+
}},
490+
Orgs: map[string]RepoConfig{"org": {Labels: []Label{{Name: "sgtm", Description: "Sounds Good To Me", Color: "green"}}}},
491+
Repos: map[string]RepoConfig{"org/repo": {Labels: []Label{{Name: "tgtm", Description: "Tastes Good To Me", Color: "blue"}}}},
492+
},
431493
ok: true,
432494
},
433495
{
@@ -452,8 +514,8 @@ func TestLoadYAML(t *testing.T) {
452514
if !errNil && !strings.Contains(err.Error(), tc.errMsg) {
453515
t.Errorf("TestLoadYAML: test case number %d, expected error '%v' to contain '%v'", i+1, err.Error(), tc.errMsg)
454516
}
455-
if errNil && !reflect.DeepEqual(*actual, tc.expected) {
456-
t.Errorf("TestLoadYAML: test case number %d, expected labels %v, got %v", i+1, tc.expected, actual)
517+
if diff := cmp.Diff(actual, &tc.expected, cmpopts.IgnoreUnexported(Label{})); errNil && diff != "" {
518+
t.Errorf("TestLoadYAML: test case number %d, labels differ:%s", i+1, diff)
457519
}
458520
}
459521
}

0 commit comments

Comments
 (0)