diff --git a/builtin/bins/provider-localfile/main.go b/builtin/bins/provider-localfile/main.go new file mode 100644 index 000000000..4a98ecfdd --- /dev/null +++ b/builtin/bins/provider-localfile/main.go @@ -0,0 +1,12 @@ +package main + +import ( + "github.com/hashicorp/terraform/builtin/providers/localfile" + "github.com/hashicorp/terraform/plugin" +) + +func main() { + plugin.Serve(&plugin.ServeOpts{ + ProviderFunc: localfile.Provider, + }) +} diff --git a/builtin/providers/local/provider.go b/builtin/providers/local/provider.go new file mode 100644 index 000000000..ee048c689 --- /dev/null +++ b/builtin/providers/local/provider.go @@ -0,0 +1,15 @@ +package local + +import ( + "github.com/hashicorp/terraform/helper/schema" + "github.com/hashicorp/terraform/terraform" +) + +func Provider() terraform.ResourceProvider { + return &schema.Provider{ + Schema: map[string]*schema.Schema{}, + ResourcesMap: map[string]*schema.Resource{ + "local_file": resourceLocalFile(), + }, + } +} diff --git a/builtin/providers/local/provider_test.go b/builtin/providers/local/provider_test.go new file mode 100644 index 000000000..7385ffe3a --- /dev/null +++ b/builtin/providers/local/provider_test.go @@ -0,0 +1,18 @@ +package local + +import ( + "testing" + + "github.com/hashicorp/terraform/helper/schema" + "github.com/hashicorp/terraform/terraform" +) + +var testProviders = map[string]terraform.ResourceProvider{ + "local": Provider(), +} + +func TestProvider(t *testing.T) { + if err := Provider().(*schema.Provider).InternalValidate(); err != nil { + t.Fatalf("err: %s", err) + } +} diff --git a/builtin/providers/local/resource_local_file.go b/builtin/providers/local/resource_local_file.go new file mode 100644 index 000000000..6f6da1b94 --- /dev/null +++ b/builtin/providers/local/resource_local_file.go @@ -0,0 +1,84 @@ +package local + +import ( + "crypto/sha1" + "encoding/hex" + "io/ioutil" + "os" + "path" + + "github.com/hashicorp/terraform/helper/schema" +) + +func resourceLocalFile() *schema.Resource { + return &schema.Resource{ + Create: resourceLocalFileCreate, + Read: resourceLocalFileRead, + Delete: resourceLocalFileDelete, + + Schema: map[string]*schema.Schema{ + "content": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "filename": { + Type: schema.TypeString, + Description: "Path to the output file", + Required: true, + ForceNew: true, + }, + }, + } +} + +func resourceLocalFileRead(d *schema.ResourceData, _ interface{}) error { + // If the output file doesn't exist, mark the resource for creation. + outputPath := d.Get("filename").(string) + if _, err := os.Stat(outputPath); os.IsNotExist(err) { + d.SetId("") + return nil + } + + // Verify that the content of the destination file matches the content we + // expect. Otherwise, the file might have been modified externally and we + // must reconcile. + outputContent, err := ioutil.ReadFile(outputPath) + if err != nil { + return err + } + + outputChecksum := sha1.Sum([]byte(outputContent)) + if hex.EncodeToString(outputChecksum[:]) != d.Id() { + d.SetId("") + return nil + } + + return nil +} + +func resourceLocalFileCreate(d *schema.ResourceData, _ interface{}) error { + content := d.Get("content").(string) + destination := d.Get("filename").(string) + + destinationDir := path.Dir(destination) + if _, err := os.Stat(destinationDir); err != nil { + if err := os.MkdirAll(destinationDir, 0777); err != nil { + return err + } + } + + if err := ioutil.WriteFile(destination, []byte(content), 0777); err != nil { + return err + } + + checksum := sha1.Sum([]byte(content)) + d.SetId(hex.EncodeToString(checksum[:])) + + return nil +} + +func resourceLocalFileDelete(d *schema.ResourceData, _ interface{}) error { + os.Remove(d.Get("filename").(string)) + return nil +} diff --git a/builtin/providers/local/resource_local_file_test.go b/builtin/providers/local/resource_local_file_test.go new file mode 100644 index 000000000..33a44f0bb --- /dev/null +++ b/builtin/providers/local/resource_local_file_test.go @@ -0,0 +1,56 @@ +package local + +import ( + "errors" + "fmt" + "io/ioutil" + "os" + "testing" + + r "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" +) + +func TestLocalFile_Basic(t *testing.T) { + var cases = []struct { + path string + content string + config string + }{ + { + "local_file", + "This is some content", + `resource "local_file" "file" { + content = "This is some content" + filename = "local_file" + }`, + }, + } + + for _, tt := range cases { + r.UnitTest(t, r.TestCase{ + Providers: testProviders, + Steps: []r.TestStep{ + { + Config: tt.config, + Check: func(s *terraform.State) error { + content, err := ioutil.ReadFile(tt.path) + if err != nil { + return fmt.Errorf("config:\n%s\n,got: %s\n", tt.config, err) + } + if string(content) != tt.content { + return fmt.Errorf("config:\n%s\ngot:\n%s\nwant:\n%s\n", tt.config, content, tt.content) + } + return nil + }, + }, + }, + CheckDestroy: func(*terraform.State) error { + if _, err := os.Stat(tt.path); os.IsNotExist(err) { + return nil + } + return errors.New("local_file did not get destroyed") + }, + }) + } +} diff --git a/command/internal_plugin_list.go b/command/internal_plugin_list.go index 2f48908c7..83d4a4a30 100644 --- a/command/internal_plugin_list.go +++ b/command/internal_plugin_list.go @@ -39,6 +39,7 @@ import ( influxdbprovider "github.com/hashicorp/terraform/builtin/providers/influxdb" kubernetesprovider "github.com/hashicorp/terraform/builtin/providers/kubernetes" libratoprovider "github.com/hashicorp/terraform/builtin/providers/librato" + localprovider "github.com/hashicorp/terraform/builtin/providers/local" logentriesprovider "github.com/hashicorp/terraform/builtin/providers/logentries" mailgunprovider "github.com/hashicorp/terraform/builtin/providers/mailgun" mysqlprovider "github.com/hashicorp/terraform/builtin/providers/mysql" @@ -115,6 +116,7 @@ var InternalProviders = map[string]plugin.ProviderFunc{ "influxdb": influxdbprovider.Provider, "kubernetes": kubernetesprovider.Provider, "librato": libratoprovider.Provider, + "local": localprovider.Provider, "logentries": logentriesprovider.Provider, "mailgun": mailgunprovider.Provider, "mysql": mysqlprovider.Provider, diff --git a/website/source/docs/providers/local/index.html.markdown b/website/source/docs/providers/local/index.html.markdown new file mode 100644 index 000000000..cc83fd241 --- /dev/null +++ b/website/source/docs/providers/local/index.html.markdown @@ -0,0 +1,19 @@ +--- +layout: "local" +page_title: "Provider: Local" +sidebar_current: "docs-local-index" +description: |- + The Local provider is used to manage local resources (i.e. files). +--- + +# Local Provider + +The Local provider is used to manage local resources (i.e. files). + +Use the navigation to the left to read about the available resources. + +## Example Usage + +``` +provider "local" {} +``` diff --git a/website/source/docs/providers/local/r/file.html.md b/website/source/docs/providers/local/r/file.html.md new file mode 100644 index 000000000..f0e3c8628 --- /dev/null +++ b/website/source/docs/providers/local/r/file.html.md @@ -0,0 +1,30 @@ +--- +layout: "local" +page_title: "Local: local_file" +sidebar_current: "docs-local-resource-file" +description: |- + Generates a local file from content. +--- + +# local\_file + +Generates a local file from a given content. + +## Example Usage + +``` +data "local_file" "foo" { + content = "foo!" + filename = "${path.module}/foo.bar" +} +``` + +## Argument Reference + +The following arguments are supported: + +* `content` - (required) The content of file to create. + +* `filename` - (required) The path of the file to create. + +NOTE: Any required parent folders are created automatically. Additionally, any existing file will get overwritten. \ No newline at end of file diff --git a/website/source/layouts/docs.erb b/website/source/layouts/docs.erb index 347eefbe4..868ce88e1 100644 --- a/website/source/layouts/docs.erb +++ b/website/source/layouts/docs.erb @@ -304,6 +304,10 @@ Librato + > + Local + + > Logentries diff --git a/website/source/layouts/local.erb b/website/source/layouts/local.erb new file mode 100644 index 000000000..bf4f56acc --- /dev/null +++ b/website/source/layouts/local.erb @@ -0,0 +1,24 @@ +<% wrap_layout :inner do %> + <% content_for :sidebar do %> + + <% end %> + + <%= yield %> + <% end %> \ No newline at end of file