berkshelf 0.4.0.rc4 → 0.4.0

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 (56) hide show
  1. data/.gitignore +2 -0
  2. data/Guardfile +6 -3
  3. data/features/default_locations.feature +122 -0
  4. data/features/install.feature +20 -4
  5. data/features/lockfile.feature +1 -6
  6. data/features/update.feature +2 -3
  7. data/generator_files/Berksfile.erb +2 -0
  8. data/generator_files/gitignore.erb +6 -0
  9. data/lib/berkshelf.rb +6 -10
  10. data/lib/berkshelf/berksfile.rb +203 -14
  11. data/lib/berkshelf/cached_cookbook.rb +5 -1
  12. data/lib/berkshelf/cli.rb +4 -0
  13. data/lib/berkshelf/cookbook_source.rb +49 -91
  14. data/lib/berkshelf/cookbook_store.rb +2 -0
  15. data/lib/berkshelf/downloader.rb +71 -51
  16. data/lib/berkshelf/errors.rb +7 -3
  17. data/lib/berkshelf/formatters.rb +6 -6
  18. data/lib/berkshelf/location.rb +171 -0
  19. data/lib/berkshelf/locations/chef_api_location.rb +252 -0
  20. data/lib/berkshelf/locations/git_location.rb +76 -0
  21. data/lib/berkshelf/locations/path_location.rb +38 -0
  22. data/lib/berkshelf/locations/site_location.rb +150 -0
  23. data/lib/berkshelf/lockfile.rb +2 -2
  24. data/lib/berkshelf/resolver.rb +12 -15
  25. data/lib/berkshelf/uploader.rb +2 -9
  26. data/lib/berkshelf/version.rb +1 -1
  27. data/spec/fixtures/lockfile_spec/without_lock/.gitkeep +0 -0
  28. data/spec/support/chef_api.rb +7 -1
  29. data/spec/unit/berkshelf/berksfile_spec.rb +157 -12
  30. data/spec/unit/berkshelf/cached_cookbook_spec.rb +19 -0
  31. data/spec/unit/berkshelf/cookbook_generator_spec.rb +1 -0
  32. data/spec/unit/berkshelf/cookbook_source_spec.rb +25 -35
  33. data/spec/unit/berkshelf/cookbook_store_spec.rb +3 -3
  34. data/spec/unit/berkshelf/downloader_spec.rb +171 -43
  35. data/spec/unit/berkshelf/formatters_spec.rb +13 -16
  36. data/spec/unit/berkshelf/{cookbook_source/location_spec.rb → location_spec.rb} +10 -10
  37. data/spec/unit/berkshelf/{cookbook_source → locations}/chef_api_location_spec.rb +4 -4
  38. data/spec/unit/berkshelf/{cookbook_source → locations}/git_location_spec.rb +8 -8
  39. data/spec/unit/berkshelf/{cookbook_source → locations}/path_location_spec.rb +5 -5
  40. data/spec/unit/berkshelf/{cookbook_source → locations}/site_location_spec.rb +17 -3
  41. data/spec/unit/berkshelf/lockfile_spec.rb +26 -17
  42. data/spec/unit/berkshelf/resolver_spec.rb +6 -5
  43. data/spec/unit/berkshelf/uploader_spec.rb +6 -4
  44. metadata +27 -31
  45. data/lib/berkshelf/cookbook_source/chef_api_location.rb +0 -256
  46. data/lib/berkshelf/cookbook_source/git_location.rb +0 -78
  47. data/lib/berkshelf/cookbook_source/location.rb +0 -167
  48. data/lib/berkshelf/cookbook_source/path_location.rb +0 -40
  49. data/lib/berkshelf/cookbook_source/site_location.rb +0 -149
  50. data/lib/berkshelf/dsl.rb +0 -45
  51. data/lib/berkshelf/tx_result.rb +0 -12
  52. data/lib/berkshelf/tx_result_set.rb +0 -37
  53. data/spec/fixtures/lockfile_spec/without_lock/Berksfile.lock +0 -5
  54. data/spec/unit/berkshelf/dsl_spec.rb +0 -42
  55. data/spec/unit/berkshelf/tx_result_set_spec.rb +0 -77
  56. data/spec/unit/berkshelf/tx_result_spec.rb +0 -21
@@ -1,78 +0,0 @@
1
- module Berkshelf
2
- class CookbookSource
3
- # @author Jamie Winsor <jamie@vialstudios.com>
4
- class GitLocation
5
- include Location
6
-
7
- location_key :git
8
- valid_options :ref, :branch, :tag
9
-
10
- attr_accessor :uri
11
- attr_accessor :branch
12
-
13
- alias_method :ref, :branch
14
- alias_method :tag, :branch
15
-
16
- # @param [#to_s] name
17
- # @param [Solve::Constraint] version_constraint
18
- # @param [Hash] options
19
- #
20
- # @option options [String] :git
21
- # the Git URL to clone
22
- # @option options [String] :ref
23
- # the commit hash or an alias to a commit hash to clone
24
- # @option options [String] :branch
25
- # same as ref
26
- # @option options [String] :tag
27
- # same as tag
28
- def initialize(name, version_constraint, options = {})
29
- @name = name
30
- @version_constraint = version_constraint
31
- @uri = options[:git]
32
- @branch = options[:branch] || options[:ref] || options[:tag]
33
-
34
- Git.validate_uri!(@uri)
35
- end
36
-
37
- # @param [#to_s] destination
38
- #
39
- # @return [Berkshelf::CachedCookbook]
40
- def download(destination)
41
- tmp_clone = Dir.mktmpdir
42
- ::Berkshelf::Git.clone(uri, tmp_clone)
43
- ::Berkshelf::Git.checkout(tmp_clone, branch) if branch
44
- unless branch
45
- self.branch = ::Berkshelf::Git.rev_parse(tmp_clone)
46
- end
47
-
48
- unless File.chef_cookbook?(tmp_clone)
49
- msg = "Cookbook '#{name}' not found at git: #{uri}"
50
- msg << " with branch '#{branch}'" if branch
51
- raise CookbookNotFound, msg
52
- end
53
-
54
- cb_path = File.join(destination, "#{self.name}-#{Git.rev_parse(tmp_clone)}")
55
- FileUtils.rm_rf(cb_path)
56
- FileUtils.mv(tmp_clone, cb_path, force: true)
57
-
58
- cached = CachedCookbook.from_store_path(cb_path)
59
- validate_cached(cached)
60
-
61
- set_downloaded_status(true)
62
- cached
63
- end
64
-
65
- def to_s
66
- s = "git: '#{uri}'"
67
- s << " with branch: '#{branch}'" if branch
68
- s
69
- end
70
-
71
- private
72
-
73
- def git
74
- @git ||= Berkshelf::Git.new(uri)
75
- end
76
- end
77
- end
78
- end
@@ -1,167 +0,0 @@
1
- module Berkshelf
2
- class CookbookSource
3
- # @author Jamie Winsor <jamie@vialstudios.com>
4
- module Location
5
- module ClassMethods
6
- # Register the location key for the including source location with CookbookSource
7
- #
8
- # @param [Symbol] key
9
- def location_key(key)
10
- CookbookSource.add_location_key(key, self)
11
- end
12
-
13
- # Register a valid option or multiple options with the CookbookSource class
14
- #
15
- # @param [Symbol] opts
16
- def valid_options(*opts)
17
- Array(opts).each do |opt|
18
- CookbookSource.add_valid_option(opt)
19
- end
20
- end
21
-
22
- # Returns an array where the first element is string representing the best version
23
- # for the given constraint and the second element is the URI to where the corresponding
24
- # version of the Cookbook can be downloaded from
25
- #
26
- # @example:
27
- # constraint = Solve::Constraint.new("~> 0.101.2")
28
- # versions = {
29
- # "1.0.0" => "http://cookbooks.opscode.com/api/v1/cookbooks/nginx/versions/1_0_0",
30
- # "2.0.0" => "http://cookbooks.opscode.com/api/v1/cookbooks/nginx/versions/2_0_0"
31
- # }
32
- #
33
- # subject.solve_for_constraint(versions, constraint) =>
34
- # [ "2.0.0", "http://cookbooks.opscode.com/api/v1/cookbooks/nginx/versions/2_0_0" ]
35
- #
36
- # @param [String, Solve::Constraint] constraint
37
- # version constraint to solve for
38
- #
39
- # @param [Hash] versions
40
- # a hash where the keys are a string representing a cookbook version and the values
41
- # are the download URL for the cookbook version.
42
- #
43
- # @return [Array, nil]
44
- def solve_for_constraint(constraint, versions)
45
- graph = Solve::Graph.new
46
- name = "none"
47
-
48
- versions.each do |version, uri|
49
- graph.artifacts(name, version)
50
- end
51
-
52
- graph.demands(name, constraint)
53
- result = Solve.it(graph)
54
-
55
- return nil if result.nil?
56
-
57
- version = result[name]
58
-
59
- [ version, versions[version] ]
60
- end
61
- end
62
-
63
- class << self
64
- def included(base)
65
- base.send :extend, ClassMethods
66
- end
67
-
68
- # Creates a new instance of a Class implementing Location with the given name and
69
- # constraint. Which Class to instantiated is determined by the values in the given
70
- # options Hash. Source Locations have an associated location_key registered with
71
- # CookbookSource. If your options Hash contains a key matching one of these location_keys
72
- # then the Class who registered that location_key will be instantiated. If you do not
73
- # provide an option with a matching location_key a SiteLocation class will be
74
- # instantiated.
75
- #
76
- # @example
77
- # Location.init("nginx", ">= 0.0.0", git: "git://github.com/RiotGames/artifact-cookbook.git") =>
78
- # instantiates a GitLocation
79
- #
80
- # Location.init("nginx", ">= 0.0.0", path: "/Users/reset/code/nginx-cookbook") =>
81
- # instantiates a PathLocation
82
- #
83
- # Location.init("nginx", ">= 0.0.0", site: "http://cookbooks.opscode.com/api/v1/cookbooks") =>
84
- # instantiates a SiteLocation
85
- #
86
- # Location.init("nginx", ">= 0.0.0", chef_api: "https://api.opscode.com/organizations/vialstudios") =>
87
- # instantiates a ChefAPILocation
88
- #
89
- # Location.init("nginx", ">= 0.0.0") =>
90
- # instantiates a SiteLocation
91
- #
92
- # @param [String] name
93
- # @param [String, Solve::Constraint] constraint
94
- # @param [Hash] options
95
- #
96
- # @return [SiteLocation, PathLocation, GitLocation, ChefAPILocation]
97
- def init(name, constraint, options = {})
98
- klass = klass_from_options(options)
99
-
100
- klass.new(name, constraint, options)
101
- end
102
-
103
- private
104
-
105
- def klass_from_options(options)
106
- location_keys = (options.keys & CookbookSource.location_keys.keys)
107
- if location_keys.length > 1
108
- location_keys.collect! { |opt| "'#{opt}'" }
109
- raise InternalError, "Only one location key (#{CookbookSource.location_keys.keys.join(', ')}) may be specified. You gave #{location_keys.join(', ')}."
110
- end
111
-
112
- if location_keys.empty?
113
- SiteLocation
114
- else
115
- CookbookSource.location_keys[location_keys.first]
116
- end
117
- end
118
- end
119
-
120
- attr_reader :name
121
- attr_reader :version_constraint
122
-
123
- # @param [#to_s] name
124
- # @param [Solve::Constraint] version_constraint
125
- # @param [Hash] options
126
- def initialize(name, version_constraint, options = {})
127
- @name = name
128
- @version_constraint = version_constraint
129
- @downloaded_status = false
130
- end
131
-
132
- # @param [#to_s] destination
133
- #
134
- # @return [Berkshelf::CachedCookbook]
135
- def download(destination)
136
- raise NotImplementedError, "Function must be implemented on includer"
137
- end
138
-
139
- # @return [Boolean]
140
- def downloaded?
141
- @downloaded_status
142
- end
143
-
144
- # Ensures that the given CachedCookbook satisfies the constraint
145
- #
146
- # @param [CachedCookbook] cached_cookbook
147
- #
148
- # @raise [ConstraintNotSatisfied] if the CachedCookbook does not satisfy the version constraint of
149
- # this instance of Location.
150
- #
151
- # @return [Boolean]
152
- def validate_cached(cached_cookbook)
153
- unless version_constraint.satisfies?(cached_cookbook.version)
154
- raise ConstraintNotSatisfied, "A cookbook satisfying '#{name}' (#{version_constraint}) not found at #{self}"
155
- end
156
-
157
- true
158
- end
159
-
160
- private
161
-
162
- def set_downloaded_status(state)
163
- @downloaded_status = state
164
- end
165
- end
166
- end
167
- end
@@ -1,40 +0,0 @@
1
- module Berkshelf
2
- class CookbookSource
3
- # @author Jamie Winsor <jamie@vialstudios.com>
4
- class PathLocation
5
- include Location
6
-
7
- location_key :path
8
-
9
- attr_accessor :path
10
-
11
- # @param [#to_s] name
12
- # @param [Solve::Constraint] version_constraint
13
- # @param [Hash] options
14
- #
15
- # @option options [String] :path
16
- # a filepath to the cookbook on your local disk
17
- def initialize(name, version_constraint, options = {})
18
- @name = name
19
- @version_constraint = version_constraint
20
- @path = File.expand_path(options[:path])
21
- set_downloaded_status(true)
22
- end
23
-
24
- # @param [#to_s] destination
25
- #
26
- # @return [Berkshelf::CachedCookbook]
27
- def download(destination)
28
- cached = CachedCookbook.from_path(path)
29
- validate_cached(cached)
30
-
31
- set_downloaded_status(true)
32
- cached
33
- end
34
-
35
- def to_s
36
- "path: '#{path}'"
37
- end
38
- end
39
- end
40
- end
@@ -1,149 +0,0 @@
1
- module Berkshelf
2
- class CookbookSource
3
- # @author Jamie Winsor <jamie@vialstudios.com>
4
- class SiteLocation
5
- include Location
6
-
7
- location_key :site
8
-
9
- attr_reader :api_uri
10
- attr_accessor :version_constraint
11
-
12
- OPSCODE_COMMUNITY_API = 'http://cookbooks.opscode.com/api/v1/cookbooks'.freeze
13
-
14
- class << self
15
- # @param [String] target
16
- # file path to the tar.gz archive on disk
17
- # @param [String] destination
18
- # file path to extract the contents of the target to
19
- def unpack(target, destination)
20
- Archive::Tar::Minitar.unpack(Zlib::GzipReader.new(File.open(target, 'rb')), destination)
21
- end
22
- end
23
-
24
- # @param [#to_s] name
25
- # @param [Solve::Constraint] version_constraint
26
- # @param [Hash] options
27
- #
28
- # @option options [String] :site
29
- # a URL pointing to a community API endpoint
30
- def initialize(name, version_constraint, options = {})
31
- options[:site] ||= OPSCODE_COMMUNITY_API
32
-
33
- @name = name
34
- @version_constraint = version_constraint
35
- @api_uri = options[:site]
36
- @rest = Chef::REST.new(api_uri, false, false)
37
- end
38
-
39
- # @param [#to_s] destination
40
- #
41
- # @return [Berkshelf::CachedCookbook]
42
- def download(destination)
43
- version, uri = target_version
44
- remote_file = rest.get_rest(uri)['file']
45
-
46
- downloaded_tf = rest.get_rest(remote_file, true)
47
-
48
- dir = Dir.mktmpdir
49
- cb_path = File.join(destination, "#{name}-#{version}")
50
-
51
- self.class.unpack(downloaded_tf.path, dir)
52
- FileUtils.mv(File.join(dir, name), cb_path, force: true)
53
-
54
- cached = CachedCookbook.from_store_path(cb_path)
55
- validate_cached(cached)
56
-
57
- set_downloaded_status(true)
58
- cached
59
- end
60
-
61
- # Returns a hash of all the cookbook versions found at communite site URL for the cookbook
62
- # name of this location.
63
- #
64
- # @example
65
- # {
66
- # "0.101.2" => "http://cookbooks.opscode.com/api/v1/cookbooks/nginx/versions/0_101_2",
67
- # "0.101.0" => "http://cookbooks.opscode.com/api/v1/cookbooks/nginx/versions/0_101_0"
68
- # }
69
- #
70
- # @return [Hash]
71
- # a hash representing the cookbook versions on at a Chef API for location's cookbook.
72
- # The keys are version strings and the values are URLs to download the cookbook version.
73
- def versions
74
- {}.tap do |versions|
75
- rest.get_rest(name)['versions'].each do |uri|
76
- version = version_from_uri(File.basename(uri))
77
-
78
- versions[version] = uri
79
- end
80
- end
81
- rescue Net::HTTPServerException => e
82
- if e.response.code == "404"
83
- raise CookbookNotFound, "Cookbook '#{name}' not found at site: '#{api_uri}'"
84
- else
85
- raise
86
- end
87
- end
88
-
89
- # Returns the latest version of the cookbook and it's download link.
90
- #
91
- # @example
92
- # [ "0.101.2" => "https://api.opscode.com/organizations/vialstudios/cookbooks/nginx/0.101.2" ]
93
- #
94
- # @return [Array]
95
- # an array containing the version and download URL for the cookbook version that
96
- # should be downloaded for this location.
97
- def latest_version
98
- quietly {
99
- uri = rest.get_rest(name)['latest_version']
100
-
101
- [ version_from_uri(uri), uri ]
102
- }
103
- rescue Net::HTTPServerException => e
104
- if e.response.code == "404"
105
- raise CookbookNotFound, "Cookbook '#{name}' not found at site: '#{api_uri}'"
106
- else
107
- raise
108
- end
109
- end
110
-
111
- def to_s
112
- "site: '#{api_uri}'"
113
- end
114
-
115
- private
116
-
117
- attr_reader :rest
118
-
119
- def uri_escape_version(version)
120
- version.gsub('.', '_')
121
- end
122
-
123
- def version_from_uri(latest_version)
124
- File.basename(latest_version).gsub('_', '.')
125
- end
126
-
127
- # Returns an array containing the version and download URL for the cookbook version that
128
- # should be downloaded for this location.
129
- #
130
- # @example
131
- # [ "0.101.2" => "https://api.opscode.com/organizations/vialstudios/cookbooks/nginx/0.101.2" ]
132
- #
133
- # @return [Array]
134
- def target_version
135
- if version_constraint
136
- solution = self.class.solve_for_constraint(version_constraint, versions)
137
-
138
- unless solution
139
- raise NoSolution, "No cookbook version of '#{name}' found at #{self} that would satisfy constraint (#{version_constraint})."
140
- end
141
-
142
- solution
143
- else
144
- latest_version
145
- end
146
- end
147
- end
148
- end
149
- end
@@ -1,45 +0,0 @@
1
- module Berkshelf
2
- module DSL
3
- @@active_group = nil
4
-
5
- def cookbook(*args)
6
- source = CookbookSource.new(*args)
7
- source.add_group(@@active_group) if @@active_group
8
- add_source(source)
9
- end
10
-
11
- def group(*args)
12
- @@active_group = args
13
- yield
14
- @@active_group = nil
15
- end
16
-
17
- def metadata(options = {})
18
- path = options[:path] || File.dirname(filepath)
19
-
20
- metadata_file = Berkshelf.find_metadata(path)
21
-
22
- unless metadata_file
23
- raise CookbookNotFound, "No 'metadata.rb' found at #{path}"
24
- end
25
-
26
- metadata = Chef::Cookbook::Metadata.new
27
- metadata.from_file(metadata_file.to_s)
28
-
29
- name = if metadata.name.empty? || metadata.name.nil?
30
- File.basename(File.dirname(metadata_file))
31
- else
32
- metadata.name
33
- end
34
-
35
- source = CookbookSource.new(name, path: File.dirname(metadata_file))
36
- add_source(source)
37
- end
38
-
39
- private
40
-
41
- def filepath
42
- File.join(File.expand_path('.'), "DSLFile")
43
- end
44
- end
45
- end