github.com/ubuntu-core/snappy@v0.0.0-20210827154228-9e584df982bb/cmd/snap/cmd_set.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 "fmt" 24 "strings" 25 26 "github.com/jessevdk/go-flags" 27 28 "github.com/snapcore/snapd/i18n" 29 "github.com/snapcore/snapd/jsonutil" 30 ) 31 32 var shortSetHelp = i18n.G("Change configuration options") 33 var longSetHelp = i18n.G(` 34 The set command changes the provided configuration options as requested. 35 36 $ snap set snap-name username=frank password=$PASSWORD 37 38 All configuration changes are persisted at once, and only after the 39 snap's configuration hook returns successfully. 40 41 Nested values may be modified via a dotted path: 42 43 $ snap set snap-name author.name=frank 44 45 Configuration option may be unset with exclamation mark: 46 $ snap set snap-name author! 47 `) 48 49 type cmdSet struct { 50 waitMixin 51 Positional struct { 52 Snap installedSnapName 53 ConfValues []string `required:"1"` 54 } `positional-args:"yes" required:"yes"` 55 56 Typed bool `short:"t"` 57 String bool `short:"s"` 58 } 59 60 func init() { 61 addCommand("set", shortSetHelp, longSetHelp, func() flags.Commander { return &cmdSet{} }, 62 waitDescs.also(map[string]string{ 63 // TRANSLATORS: This should not start with a lowercase letter. 64 "t": i18n.G("Parse the value strictly as JSON document"), 65 // TRANSLATORS: This should not start with a lowercase letter. 66 "s": i18n.G("Parse the value as a string"), 67 }), []argDesc{ 68 { 69 name: "<snap>", 70 // TRANSLATORS: This should not start with a lowercase letter. 71 desc: i18n.G("The snap to configure (e.g. hello-world)"), 72 }, { 73 // TRANSLATORS: This needs to begin with < and end with > 74 name: i18n.G("<conf value>"), 75 // TRANSLATORS: This should not start with a lowercase letter. 76 desc: i18n.G("Set (key=value) or unset (key!) configuration value"), 77 }, 78 }) 79 } 80 81 func (x *cmdSet) Execute(args []string) error { 82 if x.String && x.Typed { 83 return fmt.Errorf(i18n.G("cannot use -t and -s together")) 84 } 85 86 patchValues := make(map[string]interface{}) 87 for _, patchValue := range x.Positional.ConfValues { 88 parts := strings.SplitN(patchValue, "=", 2) 89 if len(parts) == 1 && strings.HasSuffix(patchValue, "!") { 90 patchValues[strings.TrimSuffix(patchValue, "!")] = nil 91 continue 92 } 93 if len(parts) != 2 { 94 return fmt.Errorf(i18n.G("invalid configuration: %q (want key=value)"), patchValue) 95 } 96 97 if x.String { 98 patchValues[parts[0]] = parts[1] 99 } else { 100 var value interface{} 101 if err := jsonutil.DecodeWithNumber(strings.NewReader(parts[1]), &value); err != nil { 102 if x.Typed { 103 return fmt.Errorf("failed to parse JSON: %w", err) 104 } 105 106 // Not valid JSON-- just save the string as-is. 107 patchValues[parts[0]] = parts[1] 108 } else { 109 patchValues[parts[0]] = value 110 } 111 } 112 } 113 114 snapName := string(x.Positional.Snap) 115 id, err := x.client.SetConf(snapName, patchValues) 116 if err != nil { 117 return err 118 } 119 120 if _, err := x.wait(id); err != nil { 121 if err == noWait { 122 return nil 123 } 124 return err 125 } 126 127 return nil 128 }