Skip to content
Open
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
5 changes: 5 additions & 0 deletions pkg/controller/git/mock_repo.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ type MockRepo struct {
RemoteBranchExistsFn func(branch string) (bool, error)
ResetHardFn func() error
URLFn func() string
UpdateSubmodulesFn func() error
}

func (m *MockRepo) AddAll() error {
Expand Down Expand Up @@ -146,3 +147,7 @@ func (m *MockRepo) ResetHard() error {
func (m *MockRepo) URL() string {
return m.URLFn()
}

func (m *MockRepo) UpdateSubmodules() error {
return m.UpdateSubmodulesFn()
}
9 changes: 9 additions & 0 deletions pkg/controller/git/work_tree.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,8 @@ type WorkTree interface {
ResetHard() error
// URL returns the remote URL of the repository.
URL() string
// UpdateSubmodules updates the submodules in the working tree.
UpdateSubmodules() error
}

// workTree is an implementation of the WorkTree interface for interacting with
Expand Down Expand Up @@ -634,3 +636,10 @@ func (w *workTree) ResetHard() error {
}
return nil
}

func (w *workTree) UpdateSubmodules() error {
if _, err := libExec.Exec(w.buildGitCommand("submodule", "update", "--init", "--recursive")); err != nil {
return fmt.Errorf("error updating submodules: %w", err)
}
return nil
}
7 changes: 7 additions & 0 deletions pkg/promotion/runner/builtin/git_cloner.go
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,13 @@ func (g *gitCloner) run(
checkout.Path, cfg.RepoURL, err,
)
}
if cfg.RecurseSubmodules {
err = worktree.UpdateSubmodules()
if err != nil {
return promotion.StepResult{Status: kargoapi.PromotionStepStatusErrored},
fmt.Errorf("error updating submodules for worktree at %s: %w", path, err)
}
}
key := checkout.Path
if checkout.As != "" {
key = checkout.As
Expand Down
73 changes: 73 additions & 0 deletions pkg/promotion/runner/builtin/git_cloner_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"fmt"
"net/http/httptest"
"os"
"os/exec"
"path/filepath"
"testing"

Expand Down Expand Up @@ -393,3 +394,75 @@ func Test_gitCloner_run(t *testing.T) {
res.Output["commits"],
)
}

func Test_gitCloner_run_with_submodules(t *testing.T) {
// Set up a test Git server in-process
service := gitkit.New(
gitkit.Config{
Dir: t.TempDir(),
AutoCreate: true,
},
)
require.NoError(t, service.Setup())
server := httptest.NewServer(service)
defer server.Close()

// Create submodule remote repo and push a file
subRepoURL := fmt.Sprintf("%s/sub.git", server.URL)
subRepo, err := git.Clone(subRepoURL, nil, nil)
require.NoError(t, err)
defer subRepo.Close()
err = os.WriteFile(filepath.Join(subRepo.Dir(), "sub.txt"), []byte("sub"), 0600)
require.NoError(t, err)
err = subRepo.AddAllAndCommit("Initial commit sub", nil)
require.NoError(t, err)
err = subRepo.Push(nil)
require.NoError(t, err)

// Create main repo and add the submodule
mainRepoURL := fmt.Sprintf("%s/main.git", server.URL)
mainRepo, err := git.Clone(mainRepoURL, nil, nil)
require.NoError(t, err)
defer mainRepo.Close()

// Use git submodule add to create proper submodule metadata
cmd := exec.Command("git", "submodule", "add", subRepoURL, "sub")
cmd.Dir = mainRepo.Dir()
out, err := cmd.CombinedOutput()
require.NoErrorf(t, err, "git submodule add failed: %s", string(out))

// Commit and push the submodule addition
err = mainRepo.AddAllAndCommit("Add submodule", nil)
require.NoError(t, err)
err = mainRepo.Push(nil)
require.NoError(t, err)

mainCommitID, err := mainRepo.LastCommitID()
require.NoError(t, err)

// Run git-cloner with recurseSubmodules = true
r := newGitCloner(promotion.StepRunnerCapabilities{
CredsDB: &credentials.FakeDB{},
})
runner, ok := r.(*gitCloner)
require.True(t, ok)

stepCtx := &promotion.StepContext{
WorkDir: t.TempDir(),
}

res, err := runner.run(
context.Background(),
stepCtx,
builtin.GitCloneConfig{
RepoURL: mainRepoURL,
Checkout: []builtin.Checkout{{Commit: mainCommitID, Path: "src"}},
RecurseSubmodules: true,
},
)
require.NoError(t, err)
require.Equal(t, kargoapi.PromotionStepStatusSucceeded, res.Status)

// Assert submodule file was populated inside worktree
require.FileExists(t, filepath.Join(stepCtx.WorkDir, "src", "sub", "sub.txt"))
}
4 changes: 4 additions & 0 deletions pkg/promotion/runner/builtin/schemas/git-clone-config.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@
"type": "boolean",
"description": "Indicates whether to skip TLS verification when cloning the repository. Default is false."
},
"recurseSubmodules": {
"type": "boolean",
"description": "Indicates whether to recursively clone submodules. Default is false. Note that any provided credentials must also be valid for the submodules."
},
"repoURL": {
"type": "string",
"description": "The URL of a remote Git repository to clone. Required.",
Expand Down
3 changes: 3 additions & 0 deletions pkg/x/promotion/runner/builtin/zz_config_types.go

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

4 changes: 4 additions & 0 deletions ui/src/gen/directives/git-clone-config.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@
"type": "boolean",
"description": "Indicates whether to skip TLS verification when cloning the repository. Default is false."
},
"recurseSubmodules": {
"type": "boolean",
"description": "Indicates whether to recursively clone submodules. Default is false. Note that any provided credentials must also be valid for the submodules."
},
"repoURL": {
"type": "string",
"description": "The URL of a remote Git repository to clone. Required.",
Expand Down