diff --git a/pkg/specs/apptype/determine_type.go b/pkg/specs/apptype/determine_type.go index cbad96e67..6328c7e08 100644 --- a/pkg/specs/apptype/determine_type.go +++ b/pkg/specs/apptype/determine_type.go @@ -15,6 +15,7 @@ import ( "github.com/replicatedhq/ship/pkg/constants" "github.com/replicatedhq/ship/pkg/specs/githubclient" "github.com/replicatedhq/ship/pkg/specs/gogetter" + "github.com/replicatedhq/ship/pkg/specs/localgetter" "github.com/replicatedhq/ship/pkg/state" "github.com/replicatedhq/ship/pkg/util" errors2 "github.com/replicatedhq/ship/pkg/util/errors" @@ -86,6 +87,11 @@ func (r *inspector) DetermineApplicationType(ctx context.Context, upstream strin return r.determineTypeFromContents(ctx, upstream, githubClient) } + if localgetter.IsLocalFile(&r.fs, upstream) { + fetcher := localgetter.LocalGetter{Logger: r.logger, FS: r.fs} + return r.determineTypeFromContents(ctx, upstream, &fetcher) + } + upstream, subdir, isSingleFile := gogetter.UntreeGithub(upstream) if !isSingleFile { isSingleFile = gogetter.IsShipYaml(upstream) diff --git a/pkg/specs/gogetter/go_getter.go b/pkg/specs/gogetter/go_getter.go index af5cef272..4bb9c0a83 100644 --- a/pkg/specs/gogetter/go_getter.go +++ b/pkg/specs/gogetter/go_getter.go @@ -36,8 +36,8 @@ func (g *GoGetter) GetFiles(ctx context.Context, upstream, savePath string) (str } if g.IsSingleFile { - debug.Log("event", "gogetter.GetSingleFile", "upstream", upstream, "savePath", savePath) - return g.GetSingleFile(ctx, upstream, savePath) + debug.Log("event", "gogetter.getSingleFile", "upstream", upstream, "savePath", savePath) + return g.getSingleFile(ctx, upstream, savePath) } err = getter.GetAny(savePath, upstream) @@ -65,7 +65,7 @@ func (g *GoGetter) GetFiles(ctx context.Context, upstream, savePath string) (str return filepath.Join(savePath, g.Subdir), nil } -func (g *GoGetter) GetSingleFile(ctx context.Context, upstream, savePath string) (string, error) { +func (g *GoGetter) getSingleFile(ctx context.Context, upstream, savePath string) (string, error) { tmpDir := filepath.Join(constants.ShipPathInternalTmp, "gogetter-file") err := getter.GetAny(tmpDir, upstream) diff --git a/pkg/specs/localgetter/local_getter.go b/pkg/specs/localgetter/local_getter.go new file mode 100644 index 000000000..b36d465d5 --- /dev/null +++ b/pkg/specs/localgetter/local_getter.go @@ -0,0 +1,96 @@ +package localgetter + +import ( + "context" + "os" + "path/filepath" + + "github.com/go-kit/kit/log" + "github.com/go-kit/kit/log/level" + "github.com/pkg/errors" + "github.com/spf13/afero" +) + +type LocalGetter struct { + Logger log.Logger + FS afero.Afero +} + +func (g *LocalGetter) GetFiles(ctx context.Context, upstream, savePath string) (string, error) { + debug := level.Debug(g.Logger) + debug.Log("event", "localgetter.GetFiles", "upstream", upstream, "savePath", savePath) + + err := g.copyDir(ctx, upstream, savePath) + if err != nil { + return "", errors.Wrap(err, "copy files") + } + return savePath, nil +} + +func (g *LocalGetter) copyDir(ctx context.Context, upstream, savePath string) error { + isDir, err := g.FS.IsDir(upstream) + if err != nil { + return errors.Wrapf(err, "check if %s is dir", upstream) + } + if !isDir { + // copy a single file + return g.copyFile(ctx, upstream, savePath, os.FileMode(777)) + } + + files, err := g.FS.ReadDir(upstream) + if err != nil { + return errors.Wrapf(err, "read files in dir %s", upstream) + } + + for _, file := range files { + loopFile := filepath.Join(upstream, file.Name()) + loopDest := filepath.Join(savePath, file.Name()) + if file.IsDir() { + err = g.FS.MkdirAll(loopDest, file.Mode()) + if err != nil { + return errors.Wrapf(err, "create dest dir %s", loopDest) + } + + err = g.copyDir(ctx, loopFile, loopDest) + if err != nil { + return errors.Wrapf(err, "copy dir %s", file.Name()) + } + } else { + err = g.copyFile(ctx, loopFile, loopDest, file.Mode()) + if err != nil { + return errors.Wrapf(err, "copy file %s", file.Name()) + } + } + } + return nil +} + +func (g *LocalGetter) copyFile(ctx context.Context, upstream, savePath string, mode os.FileMode) error { + saveDir := filepath.Dir(savePath) + exists, err := g.FS.Exists(saveDir) + if err != nil { + return errors.Wrapf(err, "determine if path %s exists", saveDir) + } + if !exists { + err = g.FS.MkdirAll(saveDir, os.ModePerm) + if err != nil { + return errors.Wrapf(err, "create dest dir %s", saveDir) + } + } + + contents, err := g.FS.ReadFile(upstream) + if err != nil { + return errors.Wrapf(err, "read %s file contents", upstream) + } + + err = g.FS.WriteFile(savePath, contents, mode) + return errors.Wrapf(err, "write %s file contents", savePath) +} + +func IsLocalFile(FS *afero.Afero, upstream string) bool { + exists, err := FS.Exists(upstream) + if err != nil { + return false + } + return exists +} diff --git a/pkg/specs/localgetter/local_getter_test.go b/pkg/specs/localgetter/local_getter_test.go new file mode 100644 index 000000000..a1be98f94 --- /dev/null +++ b/pkg/specs/localgetter/local_getter_test.go @@ -0,0 +1,145 @@ +package localgetter + +import ( + "context" + "os" + "path/filepath" + "testing" + + "github.com/go-kit/kit/log" + "github.com/spf13/afero" + "github.com/stretchr/testify/require" +) + +func TestLocalGetter_copyDir(t *testing.T) { + type file struct { + contents []byte + path string + } + tests := []struct { + name string + upstream string + savePath string + inFiles []file + outFiles []file + wantErr bool + }{ + { + name: "single file", + upstream: "/upstream/file", + savePath: "/save/file", + inFiles: []file{ + { + contents: []byte("hello world"), + path: "/upstream/file", + }, + }, + outFiles: []file{ + { + contents: []byte("hello world"), + path: "/upstream/file", + }, + { + contents: []byte("hello world"), + path: "/save/file", + }, + }, + }, + { + name: "single file in dir", + upstream: "/upstream/dir", + savePath: "/save/dir", + inFiles: []file{ + { + contents: []byte("hello world"), + path: "/upstream/dir/file", + }, + }, + outFiles: []file{ + { + contents: []byte("hello world"), + path: "/upstream/dir/file", + }, + { + contents: []byte("hello world"), + path: "/save/dir/file", + }, + }, + }, + { + name: "file plus subdirs", + upstream: "/upstream/", + savePath: "/save/", + inFiles: []file{ + { + contents: []byte("hello world"), + path: "/upstream/dir/file", + }, + { + contents: []byte("abc xyz"), + path: "/upstream/dir2/file", + }, + { + contents: []byte("123456789"), + path: "/upstream/file", + }, + }, + outFiles: []file{ + { + contents: []byte("hello world"), + path: "/upstream/dir/file", + }, + { + contents: []byte("abc xyz"), + path: "/upstream/dir2/file", + }, + { + contents: []byte("123456789"), + path: "/upstream/file", + }, + { + contents: []byte("hello world"), + path: "/save/dir/file", + }, + { + contents: []byte("abc xyz"), + path: "/save/dir2/file", + }, + { + contents: []byte("123456789"), + path: "/save/file", + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + req := require.New(t) + + mmFs := afero.Afero{Fs: afero.NewMemMapFs()} + + g := &LocalGetter{ + Logger: log.NewNopLogger(), + FS: mmFs, + } + + for _, file := range tt.inFiles { + req.NoError(mmFs.MkdirAll(filepath.Dir(file.path), os.ModePerm)) + req.NoError(mmFs.WriteFile(file.path, file.contents, os.ModePerm)) + } + + err := g.copyDir(context.Background(), tt.upstream, tt.savePath) + if tt.wantErr { + req.Error(err) + } else { + req.NoError(err) + } + + for _, file := range tt.outFiles { + contents, err := mmFs.ReadFile(file.path) + req.NoError(err) + req.Equal(file.contents, contents, "expected equal contents: expected %q, got %q", string(file.contents), string(contents)) + } + }) + } +}