1package get
2
3import (
4	"bytes"
5	"encoding/base64"
6	"flag"
7	"fmt"
8	"io"
9	"text/tabwriter"
10
11	"github.com/hashicorp/consul/api"
12	"github.com/hashicorp/consul/command/flags"
13	"github.com/mitchellh/cli"
14)
15
16func New(ui cli.Ui) *cmd {
17	c := &cmd{UI: ui}
18	c.init()
19	return c
20}
21
22type cmd struct {
23	UI           cli.Ui
24	flags        *flag.FlagSet
25	http         *flags.HTTPFlags
26	help         string
27	base64encode bool
28	detailed     bool
29	keys         bool
30	recurse      bool
31	separator    string
32}
33
34func (c *cmd) init() {
35	c.flags = flag.NewFlagSet("", flag.ContinueOnError)
36	c.flags.BoolVar(&c.base64encode, "base64", false,
37		"Base64 encode the value. The default value is false.")
38	c.flags.BoolVar(&c.detailed, "detailed", false,
39		"Provide additional metadata about the key in addition to the value such "+
40			"as the ModifyIndex and any flags that may have been set on the key. "+
41			"The default value is false.")
42	c.flags.BoolVar(&c.keys, "keys", false,
43		"List keys which start with the given prefix, but not their values. "+
44			"This is especially useful if you only need the key names themselves. "+
45			"This option is commonly combined with the -separator option. The default "+
46			"value is false.")
47	c.flags.BoolVar(&c.recurse, "recurse", false,
48		"Recursively look at all keys prefixed with the given path. The default "+
49			"value is false.")
50	c.flags.StringVar(&c.separator, "separator", "/",
51		"String to use as a separator between keys. The default value is \"/\", "+
52			"but this option is only taken into account when paired with the -keys flag.")
53
54	c.http = &flags.HTTPFlags{}
55	flags.Merge(c.flags, c.http.ClientFlags())
56	flags.Merge(c.flags, c.http.ServerFlags())
57	c.help = flags.Usage(help, c.flags)
58}
59
60func (c *cmd) Run(args []string) int {
61	if err := c.flags.Parse(args); err != nil {
62		return 1
63	}
64
65	key := ""
66
67	// Check for arg validation
68	args = c.flags.Args()
69	switch len(args) {
70	case 0:
71		key = ""
72	case 1:
73		key = args[0]
74	default:
75		c.UI.Error(fmt.Sprintf("Too many arguments (expected 1, got %d)", len(args)))
76		return 1
77	}
78
79	// This is just a "nice" thing to do. Since pairs cannot start with a /, but
80	// users will likely put "/" or "/foo", lets go ahead and strip that for them
81	// here.
82	if len(key) > 0 && key[0] == '/' {
83		key = key[1:]
84	}
85
86	// If the key is empty and we are not doing a recursive or key-based lookup,
87	// this is an error.
88	if key == "" && !(c.recurse || c.keys) {
89		c.UI.Error("Error! Missing KEY argument")
90		return 1
91	}
92
93	// Create and test the HTTP client
94	client, err := c.http.APIClient()
95	if err != nil {
96		c.UI.Error(fmt.Sprintf("Error connecting to Consul agent: %s", err))
97		return 1
98	}
99
100	switch {
101	case c.keys:
102		keys, _, err := client.KV().Keys(key, c.separator, &api.QueryOptions{
103			AllowStale: c.http.Stale(),
104		})
105		if err != nil {
106			c.UI.Error(fmt.Sprintf("Error querying Consul agent: %s", err))
107			return 1
108		}
109
110		for _, k := range keys {
111			c.UI.Info(string(k))
112		}
113
114		return 0
115	case c.recurse:
116		pairs, _, err := client.KV().List(key, &api.QueryOptions{
117			AllowStale: c.http.Stale(),
118		})
119		if err != nil {
120			c.UI.Error(fmt.Sprintf("Error querying Consul agent: %s", err))
121			return 1
122		}
123
124		for i, pair := range pairs {
125			if c.detailed {
126				var b bytes.Buffer
127				if err := prettyKVPair(&b, pair, c.base64encode); err != nil {
128					c.UI.Error(fmt.Sprintf("Error rendering KV pair: %s", err))
129					return 1
130				}
131
132				c.UI.Info(b.String())
133
134				if i < len(pairs)-1 {
135					c.UI.Info("")
136				}
137			} else {
138				if c.base64encode {
139					c.UI.Info(fmt.Sprintf("%s:%s", pair.Key, base64.StdEncoding.EncodeToString(pair.Value)))
140				} else {
141					c.UI.Info(fmt.Sprintf("%s:%s", pair.Key, pair.Value))
142				}
143			}
144		}
145
146		return 0
147	default:
148		pair, _, err := client.KV().Get(key, &api.QueryOptions{
149			AllowStale: c.http.Stale(),
150		})
151		if err != nil {
152			c.UI.Error(fmt.Sprintf("Error querying Consul agent: %s", err))
153			return 1
154		}
155
156		if pair == nil {
157			c.UI.Error(fmt.Sprintf("Error! No key exists at: %s", key))
158			return 1
159		}
160
161		if c.detailed {
162			var b bytes.Buffer
163			if err := prettyKVPair(&b, pair, c.base64encode); err != nil {
164				c.UI.Error(fmt.Sprintf("Error rendering KV pair: %s", err))
165				return 1
166			}
167
168			c.UI.Info(b.String())
169			return 0
170		}
171
172		if c.base64encode {
173			c.UI.Info(base64.StdEncoding.EncodeToString(pair.Value))
174		} else {
175			c.UI.Info(string(pair.Value))
176		}
177		return 0
178	}
179}
180
181func (c *cmd) Synopsis() string {
182	return synopsis
183}
184
185func (c *cmd) Help() string {
186	return c.help
187}
188
189func prettyKVPair(w io.Writer, pair *api.KVPair, base64EncodeValue bool) error {
190	tw := tabwriter.NewWriter(w, 0, 2, 6, ' ', 0)
191	fmt.Fprintf(tw, "CreateIndex\t%d\n", pair.CreateIndex)
192	fmt.Fprintf(tw, "Flags\t%d\n", pair.Flags)
193	fmt.Fprintf(tw, "Key\t%s\n", pair.Key)
194	fmt.Fprintf(tw, "LockIndex\t%d\n", pair.LockIndex)
195	fmt.Fprintf(tw, "ModifyIndex\t%d\n", pair.ModifyIndex)
196	if pair.Session == "" {
197		fmt.Fprint(tw, "Session\t-\n")
198	} else {
199		fmt.Fprintf(tw, "Session\t%s\n", pair.Session)
200	}
201	if base64EncodeValue {
202		fmt.Fprintf(tw, "Value\t%s", base64.StdEncoding.EncodeToString(pair.Value))
203	} else {
204		fmt.Fprintf(tw, "Value\t%s", pair.Value)
205	}
206	return tw.Flush()
207}
208
209const synopsis = "Retrieves or lists data from the KV store"
210const help = `
211Usage: consul kv get [options] [KEY_OR_PREFIX]
212
213  Retrieves the value from Consul's key-value store at the given key name. If no
214  key exists with that name, an error is returned. If a key exists with that
215  name but has no data, nothing is returned. If the name or prefix is omitted,
216  it defaults to "" which is the root of the key-value store.
217
218  To retrieve the value for the key named "foo" in the key-value store:
219
220      $ consul kv get foo
221
222  This will return the original, raw value stored in Consul. To view detailed
223  information about the key, specify the "-detailed" flag. This will output all
224  known metadata about the key including ModifyIndex and any user-supplied
225  flags:
226
227      $ consul kv get -detailed foo
228
229  To treat the path as a prefix and list all keys which start with the given
230  prefix, specify the "-recurse" flag:
231
232      $ consul kv get -recurse foo
233
234  This will return all key-value pairs. To just list the keys which start with
235  the specified prefix, use the "-keys" option instead:
236
237      $ consul kv get -keys foo
238
239  For a full list of options and examples, please see the Consul documentation.
240`
241