aboutsummaryrefslogtreecommitdiffhomepage
path: root/vendor/github.com/hashicorp/terraform/states/statefile/read.go
diff options
context:
space:
mode:
authorNathan Dench <ndenc2@gmail.com>2019-05-24 15:16:44 +1000
committerNathan Dench <ndenc2@gmail.com>2019-05-24 15:16:44 +1000
commit107c1cdb09c575aa2f61d97f48d8587eb6bada4c (patch)
treeca7d008643efc555c388baeaf1d986e0b6b3e28c /vendor/github.com/hashicorp/terraform/states/statefile/read.go
parent844b5a68d8af4791755b8f0ad293cc99f5959183 (diff)
downloadterraform-provider-statuscake-107c1cdb09c575aa2f61d97f48d8587eb6bada4c.tar.gz
terraform-provider-statuscake-107c1cdb09c575aa2f61d97f48d8587eb6bada4c.tar.zst
terraform-provider-statuscake-107c1cdb09c575aa2f61d97f48d8587eb6bada4c.zip
Upgrade to 0.12
Diffstat (limited to 'vendor/github.com/hashicorp/terraform/states/statefile/read.go')
-rw-r--r--vendor/github.com/hashicorp/terraform/states/statefile/read.go209
1 files changed, 209 insertions, 0 deletions
diff --git a/vendor/github.com/hashicorp/terraform/states/statefile/read.go b/vendor/github.com/hashicorp/terraform/states/statefile/read.go
new file mode 100644
index 0000000..d691c02
--- /dev/null
+++ b/vendor/github.com/hashicorp/terraform/states/statefile/read.go
@@ -0,0 +1,209 @@
1package statefile
2
3import (
4 "encoding/json"
5 "errors"
6 "fmt"
7 "io"
8 "io/ioutil"
9 "os"
10
11 version "github.com/hashicorp/go-version"
12
13 "github.com/hashicorp/terraform/tfdiags"
14 tfversion "github.com/hashicorp/terraform/version"
15)
16
17// ErrNoState is returned by ReadState when the state file is empty.
18var ErrNoState = errors.New("no state")
19
20// Read reads a state from the given reader.
21//
22// Legacy state format versions 1 through 3 are supported, but the result will
23// contain object attributes in the deprecated "flatmap" format and so must
24// be upgraded by the caller before use.
25//
26// If the state file is empty, the special error value ErrNoState is returned.
27// Otherwise, the returned error might be a wrapper around tfdiags.Diagnostics
28// potentially describing multiple errors.
29func Read(r io.Reader) (*File, error) {
30 // Some callers provide us a "typed nil" *os.File here, which would
31 // cause us to panic below if we tried to use it.
32 if f, ok := r.(*os.File); ok && f == nil {
33 return nil, ErrNoState
34 }
35
36 var diags tfdiags.Diagnostics
37
38 // We actually just buffer the whole thing in memory, because states are
39 // generally not huge and we need to do be able to sniff for a version
40 // number before full parsing.
41 src, err := ioutil.ReadAll(r)
42 if err != nil {
43 diags = diags.Append(tfdiags.Sourceless(
44 tfdiags.Error,
45 "Failed to read state file",
46 fmt.Sprintf("The state file could not be read: %s", err),
47 ))
48 return nil, diags.Err()
49 }
50
51 if len(src) == 0 {
52 return nil, ErrNoState
53 }
54
55 state, diags := readState(src)
56 if diags.HasErrors() {
57 return nil, diags.Err()
58 }
59
60 if state == nil {
61 // Should never happen
62 panic("readState returned nil state with no errors")
63 }
64
65 if state.TerraformVersion != nil && state.TerraformVersion.GreaterThan(tfversion.SemVer) {
66 return state, fmt.Errorf(
67 "state snapshot was created by Terraform v%s, which is newer than current v%s; upgrade to Terraform v%s or greater to work with this state",
68 state.TerraformVersion,
69 tfversion.SemVer,
70 state.TerraformVersion,
71 )
72 }
73
74 return state, diags.Err()
75}
76
77func readState(src []byte) (*File, tfdiags.Diagnostics) {
78 var diags tfdiags.Diagnostics
79
80 if looksLikeVersion0(src) {
81 diags = diags.Append(tfdiags.Sourceless(
82 tfdiags.Error,
83 unsupportedFormat,
84 "The state is stored in a legacy binary format that is not supported since Terraform v0.7. To continue, first upgrade the state using Terraform 0.6.16 or earlier.",
85 ))
86 return nil, diags
87 }
88
89 version, versionDiags := sniffJSONStateVersion(src)
90 diags = diags.Append(versionDiags)
91 if versionDiags.HasErrors() {
92 return nil, diags
93 }
94
95 switch version {
96 case 0:
97 diags = diags.Append(tfdiags.Sourceless(
98 tfdiags.Error,
99 unsupportedFormat,
100 "The state file uses JSON syntax but has a version number of zero. There was never a JSON-based state format zero, so this state file is invalid and cannot be processed.",
101 ))
102 return nil, diags
103 case 1:
104 return readStateV1(src)
105 case 2:
106 return readStateV2(src)
107 case 3:
108 return readStateV3(src)
109 case 4:
110 return readStateV4(src)
111 default:
112 thisVersion := tfversion.SemVer.String()
113 creatingVersion := sniffJSONStateTerraformVersion(src)
114 switch {
115 case creatingVersion != "":
116 diags = diags.Append(tfdiags.Sourceless(
117 tfdiags.Error,
118 unsupportedFormat,
119 fmt.Sprintf("The state file uses format version %d, which is not supported by Terraform %s. This state file was created by Terraform %s.", version, thisVersion, creatingVersion),
120 ))
121 default:
122 diags = diags.Append(tfdiags.Sourceless(
123 tfdiags.Error,
124 unsupportedFormat,
125 fmt.Sprintf("The state file uses format version %d, which is not supported by Terraform %s. This state file may have been created by a newer version of Terraform.", version, thisVersion),
126 ))
127 }
128 return nil, diags
129 }
130}
131
132func sniffJSONStateVersion(src []byte) (uint64, tfdiags.Diagnostics) {
133 var diags tfdiags.Diagnostics
134
135 type VersionSniff struct {
136 Version *uint64 `json:"version"`
137 }
138 var sniff VersionSniff
139 err := json.Unmarshal(src, &sniff)
140 if err != nil {
141 switch tErr := err.(type) {
142 case *json.SyntaxError:
143 diags = diags.Append(tfdiags.Sourceless(
144 tfdiags.Error,
145 unsupportedFormat,
146 fmt.Sprintf("The state file could not be parsed as JSON: syntax error at byte offset %d.", tErr.Offset),
147 ))
148 case *json.UnmarshalTypeError:
149 diags = diags.Append(tfdiags.Sourceless(
150 tfdiags.Error,
151 unsupportedFormat,
152 fmt.Sprintf("The version in the state file is %s. A positive whole number is required.", tErr.Value),
153 ))
154 default:
155 diags = diags.Append(tfdiags.Sourceless(
156 tfdiags.Error,
157 unsupportedFormat,
158 "The state file could not be parsed as JSON.",
159 ))
160 }
161 }
162
163 if sniff.Version == nil {
164 diags = diags.Append(tfdiags.Sourceless(
165 tfdiags.Error,
166 unsupportedFormat,
167 "The state file does not have a \"version\" attribute, which is required to identify the format version.",
168 ))
169 return 0, diags
170 }
171
172 return *sniff.Version, diags
173}
174
175// sniffJSONStateTerraformVersion attempts to sniff the Terraform version
176// specification from the given state file source code. The result is either
177// a version string or an empty string if no version number could be extracted.
178//
179// This is a best-effort function intended to produce nicer error messages. It
180// should not be used for any real processing.
181func sniffJSONStateTerraformVersion(src []byte) string {
182 type VersionSniff struct {
183 Version string `json:"terraform_version"`
184 }
185 var sniff VersionSniff
186
187 err := json.Unmarshal(src, &sniff)
188 if err != nil {
189 return ""
190 }
191
192 // Attempt to parse the string as a version so we won't report garbage
193 // as a version number.
194 _, err = version.NewVersion(sniff.Version)
195 if err != nil {
196 return ""
197 }
198
199 return sniff.Version
200}
201
202// unsupportedFormat is a diagnostic summary message for when the state file
203// seems to not be a state file at all, or is not a supported version.
204//
205// Use invalidFormat instead for the subtly-different case of "this looks like
206// it's intended to be a state file but it's not structured correctly".
207const unsupportedFormat = "Unsupported state file format"
208
209const upgradeFailed = "State format upgrade failed"