2016-05-04 12:32:08 -05:00
|
|
|
package command
|
|
|
|
|
|
|
|
import (
|
2016-11-02 12:30:28 -05:00
|
|
|
"fmt"
|
2018-11-16 20:37:26 -06:00
|
|
|
"log"
|
2017-06-27 10:07:45 -05:00
|
|
|
"os"
|
2018-03-20 11:44:12 -05:00
|
|
|
"path/filepath"
|
2017-05-16 20:20:08 -05:00
|
|
|
"strings"
|
2016-05-04 12:32:08 -05:00
|
|
|
"testing"
|
|
|
|
|
2018-09-29 11:45:09 -05:00
|
|
|
"github.com/mitchellh/cli"
|
|
|
|
"github.com/zclconf/go-cty/cty"
|
|
|
|
|
2018-10-14 18:54:23 -05:00
|
|
|
"github.com/hashicorp/terraform/configs/configschema"
|
2017-06-27 10:07:45 -05:00
|
|
|
"github.com/hashicorp/terraform/helper/copy"
|
2018-09-29 11:45:09 -05:00
|
|
|
"github.com/hashicorp/terraform/providers"
|
2016-05-04 12:32:08 -05:00
|
|
|
"github.com/hashicorp/terraform/terraform"
|
2018-09-29 11:45:09 -05:00
|
|
|
"github.com/hashicorp/terraform/tfdiags"
|
2016-05-04 12:32:08 -05:00
|
|
|
)
|
|
|
|
|
|
|
|
func TestImport(t *testing.T) {
|
2017-04-21 19:19:37 -05:00
|
|
|
defer testChdir(t, testFixturePath("import-provider-implicit"))()
|
|
|
|
|
2016-05-04 12:32:08 -05:00
|
|
|
statePath := testTempFile(t)
|
|
|
|
|
|
|
|
p := testProvider()
|
|
|
|
ui := new(cli.MockUi)
|
|
|
|
c := &ImportCommand{
|
|
|
|
Meta: Meta{
|
2017-04-13 20:05:58 -05:00
|
|
|
testingOverrides: metaOverridesForProvider(p),
|
|
|
|
Ui: ui,
|
2016-05-04 12:32:08 -05:00
|
|
|
},
|
|
|
|
}
|
|
|
|
|
2018-09-29 11:45:09 -05:00
|
|
|
p.ImportResourceStateFn = nil
|
|
|
|
p.ImportResourceStateResponse = providers.ImportResourceStateResponse{
|
|
|
|
ImportedResources: []providers.ImportedResource{
|
|
|
|
{
|
|
|
|
TypeName: "test_instance",
|
2018-10-14 09:59:15 -05:00
|
|
|
State: cty.ObjectVal(map[string]cty.Value{
|
2018-09-29 11:45:09 -05:00
|
|
|
"id": cty.StringVal("yay"),
|
|
|
|
}),
|
2016-05-04 12:32:08 -05:00
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
2018-10-14 18:54:23 -05:00
|
|
|
p.GetSchemaReturn = &terraform.ProviderSchema{
|
|
|
|
ResourceTypes: map[string]*configschema.Block{
|
|
|
|
"test_instance": {
|
|
|
|
Attributes: map[string]*configschema.Attribute{
|
2018-10-15 10:34:42 -05:00
|
|
|
"id": {Type: cty.String, Optional: true, Computed: true},
|
2018-10-14 18:54:23 -05:00
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
2016-05-04 12:32:08 -05:00
|
|
|
|
|
|
|
args := []string{
|
|
|
|
"-state", statePath,
|
2016-05-04 12:48:16 -05:00
|
|
|
"test_instance.foo",
|
2016-05-04 12:32:08 -05:00
|
|
|
"bar",
|
|
|
|
}
|
|
|
|
if code := c.Run(args); code != 0 {
|
|
|
|
t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
|
|
|
|
}
|
|
|
|
|
2018-09-29 11:45:09 -05:00
|
|
|
if !p.ImportResourceStateCalled {
|
|
|
|
t.Fatal("ImportResourceState should be called")
|
2016-05-04 12:32:08 -05:00
|
|
|
}
|
|
|
|
|
2016-05-04 12:48:16 -05:00
|
|
|
testStateOutput(t, statePath, testImportStr)
|
2016-05-04 12:32:08 -05:00
|
|
|
}
|
|
|
|
|
2016-11-02 12:30:28 -05:00
|
|
|
func TestImport_providerConfig(t *testing.T) {
|
|
|
|
defer testChdir(t, testFixturePath("import-provider"))()
|
|
|
|
|
|
|
|
statePath := testTempFile(t)
|
|
|
|
|
|
|
|
p := testProvider()
|
|
|
|
ui := new(cli.MockUi)
|
|
|
|
c := &ImportCommand{
|
|
|
|
Meta: Meta{
|
2017-04-13 20:05:58 -05:00
|
|
|
testingOverrides: metaOverridesForProvider(p),
|
|
|
|
Ui: ui,
|
2016-11-02 12:30:28 -05:00
|
|
|
},
|
|
|
|
}
|
|
|
|
|
2018-09-29 11:45:09 -05:00
|
|
|
p.ImportResourceStateFn = nil
|
|
|
|
p.ImportResourceStateResponse = providers.ImportResourceStateResponse{
|
|
|
|
ImportedResources: []providers.ImportedResource{
|
|
|
|
{
|
|
|
|
TypeName: "test_instance",
|
2018-10-14 09:59:15 -05:00
|
|
|
State: cty.ObjectVal(map[string]cty.Value{
|
2018-09-29 11:45:09 -05:00
|
|
|
"id": cty.StringVal("yay"),
|
|
|
|
}),
|
2016-11-02 12:30:28 -05:00
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
2018-10-14 18:54:23 -05:00
|
|
|
p.GetSchemaReturn = &terraform.ProviderSchema{
|
|
|
|
Provider: &configschema.Block{
|
|
|
|
Attributes: map[string]*configschema.Attribute{
|
2018-10-15 10:34:42 -05:00
|
|
|
"foo": {Type: cty.String, Optional: true},
|
2018-10-14 18:54:23 -05:00
|
|
|
},
|
|
|
|
},
|
|
|
|
ResourceTypes: map[string]*configschema.Block{
|
|
|
|
"test_instance": {
|
|
|
|
Attributes: map[string]*configschema.Attribute{
|
2018-10-15 10:34:42 -05:00
|
|
|
"id": {Type: cty.String, Optional: true, Computed: true},
|
2018-10-14 18:54:23 -05:00
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
2016-11-02 12:30:28 -05:00
|
|
|
|
|
|
|
configured := false
|
2018-09-29 11:45:09 -05:00
|
|
|
p.ConfigureNewFn = func(req providers.ConfigureRequest) providers.ConfigureResponse {
|
2016-11-02 12:30:28 -05:00
|
|
|
configured = true
|
|
|
|
|
2018-09-29 11:45:09 -05:00
|
|
|
cfg := req.Config
|
|
|
|
if !cfg.Type().HasAttribute("foo") {
|
|
|
|
return providers.ConfigureResponse{
|
|
|
|
Diagnostics: tfdiags.Diagnostics{}.Append(fmt.Errorf("configuration has no foo argument")),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if got, want := cfg.GetAttr("foo"), cty.StringVal("bar"); !want.RawEquals(got) {
|
|
|
|
return providers.ConfigureResponse{
|
|
|
|
Diagnostics: tfdiags.Diagnostics{}.Append(fmt.Errorf("foo argument is %#v, but want %#v", got, want)),
|
|
|
|
}
|
2016-11-02 12:30:28 -05:00
|
|
|
}
|
|
|
|
|
2018-09-29 11:45:09 -05:00
|
|
|
return providers.ConfigureResponse{}
|
2016-11-02 12:30:28 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
args := []string{
|
|
|
|
"-state", statePath,
|
|
|
|
"test_instance.foo",
|
|
|
|
"bar",
|
|
|
|
}
|
|
|
|
if code := c.Run(args); code != 0 {
|
|
|
|
t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
|
|
|
|
}
|
2016-11-02 13:11:42 -05:00
|
|
|
|
|
|
|
// Verify that we were called
|
|
|
|
if !configured {
|
|
|
|
t.Fatal("Configure should be called")
|
|
|
|
}
|
|
|
|
|
2018-09-29 11:45:09 -05:00
|
|
|
if !p.ImportResourceStateCalled {
|
|
|
|
t.Fatal("ImportResourceState should be called")
|
2016-11-02 13:11:42 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
testStateOutput(t, statePath, testImportStr)
|
|
|
|
}
|
|
|
|
|
2017-08-08 17:41:00 -05:00
|
|
|
// "remote" state provided by the "local" backend
|
|
|
|
func TestImport_remoteState(t *testing.T) {
|
|
|
|
td := tempDir(t)
|
|
|
|
copy.CopyDir(testFixturePath("import-provider-remote-state"), td)
|
|
|
|
defer os.RemoveAll(td)
|
|
|
|
defer testChdir(t, td)()
|
|
|
|
|
|
|
|
statePath := "imported.tfstate"
|
|
|
|
|
2020-03-31 16:02:40 -05:00
|
|
|
providerSource, close := newMockProviderSource(t, map[string][]string{
|
|
|
|
"test": []string{"1.2.3"},
|
|
|
|
})
|
|
|
|
defer close()
|
|
|
|
|
2017-08-08 17:41:00 -05:00
|
|
|
// init our backend
|
2018-11-15 15:31:30 -06:00
|
|
|
ui := cli.NewMockUi()
|
2017-08-08 17:41:00 -05:00
|
|
|
m := Meta{
|
|
|
|
testingOverrides: metaOverridesForProvider(testProvider()),
|
|
|
|
Ui: ui,
|
2020-03-31 16:02:40 -05:00
|
|
|
ProviderSource: providerSource,
|
2017-08-08 17:41:00 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
ic := &InitCommand{
|
|
|
|
Meta: m,
|
|
|
|
}
|
|
|
|
|
2018-11-15 15:31:30 -06:00
|
|
|
// (Using log here rather than t.Log so that these messages interleave with other trace logs)
|
|
|
|
log.Print("[TRACE] TestImport_remoteState running: terraform init")
|
2017-08-08 17:41:00 -05:00
|
|
|
if code := ic.Run([]string{}); code != 0 {
|
2018-11-15 15:31:30 -06:00
|
|
|
t.Fatalf("init failed\n%s", ui.ErrorWriter)
|
2017-08-08 17:41:00 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
p := testProvider()
|
|
|
|
ui = new(cli.MockUi)
|
|
|
|
c := &ImportCommand{
|
|
|
|
Meta: Meta{
|
|
|
|
testingOverrides: metaOverridesForProvider(p),
|
|
|
|
Ui: ui,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
2018-09-29 11:45:09 -05:00
|
|
|
p.ImportResourceStateFn = nil
|
|
|
|
p.ImportResourceStateResponse = providers.ImportResourceStateResponse{
|
|
|
|
ImportedResources: []providers.ImportedResource{
|
|
|
|
{
|
|
|
|
TypeName: "test_instance",
|
2018-10-14 09:59:15 -05:00
|
|
|
State: cty.ObjectVal(map[string]cty.Value{
|
2018-09-29 11:45:09 -05:00
|
|
|
"id": cty.StringVal("yay"),
|
|
|
|
}),
|
2017-08-08 17:41:00 -05:00
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
2018-10-14 18:54:23 -05:00
|
|
|
p.GetSchemaReturn = &terraform.ProviderSchema{
|
|
|
|
Provider: &configschema.Block{
|
|
|
|
Attributes: map[string]*configschema.Attribute{
|
2018-10-15 10:34:42 -05:00
|
|
|
"foo": {Type: cty.String, Optional: true},
|
2018-10-14 18:54:23 -05:00
|
|
|
},
|
|
|
|
},
|
|
|
|
ResourceTypes: map[string]*configschema.Block{
|
|
|
|
"test_instance": {
|
|
|
|
Attributes: map[string]*configschema.Attribute{
|
2018-10-15 10:34:42 -05:00
|
|
|
"id": {Type: cty.String, Optional: true, Computed: true},
|
2018-10-14 18:54:23 -05:00
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
2017-08-08 17:41:00 -05:00
|
|
|
|
|
|
|
configured := false
|
2018-10-14 18:54:23 -05:00
|
|
|
p.ConfigureNewFn = func(req providers.ConfigureRequest) providers.ConfigureResponse {
|
|
|
|
var diags tfdiags.Diagnostics
|
2017-08-08 17:41:00 -05:00
|
|
|
configured = true
|
2018-10-14 18:54:23 -05:00
|
|
|
if got, want := req.Config.GetAttr("foo"), cty.StringVal("bar"); !want.RawEquals(got) {
|
|
|
|
diags = diags.Append(fmt.Errorf("wrong \"foo\" value %#v; want %#v", got, want))
|
|
|
|
}
|
|
|
|
return providers.ConfigureResponse{
|
|
|
|
Diagnostics: diags,
|
2017-08-08 17:41:00 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
args := []string{
|
|
|
|
"test_instance.foo",
|
|
|
|
"bar",
|
|
|
|
}
|
2018-11-15 15:31:30 -06:00
|
|
|
log.Printf("[TRACE] TestImport_remoteState running: terraform import %s %s", args[0], args[1])
|
2017-08-08 17:41:00 -05:00
|
|
|
if code := c.Run(args); code != 0 {
|
|
|
|
fmt.Println(ui.OutputWriter)
|
|
|
|
t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
|
|
|
|
}
|
|
|
|
|
2018-03-20 11:44:12 -05:00
|
|
|
// verify that the local state was unlocked after import
|
|
|
|
if _, err := os.Stat(filepath.Join(td, fmt.Sprintf(".%s.lock.info", statePath))); !os.IsNotExist(err) {
|
|
|
|
t.Fatal("state left locked after import")
|
|
|
|
}
|
|
|
|
|
2017-08-08 17:41:00 -05:00
|
|
|
// Verify that we were called
|
|
|
|
if !configured {
|
|
|
|
t.Fatal("Configure should be called")
|
|
|
|
}
|
|
|
|
|
2018-09-29 11:45:09 -05:00
|
|
|
if !p.ImportResourceStateCalled {
|
|
|
|
t.Fatal("ImportResourceState should be called")
|
2017-08-08 17:41:00 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
testStateOutput(t, statePath, testImportStr)
|
|
|
|
}
|
|
|
|
|
2020-02-14 12:47:29 -06:00
|
|
|
// early failure on import should not leave stale lock
|
|
|
|
func TestImport_initializationErrorShouldUnlock(t *testing.T) {
|
|
|
|
td := tempDir(t)
|
|
|
|
copy.CopyDir(testFixturePath("import-provider-remote-state"), td)
|
|
|
|
defer os.RemoveAll(td)
|
|
|
|
defer testChdir(t, td)()
|
|
|
|
|
|
|
|
statePath := "imported.tfstate"
|
|
|
|
|
2020-03-31 16:02:40 -05:00
|
|
|
providerSource, close := newMockProviderSource(t, map[string][]string{
|
|
|
|
"test": []string{"1.2.3"},
|
|
|
|
})
|
|
|
|
defer close()
|
|
|
|
|
2020-02-14 12:47:29 -06:00
|
|
|
// init our backend
|
|
|
|
ui := cli.NewMockUi()
|
|
|
|
m := Meta{
|
|
|
|
testingOverrides: metaOverridesForProvider(testProvider()),
|
|
|
|
Ui: ui,
|
2020-03-31 16:02:40 -05:00
|
|
|
ProviderSource: providerSource,
|
2020-02-14 12:47:29 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
ic := &InitCommand{
|
|
|
|
Meta: m,
|
|
|
|
}
|
|
|
|
|
|
|
|
// (Using log here rather than t.Log so that these messages interleave with other trace logs)
|
|
|
|
log.Print("[TRACE] TestImport_initializationErrorShouldUnlock running: terraform init")
|
|
|
|
if code := ic.Run([]string{}); code != 0 {
|
|
|
|
t.Fatalf("init failed\n%s", ui.ErrorWriter)
|
|
|
|
}
|
|
|
|
|
|
|
|
// overwrite the config with one including a resource from an invalid provider
|
|
|
|
copy.CopyFile(filepath.Join(testFixturePath("import-provider-invalid"), "main.tf"), filepath.Join(td, "main.tf"))
|
|
|
|
|
|
|
|
p := testProvider()
|
|
|
|
ui = new(cli.MockUi)
|
|
|
|
c := &ImportCommand{
|
|
|
|
Meta: Meta{
|
|
|
|
testingOverrides: metaOverridesForProvider(p),
|
|
|
|
Ui: ui,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
args := []string{
|
|
|
|
"unknown_instance.baz",
|
|
|
|
"bar",
|
|
|
|
}
|
|
|
|
log.Printf("[TRACE] TestImport_initializationErrorShouldUnlock running: terraform import %s %s", args[0], args[1])
|
|
|
|
|
|
|
|
// this should fail
|
|
|
|
if code := c.Run(args); code != 1 {
|
|
|
|
fmt.Println(ui.OutputWriter)
|
|
|
|
t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
|
|
|
|
}
|
|
|
|
|
|
|
|
// specifically, it should fail due to a missing provider
|
|
|
|
msg := ui.ErrorWriter.String()
|
2020-04-02 18:02:46 -05:00
|
|
|
if want := `unknown provider "registry.terraform.io/hashicorp/unknown"`; !strings.Contains(msg, want) {
|
2020-02-14 12:47:29 -06:00
|
|
|
t.Errorf("incorrect message\nwant substring: %s\ngot:\n%s", want, msg)
|
|
|
|
}
|
|
|
|
|
|
|
|
// verify that the local state was unlocked after initialization error
|
|
|
|
if _, err := os.Stat(filepath.Join(td, fmt.Sprintf(".%s.lock.info", statePath))); !os.IsNotExist(err) {
|
|
|
|
t.Fatal("state left locked after import")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-01-24 15:01:23 -06:00
|
|
|
func TestImport_providerConfigWithVar(t *testing.T) {
|
|
|
|
defer testChdir(t, testFixturePath("import-provider-var"))()
|
|
|
|
|
|
|
|
statePath := testTempFile(t)
|
|
|
|
|
|
|
|
p := testProvider()
|
|
|
|
ui := new(cli.MockUi)
|
|
|
|
c := &ImportCommand{
|
|
|
|
Meta: Meta{
|
2017-04-13 20:05:58 -05:00
|
|
|
testingOverrides: metaOverridesForProvider(p),
|
|
|
|
Ui: ui,
|
2017-01-24 15:01:23 -06:00
|
|
|
},
|
|
|
|
}
|
|
|
|
|
2018-09-29 11:45:09 -05:00
|
|
|
p.ImportResourceStateFn = nil
|
|
|
|
p.ImportResourceStateResponse = providers.ImportResourceStateResponse{
|
|
|
|
ImportedResources: []providers.ImportedResource{
|
|
|
|
{
|
|
|
|
TypeName: "test_instance",
|
2018-10-14 09:59:15 -05:00
|
|
|
State: cty.ObjectVal(map[string]cty.Value{
|
2018-09-29 11:45:09 -05:00
|
|
|
"id": cty.StringVal("yay"),
|
|
|
|
}),
|
2017-01-24 15:01:23 -06:00
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
2018-10-14 18:54:23 -05:00
|
|
|
p.GetSchemaReturn = &terraform.ProviderSchema{
|
|
|
|
Provider: &configschema.Block{
|
|
|
|
Attributes: map[string]*configschema.Attribute{
|
2018-10-15 10:34:42 -05:00
|
|
|
"foo": {Type: cty.String, Optional: true},
|
2018-10-14 18:54:23 -05:00
|
|
|
},
|
|
|
|
},
|
|
|
|
ResourceTypes: map[string]*configschema.Block{
|
|
|
|
"test_instance": {
|
|
|
|
Attributes: map[string]*configschema.Attribute{
|
2018-10-15 10:34:42 -05:00
|
|
|
"id": {Type: cty.String, Optional: true, Computed: true},
|
2018-10-14 18:54:23 -05:00
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
2017-01-24 15:01:23 -06:00
|
|
|
|
|
|
|
configured := false
|
2018-10-14 18:54:23 -05:00
|
|
|
p.ConfigureNewFn = func(req providers.ConfigureRequest) providers.ConfigureResponse {
|
|
|
|
var diags tfdiags.Diagnostics
|
2017-01-24 15:01:23 -06:00
|
|
|
configured = true
|
2018-10-14 18:54:23 -05:00
|
|
|
if got, want := req.Config.GetAttr("foo"), cty.StringVal("bar"); !want.RawEquals(got) {
|
|
|
|
diags = diags.Append(fmt.Errorf("wrong \"foo\" value %#v; want %#v", got, want))
|
|
|
|
}
|
|
|
|
return providers.ConfigureResponse{
|
|
|
|
Diagnostics: diags,
|
2017-01-24 15:01:23 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
args := []string{
|
|
|
|
"-state", statePath,
|
|
|
|
"-var", "foo=bar",
|
|
|
|
"test_instance.foo",
|
|
|
|
"bar",
|
|
|
|
}
|
|
|
|
if code := c.Run(args); code != 0 {
|
|
|
|
t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
|
|
|
|
}
|
|
|
|
|
|
|
|
// Verify that we were called
|
|
|
|
if !configured {
|
|
|
|
t.Fatal("Configure should be called")
|
|
|
|
}
|
|
|
|
|
2018-09-29 11:45:09 -05:00
|
|
|
if !p.ImportResourceStateCalled {
|
|
|
|
t.Fatal("ImportResourceState should be called")
|
2017-01-24 15:01:23 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
testStateOutput(t, statePath, testImportStr)
|
|
|
|
}
|
|
|
|
|
2020-02-12 13:00:08 -06:00
|
|
|
func TestImport_providerConfigWithDataSource(t *testing.T) {
|
|
|
|
defer testChdir(t, testFixturePath("import-provider-datasource"))()
|
|
|
|
|
|
|
|
statePath := testTempFile(t)
|
|
|
|
|
|
|
|
p := testProvider()
|
|
|
|
ui := new(cli.MockUi)
|
|
|
|
c := &ImportCommand{
|
|
|
|
Meta: Meta{
|
|
|
|
testingOverrides: metaOverridesForProvider(p),
|
|
|
|
Ui: ui,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
p.ImportResourceStateFn = nil
|
|
|
|
p.ImportResourceStateResponse = providers.ImportResourceStateResponse{
|
|
|
|
ImportedResources: []providers.ImportedResource{
|
|
|
|
{
|
|
|
|
TypeName: "test_instance",
|
|
|
|
State: cty.ObjectVal(map[string]cty.Value{
|
|
|
|
"id": cty.StringVal("yay"),
|
|
|
|
}),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
p.GetSchemaReturn = &terraform.ProviderSchema{
|
|
|
|
Provider: &configschema.Block{
|
|
|
|
Attributes: map[string]*configschema.Attribute{
|
|
|
|
"foo": {Type: cty.String, Optional: true},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
ResourceTypes: map[string]*configschema.Block{
|
|
|
|
"test_instance": {
|
|
|
|
Attributes: map[string]*configschema.Attribute{
|
|
|
|
"id": {Type: cty.String, Optional: true, Computed: true},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
DataSources: map[string]*configschema.Block{
|
|
|
|
"test_data": {
|
|
|
|
Attributes: map[string]*configschema.Attribute{
|
|
|
|
"id": {Type: cty.String, Optional: true, Computed: true},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
args := []string{
|
|
|
|
"-state", statePath,
|
|
|
|
"test_instance.foo",
|
|
|
|
"bar",
|
|
|
|
}
|
|
|
|
if code := c.Run(args); code != 1 {
|
|
|
|
t.Fatalf("bad, wanted error: %d\n\n%s", code, ui.ErrorWriter.String())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-01-24 15:01:23 -06:00
|
|
|
func TestImport_providerConfigWithVarDefault(t *testing.T) {
|
|
|
|
defer testChdir(t, testFixturePath("import-provider-var-default"))()
|
|
|
|
|
|
|
|
statePath := testTempFile(t)
|
|
|
|
|
|
|
|
p := testProvider()
|
|
|
|
ui := new(cli.MockUi)
|
|
|
|
c := &ImportCommand{
|
|
|
|
Meta: Meta{
|
2017-04-13 20:05:58 -05:00
|
|
|
testingOverrides: metaOverridesForProvider(p),
|
|
|
|
Ui: ui,
|
2017-01-24 15:01:23 -06:00
|
|
|
},
|
|
|
|
}
|
|
|
|
|
2018-09-29 11:45:09 -05:00
|
|
|
p.ImportResourceStateFn = nil
|
|
|
|
p.ImportResourceStateResponse = providers.ImportResourceStateResponse{
|
|
|
|
ImportedResources: []providers.ImportedResource{
|
|
|
|
{
|
|
|
|
TypeName: "test_instance",
|
2018-10-14 09:59:15 -05:00
|
|
|
State: cty.ObjectVal(map[string]cty.Value{
|
2018-09-29 11:45:09 -05:00
|
|
|
"id": cty.StringVal("yay"),
|
|
|
|
}),
|
2017-01-24 15:01:23 -06:00
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
2018-10-14 18:54:23 -05:00
|
|
|
p.GetSchemaReturn = &terraform.ProviderSchema{
|
|
|
|
Provider: &configschema.Block{
|
|
|
|
Attributes: map[string]*configschema.Attribute{
|
2018-10-15 10:34:42 -05:00
|
|
|
"foo": {Type: cty.String, Optional: true},
|
2018-10-14 18:54:23 -05:00
|
|
|
},
|
|
|
|
},
|
|
|
|
ResourceTypes: map[string]*configschema.Block{
|
|
|
|
"test_instance": {
|
|
|
|
Attributes: map[string]*configschema.Attribute{
|
2018-10-15 10:34:42 -05:00
|
|
|
"id": {Type: cty.String, Optional: true, Computed: true},
|
2018-10-14 18:54:23 -05:00
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
2017-01-24 15:01:23 -06:00
|
|
|
|
|
|
|
configured := false
|
2018-10-14 18:54:23 -05:00
|
|
|
p.ConfigureNewFn = func(req providers.ConfigureRequest) providers.ConfigureResponse {
|
|
|
|
var diags tfdiags.Diagnostics
|
2017-01-24 15:01:23 -06:00
|
|
|
configured = true
|
2018-10-14 18:54:23 -05:00
|
|
|
if got, want := req.Config.GetAttr("foo"), cty.StringVal("bar"); !want.RawEquals(got) {
|
|
|
|
diags = diags.Append(fmt.Errorf("wrong \"foo\" value %#v; want %#v", got, want))
|
|
|
|
}
|
|
|
|
return providers.ConfigureResponse{
|
|
|
|
Diagnostics: diags,
|
2017-01-24 15:01:23 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
args := []string{
|
|
|
|
"-state", statePath,
|
|
|
|
"test_instance.foo",
|
|
|
|
"bar",
|
|
|
|
}
|
|
|
|
if code := c.Run(args); code != 0 {
|
|
|
|
t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
|
|
|
|
}
|
|
|
|
|
|
|
|
// Verify that we were called
|
|
|
|
if !configured {
|
|
|
|
t.Fatal("Configure should be called")
|
|
|
|
}
|
|
|
|
|
2018-09-29 11:45:09 -05:00
|
|
|
if !p.ImportResourceStateCalled {
|
|
|
|
t.Fatal("ImportResourceState should be called")
|
2017-01-24 15:01:23 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
testStateOutput(t, statePath, testImportStr)
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestImport_providerConfigWithVarFile(t *testing.T) {
|
|
|
|
defer testChdir(t, testFixturePath("import-provider-var-file"))()
|
|
|
|
|
|
|
|
statePath := testTempFile(t)
|
|
|
|
|
|
|
|
p := testProvider()
|
|
|
|
ui := new(cli.MockUi)
|
|
|
|
c := &ImportCommand{
|
|
|
|
Meta: Meta{
|
2017-04-13 20:05:58 -05:00
|
|
|
testingOverrides: metaOverridesForProvider(p),
|
|
|
|
Ui: ui,
|
2017-01-24 15:01:23 -06:00
|
|
|
},
|
|
|
|
}
|
|
|
|
|
2018-09-29 11:45:09 -05:00
|
|
|
p.ImportResourceStateFn = nil
|
|
|
|
p.ImportResourceStateResponse = providers.ImportResourceStateResponse{
|
|
|
|
ImportedResources: []providers.ImportedResource{
|
|
|
|
{
|
|
|
|
TypeName: "test_instance",
|
2018-10-14 09:59:15 -05:00
|
|
|
State: cty.ObjectVal(map[string]cty.Value{
|
2018-09-29 11:45:09 -05:00
|
|
|
"id": cty.StringVal("yay"),
|
|
|
|
}),
|
2017-01-24 15:01:23 -06:00
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
2018-10-14 18:54:23 -05:00
|
|
|
p.GetSchemaReturn = &terraform.ProviderSchema{
|
|
|
|
Provider: &configschema.Block{
|
|
|
|
Attributes: map[string]*configschema.Attribute{
|
2018-10-15 10:34:42 -05:00
|
|
|
"foo": {Type: cty.String, Optional: true},
|
2018-10-14 18:54:23 -05:00
|
|
|
},
|
|
|
|
},
|
|
|
|
ResourceTypes: map[string]*configschema.Block{
|
|
|
|
"test_instance": {
|
|
|
|
Attributes: map[string]*configschema.Attribute{
|
2018-10-15 10:34:42 -05:00
|
|
|
"id": {Type: cty.String, Optional: true, Computed: true},
|
2018-10-14 18:54:23 -05:00
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
2017-01-24 15:01:23 -06:00
|
|
|
|
|
|
|
configured := false
|
2018-10-14 18:54:23 -05:00
|
|
|
p.ConfigureNewFn = func(req providers.ConfigureRequest) providers.ConfigureResponse {
|
|
|
|
var diags tfdiags.Diagnostics
|
2017-01-24 15:01:23 -06:00
|
|
|
configured = true
|
2018-10-14 18:54:23 -05:00
|
|
|
if got, want := req.Config.GetAttr("foo"), cty.StringVal("bar"); !want.RawEquals(got) {
|
|
|
|
diags = diags.Append(fmt.Errorf("wrong \"foo\" value %#v; want %#v", got, want))
|
|
|
|
}
|
|
|
|
return providers.ConfigureResponse{
|
|
|
|
Diagnostics: diags,
|
2017-01-24 15:01:23 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
args := []string{
|
|
|
|
"-state", statePath,
|
|
|
|
"-var-file", "blah.tfvars",
|
|
|
|
"test_instance.foo",
|
|
|
|
"bar",
|
|
|
|
}
|
|
|
|
if code := c.Run(args); code != 0 {
|
|
|
|
t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
|
|
|
|
}
|
|
|
|
|
|
|
|
// Verify that we were called
|
|
|
|
if !configured {
|
|
|
|
t.Fatal("Configure should be called")
|
|
|
|
}
|
|
|
|
|
2018-09-29 11:45:09 -05:00
|
|
|
if !p.ImportResourceStateCalled {
|
|
|
|
t.Fatal("ImportResourceState should be called")
|
2017-01-24 15:01:23 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
testStateOutput(t, statePath, testImportStr)
|
|
|
|
}
|
|
|
|
|
2020-06-23 13:08:27 -05:00
|
|
|
func TestImport_allowMissingResourceConfig(t *testing.T) {
|
2017-09-18 13:41:30 -05:00
|
|
|
defer testChdir(t, testFixturePath("import-missing-resource-config"))()
|
|
|
|
|
|
|
|
statePath := testTempFile(t)
|
|
|
|
|
|
|
|
p := testProvider()
|
|
|
|
ui := new(cli.MockUi)
|
|
|
|
c := &ImportCommand{
|
|
|
|
Meta: Meta{
|
|
|
|
testingOverrides: metaOverridesForProvider(p),
|
|
|
|
Ui: ui,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
2018-09-29 11:45:09 -05:00
|
|
|
p.ImportResourceStateFn = nil
|
|
|
|
p.ImportResourceStateResponse = providers.ImportResourceStateResponse{
|
|
|
|
ImportedResources: []providers.ImportedResource{
|
|
|
|
{
|
|
|
|
TypeName: "test_instance",
|
2018-10-14 09:59:15 -05:00
|
|
|
State: cty.ObjectVal(map[string]cty.Value{
|
2018-09-29 11:45:09 -05:00
|
|
|
"id": cty.StringVal("yay"),
|
|
|
|
}),
|
2017-09-18 13:41:30 -05:00
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
2018-10-14 18:54:23 -05:00
|
|
|
p.GetSchemaReturn = &terraform.ProviderSchema{
|
|
|
|
ResourceTypes: map[string]*configschema.Block{
|
|
|
|
"test_instance": {
|
|
|
|
Attributes: map[string]*configschema.Attribute{
|
2018-10-15 10:34:42 -05:00
|
|
|
"id": {Type: cty.String, Optional: true, Computed: true},
|
2018-10-14 18:54:23 -05:00
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
2017-09-18 13:41:30 -05:00
|
|
|
|
|
|
|
args := []string{
|
|
|
|
"-state", statePath,
|
|
|
|
"-allow-missing-config",
|
|
|
|
"test_instance.foo",
|
|
|
|
"bar",
|
|
|
|
}
|
|
|
|
|
2020-06-23 13:08:27 -05:00
|
|
|
if code := c.Run(args); code != 0 {
|
|
|
|
t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
|
2017-09-18 13:41:30 -05:00
|
|
|
}
|
|
|
|
|
2020-06-23 13:08:27 -05:00
|
|
|
if !p.ImportResourceStateCalled {
|
|
|
|
t.Fatal("ImportResourceState should be called")
|
2020-03-20 07:15:29 -05:00
|
|
|
}
|
2020-06-23 13:08:27 -05:00
|
|
|
|
|
|
|
testStateOutput(t, statePath, testImportStr)
|
2017-09-18 13:41:30 -05:00
|
|
|
}
|
|
|
|
|
2017-12-08 12:22:07 -06:00
|
|
|
func TestImport_emptyConfig(t *testing.T) {
|
|
|
|
defer testChdir(t, testFixturePath("empty"))()
|
|
|
|
|
|
|
|
statePath := testTempFile(t)
|
|
|
|
|
|
|
|
p := testProvider()
|
|
|
|
ui := new(cli.MockUi)
|
|
|
|
c := &ImportCommand{
|
|
|
|
Meta: Meta{
|
|
|
|
testingOverrides: metaOverridesForProvider(p),
|
|
|
|
Ui: ui,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
args := []string{
|
|
|
|
"-state", statePath,
|
|
|
|
"test_instance.foo",
|
|
|
|
"bar",
|
|
|
|
}
|
|
|
|
code := c.Run(args)
|
|
|
|
if code != 1 {
|
|
|
|
t.Fatalf("import succeeded; expected failure")
|
|
|
|
}
|
|
|
|
|
|
|
|
msg := ui.ErrorWriter.String()
|
|
|
|
if want := `No Terraform configuration files`; !strings.Contains(msg, want) {
|
|
|
|
t.Errorf("incorrect message\nwant substring: %s\ngot:\n%s", want, msg)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-05-16 20:26:20 -05:00
|
|
|
func TestImport_missingResourceConfig(t *testing.T) {
|
|
|
|
defer testChdir(t, testFixturePath("import-missing-resource-config"))()
|
|
|
|
|
|
|
|
statePath := testTempFile(t)
|
|
|
|
|
|
|
|
p := testProvider()
|
|
|
|
ui := new(cli.MockUi)
|
|
|
|
c := &ImportCommand{
|
|
|
|
Meta: Meta{
|
|
|
|
testingOverrides: metaOverridesForProvider(p),
|
|
|
|
Ui: ui,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
args := []string{
|
|
|
|
"-state", statePath,
|
|
|
|
"test_instance.foo",
|
|
|
|
"bar",
|
|
|
|
}
|
|
|
|
code := c.Run(args)
|
|
|
|
if code != 1 {
|
|
|
|
t.Fatalf("import succeeded; expected failure")
|
|
|
|
}
|
|
|
|
|
|
|
|
msg := ui.ErrorWriter.String()
|
|
|
|
if want := `resource address "test_instance.foo" does not exist`; !strings.Contains(msg, want) {
|
|
|
|
t.Errorf("incorrect message\nwant substring: %s\ngot:\n%s", want, msg)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestImport_missingModuleConfig(t *testing.T) {
|
|
|
|
defer testChdir(t, testFixturePath("import-missing-resource-config"))()
|
|
|
|
|
|
|
|
statePath := testTempFile(t)
|
|
|
|
|
|
|
|
p := testProvider()
|
|
|
|
ui := new(cli.MockUi)
|
|
|
|
c := &ImportCommand{
|
|
|
|
Meta: Meta{
|
|
|
|
testingOverrides: metaOverridesForProvider(p),
|
|
|
|
Ui: ui,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
args := []string{
|
|
|
|
"-state", statePath,
|
|
|
|
"module.baz.test_instance.foo",
|
|
|
|
"bar",
|
|
|
|
}
|
|
|
|
code := c.Run(args)
|
|
|
|
if code != 1 {
|
|
|
|
t.Fatalf("import succeeded; expected failure")
|
|
|
|
}
|
|
|
|
|
|
|
|
msg := ui.ErrorWriter.String()
|
2017-12-08 12:22:07 -06:00
|
|
|
if want := `module.baz is not defined in the configuration`; !strings.Contains(msg, want) {
|
2017-05-16 20:26:20 -05:00
|
|
|
t.Errorf("incorrect message\nwant substring: %s\ngot:\n%s", want, msg)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-06-12 11:35:30 -05:00
|
|
|
func TestImportModuleVarFile(t *testing.T) {
|
2020-06-16 13:11:08 -05:00
|
|
|
td := tempDir(t)
|
|
|
|
copy.CopyDir(testFixturePath("import-module-var-file"), td)
|
|
|
|
defer os.RemoveAll(td)
|
|
|
|
defer testChdir(t, td)()
|
2020-06-12 11:35:30 -05:00
|
|
|
|
|
|
|
statePath := testTempFile(t)
|
|
|
|
|
|
|
|
p := testProvider()
|
|
|
|
p.GetSchemaReturn = &terraform.ProviderSchema{
|
|
|
|
ResourceTypes: map[string]*configschema.Block{
|
|
|
|
"test_instance": {
|
|
|
|
Attributes: map[string]*configschema.Attribute{
|
|
|
|
"foo": {Type: cty.String, Optional: true},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
providerSource, close := newMockProviderSource(t, map[string][]string{
|
|
|
|
"test": []string{"1.2.3"},
|
|
|
|
})
|
|
|
|
defer close()
|
|
|
|
|
|
|
|
// init to install the module
|
|
|
|
ui := new(cli.MockUi)
|
|
|
|
m := Meta{
|
|
|
|
testingOverrides: metaOverridesForProvider(testProvider()),
|
|
|
|
Ui: ui,
|
|
|
|
ProviderSource: providerSource,
|
|
|
|
}
|
|
|
|
|
|
|
|
ic := &InitCommand{
|
|
|
|
Meta: m,
|
|
|
|
}
|
|
|
|
if code := ic.Run([]string{}); code != 0 {
|
|
|
|
t.Fatalf("init failed\n%s", ui.ErrorWriter)
|
|
|
|
}
|
|
|
|
|
|
|
|
// import
|
|
|
|
ui = new(cli.MockUi)
|
|
|
|
c := &ImportCommand{
|
|
|
|
Meta: Meta{
|
|
|
|
testingOverrides: metaOverridesForProvider(p),
|
|
|
|
Ui: ui,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
args := []string{
|
|
|
|
"-state", statePath,
|
|
|
|
"module.child.test_instance.foo",
|
|
|
|
"bar",
|
|
|
|
}
|
|
|
|
code := c.Run(args)
|
|
|
|
if code != 0 {
|
|
|
|
t.Fatalf("import failed; expected success")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-05-16 20:20:08 -05:00
|
|
|
func TestImport_dataResource(t *testing.T) {
|
|
|
|
defer testChdir(t, testFixturePath("import-missing-resource-config"))()
|
|
|
|
|
|
|
|
statePath := testTempFile(t)
|
|
|
|
|
|
|
|
p := testProvider()
|
|
|
|
ui := new(cli.MockUi)
|
|
|
|
c := &ImportCommand{
|
|
|
|
Meta: Meta{
|
|
|
|
testingOverrides: metaOverridesForProvider(p),
|
|
|
|
Ui: ui,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
args := []string{
|
|
|
|
"-state", statePath,
|
|
|
|
"data.test_data_source.foo",
|
|
|
|
"bar",
|
|
|
|
}
|
|
|
|
code := c.Run(args)
|
|
|
|
if code != 1 {
|
|
|
|
t.Fatalf("import succeeded; expected failure")
|
|
|
|
}
|
|
|
|
|
|
|
|
msg := ui.ErrorWriter.String()
|
2018-10-09 19:47:53 -05:00
|
|
|
if want := `A managed resource address is required`; !strings.Contains(msg, want) {
|
2017-05-16 20:20:08 -05:00
|
|
|
t.Errorf("incorrect message\nwant substring: %s\ngot:\n%s", want, msg)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestImport_invalidResourceAddr(t *testing.T) {
|
|
|
|
defer testChdir(t, testFixturePath("import-missing-resource-config"))()
|
|
|
|
|
|
|
|
statePath := testTempFile(t)
|
|
|
|
|
|
|
|
p := testProvider()
|
|
|
|
ui := new(cli.MockUi)
|
|
|
|
c := &ImportCommand{
|
|
|
|
Meta: Meta{
|
|
|
|
testingOverrides: metaOverridesForProvider(p),
|
|
|
|
Ui: ui,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
args := []string{
|
|
|
|
"-state", statePath,
|
|
|
|
"bananas",
|
|
|
|
"bar",
|
|
|
|
}
|
|
|
|
code := c.Run(args)
|
|
|
|
if code != 1 {
|
|
|
|
t.Fatalf("import succeeded; expected failure")
|
|
|
|
}
|
|
|
|
|
|
|
|
msg := ui.ErrorWriter.String()
|
2018-10-09 19:47:53 -05:00
|
|
|
if want := `Error: Invalid address`; !strings.Contains(msg, want) {
|
2017-05-16 20:20:08 -05:00
|
|
|
t.Errorf("incorrect message\nwant substring: %s\ngot:\n%s", want, msg)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestImport_targetIsModule(t *testing.T) {
|
|
|
|
defer testChdir(t, testFixturePath("import-missing-resource-config"))()
|
|
|
|
|
|
|
|
statePath := testTempFile(t)
|
|
|
|
|
|
|
|
p := testProvider()
|
|
|
|
ui := new(cli.MockUi)
|
|
|
|
c := &ImportCommand{
|
|
|
|
Meta: Meta{
|
|
|
|
testingOverrides: metaOverridesForProvider(p),
|
|
|
|
Ui: ui,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
args := []string{
|
|
|
|
"-state", statePath,
|
|
|
|
"module.foo",
|
|
|
|
"bar",
|
|
|
|
}
|
|
|
|
code := c.Run(args)
|
|
|
|
if code != 1 {
|
|
|
|
t.Fatalf("import succeeded; expected failure")
|
|
|
|
}
|
|
|
|
|
|
|
|
msg := ui.ErrorWriter.String()
|
2018-10-09 19:47:53 -05:00
|
|
|
if want := `Error: Invalid address`; !strings.Contains(msg, want) {
|
2017-05-16 20:20:08 -05:00
|
|
|
t.Errorf("incorrect message\nwant substring: %s\ngot:\n%s", want, msg)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-05-04 12:48:16 -05:00
|
|
|
const testImportStr = `
|
2016-05-04 12:32:08 -05:00
|
|
|
test_instance.foo:
|
2016-05-04 12:48:16 -05:00
|
|
|
ID = yay
|
2020-04-01 14:55:25 -05:00
|
|
|
provider = provider["registry.terraform.io/hashicorp/test"]
|
2016-05-04 12:32:08 -05:00
|
|
|
`
|
2016-11-23 03:44:52 -06:00
|
|
|
|
|
|
|
const testImportCustomProviderStr = `
|
|
|
|
test_instance.foo:
|
|
|
|
ID = yay
|
2020-04-01 14:55:25 -05:00
|
|
|
provider = provider["registry.terraform.io/hashicorp/test"].alias
|
2016-11-23 03:44:52 -06:00
|
|
|
`
|
2019-09-20 09:02:42 -05:00
|
|
|
|
|
|
|
const testImportProviderMismatchStr = `
|
|
|
|
test_instance.foo:
|
|
|
|
ID = yay
|
2020-04-01 14:55:25 -05:00
|
|
|
provider = provider["registry.terraform.io/hashicorp/test-beta"]
|
2019-09-20 09:02:42 -05:00
|
|
|
`
|