diff --git a/lib/chef-dk/command/generator_commands/cookbook.rb b/lib/chef-dk/command/generator_commands/cookbook.rb index e2b5dfbaf..d5c7edfb7 100644 --- a/lib/chef-dk/command/generator_commands/cookbook.rb +++ b/lib/chef-dk/command/generator_commands/cookbook.rb @@ -63,6 +63,7 @@ def setup_context Generator.add_attr_to_context(:cookbook_root, cookbook_root) Generator.add_attr_to_context(:cookbook_name, cookbook_name) Generator.add_attr_to_context(:recipe_name, recipe_name) + Generator.add_attr_to_context(:include_chef_repo_source, false) Generator.add_attr_to_context(:policy_name, policy_name) Generator.add_attr_to_context(:policy_run_list, policy_run_list) Generator.add_attr_to_context(:policy_local_cookbook, ".") diff --git a/lib/chef-dk/command/generator_commands/policyfile.rb b/lib/chef-dk/command/generator_commands/policyfile.rb index 4e25b564a..0fc07911c 100644 --- a/lib/chef-dk/command/generator_commands/policyfile.rb +++ b/lib/chef-dk/command/generator_commands/policyfile.rb @@ -29,9 +29,15 @@ class Policyfile < Base attr_reader :new_file_basename attr_reader :policyfile_dir + attr_reader :policy_name + attr_reader :policy_run_list def initialize(*args) super + @new_file_basename = nil + @policyfile_dir = nil + @policy_name = nil + @policy_run_list = nil @params_valid = true end @@ -43,8 +49,9 @@ def setup_context super Generator.add_attr_to_context(:policyfile_dir, policyfile_dir) Generator.add_attr_to_context(:new_file_basename, new_file_basename) - Generator.add_attr_to_context(:policy_name, "example-application-service") - Generator.add_attr_to_context(:policy_run_list, "example_cookbook::default") + Generator.add_attr_to_context(:include_chef_repo_source, chef_repo_mode?) + Generator.add_attr_to_context(:policy_name, policy_name) + Generator.add_attr_to_context(:policy_run_list, policy_run_list) Generator.add_attr_to_context(:policy_local_cookbook, nil) end @@ -62,24 +69,58 @@ def run def read_and_validate_params arguments = parse_options(params) - new_file_path = - case arguments.size - when 0 - "Policyfile" - when 1 - arguments[0] - else + + new_file_path = nil + case arguments.size + when 0 + if chef_repo_mode? + err("ERROR: You must give a policy name when generating a policy in a chef-repo.") @params_valid = false return false + else + use_default_policy_settings end + when 1 + new_file_path = arguments[0] + derive_policy_settings_from_args(arguments[0]) + else + @params_valid = false + err("ERROR: too many arguments") + return false + end + + end + + private + + def use_default_policy_settings + @new_file_basename = "Policyfile" + @policy_name = "example-application-service" + @policy_run_list = "example_cookbook::default" + @policyfile_dir = Dir.pwd + end + + def derive_policy_settings_from_args(new_file_path) @new_file_basename = File.basename(new_file_path, ".rb") - @policyfile_dir = File.expand_path(File.dirname(new_file_path)) + @policy_name = @new_file_basename + @policy_run_list = "#{policy_name}::default" + given_policy_dirname = File.expand_path(File.dirname(new_file_path)) + @policyfile_dir = + if chef_repo_mode? && (given_policy_dirname == Dir.pwd) + File.expand_path("policies") + else + given_policy_dirname + end end def params_valid? @params_valid end + def chef_repo_mode? + File.exist?(File.expand_path(".chef-repo.txt")) + end + end end end diff --git a/lib/chef-dk/policyfile/dsl.rb b/lib/chef-dk/policyfile/dsl.rb index a4a6aa3b5..bc9963888 100644 --- a/lib/chef-dk/policyfile/dsl.rb +++ b/lib/chef-dk/policyfile/dsl.rb @@ -155,7 +155,7 @@ def set_default_chef_repo_source(path) if path.nil? @errors << "You must specify the path to the chef-repo when using a default_source :chef_repo" else - set_default_source(ChefRepoCookbookSource.new(path)) + set_default_source(ChefRepoCookbookSource.new(File.expand_path(path, storage_config.relative_paths_root))) end end diff --git a/lib/chef-dk/skeletons/code_generator/files/default/repo/dot-chef-repo.txt b/lib/chef-dk/skeletons/code_generator/files/default/repo/dot-chef-repo.txt new file mode 100644 index 000000000..7ee34461e --- /dev/null +++ b/lib/chef-dk/skeletons/code_generator/files/default/repo/dot-chef-repo.txt @@ -0,0 +1,6 @@ +.chef-repo.txt +============== + +This file gives ChefDK's generators a hint that you are using a Chef Repo and +this is the root directory of your Chef Repo. ChefDK's generators use this to +generate code that is designed to work with the Chef Repo workflow. diff --git a/lib/chef-dk/skeletons/code_generator/files/default/repo/policies/README.md b/lib/chef-dk/skeletons/code_generator/files/default/repo/policies/README.md new file mode 100644 index 000000000..3a24ac1ed --- /dev/null +++ b/lib/chef-dk/skeletons/code_generator/files/default/repo/policies/README.md @@ -0,0 +1,24 @@ +Create policyfiles here. When using a chef-repo, give your policyfiles +the same filename as the name set in the policyfile itself, and use the +`.rb` file extension. + +Compile the policy with a command like this: + +``` +chef install policies/my-app-frontend.rb +``` + +This will create a lockfile `policies/my-app-frontend.lock.json`. + +To update locked dependencies, run `chef update` like this: + +``` +chef update policies/my-app-fronend.rb +``` + +You can upload the policy (with associated cookbooks) to the server +using a command like: + +``` +chef push staging policies/my-app-frontend.rb +``` diff --git a/lib/chef-dk/skeletons/code_generator/recipes/repo.rb b/lib/chef-dk/skeletons/code_generator/recipes/repo.rb index 90af03b0e..9301cb128 100644 --- a/lib/chef-dk/skeletons/code_generator/recipes/repo.rb +++ b/lib/chef-dk/skeletons/code_generator/recipes/repo.rb @@ -10,6 +10,10 @@ helpers(ChefDK::Generator::TemplateHelper) end +cookbook_file "#{repo_dir}/.chef-repo.txt" do + source "repo/dot-chef-repo.txt" +end + cookbook_file "#{repo_dir}/README.md" do source "repo/README.md" end @@ -18,7 +22,11 @@ source "chefignore" end -%w{cookbooks data_bags environments roles}.each do |tlo| +# By default, we now create a policies directory and don't create a roles or +# environments directory. The skeleton files for those still exist, so just add +# roles and environments to the array here to generate a repo with these +# directories. +%w{cookbooks data_bags policies}.each do |tlo| remote_directory "#{repo_dir}/#{tlo}" do source "repo/#{tlo}" end diff --git a/lib/chef-dk/skeletons/code_generator/templates/default/Policyfile.rb.erb b/lib/chef-dk/skeletons/code_generator/templates/default/Policyfile.rb.erb index 4a716a6df..0166fdc38 100644 --- a/lib/chef-dk/skeletons/code_generator/templates/default/Policyfile.rb.erb +++ b/lib/chef-dk/skeletons/code_generator/templates/default/Policyfile.rb.erb @@ -6,6 +6,11 @@ # A name that describes what the system you're building with Chef does. name "<%= policy_name %>" +<% if include_chef_repo_source %> +# This lets you source cookbooks from your chef-repo. +default_source :chef_repo, "../" + +<% end -%> # Where to find external cookbooks: default_source :supermarket diff --git a/spec/unit/command/generator_commands/policyfile_spec.rb b/spec/unit/command/generator_commands/policyfile_spec.rb index f81cbcfce..67786e303 100644 --- a/spec/unit/command/generator_commands/policyfile_spec.rb +++ b/spec/unit/command/generator_commands/policyfile_spec.rb @@ -107,6 +107,106 @@ def generator_context end + context "when the current working directory is a chef repo" do + + let(:chef_repo_dot_txt) { File.join(tempdir, ".chef-repo.txt") } + + let(:policies_dir) { File.join(tempdir, "policies") } + + let(:expected_policyfile_content) do + <<-POLICYFILE_RB +# Policyfile.rb - Describe how you want Chef to build your system. +# +# For more information on the Policyfile feature, visit +# https://github.com/opscode/chef-dk/blob/master/POLICYFILE_README.md + +# A name that describes what the system you're building with Chef does. +name "my-app-frontend" + +# This lets you source cookbooks from your chef-repo. +default_source :chef_repo, "../" + +# Where to find external cookbooks: +default_source :supermarket + +# run_list: chef-client will run these recipes in the order specified. +run_list "my-app-frontend::default" + +# Specify a custom source for a single cookbook: +# cookbook "example_cookbook", path: "../cookbooks/example_cookbook" +POLICYFILE_RB + end + + before do + FileUtils.touch(chef_repo_dot_txt) + FileUtils.mkdir(policies_dir) + end + + context "when ARGV is empty" do + + let(:argv) { [] } + + it "errors and explains a policy name is required when using a chef-repo" do + Dir.chdir(tempdir) do + expect(generator.run).to eq(1) + end + expect(File).to_not exist(File.join(tempdir, "Policyfile.rb")) + expected_error = "ERROR: You must give a policy name when generating a policy in a chef-repo." + expect(stderr).to include(expected_error) + end + + end + + context "when ARGV is a single name with no path separators" do + + let(:argv) { ["my-app-frontend"] } + + let(:expected_policyfile_path) { File.join(policies_dir, "my-app-frontend.rb") } + + before do + Dir.chdir(tempdir) do + expect(generator.run).to eq(0) + end + end + + it "creates the policy under the policies/ directory" do + expect(File).to exist(expected_policyfile_path) + end + + it "adds chef_repo as a default source and uses argv for the policy name" do + expect(IO.read(expected_policyfile_path)).to eq(expected_policyfile_content) + end + + end + + context "when ARGV looks like a path" do + + let(:other_policy_dir) { File.join(tempdir, "other-policies") } + + let(:expected_policyfile_path) { File.join(other_policy_dir, "my-app-frontend.rb") } + + let(:argv) { [ "other-policies/my-app-frontend" ] } + + before do + FileUtils.mkdir(other_policy_dir) + + Dir.chdir(tempdir) do + expect(generator.run).to eq(0) + end + end + + it "creates the policy in the specified path" do + expect(File).to exist(expected_policyfile_path) + end + + it "adds chef_repo as a default source" do + expect(IO.read(expected_policyfile_path)).to eq(expected_policyfile_content) + end + + end + + end + context "when ARGV has too many arguments" do let(:argv) { %w{ foo bar baz } } diff --git a/spec/unit/command/generator_commands/repo_spec.rb b/spec/unit/command/generator_commands/repo_spec.rb index 23e00d9fd..707e313d0 100644 --- a/spec/unit/command/generator_commands/repo_spec.rb +++ b/spec/unit/command/generator_commands/repo_spec.rb @@ -181,6 +181,15 @@ def generator_context end end + describe ".chef-repo.txt" do + + let(:file) { ".chef-repo.txt" } + + it "explains why it's there" do + expect(file_contents).to include("This file gives ChefDK's generators a hint") + end + end + describe "cookbooks" do describe "README.md" do let(:file) { "cookbooks/README.md" } @@ -241,41 +250,24 @@ def generator_context end end - describe "environments" do + describe "policies" do describe "README.md" do - let(:file) { "environments/README.md" } - - it "has the right contents" do - expect(file_contents).to match(/Create environments here, in either the Role Ruby DSL \(\.rb\) or JSON \(\.json\) files\./) + let(:file) { "policies/README.md" } + + let(:expected_content) do + <<-README +Create policyfiles here. When using a chef-repo, give your policyfiles +the same filename as the name set in the policyfile itself, and use the +`.rb` file extension. +README end - end - - describe "example.json" do - let(:file) { "environments/example.json" } it "has the right contents" do - expect(file_contents).to match(/"description": "This is an example environment defined as JSON"/) + expect(file_contents).to include(expected_content) end end end - describe "roles" do - describe "README.md" do - let(:file) { "roles/README.md" } - - it "has the right contents" do - expect(file_contents).to match(/Create roles here, in either the Role Ruby DSL \(\.rb\) or JSON \(\.json\) files\./) - end - end - - describe "example.json" do - let(:file) { "roles/example.json" } - - it "has the right contents" do - expect(file_contents).to match(/"description": "This is an example role defined as JSON"/) - end - end - end end end end diff --git a/spec/unit/policyfile_demands_spec.rb b/spec/unit/policyfile_demands_spec.rb index c2d7da09e..163e41a3f 100644 --- a/spec/unit/policyfile_demands_spec.rb +++ b/spec/unit/policyfile_demands_spec.rb @@ -834,8 +834,10 @@ end it "raises an error describing the conflict" do + repo_path = File.expand_path("path/to/repo") + expected_err = <<-ERROR -Source supermarket(https://supermarket.chef.io) and chef_repo(path/to/repo) contain conflicting cookbooks: +Source supermarket(https://supermarket.chef.io) and chef_repo(#{repo_path}) contain conflicting cookbooks: - remote-cb - remote-cb-two ERROR diff --git a/spec/unit/policyfile_evaluation_spec.rb b/spec/unit/policyfile_evaluation_spec.rb index 18a99c1c6..e068d1278 100644 --- a/spec/unit/policyfile_evaluation_spec.rb +++ b/spec/unit/policyfile_evaluation_spec.rb @@ -262,6 +262,27 @@ expect(policyfile.default_source).to eq(expected) end + context "when the path to the chef repo is relative" do + + let(:policyfile_rb) do + <<-EOH + run_list "foo", "bar" + default_source :chef_repo, "../cookbooks" + EOH + end + + # storage_config is created with path to Policyfile.rb in CWD + let(:expected_path) { File.expand_path("../cookbooks") } + + it "sets the repo path relative to the directory the policyfile is in" do + expect(policyfile.errors).to eq([]) + expect(policyfile.default_source.size).to eq(1) + expect(policyfile.default_source.first).to be_a(ChefDK::Policyfile::ChefRepoCookbookSource) + expect(policyfile.default_source.first.path).to eq(expected_path) + end + + end + end context "with multiple default sources" do