From 72cae1c7062d58f03943a663269e00d3e3954ef1 Mon Sep 17 00:00:00 2001 From: Mostafa Moradian Date: Thu, 19 Oct 2023 19:57:22 +0200 Subject: [PATCH 1/7] Revert condition to prevent nesting many lines --- cmd/plugin_install.go | 224 +++++++++++++++++++++--------------------- 1 file changed, 112 insertions(+), 112 deletions(-) diff --git a/cmd/plugin_install.go b/cmd/plugin_install.go index 8094702b..827997fc 100644 --- a/cmd/plugin_install.go +++ b/cmd/plugin_install.go @@ -77,133 +77,133 @@ var pluginInstallCmd = &cobra.Command{ var client *github.Client var account string - if strings.HasPrefix(args[0], GitHubURLPrefix) { - // Validate the URL. - validGitHubURL := regexp.MustCompile(GitHubURLRegex) - if !validGitHubURL.MatchString(args[0]) { - log.Panic( - "Invalid URL. Use the following format: github.com/account/repository@version") + if !strings.HasPrefix(args[0], GitHubURLPrefix) { + // Pull the plugin from a local archive. + pluginFilename = filepath.Clean(args[0]) + if _, err := os.Stat(pluginFilename); os.IsNotExist(err) { + log.Panic("The plugin file could not be found") } + } - // Get the plugin version. - pluginVersion := LatestVersion - splittedURL := strings.Split(args[0], "@") - // If the version is not specified, use the latest version. - if len(splittedURL) < NumParts { - cmd.Println("Version not specified. Using latest version") - } - if len(splittedURL) >= NumParts { - pluginVersion = splittedURL[1] - } + // Validate the URL. + validGitHubURL := regexp.MustCompile(GitHubURLRegex) + if !validGitHubURL.MatchString(args[0]) { + log.Panic( + "Invalid URL. Use the following format: github.com/account/repository@version") + } - // Get the plugin account and repository. - accountRepo := strings.Split(strings.TrimPrefix(splittedURL[0], GitHubURLPrefix), "/") - if len(accountRepo) != NumParts { - log.Panic( - "Invalid URL. Use the following format: github.com/account/repository@version") - } - account = accountRepo[0] - pluginName = accountRepo[1] - if account == "" || pluginName == "" { - log.Panic( - "Invalid URL. Use the following format: github.com/account/repository@version") - } + // Get the plugin version. + pluginVersion := LatestVersion + splittedURL := strings.Split(args[0], "@") + // If the version is not specified, use the latest version. + if len(splittedURL) < NumParts { + cmd.Println("Version not specified. Using latest version") + } + if len(splittedURL) >= NumParts { + pluginVersion = splittedURL[1] + } - // Get the release artifact from GitHub. - client = github.NewClient(nil) - var release *github.RepositoryRelease - - if pluginVersion == LatestVersion || pluginVersion == "" { - // Get the latest release. - release, _, err = client.Repositories.GetLatestRelease( - context.Background(), account, pluginName) - } else if strings.HasPrefix(pluginVersion, "v") { - // Get an specific release. - release, _, err = client.Repositories.GetReleaseByTag( - context.Background(), account, pluginName, pluginVersion) - } - if err != nil { - log.Panic("The plugin could not be found") - } + // Get the plugin account and repository. + accountRepo := strings.Split(strings.TrimPrefix(splittedURL[0], GitHubURLPrefix), "/") + if len(accountRepo) != NumParts { + log.Panic( + "Invalid URL. Use the following format: github.com/account/repository@version") + } + account = accountRepo[0] + pluginName = accountRepo[1] + if account == "" || pluginName == "" { + log.Panic( + "Invalid URL. Use the following format: github.com/account/repository@version") + } - if release == nil { - log.Panic("The plugin could not be found") - } + // Get the release artifact from GitHub. + client = github.NewClient(nil) + var release *github.RepositoryRelease + + if pluginVersion == LatestVersion || pluginVersion == "" { + // Get the latest release. + release, _, err = client.Repositories.GetLatestRelease( + context.Background(), account, pluginName) + } else if strings.HasPrefix(pluginVersion, "v") { + // Get an specific release. + release, _, err = client.Repositories.GetReleaseByTag( + context.Background(), account, pluginName, pluginVersion) + } + if err != nil { + log.Panic("The plugin could not be found") + } - // Get the archive extension. - archiveExt := ExtOthers - if runtime.GOOS == "windows" { - archiveExt = ExtWindows - } + if release == nil { + log.Panic("The plugin could not be found") + } - // Find and download the plugin binary from the release assets. - pluginFilename, downloadURL, releaseID = findAsset(release, func(name string) bool { - return strings.Contains(name, runtime.GOOS) && - strings.Contains(name, runtime.GOARCH) && - strings.Contains(name, archiveExt) - }) - if downloadURL != "" && releaseID != 0 { - cmd.Println("Downloading", downloadURL) - filePath := downloadFile(client, account, pluginName, releaseID, pluginFilename) - toBeDeleted = append(toBeDeleted, filePath) - cmd.Println("Download completed successfully") - } else { - log.Panic("The plugin file could not be found in the release assets") - } + // Get the archive extension. + archiveExt := ExtOthers + if runtime.GOOS == "windows" { + archiveExt = ExtWindows + } - // Find and download the checksums.txt from the release assets. - checksumsFilename, downloadURL, releaseID = findAsset(release, func(name string) bool { - return strings.Contains(name, "checksums.txt") - }) - if checksumsFilename != "" && downloadURL != "" && releaseID != 0 { - cmd.Println("Downloading", downloadURL) - filePath := downloadFile(client, account, pluginName, releaseID, checksumsFilename) - toBeDeleted = append(toBeDeleted, filePath) - cmd.Println("Download completed successfully") - } else { - log.Panic("The checksum file could not be found in the release assets") - } + // Find and download the plugin binary from the release assets. + pluginFilename, downloadURL, releaseID = findAsset(release, func(name string) bool { + return strings.Contains(name, runtime.GOOS) && + strings.Contains(name, runtime.GOARCH) && + strings.Contains(name, archiveExt) + }) + if downloadURL != "" && releaseID != 0 { + cmd.Println("Downloading", downloadURL) + filePath := downloadFile(client, account, pluginName, releaseID, pluginFilename) + toBeDeleted = append(toBeDeleted, filePath) + cmd.Println("Download completed successfully") + } else { + log.Panic("The plugin file could not be found in the release assets") + } - // Read the checksums text file. - checksums, err := os.ReadFile(checksumsFilename) - if err != nil { - log.Panic("There was an error reading the checksums file: ", err) - } + // Find and download the checksums.txt from the release assets. + checksumsFilename, downloadURL, releaseID = findAsset(release, func(name string) bool { + return strings.Contains(name, "checksums.txt") + }) + if checksumsFilename != "" && downloadURL != "" && releaseID != 0 { + cmd.Println("Downloading", downloadURL) + filePath := downloadFile(client, account, pluginName, releaseID, checksumsFilename) + toBeDeleted = append(toBeDeleted, filePath) + cmd.Println("Download completed successfully") + } else { + log.Panic("The checksum file could not be found in the release assets") + } - // Get the checksum for the plugin binary. - sum, err := checksum.SHA256sum(pluginFilename) - if err != nil { - log.Panic("There was an error calculating the checksum: ", err) - } + // Read the checksums text file. + checksums, err := os.ReadFile(checksumsFilename) + if err != nil { + log.Panic("There was an error reading the checksums file: ", err) + } - // Verify the checksums. - checksumLines := strings.Split(string(checksums), "\n") - for _, line := range checksumLines { - if strings.Contains(line, pluginFilename) { - checksum := strings.Split(line, " ")[0] - if checksum != sum { - log.Panic("Checksum verification failed") - } - - cmd.Println("Checksum verification passed") - break - } - } + // Get the checksum for the plugin binary. + sum, err := checksum.SHA256sum(pluginFilename) + if err != nil { + log.Panic("There was an error calculating the checksum: ", err) + } - if pullOnly { - cmd.Println("Plugin binary downloaded to", pluginFilename) - // Only the checksums file will be deleted if the --pull-only flag is set. - if err := os.Remove(checksumsFilename); err != nil { - log.Panic("There was an error deleting the file: ", err) + // Verify the checksums. + checksumLines := strings.Split(string(checksums), "\n") + for _, line := range checksumLines { + if strings.Contains(line, pluginFilename) { + checksum := strings.Split(line, " ")[0] + if checksum != sum { + log.Panic("Checksum verification failed") } - return + + cmd.Println("Checksum verification passed") + break } - } else { - // Pull the plugin from a local archive. - pluginFilename = filepath.Clean(args[0]) - if _, err := os.Stat(pluginFilename); os.IsNotExist(err) { - log.Panic("The plugin file could not be found") + } + + if pullOnly { + cmd.Println("Plugin binary downloaded to", pluginFilename) + // Only the checksums file will be deleted if the --pull-only flag is set. + if err := os.Remove(checksumsFilename); err != nil { + log.Panic("There was an error deleting the file: ", err) } + return } // Extract the archive. From 3aec64388d8fa425fef3f47709907b45c24e98a2 Mon Sep 17 00:00:00 2001 From: Mostafa Moradian Date: Thu, 19 Oct 2023 20:08:31 +0200 Subject: [PATCH 2/7] Strip scheme from the plugin URL --- cmd/plugin_install.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/cmd/plugin_install.go b/cmd/plugin_install.go index 827997fc..1b4110ff 100644 --- a/cmd/plugin_install.go +++ b/cmd/plugin_install.go @@ -77,6 +77,10 @@ var pluginInstallCmd = &cobra.Command{ var client *github.Client var account string + // Strip scheme from the plugin URL. + args[0] = strings.TrimPrefix(args[0], "http://") + args[0] = strings.TrimPrefix(args[0], "https://") + if !strings.HasPrefix(args[0], GitHubURLPrefix) { // Pull the plugin from a local archive. pluginFilename = filepath.Clean(args[0]) From 3a8b6943f27288571a7db886b4d40ac0fef0ee45 Mon Sep 17 00:00:00 2001 From: Mostafa Moradian Date: Thu, 19 Oct 2023 21:43:41 +0200 Subject: [PATCH 3/7] Print and return instead of panicing on certain errors --- cmd/config_init.go | 5 +-- cmd/config_lint.go | 3 +- cmd/plugin_init.go | 5 +-- cmd/plugin_install.go | 87 +++++++++++++++++++++++++++---------------- cmd/plugin_lint.go | 3 +- cmd/plugin_list.go | 5 +-- cmd/run.go | 3 +- 7 files changed, 67 insertions(+), 44 deletions(-) diff --git a/cmd/config_init.go b/cmd/config_init.go index 9cc52db4..fe9ba030 100644 --- a/cmd/config_init.go +++ b/cmd/config_init.go @@ -1,8 +1,6 @@ package cmd import ( - "log" - "github.com/gatewayd-io/gatewayd/config" "github.com/getsentry/sentry-go" "github.com/spf13/cobra" @@ -24,7 +22,8 @@ var configInitCmd = &cobra.Command{ AttachStacktrace: config.DefaultAttachStacktrace, }) if err != nil { - log.Panic("Sentry initialization failed: ", err) + cmd.Println("Sentry initialization failed: ", err) + return } // Flush buffered events before the program terminates. diff --git a/cmd/config_lint.go b/cmd/config_lint.go index 9c4eb5d2..c8b24e58 100644 --- a/cmd/config_lint.go +++ b/cmd/config_lint.go @@ -22,7 +22,8 @@ var configLintCmd = &cobra.Command{ AttachStacktrace: config.DefaultAttachStacktrace, }) if err != nil { - log.Panic("Sentry initialization failed: ", err) + cmd.Println("Sentry initialization failed: ", err) + return } // Flush buffered events before the program terminates. diff --git a/cmd/plugin_init.go b/cmd/plugin_init.go index 8fdde99c..278a92cf 100644 --- a/cmd/plugin_init.go +++ b/cmd/plugin_init.go @@ -1,8 +1,6 @@ package cmd import ( - "log" - "github.com/gatewayd-io/gatewayd/config" "github.com/getsentry/sentry-go" "github.com/spf13/cobra" @@ -22,7 +20,8 @@ var pluginInitCmd = &cobra.Command{ AttachStacktrace: config.DefaultAttachStacktrace, }) if err != nil { - log.Panic("Sentry initialization failed: ", err) + cmd.Println("Sentry initialization failed: ", err) + return } // Flush buffered events before the program terminates. diff --git a/cmd/plugin_install.go b/cmd/plugin_install.go index 1b4110ff..9ab14810 100644 --- a/cmd/plugin_install.go +++ b/cmd/plugin_install.go @@ -2,7 +2,6 @@ package cmd import ( "context" - "log" "os" "path/filepath" "regexp" @@ -53,7 +52,8 @@ var pluginInstallCmd = &cobra.Command{ AttachStacktrace: config.DefaultAttachStacktrace, }) if err != nil { - log.Panic("Sentry initialization failed: ", err) + cmd.Println("Sentry initialization failed: ", err) + return } // Flush buffered events before the program terminates. @@ -64,8 +64,9 @@ var pluginInstallCmd = &cobra.Command{ // Validate the number of arguments. if len(args) < 1 { - log.Panic( + cmd.Println( "Invalid URL. Use the following format: github.com/account/repository@version") + return } var releaseID int64 @@ -85,15 +86,17 @@ var pluginInstallCmd = &cobra.Command{ // Pull the plugin from a local archive. pluginFilename = filepath.Clean(args[0]) if _, err := os.Stat(pluginFilename); os.IsNotExist(err) { - log.Panic("The plugin file could not be found") + cmd.Println("The plugin file could not be found") + return } } // Validate the URL. validGitHubURL := regexp.MustCompile(GitHubURLRegex) if !validGitHubURL.MatchString(args[0]) { - log.Panic( + cmd.Println( "Invalid URL. Use the following format: github.com/account/repository@version") + return } // Get the plugin version. @@ -110,14 +113,16 @@ var pluginInstallCmd = &cobra.Command{ // Get the plugin account and repository. accountRepo := strings.Split(strings.TrimPrefix(splittedURL[0], GitHubURLPrefix), "/") if len(accountRepo) != NumParts { - log.Panic( + cmd.Println( "Invalid URL. Use the following format: github.com/account/repository@version") + return } account = accountRepo[0] pluginName = accountRepo[1] if account == "" || pluginName == "" { - log.Panic( + cmd.Println( "Invalid URL. Use the following format: github.com/account/repository@version") + return } // Get the release artifact from GitHub. @@ -133,12 +138,9 @@ var pluginInstallCmd = &cobra.Command{ release, _, err = client.Repositories.GetReleaseByTag( context.Background(), account, pluginName, pluginVersion) } - if err != nil { - log.Panic("The plugin could not be found") - } - - if release == nil { - log.Panic("The plugin could not be found") + if err != nil || release == nil { + cmd.Println("The plugin could not be found") + return } // Get the archive extension. @@ -159,7 +161,8 @@ var pluginInstallCmd = &cobra.Command{ toBeDeleted = append(toBeDeleted, filePath) cmd.Println("Download completed successfully") } else { - log.Panic("The plugin file could not be found in the release assets") + cmd.Println("The plugin file could not be found in the release assets") + return } // Find and download the checksums.txt from the release assets. @@ -172,19 +175,22 @@ var pluginInstallCmd = &cobra.Command{ toBeDeleted = append(toBeDeleted, filePath) cmd.Println("Download completed successfully") } else { - log.Panic("The checksum file could not be found in the release assets") + cmd.Println("The checksum file could not be found in the release assets") + return } // Read the checksums text file. checksums, err := os.ReadFile(checksumsFilename) if err != nil { - log.Panic("There was an error reading the checksums file: ", err) + cmd.Println("There was an error reading the checksums file: ", err) + return } // Get the checksum for the plugin binary. sum, err := checksum.SHA256sum(pluginFilename) if err != nil { - log.Panic("There was an error calculating the checksum: ", err) + cmd.Println("There was an error calculating the checksum: ", err) + return } // Verify the checksums. @@ -193,7 +199,8 @@ var pluginInstallCmd = &cobra.Command{ if strings.Contains(line, pluginFilename) { checksum := strings.Split(line, " ")[0] if checksum != sum { - log.Panic("Checksum verification failed") + cmd.Println("Checksum verification failed") + return } cmd.Println("Checksum verification passed") @@ -205,7 +212,7 @@ var pluginInstallCmd = &cobra.Command{ cmd.Println("Plugin binary downloaded to", pluginFilename) // Only the checksums file will be deleted if the --pull-only flag is set. if err := os.Remove(checksumsFilename); err != nil { - log.Panic("There was an error deleting the file: ", err) + cmd.Println("There was an error deleting the file: ", err) } return } @@ -239,7 +246,8 @@ var pluginInstallCmd = &cobra.Command{ // TODO: Should we verify the checksum using the checksum.txt file instead? pluginFileSum, err = checksum.SHA256sum(filename) if err != nil { - log.Panic("There was an error calculating the checksum: ", err) + cmd.Println("There was an error calculating the checksum: ", err) + return } break } @@ -253,17 +261,20 @@ var pluginInstallCmd = &cobra.Command{ // Read the gatewayd_plugins.yaml file. pluginsConfig, err := os.ReadFile(pluginConfigFile) if err != nil { - log.Panic(err) + cmd.Println(err) + return } // Get the registered plugins from the plugins configuration file. var localPluginsConfig map[string]interface{} if err := yamlv3.Unmarshal(pluginsConfig, &localPluginsConfig); err != nil { - log.Panic("Failed to unmarshal the plugins configuration file: ", err) + cmd.Println("Failed to unmarshal the plugins configuration file: ", err) + return } pluginsList, ok := localPluginsConfig["plugins"].([]interface{}) //nolint:varnamelen if !ok { - log.Panic("There was an error reading the plugins file from disk") + cmd.Println("There was an error reading the plugins file from disk") + return } var contents string @@ -273,19 +284,25 @@ var pluginInstallCmd = &cobra.Command{ repoContents, _, _, err = client.Repositories.GetContents( context.Background(), account, pluginName, DefaultPluginConfigFilename, nil) if err != nil { - log.Panic("There was an error getting the default plugins configuration file: ", err) + cmd.Println( + "There was an error getting the default plugins configuration file: ", err) + return } // Get the contents of the file. contents, err = repoContents.GetContent() if err != nil { - log.Panic("There was an error getting the default plugins configuration file: ", err) + cmd.Println( + "There was an error getting the default plugins configuration file: ", err) + return } } else { // Get the contents of the file. contentsBytes, err := os.ReadFile( filepath.Join(pluginOutputDir, DefaultPluginConfigFilename)) if err != nil { - log.Panic("There was an error getting the default plugins configuration file: ", err) + cmd.Println( + "There was an error getting the default plugins configuration file: ", err) + return } contents = string(contentsBytes) } @@ -293,16 +310,19 @@ var pluginInstallCmd = &cobra.Command{ // Get the plugin configuration from the downloaded plugins configuration file. var downloadedPluginConfig map[string]interface{} if err := yamlv3.Unmarshal([]byte(contents), &downloadedPluginConfig); err != nil { - log.Panic("Failed to unmarshal the downloaded plugins configuration file: ", err) + cmd.Println("Failed to unmarshal the downloaded plugins configuration file: ", err) + return } defaultPluginConfig, ok := downloadedPluginConfig["plugins"].([]interface{}) if !ok { - log.Panic("There was an error reading the plugins file from the repository") + cmd.Println("There was an error reading the plugins file from the repository") + return } // Get the plugin configuration. pluginConfig, ok := defaultPluginConfig[0].(map[string]interface{}) if !ok { - log.Panic("There was an error reading the default plugin configuration") + cmd.Println("There was an error reading the default plugin configuration") + return } // Update the plugin's local path and checksum. @@ -320,12 +340,14 @@ var pluginInstallCmd = &cobra.Command{ // Marshal the map into YAML. updatedPlugins, err := yamlv3.Marshal(localPluginsConfig) if err != nil { - log.Panic("There was an error marshalling the plugins configuration: ", err) + cmd.Println("There was an error marshalling the plugins configuration: ", err) + return } // Write the YAML to the plugins config file. if err = os.WriteFile(pluginConfigFile, updatedPlugins, FilePermissions); err != nil { - log.Panic("There was an error writing the plugins configuration file: ", err) + cmd.Println("There was an error writing the plugins configuration file: ", err) + return } // Delete the downloaded and extracted files, except the plugin binary, @@ -333,7 +355,8 @@ var pluginInstallCmd = &cobra.Command{ if cleanup { for _, filename := range toBeDeleted { if err := os.Remove(filename); err != nil { - log.Panic("There was an error deleting the file: ", err) + cmd.Println("There was an error deleting the file: ", err) + return } } } diff --git a/cmd/plugin_lint.go b/cmd/plugin_lint.go index b0dfae26..e1c09208 100644 --- a/cmd/plugin_lint.go +++ b/cmd/plugin_lint.go @@ -22,7 +22,8 @@ var pluginLintCmd = &cobra.Command{ AttachStacktrace: config.DefaultAttachStacktrace, }) if err != nil { - log.Panic("Sentry initialization failed: ", err) + cmd.Println("Sentry initialization failed: ", err) + return } // Flush buffered events before the program terminates. diff --git a/cmd/plugin_list.go b/cmd/plugin_list.go index 1b9c7c86..fa3a5f27 100644 --- a/cmd/plugin_list.go +++ b/cmd/plugin_list.go @@ -1,8 +1,6 @@ package cmd import ( - "log" - "github.com/gatewayd-io/gatewayd/config" "github.com/getsentry/sentry-go" "github.com/spf13/cobra" @@ -24,7 +22,8 @@ var pluginListCmd = &cobra.Command{ AttachStacktrace: config.DefaultAttachStacktrace, }) if err != nil { - log.Panic("Sentry initialization failed: ", err) + cmd.Println("Sentry initialization failed: ", err) + return } // Flush buffered events before the program terminates. diff --git a/cmd/run.go b/cmd/run.go index 286ec85f..13fabb1c 100644 --- a/cmd/run.go +++ b/cmd/run.go @@ -176,7 +176,8 @@ var runCmd = &cobra.Command{ }) if err != nil { span.RecordError(err) - log.Panic("Sentry initialization failed: ", err) + cmd.Println("Sentry initialization failed: ", err) + return } // Flush buffered events before the program terminates. From 8e87c8171eb86d38585c17d89ccc59b2bc81f4c6 Mon Sep 17 00:00:00 2001 From: Mostafa Moradian Date: Thu, 19 Oct 2023 23:21:26 +0200 Subject: [PATCH 4/7] Ask user to backup the configuration file if it exists Ask user if they want to update the plugin config or not Update existing plugin config, rather than appending to the plugin list --- cmd/config_lint.go | 1 + cmd/plugin_install.go | 129 +++++++++++++++++++++++++++---------- cmd/plugin_install_test.go | 3 +- cmd/plugin_lint.go | 1 + cmd/run_test.go | 3 +- cmd/utils.go | 10 +++ 6 files changed, 111 insertions(+), 36 deletions(-) diff --git a/cmd/config_lint.go b/cmd/config_lint.go index c8b24e58..a9c67c52 100644 --- a/cmd/config_lint.go +++ b/cmd/config_lint.go @@ -1,3 +1,4 @@ +//nolint:dupl package cmd import ( diff --git a/cmd/plugin_install.go b/cmd/plugin_install.go index 9ab14810..c1e81261 100644 --- a/cmd/plugin_install.go +++ b/cmd/plugin_install.go @@ -2,6 +2,8 @@ package cmd import ( "context" + "fmt" + "log" "os" "path/filepath" "regexp" @@ -32,6 +34,8 @@ var ( pluginOutputDir string pullOnly bool cleanup bool + update bool + backupConfig bool ) // pluginInstallCmd represents the plugin install command. @@ -217,6 +221,78 @@ var pluginInstallCmd = &cobra.Command{ return } + // Create a new gatewayd_plugins.yaml file if it doesn't exist. + if _, err := os.Stat(pluginConfigFile); os.IsNotExist(err) { + generateConfig(cmd, Plugins, pluginConfigFile, false) + } else { + // If the config file exists, we should prompt the user to backup + // the plugins configuration file. + if !backupConfig { + cmd.Print("Do you want to backup the plugins configuration file? [Y/n] ") + var backupOption string + _, err := fmt.Scanln(&backupOption) + if err == nil && (backupOption == "y" || backupOption == "Y") { + backupConfig = true + } + } + } + + // Read the gatewayd_plugins.yaml file. + pluginsConfig, err := os.ReadFile(pluginConfigFile) + if err != nil { + log.Println(err) + return + } + + // Get the registered plugins from the plugins configuration file. + var localPluginsConfig map[string]interface{} + if err := yamlv3.Unmarshal(pluginsConfig, &localPluginsConfig); err != nil { + log.Println("Failed to unmarshal the plugins configuration file: ", err) + return + } + pluginsList, ok := localPluginsConfig["plugins"].([]interface{}) //nolint:varnamelen + if !ok { + log.Println("There was an error reading the plugins file from disk") + return + } + + // Check if the plugin is already installed. + for _, plugin := range pluginsList { + // User already chosen to update the plugin using the --update CLI flag. + if update { + break + } + + if pluginInstance, ok := plugin.(map[string]interface{}); ok { + if pluginInstance["name"] == pluginName { + // Show a list of options to the user. + cmd.Println("Plugin is already installed.") + cmd.Print("Do you want to update the plugin? [y/N] ") + + var updateOption string + _, err := fmt.Scanln(&updateOption) + if err == nil && (updateOption == "y" || updateOption == "Y") { + break + } + + cmd.Println("Aborting...") + if cleanup { + deleteFiles(toBeDeleted) + } + return + } + } + } + + // Check if the user wants to take a backup of the plugins configuration file. + if backupConfig { + backupFilename := fmt.Sprintf("%s.bak", pluginConfigFile) + if err := os.WriteFile(backupFilename, pluginsConfig, FilePermissions); err != nil { + cmd.Println("There was an error backing up the plugins configuration file: ", err) + } + cmd.Println("Backup completed successfully") + } + // Extract the archive. var filenames []string if runtime.GOOS == "windows" { @@ -253,30 +329,6 @@ var pluginInstallCmd = &cobra.Command{ } } - // Create a new gatewayd_plugins.yaml file if it doesn't exist. - if _, err := os.Stat(pluginConfigFile); os.IsNotExist(err) { - generateConfig(cmd, Plugins, pluginConfigFile, false) - } - - // Read the gatewayd_plugins.yaml file. - pluginsConfig, err := os.ReadFile(pluginConfigFile) - if err != nil { - cmd.Println(err) - return - } - - // Get the registered plugins from the plugins configuration file. - var localPluginsConfig map[string]interface{} - if err := yamlv3.Unmarshal(pluginsConfig, &localPluginsConfig); err != nil { - cmd.Println("Failed to unmarshal the plugins configuration file: ", err) - return - } - pluginsList, ok := localPluginsConfig["plugins"].([]interface{}) //nolint:varnamelen - if !ok { - cmd.Println("There was an error reading the plugins file from disk") - return - } - var contents string if strings.HasPrefix(args[0], GitHubURLPrefix) { // Get the list of files in the repository. @@ -329,11 +381,21 @@ var pluginInstallCmd = &cobra.Command{ pluginConfig["localPath"] = localPath pluginConfig["checksum"] = pluginFileSum - // TODO: Check if the plugin is already installed. - // https://github.com/gatewayd-io/gatewayd/issues/312 - // Add the plugin config to the list of plugin configs. - pluginsList = append(pluginsList, pluginConfig) + added := false + for idx, plugin := range pluginsList { + if pluginInstance, ok := plugin.(map[string]interface{}); ok { + if pluginInstance["name"] == pluginName { + pluginsList[idx] = pluginConfig + added = true + break + } + } + } + if !added { + pluginsList = append(pluginsList, pluginConfig) + } + // Merge the result back into the config map. localPluginsConfig["plugins"] = pluginsList @@ -353,12 +415,7 @@ var pluginInstallCmd = &cobra.Command{ // Delete the downloaded and extracted files, except the plugin binary, // if the --cleanup flag is set. if cleanup { - for _, filename := range toBeDeleted { - if err := os.Remove(filename); err != nil { - cmd.Println("There was an error deleting the file: ", err) - return - } - } + deleteFiles(toBeDeleted) } // TODO: Add a rollback mechanism. @@ -380,6 +437,10 @@ func init() { pluginInstallCmd.Flags().BoolVar( &cleanup, "cleanup", true, "Delete downloaded and extracted files after installing the plugin (except the plugin binary)") + pluginInstallCmd.Flags().BoolVar( + &update, "update", false, "Update the plugin if it already exists") + pluginInstallCmd.Flags().BoolVar( + &backupConfig, "backup", false, "Backup the plugins configuration file before installing the plugin") pluginInstallCmd.Flags().BoolVar( &enableSentry, "sentry", true, "Enable Sentry") // Already exists in run.go } diff --git a/cmd/plugin_install_test.go b/cmd/plugin_install_test.go index 7e0c37f4..703c60e9 100644 --- a/cmd/plugin_install_test.go +++ b/cmd/plugin_install_test.go @@ -21,7 +21,8 @@ func Test_pluginInstallCmd(t *testing.T) { // Test plugin install command. output, err = executeCommandC( rootCmd, "plugin", "install", - "github.com/gatewayd-io/gatewayd-plugin-cache@v0.2.4", "-p", pluginTestConfigFile) + "github.com/gatewayd-io/gatewayd-plugin-cache@v0.2.4", + "-p", pluginTestConfigFile, "--update") assert.NoError(t, err, "plugin install should not return an error") assert.Contains(t, output, "Downloading https://github.com/gatewayd-io/gatewayd-plugin-cache/releases/download/v0.2.4/gatewayd-plugin-cache-linux-amd64-v0.2.4.tar.gz") //nolint:lll assert.Contains(t, output, "Downloading https://github.com/gatewayd-io/gatewayd-plugin-cache/releases/download/v0.2.4/checksums.txt") //nolint:lll diff --git a/cmd/plugin_lint.go b/cmd/plugin_lint.go index e1c09208..9dbb5d4f 100644 --- a/cmd/plugin_lint.go +++ b/cmd/plugin_lint.go @@ -1,3 +1,4 @@ +//nolint:dupl package cmd import ( diff --git a/cmd/run_test.go b/cmd/run_test.go index 40761ca6..baaff4a3 100644 --- a/cmd/run_test.go +++ b/cmd/run_test.go @@ -147,7 +147,8 @@ func Test_runCmdWithCachePlugin(t *testing.T) { // Test plugin install command. output, err := executeCommandC( rootCmd, "plugin", "install", - "github.com/gatewayd-io/gatewayd-plugin-cache@v0.2.4", "-p", pluginTestConfigFile) + "github.com/gatewayd-io/gatewayd-plugin-cache@v0.2.4", + "-p", pluginTestConfigFile, "--update") assert.NoError(t, err, "plugin install should not return an error") assert.Contains(t, output, "Downloading https://github.com/gatewayd-io/gatewayd-plugin-cache/releases/download/v0.2.4/gatewayd-plugin-cache-linux-amd64-v0.2.4.tar.gz") //nolint:lll assert.Contains(t, output, "Downloading https://github.com/gatewayd-io/gatewayd-plugin-cache/releases/download/v0.2.4/checksums.txt") //nolint:lll diff --git a/cmd/utils.go b/cmd/utils.go index 2df1508e..54ba8f50 100644 --- a/cmd/utils.go +++ b/cmd/utils.go @@ -447,3 +447,13 @@ func downloadFile( return filePath } + +// deleteFiles deletes the files in the toBeDeleted list. +func deleteFiles(toBeDeleted []string) { + for _, filename := range toBeDeleted { + if err := os.Remove(filename); err != nil { + log.Println("There was an error deleting the file: ", err) + return + } + } +} From 9de4f3c576c41f6d9ea9d01793e101665c14198f Mon Sep 17 00:00:00 2001 From: Mostafa Moradian Date: Fri, 20 Oct 2023 00:13:24 +0200 Subject: [PATCH 5/7] Include cause of failure (error) --- cmd/plugin_install.go | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/cmd/plugin_install.go b/cmd/plugin_install.go index c1e81261..61fc695d 100644 --- a/cmd/plugin_install.go +++ b/cmd/plugin_install.go @@ -142,8 +142,14 @@ var pluginInstallCmd = &cobra.Command{ release, _, err = client.Repositories.GetReleaseByTag( context.Background(), account, pluginName, pluginVersion) } - if err != nil || release == nil { - cmd.Println("The plugin could not be found") + + if err != nil { + cmd.Println("The plugin could not be found: ", err.Error()) + return + } + + if release == nil { + cmd.Println("The plugin could not be found in the release assets") return } From d1b17ec00207a68c885dacc66ccab682b7d1eb42 Mon Sep 17 00:00:00 2001 From: Mostafa Moradian Date: Fri, 20 Oct 2023 00:14:07 +0200 Subject: [PATCH 6/7] Add --no-prompt flag to prevent prompting the user --- cmd/plugin_install.go | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/cmd/plugin_install.go b/cmd/plugin_install.go index 61fc695d..838cfb85 100644 --- a/cmd/plugin_install.go +++ b/cmd/plugin_install.go @@ -36,6 +36,7 @@ var ( cleanup bool update bool backupConfig bool + noPrompt bool ) // pluginInstallCmd represents the plugin install command. @@ -233,7 +234,7 @@ var pluginInstallCmd = &cobra.Command{ } else { // If the config file exists, we should prompt the user to backup // the plugins configuration file. - if !backupConfig { + if !backupConfig && !noPrompt { cmd.Print("Do you want to backup the plugins configuration file? [Y/n] ") var backupOption string _, err := fmt.Scanln(&backupOption) @@ -273,12 +274,14 @@ var pluginInstallCmd = &cobra.Command{ if pluginInstance["name"] == pluginName { // Show a list of options to the user. cmd.Println("Plugin is already installed.") - cmd.Print("Do you want to update the plugin? [y/N] ") - - var updateOption string - _, err := fmt.Scanln(&updateOption) - if err == nil && (updateOption == "y" || updateOption == "Y") { - break + if !noPrompt { + cmd.Print("Do you want to update the plugin? [y/N] ") + + var updateOption string + _, err := fmt.Scanln(&updateOption) + if err == nil && (updateOption == "y" || updateOption == "Y") { + break + } } cmd.Println("Aborting...") @@ -443,6 +446,8 @@ func init() { pluginInstallCmd.Flags().BoolVar( &cleanup, "cleanup", true, "Delete downloaded and extracted files after installing the plugin (except the plugin binary)") + pluginInstallCmd.Flags().BoolVar( + &noPrompt, "no-prompt", true, "Do not prompt for user input") pluginInstallCmd.Flags().BoolVar( &update, "update", false, "Update the plugin if it already exists") pluginInstallCmd.Flags().BoolVar( From 50efecaa1c93933fdbb748fc4ccfdcaf329271a5 Mon Sep 17 00:00:00 2001 From: Mostafa Moradian Date: Fri, 20 Oct 2023 10:42:15 +0200 Subject: [PATCH 7/7] Check if config file is backed up --- cmd/plugin_install_test.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/cmd/plugin_install_test.go b/cmd/plugin_install_test.go index 703c60e9..25049de0 100644 --- a/cmd/plugin_install_test.go +++ b/cmd/plugin_install_test.go @@ -22,7 +22,7 @@ func Test_pluginInstallCmd(t *testing.T) { output, err = executeCommandC( rootCmd, "plugin", "install", "github.com/gatewayd-io/gatewayd-plugin-cache@v0.2.4", - "-p", pluginTestConfigFile, "--update") + "-p", pluginTestConfigFile, "--update", "--backup") assert.NoError(t, err, "plugin install should not return an error") assert.Contains(t, output, "Downloading https://github.com/gatewayd-io/gatewayd-plugin-cache/releases/download/v0.2.4/gatewayd-plugin-cache-linux-amd64-v0.2.4.tar.gz") //nolint:lll assert.Contains(t, output, "Downloading https://github.com/gatewayd-io/gatewayd-plugin-cache/releases/download/v0.2.4/checksums.txt") //nolint:lll @@ -38,6 +38,7 @@ func Test_pluginInstallCmd(t *testing.T) { // Clean up. assert.FileExists(t, "plugins/gatewayd-plugin-cache") + assert.FileExists(t, fmt.Sprintf("%s.bak", pluginTestConfigFile)) assert.NoFileExists(t, "gatewayd-plugin-cache-linux-amd64-v0.2.4.tar.gz") assert.NoFileExists(t, "checksums.txt") assert.NoFileExists(t, "plugins/LICENSE") @@ -47,4 +48,5 @@ func Test_pluginInstallCmd(t *testing.T) { assert.NoError(t, os.RemoveAll("plugins/")) assert.NoError(t, os.Remove(pluginTestConfigFile)) + assert.NoError(t, os.Remove(fmt.Sprintf("%s.bak", pluginTestConfigFile))) }