15
// The Bundle type encapsulates access to data and operations
18
Path string // May be empty if Bundle wasn't read from a file
26
// Trick to ensure *Bundle implements the Charm interface.
27
var _ Charm = (*Bundle)(nil)
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)
40
b, err := readBundle(f, fi.Size())
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)))
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)
60
reader, err := zipOpen(zipr, "metadata.yaml")
64
b.meta, err = ReadMeta(reader)
70
reader, err = zipOpen(zipr, "config.yaml")
71
if _, ok := err.(*noBundleFile); ok {
72
b.config = NewConfig()
73
} else if err != nil {
76
b.config, err = ReadConfig(reader)
83
reader, err = zipOpen(zipr, "revision")
85
if _, ok := err.(*noBundleFile); !ok {
88
b.revision = b.meta.OldRevision
90
_, err = fmt.Fscan(reader, &b.revision)
92
return nil, errors.New("invalid revision file")
99
func zipOpen(zipr *zip.Reader, path string) (rc io.ReadCloser, err error) {
100
for _, fh := range zipr.File {
105
return nil, &noBundleFile{path}
108
type noBundleFile struct {
112
func (err noBundleFile) Error() string {
113
return fmt.Sprintf("bundle file not found: %s", err.path)
116
// Revision returns the revision number for the charm
118
func (b *Bundle) Revision() int {
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
129
// Meta returns the Meta representing the metadata.yaml file from bundle.
130
func (b *Bundle) Meta() *Meta {
134
// Config returns the Config representing the config.yaml file
135
// for the charm bundle.
136
func (b *Bundle) Config() *Config {
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.
149
f, err := os.Open(b.Path)
162
zipr, err := zip.NewReader(r, size)
167
hooks := b.meta.Hooks()
169
for _, zfile := range zipr.File {
170
if err := b.expand(hooks, dir, zfile); err != nil {
175
revFile, err := os.Create(filepath.Join(dir, "revision"))
179
_, err = revFile.Write([]byte(strconv.Itoa(b.revision)))
187
// expand unpacks a charm's zip file into the given directory.
188
// The hooks map holds all the possible hook names in the
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" {
196
r, err := zfile.Open()
203
path := filepath.Join(dir, cleanName)
204
if strings.HasSuffix(zfile.Name, "/") || mode&os.ModeDir != 0 {
205
err = os.MkdirAll(path, mode&0777)
212
base, _ := filepath.Split(path)
213
err = os.MkdirAll(base, 0755)
218
if mode&os.ModeSymlink != 0 {
219
data, err := ioutil.ReadAll(r)
223
target := string(data)
224
if err := checkSymlinkTarget(dir, cleanName, target); err != nil {
227
return os.Symlink(target, path)
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)
237
if err := checkFileType(cleanName, mode); err != nil {
241
f, err := os.OpenFile(path, os.O_CREATE|os.O_EXCL|os.O_WRONLY, mode&0777)
245
_, err = io.Copy(f, r)
250
func checkSymlinkTarget(basedir, symlink, target string) error {
251
if filepath.IsAbs(target) {
252
return fmt.Errorf("symlink %q is absolute: %q", symlink, target)
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)
261
// FWIW, being able to do this is awesome.
262
type readAtBytes []byte
264
func (b readAtBytes) ReadAt(out []byte, off int64) (n int, err error) {
265
return copy(out, b[off:]), nil