Skip to content

Commit 9797b31

Browse files
authored
Merge pull request #1184 from hashicorp/TF-28162/create-and-upload-stack-configuration
StackConfiguration's `CreateAndUpload`
2 parents aea6949 + e4f6266 commit 9797b31

File tree

3 files changed

+145
-0
lines changed

3 files changed

+145
-0
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
# Unreleased
22

3+
# v1.90.0
4+
35
## Bug Fixes
46

57
* Fixes `IngressAttributes` field decoding in `PolicySetVersion` by @rageshganeshkumar [#1164](https://github.com/hashicorp/go-tfe/pull/1164)
@@ -11,6 +13,7 @@
1113
* Adds endpoint for reruning a stack deployment by @hwatkins05-hashicorp/@Maed223 [#1176](https://github.com/hashicorp/go-tfe/pull/1176)
1214
* Adds `ReadByName` for `StackDeploymentGroup` by @Maed223 [#1181](https://github.com/hashicorp/go-tfe/pull/1181)
1315
* Adds BETA support for listing `QueryRuns`, which is EXPERIMENTAL, SUBJECT TO CHANGE, and may not be available to all users by @brandonc [#1177](https://github.com/hashicorp/go-tfe/pull/1177)
16+
* Adds BETA support for `CreateAndUpload` for `StackConfiguration`, which is EXPERIMENTAL, SUBJECT TO CHANGE, and may not be available to all users by @Maed223 [#1184](https://github.com/hashicorp/go-tfe/pull/1184)
1417

1518

1619
# v1.89.0

stack_configuration.go

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,20 @@ import (
77
"bytes"
88
"context"
99
"fmt"
10+
"io"
1011
"net/url"
12+
"time"
1113
)
1214

1315
// StackConfigurations describes all the stacks configurations-related methods that the
1416
// HCP Terraform API supports.
1517
// NOTE WELL: This is a beta feature and is subject to change until noted otherwise in the
1618
// release notes.
1719
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+
1824
// ReadConfiguration returns a stack configuration by its ID.
1925
Read(ctx context.Context, id string) (*StackConfiguration, error)
2026

@@ -155,3 +161,95 @@ func (s stackConfigurations) List(ctx context.Context, stackID string, options *
155161

156162
return result, nil
157163
}
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+
}

stack_configuration_integration_test.go

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,3 +78,47 @@ func TestStackConfigurationList(t *testing.T) {
7878
assert.GreaterOrEqual(t, listWithOptions.Pagination.TotalCount, 2)
7979
})
8080
}
81+
82+
func TestStackConfigurationCreateUploadAndRead(t *testing.T) {
83+
skipUnlessBeta(t)
84+
85+
client := testClient(t)
86+
ctx := context.Background()
87+
88+
orgTest, orgTestCleanup := createOrganization(t, client)
89+
t.Cleanup(orgTestCleanup)
90+
91+
stack, err := client.Stacks.Create(ctx, StackCreateOptions{
92+
Project: orgTest.DefaultProject,
93+
Name: "test-stack",
94+
})
95+
require.NoError(t, err)
96+
97+
ctx, cancel := context.WithTimeout(ctx, 20*time.Second)
98+
defer cancel()
99+
100+
done := make(chan struct{})
101+
go func() {
102+
for {
103+
sc, err := client.StackConfigurations.CreateAndUpload(ctx, stack.ID, "test-fixtures/stack-source", &CreateStackConfigurationOptions{
104+
SelectedDeployments: []string{"simple"},
105+
})
106+
require.NoError(t, err)
107+
108+
if sc != nil {
109+
done <- struct{}{}
110+
return
111+
}
112+
113+
time.Sleep(2 * time.Second)
114+
}
115+
}()
116+
117+
select {
118+
case <-done:
119+
t.Logf("Created and uploaded config to stack configuration")
120+
return
121+
case <-ctx.Done():
122+
require.Fail(t, "timed out waiting for stack configuration to be processed")
123+
}
124+
}

0 commit comments

Comments
 (0)