github.com/hugh712/snapd@v0.0.0-20200910133618-1a99902bd583/overlord/configstate/config/helpers.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2017 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 config 21 22 import ( 23 "bytes" 24 "encoding/json" 25 "fmt" 26 "regexp" 27 "strings" 28 29 "github.com/snapcore/snapd/jsonutil" 30 "github.com/snapcore/snapd/overlord/state" 31 "github.com/snapcore/snapd/snap" 32 ) 33 34 var validKey = regexp.MustCompile("^(?:[a-z0-9]+-?)*[a-z](?:-?[a-z0-9])*$") 35 36 func ParseKey(key string) (subkeys []string, err error) { 37 if key == "" { 38 return []string{}, nil 39 } 40 subkeys = strings.Split(key, ".") 41 for _, subkey := range subkeys { 42 if !validKey.MatchString(subkey) { 43 return nil, fmt.Errorf("invalid option name: %q", subkey) 44 } 45 } 46 return subkeys, nil 47 } 48 49 func purgeNulls(config interface{}) interface{} { 50 switch config := config.(type) { 51 // map of json raw messages is the starting point for purgeNulls, this is the configuration we receive 52 case map[string]*json.RawMessage: 53 for k, v := range config { 54 if cfg := purgeNulls(v); cfg != nil { 55 config[k] = cfg.(*json.RawMessage) 56 } else { 57 delete(config, k) 58 } 59 } 60 case map[string]interface{}: 61 for k, v := range config { 62 if cfg := purgeNulls(v); cfg != nil { 63 config[k] = cfg 64 } else { 65 delete(config, k) 66 } 67 } 68 case *json.RawMessage: 69 if config == nil { 70 return nil 71 } 72 var configm interface{} 73 if err := jsonutil.DecodeWithNumber(bytes.NewReader(*config), &configm); err != nil { 74 panic(fmt.Errorf("internal error: cannot unmarshal configuration: %v", err)) 75 } 76 if cfg := purgeNulls(configm); cfg != nil { 77 return jsonRaw(cfg) 78 } 79 return nil 80 } 81 return config 82 } 83 84 func PatchConfig(snapName string, subkeys []string, pos int, config interface{}, value *json.RawMessage) (interface{}, error) { 85 86 switch config := config.(type) { 87 case nil: 88 // Missing update map. Create and nest final value under it. 89 configm := make(map[string]interface{}) 90 _, err := PatchConfig(snapName, subkeys, pos, configm, value) 91 if err != nil { 92 return nil, err 93 } 94 return configm, nil 95 96 case *json.RawMessage: 97 // Raw replaces pristine on commit. Unpack, update, and repack. 98 var configm map[string]interface{} 99 100 if err := jsonutil.DecodeWithNumber(bytes.NewReader(*config), &configm); err != nil { 101 return nil, fmt.Errorf("snap %q option %q is not a map", snapName, strings.Join(subkeys[:pos], ".")) 102 } 103 _, err := PatchConfig(snapName, subkeys, pos, configm, value) 104 if err != nil { 105 return nil, err 106 } 107 return jsonRaw(configm), nil 108 109 case map[string]interface{}: 110 // Update map to apply against pristine on commit. 111 if pos+1 == len(subkeys) { 112 config[subkeys[pos]] = value 113 return config, nil 114 } else { 115 result, err := PatchConfig(snapName, subkeys, pos+1, config[subkeys[pos]], value) 116 if err != nil { 117 return nil, err 118 } 119 config[subkeys[pos]] = result 120 return config, nil 121 } 122 } 123 return nil, fmt.Errorf("internal error: unexpected configuration type %T", config) 124 } 125 126 // GetSnapConfig retrieves the raw configuration of a given snap. 127 func GetSnapConfig(st *state.State, snapName string) (*json.RawMessage, error) { 128 var config map[string]*json.RawMessage 129 err := st.Get("config", &config) 130 if err == state.ErrNoState { 131 return nil, nil 132 } 133 if err != nil { 134 return nil, err 135 } 136 snapcfg, ok := config[snapName] 137 if !ok { 138 return nil, nil 139 } 140 return snapcfg, nil 141 } 142 143 // SetSnapConfig replaces the configuration of a given snap. 144 func SetSnapConfig(st *state.State, snapName string, snapcfg *json.RawMessage) error { 145 var config map[string]*json.RawMessage 146 err := st.Get("config", &config) 147 isNil := snapcfg == nil || len(*snapcfg) == 0 148 if err == state.ErrNoState { 149 if isNil { 150 // bail out early 151 return nil 152 } 153 config = make(map[string]*json.RawMessage, 1) 154 } else if err != nil { 155 return err 156 } 157 if isNil { 158 delete(config, snapName) 159 } else { 160 config[snapName] = snapcfg 161 } 162 st.Set("config", config) 163 return nil 164 } 165 166 // SaveRevisionConfig makes a copy of config -> snapSnape configuration into the versioned config. 167 // It doesn't do anything if there is no configuration for given snap in the state. 168 // The caller is responsible for locking the state. 169 func SaveRevisionConfig(st *state.State, snapName string, rev snap.Revision) error { 170 var config map[string]*json.RawMessage // snap => configuration 171 var revisionConfig map[string]map[string]*json.RawMessage // snap => revision => configuration 172 173 // Get current configuration of the snap from state 174 err := st.Get("config", &config) 175 if err == state.ErrNoState { 176 return nil 177 } else if err != nil { 178 return fmt.Errorf("internal error: cannot unmarshal configuration: %v", err) 179 } 180 snapcfg, ok := config[snapName] 181 if !ok { 182 return nil 183 } 184 185 err = st.Get("revision-config", &revisionConfig) 186 if err == state.ErrNoState { 187 revisionConfig = make(map[string]map[string]*json.RawMessage) 188 } else if err != nil { 189 return err 190 } 191 cfgs := revisionConfig[snapName] 192 if cfgs == nil { 193 cfgs = make(map[string]*json.RawMessage) 194 } 195 cfgs[rev.String()] = snapcfg 196 revisionConfig[snapName] = cfgs 197 st.Set("revision-config", revisionConfig) 198 return nil 199 } 200 201 // RestoreRevisionConfig restores a given revision of snap configuration into config -> snapName. 202 // If no configuration exists for given revision it does nothing (no error). 203 // The caller is responsible for locking the state. 204 func RestoreRevisionConfig(st *state.State, snapName string, rev snap.Revision) error { 205 var config map[string]*json.RawMessage // snap => configuration 206 var revisionConfig map[string]map[string]*json.RawMessage // snap => revision => configuration 207 208 err := st.Get("revision-config", &revisionConfig) 209 if err == state.ErrNoState { 210 return nil 211 } else if err != nil { 212 return fmt.Errorf("internal error: cannot unmarshal revision-config: %v", err) 213 } 214 215 err = st.Get("config", &config) 216 if err == state.ErrNoState { 217 config = make(map[string]*json.RawMessage) 218 } else if err != nil { 219 return fmt.Errorf("internal error: cannot unmarshal configuration: %v", err) 220 } 221 222 if cfg, ok := revisionConfig[snapName]; ok { 223 if revCfg, ok := cfg[rev.String()]; ok { 224 config[snapName] = revCfg 225 st.Set("config", config) 226 } 227 } 228 229 return nil 230 } 231 232 // DiscardRevisionConfig removes configuration snapshot of given snap/revision. 233 // If no configuration exists for given revision it does nothing (no error). 234 // The caller is responsible for locking the state. 235 func DiscardRevisionConfig(st *state.State, snapName string, rev snap.Revision) error { 236 var revisionConfig map[string]map[string]*json.RawMessage // snap => revision => configuration 237 err := st.Get("revision-config", &revisionConfig) 238 if err == state.ErrNoState { 239 return nil 240 } else if err != nil { 241 return fmt.Errorf("internal error: cannot unmarshal revision-config: %v", err) 242 } 243 244 if revCfgs, ok := revisionConfig[snapName]; ok { 245 delete(revCfgs, rev.String()) 246 if len(revCfgs) == 0 { 247 delete(revisionConfig, snapName) 248 } else { 249 revisionConfig[snapName] = revCfgs 250 } 251 st.Set("revision-config", revisionConfig) 252 } 253 return nil 254 } 255 256 // DeleteSnapConfig removed configuration of given snap from the state. 257 func DeleteSnapConfig(st *state.State, snapName string) error { 258 var config map[string]map[string]*json.RawMessage // snap => key => value 259 260 err := st.Get("config", &config) 261 if err == state.ErrNoState { 262 return nil 263 } else if err != nil { 264 return fmt.Errorf("internal error: cannot unmarshal configuration: %v", err) 265 } 266 if _, ok := config[snapName]; ok { 267 delete(config, snapName) 268 st.Set("config", config) 269 } 270 return nil 271 } 272 273 // Conf is an interface describing both state and transaction. 274 type Conf interface { 275 Get(snapName, key string, result interface{}) error 276 GetMaybe(snapName, key string, result interface{}) error 277 GetPristine(snapName, key string, result interface{}) error 278 Set(snapName, key string, value interface{}) error 279 Changes() []string 280 State() *state.State 281 } 282 283 // ConfGetter is an interface for reading of config values. 284 type ConfGetter interface { 285 Get(snapName, key string, result interface{}) error 286 GetMaybe(snapName, key string, result interface{}) error 287 }