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