Merge pull request #29235 from magodo/terraform_add_output_append
`terraform add`: `-out` option append to existing config & optionally check resource existance
This commit is contained in:
commit
1f57b7a8bd
|
@ -3,6 +3,7 @@ package command
|
|||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/hcl/v2"
|
||||
|
@ -33,6 +34,43 @@ func (c *AddCommand) Run(rawArgs []string) int {
|
|||
return 1
|
||||
}
|
||||
|
||||
// In case the output configuration path is specified, we should ensure the
|
||||
// target resource address doesn't exist in the module tree indicated by
|
||||
// the existing configuration files.
|
||||
if args.OutPath != "" {
|
||||
// Ensure the directory to the path exists and is accessible.
|
||||
outDir := filepath.Dir(args.OutPath)
|
||||
if _, err := os.Stat(outDir); os.IsNotExist(err) {
|
||||
diags = diags.Append(tfdiags.Sourceless(
|
||||
tfdiags.Error,
|
||||
"The out path doesn't exist or is not accessible",
|
||||
err.Error(),
|
||||
))
|
||||
view.Diagnostics(diags)
|
||||
return 1
|
||||
}
|
||||
|
||||
config, loadDiags := c.loadConfig(outDir)
|
||||
diags = diags.Append(loadDiags)
|
||||
if diags.HasErrors() {
|
||||
view.Diagnostics(diags)
|
||||
return 1
|
||||
}
|
||||
|
||||
if config != nil && config.Module != nil {
|
||||
if rs, ok := config.Module.ManagedResources[args.Addr.ContainingResource().Config().String()]; ok {
|
||||
diags = diags.Append(&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Resource already in configuration",
|
||||
Detail: fmt.Sprintf("The resource %s is already in this configuration at %s. Resource names must be unique per type in each module.", args.Addr, rs.DeclRange),
|
||||
Subject: &rs.DeclRange,
|
||||
})
|
||||
c.View.Diagnostics(diags)
|
||||
return 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check for user-supplied plugin path
|
||||
var err error
|
||||
if c.pluginPath, err = c.loadPluginPath(); err != nil {
|
||||
|
@ -127,21 +165,6 @@ func (c *AddCommand) Run(rawArgs []string) int {
|
|||
}
|
||||
}
|
||||
|
||||
if module == nil {
|
||||
// It's fine if the module doesn't actually exist; we don't need to check if the resource exists.
|
||||
} else {
|
||||
if rs, ok := module.ManagedResources[args.Addr.ContainingResource().Config().String()]; ok {
|
||||
diags = diags.Append(&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Resource already in configuration",
|
||||
Detail: fmt.Sprintf("The resource %s is already in this configuration at %s. Resource names must be unique per type in each module.", args.Addr, rs.DeclRange),
|
||||
Subject: &rs.DeclRange,
|
||||
})
|
||||
c.View.Diagnostics(diags)
|
||||
return 1
|
||||
}
|
||||
}
|
||||
|
||||
// Get the schemas from the context
|
||||
schemas, moreDiags := lr.Core.Schemas(lr.Config, lr.InputState)
|
||||
diags = diags.Append(moreDiags)
|
||||
|
|
|
@ -118,6 +118,45 @@ resource "test_instance" "new" {
|
|||
}
|
||||
})
|
||||
|
||||
t.Run("basic to existing file", func(t *testing.T) {
|
||||
view, done := testView(t)
|
||||
c := &AddCommand{
|
||||
Meta: Meta{
|
||||
testingOverrides: overrides,
|
||||
View: view,
|
||||
},
|
||||
}
|
||||
outPath := "add.tf"
|
||||
args := []string{fmt.Sprintf("-out=%s", outPath), "test_instance.new"}
|
||||
c.Run(args)
|
||||
args = []string{fmt.Sprintf("-out=%s", outPath), "test_instance.new2"}
|
||||
code := c.Run(args)
|
||||
output := done(t)
|
||||
if code != 0 {
|
||||
fmt.Println(output.Stderr())
|
||||
t.Fatalf("wrong exit status. Got %d, want 0", code)
|
||||
}
|
||||
expected := `resource "test_instance" "new" {
|
||||
value = null # REQUIRED string
|
||||
}
|
||||
resource "test_instance" "new2" {
|
||||
value = null # REQUIRED string
|
||||
}
|
||||
`
|
||||
result, err := os.ReadFile(outPath)
|
||||
if err != nil {
|
||||
t.Fatalf("error reading result file %s: %s", outPath, err.Error())
|
||||
}
|
||||
// While the entire directory will get removed once the whole test suite
|
||||
// is done, we remove this lest it gets in the way of another (not yet
|
||||
// written) test.
|
||||
os.Remove(outPath)
|
||||
|
||||
if !cmp.Equal(expected, string(result)) {
|
||||
t.Fatalf("wrong output:\n%s", cmp.Diff(expected, string(result)))
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("optionals", func(t *testing.T) {
|
||||
view, done := testView(t)
|
||||
c := &AddCommand{
|
||||
|
@ -194,7 +233,8 @@ resource "test_instance" "new" {
|
|||
View: view,
|
||||
},
|
||||
}
|
||||
args := []string{"test_instance.exists"}
|
||||
outPath := "add.tf"
|
||||
args := []string{fmt.Sprintf("-out=%s", outPath), "test_instance.exists"}
|
||||
code := c.Run(args)
|
||||
if code != 1 {
|
||||
t.Fatalf("wrong exit status. Got %d, want 0", code)
|
||||
|
@ -206,6 +246,31 @@ resource "test_instance" "new" {
|
|||
}
|
||||
})
|
||||
|
||||
t.Run("output existing resource to stdout", func(t *testing.T) {
|
||||
view, done := testView(t)
|
||||
c := &AddCommand{
|
||||
Meta: Meta{
|
||||
testingOverrides: overrides,
|
||||
View: view,
|
||||
},
|
||||
}
|
||||
args := []string{"test_instance.exists"}
|
||||
code := c.Run(args)
|
||||
output := done(t)
|
||||
if code != 0 {
|
||||
fmt.Println(output.Stderr())
|
||||
t.Fatalf("wrong exit status. Got %d, want 0", code)
|
||||
}
|
||||
expected := `resource "test_instance" "exists" {
|
||||
value = null # REQUIRED string
|
||||
}
|
||||
`
|
||||
|
||||
if !cmp.Equal(output.Stdout(), expected) {
|
||||
t.Fatalf("wrong output:\n%s", cmp.Diff(expected, output.Stdout()))
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("provider not in configuration", func(t *testing.T) {
|
||||
view, done := testView(t)
|
||||
c := &AddCommand{
|
||||
|
|
|
@ -84,7 +84,14 @@ func (v *addHuman) Resource(addr addrs.AbsResourceInstance, schema *configschema
|
|||
} else {
|
||||
// The Println call above adds this final newline automatically; we add it manually here.
|
||||
formatted = append(formatted, '\n')
|
||||
return os.WriteFile(v.outPath, formatted, 0600)
|
||||
|
||||
f, err := os.OpenFile(v.outPath, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0600)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
_, err = f.Write(formatted)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue