aws-codedeploy-agent 0.0.2 → 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- data/aws-codedeploy-agent.gemspec +5 -5
- data/certs/host-agent-deployment-signer-ca-chain.pem +30 -0
- data/conf/codedeployagent.yml +0 -1
- data/lib/instance_agent.rb +1 -13
- data/lib/instance_agent/agent/base.rb +38 -12
- data/lib/instance_agent/agent/plugin.rb +21 -0
- data/lib/instance_agent/config.rb +2 -1
- data/lib/instance_agent/platform/linux_util.rb +4 -0
- data/lib/instance_agent/plugins/codedeploy/application_specification/ace_info.rb +133 -0
- data/lib/instance_agent/plugins/codedeploy/application_specification/acl_info.rb +163 -0
- data/lib/instance_agent/plugins/codedeploy/application_specification/application_specification.rb +143 -0
- data/lib/instance_agent/plugins/codedeploy/application_specification/context_info.rb +23 -0
- data/lib/instance_agent/plugins/codedeploy/application_specification/file_info.rb +23 -0
- data/lib/instance_agent/plugins/codedeploy/application_specification/linux_permission_info.rb +121 -0
- data/lib/instance_agent/plugins/codedeploy/application_specification/mode_info.rb +66 -0
- data/lib/instance_agent/plugins/codedeploy/application_specification/range_info.rb +134 -0
- data/lib/instance_agent/plugins/codedeploy/application_specification/script_info.rb +27 -0
- data/lib/instance_agent/plugins/codedeploy/codedeploy_control.rb +100 -0
- data/lib/instance_agent/plugins/codedeploy/command_executor.rb +359 -0
- data/lib/instance_agent/plugins/codedeploy/command_poller.rb +178 -0
- data/lib/instance_agent/plugins/codedeploy/deployment_specification.rb +161 -0
- data/lib/instance_agent/plugins/codedeploy/hook_executor.rb +226 -0
- data/lib/instance_agent/plugins/codedeploy/install_instruction.rb +389 -0
- data/lib/instance_agent/plugins/codedeploy/installer.rb +147 -0
- data/lib/instance_agent/plugins/codedeploy/onpremise_config.rb +42 -0
- data/lib/instance_agent/plugins/codedeploy/register_plugin.rb +17 -0
- data/lib/instance_agent/runner/child.rb +20 -5
- data/lib/instance_agent/runner/master.rb +2 -15
- data/lib/instance_metadata.rb +2 -2
- data/test/certificate_helper.rb +1 -1
- data/test/helpers/instance_agent_helper.rb +1 -0
- data/test/instance_agent/agent/base_test.rb +16 -3
- data/test/instance_agent/config_test.rb +2 -1
- data/test/instance_agent/plugins/codedeploy/application_specification_test.rb +1713 -0
- data/test/instance_agent/{codedeploy_plugin → plugins/codedeploy}/codedeploy_control_test.rb +1 -1
- data/test/instance_agent/{codedeploy_plugin → plugins/codedeploy}/command_executor_test.rb +32 -9
- data/test/instance_agent/{codedeploy_plugin → plugins/codedeploy}/command_poller_test.rb +13 -14
- data/test/instance_agent/{codedeploy_plugin → plugins/codedeploy}/deployment_specification_test.rb +98 -25
- data/test/instance_agent/{codedeploy_plugin → plugins/codedeploy}/hook_executor_test.rb +83 -15
- data/test/instance_agent/plugins/codedeploy/install_instruction_test.rb +568 -0
- data/test/instance_agent/{codedeploy_plugin → plugins/codedeploy}/installer_test.rb +12 -9
- data/test/instance_agent/plugins/codedeploy/onpremise_config_test.rb +72 -0
- data/test/instance_agent/runner/child_test.rb +1 -1
- data/vendor/gems/.codedeploy-commands-1.0.0.created.rid +1 -1
- data/vendor/gems/codedeploy-commands/lib/aws/plugins/deploy_control_endpoint.rb +4 -0
- data/vendor/gems/jmespath-1.0.1/lib/jmespath.rb +41 -0
- data/vendor/gems/jmespath-1.0.1/lib/jmespath/caching_parser.rb +30 -0
- data/vendor/gems/jmespath-1.0.1/lib/jmespath/errors.rb +17 -0
- data/vendor/gems/jmespath-1.0.1/lib/jmespath/expr_node.rb +15 -0
- data/vendor/gems/jmespath-1.0.1/lib/jmespath/lexer.rb +116 -0
- data/vendor/gems/jmespath-1.0.1/lib/jmespath/parser.rb +347 -0
- data/vendor/gems/jmespath-1.0.1/lib/jmespath/runtime.rb +71 -0
- data/vendor/gems/jmespath-1.0.1/lib/jmespath/token.rb +41 -0
- data/vendor/gems/jmespath-1.0.1/lib/jmespath/token_stream.rb +60 -0
- data/vendor/gems/jmespath-1.0.1/lib/jmespath/tree_interpreter.rb +523 -0
- data/vendor/gems/jmespath-1.0.1/lib/jmespath/version.rb +3 -0
- data/vendor/gems/process_manager/lib/process_manager/master.rb +16 -5
- data/vendor/specifications/{aws-sdk-core-2.0.5.gemspec → aws-sdk-core-2.0.42.gemspec} +9 -11
- data/vendor/specifications/builder-3.2.2.gemspec +1 -1
- data/vendor/specifications/codedeploy-commands-1.0.0.gemspec +7 -6
- data/vendor/specifications/gli-2.5.6.gemspec +1 -1
- data/vendor/specifications/jmespath-1.0.1.gemspec +29 -0
- data/vendor/specifications/little-plugger-1.1.3.gemspec +1 -1
- data/vendor/specifications/logging-1.8.1.gemspec +1 -1
- data/vendor/specifications/multi_json-1.7.7.gemspec +1 -1
- data/vendor/specifications/multi_json-1.8.4.gemspec +1 -1
- data/vendor/specifications/multi_xml-0.5.5.gemspec +1 -1
- data/vendor/specifications/process_manager-0.0.13.gemspec +1 -1
- data/vendor/specifications/simple_pid-0.2.1.gemspec +1 -1
- metadata +76 -63
- data/lib/instance_agent/codedeploy_plugin/application_specification/ace_info.rb +0 -133
- data/lib/instance_agent/codedeploy_plugin/application_specification/acl_info.rb +0 -163
- data/lib/instance_agent/codedeploy_plugin/application_specification/application_specification.rb +0 -142
- data/lib/instance_agent/codedeploy_plugin/application_specification/context_info.rb +0 -23
- data/lib/instance_agent/codedeploy_plugin/application_specification/file_info.rb +0 -23
- data/lib/instance_agent/codedeploy_plugin/application_specification/linux_permission_info.rb +0 -121
- data/lib/instance_agent/codedeploy_plugin/application_specification/mode_info.rb +0 -66
- data/lib/instance_agent/codedeploy_plugin/application_specification/range_info.rb +0 -134
- data/lib/instance_agent/codedeploy_plugin/application_specification/script_info.rb +0 -27
- data/lib/instance_agent/codedeploy_plugin/codedeploy_control.rb +0 -72
- data/lib/instance_agent/codedeploy_plugin/command_executor.rb +0 -357
- data/lib/instance_agent/codedeploy_plugin/command_poller.rb +0 -170
- data/lib/instance_agent/codedeploy_plugin/deployment_specification.rb +0 -150
- data/lib/instance_agent/codedeploy_plugin/hook_executor.rb +0 -206
- data/lib/instance_agent/codedeploy_plugin/install_instruction.rb +0 -374
- data/lib/instance_agent/codedeploy_plugin/installer.rb +0 -143
- data/lib/instance_agent/codedeploy_plugin/request_helper.rb +0 -28
- data/test/instance_agent/codedeploy_plugin/application_specification_test.rb +0 -1710
- data/test/instance_agent/codedeploy_plugin/install_instruction_test.rb +0 -566
- data/test/instance_agent/codedeploy_plugin/request_helper_test.rb +0 -37
- data/vendor/specifications/jamespath-0.5.1.gemspec +0 -35
@@ -1,9 +1,8 @@
|
|
1
1
|
require 'test_helper'
|
2
|
-
require 'instance_agent/codedeploy_plugin/installer'
|
3
2
|
|
4
3
|
class CodeDeployPluginInstallerTest < InstanceAgentTestCase
|
5
4
|
|
6
|
-
include InstanceAgent::CodeDeployPlugin
|
5
|
+
include InstanceAgent::Plugins::CodeDeployPlugin
|
7
6
|
|
8
7
|
context "The CodeDeploy Plugin's file installer" do
|
9
8
|
|
@@ -49,8 +48,9 @@ class CodeDeployPluginInstallerTest < InstanceAgentTestCase
|
|
49
48
|
InstallInstruction
|
50
49
|
.stubs(:generate_instructions)
|
51
50
|
.yields(@instruction_builder)
|
52
|
-
.returns(
|
51
|
+
.returns(@instruction_builder)
|
53
52
|
|
53
|
+
@instruction_builder.stubs(:cleanup).returns(nil)
|
54
54
|
File.stubs(:exists?).returns(false)
|
55
55
|
File.stubs(:exists?).with("deploy-instructions-dir/ig1-cleanup").returns(false)
|
56
56
|
|
@@ -88,7 +88,7 @@ class CodeDeployPluginInstallerTest < InstanceAgentTestCase
|
|
88
88
|
|
89
89
|
InstallInstruction
|
90
90
|
.expects(:generate_instructions)
|
91
|
-
.returns(
|
91
|
+
.returns(CommandBuilder.new)
|
92
92
|
.in_sequence(call_sequence)
|
93
93
|
|
94
94
|
@installer.install(@deployment_group_id, @app_spec)
|
@@ -366,13 +366,13 @@ class CodeDeployPluginInstallerTest < InstanceAgentTestCase
|
|
366
366
|
|
367
367
|
context "with permissions" do
|
368
368
|
setup do
|
369
|
-
@permissions = [InstanceAgent::CodeDeployPlugin::ApplicationSpecification::LinuxPermissionInfo.new("dst1/src1",
|
369
|
+
@permissions = [InstanceAgent::Plugins::CodeDeployPlugin::ApplicationSpecification::LinuxPermissionInfo.new("dst1/src1",
|
370
370
|
{:type => ["file"]}),
|
371
|
-
InstanceAgent::CodeDeployPlugin::ApplicationSpecification::LinuxPermissionInfo.new("/",
|
371
|
+
InstanceAgent::Plugins::CodeDeployPlugin::ApplicationSpecification::LinuxPermissionInfo.new("/",
|
372
372
|
{:type => ["directory"],
|
373
373
|
:pattern => "dst*",
|
374
374
|
:except => []}),
|
375
|
-
InstanceAgent::CodeDeployPlugin::ApplicationSpecification::LinuxPermissionInfo.new("dst3/",
|
375
|
+
InstanceAgent::Plugins::CodeDeployPlugin::ApplicationSpecification::LinuxPermissionInfo.new("dst3/",
|
376
376
|
{:type => ["file","directory"],
|
377
377
|
:pattern => "file*",
|
378
378
|
:except => [*2]})]
|
@@ -472,8 +472,11 @@ class CodeDeployPluginInstallerTest < InstanceAgentTestCase
|
|
472
472
|
setup do
|
473
473
|
@command_list = [stub("a command", :execute => nil),
|
474
474
|
stub("a second command", :execute => nil)]
|
475
|
-
|
476
|
-
|
475
|
+
commands = mock("command builder")
|
476
|
+
commands.stubs(:command_array).returns(@command_list)
|
477
|
+
commands.stubs(:cleanup)
|
478
|
+
InstallInstruction.stubs(:generate_instructions).returns(commands)
|
479
|
+
commands.stubs(:to_json).returns("INSTALL!")
|
477
480
|
end
|
478
481
|
|
479
482
|
should "write them to disk" do
|
@@ -0,0 +1,72 @@
|
|
1
|
+
class OnPremiseConfigTest < InstanceAgentTestCase
|
2
|
+
|
3
|
+
include InstanceAgent::Plugins::CodeDeployPlugin
|
4
|
+
|
5
|
+
linux_path = '/etc/codedeploy-agent/conf/codedeploy.onpremises.yml'
|
6
|
+
|
7
|
+
context "Config file doesn't exist" do
|
8
|
+
setup do
|
9
|
+
File.stubs(:exists?).with(linux_path).returns(false)
|
10
|
+
end
|
11
|
+
|
12
|
+
should "do nothing" do
|
13
|
+
OnPremisesConfig.configure
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
context "Linux config file exists" do
|
18
|
+
setup do
|
19
|
+
File.stubs(:exists?).with(linux_path).returns(true)
|
20
|
+
end
|
21
|
+
|
22
|
+
context "Linux file is not readable" do
|
23
|
+
setup do
|
24
|
+
File.stubs(:readable?).with(linux_path).returns(false)
|
25
|
+
end
|
26
|
+
|
27
|
+
should "do nothing" do
|
28
|
+
OnPremisesConfig.configure
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
context "Linux file is valid" do
|
33
|
+
|
34
|
+
linux_file = <<-END
|
35
|
+
region: us-east-test
|
36
|
+
aws_access_key_id: linuxkey
|
37
|
+
aws_secret_access_key: linuxsecretkey
|
38
|
+
iam_user_arn: test:arn
|
39
|
+
END
|
40
|
+
|
41
|
+
setup do
|
42
|
+
File.stubs(:readable?).with(linux_path).returns(true)
|
43
|
+
File.stubs(:read).with(linux_path).returns(linux_file)
|
44
|
+
end
|
45
|
+
|
46
|
+
should "set the ENV variables correctly" do
|
47
|
+
OnPremisesConfig.configure
|
48
|
+
assert_equal 'us-east-test', ENV['AWS_REGION']
|
49
|
+
assert_equal 'linuxkey', ENV['AWS_ACCESS_KEY']
|
50
|
+
assert_equal 'linuxsecretkey', ENV['AWS_SECRET_KEY']
|
51
|
+
assert_equal 'test:arn', ENV['AWS_HOST_IDENTIFIER']
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
context "config file with invalid yaml" do
|
56
|
+
linux_file = <<-END
|
57
|
+
invalid yaml content
|
58
|
+
END
|
59
|
+
|
60
|
+
setup do
|
61
|
+
File.stubs(:readable?).with(linux_path).returns(true)
|
62
|
+
File.stubs(:read).with(linux_path).returns(linux_file)
|
63
|
+
end
|
64
|
+
|
65
|
+
should "raise an exception" do
|
66
|
+
assert_raise do
|
67
|
+
OnPremisesConfig.configure
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -5,7 +5,7 @@ class RunnerChildTest < InstanceAgentTestCase
|
|
5
5
|
setup do
|
6
6
|
@dir = '/tmp'
|
7
7
|
@agent = mock()
|
8
|
-
InstanceAgent::CodeDeployPlugin::CommandPoller.stubs(:new).returns(@agent)
|
8
|
+
InstanceAgent::Plugins::CodeDeployPlugin::CommandPoller.stubs(:new).returns(@agent)
|
9
9
|
@agent.stubs(:description).returns("CommandPoller")
|
10
10
|
InstanceAgent::Runner::Child.any_instance.stubs(:trap_signals)
|
11
11
|
@child = InstanceAgent::Runner::Child.new(0, 777)
|
@@ -1 +1 @@
|
|
1
|
-
Tue,
|
1
|
+
Tue, 30 Jun 2015 13:02:54 -0700
|
@@ -11,6 +11,10 @@ module Aws
|
|
11
11
|
url = "https://codedeploy-commands.us-east-1.amazonaws.com"
|
12
12
|
when "us-west-2"
|
13
13
|
url = "https://codedeploy-commands.us-west-2.amazonaws.com"
|
14
|
+
when "eu-west-1"
|
15
|
+
url = "https://codedeploy-commands.eu-west-1.amazonaws.com"
|
16
|
+
when "ap-southeast-2"
|
17
|
+
url = "https://codedeploy-commands.ap-southeast-2.amazonaws.com"
|
14
18
|
else
|
15
19
|
raise "Not able to find an endpoint. Unknown region."
|
16
20
|
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'multi_json'
|
2
|
+
require 'pathname'
|
3
|
+
|
4
|
+
module JMESPath
|
5
|
+
|
6
|
+
autoload :CachingParser, 'jmespath/caching_parser'
|
7
|
+
autoload :Errors, 'jmespath/errors'
|
8
|
+
autoload :ExprNode, 'jmespath/expr_node'
|
9
|
+
autoload :Lexer, 'jmespath/lexer'
|
10
|
+
autoload :Parser, 'jmespath/parser'
|
11
|
+
autoload :Runtime, 'jmespath/runtime'
|
12
|
+
autoload :Token, 'jmespath/token'
|
13
|
+
autoload :TokenStream, 'jmespath/token_stream'
|
14
|
+
autoload :TreeInterpreter, 'jmespath/tree_interpreter'
|
15
|
+
autoload :VERSION, 'jmespath/version'
|
16
|
+
|
17
|
+
class << self
|
18
|
+
|
19
|
+
|
20
|
+
# @param [String] expression A valid
|
21
|
+
# [JMESPath](https://github.com/boto/jmespath) expression.
|
22
|
+
# @param [Hash] data
|
23
|
+
# @return [Mixed,nil] Returns the matched values. Returns `nil` if the
|
24
|
+
# expression does not resolve inside `data`.
|
25
|
+
def search(expression, data)
|
26
|
+
data = case data
|
27
|
+
when Hash, Struct then data # check for most common case first
|
28
|
+
when Pathname then load_json(data)
|
29
|
+
when IO, StringIO then MultiJson.load(data.read)
|
30
|
+
else data
|
31
|
+
end
|
32
|
+
Runtime.new.search(expression, data)
|
33
|
+
end
|
34
|
+
|
35
|
+
# @api private
|
36
|
+
def load_json(path)
|
37
|
+
MultiJson.load(File.open(path, 'r', encoding: 'UTF-8') { |f| f.read })
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'thread'
|
2
|
+
|
3
|
+
module JMESPath
|
4
|
+
class CachingParser
|
5
|
+
|
6
|
+
def initialize(options = {})
|
7
|
+
@parser = options[:parser] || Parser.new(options)
|
8
|
+
@mutex = Mutex.new
|
9
|
+
@cache = {}
|
10
|
+
end
|
11
|
+
|
12
|
+
def parse(expression)
|
13
|
+
if cached = @cache[expression]
|
14
|
+
cached
|
15
|
+
else
|
16
|
+
cache_expression(expression)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def cache_expression(expression)
|
23
|
+
@mutex.synchronize do
|
24
|
+
@cache.clear if @cache.size > 1000
|
25
|
+
@cache[expression] = @parser.parse(expression)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module JMESPath
|
2
|
+
module Errors
|
3
|
+
|
4
|
+
class Error < StandardError; end
|
5
|
+
|
6
|
+
class RuntimeError < Error; end
|
7
|
+
|
8
|
+
class SyntaxError < Error; end
|
9
|
+
|
10
|
+
class InvalidTypeError < Error; end
|
11
|
+
|
12
|
+
class InvalidArityError < Error; end
|
13
|
+
|
14
|
+
class UnknownFunctionError < Error; end
|
15
|
+
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,116 @@
|
|
1
|
+
module JMESPath
|
2
|
+
# @api private
|
3
|
+
class Lexer
|
4
|
+
|
5
|
+
# @api private
|
6
|
+
TOKEN_PATTERNS = {}
|
7
|
+
|
8
|
+
# @api private
|
9
|
+
TOKEN_TYPES = {}
|
10
|
+
|
11
|
+
{
|
12
|
+
'[a-zA-Z_][a-zA-Z_0-9]*' => :identifier,
|
13
|
+
'\.' => :dot,
|
14
|
+
'\*' => :star,
|
15
|
+
'\[\]' => :flatten,
|
16
|
+
'-?\d+' => :number,
|
17
|
+
'\|\|' => :or,
|
18
|
+
'\|' => :pipe,
|
19
|
+
'\[\?' => :filter,
|
20
|
+
'\[' => :lbracket,
|
21
|
+
'\]' => :rbracket,
|
22
|
+
'"(?:\\\\\\\\|\\\\"|[^"])*"' => :quoted_identifier,
|
23
|
+
'`(?:\\\\\\\\|\\\\`|[^`])*`' => :literal,
|
24
|
+
',' => :comma,
|
25
|
+
':' => :colon,
|
26
|
+
'@' => :current,
|
27
|
+
'&' => :expref,
|
28
|
+
'\(' => :lparen,
|
29
|
+
'\)' => :rparen,
|
30
|
+
'\{' => :lbrace,
|
31
|
+
'\}' => :rbrace,
|
32
|
+
'!=' => :comparator,
|
33
|
+
'==' => :comparator,
|
34
|
+
'<=' => :comparator,
|
35
|
+
'>=' => :comparator,
|
36
|
+
'<' => :comparator,
|
37
|
+
'>' => :comparator,
|
38
|
+
'[ \t]' => :skip,
|
39
|
+
}.each.with_index do |(pattern, type), n|
|
40
|
+
TOKEN_PATTERNS[n] = pattern
|
41
|
+
TOKEN_TYPES[n] = type
|
42
|
+
end
|
43
|
+
|
44
|
+
# @api private
|
45
|
+
TOKEN_REGEX = /(#{TOKEN_PATTERNS.values.join(')|(')})/
|
46
|
+
|
47
|
+
# @api private
|
48
|
+
JSON_VALUE = /^[\["{]/
|
49
|
+
|
50
|
+
# @api private
|
51
|
+
JSON_NUMBER = /^\-?[0-9]*(\.[0-9]+)?([e|E][+|\-][0-9]+)?$/
|
52
|
+
|
53
|
+
# @param [String<JMESPath>] expression
|
54
|
+
# @return [Array<Hash>]
|
55
|
+
def tokenize(expression)
|
56
|
+
offset = 0
|
57
|
+
tokens = []
|
58
|
+
expression.scan(TOKEN_REGEX).each do |match|
|
59
|
+
match_index = match.find_index { |token| !token.nil? }
|
60
|
+
match_value = match[match_index]
|
61
|
+
type = TOKEN_TYPES[match_index]
|
62
|
+
token = Token.new(type, match_value, offset)
|
63
|
+
if token.type != :skip
|
64
|
+
case token.type
|
65
|
+
when :number then token_number(token, expression, offset)
|
66
|
+
when :literal then token_literal(token, expression, offset)
|
67
|
+
when :quoted_identifier
|
68
|
+
token_quoted_identifier(token, expression, offset)
|
69
|
+
end
|
70
|
+
tokens << token
|
71
|
+
end
|
72
|
+
offset += match_value.size
|
73
|
+
end
|
74
|
+
tokens << Token.new(:eof, nil, offset)
|
75
|
+
unless expression.size == offset
|
76
|
+
syntax_error('invalid expression', expression, offset)
|
77
|
+
end
|
78
|
+
tokens
|
79
|
+
end
|
80
|
+
|
81
|
+
private
|
82
|
+
|
83
|
+
def token_number(token, expression, offset)
|
84
|
+
token[:value] = token[:value].to_i
|
85
|
+
end
|
86
|
+
|
87
|
+
def token_literal(token, expression, offset)
|
88
|
+
token[:value] = token[:value][1..-2].lstrip.gsub('\`', '`')
|
89
|
+
token[:value] =
|
90
|
+
case token[:value]
|
91
|
+
when 'true', 'false' then token[:value] == 'true'
|
92
|
+
when 'null' then nil
|
93
|
+
when '' then syntax_error("empty json literal", expression, offset)
|
94
|
+
when JSON_VALUE then decode_json(token[:value], expression, offset)
|
95
|
+
when JSON_NUMBER then decode_json(token[:value], expression, offset)
|
96
|
+
else decode_json('"' + token[:value] + '"', expression, offset)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def token_quoted_identifier(token, expression, offset)
|
101
|
+
token[:value] = decode_json(token[:value], expression, offset)
|
102
|
+
end
|
103
|
+
|
104
|
+
def decode_json(json, expression, offset)
|
105
|
+
MultiJson.load(json)
|
106
|
+
rescue MultiJson::ParseError => e
|
107
|
+
syntax_error(e.message, expression, offset)
|
108
|
+
end
|
109
|
+
|
110
|
+
def syntax_error(message, expression, offset)
|
111
|
+
msg = message + "in #{expression.inspect} at #{offset}"
|
112
|
+
raise Errors::SyntaxError.new(msg)
|
113
|
+
end
|
114
|
+
|
115
|
+
end
|
116
|
+
end
|
@@ -0,0 +1,347 @@
|
|
1
|
+
require 'set'
|
2
|
+
|
3
|
+
module JMESPath
|
4
|
+
# @api private
|
5
|
+
class Parser
|
6
|
+
|
7
|
+
# @api private
|
8
|
+
AFTER_DOT = Set.new([
|
9
|
+
:identifier, # foo.bar
|
10
|
+
:quoted_identifier, # foo."bar"
|
11
|
+
:star, # foo.*
|
12
|
+
:lbrace, # foo[1]
|
13
|
+
:lbracket, # foo{a: 0}
|
14
|
+
:function, # foo.*.to_string(@)
|
15
|
+
:filter, # foo.[?bar==10]
|
16
|
+
])
|
17
|
+
|
18
|
+
CURRENT_NODE = { type: :current }
|
19
|
+
|
20
|
+
# @option options [Lexer] :lexer
|
21
|
+
def initialize(options = {})
|
22
|
+
@lexer = options[:lexer] || Lexer.new()
|
23
|
+
end
|
24
|
+
|
25
|
+
# @param [String<JMESPath>] expression
|
26
|
+
def parse(expression)
|
27
|
+
stream = TokenStream.new(expression, @lexer.tokenize(expression))
|
28
|
+
result = expr(stream)
|
29
|
+
if stream.token.type != :eof
|
30
|
+
raise Errors::SyntaxError, "expected :eof got #{stream.token.type}"
|
31
|
+
else
|
32
|
+
result
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
# @api private
|
37
|
+
def method_missing(method_name, *args)
|
38
|
+
if matches = method_name.match(/^(nud_|led_)(.*)/)
|
39
|
+
raise Errors::SyntaxError, "unexpected token #{matches[2]}"
|
40
|
+
else
|
41
|
+
super
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
# @param [TokenStream] stream
|
48
|
+
# @param [Integer] rbp Right binding power
|
49
|
+
def expr(stream, rbp = 0)
|
50
|
+
left = send("nud_#{stream.token.type}", stream)
|
51
|
+
while rbp < stream.token.binding_power
|
52
|
+
left = send("led_#{stream.token.type}", stream, left)
|
53
|
+
end
|
54
|
+
left
|
55
|
+
end
|
56
|
+
|
57
|
+
def nud_current(stream)
|
58
|
+
stream.next
|
59
|
+
CURRENT_NODE
|
60
|
+
end
|
61
|
+
|
62
|
+
def nud_expref(stream)
|
63
|
+
stream.next
|
64
|
+
{
|
65
|
+
type: :expression,
|
66
|
+
children: [expr(stream, 2)]
|
67
|
+
}
|
68
|
+
end
|
69
|
+
|
70
|
+
def nud_filter(stream)
|
71
|
+
led_filter(stream, CURRENT_NODE)
|
72
|
+
end
|
73
|
+
|
74
|
+
def nud_flatten(stream)
|
75
|
+
led_flatten(stream, CURRENT_NODE)
|
76
|
+
end
|
77
|
+
|
78
|
+
def nud_identifier(stream)
|
79
|
+
token = stream.token
|
80
|
+
stream.next
|
81
|
+
{ type: :field, key: token.value }
|
82
|
+
end
|
83
|
+
|
84
|
+
def nud_lbrace(stream)
|
85
|
+
valid_keys = Set.new([:quoted_identifier, :identifier])
|
86
|
+
stream.next(match:valid_keys)
|
87
|
+
pairs = []
|
88
|
+
begin
|
89
|
+
pairs << parse_key_value_pair(stream)
|
90
|
+
if stream.token.type == :comma
|
91
|
+
stream.next(match:valid_keys)
|
92
|
+
end
|
93
|
+
end while stream.token.type != :rbrace
|
94
|
+
stream.next
|
95
|
+
{
|
96
|
+
type: :multi_select_hash,
|
97
|
+
children: pairs
|
98
|
+
}
|
99
|
+
end
|
100
|
+
|
101
|
+
def nud_lbracket(stream)
|
102
|
+
stream.next
|
103
|
+
type = stream.token.type
|
104
|
+
if type == :number || type == :colon
|
105
|
+
parse_array_index_expression(stream)
|
106
|
+
elsif type == :star && stream.lookahead(1).type == :rbracket
|
107
|
+
parse_wildcard_array(stream)
|
108
|
+
else
|
109
|
+
parse_multi_select_list(stream)
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
def nud_literal(stream)
|
114
|
+
value = stream.token.value
|
115
|
+
stream.next
|
116
|
+
{
|
117
|
+
type: :literal,
|
118
|
+
value: value
|
119
|
+
}
|
120
|
+
end
|
121
|
+
|
122
|
+
def nud_quoted_identifier(stream)
|
123
|
+
token = stream.token
|
124
|
+
next_token = stream.next
|
125
|
+
if next_token.type == :lparen
|
126
|
+
msg = 'quoted identifiers are not allowed for function names'
|
127
|
+
raise Errors::SyntaxError, msg
|
128
|
+
else
|
129
|
+
{ type: :field, key: token[:value] }
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
def nud_star(stream)
|
134
|
+
parse_wildcard_object(stream, CURRENT_NODE)
|
135
|
+
end
|
136
|
+
|
137
|
+
def led_comparator(stream, left)
|
138
|
+
token = stream.token
|
139
|
+
stream.next
|
140
|
+
{
|
141
|
+
type: :comparator,
|
142
|
+
relation: token.value,
|
143
|
+
children: [
|
144
|
+
left,
|
145
|
+
expr(stream),
|
146
|
+
]
|
147
|
+
}
|
148
|
+
end
|
149
|
+
|
150
|
+
def led_dot(stream, left)
|
151
|
+
stream.next(match:AFTER_DOT)
|
152
|
+
if stream.token.type == :star
|
153
|
+
parse_wildcard_object(stream, left)
|
154
|
+
else
|
155
|
+
{
|
156
|
+
type: :subexpression,
|
157
|
+
children: [
|
158
|
+
left,
|
159
|
+
parse_dot(stream, Token::BINDING_POWER[:dot])
|
160
|
+
]
|
161
|
+
}
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
def led_filter(stream, left)
|
166
|
+
stream.next
|
167
|
+
expression = expr(stream)
|
168
|
+
if stream.token.type != :rbracket
|
169
|
+
raise Errors::SyntaxError, 'expected a closing rbracket for the filter'
|
170
|
+
end
|
171
|
+
stream.next
|
172
|
+
rhs = parse_projection(stream, Token::BINDING_POWER[:filter])
|
173
|
+
{
|
174
|
+
type: :projection,
|
175
|
+
from: :array,
|
176
|
+
children: [
|
177
|
+
left ? left : CURRENT_NODE,
|
178
|
+
{
|
179
|
+
type: :condition,
|
180
|
+
children: [expression, rhs],
|
181
|
+
}
|
182
|
+
]
|
183
|
+
}
|
184
|
+
end
|
185
|
+
|
186
|
+
def led_flatten(stream, left)
|
187
|
+
stream.next
|
188
|
+
{
|
189
|
+
type: :projection,
|
190
|
+
from: :array,
|
191
|
+
children: [
|
192
|
+
{ type: :flatten, children: [left] },
|
193
|
+
parse_projection(stream, Token::BINDING_POWER[:flatten])
|
194
|
+
]
|
195
|
+
}
|
196
|
+
end
|
197
|
+
|
198
|
+
def led_lbracket(stream, left)
|
199
|
+
stream.next(match: Set.new([:number, :colon, :star]))
|
200
|
+
type = stream.token.type
|
201
|
+
if type == :number || type == :colon
|
202
|
+
{
|
203
|
+
type: :subexpression,
|
204
|
+
children: [
|
205
|
+
left,
|
206
|
+
parse_array_index_expression(stream)
|
207
|
+
]
|
208
|
+
}
|
209
|
+
else
|
210
|
+
parse_wildcard_array(stream, left)
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
def led_lparen(stream, left)
|
215
|
+
args = []
|
216
|
+
name = left[:key]
|
217
|
+
stream.next
|
218
|
+
while stream.token.type != :rparen
|
219
|
+
args << expr(stream, 0)
|
220
|
+
if stream.token.type == :comma
|
221
|
+
stream.next
|
222
|
+
end
|
223
|
+
end
|
224
|
+
stream.next
|
225
|
+
{
|
226
|
+
type: :function,
|
227
|
+
fn: name,
|
228
|
+
children: args,
|
229
|
+
}
|
230
|
+
end
|
231
|
+
|
232
|
+
def led_or(stream, left)
|
233
|
+
stream.next
|
234
|
+
{
|
235
|
+
type: :or,
|
236
|
+
children: [left, expr(stream, Token::BINDING_POWER[:or])]
|
237
|
+
}
|
238
|
+
end
|
239
|
+
|
240
|
+
def led_pipe(stream, left)
|
241
|
+
stream.next
|
242
|
+
{
|
243
|
+
type: :pipe,
|
244
|
+
children: [left, expr(stream, Token::BINDING_POWER[:pipe])],
|
245
|
+
}
|
246
|
+
end
|
247
|
+
|
248
|
+
def parse_array_index_expression(stream)
|
249
|
+
pos = 0
|
250
|
+
parts = [nil, nil, nil]
|
251
|
+
begin
|
252
|
+
if stream.token.type == :colon
|
253
|
+
pos += 1
|
254
|
+
else
|
255
|
+
parts[pos] = stream.token.value
|
256
|
+
end
|
257
|
+
stream.next(match:Set.new([:number, :colon, :rbracket]))
|
258
|
+
end while stream.token.type != :rbracket
|
259
|
+
stream.next
|
260
|
+
if pos == 0
|
261
|
+
{ type: :index, index: parts[0] }
|
262
|
+
elsif pos > 2
|
263
|
+
raise Errors::SyntaxError, 'invalid array slice syntax: too many colons'
|
264
|
+
else
|
265
|
+
{ type: :slice, args: parts }
|
266
|
+
end
|
267
|
+
end
|
268
|
+
|
269
|
+
def parse_dot(stream, binding_power)
|
270
|
+
if stream.token.type == :lbracket
|
271
|
+
stream.next
|
272
|
+
parse_multi_select_list(stream)
|
273
|
+
else
|
274
|
+
expr(stream, binding_power)
|
275
|
+
end
|
276
|
+
end
|
277
|
+
|
278
|
+
def parse_key_value_pair(stream)
|
279
|
+
key = stream.token.value
|
280
|
+
stream.next(match:Set.new([:colon]))
|
281
|
+
stream.next
|
282
|
+
{
|
283
|
+
type: :key_value_pair,
|
284
|
+
key: key,
|
285
|
+
children: [expr(stream)]
|
286
|
+
}
|
287
|
+
end
|
288
|
+
|
289
|
+
def parse_multi_select_list(stream)
|
290
|
+
nodes = []
|
291
|
+
begin
|
292
|
+
nodes << expr(stream)
|
293
|
+
if stream.token.type == :comma
|
294
|
+
stream.next
|
295
|
+
if stream.token.type == :rbracket
|
296
|
+
raise Errors::SyntaxError, 'expression epxected, found rbracket'
|
297
|
+
end
|
298
|
+
end
|
299
|
+
end while stream.token.type != :rbracket
|
300
|
+
stream.next
|
301
|
+
{
|
302
|
+
type: :multi_select_list,
|
303
|
+
children: nodes
|
304
|
+
}
|
305
|
+
end
|
306
|
+
|
307
|
+
def parse_projection(stream, binding_power)
|
308
|
+
type = stream.token.type
|
309
|
+
if stream.token.binding_power < 10
|
310
|
+
CURRENT_NODE
|
311
|
+
elsif type == :dot
|
312
|
+
stream.next(match:AFTER_DOT)
|
313
|
+
parse_dot(stream, binding_power)
|
314
|
+
elsif type == :lbracket || type == :filter
|
315
|
+
expr(stream, binding_power)
|
316
|
+
else
|
317
|
+
raise Errors::SyntaxError, 'syntax error after projection'
|
318
|
+
end
|
319
|
+
end
|
320
|
+
|
321
|
+
def parse_wildcard_array(stream, left = nil)
|
322
|
+
stream.next(match:Set.new([:rbracket]))
|
323
|
+
stream.next
|
324
|
+
{
|
325
|
+
type: :projection,
|
326
|
+
from: :array,
|
327
|
+
children: [
|
328
|
+
left ? left : CURRENT_NODE,
|
329
|
+
parse_projection(stream, Token::BINDING_POWER[:star])
|
330
|
+
]
|
331
|
+
}
|
332
|
+
end
|
333
|
+
|
334
|
+
def parse_wildcard_object(stream, left = nil)
|
335
|
+
stream.next
|
336
|
+
{
|
337
|
+
type: :projection,
|
338
|
+
from: :object,
|
339
|
+
children: [
|
340
|
+
left ? left : CURRENT_NODE,
|
341
|
+
parse_projection(stream, Token::BINDING_POWER[:star])
|
342
|
+
]
|
343
|
+
}
|
344
|
+
end
|
345
|
+
|
346
|
+
end
|
347
|
+
end
|