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: new repo resolver logic to fetch info from a gcbrepov2 #9283

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
38 changes: 20 additions & 18 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,10 @@ exclude github.com/karrick/godirwalk v1.17.0

require (
4d63.com/tz v1.2.0
cloud.google.com/go/monitoring v1.13.0
cloud.google.com/go/cloudbuild v1.15.0
cloud.google.com/go/monitoring v1.16.1
cloud.google.com/go/profiler v0.1.0
cloud.google.com/go/storage v1.29.0
cloud.google.com/go/storage v1.30.1
github.com/AlecAivazis/survey/v2 v2.2.15
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.35.1
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/trace v1.11.1
Expand All @@ -40,14 +41,15 @@ require (
github.com/evanphx/json-patch v4.12.0+incompatible
github.com/fatih/semgroup v1.2.0
github.com/go-git/go-git/v5 v5.11.0
github.com/golang/glog v1.1.0
github.com/golang/glog v1.1.2
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da
github.com/golang/protobuf v1.5.3
github.com/google/go-cmp v0.6.0
github.com/google/go-containerregistry v0.16.1
github.com/google/go-github v17.0.0+incompatible
github.com/google/ko v0.14.0
github.com/google/uuid v1.3.0
github.com/google/uuid v1.4.0
github.com/googleapis/gax-go/v2 v2.12.0
github.com/grpc-ecosystem/grpc-gateway/v2 v2.11.3
github.com/hashicorp/hcl v1.0.0
github.com/heroku/color v0.0.6
Expand Down Expand Up @@ -89,15 +91,15 @@ require (
go.opentelemetry.io/otel/sdk/metric v0.36.0
go.opentelemetry.io/otel/trace v1.15.0
golang.org/x/crypto v0.17.0
golang.org/x/oauth2 v0.11.0
golang.org/x/sync v0.3.0
golang.org/x/oauth2 v0.13.0
golang.org/x/sync v0.4.0
golang.org/x/sys v0.15.0
golang.org/x/term v0.15.0
golang.org/x/tools v0.13.0
google.golang.org/api v0.126.0
google.golang.org/genproto v0.0.0-20230530153820-e85fd2cbaebc
google.golang.org/genproto/googleapis/api v0.0.0-20230530153820-e85fd2cbaebc
google.golang.org/grpc v1.56.3
google.golang.org/api v0.149.0
google.golang.org/genproto v0.0.0-20231016165738-49dd2c1f3d0b
google.golang.org/genproto/googleapis/api v0.0.0-20231016165738-49dd2c1f3d0b
google.golang.org/grpc v1.59.0
google.golang.org/protobuf v1.31.0
gopkg.in/go-jose/go-jose.v2 v2.6.1
gopkg.in/yaml.v2 v2.4.0
Expand All @@ -115,11 +117,12 @@ require (

require (
4d63.com/embedfiles v0.0.0-20190311033909-995e0740726f // indirect
cloud.google.com/go v0.110.2 // indirect
cloud.google.com/go/compute v1.20.1 // indirect
cloud.google.com/go v0.110.8 // indirect
cloud.google.com/go/compute v1.23.1 // indirect
cloud.google.com/go/compute/metadata v0.2.3 // indirect
cloud.google.com/go/iam v0.13.0 // indirect
cloud.google.com/go/trace v1.9.0 // indirect
cloud.google.com/go/iam v1.1.3 // indirect
cloud.google.com/go/longrunning v0.5.2 // indirect
cloud.google.com/go/trace v1.10.2 // indirect
dario.cat/mergo v1.0.0 // indirect
github.com/Azure/azure-sdk-for-go v68.0.0+incompatible // indirect
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect
Expand Down Expand Up @@ -202,10 +205,9 @@ require (
github.com/google/go-querystring v1.1.0 // indirect
github.com/google/gofuzz v1.2.0 // indirect
github.com/google/pprof v0.0.0-20210804190019-f964ff605595 // indirect
github.com/google/s2a-go v0.1.4 // indirect
github.com/google/s2a-go v0.1.7 // indirect
github.com/google/safetext v0.0.0-20230106111101-7156a760e523 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.2.3 // indirect
github.com/googleapis/gax-go/v2 v2.11.0 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect
github.com/gorilla/mux v1.8.0 // indirect
github.com/huandu/xstrings v1.4.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
Expand Down Expand Up @@ -281,7 +283,7 @@ require (
golang.org/x/time v0.3.0 // indirect
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20230530153820-e85fd2cbaebc // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20231016165738-49dd2c1f3d0b // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/warnings.v0 v0.1.2 // indirect
Expand Down
87 changes: 41 additions & 46 deletions go.sum

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion hack/boilerplate/boilerplate.py
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ def get_regexs():
# Search for "YEAR" which exists in the boilerplate, but shouldn't in the real thing
regexs["year"] = re.compile( 'YEAR' )
# dates can be 2018, company holder names can be anything
regexs["date"] = re.compile( '(2019|2020|2021|2022|2023)' )
regexs["date"] = re.compile( '(2019|2020|2021|2022|2023|2024)' )
# strip // +build \n\n build constraints
regexs["go_build_constraints"] = re.compile(r"^(// \+build.*\n)+\n", re.MULTILINE)
# strip //go:build \n build constraints (for go1.17 and higher)
Expand Down
108 changes: 108 additions & 0 deletions pkg/skaffold/gcbreposv2/repo_resolver.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
/*
Copyright 2024 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 gcbreposv2

import (
"context"
"fmt"
"net/url"

cloudbuild "cloud.google.com/go/cloudbuild/apiv2"
cloudbuildpb "cloud.google.com/go/cloudbuild/apiv2/cloudbuildpb"
"github.com/googleapis/gax-go/v2"
)

type cloudBuildRepoClient interface {
GetRepository(ctx context.Context, req *cloudbuildpb.GetRepositoryRequest, opts ...gax.CallOption) (*cloudbuildpb.Repository, error)
FetchReadToken(ctx context.Context, req *cloudbuildpb.FetchReadTokenRequest, opts ...gax.CallOption) (*cloudbuildpb.FetchReadTokenResponse, error)
Close() error
}

type Repo struct {
// Original repo URI.
URI string

// URI with oauth2 format.
CloneURI string
}

var RepositoryManagerClient = repositoryManagerClient

func GetRepoInfo(ctx context.Context, gcpProject, gcpRegion, gcpConnectionName, gcpRepoName string) (Repo, error) {
cbRepoRef := fmt.Sprintf("projects/%v/locations/%v/connections/%v/repositories/%v", gcpProject, gcpRegion, gcpConnectionName, gcpRepoName)
cbClient, err := RepositoryManagerClient(ctx)
if err != nil {
return Repo{}, fmt.Errorf("failed to create repository manager client: %w", err)
}
defer cbClient.Close()

repoURI, err := getRepoURI(ctx, cbClient, cbRepoRef)
if err != nil {
return Repo{}, fmt.Errorf("failed to get remote URI for repository %v: %w", gcpRepoName, err)
}

readAccessToken, err := getRepoReadAccessToken(ctx, cbClient, cbRepoRef)
if err != nil {
return Repo{}, fmt.Errorf("failed to get repository read access token for repo %v: %w", gcpRepoName, err)
}

repoCloneURI, err := buildRepoURIWithToken(repoURI, readAccessToken)
if err != nil {
return Repo{}, fmt.Errorf("failed to clone repo %s: trouble building repo URI with token: %w", repoURI, err)
}

return Repo{
URI: repoURI,
CloneURI: repoCloneURI,
}, nil
}

func repositoryManagerClient(ctx context.Context) (cloudBuildRepoClient, error) {
return cloudbuild.NewRepositoryManagerClient(ctx)
}

func getRepoURI(ctx context.Context, cbClient cloudBuildRepoClient, cbRepoRef string) (string, error) {
req := &cloudbuildpb.GetRepositoryRequest{
Name: cbRepoRef,
}
repoInfo, err := cbClient.GetRepository(ctx, req)
if err != nil {
return "", err
}
return repoInfo.GetRemoteUri(), nil
}

func getRepoReadAccessToken(ctx context.Context, cbClient cloudBuildRepoClient, cbRepoRef string) (string, error) {
req := &cloudbuildpb.FetchReadTokenRequest{
Repository: cbRepoRef,
}
resp, err := cbClient.FetchReadToken(ctx, req)
if err != nil {
return "", err
}
return resp.GetToken(), nil
}

func buildRepoURIWithToken(repoURI, readAccessToken string) (string, error) {
parsed, err := url.Parse(repoURI)
if err != nil {
return "", err
}

parsed.Host = fmt.Sprintf("oauth2:%v@%v", readAccessToken, parsed.Host)
return url.PathUnescape(parsed.String())
}
141 changes: 141 additions & 0 deletions pkg/skaffold/gcbreposv2/repo_resolver_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
/*
Copyright 2024 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 gcbreposv2

import (
"context"
"fmt"
"testing"

"cloud.google.com/go/cloudbuild/apiv2/cloudbuildpb"
"github.com/googleapis/gax-go/v2"

"github.com/GoogleContainerTools/skaffold/v2/testutil"
)

type GCBReposClientMock struct {
GitRepo string
ReadToken string
ErrorGetRepo bool
ErrorGetToken bool
}

const (
gcpProject = "my-project"
gcpRegion = "us-central1"
gcbConnection = "gcb-repo-connection"
gcbRepo = "repo-1"
)

func (c GCBReposClientMock) GetRepository(ctx context.Context, req *cloudbuildpb.GetRepositoryRequest, opts ...gax.CallOption) (*cloudbuildpb.Repository, error) {
if c.ErrorGetRepo {
return nil, fmt.Errorf("failed to get repo")
}

validRepoIdentifier := fmt.Sprintf("projects/%v/locations/%v/connections/%v/repositories/%v", gcpProject, gcpRegion, gcbConnection, gcbRepo)
if req.Name != validRepoIdentifier {
return nil, fmt.Errorf("invalid request, expecting %v, got %v", validRepoIdentifier, req.Name)
}

return &cloudbuildpb.Repository{
RemoteUri: c.GitRepo,
}, nil
}

func (c GCBReposClientMock) FetchReadToken(ctx context.Context, req *cloudbuildpb.FetchReadTokenRequest, opts ...gax.CallOption) (*cloudbuildpb.FetchReadTokenResponse, error) {
if c.ErrorGetToken {
return nil, fmt.Errorf("failed to get token")
}

validRepoIdentifier := fmt.Sprintf("projects/%v/locations/%v/connections/%v/repositories/%v", gcpProject, gcpRegion, gcbConnection, gcbRepo)
if req.Repository != validRepoIdentifier {
return nil, fmt.Errorf("invalid request, expecting %v, got %v", validRepoIdentifier, req.Repository)
}

return &cloudbuildpb.FetchReadTokenResponse{
Token: c.ReadToken,
}, nil
}

func (c GCBReposClientMock) Close() error { return nil }

func TestGetRepoInfo(t *testing.T) {
tests := []struct {
description string
expectedRepoInfo Repo
gcbMockClient GCBReposClientMock
shouldError bool
errorMsg string
}{
{
description: "repo info correct",
expectedRepoInfo: Repo{
URI: "https://github.com/GoogleContainerTools/skaffold",
CloneURI: "https://oauth2:token123@github.com/GoogleContainerTools/skaffold",
},
gcbMockClient: GCBReposClientMock{
GitRepo: "https://github.com/GoogleContainerTools/skaffold",
ReadToken: "token123",
},
},
{
description: "failed getting GCB repo info",
expectedRepoInfo: Repo{},
shouldError: true,
errorMsg: fmt.Sprintf("failed to get remote URI for repository %v", gcbRepo),
gcbMockClient: GCBReposClientMock{
ErrorGetRepo: true,
},
},
{
description: "failed getting GCB repo read token",
expectedRepoInfo: Repo{},
shouldError: true,
errorMsg: fmt.Sprintf("failed to get repository read access token for repo %v", gcbRepo),
gcbMockClient: GCBReposClientMock{
ErrorGetToken: true,
},
},
{
description: "failed to build clone repo URI",
expectedRepoInfo: Repo{},
shouldError: true,
errorMsg: "failed to clone repo :not-valid: trouble building repo URI with token",
gcbMockClient: GCBReposClientMock{
GitRepo: ":not-valid",
ReadToken: "token123",
},
},
}

for _, test := range tests {
testutil.Run(t, test.description, func(t *testutil.T) {
ctx := context.Background()
t.Override(&RepositoryManagerClient, func(ctx context.Context) (cloudBuildRepoClient, error) {
return test.gcbMockClient, nil
})

repoInfo, err := GetRepoInfo(ctx, gcpProject, gcpRegion, gcbConnection, gcbRepo)

t.CheckError(test.shouldError, err)
if test.shouldError {
t.CheckErrorContains(test.errorMsg, err)
}
t.CheckDeepEqual(test.expectedRepoInfo, repoInfo)
})
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.