From 6aeffdfb2c2583919c110e968782add12debd0f0 Mon Sep 17 00:00:00 2001 From: Martin Atkins Date: Thu, 27 Aug 2015 18:54:52 -0700 Subject: [PATCH] chef_node resource. --- builtin/providers/chef/provider.go | 2 +- builtin/providers/chef/resource_node.go | 216 +++++++++++++++++++ builtin/providers/chef/resource_node_test.go | 139 ++++++++++++ 3 files changed, 356 insertions(+), 1 deletion(-) create mode 100644 builtin/providers/chef/resource_node.go create mode 100644 builtin/providers/chef/resource_node_test.go diff --git a/builtin/providers/chef/provider.go b/builtin/providers/chef/provider.go index a362b0071..7a04b9775 100644 --- a/builtin/providers/chef/provider.go +++ b/builtin/providers/chef/provider.go @@ -49,7 +49,7 @@ func Provider() terraform.ResourceProvider { "chef_data_bag": resourceChefDataBag(), "chef_data_bag_item": resourceChefDataBagItem(), "chef_environment": resourceChefEnvironment(), - //"chef_node": resourceChefNode(), + "chef_node": resourceChefNode(), "chef_role": resourceChefRole(), }, diff --git a/builtin/providers/chef/resource_node.go b/builtin/providers/chef/resource_node.go new file mode 100644 index 000000000..6ded52ce2 --- /dev/null +++ b/builtin/providers/chef/resource_node.go @@ -0,0 +1,216 @@ +package chef + +import ( + "encoding/json" + "fmt" + + "github.com/hashicorp/terraform/helper/schema" + + chefc "github.com/go-chef/chef" +) + +func resourceChefNode() *schema.Resource { + return &schema.Resource{ + Create: CreateNode, + Update: UpdateNode, + Read: ReadNode, + Delete: DeleteNode, + + Schema: map[string]*schema.Schema{ + "name": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "environment_name": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Default: "_default", + }, + "automatic_attributes_json": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Default: "{}", + StateFunc: jsonStateFunc, + }, + "normal_attributes_json": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Default: "{}", + StateFunc: jsonStateFunc, + }, + "default_attributes_json": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Default: "{}", + StateFunc: jsonStateFunc, + }, + "override_attributes_json": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Default: "{}", + StateFunc: jsonStateFunc, + }, + "run_list": &schema.Schema{ + Type: schema.TypeList, + Optional: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + StateFunc: runListEntryStateFunc, + }, + }, + }, + } +} + +func CreateNode(d *schema.ResourceData, meta interface{}) error { + client := meta.(*chefc.Client) + + node, err := nodeFromResourceData(d) + if err != nil { + return err + } + + _, err = client.Nodes.Post(*node) + if err != nil { + return err + } + + d.SetId(node.Name) + return ReadNode(d, meta) +} + +func UpdateNode(d *schema.ResourceData, meta interface{}) error { + client := meta.(*chefc.Client) + + node, err := nodeFromResourceData(d) + if err != nil { + return err + } + + _, err = client.Nodes.Put(*node) + if err != nil { + return err + } + + d.SetId(node.Name) + return ReadNode(d, meta) +} + +func ReadNode(d *schema.ResourceData, meta interface{}) error { + client := meta.(*chefc.Client) + + name := d.Id() + + node, err := client.Nodes.Get(name) + if err != nil { + if errRes, ok := err.(*chefc.ErrorResponse); ok { + if errRes.Response.StatusCode == 404 { + d.SetId("") + return nil + } + } else { + return err + } + } + + d.Set("name", node.Name) + d.Set("environment_name", node.Environment) + + automaticAttrJson, err := json.Marshal(node.AutomaticAttributes) + if err != nil { + return err + } + d.Set("automatic_attributes_json", automaticAttrJson) + + normalAttrJson, err := json.Marshal(node.NormalAttributes) + if err != nil { + return err + } + d.Set("normal_attributes_json", normalAttrJson) + + defaultAttrJson, err := json.Marshal(node.DefaultAttributes) + if err != nil { + return err + } + d.Set("default_attributes_json", defaultAttrJson) + + overrideAttrJson, err := json.Marshal(node.OverrideAttributes) + if err != nil { + return err + } + d.Set("override_attributes_json", overrideAttrJson) + + runListI := make([]interface{}, len(node.RunList)) + for i, v := range node.RunList { + runListI[i] = v + } + d.Set("run_list", runListI) + + return nil +} + +func DeleteNode(d *schema.ResourceData, meta interface{}) error { + client := meta.(*chefc.Client) + + name := d.Id() + err := client.Nodes.Delete(name) + + if err == nil { + d.SetId("") + } + + return err +} + +func nodeFromResourceData(d *schema.ResourceData) (*chefc.Node, error) { + + node := &chefc.Node{ + Name: d.Get("name").(string), + Environment: d.Get("environment_name").(string), + ChefType: "node", + JsonClass: "Chef::Node", + } + + var err error + + err = json.Unmarshal( + []byte(d.Get("automatic_attributes_json").(string)), + &node.AutomaticAttributes, + ) + if err != nil { + return nil, fmt.Errorf("automatic_attributes_json: %s", err) + } + + err = json.Unmarshal( + []byte(d.Get("normal_attributes_json").(string)), + &node.NormalAttributes, + ) + if err != nil { + return nil, fmt.Errorf("normal_attributes_json: %s", err) + } + + err = json.Unmarshal( + []byte(d.Get("default_attributes_json").(string)), + &node.DefaultAttributes, + ) + if err != nil { + return nil, fmt.Errorf("default_attributes_json: %s", err) + } + + err = json.Unmarshal( + []byte(d.Get("override_attributes_json").(string)), + &node.OverrideAttributes, + ) + if err != nil { + return nil, fmt.Errorf("override_attributes_json: %s", err) + } + + runListI := d.Get("run_list").([]interface{}) + node.RunList = make([]string, len(runListI)) + for i, vI := range runListI { + node.RunList[i] = vI.(string) + } + + return node, nil +} diff --git a/builtin/providers/chef/resource_node_test.go b/builtin/providers/chef/resource_node_test.go new file mode 100644 index 000000000..ace6c75a3 --- /dev/null +++ b/builtin/providers/chef/resource_node_test.go @@ -0,0 +1,139 @@ +package chef + +import ( + "fmt" + "reflect" + "testing" + + chefc "github.com/go-chef/chef" + + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" +) + +func TestAccNode_basic(t *testing.T) { + var node chefc.Node + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccNodeCheckDestroy(&node), + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccNodeConfig_basic, + Check: resource.ComposeTestCheckFunc( + testAccNodeCheckExists("chef_node.test", &node), + func(s *terraform.State) error { + + if expected := "terraform-acc-test-basic"; node.Name != expected { + return fmt.Errorf("wrong name; expected %v, got %v", expected, node.Name) + } + if expected := "terraform-acc-test-node-basic"; node.Environment != expected { + return fmt.Errorf("wrong environment; expected %v, got %v", expected, node.Environment) + } + + expectedRunList := []string{ + "recipe[terraform@1.0.0]", + "recipe[consul]", + "role[foo]", + } + if !reflect.DeepEqual(node.RunList, expectedRunList) { + return fmt.Errorf("wrong runlist; expected %#v, got %#v", expectedRunList, node.RunList) + } + + var expectedAttributes interface{} + expectedAttributes = map[string]interface{}{ + "terraform_acc_test": true, + } + if !reflect.DeepEqual(node.AutomaticAttributes, expectedAttributes) { + return fmt.Errorf("wrong automatic attributes; expected %#v, got %#v", expectedAttributes, node.AutomaticAttributes) + } + if !reflect.DeepEqual(node.NormalAttributes, expectedAttributes) { + return fmt.Errorf("wrong normal attributes; expected %#v, got %#v", expectedAttributes, node.NormalAttributes) + } + if !reflect.DeepEqual(node.DefaultAttributes, expectedAttributes) { + return fmt.Errorf("wrong default attributes; expected %#v, got %#v", expectedAttributes, node.DefaultAttributes) + } + if !reflect.DeepEqual(node.OverrideAttributes, expectedAttributes) { + return fmt.Errorf("wrong override attributes; expected %#v, got %#v", expectedAttributes, node.OverrideAttributes) + } + + return nil + }, + ), + }, + }, + }) +} + +func testAccNodeCheckExists(rn string, node *chefc.Node) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[rn] + if !ok { + return fmt.Errorf("resource not found: %s", rn) + } + + if rs.Primary.ID == "" { + return fmt.Errorf("node id not set") + } + + client := testAccProvider.Meta().(*chefc.Client) + gotNode, err := client.Nodes.Get(rs.Primary.ID) + if err != nil { + return fmt.Errorf("error getting node: %s", err) + } + + *node = gotNode + + return nil + } +} + +func testAccNodeCheckDestroy(node *chefc.Node) resource.TestCheckFunc { + return func(s *terraform.State) error { + client := testAccProvider.Meta().(*chefc.Client) + _, err := client.Nodes.Get(node.Name) + if err == nil { + return fmt.Errorf("node still exists") + } + if _, ok := err.(*chefc.ErrorResponse); !ok { + // A more specific check is tricky because Chef Server can return + // a few different error codes in this case depending on which + // part of its stack catches the error. + return fmt.Errorf("got something other than an HTTP error (%v) when getting node", err) + } + + return nil + } +} + +const testAccNodeConfig_basic = ` +resource "chef_environment" "test" { + name = "terraform-acc-test-node-basic" +} +resource "chef_node" "test" { + name = "terraform-acc-test-basic" + environment_name = "terraform-acc-test-node-basic" + automatic_attributes_json = <