berkshelf 2.0.18 → 3.0.0.beta1

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 (128) hide show
  1. data/.ruby-version +1 -1
  2. data/.travis.yml +4 -1
  3. data/CHANGELOG.md +2 -26
  4. data/Gemfile +12 -2
  5. data/README.md +9 -1
  6. data/berkshelf.gemspec +9 -18
  7. data/bin/berks +3 -13
  8. data/features/apply_command.feature +11 -9
  9. data/features/berksfile.feature +8 -10
  10. data/features/config.feature +1 -2
  11. data/features/configure_command.feature +13 -14
  12. data/features/contingent_command.feature +13 -1
  13. data/features/cookbook_command.feature +2 -4
  14. data/features/groups_install.feature +10 -2
  15. data/features/help.feature +1 -1
  16. data/features/init_command.feature +5 -7
  17. data/features/install_command.feature +157 -228
  18. data/features/json_formatter.feature +27 -15
  19. data/features/licenses.feature +18 -12
  20. data/features/list_command.feature +6 -1
  21. data/features/lockfile.feature +116 -72
  22. data/features/outdated_command.feature +3 -47
  23. data/features/package_command.feature +10 -7
  24. data/features/shelf/show.feature +2 -2
  25. data/features/shelf/uninstall.feature +2 -2
  26. data/features/show_command.feature +10 -3
  27. data/features/step_definitions/chef/config_steps.rb +12 -0
  28. data/features/step_definitions/chef_server_steps.rb +16 -16
  29. data/features/step_definitions/cli_steps.rb +3 -79
  30. data/features/step_definitions/config_steps.rb +43 -0
  31. data/features/step_definitions/environment_steps.rb +7 -0
  32. data/features/step_definitions/filesystem_steps.rb +12 -57
  33. data/features/step_definitions/gem_steps.rb +1 -2
  34. data/features/step_definitions/json_steps.rb +3 -1
  35. data/features/step_definitions/lockfile_steps.rb +4 -0
  36. data/features/step_definitions/utility_steps.rb +0 -19
  37. data/features/support/aruba.rb +12 -0
  38. data/features/support/env.rb +52 -57
  39. data/features/update_command.feature +37 -23
  40. data/features/upload_command.feature +96 -160
  41. data/generator_files/Berksfile.erb +2 -1
  42. data/generator_files/Vagrantfile.erb +3 -0
  43. data/generator_files/default_test.rb.erb +1 -1
  44. data/generator_files/helpers.rb.erb +1 -1
  45. data/lib/berkshelf.rb +43 -24
  46. data/lib/berkshelf/api_client.rb +67 -0
  47. data/lib/berkshelf/api_client/remote_cookbook.rb +42 -0
  48. data/lib/berkshelf/berksfile.rb +232 -420
  49. data/lib/berkshelf/cached_cookbook.rb +22 -10
  50. data/lib/berkshelf/chef/config.rb +1 -0
  51. data/lib/berkshelf/cli.rb +66 -68
  52. data/lib/berkshelf/commands/shelf.rb +1 -1
  53. data/lib/berkshelf/community_rest.rb +10 -17
  54. data/lib/berkshelf/config.rb +23 -27
  55. data/lib/berkshelf/cookbook_generator.rb +3 -4
  56. data/lib/berkshelf/cookbook_store.rb +74 -17
  57. data/lib/berkshelf/core_ext/file.rb +2 -2
  58. data/lib/berkshelf/core_ext/pathname.rb +7 -5
  59. data/lib/berkshelf/{cookbook_source.rb → dependency.rb} +47 -67
  60. data/lib/berkshelf/downloader.rb +49 -106
  61. data/lib/berkshelf/errors.rb +64 -71
  62. data/lib/berkshelf/formatters.rb +11 -9
  63. data/lib/berkshelf/formatters/human_readable.rb +9 -9
  64. data/lib/berkshelf/formatters/json.rb +14 -4
  65. data/lib/berkshelf/init_generator.rb +3 -3
  66. data/lib/berkshelf/installer.rb +136 -0
  67. data/lib/berkshelf/location.rb +91 -131
  68. data/lib/berkshelf/locations/git_location.rb +9 -11
  69. data/lib/berkshelf/locations/github_location.rb +1 -1
  70. data/lib/berkshelf/locations/path_location.rb +10 -27
  71. data/lib/berkshelf/lockfile.rb +92 -70
  72. data/lib/berkshelf/logger.rb +4 -7
  73. data/lib/berkshelf/mixin/config.rb +21 -4
  74. data/lib/berkshelf/resolver.rb +60 -150
  75. data/lib/berkshelf/resolver/graph.rb +44 -0
  76. data/lib/berkshelf/source.rb +55 -0
  77. data/lib/berkshelf/source_uri.rb +38 -0
  78. data/lib/berkshelf/version.rb +1 -1
  79. data/spec/config/knife.rb +1 -1
  80. data/spec/fixtures/cassettes/Berkshelf_Resolver/_initialize/adds_the_dependencies_of_the_dependency_as_dependencies.yml +3694 -0
  81. data/spec/fixtures/cookbooks/example_cookbook/Berksfile.lock +1 -1
  82. data/spec/fixtures/lockfiles/default.lock +1 -1
  83. data/spec/spec_helper.rb +20 -121
  84. data/spec/support/chef_api.rb +3 -4
  85. data/spec/support/chef_server.rb +20 -11
  86. data/spec/support/git.rb +127 -0
  87. data/spec/support/kitchen.rb +12 -0
  88. data/spec/support/path_helpers.rb +69 -0
  89. data/spec/unit/berkshelf/api_client/remote_cookbook_spec.rb +23 -0
  90. data/spec/unit/berkshelf/api_client_spec.rb +57 -0
  91. data/spec/unit/berkshelf/berksfile_spec.rb +206 -324
  92. data/spec/unit/berkshelf/cached_cookbook_spec.rb +73 -38
  93. data/spec/unit/berkshelf/community_rest_spec.rb +30 -71
  94. data/spec/unit/berkshelf/config_spec.rb +3 -14
  95. data/spec/unit/berkshelf/cookbook_generator_spec.rb +1 -2
  96. data/spec/unit/berkshelf/cookbook_store_spec.rb +12 -7
  97. data/spec/unit/berkshelf/dependency_spec.rb +285 -0
  98. data/spec/unit/berkshelf/downloader_spec.rb +4 -183
  99. data/spec/unit/berkshelf/formatters/null_spec.rb +1 -1
  100. data/spec/unit/berkshelf/formatters_spec.rb +4 -2
  101. data/spec/unit/berkshelf/git_spec.rb +15 -15
  102. data/spec/unit/berkshelf/installer_spec.rb +39 -0
  103. data/spec/unit/berkshelf/location_spec.rb +87 -114
  104. data/spec/unit/berkshelf/locations/git_location_spec.rb +41 -53
  105. data/spec/unit/berkshelf/locations/path_location_spec.rb +13 -23
  106. data/spec/unit/berkshelf/lockfile_spec.rb +38 -40
  107. data/spec/unit/berkshelf/resolver/graph_spec.rb +44 -0
  108. data/spec/unit/berkshelf/resolver_spec.rb +34 -83
  109. data/spec/unit/berkshelf/source_spec.rb +23 -0
  110. data/spec/unit/berkshelf/source_uri_spec.rb +29 -0
  111. metadata +149 -188
  112. checksums.yaml +0 -7
  113. data/features/default_locations.feature +0 -127
  114. data/features/step_definitions/berksfile_steps.rb +0 -8
  115. data/features/step_definitions/configure_cli_steps.rb +0 -19
  116. data/features/vendor_install.feature +0 -19
  117. data/lib/berkshelf/core_ext/openuri.rb +0 -36
  118. data/lib/berkshelf/core_ext/rbzip2.rb +0 -8
  119. data/lib/berkshelf/locations/chef_api_location.rb +0 -228
  120. data/lib/berkshelf/locations/site_location.rb +0 -92
  121. data/lib/berkshelf/test.rb +0 -35
  122. data/spec/knife.rb.sample +0 -12
  123. data/spec/support/test_generators.rb +0 -27
  124. data/spec/unit/berkshelf/cli_spec.rb +0 -16
  125. data/spec/unit/berkshelf/cookbook_source_spec.rb +0 -358
  126. data/spec/unit/berkshelf/core_ext/pathname_spec.rb +0 -46
  127. data/spec/unit/berkshelf/locations/chef_api_location_spec.rb +0 -139
  128. data/spec/unit/berkshelf/locations/site_location_spec.rb +0 -19
@@ -1,12 +1,9 @@
1
1
  module Berkshelf
2
- Logger = Ridley.logger
2
+ Logger = Celluloid::Logger.dup
3
3
 
4
- Logger.class_eval do
5
- alias_method :fatal, :error
6
-
7
- def deprecate(message)
8
- trace = caller.join("\n\t")
9
- warn "DEPRECATION WARNING: #{message}\n\t#{trace}"
4
+ Logger.module_eval do
5
+ def self.fatal(string)
6
+ error(string)
10
7
  end
11
8
  end
12
9
  end
@@ -6,6 +6,9 @@ module Berkshelf
6
6
  end
7
7
 
8
8
  module ClassMethods
9
+ # @return [String, nil]
10
+ attr_reader :default_location
11
+
9
12
  # Load a Chef configuration from the given path.
10
13
  #
11
14
  # @raise [Berkshelf::ConfigNotFound]
@@ -16,10 +19,15 @@ module Berkshelf
16
19
  raise Berkshelf::ConfigNotFound.new(self.class.name, filepath)
17
20
  end
18
21
 
22
+ # @return [Berkshelf::Mixin::Config]
23
+ def instance
24
+ @instance ||= load
25
+ end
26
+
19
27
  # Load the contents of the most probable Chef config. See {location}
20
28
  # for more information on how this logic is decided.
21
29
  def load
22
- self.new(location)
30
+ new(location)
23
31
  end
24
32
 
25
33
  # Class method for defining a default option.
@@ -47,9 +55,6 @@ module Berkshelf
47
55
  @default_location = File.expand_path(path)
48
56
  end
49
57
 
50
- # @return [String, nil]
51
- attr_reader :default_location
52
-
53
58
  # Converts a path to a path usable for your current platform
54
59
  #
55
60
  # @param [String] path
@@ -64,7 +69,19 @@ module Berkshelf
64
69
  path
65
70
  end
66
71
 
72
+ # @return [Berkshelf::Mixin::Config]
73
+ def reload
74
+ @instance = nil
75
+ instance
76
+ end
77
+
78
+ # @param [Berkshelf::Mixin::Config]
79
+ def set_config(config)
80
+ @instance = config
81
+ end
82
+
67
83
  private
84
+
68
85
  # @abstract
69
86
  # include and override {location} in your class to define the
70
87
  # default location logic
@@ -1,189 +1,99 @@
1
1
  module Berkshelf
2
2
  class Resolver
3
- require_relative 'cookbook_source'
4
- require_relative 'locations/git_location'
5
- require_relative 'locations/path_location'
3
+ require_relative 'resolver/graph'
6
4
 
7
5
  extend Forwardable
8
6
 
9
7
  # @return [Berkshelf::Berksfile]
10
8
  attr_reader :berksfile
11
9
 
12
- # @return [Solve::Graph]
10
+ # @return [Resolver::Graph]
13
11
  attr_reader :graph
14
12
 
15
- # @param [Berkshelf::Berksfile] berksfile
16
- # @param [Hash] options
17
- #
18
- # @option options [Array<CookbookSource>, CookbookSource] sources
19
- def initialize(berksfile, options = {})
20
- @berksfile = berksfile
21
- @downloader = berksfile.downloader
22
- @graph = Solve::Graph.new
23
- @sources = Hash.new
24
-
25
- # Dependencies need to be added AFTER the sources. If they are
26
- # not, then one of the dependencies of a source that is added
27
- # may take precedence over an explicitly set source that appears
28
- # later in the iterator.
29
- Array(options[:sources]).each do |source|
30
- add_source(source, false)
31
- end
13
+ # @return [Array<Berkshelf::Dependency>]
14
+ # an array of dependencies that must be satisfied
15
+ attr_reader :demands
32
16
 
33
- unless options[:skip_dependencies]
34
- Array(options[:sources]).each do |source|
35
- add_source_dependencies(source)
36
- end
37
- end
17
+ # @param [Berkshelf::Berksfile] berksfile
18
+ # @param [Array<Berkshelf::Dependency>, Berkshelf::Dependency] demands
19
+ # a dependency, or array of dependencies, which must be satisfied
20
+ def initialize(berksfile, demands = [])
21
+ @berksfile = berksfile
22
+ @graph = Graph.new
23
+ @demands = Array.new
24
+
25
+ Array(demands).each { |demand| add_demand(demand) }
38
26
  end
39
27
 
40
- # Add the given source to the collection of sources for this instance
41
- # of Resolver. By default the dependencies of the given source will also
42
- # be added as sources to the collection.
28
+ # Add the given dependency to the collection of demands
29
+ #
30
+ # @param [Berkshelf::Dependency] demand
31
+ # add a dependency that must be satisfied to the graph
43
32
  #
44
- # @param [Berkshelf::CookbookSource] source
45
- # source to add
46
- # @param [Boolean] include_dependencies
47
- # adds the dependencies of the given source as sources to the collection of
48
- # if true. Dependencies will be ignored if false.
33
+ # @raise [DuplicateDemand]
49
34
  #
50
- # @return [Array<CookbookSource>]
51
- def add_source(source, include_dependencies = true)
52
- if has_source?(source)
53
- raise DuplicateSourceDefined, "A source named '#{source.name}' is already present."
35
+ # @return [Array<Berkshelf::Dependency>]
36
+ def add_demand(demand)
37
+ if has_demand?(demand)
38
+ raise DuplicateDemand, "A demand named '#{demand.name}' is already present."
54
39
  end
55
40
 
56
- @sources[source.name] = source
57
- use_source(source) || install_source(source)
58
-
59
- graph.artifacts(source.name, source.cached_cookbook.version)
41
+ demands.push(demand)
42
+ end
60
43
 
61
- if include_dependencies
62
- add_source_dependencies(source)
44
+ def add_explicit_dependencies(dependency)
45
+ unless cookbook = dependency.cached_cookbook
46
+ return nil
63
47
  end
64
48
 
65
- sources
49
+ graph.populate_local(cookbook)
66
50
  end
67
51
 
68
- # Add the dependencies of the given source as sources in the collection of sources
69
- # on this instance of Resolver. Any dependencies which already have a source in the
70
- # collection of sources of the same name will not be added to the collection a second
71
- # time.
52
+ # An array of arrays containing the name and constraint of each demand
72
53
  #
73
- # @param [CookbookSource] source
74
- # source to convert dependencies into sources
54
+ # @note this is the format that Solve uses to determine a solution for the graph
75
55
  #
76
- # @return [Array<CookbookSource>]
77
- def add_source_dependencies(source)
78
- source.cached_cookbook.dependencies.each do |name, constraint|
79
- next if has_source?(name)
80
-
81
- add_source(CookbookSource.new(berksfile, name, constraint: constraint))
82
- end
56
+ # @return [Array<String, String>]
57
+ def demand_array
58
+ demands.collect { |demand| [ demand.name, demand.version_constraint ] }
83
59
  end
84
60
 
85
- # @return [Array<Berkshelf::CookbookSource>]
86
- # an array of CookbookSources that are currently added to this resolver
87
- def sources
88
- @sources.values
89
- end
90
-
91
- # Finds a solution for the currently added sources and their dependencies and
61
+ # Finds a solution for the currently added dependencies and their dependencies and
92
62
  # returns an array of CachedCookbooks.
93
63
  #
94
- # @return [Array<Berkshelf::CachedCookbook>]
64
+ # @raise [Berkshelf::NoSolutionError] when a solution could not be found for the given demands
65
+ #
66
+ # @return [Array<Array<String, String, Dependency>>]
95
67
  def resolve
96
- demands = [].tap do |l_demands|
97
- graph.artifacts.each do |artifact|
98
- l_demands << [ artifact.name, artifact.version ]
99
- end
100
- end
101
-
102
- solution = Solve.it!(graph, demands)
68
+ graph.populate_store
69
+ graph.populate(berksfile.sources)
103
70
 
104
- [].tap do |cached_cookbooks|
105
- solution.each do |name, version|
106
- cached_cookbooks << get_source(name).cached_cookbook
107
- end
71
+ Solve.it!(graph, demand_array).collect do |name, version|
72
+ dependency = get_demand(name) || Dependency.new(berksfile, name, locked_version: version)
73
+ [ name, version, dependency ]
108
74
  end
75
+ rescue Solve::Errors::NoSolutionError
76
+ raise Berkshelf::NoSolutionError.new(demands)
109
77
  end
110
78
 
111
- # @param [CookbookSource, #to_s] source
112
- # name of the source to return
79
+ # Retrieve the given demand from the resolver
113
80
  #
114
- # @return [Berkshelf::CookbookSource]
115
- def [](source)
116
- if source.is_a?(CookbookSource)
117
- source = source.name
118
- end
119
- @sources[source.to_s]
81
+ # @param [Berkshelf::Dependency, #to_s] demand
82
+ # name of the dependency to return
83
+ #
84
+ # @return [Berkshelf::Dependency]
85
+ def [](demand)
86
+ name = demand.respond_to?(:name) ? demand.name : demand.to_s
87
+ demands.find { |demand| demand.name == name }
120
88
  end
121
- alias_method :get_source, :[]
89
+ alias_method :get_demand, :[]
122
90
 
123
- # @param [CoobookSource, #to_s] source
124
- # the source to test if the resolver has added
125
- def has_source?(source)
126
- !get_source(source).nil?
91
+ # Check if the given demand has been added to the resolver
92
+ #
93
+ # @param [Berkshelf::Dependency, #to_s] demand
94
+ # the demand or the name of the demand to check for
95
+ def has_demand?(demand)
96
+ !get_demand(demand).nil?
127
97
  end
128
-
129
- private
130
-
131
- attr_reader :downloader
132
-
133
- # @param [Berkshelf::CookbookSource] source
134
- #
135
- # @return [Boolean]
136
- def install_source(source)
137
- cached_cookbook, location = downloader.download(source)
138
- Berkshelf.formatter.install(source.name, cached_cookbook.version, location)
139
- end
140
-
141
- # Use the given source to create a constraint solution if the source has been downloaded or can
142
- # be satisfied by a cached cookbook that is already present in the cookbook store.
143
- #
144
- # @note Git location sources which have not yet been downloaded will not be satisfied by a
145
- # cached cookbook from the cookbook store.
146
- #
147
- # @param [Berkshelf::CookbookSource] source
148
- #
149
- # @raise [ConstraintNotSatisfied] if the CachedCookbook does not satisfy the version constraint of
150
- # this instance of Location.
151
- # contain a cookbook that satisfies the given version constraint of this instance of
152
- # CookbookSource.
153
- #
154
- # @return [Boolean]
155
- def use_source(source)
156
- name = source.name
157
- locked = source.locked_version
158
- location = source.location
159
-
160
- # Lock the version constraint if a locked version was supplied
161
- source.version_constraint = Solve::Constraint.new(locked.to_s) if locked
162
- constraint = source.version_constraint
163
-
164
- if source.downloaded?
165
- cached = source.cached_cookbook
166
- location.validate_cached(cached) if location
167
- Berkshelf.formatter.use(name, cached.version, location)
168
- true
169
- elsif location.is_a?(GitLocation)
170
- false
171
- else
172
- # If a locked version if specified, it must exist
173
- if locked
174
- cached = downloader.cookbook_store.cookbook(name, locked)
175
- else
176
- cached = downloader.cookbook_store.satisfy(name, constraint)
177
- end
178
-
179
- if cached
180
- get_source(source).cached_cookbook = cached
181
- Berkshelf.formatter.use(name, cached.version)
182
- true
183
- else
184
- false
185
- end
186
- end
187
- end
188
98
  end
189
99
  end
@@ -0,0 +1,44 @@
1
+ module Berkshelf
2
+ class Resolver
3
+ class Graph < Solve::Graph
4
+ # @param [Berkshelf::CookbookStore] store
5
+ def populate_store(store = nil)
6
+ store ||= Berkshelf::CookbookStore.instance
7
+
8
+ store.cookbooks.each do |cookbook|
9
+ artifacts(cookbook.cookbook_name, cookbook.version)
10
+ end
11
+ end
12
+
13
+ def populate_local(cookbook)
14
+ name = cookbook.cookbook_name
15
+ version = cookbook.version
16
+
17
+ artifacts(name, version)
18
+ cookbook.dependencies.each do |dependency, constraint|
19
+ artifacts(name, version).depends(dependency, constraint)
20
+ end
21
+ end
22
+
23
+ # @param [Array<Berkshelf::Source>, Berkshelf::Source] sources
24
+ def populate(sources)
25
+ universe(sources).each do |cookbook|
26
+ artifacts(cookbook.name, cookbook.version)
27
+
28
+ cookbook.dependencies.each do |dependency, constraint|
29
+ artifacts(cookbook.name, cookbook.version).depends(dependency, constraint)
30
+ end
31
+ end
32
+ end
33
+
34
+ # @param [Array<Berkshelf::Source>, Berkshelf::Source] sources
35
+ #
36
+ # @return [Array<Berkshelf::RemoteCookbook>]
37
+ def universe(sources)
38
+ cookbooks = []
39
+ Array(sources).each { |source| cookbooks = cookbooks | source.universe }
40
+ cookbooks
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,55 @@
1
+ module Berkshelf
2
+ class Source
3
+ include Comparable
4
+
5
+ # @return [Berkshelf::SourceURI]
6
+ attr_reader :uri
7
+
8
+ # @param [String, Berkshelf::SourceURI] uri
9
+ def initialize(uri)
10
+ @uri = SourceURI.parse(uri)
11
+ @api_client = APIClient.new(uri)
12
+ end
13
+
14
+ # @return [Hash]
15
+ def universe
16
+ @universe ||= api_client.universe
17
+ end
18
+
19
+ # @param [String] name
20
+ # @param [String] version
21
+ #
22
+ # @return [APIClient::RemoteCookbook]
23
+ def cookbook(name, version)
24
+ universe.find { |cookbook| cookbook.name == name && cookbook.version == version }
25
+ end
26
+
27
+ # @param [String] name
28
+ #
29
+ # @return [APIClient::RemoteCookbook]
30
+ def latest(name)
31
+ versions(name).sort.last
32
+ end
33
+
34
+ # @param [String] name
35
+ #
36
+ # @return [Array<APIClient::RemoteCookbook>]
37
+ def versions(name)
38
+ universe.select { |cookbook| cookbook.name == name }
39
+ end
40
+
41
+ def to_s
42
+ uri.to_s
43
+ end
44
+
45
+ def ==(other)
46
+ return false unless other.is_a?(self.class)
47
+ uri == other.uri
48
+ end
49
+
50
+ private
51
+
52
+ # @return [Berkshelf::APIClient]
53
+ attr_reader :api_client
54
+ end
55
+ end
@@ -0,0 +1,38 @@
1
+ require 'addressable/uri'
2
+
3
+ module Berkshelf
4
+ class SourceURI < Addressable::URI
5
+ class << self
6
+ # Returns a URI object based on the parsed string.
7
+ #
8
+ # @param [String, Addressable::URI, #to_str] uri
9
+ # The URI string to parse.
10
+ # No parsing is performed if the object is already an
11
+ # <code>Addressable::URI</code>.
12
+ #
13
+ # @raise [Berkshelf::InvalidSourceURI]
14
+ #
15
+ # @return [Berkshelf::SourceURI]
16
+ def parse(uri)
17
+ parsed_uri = super(uri)
18
+ parsed_uri.send(:validate)
19
+ parsed_uri
20
+ rescue TypeError, ArgumentError => ex
21
+ raise InvalidSourceURI.new(uri, ex)
22
+ end
23
+ end
24
+
25
+ VALID_SCHEMES = [ "http", "https" ].freeze
26
+
27
+ # @raise [Berkshelf::InvalidSourceURI]
28
+ def validate
29
+ super
30
+
31
+ unless VALID_SCHEMES.include?(self.scheme)
32
+ raise InvalidSourceURI.new(self, "invalid URI scheme '#{self.scheme}'. Valid schemes: #{VALID_SCHEMES}")
33
+ end
34
+ rescue Addressable::URI::InvalidURIError => ex
35
+ raise InvalidSourceURI.new(self, ex)
36
+ end
37
+ end
38
+ end