Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions provider/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ require (
github.com/pulumi/pulumi/pkg/v3 v3.218.0
github.com/pulumi/pulumi/sdk/v3 v3.218.0
github.com/stretchr/testify v1.10.0
github.com/wI2L/jsondiff v0.5.1
github.com/wI2L/jsondiff v0.7.0
github.com/zclconf/go-cty v1.13.2
go.uber.org/mock v0.4.0
google.golang.org/grpc v1.72.1
Expand Down Expand Up @@ -135,7 +135,7 @@ require (
github.com/spf13/cobra v1.10.1 // indirect
github.com/spf13/pflag v1.0.10 // indirect
github.com/texttheater/golang-levenshtein v1.0.1 // indirect
github.com/tidwall/gjson v1.17.0 // indirect
github.com/tidwall/gjson v1.18.0 // indirect
github.com/tidwall/match v1.1.1 // indirect
github.com/tidwall/pretty v1.2.1 // indirect
github.com/tidwall/sjson v1.2.5 // indirect
Expand Down
6 changes: 4 additions & 2 deletions provider/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -524,6 +524,8 @@ github.com/texttheater/golang-levenshtein v1.0.1/go.mod h1:PYAKrbF5sAiq9wd+H82hs
github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/gjson v1.17.0 h1:/Jocvlh98kcTfpN2+JzGQWQcqrPQwDrVEMApx/M5ZwM=
github.com/tidwall/gjson v1.17.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY=
github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
Expand All @@ -535,8 +537,8 @@ github.com/uber/jaeger-client-go v2.30.0+incompatible h1:D6wyKGCecFaSRUpo8lCVbaO
github.com/uber/jaeger-client-go v2.30.0+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk=
github.com/uber/jaeger-lib v2.4.1+incompatible h1:td4jdvLcExb4cBISKIpHuGoVXh+dVKhn2Um6rjCsSsg=
github.com/uber/jaeger-lib v2.4.1+incompatible/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6+uUTzImX/AauajbLI56U=
github.com/wI2L/jsondiff v0.5.1 h1:xS4zYUspH4U3IB0Lwo9+jv+MSRJSWMF87Y4BpDbFMHo=
github.com/wI2L/jsondiff v0.5.1/go.mod h1:qqG6hnK0Lsrz2BpIVCxWiK9ItsBCpIZQiv0izJjOZ9s=
github.com/wI2L/jsondiff v0.7.0 h1:1lH1G37GhBPqCfp/lrs91rf/2j3DktX6qYAKZkLuCQQ=
github.com/wI2L/jsondiff v0.7.0/go.mod h1:KAEIojdQq66oJiHhDyQez2x+sRit0vIzC9KeK0yizxM=
github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM=
github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw=
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c=
Expand Down
13 changes: 13 additions & 0 deletions provider/pkg/client/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@ type CloudControlApiClient interface {

// GetResourceRequestStatus returns the current status of a resource operation request.
GetResourceRequestStatus(ctx context.Context, requestToken string) (*types.ProgressEvent, error)

// GetResourceRequestStatusWithHooks returns the current status of a resource operation request with hook information.
GetResourceRequestStatusWithHooks(ctx context.Context, requestToken string) (*types.ProgressEvent, []types.HookProgressEvent, error)
}

// NewCloudControlApiClient creates a wrapper around the AWS SDK's Cloud Control client.
Expand Down Expand Up @@ -134,3 +137,13 @@ func (client *ccApiClientImpl) GetResourceRequestStatus(ctx context.Context, req
}
return res.ProgressEvent, nil
}

func (client *ccApiClientImpl) GetResourceRequestStatusWithHooks(ctx context.Context, requestToken string) (*types.ProgressEvent, []types.HookProgressEvent, error) {
res, err := client.cctl.GetResourceRequestStatus(ctx, &cloudcontrol.GetResourceRequestStatusInput{
RequestToken: aws.String(requestToken),
})
if err != nil {
return nil, nil, err
}
return res.ProgressEvent, res.HooksProgressEvent, nil
}
31 changes: 28 additions & 3 deletions provider/pkg/client/awaiter.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"context"
"time"

"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/aws/retry"
"github.com/aws/aws-sdk-go-v2/service/cloudcontrol/types"
"github.com/golang/glog"
Expand Down Expand Up @@ -42,7 +43,7 @@ func (a *ccAwaiterImpl) WaitForResourceOpCompletion(ctx context.Context, pi *typ
}
glog.V(9).Infof("waiting for resource %q: attempt #%d status %q", identifier, i, status)

finished, err := hasFinished(pi)
finished, err := hasFinishedWithHooks(pi, nil)
if finished || err != nil {
return pi, err
}
Expand All @@ -69,15 +70,25 @@ func (a *ccAwaiterImpl) WaitForResourceOpCompletion(ctx context.Context, pi *typ
return nil, errors.New("missing request token")
}

pi, err = a.client.GetResourceRequestStatus(ctx, *pi.RequestToken)
var hookEvents []types.HookProgressEvent
pi, hookEvents, err = a.client.GetResourceRequestStatusWithHooks(ctx, *pi.RequestToken)
if err != nil {
return nil, errors.Wrap(err, "getting resource request status")
}

finished, err = hasFinishedWithHooks(pi, hookEvents)
if finished || err != nil {
return pi, err
}
i += 1
}
}

func hasFinished(pi *types.ProgressEvent) (bool, error) {
return hasFinishedWithHooks(pi, nil)
}

func hasFinishedWithHooks(pi *types.ProgressEvent, hookEvents []types.HookProgressEvent) (bool, error) {
status := pi.OperationStatus
switch status {
case "SUCCESS":
Expand All @@ -87,7 +98,21 @@ func hasFinished(pi *types.ProgressEvent) (bool, error) {
if pi.StatusMessage != nil {
statusMessage = *pi.StatusMessage
}
return true, errors.Errorf("operation %s failed with %q: %s", pi.Operation, pi.ErrorCode, statusMessage)

hookErr := NewHookError(string(pi.Operation), string(pi.ErrorCode), statusMessage)

// Add hook information if available
if len(hookEvents) > 0 {
for _, hookEvent := range hookEvents {
hookStatus := aws.ToString(hookEvent.HookStatus)
// Check for failed hook statuses
if hookStatus == "HOOK_COMPLETE_FAILED" || hookStatus == "HOOK_FAILED" {
hookErr.WithHookEvent(hookEvent)
}
}
}

return true, hookErr
case "IN_PROGRESS", "PENDING":
return false, nil
default:
Expand Down
5 changes: 5 additions & 0 deletions provider/pkg/client/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -368,6 +368,11 @@ func (m *mockAPI) GetResourceRequestStatus(ctx context.Context, requestToken str
panic("not implemented")
}

// GetResourceRequestStatusWithHooks returns the current status of a resource operation request with hook information.
func (m *mockAPI) GetResourceRequestStatusWithHooks(ctx context.Context, requestToken string) (*types.ProgressEvent, []types.HookProgressEvent, error) {
panic("not implemented")
}

func (m *mockAPI) WaitForResourceOpCompletion(ctx context.Context, pi *types.ProgressEvent) (*types.ProgressEvent, error) {
return m.WaitForResourceOpCompletionFunc(ctx, pi)
}
Expand Down
45 changes: 45 additions & 0 deletions provider/pkg/client/hook_error.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package client

import (
"fmt"
"strings"
"time"

"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/cloudcontrol/types"
)

// HookError represents an error with CloudFormation hook information
type HookError struct {
baseMessage string
errorCode string
hookErrors []string
}

func NewHookError(operation string, errorCode string, statusMessage string) *HookError {
return &HookError{
baseMessage: fmt.Sprintf("operation %s failed with %q: %s", operation, errorCode, statusMessage),
errorCode: errorCode,
hookErrors: []string{},
}
}

func (e *HookError) WithHookEvent(hookEvent types.HookProgressEvent) *HookError {
hookMessage := fmt.Sprintf("HookName: %s, HookArn: %s, HookVersion: %s, Time: %s, HookMessage: %s",
aws.ToString(hookEvent.HookTypeName),
aws.ToString(hookEvent.HookTypeArn),
aws.ToString(hookEvent.HookTypeVersionId),
hookEvent.HookEventTime.Format(time.RFC3339),
aws.ToString(hookEvent.HookStatusMessage))

e.hookErrors = append(e.hookErrors, hookMessage)
return e
}

func (e *HookError) Error() string {
msg := e.baseMessage
if len(e.hookErrors) > 0 {
msg += ". Hook failures: " + strings.Join(e.hookErrors, "; ")
}
return msg
}