berkshelf 3.0.0.beta1 → 3.0.0.beta2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (71) hide show
  1. checksums.yaml +7 -0
  2. data/.ruby-version +1 -1
  3. data/CONTRIBUTING.md +2 -0
  4. data/LICENSE +1 -1
  5. data/README.md +1 -1
  6. data/Thorfile +2 -2
  7. data/berkshelf.gemspec +3 -3
  8. data/features/install_command.feature +36 -8
  9. data/features/json_formatter.feature +93 -3
  10. data/features/licenses.feature +1 -1
  11. data/features/lockfile.feature +0 -12
  12. data/features/outdated_command.feature +124 -0
  13. data/features/show_command.feature +44 -25
  14. data/features/step_definitions/chef/config_steps.rb +2 -2
  15. data/features/step_definitions/chef_server_steps.rb +9 -1
  16. data/features/step_definitions/config_steps.rb +1 -1
  17. data/features/step_definitions/filesystem_steps.rb +7 -0
  18. data/features/support/env.rb +2 -1
  19. data/features/update_command.feature +11 -21
  20. data/features/upload_command.feature +45 -1
  21. data/features/vendor_command.feature +83 -0
  22. data/lib/berkshelf.rb +5 -4
  23. data/lib/berkshelf/api_client/remote_cookbook.rb +13 -0
  24. data/lib/berkshelf/berksfile.rb +155 -23
  25. data/lib/berkshelf/chef.rb +0 -1
  26. data/lib/berkshelf/cli.rb +40 -31
  27. data/lib/berkshelf/dependency.rb +14 -4
  28. data/lib/berkshelf/errors.rb +74 -3
  29. data/lib/berkshelf/formatters.rb +12 -1
  30. data/lib/berkshelf/formatters/human_readable.rb +44 -5
  31. data/lib/berkshelf/formatters/json.rb +50 -8
  32. data/lib/berkshelf/installer.rb +8 -8
  33. data/lib/berkshelf/location.rb +17 -0
  34. data/lib/berkshelf/locations/git_location.rb +7 -17
  35. data/lib/berkshelf/locations/mercurial_location.rb +112 -0
  36. data/lib/berkshelf/lockfile.rb +1 -1
  37. data/lib/berkshelf/mercurial.rb +146 -0
  38. data/lib/berkshelf/version.rb +1 -1
  39. data/spec/config/knife.rb +2 -4
  40. data/spec/fixtures/lockfiles/default.lock +0 -1
  41. data/spec/support/chef_api.rb +9 -2
  42. data/spec/support/mercurial.rb +122 -0
  43. data/spec/support/path_helpers.rb +2 -2
  44. data/spec/unit/berkshelf/berksfile_spec.rb +34 -8
  45. data/spec/unit/berkshelf/dependency_spec.rb +0 -7
  46. data/spec/unit/berkshelf/formatters/null_spec.rb +1 -1
  47. data/spec/unit/berkshelf/locations/mercurial_location_spec.rb +150 -0
  48. data/spec/unit/berkshelf/lockfile_spec.rb +0 -12
  49. data/spec/unit/berkshelf/mercurial_spec.rb +173 -0
  50. metadata +32 -110
  51. data/lib/berkshelf/chef/config.rb +0 -68
  52. data/lib/berkshelf/mixin/config.rb +0 -172
  53. data/spec/fixtures/cookbooks/example_metadata_name/metadata.rb +0 -2
  54. data/spec/fixtures/cookbooks/example_metadata_no_name/metadata.rb +0 -1
  55. data/spec/fixtures/cookbooks/example_no_metadata/recipes/default.rb +0 -1
  56. data/spec/fixtures/cookbooks/nginx-0.100.5/README.md +0 -77
  57. data/spec/fixtures/cookbooks/nginx-0.100.5/attributes/default.rb +0 -65
  58. data/spec/fixtures/cookbooks/nginx-0.100.5/definitions/nginx_site.rb +0 -35
  59. data/spec/fixtures/cookbooks/nginx-0.100.5/files/default/mime.types +0 -73
  60. data/spec/fixtures/cookbooks/nginx-0.100.5/files/ubuntu/mime.types +0 -73
  61. data/spec/fixtures/cookbooks/nginx-0.100.5/libraries/nginxlib.rb +0 -1
  62. data/spec/fixtures/cookbooks/nginx-0.100.5/metadata.rb +0 -91
  63. data/spec/fixtures/cookbooks/nginx-0.100.5/providers/defprovider.rb +0 -1
  64. data/spec/fixtures/cookbooks/nginx-0.100.5/recipes/default.rb +0 -59
  65. data/spec/fixtures/cookbooks/nginx-0.100.5/resources/defresource.rb +0 -1
  66. data/spec/fixtures/cookbooks/nginx-0.100.5/templates/default/nginx.pill.erb +0 -15
  67. data/spec/fixtures/cookbooks/nginx-0.100.5/templates/default/plugins/nginx.rb.erb +0 -66
  68. data/spec/fixtures/lockfile_spec/with_lock/Berksfile +0 -1
  69. data/spec/fixtures/lockfile_spec/without_lock/.gitkeep +0 -0
  70. data/spec/fixtures/reset.pem +0 -27
  71. data/spec/unit/chef/config_spec.rb +0 -81
@@ -90,21 +90,21 @@ module Berkshelf
90
90
  private
91
91
 
92
92
  def dependency_from_lockfile(dependency)
93
- locked_dependency = lockfile.find(dependency)
93
+ locked = lockfile.find(dependency)
94
94
 
95
- return nil unless locked_dependency
95
+ return nil unless locked
96
96
 
97
97
  # If there's a locked_version, make sure it's still satisfied
98
98
  # by the constraint
99
- if locked_dependency.locked_version
100
- unless dependency.version_constraint.satisfies?(locked_dependency.locked_version)
101
- raise Berkshelf::OutdatedDependency.new(locked_dependency, dependency)
99
+ if locked.locked_version
100
+ unless dependency.version_constraint.satisfies?(locked.locked_version)
101
+ raise Berkshelf::OutdatedDependency.new(locked, dependency)
102
102
  end
103
103
  end
104
104
 
105
- # Update to the new constraint (it might have changed, but still be satisfied)
106
- locked_dependency.version_constraint = dependency.version_constraint
107
- locked_dependency
105
+ # Update to the constraint to be a hard one
106
+ locked.version_constraint = Solve::Constraint.new(locked.locked_version.to_s)
107
+ locked
108
108
  end
109
109
 
110
110
  # Merge the locked dependencies against the given dependencies.
@@ -12,6 +12,9 @@ module Berkshelf
12
12
  # Location.init('nginx', '>= 0.0.0', git: 'git://github.com/RiotGames/artifact-cookbook.git') =>
13
13
  # instantiates a GitLocation
14
14
  #
15
+ # Location.init('nginx', '>= 0.0.0', hg: 'http://hghub.com/RiotGames/') =>
16
+ # instantiates a MercurialLocation
17
+ #
15
18
  # Location.init('nginx', '>= 0.0.0', path: '/Users/reset/code/nginx-cookbook') =>
16
19
  # instantiates a PathLocation
17
20
  #
@@ -126,6 +129,20 @@ module Berkshelf
126
129
  JSON.pretty_generate(to_hash, options)
127
130
  end
128
131
  end
132
+
133
+ class ScmLocation < Location::Base
134
+ class << self
135
+ # Create a temporary directory for the cloned repository within Berkshelf's
136
+ # temporary directory
137
+ #
138
+ # @return [String]
139
+ # the path to the created temporary directory
140
+ def tmpdir
141
+ @tmpdir ||= Berkshelf.mktmpdir
142
+ end
143
+ end
144
+ end
145
+
129
146
  end
130
147
  end
131
148
 
@@ -1,15 +1,5 @@
1
1
  module Berkshelf
2
- class GitLocation < Location::Base
3
- class << self
4
- # Create a temporary directory for the cloned repository within Berkshelf's
5
- # temporary directory
6
- #
7
- # @return [String]
8
- # the path to the created temporary directory
9
- def tmpdir
10
- @tmpdir ||= Berkshelf.mktmpdir
11
- end
12
- end
2
+ class GitLocation < Location::ScmLocation
13
3
 
14
4
  set_location_key :git
15
5
  set_valid_options :ref, :branch, :tag, :rel
@@ -22,7 +12,7 @@ module Berkshelf
22
12
 
23
13
  alias_method :tag, :branch
24
14
 
25
- # @param [Solve::Constraint] version_constraint
15
+ # @param [Dependency] dependency
26
16
  # @param [Hash] options
27
17
  #
28
18
  # @option options [String] :git
@@ -37,10 +27,10 @@ module Berkshelf
37
27
  # the path within the repository to find the cookbook
38
28
  def initialize(dependency, options = {})
39
29
  super
40
- @uri = options[:git]
41
- @branch = options[:branch] || options[:tag] || 'master'
42
- @ref = options[:ref]
43
- @rel = options[:rel]
30
+ @uri = options[:git]
31
+ @branch = options[:branch] || options[:tag] || 'master'
32
+ @ref = options[:ref]
33
+ @rel = options[:rel]
44
34
 
45
35
  Git.validate_uri!(@uri)
46
36
  end
@@ -68,7 +58,7 @@ module Berkshelf
68
58
  raise CookbookNotFound, msg
69
59
  end
70
60
 
71
- cb_path = File.join(destination, "#{dependency.name}-#{ref}")
61
+ cb_path = revision_path(destination)
72
62
  FileUtils.rm_rf(cb_path)
73
63
  FileUtils.mv(tmp_path, cb_path)
74
64
 
@@ -0,0 +1,112 @@
1
+ module Berkshelf
2
+ class MercurialLocation < Location::ScmLocation
3
+
4
+ set_location_key :hg
5
+ set_valid_options :rev, :branch, :tag, :rel
6
+
7
+ attr_accessor :uri
8
+ attr_accessor :rel
9
+ attr_accessor :rev
10
+ attr_reader :options
11
+
12
+ alias_method :tag, :rev
13
+ alias_method :branch, :rev
14
+
15
+ # @param [Dependency] dependency
16
+ # @param [Hash] options
17
+ #
18
+ # @option options [String] :hg
19
+ # the URL to clone
20
+ # @option options [String] :rev
21
+ # the revision to checkout
22
+ # @option options [String] :branch
23
+ # same as rev
24
+ # @option options [String] :tag
25
+ # same as rev
26
+ # @option options [String] :rel
27
+ # the path within the repository to find the cookbook
28
+ def initialize(dependency, options = {})
29
+ super
30
+ @uri = options[:hg]
31
+ @rev = options[:rev] || options[:branch] || options[:tag] || 'default'
32
+ @rel = options[:rel]
33
+
34
+ Mercurial.validate_uri!(@uri)
35
+ end
36
+
37
+ # @return [Berkshelf::CachedCookbook]
38
+ def do_download
39
+ destination = Berkshelf::CookbookStore.instance.storage_path
40
+
41
+ if cached?(destination)
42
+ @rev ||= Berkshelf::Mercurial.rev_parse(revision_path(destination))
43
+ return local_revision(destination)
44
+ end
45
+
46
+ Berkshelf::Mercurial.checkout(clone, rev || branch || tag) if rev || branch || tag
47
+ @rev = Berkshelf::Mercurial.rev_parse(clone)
48
+
49
+ tmp_path = rel ? File.join(clone, rel) : clone
50
+ unless File.chef_cookbook?(tmp_path)
51
+ msg = "Cookbook '#{dependency.name}' not found at hg: #{uri}"
52
+ msg << " with rev '#{rev}'" if rev
53
+ msg << " at path '#{rel}'" if rel
54
+ raise CookbookNotFound, msg
55
+ end
56
+
57
+ cb_path = revision_path(destination)
58
+ FileUtils.rm_rf(cb_path)
59
+ FileUtils.mv(tmp_path, cb_path)
60
+
61
+ cached = CachedCookbook.from_store_path(cb_path)
62
+ validate_cached(cached)
63
+
64
+ cached
65
+ end
66
+
67
+ def to_hash
68
+ super.tap do |h|
69
+ h[:value] = self.uri
70
+ h[:branch] = self.branch if branch
71
+ end
72
+ end
73
+
74
+ def to_s
75
+ s = "#{self.class.location_key}: '#{uri}'"
76
+ s << " at rev: '#{rev}'" if rev
77
+ s
78
+ end
79
+
80
+ private
81
+
82
+ def hg
83
+ @hg ||= Berkshelf::Mercurial.new(uri)
84
+ end
85
+
86
+ def clone
87
+ tmp_clone = File.join(self.class.tmpdir, uri.gsub(/[\/:]/,'-'))
88
+ FileUtils.mkdir_p(File.join(File.split(tmp_clone).shift))
89
+ unless File.exists?(tmp_clone)
90
+ Berkshelf::Mercurial.clone(uri, tmp_clone)
91
+ end
92
+
93
+ tmp_clone
94
+ end
95
+
96
+ def cached?(destination)
97
+ revision_path(destination) && File.exists?(revision_path(destination))
98
+ end
99
+
100
+ def local_revision(destination)
101
+ path = revision_path(destination)
102
+ cached = Berkshelf::CachedCookbook.from_store_path(path)
103
+ validate_cached(cached)
104
+ return cached
105
+ end
106
+
107
+ def revision_path(destination)
108
+ return unless rev
109
+ File.join(destination, "#{dependency.name}-#{rev}")
110
+ end
111
+ end
112
+ end
@@ -47,7 +47,7 @@ module Berkshelf
47
47
  end
48
48
  end
49
49
 
50
- # The list of sources constrained in this lockfile.
50
+ # The list of dependencies constrained in this lockfile.
51
51
  #
52
52
  # @return [Array<Berkshelf::Dependency>]
53
53
  # the list of dependencies in this lockfile
@@ -0,0 +1,146 @@
1
+ require 'uri'
2
+ require 'buff/shell_out'
3
+
4
+ module Berkshelf
5
+ class Mercurial
6
+ HG_REGEXP = URI.regexp(%w(http https file ssh))
7
+
8
+ HAS_QUOTE_RE = %r{\"}.freeze
9
+ HAS_SPACE_RE = %r{\s}.freeze
10
+
11
+ class << self
12
+ include Buff::ShellOut
13
+
14
+ # @overload hg(commands)
15
+ # Shellout to the Mercurial executable on your system with the given commands.
16
+ #
17
+ # @param [Array<String>]
18
+ #
19
+ # @return [String]
20
+ # the output of the execution of the Mercurial command
21
+ def hg(*command)
22
+ command.unshift(hg_cmd)
23
+ command_str = command.map { |p| quote_cmd_arg(p) }.join(' ')
24
+ response = shell_out(command_str)
25
+
26
+ unless response.success?
27
+ raise MercurialError.new(response.stderr.strip)
28
+ end
29
+
30
+ response.stdout.strip
31
+ end
32
+
33
+ # Clone a remote Mercurial repository to disk
34
+ #
35
+ # @param [String] uri
36
+ # a Mercurial URI to clone
37
+ # @param [#to_s] destination
38
+ # a local path on disk to clone to
39
+ #
40
+ # @return [String]
41
+ # the destination the URI was cloned to
42
+ def clone(uri, destination = Dir.mktmpdir)
43
+ hg('clone', uri, destination.to_s)
44
+ destination
45
+ end
46
+
47
+ # Checkout the given revision in the given repository
48
+ #
49
+ # @param [String] repo_path
50
+ # path to a mercurial repo on disk
51
+ # @param [String] rev
52
+ # revision to checkout
53
+ def checkout(repo_path, rev)
54
+ Dir.chdir repo_path do
55
+ hg('update','--clean', '--rev', rev)
56
+ end
57
+ end
58
+
59
+ # @param [String] repo_path
60
+ def rev_parse(repo_path)
61
+ Dir.chdir repo_path do
62
+ hg('id', '-i')
63
+ end
64
+ end
65
+
66
+ # Return an absolute path to the Mercurial executable on your system
67
+ #
68
+ # @return [String]
69
+ # absolute path to mercurial executable
70
+ #
71
+ # @raise [MercurialNotFound] if executable is not found in system path
72
+ def find_hg
73
+ hg_path = nil
74
+ ENV['PATH'].split(::File::PATH_SEPARATOR).each do |path|
75
+ hg_path = detect_hg_path(path)
76
+ break if hg_path
77
+ end
78
+
79
+ unless hg_path
80
+ raise MercurialNotFound
81
+ end
82
+
83
+ return hg_path
84
+ end
85
+
86
+ # Determines if the given URI is a valid mercurial URI. A valid mercurial URI is a string
87
+ # containing the location of a mercurial repository by either the HTTP protocol,
88
+ # HTTPS protocol, or SSH protocol.
89
+ #
90
+ # @example Valid HTTP protocol URI
91
+ # 'http://hghub.com/project'
92
+ # @example Valid HTTPS URI
93
+ # 'https://hghub.com/project'
94
+ # @example Valid SSH protocol URI
95
+ # 'ssh://user@hghub.com:22/path/to/repo'
96
+ #
97
+ # @param [String] uri
98
+ #
99
+ # @return [Boolean]
100
+ def validate_uri(uri)
101
+ unless uri.is_a?(String)
102
+ return false
103
+ end
104
+
105
+ unless uri.slice(HG_REGEXP).nil?
106
+ return true
107
+ end
108
+
109
+ false
110
+ end
111
+
112
+ # @raise [InvalidMercurialURI] if the given object is not a String containing a valid Mercurial URI
113
+ #
114
+ # @see validate_uri
115
+ def validate_uri!(uri)
116
+ unless validate_uri(uri)
117
+ raise InvalidHgURI.new(uri)
118
+ end
119
+
120
+ true
121
+ end
122
+
123
+ private
124
+
125
+ def hg_cmd
126
+ @hg_cmd ||= find_hg
127
+ end
128
+
129
+ def quote_cmd_arg(arg)
130
+ return arg if HAS_QUOTE_RE.match(arg)
131
+ return arg unless HAS_SPACE_RE.match(arg)
132
+ "\"#{arg}\""
133
+ end
134
+
135
+ def detect_hg_path(base_dir)
136
+ %w(hg hg.exe hg.cmd).each do |hg_cmd|
137
+ potential_path = File.join(base_dir, hg_cmd)
138
+ if File.executable?(potential_path)
139
+ return potential_path
140
+ end
141
+ end
142
+ nil
143
+ end
144
+ end
145
+ end
146
+ end
@@ -1,3 +1,3 @@
1
1
  module Berkshelf
2
- VERSION = "3.0.0.beta1"
2
+ VERSION = "3.0.0.beta2"
3
3
  end
@@ -1,11 +1,9 @@
1
- current_dir = File.expand_path(File.dirname(__FILE__))
2
-
3
1
  log_level :info
4
2
  log_location STDOUT
5
3
  node_name "berkshelf"
6
- client_key "#{current_dir}/berkshelf.pem"
4
+ client_key File.expand_path("spec/config/berkshelf.pem")
7
5
  validation_client_name "validator"
8
- validation_key "#{current_dir}/validator.pem"
6
+ validation_key File.expand_path("spec/config/validator.pem")
9
7
  chef_server_url "http://localhost:26310"
10
8
  cache_type 'BasicFile'
11
9
  cache_options( :path => "#{ENV['HOME']}/.chef/checksums" )
@@ -1,5 +1,4 @@
1
1
  {
2
- "sha":"6b76225554cc1f7c0aea0f8b3f10c6743aeba67e",
3
2
  "dependencies":{
4
3
  "build-essential":{
5
4
  "locked_version":"1.1.2"
@@ -8,9 +8,16 @@ module Berkshelf
8
8
  ridley.cookbook.all
9
9
  end
10
10
 
11
- def upload_cookbook(path)
11
+ def upload_cookbook(path, options = {})
12
12
  cached = CachedCookbook.from_store_path(path)
13
- ridley.cookbook.upload(cached.path, name: cached.cookbook_name)
13
+
14
+ options = {
15
+ force: false,
16
+ freeze: false,
17
+ name: cached.cookbook_name,
18
+ }.merge(options)
19
+
20
+ ridley.cookbook.upload(cached.path, options)
14
21
  end
15
22
 
16
23
  # Remove the version of the given cookbook from the Chef Server defined
@@ -0,0 +1,122 @@
1
+ module Berkshelf
2
+ module RSpec
3
+ module Mercurial
4
+ require 'buff/shell_out'
5
+ include Buff::ShellOut
6
+
7
+ require_relative 'path_helpers'
8
+ include Berkshelf::RSpec::PathHelpers
9
+
10
+ def mercurial_origin_for(repo, options = {})
11
+ "file://localhost#{generate_fake_mercurial_remote(repo, options)}"
12
+ end
13
+
14
+ def generate_fake_mercurial_remote(uri, options = {})
15
+ repo_path = remotes.join(uri)
16
+
17
+ FileUtils.mkdir repo_path
18
+
19
+ Dir.chdir(repo_path) do
20
+ ENV['HGUSER'] = 'test_user'
21
+ shell_out "hg init"
22
+ shell_out "echo '# a change!' >> content_file"
23
+ if options[:is_cookbook]
24
+ shell_out "echo '#cookbook' >> metadata.rb"
25
+ end
26
+ shell_out "hg add ."
27
+ shell_out "hg commit -m 'A commit.'"
28
+ options[:tags].each do |tag|
29
+ shell_out "echo '#{tag}' > content_file"
30
+ shell_out "hg commit -m '#{tag} content'"
31
+ shell_out "hg tag '#{tag}'"
32
+ end if options.has_key? :tags
33
+ options[:branches].each do |branch|
34
+ shell_out "hg branch #{branch}"
35
+ shell_out "echo '#{branch}' > content_file"
36
+ shell_out "hg commit -m '#{branch} content'"
37
+ shell_out "hg up default"
38
+ end if options.has_key? :branches
39
+ end
40
+ repo_path.to_path
41
+ end
42
+
43
+ # Calculate the id for the given mercurial rev.
44
+ #
45
+ # @param [#to_s] repo
46
+ # the repository to show the rev for
47
+ # @param [#to_s] rev
48
+ # the revision to identify
49
+ #
50
+ # @return [String]
51
+ def id_for_rev(repo, rev)
52
+ Dir.chdir remote_path(repo) do
53
+ shell_out("hg id -r '#{rev}'").stdout.split(' ').first.strip
54
+ end
55
+ end
56
+
57
+ # The clone path the given repo.
58
+ #
59
+ # @param [#to_s] repo
60
+ # the name of the local repo
61
+ #
62
+ # @return [Pathname]
63
+ # the path to the clone
64
+ def clone_path(repo)
65
+ clones.join(repo.to_s)
66
+ end
67
+
68
+ # The clone path the remote repo.
69
+ #
70
+ # @param [#to_s] repo
71
+ # the name of the remote repo
72
+ #
73
+ # @return [Pathname]
74
+ # the path to the clone
75
+ def remote_path(repo)
76
+ remotes.join(repo.to_s)
77
+ end
78
+
79
+ private
80
+ # The path to store the local git clones.
81
+ #
82
+ # @return [Pathname]
83
+ def clones
84
+ ensure_and_return(tmp_path.join('clones'))
85
+ end
86
+
87
+ # The path to store the git remotes.
88
+ #
89
+ # @return [Pathname]
90
+ def remotes
91
+ ensure_and_return(tmp_path.join('remotes'))
92
+ end
93
+
94
+ # Generate a cookbook by the given name.
95
+ #
96
+ # @param [#to_s] name
97
+ # the name of the cookbook to create
98
+ # @param [Hash] options
99
+ # the list ooptions to pass to the generator
100
+ def generate_mercurial_cookbook(name, options = {})
101
+ options = {
102
+ skip_vagrant: true,
103
+ skip_test_kitchen: true,
104
+ force: true,
105
+ }.merge(options)
106
+
107
+ Berkshelf::Cli.new.invoke(:cookbook, [name.to_s], options)
108
+ end
109
+
110
+ # Make sure the given path exists and return the path
111
+ #
112
+ # @param [#to_s] path
113
+ # the path to create and return
114
+ #
115
+ # @return [Pathname]
116
+ def ensure_and_return(path)
117
+ FileUtils.mkdir(path) unless File.exist?(path)
118
+ return Pathname.new(path).expand_path
119
+ end
120
+ end
121
+ end
122
+ end