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