diff --git a/config/policy-bot.example.yml b/config/policy-bot.example.yml index 91a3a835..d15232f3 100644 --- a/config/policy-bot.example.yml +++ b/config/policy-bot.example.yml @@ -112,6 +112,13 @@ sessions: # # POLICYBOT_OPTIONS_FORCE_SHARED_POLICY environment variable. # force_shared_policy: false # +# # The shared organization to use with shared_repository. +# # If unset, defaults to using the same organization as the organization which contains the pull request repository. +# shared_organization: kouzoh +# +# # The installation ID of the app installation on the shared organization. +# shared_organization_installation_id: 81498622 +# # # The name of an organization repository to look in for a shared policy if # # a repository does not define a policy file. Can also be set by the # # POLICYBOT_OPTIONS_SHARED_REPOSITORY environment variable. Explicitly set @@ -128,6 +135,9 @@ sessions: # # Can also be set by the POLICYBOT_OPTIONS_SHARED_POLICY_PATH environment variable. # shared_policy_path: policy.yml # +# # Whether the status check should be dry-run only (log to console only, without writing to GitHub API). +# status_check_dry_run_only: false +# # # The context prefix for status checks created by the bot. Can also be set by the # # POLICYBOT_OPTIONS_STATUS_CHECK_CONTEXT environment variable. # status_check_context: policy-bot diff --git a/server/handler/base.go b/server/handler/base.go index 09e4a737..ae846834 100644 --- a/server/handler/base.go +++ b/server/handler/base.go @@ -43,7 +43,12 @@ type Base struct { } // PostStatus posts a GitHub commit status with consistent logging. -func PostStatus(ctx context.Context, client *github.Client, owner, repo, ref string, status *github.RepoStatus) error { +func PostStatus(ctx context.Context, opts *PullEvaluationOptions, client *github.Client, owner, repo, ref string, status *github.RepoStatus) error { + if opts != nil && opts.StatusCheckDryRunOnly { + zerolog.Ctx(ctx).Info().Msgf("Dry-run: would set %q status on %s to %s: %s", status.GetContext(), ref, status.GetState(), status.GetDescription()) + return nil + } + zerolog.Ctx(ctx).Info().Msgf("Setting %q status on %s to %s: %s", status.GetContext(), ref, status.GetState(), status.GetDescription()) _, _, err := client.Repositories.CreateStatus(ctx, owner, repo, ref, status) return errors.WithStack(err) diff --git a/server/handler/eval_context.go b/server/handler/eval_context.go index bf004a33..5c142c9a 100644 --- a/server/handler/eval_context.go +++ b/server/handler/eval_context.go @@ -223,12 +223,12 @@ func (ec *EvalContext) PostStatus(ctx context.Context, state, message string) { return } - if err := PostStatus(ctx, ec.Client, owner, repo, sha, &status); err != nil { + if err := PostStatus(ctx, ec.Options, ec.Client, owner, repo, sha, &status); err != nil { logger.Err(err).Msg("Failed to post repo status") } if ec.Options.PostInsecureStatusChecks { status.Context = github.Ptr(ec.Options.StatusCheckContext) - if err := PostStatus(ctx, ec.Client, owner, repo, sha, &status); err != nil { + if err := PostStatus(ctx, ec.Options, ec.Client, owner, repo, sha, &status); err != nil { logger.Err(err).Msg("Failed to post insecure repo status") } } diff --git a/server/handler/eval_options.go b/server/handler/eval_options.go index 9ff21b25..df97b2fb 100644 --- a/server/handler/eval_options.go +++ b/server/handler/eval_options.go @@ -41,13 +41,19 @@ type DescriptionPrefix struct { type PullEvaluationOptions struct { PolicyPath string `yaml:"policy_path"` - SharedRepository *string `yaml:"shared_repository"` - SharedPolicyBranch *string `yaml:"shared_policy_branch"` - SharedPolicyPath *string `yaml:"shared_policy_path"` + SharedOrganizationInstallationID *int64 `yaml:"shared_organization_installation_id"` + SharedOrganization *string `yaml:"shared_organization"` + SharedRepository *string `yaml:"shared_repository"` + SharedPolicyBranch *string `yaml:"shared_policy_branch"` + SharedPolicyPath *string `yaml:"shared_policy_path"` // Ignore PolicyPath and use SharedRepository and SharedPolicyPath only. ForceSharedPolicy bool `yaml:"force_shared_policy"` + // StatusCheckDryRunOnly will be used to determine if the status check should be dry-run only and + // not actually written to the pull request. + StatusCheckDryRunOnly bool `yaml:"status_check_dry_run_only"` + // StatusCheckContext will be used to create the status context. It will be used in the following // pattern: : StatusCheckContext string `yaml:"status_check_context"` diff --git a/server/handler/fetcher.go b/server/handler/fetcher.go index 4d6d9026..bdf3afe0 100644 --- a/server/handler/fetcher.go +++ b/server/handler/fetcher.go @@ -25,6 +25,7 @@ import ( "github.com/google/go-github/v72/github" "github.com/palantir/go-githubapp/appconfig" + "github.com/palantir/go-githubapp/githubapp" "github.com/palantir/policy-bot/policy" "github.com/palantir/policy-bot/tracing" "go.opentelemetry.io/otel/attribute" @@ -96,12 +97,14 @@ func (cc *ConfigCache) GetOrUpdate(fn func() (*FetchedConfig, error)) (*FetchedC type ConfigFetcher struct { sharedConfigCache ConfigCache - Options PullEvaluationOptions - Loader *appconfig.Loader + ClientCreator githubapp.ClientCreator + Options PullEvaluationOptions + Loader *appconfig.Loader } func (cf *ConfigFetcher) configForSharedRepository(ctx context.Context, client *github.Client, owner string) (*FetchedConfig, error) { var ref string + var err error if cf.Options.SharedPolicyBranch != nil { ref = *cf.Options.SharedPolicyBranch } else { @@ -113,6 +116,15 @@ func (cf *ConfigFetcher) configForSharedRepository(ctx context.Context, client * ref = r.GetDefaultBranch() } + // If SharedOrganization is set, both the owner organization and the client needs to be overriden + if cf.Options.SharedOrganization != nil && cf.Options.SharedOrganizationInstallationID != nil { + owner = *cf.Options.SharedOrganization + client, err = cf.ClientCreator.NewInstallationClient(*cf.Options.SharedOrganizationInstallationID) + if err != nil { + return nil, fmt.Errorf("failed to create client for shared organization %s with installation ID %d: %w", owner, *cf.Options.SharedOrganizationInstallationID, err) + } + } + file, _, _, err := client.Repositories.GetContents(ctx, owner, *cf.Options.SharedRepository, *cf.Options.SharedPolicyPath, &github.RepositoryContentGetOptions{ Ref: ref, }) diff --git a/server/handler/installation.go b/server/handler/installation.go index 4dd3aa69..e9b93704 100644 --- a/server/handler/installation.go +++ b/server/handler/installation.go @@ -107,7 +107,7 @@ func (h *Installation) postRepoInstallationStatus(ctx context.Context, client *g State: &state, Description: &message, } - if err := PostStatus(ctx, client, owner, repo, head, status); err != nil { + if err := PostStatus(ctx, h.PullOpts, client, owner, repo, head, status); err != nil { logger.Err(errors.WithStack(err)).Msg("Failed to post repo status") } } diff --git a/server/handler/merge_group.go b/server/handler/merge_group.go index 180602f0..f3dddf64 100644 --- a/server/handler/merge_group.go +++ b/server/handler/merge_group.go @@ -74,13 +74,13 @@ func (h *MergeGroup) Handle(ctx context.Context, eventType, devlieryID string, p Description: &message, } - if err := PostStatus(ctx, client, owner, repository, headSHA, status); err != nil { + if err := PostStatus(ctx, h.PullOpts, client, owner, repository, headSHA, status); err != nil { logger.Err(errors.WithStack(err)).Msg("Failed to post status check for merge group") } if h.PullOpts.PostInsecureStatusChecks { status.Context = github.Ptr(h.PullOpts.StatusCheckContext) - if err := PostStatus(ctx, client, owner, repository, headSHA, status); err != nil { + if err := PostStatus(ctx, h.PullOpts, client, owner, repository, headSHA, status); err != nil { logger.Err(err).Msg("Failed to post insecure repo status") } } diff --git a/server/handler/status.go b/server/handler/status.go index 6ace470e..ff678b92 100644 --- a/server/handler/status.go +++ b/server/handler/status.go @@ -96,8 +96,15 @@ func (h *Status) processOwn(ctx context.Context, event github.StatusEvent) error Description: &desc, } - _, _, err = client.Repositories.CreateStatus(ctx, ownerName, repoName, commitSHA, status) - return err + if h.PullOpts.StatusCheckDryRunOnly { + return nil + } + + if err := PostStatus(ctx, h.PullOpts, client, ownerName, repoName, commitSHA, status); err != nil { + logger.Err(errors.WithStack(err)).Msg("Failed to post status check for merge group") + } + + return nil } func (h *Status) processOthers(ctx context.Context, event github.StatusEvent) error { diff --git a/server/server.go b/server/server.go index 3e2ac216..e364c593 100644 --- a/server/server.go +++ b/server/server.go @@ -203,7 +203,8 @@ func New(ctx context.Context, c *Config) (*Server, error) { PullOpts: &c.Options, ConfigFetcher: &handler.ConfigFetcher{ - Options: c.Options, + ClientCreator: cc, + Options: c.Options, Loader: appconfig.NewLoader( policyPaths, appconfig.WithOwnerDefault(*c.Options.SharedRepository, sharedPolicyPaths),