github.com/jzbruno/terraform@v0.10.3-0.20180104230435-18975d727047/command/plugins.go (about) 1 package command 2 3 import ( 4 "encoding/json" 5 "errors" 6 "fmt" 7 "io/ioutil" 8 "log" 9 "os" 10 "os/exec" 11 "path/filepath" 12 "runtime" 13 "strings" 14 15 plugin "github.com/hashicorp/go-plugin" 16 terraformProvider "github.com/hashicorp/terraform/builtin/providers/terraform" 17 tfplugin "github.com/hashicorp/terraform/plugin" 18 "github.com/hashicorp/terraform/plugin/discovery" 19 "github.com/hashicorp/terraform/terraform" 20 "github.com/kardianos/osext" 21 ) 22 23 // multiVersionProviderResolver is an implementation of 24 // terraform.ResourceProviderResolver that matches the given version constraints 25 // against a set of versioned provider plugins to find the newest version of 26 // each that satisfies the given constraints. 27 type multiVersionProviderResolver struct { 28 Available discovery.PluginMetaSet 29 30 // Internal is a map that overrides the usual plugin selection process 31 // for internal plugins. These plugins do not support version constraints 32 // (will produce an error if one is set). This should be used only in 33 // exceptional circumstances since it forces the provider's release 34 // schedule to be tied to that of Terraform Core. 35 Internal map[string]terraform.ResourceProviderFactory 36 } 37 38 func choosePlugins(avail discovery.PluginMetaSet, internal map[string]terraform.ResourceProviderFactory, reqd discovery.PluginRequirements) map[string]discovery.PluginMeta { 39 candidates := avail.ConstrainVersions(reqd) 40 ret := map[string]discovery.PluginMeta{} 41 for name, metas := range candidates { 42 // If the provider is in our internal map then we ignore any 43 // discovered plugins for it since these are dealt with separately. 44 if _, isInternal := internal[name]; isInternal { 45 continue 46 } 47 48 if len(metas) == 0 { 49 continue 50 } 51 ret[name] = metas.Newest() 52 } 53 return ret 54 } 55 56 func (r *multiVersionProviderResolver) ResolveProviders( 57 reqd discovery.PluginRequirements, 58 ) (map[string]terraform.ResourceProviderFactory, []error) { 59 factories := make(map[string]terraform.ResourceProviderFactory, len(reqd)) 60 var errs []error 61 62 chosen := choosePlugins(r.Available, r.Internal, reqd) 63 for name, req := range reqd { 64 if factory, isInternal := r.Internal[name]; isInternal { 65 if !req.Versions.Unconstrained() { 66 errs = append(errs, fmt.Errorf("provider.%s: this provider is built in to Terraform and so it does not support version constraints", name)) 67 continue 68 } 69 factories[name] = factory 70 continue 71 } 72 73 if newest, available := chosen[name]; available { 74 digest, err := newest.SHA256() 75 if err != nil { 76 errs = append(errs, fmt.Errorf("provider.%s: failed to load plugin to verify its signature: %s", name, err)) 77 continue 78 } 79 if !reqd[name].AcceptsSHA256(digest) { 80 errs = append(errs, fmt.Errorf("provider.%s: new or changed plugin executable", name)) 81 continue 82 } 83 84 client := tfplugin.Client(newest) 85 factories[name] = providerFactory(client) 86 } else { 87 msg := fmt.Sprintf("provider.%s: no suitable version installed", name) 88 89 required := req.Versions.String() 90 // no version is unconstrained 91 if required == "" { 92 required = "(any version)" 93 } 94 95 foundVersions := []string{} 96 for meta := range r.Available.WithName(name) { 97 foundVersions = append(foundVersions, fmt.Sprintf("%q", meta.Version)) 98 } 99 100 found := "none" 101 if len(foundVersions) > 0 { 102 found = strings.Join(foundVersions, ", ") 103 } 104 105 msg += fmt.Sprintf("\n version requirements: %q\n versions installed: %s", required, found) 106 107 errs = append(errs, errors.New(msg)) 108 } 109 } 110 111 return factories, errs 112 } 113 114 // store the user-supplied path for plugin discovery 115 func (m *Meta) storePluginPath(pluginPath []string) error { 116 if len(pluginPath) == 0 { 117 return nil 118 } 119 120 js, err := json.MarshalIndent(pluginPath, "", " ") 121 if err != nil { 122 return err 123 } 124 125 // if this fails, so will WriteFile 126 os.MkdirAll(m.DataDir(), 0755) 127 128 return ioutil.WriteFile(filepath.Join(m.DataDir(), PluginPathFile), js, 0644) 129 } 130 131 // Load the user-defined plugin search path into Meta.pluginPath if the file 132 // exists. 133 func (m *Meta) loadPluginPath() ([]string, error) { 134 js, err := ioutil.ReadFile(filepath.Join(m.DataDir(), PluginPathFile)) 135 if os.IsNotExist(err) { 136 return nil, nil 137 } 138 139 if err != nil { 140 return nil, err 141 } 142 143 var pluginPath []string 144 if err := json.Unmarshal(js, &pluginPath); err != nil { 145 return nil, err 146 } 147 148 return pluginPath, nil 149 } 150 151 // the default location for automatically installed plugins 152 func (m *Meta) pluginDir() string { 153 return filepath.Join(m.DataDir(), "plugins", fmt.Sprintf("%s_%s", runtime.GOOS, runtime.GOARCH)) 154 } 155 156 // pluginDirs return a list of directories to search for plugins. 157 // 158 // Earlier entries in this slice get priority over later when multiple copies 159 // of the same plugin version are found, but newer versions always override 160 // older versions where both satisfy the provider version constraints. 161 func (m *Meta) pluginDirs(includeAutoInstalled bool) []string { 162 // user defined paths take precedence 163 if len(m.pluginPath) > 0 { 164 return m.pluginPath 165 } 166 167 // When searching the following directories, earlier entries get precedence 168 // if the same plugin version is found twice, but newer versions will 169 // always get preference below regardless of where they are coming from. 170 // TODO: Add auto-install dir, default vendor dir and optional override 171 // vendor dir(s). 172 dirs := []string{"."} 173 174 // Look in the same directory as the Terraform executable. 175 // If found, this replaces what we found in the config path. 176 exePath, err := osext.Executable() 177 if err != nil { 178 log.Printf("[ERROR] Error discovering exe directory: %s", err) 179 } else { 180 dirs = append(dirs, filepath.Dir(exePath)) 181 } 182 183 // add the user vendor directory 184 dirs = append(dirs, DefaultPluginVendorDir) 185 186 if includeAutoInstalled { 187 dirs = append(dirs, m.pluginDir()) 188 } 189 dirs = append(dirs, m.GlobalPluginDirs...) 190 191 return dirs 192 } 193 194 func (m *Meta) pluginCache() discovery.PluginCache { 195 dir := m.PluginCacheDir 196 if dir == "" { 197 return nil // cache disabled 198 } 199 200 dir = filepath.Join(dir, pluginMachineName) 201 202 return discovery.NewLocalPluginCache(dir) 203 } 204 205 // providerPluginSet returns the set of valid providers that were discovered in 206 // the defined search paths. 207 func (m *Meta) providerPluginSet() discovery.PluginMetaSet { 208 plugins := discovery.FindPlugins("provider", m.pluginDirs(true)) 209 210 // Add providers defined in the legacy .terraformrc, 211 if m.PluginOverrides != nil { 212 plugins = plugins.OverridePaths(m.PluginOverrides.Providers) 213 } 214 215 plugins, _ = plugins.ValidateVersions() 216 217 for p := range plugins { 218 log.Printf("[DEBUG] found valid plugin: %q", p.Name) 219 } 220 221 return plugins 222 } 223 224 // providerPluginAutoInstalledSet returns the set of providers that exist 225 // within the auto-install directory. 226 func (m *Meta) providerPluginAutoInstalledSet() discovery.PluginMetaSet { 227 plugins := discovery.FindPlugins("provider", []string{m.pluginDir()}) 228 plugins, _ = plugins.ValidateVersions() 229 230 for p := range plugins { 231 log.Printf("[DEBUG] found valid plugin: %q", p.Name) 232 } 233 234 return plugins 235 } 236 237 // providerPluginManuallyInstalledSet returns the set of providers that exist 238 // in all locations *except* the auto-install directory. 239 func (m *Meta) providerPluginManuallyInstalledSet() discovery.PluginMetaSet { 240 plugins := discovery.FindPlugins("provider", m.pluginDirs(false)) 241 242 // Add providers defined in the legacy .terraformrc, 243 if m.PluginOverrides != nil { 244 plugins = plugins.OverridePaths(m.PluginOverrides.Providers) 245 } 246 247 plugins, _ = plugins.ValidateVersions() 248 249 for p := range plugins { 250 log.Printf("[DEBUG] found valid plugin: %q", p.Name) 251 } 252 253 return plugins 254 } 255 256 func (m *Meta) providerResolver() terraform.ResourceProviderResolver { 257 return &multiVersionProviderResolver{ 258 Available: m.providerPluginSet(), 259 Internal: m.internalProviders(), 260 } 261 } 262 263 func (m *Meta) internalProviders() map[string]terraform.ResourceProviderFactory { 264 return map[string]terraform.ResourceProviderFactory{ 265 "terraform": func() (terraform.ResourceProvider, error) { 266 return terraformProvider.Provider(), nil 267 }, 268 } 269 } 270 271 // filter the requirements returning only the providers that we can't resolve 272 func (m *Meta) missingPlugins(avail discovery.PluginMetaSet, reqd discovery.PluginRequirements) discovery.PluginRequirements { 273 missing := make(discovery.PluginRequirements) 274 275 for n, r := range reqd { 276 log.Printf("[DEBUG] plugin requirements: %q=%q", n, r.Versions) 277 } 278 279 candidates := avail.ConstrainVersions(reqd) 280 281 for name, versionSet := range reqd { 282 if metas := candidates[name]; metas.Count() == 0 { 283 missing[name] = versionSet 284 } 285 } 286 287 return missing 288 } 289 290 func (m *Meta) provisionerFactories() map[string]terraform.ResourceProvisionerFactory { 291 dirs := m.pluginDirs(true) 292 plugins := discovery.FindPlugins("provisioner", dirs) 293 plugins, _ = plugins.ValidateVersions() 294 295 // For now our goal is to just find the latest version of each plugin 296 // we have on the system. All provisioners should be at version 0.0.0 297 // currently, so there should actually only be one instance of each plugin 298 // name here, even though the discovery interface forces us to pretend 299 // that might not be true. 300 301 factories := make(map[string]terraform.ResourceProvisionerFactory) 302 303 // Wire up the internal provisioners first. These might be overridden 304 // by discovered provisioners below. 305 for name := range InternalProvisioners { 306 client, err := internalPluginClient("provisioner", name) 307 if err != nil { 308 log.Printf("[WARN] failed to build command line for internal plugin %q: %s", name, err) 309 continue 310 } 311 factories[name] = provisionerFactory(client) 312 } 313 314 byName := plugins.ByName() 315 for name, metas := range byName { 316 // Since we validated versions above and we partitioned the sets 317 // by name, we're guaranteed that the metas in our set all have 318 // valid versions and that there's at least one meta. 319 newest := metas.Newest() 320 client := tfplugin.Client(newest) 321 factories[name] = provisionerFactory(client) 322 } 323 324 return factories 325 } 326 327 func internalPluginClient(kind, name string) (*plugin.Client, error) { 328 cmdLine, err := BuildPluginCommandString(kind, name) 329 if err != nil { 330 return nil, err 331 } 332 333 // See the docstring for BuildPluginCommandString for why we need to do 334 // this split here. 335 cmdArgv := strings.Split(cmdLine, TFSPACE) 336 337 cfg := &plugin.ClientConfig{ 338 Cmd: exec.Command(cmdArgv[0], cmdArgv[1:]...), 339 HandshakeConfig: tfplugin.Handshake, 340 Managed: true, 341 Plugins: tfplugin.PluginMap, 342 } 343 344 return plugin.NewClient(cfg), nil 345 } 346 347 func providerFactory(client *plugin.Client) terraform.ResourceProviderFactory { 348 return func() (terraform.ResourceProvider, error) { 349 // Request the RPC client so we can get the provider 350 // so we can build the actual RPC-implemented provider. 351 rpcClient, err := client.Client() 352 if err != nil { 353 return nil, err 354 } 355 356 raw, err := rpcClient.Dispense(tfplugin.ProviderPluginName) 357 if err != nil { 358 return nil, err 359 } 360 361 return raw.(terraform.ResourceProvider), nil 362 } 363 } 364 365 func provisionerFactory(client *plugin.Client) terraform.ResourceProvisionerFactory { 366 return func() (terraform.ResourceProvisioner, error) { 367 // Request the RPC client so we can get the provisioner 368 // so we can build the actual RPC-implemented provisioner. 369 rpcClient, err := client.Client() 370 if err != nil { 371 return nil, err 372 } 373 374 raw, err := rpcClient.Dispense(tfplugin.ProvisionerPluginName) 375 if err != nil { 376 return nil, err 377 } 378 379 return raw.(terraform.ResourceProvisioner), nil 380 } 381 }