diff --git a/cmd/git-init/main.go b/cmd/git-init/main.go index 2bab57c6742..0408d0de14f 100644 --- a/cmd/git-init/main.go +++ b/cmd/git-init/main.go @@ -29,6 +29,7 @@ var ( revision = flag.String("revision", "", "The Git revision to make the repository HEAD") path = flag.String("path", "", "Path of directory under which git repository will be copied") terminationMessagePath = flag.String("terminationMessagePath", "/dev/termination-log", "Location of file containing termination message") + submodules = flag.Bool("submodules", true, "Initialize and fetch git submodules") ) func main() { @@ -39,6 +40,11 @@ func main() { if err := git.Fetch(logger, *revision, *path, *url); err != nil { logger.Fatalf("Error fetching git repository: %s", err) } + if *submodules { + if err := git.SubmoduleFetch(logger, *path); err != nil { + logger.Fatalf("Error initalizing or fetching the git submodules") + } + } commit, err := git.Commit(logger, *revision, *path) if err != nil { diff --git a/docs/resources.md b/docs/resources.md index 365a10cd21c..68ef4411702 100644 --- a/docs/resources.md +++ b/docs/resources.md @@ -252,6 +252,9 @@ Params that can be added are the following: (branch, tag, commit SHA or ref) to clone. You can use this to control what commit [or branch](#using-a-branch) is used. _If no revision is specified, the resource will default to `latest` from `master`._ +1. `submodules`: defines if the resource should initialize and + fetch the submodules, value is either `true` or `false`. _If not + specified, this will default to true_ When used as an input, the Git resource includes the exact commit fetched in the `resourceResults` section of the `taskRun`'s status object: diff --git a/pkg/apis/pipeline/v1alpha1/git_resource.go b/pkg/apis/pipeline/v1alpha1/git_resource.go index 051a728e350..ef336824106 100644 --- a/pkg/apis/pipeline/v1alpha1/git_resource.go +++ b/pkg/apis/pipeline/v1alpha1/git_resource.go @@ -39,7 +39,8 @@ type GitResource struct { // Git revision (branch, tag, commit SHA or ref) to clone. See // https://git-scm.com/docs/gitrevisions#_specifying_revisions for more // information. - Revision string `json:"revision"` + Revision string `json:"revision"` + Submodules bool `json:"submodules"` GitImage string `json:"-"` } @@ -50,9 +51,10 @@ func NewGitResource(gitImage string, r *PipelineResource) (*GitResource, error) return nil, xerrors.Errorf("GitResource: Cannot create a Git resource from a %s Pipeline Resource", r.Spec.Type) } gitResource := GitResource{ - Name: r.Name, - Type: r.Spec.Type, - GitImage: gitImage, + Name: r.Name, + Type: r.Spec.Type, + GitImage: gitImage, + Submodules: true, } for _, param := range r.Spec.Params { switch { @@ -60,6 +62,8 @@ func NewGitResource(gitImage string, r *PipelineResource) (*GitResource, error) gitResource.URL = param.Value case strings.EqualFold(param.Name, "Revision"): gitResource.Revision = param.Value + case strings.EqualFold(param.Name, "Submodules"): + gitResource.Submodules = toBool(param.Value, true) } } // default revision to master if nothing is provided @@ -69,6 +73,17 @@ func NewGitResource(gitImage string, r *PipelineResource) (*GitResource, error) return &gitResource, nil } +func toBool(s string, d bool) bool { + switch s { + case "true": + return true + case "false": + return false + default: + return d + } +} + // GetName returns the name of the resource func (s GitResource) GetName() string { return s.Name diff --git a/pkg/apis/pipeline/v1alpha1/git_resource_test.go b/pkg/apis/pipeline/v1alpha1/git_resource_test.go index 5f3a72a16cd..50942588464 100644 --- a/pkg/apis/pipeline/v1alpha1/git_resource_test.go +++ b/pkg/apis/pipeline/v1alpha1/git_resource_test.go @@ -46,11 +46,12 @@ func Test_Valid_NewGitResource(t *testing.T) { ), ), want: &v1alpha1.GitResource{ - Name: "git-resource", - Type: v1alpha1.PipelineResourceTypeGit, - URL: "git@github.com:test/test.git", - Revision: "test", - GitImage: "override-with-git:latest", + Name: "git-resource", + Type: v1alpha1.PipelineResourceTypeGit, + URL: "git@github.com:test/test.git", + Revision: "test", + GitImage: "override-with-git:latest", + Submodules: true, }, }, { desc: "Without Revision", @@ -60,11 +61,45 @@ func Test_Valid_NewGitResource(t *testing.T) { ), ), want: &v1alpha1.GitResource{ - Name: "git-resource", - Type: v1alpha1.PipelineResourceTypeGit, - URL: "git@github.com:test/test.git", - Revision: "master", - GitImage: "override-with-git:latest", + Name: "git-resource", + Type: v1alpha1.PipelineResourceTypeGit, + URL: "git@github.com:test/test.git", + Revision: "master", + GitImage: "override-with-git:latest", + Submodules: true, + }, + }, { + desc: "With Submodules", + pipelineResource: tb.PipelineResource("git-resource", "default", + tb.PipelineResourceSpec(v1alpha1.PipelineResourceTypeGit, + tb.PipelineResourceSpecParam("URL", "git@github.com:test/test.git"), + tb.PipelineResourceSpecParam("Revision", "test"), + tb.PipelineResourceSpecParam("Submodules", "false"), + ), + ), + want: &v1alpha1.GitResource{ + Name: "git-resource", + Type: v1alpha1.PipelineResourceTypeGit, + URL: "git@github.com:test/test.git", + Revision: "test", + GitImage: "override-with-git:latest", + Submodules: false, + }, + }, { + desc: "Without Submodules", + pipelineResource: tb.PipelineResource("git-resource", "default", + tb.PipelineResourceSpec(v1alpha1.PipelineResourceTypeGit, + tb.PipelineResourceSpecParam("URL", "git@github.com:test/test.git"), + tb.PipelineResourceSpecParam("Revision", "test"), + ), + ), + want: &v1alpha1.GitResource{ + Name: "git-resource", + Type: v1alpha1.PipelineResourceTypeGit, + URL: "git@github.com:test/test.git", + Revision: "test", + GitImage: "override-with-git:latest", + Submodules: true, }, }} { t.Run(tc.desc, func(t *testing.T) { diff --git a/pkg/git/git.go b/pkg/git/git.go index 4d3a33cc26d..6d7a19bd1ab 100644 --- a/pkg/git/git.go +++ b/pkg/git/git.go @@ -45,31 +45,9 @@ func run(logger *zap.SugaredLogger, dir string, args ...string) (string, error) // Fetch fetches the specified git repository at the revision into path. func Fetch(logger *zap.SugaredLogger, revision, path, url string) error { - // HACK: This is to get git+ssh to work since ssh doesn't respect the HOME - // env variable. - homepath, err := homedir.Dir() - if err != nil { - logger.Errorf("Unexpected error: getting the user home directory: %v", err) + if err := ensureHomeEnv(logger); err != nil { return err } - homeenv := os.Getenv("HOME") - euid := os.Geteuid() - // Special case the root user/directory - if euid == 0 { - if err := os.Symlink(homeenv+"/.ssh", "/root/.ssh"); err != nil { - // Only do a warning, in case we don't have a real home - // directory writable in our image - logger.Warnf("Unexpected error: creating symlink: %v", err) - } - } else if homeenv != "" && homeenv != homepath { - if _, err := os.Stat(homepath + "/.ssh"); os.IsNotExist(err) { - if err := os.Symlink(homeenv+"/.ssh", homepath+"/.ssh"); err != nil { - // Only do a warning, in case we don't have a real home - // directory writable in our image - logger.Warnf("Unexpected error: creating symlink: %v", err) - } - } - } if revision == "" { revision = "master" @@ -111,3 +89,52 @@ func Commit(logger *zap.SugaredLogger, revision, path string) (string, error) { } return strings.TrimSuffix(output, "\n"), nil } + +func SubmoduleFetch(logger *zap.SugaredLogger, path string) error { + if err := ensureHomeEnv(logger); err != nil { + return err + } + + if path != "" { + if err := os.Chdir(path); err != nil { + return xerrors.Errorf("Failed to change directory with path %s; err: %w", path, err) + } + } + if _, err := run(logger, "", "submodule", "init"); err != nil { + return err + } + if _, err := run(logger, "", "submodule", "update", "--recursive"); err != nil { + return err + } + logger.Infof("Successfully initialized and updated submodules in path %s", path) + return nil +} + +func ensureHomeEnv(logger *zap.SugaredLogger) error { + // HACK: This is to get git+ssh to work since ssh doesn't respect the HOME + // env variable. + homepath, err := homedir.Dir() + if err != nil { + logger.Errorf("Unexpected error: getting the user home directory: %v", err) + return err + } + homeenv := os.Getenv("HOME") + euid := os.Geteuid() + // Special case the root user/directory + if euid == 0 { + if err := os.Symlink(homeenv+"/.ssh", "/root/.ssh"); err != nil { + // Only do a warning, in case we don't have a real home + // directory writable in our image + logger.Warnf("Unexpected error: creating symlink: %v", err) + } + } else if homeenv != "" && homeenv != homepath { + if _, err := os.Stat(homepath + "/.ssh"); os.IsNotExist(err) { + if err := os.Symlink(homeenv+"/.ssh", homepath+"/.ssh"); err != nil { + // Only do a warning, in case we don't have a real home + // directory writable in our image + logger.Warnf("Unexpected error: creating symlink: %v", err) + } + } + } + return nil +}