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  }