berkshelf 3.0.0.beta7 → 3.0.0.beta8

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 (94) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/.travis.yml +4 -1
  4. data/CONTRIBUTING.md +1 -1
  5. data/Gemfile +0 -1
  6. data/Guardfile +0 -8
  7. data/README.md +33 -13
  8. data/berkshelf.gemspec +3 -3
  9. data/features/commands/install.feature +16 -88
  10. data/features/commands/search.feature +15 -0
  11. data/features/commands/shelf/show.feature +2 -2
  12. data/features/commands/shelf/uninstall.feature +1 -1
  13. data/features/commands/show.feature +3 -3
  14. data/features/commands/update.feature +29 -1
  15. data/features/commands/upload.feature +172 -7
  16. data/features/commands/vendor.feature +32 -0
  17. data/features/json_formatter.feature +26 -24
  18. data/features/lifecycle.feature +285 -0
  19. data/features/lockfile.feature +9 -7
  20. data/features/step_definitions/chef_server_steps.rb +1 -0
  21. data/features/step_definitions/cli_steps.rb +2 -2
  22. data/features/step_definitions/filesystem_steps.rb +2 -4
  23. data/gem_graph.png +0 -0
  24. data/generator_files/chefignore +0 -2
  25. data/lib/berkshelf.rb +39 -14
  26. data/lib/berkshelf/berksfile.rb +161 -113
  27. data/lib/berkshelf/cached_cookbook.rb +2 -2
  28. data/lib/berkshelf/cli.rb +15 -3
  29. data/lib/berkshelf/commands/shelf.rb +3 -7
  30. data/lib/berkshelf/community_rest.rb +9 -9
  31. data/lib/berkshelf/config.rb +3 -3
  32. data/lib/berkshelf/cookbook_generator.rb +0 -8
  33. data/lib/berkshelf/cookbook_store.rb +1 -2
  34. data/lib/berkshelf/dependency.rb +25 -138
  35. data/lib/berkshelf/downloader.rb +41 -7
  36. data/lib/berkshelf/errors.rb +113 -214
  37. data/lib/berkshelf/formatters/base.rb +42 -0
  38. data/lib/berkshelf/formatters/human.rb +145 -0
  39. data/lib/berkshelf/formatters/json.rb +149 -133
  40. data/lib/berkshelf/formatters/null.rb +8 -18
  41. data/lib/berkshelf/init_generator.rb +1 -1
  42. data/lib/berkshelf/installer.rb +115 -104
  43. data/lib/berkshelf/location.rb +22 -121
  44. data/lib/berkshelf/locations/base.rb +75 -0
  45. data/lib/berkshelf/locations/git.rb +196 -0
  46. data/lib/berkshelf/locations/github.rb +8 -0
  47. data/lib/berkshelf/locations/path.rb +78 -0
  48. data/lib/berkshelf/lockfile.rb +452 -290
  49. data/lib/berkshelf/logger.rb +9 -3
  50. data/lib/berkshelf/mixin/logging.rb +4 -9
  51. data/lib/berkshelf/resolver.rb +12 -12
  52. data/lib/berkshelf/source.rb +13 -1
  53. data/lib/berkshelf/version.rb +1 -1
  54. data/spec/fixtures/cookbooks/example_cookbook-0.5.0/metadata.rb +3 -7
  55. data/spec/fixtures/cookbooks/example_cookbook/metadata.rb +3 -6
  56. data/spec/spec_helper.rb +5 -6
  57. data/spec/support/matchers/file_system_matchers.rb +4 -0
  58. data/spec/support/shared_examples/formatter.rb +11 -0
  59. data/spec/unit/berkshelf/berksfile_spec.rb +25 -28
  60. data/spec/unit/berkshelf/cli_spec.rb +19 -11
  61. data/spec/unit/berkshelf/dependency_spec.rb +4 -164
  62. data/spec/unit/berkshelf/formatters/base_spec.rb +35 -0
  63. data/spec/unit/berkshelf/formatters/human_spec.rb +7 -0
  64. data/spec/unit/berkshelf/formatters/json_spec.rb +7 -0
  65. data/spec/unit/berkshelf/formatters/null_spec.rb +7 -11
  66. data/spec/unit/berkshelf/location_spec.rb +16 -144
  67. data/spec/unit/berkshelf/locations/base_spec.rb +80 -0
  68. data/spec/unit/berkshelf/locations/git_spec.rb +249 -0
  69. data/spec/unit/berkshelf/locations/path_spec.rb +107 -0
  70. data/spec/unit/berkshelf/lockfile_parser_spec.rb +3 -3
  71. data/spec/unit/berkshelf/lockfile_spec.rb +55 -11
  72. data/spec/unit/berkshelf/logger_spec.rb +2 -2
  73. data/spec/unit/berkshelf/mixin/logging_spec.rb +5 -9
  74. data/spec/unit/berkshelf/source_spec.rb +32 -13
  75. data/spec/unit/berkshelf_spec.rb +6 -9
  76. metadata +33 -33
  77. data/.ruby-version +0 -1
  78. data/berkshelf-complete.sh +0 -75
  79. data/lib/berkshelf/formatters.rb +0 -110
  80. data/lib/berkshelf/formatters/human_readable.rb +0 -142
  81. data/lib/berkshelf/git.rb +0 -204
  82. data/lib/berkshelf/locations/git_location.rb +0 -135
  83. data/lib/berkshelf/locations/github_location.rb +0 -55
  84. data/lib/berkshelf/locations/mercurial_location.rb +0 -114
  85. data/lib/berkshelf/locations/path_location.rb +0 -88
  86. data/lib/berkshelf/mercurial.rb +0 -146
  87. data/lib/berkshelf/mixin.rb +0 -7
  88. data/spec/support/mercurial.rb +0 -123
  89. data/spec/unit/berkshelf/formatters_spec.rb +0 -114
  90. data/spec/unit/berkshelf/git_spec.rb +0 -312
  91. data/spec/unit/berkshelf/locations/git_location_spec.rb +0 -126
  92. data/spec/unit/berkshelf/locations/mercurial_location_spec.rb +0 -131
  93. data/spec/unit/berkshelf/locations/path_location_spec.rb +0 -25
  94. data/spec/unit/berkshelf/mercurial_spec.rb +0 -172
@@ -1,22 +1,12 @@
1
1
  module Berkshelf
2
- module Formatters
3
- class Null
4
- include AbstractFormatter
5
-
6
- register_formatter :null
7
-
8
- # The abstract formatter dynamically defines methods that raise an
9
- # AbstractFunction error. We need to define all of those on our class,
10
- # otherwise they will be inherited by the Ruby object model.
11
- AbstractFormatter.instance_methods.each do |meth|
12
- define_method(meth) do |*args|
13
- # intentionally do nothing
14
- end
15
- end
16
-
17
- def method_missing(meth, *args, &block)
18
- # intentionally do nothing
19
- end
2
+ class NullFormatter < BaseFormatter
3
+ # The base formatter dynamically defines methods that raise an
4
+ # AbstractFunction error. We need to define all of those on our class,
5
+ # otherwise they will be inherited by the Ruby object model.
6
+ BaseFormatter.instance_methods(false).each do |name|
7
+ class_eval <<-EOH, __FILE__, __LINE__ + 1
8
+ def #{name}(*args); end
9
+ EOH
20
10
  end
21
11
  end
22
12
  end
@@ -150,7 +150,7 @@ module Berkshelf
150
150
  # @return [nil]
151
151
  def validate_configuration
152
152
  unless Berkshelf.config.valid?
153
- raise InvalidConfiguration.new Berkshelf.config.errors
153
+ raise InvalidConfiguration.new(Berkshelf.config.errors)
154
154
  end
155
155
  end
156
156
 
@@ -29,134 +29,145 @@ module Berkshelf
29
29
 
30
30
  # @return [Array<Berkshelf::CachedCookbook>]
31
31
  def run
32
- reduce_lockfile!
32
+ lockfile.reduce!
33
33
 
34
- cookbooks = if lockfile.trusted?
35
- install_from_lockfile
36
- else
37
- install_from_universe
34
+ Berkshelf.formatter.msg('Resolving cookbook dependencies...')
35
+
36
+ dependencies, cookbooks = if lockfile.trusted?
37
+ install_from_lockfile
38
+ else
39
+ install_from_universe
40
+ end
41
+
42
+ Berkshelf.log.debug " Finished resolving, calculating locks"
43
+
44
+ to_lock = dependencies.select do |dependency|
45
+ berksfile.has_dependency?(dependency)
46
+ end
47
+
48
+ Berkshelf.log.debug " New locks"
49
+ to_lock.each do |lock|
50
+ Berkshelf.log.debug " #{lock}"
38
51
  end
39
52
 
40
53
  lockfile.graph.update(cookbooks)
41
- lockfile.update(berksfile.dependencies)
54
+ lockfile.update(to_lock)
42
55
  lockfile.save
43
56
 
44
57
  cookbooks
45
58
  end
46
59
 
47
- # Install all the dependencies from the lockfile graph.
48
- #
49
- # @return [Array<CachedCookbook>]
50
- # the list of installed cookbooks
51
- def install_from_lockfile
52
- locks = lockfile.graph.locks
60
+ private
53
61
 
54
- # Only construct the universe if we are going to download things
55
- unless locks.all? { |_, dependency| dependency.downloaded? }
56
- build_universe
57
- end
62
+ # Install a specific dependency.
63
+ #
64
+ # @param [Dependency]
65
+ # the dependency to install
66
+ # @return [CachedCookbook]
67
+ # the installed cookbook
68
+ def install(dependency)
69
+ Berkshelf.log.info "Installing #{dependency}"
58
70
 
59
- locks.sort.collect do |name, dependency|
60
- install(dependency)
61
- end
62
- end
71
+ if dependency.installed?
72
+ Berkshelf.log.debug " Already installed - skipping install"
63
73
 
64
- # Resolve and install the dependencies from the "universe", updating the
65
- # lockfile appropiately.
66
- #
67
- # @return [Array<CachedCookbook>]
68
- # the list of installed cookbooks
69
- def install_from_universe
70
- dependencies = lockfile.graph.locks.values + berksfile.dependencies
71
- dependencies = dependencies.inject({}) do |hash, dependency|
72
- # Fancy way of ensuring no duplicate dependencies are used...
73
- hash[dependency.name] ||= dependency
74
- hash
75
- end.values
76
-
77
- resolver = Resolver.new(berksfile, dependencies)
78
-
79
- # Download all SCM locations first, since they might have additional
80
- # constraints that we don't yet know about
81
- dependencies.select(&:scm_location?).each do |dependency|
82
- Berkshelf.formatter.fetch(dependency)
83
- dependency.download
84
- end
74
+ Berkshelf.formatter.use(dependency)
75
+ dependency.cached_cookbook
76
+ else
77
+ name, version = dependency.name, dependency.locked_version.to_s
78
+ source = berksfile.source_for(name, version)
79
+
80
+ Berkshelf.log.debug " Downloading #{dependency.name} (#{dependency.locked_version}) from #{source}"
81
+
82
+ cookbook = source.cookbook(name, version)
85
83
 
86
- # Unlike when installing from the lockfile, we _always_ need to build
87
- # the universe when installing from the universe... duh
88
- build_universe
84
+ Berkshelf.log.debug " => #{cookbook.inspect}"
89
85
 
90
- # Add any explicit dependencies for already-downloaded cookbooks (like
91
- # path locations)
92
- dependencies.each do |dependency|
93
- if cookbook = dependency.cached_cookbook
94
- resolver.add_explicit_dependencies(cookbook)
86
+ Berkshelf.formatter.install(source, cookbook)
87
+
88
+ stash = downloader.download(name, version)
89
+ CookbookStore.import(name, version, stash)
95
90
  end
96
91
  end
97
92
 
98
- resolver.resolve.sort.collect do |dependency|
99
- install(dependency)
100
- end
101
- end
93
+ # Install all the dependencies from the lockfile graph.
94
+ #
95
+ # @return [Array<Array<Dependency> Array<CachedCookbook>>]
96
+ # the list of installed dependencies and cookbooks
97
+ def install_from_lockfile
98
+ Berkshelf.log.info "Installing from lockfile"
99
+
100
+ dependencies = lockfile.graph.locks.values
101
+
102
+ Berkshelf.log.debug " Dependencies"
103
+ dependencies.map do |dependency|
104
+ Berkshelf.log.debug " #{dependency}"
105
+ end
106
+
107
+ download_locations(dependencies)
102
108
 
103
- # Install a specific dependency.
104
- #
105
- # @param [Dependency]
106
- # the dependency to install
107
- # @return [CachedCookbook]
108
- # the installed cookbook
109
- def install(dependency)
110
- if dependency.downloaded?
111
- Berkshelf.formatter.use(dependency)
112
- dependency.cached_cookbook
113
- else
114
- name, version = dependency.name, dependency.locked_version.to_s
115
- source = berksfile.source_for(name, version)
116
- cookbook = source.cookbook(name, version)
117
-
118
- Berkshelf.formatter.install(source, cookbook)
119
-
120
- stash = downloader.download(name, version)
121
- CookbookStore.import(name, version, stash)
109
+ # Only construct the universe if we are going to install things
110
+ unless dependencies.all?(&:installed?)
111
+ Berkshelf.log.debug " Not all dependencies are installed"
112
+ build_universe
113
+ end
114
+
115
+ cookbooks = dependencies.sort.collect do |dependency|
116
+ install(dependency)
117
+ end
118
+
119
+ [dependencies, cookbooks]
122
120
  end
123
- end
124
121
 
125
- private
122
+ # Resolve and install the dependencies from the "universe", updating the
123
+ # lockfile appropiately.
124
+ #
125
+ # @return [Array<Array<Dependency> Array<CachedCookbook>>]
126
+ # the list of installed dependencies and cookbooks
127
+ def install_from_universe
128
+ Berkshelf.log.info "Installing from universe"
129
+
130
+ dependencies = lockfile.graph.locks.values + berksfile.dependencies
131
+ dependencies = dependencies.inject({}) do |hash, dependency|
132
+ # Fancy way of ensuring no duplicate dependencies are used...
133
+ hash[dependency.name] ||= dependency
134
+ hash
135
+ end.values
136
+
137
+ download_locations(dependencies)
138
+
139
+ Berkshelf.log.debug " Creating a resolver"
140
+ resolver = Resolver.new(berksfile, dependencies)
141
+
142
+ # Unlike when installing from the lockfile, we _always_ need to build
143
+ # the universe when installing from the universe... duh
144
+ build_universe
126
145
 
127
- # Iterate over each top-level dependency defined in the lockfile and
128
- # check if that dependency is still defined in the Berksfile.
129
- #
130
- # If the dependency is no longer present in the Berksfile, it is "safely"
131
- # removed using {Lockfile#unlock} and {Lockfile#remove}. This prevents
132
- # the lockfile from "leaking" dependencies when they have been removed
133
- # from the Berksfile, but still remained locked in the lockfile.
134
- #
135
- # If the dependency exists, a constraint comparison is conducted to verify
136
- # that the locked dependency still satisifes the original constraint. This
137
- # handles the edge case where a user has updated or removed a constraint
138
- # on a dependency that already existed in the lockfile.
139
- #
140
- # @raise [OutdatedDependency]
141
- # if the constraint exists, but is no longer satisifed by the existing
142
- # locked version
143
- #
144
- # @return [Array<Dependency>]
145
- def reduce_lockfile!
146
- lockfile.dependencies.each do |dependency|
147
- if berksfile.dependencies.map(&:name).include?(dependency.name)
148
- locked = lockfile.graph.find(dependency)
149
- next if locked.nil?
150
-
151
- unless dependency.version_constraint.satisfies?(locked.version)
152
- raise OutdatedDependency.new(locked, dependency)
146
+ # Add any explicit dependencies for already-downloaded cookbooks (like
147
+ # path locations)
148
+ dependencies.each do |dependency|
149
+ if cookbook = dependency.cached_cookbook
150
+ Berkshelf.log.debug " Adding explicit dependency on #{cookbook}"
151
+ resolver.add_explicit_dependencies(cookbook)
153
152
  end
154
- else
155
- lockfile.unlock(dependency)
156
153
  end
154
+
155
+ Berkshelf.log.debug " Starting resolution..."
156
+
157
+ cookbooks = resolver.resolve.sort.collect do |dependency|
158
+ install(dependency)
159
+ end
160
+
161
+ [dependencies, cookbooks]
157
162
  end
158
163
 
159
- lockfile.save
160
- end
164
+ def download_locations(dependencies)
165
+ dependencies.select(&:location).each do |dependency|
166
+ unless dependency.location.installed?
167
+ Berkshelf.formatter.fetch(dependency)
168
+ dependency.location.install
169
+ end
170
+ end
171
+ end
161
172
  end
162
173
  end
@@ -1,146 +1,47 @@
1
1
  module Berkshelf
2
- module Location
2
+ class Location
3
3
  class << self
4
- # Creates a new instance of a Class implementing Location with the given name and
5
- # constraint. Which Class to instantiated is determined by the values in the given
6
- # options Hash. Source Locations have an associated location_key registered with
7
- # Berkshelf::Dependency. If your options Hash contains a key matching one of these location_keys
8
- # then the Class who registered that location_key will be instantiated. If you do not
9
- # provide an option with a matching location_key nil will be returned.
4
+ # Create a new instance of a Location class given dependency and options.
5
+ # The type of class is determined by the values in the given +options+
6
+ # Hash.
10
7
  #
11
- # @example
12
- # Location.init('nginx', '>= 0.0.0', git: 'git://github.com/RiotGames/artifact-cookbook.git') =>
13
- # instantiates a GitLocation
8
+ # If you do not provide an option with a matching location id, +nil+
9
+ # is returned.
14
10
  #
15
- # Location.init('nginx', '>= 0.0.0', hg: 'http://hghub.com/RiotGames/') =>
16
- # instantiates a MercurialLocation
11
+ # @example Create a git location
12
+ # Location.init(dependency, git: 'git://github.com/berkshelf/berkshelf.git')
17
13
  #
18
- # Location.init('nginx', '>= 0.0.0', path: '/Users/reset/code/nginx-cookbook') =>
19
- # instantiates a PathLocation
14
+ # @example Create a GitHub location
15
+ # Location.init(dependency, github: 'berkshelf/berkshelf')
20
16
  #
21
17
  # @param [Dependency] dependency
22
18
  # @param [Hash] options
23
19
  #
24
- # @return [~Location::Base, nil]
20
+ # @return [~BaseLocation, nil]
25
21
  def init(dependency, options = {})
26
22
  if klass = klass_from_options(options)
27
23
  klass.new(dependency, options)
24
+ else
25
+ nil
28
26
  end
29
27
  end
30
28
 
31
29
  private
32
30
 
33
- def klass_from_options(options)
34
- location_keys = (options.keys & Berkshelf::Dependency.location_keys.keys)
35
- if location_keys.length > 1
36
- location_keys.collect! { |opt| "'#{opt}'" }
37
- raise InternalError, "Only one location key (#{Berkshelf::Dependency.location_keys.keys.join(', ')}) " +
38
- "may be specified. You gave #{location_keys.join(', ')}."
39
- end
40
-
41
- if location_key = location_keys.first
42
- Berkshelf::Dependency.location_keys[location_key]
43
- end
44
- end
45
- end
46
-
47
- class Base
48
- class << self
49
- # Returns the location identifier key for the class
50
- #
51
- # @return [Symbol]
52
- attr_reader :location_key
53
-
54
- # Register the location key for the including source location with Berkshelf::Dependency
31
+ # Load the correct location from the given options.
55
32
  #
56
- # @param [Symbol] key
57
- def set_location_key(key)
58
- Berkshelf::Dependency.add_location_key(key, self)
59
- @location_key = key
60
- end
33
+ # @return [Class, nil]
34
+ def klass_from_options(options)
35
+ options.each do |key, _|
36
+ id = key.to_s.capitalize
61
37
 
62
- # Register a valid option or multiple options with the Berkshelf::Dependency class
63
- #
64
- # @param [Symbol] opts
65
- def set_valid_options(*opts)
66
- Array(opts).each do |opt|
67
- Berkshelf::Dependency.add_valid_option(opt)
38
+ begin
39
+ return Berkshelf.const_get("#{id}Location")
40
+ rescue NameError; end
68
41
  end
69
- end
70
- end
71
-
72
- extend Forwardable
73
-
74
- attr_reader :dependency
75
- def_delegator :dependency, :name
76
-
77
- # @param [Berkshelf::Dependency] dependency
78
- # @param [Hash] options
79
- def initialize(dependency, options = {})
80
- @dependency = dependency
81
- @@cached_cookbook = nil
82
- end
83
-
84
- # @param [#to_s] destination
85
- #
86
- # @return [Berkshelf::CachedCookbook]
87
- def download
88
- return @cached_cookbook if @cached_cookbook
89
-
90
- cached_cookbook = do_download
91
- validate_cached(cached_cookbook)
92
- @cached_cookbook = cached_cookbook
93
- end
94
-
95
- # @param [#to_s] destination
96
- #
97
- # @return [Berkshelf::CachedCookbook]
98
- def do_download
99
- raise AbstractFunction
100
- end
101
42
 
102
- # Ensure the retrieved CachedCookbook is valid
103
- #
104
- # @param [CachedCookbook] cached_cookbook
105
- # the downloaded cookbook to validate
106
- #
107
- # @raise [CookbookValidationFailure] if given CachedCookbook does not satisfy the constraint of the location
108
- #
109
- # @todo Change MismatchedCookbookName to raise instead of warn
110
- #
111
- # @return [Boolean]
112
- def validate_cached(cached_cookbook)
113
- unless dependency.version_constraint.satisfies?(cached_cookbook.version)
114
- raise CookbookValidationFailure.new(dependency, cached_cookbook)
43
+ nil
115
44
  end
116
-
117
- unless dependency.name == cached_cookbook.cookbook_name
118
- Berkshelf.ui.warn(MismatchedCookbookName.new(dependency, cached_cookbook).to_s)
119
- end
120
-
121
- true
122
- end
123
-
124
- # Determines if the location is well formed and points to an accessible location
125
- #
126
- # @return [Boolean]
127
- def valid?
128
- true
129
- end
130
-
131
- def to_hash
132
- { type: self.class.location_key }
133
- end
134
-
135
- def to_json(options = {})
136
- JSON.pretty_generate(to_hash, options)
137
- end
138
45
  end
139
-
140
- class ScmLocation < Location::Base; end
141
46
  end
142
47
  end
143
-
144
- Dir["#{File.dirname(__FILE__)}/locations/*.rb"].sort.each do |path|
145
- require_relative "locations/#{File.basename(path, '.rb')}"
146
- end