github.com/adrian-bl/terraform@v0.7.0-rc2.0.20160705220747-de0a34fc3517/builtin/providers/aws/resource_aws_codedeploy_deployment_group.go (about)

     1  package aws
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"log"
     7  	"regexp"
     8  	"sort"
     9  	"time"
    10  
    11  	"github.com/hashicorp/terraform/helper/hashcode"
    12  	"github.com/hashicorp/terraform/helper/resource"
    13  	"github.com/hashicorp/terraform/helper/schema"
    14  
    15  	"github.com/aws/aws-sdk-go/aws"
    16  	"github.com/aws/aws-sdk-go/aws/awserr"
    17  	"github.com/aws/aws-sdk-go/service/codedeploy"
    18  )
    19  
    20  func resourceAwsCodeDeployDeploymentGroup() *schema.Resource {
    21  	return &schema.Resource{
    22  		Create: resourceAwsCodeDeployDeploymentGroupCreate,
    23  		Read:   resourceAwsCodeDeployDeploymentGroupRead,
    24  		Update: resourceAwsCodeDeployDeploymentGroupUpdate,
    25  		Delete: resourceAwsCodeDeployDeploymentGroupDelete,
    26  
    27  		Schema: map[string]*schema.Schema{
    28  			"app_name": &schema.Schema{
    29  				Type:     schema.TypeString,
    30  				Required: true,
    31  				ValidateFunc: func(v interface{}, k string) (ws []string, errors []error) {
    32  					value := v.(string)
    33  					if len(value) > 100 {
    34  						errors = append(errors, fmt.Errorf(
    35  							"%q cannot exceed 100 characters", k))
    36  					}
    37  					return
    38  				},
    39  			},
    40  
    41  			"deployment_group_name": &schema.Schema{
    42  				Type:     schema.TypeString,
    43  				Required: true,
    44  				ForceNew: true,
    45  				ValidateFunc: func(v interface{}, k string) (ws []string, errors []error) {
    46  					value := v.(string)
    47  					if len(value) > 100 {
    48  						errors = append(errors, fmt.Errorf(
    49  							"%q cannot exceed 100 characters", k))
    50  					}
    51  					return
    52  				},
    53  			},
    54  
    55  			"service_role_arn": &schema.Schema{
    56  				Type:     schema.TypeString,
    57  				Required: true,
    58  			},
    59  
    60  			"autoscaling_groups": &schema.Schema{
    61  				Type:     schema.TypeSet,
    62  				Optional: true,
    63  				Elem:     &schema.Schema{Type: schema.TypeString},
    64  				Set:      schema.HashString,
    65  			},
    66  
    67  			"deployment_config_name": &schema.Schema{
    68  				Type:     schema.TypeString,
    69  				Optional: true,
    70  				Default:  "CodeDeployDefault.OneAtATime",
    71  				ValidateFunc: func(v interface{}, k string) (ws []string, errors []error) {
    72  					value := v.(string)
    73  					if len(value) > 100 {
    74  						errors = append(errors, fmt.Errorf(
    75  							"%q cannot exceed 100 characters", k))
    76  					}
    77  					return
    78  				},
    79  			},
    80  
    81  			"ec2_tag_filter": &schema.Schema{
    82  				Type:     schema.TypeSet,
    83  				Optional: true,
    84  				Elem: &schema.Resource{
    85  					Schema: map[string]*schema.Schema{
    86  						"key": &schema.Schema{
    87  							Type:     schema.TypeString,
    88  							Optional: true,
    89  						},
    90  
    91  						"type": &schema.Schema{
    92  							Type:         schema.TypeString,
    93  							Optional:     true,
    94  							ValidateFunc: validateTagFilters,
    95  						},
    96  
    97  						"value": &schema.Schema{
    98  							Type:     schema.TypeString,
    99  							Optional: true,
   100  						},
   101  					},
   102  				},
   103  				Set: resourceAwsCodeDeployTagFilterHash,
   104  			},
   105  
   106  			"on_premises_instance_tag_filter": &schema.Schema{
   107  				Type:     schema.TypeSet,
   108  				Optional: true,
   109  				Elem: &schema.Resource{
   110  					Schema: map[string]*schema.Schema{
   111  						"key": &schema.Schema{
   112  							Type:     schema.TypeString,
   113  							Optional: true,
   114  						},
   115  
   116  						"type": &schema.Schema{
   117  							Type:         schema.TypeString,
   118  							Optional:     true,
   119  							ValidateFunc: validateTagFilters,
   120  						},
   121  
   122  						"value": &schema.Schema{
   123  							Type:     schema.TypeString,
   124  							Optional: true,
   125  						},
   126  					},
   127  				},
   128  				Set: resourceAwsCodeDeployTagFilterHash,
   129  			},
   130  
   131  			"trigger_configuration": &schema.Schema{
   132  				Type:     schema.TypeSet,
   133  				Optional: true,
   134  				Elem: &schema.Resource{
   135  					Schema: map[string]*schema.Schema{
   136  						"trigger_events": &schema.Schema{
   137  							Type:     schema.TypeSet,
   138  							Required: true,
   139  							Set:      schema.HashString,
   140  							Elem: &schema.Schema{
   141  								Type:         schema.TypeString,
   142  								ValidateFunc: validateTriggerEvent,
   143  							},
   144  						},
   145  
   146  						"trigger_name": &schema.Schema{
   147  							Type:     schema.TypeString,
   148  							Required: true,
   149  						},
   150  
   151  						"trigger_target_arn": &schema.Schema{
   152  							Type:     schema.TypeString,
   153  							Required: true,
   154  						},
   155  					},
   156  				},
   157  				Set: resourceAwsCodeDeployTriggerConfigHash,
   158  			},
   159  		},
   160  	}
   161  }
   162  
   163  func resourceAwsCodeDeployDeploymentGroupCreate(d *schema.ResourceData, meta interface{}) error {
   164  	conn := meta.(*AWSClient).codedeployconn
   165  
   166  	application := d.Get("app_name").(string)
   167  	deploymentGroup := d.Get("deployment_group_name").(string)
   168  
   169  	input := codedeploy.CreateDeploymentGroupInput{
   170  		ApplicationName:     aws.String(application),
   171  		DeploymentGroupName: aws.String(deploymentGroup),
   172  		ServiceRoleArn:      aws.String(d.Get("service_role_arn").(string)),
   173  	}
   174  	if attr, ok := d.GetOk("deployment_config_name"); ok {
   175  		input.DeploymentConfigName = aws.String(attr.(string))
   176  	}
   177  	if attr, ok := d.GetOk("autoscaling_groups"); ok {
   178  		input.AutoScalingGroups = expandStringList(attr.(*schema.Set).List())
   179  	}
   180  	if attr, ok := d.GetOk("on_premises_instance_tag_filter"); ok {
   181  		onPremFilters := buildOnPremTagFilters(attr.(*schema.Set).List())
   182  		input.OnPremisesInstanceTagFilters = onPremFilters
   183  	}
   184  	if attr, ok := d.GetOk("ec2_tag_filter"); ok {
   185  		ec2TagFilters := buildEC2TagFilters(attr.(*schema.Set).List())
   186  		input.Ec2TagFilters = ec2TagFilters
   187  	}
   188  	if attr, ok := d.GetOk("trigger_configuration"); ok {
   189  		triggerConfigs := buildTriggerConfigs(attr.(*schema.Set).List())
   190  		input.TriggerConfigurations = triggerConfigs
   191  	}
   192  
   193  	// Retry to handle IAM role eventual consistency.
   194  	var resp *codedeploy.CreateDeploymentGroupOutput
   195  	var err error
   196  	err = resource.Retry(5*time.Minute, func() *resource.RetryError {
   197  		resp, err = conn.CreateDeploymentGroup(&input)
   198  		if err != nil {
   199  			retry := false
   200  			codedeployErr, ok := err.(awserr.Error)
   201  			if !ok {
   202  				return resource.NonRetryableError(err)
   203  			}
   204  			if codedeployErr.Code() == "InvalidRoleException" {
   205  				retry = true
   206  			}
   207  			if codedeployErr.Code() == "InvalidTriggerConfigException" {
   208  				r := regexp.MustCompile("^Topic ARN .+ is not valid$")
   209  				if r.MatchString(codedeployErr.Message()) {
   210  					retry = true
   211  				}
   212  			}
   213  			if retry {
   214  				log.Printf("[DEBUG] Trying to create deployment group again: %q",
   215  					codedeployErr.Message())
   216  				return resource.RetryableError(err)
   217  			}
   218  
   219  			return resource.NonRetryableError(err)
   220  		}
   221  		return nil
   222  	})
   223  	if err != nil {
   224  		return err
   225  	}
   226  
   227  	d.SetId(*resp.DeploymentGroupId)
   228  
   229  	return resourceAwsCodeDeployDeploymentGroupRead(d, meta)
   230  }
   231  
   232  func resourceAwsCodeDeployDeploymentGroupRead(d *schema.ResourceData, meta interface{}) error {
   233  	conn := meta.(*AWSClient).codedeployconn
   234  
   235  	log.Printf("[DEBUG] Reading CodeDeploy DeploymentGroup %s", d.Id())
   236  	resp, err := conn.GetDeploymentGroup(&codedeploy.GetDeploymentGroupInput{
   237  		ApplicationName:     aws.String(d.Get("app_name").(string)),
   238  		DeploymentGroupName: aws.String(d.Get("deployment_group_name").(string)),
   239  	})
   240  	if err != nil {
   241  		return err
   242  	}
   243  
   244  	d.Set("app_name", *resp.DeploymentGroupInfo.ApplicationName)
   245  	d.Set("autoscaling_groups", resp.DeploymentGroupInfo.AutoScalingGroups)
   246  	d.Set("deployment_config_name", *resp.DeploymentGroupInfo.DeploymentConfigName)
   247  	d.Set("deployment_group_name", *resp.DeploymentGroupInfo.DeploymentGroupName)
   248  	d.Set("service_role_arn", *resp.DeploymentGroupInfo.ServiceRoleArn)
   249  	if err := d.Set("ec2_tag_filter", ec2TagFiltersToMap(resp.DeploymentGroupInfo.Ec2TagFilters)); err != nil {
   250  		return err
   251  	}
   252  	if err := d.Set("on_premises_instance_tag_filter", onPremisesTagFiltersToMap(resp.DeploymentGroupInfo.OnPremisesInstanceTagFilters)); err != nil {
   253  		return err
   254  	}
   255  	if err := d.Set("trigger_configuration", triggerConfigsToMap(resp.DeploymentGroupInfo.TriggerConfigurations)); err != nil {
   256  		return err
   257  	}
   258  
   259  	return nil
   260  }
   261  
   262  func resourceAwsCodeDeployDeploymentGroupUpdate(d *schema.ResourceData, meta interface{}) error {
   263  	conn := meta.(*AWSClient).codedeployconn
   264  
   265  	input := codedeploy.UpdateDeploymentGroupInput{
   266  		ApplicationName:            aws.String(d.Get("app_name").(string)),
   267  		CurrentDeploymentGroupName: aws.String(d.Get("deployment_group_name").(string)),
   268  	}
   269  
   270  	if d.HasChange("autoscaling_groups") {
   271  		_, n := d.GetChange("autoscaling_groups")
   272  		input.AutoScalingGroups = expandStringList(n.(*schema.Set).List())
   273  	}
   274  	if d.HasChange("deployment_config_name") {
   275  		_, n := d.GetChange("deployment_config_name")
   276  		input.DeploymentConfigName = aws.String(n.(string))
   277  	}
   278  	if d.HasChange("deployment_group_name") {
   279  		_, n := d.GetChange("deployment_group_name")
   280  		input.NewDeploymentGroupName = aws.String(n.(string))
   281  	}
   282  
   283  	// TagFilters aren't like tags. They don't append. They simply replace.
   284  	if d.HasChange("on_premises_instance_tag_filter") {
   285  		_, n := d.GetChange("on_premises_instance_tag_filter")
   286  		onPremFilters := buildOnPremTagFilters(n.(*schema.Set).List())
   287  		input.OnPremisesInstanceTagFilters = onPremFilters
   288  	}
   289  	if d.HasChange("ec2_tag_filter") {
   290  		_, n := d.GetChange("ec2_tag_filter")
   291  		ec2Filters := buildEC2TagFilters(n.(*schema.Set).List())
   292  		input.Ec2TagFilters = ec2Filters
   293  	}
   294  	if d.HasChange("trigger_configuration") {
   295  		_, n := d.GetChange("trigger_configuration")
   296  		triggerConfigs := buildTriggerConfigs(n.(*schema.Set).List())
   297  		input.TriggerConfigurations = triggerConfigs
   298  	}
   299  
   300  	log.Printf("[DEBUG] Updating CodeDeploy DeploymentGroup %s", d.Id())
   301  	// Retry to handle IAM role eventual consistency.
   302  	err := resource.Retry(5*time.Minute, func() *resource.RetryError {
   303  		_, err := conn.UpdateDeploymentGroup(&input)
   304  		if err != nil {
   305  			retry := false
   306  			codedeployErr, ok := err.(awserr.Error)
   307  			if !ok {
   308  				return resource.NonRetryableError(err)
   309  			}
   310  			if codedeployErr.Code() == "InvalidRoleException" {
   311  				retry = true
   312  			}
   313  			if codedeployErr.Code() == "InvalidTriggerConfigException" {
   314  				r := regexp.MustCompile("^Topic ARN .+ is not valid$")
   315  				if r.MatchString(codedeployErr.Message()) {
   316  					retry = true
   317  				}
   318  			}
   319  			if retry {
   320  				log.Printf("[DEBUG] Retrying Code Deployment Group Update: %q",
   321  					codedeployErr.Message())
   322  				return resource.RetryableError(err)
   323  			}
   324  
   325  			return resource.NonRetryableError(err)
   326  		}
   327  		return nil
   328  	})
   329  
   330  	if err != nil {
   331  		return err
   332  	}
   333  
   334  	return resourceAwsCodeDeployDeploymentGroupRead(d, meta)
   335  }
   336  
   337  func resourceAwsCodeDeployDeploymentGroupDelete(d *schema.ResourceData, meta interface{}) error {
   338  	conn := meta.(*AWSClient).codedeployconn
   339  
   340  	log.Printf("[DEBUG] Deleting CodeDeploy DeploymentGroup %s", d.Id())
   341  	_, err := conn.DeleteDeploymentGroup(&codedeploy.DeleteDeploymentGroupInput{
   342  		ApplicationName:     aws.String(d.Get("app_name").(string)),
   343  		DeploymentGroupName: aws.String(d.Get("deployment_group_name").(string)),
   344  	})
   345  	if err != nil {
   346  		return err
   347  	}
   348  
   349  	d.SetId("")
   350  
   351  	return nil
   352  }
   353  
   354  // buildOnPremTagFilters converts raw schema lists into a list of
   355  // codedeploy.TagFilters.
   356  func buildOnPremTagFilters(configured []interface{}) []*codedeploy.TagFilter {
   357  	filters := make([]*codedeploy.TagFilter, 0)
   358  	for _, raw := range configured {
   359  		var filter codedeploy.TagFilter
   360  		m := raw.(map[string]interface{})
   361  
   362  		if v, ok := m["key"]; ok {
   363  			filter.Key = aws.String(v.(string))
   364  		}
   365  		if v, ok := m["type"]; ok {
   366  			filter.Type = aws.String(v.(string))
   367  		}
   368  		if v, ok := m["value"]; ok {
   369  			filter.Value = aws.String(v.(string))
   370  		}
   371  
   372  		filters = append(filters, &filter)
   373  	}
   374  
   375  	return filters
   376  }
   377  
   378  // buildEC2TagFilters converts raw schema lists into a list of
   379  // codedeploy.EC2TagFilters.
   380  func buildEC2TagFilters(configured []interface{}) []*codedeploy.EC2TagFilter {
   381  	filters := make([]*codedeploy.EC2TagFilter, 0)
   382  	for _, raw := range configured {
   383  		var filter codedeploy.EC2TagFilter
   384  		m := raw.(map[string]interface{})
   385  
   386  		filter.Key = aws.String(m["key"].(string))
   387  		filter.Type = aws.String(m["type"].(string))
   388  		filter.Value = aws.String(m["value"].(string))
   389  
   390  		filters = append(filters, &filter)
   391  	}
   392  
   393  	return filters
   394  }
   395  
   396  // buildTriggerConfigs converts a raw schema list into a list of
   397  // codedeploy.TriggerConfig.
   398  func buildTriggerConfigs(configured []interface{}) []*codedeploy.TriggerConfig {
   399  	configs := make([]*codedeploy.TriggerConfig, 0, len(configured))
   400  	for _, raw := range configured {
   401  		var config codedeploy.TriggerConfig
   402  		m := raw.(map[string]interface{})
   403  
   404  		config.TriggerEvents = expandStringSet(m["trigger_events"].(*schema.Set))
   405  		config.TriggerName = aws.String(m["trigger_name"].(string))
   406  		config.TriggerTargetArn = aws.String(m["trigger_target_arn"].(string))
   407  
   408  		configs = append(configs, &config)
   409  	}
   410  	return configs
   411  }
   412  
   413  // ec2TagFiltersToMap converts lists of tag filters into a []map[string]string.
   414  func ec2TagFiltersToMap(list []*codedeploy.EC2TagFilter) []map[string]string {
   415  	result := make([]map[string]string, 0, len(list))
   416  	for _, tf := range list {
   417  		l := make(map[string]string)
   418  		if tf.Key != nil && *tf.Key != "" {
   419  			l["key"] = *tf.Key
   420  		}
   421  		if tf.Value != nil && *tf.Value != "" {
   422  			l["value"] = *tf.Value
   423  		}
   424  		if tf.Type != nil && *tf.Type != "" {
   425  			l["type"] = *tf.Type
   426  		}
   427  		result = append(result, l)
   428  	}
   429  	return result
   430  }
   431  
   432  // onPremisesTagFiltersToMap converts lists of on-prem tag filters into a []map[string]string.
   433  func onPremisesTagFiltersToMap(list []*codedeploy.TagFilter) []map[string]string {
   434  	result := make([]map[string]string, 0, len(list))
   435  	for _, tf := range list {
   436  		l := make(map[string]string)
   437  		if tf.Key != nil && *tf.Key != "" {
   438  			l["key"] = *tf.Key
   439  		}
   440  		if tf.Value != nil && *tf.Value != "" {
   441  			l["value"] = *tf.Value
   442  		}
   443  		if tf.Type != nil && *tf.Type != "" {
   444  			l["type"] = *tf.Type
   445  		}
   446  		result = append(result, l)
   447  	}
   448  	return result
   449  }
   450  
   451  // triggerConfigsToMap converts a list of []*codedeploy.TriggerConfig into a []map[string]interface{}
   452  func triggerConfigsToMap(list []*codedeploy.TriggerConfig) []map[string]interface{} {
   453  	result := make([]map[string]interface{}, 0, len(list))
   454  	for _, tc := range list {
   455  		item := make(map[string]interface{})
   456  		item["trigger_events"] = schema.NewSet(schema.HashString, flattenStringList(tc.TriggerEvents))
   457  		item["trigger_name"] = *tc.TriggerName
   458  		item["trigger_target_arn"] = *tc.TriggerTargetArn
   459  		result = append(result, item)
   460  	}
   461  	return result
   462  }
   463  
   464  func resourceAwsCodeDeployTagFilterHash(v interface{}) int {
   465  	var buf bytes.Buffer
   466  	m := v.(map[string]interface{})
   467  
   468  	// Nothing's actually required in tag filters, so we must check the
   469  	// presence of all values before attempting a hash.
   470  	if v, ok := m["key"]; ok {
   471  		buf.WriteString(fmt.Sprintf("%s-", v.(string)))
   472  	}
   473  	if v, ok := m["type"]; ok {
   474  		buf.WriteString(fmt.Sprintf("%s-", v.(string)))
   475  	}
   476  	if v, ok := m["value"]; ok {
   477  		buf.WriteString(fmt.Sprintf("%s-", v.(string)))
   478  	}
   479  
   480  	return hashcode.String(buf.String())
   481  }
   482  
   483  func resourceAwsCodeDeployTriggerConfigHash(v interface{}) int {
   484  	var buf bytes.Buffer
   485  	m := v.(map[string]interface{})
   486  	buf.WriteString(fmt.Sprintf("%s-", m["trigger_name"].(string)))
   487  	buf.WriteString(fmt.Sprintf("%s-", m["trigger_target_arn"].(string)))
   488  
   489  	if triggerEvents, ok := m["trigger_events"]; ok {
   490  		names := triggerEvents.(*schema.Set).List()
   491  		strings := make([]string, len(names))
   492  		for i, raw := range names {
   493  			strings[i] = raw.(string)
   494  		}
   495  		sort.Strings(strings)
   496  
   497  		for _, s := range strings {
   498  			buf.WriteString(fmt.Sprintf("%s-", s))
   499  		}
   500  	}
   501  	return hashcode.String(buf.String())
   502  }
   503  
   504  func validateTriggerEvent(v interface{}, k string) (ws []string, errors []error) {
   505  	value := v.(string)
   506  	triggerEvents := map[string]bool{
   507  		"DeploymentStart":   true,
   508  		"DeploymentStop":    true,
   509  		"DeploymentSuccess": true,
   510  		"DeploymentFailure": true,
   511  		"InstanceStart":     true,
   512  		"InstanceSuccess":   true,
   513  		"InstanceFailure":   true,
   514  	}
   515  
   516  	if !triggerEvents[value] {
   517  		errors = append(errors, fmt.Errorf("%q must be a valid event type value: %q", k, value))
   518  	}
   519  	return
   520  }