Merge lp:~jamesodhunt/snappy/install.yaml into lp:~snappy-dev/snappy/snappy-moved-to-github

Proposed by James Hunt on 2015-04-21
Status: Superseded
Proposed branch: lp:~jamesodhunt/snappy/install.yaml
Merge into: lp:~snappy-dev/snappy/snappy-moved-to-github
Diff against target: 535 lines (+365/-42)
9 files modified
partition/bootloader.go (+4/-0)
partition/bootloader_grub.go (+4/-0)
partition/bootloader_uboot.go (+4/-0)
partition/partition.go (+15/-0)
partition/partition_test.go (+4/-0)
provisioning/provisioning.go (+127/-0)
provisioning/provisioning_test.go (+165/-0)
snappy/systemimage.go (+2/-18)
snappy/systemimage_test.go (+40/-24)
To merge this branch: bzr merge lp:~jamesodhunt/snappy/install.yaml
Reviewer Review Type Date Requested Status
Michael Vogt Needs Information on 2015-04-24
Sergio Schvezov 2015-04-21 Needs Fixing on 2015-04-21
Review via email: mp+256925@code.launchpad.net

This proposal has been superseded by a proposal from 2015-06-09.

Description of the Change

* snappy/systemimage_test.go: Removed original sideload tests.
* snappy/provisioning_test.go: Added new sideload tests.

Change the sideload-detection strategy by having snappy parse an install.yaml file generated by the provisioning tool (ubuntu-device-flash).

This MP should be considered in combination with:

https://code.launchpad.net/~jamesodhunt/goget-ubuntu-touch/record-sideload/+merge/256190

To post a comment you must log in.
Sergio Schvezov (sergiusens) wrote :

it looks generally good, but I would prefer to have this in it's own package if possible

review: Needs Fixing
Sergio Schvezov (sergiusens) wrote :

A couple more comments and questions

James Hunt (jamesodhunt) wrote :

Thanks for reviewing. Branch updated.

James Hunt (jamesodhunt) wrote :

...aaaaand updated again.

Sergio Schvezov (sergiusens) wrote :

a couple of comments

lp:~jamesodhunt/snappy/install.yaml updated on 2015-04-24
393. By James Hunt on 2015-04-21

* Sync with lp:snappy.

394. By James Hunt on 2015-04-23

* Change InstallYamlFile to a filename as the path is going to have to
    bootloader-specific (due to grub's bind-mount).
* IsSideLoaded() (was SideLoadedSystem()) now takes a boot directory
  argument.

395. By James Hunt on 2015-04-23

* Sync with lp:snappy.

396. By James Hunt on 2015-04-23

* Comments.

397. By James Hunt on 2015-04-24

* provisioning/provisioning.go: Comments.
* provisioning/provisioning_test.go: Removed Version values to be
  consistent with soon-to-be-current ubutu-device-flash(1) behaviour.

398. By James Hunt on 2015-04-24

* Sync with lp:snappy.

James Hunt (jamesodhunt) wrote :

Hi Sergio - branch updated. Note that I haven't made the change to parseInstallYamlData() since that code was written specifically to be consistent with parsePackageYamlData().

Maybe we can deal with further refactoring on a separate MP ? :)

Michael Vogt (mvo) wrote :

Just one drive by comment, looks good otherwise even though I am not happy that partition needs that interface addition. I guess its hard to solve without as there is no other place that the file could be put that is safe from a factory reset(?).

review: Needs Information
lp:~jamesodhunt/snappy/install.yaml updated on 2015-05-01
399. By James Hunt on 2015-05-01

* Sync with lp:snappy.

400. By James Hunt on 2015-05-01

* snappy/systemimage_test.go: Added back TestCannotUpdateIfSideLoaded()
  to ensure Install() and Update() fail if the system is sideloaded.

James Hunt (jamesodhunt) wrote :

Thanks - branch updated.

> as there is no other place that the file could be put that is safe from a factory reset(?).
That's right.

Unmerged revisions

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'partition/bootloader.go'
2--- partition/bootloader.go 2015-04-14 20:37:54 +0000
3+++ partition/bootloader.go 2015-05-01 09:42:40 +0000
4@@ -80,6 +80,10 @@
5
6 // Return the additional required chroot bind mounts for this bootloader
7 AdditionalBindMounts() []string
8+
9+ // BootDir returns the (writable) bootloader-specific boot
10+ // directory.
11+ BootDir() string
12 }
13
14 type bootloaderType struct {
15
16=== modified file 'partition/bootloader_grub.go'
17--- partition/bootloader_grub.go 2015-04-30 11:28:39 +0000
18+++ partition/bootloader_grub.go 2015-05-01 09:42:40 +0000
19@@ -164,3 +164,7 @@
20 // well known location for its configuration
21 return []string{"/boot/grub"}
22 }
23+
24+func (g *grub) BootDir() string {
25+ return bootloaderGrubDir
26+}
27
28=== modified file 'partition/bootloader_uboot.go'
29--- partition/bootloader_uboot.go 2015-04-30 11:28:39 +0000
30+++ partition/bootloader_uboot.go 2015-05-01 09:42:40 +0000
31@@ -395,3 +395,7 @@
32 // nothing additional to system-boot required on uboot
33 return []string{}
34 }
35+
36+func (u *uboot) BootDir() string {
37+ return bootloaderUbootDir
38+}
39
40=== modified file 'partition/partition.go'
41--- partition/partition.go 2015-03-30 14:56:46 +0000
42+++ partition/partition.go 2015-05-01 09:42:40 +0000
43@@ -124,6 +124,10 @@
44
45 // run the function f with the otherRoot mounted
46 RunWithOther(rw MountOption, f func(otherRoot string) (err error)) (err error)
47+
48+ // Returns the full path to the (mounted and writable)
49+ // bootloader-specific boot directory.
50+ GetBootloaderDir() string
51 }
52
53 // mountEntry represents a mount this package has created.
54@@ -821,3 +825,14 @@
55
56 return bootloader.HandleAssets()
57 }
58+
59+// GetBootloaderDir returns the full path to the (mounted and writable)
60+// bootloader-specific boot directory.
61+func (p *Partition) GetBootloaderDir() string {
62+ bootloader, err := getBootloader(p)
63+ if err != nil {
64+ return ""
65+ }
66+
67+ return bootloader.BootDir()
68+}
69
70=== modified file 'partition/partition_test.go'
71--- partition/partition_test.go 2015-03-30 14:42:31 +0000
72+++ partition/partition_test.go 2015-05-01 09:42:40 +0000
73@@ -451,6 +451,10 @@
74 return nil
75 }
76
77+func (b *mockBootloader) BootDir() string {
78+ return ""
79+}
80+
81 func (s *PartitionTestSuite) TestToggleBootloaderRootfs(c *C) {
82 runCommand = mockRunCommand
83 b := &mockBootloader{}
84
85=== added directory 'provisioning'
86=== added file 'provisioning/provisioning.go'
87--- provisioning/provisioning.go 1970-01-01 00:00:00 +0000
88+++ provisioning/provisioning.go 2015-05-01 09:42:40 +0000
89@@ -0,0 +1,127 @@
90+/*
91+ * Copyright (C) 2014-2015 Canonical Ltd
92+ *
93+ * This program is free software: you can redistribute it and/or modify
94+ * it under the terms of the GNU General Public License version 3 as
95+ * published by the Free Software Foundation.
96+ *
97+ * This program is distributed in the hope that it will be useful,
98+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
99+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
100+ * GNU General Public License for more details.
101+ *
102+ * You should have received a copy of the GNU General Public License
103+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
104+ *
105+ */
106+
107+package provisioning
108+
109+import (
110+ "fmt"
111+ "io/ioutil"
112+ "log"
113+ "path/filepath"
114+ "time"
115+
116+ "launchpad.net/snappy/helpers"
117+ "launchpad.net/snappy/logger"
118+
119+ "gopkg.in/yaml.v2"
120+)
121+
122+var (
123+ // InstallYamlFile is the name of the file created by
124+ // ubuntu-device-flash(1), created at system installation time,
125+ // that contains metadata on the installation.
126+ //
127+ // XXX: Public for ubuntu-device-flash(1)
128+ InstallYamlFile = "install.yaml"
129+
130+ // ErrNoInstallYaml is emitted when InstallYamlFile does not exist.
131+ ErrNoInstallYaml = fmt.Errorf("no %s", InstallYamlFile)
132+)
133+
134+// InstallMeta encapsulates the metadata for a system install.
135+type InstallMeta struct {
136+ Timestamp time.Time
137+ InitialVersion string `yaml:"initial-version"`
138+ SystemImageServer string `yaml:"system-image-server"`
139+}
140+
141+// InstallTool encapsulates metadata on the tool used to create the
142+// system image.
143+type InstallTool struct {
144+ Name string
145+ Path string
146+ Version string
147+}
148+
149+// InstallOptions summarises the options used when creating the system image.
150+type InstallOptions struct {
151+ Size int64 `yaml:"size"`
152+ SizeUnit string `yaml:"size-unit"`
153+ Output string
154+ Channel string
155+ DevicePart string `yaml:"device-part,omitempty"`
156+ Oem string
157+ DeveloperMode bool `yaml:"developer-mode,omitempty"`
158+}
159+
160+// InstallYaml represents 'InstallYamlFile'
161+//
162+// XXX: Public for ubuntu-device-flash
163+type InstallYaml struct {
164+ InstallMeta `yaml:"meta"`
165+ InstallTool `yaml:"tool"`
166+ InstallOptions `yaml:"options"`
167+}
168+
169+func parseInstallYaml(path string) (*InstallYaml, error) {
170+ data, err := ioutil.ReadFile(path)
171+ if err != nil {
172+ return nil, ErrNoInstallYaml
173+ }
174+
175+ return parseInstallYamlData(data)
176+}
177+
178+func parseInstallYamlData(yamlData []byte) (*InstallYaml, error) {
179+ var i InstallYaml
180+ err := yaml.Unmarshal(yamlData, &i)
181+ if err != nil {
182+ log.Printf("Cannot parse %q", yamlData)
183+ return nil, err
184+ }
185+
186+ return &i, nil
187+}
188+
189+// IsSideLoaded determines if the system was installed using a
190+// custom enablement part.
191+func IsSideLoaded(bootloaderDir string) bool {
192+ file := filepath.Join(bootloaderDir, InstallYamlFile)
193+
194+ if !helpers.FileExists(file) {
195+ // the system may have been sideloaded, but we have no
196+ // way of knowing :-(
197+ return false
198+ }
199+
200+ InstallYaml, err := parseInstallYaml(file)
201+ if err != nil {
202+ logger.LogError(err)
203+ // file isn't parseable, so let's assume system is sideloaded
204+ return true
205+ }
206+
207+ if InstallYaml.InstallOptions.DevicePart != "" {
208+ // system was created with something like:
209+ //
210+ // "ubuntu-device-flash [...] --device-part=unofficial-assets.tar.xz ..."
211+ //
212+ return true
213+ }
214+
215+ return false
216+}
217
218=== added file 'provisioning/provisioning_test.go'
219--- provisioning/provisioning_test.go 1970-01-01 00:00:00 +0000
220+++ provisioning/provisioning_test.go 2015-05-01 09:42:40 +0000
221@@ -0,0 +1,165 @@
222+/*
223+ * Copyright (C) 2014-2015 Canonical Ltd
224+ *
225+ * This program is free software: you can redistribute it and/or modify
226+ * it under the terms of the GNU General Public License version 3 as
227+ * published by the Free Software Foundation.
228+ *
229+ * This program is distributed in the hope that it will be useful,
230+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
231+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
232+ * GNU General Public License for more details.
233+ *
234+ * You should have received a copy of the GNU General Public License
235+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
236+ *
237+ */
238+
239+package provisioning
240+
241+import (
242+ "io/ioutil"
243+ "os"
244+ "path/filepath"
245+ "testing"
246+
247+ . "launchpad.net/gocheck"
248+)
249+
250+// Hook up gocheck into the "go test" runner
251+func Test(t *testing.T) { TestingT(t) }
252+
253+type ProvisioningTestSuite struct {
254+ tempdir string
255+ installYamlFile string
256+}
257+
258+var _ = Suite(&ProvisioningTestSuite{})
259+
260+var yamlData = `
261+meta:
262+ timestamp: 2015-04-20T14:15:39.013515821+01:00
263+ initial-revision: r345
264+ system-image-server: http://system-image.ubuntu.com
265+
266+tool:
267+ name: ubuntu-device-flash
268+ path: /usr/bin/ubuntu-device-flash
269+ version: ""
270+
271+options:
272+ size: 3
273+ size-unit: GB
274+ output: /tmp/bbb.img
275+ channel: ubuntu-core/devel-proposed
276+ device-part: /some/path/file.tgz
277+ developer-mode: true
278+`
279+
280+var yamlDataNoDevicePart = `
281+meta:
282+ timestamp: 2015-04-20T14:15:39.013515821+01:00
283+ initial-revision: r345
284+ system-image-server: http://system-image.ubuntu.com
285+
286+tool:
287+ name: ubuntu-device-flash
288+ path: /usr/bin/ubuntu-device-flash
289+ version: ""
290+
291+options:
292+ size: 3
293+ size-unit: GB
294+ output: /tmp/bbb.img
295+ channel: ubuntu-core/devel-proposed
296+ developer-mode: true
297+`
298+
299+var garbageData = `Fooled you!?`
300+
301+func (ts *ProvisioningTestSuite) SetUpTest(c *C) {
302+ ts.tempdir = c.MkDir()
303+
304+ ts.installYamlFile = filepath.Join(ts.tempdir, "install.yaml")
305+
306+ InstallYamlFile = ts.installYamlFile
307+ os.Remove(InstallYamlFile)
308+}
309+
310+func (ts *ProvisioningTestSuite) TestSideLoadedSystemNoInstallYaml(c *C) {
311+ c.Assert(IsSideLoaded(""), Equals, false)
312+}
313+
314+func (ts *ProvisioningTestSuite) TestSideLoadedSystem(c *C) {
315+ c.Assert(IsSideLoaded(""), Equals, false)
316+
317+ err := ioutil.WriteFile(InstallYamlFile, []byte(yamlData), 0750)
318+ c.Assert(err, IsNil)
319+
320+ c.Assert(IsSideLoaded(""), Equals, true)
321+
322+ os.Remove(InstallYamlFile)
323+ c.Assert(IsSideLoaded(""), Equals, false)
324+}
325+
326+func (ts *ProvisioningTestSuite) TestSideLoadedSystemNoDevicePart(c *C) {
327+
328+ c.Assert(IsSideLoaded(""), Equals, false)
329+
330+ err := ioutil.WriteFile(InstallYamlFile, []byte(yamlDataNoDevicePart), 0750)
331+ c.Assert(err, IsNil)
332+
333+ c.Assert(IsSideLoaded(""), Equals, false)
334+
335+ os.Remove(InstallYamlFile)
336+ c.Assert(IsSideLoaded(""), Equals, false)
337+}
338+
339+func (ts *ProvisioningTestSuite) TestSideLoadedSystemGarbageInstallYaml(c *C) {
340+ c.Assert(IsSideLoaded(""), Equals, false)
341+
342+ err := ioutil.WriteFile(InstallYamlFile, []byte(garbageData), 0750)
343+ c.Assert(err, IsNil)
344+
345+ // we assume sideloaded if the file isn't parseable
346+ c.Assert(IsSideLoaded(""), Equals, true)
347+
348+ os.Remove(InstallYamlFile)
349+ c.Assert(IsSideLoaded(""), Equals, false)
350+}
351+
352+func (ts *ProvisioningTestSuite) TestParseInstallYaml(c *C) {
353+
354+ _, err := parseInstallYaml(InstallYamlFile)
355+ c.Check(err, Equals, ErrNoInstallYaml)
356+
357+ err = ioutil.WriteFile(InstallYamlFile, []byte(yamlData), 0750)
358+ c.Check(err, IsNil)
359+ _, err = parseInstallYaml(InstallYamlFile)
360+ c.Check(err, IsNil)
361+
362+ err = ioutil.WriteFile(InstallYamlFile, []byte(yamlDataNoDevicePart), 0750)
363+ c.Check(err, IsNil)
364+ _, err = parseInstallYaml(InstallYamlFile)
365+ c.Check(err, IsNil)
366+
367+ err = ioutil.WriteFile(InstallYamlFile, []byte(garbageData), 0750)
368+ c.Check(err, IsNil)
369+ _, err = parseInstallYaml(InstallYamlFile)
370+ c.Check(err, Not(Equals), nil)
371+}
372+
373+func (ts *ProvisioningTestSuite) TestParseInstallYamlData(c *C) {
374+
375+ _, err := parseInstallYamlData([]byte(""))
376+ c.Check(err, IsNil)
377+
378+ _, err = parseInstallYamlData([]byte(yamlData))
379+ c.Check(err, IsNil)
380+
381+ _, err = parseInstallYamlData([]byte(yamlDataNoDevicePart))
382+ c.Check(err, IsNil)
383+
384+ _, err = parseInstallYamlData([]byte(garbageData))
385+ c.Check(err, Not(Equals), nil)
386+}
387
388=== modified file 'snappy/systemimage.go'
389--- snappy/systemimage.go 2015-04-15 20:39:24 +0000
390+++ snappy/systemimage.go 2015-05-01 09:42:40 +0000
391@@ -31,6 +31,7 @@
392 "launchpad.net/snappy/helpers"
393 "launchpad.net/snappy/partition"
394 "launchpad.net/snappy/progress"
395+ "launchpad.net/snappy/provisioning"
396
397 "github.com/mvo5/goconfigparser"
398 )
399@@ -51,16 +52,6 @@
400 // The full path to this file needs to be passed to
401 // systemImageCli when querying a different rootfs.
402 systemImageClientConfig = "/etc/system-image/client.ini"
403-
404- // Full path to file, which if present, marks the system as
405- // having been "sideloaded", in other words having been created
406- // like this:
407- //
408- // "ubuntu-device-flash --device-part=unofficial-assets.tar.xz ..."
409- //
410- // Sideloaded systems cannot be safely upgraded since there are
411- // no device-part updates on the system-image server.
412- sideLoadedMarkerFile = "/boot/.sideloaded"
413 )
414
415 var (
416@@ -167,16 +158,9 @@
417 return s.partition.ToggleNextBoot()
418 }
419
420-// sideLoadedSystem determines if the system was installed using a
421-// custom enablement part.
422-func sideLoadedSystem() bool {
423- path := filepath.Join(systemImageRoot, sideLoadedMarkerFile)
424- return helpers.FileExists(path)
425-}
426-
427 // Install installs the snap
428 func (s *SystemImagePart) Install(pb progress.Meter, flags InstallFlags) (name string, err error) {
429- if sideLoadedSystem() {
430+ if provisioning.IsSideLoaded(s.partition.GetBootloaderDir()) {
431 return "", ErrSideLoaded
432 }
433
434
435=== modified file 'snappy/systemimage_test.go'
436--- snappy/systemimage_test.go 2015-04-22 10:35:09 +0000
437+++ snappy/systemimage_test.go 2015-05-01 09:42:40 +0000
438@@ -27,6 +27,7 @@
439 "testing"
440
441 partition "launchpad.net/snappy/partition"
442+ "launchpad.net/snappy/provisioning"
443
444 . "launchpad.net/gocheck"
445 )
446@@ -168,6 +169,13 @@
447 return f("/other")
448 }
449
450+// used by GetBootLoaderDir(), used to test sideload logic.
451+var tempBootDir string
452+
453+func (p *MockPartition) GetBootloaderDir() string {
454+ return tempBootDir
455+}
456+
457 func (s *SITestSuite) TestSystemImagePartInstallUpdatesPartition(c *C) {
458 // add a update
459 mockSystemImageIndexJSON = fmt.Sprintf(mockSystemImageIndexJSONTemplate, "2")
460@@ -359,43 +367,51 @@
461 }
462
463 func (s *SITestSuite) TestCannotUpdateIfSideLoaded(c *C) {
464+ var err error
465+ var yamlData = `
466+meta:
467+ timestamp: 2015-04-20T14:15:39.013515821+01:00
468+ initial-revision: r345
469+ system-image-server: http://system-image.ubuntu.com
470+
471+tool:
472+ name: ubuntu-device-flash
473+ path: /usr/bin/ubuntu-device-flash
474+ version: ""
475+
476+options:
477+ size: 3
478+ size-unit: GB
479+ output: /tmp/bbb.img
480+ channel: ubuntu-core/devel-proposed
481+ device-part: /some/path/file.tgz
482+ developer-mode: true
483+`
484+ tempBootDir, err = ioutil.TempDir("", "")
485+ c.Assert(err, IsNil)
486+
487 parts, err := s.systemImage.Updates()
488
489 sp := parts[0].(*SystemImagePart)
490 mockPartition := MockPartition{}
491 sp.partition = &mockPartition
492
493- sideLoaded := filepath.Join(systemImageRoot, "/boot/.sideloaded")
494+ bootDir := sp.partition.GetBootloaderDir()
495+
496+ sideLoaded := filepath.Join(bootDir, provisioning.InstallYamlFile)
497
498 err = os.MkdirAll(filepath.Dir(sideLoaded), 0775)
499 c.Assert(err, IsNil)
500
501- err = ioutil.WriteFile(sideLoaded, []byte(""), 0640)
502+ err = ioutil.WriteFile(sideLoaded, []byte(yamlData), 0640)
503 c.Assert(err, IsNil)
504
505 pb := &MockProgressMeter{}
506- // do the install
507+
508+ // Ensure the install fails if the system is sideloaded
509 _, err = sp.Install(pb, 0)
510 c.Assert(err, Equals, ErrSideLoaded)
511-}
512-
513-func (s *SITestSuite) TestSideLoadedSystem(c *C) {
514-
515- c.Assert(sideLoadedSystem(), Equals, false)
516-
517- sideLoaded := filepath.Join(systemImageRoot, sideLoadedMarkerFile)
518-
519- err := os.MkdirAll(filepath.Dir(sideLoaded), 0775)
520- c.Assert(err, IsNil)
521-
522- c.Assert(sideLoadedSystem(), Equals, false)
523-
524- err = ioutil.WriteFile(sideLoaded, []byte(""), 0640)
525- c.Assert(err, IsNil)
526-
527- c.Assert(sideLoadedSystem(), Equals, true)
528-
529- os.Remove(sideLoaded)
530-
531- c.Assert(sideLoadedSystem(), Equals, false)
532+
533+ os.Remove(tempBootDir)
534+ tempBootDir = ""
535 }

Subscribers

People subscribed via source and target branches