github.com/turtlemonvh/terraform@v0.6.9-0.20151204001754-8e40b6b855e8/builtin/providers/aws/opsworks_layers.go (about)

     1  package aws
     2  
     3  import (
     4  	"fmt"
     5  	"log"
     6  	"strconv"
     7  
     8  	"github.com/hashicorp/terraform/helper/hashcode"
     9  	"github.com/hashicorp/terraform/helper/schema"
    10  
    11  	"github.com/aws/aws-sdk-go/aws"
    12  	"github.com/aws/aws-sdk-go/aws/awserr"
    13  	"github.com/aws/aws-sdk-go/service/opsworks"
    14  )
    15  
    16  // OpsWorks has a single concept of "layer" which represents several different
    17  // layer types. The differences between these are in some extra properties that
    18  // get packed into an "Attributes" map, but in the OpsWorks UI these are presented
    19  // as first-class options, and so Terraform prefers to expose them this way and
    20  // hide the implementation detail that they are all packed into a single type
    21  // in the underlying API.
    22  //
    23  // This file contains utilities that are shared between all of the concrete
    24  // layer resource types, which have names matching aws_opsworks_*_layer .
    25  
    26  type opsworksLayerTypeAttribute struct {
    27  	AttrName  string
    28  	Type      schema.ValueType
    29  	Default   interface{}
    30  	Required  bool
    31  	WriteOnly bool
    32  }
    33  
    34  type opsworksLayerType struct {
    35  	TypeName         string
    36  	DefaultLayerName string
    37  	Attributes       map[string]*opsworksLayerTypeAttribute
    38  	CustomShortName  bool
    39  }
    40  
    41  var (
    42  	opsworksTrueString  = "1"
    43  	opsworksFalseString = "0"
    44  )
    45  
    46  func (lt *opsworksLayerType) SchemaResource() *schema.Resource {
    47  	resourceSchema := map[string]*schema.Schema{
    48  		"id": &schema.Schema{
    49  			Type:     schema.TypeString,
    50  			Computed: true,
    51  		},
    52  
    53  		"auto_assign_elastic_ips": &schema.Schema{
    54  			Type:     schema.TypeBool,
    55  			Optional: true,
    56  			Default:  false,
    57  		},
    58  
    59  		"auto_assign_public_ips": &schema.Schema{
    60  			Type:     schema.TypeBool,
    61  			Optional: true,
    62  			Default:  false,
    63  		},
    64  
    65  		"custom_instance_profile_arn": &schema.Schema{
    66  			Type:     schema.TypeString,
    67  			Optional: true,
    68  		},
    69  
    70  		"custom_setup_recipes": &schema.Schema{
    71  			Type:     schema.TypeList,
    72  			Optional: true,
    73  			Elem:     &schema.Schema{Type: schema.TypeString},
    74  		},
    75  
    76  		"custom_configure_recipes": &schema.Schema{
    77  			Type:     schema.TypeList,
    78  			Optional: true,
    79  			Elem:     &schema.Schema{Type: schema.TypeString},
    80  		},
    81  
    82  		"custom_deploy_recipes": &schema.Schema{
    83  			Type:     schema.TypeList,
    84  			Optional: true,
    85  			Elem:     &schema.Schema{Type: schema.TypeString},
    86  		},
    87  
    88  		"custom_undeploy_recipes": &schema.Schema{
    89  			Type:     schema.TypeList,
    90  			Optional: true,
    91  			Elem:     &schema.Schema{Type: schema.TypeString},
    92  		},
    93  
    94  		"custom_shutdown_recipes": &schema.Schema{
    95  			Type:     schema.TypeList,
    96  			Optional: true,
    97  			Elem:     &schema.Schema{Type: schema.TypeString},
    98  		},
    99  
   100  		"custom_security_group_ids": &schema.Schema{
   101  			Type:     schema.TypeSet,
   102  			Optional: true,
   103  			Elem:     &schema.Schema{Type: schema.TypeString},
   104  			Set:      schema.HashString,
   105  		},
   106  
   107  		"auto_healing": &schema.Schema{
   108  			Type:     schema.TypeBool,
   109  			Optional: true,
   110  			Default:  true,
   111  		},
   112  
   113  		"install_updates_on_boot": &schema.Schema{
   114  			Type:     schema.TypeBool,
   115  			Optional: true,
   116  			Default:  true,
   117  		},
   118  
   119  		"instance_shutdown_timeout": &schema.Schema{
   120  			Type:     schema.TypeInt,
   121  			Optional: true,
   122  			Default:  120,
   123  		},
   124  
   125  		"drain_elb_on_shutdown": &schema.Schema{
   126  			Type:     schema.TypeBool,
   127  			Optional: true,
   128  			Default:  true,
   129  		},
   130  
   131  		"system_packages": &schema.Schema{
   132  			Type:     schema.TypeSet,
   133  			Optional: true,
   134  			Elem:     &schema.Schema{Type: schema.TypeString},
   135  			Set:      schema.HashString,
   136  		},
   137  
   138  		"stack_id": &schema.Schema{
   139  			Type:     schema.TypeString,
   140  			ForceNew: true,
   141  			Required: true,
   142  		},
   143  
   144  		"use_ebs_optimized_instances": &schema.Schema{
   145  			Type:     schema.TypeBool,
   146  			Optional: true,
   147  			Default:  false,
   148  		},
   149  
   150  		"ebs_volume": &schema.Schema{
   151  			Type:     schema.TypeSet,
   152  			Optional: true,
   153  			Elem: &schema.Resource{
   154  				Schema: map[string]*schema.Schema{
   155  
   156  					"iops": &schema.Schema{
   157  						Type:     schema.TypeInt,
   158  						Optional: true,
   159  						Default:  0,
   160  					},
   161  
   162  					"mount_point": &schema.Schema{
   163  						Type:     schema.TypeString,
   164  						Required: true,
   165  					},
   166  
   167  					"number_of_disks": &schema.Schema{
   168  						Type:     schema.TypeInt,
   169  						Required: true,
   170  					},
   171  
   172  					"raid_level": &schema.Schema{
   173  						Type:     schema.TypeString,
   174  						Optional: true,
   175  						Default:  "",
   176  					},
   177  
   178  					"size": &schema.Schema{
   179  						Type:     schema.TypeInt,
   180  						Required: true,
   181  					},
   182  
   183  					"type": &schema.Schema{
   184  						Type:     schema.TypeString,
   185  						Optional: true,
   186  						Default:  "standard",
   187  					},
   188  				},
   189  			},
   190  			Set: func(v interface{}) int {
   191  				m := v.(map[string]interface{})
   192  				return hashcode.String(m["mount_point"].(string))
   193  			},
   194  		},
   195  	}
   196  
   197  	if lt.CustomShortName {
   198  		resourceSchema["short_name"] = &schema.Schema{
   199  			Type:     schema.TypeString,
   200  			Required: true,
   201  		}
   202  	}
   203  
   204  	if lt.DefaultLayerName != "" {
   205  		resourceSchema["name"] = &schema.Schema{
   206  			Type:     schema.TypeString,
   207  			Optional: true,
   208  			Default:  lt.DefaultLayerName,
   209  		}
   210  	} else {
   211  		resourceSchema["name"] = &schema.Schema{
   212  			Type:     schema.TypeString,
   213  			Required: true,
   214  		}
   215  	}
   216  
   217  	for key, def := range lt.Attributes {
   218  		resourceSchema[key] = &schema.Schema{
   219  			Type:     def.Type,
   220  			Default:  def.Default,
   221  			Required: def.Required,
   222  			Optional: !def.Required,
   223  		}
   224  	}
   225  
   226  	return &schema.Resource{
   227  		Read: func(d *schema.ResourceData, meta interface{}) error {
   228  			client := meta.(*AWSClient).opsworksconn
   229  			return lt.Read(d, client)
   230  		},
   231  		Create: func(d *schema.ResourceData, meta interface{}) error {
   232  			client := meta.(*AWSClient).opsworksconn
   233  			return lt.Create(d, client)
   234  		},
   235  		Update: func(d *schema.ResourceData, meta interface{}) error {
   236  			client := meta.(*AWSClient).opsworksconn
   237  			return lt.Update(d, client)
   238  		},
   239  		Delete: func(d *schema.ResourceData, meta interface{}) error {
   240  			client := meta.(*AWSClient).opsworksconn
   241  			return lt.Delete(d, client)
   242  		},
   243  
   244  		Schema: resourceSchema,
   245  	}
   246  }
   247  
   248  func (lt *opsworksLayerType) Read(d *schema.ResourceData, client *opsworks.OpsWorks) error {
   249  
   250  	req := &opsworks.DescribeLayersInput{
   251  		LayerIds: []*string{
   252  			aws.String(d.Id()),
   253  		},
   254  	}
   255  
   256  	log.Printf("[DEBUG] Reading OpsWorks layer: %s", d.Id())
   257  
   258  	resp, err := client.DescribeLayers(req)
   259  	if err != nil {
   260  		if awserr, ok := err.(awserr.Error); ok {
   261  			if awserr.Code() == "ResourceNotFoundException" {
   262  				d.SetId("")
   263  				return nil
   264  			}
   265  		}
   266  		return err
   267  	}
   268  
   269  	layer := resp.Layers[0]
   270  	d.Set("id", layer.LayerId)
   271  	d.Set("auto_assign_elastic_ips", layer.AutoAssignElasticIps)
   272  	d.Set("auto_assign_public_ips", layer.AutoAssignPublicIps)
   273  	d.Set("custom_instance_profile_arn", layer.CustomInstanceProfileArn)
   274  	d.Set("custom_security_group_ids", unwrapAwsStringList(layer.CustomSecurityGroupIds))
   275  	d.Set("auto_healing", layer.EnableAutoHealing)
   276  	d.Set("install_updates_on_boot", layer.InstallUpdatesOnBoot)
   277  	d.Set("name", layer.Name)
   278  	d.Set("system_packages", unwrapAwsStringList(layer.Packages))
   279  	d.Set("stack_id", layer.StackId)
   280  	d.Set("use_ebs_optimized_instances", layer.UseEbsOptimizedInstances)
   281  
   282  	if lt.CustomShortName {
   283  		d.Set("short_name", layer.Shortname)
   284  	}
   285  
   286  	lt.SetAttributeMap(d, layer.Attributes)
   287  	lt.SetLifecycleEventConfiguration(d, layer.LifecycleEventConfiguration)
   288  	lt.SetCustomRecipes(d, layer.CustomRecipes)
   289  	lt.SetVolumeConfigurations(d, layer.VolumeConfigurations)
   290  
   291  	return nil
   292  }
   293  
   294  func (lt *opsworksLayerType) Create(d *schema.ResourceData, client *opsworks.OpsWorks) error {
   295  
   296  	req := &opsworks.CreateLayerInput{
   297  		AutoAssignElasticIps:        aws.Bool(d.Get("auto_assign_elastic_ips").(bool)),
   298  		AutoAssignPublicIps:         aws.Bool(d.Get("auto_assign_public_ips").(bool)),
   299  		CustomInstanceProfileArn:    aws.String(d.Get("custom_instance_profile_arn").(string)),
   300  		CustomRecipes:               lt.CustomRecipes(d),
   301  		CustomSecurityGroupIds:      makeAwsStringSet(d.Get("custom_security_group_ids").(*schema.Set)),
   302  		EnableAutoHealing:           aws.Bool(d.Get("auto_healing").(bool)),
   303  		InstallUpdatesOnBoot:        aws.Bool(d.Get("install_updates_on_boot").(bool)),
   304  		LifecycleEventConfiguration: lt.LifecycleEventConfiguration(d),
   305  		Name:                     aws.String(d.Get("name").(string)),
   306  		Packages:                 makeAwsStringSet(d.Get("system_packages").(*schema.Set)),
   307  		Type:                     aws.String(lt.TypeName),
   308  		StackId:                  aws.String(d.Get("stack_id").(string)),
   309  		UseEbsOptimizedInstances: aws.Bool(d.Get("use_ebs_optimized_instances").(bool)),
   310  		Attributes:               lt.AttributeMap(d),
   311  		VolumeConfigurations:     lt.VolumeConfigurations(d),
   312  	}
   313  
   314  	if lt.CustomShortName {
   315  		req.Shortname = aws.String(d.Get("short_name").(string))
   316  	} else {
   317  		req.Shortname = aws.String(lt.TypeName)
   318  	}
   319  
   320  	log.Printf("[DEBUG] Creating OpsWorks layer: %s", d.Id())
   321  
   322  	resp, err := client.CreateLayer(req)
   323  	if err != nil {
   324  		return err
   325  	}
   326  
   327  	layerId := *resp.LayerId
   328  	d.SetId(layerId)
   329  	d.Set("id", layerId)
   330  
   331  	return lt.Read(d, client)
   332  }
   333  
   334  func (lt *opsworksLayerType) Update(d *schema.ResourceData, client *opsworks.OpsWorks) error {
   335  
   336  	req := &opsworks.UpdateLayerInput{
   337  		LayerId:                     aws.String(d.Id()),
   338  		AutoAssignElasticIps:        aws.Bool(d.Get("auto_assign_elastic_ips").(bool)),
   339  		AutoAssignPublicIps:         aws.Bool(d.Get("auto_assign_public_ips").(bool)),
   340  		CustomInstanceProfileArn:    aws.String(d.Get("custom_instance_profile_arn").(string)),
   341  		CustomRecipes:               lt.CustomRecipes(d),
   342  		CustomSecurityGroupIds:      makeAwsStringSet(d.Get("custom_security_group_ids").(*schema.Set)),
   343  		EnableAutoHealing:           aws.Bool(d.Get("auto_healing").(bool)),
   344  		InstallUpdatesOnBoot:        aws.Bool(d.Get("install_updates_on_boot").(bool)),
   345  		LifecycleEventConfiguration: lt.LifecycleEventConfiguration(d),
   346  		Name:                     aws.String(d.Get("name").(string)),
   347  		Packages:                 makeAwsStringSet(d.Get("system_packages").(*schema.Set)),
   348  		UseEbsOptimizedInstances: aws.Bool(d.Get("use_ebs_optimized_instances").(bool)),
   349  		Attributes:               lt.AttributeMap(d),
   350  		VolumeConfigurations:     lt.VolumeConfigurations(d),
   351  	}
   352  
   353  	if lt.CustomShortName {
   354  		req.Shortname = aws.String(d.Get("short_name").(string))
   355  	} else {
   356  		req.Shortname = aws.String(lt.TypeName)
   357  	}
   358  
   359  	log.Printf("[DEBUG] Updating OpsWorks layer: %s", d.Id())
   360  
   361  	_, err := client.UpdateLayer(req)
   362  	if err != nil {
   363  		return err
   364  	}
   365  
   366  	return lt.Read(d, client)
   367  }
   368  
   369  func (lt *opsworksLayerType) Delete(d *schema.ResourceData, client *opsworks.OpsWorks) error {
   370  	req := &opsworks.DeleteLayerInput{
   371  		LayerId: aws.String(d.Id()),
   372  	}
   373  
   374  	log.Printf("[DEBUG] Deleting OpsWorks layer: %s", d.Id())
   375  
   376  	_, err := client.DeleteLayer(req)
   377  	return err
   378  }
   379  
   380  func (lt *opsworksLayerType) AttributeMap(d *schema.ResourceData) map[string]*string {
   381  	attrs := map[string]*string{}
   382  
   383  	for key, def := range lt.Attributes {
   384  		value := d.Get(key)
   385  		switch def.Type {
   386  		case schema.TypeString:
   387  			strValue := value.(string)
   388  			attrs[def.AttrName] = &strValue
   389  		case schema.TypeInt:
   390  			intValue := value.(int)
   391  			strValue := strconv.Itoa(intValue)
   392  			attrs[def.AttrName] = &strValue
   393  		case schema.TypeBool:
   394  			boolValue := value.(bool)
   395  			if boolValue {
   396  				attrs[def.AttrName] = &opsworksTrueString
   397  			} else {
   398  				attrs[def.AttrName] = &opsworksFalseString
   399  			}
   400  		default:
   401  			// should never happen
   402  			panic(fmt.Errorf("Unsupported OpsWorks layer attribute type"))
   403  		}
   404  	}
   405  
   406  	return attrs
   407  }
   408  
   409  func (lt *opsworksLayerType) SetAttributeMap(d *schema.ResourceData, attrs map[string]*string) {
   410  	for key, def := range lt.Attributes {
   411  		// Ignore write-only attributes; we'll just keep what we already have stored.
   412  		// (The AWS API returns garbage placeholder values for these.)
   413  		if def.WriteOnly {
   414  			continue
   415  		}
   416  
   417  		if strPtr, ok := attrs[def.AttrName]; ok && strPtr != nil {
   418  			strValue := *strPtr
   419  
   420  			switch def.Type {
   421  			case schema.TypeString:
   422  				d.Set(key, strValue)
   423  			case schema.TypeInt:
   424  				intValue, err := strconv.Atoi(strValue)
   425  				if err == nil {
   426  					d.Set(key, intValue)
   427  				} else {
   428  					// Got garbage from the AWS API
   429  					d.Set(key, nil)
   430  				}
   431  			case schema.TypeBool:
   432  				boolValue := true
   433  				if strValue == opsworksFalseString {
   434  					boolValue = false
   435  				}
   436  				d.Set(key, boolValue)
   437  			default:
   438  				// should never happen
   439  				panic(fmt.Errorf("Unsupported OpsWorks layer attribute type"))
   440  			}
   441  			return
   442  
   443  		} else {
   444  			d.Set(key, nil)
   445  		}
   446  	}
   447  }
   448  
   449  func (lt *opsworksLayerType) LifecycleEventConfiguration(d *schema.ResourceData) *opsworks.LifecycleEventConfiguration {
   450  	return &opsworks.LifecycleEventConfiguration{
   451  		Shutdown: &opsworks.ShutdownEventConfiguration{
   452  			DelayUntilElbConnectionsDrained: aws.Bool(d.Get("drain_elb_on_shutdown").(bool)),
   453  			ExecutionTimeout:                aws.Int64(int64(d.Get("instance_shutdown_timeout").(int))),
   454  		},
   455  	}
   456  }
   457  
   458  func (lt *opsworksLayerType) SetLifecycleEventConfiguration(d *schema.ResourceData, v *opsworks.LifecycleEventConfiguration) {
   459  	if v == nil || v.Shutdown == nil {
   460  		d.Set("drain_elb_on_shutdown", nil)
   461  		d.Set("instance_shutdown_timeout", nil)
   462  	} else {
   463  		d.Set("drain_elb_on_shutdown", v.Shutdown.DelayUntilElbConnectionsDrained)
   464  		d.Set("instance_shutdown_timeout", v.Shutdown.ExecutionTimeout)
   465  	}
   466  }
   467  
   468  func (lt *opsworksLayerType) CustomRecipes(d *schema.ResourceData) *opsworks.Recipes {
   469  	return &opsworks.Recipes{
   470  		Configure: makeAwsStringList(d.Get("custom_configure_recipes").([]interface{})),
   471  		Deploy:    makeAwsStringList(d.Get("custom_deploy_recipes").([]interface{})),
   472  		Setup:     makeAwsStringList(d.Get("custom_setup_recipes").([]interface{})),
   473  		Shutdown:  makeAwsStringList(d.Get("custom_shutdown_recipes").([]interface{})),
   474  		Undeploy:  makeAwsStringList(d.Get("custom_undeploy_recipes").([]interface{})),
   475  	}
   476  }
   477  
   478  func (lt *opsworksLayerType) SetCustomRecipes(d *schema.ResourceData, v *opsworks.Recipes) {
   479  	// Null out everything first, and then we'll consider what to put back.
   480  	d.Set("custom_configure_recipes", nil)
   481  	d.Set("custom_deploy_recipes", nil)
   482  	d.Set("custom_setup_recipes", nil)
   483  	d.Set("custom_shutdown_recipes", nil)
   484  	d.Set("custom_undeploy_recipes", nil)
   485  
   486  	if v == nil {
   487  		return
   488  	}
   489  
   490  	d.Set("custom_configure_recipes", unwrapAwsStringList(v.Configure))
   491  	d.Set("custom_deploy_recipes", unwrapAwsStringList(v.Deploy))
   492  	d.Set("custom_setup_recipes", unwrapAwsStringList(v.Setup))
   493  	d.Set("custom_shutdown_recipes", unwrapAwsStringList(v.Shutdown))
   494  	d.Set("custom_undeploy_recipes", unwrapAwsStringList(v.Undeploy))
   495  }
   496  
   497  func (lt *opsworksLayerType) VolumeConfigurations(d *schema.ResourceData) []*opsworks.VolumeConfiguration {
   498  	configuredVolumes := d.Get("ebs_volume").(*schema.Set).List()
   499  	result := make([]*opsworks.VolumeConfiguration, len(configuredVolumes))
   500  
   501  	for i := 0; i < len(configuredVolumes); i++ {
   502  		volumeData := configuredVolumes[i].(map[string]interface{})
   503  
   504  		result[i] = &opsworks.VolumeConfiguration{
   505  			MountPoint:    aws.String(volumeData["mount_point"].(string)),
   506  			NumberOfDisks: aws.Int64(int64(volumeData["number_of_disks"].(int))),
   507  			Size:          aws.Int64(int64(volumeData["size"].(int))),
   508  			VolumeType:    aws.String(volumeData["type"].(string)),
   509  		}
   510  		iops := int64(volumeData["iops"].(int))
   511  		if iops != 0 {
   512  			result[i].Iops = aws.Int64(iops)
   513  		}
   514  
   515  		raidLevelStr := volumeData["raid_level"].(string)
   516  		if raidLevelStr != "" {
   517  			raidLevel, err := strconv.Atoi(raidLevelStr)
   518  			if err == nil {
   519  				result[i].RaidLevel = aws.Int64(int64(raidLevel))
   520  			}
   521  		}
   522  	}
   523  
   524  	return result
   525  }
   526  
   527  func (lt *opsworksLayerType) SetVolumeConfigurations(d *schema.ResourceData, v []*opsworks.VolumeConfiguration) {
   528  	newValue := make([]*map[string]interface{}, len(v))
   529  
   530  	for i := 0; i < len(v); i++ {
   531  		config := v[i]
   532  		data := make(map[string]interface{})
   533  		newValue[i] = &data
   534  
   535  		if config.Iops != nil {
   536  			data["iops"] = int(*config.Iops)
   537  		} else {
   538  			data["iops"] = 0
   539  		}
   540  		if config.MountPoint != nil {
   541  			data["mount_point"] = *config.MountPoint
   542  		}
   543  		if config.NumberOfDisks != nil {
   544  			data["number_of_disks"] = int(*config.NumberOfDisks)
   545  		}
   546  		if config.RaidLevel != nil {
   547  			data["raid_level"] = strconv.Itoa(int(*config.RaidLevel))
   548  		}
   549  		if config.Size != nil {
   550  			data["size"] = int(*config.Size)
   551  		}
   552  		if config.VolumeType != nil {
   553  			data["type"] = *config.VolumeType
   554  		}
   555  	}
   556  
   557  	d.Set("ebs_volume", newValue)
   558  }