github.com/terramate-io/tf@v0.0.0-20230830114523-fce866b4dfcd/plans/planfile/writer.go (about)

     1  // Copyright (c) HashiCorp, Inc.
     2  // SPDX-License-Identifier: MPL-2.0
     3  
     4  package planfile
     5  
     6  import (
     7  	"archive/zip"
     8  	"fmt"
     9  	"os"
    10  	"time"
    11  
    12  	"github.com/terramate-io/tf/configs/configload"
    13  	"github.com/terramate-io/tf/depsfile"
    14  	"github.com/terramate-io/tf/plans"
    15  	"github.com/terramate-io/tf/states/statefile"
    16  )
    17  
    18  type CreateArgs struct {
    19  	// ConfigSnapshot is a snapshot of the configuration that the plan
    20  	// was created from.
    21  	ConfigSnapshot *configload.Snapshot
    22  
    23  	// PreviousRunStateFile is a representation of the state snapshot we used
    24  	// as the original input when creating this plan, containing the same
    25  	// information as recorded at the end of the previous apply except for
    26  	// upgrading managed resource instance data to the provider's latest
    27  	// schema versions.
    28  	PreviousRunStateFile *statefile.File
    29  
    30  	// BaseStateFile is a representation of the state snapshot we used to
    31  	// create the plan, which is the result of asking the providers to refresh
    32  	// all previously-stored objects to match the current situation in the
    33  	// remote system. (If this plan was created with refreshing disabled,
    34  	// this should be the same as PreviousRunStateFile.)
    35  	StateFile *statefile.File
    36  
    37  	// Plan records the plan itself, which is the main artifact inside a
    38  	// saved plan file.
    39  	Plan *plans.Plan
    40  
    41  	// DependencyLocks records the dependency lock information that we
    42  	// checked prior to creating the plan, so we can make sure that all of the
    43  	// same dependencies are still available when applying the plan.
    44  	DependencyLocks *depsfile.Locks
    45  }
    46  
    47  // Create creates a new plan file with the given filename, overwriting any
    48  // file that might already exist there.
    49  //
    50  // A plan file contains both a snapshot of the configuration and of the latest
    51  // state file in addition to the plan itself, so that Terraform can detect
    52  // if the world has changed since the plan was created and thus refuse to
    53  // apply it.
    54  func Create(filename string, args CreateArgs) error {
    55  	f, err := os.Create(filename)
    56  	if err != nil {
    57  		return err
    58  	}
    59  	defer f.Close()
    60  
    61  	zw := zip.NewWriter(f)
    62  	defer zw.Close()
    63  
    64  	// tfplan file
    65  	{
    66  		w, err := zw.CreateHeader(&zip.FileHeader{
    67  			Name:     tfplanFilename,
    68  			Method:   zip.Deflate,
    69  			Modified: time.Now(),
    70  		})
    71  		if err != nil {
    72  			return fmt.Errorf("failed to create tfplan file: %s", err)
    73  		}
    74  		err = writeTfplan(args.Plan, w)
    75  		if err != nil {
    76  			return fmt.Errorf("failed to write plan: %s", err)
    77  		}
    78  	}
    79  
    80  	// tfstate file
    81  	{
    82  		w, err := zw.CreateHeader(&zip.FileHeader{
    83  			Name:     tfstateFilename,
    84  			Method:   zip.Deflate,
    85  			Modified: time.Now(),
    86  		})
    87  		if err != nil {
    88  			return fmt.Errorf("failed to create embedded tfstate file: %s", err)
    89  		}
    90  		err = statefile.Write(args.StateFile, w)
    91  		if err != nil {
    92  			return fmt.Errorf("failed to write state snapshot: %s", err)
    93  		}
    94  	}
    95  
    96  	// tfstate-prev file
    97  	{
    98  		w, err := zw.CreateHeader(&zip.FileHeader{
    99  			Name:     tfstatePreviousFilename,
   100  			Method:   zip.Deflate,
   101  			Modified: time.Now(),
   102  		})
   103  		if err != nil {
   104  			return fmt.Errorf("failed to create embedded tfstate-prev file: %s", err)
   105  		}
   106  		err = statefile.Write(args.PreviousRunStateFile, w)
   107  		if err != nil {
   108  			return fmt.Errorf("failed to write previous state snapshot: %s", err)
   109  		}
   110  	}
   111  
   112  	// tfconfig directory
   113  	{
   114  		err := writeConfigSnapshot(args.ConfigSnapshot, zw)
   115  		if err != nil {
   116  			return fmt.Errorf("failed to write config snapshot: %s", err)
   117  		}
   118  	}
   119  
   120  	// .terraform.lock.hcl file, containing dependency lock information
   121  	if args.DependencyLocks != nil { // (this was a later addition, so not all callers set it, but main callers should)
   122  		src, diags := depsfile.SaveLocksToBytes(args.DependencyLocks)
   123  		if diags.HasErrors() {
   124  			return fmt.Errorf("failed to write embedded dependency lock file: %s", diags.Err().Error())
   125  		}
   126  
   127  		w, err := zw.CreateHeader(&zip.FileHeader{
   128  			Name:     dependencyLocksFilename,
   129  			Method:   zip.Deflate,
   130  			Modified: time.Now(),
   131  		})
   132  		if err != nil {
   133  			return fmt.Errorf("failed to create embedded dependency lock file: %s", err)
   134  		}
   135  		_, err = w.Write(src)
   136  		if err != nil {
   137  			return fmt.Errorf("failed to write embedded dependency lock file: %s", err)
   138  		}
   139  	}
   140  
   141  	return nil
   142  }