diff --git a/builtin/providers/chef/provider.go b/builtin/providers/chef/provider.go index 668d9e208..6a7f8e540 100644 --- a/builtin/providers/chef/provider.go +++ b/builtin/providers/chef/provider.go @@ -46,7 +46,7 @@ func Provider() terraform.ResourceProvider { //"chef_cookbook": resourceChefCookbook(), "chef_data_bag": resourceChefDataBag(), "chef_data_bag_item": resourceChefDataBagItem(), - //"chef_environment": resourceChefEnvironment(), + "chef_environment": resourceChefEnvironment(), //"chef_node": resourceChefNode(), //"chef_role": resourceChefRole(), }, diff --git a/builtin/providers/chef/resource_environment.go b/builtin/providers/chef/resource_environment.go new file mode 100644 index 000000000..605f037ac --- /dev/null +++ b/builtin/providers/chef/resource_environment.go @@ -0,0 +1,183 @@ +package chef + +import ( + "encoding/json" + "fmt" + + "github.com/hashicorp/terraform/helper/schema" + + chefc "github.com/go-chef/chef" +) + +func resourceChefEnvironment() *schema.Resource { + return &schema.Resource{ + Create: CreateEnvironment, + Update: UpdateEnvironment, + Read: ReadEnvironment, + Delete: DeleteEnvironment, + + Schema: map[string]*schema.Schema{ + "name": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "description": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Default: "Managed by Terraform", + }, + "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, + }, + "cookbook_constraints": &schema.Schema{ + Type: schema.TypeMap, + Optional: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + }, + } +} + +func CreateEnvironment(d *schema.ResourceData, meta interface{}) error { + client := meta.(*chefc.Client) + + env, err := environmentFromResourceData(d) + if err != nil { + return err + } + + _, err = client.Environments.Create(env) + if err != nil { + return err + } + + d.SetId(env.Name) + return ReadEnvironment(d, meta) +} + +func UpdateEnvironment(d *schema.ResourceData, meta interface{}) error { + client := meta.(*chefc.Client) + + env, err := environmentFromResourceData(d) + if err != nil { + return err + } + + _, err = client.Environments.Put(env) + if err != nil { + return err + } + + d.SetId(env.Name) + return ReadEnvironment(d, meta) +} + +func ReadEnvironment(d *schema.ResourceData, meta interface{}) error { + client := meta.(*chefc.Client) + + name := d.Id() + + env, err := client.Environments.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", env.Name) + d.Set("description", env.Description) + + defaultAttrJson, err := json.Marshal(env.DefaultAttributes) + if err != nil { + return err + } + d.Set("default_attributes_json", defaultAttrJson) + + overrideAttrJson, err := json.Marshal(env.OverrideAttributes) + if err != nil { + return err + } + d.Set("override_attributes_json", overrideAttrJson) + + cookbookVersionsI := map[string]interface{}{} + for k, v := range env.CookbookVersions { + cookbookVersionsI[k] = v + } + d.Set("cookbook_constraints", cookbookVersionsI) + + return nil +} + +func DeleteEnvironment(d *schema.ResourceData, meta interface{}) error { + client := meta.(*chefc.Client) + + name := d.Id() + + // For some reason Environments.Delete is not exposed by the + // underlying client library, so we have to do this manually. + + path := fmt.Sprintf("environments/%s", name) + + httpReq, err := client.NewRequest("DELETE", path, nil) + if err != nil { + return err + } + + _, err = client.Do(httpReq, nil) + if err == nil { + d.SetId("") + } + + return err +} + +func environmentFromResourceData(d *schema.ResourceData) (*chefc.Environment, error) { + + env := &chefc.Environment{ + Name: d.Get("name").(string), + Description: d.Get("description").(string), + ChefType: "environment", + } + + var err error + + err = json.Unmarshal( + []byte(d.Get("default_attributes_json").(string)), + &env.DefaultAttributes, + ) + if err != nil { + return nil, fmt.Errorf("default_attributes_json: %s", err) + } + + err = json.Unmarshal( + []byte(d.Get("override_attributes_json").(string)), + &env.OverrideAttributes, + ) + if err != nil { + return nil, fmt.Errorf("override_attributes_json: %s", err) + } + + env.CookbookVersions = make(map[string]string) + for k, vI := range d.Get("cookbook_constraints").(map[string]interface{}) { + env.CookbookVersions[k] = vI.(string) + } + + return env, nil +} diff --git a/builtin/providers/chef/resource_environment_test.go b/builtin/providers/chef/resource_environment_test.go new file mode 100644 index 000000000..b441d2ffc --- /dev/null +++ b/builtin/providers/chef/resource_environment_test.go @@ -0,0 +1,120 @@ +package chef + +import ( + "fmt" + "reflect" + "testing" + + chefc "github.com/go-chef/chef" + + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" +) + +func TestAccEnvironment_basic(t *testing.T) { + var env chefc.Environment + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccEnvironmentCheckDestroy(&env), + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccEnvironmentConfig_basic, + Check: resource.ComposeTestCheckFunc( + testAccEnvironmentCheckExists("chef_environment.test", &env), + func(s *terraform.State) error { + + if expected := "terraform-acc-test-basic"; env.Name != expected { + return fmt.Errorf("wrong name; expected %v, got %v", expected, env.Name) + } + if expected := "Terraform Acceptance Tests"; env.Description != expected { + return fmt.Errorf("wrong description; expected %v, got %v", expected, env.Description) + } + + expectedConstraints := map[string]string{ + "terraform": "= 1.0.0", + } + if !reflect.DeepEqual(env.CookbookVersions, expectedConstraints) { + return fmt.Errorf("wrong cookbook constraints; expected %#v, got %#v", expectedConstraints, env.CookbookVersions) + } + + var expectedAttributes interface{} + expectedAttributes = map[string]interface{}{ + "terraform_acc_test": true, + } + if !reflect.DeepEqual(env.DefaultAttributes, expectedAttributes) { + return fmt.Errorf("wrong default attributes; expected %#v, got %#v", expectedAttributes, env.DefaultAttributes) + } + if !reflect.DeepEqual(env.OverrideAttributes, expectedAttributes) { + return fmt.Errorf("wrong override attributes; expected %#v, got %#v", expectedAttributes, env.OverrideAttributes) + } + + return nil + }, + ), + }, + }, + }) +} + +func testAccEnvironmentCheckExists(rn string, env *chefc.Environment) 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("environment id not set") + } + + client := testAccProvider.Meta().(*chefc.Client) + gotEnv, err := client.Environments.Get(rs.Primary.ID) + if err != nil { + return fmt.Errorf("error getting environment: %s", err) + } + + *env = *gotEnv + + return nil + } +} + +func testAccEnvironmentCheckDestroy(env *chefc.Environment) resource.TestCheckFunc { + return func(s *terraform.State) error { + client := testAccProvider.Meta().(*chefc.Client) + _, err := client.Environments.Get(env.Name) + if err == nil { + return fmt.Errorf("environment 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 environment", err) + } + + return nil + } +} + +const testAccEnvironmentConfig_basic = ` +resource "chef_environment" "test" { + name = "terraform-acc-test-basic" + description = "Terraform Acceptance Tests" + default_attributes_json = <