berkshelf 0.4.0.rc1 → 0.4.0.rc2

Sign up to get free protection for your applications and to get access to all the features.
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