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

Proposed by John Lenton on 2015-05-28
Status: Merged
Approved by: Michael Vogt on 2015-05-29
Approved revision: 477
Merged at revision: 479
Proposed branch: lp:~chipaca/snappy/clickety
Merge into: lp:~snappy-dev/snappy/snappy-moved-to-github
Prerequisite: lp:~chipaca/snappy/use-skipdir
Diff against target: 815 lines (+307/-287)
7 files modified
clickdeb/deb.go (+12/-0)
snappy/click.go (+9/-240)
snappy/click_test.go (+7/-20)
snappy/common_test.go (+10/-3)
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
Michael Vogt 2015-05-28 Approve on 2015-05-29
Review via email: mp+260472@code.launchpad.net

This proposal supersedes a proposal from 2015-05-28.

Commit Message

Chipping away at installClick.

To post a comment you must log in.
Sergio Schvezov (sergiusens) : Posted in a previous version of this proposal
John Lenton (chipaca) : Posted in a previous version of this proposal
Sergio Schvezov (sergiusens) wrote :

some initial comments before breakfast ;-)

Michael Vogt (mvo) wrote :

Looks good, some comments inline, only a single one is really relevant I think.

John Lenton (chipaca) :
lp:~chipaca/snappy/clickety updated on 2015-05-28
477. By John Lenton on 2015-05-28

tweaks as per review comments

Michael Vogt (mvo) wrote :

Thanks!

review: Approve

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

Subscribers

People subscribed via source and target branches