diff --git a/builtin/providers/chef/provider.go b/builtin/providers/chef/provider.go index 9f3e41255..668d9e208 100644 --- a/builtin/providers/chef/provider.go +++ b/builtin/providers/chef/provider.go @@ -1,6 +1,7 @@ package chef import ( + "encoding/json" "io/ioutil" "os" "time" @@ -44,7 +45,7 @@ func Provider() terraform.ResourceProvider { //"chef_client": resourceChefClient(), //"chef_cookbook": resourceChefCookbook(), "chef_data_bag": resourceChefDataBag(), - //"chef_data_bag_item": resourceChefDataBagItem(), + "chef_data_bag_item": resourceChefDataBagItem(), //"chef_environment": resourceChefEnvironment(), //"chef_node": resourceChefNode(), //"chef_role": resourceChefRole(), @@ -77,3 +78,20 @@ func providerPrivateKeyEnvDefault() (interface{}, error) { return nil, nil } + +func jsonStateFunc(value interface{}) string { + // Parse and re-stringify the JSON to make sure it's always kept + // in a normalized form. + in, ok := value.(string) + if !ok { + return "null" + } + var tmp map[string]interface{} + + // Assuming the value must be valid JSON since it passed okay through + // our prepareDataBagItemContent function earlier. + json.Unmarshal([]byte(in), &tmp) + + jsonValue, _ := json.Marshal(&tmp) + return string(jsonValue) +} diff --git a/builtin/providers/chef/resource_data_bag_item.go b/builtin/providers/chef/resource_data_bag_item.go new file mode 100644 index 000000000..ff6f7ac67 --- /dev/null +++ b/builtin/providers/chef/resource_data_bag_item.go @@ -0,0 +1,120 @@ +package chef + +import ( + "encoding/json" + "fmt" + + "github.com/hashicorp/terraform/helper/schema" + + chefc "github.com/go-chef/chef" +) + +func resourceChefDataBagItem() *schema.Resource { + return &schema.Resource{ + Create: CreateDataBagItem, + Read: ReadDataBagItem, + Delete: DeleteDataBagItem, + + Schema: map[string]*schema.Schema{ + "data_bag_name": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "content_json": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + StateFunc: jsonStateFunc, + }, + "id": &schema.Schema{ + Type: schema.TypeString, + Computed: true, + }, + }, + } +} + +func CreateDataBagItem(d *schema.ResourceData, meta interface{}) error { + client := meta.(*chefc.Client) + + dataBagName := d.Get("data_bag_name").(string) + itemId, itemContent, err := prepareDataBagItemContent(d.Get("content_json").(string)) + if err != nil { + return err + } + + err = client.DataBags.CreateItem(dataBagName, itemContent) + if err != nil { + return err + } + + d.SetId(itemId) + d.Set("id", itemId) + return nil +} + +func ReadDataBagItem(d *schema.ResourceData, meta interface{}) error { + client := meta.(*chefc.Client) + + // The Chef API provides no API to read a data bag's metadata, + // but we can try to read its items and use that as a proxy for + // whether it still exists. + + itemId := d.Id() + dataBagName := d.Get("data_bag_name").(string) + + value, err := client.DataBags.GetItem(dataBagName, itemId) + if err != nil { + if errRes, ok := err.(*chefc.ErrorResponse); ok { + if errRes.Response.StatusCode == 404 { + d.SetId("") + return nil + } + } else { + return err + } + } + + jsonContent, err := json.Marshal(value) + if err != nil { + return err + } + + d.Set("content_json", string(jsonContent)) + + return nil +} + +func DeleteDataBagItem(d *schema.ResourceData, meta interface{}) error { + client := meta.(*chefc.Client) + + itemId := d.Id() + dataBagName := d.Get("data_bag_name").(string) + + err := client.DataBags.DeleteItem(dataBagName, itemId) + if err == nil { + d.SetId("") + d.Set("id", "") + } + return err +} + +func prepareDataBagItemContent(contentJson string) (string, interface{}, error) { + var value map[string]interface{} + err := json.Unmarshal([]byte(contentJson), &value) + if err != nil { + return "", nil, err + } + + var itemId string + if itemIdI, ok := value["id"]; ok { + itemId, _ = itemIdI.(string) + } + + if itemId == "" { + return "", nil, fmt.Errorf("content_json must have id attribute, set to a string") + } + + return itemId, value, nil +} diff --git a/builtin/providers/chef/resource_data_bag_item_test.go b/builtin/providers/chef/resource_data_bag_item_test.go new file mode 100644 index 000000000..9630d8b6c --- /dev/null +++ b/builtin/providers/chef/resource_data_bag_item_test.go @@ -0,0 +1,95 @@ +package chef + +import ( + "fmt" + "reflect" + "testing" + + chefc "github.com/go-chef/chef" + + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" +) + +func TestAccDataBagItem_basic(t *testing.T) { + var dataBagItemName string + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccDataBagItemCheckDestroy(dataBagItemName), + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccDataBagItemConfig_basic, + Check: testAccDataBagItemCheck( + "chef_data_bag_item.test", &dataBagItemName, + ), + }, + }, + }) +} + +func testAccDataBagItemCheck(rn string, name *string) 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("data bag item id not set") + } + + client := testAccProvider.Meta().(*chefc.Client) + content, err := client.DataBags.GetItem("terraform-acc-test-bag-item-basic", rs.Primary.ID) + if err != nil { + return fmt.Errorf("error getting data bag item: %s", err) + } + + expectedContent := map[string]interface{}{ + "id": "terraform_acc_test", + "something_else": true, + } + if !reflect.DeepEqual(content, expectedContent) { + return fmt.Errorf("wrong content: expected %#v, got %#v", expectedContent, content) + } + + if expected := "terraform_acc_test"; rs.Primary.Attributes["id"] != expected { + return fmt.Errorf("wrong id; expected %#v, got %#v", expected, rs.Primary.Attributes["id"]) + } + + *name = rs.Primary.ID + + return nil + } +} + +func testAccDataBagItemCheckDestroy(name string) resource.TestCheckFunc { + return func(s *terraform.State) error { + client := testAccProvider.Meta().(*chefc.Client) + _, err := client.DataBags.GetItem("terraform-acc-test-bag-item-basic", name) + if err == nil { + return fmt.Errorf("data bag item still exists") + } + if _, ok := err.(*chefc.ErrorResponse); err != nil && !ok { + return fmt.Errorf("got something other than an HTTP error (%v) when getting data bag item", err) + } + + return nil + } +} + +const testAccDataBagItemConfig_basic = ` +resource "chef_data_bag" "test" { + name = "terraform-acc-test-bag-item-basic" +} +resource "chef_data_bag_item" "test" { + data_bag_name = "terraform-acc-test-bag-item-basic" + depends_on = ["chef_data_bag.test"] + content_json = <