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