~juju-qa/ubuntu/yakkety/juju/2.0-rc3-again

« back to all changes in this revision

Viewing changes to src/launchpad.net/juju-core/charm/bundle.go

  • Committer: Package Import Robot
  • Author(s): James Page
  • Date: 2013-04-24 22:34:47 UTC
  • Revision ID: package-import@ubuntu.com-20130424223447-f0qdji7ubnyo0s71
Tags: upstream-1.10.0.1
ImportĀ upstreamĀ versionĀ 1.10.0.1

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
package charm
 
2
 
 
3
import (
 
4
        "archive/zip"
 
5
        "errors"
 
6
        "fmt"
 
7
        "io"
 
8
        "io/ioutil"
 
9
        "os"
 
10
        "path/filepath"
 
11
        "strconv"
 
12
        "strings"
 
13
)
 
14
 
 
15
// The Bundle type encapsulates access to data and operations
 
16
// on a charm bundle.
 
17
type Bundle struct {
 
18
        Path     string // May be empty if Bundle wasn't read from a file
 
19
        meta     *Meta
 
20
        config   *Config
 
21
        revision int
 
22
        r        io.ReaderAt
 
23
        size     int64
 
24
}
 
25
 
 
26
// Trick to ensure *Bundle implements the Charm interface.
 
27
var _ Charm = (*Bundle)(nil)
 
28
 
 
29
// ReadBundle returns a Bundle for the charm in path.
 
30
func ReadBundle(path string) (bundle *Bundle, err error) {
 
31
        f, err := os.Open(path)
 
32
        if err != nil {
 
33
                return
 
34
        }
 
35
        defer f.Close()
 
36
        fi, err := f.Stat()
 
37
        if err != nil {
 
38
                return
 
39
        }
 
40
        b, err := readBundle(f, fi.Size())
 
41
        if err != nil {
 
42
                return
 
43
        }
 
44
        b.Path = path
 
45
        return b, nil
 
46
}
 
47
 
 
48
// ReadBundleBytes returns a Bundle read from the given data.
 
49
// Make sure the bundle fits in memory before using this.
 
50
func ReadBundleBytes(data []byte) (bundle *Bundle, err error) {
 
51
        return readBundle(readAtBytes(data), int64(len(data)))
 
52
}
 
53
 
 
54
func readBundle(r io.ReaderAt, size int64) (bundle *Bundle, err error) {
 
55
        b := &Bundle{r: r, size: size}
 
56
        zipr, err := zip.NewReader(r, size)
 
57
        if err != nil {
 
58
                return
 
59
        }
 
60
        reader, err := zipOpen(zipr, "metadata.yaml")
 
61
        if err != nil {
 
62
                return
 
63
        }
 
64
        b.meta, err = ReadMeta(reader)
 
65
        reader.Close()
 
66
        if err != nil {
 
67
                return
 
68
        }
 
69
 
 
70
        reader, err = zipOpen(zipr, "config.yaml")
 
71
        if _, ok := err.(*noBundleFile); ok {
 
72
                b.config = NewConfig()
 
73
        } else if err != nil {
 
74
                return nil, err
 
75
        } else {
 
76
                b.config, err = ReadConfig(reader)
 
77
                reader.Close()
 
78
                if err != nil {
 
79
                        return nil, err
 
80
                }
 
81
        }
 
82
 
 
83
        reader, err = zipOpen(zipr, "revision")
 
84
        if err != nil {
 
85
                if _, ok := err.(*noBundleFile); !ok {
 
86
                        return
 
87
                }
 
88
                b.revision = b.meta.OldRevision
 
89
        } else {
 
90
                _, err = fmt.Fscan(reader, &b.revision)
 
91
                if err != nil {
 
92
                        return nil, errors.New("invalid revision file")
 
93
                }
 
94
        }
 
95
 
 
96
        return b, nil
 
97
}
 
98
 
 
99
func zipOpen(zipr *zip.Reader, path string) (rc io.ReadCloser, err error) {
 
100
        for _, fh := range zipr.File {
 
101
                if fh.Name == path {
 
102
                        return fh.Open()
 
103
                }
 
104
        }
 
105
        return nil, &noBundleFile{path}
 
106
}
 
107
 
 
108
type noBundleFile struct {
 
109
        path string
 
110
}
 
111
 
 
112
func (err noBundleFile) Error() string {
 
113
        return fmt.Sprintf("bundle file not found: %s", err.path)
 
114
}
 
115
 
 
116
// Revision returns the revision number for the charm
 
117
// expanded in dir.
 
118
func (b *Bundle) Revision() int {
 
119
        return b.revision
 
120
}
 
121
 
 
122
// SetRevision changes the charm revision number. This affects the
 
123
// revision reported by Revision and the revision of the charm
 
124
// directory created by ExpandTo.
 
125
func (b *Bundle) SetRevision(revision int) {
 
126
        b.revision = revision
 
127
}
 
128
 
 
129
// Meta returns the Meta representing the metadata.yaml file from bundle.
 
130
func (b *Bundle) Meta() *Meta {
 
131
        return b.meta
 
132
}
 
133
 
 
134
// Config returns the Config representing the config.yaml file
 
135
// for the charm bundle.
 
136
func (b *Bundle) Config() *Config {
 
137
        return b.config
 
138
}
 
139
 
 
140
// ExpandTo expands the charm bundle into dir, creating it if necessary.
 
141
// If any errors occur during the expansion procedure, the process will
 
142
// continue. Only the last error found is returned.
 
143
func (b *Bundle) ExpandTo(dir string) (err error) {
 
144
        // If we have a Path, reopen the file. Otherwise, try to use
 
145
        // the original ReaderAt.
 
146
        r := b.r
 
147
        size := b.size
 
148
        if b.Path != "" {
 
149
                f, err := os.Open(b.Path)
 
150
                if err != nil {
 
151
                        return err
 
152
                }
 
153
                defer f.Close()
 
154
                fi, err := f.Stat()
 
155
                if err != nil {
 
156
                        return err
 
157
                }
 
158
                r = f
 
159
                size = fi.Size()
 
160
        }
 
161
 
 
162
        zipr, err := zip.NewReader(r, size)
 
163
        if err != nil {
 
164
                return err
 
165
        }
 
166
 
 
167
        hooks := b.meta.Hooks()
 
168
        var lasterr error
 
169
        for _, zfile := range zipr.File {
 
170
                if err := b.expand(hooks, dir, zfile); err != nil {
 
171
                        lasterr = err
 
172
                }
 
173
        }
 
174
 
 
175
        revFile, err := os.Create(filepath.Join(dir, "revision"))
 
176
        if err != nil {
 
177
                return err
 
178
        }
 
179
        _, err = revFile.Write([]byte(strconv.Itoa(b.revision)))
 
180
        revFile.Close()
 
181
        if err != nil {
 
182
                return err
 
183
        }
 
184
        return lasterr
 
185
}
 
186
 
 
187
// expand unpacks a charm's zip file into the given directory.
 
188
// The hooks map holds all the possible hook names in the
 
189
// charm.
 
190
func (b *Bundle) expand(hooks map[string]bool, dir string, zfile *zip.File) error {
 
191
        cleanName := filepath.Clean(zfile.Name)
 
192
        if cleanName == "revision" {
 
193
                return nil
 
194
        }
 
195
 
 
196
        r, err := zfile.Open()
 
197
        if err != nil {
 
198
                return err
 
199
        }
 
200
        defer r.Close()
 
201
 
 
202
        mode := zfile.Mode()
 
203
        path := filepath.Join(dir, cleanName)
 
204
        if strings.HasSuffix(zfile.Name, "/") || mode&os.ModeDir != 0 {
 
205
                err = os.MkdirAll(path, mode&0777)
 
206
                if err != nil {
 
207
                        return err
 
208
                }
 
209
                return nil
 
210
        }
 
211
 
 
212
        base, _ := filepath.Split(path)
 
213
        err = os.MkdirAll(base, 0755)
 
214
        if err != nil {
 
215
                return err
 
216
        }
 
217
 
 
218
        if mode&os.ModeSymlink != 0 {
 
219
                data, err := ioutil.ReadAll(r)
 
220
                if err != nil {
 
221
                        return err
 
222
                }
 
223
                target := string(data)
 
224
                if err := checkSymlinkTarget(dir, cleanName, target); err != nil {
 
225
                        return err
 
226
                }
 
227
                return os.Symlink(target, path)
 
228
        }
 
229
        if filepath.Dir(cleanName) == "hooks" {
 
230
                hookName := filepath.Base(cleanName)
 
231
                if _, ok := hooks[hookName]; mode&os.ModeType == 0 && ok {
 
232
                        // Set all hooks executable (by owner)
 
233
                        mode = mode | 0100
 
234
                }
 
235
        }
 
236
 
 
237
        if err := checkFileType(cleanName, mode); err != nil {
 
238
                return err
 
239
        }
 
240
 
 
241
        f, err := os.OpenFile(path, os.O_CREATE|os.O_EXCL|os.O_WRONLY, mode&0777)
 
242
        if err != nil {
 
243
                return err
 
244
        }
 
245
        _, err = io.Copy(f, r)
 
246
        f.Close()
 
247
        return err
 
248
}
 
249
 
 
250
func checkSymlinkTarget(basedir, symlink, target string) error {
 
251
        if filepath.IsAbs(target) {
 
252
                return fmt.Errorf("symlink %q is absolute: %q", symlink, target)
 
253
        }
 
254
        p := filepath.Join(filepath.Dir(symlink), target)
 
255
        if p == ".." || strings.HasPrefix(p, "../") {
 
256
                return fmt.Errorf("symlink %q links out of charm: %q", symlink, target)
 
257
        }
 
258
        return nil
 
259
}
 
260
 
 
261
// FWIW, being able to do this is awesome.
 
262
type readAtBytes []byte
 
263
 
 
264
func (b readAtBytes) ReadAt(out []byte, off int64) (n int, err error) {
 
265
        return copy(out, b[off:]), nil
 
266
}