berkshelf 1.4.6 → 2.0.0.beta
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +1 -0
- data/CHANGELOG.md +1 -5
- data/CONTRIBUTING.md +3 -1
- data/Gemfile +11 -1
- data/README.md +16 -0
- data/Thorfile +3 -1
- data/berkshelf.gemspec +26 -38
- data/features/apply_command.feature +32 -0
- data/features/configure_command.feature +31 -0
- data/features/contingent_command.feature +5 -5
- data/features/default_locations.feature +2 -2
- data/features/groups_install.feature +19 -20
- data/features/info_command.feature +13 -13
- data/features/install_command.feature +86 -83
- data/features/json_formatter.feature +60 -23
- data/features/list_command.feature +5 -11
- data/features/lockfile.feature +286 -6
- data/features/open_command.feature +8 -4
- data/features/outdated_command.feature +8 -15
- data/features/package_command.feature +39 -0
- data/features/show_command.feature +8 -9
- data/features/step_definitions/chef_server_steps.rb +20 -2
- data/features/step_definitions/cli_steps.rb +10 -2
- data/features/step_definitions/configure_cli_steps.rb +7 -0
- data/features/step_definitions/filesystem_steps.rb +19 -14
- data/features/step_definitions/json_steps.rb +22 -5
- data/features/step_definitions/utility_steps.rb +13 -1
- data/features/support/env.rb +10 -23
- data/features/update_command.feature +105 -24
- data/features/upload_command.feature +0 -14
- data/features/vendor_install.feature +3 -3
- data/generator_files/Vagrantfile.erb +11 -11
- data/lib/berkshelf.rb +6 -5
- data/lib/berkshelf/berksfile.rb +267 -99
- data/lib/berkshelf/cli.rb +70 -34
- data/lib/berkshelf/cli_commands/test_command.rb +11 -0
- data/lib/berkshelf/community_rest.rb +1 -1
- data/lib/berkshelf/config.rb +19 -2
- data/lib/berkshelf/cookbook_source.rb +41 -12
- data/lib/berkshelf/cookbook_store.rb +8 -4
- data/lib/berkshelf/errors.rb +61 -29
- data/lib/berkshelf/formatters.rb +13 -19
- data/lib/berkshelf/formatters/human_readable.rb +8 -0
- data/lib/berkshelf/formatters/json.rb +12 -1
- data/lib/berkshelf/formatters/null.rb +23 -0
- data/lib/berkshelf/init_generator.rb +22 -11
- data/lib/berkshelf/location.rb +8 -10
- data/lib/berkshelf/locations/chef_api_location.rb +4 -5
- data/lib/berkshelf/locations/git_location.rb +14 -12
- data/lib/berkshelf/locations/path_location.rb +16 -1
- data/lib/berkshelf/locations/site_location.rb +1 -3
- data/lib/berkshelf/lockfile.rb +230 -33
- data/lib/berkshelf/resolver.rb +2 -1
- data/lib/berkshelf/ui.rb +4 -8
- data/lib/berkshelf/version.rb +1 -1
- data/lib/thor/monkies/shell.rb +2 -5
- data/spec/fixtures/cassettes/Berkshelf_Resolver/{ClassMethods/_initialize → _initialize}/adds_the_dependencies_of_the_source_as_sources.yml +0 -0
- data/spec/fixtures/cookbooks/example_cookbook/.gitignore +2 -0
- data/spec/fixtures/cookbooks/example_cookbook/.kitchen.yml +26 -0
- data/spec/fixtures/cookbooks/example_cookbook/Berksfile.lock +5 -0
- data/spec/fixtures/lockfiles/default.lock +11 -0
- data/spec/{config/knife.rb → knife.rb.sample} +2 -2
- data/spec/spec_helper.rb +15 -3
- data/spec/support/chef_api.rb +19 -5
- data/spec/support/chef_server.rb +4 -3
- data/spec/support/knife.rb +18 -0
- data/spec/unit/berkshelf/berksfile_spec.rb +332 -235
- data/spec/unit/berkshelf/cached_cookbook_spec.rb +40 -42
- data/spec/unit/berkshelf/chef/cookbook/chefignore_spec.rb +11 -15
- data/spec/unit/berkshelf/community_rest_spec.rb +16 -14
- data/spec/unit/berkshelf/config_spec.rb +36 -20
- data/spec/unit/berkshelf/cookbook_generator_spec.rb +6 -10
- data/spec/unit/berkshelf/cookbook_source_spec.rb +244 -183
- data/spec/unit/berkshelf/cookbook_store_spec.rb +72 -76
- data/spec/unit/berkshelf/core_ext/file_utils_spec.rb +2 -2
- data/spec/unit/berkshelf/downloader_spec.rb +137 -157
- data/spec/unit/berkshelf/errors_spec.rb +1 -1
- data/spec/unit/berkshelf/formatters/null_spec.rb +17 -0
- data/spec/unit/berkshelf/formatters_spec.rb +83 -90
- data/spec/unit/berkshelf/git_spec.rb +219 -207
- data/spec/unit/berkshelf/init_generator_spec.rb +73 -73
- data/spec/unit/berkshelf/location_spec.rb +143 -162
- data/spec/unit/berkshelf/locations/chef_api_location_spec.rb +94 -89
- data/spec/unit/berkshelf/locations/git_location_spec.rb +75 -59
- data/spec/unit/berkshelf/locations/path_location_spec.rb +46 -30
- data/spec/unit/berkshelf/locations/site_location_spec.rb +4 -4
- data/spec/unit/berkshelf/lockfile_spec.rb +185 -1
- data/spec/unit/berkshelf/logger_spec.rb +6 -24
- data/spec/unit/berkshelf/mixin/logging_spec.rb +6 -8
- data/spec/unit/berkshelf/resolver_spec.rb +36 -38
- data/spec/unit/berkshelf/ui_spec.rb +9 -10
- data/spec/unit/berkshelf_spec.rb +41 -40
- data/spec/unit/chef/config_spec.rb +9 -11
- metadata +55 -203
- data/spec/config/berkshelf.pem +0 -27
- data/spec/config/validator.pem +0 -27
data/lib/berkshelf/formatters.rb
CHANGED
@@ -69,28 +69,22 @@ module Berkshelf
|
|
69
69
|
end
|
70
70
|
end
|
71
71
|
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
end
|
83
|
-
|
84
|
-
def upload(cookbook, version, chef_server_url)
|
85
|
-
raise AbstractFunction, "#upload must be implemented on #{self.class}"
|
72
|
+
class << self
|
73
|
+
private
|
74
|
+
|
75
|
+
def formatter_methods(*args)
|
76
|
+
args.each do |meth|
|
77
|
+
define_method(meth.to_sym) do |*args|
|
78
|
+
raise AbstractFunction, "##{meth} must be implemented on #{self.class}"
|
79
|
+
end unless respond_to?(meth.to_sym)
|
80
|
+
end
|
81
|
+
end
|
86
82
|
end
|
87
83
|
|
88
|
-
|
89
|
-
raise AbstractFunction, "#msg must be implemented on #{self.class}"
|
90
|
-
end
|
84
|
+
formatter_methods :install, :use, :upload, :msg, :error, :package
|
91
85
|
|
92
|
-
def
|
93
|
-
|
86
|
+
def cleanup_hook
|
87
|
+
# run after the task is finished
|
94
88
|
end
|
95
89
|
|
96
90
|
private
|
@@ -33,6 +33,14 @@ module Berkshelf
|
|
33
33
|
Berkshelf.ui.info "Uploading #{cookbook} (#{version}) to: '#{chef_api_url}'"
|
34
34
|
end
|
35
35
|
|
36
|
+
# Output a Cookbook package message using {Berkshelf.ui}
|
37
|
+
#
|
38
|
+
# @param [String] cookbook
|
39
|
+
# @param [String] destination
|
40
|
+
def package(cookbook, destination)
|
41
|
+
Berkshelf.ui.info "Cookbook '#{cookbook}' saved to #{destination}!"
|
42
|
+
end
|
43
|
+
|
36
44
|
# Output a generic message using {Berkshelf.ui}
|
37
45
|
#
|
38
46
|
# @param [String] message
|
@@ -7,6 +7,8 @@ module Berkshelf
|
|
7
7
|
register_formatter :json
|
8
8
|
|
9
9
|
def initialize
|
10
|
+
Berkshelf.ui.mute!
|
11
|
+
|
10
12
|
@output = {
|
11
13
|
cookbooks: Array.new,
|
12
14
|
errors: Array.new,
|
@@ -22,7 +24,7 @@ module Berkshelf
|
|
22
24
|
output[:cookbooks] << details
|
23
25
|
end
|
24
26
|
|
25
|
-
print
|
27
|
+
print ::JSON.pretty_generate(output)
|
26
28
|
end
|
27
29
|
|
28
30
|
# Add a Cookbook installation entry to delayed output
|
@@ -58,6 +60,15 @@ module Berkshelf
|
|
58
60
|
cookbooks[cookbook][:uploaded_to] = chef_api_url
|
59
61
|
end
|
60
62
|
|
63
|
+
# Add a Cookbook package entry to delayed output
|
64
|
+
#
|
65
|
+
# @param [String] cookbook
|
66
|
+
# @param [String] destination
|
67
|
+
def package(cookbook, destination)
|
68
|
+
cookbooks[cookbook] ||= {}
|
69
|
+
cookbooks[cookbook][:destination] = destination
|
70
|
+
end
|
71
|
+
|
61
72
|
# Add a generic message entry to delayed output
|
62
73
|
#
|
63
74
|
# @param [String] message
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Berkshelf
|
2
|
+
module Formatters
|
3
|
+
# @author Seth Vargo <sethvargo@gmail.com>
|
4
|
+
class Null
|
5
|
+
include AbstractFormatter
|
6
|
+
|
7
|
+
register_formatter :null
|
8
|
+
|
9
|
+
# The abstract formatter dynamically defines methods that raise an
|
10
|
+
# AbstractFunction error. We need to define all of those on our class,
|
11
|
+
# otherwise they will be inherited by the Ruby object model.
|
12
|
+
AbstractFormatter.instance_methods.each do |meth|
|
13
|
+
define_method(meth) do |*args|
|
14
|
+
# intentionally do nothing
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def method_missing(meth, *args, &block)
|
19
|
+
# intentionally do nothing
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -19,15 +19,18 @@ module Berkshelf
|
|
19
19
|
|
20
20
|
class_option :skip_vagrant,
|
21
21
|
type: :boolean,
|
22
|
-
default: false
|
22
|
+
default: false,
|
23
|
+
desc: "Skips adding a Vagrantfile and adding supporting gems to the Gemfile"
|
23
24
|
|
24
25
|
class_option :skip_git,
|
25
26
|
type: :boolean,
|
26
|
-
default: false
|
27
|
+
default: false,
|
28
|
+
desc: "Skips adding a .gitignore and running git init in the cookbook directory"
|
27
29
|
|
28
30
|
class_option :foodcritic,
|
29
31
|
type: :boolean,
|
30
|
-
default: false
|
32
|
+
default: false,
|
33
|
+
desc: "Creates a Thorfile with Foodcritic support to lint test your cookbook"
|
31
34
|
|
32
35
|
class_option :chef_minitest,
|
33
36
|
type: :boolean,
|
@@ -35,20 +38,22 @@ module Berkshelf
|
|
35
38
|
|
36
39
|
class_option :scmversion,
|
37
40
|
type: :boolean,
|
38
|
-
default: false
|
41
|
+
default: false,
|
42
|
+
desc: "Creates a Thorfile with SCMVersion support to manage versions for continuous integration"
|
39
43
|
|
40
44
|
class_option :no_bundler,
|
41
45
|
type: :boolean,
|
42
|
-
default: false
|
46
|
+
default: false,
|
47
|
+
desc: "Skips generation of a Gemfile and other Bundler specific support"
|
43
48
|
|
44
49
|
class_option :cookbook_name,
|
45
50
|
type: :string
|
46
51
|
|
47
|
-
class_option :
|
48
|
-
type: :
|
49
|
-
default:
|
52
|
+
class_option :skip_test_kitchen,
|
53
|
+
type: :boolean,
|
54
|
+
default: false,
|
55
|
+
desc: "Skip adding a testing environment to your cookbook"
|
50
56
|
|
51
|
-
# Generate the cookbook
|
52
57
|
def generate
|
53
58
|
validate_configuration
|
54
59
|
check_option_support
|
@@ -87,6 +92,10 @@ module Berkshelf
|
|
87
92
|
template "Gemfile.erb", target.join("Gemfile")
|
88
93
|
end
|
89
94
|
|
95
|
+
unless options[:skip_test_kitchen]
|
96
|
+
Kitchen::Generator::Init.new([], options).invoke_all
|
97
|
+
end
|
98
|
+
|
90
99
|
unless options[:skip_vagrant]
|
91
100
|
template "Vagrantfile.erb", target.join("Vagrantfile")
|
92
101
|
::Berkshelf::Cli.new([], berksfile: target.join("Berksfile")).invoke(:install)
|
@@ -95,6 +104,10 @@ module Berkshelf
|
|
95
104
|
|
96
105
|
private
|
97
106
|
|
107
|
+
def berkshelf_config
|
108
|
+
Berkshelf::Config.instance
|
109
|
+
end
|
110
|
+
|
98
111
|
# Read the cookbook name from the metadata.rb
|
99
112
|
#
|
100
113
|
# @return [String]
|
@@ -119,7 +132,6 @@ module Berkshelf
|
|
119
132
|
end
|
120
133
|
end
|
121
134
|
|
122
|
-
|
123
135
|
# Check for supporting gems for provided options
|
124
136
|
#
|
125
137
|
# @return [Boolean]
|
@@ -127,7 +139,6 @@ module Berkshelf
|
|
127
139
|
assert_option_supported(:foodcritic) &&
|
128
140
|
assert_option_supported(:scmversion, 'thor-scmversion') &&
|
129
141
|
assert_default_supported(:no_bundler, 'bundler')
|
130
|
-
# Vagrant is a dependency of Berkshelf; it will always appear available to the Berkshelf process.
|
131
142
|
end
|
132
143
|
|
133
144
|
# Warn if the supporting gem for an option is not installed
|
data/lib/berkshelf/location.rb
CHANGED
@@ -145,19 +145,17 @@ module Berkshelf
|
|
145
145
|
#
|
146
146
|
# @raise [CookbookValidationFailure] if given CachedCookbook does not satisfy the constraint of the location
|
147
147
|
#
|
148
|
+
# @todo Change MismatchedCookbookName to raise instead of warn
|
149
|
+
#
|
148
150
|
# @return [Boolean]
|
149
151
|
def validate_cached(cached_cookbook)
|
150
152
|
unless version_constraint.satisfies?(cached_cookbook.version)
|
151
|
-
|
152
|
-
msg << " (#{self.version_constraint}). This usually happens if the Chef server contains a cookbook that"
|
153
|
-
msg << " contains a metadata file with a missing or mis-matched version number."
|
154
|
-
raise CookbookValidationFailure, msg
|
153
|
+
raise CookbookValidationFailure.new(self, cached_cookbook)
|
155
154
|
end
|
156
155
|
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
# end
|
156
|
+
unless self.name == cached_cookbook.cookbook_name
|
157
|
+
Berkshelf.ui.warn(MismatchedCookbookName.new(self, cached_cookbook).to_s)
|
158
|
+
end
|
161
159
|
|
162
160
|
true
|
163
161
|
end
|
@@ -168,8 +166,8 @@ module Berkshelf
|
|
168
166
|
}
|
169
167
|
end
|
170
168
|
|
171
|
-
def to_json
|
172
|
-
|
169
|
+
def to_json(options = {})
|
170
|
+
JSON.pretty_generate(to_hash, options)
|
173
171
|
end
|
174
172
|
|
175
173
|
private
|
@@ -4,7 +4,7 @@ module Berkshelf
|
|
4
4
|
class << self
|
5
5
|
# @return [Proc]
|
6
6
|
def finalizer
|
7
|
-
proc { conn.terminate if conn.alive? }
|
7
|
+
proc { conn.terminate if defined?(conn) && conn.alive? }
|
8
8
|
end
|
9
9
|
|
10
10
|
# @param [String] node_name
|
@@ -150,7 +150,7 @@ module Berkshelf
|
|
150
150
|
# @return [Berkshelf::CachedCookbook]
|
151
151
|
def download(destination)
|
152
152
|
berks_path = File.join(destination, "#{name}-#{target_cookbook.version}")
|
153
|
-
|
153
|
+
|
154
154
|
temp_path = target_cookbook.download
|
155
155
|
FileUtils.mv(temp_path, berks_path)
|
156
156
|
|
@@ -174,14 +174,13 @@ module Berkshelf
|
|
174
174
|
else
|
175
175
|
conn.cookbook.latest_version(name)
|
176
176
|
end
|
177
|
-
rescue Ridley::Errors::HTTPNotFound
|
178
|
-
Ridley::Errors::ResourceNotFound
|
177
|
+
rescue Ridley::Errors::HTTPNotFound
|
179
178
|
@target_cookbook = nil
|
180
179
|
end
|
181
180
|
|
182
181
|
if @target_cookbook.nil?
|
183
182
|
msg = "Cookbook '#{name}' found at #{self}"
|
184
|
-
msg << " that would satisfy constraint (#{version_constraint}" if version_constraint
|
183
|
+
msg << " that would satisfy constraint (#{version_constraint})" if version_constraint
|
185
184
|
raise CookbookNotFound, msg
|
186
185
|
end
|
187
186
|
|
@@ -20,10 +20,9 @@ module Berkshelf
|
|
20
20
|
attr_accessor :uri
|
21
21
|
attr_accessor :branch
|
22
22
|
attr_accessor :rel
|
23
|
-
attr_accessor :
|
23
|
+
attr_accessor :ref
|
24
24
|
attr_reader :options
|
25
25
|
|
26
|
-
alias_method :ref, :branch
|
27
26
|
alias_method :tag, :branch
|
28
27
|
|
29
28
|
# @param [#to_s] name
|
@@ -44,9 +43,9 @@ module Berkshelf
|
|
44
43
|
@name = name
|
45
44
|
@version_constraint = version_constraint
|
46
45
|
@uri = options[:git]
|
47
|
-
@branch = options[:branch] || options[:
|
46
|
+
@branch = options[:branch] || options[:tag] || 'master'
|
47
|
+
@ref = options[:ref]
|
48
48
|
@rel = options[:rel]
|
49
|
-
@branch_name = @branch.gsub("-", "_").gsub("/", "__") # In case the remote is specified
|
50
49
|
|
51
50
|
Git.validate_uri!(@uri)
|
52
51
|
end
|
@@ -55,22 +54,24 @@ module Berkshelf
|
|
55
54
|
#
|
56
55
|
# @return [Berkshelf::CachedCookbook]
|
57
56
|
def download(destination)
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
unless branch
|
62
|
-
self.branch = ::Berkshelf::Git.rev_parse(clone)
|
57
|
+
if cached?(destination)
|
58
|
+
@ref = Berkshelf::Git.rev_parse(revision_path(destination))
|
59
|
+
return local_revision(destination)
|
63
60
|
end
|
64
61
|
|
62
|
+
Berkshelf::Git.checkout(clone, ref || branch) if ref || branch
|
63
|
+
@ref = Berkshelf::Git.rev_parse(clone)
|
64
|
+
|
65
65
|
tmp_path = rel ? File.join(clone, rel) : clone
|
66
66
|
unless File.chef_cookbook?(tmp_path)
|
67
67
|
msg = "Cookbook '#{name}' not found at git: #{uri}"
|
68
68
|
msg << " with branch '#{branch}'" if branch
|
69
|
+
msg << " with ref '#{ref}'" if ref
|
69
70
|
msg << " at path '#{rel}'" if rel
|
70
71
|
raise CookbookNotFound, msg
|
71
72
|
end
|
72
73
|
|
73
|
-
cb_path = File.join(destination, "#{name}-#{
|
74
|
+
cb_path = File.join(destination, "#{name}-#{ref}")
|
74
75
|
FileUtils.rm_rf(cb_path)
|
75
76
|
FileUtils.mv(tmp_path, cb_path)
|
76
77
|
|
@@ -91,6 +92,7 @@ module Berkshelf
|
|
91
92
|
def to_s
|
92
93
|
s = "#{self.class.location_key}: '#{uri}'"
|
93
94
|
s << " with branch: '#{branch}'" if branch
|
95
|
+
s << " at ref: '#{ref}'" if ref
|
94
96
|
s
|
95
97
|
end
|
96
98
|
|
@@ -122,8 +124,8 @@ module Berkshelf
|
|
122
124
|
end
|
123
125
|
|
124
126
|
def revision_path(destination)
|
125
|
-
return unless
|
126
|
-
File.join(destination, "#{name}-#{
|
127
|
+
return unless ref
|
128
|
+
File.join(destination, "#{name}-#{ref}")
|
127
129
|
end
|
128
130
|
end
|
129
131
|
end
|
@@ -57,8 +57,23 @@ module Berkshelf
|
|
57
57
|
super.merge(value: self.path)
|
58
58
|
end
|
59
59
|
|
60
|
+
# The string representation of this PathLocation. If the path
|
61
|
+
# is the default cookbook store, just leave it out, because
|
62
|
+
# it's probably just cached.
|
63
|
+
#
|
64
|
+
# @example
|
65
|
+
# loc.to_s #=> artifact (1.4.0)
|
66
|
+
#
|
67
|
+
# @example
|
68
|
+
# loc.to_s #=> artifact (1.4.0) at path: '/Users/Seth/Dev/artifact'
|
69
|
+
#
|
70
|
+
# @return [String]
|
60
71
|
def to_s
|
61
|
-
|
72
|
+
if path.to_s.include?(Berkshelf.berkshelf_path.to_s)
|
73
|
+
"#{self.class.location_key}"
|
74
|
+
else
|
75
|
+
"#{self.class.location_key}: '#{path}'"
|
76
|
+
end
|
62
77
|
end
|
63
78
|
end
|
64
79
|
end
|
@@ -22,9 +22,7 @@ module Berkshelf
|
|
22
22
|
@name = name
|
23
23
|
@version_constraint = version_constraint
|
24
24
|
|
25
|
-
api_uri = if options[:site].nil?
|
26
|
-
SHORTNAMES[:opscode]
|
27
|
-
elsif SHORTNAMES.has_key?(options[:site])
|
25
|
+
api_uri = if options[:site].nil? || SHORTNAMES.has_key?(options[:site])
|
28
26
|
SHORTNAMES[options[:site]]
|
29
27
|
elsif options[:site].kind_of?(Symbol)
|
30
28
|
raise InvalidSiteShortnameError.new(options[:site])
|
data/lib/berkshelf/lockfile.rb
CHANGED
@@ -1,54 +1,251 @@
|
|
1
1
|
module Berkshelf
|
2
|
+
# The object representation of the Berkshelf lockfile. The lockfile is useful
|
3
|
+
# when working in teams where the same cookbook versions are desired across
|
4
|
+
# multiple workstations.
|
5
|
+
#
|
6
|
+
# @author Seth Vargo <sethvargo@gmail.com>
|
2
7
|
class Lockfile
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
end
|
8
|
+
# @return [Pathname]
|
9
|
+
# the path to this Lockfile
|
10
|
+
attr_reader :filepath
|
7
11
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
contents.delete_if do |line|
|
12
|
-
line =~ /cookbook '(#{sources.map(&:name).join('|')})'/
|
13
|
-
end
|
12
|
+
# @return [Berkshelf::Berksfile]
|
13
|
+
# the Berksfile for this Lockfile
|
14
|
+
attr_reader :berksfile
|
14
15
|
|
15
|
-
|
16
|
-
|
17
|
-
|
16
|
+
# @return [String]
|
17
|
+
# the last known SHA of the Berksfile
|
18
|
+
attr_accessor :sha
|
18
19
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
20
|
+
# Create a new lockfile instance associated with the given Berksfile. If a
|
21
|
+
# Lockfile exists, it is automatically loaded. Otherwise, an empty instance is
|
22
|
+
# created and ready for use.
|
23
|
+
#
|
24
|
+
# @param berksfile [Berkshelf::Berksfile]
|
25
|
+
# the Berksfile associated with this Lockfile
|
26
|
+
def initialize(berksfile)
|
27
|
+
@berksfile = berksfile
|
28
|
+
@filepath = File.expand_path("#{berksfile.filepath}.lock")
|
29
|
+
@sources = {}
|
30
|
+
|
31
|
+
load! if File.exists?(@filepath)
|
32
|
+
end
|
24
33
|
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
34
|
+
# Load the lockfile from file system.
|
35
|
+
def load!
|
36
|
+
contents = File.read(filepath)
|
37
|
+
|
38
|
+
begin
|
39
|
+
hash = JSON.parse(contents, symbolize_names: true)
|
40
|
+
rescue JSON::ParserError
|
41
|
+
if contents =~ /^cookbook ["'](.+)["']/
|
42
|
+
Berkshelf.ui.warn "You are using the old lockfile format. Attempting to convert..."
|
43
|
+
hash = LockfileLegacy.parse(contents)
|
29
44
|
else
|
30
|
-
|
45
|
+
raise
|
31
46
|
end
|
47
|
+
end
|
48
|
+
|
49
|
+
@sha = hash[:sha]
|
32
50
|
|
33
|
-
|
51
|
+
hash[:sources].each do |name, options|
|
52
|
+
add(CookbookSource.new(berksfile, name.to_s, options))
|
34
53
|
end
|
35
54
|
end
|
36
55
|
|
37
|
-
|
56
|
+
# Set the sha value to nil to mark that the lockfile is not out of
|
57
|
+
# sync with the Berksfile.
|
58
|
+
def reset_sha!
|
59
|
+
@sha = nil
|
60
|
+
end
|
61
|
+
|
62
|
+
# The list of sources constrained in this lockfile.
|
63
|
+
#
|
64
|
+
# @return [Array<Berkshelf::CookbookSource>]
|
65
|
+
# the list of sources in this lockfile
|
66
|
+
def sources
|
67
|
+
@sources.values
|
68
|
+
end
|
69
|
+
|
70
|
+
# Find the given source in this lockfile. This method accepts a source
|
71
|
+
# attribute which may either be the name of a cookbook (String) or an
|
72
|
+
# actual cookbook source.
|
73
|
+
#
|
74
|
+
# @param [String, Berkshelf::CookbookSource] source
|
75
|
+
# the cookbook source/name to find
|
76
|
+
# @return [CookbookSource, nil]
|
77
|
+
# the cookbook source from this lockfile or nil if one was not found
|
78
|
+
def find(source)
|
79
|
+
@sources[cookbook_name(source).to_s]
|
80
|
+
end
|
81
|
+
|
82
|
+
# Determine if this lockfile contains the given source.
|
83
|
+
#
|
84
|
+
# @param [String, Berkshelf::CookbookSource] source
|
85
|
+
# the cookbook source/name to determine existence of
|
86
|
+
# @return [Boolean]
|
87
|
+
# true if the source exists, false otherwise
|
88
|
+
def has_source?(source)
|
89
|
+
!find(source).nil?
|
90
|
+
end
|
91
|
+
|
92
|
+
# Replace the current list of sources with `sources`. This method does
|
93
|
+
# not write out the lockfile - it only changes the state of the object.
|
94
|
+
#
|
95
|
+
# @param [Array<Berkshelf::CookbookSource>] sources
|
96
|
+
# the list of sources to update
|
97
|
+
# @option options [String] :sha
|
98
|
+
# the sha of the Berksfile updating the sources
|
99
|
+
def update(sources, options = {})
|
100
|
+
reset_sources!
|
101
|
+
@sha = options[:sha]
|
102
|
+
|
103
|
+
sources.each { |source| append(source) }
|
104
|
+
save
|
105
|
+
end
|
106
|
+
|
107
|
+
# Add the given source to the `sources` list, if it doesn't already exist.
|
108
|
+
#
|
109
|
+
# @param [Berkshelf::CookbookSource] source
|
110
|
+
# the source to append to the sources list
|
111
|
+
def add(source)
|
112
|
+
@sources[cookbook_name(source)] = source
|
113
|
+
end
|
114
|
+
alias_method :append, :add
|
115
|
+
|
116
|
+
# Remove the given source from this lockfile. This method accepts a source
|
117
|
+
# attribute which may either be the name of a cookbook (String) or an
|
118
|
+
# actual cookbook source.
|
119
|
+
#
|
120
|
+
# @param [String, Berkshelf::CookbookSource] source
|
121
|
+
# the cookbook source/name to remove
|
122
|
+
#
|
123
|
+
# @raise [Berkshelf::CookbookNotFound]
|
124
|
+
# if the provided source does not exist
|
125
|
+
def remove(source)
|
126
|
+
unless has_source?(source)
|
127
|
+
raise Berkshelf::CookbookNotFound, "'#{cookbook_name(source)}' does not exist in this lockfile!"
|
128
|
+
end
|
129
|
+
|
130
|
+
@sources.delete(cookbook_name(source))
|
131
|
+
end
|
132
|
+
alias_method :unlock, :remove
|
38
133
|
|
39
|
-
|
134
|
+
# @return [String]
|
135
|
+
# the string representation of the lockfile
|
136
|
+
def to_s
|
137
|
+
"#<Berkshelf::Lockfile #{Pathname.new(filepath).basename}>"
|
138
|
+
end
|
40
139
|
|
41
|
-
|
42
|
-
|
140
|
+
# @return [String]
|
141
|
+
# the detailed string representation of the lockfile
|
142
|
+
def inspect
|
143
|
+
"#<Berkshelf::Lockfile #{Pathname.new(filepath).basename}, sources: #{sources.inspect}>"
|
43
144
|
end
|
44
145
|
|
45
|
-
|
46
|
-
|
47
|
-
|
146
|
+
# Write the current lockfile to a hash
|
147
|
+
#
|
148
|
+
# @return [Hash]
|
149
|
+
# the hash representation of this lockfile
|
150
|
+
# * :sha [String] the last-known sha for the berksfile
|
151
|
+
# * :sources [Array<Berkshelf::CookbookSource>] the list of sources
|
152
|
+
def to_hash
|
153
|
+
{
|
154
|
+
sha: sha,
|
155
|
+
sources: @sources
|
156
|
+
}
|
48
157
|
end
|
49
158
|
|
50
|
-
|
51
|
-
|
159
|
+
# The JSON representation of this lockfile
|
160
|
+
#
|
161
|
+
# Relies on {#to_hash} to generate the json
|
162
|
+
#
|
163
|
+
# @return [String]
|
164
|
+
# the JSON representation of this lockfile
|
165
|
+
def to_json(options = {})
|
166
|
+
JSON.pretty_generate(to_hash, options)
|
52
167
|
end
|
168
|
+
|
169
|
+
private
|
170
|
+
|
171
|
+
# Save the contents of the lockfile to disk.
|
172
|
+
def save
|
173
|
+
File.open(filepath, 'w') do |file|
|
174
|
+
file.write to_json + "\n"
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
# Clear the sources array
|
179
|
+
def reset_sources!
|
180
|
+
@sources = {}
|
181
|
+
end
|
182
|
+
|
183
|
+
# Return the name of this cookbook (because it's the key in our
|
184
|
+
# table).
|
185
|
+
#
|
186
|
+
# @param [Berkshelf::CookbookSource, #to_s] source
|
187
|
+
# the source to find the name from
|
188
|
+
#
|
189
|
+
# @return [String]
|
190
|
+
# the name of the cookbook
|
191
|
+
def cookbook_name(source)
|
192
|
+
source.is_a?(CookbookSource) ? source.name : source.to_s
|
193
|
+
end
|
194
|
+
|
195
|
+
# Legacy support for old lockfiles
|
196
|
+
#
|
197
|
+
# @author Seth Vargo <sethvargo@gmail.com>
|
198
|
+
# @todo Remove this class in the next major release.
|
199
|
+
class LockfileLegacy
|
200
|
+
class << self
|
201
|
+
# Read the old lockfile content and instance eval in context.
|
202
|
+
#
|
203
|
+
# @param [String] content
|
204
|
+
# the string content read from a legacy lockfile
|
205
|
+
def parse(content)
|
206
|
+
sources = {}.tap do |hash|
|
207
|
+
content.split("\n").each do |line|
|
208
|
+
next if line.empty?
|
209
|
+
|
210
|
+
source = self.new(line)
|
211
|
+
hash[source.name] = source.options
|
212
|
+
end
|
213
|
+
end
|
214
|
+
|
215
|
+
{
|
216
|
+
sha: nil,
|
217
|
+
sources: sources
|
218
|
+
}
|
219
|
+
end
|
220
|
+
end
|
221
|
+
|
222
|
+
# @return [Hash]
|
223
|
+
# the hash of options
|
224
|
+
attr_reader :options
|
225
|
+
|
226
|
+
# @return [String]
|
227
|
+
# the name of this cookbook
|
228
|
+
attr_reader :name
|
229
|
+
|
230
|
+
# Create a new legacy lockfile for processing
|
231
|
+
#
|
232
|
+
# @param [String] content
|
233
|
+
# the content to parse out and convert to a hash
|
234
|
+
def initialize(content)
|
235
|
+
instance_eval(content).to_hash
|
236
|
+
end
|
237
|
+
|
238
|
+
# Method defined in legacy lockfiles (since we are using
|
239
|
+
# instance_eval).
|
240
|
+
#
|
241
|
+
# @param [String] name
|
242
|
+
# the name of this cookbook
|
243
|
+
# @option options [String] :locked_version
|
244
|
+
# the locked version of this cookbook
|
245
|
+
def cookbook(name, options = {})
|
246
|
+
@name = name
|
247
|
+
@options = options
|
248
|
+
end
|
249
|
+
end
|
53
250
|
end
|
54
251
|
end
|