github.com/ezbercih/terraform@v0.1.1-0.20140729011846-3c33865e0839/config.go (about) 1 package main 2 3 import ( 4 "os/exec" 5 "path/filepath" 6 7 "github.com/hashicorp/terraform/plugin" 8 "github.com/hashicorp/terraform/rpc" 9 "github.com/hashicorp/terraform/terraform" 10 "github.com/mitchellh/go-libucl" 11 "github.com/mitchellh/osext" 12 ) 13 14 // Config is the structure of the configuration for the Terraform CLI. 15 // 16 // This is not the configuration for Terraform itself. That is in the 17 // "config" package. 18 type Config struct { 19 Providers map[string]string 20 Provisioners map[string]string 21 } 22 23 // BuiltinConfig is the built-in defaults for the configuration. These 24 // can be overridden by user configurations. 25 var BuiltinConfig Config 26 27 // ContextOpts are the global ContextOpts we use to initialize the CLI. 28 var ContextOpts terraform.ContextOpts 29 30 // Put the parse flags we use for libucl in a constant so we can get 31 // equally behaving parsing everywhere. 32 const libuclParseFlags = libucl.ParserNoTime 33 34 func init() { 35 BuiltinConfig.Providers = map[string]string{ 36 "aws": "terraform-provider-aws", 37 "digitalocean": "terraform-provider-digitalocean", 38 "heroku": "terraform-provider-heroku", 39 "dnsimple": "terraform-provider-dnsimple", 40 "consul": "terraform-provider-consul", 41 "cloudflare": "terraform-provider-cloudflare", 42 } 43 BuiltinConfig.Provisioners = map[string]string{ 44 "local-exec": "terraform-provisioner-local-exec", 45 "remote-exec": "terraform-provisioner-remote-exec", 46 "file": "terraform-provisioner-file", 47 } 48 } 49 50 // LoadConfig loads the CLI configuration from ".terraformrc" files. 51 func LoadConfig(path string) (*Config, error) { 52 var obj *libucl.Object 53 54 // Parse the file and get the root object. 55 parser := libucl.NewParser(libuclParseFlags) 56 err := parser.AddFile(path) 57 if err == nil { 58 obj = parser.Object() 59 defer obj.Close() 60 } 61 defer parser.Close() 62 63 // If there was an error parsing, return now. 64 if err != nil { 65 return nil, err 66 } 67 68 // Build up the result 69 var result Config 70 71 if err := obj.Decode(&result); err != nil { 72 return nil, err 73 } 74 75 return &result, nil 76 } 77 78 // Merge merges two configurations and returns a third entirely 79 // new configuration with the two merged. 80 func (c1 *Config) Merge(c2 *Config) *Config { 81 var result Config 82 result.Providers = make(map[string]string) 83 result.Provisioners = make(map[string]string) 84 for k, v := range c1.Providers { 85 result.Providers[k] = v 86 } 87 for k, v := range c2.Providers { 88 result.Providers[k] = v 89 } 90 for k, v := range c1.Provisioners { 91 result.Provisioners[k] = v 92 } 93 for k, v := range c2.Provisioners { 94 result.Provisioners[k] = v 95 } 96 97 return &result 98 } 99 100 // ProviderFactories returns the mapping of prefixes to 101 // ResourceProviderFactory that can be used to instantiate a 102 // binary-based plugin. 103 func (c *Config) ProviderFactories() map[string]terraform.ResourceProviderFactory { 104 result := make(map[string]terraform.ResourceProviderFactory) 105 for k, v := range c.Providers { 106 result[k] = c.providerFactory(v) 107 } 108 109 return result 110 } 111 112 func (c *Config) providerFactory(path string) terraform.ResourceProviderFactory { 113 return func() (terraform.ResourceProvider, error) { 114 // Build the plugin client configuration and init the plugin 115 var config plugin.ClientConfig 116 config.Cmd = pluginCmd(path) 117 config.Managed = true 118 client := plugin.NewClient(&config) 119 120 // Request the RPC client and service name from the client 121 // so we can build the actual RPC-implemented provider. 122 rpcClient, err := client.Client() 123 if err != nil { 124 return nil, err 125 } 126 127 service, err := client.Service() 128 if err != nil { 129 return nil, err 130 } 131 132 return &rpc.ResourceProvider{ 133 Client: rpcClient, 134 Name: service, 135 }, nil 136 } 137 } 138 139 // ProvisionerFactories returns the mapping of prefixes to 140 // ResourceProvisionerFactory that can be used to instantiate a 141 // binary-based plugin. 142 func (c *Config) ProvisionerFactories() map[string]terraform.ResourceProvisionerFactory { 143 result := make(map[string]terraform.ResourceProvisionerFactory) 144 for k, v := range c.Provisioners { 145 result[k] = c.provisionerFactory(v) 146 } 147 148 return result 149 } 150 151 func (c *Config) provisionerFactory(path string) terraform.ResourceProvisionerFactory { 152 return func() (terraform.ResourceProvisioner, error) { 153 // Build the plugin client configuration and init the plugin 154 var config plugin.ClientConfig 155 config.Cmd = pluginCmd(path) 156 config.Managed = true 157 client := plugin.NewClient(&config) 158 159 // Request the RPC client and service name from the client 160 // so we can build the actual RPC-implemented provider. 161 rpcClient, err := client.Client() 162 if err != nil { 163 return nil, err 164 } 165 166 service, err := client.Service() 167 if err != nil { 168 return nil, err 169 } 170 171 return &rpc.ResourceProvisioner{ 172 Client: rpcClient, 173 Name: service, 174 }, nil 175 } 176 } 177 178 func pluginCmd(path string) *exec.Cmd { 179 originalPath := path 180 181 // First look for the provider on the PATH. 182 path, err := exec.LookPath(path) 183 if err != nil { 184 // If that doesn't work, look for it in the same directory 185 // as the executable that is running. 186 exePath, err := osext.Executable() 187 if err == nil { 188 path = filepath.Join( 189 filepath.Dir(exePath), 190 filepath.Base(originalPath)) 191 } 192 } 193 194 // If we still don't have a path set, then set it to the 195 // original path and let any errors that happen bubble out. 196 if path == "" { 197 path = originalPath 198 } 199 200 // Build the command to execute the plugin 201 return exec.Command(path) 202 }