github.com/rstandt/terraform@v0.12.32-0.20230710220336-b1063613405c/internal/modsdir/manifest.go (about)

     1  package modsdir
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"io"
     7  	"io/ioutil"
     8  	"log"
     9  	"os"
    10  	"path/filepath"
    11  
    12  	version "github.com/hashicorp/go-version"
    13  
    14  	"github.com/hashicorp/terraform/addrs"
    15  )
    16  
    17  // Record represents some metadata about an installed module, as part
    18  // of a ModuleManifest.
    19  type Record struct {
    20  	// Key is a unique identifier for this particular module, based on its
    21  	// position within the static module tree.
    22  	Key string `json:"Key"`
    23  
    24  	// SourceAddr is the source address given for this module in configuration.
    25  	// This is used only to detect if the source was changed in configuration
    26  	// since the module was last installed, which means that the installer
    27  	// must re-install it.
    28  	SourceAddr string `json:"Source"`
    29  
    30  	// Version is the exact version of the module, which results from parsing
    31  	// VersionStr. nil for un-versioned modules.
    32  	Version *version.Version `json:"-"`
    33  
    34  	// VersionStr is the version specifier string. This is used only for
    35  	// serialization in snapshots and should not be accessed or updated
    36  	// by any other codepaths; use "Version" instead.
    37  	VersionStr string `json:"Version,omitempty"`
    38  
    39  	// Dir is the path to the local directory where the module is installed.
    40  	Dir string `json:"Dir"`
    41  }
    42  
    43  // Manifest is a map used to keep track of the filesystem locations
    44  // and other metadata about installed modules.
    45  //
    46  // The configuration loader refers to this, while the module installer updates
    47  // it to reflect any changes to the installed modules.
    48  type Manifest map[string]Record
    49  
    50  func (m Manifest) ModuleKey(path addrs.Module) string {
    51  	return path.String()
    52  }
    53  
    54  // manifestSnapshotFile is an internal struct used only to assist in our JSON
    55  // serialization of manifest snapshots. It should not be used for any other
    56  // purpose.
    57  type manifestSnapshotFile struct {
    58  	Records []Record `json:"Modules"`
    59  }
    60  
    61  func ReadManifestSnapshot(r io.Reader) (Manifest, error) {
    62  	src, err := ioutil.ReadAll(r)
    63  	if err != nil {
    64  		return nil, err
    65  	}
    66  
    67  	if len(src) == 0 {
    68  		// This should never happen, but we'll tolerate it as if it were
    69  		// a valid empty JSON object.
    70  		return make(Manifest), nil
    71  	}
    72  
    73  	var read manifestSnapshotFile
    74  	err = json.Unmarshal(src, &read)
    75  
    76  	new := make(Manifest)
    77  	for _, record := range read.Records {
    78  		if record.VersionStr != "" {
    79  			record.Version, err = version.NewVersion(record.VersionStr)
    80  			if err != nil {
    81  				return nil, fmt.Errorf("invalid version %q for %s: %s", record.VersionStr, record.Key, err)
    82  			}
    83  		}
    84  
    85  		// Ensure Windows is using the proper modules path format after
    86  		// reading the modules manifest Dir records
    87  		record.Dir = filepath.FromSlash(record.Dir)
    88  
    89  		if _, exists := new[record.Key]; exists {
    90  			// This should never happen in any valid file, so we'll catch it
    91  			// and report it to avoid confusing/undefined behavior if the
    92  			// snapshot file was edited incorrectly outside of Terraform.
    93  			return nil, fmt.Errorf("snapshot file contains two records for path %s", record.Key)
    94  		}
    95  		new[record.Key] = record
    96  	}
    97  	return new, nil
    98  }
    99  
   100  func ReadManifestSnapshotForDir(dir string) (Manifest, error) {
   101  	fn := filepath.Join(dir, ManifestSnapshotFilename)
   102  	r, err := os.Open(fn)
   103  	if err != nil {
   104  		if os.IsNotExist(err) {
   105  			return make(Manifest), nil // missing file is okay and treated as empty
   106  		}
   107  		return nil, err
   108  	}
   109  	return ReadManifestSnapshot(r)
   110  }
   111  
   112  func (m Manifest) WriteSnapshot(w io.Writer) error {
   113  	var write manifestSnapshotFile
   114  
   115  	for _, record := range m {
   116  		// Make sure VersionStr is in sync with Version, since we encourage
   117  		// callers to manipulate Version and ignore VersionStr.
   118  		if record.Version != nil {
   119  			record.VersionStr = record.Version.String()
   120  		} else {
   121  			record.VersionStr = ""
   122  		}
   123  
   124  		// Ensure Dir is written in a format that can be read by Linux and
   125  		// Windows nodes for remote and apply compatibility
   126  		record.Dir = filepath.ToSlash(record.Dir)
   127  		write.Records = append(write.Records, record)
   128  	}
   129  
   130  	src, err := json.Marshal(write)
   131  	if err != nil {
   132  		return err
   133  	}
   134  
   135  	_, err = w.Write(src)
   136  	return err
   137  }
   138  
   139  func (m Manifest) WriteSnapshotToDir(dir string) error {
   140  	fn := filepath.Join(dir, ManifestSnapshotFilename)
   141  	log.Printf("[TRACE] modsdir: writing modules manifest to %s", fn)
   142  	w, err := os.Create(fn)
   143  	if err != nil {
   144  		return err
   145  	}
   146  	return m.WriteSnapshot(w)
   147  }