vitess.io/vitess@v0.16.2/go/vt/vtctl/topo.go (about) 1 /* 2 Copyright 2019 The Vitess Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package vtctl 18 19 import ( 20 "context" 21 "encoding/json" 22 "fmt" 23 "os" 24 25 "github.com/spf13/pflag" 26 27 "vitess.io/vitess/go/vt/topo" 28 "vitess.io/vitess/go/vt/wrangler" 29 ) 30 31 // This file contains the topo command group for vtctl. 32 33 const topoGroupName = "Topo" 34 35 func init() { 36 addCommandGroup(topoGroupName) 37 38 addCommand(topoGroupName, command{ 39 name: "TopoCat", 40 method: commandTopoCat, 41 params: "[--cell <cell>] [--decode_proto] [--decode_proto_json] [--long] <path> [<path>...]", 42 help: "Retrieves the file(s) at <path> from the topo service, and displays it. It can resolve wildcards, and decode the proto-encoded data.", 43 }) 44 45 addCommand(topoGroupName, command{ 46 name: "TopoCp", 47 method: commandTopoCp, 48 params: "[--cell <cell>] [--to_topo] <src> <dst>", 49 help: "Copies a file from topo to local file structure, or the other way around", 50 }) 51 } 52 53 func commandTopoCat(ctx context.Context, wr *wrangler.Wrangler, subFlags *pflag.FlagSet, args []string) error { 54 cell := subFlags.String("cell", topo.GlobalCell, "topology cell to cat the file from. Defaults to global cell.") 55 long := subFlags.Bool("long", false, "long listing.") 56 decodeProtoJSON := subFlags.Bool("decode_proto_json", false, "decode proto files and display them as json") 57 decodeProto := subFlags.Bool("decode_proto", false, "decode proto files and display them as text") 58 subFlags.Parse(args) 59 if subFlags.NArg() == 0 { 60 return fmt.Errorf("TopoCat: no path specified") 61 } 62 resolved, err := wr.TopoServer().ResolveWildcards(ctx, *cell, subFlags.Args()) 63 if err != nil { 64 return fmt.Errorf("TopoCat: invalid wildcards: %v", err) 65 } 66 if len(resolved) == 0 { 67 // The wildcards didn't result in anything, we're done. 68 return nil 69 } 70 71 conn, err := wr.TopoServer().ConnForCell(ctx, *cell) 72 if err != nil { 73 return err 74 } 75 76 var topologyDecoder TopologyDecoder 77 switch { 78 case *decodeProtoJSON: 79 topologyDecoder = JSONTopologyDecoder{} 80 case *decodeProto: 81 topologyDecoder = ProtoTopologyDecoder{} 82 default: 83 topologyDecoder = PlainTopologyDecoder{} 84 } 85 86 return topologyDecoder.decode(ctx, resolved, conn, wr, *long) 87 } 88 89 func commandTopoCp(ctx context.Context, wr *wrangler.Wrangler, subFlags *pflag.FlagSet, args []string) error { 90 cell := subFlags.String("cell", topo.GlobalCell, "topology cell to use for the copy. Defaults to global cell.") 91 toTopo := subFlags.Bool("to_topo", false, "copies from local server to topo instead (reverse direction).") 92 subFlags.Parse(args) 93 if subFlags.NArg() != 2 { 94 return fmt.Errorf("TopoCp: need source and destination") 95 } 96 from := subFlags.Arg(0) 97 to := subFlags.Arg(1) 98 if *toTopo { 99 return copyFileToTopo(ctx, wr.TopoServer(), *cell, from, to) 100 } 101 return copyFileFromTopo(ctx, wr.TopoServer(), *cell, from, to) 102 } 103 104 func copyFileFromTopo(ctx context.Context, ts *topo.Server, cell, from, to string) error { 105 conn, err := ts.ConnForCell(ctx, cell) 106 if err != nil { 107 return err 108 } 109 data, _, err := conn.Get(ctx, from) 110 if err != nil { 111 return err 112 } 113 return os.WriteFile(to, data, 0644) 114 } 115 116 func copyFileToTopo(ctx context.Context, ts *topo.Server, cell, from, to string) error { 117 conn, err := ts.ConnForCell(ctx, cell) 118 if err != nil { 119 return err 120 } 121 data, err := os.ReadFile(from) 122 if err != nil { 123 return err 124 } 125 _, err = conn.Update(ctx, to, data, nil) 126 return err 127 } 128 129 // TopologyDecoder interface for exporting out a leaf node in a readable form 130 type TopologyDecoder interface { 131 decode(context.Context, []string, topo.Conn, *wrangler.Wrangler, bool) error 132 } 133 134 // ProtoTopologyDecoder exports topo node as a proto 135 type ProtoTopologyDecoder struct{} 136 137 // PlainTopologyDecoder exports topo node as plain text 138 type PlainTopologyDecoder struct{} 139 140 // JSONTopologyDecoder exports topo node as JSON 141 type JSONTopologyDecoder struct{} 142 143 func (d ProtoTopologyDecoder) decode(ctx context.Context, topoPaths []string, conn topo.Conn, wr *wrangler.Wrangler, long bool) error { 144 hasError := false 145 for _, topoPath := range topoPaths { 146 data, version, err := conn.Get(ctx, topoPath) 147 if err != nil { 148 hasError = true 149 wr.Logger().Printf("TopoCat: Get(%v) failed: %v\n", topoPath, err) 150 continue 151 } 152 153 if long { 154 wr.Logger().Printf("path=%v version=%v\n", topoPath, version) 155 } 156 157 decoded, err := topo.DecodeContent(topoPath, data, false) 158 if err != nil { 159 wr.Logger().Warningf("TopoCat: cannot proto decode %v: %v", topoPath, err) 160 decoded = string(data) 161 } 162 163 wr.Logger().Printf(decoded) 164 if len(decoded) > 0 && decoded[len(decoded)-1] != '\n' && long { 165 wr.Logger().Printf("\n") 166 } 167 } 168 169 if hasError { 170 return fmt.Errorf("TopoCat: some paths had errors") 171 } 172 return nil 173 } 174 175 func (d PlainTopologyDecoder) decode(ctx context.Context, topoPaths []string, conn topo.Conn, wr *wrangler.Wrangler, long bool) error { 176 hasError := false 177 for _, topoPath := range topoPaths { 178 data, version, err := conn.Get(ctx, topoPath) 179 if err != nil { 180 hasError = true 181 wr.Logger().Printf("TopoCat: Get(%v) failed: %v\n", topoPath, err) 182 continue 183 } 184 185 if long { 186 wr.Logger().Printf("path=%v version=%v\n", topoPath, version) 187 } 188 decoded := string(data) 189 wr.Logger().Printf(decoded) 190 if len(decoded) > 0 && decoded[len(decoded)-1] != '\n' && long { 191 wr.Logger().Printf("\n") 192 } 193 } 194 195 if hasError { 196 return fmt.Errorf("TopoCat: some paths had errors") 197 } 198 return nil 199 } 200 201 func (d JSONTopologyDecoder) decode(ctx context.Context, topoPaths []string, conn topo.Conn, wr *wrangler.Wrangler, long bool) error { 202 hasError := false 203 var jsonData []any 204 for _, topoPath := range topoPaths { 205 data, version, err := conn.Get(ctx, topoPath) 206 if err != nil { 207 hasError = true 208 wr.Logger().Printf("TopoCat: Get(%v) failed: %v\n", topoPath, err) 209 continue 210 } 211 212 decoded, err := topo.DecodeContent(topoPath, data, true) 213 if err != nil { 214 hasError = true 215 wr.Logger().Printf("TopoCat: cannot proto decode %v: %v", topoPath, err) 216 continue 217 } 218 219 var jsonDatum map[string]any 220 if err = json.Unmarshal([]byte(decoded), &jsonDatum); err != nil { 221 hasError = true 222 wr.Logger().Printf("TopoCat: cannot json Unmarshal %v: %v", topoPath, err) 223 continue 224 } 225 226 if long { 227 jsonDatum["__path"] = topoPath 228 jsonDatum["__version"] = version.String() 229 } 230 jsonData = append(jsonData, jsonDatum) 231 } 232 233 jsonBytes, err := json.Marshal(jsonData) 234 if err != nil { 235 hasError = true 236 wr.Logger().Printf("TopoCat: cannot json Marshal: %v", err) 237 } else { 238 wr.Logger().Printf(string(jsonBytes) + "\n") 239 } 240 241 if hasError { 242 return fmt.Errorf("TopoCat: some paths had errors") 243 } 244 return nil 245 }