From 9740f91417319deb9dd8b7a0a1836d67f1709c25 Mon Sep 17 00:00:00 2001 From: jonathan stewart <5750305+jon-stewart@users.noreply.github.com> Date: Fri, 1 Mar 2024 16:05:47 +0000 Subject: [PATCH] fix: component download AWS XML error (#1572) --- cli/cmd/component.go | 8 +-- lwcomponent/catalog.go | 29 +++++++++++ lwcomponent/catalog_test.go | 97 ++++++++++++++++++++++++++++++++++--- lwcomponent/staging.go | 6 +++ lwcomponent/staging_test.go | 6 +-- 5 files changed, 133 insertions(+), 13 deletions(-) diff --git a/cli/cmd/component.go b/cli/cmd/component.go index e00cb78cd..3ae6e7c5d 100644 --- a/cli/cmd/component.go +++ b/cli/cmd/component.go @@ -443,10 +443,11 @@ func installComponent(args []string) (err error) { } stageClose, err := catalog.Stage(component, versionArg, progressClosure) + defer stageClose() if err != nil { + cli.StopProgress() return } - defer stageClose() downloadComplete <- 0 @@ -631,10 +632,11 @@ func updateComponent(args []string) (err error) { } stageClose, err := catalog.Stage(component, versionArg, progressClosure) + defer stageClose() if err != nil { + cli.StopProgress() return } - defer stageClose() downloadComplete <- 0 @@ -879,7 +881,7 @@ func componentsToTable() [][]string { return out } -func prototypeRunComponentsInstall(cmd *cobra.Command, args []string) (err error) { +func prototypeRunComponentsInstall(_ *cobra.Command, args []string) (err error) { var ( componentName string = args[0] downloadComplete = make(chan int8) diff --git a/lwcomponent/catalog.go b/lwcomponent/catalog.go index cb0ab23ba..f973b6d04 100644 --- a/lwcomponent/catalog.go +++ b/lwcomponent/catalog.go @@ -1,6 +1,7 @@ package lwcomponent import ( + "encoding/xml" "fmt" "os" "path/filepath" @@ -155,6 +156,11 @@ func (c *Catalog) Stage( return } + err = parseAWSXMLError(filepath.Join(stage.Directory(), stage.Filename())) + if err != nil { + return + } + if err = stage.Unpack(); err != nil { stage.Close() return @@ -444,3 +450,26 @@ func componentDirectory(componentName string) (string, error) { return filepath.Join(dir, componentName), nil } + +type awsXMLError struct { + xml.Name + Code string `xml:"Code"` + Message string `xml:"Message"` +} + +func parseAWSXMLError(path string) error { + data, err := os.ReadFile(path) + if err != nil { + return err + } + + xmlError := &awsXMLError{} + err = xml.Unmarshal(data, xmlError) + if err != nil { + return nil + } + + log.Error(string(data)) + + return errors.Errorf("Code: %s. Message: %s", xmlError.Code, xmlError.Message) +} diff --git a/lwcomponent/catalog_test.go b/lwcomponent/catalog_test.go index e735fe2e1..57ea16cd3 100644 --- a/lwcomponent/catalog_test.go +++ b/lwcomponent/catalog_test.go @@ -549,13 +549,57 @@ func TestCatalogStage(t *testing.T) { fmt.Fprint(w, generateComponentsResponse(prefix, apiComponentCount)) }) + url := "s3-download" + + fakeServer.MockAPI(url, func(w http.ResponseWriter, r *http.Request) { + dir, _ := os.MkdirTemp("", "cdk-component-stage-tar-gz-") + + path := MakeGzip(name, MakeTar(name, "1.0.0", dir, "component", "sig")) + + data, err := os.ReadFile(path) + if err != nil { + panic(err) + } + + w.Write(data) + }) + + XMLUrl := "s3-error" + + fakeServer.MockAPI(XMLUrl, func(w http.ResponseWriter, r *http.Request) { + data := []byte(` + PermanentRedirect + The bucket you are attempting to access must be addressed using the specified endpoint. + Please send all future requests to this endpoint. + lw-cdk-store.s3-us-west-2.amazonaws.com + lw-cdk-storeVFXE02WRA7339CW6`) + w.Write(data) + }) + + EOFUrl := "eof" + + fakeServer.MockAPI(EOFUrl, func(w http.ResponseWriter, r *http.Request) { + data := []byte("") + w.Write(data) + }) + fakeServer.MockAPI("Components/Artifact/1", func(w http.ResponseWriter, r *http.Request) { assert.Equal(t, "GET", r.Method, "Components API only accepts HTTP GET") - if r.URL.Query().Get("version") != version { - http.Error(w, "component version not found", http.StatusNotFound) - } else { - fmt.Fprint(w, generateFetchResponse(1, name, version, "")) + l := r.URL.Query().Get("version") + switch l { + case "1.0.0": + { + fmt.Fprint(w, generateFetchResponse(1, name, version, fmt.Sprintf("%s/api/v2/%s", fakeServer.URL(), url))) + } + case "3.0.1": + { + fmt.Fprint(w, generateFetchResponse(1, name, version, fmt.Sprintf("%s/api/v2/%s", fakeServer.URL(), EOFUrl))) + } + case "5.4.3": + { + fmt.Fprint(w, generateFetchResponse(1, name, version, fmt.Sprintf("%s/api/v2/%s", fakeServer.URL(), XMLUrl))) + } } }) @@ -575,7 +619,7 @@ func TestCatalogStage(t *testing.T) { api.WithURL(fakeServer.URL()), ) - catalog, err := lwcomponent.NewCatalog(client, newTestStage) + catalog, err := lwcomponent.NewCatalog(client, lwcomponent.NewStageTarGz) assert.NotNil(t, catalog) assert.Nil(t, err) @@ -589,6 +633,29 @@ func TestCatalogStage(t *testing.T) { defer stageClose() }) + // @jon-stewart: TODO GROW-2765 + // t.Run("EOF Error", func(t *testing.T) { + // component, err := catalog.GetComponent(name) + // assert.NotNil(t, component) + // assert.Nil(t, err) + + // stageClose, err := catalog.Stage(component, "3.0.1", ProgressClosure) + // assert.NotNil(t, err) + // defer stageClose() + // }) + + t.Run("AWS XML Error", func(t *testing.T) { + component, err := catalog.GetComponent(name) + assert.NotNil(t, component) + assert.Nil(t, err) + + stageClose, err := catalog.Stage(component, "5.4.3", ProgressClosure) + assert.NotNil(t, err) + assert.Contains(t, err.Error(), "PermanentRedirect") + + defer stageClose() + }) + t.Run("already installed", func(t *testing.T) { CreateLocalComponent(name, version, false) @@ -646,8 +713,19 @@ func (t *testStage) Directory() string { return t.dir } +// Filename implements lwcomponent.Stager. +func (t *testStage) Filename() string { + return "newTestStageFile" +} + // Download implements lwcomponent.Stager. -func (*testStage) Download(func(string, int64)) error { +func (t *testStage) Download(func(string, int64)) error { + file, err := os.Create(filepath.Join(t.dir, t.Filename())) + if err != nil { + log.Fatal(err) + } + defer file.Close() + return nil } @@ -678,7 +756,12 @@ func (*testStage) Validate() error { } func newTestStage(name, artifactUrl string, size int64) (stage lwcomponent.Stager, err error) { - stage = &testStage{} + dir, err := os.MkdirTemp("", "newTestStage") + if err != nil { + panic(err) + } + + stage = &testStage{dir: dir} return } diff --git a/lwcomponent/staging.go b/lwcomponent/staging.go index 7f94c7dd2..05b27774a 100644 --- a/lwcomponent/staging.go +++ b/lwcomponent/staging.go @@ -28,6 +28,8 @@ type Stager interface { Download(progressClosure func(filepath string, sizeB int64)) error + Filename() string + Signature() (sig []byte, err error) Unpack() error @@ -87,6 +89,10 @@ func (s *stageTarGz) Directory() string { return s.dir } +func (s *stageTarGz) Filename() string { + return filepath.Base(s.artifactUrl.Path) +} + func (s *stageTarGz) Download(progressClosure func(filepath string, sizeB int64)) (err error) { fileName := filepath.Base(s.artifactUrl.Path) diff --git a/lwcomponent/staging_test.go b/lwcomponent/staging_test.go index f7ecbea6a..d31cddc63 100644 --- a/lwcomponent/staging_test.go +++ b/lwcomponent/staging_test.go @@ -103,12 +103,12 @@ func TestStagingTarGzUnpack(t *testing.T) { assert.NotNil(t, stage) defer stage.Close() - makeGzip(name, makeTar(name, "1.1.1", stage.Directory(), componentData, sigData)) + MakeGzip(name, MakeTar(name, "1.1.1", stage.Directory(), componentData, sigData)) stage.Unpack() } -func makeTar(name string, version string, dir string, data string, sig string) string { +func MakeTar(name string, version string, dir string, data string, sig string) string { tarname := fmt.Sprintf("%s.tar", name) path := filepath.Join(dir, tarname) @@ -147,7 +147,7 @@ func makeTar(name string, version string, dir string, data string, sig string) s return path } -func makeGzip(name, path string) string { +func MakeGzip(name, path string) string { reader, err := os.Open(path) if err != nil { panic(err)