Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cancelot: fix filtering and upgrade #614

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions cancelot/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
FROM gcr.io/cloud-builders/go AS build-env

ADD ./ /go/src/
WORKDIR /build
COPY . .

ENV GOBIN=/go/bin
RUN go get -d -v ./...
RUN go install /go/src/main.go
RUN go mod download
RUN go build -v -o ./main .

FROM alpine:latest

RUN apk add --no-cache ca-certificates

COPY --from=build-env /go/bin/main /go/bin/main
COPY --from=build-env /build/main /cancelot

ENTRYPOINT [ "/go/bin/main" ]
ENTRYPOINT [ "/cancelot" ]
89 changes: 45 additions & 44 deletions cancelot/cancelot/cancelpreviousbuild.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,15 @@ package cancelot

import (
"context"
"errors"
"fmt"
"log"
"time"

"cloud.google.com/go/cloudbuild/apiv1/v2/cloudbuildpb"
"github.com/avast/retry-go"
cloudbuild "google.golang.org/api/cloudbuild/v1"
"golang.org/x/sync/errgroup"
"google.golang.org/api/iterator"
)

// CancelPreviousBuild checks for previous running builds on the same branch, in order to cancel them
Expand All @@ -21,76 +25,73 @@ func CancelPreviousBuild(ctx context.Context, currentBuildID string, branchName
},
)
if err != nil {
log.Fatalf("Failed even after retries")
log.Fatal("Too many failed retries")
}
}

func cancelPreviousBuild(ctx context.Context, currentBuildID string, branchName string, sameTriggerOnly bool) error {
svc := gcbClient(ctx)
client := gcbClient(ctx)
project, err := getProject()
if err != nil {
return fmt.Errorf("cancelPreviousBuild: Failed to get project: %v", err)
return fmt.Errorf("getProject: %w", err)
}

log.Printf("Going to fetch current build details for: %s", currentBuildID)

currentBuildResponse, currentBuildError := svc.Projects.Builds.Get(project, currentBuildID).Do()
if currentBuildError != nil {
return fmt.Errorf("cancelPreviousBuild: Failed to get build details from Cloud Build: %v", currentBuildError)
currentBuild, err := client.GetBuild(ctx, &cloudbuildpb.GetBuildRequest{ProjectId: project, Id: currentBuildID})
if err != nil {
return fmt.Errorf("client.GetBuild: %w", err)
}

log.Printf("Going to check ongoing jobs for branch: %s", branchName)

onGoingJobFilter := fmt.Sprintf(`
build_id != "%s" AND
source.repo_source.branch_name = "%s" AND
status = "WORKING" AND
start_time<"%s"`,
filter := fmt.Sprintf(
`build_id != "%s" AND (status = "WORKING" OR status = "QUEUED") AND create_time < "%s"`,
currentBuildID,
branchName,
currentBuildResponse.StartTime)
currentBuild.StartTime.AsTime().Format(time.RFC3339),
)

if sameTriggerOnly {
onGoingJobFilter = fmt.Sprintf(`
%s AND
trigger_id = "%s"`,
onGoingJobFilter,
currentBuildResponse.BuildTriggerId)
}

log.Printf("Builds filter created: %s", onGoingJobFilter)

onGoingBuildsResponse, onGoingBuildsError := svc.Projects.Builds.List(project).Filter(onGoingJobFilter).Do()

if onGoingBuildsError != nil {
return fmt.Errorf("cancelPreviousBuild: Failed to get builds from Cloud Build: %v", onGoingBuildsError)
filter = fmt.Sprintf(`%s AND trigger_id = "%s"`, filter, currentBuild.BuildTriggerId)
}

onGoingBuilds := onGoingBuildsResponse.Builds
numOfOnGoingBuilds := len(onGoingBuilds)
filterRepoLocally := false
repoName := currentBuild.Source.GetRepoSource().GetRepoName()

if sameTriggerOnly {
log.Printf("Ongoing builds triggered by %s for %s has size of: %d", currentBuildResponse.BuildTriggerId, branchName, numOfOnGoingBuilds)
if repoName == "" {
// Connected repos don't have repo_name/branch_name filled in, so we need to resort to additional local filtering.
filterRepoLocally = true
repoName = currentBuild.Substitutions["REPO_NAME"]
} else {
log.Printf("Ongoing builds for %s has size of: %d", branchName, numOfOnGoingBuilds)
filter = fmt.Sprintf(`%s AND source.repo_source.repo_name = "%s" AND source.repo_source.branch_name = "%s"`, filter, repoName, branchName)
}

if numOfOnGoingBuilds == 0 {
return nil
log.Printf("Using builds filter: %s", filter)
if filterRepoLocally {
log.Println("Using local repo and branch filtering as this trigger is configured with a connected source")
}

for _, build := range onGoingBuilds {
log.Printf("Going to cancel build with id: %s", build.Id)
var cancels errgroup.Group
iter := client.ListBuilds(ctx, &cloudbuildpb.ListBuildsRequest{ProjectId: project, Filter: filter})
for {
build, err := iter.Next()
if err != nil {
if errors.Is(err, iterator.Done) {
break
}

cancelBuildCall := svc.Projects.Builds.Cancel(project, build.Id, &cloudbuild.CancelBuildRequest{})
buildCancelResponse, buildCancelError := cancelBuildCall.Do()
return fmt.Errorf("client.ListBuilds iter.Next: %w", err)
}

if buildCancelError != nil {
return fmt.Errorf("cancelPreviousBuild: Failed to cancel build with id:%s - %v", build.Id, buildCancelError)
if filterRepoLocally && (build.Substitutions["REPO_NAME"] != repoName || build.Substitutions["BRANCH_NAME"] != branchName) {
continue
}

log.Printf("Cancelled build with id:%s", buildCancelResponse.Id)
cancels.Go(func() error {
log.Printf("Canceling build %s (started at %s)", build.Id, build.CreateTime.AsTime().Format(time.RFC3339))

_, err := client.CancelBuild(ctx, &cloudbuildpb.CancelBuildRequest{ProjectId: build.ProjectId, Id: build.Id})
return err
})
}

return nil
return cancels.Wait()
}
22 changes: 11 additions & 11 deletions cancelot/cancelot/cloudbuild.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,8 @@ import (
"os/exec"
"strings"

cloudbuild "cloud.google.com/go/cloudbuild/apiv1/v2"
"cloud.google.com/go/compute/metadata"
"golang.org/x/oauth2/google"
cloudbuild "google.golang.org/api/cloudbuild/v1"
)

// getProject gets the project ID.
Expand All @@ -22,10 +21,12 @@ func getProject() (string, error) {
log.Printf("Failed to get project ID from instance metadata")
return "", err
}

return projectID, nil
}

// Shell out to gcloud.
cmd := exec.Command("gcloud", "config", "get-value", "project")
cmd := exec.Command("gcloud", "config", "get", "project")
var out bytes.Buffer
cmd.Stdout = &out
err := cmd.Run()
Expand All @@ -34,17 +35,16 @@ func getProject() (string, error) {
return "", err
}
projectID := strings.TrimSuffix(out.String(), "\n")
projectID = strings.TrimSuffix(projectID, "\r")

return projectID, nil
}

func gcbClient(ctx context.Context) *cloudbuild.Service {
client, err := google.DefaultClient(ctx, cloudbuild.CloudPlatformScope)
func gcbClient(ctx context.Context) *cloudbuild.Client {
client, err := cloudbuild.NewClient(ctx)
if err != nil {
log.Fatalf("Caught error creating client: %v", err)
log.Fatalf("Failed to create cloudbuild client: %v", err)
}
svc, err := cloudbuild.New(client)
if err != nil {
log.Fatalf("Caught error creating service: %v", err)
}
return svc

return client
}
31 changes: 31 additions & 0 deletions cancelot/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
module github.com/GoogleCloudPlatform/cloud-builders-community/cancelot

go 1.18

require (
cloud.google.com/go/cloudbuild v1.9.0
cloud.google.com/go/compute/metadata v0.2.3
github.com/avast/retry-go v3.0.0+incompatible
golang.org/x/sync v0.1.0
google.golang.org/api v0.114.0
)

require (
cloud.google.com/go v0.110.0 // indirect
cloud.google.com/go/compute v1.19.0 // indirect
cloud.google.com/go/longrunning v0.4.1 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/golang/protobuf v1.5.3 // indirect
github.com/google/go-cmp v0.5.9 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.2.3 // indirect
github.com/googleapis/gax-go/v2 v2.8.0 // indirect
go.opencensus.io v0.24.0 // indirect
golang.org/x/net v0.8.0 // indirect
golang.org/x/oauth2 v0.6.0 // indirect
golang.org/x/sys v0.6.0 // indirect
golang.org/x/text v0.8.0 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/genproto v0.0.0-20230330154414-c0448cd141ea // indirect
google.golang.org/grpc v1.54.0 // indirect
google.golang.org/protobuf v1.30.0 // indirect
)
Loading