diff options
author | Nathan Dench <ndenc2@gmail.com> | 2019-05-24 15:16:44 +1000 |
---|---|---|
committer | Nathan Dench <ndenc2@gmail.com> | 2019-05-24 15:16:44 +1000 |
commit | 107c1cdb09c575aa2f61d97f48d8587eb6bada4c (patch) | |
tree | ca7d008643efc555c388baeaf1d986e0b6b3e28c /vendor/github.com/hashicorp/terraform/states/statefile/read.go | |
parent | 844b5a68d8af4791755b8f0ad293cc99f5959183 (diff) | |
download | terraform-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.go | 209 |
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 @@ | |||
1 | package statefile | ||
2 | |||
3 | import ( | ||
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. | ||
18 | var 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. | ||
29 | func 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 | |||
77 | func 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 | |||
132 | func 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. | ||
181 | func 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". | ||
207 | const unsupportedFormat = "Unsupported state file format" | ||
208 | |||
209 | const upgradeFailed = "State format upgrade failed" | ||