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