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  }