github.com/1aal/kubeblocks@v0.0.0-20231107070852-e1c03e598921/pkg/cli/cmd/kubeblocks/config.go (about) 1 /* 2 Copyright (C) 2022-2023 ApeCloud Co., Ltd 3 4 This file is part of KubeBlocks project 5 6 This program is free software: you can redistribute it and/or modify 7 it under the terms of the GNU Affero General Public License as published by 8 the Free Software Foundation, either version 3 of the License, or 9 (at your option) any later version. 10 11 This program is distributed in the hope that it will be useful 12 but WITHOUT ANY WARRANTY; without even the implied warranty of 13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 GNU Affero General Public License for more details. 15 16 You should have received a copy of the GNU Affero General Public License 17 along with this program. If not, see <http://www.gnu.org/licenses/>. 18 */ 19 20 package kubeblocks 21 22 import ( 23 "context" 24 "strings" 25 "time" 26 27 "github.com/spf13/cobra" 28 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 29 "k8s.io/cli-runtime/pkg/genericiooptions" 30 "k8s.io/client-go/kubernetes" 31 cmdutil "k8s.io/kubectl/pkg/cmd/util" 32 "k8s.io/kubectl/pkg/util/templates" 33 34 "github.com/1aal/kubeblocks/pkg/cli/printer" 35 "github.com/1aal/kubeblocks/pkg/cli/types" 36 "github.com/1aal/kubeblocks/pkg/cli/util" 37 "github.com/1aal/kubeblocks/pkg/cli/util/helm" 38 ) 39 40 var showAllConfig = false 41 var filterConfig = "" 42 43 // keyWhiteList is a list of which kubeblocks configs are rolled out by default 44 var keyWhiteList = []string{ 45 "addonController", 46 "dataProtection", 47 "affinity", 48 "tolerations", 49 } 50 51 var sensitiveValues = []string{ 52 "cloudProvider.accessKey", 53 "cloudProvider.secretKey", 54 } 55 56 var backupConfigExample = templates.Examples(` 57 # Enable the snapshot-controller and volume snapshot, to support snapshot backup. 58 kbcli kubeblocks config --set snapshot-controller.enabled=true 59 `) 60 61 var describeConfigExample = templates.Examples(` 62 # Describe the KubeBlocks config. 63 kbcli kubeblocks describe-config 64 # Describe all the KubeBlocks configs 65 kbcli kubeblocks describe-config --all 66 # Describe the desired KubeBlocks configs by filter conditions 67 kbcli kubeblocks describe-config --filter=addonController,affinity 68 `) 69 70 // NewConfigCmd creates the config command 71 func NewConfigCmd(f cmdutil.Factory, streams genericiooptions.IOStreams) *cobra.Command { 72 o := &InstallOptions{ 73 Options: Options{ 74 IOStreams: streams, 75 Wait: true, 76 }, 77 } 78 79 cmd := &cobra.Command{ 80 Use: "config", 81 Short: "KubeBlocks config.", 82 Example: backupConfigExample, 83 Args: cobra.NoArgs, 84 Run: func(cmd *cobra.Command, args []string) { 85 util.CheckErr(o.Complete(f, cmd)) 86 util.CheckErr(o.Upgrade()) 87 util.CheckErr(markKubeBlocksPodsToLoadConfigMap(o.Client)) 88 }, 89 } 90 helm.AddValueOptionsFlags(cmd.Flags(), &o.ValueOpts) 91 return cmd 92 } 93 94 func NewDescribeConfigCmd(f cmdutil.Factory, streams genericiooptions.IOStreams) *cobra.Command { 95 o := &InstallOptions{ 96 Options: Options{ 97 IOStreams: streams, 98 }, 99 } 100 var output printer.Format 101 cmd := &cobra.Command{ 102 Use: "describe-config", 103 Short: "Describe KubeBlocks config.", 104 Example: describeConfigExample, 105 Args: cobra.NoArgs, 106 Run: func(cmd *cobra.Command, args []string) { 107 util.CheckErr(o.Complete(f, cmd)) 108 util.CheckErr(describeConfig(o, output, getHelmValues)) 109 }, 110 } 111 printer.AddOutputFlag(cmd, &output) 112 cmd.Flags().BoolVarP(&showAllConfig, "all", "A", false, "show all kubeblocks configs value") 113 cmd.Flags().StringVar(&filterConfig, "filter", "", "filter the desired kubeblocks configs, multiple filtered strings are comma separated") 114 return cmd 115 } 116 117 // getHelmValues gets all kubeblocks values by helm and filter the addons values 118 func getHelmValues(release string, opt *Options) (map[string]interface{}, error) { 119 if len(opt.HelmCfg.Namespace()) == 0 { 120 namespace, err := util.GetKubeBlocksNamespace(opt.Client) 121 if err != nil { 122 return nil, err 123 } 124 opt.HelmCfg.SetNamespace(namespace) 125 } 126 values, err := helm.GetValues(release, opt.HelmCfg) 127 if err != nil { 128 return nil, err 129 } 130 // filter the addons values 131 list, err := opt.Dynamic.Resource(types.AddonGVR()).List(context.Background(), metav1.ListOptions{}) 132 if err != nil { 133 return nil, err 134 } 135 for _, item := range list.Items { 136 delete(values, item.GetName()) 137 } 138 // encrypted the sensitive values 139 for _, key := range sensitiveValues { 140 sp := strings.Split(key, ".") 141 rootKey := sp[0] 142 if node, ok := values[rootKey]; ok { 143 encryptNodeData(values, node, sp, 0) 144 } 145 } 146 return pruningConfigResults(values), nil 147 } 148 149 // encryptNodeData encrypts the specified key of helm values. will ignore the key if the type of the value is in [map, slice]. 150 func encryptNodeData(parentNode map[string]interface{}, node interface{}, sp []string, index int) { 151 switch v := node.(type) { 152 case map[string]interface{}: 153 // do nothing, if target node is not the leaf node 154 if len(sp)-1 == index { 155 return 156 } 157 index += 1 158 encryptNodeData(v, v[sp[index]], sp, index) 159 case []interface{}: 160 // ignore slice ? 161 default: 162 // reach the leaf node, encrypt the value 163 key := sp[index] 164 if _, ok := parentNode[key]; ok { 165 parentNode[key] = "******" 166 } 167 } 168 } 169 170 // pruningConfigResults prunes the configs results by options 171 func pruningConfigResults(configs map[string]interface{}) map[string]interface{} { 172 if showAllConfig { 173 return configs 174 } 175 if filterConfig != "" { 176 keyWhiteList = strings.Split(filterConfig, ",") 177 } 178 res := make(map[string]interface{}, len(keyWhiteList)) 179 for _, whiteKey := range keyWhiteList { 180 res[whiteKey] = configs[whiteKey] 181 } 182 return res 183 } 184 185 type fn func(release string, opt *Options) (map[string]interface{}, error) 186 187 // describeConfig outputs the configs got by the fn in specified format 188 func describeConfig(o *InstallOptions, format printer.Format, f fn) error { 189 values, err := f(types.KubeBlocksReleaseName, &o.Options) 190 if err != nil { 191 return err 192 } 193 printer.PrintHelmValues(values, format, o.Out) 194 return nil 195 } 196 197 // markKubeBlocksPodsToLoadConfigMap marks an annotation of the KubeBlocks pods to load the projected volumes of configmap. 198 // kubelet periodically requeues the Pod every 60-90 seconds, exactly the time it takes for Secret/ConfigMaps can be loaded in the config volumes. 199 func markKubeBlocksPodsToLoadConfigMap(client kubernetes.Interface) error { 200 deploy, err := util.GetKubeBlocksDeploy(client) 201 if err != nil { 202 return err 203 } 204 if deploy == nil { 205 return nil 206 } 207 pods, err := client.CoreV1().Pods(deploy.Namespace).List(context.Background(), metav1.ListOptions{ 208 LabelSelector: "app.kubernetes.io/name=" + types.KubeBlocksChartName, 209 }) 210 if err != nil { 211 return err 212 } 213 for _, pod := range pods.Items { 214 // mark the pod to load configmap 215 if pod.Annotations == nil { 216 pod.Annotations = map[string]string{} 217 } 218 pod.Annotations[types.ReloadConfigMapAnnotationKey] = time.Now().Format(time.RFC3339Nano) 219 _, _ = client.CoreV1().Pods(deploy.Namespace).Update(context.TODO(), &pod, metav1.UpdateOptions{}) 220 } 221 return nil 222 }