github.com/lymingtonprecision/terraform@v0.9.9-0.20170613092852-62acef9611a9/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 d := ctx.Value(schema.ProvConfigDataKey).(*schema.ResourceData) 260 261 // Decode the raw config for this provisioner 262 p, err := decodeConfig(d) 263 if err != nil { 264 return err 265 } 266 267 if p.OSType == "" { 268 switch t := d.State().Ephemeral.ConnInfo["type"]; t { 269 case "ssh", "": // The default connection type is ssh, so if the type is empty assume ssh 270 p.OSType = "linux" 271 case "winrm": 272 p.OSType = "windows" 273 default: 274 return fmt.Errorf("Unsupported connection type: %s", t) 275 } 276 } 277 278 // Set some values based on the targeted OS 279 switch p.OSType { 280 case "linux": 281 p.cleanupUserKeyCmd = fmt.Sprintf("rm -f %s", path.Join(linuxConfDir, p.UserName+".pem")) 282 p.createConfigFiles = p.linuxCreateConfigFiles 283 p.installChefClient = p.linuxInstallChefClient 284 p.fetchChefCertificates = p.fetchChefCertificatesFunc(linuxKnifeCmd, linuxConfDir) 285 p.generateClientKey = p.generateClientKeyFunc(linuxKnifeCmd, linuxConfDir, linuxNoOutput) 286 p.configureVaults = p.configureVaultsFunc(linuxGemCmd, linuxKnifeCmd, linuxConfDir) 287 p.runChefClient = p.runChefClientFunc(linuxChefCmd, linuxConfDir) 288 p.useSudo = !p.PreventSudo && d.State().Ephemeral.ConnInfo["user"] != "root" 289 case "windows": 290 p.cleanupUserKeyCmd = fmt.Sprintf("cd %s && del /F /Q %s", windowsConfDir, p.UserName+".pem") 291 p.createConfigFiles = p.windowsCreateConfigFiles 292 p.installChefClient = p.windowsInstallChefClient 293 p.fetchChefCertificates = p.fetchChefCertificatesFunc(windowsKnifeCmd, windowsConfDir) 294 p.generateClientKey = p.generateClientKeyFunc(windowsKnifeCmd, windowsConfDir, windowsNoOutput) 295 p.configureVaults = p.configureVaultsFunc(windowsGemCmd, windowsKnifeCmd, windowsConfDir) 296 p.runChefClient = p.runChefClientFunc(windowsChefCmd, windowsConfDir) 297 p.useSudo = false 298 default: 299 return fmt.Errorf("Unsupported os type: %s", p.OSType) 300 } 301 302 // Get a new communicator 303 comm, err := communicator.New(d.State()) 304 if err != nil { 305 return err 306 } 307 308 // Wait and retry until we establish the connection 309 err = retryFunc(comm.Timeout(), func() error { 310 return comm.Connect(o) 311 }) 312 if err != nil { 313 return err 314 } 315 defer comm.Disconnect() 316 317 // Make sure we always delete the user key from the new node! 318 var once sync.Once 319 cleanupUserKey := func() { 320 o.Output("Cleanup user key...") 321 if err := p.runCommand(o, comm, p.cleanupUserKeyCmd); err != nil { 322 o.Output("WARNING: Failed to cleanup user key on new node: " + err.Error()) 323 } 324 } 325 defer once.Do(cleanupUserKey) 326 327 if !p.SkipInstall { 328 if err := p.installChefClient(o, comm); err != nil { 329 return err 330 } 331 } 332 333 o.Output("Creating configuration files...") 334 if err := p.createConfigFiles(o, comm); err != nil { 335 return err 336 } 337 338 if !p.SkipRegister { 339 if p.FetchChefCertificates { 340 o.Output("Fetch Chef certificates...") 341 if err := p.fetchChefCertificates(o, comm); err != nil { 342 return err 343 } 344 } 345 346 o.Output("Generate the private key...") 347 if err := p.generateClientKey(o, comm); err != nil { 348 return err 349 } 350 } 351 352 if p.Vaults != nil { 353 o.Output("Configure Chef vaults...") 354 if err := p.configureVaults(o, comm); err != nil { 355 return err 356 } 357 } 358 359 // Cleanup the user key before we run Chef-Client to prevent issues 360 // with rights caused by changing settings during the run. 361 once.Do(cleanupUserKey) 362 363 o.Output("Starting initial Chef-Client run...") 364 if err := p.runChefClient(o, comm); err != nil { 365 return err 366 } 367 368 return nil 369 } 370 371 func validateFn(d *schema.ResourceData) (ws []string, es []error) { 372 p, err := decodeConfig(d) 373 if err != nil { 374 es = append(es, err) 375 return ws, es 376 } 377 378 if !p.UsePolicyfile && p.RunList == nil { 379 es = append(es, errors.New("Key not found: run_list")) 380 } 381 if p.UsePolicyfile && p.PolicyName == "" { 382 es = append(es, errors.New("Policyfile enabled but key not found: policy_name")) 383 } 384 if p.UsePolicyfile && p.PolicyGroup == "" { 385 es = append(es, errors.New("Policyfile enabled but key not found: policy_group")) 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 for vault, items := range p.Vaults { 562 for _, item := range items { 563 updateCmd := fmt.Sprintf("%s vault update %s %s -C %s -M client %s", 564 knifeCmd, 565 vault, 566 item, 567 p.NodeName, 568 options, 569 ) 570 if err := p.runCommand(o, comm, updateCmd); err != nil { 571 return err 572 } 573 } 574 } 575 576 return nil 577 } 578 } 579 580 func (p *provisioner) runChefClientFunc(chefCmd string, confDir string) provisionFn { 581 return func(o terraform.UIOutput, comm communicator.Communicator) error { 582 fb := path.Join(confDir, firstBoot) 583 var cmd string 584 585 // Policyfiles do not support chef environments, so don't pass the `-E` flag. 586 switch { 587 case p.UsePolicyfile && p.NamedRunList == "": 588 cmd = fmt.Sprintf("%s -j %q", chefCmd, fb) 589 case p.UsePolicyfile && p.NamedRunList != "": 590 cmd = fmt.Sprintf("%s -j %q -n %q", chefCmd, fb, p.NamedRunList) 591 default: 592 cmd = fmt.Sprintf("%s -j %q -E %q", chefCmd, fb, p.Environment) 593 } 594 595 if p.LogToFile { 596 if err := os.MkdirAll(logfileDir, 0755); err != nil { 597 return fmt.Errorf("Error creating logfile directory %s: %v", logfileDir, err) 598 } 599 600 logFile := path.Join(logfileDir, p.NodeName) 601 f, err := os.Create(path.Join(logFile)) 602 if err != nil { 603 return fmt.Errorf("Error creating logfile %s: %v", logFile, err) 604 } 605 f.Close() 606 607 o.Output("Writing Chef Client output to " + logFile) 608 o = p 609 } 610 611 return p.runCommand(o, comm, cmd) 612 } 613 } 614 615 // Output implementation of terraform.UIOutput interface 616 func (p *provisioner) Output(output string) { 617 logFile := path.Join(logfileDir, p.NodeName) 618 f, err := os.OpenFile(logFile, os.O_APPEND|os.O_WRONLY, 0666) 619 if err != nil { 620 log.Printf("Error creating logfile %s: %v", logFile, err) 621 return 622 } 623 defer f.Close() 624 625 // These steps are needed to remove any ANSI escape codes used to colorize 626 // the output and to make sure we have proper line endings before writing 627 // the string to the logfile. 628 re := regexp.MustCompile(`\x1b\[[0-9;]+m`) 629 output = re.ReplaceAllString(output, "") 630 output = strings.Replace(output, "\r", "\n", -1) 631 632 if _, err := f.WriteString(output); err != nil { 633 log.Printf("Error writing output to logfile %s: %v", logFile, err) 634 } 635 636 if err := f.Sync(); err != nil { 637 log.Printf("Error saving logfile %s to disk: %v", logFile, err) 638 } 639 } 640 641 // runCommand is used to run already prepared commands 642 func (p *provisioner) runCommand(o terraform.UIOutput, comm communicator.Communicator, command string) error { 643 // Unless prevented, prefix the command with sudo 644 if p.useSudo { 645 command = "sudo " + command 646 } 647 648 outR, outW := io.Pipe() 649 errR, errW := io.Pipe() 650 outDoneCh := make(chan struct{}) 651 errDoneCh := make(chan struct{}) 652 go p.copyOutput(o, outR, outDoneCh) 653 go p.copyOutput(o, errR, errDoneCh) 654 655 cmd := &remote.Cmd{ 656 Command: command, 657 Stdout: outW, 658 Stderr: errW, 659 } 660 661 err := comm.Start(cmd) 662 if err != nil { 663 return fmt.Errorf("Error executing command %q: %v", cmd.Command, err) 664 } 665 666 cmd.Wait() 667 if cmd.ExitStatus != 0 { 668 err = fmt.Errorf( 669 "Command %q exited with non-zero exit status: %d", cmd.Command, cmd.ExitStatus) 670 } 671 672 // Wait for output to clean up 673 outW.Close() 674 errW.Close() 675 <-outDoneCh 676 <-errDoneCh 677 678 return err 679 } 680 681 func (p *provisioner) copyOutput(o terraform.UIOutput, r io.Reader, doneCh chan<- struct{}) { 682 defer close(doneCh) 683 lr := linereader.New(r) 684 for line := range lr.Ch { 685 o.Output(line) 686 } 687 } 688 689 // retryFunc is used to retry a function for a given duration 690 func retryFunc(timeout time.Duration, f func() error) error { 691 finish := time.After(timeout) 692 for { 693 err := f() 694 if err == nil { 695 return nil 696 } 697 log.Printf("Retryable error: %v", err) 698 699 select { 700 case <-finish: 701 return err 702 case <-time.After(3 * time.Second): 703 } 704 } 705 } 706 707 func decodeConfig(d *schema.ResourceData) (*provisioner, error) { 708 p := &provisioner{ 709 ClientOptions: getStringList(d.Get("client_options")), 710 DisableReporting: d.Get("disable_reporting").(bool), 711 Environment: d.Get("environment").(string), 712 FetchChefCertificates: d.Get("fetch_chef_certificates").(bool), 713 LogToFile: d.Get("log_to_file").(bool), 714 UsePolicyfile: d.Get("use_policyfile").(bool), 715 PolicyGroup: d.Get("policy_group").(string), 716 PolicyName: d.Get("policy_name").(string), 717 HTTPProxy: d.Get("http_proxy").(string), 718 HTTPSProxy: d.Get("https_proxy").(string), 719 NOProxy: getStringList(d.Get("no_proxy")), 720 NamedRunList: d.Get("named_run_list").(string), 721 NodeName: d.Get("node_name").(string), 722 OhaiHints: getStringList(d.Get("ohai_hints")), 723 OSType: d.Get("os_type").(string), 724 RecreateClient: d.Get("recreate_client").(bool), 725 PreventSudo: d.Get("prevent_sudo").(bool), 726 RunList: getStringList(d.Get("run_list")), 727 SecretKey: d.Get("secret_key").(string), 728 ServerURL: d.Get("server_url").(string), 729 SkipInstall: d.Get("skip_install").(bool), 730 SkipRegister: d.Get("skip_register").(bool), 731 SSLVerifyMode: d.Get("ssl_verify_mode").(string), 732 UserName: d.Get("user_name").(string), 733 UserKey: d.Get("user_key").(string), 734 Version: d.Get("version").(string), 735 } 736 737 // Make sure the supplied URL has a trailing slash 738 p.ServerURL = strings.TrimSuffix(p.ServerURL, "/") + "/" 739 740 for i, hint := range p.OhaiHints { 741 hintPath, err := homedir.Expand(hint) 742 if err != nil { 743 return nil, fmt.Errorf("Error expanding the path %s: %v", hint, err) 744 } 745 p.OhaiHints[i] = hintPath 746 } 747 748 if attrs, ok := d.GetOk("attributes_json"); ok { 749 var m map[string]interface{} 750 if err := json.Unmarshal([]byte(attrs.(string)), &m); err != nil { 751 return nil, fmt.Errorf("Error parsing attributes_json: %v", err) 752 } 753 p.Attributes = m 754 } 755 756 if vaults, ok := d.GetOk("vault_json"); ok { 757 var m map[string]interface{} 758 if err := json.Unmarshal([]byte(vaults.(string)), &m); err != nil { 759 return nil, fmt.Errorf("Error parsing vault_json: %v", err) 760 } 761 762 v := make(map[string][]string) 763 for vault, items := range m { 764 switch items := items.(type) { 765 case []interface{}: 766 for _, item := range items { 767 if item, ok := item.(string); ok { 768 v[vault] = append(v[vault], item) 769 } 770 } 771 case interface{}: 772 if item, ok := items.(string); ok { 773 v[vault] = append(v[vault], item) 774 } 775 } 776 } 777 778 p.Vaults = v 779 } 780 781 return p, nil 782 } 783 784 func getStringList(v interface{}) []string { 785 if v == nil { 786 return nil 787 } 788 switch l := v.(type) { 789 case []string: 790 return l 791 case []interface{}: 792 arr := make([]string, len(l)) 793 for i, x := range l { 794 arr[i] = x.(string) 795 } 796 return arr 797 default: 798 panic(fmt.Sprintf("Unsupported type: %T", v)) 799 } 800 }