mirror of
https://github.com/opentofu/opentofu.git
synced 2024-12-30 10:47:14 -06:00
f7512ca29f
Docker's API is huge and only a small subset is currently implemented, but this is expected to grow over time. Currently it's enough to satisfy the use cases of probably 95% of Docker users. I'm preparing this initial pull request as a preview step for feedback. My ideal scenario would be to develop this within a branch in the main repository; the more eyes and testing and pitching in on the code, the better (this would avoid a merge request-to-the-merge-request scenario, as I figure this will be built up over the longer term, even before a merge into master). Unit tests do not exist yet. Right now I've just been focused on getting initial functionality ported over. I've been testing each option extensively via the Docker inspect capabilities. This code (C)2014-2015 Akamai Technologies, Inc. <opensource@akamai.com>
282 lines
6.7 KiB
Go
282 lines
6.7 KiB
Go
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"io/ioutil"
|
|
"log"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"github.com/hashicorp/hcl"
|
|
"github.com/hashicorp/terraform/plugin"
|
|
"github.com/hashicorp/terraform/terraform"
|
|
"github.com/mitchellh/osext"
|
|
)
|
|
|
|
// Config is the structure of the configuration for the Terraform CLI.
|
|
//
|
|
// This is not the configuration for Terraform itself. That is in the
|
|
// "config" package.
|
|
type Config struct {
|
|
Providers map[string]string
|
|
Provisioners map[string]string
|
|
|
|
DisableCheckpoint bool `hcl:"disable_checkpoint"`
|
|
DisableCheckpointSignature bool `hcl:"disable_checkpoint_signature"`
|
|
}
|
|
|
|
// BuiltinConfig is the built-in defaults for the configuration. These
|
|
// can be overridden by user configurations.
|
|
var BuiltinConfig Config
|
|
|
|
// ContextOpts are the global ContextOpts we use to initialize the CLI.
|
|
var ContextOpts terraform.ContextOpts
|
|
|
|
// ConfigFile returns the default path to the configuration file.
|
|
//
|
|
// On Unix-like systems this is the ".terraformrc" file in the home directory.
|
|
// On Windows, this is the "terraform.rc" file in the application data
|
|
// directory.
|
|
func ConfigFile() (string, error) {
|
|
return configFile()
|
|
}
|
|
|
|
// ConfigDir returns the configuration directory for Terraform.
|
|
func ConfigDir() (string, error) {
|
|
return configDir()
|
|
}
|
|
|
|
// LoadConfig loads the CLI configuration from ".terraformrc" files.
|
|
func LoadConfig(path string) (*Config, error) {
|
|
// Read the HCL file and prepare for parsing
|
|
d, err := ioutil.ReadFile(path)
|
|
if err != nil {
|
|
return nil, fmt.Errorf(
|
|
"Error reading %s: %s", path, err)
|
|
}
|
|
|
|
// Parse it
|
|
obj, err := hcl.Parse(string(d))
|
|
if err != nil {
|
|
return nil, fmt.Errorf(
|
|
"Error parsing %s: %s", path, err)
|
|
}
|
|
|
|
// Build up the result
|
|
var result Config
|
|
if err := hcl.DecodeObject(&result, obj); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &result, nil
|
|
}
|
|
|
|
// Discover discovers plugins.
|
|
//
|
|
// This looks in the directory of the executable and the CWD, in that
|
|
// order for priority.
|
|
func (c *Config) Discover() error {
|
|
// Look in the cwd.
|
|
if err := c.discover("."); err != nil {
|
|
return err
|
|
}
|
|
|
|
// Look in the plugins directory. This will override any found
|
|
// in the current directory.
|
|
dir, err := ConfigDir()
|
|
if err != nil {
|
|
log.Printf("[ERR] Error loading config directory: %s", err)
|
|
} else {
|
|
if err := c.discover(filepath.Join(dir, "plugins")); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
// Next, look in the same directory as the executable. Any conflicts
|
|
// will overwrite those found in our current directory.
|
|
exePath, err := osext.Executable()
|
|
if err != nil {
|
|
log.Printf("[ERR] Error loading exe directory: %s", err)
|
|
} else {
|
|
if err := c.discover(filepath.Dir(exePath)); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Merge merges two configurations and returns a third entirely
|
|
// new configuration with the two merged.
|
|
func (c1 *Config) Merge(c2 *Config) *Config {
|
|
var result Config
|
|
result.Providers = make(map[string]string)
|
|
result.Provisioners = make(map[string]string)
|
|
for k, v := range c1.Providers {
|
|
result.Providers[k] = v
|
|
}
|
|
for k, v := range c2.Providers {
|
|
result.Providers[k] = v
|
|
}
|
|
for k, v := range c1.Provisioners {
|
|
result.Provisioners[k] = v
|
|
}
|
|
for k, v := range c2.Provisioners {
|
|
result.Provisioners[k] = v
|
|
}
|
|
|
|
return &result
|
|
}
|
|
|
|
func (c *Config) discover(path string) error {
|
|
var err error
|
|
|
|
if !filepath.IsAbs(path) {
|
|
path, err = filepath.Abs(path)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
err = c.discoverSingle(
|
|
filepath.Join(path, "terraform-provider-*"), &c.Providers)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = c.discoverSingle(
|
|
filepath.Join(path, "terraform-provisioner-*"), &c.Provisioners)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (c *Config) discoverSingle(glob string, m *map[string]string) error {
|
|
matches, err := filepath.Glob(glob)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if *m == nil {
|
|
*m = make(map[string]string)
|
|
}
|
|
|
|
for _, match := range matches {
|
|
file := filepath.Base(match)
|
|
|
|
// If the filename has a ".", trim up to there
|
|
if idx := strings.Index(file, "."); idx >= 0 {
|
|
file = file[:idx]
|
|
}
|
|
|
|
// Look for foo-bar-baz. The plugin name is "baz"
|
|
parts := strings.SplitN(file, "-", 3)
|
|
if len(parts) != 3 {
|
|
continue
|
|
}
|
|
|
|
log.Printf("[DEBUG] Discovered plugin: %s = %s", parts[2], match)
|
|
(*m)[parts[2]] = match
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// ProviderFactories returns the mapping of prefixes to
|
|
// ResourceProviderFactory that can be used to instantiate a
|
|
// binary-based plugin.
|
|
func (c *Config) ProviderFactories() map[string]terraform.ResourceProviderFactory {
|
|
result := make(map[string]terraform.ResourceProviderFactory)
|
|
for k, v := range c.Providers {
|
|
result[k] = c.providerFactory(v)
|
|
}
|
|
|
|
return result
|
|
}
|
|
|
|
func (c *Config) providerFactory(path string) terraform.ResourceProviderFactory {
|
|
// Build the plugin client configuration and init the plugin
|
|
var config plugin.ClientConfig
|
|
config.Cmd = pluginCmd(path)
|
|
config.Managed = true
|
|
client := plugin.NewClient(&config)
|
|
|
|
return func() (terraform.ResourceProvider, error) {
|
|
// Request the RPC client so we can get the provider
|
|
// so we can build the actual RPC-implemented provider.
|
|
rpcClient, err := client.Client()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return rpcClient.ResourceProvider()
|
|
}
|
|
}
|
|
|
|
// ProvisionerFactories returns the mapping of prefixes to
|
|
// ResourceProvisionerFactory that can be used to instantiate a
|
|
// binary-based plugin.
|
|
func (c *Config) ProvisionerFactories() map[string]terraform.ResourceProvisionerFactory {
|
|
result := make(map[string]terraform.ResourceProvisionerFactory)
|
|
for k, v := range c.Provisioners {
|
|
result[k] = c.provisionerFactory(v)
|
|
}
|
|
|
|
return result
|
|
}
|
|
|
|
func (c *Config) provisionerFactory(path string) terraform.ResourceProvisionerFactory {
|
|
// Build the plugin client configuration and init the plugin
|
|
var config plugin.ClientConfig
|
|
config.Cmd = pluginCmd(path)
|
|
config.Managed = true
|
|
client := plugin.NewClient(&config)
|
|
|
|
return func() (terraform.ResourceProvisioner, error) {
|
|
rpcClient, err := client.Client()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return rpcClient.ResourceProvisioner()
|
|
}
|
|
}
|
|
|
|
func pluginCmd(path string) *exec.Cmd {
|
|
cmdPath := ""
|
|
|
|
// If the path doesn't contain a separator, look in the same
|
|
// directory as the Terraform executable first.
|
|
if !strings.ContainsRune(path, os.PathSeparator) {
|
|
exePath, err := osext.Executable()
|
|
if err == nil {
|
|
temp := filepath.Join(
|
|
filepath.Dir(exePath),
|
|
filepath.Base(path))
|
|
|
|
if _, err := os.Stat(temp); err == nil {
|
|
cmdPath = temp
|
|
}
|
|
}
|
|
|
|
// If we still haven't found the executable, look for it
|
|
// in the PATH.
|
|
if v, err := exec.LookPath(path); err == nil {
|
|
cmdPath = v
|
|
}
|
|
}
|
|
|
|
// If we still don't have a path, then just set it to the original
|
|
// given path.
|
|
if cmdPath == "" {
|
|
cmdPath = path
|
|
}
|
|
|
|
// Build the command to execute the plugin
|
|
return exec.Command(cmdPath)
|
|
}
|