From 1c07506a9d0e65be9380a892af1bce551419e620 Mon Sep 17 00:00:00 2001 From: Guy Sartorelli <36352093+GuySartorelli@users.noreply.github.com> Date: Wed, 4 Sep 2024 11:17:57 +1200 Subject: [PATCH] feat: add `--project` flag to `ddev add-on` commands, fixes #6513 (#6517) [skip ci] --- cmd/ddev/cmd/addon-get.go | 15 +++++----- cmd/ddev/cmd/addon-list.go | 12 ++++++-- cmd/ddev/cmd/addon-remove.go | 9 ++++-- cmd/ddev/cmd/addon_test.go | 42 ++++++++++++++++++++++++++++ cmd/ddev/cmd/get.go | 16 ++++++++++- docs/content/users/usage/commands.md | 10 +++++++ 6 files changed, 90 insertions(+), 14 deletions(-) diff --git a/cmd/ddev/cmd/addon-get.go b/cmd/ddev/cmd/addon-get.go index 5ae4d97221c..7f6f67fcaca 100644 --- a/cmd/ddev/cmd/addon-get.go +++ b/cmd/ddev/cmd/addon-get.go @@ -24,13 +24,14 @@ import ( // AddonGetCmd is the "ddev add-on get" command var AddonGetCmd = &cobra.Command{ - Use: "get [project]", + Use: "get ", Aliases: []string{"install"}, - Args: cobra.MinimumNArgs(1), + Args: cobra.ExactArgs(1), Short: "Get/Download a 3rd party add-on (service, provider, etc.)", Long: `Get/Download a 3rd party add-on (service, provider, etc.). This can be a GitHub repo, in which case the latest release will be used, or it can be a link to a .tar.gz in the correct format (like a particular release's .tar.gz) or it can be a local directory.`, Example: `ddev add-on get ddev/ddev-redis ddev add-on get ddev/ddev-redis --version v1.0.4 +ddev add-on get ddev/ddev-redis --project my-project ddev add-on get https://github.com/ddev/ddev-drupal-solr/archive/refs/tags/v1.2.3.tar.gz ddev add-on get /path/to/package ddev add-on get /path/to/tarball.tar.gz @@ -48,14 +49,10 @@ ddev add-on get /path/to/tarball.tar.gz verbose = true } - apps, err := getRequestedProjects(args[1:], false) + app, err := ddevapp.GetActiveApp(cmd.Flag("project").Value.String()) if err != nil { - util.Failed("Unable to get project(s) %v: %v", args, err) + util.Failed("Unable to get project %v: %v", cmd.Flag("project").Value.String(), err) } - if len(apps) == 0 { - util.Failed("No project(s) found") - } - app := apps[0] err = os.Chdir(app.AppRoot) if err != nil { util.Failed("Unable to change directory to project root %s: %v", app.AppRoot, err) @@ -349,6 +346,8 @@ func createManifestFile(app *ddevapp.DdevApp, addonName string, repository strin func init() { AddonGetCmd.Flags().String("version", "", `Specify a particular version of add-on to install`) AddonGetCmd.Flags().BoolP("verbose", "v", false, "Extended/verbose output") + AddonGetCmd.Flags().String("project", "", "Name of the project to install the add-on in") + _ = AddonGetCmd.RegisterFlagCompletionFunc("project", ddevapp.GetProjectNamesFunc("all", 0)) AddonCmd.AddCommand(AddonGetCmd) } diff --git a/cmd/ddev/cmd/addon-list.go b/cmd/ddev/cmd/addon-list.go index 0808963472b..1554a35fefb 100644 --- a/cmd/ddev/cmd/addon-list.go +++ b/cmd/ddev/cmd/addon-list.go @@ -20,18 +20,24 @@ import ( // AddonListCmd is the "ddev add-on list" command var AddonListCmd = &cobra.Command{ Use: "list", + Args: cobra.NoArgs, Short: "List available or installed DDEV add-ons", Long: `List available or installed DDEV add-ons. Without '--all' it shows only official DDEV add-ons. To list installed add-ons, use '--installed'`, Example: `ddev add-on list ddev add-on list --all ddev add-on list --installed +ddev add-on list --installed --project my-project `, Run: func(cmd *cobra.Command, _ []string) { + if cmd.Flags().Changed("project") && !cmd.Flags().Changed("installed") { + util.Failed("--project flag can only be used with --installed flag") + } + // List installed add-ons if cmd.Flags().Changed("installed") { - app, err := ddevapp.GetActiveApp("") + app, err := ddevapp.GetActiveApp(cmd.Flag("project").Value.String()) if err != nil { - util.Failed("Unable to find active project: %v", err) + util.Failed("Unable to get project %v: %v", cmd.Flag("project").Value.String(), err) } ListInstalledAddons(app) @@ -130,6 +136,8 @@ func renderRepositoryList(repos []*github.Repository) string { func init() { AddonListCmd.Flags().Bool("all", false, `List unofficial DDEV add-ons for in addition to the official ones`) AddonListCmd.Flags().Bool("installed", false, `Show installed DDEV add-ons`) + AddonListCmd.Flags().String("project", "", "Name of the project to list the add-ons for. Can only be used with `--installed`") + _ = AddonListCmd.RegisterFlagCompletionFunc("project", ddevapp.GetProjectNamesFunc("all", 0)) // Can't do 'ddev add-on list --all --installed', because the "installed" flag already shows *all* installed add-ons AddonListCmd.MarkFlagsMutuallyExclusive("all", "installed") diff --git a/cmd/ddev/cmd/addon-remove.go b/cmd/ddev/cmd/addon-remove.go index 7e03bbb5e3f..97c44cbf67e 100644 --- a/cmd/ddev/cmd/addon-remove.go +++ b/cmd/ddev/cmd/addon-remove.go @@ -15,12 +15,13 @@ var AddonRemoveCmd = &cobra.Command{ Short: "Remove a DDEV add-on which was previously installed in this project", Example: `ddev add-on remove someaddonname, ddev add-on remove someowner/ddev-someaddonname, -ddev add-on remove ddev-someaddonname +ddev add-on remove ddev-someaddonname, +ddev add-on remove ddev-someaddonname --project my-project `, Run: func(cmd *cobra.Command, args []string) { - app, err := ddevapp.GetActiveApp("") + app, err := ddevapp.GetActiveApp(cmd.Flag("project").Value.String()) if err != nil { - util.Failed("Unable to find active project: %v", err) + util.Failed("Unable to get project %v: %v", cmd.Flag("project").Value.String(), err) } origDir, _ := os.Getwd() @@ -48,5 +49,7 @@ ddev add-on remove ddev-someaddonname func init() { AddonRemoveCmd.Flags().BoolP("verbose", "v", false, "Extended/verbose output") + AddonRemoveCmd.Flags().String("project", "", "Name of the project to remove the add-on from") + _ = AddonRemoveCmd.RegisterFlagCompletionFunc("project", ddevapp.GetProjectNamesFunc("all", 0)) AddonCmd.AddCommand(AddonRemoveCmd) } diff --git a/cmd/ddev/cmd/addon_test.go b/cmd/ddev/cmd/addon_test.go index 490ba5fb700..80973b8b1db 100644 --- a/cmd/ddev/cmd/addon_test.go +++ b/cmd/ddev/cmd/addon_test.go @@ -144,6 +144,48 @@ func TestCmdAddonInstalled(t *testing.T) { assert.NoError(err, "unable to ddev add-on get redis: %v, output='%s'", err, out) } +// TestCmdAddonProjectFlag tests the `--project` flag in `ddev add-on` subcommands +func TestCmdAddonProjectFlag(t *testing.T) { + if os.Getenv("DDEV_RUN_GET_TESTS") != "true" { + t.Skip("Skipping because DDEV_RUN_GET_TESTS is not set") + } + origDdevDebug := os.Getenv("DDEV_DEBUG") + _ = os.Unsetenv("DDEV_DEBUG") + assert := asrt.New(t) + + site := TestSites[0] + // Explicitly don't chdir to the project + + t.Cleanup(func() { + _, err := exec.RunHostCommand(DdevBin, "add-on", "remove", "redis", "--project", site.Name) + assert.NoError(err) + _ = os.RemoveAll(filepath.Join(globalconfig.GetGlobalDdevDir(), "commands/web/global-touched")) + _ = os.Setenv("DDEV_DEBUG", origDdevDebug) + }) + + // Install the add-on using the `--project` flag + out, err := exec.RunHostCommand(DdevBin, "add-on", "get", "ddev/ddev-redis", "--project", site.Name, "--json-output") + require.NoError(t, err, "failed ddev add-on get ddev/ddev-redis --project %s --json-output: %v (output='%s')", site.Name, err, out) + + redisManifest := getManifestFromLogs(t, out) + require.NoError(t, err) + + installedOutput, err := exec.RunHostCommand(DdevBin, "add-on", "list", "--installed", "--project", site.Name, "--json-output") + require.NoError(t, err, "failed ddev add-on list --installed --project %s --json-output: %v (output='%s')", site.Name, err, installedOutput) + installedManifests := getManifestMapFromLogs(t, installedOutput) + + require.NotEmptyf(t, redisManifest["Version"], "redis manifest is empty: %v", redisManifest) + assert.Equal(redisManifest["Version"], installedManifests["redis"]["Version"]) + + // Remove the add-on using the `--project` flag + out, err = exec.RunHostCommand(DdevBin, "add-on", "remove", "ddev/ddev-redis", "--project", site.Name) + require.NoError(t, err, "unable to ddev add-on remove ddev/ddev-redis --project %s: %v, output='%s'", site.Name, err, out) + + // Now make sure we put it back so it can be removed in cleanup + out, err = exec.RunHostCommand(DdevBin, "add-on", "get", "ddev/ddev-redis", "--project", site.Name) + assert.NoError(err, "unable to ddev add-on get ddev/ddev-redis --project %s: %v, output='%s'", site.Name, err, out) +} + // getManifestFromLogs returns the manifest built from 'raw' section of // ddev add-on get -j output func getManifestFromLogs(t *testing.T, jsonOut string) map[string]interface{} { diff --git a/cmd/ddev/cmd/get.go b/cmd/ddev/cmd/get.go index b103226e2fb..c8439233cec 100644 --- a/cmd/ddev/cmd/get.go +++ b/cmd/ddev/cmd/get.go @@ -66,8 +66,21 @@ ddev get --remove ddev-someaddonname if len(args) < 1 { util.Failed("You must specify an add-on to download") } + if len(args) > 1 { + // Update --project flag if projects were passed as args. + apps, err := getRequestedProjects(args[1:], false) + if err != nil { + util.Failed("Unable to get project(s) %v: %v", args, err) + } + if len(apps) > 0 { + err = cmd.Flag("project").Value.Set(apps[0].GetName()) + if err != nil { + util.Failed("Unable to set --project flag %v: %v", apps[0].GetName(), err) + } + } + } // Hand off execution for ddev get - AddonGetCmd.Run(cmd, args) + AddonGetCmd.Run(cmd, []string{args[0]}) }, } @@ -78,5 +91,6 @@ func init() { Get.Flags().String("remove", "", `Remove a ddev-get add-on`) Get.Flags().String("version", "", `Specify a particular version of add-on to install`) Get.Flags().BoolP("verbose", "v", false, "Extended/verbose output for ddev get") + Get.Flags().String("project", "", "Name of the project to install the add-on in") RootCmd.AddCommand(Get) } diff --git a/docs/content/users/usage/commands.md b/docs/content/users/usage/commands.md index f3519f1eae6..62d432ec238 100644 --- a/docs/content/users/usage/commands.md +++ b/docs/content/users/usage/commands.md @@ -633,6 +633,7 @@ Download an add-on (service, provider, etc.). Flags: +* `--project `: Specify a project to install the add-on into. Defaults to checking for a project in the current directory. * `--version `: Specify a version to download * `--verbose`, `-v`: Output verbose error information with Bash `set -x` (default `false`) @@ -656,6 +657,9 @@ ddev add-on get /path/to/package # Copy an add-on from a tarball in another directory ddev add-on get /path/to/tarball.tar.gz + +# Download the official Redis add-on and install it into a project named "my-project" +ddev add-on get ddev/ddev-redis --project my-project ``` In general, you can run `ddev add-on get` multiple times without doing any damage. Updating an add-on can be done by running `ddev add-on get `. If you have changed an add-on file and removed the `#ddev-generated` marker in the file, that file will not be touched and DDEV will let you know about it. @@ -666,6 +670,7 @@ Remove an installed add-on. Accepts the full add-on name, the short name of the Flags: +* `--project `: Specify a project to remove the add-on from. Defaults to checking for a project in the current directory. * `--verbose`, `-v`: Output verbose error information with Bash `set -x` (default `false`) Example: @@ -674,6 +679,7 @@ Example: ddev add-on remove redis ddev add-on remove ddev-redis ddev add-on remove ddev/ddev-redis +ddev add-on remove ddev/ddev-redis --project my-project ``` ### `add-on list` @@ -684,6 +690,7 @@ Flags: * `--all`: List unofficial *and* official add-ons. (default `true`) * `--installed`: List installed add-ons +* `--project `: Specify a project to remove the add-on from. Can only be used with the `--installed` flag. Defaults to checking for a project in the current directory. Example: @@ -696,6 +703,9 @@ ddev add-on list --all # List installed add-ons ddev add-on list --installed + +# List installed add-ons for a specific project +ddev add-on list --installed --project my-project ``` ## `heidisql`