github.com/bugraaydogar/snapd@v0.0.0-20210315170335-8c70bb858939/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 87 switch config := config.(type) { 88 case nil: 89 // Missing update map. Create and nest final value under it. 90 configm := make(map[string]interface{}) 91 _, err := PatchConfig(snapName, subkeys, pos, configm, value) 92 if err != nil { 93 return nil, err 94 } 95 return configm, nil 96 97 case *json.RawMessage: 98 // Raw replaces pristine on commit. Unpack, update, and repack. 99 var configm map[string]interface{} 100 101 if err := jsonutil.DecodeWithNumber(bytes.NewReader(*config), &configm); err != nil { 102 return nil, fmt.Errorf("snap %q option %q is not a map", snapName, strings.Join(subkeys[:pos], ".")) 103 } 104 _, err := PatchConfig(snapName, subkeys, pos, configm, value) 105 if err != nil { 106 return nil, err 107 } 108 return jsonRaw(configm), nil 109 110 case map[string]interface{}: 111 // Update map to apply against pristine on commit. 112 if pos+1 == len(subkeys) { 113 config[subkeys[pos]] = value 114 return config, nil 115 } else { 116 result, err := PatchConfig(snapName, subkeys, pos+1, config[subkeys[pos]], value) 117 if err != nil { 118 return nil, err 119 } 120 config[subkeys[pos]] = result 121 return config, nil 122 } 123 } 124 return nil, fmt.Errorf("internal error: unexpected configuration type %T", config) 125 } 126 127 // GetSnapConfig retrieves the raw configuration of a given snap. 128 func GetSnapConfig(st *state.State, snapName string) (*json.RawMessage, error) { 129 var config map[string]*json.RawMessage 130 err := st.Get("config", &config) 131 if err == state.ErrNoState { 132 return nil, nil 133 } 134 if err != nil { 135 return nil, err 136 } 137 snapcfg, ok := config[snapName] 138 if !ok { 139 return nil, nil 140 } 141 return snapcfg, nil 142 } 143 144 // SetSnapConfig replaces the configuration of a given snap. 145 func SetSnapConfig(st *state.State, snapName string, snapcfg *json.RawMessage) error { 146 var config map[string]*json.RawMessage 147 err := st.Get("config", &config) 148 // empty nil snapcfg should be an empty message, but deal with "null" as well. 149 isNil := snapcfg == nil || len(*snapcfg) == 0 || bytes.Compare(*snapcfg, []byte("null")) == 0 150 if err == state.ErrNoState { 151 if isNil { 152 // bail out early 153 return nil 154 } 155 config = make(map[string]*json.RawMessage, 1) 156 } else if err != nil { 157 return err 158 } 159 if isNil { 160 delete(config, snapName) 161 } else { 162 config[snapName] = snapcfg 163 } 164 st.Set("config", config) 165 return nil 166 } 167 168 // SaveRevisionConfig makes a copy of config -> snapSnape configuration into the versioned config. 169 // It doesn't do anything if there is no configuration for given snap in the state. 170 // The caller is responsible for locking the state. 171 func SaveRevisionConfig(st *state.State, snapName string, rev snap.Revision) error { 172 var config map[string]*json.RawMessage // snap => configuration 173 var revisionConfig map[string]map[string]*json.RawMessage // snap => revision => configuration 174 175 // Get current configuration of the snap from state 176 err := st.Get("config", &config) 177 if err == state.ErrNoState { 178 return nil 179 } else if err != nil { 180 return fmt.Errorf("internal error: cannot unmarshal configuration: %v", err) 181 } 182 snapcfg, ok := config[snapName] 183 if !ok { 184 return nil 185 } 186 187 err = st.Get("revision-config", &revisionConfig) 188 if err == state.ErrNoState { 189 revisionConfig = make(map[string]map[string]*json.RawMessage) 190 } else if err != nil { 191 return err 192 } 193 cfgs := revisionConfig[snapName] 194 if cfgs == nil { 195 cfgs = make(map[string]*json.RawMessage) 196 } 197 cfgs[rev.String()] = snapcfg 198 revisionConfig[snapName] = cfgs 199 st.Set("revision-config", revisionConfig) 200 return nil 201 } 202 203 // RestoreRevisionConfig restores a given revision of snap configuration into config -> snapName. 204 // If no configuration exists for given revision it does nothing (no error). 205 // The caller is responsible for locking the state. 206 func RestoreRevisionConfig(st *state.State, snapName string, rev snap.Revision) error { 207 var config map[string]*json.RawMessage // snap => configuration 208 var revisionConfig map[string]map[string]*json.RawMessage // snap => revision => configuration 209 210 err := st.Get("revision-config", &revisionConfig) 211 if err == state.ErrNoState { 212 return nil 213 } else if err != nil { 214 return fmt.Errorf("internal error: cannot unmarshal revision-config: %v", err) 215 } 216 217 err = st.Get("config", &config) 218 if err == state.ErrNoState { 219 config = make(map[string]*json.RawMessage) 220 } else if err != nil { 221 return fmt.Errorf("internal error: cannot unmarshal configuration: %v", err) 222 } 223 224 if cfg, ok := revisionConfig[snapName]; ok { 225 if revCfg, ok := cfg[rev.String()]; ok { 226 config[snapName] = revCfg 227 st.Set("config", config) 228 } 229 } 230 231 return nil 232 } 233 234 // DiscardRevisionConfig removes configuration snapshot of given snap/revision. 235 // If no configuration exists for given revision it does nothing (no error). 236 // The caller is responsible for locking the state. 237 func DiscardRevisionConfig(st *state.State, snapName string, rev snap.Revision) error { 238 var revisionConfig map[string]map[string]*json.RawMessage // snap => revision => configuration 239 err := st.Get("revision-config", &revisionConfig) 240 if err == state.ErrNoState { 241 return nil 242 } else if err != nil { 243 return fmt.Errorf("internal error: cannot unmarshal revision-config: %v", err) 244 } 245 246 if revCfgs, ok := revisionConfig[snapName]; ok { 247 delete(revCfgs, rev.String()) 248 if len(revCfgs) == 0 { 249 delete(revisionConfig, snapName) 250 } else { 251 revisionConfig[snapName] = revCfgs 252 } 253 st.Set("revision-config", revisionConfig) 254 } 255 return nil 256 } 257 258 // DeleteSnapConfig removed configuration of given snap from the state. 259 func DeleteSnapConfig(st *state.State, snapName string) error { 260 var config map[string]map[string]*json.RawMessage // snap => key => value 261 262 err := st.Get("config", &config) 263 if err == state.ErrNoState { 264 return nil 265 } else if err != nil { 266 return fmt.Errorf("internal error: cannot unmarshal configuration: %v", err) 267 } 268 if _, ok := config[snapName]; ok { 269 delete(config, snapName) 270 st.Set("config", config) 271 } 272 return nil 273 } 274 275 // Conf is an interface describing both state and transaction. 276 type Conf interface { 277 Get(snapName, key string, result interface{}) error 278 GetMaybe(snapName, key string, result interface{}) error 279 GetPristine(snapName, key string, result interface{}) error 280 Set(snapName, key string, value interface{}) error 281 Changes() []string 282 State() *state.State 283 } 284 285 // ConfGetter is an interface for reading of config values. 286 type ConfGetter interface { 287 Get(snapName, key string, result interface{}) error 288 GetMaybe(snapName, key string, result interface{}) error 289 } 290 291 // Patch sets values in cfg for the provided snap's configuration 292 // based on patch. 293 // patch keys can be dotted as the key argument to Set. 294 // The patch is applied according to the order of its keys sorted by depth, 295 // with top keys sorted first. 296 func Patch(cfg Conf, snapName string, patch map[string]interface{}) error { 297 patchKeys := sortPatchKeysByDepth(patch) 298 for _, key := range patchKeys { 299 if err := cfg.Set(snapName, key, patch[key]); err != nil { 300 return err 301 } 302 } 303 return nil 304 } 305 306 func sortPatchKeysByDepth(patch map[string]interface{}) []string { 307 if len(patch) == 0 { 308 return nil 309 } 310 depths := make(map[string]int, len(patch)) 311 keys := make([]string, 0, len(patch)) 312 for k := range patch { 313 depths[k] = strings.Count(k, ".") 314 keys = append(keys, k) 315 } 316 317 sort.Slice(keys, func(i, j int) bool { 318 return depths[keys[i]] < depths[keys[j]] 319 }) 320 return keys 321 }