berkshelf 0.6.0.beta2 → 0.6.0.beta3
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/.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
|