diff --git a/commands.go b/commands.go index ad4bb180d21e..7c732fa71750 100644 --- a/commands.go +++ b/commands.go @@ -428,6 +428,12 @@ func initCommands( }, nil } + Commands["stacks"] = func() (cli.Command, error) { + return &command.StacksCommand{ + Meta: meta, + }, nil + } + // "rpcapi" is handled a bit differently because the whole point of // this interface is to bypass the CLI layer so wrapping automation can // get as-direct-as-possible access to Terraform Core functionality, diff --git a/go.mod b/go.mod index ecd5dc3b4e5f..aa6c39dc9e04 100644 --- a/go.mod +++ b/go.mod @@ -19,7 +19,7 @@ require ( github.com/davecgh/go-spew v1.1.1 github.com/dylanmei/winrmtest v0.0.0-20210303004826-fbc9ae56efb6 github.com/go-test/deep v1.0.3 - github.com/google/go-cmp v0.6.0 + github.com/google/go-cmp v0.7.0 github.com/google/uuid v1.6.0 github.com/hashicorp/cli v1.1.7 github.com/hashicorp/go-checkpoint v0.5.0 @@ -66,31 +66,33 @@ require ( github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940 github.com/zclconf/go-cty-yaml v1.1.0 go.opentelemetry.io/contrib/exporters/autoexport v0.45.0 - go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.46.0 + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.59.0 go.opentelemetry.io/otel v1.34.0 - go.opentelemetry.io/otel/sdk v1.30.0 + go.opentelemetry.io/otel/sdk v1.34.0 go.opentelemetry.io/otel/trace v1.34.0 go.uber.org/mock v0.4.0 - golang.org/x/crypto v0.33.0 + golang.org/x/crypto v0.36.0 golang.org/x/mod v0.23.0 - golang.org/x/net v0.35.0 - golang.org/x/oauth2 v0.27.0 - golang.org/x/sys v0.30.0 - golang.org/x/term v0.29.0 - golang.org/x/text v0.22.0 + golang.org/x/net v0.37.0 + golang.org/x/oauth2 v0.28.0 + golang.org/x/sys v0.31.0 + golang.org/x/term v0.30.0 + golang.org/x/text v0.23.0 golang.org/x/tools v0.30.0 golang.org/x/tools/cmd/cover v0.1.0-deprecated - google.golang.org/grpc v1.59.0 + google.golang.org/grpc v1.71.0 google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.2.0 - google.golang.org/protobuf v1.34.2 + google.golang.org/protobuf v1.36.5 honnef.co/go/tools v0.6.0 ) require ( - cloud.google.com/go v0.110.7 // indirect - cloud.google.com/go/compute/metadata v0.3.0 // indirect - cloud.google.com/go/iam v1.1.1 // indirect - cloud.google.com/go/storage v1.30.1 // indirect + cloud.google.com/go v0.112.2 // indirect + cloud.google.com/go/auth v0.15.0 // indirect + cloud.google.com/go/auth/oauth2adapt v0.2.7 // indirect + cloud.google.com/go/compute/metadata v0.6.0 // indirect + cloud.google.com/go/iam v1.1.6 // indirect + cloud.google.com/go/storage v1.39.1 // indirect github.com/AlecAivazis/survey/v2 v2.3.7 // indirect github.com/Azure/go-autorest v14.2.0+incompatible // indirect github.com/Azure/go-autorest/autorest v0.11.29 // indirect @@ -153,6 +155,7 @@ require ( github.com/dylanmei/iso8601 v0.1.0 // indirect github.com/emicklei/go-restful/v3 v3.8.0 // indirect github.com/fatih/color v1.18.0 // indirect + github.com/felixge/httpsnoop v1.0.4 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect @@ -171,9 +174,9 @@ require ( github.com/google/go-github/v62 v62.0.0 // indirect github.com/google/go-querystring v1.1.0 // indirect github.com/google/gofuzz v1.1.0 // indirect - github.com/google/s2a-go v0.1.4 // indirect - github.com/googleapis/enterprise-certificate-proxy v0.2.3 // indirect - github.com/googleapis/gax-go/v2 v2.11.0 // indirect + github.com/google/s2a-go v0.1.9 // indirect + github.com/googleapis/enterprise-certificate-proxy v0.3.5 // indirect + github.com/googleapis/gax-go/v2 v2.14.1 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 // indirect github.com/hashicorp/aws-sdk-go-base/v2 v2.0.0-beta.62 // indirect github.com/hashicorp/consul/api v1.13.0 // indirect @@ -247,20 +250,20 @@ require ( go.opencensus.io v0.24.0 // indirect go.opentelemetry.io/auto/sdk v1.1.0 // indirect go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws v0.59.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.19.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0 // indirect go.opentelemetry.io/otel/metric v1.34.0 // indirect go.opentelemetry.io/proto/otlp v1.0.0 // indirect golang.org/x/exp/typeparams v0.0.0-20231108232855-2478ac86f678 // indirect - golang.org/x/sync v0.11.0 // indirect - golang.org/x/time v0.9.0 // indirect - golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect - google.golang.org/api v0.126.0 // indirect + golang.org/x/sync v0.12.0 // indirect + golang.org/x/time v0.11.0 // indirect + google.golang.org/api v0.226.0 // indirect google.golang.org/appengine v1.6.8 // indirect - google.golang.org/genproto v0.0.0-20230822172742-b8732ec3820d // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20230822172742-b8732ec3820d // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d // indirect + google.golang.org/genproto v0.0.0-20240213162025-012b6fc9bca9 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20250106144421-5f5ef82da422 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250313205543-e70fdf4c4cb4 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/ini.v1 v1.66.2 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect diff --git a/go.sum b/go.sum index 6d083c886be2..a2c0684f95f9 100644 --- a/go.sum +++ b/go.sum @@ -36,8 +36,8 @@ cloud.google.com/go v0.104.0/go.mod h1:OO6xxXdJyvuJPcEPBLN9BJPD+jep5G1+2U5B5gkRY cloud.google.com/go v0.105.0/go.mod h1:PrLgOJNe5nfE9UMxKxgXj4mD3voiP+YQ6gdt6KMFOKM= cloud.google.com/go v0.107.0/go.mod h1:wpc2eNrD7hXUTy8EKS10jkxpZBjASrORK7goS+3YX2I= cloud.google.com/go v0.110.0/go.mod h1:SJnCLqQ0FCFGSZMUNUf84MV3Aia54kn7pi8st7tMzaY= -cloud.google.com/go v0.110.7 h1:rJyC7nWRg2jWGZ4wSJ5nY65GTdYJkg0cd/uXb+ACI6o= -cloud.google.com/go v0.110.7/go.mod h1:+EYjdK8e5RME/VY/qLCAtuyALQ9q67dvuum8i+H5xsI= +cloud.google.com/go v0.112.2 h1:ZaGT6LiG7dBzi6zNOvVZwacaXlmf3lRqnC4DQzqyRQw= +cloud.google.com/go v0.112.2/go.mod h1:iEqjp//KquGIJV/m+Pk3xecgKNhV+ry+vVTsy4TbDms= cloud.google.com/go/accessapproval v1.4.0/go.mod h1:zybIuC3KpDOvotz59lFe5qxRZx6C75OtwbisN56xYB4= cloud.google.com/go/accessapproval v1.5.0/go.mod h1:HFy3tuiGvMdcd/u+Cu5b9NkO1pEICJ46IR82PoUdplw= cloud.google.com/go/accessapproval v1.6.0/go.mod h1:R0EiYnwV5fsRFiKZkPHr6mwyk2wxUJ30nL4j2pcFY2E= @@ -99,6 +99,10 @@ cloud.google.com/go/assuredworkloads v1.7.0/go.mod h1:z/736/oNmtGAyU47reJgGN+KVo cloud.google.com/go/assuredworkloads v1.8.0/go.mod h1:AsX2cqyNCOvEQC8RMPnoc0yEarXQk6WEKkxYfL6kGIo= cloud.google.com/go/assuredworkloads v1.9.0/go.mod h1:kFuI1P78bplYtT77Tb1hi0FMxM0vVpRC7VVoJC3ZoT0= cloud.google.com/go/assuredworkloads v1.10.0/go.mod h1:kwdUQuXcedVdsIaKgKTp9t0UJkE5+PAVNhdQm4ZVq2E= +cloud.google.com/go/auth v0.15.0 h1:Ly0u4aA5vG/fsSsxu98qCQBemXtAtJf+95z9HK+cxps= +cloud.google.com/go/auth v0.15.0/go.mod h1:WJDGqZ1o9E9wKIL+IwStfyn/+s59zl4Bi+1KQNVXLZ8= +cloud.google.com/go/auth/oauth2adapt v0.2.7 h1:/Lc7xODdqcEw8IrZ9SvwnlLX6j9FHQM74z6cBk9Rw6M= +cloud.google.com/go/auth/oauth2adapt v0.2.7/go.mod h1:NTbTTzfvPl1Y3V1nPpOgl2w6d/FjO7NNUQaWSox6ZMc= cloud.google.com/go/automl v1.5.0/go.mod h1:34EjfoFGMZ5sgJ9EoLsRtdPSNZLcfflJR39VbVNS2M0= cloud.google.com/go/automl v1.6.0/go.mod h1:ugf8a6Fx+zP0D59WLhqgTDsQI9w07o64uf/Is3Nh5p8= cloud.google.com/go/automl v1.7.0/go.mod h1:RL9MYCCsJEOmt0Wf3z9uzG0a7adTT1fe+aObgSpkCt8= @@ -178,8 +182,8 @@ cloud.google.com/go/compute/metadata v0.1.0/go.mod h1:Z1VN+bulIf6bt4P/C37K4DyZYZ cloud.google.com/go/compute/metadata v0.2.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k= cloud.google.com/go/compute/metadata v0.2.1/go.mod h1:jgHgmJd2RKBGzXqF5LR2EZMGxBkeanZ9wwa75XHJgOM= cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= -cloud.google.com/go/compute/metadata v0.3.0 h1:Tz+eQXMEqDIKRsmY3cHTL6FVaynIjX2QxYC4trgAKZc= -cloud.google.com/go/compute/metadata v0.3.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k= +cloud.google.com/go/compute/metadata v0.6.0 h1:A6hENjEsCDtC1k8byVsgwvVcioamEHvZ4j01OwKxG9I= +cloud.google.com/go/compute/metadata v0.6.0/go.mod h1:FjyFAW1MW0C203CEOMDTu3Dk1FlqW3Rga40jzHL4hfg= cloud.google.com/go/contactcenterinsights v1.3.0/go.mod h1:Eu2oemoePuEFc/xKFPjbTuPSj0fYJcPls9TFlPNnHHY= cloud.google.com/go/contactcenterinsights v1.4.0/go.mod h1:L2YzkGbPsv+vMQMCADxJoT9YiTTnSEd6fEvCeHTYVck= cloud.google.com/go/contactcenterinsights v1.6.0/go.mod h1:IIDlT6CLcDoyv79kDv8iWxMSTZhLxSCofVV5W6YFM/w= @@ -313,8 +317,8 @@ cloud.google.com/go/iam v0.8.0/go.mod h1:lga0/y3iH6CX7sYqypWJ33hf7kkfXJag67naqGE cloud.google.com/go/iam v0.11.0/go.mod h1:9PiLDanza5D+oWFZiH1uG+RnRCfEGKoyl6yo4cgWZGY= cloud.google.com/go/iam v0.12.0/go.mod h1:knyHGviacl11zrtZUoDuYpDgLjvr28sLQaG0YB2GYAY= cloud.google.com/go/iam v0.13.0/go.mod h1:ljOg+rcNfzZ5d6f1nAUJ8ZIxOaZUVoS14bKCtaLZ/D0= -cloud.google.com/go/iam v1.1.1 h1:lW7fzj15aVIXYHREOqjRBV9PsH0Z6u8Y46a1YGvQP4Y= -cloud.google.com/go/iam v1.1.1/go.mod h1:A5avdyVL2tCppe4unb0951eI9jreack+RJ0/d+KUZOU= +cloud.google.com/go/iam v1.1.6 h1:bEa06k05IO4f4uJonbB5iAgKTPpABy1ayxaIZV/GHVc= +cloud.google.com/go/iam v1.1.6/go.mod h1:O0zxdPeGBoFdWW3HWmBxJsk0pfvNM/p/qa82rWOGTwI= cloud.google.com/go/iap v1.4.0/go.mod h1:RGFwRJdihTINIe4wZ2iCP0zF/qu18ZwyKxrhMhygBEc= cloud.google.com/go/iap v1.5.0/go.mod h1:UH/CGgKd4KyohZL5Pt0jSKE4m3FR51qg6FKQ/z/Ix9A= cloud.google.com/go/iap v1.6.0/go.mod h1:NSuvI9C/j7UdjGjIde7t7HBz+QTwBcapPE07+sSRcLk= @@ -334,8 +338,8 @@ cloud.google.com/go/kms v1.8.0/go.mod h1:4xFEhYFqvW+4VMELtZyxomGSYtSQKzM178ylFW4 cloud.google.com/go/kms v1.9.0/go.mod h1:qb1tPTgfF9RQP8e1wq4cLFErVuTJv7UsSC915J8dh3w= cloud.google.com/go/kms v1.10.0/go.mod h1:ng3KTUtQQU9bPX3+QGLsflZIHlkbn8amFAMY63m8d24= cloud.google.com/go/kms v1.10.1/go.mod h1:rIWk/TryCkR59GMC3YtHtXeLzd634lBbKenvyySAyYI= -cloud.google.com/go/kms v1.15.0 h1:xYl5WEaSekKYN5gGRyhjvZKM22GVBBCzegGNVPy+aIs= -cloud.google.com/go/kms v1.15.0/go.mod h1:c9J991h5DTl+kg7gi3MYomh12YEENGrf48ee/N/2CDM= +cloud.google.com/go/kms v1.15.7 h1:7caV9K3yIxvlQPAcaFffhlT7d1qpxjB1wHBtjWa13SM= +cloud.google.com/go/kms v1.15.7/go.mod h1:ub54lbsa6tDkUwnu4W7Yt1aAIFLnspgh0kPGToDukeI= cloud.google.com/go/language v1.4.0/go.mod h1:F9dRpNFQmJbkaop6g0JhSBXCNlO90e1KWx5iDdxbWic= cloud.google.com/go/language v1.6.0/go.mod h1:6dJ8t3B+lUYfStgls25GusK04NLh3eDLQnWM3mdEbhI= cloud.google.com/go/language v1.7.0/go.mod h1:DJ6dYN/W+SQOjF8e1hLQXMF21AkH2w9wiPzPCJa2MIE= @@ -535,8 +539,8 @@ cloud.google.com/go/storage v1.23.0/go.mod h1:vOEEDNFnciUMhBeT6hsJIn3ieU5cFRmzeL cloud.google.com/go/storage v1.27.0/go.mod h1:x9DOL8TK/ygDUMieqwfhdpQryTeEkhGKMi80i/iqR2s= cloud.google.com/go/storage v1.28.1/go.mod h1:Qnisd4CqDdo6BGs2AD5LLnEsmSQ80wQ5ogcBBKhU86Y= cloud.google.com/go/storage v1.29.0/go.mod h1:4puEjyTKnku6gfKoTfNOU/W+a9JyuVNxjpS5GBrB8h4= -cloud.google.com/go/storage v1.30.1 h1:uOdMxAs8HExqBlnLtnQyP0YkvbiDpdGShGKtx6U/oNM= -cloud.google.com/go/storage v1.30.1/go.mod h1:NfxhC0UJE1aXSx7CIIbCf7y9HKT7BiccwkR7+P7gN8E= +cloud.google.com/go/storage v1.39.1 h1:MvraqHKhogCOTXTlct/9C3K3+Uy2jBmFYb3/Sp6dVtY= +cloud.google.com/go/storage v1.39.1/go.mod h1:xK6xZmxZmo+fyP7+DEF6FhNc24/JAe95OLyOHCXFH1o= cloud.google.com/go/storagetransfer v1.5.0/go.mod h1:dxNzUopWy7RQevYFHewchb29POFv3/AaBgnhqzqiK0w= cloud.google.com/go/storagetransfer v1.6.0/go.mod h1:y77xm4CQV/ZhFZH75PLEXY0ROiS7Gh6pSKrM8dJyg6I= cloud.google.com/go/storagetransfer v1.7.0/go.mod h1:8Giuj1QNb1kfLAiWM1bN6dHzfdlDAVC9rv9abHot2W4= @@ -818,7 +822,6 @@ github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWH github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20220314180256-7f1daf1720fc/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20230105202645-06c439db220b/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4 h1:/inchEIKaYC1Akx+H+gqO04wryn5h75LSazbRlnya1k= github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= @@ -852,14 +855,14 @@ github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7 github.com/envoyproxy/protoc-gen-validate v0.6.7/go.mod h1:dyJXwwfPK2VSqiB9Klm1J6romD608Ba7Hij42vrOBCo= github.com/envoyproxy/protoc-gen-validate v0.9.1/go.mod h1:OKNgG7TCp5pF4d6XftA0++PMirau2/yoOwVac3AbF2w= github.com/envoyproxy/protoc-gen-validate v0.10.1/go.mod h1:DRjgyB0I43LtJapqN6NiRwroiAU2PaFuvk/vjgh61ss= -github.com/envoyproxy/protoc-gen-validate v1.0.2 h1:QkIBuU5k+x7/QXPvPPnWXWlCdaBFApVqftFV6k087DA= -github.com/envoyproxy/protoc-gen-validate v1.0.2/go.mod h1:GpiZQP3dDbg4JouG/NNS7QWXpgx6x8QiMKdmN72jogE= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= +github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= +github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE= @@ -928,8 +931,8 @@ github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGw github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4= github.com/golang/glog v1.1.0/go.mod h1:pfYeQZ3JWZoXTV5sFc986z3HTpwQs9At6P4ImfuP3NQ= -github.com/golang/glog v1.1.2 h1:DVjP2PbBOzHyzA+dn3WhHIq4NdVu3Q+pvivFICf/7fo= -github.com/golang/glog v1.1.2/go.mod h1:zR+okUeTbrL6EL3xHUDxZuEtGv04p5shwip1+mL/rLQ= +github.com/golang/glog v1.2.4 h1:CNNw5U8lSiiBk7druxtSHHTsRWcxKoac6kZKm2peBBc= +github.com/golang/glog v1.2.4/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -990,8 +993,9 @@ github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/go-github/v45 v45.2.0 h1:5oRLszbrkvxDDqBCNj2hjDZMKmvexaZ1xw/FCD+K3FI= github.com/google/go-github/v45 v45.2.0/go.mod h1:FObaZJEDSTa/WGCzZ2Z3eoCDXWJKMenWWTrd8jrta28= github.com/google/go-github/v62 v62.0.0 h1:/6mGCaRywZz9MuHyw9gD1CwsbmBX8GWsbFkwMmHdhl4= @@ -1025,8 +1029,8 @@ github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLe github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= -github.com/google/s2a-go v0.1.4 h1:1kZ/sQM3srePvKs3tXAvQzo66XfcReoqFpIpIccE7Oc= -github.com/google/s2a-go v0.1.4/go.mod h1:Ej+mSEMGRnqRzjc7VtF+jdBwYG5fuJfiZ8ELkjEwM0A= +github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0= +github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= @@ -1036,8 +1040,9 @@ github.com/googleapis/enterprise-certificate-proxy v0.0.0-20220520183353-fd19c99 github.com/googleapis/enterprise-certificate-proxy v0.1.0/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8= github.com/googleapis/enterprise-certificate-proxy v0.2.0/go.mod h1:8C0jb7/mgJe/9KK8Lm7X9ctZC2t60YyIpYEI16jx0Qg= github.com/googleapis/enterprise-certificate-proxy v0.2.1/go.mod h1:AwSRAtLfXpU5Nm3pW+v7rGDHp09LsPtGY9MduiEsR9k= -github.com/googleapis/enterprise-certificate-proxy v0.2.3 h1:yk9/cqRKtT9wXZSsRH9aurXEpJX+U6FLtpYTdC3R06k= github.com/googleapis/enterprise-certificate-proxy v0.2.3/go.mod h1:AwSRAtLfXpU5Nm3pW+v7rGDHp09LsPtGY9MduiEsR9k= +github.com/googleapis/enterprise-certificate-proxy v0.3.5 h1:VgzTY2jogw3xt39CusEnFJWm7rlsq5yL5q9XdLOuP5g= +github.com/googleapis/enterprise-certificate-proxy v0.3.5/go.mod h1:MkHOF77EYAE7qfSuSS9PU6g4Nt4e11cnsDUowfwewLA= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0= @@ -1049,8 +1054,8 @@ github.com/googleapis/gax-go/v2 v2.5.1/go.mod h1:h6B0KMMFNtI2ddbGJn3T3ZbwkeT6yqE github.com/googleapis/gax-go/v2 v2.6.0/go.mod h1:1mjbznJAPHFpesgE5ucqfYEscaz5kMdcIDwU/6+DDoY= github.com/googleapis/gax-go/v2 v2.7.0/go.mod h1:TEop28CZZQ2y+c0VxMUmu1lV+fQx57QpBWsYpwqHJx8= github.com/googleapis/gax-go/v2 v2.7.1/go.mod h1:4orTrqY6hXxxaUL4LHIPl6lGo8vAE38/qKbhSAKP6QI= -github.com/googleapis/gax-go/v2 v2.11.0 h1:9V9PWXEsWnPpQhu/PeQIkS4eGzMlTLGgt80cUUI8Ki4= -github.com/googleapis/gax-go/v2 v2.11.0/go.mod h1:DxmR61SGKkGLa2xigwuZIQpkCI2S5iydzRfb3peWZJI= +github.com/googleapis/gax-go/v2 v2.14.1 h1:hb0FFeiPaQskmvakKu5EbCbpntQn48jyHuvrkurSS/Q= +github.com/googleapis/gax-go/v2 v2.14.1/go.mod h1:Hb/NubMaVM88SrNkvl8X/o8XWwDJEPqouaLeN2IUxoA= github.com/googleapis/go-type-adapters v1.0.0/go.mod h1:zHW75FOG2aur7gAO2B+MLby+cLsWGBF62rFAi7WjWO4= github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= @@ -1514,8 +1519,10 @@ go.opentelemetry.io/contrib/exporters/autoexport v0.45.0 h1:KU3hwb3O+fc2F15lltmD go.opentelemetry.io/contrib/exporters/autoexport v0.45.0/go.mod h1:9hFI4YY6Ehe9enzw9qGlKAjJGQAtEo75Ysrb3byOZtI= go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws v0.59.0 h1:bFkfHqO3IoO0VlUAuFxUhf5zctq/OD8H0wq77hxoeN4= go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws v0.59.0/go.mod h1:2Wj/UyCzrPIweApqPFgXXRNZrpoz/sbU8UxeM6Dby3Q= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.46.0 h1:PzIubN4/sjByhDRHLviCjJuweBXWFZWhghjg7cS28+M= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.46.0/go.mod h1:Ct6zzQEuGK3WpJs2n4dn+wfJYzd/+hNnxMRTWjGn30M= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.59.0 h1:rgMkmiGfix9vFJDcDi1PK8WEQP4FLQwLDfhp5ZLpFeE= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.59.0/go.mod h1:ijPqXp5P6IRRByFVVg9DY8P5HkxkHE5ARIa+86aXPf4= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0 h1:CV7UdSGJt/Ao6Gp4CXckLxVRRsRgDHoI8XjbL3PDl8s= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0/go.mod h1:FRmFuRJfag1IZ2dPkHnEoSFVgTVPUd2qf5Vi69hLb8I= go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY= go.opentelemetry.io/otel v1.34.0/go.mod h1:OWFPOQ+h4G8xpyjgqo4SxJYdDQ/qmRH+wivy7zzx9oI= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0 h1:Mne5On7VWdx7omSrSSZvM4Kw7cS7NQkOOmLcgscI51U= @@ -1528,8 +1535,10 @@ go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.19.0 h1:Nw7Dv4lwvGrI68+ go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.19.0/go.mod h1:1MsF6Y7gTqosgoZvHlzcaaM8DIMNZgJh87ykokoNH7Y= go.opentelemetry.io/otel/metric v1.34.0 h1:+eTR3U0MyfWjRDhmFMxe2SsW64QrZ84AOhvqS7Y+PoQ= go.opentelemetry.io/otel/metric v1.34.0/go.mod h1:CEDrp0fy2D0MvkXE+dPV7cMi8tWZwX3dmaIhwPOaqHE= -go.opentelemetry.io/otel/sdk v1.30.0 h1:cHdik6irO49R5IysVhdn8oaiR9m8XluDaJAs4DfOrYE= -go.opentelemetry.io/otel/sdk v1.30.0/go.mod h1:p14X4Ok8S+sygzblytT1nqG98QG2KYKv++HE0LY/mhg= +go.opentelemetry.io/otel/sdk v1.34.0 h1:95zS4k/2GOy069d321O8jWgYsW3MzVV+KuSPKp7Wr1A= +go.opentelemetry.io/otel/sdk v1.34.0/go.mod h1:0e/pNiaMAqaykJGKbi+tSjWfNNHMTxoC9qANsCzbyxU= +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/trace v1.34.0 h1:+ouXS2V8Rd4hp4580a8q23bg0azF2nI8cqLYnC8mh/k= go.opentelemetry.io/otel/trace v1.34.0/go.mod h1:Svm7lSjQD7kG7KJ/MUHPVXSDGz2OX4h0M2jHBhmSfRE= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= @@ -1556,7 +1565,6 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.0.0-20220314234659-1baeb1ce4c0b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= @@ -1567,8 +1575,8 @@ golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliY golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= -golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus= -golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M= +golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= +golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -1702,8 +1710,8 @@ golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= -golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8= -golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk= +golang.org/x/net v0.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c= +golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -1733,8 +1741,8 @@ golang.org/x/oauth2 v0.4.0/go.mod h1:RznEsdpjGAINPTOF0UH/t+xJ75L18YO3Ho6Pyn+uRec golang.org/x/oauth2 v0.5.0/go.mod h1:9/XBHVqLaWO3/BRHs5jbpYCnOZVjj5V0ndyaAM7KB4I= golang.org/x/oauth2 v0.6.0/go.mod h1:ycmewcwgD4Rpr3eZJLSB4Kyyljb3qDh40vJ8STE5HKw= golang.org/x/oauth2 v0.7.0/go.mod h1:hPLQkd9LyjfXTiRohC/41GhcFqxisoUQ99sCUOHO9x4= -golang.org/x/oauth2 v0.27.0 h1:da9Vo7/tDv5RH/7nZDz1eMGS/q1Vv1N/7FCrBhI9I3M= -golang.org/x/oauth2 v0.27.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8= +golang.org/x/oauth2 v0.28.0 h1:CrgCKl8PPAVtLnU3c+EDw6x11699EWlsDeWNWKdIOkc= +golang.org/x/oauth2 v0.28.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -1755,8 +1763,8 @@ golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w= -golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw= +golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -1863,8 +1871,8 @@ golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= -golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= +golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -1879,8 +1887,8 @@ golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek= -golang.org/x/term v0.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU= -golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s= +golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y= +golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -1902,16 +1910,16 @@ golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= -golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= -golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= +golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= +golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20220922220347-f3bd1da661af/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.1.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY= -golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0= +golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -1989,8 +1997,9 @@ golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= -golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk= golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= +golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 h1:+cNy6SZtPcJQH3LJVLOSmiC7MMxXNOb3PU/VUEz+EhU= +golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo= gonum.org/v1/gonum v0.8.2/go.mod h1:oe/vMfY3deqTw+1EZJhuvEW2iwGF1bW9wwu7XCu0+v0= gonum.org/v1/gonum v0.9.3/go.mod h1:TZumC3NeyVQskjXqmyWt4S3bINhy7B4eYwW69EbyX+0= @@ -2056,8 +2065,8 @@ google.golang.org/api v0.108.0/go.mod h1:2Ts0XTHNVWxypznxWOYUeI4g3WdP9Pk2Qk58+a/ google.golang.org/api v0.110.0/go.mod h1:7FC4Vvx1Mooxh8C5HWjzZHcavuS2f6pmJpZx60ca7iI= google.golang.org/api v0.111.0/go.mod h1:qtFHvU9mhgTJegR31csQ+rwxyUTHOKFqCKWp1J0fdw0= google.golang.org/api v0.114.0/go.mod h1:ifYI2ZsFK6/uGddGfAD5BMxlnkBqCmqHSDUVi45N5Yg= -google.golang.org/api v0.126.0 h1:q4GJq+cAdMAC7XP7njvQ4tvohGLiSlytuL4BQxbIZ+o= -google.golang.org/api v0.126.0/go.mod h1:mBwVAtz+87bEN6CbA1GtZPDOqY2R5ONPqJeIlvyo4Aw= +google.golang.org/api v0.226.0 h1:9A29y1XUD+YRXfnHkO66KggxHBZWg9LsTGqm7TkUvtQ= +google.golang.org/api v0.226.0/go.mod h1:WP/0Xm4LVvMOCldfvOISnWquSRWbG2kArDZcg+W2DbY= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= @@ -2201,12 +2210,12 @@ google.golang.org/genproto v0.0.0-20230323212658-478b75c54725/go.mod h1:UUQDJDOl google.golang.org/genproto v0.0.0-20230330154414-c0448cd141ea/go.mod h1:UUQDJDOlWu4KYeJZffbWgBkS1YFobzKbLVfK69pe0Ak= google.golang.org/genproto v0.0.0-20230331144136-dcfb400f0633/go.mod h1:UUQDJDOlWu4KYeJZffbWgBkS1YFobzKbLVfK69pe0Ak= google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1/go.mod h1:nKE/iIaLqn2bQwXBg8f1g2Ylh6r5MN5CmZvuzZCgsCU= -google.golang.org/genproto v0.0.0-20230822172742-b8732ec3820d h1:VBu5YqKPv6XiJ199exd8Br+Aetz+o08F+PLMnwJQHAY= -google.golang.org/genproto v0.0.0-20230822172742-b8732ec3820d/go.mod h1:yZTlhN0tQnXo3h00fuXNCxJdLdIdnVFVBaRJ5LWBbw4= -google.golang.org/genproto/googleapis/api v0.0.0-20230822172742-b8732ec3820d h1:DoPTO70H+bcDXcd39vOqb2viZxgqeBeSGtZ55yZU4/Q= -google.golang.org/genproto/googleapis/api v0.0.0-20230822172742-b8732ec3820d/go.mod h1:KjSP20unUpOx5kyQUFa7k4OJg0qeJ7DEZflGDu2p6Bk= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d h1:uvYuEyMHKNt+lT4K3bN6fGswmK8qSvcreM3BwjDh+y4= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d/go.mod h1:+Bk1OCOj40wS2hwAMA+aCW9ypzm63QTBBHp6lQ3p+9M= +google.golang.org/genproto v0.0.0-20240213162025-012b6fc9bca9 h1:9+tzLLstTlPTRyJTh+ah5wIMsBW5c4tQwGTN3thOW9Y= +google.golang.org/genproto v0.0.0-20240213162025-012b6fc9bca9/go.mod h1:mqHbVIp48Muh7Ywss/AD6I5kNVKZMmAa/QEW58Gxp2s= +google.golang.org/genproto/googleapis/api v0.0.0-20250106144421-5f5ef82da422 h1:GVIKPyP/kLIyVOgOnTwFOrvQaQUzOzGMCxgFUOEmm24= +google.golang.org/genproto/googleapis/api v0.0.0-20250106144421-5f5ef82da422/go.mod h1:b6h1vNKhxaSoEI+5jc3PJUCustfli/mRab7295pY7rw= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250313205543-e70fdf4c4cb4 h1:iK2jbkWL86DXjEx0qiHcRE9dE4/Ahua5k6V8OWFb//c= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250313205543-e70fdf4c4cb4/go.mod h1:LuRYeWDFV6WOn90g357N17oMCaxpgCnbi/44qJvDn2I= google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= @@ -2250,8 +2259,8 @@ google.golang.org/grpc v1.52.3/go.mod h1:pu6fVzoFb+NBYNAvQL08ic+lvB2IojljRYuun5v google.golang.org/grpc v1.53.0/go.mod h1:OnIrk0ipVdj4N5d9IUoFUx72/VlD7+jUsHwZgwSMQpw= google.golang.org/grpc v1.54.0/go.mod h1:PUSEXI6iWghWaB6lXM4knEgpJNu2qUcKfDtNci3EC2g= google.golang.org/grpc v1.56.3/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s= -google.golang.org/grpc v1.59.0 h1:Z5Iec2pjwb+LEOqzpB2MR12/eKFhDPhuqW91O+4bwUk= -google.golang.org/grpc v1.59.0/go.mod h1:aUPDwccQo6OTjy7Hct4AfBPD1GptF4fyUjIkQ9YtF98= +google.golang.org/grpc v1.71.0 h1:kF77BGdPTQ4/JZWMlb9VpJ5pa25aqvVqogsxNHHdeBg= +google.golang.org/grpc v1.71.0/go.mod h1:H0GRtasmQOh9LkFoCPDu3ZrwUtD1YGE+b2vYBYd/8Ec= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.2.0 h1:TLkBREm4nIsEcexnCjgQd5GQWaHcqMzwQV0TX9pq8S0= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.2.0/go.mod h1:DNq5QpG7LJqD2AamLZ7zvKE0DEpVl2BSEVjFycAAjRY= @@ -2273,8 +2282,8 @@ google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqw google.golang.org/protobuf v1.29.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= -google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= -google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= +google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM= +google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d/go.mod h1:cuepJuh7vyXfUyUwEgHQXw849cJrilpS5NeIjOWESAw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/internal/command/stacks.go b/internal/command/stacks.go new file mode 100644 index 000000000000..0e325bc6335b --- /dev/null +++ b/internal/command/stacks.go @@ -0,0 +1,264 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: BUSL-1.1 + +package command + +import ( + "bytes" + "fmt" + "io" + "log" + "net/url" + "os" + "os/exec" + "path" + "runtime" + + "google.golang.org/grpc/metadata" + + "github.com/hashicorp/go-plugin" + "github.com/hashicorp/terraform/internal/logging" + "github.com/hashicorp/terraform/internal/stacksplugin" + "github.com/hashicorp/terraform/internal/stacksplugin/stacksplugin1" + "github.com/hashicorp/terraform/internal/tfdiags" +) + +// CloudCommand is a Command implementation that interacts with Terraform +// Cloud for operations that are inherently planless. It delegates +// all execution to an internal plugin. +type StacksCommand struct { + Meta + // Path to the plugin server executable + pluginBinary string + // Service URL we can download plugin release binaries from + pluginService *url.URL + // Everything the plugin needs to build a client and Do Things + pluginConfig StacksPluginConfig +} + +const ( + // DefaultCloudPluginVersion is the implied protocol version, though all + // historical versions are defined explicitly. + DefaultStacksPluginVersion = 1 + + // ExitRPCError is the exit code that is returned if an plugin + // communication error occurred. + ExitStacksRPCError = 99 + + // ExitPluginError is the exit code that is returned if the plugin + // cannot be downloaded. + ExitStacksPluginError = 98 + + // The regular HCP Terraform API service that the go-tfe client relies on. + tfeStacksServiceID = "tfe.v2" + // The cloud plugin release download service that the BinaryManager relies + // on to fetch the plugin. + stackspluginServiceID = "stacksplugin.v1" +) + +var ( + // Handshake is used to verify that the plugin is the appropriate plugin for + // the client. This is not a security verification. + StacksHandshake = plugin.HandshakeConfig{ + MagicCookieKey: "TF_STACKS_MAGIC_COOKIE", + MagicCookieValue: "c9183f264a1db49ef2cbcc7b74f508a7bba9c3704c47cde3d130ae7f3b7a59c8f97a1e43d9e17ec0ac43a57fd250f373b2a8d991431d9fb1ea7bc48c8e7696fd", + ProtocolVersion: DefaultStacksPluginVersion, + } + // CloudPluginDataDir is the name of the directory within the data directory + StacksPluginDataDir = "stacksplugin" +) + +func (c *StacksCommand) realRun(args []string, stdout, stderr io.Writer) int { + args = c.Meta.process(args) + + diags := c.initPlugin() + if diags.HasWarnings() || diags.HasErrors() { + c.View.Diagnostics(diags) + } + if diags.HasErrors() { + return ExitPluginError + } + + client := plugin.NewClient(&plugin.ClientConfig{ + HandshakeConfig: StacksHandshake, + AllowedProtocols: []plugin.Protocol{plugin.ProtocolGRPC}, + Cmd: exec.Command(c.pluginBinary), + Logger: logging.NewCloudLogger(), + VersionedPlugins: map[int]plugin.PluginSet{ + 1: { + "stacks": &stacksplugin1.GRPCStacksPlugin{ + Metadata: c.pluginConfig.ToMetadata(), + Services: c.Meta.Services, + }, + }, + }, + }) + defer client.Kill() + + // Connect via RPC + rpcClient, err := client.Client() + if err != nil { + fmt.Fprintf(stderr, "Failed to create cloud plugin client: %s", err) + return ExitRPCError + } + + // Request the plugin + raw, err := rpcClient.Dispense("stacks") + if err != nil { + fmt.Fprintf(stderr, "Failed to request cloud plugin interface: %s", err) + return ExitRPCError + } + + // Proxy the request + // Note: future changes will need to determine the type of raw when + // multiple versions are possible. + stacks1, ok := raw.(stacksplugin.Stacks1) + if !ok { + c.Ui.Error("If more than one cloudplugin versions are available, they need to be added to the cloud command. This is a bug in Terraform.") + return ExitRPCError + } + + return stacks1.Execute(args, stdout, stderr) +} + +// discoverAndConfigure is an implementation detail of initPlugin. It fills in the +// pluginService and pluginConfig fields on a CloudCommand struct. +func (c *StacksCommand) discoverAndConfigure() tfdiags.Diagnostics { + var diags tfdiags.Diagnostics + + tfBinaryPath, err := os.Executable() + if err != nil { + return diags.Append(tfdiags.Sourceless( + tfdiags.Error, + "Terraform binary path not found", + "Terraform binary path not found: "+err.Error(), + )) + } + // just a dummy value to avoid nil pointer dereference, otherwise are just testing the dev plugin override + c.pluginService = &url.URL{ + Scheme: "https", + Host: "api.stacks.hashicorp.com", + } + + // Now just steal everything we need so we can pass it to the plugin later. + c.pluginConfig = StacksPluginConfig{ + TerraformBinaryPath: tfBinaryPath, + } + + return diags +} + +func (c *StacksCommand) initPlugin() tfdiags.Diagnostics { + var diags tfdiags.Diagnostics + var errorSummary = "Cloud plugin initialization error" + + // Initialization can be aborted by interruption signals + ctx, done := c.InterruptibleContext(c.CommandContext()) + defer done() + + // Discover service URLs, and build out the plugin config + diags = diags.Append(c.discoverAndConfigure()) + if diags.HasErrors() { + return diags + } + + packagesPath, err := c.initPackagesCache() + if err != nil { + return diags.Append(tfdiags.Sourceless(tfdiags.Error, errorSummary, err.Error())) + } + + overridePath := os.Getenv("TF_STACKS_PLUGIN_DEV_OVERRIDE") + + bm, err := stacksplugin.NewBinaryManager(ctx, packagesPath, overridePath, c.pluginService, runtime.GOOS, runtime.GOARCH) + if err != nil { + return diags.Append(tfdiags.Sourceless(tfdiags.Error, errorSummary, err.Error())) + } + + version, err := bm.Resolve() + if err != nil { + return diags.Append(tfdiags.Sourceless(tfdiags.Error, "Cloud plugin download error", err.Error())) + } + + var cacheTraceMsg = "" + if version.ResolvedFromCache { + cacheTraceMsg = " (resolved from cache)" + } + if version.ResolvedFromDevOverride { + cacheTraceMsg = " (resolved from dev override)" + detailMsg := fmt.Sprintf("Instead of using the current released version, Terraform is loading the stacks plugin from the following location:\n\n - %s\n\nOverriding the stacks plugin location can cause unexpected behavior, and is only intended for use when developing new versions of the plugin.", version.Path) + diags = diags.Append(tfdiags.Sourceless( + tfdiags.Warning, + "Stacks plugin development overrides are in effect", + detailMsg, + )) + } + log.Printf("[TRACE] plugin %q binary located at %q%s", version.ProductVersion, version.Path, cacheTraceMsg) + c.pluginBinary = version.Path + return diags +} + +func (c *StacksCommand) initPackagesCache() (string, error) { + packagesPath := path.Join(c.WorkingDir.DataDir(), StacksPluginDataDir) + + if info, err := os.Stat(packagesPath); err != nil || !info.IsDir() { + log.Printf("[TRACE] initialized cloudplugin cache directory at %q", packagesPath) + err = os.MkdirAll(packagesPath, 0755) + if err != nil { + return "", fmt.Errorf("failed to initialize cloudplugin cache directory: %w", err) + } + } else { + log.Printf("[TRACE] cloudplugin cache directory found at %q", packagesPath) + } + + return packagesPath, nil +} + +// Run runs the cloud command with the given arguments. +func (c *StacksCommand) Run(args []string) int { + args = c.Meta.process(args) + return c.realRun(args, c.Meta.Streams.Stdout.File, c.Meta.Streams.Stderr.File) +} + +// Help returns help text for the cloud command. +func (c *StacksCommand) Help() string { + helpText := new(bytes.Buffer) + if exitCode := c.realRun([]string{}, helpText, io.Discard); exitCode != 0 { + return "" + } + + return helpText.String() +} + +// Synopsis returns a short summary of the cloud command. +func (c *StacksCommand) Synopsis() string { + return "Manage HCP Terraform settings and metadata" +} + +// CloudPluginConfig is everything the cloud plugin needs to know to configure a +// client and talk to HCP Terraform. +type StacksPluginConfig struct { + // Maybe someday we can use struct tags to automate grabbing these out of + // the metadata headers! And verify client-side that we're sending the right + // stuff, instead of having it all be a stringly-typed mystery ball! I want + // to believe in that distant shining day! 🌻 Meantime, these struct tags + // serve purely as docs. + Address string `md:"tfc-address"` + BasePath string `md:"tfc-base-path"` + DisplayHostname string `md:"tfc-display-hostname"` + Token string `md:"tfc-token"` + Organization string `md:"tfc-organization"` + TerraformBinaryPath string `md:"terraform-binary-path"` +} + +func (c StacksPluginConfig) ToMetadata() metadata.MD { + // First, do everything except tags the easy way + md := metadata.Pairs( + "tfc-address", c.Address, + "tfc-base-path", c.BasePath, + "tfc-display-hostname", c.DisplayHostname, + "tfc-token", c.Token, + "tfc-organization", c.Organization, + "terraform-binary-path", c.TerraformBinaryPath, + ) + return md +} diff --git a/internal/rpcapi/stacks_plugin_client.go b/internal/rpcapi/stacks_plugin_client.go new file mode 100644 index 000000000000..0f0ab1c8956d --- /dev/null +++ b/internal/rpcapi/stacks_plugin_client.go @@ -0,0 +1,134 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: BUSL-1.1 + +package rpcapi + +import ( + "context" + "fmt" + "io" + "log" + + "github.com/hashicorp/go-plugin" + "github.com/hashicorp/terraform-svchost/disco" + "github.com/hashicorp/terraform/internal/rpcapi/dynrpcserver" + "github.com/hashicorp/terraform/internal/rpcapi/terraform1/dependencies" + "github.com/hashicorp/terraform/internal/rpcapi/terraform1/packages" + "github.com/hashicorp/terraform/internal/rpcapi/terraform1/stacks" + "github.com/hashicorp/terraform/internal/stacksplugin" + "github.com/hashicorp/terraform/internal/stacksplugin/stacksproto1" + "google.golang.org/grpc" +) + +// GRPCCloudClient is the client interface for interacting with terraform-cloudplugin +type GRPCStacksClient struct { + Client stacksproto1.CommandServiceClient + Broker *plugin.GRPCBroker + Services *disco.Disco + Context context.Context +} + +// Proof that GRPCStacksClient fulfills the go-plugin interface +var _ stacksplugin.Stacks1 = GRPCStacksClient{} + +// Execute sends the client Execute request and waits for the plugin to return +// an exit code response before returning +func (c GRPCStacksClient) Execute(args []string, stdout, stderr io.Writer) int { + handles := newHandleTable() + + dependenciesServer := dynrpcserver.NewDependenciesStub() + packagesServer := dynrpcserver.NewPackagesStub() + stacksServer := dynrpcserver.NewStacksStub() + + var s *grpc.Server + dependenciesServerFunc := func(opts []grpc.ServerOption) *grpc.Server { + s = grpc.NewServer(opts...) + dependencies.RegisterDependenciesServer(s, dependenciesServer) + dependenciesServer.ActivateRPCServer(newDependenciesServer(handles, c.Services)) + + return s + } + + dependenciesBrokerID := c.Broker.NextId() + go c.Broker.AcceptAndServe(dependenciesBrokerID, dependenciesServerFunc) + + packagesServerFunc := func(opts []grpc.ServerOption) *grpc.Server { + s = grpc.NewServer(opts...) + packages.RegisterPackagesServer(s, packagesServer) + packagesServer.ActivateRPCServer(newPackagesServer(c.Services)) + + return s + } + + packagesBrokerID := c.Broker.NextId() + go c.Broker.AcceptAndServe(packagesBrokerID, packagesServerFunc) + + stacksServerFunc := func(opts []grpc.ServerOption) *grpc.Server { + s = grpc.NewServer(opts...) + stacks.RegisterStacksServer(s, stacksServer) + stacksServer.ActivateRPCServer(newStacksServer(newStopper(), handles, &serviceOpts{ + experimentsAllowed: true, + })) + return s + } + + stacksBrokerID := c.Broker.NextId() + go c.Broker.AcceptAndServe(stacksBrokerID, stacksServerFunc) + + client, err := c.Client.Execute(c.Context, &stacksproto1.CommandRequest{ + DependenciesServer: dependenciesBrokerID, + PackagesServer: packagesBrokerID, + StacksServer: stacksBrokerID, + Args: args, + }) + + if err != nil { + fmt.Fprint(stderr, err.Error()) + return 1 + } + + for { + // cloudplugin streams output as multiple CommandResponse value. Each + // value will either contain stdout bytes, stderr bytes, or an exit code. + response, err := client.Recv() + if err == io.EOF { + log.Print("[DEBUG] received EOF from cloudplugin") + break + } else if err != nil { + fmt.Fprintf(stderr, "Failed to receive command response from cloudplugin: %s", err) + return 1 + } + + if bytes := response.GetStdout(); len(bytes) > 0 { + written, err := fmt.Fprint(stdout, string(bytes)) + if err != nil { + log.Printf("[ERROR] Failed to write cloudplugin output to stdout: %s", err) + return 1 + } + if written != len(bytes) { + log.Printf("[ERROR] Wrote %d bytes to stdout but expected to write %d", written, len(bytes)) + } + } else if bytes := response.GetStderr(); len(bytes) > 0 { + written, err := fmt.Fprint(stderr, string(bytes)) + if err != nil { + log.Printf("[ERROR] Failed to write cloudplugin output to stderr: %s", err) + return 1 + } + if written != len(bytes) { + log.Printf("[ERROR] Wrote %d bytes to stdout but expected to write %d", written, len(bytes)) + } + } else { + exitCode := response.GetExitCode() + log.Printf("[TRACE] received exit code: %d", exitCode) + if exitCode < 0 || exitCode > 255 { + log.Printf("[ERROR] cloudplugin returned an invalid error code %d", exitCode) + return 255 + } + return int(exitCode) + } + } + + // This should indicate a bug in the plugin + fmt.Fprint(stderr, "cloudplugin exited without responding with an error code") + return 1 +} diff --git a/internal/stacksplugin/binary.go b/internal/stacksplugin/binary.go new file mode 100644 index 000000000000..b0ef1a9cbbde --- /dev/null +++ b/internal/stacksplugin/binary.go @@ -0,0 +1,275 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: BUSL-1.1 + +package stacksplugin + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "log" + "net/url" + "os" + "path" + "path/filepath" + "strings" + "time" + + "github.com/hashicorp/go-getter" + svchost "github.com/hashicorp/terraform-svchost" + "github.com/hashicorp/terraform/internal/releaseauth" +) + +// BinaryManager downloads, caches, and returns information about the +// terraform-cloudplugin binary downloaded from the specified backend. +type BinaryManager struct { + signingKey string + binaryName string + stacksPluginDataDir string + overridePath string + host svchost.Hostname + client *StacksPluginClient + goos string + arch string + ctx context.Context +} + +// Binary is a struct containing the path to an authenticated binary corresponding to +// a backend service. +type Binary struct { + Path string + ProductVersion string + ResolvedFromCache bool + ResolvedFromDevOverride bool +} + +const ( + KB = 1000 + MB = 1000 * KB +) + +// BinaryManager initializes a new BinaryManager to broker data between the +// specified directory location containing cloudplugin package data and a +// HCP Terraform backend URL. +func NewBinaryManager(ctx context.Context, stacksPluginDataDir, overridePath string, serviceURL *url.URL, goos, arch string) (*BinaryManager, error) { + client, err := NewStacksPluginClient(ctx, serviceURL) + if err != nil { + return nil, fmt.Errorf("could not initialize cloudplugin version manager: %w", err) + } + + return &BinaryManager{ + stacksPluginDataDir: stacksPluginDataDir, + overridePath: overridePath, + host: svchost.Hostname(serviceURL.Host), + client: client, + binaryName: "tfstacks", + goos: goos, + arch: arch, + ctx: ctx, + }, nil +} + +func (v BinaryManager) binaryLocation() string { + return path.Join(v.stacksPluginDataDir, "bin", fmt.Sprintf("%s_%s", v.goos, v.arch)) +} + +func (v BinaryManager) cachedVersion(version string) *string { + binaryPath := path.Join(v.binaryLocation(), v.binaryName) + + if _, err := os.Stat(binaryPath); err != nil { + return nil + } + + // The version from the manifest must match the contents of ".version" + versionData, err := os.ReadFile(path.Join(v.binaryLocation(), ".version")) + if err != nil || strings.Trim(string(versionData), " \n\r\t") != version { + return nil + } + + return &binaryPath +} + +// Resolve fetches, authenticates, and caches a plugin binary matching the specifications +// and returns its location and version. +func (v BinaryManager) Resolve() (*Binary, error) { + if v.overridePath != "" { + log.Printf("[TRACE] Using dev override for cloudplugin binary") + return v.resolveDev() + } + return v.resolveRelease() +} + +func (v BinaryManager) resolveDev() (*Binary, error) { + return &Binary{ + Path: v.overridePath, + ProductVersion: "dev", + ResolvedFromDevOverride: true, + }, nil +} + +func (v BinaryManager) resolveRelease() (*Binary, error) { + manifest, err := v.latestManifest(v.ctx) + if err != nil { + return nil, fmt.Errorf("could not resolve cloudplugin version for host %q: %w", v.host.ForDisplay(), err) + } + + buildInfo, err := manifest.Select(v.goos, v.arch) + if err != nil { + return nil, err + } + + // Check if there's a cached binary + if cachedBinary := v.cachedVersion(manifest.Version); cachedBinary != nil { + return &Binary{ + Path: *cachedBinary, + ProductVersion: manifest.Version, + ResolvedFromCache: true, + }, nil + } + + // Download the archive + t, err := os.CreateTemp(os.TempDir(), "terraform-cloudplugin") + if err != nil { + return nil, fmt.Errorf("failed to create temp file for download: %w", err) + } + defer os.Remove(t.Name()) + + err = v.client.DownloadFile(buildInfo.URL, t) + if err != nil { + return nil, err + } + t.Close() // Close only returns an error if it's already been called + + // Authenticate the archive + err = v.verifyCloudPlugin(manifest, buildInfo, t.Name()) + if err != nil { + return nil, fmt.Errorf("could not resolve cloudplugin version %q: %w", manifest.Version, err) + } + + // Unarchive + unzip := getter.ZipDecompressor{ + FilesLimit: 1, + FileSizeLimit: 500 * MB, + } + targetPath := v.binaryLocation() + log.Printf("[TRACE] decompressing %q to %q", t.Name(), targetPath) + + err = unzip.Decompress(targetPath, t.Name(), true, 0000) + if err != nil { + return nil, fmt.Errorf("failed to decompress cloud plugin: %w", err) + } + + err = os.WriteFile(path.Join(targetPath, ".version"), []byte(manifest.Version), 0644) + if err != nil { + log.Printf("[ERROR] failed to write .version file to %q: %s", targetPath, err) + } + + return &Binary{ + Path: path.Join(targetPath, v.binaryName), + ProductVersion: manifest.Version, + ResolvedFromCache: false, + }, nil +} + +// Useful for small files that can be decoded all at once +func (v BinaryManager) downloadFileBuffer(pathOrURL string) ([]byte, error) { + buffer := bytes.Buffer{} + err := v.client.DownloadFile(pathOrURL, &buffer) + if err != nil { + return nil, err + } + + return buffer.Bytes(), err +} + +// verifyCloudPlugin authenticates the downloaded release archive +func (v BinaryManager) verifyCloudPlugin(archiveManifest *Release, info *BuildArtifact, archiveLocation string) error { + signature, err := v.downloadFileBuffer(archiveManifest.URLSHASumsSignatures[0]) + if err != nil { + return fmt.Errorf("failed to download cloudplugin SHA256SUMS signature file: %w", err) + } + sums, err := v.downloadFileBuffer(archiveManifest.URLSHASums) + if err != nil { + return fmt.Errorf("failed to download cloudplugin SHA256SUMS file: %w", err) + } + + checksums, err := releaseauth.ParseChecksums(sums) + if err != nil { + return fmt.Errorf("failed to parse cloudplugin SHA256SUMS file: %w", err) + } + + filename := path.Base(info.URL) + reportedSHA, ok := checksums[filename] + if !ok { + return fmt.Errorf("could not find checksum for file %q", filename) + } + + sigAuth := releaseauth.NewSignatureAuthentication(signature, sums) + if len(v.signingKey) > 0 { + sigAuth.PublicKey = v.signingKey + } + + all := releaseauth.AllAuthenticators( + releaseauth.NewChecksumAuthentication(reportedSHA, archiveLocation), + sigAuth, + ) + + return all.Authenticate() +} + +func (v BinaryManager) latestManifest(ctx context.Context) (*Release, error) { + manifestCacheLocation := path.Join(v.stacksPluginDataDir, v.host.String(), "manifest.json") + + // Find the manifest cache for the hostname. + data, err := os.ReadFile(manifestCacheLocation) + modTime := time.Time{} + var localManifest *Release + if err != nil { + log.Printf("[TRACE] no cloudplugin manifest cache found for host %q", v.host) + } else { + log.Printf("[TRACE] cloudplugin manifest cache found for host %q", v.host) + + localManifest, err = decodeManifest(bytes.NewBuffer(data)) + modTime = localManifest.TimestampUpdated + if err != nil { + log.Printf("[WARN] failed to decode cloudplugin manifest cache %q: %s", manifestCacheLocation, err) + } + } + + // Even though we may have a local manifest, always see if there is a newer remote manifest + result, err := v.client.FetchManifest(modTime) + // FetchManifest can return nil, nil (see below) + if err != nil { + return nil, fmt.Errorf("failed to fetch cloudplugin manifest: %w", err) + } + + // No error and no remoteManifest means the existing manifest is not modified + // and it's safe to use the local manifest + if result == nil && localManifest != nil { + result = localManifest + } else { + data, err := json.Marshal(result) + if err != nil { + return nil, fmt.Errorf("failed to dump cloudplugin manifest to JSON: %w", err) + } + + // Ensure target directory exists + if err := os.MkdirAll(filepath.Dir(manifestCacheLocation), 0755); err != nil { + return nil, fmt.Errorf("failed to create cloudplugin manifest cache directory: %w", err) + } + + output, err := os.Create(manifestCacheLocation) + if err != nil { + return nil, fmt.Errorf("failed to create cloudplugin manifest cache: %w", err) + } + + _, err = output.Write(data) + if err != nil { + return nil, fmt.Errorf("failed to write cloudplugin manifest cache: %w", err) + } + log.Printf("[TRACE] wrote cloudplugin manifest cache to %q", manifestCacheLocation) + } + + return result, nil +} diff --git a/internal/stacksplugin/client.go b/internal/stacksplugin/client.go new file mode 100644 index 000000000000..ce08caca90f6 --- /dev/null +++ b/internal/stacksplugin/client.go @@ -0,0 +1,323 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: BUSL-1.1 + +package stacksplugin + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "io" + "log" + "net/http" + "net/url" + "strings" + "time" + + "github.com/hashicorp/go-retryablehttp" + "github.com/hashicorp/terraform/internal/httpclient" + "github.com/hashicorp/terraform/internal/logging" + "github.com/hashicorp/terraform/internal/releaseauth" +) + +var ( + defaultRequestTimeout = 60 * time.Second +) + +// SHASumsSignatures holds a list of URLs, each referring a detached signature of the release's build artifacts. +type SHASumsSignatures []string + +// BuildArtifact represents a single build artifact in a release response. +type BuildArtifact struct { + + // The hardware architecture of the build artifact + // Enum: [386 all amd64 amd64-lxc arm arm5 arm6 arm64 arm7 armelv5 armhfv6 i686 mips mips64 mipsle ppc64le s390x ui x86_64] + Arch string `json:"arch"` + + // The Operating System corresponding to the build artifact + // Enum: [archlinux centos darwin debian dragonfly freebsd linux netbsd openbsd plan9 python solaris terraform web windows] + Os string `json:"os"` + + // This build is unsupported and provided for convenience only. + Unsupported bool `json:"unsupported,omitempty"` + + // The URL where this build can be downloaded + URL string `json:"url"` +} + +// ReleaseStatus Status of the product release +// Example: {"message":"This release is supported","state":"supported"} +type ReleaseStatus struct { + + // Provides information about the most recent change; must be provided when Name="withdrawn" + Message string `json:"message,omitempty"` + + // The state name of the release + // Enum: [supported unsupported withdrawn] + State string `json:"state"` + + // The timestamp for the creation of the product release status + // Example: 2009-11-10T23:00:00Z + // Format: date-time + TimestampUpdated time.Time `json:"timestamp_updated"` +} + +// Release All metadata for a single product release +type Release struct { + // builds + Builds []*BuildArtifact `json:"builds,omitempty"` + + // A docker image name and tag for this release in the format `name`:`tag` + // Example: consul:1.10.0-beta3 + DockerNameTag string `json:"docker_name_tag,omitempty"` + + // True if and only if this product release is a prerelease. + IsPrerelease bool `json:"is_prerelease"` + + // The license class indicates how this product is licensed. + // Enum: [enterprise hcp oss] + LicenseClass string `json:"license_class"` + + // The product name + // Example: consul-enterprise + // Required: true + Name string `json:"name"` + + // Status + Status ReleaseStatus `json:"status"` + + // Timestamp at which this product release was created. + // Example: 2009-11-10T23:00:00Z + // Format: date-time + TimestampCreated time.Time `json:"timestamp_created"` + + // Timestamp when this product release was most recently updated. + // Example: 2009-11-10T23:00:00Z + // Format: date-time + TimestampUpdated time.Time `json:"timestamp_updated"` + + // URL for a blogpost announcing this release + URLBlogpost string `json:"url_blogpost,omitempty"` + + // URL for the changelog covering this release + URLChangelog string `json:"url_changelog,omitempty"` + + // The project's docker repo on Amazon ECR-Public + URLDockerRegistryDockerhub string `json:"url_docker_registry_dockerhub,omitempty"` + + // The project's docker repo on DockerHub + URLDockerRegistryEcr string `json:"url_docker_registry_ecr,omitempty"` + + // URL for the software license applicable to this release + // Required: true + URLLicense string `json:"url_license,omitempty"` + + // The project's website URL + URLProjectWebsite string `json:"url_project_website,omitempty"` + + // URL for this release's change notes + URLReleaseNotes string `json:"url_release_notes,omitempty"` + + // URL for this release's file containing checksums of all the included build artifacts + URLSHASums string `json:"url_shasums"` + + // An array of URLs, each pointing to a signature file. Each signature file is a detached signature + // of the checksums file (see field `url_shasums`). Signature files may or may not embed the signing + // key ID in the filename. + URLSHASumsSignatures SHASumsSignatures `json:"url_shasums_signatures"` + + // URL for the product's source code repository. This field is empty for + // enterprise and hcp products. + URLSourceRepository string `json:"url_source_repository,omitempty"` + + // The version of this release + // Example: 1.10.0-beta3 + // Required: true + Version string `json:"version"` +} + +// CloudPluginClient fetches and verifies release distributions of the cloudplugin +// that correspond to an upstream backend. +type StacksPluginClient struct { + serviceURL *url.URL + httpClient *retryablehttp.Client + ctx context.Context +} + +func requestLogHook(logger retryablehttp.Logger, req *http.Request, i int) { + if i > 0 { + logger.Printf("[INFO] Previous request to the remote cloud manifest failed, attempting retry.") + } +} + +func decodeManifest(data io.Reader) (*Release, error) { + var man Release + dec := json.NewDecoder(data) + if err := dec.Decode(&man); err != nil { + return nil, ErrQueryFailed{ + inner: fmt.Errorf("failed to decode response body: %w", err), + } + } + + return &man, nil +} + +// NewCloudPluginClient creates a new client for downloading and verifying +// terraform-cloudplugin archives +func NewStacksPluginClient(ctx context.Context, serviceURL *url.URL) (*StacksPluginClient, error) { + httpClient := httpclient.New() + httpClient.Timeout = defaultRequestTimeout + + retryableClient := retryablehttp.NewClient() + retryableClient.HTTPClient = httpClient + retryableClient.RetryMax = 3 + retryableClient.RequestLogHook = requestLogHook + retryableClient.Logger = logging.HCLogger() + + return &StacksPluginClient{ + httpClient: retryableClient, + serviceURL: serviceURL, + ctx: ctx, + }, nil +} + +// FetchManifest retrieves the cloudplugin manifest from HCP Terraform, +// but returns a nil manifest if a 304 response is received, depending +// on the lastModified time. +func (c StacksPluginClient) FetchManifest(lastModified time.Time) (*Release, error) { + req, _ := retryablehttp.NewRequestWithContext(c.ctx, "GET", c.serviceURL.JoinPath("manifest.json").String(), nil) + req.Header.Set("If-Modified-Since", lastModified.Format(http.TimeFormat)) + + resp, err := c.httpClient.Do(req) + if err != nil { + if errors.Is(err, context.Canceled) { + return nil, ErrRequestCanceled + } + return nil, ErrQueryFailed{ + inner: err, + } + } + defer resp.Body.Close() + + switch resp.StatusCode { + case http.StatusOK: + manifest, err := decodeManifest(resp.Body) + if err != nil { + return nil, err + } + return manifest, nil + case http.StatusNotModified: + return nil, nil + case http.StatusNotFound: + return nil, ErrCloudPluginNotSupported + default: + return nil, ErrQueryFailed{ + inner: errors.New(resp.Status), + } + } +} + +// DownloadFile gets the URL at the specified path or URL and writes the +// contents to the specified Writer. +func (c StacksPluginClient) DownloadFile(pathOrURL string, writer io.Writer) error { + url, err := c.resolveManifestURL(pathOrURL) + if err != nil { + return err + } + req, err := retryablehttp.NewRequestWithContext(c.ctx, "GET", url.String(), nil) + if err != nil { + return fmt.Errorf("invalid URL %q was provided by the cloudplugin manifest: %w", url, err) + } + resp, err := c.httpClient.Do(req) + if err != nil { + if errors.Is(err, context.Canceled) { + return ErrRequestCanceled + } + return err + } + defer resp.Body.Close() + + switch resp.StatusCode { + case http.StatusOK: + // OK + case http.StatusNotFound: + return ErrCloudPluginNotFound + default: + return ErrQueryFailed{ + inner: errors.New(resp.Status), + } + } + + _, err = io.Copy(writer, resp.Body) + if err != nil { + return fmt.Errorf("failed to write downloaded file: %w", err) + } + + return nil +} + +func (c StacksPluginClient) resolveManifestURL(pathOrURL string) (*url.URL, error) { + if strings.HasPrefix(pathOrURL, "/") { + copy := *c.serviceURL + copy.Path = "" + return copy.JoinPath(pathOrURL), nil + } + + result, err := url.Parse(pathOrURL) + if err != nil { + return nil, fmt.Errorf("received malformed URL %q from cloudplugin manifest: %w", pathOrURL, err) + } + return result, nil +} + +// Select gets the specific build data from the Manifest for the specified OS/Architecture +func (m Release) Select(goos, arch string) (*BuildArtifact, error) { + var supported []string + var found *BuildArtifact + for _, build := range m.Builds { + key := fmt.Sprintf("%s_%s", build.Os, build.Arch) + supported = append(supported, key) + + if goos == build.Os && arch == build.Arch { + found = build + } + } + + osArchKey := fmt.Sprintf("%s_%s", goos, arch) + log.Printf("[TRACE] checking for cloudplugin archive for %s. Supported architectures: %v", osArchKey, supported) + + if found == nil { + return nil, ErrArchNotSupported + } + + return found, nil +} + +// PrimarySHASumsSignatureURL returns the URL among the URLSHASumsSignatures that matches +// the public key known by this version of terraform. It falls back to the first URL with no +// ID in the URL. +func (m Release) PrimarySHASumsSignatureURL() (string, error) { + if len(m.URLSHASumsSignatures) == 0 { + return "", fmt.Errorf("no SHA256SUMS URLs were available") + } + + findBySuffix := func(suffix string) string { + for _, url := range m.URLSHASumsSignatures { + if len(url) > len(suffix) && strings.EqualFold(suffix, url[len(url)-len(suffix):]) { + return url + } + } + return "" + } + + withKeyID := findBySuffix(fmt.Sprintf(".%s.sig", releaseauth.HashiCorpPublicKeyID)) + if withKeyID == "" { + withNoKeyID := findBySuffix("_SHA256SUMS.sig") + if withNoKeyID == "" { + return "", fmt.Errorf("no SHA256SUMS URLs matched the known public key") + } + return withNoKeyID, nil + } + return withKeyID, nil +} diff --git a/internal/stacksplugin/errors.go b/internal/stacksplugin/errors.go new file mode 100644 index 000000000000..a4fbb4f0d1cc --- /dev/null +++ b/internal/stacksplugin/errors.go @@ -0,0 +1,57 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: BUSL-1.1 + +package stacksplugin + +import ( + "errors" + "fmt" +) + +var ( + // ErrCloudPluginNotSupported is the error returned when the upstream HCP Terraform does not + // have a manifest. + ErrCloudPluginNotSupported = errors.New("cloud plugin is not supported by the remote version of Terraform Enterprise") + + // ErrRequestCanceled is the error returned when the context was cancelled. + ErrRequestCanceled = errors.New("request was canceled") + + // ErrArchNotSupported is the error returned when the cloudplugin does not have a build for the + // current OS/Architecture. + ErrArchNotSupported = errors.New("cloud plugin is not supported by your computer architecture/operating system") + + // ErrCloudPluginNotFound is the error returned when the cloudplugin manifest points to a location + // that was does not exist. + ErrCloudPluginNotFound = errors.New("cloud plugin download was not found in the location specified in the manifest") +) + +// ErrQueryFailed is the error returned when the cloudplugin http client request fails +type ErrQueryFailed struct { + inner error +} + +// ErrCloudPluginNotVerified is the error returned when the archive authentication process fails +type ErrStacksPluginNotVerified struct { + inner error +} + +// Error returns a string representation of ErrQueryFailed +func (e ErrQueryFailed) Error() string { + return fmt.Sprintf("failed to fetch Stacks plugin from HCP Terraform: %s", e.inner) +} + +// Unwrap returns the inner error of ErrQueryFailed +func (e ErrQueryFailed) Unwrap() error { + // Return the inner error. + return e.inner +} + +// Error returns the string representation of ErrStacksPluginNotVerified +func (e ErrStacksPluginNotVerified) Error() string { + return fmt.Sprintf("failed to verify Stacks plugin. Ensure that the referenced plugin is the official HashiCorp distribution: %s", e.inner) +} + +// Unwrap returns the inner error of ErrStacksPluginNotVerified +func (e ErrStacksPluginNotVerified) Unwrap() error { + return e.inner +} diff --git a/internal/stacksplugin/interface.go b/internal/stacksplugin/interface.go new file mode 100644 index 000000000000..015fdb4d7b41 --- /dev/null +++ b/internal/stacksplugin/interface.go @@ -0,0 +1,14 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: BUSL-1.1 + +package stacksplugin + +import ( + "io" +) + +// Stacks1 interface for Terraform plugin operations +type Stacks1 interface { + // Execute runs a command with the provided arguments and returns the exit code + Execute(args []string, stdout, stderr io.Writer) int +} diff --git a/internal/stacksplugin/stacksplugin1/grpc_plugin.go b/internal/stacksplugin/stacksplugin1/grpc_plugin.go new file mode 100644 index 000000000000..9d35f3cdf034 --- /dev/null +++ b/internal/stacksplugin/stacksplugin1/grpc_plugin.go @@ -0,0 +1,63 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: BUSL-1.1 + +package stacksplugin1 + +import ( + "context" + "errors" + "net/rpc" + + "github.com/hashicorp/go-plugin" + "github.com/hashicorp/terraform-svchost/disco" + "github.com/hashicorp/terraform/internal/rpcapi" + "github.com/hashicorp/terraform/internal/stacksplugin" + "github.com/hashicorp/terraform/internal/stacksplugin/stacksproto1" + "google.golang.org/grpc" + "google.golang.org/grpc/metadata" +) + +// GRPCCloudPlugin is the go-plugin implementation, but only the client +// implementation exists in this package. +type GRPCStacksPlugin struct { + plugin.GRPCPlugin + Metadata metadata.MD + Impl stacksplugin.Stacks1 + Services *disco.Disco +} + +type GRPCStacksServer struct { + // This is the real implementation + Impl stacksplugin.Stacks1 + + broker *plugin.GRPCBroker +} + +// Server always returns an error; we're only implementing the GRPCPlugin +// interface, not the Plugin interface. +func (p *GRPCStacksPlugin) Server(*plugin.MuxBroker) (interface{}, error) { + return nil, errors.New("cloudplugin only implements gRPC clients") +} + +// Client always returns an error; we're only implementing the GRPCPlugin +// interface, not the Plugin interface. +func (p *GRPCStacksPlugin) Client(*plugin.MuxBroker, *rpc.Client) (interface{}, error) { + return nil, errors.New("cloudplugin only implements gRPC clients") +} + +// GRPCClient returns a new GRPC client for interacting with the cloud plugin server. +func (p *GRPCStacksPlugin) GRPCClient(ctx context.Context, broker *plugin.GRPCBroker, c *grpc.ClientConn) (interface{}, error) { + ctx = metadata.NewOutgoingContext(ctx, p.Metadata) + return &rpcapi.GRPCStacksClient{ + Client: stacksproto1.NewCommandServiceClient(c), + Broker: broker, + Services: p.Services, + Context: ctx, + }, nil +} + +// GRPCServer always returns an error; we're only implementing the client +// interface, not the server. +func (p *GRPCStacksPlugin) GRPCServer(broker *plugin.GRPCBroker, s *grpc.Server) error { + return errors.ErrUnsupported +} diff --git a/internal/stacksplugin/stacksproto1/stacksproto1.pb.go b/internal/stacksplugin/stacksproto1/stacksproto1.pb.go new file mode 100644 index 000000000000..82e7b828cf13 --- /dev/null +++ b/internal/stacksplugin/stacksproto1/stacksproto1.pb.go @@ -0,0 +1,286 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: BUSL-1.1 + +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.36.5 +// protoc v5.29.3 +// source: stacksproto1.proto + +package stacksproto1 + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" + unsafe "unsafe" +) + +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) +) + +// CommandRequest is used to request the execution of a specific command with +// provided flags. It is the raw args from the HCP Terraform command. +type CommandRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Args []string `protobuf:"bytes,1,rep,name=args,proto3" json:"args,omitempty"` + DependenciesServer uint32 `protobuf:"varint,2,opt,name=dependencies_server,json=dependenciesServer,proto3" json:"dependencies_server,omitempty"` + PackagesServer uint32 `protobuf:"varint,3,opt,name=packages_server,json=packagesServer,proto3" json:"packages_server,omitempty"` + StacksServer uint32 `protobuf:"varint,4,opt,name=stacks_server,json=stacksServer,proto3" json:"stacks_server,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *CommandRequest) Reset() { + *x = CommandRequest{} + mi := &file_stacksproto1_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *CommandRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CommandRequest) ProtoMessage() {} + +func (x *CommandRequest) ProtoReflect() protoreflect.Message { + mi := &file_stacksproto1_proto_msgTypes[0] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CommandRequest.ProtoReflect.Descriptor instead. +func (*CommandRequest) Descriptor() ([]byte, []int) { + return file_stacksproto1_proto_rawDescGZIP(), []int{0} +} + +func (x *CommandRequest) GetArgs() []string { + if x != nil { + return x.Args + } + return nil +} + +func (x *CommandRequest) GetDependenciesServer() uint32 { + if x != nil { + return x.DependenciesServer + } + return 0 +} + +func (x *CommandRequest) GetPackagesServer() uint32 { + if x != nil { + return x.PackagesServer + } + return 0 +} + +func (x *CommandRequest) GetStacksServer() uint32 { + if x != nil { + return x.StacksServer + } + return 0 +} + +// CommandResponse contains the result of the command execution, including any +// output or errors. +type CommandResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Types that are valid to be assigned to Data: + // + // *CommandResponse_ExitCode + // *CommandResponse_Stdout + // *CommandResponse_Stderr + Data isCommandResponse_Data `protobuf_oneof:"data"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *CommandResponse) Reset() { + *x = CommandResponse{} + mi := &file_stacksproto1_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *CommandResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CommandResponse) ProtoMessage() {} + +func (x *CommandResponse) ProtoReflect() protoreflect.Message { + mi := &file_stacksproto1_proto_msgTypes[1] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CommandResponse.ProtoReflect.Descriptor instead. +func (*CommandResponse) Descriptor() ([]byte, []int) { + return file_stacksproto1_proto_rawDescGZIP(), []int{1} +} + +func (x *CommandResponse) GetData() isCommandResponse_Data { + if x != nil { + return x.Data + } + return nil +} + +func (x *CommandResponse) GetExitCode() int32 { + if x != nil { + if x, ok := x.Data.(*CommandResponse_ExitCode); ok { + return x.ExitCode + } + } + return 0 +} + +func (x *CommandResponse) GetStdout() []byte { + if x != nil { + if x, ok := x.Data.(*CommandResponse_Stdout); ok { + return x.Stdout + } + } + return nil +} + +func (x *CommandResponse) GetStderr() []byte { + if x != nil { + if x, ok := x.Data.(*CommandResponse_Stderr); ok { + return x.Stderr + } + } + return nil +} + +type isCommandResponse_Data interface { + isCommandResponse_Data() +} + +type CommandResponse_ExitCode struct { + ExitCode int32 `protobuf:"varint,1,opt,name=exitCode,proto3,oneof"` +} + +type CommandResponse_Stdout struct { + Stdout []byte `protobuf:"bytes,2,opt,name=stdout,proto3,oneof"` +} + +type CommandResponse_Stderr struct { + Stderr []byte `protobuf:"bytes,3,opt,name=stderr,proto3,oneof"` +} + +func (*CommandResponse_ExitCode) isCommandResponse_Data() {} + +func (*CommandResponse_Stdout) isCommandResponse_Data() {} + +func (*CommandResponse_Stderr) isCommandResponse_Data() {} + +var File_stacksproto1_proto protoreflect.FileDescriptor + +var file_stacksproto1_proto_rawDesc = string([]byte{ + 0x0a, 0x12, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x73, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x31, 0x2e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0c, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x73, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x31, 0x22, 0xa3, 0x01, 0x0a, 0x0e, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x61, 0x72, 0x67, 0x73, 0x18, 0x01, 0x20, + 0x03, 0x28, 0x09, 0x52, 0x04, 0x61, 0x72, 0x67, 0x73, 0x12, 0x2f, 0x0a, 0x13, 0x64, 0x65, 0x70, + 0x65, 0x6e, 0x64, 0x65, 0x6e, 0x63, 0x69, 0x65, 0x73, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x12, 0x64, 0x65, 0x70, 0x65, 0x6e, 0x64, 0x65, 0x6e, + 0x63, 0x69, 0x65, 0x73, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x12, 0x27, 0x0a, 0x0f, 0x70, 0x61, + 0x63, 0x6b, 0x61, 0x67, 0x65, 0x73, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x0d, 0x52, 0x0e, 0x70, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x73, 0x53, 0x65, 0x72, + 0x76, 0x65, 0x72, 0x12, 0x23, 0x0a, 0x0d, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x73, 0x5f, 0x73, 0x65, + 0x72, 0x76, 0x65, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0c, 0x73, 0x74, 0x61, 0x63, + 0x6b, 0x73, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x22, 0x6b, 0x0a, 0x0f, 0x43, 0x6f, 0x6d, 0x6d, + 0x61, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1c, 0x0a, 0x08, 0x65, + 0x78, 0x69, 0x74, 0x43, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x48, 0x00, 0x52, + 0x08, 0x65, 0x78, 0x69, 0x74, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x18, 0x0a, 0x06, 0x73, 0x74, 0x64, + 0x6f, 0x75, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x48, 0x00, 0x52, 0x06, 0x73, 0x74, 0x64, + 0x6f, 0x75, 0x74, 0x12, 0x18, 0x0a, 0x06, 0x73, 0x74, 0x64, 0x65, 0x72, 0x72, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x0c, 0x48, 0x00, 0x52, 0x06, 0x73, 0x74, 0x64, 0x65, 0x72, 0x72, 0x42, 0x06, 0x0a, + 0x04, 0x64, 0x61, 0x74, 0x61, 0x32, 0x5c, 0x0a, 0x0e, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, + 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x4a, 0x0a, 0x07, 0x45, 0x78, 0x65, 0x63, 0x75, + 0x74, 0x65, 0x12, 0x1c, 0x2e, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x73, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x31, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x1d, 0x2e, 0x73, 0x74, 0x61, 0x63, 0x6b, 0x73, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x31, 0x2e, + 0x43, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, + 0x00, 0x30, 0x01, 0x42, 0x43, 0x5a, 0x41, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, + 0x6d, 0x2f, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2f, 0x74, 0x65, 0x72, 0x72, + 0x61, 0x66, 0x6f, 0x72, 0x6d, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x73, + 0x74, 0x61, 0x63, 0x6b, 0x73, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x2f, 0x73, 0x74, 0x61, 0x63, + 0x6b, 0x73, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +}) + +var ( + file_stacksproto1_proto_rawDescOnce sync.Once + file_stacksproto1_proto_rawDescData []byte +) + +func file_stacksproto1_proto_rawDescGZIP() []byte { + file_stacksproto1_proto_rawDescOnce.Do(func() { + file_stacksproto1_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_stacksproto1_proto_rawDesc), len(file_stacksproto1_proto_rawDesc))) + }) + return file_stacksproto1_proto_rawDescData +} + +var file_stacksproto1_proto_msgTypes = make([]protoimpl.MessageInfo, 2) +var file_stacksproto1_proto_goTypes = []any{ + (*CommandRequest)(nil), // 0: stacksproto1.CommandRequest + (*CommandResponse)(nil), // 1: stacksproto1.CommandResponse +} +var file_stacksproto1_proto_depIdxs = []int32{ + 0, // 0: stacksproto1.CommandService.Execute:input_type -> stacksproto1.CommandRequest + 1, // 1: stacksproto1.CommandService.Execute:output_type -> stacksproto1.CommandResponse + 1, // [1:2] is the sub-list for method output_type + 0, // [0:1] is the sub-list for method input_type + 0, // [0:0] is the sub-list for extension type_name + 0, // [0:0] is the sub-list for extension extendee + 0, // [0:0] is the sub-list for field type_name +} + +func init() { file_stacksproto1_proto_init() } +func file_stacksproto1_proto_init() { + if File_stacksproto1_proto != nil { + return + } + file_stacksproto1_proto_msgTypes[1].OneofWrappers = []any{ + (*CommandResponse_ExitCode)(nil), + (*CommandResponse_Stdout)(nil), + (*CommandResponse_Stderr)(nil), + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: unsafe.Slice(unsafe.StringData(file_stacksproto1_proto_rawDesc), len(file_stacksproto1_proto_rawDesc)), + NumEnums: 0, + NumMessages: 2, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_stacksproto1_proto_goTypes, + DependencyIndexes: file_stacksproto1_proto_depIdxs, + MessageInfos: file_stacksproto1_proto_msgTypes, + }.Build() + File_stacksproto1_proto = out.File + file_stacksproto1_proto_goTypes = nil + file_stacksproto1_proto_depIdxs = nil +} diff --git a/internal/stacksplugin/stacksproto1/stacksproto1.proto b/internal/stacksplugin/stacksproto1/stacksproto1.proto new file mode 100644 index 000000000000..d9413a63d022 --- /dev/null +++ b/internal/stacksplugin/stacksproto1/stacksproto1.proto @@ -0,0 +1,33 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: BUSL-1.1 + +syntax = "proto3"; +package stacksproto1; + +option go_package = "github.com/hashicorp/terraform/internal/stacksplugin/stacksproto1"; + +// CommandRequest is used to request the execution of a specific command with +// provided flags. It is the raw args from the HCP Terraform command. +message CommandRequest { + repeated string args = 1; + uint32 dependencies_server = 2; + uint32 packages_server = 3; + uint32 stacks_server = 4; +} + +// CommandResponse contains the result of the command execution, including any +// output or errors. +message CommandResponse { + oneof data { + int32 exitCode = 1; + bytes stdout = 2; + bytes stderr = 3; + } +} + +// PluginService defines the gRPC service to handle available commands and +// their execution. +service CommandService { + // Execute runs a specific command with the provided flags and returns the result. + rpc Execute(CommandRequest) returns (stream CommandResponse) {} +} \ No newline at end of file diff --git a/internal/stacksplugin/stacksproto1/stacksproto1_grpc.pb.go b/internal/stacksplugin/stacksproto1/stacksproto1_grpc.pb.go new file mode 100644 index 000000000000..c6b89d7232a9 --- /dev/null +++ b/internal/stacksplugin/stacksproto1/stacksproto1_grpc.pb.go @@ -0,0 +1,135 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: BUSL-1.1 + +// Code generated by protoc-gen-go-grpc. DO NOT EDIT. +// versions: +// - protoc-gen-go-grpc v1.5.1 +// - protoc v5.29.3 +// source: stacksproto1.proto + +package stacksproto1 + +import ( + context "context" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" +) + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +// Requires gRPC-Go v1.64.0 or later. +const _ = grpc.SupportPackageIsVersion9 + +const ( + CommandService_Execute_FullMethodName = "/stacksproto1.CommandService/Execute" +) + +// CommandServiceClient is the client API for CommandService service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +// +// PluginService defines the gRPC service to handle available commands and +// their execution. +type CommandServiceClient interface { + // Execute runs a specific command with the provided flags and returns the result. + Execute(ctx context.Context, in *CommandRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[CommandResponse], error) +} + +type commandServiceClient struct { + cc grpc.ClientConnInterface +} + +func NewCommandServiceClient(cc grpc.ClientConnInterface) CommandServiceClient { + return &commandServiceClient{cc} +} + +func (c *commandServiceClient) Execute(ctx context.Context, in *CommandRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[CommandResponse], error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + stream, err := c.cc.NewStream(ctx, &CommandService_ServiceDesc.Streams[0], CommandService_Execute_FullMethodName, cOpts...) + if err != nil { + return nil, err + } + x := &grpc.GenericClientStream[CommandRequest, CommandResponse]{ClientStream: stream} + if err := x.ClientStream.SendMsg(in); err != nil { + return nil, err + } + if err := x.ClientStream.CloseSend(); err != nil { + return nil, err + } + return x, nil +} + +// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name. +type CommandService_ExecuteClient = grpc.ServerStreamingClient[CommandResponse] + +// CommandServiceServer is the server API for CommandService service. +// All implementations must embed UnimplementedCommandServiceServer +// for forward compatibility. +// +// PluginService defines the gRPC service to handle available commands and +// their execution. +type CommandServiceServer interface { + // Execute runs a specific command with the provided flags and returns the result. + Execute(*CommandRequest, grpc.ServerStreamingServer[CommandResponse]) error + mustEmbedUnimplementedCommandServiceServer() +} + +// UnimplementedCommandServiceServer must be embedded to have +// forward compatible implementations. +// +// NOTE: this should be embedded by value instead of pointer to avoid a nil +// pointer dereference when methods are called. +type UnimplementedCommandServiceServer struct{} + +func (UnimplementedCommandServiceServer) Execute(*CommandRequest, grpc.ServerStreamingServer[CommandResponse]) error { + return status.Errorf(codes.Unimplemented, "method Execute not implemented") +} +func (UnimplementedCommandServiceServer) mustEmbedUnimplementedCommandServiceServer() {} +func (UnimplementedCommandServiceServer) testEmbeddedByValue() {} + +// UnsafeCommandServiceServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to CommandServiceServer will +// result in compilation errors. +type UnsafeCommandServiceServer interface { + mustEmbedUnimplementedCommandServiceServer() +} + +func RegisterCommandServiceServer(s grpc.ServiceRegistrar, srv CommandServiceServer) { + // If the following call pancis, it indicates UnimplementedCommandServiceServer was + // embedded by pointer and is nil. This will cause panics if an + // unimplemented method is ever invoked, so we test this at initialization + // time to prevent it from happening at runtime later due to I/O. + if t, ok := srv.(interface{ testEmbeddedByValue() }); ok { + t.testEmbeddedByValue() + } + s.RegisterService(&CommandService_ServiceDesc, srv) +} + +func _CommandService_Execute_Handler(srv interface{}, stream grpc.ServerStream) error { + m := new(CommandRequest) + if err := stream.RecvMsg(m); err != nil { + return err + } + return srv.(CommandServiceServer).Execute(m, &grpc.GenericServerStream[CommandRequest, CommandResponse]{ServerStream: stream}) +} + +// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name. +type CommandService_ExecuteServer = grpc.ServerStreamingServer[CommandResponse] + +// CommandService_ServiceDesc is the grpc.ServiceDesc for CommandService service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var CommandService_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "stacksproto1.CommandService", + HandlerType: (*CommandServiceServer)(nil), + Methods: []grpc.MethodDesc{}, + Streams: []grpc.StreamDesc{ + { + StreamName: "Execute", + Handler: _CommandService_Execute_Handler, + ServerStreams: true, + }, + }, + Metadata: "stacksproto1.proto", +}