github.com/opentofu/opentofu@v1.7.1/internal/depsfile/locks.go (about)

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