github.com/bugraaydogar/snapd@v0.0.0-20210315170335-8c70bb858939/daemon/api_snap_conf_test.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2014-2020 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 daemon_test
    21  
    22  import (
    23  	"bytes"
    24  	"encoding/json"
    25  	"net/http"
    26  	"net/http/httptest"
    27  	"strings"
    28  
    29  	"gopkg.in/check.v1"
    30  
    31  	"github.com/snapcore/snapd/overlord/configstate/config"
    32  	"github.com/snapcore/snapd/testutil"
    33  )
    34  
    35  var _ = check.Suite(&snapConfSuite{})
    36  
    37  type snapConfSuite struct {
    38  	apiBaseSuite
    39  }
    40  
    41  func (s *snapConfSuite) runGetConf(c *check.C, snapName string, keys []string, statusCode int) map[string]interface{} {
    42  	req, err := http.NewRequest("GET", "/v2/snaps/"+snapName+"/conf?keys="+strings.Join(keys, ","), nil)
    43  	c.Check(err, check.IsNil)
    44  	rec := httptest.NewRecorder()
    45  	s.req(c, req, nil).ServeHTTP(rec, req)
    46  	c.Check(rec.Code, check.Equals, statusCode)
    47  
    48  	var body map[string]interface{}
    49  	err = json.Unmarshal(rec.Body.Bytes(), &body)
    50  	c.Check(err, check.IsNil)
    51  	return body["result"].(map[string]interface{})
    52  }
    53  
    54  func (s *snapConfSuite) TestGetConfSingleKey(c *check.C) {
    55  	d := s.daemon(c)
    56  
    57  	// Set a config that we'll get in a moment
    58  	d.Overlord().State().Lock()
    59  	tr := config.NewTransaction(d.Overlord().State())
    60  	tr.Set("test-snap", "test-key1", "test-value1")
    61  	tr.Set("test-snap", "test-key2", "test-value2")
    62  	tr.Commit()
    63  	d.Overlord().State().Unlock()
    64  
    65  	result := s.runGetConf(c, "test-snap", []string{"test-key1"}, 200)
    66  	c.Check(result, check.DeepEquals, map[string]interface{}{"test-key1": "test-value1"})
    67  
    68  	result = s.runGetConf(c, "test-snap", []string{"test-key1", "test-key2"}, 200)
    69  	c.Check(result, check.DeepEquals, map[string]interface{}{"test-key1": "test-value1", "test-key2": "test-value2"})
    70  }
    71  
    72  func (s *snapConfSuite) TestGetConfCoreSystemAlias(c *check.C) {
    73  	d := s.daemon(c)
    74  
    75  	// Set a config that we'll get in a moment
    76  	d.Overlord().State().Lock()
    77  	tr := config.NewTransaction(d.Overlord().State())
    78  	tr.Set("core", "test-key1", "test-value1")
    79  	tr.Commit()
    80  	d.Overlord().State().Unlock()
    81  
    82  	result := s.runGetConf(c, "core", []string{"test-key1"}, 200)
    83  	c.Check(result, check.DeepEquals, map[string]interface{}{"test-key1": "test-value1"})
    84  
    85  	result = s.runGetConf(c, "system", []string{"test-key1"}, 200)
    86  	c.Check(result, check.DeepEquals, map[string]interface{}{"test-key1": "test-value1"})
    87  }
    88  
    89  func (s *snapConfSuite) TestGetConfMissingKey(c *check.C) {
    90  	s.daemon(c)
    91  	result := s.runGetConf(c, "test-snap", []string{"test-key2"}, 400)
    92  	c.Check(result, check.DeepEquals, map[string]interface{}{
    93  		"value": map[string]interface{}{
    94  			"SnapName": "test-snap",
    95  			"Key":      "test-key2",
    96  		},
    97  		"message": `snap "test-snap" has no "test-key2" configuration option`,
    98  		"kind":    "option-not-found",
    99  	})
   100  }
   101  
   102  func (s *snapConfSuite) TestGetRootDocument(c *check.C) {
   103  	d := s.daemon(c)
   104  	d.Overlord().State().Lock()
   105  	tr := config.NewTransaction(d.Overlord().State())
   106  	tr.Set("test-snap", "test-key1", "test-value1")
   107  	tr.Set("test-snap", "test-key2", "test-value2")
   108  	tr.Commit()
   109  	d.Overlord().State().Unlock()
   110  
   111  	result := s.runGetConf(c, "test-snap", nil, 200)
   112  	c.Check(result, check.DeepEquals, map[string]interface{}{"test-key1": "test-value1", "test-key2": "test-value2"})
   113  }
   114  
   115  func (s *snapConfSuite) TestGetConfBadKey(c *check.C) {
   116  	s.daemon(c)
   117  	// TODO: this one in particular should really be a 400 also
   118  	result := s.runGetConf(c, "test-snap", []string{"."}, 500)
   119  	c.Check(result, check.DeepEquals, map[string]interface{}{"message": `invalid option name: ""`})
   120  }
   121  
   122  const configYaml = `
   123  name: config-snap
   124  version: 1
   125  hooks:
   126      configure:
   127  `
   128  
   129  func (s *snapConfSuite) TestSetConf(c *check.C) {
   130  	d := s.daemon(c)
   131  	s.mockSnap(c, configYaml)
   132  
   133  	// Mock the hook runner
   134  	hookRunner := testutil.MockCommand(c, "snap", "")
   135  	defer hookRunner.Restore()
   136  
   137  	d.Overlord().Loop()
   138  	defer d.Overlord().Stop()
   139  
   140  	text, err := json.Marshal(map[string]interface{}{"key": "value"})
   141  	c.Assert(err, check.IsNil)
   142  
   143  	buffer := bytes.NewBuffer(text)
   144  	req, err := http.NewRequest("PUT", "/v2/snaps/config-snap/conf", buffer)
   145  	c.Assert(err, check.IsNil)
   146  
   147  	rec := httptest.NewRecorder()
   148  	s.req(c, req, nil).ServeHTTP(rec, req)
   149  	c.Check(rec.Code, check.Equals, 202)
   150  
   151  	var body map[string]interface{}
   152  	err = json.Unmarshal(rec.Body.Bytes(), &body)
   153  	c.Assert(err, check.IsNil)
   154  	id := body["change"].(string)
   155  
   156  	st := d.Overlord().State()
   157  	st.Lock()
   158  	chg := st.Change(id)
   159  	st.Unlock()
   160  	c.Assert(chg, check.NotNil)
   161  
   162  	<-chg.Ready()
   163  
   164  	st.Lock()
   165  	err = chg.Err()
   166  	st.Unlock()
   167  	c.Assert(err, check.IsNil)
   168  
   169  	// Check that the configure hook was run correctly
   170  	c.Check(hookRunner.Calls(), check.DeepEquals, [][]string{{
   171  		"snap", "run", "--hook", "configure", "-r", "unset", "config-snap",
   172  	}})
   173  }
   174  
   175  func (s *snapConfSuite) TestSetConfCoreSystemAlias(c *check.C) {
   176  	d := s.daemon(c)
   177  	s.mockSnap(c, `
   178  name: core
   179  version: 1
   180  `)
   181  	// Mock the hook runner
   182  	hookRunner := testutil.MockCommand(c, "snap", "")
   183  	defer hookRunner.Restore()
   184  
   185  	d.Overlord().Loop()
   186  	defer d.Overlord().Stop()
   187  
   188  	text, err := json.Marshal(map[string]interface{}{"proxy.ftp": "value"})
   189  	c.Assert(err, check.IsNil)
   190  
   191  	buffer := bytes.NewBuffer(text)
   192  	req, err := http.NewRequest("PUT", "/v2/snaps/system/conf", buffer)
   193  	c.Assert(err, check.IsNil)
   194  
   195  	rec := httptest.NewRecorder()
   196  	s.req(c, req, nil).ServeHTTP(rec, req)
   197  	c.Check(rec.Code, check.Equals, 202)
   198  
   199  	var body map[string]interface{}
   200  	err = json.Unmarshal(rec.Body.Bytes(), &body)
   201  	c.Assert(err, check.IsNil)
   202  	id := body["change"].(string)
   203  
   204  	st := d.Overlord().State()
   205  	st.Lock()
   206  	chg := st.Change(id)
   207  	st.Unlock()
   208  	c.Assert(chg, check.NotNil)
   209  
   210  	<-chg.Ready()
   211  
   212  	st.Lock()
   213  	err = chg.Err()
   214  	c.Assert(err, check.IsNil)
   215  
   216  	tr := config.NewTransaction(st)
   217  	st.Unlock()
   218  	c.Assert(err, check.IsNil)
   219  
   220  	var value string
   221  	tr.Get("core", "proxy.ftp", &value)
   222  	c.Assert(value, check.Equals, "value")
   223  
   224  }
   225  
   226  func (s *snapConfSuite) TestSetConfNumber(c *check.C) {
   227  	d := s.daemon(c)
   228  	s.mockSnap(c, configYaml)
   229  
   230  	// Mock the hook runner
   231  	hookRunner := testutil.MockCommand(c, "snap", "")
   232  	defer hookRunner.Restore()
   233  
   234  	d.Overlord().Loop()
   235  	defer d.Overlord().Stop()
   236  
   237  	text, err := json.Marshal(map[string]interface{}{"key": 1234567890})
   238  	c.Assert(err, check.IsNil)
   239  
   240  	buffer := bytes.NewBuffer(text)
   241  	req, err := http.NewRequest("PUT", "/v2/snaps/config-snap/conf", buffer)
   242  	c.Assert(err, check.IsNil)
   243  
   244  	rec := httptest.NewRecorder()
   245  	s.req(c, req, nil).ServeHTTP(rec, req)
   246  	c.Check(rec.Code, check.Equals, 202)
   247  
   248  	var body map[string]interface{}
   249  	err = json.Unmarshal(rec.Body.Bytes(), &body)
   250  	c.Assert(err, check.IsNil)
   251  	id := body["change"].(string)
   252  
   253  	st := d.Overlord().State()
   254  	st.Lock()
   255  	chg := st.Change(id)
   256  	st.Unlock()
   257  	c.Assert(chg, check.NotNil)
   258  
   259  	<-chg.Ready()
   260  
   261  	st.Lock()
   262  	defer st.Unlock()
   263  	tr := config.NewTransaction(d.Overlord().State())
   264  	var result interface{}
   265  	c.Assert(tr.Get("config-snap", "key", &result), check.IsNil)
   266  	c.Assert(result, check.DeepEquals, json.Number("1234567890"))
   267  }
   268  
   269  func (s *snapConfSuite) TestSetConfBadSnap(c *check.C) {
   270  	s.daemonWithOverlordMockAndStore(c)
   271  
   272  	text, err := json.Marshal(map[string]interface{}{"key": "value"})
   273  	c.Assert(err, check.IsNil)
   274  
   275  	buffer := bytes.NewBuffer(text)
   276  	req, err := http.NewRequest("PUT", "/v2/snaps/config-snap/conf", buffer)
   277  	c.Assert(err, check.IsNil)
   278  
   279  	rec := httptest.NewRecorder()
   280  	s.req(c, req, nil).ServeHTTP(rec, req)
   281  	c.Check(rec.Code, check.Equals, 404)
   282  
   283  	var body map[string]interface{}
   284  	err = json.Unmarshal(rec.Body.Bytes(), &body)
   285  	c.Assert(err, check.IsNil)
   286  	c.Check(body, check.DeepEquals, map[string]interface{}{
   287  		"status-code": 404.,
   288  		"status":      "Not Found",
   289  		"result": map[string]interface{}{
   290  			"message": `snap "config-snap" is not installed`,
   291  			"kind":    "snap-not-found",
   292  			"value":   "config-snap",
   293  		},
   294  		"type": "error"})
   295  }
   296  
   297  func (s *snapConfSuite) TestSetConfChangeConflict(c *check.C) {
   298  	s.daemon(c)
   299  	s.mockSnap(c, configYaml)
   300  
   301  	s.simulateConflict("config-snap")
   302  
   303  	text, err := json.Marshal(map[string]interface{}{"key": "value"})
   304  	c.Assert(err, check.IsNil)
   305  
   306  	buffer := bytes.NewBuffer(text)
   307  	req, err := http.NewRequest("PUT", "/v2/snaps/config-snap/conf", buffer)
   308  	c.Assert(err, check.IsNil)
   309  
   310  	rec := httptest.NewRecorder()
   311  	s.req(c, req, nil).ServeHTTP(rec, req)
   312  	c.Check(rec.Code, check.Equals, 409)
   313  
   314  	var body map[string]interface{}
   315  	err = json.Unmarshal(rec.Body.Bytes(), &body)
   316  	c.Assert(err, check.IsNil)
   317  	c.Check(body, check.DeepEquals, map[string]interface{}{
   318  		"status-code": 409.,
   319  		"status":      "Conflict",
   320  		"result": map[string]interface{}{
   321  			"message": `snap "config-snap" has "manip" change in progress`,
   322  			"kind":    "snap-change-conflict",
   323  			"value": map[string]interface{}{
   324  				"change-kind": "manip",
   325  				"snap-name":   "config-snap",
   326  			},
   327  		},
   328  		"type": "error"})
   329  }