aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJosh Cooper <josh@puppet.com>2018-08-15 17:07:29 -0700
committerGitHub <noreply@github.com>2018-08-15 17:07:29 -0700
commit6df0e151e419539dca90849abd9c81a730c2b702 (patch)
tree4722f0ee30cc438dd2c9e6405bcc5edbf7cc6d80
parente85283b0f41ae1635954c76b2978e34c260794cd (diff)
parent5fdf3e2567bab490e42bfe8d16b875c1af8359ee (diff)
downloadpuppet-augeas_core-6df0e151e419539dca90849abd9c81a730c2b702.tar.gz
puppet-augeas_core-6df0e151e419539dca90849abd9c81a730c2b702.tar.bz2
Merge pull request #4 from melissa/1.0.1-release
1.0.1 release
-rw-r--r--.sync.yml2
-rw-r--r--CHANGELOG.md18
-rw-r--r--Gemfile2
-rw-r--r--lib/puppet/provider/augeas/augeas.rb16
-rw-r--r--lib/puppet_x/augeas/util/parser.rb215
-rw-r--r--metadata.json6
-rw-r--r--spec/spec_helper_acceptance.rb2
-rw-r--r--spec/unit/provider/augeas/augeas_spec.rb65
-rw-r--r--spec/unit/puppet_x/augeas/util/parser_spec.rb110
9 files changed, 416 insertions, 20 deletions
diff --git a/.sync.yml b/.sync.yml
index 100cd76..9d88d65 100644
--- a/.sync.yml
+++ b/.sync.yml
@@ -45,6 +45,6 @@ Gemfile:
from_env: BEAKER_RSPEC_VERSION
- gem: beaker-puppet
from_env: BEAKER_PUPPET_VERSION
- version: '~> 0.14'
+ version: '~> 0.16'
':development':
- gem: puppet-strings
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 4c954cd..7b2d9c4 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,10 +2,20 @@
All notable changes to this project will be documented in this file.
-## Release 0.1.0
+The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org).
-**Features**
+## [1.0.1] - 2018-08-15
+### Added
+- (MODULE-7443) Safely deserialize stringified array
+### Changed
+- (maint) Move array parser logic into a util module
+- (maint) Update beaker-puppet version to 0.16
+- Include puppet 6 and remove default role
+- (PUP-9052) Bump puppet req to at least puppet 6
-**Bugfixes**
+## [1.0.0] - 2018-06-07
+### Summary
+This is the initial release of the extracted augeas module
-**Known Issues**
+[1.0.1]: https://github.com/puppetlabs/puppetlabs-augeas_core/compare/1.0.0...1.0.1
+[1.0.0]: https://github.com/puppetlabs/puppetlabs-augeas_core/releases/tag/1.0.0
diff --git a/Gemfile b/Gemfile
index 1c6557c..4c9526b 100644
--- a/Gemfile
+++ b/Gemfile
@@ -43,7 +43,7 @@ group :system_tests do
gem "beaker-pe", require: false
gem "beaker-hostgenerator"
gem "beaker-rspec"
- gem "beaker-puppet", *location_for(ENV['BEAKER_PUPPET_VERSION'] || '~> 0.14')
+ gem "beaker-puppet", *location_for(ENV['BEAKER_PUPPET_VERSION'] || '~> 0.16')
end
puppet_version = ENV['PUPPET_GEM_VERSION']
diff --git a/lib/puppet/provider/augeas/augeas.rb b/lib/puppet/provider/augeas/augeas.rb
index 05183e5..ee52ee6 100644
--- a/lib/puppet/provider/augeas/augeas.rb
+++ b/lib/puppet/provider/augeas/augeas.rb
@@ -18,12 +18,13 @@ require 'strscan'
require 'puppet/util'
require 'puppet/util/diff'
require 'puppet/util/package'
-require 'json'
+require 'puppet_x/augeas/util/parser'
Puppet::Type.type(:augeas).provide(:augeas) do
include Puppet::Util
include Puppet::Util::Diff
include Puppet::Util::Package
+ include PuppetX::Augeas::Util::Parser
confine feature: :augeas
@@ -281,7 +282,7 @@ Puppet::Type.type(:augeas).provide(:augeas) do
when '=='
begin
arg = clause_array.shift
- new_array = to_array(arg)
+ new_array = parse_to_array(arg)
return_value = (values == new_array)
rescue
fail(_('Invalid array in command: %{cmd}') % { cmd: cmd_array.join(' ') })
@@ -289,7 +290,7 @@ Puppet::Type.type(:augeas).provide(:augeas) do
when '!='
begin
arg = clause_array.shift
- new_array = to_array(arg)
+ new_array = parse_to_array(arg)
return_value = (values != new_array)
rescue
fail(_('Invalid array in command: %{cmd}') % { cmd: cmd_array.join(' ') })
@@ -337,7 +338,7 @@ Puppet::Type.type(:augeas).provide(:augeas) do
when '=='
begin
arg = clause_array.shift
- new_array = to_array(arg)
+ new_array = parse_to_array(arg)
return_value = (result == new_array)
rescue
fail(_('Invalid array in command: %{cmd}') % { cmd: cmd_array.join(' ') })
@@ -345,7 +346,7 @@ Puppet::Type.type(:augeas).provide(:augeas) do
when '!='
begin
arg = clause_array.shift
- new_array = to_array(arg)
+ new_array = parse_to_array(arg)
return_value = (result != new_array)
rescue
fail(_('Invalid array in command: %{cmd}') % { cmd: cmd_array.join(' ') })
@@ -572,9 +573,4 @@ Puppet::Type.type(:augeas).provide(:augeas) do
end
end
# rubocop:enable Style/GuardClause
-
- def to_array(string)
- JSON.parse(string.tr("'", '"'))
- end
- private :to_array
end
diff --git a/lib/puppet_x/augeas/util/parser.rb b/lib/puppet_x/augeas/util/parser.rb
new file mode 100644
index 0000000..1abf42f
--- /dev/null
+++ b/lib/puppet_x/augeas/util/parser.rb
@@ -0,0 +1,215 @@
+# rubocop:disable Style/Documentation
+module PuppetX; end
+module PuppetX::Augeas; end
+module PuppetX::Augeas::Util; end
+# rubocop:enable Style/Documentation
+
+# Container for helpers to parse user provided data contained in manifests.
+module PuppetX::Augeas::Util::Parser
+ TOKEN_ARRAY_CLOSE = %r{\s*\]\s*}
+ TOKEN_ARRAY_OPEN = %r{\s*\[\s*}
+ TOKEN_ARRAY_SEPARATOR = %r{\s*,\s*}
+ TOKEN_CLOSE_CURLY = %r|}|
+ TOKEN_DOUBLE_QUOTE = %r{"}
+ TOKEN_DOUBLE_QUOTE_ESCAPED_CHAR = %r{\\(["\\abtnvfres0-7xu])}
+ TOKEN_DOUBLE_QUOTE_UNESCAPED_CHAR = %r{[^"\\]}
+ TOKEN_HEX_CHAR = %r{[0-9a-fA-F]{1,2}}
+ TOKEN_OCTAL_CHAR = %r{[0-7]{1,3}}
+ TOKEN_OPEN_CURLY = %r|{|
+ TOKEN_SINGLE_QUOTE = %r{'}
+ TOKEN_SINGLE_QUOTE_ESCAPED_CHAR = %r{\\(['\\])}
+ TOKEN_SINGLE_QUOTE_UNESCAPED_CHAR = %r{[^'\\]}
+ TOKEN_SPACE = %r{\s}
+ TOKEN_UNICODE_LONG_HEX_CHAR = %r{[0-9a-fA-F]{1,6}}
+ TOKEN_UNICODE_SHORT_HEX_CHAR = %r{[0-9a-fA-F]{4}}
+
+ # Parse a string into the (nearly) equivalent Ruby array. This only handles
+ # arrays with string members (double-, or single-quoted), and does not
+ # support the full quite of escape sequences that Ruby allows in
+ # double-quoted strings.
+ #
+ # @param [String] The string to be parsed.
+ # @return [Array<String>] The parsed array elements, including handling any
+ # escape sequences.
+ def parse_to_array(string)
+ s = StringScanner.new(string)
+ match = array_open(s)
+ raise "Unexpected character in array at: #{s.rest}" if match.nil?
+
+ array_content = array_values(s)
+
+ match = array_close(s)
+ raise "Unexpected character in array at: #{s.rest}" if match.nil? || !s.empty?
+
+ array_content
+ end
+
+ def array_open(scanner)
+ scanner.scan(TOKEN_ARRAY_OPEN)
+ end
+ private :array_open
+
+ def array_close(scanner)
+ scanner.scan(TOKEN_ARRAY_CLOSE)
+ end
+ private :array_close
+
+ def array_separator(scanner)
+ scanner.scan(TOKEN_ARRAY_SEPARATOR)
+ end
+ private :array_separator
+
+ def single_quote_unescaped_char(scanner)
+ scanner.scan(TOKEN_SINGLE_QUOTE_UNESCAPED_CHAR)
+ end
+ private :single_quote_unescaped_char
+
+ def single_quote_escaped_char(scanner)
+ scanner.scan(TOKEN_SINGLE_QUOTE_ESCAPED_CHAR) && scanner[1]
+ end
+ private :single_quote_escaped_char
+
+ def single_quote_char(scanner)
+ single_quote_escaped_char(scanner) || single_quote_unescaped_char(scanner)
+ end
+ private :single_quote_char
+
+ def double_quote_unescaped_char(scanner)
+ scanner.scan(TOKEN_DOUBLE_QUOTE_UNESCAPED_CHAR)
+ end
+ private :double_quote_unescaped_char
+
+ # This handles the possible Ruby escape sequences in double-quoted strings,
+ # except for \M-x, \M-\C-x, \M-\cx, \c\M-x, \c?, and \C-?. The full list of
+ # escape sequences, and their meanings is taken from:
+ # https://github.com/ruby/ruby/blob/90fdfec11a4a42653722e2ce2a672d6e87a57b8e/doc/syntax/literals.rdoc#strings
+ def double_quote_escaped_char(scanner)
+ match = scanner.scan(TOKEN_DOUBLE_QUOTE_ESCAPED_CHAR)
+ return nil if match.nil?
+
+ case scanner[1]
+ when '\\' then return '\\'
+ when '"' then return '"'
+ when 'a' then return "\a"
+ when 'b' then return "\b"
+ when 't' then return "\t"
+ when 'n' then return "\n"
+ when 'v' then return "\v"
+ when 'f' then return "\f"
+ when 'r' then return "\r"
+ when 'e' then return "\e"
+ when 's' then return "\s"
+ when %r{[0-7]}
+ # Back the scanner up by one byte so we can grab all of the potential
+ # octal digits at the same time.
+ scanner.pos = scanner.pos - 1
+ octal_character = scanner.scan(TOKEN_OCTAL_CHAR)
+
+ return octal_character.to_i(8).chr
+ when 'x'
+ hex_character = scanner.scan(TOKEN_HEX_CHAR)
+ return nil if hex_character.nil?
+
+ hex_character.to_i(16).chr
+ when 'u'
+ return unicode_short_hex_character(scanner) || unicode_long_hex_characters(scanner)
+ else
+ # Not a valid escape sequence as far as we're concerned.
+ return nil
+ end
+ end
+ private :double_quote_escaped_char
+
+ def unicode_short_hex_character(scanner)
+ unicode_character = scanner.scan(TOKEN_UNICODE_SHORT_HEX_CHAR)
+ return nil if unicode_character.nil?
+
+ [unicode_character.hex].pack 'U'
+ end
+ private :unicode_short_hex_character
+
+ def unicode_long_hex_characters(scanner)
+ unicode_string = ''
+ return nil unless scanner.scan(TOKEN_OPEN_CURLY)
+
+ loop do
+ char = scanner.scan(TOKEN_UNICODE_LONG_HEX_CHAR)
+ break if char.nil?
+ unicode_string << [char.hex].pack('U')
+
+ separator = scanner.scan(TOKEN_SPACE)
+ break if separator.nil?
+ end
+
+ return nil if scanner.scan(TOKEN_CLOSE_CURLY).nil? || unicode_string.empty?
+
+ unicode_string
+ end
+ private :unicode_long_hex_characters
+
+ def single_quoted_string(scanner)
+ quoted_string = ''
+
+ match = scanner.scan(TOKEN_SINGLE_QUOTE)
+ return nil if match.nil?
+
+ loop do
+ match = single_quote_char(scanner)
+ break if match.nil?
+
+ quoted_string << match
+ end
+
+ match = scanner.scan(TOKEN_SINGLE_QUOTE)
+ return quoted_string if match
+
+ nil
+ end
+ private :single_quoted_string
+
+ def double_quote_char(scanner)
+ double_quote_escaped_char(scanner) || double_quote_unescaped_char(scanner)
+ end
+ private :double_quote_char
+
+ def double_quoted_string(scanner)
+ quoted_string = ''
+
+ match = scanner.scan(TOKEN_DOUBLE_QUOTE)
+ return nil if match.nil?
+
+ loop do
+ match = double_quote_char(scanner)
+ break if match.nil?
+
+ quoted_string << match
+ end
+
+ match = scanner.scan(TOKEN_DOUBLE_QUOTE)
+ return quoted_string if match
+
+ nil
+ end
+ private :double_quoted_string
+
+ def quoted_string(scanner)
+ single_quoted_string(scanner) || double_quoted_string(scanner)
+ end
+ private :quoted_string
+
+ def array_values(scanner)
+ values = []
+
+ loop do
+ match = quoted_string(scanner)
+ break if match.nil?
+ values << match
+
+ match = array_separator(scanner)
+ break if match.nil?
+ end
+
+ values
+ end
+ private :array_values
+end
diff --git a/metadata.json b/metadata.json
index 5d7853d..255205b 100644
--- a/metadata.json
+++ b/metadata.json
@@ -1,11 +1,11 @@
{
"name": "puppetlabs-augeas_core",
- "version": "1.0.0",
+ "version": "1.0.1",
"author": "Puppet Labs",
"summary": "Manage files using Augeas",
"license": "Apache-2.0",
"source": "https://github.com/puppetlabs/puppetlabs-augeas_core",
- "project_page": "https://puppet.com/docs/puppet/latest/types/augeas.html",
+ "project_page": "https://github.com/puppetlabs/puppetlabs-augeas_core/blob/master/README.md",
"issues_url": "https://tickets.puppetlabs.com/projects/MODULES",
"dependencies": [
@@ -57,7 +57,7 @@
"requirements": [
{
"name": "puppet",
- "version_requirement": ">= 4.7.0 < 6.0.0"
+ "version_requirement": ">= 6.0.0 < 7.0.0"
}
],
"pdk-version": "1.6.0",
diff --git a/spec/spec_helper_acceptance.rb b/spec/spec_helper_acceptance.rb
index 848f7d9..a6836f2 100644
--- a/spec/spec_helper_acceptance.rb
+++ b/spec/spec_helper_acceptance.rb
@@ -6,7 +6,7 @@ RSpec.configure do |c|
c.before :suite do
unless ENV['BEAKER_provision'] == 'no'
run_puppet_install_helper
- install_module_on(hosts_as('default'))
+ install_module_on(hosts)
install_module_dependencies_on(hosts)
end
end
diff --git a/spec/unit/provider/augeas/augeas_spec.rb b/spec/unit/provider/augeas/augeas_spec.rb
index 6166140..180f89c 100644
--- a/spec/unit/provider/augeas/augeas_spec.rb
+++ b/spec/unit/provider/augeas/augeas_spec.rb
@@ -262,6 +262,49 @@ describe Puppet::Type.type(:augeas).provider(:augeas) do
command = ['values', 'fake value', "== ['set', 'of', 'values']"]
expect(provider.process_values(command)).to eq(true)
end
+ it 'returns true for an array match with double quotes and spaces' do
+ command = ['values', 'fake value', '== [ "set" , "of" , "values" ] ']
+ expect(provider.process_values(command)).to eq(true)
+ end
+
+ it 'returns true for an array match with internally escaped single quotes' do
+ provider.aug.stubs(:match).returns(['set', "o'values", 'here'])
+ provider.aug.stubs(:get).returns('set').then.returns("o'values").then.returns('here')
+ command = ['values', 'fake value', "== [ 'set', 'o\\'values', 'here']"]
+ expect(provider.process_values(command)).to eq(true)
+ end
+
+ it 'returns true for an array match with octal character sequences' do
+ command = ['values', 'fake value', '== ["\\x73et", "of", "values"]']
+ expect(provider.process_values(command)).to eq(true)
+ end
+
+ it 'returns true for an array match with hex character sequences' do
+ command = ['values', 'fake value', '== ["\\163et", "of", "values"]']
+ expect(provider.process_values(command)).to eq(true)
+ end
+
+ it 'returns true for an array match with short unicode escape sequences' do
+ command = ['values', 'fake value', '== ["\\u0073et", "of", "values"]']
+ expect(provider.process_values(command)).to eq(true)
+ end
+
+ it 'returns true for an array match with single character long unicode escape sequences' do
+ command = ['values', 'fake value', '== ["\\u{0073}et", "of", "values"]']
+ expect(provider.process_values(command)).to eq(true)
+ end
+
+ it 'returns true for an array match with multi-character long unicode escape sequences' do
+ command = ['values', 'fake value', '== ["\\u{0073 0065 0074}", "of", "values"]']
+ expect(provider.process_values(command)).to eq(true)
+ end
+
+ it 'returns true for an array match with literal backslashes' do
+ provider.aug.stubs(:match).returns(['set', 'o\\values', 'here'])
+ provider.aug.stubs(:get).returns('set').then.returns('o\\values').then.returns('here')
+ command = ['values', 'fake value', '== [ "set", "o\\\\values", "here"]']
+ expect(provider.process_values(command)).to eq(true)
+ end
it 'returns false for an array non match' do
command = ['values', 'fake value', "== ['this', 'should', 'not', 'match']"]
@@ -277,6 +320,18 @@ describe Puppet::Type.type(:augeas).provider(:augeas) do
command = ['values', 'fake value', "!= ['this', 'should', 'not', 'match']"]
expect(provider.process_values(command)).to eq(true)
end
+
+ it 'returns true for an array non match with double quotes and spaces' do
+ command = ['values', 'fake value', '!= [ "this" , "should" ,"not", "match" ] ']
+ expect(provider.process_values(command)).to eq(true)
+ end
+
+ it 'returns true for an empty array match' do
+ provider.aug.stubs(:match).returns([])
+ provider.aug.stubs(:get)
+ command = ['values', 'fake value', '== []']
+ expect(provider.process_values(command)).to eq(true)
+ end
end
describe 'match filters' do
@@ -322,6 +377,11 @@ describe Puppet::Type.type(:augeas).provider(:augeas) do
expect(provider.process_match(command)).to eq(true)
end
+ it 'returns true for an array match with double quotes and spaces' do
+ command = ['match', 'fake value', '== [ "set" , "of" , "values" ] ']
+ expect(provider.process_match(command)).to eq(true)
+ end
+
it 'returns false for an array non match' do
command = ['match', 'fake value', "== ['this', 'should', 'not', 'match']"]
expect(provider.process_match(command)).to eq(false)
@@ -336,6 +396,11 @@ describe Puppet::Type.type(:augeas).provider(:augeas) do
command = ['match', 'fake value', "!= ['this', 'should', 'not', 'match']"]
expect(provider.process_match(command)).to eq(true)
end
+
+ it 'returns true for an array non match with double quotes and spaces' do
+ command = ['match', 'fake value', '!= [ "this" , "should" ,"not", "match" ] ']
+ expect(provider.process_match(command)).to eq(true)
+ end
end
describe 'need to run' do
diff --git a/spec/unit/puppet_x/augeas/util/parser_spec.rb b/spec/unit/puppet_x/augeas/util/parser_spec.rb
new file mode 100644
index 0000000..f8b5b2b
--- /dev/null
+++ b/spec/unit/puppet_x/augeas/util/parser_spec.rb
@@ -0,0 +1,110 @@
+require 'spec_helper'
+require 'puppet_x/augeas/util/parser'
+
+describe PuppetX::Augeas::Util::Parser do
+ include described_class
+
+ it 'handles an empty array' do
+ expect(parse_to_array('[]')).to eq([])
+ end
+
+ it 'handles an array with a simple single-quoted entry' do
+ expect(parse_to_array("['entry']")).to eq(['entry'])
+ end
+
+ it 'handles an array with a simple double-quoted entry' do
+ expect(parse_to_array('["entry"]')).to eq(['entry'])
+ end
+
+ it 'handles an array with both single- and double-quoted entries' do
+ expect(parse_to_array(%q(['first', "second"]))).to eq(['first', 'second'])
+ end
+
+ context 'inside single-quoted strings' do
+ it 'allows a literal backslash' do
+ expect(parse_to_array("['entry\\\\here']")).to eq(['entry\\here'])
+ end
+
+ it 'allows an internal single-quote' do
+ expect(parse_to_array("['entry\\'here']")).to eq(['entry\'here'])
+ end
+ end
+
+ context 'inside double-quoted strings' do
+ it 'allows a literal backslash' do
+ expect(parse_to_array('["entry\\\\here"]')).to eq(['entry\\here'])
+ end
+
+ it 'allows an internal double-quote' do
+ expect(parse_to_array('["entry\\"here"]')).to eq(['entry"here'])
+ end
+
+ it 'does not require escaping a single-quote' do
+ expect(parse_to_array('["entry\'here"]')).to eq(["entry'here"])
+ end
+
+ it 'allows a bell character escape' do
+ expect(parse_to_array('["entry\\ahere"]')).to eq(["entry\ahere"])
+ end
+
+ it 'allows a backspace character escape' do
+ expect(parse_to_array('["entry\\bhere"]')).to eq(["entry\bhere"])
+ end
+
+ it 'allows a horizontal tab character escape' do
+ expect(parse_to_array('["entry\\there"]')).to eq(["entry\there"])
+ end
+
+ it 'allows a line feed character escape' do
+ expect(parse_to_array('["entry\\nhere"]')).to eq(["entry\nhere"])
+ end
+
+ it 'allows a vertical tab character escape' do
+ expect(parse_to_array('["entry\\vhere"]')).to eq(["entry\vhere"])
+ end
+
+ it 'allows a form feed character escape' do
+ expect(parse_to_array('["entry\\fhere"]')).to eq(["entry\fhere"])
+ end
+
+ it 'allows a carriage return character escape' do
+ expect(parse_to_array('["entry\\rhere"]')).to eq(["entry\rhere"])
+ end
+
+ it 'allows an escape character escape' do
+ expect(parse_to_array('["entry\\ehere"]')).to eq(["entry\ehere"])
+ end
+
+ it 'allows a space character escape' do
+ expect(parse_to_array('["entry\\shere"]')).to eq(['entry here'])
+ end
+
+ it 'allows octal character escapes' do
+ expect(parse_to_array('["\7", "\41", "\101", "\1411"]')).to eq(["\a", '!', 'A', 'a1'])
+ end
+
+ it 'allows hexadecimal character escapes with \\x' do
+ expect(parse_to_array('["\x7", "\x21", "\x211"]')).to eq(["\a", '!', '!1'])
+ end
+
+ it 'allows single-character unicode hexadecimal character escapes with \\u' do
+ expect(parse_to_array('["\u2015", "\u20222"]')).to eq(["\u2015", "\u2022" << '2'])
+ end
+
+ it 'allows multi-character unicode hexadecimal character escapes with \\u{...}' do
+ expect(parse_to_array('["\u{7}", "\u{20}", "\u{100}", "\u{2026}", "\u{1F464}", "\u{100000}", "\u{53 74 72 69 6E 67}"]')).to eq(["\a", ' ', "\u{100}", "\u{2026}", "\u{1F464}", "\u{100000}", 'String'])
+ end
+ end
+
+ it 'fails with garbage in front of the array' do
+ expect { parse_to_array("junk ['good', 'array', 'here']") }.to raise_error(RuntimeError, %r{^Unexpected character in array at: junk \['good})
+ end
+
+ it 'fails with garbage in the middle of the array' do
+ expect { parse_to_array("['got', 'some', junk 'here']") }.to raise_error(RuntimeError, %r{^Unexpected character in array at: junk 'here'})
+ end
+
+ it 'fails with garbage after the array' do
+ expect { parse_to_array("['good', 'array', 'here'] junk after") }.to raise_error(RuntimeError, %r{^Unexpected character in array at: junk after})
+ end
+end