github.com/pingcap/tiflow@v0.0.0-20240520035814-5bf52d54e205/pkg/cmd/util/helper.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 util
    15  
    16  import (
    17  	"context"
    18  	"encoding/json"
    19  	"net/url"
    20  	"os"
    21  	"os/signal"
    22  	"strings"
    23  	"syscall"
    24  
    25  	"github.com/BurntSushi/toml"
    26  	"github.com/pingcap/errors"
    27  	"github.com/pingcap/log"
    28  	cmdconetxt "github.com/pingcap/tiflow/pkg/cmd/context"
    29  	cerror "github.com/pingcap/tiflow/pkg/errors"
    30  	"github.com/pingcap/tiflow/pkg/logutil"
    31  	"github.com/spf13/cobra"
    32  	"go.uber.org/zap"
    33  	"golang.org/x/net/http/httpproxy"
    34  )
    35  
    36  // Endpoint schemes.
    37  const (
    38  	HTTP  = "http"
    39  	HTTPS = "https"
    40  )
    41  
    42  // InitCmd initializes the logger, the default context and returns its cancel function.
    43  func InitCmd(cmd *cobra.Command, logCfg *logutil.Config) context.CancelFunc {
    44  	// Init log.
    45  	err := logutil.InitLogger(
    46  		logCfg,
    47  		logutil.WithInitGRPCLogger(),
    48  		logutil.WithInitSaramaLogger(),
    49  		logutil.WithInitMySQLLogger())
    50  	if err != nil {
    51  		cmd.Printf("init logger error %v\n", errors.ErrorStack(err))
    52  		os.Exit(1)
    53  	}
    54  	log.Info("init log", zap.String("file", logCfg.File), zap.String("level", logCfg.Level))
    55  
    56  	ctx, cancel := context.WithCancel(context.Background())
    57  	cmdconetxt.SetDefaultContext(ctx)
    58  
    59  	return cancel
    60  }
    61  
    62  // shutdownNotify is a callback to notify caller that TiCDC is about to shutdown.
    63  // It returns a done channel which receive an empty struct when shutdown is complete.
    64  // It must be non-blocking.
    65  type shutdownNotify func() <-chan struct{}
    66  
    67  // InitSignalHandling initializes signal handling.
    68  // It must be called after InitCmd.
    69  func InitSignalHandling(shutdown shutdownNotify, cancel context.CancelFunc) {
    70  	// systemd and k8s send signals twice. The first is for graceful shutdown,
    71  	// and the second is for force shutdown.
    72  	// We use 2 for channel length to ease testing.
    73  	signalChanLen := 2
    74  	sc := make(chan os.Signal, signalChanLen)
    75  	signal.Notify(sc,
    76  		syscall.SIGHUP,
    77  		syscall.SIGINT,
    78  		syscall.SIGTERM,
    79  		syscall.SIGQUIT)
    80  
    81  	go func() {
    82  		sig := <-sc
    83  		log.Info("got signal, prepare to shutdown", zap.Stringer("signal", sig))
    84  		done := shutdown()
    85  		select {
    86  		case <-done:
    87  			log.Info("shutdown complete")
    88  		case sig = <-sc:
    89  			log.Info("got signal, force shutdown", zap.Stringer("signal", sig))
    90  		}
    91  		cancel()
    92  	}()
    93  }
    94  
    95  // LogHTTPProxies logs HTTP proxy relative environment variables.
    96  func LogHTTPProxies() {
    97  	fields := findProxyFields()
    98  	if len(fields) > 0 {
    99  		log.Info("using proxy config", fields...)
   100  	}
   101  }
   102  
   103  func findProxyFields() []zap.Field {
   104  	proxyCfg := httpproxy.FromEnvironment()
   105  	fields := make([]zap.Field, 0, 3)
   106  	if proxyCfg.HTTPProxy != "" {
   107  		fields = append(fields, zap.String("http_proxy", proxyCfg.HTTPProxy))
   108  	}
   109  	if proxyCfg.HTTPSProxy != "" {
   110  		fields = append(fields, zap.String("https_proxy", proxyCfg.HTTPSProxy))
   111  	}
   112  	if proxyCfg.NoProxy != "" {
   113  		fields = append(fields, zap.String("no_proxy", proxyCfg.NoProxy))
   114  	}
   115  	return fields
   116  }
   117  
   118  // StrictDecodeFile decodes the toml file strictly. If any item in confFile file is not mapped
   119  // into the Config struct, issue an error and stop the server from starting.
   120  func StrictDecodeFile(path, component string, cfg interface{}, ignoreCheckItems ...string) error {
   121  	metaData, err := toml.DecodeFile(path, cfg)
   122  	if err != nil {
   123  		return errors.Trace(err)
   124  	}
   125  
   126  	// check if item is a ignoreCheckItem
   127  	hasIgnoreItem := func(item []string) bool {
   128  		for _, ignoreCheckItem := range ignoreCheckItems {
   129  			if item[0] == ignoreCheckItem {
   130  				return true
   131  			}
   132  		}
   133  		return false
   134  	}
   135  
   136  	if undecoded := metaData.Undecoded(); len(undecoded) > 0 {
   137  		var b strings.Builder
   138  		hasUnknownConfigSize := 0
   139  		for _, item := range undecoded {
   140  			if hasIgnoreItem(item) {
   141  				continue
   142  			}
   143  
   144  			if hasUnknownConfigSize > 0 {
   145  				b.WriteString(", ")
   146  			}
   147  			b.WriteString(item.String())
   148  			hasUnknownConfigSize++
   149  		}
   150  		if hasUnknownConfigSize > 0 {
   151  			err = errors.Errorf("component %s's config file %s contained unknown configuration options: %s",
   152  				component, path, b.String())
   153  		}
   154  	}
   155  	return errors.Trace(err)
   156  }
   157  
   158  // VerifyPdEndpoint verifies whether the pd endpoint is a valid http or https URL.
   159  // The certificate is required when using https.
   160  func VerifyPdEndpoint(pdEndpoint string, useTLS bool) error {
   161  	u, err := url.Parse(pdEndpoint)
   162  	if err != nil {
   163  		return errors.Annotate(err, "parse PD endpoint")
   164  	}
   165  	if (u.Scheme != HTTP && u.Scheme != HTTPS) || u.Host == "" {
   166  		return errors.New("PD endpoint should be a valid http or https URL")
   167  	}
   168  
   169  	if useTLS {
   170  		if u.Scheme == HTTP {
   171  			return errors.New("PD endpoint scheme should be https")
   172  		}
   173  	} else {
   174  		if u.Scheme == HTTPS {
   175  			return errors.New("PD endpoint scheme is https, please provide certificate")
   176  		}
   177  	}
   178  	return nil
   179  }
   180  
   181  // JSONPrint will output the data in JSON format.
   182  func JSONPrint(cmd *cobra.Command, v interface{}) error {
   183  	data, err := json.MarshalIndent(v, "", "  ")
   184  	if err != nil {
   185  		return err
   186  	}
   187  	cmd.Printf("%s\n", data)
   188  	return nil
   189  }
   190  
   191  // CheckErr is used to cmd err.
   192  func CheckErr(err error) {
   193  	if cerror.IsCliUnprintableError(err) {
   194  		err = nil
   195  	}
   196  	if err != nil {
   197  		if strings.Contains(err.Error(), string(cerror.ErrCredentialNotFound.RFCCode())) {
   198  			msg := ", please use the following command to create a new one:\n" +
   199  				"1. specify the credential in the command line with `cdc cli --user <user> --password <password>`.\n" +
   200  				"2. specify the credential in the environment variables with `export TICDC_USER=<user> TICDC_PASSWORD=<password>`.\n" +
   201  				"3. `cdc cli configure-credentials` to initialize the default credential config.\n"
   202  			err = errors.New(err.Error() + msg)
   203  		}
   204  	}
   205  	cobra.CheckErr(err)
   206  }