Skip to content

Commit

Permalink
chore: new googleCloudBuildRepoV2 field to configure a remote depende…
Browse files Browse the repository at this point in the history
…ncy (#9293)

* chore: new googleCloudBuildRepoV2 field to configure a remote dependency

* chore: change in gitutil logic to support the gcb repository scenario

* test: integration tests with gcb remote dependency

* fix: add random tag to BuildDeploy test to avoid collisions
  • Loading branch information
renzodavid9 authored Feb 7, 2024
1 parent 1f652f0 commit 0a1f317
Show file tree
Hide file tree
Showing 8 changed files with 349 additions and 49 deletions.
64 changes: 64 additions & 0 deletions docs-v2/content/en/schemas/v4beta10.json
Original file line number Diff line number Diff line change
Expand Up @@ -1319,6 +1319,11 @@
"description": "describes a remote git repository containing the required configs.",
"x-intellij-html-description": "describes a remote git repository containing the required configs."
},
"googleCloudBuildRepoV2": {
"$ref": "#/definitions/GoogleCloudBuildRepoV2Info",
"description": "describes a [Google Cloud Build repository (2nd gen)](https://cloud.google.com/build/docs/repositories#repositories_2nd_gen) that points to a repo with the required configs.",
"x-intellij-html-description": "describes a <a href=\"https://cloud.google.com/build/docs/repositories#repositories_2nd_gen\">Google Cloud Build repository (2nd gen)</a> that points to a repo with the required configs."
},
"googleCloudStorage": {
"$ref": "#/definitions/GoogleCloudStorageInfo",
"description": "describes remote Google Cloud Storage objects containing the required configs.",
Expand All @@ -1335,6 +1340,7 @@
"path",
"git",
"googleCloudStorage",
"googleCloudBuildRepoV2",
"activeProfiles"
],
"additionalProperties": false,
Expand Down Expand Up @@ -2133,6 +2139,64 @@
"description": "*beta* describes how to do a remote build on [Google Cloud Build](https://cloud.google.com/cloud-build/docs/). Docker and Jib artifacts can be built on Cloud Build. The `projectId` needs to be provided and the currently logged in user should be given permissions to trigger new builds.",
"x-intellij-html-description": "<em>beta</em> describes how to do a remote build on <a href=\"https://cloud.google.com/cloud-build/docs/\">Google Cloud Build</a>. Docker and Jib artifacts can be built on Cloud Build. The <code>projectId</code> needs to be provided and the currently logged in user should be given permissions to trigger new builds."
},
"GoogleCloudBuildRepoV2Info": {
"required": [
"projectID",
"region",
"connection",
"repo"
],
"properties": {
"connection": {
"type": "string",
"description": "name of the GCB repository connection associated with the repo.",
"x-intellij-html-description": "name of the GCB repository connection associated with the repo."
},
"path": {
"type": "string",
"description": "relative path from the repo root to the skaffold configuration file. eg. `getting-started/skaffold.yaml`.",
"x-intellij-html-description": "relative path from the repo root to the skaffold configuration file. eg. <code>getting-started/skaffold.yaml</code>."
},
"projectID": {
"type": "string",
"description": "ID of the GCP project where the repository is configured.",
"x-intellij-html-description": "ID of the GCP project where the repository is configured."
},
"ref": {
"type": "string",
"description": "git ref the repo should be cloned from. e.g. `master` or `main`.",
"x-intellij-html-description": "git ref the repo should be cloned from. e.g. <code>master</code> or <code>main</code>."
},
"region": {
"type": "string",
"description": "GCP region where the repository is configured.",
"x-intellij-html-description": "GCP region where the repository is configured."
},
"repo": {
"type": "string",
"description": "name of repository under the given connection.",
"x-intellij-html-description": "name of repository under the given connection."
},
"sync": {
"type": "boolean",
"description": "when set to `true` will reset the cached repository to the latest commit from remote on every run. To use the cached repository with uncommitted changes or unpushed commits, it needs to be set to `false`.",
"x-intellij-html-description": "when set to <code>true</code> will reset the cached repository to the latest commit from remote on every run. To use the cached repository with uncommitted changes or unpushed commits, it needs to be set to <code>false</code>."
}
},
"preferredOrder": [
"projectID",
"region",
"connection",
"repo",
"path",
"ref",
"sync"
],
"additionalProperties": false,
"type": "object",
"description": "contains information on the origin of skaffold configurations cloned from Google Cloud Build repository (2nd gen).",
"x-intellij-html-description": "contains information on the origin of skaffold configurations cloned from Google Cloud Build repository (2nd gen)."
},
"GoogleCloudStorageInfo": {
"properties": {
"path": {
Expand Down
4 changes: 3 additions & 1 deletion integration/deploy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ import (
"strings"
"testing"

"github.com/google/uuid"

"github.com/GoogleContainerTools/skaffold/v2/cmd/skaffold/app/flags"
"github.com/GoogleContainerTools/skaffold/v2/integration/skaffold"
"github.com/GoogleContainerTools/skaffold/v2/pkg/skaffold/util"
Expand All @@ -38,7 +40,7 @@ func TestBuildDeploy(t *testing.T) {

ns, client := SetupNamespace(t)

outputBytes := skaffold.Build("--quiet", "--platform=linux/arm64,linux/amd64").InDir("examples/nodejs").InNs(ns.Name).RunOrFailOutput(t)
outputBytes := skaffold.Build("--quiet", "--platform=linux/arm64,linux/amd64", "--cache-artifacts=false", "--tag", uuid.New().String()).InDir("examples/nodejs").InNs(ns.Name).RunOrFailOutput(t)
// Parse the Build Output
buildArtifacts, err := flags.ParseBuildOutput(outputBytes)
failNowIfError(t, err)
Expand Down
122 changes: 122 additions & 0 deletions integration/remote_config_dependency_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
/*
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 integration

import (
"testing"

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

func TestRenderWithGCBRepositoryRemoteDependency(t *testing.T) {
tests := []struct {
description string
configFile string
shouldErr bool
expectedOutput string
expectedErrMsg string
}{
{
description: "GCB repository remote dependency with private git repo",
configFile: `apiVersion: skaffold/v4beta10
kind: Config
requires:
- googleCloudBuildRepoV2:
projectID: k8s-skaffold
region: us-central1
connection: github-connection-e2e-tests
repo: skaffold-getting-started
`,
expectedOutput: `apiVersion: v1
kind: Pod
metadata:
name: getting-started
spec:
containers:
- image: skaffold-example:fixed
name: getting-started
`,
},
{
description: "GCB repository remote dependency with private git repo, pointing to an specific branch",
configFile: `apiVersion: skaffold/v4beta10
kind: Config
requires:
- googleCloudBuildRepoV2:
projectID: k8s-skaffold
region: us-central1
connection: github-connection-e2e-tests
repo: skaffold-getting-started
ref: feature-branch
`,
expectedOutput: `apiVersion: apps/v1
kind: Deployment
metadata:
name: my-deployment
labels:
app: my-deployment
spec:
replicas: 1
selector:
matchLabels:
app: my-deployment
template:
metadata:
labels:
app: my-deployment
spec:
containers:
- name: getting-started
image: skaffold-example-deployment:fixed
`,
},
{
description: "GCB repository remote dependency with private git repo fails, bad configuration",
configFile: `apiVersion: skaffold/v4beta10
kind: Config
requires:
- googleCloudBuildRepoV2:
projectID: bad-repo
region: us-central1
connection: github-connection-e2e-tests
repo: skaffold-getting-started
ref: feature-branch
`,
shouldErr: true,
expectedErrMsg: "getting GCB repo info for skaffold-getting-started: failed to get remote URI for repository skaffold-getting-started",
},
}

for _, test := range tests {
testutil.Run(t, test.description, func(t *testutil.T) {
MarkIntegrationTest(t.T, NeedsGcp)
tmpDir := t.NewTempDir()
tmpDir.Write("skaffold.yaml", test.configFile)
args := []string{"--remote-cache-dir", tmpDir.Root(), "--tag", "fixed", "--default-repo=", "--digest-source", "tag"}
output, err := skaffold.Render(args...).InDir(tmpDir.Root()).RunWithCombinedOutput(t.T)

t.CheckError(test.shouldErr, err)

if !test.shouldErr {
t.CheckDeepEqual(test.expectedOutput, string(output), testutil.YamlObj(t.T))
} else {
t.CheckContains(test.expectedErrMsg, string(output))
}
})
}
}
3 changes: 1 addition & 2 deletions pkg/skaffold/git/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,11 @@ import (
"fmt"

sErrors "github.com/GoogleContainerTools/skaffold/v2/pkg/skaffold/errors"
"github.com/GoogleContainerTools/skaffold/v2/pkg/skaffold/schema/latest"
"github.com/GoogleContainerTools/skaffold/v2/proto/v1"
)

// SyncDisabledErr returns error when git repository sync is turned off by the user but the repository clone doesn't exist inside the cache directory.
func SyncDisabledErr(g latest.GitInfo, repoCacheDir string) error {
func SyncDisabledErr(g Config, repoCacheDir string) error {
msg := fmt.Sprintf("cache directory %q for repository %q at ref %q does not exist, and repository sync is explicitly disabled via flag `--sync-remote-cache`", repoCacheDir, g.Repo, g.Ref)
return sErrors.NewError(fmt.Errorf(msg),
&proto.ActionableErr{
Expand Down
47 changes: 37 additions & 10 deletions pkg/skaffold/git/gitutil.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,24 +28,31 @@ import (
"strings"

"github.com/GoogleContainerTools/skaffold/v2/pkg/skaffold/config"
"github.com/GoogleContainerTools/skaffold/v2/pkg/skaffold/schema/latest"
"github.com/GoogleContainerTools/skaffold/v2/pkg/skaffold/output/log"
"github.com/GoogleContainerTools/skaffold/v2/pkg/skaffold/util"
)

// SyncRepo syncs the target git repository with skaffold's local cache and returns the path to the repository root directory.
var SyncRepo = syncRepo
var findGit = func() (string, error) { return exec.LookPath("git") }

type Config struct {
Repo string
RepoCloneURI string
Ref string
Sync *bool
}

// defaultRef returns the default ref as "master" if master branch exists in
// remote repository, falls back to "main" if master branch doesn't exist
func defaultRef(ctx context.Context, repo string) (string, error) {
func defaultRef(ctx context.Context, repoCloneURI, repo string) (string, error) {
masterRef := "master"
mainRef := "main"
masterExists, err := branchExists(ctx, repo, masterRef)
masterExists, err := branchExists(ctx, repoCloneURI, repo, masterRef)
if err != nil {
return "", err
}
mainExists, err := branchExists(ctx, repo, mainRef)
mainExists, err := branchExists(ctx, repoCloneURI, repo, mainRef)
if err != nil {
return "", err
}
Expand All @@ -58,12 +65,12 @@ func defaultRef(ctx context.Context, repo string) (string, error) {
}

// BranchExists checks if branch is present in the input repo
func branchExists(ctx context.Context, repo, branch string) (bool, error) {
func branchExists(ctx context.Context, repoCloneURI, repo, branch string) (bool, error) {
gitProgram, err := findGit()
if err != nil {
return false, err
}
out, err := util.RunCmdOut(ctx, exec.Command(gitProgram, "ls-remote", "--heads", repo, branch))
out, err := util.RunCmdOut(ctx, exec.Command(gitProgram, "ls-remote", "--heads", repoCloneURI, branch))
if err != nil {
// stdErr contains the error message for os related errors, git permission errors
// and if repo doesn't exist
Expand All @@ -78,7 +85,7 @@ func branchExists(ctx context.Context, repo, branch string) (bool, error) {
}

// getRepoDir returns the cache directory name for a remote repo
func getRepoDir(g latest.GitInfo) (string, error) {
func getRepoDir(g Config) (string, error) {
inputs := []string{g.Repo, g.Ref}
hasher := sha256.New()
enc := json.NewEncoder(hasher)
Expand All @@ -89,7 +96,7 @@ func getRepoDir(g latest.GitInfo) (string, error) {
return base64.URLEncoding.EncodeToString(hasher.Sum(nil))[:32], nil
}

func syncRepo(ctx context.Context, g latest.GitInfo, opts config.SkaffoldOptions) (string, error) {
func syncRepo(ctx context.Context, g Config, opts config.SkaffoldOptions) (string, error) {
skaffoldCacheDir, err := config.GetRemoteCacheDir(opts)
r := gitCmd{Dir: skaffoldCacheDir}
if err != nil {
Expand All @@ -102,7 +109,7 @@ func syncRepo(ctx context.Context, g latest.GitInfo, opts config.SkaffoldOptions

ref := g.Ref
if ref == "" {
ref, err = defaultRef(ctx, g.Repo)
ref, err = defaultRef(ctx, g.RepoCloneURI, g.Repo)
if err != nil {
return "", fmt.Errorf("failed to clone repo %s: trouble getting default branch: %w", g.Repo, err)
}
Expand All @@ -117,7 +124,7 @@ func syncRepo(ctx context.Context, g latest.GitInfo, opts config.SkaffoldOptions
if opts.SyncRemoteCache.CloneDisabled() {
return "", SyncDisabledErr(g, repoCacheDir)
}
if _, err := r.Run(ctx, "clone", g.Repo, fmt.Sprintf("./%s", hash), "--branch", ref, "--depth", "1"); err != nil {
if _, err := r.Run(ctx, "clone", g.RepoCloneURI, fmt.Sprintf("./%s", hash), "--branch", ref, "--depth", "1"); err != nil {
return "", fmt.Errorf("failed to clone repo: %w", err)
}
} else {
Expand All @@ -139,6 +146,8 @@ func syncRepo(ctx context.Context, g latest.GitInfo, opts config.SkaffoldOptions
return repoCacheDir, nil
}

tryUpdateRemoteOriginFetchURL(ctx, r, g.RepoCloneURI)

if _, err = r.Run(ctx, "fetch", "origin", ref); err != nil {
return "", fmt.Errorf("failed to clone repo %s: unable to find any matching refs %s; run 'git clone <REPO>; stat <DIR/SUBDIR>' to verify credentials: %w", g.Repo, ref, err)
}
Expand Down Expand Up @@ -169,3 +178,21 @@ func (g *gitCmd) Run(ctx context.Context, args ...string) ([]byte, error) {
cmd.Dir = g.Dir
return util.RunCmdOut(ctx, cmd)
}

func tryUpdateRemoteOriginFetchURL(ctx context.Context, r gitCmd, newFetchURI string) {
output, err := r.Run(ctx, "remote", "get-url", "origin")
if err != nil {
log.Entry(ctx).Debugf("failed to get remote origin fetch URI: %v", err)
return
}

currentFetchURI := strings.TrimSpace(string(output))
if currentFetchURI == newFetchURI {
return
}

_, err = r.Run(ctx, "remote", "set-url", "origin", newFetchURI, currentFetchURI)
if err != nil {
log.Entry(ctx).Debugf("failed to update remote origin fetch URI: %v", err)
}
}
Loading

0 comments on commit 0a1f317

Please sign in to comment.