|
7 | 7 | "fmt"
|
8 | 8 | "io"
|
9 | 9 | "io/fs"
|
| 10 | + "os" |
| 11 | + "path/filepath" |
10 | 12 | "strings"
|
11 | 13 |
|
12 | 14 | "github.com/fatih/color"
|
@@ -63,7 +65,6 @@ func buildSteps(env *environment, run *run.Run) (steps []step) {
|
63 | 65 | steps = append(steps, bldr.downloadPlanFile)
|
64 | 66 | steps = append(steps, bldr.terraformInit)
|
65 | 67 | steps = append(steps, bldr.terraformApply)
|
66 |
| - steps = append(steps, bldr.uploadState) |
67 | 68 | }
|
68 | 69 |
|
69 | 70 | return
|
@@ -128,7 +129,7 @@ func (b *stepsBuilder) deleteBackendConfig(ctx context.Context) error {
|
128 | 129 | return nil
|
129 | 130 | }
|
130 | 131 |
|
131 |
| -// downloadState downloads current state to disk. If there is no state yet |
| 132 | +// downloadState downloads current state to disk. If there is no state yet then |
132 | 133 | // nothing will be downloaded and no error will be reported.
|
133 | 134 | func (b *stepsBuilder) downloadState(ctx context.Context) error {
|
134 | 135 | statefile, err := b.DownloadCurrentState(ctx, b.WorkspaceID)
|
@@ -174,7 +175,34 @@ func (b *stepsBuilder) terraformPlan(ctx context.Context) error {
|
174 | 175 | return b.executeTerraform(args)
|
175 | 176 | }
|
176 | 177 |
|
177 |
| -func (b *stepsBuilder) terraformApply(ctx context.Context) error { |
| 178 | +func (b *stepsBuilder) terraformApply(ctx context.Context) (err error) { |
| 179 | + // prior to running an apply, capture info about local state file |
| 180 | + // so we can detect changes... |
| 181 | + statePath := filepath.Join(b.workdir.String(), localStateFilename) |
| 182 | + stateInfoBefore, _ := os.Stat(statePath) |
| 183 | + // ...and after the apply finishes, determine if there were changes, and if |
| 184 | + // so, create a new state version. We do this even if the apply failed |
| 185 | + // because since terraform v1.5, an apply can persist partial updates: |
| 186 | + // |
| 187 | + // https://github.com/hashicorp/terraform/pull/32680 |
| 188 | + defer func() { |
| 189 | + stateInfoAfter, _ := os.Stat(statePath) |
| 190 | + if stateInfoAfter == nil { |
| 191 | + // no state file found |
| 192 | + return |
| 193 | + } |
| 194 | + if stateInfoBefore != nil && stateInfoAfter.ModTime().Equal(stateInfoBefore.ModTime()) { |
| 195 | + // no change to state file |
| 196 | + return |
| 197 | + } |
| 198 | + // either there was no state file before and there is one now, or the |
| 199 | + // state file modification time has changed. In either case we upload |
| 200 | + // the new state. |
| 201 | + if stateErr := b.uploadState(ctx); stateErr != nil { |
| 202 | + err = errors.Join(err, stateErr) |
| 203 | + } |
| 204 | + }() |
| 205 | + |
178 | 206 | args := []string{"apply"}
|
179 | 207 | if b.IsDestroy {
|
180 | 208 | args = append(args, "-destroy")
|
|
0 commit comments