diff --git a/aws/provider.go b/aws/provider.go index 52bb9fefeef..16cab81d82a 100644 --- a/aws/provider.go +++ b/aws/provider.go @@ -685,6 +685,7 @@ func Provider() terraform.ResourceProvider { "aws_service_discovery_private_dns_namespace": resourceAwsServiceDiscoveryPrivateDnsNamespace(), "aws_service_discovery_public_dns_namespace": resourceAwsServiceDiscoveryPublicDnsNamespace(), "aws_service_discovery_service": resourceAwsServiceDiscoveryService(), + "aws_servicequotas_service_quota": resourceAwsServiceQuotasServiceQuota(), "aws_shield_protection": resourceAwsShieldProtection(), "aws_simpledb_domain": resourceAwsSimpleDBDomain(), "aws_ssm_activation": resourceAwsSsmActivation(), diff --git a/aws/resource_aws_servicequotas_service_quota.go b/aws/resource_aws_servicequotas_service_quota.go new file mode 100644 index 00000000000..50067285198 --- /dev/null +++ b/aws/resource_aws_servicequotas_service_quota.go @@ -0,0 +1,240 @@ +package aws + +import ( + "fmt" + "strings" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/servicequotas" + "github.com/hashicorp/terraform/helper/schema" +) + +func resourceAwsServiceQuotasServiceQuota() *schema.Resource { + return &schema.Resource{ + Create: resourceAwsServiceQuotasServiceQuotaCreate, + Read: resourceAwsServiceQuotasServiceQuotaRead, + Update: resourceAwsServiceQuotasServiceQuotaUpdate, + Delete: schema.Noop, + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + + Schema: map[string]*schema.Schema{ + "adjustable": { + Type: schema.TypeBool, + Computed: true, + }, + "arn": { + Type: schema.TypeString, + Computed: true, + }, + "default_value": { + Type: schema.TypeFloat, + Computed: true, + }, + "quota_code": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "quota_name": { + Type: schema.TypeString, + Computed: true, + }, + "request_id": { + Type: schema.TypeString, + Computed: true, + }, + "request_status": { + Type: schema.TypeString, + Computed: true, + }, + "service_code": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "service_name": { + Type: schema.TypeString, + Computed: true, + }, + "value": { + Type: schema.TypeFloat, + Required: true, + }, + }, + } +} + +func resourceAwsServiceQuotasServiceQuotaCreate(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).servicequotasconn + + quotaCode := d.Get("quota_code").(string) + serviceCode := d.Get("service_code").(string) + value := d.Get("value").(float64) + + d.SetId(fmt.Sprintf("%s/%s", serviceCode, quotaCode)) + + input := &servicequotas.GetServiceQuotaInput{ + QuotaCode: aws.String(quotaCode), + ServiceCode: aws.String(serviceCode), + } + + output, err := conn.GetServiceQuota(input) + + if err != nil { + return fmt.Errorf("error getting Service Quotas Service Quota (%s): %s", d.Id(), err) + } + + if output == nil { + return fmt.Errorf("error getting Service Quotas Service Quota (%s): empty result", d.Id()) + } + + if value > aws.Float64Value(output.Quota.Value) { + input := &servicequotas.RequestServiceQuotaIncreaseInput{ + DesiredValue: aws.Float64(value), + QuotaCode: aws.String(quotaCode), + ServiceCode: aws.String(serviceCode), + } + + output, err := conn.RequestServiceQuotaIncrease(input) + + if err != nil { + return fmt.Errorf("error requesting Service Quota (%s) increase: %s", d.Id(), err) + } + + if output == nil || output.RequestedQuota == nil { + return fmt.Errorf("error requesting Service Quota (%s) increase: empty result", d.Id()) + } + + d.Set("request_id", output.RequestedQuota.Id) + } + + return resourceAwsServiceQuotasServiceQuotaRead(d, meta) +} + +func resourceAwsServiceQuotasServiceQuotaRead(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).servicequotasconn + + serviceCode, quotaCode, err := resourceAwsServiceQuotasServiceQuotaParseID(d.Id()) + + if err != nil { + return err + } + + input := &servicequotas.GetServiceQuotaInput{ + QuotaCode: aws.String(quotaCode), + ServiceCode: aws.String(serviceCode), + } + + output, err := conn.GetServiceQuota(input) + + if err != nil { + return fmt.Errorf("error getting Service Quotas Service Quota (%s): %s", d.Id(), err) + } + + if output == nil { + return fmt.Errorf("error getting Service Quotas Service Quota (%s): empty result", d.Id()) + } + + defaultInput := &servicequotas.GetAWSDefaultServiceQuotaInput{ + QuotaCode: aws.String(quotaCode), + ServiceCode: aws.String(serviceCode), + } + + defaultOutput, err := conn.GetAWSDefaultServiceQuota(defaultInput) + + if err != nil { + return fmt.Errorf("error getting Service Quotas Default Service Quota (%s): %s", d.Id(), err) + } + + if defaultOutput == nil { + return fmt.Errorf("error getting Service Quotas Default Service Quota (%s): empty result", d.Id()) + } + + d.Set("adjustable", output.Quota.Adjustable) + d.Set("arn", output.Quota.QuotaArn) + d.Set("default_value", defaultOutput.Quota.Value) + d.Set("quota_code", output.Quota.QuotaCode) + d.Set("quota_name", output.Quota.QuotaName) + d.Set("service_code", output.Quota.ServiceCode) + d.Set("service_name", output.Quota.ServiceName) + d.Set("value", output.Quota.Value) + + requestID := d.Get("request_id").(string) + + if requestID != "" { + input := &servicequotas.GetRequestedServiceQuotaChangeInput{ + RequestId: aws.String(requestID), + } + + output, err := conn.GetRequestedServiceQuotaChange(input) + + if isAWSErr(err, servicequotas.ErrCodeNoSuchResourceException, "") { + d.Set("request_id", "") + d.Set("request_status", "") + return nil + } + + if err != nil { + return fmt.Errorf("error getting Service Quotas Requested Service Quota Change (%s): %s", requestID, err) + } + + if output == nil || output.RequestedQuota == nil { + return fmt.Errorf("error getting Service Quotas Requested Service Quota Change (%s): empty result", requestID) + } + + requestStatus := aws.StringValue(output.RequestedQuota.Status) + d.Set("request_status", requestStatus) + + switch requestStatus { + case servicequotas.RequestStatusApproved, servicequotas.RequestStatusCaseClosed, servicequotas.RequestStatusDenied: + d.Set("request_id", "") + case servicequotas.RequestStatusCaseOpened, servicequotas.RequestStatusPending: + d.Set("value", output.RequestedQuota.DesiredValue) + } + } + + return nil +} + +func resourceAwsServiceQuotasServiceQuotaUpdate(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).servicequotasconn + + value := d.Get("value").(float64) + serviceCode, quotaCode, err := resourceAwsServiceQuotasServiceQuotaParseID(d.Id()) + + if err != nil { + return err + } + + input := &servicequotas.RequestServiceQuotaIncreaseInput{ + DesiredValue: aws.Float64(value), + QuotaCode: aws.String(quotaCode), + ServiceCode: aws.String(serviceCode), + } + + output, err := conn.RequestServiceQuotaIncrease(input) + + if err != nil { + return fmt.Errorf("error requesting Service Quota (%s) increase: %s", d.Id(), err) + } + + if output == nil || output.RequestedQuota == nil { + return fmt.Errorf("error requesting Service Quota (%s) increase: empty result", d.Id()) + } + + d.Set("request_id", output.RequestedQuota.Id) + + return resourceAwsServiceQuotasServiceQuotaRead(d, meta) +} + +func resourceAwsServiceQuotasServiceQuotaParseID(id string) (string, string, error) { + parts := strings.SplitN(id, "/", 2) + + if len(parts) != 2 || parts[0] == "" || parts[1] == "" { + return "", "", fmt.Errorf("unexpected format of ID (%s), expected SERVICE-CODE/QUOTA-CODE", id) + } + + return parts[0], parts[1], nil +} diff --git a/aws/resource_aws_servicequotas_service_quota_test.go b/aws/resource_aws_servicequotas_service_quota_test.go new file mode 100644 index 00000000000..21e943ef6c0 --- /dev/null +++ b/aws/resource_aws_servicequotas_service_quota_test.go @@ -0,0 +1,177 @@ +package aws + +import ( + "fmt" + "os" + "testing" + + "github.com/aws/aws-sdk-go/service/servicequotas" + "github.com/hashicorp/terraform/helper/resource" +) + +// This resource is different than many since quotas are pre-existing +// and the resource is only designed to help with increases. +// In the basic case, we test that the resource can match the existing quota +// without unexpected changes. +func TestAccAwsServiceQuotasServiceQuota_basic(t *testing.T) { + dataSourceName := "data.aws_servicequotas_service_quota.test" + resourceName := "aws_servicequotas_service_quota.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t); testAccPreCheckAWSServiceQuotas(t) }, + Providers: testAccProviders, + CheckDestroy: nil, + Steps: []resource.TestStep{ + { + Config: testAccAwsServiceQuotasServiceQuotaConfigSameValue("L-F678F1CE", "vpc"), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrPair(resourceName, "adjustable", dataSourceName, "adjustable"), + resource.TestCheckResourceAttrPair(resourceName, "arn", dataSourceName, "arn"), + resource.TestCheckResourceAttrPair(resourceName, "default_value", dataSourceName, "default_value"), + resource.TestCheckResourceAttrPair(resourceName, "quota_code", dataSourceName, "quota_code"), + resource.TestCheckResourceAttrPair(resourceName, "quota_name", dataSourceName, "quota_name"), + resource.TestCheckResourceAttrPair(resourceName, "service_code", dataSourceName, "service_code"), + resource.TestCheckResourceAttrPair(resourceName, "service_name", dataSourceName, "service_name"), + resource.TestCheckResourceAttrPair(resourceName, "value", dataSourceName, "value"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccAwsServiceQuotasServiceQuota_Value_IncreaseOnCreate(t *testing.T) { + quotaCode := os.Getenv("SERVICEQUOTAS_INCREASE_ON_CREATE_QUOTA_CODE") + if quotaCode == "" { + t.Skip( + "Environment variable SERVICEQUOTAS_INCREASE_ON_CREATE_QUOTA_CODE is not set. " + + "WARNING: This test will submit a real service quota increase!") + } + + serviceCode := os.Getenv("SERVICEQUOTAS_INCREASE_ON_CREATE_SERVICE_CODE") + if serviceCode == "" { + t.Skip( + "Environment variable SERVICEQUOTAS_INCREASE_ON_CREATE_SERVICE_CODE is not set. " + + "WARNING: This test will submit a real service quota increase!") + } + + value := os.Getenv("SERVICEQUOTAS_INCREASE_ON_CREATE_VALUE") + if value == "" { + t.Skip( + "Environment variable SERVICEQUOTAS_INCREASE_ON_CREATE_VALUE is not set. " + + "WARNING: This test will submit a real service quota increase!") + } + + resourceName := "aws_servicequotas_service_quota.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t); testAccPreCheckAWSServiceQuotas(t) }, + Providers: testAccProviders, + CheckDestroy: nil, + Steps: []resource.TestStep{ + { + Config: testAccAwsServiceQuotasServiceQuotaConfigValue(quotaCode, serviceCode, value), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "quota_code", quotaCode), + resource.TestCheckResourceAttr(resourceName, "service_code", serviceCode), + resource.TestCheckResourceAttr(resourceName, "value", value), + ), + }, + }, + }) +} + +func TestAccAwsServiceQuotasServiceQuota_Value_IncreaseOnUpdate(t *testing.T) { + quotaCode := os.Getenv("SERVICEQUOTAS_INCREASE_ON_UPDATE_QUOTA_CODE") + if quotaCode == "" { + t.Skip( + "Environment variable SERVICEQUOTAS_INCREASE_ON_UPDATE_QUOTA_CODE is not set. " + + "WARNING: This test will submit a real service quota increase!") + } + + serviceCode := os.Getenv("SERVICEQUOTAS_INCREASE_ON_UPDATE_SERVICE_CODE") + if serviceCode == "" { + t.Skip( + "Environment variable SERVICEQUOTAS_INCREASE_ON_UPDATE_SERVICE_CODE is not set. " + + "WARNING: This test will submit a real service quota increase!") + } + + value := os.Getenv("SERVICEQUOTAS_INCREASE_ON_UPDATE_VALUE") + if value == "" { + t.Skip( + "Environment variable SERVICEQUOTAS_INCREASE_ON_UPDATE_VALUE is not set. " + + "WARNING: This test will submit a real service quota increase!") + } + + dataSourceName := "aws_servicequotas_service_quota.test" + resourceName := "aws_servicequotas_service_quota.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t); testAccPreCheckAWSServiceQuotas(t) }, + Providers: testAccProviders, + CheckDestroy: nil, + Steps: []resource.TestStep{ + { + Config: testAccAwsServiceQuotasServiceQuotaConfigSameValue(quotaCode, serviceCode), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "quota_code", quotaCode), + resource.TestCheckResourceAttr(resourceName, "service_code", serviceCode), + resource.TestCheckResourceAttrPair(resourceName, "value", dataSourceName, "value"), + ), + }, + { + Config: testAccAwsServiceQuotasServiceQuotaConfigValue(quotaCode, serviceCode, value), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "quota_code", quotaCode), + resource.TestCheckResourceAttr(resourceName, "service_code", serviceCode), + resource.TestCheckResourceAttr(resourceName, "value", value), + ), + }, + }, + }) +} + +func testAccPreCheckAWSServiceQuotas(t *testing.T) { + conn := testAccProvider.Meta().(*AWSClient).servicequotasconn + + input := &servicequotas.ListServicesInput{} + + _, err := conn.ListServices(input) + + if testAccPreCheckSkipError(err) { + t.Skipf("skipping acceptance testing: %s", err) + } + + if err != nil { + t.Fatalf("unexpected PreCheck error: %s", err) + } +} + +func testAccAwsServiceQuotasServiceQuotaConfigSameValue(quotaCode, serviceCode string) string { + return fmt.Sprintf(` +data "aws_servicequotas_service_quota" "test" { + quota_code = %[1]q + service_code = %[2]q +} + +resource "aws_servicequotas_service_quota" "test" { + quota_code = "${data.aws_servicequotas_service_quota.test.quota_code}" + service_code = "${data.aws_servicequotas_service_quota.test.service_code}" + value = "${data.aws_servicequotas_service_quota.test.value}" +} +`, quotaCode, serviceCode) +} + +func testAccAwsServiceQuotasServiceQuotaConfigValue(quotaCode, serviceCode, value string) string { + return fmt.Sprintf(` +resource "aws_servicequotas_service_quota" "test" { + quota_code = %[1]q + service_code = %[2]q + value = %[3]s +} +`, quotaCode, serviceCode, value) +} diff --git a/website/aws.erb b/website/aws.erb index ca0f95c2d63..cda0effc88c 100644 --- a/website/aws.erb +++ b/website/aws.erb @@ -2499,6 +2499,17 @@ +
  • + Service Quotas Resources + +
  • +
  • Shield Resources