github.com/ottenhoff/terraform@v0.7.0-rc1.0.20160607213102-ac2d195cc560/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  	_, err := conn.UpdateDeploymentGroup(&input)
   302  	if err != nil {
   303  		return err
   304  	}
   305  
   306  	return resourceAwsCodeDeployDeploymentGroupRead(d, meta)
   307  }
   308  
   309  func resourceAwsCodeDeployDeploymentGroupDelete(d *schema.ResourceData, meta interface{}) error {
   310  	conn := meta.(*AWSClient).codedeployconn
   311  
   312  	log.Printf("[DEBUG] Deleting CodeDeploy DeploymentGroup %s", d.Id())
   313  	_, err := conn.DeleteDeploymentGroup(&codedeploy.DeleteDeploymentGroupInput{
   314  		ApplicationName:     aws.String(d.Get("app_name").(string)),
   315  		DeploymentGroupName: aws.String(d.Get("deployment_group_name").(string)),
   316  	})
   317  	if err != nil {
   318  		return err
   319  	}
   320  
   321  	d.SetId("")
   322  
   323  	return nil
   324  }
   325  
   326  // buildOnPremTagFilters converts raw schema lists into a list of
   327  // codedeploy.TagFilters.
   328  func buildOnPremTagFilters(configured []interface{}) []*codedeploy.TagFilter {
   329  	filters := make([]*codedeploy.TagFilter, 0)
   330  	for _, raw := range configured {
   331  		var filter codedeploy.TagFilter
   332  		m := raw.(map[string]interface{})
   333  
   334  		if v, ok := m["key"]; ok {
   335  			filter.Key = aws.String(v.(string))
   336  		}
   337  		if v, ok := m["type"]; ok {
   338  			filter.Type = aws.String(v.(string))
   339  		}
   340  		if v, ok := m["value"]; ok {
   341  			filter.Value = aws.String(v.(string))
   342  		}
   343  
   344  		filters = append(filters, &filter)
   345  	}
   346  
   347  	return filters
   348  }
   349  
   350  // buildEC2TagFilters converts raw schema lists into a list of
   351  // codedeploy.EC2TagFilters.
   352  func buildEC2TagFilters(configured []interface{}) []*codedeploy.EC2TagFilter {
   353  	filters := make([]*codedeploy.EC2TagFilter, 0)
   354  	for _, raw := range configured {
   355  		var filter codedeploy.EC2TagFilter
   356  		m := raw.(map[string]interface{})
   357  
   358  		filter.Key = aws.String(m["key"].(string))
   359  		filter.Type = aws.String(m["type"].(string))
   360  		filter.Value = aws.String(m["value"].(string))
   361  
   362  		filters = append(filters, &filter)
   363  	}
   364  
   365  	return filters
   366  }
   367  
   368  // buildTriggerConfigs converts a raw schema list into a list of
   369  // codedeploy.TriggerConfig.
   370  func buildTriggerConfigs(configured []interface{}) []*codedeploy.TriggerConfig {
   371  	configs := make([]*codedeploy.TriggerConfig, 0, len(configured))
   372  	for _, raw := range configured {
   373  		var config codedeploy.TriggerConfig
   374  		m := raw.(map[string]interface{})
   375  
   376  		config.TriggerEvents = expandStringSet(m["trigger_events"].(*schema.Set))
   377  		config.TriggerName = aws.String(m["trigger_name"].(string))
   378  		config.TriggerTargetArn = aws.String(m["trigger_target_arn"].(string))
   379  
   380  		configs = append(configs, &config)
   381  	}
   382  	return configs
   383  }
   384  
   385  // ec2TagFiltersToMap converts lists of tag filters into a []map[string]string.
   386  func ec2TagFiltersToMap(list []*codedeploy.EC2TagFilter) []map[string]string {
   387  	result := make([]map[string]string, 0, len(list))
   388  	for _, tf := range list {
   389  		l := make(map[string]string)
   390  		if *tf.Key != "" {
   391  			l["key"] = *tf.Key
   392  		}
   393  		if *tf.Value != "" {
   394  			l["value"] = *tf.Value
   395  		}
   396  		if *tf.Type != "" {
   397  			l["type"] = *tf.Type
   398  		}
   399  		result = append(result, l)
   400  	}
   401  	return result
   402  }
   403  
   404  // onPremisesTagFiltersToMap converts lists of on-prem tag filters into a []map[string]string.
   405  func onPremisesTagFiltersToMap(list []*codedeploy.TagFilter) []map[string]string {
   406  	result := make([]map[string]string, 0, len(list))
   407  	for _, tf := range list {
   408  		l := make(map[string]string)
   409  		if tf.Key != nil && *tf.Key != "" {
   410  			l["key"] = *tf.Key
   411  		}
   412  		if tf.Value != nil && *tf.Value != "" {
   413  			l["value"] = *tf.Value
   414  		}
   415  		if tf.Type != nil && *tf.Type != "" {
   416  			l["type"] = *tf.Type
   417  		}
   418  		result = append(result, l)
   419  	}
   420  	return result
   421  }
   422  
   423  // triggerConfigsToMap converts a list of []*codedeploy.TriggerConfig into a []map[string]interface{}
   424  func triggerConfigsToMap(list []*codedeploy.TriggerConfig) []map[string]interface{} {
   425  	result := make([]map[string]interface{}, 0, len(list))
   426  	for _, tc := range list {
   427  		item := make(map[string]interface{})
   428  		item["trigger_events"] = schema.NewSet(schema.HashString, flattenStringList(tc.TriggerEvents))
   429  		item["trigger_name"] = *tc.TriggerName
   430  		item["trigger_target_arn"] = *tc.TriggerTargetArn
   431  		result = append(result, item)
   432  	}
   433  	return result
   434  }
   435  
   436  func resourceAwsCodeDeployTagFilterHash(v interface{}) int {
   437  	var buf bytes.Buffer
   438  	m := v.(map[string]interface{})
   439  
   440  	// Nothing's actually required in tag filters, so we must check the
   441  	// presence of all values before attempting a hash.
   442  	if v, ok := m["key"]; ok {
   443  		buf.WriteString(fmt.Sprintf("%s-", v.(string)))
   444  	}
   445  	if v, ok := m["type"]; ok {
   446  		buf.WriteString(fmt.Sprintf("%s-", v.(string)))
   447  	}
   448  	if v, ok := m["value"]; ok {
   449  		buf.WriteString(fmt.Sprintf("%s-", v.(string)))
   450  	}
   451  
   452  	return hashcode.String(buf.String())
   453  }
   454  
   455  func resourceAwsCodeDeployTriggerConfigHash(v interface{}) int {
   456  	var buf bytes.Buffer
   457  	m := v.(map[string]interface{})
   458  	buf.WriteString(fmt.Sprintf("%s-", m["trigger_name"].(string)))
   459  	buf.WriteString(fmt.Sprintf("%s-", m["trigger_target_arn"].(string)))
   460  
   461  	if triggerEvents, ok := m["trigger_events"]; ok {
   462  		names := triggerEvents.(*schema.Set).List()
   463  		strings := make([]string, len(names))
   464  		for i, raw := range names {
   465  			strings[i] = raw.(string)
   466  		}
   467  		sort.Strings(strings)
   468  
   469  		for _, s := range strings {
   470  			buf.WriteString(fmt.Sprintf("%s-", s))
   471  		}
   472  	}
   473  	return hashcode.String(buf.String())
   474  }
   475  
   476  func validateTriggerEvent(v interface{}, k string) (ws []string, errors []error) {
   477  	value := v.(string)
   478  	triggerEvents := map[string]bool{
   479  		"DeploymentStart":   true,
   480  		"DeploymentStop":    true,
   481  		"DeploymentSuccess": true,
   482  		"DeploymentFailure": true,
   483  		"InstanceStart":     true,
   484  		"InstanceSuccess":   true,
   485  		"InstanceFailure":   true,
   486  	}
   487  
   488  	if !triggerEvents[value] {
   489  		errors = append(errors, fmt.Errorf("%q must be a valid event type value: %q", k, value))
   490  	}
   491  	return
   492  }