github.com/cycloidio/terraform@v1.1.10-0.20220513142504-76d5c768dc63/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  	"strings"
    12  
    13  	version "github.com/hashicorp/go-version"
    14  
    15  	"github.com/cycloidio/terraform/addrs"
    16  )
    17  
    18  // Record represents some metadata about an installed module, as part
    19  // of a ModuleManifest.
    20  type Record struct {
    21  	// Key is a unique identifier for this particular module, based on its
    22  	// position within the static module tree.
    23  	Key string `json:"Key"`
    24  
    25  	// SourceAddr is the source address given for this module in configuration.
    26  	// This is used only to detect if the source was changed in configuration
    27  	// since the module was last installed, which means that the installer
    28  	// must re-install it.
    29  	//
    30  	// This should always be the result of calling method String on an
    31  	// addrs.ModuleSource value, to get a suitably-normalized result.
    32  	SourceAddr string `json:"Source"`
    33  
    34  	// Version is the exact version of the module, which results from parsing
    35  	// VersionStr. nil for un-versioned modules.
    36  	Version *version.Version `json:"-"`
    37  
    38  	// VersionStr is the version specifier string. This is used only for
    39  	// serialization in snapshots and should not be accessed or updated
    40  	// by any other codepaths; use "Version" instead.
    41  	VersionStr string `json:"Version,omitempty"`
    42  
    43  	// Dir is the path to the local directory where the module is installed.
    44  	Dir string `json:"Dir"`
    45  }
    46  
    47  // Manifest is a map used to keep track of the filesystem locations
    48  // and other metadata about installed modules.
    49  //
    50  // The configuration loader refers to this, while the module installer updates
    51  // it to reflect any changes to the installed modules.
    52  type Manifest map[string]Record
    53  
    54  func (m Manifest) ModuleKey(path addrs.Module) string {
    55  	if len(path) == 0 {
    56  		return ""
    57  	}
    58  	return strings.Join([]string(path), ".")
    59  
    60  }
    61  
    62  // manifestSnapshotFile is an internal struct used only to assist in our JSON
    63  // serialization of manifest snapshots. It should not be used for any other
    64  // purpose.
    65  type manifestSnapshotFile struct {
    66  	Records []Record `json:"Modules"`
    67  }
    68  
    69  func ReadManifestSnapshot(r io.Reader) (Manifest, error) {
    70  	src, err := ioutil.ReadAll(r)
    71  	if err != nil {
    72  		return nil, 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  		return make(Manifest), nil
    79  	}
    80  
    81  	var read manifestSnapshotFile
    82  	err = json.Unmarshal(src, &read)
    83  	if err != nil {
    84  		return nil, fmt.Errorf("error unmarshalling snapshot: %v", err)
    85  	}
    86  	new := make(Manifest)
    87  	for _, record := range read.Records {
    88  		if record.VersionStr != "" {
    89  			record.Version, err = version.NewVersion(record.VersionStr)
    90  			if err != nil {
    91  				return nil, fmt.Errorf("invalid version %q for %s: %s", record.VersionStr, record.Key, err)
    92  			}
    93  		}
    94  
    95  		// Historically we didn't normalize the module source addresses when
    96  		// writing them into the manifest, and so we'll make a best effort
    97  		// to normalize them back in on read so that we can just gracefully
    98  		// upgrade on the next "terraform init".
    99  		if record.SourceAddr != "" {
   100  			if addr, err := addrs.ParseModuleSource(record.SourceAddr); err == nil {
   101  				// This is a best effort sort of thing. If the source
   102  				// address isn't valid then we'll just leave it as-is
   103  				// and let another component detect that downstream,
   104  				// to preserve the old behavior in that case.
   105  				record.SourceAddr = addr.String()
   106  			}
   107  		}
   108  
   109  		// Ensure Windows is using the proper modules path format after
   110  		// reading the modules manifest Dir records
   111  		record.Dir = filepath.FromSlash(record.Dir)
   112  
   113  		if _, exists := new[record.Key]; exists {
   114  			// This should never happen in any valid file, so we'll catch it
   115  			// and report it to avoid confusing/undefined behavior if the
   116  			// snapshot file was edited incorrectly outside of Terraform.
   117  			return nil, fmt.Errorf("snapshot file contains two records for path %s", record.Key)
   118  		}
   119  		new[record.Key] = record
   120  	}
   121  	return new, nil
   122  }
   123  
   124  func ReadManifestSnapshotForDir(dir string) (Manifest, error) {
   125  	fn := filepath.Join(dir, ManifestSnapshotFilename)
   126  	r, err := os.Open(fn)
   127  	if err != nil {
   128  		if os.IsNotExist(err) {
   129  			return make(Manifest), nil // missing file is okay and treated as empty
   130  		}
   131  		return nil, err
   132  	}
   133  	return ReadManifestSnapshot(r)
   134  }
   135  
   136  func (m Manifest) WriteSnapshot(w io.Writer) error {
   137  	var write manifestSnapshotFile
   138  
   139  	for _, record := range m {
   140  		// Make sure VersionStr is in sync with Version, since we encourage
   141  		// callers to manipulate Version and ignore VersionStr.
   142  		if record.Version != nil {
   143  			record.VersionStr = record.Version.String()
   144  		} else {
   145  			record.VersionStr = ""
   146  		}
   147  
   148  		// Ensure Dir is written in a format that can be read by Linux and
   149  		// Windows nodes for remote and apply compatibility
   150  		record.Dir = filepath.ToSlash(record.Dir)
   151  		write.Records = append(write.Records, record)
   152  	}
   153  
   154  	src, err := json.Marshal(write)
   155  	if err != nil {
   156  		return err
   157  	}
   158  
   159  	_, err = w.Write(src)
   160  	return err
   161  }
   162  
   163  func (m Manifest) WriteSnapshotToDir(dir string) error {
   164  	fn := filepath.Join(dir, ManifestSnapshotFilename)
   165  	log.Printf("[TRACE] modsdir: writing modules manifest to %s", fn)
   166  	w, err := os.Create(fn)
   167  	if err != nil {
   168  		return err
   169  	}
   170  	return m.WriteSnapshot(w)
   171  }