github.com/kjmkznr/terraform@v0.5.2-0.20180216194316-1d0f5fdac99e/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 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 ctx, cancel := context.WithTimeout(ctx, comm.Timeout()) 310 defer cancel() 311 312 // Wait and retry until we establish the connection 313 err = communicator.Retry(ctx, func() error { 314 return comm.Connect(o) 315 }) 316 if err != nil { 317 return err 318 } 319 defer comm.Disconnect() 320 321 // Make sure we always delete the user key from the new node! 322 var once sync.Once 323 cleanupUserKey := func() { 324 o.Output("Cleanup user key...") 325 if err := p.runCommand(o, comm, p.cleanupUserKeyCmd); err != nil { 326 o.Output("WARNING: Failed to cleanup user key on new node: " + err.Error()) 327 } 328 } 329 defer once.Do(cleanupUserKey) 330 331 if !p.SkipInstall { 332 if err := p.installChefClient(o, comm); err != nil { 333 return err 334 } 335 } 336 337 o.Output("Creating configuration files...") 338 if err := p.createConfigFiles(o, comm); err != nil { 339 return err 340 } 341 342 if !p.SkipRegister { 343 if p.FetchChefCertificates { 344 o.Output("Fetch Chef certificates...") 345 if err := p.fetchChefCertificates(o, comm); err != nil { 346 return err 347 } 348 } 349 350 o.Output("Generate the private key...") 351 if err := p.generateClientKey(o, comm); err != nil { 352 return err 353 } 354 } 355 356 if p.Vaults != nil { 357 o.Output("Configure Chef vaults...") 358 if err := p.configureVaults(o, comm); err != nil { 359 return err 360 } 361 } 362 363 // Cleanup the user key before we run Chef-Client to prevent issues 364 // with rights caused by changing settings during the run. 365 once.Do(cleanupUserKey) 366 367 o.Output("Starting initial Chef-Client run...") 368 if err := p.runChefClient(o, comm); err != nil { 369 return err 370 } 371 372 return nil 373 } 374 375 func validateFn(c *terraform.ResourceConfig) (ws []string, es []error) { 376 usePolicyFile := false 377 if usePolicyFileRaw, ok := c.Get("use_policyfile"); ok { 378 switch usePolicyFileRaw := usePolicyFileRaw.(type) { 379 case bool: 380 usePolicyFile = usePolicyFileRaw 381 case string: 382 usePolicyFileBool, err := strconv.ParseBool(usePolicyFileRaw) 383 if err != nil { 384 return ws, append(es, errors.New("\"use_policyfile\" must be a boolean")) 385 } 386 usePolicyFile = usePolicyFileBool 387 default: 388 return ws, append(es, errors.New("\"use_policyfile\" must be a boolean")) 389 } 390 } 391 392 if !usePolicyFile && !c.IsSet("run_list") { 393 es = append(es, errors.New("\"run_list\": required field is not set")) 394 } 395 if usePolicyFile && !c.IsSet("policy_name") { 396 es = append(es, errors.New("using policyfile, but \"policy_name\" not set")) 397 } 398 if usePolicyFile && !c.IsSet("policy_group") { 399 es = append(es, errors.New("using policyfile, but \"policy_group\" not set")) 400 } 401 402 return ws, es 403 } 404 405 func (p *provisioner) deployConfigFiles(o terraform.UIOutput, comm communicator.Communicator, confDir string) error { 406 // Copy the user key to the new instance 407 pk := strings.NewReader(p.UserKey) 408 if err := comm.Upload(path.Join(confDir, p.UserName+".pem"), pk); err != nil { 409 return fmt.Errorf("Uploading user key failed: %v", err) 410 } 411 412 if p.SecretKey != "" { 413 // Copy the secret key to the new instance 414 s := strings.NewReader(p.SecretKey) 415 if err := comm.Upload(path.Join(confDir, secretKey), s); err != nil { 416 return fmt.Errorf("Uploading %s failed: %v", secretKey, err) 417 } 418 } 419 420 // Make sure the SSLVerifyMode value is written as a symbol 421 if p.SSLVerifyMode != "" && !strings.HasPrefix(p.SSLVerifyMode, ":") { 422 p.SSLVerifyMode = fmt.Sprintf(":%s", p.SSLVerifyMode) 423 } 424 425 // Make strings.Join available for use within the template 426 funcMap := template.FuncMap{ 427 "join": strings.Join, 428 } 429 430 // Create a new template and parse the client config into it 431 t := template.Must(template.New(clienrb).Funcs(funcMap).Parse(clientConf)) 432 433 var buf bytes.Buffer 434 err := t.Execute(&buf, p) 435 if err != nil { 436 return fmt.Errorf("Error executing %s template: %s", clienrb, err) 437 } 438 439 // Copy the client config to the new instance 440 if err = comm.Upload(path.Join(confDir, clienrb), &buf); err != nil { 441 return fmt.Errorf("Uploading %s failed: %v", clienrb, err) 442 } 443 444 // Create a map with first boot settings 445 fb := make(map[string]interface{}) 446 if p.Attributes != nil { 447 fb = p.Attributes 448 } 449 450 // Check if the run_list was also in the attributes and if so log a warning 451 // that it will be overwritten with the value of the run_list argument. 452 if _, found := fb["run_list"]; found { 453 log.Printf("[WARN] Found a 'run_list' specified in the configured attributes! " + 454 "This value will be overwritten by the value of the `run_list` argument!") 455 } 456 457 // Add the initial runlist to the first boot settings 458 if !p.UsePolicyfile { 459 fb["run_list"] = p.RunList 460 } 461 462 // Marshal the first boot settings to JSON 463 d, err := json.Marshal(fb) 464 if err != nil { 465 return fmt.Errorf("Failed to create %s data: %s", firstBoot, err) 466 } 467 468 // Copy the first-boot.json to the new instance 469 if err := comm.Upload(path.Join(confDir, firstBoot), bytes.NewReader(d)); err != nil { 470 return fmt.Errorf("Uploading %s failed: %v", firstBoot, err) 471 } 472 473 return nil 474 } 475 476 func (p *provisioner) deployOhaiHints(o terraform.UIOutput, comm communicator.Communicator, hintDir string) error { 477 for _, hint := range p.OhaiHints { 478 // Open the hint file 479 f, err := os.Open(hint) 480 if err != nil { 481 return err 482 } 483 defer f.Close() 484 485 // Copy the hint to the new instance 486 if err := comm.Upload(path.Join(hintDir, path.Base(hint)), f); err != nil { 487 return fmt.Errorf("Uploading %s failed: %v", path.Base(hint), err) 488 } 489 } 490 491 return nil 492 } 493 494 func (p *provisioner) fetchChefCertificatesFunc( 495 knifeCmd string, 496 confDir string) func(terraform.UIOutput, communicator.Communicator) error { 497 return func(o terraform.UIOutput, comm communicator.Communicator) error { 498 clientrb := path.Join(confDir, clienrb) 499 cmd := fmt.Sprintf("%s ssl fetch -c %s", knifeCmd, clientrb) 500 501 return p.runCommand(o, comm, cmd) 502 } 503 } 504 505 func (p *provisioner) generateClientKeyFunc(knifeCmd string, confDir string, noOutput string) provisionFn { 506 return func(o terraform.UIOutput, comm communicator.Communicator) error { 507 options := fmt.Sprintf("-c %s -u %s --key %s", 508 path.Join(confDir, clienrb), 509 p.UserName, 510 path.Join(confDir, p.UserName+".pem"), 511 ) 512 513 // See if we already have a node object 514 getNodeCmd := fmt.Sprintf("%s node show %s %s %s", knifeCmd, p.NodeName, options, noOutput) 515 node := p.runCommand(o, comm, getNodeCmd) == nil 516 517 // See if we already have a client object 518 getClientCmd := fmt.Sprintf("%s client show %s %s %s", knifeCmd, p.NodeName, options, noOutput) 519 client := p.runCommand(o, comm, getClientCmd) == nil 520 521 // If we have a client, we can only continue if we are to recreate the client 522 if client && !p.RecreateClient { 523 return fmt.Errorf( 524 "Chef client %q already exists, set recreate_client=true to automatically recreate the client", p.NodeName) 525 } 526 527 // If the node exists, try to delete it 528 if node { 529 deleteNodeCmd := fmt.Sprintf("%s node delete %s -y %s", 530 knifeCmd, 531 p.NodeName, 532 options, 533 ) 534 if err := p.runCommand(o, comm, deleteNodeCmd); err != nil { 535 return err 536 } 537 } 538 539 // If the client exists, try to delete it 540 if client { 541 deleteClientCmd := fmt.Sprintf("%s client delete %s -y %s", 542 knifeCmd, 543 p.NodeName, 544 options, 545 ) 546 if err := p.runCommand(o, comm, deleteClientCmd); err != nil { 547 return err 548 } 549 } 550 551 // Create the new client object 552 createClientCmd := fmt.Sprintf("%s client create %s -d -f %s %s", 553 knifeCmd, 554 p.NodeName, 555 path.Join(confDir, "client.pem"), 556 options, 557 ) 558 559 return p.runCommand(o, comm, createClientCmd) 560 } 561 } 562 563 func (p *provisioner) configureVaultsFunc(gemCmd string, knifeCmd string, confDir string) provisionFn { 564 return func(o terraform.UIOutput, comm communicator.Communicator) error { 565 if err := p.runCommand(o, comm, fmt.Sprintf("%s install chef-vault", gemCmd)); err != nil { 566 return err 567 } 568 569 options := fmt.Sprintf("-c %s -u %s --key %s", 570 path.Join(confDir, clienrb), 571 p.UserName, 572 path.Join(confDir, p.UserName+".pem"), 573 ) 574 575 // if client gets recreated, remove (old) client (with old keys) from vaults/items 576 // otherwise, the (new) client (with new keys) will not be able to decrypt the vault 577 if p.RecreateClient { 578 for vault, items := range p.Vaults { 579 for _, item := range items { 580 deleteCmd := fmt.Sprintf("%s vault remove %s %s -C \"%s\" -M client %s", 581 knifeCmd, 582 vault, 583 item, 584 p.NodeName, 585 options, 586 ) 587 if err := p.runCommand(o, comm, deleteCmd); err != nil { 588 return err 589 } 590 } 591 } 592 } 593 594 for vault, items := range p.Vaults { 595 for _, item := range items { 596 updateCmd := fmt.Sprintf("%s vault update %s %s -C %s -M client %s", 597 knifeCmd, 598 vault, 599 item, 600 p.NodeName, 601 options, 602 ) 603 if err := p.runCommand(o, comm, updateCmd); err != nil { 604 return err 605 } 606 } 607 } 608 609 return nil 610 } 611 } 612 613 func (p *provisioner) runChefClientFunc(chefCmd string, confDir string) provisionFn { 614 return func(o terraform.UIOutput, comm communicator.Communicator) error { 615 fb := path.Join(confDir, firstBoot) 616 var cmd string 617 618 // Policyfiles do not support chef environments, so don't pass the `-E` flag. 619 switch { 620 case p.UsePolicyfile && p.NamedRunList == "": 621 cmd = fmt.Sprintf("%s -j %q", chefCmd, fb) 622 case p.UsePolicyfile && p.NamedRunList != "": 623 cmd = fmt.Sprintf("%s -j %q -n %q", chefCmd, fb, p.NamedRunList) 624 default: 625 cmd = fmt.Sprintf("%s -j %q -E %q", chefCmd, fb, p.Environment) 626 } 627 628 if p.LogToFile { 629 if err := os.MkdirAll(logfileDir, 0755); err != nil { 630 return fmt.Errorf("Error creating logfile directory %s: %v", logfileDir, err) 631 } 632 633 logFile := path.Join(logfileDir, p.NodeName) 634 f, err := os.Create(path.Join(logFile)) 635 if err != nil { 636 return fmt.Errorf("Error creating logfile %s: %v", logFile, err) 637 } 638 f.Close() 639 640 o.Output("Writing Chef Client output to " + logFile) 641 o = p 642 } 643 644 return p.runCommand(o, comm, cmd) 645 } 646 } 647 648 // Output implementation of terraform.UIOutput interface 649 func (p *provisioner) Output(output string) { 650 logFile := path.Join(logfileDir, p.NodeName) 651 f, err := os.OpenFile(logFile, os.O_APPEND|os.O_WRONLY, 0666) 652 if err != nil { 653 log.Printf("Error creating logfile %s: %v", logFile, err) 654 return 655 } 656 defer f.Close() 657 658 // These steps are needed to remove any ANSI escape codes used to colorize 659 // the output and to make sure we have proper line endings before writing 660 // the string to the logfile. 661 re := regexp.MustCompile(`\x1b\[[0-9;]+m`) 662 output = re.ReplaceAllString(output, "") 663 output = strings.Replace(output, "\r", "\n", -1) 664 665 if _, err := f.WriteString(output); err != nil { 666 log.Printf("Error writing output to logfile %s: %v", logFile, err) 667 } 668 669 if err := f.Sync(); err != nil { 670 log.Printf("Error saving logfile %s to disk: %v", logFile, err) 671 } 672 } 673 674 // runCommand is used to run already prepared commands 675 func (p *provisioner) runCommand(o terraform.UIOutput, comm communicator.Communicator, command string) error { 676 // Unless prevented, prefix the command with sudo 677 if p.useSudo { 678 command = "sudo " + command 679 } 680 681 outR, outW := io.Pipe() 682 errR, errW := io.Pipe() 683 outDoneCh := make(chan struct{}) 684 errDoneCh := make(chan struct{}) 685 go p.copyOutput(o, outR, outDoneCh) 686 go p.copyOutput(o, errR, errDoneCh) 687 688 cmd := &remote.Cmd{ 689 Command: command, 690 Stdout: outW, 691 Stderr: errW, 692 } 693 694 err := comm.Start(cmd) 695 if err != nil { 696 return fmt.Errorf("Error executing command %q: %v", cmd.Command, err) 697 } 698 699 cmd.Wait() 700 if cmd.ExitStatus != 0 { 701 err = fmt.Errorf( 702 "Command %q exited with non-zero exit status: %d", cmd.Command, cmd.ExitStatus) 703 } 704 705 // Wait for output to clean up 706 outW.Close() 707 errW.Close() 708 <-outDoneCh 709 <-errDoneCh 710 711 return err 712 } 713 714 func (p *provisioner) copyOutput(o terraform.UIOutput, r io.Reader, doneCh chan<- struct{}) { 715 defer close(doneCh) 716 lr := linereader.New(r) 717 for line := range lr.Ch { 718 o.Output(line) 719 } 720 } 721 722 func decodeConfig(d *schema.ResourceData) (*provisioner, error) { 723 p := &provisioner{ 724 ClientOptions: getStringList(d.Get("client_options")), 725 DisableReporting: d.Get("disable_reporting").(bool), 726 Environment: d.Get("environment").(string), 727 FetchChefCertificates: d.Get("fetch_chef_certificates").(bool), 728 LogToFile: d.Get("log_to_file").(bool), 729 UsePolicyfile: d.Get("use_policyfile").(bool), 730 PolicyGroup: d.Get("policy_group").(string), 731 PolicyName: d.Get("policy_name").(string), 732 HTTPProxy: d.Get("http_proxy").(string), 733 HTTPSProxy: d.Get("https_proxy").(string), 734 NOProxy: getStringList(d.Get("no_proxy")), 735 NamedRunList: d.Get("named_run_list").(string), 736 NodeName: d.Get("node_name").(string), 737 OhaiHints: getStringList(d.Get("ohai_hints")), 738 OSType: d.Get("os_type").(string), 739 RecreateClient: d.Get("recreate_client").(bool), 740 PreventSudo: d.Get("prevent_sudo").(bool), 741 RunList: getStringList(d.Get("run_list")), 742 SecretKey: d.Get("secret_key").(string), 743 ServerURL: d.Get("server_url").(string), 744 SkipInstall: d.Get("skip_install").(bool), 745 SkipRegister: d.Get("skip_register").(bool), 746 SSLVerifyMode: d.Get("ssl_verify_mode").(string), 747 UserName: d.Get("user_name").(string), 748 UserKey: d.Get("user_key").(string), 749 Version: d.Get("version").(string), 750 } 751 752 // Make sure the supplied URL has a trailing slash 753 p.ServerURL = strings.TrimSuffix(p.ServerURL, "/") + "/" 754 755 for i, hint := range p.OhaiHints { 756 hintPath, err := homedir.Expand(hint) 757 if err != nil { 758 return nil, fmt.Errorf("Error expanding the path %s: %v", hint, err) 759 } 760 p.OhaiHints[i] = hintPath 761 } 762 763 if attrs, ok := d.GetOk("attributes_json"); ok { 764 var m map[string]interface{} 765 if err := json.Unmarshal([]byte(attrs.(string)), &m); err != nil { 766 return nil, fmt.Errorf("Error parsing attributes_json: %v", err) 767 } 768 p.Attributes = m 769 } 770 771 if vaults, ok := d.GetOk("vault_json"); ok { 772 var m map[string]interface{} 773 if err := json.Unmarshal([]byte(vaults.(string)), &m); err != nil { 774 return nil, fmt.Errorf("Error parsing vault_json: %v", err) 775 } 776 777 v := make(map[string][]string) 778 for vault, items := range m { 779 switch items := items.(type) { 780 case []interface{}: 781 for _, item := range items { 782 if item, ok := item.(string); ok { 783 v[vault] = append(v[vault], item) 784 } 785 } 786 case interface{}: 787 if item, ok := items.(string); ok { 788 v[vault] = append(v[vault], item) 789 } 790 } 791 } 792 793 p.Vaults = v 794 } 795 796 return p, nil 797 } 798 799 func getStringList(v interface{}) []string { 800 var result []string 801 802 switch v := v.(type) { 803 case nil: 804 return result 805 case []interface{}: 806 for _, vv := range v { 807 if vv, ok := vv.(string); ok { 808 result = append(result, vv) 809 } 810 } 811 return result 812 default: 813 panic(fmt.Sprintf("Unsupported type: %T", v)) 814 } 815 }