diff --git a/README.md b/README.md index 07985f858..dd058ff1a 100644 --- a/README.md +++ b/README.md @@ -86,7 +86,7 @@ Output should look like so: ``` ⌛ Making the jump to hyperspace... -✅ Downloaded binaries and completed components set up. +✅ Downloaded binaries and completed resources set up. ℹ️ daprd binary has been installed to $HOME/.dapr/bin. ℹ️ dapr_placement container is running. ℹ️ dapr_redis container is running. @@ -99,10 +99,12 @@ Output should look like so: This step creates the following defaults: -1. components folder which is later used during `dapr run` unless the `--resources-path` (`--components-path` is deprecated and will be removed in future releases) option is provided. For Linux/MacOS, the default components folder path is `$HOME/.dapr/components` and for Windows it is `%USERPROFILE%\.dapr\components`. -2. component files in the components folder called `pubsub.yaml` and `statestore.yaml`. +1. resources folder which is later used during `dapr run` unless the `--resources-path` (`--components-path` is deprecated and will be removed in future releases) option is provided. For Linux/MacOS, the default resources folder path is `$HOME/.dapr/resources` and for Windows it is `%USERPROFILE%\.dapr\resources`. +2. resoources files in the resources folder called `pubsub.yaml` and `statestore.yaml`. 3. default config file `$HOME/.dapr/config.yaml` for Linux/MacOS or for Windows at `%USERPROFILE%\.dapr\config.yaml` to enable tracing on `dapr init` call. Can be overridden with the `--config` flag on `dapr run`. +> Note: If there is a `components` directory present in the default dapr installation path `$HOME/.dapr/resources` (for Linux/MacOS) or `%USERPROFILE%\.dapr\resources` (for windows) then `dapr init` will copy the `resources` from the `components` to `resources` directory. This is to ensure safe migration from using `components` to `resources` directory. + #### Slim Init Alternatively to the above, to have the CLI not install any default configuration files or run Docker containers, use the `--slim` flag with the init command. Only Dapr binaries will be installed. @@ -115,13 +117,15 @@ Output should look like so: ```bash ⌛ Making the jump to hyperspace... -✅ Downloaded binaries and completed components set up. +✅ Downloaded binaries and completed resources set up. ℹ️ daprd binary has been installed to $HOME/.dapr/bin. ℹ️ placement binary has been installed. ✅ Success! Dapr is up and running. To get started, go here: https://aka.ms/dapr-getting-started ``` ->Note: When initializing Dapr with the `--slim` flag only the Dapr runtime binary and the placement service binary are installed. An empty default components folder is created with no default configuration files. During `dapr run` user should use `--resources-path` (`--components-path` is deprecated and will be removed in future releases) to point to a components directory with custom configurations files or alternatively place these files in the default directory. For Linux/MacOS, the default components directory path is `$HOME/.dapr/components` and for Windows it is `%USERPROFILE%\.dapr\components`. +>Note: When initializing Dapr with the `--slim` flag only the Dapr runtime binary and the placement service binary are installed. An empty default `resources` folder is created with no default configuration files. During `dapr run` user should use `--resources-path` (`--components-path` is deprecated and will be removed in future releases) to point to a `resources` directory with custom configurations files or alternatively place these files in the default directory. For Linux/MacOS, the default `resources` directory path is `$HOME/.dapr/resources` and for Windows it is `%USERPROFILE%\.dapr\resources`. + +> Note: If there is a `components` directory present in the default dapr installation path `$HOME/.dapr/resources` (for Linux/MacOS) or `%USERPROFILE%\.dapr\resources` (for windows) then `dapr init` will copy the `resources` from the `components` to `resources` directory. This is to ensure safe migration from using `components` to `resources` directory. #### Install a specific runtime version @@ -444,7 +448,7 @@ Launch Dapr and your app: dapr run --app-id nodeapp --app-port 3000 node app.js ``` -Note: To choose a non-default components folder, use the --resources-path(--components-path is deprecated) option. +Note: To choose a non-default resources folder, use the --resources-path option (--components-path is deprecated, see https://github.com/dapr/cli/issues/953 for more information). Invoke your app: @@ -562,8 +566,8 @@ To use a custom path for component definitions ```bash dapr run --resources-path [custom path] -> Note: --components-path flag is deprecated. It will continue to work until it is removed completely. ``` +> Note: --components-path flag is deprecated. It will continue to work until it is removed completely. See https://github.com/dapr/cli/issues/953 for more information. ### List Configurations diff --git a/cmd/run.go b/cmd/run.go index bca9dee1f..1bd681557 100644 --- a/cmd/run.go +++ b/cmd/run.go @@ -136,7 +136,7 @@ dapr run --run-file /path/to/directory // Fallback to default components directory if not specified. if componentsPath == "" { - componentsPath = standalone.GetDaprComponentsPath(daprDirPath) + componentsPath = standalone.GetResourcesDir(daprDirPath) } if unixDomainSocket != "" { diff --git a/pkg/runexec/runexec_test.go b/pkg/runexec/runexec_test.go index 6dd7f886f..55ce2175f 100644 --- a/pkg/runexec/runexec_test.go +++ b/pkg/runexec/runexec_test.go @@ -77,10 +77,10 @@ func setupRun(t *testing.T) { myDaprPath, err := standalone.GetDaprRuntimePath("") assert.NoError(t, err) - componentsDir := standalone.GetDaprComponentsPath(myDaprPath) + resourcesDir := standalone.GetDaprResourcesPath(myDaprPath) configFile := standalone.GetDaprConfigPath(myDaprPath) - err = os.MkdirAll(componentsDir, 0o700) - assert.Equal(t, nil, err, "Unable to setup components dir before running test") + err = os.MkdirAll(resourcesDir, 0o700) + assert.Equal(t, nil, err, "Unable to setup resources dir before running test") file, err := os.Create(configFile) file.Close() assert.Equal(t, nil, err, "Unable to create config file before running test") @@ -90,11 +90,11 @@ func tearDownRun(t *testing.T) { myDaprPath, err := standalone.GetDaprRuntimePath("") assert.NoError(t, err) - componentsDir := standalone.GetDaprComponentsPath(myDaprPath) + componentsDir := standalone.GetDaprResourcesPath(myDaprPath) configFile := standalone.GetDaprConfigPath(myDaprPath) err = os.RemoveAll(componentsDir) - assert.Equal(t, nil, err, "Unable to delete default components dir after running test") + assert.Equal(t, nil, err, "Unable to delete default resources dir after running test") err = os.Remove(configFile) assert.Equal(t, nil, err, "Unable to delete default config file after running test") } @@ -117,7 +117,7 @@ func assertCommonArgs(t *testing.T, basicConfig *standalone.RunConfig, output *R assertArgumentEqual(t, "app-max-concurrency", "-1", output.DaprCMD.Args) assertArgumentEqual(t, "app-protocol", "http", output.DaprCMD.Args) assertArgumentEqual(t, "app-port", "3000", output.DaprCMD.Args) - assertArgumentEqual(t, "components-path", standalone.GetDaprComponentsPath(daprPath), output.DaprCMD.Args) + assertArgumentEqual(t, "components-path", standalone.GetDaprResourcesPath(daprPath), output.DaprCMD.Args) assertArgumentEqual(t, "app-ssl", "", output.DaprCMD.Args) assertArgumentEqual(t, "metrics-port", "9001", output.DaprCMD.Args) assertArgumentEqual(t, "dapr-http-max-request-size", "-1", output.DaprCMD.Args) @@ -171,14 +171,14 @@ func TestRun(t *testing.T) { myDaprPath, err := standalone.GetDaprRuntimePath("") assert.NoError(t, err) - componentsDir := standalone.GetDaprComponentsPath(myDaprPath) + resourcesDir := standalone.GetDaprResourcesPath(myDaprPath) configFile := standalone.GetDaprConfigPath(myDaprPath) sharedRunConfig := &standalone.SharedRunConfig{ LogLevel: "WARN", EnableProfiling: false, AppProtocol: "http", - ComponentsPath: componentsDir, + ComponentsPath: resourcesDir, AppSSL: true, MaxRequestBodySize: -1, HTTPReadBufferSize: -1, @@ -190,8 +190,8 @@ func TestRun(t *testing.T) { AppPort: 3000, HTTPPort: 8000, GRPCPort: 50001, - Command: []string{"MyCommand", "--my-arg"}, ProfilePort: 9090, + Command: []string{"MyCommand", "--my-arg"}, MetricsPort: 9001, InternalGRPCPort: 5050, SharedRunConfig: *sharedRunConfig, diff --git a/pkg/standalone/common.go b/pkg/standalone/common.go index ea077abc8..5c9a3016c 100644 --- a/pkg/standalone/common.go +++ b/pkg/standalone/common.go @@ -14,10 +14,14 @@ limitations under the License. package standalone import ( + "fmt" "os" path_filepath "path/filepath" "runtime" "strings" + + "github.com/dapr/cli/pkg/print" + "github.com/dapr/cli/utils" ) const ( @@ -25,8 +29,7 @@ const ( DefaultConfigFileName = "config.yaml" DefaultResourcesDirName = "resources" - defaultDaprBinDirName = "bin" - defaultComponentsDirName = "components" + defaultDaprBinDirName = "bin" ) // GetDaprRuntimePath returns the dapr runtime installation path. @@ -76,9 +79,77 @@ func lookupBinaryFilePath(inputInstallPath string, binaryFilePrefix string) (str } func GetDaprComponentsPath(daprDir string) string { - return path_filepath.Join(daprDir, defaultComponentsDirName) + return path_filepath.Join(daprDir, utils.DefaultComponentsDirName) +} + +func GetDaprResourcesPath(daprDir string) string { + return path_filepath.Join(daprDir, utils.DefaultResourcesDirName) } func GetDaprConfigPath(daprDir string) string { return path_filepath.Join(daprDir, DefaultConfigFileName) } + +// GetResourcesDir returns the path to the resources directory if it exists, otherwise it returns the path of components directory. +// TODO: Remove this function and replace all its usage with above defined `DefaultResourcesDirPath` when `--components-path` flag is removed. +func GetResourcesDir(daprDir string) string { + defaultResourcesDirPath := GetDaprResourcesPath(daprDir) + if _, err := os.Stat(defaultResourcesDirPath); os.IsNotExist(err) { + return GetDaprComponentsPath(daprDir) + } + return defaultResourcesDirPath +} + +// moveDir moves files from src to dest. If there are files in src, it deletes the existing files in dest before copying from src and then deletes the src directory. +func moveDir(src, dest string) error { + destFiles, err := os.ReadDir(dest) + if err != nil { + return fmt.Errorf("error reading files from %s: %w", dest, err) + } + srcFiles, err := os.ReadDir(src) + if err != nil { + return fmt.Errorf("error reading files from %s: %w", src, err) + } + if len(srcFiles) > 0 { + // delete the existing files in dest before copying from src if there are files in src. + for _, file := range destFiles { + err = os.Remove(path_filepath.Join(dest, file.Name())) + if err != nil { + return fmt.Errorf("error removing file %s: %w", file.Name(), err) + } + } + print.InfoStatusEvent(os.Stdout, "Moving files from %q to %q", src, dest) + var content []byte + for _, file := range srcFiles { + content, err = os.ReadFile(path_filepath.Join(src, file.Name())) + if err != nil { + return fmt.Errorf("error reading file %s: %w", file.Name(), err) + } + // #nosec G306 + err = os.WriteFile(path_filepath.Join(dest, file.Name()), content, 0o644) + if err != nil { + return fmt.Errorf("error writing file %s: %w", file.Name(), err) + } + } + } + err = os.RemoveAll(src) + if err != nil { + return fmt.Errorf("error removing directory %s: %w", src, err) + } + return nil +} + +// createSymLink creates a symlink from dirName to symLink. +func createSymLink(dirName, symLink string) error { + if _, err := os.Stat(dirName); err != nil { + if os.IsNotExist(err) { + return fmt.Errorf("directory %s does not exist", dirName) + } + return fmt.Errorf("error reading directory %s: %w", dirName, err) + } + err := os.Symlink(dirName, symLink) + if err != nil { + return fmt.Errorf("error creating symlink from %s to %s: %w", dirName, symLink, err) + } + return nil +} diff --git a/pkg/standalone/common_test.go b/pkg/standalone/common_test.go index 416dd0808..0ac276e7a 100644 --- a/pkg/standalone/common_test.go +++ b/pkg/standalone/common_test.go @@ -67,3 +67,115 @@ func TestGetDaprPath(t *testing.T) { assert.Equal(t, path_filepath.Join(input, ".dapr"), p, "path should be /path/to/dapr/.dapr") }) } + +func TestCreateSymLink(t *testing.T) { + // create a temp dir to hold the symlink and actual directory. + tempDir := createTempDir(t, "dapr-test", "") + defer cleanupTempDir(t, tempDir) + originalDir := createTempDir(t, "original_dir", tempDir) + existingSymLinkDir := createTempDir(t, "new_name_exist", tempDir) + tests := []struct { + name string + actualDirName string + symLinkName string + expectedError bool + }{ + { + name: "create symlink for resources directory", + actualDirName: originalDir, + symLinkName: path_filepath.Join(tempDir, "new_name"), + expectedError: false, + }, + { + name: "create symlink when resources directory does not exist", + actualDirName: "invalid-dir", + symLinkName: "new_name", + expectedError: true, + }, + { + name: "create symlink when symlink named directory already exists", + actualDirName: originalDir, + symLinkName: existingSymLinkDir, + expectedError: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := createSymLink(tt.actualDirName, tt.symLinkName) + assert.Equal(t, tt.expectedError, err != nil) + }) + } +} + +func TestMoveDir(t *testing.T) { + // create a temp dir to hold the source and destination directory. + tempDir := createTempDir(t, "dapr-test", "") + defer cleanupTempDir(t, tempDir) + // create a file in the source and destination directory. + src1 := createTempDir(t, "src1", tempDir) + dest2 := createTempDir(t, "dest2", tempDir) + srcFile := createTempFile(t, src1, "pubsub.yaml") + destFile := createTempFile(t, dest2, "pubsub-dest.yaml") + tests := []struct { + name string + srcDirName string + destDirName string + expectedError bool + presentFileName string + }{ + { + name: "move directory when source directory contains files", + srcDirName: src1, + destDirName: createTempDir(t, "dest1", tempDir), + expectedError: false, + presentFileName: path_filepath.Base(srcFile), + }, + { + name: "move directory when source directory does not contain files", + srcDirName: createTempDir(t, "src2", tempDir), + destDirName: dest2, + expectedError: false, + presentFileName: path_filepath.Base(destFile), + }, + { + name: "move directory when source directory does not exists", + srcDirName: path_filepath.Join(tempDir, "non-existent-source-dir"), + destDirName: createTempDir(t, "dest3", tempDir), + expectedError: true, + }, + { + name: "move directory when destination directory does not exists", + srcDirName: createTempDir(t, "src4", tempDir), + destDirName: path_filepath.Join(tempDir, "non-existent-dir-dir"), + expectedError: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := moveDir(tt.srcDirName, tt.destDirName) + assert.Equal(t, tt.expectedError, err != nil) + if tt.presentFileName != "" { + // check if the files are moved correctly. + assert.FileExists(t, path_filepath.Join(tt.destDirName, tt.presentFileName)) + } + }) + } +} + +func createTempDir(t *testing.T, tempDirName, containerDir string) string { + dirName, err := os.MkdirTemp(containerDir, tempDirName) + assert.NoError(t, err) + return dirName +} + +func createTempFile(t *testing.T, tempDirName, fileName string) string { + file, err := os.CreateTemp(tempDirName, fileName) + assert.NoError(t, err) + defer file.Close() + return file.Name() +} + +func cleanupTempDir(t *testing.T, dirName string) { + err := os.RemoveAll(dirName) + assert.NoError(t, err) +} diff --git a/pkg/standalone/standalone.go b/pkg/standalone/standalone.go index 3f063a7af..45b5ace89 100644 --- a/pkg/standalone/standalone.go +++ b/pkg/standalone/standalone.go @@ -253,8 +253,8 @@ func Init(runtimeVersion, dashboardVersion string, dockerNetwork string, slimMod stopSpinning := print.Spinner(os.Stdout, msg) defer stopSpinning(print.Failure) - // Make default components directory. - err = makeDefaultComponentsDir(installDir) + // Make default resources directory. + err = makeDefaultResourcesDir(installDir) if err != nil { return err } @@ -290,9 +290,9 @@ func Init(runtimeVersion, dashboardVersion string, dockerNetwork string, slimMod stopSpinning(print.Success) - msg = "Downloaded binaries and completed components set up." + msg = "Downloaded binaries and completed resources set up." if isAirGapInit { - msg = "Extracted binaries and completed components set up." + msg = "Extracted binaries and completed resources set up." } print.SuccessStatusEvent(os.Stdout, msg) print.InfoStatusEvent(os.Stdout, "%s binary has been installed to %s.", daprRuntimeFilePrefix, daprBinDir) @@ -306,9 +306,10 @@ func Init(runtimeVersion, dashboardVersion string, dockerNetwork string, slimMod if isAirGapInit { dockerContainerNames = []string{DaprPlacementContainerName} } + var ok bool for _, container := range dockerContainerNames { containerName := utils.CreateContainerName(container, dockerNetwork) - ok, err := confirmContainerIsRunningOrExists(containerName, true, runtimeCmd) + ok, err = confirmContainerIsRunningOrExists(containerName, true, runtimeCmd) if err != nil { return err } @@ -318,6 +319,11 @@ func Init(runtimeVersion, dashboardVersion string, dockerNetwork string, slimMod } print.InfoStatusEvent(os.Stdout, "Use `%s ps` to check running containers.", runtimeCmd) } + // TODO: remove below method when usages of components-path flag and components directory removed completely. + err = copyFilesAndCreateSymlink(GetDaprComponentsPath(installDir), GetDaprResourcesPath(installDir)) + if err != nil { + return err + } return nil } @@ -669,16 +675,16 @@ func createComponentsAndConfiguration(wg *sync.WaitGroup, errorChan chan<- error } var err error - // Make default components & config. - componentsDir := GetDaprComponentsPath(info.installDir) + // Make default resources & config. + resourcesDir := GetDaprResourcesPath(info.installDir) configPath := GetDaprConfigPath(info.installDir) - err = createRedisPubSub(redisHost, componentsDir) + err = createRedisPubSub(redisHost, resourcesDir) if err != nil { errorChan <- fmt.Errorf("error creating redis pubsub component file: %w", err) return } - err = createRedisStateStore(redisHost, componentsDir) + err = createRedisStateStore(redisHost, resourcesDir) if err != nil { errorChan <- fmt.Errorf("error creating redis statestore component file: %w", err) return @@ -706,19 +712,19 @@ func createSlimConfiguration(wg *sync.WaitGroup, errorChan chan<- error, info in } } -func makeDefaultComponentsDir(installDir string) error { - // Make default components directory. - componentsDir := GetDaprComponentsPath(installDir) +func makeDefaultResourcesDir(installDir string) error { + // Make default resources directory. + resourcesDir := GetDaprResourcesPath(installDir) //nolint - _, err := os.Stat(componentsDir) + _, err := os.Stat(resourcesDir) if os.IsNotExist(err) { - errDir := os.MkdirAll(componentsDir, 0o755) + errDir := os.MkdirAll(resourcesDir, 0o755) if errDir != nil { - return fmt.Errorf("error creating default components folder: %w", errDir) + return fmt.Errorf("error creating default resources folder: %w", errDir) } } - os.Chmod(componentsDir, 0o777) + os.Chmod(resourcesDir, 0o777) return nil } @@ -1248,3 +1254,29 @@ func setAirGapInit(fromDir string) { // mostly this is used for unit testing aprat from one use in Init() function. isAirGapInit = (len(strings.TrimSpace(fromDir)) != 0) } + +// copyFilesAndCreateSymlink copies files from src to dest. It deletes the existing files in dest before copying from src. +// this method also deletes the src dir and makes it as a symlink to resources directory. +// please see this comment for more details:https://github.com/dapr/cli/pull/1149#issuecomment-1364424345 +// TODO: Remove this function when `--components-path` flag is removed. +func copyFilesAndCreateSymlink(src, dest string) error { + var err error + if _, err = os.Stat(src); err != nil { + // if the src directory does not exist, create symlink and return nil, because there is nothing to copy from. + if os.IsNotExist(err) { + err = createSymLink(dest, src) + if err != nil { + return err + } + return nil + } + return fmt.Errorf("error reading directory %s: %w", src, err) + } + if err = moveDir(src, dest); err != nil { + return err + } + if err = createSymLink(dest, src); err != nil { + return err + } + return nil +} diff --git a/pkg/standalone/standalone_test.go b/pkg/standalone/standalone_test.go index c035cb1da..039080f02 100644 --- a/pkg/standalone/standalone_test.go +++ b/pkg/standalone/standalone_test.go @@ -15,6 +15,7 @@ package standalone import ( "os" + path_filepath "path/filepath" "testing" "github.com/stretchr/testify/assert" @@ -310,3 +311,42 @@ func TestIsAirGapInit(t *testing.T) { }) } } + +func TestCopyFilesAndCreateSymlink(t *testing.T) { + // create a temp dir to hold the symlink and actual directory. + tempDir := createTempDir(t, "dapr-test", "") + defer cleanupTempDir(t, tempDir) + destDir := createTempDir(t, "dest", tempDir) + srcDir := createTempDir(t, "src", tempDir) + srcFile := createTempFile(t, srcDir, "pubsub.yaml") + tests := []struct { + name string + destDirName string + srcDirName string + expectedError bool + presentFileName string + }{ + { + name: "copy files and create symlink for destination directory when source dir exists", + destDirName: destDir, + srcDirName: srcDir, + expectedError: false, + presentFileName: srcFile, + }, + { + name: "copy files and create symlink for destination directory when source dir does not exists", + destDirName: destDir, + srcDirName: path_filepath.Join(tempDir, "non-existent-source-dir"), + expectedError: false, + presentFileName: path_filepath.Base(srcFile), + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := copyFilesAndCreateSymlink(tt.srcDirName, tt.destDirName) + assert.Equal(t, tt.expectedError, err != nil) + // check if the files are copied. + assert.FileExists(t, path_filepath.Join(tt.srcDirName, path_filepath.Base(tt.presentFileName))) + }) + } +} diff --git a/tests/e2e/standalone/commands.go b/tests/e2e/standalone/commands.go index 5072fbc06..d2b009d1a 100644 --- a/tests/e2e/standalone/commands.go +++ b/tests/e2e/standalone/commands.go @@ -23,7 +23,6 @@ import ( "github.com/dapr/cli/tests/e2e/common" "github.com/dapr/cli/tests/e2e/spawn" - "github.com/dapr/cli/utils" ) // cmdDashboard runs the Dapr dashboard and blocks until it is started. @@ -148,20 +147,18 @@ func daprStop(stopArgs ...string) (string, error) { return spawn.Command(common.GetDaprPath(), stopArgs...) } -// cmdUninstall uninstalls Dapr with --all flag and returns the command output and error. -func cmdUninstall(args ...string) (string, error) { +// cmdUninstallAll uninstalls Dapr with --all flag and returns the command output and error. +func cmdUninstallAll(args ...string) (string, error) { uninstallArgs := []string{"uninstall", "--all"} - - daprContainerRuntime := containerRuntime() - - // Add --container-runtime flag only if daprContainerRuntime is not empty, or overridden via args. - // This is only valid for non-slim mode. - if !isSlimMode() && daprContainerRuntime != "" && !utils.Contains(args, "--container-runtime") { - uninstallArgs = append(uninstallArgs, "--container-runtime", daprContainerRuntime) - } uninstallArgs = append(uninstallArgs, args...) + return uninstallDapr(uninstallArgs...) +} - return spawn.Command(common.GetDaprPath(), uninstallArgs...) +// cmdUninstall uninstalls Dapr without --all flag and returns the command output and error. +func cmdUninstall(args ...string) (string, error) { + uninstallArgs := []string{"uninstall"} + uninstallArgs = append(uninstallArgs, args...) + return uninstallDapr(uninstallArgs...) } // cmdVersion checks the version of Dapr and returns the command output and error. diff --git a/tests/e2e/standalone/init_negative_test.go b/tests/e2e/standalone/init_negative_test.go index 9fe67d5ee..10be60de5 100644 --- a/tests/e2e/standalone/init_negative_test.go +++ b/tests/e2e/standalone/init_negative_test.go @@ -27,7 +27,7 @@ import ( func TestStandaloneInitNegatives(t *testing.T) { // Ensure a clean environment - must(t, cmdUninstall, "failed to uninstall Dapr") + must(t, cmdUninstallAll, "failed to uninstall Dapr") homeDir, err := os.UserHomeDir() require.NoError(t, err, "expected no error on querying for os home dir") @@ -60,7 +60,7 @@ func TestStandaloneInitNegatives(t *testing.T) { t.Run("uninstall without install", func(t *testing.T) { t.Parallel() - output, err := cmdUninstall() + output, err := cmdUninstallAll() require.NoError(t, err, "expected no error on uninstall without install") require.Contains(t, output, "Removing Dapr from your machine...", "expected output to contain message") path := filepath.Join(homeDir, ".dapr", "bin") diff --git a/tests/e2e/standalone/init_test.go b/tests/e2e/standalone/init_test.go index 14a688bc2..a5f10da9c 100644 --- a/tests/e2e/standalone/init_test.go +++ b/tests/e2e/standalone/init_test.go @@ -47,7 +47,7 @@ func TestStandaloneInit(t *testing.T) { } // Ensure a clean environment - must(t, cmdUninstall, "failed to uninstall Dapr") + must(t, cmdUninstallAll, "failed to uninstall Dapr") args := []string{ "--runtime-version", daprRuntimeVersion, "--dashboard-version", daprDashboardVersion, @@ -64,7 +64,7 @@ func TestStandaloneInit(t *testing.T) { } // Ensure a clean environment - must(t, cmdUninstall, "failed to uninstall Dapr") + must(t, cmdUninstallAll, "failed to uninstall Dapr") args := []string{ "--runtime-version", daprRuntimeVersion, "--dashboard-version", daprDashboardVersion, @@ -78,7 +78,7 @@ func TestStandaloneInit(t *testing.T) { t.Run("init should error out if container runtime is not valid", func(t *testing.T) { // Ensure a clean environment - must(t, cmdUninstall, "failed to uninstall Dapr") + must(t, cmdUninstallAll, "failed to uninstall Dapr") args := []string{ "--runtime-version", daprRuntimeVersion, "--dashboard-version", daprDashboardVersion, @@ -91,7 +91,7 @@ func TestStandaloneInit(t *testing.T) { t.Run("init", func(t *testing.T) { // Ensure a clean environment - must(t, cmdUninstall, "failed to uninstall Dapr") + must(t, cmdUninstallAll, "failed to uninstall Dapr") args := []string{ "--runtime-version", daprRuntimeVersion, @@ -115,7 +115,7 @@ func TestStandaloneInit(t *testing.T) { t.Run("init with mariner images", func(t *testing.T) { // Ensure a clean environment - must(t, cmdUninstall, "failed to uninstall Dapr") + must(t, cmdUninstallAll, "failed to uninstall Dapr") args := []string{ "--runtime-version", daprRuntimeVersion, @@ -140,7 +140,7 @@ func TestStandaloneInit(t *testing.T) { t.Run("init without runtime-version flag", func(t *testing.T) { // Ensure a clean environment - must(t, cmdUninstall, "failed to uninstall Dapr") + must(t, cmdUninstallAll, "failed to uninstall Dapr") output, err := cmdInit() t.Log(output) @@ -262,7 +262,7 @@ func verifyBinaries(t *testing.T, daprPath, runtimeVersion, dashboardVersion str } } -// verifyConfigs ensures that the Dapr configuration and component YAMLs +// verifyConfigs ensures that the Dapr configuration and resources YAMLs // are present in the correct path and have the correct values. func verifyConfigs(t *testing.T, daprPath string) { configSpec := map[interface{}]interface{}{} @@ -289,9 +289,9 @@ func verifyConfigs(t *testing.T, daprPath string) { }, } - // The default components are not installed in slim mode. + // The default resources are not installed in slim mode. if !isSlimMode() { - configs[filepath.Join("components", "statestore.yaml")] = map[string]interface{}{ + configs[filepath.Join("resources", "statestore.yaml")] = map[string]interface{}{ "apiVersion": "dapr.io/v1alpha1", "kind": "Component", "metadata": map[interface{}]interface{}{ @@ -316,7 +316,7 @@ func verifyConfigs(t *testing.T, daprPath string) { }, }, } - configs[filepath.Join("components", "pubsub.yaml")] = map[string]interface{}{ + configs[filepath.Join("resources", "pubsub.yaml")] = map[string]interface{}{ "apiVersion": "dapr.io/v1alpha1", "kind": "Component", "metadata": map[interface{}]interface{}{ diff --git a/tests/e2e/standalone/run_test.go b/tests/e2e/standalone/run_test.go index f7efb628c..98bfad072 100644 --- a/tests/e2e/standalone/run_test.go +++ b/tests/e2e/standalone/run_test.go @@ -173,6 +173,21 @@ func TestStandaloneRun(t *testing.T) { assert.Contains(t, output, "Exited Dapr successfully") }) + // TODO: Remove this test when the deprecated --components-path flag is removed. + t.Run(fmt.Sprintf("check run with components-path flag"), func(t *testing.T) { + args := []string{ + "--app-id", "testapp", + "--components-path", "../testdata/resources", + "--", "bash", "-c", "echo 'test'", + } + output, err := cmdRun("", args...) + t.Log(output) + require.NoError(t, err, "run failed") + assert.Contains(t, output, "component loaded. name: test-statestore, type: state.in-memory/v1") + assert.Contains(t, output, "Exited App successfully") + assert.Contains(t, output, "Exited Dapr successfully") + }) + t.Run("run with unknown flags", func(t *testing.T) { output, err := cmdRun("", "--flag") require.Error(t, err, "expected error on run unknown flag") diff --git a/tests/e2e/standalone/stop_test.go b/tests/e2e/standalone/stop_test.go index c14ac364a..d756abc7d 100644 --- a/tests/e2e/standalone/stop_test.go +++ b/tests/e2e/standalone/stop_test.go @@ -24,6 +24,7 @@ import ( ) func TestStandaloneStop(t *testing.T) { + // Ensure a clean environment. ensureDaprInstallation(t) executeAgainstRunningDapr(t, func() { t.Run("stop", func(t *testing.T) { diff --git a/tests/e2e/standalone/uninstall_test.go b/tests/e2e/standalone/uninstall_test.go index ae1545a99..167ab783d 100644 --- a/tests/e2e/standalone/uninstall_test.go +++ b/tests/e2e/standalone/uninstall_test.go @@ -31,7 +31,7 @@ import ( func TestStandaloneUninstall(t *testing.T) { t.Run("uninstall should error out if container runtime is not valid", func(t *testing.T) { - output, err := cmdUninstall("--container-runtime", "invalid") + output, err := cmdUninstallAll("--container-runtime", "invalid") require.Error(t, err, "expected error if container runtime is invalid") require.Contains(t, output, "Invalid container runtime") }) @@ -39,7 +39,7 @@ func TestStandaloneUninstall(t *testing.T) { t.Run("uninstall", func(t *testing.T) { ensureDaprInstallation(t) - output, err := cmdUninstall() + output, err := cmdUninstallAll() t.Log(output) require.NoError(t, err, "dapr uninstall failed") assert.Contains(t, output, "Dapr has been removed successfully") diff --git a/tests/e2e/standalone/upgrade_test.go b/tests/e2e/standalone/upgrade_test.go new file mode 100644 index 000000000..0f5b293e9 --- /dev/null +++ b/tests/e2e/standalone/upgrade_test.go @@ -0,0 +1,169 @@ +//go:build e2e +// +build e2e + +/* +Copyright 2022 The Dapr Authors +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// TODO: Remove the test file when `--components-path` flag is removed. +// This file contains tests for the migration of components directory to resources directory. +package standalone_test + +import ( + "fmt" + "os" + "path/filepath" + "testing" + + "github.com/dapr/cli/utils" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +var ( + // DefaultComponentsDirPath is the default components directory path. + defaultComponentsDirPath = "" + defaultResourcesDirPath = "" +) + +// Tests precedence for --components-path and --resources-path flags. +func TestResourcesLoadPrecedence(t *testing.T) { + t.Cleanup(func() { + // Ensure environment is clean after tests are complete. + must(t, cmdUninstallAll, "failed to uninstall Dapr") + }) + + homeDir, err := os.UserHomeDir() + assert.NoError(t, err, "cannot get user home directory") + defaultComponentsDirPath = filepath.Join(homeDir, ".dapr", utils.DefaultComponentsDirName) + defaultResourcesDirPath = filepath.Join(homeDir, ".dapr", utils.DefaultResourcesDirName) + + t.Run("without pre-existing components directory", func(t *testing.T) { + // Ensure a clean environment. + must(t, cmdUninstallAll, "failed to uninstall Dapr") + + // install dapr -> installs dapr, creates resources dir and symlink components dir. + ensureDaprInstallation(t) + + // check dapr run -> should not load a in-memory statestore component "test-statestore". + testDaprRunOutput(t, false) + + // copy an in-memomy state store component to the resources directory. + copyInMemStateStore(t, defaultResourcesDirPath) + + // check dapr run -> should load in-memory component "test-statestore" from resources directory. + testDaprRunOutput(t, true) + + // dapr run with --components-path flag -> should also load the in-memory component because "components" directory is symlinked to "resources" directory. + testDaprRunOutput(t, true, "--components-path", defaultComponentsDirPath) + + // dapr run with --resources-path flag -> should also load the in-memory component. + testDaprRunOutput(t, true, "--resources-path", defaultResourcesDirPath) + + // dapr run with both flags --resources-path and --components-path. + args := []string{ + "--components-path", defaultComponentsDirPath, + "--resources-path", defaultResourcesDirPath, + } + testDaprRunOutput(t, true, args...) + }) + + t.Run("with pre-existing components directory", func(t *testing.T) { + // Ensure a clean environment. + must(t, cmdUninstallAll, "failed to uninstall Dapr") + + // install dapr -> installs dapr, creates resources dir and symlink components dir. + ensureDaprInstallation(t) + + // test setup -> remove created symlink and rename resources directory to components. + prepareComponentsDir(t) + + // copy an in-memomy state store component to the components directory. + copyInMemStateStore(t, defaultComponentsDirPath) + + // uninstall without removing the components directory. + must(t, cmdUninstall, "failed to uninstall Dapr") + + // install dapr -> installs dapr. It does following - + // 1) creates resources directory. 2)copy resources from components to resources directory. + // 3) delete components directory. 4) creates symlink components for resources directory. + ensureDaprInstallation(t) + + // check dapr run -> should load the in-memory statestore component "test-statestore". + testDaprRunOutput(t, true) + + // dapr run with --components-path flag -> should also load the in-memory component because "components" directory is symlinked to "resources" directory. + testDaprRunOutput(t, true, "--components-path", defaultComponentsDirPath) + + // dapr run with --resources-path flag -> should also load the in-memory component. + testDaprRunOutput(t, true, "--resources-path", defaultResourcesDirPath) + }) + + t.Run("add resources to components directory post dapr install", func(t *testing.T) { + // Ensure a clean environment. + must(t, cmdUninstallAll, "failed to uninstall Dapr") + + // install dapr -> installs dapr, creates resources dir and symlink components dir. + ensureDaprInstallation(t) + + // check dapr run -> should not load a in-memory statestore component "test-statestore". + testDaprRunOutput(t, false) + + // copy an in-memomy state store component to the components directory. + copyInMemStateStore(t, defaultComponentsDirPath) + + // check dapr run -> should load in-memory component "test-statestore" from resources directory. + testDaprRunOutput(t, true) + + // dapr run with --components-path flag -> should also load the in-memory component because "components" directory is symlinked to "resources" directory. + testDaprRunOutput(t, true, "--components-path", defaultComponentsDirPath) + + // dapr run with --resources-path flag -> should also load the in-memory component. + testDaprRunOutput(t, true, "--resources-path", defaultResourcesDirPath) + }) +} + +func prepareComponentsDir(t *testing.T) { + // remove symlink. + err := os.Remove(defaultComponentsDirPath) + assert.NoError(t, err, fmt.Sprintf("cannot remove symlink %q", defaultComponentsDirPath)) + + // rename resources directory to components. + err = os.Rename(defaultResourcesDirPath, defaultComponentsDirPath) + assert.NoError(t, err, fmt.Sprintf("cannot rename %q to %q", defaultResourcesDirPath, defaultComponentsDirPath)) +} + +func copyInMemStateStore(t *testing.T, targetDirPath string) { + filePath := filepath.Join("../testdata/resources", "test-statestore.yaml") + content, err := os.ReadFile(filePath) + assert.NoError(t, err, "cannot read testdata/resources/test-statestore.yaml file") + err = os.WriteFile(filepath.Join(targetDirPath, "test-statestore.yaml"), content, 0644) + assert.NoError(t, err, fmt.Sprintf("cannot write testdata/resources/test-statestore.yaml file to %q directory", targetDirPath)) +} + +func testDaprRunOutput(t *testing.T, inMemoryCompPresent bool, flags ...string) { + args := []string{ + "--app-id", "testapp", + "--", "bash", "-c", "echo 'test'", + } + args = append(args, flags...) + output, err := cmdRun("", args...) + t.Log(output) + require.NoError(t, err, "run failed") + if inMemoryCompPresent { + assert.Contains(t, output, "component loaded. name: test-statestore, type: state.in-memory/v1") + } else { + assert.NotContains(t, output, "component loaded. name: test-statestore, type: state.in-memory/v1") + } + assert.Contains(t, output, "Exited App successfully") + assert.Contains(t, output, "Exited Dapr successfully") +} diff --git a/tests/e2e/standalone/utils.go b/tests/e2e/standalone/utils.go index 87d2fa78c..7f9092a89 100644 --- a/tests/e2e/standalone/utils.go +++ b/tests/e2e/standalone/utils.go @@ -29,6 +29,8 @@ import ( "github.com/stretchr/testify/require" "github.com/dapr/cli/tests/e2e/common" + "github.com/dapr/cli/tests/e2e/spawn" + "github.com/dapr/cli/utils" ) // getSocketCases return different unix socket paths for testing across Dapr commands. @@ -125,29 +127,32 @@ func executeAgainstRunningDapr(t *testing.T, f func(), daprArgs ...string) { // ensureDaprInstallation ensures that Dapr is installed. // If Dapr is not installed, a new installation is attempted. func ensureDaprInstallation(t *testing.T) { - daprRuntimeVersion, daprDashboardVersion := common.GetVersionsFromEnv(t, false) homeDir, err := os.UserHomeDir() require.NoError(t, err, "failed to get user home directory") daprPath := filepath.Join(homeDir, ".dapr") - _, err = os.Stat(daprPath) - if os.IsNotExist(err) { - args := []string{ - "--runtime-version", daprRuntimeVersion, - "--dashboard-version", daprDashboardVersion, + if _, err = os.Stat(daprPath); err != nil { + if os.IsNotExist(err) { + installDapr(t) + } else { + // Some other error occurred. + require.NoError(t, err, "failed to stat dapr installation") } - output, err := cmdInit(args...) - require.NoError(t, err, "failed to install dapr:%v", output) - } else if err != nil { - // Some other error occurred. - require.NoError(t, err, "failed to stat dapr installation") } - - // Slim mode does not have any components by default. - // Install the components required by the tests. + daprBinPath := filepath.Join(daprPath, "bin") + if _, err = os.Stat(daprBinPath); err != nil { + if os.IsNotExist(err) { + installDapr(t) + } else { + // Some other error occurred. + require.NoError(t, err, "failed to stat dapr installation") + } + } + // Slim mode does not have any resources by default. + // Install the resources required by the tests. if isSlimMode() { - err = createSlimComponents(filepath.Join(daprPath, "components")) - require.NoError(t, err, "failed to create components") + err = createSlimComponents(filepath.Join(daprPath, utils.DefaultResourcesDirName)) + require.NoError(t, err, "failed to create resources directory for slim mode") } } @@ -157,3 +162,24 @@ func containerRuntime() string { } return "" } + +func installDapr(t *testing.T) { + daprRuntimeVersion, daprDashboardVersion := common.GetVersionsFromEnv(t, false) + args := []string{ + "--runtime-version", daprRuntimeVersion, + "--dashboard-version", daprDashboardVersion, + } + output, err := cmdInit(args...) + require.NoError(t, err, "failed to install dapr:%v", output) +} + +func uninstallDapr(uninstallArgs ...string) (string, error) { + daprContainerRuntime := containerRuntime() + + // Add --container-runtime flag only if daprContainerRuntime is not empty, or overridden via args. + // This is only valid for non-slim mode. + if !isSlimMode() && daprContainerRuntime != "" && !utils.Contains(uninstallArgs, "--container-runtime") { + uninstallArgs = append(uninstallArgs, "--container-runtime", daprContainerRuntime) + } + return spawn.Command(common.GetDaprPath(), uninstallArgs...) +} diff --git a/utils/utils.go b/utils/utils.go index 8d612629e..a942d3767 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -41,6 +41,9 @@ const ( DOCKER ContainerRuntime = "docker" PODMAN ContainerRuntime = "podman" + DefaultComponentsDirName = "components" + DefaultResourcesDirName = "resources" + marinerImageVariantName = "mariner" socketFormat = "%s/dapr-%s-%s.socket"