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