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

feat: Add privateWorkerPool and location configuration for gcb #7440

Merged
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
8 changes: 7 additions & 1 deletion docs/content/en/schemas/v2beta29.json
Original file line number Diff line number Diff line change
Expand Up @@ -1860,6 +1860,11 @@
"description": "ID of your Cloud Platform Project. If it is not provided, Skaffold will guess it from the image name. For example, given the artifact image name `gcr.io/myproject/image`, Skaffold will use the `myproject` GCP project.",
"x-intellij-html-description": "ID of your Cloud Platform Project. If it is not provided, Skaffold will guess it from the image name. For example, given the artifact image name <code>gcr.io/myproject/image</code>, Skaffold will use the <code>myproject</code> GCP project."
},
"region": {
"type": "string",
"description": "configures the region to run the build. If WorkerPool is configured, the region will be deduced from the WorkerPool configuration. If neither WorkerPool nor Region is configured, the build will be run in global(non-regional). See [Cloud Build locations](https://cloud.google.com/build/docs/locations).",
"x-intellij-html-description": "configures the region to run the build. If WorkerPool is configured, the region will be deduced from the WorkerPool configuration. If neither WorkerPool nor Region is configured, the build will be run in global(non-regional). See <a href=\"https://cloud.google.com/build/docs/locations\">Cloud Build locations</a>."
},
"timeout": {
"type": "string",
"description": "amount of time (in seconds) that this build should be allowed to run. See [Cloud Build Reference](https://cloud.google.com/cloud-build/docs/api/reference/rest/v1/projects.builds#resource-build).",
Expand All @@ -1884,7 +1889,8 @@
"gradleImage",
"packImage",
"concurrency",
"workerPool"
"workerPool",
"region"
],
"additionalProperties": false,
"type": "object",
Expand Down
2 changes: 1 addition & 1 deletion integration/inspect_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ func TestInspectBuildEnv(t *testing.T) {

gcbParams := []string{
"--projectId", "proj2",
"--workerPool", "pool2",
"--workerPool", "projects/test/locations/asia-east1/workerPools/pool2",
"--timeout", "180s",
"--machineType", "vm2",
"--logStreamingOption", "STREAM_ON",
Expand Down
5 changes: 5 additions & 0 deletions integration/run_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,11 @@ func TestRunGCPOnly(t *testing.T) {
dir: "testdata/gcb-sub-folder",
pods: []string{"getting-started"},
},
{
description: "Google Cloud Build with location",
dir: "testdata/gcb-with-location",
pods: []string{"getting-started"},
},
{
description: "Google Cloud Build with source artifact dependencies",
dir: "examples/microservices",
Expand Down
14 changes: 14 additions & 0 deletions integration/testdata/gcb-with-location/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
FROM golang:1.18 as builder
WORKDIR /code
COPY main.go .
COPY go.mod .
# `skaffold debug` sets SKAFFOLD_GO_GCFLAGS to disable compiler optimizations
ARG SKAFFOLD_GO_GCFLAGS
RUN go build -gcflags="${SKAFFOLD_GO_GCFLAGS}" -trimpath -o /app main.go

FROM alpine:3
# Define GOTRACEBACK to mark this container as using the Go language runtime
# for `skaffold debug` (https://skaffold.dev/docs/workflows/debug/).
ENV GOTRACEBACK=single
CMD ["./app"]
COPY --from=builder /app .
3 changes: 3 additions & 0 deletions integration/testdata/gcb-with-location/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module github.com/GoogleContainerTools/skaffold/examples/google-cloud-build

go 1.18
8 changes: 8 additions & 0 deletions integration/testdata/gcb-with-location/k8s-pod.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
apiVersion: v1
kind: Pod
metadata:
name: getting-started
spec:
containers:
- name: getting-started
image: gcr.io/k8s-skaffold/skaffold-example-with-location
14 changes: 14 additions & 0 deletions integration/testdata/gcb-with-location/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package main

import (
"fmt"
"time"
)

func main() {
for {
fmt.Println("Hello world!")

time.Sleep(time.Second * 1)
}
}
12 changes: 12 additions & 0 deletions integration/testdata/gcb-with-location/skaffold.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
apiVersion: skaffold/v2beta29
kind: Config
build:
googleCloudBuild:
projectId: k8s-skaffold
region: asia-east1
artifacts:
- image: gcr.io/k8s-skaffold/skaffold-example-with-location
deploy:
kubectl:
manifests:
- k8s-*
4 changes: 2 additions & 2 deletions integration/testdata/inspect/gcb/skaffold.add.default.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ build:
logging: LEGACY
logStreamingOption: STREAM_ON
concurrency: 2
workerPool: pool2
workerPool: projects/test/locations/asia-east1/workerPools/pool2
profiles:
- name: gcb
build:
Expand All @@ -23,4 +23,4 @@ profiles:
logging: GCS_ONLY
logStreamingOption: STREAM_DEFAULT
concurrency: 3
workerPool: pool1
workerPool: projects/test/locations/asia-east1/workerPools/pool1
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ build:
logging: GCS_ONLY
logStreamingOption: STREAM_DEFAULT
concurrency: 3
workerPool: pool1
profiles:
- name: local
build:
Expand All @@ -27,4 +26,4 @@ profiles:
logging: LEGACY
logStreamingOption: STREAM_ON
concurrency: 2
workerPool: pool2
workerPool: projects/test/locations/asia-east1/workerPools/pool2
1 change: 0 additions & 1 deletion integration/testdata/inspect/gcb/skaffold.gcb.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ build:
logging: GCS_ONLY
logStreamingOption: STREAM_DEFAULT
concurrency: 3
workerPool: pool1
profiles:
- name: local
build:
Expand Down
2 changes: 1 addition & 1 deletion integration/testdata/inspect/gcb/skaffold.local.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,4 @@ profiles:
logging: GCS_ONLY
logStreamingOption: STREAM_DEFAULT
concurrency: 3
workerPool: pool1
workerPool: projects/test/locations/asia-east1/workerPools/pool1
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ build:
logging: LEGACY
logStreamingOption: STREAM_ON
concurrency: 2
workerPool: pool2
workerPool: projects/test/locations/asia-east1/workerPools/pool2
profiles:
- name: local
build:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,4 @@ profiles:
logging: LEGACY
logStreamingOption: STREAM_ON
concurrency: 2
workerPool: pool2
workerPool: projects/test/locations/asia-east1/workerPools/pool2
85 changes: 62 additions & 23 deletions pkg/skaffold/build/gcb/cloud_build.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (
"fmt"
"io"
"net/http"
"strings"
"time"

cstorage "cloud.google.com/go/storage"
Expand Down Expand Up @@ -159,24 +160,10 @@ func (b *Builder) buildArtifactWithCloudBuild(ctx context.Context, out io.Writer
Message: fmt.Sprintf("could not create build description: %s", err),
})
}
call := cbclient.Projects.Builds.Create(projectID, &buildSpec)
op, err := call.Context(ctx).Do()

if err != nil {
return "", sErrors.NewErrorWithStatusCode(&proto.ActionableErr{
ErrCode: proto.StatusCode_BUILD_GCB_CREATE_BUILD_ERR,
Message: fmt.Sprintf("error creating build: %s", err),
})
}

remoteID, err := getBuildID(op)
remoteID, getBuildFunc, err := b.createCloudBuild(ctx, cbclient, projectID, buildSpec)
if err != nil {
return "", sErrors.NewErrorWithStatusCode(&proto.ActionableErr{
ErrCode: proto.StatusCode_BUILD_GCB_GET_BUILD_ID_ERR,
Message: err.Error(),
})
return "", err
}

logsObject := fmt.Sprintf("log-%s.txt", remoteID)
output.Default.Fprintf(out, "Logs are available at \nhttps://console.cloud.google.com/m/cloudstorage/b/%s/o/%s\n", cbBucket, logsObject)

Expand All @@ -185,28 +172,30 @@ func (b *Builder) buildArtifactWithCloudBuild(ctx context.Context, out io.Writer
watch:
for {
var cb *cloudbuild.Build
var err error
var errE error
log.Entry(ctx).Debugf("current offset %d", offset)
backoff := NewStatusBackoff()
if waitErr := wait.Poll(backoff.Duration, RetryTimeout, func() (bool, error) {
time.Sleep(backoff.Step())
cb, err = cbclient.Projects.Builds.Get(projectID, remoteID).Do()
if err == nil {
step := backoff.Step()
log.Entry(ctx).Debugf("backing off for %s", step)
time.Sleep(step)
cb, errE = getBuildFunc()
if errE == nil {
return true, nil
}
// Error code 429 is the error code for quota exceeded https://github.com/googleapis/googleapis/blob/master/google/rpc/code.proto
if apiErr, ok := err.(*googleapi.Error); ok && apiErr.Code == 429 {
if apiErr, ok := errE.(*googleapi.Error); ok && apiErr.Code == 429 {
// if we hit the rate limit, continue to retry
return false, nil
}
return false, err
return false, errE
}); waitErr != nil {
return "", sErrors.NewErrorWithStatusCode(&proto.ActionableErr{
ErrCode: proto.StatusCode_BUILD_GCB_GET_BUILD_STATUS_ERR,
Message: fmt.Sprintf("error getting build status: %s", waitErr),
})
}
if err != nil {
if errE != nil {
return "", sErrors.NewErrorWithStatusCode(&proto.ActionableErr{
ErrCode: proto.StatusCode_BUILD_GCB_GET_BUILD_STATUS_ERR,
Message: fmt.Sprintf("error getting build status %s", err),
Expand Down Expand Up @@ -383,3 +372,53 @@ func (b *Builder) createBucketIfNotExists(ctx context.Context, c *cstorage.Clien
log.Entry(ctx).Debugf("Created bucket %s in %s", bucket, projectID)
return nil
}

func (b *Builder) createCloudBuild(ctx context.Context, cbclient *cloudbuild.Service, projectID string, buildSpec cloudbuild.Build) (string, func(opts ...googleapi.CallOption) (*cloudbuild.Build, error), error) {
var op *cloudbuild.Operation
var err error
if b.WorkerPool == "" && b.Region == "" {
op, err = cbclient.Projects.Builds.Create(projectID, &buildSpec).Context(ctx).Do()
if err != nil {
return "", nil, sErrors.NewErrorWithStatusCode(&proto.ActionableErr{
ErrCode: proto.StatusCode_BUILD_GCB_CREATE_BUILD_ERR,
Message: fmt.Sprintf("error creating build: %s", err),
})
}
remoteID, errB := getBuildID(op)
if errB != nil {
return "", nil, sErrors.NewErrorWithStatusCode(&proto.ActionableErr{
ErrCode: proto.StatusCode_BUILD_GCB_GET_BUILD_ID_ERR,
Message: err.Error(),
})
}
return remoteID, cbclient.Projects.Builds.Get(projectID, remoteID).Do, nil
}

var location string

if b.Region != "" {
location = fmt.Sprintf("projects/%s/locations/%s", projectID, b.Region)
}
if b.WorkerPool != "" {
location = strings.Split(b.WorkerPool, "/workerPools/")[0]
}
log.Entry(ctx).Debugf("location: %s", location)
// location should match the format "projects/{project}/locations/{location}"
op, err = cbclient.Projects.Locations.Builds.Create(location, &buildSpec).Context(ctx).Do()
if err != nil {
return "", nil, sErrors.NewErrorWithStatusCode(&proto.ActionableErr{
ErrCode: proto.StatusCode_BUILD_GCB_CREATE_BUILD_ERR,
Message: fmt.Sprintf("error creating build: %s", err),
})
}
remoteID, err := getBuildID(op)
if err != nil {
return "", nil, sErrors.NewErrorWithStatusCode(&proto.ActionableErr{
ErrCode: proto.StatusCode_BUILD_GCB_GET_BUILD_ID_ERR,
Message: err.Error(),
})
}
// build id should match the format "projects/{project}/locations/{location}/builds/{buildID}"
buildID := fmt.Sprintf("%s/builds/%s", location, remoteID)
return remoteID, cbclient.Projects.Locations.Builds.Get(buildID).Do, nil
}
6 changes: 6 additions & 0 deletions pkg/skaffold/schema/latest/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -377,6 +377,12 @@ type GoogleCloudBuild struct {

// WorkerPool configures a pool of workers to run the build.
WorkerPool string `yaml:"workerPool,omitempty"`

// Region configures the region to run the build. If WorkerPool is configured, the region will
// be deduced from the WorkerPool configuration. If neither WorkerPool nor Region is configured,
// the build will be run in global(non-regional).
// See [Cloud Build locations](https://cloud.google.com/build/docs/locations).
Region string `yaml:"region,omitempty"`
}

// KanikoCache configures Kaniko caching. If a cache is specified, Kaniko will
Expand Down
4 changes: 4 additions & 0 deletions pkg/skaffold/schema/v2beta28/upgrade.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ import (

// Upgrade upgrades a configuration to the next version.
// Config changes from v2beta28 to v2beta29
// 1. Additions:
// GoogleCloudBuild.Region
// 2. Removals:
// 3. No updates
func (c *SkaffoldConfig) Upgrade() (util.VersionedConfig, error) {
var newConfig next.SkaffoldConfig
pkgutil.CloneThroughJSON(c, &newConfig)
Expand Down
15 changes: 15 additions & 0 deletions pkg/skaffold/schema/validation/validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ var (
validateYamltags = yamltags.ValidateStruct
DefaultConfig = Options{CheckDeploySource: true}
dependencyAliasPattern = regexp.MustCompile(`^[a-zA-Z_][a-zA-Z0-9_]*$`)
gcbWorkerPoolPattern = regexp.MustCompile(`projects/[^\/]*/locations/[^\/]*/workerPools/[^\/]*`)
)

type Options struct {
Expand Down Expand Up @@ -83,6 +84,7 @@ func ProcessToErrorWithLocation(configs parser.SkaffoldConfigSet, validateConfig
errs = append(errs, validateArtifactTypes(config, config.Build)...)
errs = append(errs, validateTaggingPolicy(config, config.Build)...)
errs = append(errs, validateCustomTest(config, config.Test)...)
errs = append(errs, validateGCBConfig(config, config.Build)...)
}
errs = append(errs, validateArtifactDependencies(configs)...)
if validateConfig.CheckDeploySource {
Expand Down Expand Up @@ -629,6 +631,19 @@ func validateArtifactTypes(cfg *parser.SkaffoldConfigEntry, bc latest.BuildConfi
return cfgErrs
}

// validateGCBConfig checks if GCB config is valid.
func validateGCBConfig(cfg *parser.SkaffoldConfigEntry, bc latest.BuildConfig) (cfgErrs []ErrorWithLocation) {
if bc.GoogleCloudBuild != nil && bc.GoogleCloudBuild.WorkerPool != "" {
if !gcbWorkerPoolPattern.MatchString(bc.GoogleCloudBuild.WorkerPool) {
cfgErrs = append(cfgErrs, ErrorWithLocation{
Error: fmt.Errorf("invalid value for worker pool. Must match pattern projects/{project}/locations/{location}/workerPools/{worker_pool}"),
Location: cfg.YAMLInfos.Locate(&cfg.Build.GoogleCloudBuild.WorkerPool),
})
}
}
return cfgErrs
}

// validateLogPrefix checks that logs are configured with a valid prefix.
func validateLogPrefix(cfg *parser.SkaffoldConfigEntry, lc latest.LogsConfig) []ErrorWithLocation {
validPrefixes := []string{"", "auto", "container", "podAndContainer", "none"}
Expand Down