~nskaggs/+junk/xenial-test

« back to all changes in this revision

Viewing changes to src/github.com/juju/go4/wkfs/gcs/gcs.go

  • Committer: Nicholas Skaggs
  • Date: 2016-10-24 20:56:05 UTC
  • Revision ID: nicholas.skaggs@canonical.com-20161024205605-z8lta0uvuhtxwzwl
Initi with beta15

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/*
 
2
Copyright 2014 The Camlistore Authors
 
3
 
 
4
Licensed under the Apache License, Version 2.0 (the "License");
 
5
you may not use this file except in compliance with the License.
 
6
You may obtain a copy of the License at
 
7
 
 
8
     http://www.apache.org/licenses/LICENSE-2.0
 
9
 
 
10
Unless required by applicable law or agreed to in writing, software
 
11
distributed under the License is distributed on an "AS IS" BASIS,
 
12
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 
13
See the License for the specific language governing permissions and
 
14
limitations under the License.
 
15
*/
 
16
 
 
17
// Package gcs registers a Google Cloud Storage filesystem at the
 
18
// well-known /gcs/ filesystem path if the current machine is running
 
19
// on Google Compute Engine.
 
20
//
 
21
// It was initially only meant for small files, and as such, it can only
 
22
// read files smaller than 1MB for now.
 
23
package gcs
 
24
 
 
25
import (
 
26
        "bytes"
 
27
        "fmt"
 
28
        "io"
 
29
        "io/ioutil"
 
30
        "os"
 
31
        "path"
 
32
        "strings"
 
33
        "time"
 
34
 
 
35
        "github.com/juju/go4/wkfs"
 
36
        "golang.org/x/net/context"
 
37
        "golang.org/x/oauth2"
 
38
        "golang.org/x/oauth2/google"
 
39
        "google.golang.org/cloud"
 
40
        "google.golang.org/cloud/compute/metadata"
 
41
        "google.golang.org/cloud/storage"
 
42
)
 
43
 
 
44
// Max size for all files read, because we use a bytes.Reader as our file
 
45
// reader, instead of storage.NewReader. This is because we get all wkfs.File
 
46
// methods for free by embedding a bytes.Reader. This filesystem was only supposed
 
47
// to be for configuration data only, so this is ok for now.
 
48
const maxSize = 1 << 20
 
49
 
 
50
func init() {
 
51
        if !metadata.OnGCE() {
 
52
                return
 
53
        }
 
54
        hc, err := google.DefaultClient(oauth2.NoContext)
 
55
        if err != nil {
 
56
                registerBrokenFS(fmt.Errorf("could not get http client for context: %v", err))
 
57
                return
 
58
        }
 
59
        projID, err := metadata.ProjectID()
 
60
        if projID == "" || err != nil {
 
61
                registerBrokenFS(fmt.Errorf("could not get GCE project ID: %v", err))
 
62
                return
 
63
        }
 
64
        ctx := cloud.NewContext(projID, hc)
 
65
        sc, err := storage.NewClient(ctx)
 
66
        if err != nil {
 
67
                registerBrokenFS(fmt.Errorf("could not get cloud storage client: %v", err))
 
68
                return
 
69
        }
 
70
        wkfs.RegisterFS("/gcs/", &gcsFS{
 
71
                ctx: ctx,
 
72
                sc:  sc,
 
73
        })
 
74
}
 
75
 
 
76
type gcsFS struct {
 
77
        ctx context.Context
 
78
        sc  *storage.Client
 
79
        err error // sticky error
 
80
}
 
81
 
 
82
func registerBrokenFS(err error) {
 
83
        wkfs.RegisterFS("/gcs/", &gcsFS{
 
84
                err: err,
 
85
        })
 
86
}
 
87
 
 
88
func (fs *gcsFS) parseName(name string) (bucket, fileName string, err error) {
 
89
        if fs.err != nil {
 
90
                return "", "", fs.err
 
91
        }
 
92
        name = strings.TrimPrefix(name, "/gcs/")
 
93
        i := strings.Index(name, "/")
 
94
        if i < 0 {
 
95
                return name, "", nil
 
96
        }
 
97
        return name[:i], name[i+1:], nil
 
98
}
 
99
 
 
100
// Open opens the named file for reading. It returns an error if the file size
 
101
// is larger than 1 << 20.
 
102
func (fs *gcsFS) Open(name string) (wkfs.File, error) {
 
103
        bucket, fileName, err := fs.parseName(name)
 
104
        if err != nil {
 
105
                return nil, err
 
106
        }
 
107
        obj := fs.sc.Bucket(bucket).Object(fileName)
 
108
        attrs, err := obj.Attrs(fs.ctx)
 
109
        if err != nil {
 
110
                return nil, err
 
111
        }
 
112
        size := attrs.Size
 
113
        if size > maxSize {
 
114
                return nil, fmt.Errorf("file %s too large (%d bytes) for /gcs/ filesystem", name, size)
 
115
        }
 
116
        rc, err := obj.NewReader(fs.ctx)
 
117
        if err != nil {
 
118
                return nil, err
 
119
        }
 
120
        defer rc.Close()
 
121
 
 
122
        slurp, err := ioutil.ReadAll(io.LimitReader(rc, size))
 
123
        if err != nil {
 
124
                return nil, err
 
125
        }
 
126
        return &file{
 
127
                name:   name,
 
128
                Reader: bytes.NewReader(slurp),
 
129
        }, nil
 
130
}
 
131
 
 
132
func (fs *gcsFS) Stat(name string) (os.FileInfo, error) { return fs.Lstat(name) }
 
133
func (fs *gcsFS) Lstat(name string) (os.FileInfo, error) {
 
134
        bucket, fileName, err := fs.parseName(name)
 
135
        if err != nil {
 
136
                return nil, err
 
137
        }
 
138
        attrs, err := fs.sc.Bucket(bucket).Object(fileName).Attrs(fs.ctx)
 
139
        if err == storage.ErrObjectNotExist {
 
140
                return nil, os.ErrNotExist
 
141
        }
 
142
        if err != nil {
 
143
                return nil, err
 
144
        }
 
145
        return &statInfo{
 
146
                name: attrs.Name,
 
147
                size: attrs.Size,
 
148
        }, nil
 
149
}
 
150
 
 
151
func (fs *gcsFS) MkdirAll(path string, perm os.FileMode) error { return nil }
 
152
 
 
153
func (fs *gcsFS) OpenFile(name string, flag int, perm os.FileMode) (wkfs.FileWriter, error) {
 
154
        bucket, fileName, err := fs.parseName(name)
 
155
        if err != nil {
 
156
                return nil, err
 
157
        }
 
158
        switch flag {
 
159
        case os.O_WRONLY | os.O_CREATE | os.O_EXCL:
 
160
        case os.O_WRONLY | os.O_CREATE | os.O_TRUNC:
 
161
        default:
 
162
                return nil, fmt.Errorf("Unsupported OpenFlag flag mode %d on Google Cloud Storage", flag)
 
163
        }
 
164
        if flag&os.O_EXCL != 0 {
 
165
                if _, err := fs.Stat(name); err == nil {
 
166
                        return nil, os.ErrExist
 
167
                }
 
168
        }
 
169
        // TODO(mpl): consider adding perm to the object's ObjectAttrs.Metadata
 
170
        return fs.sc.Bucket(bucket).Object(fileName).NewWriter(fs.ctx), nil
 
171
}
 
172
 
 
173
type statInfo struct {
 
174
        name    string
 
175
        size    int64
 
176
        isDir   bool
 
177
        modtime time.Time
 
178
}
 
179
 
 
180
func (si *statInfo) IsDir() bool        { return si.isDir }
 
181
func (si *statInfo) ModTime() time.Time { return si.modtime }
 
182
func (si *statInfo) Mode() os.FileMode  { return 0644 }
 
183
func (si *statInfo) Name() string       { return path.Base(si.name) }
 
184
func (si *statInfo) Size() int64        { return si.size }
 
185
func (si *statInfo) Sys() interface{}   { return nil }
 
186
 
 
187
type file struct {
 
188
        name string
 
189
        *bytes.Reader
 
190
}
 
191
 
 
192
func (*file) Close() error   { return nil }
 
193
func (f *file) Name() string { return path.Base(f.name) }
 
194
func (f *file) Stat() (os.FileInfo, error) {
 
195
        panic("Stat not implemented on /gcs/ files yet")
 
196
}