Merge lp:~mvo/snappy/snappy-snapfs-refactor-ftw into lp:~snappy-dev/snappy/snappy-moved-to-github

Proposed by Michael Vogt
Status: Work in progress
Proposed branch: lp:~mvo/snappy/snappy-snapfs-refactor-ftw
Merge into: lp:~snappy-dev/snappy/snappy-moved-to-github
Prerequisite: lp:~mvo/snappy/snappy-snapfs-boot-ok
Diff against target: 588 lines (+191/-80)
11 files modified
pkg/clickdeb/deb.go (+11/-2)
pkg/lightweight/lightweight_test.go (+1/-1)
snappy/click.go (+0/-1)
snappy/kernel.go (+31/-8)
snappy/oem.go (+27/-1)
snappy/os.go (+36/-0)
snappy/purge.go (+1/-1)
snappy/purge_test.go (+1/-1)
snappy/snapp.go (+56/-47)
snappy/snapp_snapfs_test.go (+23/-14)
snappy/snapp_test.go (+4/-4)
To merge this branch: bzr merge lp:~mvo/snappy/snappy-snapfs-refactor-ftw
Reviewer Review Type Date Requested Status
John Lenton (community) Needs Fixing
Review via email: mp+275024@code.launchpad.net
To post a comment you must log in.
786. By Michael Vogt

more snap types

787. By Michael Vogt

start refactor code out of generic and into OemSnap

Revision history for this message
John Lenton (chipaca) :
review: Needs Fixing
Revision history for this message
John Lenton (chipaca) :
788. By Michael Vogt

more SnapIF work

789. By Michael Vogt

move kernel unpack into kernel snap

Revision history for this message
Michael Vogt (mvo) :

Unmerged revisions

789. By Michael Vogt

move kernel unpack into kernel snap

788. By Michael Vogt

more SnapIF work

787. By Michael Vogt

start refactor code out of generic and into OemSnap

786. By Michael Vogt

more snap types

785. By Michael Vogt

start refactor to have OemSnap etc

784. By Michael Vogt

add boot-ok for new-style all-snaps

783. By Michael Vogt

disallow removal of the active kernel and os

782. By Michael Vogt

remove kernel assets in /boot/ on snap removal

781. By Michael Vogt

show a reboot required message (not complete yet)

780. By Michael Vogt

go fmt

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'pkg/clickdeb/deb.go'
2--- pkg/clickdeb/deb.go 2015-10-20 14:48:11 +0000
3+++ pkg/clickdeb/deb.go 2015-10-20 14:48:11 +0000
4@@ -30,6 +30,7 @@
5 "os"
6 "os/exec"
7 "path/filepath"
8+ "runtime"
9 "strings"
10 "time"
11
12@@ -127,13 +128,19 @@
13 file *os.File
14 }
15
16+func clickDebFinalizer(d *ClickDeb) {
17+ d.file.Close()
18+}
19+
20 // Open calls os.Open and uses that file for the backing file.
21 func Open(path string) (*ClickDeb, error) {
22 f, err := os.Open(path)
23 if err != nil {
24 return nil, err
25 }
26- return &ClickDeb{f}, nil
27+ deb := &ClickDeb{f}
28+ runtime.SetFinalizer(deb, clickDebFinalizer)
29+ return deb, nil
30 }
31
32 // Create calls os.Create and uses that file for the backing file.
33@@ -142,7 +149,9 @@
34 if err != nil {
35 return nil, err
36 }
37- return &ClickDeb{f}, nil
38+ deb := &ClickDeb{f}
39+ runtime.SetFinalizer(deb, clickDebFinalizer)
40+ return deb, nil
41 }
42
43 // Name returns the Name of the backing file
44
45=== modified file 'pkg/lightweight/lightweight_test.go'
46--- pkg/lightweight/lightweight_test.go 2015-10-10 09:48:49 +0000
47+++ pkg/lightweight/lightweight_test.go 2015-10-20 14:48:11 +0000
48@@ -388,7 +388,7 @@
49 c.Check(oem.ActiveIndex(), check.Equals, -1)
50 p, err := oem.Load(0)
51 c.Check(err, check.IsNil)
52- c.Check(p, check.FitsTypeOf, new(snappy.SnapPart))
53+ c.Check(p, check.FitsTypeOf, new(snappy.OemSnap))
54 c.Check(p.Version(), check.Equals, "3")
55 }
56
57
58=== modified file 'snappy/click.go'
59--- snappy/click.go 2015-10-15 07:32:34 +0000
60+++ snappy/click.go 2015-10-20 14:48:11 +0000
61@@ -771,7 +771,6 @@
62 if err != nil {
63 return "", err
64 }
65- defer part.deb.Close()
66
67 return part.Install(inter, flags)
68 }
69
70=== modified file 'snappy/kernel.go'
71--- snappy/kernel.go 2015-10-20 14:48:11 +0000
72+++ snappy/kernel.go 2015-10-20 14:48:11 +0000
73@@ -25,47 +25,70 @@
74
75 "launchpad.net/snappy/partition"
76 "launchpad.net/snappy/pkg/snapfs"
77+ "launchpad.net/snappy/progress"
78 )
79
80-func unpackKernel(s *SnapPart) error {
81+type KernelSnap struct {
82+ SnapPart
83+}
84+
85+// OEM snaps should not be removed as they are a key
86+// building block for OEMs. Prunning non active ones
87+// is acceptible.
88+func (s *KernelSnap) Uninstall(pb progress.Meter) (err error) {
89+ if s.IsActive() {
90+ return ErrPackageNotRemovable
91+ }
92+
93+ return s.SnapPart.Uninstall(pb)
94+}
95+
96+func (s *KernelSnap) Install(inter progress.Meter, flags InstallFlags) (name string, err error) {
97+ // do the generic install
98+ name, err = s.SnapPart.Install(inter, flags)
99+ if err != nil {
100+ return name, err
101+ }
102+
103+ // now do the kernel specific bits
104 bootdir := partition.BootloaderDir()
105 if err := os.MkdirAll(filepath.Join(bootdir, s.Version()), 0755); err !=
106 nil {
107- return err
108+ return name, err
109 }
110 blobName := filepath.Base(snapfs.BlobPath(s.basedir))
111 dstDir := filepath.Join(bootdir, blobName)
112 if s.m.Kernel != "" {
113 src := s.m.Kernel
114 if err := s.deb.Unpack(src, dstDir); err != nil {
115- return err
116+ return name, err
117 }
118 src = filepath.Join(dstDir, s.m.Kernel)
119 dst := filepath.Join(dstDir, partition.NormalizeKernelInitrdName(s.m.Kernel))
120 if err := os.Rename(src, dst); err != nil {
121- return err
122+ return name, err
123 }
124 }
125 if s.m.Initrd != "" {
126 src := s.m.Initrd
127 if err := s.deb.Unpack(src, dstDir); err != nil {
128- return err
129+ return name, err
130 }
131 src = filepath.Join(dstDir, s.m.Initrd)
132 dst := filepath.Join(dstDir, partition.NormalizeKernelInitrdName(s.m.Initrd))
133 if err := os.Rename(src, dst); err != nil {
134- return err
135+ return name, err
136 }
137 }
138 if s.m.Dtbs != "" {
139 src := s.m.Dtbs
140 dst := filepath.Join(dstDir, s.m.Dtbs)
141 if err := s.deb.Unpack(src, dst); err != nil {
142- return err
143+ return name, err
144 }
145 }
146
147- return nil
148+ return name, nil
149 }
150
151 func removeKernel(s *SnapPart) error {
152
153=== modified file 'snappy/oem.go'
154--- snappy/oem.go 2015-09-25 15:27:11 +0000
155+++ snappy/oem.go 2015-10-20 14:48:11 +0000
156@@ -34,8 +34,34 @@
157 "launchpad.net/snappy/dirs"
158 "launchpad.net/snappy/logger"
159 "launchpad.net/snappy/pkg"
160+ "launchpad.net/snappy/progress"
161 )
162
163+type OemSnap struct {
164+ SnapPart
165+}
166+
167+func (s *OemSnap) Install(inter progress.Meter, flags InstallFlags) (name string, err error) {
168+ name, err = s.SnapPart.Install(inter, flags)
169+ if err != nil {
170+ return "", err
171+ }
172+
173+ if err := installOemHardwareUdevRules(s.m); err != nil {
174+ return "", err
175+ }
176+
177+ return name, nil
178+}
179+
180+func (s *OemSnap) Uninstall(pb progress.Meter) (err error) {
181+ if s.IsActive() {
182+ return ErrPackageNotRemovable
183+ }
184+
185+ return s.SnapPart.Uninstall(pb)
186+}
187+
188 // OEM represents the structure inside the package.yaml for the oem component
189 // of an oem package type.
190 type OEM struct {
191@@ -129,7 +155,7 @@
192 func getOemImpl() (*packageYaml, error) {
193 oems, _ := ActiveSnapsByType(pkg.TypeOem)
194 if len(oems) == 1 {
195- return oems[0].(*SnapPart).m, nil
196+ return oems[0].(*OemSnap).m, nil
197 }
198
199 return nil, errors.New("no oem snap")
200
201=== added file 'snappy/os.go'
202--- snappy/os.go 1970-01-01 00:00:00 +0000
203+++ snappy/os.go 2015-10-20 14:48:11 +0000
204@@ -0,0 +1,36 @@
205+package snappy
206+
207+// -*- Mode: Go; indent-tabs-mode: t -*-
208+
209+/*
210+ * Copyright (C) 2014-2015 Canonical Ltd
211+ *
212+ * This program is free software: you can redistribute it and/or modify
213+ * it under the terms of the GNU General Public License version 3 as
214+ * published by the Free Software Foundation.
215+ *
216+ * This program is distributed in the hope that it will be useful,
217+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
218+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
219+ * GNU General Public License for more details.
220+ *
221+ * You should have received a copy of the GNU General Public License
222+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
223+ *
224+ */
225+
226+import (
227+ "launchpad.net/snappy/progress"
228+)
229+
230+type OsSnap struct {
231+ SnapPart
232+}
233+
234+func (s *OsSnap) Uninstall(pb progress.Meter) (err error) {
235+ if s.IsActive() {
236+ return ErrPackageNotRemovable
237+ }
238+
239+ return s.SnapPart.Uninstall(pb)
240+}
241
242=== modified file 'snappy/purge.go'
243--- snappy/purge.go 2015-09-25 15:27:11 +0000
244+++ snappy/purge.go 2015-10-20 14:48:11 +0000
245@@ -48,7 +48,7 @@
246
247 purgeActive := flags&DoPurgeActive != 0
248
249- var active []*SnapPart
250+ var active []SnapIF
251
252 for _, datadir := range datadirs {
253 yamlPath := filepath.Join(dirs.SnapAppsDir, datadir.QualifiedName(), datadir.Version, "meta", "package.yaml")
254
255=== modified file 'snappy/purge_test.go'
256--- snappy/purge_test.go 2015-10-10 13:27:50 +0000
257+++ snappy/purge_test.go 2015-10-20 14:48:11 +0000
258@@ -59,7 +59,7 @@
259 c.Check(inter.notified, HasLen, 0)
260 }
261
262-func (s *purgeSuite) mkpkg(c *C, args ...string) (dataDir string, part *SnapPart) {
263+func (s *purgeSuite) mkpkg(c *C, args ...string) (dataDir string, part SnapIF) {
264 version := "1.10"
265 extra := ""
266 switch len(args) {
267
268=== modified file 'snappy/snapp.go'
269--- snappy/snapp.go 2015-10-20 14:48:11 +0000
270+++ snappy/snapp.go 2015-10-20 14:48:11 +0000
271@@ -209,6 +209,16 @@
272 return nil
273 }
274
275+// FIXME: name suckso
276+type SnapIF interface {
277+ Part
278+ activate(inhibitHooks bool, inter interacter) error
279+ deactivate(inhibitHooks bool, inter interacter) error
280+ Dir() string
281+ remove(inter interacter) error
282+ ServiceYamls() []ServiceYaml
283+}
284+
285 // TODO split into payloads per package type composing the common
286 // elements for all snaps.
287 type packageYaml struct {
288@@ -529,17 +539,16 @@
289 }
290
291 // NewInstalledSnapPart returns a new SnapPart from the given yamlPath
292-func NewInstalledSnapPart(yamlPath, origin string) (*SnapPart, error) {
293+func NewInstalledSnapPart(yamlPath, origin string) (SnapIF, error) {
294 m, err := parsePackageYamlFile(yamlPath)
295 if err != nil {
296 return nil, err
297 }
298
299- part, err := NewSnapPartFromYaml(yamlPath, origin, m)
300+ part, err := newSnapPartFromYaml(yamlPath, origin, m, true)
301 if err != nil {
302 return nil, err
303 }
304- part.isInstalled = true
305
306 return part, nil
307 }
308@@ -547,7 +556,7 @@
309 // NewSnapPartFromSnapFile loads a snap from the given (clickdeb) snap file.
310 // Caller should call Close on the pkg.
311 // TODO: expose that Close.
312-func NewSnapPartFromSnapFile(snapFile string, origin string, unauthOk bool) (*SnapPart, error) {
313+func NewSnapPartFromSnapFile(snapFile string, origin string, unauthOk bool) (SnapIF, error) {
314 d, err := pkg.Open(snapFile)
315 if err != nil {
316 return nil, err
317@@ -570,41 +579,45 @@
318 return nil, err
319 }
320
321- // FIXME: make a special OemSnap that knows its install dir
322- // instead of having this special case here
323- targetDir := dirs.SnapAppsDir
324- // the "oem" parts are special
325- if m.Type == pkg.TypeOem {
326- targetDir = dirs.SnapOemDir
327- }
328-
329 if origin == SideloadedOrigin {
330 m.Version = helpers.NewSideloadVersion()
331 }
332
333 fullName := m.qualifiedName(origin)
334- instDir := filepath.Join(targetDir, fullName, m.Version)
335+ instDir := filepath.Join(dirs.SnapAppsDir, fullName, m.Version)
336
337- baseSnap := &SnapPart{
338+ basePart := &SnapPart{
339 basedir: instDir,
340 origin: origin,
341 m: m,
342 deb: d,
343 }
344
345- return baseSnap, nil
346+ // FIXME: duplicated code
347+ switch m.Type {
348+ case pkg.TypeOem:
349+ basePart.basedir = filepath.Join(dirs.SnapOemDir, fullName, m.Version)
350+ return &OemSnap{SnapPart: *basePart}, nil
351+ case pkg.TypeOS:
352+ return &OsSnap{SnapPart: *basePart}, nil
353+ case pkg.TypeKernel:
354+ return &KernelSnap{SnapPart: *basePart}, nil
355+ }
356+
357+ return basePart, nil
358 }
359
360-// NewSnapPartFromYaml returns a new SnapPart from the given *packageYaml at yamlPath
361-func NewSnapPartFromYaml(yamlPath, origin string, m *packageYaml) (*SnapPart, error) {
362+// newSnapPartFromYaml returns a new SnapPart from the given *packageYaml at yamlPath
363+func newSnapPartFromYaml(yamlPath, origin string, m *packageYaml, installed bool) (SnapIF, error) {
364 if _, err := os.Stat(yamlPath); err != nil {
365 return nil, err
366 }
367
368 part := &SnapPart{
369- basedir: filepath.Dir(filepath.Dir(yamlPath)),
370- origin: origin,
371- m: m,
372+ basedir: filepath.Dir(filepath.Dir(yamlPath)),
373+ origin: origin,
374+ m: m,
375+ isInstalled: installed,
376 }
377
378 // override the package's idea of its version
379@@ -656,6 +669,19 @@
380 part.remoteM = &r
381 }
382
383+ // FIXME: duplicated code
384+ basePart := part
385+ switch m.Type {
386+ case pkg.TypeOem:
387+ fullName := m.qualifiedName(origin)
388+ basePart.basedir = filepath.Join(dirs.SnapOemDir, fullName, m.Version)
389+ return &OemSnap{SnapPart: *basePart}, nil
390+ case pkg.TypeOS:
391+ return &OsSnap{SnapPart: *basePart}, nil
392+ case pkg.TypeKernel:
393+ return &KernelSnap{SnapPart: *basePart}, nil
394+ }
395+
396 return part, nil
397 }
398
399@@ -674,6 +700,10 @@
400 return s.m.Name
401 }
402
403+func (s *SnapPart) Dir() string {
404+ return s.basedir
405+}
406+
407 // Version returns the version
408 func (s *SnapPart) Version() string {
409 if s.basedir != "" {
410@@ -809,17 +839,10 @@
411 return "", err
412 }
413
414- // the "oem" parts are special
415- if s.Type() == pkg.TypeOem {
416- if err := installOemHardwareUdevRules(s.m); err != nil {
417- return "", err
418- }
419- }
420-
421 fullName := QualifiedName(s)
422 dataDir := filepath.Join(dirs.SnapDataDir, fullName, s.Version())
423
424- var oldPart *SnapPart
425+ var oldPart SnapIF
426 if currentActiveDir, _ := filepath.EvalSymlinks(filepath.Join(s.basedir, "..", "current")); currentActiveDir != "" {
427 oldPart, err = NewInstalledSnapPart(filepath.Join(currentActiveDir, "meta", "package.yaml"), s.origin)
428 if err != nil {
429@@ -854,13 +877,6 @@
430 }
431 }
432
433- // FIXME: move into KernelSnap instead of poluting generic code
434- if s.m.Type == pkg.TypeKernel {
435- if err := unpackKernel(s); err != nil {
436- return "", err
437- }
438- }
439-
440 // legacy, the hooks (e.g. apparmor) need this. Once we converted
441 // all hooks this can go away
442 clickMetaDir := filepath.Join(s.basedir, ".click", "info")
443@@ -1160,13 +1176,6 @@
444
445 // Uninstall remove the snap from the system
446 func (s *SnapPart) Uninstall(pb progress.Meter) (err error) {
447- // OEM snaps should not be removed as they are a key
448- // building block for OEMs. Prunning non active ones
449- // is acceptible.
450- if (s.m.Type == pkg.TypeOem || s.m.Type == pkg.TypeKernel || s.m.Type == pkg.TypeOS) && s.IsActive() {
451- return ErrPackageNotRemovable
452- }
453-
454 if IsBuiltInSoftware(s.Name()) && s.IsActive() {
455 return ErrPackageNotRemovable
456 }
457@@ -1396,12 +1405,12 @@
458 }
459
460 // RefreshDependentsSecurity refreshes the security policies of dependent snaps
461-func (s *SnapPart) RefreshDependentsSecurity(oldPart *SnapPart, inter interacter) (err error) {
462+func (s *SnapPart) RefreshDependentsSecurity(oldPart SnapIF, inter interacter) (err error) {
463 oldBaseDir := ""
464 if oldPart != nil {
465- oldBaseDir = oldPart.basedir
466+ oldBaseDir = oldPart.Dir()
467 }
468- upPol, upTpl := policy.AppArmorDelta(oldBaseDir, s.basedir, s.Name()+"_")
469+ upPol, upTpl := policy.AppArmorDelta(oldBaseDir, s.Dir(), s.Name()+"_")
470
471 deps, err := s.Dependents()
472 if err != nil {
473@@ -2066,7 +2075,7 @@
474 // The returned environment contains additional SNAP_* variables that
475 // are required when calling a meta/hook/ script and that will override
476 // any already existing SNAP_* variables in os.Environment()
477-func makeSnapHookEnv(part *SnapPart) (env []string) {
478+func makeSnapHookEnv(part SnapIF) (env []string) {
479 desc := struct {
480 AppName string
481 AppArch string
482@@ -2077,7 +2086,7 @@
483 }{
484 part.Name(),
485 helpers.UbuntuArchitecture(),
486- part.basedir,
487+ part.Dir(),
488 part.Version(),
489 QualifiedName(part),
490 part.Origin(),
491
492=== modified file 'snappy/snapp_snapfs_test.go'
493--- snappy/snapp_snapfs_test.go 2015-10-20 14:48:11 +0000
494+++ snappy/snapp_snapfs_test.go 2015-10-20 14:48:11 +0000
495@@ -138,7 +138,7 @@
496 c.Assert(err, IsNil)
497
498 // ensure the right backend got picked up
499- c.Assert(part.deb, FitsTypeOf, &snapfs.Snap{})
500+ c.Assert(part.(*SnapPart).deb, FitsTypeOf, &snapfs.Snap{})
501 }
502
503 func (s *SnapfsTestSuite) TestInstallViaSnapfsWorks(c *C) {
504@@ -312,7 +312,7 @@
505 c.Assert(err, IsNil)
506 c.Assert(snap.NeedsReboot(), Equals, false)
507
508- snap.isActive = false
509+ snap.(*OsSnap).isActive = false
510 mockb.bootvars["snappy_os"] = "ubuntu-core." + testOrigin + "_15.10-1.snap"
511 c.Assert(snap.NeedsReboot(), Equals, true)
512 }
513@@ -325,7 +325,7 @@
514 c.Assert(err, IsNil)
515 c.Assert(snap.NeedsReboot(), Equals, false)
516
517- snap.isActive = false
518+ snap.(*KernelSnap).isActive = false
519 mockb.bootvars["snappy_kernel"] = "ubuntu-kernel." + testOrigin + "_4.0-1.snap"
520 c.Assert(snap.NeedsReboot(), Equals, true)
521 }
522@@ -350,15 +350,24 @@
523 c.Assert(helpers.FileExists(kernelAssetsDir), Equals, false)
524 }
525
526-func (s *SnapfsTestSuite) TestActiveKernelOSNotRemovable(c *C) {
527- for _, yaml := range []string{packageKernel, packageOS} {
528- snapYaml, err := makeInstalledMockSnap(dirs.GlobalRootDir, yaml)
529- c.Assert(err, IsNil)
530-
531- snap, err := NewInstalledSnapPart(snapYaml, testOrigin)
532- c.Assert(err, IsNil)
533-
534- snap.isActive = true
535- c.Assert(snap.Uninstall(&MockProgressMeter{}), Equals, ErrPackageNotRemovable)
536- }
537+func (s *SnapfsTestSuite) TestActiveKernelNotRemovable(c *C) {
538+ snapYaml, err := makeInstalledMockSnap(dirs.GlobalRootDir, packageKernel)
539+ c.Assert(err, IsNil)
540+
541+ snap, err := NewInstalledSnapPart(snapYaml, testOrigin)
542+ c.Assert(err, IsNil)
543+
544+ snap.(*KernelSnap).isActive = true
545+ c.Assert(snap.Uninstall(&MockProgressMeter{}), Equals, ErrPackageNotRemovable)
546+}
547+
548+func (s *SnapfsTestSuite) TestActiveOSNotRemovable(c *C) {
549+ snapYaml, err := makeInstalledMockSnap(dirs.GlobalRootDir, packageOS)
550+ c.Assert(err, IsNil)
551+
552+ snap, err := NewInstalledSnapPart(snapYaml, testOrigin)
553+ c.Assert(err, IsNil)
554+
555+ snap.(*OsSnap).isActive = true
556+ c.Assert(snap.Uninstall(&MockProgressMeter{}), Equals, ErrPackageNotRemovable)
557 }
558
559=== modified file 'snappy/snapp_test.go'
560--- snappy/snapp_test.go 2015-10-20 07:03:39 +0000
561+++ snappy/snapp_test.go 2015-10-20 14:48:11 +0000
562@@ -152,11 +152,11 @@
563 c.Assert(services[0].Name, Equals, "svc1")
564
565 // ensure we get valid Date()
566- st, err := os.Stat(snap.basedir)
567+ st, err := os.Stat(snap.Dir())
568 c.Assert(err, IsNil)
569 c.Assert(snap.Date(), Equals, st.ModTime())
570
571- c.Assert(snap.basedir, Equals, filepath.Join(s.tempdir, "apps", helloAppComposedName, "1.10"))
572+ c.Assert(snap.Dir(), Equals, filepath.Join(s.tempdir, "apps", helloAppComposedName, "1.10"))
573 c.Assert(snap.InstalledSize(), Not(Equals), -1)
574 }
575
576@@ -825,11 +825,11 @@
577 c.Assert(services[1].Description, Equals, "Service #2")
578
579 // ensure we get valid Date()
580- st, err := os.Stat(snap.basedir)
581+ st, err := os.Stat(snap.Dir())
582 c.Assert(err, IsNil)
583 c.Assert(snap.Date(), Equals, st.ModTime())
584
585- c.Assert(snap.basedir, Equals, filepath.Join(s.tempdir, "apps", helloAppComposedName, "1.10"))
586+ c.Assert(snap.Dir(), Equals, filepath.Join(s.tempdir, "apps", helloAppComposedName, "1.10"))
587 c.Assert(snap.InstalledSize(), Not(Equals), -1)
588 }
589

Subscribers

People subscribed via source and target branches