state: Add support for outputs of multiple types
This commit adds the groundwork for supporting module outputs of types other than string. In order to do so, the state version is increased from 1 to 2 (though the "public-facing" state version is actually as the first state file was binary). Tests are added to ensure that V2 (1) state is upgraded to V3 (2) state, though no separate read path is required since the V2 JSON will unmarshal correctly into the V3 structure. Outputs in a ModuleState are now of type map[string]interface{}, and a test covers round-tripping string, []string and map[string]string, which should cover all of the types in question. Type switches have been added where necessary to deal with the interface{} value, but they currently default to panicking when the input is not a string.
This commit is contained in:
parent
3393492033
commit
6aac79e194
|
@ -60,7 +60,7 @@ func resourceRemoteStateRead(d *schema.ResourceData, meta interface{}) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
var outputs map[string]string
|
var outputs map[string]interface{}
|
||||||
if !state.State().Empty() {
|
if !state.State().Empty() {
|
||||||
outputs = state.State().RootModule().Outputs
|
outputs = state.State().RootModule().Outputs
|
||||||
}
|
}
|
||||||
|
|
|
@ -50,7 +50,13 @@ EOT
|
||||||
}
|
}
|
||||||
`, testPrivateKey),
|
`, testPrivateKey),
|
||||||
Check: func(s *terraform.State) error {
|
Check: func(s *terraform.State) error {
|
||||||
got := s.RootModule().Outputs["key_pem"]
|
gotUntyped := s.RootModule().Outputs["key_pem"]
|
||||||
|
|
||||||
|
got, ok := gotUntyped.(string)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("output for \"key_pem\" is not a string")
|
||||||
|
}
|
||||||
|
|
||||||
if !strings.HasPrefix(got, "-----BEGIN CERTIFICATE REQUEST----") {
|
if !strings.HasPrefix(got, "-----BEGIN CERTIFICATE REQUEST----") {
|
||||||
return fmt.Errorf("key is missing CSR PEM preamble")
|
return fmt.Errorf("key is missing CSR PEM preamble")
|
||||||
}
|
}
|
||||||
|
|
|
@ -47,7 +47,11 @@ EOT
|
||||||
}
|
}
|
||||||
`, testCertRequest, testCACert, testCAPrivateKey),
|
`, testCertRequest, testCACert, testCAPrivateKey),
|
||||||
Check: func(s *terraform.State) error {
|
Check: func(s *terraform.State) error {
|
||||||
got := s.RootModule().Outputs["cert_pem"]
|
gotUntyped := s.RootModule().Outputs["cert_pem"]
|
||||||
|
got, ok := gotUntyped.(string)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("output for \"cert_pem\" is not a string")
|
||||||
|
}
|
||||||
if !strings.HasPrefix(got, "-----BEGIN CERTIFICATE----") {
|
if !strings.HasPrefix(got, "-----BEGIN CERTIFICATE----") {
|
||||||
return fmt.Errorf("key is missing cert PEM preamble")
|
return fmt.Errorf("key is missing cert PEM preamble")
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,7 +29,12 @@ func TestPrivateKeyRSA(t *testing.T) {
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
Check: func(s *terraform.State) error {
|
Check: func(s *terraform.State) error {
|
||||||
gotPrivate := s.RootModule().Outputs["private_key_pem"]
|
gotPrivateUntyped := s.RootModule().Outputs["private_key_pem"]
|
||||||
|
gotPrivate, ok := gotPrivateUntyped.(string)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("output for \"private_key_pem\" is not a string")
|
||||||
|
}
|
||||||
|
|
||||||
if !strings.HasPrefix(gotPrivate, "-----BEGIN RSA PRIVATE KEY----") {
|
if !strings.HasPrefix(gotPrivate, "-----BEGIN RSA PRIVATE KEY----") {
|
||||||
return fmt.Errorf("private key is missing RSA key PEM preamble")
|
return fmt.Errorf("private key is missing RSA key PEM preamble")
|
||||||
}
|
}
|
||||||
|
@ -37,12 +42,20 @@ func TestPrivateKeyRSA(t *testing.T) {
|
||||||
return fmt.Errorf("private key PEM looks too long for a 2048-bit key (got %v characters)", len(gotPrivate))
|
return fmt.Errorf("private key PEM looks too long for a 2048-bit key (got %v characters)", len(gotPrivate))
|
||||||
}
|
}
|
||||||
|
|
||||||
gotPublic := s.RootModule().Outputs["public_key_pem"]
|
gotPublicUntyped := s.RootModule().Outputs["public_key_pem"]
|
||||||
|
gotPublic, ok := gotPublicUntyped.(string)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("output for \"public_key_pem\" is not a string")
|
||||||
|
}
|
||||||
if !strings.HasPrefix(gotPublic, "-----BEGIN PUBLIC KEY----") {
|
if !strings.HasPrefix(gotPublic, "-----BEGIN PUBLIC KEY----") {
|
||||||
return fmt.Errorf("public key is missing public key PEM preamble")
|
return fmt.Errorf("public key is missing public key PEM preamble")
|
||||||
}
|
}
|
||||||
|
|
||||||
gotPublicSSH := s.RootModule().Outputs["public_key_openssh"]
|
gotPublicSSHUntyped := s.RootModule().Outputs["public_key_openssh"]
|
||||||
|
gotPublicSSH, ok := gotPublicSSHUntyped.(string)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("output for \"public_key_openssh\" is not a string")
|
||||||
|
}
|
||||||
if !strings.HasPrefix(gotPublicSSH, "ssh-rsa ") {
|
if !strings.HasPrefix(gotPublicSSH, "ssh-rsa ") {
|
||||||
return fmt.Errorf("SSH public key is missing ssh-rsa prefix")
|
return fmt.Errorf("SSH public key is missing ssh-rsa prefix")
|
||||||
}
|
}
|
||||||
|
@ -61,7 +74,11 @@ func TestPrivateKeyRSA(t *testing.T) {
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
Check: func(s *terraform.State) error {
|
Check: func(s *terraform.State) error {
|
||||||
got := s.RootModule().Outputs["key_pem"]
|
gotUntyped := s.RootModule().Outputs["key_pem"]
|
||||||
|
got, ok := gotUntyped.(string)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("output for \"key_pem\" is not a string")
|
||||||
|
}
|
||||||
if !strings.HasPrefix(got, "-----BEGIN RSA PRIVATE KEY----") {
|
if !strings.HasPrefix(got, "-----BEGIN RSA PRIVATE KEY----") {
|
||||||
return fmt.Errorf("key is missing RSA key PEM preamble")
|
return fmt.Errorf("key is missing RSA key PEM preamble")
|
||||||
}
|
}
|
||||||
|
@ -95,12 +112,22 @@ func TestPrivateKeyECDSA(t *testing.T) {
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
Check: func(s *terraform.State) error {
|
Check: func(s *terraform.State) error {
|
||||||
gotPrivate := s.RootModule().Outputs["private_key_pem"]
|
gotPrivateUntyped := s.RootModule().Outputs["private_key_pem"]
|
||||||
|
gotPrivate, ok := gotPrivateUntyped.(string)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("output for \"private_key_pem\" is not a string")
|
||||||
|
}
|
||||||
|
|
||||||
if !strings.HasPrefix(gotPrivate, "-----BEGIN EC PRIVATE KEY----") {
|
if !strings.HasPrefix(gotPrivate, "-----BEGIN EC PRIVATE KEY----") {
|
||||||
return fmt.Errorf("Private key is missing EC key PEM preamble")
|
return fmt.Errorf("Private key is missing EC key PEM preamble")
|
||||||
}
|
}
|
||||||
|
|
||||||
gotPublic := s.RootModule().Outputs["public_key_pem"]
|
gotPublicUntyped := s.RootModule().Outputs["public_key_pem"]
|
||||||
|
gotPublic, ok := gotPublicUntyped.(string)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("output for \"public_key_pem\" is not a string")
|
||||||
|
}
|
||||||
|
|
||||||
if !strings.HasPrefix(gotPublic, "-----BEGIN PUBLIC KEY----") {
|
if !strings.HasPrefix(gotPublic, "-----BEGIN PUBLIC KEY----") {
|
||||||
return fmt.Errorf("public key is missing public key PEM preamble")
|
return fmt.Errorf("public key is missing public key PEM preamble")
|
||||||
}
|
}
|
||||||
|
@ -130,17 +157,29 @@ func TestPrivateKeyECDSA(t *testing.T) {
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
Check: func(s *terraform.State) error {
|
Check: func(s *terraform.State) error {
|
||||||
gotPrivate := s.RootModule().Outputs["private_key_pem"]
|
gotPrivateUntyped := s.RootModule().Outputs["private_key_pem"]
|
||||||
|
gotPrivate, ok := gotPrivateUntyped.(string)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("output for \"private_key_pem\" is not a string")
|
||||||
|
}
|
||||||
if !strings.HasPrefix(gotPrivate, "-----BEGIN EC PRIVATE KEY----") {
|
if !strings.HasPrefix(gotPrivate, "-----BEGIN EC PRIVATE KEY----") {
|
||||||
return fmt.Errorf("Private key is missing EC key PEM preamble")
|
return fmt.Errorf("Private key is missing EC key PEM preamble")
|
||||||
}
|
}
|
||||||
|
|
||||||
gotPublic := s.RootModule().Outputs["public_key_pem"]
|
gotPublicUntyped := s.RootModule().Outputs["public_key_pem"]
|
||||||
|
gotPublic, ok := gotPublicUntyped.(string)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("output for \"public_key_pem\" is not a string")
|
||||||
|
}
|
||||||
if !strings.HasPrefix(gotPublic, "-----BEGIN PUBLIC KEY----") {
|
if !strings.HasPrefix(gotPublic, "-----BEGIN PUBLIC KEY----") {
|
||||||
return fmt.Errorf("public key is missing public key PEM preamble")
|
return fmt.Errorf("public key is missing public key PEM preamble")
|
||||||
}
|
}
|
||||||
|
|
||||||
gotPublicSSH := s.RootModule().Outputs["public_key_openssh"]
|
gotPublicSSHUntyped := s.RootModule().Outputs["public_key_openssh"]
|
||||||
|
gotPublicSSH, ok := gotPublicSSHUntyped.(string)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("output for \"public_key_openssh\" is not a string")
|
||||||
|
}
|
||||||
if !strings.HasPrefix(gotPublicSSH, "ecdsa-sha2-nistp256 ") {
|
if !strings.HasPrefix(gotPublicSSH, "ecdsa-sha2-nistp256 ") {
|
||||||
return fmt.Errorf("P256 SSH public key is missing ecdsa prefix")
|
return fmt.Errorf("P256 SSH public key is missing ecdsa prefix")
|
||||||
}
|
}
|
||||||
|
|
|
@ -60,7 +60,12 @@ EOT
|
||||||
}
|
}
|
||||||
`, testPrivateKey),
|
`, testPrivateKey),
|
||||||
Check: func(s *terraform.State) error {
|
Check: func(s *terraform.State) error {
|
||||||
got := s.RootModule().Outputs["key_pem"]
|
gotUntyped := s.RootModule().Outputs["key_pem"]
|
||||||
|
got, ok := gotUntyped.(string)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("output for \"public_key_openssh\" is not a string")
|
||||||
|
}
|
||||||
|
|
||||||
if !strings.HasPrefix(got, "-----BEGIN CERTIFICATE----") {
|
if !strings.HasPrefix(got, "-----BEGIN CERTIFICATE----") {
|
||||||
return fmt.Errorf("key is missing cert PEM preamble")
|
return fmt.Errorf("key is missing cert PEM preamble")
|
||||||
}
|
}
|
||||||
|
|
|
@ -98,7 +98,13 @@ func (c *OutputCommand) Run(args []string) int {
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
c.Ui.Output(v)
|
switch output := v.(type) {
|
||||||
|
case string:
|
||||||
|
c.Ui.Output(output)
|
||||||
|
default:
|
||||||
|
panic(fmt.Errorf("Unknown output type: %T", output))
|
||||||
|
}
|
||||||
|
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -16,7 +16,7 @@ func TestOutput(t *testing.T) {
|
||||||
Modules: []*terraform.ModuleState{
|
Modules: []*terraform.ModuleState{
|
||||||
&terraform.ModuleState{
|
&terraform.ModuleState{
|
||||||
Path: []string{"root"},
|
Path: []string{"root"},
|
||||||
Outputs: map[string]string{
|
Outputs: map[string]interface{}{
|
||||||
"foo": "bar",
|
"foo": "bar",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -52,13 +52,13 @@ func TestModuleOutput(t *testing.T) {
|
||||||
Modules: []*terraform.ModuleState{
|
Modules: []*terraform.ModuleState{
|
||||||
&terraform.ModuleState{
|
&terraform.ModuleState{
|
||||||
Path: []string{"root"},
|
Path: []string{"root"},
|
||||||
Outputs: map[string]string{
|
Outputs: map[string]interface{}{
|
||||||
"foo": "bar",
|
"foo": "bar",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
&terraform.ModuleState{
|
&terraform.ModuleState{
|
||||||
Path: []string{"root", "my_module"},
|
Path: []string{"root", "my_module"},
|
||||||
Outputs: map[string]string{
|
Outputs: map[string]interface{}{
|
||||||
"blah": "tastatur",
|
"blah": "tastatur",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -96,7 +96,7 @@ func TestMissingModuleOutput(t *testing.T) {
|
||||||
Modules: []*terraform.ModuleState{
|
Modules: []*terraform.ModuleState{
|
||||||
&terraform.ModuleState{
|
&terraform.ModuleState{
|
||||||
Path: []string{"root"},
|
Path: []string{"root"},
|
||||||
Outputs: map[string]string{
|
Outputs: map[string]interface{}{
|
||||||
"foo": "bar",
|
"foo": "bar",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -129,7 +129,7 @@ func TestOutput_badVar(t *testing.T) {
|
||||||
Modules: []*terraform.ModuleState{
|
Modules: []*terraform.ModuleState{
|
||||||
&terraform.ModuleState{
|
&terraform.ModuleState{
|
||||||
Path: []string{"root"},
|
Path: []string{"root"},
|
||||||
Outputs: map[string]string{
|
Outputs: map[string]interface{}{
|
||||||
"foo": "bar",
|
"foo": "bar",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -160,7 +160,7 @@ func TestOutput_blank(t *testing.T) {
|
||||||
Modules: []*terraform.ModuleState{
|
Modules: []*terraform.ModuleState{
|
||||||
&terraform.ModuleState{
|
&terraform.ModuleState{
|
||||||
Path: []string{"root"},
|
Path: []string{"root"},
|
||||||
Outputs: map[string]string{
|
Outputs: map[string]interface{}{
|
||||||
"foo": "bar",
|
"foo": "bar",
|
||||||
"name": "john-doe",
|
"name": "john-doe",
|
||||||
},
|
},
|
||||||
|
@ -253,7 +253,7 @@ func TestOutput_noVars(t *testing.T) {
|
||||||
Modules: []*terraform.ModuleState{
|
Modules: []*terraform.ModuleState{
|
||||||
&terraform.ModuleState{
|
&terraform.ModuleState{
|
||||||
Path: []string{"root"},
|
Path: []string{"root"},
|
||||||
Outputs: map[string]string{},
|
Outputs: map[string]interface{}{},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -282,7 +282,7 @@ func TestOutput_stateDefault(t *testing.T) {
|
||||||
Modules: []*terraform.ModuleState{
|
Modules: []*terraform.ModuleState{
|
||||||
&terraform.ModuleState{
|
&terraform.ModuleState{
|
||||||
Path: []string{"root"},
|
Path: []string{"root"},
|
||||||
Outputs: map[string]string{
|
Outputs: map[string]interface{}{
|
||||||
"foo": "bar",
|
"foo": "bar",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -245,7 +245,7 @@ func (f *fakeAtlas) handler(resp http.ResponseWriter, req *http.Request) {
|
||||||
// loads the state.
|
// loads the state.
|
||||||
var testStateModuleOrderChange = []byte(
|
var testStateModuleOrderChange = []byte(
|
||||||
`{
|
`{
|
||||||
"version": 1,
|
"version": 2,
|
||||||
"serial": 1,
|
"serial": 1,
|
||||||
"modules": [
|
"modules": [
|
||||||
{
|
{
|
||||||
|
@ -276,7 +276,7 @@ var testStateModuleOrderChange = []byte(
|
||||||
|
|
||||||
var testStateSimple = []byte(
|
var testStateSimple = []byte(
|
||||||
`{
|
`{
|
||||||
"version": 1,
|
"version": 2,
|
||||||
"serial": 1,
|
"serial": 1,
|
||||||
"modules": [
|
"modules": [
|
||||||
{
|
{
|
||||||
|
|
|
@ -36,7 +36,7 @@ func TestState(t *testing.T, s interface{}) {
|
||||||
if ws, ok := s.(StateWriter); ok {
|
if ws, ok := s.(StateWriter); ok {
|
||||||
current.Modules = append(current.Modules, &terraform.ModuleState{
|
current.Modules = append(current.Modules, &terraform.ModuleState{
|
||||||
Path: []string{"root"},
|
Path: []string{"root"},
|
||||||
Outputs: map[string]string{
|
Outputs: map[string]interface{}{
|
||||||
"bar": "baz",
|
"bar": "baz",
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
@ -94,7 +94,7 @@ func TestState(t *testing.T, s interface{}) {
|
||||||
current.Modules = []*terraform.ModuleState{
|
current.Modules = []*terraform.ModuleState{
|
||||||
&terraform.ModuleState{
|
&terraform.ModuleState{
|
||||||
Path: []string{"root", "somewhere"},
|
Path: []string{"root", "somewhere"},
|
||||||
Outputs: map[string]string{"serialCheck": "true"},
|
Outputs: map[string]interface{}{"serialCheck": "true"},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
if err := writer.WriteState(current); err != nil {
|
if err := writer.WriteState(current); err != nil {
|
||||||
|
@ -123,7 +123,7 @@ func TestStateInitial() *terraform.State {
|
||||||
Modules: []*terraform.ModuleState{
|
Modules: []*terraform.ModuleState{
|
||||||
&terraform.ModuleState{
|
&terraform.ModuleState{
|
||||||
Path: []string{"root", "child"},
|
Path: []string{"root", "child"},
|
||||||
Outputs: map[string]string{
|
Outputs: map[string]interface{}{
|
||||||
"foo": "bar",
|
"foo": "bar",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -969,7 +969,7 @@ func TestContext2Apply_moduleDestroyOrder(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Outputs: map[string]string{
|
Outputs: map[string]interface{}{
|
||||||
"a_output": "a",
|
"a_output": "a",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -1438,7 +1438,7 @@ func TestContext2Apply_outputOrphan(t *testing.T) {
|
||||||
Modules: []*ModuleState{
|
Modules: []*ModuleState{
|
||||||
&ModuleState{
|
&ModuleState{
|
||||||
Path: rootModulePath,
|
Path: rootModulePath,
|
||||||
Outputs: map[string]string{
|
Outputs: map[string]interface{}{
|
||||||
"foo": "bar",
|
"foo": "bar",
|
||||||
"bar": "baz",
|
"bar": "baz",
|
||||||
},
|
},
|
||||||
|
|
|
@ -452,7 +452,7 @@ func TestContext2Refresh_output(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
Outputs: map[string]string{
|
Outputs: map[string]interface{}{
|
||||||
"foo": "foo",
|
"foo": "foo",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -738,7 +738,7 @@ func TestContext2Refresh_orphanModule(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Outputs: map[string]string{
|
Outputs: map[string]interface{}{
|
||||||
"id": "i-bcd234",
|
"id": "i-bcd234",
|
||||||
"grandchild_id": "i-cde345",
|
"grandchild_id": "i-cde345",
|
||||||
},
|
},
|
||||||
|
@ -752,7 +752,7 @@ func TestContext2Refresh_orphanModule(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Outputs: map[string]string{
|
Outputs: map[string]interface{}{
|
||||||
"id": "i-cde345",
|
"id": "i-cde345",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -110,6 +110,25 @@ func (i *Interpolater) valueCountVar(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func interfaceToHILVariable(input interface{}) ast.Variable {
|
||||||
|
switch v := input.(type) {
|
||||||
|
case string:
|
||||||
|
return ast.Variable{
|
||||||
|
Type: ast.TypeString,
|
||||||
|
Value: v,
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
panic(fmt.Errorf("Unknown interface type %T in interfaceToHILVariable", v))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func unknownVariable() ast.Variable {
|
||||||
|
return ast.Variable{
|
||||||
|
Type: ast.TypeString,
|
||||||
|
Value: config.UnknownVariableValue,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (i *Interpolater) valueModuleVar(
|
func (i *Interpolater) valueModuleVar(
|
||||||
scope *InterpolationScope,
|
scope *InterpolationScope,
|
||||||
n string,
|
n string,
|
||||||
|
@ -136,7 +155,6 @@ func (i *Interpolater) valueModuleVar(
|
||||||
defer i.StateLock.RUnlock()
|
defer i.StateLock.RUnlock()
|
||||||
|
|
||||||
// Get the module where we're looking for the value
|
// Get the module where we're looking for the value
|
||||||
var value string
|
|
||||||
mod := i.State.ModuleByPath(path)
|
mod := i.State.ModuleByPath(path)
|
||||||
if mod == nil {
|
if mod == nil {
|
||||||
// If the module doesn't exist, then we can return an empty string.
|
// If the module doesn't exist, then we can return an empty string.
|
||||||
|
@ -145,21 +163,18 @@ func (i *Interpolater) valueModuleVar(
|
||||||
// modules reference other modules, and graph ordering should
|
// modules reference other modules, and graph ordering should
|
||||||
// ensure that the module is in the state, so if we reach this
|
// ensure that the module is in the state, so if we reach this
|
||||||
// point otherwise it really is a panic.
|
// point otherwise it really is a panic.
|
||||||
value = config.UnknownVariableValue
|
result[n] = unknownVariable()
|
||||||
} else {
|
} else {
|
||||||
// Get the value from the outputs
|
// Get the value from the outputs
|
||||||
var ok bool
|
if value, ok := mod.Outputs[v.Field]; ok {
|
||||||
value, ok = mod.Outputs[v.Field]
|
result[n] = interfaceToHILVariable(value)
|
||||||
if !ok {
|
} else {
|
||||||
// Same reasons as the comment above.
|
// Same reasons as the comment above.
|
||||||
value = config.UnknownVariableValue
|
result[n] = unknownVariable()
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
result[n] = ast.Variable{
|
|
||||||
Value: value,
|
|
||||||
Type: ast.TypeString,
|
|
||||||
}
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -67,7 +67,7 @@ func TestInterpolater_moduleVariable(t *testing.T) {
|
||||||
},
|
},
|
||||||
&ModuleState{
|
&ModuleState{
|
||||||
Path: []string{RootModuleName, "child"},
|
Path: []string{RootModuleName, "child"},
|
||||||
Outputs: map[string]string{
|
Outputs: map[string]interface{}{
|
||||||
"foo": "bar",
|
"foo": "bar",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -18,7 +18,7 @@ import (
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// StateVersion is the current version for our state file
|
// StateVersion is the current version for our state file
|
||||||
StateVersion = 1
|
StateVersion = 2
|
||||||
)
|
)
|
||||||
|
|
||||||
// rootModulePath is the path of the root module
|
// rootModulePath is the path of the root module
|
||||||
|
@ -540,7 +540,7 @@ type ModuleState struct {
|
||||||
// Outputs declared by the module and maintained for each module
|
// Outputs declared by the module and maintained for each module
|
||||||
// even though only the root module technically needs to be kept.
|
// even though only the root module technically needs to be kept.
|
||||||
// This allows operators to inspect values at the boundaries.
|
// This allows operators to inspect values at the boundaries.
|
||||||
Outputs map[string]string `json:"outputs"`
|
Outputs map[string]interface{} `json:"outputs"`
|
||||||
|
|
||||||
// Resources is a mapping of the logically named resource to
|
// Resources is a mapping of the logically named resource to
|
||||||
// the state of the resource. Each resource may actually have
|
// the state of the resource. Each resource may actually have
|
||||||
|
@ -665,7 +665,7 @@ func (m *ModuleState) View(id string) *ModuleState {
|
||||||
|
|
||||||
func (m *ModuleState) init() {
|
func (m *ModuleState) init() {
|
||||||
if m.Outputs == nil {
|
if m.Outputs == nil {
|
||||||
m.Outputs = make(map[string]string)
|
m.Outputs = make(map[string]interface{})
|
||||||
}
|
}
|
||||||
if m.Resources == nil {
|
if m.Resources == nil {
|
||||||
m.Resources = make(map[string]*ResourceState)
|
m.Resources = make(map[string]*ResourceState)
|
||||||
|
@ -678,7 +678,7 @@ func (m *ModuleState) deepcopy() *ModuleState {
|
||||||
}
|
}
|
||||||
n := &ModuleState{
|
n := &ModuleState{
|
||||||
Path: make([]string, len(m.Path)),
|
Path: make([]string, len(m.Path)),
|
||||||
Outputs: make(map[string]string, len(m.Outputs)),
|
Outputs: make(map[string]interface{}, len(m.Outputs)),
|
||||||
Resources: make(map[string]*ResourceState, len(m.Resources)),
|
Resources: make(map[string]*ResourceState, len(m.Resources)),
|
||||||
}
|
}
|
||||||
copy(n.Path, m.Path)
|
copy(n.Path, m.Path)
|
||||||
|
@ -1338,7 +1338,8 @@ func ReadState(src io.Reader) (*State, error) {
|
||||||
return upgradeV0State(old)
|
return upgradeV0State(old)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Otherwise, must be V2
|
// Otherwise, must be V2 or V3 - V2 reads as V3 however so we need take
|
||||||
|
// no special action here - new state will be written as V3.
|
||||||
dec := json.NewDecoder(buf)
|
dec := json.NewDecoder(buf)
|
||||||
state := &State{}
|
state := &State{}
|
||||||
if err := dec.Decode(state); err != nil {
|
if err := dec.Decode(state); err != nil {
|
||||||
|
@ -1419,8 +1420,12 @@ func upgradeV0State(old *StateV0) (*State, error) {
|
||||||
// directly into the root module.
|
// directly into the root module.
|
||||||
root := s.RootModule()
|
root := s.RootModule()
|
||||||
|
|
||||||
// Copy the outputs
|
// Copy the outputs, first converting them to map[string]interface{}
|
||||||
root.Outputs = old.Outputs
|
oldOutputs := make(map[string]interface{}, len(old.Outputs))
|
||||||
|
for key, value := range old.Outputs {
|
||||||
|
oldOutputs[key] = value
|
||||||
|
}
|
||||||
|
root.Outputs = oldOutputs
|
||||||
|
|
||||||
// Upgrade the resources
|
// Upgrade the resources
|
||||||
for id, rs := range old.Resources {
|
for id, rs := range old.Resources {
|
||||||
|
|
|
@ -76,6 +76,38 @@ func TestStateAddModule(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestStateOutputTypeRoundTrip(t *testing.T) {
|
||||||
|
state := &State{
|
||||||
|
Modules: []*ModuleState{
|
||||||
|
&ModuleState{
|
||||||
|
Path: RootModulePath,
|
||||||
|
Outputs: map[string]interface{}{
|
||||||
|
"string_output": "String Value",
|
||||||
|
"list_output": []interface{}{"List", "Value"},
|
||||||
|
"map_output": map[string]interface{}{
|
||||||
|
"key1": "Map",
|
||||||
|
"key2": "Value",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
if err := WriteState(state, buf); err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
roundTripped, err := ReadState(buf)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(state, roundTripped) {
|
||||||
|
t.Fatalf("bad: %#v", roundTripped)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestStateModuleOrphans(t *testing.T) {
|
func TestStateModuleOrphans(t *testing.T) {
|
||||||
state := &State{
|
state := &State{
|
||||||
Modules: []*ModuleState{
|
Modules: []*ModuleState{
|
||||||
|
@ -1162,6 +1194,33 @@ func TestInstanceState_MergeDiff_nilDiff(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestReadUpgradeStateV1toV2(t *testing.T) {
|
||||||
|
// ReadState should transparently detect the old version but will upgrade
|
||||||
|
// it on Write.
|
||||||
|
actual, err := ReadState(strings.NewReader(testV1State))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
if err := WriteState(actual, buf); err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if actual.Version != 2 {
|
||||||
|
t.Fatalf("bad: State version not incremented; is %d", actual.Version)
|
||||||
|
}
|
||||||
|
|
||||||
|
roundTripped, err := ReadState(buf)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(actual, roundTripped) {
|
||||||
|
t.Fatalf("bad: %#v", actual)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestReadUpgradeState(t *testing.T) {
|
func TestReadUpgradeState(t *testing.T) {
|
||||||
state := &StateV0{
|
state := &StateV0{
|
||||||
Resources: map[string]*ResourceStateV0{
|
Resources: map[string]*ResourceStateV0{
|
||||||
|
@ -1486,3 +1545,34 @@ func TestParseResourceStateKey(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const testV1State = `{
|
||||||
|
"version": 1,
|
||||||
|
"serial": 9,
|
||||||
|
"remote": {
|
||||||
|
"type": "http",
|
||||||
|
"config": {
|
||||||
|
"url": "http://my-cool-server.com/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"modules": [
|
||||||
|
{
|
||||||
|
"path": [
|
||||||
|
"root"
|
||||||
|
],
|
||||||
|
"outputs": null,
|
||||||
|
"resources": {
|
||||||
|
"foo": {
|
||||||
|
"type": "",
|
||||||
|
"primary": {
|
||||||
|
"id": "bar"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"depends_on": [
|
||||||
|
"aws_instance.bar"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
|
@ -11,7 +11,7 @@ func TestAddOutputOrphanTransformer(t *testing.T) {
|
||||||
Modules: []*ModuleState{
|
Modules: []*ModuleState{
|
||||||
&ModuleState{
|
&ModuleState{
|
||||||
Path: RootModulePath,
|
Path: RootModulePath,
|
||||||
Outputs: map[string]string{
|
Outputs: map[string]interface{}{
|
||||||
"foo": "bar",
|
"foo": "bar",
|
||||||
"bar": "baz",
|
"bar": "baz",
|
||||||
},
|
},
|
||||||
|
|
Loading…
Reference in New Issue