From 1d915adad105e7e2e16de92d784ae5c878d34cf5 Mon Sep 17 00:00:00 2001 From: Adrien Delorme Date: Tue, 17 Aug 2021 11:24:29 +0200 Subject: [PATCH] file provisioner: add possibility to set content + tests (#11209) --- provisioner/file/provisioner.go | 30 +++++++++++++- provisioner/file/provisioner.hcl2spec.go | 2 + provisioner/file/provisioner_test.go | 39 +++++++++++++++++++ .../provisioner/file/Config-required.mdx | 7 ++++ 4 files changed, 76 insertions(+), 2 deletions(-) diff --git a/provisioner/file/provisioner.go b/provisioner/file/provisioner.go index 6054ddb2c..7a8915c2d 100644 --- a/provisioner/file/provisioner.go +++ b/provisioner/file/provisioner.go @@ -17,10 +17,18 @@ import ( packersdk "github.com/hashicorp/packer-plugin-sdk/packer" "github.com/hashicorp/packer-plugin-sdk/template/config" "github.com/hashicorp/packer-plugin-sdk/template/interpolate" + "github.com/hashicorp/packer-plugin-sdk/tmp" ) type Config struct { common.PackerConfig `mapstructure:",squash"` + // This is the content to copy to `destination`. If destination is a file, + // content will be written to that file, in case of a directory a file named + // `pkr-file-content` is created. It's recommended to use a file as the + // destination. A template_file might be referenced in here, or any + // interpolation syntax. This attribute cannot be specified with source or + // sources. + Content string `mapstructure:"content" required:"true"` // The path to a local file or directory to upload to the // machine. The path can be absolute or relative. If it is relative, it is // relative to the working directory when Packer is executed. If this is a @@ -102,9 +110,14 @@ func (p *Provisioner) Prepare(raws ...interface{}) error { } } - if len(p.config.Sources) < 1 { + if len(p.config.Sources) < 1 && p.config.Content == "" { errs = packersdk.MultiErrorAppend(errs, - errors.New("Source must be specified.")) + errors.New("source, sources or content must be specified.")) + } + + if len(p.config.Sources) > 0 && p.config.Content != "" { + errs = packersdk.MultiErrorAppend(errs, + errors.New("source(s) conflicts with content.")) } if p.config.Destination == "" { @@ -125,6 +138,19 @@ func (p *Provisioner) Provision(ctx context.Context, ui packersdk.Ui, comm packe } p.config.ctx.Data = generatedData + if p.config.Content != "" { + file, err := tmp.File("pkr-file-content") + if err != nil { + return err + } + defer file.Close() + if _, err := file.WriteString(p.config.Content); err != nil { + return err + } + p.config.Content = "" + p.config.Sources = append(p.config.Sources, file.Name()) + } + if p.config.Direction == "download" { return p.ProvisionDownload(ui, comm) } else { diff --git a/provisioner/file/provisioner.hcl2spec.go b/provisioner/file/provisioner.hcl2spec.go index 428acfea3..3a90f0f3f 100644 --- a/provisioner/file/provisioner.hcl2spec.go +++ b/provisioner/file/provisioner.hcl2spec.go @@ -18,6 +18,7 @@ type FlatConfig struct { PackerOnError *string `mapstructure:"packer_on_error" cty:"packer_on_error" hcl:"packer_on_error"` PackerUserVars map[string]string `mapstructure:"packer_user_variables" cty:"packer_user_variables" hcl:"packer_user_variables"` PackerSensitiveVars []string `mapstructure:"packer_sensitive_variables" cty:"packer_sensitive_variables" hcl:"packer_sensitive_variables"` + Content *string `mapstructure:"content" required:"true" cty:"content" hcl:"content"` Source *string `mapstructure:"source" required:"true" cty:"source" hcl:"source"` Sources []string `mapstructure:"sources" required:"false" cty:"sources" hcl:"sources"` Destination *string `mapstructure:"destination" required:"true" cty:"destination" hcl:"destination"` @@ -45,6 +46,7 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec { "packer_on_error": &hcldec.AttrSpec{Name: "packer_on_error", Type: cty.String, Required: false}, "packer_user_variables": &hcldec.AttrSpec{Name: "packer_user_variables", Type: cty.Map(cty.String), Required: false}, "packer_sensitive_variables": &hcldec.AttrSpec{Name: "packer_sensitive_variables", Type: cty.List(cty.String), Required: false}, + "content": &hcldec.AttrSpec{Name: "content", Type: cty.String, Required: false}, "source": &hcldec.AttrSpec{Name: "source", Type: cty.String, Required: false}, "sources": &hcldec.AttrSpec{Name: "sources", Type: cty.List(cty.String), Required: false}, "destination": &hcldec.AttrSpec{Name: "destination", Type: cty.String, Required: false}, diff --git a/provisioner/file/provisioner_test.go b/provisioner/file/provisioner_test.go index 0df7680a6..428443bdc 100644 --- a/provisioner/file/provisioner_test.go +++ b/provisioner/file/provisioner_test.go @@ -151,6 +151,45 @@ func TestProvisionerProvision_SendsFile(t *testing.T) { } } +func TestProvisionerProvision_SendsContent(t *testing.T) { + var p Provisioner + + dst := "something.txt" + content := "hello" + config := map[string]interface{}{ + "content": content, + "destination": dst, + } + + if err := p.Prepare(config); err != nil { + t.Fatalf("err: %s", err) + } + + b := bytes.NewBuffer(nil) + ui := &packersdk.BasicUi{ + Writer: b, + PB: &packersdk.NoopProgressTracker{}, + } + comm := &packersdk.MockCommunicator{} + err := p.Provision(context.Background(), ui, comm, make(map[string]interface{})) + if err != nil { + t.Fatalf("should successfully provision: %s", err) + } + + if !strings.Contains(b.String(), "something") { + t.Fatalf("should print destination filename") + } + + if comm.UploadPath != dst { + t.Fatalf("should upload to configured destination") + } + + if comm.UploadData != content { + t.Fatalf("should upload with source file's data") + } + +} + func TestProvisionerProvision_SendsFileMultipleFiles(t *testing.T) { var p Provisioner tf1, err := ioutil.TempFile("", "packer") diff --git a/website/content/partials/provisioner/file/Config-required.mdx b/website/content/partials/provisioner/file/Config-required.mdx index 7f85b07be..ae787ef35 100644 --- a/website/content/partials/provisioner/file/Config-required.mdx +++ b/website/content/partials/provisioner/file/Config-required.mdx @@ -1,5 +1,12 @@ +- `content` (string) - This is the content to copy to `destination`. If destination is a file, + content will be written to that file, in case of a directory a file named + `pkr-file-content` is created. It's recommended to use a file as the + destination. A template_file might be referenced in here, or any + interpolation syntax. This attribute cannot be specified with source or + sources. + - `source` (string) - The path to a local file or directory to upload to the machine. The path can be absolute or relative. If it is relative, it is relative to the working directory when Packer is executed. If this is a