github.com/opentofu/opentofu@v1.7.1/internal/backend/local/backend_refresh.go (about) 1 // Copyright (c) The OpenTofu Authors 2 // SPDX-License-Identifier: MPL-2.0 3 // Copyright (c) 2023 HashiCorp, Inc. 4 // SPDX-License-Identifier: MPL-2.0 5 6 package local 7 8 import ( 9 "context" 10 "fmt" 11 "log" 12 "os" 13 14 "github.com/opentofu/opentofu/internal/backend" 15 "github.com/opentofu/opentofu/internal/logging" 16 "github.com/opentofu/opentofu/internal/states" 17 "github.com/opentofu/opentofu/internal/states/statemgr" 18 "github.com/opentofu/opentofu/internal/tfdiags" 19 ) 20 21 func (b *Local) opRefresh( 22 stopCtx context.Context, 23 cancelCtx context.Context, 24 op *backend.Operation, 25 runningOp *backend.RunningOperation) { 26 27 var diags tfdiags.Diagnostics 28 29 // Check if our state exists if we're performing a refresh operation. We 30 // only do this if we're managing state with this backend. 31 if b.Backend == nil { 32 if _, err := os.Stat(b.StatePath); err != nil { 33 if os.IsNotExist(err) { 34 err = nil 35 } 36 37 if err != nil { 38 diags = diags.Append(tfdiags.Sourceless( 39 tfdiags.Error, 40 "Cannot read state file", 41 fmt.Sprintf("Failed to read %s: %s", b.StatePath, err), 42 )) 43 op.ReportResult(runningOp, diags) 44 return 45 } 46 } 47 } 48 49 // Refresh now happens via a plan, so we need to ensure this is enabled 50 op.PlanRefresh = true 51 52 // Get our context 53 lr, _, opState, contextDiags := b.localRun(op) 54 diags = diags.Append(contextDiags) 55 if contextDiags.HasErrors() { 56 op.ReportResult(runningOp, diags) 57 return 58 } 59 60 // the state was locked during successful context creation; unlock the state 61 // when the operation completes 62 defer func() { 63 diags := op.StateLocker.Unlock() 64 if diags.HasErrors() { 65 op.View.Diagnostics(diags) 66 runningOp.Result = backend.OperationFailure 67 } 68 }() 69 70 // If we succeed then we'll overwrite this with the resulting state below, 71 // but otherwise the resulting state is just the input state. 72 runningOp.State = lr.InputState 73 if !runningOp.State.HasManagedResourceInstanceObjects() { 74 diags = diags.Append(tfdiags.Sourceless( 75 tfdiags.Warning, 76 "Empty or non-existent state", 77 "There are currently no remote objects tracked in the state, so there is nothing to refresh.", 78 )) 79 } 80 81 // get schemas before writing state 82 schemas, moreDiags := lr.Core.Schemas(lr.Config, lr.InputState) 83 diags = diags.Append(moreDiags) 84 if moreDiags.HasErrors() { 85 op.ReportResult(runningOp, diags) 86 return 87 } 88 89 // Perform the refresh in a goroutine so we can be interrupted 90 var newState *states.State 91 var refreshDiags tfdiags.Diagnostics 92 doneCh := make(chan struct{}) 93 panicHandler := logging.PanicHandlerWithTraceFn() 94 go func() { 95 defer panicHandler() 96 defer close(doneCh) 97 newState, refreshDiags = lr.Core.Refresh(lr.Config, lr.InputState, lr.PlanOpts) 98 log.Printf("[INFO] backend/local: refresh calling Refresh") 99 }() 100 101 if b.opWait(doneCh, stopCtx, cancelCtx, lr.Core, opState, op.View) { 102 return 103 } 104 105 // Write the resulting state to the running op 106 runningOp.State = newState 107 diags = diags.Append(refreshDiags) 108 if refreshDiags.HasErrors() { 109 op.ReportResult(runningOp, diags) 110 return 111 } 112 113 err := statemgr.WriteAndPersist(opState, newState, schemas) 114 if err != nil { 115 diags = diags.Append(fmt.Errorf("failed to write state: %w", err)) 116 op.ReportResult(runningOp, diags) 117 return 118 } 119 120 // Show any remaining warnings before exiting 121 op.ReportResult(runningOp, diags) 122 }