github.com/Lephar/snapd@v0.0.0-20210825215435-c7fba9cef4d2/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, the corresponding values are returned: 41 42 $ snap get snap-name username password 43 Key Value 44 username frank 45 password ... 46 47 Nested values may be retrieved via a dotted path: 48 49 $ snap get snap-name author.name 50 frank 51 `) 52 53 type cmdGet struct { 54 clientMixin 55 Positional struct { 56 Snap installedSnapName `required:"yes"` 57 Keys []string 58 } `positional-args:"yes"` 59 60 Typed bool `short:"t"` 61 Document bool `short:"d"` 62 List bool `short:"l"` 63 } 64 65 func init() { 66 addCommand("get", shortGetHelp, longGetHelp, func() flags.Commander { return &cmdGet{} }, 67 map[string]string{ 68 // TRANSLATORS: This should not start with a lowercase letter. 69 "d": i18n.G("Always return document, even with single key"), 70 // TRANSLATORS: This should not start with a lowercase letter. 71 "l": i18n.G("Always return list, even with single key"), 72 // TRANSLATORS: This should not start with a lowercase letter. 73 "t": i18n.G("Strict typing with nulls and quoted strings"), 74 }, []argDesc{ 75 { 76 name: "<snap>", 77 // TRANSLATORS: This should not start with a lowercase letter. 78 desc: i18n.G("The snap whose conf is being requested"), 79 }, 80 { 81 // TRANSLATORS: This needs to begin with < and end with > 82 name: i18n.G("<key>"), 83 // TRANSLATORS: This should not start with a lowercase letter. 84 desc: i18n.G("Key of interest within the configuration"), 85 }, 86 }) 87 } 88 89 type ConfigValue struct { 90 Path string 91 Value interface{} 92 } 93 94 type byConfigPath []ConfigValue 95 96 func (s byConfigPath) Len() int { return len(s) } 97 func (s byConfigPath) Swap(i, j int) { s[i], s[j] = s[j], s[i] } 98 func (s byConfigPath) Less(i, j int) bool { 99 other := s[j].Path 100 for k, c := range s[i].Path { 101 if len(other) <= k { 102 return false 103 } 104 105 switch { 106 case c == rune(other[k]): 107 continue 108 case c == '.': 109 return true 110 case other[k] == '.' || c > rune(other[k]): 111 return false 112 } 113 return true 114 } 115 return true 116 } 117 118 func sortByPath(config []ConfigValue) { 119 sort.Sort(byConfigPath(config)) 120 } 121 122 func flattenConfig(cfg map[string]interface{}, root bool) (values []ConfigValue) { 123 const docstr = "{...}" 124 for k, v := range cfg { 125 if input, ok := v.(map[string]interface{}); ok { 126 if root { 127 values = append(values, ConfigValue{k, docstr}) 128 } else { 129 for kk, vv := range input { 130 p := k + "." + kk 131 if _, ok := vv.(map[string]interface{}); ok { 132 values = append(values, ConfigValue{p, docstr}) 133 } else { 134 values = append(values, ConfigValue{p, vv}) 135 } 136 } 137 } 138 } else { 139 values = append(values, ConfigValue{k, v}) 140 } 141 } 142 sortByPath(values) 143 return values 144 } 145 146 func rootRequested(confKeys []string) bool { 147 return len(confKeys) == 0 148 } 149 150 // outputJson will be used when the user requested "document" output via 151 // the "-d" commandline switch. 152 func (c *cmdGet) outputJson(conf interface{}) error { 153 bytes, err := json.MarshalIndent(conf, "", "\t") 154 if err != nil { 155 return err 156 } 157 158 fmt.Fprintln(Stdout, string(bytes)) 159 return nil 160 } 161 162 // outputList will be used when the user requested list output via the 163 // "-l" commandline switch. 164 func (x *cmdGet) outputList(conf map[string]interface{}) error { 165 if rootRequested(x.Positional.Keys) && len(conf) == 0 { 166 return fmt.Errorf("snap %q has no configuration", x.Positional.Snap) 167 } 168 169 w := tabWriter() 170 defer w.Flush() 171 172 fmt.Fprintf(w, "Key\tValue\n") 173 values := flattenConfig(conf, rootRequested(x.Positional.Keys)) 174 for _, v := range values { 175 fmt.Fprintf(w, "%s\t%v\n", v.Path, v.Value) 176 } 177 return nil 178 } 179 180 // outputDefault will be used when no commandline switch to override the 181 // output where used. The output follows the following rules: 182 // - a single key with a string value is printed directly 183 // - multiple keys are printed as a list to the terminal (if there is one) 184 // or as json if there is no terminal 185 // - the option "typed" is honored 186 func (x *cmdGet) outputDefault(conf map[string]interface{}, snapName string, confKeys []string) error { 187 if rootRequested(confKeys) && len(conf) == 0 { 188 return fmt.Errorf("snap %q has no configuration", snapName) 189 } 190 191 var confToPrint interface{} = conf 192 193 if len(confKeys) == 1 { 194 // if single key was requested, then just output the 195 // value unless it's a map, in which case it will be 196 // printed as a list below. 197 if _, ok := conf[confKeys[0]].(map[string]interface{}); !ok { 198 confToPrint = conf[confKeys[0]] 199 } 200 } 201 202 // conf looks like a map 203 if cfg, ok := confToPrint.(map[string]interface{}); ok { 204 if isStdinTTY { 205 return x.outputList(cfg) 206 } 207 208 // TODO: remove this conditional and the warning below 209 // after a transition period. 210 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`)) 211 return x.outputJson(confToPrint) 212 } 213 214 if s, ok := confToPrint.(string); ok && !x.Typed { 215 fmt.Fprintln(Stdout, s) 216 return nil 217 } 218 219 if confToPrint != nil || x.Typed { 220 return x.outputJson(confToPrint) 221 } 222 223 fmt.Fprintln(Stdout, "") 224 return nil 225 226 } 227 228 func (x *cmdGet) Execute(args []string) error { 229 if len(args) > 0 { 230 // TRANSLATORS: the %s is the list of extra arguments 231 return fmt.Errorf(i18n.G("too many arguments: %s"), strings.Join(args, " ")) 232 } 233 234 if x.Document && x.Typed { 235 return fmt.Errorf("cannot use -d and -t together") 236 } 237 238 if x.Document && x.List { 239 return fmt.Errorf("cannot use -d and -l together") 240 } 241 242 snapName := string(x.Positional.Snap) 243 confKeys := x.Positional.Keys 244 245 conf, err := x.client.Conf(snapName, confKeys) 246 if err != nil { 247 return err 248 } 249 250 switch { 251 case x.Document: 252 return x.outputJson(conf) 253 case x.List: 254 return x.outputList(conf) 255 default: 256 return x.outputDefault(conf, snapName, confKeys) 257 } 258 }