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
85 changes: 85 additions & 0 deletions compat/aws/cloudformation_compat_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@ package aws

import (
"context"
"errors"
"testing"

"github.com/aws/aws-sdk-go-v2/aws"
awscfn "github.com/aws/aws-sdk-go-v2/service/cloudformation"
cfntypes "github.com/aws/aws-sdk-go-v2/service/cloudformation/types"
"github.com/aws/smithy-go"

cloudemu "github.com/stackshy/cloudemu/v2"
"github.com/stackshy/cloudemu/v2/internal/compat"
Expand Down Expand Up @@ -116,6 +118,7 @@ func TestAWSCloudFormationCompat(t *testing.T) {
return continueUpdateRollback(ctx, client)
})

stackControlOps(ctx, sess, client, stack)
changeSetOps(ctx, sess, client)
exportOps(ctx, sess, client)

Expand All @@ -125,6 +128,88 @@ func TestAWSCloudFormationCompat(t *testing.T) {
})
}

// stackControlOps sets and reads a stack policy, checks CancelUpdateStack
// refuses a stack that is not updating, and rolls an UPDATE_FAILED stack
// back.
func stackControlOps(ctx context.Context, sess *compat.AWSSession, client *awscfn.Client, stack string) {
const (
svc = "cloudformation"
policy = `{"Statement":[{"Effect":"Allow","Action":"Update:*","Principal":"*","Resource":"*"}]}`
)

sess.Op(svc, "SetStackPolicy", func() error {
_, err := client.SetStackPolicy(ctx, &awscfn.SetStackPolicyInput{
StackName: aws.String(stack), StackPolicyBody: aws.String(policy),
})

return err
})

sess.Op(svc, "GetStackPolicy", func() error {
out, err := client.GetStackPolicy(ctx, &awscfn.GetStackPolicyInput{StackName: aws.String(stack)})
if err != nil {
return err
}

if aws.ToString(out.StackPolicyBody) != policy {
return errCompat("policy not returned")
}

return nil
})

sess.Op(svc, "CancelUpdateStack", func() error {
_, err := client.CancelUpdateStack(ctx, &awscfn.CancelUpdateStackInput{StackName: aws.String(stack)})

var ae smithy.APIError
if !errors.As(err, &ae) || ae.ErrorCode() != "ValidationError" {
return errCompat("CancelUpdateStack on a finished update must be a ValidationError")
}

return nil
})

sess.Op(svc, "RollbackStack", func() error {
return rollbackStack(ctx, client)
})
}

// rollbackStack leaves a stack UPDATE_FAILED with rollback disabled, then
// rolls it back.
func rollbackStack(ctx context.Context, client *awscfn.Client) error {
const name = "compat-rollback-stack"

if _, err := client.CreateStack(ctx, &awscfn.CreateStackInput{
StackName: aws.String(name), TemplateBody: aws.String(changeSetTemplate),
}); err != nil {
return err
}

if _, err := client.UpdateStack(ctx, &awscfn.UpdateStackInput{
StackName: aws.String(name), DisableRollback: aws.Bool(true),
TemplateBody: aws.String(`{"Resources":{"Bad":{"Type":"AWS::Unknown::Thing"}}}`),
}); err != nil {
return err
}

if _, err := client.RollbackStack(ctx, &awscfn.RollbackStackInput{StackName: aws.String(name)}); err != nil {
return err
}

out, err := client.DescribeStacks(ctx, &awscfn.DescribeStacksInput{StackName: aws.String(name)})
if err != nil {
return err
}

if out.Stacks[0].StackStatus != cfntypes.StackStatusUpdateRollbackComplete {
return errCompat("status " + string(out.Stacks[0].StackStatus))
}

_, err = client.DeleteStack(ctx, &awscfn.DeleteStackInput{StackName: aws.String(name)})

return err
}

// changeSetOps creates a stack from a CREATE change set, then creates and
// deletes an UPDATE change set on it.
func changeSetOps(ctx context.Context, sess *compat.AWSSession, client *awscfn.Client) {
Expand Down
6 changes: 5 additions & 1 deletion docs/compat/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ Each service's summary breaks the verified counts out per SDK/language. Only lan

| Operation | AWS (CloudFormation) |
|---|---|
| CancelUpdateStack | ✅ |
| ContinueUpdateRollback | ✅ |
| CreateChangeSet | ✅ |
| CreateStack | ✅ |
Expand All @@ -49,19 +50,22 @@ Each service's summary breaks the verified counts out per SDK/language. Only lan
| DescribeStacks | ✅ |
| EstimateTemplateCost | ✅ |
| ExecuteChangeSet | ✅ |
| GetStackPolicy | ✅ |
| GetTemplate | ✅ |
| GetTemplateSummary | ✅ |
| ListChangeSets | ✅ |
| ListExports | ✅ |
| ListImports | ✅ |
| ListStackResources | ✅ |
| ListStacks | ✅ |
| RollbackStack | ✅ |
| SetStackPolicy | ✅ |
| UpdateStack | ✅ |
| UpdateTerminationProtection | ✅ |
| ValidateTemplate | ✅ |

**cloudformation verified per language:**
- Go: AWS 22/22
- Go: AWS 26/26

## compute

Expand Down
52 changes: 52 additions & 0 deletions docs/compat/compat.json
Original file line number Diff line number Diff line change
Expand Up @@ -316,6 +316,19 @@
},
"status": "green"
},
{
"service": "cloudformation",
"operation": "CancelUpdateStack",
"providers": {
"aws": {
"native": "CloudFormation",
"clients": {
"sdkGo": "pass"
}
}
},
"status": "green"
},
{
"service": "cloudformation",
"operation": "ContinueUpdateRollback",
Expand Down Expand Up @@ -472,6 +485,19 @@
},
"status": "green"
},
{
"service": "cloudformation",
"operation": "GetStackPolicy",
"providers": {
"aws": {
"native": "CloudFormation",
"clients": {
"sdkGo": "pass"
}
}
},
"status": "green"
},
{
"service": "cloudformation",
"operation": "GetTemplate",
Expand Down Expand Up @@ -563,6 +589,32 @@
},
"status": "green"
},
{
"service": "cloudformation",
"operation": "RollbackStack",
"providers": {
"aws": {
"native": "CloudFormation",
"clients": {
"sdkGo": "pass"
}
}
},
"status": "green"
},
{
"service": "cloudformation",
"operation": "SetStackPolicy",
"providers": {
"aws": {
"native": "CloudFormation",
"clients": {
"sdkGo": "pass"
}
}
},
"status": "green"
},
{
"service": "cloudformation",
"operation": "UpdateStack",
Expand Down
4 changes: 2 additions & 2 deletions docs/coverage/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ code does not implement. Machine-readable: [`coverage.json`](./coverage.json).
| `cloudasset` | - | - | [Cloudasset](./gcp/cloudasset.md) | - | 11 |
| `cloudbilling` | - | - | [Cloudbilling](./gcp/cloudbilling.md) | - | 14 |
| `clouddeploy` | - | - | [CloudDeploy](./gcp/clouddeploy.md) | - | 11 |
| `cloudformation` | [CloudFormation](./aws/cloudformation.md) | - | - | - | 22 |
| `cloudformation` | [CloudFormation](./aws/cloudformation.md) | - | - | - | 26 |
| `cloudfront` | [CloudFront](./aws/cloudfront.md) | - | - | - | 11 |
| `cloudids` | - | - | [CloudIDS](./gcp/cloudids.md) | - | 6 |
| `cloudrun` | - | - | [CloudRun](./gcp/cloudrun.md) | - | 18 |
Expand Down Expand Up @@ -89,7 +89,7 @@ code does not implement. Machine-readable: [`coverage.json`](./coverage.json).
| `filestore` | - | - | [Filestore](./gcp/filestore.md) | - | 5 |
| `fis` | [FIS](./aws/fis.md) | - | - | - | 12 |
| `frontdoor` | - | [FrontDoor](./azure/frontdoor.md) | - | - | 12 |
| `gke` | - | - | [GKE](./gcp/gke.md) | - | 18 |
| `gke` | - | - | [GKE](./gcp/gke.md) | - | 28 |
| `gkebackup` | - | - | [GKEBackup](./gcp/gkebackup.md) | - | 11 |
| `gkehub` | - | - | [GKEHub](./gcp/gkehub.md) | - | 16 |
| `globalaccelerator` | [GlobalAccelerator](./aws/globalaccelerator.md) | - | - | - | 20 |
Expand Down
2 changes: 1 addition & 1 deletion docs/coverage/aws/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ Services cloudemu emulates for AWS, by native name. Back to the [cross-provider
| [Bedrock](./bedrock.md) | `bedrock` | 65 |
| [BedrockAgent](./bedrockagent.md) | `bedrockagent` | 32 |
| [BedrockAgentRuntime](./bedrockagentruntime.md) | `bedrockagentruntime` | 3 |
| [CloudFormation](./cloudformation.md) | (provider-native) | 22 |
| [CloudFormation](./cloudformation.md) | (provider-native) | 26 |
| [CloudFront](./cloudfront.md) | `cloudfront` | 11 |
| [CloudTrail](./cloudtrail.md) | `cloudtrail` | 60 |
| [CloudWatch](./cloudwatch.md) | `monitoring` | 12 |
Expand Down
6 changes: 5 additions & 1 deletion docs/coverage/aws/cloudformation.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@

provider-native `cloudformation` wire service (AWS-only) · no portable driver · [AWS index](./README.md)

## Operations (22)
## Operations (26)

| Operation | Description |
| --- | --- |
| `CancelUpdateStack` | |
| `ContinueUpdateRollback` | |
| `CreateChangeSet` | |
| `CreateStack` | |
Expand All @@ -19,13 +20,16 @@ provider-native `cloudformation` wire service (AWS-only) · no portable driver
| `DescribeStacks` | |
| `EstimateTemplateCost` | |
| `ExecuteChangeSet` | |
| `GetStackPolicy` | |
| `GetTemplate` | |
| `GetTemplateSummary` | |
| `ListChangeSets` | |
| `ListExports` | |
| `ListImports` | |
| `ListStackResources` | |
| `ListStacks` | |
| `RollbackStack` | |
| `SetStackPolicy` | |
| `UpdateStack` | |
| `UpdateTerminationProtection` | |
| `ValidateTemplate` | |
Expand Down
42 changes: 42 additions & 0 deletions docs/coverage/coverage.json
Original file line number Diff line number Diff line change
Expand Up @@ -3158,6 +3158,9 @@
"service": "cloudformation",
"interface": "provider-native",
"operations": [
{
"name": "CancelUpdateStack"
},
{
"name": "ContinueUpdateRollback"
},
Expand Down Expand Up @@ -3194,6 +3197,9 @@
{
"name": "ExecuteChangeSet"
},
{
"name": "GetStackPolicy"
},
{
"name": "GetTemplate"
},
Expand All @@ -3215,6 +3221,12 @@
{
"name": "ListStacks"
},
{
"name": "RollbackStack"
},
{
"name": "SetStackPolicy"
},
{
"name": "UpdateStack"
},
Expand Down Expand Up @@ -6887,6 +6899,36 @@
{
"name": "RollbackNodePool"
},
{
"name": "SetClusterLogging"
},
{
"name": "SetClusterMonitoring"
},
{
"name": "SetLegacyAbac"
},
{
"name": "SetMaintenancePolicy"
},
{
"name": "SetMasterAuth"
},
{
"name": "SetNetworkPolicy"
},
{
"name": "SetNodePoolAutoscaling"
},
{
"name": "SetNodePoolManagement"
},
{
"name": "SetNodePoolSize"
},
{
"name": "SetResourceLabels"
},
{
"name": "StartIPRotation"
},
Expand Down
2 changes: 1 addition & 1 deletion docs/coverage/gcp/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ Services cloudemu emulates for GCP, by native name. Back to the [cross-provider
| [Firestore](./firestore.md) | `database` | 24 |
| [GCE](./gce.md) | `compute` | 37 |
| [GCS](./gcs.md) | `storage` | 35 |
| [GKE](./gke.md) | (provider-native) | 18 |
| [GKE](./gke.md) | (provider-native) | 28 |
| [GKEBackup](./gkebackup.md) | `gkebackup` | 11 |
| [GKEHub](./gkehub.md) | `gkehub` | 16 |
| [IAM](./iam.md) | `iam` | 40 |
Expand Down
12 changes: 11 additions & 1 deletion docs/coverage/gcp/gke.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

provider-native `gke` wire service (GCP-only) · no portable driver · [GCP index](./README.md)

## Operations (18)
## Operations (28)

| Operation | Description |
| --- | --- |
Expand All @@ -22,6 +22,16 @@ provider-native `gke` wire service (GCP-only) · no portable driver · [GCP inde
| `ListNodePools` | |
| `ListOperations` | |
| `RollbackNodePool` | |
| `SetClusterLogging` | |
| `SetClusterMonitoring` | |
| `SetLegacyAbac` | |
| `SetMaintenancePolicy` | |
| `SetMasterAuth` | |
| `SetNetworkPolicy` | |
| `SetNodePoolAutoscaling` | |
| `SetNodePoolManagement` | |
| `SetNodePoolSize` | |
| `SetResourceLabels` | |
| `StartIPRotation` | |
| `UpdateCluster` | |
| `UpdateNodePool` | |
Expand Down
Loading
Loading