diff --git a/internal/aws/kms/key_gen.go b/internal/aws/kms/key_gen.go new file mode 100644 index 0000000000..717363c3bc --- /dev/null +++ b/internal/aws/kms/key_gen.go @@ -0,0 +1,261 @@ +// Code generated by generators/resource/main.go; DO NOT EDIT. + +package kms + +import ( + "context" + + hclog "github.com/hashicorp/go-hclog" + "github.com/hashicorp/terraform-plugin-framework/schema" + "github.com/hashicorp/terraform-plugin-framework/tfsdk" + "github.com/hashicorp/terraform-plugin-framework/types" + tflog "github.com/hashicorp/terraform-plugin-log" + . "github.com/hashicorp/terraform-provider-aws-cloudapi/internal/generic" + "github.com/hashicorp/terraform-provider-aws-cloudapi/internal/registry" + providertypes "github.com/hashicorp/terraform-provider-aws-cloudapi/internal/types" +) + +func init() { + registry.AddResourceTypeFactory("aws_kms_key", key) +} + +// key returns the Terraform aws_kms_key resource type. +// This Terraform resource type corresponds to the CloudFormation AWS::KMS::Key resource type. +func key(ctx context.Context) (tfsdk.ResourceType, error) { + attributes := map[string]schema.Attribute{ + "arn": { + // Property: Arn + // CloudFormation resource type schema: + /* + { + "type": "string" + } + */ + Type: types.StringType, + Computed: true, + }, + "description": { + // Property: Description + // CloudFormation resource type schema: + /* + { + "description": "A description of the CMK. Use a description that helps you to distinguish this CMK from others in the account, such as its intended use.", + "maxLength": 8192, + "minLength": 0, + "type": "string" + } + */ + Description: `A description of the CMK. Use a description that helps you to distinguish this CMK from others in the account, such as its intended use.`, + Type: types.StringType, + Optional: true, + }, + "enable_key_rotation": { + // Property: EnableKeyRotation + // CloudFormation resource type schema: + /* + { + "description": "Enables automatic rotation of the key material for the specified customer master key (CMK). By default, automation key rotation is not enabled.", + "type": "boolean" + } + */ + Description: `Enables automatic rotation of the key material for the specified customer master key (CMK). By default, automation key rotation is not enabled.`, + Type: types.BoolType, + Optional: true, + }, + "enabled": { + // Property: Enabled + // CloudFormation resource type schema: + /* + { + "description": "Specifies whether the customer master key (CMK) is enabled. Disabled CMKs cannot be used in cryptographic operations.", + "type": "boolean" + } + */ + Description: `Specifies whether the customer master key (CMK) is enabled. Disabled CMKs cannot be used in cryptographic operations.`, + Type: types.BoolType, + Optional: true, + }, + "key_id": { + // Property: KeyId + // CloudFormation resource type schema: + /* + { + "type": "string" + } + */ + Type: types.StringType, + Computed: true, + }, + "key_policy": { + // Property: KeyPolicy + // CloudFormation resource type schema: + /* + { + "description": "The key policy that authorizes use of the CMK. The key policy must observe the following rules.", + "type": "string" + } + */ + Description: `The key policy that authorizes use of the CMK. The key policy must observe the following rules.`, + Type: types.StringType, + Required: true, + }, + "key_spec": { + // Property: KeySpec + // CloudFormation resource type schema: + /* + { + "description": "Specifies the type of CMK to create. The default value is SYMMETRIC_DEFAULT. This property is required only for asymmetric CMKs. You can't change the KeySpec value after the CMK is created.", + "enum": [ + "SYMMETRIC_DEFAULT", + "RSA_2048", + "RSA_3072", + "RSA_4096", + "ECC_NIST_P256", + "ECC_NIST_P384", + "ECC_NIST_P521", + "ECC_SECG_P256K1" + ], + "type": "string" + } + */ + Description: `Specifies the type of CMK to create. The default value is SYMMETRIC_DEFAULT. This property is required only for asymmetric CMKs. You can't change the KeySpec value after the CMK is created.`, + Type: types.StringType, + Optional: true, + }, + "key_usage": { + // Property: KeyUsage + // CloudFormation resource type schema: + /* + { + "description": "Determines the cryptographic operations for which you can use the CMK. The default value is ENCRYPT_DECRYPT. This property is required only for asymmetric CMKs. You can't change the KeyUsage value after the CMK is created.", + "enum": [ + "ENCRYPT_DECRYPT", + "SIGN_VERIFY" + ], + "type": "string" + } + */ + Description: `Determines the cryptographic operations for which you can use the CMK. The default value is ENCRYPT_DECRYPT. This property is required only for asymmetric CMKs. You can't change the KeyUsage value after the CMK is created.`, + Type: types.StringType, + Optional: true, + }, + "pending_window_in_days": { + // Property: PendingWindowInDays + // CloudFormation resource type schema: + /* + { + "description": "Specifies the number of days in the waiting period before AWS KMS deletes a CMK that has been removed from a CloudFormation stack. Enter a value between 7 and 30 days. The default value is 30 days.", + "type": "integer" + } + */ + Description: `Specifies the number of days in the waiting period before AWS KMS deletes a CMK that has been removed from a CloudFormation stack. Enter a value between 7 and 30 days. The default value is 30 days.`, + Type: types.NumberType, + Optional: true, + // PendingWindowInDays is a write-only attribute. + }, + "tags": { + // Property: Tags + // CloudFormation resource type schema: + /* + { + "description": "An array of key-value pairs to apply to this resource.", + "insertionOrder": false, + "items": { + "description": "A key-value pair to associate with a resource.", + "properties": { + "Key": { + "description": "The key name of the tag. You can specify a value that is 1 to 128 Unicode characters in length and cannot be prefixed with aws:. You can use any of the following characters: the set of Unicode letters, digits, whitespace, _, ., /, =, +, and -.", + "maxLength": 128, + "minLength": 1, + "type": "string" + }, + "Value": { + "description": "The value for the tag. You can specify a value that is 0 to 256 Unicode characters in length and cannot be prefixed with aws:. You can use any of the following characters: the set of Unicode letters, digits, whitespace, _, ., /, =, +, and -.", + "maxLength": 256, + "minLength": 0, + "type": "string" + } + }, + "$ref": "#/definitions/Tag", + "required": [ + "Key", + "Value" + ], + "type": "object" + }, + "type": "array", + "uniqueItems": true + } + */ + Description: `An array of key-value pairs to apply to this resource.`, + Attributes: providertypes.SetNestedAttributes( + map[string]schema.Attribute{ + "key": { + // Property: Key + // CloudFormation resource type schema: + /* + { + "description": "The key name of the tag. You can specify a value that is 1 to 128 Unicode characters in length and cannot be prefixed with aws:. You can use any of the following characters: the set of Unicode letters, digits, whitespace, _, ., /, =, +, and -.", + "maxLength": 128, + "minLength": 1, + "type": "string" + } + */ + Description: `The key name of the tag. You can specify a value that is 1 to 128 Unicode characters in length and cannot be prefixed with aws:. You can use any of the following characters: the set of Unicode letters, digits, whitespace, _, ., /, =, +, and -.`, + Type: types.StringType, + Required: true, + }, + "value": { + // Property: Value + // CloudFormation resource type schema: + /* + { + "description": "The value for the tag. You can specify a value that is 0 to 256 Unicode characters in length and cannot be prefixed with aws:. You can use any of the following characters: the set of Unicode letters, digits, whitespace, _, ., /, =, +, and -.", + "maxLength": 256, + "minLength": 0, + "type": "string" + } + */ + Description: `The value for the tag. You can specify a value that is 0 to 256 Unicode characters in length and cannot be prefixed with aws:. You can use any of the following characters: the set of Unicode letters, digits, whitespace, _, ., /, =, +, and -.`, + Type: types.StringType, + Required: true, + }, + }, + providertypes.SetNestedAttributesOptions{}, + ), + Optional: true, + }, + } + + // Required for acceptance testing. + attributes["id"] = schema.Attribute{ + Description: "Uniquely identifies the resource.", + Type: types.StringType, + Computed: true, + } + + schema := schema.Schema{ + Description: `The AWS::KMS::Key resource specifies a customer master key (CMK) in AWS Key Management Service (AWS KMS). Authorized users can use the CMK to encrypt and decrypt small amounts of data (up to 4096 bytes), but they are more commonly used to generate data keys. You can also use CMKs to encrypt data stored in AWS services that are integrated with AWS KMS or within their applications.`, + Version: 1, + Attributes: attributes, + } + + var opts ResourceTypeOptions + + opts = opts.WithCloudFormationTypeName("AWS::KMS::Key").WithTerraformTypeName("aws_kms_key").WithTerraformSchema(schema) + + opts = opts.WithWriteOnlyPropertyPaths([]string{ + "/properties/PendingWindowInDays", + }) + opts = opts.WithCreateTimeoutInMinutes(0).WithUpdateTimeoutInMinutes(0).WithDeleteTimeoutInMinutes(0) + + resourceType, err := NewResourceType(ctx, opts...) + + if err != nil { + return nil, err + } + + tflog.Debug(ctx, "Generated schema", "tfTypeName", "aws_kms_key", "schema", hclog.Fmt("%v", schema)) + + return resourceType, nil +} diff --git a/internal/aws/kms/key_gen_test.go b/internal/aws/kms/key_gen_test.go new file mode 100644 index 0000000000..fab15cd92f --- /dev/null +++ b/internal/aws/kms/key_gen_test.go @@ -0,0 +1,22 @@ +// Code generated by generators/resource/main.go; DO NOT EDIT. + +package kms_test + +import ( + "regexp" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-provider-aws-cloudapi/internal/acctest" +) + +func TestAccAWSKMSKey_basic(t *testing.T) { + td := acctest.NewTestData(t, "AWS::KMS::Key", "aws_kms_key", "test") + + td.ResourceTest(t, []resource.TestStep{ + { + Config: td.EmptyConfig(), + ExpectError: regexp.MustCompile("Missing required argument"), + }, + }) +} diff --git a/internal/generic/resource.go b/internal/generic/resource.go index c3fff22941..63ca241994 100644 --- a/internal/generic/resource.go +++ b/internal/generic/resource.go @@ -407,6 +407,8 @@ func (r *resource) Read(ctx context.Context, request tfsdk.ReadResourceRequest, tflog.Debug(ctx, "Resource.Read enter", "cfTypeName", cfTypeName, "tfTypeName", tfTypeName) + tflog.Debug(ctx, "Request.State.Raw", "value", hclog.Fmt("%v", request.State.Raw)) + conn := r.provider.CloudFormationClient(ctx) currentState := &request.State @@ -446,15 +448,27 @@ func (r *resource) Read(ctx context.Context, request tfsdk.ReadResourceRequest, return } - // TODO - // TODO Consider write-only values. They can only be in the current state. - // TODO - response.State = tfsdk.State{ Schema: *schema, Raw: val, } + // Copy over any write-only values. + // They can only be in the current state. + for _, path := range r.resourceType.writeOnlyAttributePaths { + err = CopyValueAtPath(ctx, &response.State, &request.State, path) + + if err != nil { + response.Diagnostics = append(response.Diagnostics, &tfprotov6.Diagnostic{ + Severity: tfprotov6.DiagnosticSeverityError, + Summary: "Terraform State Value Not Set", + Detail: fmt.Sprintf("Unable to set Terraform State value %s. This is typically an error with the Terraform provider implementation. Original Error: %s", path, err.Error()), + }) + + return + } + } + // Set the "id" attribute. err = r.setId(ctx, id, &response.State) @@ -464,6 +478,8 @@ func (r *resource) Read(ctx context.Context, request tfsdk.ReadResourceRequest, return } + tflog.Debug(ctx, "Response.State.Raw", "value", hclog.Fmt("%v", response.State.Raw)) + tflog.Debug(ctx, "Resource.Read exit", "cfTypeName", cfTypeName, "tfTypeName", tfTypeName) } diff --git a/internal/generic/state.go b/internal/generic/state.go index 8540a8ae78..b5ce7ddfdc 100644 --- a/internal/generic/state.go +++ b/internal/generic/state.go @@ -9,9 +9,27 @@ import ( "github.com/hashicorp/terraform-plugin-framework/schema" "github.com/hashicorp/terraform-plugin-framework/tfsdk" "github.com/hashicorp/terraform-plugin-go/tftypes" + tflog "github.com/hashicorp/terraform-plugin-log" "github.com/hashicorp/terraform-provider-aws-cloudapi/internal/naming" ) +// CopyValueAtPath copies the value at a specified path from source State to destination State. +func CopyValueAtPath(ctx context.Context, dst, src *tfsdk.State, path *tftypes.AttributePath) error { + val, err := src.GetAttribute(ctx, path) + + if err != nil { + return err + } + + err = dst.SetAttribute(ctx, path, val) + + if err != nil { + return err + } + + return nil +} + // SetUnknownValuesFromCloudFormationResourceModel fills any unknown State values from a CloudFormation ResourceModel (string). func SetUnknownValuesFromCloudFormationResourceModel(ctx context.Context, state *tfsdk.State, resourceModel string) error { var v interface{} @@ -150,9 +168,26 @@ func getCloudFormationResourceModelValue(ctx context.Context, schema *schema.Sch vals = append(vals, val) path = path.WithoutLastStep() } + // TODO + // TODO This prevents a crash in Terraform Core, but is it correct? + // TODO + if len(vals) == 0 { + return tftypes.NewValue(typ, nil), nil + } return tftypes.NewValue(typ, vals), nil case map[string]interface{}: + if typ.Is(tftypes.String) { + // Value is JSON string. + val, err := json.Marshal(v) + + if err != nil { + return tftypes.Value{}, err + } + + return tftypes.NewValue(typ, string(val)), nil + } + isObject := typ.Is(tftypes.Object{}) vals := make(map[string]tftypes.Value) for key, v := range v { @@ -164,6 +199,11 @@ func getCloudFormationResourceModelValue(ctx context.Context, schema *schema.Sch } val, err := getCloudFormationResourceModelValue(ctx, schema, path, v) if err != nil { + if isObject { + tflog.Info(ctx, "not found in Terraform schema", "key", key, "path", path, "error", err.Error()) + path = path.WithoutLastStep() + continue + } return tftypes.Value{}, err } if isObject { diff --git a/internal/generic/state_test.go b/internal/generic/state_test.go index 97e636990a..ed3b90c78e 100644 --- a/internal/generic/state_test.go +++ b/internal/generic/state_test.go @@ -35,6 +35,33 @@ var testSimpleSchema = schema.Schema{ }, } +var testSimpleSchemaWithList = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "arn": { + Type: types.StringType, + Computed: true, + }, + "identifier": { + Type: types.StringType, + Computed: true, + }, + "name": { + Type: types.StringType, + Required: true, + }, + "number": { + Type: types.NumberType, + Optional: true, + }, + "ports": { + Type: types.ListType{ + ElemType: types.NumberType, + }, + Optional: true, + }, + }, +} + // Adapted from https://github.com/hashicorp/terraform-plugin-framework/blob/1a7927fec93459115be87f283dd1ee7941b30578/tfsdk/state_test.go. var testComplexSchema = schema.Schema{ Attributes: map[string]schema.Attribute{ @@ -237,6 +264,130 @@ func TestGetCloudFormationResourceModelValue(t *testing.T) { "number": tftypes.NewValue(tftypes.Number, 42), }), }, + { + TestName: "simple State with JSON string", + Schema: testSimpleSchema, + ResourceModel: map[string]interface{}{ + "Arn": "arn:aws:test:::test", + "Name": map[string]interface{}{ + "Value": "testing", + }, + "Number": float64(42), + }, + ExpectedValue: tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "arn": tftypes.String, + "identifier": tftypes.String, + "name": tftypes.String, + "number": tftypes.Number, + }, + }, map[string]tftypes.Value{ + "arn": tftypes.NewValue(tftypes.String, "arn:aws:test:::test"), + "identifier": tftypes.NewValue(tftypes.String, nil), + "name": tftypes.NewValue(tftypes.String, `{"Value":"testing"}`), + "number": tftypes.NewValue(tftypes.Number, 42), + }), + }, + { + TestName: "simple State with extra field", + Schema: testSimpleSchema, + ResourceModel: map[string]interface{}{ + "Arn": "arn:aws:test:::test", + "Height": float64(1.75), + "Name": "testing", + "Number": float64(42), + }, + ExpectedValue: tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "arn": tftypes.String, + "identifier": tftypes.String, + "name": tftypes.String, + "number": tftypes.Number, + }, + }, map[string]tftypes.Value{ + "arn": tftypes.NewValue(tftypes.String, "arn:aws:test:::test"), + "identifier": tftypes.NewValue(tftypes.String, nil), + "name": tftypes.NewValue(tftypes.String, "testing"), + "number": tftypes.NewValue(tftypes.Number, 42), + }), + }, + { + TestName: "simple State with List", + Schema: testSimpleSchemaWithList, + ResourceModel: map[string]interface{}{ + "Arn": "arn:aws:test:::test", + "Name": "testing", + "Number": float64(42), + "Ports": []interface{}{float64(8080), float64(8443)}, + }, + ExpectedValue: tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "arn": tftypes.String, + "identifier": tftypes.String, + "name": tftypes.String, + "number": tftypes.Number, + "ports": tftypes.List{ElementType: tftypes.Number}, + }, + }, map[string]tftypes.Value{ + "arn": tftypes.NewValue(tftypes.String, "arn:aws:test:::test"), + "identifier": tftypes.NewValue(tftypes.String, nil), + "name": tftypes.NewValue(tftypes.String, "testing"), + "number": tftypes.NewValue(tftypes.Number, 42), + "ports": tftypes.NewValue(tftypes.List{ElementType: tftypes.Number}, []tftypes.Value{ + tftypes.NewValue(tftypes.Number, 8080), + tftypes.NewValue(tftypes.Number, 8443), + }), + }), + }, + { + TestName: "simple State with empty List", + Schema: testSimpleSchemaWithList, + ResourceModel: map[string]interface{}{ + "Arn": "arn:aws:test:::test", + "Name": "testing", + "Number": float64(42), + "Ports": []interface{}{}, + }, + ExpectedValue: tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "arn": tftypes.String, + "identifier": tftypes.String, + "name": tftypes.String, + "number": tftypes.Number, + "ports": tftypes.List{ElementType: tftypes.Number}, + }, + }, map[string]tftypes.Value{ + "arn": tftypes.NewValue(tftypes.String, "arn:aws:test:::test"), + "identifier": tftypes.NewValue(tftypes.String, nil), + "name": tftypes.NewValue(tftypes.String, "testing"), + "number": tftypes.NewValue(tftypes.Number, 42), + "ports": tftypes.NewValue(tftypes.List{ElementType: tftypes.Number}, nil), + }), + }, + { + TestName: "simple State with missing List", + Schema: testSimpleSchemaWithList, + ResourceModel: map[string]interface{}{ + "Arn": "arn:aws:test:::test", + "Name": "testing", + "Number": float64(42), + }, + ExpectedValue: tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "arn": tftypes.String, + "identifier": tftypes.String, + "name": tftypes.String, + "number": tftypes.Number, + "ports": tftypes.List{ElementType: tftypes.Number}, + }, + }, map[string]tftypes.Value{ + "arn": tftypes.NewValue(tftypes.String, "arn:aws:test:::test"), + "identifier": tftypes.NewValue(tftypes.String, nil), + "name": tftypes.NewValue(tftypes.String, "testing"), + "number": tftypes.NewValue(tftypes.Number, 42), + "ports": tftypes.NewValue(tftypes.List{ElementType: tftypes.Number}, nil), + }), + }, { TestName: "complex State", Schema: testComplexSchema, @@ -372,8 +523,10 @@ func TestGetCloudFormationResourceModelValue(t *testing.T) { t.Fatalf("unexpected error from GetCloudFormationResourceModelRawValue: %s", err) } - if diff := cmp.Diff(got, testCase.ExpectedValue); diff != "" { - t.Errorf("unexpected diff (+wanted, -got): %s", diff) + if err == nil { + if diff := cmp.Diff(got, testCase.ExpectedValue); diff != "" { + t.Errorf("unexpected diff (+wanted, -got): %s", diff) + } } }) } @@ -430,8 +583,10 @@ func TestGetUnknownValuePaths(t *testing.T) { t.Fatalf("unexpected error from GetUnknownValuePaths: %s", err) } - if diff := cmp.Diff(got, testCase.ExpectedPaths, opts); diff != "" { - t.Errorf("unexpected diff (+wanted, -got): %s", diff) + if err == nil { + if diff := cmp.Diff(got, testCase.ExpectedPaths, opts); diff != "" { + t.Errorf("unexpected diff (+wanted, -got): %s", diff) + } } }) } @@ -485,8 +640,235 @@ func TestSetUnknownValuesFromCloudFormationResourceModel(t *testing.T) { t.Fatalf("unexpected error from SetUnknownValuesFromCloudFormationResourceModelRaw: %s", err) } - if diff := cmp.Diff(testCase.State, testCase.ExpectedState); diff != "" { - t.Errorf("unexpected diff (+wanted, -got): %s", diff) + if err == nil { + if diff := cmp.Diff(testCase.State, testCase.ExpectedState); diff != "" { + t.Errorf("unexpected diff (+wanted, -got): %s", diff) + } + } + }) + } +} + +func TestCopyValueAtPath(t *testing.T) { + testCases := []struct { + TestName string + SrcState tfsdk.State + DstState tfsdk.State + Path *tftypes.AttributePath + ExpectedError bool + ExpectedState tfsdk.State + }{ + { + TestName: "simple State", + SrcState: tfsdk.State{ + Raw: tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "arn": tftypes.String, + "name": tftypes.String, + "number": tftypes.Number, + "identifier": tftypes.String, + }, + }, map[string]tftypes.Value{ + "arn": tftypes.NewValue(tftypes.String, "arnsrc"), + "name": tftypes.NewValue(tftypes.String, "namesrc"), + "number": tftypes.NewValue(tftypes.Number, 42), + "identifier": tftypes.NewValue(tftypes.String, "idsrc"), + }), + Schema: testSimpleSchema, + }, + DstState: tfsdk.State{ + Raw: tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "arn": tftypes.String, + "name": tftypes.String, + "number": tftypes.Number, + "identifier": tftypes.String, + }, + }, map[string]tftypes.Value{ + "arn": tftypes.NewValue(tftypes.String, "arndest"), + "name": tftypes.NewValue(tftypes.String, "namedest"), + "number": tftypes.NewValue(tftypes.Number, 0), + "identifier": tftypes.NewValue(tftypes.String, "iddest"), + }), + Schema: testSimpleSchema, + }, + Path: tftypes.NewAttributePath().WithAttributeName("number"), + ExpectedState: tfsdk.State{ + Raw: tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "arn": tftypes.String, + "name": tftypes.String, + "number": tftypes.Number, + "identifier": tftypes.String, + }, + }, map[string]tftypes.Value{ + "arn": tftypes.NewValue(tftypes.String, "arndest"), + "name": tftypes.NewValue(tftypes.String, "namedest"), + "number": tftypes.NewValue(tftypes.Number, 42), + "identifier": tftypes.NewValue(tftypes.String, "iddest"), + }), + Schema: testSimpleSchema, + }, + }, + { + TestName: "simple State with Null in Src", + SrcState: tfsdk.State{ + Raw: tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "arn": tftypes.String, + "name": tftypes.String, + "number": tftypes.Number, + "identifier": tftypes.String, + }, + }, map[string]tftypes.Value{ + "arn": tftypes.NewValue(tftypes.String, nil), + "name": tftypes.NewValue(tftypes.String, "namesrc"), + "number": tftypes.NewValue(tftypes.Number, 42), + "identifier": tftypes.NewValue(tftypes.String, "idsrc"), + }), + Schema: testSimpleSchema, + }, + DstState: tfsdk.State{ + Raw: tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "arn": tftypes.String, + "name": tftypes.String, + "number": tftypes.Number, + "identifier": tftypes.String, + }, + }, map[string]tftypes.Value{ + "arn": tftypes.NewValue(tftypes.String, "arndest"), + "name": tftypes.NewValue(tftypes.String, "namedest"), + "number": tftypes.NewValue(tftypes.Number, 43), + "identifier": tftypes.NewValue(tftypes.String, "iddest"), + }), + Schema: testSimpleSchema, + }, + Path: tftypes.NewAttributePath().WithAttributeName("arn"), + ExpectedState: tfsdk.State{ + Raw: tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "arn": tftypes.String, + "name": tftypes.String, + "number": tftypes.Number, + "identifier": tftypes.String, + }, + }, map[string]tftypes.Value{ + "arn": tftypes.NewValue(tftypes.String, nil), + "name": tftypes.NewValue(tftypes.String, "namedest"), + "number": tftypes.NewValue(tftypes.Number, 43), + "identifier": tftypes.NewValue(tftypes.String, "iddest"), + }), + Schema: testSimpleSchema, + }, + }, + { + TestName: "simple State with Null in Dst", + SrcState: tfsdk.State{ + Raw: tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "arn": tftypes.String, + "name": tftypes.String, + "number": tftypes.Number, + "identifier": tftypes.String, + }, + }, map[string]tftypes.Value{ + "arn": tftypes.NewValue(tftypes.String, "arnsrc"), + "name": tftypes.NewValue(tftypes.String, "namesrc"), + "number": tftypes.NewValue(tftypes.Number, 42), + "identifier": tftypes.NewValue(tftypes.String, "idsrc"), + }), + Schema: testSimpleSchema, + }, + DstState: tfsdk.State{ + Raw: tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "arn": tftypes.String, + "name": tftypes.String, + "number": tftypes.Number, + "identifier": tftypes.String, + }, + }, map[string]tftypes.Value{ + "arn": tftypes.NewValue(tftypes.String, nil), + "name": tftypes.NewValue(tftypes.String, "namedest"), + "number": tftypes.NewValue(tftypes.Number, 43), + "identifier": tftypes.NewValue(tftypes.String, "iddest"), + }), + Schema: testSimpleSchema, + }, + Path: tftypes.NewAttributePath().WithAttributeName("arn"), + ExpectedState: tfsdk.State{ + Raw: tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "arn": tftypes.String, + "name": tftypes.String, + "number": tftypes.Number, + "identifier": tftypes.String, + }, + }, map[string]tftypes.Value{ + "arn": tftypes.NewValue(tftypes.String, "arnsrc"), + "name": tftypes.NewValue(tftypes.String, "namedest"), + "number": tftypes.NewValue(tftypes.Number, 43), + "identifier": tftypes.NewValue(tftypes.String, "iddest"), + }), + Schema: testSimpleSchema, + }, + }, + { + TestName: "invalid Path", + SrcState: tfsdk.State{ + Raw: tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "arn": tftypes.String, + "name": tftypes.String, + "number": tftypes.Number, + "identifier": tftypes.String, + }, + }, map[string]tftypes.Value{ + "arn": tftypes.NewValue(tftypes.String, "arnsrc"), + "name": tftypes.NewValue(tftypes.String, "namesrc"), + "number": tftypes.NewValue(tftypes.Number, 42), + "identifier": tftypes.NewValue(tftypes.String, "idsrc"), + }), + Schema: testSimpleSchema, + }, + DstState: tfsdk.State{ + Raw: tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "arn": tftypes.String, + "name": tftypes.String, + "number": tftypes.Number, + "identifier": tftypes.String, + }, + }, map[string]tftypes.Value{ + "arn": tftypes.NewValue(tftypes.String, "arndest"), + "name": tftypes.NewValue(tftypes.String, "namedest"), + "number": tftypes.NewValue(tftypes.Number, 0), + "identifier": tftypes.NewValue(tftypes.String, "iddest"), + }), + Schema: testSimpleSchema, + }, + Path: tftypes.NewAttributePath().WithAttributeName("height"), + ExpectedError: true, + }, + } + + for _, testCase := range testCases { + t.Run(testCase.TestName, func(t *testing.T) { + err := CopyValueAtPath(context.TODO(), &testCase.DstState, &testCase.SrcState, testCase.Path) + + if err == nil && testCase.ExpectedError { + t.Fatalf("expected error from CopyValueAtPath") + } + + if err != nil && !testCase.ExpectedError { + t.Fatalf("unexpected error from CopyValueAtPath: %s", err) + } + + if err == nil { + if diff := cmp.Diff(testCase.DstState, testCase.ExpectedState); diff != "" { + t.Errorf("unexpected diff (+wanted, -got): %s", diff) + } } }) } @@ -645,8 +1027,10 @@ func TestPlanGetCloudFormationDesiredState(t *testing.T) { t.Fatalf("unexpected error: %s", err) } - if diff := cmp.Diff(got, testCase.ExpectedState); diff != "" { - t.Errorf("unexpected diff (+wanted, -got): %s", diff) + if err == nil { + if diff := cmp.Diff(got, testCase.ExpectedState); diff != "" { + t.Errorf("unexpected diff (+wanted, -got): %s", diff) + } } }) } diff --git a/internal/provider/all_schemas.hcl b/internal/provider/all_schemas.hcl index fdb62b89ff..d6da877564 100644 --- a/internal/provider/all_schemas.hcl +++ b/internal/provider/all_schemas.hcl @@ -84,4 +84,12 @@ resource_schema "aws_ecs_task_definition" { } local = "../service/cloudformation/schemas/us-west-2/aws-ecs-taskdefinition.json" -} \ No newline at end of file +} + +resource_schema "aws_kms_key" { + source { + url = "???" + } + + local = "../service/cloudformation/schemas/us-west-2/aws-kms-key.json" +} diff --git a/internal/provider/resources.go b/internal/provider/resources.go index 6254412fed..5475d8a6c4 100644 --- a/internal/provider/resources.go +++ b/internal/provider/resources.go @@ -8,6 +8,7 @@ //go:generate go run generators/resource/main.go -resource aws_xray_sampling_rule -cfschema /Users/ewbankkit/src/github.com/hashicorp/terraform-provider-aws-cloudapi/internal/service/cloudformation/schemas/us-west-2/aws-xray-samplingrule.json -package xray -- ../aws/xray/sampling_rule_gen.go ../aws/xray/sampling_rule_gen_test.go //go:generate go run generators/resource/main.go -resource aws_ecs_service -cfschema /Users/ewbankkit/src/github.com/hashicorp/terraform-provider-aws-cloudapi/internal/service/cloudformation/schemas/us-west-2/aws-ecs-service.json -package ecs -- ../aws/ecs/service_gen.go ../aws/ecs/service_gen_test.go //go:generate go run generators/resource/main.go -resource aws_ecs_task_definition -cfschema /Users/ewbankkit/src/github.com/hashicorp/terraform-provider-aws-cloudapi/internal/service/cloudformation/schemas/us-west-2/aws-ecs-taskdefinition.json -package ecs -- ../aws/ecs/task_definition_gen.go ../aws/ecs/task_definition_gen_test.go +//go:generate go run generators/resource/main.go -resource aws_kms_key -cfschema /Users/ewbankkit/src/github.com/hashicorp/terraform-provider-aws-cloudapi/internal/service/cloudformation/schemas/us-west-2/aws-kms-key.json -package kms -- ../aws/kms/key_gen.go ../aws/kms/key_gen_test.go package provider @@ -15,6 +16,7 @@ import ( _ "github.com/hashicorp/terraform-provider-aws-cloudapi/internal/aws/appmesh" _ "github.com/hashicorp/terraform-provider-aws-cloudapi/internal/aws/backup" _ "github.com/hashicorp/terraform-provider-aws-cloudapi/internal/aws/ecs" + _ "github.com/hashicorp/terraform-provider-aws-cloudapi/internal/aws/kms" _ "github.com/hashicorp/terraform-provider-aws-cloudapi/internal/aws/logs" _ "github.com/hashicorp/terraform-provider-aws-cloudapi/internal/aws/sagemaker" _ "github.com/hashicorp/terraform-provider-aws-cloudapi/internal/aws/stepfunctions" diff --git a/internal/service/cloudformation/waiter.go b/internal/service/cloudformation/waiter.go index 12e5719d11..8f16bfed5f 100644 --- a/internal/service/cloudformation/waiter.go +++ b/internal/service/cloudformation/waiter.go @@ -31,7 +31,7 @@ func WaitForResourceRequestSuccess(ctx context.Context, client *cloudformation.C // Resource not found error on delete is OK. return false, nil } - return false, fmt.Errorf("waiter state transitioned to %s", value) + return false, fmt.Errorf("waiter state transitioned to %s. StatusMessage: %s. ErrorCode: %s", value, aws.ToString(progressEvent.StatusMessage), progressEvent.ErrorCode) } } @@ -193,19 +193,3 @@ func (w *ResourceRequestStatusSuccessWaiter) Wait(ctx context.Context, params *c } return fmt.Errorf("exceeded max wait time for ResourceRequestStatusSuccess waiter") } - -func resourceRequestStatusSuccessStateRetryable(ctx context.Context, input *cloudformation.GetResourceRequestStatusInput, output *cloudformation.GetResourceRequestStatusOutput, err error) (bool, error) { - if err == nil { - switch value := output.ProgressEvent.OperationStatus; value { - case types.OperationStatusSuccess: - return false, nil - case types.OperationStatusFailed: - if output.ProgressEvent.ErrorCode == types.HandlerErrorCodeNotFound && output.ProgressEvent.Operation == types.OperationDelete { - return false, nil - } - return false, fmt.Errorf("waiter state transitioned to %s", value) - } - } - - return true, nil -} diff --git a/test/main.tf b/test/main.tf index e0a83393af..d3930e1458 100644 --- a/test/main.tf +++ b/test/main.tf @@ -10,9 +10,36 @@ provider "cloudapi" { region = "us-west-2" } -resource "aws_logs_log_group" "test" { +resource "aws_kms_key" "test" { provider = cloudapi - log_group_name = "CloudAPI_testing" - retention_in_days = 120 + key_policy = jsonencode({ + Id = "kms-tf-1" + Statement = [ + { + Action = "kms:*" + Effect = "Allow" + Principal = { + AWS = "*" + } + + Resource = "*" + Sid = "Enable IAM User Permissions" + }, + ] + Version = "2012-10-17" + }) + + pending_window_in_days = 8 + + # tags = [ + # { + # key = "Name" + # value = "Testing" + # }, + # { + # key = "Env" + # value = "dev" + # } + # ] } \ No newline at end of file