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

Use active gcloud credentials for executing cloudbuild when available #2731

Merged
merged 1 commit into from
Aug 28, 2019
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
1 change: 1 addition & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -349,6 +349,7 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
google.golang.org/api v0.7.0 h1:9sdfJOzWlkqPltHAuzT2Cp+yrBeY1KRVYgms8soxMwM=
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
google.golang.org/api v0.9.0 h1:jbyannxz0XFD3zdjgrSUsaJbgpH4eTrkdhRChkHPfO8=
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why did we need this update?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤷‍♂️

google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.5.0 h1:KxkO13IPW4Lslp2bz+KHP2E3gtFlrIGNThxkZQ3g+4c=
Expand Down
7 changes: 6 additions & 1 deletion pkg/skaffold/build/cluster/sources/gcs.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,12 @@ func (g *GCSBucket) Setup(ctx context.Context, out io.Writer, artifact *latest.A
color.Default.Fprintln(out, "Uploading sources to", bucket, "GCS bucket")

g.tarName = fmt.Sprintf("context-%s.tar.gz", initialTag)
if err := sources.UploadToGCS(ctx, artifact, bucket, g.tarName, dependencies); err != nil {
c, err := gcp.CloudStorageClient()
if err != nil {
return "", errors.Wrap(err, "getting cloud storage client")
}
defer c.Close()
if err := sources.UploadToGCS(ctx, c, artifact, bucket, g.tarName, dependencies); err != nil {
return "", errors.Wrap(err, "uploading sources to GCS")
}

Expand Down
36 changes: 10 additions & 26 deletions pkg/skaffold/build/gcb/cloud_build.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@ import (
"github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest"
"github.com/GoogleContainerTools/skaffold/pkg/skaffold/sources"
"github.com/GoogleContainerTools/skaffold/pkg/skaffold/util"
"github.com/GoogleContainerTools/skaffold/pkg/skaffold/version"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
cloudbuild "google.golang.org/api/cloudbuild/v1"
Expand All @@ -50,13 +49,12 @@ func (b *Builder) Build(ctx context.Context, out io.Writer, tags tag.ImageTags,
}

func (b *Builder) buildArtifactWithCloudBuild(ctx context.Context, out io.Writer, artifact *latest.Artifact, tag string) (string, error) {
cbclient, err := cloudbuild.NewService(ctx)
cbclient, err := gcp.CloudBuildClient()
if err != nil {
return "", errors.Wrap(err, "getting cloudbuild client")
}
cbclient.UserAgent = version.UserAgent()

c, err := cstorage.NewClient(ctx)
c, err := gcp.CloudStorageClient()
if err != nil {
return "", errors.Wrap(err, "getting cloud storage client")
}
Expand All @@ -75,10 +73,10 @@ func (b *Builder) buildArtifactWithCloudBuild(ctx context.Context, out io.Writer
cbBucket := fmt.Sprintf("%s%s", projectID, constants.GCSBucketSuffix)
buildObject := fmt.Sprintf("source/%s-%s.tar.gz", projectID, util.RandomID())

if err := b.createBucketIfNotExists(ctx, projectID, cbBucket); err != nil {
if err := b.createBucketIfNotExists(ctx, c, projectID, cbBucket); err != nil {
return "", errors.Wrap(err, "creating bucket if not exists")
}
if err := b.checkBucketProjectCorrect(ctx, projectID, cbBucket); err != nil {
if err := b.checkBucketProjectCorrect(ctx, c, projectID, cbBucket); err != nil {
return "", errors.Wrap(err, "checking bucket is in correct project")
}

Expand All @@ -88,7 +86,7 @@ func (b *Builder) buildArtifactWithCloudBuild(ctx context.Context, out io.Writer
}

color.Default.Fprintf(out, "Pushing code to gs://%s/%s\n", cbBucket, buildObject)
if err := sources.UploadToGCS(ctx, artifact, cbBucket, buildObject, dependencies); err != nil {
if err := sources.UploadToGCS(ctx, c, artifact, cbBucket, buildObject, dependencies); err != nil {
return "", errors.Wrap(err, "uploading source tarball")
}

Expand Down Expand Up @@ -136,7 +134,7 @@ watch:
return "", errors.Wrap(err, "getting build status")
}

r, err := b.getLogs(ctx, offset, cbBucket, logsObject)
r, err := b.getLogs(ctx, c, offset, cbBucket, logsObject)
if err != nil {
return "", errors.Wrap(err, "getting logs")
}
Expand Down Expand Up @@ -198,13 +196,7 @@ func getDigest(b *cloudbuild.Build, defaultToTag string) (string, error) {
return docker.RemoteDigest(defaultToTag, nil)
}

func (b *Builder) getLogs(ctx context.Context, offset int64, bucket, objectName string) (io.ReadCloser, error) {
c, err := cstorage.NewClient(ctx)
if err != nil {
return nil, errors.Wrap(err, "getting storage client")
}
defer c.Close()

func (b *Builder) getLogs(ctx context.Context, c *cstorage.Client, offset int64, bucket, objectName string) (io.ReadCloser, error) {
r, err := c.Bucket(bucket).Object(objectName).NewRangeReader(ctx, offset, -1)
if err != nil {
if gerr, ok := err.(*googleapi.Error); ok {
Expand All @@ -224,11 +216,7 @@ func (b *Builder) getLogs(ctx context.Context, offset int64, bucket, objectName
return r, nil
}

func (b *Builder) checkBucketProjectCorrect(ctx context.Context, projectID, bucket string) error {
c, err := cstorage.NewClient(ctx)
if err != nil {
return errors.Wrap(err, "getting storage client")
}
func (b *Builder) checkBucketProjectCorrect(ctx context.Context, c *cstorage.Client, projectID, bucket string) error {
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
Expand All @@ -248,12 +236,8 @@ func (b *Builder) checkBucketProjectCorrect(ctx context.Context, projectID, buck
}
}

func (b *Builder) createBucketIfNotExists(ctx context.Context, projectID, bucket string) error {
c, err := cstorage.NewClient(ctx)
if err != nil {
return errors.Wrap(err, "getting storage client")
}
defer c.Close()
func (b *Builder) createBucketIfNotExists(ctx context.Context, c *cstorage.Client, projectID, bucket string) error {
var err error

_, err = c.Bucket(bucket).Attrs(ctx)

Expand Down
43 changes: 43 additions & 0 deletions pkg/skaffold/gcp/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,23 @@ limitations under the License.
package gcp

import (
"context"
"encoding/json"
"os/exec"
"strings"
"sync"

"github.com/GoogleContainerTools/skaffold/pkg/skaffold/util"
"github.com/docker/cli/cli/config/configfile"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"golang.org/x/oauth2/google"
)

var (
creds *google.Credentials
credsOnce sync.Once
credsErr error
)

// AutoConfigureGCRCredentialHelper automatically adds the `gcloud` credential helper
Expand Down Expand Up @@ -50,3 +62,34 @@ func AutoConfigureGCRCredentialHelper(cf *configfile.ConfigFile, registry string
}
cf.CredentialHelpers[registry] = "gcloud"
}

func activeUserCredentials() (*google.Credentials, error) {
credsOnce.Do(func() {
cmd := exec.Command("gcloud", "auth", "print-access-token", "--format=json")
body, err := util.RunCmdOut(cmd)
if err != nil {
credsErr = errors.Wrap(err, "retrieving gcloud access token")
return
}
jsonCreds := make(map[string]interface{})
json.Unmarshal(body, &jsonCreds)
jsonCreds["type"] = "authorized_user"
body, _ = json.Marshal(jsonCreds)

c, err := google.CredentialsFromJSON(context.Background(), body)
if err != nil {
logrus.Infof("unable to retrieve google creds: %v", err)
logrus.Infof("falling back to application default credentials")
return
}
_, err = c.TokenSource.Token()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this line might panic - if CredentialsFromJSON results in error?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good catch, added a return here

if err != nil {
logrus.Infof("unable to retrieve token: %v", err)
logrus.Infof("falling back to application default credentials")
return
}
creds = c
})

return creds, credsErr
}
76 changes: 76 additions & 0 deletions pkg/skaffold/gcp/client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/*
Copyright 2019 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 gcp

import (
"context"
"sync"

"github.com/GoogleContainerTools/skaffold/pkg/skaffold/version"

cstorage "cloud.google.com/go/storage"
"github.com/pkg/errors"
cloudbuild "google.golang.org/api/cloudbuild/v1"
"google.golang.org/api/option"
)

var (
cbclient *cloudbuild.Service
cbOnce sync.Once
cbErr error
)

// CloudBuildClient returns an authenticated client for interacting with
// the Google Cloud Build API. This client is created once and cached
// for repeated use in Skaffold.
func CloudBuildClient() (*cloudbuild.Service, error) {
cbOnce.Do(func() {
var options []option.ClientOption
var err error
creds, cErr := activeUserCredentials()
if cErr == nil && creds != nil {
options = append(options, option.WithCredentials(creds))
}

c, err := cloudbuild.NewService(context.Background(), options...)
if err != nil {
cbErr = err
return
}
c.UserAgent = version.UserAgent()
cbclient = c
})

return cbclient, cbErr
}

// CloudStorageClient returns an authenticated client for interacting with
// the Google Cloud Storage API. This client is not cached by Skaffold,
// because it needs to be closed each time it is done being used by the caller.
func CloudStorageClient() (*cstorage.Client, error) {
var options []option.ClientOption
var err error
creds, cErr := activeUserCredentials()
if cErr == nil && creds != nil {
options = append(options, option.WithCredentials(creds))
}
c, err := cstorage.NewClient(context.Background(), options...)
if err != nil {
return nil, errors.Wrap(err, "getting cloud storage client")
}
return c, nil
}
8 changes: 1 addition & 7 deletions pkg/skaffold/sources/upload.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,13 +36,7 @@ func TarGz(ctx context.Context, w io.Writer, a *latest.Artifact, dependencies []
}

// UploadToGCS uploads the artifact's sources to a GCS bucket.
func UploadToGCS(ctx context.Context, a *latest.Artifact, bucket, objectName string, dependencies []string) error {
c, err := cstorage.NewClient(ctx)
if err != nil {
return errors.Wrap(err, "creating GCS client")
}
defer c.Close()

func UploadToGCS(ctx context.Context, c *cstorage.Client, a *latest.Artifact, bucket, objectName string, dependencies []string) error {
w := c.Bucket(bucket).Object(objectName).NewWriter(ctx)
if err := TarGz(ctx, w, a, dependencies); err != nil {
return errors.Wrap(err, "uploading targz to google storage")
Expand Down