berkshelf 0.4.0 → 0.5.0.rc1

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 (51) hide show
  1. data/berkshelf.gemspec +8 -3
  2. data/bin/berks +1 -1
  3. data/features/groups_install.feature +67 -0
  4. data/features/install.feature +1 -71
  5. data/features/json_formatter.feature +0 -17
  6. data/features/step_definitions/filesystem_steps.rb +1 -3
  7. data/features/update.feature +3 -6
  8. data/features/vendor_install.feature +20 -0
  9. data/generator_files/Vagrantfile.erb +25 -3
  10. data/lib/berkshelf.rb +27 -17
  11. data/lib/berkshelf/base_generator.rb +5 -0
  12. data/lib/berkshelf/berksfile.rb +82 -77
  13. data/lib/berkshelf/cli.rb +37 -26
  14. data/lib/berkshelf/cookbook_source.rb +14 -4
  15. data/lib/berkshelf/errors.rb +4 -1
  16. data/lib/berkshelf/formatters.rb +69 -5
  17. data/lib/berkshelf/formatters/human_readable.rb +26 -8
  18. data/lib/berkshelf/formatters/json.rb +51 -23
  19. data/lib/berkshelf/init_generator.rb +4 -4
  20. data/lib/berkshelf/location.rb +29 -6
  21. data/lib/berkshelf/locations/chef_api_location.rb +23 -5
  22. data/lib/berkshelf/locations/git_location.rb +13 -6
  23. data/lib/berkshelf/locations/path_location.rb +8 -4
  24. data/lib/berkshelf/locations/site_location.rb +9 -3
  25. data/lib/berkshelf/ui.rb +34 -0
  26. data/lib/berkshelf/uploader.rb +37 -103
  27. data/lib/berkshelf/vagrant.rb +65 -0
  28. data/lib/berkshelf/vagrant/action/clean.rb +24 -0
  29. data/lib/berkshelf/vagrant/action/install.rb +47 -0
  30. data/lib/berkshelf/vagrant/action/set_ui.rb +17 -0
  31. data/lib/berkshelf/vagrant/action/upload.rb +40 -0
  32. data/lib/berkshelf/vagrant/config.rb +70 -0
  33. data/lib/berkshelf/vagrant/middleware.rb +52 -0
  34. data/lib/berkshelf/version.rb +1 -1
  35. data/lib/thor/monkies.rb +3 -0
  36. data/lib/thor/monkies/hash_with_indifferent_access.rb +13 -0
  37. data/lib/vagrant_init.rb +2 -0
  38. data/spec/spec_helper.rb +5 -0
  39. data/spec/support/chef_api.rb +6 -1
  40. data/spec/unit/berkshelf/berksfile_spec.rb +93 -55
  41. data/spec/unit/berkshelf/formatters_spec.rb +99 -1
  42. data/spec/unit/berkshelf/init_generator_spec.rb +1 -1
  43. data/spec/unit/berkshelf/location_spec.rb +44 -7
  44. data/spec/unit/berkshelf/lockfile_spec.rb +2 -2
  45. data/spec/unit/berkshelf/uploader_spec.rb +2 -20
  46. data/spec/unit/berkshelf_spec.rb +2 -2
  47. metadata +100 -14
  48. data/features/without.feature +0 -26
  49. data/lib/berkshelf/core_ext/fileutils.rb +0 -90
  50. data/lib/berkshelf/core_ext/kernel.rb +0 -33
  51. data/spec/unit/berkshelf/core_ext/fileutils_spec.rb +0 -20
@@ -1,4 +1,5 @@
1
1
  require 'thor'
2
+ require 'thor/monkies'
2
3
  require 'berkshelf'
3
4
 
4
5
  module Berkshelf
@@ -11,12 +12,11 @@ module Berkshelf
11
12
  end
12
13
  end
13
14
 
14
- def initialize(*)
15
- super
16
- # JW TODO: Replace Chef::Knife::UI with our own UI class
17
- ::Berkshelf.ui = Chef::Knife::UI.new(STDOUT, STDERR, STDIN, {})
18
- ::Berkshelf.config_path = @options[:config]
19
- ::Berkshelf.set_format @options[:format]
15
+ def initialize(*args)
16
+ super(*args)
17
+ self.shell = Berkshelf.ui
18
+ Berkshelf.config_path = @options[:config]
19
+ Berkshelf.set_format @options[:format]
20
20
  @options = options.dup # unfreeze frozen options Hash from Thor
21
21
  end
22
22
 
@@ -36,33 +36,32 @@ module Berkshelf
36
36
  banner: "PATH"
37
37
  class_option :format,
38
38
  type: :string,
39
+ default: "human",
39
40
  desc: "Output format to use.",
40
41
  aliases: "-F",
41
42
  banner: "FORMAT"
42
43
 
43
- method_option :shims,
44
- type: :string,
45
- default: nil,
46
- lazy_default: File.join(Dir.pwd, "cookbooks"),
47
- desc: "Create a directory of shims pointing to Cookbook Versions.",
48
- banner: "PATH"
49
- method_option :without,
44
+ method_option :except,
50
45
  type: :array,
51
- default: Array.new,
52
46
  desc: "Exclude cookbooks that are in these groups.",
53
- aliases: "-w"
47
+ aliases: "-e"
48
+ method_option :only,
49
+ type: :array,
50
+ desc: "Only cookbooks that are in these groups.",
51
+ aliases: "-o"
54
52
  method_option :berksfile,
55
53
  type: :string,
56
54
  default: File.join(Dir.pwd, Berkshelf::DEFAULT_FILENAME),
57
55
  desc: "Path to a Berksfile to operate off of.",
58
56
  aliases: "-b",
59
57
  banner: "PATH"
58
+ method_option :path,
59
+ type: :string,
60
+ desc: "Path to install cookbooks to (i.e. vendor/cookbooks).",
61
+ aliases: "-p",
62
+ banner: "PATH"
60
63
  desc "install", "Install the Cookbooks specified by a Berksfile or a Berksfile.lock."
61
64
  def install
62
- unless options[:shims].nil?
63
- options[:shims] = File.expand_path(options[:shims])
64
- end
65
-
66
65
  berksfile = ::Berkshelf::Berksfile.from_file(options[:berksfile])
67
66
  berksfile.install(options)
68
67
  end
@@ -73,11 +72,14 @@ module Berkshelf
73
72
  desc: "Path to a Berksfile to operate off of.",
74
73
  aliases: "-b",
75
74
  banner: "PATH"
76
- method_option :without,
75
+ method_option :except,
77
76
  type: :array,
78
- default: Array.new,
79
77
  desc: "Exclude cookbooks that are in these groups.",
80
- aliases: "-w"
78
+ aliases: "-e"
79
+ method_option :only,
80
+ type: :array,
81
+ desc: "Only cookbooks that are in these groups.",
82
+ aliases: "-o"
81
83
  desc "update", "Update all Cookbooks and their dependencies specified by a Berksfile to their latest versions."
82
84
  def update
83
85
  Lockfile.remove!
@@ -90,11 +92,14 @@ module Berkshelf
90
92
  desc: "Path to a Berksfile to operate off of.",
91
93
  aliases: "-b",
92
94
  banner: "PATH"
93
- method_option :without,
95
+ method_option :except,
94
96
  type: :array,
95
- default: Array.new,
96
97
  desc: "Exclude cookbooks that are in these groups.",
97
- aliases: "-w"
98
+ aliases: "-e"
99
+ method_option :only,
100
+ type: :array,
101
+ desc: "Only cookbooks that are in these groups.",
102
+ aliases: "-o"
98
103
  method_option :freeze,
99
104
  type: :boolean,
100
105
  default: false,
@@ -107,7 +112,13 @@ module Berkshelf
107
112
  def upload
108
113
  Berkshelf.load_config
109
114
  berksfile = ::Berkshelf::Berksfile.from_file(options[:berksfile])
110
- berksfile.upload(Chef::Config[:chef_server_url], options)
115
+
116
+ berksfile.upload(
117
+ server_url: Chef::Config[:chef_server_url],
118
+ client_name: Chef::Config[:node_name],
119
+ client_key: Chef::Config[:client_key],
120
+ organization: ChefAPILocation.extract_organization(Chef::Config[:chef_server_url])
121
+ )
111
122
  end
112
123
 
113
124
  desc "init [PATH]", "Prepare a local path to have its Cookbook dependencies managed by Berkshelf."
@@ -69,6 +69,8 @@ module Berkshelf
69
69
  attr_reader :location
70
70
  attr_accessor :cached_cookbook
71
71
 
72
+ def_delegator :cached_cookbook, :version, :locked_version
73
+
72
74
  # @param [String] name
73
75
  # @param [Hash] options
74
76
  #
@@ -132,14 +134,22 @@ module Berkshelf
132
134
  groups.include?(group.to_sym)
133
135
  end
134
136
 
135
- def locked_version
136
- @locked_version || cached_cookbook.version
137
- end
138
-
139
137
  def to_s
140
138
  msg = "#{self.name} (#{self.version_constraint}) groups: #{self.groups}"
141
139
  msg << " location: #{self.location}" if self.location
142
140
  msg
143
141
  end
142
+
143
+ def to_hash
144
+ {}.tap do |h|
145
+ h[:name] = self.name
146
+ h[:locked_version] = self.locked_version
147
+ h[:location] = self.location.to_hash if self.location
148
+ end
149
+ end
150
+
151
+ def to_json
152
+ MultiJson.dump(self.to_hash, pretty: true)
153
+ end
144
154
  end
145
155
  end
@@ -12,6 +12,7 @@ module Berkshelf
12
12
  end
13
13
 
14
14
  class InternalError < BerkshelfError; status_code(99); end
15
+ class ArgumentError < InternalError; end
15
16
  class AbstractFunction < InternalError
16
17
  def to_s
17
18
  "Function must be implemented on includer"
@@ -74,7 +75,9 @@ module Berkshelf
74
75
  status_code(113)
75
76
 
76
77
  def status_code
77
- @original_error ? @original_error.status_code : 113
78
+ @original_error.respond_to?(:status_code) ? @original_error.status_code : 113
78
79
  end
79
80
  end
81
+
82
+ class AmbiguousCookbookName < BerkshelfError; status_code(114); end
80
83
  end
@@ -1,10 +1,74 @@
1
1
  module Berkshelf
2
+ # @author Michael Ivey <ivey@gweezlebur.com>
3
+ # @author Jamie Winsor <jamie@vialstudios.com>
2
4
  module Formatters
5
+ class << self
6
+ @@formatters = Hash.new
7
+
8
+ # Access the formatters map that links string symbols to Formatter
9
+ # implementations
10
+ #
11
+ # @return [Hash]
12
+ def formatters
13
+ @@formatters
14
+ end
15
+
16
+ # @param [#to_sym] id
17
+ # @param [Constant] klass
18
+ #
19
+ # @raise [Berkshelf::InternalError] if an ID that has already been registered is attempted
20
+ # to be registered again
21
+ #
22
+ # @return [Hash]
23
+ # a hash of registered formatters
24
+ def register(id, klass)
25
+ unless id.respond_to?(:to_sym)
26
+ raise ArgumentError, "Invalid Formatter ID: must respond to #to_sym. You gave: #{id}"
27
+ end
28
+
29
+ id = id.to_sym
30
+ if self.formatters.has_key?(id)
31
+ raise Berkshelf::InternalError, "Formatter ID '#{id}' already registered"
32
+ end
33
+
34
+ self.formatters[id] = klass
35
+ end
36
+
37
+ # @param [#to_sym] id
38
+ #
39
+ # @return [~AbstractFormatter, nil]
40
+ def get(id)
41
+ unless id.respond_to?(:to_sym)
42
+ raise ArgumentError, "Invalid Formatter ID: must respond to #to_sym. You gave: #{id}"
43
+ end
44
+
45
+ self.formatters.fetch(id.to_sym, nil)
46
+ end
47
+ alias_method :[], :get
48
+ end
49
+
50
+ # @author Michael Ivey <ivey@gweezlebur.com>
51
+ #
3
52
  # @abstract Include and override {#install} {#use} {#upload}
4
- # {#shims_written} {#msg} {#error} to implement.
53
+ # {#msg} {#error} to implement.
5
54
  #
6
55
  # Implement {#cleanup_hook} to run any steps required to run after the task is finished
7
56
  module AbstractFormatter
57
+ extend ActiveSupport::Concern
58
+
59
+ module ClassMethods
60
+ # @param [Symbol] id
61
+ #
62
+ # @raise [Berkshelf::InternalError] if an ID that has already been registered is attempted
63
+ # to be registered again
64
+ #
65
+ # @return [Hash]
66
+ # a hash of registered formatters
67
+ def register_formatter(id)
68
+ Formatters.register(id, self)
69
+ end
70
+ end
71
+
8
72
  def cleanup_hook
9
73
  # run after the task is finished
10
74
  end
@@ -21,10 +85,6 @@ module Berkshelf
21
85
  raise AbstractFunction, "#upload must be implemented on #{self.class}"
22
86
  end
23
87
 
24
- def shims_written(directory)
25
- raise AbstractFunction, "#shims_written must be implemented on #{self.class}"
26
- end
27
-
28
88
  def msg(message)
29
89
  raise AbstractFunction, "#msg must be implemented on #{self.class}"
30
90
  end
@@ -32,6 +92,10 @@ module Berkshelf
32
92
  def error(message)
33
93
  raise AbstractFunction, "#error must be implemented on #{self.class}"
34
94
  end
95
+
96
+ private
97
+
98
+ attr_reader :args
35
99
  end
36
100
  end
37
101
  end
@@ -1,30 +1,48 @@
1
1
  module Berkshelf
2
2
  module Formatters
3
+ # @author Michael Ivey <ivey@gweezlebur.com>
3
4
  class HumanReadable
4
5
  include AbstractFormatter
5
6
 
6
- Berkshelf.formatters["human"] = self
7
+ register_formatter :human
7
8
 
9
+ # Output a Cookbook installation message using {Berkshelf.ui}
10
+ #
11
+ # @param [String] cookbook
12
+ # @param [String] version
13
+ # @param [~Location] location
8
14
  def install(cookbook, version, location)
9
15
  Berkshelf.ui.info "Installing #{cookbook} (#{version}) from #{location}"
10
16
  end
11
17
 
12
- def use(cookbook, version, path=nil)
18
+ # Output a Cookbook use message using {Berkshelf.ui}
19
+ #
20
+ # @param [String] cookbook
21
+ # @param [String] version
22
+ # @param [String] path
23
+ def use(cookbook, version, path = nil)
13
24
  Berkshelf.ui.info "Using #{cookbook} (#{version})#{' at '+path if path}"
14
25
  end
15
26
 
16
- def upload(cookbook, version, chef_server_url)
17
- Berkshelf.ui.info "Uploading #{cookbook} (#{version}) to: '#{chef_server_url}'"
18
- end
19
-
20
- def shims_written(directory)
21
- Berkshelf.ui.info "Shims written to: '#{directory}'"
27
+ # Output a Cookbook upload message using {Berkshelf.ui}
28
+ #
29
+ # @param [String] cookbook
30
+ # @param [String] version
31
+ # @param [String] chef_api_url
32
+ def upload(cookbook, version, chef_api_url)
33
+ Berkshelf.ui.info "Uploading #{cookbook} (#{version}) to: '#{chef_api_url}'"
22
34
  end
23
35
 
36
+ # Output a generic message using {Berkshelf.ui}
37
+ #
38
+ # @param [String] message
24
39
  def msg(message)
25
40
  Berkshelf.ui.info message
26
41
  end
27
42
 
43
+ # Output an error message using {Berkshelf.ui}
44
+ #
45
+ # @param [String] message
28
46
  def error(message)
29
47
  Berkshelf.ui.error message
30
48
  end
@@ -1,53 +1,81 @@
1
1
  module Berkshelf
2
2
  module Formatters
3
+ # @author Michael Ivey <ivey@gweezlebur.com>
3
4
  class JSON
4
5
  include AbstractFormatter
5
6
 
6
- Berkshelf.formatters["json"] = self
7
+ register_formatter :json
7
8
 
8
9
  def initialize
9
- @output = {cookbooks: [], errors: [], messages: []}
10
- @cookbooks = {}
10
+ @output = {
11
+ cookbooks: Array.new,
12
+ errors: Array.new,
13
+ messages: Array.new
14
+ }
15
+ @cookbooks = Hash.new
11
16
  super
12
17
  end
13
18
 
14
19
  def cleanup_hook
15
- @cookbooks.each do |name, details|
20
+ cookbooks.each do |name, details|
16
21
  details[:name] = name
17
- @output[:cookbooks] << details
22
+ output[:cookbooks] << details
18
23
  end
19
- print @output.to_json
24
+
25
+ print MultiJson.dump(output)
20
26
  end
21
27
 
28
+ # Add a Cookbook installation entry to delayed output
29
+ #
30
+ # @param [String] cookbook
31
+ # @param [String] version
32
+ # @param [~Location] location
22
33
  def install(cookbook, version, location)
23
- @cookbooks[cookbook] ||= {}
24
- @cookbooks[cookbook][:version] = version
25
- @cookbooks[cookbook][:location] = location
34
+ cookbooks[cookbook] ||= {}
35
+ cookbooks[cookbook][:version] = version
36
+ cookbooks[cookbook][:location] = location.to_s
26
37
  end
27
38
 
28
- def use(cookbook, version, path=nil)
29
- @cookbooks[cookbook] ||= {}
30
- @cookbooks[cookbook][:version] = version
31
- @cookbooks[cookbook][:location] = path if path
39
+ # Add a Cookbook use entry to delayed output
40
+ #
41
+ # @param [String] cookbook
42
+ # @param [String] version
43
+ # @param [String] path
44
+ def use(cookbook, version, path = nil)
45
+ cookbooks[cookbook] ||= {}
46
+ cookbooks[cookbook][:version] = version
47
+ cookbooks[cookbook][:location] = path if path
32
48
  end
33
49
 
34
- def upload(cookbook, version, chef_server_url)
35
- @cookbooks[cookbook] ||= {}
36
- @cookbooks[cookbook][:version] = version
37
- @cookbooks[cookbook][:uploaded_to] = chef_server_url
38
- end
39
-
40
- def shims_written(directory)
41
- @output[:shims_dir] = directory
50
+ # Add a Cookbook upload entry to delayed output
51
+ #
52
+ # @param [String] cookbook
53
+ # @param [String] version
54
+ # @param [String] chef_api_url
55
+ def upload(cookbook, version, chef_api_url)
56
+ cookbooks[cookbook] ||= {}
57
+ cookbooks[cookbook][:version] = version
58
+ cookbooks[cookbook][:uploaded_to] = chef_api_url
42
59
  end
43
60
 
61
+ # Add a generic message entry to delayed output
62
+ #
63
+ # @param [String] message
44
64
  def msg(message)
45
- @output[:messages] << message
65
+ output[:messages] << message
46
66
  end
47
67
 
68
+ # Add an error message entry to delayed output
69
+ #
70
+ # @param [String] message
48
71
  def error(message)
49
- @output[:errors] << message
72
+ output[:errors] << message
50
73
  end
74
+
75
+ private
76
+
77
+ attr_reader :output
78
+ attr_reader :cookbooks
51
79
  end
52
80
  end
53
81
  end
@@ -1,8 +1,8 @@
1
1
  module Berkshelf
2
2
  # @author Jamie Winsor <jamie@vialstudios.com>
3
3
  class InitGenerator < BaseGenerator
4
- def initialize(*)
5
- super
4
+ def initialize(*args)
5
+ super(*args)
6
6
  if @options[:cookbook_name]
7
7
  @cookbook_name = @options[:cookbook_name]
8
8
  end
@@ -54,7 +54,7 @@ module Berkshelf
54
54
  template "gitignore.erb", target.join(".gitignore")
55
55
  unless File.exists?(target.join(".git"))
56
56
  inside target do
57
- run "git init"
57
+ run "git init", capture: true
58
58
  end
59
59
  end
60
60
  end
@@ -73,7 +73,7 @@ module Berkshelf
73
73
 
74
74
  if options[:vagrant]
75
75
  template "Vagrantfile.erb", target.join("Vagrantfile")
76
- ::Berkshelf::Cli.new([], berksfile: target.join("Berksfile"), shims: target.join("cookbooks")).invoke(:install)
76
+ ::Berkshelf::Cli.new([], berksfile: target.join("Berksfile")).invoke(:install)
77
77
  end
78
78
  end
79
79