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 }