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