Merge branch 'master' into jszwedko-fix-sg

* master: (23 commits)
  typo
  Update CHANGELOG.md
  provider/aws: Add docs for autoscaling_policy + cloudwatch_metric_alarm
  provider/aws: Add autoscaling_policy
  provider/aws: Add cloudwatch_metric_alarm
  Update CHANGELOG.md
  Update CHANGELOG.md
  provider/template: don't error when rendering fails in Exists
  Update CHANGELOG.md
  Added Azure SQL server and service support.
  Update CHANGELOG.md
  docs: clarify wording around destroy/apply args
  Getting Started: Added a Next Step upon finishing install.
  docs: add description of archive format to download page
  docs: snapshot plugin dependencies when releasing
  add v0.5.3 transitory deps
  Fixes support for changing just the read / write capacity of a GSI
  Change sleep time for DynamoDB table waits from 3 seconds to 5 seconds
  Remove request for attribute changes
  Fix AWS SDK imports
  ...
This commit is contained in:
Clint Shryock 2015-06-19 11:31:41 -05:00
commit a3cbb74a2e
32 changed files with 2997 additions and 20 deletions

View File

@ -2,8 +2,11 @@
FEATURES:
* **New provider: `azure`** [GH-2053]
* **New provider: `azure`** [GH-2052, GH-2053, GH-2372, GH-2380]
* **New resource: `aws_autoscaling_notification`** [GH-2197]
* **New resource: `aws_autoscaling_policy`** [GH-2201]
* **New resource: `aws_cloudwatch_metric_alarm`** [GH-2201]
* **New resource: `aws_dynamodb_table`** [GH-2121]
* **New resource: `aws_ecs_cluster`** [GH-1803]
* **New resource: `aws_ecs_service`** [GH-1803]
* **New resource: `aws_ecs_task_definition`** [GH-1803]
@ -14,7 +17,7 @@ FEATURES:
* **New remote state backend: `swift`**: You can now store remote state in
a OpenStack Swift. [GH-2254]
* command/output: support display of module outputs [GH-2102]
* core: keys() and values() funcs for map variables [GH-2198]
* core: `keys()` and `values()` funcs for map variables [GH-2198]
IMPROVEMENTS:
@ -40,6 +43,8 @@ BUG FIXES:
when non-lowercase protocol strings were used [GH-2246]
* provider/aws: corrected frankfurt S3 website region [GH-2259]
* provider/aws: `aws_elasticache_cluster` port is required [GH-2160]
* provider/template: fix issue causing "unknown variable" rendering errors
when an existing set of template variables is changed [GH-2386]
## 0.5.3 (June 1, 2015)

View File

@ -30,7 +30,7 @@ export VERSION="vX.Y.Z"
# Edit version.go, setting VersionPrelease to empty string
# Snapshot dependency information
godep save
godep save ./...
mv Godeps/Godeps.json deps/$(echo $VERSION | sed 's/\./-/g').json
rm -rf Godeps

View File

@ -10,6 +10,8 @@ import (
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/credentials"
"github.com/aws/aws-sdk-go/service/autoscaling"
"github.com/aws/aws-sdk-go/service/cloudwatch"
"github.com/aws/aws-sdk-go/service/dynamodb"
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/aws/aws-sdk-go/service/ecs"
"github.com/aws/aws-sdk-go/service/elasticache"
@ -36,6 +38,8 @@ type Config struct {
}
type AWSClient struct {
cloudwatchconn *cloudwatch.CloudWatch
dynamodbconn *dynamodb.DynamoDB
ec2conn *ec2.EC2
ecsconn *ecs.ECS
elbconn *elb.ELB
@ -88,6 +92,9 @@ func (c *Config) Client() (interface{}, error) {
MaxRetries: c.MaxRetries,
}
log.Println("[INFO] Initializing DynamoDB connection")
client.dynamodbconn = dynamodb.New(awsConfig)
log.Println("[INFO] Initializing ELB connection")
client.elbconn = elb.New(awsConfig)
@ -138,6 +145,9 @@ func (c *Config) Client() (interface{}, error) {
log.Println("[INFO] Initializing Lambda Connection")
client.lambdaconn = lambda.New(awsConfig)
log.Println("[INFO] Initializing CloudWatch SDK connection")
client.cloudwatchconn = cloudwatch.New(awsConfig)
}
if len(errs) > 0 {

View File

@ -86,11 +86,14 @@ func Provider() terraform.ResourceProvider {
"aws_app_cookie_stickiness_policy": resourceAwsAppCookieStickinessPolicy(),
"aws_autoscaling_group": resourceAwsAutoscalingGroup(),
"aws_autoscaling_notification": resourceAwsAutoscalingNotification(),
"aws_autoscaling_policy": resourceAwsAutoscalingPolicy(),
"aws_cloudwatch_metric_alarm": resourceAwsCloudWatchMetricAlarm(),
"aws_customer_gateway": resourceAwsCustomerGateway(),
"aws_db_instance": resourceAwsDbInstance(),
"aws_db_parameter_group": resourceAwsDbParameterGroup(),
"aws_db_security_group": resourceAwsDbSecurityGroup(),
"aws_db_subnet_group": resourceAwsDbSubnetGroup(),
"aws_dynamodb_table": resourceAwsDynamoDbTable(),
"aws_ebs_volume": resourceAwsEbsVolume(),
"aws_ecs_cluster": resourceAwsEcsCluster(),
"aws_ecs_service": resourceAwsEcsService(),

View File

@ -408,7 +408,7 @@ func waitForASGCapacity(d *schema.ResourceData, meta interface{}) error {
}
wantELB := d.Get("min_elb_capacity").(int)
log.Printf("[DEBUG] Wanting for capacity: %d ASG, %d ELB", wantASG, wantELB)
log.Printf("[DEBUG] Waiting for capacity: %d ASG, %d ELB", wantASG, wantELB)
return resource.Retry(waitForASGCapacityTimeout, func() error {
g, err := getAwsAutoscalingGroup(d, meta)

View File

@ -0,0 +1,181 @@
package aws
import (
"fmt"
"log"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/autoscaling"
"github.com/hashicorp/terraform/helper/schema"
)
func resourceAwsAutoscalingPolicy() *schema.Resource {
return &schema.Resource{
Create: resourceAwsAutoscalingPolicyCreate,
Read: resourceAwsAutoscalingPolicyRead,
Update: resourceAwsAutoscalingPolicyUpdate,
Delete: resourceAwsAutoscalingPolicyDelete,
Schema: map[string]*schema.Schema{
"arn": &schema.Schema{
Type: schema.TypeString,
Computed: true,
},
"name": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"adjustment_type": &schema.Schema{
Type: schema.TypeString,
Required: true,
},
"autoscaling_group_name": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"cooldown": &schema.Schema{
Type: schema.TypeInt,
Optional: true,
},
"min_adjustment_step": &schema.Schema{
Type: schema.TypeInt,
Optional: true,
},
"scaling_adjustment": &schema.Schema{
Type: schema.TypeInt,
Required: true,
},
},
}
}
func resourceAwsAutoscalingPolicyCreate(d *schema.ResourceData, meta interface{}) error {
autoscalingconn := meta.(*AWSClient).autoscalingconn
params := getAwsAutoscalingPutScalingPolicyInput(d)
log.Printf("[DEBUG] AutoScaling PutScalingPolicy: %#v", params)
resp, err := autoscalingconn.PutScalingPolicy(&params)
if err != nil {
return fmt.Errorf("Error putting scaling policy: %s", err)
}
d.Set("arn", resp.PolicyARN)
d.SetId(d.Get("name").(string))
log.Printf("[INFO] AutoScaling Scaling PolicyARN: %s", d.Get("arn").(string))
return resourceAwsAutoscalingPolicyRead(d, meta)
}
func resourceAwsAutoscalingPolicyRead(d *schema.ResourceData, meta interface{}) error {
p, err := getAwsAutoscalingPolicy(d, meta)
if err != nil {
return err
}
if p == nil {
d.SetId("")
return nil
}
log.Printf("[DEBUG] Read Scaling Policy: ASG: %s, SP: %s, Obj: %#v", d.Get("autoscaling_group_name"), d.Get("name"), p)
d.Set("adjustment_type", p.AdjustmentType)
d.Set("autoscaling_group_name", p.AutoScalingGroupName)
d.Set("cooldown", p.Cooldown)
d.Set("min_adjustment_step", p.MinAdjustmentStep)
d.Set("arn", p.PolicyARN)
d.Set("name", p.PolicyName)
d.Set("scaling_adjustment", p.ScalingAdjustment)
return nil
}
func resourceAwsAutoscalingPolicyUpdate(d *schema.ResourceData, meta interface{}) error {
autoscalingconn := meta.(*AWSClient).autoscalingconn
params := getAwsAutoscalingPutScalingPolicyInput(d)
log.Printf("[DEBUG] Autoscaling Update Scaling Policy: %#v", params)
_, err := autoscalingconn.PutScalingPolicy(&params)
if err != nil {
return err
}
return resourceAwsAutoscalingPolicyRead(d, meta)
}
func resourceAwsAutoscalingPolicyDelete(d *schema.ResourceData, meta interface{}) error {
autoscalingconn := meta.(*AWSClient).autoscalingconn
p, err := getAwsAutoscalingPolicy(d, meta)
if err != nil {
return err
}
if p == nil {
return nil
}
params := autoscaling.DeletePolicyInput{
AutoScalingGroupName: aws.String(d.Get("autoscaling_group_name").(string)),
PolicyName: aws.String(d.Get("name").(string)),
}
if _, err := autoscalingconn.DeletePolicy(&params); err != nil {
return fmt.Errorf("Autoscaling Scaling Policy: %s ", err)
}
d.SetId("")
return nil
}
// PutScalingPolicy seems to require all params to be resent, so create and update can share this common function
func getAwsAutoscalingPutScalingPolicyInput(d *schema.ResourceData) autoscaling.PutScalingPolicyInput {
var params = autoscaling.PutScalingPolicyInput{
AutoScalingGroupName: aws.String(d.Get("autoscaling_group_name").(string)),
PolicyName: aws.String(d.Get("name").(string)),
}
if v, ok := d.GetOk("adjustment_type"); ok {
params.AdjustmentType = aws.String(v.(string))
}
if v, ok := d.GetOk("cooldown"); ok {
params.Cooldown = aws.Long(int64(v.(int)))
}
if v, ok := d.GetOk("scaling_adjustment"); ok {
params.ScalingAdjustment = aws.Long(int64(v.(int)))
}
if v, ok := d.GetOk("min_adjustment_step"); ok {
params.MinAdjustmentStep = aws.Long(int64(v.(int)))
}
return params
}
func getAwsAutoscalingPolicy(d *schema.ResourceData, meta interface{}) (*autoscaling.ScalingPolicy, error) {
autoscalingconn := meta.(*AWSClient).autoscalingconn
params := autoscaling.DescribePoliciesInput{
AutoScalingGroupName: aws.String(d.Get("autoscaling_group_name").(string)),
PolicyNames: []*string{aws.String(d.Get("name").(string))},
}
log.Printf("[DEBUG] AutoScaling Scaling Policy Describe Params: %#v", params)
resp, err := autoscalingconn.DescribePolicies(&params)
if err != nil {
return nil, fmt.Errorf("Error retrieving scaling policies: %s", err)
}
// find scaling policy
name := d.Get("name")
for idx, sp := range resp.ScalingPolicies {
if *sp.PolicyName == name {
return resp.ScalingPolicies[idx], nil
}
}
// policy not found
return nil, nil
}

View File

@ -0,0 +1,115 @@
package aws
import (
"fmt"
"testing"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/autoscaling"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform"
)
func TestAccAWSAutoscalingPolicy_basic(t *testing.T) {
var policy autoscaling.ScalingPolicy
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckAWSAutoscalingPolicyDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccAWSAutoscalingPolicyConfig,
Check: resource.ComposeTestCheckFunc(
testAccCheckScalingPolicyExists("aws_autoscaling_policy.foobar", &policy),
resource.TestCheckResourceAttr("aws_autoscaling_policy.foobar", "adjustment_type", "ChangeInCapacity"),
resource.TestCheckResourceAttr("aws_autoscaling_policy.foobar", "cooldown", "300"),
),
},
},
})
}
func testAccCheckScalingPolicyExists(n string, policy *autoscaling.ScalingPolicy) resource.TestCheckFunc {
return func(s *terraform.State) error {
rs, ok := s.RootModule().Resources[n]
if !ok {
rs = rs
return fmt.Errorf("Not found: %s", n)
}
conn := testAccProvider.Meta().(*AWSClient).autoscalingconn
params := &autoscaling.DescribePoliciesInput{
AutoScalingGroupName: aws.String(rs.Primary.Attributes["autoscaling_group_name"]),
PolicyNames: []*string{aws.String(rs.Primary.ID)},
}
resp, err := conn.DescribePolicies(params)
if err != nil {
return err
}
if len(resp.ScalingPolicies) == 0 {
return fmt.Errorf("ScalingPolicy not found")
}
return nil
}
}
func testAccCheckAWSAutoscalingPolicyDestroy(s *terraform.State) error {
conn := testAccProvider.Meta().(*AWSClient).autoscalingconn
for _, rs := range s.RootModule().Resources {
if rs.Type != "aws_autoscaling_group" {
continue
}
params := autoscaling.DescribePoliciesInput{
AutoScalingGroupName: aws.String(rs.Primary.Attributes["autoscaling_group_name"]),
PolicyNames: []*string{aws.String(rs.Primary.ID)},
}
resp, err := conn.DescribePolicies(&params)
if err == nil {
if len(resp.ScalingPolicies) != 0 &&
*resp.ScalingPolicies[0].PolicyName == rs.Primary.ID {
return fmt.Errorf("Scaling Policy Still Exists: %s", rs.Primary.ID)
}
}
}
return nil
}
var testAccAWSAutoscalingPolicyConfig = fmt.Sprintf(`
resource "aws_launch_configuration" "foobar" {
name = "terraform-test-foobar5"
image_id = "ami-21f78e11"
instance_type = "t1.micro"
}
resource "aws_autoscaling_group" "foobar" {
availability_zones = ["us-west-2a"]
name = "terraform-test-foobar5"
max_size = 5
min_size = 2
health_check_grace_period = 300
health_check_type = "ELB"
force_delete = true
termination_policies = ["OldestInstance"]
launch_configuration = "${aws_launch_configuration.foobar.name}"
tag {
key = "Foo"
value = "foo-bar"
propagate_at_launch = true
}
}
resource "aws_autoscaling_policy" "foobar" {
name = "foobar"
scaling_adjustment = 4
adjustment_type = "ChangeInCapacity"
cooldown = 300
autoscaling_group_name = "${aws_autoscaling_group.foobar.name}"
}
`)

View File

@ -0,0 +1,288 @@
package aws
import (
"fmt"
"log"
"github.com/hashicorp/terraform/helper/hashcode"
"github.com/hashicorp/terraform/helper/schema"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/cloudwatch"
)
func resourceAwsCloudWatchMetricAlarm() *schema.Resource {
return &schema.Resource{
Create: resourceAwsCloudWatchMetricAlarmCreate,
Read: resourceAwsCloudWatchMetricAlarmRead,
Update: resourceAwsCloudWatchMetricAlarmUpdate,
Delete: resourceAwsCloudWatchMetricAlarmDelete,
Schema: map[string]*schema.Schema{
"alarm_name": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"comparison_operator": &schema.Schema{
Type: schema.TypeString,
Required: true,
},
"evaluation_periods": &schema.Schema{
Type: schema.TypeInt,
Required: true,
},
"metric_name": &schema.Schema{
Type: schema.TypeString,
Required: true,
},
"namespace": &schema.Schema{
Type: schema.TypeString,
Required: true,
},
"period": &schema.Schema{
Type: schema.TypeInt,
Required: true,
},
"statistic": &schema.Schema{
Type: schema.TypeString,
Required: true,
},
"threshold": &schema.Schema{
Type: schema.TypeFloat,
Required: true,
},
"actions_enabled": &schema.Schema{
Type: schema.TypeBool,
Optional: true,
Default: true,
},
"alarm_actions": &schema.Schema{
Type: schema.TypeSet,
Optional: true,
Elem: &schema.Schema{Type: schema.TypeString},
Set: func(v interface{}) int {
return hashcode.String(v.(string))
},
},
"alarm_description": &schema.Schema{
Type: schema.TypeString,
Optional: true,
},
"dimensions": &schema.Schema{
Type: schema.TypeMap,
Optional: true,
},
"insufficient_data_actions": &schema.Schema{
Type: schema.TypeSet,
Optional: true,
Elem: &schema.Schema{Type: schema.TypeString},
Set: func(v interface{}) int {
return hashcode.String(v.(string))
},
},
"ok_actions": &schema.Schema{
Type: schema.TypeSet,
Optional: true,
Elem: &schema.Schema{Type: schema.TypeString},
Set: func(v interface{}) int {
return hashcode.String(v.(string))
},
},
"unit": &schema.Schema{
Type: schema.TypeString,
Optional: true,
},
},
}
}
func resourceAwsCloudWatchMetricAlarmCreate(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*AWSClient).cloudwatchconn
params := getAwsCloudWatchPutMetricAlarmInput(d)
log.Printf("[DEBUG] Creating CloudWatch Metric Alarm: %#v", params)
_, err := conn.PutMetricAlarm(&params)
if err != nil {
return fmt.Errorf("Creating metric alarm failed: %s", err)
}
d.SetId(d.Get("alarm_name").(string))
log.Println("[INFO] CloudWatch Metric Alarm created")
return resourceAwsCloudWatchMetricAlarmRead(d, meta)
}
func resourceAwsCloudWatchMetricAlarmRead(d *schema.ResourceData, meta interface{}) error {
a, err := getAwsCloudWatchMetricAlarm(d, meta)
if err != nil {
return err
}
if a == nil {
d.SetId("")
return nil
}
log.Printf("[DEBUG] Reading CloudWatch Metric Alarm: %s", d.Get("alarm_name"))
d.Set("actions_enabled", a.ActionsEnabled)
if err := d.Set("alarm_actions", _strArrPtrToList(a.AlarmActions)); err != nil {
log.Printf("[WARN] Error setting Alarm Actions: %s", err)
}
d.Set("alarm_description", a.AlarmDescription)
d.Set("alarm_name", a.AlarmName)
d.Set("comparison_operator", a.ComparisonOperator)
d.Set("dimensions", a.Dimensions)
d.Set("evaluation_periods", a.EvaluationPeriods)
if err := d.Set("insufficient_data_actions", _strArrPtrToList(a.InsufficientDataActions)); err != nil {
log.Printf("[WARN] Error setting Insufficient Data Actions: %s", err)
}
d.Set("metric_name", a.MetricName)
d.Set("namespace", a.Namespace)
if err := d.Set("ok_actions", _strArrPtrToList(a.OKActions)); err != nil {
log.Printf("[WARN] Error setting OK Actions: %s", err)
}
d.Set("period", a.Period)
d.Set("statistic", a.Statistic)
d.Set("threshold", a.Threshold)
d.Set("unit", a.Unit)
return nil
}
func resourceAwsCloudWatchMetricAlarmUpdate(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*AWSClient).cloudwatchconn
params := getAwsCloudWatchPutMetricAlarmInput(d)
log.Printf("[DEBUG] Updating CloudWatch Metric Alarm: %#v", params)
_, err := conn.PutMetricAlarm(&params)
if err != nil {
return fmt.Errorf("Updating metric alarm failed: %s", err)
}
log.Println("[INFO] CloudWatch Metric Alarm updated")
return resourceAwsCloudWatchMetricAlarmRead(d, meta)
}
func resourceAwsCloudWatchMetricAlarmDelete(d *schema.ResourceData, meta interface{}) error {
p, err := getAwsCloudWatchMetricAlarm(d, meta)
if err != nil {
return err
}
if p == nil {
log.Printf("[DEBUG] CloudWatch Metric Alarm %s is already gone", d.Id())
return nil
}
log.Printf("[INFO] Deleting CloudWatch Metric Alarm: %s", d.Id())
conn := meta.(*AWSClient).cloudwatchconn
params := cloudwatch.DeleteAlarmsInput{
AlarmNames: []*string{aws.String(d.Id())},
}
if _, err := conn.DeleteAlarms(&params); err != nil {
return fmt.Errorf("Error deleting CloudWatch Metric Alarm: %s", err)
}
log.Println("[INFO] CloudWatch Metric Alarm deleted")
d.SetId("")
return nil
}
func getAwsCloudWatchPutMetricAlarmInput(d *schema.ResourceData) cloudwatch.PutMetricAlarmInput {
params := cloudwatch.PutMetricAlarmInput{
AlarmName: aws.String(d.Get("alarm_name").(string)),
ComparisonOperator: aws.String(d.Get("comparison_operator").(string)),
EvaluationPeriods: aws.Long(int64(d.Get("evaluation_periods").(int))),
MetricName: aws.String(d.Get("metric_name").(string)),
Namespace: aws.String(d.Get("namespace").(string)),
Period: aws.Long(int64(d.Get("period").(int))),
Statistic: aws.String(d.Get("statistic").(string)),
Threshold: aws.Double(d.Get("threshold").(float64)),
}
if v := d.Get("actions_enabled"); v != nil {
params.ActionsEnabled = aws.Boolean(v.(bool))
}
if v, ok := d.GetOk("alarm_description"); ok {
params.AlarmDescription = aws.String(v.(string))
}
if v, ok := d.GetOk("unit"); ok {
params.Unit = aws.String(v.(string))
}
var alarmActions []*string
if v := d.Get("alarm_actions"); v != nil {
for _, v := range v.(*schema.Set).List() {
str := v.(string)
alarmActions = append(alarmActions, aws.String(str))
}
params.AlarmActions = alarmActions
}
var insufficientDataActions []*string
if v := d.Get("insufficient_data_actions"); v != nil {
for _, v := range v.(*schema.Set).List() {
str := v.(string)
insufficientDataActions = append(insufficientDataActions, aws.String(str))
}
params.InsufficientDataActions = insufficientDataActions
}
var okActions []*string
if v := d.Get("ok_actions"); v != nil {
for _, v := range v.(*schema.Set).List() {
str := v.(string)
okActions = append(okActions, aws.String(str))
}
params.OKActions = okActions
}
a := d.Get("dimensions").(map[string]interface{})
dimensions := make([]*cloudwatch.Dimension, 0, len(a))
for k, v := range a {
dimensions = append(dimensions, &cloudwatch.Dimension{
Name: aws.String(k),
Value: aws.String(v.(string)),
})
}
params.Dimensions = dimensions
return params
}
func getAwsCloudWatchMetricAlarm(d *schema.ResourceData, meta interface{}) (*cloudwatch.MetricAlarm, error) {
conn := meta.(*AWSClient).cloudwatchconn
params := cloudwatch.DescribeAlarmsInput{
AlarmNames: []*string{aws.String(d.Id())},
}
resp, err := conn.DescribeAlarms(&params)
if err != nil {
return nil, nil
}
// Find it and return it
for idx, ma := range resp.MetricAlarms {
if *ma.AlarmName == d.Id() {
return resp.MetricAlarms[idx], nil
}
}
return nil, nil
}
func _strArrPtrToList(strArrPtr []*string) []string {
var result []string
for _, elem := range strArrPtr {
result = append(result, *elem)
}
return result
}

View File

@ -0,0 +1,95 @@
package aws
import (
"fmt"
"testing"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/cloudwatch"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform"
)
func TestAccAWSCloudWatchMetricAlarm_basic(t *testing.T) {
var alarm cloudwatch.MetricAlarm
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckAWSCloudWatchMetricAlarmDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccAWSCloudWatchMetricAlarmConfig,
Check: resource.ComposeTestCheckFunc(
testAccCheckCloudWatchMetricAlarmExists("aws_cloudwatch_metric_alarm.foobar", &alarm),
resource.TestCheckResourceAttr("aws_cloudwatch_metric_alarm.foobar", "metric_name", "CPUUtilization"),
resource.TestCheckResourceAttr("aws_cloudwatch_metric_alarm.foobar", "statistic", "Average"),
),
},
},
})
}
func testAccCheckCloudWatchMetricAlarmExists(n string, alarm *cloudwatch.MetricAlarm) resource.TestCheckFunc {
return func(s *terraform.State) error {
rs, ok := s.RootModule().Resources[n]
if !ok {
return fmt.Errorf("Not found: %s", n)
}
conn := testAccProvider.Meta().(*AWSClient).cloudwatchconn
params := cloudwatch.DescribeAlarmsInput{
AlarmNames: []*string{aws.String(rs.Primary.ID)},
}
resp, err := conn.DescribeAlarms(&params)
if err != nil {
return err
}
if len(resp.MetricAlarms) == 0 {
return fmt.Errorf("Alarm not found")
}
*alarm = *resp.MetricAlarms[0]
return nil
}
}
func testAccCheckAWSCloudWatchMetricAlarmDestroy(s *terraform.State) error {
conn := testAccProvider.Meta().(*AWSClient).cloudwatchconn
for _, rs := range s.RootModule().Resources {
if rs.Type != "aws_cloudwatch_metric_alarm" {
continue
}
params := cloudwatch.DescribeAlarmsInput{
AlarmNames: []*string{aws.String(rs.Primary.ID)},
}
resp, err := conn.DescribeAlarms(&params)
if err == nil {
if len(resp.MetricAlarms) != 0 &&
*resp.MetricAlarms[0].AlarmName == rs.Primary.ID {
return fmt.Errorf("Alarm Still Exists: %s", rs.Primary.ID)
}
}
}
return nil
}
var testAccAWSCloudWatchMetricAlarmConfig = fmt.Sprintf(`
resource "aws_cloudwatch_metric_alarm" "foobar" {
alarm_name = "terraform-test-foobar5"
comparison_operator = "GreaterThanOrEqualToThreshold"
evaluation_periods = "2"
metric_name = "CPUUtilization"
namespace = "AWS/EC2"
period = "120"
statistic = "Average"
threshold = "80"
alarm_description = "This metric monitor ec2 cpu utilization"
insufficient_data_actions = []
}
`)

View File

@ -0,0 +1,704 @@
package aws
import (
"bytes"
"fmt"
"log"
"time"
"github.com/hashicorp/terraform/helper/schema"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/dynamodb"
"github.com/hashicorp/terraform/helper/hashcode"
)
// A number of these are marked as computed because if you don't
// provide a value, DynamoDB will provide you with defaults (which are the
// default values specified below)
func resourceAwsDynamoDbTable() *schema.Resource {
return &schema.Resource{
Create: resourceAwsDynamoDbTableCreate,
Read: resourceAwsDynamoDbTableRead,
Update: resourceAwsDynamoDbTableUpdate,
Delete: resourceAwsDynamoDbTableDelete,
Schema: map[string]*schema.Schema{
"name": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"hash_key": &schema.Schema{
Type: schema.TypeString,
Required: true,
},
"range_key": &schema.Schema{
Type: schema.TypeString,
Optional: true,
},
"write_capacity": &schema.Schema{
Type: schema.TypeInt,
Required: true,
},
"read_capacity": &schema.Schema{
Type: schema.TypeInt,
Required: true,
},
"attribute": &schema.Schema{
Type: schema.TypeSet,
Required: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"name": &schema.Schema{
Type: schema.TypeString,
Required: true,
},
"type": &schema.Schema{
Type: schema.TypeString,
Required: true,
},
},
},
Set: func(v interface{}) int {
var buf bytes.Buffer
m := v.(map[string]interface{})
buf.WriteString(fmt.Sprintf("%s-", m["name"].(string)))
return hashcode.String(buf.String())
},
},
"local_secondary_index": &schema.Schema{
Type: schema.TypeSet,
Optional: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"name": &schema.Schema{
Type: schema.TypeString,
Required: true,
},
"range_key": &schema.Schema{
Type: schema.TypeString,
Required: true,
},
"projection_type": &schema.Schema{
Type: schema.TypeString,
Required: true,
},
"non_key_attributes": &schema.Schema{
Type: schema.TypeList,
Optional: true,
Elem: &schema.Schema{Type: schema.TypeString},
},
},
},
Set: func(v interface{}) int {
var buf bytes.Buffer
m := v.(map[string]interface{})
buf.WriteString(fmt.Sprintf("%s-", m["name"].(string)))
return hashcode.String(buf.String())
},
},
"global_secondary_index": &schema.Schema{
Type: schema.TypeSet,
Optional: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"name": &schema.Schema{
Type: schema.TypeString,
Required: true,
},
"write_capacity": &schema.Schema{
Type: schema.TypeInt,
Required: true,
},
"read_capacity": &schema.Schema{
Type: schema.TypeInt,
Required: true,
},
"hash_key": &schema.Schema{
Type: schema.TypeString,
Required: true,
},
"range_key": &schema.Schema{
Type: schema.TypeString,
Optional: true,
},
"projection_type": &schema.Schema{
Type: schema.TypeString,
Required: true,
},
"non_key_attributes": &schema.Schema{
Type: schema.TypeList,
Optional: true,
Elem: &schema.Schema{Type: schema.TypeString},
},
},
},
// GSI names are the uniqueness constraint
Set: func(v interface{}) int {
var buf bytes.Buffer
m := v.(map[string]interface{})
buf.WriteString(fmt.Sprintf("%s-", m["name"].(string)))
buf.WriteString(fmt.Sprintf("%d-", m["write_capacity"].(int)))
buf.WriteString(fmt.Sprintf("%d-", m["read_capacity"].(int)))
return hashcode.String(buf.String())
},
},
},
}
}
func resourceAwsDynamoDbTableCreate(d *schema.ResourceData, meta interface{}) error {
dynamodbconn := meta.(*AWSClient).dynamodbconn
name := d.Get("name").(string)
log.Printf("[DEBUG] DynamoDB table create: %s", name)
throughput := &dynamodb.ProvisionedThroughput{
ReadCapacityUnits: aws.Long(int64(d.Get("read_capacity").(int))),
WriteCapacityUnits: aws.Long(int64(d.Get("write_capacity").(int))),
}
hash_key_name := d.Get("hash_key").(string)
keyschema := []*dynamodb.KeySchemaElement{
&dynamodb.KeySchemaElement{
AttributeName: aws.String(hash_key_name),
KeyType: aws.String("HASH"),
},
}
if range_key, ok := d.GetOk("range_key"); ok {
range_schema_element := &dynamodb.KeySchemaElement{
AttributeName: aws.String(range_key.(string)),
KeyType: aws.String("RANGE"),
}
keyschema = append(keyschema, range_schema_element)
}
req := &dynamodb.CreateTableInput{
TableName: aws.String(name),
ProvisionedThroughput: throughput,
KeySchema: keyschema,
}
if attributedata, ok := d.GetOk("attribute"); ok {
attributes := []*dynamodb.AttributeDefinition{}
attributeSet := attributedata.(*schema.Set)
for _, attribute := range attributeSet.List() {
attr := attribute.(map[string]interface{})
attributes = append(attributes, &dynamodb.AttributeDefinition{
AttributeName: aws.String(attr["name"].(string)),
AttributeType: aws.String(attr["type"].(string)),
})
}
req.AttributeDefinitions = attributes
}
if lsidata, ok := d.GetOk("local_secondary_index"); ok {
fmt.Printf("[DEBUG] Adding LSI data to the table")
lsiSet := lsidata.(*schema.Set)
localSecondaryIndexes := []*dynamodb.LocalSecondaryIndex{}
for _, lsiObject := range lsiSet.List() {
lsi := lsiObject.(map[string]interface{})
projection := &dynamodb.Projection{
ProjectionType: aws.String(lsi["projection_type"].(string)),
}
if lsi["projection_type"] != "ALL" {
non_key_attributes := []*string{}
for _, attr := range lsi["non_key_attributes"].([]interface{}) {
non_key_attributes = append(non_key_attributes, aws.String(attr.(string)))
}
projection.NonKeyAttributes = non_key_attributes
}
localSecondaryIndexes = append(localSecondaryIndexes, &dynamodb.LocalSecondaryIndex{
IndexName: aws.String(lsi["name"].(string)),
KeySchema: []*dynamodb.KeySchemaElement{
&dynamodb.KeySchemaElement{
AttributeName: aws.String(hash_key_name),
KeyType: aws.String("HASH"),
},
&dynamodb.KeySchemaElement{
AttributeName: aws.String(lsi["range_key"].(string)),
KeyType: aws.String("RANGE"),
},
},
Projection: projection,
})
}
req.LocalSecondaryIndexes = localSecondaryIndexes
fmt.Printf("[DEBUG] Added %d LSI definitions", len(localSecondaryIndexes))
}
if gsidata, ok := d.GetOk("global_secondary_index"); ok {
globalSecondaryIndexes := []*dynamodb.GlobalSecondaryIndex{}
gsiSet := gsidata.(*schema.Set)
for _, gsiObject := range gsiSet.List() {
gsi := gsiObject.(map[string]interface{})
gsiObject := createGSIFromData(&gsi)
globalSecondaryIndexes = append(globalSecondaryIndexes, &gsiObject)
}
req.GlobalSecondaryIndexes = globalSecondaryIndexes
}
output, err := dynamodbconn.CreateTable(req)
if err != nil {
return fmt.Errorf("Error creating DynamoDB table: %s", err)
}
d.SetId(*output.TableDescription.TableName)
// Creation complete, nothing to re-read
return nil
}
func resourceAwsDynamoDbTableUpdate(d *schema.ResourceData, meta interface{}) error {
log.Printf("[DEBUG] Updating DynamoDB table %s", d.Id())
dynamodbconn := meta.(*AWSClient).dynamodbconn
// Ensure table is active before trying to update
waitForTableToBeActive(d.Id(), meta)
// LSI can only be done at create-time, abort if it's been changed
if d.HasChange("local_secondary_index") {
return fmt.Errorf("Local secondary indexes can only be built at creation, you cannot update them!")
}
if d.HasChange("hash_key") {
return fmt.Errorf("Hash key can only be specified at creation, you cannot modify it.")
}
if d.HasChange("range_key") {
return fmt.Errorf("Range key can only be specified at creation, you cannot modify it.")
}
if d.HasChange("read_capacity") || d.HasChange("write_capacity") {
req := &dynamodb.UpdateTableInput{
TableName: aws.String(d.Id()),
}
throughput := &dynamodb.ProvisionedThroughput{
ReadCapacityUnits: aws.Long(int64(d.Get("read_capacity").(int))),
WriteCapacityUnits: aws.Long(int64(d.Get("write_capacity").(int))),
}
req.ProvisionedThroughput = throughput
_, err := dynamodbconn.UpdateTable(req)
if err != nil {
return err
}
waitForTableToBeActive(d.Id(), meta)
}
if d.HasChange("global_secondary_index") {
log.Printf("[DEBUG] Changed GSI data")
req := &dynamodb.UpdateTableInput{
TableName: aws.String(d.Id()),
}
o, n := d.GetChange("global_secondary_index")
oldSet := o.(*schema.Set)
newSet := n.(*schema.Set)
// Track old names so we can know which ones we need to just update based on
// capacity changes, terraform appears to only diff on the set hash, not the
// contents so we need to make sure we don't delete any indexes that we
// just want to update the capacity for
oldGsiNameSet := make(map[string]bool)
newGsiNameSet := make(map[string]bool)
for _, gsidata := range oldSet.List() {
gsiName := gsidata.(map[string]interface{})["name"].(string)
oldGsiNameSet[gsiName] = true
}
for _, gsidata := range newSet.List() {
gsiName := gsidata.(map[string]interface{})["name"].(string)
newGsiNameSet[gsiName] = true
}
// First determine what's new
for _, newgsidata := range newSet.List() {
updates := []*dynamodb.GlobalSecondaryIndexUpdate{}
newGsiName := newgsidata.(map[string]interface{})["name"].(string)
if _, exists := oldGsiNameSet[newGsiName]; !exists {
attributes := []*dynamodb.AttributeDefinition{}
gsidata := newgsidata.(map[string]interface{})
gsi := createGSIFromData(&gsidata)
log.Printf("[DEBUG] Adding GSI %s", *gsi.IndexName)
update := &dynamodb.GlobalSecondaryIndexUpdate{
Create: &dynamodb.CreateGlobalSecondaryIndexAction{
IndexName: gsi.IndexName,
KeySchema: gsi.KeySchema,
ProvisionedThroughput: gsi.ProvisionedThroughput,
Projection: gsi.Projection,
},
}
updates = append(updates, update)
// Hash key is required, range key isn't
hashkey_type, err := getAttributeType(d, *(gsi.KeySchema[0].AttributeName))
if err != nil {
return err
}
attributes = append(attributes, &dynamodb.AttributeDefinition{
AttributeName: gsi.KeySchema[0].AttributeName,
AttributeType: aws.String(hashkey_type),
})
// If there's a range key, there will be 2 elements in KeySchema
if len(gsi.KeySchema) == 2 {
rangekey_type, err := getAttributeType(d, *(gsi.KeySchema[1].AttributeName))
if err != nil {
return err
}
attributes = append(attributes, &dynamodb.AttributeDefinition{
AttributeName: gsi.KeySchema[1].AttributeName,
AttributeType: aws.String(rangekey_type),
})
}
req.AttributeDefinitions = attributes
req.GlobalSecondaryIndexUpdates = updates
_, err = dynamodbconn.UpdateTable(req)
if err != nil {
return err
}
waitForTableToBeActive(d.Id(), meta)
waitForGSIToBeActive(d.Id(), *gsi.IndexName, meta)
}
}
for _, oldgsidata := range oldSet.List() {
updates := []*dynamodb.GlobalSecondaryIndexUpdate{}
oldGsiName := oldgsidata.(map[string]interface{})["name"].(string)
if _, exists := newGsiNameSet[oldGsiName]; !exists {
gsidata := oldgsidata.(map[string]interface{})
log.Printf("[DEBUG] Deleting GSI %s", gsidata["name"].(string))
update := &dynamodb.GlobalSecondaryIndexUpdate{
Delete: &dynamodb.DeleteGlobalSecondaryIndexAction{
IndexName: aws.String(gsidata["name"].(string)),
},
}
updates = append(updates, update)
req.GlobalSecondaryIndexUpdates = updates
_, err := dynamodbconn.UpdateTable(req)
if err != nil {
return err
}
waitForTableToBeActive(d.Id(), meta)
}
}
}
// Update any out-of-date read / write capacity
if gsiObjects, ok := d.GetOk("global_secondary_index"); ok {
gsiSet := gsiObjects.(*schema.Set)
if len(gsiSet.List()) > 0 {
log.Printf("Updating capacity as needed!")
// We can only change throughput, but we need to make sure it's actually changed
tableDescription, err := dynamodbconn.DescribeTable(&dynamodb.DescribeTableInput{
TableName: aws.String(d.Id()),
})
if err != nil {
return err
}
table := tableDescription.Table
updates := []*dynamodb.GlobalSecondaryIndexUpdate{}
for _, updatedgsidata := range gsiSet.List() {
gsidata := updatedgsidata.(map[string]interface{})
gsiName := gsidata["name"].(string)
gsiWriteCapacity := gsidata["write_capacity"].(int)
gsiReadCapacity := gsidata["read_capacity"].(int)
log.Printf("[DEBUG] Updating GSI %s", gsiName)
gsi, err := getGlobalSecondaryIndex(gsiName, table.GlobalSecondaryIndexes)
if err != nil {
return err
}
capacityUpdated := false
if int64(gsiReadCapacity) != *(gsi.ProvisionedThroughput.ReadCapacityUnits) ||
int64(gsiWriteCapacity) != *(gsi.ProvisionedThroughput.WriteCapacityUnits) {
capacityUpdated = true
}
if capacityUpdated {
update := &dynamodb.GlobalSecondaryIndexUpdate{
Update: &dynamodb.UpdateGlobalSecondaryIndexAction{
IndexName: aws.String(gsidata["name"].(string)),
ProvisionedThroughput: &dynamodb.ProvisionedThroughput{
WriteCapacityUnits: aws.Long(int64(gsiWriteCapacity)),
ReadCapacityUnits: aws.Long(int64(gsiReadCapacity)),
},
},
}
updates = append(updates, update)
}
if len(updates) > 0 {
req := &dynamodb.UpdateTableInput{
TableName: aws.String(d.Id()),
}
req.GlobalSecondaryIndexUpdates = updates
log.Printf("[DEBUG] Updating GSI read / write capacity on %s", d.Id())
_, err := dynamodbconn.UpdateTable(req)
if err != nil {
log.Printf("[DEBUG] Error updating table: %s", err)
return err
}
}
}
}
}
return resourceAwsDynamoDbTableRead(d, meta)
}
func resourceAwsDynamoDbTableRead(d *schema.ResourceData, meta interface{}) error {
dynamodbconn := meta.(*AWSClient).dynamodbconn
log.Printf("[DEBUG] Loading data for DynamoDB table '%s'", d.Id())
req := &dynamodb.DescribeTableInput{
TableName: aws.String(d.Id()),
}
result, err := dynamodbconn.DescribeTable(req)
if err != nil {
return err
}
table := result.Table
d.Set("write_capacity", table.ProvisionedThroughput.WriteCapacityUnits)
d.Set("read_capacity", table.ProvisionedThroughput.ReadCapacityUnits)
attributes := []interface{}{}
for _, attrdef := range table.AttributeDefinitions {
attribute := map[string]string{
"name": *(attrdef.AttributeName),
"type": *(attrdef.AttributeType),
}
attributes = append(attributes, attribute)
log.Printf("[DEBUG] Added Attribute: %s", attribute["name"])
}
d.Set("attribute", attributes)
gsiList := make([]map[string]interface{}, 0, len(table.GlobalSecondaryIndexes))
for _, gsiObject := range table.GlobalSecondaryIndexes {
gsi := map[string]interface{}{
"write_capacity": *(gsiObject.ProvisionedThroughput.WriteCapacityUnits),
"read_capacity": *(gsiObject.ProvisionedThroughput.ReadCapacityUnits),
"name": *(gsiObject.IndexName),
}
for _, attribute := range gsiObject.KeySchema {
if *attribute.KeyType == "HASH" {
gsi["hash_key"] = *attribute.AttributeName
}
if *attribute.KeyType == "RANGE" {
gsi["range_key"] = *attribute.AttributeName
}
}
gsi["projection_type"] = *(gsiObject.Projection.ProjectionType)
gsi["non_key_attributes"] = gsiObject.Projection.NonKeyAttributes
gsiList = append(gsiList, gsi)
log.Printf("[DEBUG] Added GSI: %s - Read: %d / Write: %d", gsi["name"], gsi["read_capacity"], gsi["write_capacity"])
}
d.Set("global_secondary_index", gsiList)
return nil
}
func resourceAwsDynamoDbTableDelete(d *schema.ResourceData, meta interface{}) error {
dynamodbconn := meta.(*AWSClient).dynamodbconn
waitForTableToBeActive(d.Id(), meta)
log.Printf("[DEBUG] DynamoDB delete table: %s", d.Id())
_, err := dynamodbconn.DeleteTable(&dynamodb.DeleteTableInput{
TableName: aws.String(d.Id()),
})
if err != nil {
return err
}
return nil
}
func createGSIFromData(data *map[string]interface{}) dynamodb.GlobalSecondaryIndex {
projection := &dynamodb.Projection{
ProjectionType: aws.String((*data)["projection_type"].(string)),
}
if (*data)["projection_type"] != "ALL" {
non_key_attributes := []*string{}
for _, attr := range (*data)["non_key_attributes"].([]interface{}) {
non_key_attributes = append(non_key_attributes, aws.String(attr.(string)))
}
projection.NonKeyAttributes = non_key_attributes
}
writeCapacity := (*data)["write_capacity"].(int)
readCapacity := (*data)["read_capacity"].(int)
key_schema := []*dynamodb.KeySchemaElement{
&dynamodb.KeySchemaElement{
AttributeName: aws.String((*data)["hash_key"].(string)),
KeyType: aws.String("HASH"),
},
}
range_key_name := (*data)["range_key"]
if range_key_name != "" {
range_key_element := &dynamodb.KeySchemaElement{
AttributeName: aws.String(range_key_name.(string)),
KeyType: aws.String("RANGE"),
}
key_schema = append(key_schema, range_key_element)
}
return dynamodb.GlobalSecondaryIndex{
IndexName: aws.String((*data)["name"].(string)),
KeySchema: key_schema,
Projection: projection,
ProvisionedThroughput: &dynamodb.ProvisionedThroughput{
WriteCapacityUnits: aws.Long(int64(writeCapacity)),
ReadCapacityUnits: aws.Long(int64(readCapacity)),
},
}
}
func getGlobalSecondaryIndex(indexName string, indexList []*dynamodb.GlobalSecondaryIndexDescription) (*dynamodb.GlobalSecondaryIndexDescription, error) {
for _, gsi := range indexList {
if *(gsi.IndexName) == indexName {
return gsi, nil
}
}
return &dynamodb.GlobalSecondaryIndexDescription{}, fmt.Errorf("Can't find a GSI by that name...")
}
func getAttributeType(d *schema.ResourceData, attributeName string) (string, error) {
if attributedata, ok := d.GetOk("attribute"); ok {
attributeSet := attributedata.(*schema.Set)
for _, attribute := range attributeSet.List() {
attr := attribute.(map[string]interface{})
if attr["name"] == attributeName {
return attr["type"].(string), nil
}
}
}
return "", fmt.Errorf("Unable to find an attribute named %s", attributeName)
}
func waitForGSIToBeActive(tableName string, gsiName string, meta interface{}) error {
dynamodbconn := meta.(*AWSClient).dynamodbconn
req := &dynamodb.DescribeTableInput{
TableName: aws.String(tableName),
}
activeIndex := false
for activeIndex == false {
result, err := dynamodbconn.DescribeTable(req)
if err != nil {
return err
}
table := result.Table
var targetGSI *dynamodb.GlobalSecondaryIndexDescription = nil
for _, gsi := range table.GlobalSecondaryIndexes {
if *gsi.IndexName == gsiName {
targetGSI = gsi
}
}
if targetGSI != nil {
activeIndex = *targetGSI.IndexStatus == "ACTIVE"
if !activeIndex {
log.Printf("[DEBUG] Sleeping for 5 seconds for %s GSI to become active", gsiName)
time.Sleep(5 * time.Second)
}
} else {
log.Printf("[DEBUG] GSI %s did not exist, giving up", gsiName)
break
}
}
return nil
}
func waitForTableToBeActive(tableName string, meta interface{}) error {
dynamodbconn := meta.(*AWSClient).dynamodbconn
req := &dynamodb.DescribeTableInput{
TableName: aws.String(tableName),
}
activeState := false
for activeState == false {
result, err := dynamodbconn.DescribeTable(req)
if err != nil {
return err
}
activeState = *(result.Table.TableStatus) == "ACTIVE"
// Wait for a few seconds
if !activeState {
log.Printf("[DEBUG] Sleeping for 5 seconds for table to become active")
time.Sleep(5 * time.Second)
}
}
return nil
}

View File

@ -0,0 +1,296 @@
package aws
import (
"fmt"
"testing"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/awserr"
"github.com/aws/aws-sdk-go/service/dynamodb"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform"
)
func TestAccAWSDynamoDbTable(t *testing.T) {
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckAWSDynamoDbTableDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccAWSDynamoDbConfigInitialState,
Check: resource.ComposeTestCheckFunc(
testAccCheckInitialAWSDynamoDbTableExists("aws_dynamodb_table.basic-dynamodb-table"),
),
},
resource.TestStep{
Config: testAccAWSDynamoDbConfigAddSecondaryGSI,
Check: resource.ComposeTestCheckFunc(
testAccCheckDynamoDbTableWasUpdated("aws_dynamodb_table.basic-dynamodb-table"),
),
},
},
})
}
func testAccCheckAWSDynamoDbTableDestroy(s *terraform.State) error {
conn := testAccProvider.Meta().(*AWSClient).dynamodbconn
for _, rs := range s.RootModule().Resources {
if rs.Type != "aws_dynamodb_table" {
continue
}
fmt.Printf("[DEBUG] Checking if DynamoDB table %s exists", rs.Primary.ID)
// Check if queue exists by checking for its attributes
params := &dynamodb.DescribeTableInput{
TableName: aws.String(rs.Primary.ID),
}
_, err := conn.DescribeTable(params)
if err == nil {
return fmt.Errorf("DynamoDB table %s still exists. Failing!", rs.Primary.ID)
}
// Verify the error is what we want
_, ok := err.(awserr.Error)
if !ok {
return err
}
}
return nil
}
func testAccCheckInitialAWSDynamoDbTableExists(n string) resource.TestCheckFunc {
return func(s *terraform.State) error {
fmt.Printf("[DEBUG] Trying to create initial table state!")
rs, ok := s.RootModule().Resources[n]
if !ok {
return fmt.Errorf("Not found: %s", n)
}
if rs.Primary.ID == "" {
return fmt.Errorf("No DynamoDB table name specified!")
}
conn := testAccProvider.Meta().(*AWSClient).dynamodbconn
params := &dynamodb.DescribeTableInput{
TableName: aws.String(rs.Primary.ID),
}
resp, err := conn.DescribeTable(params)
if err != nil {
fmt.Printf("[ERROR] Problem describing table '%s': %s", rs.Primary.ID, err)
return err
}
table := resp.Table
fmt.Printf("[DEBUG] Checking on table %s", rs.Primary.ID)
if *table.ProvisionedThroughput.WriteCapacityUnits != 20 {
return fmt.Errorf("Provisioned write capacity was %d, not 20!", table.ProvisionedThroughput.WriteCapacityUnits)
}
if *table.ProvisionedThroughput.ReadCapacityUnits != 10 {
return fmt.Errorf("Provisioned read capacity was %d, not 10!", table.ProvisionedThroughput.ReadCapacityUnits)
}
attrCount := len(table.AttributeDefinitions)
gsiCount := len(table.GlobalSecondaryIndexes)
lsiCount := len(table.LocalSecondaryIndexes)
if attrCount != 4 {
return fmt.Errorf("There were %d attributes, not 4 like there should have been!", attrCount)
}
if gsiCount != 1 {
return fmt.Errorf("There were %d GSIs, not 1 like there should have been!", gsiCount)
}
if lsiCount != 1 {
return fmt.Errorf("There were %d LSIs, not 1 like there should have been!", lsiCount)
}
attrmap := dynamoDbAttributesToMap(&table.AttributeDefinitions)
if attrmap["TestTableHashKey"] != "S" {
return fmt.Errorf("Test table hash key was of type %s instead of S!", attrmap["TestTableHashKey"])
}
if attrmap["TestTableRangeKey"] != "S" {
return fmt.Errorf("Test table range key was of type %s instead of S!", attrmap["TestTableRangeKey"])
}
if attrmap["TestLSIRangeKey"] != "N" {
return fmt.Errorf("Test table LSI range key was of type %s instead of N!", attrmap["TestLSIRangeKey"])
}
if attrmap["TestGSIRangeKey"] != "S" {
return fmt.Errorf("Test table GSI range key was of type %s instead of S!", attrmap["TestGSIRangeKey"])
}
return nil
}
}
func testAccCheckDynamoDbTableWasUpdated(n string) resource.TestCheckFunc {
return func(s *terraform.State) error {
rs, ok := s.RootModule().Resources[n]
if !ok {
return fmt.Errorf("Not found: %s", n)
}
if rs.Primary.ID == "" {
return fmt.Errorf("No DynamoDB table name specified!")
}
conn := testAccProvider.Meta().(*AWSClient).dynamodbconn
params := &dynamodb.DescribeTableInput{
TableName: aws.String(rs.Primary.ID),
}
resp, err := conn.DescribeTable(params)
table := resp.Table
if err != nil {
return err
}
attrCount := len(table.AttributeDefinitions)
gsiCount := len(table.GlobalSecondaryIndexes)
lsiCount := len(table.LocalSecondaryIndexes)
if attrCount != 4 {
return fmt.Errorf("There were %d attributes, not 4 like there should have been!", attrCount)
}
if gsiCount != 1 {
return fmt.Errorf("There were %d GSIs, not 1 like there should have been!", gsiCount)
}
if lsiCount != 1 {
return fmt.Errorf("There were %d LSIs, not 1 like there should have been!", lsiCount)
}
if dynamoDbGetGSIIndex(&table.GlobalSecondaryIndexes, "ReplacementTestTableGSI") == -1 {
return fmt.Errorf("Could not find GSI named 'ReplacementTestTableGSI' in the table!")
}
if dynamoDbGetGSIIndex(&table.GlobalSecondaryIndexes, "InitialTestTableGSI") != -1 {
return fmt.Errorf("Should have removed 'InitialTestTableGSI' but it still exists!")
}
attrmap := dynamoDbAttributesToMap(&table.AttributeDefinitions)
if attrmap["TestTableHashKey"] != "S" {
return fmt.Errorf("Test table hash key was of type %s instead of S!", attrmap["TestTableHashKey"])
}
if attrmap["TestTableRangeKey"] != "S" {
return fmt.Errorf("Test table range key was of type %s instead of S!", attrmap["TestTableRangeKey"])
}
if attrmap["TestLSIRangeKey"] != "N" {
return fmt.Errorf("Test table LSI range key was of type %s instead of N!", attrmap["TestLSIRangeKey"])
}
if attrmap["ReplacementGSIRangeKey"] != "N" {
return fmt.Errorf("Test table replacement GSI range key was of type %s instead of N!", attrmap["ReplacementGSIRangeKey"])
}
return nil
}
}
func dynamoDbGetGSIIndex(gsiList *[]*dynamodb.GlobalSecondaryIndexDescription, target string) int {
for idx, gsiObject := range *gsiList {
if *gsiObject.IndexName == target {
return idx
}
}
return -1
}
func dynamoDbAttributesToMap(attributes *[]*dynamodb.AttributeDefinition) map[string]string {
attrmap := make(map[string]string)
for _, attrdef := range *attributes {
attrmap[*(attrdef.AttributeName)] = *(attrdef.AttributeType)
}
return attrmap
}
const testAccAWSDynamoDbConfigInitialState = `
resource "aws_dynamodb_table" "basic-dynamodb-table" {
name = "TerraformTestTable"
read_capacity = 10
write_capacity = 20
hash_key = "TestTableHashKey"
range_key = "TestTableRangeKey"
attribute {
name = "TestTableHashKey"
type = "S"
}
attribute {
name = "TestTableRangeKey"
type = "S"
}
attribute {
name = "TestLSIRangeKey"
type = "N"
}
attribute {
name = "TestGSIRangeKey"
type = "S"
}
local_secondary_index {
name = "TestTableLSI"
range_key = "TestLSIRangeKey"
projection_type = "ALL"
}
global_secondary_index {
name = "InitialTestTableGSI"
hash_key = "TestTableHashKey"
range_key = "TestGSIRangeKey"
write_capacity = 10
read_capacity = 10
projection_type = "ALL"
}
}
`
const testAccAWSDynamoDbConfigAddSecondaryGSI = `
resource "aws_dynamodb_table" "basic-dynamodb-table" {
name = "TerraformTestTable"
read_capacity = 20
write_capacity = 20
hash_key = "TestTableHashKey"
range_key = "TestTableRangeKey"
attribute {
name = "TestTableHashKey"
type = "S"
}
attribute {
name = "TestTableRangeKey"
type = "S"
}
attribute {
name = "TestLSIRangeKey"
type = "N"
}
attribute {
name = "ReplacementGSIRangeKey"
type = "N"
}
local_secondary_index {
name = "TestTableLSI"
range_key = "TestLSIRangeKey"
projection_type = "ALL"
}
global_secondary_index {
name = "ReplacementTestTableGSI"
hash_key = "TestTableHashKey"
range_key = "ReplacementGSIRangeKey"
write_capacity = 5
read_capacity = 5
projection_type = "ALL"
}
}
`

View File

@ -9,6 +9,7 @@ import (
"github.com/Azure/azure-sdk-for-go/management/hostedservice"
"github.com/Azure/azure-sdk-for-go/management/networksecuritygroup"
"github.com/Azure/azure-sdk-for-go/management/osimage"
"github.com/Azure/azure-sdk-for-go/management/sql"
"github.com/Azure/azure-sdk-for-go/management/storageservice"
"github.com/Azure/azure-sdk-for-go/management/virtualmachine"
"github.com/Azure/azure-sdk-for-go/management/virtualmachinedisk"
@ -36,6 +37,8 @@ type Client struct {
osImageClient osimage.OSImageClient
sqlClient sql.SqlDatabaseClient
storageServiceClient storageservice.StorageServiceClient
vmClient virtualmachine.VirtualMachineClient
@ -107,6 +110,7 @@ func (c *Config) NewClientFromSettingsFile() (*Client, error) {
hostedServiceClient: hostedservice.NewClient(mc),
secGroupClient: networksecuritygroup.NewClient(mc),
osImageClient: osimage.NewClient(mc),
sqlClient: sql.NewClient(mc),
storageServiceClient: storageservice.NewClient(mc),
vmClient: virtualmachine.NewClient(mc),
vmDiskClient: virtualmachinedisk.NewClient(mc),
@ -129,6 +133,7 @@ func (c *Config) NewClient() (*Client, error) {
hostedServiceClient: hostedservice.NewClient(mc),
secGroupClient: networksecuritygroup.NewClient(mc),
osImageClient: osimage.NewClient(mc),
sqlClient: sql.NewClient(mc),
storageServiceClient: storageservice.NewClient(mc),
vmClient: virtualmachine.NewClient(mc),
vmDiskClient: virtualmachinedisk.NewClient(mc),

View File

@ -34,6 +34,8 @@ func Provider() terraform.ResourceProvider {
ResourcesMap: map[string]*schema.Resource{
"azure_instance": resourceAzureInstance(),
"azure_data_disk": resourceAzureDataDisk(),
"azure_sql_database_server": resourceAzureSqlDatabaseServer(),
"azure_sql_database_service": resourceAzureSqlDatabaseService(),
"azure_hosted_service": resourceAzureHostedService(),
"azure_storage_service": resourceAzureStorageService(),
"azure_storage_container": resourceAzureStorageContainer(),

View File

@ -0,0 +1,118 @@
package azure
import (
"fmt"
"log"
"github.com/Azure/azure-sdk-for-go/management/sql"
"github.com/hashicorp/terraform/helper/schema"
)
// resourceAzureDatabaseServer returns the *schema.Resource associated
// to a database server on Azure.
func resourceAzureSqlDatabaseServer() *schema.Resource {
return &schema.Resource{
Create: resourceAzureSqlDatabaseServerCreate,
Read: resourceAzureSqlDatabaseServerRead,
Delete: resourceAzureSqlDatabaseServerDelete,
Schema: map[string]*schema.Schema{
"name": &schema.Schema{
Type: schema.TypeString,
Computed: true,
ForceNew: true,
},
"location": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"username": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"password": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"version": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Default: "12.0",
ForceNew: true,
},
"url": &schema.Schema{
Type: schema.TypeString,
Computed: true,
},
},
}
}
// resourceAzureSqlDatabaseServerCreate does all the necessary API calls to
// create an SQL database server off Azure.
func resourceAzureSqlDatabaseServerCreate(d *schema.ResourceData, meta interface{}) error {
sqlClient := meta.(*Client).sqlClient
log.Println("[INFO] Began constructing SQL Server creation request.")
params := sql.DatabaseServerCreateParams{
Location: d.Get("location").(string),
AdministratorLogin: d.Get("username").(string),
AdministratorLoginPassword: d.Get("password").(string),
Version: d.Get("version").(string),
}
log.Println("[INFO] Issuing SQL Server creation request to Azure.")
name, err := sqlClient.CreateServer(params)
if err != nil {
return fmt.Errorf("Error creating SQL Server on Azure: %s", err)
}
d.Set("name", name)
d.SetId(name)
return resourceAzureSqlDatabaseServerRead(d, meta)
}
// resourceAzureSqlDatabaseServerRead does all the necessary API calls to
// read the state of the SQL database server off Azure.
func resourceAzureSqlDatabaseServerRead(d *schema.ResourceData, meta interface{}) error {
sqlClient := meta.(*Client).sqlClient
log.Println("[INFO] Sending SQL Servers list query to Azure.")
srvList, err := sqlClient.ListServers()
if err != nil {
return fmt.Errorf("Error issuing SQL Servers list query to Azure: %s", err)
}
// search for our particular server:
name := d.Get("name")
for _, srv := range srvList.DatabaseServers {
if srv.Name == name {
d.Set("url", srv.FullyQualifiedDomainName)
d.Set("state", srv.State)
return nil
}
}
// if reached here; it means out server doesn't exist, so we must untrack it:
d.SetId("")
return nil
}
// resourceAzureSqlDatabaseServerDelete does all the necessary API calls to
// delete the SQL database server off Azure.
func resourceAzureSqlDatabaseServerDelete(d *schema.ResourceData, meta interface{}) error {
sqlClient := meta.(*Client).sqlClient
log.Println("[INFO] Sending SQL Server deletion request to Azure.")
name := d.Get("name").(string)
err := sqlClient.DeleteServer(name)
if err != nil {
return fmt.Errorf("Error while issuing SQL Server deletion request to Azure: %s", err)
}
return nil
}

View File

@ -0,0 +1,119 @@
package azure
import (
"fmt"
"testing"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform"
)
// testAccAzureSqlServerName is a helper variable in which to store
// the randomly-generated name of the SQL Server after it is created.
// The anonymous function is there because go is too good to &"" directly.
var testAccAzureSqlServerName *string = func(s string) *string { return &s }("")
func TestAccAzureSqlDatabaseServer(t *testing.T) {
name := "azure_sql_database_server.foo"
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckAzureSqlDatabaseServerDeleted,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccAzureSqlDatabaseServerConfig,
Check: resource.ComposeTestCheckFunc(
testAccAzureSqlDatabaseServerGetName,
testAccCheckAzureSqlDatabaseServerExists(name),
resource.TestCheckResourceAttrPtr(name, "name", testAccAzureSqlServerName),
resource.TestCheckResourceAttr(name, "username", "SuperUser"),
resource.TestCheckResourceAttr(name, "password", "SuperSEKR3T"),
resource.TestCheckResourceAttr(name, "version", "2.0"),
),
},
},
})
}
func testAccCheckAzureSqlDatabaseServerExists(name string) resource.TestCheckFunc {
return func(s *terraform.State) error {
resource, ok := s.RootModule().Resources[name]
if !ok {
return fmt.Errorf("SQL Server %s doesn't exist.", name)
}
if resource.Primary.ID == "" {
return fmt.Errorf("SQL Server %s resource ID not set.", name)
}
sqlClient := testAccProvider.Meta().(*Client).sqlClient
servers, err := sqlClient.ListServers()
if err != nil {
return fmt.Errorf("Error issuing Azure SQL Server list request: %s", err)
}
for _, srv := range servers.DatabaseServers {
if srv.Name == resource.Primary.ID {
return nil
}
}
return fmt.Errorf("SQL Server %s doesn't exist.", name)
}
}
func testAccCheckAzureSqlDatabaseServerDeleted(s *terraform.State) error {
for _, resource := range s.RootModule().Resources {
if resource.Type != "azure_sql_database_server" {
continue
}
if resource.Primary.ID == "" {
return fmt.Errorf("SQL Server resource ID not set.")
}
sqlClient := testAccProvider.Meta().(*Client).sqlClient
servers, err := sqlClient.ListServers()
if err != nil {
return fmt.Errorf("Error issuing Azure SQL Server list request: %s", err)
}
for _, srv := range servers.DatabaseServers {
if srv.Name == resource.Primary.ID {
fmt.Errorf("SQL Server %s still exists.", resource.Primary.ID)
}
}
}
return nil
}
// testAccAzureSqlDatabaseServerGetName is ahelper function which reads the current
// state form Terraform and sets the testAccAzureSqlServerName variable
// to the ID (which is actually the name) of the newly created server.
// It is modeled as a resource.TestCheckFunc so as to be easily-embeddable in
// test cases and run live.
func testAccAzureSqlDatabaseServerGetName(s *terraform.State) error {
for _, resource := range s.RootModule().Resources {
if resource.Type != "azure_sql_database_server" {
continue
}
if resource.Primary.ID == "" {
return fmt.Errorf("Azure SQL Server resource ID not set.")
}
*testAccAzureSqlServerName = resource.Primary.ID
return nil
}
return fmt.Errorf("No Azure SQL Servers found.")
}
const testAccAzureSqlDatabaseServerConfig = `
resource "azure_sql_database_server" "foo" {
location = "West US"
username = "SuperUser"
password = "SuperSEKR3T"
version = "2.0"
}
`

View File

@ -0,0 +1,234 @@
package azure
import (
"fmt"
"log"
"strconv"
"strings"
"github.com/Azure/azure-sdk-for-go/management/sql"
"github.com/hashicorp/terraform/helper/schema"
)
// resourceAzureSqlDatabaseService returns the *schema.Resource
// associated to an SQL Database Service on Azure.
func resourceAzureSqlDatabaseService() *schema.Resource {
return &schema.Resource{
Create: resourceAzureSqlDatabaseServiceCreate,
Read: resourceAzureSqlDatabaseServiceRead,
Update: resourceAzureSqlDatabaseServiceUpdate,
Exists: resourceAzureSqlDatabaseServiceExists,
Delete: resourceAzureSqlDatabaseServiceDelete,
Schema: map[string]*schema.Schema{
"name": &schema.Schema{
Type: schema.TypeString,
Required: true,
},
"database_server_name": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"collation": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Computed: true,
ForceNew: true,
},
"edition": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Computed: true,
},
"max_size_bytes": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Computed: true,
},
"service_level_id": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Computed: true,
},
},
}
}
// resourceAzureSqlDatabaseServiceCreate does all the necessary API calls to
// create an SQL Database Service on Azure.
func resourceAzureSqlDatabaseServiceCreate(d *schema.ResourceData, meta interface{}) error {
sqlClient := meta.(*Client).sqlClient
log.Println("[INFO] Creating Azure SQL Database service creation request.")
name := d.Get("name").(string)
serverName := d.Get("database_server_name").(string)
params := sql.DatabaseCreateParams{
Name: name,
Edition: d.Get("edition").(string),
CollationName: d.Get("collation").(string),
ServiceObjectiveID: d.Get("service_level_id").(string),
}
if maxSize, ok := d.GetOk("max_size_bytes"); ok {
val, err := strconv.ParseInt(maxSize.(string), 10, 64)
if err != nil {
return fmt.Errorf("Provided max_size_bytes is not an integer: %s", err)
}
params.MaxSizeBytes = val
}
log.Println("[INFO] Sending SQL Database Service creation request to Azure.")
err := sqlClient.CreateDatabase(serverName, params)
if err != nil {
return fmt.Errorf("Error issuing Azure SQL Database Service creation: %s", err)
}
log.Println("[INFO] Beginning wait for Azure SQL Database Service creation.")
err = sqlClient.WaitForDatabaseCreation(serverName, name, nil)
if err != nil {
return fmt.Errorf("Error whilst waiting for Azure SQL Database Service creation: %s", err)
}
d.SetId(name)
return resourceAzureSqlDatabaseServiceRead(d, meta)
}
// resourceAzureSqlDatabaseServiceRead does all the necessary API calls to
// read the state of the SQL Database Service off Azure.
func resourceAzureSqlDatabaseServiceRead(d *schema.ResourceData, meta interface{}) error {
sqlClient := meta.(*Client).sqlClient
log.Println("[INFO] Issuing Azure SQL Database Services list operation.")
serverName := d.Get("database_server_name").(string)
dbs, err := sqlClient.ListDatabases(serverName)
if err != nil {
return fmt.Errorf("Error whilst listing Database Services off Azure: %s", err)
}
// search for our database:
var found bool
name := d.Get("name").(string)
for _, db := range dbs.ServiceResources {
if db.Name == name {
found = true
d.Set("edition", db.Edition)
d.Set("collation", db.CollationName)
d.Set("max_size_bytes", strconv.FormatInt(db.MaxSizeBytes, 10))
d.Set("service_level_id", db.ServiceObjectiveID)
break
}
}
// if not found; we must untrack the resource:
if !found {
d.SetId("")
}
return nil
}
// resourceAzureSqlDatabaseServiceUpdate does all the necessary API calls to
// update the state of the SQL Database Service off Azure.
func resourceAzureSqlDatabaseServiceUpdate(d *schema.ResourceData, meta interface{}) error {
azureClient := meta.(*Client)
mgmtClient := azureClient.mgmtClient
sqlClient := azureClient.sqlClient
serverName := d.Get("database_server_name").(string)
// changes to the name must occur seperately from changes to the attributes:
if d.HasChange("name") {
oldv, newv := d.GetChange("name")
// issue the update request:
log.Println("[INFO] Issuing Azure Database Service name change.")
reqID, err := sqlClient.UpdateDatabase(serverName, oldv.(string),
sql.ServiceResourceUpdateParams{
Name: newv.(string),
})
// wait for the update to occur:
log.Println("[INFO] Waiting for Azure SQL Database Service name change.")
err = mgmtClient.WaitForOperation(reqID, nil)
if err != nil {
return fmt.Errorf("Error waiting for Azure SQL Database Service name update: %s", err)
}
// set the new name as the ID:
d.SetId(newv.(string))
}
name := d.Get("name").(string)
cedition := d.HasChange("edition")
cmaxsize := d.HasChange("max_size_bytes")
clevel := d.HasChange("service_level_id")
if cedition || cmaxsize || clevel {
updateParams := sql.ServiceResourceUpdateParams{
// we still have to stick the name in here for good measure:
Name: name,
}
// build the update request:
if cedition {
updateParams.Edition = d.Get("edition").(string)
}
if maxSize, ok := d.GetOk("max_size_bytes"); cmaxsize && ok && maxSize.(string) != "" {
val, err := strconv.ParseInt(maxSize.(string), 10, 64)
if err != nil {
return fmt.Errorf("Provided max_size_bytes is not an integer: %s", err)
}
updateParams.MaxSizeBytes = val
}
if clevel {
updateParams.ServiceObjectiveID = d.Get("service_level_id").(string)
}
// issue the update:
log.Println("[INFO] Issuing Azure Database Service parameter update.")
reqID, err := sqlClient.UpdateDatabase(serverName, name, updateParams)
if err != nil {
return fmt.Errorf("Failed issuing Azure SQL Service paramater update: %s", err)
}
log.Println("[INFO] Waiting for Azure SQL Database Service parameter update.")
err = mgmtClient.WaitForOperation(reqID, nil)
if err != nil {
return fmt.Errorf("Error waiting for Azure SQL Database Service parameter update: %s", err)
}
}
return nil
}
// resourceAzureSqlDatabaseServiceExists does all the necessary API calls to
// check for the existence of the SQL Database Service off Azure.
func resourceAzureSqlDatabaseServiceExists(d *schema.ResourceData, meta interface{}) (bool, error) {
sqlClient := meta.(*Client).sqlClient
log.Println("[INFO] Issuing Azure SQL Database Service get request.")
name := d.Get("name").(string)
serverName := d.Get("database_server_name").(string)
_, err := sqlClient.GetDatabase(serverName, name)
if err != nil {
if strings.Contains(err.Error(), "does not exist") {
d.SetId("")
return false, nil
} else {
return false, fmt.Errorf("Error whilst getting Azure SQL Database Service info: %s", err)
}
}
return true, nil
}
// resourceAzureSqlDatabaseServiceDelete does all the necessary API calls to
// delete the SQL Database Service off Azure.
func resourceAzureSqlDatabaseServiceDelete(d *schema.ResourceData, meta interface{}) error {
sqlClient := meta.(*Client).sqlClient
log.Println("[INFO] Issuing Azure SQL Database deletion request.")
name := d.Get("name").(string)
serverName := d.Get("database_server_name").(string)
return sqlClient.DeleteDatabase(serverName, name)
}

View File

@ -0,0 +1,189 @@
package azure
import (
"fmt"
"testing"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform"
)
func TestAccAzureSqlDatabaseServiceBasic(t *testing.T) {
name := "azure_sql_database_service.foo"
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckAzureSqlDatabaseServiceDeleted,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccAzureSqlDatabaseServiceConfigBasic,
Check: resource.ComposeTestCheckFunc(
testAccAzureSqlDatabaseServerGetName,
testAccCheckAzureSqlDatabaseServiceExists(name),
resource.TestCheckResourceAttr(name, "name", "terraform-testing-db"),
resource.TestCheckResourceAttrPtr(name, "database_server_name",
testAccAzureSqlServerName),
resource.TestCheckResourceAttr(name, "collation",
"SQL_Latin1_General_CP1_CI_AS"),
resource.TestCheckResourceAttr(name, "edition", "Standard"),
),
},
},
})
}
func TestAccAzureSqlDatabaseServiceAdvanced(t *testing.T) {
name := "azure_sql_database_service.foo"
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckAzureSqlDatabaseServiceDeleted,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccAzureSqlDatabaseServiceConfigAdvanced,
Check: resource.ComposeTestCheckFunc(
testAccAzureSqlDatabaseServerGetName,
testAccCheckAzureSqlDatabaseServiceExists(name),
resource.TestCheckResourceAttr(name, "name", "terraform-testing-db"),
resource.TestCheckResourceAttrPtr(name, "database_server_name",
testAccAzureSqlServerName),
resource.TestCheckResourceAttr(name, "edition", "Premium"),
resource.TestCheckResourceAttr(name, "collation",
"Arabic_BIN"),
resource.TestCheckResourceAttr(name, "max_size_bytes", "10737418240"),
resource.TestCheckResourceAttr(name, "service_level_id",
"7203483a-c4fb-4304-9e9f-17c71c904f5d"),
),
},
},
})
}
func TestAccAzureSqlDatabaseServiceUpdate(t *testing.T) {
name := "azure_sql_database_service.foo"
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckAzureSqlDatabaseServiceDeleted,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccAzureSqlDatabaseServiceConfigAdvanced,
Check: resource.ComposeTestCheckFunc(
testAccAzureSqlDatabaseServerGetName,
testAccCheckAzureSqlDatabaseServiceExists(name),
resource.TestCheckResourceAttr(name, "name", "terraform-testing-db"),
resource.TestCheckResourceAttrPtr(name, "database_server_name",
testAccAzureSqlServerName),
resource.TestCheckResourceAttr(name, "edition", "Premium"),
resource.TestCheckResourceAttr(name, "collation",
"Arabic_BIN"),
resource.TestCheckResourceAttr(name, "max_size_bytes", "10737418240"),
resource.TestCheckResourceAttr(name, "service_level_id",
"7203483a-c4fb-4304-9e9f-17c71c904f5d"),
),
},
resource.TestStep{
Config: testAccAzureSqlDatabaseServiceConfigUpdate,
Check: resource.ComposeTestCheckFunc(
testAccAzureSqlDatabaseServerGetName,
testAccCheckAzureSqlDatabaseServiceExists(name),
resource.TestCheckResourceAttr(name, "name",
"terraform-testing-db-renamed"),
resource.TestCheckResourceAttrPtr(name, "database_server_name",
testAccAzureSqlServerName),
resource.TestCheckResourceAttr(name, "edition", "Standard"),
resource.TestCheckResourceAttr(name, "collation",
"SQL_Latin1_General_CP1_CI_AS"),
resource.TestCheckResourceAttr(name, "max_size_bytes", "5368709120"),
resource.TestCheckResourceAttr(name, "service_level_id",
"f1173c43-91bd-4aaa-973c-54e79e15235b"),
),
},
},
})
}
func testAccCheckAzureSqlDatabaseServiceExists(name string) resource.TestCheckFunc {
return func(s *terraform.State) error {
resource, ok := s.RootModule().Resources[name]
if !ok {
return fmt.Errorf("SQL Service %s doesn't exist.", name)
}
if resource.Primary.ID == "" {
return fmt.Errorf("SQL Service %s resource ID not set.", name)
}
sqlClient := testAccProvider.Meta().(*Client).sqlClient
dbs, err := sqlClient.ListDatabases(*testAccAzureSqlServerName)
if err != nil {
return fmt.Errorf("Error issuing Azure SQL Service list request: %s", err)
}
for _, srv := range dbs.ServiceResources {
if srv.Name == resource.Primary.ID {
return nil
}
}
return fmt.Errorf("SQL Service %s doesn't exist.", name)
}
}
func testAccCheckAzureSqlDatabaseServiceDeleted(s *terraform.State) error {
for _, resource := range s.RootModule().Resources {
if resource.Type != "azure_sql_database_server" {
continue
}
if resource.Primary.ID == "" {
return fmt.Errorf("SQL Service resource ID not set.")
}
sqlClient := testAccProvider.Meta().(*Client).sqlClient
dbs, err := sqlClient.ListDatabases(*testAccAzureSqlServerName)
if err != nil {
return fmt.Errorf("Error issuing Azure SQL Service list request: %s", err)
}
for _, srv := range dbs.ServiceResources {
if srv.Name == resource.Primary.ID {
fmt.Errorf("SQL Service %s still exists.", resource.Primary.ID)
}
}
}
return nil
}
const testAccAzureSqlDatabaseServiceConfigBasic = testAccAzureSqlDatabaseServerConfig + `
resource "azure_sql_database_service" "foo" {
name = "terraform-testing-db"
database_server_name = "${azure_sql_database_server.foo.name}"
edition = "Standard"
}
`
const testAccAzureSqlDatabaseServiceConfigAdvanced = testAccAzureSqlDatabaseServerConfig + `
resource "azure_sql_database_service" "foo" {
name = "terraform-testing-db"
database_server_name = "${azure_sql_database_server.foo.name}"
edition = "Premium"
collation = "Arabic_BIN"
max_size_bytes = "10737418240"
service_level_id = "7203483a-c4fb-4304-9e9f-17c71c904f5d"
}
`
const testAccAzureSqlDatabaseServiceConfigUpdate = testAccAzureSqlDatabaseServerConfig + `
resource "azure_sql_database_service" "foo" {
name = "terraform-testing-db-renamed"
database_server_name = "${azure_sql_database_server.foo.name}"
edition = "Standard"
collation = "SQL_Latin1_General_CP1_CI_AS"
max_size_bytes = "5368709120"
service_level_id = "f1173c43-91bd-4aaa-973c-54e79e15235b"
}
`

View File

@ -5,6 +5,7 @@ import (
"encoding/hex"
"fmt"
"io/ioutil"
"log"
"os"
"path/filepath"
@ -75,8 +76,14 @@ func Delete(d *schema.ResourceData, meta interface{}) error {
func Exists(d *schema.ResourceData, meta interface{}) (bool, error) {
rendered, err := render(d)
if err != nil {
if _, ok := err.(templateRenderError); ok {
log.Printf("[DEBUG] Got error while rendering in Exists: %s", err)
log.Printf("[DEBUG] Returning false so the template re-renders using latest variables from config.")
return false, nil
} else {
return false, err
}
}
return hash(rendered) == d.Id(), nil
}
@ -87,6 +94,8 @@ func Read(d *schema.ResourceData, meta interface{}) error {
return nil
}
type templateRenderError error
var readfile func(string) ([]byte, error) = ioutil.ReadFile // testing hook
func render(d *schema.ResourceData) (string, error) {
@ -105,7 +114,9 @@ func render(d *schema.ResourceData) (string, error) {
rendered, err := execute(string(buf), vars)
if err != nil {
return "", fmt.Errorf("failed to render %v: %v", filename, err)
return "", templateRenderError(
fmt.Errorf("failed to render %v: %v", filename, err),
)
}
return rendered, nil

View File

@ -34,15 +34,7 @@ func TestTemplateRendering(t *testing.T) {
Providers: testProviders,
Steps: []r.TestStep{
r.TestStep{
Config: `
resource "template_file" "t0" {
filename = "mock"
vars = ` + tt.vars + `
}
output "rendered" {
value = "${template_file.t0.rendered}"
}
`,
Config: testTemplateConfig(tt.vars),
Check: func(s *terraform.State) error {
got := s.RootModule().Outputs["rendered"]
if tt.want != got {
@ -55,3 +47,55 @@ output "rendered" {
})
}
}
// https://github.com/hashicorp/terraform/issues/2344
func TestTemplateVariableChange(t *testing.T) {
steps := []struct {
vars string
template string
want string
}{
{`{a="foo"}`, `${a}`, `foo`},
{`{b="bar"}`, `${b}`, `bar`},
}
var testSteps []r.TestStep
for i, step := range steps {
testSteps = append(testSteps, r.TestStep{
PreConfig: func(template string) func() {
return func() {
readfile = func(string) ([]byte, error) {
return []byte(template), nil
}
}
}(step.template),
Config: testTemplateConfig(step.vars),
Check: func(i int, want string) r.TestCheckFunc {
return func(s *terraform.State) error {
got := s.RootModule().Outputs["rendered"]
if want != got {
return fmt.Errorf("[%d] got:\n%q\nwant:\n%q\n", i, got, want)
}
return nil
}
}(i, step.want),
})
}
r.Test(t, r.TestCase{
Providers: testProviders,
Steps: testSteps,
})
}
func testTemplateConfig(vars string) string {
return `
resource "template_file" "t0" {
filename = "mock"
vars = ` + vars + `
}
output "rendered" {
value = "${template_file.t0.rendered}"
}
`
}

176
deps/v0-5-3.json vendored
View File

@ -120,6 +120,182 @@
{
"ImportPath": "golang.org/x/crypto/ssh/terminal",
"Rev": "c10c31b5e94b6f7a0283272dc2bb27163dcea24b"
},
{
"ImportPath": "github.com/Sirupsen/logrus",
"Rev": "52919f182f9c314f8a38c5afe96506f73d02b4b2"
},
{
"ImportPath": "github.com/armon/circbuf",
"Rev": "f092b4f207b6e5cce0569056fba9e1a2735cb6cf"
},
{
"ImportPath": "github.com/cyberdelia/heroku-go",
"Rev": "594d483b9b6a8ddc7cd2f1e3e7d1de92fa2de665"
},
{
"ImportPath": "github.com/docker/docker",
"Rev": "42cfc95549728014811cc9aa2c5b07bdf5553a54"
},
{
"ImportPath": "github.com/dylanmei/iso8601",
"Rev": "2075bf119b58e5576c6ed9f867b8f3d17f2e54d4"
},
{
"ImportPath": "github.com/dylanmei/winrmtest",
"Rev": "3e9661c52c45dab9a8528966a23d421922fca9b9"
},
{
"ImportPath": "github.com/fsouza/go-dockerclient",
"Rev": "f90594a4da6a7cbdaedd29ee5495ddd6b39fe5d3"
},
{
"ImportPath": "github.com/hashicorp/atlas-go",
"Rev": "6a87d5f443991e9916104392cd5fc77678843e1d"
},
{
"ImportPath": "github.com/hashicorp/aws-sdk-go",
"Rev": "e6ea0192eee4640f32ec73c0cbb71f63e4f2b65a"
},
{
"ImportPath": "github.com/hashicorp/consul",
"Rev": "9417fd37686241d65918208874a7faa4d0cd92d2"
},
{
"ImportPath": "github.com/hashicorp/errwrap",
"Rev": "7554cd9344cec97297fa6649b055a8c98c2a1e55"
},
{
"ImportPath": "github.com/hashicorp/go-checkpoint",
"Rev": "88326f6851319068e7b34981032128c0b1a6524d"
},
{
"ImportPath": "github.com/hashicorp/go-multierror",
"Rev": "fcdddc395df1ddf4247c69bd436e84cfa0733f7e"
},
{
"ImportPath": "github.com/hashicorp/go-version",
"Rev": "999359b6b7a041ce16e695d51e92145b83f01087"
},
{
"ImportPath": "github.com/hashicorp/hcl",
"Rev": "513e04c400ee2e81e97f5e011c08fb42c6f69b84"
},
{
"ImportPath": "github.com/hashicorp/yamux",
"Rev": "b2e55852ddaf823a85c67f798080eb7d08acd71d"
},
{
"ImportPath": "github.com/imdario/mergo",
"Rev": "2fcac9923693d66dc0e03988a31b21da05cdea84"
},
{
"ImportPath": "github.com/masterzen/simplexml",
"Rev": "95ba30457eb1121fa27753627c774c7cd4e90083"
},
{
"ImportPath": "github.com/masterzen/winrm",
"Rev": "132339029dfa67fd39ff8edeed2af78f2cca4fbb"
},
{
"ImportPath": "github.com/masterzen/xmlpath",
"Rev": "13f4951698adc0fa9c1dda3e275d489a24201161"
},
{
"ImportPath": "github.com/mitchellh/cli",
"Rev": "6cc8bc522243675a2882b81662b0b0d2e04b99c9"
},
{
"ImportPath": "github.com/mitchellh/colorstring",
"Rev": "61164e49940b423ba1f12ddbdf01632ac793e5e9"
},
{
"ImportPath": "github.com/mitchellh/copystructure",
"Rev": "6fc66267e9da7d155a9d3bd489e00dad02666dc6"
},
{
"ImportPath": "github.com/mitchellh/go-homedir",
"Rev": "1f6da4a72e57d4e7edd4a7295a585e0a3999a2d4"
},
{
"ImportPath": "github.com/mitchellh/gox",
"Rev": "e8e6fd4fe12510cc46893dff18c5188a6a6dc549"
},
{
"ImportPath": "github.com/mitchellh/iochan",
"Rev": "b584a329b193e206025682ae6c10cdbe03b0cd77"
},
{
"ImportPath": "github.com/mitchellh/go-linereader",
"Rev": "07bab5fdd9580500aea6ada0e09df4aa28e68abd"
},
{
"ImportPath": "github.com/mitchellh/mapstructure",
"Rev": "442e588f213303bec7936deba67901f8fc8f18b1"
},
{
"ImportPath": "github.com/mitchellh/osext",
"Rev": "0dd3f918b21bec95ace9dc86c7e70266cfc5c702"
},
{
"ImportPath": "github.com/mitchellh/packer",
"Rev": "350a5f8cad6a0e4c2b24c3049a84c4f294416e16"
},
{
"ImportPath": "github.com/mitchellh/panicwrap",
"Rev": "45cbfd3bae250c7676c077fb275be1a2968e066a"
},
{
"ImportPath": "github.com/mitchellh/prefixedio",
"Rev": "89d9b535996bf0a185f85b59578f2e245f9e1724"
},
{
"ImportPath": "github.com/mitchellh/reflectwalk",
"Rev": "242be0c275dedfba00a616563e6db75ab8f279ec"
},
{
"ImportPath": "github.com/nu7hatch/gouuid",
"Rev": "179d4d0c4d8d407a32af483c2354df1d2c91e6c3"
},
{
"ImportPath": "github.com/packer-community/winrmcp",
"Rev": "650a91d1da6dc3fefa8f052289ffce648924a304"
},
{
"ImportPath": "github.com/pearkes/cloudflare",
"Rev": "19e280b056f3742e535ea12ae92a37ea7767ea82"
},
{
"ImportPath": "github.com/pearkes/digitalocean",
"Rev": "e966f00c2d9de5743e87697ab77c7278f5998914"
},
{
"ImportPath": "github.com/pearkes/dnsimple",
"Rev": "1e0c2b0eb33ca7b5632a130d6d34376a1ea46c84"
},
{
"ImportPath": "github.com/pearkes/mailgun",
"Rev": "5b02e7e9ffee9869f81393e80db138f6ff726260"
},
{
"ImportPath": "github.com/rackspace/gophercloud",
"Rev": "9ad4137a6b3e786b9c1e161b8d354b44482ab6d7"
},
{
"ImportPath": "github.com/satori/go.uuid",
"Rev": "7c7f2020c4c9491594b85767967f4619c2fa75f9"
},
{
"ImportPath": "github.com/soniah/dnsmadeeasy",
"Rev": "5578a8c15e33958c61cf7db720b6181af65f4a9e"
},
{
"ImportPath": "github.com/vaughan0/go-ini",
"Rev": "a98ad7ee00ec53921f08832bc06ecf7fd600e6a1"
},
{
"ImportPath": "github.com/xanzy/go-cloudstack",
"Rev": "f73f6ff1b843dbdac0a01da7b7f39883adfe2bdb"
}
]
}

View File

@ -60,6 +60,10 @@ type TestCase struct {
// potentially complex update logic. In general, simply create/destroy
// tests will only need one step.
type TestStep struct {
// PreConfig is called before the Config is applied to perform any per-step
// setup that needs to happen
PreConfig func()
// Config a string of the configuration to give to Terraform.
Config string
@ -160,6 +164,10 @@ func testStep(
opts terraform.ContextOpts,
state *terraform.State,
step TestStep) (*terraform.State, error) {
if step.PreConfig != nil {
step.PreConfig()
}
cfgPath, err := ioutil.TempDir("", "tf-test")
if err != nil {
return state, fmt.Errorf(

View File

@ -14,7 +14,7 @@ set of actions generated by a `terraform plan` execution plan.
## Usage
Usage: `terraform apply [options] [dir]`
Usage: `terraform apply [options] [dir-or-plan]`
By default, `apply` scans the current directory for the configuration
and applies the changes appropriately. However, a path to another configuration

View File

@ -18,9 +18,11 @@ Usage: `terraform destroy [options] [dir]`
Infrastructure managed by Terraform will be destroyed. This will ask for
confirmation before destroying.
This command accepts all the flags that the
[apply command](/docs/commands/apply.html) accepts. If `-force` is
set, then the destroy confirmation will not be shown.
This command accepts all the arguments and flags that the [apply
command](/docs/commands/apply.html) accepts, with the exception of a plan file
argument.
If `-force` is set, then the destroy confirmation will not be shown.
The `-target` flag, instead of affecting "dependencies" will instead also
destroy any resources that _depend on_ the target(s) specified.

View File

@ -0,0 +1,53 @@
---
layout: "aws"
page_title: "AWS: aws_autoscaling_policy"
sidebar_current: "docs-aws-resource-autoscaling-policy"
description: |-
Provides an AutoScaling Scaling Group resource.
---
# aws\_autoscaling\_policy
Provides an AutoScaling Scaling Policy resource.
~> **NOTE:** You may want to omit `desired_capacity` attribute from attached `aws_autoscaling_group`
when using autoscaling policies. It's good practice to pick either
[manual](http://docs.aws.amazon.com/AutoScaling/latest/DeveloperGuide/as-manual-scaling.html)
or [dynamic](http://docs.aws.amazon.com/AutoScaling/latest/DeveloperGuide/as-scale-based-on-demand.html)
(policy-based) scaling.
## Example Usage
```
resource "aws_autoscaling_policy" "bat" {
name = "foobar3-terraform-test"
scaling_adjustment = 4
adjustment_type = "ChangeInCapacity"
cooldown = 300
autoscaling_group_name = "${aws_autoscaling_group.bar.name}"
}
resource "aws_autoscaling_group" "bar" {
availability_zones = ["us-east-1a"]
name = "foobar3-terraform-test"
max_size = 5
min_size = 2
health_check_grace_period = 300
health_check_type = "ELB"
force_delete = true
launch_configuration = "${aws_launch_configuration.foo.name}"
}
```
## Argument Reference
The following arguments are supported:
* `name` - (Required) The name of the policy.
* `autoscaling_group_name` - (Required) The name or ARN of the group.
* `adjustment_type` - (Required) Specifies whether the `scaling_adjustment` is an absolute number or a percentage of the current capacity. Valid values are `ChangeInCapacity`, `ExactCapacity`, and `PercentChangeInCapacity`.
* `scaling_adjustment` - (Required) The number of instances by which to scale. `adjustment_type` determines the interpretation of this number (e.g., as an absolute number or as a percentage of the existing Auto Scaling group size). A positive increment adds to the current capacity and a negative value removes from the current capacity.
* `cooldown` - (Optional) The amount of time, in seconds, after a scaling activity completes and before the next scaling activity can start.
* `min_adjustment_step` - (Optional) Used with `adjustment_type` with the value `PercentChangeInCapacity`, the scaling policy changes the `desired_capacity` of the Auto Scaling group by at least the number of instances specified in the value.
## Attribute Reference
* `arn` - The ARN assigned by AWS to the scaling policy.

View File

@ -0,0 +1,75 @@
---
layout: "aws"
page_title: "AWS: cloudwatch_metric_alarm"
sidebar_current: "docs-aws-resource-cloudwatch-metric-alarm"
description: |-
Provides an AutoScaling Scaling Group resource.
---
# aws\_cloudwatch\_metric\_alarm
Provides a CloudWatch Metric Alarm resource.
## Example Usage
```
resource "aws_cloudwatch_metric_alarm" "foobar" {
alarm_name = "terraform-test-foobar5"
comparison_operator = "GreaterThanOrEqualToThreshold"
evaluation_periods = "2"
metric_name = "CPUUtilization"
namespace = "AWS/EC2"
period = "120"
statistic = "Average"
threshold = "80"
alarm_description = "This metric monitor ec2 cpu utilization"
insufficient_data_actions = []
}
```
## Example in Conjuction with Scaling Policies
```
resource "aws_autoscaling_policy" "bat" {
name = "foobar3-terraform-test"
scaling_adjustment = 4
adjustment_type = "ChangeInCapacity"
cooldown = 300
autoscaling_group_name = "${aws_autoscaling_group.bar.name}"
}
resource "aws_cloudwatch_metric_alarm" "bat" {
alarm_name = "terraform-test-foobar5"
comparison_operator = "GreaterThanOrEqualToThreshold"
evaluation_periods = "2"
metric_name = "CPUUtilization"
namespace = "AWS/EC2"
period = "120"
statistic = "Average"
threshold = "80"
alarm_description = "This metric monitor ec2 cpu utilization"
alarm_actions = ["${aws_autoscaling_policy.bat.arn}"]
}
```
## Argument Reference
See [related part of AWS Docs](http://docs.aws.amazon.com/AmazonCloudWatch/latest/APIReference/API_PutMetricAlarm.html)
for details about valid values.
The following arguments are supported:
* `alarm_name` - (Required) The descriptive name for the alarm. This name must be unique within the user's AWS account
* `comparison_operator` - (Required) The arithmetic operation to use when comparing the specified Statistic and Threshold. The specified Statistic value is used as the first operand. Either of the following is supported: `GreaterThanOrEqualToThreshold`, `GreaterThanThreshold`, `LessThanThreshold`, `LessThanOrEqualToThreshold`.
* `evaluation_periods` - (Required) The number of periods over which data is compared to the specified threshold.
* `metric_name` - (Required) The name for the alarm's associated metric.
See docs for [supported metrics]([valid metrics](http://docs.aws.amazon.com/AmazonCloudWatch/latest/DeveloperGuide/CW_Support_For_AWS.html)).
* `namespace` - (Required) The namespace for the alarm's associated metric.
* `period` - (Required) The period in seconds over which the specified `statistic` is applied.
* `statistic` - (Required) The statistic to apply to the alarm's associated metric.
Either of the following is supported: `SampleCount`, `Average`, `Sum`, `Minimum`, `Maximum`
* `threshold` - (Required) The value against which the specified statistic is compared.
* `actions_enabled` - (Optional) Indicates whether or not actions should be executed during any changes to the alarm's state. Defaults to `true`.
* `alarm_actions` - (Optional) The list of actions to execute when this alarm transitions into an ALARM state from any other state. Each action is specified as an Amazon Resource Number (ARN).
* `alarm_description` - (Optional) The description for the alarm.
* `dimensions` - (Optional) The dimensions for the alarm's associated metric.
* `insufficient_data_actions` - (Optional) The list of actions to execute when this alarm transitions into an INSUFFICIENT_DATA state from any other state. Each action is specified as an Amazon Resource Number (ARN).
* `ok_actions` - (Optional) The list of actions to execute when this alarm transitions into an OK state from any other state. Each action is specified as an Amazon Resource Number (ARN).
* `unit` - (Optional) The unit for the alarm's associated metric.

View File

@ -0,0 +1,109 @@
---
layout: "aws"
page_title: "AWS: dynamodb_table"
sidebar_current: "docs-aws-resource-dynamodb-table"
description: |-
Provides a DynamoDB table resource
---
# aws\_dynamodb\_table
Provides a DynamoDB table resource
## Example Usage
The following dynamodb table description models the table and GSI shown
in the [AWS SDK example documentation](http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/GSI.html)
```
resource "aws_dynamodb_table" "basic-dynamodb-table" {
name = "GameScores"
read_capacity = 20
write_capacity = 20
hash_key = "UserId"
range_key = "GameTitle"
attribute {
name = "Username"
type = "S"
}
attribute {
name = "GameTitle"
type = "S"
}
attribute {
name = "TopScore"
type = "N"
}
attribute {
name = "TopScoreDateTime"
type = "S"
}
attribute {
name = "Wins"
type = "N"
}
attribute {
name = "Losses"
type = "N"
}
global_secondary_index {
name = "GameTitleIndex"
hash_key = "GameTitle"
range_key = "TopScore"
write_capacity = 10
read_capacity = 10
projection_type = "INCLUDE"
non_key_attributes = [ "UserId" ]
}
}
```
## Argument Reference
The following arguments are supported:
* `name` - (Required) The name of the table, this needs to be unique
within a region.
* `read_capacity` - (Required) The number of read units for this table
* `write_capacity` - (Required) The number of write units for this table
* `hash_key` - (Required) The attribute to use as the hash key (the
attribute must also be defined as an attribute record
* `range_key` - (Optional) The attribute to use as the range key (must
also be defined)
* `attribute` - Define an attribute, has two properties:
* `name` - The name of the attribute
* `type` - One of: S, N, or B for (S)tring, (N)umber or (B)inary data
* `local_secondary_index` - (Optional) Describe an LSI on the table;
these can only be allocated *at creation* so you cannot change this
definition after you have created the resource.
* `global_secondary_index` - (Optional) Describe a GSO for the table;
subject to the normal limits on the number of GSIs, projected
attributes, etc.
For both `local_secondary_index` and `global_secondary_index` objects,
the following properties are supported:
* `name` - (Required) The name of the LSI or GSI
* `hash_key` - (Required) The name of the hash key in the index; must be
defined as an attribute in the resource
* `range_key` - (Required) The name of the range key; must be defined
* `projection_type` - (Required) One of "ALL", "INCLUDE" or "KEYS_ONLY"
where *ALL* projects every attribute into the index, *KEYS_ONLY*
projects just the hash and range key into the index, and *INCLUDE*
projects only the keys specified in the _non_key_attributes_
parameter.
* `non_key_attributes` - (Optional) Only required with *INCLUDE* as a
projection type; a list of attributes to project into the index. For
each attribute listed, you need to make sure that it has been defined in
the table object.
For `global_secondary_index` objects only, you need to specify
`write_capacity` and `read_capacity` in the same way you would for the
table as they have separate I/O capacity.
## Attributes Reference
The following attributes are exported:
* `id` - The name of the table

View File

@ -0,0 +1,50 @@
---
layout: "azure"
page_title: "Azure: azure_sql_database_server"
sidebar_current: "docs-azure-sql-database-server"
description: |-
Allocates a new SQL Database Server on Azure.
---
# azure\_sql\_database\_server
Allocates a new SQL Database Server on Azure.
## Example Usage
```
resource "azure_sql_database_server" "sql-serv" {
name = "<computed>"
location = "West US"
username = "SuperUser"
password = "SuperSEKR3T"
version = "2.0"
url = "<computed>"
}
```
## Argument Reference
The following arguments are supported:
* `name` - (Computed) The name of the database server. It is determined upon
creation as it is randomly-generated per server.
* `location` - (Required) The location where the database server should be created.
For a list of all Azure locations, please consult [this link](http://azure.microsoft.com/en-us/regions/).
* `username` - (Required) The username for the administrator of the database server.
* `password` - (Required) The password for the administrator of the database server.
* `version` - (Optional) The version of the database server to be used. Can be any
one of `2.0` or `12.0`.
* `url` - (Computed) The fully qualified domain name of the database server.
Will be of the form `<name>.database.windows.net`.
## Attributes Reference
The following attributes are exported:
* `id` - The database server ID. Coincides with the randomly-generated `name`.

View File

@ -0,0 +1,53 @@
---
layout: "azure"
page_title: "Azure: azure_sql_database_service"
sidebar_current: "docs-azure-sql-database-service"
description: |-
Creates a new SQL Database Service on an Azure Database Server.
---
# azure\_sql\_database\_service
Creates a new SQL database service on an Azure database server.
## Example Usage
```
resource "azure_sql_database_service" "sql-server" {
name = "terraform-testing-db-renamed"
database_server_name = "flibberflabber"
edition = "Standard"
collation = "SQL_Latin1_General_CP1_CI_AS"
max_size_bytes = "5368709120"
service_level_id = "f1173c43-91bd-4aaa-973c-54e79e15235b"
}
```
## Argument Reference
The following arguments are supported:
* `name` - (Required) The name of the database service.
* `database_server_name` - (Required) The name of the database server this service
should run on. Changes here force the creation of a new resource.
* `edition` - (Optional) The edition of the database service. For more information
on each variant, please view [this](https://msdn.microsoft.com/library/azure/dn741340.aspx) link.
* `collation` - (Optional) The collation to be used within the database service.
Defaults to the standard Latin charset.
* `max_size_bytes` - (Optional) The maximum size in bytes the database service
should be allowed to expand to. Range depends on the database `edition`
selected above.
* `service_level_id` - (Optional) The ID corresponding to the service level per
edition. Please refer to [this](https://msdn.microsoft.com/en-us/library/azure/dn505701.aspx) link for more details.
## Attributes Reference
The following attributes are exported:
* `id` - The database service ID. Coincides with the given `name`.

View File

@ -17,6 +17,12 @@ description: |-
operating system and architecture. You can find SHA256 checksums
for packages <a href="https://dl.bintray.com/mitchellh/terraform/terraform_<%= latest_version %>_SHA256SUMS?direct">here</a>.
</p>
<p>
Each release archive is a <code>zip</code> file containing the Terraform binary
executables at the top level. These executables are meant to be extracted
to a location where they can be found by your shell.
</p>
</div>
</div>
<% product_versions.each do |os, versions| %>

View File

@ -57,4 +57,10 @@ If you get an error that `terraform` could not be found, then your PATH
environment variable was not setup properly. Please go back and ensure
that your PATH variable contains the directory where Terraform was installed.
Otherwise, Terraform is installed and ready to go!
Otherwise, Terraform is installed and ready to go! Nice!
## Next Step
Time to [build infrastructure](/intro/getting-started/build.html)
using a minimal Terraform configuration file. You will be able to
examine Terraform's execution plan before you deploy it to AWS.

View File

@ -21,6 +21,14 @@
<a href="/docs/providers/aws/r/autoscaling_notification.html">aws_autoscaling_notification</a>
</li>
<li<%= sidebar_current("docs-aws-resource-autoscaling-policy") %>>
<a href="/docs/providers/aws/r/autoscaling_policy.html">aws_autoscaling_policy</a>
</li>
<li<%= sidebar_current("docs-aws-resource-cloudwatch-metric-alarm") %>>
<a href="/docs/providers/aws/r/cloudwatch_metric_alarm.html">aws_cloudwatch_metric_alarm</a>
</li>
<li<%= sidebar_current("docs-aws-resource-customer-gateway") %>>
<a href="/docs/providers/aws/r/customer_gateway.html">aws_customer_gateway</a>
</li>
@ -41,6 +49,10 @@
<a href="/docs/providers/aws/r/db_subnet_group.html">aws_db_subnet_group</a>
</li>
<li<%= sidebar_current("docs-aws-resource-dynamodb-table") %>>
<a href="/docs/providers/aws/r/dynamodb_table.html">aws_dynamodb_table</a>
</li>
<li<%= sidebar_current("docs-aws-resource-ebs-volume") %>>
<a href="/docs/providers/aws/r/ebs_volume.html">aws_ebs_volume</a>
</li>

View File

@ -41,6 +41,14 @@
<a href="/docs/providers/azure/r/security_group_rule.html">azure_security_group_rule</a>
</li>
<li<%= sidebar_current("docs-azure-resource-sql-database-server") %>>
<a href="/docs/providers/azure/r/sql_database_server.html">azure_sql_database_server</a>
</li>
<li<%= sidebar_current("docs-azure-resource-sql-database-service") %>>
<a href="/docs/providers/azure/r/sql_database_service.html">azure_sql_database_service</a>
</li>
<li<%= sidebar_current("docs-azure-resource-storage-blob") %>>
<a href="/docs/providers/azure/r/storage_blob.html">azure_storage_blob</a>
</li>