github.com/vtorhonen/terraform@v0.9.0-beta2.0.20170307220345-5d894e4ffda7/builtin/providers/aws/resource_aws_kinesis_firehose_delivery_stream.go (about)

     1  package aws
     2  
     3  import (
     4  	"fmt"
     5  	"log"
     6  	"strings"
     7  	"time"
     8  
     9  	"github.com/aws/aws-sdk-go/aws"
    10  	"github.com/aws/aws-sdk-go/aws/awserr"
    11  	"github.com/aws/aws-sdk-go/service/firehose"
    12  	"github.com/hashicorp/terraform/helper/resource"
    13  	"github.com/hashicorp/terraform/helper/schema"
    14  )
    15  
    16  func cloudWatchLoggingOptionsSchema() *schema.Schema {
    17  	return &schema.Schema{
    18  		Type:     schema.TypeSet,
    19  		MaxItems: 1,
    20  		Optional: true,
    21  		Computed: true,
    22  		Elem: &schema.Resource{
    23  			Schema: map[string]*schema.Schema{
    24  				"enabled": {
    25  					Type:     schema.TypeBool,
    26  					Optional: true,
    27  					Default:  false,
    28  				},
    29  
    30  				"log_group_name": {
    31  					Type:     schema.TypeString,
    32  					Optional: true,
    33  				},
    34  
    35  				"log_stream_name": {
    36  					Type:     schema.TypeString,
    37  					Optional: true,
    38  				},
    39  			},
    40  		},
    41  	}
    42  }
    43  
    44  func resourceAwsKinesisFirehoseDeliveryStream() *schema.Resource {
    45  	return &schema.Resource{
    46  		Create: resourceAwsKinesisFirehoseDeliveryStreamCreate,
    47  		Read:   resourceAwsKinesisFirehoseDeliveryStreamRead,
    48  		Update: resourceAwsKinesisFirehoseDeliveryStreamUpdate,
    49  		Delete: resourceAwsKinesisFirehoseDeliveryStreamDelete,
    50  
    51  		SchemaVersion: 1,
    52  		MigrateState:  resourceAwsKinesisFirehoseMigrateState,
    53  		Schema: map[string]*schema.Schema{
    54  			"name": {
    55  				Type:     schema.TypeString,
    56  				Required: true,
    57  				ForceNew: true,
    58  				ValidateFunc: func(v interface{}, k string) (ws []string, errors []error) {
    59  					value := v.(string)
    60  					if len(value) > 64 {
    61  						errors = append(errors, fmt.Errorf(
    62  							"%q cannot be longer than 64 characters", k))
    63  					}
    64  					return
    65  				},
    66  			},
    67  
    68  			"destination": {
    69  				Type:     schema.TypeString,
    70  				Required: true,
    71  				ForceNew: true,
    72  				StateFunc: func(v interface{}) string {
    73  					value := v.(string)
    74  					return strings.ToLower(value)
    75  				},
    76  				ValidateFunc: func(v interface{}, k string) (ws []string, errors []error) {
    77  					value := v.(string)
    78  					if value != "s3" && value != "redshift" && value != "elasticsearch" {
    79  						errors = append(errors, fmt.Errorf(
    80  							"%q must be one of 's3', 'redshift', 'elasticsearch'", k))
    81  					}
    82  					return
    83  				},
    84  			},
    85  
    86  			"s3_configuration": {
    87  				Type:     schema.TypeList,
    88  				Required: true,
    89  				MaxItems: 1,
    90  				Elem: &schema.Resource{
    91  					Schema: map[string]*schema.Schema{
    92  						"bucket_arn": {
    93  							Type:     schema.TypeString,
    94  							Required: true,
    95  						},
    96  
    97  						"buffer_size": {
    98  							Type:     schema.TypeInt,
    99  							Optional: true,
   100  							Default:  5,
   101  						},
   102  
   103  						"buffer_interval": {
   104  							Type:     schema.TypeInt,
   105  							Optional: true,
   106  							Default:  300,
   107  						},
   108  
   109  						"compression_format": {
   110  							Type:     schema.TypeString,
   111  							Optional: true,
   112  							Default:  "UNCOMPRESSED",
   113  						},
   114  
   115  						"kms_key_arn": {
   116  							Type:         schema.TypeString,
   117  							Optional:     true,
   118  							ValidateFunc: validateArn,
   119  						},
   120  
   121  						"role_arn": {
   122  							Type:     schema.TypeString,
   123  							Required: true,
   124  						},
   125  
   126  						"prefix": {
   127  							Type:     schema.TypeString,
   128  							Optional: true,
   129  						},
   130  
   131  						"cloudwatch_logging_options": cloudWatchLoggingOptionsSchema(),
   132  					},
   133  				},
   134  			},
   135  
   136  			"redshift_configuration": {
   137  				Type:     schema.TypeList,
   138  				Optional: true,
   139  				MaxItems: 1,
   140  				Elem: &schema.Resource{
   141  					Schema: map[string]*schema.Schema{
   142  						"cluster_jdbcurl": {
   143  							Type:     schema.TypeString,
   144  							Required: true,
   145  						},
   146  
   147  						"username": {
   148  							Type:     schema.TypeString,
   149  							Required: true,
   150  						},
   151  
   152  						"password": {
   153  							Type:     schema.TypeString,
   154  							Required: true,
   155  						},
   156  
   157  						"role_arn": {
   158  							Type:     schema.TypeString,
   159  							Required: true,
   160  						},
   161  
   162  						"retry_duration": {
   163  							Type:     schema.TypeInt,
   164  							Optional: true,
   165  							Default:  3600,
   166  							ValidateFunc: func(v interface{}, k string) (ws []string, errors []error) {
   167  								value := v.(int)
   168  								if value < 0 || value > 7200 {
   169  									errors = append(errors, fmt.Errorf(
   170  										"%q must be in the range from 0 to 7200 seconds.", k))
   171  								}
   172  								return
   173  							},
   174  						},
   175  
   176  						"copy_options": {
   177  							Type:     schema.TypeString,
   178  							Optional: true,
   179  						},
   180  
   181  						"data_table_columns": {
   182  							Type:     schema.TypeString,
   183  							Optional: true,
   184  						},
   185  
   186  						"data_table_name": {
   187  							Type:     schema.TypeString,
   188  							Required: true,
   189  						},
   190  
   191  						"cloudwatch_logging_options": cloudWatchLoggingOptionsSchema(),
   192  					},
   193  				},
   194  			},
   195  
   196  			"elasticsearch_configuration": {
   197  				Type:     schema.TypeList,
   198  				Optional: true,
   199  				MaxItems: 1,
   200  				Elem: &schema.Resource{
   201  					Schema: map[string]*schema.Schema{
   202  						"buffering_interval": {
   203  							Type:     schema.TypeInt,
   204  							Optional: true,
   205  							Default:  300,
   206  							ValidateFunc: func(v interface{}, k string) (ws []string, errors []error) {
   207  								value := v.(int)
   208  								if value < 60 || value > 900 {
   209  									errors = append(errors, fmt.Errorf(
   210  										"%q must be in the range from 60 to 900 seconds.", k))
   211  								}
   212  								return
   213  							},
   214  						},
   215  
   216  						"buffering_size": {
   217  							Type:     schema.TypeInt,
   218  							Optional: true,
   219  							Default:  5,
   220  							ValidateFunc: func(v interface{}, k string) (ws []string, errors []error) {
   221  								value := v.(int)
   222  								if value < 1 || value > 100 {
   223  									errors = append(errors, fmt.Errorf(
   224  										"%q must be in the range from 1 to 100 MB.", k))
   225  								}
   226  								return
   227  							},
   228  						},
   229  
   230  						"domain_arn": {
   231  							Type:     schema.TypeString,
   232  							Required: true,
   233  						},
   234  
   235  						"index_name": {
   236  							Type:     schema.TypeString,
   237  							Required: true,
   238  						},
   239  
   240  						"index_rotation_period": {
   241  							Type:     schema.TypeString,
   242  							Optional: true,
   243  							Default:  "OneDay",
   244  							ValidateFunc: func(v interface{}, k string) (ws []string, errors []error) {
   245  								value := v.(string)
   246  								if value != "NoRotation" && value != "OneHour" && value != "OneDay" && value != "OneWeek" && value != "OneMonth" {
   247  									errors = append(errors, fmt.Errorf(
   248  										"%q must be one of 'NoRotation', 'OneHour', 'OneDay', 'OneWeek', 'OneMonth'", k))
   249  								}
   250  								return
   251  							},
   252  						},
   253  
   254  						"retry_duration": {
   255  							Type:     schema.TypeInt,
   256  							Optional: true,
   257  							Default:  300,
   258  							ValidateFunc: func(v interface{}, k string) (ws []string, errors []error) {
   259  								value := v.(int)
   260  								if value < 0 || value > 7200 {
   261  									errors = append(errors, fmt.Errorf(
   262  										"%q must be in the range from 0 to 7200 seconds.", k))
   263  								}
   264  								return
   265  							},
   266  						},
   267  
   268  						"role_arn": {
   269  							Type:     schema.TypeString,
   270  							Required: true,
   271  						},
   272  
   273  						"s3_backup_mode": {
   274  							Type:     schema.TypeString,
   275  							Optional: true,
   276  							Default:  "FailedDocumentsOnly",
   277  							ValidateFunc: func(v interface{}, k string) (ws []string, errors []error) {
   278  								value := v.(string)
   279  								if value != "FailedDocumentsOnly" && value != "AllDocuments" {
   280  									errors = append(errors, fmt.Errorf(
   281  										"%q must be one of 'FailedDocumentsOnly', 'AllDocuments'", k))
   282  								}
   283  								return
   284  							},
   285  						},
   286  
   287  						"type_name": {
   288  							Type:     schema.TypeString,
   289  							Optional: true,
   290  							ValidateFunc: func(v interface{}, k string) (ws []string, errors []error) {
   291  								value := v.(string)
   292  								if len(value) > 100 {
   293  									errors = append(errors, fmt.Errorf(
   294  										"%q cannot be longer than 100 characters", k))
   295  								}
   296  								return
   297  							},
   298  						},
   299  
   300  						"cloudwatch_logging_options": cloudWatchLoggingOptionsSchema(),
   301  					},
   302  				},
   303  			},
   304  
   305  			"arn": {
   306  				Type:     schema.TypeString,
   307  				Optional: true,
   308  				Computed: true,
   309  			},
   310  
   311  			"version_id": {
   312  				Type:     schema.TypeString,
   313  				Optional: true,
   314  				Computed: true,
   315  			},
   316  
   317  			"destination_id": {
   318  				Type:     schema.TypeString,
   319  				Optional: true,
   320  				Computed: true,
   321  			},
   322  		},
   323  	}
   324  }
   325  
   326  func createS3Config(d *schema.ResourceData) *firehose.S3DestinationConfiguration {
   327  	s3 := d.Get("s3_configuration").([]interface{})[0].(map[string]interface{})
   328  
   329  	configuration := &firehose.S3DestinationConfiguration{
   330  		BucketARN: aws.String(s3["bucket_arn"].(string)),
   331  		RoleARN:   aws.String(s3["role_arn"].(string)),
   332  		BufferingHints: &firehose.BufferingHints{
   333  			IntervalInSeconds: aws.Int64(int64(s3["buffer_interval"].(int))),
   334  			SizeInMBs:         aws.Int64(int64(s3["buffer_size"].(int))),
   335  		},
   336  		Prefix:                  extractPrefixConfiguration(s3),
   337  		CompressionFormat:       aws.String(s3["compression_format"].(string)),
   338  		EncryptionConfiguration: extractEncryptionConfiguration(s3),
   339  	}
   340  
   341  	if _, ok := s3["cloudwatch_logging_options"]; ok {
   342  		configuration.CloudWatchLoggingOptions = extractCloudWatchLoggingConfiguration(s3)
   343  	}
   344  
   345  	return configuration
   346  }
   347  
   348  func updateS3Config(d *schema.ResourceData) *firehose.S3DestinationUpdate {
   349  	s3 := d.Get("s3_configuration").([]interface{})[0].(map[string]interface{})
   350  
   351  	configuration := &firehose.S3DestinationUpdate{
   352  		BucketARN: aws.String(s3["bucket_arn"].(string)),
   353  		RoleARN:   aws.String(s3["role_arn"].(string)),
   354  		BufferingHints: &firehose.BufferingHints{
   355  			IntervalInSeconds: aws.Int64((int64)(s3["buffer_interval"].(int))),
   356  			SizeInMBs:         aws.Int64((int64)(s3["buffer_size"].(int))),
   357  		},
   358  		Prefix:                   extractPrefixConfiguration(s3),
   359  		CompressionFormat:        aws.String(s3["compression_format"].(string)),
   360  		EncryptionConfiguration:  extractEncryptionConfiguration(s3),
   361  		CloudWatchLoggingOptions: extractCloudWatchLoggingConfiguration(s3),
   362  	}
   363  
   364  	if _, ok := s3["cloudwatch_logging_options"]; ok {
   365  		configuration.CloudWatchLoggingOptions = extractCloudWatchLoggingConfiguration(s3)
   366  	}
   367  
   368  	return configuration
   369  }
   370  
   371  func extractEncryptionConfiguration(s3 map[string]interface{}) *firehose.EncryptionConfiguration {
   372  	if key, ok := s3["kms_key_arn"]; ok && len(key.(string)) > 0 {
   373  		return &firehose.EncryptionConfiguration{
   374  			KMSEncryptionConfig: &firehose.KMSEncryptionConfig{
   375  				AWSKMSKeyARN: aws.String(key.(string)),
   376  			},
   377  		}
   378  	}
   379  
   380  	return &firehose.EncryptionConfiguration{
   381  		NoEncryptionConfig: aws.String("NoEncryption"),
   382  	}
   383  }
   384  
   385  func extractCloudWatchLoggingConfiguration(s3 map[string]interface{}) *firehose.CloudWatchLoggingOptions {
   386  	config := s3["cloudwatch_logging_options"].(*schema.Set).List()
   387  	if len(config) == 0 {
   388  		return nil
   389  	}
   390  
   391  	loggingConfig := config[0].(map[string]interface{})
   392  	loggingOptions := &firehose.CloudWatchLoggingOptions{
   393  		Enabled: aws.Bool(loggingConfig["enabled"].(bool)),
   394  	}
   395  
   396  	if v, ok := loggingConfig["log_group_name"]; ok {
   397  		loggingOptions.LogGroupName = aws.String(v.(string))
   398  	}
   399  
   400  	if v, ok := loggingConfig["log_stream_name"]; ok {
   401  		loggingOptions.LogStreamName = aws.String(v.(string))
   402  	}
   403  
   404  	return loggingOptions
   405  
   406  }
   407  
   408  func extractPrefixConfiguration(s3 map[string]interface{}) *string {
   409  	if v, ok := s3["prefix"]; ok {
   410  		return aws.String(v.(string))
   411  	}
   412  
   413  	return nil
   414  }
   415  
   416  func createRedshiftConfig(d *schema.ResourceData, s3Config *firehose.S3DestinationConfiguration) (*firehose.RedshiftDestinationConfiguration, error) {
   417  	redshiftRaw, ok := d.GetOk("redshift_configuration")
   418  	if !ok {
   419  		return nil, fmt.Errorf("[ERR] Error loading Redshift Configuration for Kinesis Firehose: redshift_configuration not found")
   420  	}
   421  	rl := redshiftRaw.([]interface{})
   422  
   423  	redshift := rl[0].(map[string]interface{})
   424  
   425  	configuration := &firehose.RedshiftDestinationConfiguration{
   426  		ClusterJDBCURL:  aws.String(redshift["cluster_jdbcurl"].(string)),
   427  		RetryOptions:    extractRedshiftRetryOptions(redshift),
   428  		Password:        aws.String(redshift["password"].(string)),
   429  		Username:        aws.String(redshift["username"].(string)),
   430  		RoleARN:         aws.String(redshift["role_arn"].(string)),
   431  		CopyCommand:     extractCopyCommandConfiguration(redshift),
   432  		S3Configuration: s3Config,
   433  	}
   434  
   435  	if _, ok := redshift["cloudwatch_logging_options"]; ok {
   436  		configuration.CloudWatchLoggingOptions = extractCloudWatchLoggingConfiguration(redshift)
   437  	}
   438  
   439  	return configuration, nil
   440  }
   441  
   442  func updateRedshiftConfig(d *schema.ResourceData, s3Update *firehose.S3DestinationUpdate) (*firehose.RedshiftDestinationUpdate, error) {
   443  	redshiftRaw, ok := d.GetOk("redshift_configuration")
   444  	if !ok {
   445  		return nil, fmt.Errorf("[ERR] Error loading Redshift Configuration for Kinesis Firehose: redshift_configuration not found")
   446  	}
   447  	rl := redshiftRaw.([]interface{})
   448  
   449  	redshift := rl[0].(map[string]interface{})
   450  
   451  	configuration := &firehose.RedshiftDestinationUpdate{
   452  		ClusterJDBCURL: aws.String(redshift["cluster_jdbcurl"].(string)),
   453  		RetryOptions:   extractRedshiftRetryOptions(redshift),
   454  		Password:       aws.String(redshift["password"].(string)),
   455  		Username:       aws.String(redshift["username"].(string)),
   456  		RoleARN:        aws.String(redshift["role_arn"].(string)),
   457  		CopyCommand:    extractCopyCommandConfiguration(redshift),
   458  		S3Update:       s3Update,
   459  	}
   460  
   461  	if _, ok := redshift["cloudwatch_logging_options"]; ok {
   462  		configuration.CloudWatchLoggingOptions = extractCloudWatchLoggingConfiguration(redshift)
   463  	}
   464  
   465  	return configuration, nil
   466  }
   467  
   468  func createElasticsearchConfig(d *schema.ResourceData, s3Config *firehose.S3DestinationConfiguration) (*firehose.ElasticsearchDestinationConfiguration, error) {
   469  	esConfig, ok := d.GetOk("elasticsearch_configuration")
   470  	if !ok {
   471  		return nil, fmt.Errorf("[ERR] Error loading Elasticsearch Configuration for Kinesis Firehose: elasticsearch_configuration not found")
   472  	}
   473  	esList := esConfig.([]interface{})
   474  
   475  	es := esList[0].(map[string]interface{})
   476  
   477  	config := &firehose.ElasticsearchDestinationConfiguration{
   478  		BufferingHints:  extractBufferingHints(es),
   479  		DomainARN:       aws.String(es["domain_arn"].(string)),
   480  		IndexName:       aws.String(es["index_name"].(string)),
   481  		RetryOptions:    extractElasticSearchRetryOptions(es),
   482  		RoleARN:         aws.String(es["role_arn"].(string)),
   483  		TypeName:        aws.String(es["type_name"].(string)),
   484  		S3Configuration: s3Config,
   485  	}
   486  
   487  	if _, ok := es["cloudwatch_logging_options"]; ok {
   488  		config.CloudWatchLoggingOptions = extractCloudWatchLoggingConfiguration(es)
   489  	}
   490  
   491  	if indexRotationPeriod, ok := es["index_rotation_period"]; ok {
   492  		config.IndexRotationPeriod = aws.String(indexRotationPeriod.(string))
   493  	}
   494  	if s3BackupMode, ok := es["s3_backup_mode"]; ok {
   495  		config.S3BackupMode = aws.String(s3BackupMode.(string))
   496  	}
   497  
   498  	return config, nil
   499  }
   500  
   501  func updateElasticsearchConfig(d *schema.ResourceData, s3Update *firehose.S3DestinationUpdate) (*firehose.ElasticsearchDestinationUpdate, error) {
   502  	esConfig, ok := d.GetOk("elasticsearch_configuration")
   503  	if !ok {
   504  		return nil, fmt.Errorf("[ERR] Error loading Elasticsearch Configuration for Kinesis Firehose: elasticsearch_configuration not found")
   505  	}
   506  	esList := esConfig.([]interface{})
   507  
   508  	es := esList[0].(map[string]interface{})
   509  
   510  	update := &firehose.ElasticsearchDestinationUpdate{
   511  		BufferingHints: extractBufferingHints(es),
   512  		DomainARN:      aws.String(es["domain_arn"].(string)),
   513  		IndexName:      aws.String(es["index_name"].(string)),
   514  		RetryOptions:   extractElasticSearchRetryOptions(es),
   515  		RoleARN:        aws.String(es["role_arn"].(string)),
   516  		TypeName:       aws.String(es["type_name"].(string)),
   517  		S3Update:       s3Update,
   518  	}
   519  
   520  	if _, ok := es["cloudwatch_logging_options"]; ok {
   521  		update.CloudWatchLoggingOptions = extractCloudWatchLoggingConfiguration(es)
   522  	}
   523  
   524  	if indexRotationPeriod, ok := es["index_rotation_period"]; ok {
   525  		update.IndexRotationPeriod = aws.String(indexRotationPeriod.(string))
   526  	}
   527  
   528  	return update, nil
   529  }
   530  
   531  func extractBufferingHints(es map[string]interface{}) *firehose.ElasticsearchBufferingHints {
   532  	bufferingHints := &firehose.ElasticsearchBufferingHints{}
   533  
   534  	if bufferingInterval, ok := es["buffering_interval"].(int); ok {
   535  		bufferingHints.IntervalInSeconds = aws.Int64(int64(bufferingInterval))
   536  	}
   537  	if bufferingSize, ok := es["buffering_size"].(int); ok {
   538  		bufferingHints.SizeInMBs = aws.Int64(int64(bufferingSize))
   539  	}
   540  
   541  	return bufferingHints
   542  }
   543  
   544  func extractElasticSearchRetryOptions(es map[string]interface{}) *firehose.ElasticsearchRetryOptions {
   545  	retryOptions := &firehose.ElasticsearchRetryOptions{}
   546  
   547  	if retryDuration, ok := es["retry_duration"].(int); ok {
   548  		retryOptions.DurationInSeconds = aws.Int64(int64(retryDuration))
   549  	}
   550  
   551  	return retryOptions
   552  }
   553  
   554  func extractRedshiftRetryOptions(redshift map[string]interface{}) *firehose.RedshiftRetryOptions {
   555  	retryOptions := &firehose.RedshiftRetryOptions{}
   556  
   557  	if retryDuration, ok := redshift["retry_duration"].(int); ok {
   558  		retryOptions.DurationInSeconds = aws.Int64(int64(retryDuration))
   559  	}
   560  
   561  	return retryOptions
   562  }
   563  
   564  func extractCopyCommandConfiguration(redshift map[string]interface{}) *firehose.CopyCommand {
   565  	cmd := &firehose.CopyCommand{
   566  		DataTableName: aws.String(redshift["data_table_name"].(string)),
   567  	}
   568  	if copyOptions, ok := redshift["copy_options"]; ok {
   569  		cmd.CopyOptions = aws.String(copyOptions.(string))
   570  	}
   571  	if columns, ok := redshift["data_table_columns"]; ok {
   572  		cmd.DataTableColumns = aws.String(columns.(string))
   573  	}
   574  
   575  	return cmd
   576  }
   577  
   578  func resourceAwsKinesisFirehoseDeliveryStreamCreate(d *schema.ResourceData, meta interface{}) error {
   579  	conn := meta.(*AWSClient).firehoseconn
   580  
   581  	sn := d.Get("name").(string)
   582  	s3Config := createS3Config(d)
   583  
   584  	createInput := &firehose.CreateDeliveryStreamInput{
   585  		DeliveryStreamName: aws.String(sn),
   586  	}
   587  
   588  	if d.Get("destination").(string) == "s3" {
   589  		createInput.S3DestinationConfiguration = s3Config
   590  	} else if d.Get("destination").(string) == "elasticsearch" {
   591  		esConfig, err := createElasticsearchConfig(d, s3Config)
   592  		if err != nil {
   593  			return err
   594  		}
   595  		createInput.ElasticsearchDestinationConfiguration = esConfig
   596  	} else {
   597  		rc, err := createRedshiftConfig(d, s3Config)
   598  		if err != nil {
   599  			return err
   600  		}
   601  		createInput.RedshiftDestinationConfiguration = rc
   602  	}
   603  
   604  	var lastError error
   605  	err := resource.Retry(1*time.Minute, func() *resource.RetryError {
   606  		_, err := conn.CreateDeliveryStream(createInput)
   607  		if err != nil {
   608  			log.Printf("[DEBUG] Error creating Firehose Delivery Stream: %s", err)
   609  			lastError = err
   610  
   611  			if awsErr, ok := err.(awserr.Error); ok {
   612  				// IAM roles can take ~10 seconds to propagate in AWS:
   613  				// http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/iam-roles-for-amazon-ec2.html#launch-instance-with-role-console
   614  				if awsErr.Code() == "InvalidArgumentException" && strings.Contains(awsErr.Message(), "Firehose is unable to assume role") {
   615  					log.Printf("[DEBUG] Firehose could not assume role referenced, retrying...")
   616  					return resource.RetryableError(awsErr)
   617  				}
   618  			}
   619  			// Not retryable
   620  			return resource.NonRetryableError(err)
   621  		}
   622  
   623  		return nil
   624  	})
   625  	if err != nil {
   626  		if awsErr, ok := lastError.(awserr.Error); ok {
   627  			return fmt.Errorf("[WARN] Error creating Kinesis Firehose Delivery Stream: \"%s\", code: \"%s\"", awsErr.Message(), awsErr.Code())
   628  		}
   629  		return err
   630  	}
   631  
   632  	stateConf := &resource.StateChangeConf{
   633  		Pending:    []string{"CREATING"},
   634  		Target:     []string{"ACTIVE"},
   635  		Refresh:    firehoseStreamStateRefreshFunc(conn, sn),
   636  		Timeout:    20 * time.Minute,
   637  		Delay:      10 * time.Second,
   638  		MinTimeout: 3 * time.Second,
   639  	}
   640  
   641  	firehoseStream, err := stateConf.WaitForState()
   642  	if err != nil {
   643  		return fmt.Errorf(
   644  			"Error waiting for Kinesis Stream (%s) to become active: %s",
   645  			sn, err)
   646  	}
   647  
   648  	s := firehoseStream.(*firehose.DeliveryStreamDescription)
   649  	d.SetId(*s.DeliveryStreamARN)
   650  	d.Set("arn", s.DeliveryStreamARN)
   651  
   652  	return resourceAwsKinesisFirehoseDeliveryStreamRead(d, meta)
   653  }
   654  
   655  func resourceAwsKinesisFirehoseDeliveryStreamUpdate(d *schema.ResourceData, meta interface{}) error {
   656  	conn := meta.(*AWSClient).firehoseconn
   657  
   658  	sn := d.Get("name").(string)
   659  	s3Config := updateS3Config(d)
   660  
   661  	updateInput := &firehose.UpdateDestinationInput{
   662  		DeliveryStreamName:             aws.String(sn),
   663  		CurrentDeliveryStreamVersionId: aws.String(d.Get("version_id").(string)),
   664  		DestinationId:                  aws.String(d.Get("destination_id").(string)),
   665  	}
   666  
   667  	if d.Get("destination").(string) == "s3" {
   668  		updateInput.S3DestinationUpdate = s3Config
   669  	} else if d.Get("destination").(string) == "elasticsearch" {
   670  		esUpdate, err := updateElasticsearchConfig(d, s3Config)
   671  		if err != nil {
   672  			return err
   673  		}
   674  		updateInput.ElasticsearchDestinationUpdate = esUpdate
   675  	} else {
   676  		rc, err := updateRedshiftConfig(d, s3Config)
   677  		if err != nil {
   678  			return err
   679  		}
   680  		updateInput.RedshiftDestinationUpdate = rc
   681  	}
   682  
   683  	_, err := conn.UpdateDestination(updateInput)
   684  	if err != nil {
   685  		return fmt.Errorf(
   686  			"Error Updating Kinesis Firehose Delivery Stream: \"%s\"\n%s",
   687  			sn, err)
   688  	}
   689  
   690  	return resourceAwsKinesisFirehoseDeliveryStreamRead(d, meta)
   691  }
   692  
   693  func resourceAwsKinesisFirehoseDeliveryStreamRead(d *schema.ResourceData, meta interface{}) error {
   694  	conn := meta.(*AWSClient).firehoseconn
   695  
   696  	resp, err := conn.DescribeDeliveryStream(&firehose.DescribeDeliveryStreamInput{
   697  		DeliveryStreamName: aws.String(d.Get("name").(string)),
   698  	})
   699  
   700  	if err != nil {
   701  		if awsErr, ok := err.(awserr.Error); ok {
   702  			if awsErr.Code() == "ResourceNotFoundException" {
   703  				d.SetId("")
   704  				return nil
   705  			}
   706  			return fmt.Errorf("[WARN] Error reading Kinesis Firehose Delivery Stream: \"%s\", code: \"%s\"", awsErr.Message(), awsErr.Code())
   707  		}
   708  		return err
   709  	}
   710  
   711  	s := resp.DeliveryStreamDescription
   712  	d.Set("version_id", s.VersionId)
   713  	d.Set("arn", *s.DeliveryStreamARN)
   714  	if len(s.Destinations) > 0 {
   715  		destination := s.Destinations[0]
   716  		d.Set("destination_id", *destination.DestinationId)
   717  	}
   718  
   719  	return nil
   720  }
   721  
   722  func resourceAwsKinesisFirehoseDeliveryStreamDelete(d *schema.ResourceData, meta interface{}) error {
   723  	conn := meta.(*AWSClient).firehoseconn
   724  
   725  	sn := d.Get("name").(string)
   726  	_, err := conn.DeleteDeliveryStream(&firehose.DeleteDeliveryStreamInput{
   727  		DeliveryStreamName: aws.String(sn),
   728  	})
   729  
   730  	if err != nil {
   731  		return err
   732  	}
   733  
   734  	stateConf := &resource.StateChangeConf{
   735  		Pending:    []string{"DELETING"},
   736  		Target:     []string{"DESTROYED"},
   737  		Refresh:    firehoseStreamStateRefreshFunc(conn, sn),
   738  		Timeout:    20 * time.Minute,
   739  		Delay:      10 * time.Second,
   740  		MinTimeout: 3 * time.Second,
   741  	}
   742  
   743  	_, err = stateConf.WaitForState()
   744  	if err != nil {
   745  		return fmt.Errorf(
   746  			"Error waiting for Delivery Stream (%s) to be destroyed: %s",
   747  			sn, err)
   748  	}
   749  
   750  	d.SetId("")
   751  	return nil
   752  }
   753  
   754  func firehoseStreamStateRefreshFunc(conn *firehose.Firehose, sn string) resource.StateRefreshFunc {
   755  	return func() (interface{}, string, error) {
   756  		describeOpts := &firehose.DescribeDeliveryStreamInput{
   757  			DeliveryStreamName: aws.String(sn),
   758  		}
   759  		resp, err := conn.DescribeDeliveryStream(describeOpts)
   760  		if err != nil {
   761  			if awsErr, ok := err.(awserr.Error); ok {
   762  				if awsErr.Code() == "ResourceNotFoundException" {
   763  					return 42, "DESTROYED", nil
   764  				}
   765  				return nil, awsErr.Code(), err
   766  			}
   767  			return nil, "failed", err
   768  		}
   769  
   770  		return resp.DeliveryStreamDescription, *resp.DeliveryStreamDescription.DeliveryStreamStatus, nil
   771  	}
   772  }