aboutsummaryrefslogtreecommitdiff
path: root/server/wkfs/s3fs/s3.go
blob: 331b55fc054dc52effe84c06044c732a4dfb21c5 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
package s3fs

import (
	"bytes"
	"errors"
	"io/ioutil"
	"os"
	"path"
	"strings"
	"time"

	"go4.org/wkfs"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/awserr"
	"github.com/aws/aws-sdk-go/aws/credentials"
	"github.com/aws/aws-sdk-go/aws/session"
	"github.com/aws/aws-sdk-go/service/s3"
	"github.com/nsheridan/cashier/server/config"
)

// Register the /s3/ filesystem as a well-known filesystem.
func Register(config *config.AWS) {
	if config == nil {
		registerBrokenFS(errors.New("aws credentials not found"))
		return
	}
	ac := &aws.Config{}
	// If region is unset the SDK will attempt to read the region from the environment.
	if config.Region != "" {
		ac.Region = aws.String(config.Region)
	}
	// Attempt to get credentials from the cashier config.
	// Otherwise check for standard credentials. If neither are present register the fs as broken.
	// TODO: implement this as a provider.
	if config.AccessKey != "" && config.SecretKey != "" {
		ac.Credentials = credentials.NewStaticCredentials(config.AccessKey, config.SecretKey, "")
	} else {
		_, err := session.New().Config.Credentials.Get()
		if err != nil {
			registerBrokenFS(errors.New("aws credentials not found"))
			return
		}
	}
	sc := s3.New(session.New(ac))
	if aws.StringValue(sc.Config.Region) == "" {
		registerBrokenFS(errors.New("aws region configuration not found"))
		return
	}
	wkfs.RegisterFS("/s3/", &s3FS{
		sc: sc,
	})
}

func registerBrokenFS(err error) {
	wkfs.RegisterFS("/s3/", &s3FS{
		err: err,
	})
}

type s3FS struct {
	sc  *s3.S3
	err error
}

func (fs *s3FS) parseName(name string) (bucket, fileName string, err error) {
	if fs.err != nil {
		return "", "", fs.err
	}
	name = strings.TrimPrefix(name, "/s3/")
	i := strings.Index(name, "/")
	if i < 0 {
		return name, "", nil
	}
	return name[:i], name[i+1:], nil
}

// Open opens the named file for reading.
func (fs *s3FS) Open(name string) (wkfs.File, error) {
	bucket, fileName, err := fs.parseName(name)
	if err != nil {
		return nil, err
	}
	obj, err := fs.sc.GetObject(&s3.GetObjectInput{
		Bucket: &bucket,
		Key:    &fileName,
	})
	if err != nil {
		return nil, err
	}
	defer obj.Body.Close()
	slurp, err := ioutil.ReadAll(obj.Body)
	if err != nil {
		return nil, err
	}
	return &file{
		name:   name,
		Reader: bytes.NewReader(slurp),
	}, nil
}

func (fs *s3FS) Stat(name string) (os.FileInfo, error) { return fs.Lstat(name) }
func (fs *s3FS) Lstat(name string) (os.FileInfo, error) {
	bucket, fileName, err := fs.parseName(name)
	if err != nil {
		return nil, err
	}
	obj, err := fs.sc.GetObject(&s3.GetObjectInput{
		Bucket: &bucket,
		Key:    &fileName,
	})
	if err != nil {
		if awsErr, ok := err.(awserr.Error); ok {
			if awsErr.Code() == "NoSuchKey" {
				return nil, os.ErrNotExist
			}
		}
	}
	if err != nil {
		return nil, err
	}
	return &statInfo{
		name: path.Base(fileName),
		size: *obj.ContentLength,
	}, nil
}

func (fs *s3FS) MkdirAll(path string, perm os.FileMode) error { return nil }

func (fs *s3FS) OpenFile(name string, flag int, perm os.FileMode) (wkfs.FileWriter, error) {
	return nil, errors.New("not implemented")
}

type statInfo struct {
	name    string
	size    int64
	isDir   bool
	modtime time.Time
}

func (si *statInfo) IsDir() bool        { return si.isDir }
func (si *statInfo) ModTime() time.Time { return si.modtime }
func (si *statInfo) Mode() os.FileMode  { return 0644 }
func (si *statInfo) Name() string       { return path.Base(si.name) }
func (si *statInfo) Size() int64        { return si.size }
func (si *statInfo) Sys() interface{}   { return nil }

type file struct {
	name string
	*bytes.Reader
}

func (*file) Close() error   { return nil }
func (f *file) Name() string { return path.Base(f.name) }
func (f *file) Stat() (os.FileInfo, error) {
	panic("Stat not implemented on /s3/ files yet")
}