Skip to content

Commit

Permalink
feat(repo settings): approve build mechanism for pull_request events (
Browse files Browse the repository at this point in the history
#328)

* init commit

* add testing

* Update library/repo.go

Co-authored-by: Jacob Floyd <cognifloyd@gmail.com>

* audit approval and allow more options for approve field

* pack PR data into a PullRequest struct for webhooks

* last name change I promise

---------

Co-authored-by: Tim Huynh <tim_huynh94@yahoo.com>
Co-authored-by: Jacob Floyd <cognifloyd@gmail.com>
  • Loading branch information
3 people authored Nov 27, 2023
1 parent e7d5019 commit b43cd77
Show file tree
Hide file tree
Showing 11 changed files with 211 additions and 6 deletions.
18 changes: 18 additions & 0 deletions constants/repo.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,21 @@ const (
// in Vela to control their pipeline being compiled as Starlark templates.
PipelineTypeStarlark = "starlark"
)

// Repo ApproveBuild types.
const (
// ApproveForkAlways defines the CI strategy of having a repo administrator approve
// all builds triggered from a forked PR.
ApproveForkAlways = "fork-always"

// ApproveForkNoWrite defines the CI strategy of having a repo administrator approve
// all builds triggered from a forked PR where the author does not have write access.
ApproveForkNoWrite = "fork-no-write"

// ApproveOnce defines the CI strategy of having a repo administrator approve
// all builds triggered from an outside contributor if this is their first time contributing.
ApproveOnce = "first-time"

// ApproveNever defines the CI strategy of never having to approve CI builds from outside contributors.
ApproveNever = "never"
)
3 changes: 3 additions & 0 deletions constants/status.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ const (
// StatusPending defines the status type for build and step pending statuses.
StatusPending = "pending"

// StatusPendingApproval defines the status type for a build waiting to be approved to run.
StatusPendingApproval = "pending approval"

// StatusRunning defines the status type for build and step running statuses.
StatusRunning = "running"

Expand Down
19 changes: 18 additions & 1 deletion database/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ type Build struct {
Host sql.NullString `sql:"host"`
Runtime sql.NullString `sql:"runtime"`
Distribution sql.NullString `sql:"distribution"`
ApprovedAt sql.NullInt64 `sql:"approved_at"`
ApprovedBy sql.NullString `sql:"approved_by"`
}

// Crop prepares the Build type for inserting into the database by
Expand Down Expand Up @@ -92,7 +94,7 @@ func (b *Build) Crop() *Build {
// value for the field, the valid flag is set to
// false causing it to be NULL in the database.
//
//nolint:gocyclo // ignore cyclomatic complexity due to number of fields
//nolint:gocyclo,funlen // ignore cyclomatic complexity due to number of fields
func (b *Build) Nullify() *Build {
if b == nil {
return nil
Expand Down Expand Up @@ -248,6 +250,16 @@ func (b *Build) Nullify() *Build {
b.Distribution.Valid = false
}

// check if the ApprovedAt field should be false
if b.ApprovedAt.Int64 == 0 {
b.ApprovedAt.Valid = false
}

// check if the ApprovedBy field should be false
if len(b.ApprovedBy.String) == 0 {
b.ApprovedBy.Valid = false
}

return b
}

Expand Down Expand Up @@ -287,6 +299,8 @@ func (b *Build) ToLibrary() *library.Build {
build.SetHost(b.Host.String)
build.SetRuntime(b.Runtime.String)
build.SetDistribution(b.Distribution.String)
build.SetApprovedAt(b.ApprovedAt.Int64)
build.SetApprovedBy(b.ApprovedBy.String)

return build
}
Expand Down Expand Up @@ -328,6 +342,7 @@ func (b *Build) Validate() error {
b.Host = sql.NullString{String: sanitize(b.Host.String), Valid: b.Host.Valid}
b.Runtime = sql.NullString{String: sanitize(b.Runtime.String), Valid: b.Runtime.Valid}
b.Distribution = sql.NullString{String: sanitize(b.Distribution.String), Valid: b.Distribution.Valid}
b.ApprovedBy = sql.NullString{String: sanitize(b.ApprovedBy.String), Valid: b.ApprovedBy.Valid}

return nil
}
Expand Down Expand Up @@ -367,6 +382,8 @@ func BuildFromLibrary(b *library.Build) *Build {
Host: sql.NullString{String: b.GetHost(), Valid: true},
Runtime: sql.NullString{String: b.GetRuntime(), Valid: true},
Distribution: sql.NullString{String: b.GetDistribution(), Valid: true},
ApprovedAt: sql.NullInt64{Int64: b.GetApprovedAt(), Valid: true},
ApprovedBy: sql.NullString{String: b.GetApprovedBy(), Valid: true},
}

return build.Nullify()
Expand Down
6 changes: 6 additions & 0 deletions database/build_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,8 @@ func TestDatabase_Build_ToLibrary(t *testing.T) {
want.SetRuntime("docker")
want.SetDistribution("linux")
want.SetDeployPayload(raw.StringSliceMap{"foo": "test1", "bar": "test2"})
want.SetApprovedAt(1563474076)
want.SetApprovedBy("OctoCat")

// run test
got := testBuild().ToLibrary()
Expand Down Expand Up @@ -228,6 +230,8 @@ func TestDatabase_BuildFromLibrary(t *testing.T) {
b.SetRuntime("docker")
b.SetDistribution("linux")
b.SetDeployPayload(raw.StringSliceMap{"foo": "test1", "bar": "test2"})
b.SetApprovedAt(1563474076)
b.SetApprovedBy("OctoCat")

want := testBuild()

Expand Down Expand Up @@ -286,5 +290,7 @@ func testBuild() *Build {
Host: sql.NullString{String: "example.company.com", Valid: true},
Runtime: sql.NullString{String: "docker", Valid: true},
Distribution: sql.NullString{String: "linux", Valid: true},
ApprovedAt: sql.NullInt64{Int64: 1563474076, Valid: true},
ApprovedBy: sql.NullString{String: "OctoCat", Valid: true},
}
}
8 changes: 8 additions & 0 deletions database/repo.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ type Repo struct {
AllowComment sql.NullBool `sql:"allow_comment"`
PipelineType sql.NullString `sql:"pipeline_type"`
PreviousName sql.NullString `sql:"previous_name"`
ApproveBuild sql.NullString `sql:"approve_build"`
}

// Decrypt will manipulate the existing repo hash by
Expand Down Expand Up @@ -198,6 +199,11 @@ func (r *Repo) Nullify() *Repo {
r.PreviousName.Valid = false
}

// check if the ApproveForkBuild field should be false
if len(r.ApproveBuild.String) == 0 {
r.ApproveBuild.Valid = false
}

return r
}

Expand Down Expand Up @@ -230,6 +236,7 @@ func (r *Repo) ToLibrary() *library.Repo {
repo.SetAllowComment(r.AllowComment.Bool)
repo.SetPipelineType(r.PipelineType.String)
repo.SetPreviousName(r.PreviousName.String)
repo.SetApproveBuild(r.ApproveBuild.String)

return repo
}
Expand Down Expand Up @@ -325,6 +332,7 @@ func RepoFromLibrary(r *library.Repo) *Repo {
AllowComment: sql.NullBool{Bool: r.GetAllowComment(), Valid: true},
PipelineType: sql.NullString{String: r.GetPipelineType(), Valid: true},
PreviousName: sql.NullString{String: r.GetPreviousName(), Valid: true},
ApproveBuild: sql.NullString{String: r.GetApproveBuild(), Valid: true},
}

return repo.Nullify()
Expand Down
5 changes: 5 additions & 0 deletions database/repo_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"reflect"
"testing"

"github.com/go-vela/types/constants"
"github.com/go-vela/types/library"
)

Expand Down Expand Up @@ -118,6 +119,7 @@ func TestDatabase_Repo_Nullify(t *testing.T) {
Timeout: sql.NullInt64{Int64: 0, Valid: false},
Visibility: sql.NullString{String: "", Valid: false},
PipelineType: sql.NullString{String: "", Valid: false},
ApproveBuild: sql.NullString{String: "", Valid: false},
}

// setup tests
Expand Down Expand Up @@ -177,6 +179,7 @@ func TestDatabase_Repo_ToLibrary(t *testing.T) {
want.SetAllowComment(false)
want.SetPipelineType("yaml")
want.SetPreviousName("oldName")
want.SetApproveBuild(constants.ApproveNever)

// run test
got := testRepo().ToLibrary()
Expand Down Expand Up @@ -330,6 +333,7 @@ func TestDatabase_RepoFromLibrary(t *testing.T) {
r.SetAllowComment(false)
r.SetPipelineType("yaml")
r.SetPreviousName("oldName")
r.SetApproveBuild(constants.ApproveNever)

want := testRepo()

Expand Down Expand Up @@ -369,5 +373,6 @@ func testRepo() *Repo {
AllowComment: sql.NullBool{Bool: false, Valid: true},
PipelineType: sql.NullString{String: "yaml", Valid: true},
PreviousName: sql.NullString{String: "oldName", Valid: true},
ApproveBuild: sql.NullString{String: constants.ApproveNever, Valid: true},
}
}
60 changes: 60 additions & 0 deletions library/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ type Build struct {
Host *string `json:"host,omitempty"`
Runtime *string `json:"runtime,omitempty"`
Distribution *string `json:"distribution,omitempty"`
ApprovedAt *int64 `json:"approved_at,omitempty"`
ApprovedBy *string `json:"approved_by,omitempty"`
}

// Duration calculates and returns the total amount of
Expand Down Expand Up @@ -83,6 +85,8 @@ func (b *Build) Duration() string {
// provided from the fields of the Build type.
func (b *Build) Environment(workspace, channel string) map[string]string {
envs := map[string]string{
"VELA_BUILD_APPROVED_AT": ToString(b.GetApprovedAt()),
"VELA_BUILD_APPROVED_BY": ToString(b.GetApprovedBy()),
"VELA_BUILD_AUTHOR": ToString(b.GetAuthor()),
"VELA_BUILD_AUTHOR_EMAIL": ToString(b.GetEmail()),
"VELA_BUILD_BASE_REF": ToString(b.GetBaseRef()),
Expand Down Expand Up @@ -600,6 +604,32 @@ func (b *Build) GetDistribution() string {
return *b.Distribution
}

// GetApprovedAt returns the ApprovedAt field.
//
// When the provided Build type is nil, or the field within
// the type is nil, it returns the zero value for the field.
func (b *Build) GetApprovedAt() int64 {
// return zero value if Build type or ApprovedAt field is nil
if b == nil || b.ApprovedAt == nil {
return 0
}

return *b.ApprovedAt
}

// GetApprovedBy returns the ApprovedBy field.
//
// When the provided Build type is nil, or the field within
// the type is nil, it returns the zero value for the field.
func (b *Build) GetApprovedBy() string {
// return zero value if Build type or ApprovedBy field is nil
if b == nil || b.ApprovedBy == nil {
return ""
}

return *b.ApprovedBy
}

// SetID sets the ID field.
//
// When the provided Build type is nil, it
Expand Down Expand Up @@ -1003,11 +1033,39 @@ func (b *Build) SetDistribution(v string) {
b.Distribution = &v
}

// SetApprovedAt sets the ApprovedAt field.
//
// When the provided Build type is nil, it
// will set nothing and immediately return.
func (b *Build) SetApprovedAt(v int64) {
// return if Build type is nil
if b == nil {
return
}

b.ApprovedAt = &v
}

// SetApprovedBy sets the ApprovedBy field.
//
// When the provided Build type is nil, it
// will set nothing and immediately return.
func (b *Build) SetApprovedBy(v string) {
// return if Build type is nil
if b == nil {
return
}

b.ApprovedBy = &v
}

// String implements the Stringer interface for the Build type.
//
//nolint:dupl // this is duplicated in the test
func (b *Build) String() string {
return fmt.Sprintf(`{
ApprovedAt: %d,
ApprovedBy: %s,
Author: %s,
BaseRef: %s,
Branch: %s,
Expand Down Expand Up @@ -1040,6 +1098,8 @@ func (b *Build) String() string {
Status: %s,
Title: %s,
}`,
b.GetApprovedAt(),
b.GetApprovedBy(),
b.GetAuthor(),
b.GetBaseRef(),
b.GetBranch(),
Expand Down
Loading

0 comments on commit b43cd77

Please sign in to comment.