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
=== modified file 'clickdeb/deb.go'
--- clickdeb/deb.go 2015-05-19 14:09:19 +0000
+++ clickdeb/deb.go 2015-05-28 12:02:18 +0000
@@ -44,6 +44,18 @@
44 ErrSnapInvalidContent = errors.New("snap contains invalid content")44 ErrSnapInvalidContent = errors.New("snap contains invalid content")
45)45)
4646
47// ErrUnpackFailed is the error type for a snap unpack problem
48type ErrUnpackFailed struct {
49 snapFile string
50 instDir string
51 origErr error
52}
53
54// ErrUnpackFailed is returned if unpacking a snap fails
55func (e *ErrUnpackFailed) Error() string {
56 return fmt.Sprintf("unpack %s to %s failed with %s", e.snapFile, e.instDir, e.origErr)
57}
58
47// simple pipe based xz reader59// simple pipe based xz reader
48func xzPipeReader(r io.Reader) io.Reader {60func xzPipeReader(r io.Reader) io.Reader {
49 pr, pw := io.Pipe()61 pr, pw := io.Pipe()
@@ -186,6 +198,18 @@
186 return content, nil198 return content, nil
187}199}
188200
201// ExtractHashes gets "hashes.yaml" from the clickdeb and writes it to
202// the given directory
203func (d *ClickDeb) ExtractHashes(dir string) error {
204 hashesFile := filepath.Join(dir, "hashes.yaml")
205 hashesData, err := d.ControlMember("hashes.yaml")
206 if err != nil {
207 return err
208 }
209
210 return ioutil.WriteFile(hashesFile, hashesData, 0644)
211}
212
189// Unpack unpacks the data.tar.{gz,bz2,xz} into the given target directory213// Unpack unpacks the data.tar.{gz,bz2,xz} into the given target directory
190// with click specific verification, i.e. no files will be extracted outside214// with click specific verification, i.e. no files will be extracted outside
191// of the targetdir (no ".." inside the data.tar is allowed)215// of the targetdir (no ".." inside the data.tar is allowed)
@@ -453,3 +477,28 @@
453477
454 return dataReader, nil478 return dataReader, nil
455}479}
480
481// UnpackWithDropPrivs will unapck the ClickDeb content into the
482// target dir and drop privs when doing this.
483//
484// To do this reliably in go we need to exec a helper as we can not
485// just fork() and drop privs in the child (no support for stock fork in go)
486func (d *ClickDeb) UnpackWithDropPrivs(instDir, rootdir string) error {
487 // no need to drop privs, we are not root
488 if !helpers.ShouldDropPrivs() {
489 return d.Unpack(instDir)
490 }
491
492 cmd := exec.Command("snappy", "internal-unpack", d.Name(), instDir, rootdir)
493 cmd.Stdout = os.Stdout
494 cmd.Stderr = os.Stderr
495 if err := cmd.Run(); err != nil {
496 return &ErrUnpackFailed{
497 snapFile: d.Name(),
498 instDir: instDir,
499 origErr: err,
500 }
501 }
502
503 return nil
504}
456505
=== modified file 'snappy/build.go'
--- snappy/build.go 2015-05-15 13:33:27 +0000
+++ snappy/build.go 2015-05-28 12:02:18 +0000
@@ -268,10 +268,12 @@
268 hashes.ArchiveSha512 = sha512268 hashes.ArchiveSha512 = sha512
269269
270 err = filepath.Walk(buildDir, func(path string, info os.FileInfo, err error) error {270 err = filepath.Walk(buildDir, func(path string, info os.FileInfo, err error) error {
271 if strings.HasPrefix(path[len(buildDir):], "/DEBIAN") {271 // path will always start with buildDir...
272 return nil272 if path[len(buildDir):] == "/DEBIAN" {
273 return filepath.SkipDir
273 }274 }
274 if path == buildDir {275 // ...so if path's length is == buildDir, it's buildDir
276 if len(path) == len(buildDir) {
275 return nil277 return nil
276 }278 }
277279
278280
=== modified file 'snappy/click.go'
--- snappy/click.go 2015-05-28 11:19:41 +0000
+++ snappy/click.go 2015-05-28 12:02:18 +0000
@@ -47,6 +47,7 @@
47 "launchpad.net/snappy/logger"47 "launchpad.net/snappy/logger"
48 "launchpad.net/snappy/pkg"48 "launchpad.net/snappy/pkg"
49 "launchpad.net/snappy/policy"49 "launchpad.net/snappy/policy"
50 "launchpad.net/snappy/progress"
50 "launchpad.net/snappy/systemd"51 "launchpad.net/snappy/systemd"
5152
52 "github.com/mvo5/goconfigparser"53 "github.com/mvo5/goconfigparser"
@@ -290,16 +291,6 @@
290 return nil291 return nil
291}292}
292293
293func writeHashesFile(d *clickdeb.ClickDeb, instDir string) error {
294 hashesFile := filepath.Join(instDir, "meta", "hashes.yaml")
295 hashesData, err := d.ControlMember("hashes.yaml")
296 if err != nil {
297 return err
298 }
299
300 return ioutil.WriteFile(hashesFile, hashesData, 0644)
301}
302
303// generate the name294// generate the name
304func generateBinaryName(m *packageYaml, binary Binary) string {295func generateBinaryName(m *packageYaml, binary Binary) string {
305 var binName string296 var binName string
@@ -724,58 +715,6 @@
724 return nil715 return nil
725}716}
726717
727// takes a name and PATH (colon separated) and returns the full qualified path
728func findBinaryInPath(name, path string) string {
729 for _, entry := range strings.Split(path, ":") {
730 fname := filepath.Join(entry, name)
731 if st, err := os.Stat(fname); err == nil {
732 // check for any x bit
733 if st.Mode()&0111 != 0 {
734 return fname
735 }
736 }
737 }
738
739 return ""
740}
741
742// unpackWithDropPrivs is a helper that will unapck the ClickDeb content
743// into the target dir and drop privs when doing this.
744//
745// To do this reliably in go we need to exec a helper as we can not
746// just fork() and drop privs in the child (no support for stock fork in go)
747func unpackWithDropPrivs(d *clickdeb.ClickDeb, instDir string) error {
748 // no need to drop privs, we are not root
749 if !helpers.ShouldDropPrivs() {
750 return d.Unpack(instDir)
751 }
752
753 // find priv helper executable
754 privHelper := ""
755 for _, path := range []string{"PATH", "GOPATH"} {
756 privHelper = findBinaryInPath("snappy", os.Getenv(path))
757 if privHelper != "" {
758 break
759 }
760 }
761 if privHelper == "" {
762 return ErrUnpackHelperNotFound
763 }
764
765 cmd := exec.Command(privHelper, "internal-unpack", d.Name(), instDir, globalRootDir)
766 cmd.Stdout = os.Stdout
767 cmd.Stderr = os.Stderr
768 if err := cmd.Run(); err != nil {
769 return &ErrUnpackFailed{
770 snapFile: d.Name(),
771 instDir: instDir,
772 origErr: err,
773 }
774 }
775
776 return nil
777}
778
779type agreer interface {718type agreer interface {
780 Agreed(intro, license string) bool719 Agreed(intro, license string) bool
781}720}
@@ -809,237 +748,15 @@
809 return nil748 return nil
810}749}
811750
812func installClick(snapFile string, flags InstallFlags, inter interacter, origin string) (name string, err error) {751func installClick(snapFile string, flags InstallFlags, inter progress.Meter, origin string) (name string, err error) {
813 allowUnauthenticated := (flags & AllowUnauthenticated) != 0752 allowUnauthenticated := (flags & AllowUnauthenticated) != 0
814 if err := auditClick(snapFile, allowUnauthenticated); err != nil {753 part, err := NewSnapPartFromSnap(snapFile, origin, allowUnauthenticated)
815 return "", err754 if err != nil {
816 // ?755 return "", err
817 //return SnapAuditError756 }
818 }757 defer part.deb.Close()
819758
820 d, err := clickdeb.Open(snapFile)759 return part.Install(inter, flags)
821 if err != nil {
822 return "", err
823 }
824 defer d.Close()
825
826 manifestData, err := d.ControlMember("manifest")
827 if err != nil {
828 logger.Noticef("Snap inspect failed for %q: %v", snapFile, err)
829 return "", err
830 }
831
832 yamlData, err := d.MetaMember("package.yaml")
833 if err != nil {
834 return "", err
835 }
836
837 m, err := parsePackageYamlData(yamlData)
838 if err != nil {
839 return "", err
840 }
841
842 if err := m.checkForPackageInstalled(origin); err != nil {
843 return "", err
844 }
845
846 if err := m.checkForNameClashes(); err != nil {
847 return "", err
848 }
849
850 if err := m.checkForFrameworks(); err != nil {
851 return "", err
852 }
853
854 targetDir := snapAppsDir
855 // the "oem" parts are special
856 if m.Type == pkg.TypeOem {
857 targetDir = snapOemDir
858
859 // TODO do the following at a higher level once the store publishes snap types
860 // this is horrible
861 if allowOEM := (flags & AllowOEM) != 0; !allowOEM {
862 if currentOEM, err := getOem(); err == nil {
863 if currentOEM.Name != m.Name {
864 return "", ErrOEMPackageInstall
865 }
866 } else {
867 // there should always be an oem package now
868 return "", ErrOEMPackageInstall
869 }
870 }
871
872 if err := installOemHardwareUdevRules(m); err != nil {
873 return "", err
874 }
875 }
876
877 fullName := m.qualifiedName(origin)
878 instDir := filepath.Join(targetDir, fullName, m.Version)
879 currentActiveDir, _ := filepath.EvalSymlinks(filepath.Join(instDir, "..", "current"))
880
881 if err := m.checkLicenseAgreement(inter, d, currentActiveDir); err != nil {
882 return "", err
883 }
884
885 dataDir := filepath.Join(snapDataDir, fullName, m.Version)
886
887 if err := os.MkdirAll(instDir, 0755); err != nil {
888 logger.Noticef("Can not create %q: %v", instDir, err)
889 return "", err
890 }
891
892 // if anything goes wrong here we cleanup
893 defer func() {
894 if err != nil {
895 if e := os.RemoveAll(instDir); e != nil && !os.IsNotExist(e) {
896 logger.Noticef("Failed to remove %q: %v", instDir, e)
897 }
898 }
899 }()
900
901 // we need to call the external helper so that we can reliable drop
902 // privs
903 if err := unpackWithDropPrivs(d, instDir); err != nil {
904 return "", err
905 }
906
907 // legacy, the hooks (e.g. apparmor) need this. Once we converted
908 // all hooks this can go away
909 clickMetaDir := path.Join(instDir, ".click", "info")
910 if err := os.MkdirAll(clickMetaDir, 0755); err != nil {
911 return "", err
912 }
913 if err := writeCompatManifestJSON(clickMetaDir, manifestData, origin); err != nil {
914 return "", err
915 }
916
917 // write the hashes now
918 if err := writeHashesFile(d, instDir); err != nil {
919 return "", err
920 }
921
922 inhibitHooks := (flags & InhibitHooks) != 0
923
924 // deal with the data:
925 //
926 // if there was a previous version, stop it
927 // from being active so that it stops running and can no longer be
928 // started then copy the data
929 //
930 // otherwise just create a empty data dir
931 if currentActiveDir != "" {
932 oldM, err := parsePackageYamlFile(filepath.Join(currentActiveDir, "meta", "package.yaml"))
933 if err != nil {
934 return "", err
935 }
936
937 // we need to stop making it active
938 err = unsetActiveClick(currentActiveDir, inhibitHooks, inter)
939 defer func() {
940 if err != nil {
941 if cerr := setActiveClick(currentActiveDir, inhibitHooks, inter); cerr != nil {
942 logger.Noticef("Setting old version back to active failed: %v", cerr)
943 }
944 }
945 }()
946 if err != nil {
947 return "", err
948 }
949
950 err = copySnapData(fullName, oldM.Version, m.Version)
951 } else {
952 err = os.MkdirAll(dataDir, 0755)
953 }
954
955 defer func() {
956 if err != nil {
957 if cerr := removeSnapData(fullName, m.Version); cerr != nil {
958 logger.Noticef("When cleaning up data for %s %s: %v", m.Name, m.Version, cerr)
959 }
960 }
961 }()
962
963 if err != nil {
964 return "", err
965 }
966
967 // and finally make active
968 err = setActiveClick(instDir, inhibitHooks, inter)
969 defer func() {
970 if err != nil && currentActiveDir != "" {
971 if cerr := setActiveClick(currentActiveDir, inhibitHooks, inter); cerr != nil {
972 logger.Noticef("When setting old %s version back to active: %v", m.Name, cerr)
973 }
974 }
975 }()
976 if err != nil {
977 return "", err
978 }
979
980 // oh, one more thing: refresh the security bits
981 if !inhibitHooks {
982 part, err := NewSnapPartFromYaml(filepath.Join(instDir, "meta", "package.yaml"), origin, m)
983 if err != nil {
984 return "", err
985 }
986
987 deps, err := part.Dependents()
988 if err != nil {
989 return "", err
990 }
991
992 sysd := systemd.New(globalRootDir, inter)
993 stopped := make(map[string]time.Duration)
994 defer func() {
995 if err != nil {
996 for serviceName := range stopped {
997 if e := sysd.Start(serviceName); e != nil {
998 inter.Notify(fmt.Sprintf("unable to restart %s with the old %s: %s", serviceName, part.Name(), e))
999 }
1000 }
1001 }
1002 }()
1003
1004 for _, dep := range deps {
1005 if !dep.IsActive() {
1006 continue
1007 }
1008 for _, svc := range dep.Services() {
1009 serviceName := filepath.Base(generateServiceFileName(dep.m, svc))
1010 timeout := time.Duration(svc.StopTimeout)
1011 if err = sysd.Stop(serviceName, timeout); err != nil {
1012 inter.Notify(fmt.Sprintf("unable to stop %s; aborting install: %s", serviceName, err))
1013 return "", err
1014 }
1015 stopped[serviceName] = timeout
1016 }
1017 }
1018
1019 if err := part.RefreshDependentsSecurity(currentActiveDir, inter); err != nil {
1020 return "", err
1021 }
1022
1023 started := make(map[string]time.Duration)
1024 defer func() {
1025 if err != nil {
1026 for serviceName, timeout := range started {
1027 if e := sysd.Stop(serviceName, timeout); e != nil {
1028 inter.Notify(fmt.Sprintf("unable to stop %s with the old %s: %s", serviceName, part.Name(), e))
1029 }
1030 }
1031 }
1032 }()
1033 for serviceName, timeout := range stopped {
1034 if err = sysd.Start(serviceName); err != nil {
1035 inter.Notify(fmt.Sprintf("unable to restart %s; aborting install: %s", serviceName, err))
1036 return "", err
1037 }
1038 started[serviceName] = timeout
1039 }
1040 }
1041
1042 return m.Name, nil
1043}760}
1044761
1045// removeSnapData removes the data for the given version of the given snap762// removeSnapData removes the data for the given version of the given snap
1046763
=== modified file 'snappy/click_test.go'
--- snappy/click_test.go 2015-05-28 11:17:44 +0000
+++ snappy/click_test.go 2015-05-28 12:02:18 +0000
@@ -246,19 +246,6 @@
246 c.Assert(err, NotNil)246 c.Assert(err, NotNil)
247}247}
248248
249type agreerator struct {
250 y bool
251 intro string
252 license string
253}
254
255func (a *agreerator) Agreed(intro, license string) bool {
256 a.intro = intro
257 a.license = license
258 return a.y
259}
260func (a *agreerator) Notify(string) {}
261
262// if the snap asks for accepting a license, and an agreer isn't provided,249// if the snap asks for accepting a license, and an agreer isn't provided,
263// install fails250// install fails
264func (s *SnapTestSuite) TestLocalSnapInstallMissingAccepterFails(c *C) {251func (s *SnapTestSuite) TestLocalSnapInstallMissingAccepterFails(c *C) {
@@ -271,7 +258,7 @@
271// Agreed returns false, install fails258// Agreed returns false, install fails
272func (s *SnapTestSuite) TestLocalSnapInstallNegAccepterFails(c *C) {259func (s *SnapTestSuite) TestLocalSnapInstallNegAccepterFails(c *C) {
273 pkg := makeTestSnapPackage(c, "explicit-license-agreement: Y")260 pkg := makeTestSnapPackage(c, "explicit-license-agreement: Y")
274 _, err := installClick(pkg, 0, &agreerator{y: false}, testOrigin)261 _, err := installClick(pkg, 0, &MockProgressMeter{y: false}, testOrigin)
275 c.Check(err, Equals, ErrLicenseNotAccepted)262 c.Check(err, Equals, ErrLicenseNotAccepted)
276}263}
277264
@@ -282,7 +269,7 @@
282 defer func() { licenseChecker = checkLicenseExists }()269 defer func() { licenseChecker = checkLicenseExists }()
283270
284 pkg := makeTestSnapPackageFull(c, "explicit-license-agreement: Y", false)271 pkg := makeTestSnapPackageFull(c, "explicit-license-agreement: Y", false)
285 _, err := installClick(pkg, 0, &agreerator{y: true}, testOrigin)272 _, err := installClick(pkg, 0, &MockProgressMeter{y: true}, testOrigin)
286 c.Check(err, Equals, ErrLicenseNotProvided)273 c.Check(err, Equals, ErrLicenseNotProvided)
287}274}
288275
@@ -290,14 +277,14 @@
290// Agreed returns true, install succeeds277// Agreed returns true, install succeeds
291func (s *SnapTestSuite) TestLocalSnapInstallPosAccepterWorks(c *C) {278func (s *SnapTestSuite) TestLocalSnapInstallPosAccepterWorks(c *C) {
292 pkg := makeTestSnapPackage(c, "explicit-license-agreement: Y")279 pkg := makeTestSnapPackage(c, "explicit-license-agreement: Y")
293 _, err := installClick(pkg, 0, &agreerator{y: true}, testOrigin)280 _, err := installClick(pkg, 0, &MockProgressMeter{y: true}, testOrigin)
294 c.Check(err, Equals, nil)281 c.Check(err, Equals, nil)
295}282}
296283
297// Agreed is given reasonable values for intro and license284// Agreed is given reasonable values for intro and license
298func (s *SnapTestSuite) TestLocalSnapInstallAccepterReasonable(c *C) {285func (s *SnapTestSuite) TestLocalSnapInstallAccepterReasonable(c *C) {
299 pkg := makeTestSnapPackage(c, "name: foobar\nexplicit-license-agreement: Y")286 pkg := makeTestSnapPackage(c, "name: foobar\nexplicit-license-agreement: Y")
300 ag := &agreerator{y: true}287 ag := &MockProgressMeter{y: true}
301 _, err := installClick(pkg, 0, ag, testOrigin)288 _, err := installClick(pkg, 0, ag, testOrigin)
302 c.Assert(err, Equals, nil)289 c.Assert(err, Equals, nil)
303 c.Check(ag.intro, Matches, ".*foobar.*requires.*license.*")290 c.Check(ag.intro, Matches, ".*foobar.*requires.*license.*")
@@ -307,7 +294,7 @@
307// If a previous version is installed with the same license version, the agreer294// If a previous version is installed with the same license version, the agreer
308// isn't called295// isn't called
309func (s *SnapTestSuite) TestPreviouslyAcceptedLicense(c *C) {296func (s *SnapTestSuite) TestPreviouslyAcceptedLicense(c *C) {
310 ag := &agreerator{y: true}297 ag := &MockProgressMeter{y: true}
311 yaml := "name: foox\nexplicit-license-agreement: Y\nlicense-version: 2\n"298 yaml := "name: foox\nexplicit-license-agreement: Y\nlicense-version: 2\n"
312 yamlFile, err := makeInstalledMockSnap(s.tempdir, yaml+"version: 1")299 yamlFile, err := makeInstalledMockSnap(s.tempdir, yaml+"version: 1")
313 pkgdir := filepath.Dir(filepath.Dir(yamlFile))300 pkgdir := filepath.Dir(filepath.Dir(yamlFile))
@@ -325,7 +312,7 @@
325// If a previous version is installed with the same license version, but without312// If a previous version is installed with the same license version, but without
326// explicit license agreement set, the agreer *is* called313// explicit license agreement set, the agreer *is* called
327func (s *SnapTestSuite) TestSameLicenseVersionButNotRequired(c *C) {314func (s *SnapTestSuite) TestSameLicenseVersionButNotRequired(c *C) {
328 ag := &agreerator{y: true}315 ag := &MockProgressMeter{y: true}
329 yaml := "name: foox\nlicense-version: 2\n"316 yaml := "name: foox\nlicense-version: 2\n"
330 yamlFile, err := makeInstalledMockSnap(s.tempdir, yaml+"version: 1")317 yamlFile, err := makeInstalledMockSnap(s.tempdir, yaml+"version: 1")
331 pkgdir := filepath.Dir(filepath.Dir(yamlFile))318 pkgdir := filepath.Dir(filepath.Dir(yamlFile))
@@ -342,7 +329,7 @@
342// If a previous version is installed with a different license version, the329// If a previous version is installed with a different license version, the
343// agreer *is* called330// agreer *is* called
344func (s *SnapTestSuite) TestDifferentLicenseVersion(c *C) {331func (s *SnapTestSuite) TestDifferentLicenseVersion(c *C) {
345 ag := &agreerator{y: true}332 ag := &MockProgressMeter{y: true}
346 yaml := "name: foox\nexplicit-license-agreement: Y\n"333 yaml := "name: foox\nexplicit-license-agreement: Y\n"
347 yamlFile, err := makeInstalledMockSnap(s.tempdir, yaml+"license-version: 2\nversion: 1")334 yamlFile, err := makeInstalledMockSnap(s.tempdir, yaml+"license-version: 2\nversion: 1")
348 pkgdir := filepath.Dir(filepath.Dir(yamlFile))335 pkgdir := filepath.Dir(filepath.Dir(yamlFile))
@@ -1021,22 +1008,6 @@
10211008
1022}1009}
10231010
1024func (s *SnapTestSuite) TestFindBinaryInPath(c *C) {
1025 fakeBinDir := c.MkDir()
1026 runMePath := filepath.Join(fakeBinDir, "runme")
1027 err := ioutil.WriteFile(runMePath, []byte(""), 0755)
1028 c.Assert(err, IsNil)
1029
1030 p := filepath.Join(fakeBinDir, "not-executable")
1031 err = ioutil.WriteFile(p, []byte(""), 0644)
1032 c.Assert(err, IsNil)
1033
1034 fakePATH := fmt.Sprintf("/some/dir:%s", fakeBinDir)
1035 c.Assert(findBinaryInPath("runme", fakePATH), Equals, runMePath)
1036 c.Assert(findBinaryInPath("no-such-binary-nowhere", fakePATH), Equals, "")
1037 c.Assert(findBinaryInPath("not-executable", fakePATH), Equals, "")
1038}
1039
1040func (s *SnapTestSuite) TestLocalSnapInstallRunHooks(c *C) {1011func (s *SnapTestSuite) TestLocalSnapInstallRunHooks(c *C) {
1041 // we can not strip the global rootdir for the hook tests1012 // we can not strip the global rootdir for the hook tests
1042 stripGlobalRootDir = func(s string) string { return s }1013 stripGlobalRootDir = func(s string) string { return s }
10431014
=== modified file 'snappy/common_test.go'
--- snappy/common_test.go 2015-05-20 17:24:29 +0000
+++ snappy/common_test.go 2015-05-28 12:02:18 +0000
@@ -64,7 +64,7 @@
64 return "", err64 return "", err
65 }65 }
6666
67 dirName := fmt.Sprintf("%s.%s", m.Name, testOrigin)67 dirName := m.qualifiedName(testOrigin)
68 metaDir := filepath.Join(tempdir, "apps", dirName, m.Version, "meta")68 metaDir := filepath.Join(tempdir, "apps", dirName, m.Version, "meta")
69 if err := os.MkdirAll(metaDir, 0775); err != nil {69 if err := os.MkdirAll(metaDir, 0775); err != nil {
70 return "", err70 return "", err
@@ -191,7 +191,12 @@
191 spin bool191 spin bool
192 spinMsg string192 spinMsg string
193 written int193 written int
194 // Notifier:
194 notified []string195 notified []string
196 // Agreer:
197 intro string
198 license string
199 y bool
195}200}
196201
197func (m *MockProgressMeter) Start(pkg string, total float64) {202func (m *MockProgressMeter) Start(pkg string, total float64) {
@@ -214,8 +219,10 @@
214func (m *MockProgressMeter) Finished() {219func (m *MockProgressMeter) Finished() {
215 m.finished = true220 m.finished = true
216}221}
217func (m *MockProgressMeter) Agreed(string, string) bool {222func (m *MockProgressMeter) Agreed(intro, license string) bool {
218 return false223 m.intro = intro
224 m.license = license
225 return m.y
219}226}
220func (m *MockProgressMeter) Notify(msg string) {227func (m *MockProgressMeter) Notify(msg string) {
221 m.notified = append(m.notified, msg)228 m.notified = append(m.notified, msg)
222229
=== modified file 'snappy/errors.go'
--- snappy/errors.go 2015-05-20 17:24:29 +0000
+++ snappy/errors.go 2015-05-28 12:02:18 +0000
@@ -98,10 +98,6 @@
98 // a not (yet) supported platform98 // a not (yet) supported platform
99 ErrBuildPlatformNotSupported = errors.New("building on a not (yet) supported platform")99 ErrBuildPlatformNotSupported = errors.New("building on a not (yet) supported platform")
100100
101 // ErrUnpackHelperNotFound is returned if the unpack helper
102 // can not be found
103 ErrUnpackHelperNotFound = errors.New("unpack helper not found, do you have snappy installed in your PATH or GOPATH?")
104
105 // ErrLicenseNotAccepted is returned when the user does not accept the101 // ErrLicenseNotAccepted is returned when the user does not accept the
106 // license102 // license
107 ErrLicenseNotAccepted = errors.New("license not accepted")103 ErrLicenseNotAccepted = errors.New("license not accepted")
@@ -155,18 +151,6 @@
155 return fmt.Sprintf("%s failed to install: %s", e.snap, e.origErr)151 return fmt.Sprintf("%s failed to install: %s", e.snap, e.origErr)
156}152}
157153
158// ErrUnpackFailed is the error type for a snap unpack problem
159type ErrUnpackFailed struct {
160 snapFile string
161 instDir string
162 origErr error
163}
164
165// ErrUnpackFailed is returned if unpacking a snap fails
166func (e *ErrUnpackFailed) Error() string {
167 return fmt.Sprintf("unpack %s to %s failed with %s", e.snapFile, e.instDir, e.origErr)
168}
169
170// ErrHookFailed is returned if a hook command fails154// ErrHookFailed is returned if a hook command fails
171type ErrHookFailed struct {155type ErrHookFailed struct {
172 cmd string156 cmd string
173157
=== modified file 'snappy/oem.go'
--- snappy/oem.go 2015-05-28 11:17:44 +0000
+++ snappy/oem.go 2015-05-28 12:02:18 +0000
@@ -102,7 +102,7 @@
102// logic for an oem package in every other function102// logic for an oem package in every other function
103var getOem = getOemImpl103var getOem = getOemImpl
104104
105var getOemImpl = func() (*packageYaml, error) {105func getOemImpl() (*packageYaml, error) {
106 oems, _ := ActiveSnapsByType(pkg.TypeOem)106 oems, _ := ActiveSnapsByType(pkg.TypeOem)
107 if len(oems) == 1 {107 if len(oems) == 1 {
108 return oems[0].(*SnapPart).m, nil108 return oems[0].(*SnapPart).m, nil
109109
=== modified file 'snappy/snapp.go'
--- snappy/snapp.go 2015-05-20 17:24:29 +0000
+++ snappy/snapp.go 2015-05-28 12:02:18 +0000
@@ -22,7 +22,6 @@
22import (22import (
23 "bytes"23 "bytes"
24 "encoding/json"24 "encoding/json"
25 "errors"
26 "fmt"25 "fmt"
27 "io"26 "io"
28 "io/ioutil"27 "io/ioutil"
@@ -46,6 +45,7 @@
46 "launchpad.net/snappy/policy"45 "launchpad.net/snappy/policy"
47 "launchpad.net/snappy/progress"46 "launchpad.net/snappy/progress"
48 "launchpad.net/snappy/release"47 "launchpad.net/snappy/release"
48 "launchpad.net/snappy/systemd"
49)49)
5050
51const (51const (
@@ -170,8 +170,8 @@
170 isActive bool170 isActive bool
171 isInstalled bool171 isInstalled bool
172 description string172 description string
173173 deb *clickdeb.ClickDeb
174 basedir string174 basedir string
175}175}
176176
177var commasplitter = regexp.MustCompile(`\s*,\s*`).Split177var commasplitter = regexp.MustCompile(`\s*,\s*`).Split
@@ -456,10 +456,50 @@
456 if err != nil {456 if err != nil {
457 return nil, err457 return nil, err
458 }458 }
459 part.isInstalled = true
459460
460 return part, nil461 return part, nil
461}462}
462463
464// NewSnapPartFromSnap loads a snap from the given (clickdeb) snap file.
465// Caller should call Close on the clickdeb.
466// TODO: expose that Close.
467func NewSnapPartFromSnap(snapFile string, origin string, unauthOk bool) (*SnapPart, error) {
468 if err := clickdeb.Verify(snapFile, unauthOk); err != nil {
469 return nil, err
470 }
471
472 d, err := clickdeb.Open(snapFile)
473 if err != nil {
474 return nil, err
475 }
476
477 yamlData, err := d.MetaMember("package.yaml")
478 if err != nil {
479 return nil, err
480 }
481
482 m, err := parsePackageYamlData(yamlData)
483 if err != nil {
484 return nil, err
485 }
486
487 targetDir := snapAppsDir
488 // the "oem" parts are special
489 if m.Type == pkg.TypeOem {
490 targetDir = snapOemDir
491 }
492 fullName := m.qualifiedName(origin)
493 instDir := filepath.Join(targetDir, fullName, m.Version)
494
495 return &SnapPart{
496 basedir: instDir,
497 origin: origin,
498 m: m,
499 deb: d,
500 }, nil
501}
502
463// NewSnapPartFromYaml returns a new SnapPart from the given *packageYaml at yamlPath503// NewSnapPartFromYaml returns a new SnapPart from the given *packageYaml at yamlPath
464func NewSnapPartFromYaml(yamlPath, origin string, m *packageYaml) (*SnapPart, error) {504func NewSnapPartFromYaml(yamlPath, origin string, m *packageYaml) (*SnapPart, error) {
465 if _, err := os.Stat(yamlPath); err != nil {505 if _, err := os.Stat(yamlPath); err != nil {
@@ -467,10 +507,9 @@
467 }507 }
468508
469 part := &SnapPart{509 part := &SnapPart{
470 basedir: filepath.Dir(filepath.Dir(yamlPath)),510 basedir: filepath.Dir(filepath.Dir(yamlPath)),
471 isInstalled: true,511 origin: origin,
472 origin: origin,512 m: m,
473 m: m,
474 }513 }
475514
476 // check if the part is active515 // check if the part is active
@@ -610,8 +649,184 @@
610}649}
611650
612// Install installs the snap651// Install installs the snap
613func (s *SnapPart) Install(pb progress.Meter, flags InstallFlags) (name string, err error) {652func (s *SnapPart) Install(inter progress.Meter, flags InstallFlags) (name string, err error) {
614 return "", errors.New("Install of a local part is not possible")653 allowOEM := (flags & AllowOEM) != 0
654 inhibitHooks := (flags & InhibitHooks) != 0
655
656 if s.IsInstalled() {
657 return "", ErrAlreadyInstalled
658 }
659
660 if err := s.CanInstall(allowOEM, inter); err != nil {
661 return "", err
662 }
663
664 manifestData, err := s.deb.ControlMember("manifest")
665 if err != nil {
666 logger.Noticef("Snap inspect failed for %q: %v", s.Name(), err)
667 return "", err
668 }
669
670 // the "oem" parts are special
671 if s.Type() == pkg.TypeOem {
672 if err := installOemHardwareUdevRules(s.m); err != nil {
673 return "", err
674 }
675 }
676
677 fullName := QualifiedName(s)
678 currentActiveDir, _ := filepath.EvalSymlinks(filepath.Join(s.basedir, "..", "current"))
679 dataDir := filepath.Join(snapDataDir, fullName, s.Version())
680
681 if err := os.MkdirAll(s.basedir, 0755); err != nil {
682 logger.Noticef("Can not create %q: %v", s.basedir, err)
683 return "", err
684 }
685
686 // if anything goes wrong here we cleanup
687 defer func() {
688 if err != nil {
689 if e := os.RemoveAll(s.basedir); e != nil && !os.IsNotExist(e) {
690 logger.Noticef("Failed to remove %q: %v", s.basedir, e)
691 }
692 }
693 }()
694
695 // we need to call the external helper so that we can reliable drop
696 // privs
697 if err := s.deb.UnpackWithDropPrivs(s.basedir, globalRootDir); err != nil {
698 return "", err
699 }
700
701 // legacy, the hooks (e.g. apparmor) need this. Once we converted
702 // all hooks this can go away
703 clickMetaDir := filepath.Join(s.basedir, ".click", "info")
704 if err := os.MkdirAll(clickMetaDir, 0755); err != nil {
705 return "", err
706 }
707 if err := writeCompatManifestJSON(clickMetaDir, manifestData, s.origin); err != nil {
708 return "", err
709 }
710
711 // write the hashes now
712 if err := s.deb.ExtractHashes(filepath.Join(s.basedir, "meta")); err != nil {
713 return "", err
714 }
715
716 // deal with the data:
717 //
718 // if there was a previous version, stop it
719 // from being active so that it stops running and can no longer be
720 // started then copy the data
721 //
722 // otherwise just create a empty data dir
723 if currentActiveDir != "" {
724 oldM, err := parsePackageYamlFile(filepath.Join(currentActiveDir, "meta", "package.yaml"))
725 if err != nil {
726 return "", err
727 }
728
729 // we need to stop making it active
730 err = unsetActiveClick(currentActiveDir, inhibitHooks, inter)
731 defer func() {
732 if err != nil {
733 if cerr := setActiveClick(currentActiveDir, inhibitHooks, inter); cerr != nil {
734 logger.Noticef("Setting old version back to active failed: %v", cerr)
735 }
736 }
737 }()
738 if err != nil {
739 return "", err
740 }
741
742 err = copySnapData(fullName, oldM.Version, s.Version())
743 } else {
744 err = os.MkdirAll(dataDir, 0755)
745 }
746
747 defer func() {
748 if err != nil {
749 if cerr := removeSnapData(fullName, s.Version()); cerr != nil {
750 logger.Noticef("When cleaning up data for %s %s: %v", s.Name(), s.Version(), cerr)
751 }
752 }
753 }()
754
755 if err != nil {
756 return "", err
757 }
758
759 // and finally make active
760 err = setActiveClick(s.basedir, inhibitHooks, inter)
761 defer func() {
762 if err != nil && currentActiveDir != "" {
763 if cerr := setActiveClick(currentActiveDir, inhibitHooks, inter); cerr != nil {
764 logger.Noticef("When setting old %s version back to active: %v", s.Name(), cerr)
765 }
766 }
767 }()
768 if err != nil {
769 return "", err
770 }
771
772 // oh, one more thing: refresh the security bits
773 if !inhibitHooks {
774 deps, err := s.Dependents()
775 if err != nil {
776 return "", err
777 }
778
779 sysd := systemd.New(globalRootDir, inter)
780 stopped := make(map[string]time.Duration)
781 defer func() {
782 if err != nil {
783 for serviceName := range stopped {
784 if e := sysd.Start(serviceName); e != nil {
785 inter.Notify(fmt.Sprintf("unable to restart %s with the old %s: %s", serviceName, s.Name(), e))
786 }
787 }
788 }
789 }()
790
791 for _, dep := range deps {
792 if !dep.IsActive() {
793 continue
794 }
795 for _, svc := range dep.Services() {
796 serviceName := filepath.Base(generateServiceFileName(dep.m, svc))
797 timeout := time.Duration(svc.StopTimeout)
798 if err = sysd.Stop(serviceName, timeout); err != nil {
799 inter.Notify(fmt.Sprintf("unable to stop %s; aborting install: %s", serviceName, err))
800 return "", err
801 }
802 stopped[serviceName] = timeout
803 }
804 }
805
806 if err := s.RefreshDependentsSecurity(currentActiveDir, inter); err != nil {
807 return "", err
808 }
809
810 started := make(map[string]time.Duration)
811 defer func() {
812 if err != nil {
813 for serviceName, timeout := range started {
814 if e := sysd.Stop(serviceName, timeout); e != nil {
815 inter.Notify(fmt.Sprintf("unable to stop %s with the old %s: %s", serviceName, s.Name(), e))
816 }
817 }
818 }
819 }()
820 for serviceName, timeout := range stopped {
821 if err = sysd.Start(serviceName); err != nil {
822 inter.Notify(fmt.Sprintf("unable to restart %s; aborting install: %s", serviceName, err))
823 return "", err
824 }
825 started[serviceName] = timeout
826 }
827 }
828
829 return s.Name(), nil
615}830}
616831
617// SetActive sets the snap active832// SetActive sets the snap active
@@ -716,6 +931,45 @@
716 return needed, nil931 return needed, nil
717}932}
718933
934// CanInstall checks whether the SnapPart passes a series of tests required for installation
935func (s *SnapPart) CanInstall(allowOEM bool, inter interacter) error {
936 if s.IsInstalled() {
937 return ErrAlreadyInstalled
938 }
939
940 if err := s.m.checkForPackageInstalled(s.Origin()); err != nil {
941 return err
942 }
943
944 if err := s.m.checkForNameClashes(); err != nil {
945 return err
946 }
947
948 if err := s.m.checkForFrameworks(); err != nil {
949 return err
950 }
951
952 if s.Type() == pkg.TypeOem {
953 if !allowOEM {
954 if currentOEM, err := getOem(); err == nil {
955 if currentOEM.Name != s.Name() {
956 return ErrOEMPackageInstall
957 }
958 } else {
959 // there should always be an oem package now
960 return ErrOEMPackageInstall
961 }
962 }
963 }
964
965 curr, _ := filepath.EvalSymlinks(filepath.Join(s.basedir, "..", "current"))
966 if err := s.m.checkLicenseAgreement(inter, s.deb, curr); err != nil {
967 return err
968 }
969
970 return nil
971}
972
719var timestampUpdater = helpers.UpdateTimestamp973var timestampUpdater = helpers.UpdateTimestamp
720974
721func updateAppArmorJSONTimestamp(fullName, thing, version string) error {975func updateAppArmorJSONTimestamp(fullName, thing, version string) error {
@@ -829,6 +1083,7 @@
829 if err != nil {1083 if err != nil {
830 return nil, err1084 return nil, err
831 }1085 }
1086
832 for _, yamlfile := range matches {1087 for _, yamlfile := range matches {
8331088
834 // skip "current" and similar symlinks1089 // skip "current" and similar symlinks
@@ -840,20 +1095,8 @@
840 continue1095 continue
841 }1096 }
8421097
843 m, err := parsePackageYamlFile(realpath)1098 origin, _ := originFromYamlPath(realpath)
844 if err != nil {1099 snap, err := NewInstalledSnapPart(realpath, origin)
845 return nil, err
846 }
847
848 origin := ""
849 if m.Type != pkg.TypeFramework && m.Type != pkg.TypeOem {
850 origin, err = originFromYamlPath(realpath)
851 if err != nil {
852 return nil, err
853 }
854 }
855
856 snap, err := NewSnapPartFromYaml(realpath, origin, m)
857 if err != nil {1100 if err != nil {
858 return nil, err1101 return nil, err
859 }1102 }
@@ -873,6 +1116,7 @@
873 return ext[1:]1116 return ext[1:]
874}1117}
8751118
1119// originFromYamlPath *must* return "" if it's returning error.
876func originFromYamlPath(path string) (string, error) {1120func originFromYamlPath(path string) (string, error) {
877 origin := originFromBasedir(filepath.Join(path, "..", ".."))1121 origin := originFromBasedir(filepath.Join(path, "..", ".."))
8781122
8791123
=== modified file 'snappy/snapp_test.go'
--- snappy/snapp_test.go 2015-05-20 17:24:29 +0000
+++ snappy/snapp_test.go 2015-05-28 12:02:18 +0000
@@ -145,6 +145,7 @@
145 c.Check(snap.Version(), Equals, "1.10")145 c.Check(snap.Version(), Equals, "1.10")
146 c.Check(snap.IsActive(), Equals, false)146 c.Check(snap.IsActive(), Equals, false)
147 c.Check(snap.Description(), Equals, "Hello")147 c.Check(snap.Description(), Equals, "Hello")
148 c.Check(snap.IsInstalled(), Equals, true)
148149
149 services := snap.Services()150 services := snap.Services()
150 c.Assert(services, HasLen, 1)151 c.Assert(services, HasLen, 1)

Subscribers

People subscribed via source and target branches