berkshelf 0.4.0.rc1 → 0.4.0.rc2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. data/bin/berks +6 -1
  2. data/features/cookbook_command.feature +39 -0
  3. data/features/step_definitions/cli_steps.rb +8 -0
  4. data/features/step_definitions/filesystem_steps.rb +120 -1
  5. data/features/support/env.rb +1 -1
  6. data/lib/berkshelf.rb +16 -4
  7. data/lib/berkshelf/base_generator.rb +20 -0
  8. data/lib/berkshelf/berksfile.rb +18 -10
  9. data/lib/berkshelf/cli.rb +36 -18
  10. data/lib/berkshelf/cookbook_generator.rb +112 -0
  11. data/lib/berkshelf/cookbook_source/git_location.rb +1 -0
  12. data/lib/berkshelf/dsl.rb +7 -1
  13. data/lib/berkshelf/generator_files/Gemfile.erb +12 -0
  14. data/lib/berkshelf/generator_files/README.md.erb +13 -0
  15. data/lib/berkshelf/generator_files/Thorfile.erb +11 -0
  16. data/lib/berkshelf/generator_files/Vagrantfile.erb +63 -0
  17. data/lib/berkshelf/generator_files/chefignore +1 -0
  18. data/lib/berkshelf/generator_files/default_recipe.erb +6 -0
  19. data/lib/berkshelf/generator_files/gitignore.erb +6 -0
  20. data/lib/berkshelf/generator_files/licenses/apachev2.erb +13 -0
  21. data/lib/berkshelf/generator_files/licenses/gplv2.erb +15 -0
  22. data/lib/berkshelf/generator_files/licenses/gplv3.erb +14 -0
  23. data/lib/berkshelf/generator_files/licenses/mit.erb +20 -0
  24. data/lib/berkshelf/generator_files/licenses/reserved.erb +3 -0
  25. data/lib/berkshelf/generator_files/metadata.rb.erb +12 -0
  26. data/lib/berkshelf/init_generator.rb +69 -12
  27. data/lib/berkshelf/version.rb +1 -1
  28. data/spec/unit/berkshelf/berksfile_spec.rb +20 -20
  29. data/spec/unit/berkshelf/cookbook_generator_spec.rb +79 -0
  30. data/spec/unit/berkshelf/git_spec.rb +9 -9
  31. data/spec/unit/berkshelf/init_generator_spec.rb +107 -7
  32. metadata +20 -2
data/bin/berks CHANGED
@@ -2,4 +2,9 @@
2
2
  $:.push File.expand_path("../../lib", __FILE__)
3
3
  require 'berkshelf'
4
4
 
5
- Berkshelf::Cli.start
5
+ begin
6
+ Berkshelf::Cli.start
7
+ rescue Berkshelf::BerkshelfError => e
8
+ Berkshelf.ui.fatal e
9
+ exit e.status_code
10
+ end
@@ -0,0 +1,39 @@
1
+ Feature: cookbook command
2
+ As a Cookbook author
3
+ I want a way to quickly generate a Cookbook skeleton that contains supporting Berkshelf files
4
+ So I can quickly and automatically generate a Cookbook containing Berkshelf supporting files or other common supporting files
5
+
6
+ Scenario: creating a new cookbook skeleton
7
+ When I run the cookbook command to create "sparkle_motion"
8
+ Then I should have a new cookbook skeleton "sparkle_motion"
9
+ And the exit status should be 0
10
+
11
+ Scenario: creating a new cookbook skeleton with Vagrant support
12
+ When I run the cookbook command to create "sparkle_motion" with options:
13
+ | --vagrant |
14
+ Then I should have a new cookbook skeleton "sparkle_motion" with Vagrant support
15
+ And the exit status should be 0
16
+
17
+ Scenario: creating a new cookbook skeleton with Git support
18
+ When I run the cookbook command to create "sparkle_motion" with options:
19
+ | --git |
20
+ Then I should have a new cookbook skeleton "sparkle_motion" with Git support
21
+ And the exit status should be 0
22
+
23
+ Scenario: creating a new cookbook skeleton with Foodcritic support
24
+ When I run the cookbook command to create "sparkle_motion" with options:
25
+ | --foodcritic |
26
+ Then I should have a new cookbook skeleton "sparkle_motion" with Foodcritic support
27
+ And the exit status should be 0
28
+
29
+ Scenario: creating a new cookbook skeleton with SCMVersion support
30
+ When I run the cookbook command to create "sparkle_motion" with options:
31
+ | --scmversion |
32
+ Then I should have a new cookbook skeleton "sparkle_motion" with SCMVersion support
33
+ And the exit status should be 0
34
+
35
+ Scenario: creating a new cookbook skeleton without Bundler support
36
+ When I run the cookbook command to create "sparkle_motion" with options:
37
+ | --no-bundler |
38
+ Then I should have a new cookbook skeleton "sparkle_motion" without Bundler support
39
+ And the exit status should be 0
@@ -54,6 +54,14 @@ When /^I run the upload command$/ do
54
54
  run_simple(unescape("berks upload"), true)
55
55
  end
56
56
 
57
+ When /^I run the cookbook command to create "(.*?)"$/ do |name|
58
+ run_simple(unescape("berks cookbook #{name}"))
59
+ end
60
+
61
+ When /^I run the cookbook command to create "(.*?)" with options:$/ do |name, options|
62
+ run_simple(unescape("berks cookbook #{name} #{options.raw.join(" ")}"))
63
+ end
64
+
57
65
  Then /^the CLI should exit with the status code for error "(.*?)"$/ do |error_constant|
58
66
  exit_status = Berkshelf.const_get(error_constant).status_code
59
67
  assert_exit_status(exit_status)
@@ -32,7 +32,6 @@ Given /^the cookbook store contains a cookbook "(.*?)" "(.*?)" with dependencies
32
32
  generate_cookbook(cookbook_store, name, version, dependencies: dependencies.raw)
33
33
  end
34
34
 
35
-
36
35
  Then /^the cookbook store should have the cookbooks:$/ do |cookbooks|
37
36
  cookbooks.raw.each do |name, version|
38
37
  cookbook_store.should have_structure {
@@ -65,10 +64,130 @@ Then /^the cookbook store should not have the cookbooks:$/ do |cookbooks|
65
64
  end
66
65
  end
67
66
 
67
+ Then /^I should have the cookbook "(.*?)"$/ do |name|
68
+ Pathname.new(current_dir).join(name).should be_cookbook
69
+ end
70
+
71
+ Then /^I should have a new cookbook skeleton "(.*?)"$/ do |name|
72
+ cb_path = Pathname.new(current_dir).join(name)
73
+ cb_path.should have_structure {
74
+ directory "attributes"
75
+ directory "definitions"
76
+ directory "files" do
77
+ directory "default"
78
+ end
79
+ directory "libraries"
80
+ directory "providers"
81
+ directory "recipes" do
82
+ file "default.rb"
83
+ end
84
+ directory "resources"
85
+ directory "templates" do
86
+ directory "default"
87
+ end
88
+ file "README.md"
89
+ file "metadata.rb"
90
+ file "Berksfile" do
91
+ contains "metadata"
92
+ end
93
+ file "chefignore"
94
+ file "Berksfile"
95
+ file "Gemfile" do
96
+ contains "gem 'berkshelf'"
97
+ end
98
+ }
99
+ end
100
+
101
+ Then /^I should have a new cookbook skeleton "(.*?)" with Vagrant support$/ do |name|
102
+ steps %Q{ Then I should have a new cookbook skeleton "#{name}" }
103
+
104
+ cb_path = Pathname.new(current_dir).join(name)
105
+ cb_path.should have_structure {
106
+ file "Gemfile" do
107
+ contains "gem 'vagrant'"
108
+ end
109
+ file "Vagrantfile" do
110
+ contains "recipe[#{name}::default]"
111
+ end
112
+ directory "cookbooks" do
113
+ directory name
114
+ end
115
+ }
116
+ end
117
+
118
+ Then /^I should have a new cookbook skeleton "(.*?)" with Git support$/ do |name|
119
+ steps %Q{ Then I should have a new cookbook skeleton "#{name}" }
120
+
121
+ cb_path = Pathname.new(current_dir).join(name)
122
+ cb_path.should have_structure {
123
+ file ".gitignore"
124
+ }
125
+ end
126
+
127
+ Then /^I should have a new cookbook skeleton "(.*?)" with Foodcritic support$/ do |name|
128
+ steps %Q{ Then I should have a new cookbook skeleton "#{name}" }
129
+
130
+ cb_path = Pathname.new(current_dir).join(name)
131
+ cb_path.should have_structure {
132
+ file "Gemfile" do
133
+ contains "gem 'thor-foodcritic'"
134
+ end
135
+ file "Thorfile" do
136
+ contains "require 'thor/foodcritic'"
137
+ end
138
+ }
139
+ end
140
+
141
+ Then /^I should have a new cookbook skeleton "(.*?)" with SCMVersion support$/ do |name|
142
+ steps %Q{ Then I should have a new cookbook skeleton "#{name}" }
143
+
144
+ cb_path = Pathname.new(current_dir).join(name)
145
+ cb_path.should have_structure {
146
+ file "Gemfile" do
147
+ contains "gem 'thor-scmversion'"
148
+ end
149
+ file "Thorfile" do
150
+ contains "require 'thor/scmversion'"
151
+ end
152
+ }
153
+ end
154
+
155
+ Then /^I should have a new cookbook skeleton "(.*?)" without Bundler support$/ do |name|
156
+ cb_path = Pathname.new(current_dir).join(name)
157
+ cb_path.should have_structure {
158
+ directory "attributes"
159
+ directory "definitions"
160
+ directory "files" do
161
+ directory "default"
162
+ end
163
+ directory "libraries"
164
+ directory "providers"
165
+ directory "recipes" do
166
+ file "default.rb"
167
+ end
168
+ directory "resources"
169
+ directory "templates" do
170
+ directory "default"
171
+ end
172
+ file "README.md"
173
+ file "metadata.rb"
174
+ file "Berksfile" do
175
+ contains "metadata"
176
+ end
177
+ file "chefignore"
178
+ file "Berksfile"
179
+ no_file "Gemfile"
180
+ }
181
+ end
182
+
68
183
  Then /^the cookbook "(.*?)" should have the following files:$/ do |name, files|
69
184
  check_file_presence(files.raw.map{|file_row| File.join(name, file_row[0])}, true)
70
185
  end
71
186
 
187
+ Then /^the cookbook "(.*?)" should not have the following files:$/ do |name, files|
188
+ check_file_presence(files.raw.map{|file_row| File.join(name, file_row[0])}, false)
189
+ end
190
+
72
191
  Then /^the file "(.*?)" in the cookbook "(.*?)" should contain:$/ do |file_name, cookbook_name, content|
73
192
  Pathname.new(current_dir).join(cookbook_name).should have_structure {
74
193
  file "Berksfile" do
@@ -32,7 +32,7 @@ Spork.prefork do
32
32
  end
33
33
 
34
34
  def cookbook_store
35
- Pathname.new(ENV["BERKSHELF_PATH"])
35
+ Pathname.new(File.join(ENV["BERKSHELF_PATH"],"cookbooks"))
36
36
  end
37
37
 
38
38
  def clean_cookbook_store
@@ -24,7 +24,9 @@ module Berkshelf
24
24
  autoload :Git, 'berkshelf/git'
25
25
  autoload :Berksfile, 'berkshelf/berksfile'
26
26
  autoload :Lockfile, 'berkshelf/lockfile'
27
+ autoload :BaseGenerator, 'berkshelf/base_generator'
27
28
  autoload :InitGenerator, 'berkshelf/init_generator'
29
+ autoload :CookbookGenerator, 'berkshelf/cookbook_generator'
28
30
  autoload :CookbookSource, 'berkshelf/cookbook_source'
29
31
  autoload :CookbookStore, 'berkshelf/cookbook_store'
30
32
  autoload :CachedCookbook, 'berkshelf/cached_cookbook'
@@ -49,17 +51,27 @@ module Berkshelf
49
51
  @ui ||= Chef::Knife::UI.new(null_stream, null_stream, STDIN, {})
50
52
  end
51
53
 
52
- # Returns the filepath to the location Cookbooks will be downloaded to
53
- # or uploaded from. By default this is '~/.berkshelf' but can be overridden
54
- # by specifying a value for the ENV variable 'BERKSHELF_PATH'.
54
+ # Returns the filepath to the location Berskhelf will use for
55
+ # storage; temp files will go here, Cookbooks will be downloaded
56
+ # to or uploaded from here. By default this is '~/.berkshelf' but
57
+ # can be overridden by specifying a value for the ENV variable
58
+ # 'BERKSHELF_PATH'.
55
59
  #
56
60
  # @return [String]
57
61
  def berkshelf_path
58
62
  ENV["BERKSHELF_PATH"] || DEFAULT_STORE_PATH
59
63
  end
60
64
 
65
+ def tmp_dir
66
+ File.join(berkshelf_path, "tmp")
67
+ end
68
+
69
+ def cookbooks_dir
70
+ File.join(berkshelf_path, "cookbooks")
71
+ end
72
+
61
73
  def cookbook_store
62
- @cookbook_store ||= CookbookStore.new(berkshelf_path)
74
+ @cookbook_store ||= CookbookStore.new(cookbooks_dir)
63
75
  end
64
76
 
65
77
  def downloader
@@ -0,0 +1,20 @@
1
+ require 'thor/group'
2
+
3
+ module Berkshelf
4
+ # @author Jamie Winsor <jamie@vialstudios.com>
5
+ class BaseGenerator < Thor::Group
6
+ class << self
7
+ def source_root
8
+ File.expand_path(File.join(File.dirname(__FILE__), "generator_files"))
9
+ end
10
+ end
11
+
12
+ include Thor::Actions
13
+
14
+ private
15
+
16
+ def target
17
+ @target ||= Pathname.new(File.expand_path(path))
18
+ end
19
+ end
20
+ end
@@ -6,18 +6,12 @@ module Berkshelf
6
6
  class << self
7
7
  def from_file(file)
8
8
  content = File.read(file)
9
- read(content)
9
+ object = new(file)
10
+ object.load(content)
10
11
  rescue Errno::ENOENT => e
11
12
  raise BerksfileNotFound, "No Berksfile or Berksfile.lock found at: #{file}"
12
13
  end
13
14
 
14
- def read(content)
15
- object = new
16
- object.instance_eval(content)
17
-
18
- object
19
- end
20
-
21
15
  # @param [Array] sources
22
16
  # an array of sources to filter
23
17
  # @param [Array, Symbol] excluded
@@ -34,7 +28,10 @@ module Berkshelf
34
28
  end
35
29
  end
36
30
 
37
- def initialize
31
+ attr_reader :filepath
32
+
33
+ def initialize(path)
34
+ @filepath = path
38
35
  @sources = Hash.new
39
36
  end
40
37
 
@@ -190,7 +187,7 @@ module Berkshelf
190
187
  if descendant_directory?(path, Dir.pwd)
191
188
  actual_path = path
192
189
  FileUtils.rm_rf(actual_path)
193
- path = Dir.mktmpdir("berkshelf-")
190
+ path = File.join(Berkshelf.tmp_dir, "shims")
194
191
  end
195
192
 
196
193
  FileUtils.mkdir_p(path)
@@ -205,6 +202,17 @@ module Berkshelf
205
202
  end
206
203
  end
207
204
 
205
+ # Reload this instance of Berksfile with the given content. The content
206
+ # is a string that may contain terms from the included DSL.
207
+ #
208
+ # @param [String] content
209
+ #
210
+ # @return [Berksfile]
211
+ def load(content)
212
+ instance_eval(content)
213
+ self
214
+ end
215
+
208
216
  private
209
217
 
210
218
  def descendant_directory?(candidate, parent)
@@ -10,9 +10,6 @@ module Berkshelf
10
10
  ::Berkshelf.ui = Chef::Knife::UI.new(STDOUT, STDERR, STDIN, {})
11
11
  ::Berkshelf.config_path = @options[:config]
12
12
  @options = options.dup # unfreeze frozen options Hash from Thor
13
- rescue BerkshelfError => e
14
- Berkshelf.ui.fatal e
15
- exit e.status_code
16
13
  end
17
14
 
18
15
  namespace "berkshelf"
@@ -21,6 +18,7 @@ module Berkshelf
21
18
  map 'up' => :upload
22
19
  map 'ud' => :update
23
20
  map 'ver' => :version
21
+ map 'book' => :cookbook
24
22
 
25
23
  class_option :config,
26
24
  type: :string,
@@ -50,9 +48,6 @@ module Berkshelf
50
48
  def install
51
49
  berksfile = ::Berkshelf::Berksfile.from_file(options[:berksfile])
52
50
  berksfile.install(options)
53
- rescue BerkshelfError => e
54
- Berkshelf.ui.fatal e
55
- exit e.status_code
56
51
  end
57
52
 
58
53
  method_option :berksfile,
@@ -70,9 +65,6 @@ module Berkshelf
70
65
  def update
71
66
  Lockfile.remove!
72
67
  invoke :install
73
- rescue BerkshelfError => e
74
- Berkshelf.ui.fatal e
75
- exit e.status_code
76
68
  end
77
69
 
78
70
  method_option :berksfile,
@@ -99,25 +91,18 @@ module Berkshelf
99
91
  Berkshelf.load_config
100
92
  berksfile = ::Berkshelf::Berksfile.from_file(options[:berksfile])
101
93
  berksfile.upload(Chef::Config[:chef_server_url], options)
102
- rescue BerkshelfError => e
103
- Berkshelf.ui.fatal e
104
- exit e.status_code
105
94
  end
106
95
 
107
- desc "init [PATH]", "Prepare a local path to have it's Cookbook dependencies managed by Berkshelf."
96
+ desc "init [PATH]", "Prepare a local path to have its Cookbook dependencies managed by Berkshelf."
108
97
  def init(path = Dir.pwd)
109
98
  if File.chef_cookbook?(path)
110
99
  options[:chefignore] = true
111
100
  options[:metadata_entry] = true
112
101
  end
113
102
 
114
- generator = ::Berkshelf::InitGenerator.new([path], options)
115
- generator.invoke_all
103
+ ::Berkshelf::InitGenerator.new([path], options).invoke_all
116
104
 
117
105
  ::Berkshelf.ui.info "Successfully initialized"
118
- rescue BerkshelfError => e
119
- Berkshelf.ui.fatal e
120
- exit e.status_code
121
106
  end
122
107
 
123
108
  desc "version", "Display version and copyright information"
@@ -127,6 +112,39 @@ module Berkshelf
127
112
  Berkshelf.ui.info license
128
113
  end
129
114
 
115
+ method_option :vagrant,
116
+ type: :boolean,
117
+ desc: "Creates a Vagrantfile and dynamically change other generated files to support Vagrant"
118
+ method_option :git,
119
+ type: :boolean,
120
+ desc: "Creates additional git specific files if your project will be managed by git"
121
+ method_option :foodcritic,
122
+ type: :boolean,
123
+ desc: "Creates a Thorfile with Foodcritic support to lint test your cookbook"
124
+ method_option :scmversion,
125
+ type: :boolean,
126
+ desc: "Creates a Thorfile with SCMVersion support to manage versions for continuous integration"
127
+ method_option :no_bundler,
128
+ type: :boolean,
129
+ desc: "Skips generation of a Gemfile and other Bundler specific support"
130
+ method_option :license,
131
+ type: :string,
132
+ default: "reserved",
133
+ desc: "License for cookbook (apachev2, gplv2, gplv3, mit, reserved)",
134
+ aliases: "-L"
135
+ method_option :maintainer,
136
+ type: :string,
137
+ desc: "Name of cookbook maintainer",
138
+ aliases: "-m"
139
+ method_option :maintainer_email,
140
+ type: :string,
141
+ desc: "Email address of cookbook maintainer",
142
+ aliases: "-e"
143
+ desc "cookbook NAME", "Create a skeleton for a new cookbook"
144
+ def cookbook(name)
145
+ ::Berkshelf::CookbookGenerator.new([name, File.join(Dir.pwd, name)], options).invoke_all
146
+ end
147
+
130
148
  private
131
149
 
132
150
  def version_header
@@ -0,0 +1,112 @@
1
+ module Berkshelf
2
+ # @author Jamie Winsor <jamie@vialstudios.com>
3
+ class CookbookGenerator < BaseGenerator
4
+ argument :name,
5
+ type: :string,
6
+ required: true
7
+
8
+ argument :path,
9
+ type: :string,
10
+ required: true
11
+
12
+ class_option :vagrant,
13
+ type: :boolean,
14
+ default: false
15
+
16
+ class_option :git,
17
+ type: :boolean,
18
+ default: false
19
+
20
+ class_option :foodcritic,
21
+ type: :boolean,
22
+ default: false
23
+
24
+ class_option :scmversion,
25
+ type: :boolean,
26
+ default: false
27
+
28
+ class_option :no_bundler,
29
+ type: :boolean,
30
+ default: false
31
+
32
+ class_option :license,
33
+ type: :string,
34
+ default: "reserved"
35
+
36
+ class_option :maintainer,
37
+ type: :string,
38
+ default: "YOUR_NAME"
39
+
40
+ class_option :maintainer_email,
41
+ type: :string,
42
+ default: "YOUR_EMAIL"
43
+
44
+ def generate
45
+ empty_directory target.join("files/default")
46
+ empty_directory target.join("templates/default")
47
+ empty_directory target.join("attributes")
48
+ empty_directory target.join("definitions")
49
+ empty_directory target.join("libraries")
50
+ empty_directory target.join("providers")
51
+ empty_directory target.join("recipes")
52
+ empty_directory target.join("resources")
53
+
54
+ template "default_recipe.erb", target.join("recipes/default.rb")
55
+ template "metadata.rb.erb", target.join("metadata.rb")
56
+ template license_file, target.join("LICENSE")
57
+ template "README.md.erb", target.join("README.md")
58
+
59
+ ::Berkshelf::InitGenerator.new([target], options.merge(default_options)).invoke_all
60
+ end
61
+
62
+ private
63
+
64
+ def commented(content)
65
+ content.split("\n").collect { |s| "# #{s}" }.join("\n")
66
+ end
67
+
68
+ def license_name
69
+ case options[:license]
70
+ when "apachev2"; "Apache 2.0"
71
+ when "gplv2"; "GNU Public License 2.0"
72
+ when "gplv3"; "GNU Public License 3.0"
73
+ when "mit"; "MIT"
74
+ when "reserved"; "All rights reserved"
75
+ else
76
+ raise Berkshelf::InternalError, "Unknown license: '#{options[:license]}'"
77
+ end
78
+ end
79
+
80
+ def license
81
+ ERB.new(File.read(File.join(self.class.source_root, license_file))).result(binding)
82
+ end
83
+
84
+ def license_file
85
+ case options[:license]
86
+ when "apachev2"; "licenses/apachev2.erb"
87
+ when "gplv2"; "licenses/gplv2.erb"
88
+ when "gplv3"; "licenses/gplv3.erb"
89
+ when "mit"; "licenses/mit.erb"
90
+ when "reserved"; "licenses/reserved.erb"
91
+ else
92
+ raise Berkshelf::InternalError, "Unknown license: '#{options[:license]}'"
93
+ end
94
+ end
95
+
96
+ def copyright_year
97
+ Time.now.year
98
+ end
99
+
100
+ def maintainer
101
+ options[:maintainer]
102
+ end
103
+
104
+ def maintainer_email
105
+ options[:maintainer_email]
106
+ end
107
+
108
+ def default_options
109
+ { metadata_entry: true, chefignore: true, cookbook_name: name }
110
+ end
111
+ end
112
+ end