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 }