From 1b290ebd408d1bf7b0787b6a2469f47a81f87509 Mon Sep 17 00:00:00 2001 From: Gareth Oakley Date: Tue, 18 Dec 2018 11:48:56 +0000 Subject: [PATCH 1/4] r/aws_licensemanager: Add support for associating licenses --- aws/provider.go | 1 + aws/resource_aws_launch_template.go | 48 ++++++ ...resource_aws_licensemanager_association.go | 146 ++++++++++++++++++ website/docs/r/launch_template.html.markdown | 13 ++ .../r/licensemanager_association.markdown | 48 ++++++ 5 files changed, 256 insertions(+) create mode 100644 aws/resource_aws_licensemanager_association.go create mode 100644 website/docs/r/licensemanager_association.markdown 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..1b3393fc953 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.TypeList, + 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(e []*ec2.LaunchTemplateLicenseConfiguration) []interface{} { + s := []interface{}{} + for _, v := range e { + 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.([]interface{}) + + 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_licensemanager_association.go b/aws/resource_aws_licensemanager_association.go new file mode 100644 index 00000000000..142d57ebcc8 --- /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 := parseLicenseManagerAssociationId(d.Id()) + if err != nil { + return err + } + + licenseSpecification, err := findLicenseSpecificationForResource(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_count", licenseConfigurationArn) + + return nil +} + +func findLicenseSpecificationForResource(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 parseLicenseManagerAssociationId(id string) (string, string, error) { + parts := strings.SplitN(id, ",", 2) + if len(parts) != 2 { + return "", "", fmt.Errorf("Could not parse License Manager association ID: %s", id) + } + return parts[0], parts[1], nil +} + +func resourceAwsLicenseManagerAssociationDelete(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).licensemanagerconn + + resourceArn, licenseConfigurationArn, err := parseLicenseManagerAssociationId(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/website/docs/r/launch_template.html.markdown b/website/docs/r/launch_template.html.markdown index 68927d255c5..bb4b81fcb08 100644 --- a/website/docs/r/launch_template.html.markdown +++ b/website/docs/r/launch_template.html.markdown @@ -58,6 +58,10 @@ resource "aws_launch_template" "foo" { key_name = "test" + license_specification { + license_configuration_arn = "arn:aws:license-manager:eu-west-1:123456789012:license-configuration:lic-0123456789abcdef0123456789abcdef" + } + monitoring { enabled = true } @@ -113,6 +117,7 @@ The following arguments are supported: * `instance_type` - The type of the instance. * `kernel_id` - The kernel ID. * `key_name` - The key name to use for the instance. +* `license_specification` - A list of license specifications to associate with. See [License Specifications](#license-specificiations) below for more details. * `monitoring` - The monitoring option for the instance. See [Monitoring](#monitoring) below for more details. * `network_interfaces` - Customize network interfaces to be attached at instance boot time. See [Network Interfaces](#network-interfaces) below for more details. @@ -193,6 +198,14 @@ The `iam_instance_profile` block supports the following: * `arn` - The Amazon Resource Name (ARN) of the instance profile. * `name` - The name of the instance profile. +### License Specifications + +Associate one of more license configurations. + +The `license_specification` block supports the following: + +* `license_configuration_arn` - (Required) ARN of the license configuration. + ### Market Options The market (purchasing) option for the instances. diff --git a/website/docs/r/licensemanager_association.markdown b/website/docs/r/licensemanager_association.markdown new file mode 100644 index 00000000000..f06c0db6882 --- /dev/null +++ b/website/docs/r/licensemanager_association.markdown @@ -0,0 +1,48 @@ +--- +layout: "aws" +page_title: "AWS: aws_licensemanager_association" +sidebar_current: "docs-aws-resource-licensemanager-license-configuration" +description: |- + Provides a License Manager association resource. +--- + +# aws_licensemanager_association + +Provides a License Manager association. + +~> **Note:** License configurations can also be associated with launch templates by specifying the `license_specifications` block for an `aws_launch_template`. + +## Example Usage + +```hcl +resource "aws_licensemanager_license_configuration" "example" { + name = "Example" + license_counting_type = "Instance" +} + +resource "aws_licensemanager_association" "example" { + license_configuration_arn = "${aws_licensemanager_license_configuration.example.arn}" + resource_arn = ... +} +``` + +## Argument Reference + +The following arguments are supported: + +* `license_configuration_arn` - (Required) ARN of the license configuration. +* `resource_arn` - (Required) ARN of the resource associated with the license configuration. + +## Attributes Reference + +In addition to all arguments above, the following attributes are exported: + +* `id` - The license configuration ARN. + +## Import + +License configurations can be imported in the form `resource_arn,license_configuration_arn`, e.g. + +``` +$ terraform import aws_licensemanager_association.example arn:aws:ec2:eu-west-1:123456789012:image/ami-123456789abcdef01,arn:aws:license-manager:eu-west-1:123456789012:license-configuration:lic-0123456789abcdef0123456789abcdef +``` From f675f538743fb31cf65d5a6236d36718aa97f89d Mon Sep 17 00:00:00 2001 From: Gareth Oakley Date: Wed, 19 Dec 2018 17:35:13 +0000 Subject: [PATCH 2/4] r/aws_licensemanager: Tests for associating licenses --- aws/resource_aws_launch_template_test.go | 39 +++++ ...resource_aws_licensemanager_association.go | 2 +- ...rce_aws_licensemanager_association_test.go | 136 ++++++++++++++++++ website/aws.erb | 4 + 4 files changed, 180 insertions(+), 1 deletion(-) create mode 100644 aws/resource_aws_licensemanager_association_test.go diff --git a/aws/resource_aws_launch_template_test.go b/aws/resource_aws_launch_template_test.go index ec4f27afdb3..81965b65b48 100644 --- a/aws/resource_aws_launch_template_test.go +++ b/aws/resource_aws_launch_template_test.go @@ -533,6 +533,28 @@ 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"), + resource.TestCheckResourceAttrPair(resName, "license_specification.0.license_configuration_arn", "aws_licensemanager_license_configuration.example", "id"), + ), + }, + }, + }) +} + func testAccCheckAWSLaunchTemplateExists(n string, t *ec2.LaunchTemplate) resource.TestCheckFunc { return func(s *terraform.State) error { rs, ok := s.RootModule().Resources[n] @@ -859,6 +881,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 index 142d57ebcc8..ceb01ba31bc 100644 --- a/aws/resource_aws_licensemanager_association.go +++ b/aws/resource_aws_licensemanager_association.go @@ -81,7 +81,7 @@ func resourceAwsLicenseManagerAssociationRead(d *schema.ResourceData, meta inter } d.Set("resource_arn", resourceArn) - d.Set("license_count", licenseConfigurationArn) + d.Set("license_configuration_arn", licenseConfigurationArn) 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..462792d6414 --- /dev/null +++ b/aws/resource_aws_licensemanager_association_test.go @@ -0,0 +1,136 @@ +package aws + +import ( + "fmt" + "testing" + + "github.com/aws/aws-sdk-go/aws" + "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 { + 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 := parseLicenseManagerAssociationId(rs.Primary.ID) + if err != nil { + return err + } + + conn := testAccProvider.Meta().(*AWSClient).licensemanagerconn + resp, err := conn.ListLicenseSpecificationsForResource(&licensemanager.ListLicenseSpecificationsForResourceInput{ + ResourceArn: aws.String(resourceArn), + }) + + if err != nil { + return fmt.Errorf("Error retrieving License Manager association (%s): %s", rs.Primary.ID, err) + } + + for _, ls := range resp.LicenseSpecifications { + if aws.StringValue(ls.LicenseConfigurationArn) == licenseConfigurationArn { + *licenseSpecification = *ls + return nil + } + } + + return fmt.Errorf("Error retrieving License Manager association (%s): Not found", rs.Primary.ID) + } +} + +func testAccCheckLicenseManagerAssociationDestroy(s *terraform.State) error { + conn := testAccProvider.Meta().(*AWSClient).licensemanagerconn + + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_licensemanager_association" { + continue + } + + // Try to find the resource + resourceArn, licenseConfigurationArn, err := parseLicenseManagerAssociationId(rs.Primary.ID) + if err != nil { + return err + } + + resp, err := conn.ListLicenseSpecificationsForResource(&licensemanager.ListLicenseSpecificationsForResourceInput{ + ResourceArn: aws.String(resourceArn), + }) + + if err != nil { + return err + } + + for _, ls := range resp.LicenseSpecifications { + if aws.StringValue(ls.LicenseConfigurationArn) == licenseConfigurationArn { + 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