// txnCommandFunc executes the "txn" command. func txnCommandFunc(c *cli.Context) { if len(c.Args()) != 0 { panic("unexpected args") } reader := bufio.NewReader(os.Stdin) next := compareState txn := &pb.TxnRequest{} for next != nil { next = next(txn, reader) } conn, err := grpc.Dial(c.GlobalString("endpoint")) if err != nil { panic(err) } etcd := pb.NewEtcdClient(conn) resp, err := etcd.Txn(context.Background(), txn) if err != nil { fmt.Println(err) } if resp.Succeeded { fmt.Println("executed success request list") } else { fmt.Println("executed failure request list") } }
func newClient(c *cli.Context) (client.Client, error) { eps, err := getEndpoints(c) if err != nil { return nil, err } tr, err := getTransport(c) if err != nil { return nil, err } cfg := client.Config{ Transport: tr, Endpoints: eps, HeaderTimeoutPerRequest: c.GlobalDuration("timeout"), } uFlag := c.GlobalString("username") if uFlag != "" { username, password, err := getUsernamePasswordFromFlag(uFlag) if err != nil { return nil, err } cfg.Username = username cfg.Password = password } return client.New(cfg) }
// mkCommandFunc executes the "mk" command. func mkCommandFunc(c *cli.Context, ki client.KeysAPI) { if len(c.Args()) == 0 { handleError(ExitBadArgs, errors.New("key required")) } key := c.Args()[0] value, err := argOrStdin(c.Args(), os.Stdin, 1) if err != nil { handleError(ExitBadArgs, errors.New("value required")) } ttl := c.Int("ttl") ctx, cancel := contextWithTotalTimeout(c) // Since PrevNoExist means that the Node must not exist previously, // this Set method always creates a new key. Therefore, mk command // succeeds only if the key did not previously exist, and the command // prevents one from overwriting values accidentally. resp, err := ki.Set(ctx, key, value, &client.SetOptions{TTL: time.Duration(ttl) * time.Second, PrevExist: client.PrevNoExist}) cancel() if err != nil { handleError(ExitServerError, err) } printResponseKey(resp, c.GlobalString("output")) }
// deleteRangeCommandFunc executes the "delegeRange" command. func deleteRangeCommandFunc(c *cli.Context) { if len(c.Args()) == 0 { panic("bad arg") } var rangeEnd []byte key := []byte(c.Args()[0]) if len(c.Args()) > 1 { rangeEnd = []byte(c.Args()[1]) } conn, err := grpc.Dial(c.GlobalString("endpoint")) if err != nil { panic(err) } etcd := pb.NewEtcdClient(conn) req := &pb.DeleteRangeRequest{Key: key, RangeEnd: rangeEnd} etcd.DeleteRange(context.Background(), req) if rangeEnd != nil { fmt.Printf("range [%s, %s) is deleted\n", string(key), string(rangeEnd)) } else { fmt.Printf("key %s is deleted\n", string(key)) } }
func mustNewAuthRoleAPI(c *cli.Context) client.AuthRoleAPI { hc := mustNewClient(c) if c.GlobalBool("debug") { fmt.Fprintf(os.Stderr, "Cluster-Endpoints: %s\n", strings.Join(hc.Endpoints(), ", ")) } return client.NewAuthRoleAPI(hc) }
func mustRoleAPIAndName(c *cli.Context) (client.AuthRoleAPI, string) { args := c.Args() if len(args) != 1 { fmt.Fprintln(os.Stderr, "Please provide a role name") os.Exit(1) } name := args[0] api := mustNewAuthRoleAPI(c) return api, name }
func mustUserAPIAndName(c *cli.Context) (client.AuthUserAPI, string) { args := c.Args() if len(args) != 1 { fmt.Fprintln(os.Stderr, "Please provide a username") os.Exit(1) } api := mustNewAuthUserAPI(c) username := args[0] return api, username }
func mustNewClientNoSync(c *cli.Context) client.Client { hc, err := newClient(c) if err != nil { fmt.Fprintln(os.Stderr, err.Error()) os.Exit(1) } if c.GlobalBool("debug") { fmt.Fprintf(os.Stderr, "Cluster-Endpoints: %s\n", strings.Join(hc.Endpoints(), ", ")) client.EnablecURLDebug() } return hc }
func actionMemberAdd(c *cli.Context) { args := c.Args() if len(args) != 2 { fmt.Fprintln(os.Stderr, "Provide a name and a single member peerURL") os.Exit(1) } mAPI := mustNewMembersAPI(c) url := args[1] ctx, cancel := contextWithTotalTimeout(c) defer cancel() m, err := mAPI.Add(ctx, url) if err != nil { fmt.Fprintln(os.Stderr, err.Error()) os.Exit(1) } newID := m.ID newName := args[0] fmt.Printf("Added member named %s with ID %s to cluster\n", newName, newID) members, err := mAPI.List(ctx) if err != nil { fmt.Fprintln(os.Stderr, err.Error()) os.Exit(1) } conf := []string{} for _, memb := range members { for _, u := range memb.PeerURLs { n := memb.Name if memb.ID == newID { n = newName } conf = append(conf, fmt.Sprintf("%s=%s", n, u)) } } fmt.Print("\n") fmt.Printf("ETCD_NAME=%q\n", newName) fmt.Printf("ETCD_INITIAL_CLUSTER=%q\n", strings.Join(conf, ",")) fmt.Printf("ETCD_INITIAL_CLUSTER_STATE=\"existing\"\n") }
// handleBackup handles a request that intends to do a backup. func handleBackup(c *cli.Context) { srcSnap := path.Join(c.String("data-dir"), "member", "snap") destSnap := path.Join(c.String("backup-dir"), "member", "snap") srcWAL := path.Join(c.String("data-dir"), "member", "wal") destWAL := path.Join(c.String("backup-dir"), "member", "wal") if err := os.MkdirAll(destSnap, 0700); err != nil { log.Fatalf("failed creating backup snapshot dir %v: %v", destSnap, err) } ss := snap.New(srcSnap) snapshot, err := ss.Load() if err != nil && err != snap.ErrNoSnapshot { log.Fatal(err) } var walsnap walpb.Snapshot if snapshot != nil { walsnap.Index, walsnap.Term = snapshot.Metadata.Index, snapshot.Metadata.Term newss := snap.New(destSnap) if err := newss.SaveSnap(*snapshot); err != nil { log.Fatal(err) } } w, err := wal.OpenForRead(srcWAL, walsnap) if err != nil { log.Fatal(err) } defer w.Close() wmetadata, state, ents, err := w.ReadAll() switch err { case nil: case wal.ErrSnapshotNotFound: fmt.Printf("Failed to find the match snapshot record %+v in wal %v.", walsnap, srcWAL) fmt.Printf("etcdctl will add it back. Start auto fixing...") default: log.Fatal(err) } var metadata etcdserverpb.Metadata pbutil.MustUnmarshal(&metadata, wmetadata) idgen := idutil.NewGenerator(0, time.Now()) metadata.NodeID = idgen.Next() metadata.ClusterID = idgen.Next() neww, err := wal.Create(destWAL, pbutil.MustMarshal(&metadata)) if err != nil { log.Fatal(err) } defer neww.Close() if err := neww.Save(state, ents); err != nil { log.Fatal(err) } if err := neww.SaveSnapshot(walsnap); err != nil { log.Fatal(err) } }
func actionRoleList(c *cli.Context) { if len(c.Args()) != 0 { fmt.Fprintln(os.Stderr, "No arguments accepted") os.Exit(1) } r := mustNewAuthRoleAPI(c) ctx, cancel := contextWithTotalTimeout(c) roles, err := r.ListRoles(ctx) cancel() if err != nil { fmt.Fprintln(os.Stderr, err.Error()) os.Exit(1) } for _, role := range roles { fmt.Printf("%s\n", role) } }
func actionUserList(c *cli.Context) { if len(c.Args()) != 0 { fmt.Fprintln(os.Stderr, "No arguments accepted") os.Exit(1) } u := mustNewAuthUserAPI(c) ctx, cancel := contextWithTotalTimeout(c) users, err := u.ListUsers(ctx) cancel() if err != nil { fmt.Fprintln(os.Stderr, err.Error()) os.Exit(1) } for _, user := range users { fmt.Printf("%s\n", user) } }
func actionMemberRemove(c *cli.Context) { args := c.Args() if len(args) != 1 { fmt.Fprintln(os.Stderr, "Provide a single member ID") os.Exit(1) } removalID := args[0] mAPI := mustNewMembersAPI(c) ctx, cancel := contextWithTotalTimeout(c) defer cancel() // Get the list of members. members, err := mAPI.List(ctx) if err != nil { fmt.Fprintln(os.Stderr, "Error while verifying ID against known members:", err.Error()) os.Exit(1) } // Sanity check the input. foundID := false for _, m := range members { if m.ID == removalID { foundID = true } if m.Name == removalID { // Note that, so long as it's not ambiguous, we *could* do the right thing by name here. fmt.Fprintf(os.Stderr, "Found a member named %s; if this is correct, please use its ID, eg:\n\tetcdctl member remove %s\n", m.Name, m.ID) fmt.Fprintf(os.Stderr, "For more details, read the documentation at https://github.com/algoadv/etcd/blob/master/Documentation/runtime-configuration.md#remove-a-member\n\n") } } if !foundID { fmt.Fprintf(os.Stderr, "Couldn't find a member in the cluster with an ID of %s.\n", removalID) os.Exit(1) } // Actually attempt to remove the member. err = mAPI.Remove(ctx, removalID) if err != nil { fmt.Fprintf(os.Stderr, "Received an error trying to remove member %s: %s", removalID, err.Error()) os.Exit(1) } fmt.Printf("Removed member %s from cluster\n", removalID) }
func actionMemberUpdate(c *cli.Context) { args := c.Args() if len(args) != 2 { fmt.Fprintln(os.Stderr, "Provide an ID and a list of comma separated peerURL (0xabcd http://example.com,http://example1.com)") os.Exit(1) } mAPI := mustNewMembersAPI(c) mid := args[0] urls := args[1] ctx, cancel := contextWithTotalTimeout(c) err := mAPI.Update(ctx, mid, strings.Split(urls, ",")) cancel() if err != nil { fmt.Fprintln(os.Stderr, err.Error()) os.Exit(1) } fmt.Printf("Updated member with ID %s in cluster\n", mid) }
// updateCommandFunc executes the "update" command. func updateCommandFunc(c *cli.Context, ki client.KeysAPI) { if len(c.Args()) == 0 { handleError(ExitBadArgs, errors.New("key required")) } key := c.Args()[0] value, err := argOrStdin(c.Args(), os.Stdin, 1) if err != nil { handleError(ExitBadArgs, errors.New("value required")) } ttl := c.Int("ttl") ctx, cancel := contextWithTotalTimeout(c) resp, err := ki.Set(ctx, key, value, &client.SetOptions{TTL: time.Duration(ttl) * time.Second, PrevExist: client.PrevExist}) cancel() if err != nil { handleError(ExitServerError, err) } printResponseKey(resp, c.GlobalString("output")) }
func userGrantRevoke(c *cli.Context, grant bool) { roles := c.StringSlice("roles") if len(roles) == 0 { fmt.Fprintln(os.Stderr, "No roles specified; please use `-roles`") os.Exit(1) } ctx, cancel := contextWithTotalTimeout(c) defer cancel() api, user := mustUserAPIAndName(c) currentUser, err := api.GetUser(ctx, user) if currentUser == nil { fmt.Fprintln(os.Stderr, err.Error()) os.Exit(1) } var newUser *client.User if grant { newUser, err = api.GrantUser(ctx, user, roles) } else { newUser, err = api.RevokeUser(ctx, user, roles) } sort.Strings(newUser.Roles) sort.Strings(currentUser.Roles) if reflect.DeepEqual(newUser.Roles, currentUser.Roles) { if grant { fmt.Printf("User unchanged; roles already granted") } else { fmt.Printf("User unchanged; roles already revoked") } } if err != nil { fmt.Fprintln(os.Stderr, err.Error()) os.Exit(1) } fmt.Printf("User %s updated\n", user) }
func actionMemberList(c *cli.Context) { if len(c.Args()) != 0 { fmt.Fprintln(os.Stderr, "No arguments accepted") os.Exit(1) } mAPI := mustNewMembersAPI(c) ctx, cancel := contextWithTotalTimeout(c) defer cancel() members, err := mAPI.List(ctx) if err != nil { fmt.Fprintln(os.Stderr, err.Error()) os.Exit(1) } for _, m := range members { if len(m.Name) == 0 { fmt.Printf("%s[unstarted]: peerURLs=%s\n", m.ID, strings.Join(m.PeerURLs, ",")) } else { fmt.Printf("%s: name=%s peerURLs=%s clientURLs=%s\n", m.ID, m.Name, strings.Join(m.PeerURLs, ","), strings.Join(m.ClientURLs, ",")) } } }
func getDomainDiscoveryFlagValue(c *cli.Context) ([]string, error) { domainstr := c.GlobalString("discovery-srv") // Use an environment variable if nothing was supplied on the // command line if domainstr == "" { domainstr = os.Getenv("ETCDCTL_DISCOVERY_SRV") } // If we still don't have domain discovery, return nothing if domainstr == "" { return []string{}, nil } discoverer := client.NewSRVDiscover() eps, err := discoverer.Discover(domainstr) if err != nil { return nil, err } return eps, err }
func getPeersFlagValue(c *cli.Context) []string { peerstr := c.GlobalString("endpoint") if peerstr == "" { peerstr = os.Getenv("ETCDCTL_ENDPOINT") } if peerstr == "" { peerstr = c.GlobalString("peers") } if peerstr == "" { peerstr = os.Getenv("ETCDCTL_PEERS") } // If we still don't have peers, use a default if peerstr == "" { peerstr = "http://127.0.0.1:4001,http://127.0.0.1:2379" } return strings.Split(peerstr, ",") }
// rangeCommandFunc executes the "range" command. func rangeCommandFunc(c *cli.Context) { if len(c.Args()) == 0 { panic("bad arg") } var rangeEnd []byte key := []byte(c.Args()[0]) if len(c.Args()) > 1 { rangeEnd = []byte(c.Args()[1]) } conn, err := grpc.Dial(c.GlobalString("endpoint")) if err != nil { panic(err) } etcd := pb.NewEtcdClient(conn) req := &pb.RangeRequest{Key: key, RangeEnd: rangeEnd} resp, err := etcd.Range(context.Background(), req) for _, kv := range resp.Kvs { fmt.Printf("%s %s\n", string(kv.Key), string(kv.Value)) } }
func mustNewClient(c *cli.Context) client.Client { hc, err := newClient(c) if err != nil { fmt.Fprintln(os.Stderr, err.Error()) os.Exit(1) } debug := c.GlobalBool("debug") if debug { client.EnablecURLDebug() } if !c.GlobalBool("no-sync") { if debug { fmt.Fprintf(os.Stderr, "start to sync cluster using endpoints(%s)\n", strings.Join(hc.Endpoints(), ",")) } ctx, cancel := context.WithTimeout(context.Background(), client.DefaultRequestTimeout) err := hc.Sync(ctx) cancel() if err != nil { if err == client.ErrNoEndpoints { fmt.Fprintf(os.Stderr, "etcd cluster has no published client endpoints.\n") fmt.Fprintf(os.Stderr, "Try '--no-sync' if you want to access non-published client endpoints(%s).\n", strings.Join(hc.Endpoints(), ",")) } handleError(ExitServerError, err) os.Exit(1) } if debug { fmt.Fprintf(os.Stderr, "got endpoints(%s) after sync\n", strings.Join(hc.Endpoints(), ",")) } } if debug { fmt.Fprintf(os.Stderr, "Cluster-Endpoints: %s\n", strings.Join(hc.Endpoints(), ", ")) } return hc }
func authEnableDisable(c *cli.Context, enable bool) { if len(c.Args()) != 0 { fmt.Fprintln(os.Stderr, "No arguments accepted") os.Exit(1) } s := mustNewAuthAPI(c) ctx, cancel := contextWithTotalTimeout(c) var err error if enable { err = s.Enable(ctx) } else { err = s.Disable(ctx) } cancel() if err != nil { fmt.Fprintln(os.Stderr, err.Error()) os.Exit(1) } if enable { fmt.Println("Authentication Enabled") } else { fmt.Println("Authentication Disabled") } }
// putCommandFunc executes the "put" command. func putCommandFunc(c *cli.Context) { if len(c.Args()) != 2 { panic("bad arg") } key := []byte(c.Args()[0]) value := []byte(c.Args()[1]) conn, err := grpc.Dial(c.GlobalString("endpoint")) if err != nil { panic(err) } etcd := pb.NewEtcdClient(conn) req := &pb.PutRequest{Key: key, Value: value} etcd.Put(context.Background(), req) fmt.Printf("%s %s\n", key, value) }
// getCommandFunc executes the "get" command. func getCommandFunc(c *cli.Context, ki client.KeysAPI) { if len(c.Args()) == 0 { handleError(ExitBadArgs, errors.New("key required")) } key := c.Args()[0] sorted := c.Bool("sort") ctx, cancel := contextWithTotalTimeout(c) resp, err := ki.Get(ctx, key, &client.GetOptions{Sort: sorted}) cancel() if err != nil { handleError(ExitServerError, err) } if resp.Node.Dir { fmt.Fprintln(os.Stderr, fmt.Sprintf("%s: is a directory", resp.Node.Key)) os.Exit(1) } printResponseKey(resp, c.GlobalString("output")) }
// mkdirCommandFunc executes the "mkdir" command. func mkdirCommandFunc(c *cli.Context, ki client.KeysAPI, prevExist client.PrevExistType) { if len(c.Args()) == 0 { handleError(ExitBadArgs, errors.New("key required")) } key := c.Args()[0] ttl := c.Int("ttl") ctx, cancel := contextWithTotalTimeout(c) _, err := ki.Set(ctx, key, "", &client.SetOptions{TTL: time.Duration(ttl) * time.Second, Dir: true, PrevExist: prevExist}) cancel() if err != nil { handleError(ExitServerError, err) } }
// rmdirCommandFunc executes the "rmdir" command. func rmdirCommandFunc(c *cli.Context, ki client.KeysAPI) { if len(c.Args()) == 0 { handleError(ExitBadArgs, errors.New("key required")) } key := c.Args()[0] ctx, cancel := contextWithTotalTimeout(c) resp, err := ki.Delete(ctx, key, &client.DeleteOptions{Dir: true}) cancel() if err != nil { handleError(ExitServerError, err) } if !resp.Node.Dir { printResponseKey(resp, c.GlobalString("output")) } }
// compactionCommandFunc executes the "compaction" command. func compactionCommandFunc(c *cli.Context) { if len(c.Args()) != 1 { panic("bad arg") } rev, err := strconv.ParseInt(c.Args()[0], 10, 64) if err != nil { panic("bad arg") } conn, err := grpc.Dial(c.GlobalString("endpoint")) if err != nil { panic(err) } etcd := pb.NewEtcdClient(conn) req := &pb.CompactionRequest{Revision: rev} etcd.Compact(context.Background(), req) }
func getTransport(c *cli.Context) (*http.Transport, error) { cafile := c.GlobalString("ca-file") certfile := c.GlobalString("cert-file") keyfile := c.GlobalString("key-file") // Use an environment variable if nothing was supplied on the // command line if cafile == "" { cafile = os.Getenv("ETCDCTL_CA_FILE") } if certfile == "" { certfile = os.Getenv("ETCDCTL_CERT_FILE") } if keyfile == "" { keyfile = os.Getenv("ETCDCTL_KEY_FILE") } tls := transport.TLSInfo{ CAFile: cafile, CertFile: certfile, KeyFile: keyfile, } return transport.NewTransport(tls, defaultDialTimeout) }
func roleGrantRevoke(c *cli.Context, grant bool) { path := c.String("path") if path == "" { fmt.Fprintln(os.Stderr, "No path specified; please use `-path`") os.Exit(1) } if pathutil.CanonicalURLPath(path) != path { fmt.Fprintf(os.Stderr, "Not canonical path; please use `-path=%s`\n", pathutil.CanonicalURLPath(path)) os.Exit(1) } read := c.Bool("read") write := c.Bool("write") rw := c.Bool("readwrite") permcount := 0 for _, v := range []bool{read, write, rw} { if v { permcount++ } } if permcount != 1 { fmt.Fprintln(os.Stderr, "Please specify exactly one of -read, -write or -readwrite") os.Exit(1) } var permType client.PermissionType switch { case read: permType = client.ReadPermission case write: permType = client.WritePermission case rw: permType = client.ReadWritePermission } api, role := mustRoleAPIAndName(c) ctx, cancel := contextWithTotalTimeout(c) defer cancel() currentRole, err := api.GetRole(ctx, role) if err != nil { fmt.Fprintln(os.Stderr, err.Error()) os.Exit(1) } var newRole *client.Role if grant { newRole, err = api.GrantRoleKV(ctx, role, []string{path}, permType) } else { newRole, err = api.RevokeRoleKV(ctx, role, []string{path}, permType) } if err != nil { fmt.Fprintln(os.Stderr, err.Error()) os.Exit(1) } if reflect.DeepEqual(newRole, currentRole) { if grant { fmt.Printf("Role unchanged; already granted") } else { fmt.Printf("Role unchanged; already revoked") } } fmt.Printf("Role %s updated\n", role) }
// execWatchCommandFunc executes the "exec-watch" command. func execWatchCommandFunc(c *cli.Context, ki client.KeysAPI) { args := c.Args() argslen := len(args) if argslen < 2 { handleError(ExitBadArgs, errors.New("key and command to exec required")) } var ( key string cmdArgs []string ) foundSep := false for i := range args { if args[i] == "--" && i != 0 { foundSep = true break } } if foundSep { key = args[0] cmdArgs = args[2:] } else { // If no flag is parsed, the order of key and cmdArgs will be switched and // args will not contain `--`. key = args[argslen-1] cmdArgs = args[:argslen-1] } index := 0 if c.Int("after-index") != 0 { index = c.Int("after-index") + 1 } recursive := c.Bool("recursive") sigch := make(chan os.Signal, 1) signal.Notify(sigch, os.Interrupt) go func() { <-sigch os.Exit(0) }() w := ki.Watcher(key, &client.WatcherOptions{AfterIndex: uint64(index), Recursive: recursive}) for { resp, err := w.Next(context.TODO()) if err != nil { handleError(ExitServerError, err) } if resp.Node.Dir { fmt.Fprintf(os.Stderr, "Ignored dir %s change", resp.Node.Key) continue } cmd := exec.Command(cmdArgs[0], cmdArgs[1:]...) cmd.Env = environResponse(resp, os.Environ()) stdout, err := cmd.StdoutPipe() if err != nil { fmt.Fprintf(os.Stderr, err.Error()) os.Exit(1) } stderr, err := cmd.StderrPipe() if err != nil { fmt.Fprintf(os.Stderr, err.Error()) os.Exit(1) } go func() { err := cmd.Start() if err != nil { fmt.Fprintf(os.Stderr, err.Error()) os.Exit(1) } go io.Copy(os.Stdout, stdout) go io.Copy(os.Stderr, stderr) cmd.Wait() }() } }