github.com/rigado/snapd@v2.42.5-go-mod+incompatible/overlord/configstate/config/helpers_test.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 config_test 21 22 import ( 23 "bytes" 24 "encoding/json" 25 26 . "gopkg.in/check.v1" 27 28 "github.com/snapcore/snapd/features" 29 "github.com/snapcore/snapd/jsonutil" 30 "github.com/snapcore/snapd/overlord/configstate/config" 31 "github.com/snapcore/snapd/overlord/state" 32 "github.com/snapcore/snapd/snap" 33 ) 34 35 type configHelpersSuite struct { 36 state *state.State 37 } 38 39 var _ = Suite(&configHelpersSuite{}) 40 41 func (s *configHelpersSuite) SetUpTest(c *C) { 42 s.state = state.New(nil) 43 } 44 45 func (s *configHelpersSuite) TestConfigSnapshot(c *C) { 46 s.state.Lock() 47 defer s.state.Unlock() 48 49 tr := config.NewTransaction(s.state) 50 c.Assert(tr.Set("snap1", "foo", "a"), IsNil) 51 c.Assert(tr.Set("snap2", "bar", "q"), IsNil) 52 tr.Commit() 53 54 // store current config 55 c.Assert(config.SaveRevisionConfig(s.state, "snap1", snap.R(1)), IsNil) 56 c.Assert(config.SaveRevisionConfig(s.state, "snap2", snap.R(7)), IsNil) 57 58 var cfgsnapshot map[string]map[string]map[string]interface{} 59 c.Assert(s.state.Get("revision-config", &cfgsnapshot), IsNil) 60 c.Assert(cfgsnapshot, DeepEquals, map[string]map[string]map[string]interface{}{ 61 "snap1": {"1": {"foo": "a"}}, 62 "snap2": {"7": {"bar": "q"}}, 63 }) 64 65 c.Assert(cfgsnapshot["snap1"], NotNil) 66 67 // modify 'foo' config key 68 tr = config.NewTransaction(s.state) 69 c.Assert(tr.Set("snap1", "foo", "b"), IsNil) 70 tr.Commit() 71 72 // store current config 73 c.Assert(config.SaveRevisionConfig(s.state, "snap1", snap.R(2)), IsNil) 74 75 c.Assert(s.state.Get("revision-config", &cfgsnapshot), IsNil) 76 c.Assert(cfgsnapshot, DeepEquals, map[string]map[string]map[string]interface{}{ 77 "snap1": {"1": {"foo": "a"}, "2": {"foo": "b"}}, 78 "snap2": {"7": {"bar": "q"}}, 79 }) 80 81 var value string 82 83 // Restore first revision 84 c.Assert(config.RestoreRevisionConfig(s.state, "snap1", snap.R(1)), IsNil) 85 tr = config.NewTransaction(s.state) 86 c.Assert(tr.Get("snap1", "foo", &value), IsNil) 87 c.Check(value, Equals, "a") 88 89 // Restore second revision 90 c.Assert(config.RestoreRevisionConfig(s.state, "snap1", snap.R(2)), IsNil) 91 tr = config.NewTransaction(s.state) 92 c.Assert(tr.Get("snap1", "foo", &value), IsNil) 93 c.Check(value, Equals, "b") 94 } 95 96 func (s *configHelpersSuite) TestDiscardRevisionConfig(c *C) { 97 s.state.Lock() 98 defer s.state.Unlock() 99 100 tr := config.NewTransaction(s.state) 101 c.Assert(tr.Set("snap3", "foo", "a"), IsNil) 102 tr.Commit() 103 104 for i := 1; i <= 3; i++ { 105 c.Assert(config.SaveRevisionConfig(s.state, "snap3", snap.R(i)), IsNil) 106 } 107 108 var cfgsnapshot map[string]map[string]interface{} 109 c.Assert(s.state.Get("revision-config", &cfgsnapshot), IsNil) 110 c.Assert(cfgsnapshot["snap3"], NotNil) 111 c.Assert(cfgsnapshot["snap3"], HasLen, 3) 112 113 for i := 1; i <= 2; i++ { 114 c.Assert(config.DiscardRevisionConfig(s.state, "snap3", snap.R(i)), IsNil) 115 } 116 cfgsnapshot = nil 117 c.Assert(s.state.Get("revision-config", &cfgsnapshot), IsNil) 118 c.Assert(cfgsnapshot["snap3"], NotNil) 119 c.Assert(cfgsnapshot["snap3"], HasLen, 1) 120 121 // removing the last revision removes snap completely from the config map 122 cfgsnapshot = nil 123 c.Assert(config.DiscardRevisionConfig(s.state, "snap3", snap.R(3)), IsNil) 124 c.Assert(s.state.Get("revision-config", &cfgsnapshot), IsNil) 125 c.Assert(cfgsnapshot["snap3"], IsNil) 126 } 127 128 func (s *configHelpersSuite) TestConfigSnapshotNoConfigs(c *C) { 129 s.state.Lock() 130 defer s.state.Unlock() 131 132 // snap has no config in global state 133 c.Assert(config.SaveRevisionConfig(s.state, "snap1", snap.R(1)), IsNil) 134 135 // snap has no config in global state, but config is not nil 136 tr := config.NewTransaction(s.state) 137 c.Assert(tr.Set("snap2", "bar", "q"), IsNil) 138 tr.Commit() 139 c.Assert(config.SaveRevisionConfig(s.state, "snap1", snap.R(1)), IsNil) 140 141 // no configuration to restore in revision-config 142 c.Assert(config.RestoreRevisionConfig(s.state, "snap1", snap.R(1)), IsNil) 143 } 144 145 func (s *configHelpersSuite) TestSnapConfig(c *C) { 146 s.state.Lock() 147 defer s.state.Unlock() 148 149 empty1 := json.RawMessage(nil) 150 151 for _, emptyCfg := range []*json.RawMessage{nil, &empty1, {}} { 152 rawCfg, err := config.GetSnapConfig(s.state, "snap1") 153 c.Assert(err, IsNil) 154 c.Check(rawCfg, IsNil) 155 156 // can set to empty when empty and it's fine 157 c.Assert(config.SetSnapConfig(s.state, "snap1", emptyCfg), IsNil) 158 rawCfg, err = config.GetSnapConfig(s.state, "snap1") 159 c.Assert(err, IsNil) 160 c.Check(rawCfg, IsNil) 161 162 cfg := json.RawMessage(`{"foo":"bar"}`) 163 c.Assert(config.SetSnapConfig(s.state, "snap1", &cfg), IsNil) 164 165 // the set sets it 166 rawCfg, err = config.GetSnapConfig(s.state, "snap1") 167 c.Assert(err, IsNil) 168 c.Assert(rawCfg, NotNil) 169 c.Check(*rawCfg, DeepEquals, json.RawMessage(`{"foo":"bar"}`)) 170 171 // empty or nil clears it 172 c.Assert(config.SetSnapConfig(s.state, "snap1", emptyCfg), IsNil) 173 rawCfg, err = config.GetSnapConfig(s.state, "snap1") 174 c.Assert(err, IsNil) 175 c.Check(rawCfg, IsNil) 176 } 177 } 178 179 func (s *configHelpersSuite) TestGetFeatureFlag(c *C) { 180 s.state.Lock() 181 defer s.state.Unlock() 182 tr := config.NewTransaction(s.state) 183 184 // Feature flags have a value even if unset. 185 flag, err := config.GetFeatureFlag(tr, features.Layouts) 186 c.Assert(err, IsNil) 187 c.Check(flag, Equals, true) 188 189 // Feature flags can be disabled. 190 c.Assert(tr.Set("core", "experimental.layouts", "false"), IsNil) 191 flag, err = config.GetFeatureFlag(tr, features.Layouts) 192 c.Assert(err, IsNil) 193 c.Check(flag, Equals, false) 194 195 // Feature flags can be enabled. 196 c.Assert(tr.Set("core", "experimental.layouts", "true"), IsNil) 197 flag, err = config.GetFeatureFlag(tr, features.Layouts) 198 c.Assert(err, IsNil) 199 c.Check(flag, Equals, true) 200 201 // Feature flags must have a well-known value. 202 c.Assert(tr.Set("core", "experimental.layouts", "banana"), IsNil) 203 _, err = config.GetFeatureFlag(tr, features.Layouts) 204 c.Assert(err, ErrorMatches, `layouts can only be set to 'true' or 'false', got "banana"`) 205 } 206 207 func (s *configHelpersSuite) TestPatchInvalidConfig(c *C) { 208 s.state.Lock() 209 defer s.state.Unlock() 210 211 invalid := []string{} 212 value := json.RawMessage([]byte("[]")) 213 _, err := config.PatchConfig("snap1", []string{"foo"}, 0, invalid, &value) 214 c.Assert(err, ErrorMatches, `internal error: unexpected configuration type \[\]string`) 215 } 216 217 func (s *configHelpersSuite) TestPurgeNulls(c *C) { 218 cfg1 := map[string]interface{}{ 219 "foo": nil, 220 "bar": map[string]interface{}{ 221 "one": 1, 222 "two": nil, 223 }, 224 "baz": map[string]interface{}{ 225 "three": nil, 226 }, 227 } 228 config.PurgeNulls(cfg1) 229 c.Check(cfg1, DeepEquals, map[string]interface{}{ 230 "bar": map[string]interface{}{ 231 "one": 1, 232 }, 233 "baz": map[string]interface{}{}, 234 }) 235 236 cfg2 := map[string]interface{}{"foo": nil} 237 c.Check(config.PurgeNulls(cfg2), DeepEquals, map[string]interface{}{}) 238 c.Check(cfg2, DeepEquals, map[string]interface{}{}) 239 240 jsonData, err := json.Marshal(map[string]interface{}{ 241 "foo": nil, 242 "bar": map[string]interface{}{ 243 "one": 2, 244 "two": nil, 245 }, 246 "baz": map[string]interface{}{ 247 "three": nil, 248 }, 249 }) 250 c.Assert(err, IsNil) 251 raw := json.RawMessage(jsonData) 252 cfg4 := map[string]*json.RawMessage{ 253 "root": &raw, 254 } 255 config.PurgeNulls(cfg4) 256 257 val, ok := cfg4["root"] 258 c.Assert(ok, Equals, true) 259 260 var out interface{} 261 jsonutil.DecodeWithNumber(bytes.NewReader(*val), &out) 262 c.Check(out, DeepEquals, map[string]interface{}{ 263 "bar": map[string]interface{}{ 264 "one": json.Number("2"), 265 }, 266 "baz": map[string]interface{}{}, 267 }) 268 269 sub := json.RawMessage(`{"foo":"bar"}`) 270 cfg5 := map[string]interface{}{ 271 "core": map[string]*json.RawMessage{ 272 "proxy": nil, 273 "sub": &sub, 274 }, 275 } 276 config.PurgeNulls(cfg5) 277 c.Check(cfg5, DeepEquals, map[string]interface{}{ 278 "core": map[string]*json.RawMessage{ 279 "sub": &sub, 280 }, 281 }) 282 } 283 284 func (s *configHelpersSuite) TestPurgeNullsTopLevelNull(c *C) { 285 cfgJSON := `{ 286 "experimental": { 287 "parallel-instances": true, 288 "snapd-snap": true 289 }, 290 "proxy": null, 291 "seed": { 292 "loaded": true 293 } 294 }` 295 var cfg map[string]*json.RawMessage 296 err := jsonutil.DecodeWithNumber(bytes.NewReader([]byte(cfgJSON)), &cfg) 297 c.Assert(err, IsNil) 298 299 config.PurgeNulls(cfg) 300 301 cfgJSON2, err := json.Marshal(cfg) 302 c.Assert(err, IsNil) 303 304 var out interface{} 305 jsonutil.DecodeWithNumber(bytes.NewReader(cfgJSON2), &out) 306 c.Check(out, DeepEquals, map[string]interface{}{ 307 "experimental": map[string]interface{}{ 308 "parallel-instances": true, 309 "snapd-snap": true, 310 }, 311 "seed": map[string]interface{}{ 312 "loaded": true, 313 }, 314 }) 315 }