Skip to content

Commit 6659402

Browse files
authored
Initial commit for AWS Catalog (#3372)
1 parent 710ac08 commit 6659402

File tree

2 files changed

+472
-0
lines changed

2 files changed

+472
-0
lines changed

v2/pkg/catalog/aws/catalog.go

Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
package aws
2+
3+
import (
4+
"bytes"
5+
"context"
6+
"errors"
7+
"fmt"
8+
"io"
9+
"path/filepath"
10+
"strings"
11+
12+
"github.com/aws/aws-sdk-go-v2/aws"
13+
"github.com/aws/aws-sdk-go-v2/config"
14+
"github.com/aws/aws-sdk-go-v2/credentials"
15+
"github.com/aws/aws-sdk-go-v2/feature/s3/manager"
16+
"github.com/aws/aws-sdk-go-v2/service/s3"
17+
)
18+
19+
// Catalog manages the AWS S3 template catalog
20+
type Catalog struct {
21+
svc client
22+
}
23+
24+
// client interface abstracts S3 connections
25+
type client interface {
26+
getAllKeys() ([]string, error)
27+
downloadKey(name string) (io.ReadCloser, error)
28+
setBucket(bucket string)
29+
}
30+
31+
type s3svc struct {
32+
client *s3.Client
33+
bucket string
34+
}
35+
36+
// NewCatalog creates a new AWS Catalog object given a required S3 bucket name and optional configurations. If
37+
// no configurations to set AWS keys are provided then environment variables will be used to obtain AWS credentials.
38+
func NewCatalog(bucket string, configurations ...func(*Catalog) error) (Catalog, error) {
39+
var c Catalog
40+
41+
for _, configuration := range configurations {
42+
err := configuration(&c)
43+
if err != nil {
44+
return c, err
45+
}
46+
}
47+
48+
if c.svc == nil {
49+
cfg, err := config.LoadDefaultConfig(context.TODO())
50+
if err != nil {
51+
return c, err
52+
}
53+
54+
c.svc = &s3svc{
55+
client: s3.NewFromConfig(cfg),
56+
}
57+
}
58+
c.svc.setBucket(bucket)
59+
60+
return c, nil
61+
}
62+
63+
// WithAWSKeys enables explicitly setting the AWS access key, secret key and region
64+
func WithAWSKeys(accessKey, secretKey, region string) func(*Catalog) error {
65+
return func(c *Catalog) error {
66+
cfg, err := config.LoadDefaultConfig(context.TODO(),
67+
config.WithCredentialsProvider(credentials.NewStaticCredentialsProvider(accessKey, secretKey, "")),
68+
config.WithRegion(region))
69+
if err != nil {
70+
return err
71+
}
72+
73+
c.svc = &s3svc{
74+
client: s3.NewFromConfig(cfg),
75+
bucket: "",
76+
}
77+
78+
return nil
79+
}
80+
}
81+
82+
// OpenFile downloads a file from S3 and returns the contents as an io.ReadCloser
83+
func (c Catalog) OpenFile(filename string) (io.ReadCloser, error) {
84+
if filename == "" {
85+
return nil, errors.New("empty filename")
86+
}
87+
88+
return c.svc.downloadKey(filename)
89+
}
90+
91+
// GetTemplatePath looks for a target string performing a simple substring check
92+
// against all S3 keys. If the input includes a wildcard (*) it is removed.
93+
func (c Catalog) GetTemplatePath(target string) ([]string, error) {
94+
target = strings.ReplaceAll(target, "*", "")
95+
96+
keys, err := c.svc.getAllKeys()
97+
if err != nil {
98+
return nil, err
99+
}
100+
101+
var matches []string
102+
for _, key := range keys {
103+
if strings.Contains(key, target) {
104+
matches = append(matches, key)
105+
}
106+
}
107+
108+
return matches, nil
109+
}
110+
111+
// GetTemplatesPath returns all templates from S3
112+
func (c Catalog) GetTemplatesPath(definitions []string) ([]string, map[string]error) {
113+
keys, err := c.svc.getAllKeys()
114+
if err != nil {
115+
// necessary to implement the Catalog interface
116+
return nil, map[string]error{"aws": err}
117+
}
118+
119+
return keys, nil
120+
}
121+
122+
// ResolvePath gets a full S3 key given the first param. If the second parameter is
123+
// provided it tries to find paths relative to the second path.
124+
func (c Catalog) ResolvePath(templateName, second string) (string, error) {
125+
keys, err := c.svc.getAllKeys()
126+
if err != nil {
127+
return "", err
128+
}
129+
130+
// if c second path is given, it's c folder and we join the two and check against keys
131+
if second != "" {
132+
target := filepath.Join(filepath.Dir(second), templateName)
133+
for _, key := range keys {
134+
if key == target {
135+
return key, nil
136+
}
137+
}
138+
}
139+
140+
// check if templateName is already an absolute path to c key
141+
for _, key := range keys {
142+
if key == templateName {
143+
return templateName, nil
144+
}
145+
}
146+
147+
return "", fmt.Errorf("no such path found: %s", templateName)
148+
}
149+
150+
func (s *s3svc) getAllKeys() ([]string, error) {
151+
paginator := s3.NewListObjectsV2Paginator(s.client, &s3.ListObjectsV2Input{
152+
Bucket: &s.bucket,
153+
})
154+
155+
var keys []string
156+
157+
for paginator.HasMorePages() {
158+
page, err := paginator.NextPage(context.TODO())
159+
if err != nil {
160+
return nil, err
161+
}
162+
for _, obj := range page.Contents {
163+
key := aws.ToString(obj.Key)
164+
keys = append(keys, key)
165+
}
166+
}
167+
168+
return keys, nil
169+
}
170+
171+
func (s *s3svc) downloadKey(name string) (io.ReadCloser, error) {
172+
downloader := manager.NewDownloader(s.client)
173+
buf := manager.NewWriteAtBuffer([]byte{})
174+
_, err := downloader.Download(context.TODO(), buf, &s3.GetObjectInput{
175+
Bucket: aws.String(s.bucket),
176+
Key: aws.String(name),
177+
})
178+
if err != nil {
179+
return nil, err
180+
}
181+
182+
return io.NopCloser(bytes.NewReader(buf.Bytes())), nil
183+
}
184+
185+
func (s *s3svc) setBucket(bucket string) {
186+
s.bucket = bucket
187+
}

0 commit comments

Comments
 (0)