diff --git a/.doc_gen/metadata/redshift_metadata.yaml b/.doc_gen/metadata/redshift_metadata.yaml index 526d4e2768d..2ab4d973613 100644 --- a/.doc_gen/metadata/redshift_metadata.yaml +++ b/.doc_gen/metadata/redshift_metadata.yaml @@ -5,6 +5,14 @@ redshift_Hello: synopsis: get started using &RS;. category: Hello languages: + Go: + versions: + - sdk_version: 2 + github: gov2/redshift + excerpts: + - description: + snippet_tags: + - gov2.redshift.Hello Java: versions: - sdk_version: 2 @@ -23,7 +31,7 @@ redshift_Hello: snippet_tags: - python.example_code.redshift.Hello services: - redshift: {describeClusters} + redshift: {DescribeClusters} redshift_ListDatabases: languages: Java: @@ -39,6 +47,16 @@ redshift_ListDatabases: redshift: {ListDatabases} redshift_CreateCluster: languages: + Go: + versions: + - sdk_version: 2 + github: gov2/redshift + excerpts: + - description: + snippet_tags: + - gov2.redshift.Imports + - gov2.redshift.ActionsStruct + - gov2.redshift.CreateCluster Kotlin: versions: - sdk_version: 1 @@ -86,6 +104,16 @@ redshift_CreateCluster: redshift: {CreateCluster} redshift_DeleteCluster: languages: + Go: + versions: + - sdk_version: 2 + github: gov2/redshift + excerpts: + - description: + snippet_tags: + - gov2.redshift.Imports + - gov2.redshift.ActionsStruct + - gov2.redshift.DeleteCluster Kotlin: versions: - sdk_version: 1 @@ -133,6 +161,16 @@ redshift_DeleteCluster: redshift: {DeleteCluster} redshift_DescribeClusters: languages: + Go: + versions: + - sdk_version: 2 + github: gov2/redshift + excerpts: + - description: + snippet_tags: + - gov2.redshift.Imports + - gov2.redshift.ActionsStruct + - gov2.redshift.DescribeClusters Kotlin: versions: - sdk_version: 1 @@ -180,6 +218,16 @@ redshift_DescribeClusters: redshift: {DescribeClusters} redshift_ModifyCluster: languages: + Go: + versions: + - sdk_version: 2 + github: gov2/redshift + excerpts: + - description: + snippet_tags: + - gov2.redshift.Imports + - gov2.redshift.ActionsStruct + - gov2.redshift.ModifyCluster Kotlin: versions: - sdk_version: 1 @@ -297,9 +345,17 @@ redshift_ExecuteStatement: services: redshift: {ExecuteStatement} redshift_Scenario: - synopsis: learn core operations for &RS; using an &AWS;. + synopsis: learn core operations for &RS; using an &AWS; SDK. category: Basics languages: + Go: + versions: + - sdk_version: 2 + github: gov2/redshift + excerpts: + - description: + snippet_tags: + - gov2.redshift.BasicsScenario Java: versions: - sdk_version: 2 @@ -335,4 +391,4 @@ redshift_Scenario: - python.example_code.redshift_data.ListDatabases - python.example_code.redshift.DeleteCluster services: - redshift: {createCluster, describeClusters, executeStatement, describeStatement, modifyCluster, getStatementResult, listDatabasesPaginator} + redshift: {CreateCluster, DescribeClusters, ExecuteStatement, DescribeStatement, ModifyCluster, GetStatementResult, ListDatabasesPaginator} diff --git a/gov2/redshift/README.md b/gov2/redshift/README.md new file mode 100644 index 00000000000..554c5a724fc --- /dev/null +++ b/gov2/redshift/README.md @@ -0,0 +1,120 @@ +# Amazon Redshift code examples for the SDK for Go V2 + +## Overview + +Shows how to use the AWS SDK for Go V2 to work with Amazon Redshift. + + + + +_Amazon Redshift is a fast, fully managed, petabyte-scale data warehouse service that makes it simple and cost-effective to efficiently analyze all your data using your existing business intelligence tools._ + +## ⚠ Important + +* Running this code might result in charges to your AWS account. For more details, see [AWS Pricing](https://aws.amazon.com/pricing/) and [Free Tier](https://aws.amazon.com/free/). +* Running the tests might result in charges to your AWS account. +* We recommend that you grant your code least privilege. At most, grant only the minimum permissions required to perform the task. For more information, see [Grant least privilege](https://docs.aws.amazon.com/IAM/latest/UserGuide/best-practices.html#grant-least-privilege). +* This code is not tested in every AWS Region. For more information, see [AWS Regional Services](https://aws.amazon.com/about-aws/global-infrastructure/regional-product-services). + + + + +## Code examples + +### Prerequisites + +For prerequisites, see the [README](../README.md#Prerequisites) in the `gov2` folder. + + + + + +### Get started + +- [Hello Amazon Redshift](hello/hello.go#L4) (`DescribeClusters`) + + +### Basics + +Code examples that show you how to perform the essential operations within a service. + +- [Learn the basics](scenarios/redshift_basics.go) + + +### Single actions + +Code excerpts that show you how to call individual service functions. + +- [CreateCluster](actions/redshift_actions.go#L29) +- [DeleteCluster](actions/redshift_actions.go#L84) +- [DescribeClusters](actions/redshift_actions.go#L108) +- [ModifyCluster](actions/redshift_actions.go#L58) + + + + + +## Run the examples + +### Instructions + + + + + +#### Hello Amazon Redshift + +This example shows you how to get started using Amazon Redshift. + +``` +go run ./hello +``` + +#### Run a scenario + +All scenarios can be run with the `cmd` runner. To get a list of scenarios +and to get help for running a scenario, use the following command: + +``` +go run ./cmd -h +``` +#### Learn the basics + +This example shows you how to learn core operations for Amazon Redshift using an AWS SDK. + + + + + + + + + + +### Tests + +⚠ Running tests might result in charges to your AWS account. + + +To find instructions for running these tests, see the [README](../README.md#Tests) +in the `gov2` folder. + + + + + + +## Additional resources + +- [Amazon Redshift Management Guide](https://docs.aws.amazon.com/redshift/latest/mgmt/welcome.html) +- [Amazon Redshift API Reference](https://docs.aws.amazon.com/redshift/latest/APIReference/Welcome.html) +- [SDK for Go V2 Amazon Redshift reference](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/service/redshift) + + + + +--- + +Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + +SPDX-License-Identifier: Apache-2.0 \ No newline at end of file diff --git a/gov2/redshift/actions/redshift_actions.go b/gov2/redshift/actions/redshift_actions.go new file mode 100644 index 00000000000..cdc0239caa5 --- /dev/null +++ b/gov2/redshift/actions/redshift_actions.go @@ -0,0 +1,126 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package actions + +// snippet-start:[gov2.redshift.Imports] + +import ( + "context" + "errors" + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/redshift" + "github.com/aws/aws-sdk-go-v2/service/redshift/types" + "log" + "time" +) + +// snippet-end:[gov2.redshift.Imports] + +// snippet-start:[gov2.redshift.ActionsStruct] + +// RedshiftActions wraps Redshift service actions. +type RedshiftActions struct { + RedshiftClient *redshift.Client +} + +// snippet-end:[gov2.redshift.ActionsStruct] + +// snippet-start:[gov2.redshift.CreateCluster] + +// CreateCluster sends a request to create a cluster with the given clusterId using the provided credentials. +func (actor RedshiftActions) CreateCluster(ctx context.Context, clusterId string, userName string, userPassword string, nodeType string, clusterType string, publiclyAccessible bool) (*redshift.CreateClusterOutput, error) { + // Create a new Redshift cluster + input := &redshift.CreateClusterInput{ + ClusterIdentifier: aws.String(clusterId), + MasterUserPassword: aws.String(userPassword), + MasterUsername: aws.String(userName), + NodeType: aws.String(nodeType), + ClusterType: aws.String(clusterType), + PubliclyAccessible: aws.Bool(publiclyAccessible), + } + var opErr *types.ClusterAlreadyExistsFault + output, err := actor.RedshiftClient.CreateCluster(ctx, input) + if err != nil && errors.As(err, &opErr) { + log.Println("Cluster already exists") + return nil, nil + } else if err != nil { + log.Printf("Failed to create Redshift cluster: %v\n", err) + return nil, err + } + + log.Printf("Created cluster %s\n", *output.Cluster.ClusterIdentifier) + return output, nil +} + +// snippet-end:[gov2.redshift.CreateCluster] + +// snippet-start:[gov2.redshift.ModifyCluster] + +// ModifyCluster sets the preferred maintenance window for the given cluster. +func (actor RedshiftActions) ModifyCluster(ctx context.Context, clusterId string, maintenanceWindow string) *redshift.ModifyClusterOutput { + // Modify the cluster's maintenance window + input := &redshift.ModifyClusterInput{ + ClusterIdentifier: aws.String(clusterId), + PreferredMaintenanceWindow: aws.String(maintenanceWindow), + } + + var opErr *types.InvalidClusterStateFault + output, err := actor.RedshiftClient.ModifyCluster(ctx, input) + if err != nil && errors.As(err, &opErr) { + log.Println("Cluster is in an invalid state.") + panic(err) + } else if err != nil { + log.Printf("Failed to modify Redshift cluster: %v\n", err) + panic(err) + } + + log.Printf("The cluster was successfully modified and now has %s as the maintenance window\n", *output.Cluster.PreferredMaintenanceWindow) + return output +} + +// snippet-end:[gov2.redshift.ModifyCluster] + +// snippet-start:[gov2.redshift.DeleteCluster] + +// DeleteCluster deletes the given cluster. +func (actor RedshiftActions) DeleteCluster(ctx context.Context, clusterId string) (bool, error) { + // Delete the specified Redshift cluster + + waiter := redshift.NewClusterDeletedWaiter(actor.RedshiftClient) + err := waiter.Wait(ctx, &redshift.DescribeClustersInput{ + ClusterIdentifier: aws.String(clusterId), + }, 5*time.Minute) + var opErr *types.ClusterNotFoundFault + if err != nil && errors.As(err, &opErr) { + log.Println("Cluster was not found. Where could it be?") + return false, err + } else if err != nil { + log.Printf("Failed to delete Redshift cluster: %v\n", err) + return false, err + } + log.Printf("The %s was deleted\n", clusterId) + return true, nil +} + +// snippet-end:[gov2.redshift.DeleteCluster] + +// snippet-start:[gov2.redshift.DescribeClusters] + +// DescribeClusters returns information about the given cluster. +func (actor RedshiftActions) DescribeClusters(ctx context.Context, clusterId string) (*redshift.DescribeClustersOutput, error) { + input, err := actor.RedshiftClient.DescribeClusters(ctx, &redshift.DescribeClustersInput{ + ClusterIdentifier: aws.String(clusterId), + }) + var opErr *types.AccessToClusterDeniedFault + if errors.As(err, &opErr) { + println("Access to cluster denied.") + panic(err) + } else if err != nil { + println("Failed to describe Redshift clusters.") + return nil, err + } + return input, nil +} + +// snippet-end:[gov2.redshift.DescribeClusters] diff --git a/gov2/redshift/actions/redshiftdata_actions.go b/gov2/redshift/actions/redshiftdata_actions.go new file mode 100644 index 00000000000..b70a6c1e04b --- /dev/null +++ b/gov2/redshift/actions/redshiftdata_actions.go @@ -0,0 +1,265 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package actions + +import ( + "context" + "errors" + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/redshiftdata" + "github.com/aws/aws-sdk-go-v2/service/redshiftdata/types" + "github.com/awsdocs/aws-doc-sdk-examples/gov2/demotools" + "log" +) + +// snippet-start:[gov2.redshift.DataActionsStruct] + +// RedshiftDataActions wraps RedshiftData actions. +type RedshiftDataActions struct { + RedshiftDataClient *redshiftdata.Client +} + +// snippet-end:[gov2.redshift.DataActionsStruct] + +// snippet-start:[gov2.redshift.RedshiftQuery.struct] + +// RedshiftQuery makes it easier to deal with RedshiftQuery objects. +type RedshiftQuery struct { + Result interface{} + Input redshiftdata.DescribeStatementInput + Context context.Context +} + +// snippet-end:[gov2.redshift.RedshiftQuery.struct] + +// snippet-start:[gov2.redshift.ExecuteStatement + +// ExecuteStatement calls the ExecuteStatement operation from the RedshiftDataClient +func (actor RedshiftDataActions) ExecuteStatement(ctx context.Context, input redshiftdata.ExecuteStatementInput) (*redshiftdata.ExecuteStatementOutput, error) { + return actor.RedshiftDataClient.ExecuteStatement(ctx, &input) +} + +// snippet-end:[gov2.redshift.ExecuteStatement + +// snippet-start:[gov2.redshift.ExecuteBatchStatement + +// ExecuteBatchStatement calls the BatchExecuteStatement operation from the RedshiftDataClient +func (actor RedshiftDataActions) ExecuteBatchStatement(ctx context.Context, input redshiftdata.BatchExecuteStatementInput) (*redshiftdata.BatchExecuteStatementOutput, error) { + return actor.RedshiftDataClient.BatchExecuteStatement(ctx, &input) +} + +// snippet-end:[gov2.redshift.ExecuteBatchStatement + +// snippet-start:[gov2.redshift.ListDatabases] + +// ListDatabases lists all databases in the given cluster. +func (actor RedshiftDataActions) ListDatabases(ctx context.Context, clusterId string, databaseName string, userName string) error { + + var opErr *types.DatabaseConnectionException + var databaseNames []string + paginator := redshiftdata.NewListDatabasesPaginator(actor.RedshiftDataClient, &redshiftdata.ListDatabasesInput{ + Database: aws.String(databaseName), + ClusterIdentifier: aws.String(clusterId), + DbUser: aws.String(userName), + }) + for paginator.HasMorePages() { + output, err := paginator.NextPage(ctx) + if err != nil && errors.As(err, &opErr) { + log.Printf("Could not connect to the database.") + panic(err) + } else if err != nil { + log.Printf("Couldn't finish listing the tables. Here's why: %v\n", err) + return err + } else { + databaseNames = append(databaseNames, output.Databases...) + } + } + + for _, database := range databaseNames { + log.Printf("The database name is : %s\n", database) + } + return nil +} + +// snippet-end:[gov2.redshift.ListDatabases] + +// snippet-start:[gov2.redshift.CreateTable] + +// CreateTable creates a table named in the database with the given arguments. +func (actor RedshiftDataActions) CreateTable(ctx context.Context, clusterId string, databaseName string, tableName string, userName string, pauser demotools.IPausable, args []string) (*redshiftdata.ExecuteStatementOutput, error) { + sql := "CREATE TABLE " + tableName + " (" + + "id bigint identity(1, 1), " + + "PRIMARY KEY (id)" + for _, value := range args { + sql += ", " + value + } + sql += ");" + createTableInput := &redshiftdata.ExecuteStatementInput{ + ClusterIdentifier: aws.String(clusterId), + Database: aws.String(databaseName), + DbUser: aws.String(userName), + Sql: aws.String(sql), + } + + var opErr *types.DatabaseConnectionException + output, err := actor.RedshiftDataClient.ExecuteStatement(ctx, createTableInput) + if err != nil && errors.As(err, &opErr) { + log.Printf("Could not connect to the database.") + } else if err != nil { + log.Printf("Failed to create table: %v\n", err) + return nil, err + } + + describeStatementInput := &redshiftdata.DescribeStatementInput{Id: output.Id} + query := RedshiftQuery{ + Context: ctx, + Input: *describeStatementInput, + Result: output, + } + + err = actor.WaitForQueryStatus(query, pauser, true) + if err != nil { + log.Printf("Failed to execute query: %v\n", err) + panic(err) + } + log.Printf("Successfully executed query\n") + + log.Println("Table created:", *output.Id) + return output, nil +} + +// snippet-end:[gov2.redshift.CreateTable] + +// snippet-start:[gov2.redshift.DeleteTable] + +// DeleteTable drops the table named from the database. +func (actor RedshiftDataActions) DeleteTable(ctx context.Context, clusterId string, databaseName string, tableName string, userName string) (bool, error) { + sql := "DROP TABLE " + tableName + deleteTableInput := &redshiftdata.ExecuteStatementInput{ + ClusterIdentifier: aws.String(clusterId), + Database: aws.String(databaseName), + DbUser: aws.String(userName), + Sql: aws.String(sql), + } + + var opErr *types.DatabaseConnectionException + output, err := actor.RedshiftDataClient.ExecuteStatement(ctx, deleteTableInput) + if err != nil && errors.As(err, &opErr) { + log.Printf("Could not connect to the database.") + } else if err != nil { + log.Printf("Failed to delete table "+tableName+" from "+databaseName+" database: %v\n", err) + return false, err + } + + log.Println(tableName+" table deleted from "+databaseName+" database:", *output.Id) + return true, nil +} + +// snippet-end:[gov2.redshift.DeleteTable] + +// snippet-start:[gov2.redshift.DeleteRows] + +// DeleteDataRows deletes all rows from the given table. +func (actor RedshiftDataActions) DeleteDataRows(ctx context.Context, clusterId string, databaseName string, tableName string, userName string, pauser demotools.IPausable) (bool, error) { + deleteRows := &redshiftdata.ExecuteStatementInput{ + ClusterIdentifier: aws.String(clusterId), + Database: aws.String(databaseName), + DbUser: aws.String(userName), + Sql: aws.String("DELETE FROM " + tableName + ";"), + } + + result, err := actor.RedshiftDataClient.ExecuteStatement(ctx, deleteRows) + if err != nil { + log.Printf("Failed to execute batch statement: %v\n", err) + return false, err + } + describeInput := redshiftdata.DescribeStatementInput{ + Id: result.Id, + } + query := RedshiftQuery{ + Context: ctx, + Result: result, + Input: describeInput, + } + err = actor.WaitForQueryStatus(query, pauser, true) + if err != nil { + log.Printf("Failed to execute delete query: %v\n", err) + return false, err + } + + log.Printf("Successfully executed delete statement\n") + return true, nil +} + +// snippet-end:[gov2.redshift.DeleteRows] + +// snippet-start:[gov2.redshift.DescribeStatement] + +// DescribeStatement gets information about the given statement. +func (actor RedshiftDataActions) DescribeStatement(query RedshiftQuery) (*redshiftdata.DescribeStatementOutput, error) { + describeOutput, err := actor.RedshiftDataClient.DescribeStatement(query.Context, &query.Input) + var opErr *types.QueryTimeoutException + if errors.As(err, &opErr) { + println("The connection to the redshift data request timed out.") + panic(err) + } else if err != nil { + println("Failed to execute describe statement") + return nil, err + } + return describeOutput, nil +} + +// snippet-end:[gov2.redshift.DescribeStatement] + +// snippet-start:[gov2.redshift.WaitForQueryStatus] + +// WaitForQueryStatus waits until the given RedshiftQuery object has succeeded or failed. +func (actor RedshiftDataActions) WaitForQueryStatus(query RedshiftQuery, pauser demotools.IPausable, showProgress bool) error { + done := false + attempts := 0 + maxWaitCycles := 30 + for done == false { + describeOutput, err := actor.DescribeStatement(query) + if err != nil { + return err + } + if describeOutput.Status == "FAILED" { + return errors.New("failed to describe statement") + } + if attempts >= maxWaitCycles { + return errors.New("timed out waiting for statement") + } + if showProgress { + log.Print(".") + } + if describeOutput.Status == "FINISHED" { + done = true + } + attempts++ + pauser.Pause(attempts) + } + return nil +} + +// snippet-end:[gov2.redshift.WaitForQueryStatus] + +// snippet-start:[gov2.redshift.GetStatementResult] + +// GetStatementResult returns the result of the statement with the given id. +func (actor RedshiftDataActions) GetStatementResult(ctx context.Context, statementId string) (*redshiftdata.GetStatementResultOutput, error) { + + var opErr *types.QueryTimeoutException + getStatementResultOutput, err := actor.RedshiftDataClient.GetStatementResult(ctx, &redshiftdata.GetStatementResultInput{ + Id: aws.String(statementId), + }) + if err != nil && errors.As(err, &opErr) { + log.Printf("Query timed out: %v\n", err) + return nil, err + } else if err != nil { + return nil, err + } + return getStatementResultOutput, nil +} + +// snippet-end:[gov2.redshift.GetStatementResult] diff --git a/gov2/redshift/cmd/main.go b/gov2/redshift/cmd/main.go new file mode 100644 index 00000000000..bdf6faedf02 --- /dev/null +++ b/gov2/redshift/cmd/main.go @@ -0,0 +1,63 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package main + +import ( + "context" + "flag" + "fmt" + "github.com/awsdocs/aws-doc-sdk-examples/gov2/demotools" + "github.com/awsdocs/aws-doc-sdk-examples/gov2/redshift/scenarios" + "log" + "math/rand" + "time" + + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/config" +) + +// main loads default AWS credentials and configuration from the ~/.aws folder and runs +// a scenario specified by the `-scenario` flag. +// +// `-scenario` can be one of the following: +// +// - `basics` - Runs the interactive Basics scenario to show core Redshift actions. +func main() { + scenarioMap := map[string]func(sdkConfig aws.Config, helper scenarios.IScenarioHelper){ + "basics": runRedshiftBasicsScenario, + } + choices := make([]string, len(scenarioMap)) + choiceIndex := 0 + for choice := range scenarioMap { + choices[choiceIndex] = choice + choiceIndex++ + } + scenario := flag.String( + "scenario", "", + fmt.Sprintf("The scenario to run. Must be one of %v.", choices)) + flag.Parse() + + if runScenario, ok := scenarioMap[*scenario]; !ok { + fmt.Printf("'%v' is not a valid scenario.\n", *scenario) + flag.Usage() + } else { + sdkConfig, err := config.LoadDefaultConfig(context.TODO()) + if err != nil { + log.Fatalf("unable to load SDK config, %v", err) + } + + log.SetFlags(0) + helper := scenarios.ScenarioHelper{ + Prefix: "redshift_basics", + Random: rand.New(rand.NewSource(time.Now().UnixNano())), + } + runScenario(sdkConfig, helper) + } +} + +func runRedshiftBasicsScenario(sdkConfig aws.Config, helper scenarios.IScenarioHelper) { + pauser := demotools.Pauser{} + scenario := scenarios.RedshiftBasics(sdkConfig, demotools.NewQuestioner(), pauser, demotools.NewStandardFileSystem(), helper) + scenario.Run() +} diff --git a/gov2/redshift/go.mod b/gov2/redshift/go.mod new file mode 100644 index 00000000000..ab9296b0fb3 --- /dev/null +++ b/gov2/redshift/go.mod @@ -0,0 +1,30 @@ +module github.com/awsdocs/aws-doc-sdk-examples/gov2/redshift + +go 1.21 + +require ( + github.com/aws/aws-sdk-go-v2 v1.30.5 + github.com/aws/aws-sdk-go-v2/config v1.27.33 + github.com/aws/aws-sdk-go-v2/service/redshift v1.46.8 + github.com/aws/aws-sdk-go-v2/service/redshiftdata v1.28.2 + github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.32.9 + github.com/aws/smithy-go v1.20.4 + github.com/awsdocs/aws-doc-sdk-examples/gov2/demotools v0.0.0-20240911175713-48a391575470 + github.com/awsdocs/aws-doc-sdk-examples/gov2/testtools v0.0.0-20240911175713-48a391575470 +) + +require ( + github.com/aws/aws-sdk-go-v2/credentials v1.17.32 // indirect + github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.13 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.17 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.17 // indirect + github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.4 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.19 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.22.7 // indirect + github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.7 // indirect + github.com/aws/aws-sdk-go-v2/service/sts v1.30.7 // indirect + github.com/jmespath/go-jmespath v0.4.0 // indirect + golang.org/x/sys v0.25.0 // indirect + golang.org/x/term v0.24.0 // indirect +) diff --git a/gov2/redshift/go.sum b/gov2/redshift/go.sum new file mode 100644 index 00000000000..b97be7dbff3 --- /dev/null +++ b/gov2/redshift/go.sum @@ -0,0 +1,52 @@ +github.com/aws/aws-sdk-go-v2 v1.30.5 h1:mWSRTwQAb0aLE17dSzztCVJWI9+cRMgqebndjwDyK0g= +github.com/aws/aws-sdk-go-v2 v1.30.5/go.mod h1:CT+ZPWXbYrci8chcARI3OmI/qgd+f6WtuLOoaIA8PR0= +github.com/aws/aws-sdk-go-v2/config v1.27.33 h1:Nof9o/MsmH4oa0s2q9a0k7tMz5x/Yj5k06lDODWz3BU= +github.com/aws/aws-sdk-go-v2/config v1.27.33/go.mod h1:kEqdYzRb8dd8Sy2pOdEbExTTF5v7ozEXX0McgPE7xks= +github.com/aws/aws-sdk-go-v2/credentials v1.17.32 h1:7Cxhp/BnT2RcGy4VisJ9miUPecY+lyE9I8JvcZofn9I= +github.com/aws/aws-sdk-go-v2/credentials v1.17.32/go.mod h1:P5/QMF3/DCHbXGEGkdbilXHsyTBX5D3HSwcrSc9p20I= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.13 h1:pfQ2sqNpMVK6xz2RbqLEL0GH87JOwSxPV2rzm8Zsb74= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.13/go.mod h1:NG7RXPUlqfsCLLFfi0+IpKN4sCB9D9fw/qTaSB+xRoU= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.17 h1:pI7Bzt0BJtYA0N/JEC6B8fJ4RBrEMi1LBrkMdFYNSnQ= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.17/go.mod h1:Dh5zzJYMtxfIjYW+/evjQ8uj2OyR/ve2KROHGHlSFqE= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.17 h1:Mqr/V5gvrhA2gvgnF42Zh5iMiQNcOYthFYwCyrnuWlc= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.17/go.mod h1:aLJpZlCmjE+V+KtN1q1uyZkfnUWpQGpbsn89XPKyzfU= +github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 h1:VaRN3TlFdd6KxX1x3ILT5ynH6HvKgqdiXoTxAF4HQcQ= +github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1/go.mod h1:FbtygfRFze9usAadmnGJNc8KsP346kEe+y2/oyhGAGc= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.4 h1:KypMCbLPPHEmf9DgMGw51jMj77VfGPAN2Kv4cfhlfgI= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.4/go.mod h1:Vz1JQXliGcQktFTN/LN6uGppAIRoLBR2bMvIMP0gOjc= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.19 h1:rfprUlsdzgl7ZL2KlXiUAoJnI/VxfHCvDFr2QDFj6u4= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.19/go.mod h1:SCWkEdRq8/7EK60NcvvQ6NXKuTcchAD4ROAsC37VEZE= +github.com/aws/aws-sdk-go-v2/service/redshift v1.46.8 h1:UBqd0JhsXpCDUf/7ulfzYTx4t+OoJ/iOT7+RefurHis= +github.com/aws/aws-sdk-go-v2/service/redshift v1.46.8/go.mod h1:UdcfC9kA4bn3cdUdFYVCeXZcoPka6WNzbYyRAX/Vpy0= +github.com/aws/aws-sdk-go-v2/service/redshiftdata v1.28.2 h1:oNarSIarQfMAZHeUhD2JOkdEpPfUFfoPKmb1GBK17Kc= +github.com/aws/aws-sdk-go-v2/service/redshiftdata v1.28.2/go.mod h1:9HLbgBikxAqW0V3Q8eQMQvoW1XRq0J7TjqYe8Lpiwx4= +github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.32.9 h1:croIrE67fpV6wff+0M8jbrJZpKSlrqVGrCnqNU5rtoI= +github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.32.9/go.mod h1:BYr9P/rrcLNJ8A36nT15p8tpoVDZ5lroHuMn/njecBw= +github.com/aws/aws-sdk-go-v2/service/sso v1.22.7 h1:pIaGg+08llrP7Q5aiz9ICWbY8cqhTkyy+0SHvfzQpTc= +github.com/aws/aws-sdk-go-v2/service/sso v1.22.7/go.mod h1:eEygMHnTKH/3kNp9Jr1n3PdejuSNcgwLe1dWgQtO0VQ= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.7 h1:/Cfdu0XV3mONYKaOt1Gr0k1KvQzkzPyiKUdlWJqy+J4= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.7/go.mod h1:bCbAxKDqNvkHxRaIMnyVPXPo+OaPRwvmgzMxbz1VKSA= +github.com/aws/aws-sdk-go-v2/service/sts v1.30.7 h1:NKTa1eqZYw8tiHSRGpP0VtTdub/8KNk8sDkNPFaOKDE= +github.com/aws/aws-sdk-go-v2/service/sts v1.30.7/go.mod h1:NXi1dIAGteSaRLqYgarlhP/Ij0cFT+qmCwiJqWh/U5o= +github.com/aws/smithy-go v1.20.4 h1:2HK1zBdPgRbjFOHlfeQZfpC4r72MOb9bZkiFwggKO+4= +github.com/aws/smithy-go v1.20.4/go.mod h1:irrKGvNn1InZwb2d7fkIRNucdfwR8R+Ts3wxYa/cJHg= +github.com/awsdocs/aws-doc-sdk-examples/gov2/demotools v0.0.0-20240911175713-48a391575470 h1:hmMnoNelE/ZROjW70fUd2EBH5pJFIgTTQuDnGkwdoxs= +github.com/awsdocs/aws-doc-sdk-examples/gov2/demotools v0.0.0-20240911175713-48a391575470/go.mod h1:iBzksyiv5HVU+cymGDQbbvcecca+rsARJlDFL8np8oE= +github.com/awsdocs/aws-doc-sdk-examples/gov2/testtools v0.0.0-20240911175713-48a391575470 h1:Jy5/JaR6cJKU4Fc8IDHQooCcwkr1vnewroBAtTAdnz4= +github.com/awsdocs/aws-doc-sdk-examples/gov2/testtools v0.0.0-20240911175713-48a391575470/go.mod h1:9Oj/8PZn3D5Ftp/Z1QWrIEFE0daERMqfJawL9duHRfc= +github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= +github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= +github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= +github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= +golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.24.0 h1:Mh5cbb+Zk2hqqXNO7S1iTjEphVL+jb8ZWaqh/g+JWkM= +golang.org/x/term v0.24.0/go.mod h1:lOBK/LVxemqiMij05LGJ0tzNr8xlmwBRJ81PX6wVLH8= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/gov2/redshift/hello/hello.go b/gov2/redshift/hello/hello.go new file mode 100644 index 00000000000..b690c0c6e95 --- /dev/null +++ b/gov2/redshift/hello/hello.go @@ -0,0 +1,46 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +// snippet-start:[gov2.redshift.Hello] + +package main + +import ( + "context" + "fmt" + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/config" + "github.com/aws/aws-sdk-go-v2/service/redshift" +) + +// main uses the AWS SDK for Go V2 to create a Redshift client +// and list up to 10 clusters in your account. +// This example uses the default settings specified in your shared credentials +// and config files. +func main() { + sdkConfig, err := config.LoadDefaultConfig(context.TODO()) + if err != nil { + fmt.Println("Couldn't load default configuration. Have you set up your AWS account?") + fmt.Println(err) + return + } + redshiftClient := redshift.NewFromConfig(sdkConfig) + count := 20 + fmt.Printf("Let's list up to %v clusters for your account.\n", count) + result, err := redshiftClient.DescribeClusters(context.TODO(), &redshift.DescribeClustersInput{ + MaxRecords: aws.Int32(int32(count)), + }) + if err != nil { + fmt.Printf("Couldn't list clusters for your account. Here's why: %v\n", err) + return + } + if len(result.Clusters) == 0 { + fmt.Println("You don't have any clusters!") + return + } + for _, cluster := range result.Clusters { + fmt.Printf("\t%v : %v\n", *cluster.ClusterIdentifier, *cluster.ClusterStatus) + } +} + +// snippet-end:[gov2.redshift.Hello] diff --git a/gov2/redshift/scenarios/redshift_basics.go b/gov2/redshift/scenarios/redshift_basics.go new file mode 100644 index 00000000000..0bba40177dc --- /dev/null +++ b/gov2/redshift/scenarios/redshift_basics.go @@ -0,0 +1,409 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +// snippet-start:[gov2.redshift.BasicsScenario] + +package scenarios + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "github.com/aws/aws-sdk-go-v2/aws" + redshift_types "github.com/aws/aws-sdk-go-v2/service/redshift/types" + redshiftdata_types "github.com/aws/aws-sdk-go-v2/service/redshiftdata/types" + "github.com/aws/aws-sdk-go-v2/service/secretsmanager" + "github.com/awsdocs/aws-doc-sdk-examples/gov2/demotools" + "github.com/awsdocs/aws-doc-sdk-examples/gov2/redshift/actions" + "log" + "math/rand" + "strings" + "time" + + "github.com/aws/aws-sdk-go-v2/service/redshift" + "github.com/aws/aws-sdk-go-v2/service/redshiftdata" +) + +// IScenarioHelper abstracts input and wait functions from a scenario so that they +// can be mocked for unit testing. +type IScenarioHelper interface { + GetName() string +} + +const rMax = 100000 + +type ScenarioHelper struct { + Prefix string + Random *rand.Rand +} + +// GetName returns a unique name formed of a prefix and a random number. +func (helper ScenarioHelper) GetName() string { + return fmt.Sprintf("%v%v", helper.Prefix, helper.Random.Intn(rMax)) +} + +// RedshiftBasicsScenario separates the steps of this scenario into individual functions so that +// they are simpler to read and understand. +type RedshiftBasicsScenario struct { + sdkConfig aws.Config + helper IScenarioHelper + questioner demotools.IQuestioner + pauser demotools.IPausable + filesystem demotools.IFileSystem + redshiftActor *actions.RedshiftActions + redshiftDataActor *actions.RedshiftDataActions + secretsmanager *SecretsManager +} + +// SecretsManager is used to retrieve username and password information from a secure service. +type SecretsManager struct { + SecretsManagerClient *secretsmanager.Client +} + +// RedshiftBasics constructs a new Redshift Basics runner. +func RedshiftBasics(sdkConfig aws.Config, questioner demotools.IQuestioner, pauser demotools.IPausable, filesystem demotools.IFileSystem, helper IScenarioHelper) RedshiftBasicsScenario { + scenario := RedshiftBasicsScenario{ + sdkConfig: sdkConfig, + helper: helper, + questioner: questioner, + pauser: pauser, + filesystem: filesystem, + secretsmanager: &SecretsManager{SecretsManagerClient: secretsmanager.NewFromConfig(sdkConfig)}, + redshiftActor: &actions.RedshiftActions{RedshiftClient: redshift.NewFromConfig(sdkConfig)}, + redshiftDataActor: &actions.RedshiftDataActions{RedshiftDataClient: redshiftdata.NewFromConfig(sdkConfig)}, + } + return scenario +} + +// snippet-start:[gov2.redshift.Movie.struct] + +// Movie makes it easier to use Movie objects given in json format. +type Movie struct { + ID int `json:"id"` + Title string `json:"title"` + Year int `json:"year"` +} + +// snippet-end:[gov2.redshift.Movie.struct] + +// User makes it easier to get the User data back from SecretsManager and use it later. +type User struct { + Username string `json:"userName"` + Password string `json:"userPassword"` +} + +// Run runs the RedshiftBasics interactive example that shows you how to use Amazon +// Redshift and how to interact with its common endpoints. +// +// 0. Retrieve username and password information to access Redshift. +// 1. Create a cluster. +// 2. Wait for the cluster to become available. +// 3. List the available databases in the region. +// 4. Create a table named "Movies" in the "dev" database. +// 5. Populate the movies table from the "movies.json" file. +// 6. Query the movies table by year. +// 7. Modify the cluster's maintenance window. +// 8. Optionally clean up all resources created during this demo. +// +// This example creates an Amazon Redshift service client from the specified sdkConfig so that +// you can replace it with a mocked or stubbed config for unit testing. +// +// It uses a questioner from the `demotools` package to get input during the example. +// This package can be found in the ..\..\demotools folder of this repo. +func (runner *RedshiftBasicsScenario) Run() { + + user := User{} + secretId := "s3express/basics/secrets" + clusterId := "demo-cluster-1" + maintenanceWindow := "wed:07:30-wed:08:00" + databaseName := "dev" + tableName := "Movies" + fileName := "Movies.json" + nodeType := "ra3.xlplus" + clusterType := "single-node" + ctx := context.TODO() + + defer func() { + if r := recover(); r != nil { + log.Println("Something went wrong with the demo.") + _, isMock := runner.questioner.(*demotools.MockQuestioner) + if isMock || runner.questioner.AskBool("Do you want to see the full error message (y/n)?", "y") { + log.Println(r) + } + runner.cleanUpResources(ctx, clusterId, databaseName, tableName, user.Username, runner.questioner) + } + }() + + // Retrieve the userName and userPassword from SecretsManager + output, err := runner.secretsmanager.SecretsManagerClient.GetSecretValue(ctx, &secretsmanager.GetSecretValueInput{ + SecretId: aws.String(secretId), + }) + if err != nil { + log.Printf("There was a problem getting the secret value: %s", err) + log.Printf("Please make sure to create a secret named 's3express/basics/secrets' with keys of 'userName' and 'userPassword'.") + panic(err) + } + + err = json.Unmarshal([]byte(*output.SecretString), &user) + if err != nil { + log.Printf("There was a problem parsing the secret value from JSON: %s", err) + panic(err) + } + + // Create the Redshift cluster + _, err = runner.redshiftActor.CreateCluster(ctx, clusterId, user.Username, user.Password, nodeType, clusterType, true) + if err != nil { + var clusterAlreadyExistsFault *redshift_types.ClusterAlreadyExistsFault + if errors.As(err, &clusterAlreadyExistsFault) { + log.Println("Cluster already exists. Continuing.") + } else { + log.Println("Error creating cluster.") + panic(err) + } + } + + // Wait for the cluster to become available + waiter := redshift.NewClusterAvailableWaiter(runner.redshiftActor.RedshiftClient) + err = waiter.Wait(ctx, &redshift.DescribeClustersInput{ + ClusterIdentifier: aws.String(clusterId), + }, 5*time.Minute) + if err != nil { + log.Println("An error occurred waiting for the cluster.") + panic(err) + } + + // Get some info about the cluster + describeOutput, err := runner.redshiftActor.DescribeClusters(ctx, clusterId) + if err != nil { + log.Println("Something went wrong trying to get information about the cluster.") + panic(err) + } + log.Println("Here's some information about the cluster.") + log.Printf("The cluster's status is %s", *describeOutput.Clusters[0].ClusterStatus) + log.Printf("The cluster was created at %s", *describeOutput.Clusters[0].ClusterCreateTime) + + // List databases + log.Println("List databases in", clusterId) + runner.questioner.Ask("Press Enter to continue...") + err = runner.redshiftDataActor.ListDatabases(ctx, clusterId, databaseName, user.Username) + if err != nil { + log.Printf("Failed to list databases: %v\n", err) + panic(err) + } + + // Create the "Movies" table + log.Println("Now you will create a table named " + tableName + ".") + runner.questioner.Ask("Press Enter to continue...") + err = nil + result, err := runner.redshiftDataActor.CreateTable(ctx, clusterId, databaseName, tableName, user.Username, runner.pauser, []string{"title VARCHAR(256)", "year INT"}) + if err != nil { + log.Printf("Failed to create table: %v\n", err) + panic(err) + } + + describeInput := redshiftdata.DescribeStatementInput{ + Id: result.Id, + } + query := actions.RedshiftQuery{ + Context: ctx, + Input: describeInput, + Result: result, + } + err = runner.redshiftDataActor.WaitForQueryStatus(query, runner.pauser, true) + if err != nil { + log.Printf("Failed to execute query: %v\n", err) + panic(err) + } + log.Printf("Successfully executed query\n") + + // Populate the "Movies" table + runner.PopulateMoviesTable(ctx, clusterId, databaseName, tableName, user.Username, fileName) + + // Query the "Movies" table by year + log.Println("Query the Movies table by year.") + year := runner.questioner.AskInt( + fmt.Sprintf("Enter a value between %v and %v:", 2012, 2014), + demotools.InIntRange{Lower: 2012, Upper: 2014}) + runner.QueryMoviesByYear(ctx, clusterId, databaseName, tableName, user.Username, year) + + // Modify the cluster's maintenance window + runner.redshiftActor.ModifyCluster(ctx, clusterId, maintenanceWindow) + + // Delete the Redshift cluster if confirmed + runner.cleanUpResources(ctx, clusterId, databaseName, tableName, user.Username, runner.questioner) + + log.Println("Thanks for watching!") +} + +// cleanUpResources asks the user if they would like to delete each resource created during the scenario, from most +// impactful to least impactful. If any choice to delete is made, further deletion attempts are skipped. +func (runner *RedshiftBasicsScenario) cleanUpResources(ctx context.Context, clusterId string, databaseName string, tableName string, userName string, questioner demotools.IQuestioner) { + deleted := false + var err error = nil + if questioner.AskBool("Do you want to delete the entire cluster? This will clean up all resources. (y/n)", "y") { + deleted, err = runner.redshiftActor.DeleteCluster(ctx, clusterId) + if err != nil { + log.Printf("Error deleting cluster: %v", err) + } + } + if !deleted && questioner.AskBool("Do you want to delete the dev table? This will clean up all inserted records but keep your cluster intact. (y/n)", "y") { + deleted, err = runner.redshiftDataActor.DeleteTable(ctx, clusterId, databaseName, tableName, userName) + if err != nil { + log.Printf("Error deleting movies table: %v", err) + } + } + if !deleted && questioner.AskBool("Do you want to delete all rows in the Movies table? This will clean up all inserted records but keep your cluster and table intact. (y/n)", "y") { + deleted, err = runner.redshiftDataActor.DeleteDataRows(ctx, clusterId, databaseName, tableName, userName, runner.pauser) + if err != nil { + log.Printf("Error deleting data rows: %v", err) + } + } + if !deleted { + log.Print("Please manually delete any unwanted resources.") + } +} + +// snippet-start:[gov2.redshift.loadMoviesFromJSON] + +// loadMoviesFromJSON takes the file and populates a slice of Movie objects. +func (runner *RedshiftBasicsScenario) loadMoviesFromJSON(fileName string, filesystem demotools.IFileSystem) ([]Movie, error) { + file, err := filesystem.OpenFile("../../resources/sample_files/" + fileName) + if err != nil { + return nil, err + } + defer filesystem.CloseFile(file) + + var movies []Movie + err = json.NewDecoder(file).Decode(&movies) + if err != nil { + return nil, err + } + + return movies, nil +} + +// snippet-end:[gov2.redshift.loadMoviesFromJSON] + +// snippet-start:[gov2.redshift.PopulateMoviesTable] + +// PopulateMoviesTable reads data from the file and inserts records into the "Movies" table. +func (runner *RedshiftBasicsScenario) PopulateMoviesTable(ctx context.Context, clusterId string, databaseName string, tableName string, userName string, fileName string) { + log.Println("Populate the " + tableName + " table using the " + fileName + " file.") + numRecords := runner.questioner.AskInt( + fmt.Sprintf("Enter a value between %v and %v:", 10, 100), + demotools.InIntRange{Lower: 10, Upper: 100}) + + movies, err := runner.loadMoviesFromJSON(fileName, runner.filesystem) + if err != nil { + log.Printf("Failed to load movies from JSON: %v\n", err) + panic(err) + } + + var sqlStatements []string + + for i, movie := range movies { + if i >= numRecords { + break + } + + sqlStatement := fmt.Sprintf(`INSERT INTO %s (title, year) VALUES ('%s', %d);`, + tableName, + strings.Replace(movie.Title, "'", "''", -1), // Double any single quotes to escape them + movie.Year) + + sqlStatements = append(sqlStatements, sqlStatement) + } + + input := &redshiftdata.BatchExecuteStatementInput{ + ClusterIdentifier: aws.String(clusterId), + Database: aws.String(databaseName), + DbUser: aws.String(userName), + Sqls: sqlStatements, + } + + result, err := runner.redshiftDataActor.ExecuteBatchStatement(ctx, *input) + if err != nil { + log.Printf("Failed to execute batch statement: %v\n", err) + panic(err) + } + + describeInput := redshiftdata.DescribeStatementInput{ + Id: result.Id, + } + + query := actions.RedshiftQuery{ + Context: ctx, + Result: result, + Input: describeInput, + } + err = runner.redshiftDataActor.WaitForQueryStatus(query, runner.pauser, true) + if err != nil { + log.Printf("Failed to execute batch insert query: %v\n", err) + return + } + log.Printf("Successfully executed batch statement\n") + + log.Printf("%d records were added to the Movies table.\n", numRecords) +} + +// snippet-end:[gov2.redshift.PopulateMoviesTable] + +// snippet-start:[gov2.redshift.QueryMoviesByYear] + +// QueryMoviesByYear retrieves only movies from the "Movies" table which match the given year. +func (runner *RedshiftBasicsScenario) QueryMoviesByYear(ctx context.Context, clusterId string, databaseName string, tableName string, userName string, year int) { + + sqlStatement := fmt.Sprintf(`SELECT title FROM %s WHERE year = %d;`, tableName, year) + + input := &redshiftdata.ExecuteStatementInput{ + ClusterIdentifier: aws.String(clusterId), + Database: aws.String(databaseName), + DbUser: aws.String(userName), + Sql: aws.String(sqlStatement), + } + + result, err := runner.redshiftDataActor.ExecuteStatement(ctx, *input) + if err != nil { + log.Printf("Failed to query movies: %v\n", err) + panic(err) + } + + log.Println("The identifier of the statement is ", *result.Id) + + describeInput := redshiftdata.DescribeStatementInput{ + Id: result.Id, + } + + query := actions.RedshiftQuery{ + Context: ctx, + Input: describeInput, + Result: result, + } + err = runner.redshiftDataActor.WaitForQueryStatus(query, runner.pauser, true) + if err != nil { + log.Printf("Failed to execute query: %v\n", err) + panic(err) + } + log.Printf("Successfully executed query\n") + + getResultOutput, err := runner.redshiftDataActor.GetStatementResult(ctx, *result.Id) + if err != nil { + log.Printf("Failed to query movies: %v\n", err) + panic(err) + } + for _, row := range getResultOutput.Records { + for _, col := range row { + title, ok := col.(*redshiftdata_types.FieldMemberStringValue) + if !ok { + log.Println("Failed to parse the field") + } else { + log.Printf("The Movie title field is %s\n", title.Value) + } + } + } +} + +// snippet-end:[gov2.redshift.QueryMoviesByYear] + +// snippet-end:[gov2.redshift.BasicsScenario] diff --git a/gov2/redshift/scenarios/redshift_basics_integration_test.go b/gov2/redshift/scenarios/redshift_basics_integration_test.go new file mode 100644 index 00000000000..e8debb238b0 --- /dev/null +++ b/gov2/redshift/scenarios/redshift_basics_integration_test.go @@ -0,0 +1,63 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +//go:build integration +// +build integration + +// SPDX-License-Identifier: Apache-2.0 + +// Integration test for the Amazon Redshift Basics scenario. + +package scenarios + +import ( + "bytes" + "context" + "github.com/aws/aws-sdk-go-v2/config" + "github.com/awsdocs/aws-doc-sdk-examples/gov2/demotools" + "log" + "math/rand" + "os" + "strings" + "testing" +) + +// MockPauser holds the pausable object. +type MockPauser struct{} + +// Pause waits for the specified number of seconds. +func (pausable MockPauser) Pause(secs int) {} + +func TestBasicsScenario_Integration(t *testing.T) { + outFile := "integ-test.out" + mockQuestioner := &demotools.MockQuestioner{ + Answers: []string{ + "enter", "enter", "10", "2013", "n", "y", + }, + } + + sdkConfig, err := config.LoadDefaultConfig(context.TODO()) + if err != nil { + log.Fatalf("unable to load SDK config, %v", err) + } + + log.SetFlags(0) + var buf bytes.Buffer + log.SetOutput(&buf) + + pwd, _ := os.Getwd() + file, err := os.Open(pwd + "/../../../resources/sample_files/Movies.json") + helper := ScenarioHelper{ + Prefix: "redshift_basics_integration_tests", + Random: rand.New(rand.NewSource(0)), + } + scenario := RedshiftBasics(sdkConfig, mockQuestioner, demotools.Pauser{}, demotools.NewMockFileSystem(file), helper) + scenario.Run() + + _ = os.Remove(outFile) + + log.SetOutput(os.Stderr) + output := strings.ToLower(buf.String()) + if strings.Contains(output, "error") || strings.Contains(output, "failed") { + t.Errorf("didn't run to successful completion. Here's the log:\n%v", buf.String()) + } +} diff --git a/gov2/redshift/scenarios/redshift_basics_test.go b/gov2/redshift/scenarios/redshift_basics_test.go new file mode 100644 index 00000000000..28a3281c852 --- /dev/null +++ b/gov2/redshift/scenarios/redshift_basics_test.go @@ -0,0 +1,128 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +// Unit tests for the get started scenario. + +package scenarios + +import ( + "encoding/json" + "fmt" + "github.com/awsdocs/aws-doc-sdk-examples/gov2/redshift/stubs" + "io" + "math/rand" + "testing" + "time" + + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/awsdocs/aws-doc-sdk-examples/gov2/demotools" + "github.com/awsdocs/aws-doc-sdk-examples/gov2/testtools" +) + +// TestRunGetStartedScenario runs the scenario multiple times. The first time, it runs with no +// errors. In subsequent runs, it specifies that each stub in the sequence should +// raise an error and verifies the results. +func TestRunBasicsScenario(t *testing.T) { + scenarioTest := BasicsScenarioTest{ + Helper: ScenarioHelper{ + Prefix: "basics_test_", + Random: rand.New(rand.NewSource(time.Now().UnixNano())), + }, + File: MockFile{ + ReturnData: []byte(`[{"year": 2013, "title": "Rush"}]`), + }, + } + testtools.RunScenarioTests(&scenarioTest, t) +} + +// httpErr is used to mock an HTTP error. This is required by the download manager, +// which calls GetObject until it receives a 415 status code. IDEs may indicate it's +// unused, but it is required for side effects. +type httpErr struct { + statusCode int +} + +type MockFile struct { + ReturnData json.RawMessage +} + +func (f MockFile) Read(p []byte) (n int, err error) { + n = copy(p, f.ReturnData[0:]) + return n, nil +} +func (f MockFile) Write(p []byte) (n int, err error) { + return 0, nil +} +func (f MockFile) Close() error { + return nil +} + +func (responseErr httpErr) HTTPStatusCode() int { return responseErr.statusCode } +func (responseErr httpErr) Error() string { + return fmt.Sprintf("HTTP error: %v", responseErr.statusCode) +} + +// GetStartedScenarioTest encapsulates data for a scenario test. +type BasicsScenarioTest struct { + Config aws.Config + File io.ReadWriteCloser + Helper IScenarioHelper + Answers []string + OutFilename string +} + +// SetupDataAndStubs sets up test data and builds the stubs that are used to return +// mocked data. +func (scenarioTest *BasicsScenarioTest) SetupDataAndStubs() []testtools.Stub { + // set up variables + clusterId := "demo-cluster-1" + secretId := "s3express/basics/secrets" + user := User{ + Username: "testUser", + Password: "testPassword", + } + databaseName := "dev" + nodeType := "ra3.xlplus" + clusterType := "single-node" + publiclyAccessible := true + testId := "test-result-id" + sql := "test sql statement" + sqls := []string{sql} + scenarioTest.OutFilename = "test.out" + + scenarioTest.Answers = []string{ + "enter", "enter", "10", "2013", "y", "y", "y", + } + + // set up stubs + var stubList []testtools.Stub + stubList = append(stubList, stubs.StubGetSecretValue(secretId, user.Username, user.Password, nil)) + stubList = append(stubList, stubs.StubCreateCluster(clusterId, user.Username, user.Password, nodeType, clusterType, publiclyAccessible, nil)) + stubList = append(stubList, stubs.StubDescribeClusters(clusterId, nil)) + stubList = append(stubList, stubs.StubDescribeClusters(clusterId, nil)) + stubList = append(stubList, stubs.StubListDatabases(clusterId, databaseName, user.Username, nil)) + stubList = append(stubList, stubs.StubExecuteStatement(clusterId, databaseName, user.Username, sql, testId, nil)) + stubList = append(stubList, stubs.StubDescribeStatement(testId, nil)) + stubList = append(stubList, stubs.StubDescribeStatement(testId, nil)) + stubList = append(stubList, stubs.StubBatchExecuteStatement(clusterId, databaseName, user.Username, sqls, testId, nil)) + stubList = append(stubList, stubs.StubDescribeStatement(testId, nil)) + stubList = append(stubList, stubs.StubExecuteStatement(clusterId, databaseName, user.Username, sql, testId, nil)) + stubList = append(stubList, stubs.StubDescribeStatement(testId, nil)) + stubList = append(stubList, stubs.StubGetStatementResult(nil)) + stubList = append(stubList, stubs.StubModifyCluster(nil)) + stubList = append(stubList, stubs.StubDeleteCluster(clusterId)) + + return stubList +} + +// RunSubTest performs a single test run with a set of stubs set up to run with +// or without errors. +func (scenarioTest *BasicsScenarioTest) RunSubTest(stubber *testtools.AwsmStubber) { + mockQuestioner := demotools.MockQuestioner{Answers: scenarioTest.Answers} + scenario := RedshiftBasics(*stubber.SdkConfig, &mockQuestioner, demotools.Pauser{}, demotools.NewMockFileSystem(scenarioTest.File), scenarioTest.Helper) + + scenario.Run() +} + +func (scenarioTest *BasicsScenarioTest) Cleanup() { +} diff --git a/gov2/redshift/stubs/redshift_data_stubs.go b/gov2/redshift/stubs/redshift_data_stubs.go new file mode 100644 index 00000000000..574c14d1a15 --- /dev/null +++ b/gov2/redshift/stubs/redshift_data_stubs.go @@ -0,0 +1,84 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package stubs + +import ( + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/redshiftdata" + "github.com/awsdocs/aws-doc-sdk-examples/gov2/testtools" +) + +func StubListDatabases(clusterId string, databaseName string, userName string, raiseErr *testtools.StubError) testtools.Stub { + return testtools.Stub{ + OperationName: "ListDatabases", + Input: &redshiftdata.ListDatabasesInput{ + ClusterIdentifier: aws.String(clusterId), + Database: aws.String(databaseName), + DbUser: aws.String(userName), + }, + Output: &redshiftdata.ListDatabasesOutput{ + Databases: []string{databaseName}, + }, + Error: raiseErr, + } +} + +func StubExecuteStatement(clusterId string, databaseName string, userName, sql string, resultId string, raiseErr *testtools.StubError) testtools.Stub { + return testtools.Stub{ + OperationName: "ExecuteStatement", + Input: &redshiftdata.ExecuteStatementInput{ + ClusterIdentifier: aws.String(clusterId), + Database: aws.String(databaseName), + DbUser: aws.String(userName), + Sql: aws.String(sql), + }, + Output: &redshiftdata.ExecuteStatementOutput{ + Id: aws.String(resultId), + }, + IgnoreFields: []string{"ClientToken", "Sql"}, + Error: raiseErr, + } +} + +func StubBatchExecuteStatement(clusterId string, databaseName string, userName string, sqlStatements []string, resultId string, raiseErr *testtools.StubError) testtools.Stub { + return testtools.Stub{ + OperationName: "BatchExecuteStatement", + Input: &redshiftdata.BatchExecuteStatementInput{ + ClusterIdentifier: aws.String(clusterId), + Database: aws.String(databaseName), + DbUser: aws.String(userName), + Sqls: sqlStatements, + }, + Output: &redshiftdata.BatchExecuteStatementOutput{ + Id: aws.String(resultId), + }, + IgnoreFields: []string{"Sqls", "ClientToken"}, + Error: raiseErr, + } +} + +func StubDescribeStatement(statementId string, raiseErr *testtools.StubError) testtools.Stub { + return testtools.Stub{ + OperationName: "DescribeStatement", + Input: &redshiftdata.DescribeStatementInput{ + Id: aws.String(statementId), + }, + Output: &redshiftdata.DescribeStatementOutput{ + Status: "FINISHED", + }, + IgnoreFields: []string{"Id"}, + Error: raiseErr, + } +} + +func StubGetStatementResult(raiseErr *testtools.StubError) testtools.Stub { + return testtools.Stub{ + OperationName: "GetStatementResult", + Input: &redshiftdata.GetStatementResultInput{ + Id: aws.String("test-result-id"), + }, + Output: &redshiftdata.GetStatementResultOutput{}, + Error: raiseErr, + } +} diff --git a/gov2/redshift/stubs/redshift_stubs.go b/gov2/redshift/stubs/redshift_stubs.go new file mode 100644 index 00000000000..ccade45569d --- /dev/null +++ b/gov2/redshift/stubs/redshift_stubs.go @@ -0,0 +1,88 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package stubs + +import ( + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/redshift" + "github.com/aws/aws-sdk-go-v2/service/redshift/types" + "github.com/aws/smithy-go" + "github.com/awsdocs/aws-doc-sdk-examples/gov2/testtools" + "time" +) + +func StubDescribeClusters(clusterId string, raiseErr *testtools.StubError) testtools.Stub { + clusters := []types.Cluster{ + { + ClusterStatus: aws.String("available"), + ClusterCreateTime: aws.Time(time.Now()), + }, + } + return testtools.Stub{ + OperationName: "DescribeClusters", + Input: &redshift.DescribeClustersInput{ + ClusterIdentifier: &clusterId, + }, + Output: &redshift.DescribeClustersOutput{ + Clusters: clusters, + }, + SkipErrorTest: false, + IgnoreFields: nil, + Error: raiseErr, + } +} + +func StubCreateCluster(clusterId string, userName string, userPassword string, nodeType string, clusterType string, publiclyAccessible bool, raiseErr *testtools.StubError) testtools.Stub { + input := &redshift.CreateClusterInput{ + ClusterIdentifier: aws.String(clusterId), + MasterUserPassword: aws.String(userPassword), + MasterUsername: aws.String(userName), + NodeType: aws.String(nodeType), + ClusterType: aws.String(clusterType), + PubliclyAccessible: aws.Bool(publiclyAccessible), + } + return testtools.Stub{ + OperationName: "CreateCluster", + Input: input, + Output: &redshift.CreateClusterOutput{ + Cluster: &types.Cluster{ + ClusterStatus: aws.String("available"), + ClusterIdentifier: aws.String("test-cluster-identifier"), + }, + }, + Error: raiseErr, + } +} + +func StubModifyCluster(raiseErr *testtools.StubError) testtools.Stub { + return testtools.Stub{ + OperationName: "ModifyCluster", + Input: &redshift.ModifyClusterInput{ + ClusterIdentifier: aws.String("demo-cluster-1"), + PreferredMaintenanceWindow: aws.String("wed:07:30-wed:08:00"), + }, + Output: &redshift.ModifyClusterOutput{ + Cluster: &types.Cluster{ + PreferredMaintenanceWindow: aws.String("wed:07:30-wed:08:00"), + }, + }, + Error: raiseErr, + } +} + +func StubDeleteCluster(clusterId string) testtools.Stub { + return testtools.Stub{ + OperationName: "DescribeClusters", // Because a waiter is used, this is the actual called mocked. + Input: &redshift.DescribeClustersInput{ + ClusterIdentifier: &clusterId, + }, + SkipErrorTest: true, + Error: &testtools.StubError{ + Err: &smithy.GenericAPIError{ + Code: "ClusterNotFound", + Message: "ClusterNotFound", + }, + }, + } +} diff --git a/gov2/redshift/stubs/secrets_manager_stubs.go b/gov2/redshift/stubs/secrets_manager_stubs.go new file mode 100644 index 00000000000..5fcff2fb6a9 --- /dev/null +++ b/gov2/redshift/stubs/secrets_manager_stubs.go @@ -0,0 +1,24 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package stubs + +import ( + "fmt" + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/secretsmanager" + "github.com/awsdocs/aws-doc-sdk-examples/gov2/testtools" +) + +func StubGetSecretValue(secretId string, userName string, userPassword string, raiseErr *testtools.StubError) testtools.Stub { + return testtools.Stub{ + OperationName: "GetSecretValue", + Input: &secretsmanager.GetSecretValueInput{ + SecretId: aws.String(secretId), + }, + Output: &secretsmanager.GetSecretValueOutput{ + SecretString: aws.String(fmt.Sprintf(`{"userName": "%s", "userPassword": "%s"}`, userName, userPassword)), + }, + Error: raiseErr, + } +} diff --git a/javav2/example_code/redshift/README.md b/javav2/example_code/redshift/README.md index 260d314c44f..74823ff8740 100644 --- a/javav2/example_code/redshift/README.md +++ b/javav2/example_code/redshift/README.md @@ -31,7 +31,7 @@ For prerequisites, see the [README](../../README.md#Prerequisites) in the `javav ### Get started -- [Hello Amazon Redshift](src/main/java/com/example/redshift/HelloRedshift.java#L6) (`describeClusters`) +- [Hello Amazon Redshift](src/main/java/com/example/redshift/HelloRedshift.java#L6) (`DescribeClusters`) ### Basics @@ -73,7 +73,7 @@ This example shows you how to get started using Amazon Redshift. #### Learn the basics -This example shows you how to learn core operations for Amazon Redshift using an AWS. +This example shows you how to learn core operations for Amazon Redshift using an AWS SDK. diff --git a/python/example_code/redshift/README.md b/python/example_code/redshift/README.md index 58107d2d5d2..865bbedb419 100644 --- a/python/example_code/redshift/README.md +++ b/python/example_code/redshift/README.md @@ -36,7 +36,7 @@ python -m pip install -r requirements.txt ### Get started -- [Hello Amazon Redshift](hello.py#L4) (`describeClusters`) +- [Hello Amazon Redshift](hello.py#L4) (`DescribeClusters`) ### Basics @@ -79,7 +79,7 @@ python hello.py #### Learn the basics -This example shows you how to learn core operations for Amazon Redshift using an AWS. +This example shows you how to learn core operations for Amazon Redshift using an AWS SDK.