@@ -7,14 +7,20 @@ import (
7
7
"bytes"
8
8
"context"
9
9
"fmt"
10
+ "io"
10
11
"net/url"
12
+ "time"
11
13
)
12
14
13
15
// StackConfigurations describes all the stacks configurations-related methods that the
14
16
// HCP Terraform API supports.
15
17
// NOTE WELL: This is a beta feature and is subject to change until noted otherwise in the
16
18
// release notes.
17
19
type StackConfigurations interface {
20
+ // CreateAndUpload packages and uploads the specified Terraform Stacks
21
+ // configuration files in association with a Stack.
22
+ CreateAndUpload (ctx context.Context , stackID string , path string , opts * CreateStackConfigurationOptions ) (* StackConfiguration , error )
23
+
18
24
// ReadConfiguration returns a stack configuration by its ID.
19
25
Read (ctx context.Context , id string ) (* StackConfiguration , error )
20
26
@@ -155,3 +161,95 @@ func (s stackConfigurations) List(ctx context.Context, stackID string, options *
155
161
156
162
return result , nil
157
163
}
164
+
165
+ type CreateStackConfigurationOptions struct {
166
+ SelectedDeployments []string `jsonapi:"attr,selected-deployments,omitempty"`
167
+ SpeculativeEnabled * bool `jsonapi:"attr,speculative,omitempty"`
168
+ }
169
+
170
+ // CreateAndUpload packages and uploads the specified Terraform Stacks
171
+ // configuration files in association with a Stack.
172
+ func (s stackConfigurations ) CreateAndUpload (ctx context.Context , stackID , path string , opts * CreateStackConfigurationOptions ) (* StackConfiguration , error ) {
173
+ if opts == nil {
174
+ opts = & CreateStackConfigurationOptions {}
175
+ }
176
+ u := fmt .Sprintf ("stacks/%s/stack-configurations" , url .PathEscape (stackID ))
177
+ req , err := s .client .NewRequest ("POST" , u , opts )
178
+ if err != nil {
179
+ return nil , fmt .Errorf ("error creating stack configuration request for stack %q: %w" , stackID , err )
180
+ }
181
+
182
+ sc := & StackConfiguration {}
183
+ err = req .Do (ctx , sc )
184
+ if err != nil {
185
+ return nil , fmt .Errorf ("error creating stack configuration for stack %q: %w" , stackID , err )
186
+ }
187
+
188
+ uploadURL , err := s .pollForUploadURL (ctx , sc .ID )
189
+ if err != nil {
190
+ return nil , fmt .Errorf ("error retrieving upload URL for stack configuration %q: %w" , sc .ID , err )
191
+ }
192
+
193
+ body , err := packContents (path )
194
+ if err != nil {
195
+ return nil , err
196
+ }
197
+
198
+ err = s .UploadTarGzip (ctx , uploadURL , body )
199
+ if err != nil {
200
+ return nil , err
201
+ }
202
+
203
+ return sc , nil
204
+ }
205
+
206
+ // PollForUploadURL polls for the upload URL of a stack configuration until it becomes available.
207
+ // It makes a request every 2 seconds until the upload URL is present in the response.
208
+ // It will timeout after 10 seconds.
209
+ func (s stackConfigurations ) pollForUploadURL (ctx context.Context , stackConfigurationID string ) (string , error ) {
210
+ ticker := time .NewTicker (2 * time .Second )
211
+ defer ticker .Stop ()
212
+
213
+ timeout := time .NewTimer (15 * time .Second )
214
+ defer timeout .Stop ()
215
+
216
+ for {
217
+ select {
218
+ case <- ctx .Done ():
219
+ return "" , ctx .Err ()
220
+ case <- timeout .C :
221
+ return "" , fmt .Errorf ("timeout waiting for upload URL for stack configuration %q" , stackConfigurationID )
222
+ case <- ticker .C :
223
+ urlReq , err := s .client .NewRequest ("GET" , fmt .Sprintf ("stack-configurations/%s/upload-url" , stackConfigurationID ), nil )
224
+ if err != nil {
225
+ return "" , fmt .Errorf ("error creating upload URL request for stack configuration %q: %w" , stackConfigurationID , err )
226
+ }
227
+
228
+ type UploadURLResponse struct {
229
+ Data struct {
230
+ SourceUploadURL * string `json:"source-upload-url"`
231
+ } `json:"data"`
232
+ }
233
+
234
+ uploadResp := & UploadURLResponse {}
235
+ err = urlReq .DoJSON (ctx , uploadResp )
236
+ if err != nil {
237
+ return "" , fmt .Errorf ("error getting upload URL for stack configuration %q: %w" , stackConfigurationID , err )
238
+ }
239
+
240
+ if uploadResp .Data .SourceUploadURL != nil {
241
+ return * uploadResp .Data .SourceUploadURL , nil
242
+ }
243
+ }
244
+ }
245
+ }
246
+
247
+ // UploadTarGzip is used to upload Terraform configuration files contained a tar gzip archive.
248
+ // Any stream implementing io.Reader can be passed into this method. This method is also
249
+ // particularly useful for tar streams created by non-default go-slug configurations.
250
+ //
251
+ // **Note**: This method does not validate the content being uploaded and is therefore the caller's
252
+ // responsibility to ensure the raw content is a valid Terraform configuration.
253
+ func (s stackConfigurations ) UploadTarGzip (ctx context.Context , uploadURL string , archive io.Reader ) error {
254
+ return s .client .doForeignPUTRequest (ctx , uploadURL , archive )
255
+ }
0 commit comments