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  }