From db093cc1d61cf950365f69ace7c0212020add019 Mon Sep 17 00:00:00 2001 From: nikimanoledaki Date: Fri, 6 Jun 2025 15:56:01 +0200 Subject: [PATCH 1/4] Bootstrap CODEOWNERS generator script --- scripts/generate-codeowners/README | 14 +++++++ scripts/generate-codeowners/go.mod | 10 +++++ scripts/generate-codeowners/go.sum | 9 ++++ scripts/generate-codeowners/main.go | 42 +++++++++++++++++++ .../pkg/generator/generator.go | 37 ++++++++++++++++ .../pkg/generator/generator_test.go | 35 ++++++++++++++++ 6 files changed, 147 insertions(+) create mode 100644 scripts/generate-codeowners/README create mode 100644 scripts/generate-codeowners/go.mod create mode 100644 scripts/generate-codeowners/go.sum create mode 100644 scripts/generate-codeowners/main.go create mode 100644 scripts/generate-codeowners/pkg/generator/generator.go create mode 100644 scripts/generate-codeowners/pkg/generator/generator_test.go diff --git a/scripts/generate-codeowners/README b/scripts/generate-codeowners/README new file mode 100644 index 000000000..75b1bcedd --- /dev/null +++ b/scripts/generate-codeowners/README @@ -0,0 +1,14 @@ +# Generate Codeowners + +You will need a JSON file. This can be converted from a YAML or CSV formatted file. + +To convert a CSV file into JSON format: +``` +cat Grafana_TF_Provider_Ownership.csv | python -c 'import csv, json, sys; print(json.dumps([dict(r) for r in csv.DictReader(sys.stdin)]))' > grafana_tf_provider_ownership.json +``` + +## How to run + +``` +go run ./... +``` diff --git a/scripts/generate-codeowners/go.mod b/scripts/generate-codeowners/go.mod new file mode 100644 index 000000000..cc8dd2026 --- /dev/null +++ b/scripts/generate-codeowners/go.mod @@ -0,0 +1,10 @@ +module github.com/grafanalabs/terraform-provider-grafana/scripts/generate-codeowners + +go 1.24.2 + +require ( + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/stretchr/testify v1.10.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/scripts/generate-codeowners/go.sum b/scripts/generate-codeowners/go.sum new file mode 100644 index 000000000..fe99d7116 --- /dev/null +++ b/scripts/generate-codeowners/go.sum @@ -0,0 +1,9 @@ +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/scripts/generate-codeowners/main.go b/scripts/generate-codeowners/main.go new file mode 100644 index 000000000..7600e8820 --- /dev/null +++ b/scripts/generate-codeowners/main.go @@ -0,0 +1,42 @@ +package main + +import ( + "log" + "os" + "path/filepath" + + "github.com/grafanalabs/terraform-provider-grafana/scripts/generate-codeowners/pkg/generator" +) + +const ( + generatedMarker = "# GENERATED BELOW (Regenerate with 'make CODEOWNERS')" +) + +func main() { + repoRoot, err := filepath.Abs("../../.github") + if err != nil { + log.Fatalf("Failed to get absolute path: %v", err) + } + + // Open the CODEOWNERS file for writing + codeownersFilePath := filepath.Join(repoRoot, "CODEOWNERS") + codeownersFile, err := os.OpenFile(codeownersFilePath, os.O_WRONLY, 0644) + if err != nil { + log.Fatalf("Failed to open CODEOWNERS file: %v", err) + } + defer func() { + if err := codeownersFile.Close(); err != nil { + log.Fatalf("Failed to close CODEOWNERS file: %v", err) + } + }() + + pathsToCheck := []string{ + "docs", + filepath.Join("internal", "resources"), + } + + generator := generator.New(repoRoot, codeownersFile) + if err := generator.Generate(pathsToCheck); err != nil { + log.Fatal(err) + } +} diff --git a/scripts/generate-codeowners/pkg/generator/generator.go b/scripts/generate-codeowners/pkg/generator/generator.go new file mode 100644 index 000000000..ad8cf19cc --- /dev/null +++ b/scripts/generate-codeowners/pkg/generator/generator.go @@ -0,0 +1,37 @@ +package generator + +import ( + "io" + "io/fs" + "os" +) + +type RepoRootFs interface { + fs.FS + Root() string +} + +type RepoRootDirFs struct { + fs.FS + root string +} + +func (r *RepoRootDirFs) Root() string { + return r.root +} + +type Generator struct { + fs RepoRootFs + writer io.Writer +} + +func New(repoRoot string, writer io.Writer) *Generator { + return &Generator{ + fs: &RepoRootDirFs{FS: os.DirFS(repoRoot), root: repoRoot}, + writer: writer, + } +} + +func (g *Generator) Generate(pathsToCheck []string) error { + return nil +} diff --git a/scripts/generate-codeowners/pkg/generator/generator_test.go b/scripts/generate-codeowners/pkg/generator/generator_test.go new file mode 100644 index 000000000..14627b165 --- /dev/null +++ b/scripts/generate-codeowners/pkg/generator/generator_test.go @@ -0,0 +1,35 @@ +package generator_test + +import ( + "os" + "testing" + + "github.com/grafanalabs/terraform-provider-grafana/scripts/generate-codeowners/pkg/generator" + "github.com/stretchr/testify/assert" +) + +func TestGenerate(t *testing.T) { + tests := []struct { + name string + wantErr bool + repoRoot string + codeownersFile *os.File + pathsToCheck []string + }{ + { + name: "empty paths", + pathsToCheck: []string{}, + wantErr: false, + repoRoot: ".", + codeownersFile: os.Stdout, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + generator := generator.New(tt.repoRoot, tt.codeownersFile) + err := generator.Generate(tt.pathsToCheck) + assert.NoError(t, err) + }) + } +} From 79fe327f54f173e7703db294b558fe8482a8f01d Mon Sep 17 00:00:00 2001 From: nikimanoledaki Date: Fri, 6 Jun 2025 16:25:55 +0200 Subject: [PATCH 2/4] Read ownership file --- scripts/generate-codeowners/main.go | 17 ++++++++++++++--- .../pkg/generator/generator.go | 12 +++++++----- .../pkg/generator/generator_test.go | 4 +++- 3 files changed, 24 insertions(+), 9 deletions(-) diff --git a/scripts/generate-codeowners/main.go b/scripts/generate-codeowners/main.go index 7600e8820..17287a92f 100644 --- a/scripts/generate-codeowners/main.go +++ b/scripts/generate-codeowners/main.go @@ -13,13 +13,13 @@ const ( ) func main() { - repoRoot, err := filepath.Abs("../../.github") + repoRoot, err := filepath.Abs("../../") if err != nil { log.Fatalf("Failed to get absolute path: %v", err) } // Open the CODEOWNERS file for writing - codeownersFilePath := filepath.Join(repoRoot, "CODEOWNERS") + codeownersFilePath := filepath.Join(repoRoot, ".github", "CODEOWNERS") codeownersFile, err := os.OpenFile(codeownersFilePath, os.O_WRONLY, 0644) if err != nil { log.Fatalf("Failed to open CODEOWNERS file: %v", err) @@ -35,7 +35,18 @@ func main() { filepath.Join("internal", "resources"), } - generator := generator.New(repoRoot, codeownersFile) + ownershipFilePath := filepath.Join(repoRoot, "ownership.json") + ownershipFile, err := os.OpenFile(ownershipFilePath, os.O_RDONLY, 0644) + if err != nil { + log.Fatalf("Failed to open ownership file: %v", err) + } + defer func() { + if err := ownershipFile.Close(); err != nil { + log.Fatalf("Failed to close ownership file: %v", err) + } + }() + + generator := generator.New(repoRoot, codeownersFile, ownershipFile) if err := generator.Generate(pathsToCheck); err != nil { log.Fatal(err) } diff --git a/scripts/generate-codeowners/pkg/generator/generator.go b/scripts/generate-codeowners/pkg/generator/generator.go index ad8cf19cc..69570b9a0 100644 --- a/scripts/generate-codeowners/pkg/generator/generator.go +++ b/scripts/generate-codeowners/pkg/generator/generator.go @@ -21,14 +21,16 @@ func (r *RepoRootDirFs) Root() string { } type Generator struct { - fs RepoRootFs - writer io.Writer + fs RepoRootFs + codeownersWriter io.Writer + ownershipReader io.Reader } -func New(repoRoot string, writer io.Writer) *Generator { +func New(repoRoot string, codeownersFile io.Writer, ownershipFile io.Reader) *Generator { return &Generator{ - fs: &RepoRootDirFs{FS: os.DirFS(repoRoot), root: repoRoot}, - writer: writer, + fs: &RepoRootDirFs{FS: os.DirFS(repoRoot), root: repoRoot}, + codeownersWriter: codeownersFile, + ownershipReader: ownershipFile, } } diff --git a/scripts/generate-codeowners/pkg/generator/generator_test.go b/scripts/generate-codeowners/pkg/generator/generator_test.go index 14627b165..92a9a572a 100644 --- a/scripts/generate-codeowners/pkg/generator/generator_test.go +++ b/scripts/generate-codeowners/pkg/generator/generator_test.go @@ -15,6 +15,7 @@ func TestGenerate(t *testing.T) { repoRoot string codeownersFile *os.File pathsToCheck []string + ownershipFile *os.File }{ { name: "empty paths", @@ -22,12 +23,13 @@ func TestGenerate(t *testing.T) { wantErr: false, repoRoot: ".", codeownersFile: os.Stdout, + ownershipFile: os.Stdout, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - generator := generator.New(tt.repoRoot, tt.codeownersFile) + generator := generator.New(tt.repoRoot, tt.codeownersFile, tt.ownershipFile) err := generator.Generate(tt.pathsToCheck) assert.NoError(t, err) }) From dc0b718477e2af7520a3fe29662643462ca4fc0e Mon Sep 17 00:00:00 2001 From: nikimanoledaki Date: Fri, 27 Jun 2025 15:47:39 +0200 Subject: [PATCH 3/4] Add GH workflow to fetch and generate ownership metadata --- .github/workflows/generate-ownership.yml | 38 ++++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 .github/workflows/generate-ownership.yml diff --git a/.github/workflows/generate-ownership.yml b/.github/workflows/generate-ownership.yml new file mode 100644 index 000000000..763a06cb0 --- /dev/null +++ b/.github/workflows/generate-ownership.yml @@ -0,0 +1,38 @@ +name: Fetch and generate ownership information + +on: + workflow_dispatch: {} + +jobs: + fetch-owners: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v4 + - name: Checkout shared workflows + uses: actions/checkout@v4 + with: + repository: grafana/shared-workflows + path: shared-workflows + - name: Copy Shared Actions + run: | + mkdir -p /home/runner/work/plugins-private/plugins-private/shared-workflows + cp -r $GITHUB_WORKSPACE/shared-workflows/* /home/runner/work/plugins-private/plugins-private/shared-workflows + - name: Get IAP Token + id: get-secrets + uses: ./shared-workflows/actions/get-vault-secrets + with: + # Secrets placed in the ci/common/ path in Vault + common_secrets: | + GCP_IAP_SERVICE_ACCOUNT_KEY_DEV=grafana-com-iap:gcp_iap_service_account_key_ops + - name: gcloud-iap-auth-dev + run: | + printenv GCP_IAP_SERVICE_ACCOUNT_KEY_DEV > /tmp/gcp_service_account_key.json + gcloud auth activate-service-account --key-file=/tmp/gcp_service_account_key.json + gcloud auth print-identity-token \ + --audiences="194555723165-aftshfqa32nig79trcrh96ha94ta46jd.apps.googleusercontent.com" \ + --project "grafanalabs-global" \ + --quiet \ + --verbosity=error > /tmp/iap.token + - name: Generate CODEOWNERS file + run: go run ./scripts/generate-codeowners/... From a00b31dfca3f5e2d149435dfc65efabe26f7969c Mon Sep 17 00:00:00 2001 From: nikimanoledaki Date: Wed, 2 Jul 2025 12:51:37 +0200 Subject: [PATCH 4/4] Read ownership file --- .../pkg/generator/generator.go | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/scripts/generate-codeowners/pkg/generator/generator.go b/scripts/generate-codeowners/pkg/generator/generator.go index 69570b9a0..a8ab5cf3c 100644 --- a/scripts/generate-codeowners/pkg/generator/generator.go +++ b/scripts/generate-codeowners/pkg/generator/generator.go @@ -1,6 +1,7 @@ package generator import ( + "encoding/json" "io" "io/fs" "os" @@ -35,5 +36,20 @@ func New(repoRoot string, codeownersFile io.Writer, ownershipFile io.Reader) *Ge } func (g *Generator) Generate(pathsToCheck []string) error { + g.readOwnershipFile() + return nil +} + +func (g *Generator) readOwnershipFile() error { + ownershipFile, err := io.ReadAll(g.ownershipReader) + if err != nil { + return err + } + + var ownership []map[string]interface{} + if err := json.Unmarshal(ownershipFile, &ownership); err != nil { + return err + } + return nil }