github.com/federicobaldo/terraform@v0.6.15-0.20160323222747-b20f680cbf05/config.go (about) 1 package main 2 3 import ( 4 "fmt" 5 "io/ioutil" 6 "log" 7 "os" 8 "os/exec" 9 "path/filepath" 10 "strings" 11 12 "github.com/hashicorp/hcl" 13 "github.com/hashicorp/terraform/plugin" 14 "github.com/hashicorp/terraform/terraform" 15 "github.com/kardianos/osext" 16 ) 17 18 // Config is the structure of the configuration for the Terraform CLI. 19 // 20 // This is not the configuration for Terraform itself. That is in the 21 // "config" package. 22 type Config struct { 23 Providers map[string]string 24 Provisioners map[string]string 25 26 DisableCheckpoint bool `hcl:"disable_checkpoint"` 27 DisableCheckpointSignature bool `hcl:"disable_checkpoint_signature"` 28 } 29 30 // BuiltinConfig is the built-in defaults for the configuration. These 31 // can be overridden by user configurations. 32 var BuiltinConfig Config 33 34 // ContextOpts are the global ContextOpts we use to initialize the CLI. 35 var ContextOpts terraform.ContextOpts 36 37 // ConfigFile returns the default path to the configuration file. 38 // 39 // On Unix-like systems this is the ".terraformrc" file in the home directory. 40 // On Windows, this is the "terraform.rc" file in the application data 41 // directory. 42 func ConfigFile() (string, error) { 43 return configFile() 44 } 45 46 // ConfigDir returns the configuration directory for Terraform. 47 func ConfigDir() (string, error) { 48 return configDir() 49 } 50 51 // LoadConfig loads the CLI configuration from ".terraformrc" files. 52 func LoadConfig(path string) (*Config, error) { 53 // Read the HCL file and prepare for parsing 54 d, err := ioutil.ReadFile(path) 55 if err != nil { 56 return nil, fmt.Errorf( 57 "Error reading %s: %s", path, err) 58 } 59 60 // Parse it 61 obj, err := hcl.Parse(string(d)) 62 if err != nil { 63 return nil, fmt.Errorf( 64 "Error parsing %s: %s", path, err) 65 } 66 67 // Build up the result 68 var result Config 69 if err := hcl.DecodeObject(&result, obj); err != nil { 70 return nil, err 71 } 72 73 return &result, nil 74 } 75 76 // Discover discovers plugins. 77 // 78 // This looks in the directory of the executable and the CWD, in that 79 // order for priority. 80 func (c *Config) Discover() error { 81 // Look in the cwd. 82 if err := c.discover("."); err != nil { 83 return err 84 } 85 86 // Look in the plugins directory. This will override any found 87 // in the current directory. 88 dir, err := ConfigDir() 89 if err != nil { 90 log.Printf("[ERR] Error loading config directory: %s", err) 91 } else { 92 if err := c.discover(filepath.Join(dir, "plugins")); err != nil { 93 return err 94 } 95 } 96 97 // Next, look in the same directory as the executable. Any conflicts 98 // will overwrite those found in our current directory. 99 exePath, err := osext.Executable() 100 if err != nil { 101 log.Printf("[ERR] Error loading exe directory: %s", err) 102 } else { 103 if err := c.discover(filepath.Dir(exePath)); err != nil { 104 return err 105 } 106 } 107 108 return nil 109 } 110 111 // Merge merges two configurations and returns a third entirely 112 // new configuration with the two merged. 113 func (c1 *Config) Merge(c2 *Config) *Config { 114 var result Config 115 result.Providers = make(map[string]string) 116 result.Provisioners = make(map[string]string) 117 for k, v := range c1.Providers { 118 result.Providers[k] = v 119 } 120 for k, v := range c2.Providers { 121 result.Providers[k] = v 122 } 123 for k, v := range c1.Provisioners { 124 result.Provisioners[k] = v 125 } 126 for k, v := range c2.Provisioners { 127 result.Provisioners[k] = v 128 } 129 130 return &result 131 } 132 133 func (c *Config) discover(path string) error { 134 var err error 135 136 if !filepath.IsAbs(path) { 137 path, err = filepath.Abs(path) 138 if err != nil { 139 return err 140 } 141 } 142 143 err = c.discoverSingle( 144 filepath.Join(path, "terraform-provider-*"), &c.Providers) 145 if err != nil { 146 return err 147 } 148 149 err = c.discoverSingle( 150 filepath.Join(path, "terraform-provisioner-*"), &c.Provisioners) 151 if err != nil { 152 return err 153 } 154 155 return nil 156 } 157 158 func (c *Config) discoverSingle(glob string, m *map[string]string) error { 159 matches, err := filepath.Glob(glob) 160 if err != nil { 161 return err 162 } 163 164 if *m == nil { 165 *m = make(map[string]string) 166 } 167 168 for _, match := range matches { 169 file := filepath.Base(match) 170 171 // If the filename has a ".", trim up to there 172 if idx := strings.Index(file, "."); idx >= 0 { 173 file = file[:idx] 174 } 175 176 // Look for foo-bar-baz. The plugin name is "baz" 177 parts := strings.SplitN(file, "-", 3) 178 if len(parts) != 3 { 179 continue 180 } 181 182 log.Printf("[DEBUG] Discovered plugin: %s = %s", parts[2], match) 183 (*m)[parts[2]] = match 184 } 185 186 return nil 187 } 188 189 // ProviderFactories returns the mapping of prefixes to 190 // ResourceProviderFactory that can be used to instantiate a 191 // binary-based plugin. 192 func (c *Config) ProviderFactories() map[string]terraform.ResourceProviderFactory { 193 result := make(map[string]terraform.ResourceProviderFactory) 194 for k, v := range c.Providers { 195 result[k] = c.providerFactory(v) 196 } 197 198 return result 199 } 200 201 func (c *Config) providerFactory(path string) terraform.ResourceProviderFactory { 202 // Build the plugin client configuration and init the plugin 203 var config plugin.ClientConfig 204 config.Cmd = pluginCmd(path) 205 config.Managed = true 206 client := plugin.NewClient(&config) 207 208 return func() (terraform.ResourceProvider, error) { 209 // Request the RPC client so we can get the provider 210 // so we can build the actual RPC-implemented provider. 211 rpcClient, err := client.Client() 212 if err != nil { 213 return nil, err 214 } 215 216 return rpcClient.ResourceProvider() 217 } 218 } 219 220 // ProvisionerFactories returns the mapping of prefixes to 221 // ResourceProvisionerFactory that can be used to instantiate a 222 // binary-based plugin. 223 func (c *Config) ProvisionerFactories() map[string]terraform.ResourceProvisionerFactory { 224 result := make(map[string]terraform.ResourceProvisionerFactory) 225 for k, v := range c.Provisioners { 226 result[k] = c.provisionerFactory(v) 227 } 228 229 return result 230 } 231 232 func (c *Config) provisionerFactory(path string) terraform.ResourceProvisionerFactory { 233 // Build the plugin client configuration and init the plugin 234 var config plugin.ClientConfig 235 config.Cmd = pluginCmd(path) 236 config.Managed = true 237 client := plugin.NewClient(&config) 238 239 return func() (terraform.ResourceProvisioner, error) { 240 rpcClient, err := client.Client() 241 if err != nil { 242 return nil, err 243 } 244 245 return rpcClient.ResourceProvisioner() 246 } 247 } 248 249 func pluginCmd(path string) *exec.Cmd { 250 cmdPath := "" 251 252 // If the path doesn't contain a separator, look in the same 253 // directory as the Terraform executable first. 254 if !strings.ContainsRune(path, os.PathSeparator) { 255 exePath, err := osext.Executable() 256 if err == nil { 257 temp := filepath.Join( 258 filepath.Dir(exePath), 259 filepath.Base(path)) 260 261 if _, err := os.Stat(temp); err == nil { 262 cmdPath = temp 263 } 264 } 265 266 // If we still haven't found the executable, look for it 267 // in the PATH. 268 if v, err := exec.LookPath(path); err == nil { 269 cmdPath = v 270 } 271 } 272 273 // If we still don't have a path, then just set it to the original 274 // given path. 275 if cmdPath == "" { 276 cmdPath = path 277 } 278 279 // Build the command to execute the plugin 280 return exec.Command(cmdPath) 281 }