github.com/pingcap/tiflow@v0.0.0-20240520035814-5bf52d54e205/pkg/cmd/cli/cli_changefeed_update.go (about)

     1  // Copyright 2021 PingCAP, Inc.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // See the License for the specific language governing permissions and
    12  // limitations under the License.
    13  
    14  package cli
    15  
    16  import (
    17  	"encoding/json"
    18  	"strings"
    19  
    20  	"github.com/pingcap/log"
    21  	v2 "github.com/pingcap/tiflow/cdc/api/v2"
    22  	apiv2client "github.com/pingcap/tiflow/pkg/api/v2"
    23  	cmdcontext "github.com/pingcap/tiflow/pkg/cmd/context"
    24  	"github.com/pingcap/tiflow/pkg/cmd/factory"
    25  	"github.com/pingcap/tiflow/pkg/cmd/util"
    26  	putil "github.com/pingcap/tiflow/pkg/util"
    27  	"github.com/r3labs/diff"
    28  	"github.com/spf13/cobra"
    29  	"github.com/spf13/pflag"
    30  	"go.uber.org/zap"
    31  )
    32  
    33  // updateChangefeedOptions defines common flags for the `cli changefeed update` command.
    34  type updateChangefeedOptions struct {
    35  	apiV2Client apiv2client.APIV2Interface
    36  
    37  	commonChangefeedOptions *changefeedCommonOptions
    38  	changefeedID            string
    39  	namespace               string
    40  }
    41  
    42  // newUpdateChangefeedOptions creates new options for the `cli changefeed update` command.
    43  func newUpdateChangefeedOptions(commonChangefeedOptions *changefeedCommonOptions) *updateChangefeedOptions {
    44  	return &updateChangefeedOptions{
    45  		commonChangefeedOptions: commonChangefeedOptions,
    46  	}
    47  }
    48  
    49  // addFlags receives a *cobra.Command reference and binds
    50  // flags related to template printing to it.
    51  func (o *updateChangefeedOptions) addFlags(cmd *cobra.Command) {
    52  	o.commonChangefeedOptions.addFlags(cmd)
    53  	cmd.PersistentFlags().StringVarP(&o.namespace, "namespace", "n", "default", "Replication task (changefeed) Namespace")
    54  	cmd.PersistentFlags().StringVarP(&o.changefeedID, "changefeed-id", "c", "", "Replication task (changefeed) ID")
    55  	_ = cmd.MarkPersistentFlagRequired("changefeed-id")
    56  }
    57  
    58  func (o *updateChangefeedOptions) getChangefeedConfig(cmd *cobra.Command,
    59  	info *v2.ChangeFeedInfo,
    60  ) *v2.ChangefeedConfig {
    61  	replicaConfig := info.Config
    62  	res := &v2.ChangefeedConfig{
    63  		TargetTs:      info.TargetTs,
    64  		SinkURI:       info.SinkURI,
    65  		ReplicaConfig: replicaConfig,
    66  	}
    67  	cmd.Flags().Visit(func(flag *pflag.Flag) {
    68  		switch flag.Name {
    69  		case "upstream-pd":
    70  			res.PDAddrs = strings.Split(o.commonChangefeedOptions.upstreamPDAddrs, ",")
    71  		case "upstream-ca":
    72  			res.CAPath = o.commonChangefeedOptions.upstreamCaPath
    73  		case "upstream-cert":
    74  			res.CertPath = o.commonChangefeedOptions.upstreamCertPath
    75  		case "upstream-key":
    76  			res.KeyPath = o.commonChangefeedOptions.upstreamKeyPath
    77  		}
    78  	})
    79  	return res
    80  }
    81  
    82  // complete adapts from the command line args to the data and client required.
    83  func (o *updateChangefeedOptions) complete(f factory.Factory) error {
    84  	apiClient, err := f.APIV2Client()
    85  	if err != nil {
    86  		return err
    87  	}
    88  	o.apiV2Client = apiClient
    89  	return nil
    90  }
    91  
    92  // run the `cli changefeed update` command.
    93  func (o *updateChangefeedOptions) run(cmd *cobra.Command) error {
    94  	ctx := cmdcontext.GetDefaultContext()
    95  
    96  	old, err := o.apiV2Client.Changefeeds().Get(ctx, o.namespace, o.changefeedID)
    97  	if err != nil {
    98  		return err
    99  	}
   100  
   101  	newInfo, err := o.applyChanges(old, cmd)
   102  	if err != nil {
   103  		return err
   104  	}
   105  	// sink uri is not changed, set old to empty to skip diff
   106  	if newInfo.SinkURI == "" {
   107  		old.SinkURI = ""
   108  	}
   109  	changelog, err := diff.Diff(old, newInfo)
   110  	if err != nil {
   111  		return err
   112  	}
   113  	if len(changelog) == 0 {
   114  		cmd.Printf("changefeed config is the same with the old one, do nothing\n")
   115  		return nil
   116  	}
   117  	cmd.Printf("Diff of changefeed config:\n")
   118  	for _, change := range changelog {
   119  		cmd.Printf("%+v\n", change)
   120  	}
   121  
   122  	if !o.commonChangefeedOptions.noConfirm {
   123  		cmd.Printf("Could you agree to apply changes above to changefeed [Y/N]\n")
   124  		confirmed := readYOrN(cmd)
   125  		if !confirmed {
   126  			cmd.Printf("No update to changefeed.\n")
   127  			return nil
   128  		}
   129  	}
   130  
   131  	changefeedConfig := o.getChangefeedConfig(cmd, newInfo)
   132  	info, err := o.apiV2Client.Changefeeds().Update(ctx, changefeedConfig, o.namespace, o.changefeedID)
   133  	if err != nil {
   134  		return err
   135  	}
   136  	infoStr, err := json.Marshal(info)
   137  	if err != nil {
   138  		return err
   139  	}
   140  	cmd.Printf("Update changefeed config successfully! "+
   141  		"\nID: %s\nInfo: %s\n", o.changefeedID, infoStr)
   142  
   143  	return nil
   144  }
   145  
   146  // applyChanges applies the new changes to the old changefeed.
   147  func (o *updateChangefeedOptions) applyChanges(oldInfo *v2.ChangeFeedInfo,
   148  	cmd *cobra.Command,
   149  ) (*v2.ChangeFeedInfo, error) {
   150  	newInfo, err := oldInfo.Clone()
   151  	if err != nil {
   152  		return nil, err
   153  	}
   154  	newInfo.SinkURI = ""
   155  	cmd.Flags().Visit(func(flag *pflag.Flag) {
   156  		switch flag.Name {
   157  		case "target-ts":
   158  			newInfo.TargetTs = o.commonChangefeedOptions.targetTs
   159  		case "sink-uri":
   160  			newInfo.SinkURI = o.commonChangefeedOptions.sinkURI
   161  		case "config":
   162  			cfg := newInfo.Config.ToInternalReplicaConfig()
   163  			if err = o.commonChangefeedOptions.strictDecodeConfig("TiCDC changefeed", cfg); err != nil {
   164  				log.Error("decode config file error", zap.Error(err))
   165  			}
   166  			newInfo.Config = v2.ToAPIReplicaConfig(cfg)
   167  		case "schema-registry":
   168  			newInfo.Config.Sink.SchemaRegistry = putil.AddressOf(o.commonChangefeedOptions.schemaRegistry)
   169  		case "sort-engine":
   170  		case "sort-dir":
   171  			log.Warn("this flag cannot be updated and will be ignored", zap.String("flagName", flag.Name))
   172  		case "changefeed-id", "no-confirm":
   173  			// Do nothing, these are some flags from the changefeed command,
   174  			// we don't use it to update, but we do use these flags.
   175  		case "pd", "log-level", "key", "cert", "ca", "server":
   176  		// Do nothing, this is a flags from the cli command
   177  		// we don't use it to update, but we do use these flags.
   178  		case "upstream-pd", "upstream-ca", "upstream-cert", "upstream-key":
   179  		default:
   180  			// use this default branch to prevent new added parameter is not added
   181  			log.Warn("unsupported flag, please report a bug", zap.String("flagName", flag.Name))
   182  		}
   183  	})
   184  	if err != nil {
   185  		return nil, err
   186  	}
   187  	return newInfo, nil
   188  }
   189  
   190  // newCmdPauseChangefeed creates the `cli changefeed update` command.
   191  func newCmdUpdateChangefeed(f factory.Factory) *cobra.Command {
   192  	commonChangefeedOptions := newChangefeedCommonOptions()
   193  	o := newUpdateChangefeedOptions(commonChangefeedOptions)
   194  
   195  	command := &cobra.Command{
   196  		Use:   "update",
   197  		Short: "Update config of an existing replication task (changefeed)",
   198  		Args:  cobra.NoArgs,
   199  		Run: func(cmd *cobra.Command, args []string) {
   200  			util.CheckErr(o.complete(f))
   201  			util.CheckErr(o.run(cmd))
   202  		},
   203  	}
   204  
   205  	o.addFlags(command)
   206  
   207  	return command
   208  }