diff --git a/README.md b/README.md index e5e2db4..de11d26 100644 --- a/README.md +++ b/README.md @@ -54,6 +54,8 @@ the [`local-config.yaml`][config] can be used to set the variables. | | | | | `BITBUCKET_USERNAME` | | Name of the user used for basic git authentication to pull and update the metadata repository. | | `BITBUCKET_PASSWORD` | | Password of the user used for basic git authentication to pull and update the metadata repository. | +| `PULL_REQUEST_BUILD_URL` | | Url to link to on pull request builds. Should probably be the public URL of this service. | +| `PULL_REQUEST_BUILD_KEY` | `metadata-service` | Key for pull request builds on pull requests in the underlying git repository. Changed files are syntactically validated. | | | | | | `GIT_COMMITTER_NAME` | | Name of the user used to create the Git commits. | | `GIT_COMMITTER_EMAIL` | | E-Mail of the user used to create the Git commits. | diff --git a/go.mod b/go.mod index 43cbabe..f5afc7d 100644 --- a/go.mod +++ b/go.mod @@ -51,6 +51,7 @@ require ( github.com/elastic/go-windows v1.0.1 // indirect github.com/emirpasic/gods v1.18.1 // indirect github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect + github.com/go-playground/webhooks/v6 v6.3.0 // indirect github.com/goccy/go-json v0.10.2 // indirect github.com/golang-jwt/jwt/v4 v4.5.0 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect diff --git a/go.sum b/go.sum index d6699f3..a4e14ad 100644 --- a/go.sum +++ b/go.sum @@ -94,9 +94,12 @@ github.com/go-git/go-git/v5 v5.12.0 h1:7Md+ndsjrzZxbddRDZjF14qK+NN56sy6wkqaVrjZt github.com/go-git/go-git/v5 v5.12.0/go.mod h1:FTM9VKtnI2m65hNI/TenDDDnUf2Q9FHnXYjuz9i5OEY= github.com/go-http-utils/headers v0.0.0-20181008091004-fed159eddc2a h1:v6zMvHuY9yue4+QkG/HQ/W67wvtQmWJ4SDo9aK/GIno= github.com/go-http-utils/headers v0.0.0-20181008091004-fed159eddc2a/go.mod h1:I79BieaU4fxrw4LMXby6q5OS9XnoR9UIKLOzDFjUmuw= +github.com/go-playground/webhooks/v6 v6.3.0 h1:zBLUxK1Scxwi97TmZt5j/B/rLlard2zY7P77FHg58FE= +github.com/go-playground/webhooks/v6 v6.3.0/go.mod h1:GCocmfMtpJdkEOM1uG9p2nXzg1kY5X/LtvQgtPHUaaA= github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/gogits/go-gogs-client v0.0.0-20200905025246-8bb8a50cb355/go.mod h1:cY2AIrMgHm6oOHmR7jY+9TtjzSjQ3iG7tURJG3Y6XH0= github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg= github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= diff --git a/internal/acorn/config/customconfigint.go b/internal/acorn/config/customconfigint.go index 1007b50..675905a 100644 --- a/internal/acorn/config/customconfigint.go +++ b/internal/acorn/config/customconfigint.go @@ -33,6 +33,8 @@ type CustomConfiguration interface { MetadataRepoUrl() string MetadataRepoMainline() string + MetadataRepoProject() string + MetadataRepoName() string UpdateJobIntervalCronPart() string UpdateJobTimeoutSeconds() uint16 @@ -66,6 +68,9 @@ type CustomConfiguration interface { RedisUrl() string RedisPassword() string + + PullRequestBuildUrl() string + PullRequestBuildKey() string } type NotificationConsumerConfig struct { @@ -123,4 +128,6 @@ const ( KeyAllowedFileCategories = "ALLOWED_FILE_CATEGORIES" KeyRedisUrl = "REDIS_URL" KeyRedisPassword = "REDIS_PASSWORD" + KeyPullRequestBuildUrl = "PULL_REQUEST_BUILD_URL" + KeyPullRequestBuildKey = "PULL_REQUEST_BUILD_KEY" ) diff --git a/internal/acorn/errors/httperror/error.go b/internal/acorn/errors/httperror/error.go index 9160722..d4efae0 100644 --- a/internal/acorn/errors/httperror/error.go +++ b/internal/acorn/errors/httperror/error.go @@ -8,6 +8,7 @@ import ( type Error interface { Ctx() context.Context IsHttpError() bool + Status() int } // this also implements the error interface diff --git a/internal/acorn/repository/bitbucketint.go b/internal/acorn/repository/bitbucketint.go index 5780c35..655f086 100644 --- a/internal/acorn/repository/bitbucketint.go +++ b/internal/acorn/repository/bitbucketint.go @@ -14,6 +14,14 @@ type Bitbucket interface { GetBitbucketUser(ctx context.Context, username string) (BitbucketUser, error) GetBitbucketUsers(ctx context.Context, usernames []string) ([]BitbucketUser, error) FilterExistingUsernames(ctx context.Context, usernames []string) ([]string, error) + + // GetChangedFilesOnPullRequest returns the file paths and contents list of changed files, and the + // head commit hash of the pull request source for which the files were obtained. + GetChangedFilesOnPullRequest(ctx context.Context, pullRequestId int) ([]File, string, error) + + AddCommitBuildStatus(ctx context.Context, commitHash string, url string, key string, success bool) error + + CreatePullRequestComment(ctx context.Context, pullRequestId int, comment string) error } type BitbucketUser struct { @@ -21,3 +29,8 @@ type BitbucketUser struct { Name string `json:"name"` Active bool `json:"active"` } + +type File struct { + Path string + Contents string +} diff --git a/internal/acorn/service/prvalidatorint.go b/internal/acorn/service/prvalidatorint.go new file mode 100644 index 0000000..5343940 --- /dev/null +++ b/internal/acorn/service/prvalidatorint.go @@ -0,0 +1,15 @@ +package service + +import "context" + +// PRValidator validates pull requests in the underlying repository to prevent bringing invalid content to the mainline. +type PRValidator interface { + IsPRValidator() bool + + // ValidatePullRequest validates the pull request, commenting on it and setting a build result. + // + // Failures to validate a pull request are not considered errors. Errors are only returned if + // the process of validation could not be completed (failure to respond by git server, + // could not obtain file list, etc.) + ValidatePullRequest(ctx context.Context, id uint64, toRef string, fromRef string) error +} diff --git a/internal/repository/bitbucket/bbclient/bbclient.go b/internal/repository/bitbucket/bbclient/bbclient.go index 5f8ccc2..6d77974 100644 --- a/internal/repository/bitbucket/bbclient/bbclient.go +++ b/internal/repository/bitbucket/bbclient/bbclient.go @@ -140,10 +140,10 @@ func (c *Impl) betweenFailureAndRetry() aurestclientapi.BeforeRetryCallback { func (c *Impl) call(ctx context.Context, method string, requestUrlExtension string, requestBody interface{}, responseBodyPointer interface{}) error { remoteUrl := fmt.Sprintf("%s/%s", c.apiBaseUrl, requestUrlExtension) - response := &aurestclientapi.ParsedResponse{ + response := aurestclientapi.ParsedResponse{ Body: responseBodyPointer, } - err := c.Client.Perform(ctx, method, remoteUrl, requestBody, response) + err := c.Client.Perform(ctx, method, remoteUrl, requestBody, &response) if err != nil { return err } @@ -167,3 +167,92 @@ func (c *Impl) GetBitbucketUser(ctx context.Context, username string) (repositor err := c.call(ctx, http.MethodGet, urlExt, nil, &response) return response, err } + +func (c *Impl) GetPullRequest(ctx context.Context, projectKey string, repositorySlug string, pullRequestId int32) (bbclientint.PullRequest, error) { + urlExt := fmt.Sprintf("%s/projects/%s/repos/%s/pull-requests/%d", + bbclientint.CoreApi, + url.PathEscape(projectKey), + url.PathEscape(repositorySlug), + pullRequestId) + response := bbclientint.PullRequest{} + err := c.call(ctx, http.MethodGet, urlExt, nil, &response) + return response, err +} + +func (c *Impl) GetChanges(ctx context.Context, projectKey string, repositorySlug string, sinceHash string, untilHash string) (bbclientint.Changes, error) { + // since : main + // until : pr head + urlExt := fmt.Sprintf("%s/projects/%s/repos/%s/changes?since=%s&until=%s&limit=%d", + bbclientint.CoreApi, + url.PathEscape(projectKey), + url.PathEscape(repositorySlug), + url.QueryEscape(sinceHash), + url.QueryEscape(untilHash), + 1000) // TODO pagination? + response := bbclientint.Changes{} + err := c.call(ctx, http.MethodGet, urlExt, nil, &response) + return response, err +} + +func (c *Impl) getFileContentsPage(ctx context.Context, projectKey string, repositorySlug string, atHash string, path string, start int, limit int) (bbclientint.PaginatedLines, error) { + escapedPath := "" + for _, pathComponent := range strings.Split(path, "/") { + escapedPath += "/" + url.PathEscape(pathComponent) + } + urlExt := fmt.Sprintf("%s/projects/%s/repos/%s/browse/%s?at=%s&start=%d&limit=%d", + bbclientint.CoreApi, + url.PathEscape(projectKey), + url.PathEscape(repositorySlug), + escapedPath, + url.QueryEscape(atHash), + start, + limit) + response := bbclientint.PaginatedLines{} + err := c.call(ctx, http.MethodGet, urlExt, nil, &response) + return response, err +} + +func (c *Impl) GetFileContentsAt(ctx context.Context, projectKey string, repositorySlug string, atHash string, path string) (string, error) { + var contents strings.Builder + var err error + start := 0 + + page := bbclientint.PaginatedLines{ + IsLastPage: false, + NextPageStart: &start, + } + + for !page.IsLastPage && page.NextPageStart != nil { + page, err = c.getFileContentsPage(ctx, projectKey, repositorySlug, atHash, path, *page.NextPageStart, 1000) + if err != nil { + return contents.String(), err + } + for _, line := range page.Lines { + contents.WriteString(line.Text + "\n") + } + } + + return contents.String(), nil +} + +func (c *Impl) AddProjectRepositoryCommitBuildStatus(ctx context.Context, projectKey string, repositorySlug string, commitId string, commitBuildStatusRequest bbclientint.CommitBuildStatusRequest) error { + urlExt := fmt.Sprintf("%s/projects/%s/repos/%s/commits/%s/builds", + bbclientint.CoreApi, + url.PathEscape(projectKey), + url.PathEscape(repositorySlug), + url.PathEscape(commitId)) + + return c.call(ctx, http.MethodPost, urlExt, commitBuildStatusRequest, nil) +} + +func (c *Impl) CreatePullRequestComment(ctx context.Context, projectKey string, repositorySlug string, pullRequestId int64, pullRequestCommentRequest bbclientint.PullRequestCommentRequest) (bbclientint.PullRequestComment, error) { + urlExt := fmt.Sprintf("%s/projects/%s/repos/%s/pull-requests/%d/comments", + bbclientint.CoreApi, + url.PathEscape(projectKey), + url.PathEscape(repositorySlug), + pullRequestId) + + response := bbclientint.PullRequestComment{} + err := c.call(ctx, http.MethodPost, urlExt, pullRequestCommentRequest, &response) + return response, err +} diff --git a/internal/repository/bitbucket/bbclientint/bbclientint.go b/internal/repository/bitbucket/bbclientint/bbclientint.go index deba004..4619f23 100644 --- a/internal/repository/bitbucket/bbclientint/bbclientint.go +++ b/internal/repository/bitbucket/bbclientint/bbclientint.go @@ -9,6 +9,14 @@ type BitbucketClient interface { Setup() error GetBitbucketUser(ctx context.Context, username string) (repository.BitbucketUser, error) + + GetPullRequest(ctx context.Context, projectKey string, repositorySlug string, pullRequestId int32) (PullRequest, error) + GetChanges(ctx context.Context, projectKey string, repositorySlug string, sinceHash string, untilHash string) (Changes, error) + GetFileContentsAt(ctx context.Context, projectKey string, repositorySlug string, atHash string, path string) (string, error) + + AddProjectRepositoryCommitBuildStatus(ctx context.Context, projectKey string, repositorySlug string, commitId string, commitBuildStatusRequest CommitBuildStatusRequest) error + + CreatePullRequestComment(ctx context.Context, projectKey string, repositorySlug string, pullRequestId int64, pullRequestCommentRequest PullRequestCommentRequest) (PullRequestComment, error) } const ( diff --git a/internal/repository/bitbucket/bbclientint/bbmodels.go b/internal/repository/bitbucket/bbclientint/bbmodels.go new file mode 100644 index 0000000..bbef3b0 --- /dev/null +++ b/internal/repository/bitbucket/bbclientint/bbmodels.go @@ -0,0 +1,214 @@ +package bbclientint + +// not part of spec - FileOrDirectory is missing useful fields + +type PaginatedLines struct { + Lines []struct { + Text string `json:"text"` + } `json:"lines"` + Start int `json:"start"` + Size int `json:"size"` + IsLastPage bool `json:"isLastPage"` + Limit int `json:"limit"` + NextPageStart *int `json:"nextPageStart"` +} + +// part of spec, sorted alphabetically + +type Changes struct { + FromHash string `json:"fromHash"` + ToHash string `json:"toHash"` + Values []Change `json:"values,omitempty"` + Size int `json:"size"` + IsLastPage bool `json:"isLastPage"` + Start int `json:"start"` + Limit int `json:"limit"` + NextPageStart *int `json:"nextPageStart"` +} + +type Change struct { + ContentId string `json:"contentId"` + FromContentId string `json:"fromContentId"` + Path struct { + Components []string `json:"components"` + Parent string `json:"parent"` + Name string `json:"name"` + Extension string `json:"extension"` + ToString string `json:"toString"` + } `json:"path"` + Executable bool `json:"executable"` + PercentUnchanged int `json:"percentUnchanged"` + Type string `json:"type"` + NodeType string `json:"nodeType"` + SrcExecutable bool `json:"srcExecutable"` + Links struct { + Self []struct { + Href string `json:"href"` + } `json:"self"` + } `json:"links"` + Properties struct { + GitChangeType string `json:"gitChangeType"` + } `json:"properties"` +} + +type CommitBuildStatusRequest struct { + Key string `yaml:"key" json:"key"` + State string `yaml:"state" json:"state"` + Url string `yaml:"url" json:"url"` + BuildNumber *int32 `yaml:"buildNumber,omitempty" json:"buildNumber,omitempty"` + Description *string `yaml:"description,omitempty" json:"description,omitempty"` + Duration *int32 `yaml:"duration,omitempty" json:"duration,omitempty"` + LastUpdated *int32 `yaml:"lastUpdated,omitempty" json:"lastUpdated,omitempty"` + Name *string `yaml:"name,omitempty" json:"name,omitempty"` + Parent *string `yaml:"parent,omitempty" json:"parent,omitempty"` + Ref *string `yaml:"ref,omitempty" json:"ref,omitempty"` + TestResults *TestResults `yaml:"testResults,omitempty" json:"testResults,omitempty"` +} + +type Link struct { + Href string `yaml:"href" json:"href"` + Name *string `yaml:"name,omitempty" json:"name,omitempty"` +} + +type ProjectLinks struct { + Self []Link `yaml:"self,omitempty" json:"self,omitempty"` +} + +type PullRequest struct { + Id int64 `yaml:"id" json:"id"` + Version *int32 `yaml:"version,omitempty" json:"version,omitempty"` + Title string `yaml:"title" json:"title"` + Description string `yaml:"description" json:"description"` + State PullRequestState `yaml:"state" json:"state"` + Open bool `yaml:"open" json:"open"` + Closed bool `yaml:"closed" json:"closed"` + CreatedDate *int64 `yaml:"createdDate,omitempty" json:"createdDate,omitempty"` + UpdatedDate *int64 `yaml:"updatedDate,omitempty" json:"updatedDate,omitempty"` + FromRef RepositoryRef `yaml:"fromRef" json:"fromRef"` + ToRef RepositoryRef `yaml:"toRef" json:"toRef"` + Locked bool `yaml:"locked" json:"locked"` + Author *UserRole `yaml:"author,omitempty" json:"author,omitempty"` + Reviewers []UserRole `yaml:"reviewers,omitempty" json:"reviewers,omitempty"` + Participants []UserRole `yaml:"participants,omitempty" json:"participants,omitempty"` + Links *ProjectLinks `yaml:"links,omitempty" json:"links,omitempty"` +} + +type PullRequestComment struct { + Properties PullRequestCommentProperties `yaml:"properties" json:"properties"` + Id int32 `yaml:"id" json:"id"` + Version int32 `yaml:"version" json:"version"` + Text string `yaml:"text" json:"text"` + Author PullRequestCommentAuthor `yaml:"author" json:"author"` + CreatedDate int64 `yaml:"createdDate" json:"createdDate"` + UpdatedDate int64 `yaml:"updatedDate" json:"updatedDate"` + Comments []PullRequestComment `yaml:"comments" json:"comments"` + Tasks []interface{} `yaml:"tasks" json:"tasks"` + Severity string `yaml:"severity" json:"severity"` + State string `yaml:"state" json:"state"` + PermittedOperations PullRequestCommentPermittedOperations `yaml:"permittedOperations" json:"permittedOperations"` +} + +type PullRequestCommentAuthor struct { + Name string `yaml:"name" json:"name"` + EmailAddress string `yaml:"emailAddress" json:"emailAddress"` + Id int32 `yaml:"id" json:"id"` + DisplayName string `yaml:"displayName" json:"displayName"` + Active bool `yaml:"active" json:"active"` + Slug string `yaml:"slug" json:"slug"` + Type string `yaml:"type" json:"type"` +} + +type PullRequestCommentPage struct { + Size int32 `yaml:"size" json:"size"` + Limit int32 `yaml:"limit" json:"limit"` + Start int32 `yaml:"start" json:"start"` + IsLastPage bool `yaml:"isLastPage" json:"isLastPage"` + NextPageStart *int32 `yaml:"nextPageStart,omitempty" json:"nextPageStart,omitempty"` + Values []PullRequestComment `yaml:"values" json:"values"` +} + +type PullRequestCommentPermittedOperations struct { + Editable bool `yaml:"editable" json:"editable"` + Deletable bool `yaml:"deletable" json:"deletable"` +} + +type PullRequestCommentProperties struct { + Key string `yaml:"key" json:"key"` +} + +type PullRequestCommentRequest struct { + Text string `yaml:"text" json:"text"` + Parent *PullRequestCommentRequestParent `yaml:"parent,omitempty" json:"parent,omitempty"` + Anchor *PullRequestCommentRequestAnchor `yaml:"anchor,omitempty" json:"anchor,omitempty"` + Severity *string `yaml:"severity,omitempty" json:"severity,omitempty"` + State *string `yaml:"state,omitempty" json:"state,omitempty"` +} + +type PullRequestCommentRequestAnchor struct { + Line *int32 `yaml:"line,omitempty" json:"line,omitempty"` + LineType *string `yaml:"lineType,omitempty" json:"lineType,omitempty"` + FileType *string `yaml:"fileType,omitempty" json:"fileType,omitempty"` + Path *string `yaml:"path,omitempty" json:"path,omitempty"` + SrcPath *string `yaml:"srcPath,omitempty" json:"srcPath,omitempty"` +} + +type PullRequestCommentRequestParent struct { + Id int32 `yaml:"id" json:"id"` + Severity *string `yaml:"severity,omitempty" json:"severity,omitempty"` + State *string `yaml:"state,omitempty" json:"state,omitempty"` +} + +type PullRequestState string + +// List of pullRequestState +const ( + OPEN PullRequestState = "OPEN" + MERGED PullRequestState = "MERGED" + DECLINED PullRequestState = "DECLINED" +) + +// All allowed values of PullRequestState enum +var AllowedPullRequestStateEnumValues = []PullRequestState{ + "OPEN", + "MERGED", + "DECLINED", +} + +type RepositoryRef struct { + Id string `yaml:"id" json:"id"` + LatestCommit string `yaml:"latestCommit" json:"latestCommit"` + Repository RepositoryRefRepository `yaml:"repository" json:"repository"` +} + +type RepositoryRefRepository struct { + Slug string `yaml:"slug" json:"slug"` + Name *string `yaml:"name,omitempty" json:"name,omitempty"` + Project RepositoryRefRepositoryProject `yaml:"project" json:"project"` +} + +type RepositoryRefRepositoryProject struct { + Key string `yaml:"key" json:"key"` +} + +type TestResults struct { + Failed int32 `yaml:"failed" json:"failed"` + Skipped int32 `yaml:"skipped" json:"skipped"` + Successful int32 `yaml:"successful" json:"successful"` +} + +type User struct { + Id *int32 `yaml:"id,omitempty" json:"id,omitempty"` + Name string `yaml:"name" json:"name"` + EmailAddress *string `yaml:"emailAddress,omitempty" json:"emailAddress,omitempty"` + DisplayName *string `yaml:"displayName,omitempty" json:"displayName,omitempty"` + Active bool `yaml:"active" json:"active"` + Slug string `yaml:"slug" json:"slug"` + Type *string `yaml:"type,omitempty" json:"type,omitempty"` +} + +type UserRole struct { + User User `yaml:"user" json:"user"` + Role *string `yaml:"role,omitempty" json:"role,omitempty"` + Approved *bool `yaml:"approved,omitempty" json:"approved,omitempty"` + Status *string `yaml:"status,omitempty" json:"status,omitempty"` +} diff --git a/internal/repository/bitbucket/repository.go b/internal/repository/bitbucket/repository.go index 2b15aa8..39f6b0e 100644 --- a/internal/repository/bitbucket/repository.go +++ b/internal/repository/bitbucket/repository.go @@ -3,10 +3,12 @@ package bitbucket import ( "context" "fmt" + "github.com/Interhyp/metadata-service/internal/acorn/config" "github.com/Interhyp/metadata-service/internal/acorn/errors/httperror" "github.com/Interhyp/metadata-service/internal/acorn/repository" "github.com/Interhyp/metadata-service/internal/repository/bitbucket/bbclient" "github.com/Interhyp/metadata-service/internal/repository/bitbucket/bbclientint" + aulogging "github.com/StephanHCB/go-autumn-logging" auzerolog "github.com/StephanHCB/go-autumn-logging-zerolog" librepo "github.com/StephanHCB/go-backend-service-common/acorns/repository" "net/http" @@ -14,23 +16,26 @@ import ( ) type Impl struct { - Configuration librepo.Configuration - Logging librepo.Logging - Vault librepo.Vault + Configuration librepo.Configuration + CustomConfiguration config.CustomConfiguration + Logging librepo.Logging + Vault librepo.Vault LowLevel bbclientint.BitbucketClient } func New( configuration librepo.Configuration, + customConfiguration config.CustomConfiguration, logging librepo.Logging, vault librepo.Vault, ) repository.Bitbucket { return &Impl{ - Configuration: configuration, - Logging: logging, - Vault: vault, - LowLevel: bbclient.New(configuration, logging, vault), + Configuration: configuration, + CustomConfiguration: customConfiguration, + Logging: logging, + Vault: vault, + LowLevel: bbclient.New(configuration, logging, vault), } } @@ -108,3 +113,75 @@ func Unique[T comparable](sliceList []T) []T { } return list } + +func (r *Impl) GetChangedFilesOnPullRequest(ctx context.Context, pullRequestId int) ([]repository.File, string, error) { + aulogging.Logger.Ctx(ctx).Info().Printf("obtaining changes for pull request %d", pullRequestId) + + project := r.CustomConfiguration.MetadataRepoProject() + slug := r.CustomConfiguration.MetadataRepoName() + pullRequest, err := r.LowLevel.GetPullRequest(ctx, project, slug, int32(pullRequestId)) + if err != nil { + return nil, "", err + } + + prSourceHead := pullRequest.FromRef.LatestCommit + changes, err := r.LowLevel.GetChanges(ctx, project, slug, pullRequest.ToRef.LatestCommit, prSourceHead) + if err != nil { + return nil, prSourceHead, err + } + + aulogging.Logger.Ctx(ctx).Info().Printf("pull request had %d changed files", len(changes.Values)) + + result := make([]repository.File, 0) + for _, change := range changes.Values { + contents, err := r.LowLevel.GetFileContentsAt(ctx, project, slug, prSourceHead, change.Path.ToString) + if err != nil { + asHttpError, ok := err.(httperror.Error) + if ok && asHttpError.Status() == http.StatusNotFound { + aulogging.Logger.Ctx(ctx).Debug().Printf("path %s not present on PR head - skipping and continuing", change.Path.ToString) + continue // expected situation - happens for deleted files, or for files added on mainline after fork (which show up in changes) + } else { + aulogging.Logger.Ctx(ctx).Info().Printf("failed to retrieve change for %s: %s", change.Path.ToString, err.Error()) + return nil, prSourceHead, err + } + } + + result = append(result, repository.File{ + Path: change.Path.ToString, + Contents: contents, + }) + } + + aulogging.Logger.Ctx(ctx).Info().Printf("successfully obtained %d changes for pull request %d", len(result), pullRequestId) + return result, prSourceHead, nil +} + +func (r *Impl) AddCommitBuildStatus(ctx context.Context, commitHash string, url string, key string, success bool) error { + project := r.CustomConfiguration.MetadataRepoProject() + slug := r.CustomConfiguration.MetadataRepoName() + + state := "FAILED" + if success { + state = "SUCCESS" + } + + request := bbclientint.CommitBuildStatusRequest{ + Key: key, + State: state, + Url: url, + } + + return r.LowLevel.AddProjectRepositoryCommitBuildStatus(ctx, project, slug, commitHash, request) +} + +func (r *Impl) CreatePullRequestComment(ctx context.Context, pullRequestId int, comment string) error { + project := r.CustomConfiguration.MetadataRepoProject() + slug := r.CustomConfiguration.MetadataRepoName() + + request := bbclientint.PullRequestCommentRequest{ + Text: comment, + } + + _, err := r.LowLevel.CreatePullRequestComment(ctx, project, slug, int64(pullRequestId), request) + return err +} diff --git a/internal/repository/bitbucket/repository_test.go b/internal/repository/bitbucket/repository_test.go index eb090f2..bc208ad 100644 --- a/internal/repository/bitbucket/repository_test.go +++ b/internal/repository/bitbucket/repository_test.go @@ -2,6 +2,7 @@ package bitbucket import ( "context" + configint "github.com/Interhyp/metadata-service/internal/acorn/config" "github.com/Interhyp/metadata-service/internal/acorn/errors/httperror" "github.com/Interhyp/metadata-service/internal/acorn/repository" "github.com/Interhyp/metadata-service/internal/repository/config" @@ -29,7 +30,8 @@ func TestNewAndSetup(t *testing.T) { vault := &vaultmock.VaultImpl{} logger := &logging.LoggingImpl{} conf := tstConfig(t) - cut := New(conf, logger, vault) + customConf := configint.Custom(conf) + cut := New(conf, customConf, logger, vault) lowLevel := &bbclientmock.BitbucketClientMock{} cut.(*Impl).LowLevel = lowLevel diff --git a/internal/repository/config/accessors.go b/internal/repository/config/accessors.go index e545efb..f17a66b 100644 --- a/internal/repository/config/accessors.go +++ b/internal/repository/config/accessors.go @@ -174,3 +174,47 @@ func (c *CustomConfigImpl) RedisUrl() string { func (c *CustomConfigImpl) RedisPassword() string { return c.VRedisPassword } + +func (c *CustomConfigImpl) MetadataRepoProject() string { + sshUrl := c.SSHMetadataRepositoryUrl() + if sshUrl != "" { + match := c.BitbucketGitUrlMatcher.FindStringSubmatch(sshUrl) + if len(match) == 3 { + return match[1] + } + } + httpUrl := c.MetadataRepoUrl() + if httpUrl != "" { + match := c.BitbucketGitUrlMatcher.FindStringSubmatch(sshUrl) + if len(match) == 3 { + return match[1] + } + } + return "" +} + +func (c *CustomConfigImpl) MetadataRepoName() string { + sshUrl := c.SSHMetadataRepositoryUrl() + if sshUrl != "" { + match := c.BitbucketGitUrlMatcher.FindStringSubmatch(sshUrl) + if len(match) == 3 { + return match[2] + } + } + httpUrl := c.MetadataRepoUrl() + if httpUrl != "" { + match := c.BitbucketGitUrlMatcher.FindStringSubmatch(sshUrl) + if len(match) == 3 { + return match[2] + } + } + return "" +} + +func (c *CustomConfigImpl) PullRequestBuildUrl() string { + return c.VPullRequestBuildUrl +} + +func (c *CustomConfigImpl) PullRequestBuildKey() string { + return c.VPullRequestBuildKey +} diff --git a/internal/repository/config/config.go b/internal/repository/config/config.go index 82a263e..f227e39 100644 --- a/internal/repository/config/config.go +++ b/internal/repository/config/config.go @@ -294,4 +294,18 @@ var CustomConfigItems = []auconfigapi.ConfigItem{ Description: "password used to access the redis", Validate: auconfigapi.ConfigNeedsNoValidation, }, + { + Key: config.KeyPullRequestBuildUrl, + EnvName: config.KeyPullRequestBuildUrl, + Default: "", + Description: "Url that pull request builds should link to.", + Validate: auconfigenv.ObtainPatternValidator("^https?://.*$"), + }, + { + Key: config.KeyPullRequestBuildKey, + EnvName: config.KeyPullRequestBuildKey, + Default: "metadata-service", + Description: "Key to use for pull request builds.", + Validate: auconfigapi.ConfigNeedsNoValidation, + }, } diff --git a/internal/repository/config/plumbing.go b/internal/repository/config/plumbing.go index 33bbfd0..e6fc5ff 100644 --- a/internal/repository/config/plumbing.go +++ b/internal/repository/config/plumbing.go @@ -63,13 +63,17 @@ type CustomConfigImpl struct { VAllowedFileCategories []string VRedisUrl string VRedisPassword string + VPullRequestBuildUrl string + VPullRequestBuildKey string - VKafkaConfig *aukafka.Config + VKafkaConfig *aukafka.Config + BitbucketGitUrlMatcher *regexp.Regexp } func New() (librepo.Configuration, config.CustomConfiguration) { instance := &CustomConfigImpl{ - VKafkaConfig: aukafka.NewConfig(), + VKafkaConfig: aukafka.NewConfig(), + BitbucketGitUrlMatcher: regexp.MustCompile(`/([^/]+)/([^/]+).git$`), } configItems := make([]auconfigapi.ConfigItem, 0) configItems = append(configItems, CustomConfigItems...) @@ -123,6 +127,8 @@ func (c *CustomConfigImpl) Obtain(getter func(key string) string) { c.VAllowedFileCategories, _ = parseAllowedFileCategories(getter(config.KeyAllowedFileCategories)) c.VRedisUrl = getter(config.KeyRedisUrl) c.VRedisPassword = getter(config.KeyRedisPassword) + c.VPullRequestBuildUrl = getter(config.KeyPullRequestBuildUrl) + c.VPullRequestBuildKey = getter(config.KeyPullRequestBuildKey) c.VKafkaConfig.Obtain(getter) } diff --git a/internal/repository/config/validation_test.go b/internal/repository/config/validation_test.go index 29b2688..5ae1b0e 100644 --- a/internal/repository/config/validation_test.go +++ b/internal/repository/config/validation_test.go @@ -78,7 +78,7 @@ func TestValidate_LotsOfErrors(t *testing.T) { _, err := tstSetupCutAndLogRecorder(t, "invalid-config-values.yaml") require.NotNil(t, err) - require.Contains(t, err.Error(), "some configuration values failed to validate or parse. There were 27 error(s). See details above") + require.Contains(t, err.Error(), "some configuration values failed to validate or parse. There were 28 error(s). See details above") actualLog := goauzerolog.RecordedLogForTesting.String() diff --git a/internal/service/prvalidator/prvalidator.go b/internal/service/prvalidator/prvalidator.go new file mode 100644 index 0000000..cebdd02 --- /dev/null +++ b/internal/service/prvalidator/prvalidator.go @@ -0,0 +1,103 @@ +package prvalidator + +import ( + "context" + "fmt" + openapi "github.com/Interhyp/metadata-service/api" + "github.com/Interhyp/metadata-service/internal/acorn/config" + "github.com/Interhyp/metadata-service/internal/acorn/repository" + "github.com/Interhyp/metadata-service/internal/acorn/service" + aulogging "github.com/StephanHCB/go-autumn-logging" + librepo "github.com/StephanHCB/go-backend-service-common/acorns/repository" + "gopkg.in/yaml.v3" + "strings" +) + +type Impl struct { + Configuration librepo.Configuration + CustomConfiguration config.CustomConfiguration + Logging librepo.Logging + Timestamp librepo.Timestamp + BitBucket repository.Bitbucket +} + +func New( + configuration librepo.Configuration, + customConfig config.CustomConfiguration, + logging librepo.Logging, + timestamp librepo.Timestamp, + bitbucket repository.Bitbucket, +) service.PRValidator { + return &Impl{ + Configuration: configuration, + CustomConfiguration: customConfig, + Logging: logging, + Timestamp: timestamp, + BitBucket: bitbucket, + } +} + +func (s *Impl) IsPRValidator() bool { + return true +} + +func (s *Impl) ValidatePullRequest(ctx context.Context, id uint64, toRef string, fromRef string) error { + fileInfos, prHead, err := s.BitBucket.GetChangedFilesOnPullRequest(ctx, int(id)) + if err != nil { + return fmt.Errorf("error getting changed files on pull request: %v", err) + } + + var errorMessages []string + for _, fileInfo := range fileInfos { + err := s.validateYamlFile(ctx, fileInfo.Path, fileInfo.Contents) + if err != nil { + errorMessages = append(errorMessages, err.Error()) + } + } + + buildUrl := s.CustomConfiguration.PullRequestBuildUrl() + buildKey := s.CustomConfiguration.PullRequestBuildKey() + message := "all changed files are valid\n" + if len(errorMessages) > 0 { + message = "# yaml validation failure\n\nThere were validation errors in changed files. Please fix yaml syntax and/or remove unknown fields:\n\n" + + strings.Join(errorMessages, "\n\n") + "\n" + } + err = s.BitBucket.CreatePullRequestComment(ctx, int(id), message) + if err != nil { + return fmt.Errorf("error creating pull request comment: %v", err) + } + err = s.BitBucket.AddCommitBuildStatus(ctx, prHead, buildUrl, buildKey, len(errorMessages) == 0) + if err != nil { + return fmt.Errorf("error adding commit build status: %v", err) + } + + return nil +} + +func (s *Impl) validateYamlFile(ctx context.Context, path string, contents string) error { + if strings.HasPrefix(path, "owners/") && strings.HasSuffix(path, ".yaml") { + if strings.Contains(path, "owner.info.yaml") { + return parseStrict(ctx, path, contents, &openapi.OwnerDto{}) + } else if strings.Contains(path, "/services/") { + return parseStrict(ctx, path, contents, &openapi.ServiceDto{}) + } else if strings.Contains(path, "/repositories/") { + return parseStrict(ctx, path, contents, &openapi.RepositoryDto{}) + } else { + aulogging.Logger.Ctx(ctx).Info().Printf("ignoring changed file %s in pull request (neither owner info, nor service nor repository)", path) + return nil + } + } else { + aulogging.Logger.Ctx(ctx).Info().Printf("ignoring changed file %s in pull request (not in owners/ or not .yaml)", path) + return nil + } +} + +func parseStrict[T openapi.OwnerDto | openapi.ServiceDto | openapi.RepositoryDto](_ context.Context, path string, contents string, resultPtr *T) error { + decoder := yaml.NewDecoder(strings.NewReader(contents)) + decoder.KnownFields(true) + err := decoder.Decode(resultPtr) + if err != nil { + return fmt.Errorf(" - failed to parse `%s`:\n %s", path, strings.ReplaceAll(err.Error(), "\n", "\n ")) + } + return nil +} diff --git a/internal/web/app/app.go b/internal/web/app/app.go index 15f80b4..f490c4e 100644 --- a/internal/web/app/app.go +++ b/internal/web/app/app.go @@ -17,6 +17,7 @@ import ( "github.com/Interhyp/metadata-service/internal/repository/sshAuthProvider" "github.com/Interhyp/metadata-service/internal/service/mapper" "github.com/Interhyp/metadata-service/internal/service/owners" + "github.com/Interhyp/metadata-service/internal/service/prvalidator" "github.com/Interhyp/metadata-service/internal/service/repositories" "github.com/Interhyp/metadata-service/internal/service/services" "github.com/Interhyp/metadata-service/internal/service/trigger" @@ -59,6 +60,7 @@ type ApplicationImpl struct { Owners service.Owners Services service.Services Repositories service.Repositories + PRValidator service.PRValidator // controllers (incoming connectors) HealthCtl libcontroller.HealthController @@ -176,7 +178,7 @@ func (a *ApplicationImpl) ConstructRepositories() error { } if a.Bitbucket == nil { - a.Bitbucket = bitbucket.New(a.Config, a.Logging, a.Vault) + a.Bitbucket = bitbucket.New(a.Config, a.CustomConfig, a.Logging, a.Vault) } if err := a.Bitbucket.Setup(); err != nil { return err @@ -228,6 +230,9 @@ func (a *ApplicationImpl) ConstructServices() error { return err } + a.PRValidator = prvalidator.New(a.Config, a.CustomConfig, a.Logging, a.Timestamp, a.Bitbucket) + // no setup required + return nil } @@ -239,7 +244,7 @@ func (a *ApplicationImpl) ConstructControllers() error { a.OwnerCtl = ownerctl.New(a.Config, a.CustomConfig, a.Logging, a.Timestamp, a.Owners) a.ServiceCtl = servicectl.New(a.Config, a.CustomConfig, a.Logging, a.Timestamp, a.Services) a.RepositoryCtl = repositoryctl.New(a.Config, a.CustomConfig, a.Logging, a.Timestamp, a.Repositories) - a.WebhookCtl = webhookctl.New(a.Logging, a.Timestamp, a.Updater) + a.WebhookCtl = webhookctl.New(a.Logging, a.Timestamp, a.Updater, a.PRValidator) a.Server = server.New(a.Config, a.CustomConfig, a.Logging, a.IdentityProvider, a.HealthCtl, a.SwaggerCtl, a.OwnerCtl, a.ServiceCtl, a.RepositoryCtl, a.WebhookCtl) diff --git a/internal/web/controller/webhookctl/webhookctl.go b/internal/web/controller/webhookctl/webhookctl.go index a60cc21..d2daf16 100644 --- a/internal/web/controller/webhookctl/webhookctl.go +++ b/internal/web/controller/webhookctl/webhookctl.go @@ -2,9 +2,11 @@ package webhookctl import ( "context" + "fmt" "github.com/Interhyp/metadata-service/internal/acorn/controller" "github.com/Interhyp/metadata-service/internal/acorn/service" "github.com/StephanHCB/go-backend-service-common/web/util/contexthelper" + bitbucketserver "github.com/go-playground/webhooks/v6/bitbucket-server" "net/http" "github.com/Interhyp/metadata-service/internal/web/util" @@ -14,20 +16,23 @@ import ( ) type Impl struct { - Logging librepo.Logging - Timestamp librepo.Timestamp - Updater service.Updater + Logging librepo.Logging + Timestamp librepo.Timestamp + Updater service.Updater + PRValidator service.PRValidator } func New( logging librepo.Logging, timestamp librepo.Timestamp, updater service.Updater, + prValidator service.PRValidator, ) controller.WebhookController { return &Impl{ - Logging: logging, - Timestamp: timestamp, - Updater: updater, + Logging: logging, + Timestamp: timestamp, + Updater: updater, + PRValidator: prValidator, } } @@ -37,10 +42,12 @@ func (c *Impl) IsWebhookController() bool { func (c *Impl) WireUp(_ context.Context, router chi.Router) { router.Post("/webhook", c.Webhook) + router.Post("/webhook/bitbucket", c.WebhookBitBucket) } // --- handlers --- +// Webhook is deprecated and will be removed after the switch func (c *Impl) Webhook(w http.ResponseWriter, r *http.Request) { ctx := r.Context() @@ -55,3 +62,73 @@ func (c *Impl) Webhook(w http.ResponseWriter, r *http.Request) { util.SuccessNoBody(ctx, w, r, http.StatusNoContent) } + +func (c *Impl) WebhookBitBucket(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + + aulogging.Logger.Ctx(ctx).Info().Printf("received webhook from BitBucket") + webhook, err := bitbucketserver.New() // we don't need signature checking here + if err != nil { + aulogging.Logger.Ctx(ctx).Info().WithErr(err).Printf("unexpected error while instantiating bitbucket webhook parser - ignoring webhook") + util.UnexpectedErrorHandler(ctx, w, r, err, c.Timestamp.Now()) + return + } + + eventPayload, err := webhook.Parse(r, bitbucketserver.DiagnosticsPingEvent, bitbucketserver.PullRequestOpenedEvent, + bitbucketserver.RepositoryReferenceChangedEvent, bitbucketserver.PullRequestModifiedEvent, bitbucketserver.PullRequestFromReferenceUpdatedEvent) + if err != nil { + aulogging.Logger.Ctx(ctx).Info().WithErr(err).Printf("bad request error while parsing bitbucket webhook payload - ignoring webhook") + util.ErrorHandler(ctx, w, r, "webhook.payload.invalid", http.StatusBadRequest, "parse payload error", c.Timestamp.Now()) + return + } + + routineCtx, routineCtxCancel := contexthelper.AsyncCopyRequestContext(ctx, "webhookBitbucket", "backgroundJob") + go func() { + defer routineCtxCancel() + + switch eventPayload.(type) { + case bitbucketserver.PullRequestOpenedPayload: + payload, ok := eventPayload.(bitbucketserver.PullRequestOpenedPayload) + c.validatePullRequest(routineCtx, "opened", ok, payload.PullRequest) + case bitbucketserver.PullRequestModifiedPayload: + payload, ok := eventPayload.(bitbucketserver.PullRequestModifiedPayload) + c.validatePullRequest(routineCtx, "modified", ok, payload.PullRequest) + case bitbucketserver.PullRequestFromReferenceUpdatedPayload: + payload, ok := eventPayload.(bitbucketserver.PullRequestFromReferenceUpdatedPayload) + c.validatePullRequest(routineCtx, "from_reference", ok, payload.PullRequest) + case bitbucketserver.RepositoryReferenceChangedPayload: + payload, ok := eventPayload.(bitbucketserver.RepositoryReferenceChangedPayload) + if !ok || len(payload.Changes) < 1 || payload.Changes[0].ReferenceID == "" { + aulogging.Logger.Ctx(routineCtx).Error().Printf("bad request while processing bitbucket webhook - got reference changed with invalid info - ignoring webhook") + return + } + aulogging.Logger.Ctx(routineCtx).Info().Printf("got repository reference changed, refreshing caches") + + err = c.Updater.PerformFullUpdateWithNotifications(routineCtx) + if err != nil { + aulogging.Logger.Ctx(routineCtx).Error().WithErr(err).Printf("webhook error") + } + default: + // ignore unknown events + } + }() + + util.SuccessNoBody(ctx, w, r, http.StatusNoContent) +} + +func (c *Impl) validatePullRequest(ctx context.Context, operation string, parsedOk bool, pullRequestPayload bitbucketserver.PullRequest) { + description := fmt.Sprintf("id: %d, toRef: %s, fromRef: %s", pullRequestPayload.ID, pullRequestPayload.ToRef.ID, pullRequestPayload.FromRef.ID) + if !parsedOk || pullRequestPayload.ID == 0 || pullRequestPayload.ToRef.ID == "" || pullRequestPayload.FromRef.ID == "" { + aulogging.Logger.Ctx(ctx).Error().Printf("bad request while processing bitbucket webhook - got pull request %s with invalid info (%s) - ignoring webhook", operation, description) + return + } + aulogging.Logger.Ctx(ctx).Info().Printf("got pull request %s (%s)", operation, description) + + err := c.PRValidator.ValidatePullRequest(ctx, pullRequestPayload.ID, pullRequestPayload.ToRef.ID, pullRequestPayload.FromRef.ID) + if err != nil { + aulogging.Logger.Ctx(ctx).Error().WithErr(err).Printf("error while processing bitbucket webhook: pull request %s (%s): %s", operation, description, err.Error()) + return + } + + aulogging.Logger.Ctx(ctx).Info().Printf("successfully processed pull request %s (%s) event", operation, description) +} diff --git a/internal/web/server/server.go b/internal/web/server/server.go index 2e7cce6..ebc3f04 100644 --- a/internal/web/server/server.go +++ b/internal/web/server/server.go @@ -117,6 +117,7 @@ func (s *Impl) WireUp(ctx context.Context) { "GET /rest/api/v1/services.*", "GET /rest/api/v1/repositories.*", "POST /webhook", + "POST /webhook/bitbucket", // health (provides just up) "GET /", "GET /health", diff --git a/local-config.template.yaml b/local-config.template.yaml index a6e7460..59b5376 100644 --- a/local-config.template.yaml +++ b/local-config.template.yaml @@ -13,6 +13,9 @@ BITBUCKET_SERVER: https://bitbucket.subdomain.com BITBUCKET_CACHE_SIZE: 1000 BITBUCKET_CACHE_RETENTION_SECONDS: 3600 +# Url to this service, used as link in Pull Request validation builds +PULL_REQUEST_BUILD_URL: https://metadata-service.example.com + GIT_COMMITTER_NAME: GIT_COMMITTER_EMAIL: diff --git a/test/mock/bbclientmock/bbclientmock.go b/test/mock/bbclientmock/bbclientmock.go index 6640c8d..381db61 100644 --- a/test/mock/bbclientmock/bbclientmock.go +++ b/test/mock/bbclientmock/bbclientmock.go @@ -5,6 +5,7 @@ import ( "errors" "github.com/Interhyp/metadata-service/internal/acorn/errors/httperror" "github.com/Interhyp/metadata-service/internal/acorn/repository" + "github.com/Interhyp/metadata-service/internal/repository/bitbucket/bbclientint" "strings" ) @@ -38,3 +39,26 @@ func (m *BitbucketClientMock) GetBitbucketUser(ctx context.Context, username str func (m *BitbucketClientMock) Setup() error { return nil } + +func (c *BitbucketClientMock) GetPullRequest(ctx context.Context, projectKey string, repositorySlug string, pullRequestId int32) (bbclientint.PullRequest, error) { + response := bbclientint.PullRequest{} + return response, nil +} + +func (c *BitbucketClientMock) GetChanges(ctx context.Context, projectKey string, repositorySlug string, sinceHash string, untilHash string) (bbclientint.Changes, error) { + response := bbclientint.Changes{} + return response, nil +} + +func (c *BitbucketClientMock) GetFileContentsAt(ctx context.Context, projectKey string, repositorySlug string, atHash string, path string) (string, error) { + return "", nil +} + +func (c *BitbucketClientMock) AddProjectRepositoryCommitBuildStatus(ctx context.Context, projectKey string, repositorySlug string, commitId string, commitBuildStatusRequest bbclientint.CommitBuildStatusRequest) error { + return nil +} + +func (c *BitbucketClientMock) CreatePullRequestComment(ctx context.Context, projectKey string, repositorySlug string, pullRequestId int64, pullRequestCommentRequest bbclientint.PullRequestCommentRequest) (bbclientint.PullRequestComment, error) { + response := bbclientint.PullRequestComment{} + return response, nil +} diff --git a/test/mock/bitbucketmock/bitbucketmock.go b/test/mock/bitbucketmock/bitbucketmock.go index 59304e0..98e1d89 100644 --- a/test/mock/bitbucketmock/bitbucketmock.go +++ b/test/mock/bitbucketmock/bitbucketmock.go @@ -49,3 +49,15 @@ func (b *BitbucketMock) FilterExistingUsernames(ctx context.Context, usernames [ } return usernames, nil } + +func (b *BitbucketMock) GetChangedFilesOnPullRequest(ctx context.Context, pullRequestId int) ([]repository.File, string, error) { + return []repository.File{}, "", nil +} + +func (r *BitbucketMock) AddCommitBuildStatus(ctx context.Context, commitHash string, url string, key string, success bool) error { + return nil +} + +func (r *BitbucketMock) CreatePullRequestComment(ctx context.Context, pullRequestId int, comment string) error { + return nil +} diff --git a/test/mock/configmock/configmock.go b/test/mock/configmock/configmock.go index 5e8f5f0..59952f3 100644 --- a/test/mock/configmock/configmock.go +++ b/test/mock/configmock/configmock.go @@ -256,3 +256,19 @@ func (c *MockConfig) RedisUrl() string { func (c *MockConfig) RedisPassword() string { return "" } + +func (c *MockConfig) MetadataRepoProject() string { + return "sample" +} + +func (c *MockConfig) MetadataRepoName() string { + return "sample-repo" +} + +func (c *MockConfig) PullRequestBuildUrl() string { + return "https://example.com" +} + +func (c *MockConfig) PullRequestBuildKey() string { + return "metadata-service" +} diff --git a/test/resources/valid-config-unique.yaml b/test/resources/valid-config-unique.yaml index 73cf071..a464416 100644 --- a/test/resources/valid-config-unique.yaml +++ b/test/resources/valid-config-unique.yaml @@ -9,6 +9,7 @@ BASIC_AUTH_PASSWORD: some-basic-auth-password BITBUCKET_USERNAME: some-bitbucket-username BITBUCKET_PASSWORD: some-bitbucket-password BITBUCKET_REVIEWER_FALLBACK: username +PULL_REQUEST_BUILD_URL: https://metadata-service.example.com GIT_COMMITTER_NAME: 'Body, Some' GIT_COMMITTER_EMAIL: 'somebody@somewhere.com' diff --git a/test/resources/valid-config.yaml b/test/resources/valid-config.yaml index 76521a9..6e77ce6 100644 --- a/test/resources/valid-config.yaml +++ b/test/resources/valid-config.yaml @@ -4,6 +4,7 @@ LOGSTYLE: plain BITBUCKET_USERNAME: localuser BITBUCKET_REVIEWER_FALLBACK: username +PULL_REQUEST_BUILD_URL: https://metadata-service.example.com AUTH_OIDC_TOKEN_AUDIENCE: some-audience AUTH_GROUP_WRITE: admin