rewrite file as an internal provisioner
This commit is contained in:
parent
1ec8d921d4
commit
256a7ec95a
|
@ -2,96 +2,131 @@ package file
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
|
"sync"
|
||||||
|
|
||||||
"github.com/hashicorp/terraform/communicator"
|
"github.com/hashicorp/terraform/communicator"
|
||||||
"github.com/hashicorp/terraform/internal/legacy/helper/schema"
|
"github.com/hashicorp/terraform/configs/configschema"
|
||||||
"github.com/hashicorp/terraform/internal/legacy/terraform"
|
"github.com/hashicorp/terraform/provisioners"
|
||||||
"github.com/mitchellh/go-homedir"
|
"github.com/mitchellh/go-homedir"
|
||||||
|
"github.com/zclconf/go-cty/cty"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Provisioner() terraform.ResourceProvisioner {
|
func New() provisioners.Interface {
|
||||||
return &schema.Provisioner{
|
return &provisioner{}
|
||||||
Schema: map[string]*schema.Schema{
|
}
|
||||||
"source": &schema.Schema{
|
|
||||||
Type: schema.TypeString,
|
type provisioner struct {
|
||||||
|
// this stored from the running context, so that Stop() can
|
||||||
|
// cancel the transfer
|
||||||
|
mu sync.Mutex
|
||||||
|
cancel context.CancelFunc
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *provisioner) GetSchema() (resp provisioners.GetSchemaResponse) {
|
||||||
|
schema := &configschema.Block{
|
||||||
|
Attributes: map[string]*configschema.Attribute{
|
||||||
|
"source": {
|
||||||
|
Type: cty.String,
|
||||||
Optional: true,
|
Optional: true,
|
||||||
ConflictsWith: []string{"content"},
|
|
||||||
},
|
},
|
||||||
|
|
||||||
"content": &schema.Schema{
|
"content": {
|
||||||
Type: schema.TypeString,
|
Type: cty.String,
|
||||||
Optional: true,
|
Optional: true,
|
||||||
ConflictsWith: []string{"source"},
|
|
||||||
},
|
},
|
||||||
|
|
||||||
"destination": &schema.Schema{
|
"destination": {
|
||||||
Type: schema.TypeString,
|
Type: cty.String,
|
||||||
Required: true,
|
Required: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
ApplyFunc: applyFn,
|
|
||||||
ValidateFunc: validateFn,
|
|
||||||
}
|
}
|
||||||
|
resp.Provisioner = schema
|
||||||
|
return resp
|
||||||
}
|
}
|
||||||
|
|
||||||
func applyFn(ctx context.Context) error {
|
func (p *provisioner) ValidateProvisionerConfig(req provisioners.ValidateProvisionerConfigRequest) (resp provisioners.ValidateProvisionerConfigResponse) {
|
||||||
connState := ctx.Value(schema.ProvRawStateKey).(*terraform.InstanceState)
|
cfg, err := p.GetSchema().Provisioner.CoerceValue(req.Config)
|
||||||
data := ctx.Value(schema.ProvConfigDataKey).(*schema.ResourceData)
|
|
||||||
|
|
||||||
// Get a new communicator
|
|
||||||
comm, err := communicator.New(connState)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
resp.Diagnostics = resp.Diagnostics.Append(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
source := cfg.GetAttr("source")
|
||||||
|
content := cfg.GetAttr("content")
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case !source.IsNull() && !content.IsNull():
|
||||||
|
resp.Diagnostics = resp.Diagnostics.Append(errors.New("Cannot set both 'source' and 'content'"))
|
||||||
|
return resp
|
||||||
|
case source.IsNull() && content.IsNull():
|
||||||
|
resp.Diagnostics = resp.Diagnostics.Append(errors.New("Must provide one of 'source' or 'content'"))
|
||||||
|
return resp
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *provisioner) ProvisionResource(req provisioners.ProvisionResourceRequest) (resp provisioners.ProvisionResourceResponse) {
|
||||||
|
p.mu.Lock()
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
p.cancel = cancel
|
||||||
|
p.mu.Unlock()
|
||||||
|
|
||||||
|
comm, err := communicator.New(req.Connection)
|
||||||
|
if err != nil {
|
||||||
|
resp.Diagnostics = resp.Diagnostics.Append(err)
|
||||||
|
return resp
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the source
|
// Get the source
|
||||||
src, deleteSource, err := getSrc(data)
|
src, deleteSource, err := getSrc(req.Config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
resp.Diagnostics = resp.Diagnostics.Append(err)
|
||||||
|
return resp
|
||||||
}
|
}
|
||||||
if deleteSource {
|
if deleteSource {
|
||||||
defer os.Remove(src)
|
defer os.Remove(src)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Begin the file copy
|
// Begin the file copy
|
||||||
dst := data.Get("destination").(string)
|
dst := req.Config.GetAttr("destination").AsString()
|
||||||
|
|
||||||
if err := copyFiles(ctx, comm, src, dst); err != nil {
|
if err := copyFiles(ctx, comm, src, dst); err != nil {
|
||||||
return err
|
resp.Diagnostics = resp.Diagnostics.Append(err)
|
||||||
}
|
return resp
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func validateFn(c *terraform.ResourceConfig) (ws []string, es []error) {
|
|
||||||
if !c.IsSet("source") && !c.IsSet("content") {
|
|
||||||
es = append(es, fmt.Errorf("Must provide one of 'source' or 'content'"))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return ws, es
|
return resp
|
||||||
}
|
}
|
||||||
|
|
||||||
// getSrc returns the file to use as source
|
// getSrc returns the file to use as source
|
||||||
func getSrc(data *schema.ResourceData) (string, bool, error) {
|
func getSrc(v cty.Value) (string, bool, error) {
|
||||||
src := data.Get("source").(string)
|
content := v.GetAttr("content")
|
||||||
if content, ok := data.GetOk("content"); ok {
|
src := v.GetAttr("source")
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case !content.IsNull():
|
||||||
file, err := ioutil.TempFile("", "tf-file-content")
|
file, err := ioutil.TempFile("", "tf-file-content")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", true, err
|
return "", true, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err = file.WriteString(content.(string)); err != nil {
|
if _, err = file.WriteString(content.AsString()); err != nil {
|
||||||
return "", true, err
|
return "", true, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return file.Name(), true, nil
|
return file.Name(), true, nil
|
||||||
}
|
|
||||||
|
|
||||||
expansion, err := homedir.Expand(src)
|
case !src.IsNull():
|
||||||
|
expansion, err := homedir.Expand(src.AsString())
|
||||||
return expansion, false, err
|
return expansion, false, err
|
||||||
|
|
||||||
|
default:
|
||||||
|
panic("source and content cannot both be null")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// copyFiles is used to copy the files from a source to a destination
|
// copyFiles is used to copy the files from a source to a destination
|
||||||
|
@ -138,5 +173,17 @@ func copyFiles(ctx context.Context, comm communicator.Communicator, src, dst str
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("Upload failed: %v", err)
|
return fmt.Errorf("Upload failed: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *provisioner) Stop() error {
|
||||||
|
p.mu.Lock()
|
||||||
|
defer p.mu.Unlock()
|
||||||
|
p.cancel()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *provisioner) Close() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
|
@ -3,110 +3,95 @@ package file
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/hashicorp/terraform/configs/hcl2shim"
|
"github.com/hashicorp/terraform/provisioners"
|
||||||
"github.com/hashicorp/terraform/internal/legacy/helper/schema"
|
"github.com/zclconf/go-cty/cty"
|
||||||
"github.com/hashicorp/terraform/internal/legacy/terraform"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestResourceProvisioner_impl(t *testing.T) {
|
|
||||||
var _ terraform.ResourceProvisioner = Provisioner()
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestProvisioner(t *testing.T) {
|
|
||||||
if err := Provisioner().(*schema.Provisioner).InternalValidate(); err != nil {
|
|
||||||
t.Fatalf("err: %s", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestResourceProvider_Validate_good_source(t *testing.T) {
|
func TestResourceProvider_Validate_good_source(t *testing.T) {
|
||||||
c := testConfig(t, map[string]interface{}{
|
v := cty.ObjectVal(map[string]cty.Value{
|
||||||
"source": "/tmp/foo",
|
"source": cty.StringVal("/tmp/foo"),
|
||||||
"destination": "/tmp/bar",
|
"destination": cty.StringVal("/tmp/bar"),
|
||||||
})
|
})
|
||||||
|
|
||||||
warn, errs := Provisioner().Validate(c)
|
resp := New().ValidateProvisionerConfig(provisioners.ValidateProvisionerConfigRequest{
|
||||||
if len(warn) > 0 {
|
Config: v,
|
||||||
t.Fatalf("Warnings: %v", warn)
|
})
|
||||||
}
|
|
||||||
if len(errs) > 0 {
|
if len(resp.Diagnostics) > 0 {
|
||||||
t.Fatalf("Errors: %v", errs)
|
t.Fatal(resp.Diagnostics.ErrWithWarnings())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestResourceProvider_Validate_good_content(t *testing.T) {
|
func TestResourceProvider_Validate_good_content(t *testing.T) {
|
||||||
c := testConfig(t, map[string]interface{}{
|
v := cty.ObjectVal(map[string]cty.Value{
|
||||||
"content": "value to copy",
|
"content": cty.StringVal("value to copy"),
|
||||||
"destination": "/tmp/bar",
|
"destination": cty.StringVal("/tmp/bar"),
|
||||||
})
|
})
|
||||||
|
|
||||||
warn, errs := Provisioner().Validate(c)
|
resp := New().ValidateProvisionerConfig(provisioners.ValidateProvisionerConfigRequest{
|
||||||
if len(warn) > 0 {
|
Config: v,
|
||||||
t.Fatalf("Warnings: %v", warn)
|
})
|
||||||
}
|
|
||||||
if len(errs) > 0 {
|
if len(resp.Diagnostics) > 0 {
|
||||||
t.Fatalf("Errors: %v", errs)
|
t.Fatal(resp.Diagnostics.ErrWithWarnings())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestResourceProvider_Validate_good_unknown_variable_value(t *testing.T) {
|
func TestResourceProvider_Validate_good_unknown_variable_value(t *testing.T) {
|
||||||
c := testConfig(t, map[string]interface{}{
|
v := cty.ObjectVal(map[string]cty.Value{
|
||||||
"content": hcl2shim.UnknownVariableValue,
|
"content": cty.UnknownVal(cty.String),
|
||||||
"destination": "/tmp/bar",
|
"destination": cty.StringVal("/tmp/bar"),
|
||||||
})
|
})
|
||||||
|
|
||||||
warn, errs := Provisioner().Validate(c)
|
resp := New().ValidateProvisionerConfig(provisioners.ValidateProvisionerConfigRequest{
|
||||||
if len(warn) > 0 {
|
Config: v,
|
||||||
t.Fatalf("Warnings: %v", warn)
|
})
|
||||||
}
|
|
||||||
if len(errs) > 0 {
|
if len(resp.Diagnostics) > 0 {
|
||||||
t.Fatalf("Errors: %v", errs)
|
t.Fatal(resp.Diagnostics.ErrWithWarnings())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestResourceProvider_Validate_bad_not_destination(t *testing.T) {
|
func TestResourceProvider_Validate_bad_not_destination(t *testing.T) {
|
||||||
c := testConfig(t, map[string]interface{}{
|
v := cty.ObjectVal(map[string]cty.Value{
|
||||||
"source": "nope",
|
"source": cty.StringVal("nope"),
|
||||||
})
|
})
|
||||||
|
|
||||||
warn, errs := Provisioner().Validate(c)
|
resp := New().ValidateProvisionerConfig(provisioners.ValidateProvisionerConfigRequest{
|
||||||
if len(warn) > 0 {
|
Config: v,
|
||||||
t.Fatalf("Warnings: %v", warn)
|
})
|
||||||
}
|
|
||||||
if len(errs) == 0 {
|
if !resp.Diagnostics.HasErrors() {
|
||||||
t.Fatalf("Should have errors")
|
t.Fatal("Should have errors")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestResourceProvider_Validate_bad_no_source(t *testing.T) {
|
func TestResourceProvider_Validate_bad_no_source(t *testing.T) {
|
||||||
c := testConfig(t, map[string]interface{}{
|
v := cty.ObjectVal(map[string]cty.Value{
|
||||||
"destination": "/tmp/bar",
|
"destination": cty.StringVal("/tmp/bar"),
|
||||||
})
|
})
|
||||||
|
|
||||||
warn, errs := Provisioner().Validate(c)
|
resp := New().ValidateProvisionerConfig(provisioners.ValidateProvisionerConfigRequest{
|
||||||
if len(warn) > 0 {
|
Config: v,
|
||||||
t.Fatalf("Warnings: %v", warn)
|
})
|
||||||
}
|
|
||||||
if len(errs) == 0 {
|
if !resp.Diagnostics.HasErrors() {
|
||||||
t.Fatalf("Should have errors")
|
t.Fatal("Should have errors")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestResourceProvider_Validate_bad_to_many_src(t *testing.T) {
|
func TestResourceProvider_Validate_bad_to_many_src(t *testing.T) {
|
||||||
c := testConfig(t, map[string]interface{}{
|
v := cty.ObjectVal(map[string]cty.Value{
|
||||||
"source": "nope",
|
"source": cty.StringVal("nope"),
|
||||||
"content": "value to copy",
|
"content": cty.StringVal("vlue to copy"),
|
||||||
"destination": "/tmp/bar",
|
"destination": cty.StringVal("/tmp/bar"),
|
||||||
})
|
})
|
||||||
|
|
||||||
warn, errs := Provisioner().Validate(c)
|
resp := New().ValidateProvisionerConfig(provisioners.ValidateProvisionerConfigRequest{
|
||||||
if len(warn) > 0 {
|
Config: v,
|
||||||
t.Fatalf("Warnings: %v", warn)
|
})
|
||||||
}
|
|
||||||
if len(errs) == 0 {
|
|
||||||
t.Fatalf("Should have errors")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func testConfig(t *testing.T, c map[string]interface{}) *terraform.ResourceConfig {
|
if !resp.Diagnostics.HasErrors() {
|
||||||
return terraform.NewResourceConfigRaw(c)
|
t.Fatal("Should have errors")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue