diff --git a/go.mod b/go.mod index 2eacd66d0..3e3a018d8 100644 --- a/go.mod +++ b/go.mod @@ -22,7 +22,7 @@ require ( github.com/google/uuid v1.1.1 github.com/gruntwork-io/gruntwork-cli v0.7.0 github.com/hashicorp/go-multierror v1.1.0 - github.com/hashicorp/hcl v1.0.0 + github.com/hashicorp/hcl/v2 v2.8.2 github.com/imdario/mergo v0.3.7 // indirect github.com/jinzhu/copier v0.0.0-20190924061706-b57f9002281a github.com/jstemmer/go-junit-report v0.9.1 @@ -35,6 +35,7 @@ require ( github.com/sirupsen/logrus v1.4.2 github.com/stretchr/testify v1.4.0 github.com/urfave/cli v1.22.2 + github.com/zclconf/go-cty v1.2.0 golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 golang.org/x/net v0.0.0-20200707034311-ab3426394381 golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d diff --git a/go.sum b/go.sum index 662ac7b26..57b7d5fae 100644 --- a/go.sum +++ b/go.sum @@ -72,8 +72,15 @@ github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbt github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= +github.com/agext/levenshtein v1.2.1 h1:QmvMAjj2aEICytGiWzmxoE0x2KZvE0fvmqMOfy2tjT8= +github.com/agext/levenshtein v1.2.1/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/apparentlymart/go-dump v0.0.0-20180507223929-23540a00eaa3/go.mod h1:oL81AME2rN47vu18xqj1S1jPIPuN7afo62yKTNn3XMM= +github.com/apparentlymart/go-textseg v1.0.0 h1:rRmlIsPEEhUTIKQb7T++Nz/A5Q6C9IuX2wFoYVvnCs0= +github.com/apparentlymart/go-textseg v1.0.0/go.mod h1:z96Txxhf3xSFMPmb5X/1W05FF/Nj9VFpLOpjS5yuumk= +github.com/apparentlymart/go-textseg/v12 v12.0.0 h1:bNEQyAGak9tojivJNkoqWErVCQbjdL7GzRt3F8NvfJ0= +github.com/apparentlymart/go-textseg/v12 v12.0.0/go.mod h1:S/4uRK2UtaQttw1GenVJEynmyUenKwP++x/+DdGV/Ec= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/aws/aws-lambda-go v1.13.3 h1:SuCy7H3NLyp+1Mrfp+m80jcbi9KYWAs9/BXwppwRDzY= github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU= @@ -176,6 +183,8 @@ github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh github.com/go-sql-driver/mysql v1.4.1 h1:g24URVg0OFbNUTx9qqY1IRZ9D9z3iPyi5zKhQZpNwpA= github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/go-test/deep v1.0.3 h1:ZrJSEWsXzPOxaZnFteGEfooLba+ju3FYIbOrS+rQd68= +github.com/go-test/deep v1.0.3/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= @@ -191,6 +200,7 @@ github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfb github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= github.com/golang/protobuf v0.0.0-20161109072736-4bd1920723d7/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.1.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs= @@ -252,6 +262,8 @@ github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ github.com/hashicorp/golang-lru v0.5.3/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/hashicorp/hcl/v2 v2.8.2 h1:wmFle3D1vu0okesm8BTLVDyJ6/OL9DCLUwn0b2OptiY= +github.com/hashicorp/hcl/v2 v2.8.2/go.mod h1:bQTN5mpo+jewjJgh8jr0JUguIi7qPHUF6yIfAEN3jqY= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= @@ -290,6 +302,8 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348 h1:MtvEpTB6LX3vkb4ax0b5D2DHbNAUsen0Gx5wZoq3lV4= +github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k= github.com/magiconair/properties v1.8.0 h1:LLgXmsheXeRoUOBOjtwPQCWIYqM/LU1ayDtDePerRcY= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= @@ -311,6 +325,8 @@ github.com/miekg/dns v1.1.31 h1:sJFOl9BgwbYAWOGEwr61FU28pqsBNdpRBnhGXtO06Oo= github.com/miekg/dns v1.1.31/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7 h1:DpOJ2HYzCv8LZP15IdmG+YdwD2luVPHITV96TkirNBM= +github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= @@ -369,6 +385,8 @@ github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQD github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/sclevine/spec v1.2.0/go.mod h1:W4J29eT/Kzv7/b9IWLB055Z+qvVC9vt0Arko24q7p+U= +github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ= +github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= @@ -384,6 +402,7 @@ github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tL github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.2/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= @@ -404,9 +423,12 @@ github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtX github.com/urfave/cli v1.22.2 h1:gsqYFH8bb9ekPA12kRo0hfjngWQjkJPlN9R0N78BoUo= github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/vdemeester/k8s-pkg-credentialprovider v0.0.0-20200107171650-7c61ffa44238/go.mod h1:JwQJCMWpUDqjZrB5jpw0f5VbN7U95zxFy1ZDpoEarGo= +github.com/vmihailenco/msgpack v3.3.3+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk= github.com/vmware/govmomi v0.20.3/go.mod h1:URlwyTFZX72RmxtxuaFL2Uj3fD1JTvZdx59bHWk6aFU= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= +github.com/zclconf/go-cty v1.2.0 h1:sPHsy7ADcIZQP3vILvTjrh74ZA175TFP5vqiNK1UmlI= +github.com/zclconf/go-cty v1.2.0/go.mod h1:hOPWgoHbaTUnI5k4D2ld+GRpFJSCe6bCM7m1q/N4PQ8= go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= @@ -420,6 +442,7 @@ golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnf golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= @@ -453,6 +476,7 @@ golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180811021610-c39426892332/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -503,6 +527,7 @@ golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502175342-a43fa875dd82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= diff --git a/modules/terraform/errors.go b/modules/terraform/errors.go index f5ae2534b..d80a8514b 100644 --- a/modules/terraform/errors.go +++ b/modules/terraform/errors.go @@ -2,6 +2,7 @@ package terraform import ( "fmt" + "reflect" ) // TgInvalidBinary occurs when a terragrunt function is called and the TerraformBinary is @@ -77,11 +78,12 @@ func (err InputFileKeyNotFound) Error() string { return fmt.Sprintf("tfvar file %q doesn't contain a value for the key %q", err.FilePath, err.Key) } -type HclDecodeError struct { - FilePath string - ErrorText string +// PanicWhileParsingVarFile is returned when the HCL parsing routine panics due to errors. +type PanicWhileParsingVarFile struct { + ConfigFile string + RecoveredValue interface{} } -func (err HclDecodeError) Error() string { - return fmt.Sprintf("%s - %s", err.FilePath, err.ErrorText) +func (err PanicWhileParsingVarFile) Error() string { + return fmt.Sprintf("Recovering panic while parsing '%s'. Got error of type '%v': %v", err.ConfigFile, reflect.TypeOf(err.RecoveredValue), err.RecoveredValue) } diff --git a/modules/terraform/var-file.go b/modules/terraform/var-file.go index 4572ae82b..e5ff5b62b 100644 --- a/modules/terraform/var-file.go +++ b/modules/terraform/var-file.go @@ -1,14 +1,17 @@ package terraform import ( - "errors" + "encoding/json" "fmt" "io/ioutil" "reflect" "testing" - "github.com/hashicorp/hcl" + "github.com/hashicorp/hcl/v2/hclparse" "github.com/stretchr/testify/require" + "github.com/zclconf/go-cty/cty" + "github.com/zclconf/go-cty/cty/gocty" + ctyjson "github.com/zclconf/go-cty/cty/json" ) // GetVariableAsStringFromVarFile Gets the string represention of a variable from a provided input file found in VarFile @@ -25,9 +28,7 @@ func GetVariableAsStringFromVarFile(t *testing.T, fileName string, key string) s // For list or map, use GetVariableAsListFromVarFile or GetVariableAsMapFromVarFile, respectively. func GetVariableAsStringFromVarFileE(t *testing.T, fileName string, key string) (string, error) { var variables map[string]interface{} - err := GetAllVariablesFromVarFileE(t, fileName, &variables) - if err != nil { return "", err } @@ -46,7 +47,6 @@ func GetVariableAsStringFromVarFileE(t *testing.T, fileName string, key string) func GetVariableAsMapFromVarFile(t *testing.T, fileName string, key string) map[string]string { result, err := GetVariableAsMapFromVarFileE(t, fileName, key) require.NoError(t, err) - return result } @@ -55,10 +55,7 @@ func GetVariableAsMapFromVarFile(t *testing.T, fileName string, key string) map[ // Returns an error if GetAllVariablesFromVarFileE returns an error, the key provided does not exist, or the value associated with the key is not a map func GetVariableAsMapFromVarFileE(t *testing.T, fileName string, key string) (map[string]string, error) { var variables map[string]interface{} - - resultMap := make(map[string]string) err := GetAllVariablesFromVarFileE(t, fileName, &variables) - if err != nil { return nil, err } @@ -69,20 +66,14 @@ func GetVariableAsMapFromVarFileE(t *testing.T, fileName string, key string) (ma return nil, InputFileKeyNotFound{FilePath: fileName, Key: key} } - if reflect.TypeOf(variable).String() != "[]map[string]interface {}" { - return nil, UnexpectedOutputType{Key: key, ExpectedType: "[]map[string]interface {}", ActualType: reflect.TypeOf(variable).String()} + if reflect.TypeOf(variable).String() != "map[string]interface {}" { + return nil, UnexpectedOutputType{Key: key, ExpectedType: "map[string]interface {}", ActualType: reflect.TypeOf(variable).String()} } - mapKeys := variable.([]map[string]interface{}) - - if len(mapKeys) == 0 { - return nil, errors.New("no map keys could be found for given map") - } - - for mapKey, mapVal := range mapKeys[0] { + resultMap := make(map[string]string) + for mapKey, mapVal := range variable.(map[string]interface{}) { resultMap[mapKey] = fmt.Sprintf("%v", mapVal) } - return resultMap, nil } @@ -100,15 +91,12 @@ func GetVariableAsListFromVarFile(t *testing.T, fileName string, key string) []s // Will return error if GetAllVariablesFromVarFileE returns an error, the key provided does not exist, or the value associated with the key is not a list func GetVariableAsListFromVarFileE(t *testing.T, fileName string, key string) ([]string, error) { var variables map[string]interface{} - resultArray := []string{} err := GetAllVariablesFromVarFileE(t, fileName, &variables) - if err != nil { return nil, err } variable, exists := variables[key] - if !exists { return nil, InputFileKeyNotFound{FilePath: fileName, Key: key} } @@ -117,6 +105,7 @@ func GetVariableAsListFromVarFileE(t *testing.T, fileName string, key string) ([ return nil, UnexpectedOutputType{Key: key, ExpectedType: "[]interface {}", ActualType: reflect.TypeOf(variable).String()} } + resultArray := []string{} for _, item := range variable.([]interface{}) { resultArray = append(resultArray, fmt.Sprintf("%v", item)) } @@ -124,26 +113,125 @@ func GetVariableAsListFromVarFileE(t *testing.T, fileName string, key string) ([ return resultArray, nil } -// GetAllVariablesFromVarFile Parses all data from a provided input file found in VarFile and stores the result in the value pointed to by out +// GetAllVariablesFromVarFileE Parses all data from a provided input file found ind in VarFile and stores the result in +// the value pointed to by out. func GetAllVariablesFromVarFile(t *testing.T, fileName string, out interface{}) { err := GetAllVariablesFromVarFileE(t, fileName, out) require.NoError(t, err) } -// GetAllVariablesFromVarFileE Parses all data from a provided input file found ind in VarFile and stores the result in the value pointed to by out -// Returns an error if the specified file does not exist, the specified file is not readable, or the specified file cannot be decoded from HCL +// GetAllVariablesFromVarFileE Parses all data from a provided input file found ind in VarFile and stores the result in +// the value pointed to by out. Returns an error if the specified file does not exist, the specified file is not +// readable, or the specified file cannot be decoded from HCL. func GetAllVariablesFromVarFileE(t *testing.T, fileName string, out interface{}) error { fileContents, err := ioutil.ReadFile(fileName) + if err != nil { + return err + } + + return parseAndDecodeVarFile(string(fileContents), fileName, out) +} +// parseAndDecodeVarFile uses the HCL2 parser to parse the given varfile string into an HCL file body, and then decode it +// into a map that maps var names to values. +func parseAndDecodeVarFile(hclContents string, filename string, out interface{}) (err error) { + // The HCL2 parser and especially cty conversions will panic in many types of errors, so we have to recover from + // those panics here and convert them to normal errors + defer func() { + if recovered := recover(); recovered != nil { + err = PanicWhileParsingVarFile{RecoveredValue: recovered, ConfigFile: filename} + } + }() + + parser := hclparse.NewParser() + + file, parseDiagnostics := parser.ParseHCL([]byte(hclContents), filename) + if parseDiagnostics != nil && parseDiagnostics.HasErrors() { + return parseDiagnostics + } + + // VarFiles should only have attributes, so extract the attributes and decode the expressions into the return map. + attrs, hclDiags := file.Body.JustAttributes() + if hclDiags != nil && hclDiags.HasErrors() { + return hclDiags + } + + valMap := map[string]cty.Value{} + for name, attr := range attrs { + val, hclDiags := attr.Expr.Value(nil) // nil because no function calls or variable references are allowed here + if hclDiags != nil && hclDiags.HasErrors() { + return hclDiags + } + valMap[name] = val + } + + ctyVal, err := convertValuesMapToCtyVal(valMap) if err != nil { return err } - err = hcl.Decode(out, string(fileContents)) + typedOut, hasType := out.(*map[string]interface{}) + if hasType { + genericMap, err := parseCtyValueToMap(ctyVal) + if err != nil { + return err + } + *typedOut = genericMap + return nil + } + return gocty.FromCtyValue(ctyVal, out) +} +// This is a hacky workaround to convert a cty Value to a Go map[string]interface{}. cty does not support this directly +// (https://github.com/hashicorp/hcl2/issues/108) and doing it with gocty.FromCtyValue is nearly impossible, as cty +// requires you to specify all the output types and will error out when it hits interface{}. So, as an ugly workaround, +// we convert the given value to JSON using cty's JSON library and then convert the JSON back to a +// map[string]interface{} using the Go json library. +func parseCtyValueToMap(value cty.Value) (map[string]interface{}, error) { + jsonBytes, err := ctyjson.Marshal(value, cty.DynamicPseudoType) if err != nil { - return HclDecodeError{FilePath: fileName, ErrorText: err.Error()} + return nil, err + } + + var ctyJsonOutput CtyJsonOutput + if err := json.Unmarshal(jsonBytes, &ctyJsonOutput); err != nil { + return nil, err + } + + return ctyJsonOutput.Value, nil +} + +// When you convert a cty value to JSON, if any of that types are not yet known (i.e., are labeled as +// DynamicPseudoType), cty's Marshall method will write the type information to a type field and the actual value to +// a value field. This struct is used to capture that information so when we parse the JSON back into a Go struct, we +// can pull out just the Value field we need. +type CtyJsonOutput struct { + Value map[string]interface{} + Type interface{} +} + +// convertValuesMapToCtyVal takes a map of name - cty.Value pairs and converts to a single cty.Value object that can +// then be converted to other go types. +func convertValuesMapToCtyVal(valMap map[string]cty.Value) (cty.Value, error) { + valMapAsCty := cty.NilVal + if valMap != nil && len(valMap) > 0 { + var err error + valMapAsCty, err = gocty.ToCtyValue(valMap, generateTypeFromValuesMap(valMap)) + if err != nil { + return valMapAsCty, err + } } + return valMapAsCty, nil +} - return nil +// generateTypeFromValuesMap takes a values map and returns an object type that has the same number of fields, but +// bound to each type of the underlying evaluated expression. This is the only way the HCL decoder will be happy, as +// object type is the only map type that allows different types for each attribute (cty.Map requires all attributes to +// have the same type. +func generateTypeFromValuesMap(valMap map[string]cty.Value) cty.Type { + outType := map[string]cty.Type{} + for k, v := range valMap { + outType[k] = v.Type() + } + return cty.Object(outType) } diff --git a/modules/terraform/var-file_test.go b/modules/terraform/var-file_test.go index 39e3b3e6b..b5c0aa76d 100644 --- a/modules/terraform/var-file_test.go +++ b/modules/terraform/var-file_test.go @@ -74,7 +74,6 @@ func TestGetVariableAsMapFromVarFile(t *testing.T) { defer os.Remove(randomFileName) val := GetVariableAsMapFromVarFile(t, randomFileName, "tags") - require.Equal(t, expected, val) } @@ -175,64 +174,60 @@ func TestGetVariableAsListKeyDoesNotExist(t *testing.T) { } func TestGetAllVariablesFromVarFileEFileDoesNotExist(t *testing.T) { var variables map[string]interface{} - - err := GetAllVariablesFromVarFileE(t, "filea", variables) - + err := GetAllVariablesFromVarFileE(t, "filea", &variables) require.Equal(t, "open filea: no such file or directory", err.Error()) } func TestGetAllVariablesFromVarFileBadFile(t *testing.T) { - var variables map[string]interface{} - randomFileName := fmt.Sprintf("./%s.tfvars", random.UniqueId()) testHcl := []byte(` thiswillnotwork`) - err := ioutil.WriteFile(randomFileName, testHcl, 0644) - - if err != nil { - fmt.Println(err.Error()) - t.FailNow() - } - + WriteFile(t, randomFileName, testHcl) defer os.Remove(randomFileName) - err = GetAllVariablesFromVarFileE(t, randomFileName, variables) - + var variables map[string]interface{} + err := GetAllVariablesFromVarFileE(t, randomFileName, &variables) require.Error(t, err) // HCL library could change their error string, so we are only testing the error string contains what we add to it - require.Regexp(t, fmt.Sprintf("^%s - ", randomFileName), err.Error()) - + require.Regexp(t, fmt.Sprintf("^%s:2,3-18: ", randomFileName), err.Error()) } func TestGetAllVariablesFromVarFile(t *testing.T) { - var variables map[string]interface{} - randomFileName := fmt.Sprintf("./%s.tfvars", random.UniqueId()) testHcl := []byte(` aws_region = "us-east-2" `) - err := ioutil.WriteFile(randomFileName, testHcl, 0644) - - if err != nil { - fmt.Println(err.Error()) - t.FailNow() - } - + WriteFile(t, randomFileName, testHcl) defer os.Remove(randomFileName) - err = GetAllVariablesFromVarFileE(t, randomFileName, &variables) - - if err != nil { - t.FailNow() - } + var variables map[string]interface{} + err := GetAllVariablesFromVarFileE(t, randomFileName, &variables) + require.NoError(t, err) expected := make(map[string]interface{}) expected["aws_region"] = "us-east-2" require.Equal(t, expected, variables) +} + +func TestGetAllVariablesFromVarFileStructOut(t *testing.T) { + randomFileName := fmt.Sprintf("./%s.tfvars", random.UniqueId()) + testHcl := []byte(` + aws_region = "us-east-2" + `) + + WriteFile(t, randomFileName, testHcl) + defer os.Remove(randomFileName) + + var region struct { + AwsRegion string `cty:"aws_region"` + } + err := GetAllVariablesFromVarFileE(t, randomFileName, ®ion) + require.NoError(t, err) + require.Equal(t, "us-east-2", region.AwsRegion) }