backend/remote: add the run ID to associate state

If a run ID is available, we need to make sure we pass that when creating a new state version so the state will be properly associated with the run.
This commit is contained in:
Sander van Harmelen 2018-09-08 11:38:21 +02:00
parent cd8fcf74e0
commit cd6d75bc03
4 changed files with 42 additions and 1 deletions

View File

@ -5,6 +5,7 @@ import (
"fmt" "fmt"
"log" "log"
"net/url" "net/url"
"os"
"sort" "sort"
"strings" "strings"
"sync" "sync"
@ -278,6 +279,9 @@ func (b *Remote) State(workspace string) (state.State, error) {
client: b.client, client: b.client,
organization: b.organization, organization: b.organization,
workspace: workspace, workspace: workspace,
// This is optionally set during Terraform Enterprise runs.
runID: os.Getenv("TFE_RUN_ID"),
} }
return &remote.State{Client: client}, nil return &remote.State{Client: client}, nil

View File

@ -343,8 +343,13 @@ func (m *mockStateVersions) List(ctx context.Context, options tfe.StateVersionLi
func (m *mockStateVersions) Create(ctx context.Context, workspaceID string, options tfe.StateVersionCreateOptions) (*tfe.StateVersion, error) { func (m *mockStateVersions) Create(ctx context.Context, workspaceID string, options tfe.StateVersionCreateOptions) (*tfe.StateVersion, error) {
id := generateID("sv-") id := generateID("sv-")
runID := os.Getenv("TFE_RUN_ID")
url := fmt.Sprintf("https://app.terraform.io/_archivist/%s", id) url := fmt.Sprintf("https://app.terraform.io/_archivist/%s", id)
if runID != "" && (options.Run == nil || runID != options.Run.ID) {
return nil, fmt.Errorf("option.Run.ID does not contain the ID exported by TFE_RUN_ID")
}
sv := &tfe.StateVersion{ sv := &tfe.StateVersion{
ID: id, ID: id,
DownloadURL: url, DownloadURL: url,

View File

@ -15,6 +15,7 @@ import (
type remoteClient struct { type remoteClient struct {
client *tfe.Client client *tfe.Client
organization string organization string
runID string
workspace string workspace string
} }
@ -70,7 +71,7 @@ func (r *remoteClient) Put(state []byte) error {
return fmt.Errorf("Error retrieving workspace: %v", err) return fmt.Errorf("Error retrieving workspace: %v", err)
} }
// the state into a buffer. // Read the raw state into a Terraform state.
tfState, err := terraform.ReadState(bytes.NewReader(state)) tfState, err := terraform.ReadState(bytes.NewReader(state))
if err != nil { if err != nil {
return fmt.Errorf("Error reading state: %s", err) return fmt.Errorf("Error reading state: %s", err)
@ -83,6 +84,12 @@ func (r *remoteClient) Put(state []byte) error {
State: tfe.String(base64.StdEncoding.EncodeToString(state)), State: tfe.String(base64.StdEncoding.EncodeToString(state)),
} }
// If we have a run ID, make sure to add it to the options
// so the state will be properly associated with the run.
if r.runID != "" {
options.Run = &tfe.Run{ID: r.runID}
}
// Create the new state. // Create the new state.
_, err = r.client.StateVersions.Create(ctx, w.ID, options) _, err = r.client.StateVersions.Create(ctx, w.ID, options)
if err != nil { if err != nil {

View File

@ -1,9 +1,12 @@
package remote package remote
import ( import (
"bytes"
"os"
"testing" "testing"
"github.com/hashicorp/terraform/state/remote" "github.com/hashicorp/terraform/state/remote"
"github.com/hashicorp/terraform/terraform"
) )
func TestRemoteClient_impl(t *testing.T) { func TestRemoteClient_impl(t *testing.T) {
@ -14,3 +17,25 @@ func TestRemoteClient(t *testing.T) {
client := testRemoteClient(t) client := testRemoteClient(t)
remote.TestClient(t, client) remote.TestClient(t, client)
} }
func TestRemoteClient_withRunID(t *testing.T) {
// Set the TFE_RUN_ID environment variable before creating the client!
if err := os.Setenv("TFE_RUN_ID", generateID("run-")); err != nil {
t.Fatalf("error setting env var TFE_RUN_ID: %v", err)
}
// Create a new test client.
client := testRemoteClient(t)
// Create a new empty state.
state := bytes.NewBuffer(nil)
if err := terraform.WriteState(terraform.NewState(), state); err != nil {
t.Fatalf("expected no error, got: %v", err)
}
// Store the new state to verify (this will be done
// by the mock that is used) that the run ID is set.
if err := client.Put(state.Bytes()); err != nil {
t.Fatalf("expected no error, got %v", err)
}
}