1package storage
2
3import (
4	"context"
5	"encoding/hex"
6	"encoding/json"
7	"fmt"
8	"io"
9
10	gcs "cloud.google.com/go/storage"
11	"github.com/mholt/archiver"
12	oauthgoogle "golang.org/x/oauth2/google"
13	"google.golang.org/api/iterator"
14	"google.golang.org/api/option"
15)
16
17// untested gcs api instantiation
18type objectHandleWrapper struct {
19	objectHandle *gcs.ObjectHandle
20}
21
22func (o objectHandleWrapper) Version() (Version, error) {
23	r, err := o.objectHandle.Attrs(context.Background())
24	if err == gcs.ErrObjectNotExist {
25		return Version{}, ObjectNotFoundError
26	}
27	if err != nil {
28		return Version{}, err
29	}
30
31	return Version{Name: r.Name, Ref: hex.EncodeToString(r.MD5), Updated: r.Updated}, nil
32}
33
34func (o objectHandleWrapper) NewReader() (io.ReadCloser, error) {
35	r, err := o.objectHandle.NewReader(context.Background())
36	if err == gcs.ErrObjectNotExist {
37		return nil, ObjectNotFoundError
38	}
39	return r, err
40}
41
42func (o objectHandleWrapper) NewWriter() io.WriteCloser {
43	return o.objectHandle.NewWriter(context.Background())
44}
45
46type bucketHandleWrapper struct {
47	bucketHandle *gcs.BucketHandle
48}
49
50func (b bucketHandleWrapper) GetAllObjects() ([]Object, error) {
51	objectIter := b.bucketHandle.Objects(context.Background(), nil)
52
53	var objects []Object
54	for {
55		next, err := objectIter.Next()
56		if err == iterator.Done {
57			break
58		}
59		if err != nil {
60			return nil, err
61		}
62		handle := objectHandleWrapper{objectHandle: b.bucketHandle.Object(next.Name)}
63		objects = append(objects, handle)
64	}
65	return objects, nil
66}
67
68func (b bucketHandleWrapper) Delete() error {
69	objectIter := b.bucketHandle.Objects(context.Background(), nil)
70
71	for {
72		next, err := objectIter.Next()
73		if err == iterator.Done {
74			break
75		}
76		if err != nil {
77			return err
78		}
79		err = b.bucketHandle.Object(next.Name).Delete(context.Background())
80		if err != nil {
81			return err
82		}
83	}
84	return b.bucketHandle.Delete(context.Background())
85}
86
87func NewGCSStorage(serviceAccountKey, objectName, bucketName string) (Storage, error) {
88	storageJwtConf, err := oauthgoogle.JWTConfigFromJSON([]byte(serviceAccountKey), gcs.ScopeReadWrite)
89	if err != nil {
90		return Storage{}, fmt.Errorf("failed to form JWT config from GCP storage account key: %s", err)
91	}
92	ctx := context.Background()
93	tokenSource := storageJwtConf.TokenSource(ctx)
94
95	storageClient, err := gcs.NewClient(ctx, option.WithTokenSource(tokenSource))
96	if err != nil {
97		return Storage{}, fmt.Errorf("failed to instantiate storageclient: %s", err)
98	}
99
100	p := struct {
101		ProjectId string `json:"project_id"`
102	}{}
103	if err := json.Unmarshal([]byte(serviceAccountKey), &p); err != nil {
104		return Storage{}, fmt.Errorf("Unmarshalling account key for project id: %s", err)
105	}
106	bucket := storageClient.Bucket(bucketName).UserProject(p.ProjectId)
107
108	_, err = bucket.Attrs(ctx)
109	if err != nil && err != gcs.ErrBucketNotExist {
110		return Storage{}, fmt.Errorf("Failed to get bucket: %s", err)
111	} else if err == gcs.ErrBucketNotExist {
112		err = bucket.Create(ctx, p.ProjectId, nil)
113	}
114	if err != nil {
115		return Storage{}, fmt.Errorf("Failed to create bucket: %s", err)
116	}
117
118	object := bucket.Object(objectName)
119
120	return Storage{
121		Name: objectName,
122		Bucket: bucketHandleWrapper{
123			bucketHandle: bucket,
124		},
125		Object: objectHandleWrapper{
126			objectHandle: object,
127		},
128		Archiver: archiver.TarGz,
129	}, nil
130}
131