Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

data source/service: Add data source for service #16640

Merged
merged 12 commits into from
Feb 10, 2022
3 changes: 3 additions & 0 deletions .changelog/16640.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:new-data-source
aws_service
```
1 change: 1 addition & 0 deletions internal/provider/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -681,6 +681,7 @@ func Provider() *schema.Provider {
"aws_partition": meta.DataSourcePartition(),
"aws_region": meta.DataSourceRegion(),
"aws_regions": meta.DataSourceRegions(),
"aws_service": meta.DataSourceService(),

"aws_mq_broker": mq.DataSourceBroker(),

Expand Down
121 changes: 121 additions & 0 deletions internal/service/meta/service_data_source.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
package meta

import (
"fmt"
"strings"

"github.com/aws/aws-sdk-go/aws/endpoints"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-provider-aws/internal/conns"
)

func DataSourceService() *schema.Resource {
return &schema.Resource{
Read: dataSourceServiceRead,

Schema: map[string]*schema.Schema{
"dns_name": {
Type: schema.TypeString,
Computed: true,
Optional: true,
ExactlyOneOf: []string{"dns_name", "reverse_dns_name", "service_id"},
},
"partition": {
Type: schema.TypeString,
Computed: true,
},
"region": {
Type: schema.TypeString,
Computed: true,
Optional: true,
ConflictsWith: []string{"dns_name", "reverse_dns_name"},
},
"reverse_dns_name": {
Type: schema.TypeString,
Computed: true,
Optional: true,
ExactlyOneOf: []string{"dns_name", "reverse_dns_name", "service_id"},
},
"reverse_dns_prefix": {
Type: schema.TypeString,
Computed: true,
Optional: true,
ConflictsWith: []string{"dns_name", "reverse_dns_name"},
},
"service_id": {
Type: schema.TypeString,
Computed: true,
Optional: true,
ExactlyOneOf: []string{"dns_name", "reverse_dns_name", "service_id"},
},
"supported": {
Type: schema.TypeBool,
Computed: true,
},
},
}
}

func dataSourceServiceRead(d *schema.ResourceData, meta interface{}) error {
client := meta.(*conns.AWSClient)

if v, ok := d.GetOk("reverse_dns_name"); ok {
serviceParts := strings.Split(v.(string), ".")
if len(serviceParts) < 4 {
return fmt.Errorf("reverse service DNS names must have at least 4 parts (%s has %d)", v.(string), len(serviceParts))
}

d.Set("service_id", serviceParts[len(serviceParts)-1])
d.Set("region", serviceParts[len(serviceParts)-2])
d.Set("reverse_dns_prefix", strings.Join(serviceParts[0:len(serviceParts)-2], "."))
}

if v, ok := d.GetOk("dns_name"); ok {
serviceParts := InvertStringSlice(strings.Split(v.(string), "."))
if len(serviceParts) < 4 {
return fmt.Errorf("service DNS names must have at least 4 parts (%s has %d)", v.(string), len(serviceParts))
}

d.Set("service_id", serviceParts[len(serviceParts)-1])
d.Set("region", serviceParts[len(serviceParts)-2])
d.Set("reverse_dns_prefix", strings.Join(serviceParts[0:len(serviceParts)-2], "."))
}

if _, ok := d.GetOk("region"); !ok {
d.Set("region", client.Region)
}

if _, ok := d.GetOk("service_id"); !ok {
return fmt.Errorf("service ID not provided directly or through a DNS name")
}

if _, ok := d.GetOk("reverse_dns_prefix"); !ok {
dnsParts := strings.Split(meta.(*conns.AWSClient).DNSSuffix, ".")
d.Set("reverse_dns_prefix", strings.Join(InvertStringSlice(dnsParts), "."))
}

reverseDNS := fmt.Sprintf("%s.%s.%s", d.Get("reverse_dns_prefix").(string), d.Get("region").(string), d.Get("service_id").(string))
d.Set("reverse_dns_name", reverseDNS)
d.Set("dns_name", strings.ToLower(strings.Join(InvertStringSlice(strings.Split(reverseDNS, ".")), ".")))

d.Set("supported", true)
if partition, ok := endpoints.PartitionForRegion(endpoints.DefaultPartitions(), d.Get("region").(string)); ok {
d.Set("partition", partition.ID())
if _, ok := partition.Services()[d.Get("service_id").(string)]; !ok {
d.Set("supported", false)
}
}

d.SetId(reverseDNS)

return nil
}

// invertStringSlice returns inverted string slice without sorting slice like sort.Reverse()
func InvertStringSlice(slice []string) []string {
inverse := make([]string, 0)
for i := 0; i < len(slice); i++ {
inverse = append(inverse, slice[len(slice)-i-1])
}
return inverse
}
207 changes: 207 additions & 0 deletions internal/service/meta/service_data_source_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
package meta_test

import (
"fmt"
"reflect"
"testing"

"github.com/aws/aws-sdk-go/aws/endpoints"
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/aws/aws-sdk-go/service/rds"
"github.com/aws/aws-sdk-go/service/s3"
"github.com/aws/aws-sdk-go/service/waf"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
"github.com/hashicorp/terraform-provider-aws/internal/acctest"
tfmeta "github.com/hashicorp/terraform-provider-aws/internal/service/meta"
)

func TestInvertStringSlice(t *testing.T) {
testCases := []struct {
Name string
Input []string
Expected []string
}{
{
Name: "DNS Suffix",
Input: []string{"amazonaws", "com", "cn"},
Expected: []string{"cn", "com", "amazonaws"},
},
{
Name: "Ordered List",
Input: []string{"abc", "bcd", "cde", "xyz", "zzz"},
Expected: []string{"zzz", "xyz", "cde", "bcd", "abc"},
},
{
Name: "Unordered List",
Input: []string{"abc", "zzz", "bcd", "xyz", "cde"},
Expected: []string{"cde", "xyz", "bcd", "zzz", "abc"},
},
}

for _, testCase := range testCases {
t.Run(testCase.Name, func(t *testing.T) {
if !reflect.DeepEqual(tfmeta.InvertStringSlice(testCase.Input), testCase.Expected) {
t.Errorf("got %v, expected %v", tfmeta.InvertStringSlice(testCase.Input), testCase.Expected)
}
})
}
}

func TestAccMetaService_basic(t *testing.T) {
dataSourceName := "data.aws_service.default"

resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { acctest.PreCheck(t) },
ErrorCheck: acctest.ErrorCheck(t, tfmeta.PseudoServiceID),
Providers: acctest.Providers,
Steps: []resource.TestStep{
{
Config: testAccCheckServiceConfig_basic(),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr(dataSourceName, "dns_name", fmt.Sprintf("%s.%s.%s", ec2.EndpointsID, acctest.Region(), "amazonaws.com")),
resource.TestCheckResourceAttr(dataSourceName, "partition", acctest.Partition()),
resource.TestCheckResourceAttr(dataSourceName, "reverse_dns_prefix", "com.amazonaws"),
resource.TestCheckResourceAttr(dataSourceName, "region", acctest.Region()),
resource.TestCheckResourceAttr(dataSourceName, "reverse_dns_name", fmt.Sprintf("%s.%s.%s", "com.amazonaws", acctest.Region(), ec2.EndpointsID)),
resource.TestCheckResourceAttr(dataSourceName, "service_id", ec2.EndpointsID),
resource.TestCheckResourceAttr(dataSourceName, "supported", "true"),
),
},
},
})
}

func TestAccMetaService_byReverseDNSName(t *testing.T) {
dataSourceName := "data.aws_service.test"

resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { acctest.PreCheck(t) },
ErrorCheck: acctest.ErrorCheck(t, tfmeta.PseudoServiceID),
Providers: acctest.Providers,
Steps: []resource.TestStep{
{
Config: testAccCheckServiceConfig_byReverseDNSName(),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr(dataSourceName, "region", endpoints.CnNorth1RegionID),
resource.TestCheckResourceAttr(dataSourceName, "reverse_dns_name", fmt.Sprintf("%s.%s.%s", "cn.com.amazonaws", endpoints.CnNorth1RegionID, s3.EndpointsID)),
resource.TestCheckResourceAttr(dataSourceName, "reverse_dns_prefix", "cn.com.amazonaws"),
resource.TestCheckResourceAttr(dataSourceName, "service_id", s3.EndpointsID),
resource.TestCheckResourceAttr(dataSourceName, "supported", "true"),
),
},
},
})
}

func TestAccMetaService_byDNSName(t *testing.T) {
dataSourceName := "data.aws_service.test"

resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { acctest.PreCheck(t) },
ErrorCheck: acctest.ErrorCheck(t, tfmeta.PseudoServiceID),
Providers: acctest.Providers,
Steps: []resource.TestStep{
{
Config: testAccCheckServiceConfig_byDNSName(),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr(dataSourceName, "region", endpoints.UsEast1RegionID),
resource.TestCheckResourceAttr(dataSourceName, "reverse_dns_name", fmt.Sprintf("%s.%s.%s", "com.amazonaws", endpoints.UsEast1RegionID, rds.EndpointsID)),
resource.TestCheckResourceAttr(dataSourceName, "reverse_dns_prefix", "com.amazonaws"),
resource.TestCheckResourceAttr(dataSourceName, "service_id", rds.EndpointsID),
resource.TestCheckResourceAttr(dataSourceName, "supported", "true"),
),
},
},
})
}

func TestAccMetaService_byParts(t *testing.T) {
dataSourceName := "data.aws_service.test"

resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { acctest.PreCheck(t) },
ErrorCheck: acctest.ErrorCheck(t, tfmeta.PseudoServiceID),
Providers: acctest.Providers,
Steps: []resource.TestStep{
{
Config: testAccCheckServiceConfig_byPart(),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr(dataSourceName, "dns_name", fmt.Sprintf("%s.%s.%s", s3.EndpointsID, acctest.Region(), "amazonaws.com")),
resource.TestCheckResourceAttr(dataSourceName, "reverse_dns_name", fmt.Sprintf("%s.%s.%s", "com.amazonaws", acctest.Region(), s3.EndpointsID)),
resource.TestCheckResourceAttr(dataSourceName, "supported", "true"),
),
},
},
})
}

func TestAccMetaService_unsupported(t *testing.T) {
dataSourceName := "data.aws_service.test"

resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { acctest.PreCheck(t) },
ErrorCheck: acctest.ErrorCheck(t, tfmeta.PseudoServiceID),
Providers: acctest.Providers,
Steps: []resource.TestStep{
{
Config: testAccCheckServiceConfig_unsupported(),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr(dataSourceName, "dns_name", fmt.Sprintf("%s.%s.%s", waf.EndpointsID, endpoints.UsGovWest1RegionID, "amazonaws.com")),
resource.TestCheckResourceAttr(dataSourceName, "partition", endpoints.AwsUsGovPartitionID),
resource.TestCheckResourceAttr(dataSourceName, "reverse_dns_prefix", "com.amazonaws"),
resource.TestCheckResourceAttr(dataSourceName, "region", endpoints.UsGovWest1RegionID),
resource.TestCheckResourceAttr(dataSourceName, "reverse_dns_name", fmt.Sprintf("%s.%s.%s", "com.amazonaws", endpoints.UsGovWest1RegionID, waf.EndpointsID)),
resource.TestCheckResourceAttr(dataSourceName, "service_id", waf.EndpointsID),
resource.TestCheckResourceAttr(dataSourceName, "supported", "false"),
),
},
},
})
}

func testAccCheckServiceConfig_basic() string {
return fmt.Sprintf(`
data "aws_service" "default" {
service_id = %[1]q
}
`, ec2.EndpointsID)
}

func testAccCheckServiceConfig_byReverseDNSName() string {
// lintignore:AWSAT003
return `
data "aws_service" "test" {
reverse_dns_name = "cn.com.amazonaws.cn-north-1.s3"
}
`
}

func testAccCheckServiceConfig_byDNSName() string {
// lintignore:AWSAT003
return `
data "aws_service" "test" {
dns_name = "rds.us-east-1.amazonaws.com"
}
`
}

func testAccCheckServiceConfig_byPart() string {
return `
data "aws_region" "current" {}

data "aws_service" "test" {
reverse_dns_prefix = "com.amazonaws"
region = data.aws_region.current.name
service_id = "s3"
}
`
}

func testAccCheckServiceConfig_unsupported() string {
// lintignore:AWSAT003
return `
data "aws_service" "test" {
reverse_dns_name = "com.amazonaws.us-gov-west-1.waf"
}
`
}
Loading