Merge lp:~chipaca/snappy/clickety into lp:~snappy-dev/snappy/snappy-moved-to-github

Proposed by John Lenton
Status: Superseded
Proposed branch: lp:~chipaca/snappy/clickety
Merge into: lp:~snappy-dev/snappy/snappy-moved-to-github
Diff against target: 999 lines (+349/-374)
9 files modified
clickdeb/deb.go (+49/-0)
snappy/build.go (+5/-3)
snappy/click.go (+9/-292)
snappy/click_test.go (+7/-36)
snappy/common_test.go (+10/-3)
snappy/errors.go (+0/-16)
snappy/oem.go (+1/-1)
snappy/snapp.go (+267/-23)
snappy/snapp_test.go (+1/-0)
To merge this branch: bzr merge lp:~chipaca/snappy/clickety
Reviewer Review Type Date Requested Status
Snappy Developers Pending
Review via email: mp+260456@code.launchpad.net

This proposal has been superseded by a proposal from 2015-05-28.

To post a comment you must log in.
Revision history for this message
Sergio Schvezov (sergiusens) :
Revision history for this message
John Lenton (chipaca) :
lp:~chipaca/snappy/clickety updated
477. By John Lenton

tweaks as per review comments

Unmerged revisions

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'clickdeb/deb.go'
2--- clickdeb/deb.go 2015-05-19 14:09:19 +0000
3+++ clickdeb/deb.go 2015-05-28 12:02:18 +0000
4@@ -44,6 +44,18 @@
5 ErrSnapInvalidContent = errors.New("snap contains invalid content")
6 )
7
8+// ErrUnpackFailed is the error type for a snap unpack problem
9+type ErrUnpackFailed struct {
10+ snapFile string
11+ instDir string
12+ origErr error
13+}
14+
15+// ErrUnpackFailed is returned if unpacking a snap fails
16+func (e *ErrUnpackFailed) Error() string {
17+ return fmt.Sprintf("unpack %s to %s failed with %s", e.snapFile, e.instDir, e.origErr)
18+}
19+
20 // simple pipe based xz reader
21 func xzPipeReader(r io.Reader) io.Reader {
22 pr, pw := io.Pipe()
23@@ -186,6 +198,18 @@
24 return content, nil
25 }
26
27+// ExtractHashes gets "hashes.yaml" from the clickdeb and writes it to
28+// the given directory
29+func (d *ClickDeb) ExtractHashes(dir string) error {
30+ hashesFile := filepath.Join(dir, "hashes.yaml")
31+ hashesData, err := d.ControlMember("hashes.yaml")
32+ if err != nil {
33+ return err
34+ }
35+
36+ return ioutil.WriteFile(hashesFile, hashesData, 0644)
37+}
38+
39 // Unpack unpacks the data.tar.{gz,bz2,xz} into the given target directory
40 // with click specific verification, i.e. no files will be extracted outside
41 // of the targetdir (no ".." inside the data.tar is allowed)
42@@ -453,3 +477,28 @@
43
44 return dataReader, nil
45 }
46+
47+// UnpackWithDropPrivs will unapck the ClickDeb content into the
48+// target dir and drop privs when doing this.
49+//
50+// To do this reliably in go we need to exec a helper as we can not
51+// just fork() and drop privs in the child (no support for stock fork in go)
52+func (d *ClickDeb) UnpackWithDropPrivs(instDir, rootdir string) error {
53+ // no need to drop privs, we are not root
54+ if !helpers.ShouldDropPrivs() {
55+ return d.Unpack(instDir)
56+ }
57+
58+ cmd := exec.Command("snappy", "internal-unpack", d.Name(), instDir, rootdir)
59+ cmd.Stdout = os.Stdout
60+ cmd.Stderr = os.Stderr
61+ if err := cmd.Run(); err != nil {
62+ return &ErrUnpackFailed{
63+ snapFile: d.Name(),
64+ instDir: instDir,
65+ origErr: err,
66+ }
67+ }
68+
69+ return nil
70+}
71
72=== modified file 'snappy/build.go'
73--- snappy/build.go 2015-05-15 13:33:27 +0000
74+++ snappy/build.go 2015-05-28 12:02:18 +0000
75@@ -268,10 +268,12 @@
76 hashes.ArchiveSha512 = sha512
77
78 err = filepath.Walk(buildDir, func(path string, info os.FileInfo, err error) error {
79- if strings.HasPrefix(path[len(buildDir):], "/DEBIAN") {
80- return nil
81+ // path will always start with buildDir...
82+ if path[len(buildDir):] == "/DEBIAN" {
83+ return filepath.SkipDir
84 }
85- if path == buildDir {
86+ // ...so if path's length is == buildDir, it's buildDir
87+ if len(path) == len(buildDir) {
88 return nil
89 }
90
91
92=== modified file 'snappy/click.go'
93--- snappy/click.go 2015-05-28 11:19:41 +0000
94+++ snappy/click.go 2015-05-28 12:02:18 +0000
95@@ -47,6 +47,7 @@
96 "launchpad.net/snappy/logger"
97 "launchpad.net/snappy/pkg"
98 "launchpad.net/snappy/policy"
99+ "launchpad.net/snappy/progress"
100 "launchpad.net/snappy/systemd"
101
102 "github.com/mvo5/goconfigparser"
103@@ -290,16 +291,6 @@
104 return nil
105 }
106
107-func writeHashesFile(d *clickdeb.ClickDeb, instDir string) error {
108- hashesFile := filepath.Join(instDir, "meta", "hashes.yaml")
109- hashesData, err := d.ControlMember("hashes.yaml")
110- if err != nil {
111- return err
112- }
113-
114- return ioutil.WriteFile(hashesFile, hashesData, 0644)
115-}
116-
117 // generate the name
118 func generateBinaryName(m *packageYaml, binary Binary) string {
119 var binName string
120@@ -724,58 +715,6 @@
121 return nil
122 }
123
124-// takes a name and PATH (colon separated) and returns the full qualified path
125-func findBinaryInPath(name, path string) string {
126- for _, entry := range strings.Split(path, ":") {
127- fname := filepath.Join(entry, name)
128- if st, err := os.Stat(fname); err == nil {
129- // check for any x bit
130- if st.Mode()&0111 != 0 {
131- return fname
132- }
133- }
134- }
135-
136- return ""
137-}
138-
139-// unpackWithDropPrivs is a helper that will unapck the ClickDeb content
140-// into the target dir and drop privs when doing this.
141-//
142-// To do this reliably in go we need to exec a helper as we can not
143-// just fork() and drop privs in the child (no support for stock fork in go)
144-func unpackWithDropPrivs(d *clickdeb.ClickDeb, instDir string) error {
145- // no need to drop privs, we are not root
146- if !helpers.ShouldDropPrivs() {
147- return d.Unpack(instDir)
148- }
149-
150- // find priv helper executable
151- privHelper := ""
152- for _, path := range []string{"PATH", "GOPATH"} {
153- privHelper = findBinaryInPath("snappy", os.Getenv(path))
154- if privHelper != "" {
155- break
156- }
157- }
158- if privHelper == "" {
159- return ErrUnpackHelperNotFound
160- }
161-
162- cmd := exec.Command(privHelper, "internal-unpack", d.Name(), instDir, globalRootDir)
163- cmd.Stdout = os.Stdout
164- cmd.Stderr = os.Stderr
165- if err := cmd.Run(); err != nil {
166- return &ErrUnpackFailed{
167- snapFile: d.Name(),
168- instDir: instDir,
169- origErr: err,
170- }
171- }
172-
173- return nil
174-}
175-
176 type agreer interface {
177 Agreed(intro, license string) bool
178 }
179@@ -809,237 +748,15 @@
180 return nil
181 }
182
183-func installClick(snapFile string, flags InstallFlags, inter interacter, origin string) (name string, err error) {
184+func installClick(snapFile string, flags InstallFlags, inter progress.Meter, origin string) (name string, err error) {
185 allowUnauthenticated := (flags & AllowUnauthenticated) != 0
186- if err := auditClick(snapFile, allowUnauthenticated); err != nil {
187- return "", err
188- // ?
189- //return SnapAuditError
190- }
191-
192- d, err := clickdeb.Open(snapFile)
193- if err != nil {
194- return "", err
195- }
196- defer d.Close()
197-
198- manifestData, err := d.ControlMember("manifest")
199- if err != nil {
200- logger.Noticef("Snap inspect failed for %q: %v", snapFile, err)
201- return "", err
202- }
203-
204- yamlData, err := d.MetaMember("package.yaml")
205- if err != nil {
206- return "", err
207- }
208-
209- m, err := parsePackageYamlData(yamlData)
210- if err != nil {
211- return "", err
212- }
213-
214- if err := m.checkForPackageInstalled(origin); err != nil {
215- return "", err
216- }
217-
218- if err := m.checkForNameClashes(); err != nil {
219- return "", err
220- }
221-
222- if err := m.checkForFrameworks(); err != nil {
223- return "", err
224- }
225-
226- targetDir := snapAppsDir
227- // the "oem" parts are special
228- if m.Type == pkg.TypeOem {
229- targetDir = snapOemDir
230-
231- // TODO do the following at a higher level once the store publishes snap types
232- // this is horrible
233- if allowOEM := (flags & AllowOEM) != 0; !allowOEM {
234- if currentOEM, err := getOem(); err == nil {
235- if currentOEM.Name != m.Name {
236- return "", ErrOEMPackageInstall
237- }
238- } else {
239- // there should always be an oem package now
240- return "", ErrOEMPackageInstall
241- }
242- }
243-
244- if err := installOemHardwareUdevRules(m); err != nil {
245- return "", err
246- }
247- }
248-
249- fullName := m.qualifiedName(origin)
250- instDir := filepath.Join(targetDir, fullName, m.Version)
251- currentActiveDir, _ := filepath.EvalSymlinks(filepath.Join(instDir, "..", "current"))
252-
253- if err := m.checkLicenseAgreement(inter, d, currentActiveDir); err != nil {
254- return "", err
255- }
256-
257- dataDir := filepath.Join(snapDataDir, fullName, m.Version)
258-
259- if err := os.MkdirAll(instDir, 0755); err != nil {
260- logger.Noticef("Can not create %q: %v", instDir, err)
261- return "", err
262- }
263-
264- // if anything goes wrong here we cleanup
265- defer func() {
266- if err != nil {
267- if e := os.RemoveAll(instDir); e != nil && !os.IsNotExist(e) {
268- logger.Noticef("Failed to remove %q: %v", instDir, e)
269- }
270- }
271- }()
272-
273- // we need to call the external helper so that we can reliable drop
274- // privs
275- if err := unpackWithDropPrivs(d, instDir); err != nil {
276- return "", err
277- }
278-
279- // legacy, the hooks (e.g. apparmor) need this. Once we converted
280- // all hooks this can go away
281- clickMetaDir := path.Join(instDir, ".click", "info")
282- if err := os.MkdirAll(clickMetaDir, 0755); err != nil {
283- return "", err
284- }
285- if err := writeCompatManifestJSON(clickMetaDir, manifestData, origin); err != nil {
286- return "", err
287- }
288-
289- // write the hashes now
290- if err := writeHashesFile(d, instDir); err != nil {
291- return "", err
292- }
293-
294- inhibitHooks := (flags & InhibitHooks) != 0
295-
296- // deal with the data:
297- //
298- // if there was a previous version, stop it
299- // from being active so that it stops running and can no longer be
300- // started then copy the data
301- //
302- // otherwise just create a empty data dir
303- if currentActiveDir != "" {
304- oldM, err := parsePackageYamlFile(filepath.Join(currentActiveDir, "meta", "package.yaml"))
305- if err != nil {
306- return "", err
307- }
308-
309- // we need to stop making it active
310- err = unsetActiveClick(currentActiveDir, inhibitHooks, inter)
311- defer func() {
312- if err != nil {
313- if cerr := setActiveClick(currentActiveDir, inhibitHooks, inter); cerr != nil {
314- logger.Noticef("Setting old version back to active failed: %v", cerr)
315- }
316- }
317- }()
318- if err != nil {
319- return "", err
320- }
321-
322- err = copySnapData(fullName, oldM.Version, m.Version)
323- } else {
324- err = os.MkdirAll(dataDir, 0755)
325- }
326-
327- defer func() {
328- if err != nil {
329- if cerr := removeSnapData(fullName, m.Version); cerr != nil {
330- logger.Noticef("When cleaning up data for %s %s: %v", m.Name, m.Version, cerr)
331- }
332- }
333- }()
334-
335- if err != nil {
336- return "", err
337- }
338-
339- // and finally make active
340- err = setActiveClick(instDir, inhibitHooks, inter)
341- defer func() {
342- if err != nil && currentActiveDir != "" {
343- if cerr := setActiveClick(currentActiveDir, inhibitHooks, inter); cerr != nil {
344- logger.Noticef("When setting old %s version back to active: %v", m.Name, cerr)
345- }
346- }
347- }()
348- if err != nil {
349- return "", err
350- }
351-
352- // oh, one more thing: refresh the security bits
353- if !inhibitHooks {
354- part, err := NewSnapPartFromYaml(filepath.Join(instDir, "meta", "package.yaml"), origin, m)
355- if err != nil {
356- return "", err
357- }
358-
359- deps, err := part.Dependents()
360- if err != nil {
361- return "", err
362- }
363-
364- sysd := systemd.New(globalRootDir, inter)
365- stopped := make(map[string]time.Duration)
366- defer func() {
367- if err != nil {
368- for serviceName := range stopped {
369- if e := sysd.Start(serviceName); e != nil {
370- inter.Notify(fmt.Sprintf("unable to restart %s with the old %s: %s", serviceName, part.Name(), e))
371- }
372- }
373- }
374- }()
375-
376- for _, dep := range deps {
377- if !dep.IsActive() {
378- continue
379- }
380- for _, svc := range dep.Services() {
381- serviceName := filepath.Base(generateServiceFileName(dep.m, svc))
382- timeout := time.Duration(svc.StopTimeout)
383- if err = sysd.Stop(serviceName, timeout); err != nil {
384- inter.Notify(fmt.Sprintf("unable to stop %s; aborting install: %s", serviceName, err))
385- return "", err
386- }
387- stopped[serviceName] = timeout
388- }
389- }
390-
391- if err := part.RefreshDependentsSecurity(currentActiveDir, inter); err != nil {
392- return "", err
393- }
394-
395- started := make(map[string]time.Duration)
396- defer func() {
397- if err != nil {
398- for serviceName, timeout := range started {
399- if e := sysd.Stop(serviceName, timeout); e != nil {
400- inter.Notify(fmt.Sprintf("unable to stop %s with the old %s: %s", serviceName, part.Name(), e))
401- }
402- }
403- }
404- }()
405- for serviceName, timeout := range stopped {
406- if err = sysd.Start(serviceName); err != nil {
407- inter.Notify(fmt.Sprintf("unable to restart %s; aborting install: %s", serviceName, err))
408- return "", err
409- }
410- started[serviceName] = timeout
411- }
412- }
413-
414- return m.Name, nil
415+ part, err := NewSnapPartFromSnap(snapFile, origin, allowUnauthenticated)
416+ if err != nil {
417+ return "", err
418+ }
419+ defer part.deb.Close()
420+
421+ return part.Install(inter, flags)
422 }
423
424 // removeSnapData removes the data for the given version of the given snap
425
426=== modified file 'snappy/click_test.go'
427--- snappy/click_test.go 2015-05-28 11:17:44 +0000
428+++ snappy/click_test.go 2015-05-28 12:02:18 +0000
429@@ -246,19 +246,6 @@
430 c.Assert(err, NotNil)
431 }
432
433-type agreerator struct {
434- y bool
435- intro string
436- license string
437-}
438-
439-func (a *agreerator) Agreed(intro, license string) bool {
440- a.intro = intro
441- a.license = license
442- return a.y
443-}
444-func (a *agreerator) Notify(string) {}
445-
446 // if the snap asks for accepting a license, and an agreer isn't provided,
447 // install fails
448 func (s *SnapTestSuite) TestLocalSnapInstallMissingAccepterFails(c *C) {
449@@ -271,7 +258,7 @@
450 // Agreed returns false, install fails
451 func (s *SnapTestSuite) TestLocalSnapInstallNegAccepterFails(c *C) {
452 pkg := makeTestSnapPackage(c, "explicit-license-agreement: Y")
453- _, err := installClick(pkg, 0, &agreerator{y: false}, testOrigin)
454+ _, err := installClick(pkg, 0, &MockProgressMeter{y: false}, testOrigin)
455 c.Check(err, Equals, ErrLicenseNotAccepted)
456 }
457
458@@ -282,7 +269,7 @@
459 defer func() { licenseChecker = checkLicenseExists }()
460
461 pkg := makeTestSnapPackageFull(c, "explicit-license-agreement: Y", false)
462- _, err := installClick(pkg, 0, &agreerator{y: true}, testOrigin)
463+ _, err := installClick(pkg, 0, &MockProgressMeter{y: true}, testOrigin)
464 c.Check(err, Equals, ErrLicenseNotProvided)
465 }
466
467@@ -290,14 +277,14 @@
468 // Agreed returns true, install succeeds
469 func (s *SnapTestSuite) TestLocalSnapInstallPosAccepterWorks(c *C) {
470 pkg := makeTestSnapPackage(c, "explicit-license-agreement: Y")
471- _, err := installClick(pkg, 0, &agreerator{y: true}, testOrigin)
472+ _, err := installClick(pkg, 0, &MockProgressMeter{y: true}, testOrigin)
473 c.Check(err, Equals, nil)
474 }
475
476 // Agreed is given reasonable values for intro and license
477 func (s *SnapTestSuite) TestLocalSnapInstallAccepterReasonable(c *C) {
478 pkg := makeTestSnapPackage(c, "name: foobar\nexplicit-license-agreement: Y")
479- ag := &agreerator{y: true}
480+ ag := &MockProgressMeter{y: true}
481 _, err := installClick(pkg, 0, ag, testOrigin)
482 c.Assert(err, Equals, nil)
483 c.Check(ag.intro, Matches, ".*foobar.*requires.*license.*")
484@@ -307,7 +294,7 @@
485 // If a previous version is installed with the same license version, the agreer
486 // isn't called
487 func (s *SnapTestSuite) TestPreviouslyAcceptedLicense(c *C) {
488- ag := &agreerator{y: true}
489+ ag := &MockProgressMeter{y: true}
490 yaml := "name: foox\nexplicit-license-agreement: Y\nlicense-version: 2\n"
491 yamlFile, err := makeInstalledMockSnap(s.tempdir, yaml+"version: 1")
492 pkgdir := filepath.Dir(filepath.Dir(yamlFile))
493@@ -325,7 +312,7 @@
494 // If a previous version is installed with the same license version, but without
495 // explicit license agreement set, the agreer *is* called
496 func (s *SnapTestSuite) TestSameLicenseVersionButNotRequired(c *C) {
497- ag := &agreerator{y: true}
498+ ag := &MockProgressMeter{y: true}
499 yaml := "name: foox\nlicense-version: 2\n"
500 yamlFile, err := makeInstalledMockSnap(s.tempdir, yaml+"version: 1")
501 pkgdir := filepath.Dir(filepath.Dir(yamlFile))
502@@ -342,7 +329,7 @@
503 // If a previous version is installed with a different license version, the
504 // agreer *is* called
505 func (s *SnapTestSuite) TestDifferentLicenseVersion(c *C) {
506- ag := &agreerator{y: true}
507+ ag := &MockProgressMeter{y: true}
508 yaml := "name: foox\nexplicit-license-agreement: Y\n"
509 yamlFile, err := makeInstalledMockSnap(s.tempdir, yaml+"license-version: 2\nversion: 1")
510 pkgdir := filepath.Dir(filepath.Dir(yamlFile))
511@@ -1021,22 +1008,6 @@
512
513 }
514
515-func (s *SnapTestSuite) TestFindBinaryInPath(c *C) {
516- fakeBinDir := c.MkDir()
517- runMePath := filepath.Join(fakeBinDir, "runme")
518- err := ioutil.WriteFile(runMePath, []byte(""), 0755)
519- c.Assert(err, IsNil)
520-
521- p := filepath.Join(fakeBinDir, "not-executable")
522- err = ioutil.WriteFile(p, []byte(""), 0644)
523- c.Assert(err, IsNil)
524-
525- fakePATH := fmt.Sprintf("/some/dir:%s", fakeBinDir)
526- c.Assert(findBinaryInPath("runme", fakePATH), Equals, runMePath)
527- c.Assert(findBinaryInPath("no-such-binary-nowhere", fakePATH), Equals, "")
528- c.Assert(findBinaryInPath("not-executable", fakePATH), Equals, "")
529-}
530-
531 func (s *SnapTestSuite) TestLocalSnapInstallRunHooks(c *C) {
532 // we can not strip the global rootdir for the hook tests
533 stripGlobalRootDir = func(s string) string { return s }
534
535=== modified file 'snappy/common_test.go'
536--- snappy/common_test.go 2015-05-20 17:24:29 +0000
537+++ snappy/common_test.go 2015-05-28 12:02:18 +0000
538@@ -64,7 +64,7 @@
539 return "", err
540 }
541
542- dirName := fmt.Sprintf("%s.%s", m.Name, testOrigin)
543+ dirName := m.qualifiedName(testOrigin)
544 metaDir := filepath.Join(tempdir, "apps", dirName, m.Version, "meta")
545 if err := os.MkdirAll(metaDir, 0775); err != nil {
546 return "", err
547@@ -191,7 +191,12 @@
548 spin bool
549 spinMsg string
550 written int
551+ // Notifier:
552 notified []string
553+ // Agreer:
554+ intro string
555+ license string
556+ y bool
557 }
558
559 func (m *MockProgressMeter) Start(pkg string, total float64) {
560@@ -214,8 +219,10 @@
561 func (m *MockProgressMeter) Finished() {
562 m.finished = true
563 }
564-func (m *MockProgressMeter) Agreed(string, string) bool {
565- return false
566+func (m *MockProgressMeter) Agreed(intro, license string) bool {
567+ m.intro = intro
568+ m.license = license
569+ return m.y
570 }
571 func (m *MockProgressMeter) Notify(msg string) {
572 m.notified = append(m.notified, msg)
573
574=== modified file 'snappy/errors.go'
575--- snappy/errors.go 2015-05-20 17:24:29 +0000
576+++ snappy/errors.go 2015-05-28 12:02:18 +0000
577@@ -98,10 +98,6 @@
578 // a not (yet) supported platform
579 ErrBuildPlatformNotSupported = errors.New("building on a not (yet) supported platform")
580
581- // ErrUnpackHelperNotFound is returned if the unpack helper
582- // can not be found
583- ErrUnpackHelperNotFound = errors.New("unpack helper not found, do you have snappy installed in your PATH or GOPATH?")
584-
585 // ErrLicenseNotAccepted is returned when the user does not accept the
586 // license
587 ErrLicenseNotAccepted = errors.New("license not accepted")
588@@ -155,18 +151,6 @@
589 return fmt.Sprintf("%s failed to install: %s", e.snap, e.origErr)
590 }
591
592-// ErrUnpackFailed is the error type for a snap unpack problem
593-type ErrUnpackFailed struct {
594- snapFile string
595- instDir string
596- origErr error
597-}
598-
599-// ErrUnpackFailed is returned if unpacking a snap fails
600-func (e *ErrUnpackFailed) Error() string {
601- return fmt.Sprintf("unpack %s to %s failed with %s", e.snapFile, e.instDir, e.origErr)
602-}
603-
604 // ErrHookFailed is returned if a hook command fails
605 type ErrHookFailed struct {
606 cmd string
607
608=== modified file 'snappy/oem.go'
609--- snappy/oem.go 2015-05-28 11:17:44 +0000
610+++ snappy/oem.go 2015-05-28 12:02:18 +0000
611@@ -102,7 +102,7 @@
612 // logic for an oem package in every other function
613 var getOem = getOemImpl
614
615-var getOemImpl = func() (*packageYaml, error) {
616+func getOemImpl() (*packageYaml, error) {
617 oems, _ := ActiveSnapsByType(pkg.TypeOem)
618 if len(oems) == 1 {
619 return oems[0].(*SnapPart).m, nil
620
621=== modified file 'snappy/snapp.go'
622--- snappy/snapp.go 2015-05-20 17:24:29 +0000
623+++ snappy/snapp.go 2015-05-28 12:02:18 +0000
624@@ -22,7 +22,6 @@
625 import (
626 "bytes"
627 "encoding/json"
628- "errors"
629 "fmt"
630 "io"
631 "io/ioutil"
632@@ -46,6 +45,7 @@
633 "launchpad.net/snappy/policy"
634 "launchpad.net/snappy/progress"
635 "launchpad.net/snappy/release"
636+ "launchpad.net/snappy/systemd"
637 )
638
639 const (
640@@ -170,8 +170,8 @@
641 isActive bool
642 isInstalled bool
643 description string
644-
645- basedir string
646+ deb *clickdeb.ClickDeb
647+ basedir string
648 }
649
650 var commasplitter = regexp.MustCompile(`\s*,\s*`).Split
651@@ -456,10 +456,50 @@
652 if err != nil {
653 return nil, err
654 }
655+ part.isInstalled = true
656
657 return part, nil
658 }
659
660+// NewSnapPartFromSnap loads a snap from the given (clickdeb) snap file.
661+// Caller should call Close on the clickdeb.
662+// TODO: expose that Close.
663+func NewSnapPartFromSnap(snapFile string, origin string, unauthOk bool) (*SnapPart, error) {
664+ if err := clickdeb.Verify(snapFile, unauthOk); err != nil {
665+ return nil, err
666+ }
667+
668+ d, err := clickdeb.Open(snapFile)
669+ if err != nil {
670+ return nil, err
671+ }
672+
673+ yamlData, err := d.MetaMember("package.yaml")
674+ if err != nil {
675+ return nil, err
676+ }
677+
678+ m, err := parsePackageYamlData(yamlData)
679+ if err != nil {
680+ return nil, err
681+ }
682+
683+ targetDir := snapAppsDir
684+ // the "oem" parts are special
685+ if m.Type == pkg.TypeOem {
686+ targetDir = snapOemDir
687+ }
688+ fullName := m.qualifiedName(origin)
689+ instDir := filepath.Join(targetDir, fullName, m.Version)
690+
691+ return &SnapPart{
692+ basedir: instDir,
693+ origin: origin,
694+ m: m,
695+ deb: d,
696+ }, nil
697+}
698+
699 // NewSnapPartFromYaml returns a new SnapPart from the given *packageYaml at yamlPath
700 func NewSnapPartFromYaml(yamlPath, origin string, m *packageYaml) (*SnapPart, error) {
701 if _, err := os.Stat(yamlPath); err != nil {
702@@ -467,10 +507,9 @@
703 }
704
705 part := &SnapPart{
706- basedir: filepath.Dir(filepath.Dir(yamlPath)),
707- isInstalled: true,
708- origin: origin,
709- m: m,
710+ basedir: filepath.Dir(filepath.Dir(yamlPath)),
711+ origin: origin,
712+ m: m,
713 }
714
715 // check if the part is active
716@@ -610,8 +649,184 @@
717 }
718
719 // Install installs the snap
720-func (s *SnapPart) Install(pb progress.Meter, flags InstallFlags) (name string, err error) {
721- return "", errors.New("Install of a local part is not possible")
722+func (s *SnapPart) Install(inter progress.Meter, flags InstallFlags) (name string, err error) {
723+ allowOEM := (flags & AllowOEM) != 0
724+ inhibitHooks := (flags & InhibitHooks) != 0
725+
726+ if s.IsInstalled() {
727+ return "", ErrAlreadyInstalled
728+ }
729+
730+ if err := s.CanInstall(allowOEM, inter); err != nil {
731+ return "", err
732+ }
733+
734+ manifestData, err := s.deb.ControlMember("manifest")
735+ if err != nil {
736+ logger.Noticef("Snap inspect failed for %q: %v", s.Name(), err)
737+ return "", err
738+ }
739+
740+ // the "oem" parts are special
741+ if s.Type() == pkg.TypeOem {
742+ if err := installOemHardwareUdevRules(s.m); err != nil {
743+ return "", err
744+ }
745+ }
746+
747+ fullName := QualifiedName(s)
748+ currentActiveDir, _ := filepath.EvalSymlinks(filepath.Join(s.basedir, "..", "current"))
749+ dataDir := filepath.Join(snapDataDir, fullName, s.Version())
750+
751+ if err := os.MkdirAll(s.basedir, 0755); err != nil {
752+ logger.Noticef("Can not create %q: %v", s.basedir, err)
753+ return "", err
754+ }
755+
756+ // if anything goes wrong here we cleanup
757+ defer func() {
758+ if err != nil {
759+ if e := os.RemoveAll(s.basedir); e != nil && !os.IsNotExist(e) {
760+ logger.Noticef("Failed to remove %q: %v", s.basedir, e)
761+ }
762+ }
763+ }()
764+
765+ // we need to call the external helper so that we can reliable drop
766+ // privs
767+ if err := s.deb.UnpackWithDropPrivs(s.basedir, globalRootDir); err != nil {
768+ return "", err
769+ }
770+
771+ // legacy, the hooks (e.g. apparmor) need this. Once we converted
772+ // all hooks this can go away
773+ clickMetaDir := filepath.Join(s.basedir, ".click", "info")
774+ if err := os.MkdirAll(clickMetaDir, 0755); err != nil {
775+ return "", err
776+ }
777+ if err := writeCompatManifestJSON(clickMetaDir, manifestData, s.origin); err != nil {
778+ return "", err
779+ }
780+
781+ // write the hashes now
782+ if err := s.deb.ExtractHashes(filepath.Join(s.basedir, "meta")); err != nil {
783+ return "", err
784+ }
785+
786+ // deal with the data:
787+ //
788+ // if there was a previous version, stop it
789+ // from being active so that it stops running and can no longer be
790+ // started then copy the data
791+ //
792+ // otherwise just create a empty data dir
793+ if currentActiveDir != "" {
794+ oldM, err := parsePackageYamlFile(filepath.Join(currentActiveDir, "meta", "package.yaml"))
795+ if err != nil {
796+ return "", err
797+ }
798+
799+ // we need to stop making it active
800+ err = unsetActiveClick(currentActiveDir, inhibitHooks, inter)
801+ defer func() {
802+ if err != nil {
803+ if cerr := setActiveClick(currentActiveDir, inhibitHooks, inter); cerr != nil {
804+ logger.Noticef("Setting old version back to active failed: %v", cerr)
805+ }
806+ }
807+ }()
808+ if err != nil {
809+ return "", err
810+ }
811+
812+ err = copySnapData(fullName, oldM.Version, s.Version())
813+ } else {
814+ err = os.MkdirAll(dataDir, 0755)
815+ }
816+
817+ defer func() {
818+ if err != nil {
819+ if cerr := removeSnapData(fullName, s.Version()); cerr != nil {
820+ logger.Noticef("When cleaning up data for %s %s: %v", s.Name(), s.Version(), cerr)
821+ }
822+ }
823+ }()
824+
825+ if err != nil {
826+ return "", err
827+ }
828+
829+ // and finally make active
830+ err = setActiveClick(s.basedir, inhibitHooks, inter)
831+ defer func() {
832+ if err != nil && currentActiveDir != "" {
833+ if cerr := setActiveClick(currentActiveDir, inhibitHooks, inter); cerr != nil {
834+ logger.Noticef("When setting old %s version back to active: %v", s.Name(), cerr)
835+ }
836+ }
837+ }()
838+ if err != nil {
839+ return "", err
840+ }
841+
842+ // oh, one more thing: refresh the security bits
843+ if !inhibitHooks {
844+ deps, err := s.Dependents()
845+ if err != nil {
846+ return "", err
847+ }
848+
849+ sysd := systemd.New(globalRootDir, inter)
850+ stopped := make(map[string]time.Duration)
851+ defer func() {
852+ if err != nil {
853+ for serviceName := range stopped {
854+ if e := sysd.Start(serviceName); e != nil {
855+ inter.Notify(fmt.Sprintf("unable to restart %s with the old %s: %s", serviceName, s.Name(), e))
856+ }
857+ }
858+ }
859+ }()
860+
861+ for _, dep := range deps {
862+ if !dep.IsActive() {
863+ continue
864+ }
865+ for _, svc := range dep.Services() {
866+ serviceName := filepath.Base(generateServiceFileName(dep.m, svc))
867+ timeout := time.Duration(svc.StopTimeout)
868+ if err = sysd.Stop(serviceName, timeout); err != nil {
869+ inter.Notify(fmt.Sprintf("unable to stop %s; aborting install: %s", serviceName, err))
870+ return "", err
871+ }
872+ stopped[serviceName] = timeout
873+ }
874+ }
875+
876+ if err := s.RefreshDependentsSecurity(currentActiveDir, inter); err != nil {
877+ return "", err
878+ }
879+
880+ started := make(map[string]time.Duration)
881+ defer func() {
882+ if err != nil {
883+ for serviceName, timeout := range started {
884+ if e := sysd.Stop(serviceName, timeout); e != nil {
885+ inter.Notify(fmt.Sprintf("unable to stop %s with the old %s: %s", serviceName, s.Name(), e))
886+ }
887+ }
888+ }
889+ }()
890+ for serviceName, timeout := range stopped {
891+ if err = sysd.Start(serviceName); err != nil {
892+ inter.Notify(fmt.Sprintf("unable to restart %s; aborting install: %s", serviceName, err))
893+ return "", err
894+ }
895+ started[serviceName] = timeout
896+ }
897+ }
898+
899+ return s.Name(), nil
900 }
901
902 // SetActive sets the snap active
903@@ -716,6 +931,45 @@
904 return needed, nil
905 }
906
907+// CanInstall checks whether the SnapPart passes a series of tests required for installation
908+func (s *SnapPart) CanInstall(allowOEM bool, inter interacter) error {
909+ if s.IsInstalled() {
910+ return ErrAlreadyInstalled
911+ }
912+
913+ if err := s.m.checkForPackageInstalled(s.Origin()); err != nil {
914+ return err
915+ }
916+
917+ if err := s.m.checkForNameClashes(); err != nil {
918+ return err
919+ }
920+
921+ if err := s.m.checkForFrameworks(); err != nil {
922+ return err
923+ }
924+
925+ if s.Type() == pkg.TypeOem {
926+ if !allowOEM {
927+ if currentOEM, err := getOem(); err == nil {
928+ if currentOEM.Name != s.Name() {
929+ return ErrOEMPackageInstall
930+ }
931+ } else {
932+ // there should always be an oem package now
933+ return ErrOEMPackageInstall
934+ }
935+ }
936+ }
937+
938+ curr, _ := filepath.EvalSymlinks(filepath.Join(s.basedir, "..", "current"))
939+ if err := s.m.checkLicenseAgreement(inter, s.deb, curr); err != nil {
940+ return err
941+ }
942+
943+ return nil
944+}
945+
946 var timestampUpdater = helpers.UpdateTimestamp
947
948 func updateAppArmorJSONTimestamp(fullName, thing, version string) error {
949@@ -829,6 +1083,7 @@
950 if err != nil {
951 return nil, err
952 }
953+
954 for _, yamlfile := range matches {
955
956 // skip "current" and similar symlinks
957@@ -840,20 +1095,8 @@
958 continue
959 }
960
961- m, err := parsePackageYamlFile(realpath)
962- if err != nil {
963- return nil, err
964- }
965-
966- origin := ""
967- if m.Type != pkg.TypeFramework && m.Type != pkg.TypeOem {
968- origin, err = originFromYamlPath(realpath)
969- if err != nil {
970- return nil, err
971- }
972- }
973-
974- snap, err := NewSnapPartFromYaml(realpath, origin, m)
975+ origin, _ := originFromYamlPath(realpath)
976+ snap, err := NewInstalledSnapPart(realpath, origin)
977 if err != nil {
978 return nil, err
979 }
980@@ -873,6 +1116,7 @@
981 return ext[1:]
982 }
983
984+// originFromYamlPath *must* return "" if it's returning error.
985 func originFromYamlPath(path string) (string, error) {
986 origin := originFromBasedir(filepath.Join(path, "..", ".."))
987
988
989=== modified file 'snappy/snapp_test.go'
990--- snappy/snapp_test.go 2015-05-20 17:24:29 +0000
991+++ snappy/snapp_test.go 2015-05-28 12:02:18 +0000
992@@ -145,6 +145,7 @@
993 c.Check(snap.Version(), Equals, "1.10")
994 c.Check(snap.IsActive(), Equals, false)
995 c.Check(snap.Description(), Equals, "Hello")
996+ c.Check(snap.IsInstalled(), Equals, true)
997
998 services := snap.Services()
999 c.Assert(services, HasLen, 1)

Subscribers

People subscribed via source and target branches