github.com/kanishk98/terraform@v1.3.0-dev.0.20220917174235-661ca8088a6a/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  	// overriddenProviders is a subset of providers which we might be tracking
    22  	// in field providers but whose lock information we're disregarding for
    23  	// this particular run due to some feature that forces Terraform to not
    24  	// use a normally-installed plugin for it. For example, the "provider dev
    25  	// overrides" feature means that we'll be using an arbitrary directory on
    26  	// disk as the package, regardless of what might be selected in "providers".
    27  	//
    28  	// overriddenProviders is an in-memory-only annotation, never stored as
    29  	// part of a lock file and thus not persistent between Terraform runs.
    30  	// The CLI layer is generally the one responsible for populating this,
    31  	// by calling SetProviderOverridden in response to CLI Configuration
    32  	// settings, environment variables, or whatever similar sources.
    33  	overriddenProviders map[addrs.Provider]struct{}
    34  
    35  	// TODO: In future we'll also have module locks, but the design of that
    36  	// still needs some more work and we're deferring that to get the
    37  	// provider locking capability out sooner, because it's more common to
    38  	// directly depend on providers maintained outside your organization than
    39  	// modules maintained outside your organization.
    40  
    41  	// sources is a copy of the map of source buffers produced by the HCL
    42  	// parser during loading, which we retain only so that the caller can
    43  	// use it to produce source code snippets in error messages.
    44  	sources map[string][]byte
    45  }
    46  
    47  // NewLocks constructs and returns a new Locks object that initially contains
    48  // no locks at all.
    49  func NewLocks() *Locks {
    50  	return &Locks{
    51  		providers: make(map[addrs.Provider]*ProviderLock),
    52  
    53  		// no "sources" here, because that's only for locks objects loaded
    54  		// from files.
    55  	}
    56  }
    57  
    58  // Provider returns the stored lock for the given provider, or nil if that
    59  // provider currently has no lock.
    60  func (l *Locks) Provider(addr addrs.Provider) *ProviderLock {
    61  	return l.providers[addr]
    62  }
    63  
    64  // AllProviders returns a map describing all of the provider locks in the
    65  // receiver.
    66  func (l *Locks) AllProviders() map[addrs.Provider]*ProviderLock {
    67  	// We return a copy of our internal map so that future calls to
    68  	// SetProvider won't modify the map we're returning, or vice-versa.
    69  	ret := make(map[addrs.Provider]*ProviderLock, len(l.providers))
    70  	for k, v := range l.providers {
    71  		ret[k] = v
    72  	}
    73  	return ret
    74  }
    75  
    76  // SetProvider creates a new lock or replaces the existing lock for the given
    77  // provider.
    78  //
    79  // SetProvider returns the newly-created provider lock object, which
    80  // invalidates any ProviderLock object previously returned from Provider or
    81  // SetProvider for the given provider address.
    82  //
    83  // The ownership of the backing array for the slice of hashes passes to this
    84  // function, and so the caller must not read or write that backing array after
    85  // calling SetProvider.
    86  //
    87  // Only lockable providers can be passed to this method. If you pass a
    88  // non-lockable provider address then this function will panic. Use
    89  // function ProviderIsLockable to determine whether a particular provider
    90  // should participate in the version locking mechanism.
    91  func (l *Locks) SetProvider(addr addrs.Provider, version getproviders.Version, constraints getproviders.VersionConstraints, hashes []getproviders.Hash) *ProviderLock {
    92  	if !ProviderIsLockable(addr) {
    93  		panic(fmt.Sprintf("Locks.SetProvider with non-lockable provider %s", addr))
    94  	}
    95  
    96  	new := NewProviderLock(addr, version, constraints, hashes)
    97  	l.providers[new.addr] = new
    98  	return new
    99  }
   100  
   101  // RemoveProvider removes any existing lock file entry for the given provider.
   102  //
   103  // If the given provider did not already have a lock entry, RemoveProvider is
   104  // a no-op.
   105  //
   106  // Only lockable providers can be passed to this method. If you pass a
   107  // non-lockable provider address then this function will panic. Use
   108  // function ProviderIsLockable to determine whether a particular provider
   109  // should participate in the version locking mechanism.
   110  func (l *Locks) RemoveProvider(addr addrs.Provider) {
   111  	if !ProviderIsLockable(addr) {
   112  		panic(fmt.Sprintf("Locks.RemoveProvider with non-lockable provider %s", addr))
   113  	}
   114  
   115  	delete(l.providers, addr)
   116  }
   117  
   118  // SetProviderOverridden records that this particular Terraform process will
   119  // not pay attention to the recorded lock entry for the given provider, and
   120  // will instead access that provider's functionality in some other special
   121  // way that isn't sensitive to provider version selections or checksums.
   122  //
   123  // This is an in-memory-only annotation which lives only inside a particular
   124  // Locks object, and is never persisted as part of a saved lock file on disk.
   125  // It's valid to still use other methods of the reciever to access
   126  // already-stored lock information and to update lock information for an
   127  // overridden provider, but some callers may need to use ProviderIsOverridden
   128  // to selectively disregard stored lock information for overridden providers,
   129  // depending on what they intended to use the lock information for.
   130  func (l *Locks) SetProviderOverridden(addr addrs.Provider) {
   131  	if l.overriddenProviders == nil {
   132  		l.overriddenProviders = make(map[addrs.Provider]struct{})
   133  	}
   134  	l.overriddenProviders[addr] = struct{}{}
   135  }
   136  
   137  // ProviderIsOverridden returns true only if the given provider address was
   138  // previously registered as overridden by calling SetProviderOverridden.
   139  func (l *Locks) ProviderIsOverridden(addr addrs.Provider) bool {
   140  	_, ret := l.overriddenProviders[addr]
   141  	return ret
   142  }
   143  
   144  // SetSameOverriddenProviders updates the receiver to mark as overridden all
   145  // of the same providers already marked as overridden in the other given locks.
   146  //
   147  // This allows propagating override information between different lock objects,
   148  // as if calling SetProviderOverridden for each address already overridden
   149  // in the other given locks. If the reciever already has overridden providers,
   150  // SetSameOverriddenProviders will preserve them.
   151  func (l *Locks) SetSameOverriddenProviders(other *Locks) {
   152  	if other == nil {
   153  		return
   154  	}
   155  	for addr := range other.overriddenProviders {
   156  		l.SetProviderOverridden(addr)
   157  	}
   158  }
   159  
   160  // NewProviderLock creates a new ProviderLock object that isn't associated
   161  // with any Locks object.
   162  //
   163  // This is here primarily for testing. Most callers should use Locks.SetProvider
   164  // to construct a new provider lock and insert it into a Locks object at the
   165  // same time.
   166  //
   167  // The ownership of the backing array for the slice of hashes passes to this
   168  // function, and so the caller must not read or write that backing array after
   169  // calling NewProviderLock.
   170  //
   171  // Only lockable providers can be passed to this method. If you pass a
   172  // non-lockable provider address then this function will panic. Use
   173  // function ProviderIsLockable to determine whether a particular provider
   174  // should participate in the version locking mechanism.
   175  func NewProviderLock(addr addrs.Provider, version getproviders.Version, constraints getproviders.VersionConstraints, hashes []getproviders.Hash) *ProviderLock {
   176  	if !ProviderIsLockable(addr) {
   177  		panic(fmt.Sprintf("Locks.NewProviderLock with non-lockable provider %s", addr))
   178  	}
   179  
   180  	// Normalize the hashes into lexical order so that we can do straightforward
   181  	// equality tests between different locks for the same provider. The
   182  	// hashes are logically a set, so the given order is insignificant.
   183  	sort.Slice(hashes, func(i, j int) bool {
   184  		return string(hashes[i]) < string(hashes[j])
   185  	})
   186  
   187  	// This is a slightly-tricky in-place deduping to avoid unnecessarily
   188  	// allocating a new array in the common case where there are no duplicates:
   189  	// we iterate over "hashes" at the same time as appending to another slice
   190  	// with the same backing array, relying on the fact that deduping can only
   191  	// _skip_ elements from the input, and will never generate additional ones
   192  	// that would cause the writer to get ahead of the reader. This also
   193  	// assumes that we already sorted the items, which means that any duplicates
   194  	// will be consecutive in the sequence.
   195  	dedupeHashes := hashes[:0]
   196  	prevHash := getproviders.NilHash
   197  	for _, hash := range hashes {
   198  		if hash != prevHash {
   199  			dedupeHashes = append(dedupeHashes, hash)
   200  			prevHash = hash
   201  		}
   202  	}
   203  
   204  	return &ProviderLock{
   205  		addr:               addr,
   206  		version:            version,
   207  		versionConstraints: constraints,
   208  		hashes:             dedupeHashes,
   209  	}
   210  }
   211  
   212  // ProviderIsLockable returns true if the given provider is eligible for
   213  // version locking.
   214  //
   215  // Currently, all providers except builtin and legacy providers are eligible
   216  // for locking.
   217  func ProviderIsLockable(addr addrs.Provider) bool {
   218  	return !(addr.IsBuiltIn() || addr.IsLegacy())
   219  }
   220  
   221  // Sources returns the source code of the file the receiver was generated from,
   222  // or an empty map if the receiver wasn't generated from a file.
   223  //
   224  // This return type matches the one expected by HCL diagnostics printers to
   225  // produce source code snapshots, which is the only intended use for this
   226  // method.
   227  func (l *Locks) Sources() map[string][]byte {
   228  	return l.sources
   229  }
   230  
   231  // Equal returns true if the given Locks represents the same information as
   232  // the receiver.
   233  //
   234  // Equal explicitly _does not_ consider the equality of version constraints
   235  // in the saved locks, because those are saved only as hints to help the UI
   236  // explain what's changed between runs, and are never used as part of
   237  // dependency installation decisions.
   238  func (l *Locks) Equal(other *Locks) bool {
   239  	if len(l.providers) != len(other.providers) {
   240  		return false
   241  	}
   242  	for addr, thisLock := range l.providers {
   243  		otherLock, ok := other.providers[addr]
   244  		if !ok {
   245  			return false
   246  		}
   247  
   248  		if thisLock.addr != otherLock.addr {
   249  			// It'd be weird to get here because we already looked these up
   250  			// by address above.
   251  			return false
   252  		}
   253  		if thisLock.version != otherLock.version {
   254  			// Equality rather than "Version.Same" because changes to the
   255  			// build metadata are significant for the purpose of this function:
   256  			// it's a different package even if it has the same precedence.
   257  			return false
   258  		}
   259  
   260  		// Although "hashes" is declared as a slice, it's logically an
   261  		// unordered set. However, we normalize the slice of hashes when
   262  		// recieving it in NewProviderLock, so we can just do a simple
   263  		// item-by-item equality test here.
   264  		if len(thisLock.hashes) != len(otherLock.hashes) {
   265  			return false
   266  		}
   267  		for i := range thisLock.hashes {
   268  			if thisLock.hashes[i] != otherLock.hashes[i] {
   269  				return false
   270  			}
   271  		}
   272  	}
   273  	// We don't need to worry about providers that are in "other" but not
   274  	// in the receiver, because we tested the lengths being equal above.
   275  
   276  	return true
   277  }
   278  
   279  // EqualProviderAddress returns true if the given Locks have the same provider
   280  // address as the receiver. This doesn't check version and hashes.
   281  func (l *Locks) EqualProviderAddress(other *Locks) bool {
   282  	if len(l.providers) != len(other.providers) {
   283  		return false
   284  	}
   285  
   286  	for addr := range l.providers {
   287  		_, ok := other.providers[addr]
   288  		if !ok {
   289  			return false
   290  		}
   291  	}
   292  
   293  	return true
   294  }
   295  
   296  // Empty returns true if the given Locks object contains no actual locks.
   297  //
   298  // UI code might wish to use this to distinguish a lock file being
   299  // written for the first time from subsequent updates to that lock file.
   300  func (l *Locks) Empty() bool {
   301  	return len(l.providers) == 0
   302  }
   303  
   304  // DeepCopy creates a new Locks that represents the same information as the
   305  // receiver but does not share memory for any parts of the structure that.
   306  // are mutable through methods on Locks.
   307  //
   308  // Note that this does _not_ create deep copies of parts of the structure
   309  // that are technically mutable but are immutable by convention, such as the
   310  // array underlying the slice of version constraints. Callers may mutate the
   311  // resulting data structure only via the direct methods of Locks.
   312  func (l *Locks) DeepCopy() *Locks {
   313  	ret := NewLocks()
   314  	for addr, lock := range l.providers {
   315  		var hashes []getproviders.Hash
   316  		if len(lock.hashes) > 0 {
   317  			hashes = make([]getproviders.Hash, len(lock.hashes))
   318  			copy(hashes, lock.hashes)
   319  		}
   320  		ret.SetProvider(addr, lock.version, lock.versionConstraints, hashes)
   321  	}
   322  	return ret
   323  }
   324  
   325  // ProviderLock represents lock information for a specific provider.
   326  type ProviderLock struct {
   327  	// addr is the address of the provider this lock applies to.
   328  	addr addrs.Provider
   329  
   330  	// version is the specific version that was previously selected, while
   331  	// versionConstraints is the constraint that was used to make that
   332  	// selection, which we can potentially use to hint to run
   333  	// e.g. terraform init -upgrade if a user has changed a version
   334  	// constraint but the previous selection still remains valid.
   335  	// "version" is therefore authoritative, while "versionConstraints" is
   336  	// just for a UI hint and not used to make any real decisions.
   337  	version            getproviders.Version
   338  	versionConstraints getproviders.VersionConstraints
   339  
   340  	// hashes contains zero or more hashes of packages or package contents
   341  	// for the package associated with the selected version across all of
   342  	// the supported platforms.
   343  	//
   344  	// hashes can contain a mixture of hashes in different formats to support
   345  	// changes over time. The new-style hash format is to have a string
   346  	// starting with "h" followed by a version number and then a colon, like
   347  	// "h1:" for the first hash format version. Other hash versions following
   348  	// this scheme may come later. These versioned hash schemes are implemented
   349  	// in the getproviders package; for example, "h1:" is implemented in
   350  	// getproviders.HashV1 .
   351  	//
   352  	// There is also a legacy hash format which is just a lowercase-hex-encoded
   353  	// SHA256 hash of the official upstream .zip file for the selected version.
   354  	// We'll allow as that a stop-gap until we can upgrade Terraform Registry
   355  	// to support the new scheme, but is non-ideal because we can verify it only
   356  	// when we have the original .zip file exactly; we can't verify a local
   357  	// directory containing the unpacked contents of that .zip file.
   358  	//
   359  	// We ideally want to populate hashes for all available platforms at
   360  	// once, by referring to the signed checksums file in the upstream
   361  	// registry. In that ideal case it's possible to later work with the same
   362  	// configuration on a different platform while still verifying the hashes.
   363  	// However, installation from any method other than an origin registry
   364  	// means we can only populate the hash for the current platform, and so
   365  	// it won't be possible to verify a subsequent installation of the same
   366  	// provider on a different platform.
   367  	hashes []getproviders.Hash
   368  }
   369  
   370  // Provider returns the address of the provider this lock applies to.
   371  func (l *ProviderLock) Provider() addrs.Provider {
   372  	return l.addr
   373  }
   374  
   375  // Version returns the currently-selected version for the corresponding provider.
   376  func (l *ProviderLock) Version() getproviders.Version {
   377  	return l.version
   378  }
   379  
   380  // VersionConstraints returns the version constraints that were recorded as
   381  // being used to choose the version returned by Version.
   382  //
   383  // These version constraints are not authoritative for future selections and
   384  // are included only so Terraform can detect if the constraints in
   385  // configuration have changed since a selection was made, and thus hint to the
   386  // user that they may need to run terraform init -upgrade to apply the new
   387  // constraints.
   388  func (l *ProviderLock) VersionConstraints() getproviders.VersionConstraints {
   389  	return l.versionConstraints
   390  }
   391  
   392  // AllHashes returns all of the package hashes that were recorded when this
   393  // lock was created. If no hashes were recorded for that platform, the result
   394  // is a zero-length slice.
   395  //
   396  // If your intent is to verify a package against the recorded hashes, use
   397  // PreferredHashes to get only the hashes which the current version
   398  // of Terraform considers the strongest of the available hashing schemes, one
   399  // of which must match in order for verification to be considered successful.
   400  //
   401  // Do not modify the backing array of the returned slice.
   402  func (l *ProviderLock) AllHashes() []getproviders.Hash {
   403  	return l.hashes
   404  }
   405  
   406  // ContainsAll returns true if the hashes in this ProviderLock contains
   407  // all the hashes in the target.
   408  //
   409  // This function assumes the hashes are in each ProviderLock are sorted.
   410  // If the ProviderLock was created by the NewProviderLock constructor then
   411  // the hashes are guaranteed to be sorted.
   412  func (l *ProviderLock) ContainsAll(target *ProviderLock) bool {
   413  	if target == nil || len(target.hashes) == 0 {
   414  		return true
   415  	}
   416  
   417  	targetIndex := 0
   418  	for ix := 0; ix < len(l.hashes); ix++ {
   419  		if l.hashes[ix] == target.hashes[targetIndex] {
   420  			targetIndex++
   421  
   422  			if targetIndex >= len(target.hashes) {
   423  				return true
   424  			}
   425  		}
   426  	}
   427  	return false
   428  }
   429  
   430  // PreferredHashes returns a filtered version of the AllHashes return value
   431  // which includes only the strongest of the availabile hash schemes, in
   432  // case legacy hash schemes are deprecated over time but still supported for
   433  // upgrade purposes.
   434  //
   435  // At least one of the given hashes must match for a package to be considered
   436  // valud.
   437  func (l *ProviderLock) PreferredHashes() []getproviders.Hash {
   438  	return getproviders.PreferredHashes(l.hashes)
   439  }