Skip to content

Commit d7787d2

Browse files
authored
Merge pull request #675 from ericzbeard/awscli-modules
Add S3 URI handling to modules
2 parents c97f424 + 0ca4b4f commit d7787d2

File tree

16 files changed

+328
-231
lines changed

16 files changed

+328
-231
lines changed

cft/pkg/conditions.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ func evalCond(condName string, condValue interface{}, conditions map[string]inte
124124
return evaluateNot(not, conditions, module)
125125
}
126126
if equals, ok := v["Fn::Equals"]; ok {
127-
return evaluateEquals(equals, module)
127+
return evaluateEquals(equals)
128128
}
129129
if condition, ok := v["Condition"]; ok {
130130
// Reference to another condition
@@ -210,7 +210,7 @@ func evaluateNot(notExpr interface{}, conditions map[string]interface{}, module
210210
}
211211

212212
// evaluateEquals evaluates an Fn::Equals condition
213-
func evaluateEquals(equalsExpr interface{}, module *Module) (bool, error) {
213+
func evaluateEquals(equalsExpr interface{}) (bool, error) {
214214
equalsList, ok := equalsExpr.([]interface{})
215215
if !ok || len(equalsList) != 2 {
216216
return false, fmt.Errorf("Fn::Equals requires exactly two values")

cft/pkg/content.go

Lines changed: 35 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -17,33 +17,21 @@ type ModuleContent struct {
1717
BaseUri string
1818
}
1919

20-
// Helper function to handle zip file extraction with proper path resolution
21-
func handleZipFile(root string, location string, hash string, path string) ([]byte, error) {
22-
// Resolve the path to the zip file if it's a local path
23-
zipPath := location
24-
if !strings.HasPrefix(zipPath, "http://") && !strings.HasPrefix(zipPath, "https://") {
25-
// If it's a relative path, resolve it relative to the template's directory
26-
if !filepath.IsAbs(zipPath) {
27-
zipPath = filepath.Join(root, zipPath)
28-
}
29-
}
20+
func isHttpsUrl(uri string) bool {
21+
return strings.HasPrefix(uri, "https://")
22+
}
3023

31-
// Check if the zip file exists if it's a local file
32-
if !strings.HasPrefix(zipPath, "http://") && !strings.HasPrefix(zipPath, "https://") {
33-
_, err := os.Stat(zipPath)
34-
if err != nil {
35-
return nil, fmt.Errorf("error accessing zip file %s: %v", zipPath, err)
36-
}
37-
}
24+
func isS3URI(uri string) bool {
25+
return strings.HasPrefix(uri, "s3://")
26+
}
3827

39-
// Unzip, verify hash if there is one, and put the files in memory
40-
content, err := DownloadFromZip(zipPath, hash, path)
41-
if err != nil {
42-
config.Debugf("ZIP: Error extracting from zip: %v", err)
43-
return nil, err
28+
// resolveZipLocation ensures that local zip paths are resolved relative to the template's directory
29+
func resolveZipLocation(root string, zipLocation string) string {
30+
// For local files, resolve the path relative to the template's directory
31+
if !isS3URI(zipLocation) && !isHttpsUrl(zipLocation) && !filepath.IsAbs(zipLocation) {
32+
return filepath.Join(root, zipLocation)
4433
}
45-
46-
return content, nil
34+
return zipLocation
4735
}
4836

4937
// Get the module's content from a local file, memory, or a remote uri
@@ -54,6 +42,8 @@ func getModuleContent(
5442
baseUri string,
5543
uri string) (*ModuleContent, error) {
5644

45+
config.Debugf("getModuleContent root: %s, uri: %s", root, uri)
46+
5747
var content []byte
5848
var err error
5949
var newRootDir string
@@ -71,7 +61,8 @@ func getModuleContent(
7161

7262
if strings.HasSuffix(packageAlias.Location, ".zip") {
7363
isZip = true
74-
content, err = handleZipFile(root, packageAlias.Location, packageAlias.Hash, path)
64+
zipLocation := resolveZipLocation(root, packageAlias.Location)
65+
content, err = DownloadFromZip(zipLocation, packageAlias.Hash, path)
7566
if err != nil {
7667
return nil, err
7768
}
@@ -92,12 +83,21 @@ func getModuleContent(
9283
// getModuleContent: root=cft/pkg/tmpl/awscli-modules, baseUri=, uri=package.zip/zip-module.yaml
9384
if strings.Contains(uri, ".zip/") {
9485
isZip = true
95-
tokens := strings.Split(uri, "/")
96-
location := tokens[0]
97-
path := strings.Join(tokens[1:], "/")
98-
content, err = handleZipFile(root, location, "", path)
99-
if err != nil {
100-
return nil, err
86+
87+
// Extract the zip location and path within the zip
88+
zipIndex := strings.Index(uri, ".zip/")
89+
if zipIndex > 0 {
90+
zipLocation := uri[:zipIndex+4] // Include the .zip part
91+
zipPath := uri[zipIndex+5:] // Skip the .zip/ part
92+
93+
zipLocation = resolveZipLocation(root, zipLocation)
94+
config.Debugf("Extracting from zip: %s, path: %s", zipLocation, zipPath)
95+
96+
// Use DownloadFromZip directly - it can handle S3, HTTPS, and local files
97+
content, err = DownloadFromZip(zipLocation, "", zipPath)
98+
if err != nil {
99+
return nil, err
100+
}
101101
}
102102
}
103103

@@ -109,7 +109,8 @@ func getModuleContent(
109109
path := strings.Replace(uri, packageAlias.Alias+"/", "", 1)
110110
if strings.HasSuffix(packageAlias.Location, ".zip") {
111111
isZip = true
112-
content, err = handleZipFile(root, packageAlias.Location, packageAlias.Hash, path)
112+
zipLocation := resolveZipLocation(root, packageAlias.Location)
113+
content, err = DownloadFromZip(zipLocation, packageAlias.Hash, path)
113114
if err != nil {
114115
return nil, err
115116
}
@@ -122,7 +123,7 @@ func getModuleContent(
122123
// Is this a local file or a URL or did we already unzip a package?
123124
if isZip {
124125
config.Debugf("Using content from a zipped module package (length: %d bytes)", len(content))
125-
} else if strings.HasPrefix(uri, "https://") {
126+
} else if isHttpsUrl(uri) || isS3URI(uri) {
126127
config.Debugf("Downloading from URL: %s", uri)
127128
content, err = downloadModule(uri)
128129
if err != nil {
@@ -138,6 +139,7 @@ func getModuleContent(
138139
baseUri = strings.Join(urlParts[:len(urlParts)-1], "/")
139140

140141
} else {
142+
config.Debugf("Downloading from a local file, baseUri=%s, uri=%s", baseUri, uri)
141143
if baseUri != "" {
142144
// If we have a base URL, prepend it to the relative path
143145
uri = baseUri + "/" + uri

cft/pkg/download.go

Lines changed: 42 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
"path/filepath"
1212
"strings"
1313

14+
"github.com/aws-cloudformation/rain/internal/aws/s3"
1415
"github.com/aws-cloudformation/rain/internal/config"
1516
"github.com/google/uuid"
1617
)
@@ -42,11 +43,25 @@ func downloadHash(uri string) (string, error) {
4243

4344
// DownloadFromZip retrieves a single file from a zip file hosted on a URI
4445
func DownloadFromZip(uriString string, verifyHash string, path string) ([]byte, error) {
46+
47+
config.Debugf("DownloadFromZip uriString: %s, path: %s",
48+
uriString, path)
49+
4550
var zipData []byte
4651
var err error
47-
48-
// Check if it's a URL or local file
49-
if strings.HasPrefix(uriString, "http://") || strings.HasPrefix(uriString, "https://") {
52+
53+
isUrl := isHttpsUrl(uriString)
54+
isS3 := isS3URI(uriString)
55+
56+
// Check if it's an S3 URI, HTTPS URL, or local file
57+
if isS3 {
58+
// Download from S3
59+
config.Debugf("Downloading from S3: %s", uriString)
60+
zipData, err = downloadS3(uriString)
61+
if err != nil {
62+
return nil, fmt.Errorf("failed to download zip from S3: %v", err)
63+
}
64+
} else if isUrl {
5065
// Download from URL
5166
config.Debugf("Downloading %s", uriString)
5267
resp, err := http.Get(uriString)
@@ -59,7 +74,7 @@ func DownloadFromZip(uriString string, verifyHash string, path string) ([]byte,
5974
config.Debugf("Error closing body: %v", err)
6075
}
6176
}(resp.Body)
62-
77+
6378
zipData, err = io.ReadAll(resp.Body)
6479
if err != nil {
6580
return nil, err
@@ -112,7 +127,7 @@ func DownloadFromZip(uriString string, verifyHash string, path string) ([]byte,
112127

113128
// Download or read the hash
114129
var originalHash string
115-
if strings.HasPrefix(verifyHash, "http://") || strings.HasPrefix(verifyHash, "https://") {
130+
if isUrl {
116131
originalHash, err = downloadHash(verifyHash)
117132
if err != nil {
118133
return nil, err
@@ -215,9 +230,31 @@ func Unzip(f *os.File, dest string) error {
215230
return nil
216231
}
217232

233+
func downloadS3(uri string) ([]byte, error) {
234+
// Parse the S3 URI
235+
bucket, key, err := s3.ParseURI(uri)
236+
if err != nil {
237+
return nil, err
238+
}
239+
240+
// Download the file from S3
241+
content, err := s3.GetObject(bucket, key)
242+
if err != nil {
243+
return nil, err
244+
}
245+
246+
return content, nil
247+
}
248+
218249
// downloadModule downloads the file from the given URI and returns its content as a byte slice.
219250
func downloadModule(uri string) ([]byte, error) {
220251
config.Debugf("Downloading %s", uri)
252+
253+
// If it's an S3 uri, use the s3 package to download the file
254+
if strings.HasPrefix(uri, "s3://") {
255+
return downloadS3(uri)
256+
}
257+
221258
resp, err := http.Get(uri)
222259
if err != nil {
223260
return nil, err

cft/pkg/module.go

Lines changed: 4 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -128,11 +128,9 @@ func processModulesSection(t *cft.Template, n *yaml.Node,
128128
outputNode := node.MakeMapping()
129129

130130
err = processModule(
131-
name,
132131
parsed,
133132
outputNode,
134133
t,
135-
parsed.AsTemplate.Constants,
136134
moduleConfig,
137135
parentModule)
138136
if err != nil {
@@ -151,9 +149,6 @@ func processModulesSection(t *cft.Template, n *yaml.Node,
151149
config.Debugf("processModuleSection %s outputNode did not have any Resources", name)
152150
}
153151

154-
config.Debugf("\nparent template after %s processModule ==== \n%s\n",
155-
name, node.YamlStr(t.Node))
156-
157152
}
158153

159154
// Look for GetAtts like Content[].Arn that reference
@@ -188,11 +183,9 @@ func addScalarAttribute(out *yaml.Node, name string, moduleResource *yaml.Node,
188183

189184
// processModule performs all of the module logic and injects the content into the parent
190185
func processModule(
191-
logicalId string,
192186
parsedModule *ParsedModule,
193187
outputNode *yaml.Node,
194188
t *cft.Template,
195-
moduleConstants map[string]*yaml.Node,
196189
moduleConfig *cft.ModuleConfig,
197190
parentModule *Module) error {
198191

@@ -223,8 +216,7 @@ func processModule(
223216
},
224217
}
225218

226-
err = processRainSection(moduleAsTemplate,
227-
parsedModule.RootDir, parsedModule.FS)
219+
err = processRainSection(moduleAsTemplate)
228220
if err != nil {
229221
return err
230222
}
@@ -251,9 +243,6 @@ func processModule(
251243
return err
252244
}
253245

254-
config.Debugf("Module %s about to resolve t.Node in processModule",
255-
m.Config.Name)
256-
257246
// Resolve any references to this module in the parent template
258247
//err = m.Resolve(t.Node)
259248
//if err != nil {
@@ -376,8 +365,6 @@ func (module *Module) ProcessResources(outputNode *yaml.Node) error {
376365
// Some refs are to other resources in the module
377366
// Other refs are to the module's parameters
378367

379-
config.Debugf("%s about to resolve resource %s", module.Config.Name, nameNode.Value)
380-
381368
err = module.Resolve(clonedResource)
382369
if err != nil {
383370
return fmt.Errorf("failed to resolve refs: %v", err)
@@ -403,13 +390,6 @@ func (module *Module) ProcessResources(outputNode *yaml.Node) error {
403390
// if we try to resolve it again later.
404391
module.ParentTemplate.AddResolvedModuleNode(clonedResource)
405392

406-
parentName := ""
407-
if module.ParentModule != nil {
408-
parentName = module.ParentModule.Config.Name
409-
}
410-
config.Debugf("Module %s (p:%s) adding resource:\n%s\n", module.Config.Name,
411-
parentName,
412-
node.YamlStr(clonedResource))
413393
}
414394

415395
return nil
@@ -422,7 +402,6 @@ func processRainResourceModule(
422402
outputNode *yaml.Node,
423403
t *cft.Template,
424404
parent node.NodePair,
425-
moduleConstants map[string]*yaml.Node,
426405
source string,
427406
parsed *ParsedModule) error {
428407

@@ -455,7 +434,7 @@ func processRainResourceModule(
455434
}
456435
moduleConfig.Source = source
457436

458-
return processModule(logicalId, parsed, outputNode, t, moduleConstants, moduleConfig, nil)
437+
return processModule(parsed, outputNode, t, moduleConfig, nil)
459438
}
460439

461440
func checkPackageAlias(t *cft.Template, uri string) *cft.PackageAlias {
@@ -551,7 +530,7 @@ func module(ctx *directiveContext) (bool, error) {
551530
}
552531

553532
// This needs to happen before recursing, since sub-modules need resolved constants in the parent
554-
err = processRainSection(moduleAsTemplate, moduleContent.NewRootDir, ctx.fs)
533+
err = processRainSection(moduleAsTemplate)
555534
if err != nil {
556535
return false, err
557536
}
@@ -570,8 +549,7 @@ func module(ctx *directiveContext) (bool, error) {
570549

571550
// Create a new node to represent the parsed module
572551
var outputNode yaml.Node
573-
err = processRainResourceModule(moduleNode,
574-
&outputNode, t, parent, moduleAsTemplate.Constants, uri, parsed)
552+
err = processRainResourceModule(moduleNode, &outputNode, t, parent, uri, parsed)
575553
if err != nil {
576554
config.Debugf("processModule error: %v, moduleNode: %s", err, node.ToSJson(moduleNode))
577555
return false, fmt.Errorf("failed to process module %s: %v", uri, err)

cft/pkg/pkg.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ func transform(ctx *transformContext) (bool, error) {
106106

107107
// processRainSection returns true if the Rain section of the template existed
108108
// The section is removed by this function
109-
func processRainSection(t *cft.Template, rootDir string, fs *embed.FS) error {
109+
func processRainSection(t *cft.Template) error {
110110
t.Constants = make(map[string]*yaml.Node)
111111
rainNode, err := t.GetSection(cft.Rain)
112112
if err != nil {
@@ -139,7 +139,7 @@ func Template(t *cft.Template, rootDir string, fs *embed.FS) (*cft.Template, err
139139
var err error
140140

141141
// First look for a Rain section and store constants
142-
err = processRainSection(t, rootDir, fs)
142+
err = processRainSection(t)
143143
if err != nil {
144144
return nil, fmt.Errorf("failed to process Rain section: %v", err)
145145
}

cft/pkg/resolve.go

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,8 @@ func (module *Module) Resolve(n *yaml.Node) error {
2424
vf := func(v *visitor.Visitor) {
2525
vn := v.GetYamlNode()
2626

27-
if vn.Value != "" {
28-
config.Debugf("Resolve %s", vn.Value)
29-
}
30-
3127
if vn == module.Config.Node {
3228
// Don't resolve my own config
33-
config.Debugf("Resolve skipping self:\n%s\n", node.YamlStr(vn))
3429
v.SkipChildren()
3530
return
3631
}
@@ -44,7 +39,6 @@ func (module *Module) Resolve(n *yaml.Node) error {
4439
if module.ParentTemplate.ModuleAlreadyResolved(vn) {
4540
// If we marked a node as resolved, skip all
4641
// of its child nodes
47-
config.Debugf("Resolve skipping:\n%s\n", node.YamlStr(vn))
4842
v.SkipChildren()
4943
return
5044
}
@@ -182,8 +176,6 @@ func (module *Module) resolveParam(params *yaml.Node, n *yaml.Node, parentProps
182176
// ${Foo.Bar} is treated like a GetAtt.
183177
func (module *Module) ResolveSub(n *yaml.Node) error {
184178

185-
original := node.Clone(n)
186-
187179
prop := n.Content[1]
188180
words, err := parse.ParseSub(prop.Value, true)
189181
if err != nil {
@@ -256,10 +248,6 @@ func (module *Module) ResolveSub(n *yaml.Node) error {
256248

257249
*n = *newProp
258250

259-
config.Debugf("ResolveSub:\n%s\n -> \n%s\n",
260-
node.YamlStr(original),
261-
node.YamlStr(n))
262-
263251
return nil
264252
}
265253

0 commit comments

Comments
 (0)