diff --git a/go.mod b/go.mod index b39fa52db706..026dbb81080e 100644 --- a/go.mod +++ b/go.mod @@ -274,7 +274,7 @@ require ( github.com/aws/smithy-go v1.23.0 github.com/beevik/etree v1.6.0 github.com/cedar-policy/cedar-go v1.2.6 - github.com/davecgh/go-spew v1.1.1 + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc github.com/dlclark/regexp2 v1.11.5 github.com/gertd/go-pluralize v0.2.1 github.com/goccy/go-yaml v1.18.0 @@ -290,15 +290,15 @@ require ( github.com/hashicorp/go-uuid v1.0.3 github.com/hashicorp/go-version v1.7.0 github.com/hashicorp/hcl/v2 v2.23.0 - github.com/hashicorp/terraform-json v0.27.1 - github.com/hashicorp/terraform-plugin-framework v1.15.1 + github.com/hashicorp/terraform-json v0.27.2 + github.com/hashicorp/terraform-plugin-framework v1.16.0 github.com/hashicorp/terraform-plugin-framework-jsontypes v0.2.0 github.com/hashicorp/terraform-plugin-framework-timeouts v0.5.0 github.com/hashicorp/terraform-plugin-framework-timetypes v0.5.0 github.com/hashicorp/terraform-plugin-framework-validators v0.18.0 - github.com/hashicorp/terraform-plugin-go v0.28.0 + github.com/hashicorp/terraform-plugin-go v0.29.0 github.com/hashicorp/terraform-plugin-log v0.9.0 - github.com/hashicorp/terraform-plugin-mux v0.20.0 + github.com/hashicorp/terraform-plugin-mux v0.21.0 github.com/hashicorp/terraform-plugin-sdk/v2 v2.37.0 github.com/hashicorp/terraform-plugin-testing v1.13.3 github.com/jaswdr/faker/v2 v2.8.0 @@ -345,21 +345,21 @@ require ( github.com/google/uuid v1.6.0 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-checkpoint v0.5.0 // indirect - github.com/hashicorp/go-plugin v1.6.3 // indirect + github.com/hashicorp/go-plugin v1.7.0 // indirect github.com/hashicorp/go-retryablehttp v0.7.7 // indirect github.com/hashicorp/hc-install v0.9.2 // indirect github.com/hashicorp/logutils v1.0.0 // indirect github.com/hashicorp/terraform-exec v0.23.0 // indirect - github.com/hashicorp/terraform-registry-address v0.2.5 // indirect + github.com/hashicorp/terraform-registry-address v0.4.0 // indirect github.com/hashicorp/terraform-svchost v0.1.1 // indirect - github.com/hashicorp/yamux v0.1.1 // indirect + github.com/hashicorp/yamux v0.1.2 // indirect github.com/huandu/xstrings v1.3.3 // indirect github.com/imdario/mergo v0.3.15 // indirect github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/mitchellh/go-wordwrap v1.0.1 // indirect github.com/mitchellh/reflectwalk v1.0.2 // indirect - github.com/oklog/run v1.0.0 // indirect + github.com/oklog/run v1.1.0 // indirect github.com/posener/complete v1.2.3 // indirect github.com/spf13/cast v1.3.1 // indirect github.com/vmihailenco/msgpack v4.0.4+incompatible // indirect @@ -380,9 +380,9 @@ require ( golang.org/x/sync v0.17.0 // indirect golang.org/x/sys v0.36.0 // indirect google.golang.org/appengine v1.6.8 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20250505200425-f936aa4a68b2 // indirect - google.golang.org/grpc v1.72.1 // indirect - google.golang.org/protobuf v1.36.6 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250804133106-a7a43d27e69b // indirect + google.golang.org/grpc v1.75.1 // indirect + google.golang.org/protobuf v1.36.9 // indirect ) replace github.com/hashicorp/terraform-plugin-log => github.com/gdavison/terraform-plugin-log v0.0.0-20230928191232-6c653d8ef8fb diff --git a/go.sum b/go.sum index cf7b62f13402..45428faddc78 100644 --- a/go.sum +++ b/go.sum @@ -573,8 +573,8 @@ github.com/bgentry/speakeasy v0.1.0 h1:ByYyxL9InA1OWqxJqqp2A5pYHUrCiAL6K3J+LKSsQ github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc h1:biVzkmvwrH8WK8raXaxBx6fRVTlJILwEwQGL1I/ByEI= github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= -github.com/bufbuild/protocompile v0.4.0 h1:LbFKd2XowZvQ/kajzguUp2DC9UEIQhIq77fZZlaQsNA= -github.com/bufbuild/protocompile v0.4.0/go.mod h1:3v93+mbWn/v3xzN+31nwkJfrEpAUwp+BagBSZWx+TP8= +github.com/bufbuild/protocompile v0.14.1 h1:iA73zAf/fyljNjQKwYzUHD6AD4R8KMasmwa/FBatYVw= +github.com/bufbuild/protocompile v0.14.1/go.mod h1:ppVdAIhbr2H8asPk6k4pY7t9zB1OU5DoEw9xY/FUi1c= github.com/cedar-policy/cedar-go v1.2.6 h1:q6f1sRxhoBG7lnK/fH6oBG33ruf2yIpcfcPXNExANa0= github.com/cedar-policy/cedar-go v1.2.6/go.mod h1:h5+3CVW1oI5LXVskJG+my9TFCYI5yjh/+Ul3EJie6MI= github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0= @@ -582,8 +582,9 @@ github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZ github.com/cyphar/filepath-securejoin v0.4.1 h1:JyxxyPEaktOD+GAnqIqTf9A8tHyAG22rowi7HkoSU1s= github.com/cyphar/filepath-securejoin v0.4.1/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dlclark/regexp2 v1.11.5 h1:Q/sSnsKerHeCkc/jSTNq1oCm7KiVgUMZRDUoRu0JQZQ= github.com/dlclark/regexp2 v1.11.5/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= @@ -649,8 +650,8 @@ github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVH github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= -github.com/hashicorp/go-plugin v1.6.3 h1:xgHB+ZUSYeuJi96WtxEjzi23uh7YQpznjGh0U0UUrwg= -github.com/hashicorp/go-plugin v1.6.3/go.mod h1:MRobyh+Wc/nYy1V4KAXUiYfzxoYhs7V1mlH1Z7iY2h0= +github.com/hashicorp/go-plugin v1.7.0 h1:YghfQH/0QmPNc/AZMTFE3ac8fipZyZECHdDPshfk+mA= +github.com/hashicorp/go-plugin v1.7.0/go.mod h1:BExt6KEaIYx804z8k4gRzRLEvxKVb+kn0NMcihqOqb8= github.com/hashicorp/go-retryablehttp v0.7.7 h1:C8hUCYzor8PIfXHa4UrZkU4VvK8o9ISHxT2Q8+VepXU= github.com/hashicorp/go-retryablehttp v0.7.7/go.mod h1:pkQpWZeYWskR+D1tR2O5OcBFOxfA7DoAO6xtkuQnHTk= github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= @@ -666,10 +667,10 @@ github.com/hashicorp/logutils v1.0.0 h1:dLEQVugN8vlakKOUE3ihGLTZJRB4j+M2cdTm/ORI github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= github.com/hashicorp/terraform-exec v0.23.0 h1:MUiBM1s0CNlRFsCLJuM5wXZrzA3MnPYEsiXmzATMW/I= github.com/hashicorp/terraform-exec v0.23.0/go.mod h1:mA+qnx1R8eePycfwKkCRk3Wy65mwInvlpAeOwmA7vlY= -github.com/hashicorp/terraform-json v0.27.1 h1:zWhEracxJW6lcjt/JvximOYyc12pS/gaKSy/wzzE7nY= -github.com/hashicorp/terraform-json v0.27.1/go.mod h1:GzPLJ1PLdUG5xL6xn1OXWIjteQRT2CNT9o/6A9mi9hE= -github.com/hashicorp/terraform-plugin-framework v1.15.1 h1:2mKDkwb8rlx/tvJTlIcpw0ykcmvdWv+4gY3SIgk8Pq8= -github.com/hashicorp/terraform-plugin-framework v1.15.1/go.mod h1:hxrNI/GY32KPISpWqlCoTLM9JZsGH3CyYlir09bD/fI= +github.com/hashicorp/terraform-json v0.27.2 h1:BwGuzM6iUPqf9JYM/Z4AF1OJ5VVJEEzoKST/tRDBJKU= +github.com/hashicorp/terraform-json v0.27.2/go.mod h1:GzPLJ1PLdUG5xL6xn1OXWIjteQRT2CNT9o/6A9mi9hE= +github.com/hashicorp/terraform-plugin-framework v1.16.0 h1:tP0f+yJg0Z672e7levixDe5EpWwrTrNryPM9kDMYIpE= +github.com/hashicorp/terraform-plugin-framework v1.16.0/go.mod h1:0xFOxLy5lRzDTayc4dzK/FakIgBhNf/lC4499R9cV4Y= github.com/hashicorp/terraform-plugin-framework-jsontypes v0.2.0 h1:SJXL5FfJJm17554Kpt9jFXngdM6fXbnUnZ6iT2IeiYA= github.com/hashicorp/terraform-plugin-framework-jsontypes v0.2.0/go.mod h1:p0phD0IYhsu9bR4+6OetVvvH59I6LwjXGnTVEr8ox6E= github.com/hashicorp/terraform-plugin-framework-timeouts v0.5.0 h1:I/N0g/eLZ1ZkLZXUQ0oRSXa8YG/EF0CEuQP1wXdrzKw= @@ -678,20 +679,20 @@ github.com/hashicorp/terraform-plugin-framework-timetypes v0.5.0 h1:v3DapR8gsp3E github.com/hashicorp/terraform-plugin-framework-timetypes v0.5.0/go.mod h1:c3PnGE9pHBDfdEVG9t1S1C9ia5LW+gkFR0CygXlM8ak= github.com/hashicorp/terraform-plugin-framework-validators v0.18.0 h1:OQnlOt98ua//rCw+QhBbSqfW3QbwtVrcdWeQN5gI3Hw= github.com/hashicorp/terraform-plugin-framework-validators v0.18.0/go.mod h1:lZvZvagw5hsJwuY7mAY6KUz45/U6fiDR0CzQAwWD0CA= -github.com/hashicorp/terraform-plugin-go v0.28.0 h1:zJmu2UDwhVN0J+J20RE5huiF3XXlTYVIleaevHZgKPA= -github.com/hashicorp/terraform-plugin-go v0.28.0/go.mod h1:FDa2Bb3uumkTGSkTFpWSOwWJDwA7bf3vdP3ltLDTH6o= -github.com/hashicorp/terraform-plugin-mux v0.20.0 h1:3QpBnI9uCuL0Yy2Rq/kR9cOdmOFNhw88A2GoZtk5aXM= -github.com/hashicorp/terraform-plugin-mux v0.20.0/go.mod h1:wSIZwJjSYk86NOTX3fKUlThMT4EAV1XpBHz9SAvjQr4= +github.com/hashicorp/terraform-plugin-go v0.29.0 h1:1nXKl/nSpaYIUBU1IG/EsDOX0vv+9JxAltQyDMpq5mU= +github.com/hashicorp/terraform-plugin-go v0.29.0/go.mod h1:vYZbIyvxyy0FWSmDHChCqKvI40cFTDGSb3D8D70i9GM= +github.com/hashicorp/terraform-plugin-mux v0.21.0 h1:QsEYnzSD2c3zT8zUrUGqaFGhV/Z8zRUlU7FY3ZPJFfw= +github.com/hashicorp/terraform-plugin-mux v0.21.0/go.mod h1:Qpt8+6AD7NmL0DS7ASkN0EXpDQ2J/FnnIgeUr1tzr5A= github.com/hashicorp/terraform-plugin-sdk/v2 v2.37.0 h1:NFPMacTrY/IdcIcnUB+7hsore1ZaRWU9cnB6jFoBnIM= github.com/hashicorp/terraform-plugin-sdk/v2 v2.37.0/go.mod h1:QYmYnLfsosrxjCnGY1p9c7Zj6n9thnEE+7RObeYs3fA= github.com/hashicorp/terraform-plugin-testing v1.13.3 h1:QLi/khB8Z0a5L54AfPrHukFpnwsGL8cwwswj4RZduCo= github.com/hashicorp/terraform-plugin-testing v1.13.3/go.mod h1:WHQ9FDdiLoneey2/QHpGM/6SAYf4A7AZazVg7230pLE= -github.com/hashicorp/terraform-registry-address v0.2.5 h1:2GTftHqmUhVOeuu9CW3kwDkRe4pcBDq0uuK5VJngU1M= -github.com/hashicorp/terraform-registry-address v0.2.5/go.mod h1:PpzXWINwB5kuVS5CA7m1+eO2f1jKb5ZDIxrOPfpnGkg= +github.com/hashicorp/terraform-registry-address v0.4.0 h1:S1yCGomj30Sao4l5BMPjTGZmCNzuv7/GDTDX99E9gTk= +github.com/hashicorp/terraform-registry-address v0.4.0/go.mod h1:LRS1Ay0+mAiRkUyltGT+UHWkIqTFvigGn/LbMshfflE= github.com/hashicorp/terraform-svchost v0.1.1 h1:EZZimZ1GxdqFRinZ1tpJwVxxt49xc/S52uzrw4x0jKQ= github.com/hashicorp/terraform-svchost v0.1.1/go.mod h1:mNsjQfZyf/Jhz35v6/0LWcv26+X7JPS+buii2c9/ctc= -github.com/hashicorp/yamux v0.1.1 h1:yrQxtgseBDrq9Y652vSRDvsKCJKOUD+GzTS4Y0Y8pvE= -github.com/hashicorp/yamux v0.1.1/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ= +github.com/hashicorp/yamux v0.1.2 h1:XtB8kyFOyHXYVFnwT5C3+Bdo8gArse7j2AQ0DA0Uey8= +github.com/hashicorp/yamux v0.1.2/go.mod h1:C+zze2n6e/7wshOZep2A70/aQU6QBRWJO/G6FT1wIns= github.com/huandu/xstrings v1.3.3 h1:/Gcsuc1x8JVbJ9/rlye4xZnVAbEkGauT8lbebqcQws4= github.com/huandu/xstrings v1.3.3/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= @@ -702,8 +703,8 @@ github.com/jaswdr/faker/v2 v2.8.0/go.mod h1:jZq+qzNQr8/P+5fHd9t3txe2GNPnthrTfoht github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= -github.com/jhump/protoreflect v1.15.1 h1:HUMERORf3I3ZdX05WaQ6MIpd/NJ434hTp5YiKgfCL6c= -github.com/jhump/protoreflect v1.15.1/go.mod h1:jD/2GMKKE6OqX8qTjhADU1e6DShO+gavG9e0Q693nKo= +github.com/jhump/protoreflect v1.17.0 h1:qOEr613fac2lOuTgWN4tPAtLL7fUSbuJL5X5XumQh94= +github.com/jhump/protoreflect v1.17.0/go.mod h1:h9+vUUL38jiBzck8ck+6G/aeMX8Z4QUY/NiJPwPNi+8= github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= @@ -741,14 +742,15 @@ github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RR github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= -github.com/oklog/run v1.0.0 h1:Ru7dDtJNOyC66gQ5dQmaCa0qIsAUFY3sFpK1Xk8igrw= -github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= +github.com/oklog/run v1.1.0 h1:GEenZ1cK0+q0+wsJew9qUg/DyD8k3JzYsZAi5gYi2mA= +github.com/oklog/run v1.1.0/go.mod h1:sVPdnTZT1zYwAJeCMu2Th4T21pA3FPOQRfWjQlk7DVU= github.com/pjbgf/sha1cd v0.3.2 h1:a9wb0bp1oC2TGwStyn0Umc/IGKQnEgF0vVaZ8QF8eo4= github.com/pjbgf/sha1cd v0.3.2/go.mod h1:zQWigSxVmsHEZow5qaLtPYxpcKMMQpa09ixqBxuCS6A= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/posener/complete v1.2.3 h1:NP0eAhjcjImqslEwo/1hq7gpajME0fTLTezBKDqfXqo= github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s= github.com/pquerna/otp v1.5.0 h1:NMMR+WrmaqXU4EzdGJEE1aUUI0AMRzsp96fFFWNPwxs= @@ -802,8 +804,8 @@ go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgf go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI= go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E= go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg= -go.opentelemetry.io/otel/sdk/metric v1.34.0 h1:5CeK9ujjbFVL5c1PhLuStg1wxA7vQv7ce1EK0Gyvahk= -go.opentelemetry.io/otel/sdk/metric v1.34.0/go.mod h1:jQ/r8Ze28zRKoNRdkjCZxfs6YvBTG1+YIqyFVFYec5w= +go.opentelemetry.io/otel/sdk/metric v1.37.0 h1:90lI228XrB9jCMuSdA0673aubgRobVZFhbjxHHspCPc= +go.opentelemetry.io/otel/sdk/metric v1.37.0/go.mod h1:cNen4ZWfiD37l5NhS+Keb5RXVWZWpRE+9WyVCpbo5ps= go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE= go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= @@ -861,17 +863,19 @@ golang.org/x/tools v0.37.0 h1:DVSRzp7FwePZW356yEAChSdNcQo6Nsp+fex1SUW09lE= golang.org/x/tools v0.37.0/go.mod h1:MBN5QPQtLMHVdvsbtarmTNukZDdgwdwlO5qGacAzF0w= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= +gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM= google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250505200425-f936aa4a68b2 h1:IqsN8hx+lWLqlN+Sc3DoMy/watjofWiU8sRFgQ8fhKM= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250505200425-f936aa4a68b2/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= -google.golang.org/grpc v1.72.1 h1:HR03wO6eyZ7lknl75XlxABNVLLFc2PAb6mHlYh756mA= -google.golang.org/grpc v1.72.1/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250804133106-a7a43d27e69b h1:zPKJod4w6F1+nRGDI9ubnXYhU9NSWoFAijkHkUXeTK8= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250804133106-a7a43d27e69b/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= +google.golang.org/grpc v1.75.1 h1:/ODCNEuf9VghjgO3rqLcfg8fiOP0nSluljWFlDxELLI= +google.golang.org/grpc v1.75.1/go.mod h1:JtPAzKiq4v1xcAB2hydNlWI2RnF85XXcV0mhKXr2ecQ= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= -google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= +google.golang.org/protobuf v1.36.9 h1:w2gp2mA27hUeUzj9Ex9FBjsBm40zfaDtEWow293U7Iw= +google.golang.org/protobuf v1.36.9/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= diff --git a/internal/conns/conns.go b/internal/conns/conns.go index e2dc26434014..e59ca4c0feae 100644 --- a/internal/conns/conns.go +++ b/internal/conns/conns.go @@ -20,6 +20,13 @@ type ServicePackage interface { ServicePackageName() string } +// ServicePackageWithActions is an interface that extends ServicePackage with actions. +// Actions are imperative operations that can be invoked to perform Day-2 operations. +type ServicePackageWithActions interface { + ServicePackage + Actions(context.Context) []*types.ServicePackageAction +} + // ServicePackageWithEphemeralResources is an interface that extends ServicePackage with ephemeral resources. // Ephemeral resources are resources that are not part of the Terraform state, but are used to create other resources. type ServicePackageWithEphemeralResources interface { diff --git a/internal/framework/action_test.go b/internal/framework/action_test.go new file mode 100644 index 000000000000..a4f9206a3d04 --- /dev/null +++ b/internal/framework/action_test.go @@ -0,0 +1,53 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package framework + +import ( + "testing" + + "github.com/hashicorp/terraform-provider-aws/internal/conns" +) + +// Test that ActionWithConfigure can be instantiated and has the expected methods +func TestActionWithConfigureCompilation(t *testing.T) { + t.Parallel() + + // This test ensures our new types compile correctly + var action ActionWithConfigure + + // Test that it has the Meta method from withMeta + if action.Meta() != nil { + t.Error("Expected nil meta before configuration") + } + + // Test that it embeds withMeta correctly + action.meta = &conns.AWSClient{} + if action.Meta() == nil { + t.Error("Expected non-nil meta after setting") + } +} + +// Test that ActionWithModel can be instantiated +func TestActionWithModelCompilation(t *testing.T) { + t.Parallel() + + // Test model + type testModel struct { + Name string `tfsdk:"name"` + } + + // This test ensures our new generic type compiles correctly + var action ActionWithModel[testModel] + + // Test that it has the Meta method from ActionWithConfigure + if action.Meta() != nil { + t.Error("Expected nil meta before configuration") + } + + // Test that it embeds ActionWithConfigure correctly + action.meta = &conns.AWSClient{} + if action.Meta() == nil { + t.Error("Expected non-nil meta after setting") + } +} diff --git a/internal/framework/action_with_configure.go b/internal/framework/action_with_configure.go new file mode 100644 index 000000000000..d79695fab5f3 --- /dev/null +++ b/internal/framework/action_with_configure.go @@ -0,0 +1,30 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package framework + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-framework/action" + "github.com/hashicorp/terraform-provider-aws/internal/conns" +) + +type ActionWithConfigure struct { + withMeta +} + +// Metadata should return the full name of the action, such as +// aws_lambda_invoke. +func (*ActionWithConfigure) Metadata(_ context.Context, request action.MetadataRequest, response *action.MetadataResponse) { + // This method is implemented in the wrappers. + panic("not implemented") // lintignore:R009 +} + +// Configure enables provider-level data or clients to be set in the +// provider-defined Action type. +func (a *ActionWithConfigure) Configure(_ context.Context, request action.ConfigureRequest, _ *action.ConfigureResponse) { + if v, ok := request.ProviderData.(*conns.AWSClient); ok { + a.meta = v + } +} diff --git a/internal/framework/action_with_model.go b/internal/framework/action_with_model.go new file mode 100644 index 000000000000..2722dc3fe69f --- /dev/null +++ b/internal/framework/action_with_model.go @@ -0,0 +1,36 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package framework + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-framework/action/schema" + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/tfsdk" + "github.com/hashicorp/terraform-plugin-go/tftypes" +) + +// ActionWithModel is a structure to be embedded within an Action that has a corresponding model. +type ActionWithModel[T any] struct { + withModel[T] + ActionWithConfigure +} + +// ValidateModel validates the action's model against a schema. +func (a *ActionWithModel[T]) ValidateModel(ctx context.Context, schema *schema.Schema) diag.Diagnostics { + var diags diag.Diagnostics + state := tfsdk.State{ + Raw: tftypes.NewValue(schema.Type().TerraformType(ctx), nil), + Schema: schema, + } + + diags.Append(a.validateModel(ctx, &state)...) + + return diags +} + +type ActionValidateModel interface { + ValidateModel(ctx context.Context, schema *schema.Schema) diag.Diagnostics +} diff --git a/internal/generate/servicepackage/main.go b/internal/generate/servicepackage/main.go index c6819d4dafea..8c7b965a3f7c 100644 --- a/internal/generate/servicepackage/main.go +++ b/internal/generate/servicepackage/main.go @@ -62,6 +62,7 @@ func main() { v := &visitor{ g: g, + actions: make(map[string]ResourceDatum, 0), ephemeralResources: make(map[string]ResourceDatum, 0), frameworkDataSources: make(map[string]ResourceDatum, 0), frameworkResources: make(map[string]ResourceDatum, 0), @@ -94,6 +95,7 @@ func main() { GoV2Package: l.GoV2Package(), ProviderPackage: p, ProviderNameUpper: l.ProviderNameUpper(), + Actions: v.actions, EphemeralResources: v.ephemeralResources, FrameworkDataSources: v.frameworkDataSources, FrameworkResources: v.frameworkResources, @@ -102,6 +104,9 @@ func main() { } var imports []goImport + for resource := range maps.Values(v.actions) { + imports = append(imports, resource.goImports...) + } for resource := range maps.Values(v.ephemeralResources) { imports = append(imports, resource.goImports...) } @@ -232,6 +237,7 @@ type ServiceDatum struct { GoV2Package string // AWS SDK for Go v2 package name ProviderPackage string ProviderNameUpper string + Actions map[string]ResourceDatum EphemeralResources map[string]ResourceDatum FrameworkDataSources map[string]ResourceDatum FrameworkResources map[string]ResourceDatum @@ -260,6 +266,7 @@ type visitor struct { functionName string packageName string + actions map[string]ResourceDatum ephemeralResources map[string]ResourceDatum frameworkDataSources map[string]ResourceDatum frameworkResources map[string]ResourceDatum @@ -512,6 +519,30 @@ func (v *visitor) processFuncDecl(funcDecl *ast.FuncDecl) { } switch annotationName := m[1]; annotationName { + case "Action": + if len(args.Positional) == 0 { + v.errs = append(v.errs, fmt.Errorf("no type name: %s", fmt.Sprintf("%s.%s", v.packageName, v.functionName))) + continue + } + + typeName := args.Positional[0] + + if !validTypeName.MatchString(typeName) { + v.errs = append(v.errs, fmt.Errorf("invalid type name (%s): %s", typeName, fmt.Sprintf("%s.%s", v.packageName, v.functionName))) + continue + } + + if d.Name == "" { + v.errs = append(v.errs, fmt.Errorf("no friendly name: %s", fmt.Sprintf("%s.%s", v.packageName, v.functionName))) + continue + } + + if _, ok := v.actions[typeName]; ok { + v.errs = append(v.errs, fmt.Errorf("duplicate Action (%s): %s", typeName, fmt.Sprintf("%s.%s", v.packageName, v.functionName))) + } else { + v.actions[typeName] = d + } + case "EphemeralResource": if len(args.Positional) == 0 { v.errs = append(v.errs, fmt.Errorf("no type name: %s", fmt.Sprintf("%s.%s", v.packageName, v.functionName))) diff --git a/internal/generate/servicepackage/service_package_gen.go.gtpl b/internal/generate/servicepackage/service_package_gen.go.gtpl index a7c385529a3b..a1fbb0849a0e 100644 --- a/internal/generate/servicepackage/service_package_gen.go.gtpl +++ b/internal/generate/servicepackage/service_package_gen.go.gtpl @@ -59,6 +59,31 @@ import ( type servicePackage struct {} +{{- if .Actions }} +func (p *servicePackage) Actions(ctx context.Context) []*inttypes.ServicePackageAction { + return []*inttypes.ServicePackageAction { +{{- range $key, $value := .Actions }} + {{- $regionOverrideEnabled := and (not $.IsGlobal) $value.RegionOverrideEnabled }} + { + Factory: {{ $value.FactoryName }}, + TypeName: "{{ $key }}", + Name: "{{ $value.Name }}", + {{- if and $regionOverrideEnabled $value.ValidateRegionOverrideInPartition }} + Region: unique.Make(inttypes.ResourceRegionDefault()), + {{- else if not $regionOverrideEnabled }} + Region: unique.Make(inttypes.ResourceRegionDisabled()), + {{- else }} + Region: unique.Make(inttypes.ServicePackageResourceRegion { + IsOverrideEnabled: {{ $regionOverrideEnabled }}, + IsValidateOverrideInPartition: {{ $value.ValidateRegionOverrideInPartition }}, + }), + {{- end }} + }, +{{- end }} + } +} +{{- end }} + {{- if .EphemeralResources }} func (p *servicePackage) EphemeralResources(ctx context.Context) []*inttypes.ServicePackageEphemeralResource { return []*inttypes.ServicePackageEphemeralResource { diff --git a/internal/provider/framework/intercept.go b/internal/provider/framework/intercept.go index 9d88fb5f6e6b..d70c408d7b38 100644 --- a/internal/provider/framework/intercept.go +++ b/internal/provider/framework/intercept.go @@ -8,6 +8,7 @@ import ( "slices" "github.com/aws/aws-sdk-go-v2/aws" + "github.com/hashicorp/terraform-plugin-framework/action" "github.com/hashicorp/terraform-plugin-framework/datasource" "github.com/hashicorp/terraform-plugin-framework/ephemeral" "github.com/hashicorp/terraform-plugin-framework/resource" @@ -269,9 +270,45 @@ const ( Finally // Interceptor is invoked after After or OnError ) +// An action interceptor is functionality invoked during the action's lifecycle. +// If a Before interceptor returns Diagnostics indicating an error occurred then +// no further interceptors in the chain are run and neither is the schema's method. +// In other cases all interceptors in the chain are run. +type actionInvokeInterceptor interface { + // invoke is invoked for an Invoke call. + invoke(context.Context, interceptorOptions[action.InvokeRequest, action.InvokeResponse]) +} + +// actionInvoke returns a slice of interceptors that run on action Invoke. +func (s interceptorInvocations) actionInvoke() []interceptorFunc[action.InvokeRequest, action.InvokeResponse] { + return tfslices.ApplyToAll(tfslices.Filter(s, func(e any) bool { + _, ok := e.(actionInvokeInterceptor) + return ok + }), func(e any) interceptorFunc[action.InvokeRequest, action.InvokeResponse] { + return e.(actionInvokeInterceptor).invoke + }) +} + +type actionSchemaInterceptor interface { + // schema is invoked for a Schema call. + schema(context.Context, interceptorOptions[action.SchemaRequest, action.SchemaResponse]) +} + +// actionSchema returns a slice of interceptors that run on action Schema. +func (s interceptorInvocations) actionSchema() []interceptorFunc[action.SchemaRequest, action.SchemaResponse] { + return tfslices.ApplyToAll(tfslices.Filter(s, func(e any) bool { + _, ok := e.(actionSchemaInterceptor) + return ok + }), func(e any) interceptorFunc[action.SchemaRequest, action.SchemaResponse] { + return e.(actionSchemaInterceptor).schema + }) +} + // interceptedRequest represents a Plugin Framework request type that can be intercepted. type interceptedRequest interface { - datasource.SchemaRequest | + action.SchemaRequest | + action.InvokeRequest | + datasource.SchemaRequest | datasource.ReadRequest | ephemeral.SchemaRequest | ephemeral.OpenRequest | @@ -288,7 +325,9 @@ type interceptedRequest interface { // interceptedResponse represents a Plugin Framework response type that can be intercepted. type interceptedResponse interface { - datasource.SchemaResponse | + action.SchemaResponse | + action.InvokeResponse | + datasource.SchemaResponse | datasource.ReadResponse | ephemeral.SchemaResponse | ephemeral.OpenResponse | @@ -397,3 +436,11 @@ func resourceModifyPlanHasError(response *resource.ModifyPlanResponse) bool { func resourceImportStateHasError(response *resource.ImportStateResponse) bool { return response.Diagnostics.HasError() } + +func actionSchemaHasError(response *action.SchemaResponse) bool { + return response.Diagnostics.HasError() +} + +func actionInvokeHasError(response *action.InvokeResponse) bool { + return response.Diagnostics.HasError() +} diff --git a/internal/provider/framework/provider.go b/internal/provider/framework/provider.go index 0745fe4329a6..2c70f3bd53da 100644 --- a/internal/provider/framework/provider.go +++ b/internal/provider/framework/provider.go @@ -15,6 +15,8 @@ import ( "unique" "github.com/hashicorp/terraform-plugin-framework-validators/listvalidator" + "github.com/hashicorp/terraform-plugin-framework/action" + aschema "github.com/hashicorp/terraform-plugin-framework/action/schema" "github.com/hashicorp/terraform-plugin-framework/datasource" datasourceschema "github.com/hashicorp/terraform-plugin-framework/datasource/schema" "github.com/hashicorp/terraform-plugin-framework/ephemeral" @@ -42,11 +44,13 @@ var ( var ( _ provider.Provider = &frameworkProvider{} + _ provider.ProviderWithActions = &frameworkProvider{} _ provider.ProviderWithFunctions = &frameworkProvider{} _ provider.ProviderWithEphemeralResources = &frameworkProvider{} ) type frameworkProvider struct { + actions []func() action.Action dataSources []func() datasource.DataSource ephemeralResources []func() ephemeral.EphemeralResource primary interface{ Meta() any } @@ -60,6 +64,7 @@ func NewProvider(ctx context.Context, primary interface{ Meta() any }) (provider log.Printf("Creating Terraform AWS Provider (Framework-style)...") provider := &frameworkProvider{ + actions: make([]func() action.Action, 0), dataSources: make([]func() datasource.DataSource, 0), ephemeralResources: make([]func() ephemeral.EphemeralResource, 0), primary: primary, @@ -344,6 +349,7 @@ func (p *frameworkProvider) Configure(ctx context.Context, request provider.Conf response.DataSourceData = v response.ResourceData = v response.EphemeralResourceData = v + response.ActionData = v } // DataSources returns a slice of functions to instantiate each DataSource @@ -370,6 +376,14 @@ func (p *frameworkProvider) EphemeralResources(ctx context.Context) []func() eph return slices.Clone(p.ephemeralResources) } +// Actions returns a slice of functions to instantiate each Action +// implementation. +// +// All actions must have unique type names. +func (p *frameworkProvider) Actions(ctx context.Context) []func() action.Action { + return slices.Clone(p.actions) +} + // Functions returns a slice of functions to instantiate each Function // implementation. // @@ -409,6 +423,14 @@ func (p *frameworkProvider) initialize(ctx context.Context) { return newWrappedResource(resourceSpec, servicePackageName) }) } + + if v, ok := sp.(conns.ServicePackageWithActions); ok { + for _, actionSpec := range v.Actions(ctx) { + p.actions = append(p.actions, func() action.Action { //nolint:contextcheck // must be a func() + return newWrappedAction(actionSpec, servicePackageName) + }) + } + } } } @@ -460,6 +482,26 @@ func (p *frameworkProvider) validateResourceSchemas(ctx context.Context) error { } } + if v, ok := sp.(conns.ServicePackageWithActions); ok { + for _, actionSpec := range v.Actions(ctx) { + typeName := actionSpec.TypeName + inner, err := actionSpec.Factory(ctx) + + if err != nil { + errs = append(errs, fmt.Errorf("creating action type (%s): %w", typeName, err)) + continue + } + + schemaResponse := action.SchemaResponse{} + inner.Schema(ctx, action.SchemaRequest{}, &schemaResponse) + + if err := validateSchemaRegionForAction(actionSpec.Region, schemaResponse.Schema); err != nil { + errs = append(errs, fmt.Errorf("action type %q: %w", typeName, err)) + continue + } + } + } + for _, resourceSpec := range sp.FrameworkResources(ctx) { typeName := resourceSpec.TypeName inner, err := resourceSpec.Factory(ctx) @@ -519,6 +561,17 @@ func validateSchemaRegionForEphemeralResource(regionSpec unique.Handle[inttypes. return nil } +func validateSchemaRegionForAction(regionSpec unique.Handle[inttypes.ServicePackageResourceRegion], schemaIface any) error { + if !tfunique.IsHandleNil(regionSpec) && regionSpec.Value().IsOverrideEnabled { + if schema, ok := schemaIface.(aschema.Schema); ok { + if _, ok := schema.Attributes[names.AttrRegion]; ok { + return fmt.Errorf("configured for enhanced regions but defines `%s` attribute in schema", names.AttrRegion) + } + } + } + return nil +} + func validateSchemaRegionForResource(regionSpec unique.Handle[inttypes.ServicePackageResourceRegion], schema resourceschema.Schema) error { if !tfunique.IsHandleNil(regionSpec) && regionSpec.Value().IsOverrideEnabled { if _, ok := schema.Attributes[names.AttrRegion]; ok { diff --git a/internal/provider/framework/region.go b/internal/provider/framework/region.go index 9b132ad9a73e..8243c4668777 100644 --- a/internal/provider/framework/region.go +++ b/internal/provider/framework/region.go @@ -7,6 +7,8 @@ import ( "context" "github.com/YakDriver/regexache" + "github.com/hashicorp/terraform-plugin-framework/action" + aschema "github.com/hashicorp/terraform-plugin-framework/action/schema" "github.com/hashicorp/terraform-plugin-framework/datasource" dsschema "github.com/hashicorp/terraform-plugin-framework/datasource/schema" "github.com/hashicorp/terraform-plugin-framework/diag" @@ -364,3 +366,43 @@ func (r resourceImportRegionNoDefaultInterceptor) importState(ctx context.Contex func resourceImportRegionNoDefault() resourceImportStateInterceptor { return &resourceImportRegionNoDefaultInterceptor{} } + +type actionInjectRegionAttributeInterceptor struct{} + +func (a actionInjectRegionAttributeInterceptor) schema(ctx context.Context, opts interceptorOptions[action.SchemaRequest, action.SchemaResponse]) { + switch response, when := opts.response, opts.when; when { + case After: + if _, exists := response.Schema.Attributes[names.AttrRegion]; !exists { + // Inject a top-level "region" attribute. + if response.Schema.Attributes == nil { + response.Schema.Attributes = make(map[string]aschema.Attribute) + } + response.Schema.Attributes[names.AttrRegion] = aschema.StringAttribute{ + Optional: true, + Description: names.TopLevelRegionAttributeDescription, + } + } + } +} + +// actionInjectRegionAttribute injects a top-level "region" attribute into an action's schema. +func actionInjectRegionAttribute() actionSchemaInterceptor { + return &actionInjectRegionAttributeInterceptor{} +} + +type actionValidateRegionInterceptor struct { +} + +func (a actionValidateRegionInterceptor) invoke(ctx context.Context, opts interceptorOptions[action.InvokeRequest, action.InvokeResponse]) { + c := opts.c + + switch when := opts.when; when { + case Before: + opts.response.Diagnostics.Append(validateInContextRegionInPartition(ctx, c)...) + } +} + +// actionValidateRegion validates that the value of the top-level `region` attribute is in the configured AWS partition. +func actionValidateRegion() actionInvokeInterceptor { + return &actionValidateRegionInterceptor{} +} diff --git a/internal/provider/framework/wrap.go b/internal/provider/framework/wrap.go index 19b5c10034ee..acb0de29adcc 100644 --- a/internal/provider/framework/wrap.go +++ b/internal/provider/framework/wrap.go @@ -6,6 +6,7 @@ package framework import ( "context" + "github.com/hashicorp/terraform-plugin-framework/action" "github.com/hashicorp/terraform-plugin-framework/datasource" "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/ephemeral" @@ -354,6 +355,158 @@ func (w *wrappedEphemeralResource) ValidateConfig(ctx context.Context, request e } } +// wrappedAction represents an interceptor dispatcher for a Plugin Framework action. +type wrappedAction struct { + inner action.ActionWithConfigure + meta *conns.AWSClient + servicePackageName string + spec *inttypes.ServicePackageAction + interceptors interceptorInvocations +} + +func newWrappedAction(spec *inttypes.ServicePackageAction, servicePackageName string) action.ActionWithConfigure { + var isRegionOverrideEnabled bool + if regionSpec := spec.Region; !tfunique.IsHandleNil(regionSpec) && regionSpec.Value().IsOverrideEnabled { + isRegionOverrideEnabled = true + } + + var interceptors interceptorInvocations + + if isRegionOverrideEnabled { + v := spec.Region.Value() + + interceptors = append(interceptors, actionInjectRegionAttribute()) + if v.IsValidateOverrideInPartition { + interceptors = append(interceptors, actionValidateRegion()) + } + } + + inner, _ := spec.Factory(context.TODO()) + + return &wrappedAction{ + inner: inner, + servicePackageName: servicePackageName, + spec: spec, + interceptors: interceptors, + } +} + +// context is run on all wrapped methods before any interceptors. +func (w *wrappedAction) context(ctx context.Context, getAttribute getAttributeFunc, c *conns.AWSClient) (context.Context, diag.Diagnostics) { + var diags diag.Diagnostics + var overrideRegion string + + var isRegionOverrideEnabled bool + if regionSpec := w.spec.Region; !tfunique.IsHandleNil(regionSpec) && regionSpec.Value().IsOverrideEnabled { + isRegionOverrideEnabled = true + } + + if isRegionOverrideEnabled && getAttribute != nil { + var target types.String + diags.Append(getAttribute(ctx, path.Root(names.AttrRegion), &target)...) + if diags.HasError() { + return ctx, diags + } + + overrideRegion = target.ValueString() + } + + ctx = conns.NewResourceContext(ctx, w.servicePackageName, w.spec.Name, overrideRegion) + if c != nil { + ctx = c.RegisterLogger(ctx) + ctx = fwflex.RegisterLogger(ctx) + ctx = logging.MaskSensitiveValuesByKey(ctx, logging.HTTPKeyRequestBody, logging.HTTPKeyResponseBody) + } + + return ctx, diags +} + +func (w *wrappedAction) Metadata(ctx context.Context, request action.MetadataRequest, response *action.MetadataResponse) { + // This method does not call down to the inner action. + response.TypeName = w.spec.TypeName +} + +func (w *wrappedAction) Schema(ctx context.Context, request action.SchemaRequest, response *action.SchemaResponse) { + ctx, diags := w.context(ctx, nil, w.meta) + response.Diagnostics.Append(diags...) + if response.Diagnostics.HasError() { + return + } + + f := func(ctx context.Context, request action.SchemaRequest, response *action.SchemaResponse) { + w.inner.Schema(ctx, request, response) + } + interceptedHandler(w.interceptors.actionSchema(), f, actionSchemaHasError, w.meta)(ctx, request, response) + + // Validate the action's model against the schema. + if v, ok := w.inner.(framework.ActionValidateModel); ok { + response.Diagnostics.Append(v.ValidateModel(ctx, &response.Schema)...) + if response.Diagnostics.HasError() { + response.Diagnostics.AddError("action model validation error", w.spec.TypeName) + return + } + } else { + response.Diagnostics.AddError("missing framework.ActionValidateModel", w.spec.TypeName) + } +} + +func (w *wrappedAction) Invoke(ctx context.Context, request action.InvokeRequest, response *action.InvokeResponse) { + ctx, diags := w.context(ctx, request.Config.GetAttribute, w.meta) + response.Diagnostics.Append(diags...) + if response.Diagnostics.HasError() { + return + } + + f := func(ctx context.Context, request action.InvokeRequest, response *action.InvokeResponse) { + w.inner.Invoke(ctx, request, response) + } + interceptedHandler(w.interceptors.actionInvoke(), f, actionInvokeHasError, w.meta)(ctx, request, response) +} + +func (w *wrappedAction) Configure(ctx context.Context, request action.ConfigureRequest, response *action.ConfigureResponse) { + if v, ok := request.ProviderData.(*conns.AWSClient); ok { + w.meta = v + } + + ctx, diags := w.context(ctx, nil, w.meta) + response.Diagnostics.Append(diags...) + if response.Diagnostics.HasError() { + return + } + + w.inner.Configure(ctx, request, response) +} + +func (w *wrappedAction) ConfigValidators(ctx context.Context) []action.ConfigValidator { + if v, ok := w.inner.(action.ActionWithConfigValidators); ok { + ctx, diags := w.context(ctx, nil, w.meta) + if diags.HasError() { + tflog.Warn(ctx, "wrapping ConfigValidators", map[string]any{ + "action": w.spec.TypeName, + "bootstrapContext error": fwdiag.DiagnosticsString(diags), + }) + + return nil + } + + return v.ConfigValidators(ctx) + } + + return nil +} + +func (w *wrappedAction) ValidateConfig(ctx context.Context, request action.ValidateConfigRequest, response *action.ValidateConfigResponse) { + if v, ok := w.inner.(action.ActionWithValidateConfig); ok { + ctx, diags := w.context(ctx, request.Config.GetAttribute, w.meta) + response.Diagnostics.Append(diags...) + if response.Diagnostics.HasError() { + return + } + + v.ValidateConfig(ctx, request, response) + } +} + // wrappedResource represents an interceptor dispatcher for a Plugin Framework resource. type wrappedResource struct { inner resource.ResourceWithConfigure diff --git a/internal/types/service_package.go b/internal/types/service_package.go index 2b1603fbf3d0..d783b86823e7 100644 --- a/internal/types/service_package.go +++ b/internal/types/service_package.go @@ -8,6 +8,7 @@ import ( "slices" "unique" + "github.com/hashicorp/terraform-plugin-framework/action" "github.com/hashicorp/terraform-plugin-framework/datasource" "github.com/hashicorp/terraform-plugin-framework/ephemeral" "github.com/hashicorp/terraform-plugin-framework/resource" @@ -42,6 +43,15 @@ type ServicePackageResourceTags struct { ResourceType string // Extra resourceType parameter value for UpdateTags etc. } +// ServicePackageAction represents a Terraform Plugin Framework action +// implemented by a service package. +type ServicePackageAction struct { + Factory func(context.Context) (action.ActionWithConfigure, error) + TypeName string + Name string + Region unique.Handle[ServicePackageResourceRegion] +} + // ServicePackageEphemeralResource represents a Terraform Plugin Framework ephemeral resource // implemented by a service package. type ServicePackageEphemeralResource struct {