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  }