Hcl::Checker
Hashicorp Configuration Language syntax checker and parser.
Installation
Add this line to your application's Gemfile:
gem 'hcl-checker'
And then execute:
$ bundle
Or install it yourself as:
$ gem install hcl-checker
Usage
Load HCL string:
hcl_string = 'provider "aws" {
region = "${var.aws_region}"
access_key = "${var.aws_access_key}"
secret_key = "${var.aws_secret_key}"
}
# This is a awesome comment
resource "aws_vpc" "default" {
cidr_block = "10.0.0.0/16"
enable_dns_hostnames = true
tags {
Name = "Event Store VPC"
}
}'
You can validate the hcl_string contents with valid? method. This will
return true if is a valid HCL or false if not.
HCL::Checker.valid? hcl_string
# => true
You can parse the hcl_string into a Hash with parse method.
HCL::Checker.parse(hcl_string)
# => {"provider"=>{"aws"=>{"region"=>"${var.aws_region}", "access_key"=>"${var.aws_access_key}", "secret_key"=>"${var.aws_secret_key}"}}, "resource"=>{"aws_vpc"=>{"default"=>{"cidr_block"=>"10.0.0.0/16", "enable_dns_hostnames"=>true, "tags"=>{"Name"=>"Event Store VPC"}}}}}
If after a parse you got a String back instead of a Hash, that string
is the parse error message, and it is also available afterwards via
last_error:
HCL::Checker.last_error
# => "Parse error on line 7, column 18: unexpected \",\" (after \"instance_type\")"
Compatibility
This gem parses full HCL1 syntax, plus a
substantial subset of HCL2 expression syntax: arithmetic/comparison/logical
operators, ternary expressions, for expressions (list and object
comprehensions), splat expressions (list[*] and the legacy list.*),
chained indexing/attribute access, the null literal, scientific notation,
string escape sequences, <<- heredoc dedenting, and %{ if }/%{ for }
string template directives.
Not supported:
- The JSON variant of HCL2 (
.tf.jsonfiles) — a fully separate parsing mode. - Expression evaluation — this is a structural parser/checker, not an
interpreter.
${...}interpolations and bare expressions (function calls, arithmetic,for/ternary, etc.) are preserved as opaque text, never evaluated.
Known caveats:
- A quoted string used as an operand inside a larger expression loses its
surrounding quotes in the reconstructed text (e.g.
var.env == "prod"becomes the textvar.env == prod). A string assigned directly as a value is unaffected (region = "us-east-1"stays exactly"us-east-1"). - A list/object literal used as an operand inside a larger expression is
rendered with Ruby's own
Array/Hashtext representation, not HCL syntax (e.g.merge({a = 1}, {b = 2})becomes the textmerge({"a"=>1}, {"b"=>2})).
Upgrading from 1.x
Version 2.0 adds the HCL2 support described above and fixes several parsing bugs (including two that could hang or silently return wrong output). Most HCL1 configs continue to parse identically, but a few things changed:
- Ruby 3.3 or newer is now required (previously
>= 2.7.0). nullnow parses as a realnil, not the string"null".- Parse error messages changed format and now include a line and column
number, e.g.
"Parse error on line 7, column 18: unexpected \",\" (after \"instance_type\")". Code that pattern-matches the exact old error text will need updating; code that only checks whetherparsereturned aHashor aString(the documented, recommended pattern above) is unaffected. for,in,ifandnullare now reserved words when they appear as a standalone token, needed for HCL2'sforexpressions and thenullliteral. They still work as plain, unquoted attribute names and block labels (for = 1,resource for "x" {}), but not in every conceivable position — a nested block literally labeledforimmediately followed by another bare identifier no longer parses, an extremely unusual construct in practice.
Development
After checking out the repo, run bin/setup to install dependencies. Then, run
rake spec to run the tests. You can also run bin/console for an interactive
prompt that will allow you to experiment.
To install this gem onto your local machine, run bundle exec rake install.
Note: if you run
bundle installdirectly instead ofbin/setupand get aBundler::PermissionErrortrying to write to something like/var/lib/gems/3.3.0/cache, your system Ruby's gem directory is root-owned (common when Ruby was installed via a distro package, e.g. apt'sruby3.3). Fix it by installing into a local, writable path instead:bundle config set --local path 'vendor/bundle' && bundle install— this is exactly whatbin/setupalready does for you.
Building grammar
If you change assets/lexer.rex or assets/parse.y, regenerate the lexer
and parser with:
$ bundle exec rake build_grammar
Building Lexer....done
Building Parser....done
This overwrites lib/hcl/checker/lexer.rb and lib/hcl/checker/parser.rb —
never edit those two files directly, they're marked DO NOT MODIFY and any
changes will be lost the next time this task runs.
Publishing to RubyGems.org
This project uses Bundler's standard gem release workflow (rake release,
provided by bundler/gem_tasks in the Rakefile).
-
Bump the version in
lib/hcl/checker/version.rb. -
Run the test suite and make sure it's green:
bundle exec rake spec. -
Commit the version bump, e.g.
git commit -am "Bump version to X.Y.Z". -
Release:
$ bundle exec rake releaseThis single command builds the
.gemfile fromhcl-checker.gemspec, creates a git tagvX.Y.Z, pushes the tag and any pending commits to the configured remote, and pushes the built gem to rubygems.org.
If you've never pushed to rubygems.org from this machine before, gem push
will prompt you to sign in (or run gem signin beforehand to store an API
key). This gem has enough downloads that RubyGems requires multi-factor
authentication to push a new version — have your TOTP code ready, or pass it
directly with gem push --otp <code> pkg/hcl-checker-X.Y.Z.gem if you need
to push the built .gem file manually instead of via rake release.
To build the .gem file without publishing it — useful to double check its
contents first — run bundle exec rake build; the file is written to pkg/.
Roadmap / Future work
- [ ]
HCL::Checker.last_erroris mutable class-level state (not thread-safe). - [ ]
parsereturns aHashon success and aString(error message) on failure — an ambiguous return type that forces callers to duck-type the result instead of, say, raising a dedicated exception class. - [ ] No JSON variant of HCL2 (
.tf.json) support. - [ ] No expression evaluation — by design (see Compatibility above), but worth revisiting if there's ever demand for it as a separate, opt-in mode.
Implementation details
Initial parser version with partial HCL 1.0 suppport was created and developed by Sikula and available at Ruby HCL Repository.
Contributing
Bug reports and pull requests are welcome on GitHub at https://github.com/mfcastellani/hcl-checker. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the Contributor Covenant code of conduct.
License
The gem is available as open source under the terms of the MIT License.