github.com/paultyng/terraform@v0.6.11-0.20180227224804-66ff8f8bed40/configs/configload/module_manifest.go (about)

     1  package configload
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"os"
     7  	"path/filepath"
     8  	"strings"
     9  
    10  	version "github.com/hashicorp/go-version"
    11  )
    12  
    13  // moduleRecord represents some metadata about an installed module, as part
    14  // of a moduleManifest.
    15  type moduleRecord struct {
    16  	// Key is a unique identifier for this particular module, based on its
    17  	// position within the static module tree.
    18  	Key string `json:"Key"`
    19  
    20  	// SourceAddr is the source address given for this module in configuration.
    21  	// This is used only to detect if the source was changed in configuration
    22  	// since the module was last installed, which means that the installer
    23  	// must re-install it.
    24  	SourceAddr string `json:"Source"`
    25  
    26  	// Version is the exact version of the module, which results from parsing
    27  	// VersionStr. nil for un-versioned modules.
    28  	Version *version.Version `json:"-"`
    29  
    30  	// VersionStr is the version specifier string. This is used only for
    31  	// serialization in snapshots and should not be accessed or updated
    32  	// by any other codepaths; use "Version" instead.
    33  	VersionStr string `json:"Version,omitempty"`
    34  
    35  	// Dir is the path to the local directory where the module is installed.
    36  	Dir string `json:"Dir"`
    37  }
    38  
    39  // moduleManifest is a map used to keep track of the filesystem locations
    40  // and other metadata about installed modules.
    41  //
    42  // The configuration loader refers to this, while the module installer updates
    43  // it to reflect any changes to the installed modules.
    44  type moduleManifest map[string]moduleRecord
    45  
    46  func manifestKey(path []string) string {
    47  	return strings.Join(path, ".")
    48  }
    49  
    50  // manifestSnapshotFile is an internal struct used only to assist in our JSON
    51  // serializtion of manifest snapshots. It should not be used for any other
    52  // purposes.
    53  type manifestSnapshotFile struct {
    54  	Records []moduleRecord `json:"Modules"`
    55  }
    56  
    57  const manifestFilename = "modules.json"
    58  
    59  func (m *moduleMgr) manifestSnapshotPath() string {
    60  	return filepath.Join(m.Dir, manifestFilename)
    61  }
    62  
    63  // readModuleManifestSnapshot loads a manifest snapshot from the filesystem.
    64  func (m *moduleMgr) readModuleManifestSnapshot() error {
    65  	src, err := m.FS.ReadFile(m.manifestSnapshotPath())
    66  	if err != nil {
    67  		if os.IsNotExist(err) {
    68  			// We'll treat a missing file as an empty manifest
    69  			m.manifest = make(moduleManifest)
    70  			return nil
    71  		}
    72  		return err
    73  	}
    74  
    75  	if len(src) == 0 {
    76  		// This should never happen, but we'll tolerate it as if it were
    77  		// a valid empty JSON object.
    78  		m.manifest = make(moduleManifest)
    79  		return nil
    80  	}
    81  
    82  	var read manifestSnapshotFile
    83  	err = json.Unmarshal(src, &read)
    84  
    85  	new := make(moduleManifest)
    86  	for _, record := range read.Records {
    87  		if record.VersionStr != "" {
    88  			record.Version, err = version.NewVersion(record.VersionStr)
    89  			if err != nil {
    90  				return fmt.Errorf("invalid version %q for %s: %s", record.VersionStr, record.Key, err)
    91  			}
    92  		}
    93  		if _, exists := new[record.Key]; exists {
    94  			// This should never happen in any valid file, so we'll catch it
    95  			// and report it to avoid confusing/undefined behavior if the
    96  			// snapshot file was edited incorrectly outside of Terraform.
    97  			return fmt.Errorf("snapshot file contains two records for path %s", record.Key)
    98  		}
    99  		new[record.Key] = record
   100  	}
   101  
   102  	m.manifest = new
   103  
   104  	return nil
   105  }
   106  
   107  // writeModuleManifestSnapshot writes a snapshot of the current manifest
   108  // to the filesystem.
   109  //
   110  // The caller must guarantee no concurrent modifications of the manifest for
   111  // the duration of a call to this function, or the behavior is undefined.
   112  func (m *moduleMgr) writeModuleManifestSnapshot() error {
   113  	var write manifestSnapshotFile
   114  
   115  	for _, record := range m.manifest {
   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  		write.Records = append(write.Records, record)
   124  	}
   125  
   126  	src, err := json.Marshal(write)
   127  	if err != nil {
   128  		return err
   129  	}
   130  
   131  	return m.FS.WriteFile(m.manifestSnapshotPath(), src, os.ModePerm)
   132  }