diff --git a/cmd/apply.go b/cmd/apply.go index f4b0fb2..b736599 100644 --- a/cmd/apply.go +++ b/cmd/apply.go @@ -35,7 +35,7 @@ var applyCmd = &cobra.Command{ return } - overlayComponentFirst := filepath.Join("common/application/base") + overlayComponentFirst := filepath.Join("common", "application", "base") baseOverlayComponent := config.GetOverlayComponent(overlayComponentFirst) applicationBaseKustomizeTemplate := TemplateFromSimpleOverlayedComponents(baseOverlayComponent) applicationResult, err := GenerateKustomizeResult(*config, applicationBaseKustomizeTemplate) @@ -44,11 +44,11 @@ var applyCmd = &cobra.Command{ return } - applicationKubernetesYamlFilePath := filepath.Join(".onepanel/application.kubernetes.yaml") + applicationKubernetesYamlFilePath := filepath.Join(".onepanel", "application.kubernetes.yaml") existsApp, err := files.Exists(applicationKubernetesYamlFilePath) if err != nil { - log.Printf("Unable to check if file %v exists", applicationKubernetesYamlFilePath) + log.Printf("Unable to check if file '%v' exists", applicationKubernetesYamlFilePath) return } @@ -77,9 +77,9 @@ var applyCmd = &cobra.Command{ resApp, errResApp, err = applyKubernetesFile(applicationKubernetesYamlFilePath) - log.Printf("%v", resApp) + log.Printf("res: %v", resApp) if errResApp != "" { - log.Printf("%v", errResApp) + log.Printf("err: %v", errResApp) } if err != nil { @@ -130,7 +130,7 @@ var applyCmd = &cobra.Command{ return } - finalKubernetesYamlFilePath := filepath.Join(".onepanel/kubernetes.yaml") + finalKubernetesYamlFilePath := filepath.Join(".onepanel", "kubernetes.yaml") exists, err := files.Exists(finalKubernetesYamlFilePath) if err != nil { diff --git a/cmd/build.go b/cmd/build.go index f70e1cf..0c0edc7 100644 --- a/cmd/build.go +++ b/cmd/build.go @@ -81,7 +81,7 @@ func GenerateKustomizeResult(config opConfig.Config, kustomizeTemplate template. } manifestPath := config.Spec.ManifestsRepo - localManifestsCopyPath := filepath.Join(".onepanel/manifests/cache") + localManifestsCopyPath := filepath.Join(".onepanel", "manifests", "cache") exists, err := files.Exists(localManifestsCopyPath) if err != nil { @@ -614,6 +614,8 @@ func HumanizeKustomizeError(err error) string { switch paramsError.ErrorType { case "missing": return fmt.Sprintf("%s is missing in your params.yaml", paramsError.Key) + case "parameter": + return fmt.Sprintf("%s can not be '%s', please enter a namespace", paramsError.Key, *paramsError.Value) case "blank": return fmt.Sprintf("%s can not be blank, please use a different namespace in your params.yaml", paramsError.Key) case "reserved": diff --git a/cmd/delete.go b/cmd/delete.go new file mode 100644 index 0000000..8cabf28 --- /dev/null +++ b/cmd/delete.go @@ -0,0 +1,95 @@ +package cmd + +import ( + "fmt" + opConfig "github.com/onepanelio/cli/config" + "github.com/onepanelio/cli/files" + "github.com/onepanelio/cli/util" + "github.com/spf13/cobra" + "path/filepath" +) + +var ( + // skipConfirmDelete if true, will skip the confirmation prompt of the delete command + skipConfirmDelete bool +) + +var deleteCmd = &cobra.Command{ + Use: "delete", + Short: "Deletes onepanel cluster resources", + Long: "Delete all onepanel kubernetes cluster resources. Does not delete database unless it is in-cluster.", + Example: "delete", + Run: func(cmd *cobra.Command, args []string) { + if skipConfirmDelete == false { + fmt.Print("Are you sure you want to delete onepanel? ('y' or 'yes' to confirm. Anything else to cancel): ") + userInput := "" + if _, err := fmt.Scanln(&userInput); err != nil { + fmt.Printf("Unable to get response\n") + return + } + + if userInput != "y" && userInput != "yes" { + return + } + } + + config, err := opConfig.FromFile("config.yaml") + if err != nil { + fmt.Printf("Unable to read configuration file: %v", err.Error()) + return + } + + paramsYamlFile, err := util.LoadDynamicYamlFromFile(config.Spec.Params) + if err != nil { + fmt.Println("Error parsing configuration file.") + return + } + + defaultNamespaceNode := paramsYamlFile.GetValue("application.defaultNamespace") + if defaultNamespaceNode == nil { + fmt.Printf("application.defaultNamespace is missing from your '%s' file\n", config.Spec.Params) + return + } + + if defaultNamespaceNode.Value == "default" { + fmt.Println("Unable to delete onepanel in the 'default' namespace") + return + } + + if defaultNamespaceNode.Value == "" { + fmt.Println("Unable to delete onepanel. No namespace set.") + return + } + + filesToDelete := []string{ + filepath.Join(".onepanel", "kubernetes.yaml"), + filepath.Join(".onepanel", "application.kubernetes.yaml"), + } + + for _, filePath := range filesToDelete { + exists, err := files.Exists(filePath) + if err != nil { + fmt.Printf("Error checking if onepanel files exist: %v\n", err.Error()) + return + } + + if !exists { + fmt.Printf("'%v' file does not exist. Are you in the directory where you ran 'opctl init'?\n", filePath) + return + } + } + + fmt.Printf("Deleting onepanel from your cluster...\n") + for _, filePath := range filesToDelete { + if err := util.KubectlDelete(filePath); err != nil { + fmt.Printf("Unable to delete: %v\n", err.Error()) + return + } + } + }, +} + +func init() { + rootCmd.AddCommand(deleteCmd) + deleteCmd.Flags().BoolVarP(&skipConfirmDelete, "yes", "y", false, "Add this in to skip the confirmation prompt") +} diff --git a/manifest/manifest.go b/manifest/manifest.go index df0f78a..62c6dec 100644 --- a/manifest/manifest.go +++ b/manifest/manifest.go @@ -134,6 +134,9 @@ func Validate(manifest *util.DynamicYaml) error { if defaultNamespace.Value == "" { return &ParamsError{Key: "application.defaultNamespace", ErrorType: "blank"} } + if defaultNamespace.Value == "" { + return &ParamsError{Key: "application.defaultNamespace", Value: &defaultNamespace.Value, ErrorType: "parameter"} + } if _, ok := reservedNamespaces[defaultNamespace.Value]; ok { return &ParamsError{Key: "application.defaultNamespace", Value: &defaultNamespace.Value, ErrorType: "reserved"} } diff --git a/util/kubectl.go b/util/kubectl.go index 17b516d..5a43847 100644 --- a/util/kubectl.go +++ b/util/kubectl.go @@ -6,15 +6,18 @@ import ( "fmt" opConfig "github.com/onepanelio/cli/config" "github.com/spf13/cobra" + k8error "k8s.io/apimachinery/pkg/util/errors" "k8s.io/cli-runtime/pkg/genericclioptions" _ "k8s.io/client-go/plugin/pkg/client/auth/azure" _ "k8s.io/client-go/plugin/pkg/client/auth/gcp" "k8s.io/kubectl/pkg/cmd/apply" + k8delete "k8s.io/kubectl/pkg/cmd/delete" "k8s.io/kubectl/pkg/cmd/get" cmdutil "k8s.io/kubectl/pkg/cmd/util" "os" "runtime" "strconv" + "strings" ) func KubectlGet(resource string, resourceName string, namespace string, extraArgs []string, flags map[string]interface{}) (stdout string, stderr string, err error) { @@ -125,6 +128,64 @@ func KubectlApply(filePath string) (stdout string, stderr string, err error) { return } +// KubectlDelete run's kubectl delete using the input filePath +func KubectlDelete(filePath string) (err error) { + kubeConfigFlags := genericclioptions.NewConfigFlags(true).WithDeprecatedPasswordFlag() + matchVersionKubeConfigFlags := cmdutil.NewMatchVersionFlags(kubeConfigFlags) + + f := cmdutil.NewFactory(matchVersionKubeConfigFlags) + ioStreams := genericclioptions.IOStreams{ + In: os.Stdin, + Out: os.Stdout, + ErrOut: os.Stderr, + } + cmd := k8delete.NewCmdDelete(f, ioStreams) + + deleteOptions := k8delete.DeleteOptions{IOStreams: ioStreams} + deleteOptions.Filenames = []string{filePath} + err = cmd.Flags().Set("filename", filePath) + if err != nil { + return err + } + + if err := deleteOptions.Complete(f, []string{}, cmd); err != nil { + return err + } + + if err := deleteOptions.RunDelete(f); err != nil { + errorAggregate, ok := err.(k8error.Aggregate) + if ok { + finalErrors := make([]error, 0) + // Skip any errors that mean "not found" + for _, errItem := range errorAggregate.Errors() { + if strings.Contains(errItem.Error(), "not found") { + continue + } + + if strings.Contains(errItem.Error(), "no matches for kind") { + continue + } + + if strings.Contains(errItem.Error(), "the server could not find the requested resource") { + continue + } + + finalErrors = append(finalErrors, errItem) + } + + if len(finalErrors) == 0 { + return nil + } + + return k8error.NewAggregate(finalErrors) + } + + return err + } + + return +} + func validateArgs(cmd *cobra.Command, args []string) error { if len(args) != 0 { return cmdutil.UsageErrorf(cmd, "Unexpected args: %v", args) diff --git a/util/status.go b/util/status.go index 279a026..e9db6b9 100644 --- a/util/status.go +++ b/util/status.go @@ -12,6 +12,7 @@ func DeploymentStatus(yamlFile *DynamicYaml) (ready bool, err error) { namespacesToCheck["application-system"] = true namespacesToCheck["onepanel"] = true namespacesToCheck["istio-system"] = true + namespacesToCheck["default"] = true if yamlFile.HasKey("certManager") { namespacesToCheck["cert-manager"] = true