github.com/meulengracht/snapd@v0.0.0-20210719210640-8bde69bcc84e/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 "sort" 28 "strings" 29 30 "github.com/snapcore/snapd/jsonutil" 31 "github.com/snapcore/snapd/overlord/state" 32 "github.com/snapcore/snapd/snap" 33 ) 34 35 var validKey = regexp.MustCompile("^(?:[a-z0-9]+-?)*[a-z](?:-?[a-z0-9])*$") 36 37 func ParseKey(key string) (subkeys []string, err error) { 38 if key == "" { 39 return []string{}, nil 40 } 41 subkeys = strings.Split(key, ".") 42 for _, subkey := range subkeys { 43 if !validKey.MatchString(subkey) { 44 return nil, fmt.Errorf("invalid option name: %q", subkey) 45 } 46 } 47 return subkeys, nil 48 } 49 50 func purgeNulls(config interface{}) interface{} { 51 switch config := config.(type) { 52 // map of json raw messages is the starting point for purgeNulls, this is the configuration we receive 53 case map[string]*json.RawMessage: 54 for k, v := range config { 55 if cfg := purgeNulls(v); cfg != nil { 56 config[k] = cfg.(*json.RawMessage) 57 } else { 58 delete(config, k) 59 } 60 } 61 case map[string]interface{}: 62 for k, v := range config { 63 if cfg := purgeNulls(v); cfg != nil { 64 config[k] = cfg 65 } else { 66 delete(config, k) 67 } 68 } 69 case *json.RawMessage: 70 if config == nil { 71 return nil 72 } 73 var configm interface{} 74 if err := jsonutil.DecodeWithNumber(bytes.NewReader(*config), &configm); err != nil { 75 panic(fmt.Errorf("internal error: cannot unmarshal configuration: %v", err)) 76 } 77 if cfg := purgeNulls(configm); cfg != nil { 78 return jsonRaw(cfg) 79 } 80 return nil 81 } 82 return config 83 } 84 85 func PatchConfig(snapName string, subkeys []string, pos int, config interface{}, value *json.RawMessage) (interface{}, error) { 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 // preserve the invariant that if PatchConfig is 104 // passed a map[string]interface{} it is not nil. 105 // If the value was set to null in the same 106 // transaction use (interface{})(nil) which is handled 107 // by the first case here. 108 // (see LP: #1920773) 109 var cfg interface{} 110 if configm != nil { 111 cfg = configm 112 } 113 result, err := PatchConfig(snapName, subkeys, pos, cfg, value) 114 if err != nil { 115 return nil, err 116 } 117 118 // PatchConfig may have recreated higher level element that was previously set to null in same 119 // transaction; returning the result for PatchConfig rather than just configm ensures we do 120 // support cases where a previously unset path is set back. 121 return jsonRaw(result), nil 122 123 case map[string]interface{}: 124 // Update map to apply against pristine on commit. 125 if pos+1 == len(subkeys) { 126 config[subkeys[pos]] = value 127 return config, nil 128 } else { 129 result, err := PatchConfig(snapName, subkeys, pos+1, config[subkeys[pos]], value) 130 if err != nil { 131 return nil, err 132 } 133 config[subkeys[pos]] = result 134 return config, nil 135 } 136 } 137 return nil, fmt.Errorf("internal error: unexpected configuration type %T", config) 138 } 139 140 // GetSnapConfig retrieves the raw configuration of a given snap. 141 func GetSnapConfig(st *state.State, snapName string) (*json.RawMessage, error) { 142 var config map[string]*json.RawMessage 143 err := st.Get("config", &config) 144 if err == state.ErrNoState { 145 return nil, nil 146 } 147 if err != nil { 148 return nil, err 149 } 150 snapcfg, ok := config[snapName] 151 if !ok { 152 return nil, nil 153 } 154 return snapcfg, nil 155 } 156 157 // SetSnapConfig replaces the configuration of a given snap. 158 func SetSnapConfig(st *state.State, snapName string, snapcfg *json.RawMessage) error { 159 var config map[string]*json.RawMessage 160 err := st.Get("config", &config) 161 // empty nil snapcfg should be an empty message, but deal with "null" as well. 162 isNil := snapcfg == nil || len(*snapcfg) == 0 || bytes.Compare(*snapcfg, []byte("null")) == 0 163 if err == state.ErrNoState { 164 if isNil { 165 // bail out early 166 return nil 167 } 168 config = make(map[string]*json.RawMessage, 1) 169 } else if err != nil { 170 return err 171 } 172 if isNil { 173 delete(config, snapName) 174 } else { 175 config[snapName] = snapcfg 176 } 177 st.Set("config", config) 178 return nil 179 } 180 181 // SaveRevisionConfig makes a copy of config -> snapSnape configuration into the versioned config. 182 // It doesn't do anything if there is no configuration for given snap in the state. 183 // The caller is responsible for locking the state. 184 func SaveRevisionConfig(st *state.State, snapName string, rev snap.Revision) error { 185 var config map[string]*json.RawMessage // snap => configuration 186 var revisionConfig map[string]map[string]*json.RawMessage // snap => revision => configuration 187 188 // Get current configuration of the snap from state 189 err := st.Get("config", &config) 190 if err == state.ErrNoState { 191 return nil 192 } else if err != nil { 193 return fmt.Errorf("internal error: cannot unmarshal configuration: %v", err) 194 } 195 snapcfg, ok := config[snapName] 196 if !ok { 197 return nil 198 } 199 200 err = st.Get("revision-config", &revisionConfig) 201 if err == state.ErrNoState { 202 revisionConfig = make(map[string]map[string]*json.RawMessage) 203 } else if err != nil { 204 return err 205 } 206 cfgs := revisionConfig[snapName] 207 if cfgs == nil { 208 cfgs = make(map[string]*json.RawMessage) 209 } 210 cfgs[rev.String()] = snapcfg 211 revisionConfig[snapName] = cfgs 212 st.Set("revision-config", revisionConfig) 213 return nil 214 } 215 216 // RestoreRevisionConfig restores a given revision of snap configuration into config -> snapName. 217 // If no configuration exists for given revision it does nothing (no error). 218 // The caller is responsible for locking the state. 219 func RestoreRevisionConfig(st *state.State, snapName string, rev snap.Revision) error { 220 var config map[string]*json.RawMessage // snap => configuration 221 var revisionConfig map[string]map[string]*json.RawMessage // snap => revision => configuration 222 223 err := st.Get("revision-config", &revisionConfig) 224 if err == state.ErrNoState { 225 return nil 226 } else if err != nil { 227 return fmt.Errorf("internal error: cannot unmarshal revision-config: %v", err) 228 } 229 230 err = st.Get("config", &config) 231 if err == state.ErrNoState { 232 config = make(map[string]*json.RawMessage) 233 } else if err != nil { 234 return fmt.Errorf("internal error: cannot unmarshal configuration: %v", err) 235 } 236 237 if cfg, ok := revisionConfig[snapName]; ok { 238 if revCfg, ok := cfg[rev.String()]; ok { 239 config[snapName] = revCfg 240 st.Set("config", config) 241 } 242 } 243 244 return nil 245 } 246 247 // DiscardRevisionConfig removes configuration snapshot of given snap/revision. 248 // If no configuration exists for given revision it does nothing (no error). 249 // The caller is responsible for locking the state. 250 func DiscardRevisionConfig(st *state.State, snapName string, rev snap.Revision) error { 251 var revisionConfig map[string]map[string]*json.RawMessage // snap => revision => configuration 252 err := st.Get("revision-config", &revisionConfig) 253 if err == state.ErrNoState { 254 return nil 255 } else if err != nil { 256 return fmt.Errorf("internal error: cannot unmarshal revision-config: %v", err) 257 } 258 259 if revCfgs, ok := revisionConfig[snapName]; ok { 260 delete(revCfgs, rev.String()) 261 if len(revCfgs) == 0 { 262 delete(revisionConfig, snapName) 263 } else { 264 revisionConfig[snapName] = revCfgs 265 } 266 st.Set("revision-config", revisionConfig) 267 } 268 return nil 269 } 270 271 // DeleteSnapConfig removed configuration of given snap from the state. 272 func DeleteSnapConfig(st *state.State, snapName string) error { 273 var config map[string]map[string]*json.RawMessage // snap => key => value 274 275 err := st.Get("config", &config) 276 if err == state.ErrNoState { 277 return nil 278 } else if err != nil { 279 return fmt.Errorf("internal error: cannot unmarshal configuration: %v", err) 280 } 281 if _, ok := config[snapName]; ok { 282 delete(config, snapName) 283 st.Set("config", config) 284 } 285 return nil 286 } 287 288 // Conf is an interface describing both state and transaction. 289 type Conf interface { 290 Get(snapName, key string, result interface{}) error 291 GetMaybe(snapName, key string, result interface{}) error 292 GetPristine(snapName, key string, result interface{}) error 293 Set(snapName, key string, value interface{}) error 294 Changes() []string 295 State() *state.State 296 } 297 298 // ConfGetter is an interface for reading of config values. 299 type ConfGetter interface { 300 Get(snapName, key string, result interface{}) error 301 GetMaybe(snapName, key string, result interface{}) error 302 GetPristine(snapName, key string, result interface{}) error 303 } 304 305 // Patch sets values in cfg for the provided snap's configuration 306 // based on patch. 307 // patch keys can be dotted as the key argument to Set. 308 // The patch is applied according to the order of its keys sorted by depth, 309 // with top keys sorted first. 310 func Patch(cfg Conf, snapName string, patch map[string]interface{}) error { 311 patchKeys := sortPatchKeysByDepth(patch) 312 for _, key := range patchKeys { 313 if err := cfg.Set(snapName, key, patch[key]); err != nil { 314 return err 315 } 316 } 317 return nil 318 } 319 320 func sortPatchKeysByDepth(patch map[string]interface{}) []string { 321 if len(patch) == 0 { 322 return nil 323 } 324 depths := make(map[string]int, len(patch)) 325 keys := make([]string, 0, len(patch)) 326 for k := range patch { 327 depths[k] = strings.Count(k, ".") 328 keys = append(keys, k) 329 } 330 331 sort.Slice(keys, func(i, j int) bool { 332 return depths[keys[i]] < depths[keys[j]] 333 }) 334 return keys 335 }