mirror of
https://github.com/opentofu/opentofu.git
synced 2025-02-25 18:45:20 -06:00
Move varhcl (body variable inspection) into hcl fork (#1919)
Signed-off-by: Christian Mesh <christianmesh1@gmail.com>
This commit is contained in:
parent
23d69e2351
commit
3c45c30249
@ -67,7 +67,7 @@ linters:
|
|||||||
- gocritic
|
- gocritic
|
||||||
- gocyclo
|
- gocyclo
|
||||||
- goimports
|
- goimports
|
||||||
- gomoddirectives
|
#- gomoddirectives Disabled while we deal with the HCL fork
|
||||||
- gomodguard
|
- gomodguard
|
||||||
- goprintffuncname
|
- goprintffuncname
|
||||||
- gosec
|
- gosec
|
||||||
@ -104,4 +104,4 @@ linters:
|
|||||||
- unused
|
- unused
|
||||||
- usestdlibvars
|
- usestdlibvars
|
||||||
- wastedassign
|
- wastedassign
|
||||||
- whitespace
|
- whitespace
|
||||||
|
4
go.mod
4
go.mod
@ -84,7 +84,7 @@ require (
|
|||||||
github.com/xanzy/ssh-agent v0.3.1
|
github.com/xanzy/ssh-agent v0.3.1
|
||||||
github.com/xlab/treeprint v0.0.0-20161029104018-1d6e34225557
|
github.com/xlab/treeprint v0.0.0-20161029104018-1d6e34225557
|
||||||
github.com/zclconf/go-cty v1.14.4
|
github.com/zclconf/go-cty v1.14.4
|
||||||
github.com/zclconf/go-cty-debug v0.0.0-20191215020915-b22d67c1ba0b
|
github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940
|
||||||
github.com/zclconf/go-cty-yaml v1.0.3
|
github.com/zclconf/go-cty-yaml v1.0.3
|
||||||
go.opentelemetry.io/contrib/exporters/autoexport v0.0.0-20230703072336-9a582bd098a2
|
go.opentelemetry.io/contrib/exporters/autoexport v0.0.0-20230703072336-9a582bd098a2
|
||||||
go.opentelemetry.io/otel v1.21.0
|
go.opentelemetry.io/otel v1.21.0
|
||||||
@ -268,4 +268,4 @@ require (
|
|||||||
|
|
||||||
go 1.21
|
go 1.21
|
||||||
|
|
||||||
replace github.com/hashicorp/hcl/v2 v2.20.1 => github.com/opentofu/hcl/v2 v2.0.0-20240416130056-03228b26f391
|
replace github.com/hashicorp/hcl/v2 v2.20.1 => github.com/opentofu/hcl/v2 v2.0.0-20240814143621-8048794c5c52
|
||||||
|
13
go.sum
13
go.sum
@ -282,7 +282,6 @@ github.com/apparentlymart/go-dump v0.0.0-20190214190832-042adf3cf4a0 h1:MzVXffFU
|
|||||||
github.com/apparentlymart/go-dump v0.0.0-20190214190832-042adf3cf4a0/go.mod h1:oL81AME2rN47vu18xqj1S1jPIPuN7afo62yKTNn3XMM=
|
github.com/apparentlymart/go-dump v0.0.0-20190214190832-042adf3cf4a0/go.mod h1:oL81AME2rN47vu18xqj1S1jPIPuN7afo62yKTNn3XMM=
|
||||||
github.com/apparentlymart/go-shquot v0.0.1 h1:MGV8lwxF4zw75lN7e0MGs7o6AFYn7L6AZaExUpLh0Mo=
|
github.com/apparentlymart/go-shquot v0.0.1 h1:MGV8lwxF4zw75lN7e0MGs7o6AFYn7L6AZaExUpLh0Mo=
|
||||||
github.com/apparentlymart/go-shquot v0.0.1/go.mod h1:lw58XsE5IgUXZ9h0cxnypdx31p9mPFIVEQ9P3c7MlrU=
|
github.com/apparentlymart/go-shquot v0.0.1/go.mod h1:lw58XsE5IgUXZ9h0cxnypdx31p9mPFIVEQ9P3c7MlrU=
|
||||||
github.com/apparentlymart/go-textseg v1.0.0/go.mod h1:z96Txxhf3xSFMPmb5X/1W05FF/Nj9VFpLOpjS5yuumk=
|
|
||||||
github.com/apparentlymart/go-textseg/v15 v15.0.0 h1:uYvfpb3DyLSCGWnctWKGj857c6ew1u1fNQOlOtuGxQY=
|
github.com/apparentlymart/go-textseg/v15 v15.0.0 h1:uYvfpb3DyLSCGWnctWKGj857c6ew1u1fNQOlOtuGxQY=
|
||||||
github.com/apparentlymart/go-textseg/v15 v15.0.0/go.mod h1:K8XmNZdhEBkdlyDdvbmmsvpAG721bKi0joRfFdHIWJ4=
|
github.com/apparentlymart/go-textseg/v15 v15.0.0/go.mod h1:K8XmNZdhEBkdlyDdvbmmsvpAG721bKi0joRfFdHIWJ4=
|
||||||
github.com/apparentlymart/go-userdirs v0.0.0-20200915174352-b0c018a67c13 h1:JtuelWqyixKApmXm3qghhZ7O96P6NKpyrlSIe8Rwnhw=
|
github.com/apparentlymart/go-userdirs v0.0.0-20200915174352-b0c018a67c13 h1:JtuelWqyixKApmXm3qghhZ7O96P6NKpyrlSIe8Rwnhw=
|
||||||
@ -529,7 +528,6 @@ github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71
|
|||||||
github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8=
|
github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8=
|
||||||
github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
|
github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
|
||||||
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
|
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
|
||||||
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.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.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
@ -925,8 +923,8 @@ github.com/onsi/gomega v1.10.1 h1:o0+MgICZLuZ7xjH7Vx6zS/zcu93/BEp1VwkIW1mEXCE=
|
|||||||
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
|
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
|
||||||
github.com/openbao/openbao/api v0.0.0-20240326035453-c075f0ef2c7e h1:LIQFfqW6BA5E2ycx8NNDgyKh0exFubHePM5pF3knogo=
|
github.com/openbao/openbao/api v0.0.0-20240326035453-c075f0ef2c7e h1:LIQFfqW6BA5E2ycx8NNDgyKh0exFubHePM5pF3knogo=
|
||||||
github.com/openbao/openbao/api v0.0.0-20240326035453-c075f0ef2c7e/go.mod h1:NUvBdXCNlmAGQ9TbYV7vS1Y9awHAjrq3QLiBWV+4Glk=
|
github.com/openbao/openbao/api v0.0.0-20240326035453-c075f0ef2c7e/go.mod h1:NUvBdXCNlmAGQ9TbYV7vS1Y9awHAjrq3QLiBWV+4Glk=
|
||||||
github.com/opentofu/hcl/v2 v2.0.0-20240416130056-03228b26f391 h1:Z2YGMhYBvmXBZlQdnlembuV4sp0lPJphIfgM9fVSjpU=
|
github.com/opentofu/hcl/v2 v2.0.0-20240814143621-8048794c5c52 h1:5O3LYVJxOHh7wfp/f3Em9EVMAK22dv7+bPj/k1hRamg=
|
||||||
github.com/opentofu/hcl/v2 v2.0.0-20240416130056-03228b26f391/go.mod h1:TZDqQ4kNKCbh1iJp99FdPiUaVDDUPivbqxZulxDYqL4=
|
github.com/opentofu/hcl/v2 v2.0.0-20240814143621-8048794c5c52/go.mod h1:62ZYHrXgPoX8xBnzl8QzbWq4dyDsDtfCRgIq1rbJEvA=
|
||||||
github.com/opentofu/registry-address v0.0.0-20230920144404-f1e51167f633 h1:81TBkM/XGIFlVvyabp0CJl00UHeVUiQjz0fddLMi848=
|
github.com/opentofu/registry-address v0.0.0-20230920144404-f1e51167f633 h1:81TBkM/XGIFlVvyabp0CJl00UHeVUiQjz0fddLMi848=
|
||||||
github.com/opentofu/registry-address v0.0.0-20230920144404-f1e51167f633/go.mod h1:HzQhpVo/NJnGmN+7FPECCVCA5ijU7AUcvf39enBKYOc=
|
github.com/opentofu/registry-address v0.0.0-20230920144404-f1e51167f633/go.mod h1:HzQhpVo/NJnGmN+7FPECCVCA5ijU7AUcvf39enBKYOc=
|
||||||
github.com/packer-community/winrmcp v0.0.0-20180921211025-c76d91c1e7db h1:9uViuKtx1jrlXLBW/pMnhOfzn3iSEdLase/But/IZRU=
|
github.com/packer-community/winrmcp v0.0.0-20180921211025-c76d91c1e7db h1:9uViuKtx1jrlXLBW/pMnhOfzn3iSEdLase/But/IZRU=
|
||||||
@ -1038,7 +1036,6 @@ github.com/tombuildsstuff/giovanni v0.15.1 h1:CVRaLOJ7C/eercCrKIsarfJ4SZoGMdBL9Q
|
|||||||
github.com/tombuildsstuff/giovanni v0.15.1/go.mod h1:0TZugJPEtqzPlMpuJHYfXY6Dq2uLPrXf98D2XQSxNbA=
|
github.com/tombuildsstuff/giovanni v0.15.1/go.mod h1:0TZugJPEtqzPlMpuJHYfXY6Dq2uLPrXf98D2XQSxNbA=
|
||||||
github.com/ulikunitz/xz v0.5.10 h1:t92gobL9l3HE202wg3rlk19F6X+JOxl9BBrCCMYEYd8=
|
github.com/ulikunitz/xz v0.5.10 h1:t92gobL9l3HE202wg3rlk19F6X+JOxl9BBrCCMYEYd8=
|
||||||
github.com/ulikunitz/xz v0.5.10/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
|
github.com/ulikunitz/xz v0.5.10/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
|
||||||
github.com/vmihailenco/msgpack v3.3.3+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk=
|
|
||||||
github.com/vmihailenco/msgpack/v5 v5.3.5 h1:5gO0H1iULLWGhs2H5tbAHIZTV8/cYafcFOr9znI5mJU=
|
github.com/vmihailenco/msgpack/v5 v5.3.5 h1:5gO0H1iULLWGhs2H5tbAHIZTV8/cYafcFOr9znI5mJU=
|
||||||
github.com/vmihailenco/msgpack/v5 v5.3.5/go.mod h1:7xyJ9e+0+9SaZT0Wt1RGleJXzli6Q/V5KbhBonMG9jc=
|
github.com/vmihailenco/msgpack/v5 v5.3.5/go.mod h1:7xyJ9e+0+9SaZT0Wt1RGleJXzli6Q/V5KbhBonMG9jc=
|
||||||
github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g=
|
github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g=
|
||||||
@ -1058,11 +1055,10 @@ github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9dec
|
|||||||
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||||
github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||||
github.com/zclconf/go-cty v1.2.0/go.mod h1:hOPWgoHbaTUnI5k4D2ld+GRpFJSCe6bCM7m1q/N4PQ8=
|
|
||||||
github.com/zclconf/go-cty v1.14.4 h1:uXXczd9QDGsgu0i/QFR/hzI5NYCHLf6NQw/atrbnhq8=
|
github.com/zclconf/go-cty v1.14.4 h1:uXXczd9QDGsgu0i/QFR/hzI5NYCHLf6NQw/atrbnhq8=
|
||||||
github.com/zclconf/go-cty v1.14.4/go.mod h1:VvMs5i0vgZdhYawQNq5kePSpLAoz8u1xvZgrPIxfnZE=
|
github.com/zclconf/go-cty v1.14.4/go.mod h1:VvMs5i0vgZdhYawQNq5kePSpLAoz8u1xvZgrPIxfnZE=
|
||||||
github.com/zclconf/go-cty-debug v0.0.0-20191215020915-b22d67c1ba0b h1:FosyBZYxY34Wul7O/MSKey3txpPYyCqVO5ZyceuQJEI=
|
github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940 h1:4r45xpDWB6ZMSMNJFMOjqrGHynW3DIBuR2H9j0ug+Mo=
|
||||||
github.com/zclconf/go-cty-debug v0.0.0-20191215020915-b22d67c1ba0b/go.mod h1:ZRKQfBXbGkpdV6QMzT3rU1kSTAnfu1dO8dPKjYprgj8=
|
github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940/go.mod h1:CmBdvvj3nqzfzJ6nTCIwDTPZ56aVGvDrmztiO5g3qrM=
|
||||||
github.com/zclconf/go-cty-yaml v1.0.3 h1:og/eOQ7lvA/WWhHGFETVWNduJM7Rjsv2RRpx1sdFMLc=
|
github.com/zclconf/go-cty-yaml v1.0.3 h1:og/eOQ7lvA/WWhHGFETVWNduJM7Rjsv2RRpx1sdFMLc=
|
||||||
github.com/zclconf/go-cty-yaml v1.0.3/go.mod h1:9YLUH4g7lOhVWqUbctnVlZ5KLpg7JAprQNgxSZ1Gyxs=
|
github.com/zclconf/go-cty-yaml v1.0.3/go.mod h1:9YLUH4g7lOhVWqUbctnVlZ5KLpg7JAprQNgxSZ1Gyxs=
|
||||||
go.etcd.io/etcd/api/v3 v3.5.4/go.mod h1:5GB2vv4A4AOn3yk7MftYGHkUfGtDHnEraIjym4dYz5A=
|
go.etcd.io/etcd/api/v3 v3.5.4/go.mod h1:5GB2vv4A4AOn3yk7MftYGHkUfGtDHnEraIjym4dYz5A=
|
||||||
@ -1181,7 +1177,6 @@ golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc=
|
|||||||
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||||
golang.org/x/net v0.0.0-20180530234432-1e491301e022/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20180530234432-1e491301e022/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-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-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-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
@ -16,9 +16,9 @@ import (
|
|||||||
"github.com/opentofu/opentofu/internal/lang"
|
"github.com/opentofu/opentofu/internal/lang"
|
||||||
|
|
||||||
"github.com/hashicorp/hcl/v2"
|
"github.com/hashicorp/hcl/v2"
|
||||||
|
"github.com/hashicorp/hcl/v2/gohcl"
|
||||||
"github.com/opentofu/opentofu/internal/encryption/keyprovider"
|
"github.com/opentofu/opentofu/internal/encryption/keyprovider"
|
||||||
"github.com/opentofu/opentofu/internal/encryption/registry"
|
"github.com/opentofu/opentofu/internal/encryption/registry"
|
||||||
"github.com/opentofu/opentofu/internal/gohcl"
|
|
||||||
"github.com/zclconf/go-cty/cty"
|
"github.com/zclconf/go-cty/cty"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -17,8 +17,8 @@ import (
|
|||||||
"github.com/davecgh/go-spew/spew"
|
"github.com/davecgh/go-spew/spew"
|
||||||
awsbase "github.com/hashicorp/aws-sdk-go-base/v2"
|
awsbase "github.com/hashicorp/aws-sdk-go-base/v2"
|
||||||
"github.com/hashicorp/hcl/v2"
|
"github.com/hashicorp/hcl/v2"
|
||||||
|
"github.com/hashicorp/hcl/v2/gohcl"
|
||||||
"github.com/hashicorp/hcl/v2/hclsyntax"
|
"github.com/hashicorp/hcl/v2/hclsyntax"
|
||||||
"github.com/opentofu/opentofu/internal/gohcl"
|
|
||||||
"github.com/opentofu/opentofu/internal/httpclient"
|
"github.com/opentofu/opentofu/internal/httpclient"
|
||||||
"github.com/opentofu/opentofu/version"
|
"github.com/opentofu/opentofu/version"
|
||||||
)
|
)
|
||||||
|
@ -1 +0,0 @@
|
|||||||
## This is a temporary fork of the github.com/hashicorp/hcl/gohcl/v2 package. It is in the process of being upstreamed
|
|
@ -1,441 +0,0 @@
|
|||||||
// Copyright (c) HashiCorp, Inc.
|
|
||||||
// SPDX-License-Identifier: MPL-2.0
|
|
||||||
|
|
||||||
package gohcl
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"reflect"
|
|
||||||
|
|
||||||
"github.com/zclconf/go-cty/cty"
|
|
||||||
|
|
||||||
"github.com/hashicorp/hcl/v2"
|
|
||||||
"github.com/zclconf/go-cty/cty/convert"
|
|
||||||
"github.com/zclconf/go-cty/cty/gocty"
|
|
||||||
)
|
|
||||||
|
|
||||||
// DecodeBody extracts the configuration within the given body into the given
|
|
||||||
// value. This value must be a non-nil pointer to either a struct or
|
|
||||||
// a map, where in the former case the configuration will be decoded using
|
|
||||||
// struct tags and in the latter case only attributes are allowed and their
|
|
||||||
// values are decoded into the map.
|
|
||||||
//
|
|
||||||
// The given EvalContext is used to resolve any variables or functions in
|
|
||||||
// expressions encountered while decoding. This may be nil to require only
|
|
||||||
// constant values, for simple applications that do not support variables or
|
|
||||||
// functions.
|
|
||||||
//
|
|
||||||
// The returned diagnostics should be inspected with its HasErrors method to
|
|
||||||
// determine if the populated value is valid and complete. If error diagnostics
|
|
||||||
// are returned then the given value may have been partially-populated but
|
|
||||||
// may still be accessed by a careful caller for static analysis and editor
|
|
||||||
// integration use-cases.
|
|
||||||
func DecodeBody(body hcl.Body, ctx *hcl.EvalContext, val interface{}) hcl.Diagnostics {
|
|
||||||
rv := reflect.ValueOf(val)
|
|
||||||
if rv.Kind() != reflect.Ptr {
|
|
||||||
panic(fmt.Sprintf("target value must be a pointer, not %s", rv.Type().String()))
|
|
||||||
}
|
|
||||||
|
|
||||||
return decodeBodyToValue(body, ctx, rv.Elem())
|
|
||||||
}
|
|
||||||
|
|
||||||
func decodeBodyToValue(body hcl.Body, ctx *hcl.EvalContext, val reflect.Value) hcl.Diagnostics {
|
|
||||||
et := val.Type()
|
|
||||||
switch et.Kind() {
|
|
||||||
case reflect.Struct:
|
|
||||||
return decodeBodyToStruct(body, ctx, val)
|
|
||||||
case reflect.Map:
|
|
||||||
return decodeBodyToMap(body, ctx, val)
|
|
||||||
default:
|
|
||||||
panic(fmt.Sprintf("target value must be pointer to struct or map, not %s", et.String()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func decodeBodyToStruct(body hcl.Body, ctx *hcl.EvalContext, val reflect.Value) hcl.Diagnostics {
|
|
||||||
schema, partial := ImpliedBodySchema(val.Interface())
|
|
||||||
|
|
||||||
var content *hcl.BodyContent
|
|
||||||
var leftovers hcl.Body
|
|
||||||
var diags hcl.Diagnostics
|
|
||||||
if partial {
|
|
||||||
content, leftovers, diags = body.PartialContent(schema)
|
|
||||||
} else {
|
|
||||||
content, diags = body.Content(schema)
|
|
||||||
}
|
|
||||||
if content == nil {
|
|
||||||
return diags
|
|
||||||
}
|
|
||||||
|
|
||||||
tags := getFieldTags(val.Type())
|
|
||||||
|
|
||||||
if tags.Body != nil {
|
|
||||||
fieldIdx := *tags.Body
|
|
||||||
field := val.Type().Field(fieldIdx)
|
|
||||||
fieldV := val.Field(fieldIdx)
|
|
||||||
switch {
|
|
||||||
case bodyType.AssignableTo(field.Type):
|
|
||||||
fieldV.Set(reflect.ValueOf(body))
|
|
||||||
|
|
||||||
default:
|
|
||||||
diags = append(diags, decodeBodyToValue(body, ctx, fieldV)...)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if tags.Remain != nil {
|
|
||||||
fieldIdx := *tags.Remain
|
|
||||||
field := val.Type().Field(fieldIdx)
|
|
||||||
fieldV := val.Field(fieldIdx)
|
|
||||||
switch {
|
|
||||||
case bodyType.AssignableTo(field.Type):
|
|
||||||
fieldV.Set(reflect.ValueOf(leftovers))
|
|
||||||
case attrsType.AssignableTo(field.Type):
|
|
||||||
attrs, attrsDiags := leftovers.JustAttributes()
|
|
||||||
if len(attrsDiags) > 0 {
|
|
||||||
diags = append(diags, attrsDiags...)
|
|
||||||
}
|
|
||||||
fieldV.Set(reflect.ValueOf(attrs))
|
|
||||||
default:
|
|
||||||
diags = append(diags, decodeBodyToValue(leftovers, ctx, fieldV)...)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for name, fieldIdx := range tags.Attributes {
|
|
||||||
attr := content.Attributes[name]
|
|
||||||
field := val.Type().Field(fieldIdx)
|
|
||||||
fieldV := val.Field(fieldIdx)
|
|
||||||
|
|
||||||
if attr == nil {
|
|
||||||
if !exprType.AssignableTo(field.Type) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// As a special case, if the target is of type hcl.Expression then
|
|
||||||
// we'll assign an actual expression that evalues to a cty null,
|
|
||||||
// so the caller can deal with it within the cty realm rather
|
|
||||||
// than within the Go realm.
|
|
||||||
synthExpr := hcl.StaticExpr(cty.NullVal(cty.DynamicPseudoType), body.MissingItemRange())
|
|
||||||
fieldV.Set(reflect.ValueOf(synthExpr))
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
switch {
|
|
||||||
case attrType.AssignableTo(field.Type):
|
|
||||||
fieldV.Set(reflect.ValueOf(attr))
|
|
||||||
case exprType.AssignableTo(field.Type):
|
|
||||||
fieldV.Set(reflect.ValueOf(attr.Expr))
|
|
||||||
case field.Type.Kind() == reflect.Ptr && field.Type.Elem().Kind() == reflect.Struct:
|
|
||||||
// TODO might want to check for nil here
|
|
||||||
rn := reflect.New(field.Type.Elem())
|
|
||||||
fieldV.Set(rn)
|
|
||||||
diags = append(diags, DecodeExpression(
|
|
||||||
attr.Expr, ctx, fieldV.Interface(),
|
|
||||||
)...)
|
|
||||||
default:
|
|
||||||
diags = append(diags, DecodeExpression(
|
|
||||||
attr.Expr, ctx, fieldV.Addr().Interface(),
|
|
||||||
)...)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
blocksByType := content.Blocks.ByType()
|
|
||||||
|
|
||||||
for typeName, fieldIdx := range tags.Blocks {
|
|
||||||
blocks := blocksByType[typeName]
|
|
||||||
field := val.Type().Field(fieldIdx)
|
|
||||||
|
|
||||||
ty := field.Type
|
|
||||||
isSlice := false
|
|
||||||
isPtr := false
|
|
||||||
if ty.Kind() == reflect.Slice {
|
|
||||||
isSlice = true
|
|
||||||
ty = ty.Elem()
|
|
||||||
}
|
|
||||||
if ty.Kind() == reflect.Ptr {
|
|
||||||
isPtr = true
|
|
||||||
ty = ty.Elem()
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(blocks) > 1 && !isSlice {
|
|
||||||
diags = append(diags, &hcl.Diagnostic{
|
|
||||||
Severity: hcl.DiagError,
|
|
||||||
Summary: fmt.Sprintf("Duplicate %s block", typeName),
|
|
||||||
Detail: fmt.Sprintf(
|
|
||||||
"Only one %s block is allowed. Another was defined at %s.",
|
|
||||||
typeName, blocks[0].DefRange.String(),
|
|
||||||
),
|
|
||||||
Subject: &blocks[1].DefRange,
|
|
||||||
})
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(blocks) == 0 {
|
|
||||||
if isSlice || isPtr {
|
|
||||||
if val.Field(fieldIdx).IsNil() {
|
|
||||||
val.Field(fieldIdx).Set(reflect.Zero(field.Type))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
diags = append(diags, &hcl.Diagnostic{
|
|
||||||
Severity: hcl.DiagError,
|
|
||||||
Summary: fmt.Sprintf("Missing %s block", typeName),
|
|
||||||
Detail: fmt.Sprintf("A %s block is required.", typeName),
|
|
||||||
Subject: body.MissingItemRange().Ptr(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
switch {
|
|
||||||
|
|
||||||
case isSlice:
|
|
||||||
elemType := ty
|
|
||||||
if isPtr {
|
|
||||||
elemType = reflect.PtrTo(ty)
|
|
||||||
}
|
|
||||||
sli := val.Field(fieldIdx)
|
|
||||||
if sli.IsNil() {
|
|
||||||
sli = reflect.MakeSlice(reflect.SliceOf(elemType), len(blocks), len(blocks))
|
|
||||||
}
|
|
||||||
|
|
||||||
for i, block := range blocks {
|
|
||||||
if isPtr {
|
|
||||||
if i >= sli.Len() {
|
|
||||||
sli = reflect.Append(sli, reflect.New(ty))
|
|
||||||
}
|
|
||||||
v := sli.Index(i)
|
|
||||||
if v.IsNil() {
|
|
||||||
v = reflect.New(ty)
|
|
||||||
}
|
|
||||||
diags = append(diags, decodeBlockToValue(block, ctx, v.Elem())...)
|
|
||||||
sli.Index(i).Set(v)
|
|
||||||
} else {
|
|
||||||
if i >= sli.Len() {
|
|
||||||
sli = reflect.Append(sli, reflect.Indirect(reflect.New(ty)))
|
|
||||||
}
|
|
||||||
diags = append(diags, decodeBlockToValue(block, ctx, sli.Index(i))...)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if sli.Len() > len(blocks) {
|
|
||||||
sli.SetLen(len(blocks))
|
|
||||||
}
|
|
||||||
|
|
||||||
val.Field(fieldIdx).Set(sli)
|
|
||||||
|
|
||||||
default:
|
|
||||||
block := blocks[0]
|
|
||||||
if isPtr {
|
|
||||||
v := val.Field(fieldIdx)
|
|
||||||
if v.IsNil() {
|
|
||||||
v = reflect.New(ty)
|
|
||||||
}
|
|
||||||
diags = append(diags, decodeBlockToValue(block, ctx, v.Elem())...)
|
|
||||||
val.Field(fieldIdx).Set(v)
|
|
||||||
} else {
|
|
||||||
diags = append(diags, decodeBlockToValue(block, ctx, val.Field(fieldIdx))...)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
return diags
|
|
||||||
}
|
|
||||||
|
|
||||||
func decodeBodyToMap(body hcl.Body, ctx *hcl.EvalContext, v reflect.Value) hcl.Diagnostics {
|
|
||||||
attrs, diags := body.JustAttributes()
|
|
||||||
if attrs == nil {
|
|
||||||
return diags
|
|
||||||
}
|
|
||||||
|
|
||||||
mv := reflect.MakeMap(v.Type())
|
|
||||||
|
|
||||||
for k, attr := range attrs {
|
|
||||||
switch {
|
|
||||||
case attrType.AssignableTo(v.Type().Elem()):
|
|
||||||
mv.SetMapIndex(reflect.ValueOf(k), reflect.ValueOf(attr))
|
|
||||||
case exprType.AssignableTo(v.Type().Elem()):
|
|
||||||
mv.SetMapIndex(reflect.ValueOf(k), reflect.ValueOf(attr.Expr))
|
|
||||||
default:
|
|
||||||
ev := reflect.New(v.Type().Elem())
|
|
||||||
diags = append(diags, DecodeExpression(attr.Expr, ctx, ev.Interface())...)
|
|
||||||
mv.SetMapIndex(reflect.ValueOf(k), ev.Elem())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
v.Set(mv)
|
|
||||||
|
|
||||||
return diags
|
|
||||||
}
|
|
||||||
|
|
||||||
func decodeBlockToValue(block *hcl.Block, ctx *hcl.EvalContext, v reflect.Value) hcl.Diagnostics {
|
|
||||||
diags := decodeBodyToValue(block.Body, ctx, v)
|
|
||||||
|
|
||||||
if len(block.Labels) > 0 {
|
|
||||||
blockTags := getFieldTags(v.Type())
|
|
||||||
for li, lv := range block.Labels {
|
|
||||||
lfieldIdx := blockTags.Labels[li].FieldIndex
|
|
||||||
v.Field(lfieldIdx).Set(reflect.ValueOf(lv))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return diags
|
|
||||||
}
|
|
||||||
|
|
||||||
// DecodeExpression extracts the value of the given expression into the given
|
|
||||||
// value. This value must be something that gocty is able to decode into,
|
|
||||||
// since the final decoding is delegated to that package. If a reference to
|
|
||||||
// a struct is provided which contains gohcl tags, it will be decoded using
|
|
||||||
// the attr and optional tags.
|
|
||||||
//
|
|
||||||
// The given EvalContext is used to resolve any variables or functions in
|
|
||||||
// expressions encountered while decoding. This may be nil to require only
|
|
||||||
// constant values, for simple applications that do not support variables or
|
|
||||||
// functions.
|
|
||||||
//
|
|
||||||
// The returned diagnostics should be inspected with its HasErrors method to
|
|
||||||
// determine if the populated value is valid and complete. If error diagnostics
|
|
||||||
// are returned then the given value may have been partially-populated but
|
|
||||||
// may still be accessed by a careful caller for static analysis and editor
|
|
||||||
// integration use-cases.
|
|
||||||
func DecodeExpression(expr hcl.Expression, ctx *hcl.EvalContext, val interface{}) hcl.Diagnostics {
|
|
||||||
srcVal, diags := expr.Value(ctx)
|
|
||||||
if diags.HasErrors() {
|
|
||||||
return diags
|
|
||||||
}
|
|
||||||
|
|
||||||
return append(diags, DecodeValue(srcVal, expr.StartRange(), expr.Range(), val)...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// DecodeValue extracts the given value into the provided target.
|
|
||||||
// This value must be something that gocty is able to decode into,
|
|
||||||
// since the final decoding is delegated to that package. If a reference to
|
|
||||||
// a struct is provided which contains gohcl tags, it will be decoded using
|
|
||||||
// the attr and optional tags.
|
|
||||||
//
|
|
||||||
// The returned diagnostics should be inspected with its HasErrors method to
|
|
||||||
// determine if the populated value is valid and complete. If error diagnostics
|
|
||||||
// are returned then the given value may have been partially-populated but
|
|
||||||
// may still be accessed by a careful caller for static analysis and editor
|
|
||||||
// integration use-cases.
|
|
||||||
func DecodeValue(srcVal cty.Value, subject hcl.Range, context hcl.Range, val interface{}) hcl.Diagnostics {
|
|
||||||
rv := reflect.ValueOf(val)
|
|
||||||
if rv.Type().Kind() == reflect.Ptr && rv.Type().Elem().Kind() == reflect.Struct && hasFieldTags(rv.Elem().Type()) {
|
|
||||||
attrs := make(hcl.Attributes)
|
|
||||||
for k, v := range srcVal.AsValueMap() {
|
|
||||||
attrs[k] = &hcl.Attribute{
|
|
||||||
Name: k,
|
|
||||||
Expr: hcl.StaticExpr(v, context),
|
|
||||||
Range: subject,
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
return decodeBodyToStruct(synthBody{
|
|
||||||
attrs: attrs,
|
|
||||||
subject: subject,
|
|
||||||
context: context,
|
|
||||||
}, nil, rv.Elem())
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
convTy, err := gocty.ImpliedType(val)
|
|
||||||
if err != nil {
|
|
||||||
panic(fmt.Sprintf("unsuitable DecodeExpression target: %s", err))
|
|
||||||
}
|
|
||||||
|
|
||||||
var diags hcl.Diagnostics
|
|
||||||
|
|
||||||
srcVal, err = convert.Convert(srcVal, convTy)
|
|
||||||
if err != nil {
|
|
||||||
diags = append(diags, &hcl.Diagnostic{
|
|
||||||
Severity: hcl.DiagError,
|
|
||||||
Summary: "Unsuitable value type",
|
|
||||||
Detail: fmt.Sprintf("Unsuitable value: %s", err.Error()),
|
|
||||||
Subject: subject.Ptr(),
|
|
||||||
Context: context.Ptr(),
|
|
||||||
})
|
|
||||||
return diags
|
|
||||||
}
|
|
||||||
|
|
||||||
err = gocty.FromCtyValue(srcVal, val)
|
|
||||||
if err != nil {
|
|
||||||
diags = append(diags, &hcl.Diagnostic{
|
|
||||||
Severity: hcl.DiagError,
|
|
||||||
Summary: "Unsuitable value type",
|
|
||||||
Detail: fmt.Sprintf("Unsuitable value: %s", err.Error()),
|
|
||||||
Subject: subject.Ptr(),
|
|
||||||
Context: context.Ptr(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return diags
|
|
||||||
}
|
|
||||||
|
|
||||||
type synthBody struct {
|
|
||||||
attrs hcl.Attributes
|
|
||||||
subject hcl.Range
|
|
||||||
context hcl.Range
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s synthBody) Content(schema *hcl.BodySchema) (*hcl.BodyContent, hcl.Diagnostics) {
|
|
||||||
body, partial, diags := s.PartialContent(schema)
|
|
||||||
|
|
||||||
attrs, _ := partial.JustAttributes()
|
|
||||||
for name := range attrs {
|
|
||||||
diags = append(diags, &hcl.Diagnostic{
|
|
||||||
Severity: hcl.DiagError,
|
|
||||||
Summary: "Unsupported argument",
|
|
||||||
Detail: fmt.Sprintf("An argument named %q is not expected here.", name),
|
|
||||||
Subject: s.subject.Ptr(),
|
|
||||||
Context: s.context.Ptr(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return body, diags
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s synthBody) PartialContent(schema *hcl.BodySchema) (*hcl.BodyContent, hcl.Body, hcl.Diagnostics) {
|
|
||||||
var diags hcl.Diagnostics
|
|
||||||
|
|
||||||
for _, block := range schema.Blocks {
|
|
||||||
panic("hcl block tags are not allowed in attribute structs: " + block.Type)
|
|
||||||
}
|
|
||||||
|
|
||||||
attrs := make(hcl.Attributes)
|
|
||||||
remainder := make(hcl.Attributes)
|
|
||||||
|
|
||||||
for _, attr := range schema.Attributes {
|
|
||||||
v, ok := s.attrs[attr.Name]
|
|
||||||
if !ok {
|
|
||||||
if attr.Required {
|
|
||||||
diags = append(diags, &hcl.Diagnostic{
|
|
||||||
Severity: hcl.DiagError,
|
|
||||||
Summary: "Missing required argument",
|
|
||||||
Detail: fmt.Sprintf("The argument %q is required, but no definition was found.", attr.Name),
|
|
||||||
Subject: s.subject.Ptr(),
|
|
||||||
Context: s.context.Ptr(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
attrs[attr.Name] = v
|
|
||||||
}
|
|
||||||
|
|
||||||
for k, v := range s.attrs {
|
|
||||||
if _, ok := attrs[k]; !ok {
|
|
||||||
remainder[k] = v
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return &hcl.BodyContent{
|
|
||||||
Attributes: attrs,
|
|
||||||
MissingItemRange: s.context,
|
|
||||||
}, synthBody{attrs: remainder}, diags
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s synthBody) JustAttributes() (hcl.Attributes, hcl.Diagnostics) {
|
|
||||||
return s.attrs, nil
|
|
||||||
}
|
|
||||||
func (s synthBody) MissingItemRange() hcl.Range {
|
|
||||||
return s.context
|
|
||||||
}
|
|
@ -1,813 +0,0 @@
|
|||||||
// Copyright (c) HashiCorp, Inc.
|
|
||||||
// SPDX-License-Identifier: MPL-2.0
|
|
||||||
|
|
||||||
package gohcl
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"reflect"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/davecgh/go-spew/spew"
|
|
||||||
"github.com/hashicorp/hcl/v2"
|
|
||||||
hclJSON "github.com/hashicorp/hcl/v2/json"
|
|
||||||
"github.com/zclconf/go-cty/cty"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestDecodeBody(t *testing.T) {
|
|
||||||
deepEquals := func(other interface{}) func(v interface{}) bool {
|
|
||||||
return func(v interface{}) bool {
|
|
||||||
return reflect.DeepEqual(v, other)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type withNameExpression struct {
|
|
||||||
Name hcl.Expression `hcl:"name"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type withTwoAttributes struct {
|
|
||||||
A string `hcl:"a,optional"`
|
|
||||||
B string `hcl:"b,optional"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type withNestedBlock struct {
|
|
||||||
Plain string `hcl:"plain,optional"`
|
|
||||||
Nested *withTwoAttributes `hcl:"nested,block"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type withListofNestedBlocks struct {
|
|
||||||
Nested []*withTwoAttributes `hcl:"nested,block"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type withListofNestedBlocksNoPointers struct {
|
|
||||||
Nested []withTwoAttributes `hcl:"nested,block"`
|
|
||||||
}
|
|
||||||
|
|
||||||
tests := []struct {
|
|
||||||
Body map[string]interface{}
|
|
||||||
Target func() interface{}
|
|
||||||
Check func(v interface{}) bool
|
|
||||||
DiagCount int
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
map[string]interface{}{},
|
|
||||||
makeInstantiateType(struct{}{}),
|
|
||||||
deepEquals(struct{}{}),
|
|
||||||
0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
map[string]interface{}{},
|
|
||||||
makeInstantiateType(struct {
|
|
||||||
Name string `hcl:"name"`
|
|
||||||
}{}),
|
|
||||||
deepEquals(struct {
|
|
||||||
Name string `hcl:"name"`
|
|
||||||
}{}),
|
|
||||||
1, // name is required
|
|
||||||
},
|
|
||||||
{
|
|
||||||
map[string]interface{}{},
|
|
||||||
makeInstantiateType(struct {
|
|
||||||
Name *string `hcl:"name"`
|
|
||||||
}{}),
|
|
||||||
deepEquals(struct {
|
|
||||||
Name *string `hcl:"name"`
|
|
||||||
}{}),
|
|
||||||
0,
|
|
||||||
}, // name nil
|
|
||||||
{
|
|
||||||
map[string]interface{}{},
|
|
||||||
makeInstantiateType(struct {
|
|
||||||
Name string `hcl:"name,optional"`
|
|
||||||
}{}),
|
|
||||||
deepEquals(struct {
|
|
||||||
Name string `hcl:"name,optional"`
|
|
||||||
}{}),
|
|
||||||
0,
|
|
||||||
}, // name optional
|
|
||||||
{
|
|
||||||
map[string]interface{}{},
|
|
||||||
makeInstantiateType(withNameExpression{}),
|
|
||||||
func(v interface{}) bool {
|
|
||||||
if v == nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
wne, valid := v.(withNameExpression)
|
|
||||||
if !valid {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if wne.Name == nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
nameVal, _ := wne.Name.Value(nil)
|
|
||||||
if !nameVal.IsNull() {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
},
|
|
||||||
0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
map[string]interface{}{
|
|
||||||
"name": "Ermintrude",
|
|
||||||
},
|
|
||||||
makeInstantiateType(withNameExpression{}),
|
|
||||||
func(v interface{}) bool {
|
|
||||||
if v == nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
wne, valid := v.(withNameExpression)
|
|
||||||
if !valid {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if wne.Name == nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
nameVal, _ := wne.Name.Value(nil)
|
|
||||||
if !nameVal.Equals(cty.StringVal("Ermintrude")).True() {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
},
|
|
||||||
0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
map[string]interface{}{
|
|
||||||
"name": "Ermintrude",
|
|
||||||
},
|
|
||||||
makeInstantiateType(struct {
|
|
||||||
Name string `hcl:"name"`
|
|
||||||
}{}),
|
|
||||||
deepEquals(struct {
|
|
||||||
Name string `hcl:"name"`
|
|
||||||
}{"Ermintrude"}),
|
|
||||||
0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
map[string]interface{}{
|
|
||||||
"name": "Ermintrude",
|
|
||||||
"age": 23,
|
|
||||||
},
|
|
||||||
makeInstantiateType(struct {
|
|
||||||
Name string `hcl:"name"`
|
|
||||||
}{}),
|
|
||||||
deepEquals(struct {
|
|
||||||
Name string `hcl:"name"`
|
|
||||||
}{"Ermintrude"}),
|
|
||||||
1, // Extraneous "age" property
|
|
||||||
},
|
|
||||||
{
|
|
||||||
map[string]interface{}{
|
|
||||||
"name": "Ermintrude",
|
|
||||||
"age": 50,
|
|
||||||
},
|
|
||||||
makeInstantiateType(struct {
|
|
||||||
Name string `hcl:"name"`
|
|
||||||
Attrs hcl.Attributes `hcl:",remain"`
|
|
||||||
}{}),
|
|
||||||
func(gotI interface{}) bool {
|
|
||||||
got := gotI.(struct {
|
|
||||||
Name string `hcl:"name"`
|
|
||||||
Attrs hcl.Attributes `hcl:",remain"`
|
|
||||||
})
|
|
||||||
return got.Name == "Ermintrude" && len(got.Attrs) == 1 && got.Attrs["age"] != nil
|
|
||||||
},
|
|
||||||
0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
map[string]interface{}{
|
|
||||||
"name": "Ermintrude",
|
|
||||||
"age": 50,
|
|
||||||
},
|
|
||||||
makeInstantiateType(struct {
|
|
||||||
Name string `hcl:"name"`
|
|
||||||
Remain hcl.Body `hcl:",remain"`
|
|
||||||
}{}),
|
|
||||||
func(gotI interface{}) bool {
|
|
||||||
got := gotI.(struct {
|
|
||||||
Name string `hcl:"name"`
|
|
||||||
Remain hcl.Body `hcl:",remain"`
|
|
||||||
})
|
|
||||||
|
|
||||||
attrs, _ := got.Remain.JustAttributes()
|
|
||||||
|
|
||||||
return got.Name == "Ermintrude" && len(attrs) == 1 && attrs["age"] != nil
|
|
||||||
},
|
|
||||||
0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
map[string]interface{}{
|
|
||||||
"name": "Ermintrude",
|
|
||||||
"living": true,
|
|
||||||
},
|
|
||||||
makeInstantiateType(struct {
|
|
||||||
Name string `hcl:"name"`
|
|
||||||
Remain map[string]cty.Value `hcl:",remain"`
|
|
||||||
}{}),
|
|
||||||
deepEquals(struct {
|
|
||||||
Name string `hcl:"name"`
|
|
||||||
Remain map[string]cty.Value `hcl:",remain"`
|
|
||||||
}{
|
|
||||||
Name: "Ermintrude",
|
|
||||||
Remain: map[string]cty.Value{
|
|
||||||
"living": cty.True,
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
map[string]interface{}{
|
|
||||||
"name": "Ermintrude",
|
|
||||||
"age": 50,
|
|
||||||
},
|
|
||||||
makeInstantiateType(struct {
|
|
||||||
Name string `hcl:"name"`
|
|
||||||
Body hcl.Body `hcl:",body"`
|
|
||||||
Remain hcl.Body `hcl:",remain"`
|
|
||||||
}{}),
|
|
||||||
func(gotI interface{}) bool {
|
|
||||||
got := gotI.(struct {
|
|
||||||
Name string `hcl:"name"`
|
|
||||||
Body hcl.Body `hcl:",body"`
|
|
||||||
Remain hcl.Body `hcl:",remain"`
|
|
||||||
})
|
|
||||||
|
|
||||||
attrs, _ := got.Body.JustAttributes()
|
|
||||||
|
|
||||||
return got.Name == "Ermintrude" && len(attrs) == 2 &&
|
|
||||||
attrs["name"] != nil && attrs["age"] != nil
|
|
||||||
},
|
|
||||||
0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
map[string]interface{}{
|
|
||||||
"noodle": map[string]interface{}{},
|
|
||||||
},
|
|
||||||
makeInstantiateType(struct {
|
|
||||||
Noodle struct{} `hcl:"noodle,block"`
|
|
||||||
}{}),
|
|
||||||
func(gotI interface{}) bool {
|
|
||||||
// Generating no diagnostics is good enough for this one.
|
|
||||||
return true
|
|
||||||
},
|
|
||||||
0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
map[string]interface{}{
|
|
||||||
"noodle": []map[string]interface{}{{}},
|
|
||||||
},
|
|
||||||
makeInstantiateType(struct {
|
|
||||||
Noodle struct{} `hcl:"noodle,block"`
|
|
||||||
}{}),
|
|
||||||
func(gotI interface{}) bool {
|
|
||||||
// Generating no diagnostics is good enough for this one.
|
|
||||||
return true
|
|
||||||
},
|
|
||||||
0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
map[string]interface{}{
|
|
||||||
"noodle": []map[string]interface{}{{}, {}},
|
|
||||||
},
|
|
||||||
makeInstantiateType(struct {
|
|
||||||
Noodle struct{} `hcl:"noodle,block"`
|
|
||||||
}{}),
|
|
||||||
func(gotI interface{}) bool {
|
|
||||||
// Generating one diagnostic is good enough for this one.
|
|
||||||
return true
|
|
||||||
},
|
|
||||||
1,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
map[string]interface{}{},
|
|
||||||
makeInstantiateType(struct {
|
|
||||||
Noodle struct{} `hcl:"noodle,block"`
|
|
||||||
}{}),
|
|
||||||
func(gotI interface{}) bool {
|
|
||||||
// Generating one diagnostic is good enough for this one.
|
|
||||||
return true
|
|
||||||
},
|
|
||||||
1,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
map[string]interface{}{
|
|
||||||
"noodle": []map[string]interface{}{},
|
|
||||||
},
|
|
||||||
makeInstantiateType(struct {
|
|
||||||
Noodle struct{} `hcl:"noodle,block"`
|
|
||||||
}{}),
|
|
||||||
func(gotI interface{}) bool {
|
|
||||||
// Generating one diagnostic is good enough for this one.
|
|
||||||
return true
|
|
||||||
},
|
|
||||||
1,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
map[string]interface{}{
|
|
||||||
"noodle": map[string]interface{}{},
|
|
||||||
},
|
|
||||||
makeInstantiateType(struct {
|
|
||||||
Noodle *struct{} `hcl:"noodle,block"`
|
|
||||||
}{}),
|
|
||||||
func(gotI interface{}) bool {
|
|
||||||
return gotI.(struct {
|
|
||||||
Noodle *struct{} `hcl:"noodle,block"`
|
|
||||||
}).Noodle != nil
|
|
||||||
},
|
|
||||||
0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
map[string]interface{}{
|
|
||||||
"noodle": []map[string]interface{}{{}},
|
|
||||||
},
|
|
||||||
makeInstantiateType(struct {
|
|
||||||
Noodle *struct{} `hcl:"noodle,block"`
|
|
||||||
}{}),
|
|
||||||
func(gotI interface{}) bool {
|
|
||||||
return gotI.(struct {
|
|
||||||
Noodle *struct{} `hcl:"noodle,block"`
|
|
||||||
}).Noodle != nil
|
|
||||||
},
|
|
||||||
0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
map[string]interface{}{
|
|
||||||
"noodle": []map[string]interface{}{},
|
|
||||||
},
|
|
||||||
makeInstantiateType(struct {
|
|
||||||
Noodle *struct{} `hcl:"noodle,block"`
|
|
||||||
}{}),
|
|
||||||
func(gotI interface{}) bool {
|
|
||||||
return gotI.(struct {
|
|
||||||
Noodle *struct{} `hcl:"noodle,block"`
|
|
||||||
}).Noodle == nil
|
|
||||||
},
|
|
||||||
0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
map[string]interface{}{
|
|
||||||
"noodle": []map[string]interface{}{{}, {}},
|
|
||||||
},
|
|
||||||
makeInstantiateType(struct {
|
|
||||||
Noodle *struct{} `hcl:"noodle,block"`
|
|
||||||
}{}),
|
|
||||||
func(gotI interface{}) bool {
|
|
||||||
// Generating one diagnostic is good enough for this one.
|
|
||||||
return true
|
|
||||||
},
|
|
||||||
1,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
map[string]interface{}{
|
|
||||||
"noodle": []map[string]interface{}{},
|
|
||||||
},
|
|
||||||
makeInstantiateType(struct {
|
|
||||||
Noodle []struct{} `hcl:"noodle,block"`
|
|
||||||
}{}),
|
|
||||||
func(gotI interface{}) bool {
|
|
||||||
noodle := gotI.(struct {
|
|
||||||
Noodle []struct{} `hcl:"noodle,block"`
|
|
||||||
}).Noodle
|
|
||||||
return len(noodle) == 0
|
|
||||||
},
|
|
||||||
0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
map[string]interface{}{
|
|
||||||
"noodle": []map[string]interface{}{{}},
|
|
||||||
},
|
|
||||||
makeInstantiateType(struct {
|
|
||||||
Noodle []struct{} `hcl:"noodle,block"`
|
|
||||||
}{}),
|
|
||||||
func(gotI interface{}) bool {
|
|
||||||
noodle := gotI.(struct {
|
|
||||||
Noodle []struct{} `hcl:"noodle,block"`
|
|
||||||
}).Noodle
|
|
||||||
return len(noodle) == 1
|
|
||||||
},
|
|
||||||
0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
map[string]interface{}{
|
|
||||||
"noodle": []map[string]interface{}{{}, {}},
|
|
||||||
},
|
|
||||||
makeInstantiateType(struct {
|
|
||||||
Noodle []struct{} `hcl:"noodle,block"`
|
|
||||||
}{}),
|
|
||||||
func(gotI interface{}) bool {
|
|
||||||
noodle := gotI.(struct {
|
|
||||||
Noodle []struct{} `hcl:"noodle,block"`
|
|
||||||
}).Noodle
|
|
||||||
return len(noodle) == 2
|
|
||||||
},
|
|
||||||
0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
map[string]interface{}{
|
|
||||||
"noodle": map[string]interface{}{},
|
|
||||||
},
|
|
||||||
makeInstantiateType(struct {
|
|
||||||
Noodle struct {
|
|
||||||
Name string `hcl:"name,label"`
|
|
||||||
} `hcl:"noodle,block"`
|
|
||||||
}{}),
|
|
||||||
func(gotI interface{}) bool {
|
|
||||||
// Generating two diagnostics is good enough for this one.
|
|
||||||
// (one for the missing noodle block and the other for
|
|
||||||
// the JSON serialization detecting the missing level of
|
|
||||||
// heirarchy for the label.)
|
|
||||||
return true
|
|
||||||
},
|
|
||||||
2,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
map[string]interface{}{
|
|
||||||
"noodle": map[string]interface{}{
|
|
||||||
"foo_foo": map[string]interface{}{},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
makeInstantiateType(struct {
|
|
||||||
Noodle struct {
|
|
||||||
Name string `hcl:"name,label"`
|
|
||||||
} `hcl:"noodle,block"`
|
|
||||||
}{}),
|
|
||||||
func(gotI interface{}) bool {
|
|
||||||
noodle := gotI.(struct {
|
|
||||||
Noodle struct {
|
|
||||||
Name string `hcl:"name,label"`
|
|
||||||
} `hcl:"noodle,block"`
|
|
||||||
}).Noodle
|
|
||||||
return noodle.Name == "foo_foo"
|
|
||||||
},
|
|
||||||
0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
map[string]interface{}{
|
|
||||||
"noodle": map[string]interface{}{
|
|
||||||
"foo_foo": map[string]interface{}{},
|
|
||||||
"bar_baz": map[string]interface{}{},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
makeInstantiateType(struct {
|
|
||||||
Noodle struct {
|
|
||||||
Name string `hcl:"name,label"`
|
|
||||||
} `hcl:"noodle,block"`
|
|
||||||
}{}),
|
|
||||||
func(gotI interface{}) bool {
|
|
||||||
// One diagnostic is enough for this one.
|
|
||||||
return true
|
|
||||||
},
|
|
||||||
1,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
map[string]interface{}{
|
|
||||||
"noodle": map[string]interface{}{
|
|
||||||
"foo_foo": map[string]interface{}{},
|
|
||||||
"bar_baz": map[string]interface{}{},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
makeInstantiateType(struct {
|
|
||||||
Noodles []struct {
|
|
||||||
Name string `hcl:"name,label"`
|
|
||||||
} `hcl:"noodle,block"`
|
|
||||||
}{}),
|
|
||||||
func(gotI interface{}) bool {
|
|
||||||
noodles := gotI.(struct {
|
|
||||||
Noodles []struct {
|
|
||||||
Name string `hcl:"name,label"`
|
|
||||||
} `hcl:"noodle,block"`
|
|
||||||
}).Noodles
|
|
||||||
return len(noodles) == 2 && (noodles[0].Name == "foo_foo" || noodles[0].Name == "bar_baz") && (noodles[1].Name == "foo_foo" || noodles[1].Name == "bar_baz") && noodles[0].Name != noodles[1].Name
|
|
||||||
},
|
|
||||||
0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
map[string]interface{}{
|
|
||||||
"noodle": map[string]interface{}{
|
|
||||||
"foo_foo": map[string]interface{}{
|
|
||||||
"type": "rice",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
makeInstantiateType(struct {
|
|
||||||
Noodle struct {
|
|
||||||
Name string `hcl:"name,label"`
|
|
||||||
Type string `hcl:"type"`
|
|
||||||
} `hcl:"noodle,block"`
|
|
||||||
}{}),
|
|
||||||
func(gotI interface{}) bool {
|
|
||||||
noodle := gotI.(struct {
|
|
||||||
Noodle struct {
|
|
||||||
Name string `hcl:"name,label"`
|
|
||||||
Type string `hcl:"type"`
|
|
||||||
} `hcl:"noodle,block"`
|
|
||||||
}).Noodle
|
|
||||||
return noodle.Name == "foo_foo" && noodle.Type == "rice"
|
|
||||||
},
|
|
||||||
0,
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
map[string]interface{}{
|
|
||||||
"name": "Ermintrude",
|
|
||||||
"age": 34,
|
|
||||||
},
|
|
||||||
makeInstantiateType(map[string]string(nil)),
|
|
||||||
deepEquals(map[string]string{
|
|
||||||
"name": "Ermintrude",
|
|
||||||
"age": "34",
|
|
||||||
}),
|
|
||||||
0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
map[string]interface{}{
|
|
||||||
"name": "Ermintrude",
|
|
||||||
"age": 89,
|
|
||||||
},
|
|
||||||
makeInstantiateType(map[string]*hcl.Attribute(nil)),
|
|
||||||
func(gotI interface{}) bool {
|
|
||||||
got := gotI.(map[string]*hcl.Attribute)
|
|
||||||
return len(got) == 2 && got["name"] != nil && got["age"] != nil
|
|
||||||
},
|
|
||||||
0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
map[string]interface{}{
|
|
||||||
"name": "Ermintrude",
|
|
||||||
"age": 13,
|
|
||||||
},
|
|
||||||
makeInstantiateType(map[string]hcl.Expression(nil)),
|
|
||||||
func(gotI interface{}) bool {
|
|
||||||
got := gotI.(map[string]hcl.Expression)
|
|
||||||
return len(got) == 2 && got["name"] != nil && got["age"] != nil
|
|
||||||
},
|
|
||||||
0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
map[string]interface{}{
|
|
||||||
"name": "Ermintrude",
|
|
||||||
"living": true,
|
|
||||||
},
|
|
||||||
makeInstantiateType(map[string]cty.Value(nil)),
|
|
||||||
deepEquals(map[string]cty.Value{
|
|
||||||
"name": cty.StringVal("Ermintrude"),
|
|
||||||
"living": cty.True,
|
|
||||||
}),
|
|
||||||
0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
// Retain "nested" block while decoding
|
|
||||||
map[string]interface{}{
|
|
||||||
"plain": "foo",
|
|
||||||
},
|
|
||||||
func() interface{} {
|
|
||||||
return &withNestedBlock{
|
|
||||||
Plain: "bar",
|
|
||||||
Nested: &withTwoAttributes{
|
|
||||||
A: "bar",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
},
|
|
||||||
func(gotI interface{}) bool {
|
|
||||||
foo := gotI.(withNestedBlock)
|
|
||||||
return foo.Plain == "foo" && foo.Nested != nil && foo.Nested.A == "bar"
|
|
||||||
},
|
|
||||||
0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
// Retain values in "nested" block while decoding
|
|
||||||
map[string]interface{}{
|
|
||||||
"nested": map[string]interface{}{
|
|
||||||
"a": "foo",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
func() interface{} {
|
|
||||||
return &withNestedBlock{
|
|
||||||
Nested: &withTwoAttributes{
|
|
||||||
B: "bar",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
},
|
|
||||||
func(gotI interface{}) bool {
|
|
||||||
foo := gotI.(withNestedBlock)
|
|
||||||
return foo.Nested.A == "foo" && foo.Nested.B == "bar"
|
|
||||||
},
|
|
||||||
0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
// Retain values in "nested" block list while decoding
|
|
||||||
map[string]interface{}{
|
|
||||||
"nested": []map[string]interface{}{
|
|
||||||
{
|
|
||||||
"a": "foo",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
func() interface{} {
|
|
||||||
return &withListofNestedBlocks{
|
|
||||||
Nested: []*withTwoAttributes{
|
|
||||||
&withTwoAttributes{
|
|
||||||
B: "bar",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
},
|
|
||||||
func(gotI interface{}) bool {
|
|
||||||
n := gotI.(withListofNestedBlocks)
|
|
||||||
return n.Nested[0].A == "foo" && n.Nested[0].B == "bar"
|
|
||||||
},
|
|
||||||
0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
// Remove additional elements from the list while decoding nested blocks
|
|
||||||
map[string]interface{}{
|
|
||||||
"nested": []map[string]interface{}{
|
|
||||||
{
|
|
||||||
"a": "foo",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
func() interface{} {
|
|
||||||
return &withListofNestedBlocks{
|
|
||||||
Nested: []*withTwoAttributes{
|
|
||||||
&withTwoAttributes{
|
|
||||||
B: "bar",
|
|
||||||
},
|
|
||||||
&withTwoAttributes{
|
|
||||||
B: "bar",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
},
|
|
||||||
func(gotI interface{}) bool {
|
|
||||||
n := gotI.(withListofNestedBlocks)
|
|
||||||
return len(n.Nested) == 1
|
|
||||||
},
|
|
||||||
0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
// Make sure decoding value slices works the same as pointer slices.
|
|
||||||
map[string]interface{}{
|
|
||||||
"nested": []map[string]interface{}{
|
|
||||||
{
|
|
||||||
"b": "bar",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"b": "baz",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
func() interface{} {
|
|
||||||
return &withListofNestedBlocksNoPointers{
|
|
||||||
Nested: []withTwoAttributes{
|
|
||||||
{
|
|
||||||
B: "foo",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
},
|
|
||||||
func(gotI interface{}) bool {
|
|
||||||
n := gotI.(withListofNestedBlocksNoPointers)
|
|
||||||
return n.Nested[0].B == "bar" && len(n.Nested) == 2
|
|
||||||
},
|
|
||||||
0,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for i, test := range tests {
|
|
||||||
// For convenience here we're going to use the JSON parser
|
|
||||||
// to process the given body.
|
|
||||||
buf, err := json.Marshal(test.Body)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("error JSON-encoding body for test %d: %s", i, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
t.Run(string(buf), func(t *testing.T) {
|
|
||||||
file, diags := hclJSON.Parse(buf, "test.json")
|
|
||||||
if len(diags) != 0 {
|
|
||||||
t.Fatalf("diagnostics while parsing: %s", diags.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
targetVal := reflect.ValueOf(test.Target())
|
|
||||||
|
|
||||||
diags = DecodeBody(file.Body, nil, targetVal.Interface())
|
|
||||||
if len(diags) != test.DiagCount {
|
|
||||||
t.Errorf("wrong number of diagnostics %d; want %d", len(diags), test.DiagCount)
|
|
||||||
for _, diag := range diags {
|
|
||||||
t.Logf(" - %s", diag.Error())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
got := targetVal.Elem().Interface()
|
|
||||||
if !test.Check(got) {
|
|
||||||
t.Errorf("wrong result\ngot: %s", spew.Sdump(got))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDecodeExpression(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
Value cty.Value
|
|
||||||
Target interface{}
|
|
||||||
Want interface{}
|
|
||||||
DiagCount int
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
cty.StringVal("hello"),
|
|
||||||
"",
|
|
||||||
"hello",
|
|
||||||
0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
cty.StringVal("hello"),
|
|
||||||
cty.NilVal,
|
|
||||||
cty.StringVal("hello"),
|
|
||||||
0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
cty.NumberIntVal(2),
|
|
||||||
"",
|
|
||||||
"2",
|
|
||||||
0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
cty.StringVal("true"),
|
|
||||||
false,
|
|
||||||
true,
|
|
||||||
0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
cty.NullVal(cty.String),
|
|
||||||
"",
|
|
||||||
"",
|
|
||||||
1, // null value is not allowed
|
|
||||||
},
|
|
||||||
{
|
|
||||||
cty.UnknownVal(cty.String),
|
|
||||||
"",
|
|
||||||
"",
|
|
||||||
1, // value must be known
|
|
||||||
},
|
|
||||||
{
|
|
||||||
cty.ListVal([]cty.Value{cty.True}),
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
1, // bool required
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for i, test := range tests {
|
|
||||||
t.Run(fmt.Sprintf("%02d", i), func(t *testing.T) {
|
|
||||||
expr := &fixedExpression{test.Value}
|
|
||||||
|
|
||||||
targetVal := reflect.New(reflect.TypeOf(test.Target))
|
|
||||||
|
|
||||||
diags := DecodeExpression(expr, nil, targetVal.Interface())
|
|
||||||
if len(diags) != test.DiagCount {
|
|
||||||
t.Errorf("wrong number of diagnostics %d; want %d", len(diags), test.DiagCount)
|
|
||||||
for _, diag := range diags {
|
|
||||||
t.Logf(" - %s", diag.Error())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
got := targetVal.Elem().Interface()
|
|
||||||
if !reflect.DeepEqual(got, test.Want) {
|
|
||||||
t.Errorf("wrong result\ngot: %#v\nwant: %#v", got, test.Want)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type fixedExpression struct {
|
|
||||||
val cty.Value
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *fixedExpression) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) {
|
|
||||||
return e.val, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *fixedExpression) Range() (r hcl.Range) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
func (e *fixedExpression) StartRange() (r hcl.Range) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *fixedExpression) Variables() []hcl.Traversal {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func makeInstantiateType(target interface{}) func() interface{} {
|
|
||||||
return func() interface{} {
|
|
||||||
return reflect.New(reflect.TypeOf(target)).Interface()
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,65 +0,0 @@
|
|||||||
// Copyright (c) HashiCorp, Inc.
|
|
||||||
// SPDX-License-Identifier: MPL-2.0
|
|
||||||
|
|
||||||
// Package gohcl allows decoding HCL configurations into Go data structures.
|
|
||||||
//
|
|
||||||
// It provides a convenient and concise way of describing the schema for
|
|
||||||
// configuration and then accessing the resulting data via native Go
|
|
||||||
// types.
|
|
||||||
//
|
|
||||||
// A struct field tag scheme is used, similar to other decoding and
|
|
||||||
// unmarshalling libraries. The tags are formatted as in the following example:
|
|
||||||
//
|
|
||||||
// ThingType string `hcl:"thing_type,attr"`
|
|
||||||
//
|
|
||||||
// Within each tag there are two comma-separated tokens. The first is the
|
|
||||||
// name of the corresponding construct in configuration, while the second
|
|
||||||
// is a keyword giving the kind of construct expected. The following
|
|
||||||
// kind keywords are supported:
|
|
||||||
//
|
|
||||||
// attr (the default) indicates that the value is to be populated from an attribute
|
|
||||||
// block indicates that the value is to populated from a block
|
|
||||||
// label indicates that the value is to populated from a block label
|
|
||||||
// optional is the same as attr, but the field is optional
|
|
||||||
// remain indicates that the value is to be populated from the remaining body after populating other fields
|
|
||||||
//
|
|
||||||
// "attr" fields may either be of type *hcl.Expression, in which case the raw
|
|
||||||
// expression is assigned, or of any type accepted by gocty, in which case
|
|
||||||
// gocty will be used to assign the value to a native Go type.
|
|
||||||
//
|
|
||||||
// "block" fields may be a struct that recursively uses the same tags, or a
|
|
||||||
// slice of such structs, in which case multiple blocks of the corresponding
|
|
||||||
// type are decoded into the slice.
|
|
||||||
//
|
|
||||||
// "body" can be placed on a single field of type hcl.Body to capture
|
|
||||||
// the full hcl.Body that was decoded for a block. This does not allow leftover
|
|
||||||
// values like "remain", so a decoding error will still be returned if leftover
|
|
||||||
// fields are given. If you want to capture the decoding body PLUS leftover
|
|
||||||
// fields, you must specify a "remain" field as well to prevent errors. The
|
|
||||||
// body field and the remain field will both contain the leftover fields.
|
|
||||||
//
|
|
||||||
// "label" fields are considered only in a struct used as the type of a field
|
|
||||||
// marked as "block", and are used sequentially to capture the labels of
|
|
||||||
// the blocks being decoded. In this case, the name token is used only as
|
|
||||||
// an identifier for the label in diagnostic messages.
|
|
||||||
//
|
|
||||||
// "optional" fields behave like "attr" fields, but they are optional
|
|
||||||
// and will not give parsing errors if they are missing.
|
|
||||||
//
|
|
||||||
// "remain" can be placed on a single field that may be either of type
|
|
||||||
// hcl.Body or hcl.Attributes, in which case any remaining body content is
|
|
||||||
// placed into this field for delayed processing. If no "remain" field is
|
|
||||||
// present then any attributes or blocks not matched by another valid tag
|
|
||||||
// will cause an error diagnostic.
|
|
||||||
//
|
|
||||||
// Only a subset of this tagging/typing vocabulary is supported for the
|
|
||||||
// "Encode" family of functions. See the EncodeIntoBody docs for full details
|
|
||||||
// on the constraints there.
|
|
||||||
//
|
|
||||||
// Broadly-speaking this package deals with two types of error. The first is
|
|
||||||
// errors in the configuration itself, which are returned as diagnostics
|
|
||||||
// written with the configuration author as the target audience. The second
|
|
||||||
// is bugs in the calling program, such as invalid struct tags, which are
|
|
||||||
// surfaced via panics since there can be no useful runtime handling of such
|
|
||||||
// errors and they should certainly not be returned to the user as diagnostics.
|
|
||||||
package gohcl
|
|
@ -1,194 +0,0 @@
|
|||||||
// Copyright (c) HashiCorp, Inc.
|
|
||||||
// SPDX-License-Identifier: MPL-2.0
|
|
||||||
|
|
||||||
package gohcl
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"reflect"
|
|
||||||
"sort"
|
|
||||||
|
|
||||||
"github.com/hashicorp/hcl/v2/hclwrite"
|
|
||||||
"github.com/zclconf/go-cty/cty/gocty"
|
|
||||||
)
|
|
||||||
|
|
||||||
// EncodeIntoBody replaces the contents of the given hclwrite Body with
|
|
||||||
// attributes and blocks derived from the given value, which must be a
|
|
||||||
// struct value or a pointer to a struct value with the struct tags defined
|
|
||||||
// in this package.
|
|
||||||
//
|
|
||||||
// This function can work only with fully-decoded data. It will ignore any
|
|
||||||
// fields tagged as "remain", any fields that decode attributes into either
|
|
||||||
// hcl.Attribute or hcl.Expression values, and any fields that decode blocks
|
|
||||||
// into hcl.Attributes values. This function does not have enough information
|
|
||||||
// to complete the decoding of these types.
|
|
||||||
//
|
|
||||||
// Any fields tagged as "label" are ignored by this function. Use EncodeAsBlock
|
|
||||||
// to produce a whole hclwrite.Block including block labels.
|
|
||||||
//
|
|
||||||
// As long as a suitable value is given to encode and the destination body
|
|
||||||
// is non-nil, this function will always complete. It will panic in case of
|
|
||||||
// any errors in the calling program, such as passing an inappropriate type
|
|
||||||
// or a nil body.
|
|
||||||
//
|
|
||||||
// The layout of the resulting HCL source is derived from the ordering of
|
|
||||||
// the struct fields, with blank lines around nested blocks of different types.
|
|
||||||
// Fields representing attributes should usually precede those representing
|
|
||||||
// blocks so that the attributes can group togather in the result. For more
|
|
||||||
// control, use the hclwrite API directly.
|
|
||||||
func EncodeIntoBody(val interface{}, dst *hclwrite.Body) {
|
|
||||||
rv := reflect.ValueOf(val)
|
|
||||||
ty := rv.Type()
|
|
||||||
if ty.Kind() == reflect.Ptr {
|
|
||||||
rv = rv.Elem()
|
|
||||||
ty = rv.Type()
|
|
||||||
}
|
|
||||||
if ty.Kind() != reflect.Struct {
|
|
||||||
panic(fmt.Sprintf("value is %s, not struct", ty.Kind()))
|
|
||||||
}
|
|
||||||
|
|
||||||
tags := getFieldTags(ty)
|
|
||||||
populateBody(rv, ty, tags, dst)
|
|
||||||
}
|
|
||||||
|
|
||||||
// EncodeAsBlock creates a new hclwrite.Block populated with the data from
|
|
||||||
// the given value, which must be a struct or pointer to struct with the
|
|
||||||
// struct tags defined in this package.
|
|
||||||
//
|
|
||||||
// If the given struct type has fields tagged with "label" tags then they
|
|
||||||
// will be used in order to annotate the created block with labels.
|
|
||||||
//
|
|
||||||
// This function has the same constraints as EncodeIntoBody and will panic
|
|
||||||
// if they are violated.
|
|
||||||
func EncodeAsBlock(val interface{}, blockType string) *hclwrite.Block {
|
|
||||||
rv := reflect.ValueOf(val)
|
|
||||||
ty := rv.Type()
|
|
||||||
if ty.Kind() == reflect.Ptr {
|
|
||||||
rv = rv.Elem()
|
|
||||||
ty = rv.Type()
|
|
||||||
}
|
|
||||||
if ty.Kind() != reflect.Struct {
|
|
||||||
panic(fmt.Sprintf("value is %s, not struct", ty.Kind()))
|
|
||||||
}
|
|
||||||
|
|
||||||
tags := getFieldTags(ty)
|
|
||||||
labels := make([]string, len(tags.Labels))
|
|
||||||
for i, lf := range tags.Labels {
|
|
||||||
lv := rv.Field(lf.FieldIndex)
|
|
||||||
// We just stringify whatever we find. It should always be a string
|
|
||||||
// but if not then we'll still do something reasonable.
|
|
||||||
labels[i] = fmt.Sprintf("%s", lv.Interface())
|
|
||||||
}
|
|
||||||
|
|
||||||
block := hclwrite.NewBlock(blockType, labels)
|
|
||||||
populateBody(rv, ty, tags, block.Body())
|
|
||||||
return block
|
|
||||||
}
|
|
||||||
|
|
||||||
func populateBody(rv reflect.Value, ty reflect.Type, tags *fieldTags, dst *hclwrite.Body) {
|
|
||||||
nameIdxs := make(map[string]int, len(tags.Attributes)+len(tags.Blocks))
|
|
||||||
namesOrder := make([]string, 0, len(tags.Attributes)+len(tags.Blocks))
|
|
||||||
for n, i := range tags.Attributes {
|
|
||||||
nameIdxs[n] = i
|
|
||||||
namesOrder = append(namesOrder, n)
|
|
||||||
}
|
|
||||||
for n, i := range tags.Blocks {
|
|
||||||
nameIdxs[n] = i
|
|
||||||
namesOrder = append(namesOrder, n)
|
|
||||||
}
|
|
||||||
sort.SliceStable(namesOrder, func(i, j int) bool {
|
|
||||||
ni, nj := namesOrder[i], namesOrder[j]
|
|
||||||
return nameIdxs[ni] < nameIdxs[nj]
|
|
||||||
})
|
|
||||||
|
|
||||||
dst.Clear()
|
|
||||||
|
|
||||||
prevWasBlock := false
|
|
||||||
for _, name := range namesOrder {
|
|
||||||
fieldIdx := nameIdxs[name]
|
|
||||||
field := ty.Field(fieldIdx)
|
|
||||||
fieldTy := field.Type
|
|
||||||
fieldVal := rv.Field(fieldIdx)
|
|
||||||
|
|
||||||
if fieldTy.Kind() == reflect.Ptr {
|
|
||||||
fieldTy = fieldTy.Elem()
|
|
||||||
fieldVal = fieldVal.Elem()
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, isAttr := tags.Attributes[name]; isAttr {
|
|
||||||
|
|
||||||
if exprType.AssignableTo(fieldTy) || attrType.AssignableTo(fieldTy) {
|
|
||||||
continue // ignore undecoded fields
|
|
||||||
}
|
|
||||||
if !fieldVal.IsValid() {
|
|
||||||
continue // ignore (field value is nil pointer)
|
|
||||||
}
|
|
||||||
if fieldTy.Kind() == reflect.Ptr && fieldVal.IsNil() {
|
|
||||||
continue // ignore
|
|
||||||
}
|
|
||||||
if prevWasBlock {
|
|
||||||
dst.AppendNewline()
|
|
||||||
prevWasBlock = false
|
|
||||||
}
|
|
||||||
|
|
||||||
valTy, err := gocty.ImpliedType(fieldVal.Interface())
|
|
||||||
if err != nil {
|
|
||||||
panic(fmt.Sprintf("cannot encode %T as HCL expression: %s", fieldVal.Interface(), err))
|
|
||||||
}
|
|
||||||
|
|
||||||
val, err := gocty.ToCtyValue(fieldVal.Interface(), valTy)
|
|
||||||
if err != nil {
|
|
||||||
// This should never happen, since we should always be able
|
|
||||||
// to decode into the implied type.
|
|
||||||
panic(fmt.Sprintf("failed to encode %T as %#v: %s", fieldVal.Interface(), valTy, err))
|
|
||||||
}
|
|
||||||
|
|
||||||
dst.SetAttributeValue(name, val)
|
|
||||||
|
|
||||||
} else { // must be a block, then
|
|
||||||
elemTy := fieldTy
|
|
||||||
isSeq := false
|
|
||||||
if elemTy.Kind() == reflect.Slice || elemTy.Kind() == reflect.Array {
|
|
||||||
isSeq = true
|
|
||||||
elemTy = elemTy.Elem()
|
|
||||||
}
|
|
||||||
|
|
||||||
if bodyType.AssignableTo(elemTy) || attrsType.AssignableTo(elemTy) {
|
|
||||||
continue // ignore undecoded fields
|
|
||||||
}
|
|
||||||
prevWasBlock = false
|
|
||||||
|
|
||||||
if isSeq {
|
|
||||||
l := fieldVal.Len()
|
|
||||||
for i := 0; i < l; i++ {
|
|
||||||
elemVal := fieldVal.Index(i)
|
|
||||||
if !elemVal.IsValid() {
|
|
||||||
continue // ignore (elem value is nil pointer)
|
|
||||||
}
|
|
||||||
if elemTy.Kind() == reflect.Ptr && elemVal.IsNil() {
|
|
||||||
continue // ignore
|
|
||||||
}
|
|
||||||
block := EncodeAsBlock(elemVal.Interface(), name)
|
|
||||||
if !prevWasBlock {
|
|
||||||
dst.AppendNewline()
|
|
||||||
prevWasBlock = true
|
|
||||||
}
|
|
||||||
dst.AppendBlock(block)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if !fieldVal.IsValid() {
|
|
||||||
continue // ignore (field value is nil pointer)
|
|
||||||
}
|
|
||||||
if elemTy.Kind() == reflect.Ptr && fieldVal.IsNil() {
|
|
||||||
continue // ignore
|
|
||||||
}
|
|
||||||
block := EncodeAsBlock(fieldVal.Interface(), name)
|
|
||||||
if !prevWasBlock {
|
|
||||||
dst.AppendNewline()
|
|
||||||
prevWasBlock = true
|
|
||||||
}
|
|
||||||
dst.AppendBlock(block)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,67 +0,0 @@
|
|||||||
// Copyright (c) HashiCorp, Inc.
|
|
||||||
// SPDX-License-Identifier: MPL-2.0
|
|
||||||
|
|
||||||
package gohcl_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/hashicorp/hcl/v2/gohcl"
|
|
||||||
"github.com/hashicorp/hcl/v2/hclwrite"
|
|
||||||
)
|
|
||||||
|
|
||||||
func ExampleEncodeIntoBody() {
|
|
||||||
type Service struct {
|
|
||||||
Name string `hcl:"name,label"`
|
|
||||||
Exe []string `hcl:"executable"`
|
|
||||||
}
|
|
||||||
type Constraints struct {
|
|
||||||
OS string `hcl:"os"`
|
|
||||||
Arch string `hcl:"arch"`
|
|
||||||
}
|
|
||||||
type App struct {
|
|
||||||
Name string `hcl:"name"`
|
|
||||||
Desc string `hcl:"description"`
|
|
||||||
Constraints *Constraints `hcl:"constraints,block"`
|
|
||||||
Services []Service `hcl:"service,block"`
|
|
||||||
}
|
|
||||||
|
|
||||||
app := App{
|
|
||||||
Name: "awesome-app",
|
|
||||||
Desc: "Such an awesome application",
|
|
||||||
Constraints: &Constraints{
|
|
||||||
OS: "linux",
|
|
||||||
Arch: "amd64",
|
|
||||||
},
|
|
||||||
Services: []Service{
|
|
||||||
{
|
|
||||||
Name: "web",
|
|
||||||
Exe: []string{"./web", "--listen=:8080"},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "worker",
|
|
||||||
Exe: []string{"./worker"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
f := hclwrite.NewEmptyFile()
|
|
||||||
gohcl.EncodeIntoBody(&app, f.Body())
|
|
||||||
fmt.Printf("%s", f.Bytes())
|
|
||||||
|
|
||||||
// Output:
|
|
||||||
// name = "awesome-app"
|
|
||||||
// description = "Such an awesome application"
|
|
||||||
//
|
|
||||||
// constraints {
|
|
||||||
// os = "linux"
|
|
||||||
// arch = "amd64"
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// service "web" {
|
|
||||||
// executable = ["./web", "--listen=:8080"]
|
|
||||||
// }
|
|
||||||
// service "worker" {
|
|
||||||
// executable = ["./worker"]
|
|
||||||
// }
|
|
||||||
}
|
|
@ -1,196 +0,0 @@
|
|||||||
// Copyright (c) HashiCorp, Inc.
|
|
||||||
// SPDX-License-Identifier: MPL-2.0
|
|
||||||
|
|
||||||
package gohcl
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"reflect"
|
|
||||||
"sort"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/hashicorp/hcl/v2"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ImpliedBodySchema produces a hcl.BodySchema derived from the type of the
|
|
||||||
// given value, which must be a struct value or a pointer to one. If an
|
|
||||||
// inappropriate value is passed, this function will panic.
|
|
||||||
//
|
|
||||||
// The second return argument indicates whether the given struct includes
|
|
||||||
// a "remain" field, and thus the returned schema is non-exhaustive.
|
|
||||||
//
|
|
||||||
// This uses the tags on the fields of the struct to discover how each
|
|
||||||
// field's value should be expressed within configuration. If an invalid
|
|
||||||
// mapping is attempted, this function will panic.
|
|
||||||
func ImpliedBodySchema(val interface{}) (schema *hcl.BodySchema, partial bool) {
|
|
||||||
ty := reflect.TypeOf(val)
|
|
||||||
|
|
||||||
if ty.Kind() == reflect.Ptr {
|
|
||||||
ty = ty.Elem()
|
|
||||||
}
|
|
||||||
|
|
||||||
if ty.Kind() != reflect.Struct {
|
|
||||||
panic(fmt.Sprintf("given value must be struct, not %T", val))
|
|
||||||
}
|
|
||||||
|
|
||||||
var attrSchemas []hcl.AttributeSchema
|
|
||||||
var blockSchemas []hcl.BlockHeaderSchema
|
|
||||||
|
|
||||||
tags := getFieldTags(ty)
|
|
||||||
|
|
||||||
attrNames := make([]string, 0, len(tags.Attributes))
|
|
||||||
for n := range tags.Attributes {
|
|
||||||
attrNames = append(attrNames, n)
|
|
||||||
}
|
|
||||||
sort.Strings(attrNames)
|
|
||||||
for _, n := range attrNames {
|
|
||||||
idx := tags.Attributes[n]
|
|
||||||
optional := tags.Optional[n]
|
|
||||||
field := ty.Field(idx)
|
|
||||||
|
|
||||||
var required bool
|
|
||||||
|
|
||||||
switch {
|
|
||||||
case field.Type.AssignableTo(exprType):
|
|
||||||
// If we're decoding to hcl.Expression then absense can be
|
|
||||||
// indicated via a null value, so we don't specify that
|
|
||||||
// the field is required during decoding.
|
|
||||||
required = false
|
|
||||||
case field.Type.Kind() != reflect.Ptr && !optional:
|
|
||||||
required = true
|
|
||||||
default:
|
|
||||||
required = false
|
|
||||||
}
|
|
||||||
|
|
||||||
attrSchemas = append(attrSchemas, hcl.AttributeSchema{
|
|
||||||
Name: n,
|
|
||||||
Required: required,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
blockNames := make([]string, 0, len(tags.Blocks))
|
|
||||||
for n := range tags.Blocks {
|
|
||||||
blockNames = append(blockNames, n)
|
|
||||||
}
|
|
||||||
sort.Strings(blockNames)
|
|
||||||
for _, n := range blockNames {
|
|
||||||
idx := tags.Blocks[n]
|
|
||||||
field := ty.Field(idx)
|
|
||||||
fty := field.Type
|
|
||||||
if fty.Kind() == reflect.Slice {
|
|
||||||
fty = fty.Elem()
|
|
||||||
}
|
|
||||||
if fty.Kind() == reflect.Ptr {
|
|
||||||
fty = fty.Elem()
|
|
||||||
}
|
|
||||||
if fty.Kind() != reflect.Struct {
|
|
||||||
panic(fmt.Sprintf(
|
|
||||||
"hcl 'block' tag kind cannot be applied to %s field %s: struct required", field.Type.String(), field.Name,
|
|
||||||
))
|
|
||||||
}
|
|
||||||
ftags := getFieldTags(fty)
|
|
||||||
var labelNames []string
|
|
||||||
if len(ftags.Labels) > 0 {
|
|
||||||
labelNames = make([]string, len(ftags.Labels))
|
|
||||||
for i, l := range ftags.Labels {
|
|
||||||
labelNames[i] = l.Name
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
blockSchemas = append(blockSchemas, hcl.BlockHeaderSchema{
|
|
||||||
Type: n,
|
|
||||||
LabelNames: labelNames,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
partial = tags.Remain != nil
|
|
||||||
schema = &hcl.BodySchema{
|
|
||||||
Attributes: attrSchemas,
|
|
||||||
Blocks: blockSchemas,
|
|
||||||
}
|
|
||||||
return schema, partial
|
|
||||||
}
|
|
||||||
|
|
||||||
func hasFieldTags(ty reflect.Type) bool {
|
|
||||||
ct := ty.NumField()
|
|
||||||
for i := 0; i < ct; i++ {
|
|
||||||
field := ty.Field(i)
|
|
||||||
tag := field.Tag.Get("hcl")
|
|
||||||
if tag != "" {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
type fieldTags struct {
|
|
||||||
Attributes map[string]int
|
|
||||||
Blocks map[string]int
|
|
||||||
Labels []labelField
|
|
||||||
Remain *int
|
|
||||||
Body *int
|
|
||||||
Optional map[string]bool
|
|
||||||
}
|
|
||||||
|
|
||||||
type labelField struct {
|
|
||||||
FieldIndex int
|
|
||||||
Name string
|
|
||||||
}
|
|
||||||
|
|
||||||
func getFieldTags(ty reflect.Type) *fieldTags {
|
|
||||||
ret := &fieldTags{
|
|
||||||
Attributes: map[string]int{},
|
|
||||||
Blocks: map[string]int{},
|
|
||||||
Optional: map[string]bool{},
|
|
||||||
}
|
|
||||||
|
|
||||||
ct := ty.NumField()
|
|
||||||
for i := 0; i < ct; i++ {
|
|
||||||
field := ty.Field(i)
|
|
||||||
tag := field.Tag.Get("hcl")
|
|
||||||
if tag == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
comma := strings.Index(tag, ",")
|
|
||||||
var name, kind string
|
|
||||||
if comma != -1 {
|
|
||||||
name = tag[:comma]
|
|
||||||
kind = tag[comma+1:]
|
|
||||||
} else {
|
|
||||||
name = tag
|
|
||||||
kind = "attr"
|
|
||||||
}
|
|
||||||
|
|
||||||
switch kind {
|
|
||||||
case "attr":
|
|
||||||
ret.Attributes[name] = i
|
|
||||||
case "block":
|
|
||||||
ret.Blocks[name] = i
|
|
||||||
case "label":
|
|
||||||
ret.Labels = append(ret.Labels, labelField{
|
|
||||||
FieldIndex: i,
|
|
||||||
Name: name,
|
|
||||||
})
|
|
||||||
case "remain":
|
|
||||||
if ret.Remain != nil {
|
|
||||||
panic("only one 'remain' tag is permitted")
|
|
||||||
}
|
|
||||||
idx := i // copy, because this loop will continue assigning to i
|
|
||||||
ret.Remain = &idx
|
|
||||||
case "body":
|
|
||||||
if ret.Body != nil {
|
|
||||||
panic("only one 'body' tag is permitted")
|
|
||||||
}
|
|
||||||
idx := i // copy, because this loop will continue assigning to i
|
|
||||||
ret.Body = &idx
|
|
||||||
case "optional":
|
|
||||||
ret.Attributes[name] = i
|
|
||||||
ret.Optional[name] = true
|
|
||||||
default:
|
|
||||||
panic(fmt.Sprintf("invalid hcl field tag kind %q on %s %q", kind, field.Type.String(), field.Name))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return ret
|
|
||||||
}
|
|
@ -1,233 +0,0 @@
|
|||||||
// Copyright (c) HashiCorp, Inc.
|
|
||||||
// SPDX-License-Identifier: MPL-2.0
|
|
||||||
|
|
||||||
package gohcl
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"reflect"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/davecgh/go-spew/spew"
|
|
||||||
"github.com/hashicorp/hcl/v2"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestImpliedBodySchema(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
val interface{}
|
|
||||||
wantSchema *hcl.BodySchema
|
|
||||||
wantPartial bool
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
struct{}{},
|
|
||||||
&hcl.BodySchema{},
|
|
||||||
false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
struct {
|
|
||||||
Ignored bool
|
|
||||||
}{},
|
|
||||||
&hcl.BodySchema{},
|
|
||||||
false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
struct {
|
|
||||||
Attr1 bool `hcl:"attr1"`
|
|
||||||
Attr2 bool `hcl:"attr2"`
|
|
||||||
}{},
|
|
||||||
&hcl.BodySchema{
|
|
||||||
Attributes: []hcl.AttributeSchema{
|
|
||||||
{
|
|
||||||
Name: "attr1",
|
|
||||||
Required: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "attr2",
|
|
||||||
Required: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
struct {
|
|
||||||
Attr *bool `hcl:"attr,attr"`
|
|
||||||
}{},
|
|
||||||
&hcl.BodySchema{
|
|
||||||
Attributes: []hcl.AttributeSchema{
|
|
||||||
{
|
|
||||||
Name: "attr",
|
|
||||||
Required: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
struct {
|
|
||||||
Thing struct{} `hcl:"thing,block"`
|
|
||||||
}{},
|
|
||||||
&hcl.BodySchema{
|
|
||||||
Blocks: []hcl.BlockHeaderSchema{
|
|
||||||
{
|
|
||||||
Type: "thing",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
struct {
|
|
||||||
Thing struct {
|
|
||||||
Type string `hcl:"type,label"`
|
|
||||||
Name string `hcl:"name,label"`
|
|
||||||
} `hcl:"thing,block"`
|
|
||||||
}{},
|
|
||||||
&hcl.BodySchema{
|
|
||||||
Blocks: []hcl.BlockHeaderSchema{
|
|
||||||
{
|
|
||||||
Type: "thing",
|
|
||||||
LabelNames: []string{"type", "name"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
struct {
|
|
||||||
Thing []struct {
|
|
||||||
Type string `hcl:"type,label"`
|
|
||||||
Name string `hcl:"name,label"`
|
|
||||||
} `hcl:"thing,block"`
|
|
||||||
}{},
|
|
||||||
&hcl.BodySchema{
|
|
||||||
Blocks: []hcl.BlockHeaderSchema{
|
|
||||||
{
|
|
||||||
Type: "thing",
|
|
||||||
LabelNames: []string{"type", "name"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
struct {
|
|
||||||
Thing *struct {
|
|
||||||
Type string `hcl:"type,label"`
|
|
||||||
Name string `hcl:"name,label"`
|
|
||||||
} `hcl:"thing,block"`
|
|
||||||
}{},
|
|
||||||
&hcl.BodySchema{
|
|
||||||
Blocks: []hcl.BlockHeaderSchema{
|
|
||||||
{
|
|
||||||
Type: "thing",
|
|
||||||
LabelNames: []string{"type", "name"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
struct {
|
|
||||||
Thing struct {
|
|
||||||
Name string `hcl:"name,label"`
|
|
||||||
Something string `hcl:"something"`
|
|
||||||
} `hcl:"thing,block"`
|
|
||||||
}{},
|
|
||||||
&hcl.BodySchema{
|
|
||||||
Blocks: []hcl.BlockHeaderSchema{
|
|
||||||
{
|
|
||||||
Type: "thing",
|
|
||||||
LabelNames: []string{"name"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
struct {
|
|
||||||
Doodad string `hcl:"doodad"`
|
|
||||||
Thing struct {
|
|
||||||
Name string `hcl:"name,label"`
|
|
||||||
} `hcl:"thing,block"`
|
|
||||||
}{},
|
|
||||||
&hcl.BodySchema{
|
|
||||||
Attributes: []hcl.AttributeSchema{
|
|
||||||
{
|
|
||||||
Name: "doodad",
|
|
||||||
Required: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Blocks: []hcl.BlockHeaderSchema{
|
|
||||||
{
|
|
||||||
Type: "thing",
|
|
||||||
LabelNames: []string{"name"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
struct {
|
|
||||||
Doodad string `hcl:"doodad"`
|
|
||||||
Config string `hcl:",remain"`
|
|
||||||
}{},
|
|
||||||
&hcl.BodySchema{
|
|
||||||
Attributes: []hcl.AttributeSchema{
|
|
||||||
{
|
|
||||||
Name: "doodad",
|
|
||||||
Required: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
struct {
|
|
||||||
Expr hcl.Expression `hcl:"expr"`
|
|
||||||
}{},
|
|
||||||
&hcl.BodySchema{
|
|
||||||
Attributes: []hcl.AttributeSchema{
|
|
||||||
{
|
|
||||||
Name: "expr",
|
|
||||||
Required: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
struct {
|
|
||||||
Meh string `hcl:"meh,optional"`
|
|
||||||
}{},
|
|
||||||
&hcl.BodySchema{
|
|
||||||
Attributes: []hcl.AttributeSchema{
|
|
||||||
{
|
|
||||||
Name: "meh",
|
|
||||||
Required: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
false,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, test := range tests {
|
|
||||||
t.Run(fmt.Sprintf("%#v", test.val), func(t *testing.T) {
|
|
||||||
schema, partial := ImpliedBodySchema(test.val)
|
|
||||||
if !reflect.DeepEqual(schema, test.wantSchema) {
|
|
||||||
t.Errorf(
|
|
||||||
"wrong schema\ngot: %s\nwant: %s",
|
|
||||||
spew.Sdump(schema), spew.Sdump(test.wantSchema),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if partial != test.wantPartial {
|
|
||||||
t.Errorf(
|
|
||||||
"wrong partial flag\ngot: %#v\nwant: %#v",
|
|
||||||
partial, test.wantPartial,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,19 +0,0 @@
|
|||||||
// Copyright (c) HashiCorp, Inc.
|
|
||||||
// SPDX-License-Identifier: MPL-2.0
|
|
||||||
|
|
||||||
package gohcl
|
|
||||||
|
|
||||||
import (
|
|
||||||
"reflect"
|
|
||||||
|
|
||||||
"github.com/hashicorp/hcl/v2"
|
|
||||||
)
|
|
||||||
|
|
||||||
var victimExpr hcl.Expression
|
|
||||||
var victimBody hcl.Body
|
|
||||||
|
|
||||||
var exprType = reflect.TypeOf(&victimExpr).Elem()
|
|
||||||
var bodyType = reflect.TypeOf(&victimBody).Elem()
|
|
||||||
var blockType = reflect.TypeOf((*hcl.Block)(nil))
|
|
||||||
var attrType = reflect.TypeOf((*hcl.Attribute)(nil))
|
|
||||||
var attrsType = reflect.TypeOf(hcl.Attributes(nil))
|
|
@ -1,99 +0,0 @@
|
|||||||
// Copyright (c) HashiCorp, Inc.
|
|
||||||
// SPDX-License-Identifier: MPL-2.0
|
|
||||||
|
|
||||||
// This is a fork of hashicorp/hcl/gohcl/decode.go that pulls out variable dependencies in attributes
|
|
||||||
|
|
||||||
package gohcl
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"reflect"
|
|
||||||
|
|
||||||
"github.com/hashicorp/hcl/v2"
|
|
||||||
)
|
|
||||||
|
|
||||||
func VariablesInBody(body hcl.Body, val interface{}) ([]hcl.Traversal, hcl.Diagnostics) {
|
|
||||||
rv := reflect.ValueOf(val)
|
|
||||||
if rv.Kind() != reflect.Ptr {
|
|
||||||
panic(fmt.Sprintf("target value must be a pointer, not %s", rv.Type().String()))
|
|
||||||
}
|
|
||||||
|
|
||||||
return findVariablesInBody(body, rv.Elem())
|
|
||||||
}
|
|
||||||
|
|
||||||
func findVariablesInBody(body hcl.Body, val reflect.Value) ([]hcl.Traversal, hcl.Diagnostics) {
|
|
||||||
et := val.Type()
|
|
||||||
switch et.Kind() {
|
|
||||||
case reflect.Struct:
|
|
||||||
return findVariablesInBodyStruct(body, val)
|
|
||||||
case reflect.Map:
|
|
||||||
return findVariablesInBodyMap(body, val)
|
|
||||||
default:
|
|
||||||
panic(fmt.Sprintf("target value must be pointer to struct or map, not %s", et.String()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func findVariablesInBodyStruct(body hcl.Body, val reflect.Value) ([]hcl.Traversal, hcl.Diagnostics) {
|
|
||||||
var variables []hcl.Traversal
|
|
||||||
|
|
||||||
schema, partial := ImpliedBodySchema(val.Interface())
|
|
||||||
|
|
||||||
var content *hcl.BodyContent
|
|
||||||
var diags hcl.Diagnostics
|
|
||||||
if partial {
|
|
||||||
content, _, diags = body.PartialContent(schema)
|
|
||||||
} else {
|
|
||||||
content, diags = body.Content(schema)
|
|
||||||
}
|
|
||||||
if content == nil {
|
|
||||||
return variables, diags
|
|
||||||
}
|
|
||||||
|
|
||||||
tags := getFieldTags(val.Type())
|
|
||||||
|
|
||||||
for name := range tags.Attributes {
|
|
||||||
attr := content.Attributes[name]
|
|
||||||
if attr != nil {
|
|
||||||
variables = append(variables, attr.Expr.Variables()...)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
blocksByType := content.Blocks.ByType()
|
|
||||||
|
|
||||||
for typeName, fieldIdx := range tags.Blocks {
|
|
||||||
blocks := blocksByType[typeName]
|
|
||||||
field := val.Type().Field(fieldIdx)
|
|
||||||
|
|
||||||
ty := field.Type
|
|
||||||
if ty.Kind() == reflect.Slice {
|
|
||||||
ty = ty.Elem()
|
|
||||||
}
|
|
||||||
if ty.Kind() == reflect.Ptr {
|
|
||||||
ty = ty.Elem()
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, block := range blocks {
|
|
||||||
blockVars, blockDiags := findVariablesInBody(block.Body, reflect.New(ty).Elem())
|
|
||||||
variables = append(variables, blockVars...)
|
|
||||||
diags = append(diags, blockDiags...)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
return variables, diags
|
|
||||||
}
|
|
||||||
|
|
||||||
func findVariablesInBodyMap(body hcl.Body, v reflect.Value) ([]hcl.Traversal, hcl.Diagnostics) {
|
|
||||||
var variables []hcl.Traversal
|
|
||||||
|
|
||||||
attrs, diags := body.JustAttributes()
|
|
||||||
if attrs == nil {
|
|
||||||
return variables, diags
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, attr := range attrs {
|
|
||||||
variables = append(variables, attr.Expr.Variables()...)
|
|
||||||
}
|
|
||||||
|
|
||||||
return variables, diags
|
|
||||||
}
|
|
@ -1,84 +0,0 @@
|
|||||||
// Copyright (c) The OpenTofu Authors
|
|
||||||
// SPDX-License-Identifier: MPL-2.0
|
|
||||||
// Copyright (c) 2023 HashiCorp, Inc.
|
|
||||||
// SPDX-License-Identifier: MPL-2.0
|
|
||||||
|
|
||||||
package gohcl_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/hashicorp/hcl/v2"
|
|
||||||
"github.com/hashicorp/hcl/v2/hclsyntax"
|
|
||||||
"github.com/opentofu/opentofu/internal/gohcl"
|
|
||||||
"github.com/zclconf/go-cty/cty"
|
|
||||||
)
|
|
||||||
|
|
||||||
var data = `
|
|
||||||
inner "foo" "bar" {
|
|
||||||
val = magic.foo.bar
|
|
||||||
data = {
|
|
||||||
"z" = nested.value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`
|
|
||||||
|
|
||||||
type InnerBlock struct {
|
|
||||||
Type string `hcl:"type,label"`
|
|
||||||
Name string `hcl:"name,label"`
|
|
||||||
Value string `hcl:"val"`
|
|
||||||
Data map[string]string `hcl:"data"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type OuterBlock struct {
|
|
||||||
Contents InnerBlock `hcl:"inner,block"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func Test(t *testing.T) {
|
|
||||||
|
|
||||||
println("> Parse HCL")
|
|
||||||
file, diags := hclsyntax.ParseConfig([]byte(data), "INLINE", hcl.Pos{Byte: 0, Line: 1, Column: 1})
|
|
||||||
|
|
||||||
println(diags.Error())
|
|
||||||
|
|
||||||
ob := &OuterBlock{}
|
|
||||||
|
|
||||||
println()
|
|
||||||
println("> Detect Variables")
|
|
||||||
vars, diags := gohcl.VariablesInBody(file.Body, ob)
|
|
||||||
println(diags.Error())
|
|
||||||
for _, v := range vars {
|
|
||||||
ident := ""
|
|
||||||
for _, p := range v {
|
|
||||||
if root, ok := p.(hcl.TraverseRoot); ok {
|
|
||||||
ident += root.Name
|
|
||||||
}
|
|
||||||
if attr, ok := p.(hcl.TraverseAttr); ok {
|
|
||||||
ident += "." + attr.Name
|
|
||||||
}
|
|
||||||
}
|
|
||||||
println("Required: " + ident)
|
|
||||||
}
|
|
||||||
|
|
||||||
println()
|
|
||||||
println("> Decode Body")
|
|
||||||
|
|
||||||
ctx := &hcl.EvalContext{
|
|
||||||
Variables: map[string]cty.Value{
|
|
||||||
"magic": cty.ObjectVal(map[string]cty.Value{
|
|
||||||
"foo": cty.ObjectVal(map[string]cty.Value{
|
|
||||||
"bar": cty.StringVal("BAR IS BEST BAR"),
|
|
||||||
}),
|
|
||||||
}),
|
|
||||||
"nested": cty.ObjectVal(map[string]cty.Value{
|
|
||||||
"value": cty.StringVal("ZISHERE"),
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
diags = gohcl.DecodeBody(file.Body, ctx, ob)
|
|
||||||
println(diags.Error())
|
|
||||||
|
|
||||||
fmt.Printf("%#v\n", ob)
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user