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