Skip to content
Merged
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
1 change: 1 addition & 0 deletions feature/github-repo-importer/Justfile
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import-repo repoName:
#!/usr/bin/env bash
set -euo pipefail
go run main.go import {{repoName}}
IFS='/' read -r owner repo <<< {{repoName}}
mkdir -p "../../feature/github-repo-provisioning/gcss_config/importer_tmp_dir/"
Expand Down
2 changes: 1 addition & 1 deletion feature/github-repo-importer/cmd/import.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ var importCmd = &cobra.Command{

repo, err := github.ImportRepo(repository)
if err != nil {
return fmt.Errorf("failed to import repo: %w", err)
return err
}

if err := github.WriteRepositoryToYaml(repo); err != nil {
Expand Down
18 changes: 16 additions & 2 deletions feature/github-repo-importer/cmd/root.go
Original file line number Diff line number Diff line change
@@ -1,19 +1,33 @@
package cmd

import (
"fmt"
"os"
"strings"

"github.com/spf13/cobra"
)

var rootCmd = &cobra.Command{
Use: "importer",
Short: "A CLI tool to fetch GitHub repository details, branch protection rules & rulesets",
Use: "importer",
Short: "A CLI tool to fetch GitHub repository details, branch protection rules & rulesets",
SilenceUsage: true,
SilenceErrors: true,
}

func Execute() {
err := rootCmd.Execute()
if err != nil {
_, _ = fmt.Fprintln(os.Stderr, "Error:", err)
if os.Getenv("GITHUB_ACTIONS") == "true" {
_, _ = fmt.Fprintf(os.Stdout, "::error::%s\n", escapeAnnotation(err.Error()))
}
os.Exit(1)
}
}

// escapeAnnotation escapes a message for use in a GitHub Actions workflow command.
func escapeAnnotation(s string) string {
r := strings.NewReplacer("%", "%25", "\r", "%0D", "\n", "%0A")
return r.Replace(s)
}
52 changes: 52 additions & 0 deletions feature/github-repo-importer/cmd/root_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package cmd

import (
"testing"

"github.com/stretchr/testify/assert"
)

func TestEscapeAnnotation(t *testing.T) {
tests := []struct {
name string
in string
want string
}{
{
name: "no special characters",
in: "repository not found",
want: "repository not found",
},
{
name: "percent is escaped",
in: "50% off",
want: "50%25 off",
},
{
name: "newline is escaped",
in: "line1\nline2",
want: "line1%0Aline2",
},
{
name: "carriage return is escaped",
in: "line1\rline2",
want: "line1%0Dline2",
},
{
name: "combined special characters",
in: "50% off\r\nnext line",
want: "50%25 off%0D%0Anext line",
},
{
name: "percent is escaped before its replacement is rescanned",
in: "%0A",
want: "%250A",
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert.Equal(t, tt.want, escapeAnnotation(tt.in))
})
}
}
17 changes: 12 additions & 5 deletions feature/github-repo-importer/pkg/github/github.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ var (
v4client *githubv4.Client
)

// ErrRepoNotFound is returned when the repository to import does not exist or
// the authenticated GitHub App has no access to it (HTTP 404).
var ErrRepoNotFound = errors.New("repository not found")

func InitializeClients() {
var err error
v3client, v4client, err = CreateGitHubClient()
Expand Down Expand Up @@ -66,7 +70,10 @@ func ImportRepo(repoName string) (*Repository, error) {
repoNameSplit := strings.Split(repoName, "/")
repo, r, err := v3client.Repositories.Get(context.Background(), repoNameSplit[0], repoNameSplit[1])
if err != nil {
return nil, fmt.Errorf("failed to fetch repo: %w (API Response: %s)", err, r.Status)
if r != nil && r.StatusCode == http.StatusNotFound {
return nil, fmt.Errorf("%w: %q — check the spelling and that the GitHub App is installed and has access to it", ErrRepoNotFound, repoName)
}
return nil, fmt.Errorf("failed to fetch repo %q: %w", repoName, err)
}

if err := dumpManager.WriteJSONFile("repository.json", repo); err != nil {
Expand Down Expand Up @@ -94,10 +101,10 @@ func ImportRepo(repoName string) (*Repository, error) {

rulesets, r, err := v3client.Repositories.GetAllRulesets(context.Background(), repoNameSplit[0], repoNameSplit[1], false)
if err != nil {
if r.StatusCode == http.StatusForbidden {
if r != nil && r.StatusCode == http.StatusForbidden {
fmt.Printf("skipping rulesets due to insufficient permissions: %v\n", err)
} else {
return nil, fmt.Errorf("failed to get all rulesets: %v", err)
return nil, fmt.Errorf("failed to get all rulesets: %w", err)
}
}

Expand Down Expand Up @@ -134,9 +141,9 @@ func ImportRepo(repoName string) (*Repository, error) {
fmt.Printf("failed to write branch_protection_rules.json: %v\n", err)
}

orgTeams, res, err := v3client.Teams.ListTeams(context.Background(), repoNameSplit[0], &github.ListOptions{PerPage: 100})
orgTeams, _, err := v3client.Teams.ListTeams(context.Background(), repoNameSplit[0], &github.ListOptions{PerPage: 100})
if err != nil {
return nil, fmt.Errorf("failed to get org teams: %w (API Response: %s)", err, res.Status)
return nil, fmt.Errorf("failed to get org teams: %w", err)
}

if err := dumpManager.WriteJSONFile("org_teams.json", orgTeams); err != nil {
Expand Down
Loading