2
Copyright 2014 The Camlistore Authors
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
8
http://www.apache.org/licenses/LICENSE-2.0
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.
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.
21
// It was initially only meant for small files, and as such, it can only
22
// read files smaller than 1MB for now.
35
"github.com/juju/go4/wkfs"
36
"golang.org/x/net/context"
38
"golang.org/x/oauth2/google"
39
"google.golang.org/cloud"
40
"google.golang.org/cloud/compute/metadata"
41
"google.golang.org/cloud/storage"
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
51
if !metadata.OnGCE() {
54
hc, err := google.DefaultClient(oauth2.NoContext)
56
registerBrokenFS(fmt.Errorf("could not get http client for context: %v", err))
59
projID, err := metadata.ProjectID()
60
if projID == "" || err != nil {
61
registerBrokenFS(fmt.Errorf("could not get GCE project ID: %v", err))
64
ctx := cloud.NewContext(projID, hc)
65
sc, err := storage.NewClient(ctx)
67
registerBrokenFS(fmt.Errorf("could not get cloud storage client: %v", err))
70
wkfs.RegisterFS("/gcs/", &gcsFS{
79
err error // sticky error
82
func registerBrokenFS(err error) {
83
wkfs.RegisterFS("/gcs/", &gcsFS{
88
func (fs *gcsFS) parseName(name string) (bucket, fileName string, err error) {
92
name = strings.TrimPrefix(name, "/gcs/")
93
i := strings.Index(name, "/")
97
return name[:i], name[i+1:], nil
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)
107
obj := fs.sc.Bucket(bucket).Object(fileName)
108
attrs, err := obj.Attrs(fs.ctx)
114
return nil, fmt.Errorf("file %s too large (%d bytes) for /gcs/ filesystem", name, size)
116
rc, err := obj.NewReader(fs.ctx)
122
slurp, err := ioutil.ReadAll(io.LimitReader(rc, size))
128
Reader: bytes.NewReader(slurp),
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)
138
attrs, err := fs.sc.Bucket(bucket).Object(fileName).Attrs(fs.ctx)
139
if err == storage.ErrObjectNotExist {
140
return nil, os.ErrNotExist
151
func (fs *gcsFS) MkdirAll(path string, perm os.FileMode) error { return nil }
153
func (fs *gcsFS) OpenFile(name string, flag int, perm os.FileMode) (wkfs.FileWriter, error) {
154
bucket, fileName, err := fs.parseName(name)
159
case os.O_WRONLY | os.O_CREATE | os.O_EXCL:
160
case os.O_WRONLY | os.O_CREATE | os.O_TRUNC:
162
return nil, fmt.Errorf("Unsupported OpenFlag flag mode %d on Google Cloud Storage", flag)
164
if flag&os.O_EXCL != 0 {
165
if _, err := fs.Stat(name); err == nil {
166
return nil, os.ErrExist
169
// TODO(mpl): consider adding perm to the object's ObjectAttrs.Metadata
170
return fs.sc.Bucket(bucket).Object(fileName).NewWriter(fs.ctx), nil
173
type statInfo struct {
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 }
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")