diff --git a/Gopkg.lock b/Gopkg.lock index bc7b8ae..4b33088 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -2,10 +2,10 @@ [[projects]] - name = "github.com/fatih/color" + name = "github.com/dghubble/sling" packages = ["."] - revision = "570b54cabe6b8eb0bc2dfce68d964677d63b5260" - version = "v1.5.0" + revision = "eb56e89ac5088bebb12eef3cb4b293300f43608b" + version = "v1.1.0" [[projects]] name = "github.com/fsnotify/fsnotify" @@ -31,10 +31,32 @@ packages = ["query"] revision = "53e6ce116135b80d037921a7fdd5138cf32d7a8a" +[[projects]] + branch = "master" + name = "github.com/hashicorp/errwrap" + packages = ["."] + revision = "7554cd9344cec97297fa6649b055a8c98c2a1e55" + +[[projects]] + branch = "master" + name = "github.com/hashicorp/go-multierror" + packages = ["."] + revision = "b7773ae218740a7be65057fc60b366a49b538a44" + [[projects]] branch = "master" name = "github.com/hashicorp/hcl" - packages = [".","hcl/ast","hcl/parser","hcl/scanner","hcl/strconv","hcl/token","json/parser","json/scanner","json/token"] + packages = [ + ".", + "hcl/ast", + "hcl/parser", + "hcl/scanner", + "hcl/strconv", + "hcl/token", + "json/parser", + "json/scanner", + "json/token" + ] revision = "23c074d0eceb2b8a5bfdbb271ab780cde70f05a8" [[projects]] @@ -55,18 +77,6 @@ revision = "be5ece7dd465ab0765a9682137865547526d1dfb" version = "v1.7.3" -[[projects]] - name = "github.com/mattn/go-colorable" - packages = ["."] - revision = "167de6bfdfba052fa6b2d3664c8f5272e23c9072" - version = "v0.0.9" - -[[projects]] - name = "github.com/mattn/go-isatty" - packages = ["."] - revision = "0360b2af4f38e8d38c7fce2a9f4e702702d73a39" - version = "v0.0.3" - [[projects]] branch = "master" name = "github.com/mitchellh/go-homedir" @@ -85,12 +95,6 @@ revision = "16398bac157da96aa88f98a2df640c7f32af1da2" version = "v1.0.1" -[[projects]] - name = "github.com/pkg/errors" - packages = ["."] - revision = "645ef00459ed84a119197bfb8d8205042c6df63d" - version = "v0.8.0" - [[projects]] branch = "master" name = "github.com/sergi/go-diff" @@ -106,7 +110,10 @@ [[projects]] branch = "master" name = "github.com/spf13/afero" - packages = [".","mem"] + packages = [ + ".", + "mem" + ] revision = "e67d870304c4bca21331b02f414f970df13aa694" [[projects]] @@ -141,7 +148,12 @@ [[projects]] name = "github.com/src-d/gcfg" - packages = [".","scanner","token","types"] + packages = [ + ".", + "scanner", + "token", + "types" + ] revision = "f187355171c936ac84a82793659ebb4936bc1c23" version = "v1.3.0" @@ -154,48 +166,133 @@ [[projects]] branch = "master" name = "golang.org/x/crypto" - packages = ["curve25519","ed25519","ed25519/internal/edwards25519","ssh","ssh/agent","ssh/knownhosts","ssh/terminal"] + packages = [ + "curve25519", + "ed25519", + "ed25519/internal/edwards25519", + "ssh", + "ssh/agent", + "ssh/knownhosts", + "ssh/terminal" + ] revision = "541b9d50ad47e36efd8fb423e938e59ff1691f68" [[projects]] branch = "master" name = "golang.org/x/net" - packages = ["context","context/ctxhttp"] + packages = [ + "context", + "context/ctxhttp" + ] revision = "aabf50738bcdd9b207582cbe796b59ed65d56680" [[projects]] branch = "master" name = "golang.org/x/oauth2" - packages = [".","internal"] + packages = [ + ".", + "internal" + ] revision = "bb50c06baba3d0c76f9d125c0719093e315b5b44" +[[projects]] + branch = "master" + name = "golang.org/x/sync" + packages = ["errgroup"] + revision = "1d60e4601c6fd243af51cc01ddf169918a5407ca" + [[projects]] branch = "master" name = "golang.org/x/sys" - packages = ["unix","windows"] + packages = [ + "unix", + "windows" + ] revision = "8dbc5d05d6edcc104950cc299a1ce6641235bc86" [[projects]] branch = "master" name = "golang.org/x/text" - packages = ["internal/gen","internal/triegen","internal/ucd","transform","unicode/cldr","unicode/norm"] + packages = [ + "internal/gen", + "internal/triegen", + "internal/ucd", + "transform", + "unicode/cldr", + "unicode/norm" + ] revision = "c01e4764d870b77f8abe5096ee19ad20d80e8075" [[projects]] name = "google.golang.org/appengine" - packages = ["internal","internal/base","internal/datastore","internal/log","internal/remote_api","internal/urlfetch","urlfetch"] + packages = [ + "internal", + "internal/base", + "internal/datastore", + "internal/log", + "internal/remote_api", + "internal/urlfetch", + "urlfetch" + ] revision = "150dc57a1b433e64154302bdc40b6bb8aefa313a" version = "v1.0.0" [[projects]] name = "gopkg.in/src-d/go-billy.v3" - packages = [".","helper/chroot","helper/polyfill","osfs","util"] + packages = [ + ".", + "helper/chroot", + "helper/polyfill", + "osfs", + "util" + ] revision = "c329b7bc7b9d24905d2bc1b85bfa29f7ae266314" version = "v3.1.0" [[projects]] name = "gopkg.in/src-d/go-git.v4" - packages = [".","config","internal/revision","plumbing","plumbing/cache","plumbing/filemode","plumbing/format/config","plumbing/format/diff","plumbing/format/gitignore","plumbing/format/idxfile","plumbing/format/index","plumbing/format/objfile","plumbing/format/packfile","plumbing/format/pktline","plumbing/object","plumbing/protocol/packp","plumbing/protocol/packp/capability","plumbing/protocol/packp/sideband","plumbing/revlist","plumbing/storer","plumbing/transport","plumbing/transport/client","plumbing/transport/file","plumbing/transport/git","plumbing/transport/http","plumbing/transport/internal/common","plumbing/transport/server","plumbing/transport/ssh","storage","storage/filesystem","storage/filesystem/internal/dotgit","storage/memory","utils/binary","utils/diff","utils/ioutil","utils/merkletrie","utils/merkletrie/filesystem","utils/merkletrie/index","utils/merkletrie/internal/frame","utils/merkletrie/noder"] + packages = [ + ".", + "config", + "internal/revision", + "plumbing", + "plumbing/cache", + "plumbing/filemode", + "plumbing/format/config", + "plumbing/format/diff", + "plumbing/format/gitignore", + "plumbing/format/idxfile", + "plumbing/format/index", + "plumbing/format/objfile", + "plumbing/format/packfile", + "plumbing/format/pktline", + "plumbing/object", + "plumbing/protocol/packp", + "plumbing/protocol/packp/capability", + "plumbing/protocol/packp/sideband", + "plumbing/revlist", + "plumbing/storer", + "plumbing/transport", + "plumbing/transport/client", + "plumbing/transport/file", + "plumbing/transport/git", + "plumbing/transport/http", + "plumbing/transport/internal/common", + "plumbing/transport/server", + "plumbing/transport/ssh", + "storage", + "storage/filesystem", + "storage/filesystem/internal/dotgit", + "storage/memory", + "utils/binary", + "utils/diff", + "utils/ioutil", + "utils/merkletrie", + "utils/merkletrie/filesystem", + "utils/merkletrie/index", + "utils/merkletrie/internal/frame", + "utils/merkletrie/noder" + ] revision = "f9879dd043f84936a1f8acb8a53b74332a7ae135" [[projects]] @@ -213,6 +310,6 @@ [solve-meta] analyzer-name = "dep" analyzer-version = 1 - inputs-digest = "74409bc505c82184b7cffcf7c38d6cfaae03606133fe0fab8ae8317b9ad9aa65" + inputs-digest = "c9c94ffb5300f9337d72ee59d3bcf8aae0c65542cf2f77bf82c5b7edf7f27d5a" solver-name = "gps-cdcl" solver-version = 1 diff --git a/Gopkg.toml b/Gopkg.toml index bfabb86..3c97442 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -37,5 +37,13 @@ revision = "f9879dd043f84936a1f8acb8a53b74332a7ae135" [[constraint]] - name = "github.com/fatih/color" - version = "1.5.0" + branch = "master" + name = "github.com/hashicorp/go-multierror" + +[[constraint]] + branch = "master" + name = "github.com/hashicorp/errwrap" + +[[constraint]] + name = "github.com/dghubble/sling" + version = "1.1.0" diff --git a/cmd/hiring.go b/cmd/hiring.go index 9d00296..a37cc7a 100644 --- a/cmd/hiring.go +++ b/cmd/hiring.go @@ -1,17 +1,21 @@ package cmd -import "github.com/spf13/cobra" +import ( + "context" + + "github.com/spf13/cobra" +) // NewHiringCmd aggregates the hiring comamnds -func NewHiringCmd() *cobra.Command { +func NewHiringCmd(ctx context.Context) *cobra.Command { // Repo commands cmd := &cobra.Command{ Use: "hiring", Short: "Github hiring tests repository management", } - cmd.AddCommand(NewHiringSendCmd()) - cmd.AddCommand(NewHiringUnseat()) + cmd.AddCommand(NewHiringSendCmd(ctx)) + cmd.AddCommand(NewHiringUnseat(ctx)) return cmd } diff --git a/cmd/hiring_send.go b/cmd/hiring_send.go index 9defcb1..cc87697 100644 --- a/cmd/hiring_send.go +++ b/cmd/hiring_send.go @@ -1,14 +1,17 @@ package cmd import ( + "context" + "errors" "fmt" "os" "github.com/google/go-github/github" + "github.com/hashicorp/errwrap" "github.com/hellofresh/github-cli/pkg/config" + gh "github.com/hellofresh/github-cli/pkg/github" + "github.com/hellofresh/github-cli/pkg/log" "github.com/hellofresh/github-cli/pkg/repo" - "github.com/pkg/errors" - log "github.com/sirupsen/logrus" "github.com/spf13/cobra" "gopkg.in/src-d/go-git.v4" "gopkg.in/src-d/go-git.v4/storage/memory" @@ -16,21 +19,17 @@ import ( type ( // HiringSendOpts are the flags for the send a hiring test command - HiringSendOpts struct { - Org string - } + HiringSendOpts struct{} ) // NewHiringSendCmd creates a new send hiring test command -func NewHiringSendCmd() *cobra.Command { - opts := &HiringSendOpts{} +func NewHiringSendCmd(ctx context.Context) *cobra.Command { cmd := &cobra.Command{ - Use: "send [username] [repo]", - Short: "Creates a new hellofresh hiring test", - Long: `Creates a new hellofresh hiring test based on the rules defined on your .github.toml`, - PreRunE: setupConnection, + Use: "send [username] [repo]", + Short: "Creates a new hellofresh hiring test", + Long: `Creates a new hellofresh hiring test based on the rules defined on your .github.toml`, RunE: func(cmd *cobra.Command, args []string) error { - return RunCreateTestRepo(args[0], args[1], opts) + return RunCreateTestRepo(ctx, args[0], args[1]) }, Args: func(cmd *cobra.Command, args []string) error { if len(args) < 1 || args[0] == "" { @@ -45,29 +44,31 @@ func NewHiringSendCmd() *cobra.Command { }, } - cmd.Flags().StringVarP(&opts.Org, "organization", "o", "", "Github's organization") - return cmd } // RunCreateTestRepo runs the command to create a new hiring test repository -func RunCreateTestRepo(candidate string, testRepo string, opts *HiringSendOpts) error { +func RunCreateTestRepo(ctx context.Context, candidate string, testRepo string) error { var err error - org := opts.Org - if org == "" { - org = globalConfig.GithubTestOrg.Organization + logger := log.WithContext(ctx) + cfg := config.WithContext(ctx) + githubClient := gh.WithContext(ctx) + if githubClient == nil { + return errors.New("failed to get github client") } + + org := cfg.Github.Organization if org == "" { - return errors.New("Please provide an organization") + return errors.New("please provide an organization") } target := fmt.Sprintf("%s-%s", candidate, testRepo) creator := repo.NewGithub(githubClient) - log.Info("Creating repository...") - _, err = creator.CreateRepo(org, &github.Repository{ + logger.Info("Creating repository...") + _, err = creator.CreateRepo(ctx, org, &github.Repository{ Name: github.String(target), Private: github.Bool(true), HasIssues: github.Bool(false), @@ -75,47 +76,47 @@ func RunCreateTestRepo(candidate string, testRepo string, opts *HiringSendOpts) HasWiki: github.Bool(false), }) if err != nil { - return errors.Wrap(err, "Could not create github repo for candidate") + return errwrap.Wrapf("could not create github repo for candidate: {{err}}", err) } - log.Info("Adding collaborators to repository...") + logger.Info("Adding collaborators to repository...") collaboratorsOpts := []*config.Collaborator{ &config.Collaborator{ Username: candidate, Permission: "push", }, } - err = creator.AddCollaborators(target, org, collaboratorsOpts) + err = creator.AddCollaborators(ctx, target, org, collaboratorsOpts) if err != nil { - return errors.Wrap(err, "Could not add collaborators to repository") + return errwrap.Wrapf("could not add collaborators to repository: {{err}}", err) } - log.Info("Cloning repository...") + logger.Info("Cloning repository...") r, err := git.Clone(memory.NewStorage(), nil, &git.CloneOptions{ Progress: os.Stdout, - URL: fmt.Sprintf("https://%s@github.com/%s/%s", globalConfig.GithubTestOrg.Token, org, testRepo), + URL: fmt.Sprintf("https://%s@github.com/%s/%s", cfg.GithubTestOrg.Token, org, testRepo), }) if err != nil { - return errors.Wrap(err, "Error cloning to repository") + return errwrap.Wrapf("error cloning to repository: {{err}}", err) } - log.Info("Changing remote...") + logger.Info("Changing remote...") remote, err := r.Remote(git.DefaultRemoteName) if err != nil { - return errors.Wrap(err, "Error changing remote for repository") + return errwrap.Wrapf("error changing remote for repository: {{err}}", err) } - log.Info("Pushing changes...") - remote.Config().URLs = []string{fmt.Sprintf("https://%s@github.com/%s/%s", globalConfig.GithubTestOrg.Token, org, target)} + logger.Info("Pushing changes...") + remote.Config().URLs = []string{fmt.Sprintf("https://%s@github.com/%s/%s", cfg.GithubTestOrg.Token, org, target)} err = remote.Push(&git.PushOptions{ RemoteName: git.DefaultRemoteName, Progress: os.Stdout, }) if err != nil { - return errors.Wrap(err, "Error pushing to repository") + return errwrap.Wrapf("error pushing to repository: {{err}}", err) } - log.Infof("Done! Hiring test for %s is created", candidate) + logger.Infof("Done! Hiring test for %s is created", candidate) return nil } diff --git a/cmd/hiring_unseat.go b/cmd/hiring_unseat.go index 946883b..1c5b5b8 100644 --- a/cmd/hiring_unseat.go +++ b/cmd/hiring_unseat.go @@ -2,12 +2,16 @@ package cmd import ( "context" + "errors" "math" "time" "github.com/google/go-github/github" - "github.com/pkg/errors" - log "github.com/sirupsen/logrus" + "github.com/hashicorp/errwrap" + "github.com/hellofresh/github-cli/pkg/config" + gh "github.com/hellofresh/github-cli/pkg/github" + "github.com/hellofresh/github-cli/pkg/log" + "github.com/sirupsen/logrus" "github.com/spf13/cobra" ) @@ -19,27 +23,24 @@ const ( type ( // UnseatOpts are the flags for the unseat command UnseatOpts struct { - Org string Page int ReposPerPage int } ) // NewHiringUnseat creates a new hiring unseat command -func NewHiringUnseat() *cobra.Command { +func NewHiringUnseat(ctx context.Context) *cobra.Command { opts := &UnseatOpts{} cmd := &cobra.Command{ - Use: "unseat", - Short: "Removes external collaborators from repositories", - Long: `Removes external (people not in the organization) collaborators from repositories`, - PreRunE: setupConnection, + Use: "unseat", + Short: "Removes external collaborators from repositories", + Long: `Removes external (people not in the organization) collaborators from repositories`, RunE: func(cmd *cobra.Command, args []string) error { - return RunUnseat(opts) + return RunUnseat(ctx, opts) }, } - cmd.Flags().StringVarP(&opts.Org, "organization", "o", "", "Github's organization") cmd.Flags().IntVar(&opts.ReposPerPage, "page-size", 50, "How many repositories should we get per page? (max 100)") cmd.Flags().IntVar(&opts.Page, "page", 1, "Starting page for repositories") @@ -47,68 +48,77 @@ func NewHiringUnseat() *cobra.Command { } // RunUnseat runs the command to create a new hiring test repository -func RunUnseat(opts *UnseatOpts) error { +func RunUnseat(ctx context.Context, opts *UnseatOpts) error { var unseatedCollaborators int - ctx := context.Background() - org := opts.Org - if org == "" { - org = globalConfig.GithubTestOrg.Organization + logger := log.WithContext(ctx) + cfg := config.WithContext(ctx) + githubClient := gh.WithContext(ctx) + if githubClient == nil { + return errors.New("failed to get github client") } + + org := cfg.Github.Organization if org == "" { - return errors.New("Please provide an organization") + return errors.New("please provide an organization") } - log.Info("Fetching repositories...") - allRepos, err := fetchAllRepos(org, opts.ReposPerPage, opts.Page) + logger.Info("Fetching repositories...") + allRepos, err := fetchAllRepos(ctx, org, opts.ReposPerPage, opts.Page) if err != nil { - return errors.Wrap(err, "Could not retrieve repositories") + return errwrap.Wrapf("could not retrieve repositories: {{err}}", err) } - log.Infof("%d repositories fetched!", len(allRepos)) + logger.Infof("%d repositories fetched!", len(allRepos)) - log.Info("Removing outside colaborators...") + logger.Info("Removing outside colaborators...") for _, repo := range allRepos { if isRepoInactive(repo) { continue } repoName := *repo.Name - log.WithField("repo", repoName).Debug("Fetching outside collaborators") + logger.WithField("repo", repoName).Debug("Fetching outside collaborators") outsideCollaborators, _, err := githubClient.Repositories.ListCollaborators(ctx, org, repoName, &github.ListCollaboratorsOptions{ Affiliation: "outside", }) if err != nil { - return errors.Wrap(err, "Could not retrieve outside collaborators") + return errwrap.Wrapf("could not retrieve outside collaborators: {{err}}", err) } for _, collaborator := range outsideCollaborators { - log.WithFields(log.Fields{ + logger.WithFields(logrus.Fields{ "repo": repoName, "collaborator": collaborator.GetLogin(), }).Info("Deleting outside collaborators") _, err := githubClient.Repositories.RemoveCollaborator(ctx, org, repoName, collaborator.GetLogin()) if err != nil { - return errors.Wrap(err, "Could not unseat outside collaborator") + return errwrap.Wrapf("could not unseat outside collaborator: {{err}}", err) } unseatedCollaborators++ } } - log.Infof("Done! %d outside collaborators unseated", unseatedCollaborators) + logger.Infof("Done! %d outside collaborators unseated", unseatedCollaborators) return nil } -func fetchAllRepos(owner string, reposPerPage int, page int) ([]*github.Repository, error) { +func fetchAllRepos(ctx context.Context, owner string, reposPerPage int, page int) ([]*github.Repository, error) { var allRepos []*github.Repository + logger := log.WithContext(ctx) + githubClient := gh.WithContext(ctx) + if githubClient == nil { + return nil, errors.New("failed to get github client") + } + opt := &github.RepositoryListByOrgOptions{ ListOptions: github.ListOptions{PerPage: reposPerPage, Page: page}, } for { - log.Debugf("Fetching repositories page [%d]", opt.Page) - repos, resp, err := githubClient.Repositories.ListByOrg(context.Background(), owner, opt) + logger.Debugf("Fetching repositories page [%d]", opt.Page) + repos, resp, err := githubClient.Repositories.ListByOrg(ctx, owner, opt) if err != nil { return allRepos, err } diff --git a/cmd/repo.go b/cmd/repo.go index bb97a1f..cc738ba 100644 --- a/cmd/repo.go +++ b/cmd/repo.go @@ -1,17 +1,21 @@ package cmd -import "github.com/spf13/cobra" +import ( + "context" + + "github.com/spf13/cobra" +) // NewRepoCmd aggregates the repo comamnds -func NewRepoCmd() *cobra.Command { +func NewRepoCmd(ctx context.Context) *cobra.Command { // Repo commands cmd := &cobra.Command{ Use: "repo", Short: "Github repository management", } - cmd.AddCommand(NewCreateRepoCmd()) - cmd.AddCommand(NewDeleteRepoCmd()) + cmd.AddCommand(NewCreateRepoCmd(ctx)) + cmd.AddCommand(NewDeleteRepoCmd(ctx)) return cmd } diff --git a/cmd/repo_create.go b/cmd/repo_create.go index 701f44c..8b3026c 100644 --- a/cmd/repo_create.go +++ b/cmd/repo_create.go @@ -1,19 +1,23 @@ package cmd import ( - "github.com/fatih/color" + "context" + "errors" + "github.com/google/go-github/github" + "github.com/hashicorp/errwrap" + "github.com/hellofresh/github-cli/pkg/config" + gh "github.com/hellofresh/github-cli/pkg/github" + "github.com/hellofresh/github-cli/pkg/log" "github.com/hellofresh/github-cli/pkg/pullapprove" "github.com/hellofresh/github-cli/pkg/repo" - "github.com/pkg/errors" - log "github.com/sirupsen/logrus" "github.com/spf13/cobra" + "golang.org/x/sync/errgroup" ) // CreateRepoOptions are the flags for the create repository command type CreateRepoOptions struct { Description string - Org string Private bool HasPullApprove bool HasTeams bool @@ -28,20 +32,19 @@ type CreateRepoOptions struct { } // NewCreateRepoCmd creates a new create repo command -func NewCreateRepoCmd() *cobra.Command { +func NewCreateRepoCmd(ctx context.Context) *cobra.Command { opts := &CreateRepoOptions{} cmd := &cobra.Command{ - Use: "create [name]", - Short: "Creates a new github repository", - Long: `Creates a new github repository based on the rules defined on your .github.toml`, - PreRunE: setupConnection, + Use: "create [name]", + Short: "Creates a new github repository", + Long: `Creates a new github repository based on the rules defined on your .github.toml`, RunE: func(cmd *cobra.Command, args []string) error { - return RunCreateRepo(args[0], opts) + return RunCreateRepo(ctx, args[0], opts) }, Args: func(cmd *cobra.Command, args []string) error { if len(args) < 1 || args[0] == "" { - return errors.New("Please provide a repository name") + return errors.New("please provide a repository name") } return nil @@ -49,7 +52,6 @@ func NewCreateRepoCmd() *cobra.Command { } cmd.Flags().StringVarP(&opts.Description, "description", "d", "", "The repository's description") - cmd.Flags().StringVarP(&opts.Org, "organization", "o", "", "Github's organization") cmd.Flags().BoolVar(&opts.Private, "private", true, "Is the repository private?") cmd.Flags().BoolVar(&opts.HasIssues, "has-issues", true, "Enables issue pages") @@ -66,36 +68,40 @@ func NewCreateRepoCmd() *cobra.Command { } // RunCreateRepo runs the command to create a new repository -func RunCreateRepo(repoName string, opts *CreateRepoOptions) error { - var err error - var org string - - description := opts.Description +func RunCreateRepo(ctx context.Context, repoName string, opts *CreateRepoOptions) error { + wg, ctx := errgroup.WithContext(ctx) + logger := log.WithContext(ctx) + cfg := config.WithContext(ctx) + githubClient := gh.WithContext(ctx) + if githubClient == nil { + return errors.New("failed to get github client") + } - org = opts.Org + org := cfg.Github.Organization if org == "" { - org = globalConfig.Github.Organization + return errors.New("please provide an organization") } + description := opts.Description githubOpts := &repo.GithubRepoOpts{ PullApprove: &repo.PullApproveOpts{ - Filename: globalConfig.PullApprove.Filename, - ProtectedBranchName: globalConfig.PullApprove.ProtectedBranchName, - Client: pullapprove.New(globalConfig.PullApprove.Token), + Filename: cfg.PullApprove.Filename, + ProtectedBranchName: cfg.PullApprove.ProtectedBranchName, + Client: pullapprove.New(cfg.PullApprove.Token), }, Labels: &repo.LabelsOpts{ - RemoveDefaultLabels: globalConfig.Github.RemoveDefaultLabels, - Labels: globalConfig.Github.Labels, + RemoveDefaultLabels: cfg.Github.RemoveDefaultLabels, + Labels: cfg.Github.Labels, }, - Teams: globalConfig.Github.Teams, - Webhooks: globalConfig.Github.Webhooks, - BranchProtections: globalConfig.Github.Protections, + Teams: cfg.Github.Teams, + Webhooks: cfg.Github.Webhooks, + BranchProtections: cfg.Github.Protections, } creator := repo.NewGithub(githubClient) - log.Info("Creating repository...") - ghRepo, err := creator.CreateRepo(org, &github.Repository{ + logger.Info("Creating repository...") + ghRepo, err := creator.CreateRepo(ctx, org, &github.Repository{ Name: github.String(repoName), Description: github.String(description), Private: github.Bool(opts.Private), @@ -104,70 +110,96 @@ func RunCreateRepo(repoName string, opts *CreateRepoOptions) error { HasPages: github.Bool(opts.HasPages), AutoInit: github.Bool(true), }) - if errors.Cause(err) == repo.ErrRepositoryAlreadyExists { - log.Info("Repository already exists. Trying to normalize it...") + if errwrap.ContainsType(err, repo.ErrRepositoryAlreadyExists) { + logger.Info("Repository already exists. Trying to normalize it...") } else if err != nil { - return errors.Wrap(err, "Could not create repository") + return errwrap.Wrapf("could not create repository: {{err}}", err) } if opts.HasPullApprove { - log.Info("Adding pull approve...") - err = creator.AddPullApprove(repoName, org, githubOpts.PullApprove) - if errors.Cause(err) == repo.ErrPullApproveFileAlreadyExists { - color.Cyan("Pull approve already exists, moving on...") - } else if err != nil { - return errors.Wrap(err, "Could not add pull approve") - } + wg.Go(func() error { + logger.Info("Adding pull approve...") + err = creator.AddPullApprove(ctx, repoName, org, githubOpts.PullApprove) + if errwrap.ContainsType(err, repo.ErrPullApproveFileAlreadyExists) { + logger.Debug("Pull approve already exists, moving on...") + } else if err != nil { + return errwrap.Wrapf("could not add pull approve: {{err}}", err) + } + + return nil + }) } if opts.HasTeams { - log.Info("Adding teams to repository...") - err = creator.AddTeamsToRepo(repoName, org, githubOpts.Teams) - if err != nil { - return errors.Wrap(err, "Could not add teams to repository") - } + wg.Go(func() error { + logger.Info("Adding teams to repository...") + err = creator.AddTeamsToRepo(ctx, repoName, org, githubOpts.Teams) + if err != nil { + return errwrap.Wrapf("could not add teams to repository: {{err}}", err) + } + + return nil + }) } if opts.HasCollaborators { - log.Info("Adding collaborators to repository...") - err = creator.AddCollaborators(repoName, org, githubOpts.Collaborators) - if err != nil { - return errors.Wrap(err, "Could not add collaborators to repository") - } + wg.Go(func() error { + logger.Info("Adding collaborators to repository...") + if err = creator.AddCollaborators(ctx, repoName, org, githubOpts.Collaborators); err != nil { + return errwrap.Wrapf("could not add collaborators to repository: {{err}}", err) + } + + return nil + }) } if opts.HasLabels { - log.Info("Adding labels to repository...") - err = creator.AddLabelsToRepo(repoName, org, githubOpts.Labels) - if errors.Cause(err) == repo.ErrLabelNotFound { - color.Cyan("Default labels does not exists, moving on...") - } else if err != nil { - return errors.Wrap(err, "Could not add labels to repository") - } + wg.Go(func() error { + logger.Info("Adding labels to repository...") + err = creator.AddLabelsToRepo(ctx, repoName, org, githubOpts.Labels) + if errwrap.ContainsType(err, repo.ErrLabeAlreadyExists) { + logger.Debug("Labels already exists, moving on...") + } else if err != nil { + return errwrap.Wrapf("could not add labels to repository: {{err}}", err) + } + + return nil + }) } if opts.HasWebhooks { - log.Info("Adding webhooks to repository...") - err = creator.AddWebhooksToRepo(repoName, org, githubOpts.Webhooks) - if errors.Cause(err) == repo.ErrWebhookAlreadyExist { - color.Cyan("Webhook already exists, moving on...") - } else if err != nil { - return errors.Wrap(err, "Could not add webhooks to repository") - } + wg.Go(func() error { + logger.Info("Adding webhooks to repository...") + err = creator.AddWebhooksToRepo(ctx, repoName, org, githubOpts.Webhooks) + if errwrap.ContainsType(err, repo.ErrWebhookAlreadyExist) { + logger.Debug("Webhook already exists, moving on...") + } else if err != nil { + return errwrap.Wrapf("could not add webhooks to repository: {{err}}", err) + } + + return nil + }) } if opts.HasBranchProtections { - log.Info("Adding branch protections to repository...") - err = creator.AddBranchProtections(repoName, org, githubOpts.BranchProtections) - if err != nil { - return errors.Wrap(err, "Could not add branch protections to repository") - } + wg.Go(func() error { + logger.Info("Adding branch protections to repository...") + if err = creator.AddBranchProtections(ctx, repoName, org, githubOpts.BranchProtections); err != nil { + return errwrap.Wrapf("could not add branch protections to repository: {{err}}", err) + } + + return nil + }) + } + + if err := wg.Wait(); err != nil { + return err } if ghRepo != nil { - log.Infof("Repository created! \n Here is how to access it %s", ghRepo.GetGitURL()) + logger.Infof("Repository created! \n Here is how to access it %s", ghRepo.GetGitURL()) } else { - log.Info("Repository normalized!") + logger.Info("Repository normalized!") } return nil diff --git a/cmd/repo_delete.go b/cmd/repo_delete.go index bf30984..f3cbc27 100644 --- a/cmd/repo_delete.go +++ b/cmd/repo_delete.go @@ -2,53 +2,55 @@ package cmd import ( "context" + "errors" - "github.com/pkg/errors" - log "github.com/sirupsen/logrus" + "github.com/hashicorp/errwrap" + "github.com/hellofresh/github-cli/pkg/config" + gh "github.com/hellofresh/github-cli/pkg/github" + "github.com/hellofresh/github-cli/pkg/log" "github.com/spf13/cobra" ) type ( // DeleteRepoOpts are the flags for the delete repo command - DeleteRepoOpts struct { - Org string - Name string - } + DeleteRepoOpts struct{} ) // NewDeleteRepoCmd creates a new delete repo command -func NewDeleteRepoCmd() *cobra.Command { +func NewDeleteRepoCmd(ctx context.Context) *cobra.Command { opts := &DeleteRepoOpts{} cmd := &cobra.Command{ Use: "delete", Short: "Deletes a github repository", RunE: func(cmd *cobra.Command, args []string) error { - return RunDeleteRepo(opts) + return RunDeleteRepo(ctx, args[0], opts) }, + Args: cobra.MinimumNArgs(1), } - cmd.Flags().StringVarP(&opts.Name, "name", "n", "", "The name of the repository") - cmd.Flags().StringVarP(&opts.Org, "organization", "o", "", "Github's organization") - return cmd } // RunDeleteRepo runs the command to delete a repository -func RunDeleteRepo(opts *DeleteRepoOpts) error { - org := opts.Org - if org == "" { - org = globalConfig.Github.Organization +func RunDeleteRepo(ctx context.Context, name string, opts *DeleteRepoOpts) error { + logger := log.WithContext(ctx) + cfg := config.WithContext(ctx) + githubClient := gh.WithContext(ctx) + if githubClient == nil { + return errors.New("failed to get github client") } + + org := cfg.Github.Organization if org == "" { - return errors.New("Please provide an organization") + return errors.New("please provide an organization") } - _, err := githubClient.Repositories.Delete(context.Background(), org, opts.Name) + _, err := githubClient.Repositories.Delete(ctx, org, name) if err != nil { - return errors.Wrap(err, "Could not delete repository") + return errwrap.Wrapf("Could not delete repository: {{err}}", err) } - log.Info("Repository %s deleted!", opts.Name) + logger.Infof("Repository %s deleted!", name) return nil } diff --git a/cmd/root.go b/cmd/root.go index 4018a06..51f2e51 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -3,66 +3,69 @@ package cmd import ( "context" - "github.com/google/go-github/github" "github.com/hellofresh/github-cli/pkg/config" - "github.com/hellofresh/github-cli/pkg/formatter" - "github.com/pkg/errors" - log "github.com/sirupsen/logrus" + "github.com/hellofresh/github-cli/pkg/github" + "github.com/hellofresh/github-cli/pkg/log" + "github.com/sirupsen/logrus" "github.com/spf13/cobra" - "golang.org/x/oauth2" ) -var ( - cfgFile string - globalConfig *config.Spec - githubClient *github.Client - token string - verbose bool +type ( + // RootOptions represents the ahoy global options + RootOptions struct { + configFile string + token string + org string + verbose bool + } +) - // RootCmd is our main command - RootCmd = &cobra.Command{ +// NewRootCmd creates the root command +func NewRootCmd() *cobra.Command { + opts := RootOptions{} + + cmd := cobra.Command{ Use: "github-cli [--config] [--token]", Short: "HF Github is a cli tool to manage your github repositories", - Long: `A simple CLI tool to help you manage your github repositories. - Complete documentation is available at https://github.com/hellofresh/github-cli`, + PersistentPreRun: func(ccmd *cobra.Command, args []string) { + if opts.verbose { + log.WithContext(context.Background()).SetLevel(logrus.DebugLevel) + } + }, } -) -func init() { - RootCmd.PersistentFlags().StringVarP(&cfgFile, "config", "c", "", "config file (default is $HOME/.github.toml)") - RootCmd.PersistentFlags().StringVarP(&token, "token", "t", "", "optional, github token for authentication (default in $HOME/.github.toml)") - RootCmd.PersistentFlags().BoolVarP(&verbose, "verbose", "v", false, "Make the operation more talkative") + cmd.PersistentFlags().StringVarP(&opts.configFile, "config", "c", "", "config file (default is $HOME/.github.toml)") + cmd.PersistentFlags().StringVarP(&opts.token, "token", "t", "", "optional, github token for authentication (default in $HOME/.github.toml)") + cmd.PersistentFlags().BoolVarP(&opts.verbose, "verbose", "v", false, "Make the operation more talkative") + cmd.PersistentFlags().StringVarP(&opts.org, "organization", "o", "", "Github's organization") - // Aggregates Root commands - RootCmd.AddCommand(NewRepoCmd()) - RootCmd.AddCommand(NewHiringCmd()) - RootCmd.AddCommand(NewVersionCmd()) - - log.SetFormatter(&formatter.CliFormatter{}) -} - -func setupConnection(cmd *cobra.Command, args []string) error { - var err error + ctx := log.NewContext(context.Background()) + ctx, err := config.NewContext(ctx, opts.configFile) + if err != nil { + log.WithContext(ctx).WithError(err).Error("Could not load configuration file") + } - if verbose { - log.SetLevel(log.DebugLevel) + cfg := config.WithContext(ctx) + if opts.token != "" { + cfg.Github.Token = opts.token + cfg.GithubTestOrg.Token = opts.token } - globalConfig, err = config.Load(cfgFile) - if err != nil { - return errors.Wrap(err, "Could not load the configurations") + if opts.org != "" { + cfg.Github.Organization = opts.org + cfg.GithubTestOrg.Organization = opts.org } + ctx = config.OverrideConfig(ctx, cfg) - if token != "" { - globalConfig.Github.Token = token - globalConfig.GithubTestOrg.Token = token + ctx, err = github.NewContext(ctx, cfg.Github.Token) + if err != nil { + log.WithContext(ctx).WithError(err).Fatal("Could not create the kube client") } - ts := oauth2.StaticTokenSource( - &oauth2.Token{AccessToken: globalConfig.Github.Token}, - ) - tc := oauth2.NewClient(context.Background(), ts) - githubClient = github.NewClient(tc) + // Aggregates Root commands + cmd.AddCommand(NewRepoCmd(ctx)) + cmd.AddCommand(NewHiringCmd(ctx)) + cmd.AddCommand(NewVersionCmd(ctx)) - return nil + return &cmd } diff --git a/cmd/version.go b/cmd/version.go index b067141..751fc64 100644 --- a/cmd/version.go +++ b/cmd/version.go @@ -1,25 +1,23 @@ package cmd import ( - log "github.com/sirupsen/logrus" + "context" + + "github.com/hellofresh/github-cli/pkg/log" "github.com/spf13/cobra" ) var version = "0.0.0-dev" // NewVersionCmd creates a new version command -func NewVersionCmd() *cobra.Command { +func NewVersionCmd(ctx context.Context) *cobra.Command { return &cobra.Command{ Use: "version", Short: "Print the version information", Aliases: []string{"v"}, Run: func(cmd *cobra.Command, args []string) { - RunVersion() + logger := log.WithContext(ctx) + logger.Infof("github-cli %s", version) }, } } - -// RunVersion runs the command to print the current version -func RunVersion() { - log.Infof("github-cli %s", version) -} diff --git a/main.go b/main.go index 6bf232a..dd0da00 100644 --- a/main.go +++ b/main.go @@ -8,7 +8,7 @@ import ( ) func main() { - if err := cmd.RootCmd.Execute(); err != nil { + if err := cmd.NewRootCmd().Execute(); err != nil { fmt.Println(err) os.Exit(1) } diff --git a/pkg/config/config.go b/pkg/config/config.go index 3b58236..a9d27af 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -1,14 +1,19 @@ package config import ( + "context" + "fmt" "os" - "os/user" - log "github.com/sirupsen/logrus" + "github.com/hashicorp/errwrap" + "github.com/hellofresh/github-cli/pkg/log" + homedir "github.com/mitchellh/go-homedir" "github.com/spf13/viper" ) type ( + configKeyType int + // Spec represents the global app configuration Spec struct { Github Github @@ -64,38 +69,62 @@ type ( } ) -// Load loads all the configurations -func Load(cfgFile string) (*Spec, error) { - // Don't forget to read config either from cfgFile or from home directory! - if cfgFile != "" { +const configKey configKeyType = iota + +// NewContext loads a configuration file into the Spec struct +func NewContext(ctx context.Context, configFile string) (context.Context, error) { + logger := log.WithContext(ctx) + + if configFile != "" { + if _, err := os.Stat(configFile); os.IsNotExist(err) { + return ctx, fmt.Errorf("invalid configuration file provided %s", configFile) + } // Use config file from the flag. - viper.SetConfigFile(cfgFile) + viper.SetConfigFile(configFile) } else { + homeDir, err := homedir.Dir() + if err != nil { + return ctx, err + } + viper.SetConfigName(".github") viper.AddConfigPath(".") - viper.AddConfigPath(homeDir()) + viper.AddConfigPath(homeDir) } + logger.Debugf("Reading config from %s...", viper.ConfigFileUsed()) + viper.SetDefault("github.token", os.Getenv("GITHUB_TOKEN")) viper.SetDefault("githubtestorg.token", os.Getenv("GITHUB_TOKEN")) - if err := viper.ReadInConfig(); err != nil { - return nil, err + err := viper.ReadInConfig() + if err != nil { + return ctx, errwrap.Wrapf("could not read configurations: {{err}}", err) } - var config *Spec - if err := viper.Unmarshal(&config); err != nil { - return nil, err + config := Spec{} + err = viper.Unmarshal(&config) + if err != nil { + return ctx, errwrap.Wrapf("could not unmarshal config file: {{err}}", err) } - return config, nil + return context.WithValue(ctx, configKey, &config), nil } -func homeDir() string { - usr, err := user.Current() - if err != nil { - log.Fatal(err) +// WithContext returns a logrus logger from the context +func WithContext(ctx context.Context) *Spec { + if ctx == nil { + return nil + } + + if ctxConfig, ok := ctx.Value(configKey).(*Spec); ok { + return ctxConfig } - return usr.HomeDir + return nil +} + +// OverrideConfig writes a new context with the the new configuration +func OverrideConfig(ctx context.Context, config *Spec) context.Context { + return context.WithValue(ctx, configKey, config) } diff --git a/pkg/github/github.go b/pkg/github/github.go new file mode 100644 index 0000000..e5d28a0 --- /dev/null +++ b/pkg/github/github.go @@ -0,0 +1,35 @@ +package github + +import ( + "context" + + "github.com/google/go-github/github" + "golang.org/x/oauth2" +) + +type githubKeyType int + +const githubKey githubKeyType = iota + +// NewContext returns a context with the github client imported +func NewContext(ctx context.Context, token string) (context.Context, error) { + ts := oauth2.StaticTokenSource( + &oauth2.Token{AccessToken: token}, + ) + tc := oauth2.NewClient(ctx, ts) + + return context.WithValue(ctx, githubKey, github.NewClient(tc)), nil +} + +// WithContext returns a github client from the context +func WithContext(ctx context.Context) *github.Client { + if ctx == nil { + return nil + } + + if ctxKube, ok := ctx.Value(githubKey).(*github.Client); ok { + return ctxKube + } + + return nil +} diff --git a/pkg/log/log.go b/pkg/log/log.go new file mode 100644 index 0000000..823d174 --- /dev/null +++ b/pkg/log/log.go @@ -0,0 +1,42 @@ +package log + +import ( + "context" + "os" + + "github.com/hellofresh/github-cli/pkg/formatter" + "github.com/sirupsen/logrus" +) + +type loggerKeyType int + +const loggerKey loggerKeyType = iota + +var logger logrus.Logger + +func init() { + logger = logrus.Logger{ + Out: os.Stderr, + Formatter: &formatter.CliFormatter{}, + Hooks: make(logrus.LevelHooks), + Level: logrus.InfoLevel, + } +} + +// NewContext returns a context that has a logrus logger +func NewContext(ctx context.Context) context.Context { + return context.WithValue(ctx, loggerKey, WithContext(ctx)) +} + +// WithContext returns a logrus logger from the context +func WithContext(ctx context.Context) *logrus.Logger { + if ctx == nil { + return &logger + } + + if ctxLogger, ok := ctx.Value(loggerKey).(*logrus.Logger); ok { + return ctxLogger + } + + return &logger +} diff --git a/pkg/pullapprove/client.go b/pkg/pullapprove/client.go index e0ea00c..b66d5c7 100644 --- a/pkg/pullapprove/client.go +++ b/pkg/pullapprove/client.go @@ -1,38 +1,80 @@ package pullapprove import ( - "bytes" + "errors" "fmt" "net/http" - "github.com/pkg/errors" + "github.com/dghubble/sling" + "github.com/hashicorp/errwrap" ) -// Client represents a pull approve client -type Client struct { - BaseURL string - Token string -} +type ( + // Client represents a pull approve client + Client struct { + Token string + client *sling.Sling + } + + createReq struct { + Name string `json:"name"` + } +) + +var ( + // ErrPullApproveUnauthorized is used when we receive a 401 from pull approve + ErrPullApproveUnauthorized = errors.New("you do not have permissions to use pull approve API") + + // ErrPullApproveServerError is used when we receive a code different than 200 from pull approve + ErrPullApproveServerError = errors.New("could not create a pull approve repository") +) // New creates a new instance of Client func New(token string) *Client { return &Client{ - BaseURL: "https://pullapprove.com/api", - Token: token, + Token: token, + client: sling.New().Base("https://pullapprove.com/api/"), } } // Create creates a new repository on pull approve -func (c *Client) Create(name string, organization string) error { - body := []byte(fmt.Sprintf(`{"name": "%s"}`, name)) - req, _ := http.NewRequest("POST", fmt.Sprintf("%s/orgs/%s/repos/", c.BaseURL, organization), bytes.NewBuffer(body)) +func (c *Client) Create(name string, org string) error { + path := fmt.Sprintf("orgs/%s/repos/", org) + cr := createReq{Name: name} + + req, err := c.client.Post(path).BodyJSON(&cr).Request() + if err != nil { + return errwrap.Wrapf("could not create the request to pull approve: {{err}}", err) + } + return c.doRequest(req) +} + +// Delete deletes a repository from pull approve +func (c *Client) Delete(name string, org string) error { + path := fmt.Sprintf("orgs/%s/%s/delete", org, name) + req, err := c.client.Post(path).Request() + if err != nil { + return errwrap.Wrapf("could not delete the request from pull approve: {{err}}", err) + } + + return c.doRequest(req) +} + +func (c *Client) doRequest(req *http.Request) error { req.Header.Add("Authorization", fmt.Sprintf("Token %s", c.Token)) - req.Header.Add("Content-Type", "application/json") - res, err := http.DefaultClient.Do(req) - if err != nil && res.StatusCode != http.StatusOK { - return errors.Wrap(err, "Could not create a pull approve repository") + resp, err := c.client.Do(req, nil, nil) + if err != nil { + return err + } + + if resp.StatusCode == http.StatusUnauthorized { + return ErrPullApproveUnauthorized + } + + if code := resp.StatusCode; code >= 400 { + return ErrPullApproveServerError } return nil diff --git a/pkg/repo/github.go b/pkg/repo/github.go index b1060b9..358c071 100644 --- a/pkg/repo/github.go +++ b/pkg/repo/github.go @@ -2,13 +2,15 @@ package repo import ( "context" + "errors" + "fmt" "net/http" "strings" "github.com/google/go-github/github" + multierror "github.com/hashicorp/go-multierror" "github.com/hellofresh/github-cli/pkg/config" "github.com/hellofresh/github-cli/pkg/pullapprove" - "github.com/pkg/errors" ) type ( @@ -42,7 +44,6 @@ type ( ) var ( - ctx = context.Background() // ErrRepositoryAlreadyExists is used when the repository already exists ErrRepositoryAlreadyExists = errors.New("github repository already exists") // ErrRepositoryLimitExceeded is used when the repository limit is exceeded @@ -51,10 +52,14 @@ var ( ErrPullApproveFileAlreadyExists = errors.New("github pull approve file already exists") // ErrLabelNotFound is used when a label is not found ErrLabelNotFound = errors.New("github label does not exist") + // ErrLabeAlreadyExists is used when a label is not found + ErrLabeAlreadyExists = errors.New("github label already exists") // ErrWebhookAlreadyExist is used when a webhook already exists ErrWebhookAlreadyExist = errors.New("github webhook already exists") // ErrOrganizationNotFound is used when a webhook already exists ErrOrganizationNotFound = errors.New("you must specify an organization to use this functionality") + // ErrPullApproveClientNotSet is used when the PA client is nil + ErrPullApproveClientNotSet = errors.New("Cannot add pull approve, since the client is nil") ) // NewGithub creates a new instance of Client @@ -65,7 +70,7 @@ func NewGithub(githubClient *github.Client) *GithubRepo { } // CreateRepo creates a github repository -func (c *GithubRepo) CreateRepo(org string, repoOpts *github.Repository) (*github.Repository, error) { +func (c *GithubRepo) CreateRepo(ctx context.Context, org string, repoOpts *github.Repository) (*github.Repository, error) { ghRepo, _, err := c.GithubClient.Repositories.Create(ctx, org, repoOpts) if githubError, ok := err.(*github.ErrorResponse); ok { if strings.Contains(githubError.Message, "Visibility can't be private") { @@ -79,23 +84,21 @@ func (c *GithubRepo) CreateRepo(org string, repoOpts *github.Repository) (*githu } // AddPullApprove adds a file to the github repository and calls pull approve API to register the new repo -func (c *GithubRepo) AddPullApprove(repo string, org string, opts *PullApproveOpts) error { - var err error +func (c *GithubRepo) AddPullApprove(ctx context.Context, repo string, org string, opts *PullApproveOpts) error { if opts.Client == nil { - return errors.New("Cannot add pull approve, since the client is nil") + return ErrPullApproveClientNotSet } fileOpt := &github.RepositoryContentFileOptions{ Message: github.String("Initialize repository :tada:"), - Content: []byte("extends: hellofresh"), + Content: []byte(fmt.Sprintf("extends: %s", org)), Branch: github.String(opts.ProtectedBranchName), } - _, _, err = c.GithubClient.Repositories.CreateFile(ctx, org, repo, opts.Filename, fileOpt) + _, _, err := c.GithubClient.Repositories.CreateFile(ctx, org, repo, opts.Filename, fileOpt) if githubError, ok := err.(*github.ErrorResponse); ok { if githubError.Response.StatusCode == http.StatusUnprocessableEntity { return ErrPullApproveFileAlreadyExists } - } else { return err } @@ -108,7 +111,7 @@ func (c *GithubRepo) AddPullApprove(repo string, org string, opts *PullApproveOp } // AddTeamsToRepo adds an slice of teams and their permissions to a repository -func (c *GithubRepo) AddTeamsToRepo(repo string, org string, teams []*config.Team) error { +func (c *GithubRepo) AddTeamsToRepo(ctx context.Context, repo string, org string, teams []*config.Team) error { var err error if org == "" { @@ -120,7 +123,9 @@ func (c *GithubRepo) AddTeamsToRepo(repo string, org string, teams []*config.Tea Permission: team.Permission, } - _, err = c.GithubClient.Organizations.AddTeamRepo(ctx, team.ID, org, repo, opt) + if _, ghErr := c.GithubClient.Organizations.AddTeamRepo(ctx, team.ID, org, repo, opt); ghErr != nil { + err = multierror.Append(err, ghErr) + } } return err @@ -128,7 +133,7 @@ func (c *GithubRepo) AddTeamsToRepo(repo string, org string, teams []*config.Tea // AddLabelsToRepo adds an slice of labels to the repository. Optionally this can also remove github's // default labels -func (c *GithubRepo) AddLabelsToRepo(repo string, org string, opts *LabelsOpts) error { +func (c *GithubRepo) AddLabelsToRepo(ctx context.Context, repo string, org string, opts *LabelsOpts) error { var err error defaultLabels := []string{"bug", "duplicate", "enhancement", "help wanted", "invalid", "question", "wontfix", "good first issue"} @@ -138,15 +143,22 @@ func (c *GithubRepo) AddLabelsToRepo(repo string, org string, opts *LabelsOpts) Color: github.String(label.Color), } - _, _, err = c.GithubClient.Issues.CreateLabel(ctx, org, repo, githubLabel) + if _, _, ghErr := c.GithubClient.Issues.CreateLabel(ctx, org, repo, githubLabel); ghErr != nil { + if internalErr, ok := ghErr.(*github.ErrorResponse); ok { + if internalErr.Response.StatusCode == http.StatusUnprocessableEntity { + err = multierror.Append(err, ErrLabeAlreadyExists) + } + } + } } if opts.RemoveDefaultLabels { for _, label := range defaultLabels { - _, err = c.GithubClient.Issues.DeleteLabel(ctx, org, repo, label) - if githubError, ok := err.(*github.ErrorResponse); ok { - if githubError.Response.StatusCode == http.StatusNotFound { - err = errors.Wrap(ErrLabelNotFound, "label not found") + if _, ghErr := c.GithubClient.Issues.DeleteLabel(ctx, org, repo, label); ghErr != nil { + if internalErr, ok := ghErr.(*github.ErrorResponse); ok { + if internalErr.Response.StatusCode == http.StatusNotFound { + err = multierror.Append(err, ErrLabelNotFound) + } } } } @@ -156,7 +168,7 @@ func (c *GithubRepo) AddLabelsToRepo(repo string, org string, opts *LabelsOpts) } // AddWebhooksToRepo adds an slice of webhooks to the repository -func (c *GithubRepo) AddWebhooksToRepo(repo string, org string, webhooks []*config.Webhook) error { +func (c *GithubRepo) AddWebhooksToRepo(ctx context.Context, repo string, org string, webhooks []*config.Webhook) error { var err error for _, webhook := range webhooks { @@ -165,9 +177,9 @@ func (c *GithubRepo) AddWebhooksToRepo(repo string, org string, webhooks []*conf Config: webhook.Config, } _, _, err = c.GithubClient.Repositories.CreateHook(ctx, org, repo, hook) - if githubError, ok := err.(*github.ErrorResponse); ok { - if githubError.Response.StatusCode == http.StatusUnprocessableEntity { - err = errors.Wrap(ErrWebhookAlreadyExist, "webhook already exists") + if ghErr, ok := err.(*github.ErrorResponse); ok { + if ghErr.Response.StatusCode == http.StatusUnprocessableEntity { + err = multierror.Append(err, ErrWebhookAlreadyExist) } } } @@ -176,7 +188,7 @@ func (c *GithubRepo) AddWebhooksToRepo(repo string, org string, webhooks []*conf } // AddBranchProtections adds an slice of branch protections to the repository -func (c *GithubRepo) AddBranchProtections(repo string, org string, protections config.BranchProtections) error { +func (c *GithubRepo) AddBranchProtections(ctx context.Context, repo string, org string, protections config.BranchProtections) error { var err error for branch, contexts := range protections { @@ -185,14 +197,16 @@ func (c *GithubRepo) AddBranchProtections(repo string, org string, protections c Contexts: contexts, }, } - _, _, err = c.GithubClient.Repositories.UpdateBranchProtection(ctx, org, repo, branch, pr) + if _, _, ghErr := c.GithubClient.Repositories.UpdateBranchProtection(ctx, org, repo, branch, pr); ghErr != nil { + err = multierror.Append(err, ghErr) + } } return err } // AddCollaborators adds an slice of collaborators and their permissions to the repository -func (c *GithubRepo) AddCollaborators(repo string, org string, collaborators []*config.Collaborator) error { +func (c *GithubRepo) AddCollaborators(ctx context.Context, repo string, org string, collaborators []*config.Collaborator) error { var err error for _, collaborator := range collaborators { @@ -200,7 +214,9 @@ func (c *GithubRepo) AddCollaborators(repo string, org string, collaborators []* Permission: collaborator.Permission, } - _, err = c.GithubClient.Repositories.AddCollaborator(ctx, org, repo, collaborator.Username, opt) + if _, ghErr := c.GithubClient.Repositories.AddCollaborator(ctx, org, repo, collaborator.Username, opt); ghErr != nil { + err = multierror.Append(err, ghErr) + } } return err