aws-codedeploy-agent 0.0.2 → 0.0.3
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/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
|