diff --git a/pkg/skaffold/build/container_builder.go b/pkg/skaffold/build/container_builder.go index 11eb7f021a4..c3c17edf784 100644 --- a/pkg/skaffold/build/container_builder.go +++ b/pkg/skaffold/build/container_builder.go @@ -21,6 +21,7 @@ import ( "encoding/json" "fmt" "io" + "strings" "time" cstorage "cloud.google.com/go/storage" @@ -30,6 +31,8 @@ import ( "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/v1alpha2" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/util" "github.com/GoogleContainerTools/skaffold/pkg/skaffold/version" + "github.com/docker/distribution/reference" + "github.com/docker/docker/registry" "github.com/pkg/errors" "github.com/sirupsen/logrus" "golang.org/x/oauth2/google" @@ -116,13 +119,18 @@ func (cb *GoogleCloudBuilder) buildArtifact(ctx context.Context, out io.Writer, } logrus.Debugf("Build args: %s", buildArgs) - cbBucket := fmt.Sprintf("%s%s", cb.ProjectID, constants.GCSBucketSuffix) - buildObject := fmt.Sprintf("source/%s-%s.tar.gz", cb.ProjectID, util.RandomID()) + projectID, err := cb.guessProjectID(artifact) + if err != nil { + return "", errors.Wrap(err, "getting projectID") + } + + cbBucket := fmt.Sprintf("%s%s", projectID, constants.GCSBucketSuffix) + buildObject := fmt.Sprintf("source/%s-%s.tar.gz", projectID, util.RandomID()) - if err := cb.createBucketIfNotExists(ctx, cbBucket); err != nil { + if err := cb.createBucketIfNotExists(ctx, projectID, cbBucket); err != nil { return "", errors.Wrap(err, "creating bucket if not exists") } - if err := cb.checkBucketProjectCorrect(ctx, cbBucket); err != nil { + if err := cb.checkBucketProjectCorrect(ctx, projectID, cbBucket); err != nil { return "", errors.Wrap(err, "checking bucket is in correct project") } @@ -133,7 +141,7 @@ func (cb *GoogleCloudBuilder) buildArtifact(ctx context.Context, out io.Writer, args := append([]string{"build", "--tag", artifact.ImageName, "-f", artifact.DockerArtifact.DockerfilePath}, buildArgs...) args = append(args, ".") - call := cbclient.Projects.Builds.Create(cb.ProjectID, &cloudbuild.Build{ + call := cbclient.Projects.Builds.Create(projectID, &cloudbuild.Build{ LogsBucket: cbBucket, Source: &cloudbuild.Source{ StorageSource: &cloudbuild.StorageSource{ @@ -165,7 +173,7 @@ func (cb *GoogleCloudBuilder) buildArtifact(ctx context.Context, out io.Writer, watch: for { logrus.Debugf("current offset %d", offset) - b, err := cbclient.Projects.Builds.Get(cb.ProjectID, remoteID).Do() + b, err := cbclient.Projects.Builds.Get(projectID, remoteID).Do() if err != nil { return "", errors.Wrap(err, "getting build status") } @@ -222,6 +230,35 @@ watch: return newTag, nil } +func (cb *GoogleCloudBuilder) guessProjectID(artifact *v1alpha2.Artifact) (string, error) { + if cb.ProjectID != "" { + return cb.ProjectID, nil + } + + ref, err := reference.ParseNormalizedNamed(artifact.ImageName) + if err != nil { + return "", errors.Wrap(err, "parsing image name for registry") + } + + repoInfo, err := registry.ParseRepositoryInfo(ref) + if err != nil { + return "", err + } + + index := repoInfo.Index + if !index.Official { + switch index.Name { + case "gcr.io", "us.gcr.io", "eu.gcr.io", "asia.gcr.io", "staging-k8s.gcr.io": + parts := strings.Split(repoInfo.Name.String(), "/") + if len(parts) >= 2 { + return parts[1], nil + } + } + } + + return "", fmt.Errorf("unable to guess GCP projectID from image name [%s]", artifact.ImageName) +} + func getBuildID(op *cloudbuild.Operation) (string, error) { if op.Metadata == nil { return "", errors.New("missing Metadata in operation") @@ -268,12 +305,12 @@ func (cb *GoogleCloudBuilder) getLogs(ctx context.Context, offset int64, bucket, return r, nil } -func (cb *GoogleCloudBuilder) checkBucketProjectCorrect(ctx context.Context, bucket string) error { +func (cb *GoogleCloudBuilder) checkBucketProjectCorrect(ctx context.Context, projectID, bucket string) error { c, err := cstorage.NewClient(ctx) if err != nil { return errors.Wrap(err, "getting storage client") } - it := c.Buckets(ctx, cb.ProjectID) + it := c.Buckets(ctx, projectID) // Set the prefix to the bucket we're looking for to only return that bucket and buckets with that prefix // that we'll filter further later on it.Prefix = bucket @@ -293,7 +330,7 @@ func (cb *GoogleCloudBuilder) checkBucketProjectCorrect(ctx context.Context, buc } -func (cb *GoogleCloudBuilder) createBucketIfNotExists(ctx context.Context, bucket string) error { +func (cb *GoogleCloudBuilder) createBucketIfNotExists(ctx context.Context, projectID, bucket string) error { c, err := cstorage.NewClient(ctx) if err != nil { return errors.Wrap(err, "getting storage client") @@ -311,11 +348,11 @@ func (cb *GoogleCloudBuilder) createBucketIfNotExists(ctx context.Context, bucke return errors.Wrapf(err, "getting bucket %s", bucket) } - if err := c.Bucket(bucket).Create(ctx, cb.ProjectID, &cstorage.BucketAttrs{ + if err := c.Bucket(bucket).Create(ctx, projectID, &cstorage.BucketAttrs{ Name: bucket, }); err != nil { return err } - logrus.Debugf("Created bucket %s in %s", bucket, cb.ProjectID) + logrus.Debugf("Created bucket %s in %s", bucket, projectID) return nil } diff --git a/pkg/skaffold/build/container_builder_test.go b/pkg/skaffold/build/container_builder_test.go new file mode 100644 index 00000000000..1e5d7d012ca --- /dev/null +++ b/pkg/skaffold/build/container_builder_test.go @@ -0,0 +1,75 @@ +/* +Copyright 2018 The Skaffold Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package build + +import ( + "testing" + + "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/v1alpha2" + "github.com/GoogleContainerTools/skaffold/testutil" +) + +func TestGuessProjectID(t *testing.T) { + var tests = []struct { + description string + config *v1alpha2.GoogleCloudBuild + artifact *v1alpha2.Artifact + expected string + shouldErr bool + }{ + { + description: "fixed projectId", + config: &v1alpha2.GoogleCloudBuild{ProjectID: "fixed"}, + artifact: &v1alpha2.Artifact{ImageName: "any"}, + expected: "fixed", + }, + { + description: "gcr.io", + config: &v1alpha2.GoogleCloudBuild{}, + artifact: &v1alpha2.Artifact{ImageName: "gcr.io/project/image"}, + expected: "project", + }, + { + description: "eu.gcr.io", + config: &v1alpha2.GoogleCloudBuild{}, + artifact: &v1alpha2.Artifact{ImageName: "gcr.io/project/image"}, + expected: "project", + }, + { + description: "docker hub", + config: &v1alpha2.GoogleCloudBuild{}, + artifact: &v1alpha2.Artifact{ImageName: "project/image"}, + shouldErr: true, + }, + { + description: "invalid GCR image", + config: &v1alpha2.GoogleCloudBuild{}, + artifact: &v1alpha2.Artifact{ImageName: "gcr.io"}, + shouldErr: true, + }, + } + + for _, test := range tests { + t.Run(test.description, func(t *testing.T) { + builder := NewGoogleCloudBuilder(test.config) + + projectID, err := builder.guessProjectID(test.artifact) + + testutil.CheckErrorAndDeepEqual(t, test.shouldErr, err, test.expected, projectID) + }) + } +}