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  }