github.com/vtorhonen/terraform@v0.9.0-beta2.0.20170307220345-5d894e4ffda7/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", s.shardCount)
   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", state.shardCount)
   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  	status            string
   294  	shardCount        int
   295  	retentionPeriod   int64
   296  	shardLevelMetrics []string
   297  }
   298  
   299  func readKinesisStreamState(conn *kinesis.Kinesis, sn string) (kinesisStreamState, error) {
   300  	describeOpts := &kinesis.DescribeStreamInput{
   301  		StreamName: aws.String(sn),
   302  	}
   303  
   304  	var state kinesisStreamState
   305  	err := conn.DescribeStreamPages(describeOpts, func(page *kinesis.DescribeStreamOutput, last bool) (shouldContinue bool) {
   306  		state.arn = aws.StringValue(page.StreamDescription.StreamARN)
   307  		state.status = aws.StringValue(page.StreamDescription.StreamStatus)
   308  		state.shardCount += len(openShards(page.StreamDescription.Shards))
   309  		state.retentionPeriod = aws.Int64Value(page.StreamDescription.RetentionPeriodHours)
   310  		state.shardLevelMetrics = flattenKinesisShardLevelMetrics(page.StreamDescription.EnhancedMonitoring)
   311  		return !last
   312  	})
   313  	return state, err
   314  }
   315  
   316  func streamStateRefreshFunc(conn *kinesis.Kinesis, sn string) resource.StateRefreshFunc {
   317  	return func() (interface{}, string, error) {
   318  		state, err := readKinesisStreamState(conn, sn)
   319  		if err != nil {
   320  			if awsErr, ok := err.(awserr.Error); ok {
   321  				if awsErr.Code() == "ResourceNotFoundException" {
   322  					return 42, "DESTROYED", nil
   323  				}
   324  				return nil, awsErr.Code(), err
   325  			}
   326  			return nil, "failed", err
   327  		}
   328  
   329  		return state, state.status, nil
   330  	}
   331  }
   332  
   333  func waitForKinesisToBeActive(conn *kinesis.Kinesis, sn string) error {
   334  	stateConf := &resource.StateChangeConf{
   335  		Pending:    []string{"UPDATING"},
   336  		Target:     []string{"ACTIVE"},
   337  		Refresh:    streamStateRefreshFunc(conn, sn),
   338  		Timeout:    5 * time.Minute,
   339  		Delay:      10 * time.Second,
   340  		MinTimeout: 3 * time.Second,
   341  	}
   342  
   343  	_, err := stateConf.WaitForState()
   344  	if err != nil {
   345  		return fmt.Errorf(
   346  			"Error waiting for Kinesis Stream (%s) to become active: %s",
   347  			sn, err)
   348  	}
   349  	return nil
   350  }
   351  
   352  // See http://docs.aws.amazon.com/kinesis/latest/dev/kinesis-using-sdk-java-resharding-merge.html
   353  func openShards(shards []*kinesis.Shard) []*kinesis.Shard {
   354  	var open []*kinesis.Shard
   355  	for _, s := range shards {
   356  		if s.SequenceNumberRange.EndingSequenceNumber == nil {
   357  			open = append(open, s)
   358  		}
   359  	}
   360  
   361  	return open
   362  }