diff --git a/builtin/provisioners/chef/linux_provisioner_test.go b/builtin/provisioners/chef/linux_provisioner_test.go index c854199e3ef1..97655e26e903 100644 --- a/builtin/provisioners/chef/linux_provisioner_test.go +++ b/builtin/provisioners/chef/linux_provisioner_test.go @@ -157,6 +157,7 @@ func TestResourceProvider_linuxInstallChefClient(t *testing.T) { p, err := decodeConfig( schema.TestResourceDataRaw(t, Provisioner().(*schema.Provisioner).Schema, tc.Config), + "linux", ) if err != nil { t.Fatalf("Error: %v", err) @@ -287,7 +288,7 @@ func TestResourceProvider_linuxCreateConfigFiles(t *testing.T) { "LocalMode": { Config: map[string]interface{}{ "use_local_mode": true, - "chef_repo": "tbd", + "chef_repo": os.TempDir(), "run_list": []interface{}{"role[testrole]"}, "environment": "testenv", "node_name": "testnode", @@ -313,7 +314,7 @@ func TestResourceProvider_linuxCreateConfigFiles(t *testing.T) { "LocalModePolicy": { Config: map[string]interface{}{ "use_local_mode": true, - "chef_repo": "tbd", + "chef_repo": os.TempDir(), "use_policyfile": true, "policy_name": "testpolicy", }, @@ -345,6 +346,7 @@ func TestResourceProvider_linuxCreateConfigFiles(t *testing.T) { p, err := decodeConfig( schema.TestResourceDataRaw(t, Provisioner().(*schema.Provisioner).Schema, tc.Config), + "linux", ) if err != nil { t.Fatalf("Error: %v", err) @@ -468,6 +470,47 @@ func TestResourceProvider_linuxDeployChefRepo(t *testing.T) { return testDeleteTmpFiles(self.Config["chef_repo"].(string)) }, }, + + "LocalModePolicyRepoFile": { + Config: map[string]interface{}{ + "use_local_mode": true, + "chef_repo": "tbd", + "use_policyfile": true, + "policy_name": "testpolicy", + }, + + Commands: map[string]bool{ + // prepare var dir for upload + "sudo mkdir -p " + linuxRepoDir: true, + "sudo chmod 777 " + linuxRepoDir: true, + "sudo " + fmt.Sprintf(chmod, linuxRepoDir, 666): true, + // client run + "sudo sh -c 'cd /var/chef && chef-client -z -j /etc/chef/first-boot.json'": true, + "sudo chef-client -z -j /etc/chef/first-boot.json": false, + }, + + Uploads: map[string]string{}, + + SetUp: func(t *testing.T, self createConfigTestCase) error { + archName := "testrepo.tgz" + layout := map[string][]string{ + "": []string{archName}, + } + tmpDir, err := testCreateTmpFiles(layout) + if err != nil { + return err + } + + repoFile := path.Join(tmpDir, archName) + self.Config["chef_repo"] = repoFile + self.Uploads[path.Join(linuxRepoDir, archName)] = "" + return nil + }, + + TearDown: func(t *testing.T, self createConfigTestCase) error { + return testDeleteTmpFiles(self.Config["chef_repo"].(string)) + }, + }, } o := new(terraform.MockUIOutput) @@ -491,6 +534,7 @@ func TestResourceProvider_linuxDeployChefRepo(t *testing.T) { p, err := decodeConfig( schema.TestResourceDataRaw(t, Provisioner().(*schema.Provisioner).Schema, tc.Config), + "linux", ) if err != nil { t.Fatalf("Error: %v", err) diff --git a/builtin/provisioners/chef/resource_provisioner.go b/builtin/provisioners/chef/resource_provisioner.go index d5e5a9942284..a74e4f33c8c1 100644 --- a/builtin/provisioners/chef/resource_provisioner.go +++ b/builtin/provisioners/chef/resource_provisioner.go @@ -11,12 +11,14 @@ import ( "log" "os" "path" + "path/filepath" "regexp" "strconv" "strings" "sync" "text/template" + "github.com/Unknwon/com" "github.com/hashicorp/terraform/communicator" "github.com/hashicorp/terraform/communicator/remote" "github.com/hashicorp/terraform/helper/schema" @@ -52,7 +54,7 @@ node_name "{{ .NodeName }}" {{ end -}} {{ if .UseLocalMode -}} -chef_repo_path "{{ .RemoteChefRepo }}" +chef_repo_path "{{ .ChefRepo.RemotePath }}" {{ else -}} chef_server_url "{{ .ServerURL }}" @@ -95,14 +97,21 @@ enable_reporting false type provisionFn func(terraform.UIOutput, communicator.Communicator) error +type chefRepo struct { + IsDir bool + LocalPath string + RemotePath string +} + type provisioner struct { - Attributes map[string]interface{} - Channel string - ClientOptions []string - DisableReporting bool - Environment string - UseLocalMode bool - ChefRepo string + Attributes map[string]interface{} + Channel string + ClientOptions []string + DisableReporting bool + Environment string + UseLocalMode bool + //ChefRepo string + ChefRepo chefRepo FetchChefCertificates bool LogToFile bool UsePolicyfile bool @@ -139,11 +148,11 @@ type provisioner struct { useSudo bool } -type clientrbVars struct { - *provisioner +// type clientrbVars struct { +// *provisioner - RemoteChefRepo string -} +// RemoteChefRepo string +// } // Provisioner returns a Chef provisioner func Provisioner() terraform.ResourceProvisioner { @@ -297,23 +306,22 @@ func applyFn(ctx context.Context) error { s := ctx.Value(schema.ProvRawStateKey).(*terraform.InstanceState) d := ctx.Value(schema.ProvConfigDataKey).(*schema.ResourceData) + var defaultOSType string + switch t := s.Ephemeral.ConnInfo["type"]; t { + case "ssh", "": // The default connection type is ssh, so if the type is empty assume ssh + defaultOSType = "linux" + case "winrm": + defaultOSType = "windows" + default: + return fmt.Errorf("Unsupported connection type: %s", t) + } + // Decode the provisioner config - p, err := decodeConfig(d) + p, err := decodeConfig(d, defaultOSType) if err != nil { return err } - if p.OSType == "" { - switch t := s.Ephemeral.ConnInfo["type"]; t { - case "ssh", "": // The default connection type is ssh, so if the type is empty assume ssh - p.OSType = "linux" - case "winrm": - p.OSType = "windows" - default: - return fmt.Errorf("Unsupported connection type: %s", t) - } - } - // Set some values based on the targeted OS switch p.OSType { case "linux": @@ -513,21 +521,11 @@ func (p *provisioner) deployConfigFiles(o terraform.UIOutput, comm communicator. "join": strings.Join, } - // Prepare clientrb template variables - clientrbVars := clientrbVars{} - clientrbVars.provisioner = p - switch p.OSType { - case "windows": - clientrbVars.RemoteChefRepo = windowsRepoDir - default: - clientrbVars.RemoteChefRepo = linuxRepoDir - } - // Create a new template and parse the client config into it t := template.Must(template.New(clienrb).Funcs(funcMap).Parse(clientConf)) var buf bytes.Buffer - err := t.Execute(&buf, &clientrbVars) + err := t.Execute(&buf, p) if err != nil { return fmt.Errorf("Error executing %s template: %s", clienrb, err) } @@ -569,71 +567,89 @@ func (p *provisioner) deployConfigFiles(o terraform.UIOutput, comm communicator. return nil } -func (p *provisioner) deployChefRepoFiles(o terraform.UIOutput, comm communicator.Communicator, varDir string) error { - repoSrc := p.ChefRepo - if repoSrc[len(repoSrc)-1] != '/' { - repoSrc = repoSrc + "/" - } +func (p *provisioner) deployChefRepoFiles(o terraform.UIOutput, comm communicator.Communicator, repoDir string) error { + repo := p.ChefRepo + if repo.IsDir { + log.Printf("[INFO] Chef repository is a directory.") - files, err := ioutil.ReadDir(repoSrc) - if err != nil { - return fmt.Errorf("Unable to read repository directoy %s: %v", repoSrc, err) - } + files, err := ioutil.ReadDir(repo.LocalPath) + if err != nil { + return fmt.Errorf("Unable to read repository directoy %s: %v", repo.LocalPath, err) + } - if p.UsePolicyfile { - var hasPolicyfile bool - var hasCookbookArtifactsDir bool + if p.UsePolicyfile { + var hasPolicyfile bool + var hasCookbookArtifactsDir bool - for _, file := range files { - if file.Name() == "Policyfile.lock.json" { - hasPolicyfile = true - } else if file.Name() == "cookbook_artifacts" { - hasCookbookArtifactsDir = true + for _, file := range files { + if file.Name() == "Policyfile.lock.json" { + hasPolicyfile = true + } else if file.Name() == "cookbook_artifacts" { + hasCookbookArtifactsDir = true + } } - } - if !hasPolicyfile { - log.Printf("[WARN] Chef repository lacks %s '%s'. This is probably a mistake.", "file", "Policyfile.lock.json") - } - if !hasCookbookArtifactsDir { - log.Printf("[WARN] Chef repository lacks %s '%s'. This is probably a mistake.", "directory", "cookbook_artifacts") - } - } else { - var hasCookbooksDir bool - var hasEnvironmentsDir bool - var hasRolesDir bool - - for _, file := range files { - if file.Name() == "cookbooks" { - hasCookbooksDir = true - } else if file.Name() == "environments" { - hasEnvironmentsDir = true - } else if file.Name() == "roles" { - hasRolesDir = true + if !hasPolicyfile { + log.Printf("[WARN] Chef repository lacks %s '%s'. This is probably a mistake.", "file", "Policyfile.lock.json") + } + if !hasCookbookArtifactsDir { + log.Printf("[WARN] Chef repository lacks %s '%s'. This is probably a mistake.", "directory", "cookbook_artifacts") + } + } else { + var hasCookbooksDir bool + var hasEnvironmentsDir bool + var hasRolesDir bool + + for _, file := range files { + if file.Name() == "cookbooks" { + hasCookbooksDir = true + } else if file.Name() == "environments" { + hasEnvironmentsDir = true + } else if file.Name() == "roles" { + hasRolesDir = true + } } - } - var usesRole bool - for _, e := range p.RunList { - if strings.HasPrefix(e, "role[") && !hasRolesDir { - usesRole = true - break + var usesRole bool + for _, e := range p.RunList { + if strings.HasPrefix(e, "role[") && !hasRolesDir { + usesRole = true + break + } + } + + if !hasCookbooksDir { + log.Printf("[WARN] Chef repository lacks %s '%s'. This is probably a mistake.", "directory", "cookbooks") + } + if p.Environment != defaultEnv && !hasEnvironmentsDir { + log.Printf("[WARN] Chef repository lacks %s '%s'. This is probably a mistake.", "directory", "environments") + } + if usesRole && !hasRolesDir { + log.Printf("[WARN] Chef repository lacks %s '%s'. This is probably a mistake.", "directory", "roles") } } - if !hasCookbooksDir { - log.Printf("[WARN] Chef repository lacks %s '%s'. This is probably a mistake.", "directory", "cookbooks") + if err := comm.UploadDir(repo.RemotePath, repo.LocalPath); err != nil { + return fmt.Errorf("Uploading %s failed: %v", repo.LocalPath, err) } - if p.Environment != defaultEnv && !hasEnvironmentsDir { - log.Printf("[WARN] Chef repository lacks %s '%s'. This is probably a mistake.", "directory", "environments") + } else { + ext := filepath.Ext(repo.LocalPath) + if com.IsSliceContainsStr([]string{".tgz", ".tar.gz", ".tar"}, ext) { + log.Printf("[INFO] Chef repository is an archive file.") + } else { + log.Printf("[WARN] Chef repository is file but does not look like an archive. This could be a mistake.") } - if usesRole && !hasRolesDir { - log.Printf("[WARN] Chef repository lacks %s '%s'. This is probably a mistake.", "directory", "roles") + + f, err := os.Open(repo.LocalPath) + if err != nil { + return err } - } + defer f.Close() - if err := comm.UploadDir(varDir, repoSrc); err != nil { - return fmt.Errorf("Uploading %s failed: %v", repoSrc, err) + err = comm.Upload(repo.RemotePath, f) + if err != nil { + return fmt.Errorf("Upload failed: %v", err) + } } return nil @@ -885,10 +901,9 @@ func (p *provisioner) copyOutput(o terraform.UIOutput, r io.Reader) { } } -func decodeConfig(d *schema.ResourceData) (*provisioner, error) { +func decodeConfig(d *schema.ResourceData, defaultOSType string) (*provisioner, error) { p := &provisioner{ UseLocalMode: d.Get("use_local_mode").(bool), - ChefRepo: d.Get("chef_repo").(string), Channel: d.Get("channel").(string), ClientOptions: getStringList(d.Get("client_options")), DisableReporting: d.Get("disable_reporting").(bool), @@ -962,6 +977,48 @@ func decodeConfig(d *schema.ResourceData) (*provisioner, error) { p.Vaults = v } + // Determine target OS + if p.OSType == "" { + p.OSType = defaultOSType + } + + // Verify chef repo + if p.UseLocalMode { + localPath := d.Get("chef_repo").(string) + localPath, err := homedir.Expand(localPath) + if err != nil { + return nil, fmt.Errorf("Error expanding the path %s: %v", localPath, err) + } + + info, err := os.Stat(localPath) + if err != nil { + return nil, fmt.Errorf("Error loading chef_repo from %s: %v", localPath, err) + } + + p.ChefRepo = chefRepo{} + if info.IsDir() { + p.ChefRepo.IsDir = true + if localPath[len(localPath)-1] != '/' { + localPath = localPath + "/" + } + + if p.OSType == "windows" { + p.ChefRepo.RemotePath = windowsRepoDir + } else { + p.ChefRepo.RemotePath = linuxRepoDir + } + } else { + if p.OSType == "windows" { + p.ChefRepo.RemotePath = path.Join(windowsRepoDir, info.Name()) + } else { + p.ChefRepo.RemotePath = path.Join(linuxRepoDir, info.Name()) + } + } + + p.ChefRepo.LocalPath = localPath + + } + return p, nil } diff --git a/builtin/provisioners/chef/resource_provisioner_test.go b/builtin/provisioners/chef/resource_provisioner_test.go index fe9352ef0aff..8e78b0ebc537 100644 --- a/builtin/provisioners/chef/resource_provisioner_test.go +++ b/builtin/provisioners/chef/resource_provisioner_test.go @@ -204,6 +204,7 @@ func TestResourceProvider_runChefClient(t *testing.T) { p, err := decodeConfig( schema.TestResourceDataRaw(t, Provisioner().(*schema.Provisioner).Schema, tc.Config), + "linux", ) if err != nil { t.Fatalf("Error: %v", err) @@ -279,6 +280,7 @@ func TestResourceProvider_fetchChefCertificates(t *testing.T) { p, err := decodeConfig( schema.TestResourceDataRaw(t, Provisioner().(*schema.Provisioner).Schema, tc.Config), + "linux", ) if err != nil { t.Fatalf("Error: %v", err) @@ -465,6 +467,7 @@ func TestResourceProvider_configureVaults(t *testing.T) { p, err := decodeConfig( schema.TestResourceDataRaw(t, Provisioner().(*schema.Provisioner).Schema, tc.Config), + "linux", ) if err != nil { t.Fatalf("Error: %v", err) diff --git a/builtin/provisioners/chef/windows_provisioner_test.go b/builtin/provisioners/chef/windows_provisioner_test.go index d844ed4f9274..c5ef2bafdb29 100644 --- a/builtin/provisioners/chef/windows_provisioner_test.go +++ b/builtin/provisioners/chef/windows_provisioner_test.go @@ -2,6 +2,7 @@ package chef import ( "fmt" + "os" "path" "testing" @@ -99,6 +100,7 @@ func TestResourceProvider_windowsInstallChefClient(t *testing.T) { p, err := decodeConfig( schema.TestResourceDataRaw(t, Provisioner().(*schema.Provisioner).Schema, tc.Config), + "windows", ) if err != nil { t.Fatalf("Error: %v", err) @@ -196,7 +198,7 @@ func TestResourceProvider_windowsCreateConfigFiles(t *testing.T) { "LocalMode": { Config: map[string]interface{}{ "use_local_mode": true, - "chef_repo": "tbd", + "chef_repo": os.TempDir(), "run_list": []interface{}{"cookbook::recipe"}, "environment": "testenv", "node_name": "testnode", @@ -215,7 +217,7 @@ func TestResourceProvider_windowsCreateConfigFiles(t *testing.T) { "LocalModePolicy": { Config: map[string]interface{}{ "use_local_mode": true, - "chef_repo": "tbd", + "chef_repo": os.TempDir(), "use_policyfile": true, "policy_name": "testpolicy", }, @@ -252,6 +254,7 @@ func TestResourceProvider_windowsCreateConfigFiles(t *testing.T) { p, err := decodeConfig( schema.TestResourceDataRaw(t, Provisioner().(*schema.Provisioner).Schema, tc.Config), + "windows", ) if err != nil { t.Fatalf("Error: %v", err) @@ -363,6 +366,7 @@ func TestResourceProvider_windowsUploadChefRepo(t *testing.T) { p, err := decodeConfig( schema.TestResourceDataRaw(t, Provisioner().(*schema.Provisioner).Schema, tc.Config), + "windows", ) if err != nil { t.Fatalf("Error: %v", err)