github.com/ddnomad/packer@v1.3.2/provisioner/chef-client/provisioner.go (about) 1 // This package implements a provisioner for Packer that uses 2 // Chef to provision the remote machine, specifically with chef-client (that is, 3 // with a Chef server). 4 package chefclient 5 6 import ( 7 "bytes" 8 "encoding/json" 9 "fmt" 10 "io/ioutil" 11 "os" 12 "path/filepath" 13 "strings" 14 15 "github.com/hashicorp/packer/common" 16 "github.com/hashicorp/packer/common/uuid" 17 "github.com/hashicorp/packer/helper/config" 18 "github.com/hashicorp/packer/packer" 19 "github.com/hashicorp/packer/provisioner" 20 "github.com/hashicorp/packer/template/interpolate" 21 ) 22 23 type guestOSTypeConfig struct { 24 executeCommand string 25 installCommand string 26 knifeCommand string 27 stagingDir string 28 } 29 30 var guestOSTypeConfigs = map[string]guestOSTypeConfig{ 31 provisioner.UnixOSType: { 32 executeCommand: "{{if .Sudo}}sudo {{end}}chef-client --no-color -c {{.ConfigPath}} -j {{.JsonPath}}", 33 installCommand: "curl -L https://omnitruck.chef.io/install.sh | {{if .Sudo}}sudo {{end}}bash", 34 knifeCommand: "{{if .Sudo}}sudo {{end}}knife {{.Args}} {{.Flags}}", 35 stagingDir: "/tmp/packer-chef-client", 36 }, 37 provisioner.WindowsOSType: { 38 executeCommand: "c:/opscode/chef/bin/chef-client.bat --no-color -c {{.ConfigPath}} -j {{.JsonPath}}", 39 installCommand: "powershell.exe -Command \". { iwr -useb https://omnitruck.chef.io/install.ps1 } | iex; install\"", 40 knifeCommand: "c:/opscode/chef/bin/knife.bat {{.Args}} {{.Flags}}", 41 stagingDir: "C:/Windows/Temp/packer-chef-client", 42 }, 43 } 44 45 type Config struct { 46 common.PackerConfig `mapstructure:",squash"` 47 48 Json map[string]interface{} 49 50 ChefEnvironment string `mapstructure:"chef_environment"` 51 ClientKey string `mapstructure:"client_key"` 52 ConfigTemplate string `mapstructure:"config_template"` 53 EncryptedDataBagSecretPath string `mapstructure:"encrypted_data_bag_secret_path"` 54 ExecuteCommand string `mapstructure:"execute_command"` 55 GuestOSType string `mapstructure:"guest_os_type"` 56 InstallCommand string `mapstructure:"install_command"` 57 KnifeCommand string `mapstructure:"knife_command"` 58 NodeName string `mapstructure:"node_name"` 59 PolicyGroup string `mapstructure:"policy_group"` 60 PolicyName string `mapstructure:"policy_name"` 61 PreventSudo bool `mapstructure:"prevent_sudo"` 62 RunList []string `mapstructure:"run_list"` 63 ServerUrl string `mapstructure:"server_url"` 64 SkipCleanClient bool `mapstructure:"skip_clean_client"` 65 SkipCleanNode bool `mapstructure:"skip_clean_node"` 66 SkipCleanStagingDirectory bool `mapstructure:"skip_clean_staging_directory"` 67 SkipInstall bool `mapstructure:"skip_install"` 68 SslVerifyMode string `mapstructure:"ssl_verify_mode"` 69 TrustedCertsDir string `mapstructure:"trusted_certs_dir"` 70 StagingDir string `mapstructure:"staging_directory"` 71 ValidationClientName string `mapstructure:"validation_client_name"` 72 ValidationKeyPath string `mapstructure:"validation_key_path"` 73 74 ctx interpolate.Context 75 } 76 77 type Provisioner struct { 78 config Config 79 guestOSTypeConfig guestOSTypeConfig 80 guestCommands *provisioner.GuestCommands 81 } 82 83 type ConfigTemplate struct { 84 ChefEnvironment string 85 ClientKey string 86 EncryptedDataBagSecretPath string 87 NodeName string 88 PolicyGroup string 89 PolicyName string 90 ServerUrl string 91 SslVerifyMode string 92 TrustedCertsDir string 93 ValidationClientName string 94 ValidationKeyPath string 95 } 96 97 type ExecuteTemplate struct { 98 ConfigPath string 99 JsonPath string 100 Sudo bool 101 } 102 103 type InstallChefTemplate struct { 104 Sudo bool 105 } 106 107 type KnifeTemplate struct { 108 Sudo bool 109 Flags string 110 Args string 111 } 112 113 func (p *Provisioner) Prepare(raws ...interface{}) error { 114 err := config.Decode(&p.config, &config.DecodeOpts{ 115 Interpolate: true, 116 InterpolateContext: &p.config.ctx, 117 InterpolateFilter: &interpolate.RenderFilter{ 118 Exclude: []string{ 119 "execute_command", 120 "install_command", 121 "knife_command", 122 }, 123 }, 124 }, raws...) 125 if err != nil { 126 return err 127 } 128 129 if p.config.GuestOSType == "" { 130 p.config.GuestOSType = provisioner.DefaultOSType 131 } 132 p.config.GuestOSType = strings.ToLower(p.config.GuestOSType) 133 134 var ok bool 135 p.guestOSTypeConfig, ok = guestOSTypeConfigs[p.config.GuestOSType] 136 if !ok { 137 return fmt.Errorf("Invalid guest_os_type: \"%s\"", p.config.GuestOSType) 138 } 139 140 p.guestCommands, err = provisioner.NewGuestCommands(p.config.GuestOSType, !p.config.PreventSudo) 141 if err != nil { 142 return fmt.Errorf("Invalid guest_os_type: \"%s\"", p.config.GuestOSType) 143 } 144 145 if p.config.ExecuteCommand == "" { 146 p.config.ExecuteCommand = p.guestOSTypeConfig.executeCommand 147 } 148 149 if p.config.InstallCommand == "" { 150 p.config.InstallCommand = p.guestOSTypeConfig.installCommand 151 } 152 153 if p.config.RunList == nil { 154 p.config.RunList = make([]string, 0) 155 } 156 157 if p.config.StagingDir == "" { 158 p.config.StagingDir = p.guestOSTypeConfig.stagingDir 159 } 160 161 if p.config.KnifeCommand == "" { 162 p.config.KnifeCommand = p.guestOSTypeConfig.knifeCommand 163 } 164 165 var errs *packer.MultiError 166 if p.config.ConfigTemplate != "" { 167 fi, err := os.Stat(p.config.ConfigTemplate) 168 if err != nil { 169 errs = packer.MultiErrorAppend( 170 errs, fmt.Errorf("Bad config template path: %s", err)) 171 } else if fi.IsDir() { 172 errs = packer.MultiErrorAppend( 173 errs, fmt.Errorf("Config template path must be a file: %s", err)) 174 } 175 } 176 177 if p.config.ServerUrl == "" { 178 errs = packer.MultiErrorAppend( 179 errs, fmt.Errorf("server_url must be set")) 180 } 181 182 if p.config.EncryptedDataBagSecretPath != "" { 183 pFileInfo, err := os.Stat(p.config.EncryptedDataBagSecretPath) 184 185 if err != nil || pFileInfo.IsDir() { 186 errs = packer.MultiErrorAppend( 187 errs, fmt.Errorf("Bad encrypted data bag secret '%s': %s", p.config.EncryptedDataBagSecretPath, err)) 188 } 189 } 190 191 if (p.config.PolicyName != "") != (p.config.PolicyGroup != "") { 192 errs = packer.MultiErrorAppend(errs, fmt.Errorf("If either policy_name or policy_group are set, they must both be set.")) 193 } 194 195 jsonValid := true 196 for k, v := range p.config.Json { 197 p.config.Json[k], err = p.deepJsonFix(k, v) 198 if err != nil { 199 errs = packer.MultiErrorAppend( 200 errs, fmt.Errorf("Error processing JSON: %s", err)) 201 jsonValid = false 202 } 203 } 204 205 if jsonValid { 206 // Process the user variables within the JSON and set the JSON. 207 // Do this early so that we can validate and show errors. 208 p.config.Json, err = p.processJsonUserVars() 209 if err != nil { 210 errs = packer.MultiErrorAppend( 211 errs, fmt.Errorf("Error processing user variables in JSON: %s", err)) 212 } 213 } 214 215 if errs != nil && len(errs.Errors) > 0 { 216 return errs 217 } 218 219 return nil 220 } 221 222 func (p *Provisioner) Provision(ui packer.Ui, comm packer.Communicator) error { 223 224 nodeName := p.config.NodeName 225 if nodeName == "" { 226 nodeName = fmt.Sprintf("packer-%s", uuid.TimeOrderedUUID()) 227 } 228 remoteValidationKeyPath := "" 229 serverUrl := p.config.ServerUrl 230 231 if !p.config.SkipInstall { 232 if err := p.installChef(ui, comm); err != nil { 233 return fmt.Errorf("Error installing Chef: %s", err) 234 } 235 } 236 237 if err := p.createDir(ui, comm, p.config.StagingDir); err != nil { 238 return fmt.Errorf("Error creating staging directory: %s", err) 239 } 240 241 if p.config.ClientKey == "" { 242 p.config.ClientKey = fmt.Sprintf("%s/client.pem", p.config.StagingDir) 243 } 244 245 encryptedDataBagSecretPath := "" 246 if p.config.EncryptedDataBagSecretPath != "" { 247 encryptedDataBagSecretPath = fmt.Sprintf("%s/encrypted_data_bag_secret", p.config.StagingDir) 248 if err := p.uploadFile(ui, 249 comm, 250 encryptedDataBagSecretPath, 251 p.config.EncryptedDataBagSecretPath); err != nil { 252 return fmt.Errorf("Error uploading encrypted data bag secret: %s", err) 253 } 254 } 255 256 if p.config.ValidationKeyPath != "" { 257 remoteValidationKeyPath = fmt.Sprintf("%s/validation.pem", p.config.StagingDir) 258 if err := p.uploadFile(ui, comm, remoteValidationKeyPath, p.config.ValidationKeyPath); err != nil { 259 return fmt.Errorf("Error copying validation key: %s", err) 260 } 261 } 262 263 configPath, err := p.createConfig( 264 ui, 265 comm, 266 nodeName, 267 serverUrl, 268 p.config.ClientKey, 269 encryptedDataBagSecretPath, 270 remoteValidationKeyPath, 271 p.config.ValidationClientName, 272 p.config.ChefEnvironment, 273 p.config.PolicyGroup, 274 p.config.PolicyName, 275 p.config.SslVerifyMode, 276 p.config.TrustedCertsDir) 277 if err != nil { 278 return fmt.Errorf("Error creating Chef config file: %s", err) 279 } 280 281 jsonPath, err := p.createJson(ui, comm) 282 if err != nil { 283 return fmt.Errorf("Error creating JSON attributes: %s", err) 284 } 285 286 err = p.executeChef(ui, comm, configPath, jsonPath) 287 288 if !(p.config.SkipCleanNode && p.config.SkipCleanClient) { 289 290 knifeConfigPath, knifeErr := p.createKnifeConfig( 291 ui, comm, nodeName, serverUrl, p.config.ClientKey, p.config.SslVerifyMode, p.config.TrustedCertsDir) 292 293 if knifeErr != nil { 294 return fmt.Errorf("Error creating knife config on node: %s", knifeErr) 295 } 296 297 if !p.config.SkipCleanNode { 298 if err := p.cleanNode(ui, comm, nodeName, knifeConfigPath); err != nil { 299 return fmt.Errorf("Error cleaning up chef node: %s", err) 300 } 301 } 302 303 if !p.config.SkipCleanClient { 304 if err := p.cleanClient(ui, comm, nodeName, knifeConfigPath); err != nil { 305 return fmt.Errorf("Error cleaning up chef client: %s", err) 306 } 307 } 308 } 309 310 if err != nil { 311 return fmt.Errorf("Error executing Chef: %s", err) 312 } 313 314 if !p.config.SkipCleanStagingDirectory { 315 if err := p.removeDir(ui, comm, p.config.StagingDir); err != nil { 316 return fmt.Errorf("Error removing %s: %s", p.config.StagingDir, err) 317 } 318 } 319 320 return nil 321 } 322 323 func (p *Provisioner) Cancel() { 324 // Just hard quit. It isn't a big deal if what we're doing keeps 325 // running on the other side. 326 os.Exit(0) 327 } 328 329 func (p *Provisioner) uploadFile(ui packer.Ui, comm packer.Communicator, remotePath string, localPath string) error { 330 ui.Message(fmt.Sprintf("Uploading %s...", localPath)) 331 332 f, err := os.Open(localPath) 333 if err != nil { 334 return err 335 } 336 defer f.Close() 337 338 return comm.Upload(remotePath, f, nil) 339 } 340 341 func (p *Provisioner) createConfig( 342 ui packer.Ui, 343 comm packer.Communicator, 344 nodeName string, 345 serverUrl string, 346 clientKey string, 347 encryptedDataBagSecretPath, 348 remoteKeyPath string, 349 validationClientName string, 350 chefEnvironment string, 351 policyGroup string, 352 policyName string, 353 sslVerifyMode string, 354 trustedCertsDir string) (string, error) { 355 356 ui.Message("Creating configuration file 'client.rb'") 357 358 // Read the template 359 tpl := DefaultConfigTemplate 360 if p.config.ConfigTemplate != "" { 361 f, err := os.Open(p.config.ConfigTemplate) 362 if err != nil { 363 return "", err 364 } 365 defer f.Close() 366 367 tplBytes, err := ioutil.ReadAll(f) 368 if err != nil { 369 return "", err 370 } 371 372 tpl = string(tplBytes) 373 } 374 375 ctx := p.config.ctx 376 ctx.Data = &ConfigTemplate{ 377 NodeName: nodeName, 378 ServerUrl: serverUrl, 379 ClientKey: clientKey, 380 ValidationKeyPath: remoteKeyPath, 381 ValidationClientName: validationClientName, 382 ChefEnvironment: chefEnvironment, 383 PolicyGroup: policyGroup, 384 PolicyName: policyName, 385 SslVerifyMode: sslVerifyMode, 386 TrustedCertsDir: trustedCertsDir, 387 EncryptedDataBagSecretPath: encryptedDataBagSecretPath, 388 } 389 configString, err := interpolate.Render(tpl, &ctx) 390 if err != nil { 391 return "", err 392 } 393 394 remotePath := filepath.ToSlash(filepath.Join(p.config.StagingDir, "client.rb")) 395 if err := comm.Upload(remotePath, bytes.NewReader([]byte(configString)), nil); err != nil { 396 return "", err 397 } 398 399 return remotePath, nil 400 } 401 402 func (p *Provisioner) createKnifeConfig(ui packer.Ui, comm packer.Communicator, nodeName string, serverUrl string, clientKey string, sslVerifyMode string, trustedCertsDir string) (string, error) { 403 ui.Message("Creating configuration file 'knife.rb'") 404 405 // Read the template 406 tpl := DefaultKnifeTemplate 407 408 ctx := p.config.ctx 409 ctx.Data = &ConfigTemplate{ 410 NodeName: nodeName, 411 ServerUrl: serverUrl, 412 ClientKey: clientKey, 413 SslVerifyMode: sslVerifyMode, 414 TrustedCertsDir: trustedCertsDir, 415 } 416 configString, err := interpolate.Render(tpl, &ctx) 417 if err != nil { 418 return "", err 419 } 420 421 remotePath := filepath.ToSlash(filepath.Join(p.config.StagingDir, "knife.rb")) 422 if err := comm.Upload(remotePath, bytes.NewReader([]byte(configString)), nil); err != nil { 423 return "", err 424 } 425 426 return remotePath, nil 427 } 428 429 func (p *Provisioner) createJson(ui packer.Ui, comm packer.Communicator) (string, error) { 430 ui.Message("Creating JSON attribute file") 431 432 jsonData := make(map[string]interface{}) 433 434 // Copy the configured JSON 435 for k, v := range p.config.Json { 436 jsonData[k] = v 437 } 438 439 // Set the run list if it was specified 440 if len(p.config.RunList) > 0 { 441 jsonData["run_list"] = p.config.RunList 442 } 443 444 jsonBytes, err := json.MarshalIndent(jsonData, "", " ") 445 if err != nil { 446 return "", err 447 } 448 449 // Upload the bytes 450 remotePath := filepath.ToSlash(filepath.Join(p.config.StagingDir, "first-boot.json")) 451 if err := comm.Upload(remotePath, bytes.NewReader(jsonBytes), nil); err != nil { 452 return "", err 453 } 454 455 return remotePath, nil 456 } 457 458 func (p *Provisioner) createDir(ui packer.Ui, comm packer.Communicator, dir string) error { 459 ui.Message(fmt.Sprintf("Creating directory: %s", dir)) 460 461 cmd := &packer.RemoteCmd{Command: p.guestCommands.CreateDir(dir)} 462 if err := cmd.StartWithUi(comm, ui); err != nil { 463 return err 464 } 465 if cmd.ExitStatus != 0 { 466 return fmt.Errorf("Non-zero exit status. See output above for more info.") 467 } 468 469 // Chmod the directory to 0777 just so that we can access it as our user 470 cmd = &packer.RemoteCmd{Command: p.guestCommands.Chmod(dir, "0777")} 471 if err := cmd.StartWithUi(comm, ui); err != nil { 472 return err 473 } 474 if cmd.ExitStatus != 0 { 475 return fmt.Errorf("Non-zero exit status. See output above for more info.") 476 } 477 478 return nil 479 } 480 481 func (p *Provisioner) cleanNode(ui packer.Ui, comm packer.Communicator, node string, knifeConfigPath string) error { 482 ui.Say("Cleaning up chef node...") 483 args := []string{"node", "delete", node} 484 if err := p.knifeExec(ui, comm, node, knifeConfigPath, args); err != nil { 485 return fmt.Errorf("Failed to cleanup node: %s", err) 486 } 487 488 return nil 489 } 490 491 func (p *Provisioner) cleanClient(ui packer.Ui, comm packer.Communicator, node string, knifeConfigPath string) error { 492 ui.Say("Cleaning up chef client...") 493 args := []string{"client", "delete", node} 494 if err := p.knifeExec(ui, comm, node, knifeConfigPath, args); err != nil { 495 return fmt.Errorf("Failed to cleanup client: %s", err) 496 } 497 498 return nil 499 } 500 501 func (p *Provisioner) knifeExec(ui packer.Ui, comm packer.Communicator, node string, knifeConfigPath string, args []string) error { 502 flags := []string{ 503 "-y", 504 "-c", knifeConfigPath, 505 } 506 507 p.config.ctx.Data = &KnifeTemplate{ 508 Sudo: !p.config.PreventSudo, 509 Flags: strings.Join(flags, " "), 510 Args: strings.Join(args, " "), 511 } 512 513 command, err := interpolate.Render(p.config.KnifeCommand, &p.config.ctx) 514 if err != nil { 515 return err 516 } 517 518 cmd := &packer.RemoteCmd{Command: command} 519 if err := cmd.StartWithUi(comm, ui); err != nil { 520 return err 521 } 522 if cmd.ExitStatus != 0 { 523 return fmt.Errorf( 524 "Non-zero exit status. See output above for more info.\n\n"+ 525 "Command: %s", 526 command) 527 } 528 529 return nil 530 } 531 532 func (p *Provisioner) removeDir(ui packer.Ui, comm packer.Communicator, dir string) error { 533 ui.Message(fmt.Sprintf("Removing directory: %s", dir)) 534 535 cmd := &packer.RemoteCmd{Command: p.guestCommands.RemoveDir(dir)} 536 if err := cmd.StartWithUi(comm, ui); err != nil { 537 return err 538 } 539 540 return nil 541 } 542 543 func (p *Provisioner) executeChef(ui packer.Ui, comm packer.Communicator, config string, json string) error { 544 p.config.ctx.Data = &ExecuteTemplate{ 545 ConfigPath: config, 546 JsonPath: json, 547 Sudo: !p.config.PreventSudo, 548 } 549 command, err := interpolate.Render(p.config.ExecuteCommand, &p.config.ctx) 550 if err != nil { 551 return err 552 } 553 554 ui.Message(fmt.Sprintf("Executing Chef: %s", command)) 555 556 cmd := &packer.RemoteCmd{ 557 Command: command, 558 } 559 560 if err := cmd.StartWithUi(comm, ui); err != nil { 561 return err 562 } 563 564 if cmd.ExitStatus != 0 { 565 return fmt.Errorf("Non-zero exit status: %d", cmd.ExitStatus) 566 } 567 568 return nil 569 } 570 571 func (p *Provisioner) installChef(ui packer.Ui, comm packer.Communicator) error { 572 ui.Message("Installing Chef...") 573 574 p.config.ctx.Data = &InstallChefTemplate{ 575 Sudo: !p.config.PreventSudo, 576 } 577 command, err := interpolate.Render(p.config.InstallCommand, &p.config.ctx) 578 if err != nil { 579 return err 580 } 581 582 ui.Message(command) 583 584 cmd := &packer.RemoteCmd{Command: command} 585 if err := cmd.StartWithUi(comm, ui); err != nil { 586 return err 587 } 588 589 if cmd.ExitStatus != 0 { 590 return fmt.Errorf( 591 "Install script exited with non-zero exit status %d", cmd.ExitStatus) 592 } 593 594 return nil 595 } 596 597 func (p *Provisioner) deepJsonFix(key string, current interface{}) (interface{}, error) { 598 if current == nil { 599 return nil, nil 600 } 601 602 switch c := current.(type) { 603 case []interface{}: 604 val := make([]interface{}, len(c)) 605 for i, v := range c { 606 var err error 607 val[i], err = p.deepJsonFix(fmt.Sprintf("%s[%d]", key, i), v) 608 if err != nil { 609 return nil, err 610 } 611 } 612 613 return val, nil 614 case []uint8: 615 return string(c), nil 616 case map[interface{}]interface{}: 617 val := make(map[string]interface{}) 618 for k, v := range c { 619 ks, ok := k.(string) 620 if !ok { 621 return nil, fmt.Errorf("%s: key is not string", key) 622 } 623 624 var err error 625 val[ks], err = p.deepJsonFix( 626 fmt.Sprintf("%s.%s", key, ks), v) 627 if err != nil { 628 return nil, err 629 } 630 } 631 632 return val, nil 633 default: 634 return current, nil 635 } 636 } 637 638 func (p *Provisioner) processJsonUserVars() (map[string]interface{}, error) { 639 jsonBytes, err := json.Marshal(p.config.Json) 640 if err != nil { 641 // This really shouldn't happen since we literally just unmarshalled 642 panic(err) 643 } 644 645 // Copy the user variables so that we can restore them later, and 646 // make sure we make the quotes JSON-friendly in the user variables. 647 originalUserVars := make(map[string]string) 648 for k, v := range p.config.ctx.UserVariables { 649 originalUserVars[k] = v 650 } 651 652 // Make sure we reset them no matter what 653 defer func() { 654 p.config.ctx.UserVariables = originalUserVars 655 }() 656 657 // Make the current user variables JSON string safe. 658 for k, v := range p.config.ctx.UserVariables { 659 v = strings.Replace(v, `\`, `\\`, -1) 660 v = strings.Replace(v, `"`, `\"`, -1) 661 p.config.ctx.UserVariables[k] = v 662 } 663 664 // Process the bytes with the template processor 665 p.config.ctx.Data = nil 666 jsonBytesProcessed, err := interpolate.Render(string(jsonBytes), &p.config.ctx) 667 if err != nil { 668 return nil, err 669 } 670 671 var result map[string]interface{} 672 if err := json.Unmarshal([]byte(jsonBytesProcessed), &result); err != nil { 673 return nil, err 674 } 675 676 return result, nil 677 } 678 679 var DefaultConfigTemplate = ` 680 log_level :info 681 log_location STDOUT 682 chef_server_url "{{.ServerUrl}}" 683 client_key "{{.ClientKey}}" 684 {{if ne .EncryptedDataBagSecretPath ""}} 685 encrypted_data_bag_secret "{{.EncryptedDataBagSecretPath}}" 686 {{end}} 687 {{if ne .ValidationClientName ""}} 688 validation_client_name "{{.ValidationClientName}}" 689 {{else}} 690 validation_client_name "chef-validator" 691 {{end}} 692 {{if ne .ValidationKeyPath ""}} 693 validation_key "{{.ValidationKeyPath}}" 694 {{end}} 695 node_name "{{.NodeName}}" 696 {{if ne .ChefEnvironment ""}} 697 environment "{{.ChefEnvironment}}" 698 {{end}} 699 {{if ne .PolicyGroup ""}} 700 policy_group "{{.PolicyGroup}}" 701 {{end}} 702 {{if ne .PolicyName ""}} 703 policy_name "{{.PolicyName}}" 704 {{end}} 705 {{if ne .SslVerifyMode ""}} 706 ssl_verify_mode :{{.SslVerifyMode}} 707 {{end}} 708 {{if ne .TrustedCertsDir ""}} 709 trusted_certs_dir "{{.TrustedCertsDir}}" 710 {{end}} 711 ` 712 713 var DefaultKnifeTemplate = ` 714 log_level :info 715 log_location STDOUT 716 chef_server_url "{{.ServerUrl}}" 717 client_key "{{.ClientKey}}" 718 node_name "{{.NodeName}}" 719 {{if ne .SslVerifyMode ""}} 720 ssl_verify_mode :{{.SslVerifyMode}} 721 {{end}} 722 {{if ne .TrustedCertsDir ""}} 723 trusted_certs_dir "{{.TrustedCertsDir}}" 724 {{end}} 725 `