Merge pull request #6752 from hashicorp/f-output-state-0.7
core: Forward port OutputState to 0.7
This commit is contained in:
commit
17442abff2
|
@ -36,7 +36,7 @@ func TestTemplateRendering(t *testing.T) {
|
|||
Config: testTemplateConfig(tt.template, tt.vars),
|
||||
Check: func(s *terraform.State) error {
|
||||
got := s.RootModule().Outputs["rendered"]
|
||||
if tt.want != got {
|
||||
if tt.want != got.Value {
|
||||
return fmt.Errorf("template:\n%s\nvars:\n%s\ngot:\n%s\nwant:\n%s\n", tt.template, tt.vars, got, tt.want)
|
||||
}
|
||||
return nil
|
||||
|
@ -65,7 +65,7 @@ func TestTemplateVariableChange(t *testing.T) {
|
|||
Check: func(i int, want string) r.TestCheckFunc {
|
||||
return func(s *terraform.State) error {
|
||||
got := s.RootModule().Outputs["rendered"]
|
||||
if want != got {
|
||||
if want != got.Value {
|
||||
return fmt.Errorf("[%d] got:\n%q\nwant:\n%q\n", i, got, want)
|
||||
}
|
||||
return nil
|
||||
|
|
|
@ -54,7 +54,11 @@ func dataSourceRemoteStateRead(d *schema.ResourceData, meta interface{}) error {
|
|||
|
||||
var outputs map[string]interface{}
|
||||
if !state.State().Empty() {
|
||||
outputs = state.State().RootModule().Outputs
|
||||
outputValueMap := make(map[string]string)
|
||||
for key, output := range state.State().RootModule().Outputs {
|
||||
//This is ok for 0.6.17 as outputs will have been strings
|
||||
outputValueMap[key] = output.Value.(string)
|
||||
}
|
||||
}
|
||||
|
||||
d.SetId(time.Now().UTC().String())
|
||||
|
|
|
@ -50,7 +50,7 @@ EOT
|
|||
}
|
||||
`, testPrivateKey),
|
||||
Check: func(s *terraform.State) error {
|
||||
gotUntyped := s.RootModule().Outputs["key_pem"]
|
||||
gotUntyped := s.RootModule().Outputs["key_pem"].Value
|
||||
|
||||
got, ok := gotUntyped.(string)
|
||||
if !ok {
|
||||
|
|
|
@ -47,7 +47,7 @@ EOT
|
|||
}
|
||||
`, testCertRequest, testCACert, testCAPrivateKey),
|
||||
Check: func(s *terraform.State) error {
|
||||
gotUntyped := s.RootModule().Outputs["cert_pem"]
|
||||
gotUntyped := s.RootModule().Outputs["cert_pem"].Value
|
||||
got, ok := gotUntyped.(string)
|
||||
if !ok {
|
||||
return fmt.Errorf("output for \"cert_pem\" is not a string")
|
||||
|
|
|
@ -29,7 +29,7 @@ func TestPrivateKeyRSA(t *testing.T) {
|
|||
}
|
||||
`,
|
||||
Check: func(s *terraform.State) error {
|
||||
gotPrivateUntyped := s.RootModule().Outputs["private_key_pem"]
|
||||
gotPrivateUntyped := s.RootModule().Outputs["private_key_pem"].Value
|
||||
gotPrivate, ok := gotPrivateUntyped.(string)
|
||||
if !ok {
|
||||
return fmt.Errorf("output for \"private_key_pem\" is not a string")
|
||||
|
@ -42,7 +42,7 @@ func TestPrivateKeyRSA(t *testing.T) {
|
|||
return fmt.Errorf("private key PEM looks too long for a 2048-bit key (got %v characters)", len(gotPrivate))
|
||||
}
|
||||
|
||||
gotPublicUntyped := s.RootModule().Outputs["public_key_pem"]
|
||||
gotPublicUntyped := s.RootModule().Outputs["public_key_pem"].Value
|
||||
gotPublic, ok := gotPublicUntyped.(string)
|
||||
if !ok {
|
||||
return fmt.Errorf("output for \"public_key_pem\" is not a string")
|
||||
|
@ -51,7 +51,7 @@ func TestPrivateKeyRSA(t *testing.T) {
|
|||
return fmt.Errorf("public key is missing public key PEM preamble")
|
||||
}
|
||||
|
||||
gotPublicSSHUntyped := s.RootModule().Outputs["public_key_openssh"]
|
||||
gotPublicSSHUntyped := s.RootModule().Outputs["public_key_openssh"].Value
|
||||
gotPublicSSH, ok := gotPublicSSHUntyped.(string)
|
||||
if !ok {
|
||||
return fmt.Errorf("output for \"public_key_openssh\" is not a string")
|
||||
|
@ -74,7 +74,7 @@ func TestPrivateKeyRSA(t *testing.T) {
|
|||
}
|
||||
`,
|
||||
Check: func(s *terraform.State) error {
|
||||
gotUntyped := s.RootModule().Outputs["key_pem"]
|
||||
gotUntyped := s.RootModule().Outputs["key_pem"].Value
|
||||
got, ok := gotUntyped.(string)
|
||||
if !ok {
|
||||
return fmt.Errorf("output for \"key_pem\" is not a string")
|
||||
|
@ -112,7 +112,7 @@ func TestPrivateKeyECDSA(t *testing.T) {
|
|||
}
|
||||
`,
|
||||
Check: func(s *terraform.State) error {
|
||||
gotPrivateUntyped := s.RootModule().Outputs["private_key_pem"]
|
||||
gotPrivateUntyped := s.RootModule().Outputs["private_key_pem"].Value
|
||||
gotPrivate, ok := gotPrivateUntyped.(string)
|
||||
if !ok {
|
||||
return fmt.Errorf("output for \"private_key_pem\" is not a string")
|
||||
|
@ -122,7 +122,7 @@ func TestPrivateKeyECDSA(t *testing.T) {
|
|||
return fmt.Errorf("Private key is missing EC key PEM preamble")
|
||||
}
|
||||
|
||||
gotPublicUntyped := s.RootModule().Outputs["public_key_pem"]
|
||||
gotPublicUntyped := s.RootModule().Outputs["public_key_pem"].Value
|
||||
gotPublic, ok := gotPublicUntyped.(string)
|
||||
if !ok {
|
||||
return fmt.Errorf("output for \"public_key_pem\" is not a string")
|
||||
|
@ -132,7 +132,7 @@ func TestPrivateKeyECDSA(t *testing.T) {
|
|||
return fmt.Errorf("public key is missing public key PEM preamble")
|
||||
}
|
||||
|
||||
gotPublicSSH := s.RootModule().Outputs["public_key_openssh"]
|
||||
gotPublicSSH := s.RootModule().Outputs["public_key_openssh"].Value.(string)
|
||||
if gotPublicSSH != "" {
|
||||
return fmt.Errorf("P224 EC key should not generate OpenSSH public key")
|
||||
}
|
||||
|
@ -157,7 +157,7 @@ func TestPrivateKeyECDSA(t *testing.T) {
|
|||
}
|
||||
`,
|
||||
Check: func(s *terraform.State) error {
|
||||
gotPrivateUntyped := s.RootModule().Outputs["private_key_pem"]
|
||||
gotPrivateUntyped := s.RootModule().Outputs["private_key_pem"].Value
|
||||
gotPrivate, ok := gotPrivateUntyped.(string)
|
||||
if !ok {
|
||||
return fmt.Errorf("output for \"private_key_pem\" is not a string")
|
||||
|
@ -166,7 +166,7 @@ func TestPrivateKeyECDSA(t *testing.T) {
|
|||
return fmt.Errorf("Private key is missing EC key PEM preamble")
|
||||
}
|
||||
|
||||
gotPublicUntyped := s.RootModule().Outputs["public_key_pem"]
|
||||
gotPublicUntyped := s.RootModule().Outputs["public_key_pem"].Value
|
||||
gotPublic, ok := gotPublicUntyped.(string)
|
||||
if !ok {
|
||||
return fmt.Errorf("output for \"public_key_pem\" is not a string")
|
||||
|
@ -175,7 +175,7 @@ func TestPrivateKeyECDSA(t *testing.T) {
|
|||
return fmt.Errorf("public key is missing public key PEM preamble")
|
||||
}
|
||||
|
||||
gotPublicSSHUntyped := s.RootModule().Outputs["public_key_openssh"]
|
||||
gotPublicSSHUntyped := s.RootModule().Outputs["public_key_openssh"].Value
|
||||
gotPublicSSH, ok := gotPublicSSHUntyped.(string)
|
||||
if !ok {
|
||||
return fmt.Errorf("output for \"public_key_openssh\" is not a string")
|
||||
|
|
|
@ -60,7 +60,7 @@ EOT
|
|||
}
|
||||
`, testPrivateKey),
|
||||
Check: func(s *terraform.State) error {
|
||||
gotUntyped := s.RootModule().Outputs["key_pem"]
|
||||
gotUntyped := s.RootModule().Outputs["key_pem"].Value
|
||||
got, ok := gotUntyped.(string)
|
||||
if !ok {
|
||||
return fmt.Errorf("output for \"public_key_openssh\" is not a string")
|
||||
|
|
|
@ -415,7 +415,7 @@ func outputsAsString(state *terraform.State, schema []*config.Output, includeHea
|
|||
}
|
||||
|
||||
v := outputs[k]
|
||||
switch typedV := v.(type) {
|
||||
switch typedV := v.Value.(type) {
|
||||
case string:
|
||||
outputBuf.WriteString(fmt.Sprintf("%s = %s\n", k, typedV))
|
||||
case []interface{}:
|
||||
|
|
|
@ -116,6 +116,7 @@ func testReadPlan(t *testing.T, path string) *terraform.Plan {
|
|||
// testState returns a test State structure that we use for a lot of tests.
|
||||
func testState() *terraform.State {
|
||||
return &terraform.State{
|
||||
Version: 2,
|
||||
Modules: []*terraform.ModuleState{
|
||||
&terraform.ModuleState{
|
||||
Path: []string{"root"},
|
||||
|
|
|
@ -95,7 +95,7 @@ func (c *OutputCommand) Run(args []string) int {
|
|||
return 1
|
||||
}
|
||||
|
||||
switch output := v.(type) {
|
||||
switch output := v.Value.(type) {
|
||||
case string:
|
||||
c.Ui.Output(output)
|
||||
return 0
|
||||
|
@ -137,7 +137,8 @@ func (c *OutputCommand) Run(args []string) int {
|
|||
return 1
|
||||
}
|
||||
default:
|
||||
panic(fmt.Errorf("Unknown output type: %T", output))
|
||||
c.Ui.Error(fmt.Sprintf("Unknown output type: %T", v.Type))
|
||||
return 1
|
||||
}
|
||||
|
||||
return 0
|
||||
|
|
|
@ -16,8 +16,11 @@ func TestOutput(t *testing.T) {
|
|||
Modules: []*terraform.ModuleState{
|
||||
&terraform.ModuleState{
|
||||
Path: []string{"root"},
|
||||
Outputs: map[string]interface{}{
|
||||
"foo": "bar",
|
||||
Outputs: map[string]*terraform.OutputState{
|
||||
"foo": &terraform.OutputState{
|
||||
Value: "bar",
|
||||
Type: "string",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -52,14 +55,20 @@ func TestModuleOutput(t *testing.T) {
|
|||
Modules: []*terraform.ModuleState{
|
||||
&terraform.ModuleState{
|
||||
Path: []string{"root"},
|
||||
Outputs: map[string]interface{}{
|
||||
"foo": "bar",
|
||||
Outputs: map[string]*terraform.OutputState{
|
||||
"foo": &terraform.OutputState{
|
||||
Value: "bar",
|
||||
Type: "string",
|
||||
},
|
||||
},
|
||||
},
|
||||
&terraform.ModuleState{
|
||||
Path: []string{"root", "my_module"},
|
||||
Outputs: map[string]interface{}{
|
||||
"blah": "tastatur",
|
||||
Outputs: map[string]*terraform.OutputState{
|
||||
"blah": &terraform.OutputState{
|
||||
Value: "tastatur",
|
||||
Type: "string",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -96,8 +105,11 @@ func TestMissingModuleOutput(t *testing.T) {
|
|||
Modules: []*terraform.ModuleState{
|
||||
&terraform.ModuleState{
|
||||
Path: []string{"root"},
|
||||
Outputs: map[string]interface{}{
|
||||
"foo": "bar",
|
||||
Outputs: map[string]*terraform.OutputState{
|
||||
"foo": &terraform.OutputState{
|
||||
Value: "bar",
|
||||
Type: "string",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -129,8 +141,11 @@ func TestOutput_badVar(t *testing.T) {
|
|||
Modules: []*terraform.ModuleState{
|
||||
&terraform.ModuleState{
|
||||
Path: []string{"root"},
|
||||
Outputs: map[string]interface{}{
|
||||
"foo": "bar",
|
||||
Outputs: map[string]*terraform.OutputState{
|
||||
"foo": &terraform.OutputState{
|
||||
Value: "bar",
|
||||
Type: "string",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -160,9 +175,15 @@ func TestOutput_blank(t *testing.T) {
|
|||
Modules: []*terraform.ModuleState{
|
||||
&terraform.ModuleState{
|
||||
Path: []string{"root"},
|
||||
Outputs: map[string]interface{}{
|
||||
"foo": "bar",
|
||||
"name": "john-doe",
|
||||
Outputs: map[string]*terraform.OutputState{
|
||||
"foo": &terraform.OutputState{
|
||||
Value: "bar",
|
||||
Type: "string",
|
||||
},
|
||||
"name": &terraform.OutputState{
|
||||
Value: "john-doe",
|
||||
Type: "string",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -253,7 +274,7 @@ func TestOutput_noVars(t *testing.T) {
|
|||
Modules: []*terraform.ModuleState{
|
||||
&terraform.ModuleState{
|
||||
Path: []string{"root"},
|
||||
Outputs: map[string]interface{}{},
|
||||
Outputs: map[string]*terraform.OutputState{},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
@ -282,8 +303,11 @@ func TestOutput_stateDefault(t *testing.T) {
|
|||
Modules: []*terraform.ModuleState{
|
||||
&terraform.ModuleState{
|
||||
Path: []string{"root"},
|
||||
Outputs: map[string]interface{}{
|
||||
"foo": "bar",
|
||||
Outputs: map[string]*terraform.OutputState{
|
||||
"foo": &terraform.OutputState{
|
||||
Value: "bar",
|
||||
Type: "string",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
|
@ -565,7 +565,7 @@ func TestCheckOutput(name, value string) TestCheckFunc {
|
|||
return fmt.Errorf("Not found: %s", name)
|
||||
}
|
||||
|
||||
if rs != value {
|
||||
if rs.Value != value {
|
||||
return fmt.Errorf(
|
||||
"Output '%s': expected %#v, got %#v",
|
||||
name,
|
||||
|
|
|
@ -322,6 +322,7 @@ func (c *AtlasClient) handleConflict(msg string, state []byte) error {
|
|||
var buf bytes.Buffer
|
||||
if err := terraform.WriteState(proposedState, &buf); err != nil {
|
||||
return conflictHandlingError(err)
|
||||
|
||||
}
|
||||
return c.Put(buf.Bytes())
|
||||
} else {
|
||||
|
|
|
@ -87,6 +87,7 @@ func TestAtlasClient_NoConflict(t *testing.T) {
|
|||
if err := terraform.WriteState(state, &stateJson); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if err := client.Put(stateJson.Bytes()); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
@ -111,7 +112,11 @@ func TestAtlasClient_LegitimateConflict(t *testing.T) {
|
|||
}
|
||||
|
||||
// Changing the state but not the serial. Should generate a conflict.
|
||||
state.RootModule().Outputs["drift"] = "happens"
|
||||
state.RootModule().Outputs["drift"] = &terraform.OutputState{
|
||||
Type: "string",
|
||||
Sensitive: false,
|
||||
Value: "happens",
|
||||
}
|
||||
|
||||
var stateJson bytes.Buffer
|
||||
if err := terraform.WriteState(state, &stateJson); err != nil {
|
||||
|
@ -255,7 +260,11 @@ var testStateModuleOrderChange = []byte(
|
|||
"grandchild"
|
||||
],
|
||||
"outputs": {
|
||||
"foo": "bar2"
|
||||
"foo": {
|
||||
"sensitive": false,
|
||||
"type": "string",
|
||||
"value": "bar"
|
||||
}
|
||||
},
|
||||
"resources": null
|
||||
},
|
||||
|
@ -266,7 +275,11 @@ var testStateModuleOrderChange = []byte(
|
|||
"grandchild"
|
||||
],
|
||||
"outputs": {
|
||||
"foo": "bar1"
|
||||
"foo": {
|
||||
"sensitive": false,
|
||||
"type": "string",
|
||||
"value": "bar"
|
||||
}
|
||||
},
|
||||
"resources": null
|
||||
}
|
||||
|
@ -284,7 +297,11 @@ var testStateSimple = []byte(
|
|||
"root"
|
||||
],
|
||||
"outputs": {
|
||||
"foo": "bar"
|
||||
"foo": {
|
||||
"sensitive": false,
|
||||
"type": "string",
|
||||
"value": "bar"
|
||||
}
|
||||
},
|
||||
"resources": null
|
||||
}
|
||||
|
|
|
@ -36,8 +36,12 @@ func TestState(t *testing.T, s interface{}) {
|
|||
if ws, ok := s.(StateWriter); ok {
|
||||
current.Modules = append(current.Modules, &terraform.ModuleState{
|
||||
Path: []string{"root"},
|
||||
Outputs: map[string]interface{}{
|
||||
"bar": "baz",
|
||||
Outputs: map[string]*terraform.OutputState{
|
||||
"bar": &terraform.OutputState{
|
||||
Type: "string",
|
||||
Sensitive: false,
|
||||
Value: "baz",
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
|
@ -93,8 +97,14 @@ func TestState(t *testing.T, s interface{}) {
|
|||
current = ¤tCopy
|
||||
current.Modules = []*terraform.ModuleState{
|
||||
&terraform.ModuleState{
|
||||
Path: []string{"root", "somewhere"},
|
||||
Outputs: map[string]interface{}{"serialCheck": "true"},
|
||||
Path: []string{"root", "somewhere"},
|
||||
Outputs: map[string]*terraform.OutputState{
|
||||
"serialCheck": &terraform.OutputState{
|
||||
Type: "string",
|
||||
Sensitive: false,
|
||||
Value: "true",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
if err := writer.WriteState(current); err != nil {
|
||||
|
@ -123,8 +133,12 @@ func TestStateInitial() *terraform.State {
|
|||
Modules: []*terraform.ModuleState{
|
||||
&terraform.ModuleState{
|
||||
Path: []string{"root", "child"},
|
||||
Outputs: map[string]interface{}{
|
||||
"foo": "bar",
|
||||
Outputs: map[string]*terraform.OutputState{
|
||||
"foo": &terraform.OutputState{
|
||||
Type: "string",
|
||||
Sensitive: false,
|
||||
Value: "bar",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
|
@ -1008,8 +1008,12 @@ func TestContext2Apply_moduleDestroyOrder(t *testing.T) {
|
|||
},
|
||||
},
|
||||
},
|
||||
Outputs: map[string]interface{}{
|
||||
"a_output": "a",
|
||||
Outputs: map[string]*OutputState{
|
||||
"a_output": &OutputState{
|
||||
Type: "string",
|
||||
Sensitive: false,
|
||||
Value: "a",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -1408,7 +1412,7 @@ func TestContext2Apply_multiVar(t *testing.T) {
|
|||
|
||||
actual := state.RootModule().Outputs["output"]
|
||||
expected := "bar0,bar1,bar2"
|
||||
if actual != expected {
|
||||
if actual.Value != expected {
|
||||
t.Fatalf("bad: \n%s", actual)
|
||||
}
|
||||
|
||||
|
@ -1436,7 +1440,7 @@ func TestContext2Apply_multiVar(t *testing.T) {
|
|||
|
||||
actual := state.RootModule().Outputs["output"]
|
||||
expected := "bar0"
|
||||
if actual != expected {
|
||||
if actual.Value != expected {
|
||||
t.Fatalf("bad: \n%s", actual)
|
||||
}
|
||||
}
|
||||
|
@ -1477,9 +1481,17 @@ func TestContext2Apply_outputOrphan(t *testing.T) {
|
|||
Modules: []*ModuleState{
|
||||
&ModuleState{
|
||||
Path: rootModulePath,
|
||||
Outputs: map[string]interface{}{
|
||||
"foo": "bar",
|
||||
"bar": "baz",
|
||||
Outputs: map[string]*OutputState{
|
||||
"foo": &OutputState{
|
||||
Type: "string",
|
||||
Sensitive: false,
|
||||
Value: "bar",
|
||||
},
|
||||
"bar": &OutputState{
|
||||
Type: "string",
|
||||
Sensitive: false,
|
||||
Value: "baz",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
|
@ -452,8 +452,12 @@ func TestContext2Refresh_output(t *testing.T) {
|
|||
},
|
||||
},
|
||||
|
||||
Outputs: map[string]interface{}{
|
||||
"foo": "foo",
|
||||
Outputs: map[string]*OutputState{
|
||||
"foo": &OutputState{
|
||||
Type: "string",
|
||||
Sensitive: false,
|
||||
Value: "foo",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -738,9 +742,15 @@ func TestContext2Refresh_orphanModule(t *testing.T) {
|
|||
},
|
||||
},
|
||||
},
|
||||
Outputs: map[string]interface{}{
|
||||
"id": "i-bcd234",
|
||||
"grandchild_id": "i-cde345",
|
||||
Outputs: map[string]*OutputState{
|
||||
"id": &OutputState{
|
||||
Value: "i-bcd234",
|
||||
Type: "string",
|
||||
},
|
||||
"grandchild_id": &OutputState{
|
||||
Value: "i-cde345",
|
||||
Type: "string",
|
||||
},
|
||||
},
|
||||
},
|
||||
&ModuleState{
|
||||
|
@ -752,8 +762,11 @@ func TestContext2Refresh_orphanModule(t *testing.T) {
|
|||
},
|
||||
},
|
||||
},
|
||||
Outputs: map[string]interface{}{
|
||||
"id": "i-cde345",
|
||||
Outputs: map[string]*OutputState{
|
||||
"id": &OutputState{
|
||||
Value: "i-cde345",
|
||||
Type: "string",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
|
@ -38,8 +38,9 @@ func (n *EvalDeleteOutput) Eval(ctx EvalContext) (interface{}, error) {
|
|||
// EvalWriteOutput is an EvalNode implementation that writes the output
|
||||
// for the given name to the current state.
|
||||
type EvalWriteOutput struct {
|
||||
Name string
|
||||
Value *config.RawConfig
|
||||
Name string
|
||||
Sensitive bool
|
||||
Value *config.RawConfig
|
||||
}
|
||||
|
||||
// TODO: test
|
||||
|
@ -80,11 +81,23 @@ func (n *EvalWriteOutput) Eval(ctx EvalContext) (interface{}, error) {
|
|||
|
||||
switch valueTyped := valueRaw.(type) {
|
||||
case string:
|
||||
mod.Outputs[n.Name] = valueTyped
|
||||
mod.Outputs[n.Name] = &OutputState{
|
||||
Type: "string",
|
||||
Sensitive: n.Sensitive,
|
||||
Value: valueTyped,
|
||||
}
|
||||
case []interface{}:
|
||||
mod.Outputs[n.Name] = valueTyped
|
||||
mod.Outputs[n.Name] = &OutputState{
|
||||
Type: "list",
|
||||
Sensitive: n.Sensitive,
|
||||
Value: valueTyped,
|
||||
}
|
||||
case map[string]interface{}:
|
||||
mod.Outputs[n.Name] = valueTyped
|
||||
mod.Outputs[n.Name] = &OutputState{
|
||||
Type: "map",
|
||||
Sensitive: n.Sensitive,
|
||||
Value: valueTyped,
|
||||
}
|
||||
default:
|
||||
return nil, fmt.Errorf("output %s is not a valid type (%T)\n", n.Name, valueTyped)
|
||||
}
|
||||
|
|
|
@ -48,8 +48,9 @@ func (n *GraphNodeConfigOutput) EvalTree() EvalNode {
|
|||
Node: &EvalSequence{
|
||||
Nodes: []EvalNode{
|
||||
&EvalWriteOutput{
|
||||
Name: n.Output.Name,
|
||||
Value: n.Output.RawConfig,
|
||||
Name: n.Output.Name,
|
||||
Sensitive: n.Output.Sensitive,
|
||||
Value: n.Output.RawConfig,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
|
@ -161,8 +161,8 @@ func (i *Interpolater) valueModuleVar(
|
|||
result[n] = unknownVariable()
|
||||
} else {
|
||||
// Get the value from the outputs
|
||||
if value, ok := mod.Outputs[v.Field]; ok {
|
||||
output, err := hil.InterfaceToVariable(value)
|
||||
if outputState, ok := mod.Outputs[v.Field]; ok {
|
||||
output, err := hil.InterfaceToVariable(outputState.Value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -68,8 +68,11 @@ func TestInterpolater_moduleVariable(t *testing.T) {
|
|||
},
|
||||
&ModuleState{
|
||||
Path: []string{RootModuleName, "child"},
|
||||
Outputs: map[string]interface{}{
|
||||
"foo": "bar",
|
||||
Outputs: map[string]*OutputState{
|
||||
"foo": &OutputState{
|
||||
Type: "string",
|
||||
Value: "bar",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
|
@ -6,7 +6,7 @@ import (
|
|||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"io/ioutil"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strconv"
|
||||
|
@ -14,6 +14,7 @@ import (
|
|||
|
||||
"github.com/hashicorp/go-version"
|
||||
"github.com/hashicorp/terraform/config"
|
||||
"github.com/mitchellh/copystructure"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -547,6 +548,69 @@ func (r *RemoteState) GoString() string {
|
|||
return fmt.Sprintf("*%#v", *r)
|
||||
}
|
||||
|
||||
// OutputState is used to track the state relevant to a single output.
|
||||
type OutputState struct {
|
||||
// Sensitive describes whether the output is considered sensitive,
|
||||
// which may lead to masking the value on screen in some cases.
|
||||
Sensitive bool `json:"sensitive"`
|
||||
// Type describes the structure of Value. Valid values are "string",
|
||||
// "map" and "list"
|
||||
Type string `json:"type"`
|
||||
// Value contains the value of the output, in the structure described
|
||||
// by the Type field.
|
||||
Value interface{} `json:"value"`
|
||||
}
|
||||
|
||||
func (s *OutputState) String() string {
|
||||
// This is a v0.6.x implementation only
|
||||
return fmt.Sprintf("%s", s.Value.(string))
|
||||
}
|
||||
|
||||
// Equal compares two OutputState structures for equality. nil values are
|
||||
// considered equal.
|
||||
func (s *OutputState) Equal(other *OutputState) bool {
|
||||
if s == nil && other == nil {
|
||||
return true
|
||||
}
|
||||
|
||||
if s == nil || other == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if s.Type != other.Type {
|
||||
return false
|
||||
}
|
||||
|
||||
if s.Sensitive != other.Sensitive {
|
||||
return false
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(s.Value, other.Value) {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func (s *OutputState) deepcopy() *OutputState {
|
||||
if s == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
valueCopy, err := copystructure.Copy(s.Value)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("Error copying output value: %s", err))
|
||||
}
|
||||
|
||||
n := &OutputState{
|
||||
Type: s.Type,
|
||||
Sensitive: s.Sensitive,
|
||||
Value: valueCopy,
|
||||
}
|
||||
|
||||
return n
|
||||
}
|
||||
|
||||
// ModuleState is used to track all the state relevant to a single
|
||||
// module. Previous to Terraform 0.3, all state belonged to the "root"
|
||||
// module.
|
||||
|
@ -558,7 +622,7 @@ type ModuleState struct {
|
|||
// Outputs declared by the module and maintained for each module
|
||||
// even though only the root module technically needs to be kept.
|
||||
// This allows operators to inspect values at the boundaries.
|
||||
Outputs map[string]interface{} `json:"outputs"`
|
||||
Outputs map[string]*OutputState `json:"outputs"`
|
||||
|
||||
// Resources is a mapping of the logically named resource to
|
||||
// the state of the resource. Each resource may actually have
|
||||
|
@ -593,7 +657,7 @@ func (m *ModuleState) Equal(other *ModuleState) bool {
|
|||
return false
|
||||
}
|
||||
for k, v := range m.Outputs {
|
||||
if !reflect.DeepEqual(other.Outputs[k], v) {
|
||||
if !other.Outputs[k].Equal(v) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
@ -683,7 +747,7 @@ func (m *ModuleState) View(id string) *ModuleState {
|
|||
|
||||
func (m *ModuleState) init() {
|
||||
if m.Outputs == nil {
|
||||
m.Outputs = make(map[string]interface{})
|
||||
m.Outputs = make(map[string]*OutputState)
|
||||
}
|
||||
if m.Resources == nil {
|
||||
m.Resources = make(map[string]*ResourceState)
|
||||
|
@ -696,14 +760,14 @@ func (m *ModuleState) deepcopy() *ModuleState {
|
|||
}
|
||||
n := &ModuleState{
|
||||
Path: make([]string, len(m.Path)),
|
||||
Outputs: make(map[string]interface{}, len(m.Outputs)),
|
||||
Outputs: make(map[string]*OutputState, len(m.Outputs)),
|
||||
Resources: make(map[string]*ResourceState, len(m.Resources)),
|
||||
Dependencies: make([]string, len(m.Dependencies)),
|
||||
}
|
||||
copy(n.Path, m.Path)
|
||||
copy(n.Dependencies, m.Dependencies)
|
||||
for k, v := range m.Outputs {
|
||||
n.Outputs[k] = v
|
||||
n.Outputs[k] = v.deepcopy()
|
||||
}
|
||||
for k, v := range m.Resources {
|
||||
n.Resources[k] = v.deepcopy()
|
||||
|
@ -722,7 +786,7 @@ func (m *ModuleState) prune() {
|
|||
}
|
||||
|
||||
for k, v := range m.Outputs {
|
||||
if v == config.UnknownVariableValue {
|
||||
if v.Value == config.UnknownVariableValue {
|
||||
delete(m.Outputs, k)
|
||||
}
|
||||
}
|
||||
|
@ -823,7 +887,7 @@ func (m *ModuleState) String() string {
|
|||
|
||||
for _, k := range ks {
|
||||
v := m.Outputs[k]
|
||||
switch vTyped := v.(type) {
|
||||
switch vTyped := v.Value.(type) {
|
||||
case string:
|
||||
buf.WriteString(fmt.Sprintf("%s = %s\n", k, vTyped))
|
||||
case []interface{}:
|
||||
|
@ -1386,6 +1450,10 @@ func (e *EphemeralState) DeepCopy() *EphemeralState {
|
|||
return n
|
||||
}
|
||||
|
||||
type jsonStateVersionIdentifier struct {
|
||||
Version int `json:"version"`
|
||||
}
|
||||
|
||||
// ReadState reads a state structure out of a reader in the format that
|
||||
// was written by WriteState.
|
||||
func ReadState(src io.Reader) (*State, error) {
|
||||
|
@ -1402,14 +1470,59 @@ func ReadState(src io.Reader) (*State, error) {
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return upgradeV0State(old)
|
||||
return old.upgrade()
|
||||
}
|
||||
|
||||
// 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)
|
||||
// If we are JSON we buffer the whole thing in memory so we can read it twice.
|
||||
// This is suboptimal, but will work for now.
|
||||
jsonBytes, err := ioutil.ReadAll(buf)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Reading state file failed: %v", err)
|
||||
}
|
||||
|
||||
versionIdentifier := &jsonStateVersionIdentifier{}
|
||||
if err := json.Unmarshal(jsonBytes, versionIdentifier); err != nil {
|
||||
return nil, fmt.Errorf("Decoding state file version failed: %v", err)
|
||||
}
|
||||
|
||||
switch versionIdentifier.Version {
|
||||
case 0:
|
||||
return nil, fmt.Errorf("State version 0 is not supported as JSON.")
|
||||
case 1:
|
||||
old, err := ReadStateV1(jsonBytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return old.upgrade()
|
||||
case 2:
|
||||
state, err := ReadStateV2(jsonBytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return state, nil
|
||||
default:
|
||||
return nil, fmt.Errorf("State version %d not supported, please update.",
|
||||
versionIdentifier.Version)
|
||||
}
|
||||
}
|
||||
|
||||
func ReadStateV1(jsonBytes []byte) (*stateV1, error) {
|
||||
state := &stateV1{}
|
||||
if err := json.Unmarshal(jsonBytes, state); err != nil {
|
||||
return nil, fmt.Errorf("Decoding state file failed: %v", err)
|
||||
}
|
||||
|
||||
if state.Version != 1 {
|
||||
return nil, fmt.Errorf("Decoded state version did not match the decoder selection: "+
|
||||
"read %d, expected 1", state.Version)
|
||||
}
|
||||
|
||||
return state, nil
|
||||
}
|
||||
|
||||
func ReadStateV2(jsonBytes []byte) (*State, error) {
|
||||
state := &State{}
|
||||
if err := dec.Decode(state); err != nil {
|
||||
if err := json.Unmarshal(jsonBytes, state); err != nil {
|
||||
return nil, fmt.Errorf("Decoding state file failed: %v", err)
|
||||
}
|
||||
|
||||
|
@ -1477,56 +1590,6 @@ func WriteState(d *State, dst io.Writer) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// upgradeV0State is used to upgrade a V0 state representation
|
||||
// into a proper State representation.
|
||||
func upgradeV0State(old *StateV0) (*State, error) {
|
||||
s := &State{}
|
||||
s.init()
|
||||
|
||||
// Old format had no modules, so we migrate everything
|
||||
// directly into the root module.
|
||||
root := s.RootModule()
|
||||
|
||||
// Copy the outputs, first converting them to map[string]interface{}
|
||||
oldOutputs := make(map[string]interface{}, len(old.Outputs))
|
||||
for key, value := range old.Outputs {
|
||||
oldOutputs[key] = value
|
||||
}
|
||||
root.Outputs = oldOutputs
|
||||
|
||||
// Upgrade the resources
|
||||
for id, rs := range old.Resources {
|
||||
newRs := &ResourceState{
|
||||
Type: rs.Type,
|
||||
}
|
||||
root.Resources[id] = newRs
|
||||
|
||||
// Migrate to an instance state
|
||||
instance := &InstanceState{
|
||||
ID: rs.ID,
|
||||
Attributes: rs.Attributes,
|
||||
}
|
||||
|
||||
// Check if this is the primary or tainted instance
|
||||
if _, ok := old.Tainted[id]; ok {
|
||||
newRs.Tainted = append(newRs.Tainted, instance)
|
||||
} else {
|
||||
newRs.Primary = instance
|
||||
}
|
||||
|
||||
// Warn if the resource uses Extra, as there is
|
||||
// no upgrade path for this! Now totally deprecated.
|
||||
if len(rs.Extra) > 0 {
|
||||
log.Printf(
|
||||
"[WARN] Resource %s uses deprecated attribute "+
|
||||
"storage, state file upgrade may be incomplete.",
|
||||
rs.ID,
|
||||
)
|
||||
}
|
||||
}
|
||||
return s, nil
|
||||
}
|
||||
|
||||
// moduleStateSort implements sort.Interface to sort module states
|
||||
type moduleStateSort []*ModuleState
|
||||
|
||||
|
|
|
@ -113,8 +113,12 @@ func TestStateAdd(t *testing.T) {
|
|||
"module.foo",
|
||||
&ModuleState{
|
||||
Path: rootModulePath,
|
||||
Outputs: map[string]interface{}{
|
||||
"foo": "bar",
|
||||
Outputs: map[string]*OutputState{
|
||||
"foo": &OutputState{
|
||||
Type: "string",
|
||||
Sensitive: false,
|
||||
Value: "bar",
|
||||
},
|
||||
},
|
||||
Dependencies: []string{"foo"},
|
||||
Resources: map[string]*ResourceState{
|
||||
|
@ -139,8 +143,12 @@ func TestStateAdd(t *testing.T) {
|
|||
Modules: []*ModuleState{
|
||||
&ModuleState{
|
||||
Path: []string{"root", "foo"},
|
||||
Outputs: map[string]interface{}{
|
||||
"foo": "bar",
|
||||
Outputs: map[string]*OutputState{
|
||||
"foo": &OutputState{
|
||||
Type: "string",
|
||||
Sensitive: false,
|
||||
Value: "bar",
|
||||
},
|
||||
},
|
||||
Dependencies: []string{"foo"},
|
||||
Resources: map[string]*ResourceState{
|
||||
|
|
|
@ -7,6 +7,7 @@ import (
|
|||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
"github.com/hashicorp/terraform/config"
|
||||
)
|
||||
|
||||
|
@ -81,12 +82,10 @@ func TestStateOutputTypeRoundTrip(t *testing.T) {
|
|||
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",
|
||||
Outputs: map[string]*OutputState{
|
||||
"string_output": &OutputState{
|
||||
Value: "String Value",
|
||||
Type: "string",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -1221,6 +1220,35 @@ func TestReadUpgradeStateV1toV2(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestReadUpgradeStateV1toV2_outputs(t *testing.T) {
|
||||
// ReadState should transparently detect the old version but will upgrade
|
||||
// it on Write.
|
||||
actual, err := ReadState(strings.NewReader(testV1StateWithOutputs))
|
||||
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) {
|
||||
spew.Config.DisableMethods = true
|
||||
t.Fatalf("bad:\n%s\n\nround tripped:\n%s\n", spew.Sdump(actual), spew.Sdump(roundTripped))
|
||||
spew.Config.DisableMethods = false
|
||||
}
|
||||
}
|
||||
|
||||
func TestReadUpgradeState(t *testing.T) {
|
||||
state := &StateV0{
|
||||
Resources: map[string]*ResourceStateV0{
|
||||
|
@ -1241,7 +1269,7 @@ func TestReadUpgradeState(t *testing.T) {
|
|||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
upgraded, err := upgradeV0State(state)
|
||||
upgraded, err := state.upgrade()
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
@ -1329,6 +1357,7 @@ func TestReadStateNewVersion(t *testing.T) {
|
|||
|
||||
func TestReadStateTFVersion(t *testing.T) {
|
||||
type tfVersion struct {
|
||||
Version int `json:"version"`
|
||||
TFVersion string `json:"terraform_version"`
|
||||
}
|
||||
|
||||
|
@ -1355,7 +1384,10 @@ func TestReadStateTFVersion(t *testing.T) {
|
|||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
buf, err := json.Marshal(&tfVersion{tc.Written})
|
||||
buf, err := json.Marshal(&tfVersion{
|
||||
Version: 2,
|
||||
TFVersion: tc.Written,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
@ -1443,7 +1475,7 @@ func TestUpgradeV0State(t *testing.T) {
|
|||
"bar": struct{}{},
|
||||
},
|
||||
}
|
||||
state, err := upgradeV0State(old)
|
||||
state, err := old.upgrade()
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
@ -1456,7 +1488,7 @@ func TestUpgradeV0State(t *testing.T) {
|
|||
if len(root.Outputs) != 1 {
|
||||
t.Fatalf("bad outputs: %v", root.Outputs)
|
||||
}
|
||||
if root.Outputs["ip"] != "127.0.0.1" {
|
||||
if root.Outputs["ip"].Value != "127.0.0.1" {
|
||||
t.Fatalf("bad outputs: %v", root.Outputs)
|
||||
}
|
||||
|
||||
|
@ -1588,3 +1620,37 @@ const testV1State = `{
|
|||
]
|
||||
}
|
||||
`
|
||||
|
||||
const testV1StateWithOutputs = `{
|
||||
"version": 1,
|
||||
"serial": 9,
|
||||
"remote": {
|
||||
"type": "http",
|
||||
"config": {
|
||||
"url": "http://my-cool-server.com/"
|
||||
}
|
||||
},
|
||||
"modules": [
|
||||
{
|
||||
"path": [
|
||||
"root"
|
||||
],
|
||||
"outputs": {
|
||||
"foo": "bar",
|
||||
"baz": "foo"
|
||||
},
|
||||
"resources": {
|
||||
"foo": {
|
||||
"type": "",
|
||||
"primary": {
|
||||
"id": "bar"
|
||||
}
|
||||
}
|
||||
},
|
||||
"depends_on": [
|
||||
"aws_instance.bar"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
`
|
||||
|
|
|
@ -11,6 +11,7 @@ import (
|
|||
"sync"
|
||||
|
||||
"github.com/hashicorp/terraform/config"
|
||||
"log"
|
||||
)
|
||||
|
||||
// The format byte is prefixed into the state file format so that we have
|
||||
|
@ -311,3 +312,57 @@ func ReadStateV0(src io.Reader) (*StateV0, error) {
|
|||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// upgradeV0State is used to upgrade a V0 state representation
|
||||
// into a State (current) representation.
|
||||
func (old *StateV0) upgrade() (*State, error) {
|
||||
s := &State{}
|
||||
s.init()
|
||||
|
||||
// Old format had no modules, so we migrate everything
|
||||
// directly into the root module.
|
||||
root := s.RootModule()
|
||||
|
||||
// Copy the outputs, first converting them to map[string]interface{}
|
||||
oldOutputs := make(map[string]*OutputState, len(old.Outputs))
|
||||
for key, value := range old.Outputs {
|
||||
oldOutputs[key] = &OutputState{
|
||||
Type: "string",
|
||||
Sensitive: false,
|
||||
Value: value,
|
||||
}
|
||||
}
|
||||
root.Outputs = oldOutputs
|
||||
|
||||
// Upgrade the resources
|
||||
for id, rs := range old.Resources {
|
||||
newRs := &ResourceState{
|
||||
Type: rs.Type,
|
||||
}
|
||||
root.Resources[id] = newRs
|
||||
|
||||
// Migrate to an instance state
|
||||
instance := &InstanceState{
|
||||
ID: rs.ID,
|
||||
Attributes: rs.Attributes,
|
||||
}
|
||||
|
||||
// Check if this is the primary or tainted instance
|
||||
if _, ok := old.Tainted[id]; ok {
|
||||
newRs.Tainted = append(newRs.Tainted, instance)
|
||||
} else {
|
||||
newRs.Primary = instance
|
||||
}
|
||||
|
||||
// Warn if the resource uses Extra, as there is
|
||||
// no upgrade path for this! Now totally deprecated.
|
||||
if len(rs.Extra) > 0 {
|
||||
log.Printf(
|
||||
"[WARN] Resource %s uses deprecated attribute "+
|
||||
"storage, state file upgrade may be incomplete.",
|
||||
rs.ID,
|
||||
)
|
||||
}
|
||||
}
|
||||
return s, nil
|
||||
}
|
||||
|
|
|
@ -0,0 +1,334 @@
|
|||
package terraform
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/mitchellh/copystructure"
|
||||
)
|
||||
|
||||
// stateV1 keeps track of a snapshot state-of-the-world that Terraform
|
||||
// can use to keep track of what real world resources it is actually
|
||||
// managing.
|
||||
//
|
||||
// stateV1 is _only used for the purposes of backwards compatibility
|
||||
// and is no longer used in Terraform.
|
||||
type stateV1 struct {
|
||||
// Version is the protocol version. "1" for a StateV1.
|
||||
Version int `json:"version"`
|
||||
|
||||
// Serial is incremented on any operation that modifies
|
||||
// the State file. It is used to detect potentially conflicting
|
||||
// updates.
|
||||
Serial int64 `json:"serial"`
|
||||
|
||||
// Remote is used to track the metadata required to
|
||||
// pull and push state files from a remote storage endpoint.
|
||||
Remote *remoteStateV1 `json:"remote,omitempty"`
|
||||
|
||||
// Modules contains all the modules in a breadth-first order
|
||||
Modules []*moduleStateV1 `json:"modules"`
|
||||
}
|
||||
|
||||
// upgrade is used to upgrade a V1 state representation
|
||||
// into a State (current) representation.
|
||||
func (old *stateV1) upgrade() (*State, error) {
|
||||
if old == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
remote, err := old.Remote.upgrade()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error upgrading State V1: %v", err)
|
||||
}
|
||||
|
||||
modules := make([]*ModuleState, len(old.Modules))
|
||||
for i, module := range old.Modules {
|
||||
upgraded, err := module.upgrade()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error upgrading State V1: %v", err)
|
||||
}
|
||||
modules[i] = upgraded
|
||||
}
|
||||
if len(modules) == 0 {
|
||||
modules = nil
|
||||
}
|
||||
|
||||
newState := &State{
|
||||
Version: old.Version,
|
||||
Serial: old.Serial,
|
||||
Remote: remote,
|
||||
Modules: modules,
|
||||
}
|
||||
|
||||
newState.sort()
|
||||
|
||||
return newState, nil
|
||||
}
|
||||
|
||||
type remoteStateV1 struct {
|
||||
// Type controls the client we use for the remote state
|
||||
Type string `json:"type"`
|
||||
|
||||
// Config is used to store arbitrary configuration that
|
||||
// is type specific
|
||||
Config map[string]string `json:"config"`
|
||||
}
|
||||
|
||||
func (old *remoteStateV1) upgrade() (*RemoteState, error) {
|
||||
if old == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
config, err := copystructure.Copy(old.Config)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error upgrading RemoteState V1: %v", err)
|
||||
}
|
||||
|
||||
return &RemoteState{
|
||||
Type: old.Type,
|
||||
Config: config.(map[string]string),
|
||||
}, nil
|
||||
}
|
||||
|
||||
type moduleStateV1 struct {
|
||||
// Path is the import path from the root module. Modules imports are
|
||||
// always disjoint, so the path represents amodule tree
|
||||
Path []string `json:"path"`
|
||||
|
||||
// Outputs declared by the module and maintained for each module
|
||||
// even though only the root module technically needs to be kept.
|
||||
// This allows operators to inspect values at the boundaries.
|
||||
Outputs map[string]string `json:"outputs"`
|
||||
|
||||
// Resources is a mapping of the logically named resource to
|
||||
// the state of the resource. Each resource may actually have
|
||||
// N instances underneath, although a user only needs to think
|
||||
// about the 1:1 case.
|
||||
Resources map[string]*resourceStateV1 `json:"resources"`
|
||||
|
||||
// Dependencies are a list of things that this module relies on
|
||||
// existing to remain intact. For example: an module may depend
|
||||
// on a VPC ID given by an aws_vpc resource.
|
||||
//
|
||||
// Terraform uses this information to build valid destruction
|
||||
// orders and to warn the user if they're destroying a module that
|
||||
// another resource depends on.
|
||||
//
|
||||
// Things can be put into this list that may not be managed by
|
||||
// Terraform. If Terraform doesn't find a matching ID in the
|
||||
// overall state, then it assumes it isn't managed and doesn't
|
||||
// worry about it.
|
||||
Dependencies []string `json:"depends_on,omitempty"`
|
||||
}
|
||||
|
||||
func (old *moduleStateV1) upgrade() (*ModuleState, error) {
|
||||
if old == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
path, err := copystructure.Copy(old.Path)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error upgrading ModuleState V1: %v", err)
|
||||
}
|
||||
|
||||
// Outputs needs upgrading to use the new structure
|
||||
outputs := make(map[string]*OutputState)
|
||||
for key, output := range old.Outputs {
|
||||
outputs[key] = &OutputState{
|
||||
Type: "string",
|
||||
Value: output,
|
||||
Sensitive: false,
|
||||
}
|
||||
}
|
||||
if len(outputs) == 0 {
|
||||
outputs = nil
|
||||
}
|
||||
|
||||
resources := make(map[string]*ResourceState)
|
||||
for key, oldResource := range old.Resources {
|
||||
upgraded, err := oldResource.upgrade()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error upgrading ModuleState V1: %v", err)
|
||||
}
|
||||
resources[key] = upgraded
|
||||
}
|
||||
if len(resources) == 0 {
|
||||
resources = nil
|
||||
}
|
||||
|
||||
dependencies, err := copystructure.Copy(old.Dependencies)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error upgrading ModuleState V1: %v", err)
|
||||
}
|
||||
|
||||
return &ModuleState{
|
||||
Path: path.([]string),
|
||||
Outputs: outputs,
|
||||
Resources: resources,
|
||||
Dependencies: dependencies.([]string),
|
||||
}, nil
|
||||
}
|
||||
|
||||
type resourceStateV1 struct {
|
||||
// This is filled in and managed by Terraform, and is the resource
|
||||
// type itself such as "mycloud_instance". If a resource provider sets
|
||||
// this value, it won't be persisted.
|
||||
Type string `json:"type"`
|
||||
|
||||
// Dependencies are a list of things that this resource relies on
|
||||
// existing to remain intact. For example: an AWS instance might
|
||||
// depend on a subnet (which itself might depend on a VPC, and so
|
||||
// on).
|
||||
//
|
||||
// Terraform uses this information to build valid destruction
|
||||
// orders and to warn the user if they're destroying a resource that
|
||||
// another resource depends on.
|
||||
//
|
||||
// Things can be put into this list that may not be managed by
|
||||
// Terraform. If Terraform doesn't find a matching ID in the
|
||||
// overall state, then it assumes it isn't managed and doesn't
|
||||
// worry about it.
|
||||
Dependencies []string `json:"depends_on,omitempty"`
|
||||
|
||||
// Primary is the current active instance for this resource.
|
||||
// It can be replaced but only after a successful creation.
|
||||
// This is the instances on which providers will act.
|
||||
Primary *instanceStateV1 `json:"primary"`
|
||||
|
||||
// Tainted is used to track any underlying instances that
|
||||
// have been created but are in a bad or unknown state and
|
||||
// need to be cleaned up subsequently. In the
|
||||
// standard case, there is only at most a single instance.
|
||||
// However, in pathological cases, it is possible for the number
|
||||
// of instances to accumulate.
|
||||
Tainted []*instanceStateV1 `json:"tainted,omitempty"`
|
||||
|
||||
// Deposed is used in the mechanics of CreateBeforeDestroy: the existing
|
||||
// Primary is Deposed to get it out of the way for the replacement Primary to
|
||||
// be created by Apply. If the replacement Primary creates successfully, the
|
||||
// Deposed instance is cleaned up. If there were problems creating the
|
||||
// replacement, the instance remains in the Deposed list so it can be
|
||||
// destroyed in a future run. Functionally, Deposed instances are very
|
||||
// similar to Tainted instances in that Terraform is only tracking them in
|
||||
// order to remember to destroy them.
|
||||
Deposed []*instanceStateV1 `json:"deposed,omitempty"`
|
||||
|
||||
// Provider is used when a resource is connected to a provider with an alias.
|
||||
// If this string is empty, the resource is connected to the default provider,
|
||||
// e.g. "aws_instance" goes with the "aws" provider.
|
||||
// If the resource block contained a "provider" key, that value will be set here.
|
||||
Provider string `json:"provider,omitempty"`
|
||||
}
|
||||
|
||||
func (old *resourceStateV1) upgrade() (*ResourceState, error) {
|
||||
if old == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
dependencies, err := copystructure.Copy(old.Dependencies)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error upgrading ResourceState V1: %v", err)
|
||||
}
|
||||
|
||||
primary, err := old.Primary.upgrade()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error upgrading ResourceState V1: %v", err)
|
||||
}
|
||||
|
||||
tainted := make([]*InstanceState, len(old.Tainted))
|
||||
for i, v := range old.Tainted {
|
||||
upgraded, err := v.upgrade()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error upgrading ResourceState V1: %v", err)
|
||||
}
|
||||
tainted[i] = upgraded
|
||||
}
|
||||
if len(tainted) == 0 {
|
||||
tainted = nil
|
||||
}
|
||||
|
||||
deposed := make([]*InstanceState, len(old.Deposed))
|
||||
for i, v := range old.Deposed {
|
||||
upgraded, err := v.upgrade()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error upgrading ResourceState V1: %v", err)
|
||||
}
|
||||
deposed[i] = upgraded
|
||||
}
|
||||
if len(deposed) == 0 {
|
||||
deposed = nil
|
||||
}
|
||||
|
||||
return &ResourceState{
|
||||
Type: old.Type,
|
||||
Dependencies: dependencies.([]string),
|
||||
Primary: primary,
|
||||
Tainted: tainted,
|
||||
Deposed: deposed,
|
||||
Provider: old.Provider,
|
||||
}, nil
|
||||
}
|
||||
|
||||
type instanceStateV1 struct {
|
||||
// A unique ID for this resource. This is opaque to Terraform
|
||||
// and is only meant as a lookup mechanism for the providers.
|
||||
ID string `json:"id"`
|
||||
|
||||
// Attributes are basic information about the resource. Any keys here
|
||||
// are accessible in variable format within Terraform configurations:
|
||||
// ${resourcetype.name.attribute}.
|
||||
Attributes map[string]string `json:"attributes,omitempty"`
|
||||
|
||||
// Ephemeral is used to store any state associated with this instance
|
||||
// that is necessary for the Terraform run to complete, but is not
|
||||
// persisted to a state file.
|
||||
Ephemeral ephemeralStateV1 `json:"-"`
|
||||
|
||||
// Meta is a simple K/V map that is persisted to the State but otherwise
|
||||
// ignored by Terraform core. It's meant to be used for accounting by
|
||||
// external client code.
|
||||
Meta map[string]string `json:"meta,omitempty"`
|
||||
}
|
||||
|
||||
func (old *instanceStateV1) upgrade() (*InstanceState, error) {
|
||||
if old == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
attributes, err := copystructure.Copy(old.Attributes)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error upgrading InstanceState V1: %v", err)
|
||||
}
|
||||
ephemeral, err := old.Ephemeral.upgrade()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error upgrading InstanceState V1: %v", err)
|
||||
}
|
||||
meta, err := copystructure.Copy(old.Meta)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error upgrading InstanceState V1: %v", err)
|
||||
}
|
||||
|
||||
return &InstanceState{
|
||||
ID: old.ID,
|
||||
Attributes: attributes.(map[string]string),
|
||||
Ephemeral: *ephemeral,
|
||||
Meta: meta.(map[string]string),
|
||||
}, nil
|
||||
}
|
||||
|
||||
type ephemeralStateV1 struct {
|
||||
// ConnInfo is used for the providers to export information which is
|
||||
// used to connect to the resource for provisioning. For example,
|
||||
// this could contain SSH or WinRM credentials.
|
||||
ConnInfo map[string]string `json:"-"`
|
||||
}
|
||||
|
||||
func (old *ephemeralStateV1) upgrade() (*EphemeralState, error) {
|
||||
connInfo, err := copystructure.Copy(old.ConnInfo)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error upgrading EphemeralState V1: %v", err)
|
||||
}
|
||||
return &EphemeralState{
|
||||
ConnInfo: connInfo.(map[string]string),
|
||||
}, nil
|
||||
}
|
|
@ -11,9 +11,15 @@ func TestAddOutputOrphanTransformer(t *testing.T) {
|
|||
Modules: []*ModuleState{
|
||||
&ModuleState{
|
||||
Path: RootModulePath,
|
||||
Outputs: map[string]interface{}{
|
||||
"foo": "bar",
|
||||
"bar": "baz",
|
||||
Outputs: map[string]*OutputState{
|
||||
"foo": &OutputState{
|
||||
Value: "bar",
|
||||
Type: "string",
|
||||
},
|
||||
"bar": &OutputState{
|
||||
Value: "baz",
|
||||
Type: "string",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
Loading…
Reference in New Issue