Merge pull request #28753 from hashicorp/jbardin/provisioner-diags
return diagnostics from provisioners
This commit is contained in:
commit
a4ed1405f5
|
@ -10,6 +10,7 @@ import (
|
||||||
"github.com/hashicorp/terraform/internal/communicator"
|
"github.com/hashicorp/terraform/internal/communicator"
|
||||||
"github.com/hashicorp/terraform/internal/configs/configschema"
|
"github.com/hashicorp/terraform/internal/configs/configschema"
|
||||||
"github.com/hashicorp/terraform/internal/provisioners"
|
"github.com/hashicorp/terraform/internal/provisioners"
|
||||||
|
"github.com/hashicorp/terraform/internal/tfdiags"
|
||||||
"github.com/mitchellh/go-homedir"
|
"github.com/mitchellh/go-homedir"
|
||||||
"github.com/zclconf/go-cty/cty"
|
"github.com/zclconf/go-cty/cty"
|
||||||
)
|
)
|
||||||
|
@ -75,20 +76,32 @@ func (p *provisioner) ValidateProvisionerConfig(req provisioners.ValidateProvisi
|
||||||
|
|
||||||
func (p *provisioner) ProvisionResource(req provisioners.ProvisionResourceRequest) (resp provisioners.ProvisionResourceResponse) {
|
func (p *provisioner) ProvisionResource(req provisioners.ProvisionResourceRequest) (resp provisioners.ProvisionResourceResponse) {
|
||||||
if req.Connection.IsNull() {
|
if req.Connection.IsNull() {
|
||||||
resp.Diagnostics = resp.Diagnostics.Append(errors.New("missing connection configuration for provisioner"))
|
resp.Diagnostics = resp.Diagnostics.Append(tfdiags.WholeContainingBody(
|
||||||
|
tfdiags.Error,
|
||||||
|
"file provisioner error",
|
||||||
|
"Missing connection configuration for provisioner.",
|
||||||
|
))
|
||||||
return resp
|
return resp
|
||||||
}
|
}
|
||||||
|
|
||||||
comm, err := communicator.New(req.Connection)
|
comm, err := communicator.New(req.Connection)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
resp.Diagnostics = resp.Diagnostics.Append(err)
|
resp.Diagnostics = resp.Diagnostics.Append(tfdiags.WholeContainingBody(
|
||||||
|
tfdiags.Error,
|
||||||
|
"file provisioner error",
|
||||||
|
err.Error(),
|
||||||
|
))
|
||||||
return resp
|
return resp
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the source
|
// Get the source
|
||||||
src, deleteSource, err := getSrc(req.Config)
|
src, deleteSource, err := getSrc(req.Config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
resp.Diagnostics = resp.Diagnostics.Append(err)
|
resp.Diagnostics = resp.Diagnostics.Append(tfdiags.WholeContainingBody(
|
||||||
|
tfdiags.Error,
|
||||||
|
"file provisioner error",
|
||||||
|
err.Error(),
|
||||||
|
))
|
||||||
return resp
|
return resp
|
||||||
}
|
}
|
||||||
if deleteSource {
|
if deleteSource {
|
||||||
|
@ -98,7 +111,11 @@ func (p *provisioner) ProvisionResource(req provisioners.ProvisionResourceReques
|
||||||
// Begin the file copy
|
// Begin the file copy
|
||||||
dst := req.Config.GetAttr("destination").AsString()
|
dst := req.Config.GetAttr("destination").AsString()
|
||||||
if err := copyFiles(p.ctx, comm, src, dst); err != nil {
|
if err := copyFiles(p.ctx, comm, src, dst); err != nil {
|
||||||
resp.Diagnostics = resp.Diagnostics.Append(err)
|
resp.Diagnostics = resp.Diagnostics.Append(tfdiags.WholeContainingBody(
|
||||||
|
tfdiags.Error,
|
||||||
|
"file provisioner error",
|
||||||
|
err.Error(),
|
||||||
|
))
|
||||||
return resp
|
return resp
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -112,7 +112,7 @@ func TestResourceProvisioner_connectionRequired(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
got := resp.Diagnostics.Err().Error()
|
got := resp.Diagnostics.Err().Error()
|
||||||
if !strings.Contains(got, "missing connection") {
|
if !strings.Contains(got, "Missing connection") {
|
||||||
t.Fatalf("expected 'missing connection' error: got %q", got)
|
t.Fatalf("expected 'Missing connection' error: got %q", got)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,7 @@ import (
|
||||||
"github.com/armon/circbuf"
|
"github.com/armon/circbuf"
|
||||||
"github.com/hashicorp/terraform/internal/configs/configschema"
|
"github.com/hashicorp/terraform/internal/configs/configschema"
|
||||||
"github.com/hashicorp/terraform/internal/provisioners"
|
"github.com/hashicorp/terraform/internal/provisioners"
|
||||||
|
"github.com/hashicorp/terraform/internal/tfdiags"
|
||||||
"github.com/mitchellh/go-linereader"
|
"github.com/mitchellh/go-linereader"
|
||||||
"github.com/zclconf/go-cty/cty"
|
"github.com/zclconf/go-cty/cty"
|
||||||
)
|
)
|
||||||
|
@ -65,7 +66,11 @@ func (p *provisioner) GetSchema() (resp provisioners.GetSchemaResponse) {
|
||||||
|
|
||||||
func (p *provisioner) ValidateProvisionerConfig(req provisioners.ValidateProvisionerConfigRequest) (resp provisioners.ValidateProvisionerConfigResponse) {
|
func (p *provisioner) ValidateProvisionerConfig(req provisioners.ValidateProvisionerConfigRequest) (resp provisioners.ValidateProvisionerConfigResponse) {
|
||||||
if _, err := p.GetSchema().Provisioner.CoerceValue(req.Config); err != nil {
|
if _, err := p.GetSchema().Provisioner.CoerceValue(req.Config); err != nil {
|
||||||
resp.Diagnostics = resp.Diagnostics.Append(err)
|
resp.Diagnostics = resp.Diagnostics.Append(tfdiags.WholeContainingBody(
|
||||||
|
tfdiags.Error,
|
||||||
|
"Invalid local-exec provisioner configuration",
|
||||||
|
err.Error(),
|
||||||
|
))
|
||||||
}
|
}
|
||||||
return resp
|
return resp
|
||||||
}
|
}
|
||||||
|
@ -73,7 +78,11 @@ func (p *provisioner) ValidateProvisionerConfig(req provisioners.ValidateProvisi
|
||||||
func (p *provisioner) ProvisionResource(req provisioners.ProvisionResourceRequest) (resp provisioners.ProvisionResourceResponse) {
|
func (p *provisioner) ProvisionResource(req provisioners.ProvisionResourceRequest) (resp provisioners.ProvisionResourceResponse) {
|
||||||
command := req.Config.GetAttr("command").AsString()
|
command := req.Config.GetAttr("command").AsString()
|
||||||
if command == "" {
|
if command == "" {
|
||||||
resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("local-exec provisioner command must be a non-empty string"))
|
resp.Diagnostics = resp.Diagnostics.Append(tfdiags.WholeContainingBody(
|
||||||
|
tfdiags.Error,
|
||||||
|
"Invalid local-exec provisioner command",
|
||||||
|
"The command must be a non-empty string.",
|
||||||
|
))
|
||||||
return resp
|
return resp
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -120,7 +129,11 @@ func (p *provisioner) ProvisionResource(req provisioners.ProvisionResourceReques
|
||||||
// See golang.org/issue/18874
|
// See golang.org/issue/18874
|
||||||
pr, pw, err := os.Pipe()
|
pr, pw, err := os.Pipe()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("failed to initialize pipe for output: %s", err))
|
resp.Diagnostics = resp.Diagnostics.Append(tfdiags.WholeContainingBody(
|
||||||
|
tfdiags.Error,
|
||||||
|
"local-exec provisioner error",
|
||||||
|
fmt.Sprintf("Failed to initialize pipe for output: %s", err),
|
||||||
|
))
|
||||||
return resp
|
return resp
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -171,8 +184,11 @@ func (p *provisioner) ProvisionResource(req provisioners.ProvisionResourceReques
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("Error running command '%s': %v. Output: %s",
|
resp.Diagnostics = resp.Diagnostics.Append(tfdiags.WholeContainingBody(
|
||||||
command, err, output.Bytes()))
|
tfdiags.Error,
|
||||||
|
"local-exec provisioner error",
|
||||||
|
fmt.Sprintf("Error running command '%s': %v. Output: %s", command, err, output.Bytes()),
|
||||||
|
))
|
||||||
return resp
|
return resp
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -15,6 +15,7 @@ import (
|
||||||
"github.com/hashicorp/terraform/internal/communicator/remote"
|
"github.com/hashicorp/terraform/internal/communicator/remote"
|
||||||
"github.com/hashicorp/terraform/internal/configs/configschema"
|
"github.com/hashicorp/terraform/internal/configs/configschema"
|
||||||
"github.com/hashicorp/terraform/internal/provisioners"
|
"github.com/hashicorp/terraform/internal/provisioners"
|
||||||
|
"github.com/hashicorp/terraform/internal/tfdiags"
|
||||||
"github.com/mitchellh/go-linereader"
|
"github.com/mitchellh/go-linereader"
|
||||||
"github.com/zclconf/go-cty/cty"
|
"github.com/zclconf/go-cty/cty"
|
||||||
)
|
)
|
||||||
|
@ -59,7 +60,11 @@ func (p *provisioner) GetSchema() (resp provisioners.GetSchemaResponse) {
|
||||||
func (p *provisioner) ValidateProvisionerConfig(req provisioners.ValidateProvisionerConfigRequest) (resp provisioners.ValidateProvisionerConfigResponse) {
|
func (p *provisioner) ValidateProvisionerConfig(req provisioners.ValidateProvisionerConfigRequest) (resp provisioners.ValidateProvisionerConfigResponse) {
|
||||||
cfg, err := p.GetSchema().Provisioner.CoerceValue(req.Config)
|
cfg, err := p.GetSchema().Provisioner.CoerceValue(req.Config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
resp.Diagnostics = resp.Diagnostics.Append(err)
|
resp.Diagnostics = resp.Diagnostics.Append(tfdiags.WholeContainingBody(
|
||||||
|
tfdiags.Error,
|
||||||
|
"Invalid remote-exec provisioner configuration",
|
||||||
|
err.Error(),
|
||||||
|
))
|
||||||
return resp
|
return resp
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -78,28 +83,43 @@ func (p *provisioner) ValidateProvisionerConfig(req provisioners.ValidateProvisi
|
||||||
set++
|
set++
|
||||||
}
|
}
|
||||||
if set != 1 {
|
if set != 1 {
|
||||||
resp.Diagnostics = resp.Diagnostics.Append(errors.New(
|
resp.Diagnostics = resp.Diagnostics.Append(tfdiags.WholeContainingBody(
|
||||||
`only one of "inline", "script", or "scripts" must be set`))
|
tfdiags.Error,
|
||||||
|
"Invalid remote-exec provisioner configuration",
|
||||||
|
`Only one of "inline", "script", or "scripts" must be set`,
|
||||||
|
))
|
||||||
}
|
}
|
||||||
return resp
|
return resp
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *provisioner) ProvisionResource(req provisioners.ProvisionResourceRequest) (resp provisioners.ProvisionResourceResponse) {
|
func (p *provisioner) ProvisionResource(req provisioners.ProvisionResourceRequest) (resp provisioners.ProvisionResourceResponse) {
|
||||||
if req.Connection.IsNull() {
|
if req.Connection.IsNull() {
|
||||||
resp.Diagnostics = resp.Diagnostics.Append(errors.New("missing connection configuration for provisioner"))
|
resp.Diagnostics = resp.Diagnostics.Append(tfdiags.WholeContainingBody(
|
||||||
|
tfdiags.Error,
|
||||||
|
"remote-exec provisioner error",
|
||||||
|
"Missing connection configuration for provisioner.",
|
||||||
|
))
|
||||||
return resp
|
return resp
|
||||||
}
|
}
|
||||||
|
|
||||||
comm, err := communicator.New(req.Connection)
|
comm, err := communicator.New(req.Connection)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
resp.Diagnostics = resp.Diagnostics.Append(err)
|
resp.Diagnostics = resp.Diagnostics.Append(tfdiags.WholeContainingBody(
|
||||||
|
tfdiags.Error,
|
||||||
|
"remote-exec provisioner error",
|
||||||
|
err.Error(),
|
||||||
|
))
|
||||||
return resp
|
return resp
|
||||||
}
|
}
|
||||||
|
|
||||||
// Collect the scripts
|
// Collect the scripts
|
||||||
scripts, err := collectScripts(req.Config)
|
scripts, err := collectScripts(req.Config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
resp.Diagnostics = resp.Diagnostics.Append(err)
|
resp.Diagnostics = resp.Diagnostics.Append(tfdiags.WholeContainingBody(
|
||||||
|
tfdiags.Error,
|
||||||
|
"remote-exec provisioner error",
|
||||||
|
err.Error(),
|
||||||
|
))
|
||||||
return resp
|
return resp
|
||||||
}
|
}
|
||||||
for _, s := range scripts {
|
for _, s := range scripts {
|
||||||
|
@ -108,7 +128,11 @@ func (p *provisioner) ProvisionResource(req provisioners.ProvisionResourceReques
|
||||||
|
|
||||||
// Copy and execute each script
|
// Copy and execute each script
|
||||||
if err := runScripts(p.ctx, req.UIOutput, comm, scripts); err != nil {
|
if err := runScripts(p.ctx, req.UIOutput, comm, scripts); err != nil {
|
||||||
resp.Diagnostics = resp.Diagnostics.Append(err)
|
resp.Diagnostics = resp.Diagnostics.Append(tfdiags.WholeContainingBody(
|
||||||
|
tfdiags.Error,
|
||||||
|
"remote-exec provisioner error",
|
||||||
|
err.Error(),
|
||||||
|
))
|
||||||
return resp
|
return resp
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -271,8 +271,8 @@ func TestResourceProvisioner_connectionRequired(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
got := resp.Diagnostics.Err().Error()
|
got := resp.Diagnostics.Err().Error()
|
||||||
if !strings.Contains(got, "missing connection") {
|
if !strings.Contains(got, "Missing connection") {
|
||||||
t.Fatalf("expected 'missing connection' error: got %q", got)
|
t.Fatalf("expected 'Missing connection' error: got %q", got)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1701,9 +1701,8 @@ func (n *NodeAbstractResourceInstance) evalApplyProvisioners(ctx EvalContext, st
|
||||||
|
|
||||||
// If there are no errors, then we append it to our output error
|
// If there are no errors, then we append it to our output error
|
||||||
// if we have one, otherwise we just output it.
|
// if we have one, otherwise we just output it.
|
||||||
err := n.applyProvisioners(ctx, state, when, provs)
|
diags = diags.Append(n.applyProvisioners(ctx, state, when, provs))
|
||||||
if err != nil {
|
if diags.HasErrors() {
|
||||||
diags = diags.Append(err)
|
|
||||||
log.Printf("[TRACE] evalApplyProvisioners: %s provisioning failed, but we will continue anyway at the caller's request", n.Addr)
|
log.Printf("[TRACE] evalApplyProvisioners: %s provisioning failed, but we will continue anyway at the caller's request", n.Addr)
|
||||||
return diags
|
return diags
|
||||||
}
|
}
|
||||||
|
@ -1737,7 +1736,7 @@ func filterProvisioners(config *configs.Resource, when configs.ProvisionerWhen)
|
||||||
}
|
}
|
||||||
|
|
||||||
// applyProvisioners executes the provisioners for a resource.
|
// applyProvisioners executes the provisioners for a resource.
|
||||||
func (n *NodeAbstractResourceInstance) applyProvisioners(ctx EvalContext, state *states.ResourceInstanceObject, when configs.ProvisionerWhen, provs []*configs.Provisioner) error {
|
func (n *NodeAbstractResourceInstance) applyProvisioners(ctx EvalContext, state *states.ResourceInstanceObject, when configs.ProvisionerWhen, provs []*configs.Provisioner) tfdiags.Diagnostics {
|
||||||
var diags tfdiags.Diagnostics
|
var diags tfdiags.Diagnostics
|
||||||
|
|
||||||
// this self is only used for destroy provisioner evaluation, and must
|
// this self is only used for destroy provisioner evaluation, and must
|
||||||
|
@ -1766,8 +1765,7 @@ func (n *NodeAbstractResourceInstance) applyProvisioners(ctx EvalContext, state
|
||||||
// Get the provisioner
|
// Get the provisioner
|
||||||
provisioner, err := ctx.Provisioner(prov.Type)
|
provisioner, err := ctx.Provisioner(prov.Type)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
diags = diags.Append(err)
|
return diags.Append(err)
|
||||||
return diags.Err()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
schema := ctx.ProvisionerSchema(prov.Type)
|
schema := ctx.ProvisionerSchema(prov.Type)
|
||||||
|
@ -1775,7 +1773,7 @@ func (n *NodeAbstractResourceInstance) applyProvisioners(ctx EvalContext, state
|
||||||
config, configDiags := evalScope(ctx, prov.Config, self, schema)
|
config, configDiags := evalScope(ctx, prov.Config, self, schema)
|
||||||
diags = diags.Append(configDiags)
|
diags = diags.Append(configDiags)
|
||||||
if diags.HasErrors() {
|
if diags.HasErrors() {
|
||||||
return diags.Err()
|
return diags
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the provisioner block contains a connection block of its own then
|
// If the provisioner block contains a connection block of its own then
|
||||||
|
@ -1807,7 +1805,7 @@ func (n *NodeAbstractResourceInstance) applyProvisioners(ctx EvalContext, state
|
||||||
connInfo, connInfoDiags = evalScope(ctx, connBody, self, connectionBlockSupersetSchema)
|
connInfo, connInfoDiags = evalScope(ctx, connBody, self, connectionBlockSupersetSchema)
|
||||||
diags = diags.Append(connInfoDiags)
|
diags = diags.Append(connInfoDiags)
|
||||||
if diags.HasErrors() {
|
if diags.HasErrors() {
|
||||||
return diags.Err()
|
return diags
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1817,7 +1815,7 @@ func (n *NodeAbstractResourceInstance) applyProvisioners(ctx EvalContext, state
|
||||||
return h.PreProvisionInstanceStep(n.Addr, prov.Type)
|
return h.PreProvisionInstanceStep(n.Addr, prov.Type)
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return diags.Append(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1874,27 +1872,17 @@ func (n *NodeAbstractResourceInstance) applyProvisioners(ctx EvalContext, state
|
||||||
diags = diags.Append(applyDiags)
|
diags = diags.Append(applyDiags)
|
||||||
if applyDiags.HasErrors() {
|
if applyDiags.HasErrors() {
|
||||||
log.Printf("[WARN] Errors while provisioning %s with %q, so aborting", n.Addr, prov.Type)
|
log.Printf("[WARN] Errors while provisioning %s with %q, so aborting", n.Addr, prov.Type)
|
||||||
return diags.Err()
|
return diags
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Deal with the hook
|
// Deal with the hook
|
||||||
if hookErr != nil {
|
if hookErr != nil {
|
||||||
return hookErr
|
return diags.Append(hookErr)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// we have to drop warning-only diagnostics for now
|
return diags
|
||||||
if diags.HasErrors() {
|
|
||||||
return diags.ErrWithWarnings()
|
|
||||||
}
|
|
||||||
|
|
||||||
// log any warnings since we can't return them
|
|
||||||
if e := diags.ErrWithWarnings(); e != nil {
|
|
||||||
log.Printf("[WARN] applyProvisioners %s: %v", n.Addr, e)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *NodeAbstractResourceInstance) evalProvisionerConfig(ctx EvalContext, body hcl.Body, self cty.Value, schema *configschema.Block) (cty.Value, tfdiags.Diagnostics) {
|
func (n *NodeAbstractResourceInstance) evalProvisionerConfig(ctx EvalContext, body hcl.Body, self cty.Value, schema *configschema.Block) (cty.Value, tfdiags.Diagnostics) {
|
||||||
|
|
Loading…
Reference in New Issue