berkshelf 0.3.3 → 0.3.7
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.
- 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
|