berkshelf 0.6.0.beta2 → 0.6.0.beta3
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +1 -2
- data/.travis.yml +7 -0
- data/Gemfile +1 -1
- data/README.md +1 -0
- data/Thorfile +7 -0
- data/berkshelf.gemspec +4 -1
- data/features/config.feature +97 -0
- data/features/config_command.feature +10 -0
- data/features/cookbook_command.feature +12 -12
- data/features/default_locations.feature +5 -0
- data/features/install.feature +16 -0
- data/features/json_formatter.feature +1 -1
- data/features/step_definitions/cli_steps.rb +5 -1
- data/features/step_definitions/filesystem_steps.rb +45 -23
- data/features/upload_command.feature +4 -4
- data/generator_files/Gemfile.erb +2 -2
- data/generator_files/Vagrantfile.erb +34 -13
- data/generator_files/config.json +22 -0
- data/lib/berkshelf/cli.rb +24 -9
- data/lib/berkshelf/config.rb +51 -0
- data/lib/berkshelf/config_generator.rb +8 -0
- data/lib/berkshelf/config_validator.rb +78 -0
- data/lib/berkshelf/cookbook_generator.rb +2 -2
- data/lib/berkshelf/core_ext/file_utils.rb +36 -0
- data/lib/berkshelf/downloader.rb +41 -14
- data/lib/berkshelf/errors.rb +20 -0
- data/lib/berkshelf/formatters/human_readable.rb +7 -0
- data/lib/berkshelf/git.rb +13 -3
- data/lib/berkshelf/init_generator.rb +17 -4
- data/lib/berkshelf/locations/chef_api_location.rb +1 -1
- data/lib/berkshelf/locations/git_location.rb +1 -1
- data/lib/berkshelf/locations/site_location.rb +1 -1
- data/lib/berkshelf/version.rb +1 -1
- data/lib/berkshelf.rb +17 -10
- data/spec/support/knife.rb +1 -1
- data/spec/support/matchers/file_system_matchers.rb +16 -1
- data/spec/unit/berkshelf/config_spec.rb +91 -0
- data/spec/unit/berkshelf/config_validator_spec.rb +68 -0
- data/spec/unit/berkshelf/cookbook_source_spec.rb +4 -4
- data/spec/unit/berkshelf/core_ext/file_utils_spec.rb +23 -0
- data/spec/unit/berkshelf/init_generator_spec.rb +34 -32
- data/spec/unit/berkshelf/locations/chef_api_location_spec.rb +1 -1
- data/spec/unit/berkshelf/lockfile_spec.rb +2 -2
- data/spec/unit/berkshelf/resolver_spec.rb +1 -1
- data/spec/unit/berkshelf/uploader_spec.rb +1 -1
- metadata +68 -5
- data/lib/vagrant_init.rb +0 -2
@@ -0,0 +1,51 @@
|
|
1
|
+
module Berkshelf
|
2
|
+
# @author Justin Campbell <justin@justincampbell.me>
|
3
|
+
class Config < Hashie::Mash
|
4
|
+
DEFAULT_PATH = "~/.berkshelf/config.json"
|
5
|
+
|
6
|
+
include ActiveModel::Validations
|
7
|
+
validates_with ConfigValidator
|
8
|
+
|
9
|
+
class << self
|
10
|
+
# @return [String, nil]
|
11
|
+
# the contents of the file
|
12
|
+
def file
|
13
|
+
File.read path if File.exists? path
|
14
|
+
end
|
15
|
+
|
16
|
+
# @param [#to_s] json
|
17
|
+
#
|
18
|
+
# @return [Config]
|
19
|
+
def from_json(json)
|
20
|
+
hash = JSON.parse(json).to_hash
|
21
|
+
|
22
|
+
new.tap do |config|
|
23
|
+
hash.each do |key, value|
|
24
|
+
config[key] = value
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
# @return [Config]
|
30
|
+
def instance
|
31
|
+
@instance ||= if file
|
32
|
+
from_json file
|
33
|
+
else
|
34
|
+
new
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# @return [String]
|
39
|
+
def path
|
40
|
+
File.expand_path DEFAULT_PATH
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
# @param [String, Symbol] key
|
45
|
+
#
|
46
|
+
# @return [Config, Object]
|
47
|
+
def [](key)
|
48
|
+
super or self.class.new
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
module Berkshelf
|
2
|
+
# @author Justin Campbell <justin@justincampbell.me>
|
3
|
+
class ConfigValidator < ActiveModel::Validator
|
4
|
+
DEFAULT_STRUCTURE = {
|
5
|
+
vagrant: {
|
6
|
+
chef: {
|
7
|
+
chef_server_url: String,
|
8
|
+
validation_client_name: String,
|
9
|
+
validation_key_path: String
|
10
|
+
},
|
11
|
+
vm: {
|
12
|
+
box: String,
|
13
|
+
box_url: String,
|
14
|
+
forward_port: Hash,
|
15
|
+
host_name: String,
|
16
|
+
network: {
|
17
|
+
bridged: Object,
|
18
|
+
hostonly: String
|
19
|
+
},
|
20
|
+
provision: String
|
21
|
+
}
|
22
|
+
}
|
23
|
+
}
|
24
|
+
|
25
|
+
# Recursively validate the structure of a hash with another hash. If
|
26
|
+
# invalid, the actual_hash will have errors added to it.
|
27
|
+
#
|
28
|
+
# @param [Hash] actual_hash
|
29
|
+
# The hash to validate
|
30
|
+
#
|
31
|
+
# @param [Hash] expected_hash
|
32
|
+
# The expected structure of actual_hash
|
33
|
+
#
|
34
|
+
# @param [Config] config
|
35
|
+
# The config object to add errors to. This is only used recursively.
|
36
|
+
#
|
37
|
+
# @return [Boolean]
|
38
|
+
def assert_in_structure(actual_hash, expected_hash, config = nil)
|
39
|
+
config ||= actual_hash
|
40
|
+
|
41
|
+
actual_hash.keys.each do |key|
|
42
|
+
unless expected_hash.keys.include? key.to_sym
|
43
|
+
config.errors.add key, "is not a valid key"
|
44
|
+
return
|
45
|
+
end
|
46
|
+
|
47
|
+
actual = actual_hash[key]
|
48
|
+
expected = expected_hash[key.to_sym]
|
49
|
+
|
50
|
+
if actual.is_a?(Hash) && expected.is_a?(Hash)
|
51
|
+
return unless assert_in_structure actual, expected, config
|
52
|
+
else
|
53
|
+
unless actual.is_a? expected
|
54
|
+
config.errors.add key, "should be an instance of #{expected}"
|
55
|
+
return
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
true
|
61
|
+
end
|
62
|
+
|
63
|
+
# @see DEFAULT_STRUCTURE
|
64
|
+
# @return [Hash]
|
65
|
+
def structure
|
66
|
+
@structure ||= DEFAULT_STRUCTURE
|
67
|
+
end
|
68
|
+
|
69
|
+
# @param [Config] config
|
70
|
+
# The config to validate
|
71
|
+
#
|
72
|
+
# @return [Boolean]
|
73
|
+
def validate(config)
|
74
|
+
assert_in_structure config, structure
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
|
3
|
+
module FileUtils
|
4
|
+
class << self
|
5
|
+
alias_method :old_mv, :mv
|
6
|
+
|
7
|
+
# Override mv to ensure {safe_mv} is run if we are on the Windows platform.
|
8
|
+
#
|
9
|
+
# @see {FileUtils::mv}
|
10
|
+
# @see {safe_mv}
|
11
|
+
def mv(src, dest, options = {})
|
12
|
+
if windows?
|
13
|
+
safe_mv(src, dest, options)
|
14
|
+
else
|
15
|
+
old_mv(src, dest, options)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
# If we encounter Errno::EACCES, which seems to happen occasionally on Windows,
|
20
|
+
# try to copy and delete the file instead of moving it.
|
21
|
+
#
|
22
|
+
# @see https://github.com/RiotGames/berkshelf/issues/140
|
23
|
+
# @see http://www.ruby-forum.com/topic/1044813
|
24
|
+
#
|
25
|
+
# @param [String] src
|
26
|
+
# @param [String] dest
|
27
|
+
# @param [Hash] options
|
28
|
+
# @see {FileUtils::mv}
|
29
|
+
def safe_mv(src, dest, options = {})
|
30
|
+
FileUtils.mv(src, dest, options)
|
31
|
+
rescue Errno::EACCES
|
32
|
+
FileUtils.cp_r(src, dest)
|
33
|
+
FileUtils.rm_rf(src)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
data/lib/berkshelf/downloader.rb
CHANGED
@@ -28,7 +28,7 @@ module Berkshelf
|
|
28
28
|
# to download cookbook sources which do not have an explicit location. An array of default locations will
|
29
29
|
# be used if no locations are explicitly added by the {#add_location} function.
|
30
30
|
def locations
|
31
|
-
@locations.
|
31
|
+
@locations.any? ? @locations : DEFAULT_LOCATIONS
|
32
32
|
end
|
33
33
|
|
34
34
|
# Create a location hash and add it to the end of the array of locations.
|
@@ -43,9 +43,10 @@ module Berkshelf
|
|
43
43
|
# @return [Hash]
|
44
44
|
def add_location(type, value, options = {})
|
45
45
|
if has_location?(type, value)
|
46
|
-
raise DuplicateLocationDefined,
|
46
|
+
raise DuplicateLocationDefined,
|
47
|
+
"A default '#{type}' location with the value '#{value}' is already defined"
|
47
48
|
end
|
48
|
-
|
49
|
+
|
49
50
|
@locations.push(type: type, value: value, options: options)
|
50
51
|
end
|
51
52
|
|
@@ -54,21 +55,48 @@ module Berkshelf
|
|
54
55
|
#
|
55
56
|
# @return [Boolean]
|
56
57
|
def has_location?(type, value)
|
57
|
-
|
58
|
+
@locations.select { |loc| loc[:type] == type && loc[:value] == value }.any?
|
58
59
|
end
|
59
60
|
|
60
|
-
# Downloads the given CookbookSource.
|
61
|
-
# the default locations of this downloader will be used to attempt to retrieve the source.
|
61
|
+
# Downloads the given CookbookSource.
|
62
62
|
#
|
63
63
|
# @param [CookbookSource] source
|
64
64
|
# the source to download
|
65
65
|
#
|
66
66
|
# @return [Array]
|
67
|
-
# an array containing the downloaded CachedCookbook and the Location used
|
67
|
+
# an array containing the downloaded CachedCookbook and the Location used
|
68
|
+
# to download the cookbook
|
68
69
|
def download(source)
|
69
70
|
cached_cookbook, location = if source.location
|
70
|
-
|
71
|
+
begin
|
72
|
+
[source.location.download(storage_path), source.location]
|
73
|
+
rescue
|
74
|
+
Berkshelf.formatter.error "Failed to download #{source.name} from #{source.location}"
|
75
|
+
|
76
|
+
raise
|
77
|
+
end
|
71
78
|
else
|
79
|
+
search_locations(source)
|
80
|
+
end
|
81
|
+
|
82
|
+
source.cached_cookbook = cached_cookbook
|
83
|
+
|
84
|
+
[cached_cookbook, location]
|
85
|
+
end
|
86
|
+
|
87
|
+
private
|
88
|
+
|
89
|
+
# Searches locations for a CookbookSource. If the source does not contain a
|
90
|
+
# value for {CookbookSource#location}, the default locations of this
|
91
|
+
# downloader will be used to attempt to retrieve the source.
|
92
|
+
#
|
93
|
+
# @param [CookbookSource] source
|
94
|
+
# the source to download
|
95
|
+
#
|
96
|
+
# @return [Array]
|
97
|
+
# an array containing the downloaded CachedCookbook and the Location used
|
98
|
+
# to download the cookbook
|
99
|
+
def search_locations(source)
|
72
100
|
cached_cookbook = nil
|
73
101
|
location = nil
|
74
102
|
|
@@ -94,13 +122,12 @@ module Berkshelf
|
|
94
122
|
[ cached_cookbook, location ]
|
95
123
|
end
|
96
124
|
|
97
|
-
source.cached_cookbook = cached_cookbook
|
98
|
-
|
99
|
-
[ cached_cookbook, location ]
|
100
|
-
end
|
101
|
-
|
102
|
-
private
|
103
125
|
|
126
|
+
# Validates that a source is an instance of CookbookSource
|
127
|
+
#
|
128
|
+
# @param [CookbookSource] source
|
129
|
+
#
|
130
|
+
# @return [Boolean]
|
104
131
|
def validate_source(source)
|
105
132
|
source.is_a?(Berkshelf::CookbookSource)
|
106
133
|
end
|
data/lib/berkshelf/errors.rb
CHANGED
@@ -80,4 +80,24 @@ module Berkshelf
|
|
80
80
|
end
|
81
81
|
|
82
82
|
class AmbiguousCookbookName < BerkshelfError; status_code(114); end
|
83
|
+
|
84
|
+
class InvalidConfiguration < BerkshelfError
|
85
|
+
status_code(115)
|
86
|
+
|
87
|
+
def initialize(errors)
|
88
|
+
@errors = errors
|
89
|
+
end
|
90
|
+
|
91
|
+
def to_s
|
92
|
+
strings = ["Invalid configuration:"]
|
93
|
+
|
94
|
+
@errors.messages.each do |key, errors|
|
95
|
+
errors.each do |error|
|
96
|
+
strings << " #{key} #{error}"
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
strings.join "\n"
|
101
|
+
end
|
102
|
+
end
|
83
103
|
end
|
data/lib/berkshelf/git.rb
CHANGED
@@ -4,8 +4,10 @@ require 'mixlib/shellout'
|
|
4
4
|
module Berkshelf
|
5
5
|
# @author Jamie Winsor <jamie@vialstudios.com>
|
6
6
|
class Git
|
7
|
-
GIT_REGEXP = URI.regexp(%w{ https git })
|
8
|
-
SSH_REGEXP = /(.+)@(.+):(.+)\.git
|
7
|
+
GIT_REGEXP = URI.regexp(%w{ https git }).freeze
|
8
|
+
SSH_REGEXP = /(.+)@(.+):(.+)\.git/.freeze
|
9
|
+
HAS_QUOTE_RE = %r{\"}.freeze
|
10
|
+
HAS_SPACE_RE = %r{\s}.freeze
|
9
11
|
|
10
12
|
class << self
|
11
13
|
# @overload git(commands)
|
@@ -16,7 +18,9 @@ module Berkshelf
|
|
16
18
|
# @return [String]
|
17
19
|
# the output of the execution of the Git command
|
18
20
|
def git(*command)
|
19
|
-
|
21
|
+
command.unshift(git_cmd)
|
22
|
+
command_str = command.map { |p| quote_cmd_arg(p) }.join(' ')
|
23
|
+
cmd = Mixlib::ShellOut.new(command_str)
|
20
24
|
cmd.run_command
|
21
25
|
|
22
26
|
unless cmd.exitstatus == 0
|
@@ -134,6 +138,12 @@ module Berkshelf
|
|
134
138
|
def git_cmd
|
135
139
|
@git_cmd ||= find_git
|
136
140
|
end
|
141
|
+
|
142
|
+
def quote_cmd_arg(arg)
|
143
|
+
return arg if HAS_QUOTE_RE.match(arg)
|
144
|
+
return arg unless HAS_SPACE_RE.match(arg)
|
145
|
+
"\"#{arg}\""
|
146
|
+
end
|
137
147
|
end
|
138
148
|
end
|
139
149
|
end
|
@@ -20,11 +20,11 @@ module Berkshelf
|
|
20
20
|
type: :boolean,
|
21
21
|
default: false
|
22
22
|
|
23
|
-
class_option :
|
23
|
+
class_option :skip_vagrant,
|
24
24
|
type: :boolean,
|
25
25
|
default: false
|
26
26
|
|
27
|
-
class_option :
|
27
|
+
class_option :skip_git,
|
28
28
|
type: :boolean,
|
29
29
|
default: false
|
30
30
|
|
@@ -43,15 +43,22 @@ module Berkshelf
|
|
43
43
|
class_option :cookbook_name,
|
44
44
|
type: :string
|
45
45
|
|
46
|
+
class_option :berkshelf_config,
|
47
|
+
type: :hash,
|
48
|
+
default: Config.instance
|
49
|
+
|
46
50
|
def generate
|
51
|
+
validate_configuration
|
52
|
+
|
47
53
|
template "Berksfile.erb", target.join("Berksfile")
|
48
54
|
|
49
55
|
if options[:chefignore]
|
50
56
|
copy_file "chefignore", target.join("chefignore")
|
51
57
|
end
|
52
58
|
|
53
|
-
|
59
|
+
unless options[:skip_git]
|
54
60
|
template "gitignore.erb", target.join(".gitignore")
|
61
|
+
|
55
62
|
unless File.exists?(target.join(".git"))
|
56
63
|
inside target do
|
57
64
|
run "git init", capture: true
|
@@ -71,7 +78,7 @@ module Berkshelf
|
|
71
78
|
template "Gemfile.erb", target.join("Gemfile")
|
72
79
|
end
|
73
80
|
|
74
|
-
|
81
|
+
unless options[:skip_vagrant]
|
75
82
|
template "Vagrantfile.erb", target.join("Vagrantfile")
|
76
83
|
::Berkshelf::Cli.new([], berksfile: target.join("Berksfile")).invoke(:install)
|
77
84
|
end
|
@@ -89,5 +96,11 @@ module Berkshelf
|
|
89
96
|
File.basename(target)
|
90
97
|
end
|
91
98
|
end
|
99
|
+
|
100
|
+
def validate_configuration
|
101
|
+
unless Config.instance.valid?
|
102
|
+
raise InvalidConfiguration.new Config.instance.errors
|
103
|
+
end
|
104
|
+
end
|
92
105
|
end
|
93
106
|
end
|
@@ -130,7 +130,7 @@ module Berkshelf
|
|
130
130
|
scratch = download_files(cookbook.manifest)
|
131
131
|
|
132
132
|
cb_path = File.join(destination, "#{name}-#{version}")
|
133
|
-
FileUtils.mv(scratch, cb_path
|
133
|
+
FileUtils.mv(scratch, cb_path)
|
134
134
|
|
135
135
|
cached = CachedCookbook.from_store_path(cb_path)
|
136
136
|
validate_cached(cached)
|
@@ -52,7 +52,7 @@ module Berkshelf
|
|
52
52
|
|
53
53
|
cb_path = File.join(destination, "#{self.name}-#{Git.rev_parse(tmp_clone)}")
|
54
54
|
FileUtils.rm_rf(cb_path)
|
55
|
-
FileUtils.mv(tmp_clone, cb_path
|
55
|
+
FileUtils.mv(tmp_clone, cb_path)
|
56
56
|
|
57
57
|
cached = CachedCookbook.from_store_path(cb_path)
|
58
58
|
validate_cached(cached)
|
@@ -53,7 +53,7 @@ module Berkshelf
|
|
53
53
|
cb_path = File.join(destination, "#{name}-#{version}")
|
54
54
|
|
55
55
|
self.class.unpack(downloaded_tf.path, dir)
|
56
|
-
FileUtils.mv(File.join(dir, name), cb_path
|
56
|
+
FileUtils.mv(File.join(dir, name), cb_path)
|
57
57
|
|
58
58
|
cached = CachedCookbook.from_store_path(cb_path)
|
59
59
|
validate_cached(cached)
|
data/lib/berkshelf/version.rb
CHANGED
data/lib/berkshelf.rb
CHANGED
@@ -1,17 +1,21 @@
|
|
1
|
+
require 'chef/cookbook/metadata'
|
2
|
+
require 'chef/cookbook_version'
|
3
|
+
require 'chef/knife'
|
4
|
+
require 'chef/platform'
|
5
|
+
|
6
|
+
require 'chozo/core_ext'
|
7
|
+
require 'active_support/core_ext'
|
8
|
+
require 'active_model'
|
9
|
+
require 'archive/tar/minitar'
|
1
10
|
require 'forwardable'
|
2
|
-
require '
|
11
|
+
require 'hashie'
|
3
12
|
require 'pathname'
|
4
|
-
require 'tmpdir'
|
5
|
-
require 'zlib'
|
6
|
-
require 'archive/tar/minitar'
|
7
|
-
require 'solve'
|
8
13
|
require 'ridley'
|
14
|
+
require 'solve'
|
9
15
|
require 'thor'
|
10
|
-
require '
|
11
|
-
require '
|
12
|
-
require '
|
13
|
-
require 'chef/cookbook_version'
|
14
|
-
require 'active_support/core_ext'
|
16
|
+
require 'tmpdir'
|
17
|
+
require 'uri'
|
18
|
+
require 'zlib'
|
15
19
|
|
16
20
|
require 'berkshelf/version'
|
17
21
|
require 'berkshelf/core_ext'
|
@@ -39,6 +43,9 @@ module Berkshelf
|
|
39
43
|
autoload :Downloader, 'berkshelf/downloader'
|
40
44
|
autoload :Uploader, 'berkshelf/uploader'
|
41
45
|
autoload :Resolver, 'berkshelf/resolver'
|
46
|
+
autoload :Config, 'berkshelf/config'
|
47
|
+
autoload :ConfigGenerator, 'berkshelf/config_generator'
|
48
|
+
autoload :ConfigValidator, 'berkshelf/config_validator'
|
42
49
|
|
43
50
|
require 'berkshelf/location'
|
44
51
|
|
data/spec/support/knife.rb
CHANGED
@@ -9,7 +9,7 @@ module Berkshelf
|
|
9
9
|
Chef::Config.from_file(path)
|
10
10
|
ENV["CHEF_CONFIG"] = path
|
11
11
|
else
|
12
|
-
raise "Cannot continue; '#{path}' must exist and have testing credentials."
|
12
|
+
raise "Cannot continue; '#{path}' must exist and have testing credentials." unless ENV['CI']
|
13
13
|
end
|
14
14
|
end
|
15
15
|
end
|
@@ -6,6 +6,7 @@ module Berkshelf
|
|
6
6
|
class File
|
7
7
|
def initialize(name, &block)
|
8
8
|
@contents = []
|
9
|
+
@negative_contents = []
|
9
10
|
@name = name
|
10
11
|
|
11
12
|
if block_given?
|
@@ -17,6 +18,10 @@ module Berkshelf
|
|
17
18
|
@contents << text
|
18
19
|
end
|
19
20
|
|
21
|
+
def does_not_contain(text)
|
22
|
+
@negative_contents << text
|
23
|
+
end
|
24
|
+
|
20
25
|
def matches?(root)
|
21
26
|
unless root.join(@name).exist?
|
22
27
|
throw :failure, root.join(@name)
|
@@ -29,6 +34,12 @@ module Berkshelf
|
|
29
34
|
throw :failure, [root.join(@name), string, contents]
|
30
35
|
end
|
31
36
|
end
|
37
|
+
|
38
|
+
@negative_contents.each do |string|
|
39
|
+
if contents.include?(string)
|
40
|
+
throw :failure, [:not, root.join(@name), string, contents]
|
41
|
+
end
|
42
|
+
end
|
32
43
|
end
|
33
44
|
end
|
34
45
|
|
@@ -82,7 +93,11 @@ module Berkshelf
|
|
82
93
|
class Root < Directory
|
83
94
|
def failure_message
|
84
95
|
if @failure.is_a?(Array) && @failure[0] == :not
|
85
|
-
|
96
|
+
if @failure[2]
|
97
|
+
"File #{@failure[1]} should not have contained \"#{@failure[2]}\""
|
98
|
+
else
|
99
|
+
"Structure should not have had #{@failure[1]}, but it did"
|
100
|
+
end
|
86
101
|
elsif @failure.is_a?(Array)
|
87
102
|
"Structure should have #{@failure[0]} with #{@failure[1]}. It had:\n#{@failure[2]}"
|
88
103
|
else
|
@@ -0,0 +1,91 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Berkshelf::Config do
|
4
|
+
subject { config }
|
5
|
+
|
6
|
+
let(:config) { klass.new }
|
7
|
+
let(:klass) { described_class }
|
8
|
+
|
9
|
+
it { should be_valid }
|
10
|
+
|
11
|
+
its(:present?) { should be_false }
|
12
|
+
|
13
|
+
it "set and gets hash keys" do
|
14
|
+
config[:a] = 1
|
15
|
+
config[:a].should == 1
|
16
|
+
end
|
17
|
+
|
18
|
+
it "does not raise an error for nested hash keys that have not been set" do
|
19
|
+
config[:d][:e]
|
20
|
+
end
|
21
|
+
|
22
|
+
it "has indifferent access" do
|
23
|
+
config[:a] = 1
|
24
|
+
config['b'] = 2
|
25
|
+
|
26
|
+
config['a'].should == 1
|
27
|
+
config[:b].should == 2
|
28
|
+
end
|
29
|
+
|
30
|
+
describe ".file" do
|
31
|
+
subject { klass.file }
|
32
|
+
|
33
|
+
context "when the file does not exist" do
|
34
|
+
before :each do
|
35
|
+
File.stub exists?: false
|
36
|
+
end
|
37
|
+
|
38
|
+
it { should be_nil }
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
describe ".from_json" do
|
43
|
+
subject(:config) { klass.from_json json }
|
44
|
+
|
45
|
+
let(:json) {
|
46
|
+
<<-JSON
|
47
|
+
{
|
48
|
+
"a": 1,
|
49
|
+
"b": {
|
50
|
+
"c": 2
|
51
|
+
}
|
52
|
+
}
|
53
|
+
JSON
|
54
|
+
}
|
55
|
+
|
56
|
+
it "has data" do
|
57
|
+
config[:a].should == 1
|
58
|
+
end
|
59
|
+
|
60
|
+
it "has nested data" do
|
61
|
+
config[:b][:c].should == 2
|
62
|
+
end
|
63
|
+
|
64
|
+
it "does not raise an error for nested hash keys that have not been set" do
|
65
|
+
config[:d][:e]
|
66
|
+
end
|
67
|
+
|
68
|
+
it "has indifferent access" do
|
69
|
+
config['a'].should == 1
|
70
|
+
config[:a].should == 1
|
71
|
+
end
|
72
|
+
|
73
|
+
context "with an invalid configuration" do
|
74
|
+
let(:json) { '{ "wat": 1 }' }
|
75
|
+
|
76
|
+
it { should_not be_valid }
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
describe ".instance" do
|
81
|
+
subject { klass.instance }
|
82
|
+
|
83
|
+
it { should be_a klass }
|
84
|
+
end
|
85
|
+
|
86
|
+
describe ".path" do
|
87
|
+
subject { klass.path }
|
88
|
+
|
89
|
+
it { should be_a String }
|
90
|
+
end
|
91
|
+
end
|