github.com/hugh712/snapd@v0.0.0-20200910133618-1a99902bd583/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  
   150  	for _, emptyCfg := range []*json.RawMessage{nil, &empty1, {}} {
   151  		rawCfg, err := config.GetSnapConfig(s.state, "snap1")
   152  		c.Assert(err, IsNil)
   153  		c.Check(rawCfg, IsNil)
   154  
   155  		// can set to empty when empty and it's fine
   156  		c.Assert(config.SetSnapConfig(s.state, "snap1", emptyCfg), IsNil)
   157  		rawCfg, err = config.GetSnapConfig(s.state, "snap1")
   158  		c.Assert(err, IsNil)
   159  		c.Check(rawCfg, IsNil)
   160  
   161  		cfg := json.RawMessage(`{"foo":"bar"}`)
   162  		c.Assert(config.SetSnapConfig(s.state, "snap1", &cfg), IsNil)
   163  
   164  		// the set sets it
   165  		rawCfg, err = config.GetSnapConfig(s.state, "snap1")
   166  		c.Assert(err, IsNil)
   167  		c.Assert(rawCfg, NotNil)
   168  		c.Check(*rawCfg, DeepEquals, json.RawMessage(`{"foo":"bar"}`))
   169  
   170  		// empty or nil clears it
   171  		c.Assert(config.SetSnapConfig(s.state, "snap1", emptyCfg), IsNil)
   172  		rawCfg, err = config.GetSnapConfig(s.state, "snap1")
   173  		c.Assert(err, IsNil)
   174  		c.Check(rawCfg, IsNil)
   175  	}
   176  }
   177  
   178  func (s *configHelpersSuite) TestPatchInvalidConfig(c *C) {
   179  	s.state.Lock()
   180  	defer s.state.Unlock()
   181  
   182  	invalid := []string{}
   183  	value := json.RawMessage([]byte("[]"))
   184  	_, err := config.PatchConfig("snap1", []string{"foo"}, 0, invalid, &value)
   185  	c.Assert(err, ErrorMatches, `internal error: unexpected configuration type \[\]string`)
   186  }
   187  
   188  func (s *configHelpersSuite) TestPurgeNulls(c *C) {
   189  	cfg1 := map[string]interface{}{
   190  		"foo": nil,
   191  		"bar": map[string]interface{}{
   192  			"one": 1,
   193  			"two": nil,
   194  		},
   195  		"baz": map[string]interface{}{
   196  			"three": nil,
   197  		},
   198  	}
   199  	config.PurgeNulls(cfg1)
   200  	c.Check(cfg1, DeepEquals, map[string]interface{}{
   201  		"bar": map[string]interface{}{
   202  			"one": 1,
   203  		},
   204  		"baz": map[string]interface{}{},
   205  	})
   206  
   207  	cfg2 := map[string]interface{}{"foo": nil}
   208  	c.Check(config.PurgeNulls(cfg2), DeepEquals, map[string]interface{}{})
   209  	c.Check(cfg2, DeepEquals, map[string]interface{}{})
   210  
   211  	jsonData, err := json.Marshal(map[string]interface{}{
   212  		"foo": nil,
   213  		"bar": map[string]interface{}{
   214  			"one": 2,
   215  			"two": nil,
   216  		},
   217  		"baz": map[string]interface{}{
   218  			"three": nil,
   219  		},
   220  	})
   221  	c.Assert(err, IsNil)
   222  	raw := json.RawMessage(jsonData)
   223  	cfg4 := map[string]*json.RawMessage{
   224  		"root": &raw,
   225  	}
   226  	config.PurgeNulls(cfg4)
   227  
   228  	val, ok := cfg4["root"]
   229  	c.Assert(ok, Equals, true)
   230  
   231  	var out interface{}
   232  	jsonutil.DecodeWithNumber(bytes.NewReader(*val), &out)
   233  	c.Check(out, DeepEquals, map[string]interface{}{
   234  		"bar": map[string]interface{}{
   235  			"one": json.Number("2"),
   236  		},
   237  		"baz": map[string]interface{}{},
   238  	})
   239  
   240  	sub := json.RawMessage(`{"foo":"bar"}`)
   241  	cfg5 := map[string]interface{}{
   242  		"core": map[string]*json.RawMessage{
   243  			"proxy": nil,
   244  			"sub":   &sub,
   245  		},
   246  	}
   247  	config.PurgeNulls(cfg5)
   248  	c.Check(cfg5, DeepEquals, map[string]interface{}{
   249  		"core": map[string]*json.RawMessage{
   250  			"sub": &sub,
   251  		},
   252  	})
   253  }
   254  
   255  func (s *configHelpersSuite) TestPurgeNullsTopLevelNull(c *C) {
   256  	cfgJSON := `{
   257    "experimental": {
   258      "parallel-instances": true,
   259      "snapd-snap": true
   260    },
   261    "proxy": null,
   262    "seed": {
   263      "loaded": true
   264    }
   265  }`
   266  	var cfg map[string]*json.RawMessage
   267  	err := jsonutil.DecodeWithNumber(bytes.NewReader([]byte(cfgJSON)), &cfg)
   268  	c.Assert(err, IsNil)
   269  
   270  	config.PurgeNulls(cfg)
   271  
   272  	cfgJSON2, err := json.Marshal(cfg)
   273  	c.Assert(err, IsNil)
   274  
   275  	var out interface{}
   276  	jsonutil.DecodeWithNumber(bytes.NewReader(cfgJSON2), &out)
   277  	c.Check(out, DeepEquals, map[string]interface{}{
   278  		"experimental": map[string]interface{}{
   279  			"parallel-instances": true,
   280  			"snapd-snap":         true,
   281  		},
   282  		"seed": map[string]interface{}{
   283  			"loaded": true,
   284  		},
   285  	})
   286  }