github.com/m3db/m3@v1.5.1-0.20231129193456-75a402aa583b/src/cmd/tools/m3ctl/main/main.go (about) 1 // Copyright (c) 2020 Uber Technologies, Inc. 2 // 3 // Permission is hereby granted, free of charge, to any person obtaining a copy 4 // of this software and associated documentation files (the "Software"), to deal 5 // in the Software without restriction, including without limitation the rights 6 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 // copies of the Software, and to permit persons to whom the Software is 8 // furnished to do so, subject to the following conditions: 9 // 10 // The above copyright notice and this permission notice shall be included in 11 // all copies or substantial portions of the Software. 12 // 13 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 // THE SOFTWARE. 20 21 package main 22 23 import ( 24 "bytes" 25 "encoding/json" 26 "fmt" 27 "os" 28 "strings" 29 30 "github.com/m3db/m3/src/cmd/tools/m3ctl/apply" 31 "github.com/m3db/m3/src/cmd/tools/m3ctl/namespaces" 32 "github.com/m3db/m3/src/cmd/tools/m3ctl/placements" 33 "github.com/m3db/m3/src/cmd/tools/m3ctl/topics" 34 "github.com/m3db/m3/src/query/generated/proto/admin" 35 36 "github.com/gogo/protobuf/jsonpb" 37 "github.com/spf13/cobra" 38 "go.uber.org/zap" 39 "go.uber.org/zap/zapcore" 40 ) 41 42 const ( 43 defaultEndpoint = "http://localhost:7201" 44 ) 45 46 // Defaults are (so output is easily consumable by JSON tools like "jq"). 47 // - Error log level so usually not printing anything unless error encountered 48 // so the output can be completely JSON. 49 // - Do not print log stack traces so errors aren't overwhelming output. 50 var defaultLoggerOptions = loggerOptions{ 51 level: zapcore.ErrorLevel, 52 enableStacktrace: false, 53 } 54 55 type loggerOptions struct { 56 level zapcore.Level 57 enableStacktrace bool 58 } 59 60 func mustNewLogger(opts loggerOptions) *zap.Logger { 61 loggerCfg := zap.NewDevelopmentConfig() 62 loggerCfg.Level = zap.NewAtomicLevelAt(opts.level) 63 loggerCfg.DisableStacktrace = !opts.enableStacktrace 64 logger, err := loggerCfg.Build() 65 if err != nil { 66 fmt.Fprintf(os.Stderr, err.Error()) 67 os.Exit(1) 68 } 69 70 return logger 71 } 72 73 func main() { 74 var ( 75 debug bool 76 endPoint string 77 headers = make(map[string]string) 78 yamlPath string 79 showAll bool 80 deleteAll bool 81 nodeName string 82 ) 83 84 logger := mustNewLogger(defaultLoggerOptions) 85 defer func() { 86 logger.Sync() 87 fmt.Printf("\n") // End line since most commands finish without an endpoint. 88 }() 89 90 rootCmd := &cobra.Command{ 91 Use: "m3ctl", 92 } 93 94 getCmd := &cobra.Command{ 95 Use: "get", 96 Short: "Get specified resources from the remote", 97 } 98 99 deleteCmd := &cobra.Command{ 100 Use: "delete", 101 Short: "Delete specified resources from the remote", 102 } 103 104 applyCmd := &cobra.Command{ 105 Use: "apply", 106 Short: "Apply various yamls to remote endpoint", 107 Long: `This will take specific yamls and send them over to the remote 108 endpoint. See the yaml/examples directory for examples. Operations such as 109 database creation, database init, adding a node, and replacing a node, are supported. 110 `, 111 Run: func(cmd *cobra.Command, args []string) { 112 fileArg := cmd.LocalFlags().Lookup("file").Value.String() 113 logger.Debug("running command", zap.String("name", cmd.Name()), zap.String("args", fileArg)) 114 115 if len(fileArg) == 0 { 116 logger.Fatal("need to specify a path to YAML file") 117 } 118 119 resp, err := apply.DoApply(endPoint, headers, yamlPath, logger) 120 if err != nil { 121 logger.Fatal("apply failed", zap.Error(err)) 122 } 123 124 os.Stdout.Write(resp) 125 }, 126 } 127 128 getNamespaceCmd := &cobra.Command{ 129 Use: "namespace []", 130 Short: "Get the namespaces from the remote endpoint", 131 Aliases: []string{"ns"}, 132 Run: func(cmd *cobra.Command, args []string) { 133 logger.Debug("running command", zap.String("command", cmd.Name())) 134 135 resp, err := namespaces.DoGet(endPoint, headers, logger) 136 if err != nil { 137 logger.Fatal("get namespace failed", zap.Error(err)) 138 } 139 140 if !showAll { 141 var registry admin.NamespaceGetResponse 142 unmarshaller := &jsonpb.Unmarshaler{AllowUnknownFields: true} 143 reader := bytes.NewReader(resp) 144 if err := unmarshaller.Unmarshal(reader, ®istry); err != nil { 145 logger.Fatal("could not unmarshal response", zap.Error(err)) 146 } 147 var namespaces []string 148 for k := range registry.Registry.Namespaces { 149 namespaces = append(namespaces, k) 150 } 151 // Keep output consistent and output JSON. 152 if err := json.NewEncoder(os.Stdout).Encode(namespaces); err != nil { 153 logger.Fatal("could not encode output", zap.Error(err)) 154 } 155 return 156 } 157 158 os.Stdout.Write(resp) 159 }, 160 } 161 162 getPlacementCmd := &cobra.Command{ 163 Use: "placement <m3db/m3coordinator/m3aggregator>", 164 Short: "Get service placement from the remote endpoint", 165 Args: cobra.ExactValidArgs(1), 166 ValidArgs: []string{"m3db", "m3coordinator", "m3aggregator"}, 167 Aliases: []string{"pl"}, 168 Run: func(cmd *cobra.Command, args []string) { 169 logger.Debug("running command", zap.String("command", cmd.Name())) 170 171 resp, err := placements.DoGet(endPoint, args[0], headers, logger) 172 if err != nil { 173 logger.Fatal("get placement failed", zap.Error(err)) 174 } 175 176 os.Stdout.Write(resp) 177 }, 178 } 179 180 deletePlacementCmd := &cobra.Command{ 181 Use: "placement <m3db/m3coordinator/m3aggregator>", 182 Short: "Delete service placement from the remote endpoint", 183 Args: cobra.ExactValidArgs(1), 184 ValidArgs: []string{"m3db", "m3coordinator", "m3aggregator"}, 185 Aliases: []string{"pl"}, 186 Run: func(cmd *cobra.Command, args []string) { 187 logger.Debug("running command", zap.String("command", cmd.Name())) 188 189 resp, err := placements.DoDelete(endPoint, args[0], headers, nodeName, deleteAll, logger) 190 if err != nil { 191 logger.Fatal("delete placement failed", zap.Error(err)) 192 } 193 194 os.Stdout.Write(resp) 195 }, 196 } 197 198 deleteNamespaceCmd := &cobra.Command{ 199 Use: "namespace", 200 Short: "Delete the namespace from the remote endpoint", 201 Aliases: []string{"ns"}, 202 Run: func(cmd *cobra.Command, args []string) { 203 logger.Debug("running command", zap.String("command", cmd.Name())) 204 205 resp, err := namespaces.DoDelete(endPoint, headers, nodeName, logger) 206 if err != nil { 207 logger.Fatal("delete namespace failed", zap.Error(err)) 208 } 209 210 os.Stdout.Write(resp) 211 }, 212 } 213 214 getTopicCmd := &cobra.Command{ 215 Use: "topic", 216 Short: "Get topic from the remote endpoint", 217 Aliases: []string{"t"}, 218 Run: func(cmd *cobra.Command, args []string) { 219 logger.Debug("running command", zap.String("command", cmd.Name())) 220 221 resp, err := topics.DoGet(endPoint, headers, logger) 222 if err != nil { 223 logger.Fatal("get topic failed", zap.Error(err)) 224 } 225 226 os.Stdout.Write(resp) //nolint:errcheck 227 }, 228 } 229 230 deleteTopicCmd := &cobra.Command{ 231 Use: "topic", 232 Short: "Delete topic from the remote endpoint", 233 Aliases: []string{"t"}, 234 Run: func(cmd *cobra.Command, args []string) { 235 logger.Debug("running command", zap.String("command", cmd.Name())) 236 237 resp, err := topics.DoDelete(endPoint, headers, logger) 238 if err != nil { 239 logger.Fatal("delete topic failed", zap.Error(err)) 240 } 241 242 os.Stdout.Write(resp) //nolint:errcheck 243 }, 244 } 245 246 rootCmd.AddCommand(getCmd, applyCmd, deleteCmd) 247 getCmd.AddCommand(getNamespaceCmd) 248 getCmd.AddCommand(getPlacementCmd) 249 getCmd.AddCommand(getTopicCmd) 250 deleteCmd.AddCommand(deletePlacementCmd) 251 deleteCmd.AddCommand(deleteNamespaceCmd) 252 deleteCmd.AddCommand(deleteTopicCmd) 253 254 var headersSlice []string 255 rootCmd.PersistentFlags().BoolVarP(&debug, "debug", "d", false, "debug log output level (cannot use JSON output)") 256 rootCmd.PersistentFlags().StringVar(&endPoint, "endpoint", defaultEndpoint, "m3coordinator endpoint URL") 257 rootCmd.PersistentFlags().StringSliceVarP(&headersSlice, "header", "H", []string{}, "headers to append to requests") 258 applyCmd.Flags().StringVarP(&yamlPath, "file", "f", "", "times to echo the input") 259 getNamespaceCmd.Flags().BoolVarP(&showAll, "show-all", "a", false, "times to echo the input") 260 deletePlacementCmd.Flags().BoolVarP(&deleteAll, "delete-all", "a", false, "delete the entire placement") 261 deleteCmd.PersistentFlags().StringVarP(&nodeName, "name", "n", "", "which namespace or node to delete") 262 263 rootCmd.PersistentPreRunE = func(cmd *cobra.Command, args []string) error { 264 // Override logger if debug flag set. 265 if debug { 266 logger = mustNewLogger(loggerOptions{ 267 level: zapcore.DebugLevel, 268 enableStacktrace: true, 269 }) 270 } 271 272 // Parse headers slice. 273 for _, h := range headersSlice { 274 parts := strings.Split(h, ":") 275 if len(parts) != 2 { 276 return fmt.Errorf( 277 "header must be of format 'name: value': actual='%s'", h) 278 } 279 280 name, value := strings.TrimSpace(parts[0]), strings.TrimSpace(parts[1]) 281 headers[name] = value 282 } 283 284 return nil 285 } 286 287 rootCmd.Execute() 288 }