mirror of
https://github.com/opentofu/opentofu.git
synced 2025-02-25 18:45:20 -06:00
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
12
builtin/bins/provider-localfile/main.go
Normal file
12
builtin/bins/provider-localfile/main.go
Normal file
@ -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,
|
||||
})
|
||||
}
|
15
builtin/providers/local/provider.go
Normal file
15
builtin/providers/local/provider.go
Normal file
@ -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(),
|
||||
},
|
||||
}
|
||||
}
|
18
builtin/providers/local/provider_test.go
Normal file
18
builtin/providers/local/provider_test.go
Normal file
@ -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)
|
||||
}
|
||||
}
|
84
builtin/providers/local/resource_local_file.go
Normal file
84
builtin/providers/local/resource_local_file.go
Normal file
@ -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
|
||||
}
|
56
builtin/providers/local/resource_local_file_test.go
Normal file
56
builtin/providers/local/resource_local_file_test.go
Normal file
@ -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,
|
||||
|
19
website/source/docs/providers/local/index.html.markdown
Normal file
19
website/source/docs/providers/local/index.html.markdown
Normal file
@ -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" {}
|
||||
```
|
30
website/source/docs/providers/local/r/file.html.md
Normal file
30
website/source/docs/providers/local/r/file.html.md
Normal file
@ -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>
|
||||
|
24
website/source/layouts/local.erb
Normal file
24
website/source/layouts/local.erb
Normal file
@ -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
Block a user