github.com/bradfeehan/terraform@v0.7.0-rc3.0.20170529055808-34b45c5ad841/builtin/providers/aws/resource_aws_kinesis_stream.go (about)

     1  package aws
     2  
     3  import (
     4  	"fmt"
     5  	"log"
     6  	"time"
     7  
     8  	"github.com/aws/aws-sdk-go/aws"
     9  	"github.com/aws/aws-sdk-go/aws/awserr"
    10  	"github.com/aws/aws-sdk-go/service/kinesis"
    11  	"github.com/hashicorp/terraform/helper/resource"
    12  	"github.com/hashicorp/terraform/helper/schema"
    13  )
    14  
    15  func resourceAwsKinesisStream() *schema.Resource {
    16  	return &schema.Resource{
    17  		Create: resourceAwsKinesisStreamCreate,
    18  		Read:   resourceAwsKinesisStreamRead,
    19  		Update: resourceAwsKinesisStreamUpdate,
    20  		Delete: resourceAwsKinesisStreamDelete,
    21  		Importer: &schema.ResourceImporter{
    22  			State: resourceAwsKinesisStreamImport,
    23  		},
    24  
    25  		Schema: map[string]*schema.Schema{
    26  			"name": {
    27  				Type:     schema.TypeString,
    28  				Required: true,
    29  				ForceNew: true,
    30  			},
    31  
    32  			"shard_count": {
    33  				Type:     schema.TypeInt,
    34  				Required: true,
    35  				ForceNew: true,
    36  			},
    37  
    38  			"retention_period": {
    39  				Type:     schema.TypeInt,
    40  				Optional: true,
    41  				Default:  24,
    42  				ValidateFunc: func(v interface{}, k string) (ws []string, errors []error) {
    43  					value := v.(int)
    44  					if value < 24 || value > 168 {
    45  						errors = append(errors, fmt.Errorf(
    46  							"%q must be between 24 and 168 hours", k))
    47  					}
    48  					return
    49  				},
    50  			},
    51  
    52  			"shard_level_metrics": {
    53  				Type:     schema.TypeSet,
    54  				Optional: true,
    55  				Elem:     &schema.Schema{Type: schema.TypeString},
    56  				Set:      schema.HashString,
    57  			},
    58  
    59  			"arn": {
    60  				Type:     schema.TypeString,
    61  				Optional: true,
    62  				Computed: true,
    63  			},
    64  			"tags": tagsSchema(),
    65  		},
    66  	}
    67  }
    68  
    69  func resourceAwsKinesisStreamImport(
    70  	d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) {
    71  	d.Set("name", d.Id())
    72  	return []*schema.ResourceData{d}, nil
    73  }
    74  
    75  func resourceAwsKinesisStreamCreate(d *schema.ResourceData, meta interface{}) error {
    76  	conn := meta.(*AWSClient).kinesisconn
    77  	sn := d.Get("name").(string)
    78  	createOpts := &kinesis.CreateStreamInput{
    79  		ShardCount: aws.Int64(int64(d.Get("shard_count").(int))),
    80  		StreamName: aws.String(sn),
    81  	}
    82  
    83  	_, err := conn.CreateStream(createOpts)
    84  	if err != nil {
    85  		if awsErr, ok := err.(awserr.Error); ok {
    86  			return fmt.Errorf("[WARN] Error creating Kinesis Stream: \"%s\", code: \"%s\"", awsErr.Message(), awsErr.Code())
    87  		}
    88  		return err
    89  	}
    90  
    91  	stateConf := &resource.StateChangeConf{
    92  		Pending:    []string{"CREATING"},
    93  		Target:     []string{"ACTIVE"},
    94  		Refresh:    streamStateRefreshFunc(conn, sn),
    95  		Timeout:    5 * time.Minute,
    96  		Delay:      10 * time.Second,
    97  		MinTimeout: 3 * time.Second,
    98  	}
    99  
   100  	streamRaw, err := stateConf.WaitForState()
   101  	if err != nil {
   102  		return fmt.Errorf(
   103  			"Error waiting for Kinesis Stream (%s) to become active: %s",
   104  			sn, err)
   105  	}
   106  
   107  	s := streamRaw.(*kinesisStreamState)
   108  	d.SetId(s.arn)
   109  	d.Set("arn", s.arn)
   110  	d.Set("shard_count", len(s.openShards))
   111  
   112  	return resourceAwsKinesisStreamUpdate(d, meta)
   113  }
   114  
   115  func resourceAwsKinesisStreamUpdate(d *schema.ResourceData, meta interface{}) error {
   116  	conn := meta.(*AWSClient).kinesisconn
   117  
   118  	d.Partial(true)
   119  	if err := setTagsKinesis(conn, d); err != nil {
   120  		return err
   121  	}
   122  
   123  	d.SetPartial("tags")
   124  	d.Partial(false)
   125  
   126  	if err := setKinesisRetentionPeriod(conn, d); err != nil {
   127  		return err
   128  	}
   129  	if err := updateKinesisShardLevelMetrics(conn, d); err != nil {
   130  		return err
   131  	}
   132  
   133  	return resourceAwsKinesisStreamRead(d, meta)
   134  }
   135  
   136  func resourceAwsKinesisStreamRead(d *schema.ResourceData, meta interface{}) error {
   137  	conn := meta.(*AWSClient).kinesisconn
   138  	sn := d.Get("name").(string)
   139  
   140  	state, err := readKinesisStreamState(conn, sn)
   141  	if err != nil {
   142  		if awsErr, ok := err.(awserr.Error); ok {
   143  			if awsErr.Code() == "ResourceNotFoundException" {
   144  				d.SetId("")
   145  				return nil
   146  			}
   147  			return fmt.Errorf("[WARN] Error reading Kinesis Stream: \"%s\", code: \"%s\"", awsErr.Message(), awsErr.Code())
   148  		}
   149  		return err
   150  
   151  	}
   152  	d.SetId(state.arn)
   153  	d.Set("arn", state.arn)
   154  	d.Set("shard_count", len(state.openShards))
   155  	d.Set("retention_period", state.retentionPeriod)
   156  
   157  	if len(state.shardLevelMetrics) > 0 {
   158  		d.Set("shard_level_metrics", state.shardLevelMetrics)
   159  	}
   160  
   161  	// set tags
   162  	describeTagsOpts := &kinesis.ListTagsForStreamInput{
   163  		StreamName: aws.String(sn),
   164  	}
   165  	tagsResp, err := conn.ListTagsForStream(describeTagsOpts)
   166  	if err != nil {
   167  		log.Printf("[DEBUG] Error retrieving tags for Stream: %s. %s", sn, err)
   168  	} else {
   169  		d.Set("tags", tagsToMapKinesis(tagsResp.Tags))
   170  	}
   171  
   172  	return nil
   173  }
   174  
   175  func resourceAwsKinesisStreamDelete(d *schema.ResourceData, meta interface{}) error {
   176  	conn := meta.(*AWSClient).kinesisconn
   177  	sn := d.Get("name").(string)
   178  	_, err := conn.DeleteStream(&kinesis.DeleteStreamInput{
   179  		StreamName: aws.String(sn),
   180  	})
   181  
   182  	if err != nil {
   183  		return err
   184  	}
   185  
   186  	stateConf := &resource.StateChangeConf{
   187  		Pending:    []string{"DELETING"},
   188  		Target:     []string{"DESTROYED"},
   189  		Refresh:    streamStateRefreshFunc(conn, sn),
   190  		Timeout:    5 * time.Minute,
   191  		Delay:      10 * time.Second,
   192  		MinTimeout: 3 * time.Second,
   193  	}
   194  
   195  	_, err = stateConf.WaitForState()
   196  	if err != nil {
   197  		return fmt.Errorf(
   198  			"Error waiting for Stream (%s) to be destroyed: %s",
   199  			sn, err)
   200  	}
   201  
   202  	d.SetId("")
   203  	return nil
   204  }
   205  
   206  func setKinesisRetentionPeriod(conn *kinesis.Kinesis, d *schema.ResourceData) error {
   207  	sn := d.Get("name").(string)
   208  
   209  	oraw, nraw := d.GetChange("retention_period")
   210  	o := oraw.(int)
   211  	n := nraw.(int)
   212  
   213  	if n == 0 {
   214  		log.Printf("[DEBUG] Kinesis Stream (%q) Retention Period Not Changed", sn)
   215  		return nil
   216  	}
   217  
   218  	if n > o {
   219  		log.Printf("[DEBUG] Increasing %s Stream Retention Period to %d", sn, n)
   220  		_, err := conn.IncreaseStreamRetentionPeriod(&kinesis.IncreaseStreamRetentionPeriodInput{
   221  			StreamName:           aws.String(sn),
   222  			RetentionPeriodHours: aws.Int64(int64(n)),
   223  		})
   224  		if err != nil {
   225  			return err
   226  		}
   227  
   228  	} else {
   229  		log.Printf("[DEBUG] Decreasing %s Stream Retention Period to %d", sn, n)
   230  		_, err := conn.DecreaseStreamRetentionPeriod(&kinesis.DecreaseStreamRetentionPeriodInput{
   231  			StreamName:           aws.String(sn),
   232  			RetentionPeriodHours: aws.Int64(int64(n)),
   233  		})
   234  		if err != nil {
   235  			return err
   236  		}
   237  	}
   238  
   239  	if err := waitForKinesisToBeActive(conn, sn); err != nil {
   240  		return err
   241  	}
   242  
   243  	return nil
   244  }
   245  
   246  func updateKinesisShardLevelMetrics(conn *kinesis.Kinesis, d *schema.ResourceData) error {
   247  	sn := d.Get("name").(string)
   248  
   249  	o, n := d.GetChange("shard_level_metrics")
   250  	if o == nil {
   251  		o = new(schema.Set)
   252  	}
   253  	if n == nil {
   254  		n = new(schema.Set)
   255  	}
   256  
   257  	os := o.(*schema.Set)
   258  	ns := n.(*schema.Set)
   259  
   260  	disableMetrics := os.Difference(ns)
   261  	if disableMetrics.Len() != 0 {
   262  		metrics := disableMetrics.List()
   263  		log.Printf("[DEBUG] Disabling shard level metrics %v for stream %s", metrics, sn)
   264  
   265  		props := &kinesis.DisableEnhancedMonitoringInput{
   266  			StreamName:        aws.String(sn),
   267  			ShardLevelMetrics: expandStringList(metrics),
   268  		}
   269  
   270  		_, err := conn.DisableEnhancedMonitoring(props)
   271  		if err != nil {
   272  			return fmt.Errorf("Failure to disable shard level metrics for stream %s: %s", sn, err)
   273  		}
   274  		if err := waitForKinesisToBeActive(conn, sn); err != nil {
   275  			return err
   276  		}
   277  	}
   278  
   279  	enabledMetrics := ns.Difference(os)
   280  	if enabledMetrics.Len() != 0 {
   281  		metrics := enabledMetrics.List()
   282  		log.Printf("[DEBUG] Enabling shard level metrics %v for stream %s", metrics, sn)
   283  
   284  		props := &kinesis.EnableEnhancedMonitoringInput{
   285  			StreamName:        aws.String(sn),
   286  			ShardLevelMetrics: expandStringList(metrics),
   287  		}
   288  
   289  		_, err := conn.EnableEnhancedMonitoring(props)
   290  		if err != nil {
   291  			return fmt.Errorf("Failure to enable shard level metrics for stream %s: %s", sn, err)
   292  		}
   293  		if err := waitForKinesisToBeActive(conn, sn); err != nil {
   294  			return err
   295  		}
   296  	}
   297  
   298  	return nil
   299  }
   300  
   301  type kinesisStreamState struct {
   302  	arn               string
   303  	creationTimestamp int64
   304  	status            string
   305  	retentionPeriod   int64
   306  	openShards        []string
   307  	closedShards      []string
   308  	shardLevelMetrics []string
   309  }
   310  
   311  func readKinesisStreamState(conn *kinesis.Kinesis, sn string) (*kinesisStreamState, error) {
   312  	describeOpts := &kinesis.DescribeStreamInput{
   313  		StreamName: aws.String(sn),
   314  	}
   315  
   316  	state := &kinesisStreamState{}
   317  	err := conn.DescribeStreamPages(describeOpts, func(page *kinesis.DescribeStreamOutput, last bool) (shouldContinue bool) {
   318  		state.arn = aws.StringValue(page.StreamDescription.StreamARN)
   319  		state.creationTimestamp = aws.TimeValue(page.StreamDescription.StreamCreationTimestamp).Unix()
   320  		state.status = aws.StringValue(page.StreamDescription.StreamStatus)
   321  		state.retentionPeriod = aws.Int64Value(page.StreamDescription.RetentionPeriodHours)
   322  		state.openShards = append(state.openShards, flattenShards(openShards(page.StreamDescription.Shards))...)
   323  		state.closedShards = append(state.closedShards, flattenShards(closedShards(page.StreamDescription.Shards))...)
   324  		state.shardLevelMetrics = flattenKinesisShardLevelMetrics(page.StreamDescription.EnhancedMonitoring)
   325  		return !last
   326  	})
   327  	return state, err
   328  }
   329  
   330  func streamStateRefreshFunc(conn *kinesis.Kinesis, sn string) resource.StateRefreshFunc {
   331  	return func() (interface{}, string, error) {
   332  		state, err := readKinesisStreamState(conn, sn)
   333  		if err != nil {
   334  			if awsErr, ok := err.(awserr.Error); ok {
   335  				if awsErr.Code() == "ResourceNotFoundException" {
   336  					return 42, "DESTROYED", nil
   337  				}
   338  				return nil, awsErr.Code(), err
   339  			}
   340  			return nil, "failed", err
   341  		}
   342  
   343  		return state, state.status, nil
   344  	}
   345  }
   346  
   347  func waitForKinesisToBeActive(conn *kinesis.Kinesis, sn string) error {
   348  	stateConf := &resource.StateChangeConf{
   349  		Pending:    []string{"UPDATING"},
   350  		Target:     []string{"ACTIVE"},
   351  		Refresh:    streamStateRefreshFunc(conn, sn),
   352  		Timeout:    5 * time.Minute,
   353  		Delay:      10 * time.Second,
   354  		MinTimeout: 3 * time.Second,
   355  	}
   356  
   357  	_, err := stateConf.WaitForState()
   358  	if err != nil {
   359  		return fmt.Errorf(
   360  			"Error waiting for Kinesis Stream (%s) to become active: %s",
   361  			sn, err)
   362  	}
   363  	return nil
   364  }
   365  
   366  func openShards(shards []*kinesis.Shard) []*kinesis.Shard {
   367  	return filterShards(shards, true)
   368  }
   369  
   370  func closedShards(shards []*kinesis.Shard) []*kinesis.Shard {
   371  	return filterShards(shards, false)
   372  }
   373  
   374  // See http://docs.aws.amazon.com/kinesis/latest/dev/kinesis-using-sdk-java-resharding-merge.html
   375  func filterShards(shards []*kinesis.Shard, open bool) []*kinesis.Shard {
   376  	res := make([]*kinesis.Shard, 0, len(shards))
   377  	for _, s := range shards {
   378  		if open && s.SequenceNumberRange.EndingSequenceNumber == nil {
   379  			res = append(res, s)
   380  		} else if !open && s.SequenceNumberRange.EndingSequenceNumber != nil {
   381  			res = append(res, s)
   382  		}
   383  	}
   384  	return res
   385  }
   386  
   387  func flattenShards(shards []*kinesis.Shard) []string {
   388  	res := make([]string, len(shards))
   389  	for i, s := range shards {
   390  		res[i] = aws.StringValue(s.ShardId)
   391  	}
   392  	return res
   393  }