Skip to content

Commit

Permalink
Let user define pipeline provenance definition
Browse files Browse the repository at this point in the history
By default on a `Push` or a `Pull Request`, Pipelines as Code will fetch the
PipelineRun definition from the branch of where the event has been triggered.

This behavior can be changed by setting the setting `pipelinerun_provenance`.
The setting currently accept two values:

- `source`: The default behavior, the PipelineRun definition will be fetched
  from the branch of where the event has been triggered.
- `default_branch`: The PipelineRun definition will be fetched from the default
  branch of the repository as configured on the git platform. For example
  `main`, `master`, or `trunk`.

Example:

This configuration specifies a repository named my-repo with a URL of
<https://github.com/my-org/my-repo>. It also sets the `pipelinerun_provenance`
setting to `default_branch`, which means that the PipelineRun definition will be
fetched from the default branch of the repository.

```yaml
apiVersion: "pipelinesascode.tekton.dev/v1alpha1"
kind: Repository
metadata:
  name: my-repo
spec:
  url: "https://github.com/owner/repo"
  settings:
    pipelinerun_provenance: "default_branch"
```

SRVKP: https://issues.redhat.com/browse/SRVKP-2896

Letting the user specify the provenance of the PipelineRun definition to default
branch is another layer of security. It ensures that only the one who has the
right to merge commit to the default branch can change the PipelineRun and have
access to the infrastrucutre.

Signed-off-by: Chmouel Boudjnah <chmouel@redhat.com>
  • Loading branch information
chmouel committed Apr 28, 2023
1 parent fd8409c commit cd9c07c
Show file tree
Hide file tree
Showing 24 changed files with 308 additions and 76 deletions.
6 changes: 6 additions & 0 deletions config/300-repositories.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,12 @@ spec:
items:
description: list of repositories where Github token can be scoped
type: string
pipelinerun_provenance:
description: From where the PipelineRun definitions will be coming from
type: string
enum:
- source
- default_branch
concurrency_limit:
description: Number of maximum pipelinerun running at any moment
type: integer
Expand Down
39 changes: 39 additions & 0 deletions docs/content/docs/guide/repositorycrd.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,45 @@ same URL. However, only the oldest created Repository CRD will be matched,
unless you use the `target-namespace` annotation.
{{< /hint >}}

### PipelineRun definition provenance

By default on a `Push` or a `Pull Request`, Pipelines as Code will fetch the
PipelineRun definition from the branch of where the event has been triggered.

This behavior can be changed by setting the setting `pipelinerun_provenance`.
The setting currently accept two values:

- `source`: The default behavior, the PipelineRun definition will be fetched
from the branch of where the event has been triggered.
- `default_branch`: The PipelineRun definition will be fetched from the default
branch of the repository as configured on the git platform. For example
`main`, `master`, or `trunk`.

Example:

This configuration specifies a repository named my-repo with a URL of
<https://github.com/my-org/my-repo>. It also sets the `pipelinerun_provenance`
setting to `default_branch`, which means that the PipelineRun definition will be
fetched from the default branch of the repository.

```yaml
apiVersion: "pipelinesascode.tekton.dev/v1alpha1"
kind: Repository
metadata:
name: my-repo
spec:
url: "https://github.com/owner/repo"
settings:
pipelinerun_provenance: "default_branch"
```

{{< hint info >}}
Letting the user specify the provenance of the PipelineRun definition to default
branch is another layer of security. It ensures that only the one who has the
right to merge commit to the default branch can change the PipelineRun and have
access to the infrastrucutre.
{{< /hint >}}

## Concurrency

`concurrency_limit` allows you to define the maximum number of PipelineRuns running at any time for a Repository.
Expand Down
2 changes: 1 addition & 1 deletion hack/dev/kind/install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -250,4 +250,4 @@ while getopts "RGgpcrb" o; do
done
shift $((OPTIND-1))

main
main
1 change: 1 addition & 0 deletions pkg/apis/pipelinesascode/v1alpha1/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ type RepositorySpec struct {

type Settings struct {
GithubAppTokenScopeRepos []string `json:"github_app_token_scope_repos,omitempty"`
PipelineRunProvenance string `json:"pipelinerun_provenance,omitempty"`
}

type Params struct {
Expand Down
6 changes: 5 additions & 1 deletion pkg/pipelineascode/match.go
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,11 @@ is that what you want? make sure you use -n when generating the secret, eg: echo

// getPipelineRunsFromRepo fetches pipelineruns from git repository and prepare them for creation
func (p *PacRun) getPipelineRunsFromRepo(ctx context.Context, repo *v1alpha1.Repository) ([]matcher.Match, error) {
rawTemplates, err := p.vcx.GetTektonDir(ctx, p.event, tektonDir)
provenance := "source"
if repo.Spec.Settings != nil && repo.Spec.Settings.PipelineRunProvenance != "" {
provenance = repo.Spec.Settings.PipelineRunProvenance
}
rawTemplates, err := p.vcx.GetTektonDir(ctx, p.event, tektonDir, provenance)
if err != nil || rawTemplates == "" {
msg := fmt.Sprintf("cannot locate templates in %s/ directory for this repository in %s", tektonDir, p.event.HeadBranch)
if err != nil {
Expand Down
2 changes: 1 addition & 1 deletion pkg/provider/bitbucketcloud/acl_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ func TestIsAllowed(t *testing.T) {

bbcloudtest.MuxOrgMember(t, mux, tt.event, tt.fields.workspaceMembers)
bbcloudtest.MuxComments(t, mux, tt.event, tt.fields.comments)
bbcloudtest.MuxFiles(t, mux, tt.event, tt.fields.filescontents)
bbcloudtest.MuxFiles(t, mux, tt.event, tt.fields.filescontents, "")

v := &Provider{Client: bbclient}

Expand Down
31 changes: 23 additions & 8 deletions pkg/provider/bitbucketcloud/bitbucket.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ type Provider struct {
Logger *zap.SugaredLogger
Token, APIURL *string
Username *string
provenance string
}

// GetTaskURI TODO: Implement ME
Expand Down Expand Up @@ -115,7 +116,8 @@ func (v *Provider) CreateStatus(_ context.Context, _ versioned.Interface, event
return nil
}

func (v *Provider) GetTektonDir(_ context.Context, event *info.Event, path string) (string, error) {
func (v *Provider) GetTektonDir(_ context.Context, event *info.Event, path, provenance string) (string, error) {
v.provenance = provenance
repositoryFiles, err := v.getDir(event, path)
if err != nil {
return "", err
Expand All @@ -125,10 +127,15 @@ func (v *Provider) GetTektonDir(_ context.Context, event *info.Event, path strin
}

func (v *Provider) getDir(event *info.Event, path string) ([]bitbucket.RepositoryFile, error) {
// default set provenance from the SHA
revision := event.SHA
if v.provenance == "default_branch" {
revision = event.DefaultBranch
}
repoFileOpts := &bitbucket.RepositoryFilesOptions{
Owner: event.Organization,
RepoSlug: event.Repository,
Ref: event.SHA,
Ref: revision,
Path: path,
}

Expand All @@ -139,8 +146,12 @@ func (v *Provider) getDir(event *info.Event, path string) ([]bitbucket.Repositor
return repositoryFiles, nil
}

func (v *Provider) GetFileInsideRepo(_ context.Context, runevent *info.Event, path, _ string) (string, error) {
return v.getBlob(runevent, runevent.SHA, path)
func (v *Provider) GetFileInsideRepo(_ context.Context, event *info.Event, path, _ string) (string, error) {
revision := event.SHA
if v.provenance == "default_branch" {
revision = event.DefaultBranch
}
return v.getBlob(event, revision, path)
}

func (v *Provider) SetClient(_ context.Context, _ *params.Run, event *info.Event) error {
Expand Down Expand Up @@ -203,16 +214,20 @@ func (v *Provider) GetCommitInfo(_ context.Context, event *info.Event) error {
return nil
}

func (v *Provider) concatAllYamlFiles(objects []bitbucket.RepositoryFile, runevent *info.Event) (string, error) {
func (v *Provider) concatAllYamlFiles(objects []bitbucket.RepositoryFile, event *info.Event) (string, error) {
var allTemplates string

revision := event.SHA
if v.provenance == "default_branch" {
revision = event.DefaultBranch
}
for _, value := range objects {
if value.Type == "commit_directory" {
objects, err := v.getDir(runevent, value.Path)
objects, err := v.getDir(event, value.Path)
if err != nil {
return "", err
}
subdirdata, err := v.concatAllYamlFiles(objects, runevent)
subdirdata, err := v.concatAllYamlFiles(objects, event)
if err != nil {
return "", err
}
Expand All @@ -222,7 +237,7 @@ func (v *Provider) concatAllYamlFiles(objects []bitbucket.RepositoryFile, runeve
allTemplates += fmt.Sprintf("\n%s\n", subdirdata)
} else if strings.HasSuffix(value.Path, ".yaml") ||
strings.HasSuffix(value.Path, ".yml") {
data, err := v.getBlob(runevent, runevent.SHA, value.Path)
data, err := v.getBlob(event, revision, value.Path)
if err != nil {
return "", err
}
Expand Down
12 changes: 10 additions & 2 deletions pkg/provider/bitbucketcloud/bitbucket_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,21 @@ func TestGetTektonDir(t *testing.T) {
contentContains string
wantErr bool
removeSuffix bool
provenance string
}{
{
name: "Get Tekton Directory",
event: bbcloudtest.MakeEvent(nil),
testDirPath: "../../pipelineascode/testdata/pull_request/.tekton",
contentContains: "kind: PipelineRun",
},
{
name: "Get Tekton Directory Mainbranch",
event: bbcloudtest.MakeEvent(&info.Event{DefaultBranch: "main"}),
testDirPath: "../../pipelineascode/testdata/pull_request/.tekton",
contentContains: "kind: PipelineRun",
provenance: "default_branch",
},
{
name: "Get Tekton Directory and subdirectory",
event: bbcloudtest.MakeEvent(nil),
Expand All @@ -54,8 +62,8 @@ func TestGetTektonDir(t *testing.T) {
bbclient, mux, tearDown := bbcloudtest.SetupBBCloudClient(t)
defer tearDown()
v := &Provider{Client: bbclient}
bbcloudtest.MuxDirContent(t, mux, tt.event, tt.testDirPath)
content, err := v.GetTektonDir(ctx, tt.event, ".tekton")
bbcloudtest.MuxDirContent(t, mux, tt.event, tt.testDirPath, tt.provenance)
content, err := v.GetTektonDir(ctx, tt.event, ".tekton", tt.provenance)
if tt.wantErr {
assert.Assert(t, err != nil, "GetTektonDir() error = %v, wantErr %v", err, tt.wantErr)
return
Expand Down
24 changes: 16 additions & 8 deletions pkg/provider/bitbucketcloud/test/bbcloudtest.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,11 +84,15 @@ func MuxOrgMember(t *testing.T, mux *http.ServeMux, event *info.Event, members [
})
}

func MuxFiles(t *testing.T, mux *http.ServeMux, event *info.Event, filescontents map[string]string) {
func MuxFiles(t *testing.T, mux *http.ServeMux, event *info.Event, filescontents map[string]string, provenance string) {
t.Helper()

sha := event.SHA
if provenance == "default_branch" {
sha = event.DefaultBranch
}
for key := range filescontents {
target := fmt.Sprintf("/repositories/%s/%s/src/%s", event.Organization, event.Repository, event.SHA)
target := fmt.Sprintf("/repositories/%s/%s/src/%s", event.Organization, event.Repository, sha)
mux.HandleFunc(target+"/"+key, func(rw http.ResponseWriter, r *http.Request) {
s := strings.ReplaceAll(r.URL.String(), target, "")
s = strings.TrimPrefix(s, "/")
Expand All @@ -97,11 +101,15 @@ func MuxFiles(t *testing.T, mux *http.ServeMux, event *info.Event, filescontents
}
}

func MuxListDirFiles(t *testing.T, mux *http.ServeMux, event *info.Event, dirs map[string][]bitbucket.RepositoryFile) {
func MuxListDirFiles(t *testing.T, mux *http.ServeMux, event *info.Event, dirs map[string][]bitbucket.RepositoryFile, provenance string) {
t.Helper()
sha := event.SHA
if provenance == "default_branch" {
sha = event.DefaultBranch
}

for key, value := range dirs {
urlp := "/repositories/" + event.Organization + "/" + event.Repository + "/src/" + event.SHA + "/" + key + "/"
urlp := "/repositories/" + event.Organization + "/" + event.Repository + "/src/" + sha + "/" + key + "/"
mux.HandleFunc(urlp, func(rw http.ResponseWriter, r *http.Request) {
dircontents := map[string][]bitbucket.RepositoryFile{
"values": value,
Expand Down Expand Up @@ -181,7 +189,7 @@ func MuxCreateComment(t *testing.T, mux *http.ServeMux, event *info.Event, expec
})
}

func MuxDirContent(t *testing.T, mux *http.ServeMux, event *info.Event, testdir string) {
func MuxDirContent(t *testing.T, mux *http.ServeMux, event *info.Event, testdir, provenance string) {
t.Helper()
files, err := os.ReadDir(testdir)
assert.NilError(t, err)
Expand All @@ -201,7 +209,7 @@ func MuxDirContent(t *testing.T, mux *http.ServeMux, event *info.Event, testdir
Type: btype,
})

MuxDirContent(t, mux, event, fpath)
MuxDirContent(t, mux, event, fpath, provenance)
} else {
btype := "file"
brfiles[relativenamedir] = append(brfiles[relativenamedir], bitbucket.RepositoryFile{
Expand All @@ -213,8 +221,8 @@ func MuxDirContent(t *testing.T, mux *http.ServeMux, event *info.Event, testdir
filecontents[relativename] = string(content)
}
}
MuxListDirFiles(t, mux, event, brfiles)
MuxFiles(t, mux, event, filecontents)
MuxListDirFiles(t, mux, event, brfiles, provenance)
MuxFiles(t, mux, event, filecontents, provenance)
}

func MakePREvent(accountid, nickname, sha, comment string) types.PullRequestEvent {
Expand Down
11 changes: 9 additions & 2 deletions pkg/provider/bitbucketserver/bitbucketserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ type Provider struct {
defaultBranchLatestCommit string
pullRequestNumber int
apiURL string
provenance string
projectKey string
}

Expand Down Expand Up @@ -153,9 +154,15 @@ func (v *Provider) getRaw(runevent *info.Event, revision, path string) (string,
return string(resp.Payload), nil
}

func (v *Provider) GetTektonDir(_ context.Context, event *info.Event, path string) (string, error) {
func (v *Provider) GetTektonDir(_ context.Context, event *info.Event, path, provenance string) (string, error) {
v.provenance = provenance
allValues, err := paginate(func(nextPage int) (*bbv1.APIResponse, error) {
localVarOptionals := map[string]interface{}{"at": event.SHA}
// according to the docs, if no at parameters is specified it will default to the default branch
// cf: https://docs.atlassian.com/bitbucket-server/rest/4.1.0/bitbucket-rest.html#idp2425664
localVarOptionals := map[string]interface{}{}
if v.provenance == "source" {
localVarOptionals = map[string]interface{}{"at": event.SHA}
}
if nextPage != 0 {
localVarOptionals["start"] = nextPage
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/provider/bitbucketserver/bitbucketserver_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ func TestGetTektonDir(t *testing.T) {
defer tearDown()
v := &Provider{Client: client, projectKey: tt.event.Organization}
bbtest.MuxDirContent(t, mux, tt.event, tt.testDirPath, tt.path)
content, err := v.GetTektonDir(ctx, tt.event, tt.path)
content, err := v.GetTektonDir(ctx, tt.event, tt.path, "") // TODO: handle provenance
if tt.wantErr {
assert.Assert(t, err != nil,
"GetTektonDir() error = %v, wantErr %v", err, tt.wantErr)
Expand Down
10 changes: 8 additions & 2 deletions pkg/provider/gitea/gitea.go
Original file line number Diff line number Diff line change
Expand Up @@ -175,9 +175,15 @@ func getCheckName(status provider.StatusOpts, pacopts *info.PacOpts) string {
return status.OriginalPipelineRunName
}

func (v *Provider) GetTektonDir(_ context.Context, event *info.Event, path string) (string, error) {
func (v *Provider) GetTektonDir(_ context.Context, event *info.Event, path, provenance string) (string, error) {
// default set provenance from the SHA
revision := event.SHA
if provenance == "default_branch" {
revision = event.DefaultBranch
}

tektonDirSha := ""
rootobjects, _, err := v.Client.GetTrees(event.Organization, event.Repository, event.SHA, false)
rootobjects, _, err := v.Client.GetTrees(event.Organization, event.Repository, revision, false)
if err != nil {
return "", err
}
Expand Down
15 changes: 13 additions & 2 deletions pkg/provider/github/github.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ type Provider struct {
Token, APIURL *string
ApplicationID *int64
providerName string
provenance string
Run *params.Run
RepositoryIDs []int64

Expand Down Expand Up @@ -253,10 +254,18 @@ func (v *Provider) SetClient(ctx context.Context, run *params.Run, event *info.E
}

// GetTektonDir Get all yaml files in tekton directory return as a single concated file
func (v *Provider) GetTektonDir(ctx context.Context, runevent *info.Event, path string) (string, error) {
func (v *Provider) GetTektonDir(ctx context.Context, runevent *info.Event, path, provenance string) (string, error) {
tektonDirSha := ""

rootobjects, _, err := v.Client.Git.GetTree(ctx, runevent.Organization, runevent.Repository, runevent.SHA, false)
v.provenance = provenance
// default set provenance from the SHA
revision := runevent.SHA
if provenance == "default_branch" {
revision = runevent.DefaultBranch
v.Logger.Infof("Using PipelineRun definition from default_branch: %s", runevent.DefaultBranch)
}

rootobjects, _, err := v.Client.Git.GetTree(ctx, runevent.Organization, runevent.Repository, revision, false)
if err != nil {
return "", err
}
Expand Down Expand Up @@ -325,6 +334,8 @@ func (v *Provider) GetFileInsideRepo(ctx context.Context, runevent *info.Event,
ref := runevent.SHA
if target != "" {
ref = runevent.BaseBranch
} else if v.provenance == "default_branch" {
ref = runevent.DefaultBranch
}

fp, objects, _, err := v.Client.Repositories.GetContents(ctx, runevent.Organization,
Expand Down

0 comments on commit cd9c07c

Please sign in to comment.