diff --git a/cmd/skaffold/app/cmd/init.go b/cmd/skaffold/app/cmd/init.go index e659fca3c28..d05f706fe5a 100644 --- a/cmd/skaffold/app/cmd/init.go +++ b/cmd/skaffold/app/cmd/init.go @@ -36,6 +36,7 @@ var ( cliKubernetesManifests []string skipBuild bool skipDeploy bool + skipUnreachableDirs bool force bool analyze bool enableJibInit bool @@ -70,6 +71,7 @@ func NewCmdInit() *cobra.Command { {Value: &enableBuildpacksInit, Name: "XXenableBuildpacksInit", DefValue: true, Usage: "", Hidden: true, IsEnum: true}, {Value: &buildpacksBuilder, Name: "XXdefaultBuildpacksBuilder", DefValue: "gcr.io/buildpacks/builder:v1", Usage: "", Hidden: true}, {Value: &enableManifestGeneration, Name: "generate-manifests", DefValue: false, Usage: "Allows skaffold to try and generate basic kubernetes resources to get your project started", IsEnum: true}, + {Value: &skipUnreachableDirs, Name: "skip-unreachable-dirs", DefValue: false, Usage: "Instead of erroring, it will skip the directories that cannot be accessed due to permissions", IsEnum: true}, }). NoArgs(doInit) } @@ -83,6 +85,7 @@ func doInit(ctx context.Context, out io.Writer) error { CliKubernetesManifests: cliKubernetesManifests, SkipBuild: skipBuild, SkipDeploy: skipDeploy, + SkipUnreachableDirs: skipUnreachableDirs, Force: force, Analyze: analyze, EnableJibInit: enableJibInit, diff --git a/docs-v2/content/en/docs/references/cli/_index.md b/docs-v2/content/en/docs/references/cli/_index.md index 352d9c8e3c6..0ad42e28ca8 100644 --- a/docs-v2/content/en/docs/references/cli/_index.md +++ b/docs-v2/content/en/docs/references/cli/_index.md @@ -1073,6 +1073,7 @@ Options: -m, --module=[]: Filter Skaffold configs to only the provided named modules --remote-cache-dir='': Specify the location of the remote cache (default $HOME/.skaffold/remote-cache) --skip-build=false: Skip generating build artifacts in Skaffold config + --skip-unreachable-dirs=false: Instead of erroring, it will skip the directories that cannot be accessed due to permissions --sync-remote-cache='always': Controls how Skaffold manages the remote config cache (see `remote-cache-dir`). One of `always` (default), `missing`, or `never`. `always` syncs remote repositories to latest on access. `missing` only clones remote repositories if they do not exist locally. `never` means the user takes responsibility for updating remote repositories. Usage: @@ -1096,6 +1097,7 @@ Env vars: * `SKAFFOLD_MODULE` (same as `--module`) * `SKAFFOLD_REMOTE_CACHE_DIR` (same as `--remote-cache-dir`) * `SKAFFOLD_SKIP_BUILD` (same as `--skip-build`) +* `SKAFFOLD_SKIP_UNREACHABLE_DIRS` (same as `--skip-unreachable-dirs`) * `SKAFFOLD_SYNC_REMOTE_CACHE` (same as `--sync-remote-cache`) ### skaffold options diff --git a/integration/init_test.go b/integration/init_test.go index 42964518585..8f9d245ebdd 100644 --- a/integration/init_test.go +++ b/integration/init_test.go @@ -18,6 +18,7 @@ package integration import ( "errors" + "fmt" "os" "os/exec" "path/filepath" @@ -211,3 +212,46 @@ func exitCode(err error) int { return 1 } + +func TestInitWithDirWithoutReadPerms(t *testing.T) { + MarkIntegrationTest(t, CanRunWithoutGcp) + tests := []struct { + description string + shouldFail bool + flags []string + dir string + dirToCreate string + }{ + { + description: "without --skip-unreachable-dirs flag, should fail", + shouldFail: true, + flags: []string{"--analyze"}, + dir: "testdata/getting-started/", + dirToCreate: "dir1", + }, + { + description: "with --skip-unreachable-dirs flag, shouldn't fail", + shouldFail: false, + flags: []string{"--analyze", "--skip-unreachable-dirs"}, + dir: "testdata/getting-started", + dirToCreate: "dir2", + }, + } + + for _, test := range tests { + testutil.Run(t, test.description, func(t *testutil.T) { + tmpDir := testutil.NewTempDir(t.T) + copyFiles(tmpDir.Root(), test.dir) + + dirWithoutReadPerms := filepath.Join(tmpDir.Root(), test.dirToCreate) + os.MkdirAll(dirWithoutReadPerms, 0377) + defer os.Remove(dirWithoutReadPerms) + + output, err := skaffold.Init(test.flags...).InDir(tmpDir.Root()).RunWithCombinedOutput(t.T) + t.CheckError(test.shouldFail, err) + if test.shouldFail { + t.CheckDeepEqual(fmt.Sprintf("open %v: permission denied\n", test.dirToCreate), string(output)) + } + }) + } +} diff --git a/pkg/skaffold/initializer/analyze/analyze.go b/pkg/skaffold/initializer/analyze/analyze.go index e8aad272cfd..23e658a0c55 100644 --- a/pkg/skaffold/initializer/analyze/analyze.go +++ b/pkg/skaffold/initializer/analyze/analyze.go @@ -18,6 +18,7 @@ package analyze import ( "context" + "errors" "os" "path/filepath" "sort" @@ -41,12 +42,13 @@ type analyzer interface { } type ProjectAnalysis struct { - configAnalyzer *skaffoldConfigAnalyzer - kubeAnalyzer *kubeAnalyzer - kustomizeAnalyzer *kustomizeAnalyzer - helmAnalyzer *helmAnalyzer - builderAnalyzer *builderAnalyzer - maxFileSize int64 + configAnalyzer *skaffoldConfigAnalyzer + kubeAnalyzer *kubeAnalyzer + kustomizeAnalyzer *kustomizeAnalyzer + helmAnalyzer *helmAnalyzer + builderAnalyzer *builderAnalyzer + maxFileSize int64 + skipUnreachableDirs bool } type HelmChartInfo struct { @@ -114,7 +116,8 @@ func NewAnalyzer(c config.Config) *ProjectAnalysis { analyzeMode: c.Analyze, targetConfig: c.Opts.ConfigurationFile, }, - maxFileSize: c.MaxFileSize, + maxFileSize: c.MaxFileSize, + skipUnreachableDirs: c.SkipUnreachableDirs, } } @@ -127,6 +130,10 @@ func (a *ProjectAnalysis) Analyze(dir string) error { } dirents, err := godirwalk.ReadDirents(dir, nil) + if errors.Is(err, os.ErrPermission) && a.skipUnreachableDirs { + log.Entry(context.TODO()).Debugf("skipping directory %s due to permissions error", dir) + return nil + } if err != nil { return err } diff --git a/pkg/skaffold/initializer/config/config.go b/pkg/skaffold/initializer/config/config.go index f3ef215eefa..6d97f59a13a 100644 --- a/pkg/skaffold/initializer/config/config.go +++ b/pkg/skaffold/initializer/config/config.go @@ -27,6 +27,7 @@ type Config struct { CliKubernetesManifests []string SkipBuild bool SkipDeploy bool + SkipUnreachableDirs bool Force bool Analyze bool EnableJibInit bool // TODO: Remove this parameter