Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 15 additions & 2 deletions lib/mixlib/config.rb
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,8 @@ def from_yaml(filename)
# objects (a deserialization RCE vector). Symbols are permitted because
# they are commonly used in config files; this matches the default
# behavior of YAML.load on Psych 4 while remaining safe on older Psych.
from_hash(YAML.safe_load(IO.read(filename), permitted_classes: [Symbol], aliases: false))
# An empty or comment-only file parses to nil; treat it as an empty config.
from_parsed_file(filename, YAML.safe_load(IO.read(filename), permitted_classes: [Symbol], aliases: false) || {})
end

# Parses valid JSON structure into Ruby
Expand All @@ -83,7 +84,7 @@ def from_yaml(filename)
# filename<String>:: A filename to read from
def from_json(filename)
require "json" unless defined?(JSON)
from_hash(JSON.parse(IO.read(filename)))
from_parsed_file(filename, JSON.parse(IO.read(filename)))
end

def from_toml(filename)
Expand Down Expand Up @@ -578,6 +579,18 @@ def apply_nested_hash(hash)

private

# Applies the result of parsing a config file, which must be a Hash.
#
# === Raises
# <ArgumentError>:: If the file's top level is not a mapping.
def from_parsed_file(filename, parsed)
unless parsed.is_a?(Hash)
raise ArgumentError, "#{filename} must contain a mapping of config options at the top level, not #{parsed.class}"
end

from_hash(parsed)
end

# Given a (nested) Hash, turn it into a single top-level hash using dots as
# nesting notation. This allows for direction translation into method-style
# setting of Config.
Expand Down
24 changes: 24 additions & 0 deletions spec/mixlib/config_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -1262,6 +1262,24 @@ class StrictClass3
ConfigIt.from_file("config.yml")
end.to raise_error(Psych::DisallowedClass)
end

it "treats an empty YAML file as an empty config" do
allow(IO).to receive(:read).with("config.yml").and_return("")

expect { ConfigIt.from_file("config.yml") }.to_not raise_error
end

it "treats a comment-only YAML file as an empty config" do
allow(IO).to receive(:read).with("config.yml").and_return("# nothing set yet\n")

expect { ConfigIt.from_file("config.yml") }.to_not raise_error
end

it "raises an ArgumentError naming the file when the top level is not a mapping" do
allow(IO).to receive(:read).with("config.yml").and_return("- one\n- two\n")

expect { ConfigIt.from_file("config.yml") }.to raise_error(ArgumentError, /config\.yml/)
end
end

describe ".from_json" do
Expand Down Expand Up @@ -1290,6 +1308,12 @@ class StrictClass3
expect(ConfigIt.foo).to eql(%w{ bar baz matazz })
expect(ConfigIt.alpha).to eql("beta")
end

it "raises an ArgumentError naming the file when the top level is not an object" do
allow(IO).to receive(:read).with("config.json").and_return("[1, 2]")

expect { ConfigIt.from_file("config.json") }.to raise_error(ArgumentError, /config\.json/)
end
end

describe ".from_toml" do
Expand Down
Loading