Skip to content

Adding a New AWS Service#

AWS frequently launches new services, and Terraform support is frequently desired by the community shortly after launch. Depending on the API surface area of the new service, this could be a major undertaking. The following steps should be followed to prepare for adding the resources that allow for Terraform management of that service.

Perform Service Design#

Before adding a new service to the provider it's a good idea to familiarize yourself with the primary workflows practitioners are likely to want to accomplish with the provider to ensure the provider design can solve this. It's not always necessary to cover 100% of the AWS service offering to unblock most workflows.

You should have an idea of what resources and data sources should be added, their dependencies and relative importance concerning the workflow. This should give you an idea of the order in which resources are to be added. It's important to note that generally, we like to review and merge resources in isolation, and avoid combining multiple new resources in one Pull Request.

Using the AWS API documentation as a reference, identify the various APIs that correspond to the CRUD operations which consist of the management surface for that resource. These will be the set of APIs called from the new resource. The API's model attributes will correspond to your resource schema.

From there begin to map out the list of resources you would like to implement, and note your plan on the GitHub issue relating to the service (or create one if one does not exist) for the community and maintainers to feedback.

Add a Service Client#

Before new resources are submitted, please raise a separate pull request containing just the new AWS SDK for Go service client.

To add an AWS SDK for Go service client:

  1. Check the file names/data/names_data.hcl for the service.

  2. If the service is there and the not_implemented attribute does not exist, you are ready to implement the first resource or data source.

  3. If the service is there and the not_implemented attribute is true, remove it and submit the client pull request as described below.

  4. Otherwise, determine the service identifier using the rule described in the Naming Guide.

  5. In names/data/names_data.hcl, add a new hcl block with all the requested information for the service following the guidance in the names README.

    Tip

    Be very careful when adding or changing data in names_data.hcl! The Provider and generators depend on the file being correct. We strongly recommend using an editor with HCL support.

Once the names data is ready, create a new service directory with the appropriate service name.

mkdir internal/service/<service>

Add a new file internal/service/<service>/generate.go with the following content. This will generate the structs required for resource self-registration.

// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

//go:generate go run ../../generate/servicepackage/main.go
// ONLY generate directives and package declaration! Do not add anything else to this file.

package <service>

Next, generate the client and ensure all dependencies are fetched.

make gen
go mod tidy

At this point a pull request with the re-generated files and new service client can be submitted.

Once the service client has been added, implement the first resource or data source in a separate PR.

Adding a Custom Service Client#

If an AWS service must be created in a non-standard way, for example, the service API's endpoint must be accessed via a single AWS Region, then:

  1. Make the skip_client_generate attribute true for the service in names/data/names_data.hcl

  2. Run make gen

  3. Add a file internal/<service>/service_package.go that contains an API client factory function, for example:

    package costoptimizationhub
    
    import (
        "context"
    
        "github.com/aws/aws-sdk-go-v2/aws"
        "github.com/aws/aws-sdk-go-v2/service/costoptimizationhub"
        "github.com/hashicorp/terraform-provider-aws/names"
    )
    
    // NewClient returns a new AWS SDK for Go v2 client for this service package's AWS API.
    func (p *servicePackage) NewClient(ctx context.Context, config map[string]any) (*costoptimizationhub.Client, error) {
        cfg := *(config["aws_sdkv2_config"].(*aws.Config))
    
        return costoptimizationhub.NewFromConfig(cfg,
            costoptimizationhub.WithEndpointResolverV2(newEndpointResolverSDKv2()),
            withBaseEndpoint(config[names.AttrEndpoint].(string)),
            func(o *costoptimizationhub.Options) {
                if config["partition"].(string) == names.StandardPartitionID {
                    // Cost Optimization Hub endpoint is available only in us-east-1 Region.
                    if cfg.Region != names.USEast1RegionID {
                        tflog.Info(ctx, "overriding region", map[string]any{
                            "original_region": cfg.Region,
                            "override_region": names.USEast1RegionID,
                        })
                        o.Region = names.USEast1RegionID
                    }
                }
            },
        ), nil
    }
    

Customizing a new Service Client#

If an AWS service must be customized after creation, for example, retry handling must be changed, then:

  1. Add a file internal/<service>/service_package.go that contains an API client customization function, for example:
package shield

import (
    "context"

    "github.com/aws/aws-sdk-go-v2/aws"
    "github.com/aws/aws-sdk-go-v2/service/shield"
    "github.com/hashicorp/aws-sdk-go-base/v2/endpoints"
    "github.com/hashicorp/terraform-plugin-log/tflog"
    "github.com/hashicorp/terraform-provider-aws/names"
)

// NewClient returns a new AWS SDK for Go v2 client for this service package's AWS API.
func (p *servicePackage) NewClient(ctx context.Context, config map[string]any) (*shield.Client, error) {
    cfg := *(config["aws_sdkv2_config"].(*aws.Config))

    return shield.NewFromConfig(cfg,
        shield.WithEndpointResolverV2(newEndpointResolverV2()),
        withBaseEndpoint(config[names.AttrEndpoint].(string)),
        func(o *shield.Options) {
            // Force "global" services to correct Regions.
            if config["partition"].(string) == endpoints.AwsPartitionID {
                if cfg.Region != names.USEast1RegionID {
                    tflog.Info(ctx, "overriding region", map[string]any{
                        "original_region": cfg.Region,
                        "override_region": names.USEast1RegionID,
                    })
                    o.Region = names.USEast1RegionID
                }
            }
        },
    ), nil
}