github.com/jaredpalmer/terraform@v1.1.0-alpha20210908.0.20210911170307-88705c943a03/internal/terraform/context_plugins.go (about) 1 package terraform 2 3 import ( 4 "fmt" 5 "log" 6 "sync" 7 8 "github.com/hashicorp/terraform/internal/addrs" 9 "github.com/hashicorp/terraform/internal/configs/configschema" 10 "github.com/hashicorp/terraform/internal/providers" 11 "github.com/hashicorp/terraform/internal/provisioners" 12 ) 13 14 // contextPlugins represents a library of available plugins (providers and 15 // provisioners) which we assume will all be used with the same 16 // terraform.Context, and thus it'll be safe to cache certain information 17 // about the providers for performance reasons. 18 type contextPlugins struct { 19 providerFactories map[addrs.Provider]providers.Factory 20 provisionerFactories map[string]provisioners.Factory 21 22 // We memoize the schemas we've previously loaded in here, to avoid 23 // repeatedly paying the cost of activating the same plugins to access 24 // their schemas in various different spots. We use schemas for many 25 // purposes in Terraform, so there isn't a single choke point where 26 // it makes sense to preload all of them. 27 providerSchemas map[addrs.Provider]*ProviderSchema 28 provisionerSchemas map[string]*configschema.Block 29 schemasLock sync.Mutex 30 } 31 32 func newContextPlugins(providerFactories map[addrs.Provider]providers.Factory, provisionerFactories map[string]provisioners.Factory) *contextPlugins { 33 ret := &contextPlugins{ 34 providerFactories: providerFactories, 35 provisionerFactories: provisionerFactories, 36 } 37 ret.init() 38 return ret 39 } 40 41 func (cp *contextPlugins) init() { 42 cp.providerSchemas = make(map[addrs.Provider]*ProviderSchema, len(cp.providerFactories)) 43 cp.provisionerSchemas = make(map[string]*configschema.Block, len(cp.provisionerFactories)) 44 } 45 46 func (cp *contextPlugins) NewProviderInstance(addr addrs.Provider) (providers.Interface, error) { 47 f, ok := cp.providerFactories[addr] 48 if !ok { 49 return nil, fmt.Errorf("unavailable provider %q", addr.String()) 50 } 51 52 return f() 53 54 } 55 56 func (cp *contextPlugins) NewProvisionerInstance(typ string) (provisioners.Interface, error) { 57 f, ok := cp.provisionerFactories[typ] 58 if !ok { 59 return nil, fmt.Errorf("unavailable provisioner %q", typ) 60 } 61 62 return f() 63 } 64 65 // ProviderSchema uses a temporary instance of the provider with the given 66 // address to obtain the full schema for all aspects of that provider. 67 // 68 // ProviderSchema memoizes results by unique provider address, so it's fine 69 // to repeatedly call this method with the same address if various different 70 // parts of Terraform all need the same schema information. 71 func (cp *contextPlugins) ProviderSchema(addr addrs.Provider) (*ProviderSchema, error) { 72 cp.schemasLock.Lock() 73 defer cp.schemasLock.Unlock() 74 75 if schema, ok := cp.providerSchemas[addr]; ok { 76 return schema, nil 77 } 78 79 log.Printf("[TRACE] terraform.contextPlugins: Initializing provider %q to read its schema", addr) 80 81 provider, err := cp.NewProviderInstance(addr) 82 if err != nil { 83 return nil, fmt.Errorf("failed to instantiate provider %q to obtain schema: %s", addr, err) 84 } 85 defer provider.Close() 86 87 resp := provider.GetProviderSchema() 88 if resp.Diagnostics.HasErrors() { 89 return nil, fmt.Errorf("failed to retrieve schema from provider %q: %s", addr, resp.Diagnostics.Err()) 90 } 91 92 s := &ProviderSchema{ 93 Provider: resp.Provider.Block, 94 ResourceTypes: make(map[string]*configschema.Block), 95 DataSources: make(map[string]*configschema.Block), 96 97 ResourceTypeSchemaVersions: make(map[string]uint64), 98 } 99 100 if resp.Provider.Version < 0 { 101 // We're not using the version numbers here yet, but we'll check 102 // for validity anyway in case we start using them in future. 103 return nil, fmt.Errorf("provider %s has invalid negative schema version for its configuration blocks,which is a bug in the provider ", addr) 104 } 105 106 for t, r := range resp.ResourceTypes { 107 if err := r.Block.InternalValidate(); err != nil { 108 return nil, fmt.Errorf("provider %s has invalid schema for managed resource type %q, which is a bug in the provider: %q", addr, t, err) 109 } 110 s.ResourceTypes[t] = r.Block 111 s.ResourceTypeSchemaVersions[t] = uint64(r.Version) 112 if r.Version < 0 { 113 return nil, fmt.Errorf("provider %s has invalid negative schema version for managed resource type %q, which is a bug in the provider", addr, t) 114 } 115 } 116 117 for t, d := range resp.DataSources { 118 if err := d.Block.InternalValidate(); err != nil { 119 return nil, fmt.Errorf("provider %s has invalid schema for data resource type %q, which is a bug in the provider: %q", addr, t, err) 120 } 121 s.DataSources[t] = d.Block 122 if d.Version < 0 { 123 // We're not using the version numbers here yet, but we'll check 124 // for validity anyway in case we start using them in future. 125 return nil, fmt.Errorf("provider %s has invalid negative schema version for data resource type %q, which is a bug in the provider", addr, t) 126 } 127 } 128 129 if resp.ProviderMeta.Block != nil { 130 s.ProviderMeta = resp.ProviderMeta.Block 131 } 132 133 cp.providerSchemas[addr] = s 134 return s, nil 135 } 136 137 // ProviderConfigSchema is a helper wrapper around ProviderSchema which first 138 // reads the full schema of the given provider and then extracts just the 139 // provider's configuration schema, which defines what's expected in a 140 // "provider" block in the configuration when configuring this provider. 141 func (cp *contextPlugins) ProviderConfigSchema(providerAddr addrs.Provider) (*configschema.Block, error) { 142 providerSchema, err := cp.ProviderSchema(providerAddr) 143 if err != nil { 144 return nil, err 145 } 146 147 return providerSchema.Provider, nil 148 } 149 150 // ResourceTypeSchema is a helper wrapper around ProviderSchema which first 151 // reads the schema of the given provider and then tries to find the schema 152 // for the resource type of the given resource mode in that provider. 153 // 154 // ResourceTypeSchema will return an error if the provider schema lookup 155 // fails, but will return nil if the provider schema lookup succeeds but then 156 // the provider doesn't have a resource of the requested type. 157 // 158 // Managed resource types have versioned schemas, so the second return value 159 // is the current schema version number for the requested resource. The version 160 // is irrelevant for other resource modes. 161 func (cp *contextPlugins) ResourceTypeSchema(providerAddr addrs.Provider, resourceMode addrs.ResourceMode, resourceType string) (*configschema.Block, uint64, error) { 162 providerSchema, err := cp.ProviderSchema(providerAddr) 163 if err != nil { 164 return nil, 0, err 165 } 166 167 schema, version := providerSchema.SchemaForResourceType(resourceMode, resourceType) 168 return schema, version, nil 169 } 170 171 // ProvisionerSchema uses a temporary instance of the provisioner with the 172 // given type name to obtain the schema for that provisioner's configuration. 173 // 174 // ProvisionerSchema memoizes results by provisioner type name, so it's fine 175 // to repeatedly call this method with the same name if various different 176 // parts of Terraform all need the same schema information. 177 func (cp *contextPlugins) ProvisionerSchema(typ string) (*configschema.Block, error) { 178 cp.schemasLock.Lock() 179 defer cp.schemasLock.Unlock() 180 181 if schema, ok := cp.provisionerSchemas[typ]; ok { 182 return schema, nil 183 } 184 185 log.Printf("[TRACE] terraform.contextPlugins: Initializing provisioner %q to read its schema", typ) 186 provisioner, err := cp.NewProvisionerInstance(typ) 187 if err != nil { 188 return nil, fmt.Errorf("failed to instantiate provisioner %q to obtain schema: %s", typ, err) 189 } 190 defer provisioner.Close() 191 192 resp := provisioner.GetSchema() 193 if resp.Diagnostics.HasErrors() { 194 return nil, fmt.Errorf("failed to retrieve schema from provisioner %q: %s", typ, resp.Diagnostics.Err()) 195 } 196 197 cp.provisionerSchemas[typ] = resp.Provisioner 198 return resp.Provisioner, nil 199 }