github.com/opsidian/terraform@v0.7.8-0.20161104123224-27c39cdfba5b/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  		if ec2err, ok := err.(awserr.Error); ok && ec2err.Code() == "DeploymentGroupDoesNotExistException" {
   242  			log.Printf("[INFO] CodeDeployment DeploymentGroup %s not found", d.Get("deployment_group_name").(string))
   243  			d.SetId("")
   244  			return nil
   245  		}
   246  
   247  		return err
   248  	}
   249  
   250  	d.Set("app_name", resp.DeploymentGroupInfo.ApplicationName)
   251  	d.Set("autoscaling_groups", resp.DeploymentGroupInfo.AutoScalingGroups)
   252  	d.Set("deployment_config_name", resp.DeploymentGroupInfo.DeploymentConfigName)
   253  	d.Set("deployment_group_name", resp.DeploymentGroupInfo.DeploymentGroupName)
   254  	d.Set("service_role_arn", resp.DeploymentGroupInfo.ServiceRoleArn)
   255  	if err := d.Set("ec2_tag_filter", ec2TagFiltersToMap(resp.DeploymentGroupInfo.Ec2TagFilters)); err != nil {
   256  		return err
   257  	}
   258  	if err := d.Set("on_premises_instance_tag_filter", onPremisesTagFiltersToMap(resp.DeploymentGroupInfo.OnPremisesInstanceTagFilters)); err != nil {
   259  		return err
   260  	}
   261  	if err := d.Set("trigger_configuration", triggerConfigsToMap(resp.DeploymentGroupInfo.TriggerConfigurations)); err != nil {
   262  		return err
   263  	}
   264  
   265  	return nil
   266  }
   267  
   268  func resourceAwsCodeDeployDeploymentGroupUpdate(d *schema.ResourceData, meta interface{}) error {
   269  	conn := meta.(*AWSClient).codedeployconn
   270  
   271  	input := codedeploy.UpdateDeploymentGroupInput{
   272  		ApplicationName:            aws.String(d.Get("app_name").(string)),
   273  		CurrentDeploymentGroupName: aws.String(d.Get("deployment_group_name").(string)),
   274  		ServiceRoleArn:             aws.String(d.Get("service_role_arn").(string)),
   275  	}
   276  
   277  	if d.HasChange("autoscaling_groups") {
   278  		_, n := d.GetChange("autoscaling_groups")
   279  		input.AutoScalingGroups = expandStringList(n.(*schema.Set).List())
   280  	}
   281  	if d.HasChange("deployment_config_name") {
   282  		_, n := d.GetChange("deployment_config_name")
   283  		input.DeploymentConfigName = aws.String(n.(string))
   284  	}
   285  	if d.HasChange("deployment_group_name") {
   286  		_, n := d.GetChange("deployment_group_name")
   287  		input.NewDeploymentGroupName = aws.String(n.(string))
   288  	}
   289  
   290  	// TagFilters aren't like tags. They don't append. They simply replace.
   291  	if d.HasChange("on_premises_instance_tag_filter") {
   292  		_, n := d.GetChange("on_premises_instance_tag_filter")
   293  		onPremFilters := buildOnPremTagFilters(n.(*schema.Set).List())
   294  		input.OnPremisesInstanceTagFilters = onPremFilters
   295  	}
   296  	if d.HasChange("ec2_tag_filter") {
   297  		_, n := d.GetChange("ec2_tag_filter")
   298  		ec2Filters := buildEC2TagFilters(n.(*schema.Set).List())
   299  		input.Ec2TagFilters = ec2Filters
   300  	}
   301  	if d.HasChange("trigger_configuration") {
   302  		_, n := d.GetChange("trigger_configuration")
   303  		triggerConfigs := buildTriggerConfigs(n.(*schema.Set).List())
   304  		input.TriggerConfigurations = triggerConfigs
   305  	}
   306  
   307  	log.Printf("[DEBUG] Updating CodeDeploy DeploymentGroup %s", d.Id())
   308  	// Retry to handle IAM role eventual consistency.
   309  	err := resource.Retry(5*time.Minute, func() *resource.RetryError {
   310  		_, err := conn.UpdateDeploymentGroup(&input)
   311  		if err != nil {
   312  			retry := false
   313  			codedeployErr, ok := err.(awserr.Error)
   314  			if !ok {
   315  				return resource.NonRetryableError(err)
   316  			}
   317  			if codedeployErr.Code() == "InvalidRoleException" {
   318  				retry = true
   319  			}
   320  			if codedeployErr.Code() == "InvalidTriggerConfigException" {
   321  				r := regexp.MustCompile("^Topic ARN .+ is not valid$")
   322  				if r.MatchString(codedeployErr.Message()) {
   323  					retry = true
   324  				}
   325  			}
   326  			if retry {
   327  				log.Printf("[DEBUG] Retrying Code Deployment Group Update: %q",
   328  					codedeployErr.Message())
   329  				return resource.RetryableError(err)
   330  			}
   331  
   332  			return resource.NonRetryableError(err)
   333  		}
   334  		return nil
   335  	})
   336  
   337  	if err != nil {
   338  		return err
   339  	}
   340  
   341  	return resourceAwsCodeDeployDeploymentGroupRead(d, meta)
   342  }
   343  
   344  func resourceAwsCodeDeployDeploymentGroupDelete(d *schema.ResourceData, meta interface{}) error {
   345  	conn := meta.(*AWSClient).codedeployconn
   346  
   347  	log.Printf("[DEBUG] Deleting CodeDeploy DeploymentGroup %s", d.Id())
   348  	_, err := conn.DeleteDeploymentGroup(&codedeploy.DeleteDeploymentGroupInput{
   349  		ApplicationName:     aws.String(d.Get("app_name").(string)),
   350  		DeploymentGroupName: aws.String(d.Get("deployment_group_name").(string)),
   351  	})
   352  	if err != nil {
   353  		return err
   354  	}
   355  
   356  	d.SetId("")
   357  
   358  	return nil
   359  }
   360  
   361  // buildOnPremTagFilters converts raw schema lists into a list of
   362  // codedeploy.TagFilters.
   363  func buildOnPremTagFilters(configured []interface{}) []*codedeploy.TagFilter {
   364  	filters := make([]*codedeploy.TagFilter, 0)
   365  	for _, raw := range configured {
   366  		var filter codedeploy.TagFilter
   367  		m := raw.(map[string]interface{})
   368  
   369  		if v, ok := m["key"]; ok {
   370  			filter.Key = aws.String(v.(string))
   371  		}
   372  		if v, ok := m["type"]; ok {
   373  			filter.Type = aws.String(v.(string))
   374  		}
   375  		if v, ok := m["value"]; ok {
   376  			filter.Value = aws.String(v.(string))
   377  		}
   378  
   379  		filters = append(filters, &filter)
   380  	}
   381  
   382  	return filters
   383  }
   384  
   385  // buildEC2TagFilters converts raw schema lists into a list of
   386  // codedeploy.EC2TagFilters.
   387  func buildEC2TagFilters(configured []interface{}) []*codedeploy.EC2TagFilter {
   388  	filters := make([]*codedeploy.EC2TagFilter, 0)
   389  	for _, raw := range configured {
   390  		var filter codedeploy.EC2TagFilter
   391  		m := raw.(map[string]interface{})
   392  
   393  		filter.Key = aws.String(m["key"].(string))
   394  		filter.Type = aws.String(m["type"].(string))
   395  		filter.Value = aws.String(m["value"].(string))
   396  
   397  		filters = append(filters, &filter)
   398  	}
   399  
   400  	return filters
   401  }
   402  
   403  // buildTriggerConfigs converts a raw schema list into a list of
   404  // codedeploy.TriggerConfig.
   405  func buildTriggerConfigs(configured []interface{}) []*codedeploy.TriggerConfig {
   406  	configs := make([]*codedeploy.TriggerConfig, 0, len(configured))
   407  	for _, raw := range configured {
   408  		var config codedeploy.TriggerConfig
   409  		m := raw.(map[string]interface{})
   410  
   411  		config.TriggerEvents = expandStringSet(m["trigger_events"].(*schema.Set))
   412  		config.TriggerName = aws.String(m["trigger_name"].(string))
   413  		config.TriggerTargetArn = aws.String(m["trigger_target_arn"].(string))
   414  
   415  		configs = append(configs, &config)
   416  	}
   417  	return configs
   418  }
   419  
   420  // ec2TagFiltersToMap converts lists of tag filters into a []map[string]string.
   421  func ec2TagFiltersToMap(list []*codedeploy.EC2TagFilter) []map[string]string {
   422  	result := make([]map[string]string, 0, len(list))
   423  	for _, tf := range list {
   424  		l := make(map[string]string)
   425  		if tf.Key != nil && *tf.Key != "" {
   426  			l["key"] = *tf.Key
   427  		}
   428  		if tf.Value != nil && *tf.Value != "" {
   429  			l["value"] = *tf.Value
   430  		}
   431  		if tf.Type != nil && *tf.Type != "" {
   432  			l["type"] = *tf.Type
   433  		}
   434  		result = append(result, l)
   435  	}
   436  	return result
   437  }
   438  
   439  // onPremisesTagFiltersToMap converts lists of on-prem tag filters into a []map[string]string.
   440  func onPremisesTagFiltersToMap(list []*codedeploy.TagFilter) []map[string]string {
   441  	result := make([]map[string]string, 0, len(list))
   442  	for _, tf := range list {
   443  		l := make(map[string]string)
   444  		if tf.Key != nil && *tf.Key != "" {
   445  			l["key"] = *tf.Key
   446  		}
   447  		if tf.Value != nil && *tf.Value != "" {
   448  			l["value"] = *tf.Value
   449  		}
   450  		if tf.Type != nil && *tf.Type != "" {
   451  			l["type"] = *tf.Type
   452  		}
   453  		result = append(result, l)
   454  	}
   455  	return result
   456  }
   457  
   458  // triggerConfigsToMap converts a list of []*codedeploy.TriggerConfig into a []map[string]interface{}
   459  func triggerConfigsToMap(list []*codedeploy.TriggerConfig) []map[string]interface{} {
   460  	result := make([]map[string]interface{}, 0, len(list))
   461  	for _, tc := range list {
   462  		item := make(map[string]interface{})
   463  		item["trigger_events"] = schema.NewSet(schema.HashString, flattenStringList(tc.TriggerEvents))
   464  		item["trigger_name"] = *tc.TriggerName
   465  		item["trigger_target_arn"] = *tc.TriggerTargetArn
   466  		result = append(result, item)
   467  	}
   468  	return result
   469  }
   470  
   471  func resourceAwsCodeDeployTagFilterHash(v interface{}) int {
   472  	var buf bytes.Buffer
   473  	m := v.(map[string]interface{})
   474  
   475  	// Nothing's actually required in tag filters, so we must check the
   476  	// presence of all values before attempting a hash.
   477  	if v, ok := m["key"]; ok {
   478  		buf.WriteString(fmt.Sprintf("%s-", v.(string)))
   479  	}
   480  	if v, ok := m["type"]; ok {
   481  		buf.WriteString(fmt.Sprintf("%s-", v.(string)))
   482  	}
   483  	if v, ok := m["value"]; ok {
   484  		buf.WriteString(fmt.Sprintf("%s-", v.(string)))
   485  	}
   486  
   487  	return hashcode.String(buf.String())
   488  }
   489  
   490  func resourceAwsCodeDeployTriggerConfigHash(v interface{}) int {
   491  	var buf bytes.Buffer
   492  	m := v.(map[string]interface{})
   493  	buf.WriteString(fmt.Sprintf("%s-", m["trigger_name"].(string)))
   494  	buf.WriteString(fmt.Sprintf("%s-", m["trigger_target_arn"].(string)))
   495  
   496  	if triggerEvents, ok := m["trigger_events"]; ok {
   497  		names := triggerEvents.(*schema.Set).List()
   498  		strings := make([]string, len(names))
   499  		for i, raw := range names {
   500  			strings[i] = raw.(string)
   501  		}
   502  		sort.Strings(strings)
   503  
   504  		for _, s := range strings {
   505  			buf.WriteString(fmt.Sprintf("%s-", s))
   506  		}
   507  	}
   508  	return hashcode.String(buf.String())
   509  }
   510  
   511  func validateTriggerEvent(v interface{}, k string) (ws []string, errors []error) {
   512  	value := v.(string)
   513  	triggerEvents := map[string]bool{
   514  		"DeploymentStart":   true,
   515  		"DeploymentStop":    true,
   516  		"DeploymentSuccess": true,
   517  		"DeploymentFailure": true,
   518  		"InstanceStart":     true,
   519  		"InstanceSuccess":   true,
   520  		"InstanceFailure":   true,
   521  	}
   522  
   523  	if !triggerEvents[value] {
   524  		errors = append(errors, fmt.Errorf("%q must be a valid event type value: %q", k, value))
   525  	}
   526  	return
   527  }