github.com/rigado/snapd@v2.42.5-go-mod+incompatible/cmd/snap/cmd_get.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2016 Canonical Ltd 5 * 6 * This program is free software: you can redistribute it and/or modify 7 * it under the terms of the GNU General Public License version 3 as 8 * published by the Free Software Foundation. 9 * 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 * 15 * You should have received a copy of the GNU General Public License 16 * along with this program. If not, see <http://www.gnu.org/licenses/>. 17 * 18 */ 19 20 package main 21 22 import ( 23 "encoding/json" 24 "fmt" 25 "sort" 26 "strings" 27 28 "github.com/jessevdk/go-flags" 29 30 "github.com/snapcore/snapd/i18n" 31 ) 32 33 var shortGetHelp = i18n.G("Print configuration options") 34 var longGetHelp = i18n.G(` 35 The get command prints configuration options for the provided snap. 36 37 $ snap get snap-name username 38 frank 39 40 If multiple option names are provided, a document is returned: 41 42 $ snap get snap-name username password 43 { 44 "username": "frank", 45 "password": "..." 46 } 47 48 Nested values may be retrieved via a dotted path: 49 50 $ snap get snap-name author.name 51 frank 52 `) 53 54 type cmdGet struct { 55 clientMixin 56 Positional struct { 57 Snap installedSnapName `required:"yes"` 58 Keys []string 59 } `positional-args:"yes"` 60 61 Typed bool `short:"t"` 62 Document bool `short:"d"` 63 List bool `short:"l"` 64 } 65 66 func init() { 67 addCommand("get", shortGetHelp, longGetHelp, func() flags.Commander { return &cmdGet{} }, 68 map[string]string{ 69 // TRANSLATORS: This should not start with a lowercase letter. 70 "d": i18n.G("Always return document, even with single key"), 71 // TRANSLATORS: This should not start with a lowercase letter. 72 "l": i18n.G("Always return list, even with single key"), 73 // TRANSLATORS: This should not start with a lowercase letter. 74 "t": i18n.G("Strict typing with nulls and quoted strings"), 75 }, []argDesc{ 76 { 77 name: "<snap>", 78 // TRANSLATORS: This should not start with a lowercase letter. 79 desc: i18n.G("The snap whose conf is being requested"), 80 }, 81 { 82 // TRANSLATORS: This needs to begin with < and end with > 83 name: i18n.G("<key>"), 84 // TRANSLATORS: This should not start with a lowercase letter. 85 desc: i18n.G("Key of interest within the configuration"), 86 }, 87 }) 88 } 89 90 type ConfigValue struct { 91 Path string 92 Value interface{} 93 } 94 95 type byConfigPath []ConfigValue 96 97 func (s byConfigPath) Len() int { return len(s) } 98 func (s byConfigPath) Swap(i, j int) { s[i], s[j] = s[j], s[i] } 99 func (s byConfigPath) Less(i, j int) bool { 100 other := s[j].Path 101 for k, c := range s[i].Path { 102 if len(other) <= k { 103 return false 104 } 105 106 switch { 107 case c == rune(other[k]): 108 continue 109 case c == '.': 110 return true 111 case other[k] == '.' || c > rune(other[k]): 112 return false 113 } 114 return true 115 } 116 return true 117 } 118 119 func sortByPath(config []ConfigValue) { 120 sort.Sort(byConfigPath(config)) 121 } 122 123 func flattenConfig(cfg map[string]interface{}, root bool) (values []ConfigValue) { 124 const docstr = "{...}" 125 for k, v := range cfg { 126 if input, ok := v.(map[string]interface{}); ok { 127 if root { 128 values = append(values, ConfigValue{k, docstr}) 129 } else { 130 for kk, vv := range input { 131 p := k + "." + kk 132 if _, ok := vv.(map[string]interface{}); ok { 133 values = append(values, ConfigValue{p, docstr}) 134 } else { 135 values = append(values, ConfigValue{p, vv}) 136 } 137 } 138 } 139 } else { 140 values = append(values, ConfigValue{k, v}) 141 } 142 } 143 sortByPath(values) 144 return values 145 } 146 147 func rootRequested(confKeys []string) bool { 148 return len(confKeys) == 0 149 } 150 151 // outputJson will be used when the user requested "document" output via 152 // the "-d" commandline switch. 153 func (c *cmdGet) outputJson(conf interface{}) error { 154 bytes, err := json.MarshalIndent(conf, "", "\t") 155 if err != nil { 156 return err 157 } 158 159 fmt.Fprintln(Stdout, string(bytes)) 160 return nil 161 } 162 163 // outputList will be used when the user requested list output via the 164 // "-l" commandline switch. 165 func (x *cmdGet) outputList(conf map[string]interface{}) error { 166 if rootRequested(x.Positional.Keys) && len(conf) == 0 { 167 return fmt.Errorf("snap %q has no configuration", x.Positional.Snap) 168 } 169 170 w := tabWriter() 171 defer w.Flush() 172 173 fmt.Fprintf(w, "Key\tValue\n") 174 values := flattenConfig(conf, rootRequested(x.Positional.Keys)) 175 for _, v := range values { 176 fmt.Fprintf(w, "%s\t%v\n", v.Path, v.Value) 177 } 178 return nil 179 } 180 181 // outputDefault will be used when no commandline switch to override the 182 // output where used. The output follows the following rules: 183 // - a single key with a string value is printed directly 184 // - multiple keys are printed as a list to the terminal (if there is one) 185 // or as json if there is no terminal 186 // - the option "typed" is honored 187 func (x *cmdGet) outputDefault(conf map[string]interface{}, snapName string, confKeys []string) error { 188 if rootRequested(confKeys) && len(conf) == 0 { 189 return fmt.Errorf("snap %q has no configuration", snapName) 190 } 191 192 var confToPrint interface{} = conf 193 194 if len(confKeys) == 1 { 195 // if single key was requested, then just output the 196 // value unless it's a map, in which case it will be 197 // printed as a list below. 198 if _, ok := conf[confKeys[0]].(map[string]interface{}); !ok { 199 confToPrint = conf[confKeys[0]] 200 } 201 } 202 203 // conf looks like a map 204 if cfg, ok := confToPrint.(map[string]interface{}); ok { 205 if isStdinTTY { 206 return x.outputList(cfg) 207 } 208 209 // TODO: remove this conditional and the warning below 210 // after a transition period. 211 fmt.Fprintf(Stderr, i18n.G(`WARNING: The output of 'snap get' will become a list with columns - use -d or -l to force the output format.\n`)) 212 return x.outputJson(confToPrint) 213 } 214 215 if s, ok := confToPrint.(string); ok && !x.Typed { 216 fmt.Fprintln(Stdout, s) 217 return nil 218 } 219 220 if confToPrint != nil || x.Typed { 221 return x.outputJson(confToPrint) 222 } 223 224 fmt.Fprintln(Stdout, "") 225 return nil 226 227 } 228 229 func (x *cmdGet) Execute(args []string) error { 230 if len(args) > 0 { 231 // TRANSLATORS: the %s is the list of extra arguments 232 return fmt.Errorf(i18n.G("too many arguments: %s"), strings.Join(args, " ")) 233 } 234 235 if x.Document && x.Typed { 236 return fmt.Errorf("cannot use -d and -t together") 237 } 238 239 if x.Document && x.List { 240 return fmt.Errorf("cannot use -d and -l together") 241 } 242 243 snapName := string(x.Positional.Snap) 244 confKeys := x.Positional.Keys 245 246 conf, err := x.client.Conf(snapName, confKeys) 247 if err != nil { 248 return err 249 } 250 251 switch { 252 case x.Document: 253 return x.outputJson(conf) 254 case x.List: 255 return x.outputList(conf) 256 default: 257 return x.outputDefault(conf, snapName, confKeys) 258 } 259 }