berkshelf 0.3.3 → 0.3.7
Sign up to get free protection for your applications and to get access to all the features.
- data/Guardfile +1 -1
- data/features/install.feature +129 -2
- data/features/lockfile.feature +1 -1
- data/features/step_definitions/filesystem_steps.rb +12 -0
- data/features/update.feature +1 -1
- data/features/upload_command.feature +1 -1
- data/lib/berkshelf/berksfile.rb +4 -1
- data/lib/berkshelf/cached_cookbook.rb +36 -9
- data/lib/berkshelf/cli.rb +2 -2
- data/lib/berkshelf/cookbook_source.rb +26 -63
- data/lib/berkshelf/cookbook_source/git_location.rb +21 -10
- data/lib/berkshelf/cookbook_source/location.rb +50 -0
- data/lib/berkshelf/cookbook_source/path_location.rb +13 -5
- data/lib/berkshelf/cookbook_source/site_location.rb +14 -4
- data/lib/berkshelf/cookbook_store.rb +34 -16
- data/lib/berkshelf/core_ext/string.rb +12 -0
- data/lib/berkshelf/downloader.rb +0 -4
- data/lib/berkshelf/errors.rb +41 -1
- data/lib/berkshelf/git.rb +83 -14
- data/lib/berkshelf/resolver.rb +85 -66
- data/lib/berkshelf/version.rb +1 -1
- data/spec/fixtures/cookbooks/example_cookbook-0.5.0/metadata.rb +1 -0
- data/spec/fixtures/cookbooks/example_metadata_name/metadata.rb +2 -0
- data/spec/fixtures/cookbooks/example_metadata_no_name/metadata.rb +1 -0
- data/spec/fixtures/cookbooks/example_no_metadata/recipes/default.rb +1 -0
- data/spec/support/chef_api.rb +42 -0
- data/spec/unit/berkshelf/cached_cookbook_spec.rb +55 -11
- data/spec/unit/berkshelf/cookbook_source/git_location_spec.rb +65 -15
- data/spec/unit/berkshelf/cookbook_source/location_spec.rb +42 -0
- data/spec/unit/berkshelf/cookbook_source/path_location_spec.rb +22 -5
- data/spec/unit/berkshelf/cookbook_source/site_location_spec.rb +32 -13
- data/spec/unit/berkshelf/cookbook_source_spec.rb +24 -33
- data/spec/unit/berkshelf/cookbook_store_spec.rb +47 -7
- data/spec/unit/berkshelf/git_spec.rb +97 -12
- data/spec/unit/berkshelf/resolver_spec.rb +72 -48
- data/spec/unit/berkshelf/uploader_spec.rb +12 -4
- metadata +13 -7
- data/spec/fixtures/cookbooks/invalid_ruby_files-1.0.0/recipes/default.rb +0 -1
- data/spec/fixtures/cookbooks/invalid_template_files-1.0.0/templates/default/broken.erb +0 -1
@@ -7,12 +7,24 @@ module Berkshelf
|
|
7
7
|
attr_accessor :uri
|
8
8
|
attr_accessor :branch
|
9
9
|
|
10
|
-
|
10
|
+
alias_method :ref, :branch
|
11
|
+
alias_method :tag, :branch
|
12
|
+
|
13
|
+
# @param [#to_s] name
|
14
|
+
# @param [DepSelector::VersionConstraint] version_constraint
|
15
|
+
# @param [Hash] options
|
16
|
+
def initialize(name, version_constraint, options)
|
11
17
|
@name = name
|
18
|
+
@version_constraint = version_constraint
|
12
19
|
@uri = options[:git]
|
13
20
|
@branch = options[:branch] || options[:ref] || options[:tag]
|
21
|
+
|
22
|
+
Git.validate_uri!(@uri)
|
14
23
|
end
|
15
24
|
|
25
|
+
# @param [#to_s] destination
|
26
|
+
#
|
27
|
+
# @return [Berkshelf::CachedCookbook]
|
16
28
|
def download(destination)
|
17
29
|
tmp_clone = Dir.mktmpdir
|
18
30
|
::Berkshelf::Git.clone(uri, tmp_clone)
|
@@ -27,20 +39,19 @@ module Berkshelf
|
|
27
39
|
raise CookbookNotFound, msg
|
28
40
|
end
|
29
41
|
|
30
|
-
cb_path = File.join(destination, "#{self.name}-#{
|
31
|
-
|
32
|
-
|
42
|
+
cb_path = File.join(destination, "#{self.name}-#{Git.rev_parse(tmp_clone)}")
|
43
|
+
FileUtils.mv(tmp_clone, cb_path, force: true)
|
44
|
+
|
45
|
+
cached = CachedCookbook.from_store_path(cb_path)
|
46
|
+
validate_cached(cached)
|
33
47
|
|
34
|
-
|
35
|
-
|
36
|
-
msg = "Cookbook '#{name}' not found at git: #{uri}"
|
37
|
-
msg << " with branch '#{branch}'" if branch
|
38
|
-
raise CookbookNotFound, msg
|
48
|
+
set_downloaded_status(true)
|
49
|
+
cached
|
39
50
|
end
|
40
51
|
|
41
52
|
def to_s
|
42
53
|
s = "git: '#{uri}'"
|
43
|
-
s << " with branch '#{branch}'" if branch
|
54
|
+
s << " with branch: '#{branch}'" if branch
|
44
55
|
s
|
45
56
|
end
|
46
57
|
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module Berkshelf
|
2
|
+
class CookbookSource
|
3
|
+
# @author Jamie Winsor <jamie@vialstudios.com>
|
4
|
+
module Location
|
5
|
+
attr_reader :name
|
6
|
+
attr_reader :version_constraint
|
7
|
+
|
8
|
+
# @param [#to_s] name
|
9
|
+
def initialize(name, version_constraint)
|
10
|
+
@name = name
|
11
|
+
@version_constraint = version_constraint
|
12
|
+
@downloaded_status = false
|
13
|
+
end
|
14
|
+
|
15
|
+
# @param [#to_s] destination
|
16
|
+
#
|
17
|
+
# @return [Berkshelf::CachedCookbook]
|
18
|
+
def download(destination)
|
19
|
+
raise NotImplementedError, "Function must be implemented on includer"
|
20
|
+
end
|
21
|
+
|
22
|
+
# @return [Boolean]
|
23
|
+
def downloaded?
|
24
|
+
@downloaded_status
|
25
|
+
end
|
26
|
+
|
27
|
+
# Ensures that the given CachedCookbook satisfies the constraint
|
28
|
+
#
|
29
|
+
# @param [CachedCookbook] cached_cookbook
|
30
|
+
#
|
31
|
+
# @raise [ConstraintNotSatisfied] if the CachedCookbook does not satisfy the version constraint of
|
32
|
+
# this instance of Location.
|
33
|
+
#
|
34
|
+
# @return [Boolean]
|
35
|
+
def validate_cached(cached_cookbook)
|
36
|
+
unless version_constraint.include?(cached_cookbook.version)
|
37
|
+
raise ConstraintNotSatisfied, "A cookbook satisfying '#{name}' (#{version_constraint}) not found at #{self}"
|
38
|
+
end
|
39
|
+
|
40
|
+
true
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
def set_downloaded_status(state)
|
46
|
+
@downloaded_status = state
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -6,17 +6,25 @@ module Berkshelf
|
|
6
6
|
|
7
7
|
attr_accessor :path
|
8
8
|
|
9
|
-
|
9
|
+
# @param [#to_s] name
|
10
|
+
# @param [DepSelector::VersionConstraint] version_constraint
|
11
|
+
# @param [Hash] options
|
12
|
+
def initialize(name, version_constraint, options = {})
|
10
13
|
@name = name
|
14
|
+
@version_constraint = version_constraint
|
11
15
|
@path = File.expand_path(options[:path])
|
16
|
+
set_downloaded_status(true)
|
12
17
|
end
|
13
18
|
|
19
|
+
# @param [#to_s] destination
|
20
|
+
#
|
21
|
+
# @return [Berkshelf::CachedCookbook]
|
14
22
|
def download(destination)
|
15
|
-
|
16
|
-
|
17
|
-
end
|
23
|
+
cached = CachedCookbook.from_path(path)
|
24
|
+
validate_cached(cached)
|
18
25
|
|
19
|
-
|
26
|
+
set_downloaded_status(true)
|
27
|
+
cached
|
20
28
|
end
|
21
29
|
|
22
30
|
def to_s
|
@@ -53,14 +53,20 @@ module Berkshelf
|
|
53
53
|
end
|
54
54
|
end
|
55
55
|
|
56
|
-
|
56
|
+
# @param [#to_s] name
|
57
|
+
# @param [DepSelector::VersionConstraint] version_constraint
|
58
|
+
# @param [Hash] options
|
59
|
+
def initialize(name, version_constraint, options = {})
|
57
60
|
options[:site] ||= OPSCODE_COMMUNITY_API
|
58
61
|
|
59
62
|
@name = name
|
60
|
-
@version_constraint =
|
63
|
+
@version_constraint = version_constraint
|
61
64
|
@api_uri = options[:site]
|
62
65
|
end
|
63
66
|
|
67
|
+
# @param [#to_s] destination
|
68
|
+
#
|
69
|
+
# @return [Berkshelf::CachedCookbook]
|
64
70
|
def download(destination)
|
65
71
|
version, uri = target_version
|
66
72
|
remote_file = rest.get_rest(uri)['file']
|
@@ -70,9 +76,13 @@ module Berkshelf
|
|
70
76
|
cb_path = File.join(destination, "#{name}-#{version}")
|
71
77
|
|
72
78
|
self.class.unpack(downloaded_tf.path, dir)
|
73
|
-
FileUtils.mv(File.join(dir, name), cb_path, :
|
79
|
+
FileUtils.mv(File.join(dir, name), cb_path, force: true)
|
74
80
|
|
75
|
-
cb_path
|
81
|
+
cached = CachedCookbook.from_store_path(cb_path)
|
82
|
+
validate_cached(cached)
|
83
|
+
|
84
|
+
set_downloaded_status(true)
|
85
|
+
cached
|
76
86
|
end
|
77
87
|
|
78
88
|
# @return [Array]
|
@@ -3,6 +3,9 @@ require 'fileutils'
|
|
3
3
|
module Berkshelf
|
4
4
|
# @author Jamie Winsor <jamie@vialstudios.com>
|
5
5
|
class CookbookStore
|
6
|
+
include DepSelector
|
7
|
+
include DepSelector::Exceptions
|
8
|
+
|
6
9
|
attr_reader :storage_path
|
7
10
|
|
8
11
|
# Create a new instance of CookbookStore with the given
|
@@ -24,24 +27,29 @@ module Berkshelf
|
|
24
27
|
# @param [String] version
|
25
28
|
# version of the Cookbook you want to retrieve
|
26
29
|
#
|
27
|
-
# @return [Berkshelf::CachedCookbook]
|
30
|
+
# @return [Berkshelf::CachedCookbook, nil]
|
28
31
|
def cookbook(name, version)
|
29
|
-
return nil unless downloaded?(name, version)
|
30
|
-
|
31
32
|
path = cookbook_path(name, version)
|
32
|
-
|
33
|
+
return nil unless path.cookbook?
|
34
|
+
|
35
|
+
CachedCookbook.from_store_path(path)
|
33
36
|
end
|
34
37
|
|
35
|
-
# Returns an array of
|
36
|
-
#
|
38
|
+
# Returns an array of the Cookbooks that have been cached to the
|
39
|
+
# storage_path of this instance of CookbookStore. Passing the filter
|
40
|
+
# option will return only the CachedCookbooks whose name match the
|
41
|
+
# filter.
|
37
42
|
#
|
38
43
|
# @return [Array<Berkshelf::CachedCookbook>]
|
39
|
-
def cookbooks
|
44
|
+
def cookbooks(filter = nil)
|
40
45
|
[].tap do |cookbooks|
|
41
46
|
storage_path.each_child do |p|
|
42
|
-
cached_cookbook = CachedCookbook.
|
47
|
+
cached_cookbook = CachedCookbook.from_store_path(p)
|
43
48
|
|
44
|
-
|
49
|
+
next unless cached_cookbook
|
50
|
+
next if filter && cached_cookbook.cookbook_name != filter
|
51
|
+
|
52
|
+
cookbooks << cached_cookbook
|
45
53
|
end
|
46
54
|
end
|
47
55
|
end
|
@@ -57,15 +65,25 @@ module Berkshelf
|
|
57
65
|
storage_path.join("#{name}-#{version}")
|
58
66
|
end
|
59
67
|
|
60
|
-
#
|
61
|
-
#
|
68
|
+
# Return a CachedCookbook matching the best solution for the given name and
|
69
|
+
# constraint. Nil is returned if no matching CachedCookbook is found.
|
62
70
|
#
|
63
|
-
# @param [
|
64
|
-
# @param [
|
71
|
+
# @param [#to_s] name
|
72
|
+
# @param [DepSelector::VersionConstraint] constraint
|
65
73
|
#
|
66
|
-
# @return [
|
67
|
-
def
|
68
|
-
|
74
|
+
# @return [Berkshelf::CachedCookbook, nil]
|
75
|
+
def satisfy(name, constraint)
|
76
|
+
graph = DependencyGraph.new
|
77
|
+
selector = Selector.new(graph)
|
78
|
+
package = graph.package(name)
|
79
|
+
solution_constraints = [ SolutionConstraint.new(graph.package(name), constraint) ]
|
80
|
+
|
81
|
+
cookbooks(name).each { |cookbook| package.add_version(Version.new(cookbook.version)) }
|
82
|
+
name, version = quietly { selector.find_solution(solution_constraints).first }
|
83
|
+
|
84
|
+
cookbook(name, version)
|
85
|
+
rescue InvalidSolutionConstraints, NoSolutionExists
|
86
|
+
nil
|
69
87
|
end
|
70
88
|
|
71
89
|
private
|
@@ -0,0 +1,12 @@
|
|
1
|
+
class String
|
2
|
+
# Separates the string on the given separator and prepends the given
|
3
|
+
# value to each and returns a new string from the result.
|
4
|
+
#
|
5
|
+
# @param [String] separator
|
6
|
+
# @param [String] value
|
7
|
+
#
|
8
|
+
# @return [String]
|
9
|
+
def prepend_each(separator, value)
|
10
|
+
lines(separator).collect { |x| value + x }.join
|
11
|
+
end
|
12
|
+
end
|
data/lib/berkshelf/downloader.rb
CHANGED
data/lib/berkshelf/errors.rb
CHANGED
@@ -1,21 +1,61 @@
|
|
1
1
|
module Berkshelf
|
2
2
|
class BerkshelfError < StandardError
|
3
3
|
class << self
|
4
|
+
# @param [Integer] code
|
4
5
|
def status_code(code)
|
5
6
|
define_method(:status_code) { code }
|
6
7
|
define_singleton_method(:status_code) { code }
|
7
8
|
end
|
8
9
|
end
|
10
|
+
|
11
|
+
alias_method :message, :to_s
|
9
12
|
end
|
10
13
|
|
11
14
|
class BerksfileNotFound < BerkshelfError; status_code(100); end
|
12
15
|
class NoVersionForConstraints < BerkshelfError; status_code(101); end
|
13
16
|
class DownloadFailure < BerkshelfError; status_code(102); end
|
14
17
|
class CookbookNotFound < BerkshelfError; status_code(103); end
|
15
|
-
class GitError < BerkshelfError
|
18
|
+
class GitError < BerkshelfError
|
19
|
+
status_code(104)
|
20
|
+
attr_reader :stderr
|
21
|
+
|
22
|
+
def initialize(stderr)
|
23
|
+
@stderr = stderr
|
24
|
+
end
|
25
|
+
|
26
|
+
def to_s
|
27
|
+
out = "An error occured during Git execution:\n"
|
28
|
+
out << stderr.prepend_each("\n", "\t")
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
16
32
|
class DuplicateSourceDefined < BerkshelfError; status_code(105); end
|
17
33
|
class NoSolution < BerkshelfError; status_code(106); end
|
18
34
|
class CookbookSyntaxError < BerkshelfError; status_code(107); end
|
19
35
|
class UploadFailure < BerkshelfError; status_code(108); end
|
20
36
|
class KnifeConfigNotFound < BerkshelfError; status_code(109); end
|
37
|
+
|
38
|
+
class InvalidGitURI < BerkshelfError
|
39
|
+
status_code(110)
|
40
|
+
attr_reader :uri
|
41
|
+
|
42
|
+
# @param [String] uri
|
43
|
+
def initialize(uri)
|
44
|
+
@uri = uri
|
45
|
+
end
|
46
|
+
|
47
|
+
def to_s
|
48
|
+
"'#{uri}' is not a valid Git URI."
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
class GitNotFound < BerkshelfError
|
53
|
+
status_code(110)
|
54
|
+
|
55
|
+
def to_s
|
56
|
+
"Could not find a Git executable in your path. Please add it and try again."
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
class ConstraintNotSatisfied < BerkshelfError; status_code(111); end
|
21
61
|
end
|
data/lib/berkshelf/git.rb
CHANGED
@@ -1,39 +1,71 @@
|
|
1
|
+
require 'uri'
|
2
|
+
require 'mixlib/shellout'
|
3
|
+
|
1
4
|
module Berkshelf
|
5
|
+
# @author Jamie Winsor <jamie@vialstudios.com>
|
2
6
|
class Git
|
7
|
+
GIT_REGEXP = URI.regexp(%w{ https git })
|
8
|
+
SSH_REGEXP = /(.+)@(.+):(.+)\/(.+)\.git/
|
9
|
+
|
3
10
|
class << self
|
11
|
+
# @overload git(commands)
|
12
|
+
# Shellout to the Git executable on your system with the given commands.
|
13
|
+
#
|
14
|
+
# @param [Array<String>]
|
15
|
+
#
|
16
|
+
# @return [String]
|
17
|
+
# the output of the execution of the Git command
|
4
18
|
def git(*command)
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
19
|
+
cmd = Mixlib::ShellOut.new(git_cmd, *command)
|
20
|
+
cmd.run_command
|
21
|
+
|
22
|
+
unless cmd.exitstatus == 0
|
23
|
+
raise GitError.new(cmd.stderr)
|
24
|
+
end
|
9
25
|
|
10
|
-
|
26
|
+
cmd.stdout.chomp
|
11
27
|
end
|
12
28
|
|
29
|
+
# Clone a remote Git repository to disk
|
30
|
+
#
|
31
|
+
# @param [String] uri
|
32
|
+
# a Git URI to clone
|
33
|
+
# @param [#to_s] destination
|
34
|
+
# a local path on disk to clone to
|
35
|
+
#
|
36
|
+
# @return [String]
|
37
|
+
# the destination the URI was cloned to
|
13
38
|
def clone(uri, destination = Dir.mktmpdir)
|
14
39
|
git("clone", uri, destination.to_s)
|
15
40
|
|
16
|
-
error_check
|
17
|
-
|
18
41
|
destination
|
19
42
|
end
|
20
43
|
|
44
|
+
# Checkout the given reference in the given repository
|
45
|
+
#
|
46
|
+
# @param [String] repo_path
|
47
|
+
# path to a Git repo on disk
|
48
|
+
# @param [String] ref
|
49
|
+
# reference to checkout
|
21
50
|
def checkout(repo_path, ref)
|
22
51
|
Dir.chdir repo_path do
|
23
52
|
git("checkout", "-q", ref)
|
24
53
|
end
|
25
54
|
end
|
26
55
|
|
56
|
+
# @param [Strin] repo_path
|
27
57
|
def rev_parse(repo_path)
|
28
58
|
Dir.chdir repo_path do
|
29
59
|
git("rev-parse", "HEAD")
|
30
60
|
end
|
31
61
|
end
|
32
62
|
|
63
|
+
# Return an absolute path to the Git executable on your system
|
33
64
|
#
|
34
|
-
#
|
35
|
-
#
|
65
|
+
# @return [String]
|
66
|
+
# absolute path to git executable
|
36
67
|
#
|
68
|
+
# @raise [GitNotFound] if executable is not found in system path
|
37
69
|
def find_git
|
38
70
|
git_path = nil
|
39
71
|
ENV["PATH"].split(File::PATH_SEPARATOR).each do |path|
|
@@ -50,21 +82,58 @@ module Berkshelf
|
|
50
82
|
end
|
51
83
|
|
52
84
|
unless git_path
|
53
|
-
raise
|
85
|
+
raise GitNotFound
|
54
86
|
end
|
55
87
|
|
56
88
|
return git_path
|
57
89
|
end
|
58
90
|
|
91
|
+
# Determines if the given URI is a valid Git URI. A valid Git URI is a string
|
92
|
+
# containing the location of a Git repository by either the Git protocol,
|
93
|
+
# SSH protocol, or HTTPS protocol.
|
94
|
+
#
|
95
|
+
# @example Valid Git protocol URI
|
96
|
+
# "git://github.com/reset/thor-foodcritic.git"
|
97
|
+
# @example Valid HTTPS URI
|
98
|
+
# "https://github.com/reset/solve.git"
|
99
|
+
# @example Valid SSH protocol URI
|
100
|
+
# "git@github.com:reset/solve.git"
|
101
|
+
#
|
102
|
+
# @param [String] uri
|
103
|
+
#
|
104
|
+
# @return [Boolean]
|
105
|
+
def validate_uri(uri)
|
106
|
+
unless uri.is_a?(String)
|
107
|
+
return false
|
108
|
+
end
|
109
|
+
|
110
|
+
unless uri.slice(SSH_REGEXP).nil?
|
111
|
+
return true
|
112
|
+
end
|
113
|
+
|
114
|
+
unless uri.slice(GIT_REGEXP).nil?
|
115
|
+
return true
|
116
|
+
end
|
117
|
+
|
118
|
+
false
|
119
|
+
end
|
120
|
+
|
121
|
+
# @raise [InvalidGitURI] if the given object is not a String containing a valid Git URI
|
122
|
+
#
|
123
|
+
# @see validate_uri
|
124
|
+
def validate_uri!(uri)
|
125
|
+
unless validate_uri(uri)
|
126
|
+
raise InvalidGitURI.new(uri)
|
127
|
+
end
|
128
|
+
|
129
|
+
true
|
130
|
+
end
|
131
|
+
|
59
132
|
private
|
60
133
|
|
61
134
|
def git_cmd
|
62
135
|
@git_cmd ||= find_git
|
63
136
|
end
|
64
|
-
|
65
|
-
def error_check
|
66
|
-
raise Berkshelf::GitError, "Did not succeed executing git; check the output above." unless $?.success?
|
67
|
-
end
|
68
137
|
end
|
69
138
|
end
|
70
139
|
end
|