berkshelf 3.0.0.beta3 → 3.0.0.beta4

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