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 }