github.com/opentofu/opentofu@v1.7.1/internal/tofu/context_plugins.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 tofu 7 8 import ( 9 "fmt" 10 "log" 11 12 "github.com/opentofu/opentofu/internal/addrs" 13 "github.com/opentofu/opentofu/internal/configs/configschema" 14 "github.com/opentofu/opentofu/internal/providers" 15 "github.com/opentofu/opentofu/internal/provisioners" 16 ) 17 18 // contextPlugins represents a library of available plugins (providers and 19 // provisioners) which we assume will all be used with the same 20 // tofu.Context, and thus it'll be safe to cache certain information 21 // about the providers for performance reasons. 22 type contextPlugins struct { 23 providerFactories map[addrs.Provider]providers.Factory 24 provisionerFactories map[string]provisioners.Factory 25 } 26 27 func newContextPlugins(providerFactories map[addrs.Provider]providers.Factory, provisionerFactories map[string]provisioners.Factory) (*contextPlugins, error) { 28 return &contextPlugins{ 29 providerFactories: providerFactories, 30 provisionerFactories: provisionerFactories, 31 }, nil // TODO remove error from this function call! 32 } 33 34 func (cp *contextPlugins) HasProvider(addr addrs.Provider) bool { 35 _, ok := cp.providerFactories[addr] 36 return ok 37 } 38 39 func (cp *contextPlugins) NewProviderInstance(addr addrs.Provider) (providers.Interface, error) { 40 f, ok := cp.providerFactories[addr] 41 if !ok { 42 return nil, fmt.Errorf("unavailable provider %q", addr.String()) 43 } 44 45 return f() 46 47 } 48 49 func (cp *contextPlugins) HasProvisioner(typ string) bool { 50 _, ok := cp.provisionerFactories[typ] 51 return ok 52 } 53 54 func (cp *contextPlugins) NewProvisionerInstance(typ string) (provisioners.Interface, error) { 55 f, ok := cp.provisionerFactories[typ] 56 if !ok { 57 return nil, fmt.Errorf("unavailable provisioner %q", typ) 58 } 59 60 return f() 61 } 62 63 // ProviderSchema uses a temporary instance of the provider with the given 64 // address to obtain the full schema for all aspects of that provider. 65 // 66 // ProviderSchema memoizes results by unique provider address, so it's fine 67 // to repeatedly call this method with the same address if various different 68 // parts of OpenTofu all need the same schema information. 69 func (cp *contextPlugins) ProviderSchema(addr addrs.Provider) (providers.ProviderSchema, error) { 70 // Check the global schema cache first. 71 // This cache is only written by the provider client, and transparently 72 // used by GetProviderSchema, but we check it here because at this point we 73 // may be able to avoid spinning up the provider instance at all. 74 // 75 // It's worth noting that ServerCapabilities.GetProviderSchemaOptional is ignored here. 76 // That is because we're checking *prior* to the provider's instantiation. 77 // GetProviderSchemaOptional only says that *if we instantiate a provider*, 78 // then we need to run the get schema call at least once. 79 // BUG This SHORT CIRCUITS the logic below and is not the only code which inserts provider schemas into the cache!! 80 schemas, ok := providers.SchemaCache.Get(addr) 81 if ok { 82 log.Printf("[TRACE] tofu.contextPlugins: Serving provider %q schema from global schema cache", addr) 83 return schemas, nil 84 } 85 86 log.Printf("[TRACE] tofu.contextPlugins: Initializing provider %q to read its schema", addr) 87 provider, err := cp.NewProviderInstance(addr) 88 if err != nil { 89 return schemas, fmt.Errorf("failed to instantiate provider %q to obtain schema: %w", addr, err) 90 } 91 defer provider.Close() 92 93 resp := provider.GetProviderSchema() 94 if resp.Diagnostics.HasErrors() { 95 return resp, fmt.Errorf("failed to retrieve schema from provider %q: %w", addr, resp.Diagnostics.Err()) 96 } 97 98 if resp.Provider.Version < 0 { 99 // We're not using the version numbers here yet, but we'll check 100 // for validity anyway in case we start using them in future. 101 return resp, fmt.Errorf("provider %s has invalid negative schema version for its configuration blocks,which is a bug in the provider ", addr) 102 } 103 104 for t, r := range resp.ResourceTypes { 105 if err := r.Block.InternalValidate(); err != nil { 106 return resp, fmt.Errorf("provider %s has invalid schema for managed resource type %q, which is a bug in the provider: %w", addr, t, err) 107 } 108 if r.Version < 0 { 109 return resp, fmt.Errorf("provider %s has invalid negative schema version for managed resource type %q, which is a bug in the provider", addr, t) 110 } 111 } 112 113 for t, d := range resp.DataSources { 114 if err := d.Block.InternalValidate(); err != nil { 115 return resp, fmt.Errorf("provider %s has invalid schema for data resource type %q, which is a bug in the provider: %w", addr, t, err) 116 } 117 if d.Version < 0 { 118 // We're not using the version numbers here yet, but we'll check 119 // for validity anyway in case we start using them in future. 120 return resp, fmt.Errorf("provider %s has invalid negative schema version for data resource type %q, which is a bug in the provider", addr, t) 121 } 122 } 123 124 return resp, nil 125 } 126 127 // ProviderConfigSchema is a helper wrapper around ProviderSchema which first 128 // reads the full schema of the given provider and then extracts just the 129 // provider's configuration schema, which defines what's expected in a 130 // "provider" block in the configuration when configuring this provider. 131 func (cp *contextPlugins) ProviderConfigSchema(providerAddr addrs.Provider) (*configschema.Block, error) { 132 providerSchema, err := cp.ProviderSchema(providerAddr) 133 if err != nil { 134 return nil, err 135 } 136 137 return providerSchema.Provider.Block, nil 138 } 139 140 // ResourceTypeSchema is a helper wrapper around ProviderSchema which first 141 // reads the schema of the given provider and then tries to find the schema 142 // for the resource type of the given resource mode in that provider. 143 // 144 // ResourceTypeSchema will return an error if the provider schema lookup 145 // fails, but will return nil if the provider schema lookup succeeds but then 146 // the provider doesn't have a resource of the requested type. 147 // 148 // Managed resource types have versioned schemas, so the second return value 149 // is the current schema version number for the requested resource. The version 150 // is irrelevant for other resource modes. 151 func (cp *contextPlugins) ResourceTypeSchema(providerAddr addrs.Provider, resourceMode addrs.ResourceMode, resourceType string) (*configschema.Block, uint64, error) { 152 providerSchema, err := cp.ProviderSchema(providerAddr) 153 if err != nil { 154 return nil, 0, err 155 } 156 157 schema, version := providerSchema.SchemaForResourceType(resourceMode, resourceType) 158 return schema, version, nil 159 } 160 161 // ProvisionerSchema uses a temporary instance of the provisioner with the 162 // given type name to obtain the schema for that provisioner's configuration. 163 // 164 // ProvisionerSchema memoizes results by provisioner type name, so it's fine 165 // to repeatedly call this method with the same name if various different 166 // parts of OpenTofu all need the same schema information. 167 func (cp *contextPlugins) ProvisionerSchema(typ string) (*configschema.Block, error) { 168 log.Printf("[TRACE] tofu.contextPlugins: Initializing provisioner %q to read its schema", typ) 169 provisioner, err := cp.NewProvisionerInstance(typ) 170 if err != nil { 171 return nil, fmt.Errorf("failed to instantiate provisioner %q to obtain schema: %w", typ, err) 172 } 173 defer provisioner.Close() 174 175 resp := provisioner.GetSchema() 176 if resp.Diagnostics.HasErrors() { 177 return nil, fmt.Errorf("failed to retrieve schema from provisioner %q: %w", typ, resp.Diagnostics.Err()) 178 } 179 180 return resp.Provisioner, nil 181 }