github.com/terramate-io/tf@v0.0.0-20230830114523-fce866b4dfcd/backend/local/backend_refresh.go (about)

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