github.com/1aal/kubeblocks@v0.0.0-20231107070852-e1c03e598921/pkg/cli/cmd/clusterversion/set_default.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 clusterversion 21 22 import ( 23 "context" 24 "encoding/json" 25 "fmt" 26 27 "github.com/spf13/cobra" 28 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 29 apitypes "k8s.io/apimachinery/pkg/types" 30 utilerrors "k8s.io/apimachinery/pkg/util/errors" 31 "k8s.io/cli-runtime/pkg/genericiooptions" 32 "k8s.io/client-go/dynamic" 33 cmdutil "k8s.io/kubectl/pkg/cmd/util" 34 "k8s.io/kubectl/pkg/util/templates" 35 36 "github.com/1aal/kubeblocks/pkg/cli/types" 37 "github.com/1aal/kubeblocks/pkg/cli/util" 38 "github.com/1aal/kubeblocks/pkg/constant" 39 ) 40 41 var ( 42 setDefaultExample = templates.Examples(` 43 # set ac-mysql-8.0.30 as the default clusterversion 44 kbcli clusterversion set-default ac-mysql-8.0.30`, 45 ) 46 47 unsetDefaultExample = templates.Examples(` 48 # unset ac-mysql-8.0.30 to default clusterversion if it's default 49 kbcli clusterversion unset-default ac-mysql-8.0.30`) 50 51 clusterVersionGVR = types.ClusterVersionGVR() 52 ) 53 54 const ( 55 annotationTrueValue = "true" 56 annotationFalseValue = "false" 57 ) 58 59 type SetOrUnsetDefaultOption struct { 60 Factory cmdutil.Factory 61 IOStreams genericiooptions.IOStreams 62 // `set-default` sets the setDefault to true, `unset-default` sets to false 63 setDefault bool 64 } 65 66 func newSetOrUnsetDefaultOptions(f cmdutil.Factory, streams genericiooptions.IOStreams, toSet bool) *SetOrUnsetDefaultOption { 67 return &SetOrUnsetDefaultOption{ 68 Factory: f, 69 IOStreams: streams, 70 setDefault: toSet, 71 } 72 } 73 74 func newSetDefaultCMD(f cmdutil.Factory, streams genericiooptions.IOStreams) *cobra.Command { 75 o := newSetOrUnsetDefaultOptions(f, streams, true) 76 cmd := &cobra.Command{ 77 Use: "set-default NAME", 78 Short: "Set the clusterversion to the default clusterversion for its clusterdefinition.", 79 Example: setDefaultExample, 80 ValidArgsFunction: util.ResourceNameCompletionFunc(f, clusterVersionGVR), 81 Run: func(cmd *cobra.Command, args []string) { 82 util.CheckErr(o.validate(args)) 83 util.CheckErr(o.run(args)) 84 }, 85 } 86 return cmd 87 } 88 89 func newUnSetDefaultCMD(f cmdutil.Factory, streams genericiooptions.IOStreams) *cobra.Command { 90 o := newSetOrUnsetDefaultOptions(f, streams, false) 91 cmd := &cobra.Command{ 92 Use: "unset-default NAME", 93 Short: "Unset the clusterversion if it's default.", 94 Example: unsetDefaultExample, 95 ValidArgsFunction: util.ResourceNameCompletionFunc(f, clusterVersionGVR), 96 Run: func(cmd *cobra.Command, args []string) { 97 util.CheckErr(o.validate(args)) 98 util.CheckErr(o.run(args)) 99 }, 100 } 101 return cmd 102 } 103 104 func (o *SetOrUnsetDefaultOption) run(args []string) error { 105 client, err := o.Factory.DynamicClient() 106 if err != nil { 107 return err 108 } 109 var allErrs []error 110 // unset-default logic 111 if !o.setDefault { 112 for _, cv := range args { 113 if err := patchDefaultClusterVersionAnnotations(client, cv, annotationFalseValue); err != nil { 114 allErrs = append(allErrs, err) 115 } 116 } 117 return utilerrors.NewAggregate(allErrs) 118 } 119 // set-default logic 120 cv2Cd, cd2DefaultCv, err := getMapsBetweenCvAndCd(client) 121 if err != nil { 122 return err 123 } 124 // alreadySet is to mark if two input args have the same clusterdefintion 125 alreadySet := make(map[string]string) 126 for _, cv := range args { 127 cd, ok := cv2Cd[cv] 128 if !ok { 129 allErrs = append(allErrs, fmt.Errorf("cluterversion \"%s\" not found", cv)) 130 continue 131 } 132 if _, ok := cd2DefaultCv[cd]; ok && cv != cd2DefaultCv[cd] { 133 allErrs = append(allErrs, fmt.Errorf("clusterdefinition \"%s\" already has a default cluster version \"%s\"", cv2Cd[cv], cd2DefaultCv[cd])) 134 continue 135 } 136 if _, ok := alreadySet[cd]; ok { 137 allErrs = append(allErrs, fmt.Errorf("\"%s\" has the same clusterdefinition with \"%s\"", cv, alreadySet[cd])) 138 continue 139 } 140 if err := patchDefaultClusterVersionAnnotations(client, cv, annotationTrueValue); err != nil { 141 allErrs = append(allErrs, err) 142 continue 143 } 144 alreadySet[cd] = cv 145 } 146 return utilerrors.NewAggregate(allErrs) 147 } 148 149 func (o *SetOrUnsetDefaultOption) validate(args []string) error { 150 if len(args) == 0 { 151 return fmt.Errorf("clusterversion name should be specified, run \"kbcli clusterversion list\" to list the clusterversions") 152 } 153 return nil 154 } 155 156 // patchDefaultClusterVersionAnnotations patches the Annotations for the clusterversion in K8S 157 func patchDefaultClusterVersionAnnotations(client dynamic.Interface, cvName string, value string) error { 158 patchData := map[string]interface{}{ 159 "metadata": map[string]interface{}{ 160 "annotations": map[string]interface{}{ 161 constant.DefaultClusterVersionAnnotationKey: value, 162 }, 163 }, 164 } 165 patchBytes, _ := json.Marshal(patchData) 166 _, err := client.Resource(clusterVersionGVR).Patch(context.Background(), cvName, apitypes.MergePatchType, patchBytes, metav1.PatchOptions{}) 167 return err 168 } 169 170 func getMapsBetweenCvAndCd(client dynamic.Interface) (map[string]string, map[string]string, error) { 171 lists, err := client.Resource(clusterVersionGVR).List(context.Background(), metav1.ListOptions{}) 172 if err != nil { 173 return nil, nil, err 174 } 175 cvToCd := make(map[string]string) 176 cdToDefaultCv := make(map[string]string) 177 for _, item := range lists.Items { 178 name := item.GetName() 179 annotations := item.GetAnnotations() 180 labels := item.GetLabels() 181 if labels == nil { 182 continue 183 } 184 if _, ok := labels[constant.ClusterDefLabelKey]; !ok { 185 continue 186 } 187 cvToCd[name] = labels[constant.ClusterDefLabelKey] 188 if annotations == nil { 189 continue 190 } 191 if annotations[constant.DefaultClusterVersionAnnotationKey] == annotationTrueValue { 192 cdToDefaultCv[labels[constant.ClusterDefLabelKey]] = name 193 } 194 } 195 return cvToCd, cdToDefaultCv, nil 196 }