Merge branch 'master' into aws-go-rds-instance
* master: providers/aws: Convert Launch Configurations to awslabs/aws-sdk-go update CHANGELOG terraform: test post state update is called command: StateHook for continous state updates terraform: more state tests, fix a bug state: deep copies are required terraform: make DeepCopy public state/remote: increment serial properly state: only change serial if changed terraform: call the EvalUpdateStateHook strategically terraform: PostStateUpdate hook and EvalUpdateStateHook
This commit is contained in:
commit
4064d29050
|
@ -4,11 +4,18 @@ FEATURES:
|
||||||
|
|
||||||
* **Self-variables** can be used to reference the current resource's
|
* **Self-variables** can be used to reference the current resource's
|
||||||
attributes within a provisioner. Ex. `${self.private_ip_address}` [GH-1033]
|
attributes within a provisioner. Ex. `${self.private_ip_address}` [GH-1033]
|
||||||
|
* **Continous state** saving during `terraform apply`. The state file is
|
||||||
|
continously updated as apply is running, meaning that the state is
|
||||||
|
less likely to become corrupt in a catastrophic case: terraform panic
|
||||||
|
or system killing Terraform.
|
||||||
|
|
||||||
IMPROVEMENTS:
|
IMPROVEMENTS:
|
||||||
|
|
||||||
* **New config function: `split`** - Split a value based on a delimiter.
|
* **New config function: `split`** - Split a value based on a delimiter.
|
||||||
This is useful for faking lists as parameters to modules.
|
This is useful for faking lists as parameters to modules.
|
||||||
|
* core: The serial of the state is only updated if there is an actual
|
||||||
|
change. This will lower the amount of state changing on things
|
||||||
|
like refresh.
|
||||||
|
|
||||||
BUG FIXES:
|
BUG FIXES:
|
||||||
|
|
||||||
|
|
|
@ -7,14 +7,13 @@ import (
|
||||||
"unicode"
|
"unicode"
|
||||||
|
|
||||||
"github.com/hashicorp/terraform/helper/multierror"
|
"github.com/hashicorp/terraform/helper/multierror"
|
||||||
"github.com/mitchellh/goamz/autoscaling"
|
|
||||||
"github.com/mitchellh/goamz/aws"
|
"github.com/mitchellh/goamz/aws"
|
||||||
"github.com/mitchellh/goamz/ec2"
|
"github.com/mitchellh/goamz/ec2"
|
||||||
"github.com/mitchellh/goamz/elb"
|
"github.com/mitchellh/goamz/elb"
|
||||||
"github.com/mitchellh/goamz/rds"
|
"github.com/mitchellh/goamz/rds"
|
||||||
|
|
||||||
awsGo "github.com/awslabs/aws-sdk-go/aws"
|
awsGo "github.com/awslabs/aws-sdk-go/aws"
|
||||||
awsAutoScaling "github.com/awslabs/aws-sdk-go/gen/autoscaling"
|
"github.com/awslabs/aws-sdk-go/gen/autoscaling"
|
||||||
awsRDS "github.com/awslabs/aws-sdk-go/gen/rds"
|
awsRDS "github.com/awslabs/aws-sdk-go/gen/rds"
|
||||||
"github.com/awslabs/aws-sdk-go/gen/route53"
|
"github.com/awslabs/aws-sdk-go/gen/route53"
|
||||||
"github.com/awslabs/aws-sdk-go/gen/s3"
|
"github.com/awslabs/aws-sdk-go/gen/s3"
|
||||||
|
@ -27,15 +26,14 @@ type Config struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type AWSClient struct {
|
type AWSClient struct {
|
||||||
ec2conn *ec2.EC2
|
ec2conn *ec2.EC2
|
||||||
elbconn *elb.ELB
|
elbconn *elb.ELB
|
||||||
autoscalingconn *autoscaling.AutoScaling
|
autoscalingconn *autoscaling.AutoScaling
|
||||||
s3conn *s3.S3
|
s3conn *s3.S3
|
||||||
rdsconn *rds.Rds
|
rdsconn *rds.Rds
|
||||||
r53conn *route53.Route53
|
r53conn *route53.Route53
|
||||||
region string
|
region string
|
||||||
awsAutoScalingconn *awsAutoScaling.AutoScaling
|
awsRDSconn *awsRDS.RDS
|
||||||
awsRDSconn *awsRDS.RDS
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Client configures and returns a fully initailized AWSClient
|
// Client configures and returns a fully initailized AWSClient
|
||||||
|
@ -69,7 +67,7 @@ func (c *Config) Client() (interface{}, error) {
|
||||||
log.Println("[INFO] Initializing ELB connection")
|
log.Println("[INFO] Initializing ELB connection")
|
||||||
client.elbconn = elb.New(auth, region)
|
client.elbconn = elb.New(auth, region)
|
||||||
log.Println("[INFO] Initializing AutoScaling connection")
|
log.Println("[INFO] Initializing AutoScaling connection")
|
||||||
client.autoscalingconn = autoscaling.New(auth, region)
|
client.autoscalingconn = autoscaling.New(creds, c.Region, nil)
|
||||||
log.Println("[INFO] Initializing S3 connection")
|
log.Println("[INFO] Initializing S3 connection")
|
||||||
client.s3conn = s3.New(creds, c.Region, nil)
|
client.s3conn = s3.New(creds, c.Region, nil)
|
||||||
log.Println("[INFO] Initializing RDS connection")
|
log.Println("[INFO] Initializing RDS connection")
|
||||||
|
@ -80,8 +78,6 @@ func (c *Config) Client() (interface{}, error) {
|
||||||
// See http://docs.aws.amazon.com/general/latest/gr/sigv4_changes.html
|
// See http://docs.aws.amazon.com/general/latest/gr/sigv4_changes.html
|
||||||
log.Println("[INFO] Initializing Route53 connection")
|
log.Println("[INFO] Initializing Route53 connection")
|
||||||
client.r53conn = route53.New(creds, "us-east-1", nil)
|
client.r53conn = route53.New(creds, "us-east-1", nil)
|
||||||
log.Println("[INFO] Initializing AWS Go AutoScaling connection")
|
|
||||||
client.awsAutoScalingconn = awsAutoScaling.New(creds, c.Region, nil)
|
|
||||||
log.Println("[INFO] Initializing AWS Go RDS connection")
|
log.Println("[INFO] Initializing AWS Go RDS connection")
|
||||||
client.awsRDSconn = awsRDS.New(creds, c.Region, nil)
|
client.awsRDSconn = awsRDS.New(creds, c.Region, nil)
|
||||||
}
|
}
|
||||||
|
|
|
@ -123,7 +123,7 @@ func resourceAwsAutoscalingGroup() *schema.Resource {
|
||||||
}
|
}
|
||||||
|
|
||||||
func resourceAwsAutoscalingGroupCreate(d *schema.ResourceData, meta interface{}) error {
|
func resourceAwsAutoscalingGroupCreate(d *schema.ResourceData, meta interface{}) error {
|
||||||
autoscalingconn := meta.(*AWSClient).awsAutoScalingconn
|
autoscalingconn := meta.(*AWSClient).autoscalingconn
|
||||||
|
|
||||||
var autoScalingGroupOpts autoscaling.CreateAutoScalingGroupType
|
var autoScalingGroupOpts autoscaling.CreateAutoScalingGroupType
|
||||||
autoScalingGroupOpts.AutoScalingGroupName = aws.String(d.Get("name").(string))
|
autoScalingGroupOpts.AutoScalingGroupName = aws.String(d.Get("name").(string))
|
||||||
|
@ -199,7 +199,7 @@ func resourceAwsAutoscalingGroupRead(d *schema.ResourceData, meta interface{}) e
|
||||||
}
|
}
|
||||||
|
|
||||||
func resourceAwsAutoscalingGroupUpdate(d *schema.ResourceData, meta interface{}) error {
|
func resourceAwsAutoscalingGroupUpdate(d *schema.ResourceData, meta interface{}) error {
|
||||||
autoscalingconn := meta.(*AWSClient).awsAutoScalingconn
|
autoscalingconn := meta.(*AWSClient).autoscalingconn
|
||||||
|
|
||||||
opts := autoscaling.UpdateAutoScalingGroupType{
|
opts := autoscaling.UpdateAutoScalingGroupType{
|
||||||
AutoScalingGroupName: aws.String(d.Id()),
|
AutoScalingGroupName: aws.String(d.Id()),
|
||||||
|
@ -232,7 +232,7 @@ func resourceAwsAutoscalingGroupUpdate(d *schema.ResourceData, meta interface{})
|
||||||
}
|
}
|
||||||
|
|
||||||
func resourceAwsAutoscalingGroupDelete(d *schema.ResourceData, meta interface{}) error {
|
func resourceAwsAutoscalingGroupDelete(d *schema.ResourceData, meta interface{}) error {
|
||||||
autoscalingconn := meta.(*AWSClient).awsAutoScalingconn
|
autoscalingconn := meta.(*AWSClient).autoscalingconn
|
||||||
|
|
||||||
// Read the autoscaling group first. If it doesn't exist, we're done.
|
// Read the autoscaling group first. If it doesn't exist, we're done.
|
||||||
// We need the group in order to check if there are instances attached.
|
// We need the group in order to check if there are instances attached.
|
||||||
|
@ -276,7 +276,7 @@ func resourceAwsAutoscalingGroupDelete(d *schema.ResourceData, meta interface{})
|
||||||
func getAwsAutoscalingGroup(
|
func getAwsAutoscalingGroup(
|
||||||
d *schema.ResourceData,
|
d *schema.ResourceData,
|
||||||
meta interface{}) (*autoscaling.AutoScalingGroup, error) {
|
meta interface{}) (*autoscaling.AutoScalingGroup, error) {
|
||||||
autoscalingconn := meta.(*AWSClient).awsAutoScalingconn
|
autoscalingconn := meta.(*AWSClient).autoscalingconn
|
||||||
|
|
||||||
describeOpts := autoscaling.AutoScalingGroupNamesType{
|
describeOpts := autoscaling.AutoScalingGroupNamesType{
|
||||||
AutoScalingGroupNames: []string{d.Id()},
|
AutoScalingGroupNames: []string{d.Id()},
|
||||||
|
@ -307,7 +307,7 @@ func getAwsAutoscalingGroup(
|
||||||
}
|
}
|
||||||
|
|
||||||
func resourceAwsAutoscalingGroupDrain(d *schema.ResourceData, meta interface{}) error {
|
func resourceAwsAutoscalingGroupDrain(d *schema.ResourceData, meta interface{}) error {
|
||||||
autoscalingconn := meta.(*AWSClient).awsAutoScalingconn
|
autoscalingconn := meta.(*AWSClient).autoscalingconn
|
||||||
|
|
||||||
// First, set the capacity to zero so the group will drain
|
// First, set the capacity to zero so the group will drain
|
||||||
log.Printf("[DEBUG] Reducing autoscaling group capacity to zero")
|
log.Printf("[DEBUG] Reducing autoscaling group capacity to zero")
|
||||||
|
|
|
@ -4,9 +4,10 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/awslabs/aws-sdk-go/aws"
|
||||||
|
"github.com/awslabs/aws-sdk-go/gen/autoscaling"
|
||||||
"github.com/hashicorp/terraform/helper/resource"
|
"github.com/hashicorp/terraform/helper/resource"
|
||||||
"github.com/hashicorp/terraform/terraform"
|
"github.com/hashicorp/terraform/terraform"
|
||||||
"github.com/mitchellh/goamz/autoscaling"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestAccAWSAutoScalingGroup_basic(t *testing.T) {
|
func TestAccAWSAutoScalingGroup_basic(t *testing.T) {
|
||||||
|
@ -51,8 +52,7 @@ func TestAccAWSAutoScalingGroup_basic(t *testing.T) {
|
||||||
testAccCheckAWSLaunchConfigurationExists("aws_launch_configuration.new", &lc),
|
testAccCheckAWSLaunchConfigurationExists("aws_launch_configuration.new", &lc),
|
||||||
resource.TestCheckResourceAttr(
|
resource.TestCheckResourceAttr(
|
||||||
"aws_autoscaling_group.bar", "desired_capacity", "5"),
|
"aws_autoscaling_group.bar", "desired_capacity", "5"),
|
||||||
resource.TestCheckResourceAttrPtr(
|
testLaunchConfigurationName("aws_autoscaling_group.bar", &lc),
|
||||||
"aws_autoscaling_group.bar", "launch_configuration", &lc.Name),
|
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -87,19 +87,19 @@ func testAccCheckAWSAutoScalingGroupDestroy(s *terraform.State) error {
|
||||||
|
|
||||||
// Try to find the Group
|
// Try to find the Group
|
||||||
describeGroups, err := conn.DescribeAutoScalingGroups(
|
describeGroups, err := conn.DescribeAutoScalingGroups(
|
||||||
&autoscaling.DescribeAutoScalingGroups{
|
&autoscaling.AutoScalingGroupNamesType{
|
||||||
Names: []string{rs.Primary.ID},
|
AutoScalingGroupNames: []string{rs.Primary.ID},
|
||||||
})
|
})
|
||||||
|
|
||||||
if err == nil {
|
if err == nil {
|
||||||
if len(describeGroups.AutoScalingGroups) != 0 &&
|
if len(describeGroups.AutoScalingGroups) != 0 &&
|
||||||
describeGroups.AutoScalingGroups[0].Name == rs.Primary.ID {
|
*describeGroups.AutoScalingGroups[0].AutoScalingGroupName == rs.Primary.ID {
|
||||||
return fmt.Errorf("AutoScaling Group still exists")
|
return fmt.Errorf("AutoScaling Group still exists")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify the error
|
// Verify the error
|
||||||
ec2err, ok := err.(*autoscaling.Error)
|
ec2err, ok := err.(aws.APIError)
|
||||||
if !ok {
|
if !ok {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -117,32 +117,32 @@ func testAccCheckAWSAutoScalingGroupAttributes(group *autoscaling.AutoScalingGro
|
||||||
return fmt.Errorf("Bad availability_zones: %s", group.AvailabilityZones[0])
|
return fmt.Errorf("Bad availability_zones: %s", group.AvailabilityZones[0])
|
||||||
}
|
}
|
||||||
|
|
||||||
if group.Name != "foobar3-terraform-test" {
|
if *group.AutoScalingGroupName != "foobar3-terraform-test" {
|
||||||
return fmt.Errorf("Bad name: %s", group.Name)
|
return fmt.Errorf("Bad name: %s", *group.AutoScalingGroupName)
|
||||||
}
|
}
|
||||||
|
|
||||||
if group.MaxSize != 5 {
|
if *group.MaxSize != 5 {
|
||||||
return fmt.Errorf("Bad max_size: %d", group.MaxSize)
|
return fmt.Errorf("Bad max_size: %d", *group.MaxSize)
|
||||||
}
|
}
|
||||||
|
|
||||||
if group.MinSize != 2 {
|
if *group.MinSize != 2 {
|
||||||
return fmt.Errorf("Bad max_size: %d", group.MinSize)
|
return fmt.Errorf("Bad max_size: %d", *group.MinSize)
|
||||||
}
|
}
|
||||||
|
|
||||||
if group.HealthCheckType != "ELB" {
|
if *group.HealthCheckType != "ELB" {
|
||||||
return fmt.Errorf("Bad health_check_type: %s", group.HealthCheckType)
|
return fmt.Errorf("Bad health_check_type: %s", *group.HealthCheckType)
|
||||||
}
|
}
|
||||||
|
|
||||||
if group.HealthCheckGracePeriod != 300 {
|
if *group.HealthCheckGracePeriod != 300 {
|
||||||
return fmt.Errorf("Bad health_check_grace_period: %d", group.HealthCheckGracePeriod)
|
return fmt.Errorf("Bad health_check_grace_period: %d", *group.HealthCheckGracePeriod)
|
||||||
}
|
}
|
||||||
|
|
||||||
if group.DesiredCapacity != 4 {
|
if *group.DesiredCapacity != 4 {
|
||||||
return fmt.Errorf("Bad desired_capacity: %d", group.DesiredCapacity)
|
return fmt.Errorf("Bad desired_capacity: %d", *group.DesiredCapacity)
|
||||||
}
|
}
|
||||||
|
|
||||||
if group.LaunchConfigurationName == "" {
|
if *group.LaunchConfigurationName == "" {
|
||||||
return fmt.Errorf("Bad launch configuration name: %s", group.LaunchConfigurationName)
|
return fmt.Errorf("Bad launch configuration name: %s", *group.LaunchConfigurationName)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
@ -172,8 +172,8 @@ func testAccCheckAWSAutoScalingGroupExists(n string, group *autoscaling.AutoScal
|
||||||
|
|
||||||
conn := testAccProvider.Meta().(*AWSClient).autoscalingconn
|
conn := testAccProvider.Meta().(*AWSClient).autoscalingconn
|
||||||
|
|
||||||
describeOpts := autoscaling.DescribeAutoScalingGroups{
|
describeOpts := autoscaling.AutoScalingGroupNamesType{
|
||||||
Names: []string{rs.Primary.ID},
|
AutoScalingGroupNames: []string{rs.Primary.ID},
|
||||||
}
|
}
|
||||||
describeGroups, err := conn.DescribeAutoScalingGroups(&describeOpts)
|
describeGroups, err := conn.DescribeAutoScalingGroups(&describeOpts)
|
||||||
|
|
||||||
|
@ -182,7 +182,7 @@ func testAccCheckAWSAutoScalingGroupExists(n string, group *autoscaling.AutoScal
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(describeGroups.AutoScalingGroups) != 1 ||
|
if len(describeGroups.AutoScalingGroups) != 1 ||
|
||||||
describeGroups.AutoScalingGroups[0].Name != rs.Primary.ID {
|
*describeGroups.AutoScalingGroups[0].AutoScalingGroupName != rs.Primary.ID {
|
||||||
return fmt.Errorf("AutoScaling Group not found")
|
return fmt.Errorf("AutoScaling Group not found")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -192,6 +192,21 @@ func testAccCheckAWSAutoScalingGroupExists(n string, group *autoscaling.AutoScal
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func testLaunchConfigurationName(n string, lc *autoscaling.LaunchConfiguration) resource.TestCheckFunc {
|
||||||
|
return func(s *terraform.State) error {
|
||||||
|
rs, ok := s.RootModule().Resources[n]
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("Not found: %s", n)
|
||||||
|
}
|
||||||
|
|
||||||
|
if *lc.LaunchConfigurationName != rs.Primary.Attributes["launch_configuration"] {
|
||||||
|
return fmt.Errorf("Launch configuration names do not match")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const testAccAWSAutoScalingGroupConfig = `
|
const testAccAWSAutoScalingGroupConfig = `
|
||||||
resource "aws_launch_configuration" "foobar" {
|
resource "aws_launch_configuration" "foobar" {
|
||||||
name = "foobarautoscaling-terraform-test"
|
name = "foobarautoscaling-terraform-test"
|
||||||
|
|
|
@ -2,15 +2,17 @@ package aws
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/sha1"
|
"crypto/sha1"
|
||||||
|
"encoding/base64"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/awslabs/aws-sdk-go/aws"
|
||||||
|
"github.com/awslabs/aws-sdk-go/gen/autoscaling"
|
||||||
"github.com/hashicorp/terraform/helper/hashcode"
|
"github.com/hashicorp/terraform/helper/hashcode"
|
||||||
"github.com/hashicorp/terraform/helper/resource"
|
"github.com/hashicorp/terraform/helper/resource"
|
||||||
"github.com/hashicorp/terraform/helper/schema"
|
"github.com/hashicorp/terraform/helper/schema"
|
||||||
"github.com/mitchellh/goamz/autoscaling"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func resourceAwsLaunchConfiguration() *schema.Resource {
|
func resourceAwsLaunchConfiguration() *schema.Resource {
|
||||||
|
@ -94,15 +96,26 @@ func resourceAwsLaunchConfiguration() *schema.Resource {
|
||||||
func resourceAwsLaunchConfigurationCreate(d *schema.ResourceData, meta interface{}) error {
|
func resourceAwsLaunchConfigurationCreate(d *schema.ResourceData, meta interface{}) error {
|
||||||
autoscalingconn := meta.(*AWSClient).autoscalingconn
|
autoscalingconn := meta.(*AWSClient).autoscalingconn
|
||||||
|
|
||||||
var createLaunchConfigurationOpts autoscaling.CreateLaunchConfiguration
|
var createLaunchConfigurationOpts autoscaling.CreateLaunchConfigurationType
|
||||||
createLaunchConfigurationOpts.Name = d.Get("name").(string)
|
createLaunchConfigurationOpts.LaunchConfigurationName = aws.String(d.Get("name").(string))
|
||||||
createLaunchConfigurationOpts.IamInstanceProfile = d.Get("iam_instance_profile").(string)
|
createLaunchConfigurationOpts.ImageID = aws.String(d.Get("image_id").(string))
|
||||||
createLaunchConfigurationOpts.ImageId = d.Get("image_id").(string)
|
createLaunchConfigurationOpts.InstanceType = aws.String(d.Get("instance_type").(string))
|
||||||
createLaunchConfigurationOpts.InstanceType = d.Get("instance_type").(string)
|
|
||||||
createLaunchConfigurationOpts.KeyName = d.Get("key_name").(string)
|
if v, ok := d.GetOk("user_data"); ok {
|
||||||
createLaunchConfigurationOpts.UserData = d.Get("user_data").(string)
|
createLaunchConfigurationOpts.UserData = aws.String(base64.StdEncoding.EncodeToString([]byte(v.(string))))
|
||||||
createLaunchConfigurationOpts.AssociatePublicIpAddress = d.Get("associate_public_ip_address").(bool)
|
}
|
||||||
createLaunchConfigurationOpts.SpotPrice = d.Get("spot_price").(string)
|
if v, ok := d.GetOk("associate_public_ip_address"); ok {
|
||||||
|
createLaunchConfigurationOpts.AssociatePublicIPAddress = aws.Boolean(v.(bool))
|
||||||
|
}
|
||||||
|
if v, ok := d.GetOk("iam_instance_profile"); ok {
|
||||||
|
createLaunchConfigurationOpts.IAMInstanceProfile = aws.String(v.(string))
|
||||||
|
}
|
||||||
|
if v, ok := d.GetOk("key_name"); ok {
|
||||||
|
createLaunchConfigurationOpts.KeyName = aws.String(v.(string))
|
||||||
|
}
|
||||||
|
if v, ok := d.GetOk("spot_price"); ok {
|
||||||
|
createLaunchConfigurationOpts.SpotPrice = aws.String(v.(string))
|
||||||
|
}
|
||||||
|
|
||||||
if v, ok := d.GetOk("security_groups"); ok {
|
if v, ok := d.GetOk("security_groups"); ok {
|
||||||
createLaunchConfigurationOpts.SecurityGroups = expandStringList(
|
createLaunchConfigurationOpts.SecurityGroups = expandStringList(
|
||||||
|
@ -110,7 +123,7 @@ func resourceAwsLaunchConfigurationCreate(d *schema.ResourceData, meta interface
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Printf("[DEBUG] autoscaling create launch configuration: %#v", createLaunchConfigurationOpts)
|
log.Printf("[DEBUG] autoscaling create launch configuration: %#v", createLaunchConfigurationOpts)
|
||||||
_, err := autoscalingconn.CreateLaunchConfiguration(&createLaunchConfigurationOpts)
|
err := autoscalingconn.CreateLaunchConfiguration(&createLaunchConfigurationOpts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("Error creating launch configuration: %s", err)
|
return fmt.Errorf("Error creating launch configuration: %s", err)
|
||||||
}
|
}
|
||||||
|
@ -128,8 +141,8 @@ func resourceAwsLaunchConfigurationCreate(d *schema.ResourceData, meta interface
|
||||||
func resourceAwsLaunchConfigurationRead(d *schema.ResourceData, meta interface{}) error {
|
func resourceAwsLaunchConfigurationRead(d *schema.ResourceData, meta interface{}) error {
|
||||||
autoscalingconn := meta.(*AWSClient).autoscalingconn
|
autoscalingconn := meta.(*AWSClient).autoscalingconn
|
||||||
|
|
||||||
describeOpts := autoscaling.DescribeLaunchConfigurations{
|
describeOpts := autoscaling.LaunchConfigurationNamesType{
|
||||||
Names: []string{d.Id()},
|
LaunchConfigurationNames: []string{d.Id()},
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Printf("[DEBUG] launch configuration describe configuration: %#v", describeOpts)
|
log.Printf("[DEBUG] launch configuration describe configuration: %#v", describeOpts)
|
||||||
|
@ -143,7 +156,7 @@ func resourceAwsLaunchConfigurationRead(d *schema.ResourceData, meta interface{}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify AWS returned our launch configuration
|
// Verify AWS returned our launch configuration
|
||||||
if describConfs.LaunchConfigurations[0].Name != d.Id() {
|
if *describConfs.LaunchConfigurations[0].LaunchConfigurationName != d.Id() {
|
||||||
return fmt.Errorf(
|
return fmt.Errorf(
|
||||||
"Unable to find launch configuration: %#v",
|
"Unable to find launch configuration: %#v",
|
||||||
describConfs.LaunchConfigurations)
|
describConfs.LaunchConfigurations)
|
||||||
|
@ -151,14 +164,28 @@ func resourceAwsLaunchConfigurationRead(d *schema.ResourceData, meta interface{}
|
||||||
|
|
||||||
lc := describConfs.LaunchConfigurations[0]
|
lc := describConfs.LaunchConfigurations[0]
|
||||||
|
|
||||||
d.Set("key_name", lc.KeyName)
|
d.Set("key_name", *lc.KeyName)
|
||||||
d.Set("iam_instance_profile", lc.IamInstanceProfile)
|
d.Set("image_id", *lc.ImageID)
|
||||||
d.Set("image_id", lc.ImageId)
|
d.Set("instance_type", *lc.InstanceType)
|
||||||
d.Set("instance_type", lc.InstanceType)
|
d.Set("name", *lc.LaunchConfigurationName)
|
||||||
d.Set("name", lc.Name)
|
|
||||||
d.Set("security_groups", lc.SecurityGroups)
|
|
||||||
d.Set("spot_price", lc.SpotPrice)
|
|
||||||
|
|
||||||
|
if lc.IAMInstanceProfile != nil {
|
||||||
|
d.Set("iam_instance_profile", *lc.IAMInstanceProfile)
|
||||||
|
} else {
|
||||||
|
d.Set("iam_instance_profile", nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
if lc.SpotPrice != nil {
|
||||||
|
d.Set("spot_price", *lc.SpotPrice)
|
||||||
|
} else {
|
||||||
|
d.Set("spot_price", nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
if lc.SecurityGroups != nil {
|
||||||
|
d.Set("security_groups", lc.SecurityGroups)
|
||||||
|
} else {
|
||||||
|
d.Set("security_groups", nil)
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -166,10 +193,10 @@ func resourceAwsLaunchConfigurationDelete(d *schema.ResourceData, meta interface
|
||||||
autoscalingconn := meta.(*AWSClient).autoscalingconn
|
autoscalingconn := meta.(*AWSClient).autoscalingconn
|
||||||
|
|
||||||
log.Printf("[DEBUG] Launch Configuration destroy: %v", d.Id())
|
log.Printf("[DEBUG] Launch Configuration destroy: %v", d.Id())
|
||||||
_, err := autoscalingconn.DeleteLaunchConfiguration(
|
err := autoscalingconn.DeleteLaunchConfiguration(
|
||||||
&autoscaling.DeleteLaunchConfiguration{Name: d.Id()})
|
&autoscaling.LaunchConfigurationNameType{LaunchConfigurationName: aws.String(d.Id())})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
autoscalingerr, ok := err.(*autoscaling.Error)
|
autoscalingerr, ok := err.(aws.APIError)
|
||||||
if ok && autoscalingerr.Code == "InvalidConfiguration.NotFound" {
|
if ok && autoscalingerr.Code == "InvalidConfiguration.NotFound" {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,9 +4,10 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/awslabs/aws-sdk-go/aws"
|
||||||
|
"github.com/awslabs/aws-sdk-go/gen/autoscaling"
|
||||||
"github.com/hashicorp/terraform/helper/resource"
|
"github.com/hashicorp/terraform/helper/resource"
|
||||||
"github.com/hashicorp/terraform/terraform"
|
"github.com/hashicorp/terraform/terraform"
|
||||||
"github.com/mitchellh/goamz/autoscaling"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestAccAWSLaunchConfiguration(t *testing.T) {
|
func TestAccAWSLaunchConfiguration(t *testing.T) {
|
||||||
|
@ -57,19 +58,19 @@ func testAccCheckAWSLaunchConfigurationDestroy(s *terraform.State) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
describe, err := conn.DescribeLaunchConfigurations(
|
describe, err := conn.DescribeLaunchConfigurations(
|
||||||
&autoscaling.DescribeLaunchConfigurations{
|
&autoscaling.LaunchConfigurationNamesType{
|
||||||
Names: []string{rs.Primary.ID},
|
LaunchConfigurationNames: []string{rs.Primary.ID},
|
||||||
})
|
})
|
||||||
|
|
||||||
if err == nil {
|
if err == nil {
|
||||||
if len(describe.LaunchConfigurations) != 0 &&
|
if len(describe.LaunchConfigurations) != 0 &&
|
||||||
describe.LaunchConfigurations[0].Name == rs.Primary.ID {
|
*describe.LaunchConfigurations[0].LaunchConfigurationName == rs.Primary.ID {
|
||||||
return fmt.Errorf("Launch Configuration still exists")
|
return fmt.Errorf("Launch Configuration still exists")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify the error
|
// Verify the error
|
||||||
providerErr, ok := err.(*autoscaling.Error)
|
providerErr, ok := err.(aws.APIError)
|
||||||
if !ok {
|
if !ok {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -83,16 +84,16 @@ func testAccCheckAWSLaunchConfigurationDestroy(s *terraform.State) error {
|
||||||
|
|
||||||
func testAccCheckAWSLaunchConfigurationAttributes(conf *autoscaling.LaunchConfiguration) resource.TestCheckFunc {
|
func testAccCheckAWSLaunchConfigurationAttributes(conf *autoscaling.LaunchConfiguration) resource.TestCheckFunc {
|
||||||
return func(s *terraform.State) error {
|
return func(s *terraform.State) error {
|
||||||
if conf.ImageId != "ami-21f78e11" {
|
if *conf.ImageID != "ami-21f78e11" {
|
||||||
return fmt.Errorf("Bad image_id: %s", conf.ImageId)
|
return fmt.Errorf("Bad image_id: %s", *conf.ImageID)
|
||||||
}
|
}
|
||||||
|
|
||||||
if conf.Name != "foobar-terraform-test" {
|
if *conf.LaunchConfigurationName != "foobar-terraform-test" {
|
||||||
return fmt.Errorf("Bad name: %s", conf.Name)
|
return fmt.Errorf("Bad name: %s", *conf.LaunchConfigurationName)
|
||||||
}
|
}
|
||||||
|
|
||||||
if conf.InstanceType != "t1.micro" {
|
if *conf.InstanceType != "t1.micro" {
|
||||||
return fmt.Errorf("Bad instance_type: %s", conf.InstanceType)
|
return fmt.Errorf("Bad instance_type: %s", *conf.InstanceType)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
@ -112,8 +113,8 @@ func testAccCheckAWSLaunchConfigurationExists(n string, res *autoscaling.LaunchC
|
||||||
|
|
||||||
conn := testAccProvider.Meta().(*AWSClient).autoscalingconn
|
conn := testAccProvider.Meta().(*AWSClient).autoscalingconn
|
||||||
|
|
||||||
describeOpts := autoscaling.DescribeLaunchConfigurations{
|
describeOpts := autoscaling.LaunchConfigurationNamesType{
|
||||||
Names: []string{rs.Primary.ID},
|
LaunchConfigurationNames: []string{rs.Primary.ID},
|
||||||
}
|
}
|
||||||
describe, err := conn.DescribeLaunchConfigurations(&describeOpts)
|
describe, err := conn.DescribeLaunchConfigurations(&describeOpts)
|
||||||
|
|
||||||
|
@ -122,7 +123,7 @@ func testAccCheckAWSLaunchConfigurationExists(n string, res *autoscaling.LaunchC
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(describe.LaunchConfigurations) != 1 ||
|
if len(describe.LaunchConfigurations) != 1 ||
|
||||||
describe.LaunchConfigurations[0].Name != rs.Primary.ID {
|
*describe.LaunchConfigurations[0].LaunchConfigurationName != rs.Primary.ID {
|
||||||
return fmt.Errorf("Launch Configuration Group not found")
|
return fmt.Errorf("Launch Configuration Group not found")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -68,7 +68,8 @@ func (c *ApplyCommand) Run(args []string) int {
|
||||||
|
|
||||||
// Prepare the extra hooks to count resources
|
// Prepare the extra hooks to count resources
|
||||||
countHook := new(CountHook)
|
countHook := new(CountHook)
|
||||||
c.Meta.extraHooks = []terraform.Hook{countHook}
|
stateHook := new(StateHook)
|
||||||
|
c.Meta.extraHooks = []terraform.Hook{countHook, stateHook}
|
||||||
|
|
||||||
if !c.Destroy && maybeInit {
|
if !c.Destroy && maybeInit {
|
||||||
// Do a detect to determine if we need to do an init + apply.
|
// Do a detect to determine if we need to do an init + apply.
|
||||||
|
@ -151,6 +152,18 @@ func (c *ApplyCommand) Run(args []string) int {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Setup the state hook for continous state updates
|
||||||
|
{
|
||||||
|
state, err := c.State()
|
||||||
|
if err != nil {
|
||||||
|
c.Ui.Error(fmt.Sprintf(
|
||||||
|
"Error reading state: %s", err))
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
stateHook.State = state
|
||||||
|
}
|
||||||
|
|
||||||
// Start the apply in a goroutine so that we can be interrupted.
|
// Start the apply in a goroutine so that we can be interrupted.
|
||||||
var state *terraform.State
|
var state *terraform.State
|
||||||
var applyErr error
|
var applyErr error
|
||||||
|
|
|
@ -0,0 +1,33 @@
|
||||||
|
package command
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/state"
|
||||||
|
"github.com/hashicorp/terraform/terraform"
|
||||||
|
)
|
||||||
|
|
||||||
|
// StateHook is a hook that continuously updates the state by calling
|
||||||
|
// WriteState on a state.State.
|
||||||
|
type StateHook struct {
|
||||||
|
terraform.NilHook
|
||||||
|
sync.Mutex
|
||||||
|
|
||||||
|
State state.State
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *StateHook) PostStateUpdate(
|
||||||
|
s *terraform.State) (terraform.HookAction, error) {
|
||||||
|
h.Lock()
|
||||||
|
defer h.Unlock()
|
||||||
|
|
||||||
|
if h.State != nil {
|
||||||
|
// Write the new state
|
||||||
|
if err := h.State.WriteState(s); err != nil {
|
||||||
|
return terraform.HookActionHalt, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Continue forth
|
||||||
|
return terraform.HookActionContinue, nil
|
||||||
|
}
|
|
@ -0,0 +1,29 @@
|
||||||
|
package command
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/state"
|
||||||
|
"github.com/hashicorp/terraform/terraform"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestStateHook_impl(t *testing.T) {
|
||||||
|
var _ terraform.Hook = new(StateHook)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStateHook(t *testing.T) {
|
||||||
|
is := &state.InmemState{}
|
||||||
|
var hook terraform.Hook = &StateHook{State: is}
|
||||||
|
|
||||||
|
s := state.TestStateInitial()
|
||||||
|
action, err := hook.PostStateUpdate(s)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
if action != terraform.HookActionContinue {
|
||||||
|
t.Fatalf("bad: %v", action)
|
||||||
|
}
|
||||||
|
if !is.State().Equal(s) {
|
||||||
|
t.Fatalf("bad state: %#v", is.State())
|
||||||
|
}
|
||||||
|
}
|
|
@ -19,7 +19,7 @@ type CacheState struct {
|
||||||
|
|
||||||
// StateReader impl.
|
// StateReader impl.
|
||||||
func (s *CacheState) State() *terraform.State {
|
func (s *CacheState) State() *terraform.State {
|
||||||
return s.state
|
return s.state.DeepCopy()
|
||||||
}
|
}
|
||||||
|
|
||||||
// WriteState will write and persist the state to the cache.
|
// WriteState will write and persist the state to the cache.
|
||||||
|
@ -104,6 +104,8 @@ func (s *CacheState) RefreshState() error {
|
||||||
s.refreshResult = CacheRefreshNoop
|
s.refreshResult = CacheRefreshNoop
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cached = durable
|
||||||
}
|
}
|
||||||
|
|
||||||
s.state = cached
|
s.state = cached
|
||||||
|
|
|
@ -10,7 +10,7 @@ type InmemState struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *InmemState) State() *terraform.State {
|
func (s *InmemState) State() *terraform.State {
|
||||||
return s.state
|
return s.state.DeepCopy()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *InmemState) RefreshState() error {
|
func (s *InmemState) RefreshState() error {
|
||||||
|
@ -18,6 +18,7 @@ func (s *InmemState) RefreshState() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *InmemState) WriteState(state *terraform.State) error {
|
func (s *InmemState) WriteState(state *terraform.State) error {
|
||||||
|
state.IncrementSerialMaybe(s.state)
|
||||||
s.state = state
|
s.state = state
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,18 +15,20 @@ type LocalState struct {
|
||||||
Path string
|
Path string
|
||||||
PathOut string
|
PathOut string
|
||||||
|
|
||||||
state *terraform.State
|
state *terraform.State
|
||||||
written bool
|
readState *terraform.State
|
||||||
|
written bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetState will force a specific state in-memory for this local state.
|
// SetState will force a specific state in-memory for this local state.
|
||||||
func (s *LocalState) SetState(state *terraform.State) {
|
func (s *LocalState) SetState(state *terraform.State) {
|
||||||
s.state = state
|
s.state = state
|
||||||
|
s.readState = state
|
||||||
}
|
}
|
||||||
|
|
||||||
// StateReader impl.
|
// StateReader impl.
|
||||||
func (s *LocalState) State() *terraform.State {
|
func (s *LocalState) State() *terraform.State {
|
||||||
return s.state
|
return s.state.DeepCopy()
|
||||||
}
|
}
|
||||||
|
|
||||||
// WriteState for LocalState always persists the state as well.
|
// WriteState for LocalState always persists the state as well.
|
||||||
|
@ -61,6 +63,9 @@ func (s *LocalState) WriteState(state *terraform.State) error {
|
||||||
}
|
}
|
||||||
defer f.Close()
|
defer f.Close()
|
||||||
|
|
||||||
|
s.state.IncrementSerialMaybe(s.readState)
|
||||||
|
s.readState = s.state
|
||||||
|
|
||||||
if err := terraform.WriteState(s.state, f); err != nil {
|
if err := terraform.WriteState(s.state, f); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -105,5 +110,6 @@ func (s *LocalState) RefreshState() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
s.state = state
|
s.state = state
|
||||||
|
s.readState = state
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,12 +13,12 @@ import (
|
||||||
type State struct {
|
type State struct {
|
||||||
Client Client
|
Client Client
|
||||||
|
|
||||||
state *terraform.State
|
state, readState *terraform.State
|
||||||
}
|
}
|
||||||
|
|
||||||
// StateReader impl.
|
// StateReader impl.
|
||||||
func (s *State) State() *terraform.State {
|
func (s *State) State() *terraform.State {
|
||||||
return s.state
|
return s.state.DeepCopy()
|
||||||
}
|
}
|
||||||
|
|
||||||
// StateWriter impl.
|
// StateWriter impl.
|
||||||
|
@ -43,11 +43,14 @@ func (s *State) RefreshState() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
s.state = state
|
s.state = state
|
||||||
|
s.readState = state
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// StatePersister impl.
|
// StatePersister impl.
|
||||||
func (s *State) PersistState() error {
|
func (s *State) PersistState() error {
|
||||||
|
s.state.IncrementSerialMaybe(s.readState)
|
||||||
|
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
if err := terraform.WriteState(s.state, &buf); err != nil {
|
if err := terraform.WriteState(s.state, &buf); err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
|
@ -7,8 +7,11 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestState(t *testing.T) {
|
func TestState(t *testing.T) {
|
||||||
s := &State{Client: new(InmemClient)}
|
s := &State{
|
||||||
s.WriteState(state.TestStateInitial())
|
Client: new(InmemClient),
|
||||||
|
state: state.TestStateInitial(),
|
||||||
|
readState: state.TestStateInitial(),
|
||||||
|
}
|
||||||
if err := s.PersistState(); err != nil {
|
if err := s.PersistState(); err != nil {
|
||||||
t.Fatalf("err: %s", err)
|
t.Fatalf("err: %s", err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,9 +28,7 @@ func TestState(t *testing.T, s interface{}) {
|
||||||
current := TestStateInitial()
|
current := TestStateInitial()
|
||||||
|
|
||||||
// Check that the initial state is correct
|
// Check that the initial state is correct
|
||||||
state := reader.State()
|
if state := reader.State(); !current.Equal(state) {
|
||||||
current.Serial = state.Serial
|
|
||||||
if !reflect.DeepEqual(state, current) {
|
|
||||||
t.Fatalf("not initial: %#v\n\n%#v", state, current)
|
t.Fatalf("not initial: %#v\n\n%#v", state, current)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -47,7 +45,7 @@ func TestState(t *testing.T, s interface{}) {
|
||||||
t.Fatalf("err: %s", err)
|
t.Fatalf("err: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if actual := reader.State(); !reflect.DeepEqual(actual, current) {
|
if actual := reader.State(); !actual.Equal(current) {
|
||||||
t.Fatalf("bad: %#v\n\n%#v", actual, current)
|
t.Fatalf("bad: %#v\n\n%#v", actual, current)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -67,11 +65,55 @@ func TestState(t *testing.T, s interface{}) {
|
||||||
|
|
||||||
// Just set the serials the same... Then compare.
|
// Just set the serials the same... Then compare.
|
||||||
actual := reader.State()
|
actual := reader.State()
|
||||||
actual.Serial = current.Serial
|
if !actual.Equal(current) {
|
||||||
if !reflect.DeepEqual(actual, current) {
|
|
||||||
t.Fatalf("bad: %#v\n\n%#v", actual, current)
|
t.Fatalf("bad: %#v\n\n%#v", actual, current)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If we can write and persist then verify that the serial
|
||||||
|
// is only implemented on change.
|
||||||
|
writer, writeOk := s.(StateWriter)
|
||||||
|
persister, persistOk := s.(StatePersister)
|
||||||
|
if writeOk && persistOk {
|
||||||
|
// Same serial
|
||||||
|
serial := current.Serial
|
||||||
|
if err := writer.WriteState(current); err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
if err := persister.PersistState(); err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if reader.State().Serial != serial {
|
||||||
|
t.Fatalf("bad: expected %d, got %d", serial, reader.State().Serial)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Change the serial
|
||||||
|
currentCopy := *current
|
||||||
|
current = ¤tCopy
|
||||||
|
current.Modules = []*terraform.ModuleState{
|
||||||
|
&terraform.ModuleState{
|
||||||
|
Path: []string{"root", "somewhere"},
|
||||||
|
Outputs: map[string]string{"serialCheck": "true"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
if err := writer.WriteState(current); err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
if err := persister.PersistState(); err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if reader.State().Serial <= serial {
|
||||||
|
t.Fatalf("bad: expected %d, got %d", serial, reader.State().Serial)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check that State() returns a copy
|
||||||
|
reader.State().Serial++
|
||||||
|
if reflect.DeepEqual(reader.State(), current) {
|
||||||
|
t.Fatal("State() should return a copy")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestStateInitial is the initial state that a State should have
|
// TestStateInitial is the initial state that a State should have
|
||||||
|
|
|
@ -224,7 +224,7 @@ func (c *Context) Apply() (*State, error) {
|
||||||
defer c.releaseRun(v)
|
defer c.releaseRun(v)
|
||||||
|
|
||||||
// Copy our own state
|
// Copy our own state
|
||||||
c.state = c.state.deepcopy()
|
c.state = c.state.DeepCopy()
|
||||||
|
|
||||||
// Do the walk
|
// Do the walk
|
||||||
_, err := c.walk(walkApply)
|
_, err := c.walk(walkApply)
|
||||||
|
@ -264,7 +264,7 @@ func (c *Context) Plan(opts *PlanOpts) (*Plan, error) {
|
||||||
c.state = &State{}
|
c.state = &State{}
|
||||||
c.state.init()
|
c.state.init()
|
||||||
} else {
|
} else {
|
||||||
c.state = old.deepcopy()
|
c.state = old.DeepCopy()
|
||||||
}
|
}
|
||||||
defer func() {
|
defer func() {
|
||||||
c.state = old
|
c.state = old
|
||||||
|
@ -299,7 +299,7 @@ func (c *Context) Refresh() (*State, error) {
|
||||||
defer c.releaseRun(v)
|
defer c.releaseRun(v)
|
||||||
|
|
||||||
// Copy our own state
|
// Copy our own state
|
||||||
c.state = c.state.deepcopy()
|
c.state = c.state.DeepCopy()
|
||||||
|
|
||||||
// Do the walk
|
// Do the walk
|
||||||
if _, err := c.walk(walkRefresh); err != nil {
|
if _, err := c.walk(walkRefresh); err != nil {
|
||||||
|
|
|
@ -4454,6 +4454,9 @@ func TestContext2Apply_hook(t *testing.T) {
|
||||||
if !h.PostApplyCalled {
|
if !h.PostApplyCalled {
|
||||||
t.Fatal("should be called")
|
t.Fatal("should be called")
|
||||||
}
|
}
|
||||||
|
if !h.PostStateUpdateCalled {
|
||||||
|
t.Fatalf("should call post state update")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestContext2Apply_idAttr(t *testing.T) {
|
func TestContext2Apply_idAttr(t *testing.T) {
|
||||||
|
|
|
@ -70,155 +70,3 @@ type EvalContext interface {
|
||||||
// be used to modify that state.
|
// be used to modify that state.
|
||||||
State() (*State, *sync.RWMutex)
|
State() (*State, *sync.RWMutex)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MockEvalContext is a mock version of EvalContext that can be used
|
|
||||||
// for tests.
|
|
||||||
type MockEvalContext struct {
|
|
||||||
HookCalled bool
|
|
||||||
HookError error
|
|
||||||
|
|
||||||
InputCalled bool
|
|
||||||
InputInput UIInput
|
|
||||||
|
|
||||||
InitProviderCalled bool
|
|
||||||
InitProviderName string
|
|
||||||
InitProviderProvider ResourceProvider
|
|
||||||
InitProviderError error
|
|
||||||
|
|
||||||
ProviderCalled bool
|
|
||||||
ProviderName string
|
|
||||||
ProviderProvider ResourceProvider
|
|
||||||
|
|
||||||
ProviderInputCalled bool
|
|
||||||
ProviderInputName string
|
|
||||||
ProviderInputConfig map[string]interface{}
|
|
||||||
|
|
||||||
SetProviderInputCalled bool
|
|
||||||
SetProviderInputName string
|
|
||||||
SetProviderInputConfig map[string]interface{}
|
|
||||||
|
|
||||||
ConfigureProviderCalled bool
|
|
||||||
ConfigureProviderName string
|
|
||||||
ConfigureProviderConfig *ResourceConfig
|
|
||||||
ConfigureProviderError error
|
|
||||||
|
|
||||||
ParentProviderConfigCalled bool
|
|
||||||
ParentProviderConfigName string
|
|
||||||
ParentProviderConfigConfig *ResourceConfig
|
|
||||||
|
|
||||||
InitProvisionerCalled bool
|
|
||||||
InitProvisionerName string
|
|
||||||
InitProvisionerProvisioner ResourceProvisioner
|
|
||||||
InitProvisionerError error
|
|
||||||
|
|
||||||
ProvisionerCalled bool
|
|
||||||
ProvisionerName string
|
|
||||||
ProvisionerProvisioner ResourceProvisioner
|
|
||||||
|
|
||||||
InterpolateCalled bool
|
|
||||||
InterpolateConfig *config.RawConfig
|
|
||||||
InterpolateResource *Resource
|
|
||||||
InterpolateConfigResult *ResourceConfig
|
|
||||||
InterpolateError error
|
|
||||||
|
|
||||||
PathCalled bool
|
|
||||||
PathPath []string
|
|
||||||
|
|
||||||
SetVariablesCalled bool
|
|
||||||
SetVariablesVariables map[string]string
|
|
||||||
|
|
||||||
DiffCalled bool
|
|
||||||
DiffDiff *Diff
|
|
||||||
DiffLock *sync.RWMutex
|
|
||||||
|
|
||||||
StateCalled bool
|
|
||||||
StateState *State
|
|
||||||
StateLock *sync.RWMutex
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *MockEvalContext) Hook(fn func(Hook) (HookAction, error)) error {
|
|
||||||
c.HookCalled = true
|
|
||||||
return c.HookError
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *MockEvalContext) Input() UIInput {
|
|
||||||
c.InputCalled = true
|
|
||||||
return c.InputInput
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *MockEvalContext) InitProvider(n string) (ResourceProvider, error) {
|
|
||||||
c.InitProviderCalled = true
|
|
||||||
c.InitProviderName = n
|
|
||||||
return c.InitProviderProvider, c.InitProviderError
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *MockEvalContext) Provider(n string) ResourceProvider {
|
|
||||||
c.ProviderCalled = true
|
|
||||||
c.ProviderName = n
|
|
||||||
return c.ProviderProvider
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *MockEvalContext) ConfigureProvider(n string, cfg *ResourceConfig) error {
|
|
||||||
c.ConfigureProviderCalled = true
|
|
||||||
c.ConfigureProviderName = n
|
|
||||||
c.ConfigureProviderConfig = cfg
|
|
||||||
return c.ConfigureProviderError
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *MockEvalContext) ParentProviderConfig(n string) *ResourceConfig {
|
|
||||||
c.ParentProviderConfigCalled = true
|
|
||||||
c.ParentProviderConfigName = n
|
|
||||||
return c.ParentProviderConfigConfig
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *MockEvalContext) ProviderInput(n string) map[string]interface{} {
|
|
||||||
c.ProviderInputCalled = true
|
|
||||||
c.ProviderInputName = n
|
|
||||||
return c.ProviderInputConfig
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *MockEvalContext) SetProviderInput(n string, cfg map[string]interface{}) {
|
|
||||||
c.SetProviderInputCalled = true
|
|
||||||
c.SetProviderInputName = n
|
|
||||||
c.SetProviderInputConfig = cfg
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *MockEvalContext) InitProvisioner(n string) (ResourceProvisioner, error) {
|
|
||||||
c.InitProvisionerCalled = true
|
|
||||||
c.InitProvisionerName = n
|
|
||||||
return c.InitProvisionerProvisioner, c.InitProvisionerError
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *MockEvalContext) Provisioner(n string) ResourceProvisioner {
|
|
||||||
c.ProvisionerCalled = true
|
|
||||||
c.ProvisionerName = n
|
|
||||||
return c.ProvisionerProvisioner
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *MockEvalContext) Interpolate(
|
|
||||||
config *config.RawConfig, resource *Resource) (*ResourceConfig, error) {
|
|
||||||
c.InterpolateCalled = true
|
|
||||||
c.InterpolateConfig = config
|
|
||||||
c.InterpolateResource = resource
|
|
||||||
return c.InterpolateConfigResult, c.InterpolateError
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *MockEvalContext) Path() []string {
|
|
||||||
c.PathCalled = true
|
|
||||||
return c.PathPath
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *MockEvalContext) SetVariables(vs map[string]string) {
|
|
||||||
c.SetVariablesCalled = true
|
|
||||||
c.SetVariablesVariables = vs
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *MockEvalContext) Diff() (*Diff, *sync.RWMutex) {
|
|
||||||
c.DiffCalled = true
|
|
||||||
return c.DiffDiff, c.DiffLock
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *MockEvalContext) State() (*State, *sync.RWMutex) {
|
|
||||||
c.StateCalled = true
|
|
||||||
return c.StateState, c.StateLock
|
|
||||||
}
|
|
||||||
|
|
|
@ -0,0 +1,166 @@
|
||||||
|
package terraform
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/config"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MockEvalContext is a mock version of EvalContext that can be used
|
||||||
|
// for tests.
|
||||||
|
type MockEvalContext struct {
|
||||||
|
HookCalled bool
|
||||||
|
HookHook Hook
|
||||||
|
HookError error
|
||||||
|
|
||||||
|
InputCalled bool
|
||||||
|
InputInput UIInput
|
||||||
|
|
||||||
|
InitProviderCalled bool
|
||||||
|
InitProviderName string
|
||||||
|
InitProviderProvider ResourceProvider
|
||||||
|
InitProviderError error
|
||||||
|
|
||||||
|
ProviderCalled bool
|
||||||
|
ProviderName string
|
||||||
|
ProviderProvider ResourceProvider
|
||||||
|
|
||||||
|
ProviderInputCalled bool
|
||||||
|
ProviderInputName string
|
||||||
|
ProviderInputConfig map[string]interface{}
|
||||||
|
|
||||||
|
SetProviderInputCalled bool
|
||||||
|
SetProviderInputName string
|
||||||
|
SetProviderInputConfig map[string]interface{}
|
||||||
|
|
||||||
|
ConfigureProviderCalled bool
|
||||||
|
ConfigureProviderName string
|
||||||
|
ConfigureProviderConfig *ResourceConfig
|
||||||
|
ConfigureProviderError error
|
||||||
|
|
||||||
|
ParentProviderConfigCalled bool
|
||||||
|
ParentProviderConfigName string
|
||||||
|
ParentProviderConfigConfig *ResourceConfig
|
||||||
|
|
||||||
|
InitProvisionerCalled bool
|
||||||
|
InitProvisionerName string
|
||||||
|
InitProvisionerProvisioner ResourceProvisioner
|
||||||
|
InitProvisionerError error
|
||||||
|
|
||||||
|
ProvisionerCalled bool
|
||||||
|
ProvisionerName string
|
||||||
|
ProvisionerProvisioner ResourceProvisioner
|
||||||
|
|
||||||
|
InterpolateCalled bool
|
||||||
|
InterpolateConfig *config.RawConfig
|
||||||
|
InterpolateResource *Resource
|
||||||
|
InterpolateConfigResult *ResourceConfig
|
||||||
|
InterpolateError error
|
||||||
|
|
||||||
|
PathCalled bool
|
||||||
|
PathPath []string
|
||||||
|
|
||||||
|
SetVariablesCalled bool
|
||||||
|
SetVariablesVariables map[string]string
|
||||||
|
|
||||||
|
DiffCalled bool
|
||||||
|
DiffDiff *Diff
|
||||||
|
DiffLock *sync.RWMutex
|
||||||
|
|
||||||
|
StateCalled bool
|
||||||
|
StateState *State
|
||||||
|
StateLock *sync.RWMutex
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *MockEvalContext) Hook(fn func(Hook) (HookAction, error)) error {
|
||||||
|
c.HookCalled = true
|
||||||
|
if c.HookHook != nil {
|
||||||
|
if _, err := fn(c.HookHook); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.HookError
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *MockEvalContext) Input() UIInput {
|
||||||
|
c.InputCalled = true
|
||||||
|
return c.InputInput
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *MockEvalContext) InitProvider(n string) (ResourceProvider, error) {
|
||||||
|
c.InitProviderCalled = true
|
||||||
|
c.InitProviderName = n
|
||||||
|
return c.InitProviderProvider, c.InitProviderError
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *MockEvalContext) Provider(n string) ResourceProvider {
|
||||||
|
c.ProviderCalled = true
|
||||||
|
c.ProviderName = n
|
||||||
|
return c.ProviderProvider
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *MockEvalContext) ConfigureProvider(n string, cfg *ResourceConfig) error {
|
||||||
|
c.ConfigureProviderCalled = true
|
||||||
|
c.ConfigureProviderName = n
|
||||||
|
c.ConfigureProviderConfig = cfg
|
||||||
|
return c.ConfigureProviderError
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *MockEvalContext) ParentProviderConfig(n string) *ResourceConfig {
|
||||||
|
c.ParentProviderConfigCalled = true
|
||||||
|
c.ParentProviderConfigName = n
|
||||||
|
return c.ParentProviderConfigConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *MockEvalContext) ProviderInput(n string) map[string]interface{} {
|
||||||
|
c.ProviderInputCalled = true
|
||||||
|
c.ProviderInputName = n
|
||||||
|
return c.ProviderInputConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *MockEvalContext) SetProviderInput(n string, cfg map[string]interface{}) {
|
||||||
|
c.SetProviderInputCalled = true
|
||||||
|
c.SetProviderInputName = n
|
||||||
|
c.SetProviderInputConfig = cfg
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *MockEvalContext) InitProvisioner(n string) (ResourceProvisioner, error) {
|
||||||
|
c.InitProvisionerCalled = true
|
||||||
|
c.InitProvisionerName = n
|
||||||
|
return c.InitProvisionerProvisioner, c.InitProvisionerError
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *MockEvalContext) Provisioner(n string) ResourceProvisioner {
|
||||||
|
c.ProvisionerCalled = true
|
||||||
|
c.ProvisionerName = n
|
||||||
|
return c.ProvisionerProvisioner
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *MockEvalContext) Interpolate(
|
||||||
|
config *config.RawConfig, resource *Resource) (*ResourceConfig, error) {
|
||||||
|
c.InterpolateCalled = true
|
||||||
|
c.InterpolateConfig = config
|
||||||
|
c.InterpolateResource = resource
|
||||||
|
return c.InterpolateConfigResult, c.InterpolateError
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *MockEvalContext) Path() []string {
|
||||||
|
c.PathCalled = true
|
||||||
|
return c.PathPath
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *MockEvalContext) SetVariables(vs map[string]string) {
|
||||||
|
c.SetVariablesCalled = true
|
||||||
|
c.SetVariablesVariables = vs
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *MockEvalContext) Diff() (*Diff, *sync.RWMutex) {
|
||||||
|
c.DiffCalled = true
|
||||||
|
return c.DiffDiff, c.DiffLock
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *MockEvalContext) State() (*State, *sync.RWMutex) {
|
||||||
|
c.StateCalled = true
|
||||||
|
return c.StateState, c.StateLock
|
||||||
|
}
|
|
@ -58,6 +58,28 @@ func (n *EvalReadState) Eval(ctx EvalContext) (interface{}, error) {
|
||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// EvalUpdateStateHook is an EvalNode implementation that calls the
|
||||||
|
// PostStateUpdate hook with the current state.
|
||||||
|
type EvalUpdateStateHook struct{}
|
||||||
|
|
||||||
|
func (n *EvalUpdateStateHook) Eval(ctx EvalContext) (interface{}, error) {
|
||||||
|
state, lock := ctx.State()
|
||||||
|
|
||||||
|
// Get a read lock so it doesn't change while we're calling this
|
||||||
|
lock.RLock()
|
||||||
|
defer lock.RUnlock()
|
||||||
|
|
||||||
|
// Call the hook
|
||||||
|
err := ctx.Hook(func(h Hook) (HookAction, error) {
|
||||||
|
return h.PostStateUpdate(state)
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
// EvalWriteState is an EvalNode implementation that reads the
|
// EvalWriteState is an EvalNode implementation that reads the
|
||||||
// InstanceState for a specific resource out of the state.
|
// InstanceState for a specific resource out of the state.
|
||||||
type EvalWriteState struct {
|
type EvalWriteState struct {
|
||||||
|
@ -111,7 +133,6 @@ func (n *EvalWriteState) Eval(ctx EvalContext) (interface{}, error) {
|
||||||
// Set the primary state
|
// Set the primary state
|
||||||
rs.Primary = *n.State
|
rs.Primary = *n.State
|
||||||
}
|
}
|
||||||
println(fmt.Sprintf("%#v", rs))
|
|
||||||
|
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,27 @@
|
||||||
|
package terraform
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestEvalUpdateStateHook(t *testing.T) {
|
||||||
|
mockHook := new(MockHook)
|
||||||
|
|
||||||
|
ctx := new(MockEvalContext)
|
||||||
|
ctx.HookHook = mockHook
|
||||||
|
ctx.StateState = &State{Serial: 42}
|
||||||
|
ctx.StateLock = new(sync.RWMutex)
|
||||||
|
|
||||||
|
node := &EvalUpdateStateHook{}
|
||||||
|
if _, err := node.Eval(ctx); err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !mockHook.PostStateUpdateCalled {
|
||||||
|
t.Fatal("should call PostStateUpdate")
|
||||||
|
}
|
||||||
|
if mockHook.PostStateUpdateState.Serial != 42 {
|
||||||
|
t.Fatalf("bad: %#v", mockHook.PostStateUpdateState)
|
||||||
|
}
|
||||||
|
}
|
|
@ -49,6 +49,9 @@ type Hook interface {
|
||||||
// resource state is refreshed, respectively.
|
// resource state is refreshed, respectively.
|
||||||
PreRefresh(*InstanceInfo, *InstanceState) (HookAction, error)
|
PreRefresh(*InstanceInfo, *InstanceState) (HookAction, error)
|
||||||
PostRefresh(*InstanceInfo, *InstanceState) (HookAction, error)
|
PostRefresh(*InstanceInfo, *InstanceState) (HookAction, error)
|
||||||
|
|
||||||
|
// PostStateUpdate is called after the state is updated.
|
||||||
|
PostStateUpdate(*State) (HookAction, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// NilHook is a Hook implementation that does nothing. It exists only to
|
// NilHook is a Hook implementation that does nothing. It exists only to
|
||||||
|
@ -100,6 +103,10 @@ func (*NilHook) PostRefresh(*InstanceInfo, *InstanceState) (HookAction, error) {
|
||||||
return HookActionContinue, nil
|
return HookActionContinue, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (*NilHook) PostStateUpdate(*State) (HookAction, error) {
|
||||||
|
return HookActionContinue, nil
|
||||||
|
}
|
||||||
|
|
||||||
// handleHook turns hook actions into panics. This lets you use the
|
// handleHook turns hook actions into panics. This lets you use the
|
||||||
// panic/recover mechanism in Go as a flow control mechanism for hook
|
// panic/recover mechanism in Go as a flow control mechanism for hook
|
||||||
// actions.
|
// actions.
|
||||||
|
|
|
@ -69,6 +69,11 @@ type MockHook struct {
|
||||||
PreRefreshState *InstanceState
|
PreRefreshState *InstanceState
|
||||||
PreRefreshReturn HookAction
|
PreRefreshReturn HookAction
|
||||||
PreRefreshError error
|
PreRefreshError error
|
||||||
|
|
||||||
|
PostStateUpdateCalled bool
|
||||||
|
PostStateUpdateState *State
|
||||||
|
PostStateUpdateReturn HookAction
|
||||||
|
PostStateUpdateError error
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *MockHook) PreApply(n *InstanceInfo, s *InstanceState, d *InstanceDiff) (HookAction, error) {
|
func (h *MockHook) PreApply(n *InstanceInfo, s *InstanceState, d *InstanceDiff) (HookAction, error) {
|
||||||
|
@ -152,3 +157,9 @@ func (h *MockHook) PostRefresh(n *InstanceInfo, s *InstanceState) (HookAction, e
|
||||||
h.PostRefreshState = s
|
h.PostRefreshState = s
|
||||||
return h.PostRefreshReturn, h.PostRefreshError
|
return h.PostRefreshReturn, h.PostRefreshError
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (h *MockHook) PostStateUpdate(s *State) (HookAction, error) {
|
||||||
|
h.PostStateUpdateCalled = true
|
||||||
|
h.PostStateUpdateState = s
|
||||||
|
return h.PostStateUpdateReturn, h.PostStateUpdateError
|
||||||
|
}
|
||||||
|
|
|
@ -53,6 +53,10 @@ func (h *stopHook) PostRefresh(*InstanceInfo, *InstanceState) (HookAction, error
|
||||||
return h.hook()
|
return h.hook()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (h *stopHook) PostStateUpdate(*State) (HookAction, error) {
|
||||||
|
return h.hook()
|
||||||
|
}
|
||||||
|
|
||||||
func (h *stopHook) hook() (HookAction, error) {
|
func (h *stopHook) hook() (HookAction, error) {
|
||||||
if h.Stopped() {
|
if h.Stopped() {
|
||||||
return HookActionHalt, nil
|
return HookActionHalt, nil
|
||||||
|
|
|
@ -161,12 +161,20 @@ func (s *State) RootModule() *ModuleState {
|
||||||
|
|
||||||
// Equal tests if one state is equal to another.
|
// Equal tests if one state is equal to another.
|
||||||
func (s *State) Equal(other *State) bool {
|
func (s *State) Equal(other *State) bool {
|
||||||
|
// If one is nil, we do a direct check
|
||||||
|
if s == nil || other == nil {
|
||||||
|
return s == other
|
||||||
|
}
|
||||||
|
|
||||||
// If the versions are different, they're certainly not equal
|
// If the versions are different, they're certainly not equal
|
||||||
if s.Version != other.Version {
|
if s.Version != other.Version {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// If any of the modules are not equal, then this state isn't equal
|
// If any of the modules are not equal, then this state isn't equal
|
||||||
|
if len(s.Modules) != len(other.Modules) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
for _, m := range s.Modules {
|
for _, m := range s.Modules {
|
||||||
// This isn't very optimal currently but works.
|
// This isn't very optimal currently but works.
|
||||||
otherM := other.ModuleByPath(m.Path)
|
otherM := other.ModuleByPath(m.Path)
|
||||||
|
@ -183,20 +191,9 @@ func (s *State) Equal(other *State) bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *State) init() {
|
// DeepCopy performs a deep copy of the state structure and returns
|
||||||
if s.Version == 0 {
|
// a new structure.
|
||||||
s.Version = StateVersion
|
func (s *State) DeepCopy() *State {
|
||||||
}
|
|
||||||
if len(s.Modules) == 0 {
|
|
||||||
root := &ModuleState{
|
|
||||||
Path: rootModulePath,
|
|
||||||
}
|
|
||||||
root.init()
|
|
||||||
s.Modules = []*ModuleState{root}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *State) deepcopy() *State {
|
|
||||||
if s == nil {
|
if s == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -214,6 +211,27 @@ func (s *State) deepcopy() *State {
|
||||||
return n
|
return n
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IncrementSerialMaybe increments the serial number of this state
|
||||||
|
// if it different from the other state.
|
||||||
|
func (s *State) IncrementSerialMaybe(other *State) {
|
||||||
|
if !s.Equal(other) {
|
||||||
|
s.Serial++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *State) init() {
|
||||||
|
if s.Version == 0 {
|
||||||
|
s.Version = StateVersion
|
||||||
|
}
|
||||||
|
if len(s.Modules) == 0 {
|
||||||
|
root := &ModuleState{
|
||||||
|
Path: rootModulePath,
|
||||||
|
}
|
||||||
|
root.init()
|
||||||
|
s.Modules = []*ModuleState{root}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// prune is used to remove any resources that are no longer required
|
// prune is used to remove any resources that are no longer required
|
||||||
func (s *State) prune() {
|
func (s *State) prune() {
|
||||||
if s == nil {
|
if s == nil {
|
||||||
|
@ -951,9 +969,6 @@ func WriteState(d *State, dst io.Writer) error {
|
||||||
// Ensure the version is set
|
// Ensure the version is set
|
||||||
d.Version = StateVersion
|
d.Version = StateVersion
|
||||||
|
|
||||||
// Always increment the serial number
|
|
||||||
d.Serial++
|
|
||||||
|
|
||||||
// Encode the data in a human-friendly way
|
// Encode the data in a human-friendly way
|
||||||
data, err := json.MarshalIndent(d, "", " ")
|
data, err := json.MarshalIndent(d, "", " ")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -116,6 +116,19 @@ func TestStateEqual(t *testing.T) {
|
||||||
Result bool
|
Result bool
|
||||||
One, Two *State
|
One, Two *State
|
||||||
}{
|
}{
|
||||||
|
// Nils
|
||||||
|
{
|
||||||
|
false,
|
||||||
|
nil,
|
||||||
|
&State{Version: 2},
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
true,
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
},
|
||||||
|
|
||||||
// Different versions
|
// Different versions
|
||||||
{
|
{
|
||||||
false,
|
false,
|
||||||
|
@ -159,6 +172,9 @@ func TestStateEqual(t *testing.T) {
|
||||||
if tc.One.Equal(tc.Two) != tc.Result {
|
if tc.One.Equal(tc.Two) != tc.Result {
|
||||||
t.Fatalf("Bad: %d\n\n%s\n\n%s", i, tc.One.String(), tc.Two.String())
|
t.Fatalf("Bad: %d\n\n%s\n\n%s", i, tc.One.String(), tc.Two.String())
|
||||||
}
|
}
|
||||||
|
if tc.Two.Equal(tc.One) != tc.Result {
|
||||||
|
t.Fatalf("Bad: %d\n\n%s\n\n%s", i, tc.One.String(), tc.Two.String())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -537,15 +553,6 @@ func TestReadWriteState(t *testing.T) {
|
||||||
t.Fatalf("bad version number: %d", state.Version)
|
t.Fatalf("bad version number: %d", state.Version)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify the serial number is incremented
|
|
||||||
if state.Serial != 10 {
|
|
||||||
t.Fatalf("bad serial: %d", state.Serial)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove the changes or the checksum will fail
|
|
||||||
state.Version = 0
|
|
||||||
state.Serial = 9
|
|
||||||
|
|
||||||
// Checksum after the write
|
// Checksum after the write
|
||||||
chksumAfter := checksumStruct(t, state)
|
chksumAfter := checksumStruct(t, state)
|
||||||
if chksumAfter != chksum {
|
if chksumAfter != chksum {
|
||||||
|
@ -557,10 +564,6 @@ func TestReadWriteState(t *testing.T) {
|
||||||
t.Fatalf("err: %s", err)
|
t.Fatalf("err: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify the changes came through
|
|
||||||
state.Version = StateVersion
|
|
||||||
state.Serial = 10
|
|
||||||
|
|
||||||
// ReadState should not restore sensitive information!
|
// ReadState should not restore sensitive information!
|
||||||
mod := state.RootModule()
|
mod := state.RootModule()
|
||||||
mod.Resources["foo"].Primary.Ephemeral = EphemeralState{}
|
mod.Resources["foo"].Primary.Ephemeral = EphemeralState{}
|
||||||
|
|
|
@ -262,6 +262,7 @@ func (n *graphNodeOrphanResource) EvalTree() EvalNode {
|
||||||
Dependencies: n.DependentOn(),
|
Dependencies: n.DependentOn(),
|
||||||
State: &state,
|
State: &state,
|
||||||
},
|
},
|
||||||
|
&EvalUpdateStateHook{},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
|
@ -400,6 +400,7 @@ func (n *graphNodeExpandedResource) EvalTree() EvalNode {
|
||||||
State: &state,
|
State: &state,
|
||||||
Error: &err,
|
Error: &err,
|
||||||
},
|
},
|
||||||
|
&EvalUpdateStateHook{},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
|
@ -163,6 +163,7 @@ func (n *graphNodeTaintedResource) EvalTree() EvalNode {
|
||||||
Tainted: &tainted,
|
Tainted: &tainted,
|
||||||
TaintedIndex: n.Index,
|
TaintedIndex: n.Index,
|
||||||
},
|
},
|
||||||
|
&EvalUpdateStateHook{},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
Loading…
Reference in New Issue