github.com/ticketmaster/terraform@v0.10.0-beta2.0.20170711045249-a12daf5aba4f/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 plugins, _ = plugins.ValidateVersions() 176 177 for p := range plugins { 178 log.Printf("[DEBUG] found valid plugin: %q", p.Name) 179 } 180 181 return plugins 182 } 183 184 // providerPluginAutoInstalledSet returns the set of providers that exist 185 // within the auto-install directory. 186 func (m *Meta) providerPluginAutoInstalledSet() discovery.PluginMetaSet { 187 plugins := discovery.FindPlugins("provider", []string{m.pluginDir()}) 188 plugins, _ = plugins.ValidateVersions() 189 190 for p := range plugins { 191 log.Printf("[DEBUG] found valid plugin: %q", p.Name) 192 } 193 194 return plugins 195 } 196 197 // providerPluginManuallyInstalledSet returns the set of providers that exist 198 // in all locations *except* the auto-install directory. 199 func (m *Meta) providerPluginManuallyInstalledSet() discovery.PluginMetaSet { 200 plugins := discovery.FindPlugins("provider", m.pluginDirs(false)) 201 plugins, _ = plugins.ValidateVersions() 202 203 for p := range plugins { 204 log.Printf("[DEBUG] found valid plugin: %q", p.Name) 205 } 206 207 return plugins 208 } 209 210 func (m *Meta) providerResolver() terraform.ResourceProviderResolver { 211 return &multiVersionProviderResolver{ 212 Available: m.providerPluginSet(), 213 } 214 } 215 216 // filter the requirements returning only the providers that we can't resolve 217 func (m *Meta) missingPlugins(avail discovery.PluginMetaSet, reqd discovery.PluginRequirements) discovery.PluginRequirements { 218 missing := make(discovery.PluginRequirements) 219 220 for n, r := range reqd { 221 log.Printf("[DEBUG] plugin requirements: %q=%q", n, r.Versions) 222 } 223 224 candidates := avail.ConstrainVersions(reqd) 225 226 for name, versionSet := range reqd { 227 if metas := candidates[name]; metas.Count() == 0 { 228 missing[name] = versionSet 229 } 230 } 231 232 return missing 233 } 234 235 func (m *Meta) provisionerFactories() map[string]terraform.ResourceProvisionerFactory { 236 dirs := m.pluginDirs(true) 237 plugins := discovery.FindPlugins("provisioner", dirs) 238 plugins, _ = plugins.ValidateVersions() 239 240 // For now our goal is to just find the latest version of each plugin 241 // we have on the system. All provisioners should be at version 0.0.0 242 // currently, so there should actually only be one instance of each plugin 243 // name here, even though the discovery interface forces us to pretend 244 // that might not be true. 245 246 factories := make(map[string]terraform.ResourceProvisionerFactory) 247 248 // Wire up the internal provisioners first. These might be overridden 249 // by discovered provisioners below. 250 for name := range InternalProvisioners { 251 client, err := internalPluginClient("provisioner", name) 252 if err != nil { 253 log.Printf("[WARN] failed to build command line for internal plugin %q: %s", name, err) 254 continue 255 } 256 factories[name] = provisionerFactory(client) 257 } 258 259 byName := plugins.ByName() 260 for name, metas := range byName { 261 // Since we validated versions above and we partitioned the sets 262 // by name, we're guaranteed that the metas in our set all have 263 // valid versions and that there's at least one meta. 264 newest := metas.Newest() 265 client := tfplugin.Client(newest) 266 factories[name] = provisionerFactory(client) 267 } 268 269 return factories 270 } 271 272 func internalPluginClient(kind, name string) (*plugin.Client, error) { 273 cmdLine, err := BuildPluginCommandString(kind, name) 274 if err != nil { 275 return nil, err 276 } 277 278 // See the docstring for BuildPluginCommandString for why we need to do 279 // this split here. 280 cmdArgv := strings.Split(cmdLine, TFSPACE) 281 282 cfg := &plugin.ClientConfig{ 283 Cmd: exec.Command(cmdArgv[0], cmdArgv[1:]...), 284 HandshakeConfig: tfplugin.Handshake, 285 Managed: true, 286 Plugins: tfplugin.PluginMap, 287 } 288 289 return plugin.NewClient(cfg), nil 290 } 291 292 func providerFactory(client *plugin.Client) terraform.ResourceProviderFactory { 293 return func() (terraform.ResourceProvider, error) { 294 // Request the RPC client so we can get the provider 295 // so we can build the actual RPC-implemented provider. 296 rpcClient, err := client.Client() 297 if err != nil { 298 return nil, err 299 } 300 301 raw, err := rpcClient.Dispense(tfplugin.ProviderPluginName) 302 if err != nil { 303 return nil, err 304 } 305 306 return raw.(terraform.ResourceProvider), nil 307 } 308 } 309 310 func provisionerFactory(client *plugin.Client) terraform.ResourceProvisionerFactory { 311 return func() (terraform.ResourceProvisioner, error) { 312 // Request the RPC client so we can get the provisioner 313 // so we can build the actual RPC-implemented provisioner. 314 rpcClient, err := client.Client() 315 if err != nil { 316 return nil, err 317 } 318 319 raw, err := rpcClient.Dispense(tfplugin.ProvisionerPluginName) 320 if err != nil { 321 return nil, err 322 } 323 324 return raw.(terraform.ResourceProvisioner), nil 325 } 326 }