github.com/opentofu/opentofu@v1.7.1/internal/plugin/discovery/requirements.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 discovery
     7  
     8  import (
     9  	"bytes"
    10  )
    11  
    12  // PluginInstallProtocolVersion is the protocol version TF-core
    13  // supports to communicate with servers, and is used to resolve
    14  // plugin discovery with OpenTofu registry, in addition to
    15  // any specified plugin version constraints
    16  const PluginInstallProtocolVersion = 5
    17  
    18  // PluginRequirements describes a set of plugins (assumed to be of a consistent
    19  // kind) that are required to exist and have versions within the given
    20  // corresponding sets.
    21  type PluginRequirements map[string]*PluginConstraints
    22  
    23  // PluginConstraints represents an element of PluginRequirements describing
    24  // the constraints for a single plugin.
    25  type PluginConstraints struct {
    26  	// Specifies that the plugin's version must be within the given
    27  	// constraints.
    28  	Versions Constraints
    29  
    30  	// If non-nil, the hash of the on-disk plugin executable must exactly
    31  	// match the SHA256 hash given here.
    32  	SHA256 []byte
    33  }
    34  
    35  // Allows returns true if the given version is within the receiver's version
    36  // constraints.
    37  func (s *PluginConstraints) Allows(v Version) bool {
    38  	return s.Versions.Allows(v)
    39  }
    40  
    41  // AcceptsSHA256 returns true if the given executable SHA256 hash is acceptable,
    42  // either because it matches the constraint or because there is no such
    43  // constraint.
    44  func (s *PluginConstraints) AcceptsSHA256(digest []byte) bool {
    45  	if s.SHA256 == nil {
    46  		return true
    47  	}
    48  	return bytes.Equal(s.SHA256, digest)
    49  }
    50  
    51  // Merge takes the contents of the receiver and the other given requirements
    52  // object and merges them together into a single requirements structure
    53  // that satisfies both sets of requirements.
    54  //
    55  // Note that it doesn't make sense to merge two PluginRequirements with
    56  // differing required plugin SHA256 hashes, since the result will never
    57  // match any plugin.
    58  func (r PluginRequirements) Merge(other PluginRequirements) PluginRequirements {
    59  	ret := make(PluginRequirements)
    60  	for n, c := range r {
    61  		ret[n] = &PluginConstraints{
    62  			Versions: Constraints{}.Append(c.Versions),
    63  			SHA256:   c.SHA256,
    64  		}
    65  	}
    66  	for n, c := range other {
    67  		if existing, exists := ret[n]; exists {
    68  			ret[n].Versions = ret[n].Versions.Append(c.Versions)
    69  
    70  			if existing.SHA256 != nil {
    71  				if c.SHA256 != nil && !bytes.Equal(c.SHA256, existing.SHA256) {
    72  					// If we've been asked to merge two constraints with
    73  					// different SHA256 hashes then we'll produce a dummy value
    74  					// that can never match anything. This is a silly edge case
    75  					// that no reasonable caller should hit.
    76  					ret[n].SHA256 = []byte(invalidProviderHash)
    77  				}
    78  			} else {
    79  				ret[n].SHA256 = c.SHA256 // might still be nil
    80  			}
    81  		} else {
    82  			ret[n] = &PluginConstraints{
    83  				Versions: Constraints{}.Append(c.Versions),
    84  				SHA256:   c.SHA256,
    85  			}
    86  		}
    87  	}
    88  	return ret
    89  }
    90  
    91  // LockExecutables applies additional constraints to the receiver that
    92  // require plugin executables with specific SHA256 digests. This modifies
    93  // the receiver in-place, since it's intended to be applied after
    94  // version constraints have been resolved.
    95  //
    96  // The given map must include a key for every plugin that is already
    97  // required. If not, any missing keys will cause the corresponding plugin
    98  // to never match, though the direct caller doesn't necessarily need to
    99  // guarantee this as long as the downstream code _applying_ these constraints
   100  // is able to deal with the non-match in some way.
   101  func (r PluginRequirements) LockExecutables(sha256s map[string][]byte) {
   102  	for name, cons := range r {
   103  		digest := sha256s[name]
   104  
   105  		if digest == nil {
   106  			// Prevent any match, which will then presumably cause the
   107  			// downstream consumer of this requirements to report an error.
   108  			cons.SHA256 = []byte(invalidProviderHash)
   109  			continue
   110  		}
   111  
   112  		cons.SHA256 = digest
   113  	}
   114  }
   115  
   116  const invalidProviderHash = "<invalid>"