From c226087c1686afb3ee9bb9ff6419c232c46754df Mon Sep 17 00:00:00 2001 From: Luis Rascao Date: Tue, 21 Nov 2023 10:53:49 +0000 Subject: [PATCH 1/4] Refactor file path validation Small refactor to turn file/dir path validation more explicit. Signed-off-by: Luis Rascao --- pkg/runfileconfig/run_file_config_parser.go | 6 +++--- utils/utils.go | 17 ++++++++++------- utils/utils_test.go | 13 ++++++++++--- 3 files changed, 23 insertions(+), 13 deletions(-) diff --git a/pkg/runfileconfig/run_file_config_parser.go b/pkg/runfileconfig/run_file_config_parser.go index 207bfd2fa..03878abdf 100644 --- a/pkg/runfileconfig/run_file_config_parser.go +++ b/pkg/runfileconfig/run_file_config_parser.go @@ -216,7 +216,7 @@ func (a *RunFileConfig) resolvePathToAbsAndValidate(baseDir string, paths ...*st return err } *path = absPath - if err = utils.ValidateFilePath(*path); err != nil { + if err = utils.ValidatePath(*path); err != nil { return err } } @@ -264,7 +264,7 @@ func (a *RunFileConfig) resolveResourcesFilePath(app *App) error { return nil } localResourcesDir := filepath.Join(app.AppDirPath, standalone.DefaultDaprDirName, standalone.DefaultResourcesDirName) - if err := utils.ValidateFilePath(localResourcesDir); err == nil { + if err := utils.ValidatePath(localResourcesDir); err == nil { app.ResourcesPaths = []string{localResourcesDir} } else if len(a.Common.ResourcesPaths) > 0 { app.ResourcesPaths = append(app.ResourcesPaths, a.Common.ResourcesPaths...) @@ -285,7 +285,7 @@ func (a *RunFileConfig) resolveConfigFilePath(app *App) error { return nil } localConfigFile := filepath.Join(app.AppDirPath, standalone.DefaultDaprDirName, standalone.DefaultConfigFileName) - if err := utils.ValidateFilePath(localConfigFile); err == nil { + if err := utils.ValidatePath(localConfigFile); err == nil { app.ConfigFile = localConfigFile } else if len(strings.TrimSpace(a.Common.ConfigFile)) > 0 { app.ConfigFile = a.Common.ConfigFile diff --git a/utils/utils.go b/utils/utils.go index 483bf85f7..5b844f9d2 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -351,13 +351,16 @@ func GetVersionAndImageVariant(imageTag string) (string, string) { return imageTag, "" } -// Returns true if the given file path is valid. -func ValidateFilePath(filePath string) error { - if filePath != "" { - if _, err := os.Stat(filePath); err != nil { - return fmt.Errorf("error in getting the file info for %s: %w", filePath, err) - } +// Returns no error if the given file/directory path is valid. +func ValidatePath(path string) error { + if path == "" { + return nil } + + if _, err := os.Stat(path); err != nil { + return fmt.Errorf("error in getting the file info for %s: %w", path, err) + } + return nil } @@ -409,7 +412,7 @@ func ReadFile(filePath string) ([]byte, error) { // FindFileInDir finds and returns the path of the given file name in the given directory. func FindFileInDir(dirPath, fileName string) (string, error) { filePath := filepath.Join(dirPath, fileName) - if err := ValidateFilePath(filePath); err != nil { + if err := ValidatePath(filePath); err != nil { return "", fmt.Errorf("error in validating the file path %q: %w", filePath, err) } return filePath, nil diff --git a/utils/utils_test.go b/utils/utils_test.go index d89ea4d0a..fcb962951 100644 --- a/utils/utils_test.go +++ b/utils/utils_test.go @@ -175,10 +175,12 @@ func TestGetVersionAndImageVariant(t *testing.T) { } } -func TestValidateFilePaths(t *testing.T) { +func TestValidatePaths(t *testing.T) { dirName := createTempDir(t, "test_validate_paths") - defer cleanupTempDir(t, dirName) validFile := createTempFile(t, dirName, "valid_test_file.yaml") + t.Cleanup(func() { + cleanupTempDir(t, dirName) + }) testcases := []struct { name string input string @@ -194,6 +196,11 @@ func TestValidateFilePaths(t *testing.T) { input: validFile, expectedErr: false, }, + { + name: "valid directory path", + input: dirName, + expectedErr: false, + }, { name: "invalid file path", input: "invalid_file_path", @@ -205,7 +212,7 @@ func TestValidateFilePaths(t *testing.T) { tc := tc t.Run(tc.name, func(t *testing.T) { t.Parallel() - actual := ValidateFilePath(tc.input) + actual := ValidatePath(tc.input) assert.Equal(t, tc.expectedErr, actual != nil) }) } From 883a209a948674a945f382e2a3e685de2bc45269 Mon Sep 17 00:00:00 2001 From: Luis Rascao Date: Tue, 21 Nov 2023 11:08:34 +0000 Subject: [PATCH 2/4] Add support for local Helm repos Signed-off-by: Luis Rascao --- README.md | 9 +++++++ pkg/kubernetes/kubernetes.go | 47 +++++++++++++++++++++++++++--------- utils/utils.go | 16 ++++++++++++ 3 files changed, 60 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index dc8aca585..a6b4abf23 100644 --- a/README.md +++ b/README.md @@ -371,6 +371,15 @@ export DAPR_HELM_REPO_PASSWORD="passwd_xxx" Setting the above parameters will allow `dapr init -k` to install Dapr images from the configured Helm repository. +A local Helm repo is also supported: + +export DAPR_HELM_REPO_URL="/home/user/dapr/helm-charts" + +To directly use HEAD helm charts, create two local symlinks under `DAPR_HELM_REPO_URL` that point to the `charts` folder of each repo: + * `dapr-dashboard-latest` -> `https://github.com/dapr/dashboard/tree/master/chart/dapr-dashboard` + * `dapr-latest` -> `https://github.com/dapr/dapr/tree/master/charts/dapr` + + ### Launch Dapr and your app The Dapr CLI lets you debug easily by launching both Dapr and your app. diff --git a/pkg/kubernetes/kubernetes.go b/pkg/kubernetes/kubernetes.go index f6bd78e5a..f9f4c22ca 100644 --- a/pkg/kubernetes/kubernetes.go +++ b/pkg/kubernetes/kubernetes.go @@ -190,12 +190,15 @@ func getVersion(releaseName string, version string) (string, error) { return actualVersion, nil } -func createTempDir() (string, error) { +func createTempDir() (string, func(), error) { dir, err := os.MkdirTemp("", "dapr") if err != nil { - return "", fmt.Errorf("error creating temp dir: %w", err) + return "", func() {}, fmt.Errorf("error creating temp dir: %w", err) } - return dir, nil + cleanup := func() { + os.RemoveAll(dir) + } + return dir, cleanup, nil } func locateChartFile(dirPath string) (string, error) { @@ -206,7 +209,12 @@ func locateChartFile(dirPath string) (string, error) { return filepath.Join(dirPath, files[0].Name()), nil } -func getHelmChart(version, releaseName, helmRepo string, config *helm.Configuration) (*chart.Chart, error) { +func pullHelmChart(version, releaseName, helmRepo string, config *helm.Configuration) (string, func(), error) { + // is helmRepo already a directory path? (ie. /home/user/dapr/helm-charts) + if localPath, err := utils.DiscoverHelmPath(helmRepo, releaseName, version); err == nil { + return localPath, func() {}, nil + } + pull := helm.NewPullWithOpts(helm.WithConfig(config)) pull.RepoURL = helmRepo pull.Username = utils.GetEnv("DAPR_HELM_REPO_USERNAME", "") @@ -218,24 +226,39 @@ func getHelmChart(version, releaseName, helmRepo string, config *helm.Configurat pull.Version = chartVersion(version) } - dir, err := createTempDir() + dir, cleanup, err := createTempDir() if err != nil { - return nil, err + return "", nil, fmt.Errorf("unable to create temp dir: %w", err) } - defer os.RemoveAll(dir) pull.DestDir = dir _, err = pull.Run(releaseName) if err != nil { - return nil, err + return "", cleanup, fmt.Errorf("unable to pull chart from repo: %w", err) } chartPath, err := locateChartFile(dir) if err != nil { - return nil, err + return "", cleanup, fmt.Errorf("unable to locate chart: %w", err) } - return loader.Load(chartPath) + + return chartPath, cleanup, nil +} + +func getHelmChart(version, releaseName, helmRepo string, config *helm.Configuration) (*chart.Chart, error) { + chartPath, cleanup, err := pullHelmChart(version, releaseName, helmRepo, config) + defer cleanup() + if err != nil { + return nil, fmt.Errorf("unable to pull helm chart: %w", err) + } + + chart, err := loader.Load(chartPath) + if err != nil { + return nil, fmt.Errorf("unable to load chart from path: %w", err) + } + + return chart, nil } func daprChartValues(config InitConfiguration, version string) (map[string]interface{}, error) { @@ -461,8 +484,8 @@ spec: zipkin: endpointAddress: "http://dapr-dev-zipkin.default.svc.cluster.local:9411/api/v2/spans" ` - tempDirPath, err := createTempDir() - defer os.RemoveAll(tempDirPath) + tempDirPath, cleanup, err := createTempDir() + defer cleanup() if err != nil { return err } diff --git a/utils/utils.go b/utils/utils.go index 5b844f9d2..d9c76e9ce 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -433,3 +433,19 @@ func AttachJobObjectToProcess(pid string, proc *os.Process) { func GetJobObjectNameFromPID(pid string) string { return pid + "-" + windowsDaprAppProcJobName } + +func DiscoverHelmPath(helmPath, release, version string) (string, error) { + // first try for a local directory path + dirPath := filepath.Join(helmPath, fmt.Sprintf("%s-%s", release, version)) + if ValidatePath(dirPath) == nil { + return dirPath, nil + } + + // not a dir, try a .tgz file instead + filePath := filepath.Join(helmPath, fmt.Sprintf("%s-%s.tgz", release, version)) + if ValidatePath(filePath) == nil { + return filePath, nil + } + + return "", fmt.Errorf("unable to find a helm path in either %s or %s", dirPath, filePath) +} From 167fa7f814ca58666ae821b7d14efb7f70357778 Mon Sep 17 00:00:00 2001 From: Luis Rascao Date: Thu, 23 Nov 2023 11:17:28 +0000 Subject: [PATCH 3/4] fixup! Add support for local Helm repos Signed-off-by: Luis Rascao --- README.md | 2 +- pkg/kubernetes/kubernetes.go | 2 +- utils/utils.go | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index a6b4abf23..b26584c7a 100644 --- a/README.md +++ b/README.md @@ -371,7 +371,7 @@ export DAPR_HELM_REPO_PASSWORD="passwd_xxx" Setting the above parameters will allow `dapr init -k` to install Dapr images from the configured Helm repository. -A local Helm repo is also supported: +A local Helm repo is also supported, this can either be a directory path or an existing .tgz file. export DAPR_HELM_REPO_URL="/home/user/dapr/helm-charts" diff --git a/pkg/kubernetes/kubernetes.go b/pkg/kubernetes/kubernetes.go index f9f4c22ca..df1079648 100644 --- a/pkg/kubernetes/kubernetes.go +++ b/pkg/kubernetes/kubernetes.go @@ -210,7 +210,7 @@ func locateChartFile(dirPath string) (string, error) { } func pullHelmChart(version, releaseName, helmRepo string, config *helm.Configuration) (string, func(), error) { - // is helmRepo already a directory path? (ie. /home/user/dapr/helm-charts) + // is helmRepo already a directory path or a .tgz file? (ie. /home/user/dapr/helm-charts). if localPath, err := utils.DiscoverHelmPath(helmRepo, releaseName, version); err == nil { return localPath, func() {}, nil } diff --git a/utils/utils.go b/utils/utils.go index d9c76e9ce..eecc55bb8 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -435,13 +435,13 @@ func GetJobObjectNameFromPID(pid string) string { } func DiscoverHelmPath(helmPath, release, version string) (string, error) { - // first try for a local directory path + // first try for a local directory path. dirPath := filepath.Join(helmPath, fmt.Sprintf("%s-%s", release, version)) if ValidatePath(dirPath) == nil { return dirPath, nil } - // not a dir, try a .tgz file instead + // not a dir, try a .tgz file instead. filePath := filepath.Join(helmPath, fmt.Sprintf("%s-%s.tgz", release, version)) if ValidatePath(filePath) == nil { return filePath, nil From 0dba2c128bd12ca5b7cee14e5054a8b25f4b8d54 Mon Sep 17 00:00:00 2001 From: Luis Rascao Date: Wed, 6 Dec 2023 17:24:46 +0000 Subject: [PATCH 4/4] Add test coverage for local Helm repo functionality Signed-off-by: Luis Rascao --- tests/e2e/common/common.go | 6 +- tests/e2e/kubernetes/kubernetes_test.go | 131 ++++++++++++++++++ tests/e2e/kubernetes/testdata/dapr-1.12.0.tgz | Bin 0 -> 22227 bytes .../testdata/dapr-dashboard-0.14.0.tgz | Bin 0 -> 3976 bytes 4 files changed, 135 insertions(+), 2 deletions(-) create mode 100644 tests/e2e/kubernetes/testdata/dapr-1.12.0.tgz create mode 100644 tests/e2e/kubernetes/testdata/dapr-dashboard-0.14.0.tgz diff --git a/tests/e2e/common/common.go b/tests/e2e/common/common.go index b6c1b5b4a..24e8f13c4 100644 --- a/tests/e2e/common/common.go +++ b/tests/e2e/common/common.go @@ -774,8 +774,10 @@ func installTest(details VersionDetails, opts TestOptions) func(t *testing.T) { args = append(args, "--dev") } if !details.UseDaprLatestVersion { - // TODO: Pass dashboard-version also when charts are released. - args = append(args, "--runtime-version", details.RuntimeVersion) + args = append(args, []string{ + "--runtime-version", details.RuntimeVersion, + "--dashboard-version", details.DashboardVersion, + }...) } if opts.HAEnabled { args = append(args, "--enable-ha") diff --git a/tests/e2e/kubernetes/kubernetes_test.go b/tests/e2e/kubernetes/kubernetes_test.go index d080eba4c..91ea8a056 100644 --- a/tests/e2e/kubernetes/kubernetes_test.go +++ b/tests/e2e/kubernetes/kubernetes_test.go @@ -17,9 +17,17 @@ limitations under the License. package kubernetes_test import ( + "archive/tar" + "compress/gzip" + "io" + "os" + "path/filepath" + "strings" "testing" "github.com/dapr/cli/tests/e2e/common" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestKubernetesNonHAModeMTLSDisabled(t *testing.T) { @@ -500,3 +508,126 @@ func TestK8sInstallwithoutRuntimeVersionwithMarinerImagesFlag(t *testing.T) { t.Run(tc.Name, tc.Callable) } } + +func TestKubernetesLocalFileHelmRepoInstall(t *testing.T) { + // ensure clean env for test + ensureCleanEnv(t, false) + + // create a temp dir to store the helm repo + helmRepoPath, err := os.MkdirTemp("", "dapr-e2e-kube-with-env-*") + assert.NoError(t, err) + // defer os.RemoveAll(helmRepoPath) // clean up + + // copy all .tar.gz files from testdata dir and uncompress them + copyAndUncompressTarGzFiles(t, helmRepoPath) + + // point the env var to the dir containing both dapr and dapr-dashboard helm charts + t.Setenv("DAPR_HELM_REPO_URL", helmRepoPath) + + // setup tests + tests := []common.TestCase{} + tests = append(tests, common.GetTestsOnInstall(currentVersionDetails, common.TestOptions{ + HAEnabled: false, + MTLSEnabled: false, + ApplyComponentChanges: true, + CheckResourceExists: map[common.Resource]bool{ + common.CustomResourceDefs: true, + common.ClusterRoles: true, + common.ClusterRoleBindings: true, + }, + })...) + + tests = append(tests, common.GetTestsOnUninstall(currentVersionDetails, common.TestOptions{ + CheckResourceExists: map[common.Resource]bool{ + common.CustomResourceDefs: true, + common.ClusterRoles: false, + common.ClusterRoleBindings: false, + }, + })...) + + // execute tests + for _, tc := range tests { + t.Run(tc.Name, tc.Callable) + } +} + +func copyAndUncompressTarGzFiles(t *testing.T, destination string) { + // find all .tar.gz files in testdata dir + files, err := filepath.Glob(filepath.Join("testdata", "*.tgz")) + require.NoError(t, err) + + for _, file := range files { + // untar the dapr/dashboard helm .tar.gz, get back the root dir of the untarred files + // it's either 'dapr' or 'dapr-dashboard' + rootDir, err := untarDaprHelmGzFile(file, destination) + require.NoError(t, err) + + // rename the root dir to the base name of the .tar.gz file + // (eg. /var/folders/4s/w0gdrc957k11vbkgyhjrk12w0000gn/T/dapr-e2e-kube-with-env-404115459/dapr-1.12.0) + base := filepath.Base(strings.TrimSuffix(file, filepath.Ext(file))) + err = os.Rename(filepath.Join(destination, rootDir), filepath.Join(destination, base)) + require.NoError(t, err) + } +} + +func untarDaprHelmGzFile(file string, destination string) (string, error) { + // open the tar.gz file + f, err := os.Open(file) + if err != nil { + return "", err + } + defer f.Close() + + // create a gzip reader + gr, err := gzip.NewReader(f) + if err != nil { + return "", err + } + defer gr.Close() + + // create a tar reader + tr := tar.NewReader(gr) + + rootDir := "" + // iterate through all the files in the tarball + for { + hdr, err := tr.Next() + if err == io.EOF { + break // end of tarball + } + if err != nil { + return "", err + } + + // build the full destination path + filename := filepath.Join(destination, hdr.Name) + + // ensure the destination directory exists + dir := filepath.Dir(filename) + if _, err := os.Stat(dir); os.IsNotExist(err) { + if err := os.MkdirAll(dir, 0700); err != nil { + return "", err + } + } + + // the root dir for all files is the same + rootDir = strings.FieldsFunc(hdr.Name, + func(c rune) bool { + return os.PathSeparator == c + })[0] + + // create the destination file + dstFile, err := os.Create(filename) + if err != nil { + return "", err + } + defer dstFile.Close() + + // copy the file contents + if _, err := io.Copy(dstFile, tr); err != nil { + return "", err + } + } + + return rootDir, nil +} diff --git a/tests/e2e/kubernetes/testdata/dapr-1.12.0.tgz b/tests/e2e/kubernetes/testdata/dapr-1.12.0.tgz new file mode 100644 index 0000000000000000000000000000000000000000..4d883cc907190f420c23f372a78accf363c2a994 GIT binary patch literal 22227 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 Q@@s)%c5Y&Dc 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 literal 0 HcmV?d00001