Skip to content

Commit 09e4828

Browse files
committed
Add aws_rdsdata_query action
1 parent 00a40b6 commit 09e4828

File tree

4 files changed

+362
-0
lines changed

4 files changed

+362
-0
lines changed
Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
// Copyright (c) HashiCorp, Inc.
2+
// SPDX-License-Identifier: MPL-2.0
3+
4+
package rdsdata
5+
6+
import (
7+
"context"
8+
"fmt"
9+
10+
"github.com/aws/aws-sdk-go-v2/service/rdsdata"
11+
"github.com/aws/aws-sdk-go-v2/service/rdsdata/types"
12+
"github.com/hashicorp/terraform-plugin-framework/action"
13+
"github.com/hashicorp/terraform-plugin-framework/action/schema"
14+
fwtypes2 "github.com/hashicorp/terraform-plugin-framework/types"
15+
"github.com/hashicorp/terraform-plugin-log/tflog"
16+
"github.com/hashicorp/terraform-provider-aws/internal/conns"
17+
"github.com/hashicorp/terraform-provider-aws/internal/framework"
18+
fwtypes "github.com/hashicorp/terraform-provider-aws/internal/framework/types"
19+
"github.com/hashicorp/terraform-provider-aws/names"
20+
)
21+
22+
// @Action(aws_rdsdata_query, name="RDS Data Query")
23+
func newQueryAction(_ context.Context) (action.ActionWithConfigure, error) {
24+
return &queryAction{}, nil
25+
}
26+
27+
// NewQueryAction creates a new query action for testing
28+
func NewQueryAction(ctx context.Context) (action.ActionWithConfigure, error) {
29+
return newQueryAction(ctx)
30+
}
31+
32+
var (
33+
_ action.Action = (*queryAction)(nil)
34+
)
35+
36+
type queryAction struct {
37+
framework.ActionWithModel[queryActionModel]
38+
client *rdsdata.Client
39+
}
40+
41+
type queryActionModel struct {
42+
framework.WithRegionModel
43+
ResourceArn fwtypes2.String `tfsdk:"resource_arn"`
44+
SecretArn fwtypes2.String `tfsdk:"secret_arn"`
45+
SQL fwtypes2.String `tfsdk:"sql"`
46+
Database fwtypes2.String `tfsdk:"database"`
47+
Parameters fwtypes.ListNestedObjectValueOf[sqlParameterModel] `tfsdk:"parameters"`
48+
IncludeResultMetadata fwtypes2.Bool `tfsdk:"include_result_metadata"`
49+
}
50+
51+
type sqlParameterModel struct {
52+
Name fwtypes2.String `tfsdk:"name"`
53+
Value fwtypes2.String `tfsdk:"value"`
54+
}
55+
56+
func (a *queryAction) Schema(ctx context.Context, req action.SchemaRequest, resp *action.SchemaResponse) {
57+
resp.Schema = schema.Schema{
58+
Description: "Executes SQL queries against Aurora Serverless clusters using RDS Data API",
59+
Attributes: map[string]schema.Attribute{
60+
names.AttrResourceARN: schema.StringAttribute{
61+
Description: "ARN of the Aurora Serverless cluster",
62+
Required: true,
63+
},
64+
"secret_arn": schema.StringAttribute{
65+
Description: "ARN of the Secrets Manager secret containing database credentials",
66+
Required: true,
67+
},
68+
"sql": schema.StringAttribute{
69+
Description: "SQL statement to execute",
70+
Required: true,
71+
},
72+
names.AttrDatabase: schema.StringAttribute{
73+
Description: "Name of the database",
74+
Optional: true,
75+
},
76+
"include_result_metadata": schema.BoolAttribute{
77+
Description: "Include column metadata in query results",
78+
Optional: true,
79+
},
80+
},
81+
Blocks: map[string]schema.Block{
82+
names.AttrParameters: schema.ListNestedBlock{
83+
Description: "SQL parameters for prepared statements",
84+
CustomType: fwtypes.NewListNestedObjectTypeOf[sqlParameterModel](ctx),
85+
NestedObject: schema.NestedBlockObject{
86+
Attributes: map[string]schema.Attribute{
87+
names.AttrName: schema.StringAttribute{
88+
Description: "Parameter name",
89+
Required: true,
90+
},
91+
names.AttrValue: schema.StringAttribute{
92+
Description: "Parameter value",
93+
Required: true,
94+
},
95+
},
96+
},
97+
},
98+
},
99+
}
100+
}
101+
102+
func (a *queryAction) Configure(ctx context.Context, req action.ConfigureRequest, resp *action.ConfigureResponse) {
103+
if req.ProviderData == nil {
104+
return
105+
}
106+
107+
meta, ok := req.ProviderData.(*conns.AWSClient)
108+
if !ok {
109+
resp.Diagnostics.AddError(
110+
"Unexpected Action Configure Type",
111+
fmt.Sprintf("Expected *conns.AWSClient, got: %T", req.ProviderData),
112+
)
113+
return
114+
}
115+
116+
a.client = meta.RDSDataClient(ctx)
117+
}
118+
119+
func (a *queryAction) Invoke(ctx context.Context, req action.InvokeRequest, resp *action.InvokeResponse) {
120+
var model queryActionModel
121+
resp.Diagnostics.Append(req.Config.Get(ctx, &model)...)
122+
if resp.Diagnostics.HasError() {
123+
return
124+
}
125+
126+
tflog.Info(ctx, "Starting RDS Data query execution", map[string]any{
127+
names.AttrResourceARN: model.ResourceArn.ValueString(),
128+
"sql_length": len(model.SQL.ValueString()),
129+
})
130+
131+
resp.SendProgress(action.InvokeProgressEvent{
132+
Message: "Executing SQL query...",
133+
})
134+
135+
input := rdsdata.ExecuteStatementInput{
136+
ResourceArn: model.ResourceArn.ValueStringPointer(),
137+
SecretArn: model.SecretArn.ValueStringPointer(),
138+
Sql: model.SQL.ValueStringPointer(),
139+
}
140+
141+
if !model.Database.IsNull() {
142+
input.Database = model.Database.ValueStringPointer()
143+
}
144+
145+
if !model.IncludeResultMetadata.IsNull() {
146+
input.IncludeResultMetadata = model.IncludeResultMetadata.ValueBool()
147+
}
148+
149+
if !model.Parameters.IsNull() {
150+
var params []sqlParameterModel
151+
resp.Diagnostics.Append(model.Parameters.ElementsAs(ctx, &params, false)...)
152+
if resp.Diagnostics.HasError() {
153+
return
154+
}
155+
156+
var sqlParams []types.SqlParameter
157+
for _, param := range params {
158+
sqlParams = append(sqlParams, types.SqlParameter{
159+
Name: param.Name.ValueStringPointer(),
160+
Value: &types.FieldMemberStringValue{Value: param.Value.ValueString()},
161+
})
162+
}
163+
input.Parameters = sqlParams
164+
}
165+
166+
output, err := a.client.ExecuteStatement(ctx, &input)
167+
if err != nil {
168+
resp.Diagnostics.AddError("SQL Execution Failed", err.Error())
169+
return
170+
}
171+
172+
resp.SendProgress(action.InvokeProgressEvent{
173+
Message: fmt.Sprintf("Query executed successfully. Records affected: %d", output.NumberOfRecordsUpdated),
174+
})
175+
176+
tflog.Info(ctx, "RDS Data query completed successfully", map[string]any{
177+
"records_updated": output.NumberOfRecordsUpdated,
178+
"has_records": len(output.Records) > 0,
179+
})
180+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
// Copyright (c) HashiCorp, Inc.
2+
// SPDX-License-Identifier: MPL-2.0
3+
4+
package rdsdata_test
5+
6+
import (
7+
"testing"
8+
9+
"github.com/hashicorp/terraform-plugin-testing/helper/resource"
10+
"github.com/hashicorp/terraform-provider-aws/internal/acctest"
11+
"github.com/hashicorp/terraform-provider-aws/names"
12+
)
13+
14+
func TestAccRDSDataQueryAction_basic(t *testing.T) {
15+
ctx := acctest.Context(t)
16+
rName := acctest.RandomWithPrefix(t, acctest.ResourcePrefix)
17+
18+
resource.ParallelTest(t, resource.TestCase{
19+
PreCheck: func() { acctest.PreCheck(ctx, t) },
20+
ErrorCheck: acctest.ErrorCheck(t, names.RDSDataServiceID),
21+
ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories,
22+
Steps: []resource.TestStep{
23+
{
24+
Config: testAccQueryActionConfig_basic(rName),
25+
},
26+
},
27+
})
28+
}
29+
30+
func testAccQueryActionConfig_basic(rName string) string {
31+
return acctest.ConfigCompose(testAccQueryDataSourceConfig_base(rName), `
32+
resource "aws_rdsdata_query" "test" {
33+
depends_on = [aws_rds_cluster_instance.test]
34+
resource_arn = aws_rds_cluster.test.arn
35+
secret_arn = aws_secretsmanager_secret_version.test.arn
36+
sql = "SELECT 1"
37+
}
38+
`)
39+
}

internal/service/rdsdata/service_package_gen.go

Lines changed: 11 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
---
2+
subcategory: "RDS (Relational Database)"
3+
layout: "aws"
4+
page_title: "AWS: aws_rdsdata_query"
5+
description: |-
6+
Executes SQL queries against Aurora Serverless databases using the RDS Data API.
7+
---
8+
9+
# Action: aws_rdsdata_query
10+
11+
~> **Note:** `aws_rdsdata_query` is in beta. Its interface and behavior may change as the feature evolves, and breaking changes are possible. It is offered as a technical preview without compatibility guarantees until Terraform 1.14 is generally available.
12+
13+
Executes SQL queries against Aurora Serverless databases using the RDS Data API. This action allows for imperative SQL execution with support for parameterized queries and transaction management.
14+
15+
For information about the RDS Data API, see the [RDS Data API Developer Guide](https://docs.aws.amazon.com/AmazonRDS/latest/AuroraUserGuide/data-api.html). For specific information about executing statements, see the [ExecuteStatement](https://docs.aws.amazon.com/rdsdataservice/latest/APIReference/API_ExecuteStatement.html) page in the RDS Data Service API Reference.
16+
17+
## Example Usage
18+
19+
### Basic Query
20+
21+
```terraform
22+
resource "aws_rds_cluster" "example" {
23+
cluster_identifier = "example-cluster"
24+
engine = "aurora-mysql"
25+
database_name = "example"
26+
master_username = "admin"
27+
master_password = "password123"
28+
enable_http_endpoint = true
29+
skip_final_snapshot = true
30+
31+
serverlessv2_scaling_configuration {
32+
max_capacity = 1
33+
min_capacity = 0.5
34+
}
35+
}
36+
37+
resource "aws_secretsmanager_secret" "example" {
38+
name = "example-db-credentials"
39+
}
40+
41+
resource "aws_secretsmanager_secret_version" "example" {
42+
secret_id = aws_secretsmanager_secret.example.id
43+
secret_string = jsonencode({
44+
username = aws_rds_cluster.example.master_username
45+
password = aws_rds_cluster.example.master_password
46+
})
47+
}
48+
49+
action "aws_rdsdata_query" "example" {
50+
config {
51+
resource_arn = aws_rds_cluster.example.arn
52+
secret_arn = aws_secretsmanager_secret_version.example.arn
53+
sql = "SELECT COUNT(*) FROM users"
54+
}
55+
}
56+
57+
resource "terraform_data" "example" {
58+
input = "trigger-query"
59+
60+
lifecycle {
61+
action_trigger {
62+
events = [before_create]
63+
actions = [action.aws_rdsdata_query.example]
64+
}
65+
}
66+
}
67+
```
68+
69+
### Parameterized Query
70+
71+
```terraform
72+
action "aws_rdsdata_query" "user_lookup" {
73+
config {
74+
resource_arn = aws_rds_cluster.example.arn
75+
secret_arn = aws_secretsmanager_secret_version.example.arn
76+
database = "example"
77+
sql = "SELECT * FROM users WHERE status = :status AND created_date > :date"
78+
79+
parameters {
80+
name = "status"
81+
value = "active"
82+
}
83+
84+
parameters {
85+
name = "date"
86+
value = "2024-01-01"
87+
}
88+
}
89+
}
90+
```
91+
92+
### Data Modification Query
93+
94+
```terraform
95+
action "aws_rdsdata_query" "update_status" {
96+
config {
97+
resource_arn = aws_rds_cluster.example.arn
98+
secret_arn = aws_secretsmanager_secret_version.example.arn
99+
database = "example"
100+
sql = "UPDATE users SET last_login = NOW() WHERE user_id = :user_id"
101+
102+
parameters {
103+
name = "user_id"
104+
value = "12345"
105+
}
106+
}
107+
}
108+
```
109+
110+
## Argument Reference
111+
112+
The following arguments are required:
113+
114+
* `resource_arn` - (Required) The Amazon Resource Name (ARN) of the Aurora Serverless DB cluster.
115+
* `secret_arn` - (Required) The ARN of the secret that enables access to the DB cluster. The secret must contain the database credentials.
116+
* `sql` - (Required) The SQL statement to execute.
117+
118+
The following arguments are optional:
119+
120+
* `database` - (Optional) The name of the database to execute the SQL statement against.
121+
* `parameters` - (Optional) Parameters for the SQL statement. See [Parameters](#parameters) below.
122+
123+
### Parameters
124+
125+
The `parameters` block supports the following:
126+
127+
* `name` - (Required) The name of the parameter.
128+
* `value` - (Required) The value of the parameter.
129+
130+
## Import
131+
132+
Actions cannot be imported.

0 commit comments

Comments
 (0)