diff --git a/aws/provider.go b/aws/provider.go index 23432689ced..3a090430178 100644 --- a/aws/provider.go +++ b/aws/provider.go @@ -527,6 +527,7 @@ func Provider() terraform.ResourceProvider { "aws_lambda_permission": resourceAwsLambdaPermission(), "aws_launch_configuration": resourceAwsLaunchConfiguration(), "aws_launch_template": resourceAwsLaunchTemplate(), + "aws_licensemanager_association": resourceAwsLicenseManagerAssociation(), "aws_licensemanager_license_configuration": resourceAwsLicenseManagerLicenseConfiguration(), "aws_lightsail_domain": resourceAwsLightsailDomain(), "aws_lightsail_instance": resourceAwsLightsailInstance(), diff --git a/aws/resource_aws_launch_template.go b/aws/resource_aws_launch_template.go index 4a118269fdb..76052a4795f 100644 --- a/aws/resource_aws_launch_template.go +++ b/aws/resource_aws_launch_template.go @@ -308,6 +308,20 @@ func resourceAwsLaunchTemplate() *schema.Resource { Optional: true, }, + "license_specification": { + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "license_configuration_arn": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validateArn, + }, + }, + }, + }, + "monitoring": { Type: schema.TypeList, Optional: true, @@ -627,6 +641,10 @@ func resourceAwsLaunchTemplateRead(d *schema.ResourceData, meta interface{}) err return err } + if err := d.Set("license_specification", getLicenseSpecifications(ltData.LicenseSpecifications)); err != nil { + return err + } + if err := d.Set("monitoring", getMonitoring(ltData.Monitoring)); err != nil { return err } @@ -830,6 +848,16 @@ func getInstanceMarketOptions(m *ec2.LaunchTemplateInstanceMarketOptions) []inte return s } +func getLicenseSpecifications(licenseSpecifications []*ec2.LaunchTemplateLicenseConfiguration) []map[string]interface{} { + var s []map[string]interface{} + for _, v := range licenseSpecifications { + s = append(s, map[string]interface{}{ + "license_configuration_arn": aws.StringValue(v.LicenseConfigurationArn), + }) + } + return s +} + func getMonitoring(m *ec2.LaunchTemplatesMonitoring) []interface{} { s := []interface{}{} if m != nil { @@ -1035,6 +1063,16 @@ func buildLaunchTemplateData(d *schema.ResourceData) (*ec2.RequestLaunchTemplate } } + if v, ok := d.GetOk("license_specification"); ok { + var licenseSpecifications []*ec2.LaunchTemplateLicenseConfigurationRequest + lsList := v.(*schema.Set).List() + + for _, ls := range lsList { + licenseSpecifications = append(licenseSpecifications, readLicenseSpecificationFromConfig(ls.(map[string]interface{}))) + } + opts.LicenseSpecifications = licenseSpecifications + } + if v, ok := d.GetOk("monitoring"); ok { m := v.([]interface{}) if len(m) > 0 { @@ -1329,6 +1367,16 @@ func readInstanceMarketOptionsFromConfig(imo map[string]interface{}) (*ec2.Launc return instanceMarketOptions, nil } +func readLicenseSpecificationFromConfig(ls map[string]interface{}) *ec2.LaunchTemplateLicenseConfigurationRequest { + licenseSpecification := &ec2.LaunchTemplateLicenseConfigurationRequest{} + + if v, ok := ls["license_configuration_arn"].(string); ok && v != "" { + licenseSpecification.LicenseConfigurationArn = aws.String(v) + } + + return licenseSpecification +} + func readPlacementFromConfig(p map[string]interface{}) *ec2.LaunchTemplatePlacementRequest { placement := &ec2.LaunchTemplatePlacementRequest{} diff --git a/aws/resource_aws_launch_template_test.go b/aws/resource_aws_launch_template_test.go index ec4f27afdb3..e2877f1782b 100644 --- a/aws/resource_aws_launch_template_test.go +++ b/aws/resource_aws_launch_template_test.go @@ -533,6 +533,27 @@ func TestAccAWSLaunchTemplate_instanceMarketOptions(t *testing.T) { }) } +func TestAccAWSLaunchTemplate_licenseSpecification(t *testing.T) { + var template ec2.LaunchTemplate + resName := "aws_launch_template.example" + rInt := acctest.RandInt() + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSLaunchTemplateDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSLaunchTemplateConfig_licenseSpecification(rInt), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSLaunchTemplateExists(resName, &template), + resource.TestCheckResourceAttr(resName, "license_specification.#", "1"), + ), + }, + }, + }) +} + func testAccCheckAWSLaunchTemplateExists(n string, t *ec2.LaunchTemplate) resource.TestCheckFunc { return func(s *terraform.State) error { rs, ok := s.RootModule().Resources[n] @@ -859,6 +880,23 @@ resource "aws_launch_template" "foo" { `, instanceType, rName, cpuCredits) } +func testAccAWSLaunchTemplateConfig_licenseSpecification(rInt int) string { + return fmt.Sprintf(` +resource "aws_licensemanager_license_configuration" "example" { + name = "Example" + license_counting_type = "vCPU" +} + +resource "aws_launch_template" "example" { + name = "foo_%d" + + license_specification { + license_configuration_arn = "${aws_licensemanager_license_configuration.example.id}" + } +} +`, rInt) +} + const testAccAWSLaunchTemplateConfig_networkInterface = ` resource "aws_vpc" "test" { cidr_block = "10.1.0.0/16" diff --git a/aws/resource_aws_licensemanager_association.go b/aws/resource_aws_licensemanager_association.go new file mode 100644 index 00000000000..646d4031bc5 --- /dev/null +++ b/aws/resource_aws_licensemanager_association.go @@ -0,0 +1,146 @@ +package aws + +import ( + "fmt" + "log" + "strings" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/licensemanager" + "github.com/hashicorp/terraform/helper/schema" +) + +func resourceAwsLicenseManagerAssociation() *schema.Resource { + return &schema.Resource{ + Create: resourceAwsLicenseManagerAssociationCreate, + Read: resourceAwsLicenseManagerAssociationRead, + Delete: resourceAwsLicenseManagerAssociationDelete, + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + + Schema: map[string]*schema.Schema{ + "resource_arn": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validateArn, + }, + "license_configuration_arn": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validateArn, + }, + }, + } +} + +func resourceAwsLicenseManagerAssociationCreate(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).licensemanagerconn + + resourceArn := d.Get("resource_arn").(string) + licenseConfigurationArn := d.Get("license_configuration_arn").(string) + + opts := &licensemanager.UpdateLicenseSpecificationsForResourceInput{ + AddLicenseSpecifications: []*licensemanager.LicenseSpecification{{ + LicenseConfigurationArn: aws.String(licenseConfigurationArn), + }}, + ResourceArn: aws.String(resourceArn), + } + + log.Printf("[DEBUG] License Manager association: %s", opts) + + _, err := conn.UpdateLicenseSpecificationsForResource(opts) + if err != nil { + return fmt.Errorf("Error creating License Manager association: %s", err) + } + + d.SetId(fmt.Sprintf("%s,%s", resourceArn, licenseConfigurationArn)) + + return resourceAwsLicenseManagerAssociationRead(d, meta) +} + +func resourceAwsLicenseManagerAssociationRead(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).licensemanagerconn + + resourceArn, licenseConfigurationArn, err := resourceAwsLicenseManagerAssociationParseId(d.Id()) + if err != nil { + return err + } + + licenseSpecification, err := resourceAwsLicenseManagerAssociationFindSpecification(conn, resourceArn, licenseConfigurationArn) + if err != nil { + return fmt.Errorf("Error reading License Manager association: %s", err) + } + + if licenseSpecification == nil { + log.Printf("[WARN] License Manager association (%s) not found, removing from state", d.Id()) + d.SetId("") + return nil + } + + d.Set("resource_arn", resourceArn) + d.Set("license_configuration_arn", licenseConfigurationArn) + + return nil +} + +func resourceAwsLicenseManagerAssociationFindSpecification(conn *licensemanager.LicenseManager, resourceArn, licenseConfigurationArn string) (*licensemanager.LicenseSpecification, error) { + opts := &licensemanager.ListLicenseSpecificationsForResourceInput{ + ResourceArn: aws.String(resourceArn), + } + + for { + resp, err := conn.ListLicenseSpecificationsForResource(opts) + + if err != nil { + return nil, err + } + + for _, licenseSpecification := range resp.LicenseSpecifications { + if aws.StringValue(licenseSpecification.LicenseConfigurationArn) == licenseConfigurationArn { + return licenseSpecification, nil + } + } + + if len(resp.LicenseSpecifications) == 0 || resp.NextToken == nil { + return nil, nil + } + + opts.NextToken = resp.NextToken + } +} + +func resourceAwsLicenseManagerAssociationParseId(id string) (string, string, error) { + parts := strings.SplitN(id, ",", 2) + if len(parts) != 2 { + return "", "", fmt.Errorf("Expected License Manager Association ID in the form resource_arn,license_configuration_arn - received: %s", id) + } + return parts[0], parts[1], nil +} + +func resourceAwsLicenseManagerAssociationDelete(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).licensemanagerconn + + resourceArn, licenseConfigurationArn, err := resourceAwsLicenseManagerAssociationParseId(d.Id()) + if err != nil { + return err + } + + opts := &licensemanager.UpdateLicenseSpecificationsForResourceInput{ + RemoveLicenseSpecifications: []*licensemanager.LicenseSpecification{{ + LicenseConfigurationArn: aws.String(licenseConfigurationArn), + }}, + ResourceArn: aws.String(resourceArn), + } + + log.Printf("[DEBUG] License Manager association: %s", opts) + + _, err = conn.UpdateLicenseSpecificationsForResource(opts) + if err != nil { + return fmt.Errorf("Error deleting License Manager association: %s", err) + } + + return nil +} diff --git a/aws/resource_aws_licensemanager_association_test.go b/aws/resource_aws_licensemanager_association_test.go new file mode 100644 index 00000000000..040ee2a46a2 --- /dev/null +++ b/aws/resource_aws_licensemanager_association_test.go @@ -0,0 +1,125 @@ +package aws + +import ( + "fmt" + "testing" + + "github.com/aws/aws-sdk-go/service/licensemanager" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" +) + +func TestAccAWSLicenseManagerAssociation_basic(t *testing.T) { + var licenseSpecification licensemanager.LicenseSpecification + resourceName := "aws_licensemanager_association.example" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckLicenseManagerAssociationDestroy, + Steps: []resource.TestStep{ + { + Config: testAccLicenseManagerAssociationConfig_basic, + Check: resource.ComposeTestCheckFunc( + testAccCheckLicenseManagerAssociationExists(resourceName, &licenseSpecification), + resource.TestCheckResourceAttrPair(resourceName, "license_configuration_arn", "aws_licensemanager_license_configuration.example", "id"), + resource.TestCheckResourceAttrPair(resourceName, "resource_arn", "aws_instance.example", "arn"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testAccCheckLicenseManagerAssociationExists(resourceName string, licenseSpecification *licensemanager.LicenseSpecification) resource.TestCheckFunc { + return func(s *terraform.State) error { + conn := testAccProvider.Meta().(*AWSClient).licensemanagerconn + + rs, ok := s.RootModule().Resources[resourceName] + if !ok { + return fmt.Errorf("Not found: %s", resourceName) + } + + if rs.Primary.ID == "" { + return fmt.Errorf("No ID is set") + } + + resourceArn, licenseConfigurationArn, err := resourceAwsLicenseManagerAssociationParseId(rs.Primary.ID) + if err != nil { + return err + } + + specification, err := resourceAwsLicenseManagerAssociationFindSpecification(conn, resourceArn, licenseConfigurationArn) + if err != nil { + return err + } + + if specification == nil { + return fmt.Errorf("Error retrieving License Manager association (%s): Not found", rs.Primary.ID) + } + + *licenseSpecification = *specification + return nil + } +} + +func testAccCheckLicenseManagerAssociationDestroy(s *terraform.State) error { + conn := testAccProvider.Meta().(*AWSClient).licensemanagerconn + + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_licensemanager_association" { + continue + } + + resourceArn, licenseConfigurationArn, err := resourceAwsLicenseManagerAssociationParseId(rs.Primary.ID) + if err != nil { + return err + } + + specification, err := resourceAwsLicenseManagerAssociationFindSpecification(conn, resourceArn, licenseConfigurationArn) + if err != nil { + return err + } + + if specification != nil { + return fmt.Errorf("License Manager association %q still exists", rs.Primary.ID) + } + } + + return nil +} + +const testAccLicenseManagerAssociationConfig_basic = ` +data "aws_ami" "example" { + most_recent = true + + filter { + name = "owner-alias" + values = ["amazon"] + } + + filter { + name = "name" + values = ["amzn-ami-vpc-nat*"] + } +} + +resource "aws_instance" "example" { + ami = "${data.aws_ami.example.id}" + instance_type = "t2.micro" +} + +resource "aws_licensemanager_license_configuration" "example" { + name = "Example" + license_counting_type = "vCPU" +} + +resource "aws_licensemanager_association" "example" { + license_configuration_arn = "${aws_licensemanager_license_configuration.example.id}" + resource_arn = "${aws_instance.example.arn}" +} +` diff --git a/website/aws.erb b/website/aws.erb index 91c3d5e64e5..7de05b0ee57 100644 --- a/website/aws.erb +++ b/website/aws.erb @@ -1668,6 +1668,10 @@ License Manager Resources