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 }