From 559c61e6b6f93270bb50944e9fb063c94bdf92e8 Mon Sep 17 00:00:00 2001 From: Poonam Jadhav Date: Fri, 11 Aug 2023 15:52:51 -0400 Subject: [PATCH] Net-2712/resource hcl parsing (#18250) * Initial protohcl implementation Co-authored-by: Matt Keeler Co-authored-by: Daniel Upton * resourcehcl: implement resource decoding on top of protohcl Co-authored-by: Daniel Upton * fix: resolve ci failures * test: add additional unmarshalling tests * refactor: update function test to clean protohcl package imports --------- Co-authored-by: Matt Keeler Co-authored-by: Daniel Upton --- go.mod | 6 + go.sum | 23 + internal/protohcl/any.go | 114 ++ internal/protohcl/attributes.go | 154 +++ internal/protohcl/blocks.go | 112 ++ internal/protohcl/cty.go | 146 +++ internal/protohcl/decoder.go | 284 +++++ internal/protohcl/doc.go | 76 ++ internal/protohcl/naming.go | 18 + internal/protohcl/oneof.go | 51 + internal/protohcl/primitives.go | 138 +++ internal/protohcl/testproto/buf.gen.yaml | 9 + internal/protohcl/testproto/example.pb.go | 994 ++++++++++++++++++ internal/protohcl/testproto/example.proto | 74 ++ internal/protohcl/unmarshal.go | 134 +++ internal/protohcl/unmarshal_test.go | 557 ++++++++++ internal/protohcl/well_known_types.go | 418 ++++++++ internal/resource/registry.go | 13 + internal/resourcehcl/any.go | 46 + internal/resourcehcl/naming.go | 35 + .../testdata/gvk-no-arguments.error | 1 + .../resourcehcl/testdata/gvk-no-arguments.hcl | 4 + .../resourcehcl/testdata/invalid-group.error | 1 + .../resourcehcl/testdata/invalid-group.hcl | 8 + .../resourcehcl/testdata/invalid-gvk.error | 1 + internal/resourcehcl/testdata/invalid-gvk.hcl | 4 + .../testdata/invalid-metadata.error | 1 + .../resourcehcl/testdata/invalid-metadata.hcl | 8 + .../resourcehcl/testdata/invalid-name.error | 1 + .../resourcehcl/testdata/invalid-name.hcl | 4 + .../testdata/no-blocks-any-first.golden | 1 + .../testdata/no-blocks-any-first.hcl | 8 + .../resourcehcl/testdata/no-blocks.golden | 1 + internal/resourcehcl/testdata/no-blocks.hcl | 33 + internal/resourcehcl/testdata/owner.golden | 1 + internal/resourcehcl/testdata/owner.hcl | 9 + .../resourcehcl/testdata/simple-gvk.golden | 1 + internal/resourcehcl/testdata/simple-gvk.hcl | 13 + .../resourcehcl/testdata/type-block.golden | 1 + internal/resourcehcl/testdata/type-block.hcl | 8 + .../testdata/unknown-field-block.error | 1 + .../testdata/unknown-field-block.hcl | 3 + .../testdata/unknown-field-object.error | 1 + .../testdata/unknown-field-object.hcl | 3 + .../resourcehcl/testdata/unknown-type.error | 1 + .../resourcehcl/testdata/unknown-type.hcl | 8 + .../resourcehcl/testdata/upstreams.golden | 1 + internal/resourcehcl/testdata/upstreams.hcl | 25 + internal/resourcehcl/unmarshal.go | 52 + internal/resourcehcl/unmarshal_test.go | 103 ++ 50 files changed, 3709 insertions(+) create mode 100644 internal/protohcl/any.go create mode 100644 internal/protohcl/attributes.go create mode 100644 internal/protohcl/blocks.go create mode 100644 internal/protohcl/cty.go create mode 100644 internal/protohcl/decoder.go create mode 100644 internal/protohcl/doc.go create mode 100644 internal/protohcl/naming.go create mode 100644 internal/protohcl/oneof.go create mode 100644 internal/protohcl/primitives.go create mode 100644 internal/protohcl/testproto/buf.gen.yaml create mode 100644 internal/protohcl/testproto/example.pb.go create mode 100644 internal/protohcl/testproto/example.proto create mode 100644 internal/protohcl/unmarshal.go create mode 100644 internal/protohcl/unmarshal_test.go create mode 100644 internal/protohcl/well_known_types.go create mode 100644 internal/resourcehcl/any.go create mode 100644 internal/resourcehcl/naming.go create mode 100644 internal/resourcehcl/testdata/gvk-no-arguments.error create mode 100644 internal/resourcehcl/testdata/gvk-no-arguments.hcl create mode 100644 internal/resourcehcl/testdata/invalid-group.error create mode 100644 internal/resourcehcl/testdata/invalid-group.hcl create mode 100644 internal/resourcehcl/testdata/invalid-gvk.error create mode 100644 internal/resourcehcl/testdata/invalid-gvk.hcl create mode 100644 internal/resourcehcl/testdata/invalid-metadata.error create mode 100644 internal/resourcehcl/testdata/invalid-metadata.hcl create mode 100644 internal/resourcehcl/testdata/invalid-name.error create mode 100644 internal/resourcehcl/testdata/invalid-name.hcl create mode 100644 internal/resourcehcl/testdata/no-blocks-any-first.golden create mode 100644 internal/resourcehcl/testdata/no-blocks-any-first.hcl create mode 100644 internal/resourcehcl/testdata/no-blocks.golden create mode 100644 internal/resourcehcl/testdata/no-blocks.hcl create mode 100644 internal/resourcehcl/testdata/owner.golden create mode 100644 internal/resourcehcl/testdata/owner.hcl create mode 100644 internal/resourcehcl/testdata/simple-gvk.golden create mode 100644 internal/resourcehcl/testdata/simple-gvk.hcl create mode 100644 internal/resourcehcl/testdata/type-block.golden create mode 100644 internal/resourcehcl/testdata/type-block.hcl create mode 100644 internal/resourcehcl/testdata/unknown-field-block.error create mode 100644 internal/resourcehcl/testdata/unknown-field-block.hcl create mode 100644 internal/resourcehcl/testdata/unknown-field-object.error create mode 100644 internal/resourcehcl/testdata/unknown-field-object.hcl create mode 100644 internal/resourcehcl/testdata/unknown-type.error create mode 100644 internal/resourcehcl/testdata/unknown-type.hcl create mode 100644 internal/resourcehcl/testdata/upstreams.golden create mode 100644 internal/resourcehcl/testdata/upstreams.hcl create mode 100644 internal/resourcehcl/unmarshal.go create mode 100644 internal/resourcehcl/unmarshal_test.go diff --git a/go.mod b/go.mod index 0d2de3f6b3..4e5b454434 100644 --- a/go.mod +++ b/go.mod @@ -61,6 +61,7 @@ require ( github.com/hashicorp/go-version v1.2.1 github.com/hashicorp/golang-lru v0.5.4 github.com/hashicorp/hcl v1.0.0 + github.com/hashicorp/hcl/v2 v2.6.0 github.com/hashicorp/hcp-scada-provider v0.2.3 github.com/hashicorp/hcp-sdk-go v0.55.0 github.com/hashicorp/hil v0.0.0-20200423225030-a18a1cd20038 @@ -96,6 +97,7 @@ require ( github.com/ryanuber/columnize v2.1.2+incompatible github.com/shirou/gopsutil/v3 v3.22.8 github.com/stretchr/testify v1.8.3 + github.com/zclconf/go-cty v1.2.0 go.etcd.io/bbolt v1.3.7 go.opentelemetry.io/otel v1.16.0 go.opentelemetry.io/otel/metric v1.16.0 @@ -138,6 +140,9 @@ require ( github.com/Azure/go-autorest/tracing v0.6.0 // indirect github.com/DataDog/datadog-go v4.8.2+incompatible // indirect github.com/Microsoft/go-winio v0.6.1 // indirect + github.com/agext/levenshtein v1.2.1 // indirect + github.com/apparentlymart/go-textseg v1.0.0 // indirect + github.com/apparentlymart/go-textseg/v12 v12.0.0 // indirect github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d // indirect github.com/benbjohnson/immutable v0.4.0 // indirect github.com/beorn7/perks v1.0.1 // indirect @@ -207,6 +212,7 @@ require ( github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect + github.com/mitchellh/go-wordwrap v1.0.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect diff --git a/go.sum b/go.sum index 5ca08d3b94..690c27b8af 100644 --- a/go.sum +++ b/go.sum @@ -111,6 +111,8 @@ github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdko github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/abdullin/seq v0.0.0-20160510034733-d5467c17e7af h1:DBNMBMuMiWYu0b+8KMJuWmfCkcxl09JwdlqwDZZ6U14= github.com/abdullin/seq v0.0.0-20160510034733-d5467c17e7af/go.mod h1:5Jv4cbFiHJMsVxt52+i0Ha45fjshj6wxYr1r19tB9bw= +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/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= @@ -118,6 +120,11 @@ github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRF github.com/aliyun/alibaba-cloud-sdk-go v1.62.156 h1:K4N91T1+RlSlx+t2dujeDviy4ehSGVjEltluDgmeHS4= github.com/aliyun/alibaba-cloud-sdk-go v1.62.156/go.mod h1:Api2AkmMgGaSUAhmk76oaFObkoeCPc/bKAqcyplPODs= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= +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/circbuf v0.0.0-20150827004946-bbbad097214e h1:QEF07wC0T1rKkctt1RINW/+RMTVmiwxETico2l3gxJA= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= @@ -312,6 +319,7 @@ github.com/go-ozzo/ozzo-validation v3.6.0+incompatible/go.mod h1:gsEKFIVnabGBt6m github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-test/deep v1.0.2/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= +github.com/go-test/deep v1.0.3/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= github.com/go-test/deep v1.1.0 h1:WOcxcdHcvdgThNXjw0t76K42FXTU7HpNQWHpA2HHNlg= github.com/go-test/deep v1.1.0/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= github.com/gobuffalo/attrs v0.0.0-20190224210810-a9411de4debd/go.mod h1:4duuawTqi2wkkpB4ePgWMaai6/Kc6WEz83bhFwpHzj0= @@ -367,6 +375,7 @@ 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.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= 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/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= @@ -552,6 +561,8 @@ github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+l github.com/hashicorp/golang-lru v0.5.4/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.6.0 h1:3krZOfGY6SziUXa6H9PJU6TyohHn7I+ARYnhbeNBz+o= +github.com/hashicorp/hcl/v2 v2.6.0/go.mod h1:bQTN5mpo+jewjJgh8jr0JUguIi7qPHUF6yIfAEN3jqY= github.com/hashicorp/hcp-scada-provider v0.2.3 h1:AarYR+/Pcv+cMvPdAlb92uOBmZfEH6ny4+DT+4NY2VQ= github.com/hashicorp/hcp-scada-provider v0.2.3/go.mod h1:ZFTgGwkzNv99PLQjTsulzaCplCzOTBh0IUQsPKzrQFo= github.com/hashicorp/hcp-sdk-go v0.55.0 h1:T4sQtgQfQJOD0uucT4hS+GZI1FmoHAQMADj277W++xw= @@ -653,6 +664,8 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +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/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/linode/linodego v0.7.1/go.mod h1:ga11n3ivecUrPCHN0rANxKmfWBJVkOXfLMZinAbj2sY= github.com/linode/linodego v0.10.0 h1:AMdb82HVgY8o3mjBXJcUv9B+fnJjfDMn2rNRGbX+jvM= @@ -710,6 +723,8 @@ github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77/go. github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= github.com/mitchellh/go-testing-interface v1.14.0 h1:/x0XQ6h+3U3nAyk1yx+bHPURrKa9sVVvYbuqZ7pIAtI= github.com/mitchellh/go-testing-interface v1.14.0/go.mod h1:gfgS7OtZj6MA4U1UrDRp04twqAjfvlZyCfX3sDjEym8= +github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= +github.com/mitchellh/go-wordwrap v1.0.0 h1:6GlHJ/LTGMrIJbwgdqdl2eEH8o+Exx/0m8ir9Gns0u4= github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= github.com/mitchellh/hashstructure v0.0.0-20170609045927-2bca23e0e452 h1:hOY53G+kBFhbYFpRVxHl5eS7laP6B1+Cq+Z9Dry1iMU= github.com/mitchellh/hashstructure v0.0.0-20170609045927-2bca23e0e452/go.mod h1:QjSHrPWS+BGUVBYkbTZWEnOh3G1DutKwClXU/ABz6AQ= @@ -847,6 +862,7 @@ github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 h1:nn5Wsu0esKSJiIVhscUt github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/segmentio/fasthash v1.0.3 h1:EI9+KE1EwvMLBWwjpRDc+fEM+prwxDYbslddQGtrmhM= github.com/segmentio/fasthash v1.0.3/go.mod h1:waKX8l2N8yckOgmSsXJi7x1ZfdKZ4x7KRMzBtS3oedY= +github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= github.com/shirou/gopsutil/v3 v3.22.8 h1:a4s3hXogo5mE2PfdfJIonDbstO/P+9JszdfhAHSzD9Y= github.com/shirou/gopsutil/v3 v3.22.8/go.mod h1:s648gW4IywYzUfE/KjXxUsqrqx/T2xO5VqOXxONeRfI= github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= @@ -871,6 +887,7 @@ github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3 github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= 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.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= @@ -912,6 +929,7 @@ github.com/uber/jaeger-lib v2.4.1+incompatible h1:td4jdvLcExb4cBISKIpHuGoVXh+dVK github.com/uber/jaeger-lib v2.4.1+incompatible/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6+uUTzImX/AauajbLI56U= github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= +github.com/vmihailenco/msgpack v3.3.3+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk= github.com/vmware/govmomi v0.18.0 h1:f7QxSmP7meCtoAmiKZogvVbLInT+CZx6Px6K5rYsJZo= github.com/vmware/govmomi v0.18.0/go.mod h1:URlwyTFZX72RmxtxuaFL2Uj3fD1JTvZdx59bHWk6aFU= github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= @@ -930,6 +948,8 @@ github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1 github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yusufpapurcu/wmi v1.2.2 h1:KBNDSne4vP5mbSWnJbO+51IMOXJB67QiYCSBrubbPRg= github.com/yusufpapurcu/wmi v1.2.2/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= +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.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ= go.etcd.io/bbolt v1.3.7 h1:j+zJOnnEjF/kyHlDDgGnVL/AIqIJPq8UoB2GSNfkUfQ= @@ -974,6 +994,7 @@ golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnf 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-20190422162423-af44ce270edf/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= +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= @@ -1035,6 +1056,7 @@ golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73r golang.org/x/net v0.0.0-20180530234432-1e491301e022/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180611182652-db08ff08e862/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-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -1137,6 +1159,7 @@ golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190419153524-e8e3143a4f4a/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-20190515120540-06a5c4944438/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190523142557-0e01d883c5c5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= diff --git a/internal/protohcl/any.go b/internal/protohcl/any.go new file mode 100644 index 0000000000..bc2a8c6faa --- /dev/null +++ b/internal/protohcl/any.go @@ -0,0 +1,114 @@ +package protohcl + +import ( + "fmt" + "strings" + + "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/reflect/protoreflect" + "google.golang.org/protobuf/reflect/protoregistry" + "google.golang.org/protobuf/types/known/anypb" +) + +const wellKnownTypeAny = "google.protobuf.Any" + +type AnyTypeProvider interface { + AnyType(*UnmarshalContext, MessageDecoder) (protoreflect.FullName, MessageDecoder, error) +} + +type AnyTypeURLProvider struct { + TypeURLFieldName string +} + +func (p *AnyTypeURLProvider) AnyType(ctx *UnmarshalContext, decoder MessageDecoder) (protoreflect.FullName, MessageDecoder, error) { + typeURLFieldName := "type_url" + if p != nil { + typeURLFieldName = p.TypeURLFieldName + } + + var typeURL *IterField + err := decoder.EachField(FieldIterator{ + Desc: (&anypb.Any{}).ProtoReflect().Descriptor(), + Func: func(field *IterField) error { + if field.Name == typeURLFieldName { + typeURL = field + } + return nil + }, + IgnoreUnknown: true, + }) + if err != nil { + return "", nil, err + } + + if typeURL == nil || typeURL.Val == nil { + return "", nil, fmt.Errorf("%s field is required to decode Any", typeURLFieldName) + } + + url, err := stringFromCty(*typeURL.Val) + if err != nil { + return "", nil, err + } + + slashIdx := strings.LastIndex(url, "/") + typeName := url + // strip all "hostname" parts of the URL path + if slashIdx > 1 && slashIdx+1 < len(url) { + typeName = url[slashIdx+1:] + } + + return protoreflect.FullName(typeName), decoder.SkipFields(typeURLFieldName), nil +} + +func (u UnmarshalOptions) decodeAny(ctx *UnmarshalContext, decoder MessageDecoder, msg protoreflect.Message) error { + var typeProvider AnyTypeProvider = &AnyTypeURLProvider{TypeURLFieldName: "type_url"} + if u.AnyTypeProvider != nil { + typeProvider = u.AnyTypeProvider + } + + var ( + typeName protoreflect.FullName + err error + ) + typeName, decoder, err = typeProvider.AnyType(ctx, decoder) + if err != nil { + return fmt.Errorf("error getting type for Any field: %w", err) + } + + // the type.googleapis.come/ should be optional + mt, err := protoregistry.GlobalTypes.FindMessageByName(typeName) + if err != nil { + return fmt.Errorf("error looking up type information for %s: %w", typeName, err) + } + + newMsg := mt.New() + + err = u.decodeMessage(&UnmarshalContext{ + Parent: ctx.Parent, + Name: ctx.Name, + Message: newMsg, + }, decoder, newMsg) + if err != nil { + return err + } + + enc, err := proto.Marshal(newMsg.Interface()) + if err != nil { + return fmt.Errorf("error marshalling Any data as protobuf value: %w", err) + } + + anyValue := msg.Interface().(*anypb.Any) + + // This will look like . and not quite like a full URL with a path + anyValue.TypeUrl = string(newMsg.Descriptor().FullName()) + anyValue.Value = enc + + return nil +} + +func isAnyField(desc protoreflect.FieldDescriptor) bool { + if desc.Kind() != protoreflect.MessageKind { + return false + } + return desc.Message().FullName() == wellKnownTypeAny +} diff --git a/internal/protohcl/attributes.go b/internal/protohcl/attributes.go new file mode 100644 index 0000000000..1339a3be22 --- /dev/null +++ b/internal/protohcl/attributes.go @@ -0,0 +1,154 @@ +package protohcl + +import ( + "fmt" + + "github.com/pkg/errors" + "github.com/zclconf/go-cty/cty" + "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/reflect/protoreflect" +) + +func (u UnmarshalOptions) decodeAttribute(ctx *UnmarshalContext, newMessage newMessageFn, f protoreflect.FieldDescriptor, val cty.Value, listAllowed bool) (protoreflect.Value, error) { + if f.IsMap() { + return u.decodeAttributeToMap(ctx, newMessage, f, val) + } + + if f.IsList() && listAllowed { + return u.decodeAttributeToList(ctx, newMessage, f, val) + } + + ok, value, err := decodeAttributeToWellKnownType(f, val) + if ok { + return value, errors.Wrapf(err, "%s: Failed to unmarshal argument %s", ctx.ErrorRange(), ctx.Name) + } + + ok, value, err = u.decodeAttributeToMessage(ctx, newMessage, f, val) + if ok { + return value, err + } + + value, err = decodeAttributeToPrimitive(f, val) + if err != nil { + return value, errors.Wrapf(err, "%s: Failed to unmarshal argument %s", ctx.ErrorRange(), ctx.Name) + } + return value, nil +} + +func (u UnmarshalOptions) decodeAttributeToMessage(ctx *UnmarshalContext, newMessage newMessageFn, desc protoreflect.FieldDescriptor, val cty.Value) (bool, protoreflect.Value, error) { + if desc.Kind() != protoreflect.MessageKind { + return false, protoreflect.Value{}, nil + } + + msg := newMessage().Message() + + ctx = &UnmarshalContext{ + Parent: ctx.Parent, + Name: ctx.Name, + Message: msg, + Range: ctx.Range, + } + + // We have limited support for HCL functions, essentially just those that + // return a protobuf message (like the resource `gvk` function) in which + // case, the message will be wrapped in a cty capsule. + if val.Type().IsCapsuleType() { + msg, ok := val.EncapsulatedValue().(proto.Message) + if ok { + return true, protoreflect.ValueOf(msg.ProtoReflect()), nil + } else { + return true, protoreflect.Value{}, fmt.Errorf("expected encapsulated value to be a message, actual type: %T", val.EncapsulatedValue()) + } + } + + if !val.Type().IsObjectType() { + return false, protoreflect.Value{}, nil + } + + decoder := newObjectDecoder(val, u.FieldNamer, ctx.ErrorRange()) + + if err := u.decodeMessage(ctx, decoder, msg); err != nil { + return true, protoreflect.Value{}, err + } + return true, protoreflect.ValueOf(msg), nil +} + +func (u UnmarshalOptions) decodeAttributeToList(ctx *UnmarshalContext, newMessage newMessageFn, desc protoreflect.FieldDescriptor, value cty.Value) (protoreflect.Value, error) { + if value.IsNull() { + return protoreflect.Value{}, nil + } + + valueType := value.Type() + if !valueType.IsListType() && !valueType.IsTupleType() { + return protoreflect.Value{}, fmt.Errorf("expected list/tuple type after HCL decode but the actual type was %s", valueType.FriendlyName()) + } + + if value.LengthInt() < 1 { + return protoreflect.Value{}, nil + } + + protoList := newMessage().List() + + var err error + var idx int + value.ForEachElement(func(_ cty.Value, val cty.Value) bool { + var protoVal protoreflect.Value + protoVal, err = u.decodeAttribute(&UnmarshalContext{ + Parent: ctx, + Name: fmt.Sprintf("%s[%d]", u.FieldNamer.NameField(desc), idx), + }, protoList.NewElement, desc, val, false) + if err != nil { + return true + } + + idx++ + protoList.Append(protoVal) + return false + }) + if err != nil { + return protoreflect.Value{}, err + } + + return protoreflect.ValueOfList(protoList), nil +} + +func (u UnmarshalOptions) decodeAttributeToMap(ctx *UnmarshalContext, newMessage newMessageFn, desc protoreflect.FieldDescriptor, value cty.Value) (protoreflect.Value, error) { + if value.IsNull() { + return protoreflect.Value{}, nil + } + + valueType := value.Type() + if !valueType.IsMapType() && !valueType.IsObjectType() { + return protoreflect.Value{}, fmt.Errorf("expected map/object type after HCL decode but the actual type was %s", valueType.FriendlyName()) + } + + if value.LengthInt() < 1 { + return protoreflect.Value{}, nil + } + + protoMap := newMessage().Map() + protoValueDesc := desc.MapValue() + var err error + + value.ForEachElement(func(key cty.Value, val cty.Value) (stop bool) { + var protoVal protoreflect.Value + protoVal, err = u.decodeAttribute(&UnmarshalContext{ + Parent: ctx, + Name: fmt.Sprintf("%s[%q]", u.FieldNamer.NameField(desc), key.AsString()), + Message: nil, // TODO: what should this really be? + }, protoMap.NewValue, protoValueDesc, val, false) + if err != nil { + return true + } + if protoVal.IsValid() { + // HCL doesn't support non-string keyed maps so we blindly use string keys + protoMap.Set(protoreflect.ValueOfString(key.AsString()).MapKey(), protoVal) + } + return false + }) + if err != nil { + return protoreflect.Value{}, err + } + + return protoreflect.ValueOfMap(protoMap), nil +} diff --git a/internal/protohcl/blocks.go b/internal/protohcl/blocks.go new file mode 100644 index 0000000000..dd156f29a4 --- /dev/null +++ b/internal/protohcl/blocks.go @@ -0,0 +1,112 @@ +package protohcl + +import ( + "fmt" + + "github.com/hashicorp/hcl/v2" + "google.golang.org/protobuf/reflect/protoreflect" +) + +func (u UnmarshalOptions) decodeBlocks(ctx *UnmarshalContext, blocks hcl.Blocks, msg protoreflect.Message, f protoreflect.FieldDescriptor) (protoreflect.Value, error) { + if f.Kind() != protoreflect.MessageKind { + return protoreflect.Value{}, fmt.Errorf("only protobuf message kinds can use HCL block syntax") + } + + if f.IsMap() { + return u.decodeBlocksToMap(ctx, blocks, msg, f) + } + + if f.IsList() { + return u.decodeBlocksToList(ctx, blocks, msg, f) + } + + return u.decodeBlocksToMessage(ctx, blocks, msg, f) +} + +func (u UnmarshalOptions) decodeBlocksToMap(ctx *UnmarshalContext, blocks hcl.Blocks, msg protoreflect.Message, f protoreflect.FieldDescriptor) (protoreflect.Value, error) { + val := msg.NewField(f) + mapVal := val.Map() + + for _, block := range blocks { + if len(block.Labels) != 1 { + return protoreflect.Value{}, fmt.Errorf("protobuf map fields must have 1 HCL block label") + } + + key := protoreflect.ValueOfString(block.Labels[0]) + value := mapVal.NewValue() + msgVal := value.Message() + + err := u.decodeMessage( + &UnmarshalContext{ + Parent: ctx, + Name: fmt.Sprintf("%s[%q]", u.FieldNamer.NameField(f), block.Labels[0]), + Message: msgVal, + Range: block.DefRange, + }, + u.bodyDecoder(block.Body), + msgVal, + ) + if err != nil { + return protoreflect.Value{}, err + } + + mapVal.Set(key.MapKey(), value) + } + return val, nil +} + +func (u UnmarshalOptions) decodeBlocksToList(ctx *UnmarshalContext, blocks hcl.Blocks, msg protoreflect.Message, f protoreflect.FieldDescriptor) (protoreflect.Value, error) { + val := msg.NewField(f) + listVal := val.List() + + var err error + for idx, block := range blocks { + if len(block.Labels) > 0 { + return protoreflect.Value{}, fmt.Errorf("repeated protobuf fields must not have HCL block labels") + } + elem := listVal.NewElement() + elemMsg := elem.Message() + + err = u.decodeMessage( + &UnmarshalContext{ + Parent: ctx, + Name: fmt.Sprintf("%s[%d]", u.FieldNamer.NameField(f), idx), + Message: elemMsg, + Range: block.DefRange, + }, + u.bodyDecoder(block.Body), + elemMsg, + ) + if err != nil { + return protoreflect.Value{}, err + } + listVal.Append(elem) + } + + return val, nil +} + +func (u UnmarshalOptions) decodeBlocksToMessage(ctx *UnmarshalContext, blocks hcl.Blocks, msg protoreflect.Message, f protoreflect.FieldDescriptor) (protoreflect.Value, error) { + if len(blocks) > 1 { + return protoreflect.Value{}, fmt.Errorf("only one HCL block may be specified for a non-repeated protobuf Message") + } + + val := msg.NewField(f) + valMsg := val.Message() + + err := u.decodeMessage( + &UnmarshalContext{ + Parent: ctx, + Name: blocks[0].Type, + Message: valMsg, + Range: blocks[0].DefRange, + }, + u.bodyDecoder(blocks[0].Body), + valMsg, + ) + if err != nil { + return protoreflect.Value{}, err + } + + return val, nil +} diff --git a/internal/protohcl/cty.go b/internal/protohcl/cty.go new file mode 100644 index 0000000000..2a0ec9ba24 --- /dev/null +++ b/internal/protohcl/cty.go @@ -0,0 +1,146 @@ +package protohcl + +import ( + "encoding/base64" + "fmt" + + "github.com/zclconf/go-cty/cty" + "github.com/zclconf/go-cty/cty/gocty" +) + +func boolFromCty(val cty.Value) (bool, error) { + if val.Type() != cty.Bool { + return false, fmt.Errorf("expected value of type %s but actual type is %s", cty.Bool.FriendlyName(), val.Type().FriendlyName()) + } + + if val.IsNull() { + return false, nil + } + + return val.True(), nil +} + +func int32FromCty(val cty.Value) (int32, error) { + if val.Type() != cty.Number { + return 0, fmt.Errorf("expected value of type %s but actual type is %s", cty.Number.FriendlyName(), val.Type().FriendlyName()) + } + + if val.IsNull() { + return 0, nil + } + + var goVal int32 + if err := gocty.FromCtyValue(val, &goVal); err != nil { + return 0, fmt.Errorf("error converting cty value of type %s to int32: %w", val.Type().FriendlyName(), err) + } + return goVal, nil +} + +func uint32FromCty(val cty.Value) (uint32, error) { + if val.Type() != cty.Number { + return 0, fmt.Errorf("expected value of type %s but actual type is %s", cty.Number.FriendlyName(), val.Type().FriendlyName()) + } + + if val.IsNull() { + return 0, nil + } + + var goVal uint32 + if err := gocty.FromCtyValue(val, &goVal); err != nil { + return 0, fmt.Errorf("error converting cty value of type %s to uint32: %w", val.Type().FriendlyName(), err) + } + return goVal, nil +} + +func int64FromCty(val cty.Value) (int64, error) { + if val.Type() != cty.Number { + return 0, fmt.Errorf("expected value of type %s but actual type is %s", cty.Number.FriendlyName(), val.Type().FriendlyName()) + } + + if val.IsNull() { + return 0, nil + } + + var goVal int64 + if err := gocty.FromCtyValue(val, &goVal); err != nil { + return 0, fmt.Errorf("error converting cty value of type %s to int64: %w", val.Type().FriendlyName(), err) + } + return goVal, nil +} + +func uint64FromCty(val cty.Value) (uint64, error) { + if val.Type() != cty.Number { + return 0, fmt.Errorf("expected value of type %s but actual type is %s", cty.Number.FriendlyName(), val.Type().FriendlyName()) + } + + if val.IsNull() { + return 0, nil + } + + var goVal uint64 + if err := gocty.FromCtyValue(val, &goVal); err != nil { + return 0, fmt.Errorf("error converting cty value of type %s to uint64: %w", val.Type().FriendlyName(), err) + } + return goVal, nil +} + +func floatFromCty(val cty.Value) (float32, error) { + if val.Type() != cty.Number { + return 0, fmt.Errorf("expected value of type %s but actual type is %s", cty.Number.FriendlyName(), val.Type().FriendlyName()) + } + + if val.IsNull() { + return 0, nil + } + + var goVal float32 + if err := gocty.FromCtyValue(val, &goVal); err != nil { + return 0, fmt.Errorf("error converting cty value of type %s to float32: %w", val.Type().FriendlyName(), err) + } + return goVal, nil +} + +func doubleFromCty(val cty.Value) (float64, error) { + if val.Type() != cty.Number { + return 0, fmt.Errorf("expected value of type %s but actual type is %s", cty.Number.FriendlyName(), val.Type().FriendlyName()) + } + + if val.IsNull() { + return 0, nil + } + + var goVal float64 + if err := gocty.FromCtyValue(val, &goVal); err != nil { + return 0, fmt.Errorf("error converting cty value of type %s to float64: %w", val.Type().FriendlyName(), err) + } + return goVal, nil +} + +func stringFromCty(val cty.Value) (string, error) { + if val.Type() != cty.String { + return "", fmt.Errorf("expected value of type %s but actual type is %s", cty.String.FriendlyName(), val.Type().FriendlyName()) + } + + if val.IsNull() { + return "", nil + } + return val.AsString(), nil +} + +func bytesFromCty(val cty.Value) ([]byte, error) { + if val.Type() != cty.String { + return nil, fmt.Errorf("expected value of type %s but actual type is %s", cty.String.FriendlyName(), val.Type().FriendlyName()) + } + + if val.IsNull() { + return nil, nil + } + + encoded := val.AsString() + decoded, err := base64.StdEncoding.DecodeString(encoded) + if err != nil { + return nil, fmt.Errorf("error base64 decoding byte string: %w", err) + } + + return decoded, nil +} diff --git a/internal/protohcl/decoder.go b/internal/protohcl/decoder.go new file mode 100644 index 0000000000..23735acd71 --- /dev/null +++ b/internal/protohcl/decoder.go @@ -0,0 +1,284 @@ +package protohcl + +import ( + "fmt" + "sort" + + "github.com/hashicorp/hcl/v2" + "github.com/zclconf/go-cty/cty" + "github.com/zclconf/go-cty/cty/function" + "google.golang.org/protobuf/reflect/protoreflect" +) + +// MessageDecoder provides an abstract way to decode protobuf messages from HCL +// blocks or objects. +type MessageDecoder interface { + // EachField calls the given iterator for each field provided in the HCL source. + EachField(iter FieldIterator) error + + // SkipFields returns a MessageDecoder that skips over the given fields. It is + // primarily used for doing two-pass decoding of protobuf `Any` fields. + SkipFields(fields ...string) MessageDecoder +} + +// IterField represents a field discovered by the MessageDecoder. +type IterField struct { + // Name is the HCL name of the field. + Name string + + // Desc is the protobuf field descriptor. + Desc protoreflect.FieldDescriptor + + // Val is the field value, only if it was given using HCL attribute syntax. + Val *cty.Value + + // Blocks contains the HCL blocks that were given for this field. + Blocks []*hcl.Block + + // Range determines where in the HCL source the field was given, it is useful + // for error messages. + Range hcl.Range +} + +// FieldIterator is given to MessageDecoder.EachField to iterate over all of the +// fields in a given HCL block or object. +type FieldIterator struct { + // IgnoreUnknown instructs the MessageDecoder to skip over any fields not + // included in Desc. + IgnoreUnknown bool + + // Desc is the protobuf descriptor for the message the caller is decoding into. + // It is used to determine which fields are valid. + Desc protoreflect.MessageDescriptor + + // Func is called for each field in the given HCL block or object. + Func func(field *IterField) error +} + +func newBodyDecoder( + body hcl.Body, + namer FieldNamer, + functions map[string]function.Function, +) MessageDecoder { + return bodyDecoder{ + body: body, + namer: namer, + functions: functions, + skipFields: make(map[string]struct{}), + } +} + +type bodyDecoder struct { + body hcl.Body + namer FieldNamer + functions map[string]function.Function + skipFields map[string]struct{} +} + +func (bd bodyDecoder) EachField(iter FieldIterator) error { + schema, err := bd.schema(iter.Desc) + if err != nil { + return err + } + + var ( + content *hcl.BodyContent + diags hcl.Diagnostics + ) + if iter.IgnoreUnknown { + content, _, diags = bd.body.PartialContent(schema) + } else { + content, diags = bd.body.Content(schema) + } + if diags.HasErrors() { + return diags + } + + fields := make([]*IterField, 0) + + for _, attr := range content.Attributes { + if _, ok := bd.skipFields[attr.Name]; ok { + continue + } + + desc := bd.namer.GetField(iter.Desc.Fields(), attr.Name) + + val, err := attr.Expr.Value(&hcl.EvalContext{Functions: bd.functions}) + if err != nil { + return err + } + + fields = append(fields, &IterField{ + Name: attr.Name, + Desc: desc, + Val: &val, + Range: attr.Expr.Range(), + }) + } + + for blockType, blocks := range content.Blocks.ByType() { + if _, ok := bd.skipFields[blockType]; ok { + continue + } + + desc := bd.namer.GetField(iter.Desc.Fields(), blockType) + + fields = append(fields, &IterField{ + Name: blockType, + Desc: desc, + Blocks: blocks, + }) + } + + // Always handle Any fields last, as decoding them may require type information + // gathered from other fields (e.g. as in the case of Resource GVKs). + sort.Slice(fields, func(a, b int) bool { + if isAnyField(fields[b].Desc) && !isAnyField(fields[a].Desc) { + return true + } + return a < b + }) + + for _, field := range fields { + if err := iter.Func(field); err != nil { + return err + } + } + + return nil +} + +func (bd bodyDecoder) SkipFields(fields ...string) MessageDecoder { + skip := make(map[string]struct{}, len(fields)+len(bd.skipFields)) + for k, v := range bd.skipFields { + skip[k] = v + } + for _, field := range fields { + skip[field] = struct{}{} + } + + // Note: we rely on the fact bd isn't a pointer to copy the struct here. + bd.skipFields = skip + return bd +} + +func (bd bodyDecoder) schema(desc protoreflect.MessageDescriptor) (*hcl.BodySchema, error) { + var schema hcl.BodySchema + + fields := desc.Fields() + for i := 0; i < fields.Len(); i++ { + f := fields.Get(i) + + kind := f.Kind() + // maps are special and whether they use block or attribute syntax depends + // on the value type + if f.IsMap() { + valueDesc := f.MapValue() + valueKind := valueDesc.Kind() + + wktHint := wellKnownTypeSchemaHint(valueDesc) + + // Message types should generally be encoded as blocks unless its a special Well Known Type + // that should use attribute encoding + if valueKind == protoreflect.MessageKind && wktHint != wellKnownAttribute { + schema.Blocks = append(schema.Blocks, hcl.BlockHeaderSchema{ + Type: bd.namer.NameField(f), + LabelNames: []string{"key"}, + }) + continue + } + + // non-message types or Well Known Message types that need attribute encoding + // get decoded as attributes + schema.Attributes = append(schema.Attributes, hcl.AttributeSchema{ + Name: bd.namer.NameField(f), + }) + continue + } + + wktHint := wellKnownTypeSchemaHint(f) + + // message types generally will use block syntax unless its a well known + // message type that requires attribute syntax specifically. + if kind == protoreflect.MessageKind && wktHint != wellKnownAttribute { + schema.Blocks = append(schema.Blocks, hcl.BlockHeaderSchema{ + Type: bd.namer.NameField(f), + }) + } + + // by default use attribute encoding + // - primitives + // - repeated primitives + // - Well Known Types requiring attribute syntax + // - repeated Well Known Types requiring attribute syntax + schema.Attributes = append(schema.Attributes, hcl.AttributeSchema{ + Name: bd.namer.NameField(f), + }) + continue + } + + // Add skipped fields to the schema so HCL doesn't throw an error when it finds them. + for field := range bd.skipFields { + schema.Attributes = append(schema.Attributes, hcl.AttributeSchema{Name: field}) + schema.Blocks = append(schema.Blocks, hcl.BlockHeaderSchema{Type: field}) + } + + return &schema, nil +} + +func newObjectDecoder(object cty.Value, namer FieldNamer, rng hcl.Range) MessageDecoder { + return objectDecoder{ + object: object, + namer: namer, + rng: rng, + skipFields: make(map[string]struct{}), + } +} + +type objectDecoder struct { + object cty.Value + namer FieldNamer + rng hcl.Range + skipFields map[string]struct{} +} + +func (od objectDecoder) EachField(iter FieldIterator) error { + for attr := range od.object.Type().AttributeTypes() { + if _, ok := od.skipFields[attr]; ok { + continue + } + + desc := od.namer.GetField(iter.Desc.Fields(), attr) + if desc == nil { + if iter.IgnoreUnknown { + continue + } else { + return fmt.Errorf("%s: Unsupported argument; An argument named %q is not expected here.", od.rng, attr) + } + } + + val := od.object.GetAttr(attr) + if err := iter.Func(&IterField{ + Name: attr, + Desc: desc, + Val: &val, + }); err != nil { + return err + } + } + return nil +} + +func (od objectDecoder) SkipFields(fields ...string) MessageDecoder { + skip := make(map[string]struct{}, len(fields)+len(od.skipFields)) + for k, v := range od.skipFields { + skip[k] = v + } + for _, field := range fields { + skip[field] = struct{}{} + } + + // Note: we rely on the fact od isn't a pointer to copy the struct here. + od.skipFields = skip + return od +} diff --git a/internal/protohcl/doc.go b/internal/protohcl/doc.go new file mode 100644 index 0000000000..aef25a109e --- /dev/null +++ b/internal/protohcl/doc.go @@ -0,0 +1,76 @@ +// The protohcl package aims to define a canonical way to translate between +// protobuf and HCL encodings of data. +// +// Similar to google.golang.org/protobuf/encoding/protojson it intends to be +// opinionated about what the canonical HCL representation of a protobuf type +// should be. +// +// As HCL is a user centric data format as opposed to JSON/Protobuf which +// are intended to be used more by machines, efficiency is not a primary goal +// of this package as it is expected that users are either doing the encoding to and +// decoding from HCL at the edge (such as within the Consul CLI) or that even +// when done on servers, the rate that servers perform these translations should +// be low enough to have any inefficiency produce a tangible performance impact. +// +// HCL has two different syntaxes that could be used to represent data: attribute and block +// +// This implementation chooses to represent primitive values, enums and the well known wrapper types and +// collections of these values (maps and repeated fields) with attribute syntax. Other messages and collections +// with message value types will be represented with block syntax for example +// +// message Foo { +// map map_of_ints = 1; +// map map_of_messages = 2; +// } +// +// would have HCL like: +// +// map_of_ints = { +// "foo": 1, +// "bar": 2, +// } +// map_of_messages "foo" { +// ...other fields +// } +// map_of_messages "bar" { +// ...other fields +// } +// +// Similar goes for list of primitives vs list of messages (except the block syntax uses no labels). The differences +// between primitive fields outside of a collection and a message field really just amounts to not having to specify +// the "=" between the field name and the "{" character. +// +// Field Mapping +// | proto3 | HCL Type | example | notes | +// |------------------------+---------------+---------+---------------------------------------------------------------------------------+ +// | message | Object | | Represented as a block | +// | enum | String | | | +// | map | Map | | All keys are converted to/from strings. | +// | repeated V | List | | | +// | bool | Bool | | | +// | string | String | | | +// | bytes | base64 String | | | +// | int32, fixed32, uint32 | Number | | | +// | int64, fixed64, uint64 | Number | | | +// | float, double | Number | | | +// +// ----- Well Known Types ----- +// +// | Any | Object | | | +// | Timestamp | String | | RFC 3339 compliant | +// | Duration | String | | String form is what would be accepted by time.ParseDuration | +// | Struct | Map | | | +// | Empty | Object | | An object with no fields | +// | BoolValue | Bool | | Mostly the same as a regular bool except null values in the HCL are preserved | +// | BytesValue | Bytes | | Mostly the same as a regular bytes except null values in the HCL are preserved | +// | StringValue | String | | Mostly the same as a regular string except null values in the HCL are preserved | +// | FloatValue | Number | | Mostly the same as a regular float except null values in the HCL are preserved | +// | DoubleValue | Number | | Mostly the same as a regular double except null values in the HCL are preserved | +// | Int32Value | Number | | Mostly the same as a regular int32 except null values in the HCL are preserved | +// | UInt32Value | Number | | Mostly the same as a regular uint32 except null values in the HCL are preserved | +// | Int64Value | Number | | Mostly the same as a regular int64 except null values in the HCL are preserved | +// | UInt64Value | Number | | Mostly the same as a regular uint64 except null values in the HCL are preserved | +// | FieldMask | String | | Each string of the FieldMask will be joined with a '.' | +// + +package protohcl diff --git a/internal/protohcl/naming.go b/internal/protohcl/naming.go new file mode 100644 index 0000000000..14f83f1f48 --- /dev/null +++ b/internal/protohcl/naming.go @@ -0,0 +1,18 @@ +package protohcl + +import "google.golang.org/protobuf/reflect/protoreflect" + +type FieldNamer interface { + NameField(protoreflect.FieldDescriptor) string + GetField(protoreflect.FieldDescriptors, string) protoreflect.FieldDescriptor +} + +type textFieldNamer struct{} + +func (textFieldNamer) NameField(fd protoreflect.FieldDescriptor) string { + return fd.TextName() +} + +func (textFieldNamer) GetField(fds protoreflect.FieldDescriptors, name string) protoreflect.FieldDescriptor { + return fds.ByTextName(name) +} diff --git a/internal/protohcl/oneof.go b/internal/protohcl/oneof.go new file mode 100644 index 0000000000..798c3a78d6 --- /dev/null +++ b/internal/protohcl/oneof.go @@ -0,0 +1,51 @@ +package protohcl + +import ( + "fmt" + "strings" + + "google.golang.org/protobuf/reflect/protoreflect" +) + +type oneOfTracker struct { + namer FieldNamer + set map[protoreflect.FullName]string +} + +func newOneOfTracker(namer FieldNamer) *oneOfTracker { + return &oneOfTracker{ + namer: namer, + set: make(map[protoreflect.FullName]string), + } +} + +func (o *oneOfTracker) markFieldAsSet(desc protoreflect.FieldDescriptor) error { + oneof := desc.ContainingOneof() + if oneof == nil { + return nil + } + + oneOfName := oneof.FullName() + + if otherFieldName, ok := o.set[oneOfName]; ok { + oneOfFields := oneof.Fields() + var builder strings.Builder + + for i := 0; i < oneOfFields.Len(); i++ { + name := o.namer.NameField(oneOfFields.Get(i)) + + if i == oneOfFields.Len()-1 { + builder.WriteString(fmt.Sprintf("%q", name)) + } else if i == oneOfFields.Len()-2 { + builder.WriteString(fmt.Sprintf("%q and ", name)) + } else { + builder.WriteString(fmt.Sprintf("%q, ", name)) + } + } + + return fmt.Errorf("Cannot set %q because %q was previously set. Only one of %s may be set.", o.namer.NameField(desc), otherFieldName, builder.String()) + } + + o.set[oneOfName] = o.namer.NameField(desc) + return nil +} diff --git a/internal/protohcl/primitives.go b/internal/protohcl/primitives.go new file mode 100644 index 0000000000..8d2f1c0250 --- /dev/null +++ b/internal/protohcl/primitives.go @@ -0,0 +1,138 @@ +package protohcl + +import ( + "fmt" + + "github.com/zclconf/go-cty/cty" + "google.golang.org/protobuf/reflect/protoreflect" +) + +func decodeAttributeToPrimitive(desc protoreflect.FieldDescriptor, val cty.Value) (protoreflect.Value, error) { + switch kind := desc.Kind(); kind { + case protoreflect.BoolKind: + return protoBoolFromCty(val) + case protoreflect.EnumKind: + return protoEnumFromCty(desc, val) + case protoreflect.Int32Kind: + return protoInt32FromCty(val) + case protoreflect.Sint32Kind: + return protoInt32FromCty(val) + case protoreflect.Uint32Kind: + return protoUint32FromCty(val) + case protoreflect.Int64Kind: + return protoInt64FromCty(val) + case protoreflect.Sint64Kind: + return protoInt64FromCty(val) + case protoreflect.Uint64Kind: + return protoUint64FromCty(val) + case protoreflect.Sfixed32Kind: + return protoInt32FromCty(val) + case protoreflect.Fixed32Kind: + return protoUint32FromCty(val) + case protoreflect.FloatKind: + return protoFloatFromCty(val) + case protoreflect.Sfixed64Kind: + return protoInt64FromCty(val) + case protoreflect.Fixed64Kind: + return protoUint64FromCty(val) + case protoreflect.DoubleKind: + return protoDoubleFromCty(val) + case protoreflect.StringKind: + return protoStringFromCty(val) + case protoreflect.BytesKind: + return protoBytesFromCty(val) + default: + return protoreflect.Value{}, fmt.Errorf("unknown primitive protobuf kind: %q", kind.String()) + } +} + +func protoBoolFromCty(val cty.Value) (protoreflect.Value, error) { + goVal, err := boolFromCty(val) + if err != nil { + return protoreflect.Value{}, err + } + return protoreflect.ValueOfBool(goVal), nil +} + +func protoEnumFromCty(desc protoreflect.FieldDescriptor, val cty.Value) (protoreflect.Value, error) { + if val.Type() != cty.String { + return protoreflect.Value{}, fmt.Errorf("expected value of type %s but actual type is %s", cty.String.FriendlyName(), val.Type().FriendlyName()) + } + + if val.IsNull() { + defaultValDesc := desc.DefaultEnumValue() + return protoreflect.ValueOfEnum(defaultValDesc.Number()), nil + } + + valDesc := desc.Enum().Values().ByName(protoreflect.Name(val.AsString())) + if valDesc == nil { + defaultValDesc := desc.DefaultEnumValue() + return protoreflect.ValueOfEnum(defaultValDesc.Number()), nil + } + + return protoreflect.ValueOfEnum(valDesc.Number()), nil +} + +func protoInt32FromCty(val cty.Value) (protoreflect.Value, error) { + goVal, err := int32FromCty(val) + if err != nil { + return protoreflect.Value{}, err + } + return protoreflect.ValueOfInt32(goVal), nil +} + +func protoUint32FromCty(val cty.Value) (protoreflect.Value, error) { + goVal, err := uint32FromCty(val) + if err != nil { + return protoreflect.Value{}, err + } + return protoreflect.ValueOfUint32(goVal), nil +} + +func protoInt64FromCty(val cty.Value) (protoreflect.Value, error) { + goVal, err := int64FromCty(val) + if err != nil { + return protoreflect.Value{}, err + } + return protoreflect.ValueOfInt64(goVal), nil +} + +func protoUint64FromCty(val cty.Value) (protoreflect.Value, error) { + goVal, err := uint64FromCty(val) + if err != nil { + return protoreflect.Value{}, err + } + return protoreflect.ValueOfUint64(goVal), nil +} + +func protoFloatFromCty(val cty.Value) (protoreflect.Value, error) { + goVal, err := floatFromCty(val) + if err != nil { + return protoreflect.Value{}, err + } + return protoreflect.ValueOfFloat32(goVal), nil +} + +func protoDoubleFromCty(val cty.Value) (protoreflect.Value, error) { + goVal, err := doubleFromCty(val) + if err != nil { + return protoreflect.Value{}, err + } + return protoreflect.ValueOfFloat64(goVal), nil +} + +func protoStringFromCty(val cty.Value) (protoreflect.Value, error) { + goVal, err := stringFromCty(val) + if err != nil { + return protoreflect.Value{}, err + } + return protoreflect.ValueOfString(goVal), nil +} + +func protoBytesFromCty(val cty.Value) (protoreflect.Value, error) { + goVal, err := bytesFromCty(val) + if err != nil { + return protoreflect.Value{}, err + } + return protoreflect.ValueOfBytes(goVal), nil +} diff --git a/internal/protohcl/testproto/buf.gen.yaml b/internal/protohcl/testproto/buf.gen.yaml new file mode 100644 index 0000000000..1adab9847c --- /dev/null +++ b/internal/protohcl/testproto/buf.gen.yaml @@ -0,0 +1,9 @@ +version: v1 +managed: + enabled: true + go_package_prefix: + default: github.com/hashicorp/consul/internal/protohcl/testproto +plugins: + - name: go + out: . + opt: paths=source_relative diff --git a/internal/protohcl/testproto/example.pb.go b/internal/protohcl/testproto/example.pb.go new file mode 100644 index 0000000000..06aba98dab --- /dev/null +++ b/internal/protohcl/testproto/example.pb.go @@ -0,0 +1,994 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.30.0 +// protoc (unknown) +// source: example.proto + +package testproto + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + anypb "google.golang.org/protobuf/types/known/anypb" + durationpb "google.golang.org/protobuf/types/known/durationpb" + emptypb "google.golang.org/protobuf/types/known/emptypb" + structpb "google.golang.org/protobuf/types/known/structpb" + timestamppb "google.golang.org/protobuf/types/known/timestamppb" + wrapperspb "google.golang.org/protobuf/types/known/wrapperspb" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type Protocol int32 + +const ( + Protocol_PROTOCOL_UNSPECIFIED Protocol = 0 + Protocol_PROTOCOL_TCP Protocol = 1 + Protocol_PROTOCOL_UDP Protocol = 2 +) + +// Enum value maps for Protocol. +var ( + Protocol_name = map[int32]string{ + 0: "PROTOCOL_UNSPECIFIED", + 1: "PROTOCOL_TCP", + 2: "PROTOCOL_UDP", + } + Protocol_value = map[string]int32{ + "PROTOCOL_UNSPECIFIED": 0, + "PROTOCOL_TCP": 1, + "PROTOCOL_UDP": 2, + } +) + +func (x Protocol) Enum() *Protocol { + p := new(Protocol) + *p = x + return p +} + +func (x Protocol) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (Protocol) Descriptor() protoreflect.EnumDescriptor { + return file_example_proto_enumTypes[0].Descriptor() +} + +func (Protocol) Type() protoreflect.EnumType { + return &file_example_proto_enumTypes[0] +} + +func (x Protocol) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use Protocol.Descriptor instead. +func (Protocol) EnumDescriptor() ([]byte, []int) { + return file_example_proto_rawDescGZIP(), []int{0} +} + +type Primitives struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + DoubleVal float64 `protobuf:"fixed64,1,opt,name=double_val,json=doubleVal,proto3" json:"double_val,omitempty"` + FloatVal float32 `protobuf:"fixed32,2,opt,name=float_val,json=floatVal,proto3" json:"float_val,omitempty"` + Int32Val int32 `protobuf:"varint,3,opt,name=int32_val,json=int32Val,proto3" json:"int32_val,omitempty"` + Int64Val int64 `protobuf:"varint,4,opt,name=int64_val,json=int64Val,proto3" json:"int64_val,omitempty"` + Uint32Val uint32 `protobuf:"varint,5,opt,name=uint32_val,json=uint32Val,proto3" json:"uint32_val,omitempty"` + Uint64Val uint64 `protobuf:"varint,6,opt,name=uint64_val,json=uint64Val,proto3" json:"uint64_val,omitempty"` + Sint32Val int32 `protobuf:"zigzag32,7,opt,name=sint32_val,json=sint32Val,proto3" json:"sint32_val,omitempty"` + Sint64Val int64 `protobuf:"zigzag64,8,opt,name=sint64_val,json=sint64Val,proto3" json:"sint64_val,omitempty"` + Fixed32Val uint32 `protobuf:"fixed32,9,opt,name=fixed32_val,json=fixed32Val,proto3" json:"fixed32_val,omitempty"` + Fixed64Val uint64 `protobuf:"fixed64,10,opt,name=fixed64_val,json=fixed64Val,proto3" json:"fixed64_val,omitempty"` + Sfixed32Val int32 `protobuf:"fixed32,11,opt,name=sfixed32_val,json=sfixed32Val,proto3" json:"sfixed32_val,omitempty"` + Sfixed64Val int64 `protobuf:"fixed64,12,opt,name=sfixed64_val,json=sfixed64Val,proto3" json:"sfixed64_val,omitempty"` + BoolVal bool `protobuf:"varint,13,opt,name=bool_val,json=boolVal,proto3" json:"bool_val,omitempty"` + StringVal string `protobuf:"bytes,14,opt,name=string_val,json=stringVal,proto3" json:"string_val,omitempty"` + ByteVal []byte `protobuf:"bytes,15,opt,name=byte_val,json=byteVal,proto3" json:"byte_val,omitempty"` +} + +func (x *Primitives) Reset() { + *x = Primitives{} + if protoimpl.UnsafeEnabled { + mi := &file_example_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Primitives) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Primitives) ProtoMessage() {} + +func (x *Primitives) ProtoReflect() protoreflect.Message { + mi := &file_example_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Primitives.ProtoReflect.Descriptor instead. +func (*Primitives) Descriptor() ([]byte, []int) { + return file_example_proto_rawDescGZIP(), []int{0} +} + +func (x *Primitives) GetDoubleVal() float64 { + if x != nil { + return x.DoubleVal + } + return 0 +} + +func (x *Primitives) GetFloatVal() float32 { + if x != nil { + return x.FloatVal + } + return 0 +} + +func (x *Primitives) GetInt32Val() int32 { + if x != nil { + return x.Int32Val + } + return 0 +} + +func (x *Primitives) GetInt64Val() int64 { + if x != nil { + return x.Int64Val + } + return 0 +} + +func (x *Primitives) GetUint32Val() uint32 { + if x != nil { + return x.Uint32Val + } + return 0 +} + +func (x *Primitives) GetUint64Val() uint64 { + if x != nil { + return x.Uint64Val + } + return 0 +} + +func (x *Primitives) GetSint32Val() int32 { + if x != nil { + return x.Sint32Val + } + return 0 +} + +func (x *Primitives) GetSint64Val() int64 { + if x != nil { + return x.Sint64Val + } + return 0 +} + +func (x *Primitives) GetFixed32Val() uint32 { + if x != nil { + return x.Fixed32Val + } + return 0 +} + +func (x *Primitives) GetFixed64Val() uint64 { + if x != nil { + return x.Fixed64Val + } + return 0 +} + +func (x *Primitives) GetSfixed32Val() int32 { + if x != nil { + return x.Sfixed32Val + } + return 0 +} + +func (x *Primitives) GetSfixed64Val() int64 { + if x != nil { + return x.Sfixed64Val + } + return 0 +} + +func (x *Primitives) GetBoolVal() bool { + if x != nil { + return x.BoolVal + } + return false +} + +func (x *Primitives) GetStringVal() string { + if x != nil { + return x.StringVal + } + return "" +} + +func (x *Primitives) GetByteVal() []byte { + if x != nil { + return x.ByteVal + } + return nil +} + +type NestedAndCollections struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Primitives *Primitives `protobuf:"bytes,1,opt,name=primitives,proto3" json:"primitives,omitempty"` + PrimitivesList []*Primitives `protobuf:"bytes,2,rep,name=primitives_list,json=primitivesList,proto3" json:"primitives_list,omitempty"` + PrimitivesMap map[string]*Primitives `protobuf:"bytes,3,rep,name=primitives_map,json=primitivesMap,proto3" json:"primitives_map,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` + ProtocolMap map[string]Protocol `protobuf:"bytes,4,rep,name=protocol_map,json=protocolMap,proto3" json:"protocol_map,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"varint,2,opt,name=value,proto3,enum=hashicorp.consul.internal.protohcl.testproto.Protocol"` + IntList []int32 `protobuf:"varint,5,rep,packed,name=int_list,json=intList,proto3" json:"int_list,omitempty"` +} + +func (x *NestedAndCollections) Reset() { + *x = NestedAndCollections{} + if protoimpl.UnsafeEnabled { + mi := &file_example_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *NestedAndCollections) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*NestedAndCollections) ProtoMessage() {} + +func (x *NestedAndCollections) ProtoReflect() protoreflect.Message { + mi := &file_example_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use NestedAndCollections.ProtoReflect.Descriptor instead. +func (*NestedAndCollections) Descriptor() ([]byte, []int) { + return file_example_proto_rawDescGZIP(), []int{1} +} + +func (x *NestedAndCollections) GetPrimitives() *Primitives { + if x != nil { + return x.Primitives + } + return nil +} + +func (x *NestedAndCollections) GetPrimitivesList() []*Primitives { + if x != nil { + return x.PrimitivesList + } + return nil +} + +func (x *NestedAndCollections) GetPrimitivesMap() map[string]*Primitives { + if x != nil { + return x.PrimitivesMap + } + return nil +} + +func (x *NestedAndCollections) GetProtocolMap() map[string]Protocol { + if x != nil { + return x.ProtocolMap + } + return nil +} + +func (x *NestedAndCollections) GetIntList() []int32 { + if x != nil { + return x.IntList + } + return nil +} + +type Wrappers struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + DoubleVal *wrapperspb.DoubleValue `protobuf:"bytes,1,opt,name=double_val,json=doubleVal,proto3" json:"double_val,omitempty"` + FloatVal *wrapperspb.FloatValue `protobuf:"bytes,2,opt,name=float_val,json=floatVal,proto3" json:"float_val,omitempty"` + Int32Val *wrapperspb.Int32Value `protobuf:"bytes,3,opt,name=int32_val,json=int32Val,proto3" json:"int32_val,omitempty"` + Int64Val *wrapperspb.Int64Value `protobuf:"bytes,4,opt,name=int64_val,json=int64Val,proto3" json:"int64_val,omitempty"` + Uint32Val *wrapperspb.UInt32Value `protobuf:"bytes,5,opt,name=uint32_val,json=uint32Val,proto3" json:"uint32_val,omitempty"` + Uint64Val *wrapperspb.UInt64Value `protobuf:"bytes,6,opt,name=uint64_val,json=uint64Val,proto3" json:"uint64_val,omitempty"` + BoolVal *wrapperspb.BoolValue `protobuf:"bytes,13,opt,name=bool_val,json=boolVal,proto3" json:"bool_val,omitempty"` + StringVal *wrapperspb.StringValue `protobuf:"bytes,14,opt,name=string_val,json=stringVal,proto3" json:"string_val,omitempty"` + BytesVal *wrapperspb.BytesValue `protobuf:"bytes,15,opt,name=bytes_val,json=bytesVal,proto3" json:"bytes_val,omitempty"` +} + +func (x *Wrappers) Reset() { + *x = Wrappers{} + if protoimpl.UnsafeEnabled { + mi := &file_example_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Wrappers) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Wrappers) ProtoMessage() {} + +func (x *Wrappers) ProtoReflect() protoreflect.Message { + mi := &file_example_proto_msgTypes[2] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Wrappers.ProtoReflect.Descriptor instead. +func (*Wrappers) Descriptor() ([]byte, []int) { + return file_example_proto_rawDescGZIP(), []int{2} +} + +func (x *Wrappers) GetDoubleVal() *wrapperspb.DoubleValue { + if x != nil { + return x.DoubleVal + } + return nil +} + +func (x *Wrappers) GetFloatVal() *wrapperspb.FloatValue { + if x != nil { + return x.FloatVal + } + return nil +} + +func (x *Wrappers) GetInt32Val() *wrapperspb.Int32Value { + if x != nil { + return x.Int32Val + } + return nil +} + +func (x *Wrappers) GetInt64Val() *wrapperspb.Int64Value { + if x != nil { + return x.Int64Val + } + return nil +} + +func (x *Wrappers) GetUint32Val() *wrapperspb.UInt32Value { + if x != nil { + return x.Uint32Val + } + return nil +} + +func (x *Wrappers) GetUint64Val() *wrapperspb.UInt64Value { + if x != nil { + return x.Uint64Val + } + return nil +} + +func (x *Wrappers) GetBoolVal() *wrapperspb.BoolValue { + if x != nil { + return x.BoolVal + } + return nil +} + +func (x *Wrappers) GetStringVal() *wrapperspb.StringValue { + if x != nil { + return x.StringVal + } + return nil +} + +func (x *Wrappers) GetBytesVal() *wrapperspb.BytesValue { + if x != nil { + return x.BytesVal + } + return nil +} + +type OneOf struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Types that are assignable to Data: + // + // *OneOf_Int32Val + // *OneOf_Primitives + Data isOneOf_Data `protobuf_oneof:"data"` +} + +func (x *OneOf) Reset() { + *x = OneOf{} + if protoimpl.UnsafeEnabled { + mi := &file_example_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *OneOf) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*OneOf) ProtoMessage() {} + +func (x *OneOf) ProtoReflect() protoreflect.Message { + mi := &file_example_proto_msgTypes[3] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use OneOf.ProtoReflect.Descriptor instead. +func (*OneOf) Descriptor() ([]byte, []int) { + return file_example_proto_rawDescGZIP(), []int{3} +} + +func (m *OneOf) GetData() isOneOf_Data { + if m != nil { + return m.Data + } + return nil +} + +func (x *OneOf) GetInt32Val() int32 { + if x, ok := x.GetData().(*OneOf_Int32Val); ok { + return x.Int32Val + } + return 0 +} + +func (x *OneOf) GetPrimitives() *Primitives { + if x, ok := x.GetData().(*OneOf_Primitives); ok { + return x.Primitives + } + return nil +} + +type isOneOf_Data interface { + isOneOf_Data() +} + +type OneOf_Int32Val struct { + Int32Val int32 `protobuf:"varint,1,opt,name=int32_val,json=int32Val,proto3,oneof"` +} + +type OneOf_Primitives struct { + Primitives *Primitives `protobuf:"bytes,2,opt,name=primitives,proto3,oneof"` // note repeated fields (including maps) are not allowed in oneofs +} + +func (*OneOf_Int32Val) isOneOf_Data() {} + +func (*OneOf_Primitives) isOneOf_Data() {} + +type NonDynamicWellKnown struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + EmptyVal *emptypb.Empty `protobuf:"bytes,1,opt,name=empty_val,json=emptyVal,proto3" json:"empty_val,omitempty"` + TimestampVal *timestamppb.Timestamp `protobuf:"bytes,2,opt,name=timestamp_val,json=timestampVal,proto3" json:"timestamp_val,omitempty"` + DurationVal *durationpb.Duration `protobuf:"bytes,3,opt,name=duration_val,json=durationVal,proto3" json:"duration_val,omitempty"` +} + +func (x *NonDynamicWellKnown) Reset() { + *x = NonDynamicWellKnown{} + if protoimpl.UnsafeEnabled { + mi := &file_example_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *NonDynamicWellKnown) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*NonDynamicWellKnown) ProtoMessage() {} + +func (x *NonDynamicWellKnown) ProtoReflect() protoreflect.Message { + mi := &file_example_proto_msgTypes[4] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use NonDynamicWellKnown.ProtoReflect.Descriptor instead. +func (*NonDynamicWellKnown) Descriptor() ([]byte, []int) { + return file_example_proto_rawDescGZIP(), []int{4} +} + +func (x *NonDynamicWellKnown) GetEmptyVal() *emptypb.Empty { + if x != nil { + return x.EmptyVal + } + return nil +} + +func (x *NonDynamicWellKnown) GetTimestampVal() *timestamppb.Timestamp { + if x != nil { + return x.TimestampVal + } + return nil +} + +func (x *NonDynamicWellKnown) GetDurationVal() *durationpb.Duration { + if x != nil { + return x.DurationVal + } + return nil +} + +type DynamicWellKnown struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + AnyVal *anypb.Any `protobuf:"bytes,1,opt,name=any_val,json=anyVal,proto3" json:"any_val,omitempty"` + StructVal *structpb.Struct `protobuf:"bytes,2,opt,name=struct_val,json=structVal,proto3" json:"struct_val,omitempty"` + AnyList []*anypb.Any `protobuf:"bytes,3,rep,name=any_list,json=anyList,proto3" json:"any_list,omitempty"` +} + +func (x *DynamicWellKnown) Reset() { + *x = DynamicWellKnown{} + if protoimpl.UnsafeEnabled { + mi := &file_example_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *DynamicWellKnown) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DynamicWellKnown) ProtoMessage() {} + +func (x *DynamicWellKnown) ProtoReflect() protoreflect.Message { + mi := &file_example_proto_msgTypes[5] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DynamicWellKnown.ProtoReflect.Descriptor instead. +func (*DynamicWellKnown) Descriptor() ([]byte, []int) { + return file_example_proto_rawDescGZIP(), []int{5} +} + +func (x *DynamicWellKnown) GetAnyVal() *anypb.Any { + if x != nil { + return x.AnyVal + } + return nil +} + +func (x *DynamicWellKnown) GetStructVal() *structpb.Struct { + if x != nil { + return x.StructVal + } + return nil +} + +func (x *DynamicWellKnown) GetAnyList() []*anypb.Any { + if x != nil { + return x.AnyList + } + return nil +} + +var File_example_proto protoreflect.FileDescriptor + +var file_example_proto_rawDesc = []byte{ + 0x0a, 0x0d, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, + 0x2c, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, + 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x68, 0x63, 0x6c, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1e, 0x67, + 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x64, + 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1b, 0x67, + 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x65, + 0x6d, 0x70, 0x74, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1f, 0x67, 0x6f, 0x6f, 0x67, + 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x74, 0x69, 0x6d, 0x65, + 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1e, 0x67, 0x6f, 0x6f, + 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x77, 0x72, 0x61, + 0x70, 0x70, 0x65, 0x72, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x19, 0x67, 0x6f, 0x6f, + 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x61, 0x6e, 0x79, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1c, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x73, 0x74, 0x72, 0x75, 0x63, 0x74, 0x2e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xdb, 0x03, 0x0a, 0x0a, 0x50, 0x72, 0x69, 0x6d, 0x69, 0x74, 0x69, + 0x76, 0x65, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x64, 0x6f, 0x75, 0x62, 0x6c, 0x65, 0x5f, 0x76, 0x61, + 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x01, 0x52, 0x09, 0x64, 0x6f, 0x75, 0x62, 0x6c, 0x65, 0x56, + 0x61, 0x6c, 0x12, 0x1b, 0x0a, 0x09, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x5f, 0x76, 0x61, 0x6c, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x02, 0x52, 0x08, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x56, 0x61, 0x6c, 0x12, + 0x1b, 0x0a, 0x09, 0x69, 0x6e, 0x74, 0x33, 0x32, 0x5f, 0x76, 0x61, 0x6c, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x05, 0x52, 0x08, 0x69, 0x6e, 0x74, 0x33, 0x32, 0x56, 0x61, 0x6c, 0x12, 0x1b, 0x0a, 0x09, + 0x69, 0x6e, 0x74, 0x36, 0x34, 0x5f, 0x76, 0x61, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, + 0x08, 0x69, 0x6e, 0x74, 0x36, 0x34, 0x56, 0x61, 0x6c, 0x12, 0x1d, 0x0a, 0x0a, 0x75, 0x69, 0x6e, + 0x74, 0x33, 0x32, 0x5f, 0x76, 0x61, 0x6c, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x09, 0x75, + 0x69, 0x6e, 0x74, 0x33, 0x32, 0x56, 0x61, 0x6c, 0x12, 0x1d, 0x0a, 0x0a, 0x75, 0x69, 0x6e, 0x74, + 0x36, 0x34, 0x5f, 0x76, 0x61, 0x6c, 0x18, 0x06, 0x20, 0x01, 0x28, 0x04, 0x52, 0x09, 0x75, 0x69, + 0x6e, 0x74, 0x36, 0x34, 0x56, 0x61, 0x6c, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x69, 0x6e, 0x74, 0x33, + 0x32, 0x5f, 0x76, 0x61, 0x6c, 0x18, 0x07, 0x20, 0x01, 0x28, 0x11, 0x52, 0x09, 0x73, 0x69, 0x6e, + 0x74, 0x33, 0x32, 0x56, 0x61, 0x6c, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x69, 0x6e, 0x74, 0x36, 0x34, + 0x5f, 0x76, 0x61, 0x6c, 0x18, 0x08, 0x20, 0x01, 0x28, 0x12, 0x52, 0x09, 0x73, 0x69, 0x6e, 0x74, + 0x36, 0x34, 0x56, 0x61, 0x6c, 0x12, 0x1f, 0x0a, 0x0b, 0x66, 0x69, 0x78, 0x65, 0x64, 0x33, 0x32, + 0x5f, 0x76, 0x61, 0x6c, 0x18, 0x09, 0x20, 0x01, 0x28, 0x07, 0x52, 0x0a, 0x66, 0x69, 0x78, 0x65, + 0x64, 0x33, 0x32, 0x56, 0x61, 0x6c, 0x12, 0x1f, 0x0a, 0x0b, 0x66, 0x69, 0x78, 0x65, 0x64, 0x36, + 0x34, 0x5f, 0x76, 0x61, 0x6c, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x06, 0x52, 0x0a, 0x66, 0x69, 0x78, + 0x65, 0x64, 0x36, 0x34, 0x56, 0x61, 0x6c, 0x12, 0x21, 0x0a, 0x0c, 0x73, 0x66, 0x69, 0x78, 0x65, + 0x64, 0x33, 0x32, 0x5f, 0x76, 0x61, 0x6c, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0f, 0x52, 0x0b, 0x73, + 0x66, 0x69, 0x78, 0x65, 0x64, 0x33, 0x32, 0x56, 0x61, 0x6c, 0x12, 0x21, 0x0a, 0x0c, 0x73, 0x66, + 0x69, 0x78, 0x65, 0x64, 0x36, 0x34, 0x5f, 0x76, 0x61, 0x6c, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x10, + 0x52, 0x0b, 0x73, 0x66, 0x69, 0x78, 0x65, 0x64, 0x36, 0x34, 0x56, 0x61, 0x6c, 0x12, 0x19, 0x0a, + 0x08, 0x62, 0x6f, 0x6f, 0x6c, 0x5f, 0x76, 0x61, 0x6c, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x08, 0x52, + 0x07, 0x62, 0x6f, 0x6f, 0x6c, 0x56, 0x61, 0x6c, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x74, 0x72, 0x69, + 0x6e, 0x67, 0x5f, 0x76, 0x61, 0x6c, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x74, + 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x12, 0x19, 0x0a, 0x08, 0x62, 0x79, 0x74, 0x65, 0x5f, + 0x76, 0x61, 0x6c, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x62, 0x79, 0x74, 0x65, 0x56, + 0x61, 0x6c, 0x22, 0xd8, 0x05, 0x0a, 0x14, 0x4e, 0x65, 0x73, 0x74, 0x65, 0x64, 0x41, 0x6e, 0x64, + 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x58, 0x0a, 0x0a, 0x70, + 0x72, 0x69, 0x6d, 0x69, 0x74, 0x69, 0x76, 0x65, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x38, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, + 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x68, 0x63, 0x6c, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x50, + 0x72, 0x69, 0x6d, 0x69, 0x74, 0x69, 0x76, 0x65, 0x73, 0x52, 0x0a, 0x70, 0x72, 0x69, 0x6d, 0x69, + 0x74, 0x69, 0x76, 0x65, 0x73, 0x12, 0x61, 0x0a, 0x0f, 0x70, 0x72, 0x69, 0x6d, 0x69, 0x74, 0x69, + 0x76, 0x65, 0x73, 0x5f, 0x6c, 0x69, 0x73, 0x74, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x38, + 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, + 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x68, 0x63, 0x6c, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x50, 0x72, + 0x69, 0x6d, 0x69, 0x74, 0x69, 0x76, 0x65, 0x73, 0x52, 0x0e, 0x70, 0x72, 0x69, 0x6d, 0x69, 0x74, + 0x69, 0x76, 0x65, 0x73, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x7c, 0x0a, 0x0e, 0x70, 0x72, 0x69, 0x6d, + 0x69, 0x74, 0x69, 0x76, 0x65, 0x73, 0x5f, 0x6d, 0x61, 0x70, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, + 0x32, 0x55, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, + 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x68, 0x63, 0x6c, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, + 0x4e, 0x65, 0x73, 0x74, 0x65, 0x64, 0x41, 0x6e, 0x64, 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, + 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x50, 0x72, 0x69, 0x6d, 0x69, 0x74, 0x69, 0x76, 0x65, 0x73, 0x4d, + 0x61, 0x70, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0d, 0x70, 0x72, 0x69, 0x6d, 0x69, 0x74, 0x69, + 0x76, 0x65, 0x73, 0x4d, 0x61, 0x70, 0x12, 0x76, 0x0a, 0x0c, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, + 0x6f, 0x6c, 0x5f, 0x6d, 0x61, 0x70, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x53, 0x2e, 0x68, + 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, + 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x68, 0x63, + 0x6c, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x4e, 0x65, 0x73, 0x74, + 0x65, 0x64, 0x41, 0x6e, 0x64, 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, + 0x2e, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x4d, 0x61, 0x70, 0x45, 0x6e, 0x74, 0x72, + 0x79, 0x52, 0x0b, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x4d, 0x61, 0x70, 0x12, 0x19, + 0x0a, 0x08, 0x69, 0x6e, 0x74, 0x5f, 0x6c, 0x69, 0x73, 0x74, 0x18, 0x05, 0x20, 0x03, 0x28, 0x05, + 0x52, 0x07, 0x69, 0x6e, 0x74, 0x4c, 0x69, 0x73, 0x74, 0x1a, 0x7a, 0x0a, 0x12, 0x50, 0x72, 0x69, + 0x6d, 0x69, 0x74, 0x69, 0x76, 0x65, 0x73, 0x4d, 0x61, 0x70, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, + 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, + 0x79, 0x12, 0x4e, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x38, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, + 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x68, 0x63, 0x6c, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, + 0x50, 0x72, 0x69, 0x6d, 0x69, 0x74, 0x69, 0x76, 0x65, 0x73, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, + 0x65, 0x3a, 0x02, 0x38, 0x01, 0x1a, 0x76, 0x0a, 0x10, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, + 0x6c, 0x4d, 0x61, 0x70, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x4c, 0x0a, 0x05, 0x76, + 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x36, 0x2e, 0x68, 0x61, 0x73, + 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, + 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x68, 0x63, 0x6c, 0x2e, + 0x74, 0x65, 0x73, 0x74, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, + 0x6f, 0x6c, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x9d, 0x04, + 0x0a, 0x08, 0x57, 0x72, 0x61, 0x70, 0x70, 0x65, 0x72, 0x73, 0x12, 0x3b, 0x0a, 0x0a, 0x64, 0x6f, + 0x75, 0x62, 0x6c, 0x65, 0x5f, 0x76, 0x61, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, + 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, + 0x2e, 0x44, 0x6f, 0x75, 0x62, 0x6c, 0x65, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x09, 0x64, 0x6f, + 0x75, 0x62, 0x6c, 0x65, 0x56, 0x61, 0x6c, 0x12, 0x38, 0x0a, 0x09, 0x66, 0x6c, 0x6f, 0x61, 0x74, + 0x5f, 0x76, 0x61, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x67, 0x6f, 0x6f, + 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x46, 0x6c, 0x6f, + 0x61, 0x74, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x08, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x56, 0x61, + 0x6c, 0x12, 0x38, 0x0a, 0x09, 0x69, 0x6e, 0x74, 0x33, 0x32, 0x5f, 0x76, 0x61, 0x6c, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x49, 0x6e, 0x74, 0x33, 0x32, 0x56, 0x61, 0x6c, 0x75, + 0x65, 0x52, 0x08, 0x69, 0x6e, 0x74, 0x33, 0x32, 0x56, 0x61, 0x6c, 0x12, 0x38, 0x0a, 0x09, 0x69, + 0x6e, 0x74, 0x36, 0x34, 0x5f, 0x76, 0x61, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, + 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, + 0x2e, 0x49, 0x6e, 0x74, 0x36, 0x34, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x08, 0x69, 0x6e, 0x74, + 0x36, 0x34, 0x56, 0x61, 0x6c, 0x12, 0x3b, 0x0a, 0x0a, 0x75, 0x69, 0x6e, 0x74, 0x33, 0x32, 0x5f, + 0x76, 0x61, 0x6c, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, + 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x55, 0x49, 0x6e, 0x74, + 0x33, 0x32, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x09, 0x75, 0x69, 0x6e, 0x74, 0x33, 0x32, 0x56, + 0x61, 0x6c, 0x12, 0x3b, 0x0a, 0x0a, 0x75, 0x69, 0x6e, 0x74, 0x36, 0x34, 0x5f, 0x76, 0x61, 0x6c, + 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x55, 0x49, 0x6e, 0x74, 0x36, 0x34, 0x56, + 0x61, 0x6c, 0x75, 0x65, 0x52, 0x09, 0x75, 0x69, 0x6e, 0x74, 0x36, 0x34, 0x56, 0x61, 0x6c, 0x12, + 0x35, 0x0a, 0x08, 0x62, 0x6f, 0x6f, 0x6c, 0x5f, 0x76, 0x61, 0x6c, 0x18, 0x0d, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x62, 0x75, 0x66, 0x2e, 0x42, 0x6f, 0x6f, 0x6c, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x07, 0x62, + 0x6f, 0x6f, 0x6c, 0x56, 0x61, 0x6c, 0x12, 0x3b, 0x0a, 0x0a, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, + 0x5f, 0x76, 0x61, 0x6c, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, + 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, + 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x09, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, + 0x56, 0x61, 0x6c, 0x12, 0x38, 0x0a, 0x09, 0x62, 0x79, 0x74, 0x65, 0x73, 0x5f, 0x76, 0x61, 0x6c, + 0x18, 0x0f, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x42, 0x79, 0x74, 0x65, 0x73, 0x56, 0x61, + 0x6c, 0x75, 0x65, 0x52, 0x08, 0x62, 0x79, 0x74, 0x65, 0x73, 0x56, 0x61, 0x6c, 0x22, 0x8a, 0x01, + 0x0a, 0x05, 0x4f, 0x6e, 0x65, 0x4f, 0x66, 0x12, 0x1d, 0x0a, 0x09, 0x69, 0x6e, 0x74, 0x33, 0x32, + 0x5f, 0x76, 0x61, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x48, 0x00, 0x52, 0x08, 0x69, 0x6e, + 0x74, 0x33, 0x32, 0x56, 0x61, 0x6c, 0x12, 0x5a, 0x0a, 0x0a, 0x70, 0x72, 0x69, 0x6d, 0x69, 0x74, + 0x69, 0x76, 0x65, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x38, 0x2e, 0x68, 0x61, 0x73, + 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, + 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x68, 0x63, 0x6c, 0x2e, + 0x74, 0x65, 0x73, 0x74, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x50, 0x72, 0x69, 0x6d, 0x69, 0x74, + 0x69, 0x76, 0x65, 0x73, 0x48, 0x00, 0x52, 0x0a, 0x70, 0x72, 0x69, 0x6d, 0x69, 0x74, 0x69, 0x76, + 0x65, 0x73, 0x42, 0x06, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x22, 0xc9, 0x01, 0x0a, 0x13, 0x4e, + 0x6f, 0x6e, 0x44, 0x79, 0x6e, 0x61, 0x6d, 0x69, 0x63, 0x57, 0x65, 0x6c, 0x6c, 0x4b, 0x6e, 0x6f, + 0x77, 0x6e, 0x12, 0x33, 0x0a, 0x09, 0x65, 0x6d, 0x70, 0x74, 0x79, 0x5f, 0x76, 0x61, 0x6c, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x52, 0x08, 0x65, + 0x6d, 0x70, 0x74, 0x79, 0x56, 0x61, 0x6c, 0x12, 0x3f, 0x0a, 0x0d, 0x74, 0x69, 0x6d, 0x65, 0x73, + 0x74, 0x61, 0x6d, 0x70, 0x5f, 0x76, 0x61, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, + 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, + 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0c, 0x74, 0x69, 0x6d, 0x65, + 0x73, 0x74, 0x61, 0x6d, 0x70, 0x56, 0x61, 0x6c, 0x12, 0x3c, 0x0a, 0x0c, 0x64, 0x75, 0x72, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x76, 0x61, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, + 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, + 0x2e, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0b, 0x64, 0x75, 0x72, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x56, 0x61, 0x6c, 0x22, 0xaa, 0x01, 0x0a, 0x10, 0x44, 0x79, 0x6e, 0x61, 0x6d, + 0x69, 0x63, 0x57, 0x65, 0x6c, 0x6c, 0x4b, 0x6e, 0x6f, 0x77, 0x6e, 0x12, 0x2d, 0x0a, 0x07, 0x61, + 0x6e, 0x79, 0x5f, 0x76, 0x61, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x67, + 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x41, + 0x6e, 0x79, 0x52, 0x06, 0x61, 0x6e, 0x79, 0x56, 0x61, 0x6c, 0x12, 0x36, 0x0a, 0x0a, 0x73, 0x74, + 0x72, 0x75, 0x63, 0x74, 0x5f, 0x76, 0x61, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, + 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, + 0x2e, 0x53, 0x74, 0x72, 0x75, 0x63, 0x74, 0x52, 0x09, 0x73, 0x74, 0x72, 0x75, 0x63, 0x74, 0x56, + 0x61, 0x6c, 0x12, 0x2f, 0x0a, 0x08, 0x61, 0x6e, 0x79, 0x5f, 0x6c, 0x69, 0x73, 0x74, 0x18, 0x03, + 0x20, 0x03, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x41, 0x6e, 0x79, 0x52, 0x07, 0x61, 0x6e, 0x79, 0x4c, + 0x69, 0x73, 0x74, 0x2a, 0x48, 0x0a, 0x08, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x12, + 0x18, 0x0a, 0x14, 0x50, 0x52, 0x4f, 0x54, 0x4f, 0x43, 0x4f, 0x4c, 0x5f, 0x55, 0x4e, 0x53, 0x50, + 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x10, 0x0a, 0x0c, 0x50, 0x52, 0x4f, + 0x54, 0x4f, 0x43, 0x4f, 0x4c, 0x5f, 0x54, 0x43, 0x50, 0x10, 0x01, 0x12, 0x10, 0x0a, 0x0c, 0x50, + 0x52, 0x4f, 0x54, 0x4f, 0x43, 0x4f, 0x4c, 0x5f, 0x55, 0x44, 0x50, 0x10, 0x02, 0x42, 0xcf, 0x02, + 0x0a, 0x30, 0x63, 0x6f, 0x6d, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, + 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x68, 0x63, 0x6c, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x42, 0x0c, 0x45, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x50, 0x72, 0x6f, 0x74, 0x6f, + 0x50, 0x01, 0x5a, 0x37, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x68, + 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2f, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2f, + 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x68, 0x63, + 0x6c, 0x2f, 0x74, 0x65, 0x73, 0x74, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0xa2, 0x02, 0x05, 0x48, 0x43, + 0x49, 0x50, 0x54, 0xaa, 0x02, 0x2c, 0x48, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, + 0x43, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, + 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x68, 0x63, 0x6c, 0x2e, 0x54, 0x65, 0x73, 0x74, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0xca, 0x02, 0x2c, 0x48, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x5c, 0x43, + 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x5c, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x5c, 0x50, + 0x72, 0x6f, 0x74, 0x6f, 0x68, 0x63, 0x6c, 0x5c, 0x54, 0x65, 0x73, 0x74, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0xe2, 0x02, 0x38, 0x48, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x5c, 0x43, 0x6f, + 0x6e, 0x73, 0x75, 0x6c, 0x5c, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x5c, 0x50, 0x72, + 0x6f, 0x74, 0x6f, 0x68, 0x63, 0x6c, 0x5c, 0x54, 0x65, 0x73, 0x74, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x5c, 0x47, 0x50, 0x42, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0xea, 0x02, 0x30, 0x48, + 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x3a, 0x3a, 0x43, 0x6f, 0x6e, 0x73, 0x75, 0x6c, + 0x3a, 0x3a, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x3a, 0x3a, 0x50, 0x72, 0x6f, 0x74, + 0x6f, 0x68, 0x63, 0x6c, 0x3a, 0x3a, 0x54, 0x65, 0x73, 0x74, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, + 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_example_proto_rawDescOnce sync.Once + file_example_proto_rawDescData = file_example_proto_rawDesc +) + +func file_example_proto_rawDescGZIP() []byte { + file_example_proto_rawDescOnce.Do(func() { + file_example_proto_rawDescData = protoimpl.X.CompressGZIP(file_example_proto_rawDescData) + }) + return file_example_proto_rawDescData +} + +var file_example_proto_enumTypes = make([]protoimpl.EnumInfo, 1) +var file_example_proto_msgTypes = make([]protoimpl.MessageInfo, 8) +var file_example_proto_goTypes = []interface{}{ + (Protocol)(0), // 0: hashicorp.consul.internal.protohcl.testproto.Protocol + (*Primitives)(nil), // 1: hashicorp.consul.internal.protohcl.testproto.Primitives + (*NestedAndCollections)(nil), // 2: hashicorp.consul.internal.protohcl.testproto.NestedAndCollections + (*Wrappers)(nil), // 3: hashicorp.consul.internal.protohcl.testproto.Wrappers + (*OneOf)(nil), // 4: hashicorp.consul.internal.protohcl.testproto.OneOf + (*NonDynamicWellKnown)(nil), // 5: hashicorp.consul.internal.protohcl.testproto.NonDynamicWellKnown + (*DynamicWellKnown)(nil), // 6: hashicorp.consul.internal.protohcl.testproto.DynamicWellKnown + nil, // 7: hashicorp.consul.internal.protohcl.testproto.NestedAndCollections.PrimitivesMapEntry + nil, // 8: hashicorp.consul.internal.protohcl.testproto.NestedAndCollections.ProtocolMapEntry + (*wrapperspb.DoubleValue)(nil), // 9: google.protobuf.DoubleValue + (*wrapperspb.FloatValue)(nil), // 10: google.protobuf.FloatValue + (*wrapperspb.Int32Value)(nil), // 11: google.protobuf.Int32Value + (*wrapperspb.Int64Value)(nil), // 12: google.protobuf.Int64Value + (*wrapperspb.UInt32Value)(nil), // 13: google.protobuf.UInt32Value + (*wrapperspb.UInt64Value)(nil), // 14: google.protobuf.UInt64Value + (*wrapperspb.BoolValue)(nil), // 15: google.protobuf.BoolValue + (*wrapperspb.StringValue)(nil), // 16: google.protobuf.StringValue + (*wrapperspb.BytesValue)(nil), // 17: google.protobuf.BytesValue + (*emptypb.Empty)(nil), // 18: google.protobuf.Empty + (*timestamppb.Timestamp)(nil), // 19: google.protobuf.Timestamp + (*durationpb.Duration)(nil), // 20: google.protobuf.Duration + (*anypb.Any)(nil), // 21: google.protobuf.Any + (*structpb.Struct)(nil), // 22: google.protobuf.Struct +} +var file_example_proto_depIdxs = []int32{ + 1, // 0: hashicorp.consul.internal.protohcl.testproto.NestedAndCollections.primitives:type_name -> hashicorp.consul.internal.protohcl.testproto.Primitives + 1, // 1: hashicorp.consul.internal.protohcl.testproto.NestedAndCollections.primitives_list:type_name -> hashicorp.consul.internal.protohcl.testproto.Primitives + 7, // 2: hashicorp.consul.internal.protohcl.testproto.NestedAndCollections.primitives_map:type_name -> hashicorp.consul.internal.protohcl.testproto.NestedAndCollections.PrimitivesMapEntry + 8, // 3: hashicorp.consul.internal.protohcl.testproto.NestedAndCollections.protocol_map:type_name -> hashicorp.consul.internal.protohcl.testproto.NestedAndCollections.ProtocolMapEntry + 9, // 4: hashicorp.consul.internal.protohcl.testproto.Wrappers.double_val:type_name -> google.protobuf.DoubleValue + 10, // 5: hashicorp.consul.internal.protohcl.testproto.Wrappers.float_val:type_name -> google.protobuf.FloatValue + 11, // 6: hashicorp.consul.internal.protohcl.testproto.Wrappers.int32_val:type_name -> google.protobuf.Int32Value + 12, // 7: hashicorp.consul.internal.protohcl.testproto.Wrappers.int64_val:type_name -> google.protobuf.Int64Value + 13, // 8: hashicorp.consul.internal.protohcl.testproto.Wrappers.uint32_val:type_name -> google.protobuf.UInt32Value + 14, // 9: hashicorp.consul.internal.protohcl.testproto.Wrappers.uint64_val:type_name -> google.protobuf.UInt64Value + 15, // 10: hashicorp.consul.internal.protohcl.testproto.Wrappers.bool_val:type_name -> google.protobuf.BoolValue + 16, // 11: hashicorp.consul.internal.protohcl.testproto.Wrappers.string_val:type_name -> google.protobuf.StringValue + 17, // 12: hashicorp.consul.internal.protohcl.testproto.Wrappers.bytes_val:type_name -> google.protobuf.BytesValue + 1, // 13: hashicorp.consul.internal.protohcl.testproto.OneOf.primitives:type_name -> hashicorp.consul.internal.protohcl.testproto.Primitives + 18, // 14: hashicorp.consul.internal.protohcl.testproto.NonDynamicWellKnown.empty_val:type_name -> google.protobuf.Empty + 19, // 15: hashicorp.consul.internal.protohcl.testproto.NonDynamicWellKnown.timestamp_val:type_name -> google.protobuf.Timestamp + 20, // 16: hashicorp.consul.internal.protohcl.testproto.NonDynamicWellKnown.duration_val:type_name -> google.protobuf.Duration + 21, // 17: hashicorp.consul.internal.protohcl.testproto.DynamicWellKnown.any_val:type_name -> google.protobuf.Any + 22, // 18: hashicorp.consul.internal.protohcl.testproto.DynamicWellKnown.struct_val:type_name -> google.protobuf.Struct + 21, // 19: hashicorp.consul.internal.protohcl.testproto.DynamicWellKnown.any_list:type_name -> google.protobuf.Any + 1, // 20: hashicorp.consul.internal.protohcl.testproto.NestedAndCollections.PrimitivesMapEntry.value:type_name -> hashicorp.consul.internal.protohcl.testproto.Primitives + 0, // 21: hashicorp.consul.internal.protohcl.testproto.NestedAndCollections.ProtocolMapEntry.value:type_name -> hashicorp.consul.internal.protohcl.testproto.Protocol + 22, // [22:22] is the sub-list for method output_type + 22, // [22:22] is the sub-list for method input_type + 22, // [22:22] is the sub-list for extension type_name + 22, // [22:22] is the sub-list for extension extendee + 0, // [0:22] is the sub-list for field type_name +} + +func init() { file_example_proto_init() } +func file_example_proto_init() { + if File_example_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_example_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Primitives); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_example_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*NestedAndCollections); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_example_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Wrappers); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_example_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*OneOf); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_example_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*NonDynamicWellKnown); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_example_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*DynamicWellKnown); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + file_example_proto_msgTypes[3].OneofWrappers = []interface{}{ + (*OneOf_Int32Val)(nil), + (*OneOf_Primitives)(nil), + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_example_proto_rawDesc, + NumEnums: 1, + NumMessages: 8, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_example_proto_goTypes, + DependencyIndexes: file_example_proto_depIdxs, + EnumInfos: file_example_proto_enumTypes, + MessageInfos: file_example_proto_msgTypes, + }.Build() + File_example_proto = out.File + file_example_proto_rawDesc = nil + file_example_proto_goTypes = nil + file_example_proto_depIdxs = nil +} diff --git a/internal/protohcl/testproto/example.proto b/internal/protohcl/testproto/example.proto new file mode 100644 index 0000000000..9a0633cd60 --- /dev/null +++ b/internal/protohcl/testproto/example.proto @@ -0,0 +1,74 @@ +syntax = "proto3"; + +package hashicorp.consul.internal.protohcl.testproto; + +import "google/protobuf/duration.proto"; +import "google/protobuf/empty.proto"; +import "google/protobuf/timestamp.proto"; +import "google/protobuf/wrappers.proto"; +import "google/protobuf/any.proto"; +import "google/protobuf/struct.proto"; + +message Primitives { + double double_val = 1; + float float_val = 2; + int32 int32_val = 3; + int64 int64_val = 4; + uint32 uint32_val = 5; + uint64 uint64_val = 6; + sint32 sint32_val = 7; + sint64 sint64_val = 8; + fixed32 fixed32_val = 9; + fixed64 fixed64_val = 10; + sfixed32 sfixed32_val = 11; + sfixed64 sfixed64_val = 12; + bool bool_val = 13; + string string_val = 14; + bytes byte_val = 15; +} + +enum Protocol { + PROTOCOL_UNSPECIFIED = 0; + PROTOCOL_TCP = 1; + PROTOCOL_UDP = 2; +} + +message NestedAndCollections { + Primitives primitives = 1; + repeated Primitives primitives_list = 2; + map primitives_map = 3; + map protocol_map = 4; + repeated int32 int_list = 5; +} + +message Wrappers { + google.protobuf.DoubleValue double_val = 1; + google.protobuf.FloatValue float_val = 2; + google.protobuf.Int32Value int32_val = 3; + google.protobuf.Int64Value int64_val = 4; + google.protobuf.UInt32Value uint32_val = 5; + google.protobuf.UInt64Value uint64_val = 6; + google.protobuf.BoolValue bool_val = 13; + google.protobuf.StringValue string_val = 14; + google.protobuf.BytesValue bytes_val = 15; +} + +message OneOf { + oneof data { + int32 int32_val = 1; + Primitives primitives = 2; + // note repeated fields (including maps) are not allowed in oneofs + } +} + +message NonDynamicWellKnown { + google.protobuf.Empty empty_val = 1; + google.protobuf.Timestamp timestamp_val = 2; + google.protobuf.Duration duration_val = 3; +} + +message DynamicWellKnown { + google.protobuf.Any any_val = 1; + google.protobuf.Struct struct_val = 2; + repeated google.protobuf.Any any_list = 3; +} diff --git a/internal/protohcl/unmarshal.go b/internal/protohcl/unmarshal.go new file mode 100644 index 0000000000..5423651b3e --- /dev/null +++ b/internal/protohcl/unmarshal.go @@ -0,0 +1,134 @@ +package protohcl + +import ( + "github.com/hashicorp/hcl/v2" + "github.com/hashicorp/hcl/v2/hclparse" + "github.com/zclconf/go-cty/cty/function" + "google.golang.org/protobuf/reflect/protoreflect" +) + +// UnmarshalContext provides information about the context in which we are +// unmarshalling HCL. It is primarily used for decoding Any fields based on +// surrounding information (e.g. the resource Type block). +type UnmarshalContext struct { + // Parent context. + Parent *UnmarshalContext + + // Name of the field that we are unmarshalling. + Name string + + // Message is the protobuf message that we are unmarshalling into (may be nil). + Message protoreflect.Message + + // Range of where this field was in the HCL source. + Range hcl.Range +} + +// ErrorRange returns a range that can be used in error messages. +func (ctx *UnmarshalContext) ErrorRange() hcl.Range { + for { + if !ctx.Range.Empty() || ctx.Parent == nil { + return ctx.Range + } + ctx = ctx.Parent + } +} + +func Unmarshal(src []byte, dest protoreflect.ProtoMessage) error { + return UnmarshalOptions{}.Unmarshal(src, dest) +} + +type UnmarshalOptions struct { + AnyTypeProvider AnyTypeProvider + SourceFileName string + FieldNamer FieldNamer + Functions map[string]function.Function +} + +func (u UnmarshalOptions) Unmarshal(src []byte, dest protoreflect.ProtoMessage) error { + rmsg := dest.ProtoReflect() + + file, diags := hclparse.NewParser().ParseHCL(src, u.SourceFileName) + + // error performing basic HCL parsing + if diags.HasErrors() { + return diags + } + + u.clearAll(rmsg) + + if u.FieldNamer == nil { + u.FieldNamer = textFieldNamer{} + } + + return u.decodeMessage( + &UnmarshalContext{Message: rmsg}, + u.bodyDecoder(file.Body), + rmsg, + ) +} + +func (u UnmarshalOptions) bodyDecoder(body hcl.Body) MessageDecoder { + return newBodyDecoder(body, u.FieldNamer, u.Functions) +} + +func (u UnmarshalOptions) decodeMessage(ctx *UnmarshalContext, decoder MessageDecoder, msg protoreflect.Message) error { + desc := msg.Descriptor() + + if desc.FullName() == wellKnownTypeAny { + return u.decodeAny(ctx, decoder, msg) + } + + tracker := newOneOfTracker(u.FieldNamer) + + return decoder.EachField(FieldIterator{ + Desc: desc, + Func: func(field *IterField) error { + if err := tracker.markFieldAsSet(field.Desc); err != nil { + return err + } + + var ( + protoVal protoreflect.Value + err error + ) + switch { + case field.Val != nil: + protoVal, err = u.decodeAttribute( + &UnmarshalContext{ + Parent: ctx, + Name: field.Name, + Range: field.Range, + }, + func() protoreflect.Value { return msg.NewField(field.Desc) }, + field.Desc, + *field.Val, + true, + ) + case len(field.Blocks) != 0: + protoVal, err = u.decodeBlocks(ctx, field.Blocks, msg, field.Desc) + default: + panic("decoder yielded no blocks or attributes") + } + if err != nil { + return err + } + + if protoVal.IsValid() { + msg.Set(field.Desc, protoVal) + } + + return nil + }, + }) +} + +type newMessageFn func() protoreflect.Value + +func (u UnmarshalOptions) clearAll(msg protoreflect.Message) { + fields := msg.Descriptor().Fields() + + for i := 0; i < fields.Len(); i++ { + msg.Clear(fields.Get(i)) + } +} diff --git a/internal/protohcl/unmarshal_test.go b/internal/protohcl/unmarshal_test.go new file mode 100644 index 0000000000..5f0eb9b180 --- /dev/null +++ b/internal/protohcl/unmarshal_test.go @@ -0,0 +1,557 @@ +package protohcl + +import ( + "encoding/json" + "fmt" + "reflect" + "testing" + "time" + + "github.com/stretchr/testify/require" + "github.com/zclconf/go-cty/cty" + "github.com/zclconf/go-cty/cty/function" + "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/types/known/anypb" + + "github.com/hashicorp/consul/internal/protohcl/testproto" + "github.com/hashicorp/hcl/v2/hclparse" +) + +func TestPrimitives(t *testing.T) { + hcl := ` + double_val = 1.234 + float_val = 2.345 + int32_val = 536870912 + int64_val = 25769803776 + uint32_val = 2148532224 + uint64_val = 9223372041149743104 + sint32_val = 536870912 + sint64_val = 25769803776 + fixed32_val = 2148532224 + fixed64_val = 9223372041149743104 + sfixed32_val = 536870912 + sfixed64_val = 25769803776 + bool_val = true + string_val = "foo" + // This is base64 encoded "bar" + byte_val = "YmFy" + ` + + var out testproto.Primitives + + err := Unmarshal([]byte(hcl), &out) + require.NoError(t, err) + + require.Equal(t, out.DoubleVal, float64(1.234)) + require.Equal(t, out.FloatVal, float32(2.345)) + require.Equal(t, out.Int32Val, int32(536870912)) + require.Equal(t, out.Int64Val, int64(25769803776)) + require.Equal(t, out.Uint32Val, uint32(2148532224)) + require.Equal(t, out.Uint64Val, uint64(9223372041149743104)) + require.Equal(t, out.Sint32Val, int32(536870912)) + require.Equal(t, out.Sint64Val, int64(25769803776)) + require.Equal(t, out.Fixed32Val, uint32(2148532224)) + require.Equal(t, out.Fixed64Val, uint64(9223372041149743104)) + require.Equal(t, out.Sfixed32Val, int32(536870912)) + require.Equal(t, out.Sfixed64Val, int64(25769803776)) + require.Equal(t, out.BoolVal, true) + require.Equal(t, out.StringVal, "foo") + require.Equal(t, out.ByteVal, []byte("bar")) +} + +func TestNestedAndCollections(t *testing.T) { + hcl := ` + primitives { + uint32_val = 42 + } + + primitives_map "foo" { + uint32_val = 42 + } + + protocol_map = { + "foo" = "PROTOCOL_TCP" + } + + primitives_list { + uint32_val = 42 + } + + primitives_list { + uint32_val = 56 + } + + int_list = [ + 1, + 2 + ] + + ` + + var out testproto.NestedAndCollections + + err := Unmarshal([]byte(hcl), &out) + require.NoError(t, err) + + require.NotNil(t, out.Primitives) + require.Equal(t, out.Primitives.Uint32Val, uint32(42)) + require.NotNil(t, out.PrimitivesMap) + require.Equal(t, out.PrimitivesMap["foo"].Uint32Val, uint32(42)) + require.NotNil(t, out.ProtocolMap) + require.Equal(t, out.ProtocolMap["foo"], testproto.Protocol_PROTOCOL_TCP) + require.Len(t, out.PrimitivesList, 2) + require.Equal(t, out.PrimitivesList[0].Uint32Val, uint32(42)) + require.Equal(t, out.PrimitivesList[1].Uint32Val, uint32(56)) + require.Len(t, out.IntList, 2) + require.Equal(t, out.IntList[1], int32(2)) +} + +func TestPrimitiveWrappers(t *testing.T) { + hcl := ` + double_val = 1.234 + float_val = 2.345 + int32_val = 536870912 + int64_val = 25769803776 + uint32_val = 2148532224 + uint64_val = 9223372041149743104 + bool_val = true + string_val = "foo" + // This is base64 encoded "bar" + bytes_val = "YmFy" + ` + var out testproto.Wrappers + + err := Unmarshal([]byte(hcl), &out) + require.NoError(t, err) + require.Equal(t, out.DoubleVal.Value, float64(1.234)) + require.Equal(t, out.FloatVal.Value, float32(2.345)) + require.Equal(t, out.Int32Val.Value, int32(536870912)) + require.Equal(t, out.Int64Val.Value, int64(25769803776)) + require.Equal(t, out.Uint32Val.Value, uint32(2148532224)) + require.Equal(t, out.Uint64Val.Value, uint64(9223372041149743104)) + require.Equal(t, out.BoolVal.Value, true) + require.Equal(t, out.StringVal.Value, "foo") + require.Equal(t, out.BytesVal.Value, []byte("bar")) +} + +func TestNonDynamicWellKnown(t *testing.T) { + hcl := ` + empty_val = {} + timestamp_val = "2023-02-27T12:34:56.789Z" + duration_val = "12s" + ` + var out testproto.NonDynamicWellKnown + + err := Unmarshal([]byte(hcl), &out) + require.NoError(t, err) + require.NotNil(t, out.EmptyVal) + require.NotNil(t, out.TimestampVal) + require.Equal(t, out.TimestampVal.AsTime(), time.Date(2023, 2, 27, 12, 34, 56, 789000000, time.UTC)) + require.NotNil(t, out.DurationVal) + require.Equal(t, out.DurationVal.AsDuration(), time.Second*12) +} + +func TestInvalidTimestamp(t *testing.T) { + if testing.Short() { + t.Skip("too slow for testing.Short") + } + + t.Parallel() + + cases := map[string]struct { + hcl string + expectXDS bool + }{ + "invalid": { + hcl: ` + timestamp_val = "Sat Jun 12 2023 14:59:57 GMT+0200" + `, + }, + "range error": { + hcl: ` + timestamp_val = "2023-02-27T25:34:56.789Z" + `, + }, + } + + for name, tc := range cases { + tc := tc + var out testproto.NonDynamicWellKnown + t.Run(name, func(t *testing.T) { + + err := Unmarshal([]byte(tc.hcl), &out) + require.Error(t, err) + require.Nil(t, out.TimestampVal) + require.ErrorContains(t, err, "error parsing timestamp") + }) + } +} + +func TestInvalidDuration(t *testing.T) { + hcl := ` + duration_val = "abc" + ` + var out testproto.NonDynamicWellKnown + + err := Unmarshal([]byte(hcl), &out) + require.ErrorContains(t, err, "error parsing string duration:") + require.Nil(t, out.DurationVal) +} + +func TestOneOf(t *testing.T) { + hcl1 := ` + int32_val = 3 + ` + + hcl2 := ` + primitives { + int32_val = 3 + } + ` + + hcl3 := ` + int32_val = 3 + primitives { + int32_val = 4 + } + ` + + var out testproto.OneOf + + err := Unmarshal([]byte(hcl1), &out) + require.NoError(t, err) + require.Equal(t, out.GetInt32Val(), int32(3)) + + err = Unmarshal([]byte(hcl2), &out) + require.NoError(t, err) + primitives := out.GetPrimitives() + require.NotNil(t, primitives) + require.Equal(t, primitives.Int32Val, int32(3)) + + err = Unmarshal([]byte(hcl3), &out) + require.Error(t, err) +} + +func TestAny(t *testing.T) { + hcl := ` + any_val { + type_url = "hashicorp.consul.internal.protohcl.testproto.Primitives" + uint32_val = 42 + } + + any_list = [ + { + type_url = "hashicorp.consul.internal.protohcl.testproto.Primitives" + uint32_val = 123 + }, + { + type_url = "hashicorp.consul.internal.protohcl.testproto.Wrappers" + uint32_val = 321 + } + ] + ` + var out testproto.DynamicWellKnown + + err := Unmarshal([]byte(hcl), &out) + require.NoError(t, err) + require.NotNil(t, out.AnyVal) + require.Equal(t, out.AnyVal.TypeUrl, "hashicorp.consul.internal.protohcl.testproto.Primitives") + + raw, err := anypb.UnmarshalNew(out.AnyVal, proto.UnmarshalOptions{}) + require.NoError(t, err) + require.NotNil(t, raw) + + primitives, ok := raw.(*testproto.Primitives) + require.True(t, ok) + require.Equal(t, primitives.Uint32Val, uint32(42)) +} + +func TestAnyTypeDynamicWellKnown(t *testing.T) { + hcl := ` + any_val { + type_url = "hashicorp.consul.internal.protohcl.testproto.DynamicWellKnown" + any_val { + type_url = "hashicorp.consul.internal.protohcl.testproto.Primitives" + uint32_val = 42 + } + } + ` + var out testproto.DynamicWellKnown + + err := Unmarshal([]byte(hcl), &out) + require.NoError(t, err) + require.NotNil(t, out.AnyVal) + require.Equal(t, out.AnyVal.TypeUrl, "hashicorp.consul.internal.protohcl.testproto.DynamicWellKnown") + + raw, err := anypb.UnmarshalNew(out.AnyVal, proto.UnmarshalOptions{}) + require.NoError(t, err) + require.NotNil(t, raw) + + anyVal, ok := raw.(*testproto.DynamicWellKnown) + require.True(t, ok) + + res, err := anypb.UnmarshalNew(anyVal.AnyVal, proto.UnmarshalOptions{}) + require.NoError(t, err) + require.NotNil(t, res) + + primitives, ok := res.(*testproto.Primitives) + require.True(t, ok) + require.Equal(t, primitives.Uint32Val, uint32(42)) +} + +func TestAnyTypeNestedAndCollections(t *testing.T) { + hcl := ` + any_val { + type_url = "hashicorp.consul.internal.protohcl.testproto.NestedAndCollections" + primitives { + uint32_val = 42 + } + } + ` + var out testproto.DynamicWellKnown + + err := Unmarshal([]byte(hcl), &out) + require.NoError(t, err) + require.NotNil(t, out.AnyVal) + require.Equal(t, out.AnyVal.TypeUrl, "hashicorp.consul.internal.protohcl.testproto.NestedAndCollections") + + raw, err := anypb.UnmarshalNew(out.AnyVal, proto.UnmarshalOptions{}) + require.NoError(t, err) + require.NotNil(t, raw) + + nestedCollections, ok := raw.(*testproto.NestedAndCollections) + require.True(t, ok) + require.NotNil(t, nestedCollections.Primitives) + require.Equal(t, nestedCollections.Primitives.Uint32Val, uint32(42)) +} + +func TestAnyTypeErrors(t *testing.T) { + type testCase struct { + description string + hcl string + error string + } + testCases := []testCase{ + { + description: "type_url is expected", + hcl: ` + any_val { + uint32_val = 42 + } + `, + error: "type_url field is required to decode Any", + }, + { + description: "type_url is unknown", + hcl: ` + any_val { + type_url = "hashicorp.consul.internal.protohcl.testproto.Integer" + uint32_val = 42 + } + `, + error: "error looking up type information for hashicorp.consul.internal.protohcl.testproto.Integer", + }, + { + description: "unknown field", + hcl: ` + any_val { + type_url = "hashicorp.consul.internal.protohcl.testproto.Primitives" + int_val = 42 + } + `, + error: "Unsupported argument; An argument named \"int_val\" is not expected here", + }, + } + + for _, tc := range testCases { + tc := tc + t.Run(tc.description, func(t *testing.T) { + t.Parallel() + var out testproto.DynamicWellKnown + + err := Unmarshal([]byte(tc.hcl), &out) + require.Error(t, err) + require.Contains(t, err.Error(), tc.error) + }) + } +} + +func TestStruct(t *testing.T) { + hcl := ` + struct_val = { + "null"= null + "bool"= true + "foo" = "bar" + "baz" = 1.234 + "nested" = { + "foo" = 12, + "bar" = "something" + } + } + ` + + var out testproto.DynamicWellKnown + + err := Unmarshal([]byte(hcl), &out) + require.NoError(t, err) + require.NotNil(t, out.StructVal) + + valMap := out.StructVal.AsMap() + jsonVal, err := json.Marshal(valMap) + require.NoError(t, err) + + expected := `{ + "null": null, + "bool": true, + "foo": "bar", + "baz": 1.234, + "nested": { + "foo": 12, + "bar": "something" + } + } + ` + require.JSONEq(t, expected, string(jsonVal)) +} + +func TestStructList(t *testing.T) { + hcl := ` + struct_val = { + "list_int" = [ + 1, + 2, + 3, + ] + "list_string": [ + "abc", + "def" + ] + "list_bool": [ + true, + false + ] + "list_maps" = [ + { + "arrr" = "matey" + }, + { + "hoist" = "the colors" + } + ] + "list_list" = [ + [ + "hello", + "world", + null + ] + ] + } + ` + + var out testproto.DynamicWellKnown + + err := Unmarshal([]byte(hcl), &out) + require.NoError(t, err) + require.NotNil(t, out.StructVal) + + valMap := out.StructVal.AsMap() + jsonVal, err := json.Marshal(valMap) + require.NoError(t, err) + + expected := `{ + "list_int": [ + 1, + 2, + 3 + ], + "list_string": [ + "abc", + "def" + ], + "list_bool": [ + true, + false + ], + "list_maps": [ + { + "arrr": "matey" + }, + { + "hoist": "the colors" + } + ], + "list_list": [ + [ + "hello", + "world", + null + ] + ] + } + ` + require.JSONEq(t, expected, string(jsonVal)) +} + +func TestFunctionExecution(t *testing.T) { + hcl := ` + primitives = primitive_defaults() + ` + + var out testproto.NestedAndCollections + + var ( + testType = cty.Capsule("type", reflect.TypeOf(testproto.Primitives{})) + + test = function.New(&function.Spec{ + Params: []function.Parameter{}, + Type: function.StaticReturnType(testType), + Impl: func(args []cty.Value, _ cty.Type) (cty.Value, error) { + t := &testproto.Primitives{ + StringVal: "test", + Int32Val: 10, + BoolVal: false, + } + return cty.CapsuleVal(testType, t), nil + }, + }) + ) + + err := UnmarshalOptions{ + Functions: map[string]function.Function{"primitive_defaults": test}, + }.Unmarshal([]byte(hcl), &out) + + require.NoError(t, err) + + require.NotNil(t, out.Primitives) + require.Equal(t, out.Primitives.StringVal, "test") + require.Equal(t, out.Primitives.Int32Val, int32(10)) + require.Equal(t, out.Primitives.BoolVal, false) +} + +func TestSkipFields(t *testing.T) { + + u := UnmarshalOptions{} + + hcl := ` + any_val { + type_url = "hashicorp.consul.internal.protohcl.testproto.Primitives" + uint32_val = 10 + }` + + file, diags := hclparse.NewParser().ParseHCL([]byte(hcl), "") + + require.False(t, diags.HasErrors()) + + decoder := u.bodyDecoder(file.Body) + + decoder = decoder.SkipFields("type_url") + + decoder = decoder.SkipFields("type_url", "uint32_val") + + expected := map[string]struct{}{ + "type_url": {}, + "uint32_val": {}, + } + + require.Contains(t, fmt.Sprintf("%v", decoder), fmt.Sprintf("%v", expected)) +} diff --git a/internal/protohcl/well_known_types.go b/internal/protohcl/well_known_types.go new file mode 100644 index 0000000000..508d9dfffe --- /dev/null +++ b/internal/protohcl/well_known_types.go @@ -0,0 +1,418 @@ +package protohcl + +import ( + "fmt" + "time" + + "github.com/zclconf/go-cty/cty" + "google.golang.org/protobuf/reflect/protoreflect" + "google.golang.org/protobuf/types/known/durationpb" + "google.golang.org/protobuf/types/known/emptypb" + "google.golang.org/protobuf/types/known/structpb" + "google.golang.org/protobuf/types/known/timestamppb" + "google.golang.org/protobuf/types/known/wrapperspb" +) + +const ( + // maximum time in seconds that a time.Time which comes from + // an RFC 3339 timestamp can represent + maxTimestampSeconds = 253402300799 + // minimum time in seconds that a time.Time which comes from + // an RFC 3339 timestamp can represent. This is negative + // because RFC 3339 base time is year 0001 whereas time.Time + // starts in 1970 + minTimestampSeconds = -62135596800 +) + +var ( + // minTime is the earliest time representable as both an + // RFC 3339 timestamp and a time.Time value + minTime = time.Unix(minTimestampSeconds, 0) + // maxTime is the latest time respresentable as both an + // RFC 3339 timestamp and a time.Time value + maxTime = time.Unix(maxTimestampSeconds, 999999999) +) + +type wktSchemaHint int + +const ( + notWellKnownType wktSchemaHint = iota + wellKnownBlock + wellKnownAttribute +) + +// wellKnownTypeSchemaHint returns what sort of syntax should be used to represent +// the well known type field. +// +// NotWellKnownType - use attribute block syntax based on other information +// WellKnownAttribute - use attribute syntax +// WellKnownBlock - use block syntax +func wellKnownTypeSchemaHint(desc protoreflect.FieldDescriptor) wktSchemaHint { + if desc.Kind() != protoreflect.MessageKind { + return notWellKnownType + } + + switch desc.Message().FullName() { + case "google.protobuf.DoubleValue": + return wellKnownAttribute + case "google.protobuf.FloatValue": + return wellKnownAttribute + case "google.protobuf.Int32Value": + return wellKnownAttribute + case "google.protobuf.Int64Value": + return wellKnownAttribute + case "google.protobuf.UInt32Value": + return wellKnownAttribute + case "google.protobuf.UInt64Value": + return wellKnownAttribute + case "google.protobuf.BoolValue": + return wellKnownAttribute + case "google.protobuf.StringValue": + return wellKnownAttribute + case "google.protobuf.BytesValue": + return wellKnownAttribute + case "google.protobuf.Empty": + // block syntax is used for Empty to allow transitioning to using + // a different proto message with fields in the future. + return wellKnownBlock + case "google.protobuf.Timestamp": + return wellKnownAttribute + case "google.protobuf.Duration": + return wellKnownAttribute + case "google.protobuf.Struct": + // as the Struct has completely free form fields that we cannot + // know about in advance we cannot use block syntax + return wellKnownAttribute + case "google.protobuf.Any": + return wellKnownBlock + default: + return notWellKnownType + } +} + +func decodeAttributeToWellKnownType(desc protoreflect.FieldDescriptor, val cty.Value) (bool, protoreflect.Value, error) { + if desc.Kind() != protoreflect.MessageKind { + return false, protoreflect.Value{}, nil + } + + switch desc.Message().FullName() { + case "google.protobuf.DoubleValue": + protoVal, err := protoDoubleWrapperFromCty(val) + return true, protoVal, err + case "google.protobuf.FloatValue": + protoVal, err := protoFloatWrapperFromCty(val) + return true, protoVal, err + case "google.protobuf.Int32Value": + protoVal, err := protoInt32WrapperFromCty(val) + return true, protoVal, err + case "google.protobuf.Int64Value": + protoVal, err := protoInt64WrapperFromCty(val) + return true, protoVal, err + case "google.protobuf.UInt32Value": + protoVal, err := protoUint32WrapperFromCty(val) + return true, protoVal, err + case "google.protobuf.UInt64Value": + protoVal, err := protoUint64WrapperFromCty(val) + return true, protoVal, err + case "google.protobuf.BoolValue": + protoVal, err := protoBoolWrapperFromCty(val) + return true, protoVal, err + case "google.protobuf.StringValue": + protoVal, err := protoStringWrapperFromCty(val) + return true, protoVal, err + case "google.protobuf.BytesValue": + protoVal, err := protoBytesWrapperFromCty(val) + return true, protoVal, err + case "google.protobuf.Empty": + protoVal, err := protoEmptyFromCty(val) + return true, protoVal, err + case "google.protobuf.Timestamp": + protoVal, err := protoTimestampFromCty(val) + return true, protoVal, err + case "google.protobuf.Duration": + protoVal, err := protoDurationFromCty(val) + return true, protoVal, err + case "google.protobuf.Struct": + protoVal, err := protoStructFromCty(val) + return true, protoVal, err + default: + return false, protoreflect.Value{}, nil + } +} + +func protoWrapperFromBool(v bool) protoreflect.Value { + return protoreflect.ValueOfMessage(wrapperspb.Bool(v).ProtoReflect()) +} +func protoWrapperFromInt32(v int32) protoreflect.Value { + return protoreflect.ValueOfMessage(wrapperspb.Int32(v).ProtoReflect()) +} +func protoWrapperFromInt64(v int64) protoreflect.Value { + return protoreflect.ValueOfMessage(wrapperspb.Int64(v).ProtoReflect()) +} +func protoWrapperFromUint32(v uint32) protoreflect.Value { + return protoreflect.ValueOfMessage(wrapperspb.UInt32(v).ProtoReflect()) +} +func protoWrapperFromUint64(v uint64) protoreflect.Value { + return protoreflect.ValueOfMessage(wrapperspb.UInt64(v).ProtoReflect()) +} +func protoWrapperFromFloat(v float32) protoreflect.Value { + return protoreflect.ValueOfMessage(wrapperspb.Float(v).ProtoReflect()) +} +func protoWrapperFromDouble(v float64) protoreflect.Value { + return protoreflect.ValueOfMessage(wrapperspb.Double(v).ProtoReflect()) +} +func protoWrapperFromString(v string) protoreflect.Value { + return protoreflect.ValueOfMessage(wrapperspb.String(v).ProtoReflect()) +} +func protoWrapperFromBytes(v []byte) protoreflect.Value { + return protoreflect.ValueOfMessage(wrapperspb.Bytes(v).ProtoReflect()) +} + +func protoBoolWrapperFromCty(val cty.Value) (protoreflect.Value, error) { + v, err := boolFromCty(val) + if err != nil { + return protoreflect.Value{}, err + } + return protoWrapperFromBool(v), nil +} +func protoInt32WrapperFromCty(val cty.Value) (protoreflect.Value, error) { + v, err := int32FromCty(val) + if err != nil { + return protoreflect.Value{}, err + } + return protoWrapperFromInt32(v), nil +} +func protoInt64WrapperFromCty(val cty.Value) (protoreflect.Value, error) { + v, err := int64FromCty(val) + if err != nil { + return protoreflect.Value{}, err + } + return protoWrapperFromInt64(v), nil +} +func protoUint32WrapperFromCty(val cty.Value) (protoreflect.Value, error) { + v, err := uint32FromCty(val) + if err != nil { + return protoreflect.Value{}, err + } + return protoWrapperFromUint32(v), nil +} +func protoUint64WrapperFromCty(val cty.Value) (protoreflect.Value, error) { + v, err := uint64FromCty(val) + if err != nil { + return protoreflect.Value{}, err + } + return protoWrapperFromUint64(v), nil +} +func protoFloatWrapperFromCty(val cty.Value) (protoreflect.Value, error) { + v, err := floatFromCty(val) + if err != nil { + return protoreflect.Value{}, err + } + return protoWrapperFromFloat(v), nil +} +func protoDoubleWrapperFromCty(val cty.Value) (protoreflect.Value, error) { + v, err := doubleFromCty(val) + if err != nil { + return protoreflect.Value{}, err + } + return protoWrapperFromDouble(v), nil +} +func protoStringWrapperFromCty(val cty.Value) (protoreflect.Value, error) { + v, err := stringFromCty(val) + if err != nil { + return protoreflect.Value{}, err + } + return protoWrapperFromString(v), nil +} +func protoBytesWrapperFromCty(val cty.Value) (protoreflect.Value, error) { + v, err := bytesFromCty(val) + if err != nil { + return protoreflect.Value{}, err + } + return protoWrapperFromBytes(v), nil +} + +func protoEmptyFromCty(val cty.Value) (protoreflect.Value, error) { + var e emptypb.Empty + if val.IsNull() { + return protoreflect.Value{}, nil + } + + valType := val.Type() + if (valType.IsObjectType() || valType.IsMapType()) && val.LengthInt() == 0 { + return protoreflect.ValueOfMessage(e.ProtoReflect()), nil + } + + return protoreflect.Value{}, fmt.Errorf("well known empty type can only be represented as an hcl map/object - actual type %q", valType.FriendlyName()) +} + +func protoTimestampFromCty(val cty.Value) (protoreflect.Value, error) { + v, err := stringFromCty(val) + if err != nil { + return protoreflect.Value{}, err + } + + t, err := time.Parse(time.RFC3339Nano, v) + if err != nil { + return protoreflect.Value{}, fmt.Errorf("error parsing timestamp: %w", err) + } + + if t.Before(minTime) || t.After(maxTime) { + return protoreflect.Value{}, fmt.Errorf("time is out of range %s to %s - %s", minTime.String(), maxTime.String(), v) + } + + return protoreflect.ValueOfMessage(timestamppb.New(t).ProtoReflect()), nil +} + +func protoDurationFromCty(val cty.Value) (protoreflect.Value, error) { + v, err := stringFromCty(val) + if err != nil { + return protoreflect.Value{}, err + } + + d, err := time.ParseDuration(v) + if err != nil { + return protoreflect.Value{}, fmt.Errorf("error parsing string duration: %w", err) + } + + return protoreflect.ValueOfMessage(durationpb.New(d).ProtoReflect()), nil +} + +func protoStructFromCty(val cty.Value) (protoreflect.Value, error) { + s, err := protoStructObjectFromCty(val) + if err != nil { + return protoreflect.Value{}, err + } + + return protoreflect.ValueOfMessage(s.ProtoReflect()), nil +} + +func protoStructObjectFromCty(val cty.Value) (*structpb.Struct, error) { + valType := val.Type() + if !valType.IsObjectType() && !valType.IsMapType() { + return nil, fmt.Errorf("Struct type must be either an object or map type") + } + + structValues := make(map[string]*structpb.Value) + + for k, v := range val.AsValueMap() { + vType := v.Type() + + if v.IsNull() { + structValues[k] = structpb.NewNullValue() + } else if vType.IsListType() || vType.IsSetType() || vType.IsTupleType() { + listVal, err := protoStructListValueFromCty(v) + if err != nil { + return nil, err + } + structValues[k] = structpb.NewListValue(listVal) + } else if vType.IsMapType() || vType.IsObjectType() { + objVal, err := protoStructObjectFromCty(v) + if err != nil { + return nil, err + } + structValues[k] = structpb.NewStructValue(objVal) + } else if vType.IsPrimitiveType() { + switch vType { + case cty.String: + stringVal, err := stringFromCty(v) + if err != nil { + return nil, err + } + + structValues[k] = structpb.NewStringValue(stringVal) + case cty.Bool: + boolVal, err := boolFromCty(v) + if err != nil { + return nil, err + } + + structValues[k] = structpb.NewBoolValue(boolVal) + case cty.Number: + doubleVal, err := doubleFromCty(v) + if err != nil { + return nil, err + } + + structValues[k] = structpb.NewNumberValue(doubleVal) + default: + return nil, fmt.Errorf("unknown cty primitive type: %s", vType.FriendlyName()) + } + } else { + return nil, fmt.Errorf("unsupported cty type: %s", vType.FriendlyName()) + } + } + + return &structpb.Struct{ + Fields: structValues, + }, nil +} + +func protoStructListValueFromCty(val cty.Value) (*structpb.ListValue, error) { + var values []*structpb.Value + + var err error + val.ForEachElement(func(_ cty.Value, value cty.Value) bool { + vType := value.Type() + + if value.IsNull() { + values = append(values, structpb.NewNullValue()) + } else if vType.IsListType() || vType.IsSetType() || vType.IsTupleType() { + var listVal *structpb.ListValue + listVal, err = protoStructListValueFromCty(value) + if err != nil { + return true + } + values = append(values, structpb.NewListValue(listVal)) + } else if vType.IsMapType() || vType.IsObjectType() { + var objVal *structpb.Struct + objVal, err = protoStructObjectFromCty(value) + if err != nil { + return true + } + values = append(values, structpb.NewStructValue(objVal)) + } else if vType.IsPrimitiveType() { + switch vType { + case cty.String: + var stringVal string + stringVal, err = stringFromCty(value) + if err != nil { + return true + } + + values = append(values, structpb.NewStringValue(stringVal)) + case cty.Bool: + var boolVal bool + boolVal, err = boolFromCty(value) + if err != nil { + return true + } + + values = append(values, structpb.NewBoolValue(boolVal)) + case cty.Number: + var doubleVal float64 + doubleVal, err = doubleFromCty(value) + if err != nil { + return true + } + + values = append(values, structpb.NewNumberValue(doubleVal)) + default: + err = fmt.Errorf("unknown cty primitive type: %s", vType.FriendlyName()) + return true + } + } else { + err = fmt.Errorf("unsupported cty type: %s", vType.FriendlyName()) + return true + } + return false + }) + + if err != nil { + return nil, err + } + + return &structpb.ListValue{ + Values: values, + }, nil +} diff --git a/internal/resource/registry.go b/internal/resource/registry.go index 27b966f828..84f889ed1c 100644 --- a/internal/resource/registry.go +++ b/internal/resource/registry.go @@ -6,6 +6,7 @@ package resource import ( "fmt" "regexp" + "strings" "sync" "google.golang.org/protobuf/proto" @@ -171,3 +172,15 @@ func (r *TypeRegistry) Types() []Registration { func ToGVK(resourceType *pbresource.Type) string { return fmt.Sprintf("%s.%s.%s", resourceType.Group, resourceType.GroupVersion, resourceType.Kind) } + +func ParseGVK(gvk string) (*pbresource.Type, error) { + parts := strings.Split(gvk, ".") + if len(parts) != 3 { + return nil, fmt.Errorf("GVK string must be in the form .., got: %s", gvk) + } + return &pbresource.Type{ + Group: parts[0], + GroupVersion: parts[1], + Kind: parts[2], + }, nil +} diff --git a/internal/resourcehcl/any.go b/internal/resourcehcl/any.go new file mode 100644 index 0000000000..0f2d657d15 --- /dev/null +++ b/internal/resourcehcl/any.go @@ -0,0 +1,46 @@ +package resourcehcl + +import ( + "errors" + "fmt" + + "google.golang.org/protobuf/reflect/protoreflect" + + "github.com/hashicorp/consul/internal/protohcl" + "github.com/hashicorp/consul/internal/resource" + "github.com/hashicorp/consul/proto-public/pbresource" +) + +// anyProvider implements protohcl.AnyTypeProvider to infer the `Data` block +// type from `ID.Type`. +type anyProvider struct { + base protohcl.AnyTypeProvider + reg resource.Registry +} + +func (p anyProvider) AnyType(ctx *protohcl.UnmarshalContext, decoder protohcl.MessageDecoder) (protoreflect.FullName, protohcl.MessageDecoder, error) { + if ctx.Name != "Data" { + return p.base.AnyType(ctx, decoder) + } + + if ctx.Parent == nil || ctx.Parent.Message == nil { + return p.base.AnyType(ctx, decoder) + } + + res, isResource := ctx.Parent.Message.Interface().(*pbresource.Resource) + if !isResource { + return p.base.AnyType(ctx, decoder) + } + + resourceType := res.GetId().GetType() + if res == nil { + return "", nil, errors.New("ID.Type not found") + } + + reg, ok := p.reg.Resolve(resourceType) + if !ok { + return "", nil, fmt.Errorf("unknown resource type: %s", resource.ToGVK(resourceType)) + } + + return reg.Proto.ProtoReflect().Descriptor().FullName(), decoder, nil +} diff --git a/internal/resourcehcl/naming.go b/internal/resourcehcl/naming.go new file mode 100644 index 0000000000..7c714bb5b6 --- /dev/null +++ b/internal/resourcehcl/naming.go @@ -0,0 +1,35 @@ +package resourcehcl + +import ( + "strings" + + "google.golang.org/protobuf/reflect/protoreflect" +) + +// fieldNamer implements protohcl.FieldNamer to name fields using PascalCase +// with support for acroynms (e.g. ID, TCP). +type fieldNamer struct{ acroynms []string } + +func (n fieldNamer) NameField(fd protoreflect.FieldDescriptor) string { + camel := fd.JSONName() + upper := strings.ToUpper(camel) + + for _, a := range n.acroynms { + if upper == a { + return a + } + } + + return strings.ToUpper(camel[:1]) + camel[1:] +} + +func (n fieldNamer) GetField(fds protoreflect.FieldDescriptors, name string) protoreflect.FieldDescriptor { + for _, a := range n.acroynms { + if name == a { + return fds.ByJSONName(strings.ToLower(a)) + } + } + + camel := strings.ToLower(name[:1]) + name[1:] + return fds.ByJSONName(camel) +} diff --git a/internal/resourcehcl/testdata/gvk-no-arguments.error b/internal/resourcehcl/testdata/gvk-no-arguments.error new file mode 100644 index 0000000000..1c290dd3d6 --- /dev/null +++ b/internal/resourcehcl/testdata/gvk-no-arguments.error @@ -0,0 +1 @@ +gvk-no-arguments.hcl:2,14-15: Not enough function arguments; Function "gvk" expects 1 argument(s). Missing value for "GVK String". \ No newline at end of file diff --git a/internal/resourcehcl/testdata/gvk-no-arguments.hcl b/internal/resourcehcl/testdata/gvk-no-arguments.hcl new file mode 100644 index 0000000000..69aee02b2c --- /dev/null +++ b/internal/resourcehcl/testdata/gvk-no-arguments.hcl @@ -0,0 +1,4 @@ +ID { + Type = gvk() + Name = "foo" +} diff --git a/internal/resourcehcl/testdata/invalid-group.error b/internal/resourcehcl/testdata/invalid-group.error new file mode 100644 index 0000000000..6db3a339c2 --- /dev/null +++ b/internal/resourcehcl/testdata/invalid-group.error @@ -0,0 +1 @@ +invalid-group.hcl:3,13-17: Failed to unmarshal argument Group: expected value of type string but actual type is number \ No newline at end of file diff --git a/internal/resourcehcl/testdata/invalid-group.hcl b/internal/resourcehcl/testdata/invalid-group.hcl new file mode 100644 index 0000000000..ca6920163e --- /dev/null +++ b/internal/resourcehcl/testdata/invalid-group.hcl @@ -0,0 +1,8 @@ +ID { + Type { + Group = 1234 + GroupVersion = "v1" + Kind = "Artist" + } + Name = "foo" +} diff --git a/internal/resourcehcl/testdata/invalid-gvk.error b/internal/resourcehcl/testdata/invalid-gvk.error new file mode 100644 index 0000000000..92ebca5eb0 --- /dev/null +++ b/internal/resourcehcl/testdata/invalid-gvk.error @@ -0,0 +1 @@ +invalid-gvk.hcl:2,10-14: Error in function call; Call to function "gvk" failed: GVK string must be in the form .., got: nope. \ No newline at end of file diff --git a/internal/resourcehcl/testdata/invalid-gvk.hcl b/internal/resourcehcl/testdata/invalid-gvk.hcl new file mode 100644 index 0000000000..565a4cc3f9 --- /dev/null +++ b/internal/resourcehcl/testdata/invalid-gvk.hcl @@ -0,0 +1,4 @@ +ID { + Type = gvk("nope") + Name = "foo" +} diff --git a/internal/resourcehcl/testdata/invalid-metadata.error b/internal/resourcehcl/testdata/invalid-metadata.error new file mode 100644 index 0000000000..352f2ffa05 --- /dev/null +++ b/internal/resourcehcl/testdata/invalid-metadata.error @@ -0,0 +1 @@ +invalid-metadata.hcl:6,12-8,2: Failed to unmarshal argument Metadata["foo"]: expected value of type string but actual type is tuple \ No newline at end of file diff --git a/internal/resourcehcl/testdata/invalid-metadata.hcl b/internal/resourcehcl/testdata/invalid-metadata.hcl new file mode 100644 index 0000000000..aa10b5686e --- /dev/null +++ b/internal/resourcehcl/testdata/invalid-metadata.hcl @@ -0,0 +1,8 @@ +ID { + Type = gvk("demo.v1.Artist") + Name = "korn" +} + +Metadata = { + "foo" = ["bar"] +} diff --git a/internal/resourcehcl/testdata/invalid-name.error b/internal/resourcehcl/testdata/invalid-name.error new file mode 100644 index 0000000000..ba977a2e27 --- /dev/null +++ b/internal/resourcehcl/testdata/invalid-name.error @@ -0,0 +1 @@ +invalid-name.hcl:3,10-14: Failed to unmarshal argument Name: expected value of type string but actual type is number \ No newline at end of file diff --git a/internal/resourcehcl/testdata/invalid-name.hcl b/internal/resourcehcl/testdata/invalid-name.hcl new file mode 100644 index 0000000000..54ca6b186b --- /dev/null +++ b/internal/resourcehcl/testdata/invalid-name.hcl @@ -0,0 +1,4 @@ +ID { + Type = gvk("demo.v1.Artist") + Name = 1234 +} diff --git a/internal/resourcehcl/testdata/no-blocks-any-first.golden b/internal/resourcehcl/testdata/no-blocks-any-first.golden new file mode 100644 index 0000000000..18a7cfd9ae --- /dev/null +++ b/internal/resourcehcl/testdata/no-blocks-any-first.golden @@ -0,0 +1 @@ +{"id":{"name":"korn","type":{"group":"demo","groupVersion":"v1","kind":"Artist"}},"data":{"@type":"hashicorp.consul.internal.demo.v1.Artist","name":"Korn"}} \ No newline at end of file diff --git a/internal/resourcehcl/testdata/no-blocks-any-first.hcl b/internal/resourcehcl/testdata/no-blocks-any-first.hcl new file mode 100644 index 0000000000..8eb8d75f43 --- /dev/null +++ b/internal/resourcehcl/testdata/no-blocks-any-first.hcl @@ -0,0 +1,8 @@ +Data = { + Name = "Korn" +} + +ID = { + Type = gvk("demo.v1.Artist") + Name = "korn" +} diff --git a/internal/resourcehcl/testdata/no-blocks.golden b/internal/resourcehcl/testdata/no-blocks.golden new file mode 100644 index 0000000000..c7243ad161 --- /dev/null +++ b/internal/resourcehcl/testdata/no-blocks.golden @@ -0,0 +1 @@ +{"id":{"type":{"group":"mesh","groupVersion":"v1alpha1","kind":"Upstreams"}},"data":{"@type":"hashicorp.consul.mesh.v1alpha1.Upstreams","workloads":{"prefixes":["api"]},"upstreams":[{"destinationRef":{"name":"db","type":{"group":"catalog","groupVersion":"v1alpha1","kind":"Service"}},"destinationPort":"tcp","ipPort":{"port":1234}}]}} diff --git a/internal/resourcehcl/testdata/no-blocks.hcl b/internal/resourcehcl/testdata/no-blocks.hcl new file mode 100644 index 0000000000..b134e81246 --- /dev/null +++ b/internal/resourcehcl/testdata/no-blocks.hcl @@ -0,0 +1,33 @@ +ID = { + Type = { + Group = "mesh" + GroupVersion = "v1alpha1" + Kind = "Upstreams" + } +} + +Data = { + Workloads = { + Prefixes = ["api"] + } + + Upstreams = [ + { + DestinationRef = { + Type = { + Group = "catalog" + GroupVersion = "v1alpha1" + Kind = "Service" + } + + Name = "db" + } + + DestinationPort = "tcp" + + IpPort = { + Port = 1234 + } + } + ] +} diff --git a/internal/resourcehcl/testdata/owner.golden b/internal/resourcehcl/testdata/owner.golden new file mode 100644 index 0000000000..9054db09bf --- /dev/null +++ b/internal/resourcehcl/testdata/owner.golden @@ -0,0 +1 @@ +{"id":{"name":"twisted-transistor","type":{"group":"demo","groupVersion":"v1","kind":"Album"}},"owner":{"name":"korn","type":{"group":"demo","groupVersion":"v1","kind":"Artist"}}} \ No newline at end of file diff --git a/internal/resourcehcl/testdata/owner.hcl b/internal/resourcehcl/testdata/owner.hcl new file mode 100644 index 0000000000..2cc65e4c66 --- /dev/null +++ b/internal/resourcehcl/testdata/owner.hcl @@ -0,0 +1,9 @@ +ID { + Type = gvk("demo.v1.Album") + Name = "twisted-transistor" +} + +Owner { + Type = gvk("demo.v1.Artist") + Name = "korn" +} diff --git a/internal/resourcehcl/testdata/simple-gvk.golden b/internal/resourcehcl/testdata/simple-gvk.golden new file mode 100644 index 0000000000..ba3fddcd6d --- /dev/null +++ b/internal/resourcehcl/testdata/simple-gvk.golden @@ -0,0 +1 @@ +{"id":{"name":"korn","type":{"group":"demo","groupVersion":"v1","kind":"Artist"}},"metadata":{"foo":"bar"},"data":{"@type":"hashicorp.consul.internal.demo.v1.Artist","name":"Korn","genre":"GENRE_METAL"}} \ No newline at end of file diff --git a/internal/resourcehcl/testdata/simple-gvk.hcl b/internal/resourcehcl/testdata/simple-gvk.hcl new file mode 100644 index 0000000000..adddcd42c8 --- /dev/null +++ b/internal/resourcehcl/testdata/simple-gvk.hcl @@ -0,0 +1,13 @@ +ID { + Type = gvk("demo.v1.Artist") + Name = "korn" +} + +Data { + Name = "Korn" + Genre = "GENRE_METAL" +} + +Metadata = { + "foo" = "bar" +} diff --git a/internal/resourcehcl/testdata/type-block.golden b/internal/resourcehcl/testdata/type-block.golden new file mode 100644 index 0000000000..963695616b --- /dev/null +++ b/internal/resourcehcl/testdata/type-block.golden @@ -0,0 +1 @@ +{"id":{"name":"korn","type":{"group":"demo","groupVersion":"v1","kind":"Artist"}}} \ No newline at end of file diff --git a/internal/resourcehcl/testdata/type-block.hcl b/internal/resourcehcl/testdata/type-block.hcl new file mode 100644 index 0000000000..f927fbe2fe --- /dev/null +++ b/internal/resourcehcl/testdata/type-block.hcl @@ -0,0 +1,8 @@ +ID { + Type { + Group = "demo" + GroupVersion = "v1" + Kind = "Artist" + } + Name = "korn" +} diff --git a/internal/resourcehcl/testdata/unknown-field-block.error b/internal/resourcehcl/testdata/unknown-field-block.error new file mode 100644 index 0000000000..602212543d --- /dev/null +++ b/internal/resourcehcl/testdata/unknown-field-block.error @@ -0,0 +1 @@ +unknown-field-block.hcl:2,3-6: Unsupported argument; An argument named "Foo" is not expected here. \ No newline at end of file diff --git a/internal/resourcehcl/testdata/unknown-field-block.hcl b/internal/resourcehcl/testdata/unknown-field-block.hcl new file mode 100644 index 0000000000..534fdb11b8 --- /dev/null +++ b/internal/resourcehcl/testdata/unknown-field-block.hcl @@ -0,0 +1,3 @@ +ID { + Foo = "bar" +} diff --git a/internal/resourcehcl/testdata/unknown-field-object.error b/internal/resourcehcl/testdata/unknown-field-object.error new file mode 100644 index 0000000000..b7651d5eb0 --- /dev/null +++ b/internal/resourcehcl/testdata/unknown-field-object.error @@ -0,0 +1 @@ +unknown-field-object.hcl:1,6-3,2: Unsupported argument; An argument named "Foo" is not expected here. \ No newline at end of file diff --git a/internal/resourcehcl/testdata/unknown-field-object.hcl b/internal/resourcehcl/testdata/unknown-field-object.hcl new file mode 100644 index 0000000000..43220099bb --- /dev/null +++ b/internal/resourcehcl/testdata/unknown-field-object.hcl @@ -0,0 +1,3 @@ +ID = { + Foo = "bar" +} diff --git a/internal/resourcehcl/testdata/unknown-type.error b/internal/resourcehcl/testdata/unknown-type.error new file mode 100644 index 0000000000..81e0ea1c81 --- /dev/null +++ b/internal/resourcehcl/testdata/unknown-type.error @@ -0,0 +1 @@ +error getting type for Any field: unknown resource type: foo.bar.Baz \ No newline at end of file diff --git a/internal/resourcehcl/testdata/unknown-type.hcl b/internal/resourcehcl/testdata/unknown-type.hcl new file mode 100644 index 0000000000..f1669b8ba8 --- /dev/null +++ b/internal/resourcehcl/testdata/unknown-type.hcl @@ -0,0 +1,8 @@ +ID { + Type = gvk("foo.bar.Baz") + Name = "qux" +} + +Data { + Foo = "bar" +} diff --git a/internal/resourcehcl/testdata/upstreams.golden b/internal/resourcehcl/testdata/upstreams.golden new file mode 100644 index 0000000000..26608ae3d5 --- /dev/null +++ b/internal/resourcehcl/testdata/upstreams.golden @@ -0,0 +1 @@ +{"id":{"name":"api","type":{"group":"mesh","groupVersion":"v1alpha1","kind":"Upstreams"}},"data":{"@type":"hashicorp.consul.mesh.v1alpha1.Upstreams","workloads":{"prefixes":["api"]},"upstreams":[{"destinationRef":{"name":"db","type":{"group":"catalog","groupVersion":"v1alpha1","kind":"Service"}},"destinationPort":"tcp","ipPort":{"port":1234}}]}} diff --git a/internal/resourcehcl/testdata/upstreams.hcl b/internal/resourcehcl/testdata/upstreams.hcl new file mode 100644 index 0000000000..c0993e4d5b --- /dev/null +++ b/internal/resourcehcl/testdata/upstreams.hcl @@ -0,0 +1,25 @@ +ID { + Type = gvk("mesh.v1alpha1.Upstreams") + Name = "api" +} + +Data { + Workloads { + Prefixes = ["api"] + } + + Upstreams = [ + { + DestinationRef = { + Type = gvk("catalog.v1alpha1.Service") + Name = "db" + } + + DestinationPort = "tcp" + + IpPort = { + Port = 1234 + } + } + ] +} diff --git a/internal/resourcehcl/unmarshal.go b/internal/resourcehcl/unmarshal.go new file mode 100644 index 0000000000..fba8bbdb53 --- /dev/null +++ b/internal/resourcehcl/unmarshal.go @@ -0,0 +1,52 @@ +package resourcehcl + +import ( + "reflect" + + "github.com/zclconf/go-cty/cty" + "github.com/zclconf/go-cty/cty/function" + + "github.com/hashicorp/consul/internal/protohcl" + "github.com/hashicorp/consul/internal/resource" + "github.com/hashicorp/consul/proto-public/pbresource" +) + +// Unmarshal the given HCL source into a resource. +func Unmarshal(src []byte, reg resource.Registry) (*pbresource.Resource, error) { + return UnmarshalOptions{}.Unmarshal(src, reg) +} + +type UnmarshalOptions struct{ SourceFileName string } + +// Unmarshal the given HCL source into a resource. +func (u UnmarshalOptions) Unmarshal(src []byte, reg resource.Registry) (*pbresource.Resource, error) { + var out pbresource.Resource + err := (protohcl.UnmarshalOptions{ + SourceFileName: u.SourceFileName, + AnyTypeProvider: anyProvider{ + base: &protohcl.AnyTypeURLProvider{TypeURLFieldName: "Type"}, + reg: reg, + }, + FieldNamer: fieldNamer{acroynms: []string{"ID", "TCP", "UDP", "HTTP"}}, + Functions: map[string]function.Function{"gvk": gvk}, + }).Unmarshal(src, &out) + return &out, err +} + +var ( + typeType = cty.Capsule("type", reflect.TypeOf(pbresource.Type{})) + + gvk = function.New(&function.Spec{ + Params: []function.Parameter{ + {Name: "GVK String", Type: cty.String}, + }, + Type: function.StaticReturnType(typeType), + Impl: func(args []cty.Value, _ cty.Type) (cty.Value, error) { + t, err := resource.ParseGVK(args[0].AsString()) + if err != nil { + return cty.NilVal, err + } + return cty.CapsuleVal(typeType, t), nil + }, + }) +) diff --git a/internal/resourcehcl/unmarshal_test.go b/internal/resourcehcl/unmarshal_test.go new file mode 100644 index 0000000000..a47d3c805c --- /dev/null +++ b/internal/resourcehcl/unmarshal_test.go @@ -0,0 +1,103 @@ +package resourcehcl_test + +import ( + "flag" + "fmt" + "os" + "path" + "testing" + + "github.com/stretchr/testify/require" + "google.golang.org/protobuf/encoding/protojson" + + "github.com/hashicorp/consul/internal/mesh" + "github.com/hashicorp/consul/internal/resource" + "github.com/hashicorp/consul/internal/resource/demo" + "github.com/hashicorp/consul/internal/resourcehcl" + "github.com/hashicorp/consul/proto-public/pbresource" + "github.com/hashicorp/consul/proto/private/prototest" +) + +var update = flag.Bool("update", false, "update golden files") + +func TestUnmarshal(t *testing.T) { + entries, err := os.ReadDir("./testdata") + require.NoError(t, err) + + read := func(t *testing.T, path string) ([]byte, bool) { + t.Helper() + + bytes, err := os.ReadFile(fmt.Sprintf("./testdata/%s", path)) + switch { + case err == nil: + return bytes, true + case os.IsNotExist(err): + return nil, false + } + + t.Fatalf("failed to read file %s %v", path, err) + return nil, false + } + + write := func(t *testing.T, path string, src []byte) { + t.Helper() + + require.NoError(t, os.WriteFile(fmt.Sprintf("./testdata/%s", path), src, 0o600)) + } + + for _, entry := range entries { + name := entry.Name() + ext := path.Ext(name) + + if ext != ".hcl" { + continue + } + + base := name[0 : len(name)-len(ext)] + + t.Run(base, func(t *testing.T) { + input, _ := read(t, name) + + registry := resource.NewRegistry() + demo.RegisterTypes(registry) + mesh.RegisterTypes(registry) + + output, err := resourcehcl.UnmarshalOptions{SourceFileName: name}. + Unmarshal(input, registry) + + if *update { + if err == nil { + json, err := protojson.Marshal(output) + require.NoError(t, err) + write(t, base+".golden", json) + } else { + write(t, base+".error", []byte(err.Error())) + } + } + + goldenJSON, haveGoldenJSON := read(t, base+".golden") + goldenError, haveGoldenError := read(t, base+".error") + + if haveGoldenError && haveGoldenJSON { + t.Fatalf("both %s.golden and %s.error exist, delete one", base, base) + } + + if !haveGoldenError && !haveGoldenJSON && !*update { + t.Fatalf("neither %s.golden or %s.error exist, run the tests again with the -update flag to create one", base, base) + } + + if haveGoldenError { + require.Error(t, err) + require.Equal(t, string(goldenError), err.Error()) + } + + if haveGoldenJSON { + require.NoError(t, err) + + var exp pbresource.Resource + require.NoError(t, protojson.Unmarshal(goldenJSON, &exp)) + prototest.AssertDeepEqual(t, &exp, output) + } + }) + } +}