├── .dockerignore ├── test ├── stop_container.sh ├── check-register.json ├── start_container.sh └── test.sh ├── action ├── action.go ├── agent_leave.go ├── agent_reload.go ├── acl_replication.go ├── agent_self.go ├── status_peers.go ├── agent_checks.go ├── status_leader.go ├── agent_services.go ├── coordinate_datacenters.go ├── catalog_datacenters.go ├── acl_list.go ├── operator_keyring_list.go ├── coordinate_nodes.go ├── session_list.go ├── catalog_nodes.go ├── agent_members.go ├── operator_keyring_use.go ├── agent_force_leave.go ├── operator_raft_config.go ├── acl_destroy.go ├── catalog_services.go ├── check_deregister.go ├── event_list.go ├── acl_info.go ├── operator_raft_delete.go ├── acl_clone.go ├── operator_autopilot_get.go ├── session_destroy.go ├── agent_join.go ├── session_info.go ├── check_fail.go ├── check_pass.go ├── check_warn.go ├── session_node.go ├── agent_maintenance.go ├── service_deregister.go ├── session_renew.go ├── catalog_node.go ├── health_node.go ├── kv_watch.go ├── health_checks.go ├── health_state.go ├── snapshot_restore.go ├── operator_keyring_remove.go ├── operator_keyring_install.go ├── agent_monitor.go ├── check_update.go ├── kv_keys.go ├── catalog_service.go ├── snapshot_save.go ├── health_service.go ├── kv_read.go ├── catalog_deregister.go ├── service_maintenance.go ├── catalog_register.go ├── raw.go ├── string_slice.go ├── kv_delete.go ├── acl_update.go ├── operator_autopilot_set.go ├── event_fire.go ├── acl_create.go ├── kv_unlock.go ├── session_create.go ├── map_slice_test.go ├── check_register.go ├── map_slice.go ├── acl.go ├── kv_write.go ├── txn.go ├── kv_lock.go ├── kv_bulkload.go ├── service_register.go ├── output.go ├── config.go └── consul.go ├── .travis.yml ├── Dockerfile ├── .gitignore ├── main.go ├── commands ├── txn.go ├── root_test.go ├── service_test.go ├── status.go ├── event.go ├── snapshot.go ├── check_test.go ├── coordinate.go ├── root.go ├── health.go ├── service.go ├── session.go ├── check.go ├── acl.go ├── kv.go ├── catalog.go ├── agent.go └── operator.go ├── Makefile ├── go.mod ├── README.md ├── Vagrantfile ├── LICENSE └── go.sum /.dockerignore: -------------------------------------------------------------------------------- 1 | bin 2 | build -------------------------------------------------------------------------------- /test/stop_container.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | docker stop consul-cli-1; docker rm consul-cli-1 4 | -------------------------------------------------------------------------------- /test/check-register.json: -------------------------------------------------------------------------------- 1 | { 2 | "ID": "test-check", 3 | "Name": "Test Check", 4 | "TTL": "15m" 5 | } 6 | -------------------------------------------------------------------------------- /action/action.go: -------------------------------------------------------------------------------- 1 | package action 2 | 3 | import ( 4 | "flag" 5 | ) 6 | 7 | type Action interface { 8 | CommandFlags() *flag.FlagSet 9 | Run([]string) error 10 | } 11 | 12 | func GlobalCommandFlags() *flag.FlagSet { 13 | return gFlags 14 | } 15 | -------------------------------------------------------------------------------- /test/start_container.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # start docker container 4 | docker run -d --name=consul-cli-1 -p 8500:8500 \ 5 | -e 'CONSUL_LOCAL_CONFIG={ 6 | "acl_datacenter": "dc1", 7 | "acl_master_token": "master_acl", 8 | "datacenter": "dc1", 9 | "encrypt": "/zp7noXDx5xC0FAi+t3CIA==", 10 | "node_name": "consul-cli-1" 11 | }' \ 12 | consul 13 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | --- 2 | language: go 3 | 4 | go: 5 | - 1.11.x 6 | - 1.12.x 7 | - 1.13.x 8 | - tip 9 | 10 | go_import_path: github.com/mantl/consul-cli 11 | 12 | env: 13 | - CGO_ENABLED=0 GO111MODULE=on 14 | 15 | install: 16 | - curl https://releases.hashicorp.com/consul/1.7.1/consul_1.7.1_linux_amd64.zip -o consul.zip 17 | - unzip consul.zip 18 | 19 | script: env PATH="$TRAVIS_BUILD_DIR:$PATH" make test 20 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.13-alpine AS builder 2 | 3 | RUN apk --no-cache add git make 4 | 5 | WORKDIR /src/consul-cli 6 | COPY . . 7 | RUN CGO_ENABLED=0 make build 8 | 9 | FROM busybox 10 | LABEL maintainer "Chris Aubuchon " 11 | 12 | ENTRYPOINT ["consul-cli"] 13 | 14 | COPY --from=builder /etc/ssl/certs /etc/ssl/certs 15 | COPY --from=builder /src/consul-cli/bin/consul-cli /usr/local/bin/ 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | vendor 10 | 11 | # Architecture specific extensions/prefixes 12 | *.[568vq] 13 | [568vq].out 14 | 15 | *.cgo1.go 16 | *.cgo2.c 17 | _cgo_defun.c 18 | _cgo_gotypes.go 19 | _cgo_export.* 20 | 21 | _testmain.go 22 | 23 | *.exe 24 | *.test 25 | *.prof 26 | 27 | *.swp 28 | consul-cli 29 | bin/ 30 | build/ 31 | .vagrant/ 32 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "io/ioutil" 6 | "log" 7 | "os" 8 | 9 | "github.com/mantl/consul-cli/commands" 10 | ) 11 | 12 | const Name = "consul-cli" 13 | const Version = "0.5.1" 14 | 15 | func main() { 16 | log.SetOutput(ioutil.Discard) 17 | 18 | root := commands.NewConsulCliCommand(Name, Version) 19 | if err := root.Execute(); err != nil { 20 | fmt.Fprintln(os.Stderr, err) 21 | os.Exit(1) 22 | } 23 | 24 | os.Exit(0) 25 | } 26 | -------------------------------------------------------------------------------- /action/agent_leave.go: -------------------------------------------------------------------------------- 1 | package action 2 | 3 | import ( 4 | "flag" 5 | ) 6 | 7 | type agentLeave struct { 8 | *config 9 | } 10 | 11 | func AgentLeaveAction() Action { 12 | return &agentLeave{ 13 | config: &gConfig, 14 | } 15 | } 16 | 17 | func (a *agentLeave) CommandFlags() *flag.FlagSet { 18 | return a.newFlagSet(FLAG_NONE) 19 | } 20 | 21 | func (a *agentLeave) Run(args []string) error { 22 | client, err := a.newAgent() 23 | if err != nil { 24 | return err 25 | } 26 | 27 | return client.Leave() 28 | } 29 | -------------------------------------------------------------------------------- /action/agent_reload.go: -------------------------------------------------------------------------------- 1 | package action 2 | 3 | import ( 4 | "flag" 5 | ) 6 | 7 | type agentReload struct { 8 | *config 9 | } 10 | 11 | func AgentReloadAction() Action { 12 | return &agentReload{ 13 | config: &gConfig, 14 | } 15 | } 16 | 17 | func (a *agentReload) CommandFlags() *flag.FlagSet { 18 | return a.newFlagSet(FLAG_NONE) 19 | } 20 | 21 | func (a *agentReload) Run(args []string) error { 22 | client, err := a.newAgent() 23 | if err != nil { 24 | return err 25 | } 26 | 27 | return client.Reload() 28 | } 29 | -------------------------------------------------------------------------------- /action/acl_replication.go: -------------------------------------------------------------------------------- 1 | package action 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | ) 7 | 8 | type aclReplication struct { 9 | *config 10 | } 11 | 12 | func AclReplicationAction() Action { 13 | return &aclReplication{ 14 | config: &gConfig, 15 | } 16 | } 17 | 18 | func (a *aclReplication) CommandFlags() *flag.FlagSet { 19 | return a.newFlagSet(FLAG_OUTPUT, FLAG_DATACENTER) 20 | } 21 | 22 | func (a *aclReplication) Run(args []string) error { 23 | return fmt.Errorf("ACL replication status not available in Consul API") 24 | } 25 | -------------------------------------------------------------------------------- /commands/txn.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import ( 4 | "github.com/spf13/cobra" 5 | 6 | "github.com/mantl/consul-cli/action" 7 | ) 8 | 9 | func newTxnCommand() *cobra.Command { 10 | t := action.TxnAction() 11 | 12 | cmd := &cobra.Command{ 13 | Use: "txn", 14 | Short: "Consul /txn endpoint interface", 15 | Long: "Consul /txn endpoint interface", 16 | RunE: func(cmd *cobra.Command, args []string) error { 17 | return t.Run(args) 18 | }, 19 | } 20 | 21 | cmd.Flags().AddGoFlagSet(t.CommandFlags()) 22 | 23 | return cmd 24 | } 25 | -------------------------------------------------------------------------------- /action/agent_self.go: -------------------------------------------------------------------------------- 1 | package action 2 | 3 | import ( 4 | "flag" 5 | ) 6 | 7 | type agentSelf struct { 8 | *config 9 | } 10 | 11 | func AgentSelfAction() Action { 12 | return &agentSelf{ 13 | config: &gConfig, 14 | } 15 | } 16 | 17 | func (a *agentSelf) CommandFlags() *flag.FlagSet { 18 | return a.newFlagSet(FLAG_OUTPUT) 19 | } 20 | 21 | func (a *agentSelf) Run(args []string) error { 22 | client, err := a.newAgent() 23 | if err != nil { 24 | return err 25 | } 26 | 27 | config, err := client.Self() 28 | if err != nil { 29 | return err 30 | } 31 | 32 | return a.Output(config) 33 | } 34 | -------------------------------------------------------------------------------- /action/status_peers.go: -------------------------------------------------------------------------------- 1 | package action 2 | 3 | import ( 4 | "flag" 5 | ) 6 | 7 | type statusPeers struct { 8 | *config 9 | } 10 | 11 | func StatusPeersAction() Action { 12 | return &statusPeers{ 13 | config: &gConfig, 14 | } 15 | } 16 | 17 | func (s *statusPeers) CommandFlags() *flag.FlagSet { 18 | return s.newFlagSet(FLAG_OUTPUT) 19 | } 20 | 21 | func (s *statusPeers) Run(args []string) error { 22 | client, err := s.newStatus() 23 | if err != nil { 24 | return err 25 | } 26 | 27 | l, err := client.Peers() 28 | if err != nil { 29 | return err 30 | } 31 | 32 | return s.Output(l) 33 | } 34 | -------------------------------------------------------------------------------- /action/agent_checks.go: -------------------------------------------------------------------------------- 1 | package action 2 | 3 | import ( 4 | "flag" 5 | ) 6 | 7 | type agentChecks struct { 8 | *config 9 | } 10 | 11 | func AgentChecksAction() Action { 12 | return &agentChecks{ 13 | config: &gConfig, 14 | } 15 | } 16 | 17 | func (a *agentChecks) CommandFlags() *flag.FlagSet { 18 | return a.newFlagSet(FLAG_NONE) 19 | } 20 | 21 | func (a *agentChecks) Run(args []string) error { 22 | client, err := a.newAgent() 23 | if err != nil { 24 | return err 25 | } 26 | 27 | config, err := client.Checks() 28 | if err != nil { 29 | return err 30 | } 31 | 32 | return a.Output(config) 33 | } 34 | -------------------------------------------------------------------------------- /action/status_leader.go: -------------------------------------------------------------------------------- 1 | package action 2 | 3 | import ( 4 | "flag" 5 | ) 6 | 7 | type statusLeader struct { 8 | *config 9 | } 10 | 11 | func StatusLeaderAction() Action { 12 | return &statusLeader{ 13 | config: &gConfig, 14 | } 15 | } 16 | 17 | func (s *statusLeader) CommandFlags() *flag.FlagSet { 18 | return s.newFlagSet(FLAG_OUTPUT) 19 | } 20 | 21 | func (s *statusLeader) Run(args []string) error { 22 | client, err := s.newStatus() 23 | if err != nil { 24 | return err 25 | } 26 | 27 | l, err := client.Leader() 28 | if err != nil { 29 | return err 30 | } 31 | 32 | return s.Output(l) 33 | } 34 | -------------------------------------------------------------------------------- /action/agent_services.go: -------------------------------------------------------------------------------- 1 | package action 2 | 3 | import ( 4 | "flag" 5 | ) 6 | 7 | type agentServices struct { 8 | *config 9 | } 10 | 11 | func AgentServicesAction() Action { 12 | return &agentServices{ 13 | config: &gConfig, 14 | } 15 | } 16 | 17 | func (a *agentServices) CommandFlags() *flag.FlagSet { 18 | return a.newFlagSet(FLAG_NONE) 19 | } 20 | 21 | func (a *agentServices) Run(args []string) error { 22 | client, err := a.newAgent() 23 | if err != nil { 24 | return err 25 | } 26 | 27 | config, err := client.Services() 28 | if err != nil { 29 | return err 30 | } 31 | 32 | return a.Output(config) 33 | } 34 | -------------------------------------------------------------------------------- /action/coordinate_datacenters.go: -------------------------------------------------------------------------------- 1 | package action 2 | 3 | import ( 4 | "flag" 5 | ) 6 | 7 | type coordDatacenters struct { 8 | *config 9 | } 10 | 11 | func CoordDatacentersAction() Action { 12 | return &coordDatacenters{ 13 | config: &gConfig, 14 | } 15 | } 16 | 17 | func (c *coordDatacenters) CommandFlags() *flag.FlagSet { 18 | return c.newFlagSet(FLAG_OUTPUT) 19 | } 20 | 21 | func (c *coordDatacenters) Run(args []string) error { 22 | client, err := c.newCoordinate() 23 | if err != nil { 24 | return err 25 | } 26 | 27 | data, err := client.Datacenters() 28 | if err != nil { 29 | return err 30 | } 31 | 32 | return c.Output(data) 33 | } 34 | -------------------------------------------------------------------------------- /action/catalog_datacenters.go: -------------------------------------------------------------------------------- 1 | package action 2 | 3 | import ( 4 | "flag" 5 | ) 6 | 7 | type catalogDatacenters struct { 8 | *config 9 | } 10 | 11 | func CatalogDatacentersAction() Action { 12 | return &catalogDatacenters{ 13 | config: &gConfig, 14 | } 15 | } 16 | 17 | func (c *catalogDatacenters) CommandFlags() *flag.FlagSet { 18 | return c.newFlagSet(FLAG_OUTPUT) 19 | } 20 | 21 | func (c *catalogDatacenters) Run(args []string) error { 22 | client, err := c.newCatalog() 23 | if err != nil { 24 | return err 25 | } 26 | 27 | config, err := client.Datacenters() 28 | if err != nil { 29 | return err 30 | } 31 | 32 | return c.Output(config) 33 | } 34 | -------------------------------------------------------------------------------- /action/acl_list.go: -------------------------------------------------------------------------------- 1 | package action 2 | 3 | import ( 4 | "flag" 5 | ) 6 | 7 | // List functions 8 | 9 | type aclList struct { 10 | *config 11 | } 12 | 13 | func AclListAction() Action { 14 | return &aclList{ 15 | config: &gConfig, 16 | } 17 | } 18 | 19 | func (a *aclList) CommandFlags() *flag.FlagSet { 20 | return a.newFlagSet(FLAG_OUTPUT, FLAG_CONSISTENCY, FLAG_BLOCKING) 21 | } 22 | 23 | func (a *aclList) Run(args []string) error { 24 | client, err := a.newACL() 25 | if err != nil { 26 | return err 27 | } 28 | 29 | queryOpts := a.queryOptions() 30 | acls, _, err := client.List(queryOpts) 31 | if err != nil { 32 | return err 33 | } 34 | 35 | return a.Output(acls) 36 | } 37 | -------------------------------------------------------------------------------- /action/operator_keyring_list.go: -------------------------------------------------------------------------------- 1 | package action 2 | 3 | import ( 4 | "flag" 5 | ) 6 | 7 | type operatorKeyringList struct { 8 | *config 9 | } 10 | 11 | func OperatorKeyringListAction() Action { 12 | return &operatorKeyringList{ 13 | config: &gConfig, 14 | } 15 | } 16 | 17 | func (o *operatorKeyringList) CommandFlags() *flag.FlagSet { 18 | return o.newFlagSet(FLAG_OUTPUT) 19 | } 20 | 21 | func (o *operatorKeyringList) Run(args []string) error { 22 | client, err := o.newOperator() 23 | if err != nil { 24 | return err 25 | } 26 | 27 | queryOpts := o.queryOptions() 28 | r, err := client.KeyringList(queryOpts) 29 | if err != nil { 30 | return err 31 | } 32 | 33 | return o.Output(r) 34 | } 35 | -------------------------------------------------------------------------------- /action/coordinate_nodes.go: -------------------------------------------------------------------------------- 1 | package action 2 | 3 | import ( 4 | "flag" 5 | ) 6 | 7 | type coordNodes struct { 8 | *config 9 | } 10 | 11 | func CoordNodesAction() Action { 12 | return &coordNodes{ 13 | config: &gConfig, 14 | } 15 | } 16 | 17 | func (c *coordNodes) CommandFlags() *flag.FlagSet { 18 | return c.newFlagSet(FLAG_DATACENTER, FLAG_OUTPUT, FLAG_CONSISTENCY, FLAG_BLOCKING) 19 | } 20 | 21 | func (c *coordNodes) Run(args []string) error { 22 | client, err := c.newCoordinate() 23 | if err != nil { 24 | return err 25 | } 26 | 27 | queryOpts := c.queryOptions() 28 | data, _, err := client.Nodes(queryOpts) 29 | if err != nil { 30 | return err 31 | } 32 | 33 | return c.Output(data) 34 | } 35 | -------------------------------------------------------------------------------- /action/session_list.go: -------------------------------------------------------------------------------- 1 | package action 2 | 3 | import ( 4 | "flag" 5 | ) 6 | 7 | type sessionList struct { 8 | *config 9 | } 10 | 11 | func SessionListAction() Action { 12 | return &sessionList{ 13 | config: &gConfig, 14 | } 15 | } 16 | 17 | func (s *sessionList) CommandFlags() *flag.FlagSet { 18 | return s.newFlagSet(FLAG_DATACENTER, FLAG_OUTPUT, FLAG_CONSISTENCY, FLAG_BLOCKING) 19 | } 20 | 21 | func (s *sessionList) Run(args []string) error { 22 | client, err := s.newSession() 23 | if err != nil { 24 | return err 25 | } 26 | 27 | queryOpts := s.queryOptions() 28 | 29 | sessions, _, err := client.List(queryOpts) 30 | if err != nil { 31 | return err 32 | } 33 | 34 | return s.Output(sessions) 35 | } 36 | -------------------------------------------------------------------------------- /action/catalog_nodes.go: -------------------------------------------------------------------------------- 1 | package action 2 | 3 | import ( 4 | "flag" 5 | ) 6 | 7 | type catalogNodes struct { 8 | *config 9 | } 10 | 11 | func CatalogNodesAction() Action { 12 | return &catalogNodes{ 13 | config: &gConfig, 14 | } 15 | } 16 | 17 | func (c *catalogNodes) CommandFlags() *flag.FlagSet { 18 | return c.newFlagSet(FLAG_DATACENTER, FLAG_CONSISTENCY, FLAG_OUTPUT, FLAG_NODEMETA, FLAG_NEAR) 19 | } 20 | 21 | func (c *catalogNodes) Run(args []string) error { 22 | client, err := c.newCatalog() 23 | if err != nil { 24 | return err 25 | } 26 | 27 | queryOpts := c.queryOptions() 28 | config, _, err := client.Nodes(queryOpts) 29 | if err != nil { 30 | return err 31 | } 32 | 33 | return c.Output(config) 34 | } 35 | -------------------------------------------------------------------------------- /action/agent_members.go: -------------------------------------------------------------------------------- 1 | package action 2 | 3 | import ( 4 | "flag" 5 | ) 6 | 7 | type agentMembers struct { 8 | wan bool 9 | *config 10 | } 11 | 12 | func AgentMembersAction() Action { 13 | return &agentMembers{ 14 | config: &gConfig, 15 | } 16 | } 17 | 18 | func (a *agentMembers) CommandFlags() *flag.FlagSet { 19 | f := a.newFlagSet(FLAG_OUTPUT) 20 | 21 | f.BoolVar(&a.wan, "wan", false, "Get list of WAN members instead of LAN") 22 | 23 | return f 24 | } 25 | 26 | func (a *agentMembers) Run(args []string) error { 27 | client, err := a.newAgent() 28 | if err != nil { 29 | return err 30 | } 31 | 32 | ms, err := client.Members(a.wan) 33 | if err != nil { 34 | return err 35 | } 36 | 37 | return a.Output(ms) 38 | } 39 | -------------------------------------------------------------------------------- /action/operator_keyring_use.go: -------------------------------------------------------------------------------- 1 | package action 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | ) 7 | 8 | type operatorKeyringUse struct { 9 | *config 10 | } 11 | 12 | func OperatorKeyringUseAction() Action { 13 | return &operatorKeyringUse{ 14 | config: &gConfig, 15 | } 16 | } 17 | 18 | func (o *operatorKeyringUse) CommandFlags() *flag.FlagSet { 19 | return o.newFlagSet(FLAG_NONE) 20 | } 21 | 22 | func (o *operatorKeyringUse) Run(args []string) error { 23 | if len(args) != 1 { 24 | return fmt.Errorf("Encryption key must be specified") 25 | } 26 | 27 | client, err := o.newOperator() 28 | if err != nil { 29 | return err 30 | } 31 | 32 | writeOpts := o.writeOptions() 33 | 34 | return client.KeyringUse(args[0], writeOpts) 35 | } 36 | -------------------------------------------------------------------------------- /action/agent_force_leave.go: -------------------------------------------------------------------------------- 1 | package action 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | ) 7 | 8 | type agentForceLeave struct { 9 | *config 10 | } 11 | 12 | func AgentForceLeaveAction() Action { 13 | return &agentForceLeave{ 14 | config: &gConfig, 15 | } 16 | } 17 | 18 | func (a *agentForceLeave) CommandFlags() *flag.FlagSet { 19 | return a.newFlagSet(FLAG_NONE) 20 | } 21 | 22 | func (a *agentForceLeave) Run(args []string) error { 23 | switch { 24 | case len(args) == 0: 25 | return fmt.Errorf("Name not provided") 26 | case len(args) > 1: 27 | return fmt.Errorf("Only one node allowed") 28 | } 29 | 30 | client, err := a.newAgent() 31 | if err != nil { 32 | return err 33 | } 34 | 35 | return client.ForceLeave(args[0]) 36 | } 37 | -------------------------------------------------------------------------------- /action/operator_raft_config.go: -------------------------------------------------------------------------------- 1 | package action 2 | 3 | import ( 4 | "flag" 5 | ) 6 | 7 | type operatorRaftConfig struct { 8 | *config 9 | } 10 | 11 | func OperatorRaftConfigAction() Action { 12 | return &operatorRaftConfig{ 13 | config: &gConfig, 14 | } 15 | } 16 | 17 | func (o *operatorRaftConfig) CommandFlags() *flag.FlagSet { 18 | return o.newFlagSet(FLAG_DATACENTER, FLAG_OUTPUT, FLAG_STALE) 19 | } 20 | 21 | func (o *operatorRaftConfig) Run(args []string) error { 22 | client, err := o.newOperator() 23 | if err != nil { 24 | return err 25 | } 26 | 27 | queryOpts := o.queryOptions() 28 | 29 | rc, err := client.RaftGetConfiguration(queryOpts) 30 | if err != nil { 31 | return err 32 | } 33 | 34 | return o.Output(rc) 35 | } 36 | -------------------------------------------------------------------------------- /action/acl_destroy.go: -------------------------------------------------------------------------------- 1 | package action 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | ) 7 | 8 | type aclDestroy struct { 9 | *config 10 | } 11 | 12 | func AclDestroyAction() Action { 13 | return &aclDestroy{ 14 | config: &gConfig, 15 | } 16 | } 17 | 18 | func (a *aclDestroy) CommandFlags() *flag.FlagSet { 19 | return a.newFlagSet(FLAG_NONE) 20 | } 21 | 22 | func (a *aclDestroy) Run(args []string) error { 23 | if len(args) != 1 { 24 | return fmt.Errorf("A single ACL id must be specified") 25 | } 26 | id := args[0] 27 | 28 | client, err := a.newACL() 29 | if err != nil { 30 | return err 31 | } 32 | 33 | writeOpts := a.writeOptions() 34 | _, err = client.Destroy(id, writeOpts) 35 | if err != nil { 36 | return err 37 | } 38 | 39 | return nil 40 | } 41 | -------------------------------------------------------------------------------- /action/catalog_services.go: -------------------------------------------------------------------------------- 1 | package action 2 | 3 | import ( 4 | "flag" 5 | ) 6 | 7 | type catalogServices struct { 8 | nodeMeta []string 9 | 10 | *config 11 | } 12 | 13 | func CatalogServicesAction() Action { 14 | return &catalogServices{ 15 | config: &gConfig, 16 | } 17 | } 18 | 19 | func (c *catalogServices) CommandFlags() *flag.FlagSet { 20 | return c.newFlagSet(FLAG_DATACENTER, FLAG_OUTPUT, FLAG_CONSISTENCY, FLAG_NODEMETA) 21 | } 22 | 23 | func (c *catalogServices) Run(args []string) error { 24 | client, err := c.newCatalog() 25 | if err != nil { 26 | return err 27 | } 28 | 29 | queryOpts := c.queryOptions() 30 | config, _, err := client.Services(queryOpts) 31 | if err != nil { 32 | return err 33 | } 34 | 35 | return c.Output(config) 36 | } 37 | -------------------------------------------------------------------------------- /action/check_deregister.go: -------------------------------------------------------------------------------- 1 | package action 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | ) 7 | 8 | type checkDeregister struct { 9 | *config 10 | } 11 | 12 | func CheckDeregisterAction() Action { 13 | return &checkDeregister{ 14 | config: &gConfig, 15 | } 16 | } 17 | 18 | func (c *checkDeregister) CommandFlags() *flag.FlagSet { 19 | return c.newFlagSet(FLAG_NONE) 20 | } 21 | 22 | func (c *checkDeregister) Run(args []string) error { 23 | if len(args) != 1 { 24 | return fmt.Errorf("A single check id must be specified") 25 | } 26 | checkId := args[0] 27 | 28 | client, err := c.newAgent() 29 | if err != nil { 30 | return err 31 | } 32 | 33 | err = client.CheckDeregister(checkId) 34 | if err != nil { 35 | return err 36 | } 37 | 38 | return nil 39 | } 40 | -------------------------------------------------------------------------------- /action/event_list.go: -------------------------------------------------------------------------------- 1 | package action 2 | 3 | import ( 4 | "flag" 5 | ) 6 | 7 | type eventList struct { 8 | name string 9 | 10 | *config 11 | } 12 | 13 | func EventListAction() Action { 14 | return &eventList{ 15 | config: &gConfig, 16 | } 17 | } 18 | 19 | func (e *eventList) CommandFlags() *flag.FlagSet { 20 | f := e.newFlagSet(FLAG_OUTPUT, FLAG_BLOCKING) 21 | 22 | f.StringVar(&e.name, "name", "", "Event name to filter on") 23 | 24 | return f 25 | } 26 | 27 | func (e *eventList) Run(args []string) error { 28 | client, err := e.newEvent() 29 | if err != nil { 30 | return err 31 | } 32 | 33 | queryOpts := e.queryOptions() 34 | 35 | data, _, err := client.List(e.name, queryOpts) 36 | if err != nil { 37 | return err 38 | } 39 | 40 | return e.Output(data) 41 | } 42 | -------------------------------------------------------------------------------- /action/acl_info.go: -------------------------------------------------------------------------------- 1 | package action 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | ) 7 | 8 | type aclInfo struct { 9 | *config 10 | } 11 | 12 | func AclInfoAction() Action { 13 | return &aclInfo{ 14 | config: &gConfig, 15 | } 16 | } 17 | 18 | func (a *aclInfo) CommandFlags() *flag.FlagSet { 19 | return a.newFlagSet(FLAG_OUTPUT, FLAG_CONSISTENCY, FLAG_BLOCKING) 20 | } 21 | 22 | func (a aclInfo) Run(args []string) error { 23 | if len(args) != 1 { 24 | return fmt.Errorf("An ACL id must be specified") 25 | } 26 | id := args[0] 27 | 28 | client, err := a.newACL() 29 | if err != nil { 30 | return err 31 | } 32 | 33 | queryOpts := a.queryOptions() 34 | acl, _, err := client.Info(id, queryOpts) 35 | if err != nil { 36 | return err 37 | } 38 | 39 | return a.Output(acl) 40 | } 41 | -------------------------------------------------------------------------------- /action/operator_raft_delete.go: -------------------------------------------------------------------------------- 1 | package action 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | ) 7 | 8 | type operatorRaftDelete struct { 9 | *config 10 | } 11 | 12 | func OperatorRaftDeleteAction() Action { 13 | return &operatorRaftDelete{ 14 | config: &gConfig, 15 | } 16 | } 17 | 18 | func (o *operatorRaftDelete) CommandFlags() *flag.FlagSet { 19 | return o.newFlagSet(FLAG_DATACENTER) 20 | } 21 | 22 | func (o *operatorRaftDelete) Run(args []string) error { 23 | if len(args) != 1 { 24 | return fmt.Errorf("A single address argument must be specified") 25 | } 26 | address := args[0] 27 | 28 | client, err := o.newOperator() 29 | if err != nil { 30 | return err 31 | } 32 | 33 | writeOpts := o.writeOptions() 34 | 35 | return client.RaftRemovePeerByAddress(address, writeOpts) 36 | } 37 | -------------------------------------------------------------------------------- /action/acl_clone.go: -------------------------------------------------------------------------------- 1 | package action 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | ) 7 | 8 | type aclClone struct { 9 | *config 10 | } 11 | 12 | func AclCloneAction() Action { 13 | return &aclClone{ 14 | config: &gConfig, 15 | } 16 | } 17 | 18 | func (a *aclClone) CommandFlags() *flag.FlagSet { 19 | return a.newFlagSet(FLAG_NONE) 20 | } 21 | 22 | func (a *aclClone) Run(args []string) error { 23 | if len(args) != 1 { 24 | return fmt.Errorf("A single ACL id must be specified") 25 | } 26 | 27 | client, err := a.consul.newACL() 28 | if err != nil { 29 | return err 30 | } 31 | 32 | writeOpts := a.consul.writeOptions() 33 | 34 | newid, _, err := client.Clone(args[0], writeOpts) 35 | if err != nil { 36 | return err 37 | } 38 | 39 | fmt.Println(newid) 40 | 41 | return nil 42 | } 43 | -------------------------------------------------------------------------------- /action/operator_autopilot_get.go: -------------------------------------------------------------------------------- 1 | // +build consul8 2 | // 3 | 4 | package action 5 | 6 | import ( 7 | "flag" 8 | ) 9 | 10 | type operatorAutopilotGet struct { 11 | *config 12 | } 13 | 14 | func OperatorAutopilotGetAction() Action { 15 | return &operatorAutopilotGet{ 16 | config: &gConfig, 17 | } 18 | } 19 | 20 | func (o *operatorAutopilotGet) CommandFlags() *flag.FlagSet { 21 | return o.newFlagSet(FLAG_DATACENTER, FLAG_OUTPUT, FLAG_STALE) 22 | } 23 | 24 | func (o *operatorAutopilotGet) Run(args []string) error { 25 | client, err := o.newOperator() 26 | if err != nil { 27 | return err 28 | } 29 | 30 | queryOpts := o.queryOptions() 31 | 32 | rc, err := client.AutopilotGetConfiguration(queryOpts) 33 | if err != nil { 34 | return err 35 | } 36 | 37 | return o.Output(rc) 38 | } 39 | -------------------------------------------------------------------------------- /action/session_destroy.go: -------------------------------------------------------------------------------- 1 | package action 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | ) 7 | 8 | type sessionDestroy struct { 9 | *config 10 | } 11 | 12 | func SessionDestroyAction() Action { 13 | return &sessionDestroy{ 14 | config: &gConfig, 15 | } 16 | } 17 | 18 | func (s *sessionDestroy) CommandFlags() *flag.FlagSet { 19 | return s.newFlagSet(FLAG_DATACENTER) 20 | } 21 | 22 | func (s *sessionDestroy) Run(args []string) error { 23 | if len(args) != 1 { 24 | return fmt.Errorf("A single session Id must be specified") 25 | } 26 | sessionid := args[0] 27 | 28 | client, err := s.newSession() 29 | if err != nil { 30 | return err 31 | } 32 | 33 | writeOpts := s.writeOptions() 34 | 35 | _, err = client.Destroy(sessionid, writeOpts) 36 | if err != nil { 37 | return err 38 | } 39 | 40 | return nil 41 | } 42 | -------------------------------------------------------------------------------- /action/agent_join.go: -------------------------------------------------------------------------------- 1 | package action 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | ) 7 | 8 | type agentJoin struct { 9 | wan bool 10 | *config 11 | } 12 | 13 | func AgentJoinAction() Action { 14 | return &agentJoin{ 15 | config: &gConfig, 16 | } 17 | } 18 | 19 | func (a *agentJoin) CommandFlags() *flag.FlagSet { 20 | f := a.newFlagSet(FLAG_NONE) 21 | 22 | f.BoolVar(&a.wan, "wan", false, "Get list of WAN join instead of LAN") 23 | 24 | return f 25 | } 26 | func (a *agentJoin) Run(args []string) error { 27 | switch { 28 | case len(args) == 0: 29 | return fmt.Errorf("Node name must be specified") 30 | case len(args) > 1: 31 | return fmt.Errorf("Only one name allowed") 32 | } 33 | 34 | client, err := a.newAgent() 35 | if err != nil { 36 | return err 37 | } 38 | 39 | return client.Join(args[0], a.wan) 40 | } 41 | -------------------------------------------------------------------------------- /action/session_info.go: -------------------------------------------------------------------------------- 1 | package action 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | ) 7 | 8 | type sessionInfo struct { 9 | *config 10 | } 11 | 12 | func SessionInfoAction() Action { 13 | return &sessionInfo{ 14 | config: &gConfig, 15 | } 16 | } 17 | 18 | func (s *sessionInfo) CommandFlags() *flag.FlagSet { 19 | return s.newFlagSet(FLAG_DATACENTER, FLAG_OUTPUT, FLAG_CONSISTENCY) 20 | } 21 | 22 | func (s *sessionInfo) Run(args []string) error { 23 | if len(args) != 1 { 24 | return fmt.Errorf("A single session Id must be specified") 25 | } 26 | sessionid := args[0] 27 | 28 | client, err := s.newSession() 29 | if err != nil { 30 | return err 31 | } 32 | 33 | queryOpts := s.queryOptions() 34 | 35 | session, _, err := client.Info(sessionid, queryOpts) 36 | if err != nil { 37 | return err 38 | } 39 | 40 | return s.Output(session) 41 | } 42 | -------------------------------------------------------------------------------- /action/check_fail.go: -------------------------------------------------------------------------------- 1 | package action 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | ) 7 | 8 | type checkFail struct { 9 | note string 10 | 11 | *config 12 | } 13 | 14 | func CheckFailAction() Action { 15 | return &checkFail{ 16 | config: &gConfig, 17 | } 18 | } 19 | 20 | func (c *checkFail) CommandFlags() *flag.FlagSet { 21 | f := c.newFlagSet(FLAG_NONE) 22 | 23 | f.StringVar(&c.note, "note", "", "Message to associate with check status") 24 | 25 | return f 26 | } 27 | 28 | func (c *checkFail) Run(args []string) error { 29 | if len(args) != 1 { 30 | return fmt.Errorf("A single check id must be specified") 31 | } 32 | checkId := args[0] 33 | 34 | client, err := c.newAgent() 35 | if err != nil { 36 | return err 37 | } 38 | 39 | err = client.FailTTL(checkId, c.note) 40 | if err != nil { 41 | return err 42 | } 43 | 44 | return nil 45 | } 46 | -------------------------------------------------------------------------------- /action/check_pass.go: -------------------------------------------------------------------------------- 1 | package action 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | ) 7 | 8 | type checkPass struct { 9 | note string 10 | 11 | *config 12 | } 13 | 14 | func CheckPassAction() Action { 15 | return &checkPass{ 16 | config: &gConfig, 17 | } 18 | } 19 | 20 | func (c *checkPass) CommandFlags() *flag.FlagSet { 21 | f := c.newFlagSet(FLAG_NONE) 22 | 23 | f.StringVar(&c.note, "note", "", "Message to associate with check status") 24 | 25 | return f 26 | } 27 | 28 | func (c *checkPass) Run(args []string) error { 29 | if len(args) != 1 { 30 | return fmt.Errorf("A single check id must be specified") 31 | } 32 | checkId := args[0] 33 | 34 | client, err := c.newAgent() 35 | if err != nil { 36 | return err 37 | } 38 | 39 | err = client.PassTTL(checkId, c.note) 40 | if err != nil { 41 | return err 42 | } 43 | 44 | return nil 45 | } 46 | -------------------------------------------------------------------------------- /action/check_warn.go: -------------------------------------------------------------------------------- 1 | package action 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | ) 7 | 8 | type checkWarn struct { 9 | note string 10 | 11 | *config 12 | } 13 | 14 | func CheckWarnAction() Action { 15 | return &checkWarn{ 16 | config: &gConfig, 17 | } 18 | } 19 | 20 | func (c *checkWarn) CommandFlags() *flag.FlagSet { 21 | f := c.newFlagSet(FLAG_NONE) 22 | 23 | f.StringVar(&c.note, "note", "", "Message to associate with check status") 24 | 25 | return f 26 | } 27 | 28 | func (c *checkWarn) Run(args []string) error { 29 | if len(args) != 1 { 30 | return fmt.Errorf("A single check id must be specified") 31 | } 32 | checkId := args[0] 33 | 34 | client, err := c.newAgent() 35 | if err != nil { 36 | return err 37 | } 38 | 39 | err = client.WarnTTL(checkId, c.note) 40 | if err != nil { 41 | return err 42 | } 43 | 44 | return nil 45 | } 46 | -------------------------------------------------------------------------------- /action/session_node.go: -------------------------------------------------------------------------------- 1 | package action 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | ) 7 | 8 | type sessionNode struct { 9 | *config 10 | } 11 | 12 | func SessionNodeAction() Action { 13 | return &sessionNode{ 14 | config: &gConfig, 15 | } 16 | } 17 | 18 | func (s *sessionNode) CommandFlags() *flag.FlagSet { 19 | return s.newFlagSet(FLAG_DATACENTER, FLAG_OUTPUT, FLAG_CONSISTENCY, FLAG_BLOCKING) 20 | } 21 | 22 | func (s *sessionNode) Run(args []string) error { 23 | if len(args) != 1 { 24 | return fmt.Errorf("A single session Id must be specified") 25 | } 26 | node := args[0] 27 | 28 | client, err := s.newSession() 29 | if err != nil { 30 | return err 31 | } 32 | 33 | queryOpts := s.queryOptions() 34 | 35 | sessions, _, err := client.Node(node, queryOpts) 36 | if err != nil { 37 | return err 38 | } 39 | 40 | return s.Output(sessions) 41 | } 42 | -------------------------------------------------------------------------------- /action/agent_maintenance.go: -------------------------------------------------------------------------------- 1 | package action 2 | 3 | import ( 4 | "flag" 5 | ) 6 | 7 | type agentMaintenance struct { 8 | enabled bool 9 | reason string 10 | *config 11 | } 12 | 13 | func AgentMaintenanceAction() Action { 14 | return &agentMaintenance{ 15 | config: &gConfig, 16 | } 17 | } 18 | 19 | func (a *agentMaintenance) CommandFlags() *flag.FlagSet { 20 | f := a.newFlagSet(FLAG_NONE) 21 | 22 | f.BoolVar(&a.enabled, "enabled", true, "Boolean value for maintenance mode") 23 | f.StringVar(&a.reason, "reason", "", "Reason for entering maintenance mode") 24 | 25 | return f 26 | } 27 | 28 | func (a *agentMaintenance) Run(args []string) error { 29 | client, err := a.newAgent() 30 | if err != nil { 31 | return err 32 | } 33 | 34 | if a.enabled { 35 | return client.EnableNodeMaintenance(a.reason) 36 | } 37 | 38 | return client.DisableNodeMaintenance() 39 | } 40 | -------------------------------------------------------------------------------- /action/service_deregister.go: -------------------------------------------------------------------------------- 1 | package action 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | 7 | "github.com/hashicorp/go-multierror" 8 | ) 9 | 10 | type serviceDeregister struct { 11 | *config 12 | } 13 | 14 | func ServiceDeregisterAction() Action { 15 | return &serviceDeregister{ 16 | config: &gConfig, 17 | } 18 | } 19 | 20 | func (s *serviceDeregister) CommandFlags() *flag.FlagSet { 21 | return s.newFlagSet(FLAG_NONE) 22 | } 23 | 24 | func (s *serviceDeregister) Run(args []string) error { 25 | if len(args) <= 0 { 26 | return fmt.Errorf("No service IDs specified") 27 | } 28 | 29 | agent, err := s.newAgent() 30 | if err != nil { 31 | return err 32 | } 33 | 34 | var result error 35 | 36 | for _, id := range args { 37 | if err := agent.ServiceDeregister(id); err != nil { 38 | result = multierror.Append(result, err) 39 | } 40 | } 41 | 42 | return result 43 | } 44 | -------------------------------------------------------------------------------- /action/session_renew.go: -------------------------------------------------------------------------------- 1 | package action 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | ) 7 | 8 | type sessionRenew struct { 9 | *config 10 | } 11 | 12 | func SessionRenewAction() Action { 13 | return &sessionRenew{ 14 | config: &gConfig, 15 | } 16 | } 17 | 18 | func (s *sessionRenew) CommandFlags() *flag.FlagSet { 19 | return s.newFlagSet(FLAG_DATACENTER, FLAG_OUTPUT) 20 | } 21 | 22 | func (s *sessionRenew) Run(args []string) error { 23 | if len(args) != 1 { 24 | return fmt.Errorf("A single session Id must be specified") 25 | } 26 | sessionid := args[0] 27 | 28 | client, err := s.newSession() 29 | if err != nil { 30 | return err 31 | } 32 | 33 | writeOpts := s.writeOptions() 34 | 35 | session, _, err := client.Renew(sessionid, writeOpts) 36 | if err != nil { 37 | return err 38 | } 39 | 40 | if session != nil { 41 | s.Output(session) 42 | } 43 | 44 | return nil 45 | } 46 | -------------------------------------------------------------------------------- /commands/root_test.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import ( 4 | "flag" 5 | "io/ioutil" 6 | "os" 7 | "testing" 8 | 9 | "github.com/hashicorp/consul/sdk/testutil" 10 | ) 11 | 12 | var consulTestAddr string 13 | 14 | func TestMain(m *testing.M) { 15 | var consulServerOutputEnabled bool 16 | flag.BoolVar(&consulServerOutputEnabled, "enable-consul-output", false, "Enables consul server output") 17 | flag.Parse() 18 | 19 | // create a test Consul server 20 | consulTestServer, err := testutil.NewTestServerConfig(func(c *testutil.TestServerConfig) { 21 | c.Connect = nil 22 | 23 | if !consulServerOutputEnabled { 24 | c.Stderr = ioutil.Discard 25 | c.Stdout = ioutil.Discard 26 | } 27 | }) 28 | 29 | if err != nil { 30 | panic(err) 31 | } 32 | 33 | consulTestAddr = consulTestServer.HTTPAddr 34 | 35 | exitCode := m.Run() 36 | consulTestServer.Stop() 37 | 38 | os.Exit(exitCode) 39 | } 40 | -------------------------------------------------------------------------------- /action/catalog_node.go: -------------------------------------------------------------------------------- 1 | package action 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | ) 7 | 8 | type catalogNode struct { 9 | *config 10 | } 11 | 12 | func CatalogNodeAction() Action { 13 | return &catalogNode{ 14 | config: &gConfig, 15 | } 16 | } 17 | 18 | func (c *catalogNode) CommandFlags() *flag.FlagSet { 19 | return c.newFlagSet(FLAG_DATACENTER, FLAG_CONSISTENCY, FLAG_OUTPUT, FLAG_BLOCKING) 20 | } 21 | 22 | func (c *catalogNode) Run(args []string) error { 23 | switch { 24 | case len(args) == 0: 25 | return fmt.Errorf("Node name must be specified") 26 | case len(args) > 1: 27 | return fmt.Errorf("Only one node allowed") 28 | } 29 | 30 | client, err := c.newCatalog() 31 | if err != nil { 32 | return err 33 | } 34 | 35 | queryOpts := c.queryOptions() 36 | config, _, err := client.Node(args[0], queryOpts) 37 | if err != nil { 38 | return err 39 | } 40 | 41 | return c.Output(config) 42 | } 43 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | NAME = $(shell awk -F\" '/^const Name/ { print $$2 }' main.go) 2 | VERSION = $(shell awk -F\" '/^const Version/ { print $$2 }' main.go) 3 | 4 | all: build 5 | 6 | build: 7 | @mkdir -p bin/ 8 | go build -o bin/$(NAME) 9 | 10 | deps: 11 | go mod download 12 | 13 | test: 14 | go test ./... $(TESTARGS) -timeout=30s -parallel=4 15 | go vet ./... 16 | 17 | xcompile: test package-clean 18 | @mkdir -p build 19 | gox \ 20 | -arch="arm amd64" \ 21 | -os="darwin freebsd linux netbsd openbsd solarios windows" \ 22 | -output="build/{{.Dir}}_$(VERSION)_{{.OS}}_{{.Arch}}/$(NAME)" 23 | 24 | package: xcompile 25 | $(eval FILES := $(shell ls build)) 26 | @mkdir -p build/zip 27 | for f in $(FILES); do \ 28 | (cd $$PWD/build/$$f && zip ../zip/$$f.zip consul-cli*); \ 29 | echo $$f; \ 30 | done 31 | 32 | package-clean: 33 | -@rm -rf build/ 34 | 35 | .PHONY: all deps updatedeps build test xcompile package package-clean 36 | -------------------------------------------------------------------------------- /action/health_node.go: -------------------------------------------------------------------------------- 1 | package action 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | ) 7 | 8 | type healthNode struct { 9 | *config 10 | } 11 | 12 | func HealthNodeAction() Action { 13 | return &healthNode{ 14 | config: &gConfig, 15 | } 16 | } 17 | 18 | func (h *healthNode) CommandFlags() *flag.FlagSet { 19 | return h.newFlagSet(FLAG_DATACENTER, FLAG_OUTPUT, FLAG_CONSISTENCY, FLAG_BLOCKING) 20 | } 21 | 22 | func (h *healthNode) Run(args []string) error { 23 | switch { 24 | case len(args) == 0: 25 | return fmt.Errorf("Node name must be specified") 26 | case len(args) > 1: 27 | return fmt.Errorf("Only one node name allowed") 28 | } 29 | node := args[0] 30 | 31 | client, err := h.newHealth() 32 | if err != nil { 33 | return err 34 | } 35 | 36 | queryOpts := h.queryOptions() 37 | 38 | n, _, err := client.Node(node, queryOpts) 39 | if err != nil { 40 | return err 41 | } 42 | 43 | return h.Output(n) 44 | } 45 | -------------------------------------------------------------------------------- /action/kv_watch.go: -------------------------------------------------------------------------------- 1 | package action 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | ) 7 | 8 | type kvWatch struct { 9 | *config 10 | } 11 | 12 | func KvWatchAction() Action { 13 | return &kvWatch{ 14 | config: &gConfig, 15 | } 16 | } 17 | 18 | func (k *kvWatch) CommandFlags() *flag.FlagSet { 19 | return k.newFlagSet(FLAG_DATACENTER, FLAG_KVOUTPUT, FLAG_CONSISTENCY, FLAG_BLOCKING) 20 | } 21 | 22 | func (k *kvWatch) Run(args []string) error { 23 | if len(args) != 1 { 24 | return fmt.Errorf("A single key path must be specified") 25 | } 26 | path := args[0] 27 | 28 | client, err := k.newKv() 29 | if err != nil { 30 | return err 31 | } 32 | 33 | queryOpts := k.queryOptions() 34 | 35 | RETRY: 36 | kv, meta, err := client.Get(path, queryOpts) 37 | if err != nil { 38 | return err 39 | } 40 | 41 | if kv == nil { 42 | queryOpts.WaitIndex = meta.LastIndex 43 | goto RETRY 44 | } 45 | 46 | return k.OutputKv(kv) 47 | } 48 | -------------------------------------------------------------------------------- /action/health_checks.go: -------------------------------------------------------------------------------- 1 | package action 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | ) 7 | 8 | type healthChecks struct { 9 | *config 10 | } 11 | 12 | func HealthChecksAction() Action { 13 | return &healthChecks{ 14 | config: &gConfig, 15 | } 16 | } 17 | 18 | func (h *healthChecks) CommandFlags() *flag.FlagSet { 19 | return h.newFlagSet(FLAG_DATACENTER, FLAG_OUTPUT, FLAG_CONSISTENCY, FLAG_BLOCKING) 20 | } 21 | 22 | func (h *healthChecks) Run(args []string) error { 23 | switch { 24 | case len(args) == 0: 25 | return fmt.Errorf("Service name must be specified") 26 | case len(args) > 1: 27 | return fmt.Errorf("Only one service name allowed") 28 | } 29 | service := args[0] 30 | 31 | client, err := h.newHealth() 32 | if err != nil { 33 | return err 34 | } 35 | 36 | queryOpts := h.queryOptions() 37 | 38 | checks, _, err := client.Checks(service, queryOpts) 39 | if err != nil { 40 | return err 41 | } 42 | 43 | return h.Output(checks) 44 | } 45 | -------------------------------------------------------------------------------- /action/health_state.go: -------------------------------------------------------------------------------- 1 | package action 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "strings" 7 | ) 8 | 9 | type healthState struct { 10 | *config 11 | } 12 | 13 | func HealthStateAction() Action { 14 | return &healthState{ 15 | config: &gConfig, 16 | } 17 | } 18 | 19 | func (h *healthState) CommandFlags() *flag.FlagSet { 20 | return h.newFlagSet(FLAG_DATACENTER, FLAG_OUTPUT, FLAG_CONSISTENCY, FLAG_BLOCKING) 21 | } 22 | 23 | func (h *healthState) Run(args []string) error { 24 | switch { 25 | case len(args) == 0: 26 | return fmt.Errorf("Check state must be specified") 27 | case len(args) > 1: 28 | return fmt.Errorf("Only one check state allowed") 29 | } 30 | state := strings.ToLower(args[0]) 31 | 32 | client, err := h.newHealth() 33 | if err != nil { 34 | return err 35 | } 36 | 37 | queryOpts := h.queryOptions() 38 | 39 | s, _, err := client.State(state, queryOpts) 40 | if err != nil { 41 | return err 42 | } 43 | 44 | return h.Output(s) 45 | } 46 | -------------------------------------------------------------------------------- /action/snapshot_restore.go: -------------------------------------------------------------------------------- 1 | package action 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "os" 7 | ) 8 | 9 | type snapshotRestore struct { 10 | *config 11 | } 12 | 13 | func SnapshotRestoreAction() Action { 14 | return &snapshotRestore{ 15 | config: &gConfig, 16 | } 17 | } 18 | 19 | func (s *snapshotRestore) CommandFlags() *flag.FlagSet { 20 | return s.newFlagSet(FLAG_DATACENTER) 21 | } 22 | 23 | func (s *snapshotRestore) Run(args []string) error { 24 | switch { 25 | case len(args) == 0: 26 | return fmt.Errorf("Path to snapshot file must be specified") 27 | case len(args) > 1: 28 | return fmt.Errorf("Only one snapshot file can be specified") 29 | } 30 | filePath := args[0] 31 | 32 | snap, err := s.newSnapshot() 33 | if err != nil { 34 | return err 35 | } 36 | 37 | f, err := os.Open(filePath) 38 | if err != nil { 39 | return err 40 | } 41 | defer f.Close() 42 | 43 | writeOpts := s.writeOptions() 44 | 45 | return snap.Restore(writeOpts, f) 46 | } 47 | -------------------------------------------------------------------------------- /action/operator_keyring_remove.go: -------------------------------------------------------------------------------- 1 | package action 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | 7 | "github.com/hashicorp/go-multierror" 8 | ) 9 | 10 | type operatorKeyringRemove struct { 11 | *config 12 | } 13 | 14 | func OperatorKeyringRemoveAction() Action { 15 | return &operatorKeyringRemove{ 16 | config: &gConfig, 17 | } 18 | } 19 | 20 | func (o *operatorKeyringRemove) CommandFlags() *flag.FlagSet { 21 | return o.newFlagSet(FLAG_NONE) 22 | } 23 | 24 | func (o *operatorKeyringRemove) Run(args []string) error { 25 | if len(args) < 1 { 26 | return fmt.Errorf("At least one gossip key must be specified") 27 | } 28 | 29 | client, err := o.newOperator() 30 | if err != nil { 31 | return err 32 | } 33 | 34 | writeOpts := o.writeOptions() 35 | 36 | var result error 37 | 38 | for _, k := range args { 39 | if err := client.KeyringRemove(k, writeOpts); err != nil { 40 | result = multierror.Append(result, err) 41 | } 42 | } 43 | 44 | return result 45 | } 46 | -------------------------------------------------------------------------------- /action/operator_keyring_install.go: -------------------------------------------------------------------------------- 1 | package action 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | 7 | "github.com/hashicorp/go-multierror" 8 | ) 9 | 10 | type operatorKeyringInstall struct { 11 | *config 12 | } 13 | 14 | func OperatorKeyringInstallAction() Action { 15 | return &operatorKeyringInstall{ 16 | config: &gConfig, 17 | } 18 | } 19 | 20 | func (o *operatorKeyringInstall) CommandFlags() *flag.FlagSet { 21 | return o.newFlagSet(FLAG_NONE) 22 | } 23 | 24 | func (o *operatorKeyringInstall) Run(args []string) error { 25 | if len(args) < 1 { 26 | return fmt.Errorf("At least one gossip key must be specified") 27 | } 28 | 29 | client, err := o.newOperator() 30 | if err != nil { 31 | return err 32 | } 33 | 34 | writeOpts := o.writeOptions() 35 | 36 | var result error 37 | 38 | for _, k := range args { 39 | if err := client.KeyringInstall(k, writeOpts); err != nil { 40 | result = multierror.Append(result, err) 41 | } 42 | } 43 | 44 | return result 45 | } 46 | -------------------------------------------------------------------------------- /action/agent_monitor.go: -------------------------------------------------------------------------------- 1 | package action 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | ) 7 | 8 | type agentMonitor struct { 9 | loglevel string 10 | *config 11 | } 12 | 13 | func AgentMonitorAction() Action { 14 | return &agentMonitor{ 15 | config: &gConfig, 16 | } 17 | } 18 | 19 | func (a *agentMonitor) CommandFlags() *flag.FlagSet { 20 | f := a.newFlagSet(FLAG_NONE) 21 | 22 | f.StringVar(&a.loglevel, "loglevel", "", "Log level to filter on. Default is info") 23 | 24 | return f 25 | } 26 | 27 | func (a *agentMonitor) Run(args []string) error { 28 | client, err := a.newAgent() 29 | if err != nil { 30 | return err 31 | } 32 | 33 | outputChan, err := client.Monitor( 34 | a.loglevel, 35 | nil, // XXX - Set up interrupts and stop things gracefully 36 | nil, // query options. No documentation on what they are used for. Leave nil for now. 37 | ) 38 | if err != nil { 39 | return err 40 | } 41 | 42 | for s := range outputChan { 43 | fmt.Printf(s) 44 | } 45 | 46 | return nil 47 | } 48 | -------------------------------------------------------------------------------- /action/check_update.go: -------------------------------------------------------------------------------- 1 | package action 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | ) 7 | 8 | type checkUpdate struct { 9 | status string 10 | output string 11 | 12 | *config 13 | } 14 | 15 | func CheckUpdateAction() Action { 16 | return &checkUpdate{ 17 | config: &gConfig, 18 | } 19 | } 20 | 21 | func (c *checkUpdate) CommandFlags() *flag.FlagSet { 22 | f := c.newFlagSet(FLAG_NONE) 23 | 24 | f.StringVar(&c.status, "status", "", "Check status. One of passing, warning or critical") 25 | f.StringVar(&c.output, "output", "", "Optional human readable message with the status of the check") 26 | 27 | return f 28 | } 29 | 30 | func (c *checkUpdate) Run(args []string) error { 31 | if len(args) != 1 { 32 | return fmt.Errorf("A single check id must be specified") 33 | } 34 | checkId := args[0] 35 | 36 | client, err := c.newAgent() 37 | if err != nil { 38 | return err 39 | } 40 | 41 | err = client.UpdateTTL(checkId, c.output, c.status) 42 | if err != nil { 43 | return err 44 | } 45 | 46 | return nil 47 | } 48 | -------------------------------------------------------------------------------- /action/kv_keys.go: -------------------------------------------------------------------------------- 1 | package action 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | ) 7 | 8 | type kvKeys struct { 9 | separator string 10 | 11 | *config 12 | } 13 | 14 | func KvKeysAction() Action { 15 | return &kvKeys{ 16 | config: &gConfig, 17 | } 18 | } 19 | 20 | func (k *kvKeys) CommandFlags() *flag.FlagSet { 21 | f := k.newFlagSet(FLAG_DATACENTER, FLAG_CONSISTENCY, FLAG_BLOCKING) 22 | 23 | f.StringVar(&k.separator, "separator", "", "List keys only up to a given separator") 24 | 25 | return f 26 | } 27 | 28 | func (k *kvKeys) Run(args []string) error { 29 | if len(args) != 1 { 30 | return fmt.Errorf("A single key path must be specified") 31 | } 32 | path := args[0] 33 | 34 | client, err := k.newKv() 35 | if err != nil { 36 | return err 37 | } 38 | 39 | queryOpts := k.queryOptions() 40 | data, _, err := client.Keys(path, k.separator, queryOpts) 41 | if err != nil { 42 | return err 43 | } 44 | 45 | k.output.template = kv_outputTemplate 46 | 47 | return k.Output(data) 48 | } 49 | 50 | var kv_outputTemplate = `{{range .}}{{.}} 51 | {{end}}` 52 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/mantl/consul-cli 2 | 3 | go 1.13 4 | 5 | require ( 6 | github.com/armon/go-metrics v0.3.2 // indirect 7 | github.com/hashicorp/consul/api v1.4.0 8 | github.com/hashicorp/consul/sdk v0.4.0 9 | github.com/hashicorp/go-immutable-radix v1.1.0 // indirect 10 | github.com/hashicorp/go-msgpack v0.5.5 // indirect 11 | github.com/hashicorp/go-multierror v1.0.0 12 | github.com/hashicorp/go-sockaddr v1.0.2 // indirect 13 | github.com/hashicorp/go-uuid v1.0.2 // indirect 14 | github.com/hashicorp/golang-lru v0.5.4 // indirect 15 | github.com/hashicorp/hcl v1.0.0 16 | github.com/hashicorp/memberlist v0.1.7 // indirect 17 | github.com/hashicorp/serf v0.8.5 // indirect 18 | github.com/pkg/errors v0.9.1 // indirect 19 | github.com/spf13/cobra v0.0.6 20 | github.com/spf13/pflag v1.0.5 // indirect 21 | github.com/stretchr/testify v1.5.0 22 | golang.org/x/crypto v0.0.0-20191106202628-ed6320f186d4 // indirect 23 | golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa // indirect 24 | golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae // indirect 25 | ) 26 | -------------------------------------------------------------------------------- /action/catalog_service.go: -------------------------------------------------------------------------------- 1 | package action 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | ) 7 | 8 | type catalogService struct { 9 | tag string 10 | nodeMeta []string 11 | 12 | *config 13 | } 14 | 15 | func CatalogServiceAction() Action { 16 | return &catalogService{ 17 | config: &gConfig, 18 | } 19 | } 20 | 21 | func (c *catalogService) CommandFlags() *flag.FlagSet { 22 | f := c.newFlagSet(FLAG_DATACENTER, FLAG_OUTPUT, FLAG_CONSISTENCY, FLAG_NODEMETA, FLAG_NEAR) 23 | 24 | f.StringVar(&c.tag, "tag", "", "Service tag to filter on") 25 | 26 | return f 27 | } 28 | 29 | func (c *catalogService) Run(args []string) error { 30 | switch { 31 | case len(args) == 0: 32 | return fmt.Errorf("Service name must be specified") 33 | case len(args) > 1: 34 | return fmt.Errorf("Only one service allowed") 35 | } 36 | 37 | client, err := c.newCatalog() 38 | if err != nil { 39 | return err 40 | } 41 | 42 | queryOpts := c.queryOptions() 43 | config, _, err := client.Service(args[0], c.tag, queryOpts) 44 | if err != nil { 45 | return err 46 | } 47 | 48 | return c.Output(config) 49 | } 50 | -------------------------------------------------------------------------------- /action/snapshot_save.go: -------------------------------------------------------------------------------- 1 | package action 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "io" 7 | "os" 8 | ) 9 | 10 | type snapshotSave struct { 11 | *config 12 | } 13 | 14 | func SnapshotSaveAction() Action { 15 | return &snapshotSave{ 16 | config: &gConfig, 17 | } 18 | } 19 | 20 | func (s *snapshotSave) CommandFlags() *flag.FlagSet { 21 | return s.newFlagSet(FLAG_DATACENTER, FLAG_STALE) 22 | } 23 | 24 | func (s *snapshotSave) Run(args []string) error { 25 | switch { 26 | case len(args) == 0: 27 | return fmt.Errorf("Path to snapshot file must be specifie") 28 | case len(args) > 1: 29 | return fmt.Errorf("Only one file path allowed") 30 | } 31 | filePath := args[0] 32 | 33 | snap, err := s.newSnapshot() 34 | if err != nil { 35 | return err 36 | } 37 | 38 | queryOpts := s.queryOptions() 39 | 40 | reader, _, err := snap.Save(queryOpts) 41 | if err != nil { 42 | return err 43 | } 44 | defer reader.Close() 45 | 46 | f, err := os.Create(filePath) 47 | if err != nil { 48 | return err 49 | } 50 | defer f.Close() 51 | 52 | _, err = io.Copy(f, reader) 53 | if err != nil { 54 | return err 55 | } 56 | 57 | return nil 58 | } 59 | -------------------------------------------------------------------------------- /action/health_service.go: -------------------------------------------------------------------------------- 1 | package action 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | ) 7 | 8 | type healthService struct { 9 | tag string 10 | passing bool 11 | 12 | *config 13 | } 14 | 15 | func HealthServiceAction() Action { 16 | return &healthService{ 17 | config: &gConfig, 18 | } 19 | } 20 | 21 | func (h *healthService) CommandFlags() *flag.FlagSet { 22 | f := h.newFlagSet(FLAG_DATACENTER, FLAG_OUTPUT, FLAG_CONSISTENCY, FLAG_BLOCKING) 23 | 24 | f.StringVar(&h.tag, "tag", "", "Service tag to filter on") 25 | f.BoolVar(&h.passing, "passing", false, "Only return passing checks") 26 | 27 | return f 28 | } 29 | 30 | func (h *healthService) Run(args []string) error { 31 | switch { 32 | case len(args) == 0: 33 | return fmt.Errorf("Service name must be specified") 34 | case len(args) > 1: 35 | return fmt.Errorf("Only one service name allowed") 36 | } 37 | service := args[0] 38 | 39 | client, err := h.newHealth() 40 | if err != nil { 41 | return err 42 | } 43 | 44 | queryOpts := h.queryOptions() 45 | 46 | s, _, err := client.Service(service, h.tag, h.passing, queryOpts) 47 | if err != nil { 48 | return err 49 | } 50 | 51 | return h.Output(s) 52 | } 53 | -------------------------------------------------------------------------------- /action/kv_read.go: -------------------------------------------------------------------------------- 1 | package action 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | ) 7 | 8 | type kvRead struct { 9 | recurse bool 10 | 11 | *config 12 | } 13 | 14 | func KvReadAction() Action { 15 | return &kvRead{ 16 | config: &gConfig, 17 | } 18 | } 19 | 20 | func (k *kvRead) CommandFlags() *flag.FlagSet { 21 | f := k.newFlagSet(FLAG_DATACENTER, FLAG_KVOUTPUT, FLAG_CONSISTENCY, FLAG_BLOCKING) 22 | 23 | f.BoolVar(&k.recurse, "recurse", false, "Perform a recursive read") 24 | 25 | return f 26 | } 27 | 28 | func (k *kvRead) Run(args []string) error { 29 | if len(args) != 1 { 30 | return fmt.Errorf("A single key path must be specified") 31 | } 32 | path := args[0] 33 | 34 | client, err := k.newKv() 35 | if err != nil { 36 | return err 37 | } 38 | 39 | queryOpts := k.queryOptions() 40 | 41 | if k.recurse { 42 | kvlist, _, err := client.List(path, queryOpts) 43 | if err != nil { 44 | return err 45 | } 46 | 47 | if kvlist == nil { 48 | return nil 49 | } 50 | 51 | return k.OutputKv(kvlist) 52 | } else { 53 | kv, _, err := client.Get(path, queryOpts) 54 | if err != nil { 55 | return err 56 | } 57 | 58 | if kv == nil { 59 | return nil 60 | } 61 | 62 | return k.OutputKv(kv) 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /action/catalog_deregister.go: -------------------------------------------------------------------------------- 1 | package action 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | 7 | consulapi "github.com/hashicorp/consul/api" 8 | ) 9 | 10 | type catalogDeregister struct { 11 | node string 12 | serviceId string 13 | checkId string 14 | 15 | *config 16 | } 17 | 18 | func CatalogDeregisterAction() Action { 19 | return &catalogDeregister{ 20 | config: &gConfig, 21 | } 22 | } 23 | 24 | func (c *catalogDeregister) CommandFlags() *flag.FlagSet { 25 | f := c.newFlagSet(FLAG_DATACENTER) 26 | 27 | f.StringVar(&c.node, "node", "", "Consul node name. Required") 28 | f.StringVar(&c.serviceId, "service-id", "", "Service ID to deregister") 29 | f.StringVar(&c.checkId, "check-id", "", "Check ID to deregister") 30 | 31 | return f 32 | } 33 | 34 | func (c *catalogDeregister) Run(args []string) error { 35 | client, err := c.newCatalog() 36 | if err != nil { 37 | return err 38 | } 39 | 40 | if c.node == "" { 41 | return fmt.Errorf("Node name is required for catalog deregistration") 42 | } 43 | 44 | writeOpts := c.writeOptions() 45 | _, err = client.Deregister(&consulapi.CatalogDeregistration{ 46 | Node: c.node, 47 | Datacenter: c.dc, 48 | ServiceID: c.serviceId, 49 | CheckID: c.checkId, 50 | }, writeOpts) 51 | 52 | return err 53 | } 54 | -------------------------------------------------------------------------------- /action/service_maintenance.go: -------------------------------------------------------------------------------- 1 | package action 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | 7 | "github.com/hashicorp/go-multierror" 8 | ) 9 | 10 | type serviceMaintenance struct { 11 | enabled bool 12 | reason string 13 | 14 | *config 15 | } 16 | 17 | func ServiceMaintenanceAction() Action { 18 | return &serviceMaintenance{ 19 | config: &gConfig, 20 | } 21 | } 22 | 23 | func (s *serviceMaintenance) CommandFlags() *flag.FlagSet { 24 | f := s.newFlagSet(FLAG_NONE) 25 | 26 | f.BoolVar(&s.enabled, "enabled", true, "Boolean value for maintenance mode") 27 | f.StringVar(&s.reason, "reason", "", "Reason for entering maintenance mode") 28 | 29 | return f 30 | } 31 | 32 | func (s *serviceMaintenance) Run(args []string) error { 33 | if len(args) <= 0 { 34 | return fmt.Errorf("No service IDs specified") 35 | } 36 | 37 | agent, err := s.newAgent() 38 | if err != nil { 39 | return err 40 | } 41 | 42 | var result error 43 | 44 | for _, id := range args { 45 | if s.enabled { 46 | if err := agent.EnableServiceMaintenance(id, s.reason); err != nil { 47 | result = multierror.Append(result, err) 48 | } 49 | } else { 50 | if err := agent.DisableServiceMaintenance(id); err != nil { 51 | result = multierror.Append(result, err) 52 | } 53 | } 54 | } 55 | 56 | return result 57 | } 58 | -------------------------------------------------------------------------------- /commands/service_test.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import ( 4 | "net/http" 5 | "net/http/httptest" 6 | "testing" 7 | "time" 8 | 9 | "github.com/stretchr/testify/assert" 10 | ) 11 | 12 | func TestServiceRegister(t *testing.T) { 13 | // wait channel for the http check 14 | requestChan := make(chan struct{}) 15 | tested := false 16 | fakeService := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 17 | assert.Equal(t, "testhost", r.Host) 18 | assert.True(t, assert.ObjectsAreEqual([]string{"TestVal1,TestVal2"}, r.Header["X-Test-Header"])) 19 | if !tested { 20 | tested = true 21 | requestChan <- struct{}{} 22 | } 23 | })) 24 | defer fakeService.Close() 25 | 26 | // register the service with `consul-cli` 27 | args := []string{"service", "register", "test-service", 28 | "--check", 29 | "--http", fakeService.URL, 30 | "--header", "Host: testhost", 31 | "--header", "X-Test-Header: TestVal1,TestVal2", 32 | "--interval", "1s", 33 | "--consul", consulTestAddr} 34 | 35 | command := NewConsulCliCommand("consul-cli", "0.0.1") 36 | command.SetArgs(args) 37 | err := command.Execute() 38 | assert.Nil(t, err) 39 | 40 | select { 41 | case <-requestChan: 42 | return 43 | case <-time.After(2 * time.Second): 44 | t.Fatalf("Timeout waiting for health check") 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # consul-cli 2 | 3 | [![Build Status](https://travis-ci.org/mantl/consul-cli.svg)](https://travis-ci.org/mantl/consul-cli) 4 | 5 | Command line interface to [Consul HTTP API](https://consul.io/docs/agent/http.html) 6 | 7 | ## Subcommands 8 | 9 | | Command | Synopsis | 10 | | ------- | -------- | 11 | | [acl](https://github.com/CiscoCloud/consul-cli/wiki/ACL) | Consul /acl endpoint 12 | | [agent](https://github.com/CiscoCloud/consul-cli/wiki/Agent) | Consul /agent endpoint 13 | | [catalog](https://github.com/CiscoCloud/consul-cli/wiki/Catalog) | Consul /catalog endpoint 14 | | [check](https://github.com/CiscoCloud/consul-cli/wiki/Check) | Consul /agent/check endpoint 15 | | [coordinate](https://github.com/CiscoCloud/consul-cli/wiki/Coordinate) | Consul /coordinate endpoint 16 | | [health](https://github.com/CiscoCloud/consul-cli/wiki/Health) | Consul /health endpoint 17 | | [kv](https://github.com/CiscoCloud/consul-cli/wiki/KV) | Consul /kv endpoint 18 | | [service](https://github.com/CiscoCloud/consul-cli/wiki/Service) | Consul /agent/service endpoint 19 | | [session](https://github.com/CiscoCloud/consul-cli/wiki/Session) | Consul /session endpoint 20 | | [status](https://github.com/CiscoCloud/consul-cli/wiki/Status) | Consul /status endpoint 21 | 22 | ## Build Docker image 23 | 24 | ``` 25 | $ docker build -t consul-cli . 26 | ``` 27 | -------------------------------------------------------------------------------- /action/catalog_register.go: -------------------------------------------------------------------------------- 1 | package action 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | 7 | consulapi "github.com/hashicorp/consul/api" 8 | ) 9 | 10 | type catalogRegister struct { 11 | node string 12 | 13 | *config 14 | } 15 | 16 | func CatalogRegisterAction() Action { 17 | return &catalogRegister{ 18 | config: &gConfig, 19 | } 20 | } 21 | 22 | func (c *catalogRegister) CommandFlags() *flag.FlagSet { 23 | f := c.newFlagSet(FLAG_DATACENTER) 24 | 25 | f.StringVar(&c.node, "node", "", "Service node") 26 | 27 | c.addServiceFlags(f) 28 | 29 | return f 30 | } 31 | 32 | func (c *catalogRegister) Run(args []string) error { 33 | if len(args) != 1 { 34 | return fmt.Errorf("A single service name must be specified") 35 | } 36 | service := args[0] 37 | 38 | client, err := c.newCatalog() 39 | if err != nil { 40 | return err 41 | } 42 | 43 | writeOpts := c.writeOptions() 44 | 45 | _, err = client.Register(&consulapi.CatalogRegistration{ 46 | Node: c.node, 47 | Address: c.service.address, 48 | Datacenter: c.dc, 49 | Service: &consulapi.AgentService{ 50 | ID: c.service.id, 51 | Service: service, 52 | Tags: c.service.tags, 53 | Port: c.service.port, 54 | EnableTagOverride: c.service.overrideTag, 55 | }, 56 | }, 57 | writeOpts) 58 | 59 | return err 60 | } 61 | -------------------------------------------------------------------------------- /action/raw.go: -------------------------------------------------------------------------------- 1 | package action 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "io/ioutil" 7 | "os" 8 | 9 | "github.com/hashicorp/hcl" 10 | ) 11 | 12 | type raw struct { 13 | data string 14 | } 15 | 16 | func (r *raw) isSet() bool { 17 | return r.data != "" 18 | } 19 | 20 | // Return string of file contents 21 | // 22 | func (r *raw) readString() (string, error) { 23 | data, err := r.read() 24 | if err != nil { 25 | return "", err 26 | } 27 | 28 | return string(data), nil 29 | } 30 | 31 | // Unmarshal JSON file contents 32 | // 33 | func (r *raw) readJSON(v interface{}) error { 34 | data, err := r.read() 35 | if err != nil { 36 | return err 37 | } 38 | 39 | // Try JSON first 40 | if err := json.Unmarshal(data, v); err == nil { 41 | return nil 42 | } 43 | 44 | // HCL next 45 | if err := hcl.Unmarshal(data, v); err == nil { 46 | return nil 47 | } 48 | 49 | return fmt.Errorf("Unable to unmarshal raw file") 50 | } 51 | 52 | // Read the data from a given path. Read from stdin 53 | // if path == "-" 54 | // 55 | func (r *raw) read() ([]byte, error) { 56 | if r.data == "-" { 57 | data, err := ioutil.ReadAll(os.Stdin) 58 | if err != nil { 59 | return nil, err 60 | } 61 | return data, nil 62 | } 63 | 64 | data, err := ioutil.ReadFile(r.data) 65 | if err != nil { 66 | return nil, err 67 | } 68 | 69 | return data, nil 70 | } 71 | -------------------------------------------------------------------------------- /commands/status.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import ( 4 | "github.com/spf13/cobra" 5 | 6 | "github.com/mantl/consul-cli/action" 7 | ) 8 | 9 | func newStatusCommand() *cobra.Command { 10 | cmd := &cobra.Command{ 11 | Use: "status", 12 | Short: "Consul /status endpoint interface", 13 | Long: "Consul /status endpoint interface", 14 | Run: func(cmd *cobra.Command, args []string) { 15 | cmd.HelpFunc()(cmd, []string{}) 16 | }, 17 | } 18 | 19 | cmd.AddCommand(newStatusLeaderCommand()) 20 | cmd.AddCommand(newStatusPeersCommand()) 21 | 22 | return cmd 23 | } 24 | 25 | func newStatusLeaderCommand() *cobra.Command { 26 | s := action.StatusLeaderAction() 27 | 28 | cmd := &cobra.Command{ 29 | Use: "leader", 30 | Short: "Get the current Raft leader", 31 | Long: "Get the current Raft leader", 32 | RunE: func(cmd *cobra.Command, args []string) error { 33 | return s.Run(args) 34 | }, 35 | } 36 | 37 | cmd.Flags().AddGoFlagSet(s.CommandFlags()) 38 | 39 | return cmd 40 | } 41 | 42 | func newStatusPeersCommand() *cobra.Command { 43 | s := action.StatusPeersAction() 44 | 45 | cmd := &cobra.Command{ 46 | Use: "peers", 47 | Short: "Get the current Raft peers", 48 | Long: "Get the current Raft peers", 49 | RunE: func(cmd *cobra.Command, args []string) error { 50 | return s.Run(args) 51 | }, 52 | } 53 | 54 | cmd.Flags().AddGoFlagSet(s.CommandFlags()) 55 | 56 | return cmd 57 | } 58 | -------------------------------------------------------------------------------- /commands/event.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import ( 4 | "github.com/spf13/cobra" 5 | 6 | "github.com/mantl/consul-cli/action" 7 | ) 8 | 9 | func newEventCommand() *cobra.Command { 10 | cmd := &cobra.Command{ 11 | Use: "event", 12 | Short: "Consul /event endpoint interface", 13 | Long: "Consul /event endpoint interface", 14 | Run: func(cmd *cobra.Command, args []string) { 15 | cmd.HelpFunc()(cmd, []string{}) 16 | }, 17 | } 18 | 19 | cmd.AddCommand(newEventFireCommand()) 20 | cmd.AddCommand(newEventListCommand()) 21 | 22 | return cmd 23 | } 24 | 25 | func newEventFireCommand() *cobra.Command { 26 | e := action.EventFireAction() 27 | 28 | cmd := &cobra.Command{ 29 | Use: "fire ", 30 | Short: "Fires a new user event", 31 | Long: "Fires a new user event", 32 | RunE: func(cmd *cobra.Command, args []string) error { 33 | return e.Run(args) 34 | }, 35 | } 36 | 37 | cmd.Flags().AddGoFlagSet(e.CommandFlags()) 38 | 39 | return cmd 40 | } 41 | 42 | func newEventListCommand() *cobra.Command { 43 | e := action.EventListAction() 44 | 45 | cmd := &cobra.Command{ 46 | Use: "list", 47 | Short: "Lists the most recent events the agent has seen", 48 | Long: "Lists the most recent events the agent has seen", 49 | RunE: func(cmd *cobra.Command, args []string) error { 50 | return e.Run(args) 51 | }, 52 | } 53 | 54 | cmd.Flags().AddGoFlagSet(e.CommandFlags()) 55 | 56 | return cmd 57 | } 58 | -------------------------------------------------------------------------------- /commands/snapshot.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import ( 4 | "github.com/spf13/cobra" 5 | 6 | "github.com/mantl/consul-cli/action" 7 | ) 8 | 9 | func newSnapshotCommand() *cobra.Command { 10 | cmd := &cobra.Command{ 11 | Use: "snapshot", 12 | Short: "Consul /snapshot endpoint interface", 13 | Long: "Consul /snapshot endpoint interface", 14 | Run: func(cmd *cobra.Command, args []string) { 15 | cmd.HelpFunc()(cmd, []string{}) 16 | }, 17 | } 18 | 19 | cmd.AddCommand(newSnapshotSaveCommand()) 20 | cmd.AddCommand(newSnapshotRestoreCommand()) 21 | 22 | return cmd 23 | } 24 | 25 | func newSnapshotSaveCommand() *cobra.Command { 26 | s := action.SnapshotSaveAction() 27 | 28 | cmd := &cobra.Command{ 29 | Use: "save snapshot_path", 30 | Short: "Create a new snapshot", 31 | Long: "Create a new snapshot", 32 | RunE: func(cmd *cobra.Command, args []string) error { 33 | return s.Run(args) 34 | }, 35 | } 36 | 37 | cmd.Flags().AddGoFlagSet(s.CommandFlags()) 38 | 39 | return cmd 40 | } 41 | 42 | func newSnapshotRestoreCommand() *cobra.Command { 43 | s := action.SnapshotRestoreAction() 44 | 45 | cmd := &cobra.Command{ 46 | Use: "restore snapshot_path", 47 | Short: "Restore a snapshot", 48 | Long: "Restore a snapshot", 49 | RunE: func(cmd *cobra.Command, args []string) error { 50 | return s.Run(args) 51 | }, 52 | } 53 | 54 | cmd.Flags().AddGoFlagSet(s.CommandFlags()) 55 | 56 | return cmd 57 | } 58 | -------------------------------------------------------------------------------- /commands/check_test.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import ( 4 | "net/http" 5 | "net/http/httptest" 6 | "testing" 7 | "time" 8 | 9 | "github.com/stretchr/testify/assert" 10 | ) 11 | 12 | func TestCheckRegister(t *testing.T) { 13 | // wait channel for the http check 14 | requestChan := make(chan struct{}) 15 | tested := false 16 | fakeService := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 17 | assert.Equal(t, "testhost", r.Host) 18 | assert.True(t, assert.ObjectsAreEqual([]string{"TestVal1,TestVal2"}, r.Header["X-Test-Header"])) 19 | if !tested { 20 | tested = true 21 | requestChan <- struct{}{} 22 | } 23 | })) 24 | defer fakeService.Close() 25 | 26 | // register the service with `consul-cli` 27 | serviceArgs := []string{"service", "register", "test-check-service", "--consul", consulTestAddr} 28 | checkArgs := []string{"check", "register", "test-http-check", "--interval", "1s", "--http", fakeService.URL, "--header", "Host: testhost", "--header", "X-Test-Header: TestVal1,TestVal2"} 29 | 30 | command := NewConsulCliCommand("consul-cli", "0.0.1") 31 | command.SetArgs(serviceArgs) 32 | err := command.Execute() 33 | assert.Nil(t, err) 34 | 35 | command.ResetFlags() 36 | 37 | command.SetArgs(checkArgs) 38 | err = command.Execute() 39 | assert.Nil(t, err) 40 | 41 | select { 42 | case <-requestChan: 43 | return 44 | case <-time.After(2 * time.Second): 45 | t.Fatalf("Timeout waiting for health check") 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /commands/coordinate.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import ( 4 | "github.com/spf13/cobra" 5 | 6 | "github.com/mantl/consul-cli/action" 7 | ) 8 | 9 | func newCoordinateCommand() *cobra.Command { 10 | cmd := &cobra.Command{ 11 | Use: "coordinate", 12 | Short: "Consul /coordinate endpoint interface", 13 | Long: "Consul /coordinate endpoint interface", 14 | Run: func(cmd *cobra.Command, args []string) { 15 | cmd.HelpFunc()(cmd, []string{}) 16 | }, 17 | } 18 | 19 | cmd.AddCommand(newCoordDatacentersCommand()) 20 | cmd.AddCommand(newCoordNodesCommand()) 21 | 22 | return cmd 23 | } 24 | 25 | func newCoordDatacentersCommand() *cobra.Command { 26 | c := action.CoordDatacentersAction() 27 | 28 | cmd := &cobra.Command{ 29 | Use: "datacenters", 30 | Short: "Queries for WAN coordinates of Consul servers", 31 | Long: "Queries for WAN coordinates of Consul servers", 32 | RunE: func(cmd *cobra.Command, args []string) error { 33 | return c.Run(args) 34 | }, 35 | } 36 | 37 | cmd.Flags().AddGoFlagSet(c.CommandFlags()) 38 | 39 | return cmd 40 | } 41 | 42 | func newCoordNodesCommand() *cobra.Command { 43 | c := action.CoordNodesAction() 44 | 45 | cmd := &cobra.Command{ 46 | Use: "nodes", 47 | Short: "Queries for LAN coordinates of Consul servers", 48 | Long: "Queries for LAN coordinates of Consul servers", 49 | RunE: func(cmd *cobra.Command, args []string) error { 50 | return c.Run(args) 51 | }, 52 | } 53 | 54 | cmd.Flags().AddGoFlagSet(c.CommandFlags()) 55 | 56 | return cmd 57 | } 58 | -------------------------------------------------------------------------------- /action/string_slice.go: -------------------------------------------------------------------------------- 1 | package action 2 | 3 | import ( 4 | "bytes" 5 | "encoding/csv" 6 | "fmt" 7 | "strings" 8 | ) 9 | 10 | var _ = fmt.Fprint 11 | 12 | // -- stringSlice Value 13 | type stringSliceValue struct { 14 | value *[]string 15 | changed bool 16 | isCSV bool 17 | } 18 | 19 | func newStringSliceValue(p *[]string) *stringSliceValue { 20 | ssv := new(stringSliceValue) 21 | ssv.value = p 22 | ssv.isCSV = true 23 | return ssv 24 | } 25 | 26 | func readAsCSV(val string) ([]string, error) { 27 | if val == "" { 28 | return []string{}, nil 29 | } 30 | stringReader := strings.NewReader(val) 31 | csvReader := csv.NewReader(stringReader) 32 | return csvReader.Read() 33 | } 34 | 35 | func writeAsCSV(vals []string) (string, error) { 36 | b := &bytes.Buffer{} 37 | w := csv.NewWriter(b) 38 | err := w.Write(vals) 39 | if err != nil { 40 | return "", err 41 | } 42 | w.Flush() 43 | return strings.TrimSuffix(b.String(), fmt.Sprintln()), nil 44 | } 45 | 46 | func (s *stringSliceValue) Set(val string) error { 47 | var v []string 48 | var err error 49 | 50 | if s.isCSV { 51 | v, err = readAsCSV(val) 52 | } else { 53 | v = []string{val} 54 | } 55 | 56 | if err != nil { 57 | return err 58 | } 59 | if !s.changed { 60 | *s.value = v 61 | } else { 62 | *s.value = append(*s.value, v...) 63 | } 64 | s.changed = true 65 | return nil 66 | } 67 | 68 | func (s *stringSliceValue) Type() string { 69 | return "stringSlice" 70 | } 71 | 72 | func (s *stringSliceValue) String() string { 73 | str, _ := writeAsCSV(*s.value) 74 | return "[" + str + "]" 75 | } 76 | -------------------------------------------------------------------------------- /action/kv_delete.go: -------------------------------------------------------------------------------- 1 | package action 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "strconv" 7 | 8 | consulapi "github.com/hashicorp/consul/api" 9 | ) 10 | 11 | type kvDelete struct { 12 | modifyIndex string 13 | recurse bool 14 | 15 | *config 16 | } 17 | 18 | func KvDeleteAction() Action { 19 | return &kvDelete{ 20 | config: &gConfig, 21 | } 22 | } 23 | 24 | func (k *kvDelete) CommandFlags() *flag.FlagSet { 25 | f := k.newFlagSet(FLAG_DATACENTER) 26 | 27 | f.StringVar(&k.modifyIndex, "modifyindex", "", "Perform a Check-and-Set delete") 28 | f.BoolVar(&k.recurse, "recurse", false, "Perform a recursive delete") 29 | 30 | return f 31 | } 32 | 33 | func (k *kvDelete) Run(args []string) error { 34 | if len(args) != 1 { 35 | return fmt.Errorf("A single key path must be specified") 36 | } 37 | path := args[0] 38 | 39 | client, err := k.newKv() 40 | if err != nil { 41 | return err 42 | } 43 | 44 | writeOpts := k.writeOptions() 45 | 46 | switch { 47 | case k.recurse: 48 | _, err := client.DeleteTree(path, writeOpts) 49 | if err != nil { 50 | return err 51 | } 52 | case k.modifyIndex != "": 53 | m, err := strconv.ParseUint(k.modifyIndex, 0, 64) 54 | if err != nil { 55 | return err 56 | } 57 | kv := consulapi.KVPair{ 58 | Key: path, 59 | ModifyIndex: m, 60 | } 61 | 62 | success, _, err := client.DeleteCAS(&kv, writeOpts) 63 | if err != nil { 64 | return err 65 | } 66 | 67 | if !success { 68 | return fmt.Errorf("Failed deleting") 69 | } 70 | default: 71 | _, err := client.Delete(path, writeOpts) 72 | if err != nil { 73 | return err 74 | } 75 | } 76 | 77 | return nil 78 | } 79 | -------------------------------------------------------------------------------- /action/acl_update.go: -------------------------------------------------------------------------------- 1 | package action 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | 7 | consulapi "github.com/hashicorp/consul/api" 8 | ) 9 | 10 | type aclUpdate struct { 11 | management bool 12 | name string 13 | rules []string 14 | *config 15 | } 16 | 17 | func AclUpdateAction() Action { 18 | return &aclUpdate{ 19 | config: &gConfig, 20 | } 21 | } 22 | 23 | func (a *aclUpdate) CommandFlags() *flag.FlagSet { 24 | f := a.newFlagSet(FLAG_RAW) 25 | 26 | f.BoolVar(&a.management, "management", false, "Create a management token") 27 | f.StringVar(&a.name, "name", "", "Name of the ACL") 28 | f.Var(newStringSliceValue(&a.rules), "rule", "Rule to create. Can be multiple rules on a command line. Format is type:path:policy") 29 | 30 | return f 31 | } 32 | 33 | func (a *aclUpdate) Run(args []string) error { 34 | if len(args) != 1 { 35 | return fmt.Errorf("An ACL id must be specified") 36 | } 37 | id := args[0] 38 | 39 | client, err := a.newACL() 40 | if err != nil { 41 | return err 42 | } 43 | 44 | entry := new(consulapi.ACLEntry) 45 | entry.Name = a.name 46 | entry.ID = id 47 | 48 | if a.management { 49 | entry.Type = consulapi.ACLManagementType 50 | } else { 51 | entry.Type = consulapi.ACLClientType 52 | } 53 | 54 | if a.raw.isSet() { 55 | rules, err := a.raw.readString() 56 | if err != nil { 57 | return err 58 | } 59 | entry.Rules = rules 60 | } else { 61 | rules, err := getRulesString(a.rules) 62 | if err != nil { 63 | return err 64 | } 65 | entry.Rules = rules 66 | } 67 | 68 | writeOpts := a.writeOptions() 69 | _, err = client.Update(entry, writeOpts) 70 | if err != nil { 71 | return err 72 | } 73 | 74 | return nil 75 | } 76 | -------------------------------------------------------------------------------- /action/operator_autopilot_set.go: -------------------------------------------------------------------------------- 1 | // +build consul8 2 | // 3 | 4 | package action 5 | 6 | import ( 7 | "flag" 8 | "fmt" 9 | "strconv" 10 | 11 | consulapi "github.com/hashicorp/consul/api" 12 | ) 13 | 14 | type operatorAutopilotSet struct { 15 | modifyIndex string 16 | cleanDead bool 17 | 18 | *config 19 | } 20 | 21 | func OperatorAutopilotSetAction() Action { 22 | return &operatorAutopilotSet{ 23 | config: &gConfig, 24 | } 25 | } 26 | 27 | func (o *operatorAutopilotSet) CommandFlags() *flag.FlagSet { 28 | f := o.newFlagSet(FLAG_DATACENTER) 29 | 30 | f.StringVar(&o.modifyIndex, "modifyindex", "", "Perform a check-and-set operation") 31 | f.BoolVar(&o.cleanDead, "clean-dead-servers", false, "Remove dead servers automatically when a new server is added") 32 | 33 | return f 34 | } 35 | 36 | func (o *operatorAutopilotSet) Run(args []string) error { 37 | client, err := o.newOperator() 38 | if err != nil { 39 | return err 40 | } 41 | 42 | writeOpts := o.writeOptions() 43 | 44 | ac := &consulapi.AutopilotConfiguration{ 45 | CleanupDeadServers: o.cleanDead, 46 | } 47 | 48 | if o.modifyIndex == "" { 49 | _, err := client.AutopilotSetConfiguration(ac, writeOpts) 50 | if err != nil { 51 | return err 52 | } 53 | } else { 54 | i, err := strconv.ParseUint(o.modifyIndex, 0, 64) 55 | if err != nil { 56 | return fmt.Errorf("Error parsing modifyIndex: %v", o.modifyIndex) 57 | } 58 | ac.ModifyIndex = i 59 | 60 | success, _, err := client.AutopilotCASConfiguration(ac, writeOpts) 61 | if err != nil { 62 | return err 63 | } 64 | 65 | if !success { 66 | return fmt.Errorf("Failed to write Autopilot configuration") 67 | } 68 | } 69 | 70 | return nil 71 | } 72 | -------------------------------------------------------------------------------- /action/event_fire.go: -------------------------------------------------------------------------------- 1 | package action 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | 7 | consulapi "github.com/hashicorp/consul/api" 8 | ) 9 | 10 | type eventFire struct { 11 | node string 12 | payload string 13 | service string 14 | tag string 15 | 16 | *config 17 | } 18 | 19 | func EventFireAction() Action { 20 | return &eventFire{ 21 | config: &gConfig, 22 | } 23 | } 24 | 25 | func (e *eventFire) CommandFlags() *flag.FlagSet { 26 | f := e.newFlagSet(FLAG_DATACENTER, FLAG_OUTPUT, FLAG_RAW) 27 | 28 | f.StringVar(&e.node, "node", "", "Filter by node name") 29 | f.StringVar(&e.payload, "payload", "", "Event payload") 30 | f.StringVar(&e.service, "service", "", "Filter by service") 31 | f.StringVar(&e.tag, "tag", "", "Filter by service tag") 32 | 33 | return f 34 | } 35 | 36 | func (e *eventFire) Run(args []string) error { 37 | var event consulapi.UserEvent 38 | 39 | if e.raw.isSet() { 40 | if err := e.raw.readJSON(&event); err != nil { 41 | return err 42 | } 43 | } else { 44 | if len(args) != 1 { 45 | return fmt.Errorf("An event name must be specified") 46 | } 47 | eventName := args[0] 48 | 49 | var payload []byte 50 | if e.payload != "" { 51 | payload = []byte(e.payload) 52 | } 53 | 54 | event = consulapi.UserEvent{ 55 | Name: eventName, 56 | NodeFilter: e.node, 57 | ServiceFilter: e.service, 58 | TagFilter: e.tag, 59 | Payload: payload, 60 | } 61 | } 62 | 63 | client, err := e.newEvent() 64 | if err != nil { 65 | return err 66 | } 67 | 68 | writeOpts := e.writeOptions() 69 | rval, _, err := client.Fire(&event, writeOpts) 70 | if err != nil { 71 | return err 72 | } 73 | 74 | return e.Output(rval) 75 | } 76 | -------------------------------------------------------------------------------- /commands/root.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/spf13/cobra" 7 | 8 | "github.com/mantl/consul-cli/action" 9 | ) 10 | 11 | func NewConsulCliCommand(name, version string) *cobra.Command { 12 | cmd := &cobra.Command{ 13 | Use: "consul-cli", 14 | Short: "Command line interface for Consul HTTP API", 15 | Long: "Command line interface for Consul HTTP API", 16 | SilenceErrors: true, 17 | PersistentPreRun: func(cmd *cobra.Command, args []string) { 18 | }, 19 | RunE: func(cmd *cobra.Command, args []string) error { 20 | cmd.HelpFunc()(cmd, []string{}) 21 | return nil 22 | }, 23 | } 24 | 25 | cmd.PersistentFlags().BoolVarP(&cmd.SilenceUsage, "quiet", "q", true, "Don't show usage on error") 26 | cmd.PersistentFlags().AddGoFlagSet(action.GlobalCommandFlags()) 27 | 28 | versionCmd := &cobra.Command{ 29 | Use: "version", 30 | Short: "Print version information", 31 | Long: "Print version information", 32 | RunE: func(cmd *cobra.Command, args []string) error { 33 | fmt.Printf("%s %s\n", name, version) 34 | return nil 35 | }, 36 | } 37 | cmd.AddCommand(versionCmd) 38 | 39 | cmd.AddCommand(newAclCommand()) 40 | cmd.AddCommand(newAgentCommand()) 41 | cmd.AddCommand(newCatalogCommand()) 42 | cmd.AddCommand(newCheckCommand()) 43 | cmd.AddCommand(newCoordinateCommand()) 44 | cmd.AddCommand(newEventCommand()) 45 | cmd.AddCommand(newHealthCommand()) 46 | cmd.AddCommand(newKvCommand()) 47 | cmd.AddCommand(newOperatorCommand()) 48 | cmd.AddCommand(newServiceCommand()) 49 | cmd.AddCommand(newSessionCommand()) 50 | cmd.AddCommand(newSnapshotCommand()) 51 | cmd.AddCommand(newStatusCommand()) 52 | cmd.AddCommand(newTxnCommand()) 53 | 54 | return cmd 55 | } 56 | -------------------------------------------------------------------------------- /action/acl_create.go: -------------------------------------------------------------------------------- 1 | package action 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | 7 | consulapi "github.com/hashicorp/consul/api" 8 | ) 9 | 10 | type aclCreate struct { 11 | management bool 12 | name string 13 | rules []string 14 | *config 15 | } 16 | 17 | func AclCreateAction() Action { 18 | return &aclCreate{ 19 | config: &gConfig, 20 | } 21 | } 22 | 23 | func (a *aclCreate) CommandFlags() *flag.FlagSet { 24 | f := a.newFlagSet(FLAG_RAW) 25 | 26 | f.BoolVar(&a.management, "management", false, "Create a management token") 27 | f.StringVar(&a.name, "name", "", "Name of the ACL") 28 | f.Var(newStringSliceValue(&a.rules), "rule", "Rule to create. Can be multiple rules on a command line. Format is type:path:policy") 29 | 30 | return f 31 | } 32 | 33 | func (a *aclCreate) Run(args []string) error { 34 | client, err := a.newACL() 35 | if err != nil { 36 | return err 37 | } 38 | 39 | entry := new(consulapi.ACLEntry) 40 | entry.Name = a.name 41 | 42 | switch { 43 | case len(args) == 1: 44 | entry.ID = args[0] 45 | case len(args) > 1: 46 | return fmt.Errorf("Only one ACL identified can be specified") 47 | } 48 | 49 | if a.management { 50 | entry.Type = consulapi.ACLManagementType 51 | } else { 52 | entry.Type = consulapi.ACLClientType 53 | } 54 | 55 | if a.raw.isSet() { 56 | rules, err := a.raw.readString() 57 | if err != nil { 58 | return err 59 | } 60 | entry.Rules = rules 61 | } else { 62 | rules, err := getRulesString(a.rules) 63 | if err != nil { 64 | return err 65 | } 66 | entry.Rules = rules 67 | } 68 | 69 | writeOpts := a.writeOptions() 70 | id, _, err := client.Create(entry, writeOpts) 71 | if err != nil { 72 | return err 73 | } 74 | 75 | fmt.Println(id) 76 | 77 | return nil 78 | } 79 | -------------------------------------------------------------------------------- /action/kv_unlock.go: -------------------------------------------------------------------------------- 1 | package action 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | ) 7 | 8 | type kvUnlock struct { 9 | session string 10 | noDestroy bool 11 | 12 | *config 13 | } 14 | 15 | func KvUnlockAction() Action { 16 | return &kvUnlock{ 17 | config: &gConfig, 18 | } 19 | } 20 | 21 | func (k *kvUnlock) CommandFlags() *flag.FlagSet { 22 | f := k.newFlagSet(FLAG_DATACENTER, FLAG_CONSISTENCY) 23 | 24 | f.StringVar(&k.session, "session", "", "Session ID of the lock holder. Required") 25 | f.BoolVar(&k.noDestroy, "no-destroy", false, "Do not destroy the session when complete") 26 | 27 | return f 28 | } 29 | 30 | func (k *kvUnlock) Run(args []string) error { 31 | if len(args) != 1 { 32 | return fmt.Errorf("A single key path must be specified") 33 | } 34 | path := args[0] 35 | 36 | if k.session == "" { 37 | return fmt.Errorf("Session ID must be provided") 38 | } 39 | 40 | client, err := k.newKv() 41 | if err != nil { 42 | return err 43 | } 44 | 45 | sessionClient, err := k.newSession() 46 | if err != nil { 47 | return err 48 | } 49 | 50 | queryOpts := k.queryOptions() 51 | 52 | kv, _, err := client.Get(path, queryOpts) 53 | if err != nil { 54 | return err 55 | } 56 | 57 | if kv == nil { 58 | return fmt.Errorf("Node '%s' does not exist", path) 59 | } 60 | 61 | if kv.Session != k.session { 62 | return fmt.Errorf("Session not lock holder") 63 | } 64 | 65 | writeOpts := k.writeOptions() 66 | 67 | success, _, err := client.Release(kv, writeOpts) 68 | if err != nil { 69 | return err 70 | } 71 | 72 | if !k.noDestroy { 73 | _, err = sessionClient.Destroy(k.session, writeOpts) 74 | if err != nil { 75 | return err 76 | } 77 | } 78 | 79 | if !success { 80 | return fmt.Errorf("Failed unlocking path") 81 | } 82 | 83 | return nil 84 | } 85 | -------------------------------------------------------------------------------- /Vagrantfile: -------------------------------------------------------------------------------- 1 | # -*- mode: ruby -*- 2 | # vi: set ft=ruby : 3 | 4 | # Vagrantfile API/syntax version. Don't touch unless you know what you're doing! 5 | VAGRANTFILE_API_VERSION = '2' 6 | 7 | @script = <