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 }