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