Skip to content
Merged
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
2,185 changes: 0 additions & 2,185 deletions api/build.go

This file was deleted.

289 changes: 289 additions & 0 deletions api/build/cancel.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,289 @@
// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
//
// Use of this source code is governed by the LICENSE file in this repository.

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/server/router/middleware/build"
"github.com/go-vela/server/router/middleware/executors"
"github.com/go-vela/server/router/middleware/org"
"github.com/go-vela/server/router/middleware/repo"
"github.com/go-vela/server/router/middleware/user"
"github.com/go-vela/server/util"
"github.com/go-vela/types/constants"
"github.com/go-vela/types/library"
"github.com/sirupsen/logrus"
)

// swagger:operation DELETE /api/v1/repos/{org}/{repo}/builds/{build}/cancel builds CancelBuild
//
// Cancel a running build
//
// ---
// produces:
// - application/json
// parameters:
// - in: path
// name: repo
// description: Name of the repo
// required: true
// type: string
// - in: path
// name: org
// description: Name of the org
// required: true
// type: string
// - in: path
// name: build
// description: Build number to cancel
// required: true
// type: integer
// security:
// - ApiKeyAuth: []
// responses:
// '200':
// description: Successfully canceled the build
// schema:
// type: string
// '400':
// description: Unable to cancel build
// schema:
// "$ref": "#/definitions/Error"
// '404':
// description: Unable to cancel build
// schema:
// "$ref": "#/definitions/Error"
// '500':
// description: Unable to cancel build
// schema:
// "$ref": "#/definitions/Error"

// CancelBuild represents the API handler to cancel a running build.
//
//nolint:funlen // ignore statement count
func CancelBuild(c *gin.Context) {
// capture middleware values
b := build.Retrieve(c)
e := executors.Retrieve(c)
o := org.Retrieve(c)
r := repo.Retrieve(c)
u := user.Retrieve(c)

entry := fmt.Sprintf("%s/%d", r.GetFullName(), b.GetNumber())

// update engine logger with API metadata
//
// https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Entry.WithFields
logrus.WithFields(logrus.Fields{
"build": b.GetNumber(),
"org": o,
"repo": r.GetName(),
"user": u.GetName(),
}).Infof("canceling build %s", entry)

switch b.GetStatus() {
case constants.StatusRunning:
// retrieve the worker info
w, err := database.FromContext(c).GetWorkerForHostname(b.GetHost())
if err != nil {
retErr := fmt.Errorf("unable to get worker for build %s: %w", entry, err)
util.HandleError(c, http.StatusNotFound, retErr)

return
}

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 {
retErr := fmt.Errorf("unable to form a request to %s: %w", u, err)
util.HandleError(c, http.StatusBadRequest, retErr)

return
}

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 {
retErr := fmt.Errorf("unable to generate auth token: %w", err)
util.HandleError(c, http.StatusInternalServerError, retErr)

return
}

// 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 {
retErr := fmt.Errorf("unable to connect to %s: %w", u, err)
util.HandleError(c, http.StatusBadRequest, retErr)

return
}
defer resp.Body.Close()

// Read Response Body
respBody, err := io.ReadAll(resp.Body)
if err != nil {
retErr := fmt.Errorf("unable to read response from %s: %w", u, err)
util.HandleError(c, http.StatusBadRequest, retErr)

return
}

err = json.Unmarshal(respBody, b)
if err != nil {
retErr := fmt.Errorf("unable to parse response from %s: %w", u, err)
util.HandleError(c, http.StatusBadRequest, retErr)

return
}

c.JSON(resp.StatusCode, b)

return
}
}
case constants.StatusPending:
break

default:
retErr := fmt.Errorf("found build %s but its status was %s", entry, b.GetStatus())

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

return
}

// build has been abandoned
// update the status in the build table
b.SetStatus(constants.StatusCanceled)

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

return
}

// retrieve the steps for the build from the step table
steps := []*library.Step{}
page := 1
perPage := 100

for page > 0 {
// retrieve build steps (per page) from the database
stepsPart, _, err := database.FromContext(c).ListStepsForBuild(b, map[string]interface{}{}, page, perPage)
if err != nil {
retErr := fmt.Errorf("unable to retrieve steps for build %s: %w", entry, err)
util.HandleError(c, http.StatusNotFound, retErr)

return
}

// add page of steps to list steps
steps = append(steps, stepsPart...)

// assume no more pages exist if under 100 results are returned
if len(stepsPart) < 100 {
page = 0
} else {
page++
}
}

// iterate over each step for the build
// setting anything running or pending to canceled
for _, step := range steps {
if step.GetStatus() == constants.StatusRunning || step.GetStatus() == constants.StatusPending {
step.SetStatus(constants.StatusCanceled)

err = database.FromContext(c).UpdateStep(step)
if err != nil {
retErr := fmt.Errorf("unable to update step %s for build %s: %w", step.GetName(), entry, err)
util.HandleError(c, http.StatusNotFound, retErr)

return
}
}
}

// retrieve the services for the build from the service table
services := []*library.Service{}
page = 1

for page > 0 {
// retrieve build services (per page) from the database
servicesPart, _, err := database.FromContext(c).ListServicesForBuild(b, map[string]interface{}{}, page, perPage)
if err != nil {
retErr := fmt.Errorf("unable to retrieve services for build %s: %w", entry, err)
util.HandleError(c, http.StatusNotFound, retErr)

return
}

// add page of services to the list of services
services = append(services, servicesPart...)

// assume no more pages exist if under 100 results are returned
if len(servicesPart) < 100 {
page = 0
} else {
page++
}
}

// iterate over each service for the build
// setting anything running or pending to canceled
for _, service := range services {
if service.GetStatus() == constants.StatusRunning || service.GetStatus() == constants.StatusPending {
service.SetStatus(constants.StatusCanceled)

err = database.FromContext(c).UpdateService(service)
if err != nil {
retErr := fmt.Errorf("unable to update service %s for build %s: %w",
service.GetName(),
entry,
err,
)
util.HandleError(c, http.StatusNotFound, retErr)

return
}
}
}

c.JSON(http.StatusOK, b)
}
56 changes: 56 additions & 0 deletions api/build/clean.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// Copyright (c) 2023 Target Brands, Inc. All rights reserved.
//
// Use of this source code is governed by the LICENSE file in this repository.

package build

import (
"fmt"
"time"

"github.com/go-vela/server/database"
"github.com/go-vela/types/constants"
"github.com/go-vela/types/library"
"github.com/sirupsen/logrus"
)

// cleanBuild is a helper function to kill the build
// without execution. This will kill all resources,
// like steps and services, for the build in the
// configured backend.
func CleanBuild(database database.Interface, b *library.Build, services []*library.Service, steps []*library.Step, e error) {
// update fields in build object
b.SetError(fmt.Sprintf("unable to publish to queue: %s", e.Error()))
b.SetStatus(constants.StatusError)
b.SetFinished(time.Now().UTC().Unix())

// send API call to update the build
err := database.UpdateBuild(b)
if err != nil {
logrus.Errorf("unable to kill build %d: %v", b.GetNumber(), err)
}

for _, s := range services {
// update fields in service object
s.SetStatus(constants.StatusKilled)
s.SetFinished(time.Now().UTC().Unix())

// send API call to update the service
err := database.UpdateService(s)
if err != nil {
logrus.Errorf("unable to kill service %s for build %d: %v", s.GetName(), b.GetNumber(), err)
}
}

for _, s := range steps {
// update fields in step object
s.SetStatus(constants.StatusKilled)
s.SetFinished(time.Now().UTC().Unix())

// send API call to update the step
err := database.UpdateStep(s)
if err != nil {
logrus.Errorf("unable to kill step %s for build %d: %v", s.GetName(), b.GetNumber(), err)
}
}
}
Loading