github.com/rigado/snapd@v2.42.5-go-mod+incompatible/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/features" 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 isNil := snapcfg == nil || len(*snapcfg) == 0 149 if err == state.ErrNoState { 150 if isNil { 151 // bail out early 152 return nil 153 } 154 config = make(map[string]*json.RawMessage, 1) 155 } else if err != nil { 156 return err 157 } 158 if isNil { 159 delete(config, snapName) 160 } else { 161 config[snapName] = snapcfg 162 } 163 st.Set("config", config) 164 return nil 165 } 166 167 // SaveRevisionConfig makes a copy of config -> snapSnape configuration into the versioned config. 168 // It doesn't do anything if there is no configuration for given snap in the state. 169 // The caller is responsible for locking the state. 170 func SaveRevisionConfig(st *state.State, snapName string, rev snap.Revision) error { 171 var config map[string]*json.RawMessage // snap => configuration 172 var revisionConfig map[string]map[string]*json.RawMessage // snap => revision => configuration 173 174 // Get current configuration of the snap from state 175 err := st.Get("config", &config) 176 if err == state.ErrNoState { 177 return nil 178 } else if err != nil { 179 return fmt.Errorf("internal error: cannot unmarshal configuration: %v", err) 180 } 181 snapcfg, ok := config[snapName] 182 if !ok { 183 return nil 184 } 185 186 err = st.Get("revision-config", &revisionConfig) 187 if err == state.ErrNoState { 188 revisionConfig = make(map[string]map[string]*json.RawMessage) 189 } else if err != nil { 190 return err 191 } 192 cfgs := revisionConfig[snapName] 193 if cfgs == nil { 194 cfgs = make(map[string]*json.RawMessage) 195 } 196 cfgs[rev.String()] = snapcfg 197 revisionConfig[snapName] = cfgs 198 st.Set("revision-config", revisionConfig) 199 return nil 200 } 201 202 // RestoreRevisionConfig restores a given revision of snap configuration into config -> snapName. 203 // If no configuration exists for given revision it does nothing (no error). 204 // The caller is responsible for locking the state. 205 func RestoreRevisionConfig(st *state.State, snapName string, rev snap.Revision) error { 206 var config map[string]*json.RawMessage // snap => configuration 207 var revisionConfig map[string]map[string]*json.RawMessage // snap => revision => configuration 208 209 err := st.Get("revision-config", &revisionConfig) 210 if err == state.ErrNoState { 211 return nil 212 } else if err != nil { 213 return fmt.Errorf("internal error: cannot unmarshal revision-config: %v", err) 214 } 215 216 err = st.Get("config", &config) 217 if err == state.ErrNoState { 218 config = make(map[string]*json.RawMessage) 219 } else if err != nil { 220 return fmt.Errorf("internal error: cannot unmarshal configuration: %v", err) 221 } 222 223 if cfg, ok := revisionConfig[snapName]; ok { 224 if revCfg, ok := cfg[rev.String()]; ok { 225 config[snapName] = revCfg 226 st.Set("config", config) 227 } 228 } 229 230 return nil 231 } 232 233 // DiscardRevisionConfig removes configuration snapshot of given snap/revision. 234 // If no configuration exists for given revision it does nothing (no error). 235 // The caller is responsible for locking the state. 236 func DiscardRevisionConfig(st *state.State, snapName string, rev snap.Revision) error { 237 var revisionConfig map[string]map[string]*json.RawMessage // snap => revision => configuration 238 err := st.Get("revision-config", &revisionConfig) 239 if err == state.ErrNoState { 240 return nil 241 } else if err != nil { 242 return fmt.Errorf("internal error: cannot unmarshal revision-config: %v", err) 243 } 244 245 if revCfgs, ok := revisionConfig[snapName]; ok { 246 delete(revCfgs, rev.String()) 247 if len(revCfgs) == 0 { 248 delete(revisionConfig, snapName) 249 } else { 250 revisionConfig[snapName] = revCfgs 251 } 252 st.Set("revision-config", revisionConfig) 253 } 254 return nil 255 } 256 257 // DeleteSnapConfig removed configuration of given snap from the state. 258 func DeleteSnapConfig(st *state.State, snapName string) error { 259 var config map[string]map[string]*json.RawMessage // snap => key => value 260 261 err := st.Get("config", &config) 262 if err == state.ErrNoState { 263 return nil 264 } else if err != nil { 265 return fmt.Errorf("internal error: cannot unmarshal configuration: %v", err) 266 } 267 if _, ok := config[snapName]; ok { 268 delete(config, snapName) 269 st.Set("config", config) 270 } 271 return nil 272 } 273 274 // Conf is an interface describing both state and transaction. 275 type Conf interface { 276 Get(snapName, key string, result interface{}) error 277 Set(snapName, key string, value interface{}) error 278 Changes() []string 279 State() *state.State 280 } 281 282 // GetFeatureFlag returns the value of a given feature flag. 283 func GetFeatureFlag(tr Conf, feature features.SnapdFeature) (bool, error) { 284 var isEnabled interface{} 285 snapName, confName := feature.ConfigOption() 286 if err := tr.Get(snapName, confName, &isEnabled); err != nil && !IsNoOption(err) { 287 return false, err 288 } 289 switch isEnabled { 290 case true, "true": 291 return true, nil 292 case false, "false": 293 return false, nil 294 case nil, "": 295 return feature.IsEnabledWhenUnset(), nil 296 } 297 return false, fmt.Errorf("%s can only be set to 'true' or 'false', got %q", feature, isEnabled) 298 }