github.com/paybyphone/terraform@v0.9.5-0.20170613192930-9706042ddd51/command/plugins.go (about) 1 package command 2 3 import ( 4 "fmt" 5 "log" 6 "os/exec" 7 "path/filepath" 8 "runtime" 9 "strings" 10 11 plugin "github.com/hashicorp/go-plugin" 12 tfplugin "github.com/hashicorp/terraform/plugin" 13 "github.com/hashicorp/terraform/plugin/discovery" 14 "github.com/hashicorp/terraform/terraform" 15 "github.com/kardianos/osext" 16 ) 17 18 // multiVersionProviderResolver is an implementation of 19 // terraform.ResourceProviderResolver that matches the given version constraints 20 // against a set of versioned provider plugins to find the newest version of 21 // each that satisfies the given constraints. 22 type multiVersionProviderResolver struct { 23 Available discovery.PluginMetaSet 24 } 25 26 func choosePlugins(avail discovery.PluginMetaSet, reqd discovery.PluginRequirements) map[string]discovery.PluginMeta { 27 candidates := avail.ConstrainVersions(reqd) 28 ret := map[string]discovery.PluginMeta{} 29 for name, metas := range candidates { 30 if len(metas) == 0 { 31 continue 32 } 33 ret[name] = metas.Newest() 34 } 35 return ret 36 } 37 38 func (r *multiVersionProviderResolver) ResolveProviders( 39 reqd discovery.PluginRequirements, 40 ) (map[string]terraform.ResourceProviderFactory, []error) { 41 factories := make(map[string]terraform.ResourceProviderFactory, len(reqd)) 42 var errs []error 43 44 chosen := choosePlugins(r.Available, reqd) 45 for name := range reqd { 46 if newest, available := chosen[name]; available { 47 digest, err := newest.SHA256() 48 if err != nil { 49 errs = append(errs, fmt.Errorf("provider.%s: failed to load plugin to verify its signature: %s", name, err)) 50 continue 51 } 52 if !reqd[name].AcceptsSHA256(digest) { 53 // This generic error message is intended to avoid troubling 54 // users with implementation details. The main useful point 55 // here is that they need to run "terraform init" to 56 // fix this, which is covered by the UI code reporting these 57 // error messages. 58 errs = append(errs, fmt.Errorf("provider.%s: installed but not yet initialized", name)) 59 continue 60 } 61 62 client := tfplugin.Client(newest) 63 factories[name] = providerFactory(client) 64 } else { 65 errs = append(errs, fmt.Errorf("provider.%s: no suitable version installed", name)) 66 } 67 } 68 69 return factories, errs 70 } 71 72 // the default location for automatically installed plugins 73 func (m *Meta) pluginDir() string { 74 return filepath.Join(m.DataDir(), "plugins", fmt.Sprintf("%s_%s", runtime.GOOS, runtime.GOARCH)) 75 } 76 77 // pluginDirs return a list of directories to search for plugins. 78 // 79 // Earlier entries in this slice get priority over later when multiple copies 80 // of the same plugin version are found, but newer versions always override 81 // older versions where both satisfy the provider version constraints. 82 func (m *Meta) pluginDirs(includeAutoInstalled bool) []string { 83 84 // When searching the following directories, earlier entries get precedence 85 // if the same plugin version is found twice, but newer versions will 86 // always get preference below regardless of where they are coming from. 87 // TODO: Add auto-install dir, default vendor dir and optional override 88 // vendor dir(s). 89 dirs := []string{"."} 90 91 // Look in the same directory as the Terraform executable. 92 // If found, this replaces what we found in the config path. 93 exePath, err := osext.Executable() 94 if err != nil { 95 log.Printf("[ERROR] Error discovering exe directory: %s", err) 96 } else { 97 dirs = append(dirs, filepath.Dir(exePath)) 98 } 99 100 if includeAutoInstalled { 101 dirs = append(dirs, m.pluginDir()) 102 } 103 dirs = append(dirs, m.GlobalPluginDirs...) 104 return dirs 105 } 106 107 // providerPluginSet returns the set of valid providers that were discovered in 108 // the defined search paths. 109 func (m *Meta) providerPluginSet() discovery.PluginMetaSet { 110 plugins := discovery.FindPlugins("provider", m.pluginDirs(true)) 111 plugins, _ = plugins.ValidateVersions() 112 113 for p := range plugins { 114 log.Printf("[DEBUG] found valid plugin: %q", p.Name) 115 } 116 117 return plugins 118 } 119 120 // providerPluginAutoInstalledSet returns the set of providers that exist 121 // within the auto-install directory. 122 func (m *Meta) providerPluginAutoInstalledSet() discovery.PluginMetaSet { 123 plugins := discovery.FindPlugins("provider", []string{m.pluginDir()}) 124 plugins, _ = plugins.ValidateVersions() 125 126 for p := range plugins { 127 log.Printf("[DEBUG] found valid plugin: %q", p.Name) 128 } 129 130 return plugins 131 } 132 133 // providerPluginManuallyInstalledSet returns the set of providers that exist 134 // in all locations *except* the auto-install directory. 135 func (m *Meta) providerPluginManuallyInstalledSet() discovery.PluginMetaSet { 136 plugins := discovery.FindPlugins("provider", m.pluginDirs(false)) 137 plugins, _ = plugins.ValidateVersions() 138 139 for p := range plugins { 140 log.Printf("[DEBUG] found valid plugin: %q", p.Name) 141 } 142 143 return plugins 144 } 145 146 func (m *Meta) providerResolver() terraform.ResourceProviderResolver { 147 return &multiVersionProviderResolver{ 148 Available: m.providerPluginSet(), 149 } 150 } 151 152 // filter the requirements returning only the providers that we can't resolve 153 func (m *Meta) missingPlugins(avail discovery.PluginMetaSet, reqd discovery.PluginRequirements) discovery.PluginRequirements { 154 missing := make(discovery.PluginRequirements) 155 156 for n, r := range reqd { 157 log.Printf("[DEBUG] plugin requirements: %q=%q", n, r.Versions) 158 } 159 160 candidates := avail.ConstrainVersions(reqd) 161 162 for name, versionSet := range reqd { 163 if metas := candidates[name]; metas.Count() == 0 { 164 missing[name] = versionSet 165 } 166 } 167 168 return missing 169 } 170 171 func (m *Meta) provisionerFactories() map[string]terraform.ResourceProvisionerFactory { 172 dirs := m.pluginDirs(true) 173 plugins := discovery.FindPlugins("provisioner", dirs) 174 plugins, _ = plugins.ValidateVersions() 175 176 // For now our goal is to just find the latest version of each plugin 177 // we have on the system. All provisioners should be at version 0.0.0 178 // currently, so there should actually only be one instance of each plugin 179 // name here, even though the discovery interface forces us to pretend 180 // that might not be true. 181 182 factories := make(map[string]terraform.ResourceProvisionerFactory) 183 184 // Wire up the internal provisioners first. These might be overridden 185 // by discovered provisioners below. 186 for name := range InternalProvisioners { 187 client, err := internalPluginClient("provisioner", name) 188 if err != nil { 189 log.Printf("[WARN] failed to build command line for internal plugin %q: %s", name, err) 190 continue 191 } 192 factories[name] = provisionerFactory(client) 193 } 194 195 byName := plugins.ByName() 196 for name, metas := range byName { 197 // Since we validated versions above and we partitioned the sets 198 // by name, we're guaranteed that the metas in our set all have 199 // valid versions and that there's at least one meta. 200 newest := metas.Newest() 201 client := tfplugin.Client(newest) 202 factories[name] = provisionerFactory(client) 203 } 204 205 return factories 206 } 207 208 func internalPluginClient(kind, name string) (*plugin.Client, error) { 209 cmdLine, err := BuildPluginCommandString(kind, name) 210 if err != nil { 211 return nil, err 212 } 213 214 // See the docstring for BuildPluginCommandString for why we need to do 215 // this split here. 216 cmdArgv := strings.Split(cmdLine, TFSPACE) 217 218 cfg := &plugin.ClientConfig{ 219 Cmd: exec.Command(cmdArgv[0], cmdArgv[1:]...), 220 HandshakeConfig: tfplugin.Handshake, 221 Managed: true, 222 Plugins: tfplugin.PluginMap, 223 } 224 225 return plugin.NewClient(cfg), nil 226 } 227 228 func providerFactory(client *plugin.Client) terraform.ResourceProviderFactory { 229 return func() (terraform.ResourceProvider, error) { 230 // Request the RPC client so we can get the provider 231 // so we can build the actual RPC-implemented provider. 232 rpcClient, err := client.Client() 233 if err != nil { 234 return nil, err 235 } 236 237 raw, err := rpcClient.Dispense(tfplugin.ProviderPluginName) 238 if err != nil { 239 return nil, err 240 } 241 242 return raw.(terraform.ResourceProvider), nil 243 } 244 } 245 246 func provisionerFactory(client *plugin.Client) terraform.ResourceProvisionerFactory { 247 return func() (terraform.ResourceProvisioner, error) { 248 // Request the RPC client so we can get the provisioner 249 // so we can build the actual RPC-implemented provisioner. 250 rpcClient, err := client.Client() 251 if err != nil { 252 return nil, err 253 } 254 255 raw, err := rpcClient.Dispense(tfplugin.ProvisionerPluginName) 256 if err != nil { 257 return nil, err 258 } 259 260 return raw.(terraform.ResourceProvisioner), nil 261 } 262 }