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>"