berkshelf 3.0.0.beta3 → 3.0.0.beta4

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 (57) hide show
  1. checksums.yaml +4 -4
  2. data/.ruby-version +1 -1
  3. data/.travis.yml +1 -0
  4. data/CHANGELOG.md +12 -2
  5. data/CONTRIBUTING.md +53 -53
  6. data/Gemfile +1 -1
  7. data/Guardfile +12 -6
  8. data/PLUGINS.md +2 -2
  9. data/README.md +7 -11
  10. data/berkshelf.gemspec +1 -1
  11. data/features/commands/apply.feature +15 -4
  12. data/features/commands/cookbook.feature +1 -1
  13. data/features/commands/install.feature +85 -15
  14. data/features/commands/list.feature +35 -2
  15. data/features/commands/outdated.feature +3 -3
  16. data/features/commands/package.feature +1 -0
  17. data/features/commands/show.feature +3 -3
  18. data/features/commands/update.feature +1 -0
  19. data/features/commands/upload.feature +28 -4
  20. data/features/json_formatter.feature +2 -0
  21. data/features/lockfile.feature +1 -0
  22. data/features/support/env.rb +8 -4
  23. data/generator_files/Gemfile.erb +9 -0
  24. data/generator_files/README.md.erb +15 -33
  25. data/generator_files/Vagrantfile.erb +6 -3
  26. data/lib/berkshelf.rb +32 -2
  27. data/lib/berkshelf/berksfile.rb +59 -83
  28. data/lib/berkshelf/cli.rb +15 -67
  29. data/lib/berkshelf/community_rest.rb +18 -2
  30. data/lib/berkshelf/dependency.rb +6 -4
  31. data/lib/berkshelf/downloader.rb +3 -0
  32. data/lib/berkshelf/errors.rb +15 -0
  33. data/lib/berkshelf/formatters.rb +1 -0
  34. data/lib/berkshelf/formatters/human_readable.rb +27 -4
  35. data/lib/berkshelf/formatters/json.rb +21 -5
  36. data/lib/berkshelf/git.rb +4 -0
  37. data/lib/berkshelf/installer.rb +3 -1
  38. data/lib/berkshelf/locations/git_location.rb +22 -10
  39. data/lib/berkshelf/locations/github_location.rb +3 -7
  40. data/lib/berkshelf/locations/path_location.rb +0 -19
  41. data/lib/berkshelf/lockfile.rb +66 -4
  42. data/lib/berkshelf/mixin/logging.rb +1 -1
  43. data/lib/berkshelf/version.rb +1 -1
  44. data/spec/spec_helper.rb +8 -4
  45. data/spec/support/git.rb +9 -9
  46. data/spec/support/mercurial.rb +9 -9
  47. data/spec/unit/berkshelf/api_client_spec.rb +1 -1
  48. data/spec/unit/berkshelf/berksfile_spec.rb +36 -59
  49. data/spec/unit/berkshelf/cli_spec.rb +16 -0
  50. data/spec/unit/berkshelf/config_spec.rb +1 -1
  51. data/spec/unit/berkshelf/cookbook_generator_spec.rb +4 -6
  52. data/spec/unit/berkshelf/locations/git_location_spec.rb +1 -1
  53. data/spec/unit/berkshelf/lockfile_spec.rb +81 -2
  54. data/spec/unit/berkshelf/resolver/graph_spec.rb +1 -1
  55. data/spec/unit/berkshelf/ui_spec.rb +4 -4
  56. metadata +7 -7
  57. data/features/commands/configure.feature +0 -89
@@ -111,56 +111,6 @@ module Berkshelf
111
111
  aliases: '-d',
112
112
  default: false
113
113
 
114
- method_option :force,
115
- type: :boolean,
116
- default: false,
117
- desc: 'create a new configuration file even if one already exists.'
118
- method_option :path,
119
- type: :string,
120
- default: Berkshelf.config.path,
121
- desc: 'The path to save the configuration file'
122
- desc 'configure', 'Create a new Berkshelf configuration file'
123
- def configure
124
- path = File.expand_path(options[:path])
125
-
126
- if File.exist?(path) && !options[:force]
127
- raise Berkshelf::ConfigExists, 'A configuration file already exists. Re-run with the --force flag if you wish to overwrite it.'
128
- end
129
-
130
- config = Berkshelf::Config.new(path)
131
-
132
- [
133
- 'chef.chef_server_url',
134
- 'chef.node_name',
135
- 'chef.client_key',
136
- 'chef.validation_client_name',
137
- 'chef.validation_key_path',
138
- 'vagrant.vm.box',
139
- 'vagrant.vm.box_url',
140
- ].each do |attribute|
141
- default = config.get_attribute(attribute)
142
-
143
- message = "Enter value for #{attribute}"
144
- message << " (default: '#{default}')" if default
145
- message << ": "
146
-
147
- input = Berkshelf.ui.ask(message)
148
-
149
- if input.present?
150
- config.set_attribute(attribute, input)
151
- end
152
- end
153
-
154
- unless config.valid?
155
- raise InvalidConfiguration.new(config.errors)
156
- end
157
-
158
- config.save
159
- Berkshelf.config = config
160
-
161
- Berkshelf.formatter.msg "Config written to: '#{path}'"
162
- end
163
-
164
114
  method_option :except,
165
115
  type: :array,
166
116
  desc: 'Exclude cookbooks that are in these groups.',
@@ -252,26 +202,31 @@ module Berkshelf
252
202
 
253
203
  options[:cookbooks] = cookbook_names
254
204
  options[:freeze] = !options[:no_freeze]
205
+ options[:validate] = false if options[:skip_syntax_check]
255
206
 
256
207
  berksfile.upload(options.symbolize_keys)
257
208
  end
258
209
 
259
- method_option :berksfile,
210
+ method_option :lockfile,
260
211
  type: :string,
261
- default: Berkshelf::DEFAULT_FILENAME,
262
- desc: 'Path to a Berksfile to operate off of.',
212
+ default: Berkshelf::Lockfile::DEFAULT_FILENAME,
213
+ desc: 'Path to a Berksfile.lock to operate off of.',
263
214
  aliases: '-b',
264
215
  banner: 'PATH'
265
216
  method_option :ssl_verify,
266
217
  type: :boolean,
267
218
  default: nil,
268
219
  desc: 'Disable/Enable SSL verification when locking cookbooks.'
269
- desc 'apply ENVIRONMENT', 'Apply the cookbook version locks from Berksfile.lock to a Chef environment'
220
+ desc 'apply ENVIRONMENT', 'Apply version locks from Berksfile.lock to a Chef environment'
270
221
  def apply(environment_name)
271
- berksfile = Berkshelf::Berksfile.from_file(options[:berksfile])
222
+ unless File.exist?(options[:lockfile])
223
+ raise LockfileNotFound, "No lockfile found at #{options[:lockfile]}"
224
+ end
225
+
226
+ lockfile = Berkshelf::Lockfile.from_file(options[:lockfile])
272
227
  lock_options = Hash[options].symbolize_keys
273
228
 
274
- berksfile.apply(environment_name, lock_options)
229
+ lockfile.apply(environment_name, lock_options)
275
230
  end
276
231
 
277
232
  method_option :berksfile,
@@ -321,15 +276,8 @@ module Berkshelf
321
276
  banner: 'PATH'
322
277
  desc 'list', 'List all cookbooks and their dependencies specified by your Berksfile'
323
278
  def list
324
- berksfile = Berksfile.from_file(options[:berksfile])
325
- dependencies = Berkshelf.ui.mute { berksfile.install }.sort
326
-
327
- if dependencies.empty?
328
- Berkshelf.formatter.msg 'There are no cookbooks installed by your Berksfile'
329
- else
330
- Berkshelf.formatter.msg 'Cookbooks installed by your Berksfile:'
331
- print_list(dependencies)
332
- end
279
+ berksfile = Berksfile.from_file(options[:berksfile])
280
+ Berkshelf.formatter.list(berksfile.list)
333
281
  end
334
282
 
335
283
  method_option :berksfile,
@@ -341,7 +289,7 @@ module Berkshelf
341
289
  desc "show [COOKBOOK]", "Display name, author, copyright, and dependency information about a cookbook"
342
290
  def show(name)
343
291
  berksfile = Berksfile.from_file(options[:berksfile])
344
- cookbook = berksfile.retrieve_locked(name)
292
+ cookbook = berksfile.retrieve_locked(berksfile.find!(name))
345
293
  Berkshelf.formatter.show(cookbook)
346
294
  end
347
295
 
@@ -381,7 +329,7 @@ module Berkshelf
381
329
  type: :boolean,
382
330
  desc: 'Do not apply the chefignore to the packaged contents',
383
331
  default: false
384
- desc "package [COOKBOOK]", "Package a cookbook and it's dependencies as a tarball"
332
+ desc "package [COOKBOOK]", "Package a cookbook and its dependencies as a tarball"
385
333
  def package(name = nil)
386
334
  berksfile = Berkshelf::Berksfile.from_file(options[:berksfile])
387
335
  berksfile.package(name, options)
@@ -111,7 +111,7 @@ module Berkshelf
111
111
  end
112
112
  end
113
113
 
114
- # Returns the latest version of the cookbook and it's download link.
114
+ # Returns the latest version of the cookbook and its download link.
115
115
  #
116
116
  # @return [String]
117
117
  def latest_version(name)
@@ -166,7 +166,7 @@ module Berkshelf
166
166
  local.binmode
167
167
 
168
168
  retryable(tries: retries, on: OpenURI::HTTPError, sleep: retry_interval) do
169
- open(target, 'rb', headers) do |remote|
169
+ open(target, 'rb', open_uri_options) do |remote|
170
170
  local.write(remote.read)
171
171
  end
172
172
  end
@@ -175,5 +175,21 @@ module Berkshelf
175
175
  ensure
176
176
  local.close(false) unless local.nil?
177
177
  end
178
+
179
+ private
180
+
181
+ def open_uri_options
182
+ options = {}
183
+ options.merge!(headers)
184
+ options.merge!(open_uri_proxy_options)
185
+ end
186
+
187
+ def open_uri_proxy_options
188
+ if proxy && proxy[:user] && proxy[:password]
189
+ {proxy_http_basic_authentication: [ proxy[:uri], proxy[:user], proxy[:password] ]}
190
+ else
191
+ {}
192
+ end
193
+ end
178
194
  end
179
195
  end
@@ -203,8 +203,12 @@ module Berkshelf
203
203
  SCM_LOCATIONS.include?(location.class.location_key)
204
204
  end
205
205
 
206
+ def <=>(other)
207
+ [self.name, self.version_constraint] <=> [other.name, other.version_constraint]
208
+ end
209
+
206
210
  def to_s
207
- "#{name} (#{version_constraint})"
211
+ "#{name} (#{locked_version || version_constraint})"
208
212
  end
209
213
 
210
214
  def inspect
@@ -218,9 +222,7 @@ module Berkshelf
218
222
 
219
223
  def to_hash
220
224
  {}.tap do |h|
221
- unless location.kind_of?(PathLocation)
222
- h[:locked_version] = locked_version.to_s
223
- end
225
+ h[:locked_version] = locked_version.to_s
224
226
 
225
227
  if location.kind_of?(PathLocation)
226
228
  h[:path] = location.relative_path(berksfile.filepath)
@@ -61,6 +61,9 @@ module Berkshelf
61
61
  verify: Berkshelf::Config.instance.ssl.verify
62
62
  }
63
63
  }
64
+ # @todo Something scary going on here - getting an instance of Kitchen::Logger from test-kitchen
65
+ # https://github.com/opscode/test-kitchen/blob/master/lib/kitchen.rb#L99
66
+ Celluloid.logger = nil unless ENV["DEBUG_CELLULOID"]
64
67
  Ridley.open(credentials) { |r| r.cookbook.download(name, version) }
65
68
  else
66
69
  raise RuntimeError, "unknown location type #{remote_cookbook.location_type}"
@@ -144,6 +144,19 @@ module Berkshelf
144
144
  end
145
145
  end
146
146
 
147
+ class InvalidGitHubIdentifier < BerkshelfError
148
+ status_code(110)
149
+
150
+ # @param [String] repo_identifier
151
+ def initialize(repo_identifier)
152
+ @repo_identifier = repo_identifier
153
+ end
154
+
155
+ def to_s
156
+ "'#{@repo_identifier}' is not a valid GitHub identifier - should not end in '.git'"
157
+ end
158
+ end
159
+
147
160
  class UnknownGitHubProtocol < BerkshelfError
148
161
  status_code(110)
149
162
 
@@ -489,4 +502,6 @@ module Berkshelf
489
502
  "#{@path} does not appear to be a valid cookbook. Does it have a `metadata.rb`?"
490
503
  end
491
504
  end
505
+
506
+ class InvalidLockFile < BerkshelfError; status_code(142); end
492
507
  end
@@ -82,6 +82,7 @@ module Berkshelf
82
82
  formatter_methods :error,
83
83
  :fetch,
84
84
  :install,
85
+ :list,
85
86
  :msg,
86
87
  :outdated,
87
88
  :package,
@@ -19,9 +19,18 @@ module Berkshelf
19
19
  #
20
20
  # @param [String] cookbook
21
21
  # @param [String] version
22
- # @param [Berkshelf::Dependency] dependency
23
- def install(cookbook, version, dependency)
24
- Berkshelf.ui.info "Installing #{cookbook} (#{version})"
22
+ # @option options [String] :api_source
23
+ # the berkshelf-api source url
24
+ # @option options [String] :location_path
25
+ # the chef server url for a cookbook's location
26
+ def install(cookbook, version, options = {})
27
+ info_message = "Installing #{cookbook} (#{version})"
28
+
29
+ if options.has_key?(:api_source) && options.has_key?(:location_path)
30
+ api_source = options[:api_source]
31
+ info_message << " from #{options[:location_path]} (via #{URI(api_source).host})" unless api_source == Berkshelf::Berksfile::DEFAULT_API_URL
32
+ end
33
+ Berkshelf.ui.info info_message
25
34
  end
26
35
 
27
36
  # Output a Cookbook use message using {Berkshelf.ui}
@@ -48,7 +57,7 @@ module Berkshelf
48
57
  # @param [Berkshelf::CachedCookbook] cookbook
49
58
  # @param [Ridley::Connection] conn
50
59
  def skip(cookbook, conn)
51
- Berkshelf.ui.info "Skipping #{cookbook.cookbook_name} (#{cookbook.version}) (already uploaded)"
60
+ Berkshelf.ui.info "Skipping #{cookbook.cookbook_name} (#{cookbook.version}) (frozen)"
52
61
  end
53
62
 
54
63
  # Output a list of outdated cookbooks and the most recent version
@@ -77,6 +86,20 @@ module Berkshelf
77
86
  Berkshelf.ui.info "Cookbook(s) packaged to #{destination}!"
78
87
  end
79
88
 
89
+ # Output a list of cookbooks using {Berkshelf.ui}
90
+ #
91
+ # @param [Hash<Dependency, CachedCookbook>] list
92
+ def list(list)
93
+ if list.empty?
94
+ Berkshelf.ui.info "There are no cookbooks installed by your Berksfile"
95
+ else
96
+ Berkshelf.ui.info "Cookbooks installed by your Berksfile:"
97
+ list.each do |dependency, cookbook|
98
+ Berkshelf.ui.info(" * #{cookbook.cookbook_name} (#{cookbook.version})")
99
+ end
100
+ end
101
+ end
102
+
80
103
  # Output Cookbook info message using {Berkshelf.ui}
81
104
  #
82
105
  # @param [CachedCookbook] cookbook
@@ -41,14 +41,17 @@ module Berkshelf
41
41
  #
42
42
  # @param [String] cookbook
43
43
  # @param [String] version
44
- # @param [~Location] location
45
- def install(cookbook, version, location)
44
+ # @option options [String] :api_source
45
+ # the berkshelf-api source url
46
+ # @option options [String] :location_path
47
+ # the chef server url for a cookbook's location
48
+ def install(cookbook, version, options = {})
46
49
  cookbooks[cookbook] ||= {}
47
50
  cookbooks[cookbook][:version] = version
48
51
 
49
- if location && location.is_a?(PathLocation)
50
- cookbooks[cookbook][:metadata] = true if location.metadata?
51
- cookbooks[cookbook][:location] = location.relative_path
52
+ if options.has_key?(:api_source) && options.has_key?(:location_path)
53
+ cookbooks[cookbook][:api_source] = options[:api_source] unless options[:api_source] == Berkshelf::Berksfile::DEFAULT_API_URL
54
+ cookbooks[cookbook][:location_path] = options[:location_path] unless options[:api_source] == Berkshelf::Berksfile::DEFAULT_API_URL
52
55
  end
53
56
  end
54
57
 
@@ -115,6 +118,19 @@ module Berkshelf
115
118
  cookbooks[cookbook][:destination] = destination
116
119
  end
117
120
 
121
+ # Output a list of cookbooks to delayed output
122
+ #
123
+ # @param [Hash<Dependency, CachedCookbook>] list
124
+ def list(list)
125
+ list.each do |dependency, cookbook|
126
+ cookbooks[cookbook.cookbook_name] ||= {}
127
+ cookbooks[cookbook.cookbook_name][:version] = cookbook.version
128
+ if dependency.location
129
+ cookbooks[cookbook.cookbook_name][:location] = dependency.location
130
+ end
131
+ end
132
+ end
133
+
118
134
  # Output Cookbook info entry to delayed output
119
135
  #
120
136
  # @param [CachedCookbook] cookbook
@@ -41,6 +41,10 @@ module Berkshelf
41
41
  # @return [String]
42
42
  # the destination the URI was cloned to
43
43
  def clone(uri, destination = Dir.mktmpdir)
44
+ if File::ALT_SEPARATOR
45
+ destination = destination.to_s.gsub(File::SEPARATOR, File::ALT_SEPARATOR)
46
+ end
47
+
44
48
  git('clone', uri, destination.to_s)
45
49
 
46
50
  destination
@@ -47,7 +47,9 @@ module Berkshelf
47
47
  Berkshelf.formatter.use(dependency.name, dependency.cached_cookbook.version, dependency.location)
48
48
  dependency.cached_cookbook
49
49
  else
50
- Berkshelf.formatter.install(name, version, dependency)
50
+ source = berksfile.sources.find { |source| source.cookbook(name, version) }
51
+ remote_cookbook = source.cookbook(name, version)
52
+ Berkshelf.formatter.install(name, version, api_source: source.to_s, location_path: remote_cookbook.location_path)
51
53
  temp_filepath = downloader.download(name, version)
52
54
  CookbookStore.import(name, version, temp_filepath)
53
55
  end
@@ -28,13 +28,23 @@ module Berkshelf
28
28
  def initialize(dependency, options = {})
29
29
  super
30
30
  @uri = options[:git]
31
- @branch = options[:branch] || options[:tag] || 'master'
32
31
  @ref = options[:ref]
32
+ @branch = options[:branch] || options[:tag] || "master" unless ref
33
+ @sha = ref
33
34
  @rel = options[:rel]
34
35
 
35
36
  Git.validate_uri!(@uri)
36
37
  end
37
38
 
39
+ def checkout_info
40
+ if @sha
41
+ kind, rev = "ref", @sha
42
+ else
43
+ kind, rev = "branch", branch
44
+ end
45
+ { :kind => kind, :rev => rev }
46
+ end
47
+
38
48
  # @param [#to_s] destination
39
49
  #
40
50
  # @return [Berkshelf::CachedCookbook]
@@ -46,14 +56,12 @@ module Berkshelf
46
56
  return local_revision(destination)
47
57
  end
48
58
 
49
- Berkshelf::Git.checkout(clone, ref || branch) if ref || branch
59
+ Berkshelf::Git.checkout(clone, ref || checkout_info[:rev])
50
60
  @ref = Berkshelf::Git.rev_parse(clone)
51
61
 
52
62
  tmp_path = rel ? File.join(clone, rel) : clone
53
63
  unless File.chef_cookbook?(tmp_path)
54
- msg = "Cookbook '#{dependency.name}' not found at git: #{uri}"
55
- msg << " with branch '#{branch}'" if branch
56
- msg << " with ref '#{ref}'" if ref
64
+ msg = "Cookbook '#{dependency.name}' not found at git: #{to_display}"
57
65
  msg << " at path '#{rel}'" if rel
58
66
  raise CookbookNotFound, msg
59
67
  end
@@ -76,21 +84,25 @@ module Berkshelf
76
84
  end
77
85
 
78
86
  def to_s
79
- s = "#{self.class.location_key}: '#{uri}'"
80
- s << " with branch: '#{branch}'" if branch
81
- s << " at ref: '#{ref}'" if ref
82
- s
87
+ "#{self.class.location_key}: #{to_display}"
83
88
  end
84
89
 
85
90
  private
86
91
 
92
+ def to_display
93
+ info = checkout_info
94
+ s = "'#{uri}' with #{info[:kind]}: '#{info[:rev]}'"
95
+ s << " at ref: '#{ref}'" if ref && (info[:kind] != "ref" || ref != info[:rev])
96
+ s
97
+ end
98
+
87
99
  def git
88
100
  @git ||= Berkshelf::Git.new(uri)
89
101
  end
90
102
 
91
103
  def clone
92
104
  tmp_clone = File.join(self.class.tmpdir, uri.gsub(/[\/:]/,'-'))
93
-
105
+ FileUtils.mkdir_p(File.join(File.split(tmp_clone).shift))
94
106
  unless File.exists?(tmp_clone)
95
107
  Berkshelf::Git.clone(uri, tmp_clone)
96
108
  end
@@ -19,6 +19,9 @@ module Berkshelf
19
19
  # the protocol with which to communicate with GitHub
20
20
  def initialize(dependency, options = {})
21
21
  @repo_identifier = options.delete(:github)
22
+ if repo_identifier.end_with?(".git")
23
+ raise InvalidGitHubIdentifier.new(repo_identifier)
24
+ end
22
25
  @protocol = (options.delete(:protocol) || DEFAULT_PROTOCOL).to_sym
23
26
  options[:git] = github_url
24
27
  super
@@ -43,13 +46,6 @@ module Berkshelf
43
46
  end
44
47
  end
45
48
 
46
- def to_s
47
- s = "#{self.class.location_key}: '#{repo_identifier}'"
48
- s << " with branch: '#{branch}'" if branch
49
- s << " over protocol: '#{protocol}'"
50
- s
51
- end
52
-
53
49
  private
54
50
 
55
51
  def default_protocol?