github.com/hooklift/terraform@v0.11.0-beta1.0.20171117000744-6786c1361ffe/builtin/provisioners/chef/resource_provisioner.go (about) 1 package chef 2 3 import ( 4 "bytes" 5 "context" 6 "encoding/json" 7 "errors" 8 "fmt" 9 "io" 10 "log" 11 "os" 12 "path" 13 "regexp" 14 "strings" 15 "sync" 16 "text/template" 17 "time" 18 19 "github.com/hashicorp/terraform/communicator" 20 "github.com/hashicorp/terraform/communicator/remote" 21 "github.com/hashicorp/terraform/helper/schema" 22 "github.com/hashicorp/terraform/terraform" 23 "github.com/mitchellh/go-homedir" 24 "github.com/mitchellh/go-linereader" 25 ) 26 27 const ( 28 clienrb = "client.rb" 29 defaultEnv = "_default" 30 firstBoot = "first-boot.json" 31 logfileDir = "logfiles" 32 linuxChefCmd = "chef-client" 33 linuxConfDir = "/etc/chef" 34 linuxNoOutput = "> /dev/null 2>&1" 35 linuxGemCmd = "/opt/chef/embedded/bin/gem" 36 linuxKnifeCmd = "knife" 37 secretKey = "encrypted_data_bag_secret" 38 windowsChefCmd = "cmd /c chef-client" 39 windowsConfDir = "C:/chef" 40 windowsNoOutput = "> nul 2>&1" 41 windowsGemCmd = "C:/opscode/chef/embedded/bin/gem" 42 windowsKnifeCmd = "cmd /c knife" 43 ) 44 45 const clientConf = ` 46 log_location STDOUT 47 chef_server_url "{{ .ServerURL }}" 48 node_name "{{ .NodeName }}" 49 {{ if .UsePolicyfile }} 50 use_policyfile true 51 policy_group "{{ .PolicyGroup }}" 52 policy_name "{{ .PolicyName }}" 53 {{ end -}} 54 55 {{ if .HTTPProxy }} 56 http_proxy "{{ .HTTPProxy }}" 57 ENV['http_proxy'] = "{{ .HTTPProxy }}" 58 ENV['HTTP_PROXY'] = "{{ .HTTPProxy }}" 59 {{ end -}} 60 61 {{ if .HTTPSProxy }} 62 https_proxy "{{ .HTTPSProxy }}" 63 ENV['https_proxy'] = "{{ .HTTPSProxy }}" 64 ENV['HTTPS_PROXY'] = "{{ .HTTPSProxy }}" 65 {{ end -}} 66 67 {{ if .NOProxy }} 68 no_proxy "{{ join .NOProxy "," }}" 69 ENV['no_proxy'] = "{{ join .NOProxy "," }}" 70 {{ end -}} 71 72 {{ if .SSLVerifyMode }} 73 ssl_verify_mode {{ .SSLVerifyMode }} 74 {{- end -}} 75 76 {{ if .DisableReporting }} 77 enable_reporting false 78 {{ end -}} 79 80 {{ if .ClientOptions }} 81 {{ join .ClientOptions "\n" }} 82 {{ end }} 83 ` 84 85 type provisionFn func(terraform.UIOutput, communicator.Communicator) error 86 87 type provisioner struct { 88 Attributes map[string]interface{} 89 ClientOptions []string 90 DisableReporting bool 91 Environment string 92 FetchChefCertificates bool 93 LogToFile bool 94 UsePolicyfile bool 95 PolicyGroup string 96 PolicyName string 97 HTTPProxy string 98 HTTPSProxy string 99 NamedRunList string 100 NOProxy []string 101 NodeName string 102 OhaiHints []string 103 OSType string 104 RecreateClient bool 105 PreventSudo bool 106 RunList []string 107 SecretKey string 108 ServerURL string 109 SkipInstall bool 110 SkipRegister bool 111 SSLVerifyMode string 112 UserName string 113 UserKey string 114 Vaults map[string][]string 115 Version string 116 117 cleanupUserKeyCmd string 118 createConfigFiles provisionFn 119 installChefClient provisionFn 120 fetchChefCertificates provisionFn 121 generateClientKey provisionFn 122 configureVaults provisionFn 123 runChefClient provisionFn 124 useSudo bool 125 } 126 127 // Provisioner returns a Chef provisioner 128 func Provisioner() terraform.ResourceProvisioner { 129 return &schema.Provisioner{ 130 Schema: map[string]*schema.Schema{ 131 "node_name": &schema.Schema{ 132 Type: schema.TypeString, 133 Required: true, 134 }, 135 "server_url": &schema.Schema{ 136 Type: schema.TypeString, 137 Required: true, 138 }, 139 "user_name": &schema.Schema{ 140 Type: schema.TypeString, 141 Required: true, 142 }, 143 "user_key": &schema.Schema{ 144 Type: schema.TypeString, 145 Required: true, 146 }, 147 148 "attributes_json": &schema.Schema{ 149 Type: schema.TypeString, 150 Optional: true, 151 }, 152 "client_options": &schema.Schema{ 153 Type: schema.TypeList, 154 Elem: &schema.Schema{Type: schema.TypeString}, 155 Optional: true, 156 }, 157 "disable_reporting": &schema.Schema{ 158 Type: schema.TypeBool, 159 Optional: true, 160 }, 161 "environment": &schema.Schema{ 162 Type: schema.TypeString, 163 Optional: true, 164 Default: defaultEnv, 165 }, 166 "fetch_chef_certificates": &schema.Schema{ 167 Type: schema.TypeBool, 168 Optional: true, 169 }, 170 "log_to_file": &schema.Schema{ 171 Type: schema.TypeBool, 172 Optional: true, 173 }, 174 "use_policyfile": &schema.Schema{ 175 Type: schema.TypeBool, 176 Optional: true, 177 }, 178 "policy_group": &schema.Schema{ 179 Type: schema.TypeString, 180 Optional: true, 181 }, 182 "policy_name": &schema.Schema{ 183 Type: schema.TypeString, 184 Optional: true, 185 }, 186 "http_proxy": &schema.Schema{ 187 Type: schema.TypeString, 188 Optional: true, 189 }, 190 "https_proxy": &schema.Schema{ 191 Type: schema.TypeString, 192 Optional: true, 193 }, 194 "no_proxy": &schema.Schema{ 195 Type: schema.TypeList, 196 Elem: &schema.Schema{Type: schema.TypeString}, 197 Optional: true, 198 }, 199 "named_run_list": &schema.Schema{ 200 Type: schema.TypeString, 201 Optional: true, 202 }, 203 "ohai_hints": &schema.Schema{ 204 Type: schema.TypeList, 205 Elem: &schema.Schema{Type: schema.TypeString}, 206 Optional: true, 207 }, 208 "os_type": &schema.Schema{ 209 Type: schema.TypeString, 210 Optional: true, 211 }, 212 "recreate_client": &schema.Schema{ 213 Type: schema.TypeBool, 214 Optional: true, 215 }, 216 "prevent_sudo": &schema.Schema{ 217 Type: schema.TypeBool, 218 Optional: true, 219 }, 220 "run_list": &schema.Schema{ 221 Type: schema.TypeList, 222 Elem: &schema.Schema{Type: schema.TypeString}, 223 Optional: true, 224 }, 225 "secret_key": &schema.Schema{ 226 Type: schema.TypeString, 227 Optional: true, 228 }, 229 "skip_install": &schema.Schema{ 230 Type: schema.TypeBool, 231 Optional: true, 232 }, 233 "skip_register": &schema.Schema{ 234 Type: schema.TypeBool, 235 Optional: true, 236 }, 237 "ssl_verify_mode": &schema.Schema{ 238 Type: schema.TypeString, 239 Optional: true, 240 }, 241 "vault_json": &schema.Schema{ 242 Type: schema.TypeString, 243 Optional: true, 244 }, 245 "version": &schema.Schema{ 246 Type: schema.TypeString, 247 Optional: true, 248 }, 249 }, 250 251 ApplyFunc: applyFn, 252 ValidateFunc: validateFn, 253 } 254 } 255 256 // TODO: Support context cancelling (Provisioner Stop) 257 func applyFn(ctx context.Context) error { 258 o := ctx.Value(schema.ProvOutputKey).(terraform.UIOutput) 259 s := ctx.Value(schema.ProvRawStateKey).(*terraform.InstanceState) 260 d := ctx.Value(schema.ProvConfigDataKey).(*schema.ResourceData) 261 262 // Decode the provisioner config 263 p, err := decodeConfig(d) 264 if err != nil { 265 return err 266 } 267 268 if p.OSType == "" { 269 switch t := s.Ephemeral.ConnInfo["type"]; t { 270 case "ssh", "": // The default connection type is ssh, so if the type is empty assume ssh 271 p.OSType = "linux" 272 case "winrm": 273 p.OSType = "windows" 274 default: 275 return fmt.Errorf("Unsupported connection type: %s", t) 276 } 277 } 278 279 // Set some values based on the targeted OS 280 switch p.OSType { 281 case "linux": 282 p.cleanupUserKeyCmd = fmt.Sprintf("rm -f %s", path.Join(linuxConfDir, p.UserName+".pem")) 283 p.createConfigFiles = p.linuxCreateConfigFiles 284 p.installChefClient = p.linuxInstallChefClient 285 p.fetchChefCertificates = p.fetchChefCertificatesFunc(linuxKnifeCmd, linuxConfDir) 286 p.generateClientKey = p.generateClientKeyFunc(linuxKnifeCmd, linuxConfDir, linuxNoOutput) 287 p.configureVaults = p.configureVaultsFunc(linuxGemCmd, linuxKnifeCmd, linuxConfDir) 288 p.runChefClient = p.runChefClientFunc(linuxChefCmd, linuxConfDir) 289 p.useSudo = !p.PreventSudo && s.Ephemeral.ConnInfo["user"] != "root" 290 case "windows": 291 p.cleanupUserKeyCmd = fmt.Sprintf("cd %s && del /F /Q %s", windowsConfDir, p.UserName+".pem") 292 p.createConfigFiles = p.windowsCreateConfigFiles 293 p.installChefClient = p.windowsInstallChefClient 294 p.fetchChefCertificates = p.fetchChefCertificatesFunc(windowsKnifeCmd, windowsConfDir) 295 p.generateClientKey = p.generateClientKeyFunc(windowsKnifeCmd, windowsConfDir, windowsNoOutput) 296 p.configureVaults = p.configureVaultsFunc(windowsGemCmd, windowsKnifeCmd, windowsConfDir) 297 p.runChefClient = p.runChefClientFunc(windowsChefCmd, windowsConfDir) 298 p.useSudo = false 299 default: 300 return fmt.Errorf("Unsupported os type: %s", p.OSType) 301 } 302 303 // Get a new communicator 304 comm, err := communicator.New(s) 305 if err != nil { 306 return err 307 } 308 309 // Wait and retry until we establish the connection 310 err = retryFunc(comm.Timeout(), func() error { 311 return comm.Connect(o) 312 }) 313 if err != nil { 314 return err 315 } 316 defer comm.Disconnect() 317 318 // Make sure we always delete the user key from the new node! 319 var once sync.Once 320 cleanupUserKey := func() { 321 o.Output("Cleanup user key...") 322 if err := p.runCommand(o, comm, p.cleanupUserKeyCmd); err != nil { 323 o.Output("WARNING: Failed to cleanup user key on new node: " + err.Error()) 324 } 325 } 326 defer once.Do(cleanupUserKey) 327 328 if !p.SkipInstall { 329 if err := p.installChefClient(o, comm); err != nil { 330 return err 331 } 332 } 333 334 o.Output("Creating configuration files...") 335 if err := p.createConfigFiles(o, comm); err != nil { 336 return err 337 } 338 339 if !p.SkipRegister { 340 if p.FetchChefCertificates { 341 o.Output("Fetch Chef certificates...") 342 if err := p.fetchChefCertificates(o, comm); err != nil { 343 return err 344 } 345 } 346 347 o.Output("Generate the private key...") 348 if err := p.generateClientKey(o, comm); err != nil { 349 return err 350 } 351 } 352 353 if p.Vaults != nil { 354 o.Output("Configure Chef vaults...") 355 if err := p.configureVaults(o, comm); err != nil { 356 return err 357 } 358 } 359 360 // Cleanup the user key before we run Chef-Client to prevent issues 361 // with rights caused by changing settings during the run. 362 once.Do(cleanupUserKey) 363 364 o.Output("Starting initial Chef-Client run...") 365 if err := p.runChefClient(o, comm); err != nil { 366 return err 367 } 368 369 return nil 370 } 371 372 func validateFn(c *terraform.ResourceConfig) (ws []string, es []error) { 373 usePolicyFile, ok := c.Get("use_policyfile") 374 if !ok { 375 usePolicyFile = false 376 } 377 378 if !usePolicyFile.(bool) && !c.IsSet("run_list") { 379 es = append(es, errors.New("\"run_list\": required field is not set")) 380 } 381 if usePolicyFile.(bool) && !c.IsSet("policy_name") { 382 es = append(es, errors.New("using policyfile, but \"policy_name\" not set")) 383 } 384 if usePolicyFile.(bool) && !c.IsSet("policy_group") { 385 es = append(es, errors.New("using policyfile, but \"policy_group\" not set")) 386 } 387 388 return ws, es 389 } 390 391 func (p *provisioner) deployConfigFiles(o terraform.UIOutput, comm communicator.Communicator, confDir string) error { 392 // Copy the user key to the new instance 393 pk := strings.NewReader(p.UserKey) 394 if err := comm.Upload(path.Join(confDir, p.UserName+".pem"), pk); err != nil { 395 return fmt.Errorf("Uploading user key failed: %v", err) 396 } 397 398 if p.SecretKey != "" { 399 // Copy the secret key to the new instance 400 s := strings.NewReader(p.SecretKey) 401 if err := comm.Upload(path.Join(confDir, secretKey), s); err != nil { 402 return fmt.Errorf("Uploading %s failed: %v", secretKey, err) 403 } 404 } 405 406 // Make sure the SSLVerifyMode value is written as a symbol 407 if p.SSLVerifyMode != "" && !strings.HasPrefix(p.SSLVerifyMode, ":") { 408 p.SSLVerifyMode = fmt.Sprintf(":%s", p.SSLVerifyMode) 409 } 410 411 // Make strings.Join available for use within the template 412 funcMap := template.FuncMap{ 413 "join": strings.Join, 414 } 415 416 // Create a new template and parse the client config into it 417 t := template.Must(template.New(clienrb).Funcs(funcMap).Parse(clientConf)) 418 419 var buf bytes.Buffer 420 err := t.Execute(&buf, p) 421 if err != nil { 422 return fmt.Errorf("Error executing %s template: %s", clienrb, err) 423 } 424 425 // Copy the client config to the new instance 426 if err = comm.Upload(path.Join(confDir, clienrb), &buf); err != nil { 427 return fmt.Errorf("Uploading %s failed: %v", clienrb, err) 428 } 429 430 // Create a map with first boot settings 431 fb := make(map[string]interface{}) 432 if p.Attributes != nil { 433 fb = p.Attributes 434 } 435 436 // Check if the run_list was also in the attributes and if so log a warning 437 // that it will be overwritten with the value of the run_list argument. 438 if _, found := fb["run_list"]; found { 439 log.Printf("[WARNING] Found a 'run_list' specified in the configured attributes! " + 440 "This value will be overwritten by the value of the `run_list` argument!") 441 } 442 443 // Add the initial runlist to the first boot settings 444 if !p.UsePolicyfile { 445 fb["run_list"] = p.RunList 446 } 447 448 // Marshal the first boot settings to JSON 449 d, err := json.Marshal(fb) 450 if err != nil { 451 return fmt.Errorf("Failed to create %s data: %s", firstBoot, err) 452 } 453 454 // Copy the first-boot.json to the new instance 455 if err := comm.Upload(path.Join(confDir, firstBoot), bytes.NewReader(d)); err != nil { 456 return fmt.Errorf("Uploading %s failed: %v", firstBoot, err) 457 } 458 459 return nil 460 } 461 462 func (p *provisioner) deployOhaiHints(o terraform.UIOutput, comm communicator.Communicator, hintDir string) error { 463 for _, hint := range p.OhaiHints { 464 // Open the hint file 465 f, err := os.Open(hint) 466 if err != nil { 467 return err 468 } 469 defer f.Close() 470 471 // Copy the hint to the new instance 472 if err := comm.Upload(path.Join(hintDir, path.Base(hint)), f); err != nil { 473 return fmt.Errorf("Uploading %s failed: %v", path.Base(hint), err) 474 } 475 } 476 477 return nil 478 } 479 480 func (p *provisioner) fetchChefCertificatesFunc( 481 knifeCmd string, 482 confDir string) func(terraform.UIOutput, communicator.Communicator) error { 483 return func(o terraform.UIOutput, comm communicator.Communicator) error { 484 clientrb := path.Join(confDir, clienrb) 485 cmd := fmt.Sprintf("%s ssl fetch -c %s", knifeCmd, clientrb) 486 487 return p.runCommand(o, comm, cmd) 488 } 489 } 490 491 func (p *provisioner) generateClientKeyFunc(knifeCmd string, confDir string, noOutput string) provisionFn { 492 return func(o terraform.UIOutput, comm communicator.Communicator) error { 493 options := fmt.Sprintf("-c %s -u %s --key %s", 494 path.Join(confDir, clienrb), 495 p.UserName, 496 path.Join(confDir, p.UserName+".pem"), 497 ) 498 499 // See if we already have a node object 500 getNodeCmd := fmt.Sprintf("%s node show %s %s %s", knifeCmd, p.NodeName, options, noOutput) 501 node := p.runCommand(o, comm, getNodeCmd) == nil 502 503 // See if we already have a client object 504 getClientCmd := fmt.Sprintf("%s client show %s %s %s", knifeCmd, p.NodeName, options, noOutput) 505 client := p.runCommand(o, comm, getClientCmd) == nil 506 507 // If we have a client, we can only continue if we are to recreate the client 508 if client && !p.RecreateClient { 509 return fmt.Errorf( 510 "Chef client %q already exists, set recreate_client=true to automatically recreate the client", p.NodeName) 511 } 512 513 // If the node exists, try to delete it 514 if node { 515 deleteNodeCmd := fmt.Sprintf("%s node delete %s -y %s", 516 knifeCmd, 517 p.NodeName, 518 options, 519 ) 520 if err := p.runCommand(o, comm, deleteNodeCmd); err != nil { 521 return err 522 } 523 } 524 525 // If the client exists, try to delete it 526 if client { 527 deleteClientCmd := fmt.Sprintf("%s client delete %s -y %s", 528 knifeCmd, 529 p.NodeName, 530 options, 531 ) 532 if err := p.runCommand(o, comm, deleteClientCmd); err != nil { 533 return err 534 } 535 } 536 537 // Create the new client object 538 createClientCmd := fmt.Sprintf("%s client create %s -d -f %s %s", 539 knifeCmd, 540 p.NodeName, 541 path.Join(confDir, "client.pem"), 542 options, 543 ) 544 545 return p.runCommand(o, comm, createClientCmd) 546 } 547 } 548 549 func (p *provisioner) configureVaultsFunc(gemCmd string, knifeCmd string, confDir string) provisionFn { 550 return func(o terraform.UIOutput, comm communicator.Communicator) error { 551 if err := p.runCommand(o, comm, fmt.Sprintf("%s install chef-vault", gemCmd)); err != nil { 552 return err 553 } 554 555 options := fmt.Sprintf("-c %s -u %s --key %s", 556 path.Join(confDir, clienrb), 557 p.UserName, 558 path.Join(confDir, p.UserName+".pem"), 559 ) 560 561 // if client gets recreated, remove (old) client (with old keys) from vaults/items 562 // otherwise, the (new) client (with new keys) will not be able to decrypt the vault 563 if p.RecreateClient { 564 for vault, items := range p.Vaults { 565 for _, item := range items { 566 deleteCmd := fmt.Sprintf("%s vault remove %s %s -C \"%s\" -M client %s", 567 knifeCmd, 568 vault, 569 item, 570 p.NodeName, 571 options, 572 ) 573 if err := p.runCommand(o, comm, deleteCmd); err != nil { 574 return err 575 } 576 } 577 } 578 } 579 580 for vault, items := range p.Vaults { 581 for _, item := range items { 582 updateCmd := fmt.Sprintf("%s vault update %s %s -C %s -M client %s", 583 knifeCmd, 584 vault, 585 item, 586 p.NodeName, 587 options, 588 ) 589 if err := p.runCommand(o, comm, updateCmd); err != nil { 590 return err 591 } 592 } 593 } 594 595 return nil 596 } 597 } 598 599 func (p *provisioner) runChefClientFunc(chefCmd string, confDir string) provisionFn { 600 return func(o terraform.UIOutput, comm communicator.Communicator) error { 601 fb := path.Join(confDir, firstBoot) 602 var cmd string 603 604 // Policyfiles do not support chef environments, so don't pass the `-E` flag. 605 switch { 606 case p.UsePolicyfile && p.NamedRunList == "": 607 cmd = fmt.Sprintf("%s -j %q", chefCmd, fb) 608 case p.UsePolicyfile && p.NamedRunList != "": 609 cmd = fmt.Sprintf("%s -j %q -n %q", chefCmd, fb, p.NamedRunList) 610 default: 611 cmd = fmt.Sprintf("%s -j %q -E %q", chefCmd, fb, p.Environment) 612 } 613 614 if p.LogToFile { 615 if err := os.MkdirAll(logfileDir, 0755); err != nil { 616 return fmt.Errorf("Error creating logfile directory %s: %v", logfileDir, err) 617 } 618 619 logFile := path.Join(logfileDir, p.NodeName) 620 f, err := os.Create(path.Join(logFile)) 621 if err != nil { 622 return fmt.Errorf("Error creating logfile %s: %v", logFile, err) 623 } 624 f.Close() 625 626 o.Output("Writing Chef Client output to " + logFile) 627 o = p 628 } 629 630 return p.runCommand(o, comm, cmd) 631 } 632 } 633 634 // Output implementation of terraform.UIOutput interface 635 func (p *provisioner) Output(output string) { 636 logFile := path.Join(logfileDir, p.NodeName) 637 f, err := os.OpenFile(logFile, os.O_APPEND|os.O_WRONLY, 0666) 638 if err != nil { 639 log.Printf("Error creating logfile %s: %v", logFile, err) 640 return 641 } 642 defer f.Close() 643 644 // These steps are needed to remove any ANSI escape codes used to colorize 645 // the output and to make sure we have proper line endings before writing 646 // the string to the logfile. 647 re := regexp.MustCompile(`\x1b\[[0-9;]+m`) 648 output = re.ReplaceAllString(output, "") 649 output = strings.Replace(output, "\r", "\n", -1) 650 651 if _, err := f.WriteString(output); err != nil { 652 log.Printf("Error writing output to logfile %s: %v", logFile, err) 653 } 654 655 if err := f.Sync(); err != nil { 656 log.Printf("Error saving logfile %s to disk: %v", logFile, err) 657 } 658 } 659 660 // runCommand is used to run already prepared commands 661 func (p *provisioner) runCommand(o terraform.UIOutput, comm communicator.Communicator, command string) error { 662 // Unless prevented, prefix the command with sudo 663 if p.useSudo { 664 command = "sudo " + command 665 } 666 667 outR, outW := io.Pipe() 668 errR, errW := io.Pipe() 669 outDoneCh := make(chan struct{}) 670 errDoneCh := make(chan struct{}) 671 go p.copyOutput(o, outR, outDoneCh) 672 go p.copyOutput(o, errR, errDoneCh) 673 674 cmd := &remote.Cmd{ 675 Command: command, 676 Stdout: outW, 677 Stderr: errW, 678 } 679 680 err := comm.Start(cmd) 681 if err != nil { 682 return fmt.Errorf("Error executing command %q: %v", cmd.Command, err) 683 } 684 685 cmd.Wait() 686 if cmd.ExitStatus != 0 { 687 err = fmt.Errorf( 688 "Command %q exited with non-zero exit status: %d", cmd.Command, cmd.ExitStatus) 689 } 690 691 // Wait for output to clean up 692 outW.Close() 693 errW.Close() 694 <-outDoneCh 695 <-errDoneCh 696 697 return err 698 } 699 700 func (p *provisioner) copyOutput(o terraform.UIOutput, r io.Reader, doneCh chan<- struct{}) { 701 defer close(doneCh) 702 lr := linereader.New(r) 703 for line := range lr.Ch { 704 o.Output(line) 705 } 706 } 707 708 // retryFunc is used to retry a function for a given duration 709 func retryFunc(timeout time.Duration, f func() error) error { 710 finish := time.After(timeout) 711 for { 712 err := f() 713 if err == nil { 714 return nil 715 } 716 log.Printf("Retryable error: %v", err) 717 718 select { 719 case <-finish: 720 return err 721 case <-time.After(3 * time.Second): 722 } 723 } 724 } 725 726 func decodeConfig(d *schema.ResourceData) (*provisioner, error) { 727 p := &provisioner{ 728 ClientOptions: getStringList(d.Get("client_options")), 729 DisableReporting: d.Get("disable_reporting").(bool), 730 Environment: d.Get("environment").(string), 731 FetchChefCertificates: d.Get("fetch_chef_certificates").(bool), 732 LogToFile: d.Get("log_to_file").(bool), 733 UsePolicyfile: d.Get("use_policyfile").(bool), 734 PolicyGroup: d.Get("policy_group").(string), 735 PolicyName: d.Get("policy_name").(string), 736 HTTPProxy: d.Get("http_proxy").(string), 737 HTTPSProxy: d.Get("https_proxy").(string), 738 NOProxy: getStringList(d.Get("no_proxy")), 739 NamedRunList: d.Get("named_run_list").(string), 740 NodeName: d.Get("node_name").(string), 741 OhaiHints: getStringList(d.Get("ohai_hints")), 742 OSType: d.Get("os_type").(string), 743 RecreateClient: d.Get("recreate_client").(bool), 744 PreventSudo: d.Get("prevent_sudo").(bool), 745 RunList: getStringList(d.Get("run_list")), 746 SecretKey: d.Get("secret_key").(string), 747 ServerURL: d.Get("server_url").(string), 748 SkipInstall: d.Get("skip_install").(bool), 749 SkipRegister: d.Get("skip_register").(bool), 750 SSLVerifyMode: d.Get("ssl_verify_mode").(string), 751 UserName: d.Get("user_name").(string), 752 UserKey: d.Get("user_key").(string), 753 Version: d.Get("version").(string), 754 } 755 756 // Make sure the supplied URL has a trailing slash 757 p.ServerURL = strings.TrimSuffix(p.ServerURL, "/") + "/" 758 759 for i, hint := range p.OhaiHints { 760 hintPath, err := homedir.Expand(hint) 761 if err != nil { 762 return nil, fmt.Errorf("Error expanding the path %s: %v", hint, err) 763 } 764 p.OhaiHints[i] = hintPath 765 } 766 767 if attrs, ok := d.GetOk("attributes_json"); ok { 768 var m map[string]interface{} 769 if err := json.Unmarshal([]byte(attrs.(string)), &m); err != nil { 770 return nil, fmt.Errorf("Error parsing attributes_json: %v", err) 771 } 772 p.Attributes = m 773 } 774 775 if vaults, ok := d.GetOk("vault_json"); ok { 776 var m map[string]interface{} 777 if err := json.Unmarshal([]byte(vaults.(string)), &m); err != nil { 778 return nil, fmt.Errorf("Error parsing vault_json: %v", err) 779 } 780 781 v := make(map[string][]string) 782 for vault, items := range m { 783 switch items := items.(type) { 784 case []interface{}: 785 for _, item := range items { 786 if item, ok := item.(string); ok { 787 v[vault] = append(v[vault], item) 788 } 789 } 790 case interface{}: 791 if item, ok := items.(string); ok { 792 v[vault] = append(v[vault], item) 793 } 794 } 795 } 796 797 p.Vaults = v 798 } 799 800 return p, nil 801 } 802 803 func getStringList(v interface{}) []string { 804 var result []string 805 806 switch v := v.(type) { 807 case nil: 808 return result 809 case []interface{}: 810 for _, vv := range v { 811 if vv, ok := vv.(string); ok { 812 result = append(result, vv) 813 } 814 } 815 return result 816 default: 817 panic(fmt.Sprintf("Unsupported type: %T", v)) 818 } 819 }