berkshelf 2.0.18 → 3.0.0.beta1

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