provider/local: Implement a new local_file resource
This commit adds the ability to provision files locally. This is useful for cases where TerraForm generates assets such as TLS certificates or templated documents that need to be saved locally. - While output variables can be used to return values to the user, it is not extremly suitable for large content or when many of these are generated, nor is it practical for operators to manually save them on disk. - While `local-exec` could be used with an `echo`, this provider works across platforms and do not require any convoluted escaping.
This commit is contained in:
parent
0bd8c7acb2
commit
bf8d932d23
|
@ -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,
|
||||
})
|
||||
}
|
|
@ -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(),
|
||||
},
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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")
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
|
|
|
@ -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" {}
|
||||
```
|
|
@ -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.
|
|
@ -304,6 +304,10 @@
|
|||
<a href="/docs/providers/librato/index.html">Librato</a>
|
||||
</li>
|
||||
|
||||
<li<%= sidebar_current("docs-providers-local") %>>
|
||||
<a href="/docs/providers/local/index.html">Local</a>
|
||||
</li>
|
||||
|
||||
<li<%= sidebar_current("docs-providers-logentries") %>>
|
||||
<a href="/docs/providers/logentries/index.html">Logentries</a>
|
||||
</li>
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
<% wrap_layout :inner do %>
|
||||
<% content_for :sidebar do %>
|
||||
<ul class="nav docs-sidenav">
|
||||
<li<%= sidebar_current("docs-home") %>>
|
||||
<a href="/docs/providers/index.html">All Providers</a>
|
||||
</li>
|
||||
|
||||
<li<%= sidebar_current("docs-local-index") %>>
|
||||
<a href="/docs/providers/local/index.html">Local Provider</a>
|
||||
</li>
|
||||
|
||||
<li<%= sidebar_current("docs-local-resource") %>>
|
||||
<a href="#">Resources</a>
|
||||
<ul class="nav nav-visible">
|
||||
<li<%= sidebar_current("docs-local-resource-file") %>>
|
||||
<a href="/docs/providers/local/r/file.html">file</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
<% end %>
|
||||
|
||||
<%= yield %>
|
||||
<% end %>
|
Loading…
Reference in New Issue