berkshelf 3.0.0.beta7 → 3.0.0.beta8

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