diff --git a/Makefile b/Makefile index 98b3b977d0..14f7d28073 100644 --- a/Makefile +++ b/Makefile @@ -144,4 +144,4 @@ dbschema: ## generate database schema with schema spy, monitor file until doc is mock: mockgen -package mockdb -destination database/mock/store.go github.com/stacklok/mediator/internal/db Store - mockgen -package mockgh -destination internal/providers/github/mock/github.go -source internal/providers/github/github.go RestAPI + mockgen -package mockgh -destination internal/providers/github/mock/github.go -source pkg/providers/v1/providers.go GitHub diff --git a/cmd/dev/app/rule_type/rule_type.go b/cmd/dev/app/rule_type/rule_type.go index 05c2b00567..629064fb89 100644 --- a/cmd/dev/app/rule_type/rule_type.go +++ b/cmd/dev/app/rule_type/rule_type.go @@ -30,10 +30,11 @@ import ( "google.golang.org/protobuf/reflect/protoreflect" "github.com/stacklok/mediator/cmd/dev/app" + "github.com/stacklok/mediator/internal/db" "github.com/stacklok/mediator/internal/engine" "github.com/stacklok/mediator/internal/engine/eval/rego" "github.com/stacklok/mediator/internal/entities" - ghclient "github.com/stacklok/mediator/internal/providers/github" + "github.com/stacklok/mediator/internal/providers" "github.com/stacklok/mediator/internal/util" pb "github.com/stacklok/mediator/pkg/api/protobuf/go/mediator/v1" ) @@ -105,12 +106,19 @@ func testCmdRun(cmd *cobra.Command, _ []string) error { return fmt.Errorf("error getting relevant fragment: %w", err) } - client, err := getProviderClient(context.Background(), rt, token) - if err != nil { - return fmt.Errorf("error getting provider client: %w", err) - } - - eng, err := engine.NewRuleTypeEngine(rt, client, token) + // TODO: Read this from a providers file instead so we can make it pluggable + eng, err := engine.NewRuleTypeEngine(rt, providers.NewProviderBuilder( + &db.Provider{ + Name: "test", + Implements: []db.ProviderType{ + "rest", + "git", + "github", + }, + }, + db.ProviderAccessToken{}, + token, + )) if err != nil { return fmt.Errorf("error creating rule type engine: %w", err) } @@ -201,18 +209,3 @@ func readEntityFromFile(fpath string, entType pb.Entity) (protoreflect.ProtoMess return out, nil } - -// getProviderClient returns a client for the provider specified in the rule type -// definition. -// TODO: This should be moved to a provider package and we should have some -// generic interface for clients. -func getProviderClient(ctx context.Context, rt *pb.RuleType, token string) (ghclient.RestAPI, error) { - switch rt.Context.Provider { - case ghclient.Github: - return ghclient.NewRestClient(ctx, ghclient.GitHubConfig{ - Token: token, - }, "") - default: - return nil, fmt.Errorf("unknown provider: %s", rt.Context.Provider) - } -} diff --git a/database/migrations/000001_init.up.sql b/database/migrations/000001_init.up.sql index 5d28a5c11f..f19d3fc952 100644 --- a/database/migrations/000001_init.up.sql +++ b/database/migrations/000001_init.up.sql @@ -350,4 +350,4 @@ INSERT INTO user_roles (user_id, role_id) VALUES (1, 1); -- Create default GitHub provider INSERT INTO providers (name, group_id, implements, definition) -VALUES ('github', 1, ARRAY ['github', 'git', 'rest']::provider_type[], '{}'); +VALUES ('github', 1, ARRAY ['github', 'git', 'rest']::provider_type[], '{"github": {}}'); diff --git a/internal/container/container.go b/internal/container/container.go index 16c0102644..75a3397ad3 100644 --- a/internal/container/container.go +++ b/internal/container/container.go @@ -44,9 +44,9 @@ import ( "google.golang.org/protobuf/proto" "google.golang.org/protobuf/types/known/timestamppb" - ghclient "github.com/stacklok/mediator/internal/providers/github" "github.com/stacklok/mediator/internal/util" pb "github.com/stacklok/mediator/pkg/api/protobuf/go/mediator/v1" + provifv1 "github.com/stacklok/mediator/pkg/providers/v1" ) // REGISTRY is the default registry @@ -80,7 +80,7 @@ var ( // GetArtifactSignatureAndWorkflowInfo returns the signature and workflow information as raw JSON for a given artifact func GetArtifactSignatureAndWorkflowInfo( ctx context.Context, - cli ghclient.RestAPI, + cli provifv1.Provider, ownerLogin, artifactName, versionName string, ) (sigInfo json.RawMessage, workflowInfo json.RawMessage, err error) { imageRef := artifactImageRef("", ownerLogin, artifactName, versionName) diff --git a/internal/controlplane/handlers_githubwebhooks.go b/internal/controlplane/handlers_githubwebhooks.go index ccf0c92fe9..ab7717b13a 100644 --- a/internal/controlplane/handlers_githubwebhooks.go +++ b/internal/controlplane/handlers_githubwebhooks.go @@ -49,9 +49,9 @@ import ( "github.com/stacklok/mediator/internal/db" "github.com/stacklok/mediator/internal/engine" "github.com/stacklok/mediator/internal/providers" - ghclient "github.com/stacklok/mediator/internal/providers/github" "github.com/stacklok/mediator/internal/util" pb "github.com/stacklok/mediator/pkg/api/protobuf/go/mediator/v1" + provifv1 "github.com/stacklok/mediator/pkg/providers/v1" ) // CONTAINER_TYPE is the type for container artifacts @@ -134,7 +134,7 @@ func (s *Server) HandleGitHubWebHook() http.HandlerFunc { // TODO: extract sender and event time from payload portably m := message.NewMessage(uuid.New().String(), nil) m.Metadata.Set("id", github.DeliveryID(r)) - m.Metadata.Set("provider", ghclient.Github) + m.Metadata.Set("provider", string(db.ProviderTypeGithub)) m.Metadata.Set("source", "https://api.github.com/") // TODO: handle other sources m.Metadata.Set("type", github.WebHookType(r)) @@ -374,11 +374,23 @@ func (s *Server) parseArtifactPublishedEvent( return fmt.Errorf("error getting provider: %w", err) } - cli, err := providers.BuildClient(ctx, dbrepo.Provider, g, s.store, s.cryptoEngine) + p, err := providers.GetProviderBuilder(ctx, prov, g, s.store, s.cryptoEngine) if err != nil { return fmt.Errorf("error building client: %w", err) } + // NOTE(jaosorior): this webhook is very specific to github + if !p.Implements(db.ProviderTypeGithub) { + log.Printf("provider %s is not supported for github webhook", p.GetName()) + return nil + } + + cli, err := p.GetGitHub(ctx) + if err != nil { + log.Printf("error creating github provider: %v", err) + return nil + } + versionedArtifact, err := gatherVersionedArtifact(ctx, cli, s.store, whPayload) if err != nil { return fmt.Errorf("error gathering versioned artifact: %w", err) @@ -419,11 +431,23 @@ func (s *Server) parsePullRequestModEvent( return fmt.Errorf("error getting provider: %w", err) } - cli, err := providers.BuildClient(ctx, prov.Name, g, s.store, s.cryptoEngine) + p, err := providers.GetProviderBuilder(ctx, prov, g, s.store, s.cryptoEngine) if err != nil { return fmt.Errorf("error building client: %w", err) } + // NOTE(jaosorior): this webhook is very specific to github + if !p.Implements(db.ProviderTypeGithub) { + log.Printf("provider %s is not supported for github webhook", p.GetName()) + return nil + } + + cli, err := p.GetGitHub(ctx) + if err != nil { + log.Printf("error creating github provider: %v", err) + return nil + } + prEvalInfo, err := getPullRequestInfoFromPayload(ctx, whPayload) if err != nil { return fmt.Errorf("error getting pull request information from payload: %w", err) @@ -502,7 +526,7 @@ func extractArtifactVersionFromPayload(ctx context.Context, payload map[string]a func gatherArtifactInfo( ctx context.Context, - client ghclient.RestAPI, + client provifv1.GitHub, payload map[string]any, ) (*pb.Artifact, error) { artifact, err := extractArtifactFromPayload(ctx, payload) @@ -561,7 +585,7 @@ func lookUpVersionBySignature( func gatherArtifactVersionInfo( ctx context.Context, - cli ghclient.RestAPI, + cli provifv1.GitHub, payload map[string]any, artifactOwnerLogin, artifactName string, ) (*pb.ArtifactVersion, error) { @@ -582,7 +606,7 @@ func gatherArtifactVersionInfo( func gatherVersionedArtifact( ctx context.Context, - cli ghclient.RestAPI, + cli provifv1.GitHub, store db.Store, payload map[string]any, ) (*pb.VersionedArtifact, error) { @@ -623,7 +647,7 @@ func gatherVersionedArtifact( func storeSignatureAndWorkflowInVersion( ctx context.Context, - client ghclient.RestAPI, + client provifv1.GitHub, artifactOwnerLogin, artifactName, packageVersionName string, version *pb.ArtifactVersion, ) error { @@ -651,7 +675,7 @@ func storeSignatureAndWorkflowInVersion( func updateArtifactVersionFromRegistry( ctx context.Context, - client ghclient.RestAPI, + client provifv1.GitHub, payload map[string]any, artifactOwnerLogin, artifactName string, version *pb.ArtifactVersion, @@ -827,7 +851,7 @@ func getPullRequestInfoFromPayload( func updatePullRequestInfoFromProvider( ctx context.Context, - cli ghclient.RestAPI, + cli provifv1.GitHub, dbrepo db.Repository, prEvalInfo *pb.PullRequest, ) error { diff --git a/internal/controlplane/handlers_organization.go b/internal/controlplane/handlers_organization.go index 2dbccaeb70..99a108b67b 100644 --- a/internal/controlplane/handlers_organization.go +++ b/internal/controlplane/handlers_organization.go @@ -162,8 +162,7 @@ func (s *Server) CreateOrganization(ctx context.Context, Name: github.Github, GroupID: grp.GroupId, Implements: github.Implements, - // TODO add actual definition - Definition: json.RawMessage("{}"), + Definition: json.RawMessage(`{"github": {}}`), }) if err != nil { return nil, status.Errorf(codes.Internal, "failed to create provider: %v", err) diff --git a/internal/controlplane/handlers_repositories.go b/internal/controlplane/handlers_repositories.go index a480d4075c..aa615e5d69 100644 --- a/internal/controlplane/handlers_repositories.go +++ b/internal/controlplane/handlers_repositories.go @@ -32,6 +32,7 @@ import ( github "github.com/stacklok/mediator/internal/providers/github" "github.com/stacklok/mediator/internal/reconcilers" pb "github.com/stacklok/mediator/pkg/api/protobuf/go/mediator/v1" + provifv1 "github.com/stacklok/mediator/pkg/providers/v1" ) // RegisterRepository adds repositories to the database and registers a webhook @@ -405,9 +406,7 @@ func (s *Server) SyncRepositories(ctx context.Context, in *pb.SyncRepositoriesRe } // Populate the database with the repositories using the GraphQL API - client, err := github.NewRestClient(ctx, github.GitHubConfig{ - Token: token.AccessToken, - }, owner_filter) + client, err := github.NewRestClient(ctx, &provifv1.GitHubConfig{}, token.AccessToken, owner_filter) if err != nil { return nil, status.Errorf(codes.Internal, "cannot create github client: %v", err) } diff --git a/internal/engine/eval/eval.go b/internal/engine/eval/eval.go index 075be24d76..128f85b4ca 100644 --- a/internal/engine/eval/eval.go +++ b/internal/engine/eval/eval.go @@ -24,12 +24,12 @@ import ( "github.com/stacklok/mediator/internal/engine/eval/rego" "github.com/stacklok/mediator/internal/engine/eval/vulncheck" engif "github.com/stacklok/mediator/internal/engine/interfaces" - ghclient "github.com/stacklok/mediator/internal/providers/github" + "github.com/stacklok/mediator/internal/providers" pb "github.com/stacklok/mediator/pkg/api/protobuf/go/mediator/v1" ) // NewRuleEvaluator creates a new rule data evaluator -func NewRuleEvaluator(rt *pb.RuleType, cli ghclient.RestAPI) (engif.Evaluator, error) { +func NewRuleEvaluator(rt *pb.RuleType, cli *providers.ProviderBuilder) (engif.Evaluator, error) { e := rt.Def.GetEval() if e == nil { return nil, fmt.Errorf("rule type missing eval configuration") diff --git a/internal/engine/eval/vulncheck/actions.go b/internal/engine/eval/vulncheck/actions.go index 6bb88cc167..e0150225c6 100644 --- a/internal/engine/eval/vulncheck/actions.go +++ b/internal/engine/eval/vulncheck/actions.go @@ -21,8 +21,8 @@ import ( "github.com/google/go-github/v53/github" - ghclient "github.com/stacklok/mediator/internal/providers/github" pb "github.com/stacklok/mediator/pkg/api/protobuf/go/mediator/v1" + provifv1 "github.com/stacklok/mediator/pkg/providers/v1" ) type prStatusHandler interface { @@ -38,7 +38,7 @@ func newPrStatusHandler( ctx context.Context, action action, pr *pb.PullRequest, - client ghclient.RestAPI, + client provifv1.GitHub, ) (prStatusHandler, error) { switch action { case actionReviewPr: diff --git a/internal/engine/eval/vulncheck/review.go b/internal/engine/eval/vulncheck/review.go index bf9151380f..1524a44510 100644 --- a/internal/engine/eval/vulncheck/review.go +++ b/internal/engine/eval/vulncheck/review.go @@ -27,8 +27,8 @@ import ( "github.com/google/go-github/v53/github" "github.com/rs/zerolog" - ghclient "github.com/stacklok/mediator/internal/providers/github" pb "github.com/stacklok/mediator/pkg/api/protobuf/go/mediator/v1" + provifv1 "github.com/stacklok/mediator/pkg/providers/v1" ) const ( @@ -100,7 +100,7 @@ func countLeadingWhitespace(line string) int { func locateDepInPr( _ context.Context, - client ghclient.RestAPI, + client provifv1.GitHub, dep *pb.PrDependencies_ContextualDependency, ) (*reviewLocation, error) { req, err := client.NewRequest("GET", dep.File.PatchUrl, nil) @@ -138,7 +138,7 @@ func reviewBodyWithSuggestion(comment string) string { } type reviewPrHandler struct { - cli ghclient.RestAPI + cli provifv1.GitHub pr *pb.PullRequest mediatorReview *github.PullRequestReview @@ -163,7 +163,7 @@ func withVulnsFoundReviewStatus(status *string) reviewPrHandlerOption { func newReviewPrHandler( ctx context.Context, pr *pb.PullRequest, - cli ghclient.RestAPI, + cli provifv1.GitHub, opts ...reviewPrHandlerOption, ) (*reviewPrHandler, error) { if pr == nil { @@ -352,7 +352,7 @@ type commitStatusPrHandler struct { func newCommitStatusPrHandler( ctx context.Context, pr *pb.PullRequest, - client ghclient.RestAPI, + client provifv1.GitHub, ) (prStatusHandler, error) { // create a reviewPrHandler and embed it in the commitStatusPrHandler rph, err := newReviewPrHandler( diff --git a/internal/engine/eval/vulncheck/review_test.go b/internal/engine/eval/vulncheck/review_test.go index 9c75790b9f..83a7dedd14 100644 --- a/internal/engine/eval/vulncheck/review_test.go +++ b/internal/engine/eval/vulncheck/review_test.go @@ -45,7 +45,7 @@ func TestReviewPrHandlerNoVulnerabilities(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() - mockClient := mock_ghclient.NewMockRestAPI(ctrl) + mockClient := mock_ghclient.NewMockGitHub(ctrl) pr := &pb.PullRequest{ Url: "https://api.github.com/repos/jakubtestorg/bad-npm/pulls/43", CommitSha: commitSHA, @@ -85,7 +85,7 @@ func TestReviewPrHandlerVulnerabilitiesDifferentIdentities(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() - mockClient := mock_ghclient.NewMockRestAPI(ctrl) + mockClient := mock_ghclient.NewMockGitHub(ctrl) pr := &pb.PullRequest{ Url: "https://api.github.com/repos/jakubtestorg/bad-npm/pulls/43", CommitSha: commitSHA, @@ -171,7 +171,7 @@ func TestReviewPrHandlerVulnerabilitiesDismissReview(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() - mockClient := mock_ghclient.NewMockRestAPI(ctrl) + mockClient := mock_ghclient.NewMockGitHub(ctrl) pr := &pb.PullRequest{ Url: "https://api.github.com/repos/jakubtestorg/bad-npm/pulls/43", CommitSha: commitSHA, @@ -222,7 +222,7 @@ func TestCommitStatusHandlerNoVulnerabilities(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() - mockClient := mock_ghclient.NewMockRestAPI(ctrl) + mockClient := mock_ghclient.NewMockGitHub(ctrl) pr := &pb.PullRequest{ Url: "https://api.github.com/repos/jakubtestorg/bad-npm/pulls/43", CommitSha: commitSHA, @@ -269,7 +269,7 @@ func TestCommitStatusPrHandlerWithVulnerabilities(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() - mockClient := mock_ghclient.NewMockRestAPI(ctrl) + mockClient := mock_ghclient.NewMockGitHub(ctrl) pr := &pb.PullRequest{ Url: "https://api.github.com/repos/jakubtestorg/bad-npm/pulls/43", CommitSha: commitSHA, diff --git a/internal/engine/eval/vulncheck/vulncheck.go b/internal/engine/eval/vulncheck/vulncheck.go index 3e63ed74ca..b69ed0a7cf 100644 --- a/internal/engine/eval/vulncheck/vulncheck.go +++ b/internal/engine/eval/vulncheck/vulncheck.go @@ -20,8 +20,9 @@ import ( "fmt" engif "github.com/stacklok/mediator/internal/engine/interfaces" - ghclient "github.com/stacklok/mediator/internal/providers/github" + "github.com/stacklok/mediator/internal/providers" pb "github.com/stacklok/mediator/pkg/api/protobuf/go/mediator/v1" + provifv1 "github.com/stacklok/mediator/pkg/providers/v1" ) const ( @@ -31,13 +32,22 @@ const ( // Evaluator is the vulncheck evaluator type Evaluator struct { - cli ghclient.RestAPI + cli provifv1.GitHub } // NewVulncheckEvaluator creates a new vulncheck evaluator -func NewVulncheckEvaluator(_ *pb.RuleType_Definition_Eval_Vulncheck, cli ghclient.RestAPI) (*Evaluator, error) { +func NewVulncheckEvaluator(_ *pb.RuleType_Definition_Eval_Vulncheck, pbuild *providers.ProviderBuilder) (*Evaluator, error) { + if pbuild == nil { + return nil, fmt.Errorf("provider builder is nil") + } + + ghcli, err := pbuild.GetGitHub(context.Background()) + if err != nil { + return nil, fmt.Errorf("failed to get github client: %w", err) + } + return &Evaluator{ - cli: cli, + cli: ghcli, }, nil } diff --git a/internal/engine/executor.go b/internal/engine/executor.go index e50ec42758..8887fcd897 100644 --- a/internal/engine/executor.go +++ b/internal/engine/executor.go @@ -27,7 +27,6 @@ import ( "github.com/stacklok/mediator/internal/db" "github.com/stacklok/mediator/internal/events" "github.com/stacklok/mediator/internal/providers" - ghclient "github.com/stacklok/mediator/internal/providers/github" pb "github.com/stacklok/mediator/pkg/api/protobuf/go/mediator/v1" ) @@ -68,12 +67,6 @@ func (e *Executor) HandleEntityEvent(msg *message.Message) error { return fmt.Errorf("error unmarshalling payload: %w", err) } - // TODO(jaosorior): get provider from database - if inf.Provider != ghclient.Github { - log.Printf("provider %s not supported", inf.Provider) - return nil - } - ctx := msg.Context() // get group info @@ -91,7 +84,7 @@ func (e *Executor) HandleEntityEvent(msg *message.Message) error { return fmt.Errorf("error getting provider: %w", err) } - cli, err := providers.BuildClient(ctx, provider.Name, inf.GroupID, e.querier, e.crypteng) + cli, err := providers.GetProviderBuilder(ctx, provider, inf.GroupID, e.querier, e.crypteng) if err != nil { return fmt.Errorf("error building client: %w", err) } @@ -114,7 +107,7 @@ func (e *Executor) evalEntityEvent( ctx context.Context, inf *EntityInfoWrapper, ectx *EntityContext, - cli ghclient.RestAPI, + cli *providers.ProviderBuilder, ) error { // Get policies relevant to group dbpols, err := e.querier.ListPoliciesByGroupID(ctx, inf.GroupID) @@ -132,7 +125,7 @@ func (e *Executor) evalEntityEvent( // Let's evaluate all the rules for this policy err = TraverseRules(relevant, func(rule *pb.Policy_Rule) error { - rt, rte, err := e.getEvaluator(ctx, *pol.Id, ectx.Provider.Name, cli, cli.GetToken(), ectx, rule) + rt, rte, err := e.getEvaluator(ctx, *pol.Id, ectx.Provider.Name, cli, ectx, rule) if err != nil { return err } @@ -156,8 +149,7 @@ func (e *Executor) getEvaluator( ctx context.Context, policyID int32, prov string, - cli ghclient.RestAPI, - token string, + cli *providers.ProviderBuilder, ectx *EntityContext, rule *pb.Policy_Rule, ) (*pb.RuleType, *RuleTypeEngine, error) { @@ -180,7 +172,7 @@ func (e *Executor) getEvaluator( // TODO(jaosorior): Rule types should be cached in memory so // we don't have to query the database for each rule. - rte, err := NewRuleTypeEngine(rt, cli, token) + rte, err := NewRuleTypeEngine(rt, cli) if err != nil { return nil, nil, fmt.Errorf("error creating rule type engine: %w", err) } diff --git a/internal/engine/ingester/builtin/builtin.go b/internal/engine/ingester/builtin/builtin.go index 2a547f69de..b24087fa7a 100644 --- a/internal/engine/ingester/builtin/builtin.go +++ b/internal/engine/ingester/builtin/builtin.go @@ -29,6 +29,7 @@ import ( evalerrors "github.com/stacklok/mediator/internal/engine/errors" engif "github.com/stacklok/mediator/internal/engine/interfaces" + "github.com/stacklok/mediator/internal/providers" "github.com/stacklok/mediator/internal/util" pb "github.com/stacklok/mediator/pkg/api/protobuf/go/mediator/v1" "github.com/stacklok/mediator/pkg/rule_methods" @@ -50,11 +51,11 @@ type BuiltinRuleDataIngest struct { // NewBuiltinRuleDataIngest creates a new builtin rule data ingest engine func NewBuiltinRuleDataIngest( builtinCfg *pb.BuiltinType, - access_token string, + pbuild *providers.ProviderBuilder, ) (*BuiltinRuleDataIngest, error) { return &BuiltinRuleDataIngest{ builtinCfg: builtinCfg, - accessToken: access_token, + accessToken: pbuild.GetToken(), method: builtinCfg.GetMethod(), ruleMethods: &rule_methods.RuleMethods{}, }, nil diff --git a/internal/engine/ingester/builtin/builtin_test.go b/internal/engine/ingester/builtin/builtin_test.go index f0273bc19f..c100e58b3e 100644 --- a/internal/engine/ingester/builtin/builtin_test.go +++ b/internal/engine/ingester/builtin/builtin_test.go @@ -26,6 +26,7 @@ import ( "google.golang.org/protobuf/reflect/protoreflect" evalerrors "github.com/stacklok/mediator/internal/engine/errors" + "github.com/stacklok/mediator/internal/providers" pb "github.com/stacklok/mediator/pkg/api/protobuf/go/mediator/v1" ) @@ -60,7 +61,7 @@ func TestBuiltInWorks(t *testing.T) { t.Run(tt.name, func(t *testing.T) { t.Parallel() - bi, err := NewBuiltinRuleDataIngest(nil, "") + bi, err := NewBuiltinRuleDataIngest(nil, &providers.ProviderBuilder{}) assert.NoError(t, err) bi.method = tt.methodName @@ -107,7 +108,7 @@ func TestBuiltinErrorCases(t *testing.T) { t.Run(tt.name, func(t *testing.T) { t.Parallel() - bi, err := NewBuiltinRuleDataIngest(nil, "") + bi, err := NewBuiltinRuleDataIngest(nil, &providers.ProviderBuilder{}) assert.NoError(t, err) bi.method = tt.methodName diff --git a/internal/engine/ingester/diff/diff.go b/internal/engine/ingester/diff/diff.go index e28f38eb80..53942d7184 100644 --- a/internal/engine/ingester/diff/diff.go +++ b/internal/engine/ingester/diff/diff.go @@ -24,8 +24,9 @@ import ( "google.golang.org/protobuf/reflect/protoreflect" engif "github.com/stacklok/mediator/internal/engine/interfaces" - ghclient "github.com/stacklok/mediator/internal/providers/github" + "github.com/stacklok/mediator/internal/providers" pb "github.com/stacklok/mediator/pkg/api/protobuf/go/mediator/v1" + provifv1 "github.com/stacklok/mediator/pkg/providers/v1" ) const ( @@ -37,23 +38,32 @@ const ( // Diff is the diff rule data ingest engine type Diff struct { - cli ghclient.RestAPI + cli provifv1.GitHub cfg *pb.DiffType } // NewDiffIngester creates a new diff ingester func NewDiffIngester( cfg *pb.DiffType, - cli ghclient.RestAPI, -) *Diff { + pbuild *providers.ProviderBuilder, +) (*Diff, error) { if cfg == nil { cfg = &pb.DiffType{} } + if pbuild == nil { + return nil, fmt.Errorf("provider builder is nil") + } + + cli, err := pbuild.GetGitHub(context.Background()) + if err != nil { + return nil, fmt.Errorf("failed to get github client: %w", err) + } + return &Diff{ cfg: cfg, cli: cli, - } + }, nil } // Ingest ingests a pull request and returns a list of dependencies diff --git a/internal/engine/ingester/git/git.go b/internal/engine/ingester/git/git.go index cc21a14db3..96072eb69e 100644 --- a/internal/engine/ingester/git/git.go +++ b/internal/engine/ingester/git/git.go @@ -20,16 +20,14 @@ import ( "context" "fmt" - memfs "github.com/go-git/go-billy/v5/memfs" - git "github.com/go-git/go-git/v5" - "github.com/go-git/go-git/v5/plumbing" - http "github.com/go-git/go-git/v5/plumbing/transport/http" - memory "github.com/go-git/go-git/v5/storage/memory" "github.com/mitchellh/mapstructure" "google.golang.org/protobuf/reflect/protoreflect" + "github.com/stacklok/mediator/internal/db" engif "github.com/stacklok/mediator/internal/engine/interfaces" + "github.com/stacklok/mediator/internal/providers" pb "github.com/stacklok/mediator/pkg/api/protobuf/go/mediator/v1" + provifv1 "github.com/stacklok/mediator/pkg/providers/v1" ) const ( @@ -40,19 +38,33 @@ const ( // Git is the engine for a rule type that uses git data ingest type Git struct { - accessToken string - cfg *pb.GitType + cfg *pb.GitType + gitprov provifv1.Git } // NewGitIngester creates a new git rule data ingest engine -func NewGitIngester(cfg *pb.GitType, token string) *Git { +func NewGitIngester(cfg *pb.GitType, pbuild *providers.ProviderBuilder) (*Git, error) { + if pbuild == nil { + return nil, fmt.Errorf("provider builder is nil") + } + + if !pbuild.Implements(db.ProviderTypeGit) { + return nil, fmt.Errorf("provider builder does not implement git") + } + if cfg == nil { cfg = &pb.GitType{} } - return &Git{ - accessToken: token, - cfg: cfg, + + gitprov, err := pbuild.GetGit() + if err != nil { + return nil, fmt.Errorf("could not get git provider: %w", err) } + + return &Git{ + cfg: cfg, + gitprov: gitprov, + }, nil } // Ingest does the actual data ingestion for a rule type by cloning a git repo @@ -69,34 +81,11 @@ func (gi *Git) Ingest(ctx context.Context, ent protoreflect.ProtoMessage, params branch := gi.getBranch(userCfg) - opts := &git.CloneOptions{ - URL: url, - SingleBranch: true, - Depth: 1, - Tags: git.NoTags, - ReferenceName: plumbing.NewBranchReferenceName(branch), - } - - if gi.accessToken != "" { - opts.Auth = &http.BasicAuth{ - // the Username can be anything but it can't be empty - Username: "mediator-user", - Password: gi.accessToken, - } - } - - if err := opts.Validate(); err != nil { - return nil, fmt.Errorf("invalid clone options: %w", err) - } - - storer := memory.NewStorage() - fs := memfs.New() - // We clone to the memfs go-billy filesystem driver, which doesn't // allow for direct access to the underlying filesystem. This is // because we want to be able to run this in a sandboxed environment // where we don't have access to the underlying filesystem. - r, err := git.CloneContext(ctx, storer, fs, opts) + r, err := gi.gitprov.Clone(ctx, url, branch) if err != nil { return nil, fmt.Errorf("could not clone repo: %w", err) } diff --git a/internal/engine/ingester/git/git_test.go b/internal/engine/ingester/git/git_test.go index 4d379839a2..3049c190ec 100644 --- a/internal/engine/ingester/git/git_test.go +++ b/internal/engine/ingester/git/git_test.go @@ -22,16 +22,33 @@ import ( "github.com/stretchr/testify/require" + "github.com/stacklok/mediator/internal/db" gitengine "github.com/stacklok/mediator/internal/engine/ingester/git" + "github.com/stacklok/mediator/internal/providers" pb "github.com/stacklok/mediator/pkg/api/protobuf/go/mediator/v1" + provifv1 "github.com/stacklok/mediator/pkg/providers/v1" ) func TestGitIngestWithCloneURLFromRepo(t *testing.T) { t.Parallel() - gi := gitengine.NewGitIngester(&pb.GitType{ + gi, err := gitengine.NewGitIngester(&pb.GitType{ Branch: "master", - }, "") + }, providers.NewProviderBuilder( + &db.Provider{ + Name: "github", + Version: provifv1.V1, + Implements: []db.ProviderType{ + "git", + "rest", + "github", + }, + }, + db.ProviderAccessToken{}, + "", + )) + require.NoError(t, err, "expected no error") + got, err := gi.Ingest(context.Background(), &pb.RepositoryResult{ CloneUrl: "https://github.com/octocat/Hello-World.git", }, map[string]interface{}{}) @@ -54,9 +71,23 @@ func TestGitIngestWithCloneURLFromRepo(t *testing.T) { func TestGitIngestWithCloneURLFromParams(t *testing.T) { t.Parallel() - gi := gitengine.NewGitIngester(&pb.GitType{ + gi, err := gitengine.NewGitIngester(&pb.GitType{ Branch: "master", - }, "") + }, providers.NewProviderBuilder( + &db.Provider{ + Name: "github", + Version: provifv1.V1, + Implements: []db.ProviderType{ + "git", + "rest", + "github", + }, + }, + db.ProviderAccessToken{}, + "", + )) + require.NoError(t, err, "expected no error") + got, err := gi.Ingest(context.Background(), &pb.Artifact{}, map[string]any{ "clone_url": "https://github.com/octocat/Hello-World.git", }) @@ -79,7 +110,23 @@ func TestGitIngestWithCloneURLFromParams(t *testing.T) { func TestGitIngestWithCustomBranchFromParams(t *testing.T) { t.Parallel() - gi := gitengine.NewGitIngester(&pb.GitType{}, "") + gi, err := gitengine.NewGitIngester(&pb.GitType{ + Branch: "master", + }, providers.NewProviderBuilder( + &db.Provider{ + Name: "github", + Version: provifv1.V1, + Implements: []db.ProviderType{ + "git", + "rest", + "github", + }, + }, + db.ProviderAccessToken{}, + "", + )) + require.NoError(t, err, "expected no error") + got, err := gi.Ingest(context.Background(), &pb.Artifact{}, map[string]any{ "clone_url": "https://github.com/octocat/Hello-World.git", "branch": "test", @@ -104,7 +151,24 @@ func TestGitIngestFailsBecauseOfAuthorization(t *testing.T) { t.Parallel() // foobar is not a valid token - gi := gitengine.NewGitIngester(&pb.GitType{}, "foobar") + gi, err := gitengine.NewGitIngester(&pb.GitType{ + Branch: "master", + }, providers.NewProviderBuilder( + &db.Provider{ + Name: "github", + Version: provifv1.V1, + Implements: []db.ProviderType{ + "git", + "rest", + "github", + }, + }, + db.ProviderAccessToken{}, + "foobar", + ), + ) + require.NoError(t, err, "expected no error") + got, err := gi.Ingest(context.Background(), &pb.Artifact{}, map[string]any{ "clone_url": "https://github.com/stacklok/mediator.git", }) @@ -116,7 +180,22 @@ func TestGitIngestFailsBecauseOfUnexistentCloneUrl(t *testing.T) { t.Parallel() // foobar is not a valid token - gi := gitengine.NewGitIngester(&pb.GitType{}, "") + gi, err := gitengine.NewGitIngester(&pb.GitType{}, providers.NewProviderBuilder( + &db.Provider{ + Name: "github", + Version: provifv1.V1, + Implements: []db.ProviderType{ + "git", + "rest", + "github", + }, + }, + db.ProviderAccessToken{}, + // No authentication is the right thing in this case. + "", + )) + require.NoError(t, err, "expected no error") + got, err := gi.Ingest(context.Background(), &pb.Artifact{}, map[string]any{ "clone_url": "https://github.com/octocat/unexistent-git-repo.git", }) diff --git a/internal/engine/ingester/ingester.go b/internal/engine/ingester/ingester.go index 3945f0979e..2d069dca86 100644 --- a/internal/engine/ingester/ingester.go +++ b/internal/engine/ingester/ingester.go @@ -26,7 +26,7 @@ import ( "github.com/stacklok/mediator/internal/engine/ingester/git" "github.com/stacklok/mediator/internal/engine/ingester/rest" engif "github.com/stacklok/mediator/internal/engine/interfaces" - ghclient "github.com/stacklok/mediator/internal/providers/github" + "github.com/stacklok/mediator/internal/providers" pb "github.com/stacklok/mediator/pkg/api/protobuf/go/mediator/v1" ) @@ -38,20 +38,21 @@ var _ engif.Ingester = (*rest.Ingestor)(nil) // NewRuleDataIngest creates a new rule data ingest based no the given rule // type definition. -func NewRuleDataIngest(rt *pb.RuleType, cli ghclient.RestAPI, access_token string) (engif.Ingester, error) { +func NewRuleDataIngest(rt *pb.RuleType, pbuild *providers.ProviderBuilder) (engif.Ingester, error) { ing := rt.Def.GetIngest() - switch rt.Def.Ingest.Type { + + switch ing.GetType() { case rest.RestRuleDataIngestType: if rt.Def.Ingest.GetRest() == nil { return nil, fmt.Errorf("rule type engine missing rest configuration") } - return rest.NewRestRuleDataIngest(ing.GetRest(), cli) + return rest.NewRestRuleDataIngest(ing.GetRest(), pbuild) case builtin.BuiltinRuleDataIngestType: if rt.Def.Ingest.GetBuiltin() == nil { return nil, fmt.Errorf("rule type engine missing internal configuration") } - return builtin.NewBuiltinRuleDataIngest(ing.GetBuiltin(), access_token) + return builtin.NewBuiltinRuleDataIngest(ing.GetBuiltin(), pbuild) case artifact.ArtifactRuleDataIngestType: if rt.Def.Ingest.GetArtifact() == nil { @@ -60,9 +61,9 @@ func NewRuleDataIngest(rt *pb.RuleType, cli ghclient.RestAPI, access_token strin return artifact.NewArtifactDataIngest(ing.GetArtifact()) case git.GitRuleDataIngestType: - return git.NewGitIngester(ing.GetGit(), access_token), nil + return git.NewGitIngester(ing.GetGit(), pbuild) case diff.DiffRuleDataIngestType: - return diff.NewDiffIngester(ing.GetDiff(), cli), nil + return diff.NewDiffIngester(ing.GetDiff(), pbuild) default: return nil, fmt.Errorf("unsupported rule type engine: %s", rt.Def.Ingest.Type) } diff --git a/internal/engine/ingester/ingester_test.go b/internal/engine/ingester/ingester_test.go index a0515bee39..471b51b2a5 100644 --- a/internal/engine/ingester/ingester_test.go +++ b/internal/engine/ingester/ingester_test.go @@ -18,23 +18,26 @@ package ingester import ( + "encoding/json" "testing" "github.com/stretchr/testify/require" + "github.com/stacklok/mediator/internal/db" "github.com/stacklok/mediator/internal/engine/ingester/artifact" "github.com/stacklok/mediator/internal/engine/ingester/builtin" "github.com/stacklok/mediator/internal/engine/ingester/git" "github.com/stacklok/mediator/internal/engine/ingester/rest" + "github.com/stacklok/mediator/internal/providers" pb "github.com/stacklok/mediator/pkg/api/protobuf/go/mediator/v1" + provifv1 "github.com/stacklok/mediator/pkg/providers/v1" ) func TestNewRuleDataIngest(t *testing.T) { t.Parallel() type args struct { - rt *pb.RuleType - access_token string + rt *pb.RuleType } tests := []struct { name string @@ -155,7 +158,26 @@ func TestNewRuleDataIngest(t *testing.T) { t.Run(tt.name, func(t *testing.T) { t.Parallel() - got, err := NewRuleDataIngest(tt.args.rt, nil, tt.args.access_token) + got, err := NewRuleDataIngest(tt.args.rt, providers.NewProviderBuilder( + &db.Provider{ + Name: "github", + Version: provifv1.V1, + Implements: []db.ProviderType{ + "rest", + "git", + "github", + }, + Definition: json.RawMessage(`{ + "rest": { + "endpoint": "https://api.github.com/repos/Foo/Bar" + }, + "git": {}, + "github": {} +}`), + }, + db.ProviderAccessToken{}, + "token", + )) if tt.wantErr { require.Error(t, err, "Expected error") require.Nil(t, got, "Expected nil") diff --git a/internal/engine/ingester/rest/rest.go b/internal/engine/ingester/rest/rest.go index a7158f3668..8aa6ea4780 100644 --- a/internal/engine/ingester/rest/rest.go +++ b/internal/engine/ingester/rest/rest.go @@ -21,6 +21,8 @@ import ( "context" "encoding/json" "fmt" + "io" + "log" "net/http" "strings" "text/template" @@ -28,8 +30,9 @@ import ( "google.golang.org/protobuf/reflect/protoreflect" engif "github.com/stacklok/mediator/internal/engine/interfaces" - ghclient "github.com/stacklok/mediator/internal/providers/github" + "github.com/stacklok/mediator/internal/providers" pb "github.com/stacklok/mediator/pkg/api/protobuf/go/mediator/v1" + provifv1 "github.com/stacklok/mediator/pkg/providers/v1" ) const ( @@ -40,7 +43,7 @@ const ( // Ingestor is the engine for a rule type that uses REST data ingest type Ingestor struct { restCfg *pb.RestType - cli ghclient.RestAPI + cli provifv1.REST endpointTemplate *template.Template method string } @@ -48,7 +51,7 @@ type Ingestor struct { // NewRestRuleDataIngest creates a new REST rule data ingest engine func NewRestRuleDataIngest( restCfg *pb.RestType, - cli ghclient.RestAPI, + pbuild *providers.ProviderBuilder, ) (*Ingestor, error) { if len(restCfg.Endpoint) == 0 { return nil, fmt.Errorf("missing endpoint") @@ -65,6 +68,11 @@ func NewRestRuleDataIngest( method = http.MethodGet } + cli, err := pbuild.GetHTTP(context.Background()) + if err != nil { + return nil, fmt.Errorf("cannot get http client: %w", err) + } + return &Ingestor{ restCfg: restCfg, cli: cli, @@ -93,28 +101,43 @@ func (rdi *Ingestor) Ingest(ctx context.Context, ent protoreflect.ProtoMessage, return nil, fmt.Errorf("cannot execute endpoint template: %w", err) } - req, err := rdi.cli.NewRequest(rdi.method, endpoint.String(), rdi.restCfg.Body) + // create string buffer + var bodyr io.Reader + if rdi.restCfg.Body != nil { + bodyr = strings.NewReader(*rdi.restCfg.Body) + } + + req, err := rdi.cli.NewRequest(rdi.method, endpoint.String(), bodyr) if err != nil { return nil, fmt.Errorf("cannot create request: %w", err) } - bodyBuf := new(bytes.Buffer) - _, err = rdi.cli.Do(ctx, req, bodyBuf) + resp, err := rdi.cli.Do(ctx, req) if err != nil { return nil, fmt.Errorf("cannot make request: %w", err) } + defer func() { + if err := resp.Body.Close(); err != nil { + log.Printf("cannot close response body: %v", err) + } + }() + var data any - data = bodyBuf if rdi.restCfg.Parse == "json" { var jsonData any - dec := json.NewDecoder(bodyBuf) + dec := json.NewDecoder(resp.Body) if err := dec.Decode(&jsonData); err != nil { return nil, fmt.Errorf("cannot decode json: %w", err) } data = jsonData + } else { + data, err = io.ReadAll(resp.Body) + if err != nil { + return nil, fmt.Errorf("cannot read response body: %w", err) + } } return &engif.Result{ diff --git a/internal/engine/ingester/rest/rest_test.go b/internal/engine/ingester/rest/rest_test.go index 4d59cf39c5..a1d0ecc0a1 100644 --- a/internal/engine/ingester/rest/rest_test.go +++ b/internal/engine/ingester/rest/rest_test.go @@ -16,12 +16,15 @@ package rest import ( + "encoding/json" "testing" "github.com/stretchr/testify/require" - ghclient "github.com/stacklok/mediator/internal/providers/github" + "github.com/stacklok/mediator/internal/db" + "github.com/stacklok/mediator/internal/providers" pb "github.com/stacklok/mediator/pkg/api/protobuf/go/mediator/v1" + provifv1 "github.com/stacklok/mediator/pkg/providers/v1" ) func TestNewRestRuleDataIngest(t *testing.T) { @@ -29,7 +32,7 @@ func TestNewRestRuleDataIngest(t *testing.T) { type args struct { restCfg *pb.RestType - cli ghclient.RestAPI + pbuild *providers.ProviderBuilder } tests := []struct { name string @@ -40,9 +43,24 @@ func TestNewRestRuleDataIngest(t *testing.T) { name: "valid rest", args: args{ restCfg: &pb.RestType{ - Endpoint: "https://api.github.com/repos/Foo/Bar", + Endpoint: "/repos/Foo/Bar", }, - cli: nil, + pbuild: providers.NewProviderBuilder( + &db.Provider{ + Name: "osv", + Version: provifv1.V1, + Implements: []db.ProviderType{ + "rest", + }, + Definition: json.RawMessage(`{ + "rest": { + "base_url": "https://api.github.com/" + } +}`), + }, + db.ProviderAccessToken{}, + "token", + ), }, wantErr: false, }, @@ -52,6 +70,22 @@ func TestNewRestRuleDataIngest(t *testing.T) { restCfg: &pb.RestType{ Endpoint: "{{", }, + pbuild: providers.NewProviderBuilder( + &db.Provider{ + Name: "osv", + Version: provifv1.V1, + Implements: []db.ProviderType{ + "rest", + }, + Definition: json.RawMessage(`{ + "rest": { + "base_url": "https://api.github.com/" + } +}`), + }, + db.ProviderAccessToken{}, + "token", + ), }, wantErr: true, }, @@ -61,6 +95,91 @@ func TestNewRestRuleDataIngest(t *testing.T) { restCfg: &pb.RestType{ Endpoint: "", }, + pbuild: providers.NewProviderBuilder( + &db.Provider{ + Name: "osv", + Version: provifv1.V1, + Implements: []db.ProviderType{ + "rest", + }, + Definition: json.RawMessage(`{ + "rest": { + "endpoint": "https://api.github.com/" + } +}`), + }, + db.ProviderAccessToken{}, + "token", + ), + }, + wantErr: true, + }, + { + name: "missing provider definition", + args: args{ + restCfg: &pb.RestType{ + Endpoint: "", + }, + pbuild: providers.NewProviderBuilder( + &db.Provider{ + Name: "osv", + Version: provifv1.V1, + Implements: []db.ProviderType{ + "rest", + }, + }, + db.ProviderAccessToken{}, + "token", + ), + }, + wantErr: true, + }, + { + name: "wrong provider definition", + args: args{ + restCfg: &pb.RestType{ + Endpoint: "", + }, + pbuild: providers.NewProviderBuilder( + &db.Provider{ + Name: "osv", + Version: provifv1.V1, + Implements: []db.ProviderType{ + "rest", + }, + Definition: json.RawMessage(`{ + "rest": { + "wrong": "https://api.github.com/" + } +}`), + }, + db.ProviderAccessToken{}, + "token", + ), + }, + wantErr: true, + }, + { + name: "invalid provider definition", + args: args{ + restCfg: &pb.RestType{ + Endpoint: "", + }, + pbuild: providers.NewProviderBuilder( + &db.Provider{ + Name: "osv", + Version: provifv1.V1, + Implements: []db.ProviderType{ + "rest", + }, + Definition: json.RawMessage(`{ + "rest": { + "base_url": "https://api.github.com/" +}`), + }, + db.ProviderAccessToken{}, + "token", + ), }, wantErr: true, }, @@ -71,7 +190,7 @@ func TestNewRestRuleDataIngest(t *testing.T) { t.Run(tt.name, func(t *testing.T) { t.Parallel() - got, err := NewRestRuleDataIngest(tt.args.restCfg, tt.args.cli) + got, err := NewRestRuleDataIngest(tt.args.restCfg, tt.args.pbuild) if tt.wantErr { require.Error(t, err, "expected error") require.Nil(t, got, "expected nil") diff --git a/internal/engine/rule_types.go b/internal/engine/rule_types.go index 0579056c72..1e82a45bf5 100644 --- a/internal/engine/rule_types.go +++ b/internal/engine/rule_types.go @@ -34,7 +34,7 @@ import ( "github.com/stacklok/mediator/internal/engine/ingester" engif "github.com/stacklok/mediator/internal/engine/interfaces" "github.com/stacklok/mediator/internal/entities" - ghclient "github.com/stacklok/mediator/internal/providers/github" + "github.com/stacklok/mediator/internal/providers" "github.com/stacklok/mediator/internal/util" pb "github.com/stacklok/mediator/pkg/api/protobuf/go/mediator/v1" ) @@ -169,18 +169,18 @@ type RuleTypeEngine struct { rval *RuleValidator rt *pb.RuleType - // TODO(JAORMX): We need to have an abstract client interface - cli ghclient.RestAPI + + cli *providers.ProviderBuilder } // NewRuleTypeEngine creates a new rule type engine -func NewRuleTypeEngine(rt *pb.RuleType, cli ghclient.RestAPI, accessToken string) (*RuleTypeEngine, error) { +func NewRuleTypeEngine(rt *pb.RuleType, cli *providers.ProviderBuilder) (*RuleTypeEngine, error) { rval, err := NewRuleValidator(rt) if err != nil { return nil, fmt.Errorf("cannot create rule validator: %w", err) } - rdi, err := ingester.NewRuleDataIngest(rt, cli, accessToken) + rdi, err := ingester.NewRuleDataIngest(rt, cli) if err != nil { return nil, fmt.Errorf("cannot create rule data ingest: %w", err) } diff --git a/internal/gh/queries/sync_repo_db.go b/internal/gh/queries/sync_repo_db.go index f939f24505..e411a38415 100644 --- a/internal/gh/queries/sync_repo_db.go +++ b/internal/gh/queries/sync_repo_db.go @@ -21,8 +21,9 @@ import ( "errors" "fmt" + "github.com/google/go-github/v53/github" + "github.com/stacklok/mediator/internal/db" - ghclient "github.com/stacklok/mediator/internal/providers/github" ) // SyncRepositoriesWithDB syncs the repositories already in the database with the @@ -39,7 +40,7 @@ import ( //gocyclo:ignore func SyncRepositoriesWithDB(ctx context.Context, store db.Store, - result ghclient.RepositoryListResult, + repos []*github.Repository, provider string, groupId int32) error { // Get all existing repositories from the database by group ID dbRepos, err := store.ListRepositoriesByGroupID(ctx, db.ListRepositoriesByGroupIDParams{ @@ -58,7 +59,7 @@ func SyncRepositoriesWithDB(ctx context.Context, } // Iterate over the repositories returned from GitHub - for _, repo := range result.Repositories { + for _, repo := range repos { // Check if the repository already exists in the database by Repo ID existingRepo, err := store.GetRepositoryByRepoID(ctx, int32(*repo.ID)) if err != nil { diff --git a/internal/providers/git/git.go b/internal/providers/git/git.go new file mode 100644 index 0000000000..42bec5ab60 --- /dev/null +++ b/internal/providers/git/git.go @@ -0,0 +1,86 @@ +// Copyright 2023 Stacklok, Inc +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package git provides a client for interacting with Git providers +package git + +import ( + "context" + "fmt" + + "github.com/go-git/go-billy/v5/memfs" + "github.com/go-git/go-git/v5" + "github.com/go-git/go-git/v5/plumbing" + "github.com/go-git/go-git/v5/plumbing/transport/http" + "github.com/go-git/go-git/v5/storage/memory" + + provifv1 "github.com/stacklok/mediator/pkg/providers/v1" +) + +// Git is the struct that contains the GitHub REST API client +type Git struct { + token string +} + +// Ensure that the Git client implements the Git interface +var _ provifv1.Git = (*Git)(nil) + +// NewGit creates a new GitHub client +func NewGit(token string) *Git { + return &Git{ + token: token, + } +} + +// GetToken returns the token for the provider +func (g *Git) GetToken() string { + return g.token +} + +// Clone clones a git repository +func (g *Git) Clone(ctx context.Context, url, branch string) (*git.Repository, error) { + opts := &git.CloneOptions{ + URL: url, + SingleBranch: true, + Depth: 1, + Tags: git.NoTags, + ReferenceName: plumbing.NewBranchReferenceName(branch), + } + + if g.token != "" { + opts.Auth = &http.BasicAuth{ + // the Username can be anything but it can't be empty + Username: "mediator-user", + Password: g.token, + } + } + + if err := opts.Validate(); err != nil { + return nil, fmt.Errorf("invalid clone options: %w", err) + } + + storer := memory.NewStorage() + fs := memfs.New() + + // We clone to the memfs go-billy filesystem driver, which doesn't + // allow for direct access to the underlying filesystem. This is + // because we want to be able to run this in a sandboxed environment + // where we don't have access to the underlying filesystem. + r, err := git.CloneContext(ctx, storer, fs, opts) + if err != nil { + return nil, fmt.Errorf("could not clone repo: %w", err) + } + + return r, nil +} diff --git a/internal/providers/github/github.go b/internal/providers/github/github.go index 031a6a1807..1b1153c982 100644 --- a/internal/providers/github/github.go +++ b/internal/providers/github/github.go @@ -17,15 +17,15 @@ package github import ( "context" - "net/http" + "encoding/json" "net/url" "time" "github.com/google/go-github/v53/github" - "github.com/shurcooL/graphql" "golang.org/x/oauth2" "github.com/stacklok/mediator/internal/db" + provifv1 "github.com/stacklok/mediator/pkg/providers/v1" ) const ( @@ -33,18 +33,6 @@ const ( ExpensiveRestCallTimeout = 15 * time.Second ) -// GitHubConfig is the struct that contains the configuration for the GitHub client -// Token: is the GitHub API token retrieved from the provider_access_tokens table -// in the database -// Endpoint: is the GitHub API endpoint -// If using the public GitHub API, Endpoint can be left blank -// disable revive linting for this struct as there is nothing wrong with the -// naming convention -type GitHubConfig struct { //revive:disable-line:exported - Token string - Endpoint string -} - // Github is the string that represents the GitHub provider const Github = "github" @@ -55,47 +43,6 @@ var Implements = []db.ProviderType{ db.ProviderTypeRest, } -// RepositoryListResult is a struct that contains the information about a GitHub repository -type RepositoryListResult struct { - Repositories []*github.Repository -} - -// RestAPI is the interface for interacting with the GitHub REST API -// Add methods here for interacting with the GitHub Rest API -// e.g. GetRepositoryRestInfo(ctx context.Context, owner string, name string) (*RepositoryInfo, error) -type RestAPI interface { - GetAuthenticatedUser(context.Context) (*github.User, error) - GetRepository(context.Context, string, string) (*github.Repository, error) - ListAllRepositories(context.Context, bool, string) (RepositoryListResult, error) - GetBranchProtection(context.Context, string, string, string) (*github.Protection, error) - ListAllPackages(context.Context, bool, string, string, int, int) (PackageListResult, error) - ListPackagesByRepository(context.Context, bool, string, string, int64, int, int) (PackageListResult, error) - GetPackageByName(context.Context, bool, string, string, string) (*github.Package, error) - GetPackageVersions(context.Context, bool, string, string, string) ([]*github.PackageVersion, error) - GetPackageVersionByTag(context.Context, bool, string, string, string, string) (*github.PackageVersion, error) - GetPackageVersionById(context.Context, bool, string, string, string, int64) (*github.PackageVersion, error) - GetPullRequest(context.Context, string, string, int) (*github.PullRequest, error) - CreateReview(context.Context, string, string, int, *github.PullRequestReviewRequest) (*github.PullRequestReview, error) - ListReviews(context.Context, string, string, int, *github.ListOptions) ([]*github.PullRequestReview, error) - DismissReview(context.Context, string, string, int, int64, - *github.PullRequestReviewDismissalRequest) (*github.PullRequestReview, error) - SetCommitStatus(context.Context, string, string, string, *github.RepoStatus) (*github.RepoStatus, error) - ListFiles(context.Context, string, string, int, int, int) ([]*github.CommitFile, *github.Response, error) - GetToken() string - GetOwner() string - - // NewRequest allows for building raw and custom requests - NewRequest(method, urlStr string, body any, opts ...github.RequestOption) (*http.Request, error) - Do(ctx context.Context, req *http.Request, v any) (*github.Response, error) -} - -// GraphQLAPI is the interface for interacting with the GitHub GraphQL API -// Add methods here for interacting with the GitHub GraphQL API -// e.g. GetRepositoryGraphInfo(ctx context.Context, owner string, name string) (*RepositoryInfo, error) -type GraphQLAPI interface { - RunQuery(ctx context.Context, query interface{}, variables map[string]interface{}) error -} - // RestClient is the struct that contains the GitHub REST API client type RestClient struct { client *github.Client @@ -103,18 +50,16 @@ type RestClient struct { owner string } -// GraphQLClient is the struct that contains the GitHub GraphQL API client -type GraphQLClient struct { - client *graphql.Client -} +// Ensure that the GitHub client implements the GitHub interface +var _ provifv1.GitHub = (*RestClient)(nil) // NewRestClient creates a new GitHub REST API client // BaseURL defaults to the public GitHub API, if needing to use a customer domain // endpoint (as is the case with GitHub Enterprise), set the Endpoint field in // the GitHubConfig struct -func NewRestClient(ctx context.Context, config GitHubConfig, owner string) (RestAPI, error) { +func NewRestClient(ctx context.Context, config *provifv1.GitHubConfig, token string, owner string) (*RestClient, error) { ts := oauth2.StaticTokenSource( - &oauth2.Token{AccessToken: config.Token}, + &oauth2.Token{AccessToken: token}, ) tc := oauth2.NewClient(ctx, ts) @@ -130,31 +75,21 @@ func NewRestClient(ctx context.Context, config GitHubConfig, owner string) (Rest return &RestClient{ client: ghClient, - token: config.Token, + token: token, owner: owner, }, nil } -// NewGraphQLClient creates a new GitHub GraphQL API client -func NewGraphQLClient(ctx context.Context, config GitHubConfig) (GraphQLAPI, error) { - ts := oauth2.StaticTokenSource( - &oauth2.Token{AccessToken: config.Token}, - ) - tc := oauth2.NewClient(ctx, ts) - - endpoint := config.Endpoint - if endpoint == "" { - endpoint = "https://api.github.com/graphql" +// ParseV1Config parses the raw config into a GitHubConfig struct +func ParseV1Config(rawCfg json.RawMessage) (*provifv1.GitHubConfig, error) { + type wrapper struct { + GitHub *provifv1.GitHubConfig `json:"github" yaml:"github" mapstructure:"github" validate:"required"` } - ghGraphQL := graphql.NewClient(endpoint, tc) - - return &GraphQLClient{ - client: ghGraphQL, - }, nil -} + var w wrapper + if err := provifv1.ParseAndValidate(rawCfg, &w); err != nil { + return nil, err + } -// RunQuery executes a GraphQL query -func (gc *GraphQLClient) RunQuery(ctx context.Context, query interface{}, variables map[string]interface{}) error { - return gc.client.Query(ctx, query, variables) + return w.GitHub, nil } diff --git a/internal/providers/github/github_graphql.go b/internal/providers/github/github_graphql.go deleted file mode 100644 index 95c3165c95..0000000000 --- a/internal/providers/github/github_graphql.go +++ /dev/null @@ -1,19 +0,0 @@ -// // Copyright 2023 Stacklok, Inc -// // -// // Licensed under the Apache License, Version 2.0 (the "License"); -// // you may not use this file except in compliance with the License. -// // You may obtain a copy of the License at -// // -// // http://www.apache.org/licenses/LICENSE-2.0 -// // -// // Unless required by applicable law or agreed to in writing, software -// // distributed under the License is distributed on an "AS IS" BASIS, -// // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// // See the License for the specific language governing permissions and -// // limitations under the License. - -// Package github provides a client for interacting with the GitHub API -package github - -// NOTE: This file is for stubbing out client code for GraphQL -// It is currently a placeholder. diff --git a/internal/providers/github/github_rest.go b/internal/providers/github/github_rest.go index 69b7253395..0d50cabb58 100644 --- a/internal/providers/github/github_rest.go +++ b/internal/providers/github/github_rest.go @@ -16,8 +16,10 @@ package github import ( + "bytes" "context" "fmt" + "io" "net/http" "github.com/google/go-github/v53/github" @@ -25,7 +27,7 @@ import ( // ListAllRepositories returns a list of all repositories for the authenticated user // Two APIs are available, contigent on whether the token is for a user or an organization -func (c *RestClient) ListAllRepositories(ctx context.Context, isOrg bool, owner string) (RepositoryListResult, error) { +func (c *RestClient) ListAllRepositories(ctx context.Context, isOrg bool, owner string) ([]*github.Repository, error) { opt := &github.RepositoryListOptions{ ListOptions: github.ListOptions{ PerPage: 100, @@ -53,7 +55,7 @@ func (c *RestClient) ListAllRepositories(ctx context.Context, isOrg bool, owner } if err != nil { - return RepositoryListResult{}, err + return allRepos, err } allRepos = append(allRepos, repos...) if resp.NextPage == 0 { @@ -67,19 +69,12 @@ func (c *RestClient) ListAllRepositories(ctx context.Context, isOrg bool, owner } } - return RepositoryListResult{ - Repositories: allRepos, - }, nil -} - -// PackageListResult is a struct to hold the results of a package list -type PackageListResult struct { - Packages []*github.Package + return allRepos, nil } // ListAllPackages returns a list of all packages for the authenticated user func (c *RestClient) ListAllPackages(ctx context.Context, isOrg bool, owner string, artifactType string, - pageNumber int, itemsPerPage int) (PackageListResult, error) { + pageNumber int, itemsPerPage int) ([]*github.Package, error) { opt := &github.PackageListOptions{ PackageType: &artifactType, ListOptions: github.ListOptions{ @@ -100,7 +95,7 @@ func (c *RestClient) ListAllPackages(ctx context.Context, isOrg bool, owner stri artifacts, resp, err = c.client.Users.ListPackages(ctx, "", opt) } if err != nil { - return PackageListResult{Packages: allContainers}, err + return allContainers, err } allContainers = append(allContainers, artifacts...) @@ -111,12 +106,12 @@ func (c *RestClient) ListAllPackages(ctx context.Context, isOrg bool, owner stri opt.Page = resp.NextPage } - return PackageListResult{Packages: allContainers}, nil + return allContainers, nil } // ListPackagesByRepository returns a list of all packages for an specific repository func (c *RestClient) ListPackagesByRepository(ctx context.Context, isOrg bool, owner string, artifactType string, - repositoryId int64, pageNumber int, itemsPerPage int) (PackageListResult, error) { + repositoryId int64, pageNumber int, itemsPerPage int) ([]*github.Package, error) { opt := &github.PackageListOptions{ PackageType: &artifactType, ListOptions: github.ListOptions{ @@ -137,7 +132,7 @@ func (c *RestClient) ListPackagesByRepository(ctx context.Context, isOrg bool, o artifacts, resp, err = c.client.Users.ListPackages(ctx, "", opt) } if err != nil { - return PackageListResult{Packages: allContainers}, err + return allContainers, err } // now just append the ones belonging to the repository @@ -153,7 +148,7 @@ func (c *RestClient) ListPackagesByRepository(ctx context.Context, isOrg bool, o opt.Page = resp.NextPage } - return PackageListResult{Packages: allContainers}, nil + return allContainers, nil } // GetPackageByName returns a single package for the authenticated user or for the org @@ -376,13 +371,24 @@ func (c *RestClient) GetAuthenticatedUser(ctx context.Context) (*github.User, er // which will be resolved to the BaseURL of the Client. Relative URLS should // always be specified without a preceding slash. If specified, the value // pointed to by body is JSON encoded and included as the request body. -func (c *RestClient) NewRequest(method, url string, body interface{}, opts ...github.RequestOption) (*http.Request, error) { - return c.client.NewRequest(method, url, body, opts...) +func (c *RestClient) NewRequest(method, url string, body io.Reader) (*http.Request, error) { + return c.client.NewRequest(method, url, body) } // Do sends an API request and returns the API response. -func (c *RestClient) Do(ctx context.Context, req *http.Request, v interface{}) (*github.Response, error) { - return c.client.Do(ctx, req, v) +func (c *RestClient) Do(ctx context.Context, req *http.Request) (*http.Response, error) { + var buf bytes.Buffer + + // The GitHub client closes the response body, so we need to capture it + // in a buffer so that we can return it to the caller + resp, err := c.client.Do(ctx, req, &buf) + if err != nil { + return nil, err + } + + resp.Response.Body = io.NopCloser(&buf) + + return resp.Response, nil } // GetToken returns the token used to authenticate with the GitHub API diff --git a/internal/providers/github/github_test.go b/internal/providers/github/github_test.go index 2d7da438f5..d7f3267562 100644 --- a/internal/providers/github/github_test.go +++ b/internal/providers/github/github_test.go @@ -19,27 +19,16 @@ import ( "testing" "github.com/stretchr/testify/assert" + + provifv1 "github.com/stacklok/mediator/pkg/providers/v1" ) func TestNewRestClient(t *testing.T) { t.Parallel() - client, err := NewRestClient(context.Background(), GitHubConfig{ - Token: "token", + client, err := NewRestClient(context.Background(), &provifv1.GitHubConfig{ Endpoint: "https://api.github.com", - }, "") - assert.NoError(t, err) - assert.NotNil(t, client) -} - -func TestNewGraphQLClient(t *testing.T) { - t.Parallel() - - client, err := NewGraphQLClient(context.Background(), GitHubConfig{ - Token: "token", - Endpoint: "https://api.github.com/graphql", - }) - + }, "token", "") assert.NoError(t, err) assert.NotNil(t, client) } diff --git a/internal/providers/github/mock/github.go b/internal/providers/github/mock/github.go index 3b9014e4a2..e5f177d781 100644 --- a/internal/providers/github/mock/github.go +++ b/internal/providers/github/mock/github.go @@ -1,44 +1,201 @@ // Code generated by MockGen. DO NOT EDIT. -// Source: internal/providers/github/github.go +// Source: pkg/providers/v1/providers.go // Package mockgh is a generated GoMock package. package mockgh import ( context "context" + io "io" http "net/http" reflect "reflect" + git "github.com/go-git/go-git/v5" gomock "github.com/golang/mock/gomock" github "github.com/google/go-github/v53/github" - github0 "github.com/stacklok/mediator/internal/providers/github" ) -// MockRestAPI is a mock of RestAPI interface. -type MockRestAPI struct { +// MockProvider is a mock of Provider interface. +type MockProvider struct { ctrl *gomock.Controller - recorder *MockRestAPIMockRecorder + recorder *MockProviderMockRecorder } -// MockRestAPIMockRecorder is the mock recorder for MockRestAPI. -type MockRestAPIMockRecorder struct { - mock *MockRestAPI +// MockProviderMockRecorder is the mock recorder for MockProvider. +type MockProviderMockRecorder struct { + mock *MockProvider } -// NewMockRestAPI creates a new mock instance. -func NewMockRestAPI(ctrl *gomock.Controller) *MockRestAPI { - mock := &MockRestAPI{ctrl: ctrl} - mock.recorder = &MockRestAPIMockRecorder{mock} +// NewMockProvider creates a new mock instance. +func NewMockProvider(ctrl *gomock.Controller) *MockProvider { + mock := &MockProvider{ctrl: ctrl} + mock.recorder = &MockProviderMockRecorder{mock} return mock } // EXPECT returns an object that allows the caller to indicate expected use. -func (m *MockRestAPI) EXPECT() *MockRestAPIMockRecorder { +func (m *MockProvider) EXPECT() *MockProviderMockRecorder { + return m.recorder +} + +// GetToken mocks base method. +func (m *MockProvider) GetToken() string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetToken") + ret0, _ := ret[0].(string) + return ret0 +} + +// GetToken indicates an expected call of GetToken. +func (mr *MockProviderMockRecorder) GetToken() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetToken", reflect.TypeOf((*MockProvider)(nil).GetToken)) +} + +// MockGit is a mock of Git interface. +type MockGit struct { + ctrl *gomock.Controller + recorder *MockGitMockRecorder +} + +// MockGitMockRecorder is the mock recorder for MockGit. +type MockGitMockRecorder struct { + mock *MockGit +} + +// NewMockGit creates a new mock instance. +func NewMockGit(ctrl *gomock.Controller) *MockGit { + mock := &MockGit{ctrl: ctrl} + mock.recorder = &MockGitMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockGit) EXPECT() *MockGitMockRecorder { + return m.recorder +} + +// Clone mocks base method. +func (m *MockGit) Clone(ctx context.Context, url, branch string) (*git.Repository, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Clone", ctx, url, branch) + ret0, _ := ret[0].(*git.Repository) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Clone indicates an expected call of Clone. +func (mr *MockGitMockRecorder) Clone(ctx, url, branch interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Clone", reflect.TypeOf((*MockGit)(nil).Clone), ctx, url, branch) +} + +// GetToken mocks base method. +func (m *MockGit) GetToken() string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetToken") + ret0, _ := ret[0].(string) + return ret0 +} + +// GetToken indicates an expected call of GetToken. +func (mr *MockGitMockRecorder) GetToken() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetToken", reflect.TypeOf((*MockGit)(nil).GetToken)) +} + +// MockHTTP is a mock of HTTP interface. +type MockHTTP struct { + ctrl *gomock.Controller + recorder *MockHTTPMockRecorder +} + +// MockHTTPMockRecorder is the mock recorder for MockHTTP. +type MockHTTPMockRecorder struct { + mock *MockHTTP +} + +// NewMockHTTP creates a new mock instance. +func NewMockHTTP(ctrl *gomock.Controller) *MockHTTP { + mock := &MockHTTP{ctrl: ctrl} + mock.recorder = &MockHTTPMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockHTTP) EXPECT() *MockHTTPMockRecorder { + return m.recorder +} + +// Do mocks base method. +func (m *MockHTTP) Do(ctx context.Context, req *http.Request) (*http.Response, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Do", ctx, req) + ret0, _ := ret[0].(*http.Response) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Do indicates an expected call of Do. +func (mr *MockHTTPMockRecorder) Do(ctx, req interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Do", reflect.TypeOf((*MockHTTP)(nil).Do), ctx, req) +} + +// GetToken mocks base method. +func (m *MockHTTP) GetToken() string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetToken") + ret0, _ := ret[0].(string) + return ret0 +} + +// GetToken indicates an expected call of GetToken. +func (mr *MockHTTPMockRecorder) GetToken() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetToken", reflect.TypeOf((*MockHTTP)(nil).GetToken)) +} + +// NewRequest mocks base method. +func (m *MockHTTP) NewRequest(method, url string, body io.Reader) (*http.Request, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "NewRequest", method, url, body) + ret0, _ := ret[0].(*http.Request) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// NewRequest indicates an expected call of NewRequest. +func (mr *MockHTTPMockRecorder) NewRequest(method, url, body interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NewRequest", reflect.TypeOf((*MockHTTP)(nil).NewRequest), method, url, body) +} + +// MockGitHub is a mock of GitHub interface. +type MockGitHub struct { + ctrl *gomock.Controller + recorder *MockGitHubMockRecorder +} + +// MockGitHubMockRecorder is the mock recorder for MockGitHub. +type MockGitHubMockRecorder struct { + mock *MockGitHub +} + +// NewMockGitHub creates a new mock instance. +func NewMockGitHub(ctrl *gomock.Controller) *MockGitHub { + mock := &MockGitHub{ctrl: ctrl} + mock.recorder = &MockGitHubMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockGitHub) EXPECT() *MockGitHubMockRecorder { return m.recorder } // CreateReview mocks base method. -func (m *MockRestAPI) CreateReview(arg0 context.Context, arg1, arg2 string, arg3 int, arg4 *github.PullRequestReviewRequest) (*github.PullRequestReview, error) { +func (m *MockGitHub) CreateReview(arg0 context.Context, arg1, arg2 string, arg3 int, arg4 *github.PullRequestReviewRequest) (*github.PullRequestReview, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "CreateReview", arg0, arg1, arg2, arg3, arg4) ret0, _ := ret[0].(*github.PullRequestReview) @@ -47,13 +204,13 @@ func (m *MockRestAPI) CreateReview(arg0 context.Context, arg1, arg2 string, arg3 } // CreateReview indicates an expected call of CreateReview. -func (mr *MockRestAPIMockRecorder) CreateReview(arg0, arg1, arg2, arg3, arg4 interface{}) *gomock.Call { +func (mr *MockGitHubMockRecorder) CreateReview(arg0, arg1, arg2, arg3, arg4 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateReview", reflect.TypeOf((*MockRestAPI)(nil).CreateReview), arg0, arg1, arg2, arg3, arg4) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateReview", reflect.TypeOf((*MockGitHub)(nil).CreateReview), arg0, arg1, arg2, arg3, arg4) } // DismissReview mocks base method. -func (m *MockRestAPI) DismissReview(arg0 context.Context, arg1, arg2 string, arg3 int, arg4 int64, arg5 *github.PullRequestReviewDismissalRequest) (*github.PullRequestReview, error) { +func (m *MockGitHub) DismissReview(arg0 context.Context, arg1, arg2 string, arg3 int, arg4 int64, arg5 *github.PullRequestReviewDismissalRequest) (*github.PullRequestReview, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "DismissReview", arg0, arg1, arg2, arg3, arg4, arg5) ret0, _ := ret[0].(*github.PullRequestReview) @@ -62,28 +219,28 @@ func (m *MockRestAPI) DismissReview(arg0 context.Context, arg1, arg2 string, arg } // DismissReview indicates an expected call of DismissReview. -func (mr *MockRestAPIMockRecorder) DismissReview(arg0, arg1, arg2, arg3, arg4, arg5 interface{}) *gomock.Call { +func (mr *MockGitHubMockRecorder) DismissReview(arg0, arg1, arg2, arg3, arg4, arg5 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DismissReview", reflect.TypeOf((*MockRestAPI)(nil).DismissReview), arg0, arg1, arg2, arg3, arg4, arg5) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DismissReview", reflect.TypeOf((*MockGitHub)(nil).DismissReview), arg0, arg1, arg2, arg3, arg4, arg5) } // Do mocks base method. -func (m *MockRestAPI) Do(ctx context.Context, req *http.Request, v any) (*github.Response, error) { +func (m *MockGitHub) Do(ctx context.Context, req *http.Request) (*http.Response, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Do", ctx, req, v) - ret0, _ := ret[0].(*github.Response) + ret := m.ctrl.Call(m, "Do", ctx, req) + ret0, _ := ret[0].(*http.Response) ret1, _ := ret[1].(error) return ret0, ret1 } // Do indicates an expected call of Do. -func (mr *MockRestAPIMockRecorder) Do(ctx, req, v interface{}) *gomock.Call { +func (mr *MockGitHubMockRecorder) Do(ctx, req interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Do", reflect.TypeOf((*MockRestAPI)(nil).Do), ctx, req, v) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Do", reflect.TypeOf((*MockGitHub)(nil).Do), ctx, req) } // GetAuthenticatedUser mocks base method. -func (m *MockRestAPI) GetAuthenticatedUser(arg0 context.Context) (*github.User, error) { +func (m *MockGitHub) GetAuthenticatedUser(arg0 context.Context) (*github.User, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetAuthenticatedUser", arg0) ret0, _ := ret[0].(*github.User) @@ -92,13 +249,13 @@ func (m *MockRestAPI) GetAuthenticatedUser(arg0 context.Context) (*github.User, } // GetAuthenticatedUser indicates an expected call of GetAuthenticatedUser. -func (mr *MockRestAPIMockRecorder) GetAuthenticatedUser(arg0 interface{}) *gomock.Call { +func (mr *MockGitHubMockRecorder) GetAuthenticatedUser(arg0 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAuthenticatedUser", reflect.TypeOf((*MockRestAPI)(nil).GetAuthenticatedUser), arg0) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAuthenticatedUser", reflect.TypeOf((*MockGitHub)(nil).GetAuthenticatedUser), arg0) } // GetBranchProtection mocks base method. -func (m *MockRestAPI) GetBranchProtection(arg0 context.Context, arg1, arg2, arg3 string) (*github.Protection, error) { +func (m *MockGitHub) GetBranchProtection(arg0 context.Context, arg1, arg2, arg3 string) (*github.Protection, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetBranchProtection", arg0, arg1, arg2, arg3) ret0, _ := ret[0].(*github.Protection) @@ -107,13 +264,13 @@ func (m *MockRestAPI) GetBranchProtection(arg0 context.Context, arg1, arg2, arg3 } // GetBranchProtection indicates an expected call of GetBranchProtection. -func (mr *MockRestAPIMockRecorder) GetBranchProtection(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { +func (mr *MockGitHubMockRecorder) GetBranchProtection(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetBranchProtection", reflect.TypeOf((*MockRestAPI)(nil).GetBranchProtection), arg0, arg1, arg2, arg3) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetBranchProtection", reflect.TypeOf((*MockGitHub)(nil).GetBranchProtection), arg0, arg1, arg2, arg3) } // GetOwner mocks base method. -func (m *MockRestAPI) GetOwner() string { +func (m *MockGitHub) GetOwner() string { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetOwner") ret0, _ := ret[0].(string) @@ -121,13 +278,13 @@ func (m *MockRestAPI) GetOwner() string { } // GetOwner indicates an expected call of GetOwner. -func (mr *MockRestAPIMockRecorder) GetOwner() *gomock.Call { +func (mr *MockGitHubMockRecorder) GetOwner() *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetOwner", reflect.TypeOf((*MockRestAPI)(nil).GetOwner)) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetOwner", reflect.TypeOf((*MockGitHub)(nil).GetOwner)) } // GetPackageByName mocks base method. -func (m *MockRestAPI) GetPackageByName(arg0 context.Context, arg1 bool, arg2, arg3, arg4 string) (*github.Package, error) { +func (m *MockGitHub) GetPackageByName(arg0 context.Context, arg1 bool, arg2, arg3, arg4 string) (*github.Package, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetPackageByName", arg0, arg1, arg2, arg3, arg4) ret0, _ := ret[0].(*github.Package) @@ -136,13 +293,13 @@ func (m *MockRestAPI) GetPackageByName(arg0 context.Context, arg1 bool, arg2, ar } // GetPackageByName indicates an expected call of GetPackageByName. -func (mr *MockRestAPIMockRecorder) GetPackageByName(arg0, arg1, arg2, arg3, arg4 interface{}) *gomock.Call { +func (mr *MockGitHubMockRecorder) GetPackageByName(arg0, arg1, arg2, arg3, arg4 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPackageByName", reflect.TypeOf((*MockRestAPI)(nil).GetPackageByName), arg0, arg1, arg2, arg3, arg4) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPackageByName", reflect.TypeOf((*MockGitHub)(nil).GetPackageByName), arg0, arg1, arg2, arg3, arg4) } // GetPackageVersionById mocks base method. -func (m *MockRestAPI) GetPackageVersionById(arg0 context.Context, arg1 bool, arg2, arg3, arg4 string, arg5 int64) (*github.PackageVersion, error) { +func (m *MockGitHub) GetPackageVersionById(arg0 context.Context, arg1 bool, arg2, arg3, arg4 string, arg5 int64) (*github.PackageVersion, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetPackageVersionById", arg0, arg1, arg2, arg3, arg4, arg5) ret0, _ := ret[0].(*github.PackageVersion) @@ -151,13 +308,13 @@ func (m *MockRestAPI) GetPackageVersionById(arg0 context.Context, arg1 bool, arg } // GetPackageVersionById indicates an expected call of GetPackageVersionById. -func (mr *MockRestAPIMockRecorder) GetPackageVersionById(arg0, arg1, arg2, arg3, arg4, arg5 interface{}) *gomock.Call { +func (mr *MockGitHubMockRecorder) GetPackageVersionById(arg0, arg1, arg2, arg3, arg4, arg5 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPackageVersionById", reflect.TypeOf((*MockRestAPI)(nil).GetPackageVersionById), arg0, arg1, arg2, arg3, arg4, arg5) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPackageVersionById", reflect.TypeOf((*MockGitHub)(nil).GetPackageVersionById), arg0, arg1, arg2, arg3, arg4, arg5) } // GetPackageVersionByTag mocks base method. -func (m *MockRestAPI) GetPackageVersionByTag(arg0 context.Context, arg1 bool, arg2, arg3, arg4, arg5 string) (*github.PackageVersion, error) { +func (m *MockGitHub) GetPackageVersionByTag(arg0 context.Context, arg1 bool, arg2, arg3, arg4, arg5 string) (*github.PackageVersion, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetPackageVersionByTag", arg0, arg1, arg2, arg3, arg4, arg5) ret0, _ := ret[0].(*github.PackageVersion) @@ -166,13 +323,13 @@ func (m *MockRestAPI) GetPackageVersionByTag(arg0 context.Context, arg1 bool, ar } // GetPackageVersionByTag indicates an expected call of GetPackageVersionByTag. -func (mr *MockRestAPIMockRecorder) GetPackageVersionByTag(arg0, arg1, arg2, arg3, arg4, arg5 interface{}) *gomock.Call { +func (mr *MockGitHubMockRecorder) GetPackageVersionByTag(arg0, arg1, arg2, arg3, arg4, arg5 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPackageVersionByTag", reflect.TypeOf((*MockRestAPI)(nil).GetPackageVersionByTag), arg0, arg1, arg2, arg3, arg4, arg5) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPackageVersionByTag", reflect.TypeOf((*MockGitHub)(nil).GetPackageVersionByTag), arg0, arg1, arg2, arg3, arg4, arg5) } // GetPackageVersions mocks base method. -func (m *MockRestAPI) GetPackageVersions(arg0 context.Context, arg1 bool, arg2, arg3, arg4 string) ([]*github.PackageVersion, error) { +func (m *MockGitHub) GetPackageVersions(arg0 context.Context, arg1 bool, arg2, arg3, arg4 string) ([]*github.PackageVersion, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetPackageVersions", arg0, arg1, arg2, arg3, arg4) ret0, _ := ret[0].([]*github.PackageVersion) @@ -181,13 +338,13 @@ func (m *MockRestAPI) GetPackageVersions(arg0 context.Context, arg1 bool, arg2, } // GetPackageVersions indicates an expected call of GetPackageVersions. -func (mr *MockRestAPIMockRecorder) GetPackageVersions(arg0, arg1, arg2, arg3, arg4 interface{}) *gomock.Call { +func (mr *MockGitHubMockRecorder) GetPackageVersions(arg0, arg1, arg2, arg3, arg4 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPackageVersions", reflect.TypeOf((*MockRestAPI)(nil).GetPackageVersions), arg0, arg1, arg2, arg3, arg4) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPackageVersions", reflect.TypeOf((*MockGitHub)(nil).GetPackageVersions), arg0, arg1, arg2, arg3, arg4) } // GetPullRequest mocks base method. -func (m *MockRestAPI) GetPullRequest(arg0 context.Context, arg1, arg2 string, arg3 int) (*github.PullRequest, error) { +func (m *MockGitHub) GetPullRequest(arg0 context.Context, arg1, arg2 string, arg3 int) (*github.PullRequest, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetPullRequest", arg0, arg1, arg2, arg3) ret0, _ := ret[0].(*github.PullRequest) @@ -196,13 +353,13 @@ func (m *MockRestAPI) GetPullRequest(arg0 context.Context, arg1, arg2 string, ar } // GetPullRequest indicates an expected call of GetPullRequest. -func (mr *MockRestAPIMockRecorder) GetPullRequest(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { +func (mr *MockGitHubMockRecorder) GetPullRequest(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPullRequest", reflect.TypeOf((*MockRestAPI)(nil).GetPullRequest), arg0, arg1, arg2, arg3) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPullRequest", reflect.TypeOf((*MockGitHub)(nil).GetPullRequest), arg0, arg1, arg2, arg3) } // GetRepository mocks base method. -func (m *MockRestAPI) GetRepository(arg0 context.Context, arg1, arg2 string) (*github.Repository, error) { +func (m *MockGitHub) GetRepository(arg0 context.Context, arg1, arg2 string) (*github.Repository, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetRepository", arg0, arg1, arg2) ret0, _ := ret[0].(*github.Repository) @@ -211,13 +368,13 @@ func (m *MockRestAPI) GetRepository(arg0 context.Context, arg1, arg2 string) (*g } // GetRepository indicates an expected call of GetRepository. -func (mr *MockRestAPIMockRecorder) GetRepository(arg0, arg1, arg2 interface{}) *gomock.Call { +func (mr *MockGitHubMockRecorder) GetRepository(arg0, arg1, arg2 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetRepository", reflect.TypeOf((*MockRestAPI)(nil).GetRepository), arg0, arg1, arg2) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetRepository", reflect.TypeOf((*MockGitHub)(nil).GetRepository), arg0, arg1, arg2) } // GetToken mocks base method. -func (m *MockRestAPI) GetToken() string { +func (m *MockGitHub) GetToken() string { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "GetToken") ret0, _ := ret[0].(string) @@ -225,45 +382,45 @@ func (m *MockRestAPI) GetToken() string { } // GetToken indicates an expected call of GetToken. -func (mr *MockRestAPIMockRecorder) GetToken() *gomock.Call { +func (mr *MockGitHubMockRecorder) GetToken() *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetToken", reflect.TypeOf((*MockRestAPI)(nil).GetToken)) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetToken", reflect.TypeOf((*MockGitHub)(nil).GetToken)) } // ListAllPackages mocks base method. -func (m *MockRestAPI) ListAllPackages(arg0 context.Context, arg1 bool, arg2, arg3 string, arg4, arg5 int) (github0.PackageListResult, error) { +func (m *MockGitHub) ListAllPackages(arg0 context.Context, arg1 bool, arg2, arg3 string, arg4, arg5 int) ([]*github.Package, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "ListAllPackages", arg0, arg1, arg2, arg3, arg4, arg5) - ret0, _ := ret[0].(github0.PackageListResult) + ret0, _ := ret[0].([]*github.Package) ret1, _ := ret[1].(error) return ret0, ret1 } // ListAllPackages indicates an expected call of ListAllPackages. -func (mr *MockRestAPIMockRecorder) ListAllPackages(arg0, arg1, arg2, arg3, arg4, arg5 interface{}) *gomock.Call { +func (mr *MockGitHubMockRecorder) ListAllPackages(arg0, arg1, arg2, arg3, arg4, arg5 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListAllPackages", reflect.TypeOf((*MockRestAPI)(nil).ListAllPackages), arg0, arg1, arg2, arg3, arg4, arg5) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListAllPackages", reflect.TypeOf((*MockGitHub)(nil).ListAllPackages), arg0, arg1, arg2, arg3, arg4, arg5) } // ListAllRepositories mocks base method. -func (m *MockRestAPI) ListAllRepositories(arg0 context.Context, arg1 bool, arg2 string) (github0.RepositoryListResult, error) { +func (m *MockGitHub) ListAllRepositories(arg0 context.Context, arg1 bool, arg2 string) ([]*github.Repository, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "ListAllRepositories", arg0, arg1, arg2) - ret0, _ := ret[0].(github0.RepositoryListResult) + ret0, _ := ret[0].([]*github.Repository) ret1, _ := ret[1].(error) return ret0, ret1 } // ListAllRepositories indicates an expected call of ListAllRepositories. -func (mr *MockRestAPIMockRecorder) ListAllRepositories(arg0, arg1, arg2 interface{}) *gomock.Call { +func (mr *MockGitHubMockRecorder) ListAllRepositories(arg0, arg1, arg2 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListAllRepositories", reflect.TypeOf((*MockRestAPI)(nil).ListAllRepositories), arg0, arg1, arg2) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListAllRepositories", reflect.TypeOf((*MockGitHub)(nil).ListAllRepositories), arg0, arg1, arg2) } // ListFiles mocks base method. -func (m *MockRestAPI) ListFiles(arg0 context.Context, arg1, arg2 string, arg3, arg4, arg5 int) ([]*github.CommitFile, *github.Response, error) { +func (m *MockGitHub) ListFiles(ctx context.Context, owner, repo string, prNumber, perPage, pageNumber int) ([]*github.CommitFile, *github.Response, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "ListFiles", arg0, arg1, arg2, arg3, arg4, arg5) + ret := m.ctrl.Call(m, "ListFiles", ctx, owner, repo, prNumber, perPage, pageNumber) ret0, _ := ret[0].([]*github.CommitFile) ret1, _ := ret[1].(*github.Response) ret2, _ := ret[2].(error) @@ -271,28 +428,28 @@ func (m *MockRestAPI) ListFiles(arg0 context.Context, arg1, arg2 string, arg3, a } // ListFiles indicates an expected call of ListFiles. -func (mr *MockRestAPIMockRecorder) ListFiles(arg0, arg1, arg2, arg3, arg4, arg5 interface{}) *gomock.Call { +func (mr *MockGitHubMockRecorder) ListFiles(ctx, owner, repo, prNumber, perPage, pageNumber interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListFiles", reflect.TypeOf((*MockRestAPI)(nil).ListFiles), arg0, arg1, arg2, arg3, arg4, arg5) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListFiles", reflect.TypeOf((*MockGitHub)(nil).ListFiles), ctx, owner, repo, prNumber, perPage, pageNumber) } // ListPackagesByRepository mocks base method. -func (m *MockRestAPI) ListPackagesByRepository(arg0 context.Context, arg1 bool, arg2, arg3 string, arg4 int64, arg5, arg6 int) (github0.PackageListResult, error) { +func (m *MockGitHub) ListPackagesByRepository(arg0 context.Context, arg1 bool, arg2, arg3 string, arg4 int64, arg5, arg6 int) ([]*github.Package, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "ListPackagesByRepository", arg0, arg1, arg2, arg3, arg4, arg5, arg6) - ret0, _ := ret[0].(github0.PackageListResult) + ret0, _ := ret[0].([]*github.Package) ret1, _ := ret[1].(error) return ret0, ret1 } // ListPackagesByRepository indicates an expected call of ListPackagesByRepository. -func (mr *MockRestAPIMockRecorder) ListPackagesByRepository(arg0, arg1, arg2, arg3, arg4, arg5, arg6 interface{}) *gomock.Call { +func (mr *MockGitHubMockRecorder) ListPackagesByRepository(arg0, arg1, arg2, arg3, arg4, arg5, arg6 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListPackagesByRepository", reflect.TypeOf((*MockRestAPI)(nil).ListPackagesByRepository), arg0, arg1, arg2, arg3, arg4, arg5, arg6) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListPackagesByRepository", reflect.TypeOf((*MockGitHub)(nil).ListPackagesByRepository), arg0, arg1, arg2, arg3, arg4, arg5, arg6) } // ListReviews mocks base method. -func (m *MockRestAPI) ListReviews(arg0 context.Context, arg1, arg2 string, arg3 int, arg4 *github.ListOptions) ([]*github.PullRequestReview, error) { +func (m *MockGitHub) ListReviews(arg0 context.Context, arg1, arg2 string, arg3 int, arg4 *github.ListOptions) ([]*github.PullRequestReview, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "ListReviews", arg0, arg1, arg2, arg3, arg4) ret0, _ := ret[0].([]*github.PullRequestReview) @@ -301,33 +458,28 @@ func (m *MockRestAPI) ListReviews(arg0 context.Context, arg1, arg2 string, arg3 } // ListReviews indicates an expected call of ListReviews. -func (mr *MockRestAPIMockRecorder) ListReviews(arg0, arg1, arg2, arg3, arg4 interface{}) *gomock.Call { +func (mr *MockGitHubMockRecorder) ListReviews(arg0, arg1, arg2, arg3, arg4 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListReviews", reflect.TypeOf((*MockRestAPI)(nil).ListReviews), arg0, arg1, arg2, arg3, arg4) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListReviews", reflect.TypeOf((*MockGitHub)(nil).ListReviews), arg0, arg1, arg2, arg3, arg4) } // NewRequest mocks base method. -func (m *MockRestAPI) NewRequest(method, urlStr string, body any, opts ...github.RequestOption) (*http.Request, error) { +func (m *MockGitHub) NewRequest(method, url string, body io.Reader) (*http.Request, error) { m.ctrl.T.Helper() - varargs := []interface{}{method, urlStr, body} - for _, a := range opts { - varargs = append(varargs, a) - } - ret := m.ctrl.Call(m, "NewRequest", varargs...) + ret := m.ctrl.Call(m, "NewRequest", method, url, body) ret0, _ := ret[0].(*http.Request) ret1, _ := ret[1].(error) return ret0, ret1 } // NewRequest indicates an expected call of NewRequest. -func (mr *MockRestAPIMockRecorder) NewRequest(method, urlStr, body interface{}, opts ...interface{}) *gomock.Call { +func (mr *MockGitHubMockRecorder) NewRequest(method, url, body interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - varargs := append([]interface{}{method, urlStr, body}, opts...) - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NewRequest", reflect.TypeOf((*MockRestAPI)(nil).NewRequest), varargs...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NewRequest", reflect.TypeOf((*MockGitHub)(nil).NewRequest), method, url, body) } // SetCommitStatus mocks base method. -func (m *MockRestAPI) SetCommitStatus(arg0 context.Context, arg1, arg2, arg3 string, arg4 *github.RepoStatus) (*github.RepoStatus, error) { +func (m *MockGitHub) SetCommitStatus(arg0 context.Context, arg1, arg2, arg3 string, arg4 *github.RepoStatus) (*github.RepoStatus, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "SetCommitStatus", arg0, arg1, arg2, arg3, arg4) ret0, _ := ret[0].(*github.RepoStatus) @@ -336,44 +488,7 @@ func (m *MockRestAPI) SetCommitStatus(arg0 context.Context, arg1, arg2, arg3 str } // SetCommitStatus indicates an expected call of SetCommitStatus. -func (mr *MockRestAPIMockRecorder) SetCommitStatus(arg0, arg1, arg2, arg3, arg4 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetCommitStatus", reflect.TypeOf((*MockRestAPI)(nil).SetCommitStatus), arg0, arg1, arg2, arg3, arg4) -} - -// MockGraphQLAPI is a mock of GraphQLAPI interface. -type MockGraphQLAPI struct { - ctrl *gomock.Controller - recorder *MockGraphQLAPIMockRecorder -} - -// MockGraphQLAPIMockRecorder is the mock recorder for MockGraphQLAPI. -type MockGraphQLAPIMockRecorder struct { - mock *MockGraphQLAPI -} - -// NewMockGraphQLAPI creates a new mock instance. -func NewMockGraphQLAPI(ctrl *gomock.Controller) *MockGraphQLAPI { - mock := &MockGraphQLAPI{ctrl: ctrl} - mock.recorder = &MockGraphQLAPIMockRecorder{mock} - return mock -} - -// EXPECT returns an object that allows the caller to indicate expected use. -func (m *MockGraphQLAPI) EXPECT() *MockGraphQLAPIMockRecorder { - return m.recorder -} - -// RunQuery mocks base method. -func (m *MockGraphQLAPI) RunQuery(ctx context.Context, query interface{}, variables map[string]interface{}) error { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "RunQuery", ctx, query, variables) - ret0, _ := ret[0].(error) - return ret0 -} - -// RunQuery indicates an expected call of RunQuery. -func (mr *MockGraphQLAPIMockRecorder) RunQuery(ctx, query, variables interface{}) *gomock.Call { +func (mr *MockGitHubMockRecorder) SetCommitStatus(arg0, arg1, arg2, arg3, arg4 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RunQuery", reflect.TypeOf((*MockGraphQLAPI)(nil).RunQuery), ctx, query, variables) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetCommitStatus", reflect.TypeOf((*MockGitHub)(nil).SetCommitStatus), arg0, arg1, arg2, arg3, arg4) } diff --git a/internal/providers/http/http.go b/internal/providers/http/http.go new file mode 100644 index 0000000000..3d792dd730 --- /dev/null +++ b/internal/providers/http/http.go @@ -0,0 +1,101 @@ +// Copyright 2023 Stacklok, Inc +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package http implements an HTTP client for interacting with an HTTP API. +package http + +import ( + "context" + "encoding/json" + "io" + "net/http" + "net/url" + + "golang.org/x/oauth2" + + provifv1 "github.com/stacklok/mediator/pkg/providers/v1" +) + +// REST is the interface for interacting with an REST API. +type REST struct { + baseURL *url.URL + cli *http.Client + tok string +} + +// Ensure that REST implements the REST interface +var _ provifv1.REST = (*REST)(nil) + +// NewREST creates a new RESTful client. +func NewREST(config *provifv1.RESTConfig, tok string) (*REST, error) { + var cli *http.Client + + if tok != "" { + ts := oauth2.StaticTokenSource( + &oauth2.Token{AccessToken: tok}, + ) + cli = oauth2.NewClient(context.Background(), ts) + } else { + cli = &http.Client{} + } + + var baseURL *url.URL + baseURL, err := baseURL.Parse(config.BaseURL) + if err != nil { + return nil, err + } + + return &REST{ + cli: cli, + baseURL: baseURL, + tok: tok, + }, nil +} + +// GetToken returns the token for the provider +func (h *REST) GetToken() string { + return h.tok +} + +// NewRequest creates an HTTP request. +func (h *REST) NewRequest(method, endpoint string, body io.Reader) (*http.Request, error) { + targetURL := endpoint + if h.baseURL != nil { + u := h.baseURL.JoinPath(endpoint) + targetURL = u.String() + } + + return http.NewRequest(method, targetURL, body) +} + +// Do executes an HTTP request. +func (h *REST) Do(ctx context.Context, req *http.Request) (*http.Response, error) { + req = req.WithContext(ctx) + + return h.cli.Do(req) +} + +// ParseV1Config parses the raw config into a HTTPConfig struct +func ParseV1Config(rawCfg json.RawMessage) (*provifv1.RESTConfig, error) { + type wrapper struct { + REST *provifv1.RESTConfig `json:"rest" validate:"required"` + } + + var w wrapper + if err := provifv1.ParseAndValidate(rawCfg, &w); err != nil { + return nil, err + } + + return w.REST, nil +} diff --git a/internal/providers/providers.go b/internal/providers/providers.go index f364256c53..e2201c4c4d 100644 --- a/internal/providers/providers.go +++ b/internal/providers/providers.go @@ -20,25 +20,27 @@ import ( "context" "fmt" + "golang.org/x/exp/slices" + "github.com/stacklok/mediator/internal/crypto" "github.com/stacklok/mediator/internal/db" + gitclient "github.com/stacklok/mediator/internal/providers/git" ghclient "github.com/stacklok/mediator/internal/providers/github" + httpclient "github.com/stacklok/mediator/internal/providers/http" + provinfv1 "github.com/stacklok/mediator/pkg/providers/v1" ) -// BuildClient is a utility function which allows for building -// a client for a provider from a groupID and provider name. -// -// TODO: This should return an abstract provider interface -// instead of a concrete github client. -func BuildClient( +// GetProviderBuilder is a utility function which allows for the creation of +// a provider factory. +func GetProviderBuilder( ctx context.Context, - prov string, + prov db.Provider, groupID int32, store db.Store, crypteng *crypto.Engine, -) (ghclient.RestAPI, error) { +) (*ProviderBuilder, error) { encToken, err := store.GetAccessTokenByGroupID(ctx, - db.GetAccessTokenByGroupIDParams{Provider: prov, GroupID: groupID}) + db.GetAccessTokenByGroupIDParams{Provider: prov.Name, GroupID: groupID}) if err != nil { return nil, fmt.Errorf("error getting access token: %w", err) } @@ -48,9 +50,98 @@ func BuildClient( return nil, fmt.Errorf("error decrypting access token: %w", err) } - cli, err := ghclient.NewRestClient(ctx, ghclient.GitHubConfig{ - Token: decryptedToken.AccessToken, - }, encToken.OwnerFilter.String) + return NewProviderBuilder(&prov, encToken, decryptedToken.AccessToken), nil +} + +// ProviderBuilder is a utility struct which allows for the creation of +// provider clients. +type ProviderBuilder struct { + p *db.Provider + tokenInf db.ProviderAccessToken + tok string +} + +// NewProviderBuilder creates a new provider builder. +func NewProviderBuilder( + p *db.Provider, + tokenInf db.ProviderAccessToken, + tok string, +) *ProviderBuilder { + return &ProviderBuilder{ + p: p, + tokenInf: tokenInf, + tok: tok, + } +} + +// Implements returns true if the provider implements the given type. +func (pb *ProviderBuilder) Implements(impl db.ProviderType) bool { + return slices.Contains(pb.p.Implements, impl) +} + +// GetName returns the name of the provider instance as defined in the +// database. +func (pb *ProviderBuilder) GetName() string { + return pb.p.Name +} + +// GetToken returns the token for the provider. +func (pb *ProviderBuilder) GetToken() string { + return pb.tok +} + +// GetGit returns a git client for the provider. +func (pb *ProviderBuilder) GetGit() (*gitclient.Git, error) { + if !pb.Implements(db.ProviderTypeGit) { + return nil, fmt.Errorf("provider does not implement git") + } + + return gitclient.NewGit(pb.tok), nil +} + +// GetHTTP returns a github client for the provider. +func (pb *ProviderBuilder) GetHTTP(ctx context.Context) (provinfv1.REST, error) { + if !pb.Implements(db.ProviderTypeRest) { + return nil, fmt.Errorf("provider does not implement rest") + } + + // We can re-use the GitHub provider in case it also implements GitHub. + // The client gives us the ability to handle rate limiting and other + // things. + if pb.Implements(db.ProviderTypeGithub) { + return pb.GetGitHub(ctx) + } + + if pb.p.Version != provinfv1.V1 { + return nil, fmt.Errorf("provider version not supported") + } + + // TODO: Parsing will change based on version + cfg, err := httpclient.ParseV1Config(pb.p.Definition) + if err != nil { + return nil, fmt.Errorf("error parsing http config: %w", err) + } + + return httpclient.NewREST(cfg, pb.tok) +} + +// GetGitHub returns a github client for the provider. +func (pb *ProviderBuilder) GetGitHub(ctx context.Context) (*ghclient.RestClient, error) { + if !pb.Implements(db.ProviderTypeGithub) { + return nil, fmt.Errorf("provider does not implement github") + } + + if pb.p.Version != provinfv1.V1 { + return nil, fmt.Errorf("provider version not supported") + } + + // TODO: Parsing will change based on version + cfg, err := ghclient.ParseV1Config(pb.p.Definition) + if err != nil { + return nil, fmt.Errorf("error parsing github config: %w", err) + } + + cli, err := ghclient.NewRestClient(ctx, cfg, pb.GetToken(), pb.tokenInf.OwnerFilter.String) if err != nil { return nil, fmt.Errorf("error creating github client: %w", err) } diff --git a/internal/reconcilers/artifacts.go b/internal/reconcilers/artifacts.go index 7ebd369782..67e60525a3 100644 --- a/internal/reconcilers/artifacts.go +++ b/internal/reconcilers/artifacts.go @@ -105,11 +105,21 @@ func (e *Reconciler) handleArtifactsReconcilerEvent(ctx context.Context, evt *Re return fmt.Errorf("error retrieving provider: %w", err) } - cli, err := providers.BuildClient(ctx, prov.Name, evt.Group, e.store, e.crypteng) + p, err := providers.GetProviderBuilder(ctx, prov, evt.Group, e.store, e.crypteng) if err != nil { return fmt.Errorf("error building client: %w", err) } + if !p.Implements(db.ProviderTypeGithub) { + log.Printf("provider %s is not supported for artifacts reconciler", prov.Name) + return nil + } + + cli, err := p.GetGitHub(ctx) + if err != nil { + return fmt.Errorf("error getting github client: %w", err) + } + isOrg := (cli.GetOwner() != "") // todo: add another type of artifacts artifacts, err := cli.ListPackagesByRepository(ctx, isOrg, repository.RepoOwner, @@ -117,7 +127,7 @@ func (e *Reconciler) handleArtifactsReconcilerEvent(ctx context.Context, evt *Re if err != nil { return fmt.Errorf("error retrieving artifacts: %w", err) } - for _, artifact := range artifacts.Packages { + for _, artifact := range artifacts { // store information if we do not have it newArtifact, err := e.store.UpsertArtifact(ctx, db.UpsertArtifactParams{RepositoryID: repository.ID, ArtifactName: artifact.GetName(), diff --git a/pkg/providers/v1/providers.go b/pkg/providers/v1/providers.go new file mode 100644 index 0000000000..1f61ca964a --- /dev/null +++ b/pkg/providers/v1/providers.go @@ -0,0 +1,115 @@ +// Copyright 2023 Stacklok, Inc +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package v1 for providers provides the public interfaces for the providers +// implemented by mediator. The providers are the sources of the data +// that is used by the rules. +package v1 + +import ( + "context" + "encoding/json" + "fmt" + "io" + "net/http" + + "github.com/go-git/go-git/v5" + "github.com/go-playground/validator/v10" + "github.com/google/go-github/v53/github" +) + +// V1 is the version of the providers interface +const ( + V1 = "v1" +) + +// Provider is the general interface for all providers +type Provider interface { + // GetToken returns the token for the provider + GetToken() string +} + +// Git is the interface for git providers +type Git interface { + Provider + + // Clone clones a git repository + Clone(ctx context.Context, url string, branch string) (*git.Repository, error) +} + +// REST is the interface for interacting with an REST API. +type REST interface { + Provider + + // NewRequest creates an HTTP request. + NewRequest(method, url string, body io.Reader) (*http.Request, error) + + // Do executes an HTTP request. + Do(ctx context.Context, req *http.Request) (*http.Response, error) +} + +// RESTConfig is the struct that contains the configuration for the HTTP client +type RESTConfig struct { + BaseURL string `json:"base_url" yaml:"base_url" mapstructure:"base_url" validate:"required"` +} + +// GitHub is the interface for interacting with the GitHub REST API +// Add methods here for interacting with the GitHub Rest API +type GitHub interface { + Provider + REST + + GetAuthenticatedUser(context.Context) (*github.User, error) + GetRepository(context.Context, string, string) (*github.Repository, error) + ListAllRepositories(context.Context, bool, string) ([]*github.Repository, error) + GetBranchProtection(context.Context, string, string, string) (*github.Protection, error) + ListAllPackages(context.Context, bool, string, string, int, int) ([]*github.Package, error) + ListPackagesByRepository(context.Context, bool, string, string, int64, int, int) ([]*github.Package, error) + GetPackageByName(context.Context, bool, string, string, string) (*github.Package, error) + GetPackageVersions(context.Context, bool, string, string, string) ([]*github.PackageVersion, error) + GetPackageVersionByTag(context.Context, bool, string, string, string, string) (*github.PackageVersion, error) + GetPackageVersionById(context.Context, bool, string, string, string, int64) (*github.PackageVersion, error) + GetPullRequest(context.Context, string, string, int) (*github.PullRequest, error) + CreateReview(context.Context, string, string, int, *github.PullRequestReviewRequest) (*github.PullRequestReview, error) + ListReviews(context.Context, string, string, int, *github.ListOptions) ([]*github.PullRequestReview, error) + DismissReview(context.Context, string, string, int, int64, + *github.PullRequestReviewDismissalRequest) (*github.PullRequestReview, error) + SetCommitStatus(context.Context, string, string, string, *github.RepoStatus) (*github.RepoStatus, error) + ListFiles(ctx context.Context, owner string, repo string, prNumber int, + perPage int, pageNumber int) ([]*github.CommitFile, *github.Response, error) + GetOwner() string +} + +// GitHubConfig is the struct that contains the configuration for the GitHub client +// Endpoint: is the GitHub API endpoint +// If using the public GitHub API, Endpoint can be left blank +// disable revive linting for this struct as there is nothing wrong with the +// naming convention +type GitHubConfig struct { + Endpoint string `json:"endpoint" yaml:"endpoint" mapstructure:"endpoint"` +} + +// ParseAndValidate parses the given provider configuration and validates it. +func ParseAndValidate(rawConfig json.RawMessage, to any) error { + if err := json.Unmarshal(rawConfig, to); err != nil { + return fmt.Errorf("error parsing http v1 provider config: %w", err) + } + + validate := validator.New(validator.WithRequiredStructEnabled()) + if err := validate.Struct(to); err != nil { + return fmt.Errorf("error validating http v1 provider config: %w", err) + } + + return nil +}