Skip to content

Commit

Permalink
Merge branch 'main' into feat/build-dag
Browse files Browse the repository at this point in the history
  • Loading branch information
plyr4 authored Oct 26, 2023
2 parents a37df7e + 4eec5e9 commit 1414e60
Show file tree
Hide file tree
Showing 27 changed files with 785 additions and 36 deletions.
16 changes: 13 additions & 3 deletions api/admin/clean.go
Original file line number Diff line number Diff line change
Expand Up @@ -108,10 +108,20 @@ func CleanResources(c *gin.Context) {

logrus.Infof("platform admin %s: cleaned %d builds in database", u.GetName(), builds)

// clean executables
executables, err := database.FromContext(c).CleanBuildExecutables(ctx)
if err != nil {
retErr := fmt.Errorf("%d builds cleaned. unable to clean build executables: %w", builds, err)

util.HandleError(c, http.StatusInternalServerError, retErr)

return
}

// clean services
services, err := database.FromContext(c).CleanServices(ctx, msg, before)
if err != nil {
retErr := fmt.Errorf("%d builds cleaned. unable to update services: %w", builds, err)
retErr := fmt.Errorf("%d builds cleaned. %d executables cleaned. unable to update services: %w", builds, executables, err)

util.HandleError(c, http.StatusInternalServerError, retErr)

Expand All @@ -123,7 +133,7 @@ func CleanResources(c *gin.Context) {
// clean steps
steps, err := database.FromContext(c).CleanSteps(msg, before)
if err != nil {
retErr := fmt.Errorf("%d builds cleaned. %d services cleaned. unable to update steps: %w", builds, services, err)
retErr := fmt.Errorf("%d builds cleaned. %d executables cleaned. %d services cleaned. unable to update steps: %w", builds, executables, services, err)

util.HandleError(c, http.StatusInternalServerError, retErr)

Expand All @@ -132,5 +142,5 @@ func CleanResources(c *gin.Context) {

logrus.Infof("platform admin %s: cleaned %d steps in database", u.GetName(), steps)

c.JSON(http.StatusOK, fmt.Sprintf("%d builds cleaned. %d services cleaned. %d steps cleaned.", builds, services, steps))
c.JSON(http.StatusOK, fmt.Sprintf("%d builds cleaned. %d executables cleaned. %d services cleaned. %d steps cleaned.", builds, executables, services, steps))
}
4 changes: 1 addition & 3 deletions api/auth/validate_oauth.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
//
// Use of this source code is governed by the LICENSE file in this repository.
// SPDX-License-Identifier: Apache-2.0

package auth

Expand Down
187 changes: 187 additions & 0 deletions api/build/auto_cancel.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
// SPDX-License-Identifier: Apache-2.0

package build

import (
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"strings"
"time"

"github.com/gin-gonic/gin"
"github.com/go-vela/server/database"
"github.com/go-vela/server/internal/token"
"github.com/go-vela/types/constants"
"github.com/go-vela/types/library"
"github.com/go-vela/types/pipeline"
)

// AutoCancel is a helper function that checks to see if any pending or running
// builds for the repo can be replaced by the current build.
func AutoCancel(c *gin.Context, b *library.Build, rB *library.Build, r *library.Repo, cancelOpts *pipeline.CancelOptions) (bool, error) {
// if build is the current build, continue
if rB.GetID() == b.GetID() {
return false, nil
}

// ensure criteria is met before auto canceling (push to same branch, or pull with same action from same head_ref)
if (strings.EqualFold(rB.GetEvent(), constants.EventPush) &&
strings.EqualFold(b.GetEvent(), constants.EventPush) &&
strings.EqualFold(b.GetBranch(), rB.GetBranch())) ||
(strings.EqualFold(rB.GetEvent(), constants.EventPull) &&
strings.EqualFold(b.GetEventAction(), rB.GetEventAction()) &&
strings.EqualFold(b.GetHeadRef(), rB.GetHeadRef())) {
switch {
case strings.EqualFold(rB.GetStatus(), constants.StatusPending) && cancelOpts.Pending:
// pending build will be handled gracefully by worker once pulled off queue
rB.SetStatus(constants.StatusCanceled)

_, err := database.FromContext(c).UpdateBuild(c, rB)
if err != nil {
return false, err
}

// remove executable from table
_, err = database.FromContext(c).PopBuildExecutable(c, rB.GetID())
if err != nil {
return true, err
}
case strings.EqualFold(rB.GetStatus(), constants.StatusRunning) && cancelOpts.Running:
// call cancelRunning routine for builds already running on worker
err := cancelRunning(c, rB, r)
if err != nil {
return false, err
}
default:
return false, nil
}

// set error message that references current build
rB.SetError(fmt.Sprintf("build was auto canceled in favor of build %d", b.GetNumber()))

_, err := database.FromContext(c).UpdateBuild(c, rB)
if err != nil {
// if this call fails, we still canceled the build, so return true
return true, err
}
}

return true, nil
}

// cancelRunning is a helper function that determines the executor currently running a build and sends an API call
// to that executor's worker to cancel the build.
func cancelRunning(c *gin.Context, b *library.Build, r *library.Repo) error {
e := new([]library.Executor)
// retrieve the worker
w, err := database.FromContext(c).GetWorkerForHostname(c, b.GetHost())
if err != nil {
return err
}

// prepare the request to the worker to retrieve executors
client := http.DefaultClient
client.Timeout = 30 * time.Second
endpoint := fmt.Sprintf("%s/api/v1/executors", w.GetAddress())

req, err := http.NewRequestWithContext(context.Background(), "GET", endpoint, nil)
if err != nil {
return err
}

tm := c.MustGet("token-manager").(*token.Manager)

// set mint token options
mto := &token.MintTokenOpts{
Hostname: "vela-server",
TokenType: constants.WorkerAuthTokenType,
TokenDuration: time.Minute * 1,
}

// mint token
tkn, err := tm.MintToken(mto)
if err != nil {
return err
}

// add the token to authenticate to the worker
req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", tkn))

// make the request to the worker and check the response
resp, err := client.Do(req)
if err != nil {
return err
}

defer resp.Body.Close()

// Read Response Body
respBody, err := io.ReadAll(resp.Body)
if err != nil {
return err
}

// parse response and validate at least one item was returned
err = json.Unmarshal(respBody, e)
if err != nil {
return err
}

for _, executor := range *e {
// check each executor on the worker running the build to see if it's running the build we want to cancel
if strings.EqualFold(executor.Repo.GetFullName(), r.GetFullName()) && *executor.GetBuild().Number == b.GetNumber() {
// prepare the request to the worker
client := http.DefaultClient
client.Timeout = 30 * time.Second

// set the API endpoint path we send the request to
u := fmt.Sprintf("%s/api/v1/executors/%d/build/cancel", w.GetAddress(), executor.GetID())

req, err := http.NewRequestWithContext(context.Background(), "DELETE", u, nil)
if err != nil {
return err
}

tm := c.MustGet("token-manager").(*token.Manager)

// set mint token options
mto := &token.MintTokenOpts{
Hostname: "vela-server",
TokenType: constants.WorkerAuthTokenType,
TokenDuration: time.Minute * 1,
}

// mint token
tkn, err := tm.MintToken(mto)
if err != nil {
return err
}

// add the token to authenticate to the worker
req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", tkn))

// perform the request to the worker
resp, err := client.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()

// Read Response Body
respBody, err := io.ReadAll(resp.Body)
if err != nil {
return err
}

err = json.Unmarshal(respBody, b)
if err != nil {
return err
}
}
}

return nil
}
14 changes: 12 additions & 2 deletions api/build/cancel.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ func CancelBuild(c *gin.Context) {
e := executors.Retrieve(c)
o := org.Retrieve(c)
r := repo.Retrieve(c)
u := user.Retrieve(c)
user := user.Retrieve(c)
ctx := c.Request.Context()

entry := fmt.Sprintf("%s/%d", r.GetFullName(), b.GetNumber())
Expand All @@ -89,7 +89,7 @@ func CancelBuild(c *gin.Context) {
"build": b.GetNumber(),
"org": o,
"repo": r.GetName(),
"user": u.GetName(),
"user": user.GetName(),
}).Infof("canceling build %s", entry)

switch b.GetStatus() {
Expand Down Expand Up @@ -169,6 +169,16 @@ func CancelBuild(c *gin.Context) {
return
}

b.SetError(fmt.Sprintf("build was canceled by %s", user.GetName()))

b, err = database.FromContext(c).UpdateBuild(ctx, b)
if err != nil {
retErr := fmt.Errorf("unable to update status for build %s: %w", entry, err)
util.HandleError(c, http.StatusInternalServerError, retErr)

return
}

c.JSON(resp.StatusCode, b)

return
Expand Down
4 changes: 1 addition & 3 deletions api/queue/queue.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
//
// Use of this source code is governed by the LICENSE file in this repository.
// SPDX-License-Identifier: Apache-2.0

package queue

Expand Down
34 changes: 34 additions & 0 deletions api/webhook/post.go
Original file line number Diff line number Diff line change
Expand Up @@ -677,6 +677,40 @@ func PostWebhook(c *gin.Context) {
repo,
u,
)

// if anything is provided in the auto_cancel metadata, then we start with true
runAutoCancel := p.Metadata.AutoCancel.Running || p.Metadata.AutoCancel.Pending || p.Metadata.AutoCancel.DefaultBranch

// if the event is a push to the default branch and the AutoCancel.DefaultBranch value is false, bypass auto cancel
if strings.EqualFold(b.GetEvent(), constants.EventPush) && strings.EqualFold(b.GetBranch(), repo.GetBranch()) && !p.Metadata.AutoCancel.DefaultBranch {
runAutoCancel = false
}

// if event is push or pull_request:synchronize, there is a chance this build could be superceding a stale build
//
// fetch pending and running builds for this repo in order to validate their merit to continue running.
if runAutoCancel &&
((strings.EqualFold(b.GetEvent(), constants.EventPull) && strings.EqualFold(b.GetEventAction(), constants.ActionSynchronize)) ||
strings.EqualFold(b.GetEvent(), constants.EventPush)) {
// fetch pending and running builds
rBs, err := database.FromContext(c).ListPendingAndRunningBuildsForRepo(c, repo)
if err != nil {
logrus.Errorf("unable to fetch pending and running builds for %s: %v", repo.GetFullName(), err)
}

for _, rB := range rBs {
// call auto cancel routine
canceled, err := build.AutoCancel(c, b, rB, repo, p.Metadata.AutoCancel)
if err != nil {
// continue cancel loop if error, but log based on type of error
if canceled {
logrus.Errorf("unable to update canceled build error message: %v", err)
} else {
logrus.Errorf("unable to cancel running build: %v", err)
}
}
}
}
}

// handleRepositoryEvent is a helper function that processes repository events from the SCM and updates
Expand Down
Loading

0 comments on commit 1414e60

Please sign in to comment.