summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authormo khan <mo@mokhan.ca>2021-05-20 13:13:36 -0600
committermo khan <mo@mokhan.ca>2021-05-20 13:13:36 -0600
commit9a7b1c1db7903fb9adeb8b2d147ec1d6d79d3505 (patch)
treeb87ead706693d57650c0501239ee9593041ac9b2
parent2b5437f4cf12d48386d9c8ac63343598ffdad2c2 (diff)
feat: parse multiple provider blocks
-rw-r--r--lib/spandx/terraform/parsers/hcl.rb31
-rw-r--r--spec/fixtures/terraform/multiple_providers/.terraform.lock.hcl40
-rw-r--r--spec/fixtures/terraform/multiple_providers/main.tf12
-rw-r--r--spec/unit/terraform/parsers/hcl_spec.rb200
4 files changed, 269 insertions, 14 deletions
diff --git a/lib/spandx/terraform/parsers/hcl.rb b/lib/spandx/terraform/parsers/hcl.rb
index 6e170fd..68ab244 100644
--- a/lib/spandx/terraform/parsers/hcl.rb
+++ b/lib/spandx/terraform/parsers/hcl.rb
@@ -6,6 +6,7 @@ module Spandx
class Hcl < Parslet::Parser
rule(:alpha) { match['a-zA-Z'] }
rule(:assign) { str('=') }
+ rule(:colon) { str(':') }
rule(:comma) { str(',') }
rule(:comment) { (str('#') | str('//')) >> ((str("\n") >> str("\r").maybe).absent? >> any).repeat >> eol }
rule(:crlf) { match('[\r\n]') }
@@ -21,11 +22,13 @@ module Spandx
rule(:major_minor_patch) { number >> dot >> number >> dot >> number }
rule(:multiline_comment) { str('/*') >> (str('*/').absent? >> any).repeat >> str('*/') }
rule(:number) { digit.repeat }
+ rule(:plus) { str('+') }
rule(:pre_release) { hyphen >> (alpha | digit).repeat }
rule(:pre_release?) { pre_release.maybe }
rule(:quote) { str('"') }
rule(:rbracket) { str(']') }
rule(:rcurly) { str('}') }
+ rule(:slash) { str('/') }
rule(:space) { match('\s') }
rule(:tilda_wacka) { str('~>') }
rule(:version) { number >> dot >> number >> dot >> number >> pre_release? }
@@ -61,39 +64,41 @@ module Spandx
end
rule :string do
- quote >> match('[0-9A-Za-z.~> :=/]').repeat.as(:value) >> quote
+ quote >> (
+ digit | dot | alpha | str('~> ') | slash | colon | assign | plus
+ ).repeat(1).as(:value) >> quote
end
rule :array_item do
- whitespace >> string >> comma >> eol
+ whitespace? >> string >> comma.maybe >> eol
end
rule :array do
- lbracket >> eol >> array_item.repeat >> rbracket
+ lbracket >> eol >> array_item.repeat >> whitespace >> rbracket
end
- rule :argument do
- alpha.repeat.as(:name) >> whitespace >> assign >> whitespace >> (array.as(:values) | string)
+ rule :argument_value do
+ (array.as(:values) | string) >> eol
end
- rule :arguments do
- (argument >> eol).repeat
+ rule :argument do
+ whitespace >> alpha.repeat(1).as(:name) >> whitespace >> assign >> whitespace >> argument_value
end
- rule :identifier do
- whitespace >> quote >> ((alpha | match('[./]')).repeat).as(:name) >> quote >> whitespace
+ rule :block_body do
+ lcurly >> crlf >> argument.repeat.as(:arguments) >> rcurly
end
- rule :block_body do
- arguments.as(:arguments)
+ rule :identifier do
+ whitespace >> quote >> (alpha | dot | slash).repeat(1).as(:name) >> quote >> whitespace
end
rule :block do
- whitespace? >> (alpha.repeat).as(:type) >> identifier >> whitespace >> lcurly >> eol >> block_body >> rcurly >> eol
+ alpha.repeat(1).as(:type) >> identifier >> block_body
end
rule :blocks do
- block.repeat.as(:blocks)
+ whitespace? >> (block >> eol.maybe).repeat(1).as(:blocks)
end
root(:blocks)
diff --git a/spec/fixtures/terraform/multiple_providers/.terraform.lock.hcl b/spec/fixtures/terraform/multiple_providers/.terraform.lock.hcl
new file mode 100644
index 0000000..b0ca24f
--- /dev/null
+++ b/spec/fixtures/terraform/multiple_providers/.terraform.lock.hcl
@@ -0,0 +1,40 @@
+# This file is maintained automatically by "terraform init".
+# Manual edits may be lost in future updates.
+
+provider "registry.terraform.io/hashicorp/aws" {
+ version = "3.40.0"
+ constraints = "~> 3.27"
+ hashes = [
+ "h1:0r9TS3qACD9xJhrfTPZR7ygoCKDWHRX4c0D5GCyfAu4=",
+ "zh:2fd824991b19837e200d19b17d8188bf71efb92c36511809484549e77b4045dd",
+ "zh:47250cb58b3bd6f2698ca17bfb962710542d6adf95637cd560f6119abf97dba2",
+ "zh:515722a8c8726541b05362ec71331264977603374a2e4d4d64f89940873143ea",
+ "zh:61b6b7542da2113278c987a0af9f230321f5ed605f1e3098824603cb09ac771b",
+ "zh:66aad13ada6344b64adbc67abad4f35c414e62838a99f78626befb8b74c760d8",
+ "zh:7d4436aeb53fa348d7fd3c2ab4a727b03c7c59bfdcdecef4a75237760f3bb3cf",
+ "zh:a4583891debc49678491510574b1c28bb4fe3f83ed2bb353959c4c1f6f409f1f",
+ "zh:b8badecea52f6996ae832144560be87e0b7c2da7fe1dcd6e6230969234b2fc55",
+ "zh:cecf64a085f640c30437ccc31bd964c21004ae8ae00cfbd95fb04037e46b88ca",
+ "zh:d81dbb9ad8ce5eca4d1fc5a7a06bbb9c47ea8691f1502e94760fa680e20e4afc",
+ "zh:f0fc724a964c7f8154bc5911d572ee411f5d181414f9b1f09de7ebdacb0d884b",
+ ]
+}
+
+provider "registry.terraform.io/hashicorp/azurerm" {
+ version = "2.59.0"
+ constraints = "~> 2.1"
+ hashes = [
+ "h1:Mp7ECMHocobalN1+ASSKG5dHB7RnhZ6Y0rEEFTT5urA=",
+ "zh:0996d1c85bccdb15aeb6bc32f763c2d85ff854b33c3c3d62c34859669e05785e",
+ "zh:37807677e68058381514897ce10dc73a0dd0f503aba98113ac79844d310010e3",
+ "zh:3bccf9215bdbcc89327582b1d9d2a633c59215ca6452dbb4f9d0a7a661074c5b",
+ "zh:4801791332ab81e51d1ead47a62e0081ec4c1f23ef0fc2e8b15fef315ecdf07a",
+ "zh:5bad44816a3eaeb335f665f6eef9b41a403a40e9bddb2db8406ab0e847f639ca",
+ "zh:64f79c4ddc2bf8384f1a42c4e430ffdc53cb1fbc565bfe1cdc6b075dcdf098e9",
+ "zh:75c96fcb592ed80cc403944faadda25aeadda7fd6de9162a8d365249b1ec1c17",
+ "zh:8604558f2f201eefe25f4c611a5d4ef4d7c75338bf2f4a6321da5caa94937947",
+ "zh:cab930e374d33b3b980c6774f3d0ac3e3d7e1e596aba586d4368d8bcf05cf9c5",
+ "zh:cf0e78eb1e84b6dd11031283878e392e55801e3acd9c5592309e6f76ebe3a621",
+ "zh:eba02fcab150775b8b8beeec0c7dbba1585a57f4e97272f48c71021c5e289579",
+ ]
+}
diff --git a/spec/fixtures/terraform/multiple_providers/main.tf b/spec/fixtures/terraform/multiple_providers/main.tf
new file mode 100644
index 0000000..e2e0818
--- /dev/null
+++ b/spec/fixtures/terraform/multiple_providers/main.tf
@@ -0,0 +1,12 @@
+terraform {
+ required_providers {
+ aws = {
+ source = "hashicorp/aws"
+ version = "~> 3.27"
+ }
+ azure = {
+ source = "hashicorp/azurerm"
+ version = "~> 2.1"
+ }
+ }
+}
diff --git a/spec/unit/terraform/parsers/hcl_spec.rb b/spec/unit/terraform/parsers/hcl_spec.rb
index a596860..8ef5012 100644
--- a/spec/unit/terraform/parsers/hcl_spec.rb
+++ b/spec/unit/terraform/parsers/hcl_spec.rb
@@ -6,7 +6,7 @@ RSpec.describe Spandx::Terraform::Parsers::Hcl do
describe '#parse' do
subject { parser.parse_with_debug(content) }
- context 'when parsing an empty provider block' do
+ context 'when parsing a single provider' do
let(:content) do
<<~HCL
# This file is maintained automatically by "terraform init".
@@ -66,6 +66,64 @@ RSpec.describe Spandx::Terraform::Parsers::Hcl do
])
end
end
+
+ context 'when parsing multiple provider blocks' do
+ let(:content) { fixture_file_content('terraform/multiple_providers/.terraform.lock.hcl') }
+
+ specify { expect(subject).to be_truthy }
+ specify { expect(subject[:blocks][0][:name].to_s).to eql('registry.terraform.io/hashicorp/aws') }
+ specify { expect(subject[:blocks][0][:type].to_s).to eql('provider') }
+ specify { expect(subject[:blocks][1][:name].to_s).to eql('registry.terraform.io/hashicorp/azurerm') }
+ specify { expect(subject[:blocks][1][:type].to_s).to eql('provider') }
+
+ specify do
+ expect(subject[:blocks][0][:arguments]).to match_array([
+ { name: 'version', value: '3.40.0' },
+ { name: 'constraints', value: '~> 3.27' },
+ {
+ name: 'hashes',
+ values: [
+ { value: 'h1:0r9TS3qACD9xJhrfTPZR7ygoCKDWHRX4c0D5GCyfAu4=' },
+ { value: 'zh:2fd824991b19837e200d19b17d8188bf71efb92c36511809484549e77b4045dd' },
+ { value: 'zh:47250cb58b3bd6f2698ca17bfb962710542d6adf95637cd560f6119abf97dba2' },
+ { value: 'zh:515722a8c8726541b05362ec71331264977603374a2e4d4d64f89940873143ea' },
+ { value: 'zh:61b6b7542da2113278c987a0af9f230321f5ed605f1e3098824603cb09ac771b' },
+ { value: 'zh:66aad13ada6344b64adbc67abad4f35c414e62838a99f78626befb8b74c760d8' },
+ { value: 'zh:7d4436aeb53fa348d7fd3c2ab4a727b03c7c59bfdcdecef4a75237760f3bb3cf' },
+ { value: 'zh:a4583891debc49678491510574b1c28bb4fe3f83ed2bb353959c4c1f6f409f1f' },
+ { value: 'zh:b8badecea52f6996ae832144560be87e0b7c2da7fe1dcd6e6230969234b2fc55' },
+ { value: 'zh:cecf64a085f640c30437ccc31bd964c21004ae8ae00cfbd95fb04037e46b88ca' },
+ { value: 'zh:d81dbb9ad8ce5eca4d1fc5a7a06bbb9c47ea8691f1502e94760fa680e20e4afc' },
+ { value: 'zh:f0fc724a964c7f8154bc5911d572ee411f5d181414f9b1f09de7ebdacb0d884b' },
+ ]
+ },
+ ])
+ end
+
+ specify do
+ expect(subject[:blocks][1][:arguments]).to match_array([
+ { name: 'version', value: '2.59.0' },
+ { name: 'constraints', value: '~> 2.1' },
+ {
+ name: 'hashes',
+ values: [
+ { value: 'h1:Mp7ECMHocobalN1+ASSKG5dHB7RnhZ6Y0rEEFTT5urA=' },
+ { value: 'zh:0996d1c85bccdb15aeb6bc32f763c2d85ff854b33c3c3d62c34859669e05785e' },
+ { value: 'zh:37807677e68058381514897ce10dc73a0dd0f503aba98113ac79844d310010e3' },
+ { value: 'zh:3bccf9215bdbcc89327582b1d9d2a633c59215ca6452dbb4f9d0a7a661074c5b' },
+ { value: 'zh:4801791332ab81e51d1ead47a62e0081ec4c1f23ef0fc2e8b15fef315ecdf07a' },
+ { value: 'zh:5bad44816a3eaeb335f665f6eef9b41a403a40e9bddb2db8406ab0e847f639ca' },
+ { value: 'zh:64f79c4ddc2bf8384f1a42c4e430ffdc53cb1fbc565bfe1cdc6b075dcdf098e9' },
+ { value: 'zh:75c96fcb592ed80cc403944faadda25aeadda7fd6de9162a8d365249b1ec1c17' },
+ { value: 'zh:8604558f2f201eefe25f4c611a5d4ef4d7c75338bf2f4a6321da5caa94937947' },
+ { value: 'zh:cab930e374d33b3b980c6774f3d0ac3e3d7e1e596aba586d4368d8bcf05cf9c5' },
+ { value: 'zh:cf0e78eb1e84b6dd11031283878e392e55801e3acd9c5592309e6f76ebe3a621' },
+ { value: 'zh:eba02fcab150775b8b8beeec0c7dbba1585a57f4e97272f48c71021c5e289579' },
+ ]
+ },
+ ])
+ end
+ end
end
describe '#version_assignment' do
@@ -133,6 +191,7 @@ RSpec.describe Spandx::Terraform::Parsers::Hcl do
end
(0..9).each { |digit| specify { expect(parser.digit).to parse(digit.to_s) } }
+ specify { expect(parser.assign).not_to parse('==') }
specify { expect(parser.assign).to parse('=') }
specify { expect(parser.comment).to parse('# Manual edits may be lost in future updates.') }
specify { expect(parser.comment).to parse('# This file is maintained automatically by "terraform init".') }
@@ -149,6 +208,8 @@ RSpec.describe Spandx::Terraform::Parsers::Hcl do
specify { expect(parser.space).to parse(' ') }
specify { expect(parser.whitespace).to parse('# This is a comment') }
specify { expect(parser.whitespace).to parse('// This is a comment') }
+ specify { expect(parser.string).to parse('"h1:fjlp3Pd3QsTLghNm7TUh/KnEMM2D3tLb7jsDLs8oWUE="') }
+ specify { expect(parser.string).to parse('"zh:2014b397dd93fa55f2f2d1338c19e5b2b77b025a76a6b1fceea0b8696e984b9c"') }
specify do
expect(parser.whitespace).to parse(<<~HCL)
@@ -179,4 +240,141 @@ RSpec.describe Spandx::Terraform::Parsers::Hcl do
]
HCL
end
+
+ specify do
+ expect(parser.block).to parse(<<~HCL.chomp)
+ provider "thing" {
+ argument = "value"
+ arguments = [
+ "value",
+ "value",
+ ]
+ }
+ HCL
+ end
+
+ specify do
+ expect(parser.block_body.parse_with_debug(<<~HCL.chomp)).not_to be_nil
+ {
+ argument = "value"
+ arguments = [
+ "value",
+ "value",
+ ]
+ }
+ HCL
+ end
+
+ specify { expect(parser.argument).to parse('argument = "value"') }
+
+ specify do
+ expect(parser.argument).to parse(<<~HCL)
+ arguments = [
+ "a",
+ "b",
+ ]
+ HCL
+ end
+
+ describe '#blocks' do
+ subject { parser.blocks.parse_with_debug(hcl) }
+
+ context 'when parsing multiple multi-line empty blocks' do
+ let(:hcl) do
+ <<~HCL
+ provider "thingy" {
+ }
+
+ provider "other.thingy" {
+ }
+ HCL
+ end
+
+ it 'parses multiple empty blocks' do
+ expect(subject[:blocks]).to match_array([
+ { type: 'provider', name: 'thingy', arguments: [] },
+ { type: 'provider', name: 'other.thingy', arguments: [] },
+ ])
+ end
+ end
+
+ context 'when parsing multiple multi-line blocks with one argument assignment to a string in the first block' do
+ let(:hcl) do
+ <<~HCL
+ provider "thingy" {
+ name = "blah"
+ }
+
+ provider "other.thingy" {
+ }
+ HCL
+ end
+
+ it 'parses multiple empty blocks' do
+ expect(subject[:blocks]).to match_array([
+ { type: 'provider', name: 'thingy', arguments: [{ name: 'name', value: 'blah' }] },
+ { type: 'provider', name: 'other.thingy', arguments: [] },
+ ])
+ end
+ end
+
+ context 'when parsing multiple multi-line blocks with one argument assignment to a string in the second block' do
+ let(:hcl) do
+ <<~HCL
+ provider "thingy" {
+ }
+
+ provider "other.thingy" {
+ name = "blah"
+ }
+ HCL
+ end
+
+ it 'parses multiple empty blocks' do
+ expect(subject[:blocks]).to match_array([
+ { type: 'provider', name: 'thingy', arguments: [] },
+ { type: 'provider', name: 'other.thingy', arguments: [{ name: 'name', value: 'blah' }] },
+ ])
+ end
+ end
+
+ context 'when parsing a blocks with one assignment to an empty array' do
+ let(:hcl) do
+ <<~HCL
+ provider "thingy" {
+ names = [
+ ]
+ }
+ HCL
+ end
+
+ pending 'parses multiple empty blocks' do
+ expect(subject[:blocks]).to match_array([
+ { type: 'provider', name: 'thingy', arguments: [{ name: 'names', values: [] }] },
+ ])
+ end
+ end
+
+ context 'when parsing multiple multi-line blocks with one assignment to a multi-line array' do
+ let(:hcl) do
+ <<~HCL
+ provider "thingy" {
+ names = [
+ "blah"
+ ]
+ }
+
+ provider "other.thingy" {
+ }
+ HCL
+ end
+
+ it 'parses multiple empty blocks' do
+ expect(subject[:blocks]).to match_array([
+ { type: 'provider', name: 'thingy', arguments: [{ name: 'names', values: [{ value: 'blah' }] }] },
+ { type: 'provider', name: 'other.thingy', arguments: [] },
+ ])
+ end
+ end
+ end
end