diff --git a/cmd/cli/plugin/package/display_utils.go b/cmd/cli/plugin/package/display_utils.go new file mode 100644 index 0000000000..47ae5689c1 --- /dev/null +++ b/cmd/cli/plugin/package/display_utils.go @@ -0,0 +1,77 @@ +// Copyright 2021 VMware, Inc. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package main + +import ( + "fmt" + "time" + + "github.com/briandowns/spinner" + + "github.com/vmware-tanzu/tanzu-framework/pkg/v1/tkg/log" + "github.com/vmware-tanzu/tanzu-framework/pkg/v1/tkg/tkgpackagedatamodel" +) + +// DisplayProgress creates an spinner instance; keeps receiving the progress messages in the channel and displays those using the spinner until an error occurs +func DisplayProgress(initialMsg string, pp *tkgpackagedatamodel.PackageProgress) error { + var ( + currMsg string + s *spinner.Spinner + err error + ) + + newSpinner := func() (*spinner.Spinner, error) { + s = spinner.New(spinner.CharSets[9], 100*time.Millisecond) + if err := s.Color("bgBlack", "bold", "fgWhite"); err != nil { + return nil, err + } + return s, nil + } + if s, err = newSpinner(); err != nil { + return err + } + + writeProgress := func(s *spinner.Spinner, msg string) error { + s.Stop() + if s, err = newSpinner(); err != nil { + return err + } + log.Infof("\n") + s.Suffix = fmt.Sprintf(" %s", msg) + s.Start() + return nil + } + + s.Suffix = fmt.Sprintf(" %s", initialMsg) + s.Start() + + defer func() { + s.Stop() + }() + for { + select { + case err := <-pp.Err: + s.FinalMSG = "\n\n" + return err + case msg := <-pp.ProgressMsg: + if msg != currMsg { + if err := writeProgress(s, msg); err != nil { + return err + } + currMsg = msg + } + case <-pp.Done: + for msg := range pp.ProgressMsg { + if msg == currMsg { + continue + } + if err := writeProgress(s, msg); err != nil { + return err + } + currMsg = msg + } + return nil + } + } +} diff --git a/cmd/cli/plugin/package/package_install.go b/cmd/cli/plugin/package/package_install.go index 63f5a88631..10dee2b364 100644 --- a/cmd/cli/plugin/package/package_install.go +++ b/cmd/cli/plugin/package/package_install.go @@ -5,9 +5,7 @@ package main import ( "fmt" - "time" - "github.com/briandowns/spinner" "github.com/spf13/cobra" "github.com/vmware-tanzu/tanzu-framework/pkg/v1/tkg/log" @@ -45,9 +43,11 @@ func init() { packageInstallCmd.MarkFlagRequired("version") //nolint } -func packageInstall(_ *cobra.Command, args []string) error { +func packageInstall(cmd *cobra.Command, args []string) error { packageInstallOp.PkgInstallName = args[0] + cmd.SilenceUsage = true + pkgClient, err := tkgpackageclient.NewTKGPackageClient(packageInstallOp.KubeConfig) if err != nil { return err @@ -58,14 +58,10 @@ func packageInstall(_ *cobra.Command, args []string) error { Err: make(chan error), Done: make(chan struct{}), } - go pkgClient.InstallPackage(packageInstallOp, pp, false) + go pkgClient.InstallPackage(packageInstallOp, pp, tkgpackagedatamodel.OperationTypeInstall) initialMsg := fmt.Sprintf("Installing package '%s'", packageInstallOp.PackageName) - if err := displayProgress(initialMsg, pp); err != nil { - if err.Error() == tkgpackagedatamodel.ErrPackageAlreadyInstalled { - log.Warningf("package install '%s' already exists in namespace '%s'", packageInstallOp.PkgInstallName, packageInstallOp.Namespace) - return nil - } + if err := DisplayProgress(initialMsg, pp); err != nil { return err } @@ -73,65 +69,3 @@ func packageInstall(_ *cobra.Command, args []string) error { packageInstallOp.PkgInstallName, packageInstallOp.Namespace)) return nil } - -func displayProgress(initialMsg string, pp *tkgpackagedatamodel.PackageProgress) error { - var ( - currMsg string - s *spinner.Spinner - err error - ) - - newSpinner := func() (*spinner.Spinner, error) { - s = spinner.New(spinner.CharSets[9], 100*time.Millisecond) - if err := s.Color("bgBlack", "bold", "fgWhite"); err != nil { - return nil, err - } - return s, nil - } - if s, err = newSpinner(); err != nil { - return err - } - - writeProgress := func(s *spinner.Spinner, msg string) error { - s.Stop() - if s, err = newSpinner(); err != nil { - return err - } - log.Infof("\n") - s.Suffix = fmt.Sprintf(" %s", msg) - s.Start() - return nil - } - - s.Suffix = fmt.Sprintf(" %s", initialMsg) - s.Start() - - defer func() { - s.Stop() - }() - for { - select { - case err := <-pp.Err: - s.FinalMSG = "\n\n" - return err - case msg := <-pp.ProgressMsg: - if msg != currMsg { - if err := writeProgress(s, msg); err != nil { - return err - } - currMsg = msg - } - case <-pp.Done: - for msg := range pp.ProgressMsg { - if msg == currMsg { - continue - } - if err := writeProgress(s, msg); err != nil { - return err - } - currMsg = msg - } - return nil - } - } -} diff --git a/cmd/cli/plugin/package/package_installed_delete.go b/cmd/cli/plugin/package/package_installed_delete.go index 4357a72e31..ca4ed55cd9 100644 --- a/cmd/cli/plugin/package/package_installed_delete.go +++ b/cmd/cli/plugin/package/package_installed_delete.go @@ -33,13 +33,10 @@ func init() { packageInstalledCmd.AddCommand(packageInstalledDeleteCmd) } -func packageUninstall(_ *cobra.Command, args []string) error { +func packageUninstall(cmd *cobra.Command, args []string) error { packageInstalledOp.PkgInstallName = args[0] - pkgClient, err := tkgpackageclient.NewTKGPackageClient(packageInstalledOp.KubeConfig) - if err != nil { - return err - } + cmd.SilenceUsage = true if !packageInstalledOp.SkipPrompt { if err := cli.AskForConfirmation(fmt.Sprintf("Deleting installed package '%s' in namespace '%s'. Are you sure?", @@ -48,6 +45,11 @@ func packageUninstall(_ *cobra.Command, args []string) error { } } + pkgClient, err := tkgpackageclient.NewTKGPackageClient(packageInstalledOp.KubeConfig) + if err != nil { + return err + } + pp := &tkgpackagedatamodel.PackageProgress{ ProgressMsg: make(chan string, 10), Err: make(chan error), @@ -56,9 +58,9 @@ func packageUninstall(_ *cobra.Command, args []string) error { go pkgClient.UninstallPackage(packageInstalledOp, pp) initialMsg := fmt.Sprintf("Uninstalling package '%s' from namespace '%s'", packageInstalledOp.PkgInstallName, packageInstalledOp.Namespace) - if err := displayProgress(initialMsg, pp); err != nil { + if err := DisplayProgress(initialMsg, pp); err != nil { if err.Error() == tkgpackagedatamodel.ErrPackageNotInstalled { - log.Warningf(fmt.Sprintf("package '%s' is not installed in namespace '%s'. Deleted previously installed resources", packageInstalledOp.PkgInstallName, packageInstalledOp.Namespace)) + log.Warningf("\npackage '%s' is not installed in namespace '%s'. Cleaned up related resources", packageInstalledOp.PkgInstallName, packageInstalledOp.Namespace) return nil } return err diff --git a/cmd/cli/plugin/package/package_installed_get.go b/cmd/cli/plugin/package/package_installed_get.go index e2be541802..95de0ae7a0 100644 --- a/cmd/cli/plugin/package/package_installed_get.go +++ b/cmd/cli/plugin/package/package_installed_get.go @@ -35,6 +35,8 @@ func init() { } func packageInstalledGet(cmd *cobra.Command, args []string) error { + cmd.SilenceUsage = true + kc, err := kappclient.NewKappClient(packageInstalledOp.KubeConfig) if err != nil { return err diff --git a/cmd/cli/plugin/package/package_installed_list.go b/cmd/cli/plugin/package/package_installed_list.go index 9219053726..d9975900d0 100644 --- a/cmd/cli/plugin/package/package_installed_list.go +++ b/cmd/cli/plugin/package/package_installed_list.go @@ -31,6 +31,8 @@ func init() { } func packageInstalledList(cmd *cobra.Command, args []string) error { + cmd.SilenceUsage = true + kc, err := kappclient.NewKappClient(packageInstalledOp.KubeConfig) if err != nil { return err diff --git a/cmd/cli/plugin/package/package_installed_update.go b/cmd/cli/plugin/package/package_installed_update.go index f5d257f536..20f56f7572 100644 --- a/cmd/cli/plugin/package/package_installed_update.go +++ b/cmd/cli/plugin/package/package_installed_update.go @@ -36,9 +36,11 @@ func init() { packageInstalledCmd.AddCommand(packageInstalledUpdateCmd) } -func packageUpdate(_ *cobra.Command, args []string) error { +func packageUpdate(cmd *cobra.Command, args []string) error { packageInstalledOp.PkgInstallName = args[0] + cmd.SilenceUsage = true + pkgClient, err := tkgpackageclient.NewTKGPackageClient(packageInstalledOp.KubeConfig) if err != nil { return err @@ -52,10 +54,14 @@ func packageUpdate(_ *cobra.Command, args []string) error { go pkgClient.UpdatePackage(packageInstalledOp, pp) initialMsg := fmt.Sprintf("Updating package '%s'", packageInstalledOp.PkgInstallName) - if err := displayProgress(initialMsg, pp); err != nil { + if err := DisplayProgress(initialMsg, pp); err != nil { + if err.Error() == tkgpackagedatamodel.ErrPackageNotInstalled { + log.Warningf("\npackage '%s' is not among the list of installed packages in namespace '%s'. Consider using the --install flag to install the package", packageInstalledOp.PkgInstallName, packageInstalledOp.Namespace) + return nil + } return err } - log.Infof("\n %s", fmt.Sprintf("Updated package install '%s' in namespace '%s'", packageInstalledOp.PkgInstallName, packageInstalledOp.Namespace)) + log.Infof("\n %s", fmt.Sprintf("Updated package install '%s' in namespace '%s'", packageInstalledOp.PkgInstallName, packageInstalledOp.Namespace)) return nil } diff --git a/cmd/cli/plugin/package/repository_add.go b/cmd/cli/plugin/package/repository_add.go index 491d03cfa0..d18c9b27cc 100644 --- a/cmd/cli/plugin/package/repository_add.go +++ b/cmd/cli/plugin/package/repository_add.go @@ -8,9 +8,9 @@ import ( "github.com/spf13/cobra" - "github.com/vmware-tanzu/tanzu-framework/pkg/v1/cli/component" "github.com/vmware-tanzu/tanzu-framework/pkg/v1/tkg/log" "github.com/vmware-tanzu/tanzu-framework/pkg/v1/tkg/tkgpackageclient" + "github.com/vmware-tanzu/tanzu-framework/pkg/v1/tkg/tkgpackagedatamodel" ) var repositoryAddCmd = &cobra.Command{ @@ -26,6 +26,9 @@ var repositoryAddCmd = &cobra.Command{ func init() { repositoryAddCmd.Flags().StringVarP(&repoOp.RepositoryURL, "url", "", "", "OCI registry url for package repository bundle") repositoryAddCmd.Flags().BoolVarP(&repoOp.CreateNamespace, "create-namespace", "", false, "Create namespace if the target namespace does not exist, optional") + repositoryAddCmd.Flags().BoolVarP(&repoOp.Wait, "wait", "", true, "Wait for the package repository reconciliation to complete, optional. To disable wait, specify --wait=false") + repositoryAddCmd.Flags().DurationVarP(&repoOp.PollInterval, "poll-interval", "", tkgpackagedatamodel.DefaultPollInterval, "Time interval between subsequent polls of package repository reconciliation status, optional") + repositoryAddCmd.Flags().DurationVarP(&repoOp.PollTimeout, "poll-timeout", "", tkgpackagedatamodel.DefaultPollTimeout, "Timeout value for polls of package repository reconciliation status, optional") repositoryAddCmd.MarkFlagRequired("url") //nolint repositoryCmd.AddCommand(repositoryAddCmd) } @@ -33,22 +36,25 @@ func init() { func repositoryAdd(cmd *cobra.Command, args []string) error { repoOp.RepositoryName = args[0] + cmd.SilenceUsage = true + pkgClient, err := tkgpackageclient.NewTKGPackageClient(repoOp.KubeConfig) if err != nil { return err } - _, err = component.NewOutputWriterWithSpinner(cmd.OutOrStdout(), outputFormat, - fmt.Sprintf("Adding package repository '%s'...", repoOp.RepositoryName), true) - if err != nil { - return err + pp := &tkgpackagedatamodel.PackageProgress{ + ProgressMsg: make(chan string, 10), + Err: make(chan error), + Done: make(chan struct{}), } + go pkgClient.AddRepository(repoOp, pp, tkgpackagedatamodel.OperationTypeInstall) - if err := pkgClient.AddRepository(repoOp); err != nil { + initialMsg := fmt.Sprintf("Adding package repository '%s'", repoOp.RepositoryName) + if err := DisplayProgress(initialMsg, pp); err != nil { return err } log.Infof("\n Added package repository '%s'", repoOp.RepositoryName) - return nil } diff --git a/cmd/cli/plugin/package/repository_delete.go b/cmd/cli/plugin/package/repository_delete.go index 8f03eb0d5c..d0bf6d7496 100644 --- a/cmd/cli/plugin/package/repository_delete.go +++ b/cmd/cli/plugin/package/repository_delete.go @@ -9,9 +9,10 @@ import ( "github.com/pkg/errors" "github.com/spf13/cobra" - "github.com/vmware-tanzu/tanzu-framework/pkg/v1/cli/component" + "github.com/vmware-tanzu/tanzu-framework/pkg/v1/cli" "github.com/vmware-tanzu/tanzu-framework/pkg/v1/tkg/log" "github.com/vmware-tanzu/tanzu-framework/pkg/v1/tkg/tkgpackageclient" + "github.com/vmware-tanzu/tanzu-framework/pkg/v1/tkg/tkgpackagedatamodel" ) var repositoryDeleteCmd = &cobra.Command{ @@ -26,6 +27,10 @@ var repositoryDeleteCmd = &cobra.Command{ func init() { repositoryDeleteCmd.Flags().BoolVarP(&repoOp.IsForceDelete, "force", "f", false, "Force deletion of the package repository, optional") + repositoryDeleteCmd.Flags().BoolVarP(&repoOp.Wait, "wait", "", true, "Wait for the package repository reconciliation to complete, optional. To disable wait, specify --wait=false") + repositoryDeleteCmd.Flags().DurationVarP(&repoOp.PollInterval, "poll-interval", "", tkgpackagedatamodel.DefaultPollInterval, "Time interval between subsequent polls of package repository reconciliation status, optional") + repositoryDeleteCmd.Flags().DurationVarP(&repoOp.PollTimeout, "poll-timeout", "", tkgpackagedatamodel.DefaultPollTimeout, "Timeout value for polls of package repository reconciliation status, optional") + repositoryDeleteCmd.Flags().BoolVarP(&repoOp.SkipPrompt, "yes", "y", false, "Delete package repository without asking for confirmation, optional") repositoryCmd.AddCommand(repositoryDeleteCmd) } @@ -36,27 +41,37 @@ func repositoryDelete(cmd *cobra.Command, args []string) error { return errors.New("incorrect number of input parameters. Usage: tanzu package repository delete REPO_NAME [FLAGS]") } - pkgClient, err := tkgpackageclient.NewTKGPackageClient(repoOp.KubeConfig) - if err != nil { - return err + cmd.SilenceUsage = true + + if !repoOp.SkipPrompt { + if err := cli.AskForConfirmation(fmt.Sprintf("Deleting package repository '%s' in namespace '%s'. Are you sure?", + repoOp.RepositoryName, repoOp.Namespace)); err != nil { + return err + } } - _, err = component.NewOutputWriterWithSpinner(cmd.OutOrStdout(), outputFormat, - fmt.Sprintf("Deleting package repository '%s'...", repoOp.RepositoryName), true) + pkgClient, err := tkgpackageclient.NewTKGPackageClient(repoOp.KubeConfig) if err != nil { return err } - found, err := pkgClient.DeleteRepository(repoOp) - if !found { - log.Warningf("\n package repository '%s' does not exist in namespace '%s'", repoOp.RepositoryName, repoOp.Namespace) - return nil + pp := &tkgpackagedatamodel.PackageProgress{ + ProgressMsg: make(chan string, 10), + Err: make(chan error), + Done: make(chan struct{}), } - if err != nil { + + go pkgClient.DeleteRepository(repoOp, pp) + + initialMsg := fmt.Sprintf("Deleting package repository '%s'", repoOp.RepositoryName) + if err := DisplayProgress(initialMsg, pp); err != nil { + if err.Error() == tkgpackagedatamodel.ErrRepoNotExists { + log.Warningf("\npackage repository '%s' does not exist in namespace '%s'", repoOp.RepositoryName, repoOp.Namespace) + return nil + } return err } log.Infof("\n Deleted package repository '%s' from namespace '%s'", repoOp.RepositoryName, repoOp.Namespace) - return nil } diff --git a/cmd/cli/plugin/package/repository_get.go b/cmd/cli/plugin/package/repository_get.go index acc3bd861c..99c8d28b1f 100644 --- a/cmd/cli/plugin/package/repository_get.go +++ b/cmd/cli/plugin/package/repository_get.go @@ -34,6 +34,9 @@ func repositoryGet(cmd *cobra.Command, args []string) error { } else { return errors.New("incorrect number of input parameters. Usage: tanzu package repository get REPOSITORY_NAME [FLAGS]") } + + cmd.SilenceUsage = true + pkgClient, err := tkgpackageclient.NewTKGPackageClient(repoOp.KubeConfig) if err != nil { return err diff --git a/cmd/cli/plugin/package/repository_list.go b/cmd/cli/plugin/package/repository_list.go index d7dec9c50a..5d55fa1ef7 100644 --- a/cmd/cli/plugin/package/repository_list.go +++ b/cmd/cli/plugin/package/repository_list.go @@ -33,6 +33,8 @@ func init() { } func repositoryList(cmd *cobra.Command, _ []string) error { + cmd.SilenceUsage = true + pkgClient, err := tkgpackageclient.NewTKGPackageClient(repoOp.KubeConfig) if err != nil { return err diff --git a/cmd/cli/plugin/package/repository_update.go b/cmd/cli/plugin/package/repository_update.go index 62d8c1af95..0b54a705fe 100644 --- a/cmd/cli/plugin/package/repository_update.go +++ b/cmd/cli/plugin/package/repository_update.go @@ -8,9 +8,9 @@ import ( "github.com/spf13/cobra" - "github.com/vmware-tanzu/tanzu-framework/pkg/v1/cli/component" "github.com/vmware-tanzu/tanzu-framework/pkg/v1/tkg/log" "github.com/vmware-tanzu/tanzu-framework/pkg/v1/tkg/tkgpackageclient" + "github.com/vmware-tanzu/tanzu-framework/pkg/v1/tkg/tkgpackagedatamodel" ) var repositoryUpdateCmd = &cobra.Command{ @@ -27,6 +27,9 @@ func init() { repositoryUpdateCmd.Flags().StringVarP(&repoOp.RepositoryURL, "url", "", "", "OCI registry url for package repository bundle") repositoryUpdateCmd.Flags().BoolVarP(&repoOp.CreateRepository, "create", "", false, "Creates the package repository if it does not exist, optional") repositoryUpdateCmd.Flags().BoolVarP(&repoOp.CreateNamespace, "create-namespace", "", false, "Create namespace if the target namespace does not exist, optional") + repositoryUpdateCmd.Flags().BoolVarP(&repoOp.Wait, "wait", "", true, "Wait for the package repository reconciliation to complete, optional. To disable wait, specify --wait=false") + repositoryUpdateCmd.Flags().DurationVarP(&repoOp.PollInterval, "poll-interval", "", tkgpackagedatamodel.DefaultPollInterval, "Time interval between subsequent polls of package repository reconciliation status, optional") + repositoryUpdateCmd.Flags().DurationVarP(&repoOp.PollTimeout, "poll-timeout", "", tkgpackagedatamodel.DefaultPollTimeout, "Timeout value for polls of package repository reconciliation status, optional") repositoryUpdateCmd.MarkFlagRequired("url") //nolint repositoryCmd.AddCommand(repositoryUpdateCmd) } @@ -34,22 +37,29 @@ func init() { func repositoryUpdate(cmd *cobra.Command, args []string) error { repoOp.RepositoryName = args[0] + cmd.SilenceUsage = true + pkgClient, err := tkgpackageclient.NewTKGPackageClient(repoOp.KubeConfig) if err != nil { return err } - _, err = component.NewOutputWriterWithSpinner(cmd.OutOrStdout(), outputFormat, - fmt.Sprintf("Updating package repository '%s'...", repoOp.RepositoryName), true) - if err != nil { - return err + pp := &tkgpackagedatamodel.PackageProgress{ + ProgressMsg: make(chan string, 10), + Err: make(chan error), + Done: make(chan struct{}), } + go pkgClient.UpdateRepository(repoOp, pp) - if err := pkgClient.UpdateRepository(repoOp); err != nil { + initialMsg := fmt.Sprintf("Updating package repository '%s'", repoOp.RepositoryName) + if err := DisplayProgress(initialMsg, pp); err != nil { + if err.Error() == tkgpackagedatamodel.ErrRepoNotExists { + log.Warningf("\npackage repository '%s' does not exist in namespace '%s'. Consider using the --create flag to add the package repository", repoOp.RepositoryName, repoOp.Namespace) + return nil + } return err } log.Infof("\n Updated package repository '%s' in namespace '%s'", repoOp.RepositoryName, repoOp.Namespace) - return nil } diff --git a/cmd/cli/plugin/package/test/lib/package_plugin.go b/cmd/cli/plugin/package/test/lib/package_plugin.go index 26981b2216..3975aa5120 100644 --- a/cmd/cli/plugin/package/test/lib/package_plugin.go +++ b/cmd/cli/plugin/package/test/lib/package_plugin.go @@ -123,6 +123,9 @@ func (p *packagePlugin) AddRepository(o *tkgpackagedatamodel.RepositoryOptions) if o.CreateNamespace { cmd += fmt.Sprintf(" --create-namespace") } + if !o.Wait { + cmd += fmt.Sprintf(" --wait=true") + } cmd = p.addKubeconfig(cmd) cmd = p.addGlobalOptions(cmd) result.Stdout, result.Stderr, result.Error = clitest.Exec(cmd) @@ -154,6 +157,9 @@ func (p *packagePlugin) UpdateRepository(o *tkgpackagedatamodel.RepositoryOption if o.CreateRepository { cmd += fmt.Sprintf(" --create") } + if !o.Wait { + cmd += fmt.Sprintf(" --wait=true") + } cmd = p.addKubeconfig(cmd) cmd = p.addGlobalOptions(cmd) result.Stdout, result.Stderr, result.Error = clitest.Exec(cmd) @@ -169,6 +175,12 @@ func (p *packagePlugin) DeleteRepository(o *tkgpackagedatamodel.RepositoryOption if o.IsForceDelete { cmd += fmt.Sprintf(" --force") } + if !o.Wait { + cmd += fmt.Sprintf(" --wait=true") + } + if o.SkipPrompt { + cmd += fmt.Sprintf(" -y") + } cmd = p.addKubeconfig(cmd) cmd = p.addGlobalOptions(cmd) result.Stdout, result.Stderr, result.Error = clitest.Exec(cmd) @@ -253,7 +265,7 @@ func (p *packagePlugin) CreateInstalledPackage(o *tkgpackagedatamodel.PackageOpt cmd += fmt.Sprintf(" --service-account-name %s", o.ServiceAccountName) } if !o.Wait { - cmd += fmt.Sprintf(" --wait=false") + cmd += fmt.Sprintf(" --wait=true") } if o.PollInterval != 0 { cmd += fmt.Sprintf(" --poll-interval %s", o.PollInterval) @@ -295,6 +307,9 @@ func (p *packagePlugin) UpdateInstalledPackage(o *tkgpackagedatamodel.PackageOpt if o.ValuesFile != "" { cmd += fmt.Sprintf(" --values-file %s", o.ValuesFile) } + if !o.Wait { + cmd += fmt.Sprintf(" --wait=true") + } cmd = p.addKubeconfig(cmd) cmd = p.addGlobalOptions(cmd) result.Stdout, result.Stderr, result.Error = clitest.Exec(cmd) diff --git a/pkg/v1/tkg/test/tkgpackageclient/package_plugin_integration_test.go b/pkg/v1/tkg/test/tkgpackageclient/package_plugin_integration_test.go index 9d89e13714..e651c66ddb 100644 --- a/pkg/v1/tkg/test/tkgpackageclient/package_plugin_integration_test.go +++ b/pkg/v1/tkg/test/tkgpackageclient/package_plugin_integration_test.go @@ -368,6 +368,7 @@ func testHelper() { Expect(result.Error).ToNot(HaveOccurred()) By("delete package repository") + repoOptions.SkipPrompt = true result = packagePlugin.DeleteRepository(&repoOptions) Expect(result.Error).ToNot(HaveOccurred()) diff --git a/pkg/v1/tkg/tkgpackageclient/interface.go b/pkg/v1/tkg/tkgpackageclient/interface.go index c71c1520ed..7fd2d31081 100644 --- a/pkg/v1/tkg/tkgpackageclient/interface.go +++ b/pkg/v1/tkg/tkgpackageclient/interface.go @@ -17,13 +17,13 @@ import ( // TKGPackageClient is the TKG package client interface type TKGPackageClient interface { AddImagePullSecret(o *tkgpackagedatamodel.ImagePullSecretOptions) error - AddRepository(o *tkgpackagedatamodel.RepositoryOptions) error + AddRepository(o *tkgpackagedatamodel.RepositoryOptions, packageProgress *tkgpackagedatamodel.PackageProgress, operationType tkgpackagedatamodel.OperationType) DeleteImagePullSecret(o *tkgpackagedatamodel.ImagePullSecretOptions) (bool, error) - DeleteRepository(o *tkgpackagedatamodel.RepositoryOptions) (bool, error) + DeleteRepository(o *tkgpackagedatamodel.RepositoryOptions, packageProgress *tkgpackagedatamodel.PackageProgress) GetPackageInstall(o *tkgpackagedatamodel.PackageOptions) (*kappipkg.PackageInstall, error) GetPackage(o *tkgpackagedatamodel.PackageOptions) (*kapppkg.PackageMetadata, *kapppkg.Package, error) GetRepository(o *tkgpackagedatamodel.RepositoryOptions) (*kappipkg.PackageRepository, error) - InstallPackage(o *tkgpackagedatamodel.PackageOptions, packageProgress *tkgpackagedatamodel.PackageProgress, update bool) + InstallPackage(o *tkgpackagedatamodel.PackageOptions, packageProgress *tkgpackagedatamodel.PackageProgress, operationType tkgpackagedatamodel.OperationType) ListImagePullSecrets(o *tkgpackagedatamodel.ImagePullSecretOptions) (*corev1.SecretList, error) ListPackageInstalls(o *tkgpackagedatamodel.PackageOptions) (*kappipkg.PackageInstallList, error) ListPackageMetadata(o *tkgpackagedatamodel.PackageAvailableOptions) (*kapppkg.PackageMetadataList, error) @@ -33,5 +33,5 @@ type TKGPackageClient interface { UninstallPackage(o *tkgpackagedatamodel.PackageOptions, packageProgress *tkgpackagedatamodel.PackageProgress) UpdateImagePullSecret(o *tkgpackagedatamodel.ImagePullSecretOptions) error UpdatePackage(o *tkgpackagedatamodel.PackageOptions, packageProgress *tkgpackagedatamodel.PackageProgress) - UpdateRepository(o *tkgpackagedatamodel.RepositoryOptions) error + UpdateRepository(o *tkgpackagedatamodel.RepositoryOptions, progress *tkgpackagedatamodel.PackageProgress) } diff --git a/pkg/v1/tkg/tkgpackageclient/package_install.go b/pkg/v1/tkg/tkgpackageclient/package_install.go index d0145e9746..eb1d543fae 100644 --- a/pkg/v1/tkg/tkgpackageclient/package_install.go +++ b/pkg/v1/tkg/tkgpackageclient/package_install.go @@ -8,6 +8,7 @@ import ( "fmt" "io/ioutil" "path/filepath" + "time" "github.com/pkg/errors" corev1 "k8s.io/api/core/v1" @@ -31,7 +32,7 @@ const ( ) // InstallPackage installs the PackageInstall and its associated resources in the cluster -func (p *pkgClient) InstallPackage(o *tkgpackagedatamodel.PackageOptions, progress *tkgpackagedatamodel.PackageProgress, update bool) { //nolint:gocyclo +func (p *pkgClient) InstallPackage(o *tkgpackagedatamodel.PackageOptions, progress *tkgpackagedatamodel.PackageProgress, operationType tkgpackagedatamodel.OperationType) { //nolint:gocyclo var ( pkgInstall *kappipkg.PackageInstall err error @@ -40,7 +41,13 @@ func (p *pkgClient) InstallPackage(o *tkgpackagedatamodel.PackageOptions, progre ) defer func() { - packageInstallProgressCleanup(err, progress, update) + if err != nil { + progress.Err <- err + } + if operationType == tkgpackagedatamodel.OperationTypeInstall { + close(progress.ProgressMsg) + close(progress.Done) + } }() if pkgInstall, err = p.kappClient.GetPackageInstall(o.PkgInstallName, o.Namespace); err != nil { @@ -51,7 +58,7 @@ func (p *pkgClient) InstallPackage(o *tkgpackagedatamodel.PackageOptions, progre } if pkgInstall != nil && pkgInstall.Name == o.PkgInstallName { - err = &tkgpackagedatamodel.PackagePluginNonCriticalError{Reason: tkgpackagedatamodel.ErrPackageAlreadyInstalled} + err = errors.New(fmt.Sprintf("package install '%s' already exists in namespace '%s'", o.PkgInstallName, o.Namespace)) return } @@ -121,23 +128,13 @@ func (p *pkgClient) InstallPackage(o *tkgpackagedatamodel.PackageOptions, progre } if o.Wait { - if err = p.waitForPackageInstallation(o, progress.ProgressMsg); err != nil { + if err = p.waitForResourceInstallation(o.PkgInstallName, o.Namespace, o.PollInterval, o.PollTimeout, progress.ProgressMsg, tkgpackagedatamodel.ResourceTypePackageInstall); err != nil { log.Warning(msgRunPackageInstalledUpdate) return } } } -func packageInstallProgressCleanup(err error, progress *tkgpackagedatamodel.PackageProgress, update bool) { - if err != nil { - progress.Err <- err - } - if !update { - close(progress.ProgressMsg) - close(progress.Done) - } -} - // createClusterAdminRole creates a ClusterRole resource func (p *pkgClient) createClusterAdminRole(o *tkgpackagedatamodel.PackageOptions) error { clusterRole := &rbacv1.ClusterRole{ @@ -277,22 +274,33 @@ func (p *pkgClient) createServiceAccount(o *tkgpackagedatamodel.PackageOptions) return true, nil } -// waitForPackageInstallation waits until the package get installed successfully or a failure happen -func (p *pkgClient) waitForPackageInstallation(o *tkgpackagedatamodel.PackageOptions, progress chan string) error { - if err := wait.Poll(o.PollInterval, o.PollTimeout, func() (done bool, err error) { - pkg, err := p.kappClient.GetPackageInstall(o.PkgInstallName, o.Namespace) - if err != nil { - return false, err +// waitForResourceInstallation waits until the package get installed successfully or a failure happen +func (p *pkgClient) waitForResourceInstallation(name, namespace string, pollInterval, pollTimeout time.Duration, progress chan string, rscType tkgpackagedatamodel.ResourceType) error { + var status kappctrl.GenericStatus + if err := wait.Poll(pollInterval, pollTimeout, func() (done bool, err error) { + switch rscType { + case tkgpackagedatamodel.ResourceTypePackageRepository: + resource, err := p.kappClient.GetPackageRepository(name, namespace) + if err != nil { + return false, err + } + status = resource.Status.GenericStatus + case tkgpackagedatamodel.ResourceTypePackageInstall: + resource, err := p.kappClient.GetPackageInstall(name, namespace) + if err != nil { + return false, err + } + status = resource.Status.GenericStatus } - for _, cond := range pkg.Status.Conditions { + for _, cond := range status.Conditions { if progress != nil { - progress <- fmt.Sprintf("Package install status: %s", cond.Type) + progress <- fmt.Sprintf("Resource install status: %s", cond.Type) } switch cond.Type { case kappctrl.ReconcileSucceeded: return true, nil case kappctrl.ReconcileFailed: - return false, fmt.Errorf("package reconciliation failed: %s", pkg.Status.UsefulErrorMessage) + return false, fmt.Errorf("resource reconciliation failed: %s. %s", status.UsefulErrorMessage, status.FriendlyDescription) } } return false, nil diff --git a/pkg/v1/tkg/tkgpackageclient/package_install_test.go b/pkg/v1/tkg/tkgpackageclient/package_install_test.go index 9da5502b91..bdb00da1ca 100644 --- a/pkg/v1/tkg/tkgpackageclient/package_install_test.go +++ b/pkg/v1/tkg/tkgpackageclient/package_install_test.go @@ -4,6 +4,7 @@ package tkgpackageclient import ( + "fmt" "os" "time" @@ -94,7 +95,6 @@ var _ = Describe("Install Package", func() { } options = opts progress *tkgpackagedatamodel.PackageProgress - update bool ) JustBeforeEach(func() { @@ -104,7 +104,7 @@ var _ = Describe("Install Package", func() { Done: make(chan struct{}), } ctl = &pkgClient{kappClient: kappCtl} - go ctl.InstallPackage(&options, progress, update) + go ctl.InstallPackage(&options, progress, tkgpackagedatamodel.OperationTypeInstall) err = testReceive(progress) }) @@ -307,7 +307,7 @@ var _ = Describe("Install Package", func() { }) It(testFailureMsg, func() { Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(Equal(tkgpackagedatamodel.ErrPackageAlreadyInstalled)) + Expect(err.Error()).To(ContainSubstring(fmt.Sprintf("package install '%s' already exists in namespace '%s'", options.PkgInstallName, options.Namespace))) }) AfterEach(func() { options = opts diff --git a/pkg/v1/tkg/tkgpackageclient/package_uninstall.go b/pkg/v1/tkg/tkgpackageclient/package_uninstall.go index 7748574a7a..b5f92421d5 100644 --- a/pkg/v1/tkg/tkgpackageclient/package_uninstall.go +++ b/pkg/v1/tkg/tkgpackageclient/package_uninstall.go @@ -7,6 +7,7 @@ import ( "context" "fmt" "strings" + "time" "github.com/pkg/errors" corev1 "k8s.io/api/core/v1" @@ -32,7 +33,7 @@ func (p *pkgClient) UninstallPackage(o *tkgpackagedatamodel.PackageOptions, prog ) defer func() { - packageProgressCleanup(err, progress) + progressCleanup(err, progress) }() progress.ProgressMsg <- fmt.Sprintf("Getting package install for '%s'", o.PkgInstallName) @@ -55,7 +56,7 @@ func (p *pkgClient) UninstallPackage(o *tkgpackagedatamodel.PackageOptions, prog return } - if err = p.waitForPackageInstallDeletion(o, progress.ProgressMsg); err != nil { + if err = p.waitForResourceDeletion(o.PkgInstallName, o.Namespace, o.PollInterval, o.PollTimeout, progress.ProgressMsg, tkgpackagedatamodel.ResourceTypePackageInstall); err != nil { return } @@ -64,14 +65,6 @@ func (p *pkgClient) UninstallPackage(o *tkgpackagedatamodel.PackageOptions, prog } } -func packageProgressCleanup(err error, progress *tkgpackagedatamodel.PackageProgress) { - if err != nil { - progress.Err <- err - } - close(progress.ProgressMsg) - close(progress.Done) -} - // deletePkgPluginCreatedResources deletes the associated resources which were installed upon installation of the PackageInstall CR func (p *pkgClient) deletePkgPluginCreatedResources(pkgInstall *kappipkg.PackageInstall, progress chan string) error { //nolint:gocyclo for k, v := range pkgInstall.GetAnnotations() { @@ -151,33 +144,6 @@ func (p *pkgClient) deletePackageInstall(o *tkgpackagedatamodel.PackageOptions) return nil } -// waitForPackageInstallDeletion waits until the PackageInstall CR gets deleted successfully or a failure happens -func (p *pkgClient) waitForPackageInstallDeletion(o *tkgpackagedatamodel.PackageOptions, progress chan string) error { - if err := wait.Poll(o.PollInterval, o.PollTimeout, func() (done bool, err error) { - pkgInstall, err := p.kappClient.GetPackageInstall(o.PkgInstallName, o.Namespace) - if err != nil { - if apierrors.IsNotFound(err) { - return true, nil - } - return false, err - } - for _, cond := range pkgInstall.Status.Conditions { - if progress != nil { - progress <- fmt.Sprintf("Package uninstall status: %s", cond.Type) - } - if cond.Type == kappctrl.DeleteFailed { - return false, fmt.Errorf("package install deletion failed: %s", pkgInstall.Status.UsefulErrorMessage) - } - } - - return false, nil - }); err != nil { - return err - } - - return nil -} - // deletePreviouslyInstalledResources deletes the related resources if previously installed through the package plugin func (p *pkgClient) deletePreviouslyInstalledResources(o *tkgpackagedatamodel.PackageOptions) error { var objMeta metav1.ObjectMeta @@ -244,3 +210,52 @@ func (p *pkgClient) deleteAnnotatedResource(obj runtime.Object, objKey crtclient return nil } + +func progressCleanup(err error, progress *tkgpackagedatamodel.PackageProgress) { + if err != nil { + progress.Err <- err + } + close(progress.ProgressMsg) + close(progress.Done) +} + +// waitForResourceDeletion waits until the CR gets deleted successfully or a failure happens +func (p *pkgClient) waitForResourceDeletion(name, namespace string, pollInterval, pollTimeout time.Duration, progress chan string, rscType tkgpackagedatamodel.ResourceType) error { + var status kappctrl.GenericStatus + if err := wait.Poll(pollInterval, pollTimeout, func() (done bool, err error) { + switch rscType { + case tkgpackagedatamodel.ResourceTypePackageRepository: + resource, err := p.kappClient.GetPackageRepository(name, namespace) + if err != nil { + if apierrors.IsNotFound(err) { + return true, nil + } + return false, err + } + status = resource.Status.GenericStatus + case tkgpackagedatamodel.ResourceTypePackageInstall: + resource, err := p.kappClient.GetPackageInstall(name, namespace) + if err != nil { + if apierrors.IsNotFound(err) { + return true, nil + } + return false, err + } + status = resource.Status.GenericStatus + } + for _, cond := range status.Conditions { + if progress != nil { + progress <- fmt.Sprintf("Resource deletion status: %s", cond.Type) + } + if cond.Type == kappctrl.DeleteFailed { + return false, fmt.Errorf("resource deletion failed: %s. %s", status.UsefulErrorMessage, status.FriendlyDescription) + } + } + + return false, nil + }); err != nil { + return err + } + + return nil +} diff --git a/pkg/v1/tkg/tkgpackageclient/package_update.go b/pkg/v1/tkg/tkgpackageclient/package_update.go index 0be207152a..edd849626b 100644 --- a/pkg/v1/tkg/tkgpackageclient/package_update.go +++ b/pkg/v1/tkg/tkgpackageclient/package_update.go @@ -27,7 +27,7 @@ func (p *pkgClient) UpdatePackage(o *tkgpackagedatamodel.PackageOptions, progres ) defer func() { - packageProgressCleanup(err, progress) + progressCleanup(err, progress) }() progress.ProgressMsg <- fmt.Sprintf("Getting package install for '%s'", o.PkgInstallName) @@ -42,7 +42,7 @@ func (p *pkgClient) UpdatePackage(o *tkgpackagedatamodel.PackageOptions, progres if pkgInstall == nil { if !o.Install { - err = errors.New(fmt.Sprintf("package '%s' is not among the list of installed packages in namespace '%s'. Consider using the install flag to install the package", o.PkgInstallName, o.Namespace)) + err = &tkgpackagedatamodel.PackagePluginNonCriticalError{Reason: tkgpackagedatamodel.ErrPackageNotInstalled} return } if o.PackageName == "" { @@ -50,7 +50,7 @@ func (p *pkgClient) UpdatePackage(o *tkgpackagedatamodel.PackageOptions, progres return } progress.ProgressMsg <- fmt.Sprintf("Installing package '%s'", o.PkgInstallName) - p.InstallPackage(o, progress, true) + p.InstallPackage(o, progress, tkgpackagedatamodel.OperationTypeUpdate) return } @@ -69,20 +69,8 @@ func (p *pkgClient) UpdatePackage(o *tkgpackagedatamodel.PackageOptions, progres } if o.ValuesFile != "" { - o.SecretName = fmt.Sprintf(tkgpackagedatamodel.SecretName, o.PkgInstallName, o.Namespace) - if o.SecretName == pkgInstallToUpdate.GetAnnotations()[tkgpackagedatamodel.TanzuPkgPluginAnnotation+"-Secret"] { - progress.ProgressMsg <- fmt.Sprintf("Updating secret '%s'", o.SecretName) - if err = p.updateDataValuesSecret(o); err != nil { - err = errors.Wrap(err, "failed to update secret based on values file") - return - } - secretCreated = false - } else { - progress.ProgressMsg <- fmt.Sprintf("Creating secret '%s'", o.SecretName) - if secretCreated, err = p.createDataValuesSecret(o); err != nil { - err = errors.Wrap(err, "failed to create secret based on values file") - return - } + if secretCreated, err = p.updateValuesFile(o, pkgInstallToUpdate, progress.ProgressMsg); err != nil { + return } pkgInstallToUpdate.Spec.Values = []kappipkg.PackageInstallValues{ @@ -99,6 +87,37 @@ func (p *pkgClient) UpdatePackage(o *tkgpackagedatamodel.PackageOptions, progres err = errors.Wrap(err, fmt.Sprintf("failed to update package '%s'", o.PkgInstallName)) return } + + if o.Wait { + if err = p.waitForResourceInstallation(o.PkgInstallName, o.Namespace, o.PollInterval, o.PollTimeout, progress.ProgressMsg, tkgpackagedatamodel.ResourceTypePackageInstall); err != nil { + return + } + } +} + +// updateValuesFile either creates or updates the values secret depending on whether the corresponding annotation exist or not +func (p *pkgClient) updateValuesFile(o *tkgpackagedatamodel.PackageOptions, pkgInstallToUpdate *kappipkg.PackageInstall, progress chan string) (bool, error) { + var ( + secretCreated bool + err error + ) + + o.SecretName = fmt.Sprintf(tkgpackagedatamodel.SecretName, o.PkgInstallName, o.Namespace) + + if o.SecretName == pkgInstallToUpdate.GetAnnotations()[tkgpackagedatamodel.TanzuPkgPluginAnnotation+"-Secret"] { + progress <- fmt.Sprintf("Updating secret '%s'", o.SecretName) + if err = p.updateDataValuesSecret(o); err != nil { + err = errors.Wrap(err, "failed to update secret based on values file") + return false, err + } + } else { + progress <- fmt.Sprintf("Creating secret '%s'", o.SecretName) + if secretCreated, err = p.createDataValuesSecret(o); err != nil { + return secretCreated, errors.Wrap(err, "failed to create secret based on values file") + } + } + + return secretCreated, nil } // updateDataValuesSecret update a secret object containing the user-provided configuration. diff --git a/pkg/v1/tkg/tkgpackageclient/package_update_test.go b/pkg/v1/tkg/tkgpackageclient/package_update_test.go index f869715f53..42e9adf844 100644 --- a/pkg/v1/tkg/tkgpackageclient/package_update_test.go +++ b/pkg/v1/tkg/tkgpackageclient/package_update_test.go @@ -67,7 +67,7 @@ var _ = Describe("Update Package", func() { }) It(testFailureMsg, func() { Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(ContainSubstring("package 'test-pkg' is not among the list of installed packages in namespace 'test-ns'")) + Expect(err.Error()).To(ContainSubstring("package install does not exist in the namespace")) }) AfterEach(func() { options = opts }) }) diff --git a/pkg/v1/tkg/tkgpackageclient/repository_add.go b/pkg/v1/tkg/tkgpackageclient/repository_add.go index 1464fde5a4..0003226c27 100644 --- a/pkg/v1/tkg/tkgpackageclient/repository_add.go +++ b/pkg/v1/tkg/tkgpackageclient/repository_add.go @@ -12,28 +12,55 @@ import ( kappctrl "github.com/vmware-tanzu/carvel-kapp-controller/pkg/apis/kappctrl/v1alpha1" kappipkg "github.com/vmware-tanzu/carvel-kapp-controller/pkg/apis/packaging/v1alpha1" + "github.com/vmware-tanzu/tanzu-framework/pkg/v1/tkg/log" "github.com/vmware-tanzu/tanzu-framework/pkg/v1/tkg/tkgpackagedatamodel" ) +const ( + msgRunPackageRepositoryUpdate = "\n\nPlease consider using 'tanzu package repository update' to update the package repository with correct settings\n" +) + // AddRepository validates the provided input and adds the package repository CR to the cluster -func (p *pkgClient) AddRepository(o *tkgpackagedatamodel.RepositoryOptions) error { - if err := p.validateRepository(o.RepositoryName, o.RepositoryURL, o.Namespace); err != nil { - return err +func (p *pkgClient) AddRepository(o *tkgpackagedatamodel.RepositoryOptions, progress *tkgpackagedatamodel.PackageProgress, operationType tkgpackagedatamodel.OperationType) { + var err error + + defer func() { + if err != nil { + progress.Err <- err + } + if operationType == tkgpackagedatamodel.OperationTypeInstall { + close(progress.ProgressMsg) + close(progress.Done) + } + }() + + progress.ProgressMsg <- "Validating provided settings for the package repository" + if err = p.validateRepository(o.RepositoryName, o.RepositoryURL, o.Namespace); err != nil { + return } if o.CreateNamespace { - if err := p.createNamespace(o.Namespace); err != nil { - return err + progress.ProgressMsg <- fmt.Sprintf("Creating namespace '%s'", o.Namespace) + if err = p.createNamespace(o.Namespace); err != nil { + return } } newPackageRepo := p.newPackageRepository(o.RepositoryName, o.RepositoryURL, o.Namespace) - if err := p.kappClient.CreatePackageRepository(newPackageRepo); err != nil { - return errors.Wrap(err, fmt.Sprintf("failed to create package repository '%s' in namespace '%s'", o.RepositoryName, o.Namespace)) + progress.ProgressMsg <- "Creating package repository resource" + + if err = p.kappClient.CreatePackageRepository(newPackageRepo); err != nil { + err = errors.Wrap(err, fmt.Sprintf("failed to create package repository '%s' in namespace '%s'", o.RepositoryName, o.Namespace)) + return } - return nil + if o.Wait { + if err = p.waitForResourceInstallation(o.RepositoryName, o.Namespace, o.PollInterval, o.PollTimeout, progress.ProgressMsg, tkgpackagedatamodel.ResourceTypePackageRepository); err != nil { + log.Warning(msgRunPackageRepositoryUpdate) + return + } + } } // newPackageRepository creates a new instance of the PackageRepository object @@ -56,12 +83,12 @@ func (p *pkgClient) validateRepository(repositoryName, repositoryImg, namespace for _, repository := range repositoryList.Items { //nolint:gocritic if repository.Name == repositoryName { - return errors.New("repository with the same name already exists") + return errors.New(fmt.Sprintf("package repository name '%s' already exists in namespace '%s'", repositoryName, namespace)) } if repository.Spec.Fetch != nil && repository.Spec.Fetch.ImgpkgBundle != nil && repository.Spec.Fetch.ImgpkgBundle.Image == repositoryImg { - return errors.New("repository with the same OCI registry URL already exists") + return errors.New(fmt.Sprintf("package repository URL '%s' already exists in namespace '%s'", repositoryImg, namespace)) } } diff --git a/pkg/v1/tkg/tkgpackageclient/repository_add_test.go b/pkg/v1/tkg/tkgpackageclient/repository_add_test.go index c1fd1cee69..21cbdae6ea 100644 --- a/pkg/v1/tkg/tkgpackageclient/repository_add_test.go +++ b/pkg/v1/tkg/tkgpackageclient/repository_add_test.go @@ -4,6 +4,8 @@ package tkgpackageclient import ( + "fmt" + . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" "github.com/pkg/errors" @@ -49,14 +51,21 @@ var _ = Describe("Add Repository", func() { CreateNamespace: false, } options = opts + progress *tkgpackagedatamodel.PackageProgress pkgRepositoryList = &kappipkg.PackageRepositoryList{ Items: []kappipkg.PackageRepository{*testRepository}, } ) JustBeforeEach(func() { + progress = &tkgpackagedatamodel.PackageProgress{ + ProgressMsg: make(chan string, 10), + Err: make(chan error), + Done: make(chan struct{}), + } ctl = &pkgClient{kappClient: kappCtl} - err = ctl.AddRepository(&options) + go ctl.AddRepository(&options, progress, tkgpackagedatamodel.OperationTypeInstall) + err = testReceive(progress) }) Context("failure in listing package repositories due to ListPackageRepositories API error", func() { @@ -78,7 +87,7 @@ var _ = Describe("Add Repository", func() { }) It(testFailureMsg, func() { Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(ContainSubstring("repository with the same name already exists")) + Expect(err.Error()).To(ContainSubstring(fmt.Sprintf("package repository name '%s' already exists in namespace '%s'", options.RepositoryName, options.Namespace))) }) AfterEach(func() { options = opts }) }) @@ -91,7 +100,7 @@ var _ = Describe("Add Repository", func() { }) It(testFailureMsg, func() { Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(ContainSubstring("repository with the same OCI registry URL already exists")) + Expect(err.Error()).To(ContainSubstring(fmt.Sprintf("package repository URL '%s' already exists in namespace '%s'", options.RepositoryURL, options.Namespace))) }) AfterEach(func() { options = opts }) }) @@ -110,7 +119,6 @@ var _ = Describe("Add Repository", func() { }) It(testFailureMsg, func() { Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(ContainSubstring("failure in Get namespace")) }) AfterEach(func() { options = opts }) diff --git a/pkg/v1/tkg/tkgpackageclient/repository_delete.go b/pkg/v1/tkg/tkgpackageclient/repository_delete.go index 0f3abf0c3e..a4f5480390 100644 --- a/pkg/v1/tkg/tkgpackageclient/repository_delete.go +++ b/pkg/v1/tkg/tkgpackageclient/repository_delete.go @@ -7,21 +7,41 @@ import ( "fmt" "github.com/pkg/errors" + apierrors "k8s.io/apimachinery/pkg/api/errors" + + kappipkg "github.com/vmware-tanzu/carvel-kapp-controller/pkg/apis/packaging/v1alpha1" "github.com/vmware-tanzu/tanzu-framework/pkg/v1/tkg/tkgpackagedatamodel" ) -func (p *pkgClient) DeleteRepository(o *tkgpackagedatamodel.RepositoryOptions) (bool, error) { - packageRepo, err := p.kappClient.GetPackageRepository(o.RepositoryName, o.Namespace) +func (p *pkgClient) DeleteRepository(o *tkgpackagedatamodel.RepositoryOptions, progress *tkgpackagedatamodel.PackageProgress) { + var ( + packageRepo *kappipkg.PackageRepository + err error + ) + + defer func() { + progressCleanup(err, progress) + }() + progress.ProgressMsg <- fmt.Sprintf("Getting package repository '%s'", o.RepositoryName) + packageRepo, err = p.kappClient.GetPackageRepository(o.RepositoryName, o.Namespace) if err != nil { - return false, nil + if apierrors.IsNotFound(err) { + err = &tkgpackagedatamodel.PackagePluginNonCriticalError{Reason: tkgpackagedatamodel.ErrRepoNotExists} + } + return } + progress.ProgressMsg <- "Deleting package repository resoure" err = p.kappClient.DeletePackageRepository(packageRepo) if err != nil { - return true, errors.Wrap(err, fmt.Sprintf("failed to delete package repository '%s' from namespace '%s'", o.RepositoryName, o.Namespace)) + err = errors.Wrap(err, fmt.Sprintf("failed to delete package repository '%s' from namespace '%s'", o.RepositoryName, o.Namespace)) } - return true, nil + if o.Wait { + if err = p.waitForResourceDeletion(o.RepositoryName, o.Namespace, o.PollInterval, o.PollTimeout, progress.ProgressMsg, tkgpackagedatamodel.ResourceTypePackageRepository); err != nil { + return + } + } } diff --git a/pkg/v1/tkg/tkgpackageclient/repository_delete_test.go b/pkg/v1/tkg/tkgpackageclient/repository_delete_test.go index c0b332d4b0..8d341cec2c 100644 --- a/pkg/v1/tkg/tkgpackageclient/repository_delete_test.go +++ b/pkg/v1/tkg/tkgpackageclient/repository_delete_test.go @@ -8,6 +8,9 @@ import ( . "github.com/onsi/gomega" "github.com/pkg/errors" + apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/runtime/schema" + "github.com/vmware-tanzu/tanzu-framework/pkg/v1/tkg/fakes" "github.com/vmware-tanzu/tanzu-framework/pkg/v1/tkg/tkgpackagedatamodel" ) @@ -21,13 +24,19 @@ var _ = Describe("Delete Repository", func() { RepositoryName: testRepoName, IsForceDelete: false, } - options = opts - found bool + options = opts + progress *tkgpackagedatamodel.PackageProgress ) JustBeforeEach(func() { + progress = &tkgpackagedatamodel.PackageProgress{ + ProgressMsg: make(chan string, 10), + Err: make(chan error), + Done: make(chan struct{}), + } ctl = &pkgClient{kappClient: kappCtl} - found, err = ctl.DeleteRepository(&options) + go ctl.DeleteRepository(&options, progress) + err = testReceive(progress) }) Context("failure in deleting the package repository due to DeletePackageRepository API error", func() { @@ -43,14 +52,26 @@ var _ = Describe("Delete Repository", func() { AfterEach(func() { options = opts }) }) - Context("not being able to get the package repository, no error should be returned", func() { + Context("not being able to get the package repository due to failure in GetPackageRepository", func() { BeforeEach(func() { kappCtl = &fakes.KappClient{} kappCtl.GetPackageRepositoryReturns(nil, errors.New("failure in GetPackageRepository")) }) It(testSuccessMsg, func() { - Expect(err).NotTo(HaveOccurred()) - Expect(found).To(BeFalse()) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("failure in GetPackageRepository")) + }) + AfterEach(func() { options = opts }) + }) + + Context("not being able to get the package repository due to non existent repository", func() { + BeforeEach(func() { + kappCtl = &fakes.KappClient{} + kappCtl.GetPackageRepositoryReturns(nil, apierrors.NewNotFound(schema.GroupResource{Resource: tkgpackagedatamodel.KindPackageRepository}, testRepoName)) + }) + It(testSuccessMsg, func() { + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring(tkgpackagedatamodel.ErrRepoNotExists)) }) AfterEach(func() { options = opts }) }) @@ -64,7 +85,6 @@ var _ = Describe("Delete Repository", func() { }) It(testSuccessMsg, func() { Expect(err).ToNot(HaveOccurred()) - Expect(found).To(BeTrue()) }) AfterEach(func() { options = opts }) }) diff --git a/pkg/v1/tkg/tkgpackageclient/repository_update.go b/pkg/v1/tkg/tkgpackageclient/repository_update.go index f962fdc491..2e401ea915 100644 --- a/pkg/v1/tkg/tkgpackageclient/repository_update.go +++ b/pkg/v1/tkg/tkgpackageclient/repository_update.go @@ -9,29 +9,48 @@ import ( "github.com/pkg/errors" k8serror "k8s.io/apimachinery/pkg/api/errors" + kappipkg "github.com/vmware-tanzu/carvel-kapp-controller/pkg/apis/packaging/v1alpha1" + "github.com/vmware-tanzu/tanzu-framework/pkg/v1/tkg/tkgpackagedatamodel" ) -func (p *pkgClient) UpdateRepository(o *tkgpackagedatamodel.RepositoryOptions) error { - existingRepository, err := p.kappClient.GetPackageRepository(o.RepositoryName, o.Namespace) - if err != nil && !k8serror.IsNotFound(err) { - return err +func (p *pkgClient) UpdateRepository(o *tkgpackagedatamodel.RepositoryOptions, progress *tkgpackagedatamodel.PackageProgress) { + var ( + existingRepository *kappipkg.PackageRepository + err error + ) + + defer func() { + progressCleanup(err, progress) + }() + + progress.ProgressMsg <- fmt.Sprintf("Getting package repository '%s'", o.RepositoryName) + existingRepository, err = p.kappClient.GetPackageRepository(o.RepositoryName, o.Namespace) + if err != nil { + if k8serror.IsNotFound(err) { + err = nil + } else { + return + } } if existingRepository != nil { repositoryToUpdate := existingRepository.DeepCopy() repositoryToUpdate.Spec.Fetch.ImgpkgBundle.Image = o.RepositoryURL - if err := p.kappClient.UpdatePackageRepository(repositoryToUpdate); err != nil { - return errors.Wrap(err, fmt.Sprintf("failed to update package repository '%s' in namespace '%s'", o.RepositoryName, o.Namespace)) + progress.ProgressMsg <- "Updating package repository resource" + if err = p.kappClient.UpdatePackageRepository(repositoryToUpdate); err != nil { + err = errors.Wrap(err, fmt.Sprintf("failed to update package repository '%s' in namespace '%s'", o.RepositoryName, o.Namespace)) } - } else if o.CreateRepository { - if err := p.AddRepository(o); err != nil { - return errors.Wrap(err, fmt.Sprintf("failed to create package repository '%s' in namespace '%s'", o.RepositoryName, o.Namespace)) + + if o.Wait { + if err = p.waitForResourceInstallation(o.RepositoryName, o.Namespace, o.PollInterval, o.PollTimeout, progress.ProgressMsg, tkgpackagedatamodel.ResourceTypePackageRepository); err != nil { + return + } } + } else if o.CreateRepository { + p.AddRepository(o, progress, tkgpackagedatamodel.OperationTypeUpdate) } else { - return errors.Wrap(err, fmt.Sprintf("failed to find package repository '%s' in namespace '%s'", o.RepositoryName, o.Namespace)) + err = &tkgpackagedatamodel.PackagePluginNonCriticalError{Reason: tkgpackagedatamodel.ErrRepoNotExists} } - - return nil } diff --git a/pkg/v1/tkg/tkgpackageclient/repository_update_test.go b/pkg/v1/tkg/tkgpackageclient/repository_update_test.go index 050ebe66b8..a82693cd5e 100644 --- a/pkg/v1/tkg/tkgpackageclient/repository_update_test.go +++ b/pkg/v1/tkg/tkgpackageclient/repository_update_test.go @@ -4,6 +4,8 @@ package tkgpackageclient import ( + "fmt" + . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" "github.com/pkg/errors" @@ -28,14 +30,21 @@ var _ = Describe("Update Repository", func() { CreateRepository: false, } options = opts + progress *tkgpackagedatamodel.PackageProgress pkgRepositoryList = &kappipkg.PackageRepositoryList{ Items: []kappipkg.PackageRepository{*testRepository}, } ) JustBeforeEach(func() { + progress = &tkgpackagedatamodel.PackageProgress{ + ProgressMsg: make(chan string, 10), + Err: make(chan error), + Done: make(chan struct{}), + } ctl = &pkgClient{kappClient: kappCtl} - err = ctl.UpdateRepository(&options) + go ctl.UpdateRepository(&options, progress) + err = testReceive(progress) }) Context("failure in getting the package repository due to GetPackageRepository API error", func() { @@ -50,18 +59,6 @@ var _ = Describe("Update Repository", func() { AfterEach(func() { options = opts }) }) - Context("failure in finding the package repository", func() { - BeforeEach(func() { - kappCtl = &fakes.KappClient{} - kappCtl.GetPackageRepositoryReturns(nil, apierrors.NewNotFound(schema.GroupResource{Resource: "Repository"}, testRepoName)) - }) - It(testFailureMsg, func() { - Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(ContainSubstring("Repository \"test-repo\" not found")) - }) - AfterEach(func() { options = opts }) - }) - Context("failure in adding package repository as a repository with the same OCI registry URL already exists", func() { BeforeEach(func() { options.CreateRepository = true @@ -72,7 +69,7 @@ var _ = Describe("Update Repository", func() { }) It(testFailureMsg, func() { Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(ContainSubstring("repository with the same OCI registry URL already exists")) + Expect(err.Error()).To(ContainSubstring(fmt.Sprintf("package repository URL '%s' already exists in namespace '%s'", options.RepositoryURL, options.Namespace))) }) AfterEach(func() { options = opts }) }) diff --git a/pkg/v1/tkg/tkgpackagedatamodel/constants.go b/pkg/v1/tkg/tkgpackagedatamodel/constants.go index b1144f7c76..00329455d3 100644 --- a/pkg/v1/tkg/tkgpackagedatamodel/constants.go +++ b/pkg/v1/tkg/tkgpackagedatamodel/constants.go @@ -7,26 +7,26 @@ package tkgpackagedatamodel import "time" const ( - ClusterRoleBindingName = "%s-%s-cluster-rolebinding" - ClusterRoleName = "%s-%s-cluster-role" - DefaultAPIVersion = "install.package.carvel.dev/v1alpha1" - DefaultPollInterval = 1 * time.Second - DefaultPollTimeout = 5 * time.Minute - ErrPackageAlreadyInstalled = "package install already exists in the namespace" - ErrPackageNotInstalled = "package install does not exist in the namespace" - KindClusterRole = "ClusterRole" - KindClusterRoleBinding = "ClusterRoleBinding" - KindNamespace = "Namespace" - KindPackageInstall = "PackageInstall" - KindPackageRepository = "PackageRepository" - KindSecret = "Secret" - KindSecretExport = "SecretExport" - KindServiceAccount = "ServiceAccount" - SecretName = "%s-%s-values" - ServiceAccountName = "%s-%s-sa" - ShortDescriptionMaxLength = 20 - TanzuPkgPluginAnnotation = "tkg.tanzu.vmware.com/tanzu-package" - TanzuPkgPluginPrefix = "tanzu-package" - TanzuPkgPluginResource = "%s-%s" - YamlSeparator = "---" + ClusterRoleBindingName = "%s-%s-cluster-rolebinding" + ClusterRoleName = "%s-%s-cluster-role" + DefaultAPIVersion = "install.package.carvel.dev/v1alpha1" + DefaultPollInterval = 1 * time.Second + DefaultPollTimeout = 5 * time.Minute + ErrPackageNotInstalled = "package install does not exist in the namespace" + ErrRepoNotExists = "package repository does not exist in the namespace" + KindClusterRole = "ClusterRole" + KindClusterRoleBinding = "ClusterRoleBinding" + KindNamespace = "Namespace" + KindPackageInstall = "PackageInstall" + KindPackageRepository = "PackageRepository" + KindSecret = "Secret" + KindSecretExport = "SecretExport" + KindServiceAccount = "ServiceAccount" + SecretName = "%s-%s-values" + ServiceAccountName = "%s-%s-sa" + ShortDescriptionMaxLength = 20 + TanzuPkgPluginAnnotation = "tkg.tanzu.vmware.com/tanzu-package" + TanzuPkgPluginPrefix = "tanzu-package" + TanzuPkgPluginResource = "%s-%s" + YamlSeparator = "---" ) diff --git a/pkg/v1/tkg/tkgpackagedatamodel/repository.go b/pkg/v1/tkg/tkgpackagedatamodel/repository.go index a75068a02a..438487d385 100644 --- a/pkg/v1/tkg/tkgpackagedatamodel/repository.go +++ b/pkg/v1/tkg/tkgpackagedatamodel/repository.go @@ -3,16 +3,22 @@ package tkgpackagedatamodel +import "time" + // RepositoryOptions includes fields for repository operations type RepositoryOptions struct { KubeConfig string Namespace string RepositoryName string RepositoryURL string + PollInterval time.Duration + PollTimeout time.Duration AllNamespaces bool CreateRepository bool CreateNamespace bool IsForceDelete bool + SkipPrompt bool + Wait bool } // NewRepositoryOptions instantiates RepositoryOptions diff --git a/pkg/v1/tkg/tkgpackagedatamodel/types.go b/pkg/v1/tkg/tkgpackagedatamodel/types.go index c5a0e62d5b..bdd8bd7341 100644 --- a/pkg/v1/tkg/tkgpackagedatamodel/types.go +++ b/pkg/v1/tkg/tkgpackagedatamodel/types.go @@ -1,6 +1,7 @@ // Copyright 2021 VMware, Inc. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 +// nolint package tkgpackagedatamodel import ( @@ -16,6 +17,20 @@ type PackagePluginNonCriticalError struct { func (e *PackagePluginNonCriticalError) Error() string { return e.Reason } +type ResourceType int + +const ( + ResourceTypePackageInstall ResourceType = iota + ResourceTypePackageRepository +) + +type OperationType int + +const ( + OperationTypeInstall OperationType = iota + OperationTypeUpdate +) + // TypeBoolPtr satisfies Value interface defined in "https://github.com/spf13/pflag/blob/master/flag.go" type TypeBoolPtr struct { ExportToAllNamespaces *bool