github.com/jaredpalmer/terraform@v1.1.0-alpha20210908.0.20210911170307-88705c943a03/internal/depsfile/locks.go (about)

     1  package depsfile
     2  
     3  import (
     4  	"fmt"
     5  	"sort"
     6  
     7  	"github.com/hashicorp/terraform/internal/addrs"
     8  	"github.com/hashicorp/terraform/internal/getproviders"
     9  )
    10  
    11  // Locks is the top-level type representing the information retained in a
    12  // dependency lock file.
    13  //
    14  // Locks and the other types used within it are mutable via various setter
    15  // methods, but they are not safe for concurrent  modifications, so it's the
    16  // caller's responsibility to prevent concurrent writes and writes concurrent
    17  // with reads.
    18  type Locks struct {
    19  	providers map[addrs.Provider]*ProviderLock
    20  
    21  	// TODO: In future we'll also have module locks, but the design of that
    22  	// still needs some more work and we're deferring that to get the
    23  	// provider locking capability out sooner, because it's more common to
    24  	// directly depend on providers maintained outside your organization than
    25  	// modules maintained outside your organization.
    26  
    27  	// sources is a copy of the map of source buffers produced by the HCL
    28  	// parser during loading, which we retain only so that the caller can
    29  	// use it to produce source code snippets in error messages.
    30  	sources map[string][]byte
    31  }
    32  
    33  // NewLocks constructs and returns a new Locks object that initially contains
    34  // no locks at all.
    35  func NewLocks() *Locks {
    36  	return &Locks{
    37  		providers: make(map[addrs.Provider]*ProviderLock),
    38  
    39  		// no "sources" here, because that's only for locks objects loaded
    40  		// from files.
    41  	}
    42  }
    43  
    44  // Provider returns the stored lock for the given provider, or nil if that
    45  // provider currently has no lock.
    46  func (l *Locks) Provider(addr addrs.Provider) *ProviderLock {
    47  	return l.providers[addr]
    48  }
    49  
    50  // AllProviders returns a map describing all of the provider locks in the
    51  // receiver.
    52  func (l *Locks) AllProviders() map[addrs.Provider]*ProviderLock {
    53  	// We return a copy of our internal map so that future calls to
    54  	// SetProvider won't modify the map we're returning, or vice-versa.
    55  	ret := make(map[addrs.Provider]*ProviderLock, len(l.providers))
    56  	for k, v := range l.providers {
    57  		ret[k] = v
    58  	}
    59  	return ret
    60  }
    61  
    62  // SetProvider creates a new lock or replaces the existing lock for the given
    63  // provider.
    64  //
    65  // SetProvider returns the newly-created provider lock object, which
    66  // invalidates any ProviderLock object previously returned from Provider or
    67  // SetProvider for the given provider address.
    68  //
    69  // The ownership of the backing array for the slice of hashes passes to this
    70  // function, and so the caller must not read or write that backing array after
    71  // calling SetProvider.
    72  //
    73  // Only lockable providers can be passed to this method. If you pass a
    74  // non-lockable provider address then this function will panic. Use
    75  // function ProviderIsLockable to determine whether a particular provider
    76  // should participate in the version locking mechanism.
    77  func (l *Locks) SetProvider(addr addrs.Provider, version getproviders.Version, constraints getproviders.VersionConstraints, hashes []getproviders.Hash) *ProviderLock {
    78  	if !ProviderIsLockable(addr) {
    79  		panic(fmt.Sprintf("Locks.SetProvider with non-lockable provider %s", addr))
    80  	}
    81  
    82  	new := NewProviderLock(addr, version, constraints, hashes)
    83  	l.providers[new.addr] = new
    84  	return new
    85  }
    86  
    87  // NewProviderLock creates a new ProviderLock object that isn't associated
    88  // with any Locks object.
    89  //
    90  // This is here primarily for testing. Most callers should use Locks.SetProvider
    91  // to construct a new provider lock and insert it into a Locks object at the
    92  // same time.
    93  //
    94  // The ownership of the backing array for the slice of hashes passes to this
    95  // function, and so the caller must not read or write that backing array after
    96  // calling NewProviderLock.
    97  //
    98  // Only lockable providers can be passed to this method. If you pass a
    99  // non-lockable provider address then this function will panic. Use
   100  // function ProviderIsLockable to determine whether a particular provider
   101  // should participate in the version locking mechanism.
   102  func NewProviderLock(addr addrs.Provider, version getproviders.Version, constraints getproviders.VersionConstraints, hashes []getproviders.Hash) *ProviderLock {
   103  	if !ProviderIsLockable(addr) {
   104  		panic(fmt.Sprintf("Locks.NewProviderLock with non-lockable provider %s", addr))
   105  	}
   106  
   107  	// Normalize the hashes into lexical order so that we can do straightforward
   108  	// equality tests between different locks for the same provider. The
   109  	// hashes are logically a set, so the given order is insignificant.
   110  	sort.Slice(hashes, func(i, j int) bool {
   111  		return string(hashes[i]) < string(hashes[j])
   112  	})
   113  
   114  	// This is a slightly-tricky in-place deduping to avoid unnecessarily
   115  	// allocating a new array in the common case where there are no duplicates:
   116  	// we iterate over "hashes" at the same time as appending to another slice
   117  	// with the same backing array, relying on the fact that deduping can only
   118  	// _skip_ elements from the input, and will never generate additional ones
   119  	// that would cause the writer to get ahead of the reader. This also
   120  	// assumes that we already sorted the items, which means that any duplicates
   121  	// will be consecutive in the sequence.
   122  	dedupeHashes := hashes[:0]
   123  	prevHash := getproviders.NilHash
   124  	for _, hash := range hashes {
   125  		if hash != prevHash {
   126  			dedupeHashes = append(dedupeHashes, hash)
   127  			prevHash = hash
   128  		}
   129  	}
   130  
   131  	return &ProviderLock{
   132  		addr:               addr,
   133  		version:            version,
   134  		versionConstraints: constraints,
   135  		hashes:             dedupeHashes,
   136  	}
   137  }
   138  
   139  // ProviderIsLockable returns true if the given provider is eligible for
   140  // version locking.
   141  //
   142  // Currently, all providers except builtin and legacy providers are eligible
   143  // for locking.
   144  func ProviderIsLockable(addr addrs.Provider) bool {
   145  	return !(addr.IsBuiltIn() || addr.IsLegacy())
   146  }
   147  
   148  // Sources returns the source code of the file the receiver was generated from,
   149  // or an empty map if the receiver wasn't generated from a file.
   150  //
   151  // This return type matches the one expected by HCL diagnostics printers to
   152  // produce source code snapshots, which is the only intended use for this
   153  // method.
   154  func (l *Locks) Sources() map[string][]byte {
   155  	return l.sources
   156  }
   157  
   158  // Equal returns true if the given Locks represents the same information as
   159  // the receiver.
   160  //
   161  // Equal explicitly _does not_ consider the equality of version constraints
   162  // in the saved locks, because those are saved only as hints to help the UI
   163  // explain what's changed between runs, and are never used as part of
   164  // dependency installation decisions.
   165  func (l *Locks) Equal(other *Locks) bool {
   166  	if len(l.providers) != len(other.providers) {
   167  		return false
   168  	}
   169  	for addr, thisLock := range l.providers {
   170  		otherLock, ok := other.providers[addr]
   171  		if !ok {
   172  			return false
   173  		}
   174  
   175  		if thisLock.addr != otherLock.addr {
   176  			// It'd be weird to get here because we already looked these up
   177  			// by address above.
   178  			return false
   179  		}
   180  		if thisLock.version != otherLock.version {
   181  			// Equality rather than "Version.Same" because changes to the
   182  			// build metadata are significant for the purpose of this function:
   183  			// it's a different package even if it has the same precedence.
   184  			return false
   185  		}
   186  
   187  		// Although "hashes" is declared as a slice, it's logically an
   188  		// unordered set. However, we normalize the slice of hashes when
   189  		// recieving it in NewProviderLock, so we can just do a simple
   190  		// item-by-item equality test here.
   191  		if len(thisLock.hashes) != len(otherLock.hashes) {
   192  			return false
   193  		}
   194  		for i := range thisLock.hashes {
   195  			if thisLock.hashes[i] != otherLock.hashes[i] {
   196  				return false
   197  			}
   198  		}
   199  	}
   200  	// We don't need to worry about providers that are in "other" but not
   201  	// in the receiver, because we tested the lengths being equal above.
   202  
   203  	return true
   204  }
   205  
   206  // EqualProviderAddress returns true if the given Locks have the same provider
   207  // address as the receiver. This doesn't check version and hashes.
   208  func (l *Locks) EqualProviderAddress(other *Locks) bool {
   209  	if len(l.providers) != len(other.providers) {
   210  		return false
   211  	}
   212  
   213  	for addr := range l.providers {
   214  		_, ok := other.providers[addr]
   215  		if !ok {
   216  			return false
   217  		}
   218  	}
   219  
   220  	return true
   221  }
   222  
   223  // Empty returns true if the given Locks object contains no actual locks.
   224  //
   225  // UI code might wish to use this to distinguish a lock file being
   226  // written for the first time from subsequent updates to that lock file.
   227  func (l *Locks) Empty() bool {
   228  	return len(l.providers) == 0
   229  }
   230  
   231  // DeepCopy creates a new Locks that represents the same information as the
   232  // receiver but does not share memory for any parts of the structure that.
   233  // are mutable through methods on Locks.
   234  //
   235  // Note that this does _not_ create deep copies of parts of the structure
   236  // that are technically mutable but are immutable by convention, such as the
   237  // array underlying the slice of version constraints. Callers may mutate the
   238  // resulting data structure only via the direct methods of Locks.
   239  func (l *Locks) DeepCopy() *Locks {
   240  	ret := NewLocks()
   241  	for addr, lock := range l.providers {
   242  		var hashes []getproviders.Hash
   243  		if len(lock.hashes) > 0 {
   244  			hashes = make([]getproviders.Hash, len(lock.hashes))
   245  			copy(hashes, lock.hashes)
   246  		}
   247  		ret.SetProvider(addr, lock.version, lock.versionConstraints, hashes)
   248  	}
   249  	return ret
   250  }
   251  
   252  // ProviderLock represents lock information for a specific provider.
   253  type ProviderLock struct {
   254  	// addr is the address of the provider this lock applies to.
   255  	addr addrs.Provider
   256  
   257  	// version is the specific version that was previously selected, while
   258  	// versionConstraints is the constraint that was used to make that
   259  	// selection, which we can potentially use to hint to run
   260  	// e.g. terraform init -upgrade if a user has changed a version
   261  	// constraint but the previous selection still remains valid.
   262  	// "version" is therefore authoritative, while "versionConstraints" is
   263  	// just for a UI hint and not used to make any real decisions.
   264  	version            getproviders.Version
   265  	versionConstraints getproviders.VersionConstraints
   266  
   267  	// hashes contains zero or more hashes of packages or package contents
   268  	// for the package associated with the selected version across all of
   269  	// the supported platforms.
   270  	//
   271  	// hashes can contain a mixture of hashes in different formats to support
   272  	// changes over time. The new-style hash format is to have a string
   273  	// starting with "h" followed by a version number and then a colon, like
   274  	// "h1:" for the first hash format version. Other hash versions following
   275  	// this scheme may come later. These versioned hash schemes are implemented
   276  	// in the getproviders package; for example, "h1:" is implemented in
   277  	// getproviders.HashV1 .
   278  	//
   279  	// There is also a legacy hash format which is just a lowercase-hex-encoded
   280  	// SHA256 hash of the official upstream .zip file for the selected version.
   281  	// We'll allow as that a stop-gap until we can upgrade Terraform Registry
   282  	// to support the new scheme, but is non-ideal because we can verify it only
   283  	// when we have the original .zip file exactly; we can't verify a local
   284  	// directory containing the unpacked contents of that .zip file.
   285  	//
   286  	// We ideally want to populate hashes for all available platforms at
   287  	// once, by referring to the signed checksums file in the upstream
   288  	// registry. In that ideal case it's possible to later work with the same
   289  	// configuration on a different platform while still verifying the hashes.
   290  	// However, installation from any method other than an origin registry
   291  	// means we can only populate the hash for the current platform, and so
   292  	// it won't be possible to verify a subsequent installation of the same
   293  	// provider on a different platform.
   294  	hashes []getproviders.Hash
   295  }
   296  
   297  // Provider returns the address of the provider this lock applies to.
   298  func (l *ProviderLock) Provider() addrs.Provider {
   299  	return l.addr
   300  }
   301  
   302  // Version returns the currently-selected version for the corresponding provider.
   303  func (l *ProviderLock) Version() getproviders.Version {
   304  	return l.version
   305  }
   306  
   307  // VersionConstraints returns the version constraints that were recorded as
   308  // being used to choose the version returned by Version.
   309  //
   310  // These version constraints are not authoritative for future selections and
   311  // are included only so Terraform can detect if the constraints in
   312  // configuration have changed since a selection was made, and thus hint to the
   313  // user that they may need to run terraform init -upgrade to apply the new
   314  // constraints.
   315  func (l *ProviderLock) VersionConstraints() getproviders.VersionConstraints {
   316  	return l.versionConstraints
   317  }
   318  
   319  // AllHashes returns all of the package hashes that were recorded when this
   320  // lock was created. If no hashes were recorded for that platform, the result
   321  // is a zero-length slice.
   322  //
   323  // If your intent is to verify a package against the recorded hashes, use
   324  // PreferredHashes to get only the hashes which the current version
   325  // of Terraform considers the strongest of the available hashing schemes, one
   326  // of which must match in order for verification to be considered successful.
   327  //
   328  // Do not modify the backing array of the returned slice.
   329  func (l *ProviderLock) AllHashes() []getproviders.Hash {
   330  	return l.hashes
   331  }
   332  
   333  // PreferredHashes returns a filtered version of the AllHashes return value
   334  // which includes only the strongest of the availabile hash schemes, in
   335  // case legacy hash schemes are deprecated over time but still supported for
   336  // upgrade purposes.
   337  //
   338  // At least one of the given hashes must match for a package to be considered
   339  // valud.
   340  func (l *ProviderLock) PreferredHashes() []getproviders.Hash {
   341  	return getproviders.PreferredHashes(l.hashes)
   342  }