From c1644141324a74ebecbe0d403d678104d7e43bdd Mon Sep 17 00:00:00 2001 From: Andrew Lavery Date: Wed, 24 Nov 2021 16:06:02 -0500 Subject: [PATCH] config export command --- cmd/kots/cli/get.go | 104 +++++++++++++++++++++++++++++++++++++++++ pkg/handlers/config.go | 23 +++++++++ 2 files changed, 127 insertions(+) diff --git a/cmd/kots/cli/get.go b/cmd/kots/cli/get.go index 057eb7c62b..944fb82761 100644 --- a/cmd/kots/cli/get.go +++ b/cmd/kots/cli/get.go @@ -11,9 +11,11 @@ import ( "github.com/pkg/errors" "github.com/spf13/cobra" "github.com/spf13/viper" + "sigs.k8s.io/yaml" handlertypes "github.com/replicatedhq/kots/pkg/api/handlers/types" "github.com/replicatedhq/kots/pkg/auth" + "github.com/replicatedhq/kots/pkg/handlers" "github.com/replicatedhq/kots/pkg/k8sutil" "github.com/replicatedhq/kots/pkg/logger" "github.com/replicatedhq/kots/pkg/print" @@ -48,6 +50,9 @@ kubectl kots get apps`, case "app", "apps": err := getAppsCmd(cmd, args) return errors.Wrap(err, "failed to get apps") + case "config": + err := getConfigCmd(cmd, args) + return errors.Wrap(err, "failed to get config") default: cmd.Help() os.Exit(1) @@ -58,6 +63,8 @@ kubectl kots get apps`, } cmd.Flags().StringP("output", "o", "", "output format. supported values: json") + cmd.Flags().Int("sequence", -1, "app sequence to retrieve config for") + cmd.Flags().String("appslug", "", "app slug to retrieve config for") return cmd } @@ -228,3 +235,100 @@ func getAppStatus(url string, authSlug string) (*handlertypes.AppStatusResponse, return status, nil } + +func getConfigCmd(cmd *cobra.Command, args []string) error { + v := viper.GetViper() + + log := logger.NewCLILogger() + + stopCh := make(chan struct{}) + defer close(stopCh) + + clientset, err := k8sutil.GetClientset() + if err != nil { + return errors.Wrap(err, "failed to get clientset") + } + + namespace := v.GetString("namespace") + if err := validateNamespace(namespace); err != nil { + return errors.Wrap(err, "failed to validate namespace") + } + + podName, err := k8sutil.FindKotsadm(clientset, namespace) + if err != nil { + return errors.Wrap(err, "failed to find kotsadm pod") + } + + localPort, errChan, err := k8sutil.PortForward(0, 3000, namespace, podName, false, stopCh, log) + if err != nil { + log.FinishSpinnerWithError() + return errors.Wrap(err, "failed to start port forwarding") + } + + go func() { + select { + case err := <-errChan: + if err != nil { + log.Error(err) + } + case <-stopCh: + } + }() + + authSlug, err := auth.GetOrCreateAuthSlug(clientset, namespace) + if err != nil { + log.FinishSpinnerWithError() + log.Info("Unable to authenticate to the Admin Console running in the %s namespace. Ensure you have read access to secrets in this namespace and try again.", namespace) + if v.GetBool("debug") { + return errors.Wrap(err, "failed to get kotsadm auth slug") + } + os.Exit(2) // not returning error here as we don't want to show the entire stack trace to normal users + } + + url := fmt.Sprintf("http://localhost:%d/api/v1/app/%s/config/%d", localPort, v.GetString("appslug"), v.GetInt("sequence")) + config, err := getConfig(url, authSlug) + if err != nil { + return errors.Wrap(err, "failed to get config") + } + + values := handlers.ConfigGroupToValues(config.ConfigGroups) + configYaml, err := yaml.Marshal(values) + if err != nil { + return errors.Wrap(err, "failed to marshal config") + } + + fmt.Print(string(configYaml)) + + return nil +} + +func getConfig(url string, authSlug string) (*handlers.CurrentAppConfigResponse, error) { + newReq, err := http.NewRequest("GET", url, nil) + if err != nil { + return nil, errors.Wrap(err, "failed to create request") + } + newReq.Header.Add("Content-Type", "application/json") + newReq.Header.Add("Authorization", authSlug) + + resp, err := http.DefaultClient.Do(newReq) + if err != nil { + return nil, errors.Wrap(err, "failed to execute request") + } + defer resp.Body.Close() + + b, err := ioutil.ReadAll(resp.Body) + if err != nil { + return nil, errors.Wrap(err, "failed to read") + } + + config := &handlers.CurrentAppConfigResponse{} + if err := json.Unmarshal(b, config); err != nil { + return nil, errors.Wrap(err, "failed to unmarshal status") + } + + if !config.Success { + return nil, fmt.Errorf("failed to get config: %s", config.Error) + } + + return config, nil +} diff --git a/pkg/handlers/config.go b/pkg/handlers/config.go index b47dffd159..e63f427384 100644 --- a/pkg/handlers/config.go +++ b/pkg/handlers/config.go @@ -994,3 +994,26 @@ func updateConfigObject(config *kotsv1beta1.Config, configValues *kotsv1beta1.Co return newConfig, nil } + +func ConfigGroupToValues(groups []kotsv1beta1.ConfigGroup) kotsv1beta1.ConfigValues { + extractedValues := kotsv1beta1.ConfigValues{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "kots.io/v1beta1", + Kind: "ConfigValues", + }, + Spec: kotsv1beta1.ConfigValuesSpec{Values: map[string]kotsv1beta1.ConfigValue{}}, + } + + for _, group := range groups { + for _, item := range group.Items { + extractedValues.Spec.Values[item.Name] = kotsv1beta1.ConfigValue{ + Default: item.Default.String(), + Value: item.Value.String(), + Data: item.Data, + Filename: item.Filename, + } + } + } + + return extractedValues +}