berkshelf 0.4.0.rc4 → 0.4.0

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