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