From 53aa3fb049ffcf395a6c4d6bb6f354cf09d4782b Mon Sep 17 00:00:00 2001 From: Martin Atkins Date: Wed, 26 Aug 2015 16:50:12 -0700 Subject: [PATCH 1/7] Entry point for chef provider. --- builtin/bins/provider-chef/main.go | 12 ++++ builtin/providers/chef/provider.go | 79 +++++++++++++++++++++++++ builtin/providers/chef/provider_test.go | 62 +++++++++++++++++++ 3 files changed, 153 insertions(+) create mode 100644 builtin/bins/provider-chef/main.go create mode 100644 builtin/providers/chef/provider.go create mode 100644 builtin/providers/chef/provider_test.go diff --git a/builtin/bins/provider-chef/main.go b/builtin/bins/provider-chef/main.go new file mode 100644 index 000000000000..b1bd8b537ef7 --- /dev/null +++ b/builtin/bins/provider-chef/main.go @@ -0,0 +1,12 @@ +package main + +import ( + "github.com/hashicorp/terraform/builtin/providers/chef" + "github.com/hashicorp/terraform/plugin" +) + +func main() { + plugin.Serve(&plugin.ServeOpts{ + ProviderFunc: chef.Provider, + }) +} diff --git a/builtin/providers/chef/provider.go b/builtin/providers/chef/provider.go new file mode 100644 index 000000000000..2319d7639b46 --- /dev/null +++ b/builtin/providers/chef/provider.go @@ -0,0 +1,79 @@ +package chef + +import ( + "io/ioutil" + "os" + "time" + + "github.com/hashicorp/terraform/helper/schema" + "github.com/hashicorp/terraform/terraform" + + chefc "github.com/go-chef/chef" +) + +func Provider() terraform.ResourceProvider { + return &schema.Provider{ + Schema: map[string]*schema.Schema{ + "server_url": &schema.Schema{ + Type: schema.TypeString, + Required: true, + DefaultFunc: schema.EnvDefaultFunc("CHEF_SERVER_URL", nil), + Description: "URL of the root of the target Chef server or organization.", + }, + "client_name": &schema.Schema{ + Type: schema.TypeString, + Required: true, + DefaultFunc: schema.EnvDefaultFunc("CHEF_CLIENT_NAME", nil), + Description: "Name of a registered client within the Chef server.", + }, + "private_key_pem": &schema.Schema{ + Type: schema.TypeString, + Required: true, + DefaultFunc: providerPrivateKeyEnvDefault, + Description: "PEM-formatted private key for client authentication.", + }, + "allow_unverified_ssl": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Description: "If set, the Chef client will permit unverifiable SSL certificates.", + }, + }, + + ResourcesMap: map[string]*schema.Resource{ + //"chef_acl": resourceChefAcl(), + //"chef_client": resourceChefClient(), + //"chef_cookbook": resourceChefCookbook(), + //"chef_data_bag": resourceChefDataBag(), + //"chef_data_bag_item": resourceChefDataBagItem(), + //"chef_environment": resourceChefEnvironment(), + //"chef_node": resourceChefNode(), + //"chef_role": resourceChefRole(), + }, + + ConfigureFunc: providerConfigure, + } +} + +func providerConfigure(d *schema.ResourceData) (interface{}, error) { + config := &chefc.Config{ + Name: d.Get("client_name").(string), + Key: d.Get("private_key_pem").(string), + BaseURL: d.Get("server_url").(string), + SkipSSL: d.Get("allow_unverified_ssl").(bool), + Timeout: 10 * time.Second, + } + + return chefc.NewClient(config) +} + +func providerPrivateKeyEnvDefault() (interface{}, error) { + if fn := os.Getenv("CHEF_PRIVATE_KEY_FILE"); fn != "" { + contents, err := ioutil.ReadFile(fn) + if err != nil { + return nil, err + } + return string(contents), nil + } + + return nil, nil +} diff --git a/builtin/providers/chef/provider_test.go b/builtin/providers/chef/provider_test.go new file mode 100644 index 000000000000..1d12945f4604 --- /dev/null +++ b/builtin/providers/chef/provider_test.go @@ -0,0 +1,62 @@ +package chef + +import ( + "os" + "testing" + + "github.com/hashicorp/terraform/helper/schema" + "github.com/hashicorp/terraform/terraform" +) + +// To run these acceptance tests, you will need access to a Chef server. +// An easy way to get one is to sign up for a hosted Chef server account +// at https://manage.chef.io/signup , after which your base URL will +// be something like https://api.opscode.com/organizations/example/ . +// You will also need to create a "client" and write its private key to +// a file somewhere. +// +// You can then set the following environment variables to make these +// tests work: +// CHEF_SERVER_URL to the base URL as described above. +// CHEF_CLIENT_NAME to the name of the client object you created. +// CHEF_PRIVATE_KEY_FILE to the path to the private key file you created. +// +// You will probably need to edit the global permissions on your Chef +// Server account to allow this client (or all clients, if you're lazy) +// to have both List and Create access on all types of object: +// https://manage.chef.io/organizations/saymedia/global_permissions +// +// With all of that done, you can run like this: +// make testacc TEST=./builtin/providers/chef + +var testAccProviders map[string]terraform.ResourceProvider +var testAccProvider *schema.Provider + +func init() { + testAccProvider = Provider().(*schema.Provider) + testAccProviders = map[string]terraform.ResourceProvider{ + "chef": testAccProvider, + } +} + +func TestProvider(t *testing.T) { + if err := Provider().(*schema.Provider).InternalValidate(); err != nil { + t.Fatalf("err: %s", err) + } +} + +func TestProvider_impl(t *testing.T) { + var _ terraform.ResourceProvider = Provider() +} + +func testAccPreCheck(t *testing.T) { + if v := os.Getenv("CHEF_SERVER_URL"); v == "" { + t.Fatal("CHEF_SERVER_URL must be set for acceptance tests") + } + if v := os.Getenv("CHEF_CLIENT_NAME"); v == "" { + t.Fatal("CHEF_CLIENT_NAME must be set for acceptance tests") + } + if v := os.Getenv("CHEF_PRIVATE_KEY_FILE"); v == "" { + t.Fatal("CHEF_PRIVATE_KEY_FILE must be set for acceptance tests") + } +} From d583b936b2a0db4a449af32de679f0115cc3a35c Mon Sep 17 00:00:00 2001 From: Martin Atkins Date: Wed, 26 Aug 2015 17:37:09 -0700 Subject: [PATCH 2/7] chef_data_bag resource. --- builtin/providers/chef/provider.go | 2 +- builtin/providers/chef/resource_data_bag.go | 77 +++++++++++++++++++ .../providers/chef/resource_data_bag_test.go | 70 +++++++++++++++++ 3 files changed, 148 insertions(+), 1 deletion(-) create mode 100644 builtin/providers/chef/resource_data_bag.go create mode 100644 builtin/providers/chef/resource_data_bag_test.go diff --git a/builtin/providers/chef/provider.go b/builtin/providers/chef/provider.go index 2319d7639b46..9f3e41255c2c 100644 --- a/builtin/providers/chef/provider.go +++ b/builtin/providers/chef/provider.go @@ -43,7 +43,7 @@ func Provider() terraform.ResourceProvider { //"chef_acl": resourceChefAcl(), //"chef_client": resourceChefClient(), //"chef_cookbook": resourceChefCookbook(), - //"chef_data_bag": resourceChefDataBag(), + "chef_data_bag": resourceChefDataBag(), //"chef_data_bag_item": resourceChefDataBagItem(), //"chef_environment": resourceChefEnvironment(), //"chef_node": resourceChefNode(), diff --git a/builtin/providers/chef/resource_data_bag.go b/builtin/providers/chef/resource_data_bag.go new file mode 100644 index 000000000000..a9c08748cdc3 --- /dev/null +++ b/builtin/providers/chef/resource_data_bag.go @@ -0,0 +1,77 @@ +package chef + +import ( + "github.com/hashicorp/terraform/helper/schema" + + chefc "github.com/go-chef/chef" +) + +func resourceChefDataBag() *schema.Resource { + return &schema.Resource{ + Create: CreateDataBag, + Read: ReadDataBag, + Delete: DeleteDataBag, + + Schema: map[string]*schema.Schema{ + "name": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "api_uri": &schema.Schema{ + Type: schema.TypeString, + Computed: true, + }, + }, + } +} + +func CreateDataBag(d *schema.ResourceData, meta interface{}) error { + client := meta.(*chefc.Client) + + dataBag := &chefc.DataBag{ + Name: d.Get("name").(string), + } + + result, err := client.DataBags.Create(dataBag) + if err != nil { + return err + } + + d.SetId(dataBag.Name) + d.Set("api_uri", result.URI) + return nil +} + +func ReadDataBag(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. + + name := d.Id() + + _, err := client.DataBags.ListItems(name) + if err != nil { + if errRes, ok := err.(*chefc.ErrorResponse); ok { + if errRes.Response.StatusCode == 404 { + d.SetId("") + return nil + } + } + } + return err +} + +func DeleteDataBag(d *schema.ResourceData, meta interface{}) error { + client := meta.(*chefc.Client) + + name := d.Id() + + _, err := client.DataBags.Delete(name) + if err == nil { + d.SetId("") + } + return err +} diff --git a/builtin/providers/chef/resource_data_bag_test.go b/builtin/providers/chef/resource_data_bag_test.go new file mode 100644 index 000000000000..92b74e5df6fa --- /dev/null +++ b/builtin/providers/chef/resource_data_bag_test.go @@ -0,0 +1,70 @@ +package chef + +import ( + "fmt" + "testing" + + chefc "github.com/go-chef/chef" + + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" +) + +func TestAccDataBag_basic(t *testing.T) { + var dataBagName string + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccDataBagCheckDestroy(dataBagName), + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccDataBagConfig_basic, + Check: testAccDataBagCheckExists("chef_data_bag.test", &dataBagName), + }, + }, + }) +} + +func testAccDataBagCheckExists(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 id not set") + } + + client := testAccProvider.Meta().(*chefc.Client) + _, err := client.DataBags.ListItems(rs.Primary.ID) + if err != nil { + return fmt.Errorf("error getting data bag: %s", err) + } + + *name = rs.Primary.ID + + return nil + } +} + +func testAccDataBagCheckDestroy(name string) resource.TestCheckFunc { + return func(s *terraform.State) error { + client := testAccProvider.Meta().(*chefc.Client) + result, err := client.DataBags.ListItems(name) + if err == nil && len(*result) != 0 { + return fmt.Errorf("data bag 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", err) + } + + return nil + } +} + +const testAccDataBagConfig_basic = ` +resource "chef_data_bag" "test" { + name = "terraform-acc-test-basic" +} +` From 406aba4a6289595c4573b9b3692719deac098b0e Mon Sep 17 00:00:00 2001 From: Martin Atkins Date: Thu, 27 Aug 2015 10:02:25 -0700 Subject: [PATCH 3/7] chef_data_bag_item resource. --- builtin/providers/chef/provider.go | 20 ++- .../providers/chef/resource_data_bag_item.go | 120 ++++++++++++++++++ .../chef/resource_data_bag_item_test.go | 95 ++++++++++++++ 3 files changed, 234 insertions(+), 1 deletion(-) create mode 100644 builtin/providers/chef/resource_data_bag_item.go create mode 100644 builtin/providers/chef/resource_data_bag_item_test.go diff --git a/builtin/providers/chef/provider.go b/builtin/providers/chef/provider.go index 9f3e41255c2c..668d9e2080a2 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 000000000000..ff6f7ac67327 --- /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 000000000000..9630d8b6c878 --- /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 = < Date: Thu, 27 Aug 2015 18:04:22 -0700 Subject: [PATCH 4/7] chef_environment resource. --- builtin/providers/chef/provider.go | 2 +- .../providers/chef/resource_environment.go | 183 ++++++++++++++++++ .../chef/resource_environment_test.go | 120 ++++++++++++ 3 files changed, 304 insertions(+), 1 deletion(-) create mode 100644 builtin/providers/chef/resource_environment.go create mode 100644 builtin/providers/chef/resource_environment_test.go diff --git a/builtin/providers/chef/provider.go b/builtin/providers/chef/provider.go index 668d9e2080a2..6a7f8e54010b 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 000000000000..605f037acbbb --- /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 000000000000..b441d2ffc42d --- /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 = < Date: Thu, 27 Aug 2015 18:29:25 -0700 Subject: [PATCH 5/7] chef_role resource. --- builtin/providers/chef/provider.go | 17 +- builtin/providers/chef/resource_role.go | 185 +++++++++++++++++++ builtin/providers/chef/resource_role_test.go | 120 ++++++++++++ 3 files changed, 321 insertions(+), 1 deletion(-) create mode 100644 builtin/providers/chef/resource_role.go create mode 100644 builtin/providers/chef/resource_role_test.go diff --git a/builtin/providers/chef/provider.go b/builtin/providers/chef/provider.go index 6a7f8e54010b..a362b0071f52 100644 --- a/builtin/providers/chef/provider.go +++ b/builtin/providers/chef/provider.go @@ -2,8 +2,10 @@ package chef import ( "encoding/json" + "fmt" "io/ioutil" "os" + "strings" "time" "github.com/hashicorp/terraform/helper/schema" @@ -48,7 +50,7 @@ func Provider() terraform.ResourceProvider { "chef_data_bag_item": resourceChefDataBagItem(), "chef_environment": resourceChefEnvironment(), //"chef_node": resourceChefNode(), - //"chef_role": resourceChefRole(), + "chef_role": resourceChefRole(), }, ConfigureFunc: providerConfigure, @@ -95,3 +97,16 @@ func jsonStateFunc(value interface{}) string { jsonValue, _ := json.Marshal(&tmp) return string(jsonValue) } + +func runListEntryStateFunc(value interface{}) string { + // Recipes in run lists can either be naked, like "foo", or can + // be explicitly qualified as "recipe[foo]". Whichever form we use, + // the server will always normalize to the explicit form, + // so we'll normalize too and then we won't generate unnecessary + // diffs when we refresh. + in := value.(string) + if !strings.Contains(in, "[") { + return fmt.Sprintf("recipe[%s]", in) + } + return in +} diff --git a/builtin/providers/chef/resource_role.go b/builtin/providers/chef/resource_role.go new file mode 100644 index 000000000000..8e3f7431635d --- /dev/null +++ b/builtin/providers/chef/resource_role.go @@ -0,0 +1,185 @@ +package chef + +import ( + "encoding/json" + "fmt" + + "github.com/hashicorp/terraform/helper/schema" + + chefc "github.com/go-chef/chef" +) + +func resourceChefRole() *schema.Resource { + return &schema.Resource{ + Create: CreateRole, + Update: UpdateRole, + Read: ReadRole, + Delete: DeleteRole, + + 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, + }, + "run_list": &schema.Schema{ + Type: schema.TypeList, + Optional: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + StateFunc: runListEntryStateFunc, + }, + }, + }, + } +} + +func CreateRole(d *schema.ResourceData, meta interface{}) error { + client := meta.(*chefc.Client) + + role, err := roleFromResourceData(d) + if err != nil { + return err + } + + _, err = client.Roles.Create(role) + if err != nil { + return err + } + + d.SetId(role.Name) + return ReadRole(d, meta) +} + +func UpdateRole(d *schema.ResourceData, meta interface{}) error { + client := meta.(*chefc.Client) + + role, err := roleFromResourceData(d) + if err != nil { + return err + } + + _, err = client.Roles.Put(role) + if err != nil { + return err + } + + d.SetId(role.Name) + return ReadRole(d, meta) +} + +func ReadRole(d *schema.ResourceData, meta interface{}) error { + client := meta.(*chefc.Client) + + name := d.Id() + + role, err := client.Roles.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", role.Name) + d.Set("description", role.Description) + + defaultAttrJson, err := json.Marshal(role.DefaultAttributes) + if err != nil { + return err + } + d.Set("default_attributes_json", defaultAttrJson) + + overrideAttrJson, err := json.Marshal(role.OverrideAttributes) + if err != nil { + return err + } + d.Set("override_attributes_json", overrideAttrJson) + + runListI := make([]interface{}, len(role.RunList)) + for i, v := range role.RunList { + runListI[i] = v + } + d.Set("run_list", runListI) + + return nil +} + +func DeleteRole(d *schema.ResourceData, meta interface{}) error { + client := meta.(*chefc.Client) + + name := d.Id() + + // For some reason Roles.Delete is not exposed by the + // underlying client library, so we have to do this manually. + + path := fmt.Sprintf("roles/%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 roleFromResourceData(d *schema.ResourceData) (*chefc.Role, error) { + + role := &chefc.Role{ + Name: d.Get("name").(string), + Description: d.Get("description").(string), + ChefType: "role", + } + + var err error + + err = json.Unmarshal( + []byte(d.Get("default_attributes_json").(string)), + &role.DefaultAttributes, + ) + if err != nil { + return nil, fmt.Errorf("default_attributes_json: %s", err) + } + + err = json.Unmarshal( + []byte(d.Get("override_attributes_json").(string)), + &role.OverrideAttributes, + ) + if err != nil { + return nil, fmt.Errorf("override_attributes_json: %s", err) + } + + runListI := d.Get("run_list").([]interface{}) + role.RunList = make([]string, len(runListI)) + for i, vI := range runListI { + role.RunList[i] = vI.(string) + } + + return role, nil +} diff --git a/builtin/providers/chef/resource_role_test.go b/builtin/providers/chef/resource_role_test.go new file mode 100644 index 000000000000..3859e4e858df --- /dev/null +++ b/builtin/providers/chef/resource_role_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 TestAccRole_basic(t *testing.T) { + var role chefc.Role + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccRoleCheckDestroy(&role), + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccRoleConfig_basic, + Check: resource.ComposeTestCheckFunc( + testAccRoleCheckExists("chef_role.test", &role), + func(s *terraform.State) error { + + if expected := "terraform-acc-test-basic"; role.Name != expected { + return fmt.Errorf("wrong name; expected %v, got %v", expected, role.Name) + } + if expected := "Terraform Acceptance Tests"; role.Description != expected { + return fmt.Errorf("wrong description; expected %v, got %v", expected, role.Description) + } + + expectedRunList := chefc.RunList{ + "recipe[terraform@1.0.0]", + "recipe[consul]", + "role[foo]", + } + if !reflect.DeepEqual(role.RunList, expectedRunList) { + return fmt.Errorf("wrong runlist; expected %#v, got %#v", expectedRunList, role.RunList) + } + + var expectedAttributes interface{} + expectedAttributes = map[string]interface{}{ + "terraform_acc_test": true, + } + if !reflect.DeepEqual(role.DefaultAttributes, expectedAttributes) { + return fmt.Errorf("wrong default attributes; expected %#v, got %#v", expectedAttributes, role.DefaultAttributes) + } + if !reflect.DeepEqual(role.OverrideAttributes, expectedAttributes) { + return fmt.Errorf("wrong override attributes; expected %#v, got %#v", expectedAttributes, role.OverrideAttributes) + } + + return nil + }, + ), + }, + }, + }) +} + +func testAccRoleCheckExists(rn string, role *chefc.Role) 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("role id not set") + } + + client := testAccProvider.Meta().(*chefc.Client) + gotRole, err := client.Roles.Get(rs.Primary.ID) + if err != nil { + return fmt.Errorf("error getting role: %s", err) + } + + *role = *gotRole + + return nil + } +} + +func testAccRoleCheckDestroy(role *chefc.Role) resource.TestCheckFunc { + return func(s *terraform.State) error { + client := testAccProvider.Meta().(*chefc.Client) + _, err := client.Roles.Get(role.Name) + if err == nil { + return fmt.Errorf("role 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 role", err) + } + + return nil + } +} + +const testAccRoleConfig_basic = ` +resource "chef_role" "test" { + name = "terraform-acc-test-basic" + description = "Terraform Acceptance Tests" + default_attributes_json = < Date: Thu, 27 Aug 2015 18:54:52 -0700 Subject: [PATCH 6/7] 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 a362b0071f52..7a04b977583e 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 000000000000..6ded52ce2fe2 --- /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 000000000000..ace6c75a3a8d --- /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 = < Date: Sat, 29 Aug 2015 09:16:50 -0700 Subject: [PATCH 7/7] Documentation for the Chef provider. --- website/source/assets/stylesheets/_docs.scss | 1 + .../docs/providers/chef/index.html.markdown | 60 +++++++++++++++++++ .../providers/chef/r/data_bag.html.markdown | 38 ++++++++++++ .../chef/r/data_bag_item.html.markdown | 48 +++++++++++++++ .../chef/r/environment.html.markdown | 40 +++++++++++++ .../docs/providers/chef/r/node.html.markdown | 48 +++++++++++++++ .../docs/providers/chef/r/role.html.markdown | 40 +++++++++++++ website/source/layouts/chef.erb | 38 ++++++++++++ website/source/layouts/docs.erb | 4 ++ 9 files changed, 317 insertions(+) create mode 100644 website/source/docs/providers/chef/index.html.markdown create mode 100644 website/source/docs/providers/chef/r/data_bag.html.markdown create mode 100644 website/source/docs/providers/chef/r/data_bag_item.html.markdown create mode 100644 website/source/docs/providers/chef/r/environment.html.markdown create mode 100644 website/source/docs/providers/chef/r/node.html.markdown create mode 100644 website/source/docs/providers/chef/r/role.html.markdown create mode 100644 website/source/layouts/chef.erb diff --git a/website/source/assets/stylesheets/_docs.scss b/website/source/assets/stylesheets/_docs.scss index ed1a598d31be..017ea474a0d7 100755 --- a/website/source/assets/stylesheets/_docs.scss +++ b/website/source/assets/stylesheets/_docs.scss @@ -9,6 +9,7 @@ body.page-sub{ body.layout-atlas, body.layout-aws, body.layout-azure, +body.layout-chef, body.layout-cloudflare, body.layout-cloudstack, body.layout-consul, diff --git a/website/source/docs/providers/chef/index.html.markdown b/website/source/docs/providers/chef/index.html.markdown new file mode 100644 index 000000000000..91bcf99826b0 --- /dev/null +++ b/website/source/docs/providers/chef/index.html.markdown @@ -0,0 +1,60 @@ +--- +layout: "chef" +page_title: "Provider: Chef" +sidebar_current: "docs-chef-index" +description: |- + Chef is a systems and cloud infrastructure automation framework. +--- + +# Chef Provider + +[Chef](https://www.chef.io/) is a systems and cloud infrastructure automation +framework. The Chef provider allows Terraform to manage various resources +that exist within [Chef Server](http://docs.chef.io/chef_server.html). + +Use the navigation to the left to read about the available resources. + +## Example Usage + +``` +# Configure the Chef provider +provider "chef" { + "server_url" = "https://api.opscode.com/organizations/example/" + + // You can set up a "Client" within the Chef Server management console. + "client_name" = "terraform" + "private_key_pem" = "${file(\"chef-terraform.pem\")}" +} + +# Create a Chef Environment +resource "chef_environment" "production" { + name = "production" +} + +# Create a Chef Role +resource "chef_role" "app_server" { + name = "app_server" + run_list = [ + "recipe[terraform]" + ] +} +``` + +## Argument Reference + +The following arguments are supported: + +* `server_url` - (Required) The HTTP(S) API URL of the Chef server to use. If + the target Chef server supports organizations, use the full URL of the + organization you wish to configure. May be provided instead via the + ``CHEF_SERVER_URL`` environment variable. +* `client_name` - (Required) The name of the client account to use when making + requests. This must have been already configured on the Chef server. + May be provided instead via the ``CHEF_CLIENT_NAME`` environment variable. +* `private_key_pem` - (Required) The PEM-formatted private key belonging to + the configured client. This is issued by the server when a new client object + is created. May be provided instead in a file whose path is in the + ``CHEF_PRIVATE_KEY_FILE`` environment variable. +* `allow_unverified_ssl` - (Optional) Boolean indicating whether to make + requests to a Chef server whose SSL certicate cannot be verified. Defaults + to ``false``. diff --git a/website/source/docs/providers/chef/r/data_bag.html.markdown b/website/source/docs/providers/chef/r/data_bag.html.markdown new file mode 100644 index 000000000000..6df60d84f579 --- /dev/null +++ b/website/source/docs/providers/chef/r/data_bag.html.markdown @@ -0,0 +1,38 @@ +--- +layout: "chef" +page_title: "Chef: chef_data_bag" +sidebar_current: "docs-chef-resource-data-bag" +description: |- + Creates and manages a data bag in Chef Server. +--- + +# chef\_data\_bag + +A [data bag](http://docs.chef.io/data_bags.html) is a collection of +configuration objects that are stored as JSON in Chef Server and can be +retrieved and used in Chef recipes. + +This resource creates the data bag itself. Inside each data bag is a collection +of items which can be created using the ``chef_data_bag_item`` resource. + +## Example Usage + +``` +resource "chef_data_bag" "example" { + name = "example-data-bag" +} +``` + +## Argument Reference + +The following arguments are supported: + +* `name` - (Required) The unique name to assign to the data bag. This is the + name that other server clients will use to find and retrieve data from the + data bag. + +## Attributes Reference + +The following attributes are exported: + +* `api_url` - The URL representing this data bag in the Chef server API. diff --git a/website/source/docs/providers/chef/r/data_bag_item.html.markdown b/website/source/docs/providers/chef/r/data_bag_item.html.markdown new file mode 100644 index 000000000000..2265c16e4f67 --- /dev/null +++ b/website/source/docs/providers/chef/r/data_bag_item.html.markdown @@ -0,0 +1,48 @@ +--- +layout: "chef" +page_title: "Chef: chef_data_bag_item" +sidebar_current: "docs-chef-resource-data-bag-item" +description: |- + Creates and manages an object within a data bag in Chef Server. +--- + +# chef\_data\_bag\_item + +A [data bag](http://docs.chef.io/data_bags.html) is a collection of +configuration objects that are stored as JSON in Chef Server and can be +retrieved and used in Chef recipes. + +This resource creates objects within an existing data bag. To create the +data bag itself, use the ``chef_data_bag`` resource. + +## Example Usage + +``` +resource "chef_data_bag_item" "example" { + data_bag_name = "example-data-bag" + content_json = < + <% content_for :sidebar do %> + + <% end %> + + <%= yield %> + <% end %> diff --git a/website/source/layouts/docs.erb b/website/source/layouts/docs.erb index ff088e8e8cbe..3deb5be98f1f 100644 --- a/website/source/layouts/docs.erb +++ b/website/source/layouts/docs.erb @@ -133,6 +133,10 @@ Azure + > + Chef + + > CloudFlare