github.com/graywolf-at-work-2/terraform-vendor@v1.4.5/internal/plans/planfile/reader.go (about)

     1  package planfile
     2  
     3  import (
     4  	"archive/zip"
     5  	"bytes"
     6  	"fmt"
     7  	"io/ioutil"
     8  
     9  	"github.com/hashicorp/terraform/internal/configs"
    10  	"github.com/hashicorp/terraform/internal/configs/configload"
    11  	"github.com/hashicorp/terraform/internal/depsfile"
    12  	"github.com/hashicorp/terraform/internal/plans"
    13  	"github.com/hashicorp/terraform/internal/states/statefile"
    14  	"github.com/hashicorp/terraform/internal/tfdiags"
    15  )
    16  
    17  const tfstateFilename = "tfstate"
    18  const tfstatePreviousFilename = "tfstate-prev"
    19  const dependencyLocksFilename = ".terraform.lock.hcl" // matches the conventional name in an input configuration
    20  
    21  // Reader is the main type used to read plan files. Create a Reader by calling
    22  // Open.
    23  //
    24  // A plan file is a random-access file format, so methods of Reader must
    25  // be used to access the individual portions of the file for further
    26  // processing.
    27  type Reader struct {
    28  	zip *zip.ReadCloser
    29  }
    30  
    31  // Open creates a Reader for the file at the given filename, or returns an
    32  // error if the file doesn't seem to be a planfile.
    33  func Open(filename string) (*Reader, error) {
    34  	r, err := zip.OpenReader(filename)
    35  	if err != nil {
    36  		// To give a better error message, we'll sniff to see if this looks
    37  		// like our old plan format from versions prior to 0.12.
    38  		if b, sErr := ioutil.ReadFile(filename); sErr == nil {
    39  			if bytes.HasPrefix(b, []byte("tfplan")) {
    40  				return nil, fmt.Errorf("the given plan file was created by an earlier version of Terraform; plan files cannot be shared between different Terraform versions")
    41  			}
    42  		}
    43  		return nil, err
    44  	}
    45  
    46  	// Sniff to make sure this looks like a plan file, as opposed to any other
    47  	// random zip file the user might have around.
    48  	var planFile *zip.File
    49  	for _, file := range r.File {
    50  		if file.Name == tfplanFilename {
    51  			planFile = file
    52  			break
    53  		}
    54  	}
    55  	if planFile == nil {
    56  		return nil, fmt.Errorf("the given file is not a valid plan file")
    57  	}
    58  
    59  	// For now, we'll just accept the presence of the tfplan file as enough,
    60  	// and wait to validate the version when the caller requests the plan
    61  	// itself.
    62  
    63  	return &Reader{
    64  		zip: r,
    65  	}, nil
    66  }
    67  
    68  // ReadPlan reads the plan embedded in the plan file.
    69  //
    70  // Errors can be returned for various reasons, including if the plan file
    71  // is not of an appropriate format version, if it was created by a different
    72  // version of Terraform, if it is invalid, etc.
    73  func (r *Reader) ReadPlan() (*plans.Plan, error) {
    74  	var planFile *zip.File
    75  	for _, file := range r.zip.File {
    76  		if file.Name == tfplanFilename {
    77  			planFile = file
    78  			break
    79  		}
    80  	}
    81  	if planFile == nil {
    82  		// This should never happen because we checked for this file during
    83  		// Open, but we'll check anyway to be safe.
    84  		return nil, fmt.Errorf("the plan file is invalid")
    85  	}
    86  
    87  	pr, err := planFile.Open()
    88  	if err != nil {
    89  		return nil, fmt.Errorf("failed to retrieve plan from plan file: %s", err)
    90  	}
    91  	defer pr.Close()
    92  
    93  	// There's a slight mismatch in how plans.Plan is modeled vs. how
    94  	// the underlying plan file format works, because the "tfplan" embedded
    95  	// file contains only some top-level metadata and the planned changes,
    96  	// and not the previous run or prior states. Therefore we need to
    97  	// build this up in multiple steps.
    98  	// This is some technical debt because historically we considered the
    99  	// planned changes and prior state as totally separate, but later realized
   100  	// that it made sense for a plans.Plan to include the prior state directly
   101  	// so we can see what state the plan applies to. Hopefully later we'll
   102  	// clean this up some more so that we don't have two different ways to
   103  	// access the prior state (this and the ReadStateFile method).
   104  	ret, err := readTfplan(pr)
   105  	if err != nil {
   106  		return nil, err
   107  	}
   108  
   109  	prevRunStateFile, err := r.ReadPrevStateFile()
   110  	if err != nil {
   111  		return nil, fmt.Errorf("failed to read previous run state from plan file: %s", err)
   112  	}
   113  	priorStateFile, err := r.ReadStateFile()
   114  	if err != nil {
   115  		return nil, fmt.Errorf("failed to read prior state from plan file: %s", err)
   116  	}
   117  
   118  	ret.PrevRunState = prevRunStateFile.State
   119  	ret.PriorState = priorStateFile.State
   120  
   121  	return ret, nil
   122  }
   123  
   124  // ReadStateFile reads the state file embedded in the plan file, which
   125  // represents the "PriorState" as defined in plans.Plan.
   126  //
   127  // If the plan file contains no embedded state file, the returned error is
   128  // statefile.ErrNoState.
   129  func (r *Reader) ReadStateFile() (*statefile.File, error) {
   130  	for _, file := range r.zip.File {
   131  		if file.Name == tfstateFilename {
   132  			r, err := file.Open()
   133  			if err != nil {
   134  				return nil, fmt.Errorf("failed to extract state from plan file: %s", err)
   135  			}
   136  			return statefile.Read(r)
   137  		}
   138  	}
   139  	return nil, statefile.ErrNoState
   140  }
   141  
   142  // ReadPrevStateFile reads the previous state file embedded in the plan file, which
   143  // represents the "PrevRunState" as defined in plans.Plan.
   144  //
   145  // If the plan file contains no embedded previous state file, the returned error is
   146  // statefile.ErrNoState.
   147  func (r *Reader) ReadPrevStateFile() (*statefile.File, error) {
   148  	for _, file := range r.zip.File {
   149  		if file.Name == tfstatePreviousFilename {
   150  			r, err := file.Open()
   151  			if err != nil {
   152  				return nil, fmt.Errorf("failed to extract previous state from plan file: %s", err)
   153  			}
   154  			return statefile.Read(r)
   155  		}
   156  	}
   157  	return nil, statefile.ErrNoState
   158  }
   159  
   160  // ReadConfigSnapshot reads the configuration snapshot embedded in the plan
   161  // file.
   162  //
   163  // This is a lower-level alternative to ReadConfig that just extracts the
   164  // source files, without attempting to parse them.
   165  func (r *Reader) ReadConfigSnapshot() (*configload.Snapshot, error) {
   166  	return readConfigSnapshot(&r.zip.Reader)
   167  }
   168  
   169  // ReadConfig reads the configuration embedded in the plan file.
   170  //
   171  // Internally this function delegates to the configs/configload package to
   172  // parse the embedded configuration and so it returns diagnostics (rather than
   173  // a native Go error as with other methods on Reader).
   174  func (r *Reader) ReadConfig() (*configs.Config, tfdiags.Diagnostics) {
   175  	var diags tfdiags.Diagnostics
   176  
   177  	snap, err := r.ReadConfigSnapshot()
   178  	if err != nil {
   179  		diags = diags.Append(tfdiags.Sourceless(
   180  			tfdiags.Error,
   181  			"Failed to read configuration from plan file",
   182  			fmt.Sprintf("The configuration file snapshot in the plan file could not be read: %s.", err),
   183  		))
   184  		return nil, diags
   185  	}
   186  
   187  	loader := configload.NewLoaderFromSnapshot(snap)
   188  	rootDir := snap.Modules[""].Dir // Root module base directory
   189  	config, configDiags := loader.LoadConfig(rootDir)
   190  	diags = diags.Append(configDiags)
   191  
   192  	return config, diags
   193  }
   194  
   195  // ReadDependencyLocks reads the dependency lock information embedded in
   196  // the plan file.
   197  //
   198  // Some test codepaths create plan files without dependency lock information,
   199  // but all main codepaths should populate this. If reading a file without
   200  // the dependency information, this will return error diagnostics.
   201  func (r *Reader) ReadDependencyLocks() (*depsfile.Locks, tfdiags.Diagnostics) {
   202  	var diags tfdiags.Diagnostics
   203  
   204  	for _, file := range r.zip.File {
   205  		if file.Name == dependencyLocksFilename {
   206  			r, err := file.Open()
   207  			if err != nil {
   208  				diags = diags.Append(tfdiags.Sourceless(
   209  					tfdiags.Error,
   210  					"Failed to read dependency locks from plan file",
   211  					fmt.Sprintf("Couldn't read the dependency lock information embedded in the plan file: %s.", err),
   212  				))
   213  				return nil, diags
   214  			}
   215  			src, err := ioutil.ReadAll(r)
   216  			if err != nil {
   217  				diags = diags.Append(tfdiags.Sourceless(
   218  					tfdiags.Error,
   219  					"Failed to read dependency locks from plan file",
   220  					fmt.Sprintf("Couldn't read the dependency lock information embedded in the plan file: %s.", err),
   221  				))
   222  				return nil, diags
   223  			}
   224  			locks, moreDiags := depsfile.LoadLocksFromBytes(src, "<saved-plan>")
   225  			diags = diags.Append(moreDiags)
   226  			return locks, diags
   227  		}
   228  	}
   229  
   230  	// If we fall out here then this is a file without dependency information.
   231  	diags = diags.Append(tfdiags.Sourceless(
   232  		tfdiags.Error,
   233  		"Saved plan has no dependency lock information",
   234  		"The specified saved plan file does not include any dependency lock information. This is a bug in the previous run of Terraform that created this file.",
   235  	))
   236  	return nil, diags
   237  }
   238  
   239  // Close closes the file, after which no other operations may be performed.
   240  func (r *Reader) Close() error {
   241  	return r.zip.Close()
   242  }