diff options
| author | mo khan <mo@mokhan.ca> | 2021-05-20 13:13:36 -0600 |
|---|---|---|
| committer | mo khan <mo@mokhan.ca> | 2021-05-20 13:13:36 -0600 |
| commit | 9a7b1c1db7903fb9adeb8b2d147ec1d6d79d3505 (patch) | |
| tree | b87ead706693d57650c0501239ee9593041ac9b2 | |
| parent | 2b5437f4cf12d48386d9c8ac63343598ffdad2c2 (diff) | |
feat: parse multiple provider blocks
| -rw-r--r-- | lib/spandx/terraform/parsers/hcl.rb | 31 | ||||
| -rw-r--r-- | spec/fixtures/terraform/multiple_providers/.terraform.lock.hcl | 40 | ||||
| -rw-r--r-- | spec/fixtures/terraform/multiple_providers/main.tf | 12 | ||||
| -rw-r--r-- | spec/unit/terraform/parsers/hcl_spec.rb | 200 |
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 |
