@@ -8,12 +8,25 @@ import (
8
8
"context"
9
9
"fmt"
10
10
"net/url"
11
+ "strings"
11
12
"time"
13
+
14
+ "golang.org/x/sync/errgroup"
12
15
)
13
16
14
17
// Compile-time proof of interface implementation.
15
18
var _ StateVersions = (* stateVersions )(nil )
16
19
20
+ // StateVersionStatus are available state version status values
21
+ type StateVersionStatus string
22
+
23
+ // Available state version statuses.
24
+ const (
25
+ StateVersionPending StateVersionStatus = "pending"
26
+ StateVersionFinalized StateVersionStatus = "finalized"
27
+ StateVersionDiscarded StateVersionStatus = "discarded"
28
+ )
29
+
17
30
// StateVersions describes all the state version related methods that
18
31
// the Terraform Enterprise API supports.
19
32
//
@@ -26,6 +39,12 @@ type StateVersions interface {
26
39
// Create a new state version for the given workspace.
27
40
Create (ctx context.Context , workspaceID string , options StateVersionCreateOptions ) (* StateVersion , error )
28
41
42
+ // Upload creates a new state version but uploads the state content directly to the object store.
43
+ // This is a more resilient form of Create and is the recommended approach to creating state versions.
44
+ //
45
+ // **Note: This method is still in BETA and subject to change.**
46
+ Upload (ctx context.Context , workspaceID string , options StateVersionUploadOptions ) (* StateVersion , error )
47
+
29
48
// Read a state version by its ID.
30
49
Read (ctx context.Context , svID string ) (* StateVersion , error )
31
50
@@ -60,12 +79,16 @@ type StateVersionList struct {
60
79
61
80
// StateVersion represents a Terraform Enterprise state version.
62
81
type StateVersion struct {
63
- ID string `jsonapi:"primary,state-versions"`
64
- CreatedAt time.Time `jsonapi:"attr,created-at,iso8601"`
65
- DownloadURL string `jsonapi:"attr,hosted-state-download-url"`
66
- Serial int64 `jsonapi:"attr,serial"`
67
- VCSCommitSHA string `jsonapi:"attr,vcs-commit-sha"`
68
- VCSCommitURL string `jsonapi:"attr,vcs-commit-url"`
82
+ ID string `jsonapi:"primary,state-versions"`
83
+ CreatedAt time.Time `jsonapi:"attr,created-at,iso8601"`
84
+ DownloadURL string `jsonapi:"attr,hosted-state-download-url"`
85
+ UploadURL string `jsonapi:"attr,hosted-state-upload-url"`
86
+ Status StateVersionStatus `jsonapi:"attr,status"`
87
+ JSONUploadURL string `jsonapi:"attr,hosted-json-state-upload-url"`
88
+ JSONDownloadURL string `jsonapi:"attr,hosted-json-state-download-url"`
89
+ Serial int64 `jsonapi:"attr,serial"`
90
+ VCSCommitSHA string `jsonapi:"attr,vcs-commit-sha"`
91
+ VCSCommitURL string `jsonapi:"attr,vcs-commit-url"`
69
92
// Whether Terraform Cloud has finished populating any StateVersion fields that required async processing.
70
93
// If `false`, some fields may appear empty even if they should actually contain data; see comments on
71
94
// individual fields for details.
@@ -147,8 +170,8 @@ type StateVersionCreateOptions struct {
147
170
// Required: The serial of the state.
148
171
Serial * int64 `jsonapi:"attr,serial"`
149
172
150
- // Required : The base64 encoded state.
151
- State * string `jsonapi:"attr,state"`
173
+ // Optional : The base64 encoded state.
174
+ State * string `jsonapi:"attr,state,omitempty "`
152
175
153
176
// Optional: Force can be set to skip certain validations. Wrong use
154
177
// of this flag can cause data loss, so USE WITH CAUTION!
@@ -173,6 +196,13 @@ type StateVersionCreateOptions struct {
173
196
JSONStateOutputs * string `jsonapi:"attr,json-state-outputs,omitempty"`
174
197
}
175
198
199
+ type StateVersionUploadOptions struct {
200
+ StateVersionCreateOptions
201
+
202
+ RawState []byte
203
+ RawJSONState []byte
204
+ }
205
+
176
206
type StateVersionModules struct {
177
207
Root StateVersionModuleRoot `jsonapi:"attr,root"`
178
208
}
@@ -243,6 +273,46 @@ func (s *stateVersions) Create(ctx context.Context, workspaceID string, options
243
273
return sv , nil
244
274
}
245
275
276
+ func (s * stateVersions ) putURL (ctx context.Context , putURL string , data []byte ) error {
277
+ reader := bytes .NewReader (data )
278
+ req , err := s .client .NewRequest ("PUT" , putURL , reader )
279
+ if err != nil {
280
+ return err
281
+ }
282
+
283
+ return req .Do (ctx , nil )
284
+ }
285
+
286
+ func (s * stateVersions ) Upload (ctx context.Context , workspaceID string , options StateVersionUploadOptions ) (* StateVersion , error ) {
287
+ if err := options .valid (); err != nil {
288
+ return nil , err
289
+ }
290
+
291
+ sv , err := s .Create (ctx , workspaceID , options .StateVersionCreateOptions )
292
+ if err != nil {
293
+ if strings .Contains (err .Error (), "param is missing or the value is empty: state" ) {
294
+ return nil , ErrStateVersionUploadNotSupported
295
+ }
296
+ }
297
+
298
+ g , _ := errgroup .WithContext (ctx )
299
+ g .Go (func () error {
300
+ return s .putURL (ctx , sv .UploadURL , options .RawState )
301
+ })
302
+ if options .RawJSONState != nil {
303
+ g .Go (func () error {
304
+ return s .putURL (ctx , sv .JSONUploadURL , options .RawJSONState )
305
+ })
306
+ }
307
+
308
+ if err := g .Wait (); err != nil {
309
+ return nil , err
310
+ }
311
+
312
+ // Re-read the state version to get the updated status, if available
313
+ return s .Read (ctx , sv .ID )
314
+ }
315
+
246
316
// Read a state version by its ID.
247
317
func (s * stateVersions ) ReadWithOptions (ctx context.Context , svID string , options * StateVersionReadOptions ) (* StateVersion , error ) {
248
318
if ! validStringID (& svID ) {
@@ -362,8 +432,18 @@ func (o StateVersionCreateOptions) valid() error {
362
432
if o .Serial == nil {
363
433
return ErrRequiredSerial
364
434
}
365
- if ! validString (o .State ) {
366
- return ErrRequiredState
435
+ return nil
436
+ }
437
+
438
+ func (o StateVersionUploadOptions ) valid () error {
439
+ if err := o .StateVersionCreateOptions .valid (); err != nil {
440
+ return err
441
+ }
442
+ if o .State != nil || o .JSONState != nil {
443
+ return ErrStateMustBeOmitted
444
+ }
445
+ if o .RawState == nil {
446
+ return ErrRequiredRawState
367
447
}
368
448
return nil
369
449
}
0 commit comments