From 38b1a515f196ba8e6fb18c05424d7cefc607eb13 Mon Sep 17 00:00:00 2001 From: boruszak Date: Tue, 11 Oct 2022 10:10:00 -0500 Subject: [PATCH] Mergimg --- .changelog/12890.txt | 3 + .changelog/14356.txt | 1 + .changelog/14527.txt | 3 + .changelog/14616.txt | 3 + .changelog/14723.txt | 3 + .changelog/14724.txt | 3 + .changelog/14747.txt | 3 + .changelog/14749.txt | 3 + .changelog/14751.txt | 3 + .changelog/14796.txt | 3 + .changelog/14797.txt | 3 + .changelog/14811.txt | 3 + .changelog/14817.txt | 3 + .changelog/14831.txt | 3 + .changelog/14854.txt | 3 + .changelog/14869.txt | 3 + .changelog/14873.txt | 3 + .changelog/14885.txt | 4 + .changelog/14903.txt | 3 + .circleci/config.yml | 6 +- .github/scripts/metrics_checker.sh | 4 +- .github/workflows/changelog-checker.yml | 4 +- .release/ci.hcl | 13 + CHANGELOG.md | 12 + GNUmakefile | 15 +- README.md | 11 +- agent/agent.go | 58 +- agent/agent_endpoint_test.go | 11 +- agent/agent_test.go | 63 + agent/cache-types/mock_PeeringLister_test.go | 63 + agent/cache-types/peerings.go | 107 + agent/cache-types/peerings_test.go | 131 + agent/cache-types/trust_bundle.go | 7 +- agent/cache-types/trust_bundle_test.go | 10 +- agent/cache-types/trust_bundles.go | 7 +- agent/cache-types/trust_bundles_test.go | 1 + agent/config/builder.go | 17 + agent/config/builder_test.go | 18 + agent/config/config.go | 9 + agent/config/runtime.go | 11 + agent/config/runtime_test.go | 89 +- .../TestRuntimeConfig_Sanitize.golden | 7 + agent/config/testdata/full-config.hcl | 7 + agent/config/testdata/full-config.json | 7 + .../merge_service_config.go | 17 +- .../merge_service_config_test.go | 2 +- agent/configentry/resolve.go | 39 +- agent/configentry/resolve_test.go | 20 + agent/connect/testing_ca.go | 26 +- agent/consul/catalog_endpoint.go | 5 +- agent/consul/client_serf.go | 4 +- agent/consul/config_test.go | 1 + agent/consul/grpc_integration_test.go | 8 +- agent/consul/health_endpoint.go | 3 +- agent/consul/internal_endpoint_test.go | 14 +- agent/consul/leader_peering_test.go | 360 +- agent/consul/leader_test.go | 16 +- agent/consul/options.go | 7 +- agent/consul/peering_backend.go | 75 +- agent/consul/peering_backend_oss_test.go | 28 +- agent/consul/peering_backend_test.go | 142 +- agent/consul/prepared_query/walk_test.go | 2 +- agent/consul/prepared_query_endpoint.go | 12 +- agent/consul/prepared_query_endpoint_test.go | 34 +- agent/consul/server.go | 73 + agent/consul/server_serf.go | 4 +- agent/consul/server_test.go | 58 +- agent/consul/servercert/manager.go | 1 - agent/consul/state/config_entry_oss_test.go | 12 +- agent/consul/state/config_entry_test.go | 10 +- agent/consul/state/peering.go | 13 +- agent/consul/state/peering_test.go | 30 +- agent/dns.go | 54 +- agent/dns_test.go | 50 + agent/grpc-external/options.go | 58 + agent/grpc-external/options_test.go | 39 + .../grpc-external/services/connectca/sign.go | 9 +- .../services/connectca/watch_roots.go | 7 +- .../services/connectca/watch_roots_test.go | 16 +- .../dataplane/get_envoy_bootstrap_params.go | 32 +- .../get_envoy_bootstrap_params_test.go | 129 +- .../dataplane/get_supported_features.go | 8 +- .../dataplane/get_supported_features_test.go | 19 +- .../services/dataplane/server.go | 3 + agent/grpc-external/services/dns/server.go | 138 + .../grpc-external/services/dns/server_test.go | 127 + .../services/peerstream/replication.go | 153 +- .../services/peerstream/server.go | 2 + .../services/peerstream/stream_resources.go | 21 +- .../services/peerstream/stream_test.go | 532 +- .../services/peerstream/stream_tracker.go | 43 +- .../peerstream/subscription_blocking.go | 24 +- .../peerstream/subscription_manager.go | 163 +- .../peerstream/subscription_manager_test.go | 188 +- .../services/peerstream/subscription_state.go | 4 + .../services/peerstream/testing.go | 20 + .../services/serverdiscovery/watch_servers.go | 8 +- .../serverdiscovery/watch_servers_test.go | 13 +- agent/grpc-external/token.go | 28 - agent/hcp/bootstrap/bootstrap.go | 305 ++ agent/hcp/bootstrap/testing.go | 160 + agent/hcp/client.go | 217 + agent/hcp/config/config.go | 30 + agent/hcp/deps.go | 23 + agent/hcp/discover/discover.go | 76 + agent/hcp/manager.go | 177 + agent/hcp/manager_test.go | 102 + agent/hcp/mock_Client.go | 167 + agent/hcp/scada/capabilities.go | 6 + agent/hcp/scada/mock_Provider.go | 302 ++ agent/hcp/scada/scada.go | 55 + agent/hcp/testing.go | 177 + agent/hcp/testserver/main.go | 45 + agent/peering_endpoint.go | 41 +- agent/peering_endpoint_test.go | 61 +- .../exported_peered_services_test.go | 12 +- agent/proxycfg-glue/glue.go | 1 + agent/proxycfg-glue/intentions_test.go | 4 +- agent/proxycfg-glue/peering_list.go | 58 + agent/proxycfg-glue/peering_list_test.go | 119 + agent/proxycfg-glue/trust_bundle_test.go | 4 +- agent/proxycfg/connect_proxy.go | 131 +- agent/proxycfg/data_sources.go | 8 + agent/proxycfg/ingress_gateway.go | 5 +- agent/proxycfg/manager_test.go | 2 + agent/proxycfg/mesh_gateway.go | 307 +- agent/proxycfg/snapshot.go | 60 +- agent/proxycfg/state.go | 11 +- agent/proxycfg/state_test.go | 303 +- agent/proxycfg/testing.go | 29 +- agent/proxycfg/testing_ingress_gateway.go | 320 ++ agent/proxycfg/testing_mesh_gateway.go | 214 + agent/proxycfg/testing_peering.go | 128 + agent/proxycfg/testing_tproxy.go | 72 +- agent/proxycfg/testing_upstreams.go | 19 +- agent/proxycfg/upstreams.go | 22 +- agent/retry_join.go | 2 + agent/retry_join_test.go | 2 +- agent/rpc/peering/service.go | 119 +- agent/rpc/peering/service_test.go | 200 +- agent/rpc/peering/validate.go | 4 +- agent/service_manager.go | 6 +- agent/setup.go | 7 + agent/structs/config_entry.go | 48 +- agent/structs/config_entry_discoverychain.go | 32 +- .../config_entry_discoverychain_test.go | 56 + agent/structs/config_entry_export_oss_test.go | 4 +- agent/structs/config_entry_exports.go | 14 +- agent/structs/config_entry_exports_test.go | 8 +- agent/structs/config_entry_gateways.go | 13 + agent/structs/config_entry_mesh.go | 7 + agent/structs/config_entry_mesh_test.go | 46 + agent/structs/config_entry_test.go | 114 +- agent/structs/prepared_query.go | 8 +- agent/structs/structs.go | 24 +- agent/structs/testing_catalog.go | 39 +- agent/testagent.go | 24 +- agent/ui_endpoint_test.go | 2 +- agent/xds/clusters.go | 242 +- agent/xds/clusters_test.go | 39 + agent/xds/config.go | 4 + agent/xds/config_test.go | 11 + agent/xds/delta.go | 7 +- agent/xds/endpoints.go | 159 +- agent/xds/envoy_versioning_test.go | 6 +- agent/xds/listeners.go | 110 +- agent/xds/listeners_ingress.go | 11 +- agent/xds/listeners_test.go | 89 + agent/xds/proxysupport/proxysupport.go | 6 +- agent/xds/resources_test.go | 22 + agent/xds/routes.go | 57 +- agent/xds/server.go | 7 +- ...and-failover-to-cluster-peer.latest.golden | 4 +- ...and-redirect-to-cluster-peer.latest.golden | 4 +- ...and-failover-to-cluster-peer.latest.golden | 4 +- ...ults-service-max-connections.latest.golden | 71 + ...ults-service-max-connections.latest.golden | 70 + ...with-service-max-connections.latest.golden | 69 + ...ateway-with-peered-upstreams.latest.golden | 146 + ...ateway-peering-control-plane.latest.golden | 24 + ...ith-imported-peered-services.latest.golden | 64 + ...through-mesh-gateway-enabled.latest.golden | 54 + ...and-failover-to-cluster-peer.latest.golden | 12 - ...and-redirect-to-cluster-peer.latest.golden | 12 - ...and-failover-to-cluster-peer.latest.golden | 12 - ...ateway-with-peered-upstreams.latest.golden | 51 + ...ateway-peering-control-plane.latest.golden | 37 + ...ith-imported-peered-services.latest.golden | 41 + ...through-mesh-gateway-enabled.latest.golden | 37 + ...ustom-public-listener-http-2.latest.golden | 5 +- ...public-listener-http-missing.latest.golden | 5 +- .../custom-public-listener-http.latest.golden | 5 +- .../custom-trace-listener.latest.golden | 5 +- .../grpc-public-listener.latest.golden | 179 + .../http-listener-with-timeouts.latest.golden | 3 + ...http-public-listener-no-xfcc.latest.golden | 3 + .../http-public-listener.latest.golden | 3 + .../http2-public-listener.latest.golden | 166 + ...ith-grpc-single-tls-listener.latest.golden | 162 + ...d-grpc-multiple-tls-listener.latest.golden | 180 + ...th-http2-single-tls-listener.latest.golden | 136 + ...h-sds-listener+service-level.latest.golden | 6 + ...h-sds-listener-gw-level-http.latest.golden | 3 + ...s-service-level-mixed-no-tls.latest.golden | 3 + ...gress-with-sds-service-level.latest.golden | 6 + ...ess-with-single-tls-listener.latest.golden | 3 + ...n-listeners-gateway-defaults.latest.golden | 15 + ...ess-with-tls-mixed-listeners.latest.golden | 3 + ...-mixed-min-version-listeners.latest.golden | 9 + ...-balance-inbound-connections.latest.golden | 124 + ...tbound-connections-bind-port.latest.golden | 124 + ...ateway-with-peered-upstreams.latest.golden | 119 + ...ateway-peering-control-plane.latest.golden | 62 + ...ith-imported-peered-services.latest.golden | 45 + ...through-mesh-gateway-enabled.latest.golden | 79 + ...h-resolver-redirect-upstream.latest.golden | 176 + ...-proxy-with-chain-and-router.latest.golden | 18 +- ...hain-and-router-header-manip.latest.golden | 18 +- ...ngress-with-chain-and-router.latest.golden | 18 +- ...ateway-with-peered-upstreams.latest.golden | 5 + ...ateway-peering-control-plane.latest.golden | 5 + ...ith-imported-peered-services.latest.golden | 5 + ...through-mesh-gateway-enabled.latest.golden | 5 + api/api.go | 6 + api/api_test.go | 8 + api/config_entry.go | 41 +- api/config_entry_discoverychain.go | 1 + api/config_entry_discoverychain_test.go | 12 + api/config_entry_exports.go | 4 +- api/config_entry_exports_test.go | 2 +- api/config_entry_gateways.go | 13 + api/config_entry_gateways_test.go | 10 + api/config_entry_test.go | 14 +- api/go.mod | 3 +- api/go.sum | 12 +- api/peering.go | 19 +- api/peering_test.go | 7 +- api/prepared_query.go | 4 +- build-support/scripts/devtools.sh | 17 +- command/agent/agent.go | 87 +- command/agent/startup_logger.go | 64 + command/helpers/helpers_test.go | 10 +- command/peering/establish/establish_test.go | 23 +- command/peering/generate/generate_test.go | 10 +- command/peering/list/list.go | 5 +- command/peering/read/read.go | 9 +- command/peering/read/read_test.go | 3 + docs/service-discovery/health-checks.md | 5 +- go.mod | 51 +- go.sum | 303 +- lib/retry/retry.go | 11 +- proto-public/pbdns/dns.pb.binary.go | 28 + proto-public/pbdns/dns.pb.go | 298 ++ proto-public/pbdns/dns.proto | 28 + proto-public/pbdns/dns_grpc.pb.go | 105 + proto-public/pbdns/mock_DNSServiceClient.go | 58 + proto-public/pbdns/mock_DNSServiceServer.go | 48 + .../pbdns/mock_UnsafeDNSServiceServer.go | 29 + proto/pbconfigentry/config_entry.gen.go | 32 + proto/pbconfigentry/config_entry.pb.binary.go | 10 + proto/pbconfigentry/config_entry.pb.go | 705 ++- proto/pbconfigentry/config_entry.proto | 15 + proto/pbpeering/peering.gen.go | 6 +- proto/pbpeering/peering.go | 20 + proto/pbpeering/peering.pb.binary.go | 10 + proto/pbpeering/peering.pb.go | 1028 ++-- proto/pbpeering/peering.proto | 28 +- proto/pbpeerstream/convert.go | 12 + proto/pbpeerstream/peerstream.pb.binary.go | 10 + proto/pbpeerstream/peerstream.pb.go | 262 +- proto/pbpeerstream/peerstream.proto | 11 +- proto/pbpeerstream/types.go | 3 +- sdk/testutil/server.go | 26 +- .../alpha/config_entries.hcl | 2 +- .../config_entries.hcl | 1 + .../alpha/base.hcl | 5 + .../alpha/config_entries.hcl | 26 + .../alpha/service_gateway.hcl | 5 + .../alpha/service_s1.hcl | 1 + .../alpha/service_s2.hcl | 10 + .../alpha/setup.sh | 11 + .../alpha/verify.bats | 27 + .../bind.hcl | 2 + .../capture.sh | 6 + .../primary/base.hcl | 3 + .../primary/config_entries.hcl | 53 + .../primary/service_s1.hcl | 22 + .../primary/service_s2.hcl | 10 + .../primary/setup.sh | 10 + .../primary/verify.bats | 74 + .../case-cfg-splitter-cluster-peering/vars.sh | 4 + .../alpha/base.hcl | 5 + .../alpha/config_entries.hcl | 34 + .../alpha/service_gateway.hcl | 5 + .../alpha/service_s1.hcl | 10 + .../alpha/service_s2.hcl | 10 + .../alpha/setup.sh | 12 + .../alpha/verify.bats | 34 + .../bind.hcl | 2 + .../capture.sh | 7 + .../primary/base.hcl | 3 + .../primary/config_entries.hcl | 88 + .../primary/service_ingress.hcl | 4 + .../primary/setup.sh | 14 + .../primary/verify.bats | 70 + .../vars.sh | 4 + .../alpha/config_entries.hcl | 2 +- .../alpha/config_entries.hcl | 2 +- .../alpha/config_entries.hcl | 2 +- .../case-cross-peers/alpha/config_entries.hcl | 2 +- .../case-cross-peers/primary/service_s1.hcl | 3 + .../case-cross-peers/primary/verify.bats | 4 + .../config_entries.hcl | 8 + .../verify.bats | 21 +- .../alpha/base.hcl | 5 + .../alpha/config_entries.hcl | 26 + .../alpha/service_gateway.hcl | 5 + .../alpha/service_s1.hcl | 1 + .../alpha/service_s2.hcl | 10 + .../alpha/setup.sh | 11 + .../alpha/verify.bats | 27 + .../bind.hcl | 2 + .../capture.sh | 6 + .../primary/base.hcl | 3 + .../primary/config_entries.hcl | 47 + .../primary/service_ingress.hcl | 4 + .../primary/service_s1.hcl | 1 + .../primary/service_s2.hcl | 10 + .../primary/setup.sh | 13 + .../primary/verify.bats | 77 + .../vars.sh | 4 + .../config_entries.hcl | 8 +- .../case-ingress-gateway-simple/verify.bats | 19 +- .../connect/envoy/consul-base-cfg/base.hcl | 2 +- .../envoy/consul-base-cfg/peering_server.hcl | 6 + test/integration/connect/envoy/helpers.bash | 48 +- test/integration/connect/envoy/run-tests.sh | 21 +- test/integration/consul-container/go.mod | 6 +- test/integration/consul-container/go.sum | 25 +- ui/{packages/consul-ui => }/.nvmrc | 0 .../app/templates/dc/nodes/show/sessions.hbs | 1 + .../app/templates/dc/nspaces/index.hbs | 15 +- .../app/templates/dc/partitions/index.hbs | 15 +- .../app/components/consul/peer/list/index.hbs | 2 +- .../consul/peer/list/test-support.js | 3 +- .../app/templates/dc/peers/index.hbs | 24 +- ui/packages/consul-ui/.eslintrc.js | 1 + .../app/components/app-view/index.hbs | 16 +- .../app/components/app-view/index.scss | 3 + .../app/components/app-view/layout.scss | 33 +- .../app/components/app-view/skin.scss | 2 +- .../app/components/breadcrumbs/layout.scss | 10 + .../consul/datacenter/selector/index.hbs | 81 +- ui/packages/consul-ui/app/styles/app.scss | 6 + .../consul-ui/app/styles/tailwind.scss | 3 + .../app/templates/dc/intentions/index.hbs | 1 + .../consul-ui/app/templates/dc/kv/edit.hbs | 3 +- .../consul-ui/app/templates/dc/kv/index.hbs | 5 +- .../app/templates/dc/nodes/index.hbs | 15 +- .../app/templates/dc/services/index.hbs | 1 + .../app/templates/dc/services/instance.hbs | 16 +- .../dc/services/show/intentions/index.hbs | 1 + ui/packages/consul-ui/config/environment.js | 1 + ui/packages/consul-ui/docs/hds.mdx | 102 + ui/packages/consul-ui/ember-cli-build.js | 47 + .../lib/startup/templates/body.html.js | 1 + .../consul-ui/mock-api/v1/health/service/_ | 26 +- ui/packages/consul-ui/package.json | 21 +- ui/packages/consul-ui/tailwind.config.js | 45 + .../acceptance/dc/intentions/index.feature | 25 + .../tests/acceptance/dc/nodes/index.feature | 13 + .../acceptance/dc/nodes/sessions/list.feature | 33 + .../tests/acceptance/dc/peers/create.feature | 43 + .../tests/acceptance/dc/peers/delete.feature | 47 + .../acceptance/dc/peers/establish.feature | 30 + .../tests/acceptance/dc/peers/index.feature | 19 +- .../acceptance/dc/peers/regenerate.feature | 21 + .../acceptance/dc/services/index.feature | 25 + .../dc/services/instances/show.feature | 23 +- .../acceptance/steps/dc/peers/create-steps.js | 10 + .../acceptance/steps/dc/peers/delete-steps.js | 10 + .../steps/dc/peers/establish-steps.js | 10 + .../steps/dc/peers/regenerate-steps.js | 10 + ui/packages/consul-ui/tests/pages.js | 2 +- .../consul-ui/translations/routes/en-us.yaml | 131 +- ui/yarn.lock | 1753 ++++++- website/content/api-docs/catalog.mdx | 4 +- website/content/api-docs/query.mdx | 2 +- .../docs/agent/config/config-files.mdx | 1 + website/content/docs/agent/telemetry.mdx | 10 +- .../content/docs/api-gateway/tech-specs.mdx | 2 +- website/content/docs/architecture/index.mdx | 187 +- website/content/docs/connect/ca/vault.mdx | 10 + .../cluster-peering/create-manage-peering.mdx | 187 +- .../docs/connect/cluster-peering/index.mdx | 20 +- .../docs/connect/cluster-peering/k8s.mdx | 105 +- .../config-entries/exported-services.mdx | 46 +- .../config-entries/ingress-gateway.mdx | 52 + .../config-entries/service-defaults.mdx | 27 + .../connect/config-entries/service-router.mdx | 18 +- .../connect/dataplane/consul-dataplane.mdx | 130 + .../content/docs/connect/dataplane/index.mdx | 78 + .../service-to-service-traffic-peers.mdx | 2 +- .../content/docs/connect/proxies/envoy.mdx | 101 +- .../consul-vs-other/api-gateway-compare.mdx | 16 + .../config-management-compare.mdx | 23 + .../consul-vs-other/dns-tools-compare.mdx | 16 + .../content/docs/consul-vs-other/index.mdx | 15 + .../consul-vs-other/service-mesh-compare.mdx | 18 + website/content/docs/ecs/index.mdx | 2 +- .../docs/enterprise/admin-partitions.mdx | 13 +- .../content/docs/enterprise/license/faq.mdx | 12 +- .../content/docs/integrate/partnerships.mdx | 79 +- .../data-integration/bootstrap-token.mdx | 2 +- .../vault/data-integration/connect-ca.mdx | 2 +- .../data-integration/enterprise-license.mdx | 4 +- .../vault/data-integration/gossip.mdx | 4 +- .../data-integration/partition-token.mdx | 11 +- .../vault/data-integration/server-tls.mdx | 4 +- .../snapshot-agent-config.mdx | 2 +- .../vault/data-integration/webhook-certs.mdx | 4 +- .../vault/systems-integration.mdx | 4 +- .../vault/wan-federation.mdx | 2 +- website/content/docs/k8s/helm.mdx | 44 +- .../docs/k8s/installation/install-cli.mdx | 1 + .../content/docs/k8s/installation/install.mdx | 22 +- website/content/docs/lambda/index.mdx | 36 +- website/content/docs/lambda/invocation.mdx | 2 +- .../docs/lambda/invoke-from-lambda.mdx | 273 + .../docs/lambda/registration/automate.mdx | 190 + .../docs/lambda/registration/manual.mdx | 84 + .../docs/release-notes/consul-k8s/v0_49_x.mdx | 46 + .../docs/upgrading/upgrade-specific.mdx | 52 +- website/package-lock.json | 4611 ++--------------- website/package.json | 2 +- website/public/img/consul-arch.png | Bin 115945 -> 0 bytes .../consul-arch-overview-consensus.svg | 1 + .../consul-arch-overview-control-plane.svg | 1 + .../consul-arch-overview-lan-gossip-pool.svg | 1 + ...iew-remote-dc-forwarding-cross-cluster.svg | 1 + .../consul-arch/consul-arch-overview-rpc.svg | 1 + ...arch-overview-wan-gossip-cross-cluster.svg | 1 + .../public/img/consul_ecosystem_diagram2.png | Bin 68144 -> 69365 bytes .../img/invoke-service-from-lambda-flow.svg | 1 + 444 files changed, 18261 insertions(+), 6945 deletions(-) create mode 100644 .changelog/12890.txt create mode 100644 .changelog/14356.txt create mode 100644 .changelog/14527.txt create mode 100644 .changelog/14616.txt create mode 100644 .changelog/14723.txt create mode 100644 .changelog/14724.txt create mode 100644 .changelog/14747.txt create mode 100644 .changelog/14749.txt create mode 100644 .changelog/14751.txt create mode 100644 .changelog/14796.txt create mode 100644 .changelog/14797.txt create mode 100644 .changelog/14811.txt create mode 100644 .changelog/14817.txt create mode 100644 .changelog/14831.txt create mode 100644 .changelog/14854.txt create mode 100644 .changelog/14869.txt create mode 100644 .changelog/14873.txt create mode 100644 .changelog/14885.txt create mode 100644 .changelog/14903.txt create mode 100644 agent/cache-types/mock_PeeringLister_test.go create mode 100644 agent/cache-types/peerings.go create mode 100644 agent/cache-types/peerings_test.go rename agent/{consul => configentry}/merge_service_config.go (94%) rename agent/{consul => configentry}/merge_service_config_test.go (99%) create mode 100644 agent/grpc-external/options.go create mode 100644 agent/grpc-external/options_test.go create mode 100644 agent/grpc-external/services/dns/server.go create mode 100644 agent/grpc-external/services/dns/server_test.go delete mode 100644 agent/grpc-external/token.go create mode 100644 agent/hcp/bootstrap/bootstrap.go create mode 100644 agent/hcp/bootstrap/testing.go create mode 100644 agent/hcp/client.go create mode 100644 agent/hcp/config/config.go create mode 100644 agent/hcp/deps.go create mode 100644 agent/hcp/discover/discover.go create mode 100644 agent/hcp/manager.go create mode 100644 agent/hcp/manager_test.go create mode 100644 agent/hcp/mock_Client.go create mode 100644 agent/hcp/scada/capabilities.go create mode 100644 agent/hcp/scada/mock_Provider.go create mode 100644 agent/hcp/scada/scada.go create mode 100644 agent/hcp/testing.go create mode 100644 agent/hcp/testserver/main.go create mode 100644 agent/proxycfg-glue/peering_list.go create mode 100644 agent/proxycfg-glue/peering_list_test.go create mode 100644 agent/structs/config_entry_mesh_test.go create mode 100644 agent/xds/testdata/clusters/ingress-with-defaults-service-max-connections.latest.golden create mode 100644 agent/xds/testdata/clusters/ingress-with-overwrite-defaults-service-max-connections.latest.golden create mode 100644 agent/xds/testdata/clusters/ingress-with-service-max-connections.latest.golden create mode 100644 agent/xds/testdata/clusters/local-mesh-gateway-with-peered-upstreams.latest.golden create mode 100644 agent/xds/testdata/clusters/mesh-gateway-peering-control-plane.latest.golden create mode 100644 agent/xds/testdata/clusters/mesh-gateway-with-imported-peered-services.latest.golden create mode 100644 agent/xds/testdata/clusters/mesh-gateway-with-peer-through-mesh-gateway-enabled.latest.golden create mode 100644 agent/xds/testdata/endpoints/local-mesh-gateway-with-peered-upstreams.latest.golden create mode 100644 agent/xds/testdata/endpoints/mesh-gateway-peering-control-plane.latest.golden create mode 100644 agent/xds/testdata/endpoints/mesh-gateway-with-imported-peered-services.latest.golden create mode 100644 agent/xds/testdata/endpoints/mesh-gateway-with-peer-through-mesh-gateway-enabled.latest.golden create mode 100644 agent/xds/testdata/listeners/grpc-public-listener.latest.golden create mode 100644 agent/xds/testdata/listeners/http2-public-listener.latest.golden create mode 100644 agent/xds/testdata/listeners/ingress-with-grpc-single-tls-listener.latest.golden create mode 100644 agent/xds/testdata/listeners/ingress-with-http2-and-grpc-multiple-tls-listener.latest.golden create mode 100644 agent/xds/testdata/listeners/ingress-with-http2-single-tls-listener.latest.golden create mode 100644 agent/xds/testdata/listeners/listener-balance-inbound-connections.latest.golden create mode 100644 agent/xds/testdata/listeners/listener-balance-outbound-connections-bind-port.latest.golden create mode 100644 agent/xds/testdata/listeners/local-mesh-gateway-with-peered-upstreams.latest.golden create mode 100644 agent/xds/testdata/listeners/mesh-gateway-peering-control-plane.latest.golden create mode 100644 agent/xds/testdata/listeners/mesh-gateway-with-imported-peered-services.latest.golden create mode 100644 agent/xds/testdata/listeners/mesh-gateway-with-peer-through-mesh-gateway-enabled.latest.golden create mode 100644 agent/xds/testdata/listeners/transparent-proxy-with-resolver-redirect-upstream.latest.golden create mode 100644 agent/xds/testdata/routes/local-mesh-gateway-with-peered-upstreams.latest.golden create mode 100644 agent/xds/testdata/routes/mesh-gateway-peering-control-plane.latest.golden create mode 100644 agent/xds/testdata/routes/mesh-gateway-with-imported-peered-services.latest.golden create mode 100644 agent/xds/testdata/routes/mesh-gateway-with-peer-through-mesh-gateway-enabled.latest.golden create mode 100644 command/agent/startup_logger.go create mode 100644 proto-public/pbdns/dns.pb.binary.go create mode 100644 proto-public/pbdns/dns.pb.go create mode 100644 proto-public/pbdns/dns.proto create mode 100644 proto-public/pbdns/dns_grpc.pb.go create mode 100644 proto-public/pbdns/mock_DNSServiceClient.go create mode 100644 proto-public/pbdns/mock_DNSServiceServer.go create mode 100644 proto-public/pbdns/mock_UnsafeDNSServiceServer.go create mode 100644 test/integration/connect/envoy/case-cfg-splitter-cluster-peering/alpha/base.hcl create mode 100644 test/integration/connect/envoy/case-cfg-splitter-cluster-peering/alpha/config_entries.hcl create mode 100644 test/integration/connect/envoy/case-cfg-splitter-cluster-peering/alpha/service_gateway.hcl create mode 100644 test/integration/connect/envoy/case-cfg-splitter-cluster-peering/alpha/service_s1.hcl create mode 100644 test/integration/connect/envoy/case-cfg-splitter-cluster-peering/alpha/service_s2.hcl create mode 100644 test/integration/connect/envoy/case-cfg-splitter-cluster-peering/alpha/setup.sh create mode 100644 test/integration/connect/envoy/case-cfg-splitter-cluster-peering/alpha/verify.bats create mode 100644 test/integration/connect/envoy/case-cfg-splitter-cluster-peering/bind.hcl create mode 100644 test/integration/connect/envoy/case-cfg-splitter-cluster-peering/capture.sh create mode 100644 test/integration/connect/envoy/case-cfg-splitter-cluster-peering/primary/base.hcl create mode 100644 test/integration/connect/envoy/case-cfg-splitter-cluster-peering/primary/config_entries.hcl create mode 100644 test/integration/connect/envoy/case-cfg-splitter-cluster-peering/primary/service_s1.hcl create mode 100644 test/integration/connect/envoy/case-cfg-splitter-cluster-peering/primary/service_s2.hcl create mode 100644 test/integration/connect/envoy/case-cfg-splitter-cluster-peering/primary/setup.sh create mode 100644 test/integration/connect/envoy/case-cfg-splitter-cluster-peering/primary/verify.bats create mode 100644 test/integration/connect/envoy/case-cfg-splitter-cluster-peering/vars.sh create mode 100644 test/integration/connect/envoy/case-cfg-splitter-peering-ingress-gateways/alpha/base.hcl create mode 100644 test/integration/connect/envoy/case-cfg-splitter-peering-ingress-gateways/alpha/config_entries.hcl create mode 100644 test/integration/connect/envoy/case-cfg-splitter-peering-ingress-gateways/alpha/service_gateway.hcl create mode 100644 test/integration/connect/envoy/case-cfg-splitter-peering-ingress-gateways/alpha/service_s1.hcl create mode 100644 test/integration/connect/envoy/case-cfg-splitter-peering-ingress-gateways/alpha/service_s2.hcl create mode 100644 test/integration/connect/envoy/case-cfg-splitter-peering-ingress-gateways/alpha/setup.sh create mode 100644 test/integration/connect/envoy/case-cfg-splitter-peering-ingress-gateways/alpha/verify.bats create mode 100644 test/integration/connect/envoy/case-cfg-splitter-peering-ingress-gateways/bind.hcl create mode 100644 test/integration/connect/envoy/case-cfg-splitter-peering-ingress-gateways/capture.sh create mode 100644 test/integration/connect/envoy/case-cfg-splitter-peering-ingress-gateways/primary/base.hcl create mode 100644 test/integration/connect/envoy/case-cfg-splitter-peering-ingress-gateways/primary/config_entries.hcl create mode 100644 test/integration/connect/envoy/case-cfg-splitter-peering-ingress-gateways/primary/service_ingress.hcl create mode 100644 test/integration/connect/envoy/case-cfg-splitter-peering-ingress-gateways/primary/setup.sh create mode 100644 test/integration/connect/envoy/case-cfg-splitter-peering-ingress-gateways/primary/verify.bats create mode 100644 test/integration/connect/envoy/case-cfg-splitter-peering-ingress-gateways/vars.sh create mode 100644 test/integration/connect/envoy/case-ingress-gateway-peering-failover/alpha/base.hcl create mode 100644 test/integration/connect/envoy/case-ingress-gateway-peering-failover/alpha/config_entries.hcl create mode 100644 test/integration/connect/envoy/case-ingress-gateway-peering-failover/alpha/service_gateway.hcl create mode 100644 test/integration/connect/envoy/case-ingress-gateway-peering-failover/alpha/service_s1.hcl create mode 100644 test/integration/connect/envoy/case-ingress-gateway-peering-failover/alpha/service_s2.hcl create mode 100644 test/integration/connect/envoy/case-ingress-gateway-peering-failover/alpha/setup.sh create mode 100644 test/integration/connect/envoy/case-ingress-gateway-peering-failover/alpha/verify.bats create mode 100644 test/integration/connect/envoy/case-ingress-gateway-peering-failover/bind.hcl create mode 100644 test/integration/connect/envoy/case-ingress-gateway-peering-failover/capture.sh create mode 100644 test/integration/connect/envoy/case-ingress-gateway-peering-failover/primary/base.hcl create mode 100644 test/integration/connect/envoy/case-ingress-gateway-peering-failover/primary/config_entries.hcl create mode 100644 test/integration/connect/envoy/case-ingress-gateway-peering-failover/primary/service_ingress.hcl create mode 100644 test/integration/connect/envoy/case-ingress-gateway-peering-failover/primary/service_s1.hcl create mode 100644 test/integration/connect/envoy/case-ingress-gateway-peering-failover/primary/service_s2.hcl create mode 100644 test/integration/connect/envoy/case-ingress-gateway-peering-failover/primary/setup.sh create mode 100644 test/integration/connect/envoy/case-ingress-gateway-peering-failover/primary/verify.bats create mode 100644 test/integration/connect/envoy/case-ingress-gateway-peering-failover/vars.sh create mode 100644 test/integration/connect/envoy/consul-base-cfg/peering_server.hcl rename ui/{packages/consul-ui => }/.nvmrc (100%) create mode 100644 ui/packages/consul-ui/app/styles/tailwind.scss create mode 100644 ui/packages/consul-ui/docs/hds.mdx create mode 100644 ui/packages/consul-ui/tailwind.config.js create mode 100644 ui/packages/consul-ui/tests/acceptance/dc/peers/create.feature create mode 100644 ui/packages/consul-ui/tests/acceptance/dc/peers/delete.feature create mode 100644 ui/packages/consul-ui/tests/acceptance/dc/peers/establish.feature create mode 100644 ui/packages/consul-ui/tests/acceptance/dc/peers/regenerate.feature create mode 100644 ui/packages/consul-ui/tests/acceptance/steps/dc/peers/create-steps.js create mode 100644 ui/packages/consul-ui/tests/acceptance/steps/dc/peers/delete-steps.js create mode 100644 ui/packages/consul-ui/tests/acceptance/steps/dc/peers/establish-steps.js create mode 100644 ui/packages/consul-ui/tests/acceptance/steps/dc/peers/regenerate-steps.js create mode 100644 website/content/docs/connect/dataplane/consul-dataplane.mdx create mode 100644 website/content/docs/connect/dataplane/index.mdx create mode 100644 website/content/docs/consul-vs-other/api-gateway-compare.mdx create mode 100644 website/content/docs/consul-vs-other/config-management-compare.mdx create mode 100644 website/content/docs/consul-vs-other/dns-tools-compare.mdx create mode 100644 website/content/docs/consul-vs-other/index.mdx create mode 100644 website/content/docs/consul-vs-other/service-mesh-compare.mdx create mode 100644 website/content/docs/lambda/invoke-from-lambda.mdx create mode 100644 website/content/docs/lambda/registration/automate.mdx create mode 100644 website/content/docs/lambda/registration/manual.mdx create mode 100644 website/content/docs/release-notes/consul-k8s/v0_49_x.mdx delete mode 100644 website/public/img/consul-arch.png create mode 100644 website/public/img/consul-arch/consul-arch-overview-consensus.svg create mode 100644 website/public/img/consul-arch/consul-arch-overview-control-plane.svg create mode 100644 website/public/img/consul-arch/consul-arch-overview-lan-gossip-pool.svg create mode 100644 website/public/img/consul-arch/consul-arch-overview-remote-dc-forwarding-cross-cluster.svg create mode 100644 website/public/img/consul-arch/consul-arch-overview-rpc.svg create mode 100644 website/public/img/consul-arch/consul-arch-overview-wan-gossip-cross-cluster.svg create mode 100644 website/public/img/invoke-service-from-lambda-flow.svg diff --git a/.changelog/12890.txt b/.changelog/12890.txt new file mode 100644 index 0000000000..4f707e3b61 --- /dev/null +++ b/.changelog/12890.txt @@ -0,0 +1,3 @@ +```release-note:improvement +connect: service-router destinations have gained a `RetryOn` field for specifying the conditions when Envoy should retry requests beyond specific status codes and generic connection failure which already exists. +``` diff --git a/.changelog/14356.txt b/.changelog/14356.txt new file mode 100644 index 0000000000..ffbcb00030 --- /dev/null +++ b/.changelog/14356.txt @@ -0,0 +1 @@ +xds: configure Envoy `alpn_protocols` for connect-proxy and ingress-gateway based on service protocol. diff --git a/.changelog/14527.txt b/.changelog/14527.txt new file mode 100644 index 0000000000..572f533bd7 --- /dev/null +++ b/.changelog/14527.txt @@ -0,0 +1,3 @@ +```release-note:improvement +ui: Improve guidance around topology visualisation +``` diff --git a/.changelog/14616.txt b/.changelog/14616.txt new file mode 100644 index 0000000000..979f4fad4d --- /dev/null +++ b/.changelog/14616.txt @@ -0,0 +1,3 @@ +```release-note:feature +connect: Add Envoy connection balancing configuration fields. +``` diff --git a/.changelog/14723.txt b/.changelog/14723.txt new file mode 100644 index 0000000000..e162bd8e15 --- /dev/null +++ b/.changelog/14723.txt @@ -0,0 +1,3 @@ +```release-note:improvement +agent/hcp: add initial HashiCorp Cloud Platform integration +``` diff --git a/.changelog/14724.txt b/.changelog/14724.txt new file mode 100644 index 0000000000..256e12b7df --- /dev/null +++ b/.changelog/14724.txt @@ -0,0 +1,3 @@ +```release-note:feature +peering: Add support for stale queries for trust bundle lookups +``` \ No newline at end of file diff --git a/.changelog/14747.txt b/.changelog/14747.txt new file mode 100644 index 0000000000..7a5e84dc2c --- /dev/null +++ b/.changelog/14747.txt @@ -0,0 +1,3 @@ +```release-note:improvement +peering: return information about the health of the peering when the leader is queried to read a peering. +``` \ No newline at end of file diff --git a/.changelog/14749.txt b/.changelog/14749.txt new file mode 100644 index 0000000000..6cee0e9881 --- /dev/null +++ b/.changelog/14749.txt @@ -0,0 +1,3 @@ +```release-note:feature +config-entry(ingress-gateway): Added support for `max_connections` for upstream clusters +``` diff --git a/.changelog/14751.txt b/.changelog/14751.txt new file mode 100644 index 0000000000..5409229f0e --- /dev/null +++ b/.changelog/14751.txt @@ -0,0 +1,3 @@ +```release-note:bug +connect: Fixed a bug where transparent proxy does not correctly spawn listeners for upstreams to service-resolvers. +``` \ No newline at end of file diff --git a/.changelog/14796.txt b/.changelog/14796.txt new file mode 100644 index 0000000000..d39552c857 --- /dev/null +++ b/.changelog/14796.txt @@ -0,0 +1,3 @@ +```release-note:improvement +peering: require TLS for peering connections using server cert signed by Connect CA +``` diff --git a/.changelog/14797.txt b/.changelog/14797.txt new file mode 100644 index 0000000000..cd58394ffa --- /dev/null +++ b/.changelog/14797.txt @@ -0,0 +1,3 @@ +```release-note:feature +peering: Ensure un-exported services get deleted even if the un-export happens while cluster peering replication is down. +``` diff --git a/.changelog/14811.txt b/.changelog/14811.txt new file mode 100644 index 0000000000..bc1f5547ed --- /dev/null +++ b/.changelog/14811.txt @@ -0,0 +1,3 @@ +```release-note:feature +DNS-proxy support via gRPC request. +``` diff --git a/.changelog/14817.txt b/.changelog/14817.txt new file mode 100644 index 0000000000..7a695c164c --- /dev/null +++ b/.changelog/14817.txt @@ -0,0 +1,3 @@ +```release-note:feature +peering: Add mesh gateway local mode support for cluster peering. +``` diff --git a/.changelog/14831.txt b/.changelog/14831.txt new file mode 100644 index 0000000000..457284ac77 --- /dev/null +++ b/.changelog/14831.txt @@ -0,0 +1,3 @@ +```release-note:improvement +connect: Bump Envoy 1.20 to 1.20.7, 1.21 to 1.21.5 and 1.22 to 1.22.5 +``` diff --git a/.changelog/14854.txt b/.changelog/14854.txt new file mode 100644 index 0000000000..87a8941ec4 --- /dev/null +++ b/.changelog/14854.txt @@ -0,0 +1,3 @@ +```release-note:breaking-change +peering: Rename `PeerName` to `Peer` on prepared queries and exported services. +``` diff --git a/.changelog/14869.txt b/.changelog/14869.txt new file mode 100644 index 0000000000..58dafb62e2 --- /dev/null +++ b/.changelog/14869.txt @@ -0,0 +1,3 @@ +```release-note:bug +grpc: Merge proxy-defaults and service-defaults in GetEnvoyBootstrapParams response. +``` diff --git a/.changelog/14873.txt b/.changelog/14873.txt new file mode 100644 index 0000000000..0ff7bf64fc --- /dev/null +++ b/.changelog/14873.txt @@ -0,0 +1,3 @@ +```release-note:feature +telemetry: emit memberlist size metrics and broadcast queue depth metric. +``` diff --git a/.changelog/14885.txt b/.changelog/14885.txt new file mode 100644 index 0000000000..532f1b2d49 --- /dev/null +++ b/.changelog/14885.txt @@ -0,0 +1,4 @@ +```release-note:bug +checks: Fixed a bug that prevented registration of UDP health checks from agent configuration files, such as service definition files with embedded health check definitions. +``` + diff --git a/.changelog/14903.txt b/.changelog/14903.txt new file mode 100644 index 0000000000..b55f2006a9 --- /dev/null +++ b/.changelog/14903.txt @@ -0,0 +1,3 @@ +```release-note:feature +ui: Removed reference to node name on service instance page when using agentless +``` diff --git a/.circleci/config.yml b/.circleci/config.yml index f246c3c297..035498ded4 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -24,9 +24,9 @@ references: VAULT_BINARY_VERSION: 1.9.4 GO_VERSION: 1.18.1 envoy-versions: &supported_envoy_versions - - &default_envoy_version "1.20.6" - - "1.21.4" - - "1.22.2" + - &default_envoy_version "1.20.7" + - "1.21.5" + - "1.22.5" - "1.23.1" nomad-versions: &supported_nomad_versions - &default_nomad_version "1.3.3" diff --git a/.github/scripts/metrics_checker.sh b/.github/scripts/metrics_checker.sh index e9924b1c22..f68c85ed1a 100755 --- a/.github/scripts/metrics_checker.sh +++ b/.github/scripts/metrics_checker.sh @@ -6,7 +6,7 @@ set -uo pipefail ### It is still up to the reviewer to make sure that any tests added are needed and meaningful. # search for any "new" or modified metric emissions -metrics_modified=$(git --no-pager diff origin/main...HEAD | grep -i "SetGauge\|EmitKey\|IncrCounter\|AddSample\|MeasureSince\|UpdateFilter") +metrics_modified=$(git --no-pager diff origin/main...HEAD | grep -i "SetGauge\|EmitKey\|IncrCounter\|AddSample\|MeasureSince\|UpdateFilter" | grep "^[+-]") # search for PR body or title metric references metrics_in_pr_body=$(echo "${PR_BODY-""}" | grep -i "metric") metrics_in_pr_title=$(echo "${PR_TITLE-""}" | grep -i "metric") @@ -15,7 +15,7 @@ metrics_in_pr_title=$(echo "${PR_TITLE-""}" | grep -i "metric") if [ "$metrics_modified" ] || [ "$metrics_in_pr_body" ] || [ "$metrics_in_pr_title" ]; then # need to check if there are modifications to metrics_test test_files_regex="*_test.go" - modified_metrics_test_files=$(git --no-pager diff HEAD "$(git merge-base HEAD "origin/main")" -- "$test_files_regex" | grep -i "metric") + modified_metrics_test_files=$(git --no-pager diff HEAD "$(git merge-base HEAD "origin/main")" -- "$test_files_regex" | grep -i "metric" | grep "^[+-]") if [ "$modified_metrics_test_files" ]; then # 1 happy path: metrics_test has been modified bc we modified metrics behavior echo "PR seems to modify metrics behavior. It seems it may have added tests to the metrics as well." diff --git a/.github/workflows/changelog-checker.yml b/.github/workflows/changelog-checker.yml index 9ee065e101..c4f467a3e0 100644 --- a/.github/workflows/changelog-checker.yml +++ b/.github/workflows/changelog-checker.yml @@ -25,11 +25,9 @@ jobs: fetch-depth: 0 # by default the checkout action doesn't checkout all branches - name: Check for changelog entry in diff run: | - pull_request_base_main=$(expr "${{ github.event.pull_request.base.ref }}" = "main") - # check if there is a diff in the .changelog directory # for PRs against the main branch, the changelog file name should match the PR number - if [ pull_request_base_main ]; then + if [ "${{ github.event.pull_request.base.ref }}" = "${{ github.event.repository.default_branch }}" ]; then enforce_matching_pull_request_number="matching this PR number " changelog_file_path=".changelog/${{ github.event.pull_request.number }}.txt" else diff --git a/.release/ci.hcl b/.release/ci.hcl index 9bbe6e7068..ea205acd11 100644 --- a/.release/ci.hcl +++ b/.release/ci.hcl @@ -275,3 +275,16 @@ event "post-publish-website" { on = "always" } } + +event "update-ironbank" { + depends = ["post-publish-website"] + action "update-ironbank" { + organization = "hashicorp" + repository = "crt-workflows-common" + workflow = "update-ironbank" + } + + notification { + on = "fail" + } +} diff --git a/CHANGELOG.md b/CHANGELOG.md index 9814bf0d18..be4b55dab5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,10 @@ BUG FIXES: ## 1.13.2 (September 20, 2022) +BREAKING CHANGES: + +* ca: If using Vault as the service mesh CA provider, the Vault policy used by Consul now requires the `update` capability on the intermediate PKI's tune mount configuration endpoint, such as `/sys/mounts/connect_inter/tune`. The breaking nature of this change will be resolved in an upcoming 1.13 patch release. Refer to [upgrade guidance](https://www.consul.io/docs/upgrading/upgrade-specific#modify-vault-policy-for-vault-ca-provider) for more information. + SECURITY: * auto-config: Added input validation for auto-config JWT authorization checks. Prior to this change, it was possible for malicious actors to construct requests which incorrectly pass custom JWT claim validation for the `AutoConfig.InitialConfiguration` endpoint. Now, only a subset of characters are allowed for the input before evaluating the bexpr. [[GH-14577](https://github.com/hashicorp/consul/issues/14577)] @@ -48,6 +52,10 @@ BUG FIXES: ## 1.12.5 (September 20, 2022) +BREAKING CHANGES: + +* ca: If using Vault as the service mesh CA provider, the Vault policy used by Consul now requires the `update` capability on the intermediate PKI's tune mount configuration endpoint, such as `/sys/mounts/connect_inter/tune`. The breaking nature of this change will be resolved in an upcoming 1.12 patch release. Refer to [upgrade guidance](https://www.consul.io/docs/upgrading/upgrade-specific#modify-vault-policy-for-vault-ca-provider) for more information. + SECURITY: * auto-config: Added input validation for auto-config JWT authorization checks. Prior to this change, it was possible for malicious actors to construct requests which incorrectly pass custom JWT claim validation for the `AutoConfig.InitialConfiguration` endpoint. Now, only a subset of characters are allowed for the input before evaluating the bexpr. [[GH-14577](https://github.com/hashicorp/consul/issues/14577)] @@ -72,6 +80,10 @@ BUG FIXES: ## 1.11.9 (September 20, 2022) +BREAKING CHANGES: + +* ca: If using Vault as the service mesh CA provider, the Vault policy used by Consul now requires the `update` capability on the intermediate PKI's tune mount configuration endpoint, such as `/sys/mounts/connect_inter/tune`. The breaking nature of this change will be resolved in an upcoming 1.11 patch release. Refer to [upgrade guidance](https://www.consul.io/docs/upgrading/upgrade-specific#modify-vault-policy-for-vault-ca-provider) for more information. + SECURITY: * auto-config: Added input validation for auto-config JWT authorization checks. Prior to this change, it was possible for malicious actors to construct requests which incorrectly pass custom JWT claim validation for the `AutoConfig.InitialConfiguration` endpoint. Now, only a subset of characters are allowed for the input before evaluating the bexpr. [[GH-14577](https://github.com/hashicorp/consul/issues/14577)] diff --git a/GNUmakefile b/GNUmakefile index f9dd160811..2ea50833f2 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -14,6 +14,8 @@ PROTOC_GEN_GO_GRPC_VERSION="v1.2.0" MOG_VERSION='v0.3.0' PROTOC_GO_INJECT_TAG_VERSION='v1.3.0' +MOCKED_PB_DIRS= pbdns + GOTAGS ?= GOPATH=$(shell go env GOPATH) GOARCH?=$(shell go env GOARCH) @@ -401,9 +403,20 @@ else endif .PHONY: proto -proto: proto-tools +proto: proto-tools proto-gen proto-mocks + +.PHONY: proto-gen +proto-gen: proto-tools @$(SHELL) $(CURDIR)/build-support/scripts/protobuf.sh +.PHONY: proto-mocks +proto-mocks: + for dir in $(MOCKED_PB_DIRS) ; do \ + cd proto-public && \ + rm -f $$dir/mock*.go && \ + mockery --dir $$dir --inpackage --all --recursive --log-level trace ; \ + done + .PHONY: proto-format proto-format: proto-tools @buf format -w diff --git a/README.md b/README.md index 44556afdff..e76c4588b6 100644 --- a/README.md +++ b/README.md @@ -17,10 +17,10 @@ Consul provides several key features: * **Multi-Datacenter** - Consul is built to be datacenter aware, and can support any number of regions without complex configuration. -* **Service Mesh/Service Segmentation** - Consul Connect enables secure service-to-service +* **Service Mesh** - Consul Service Mesh enables secure service-to-service communication with automatic TLS encryption and identity-based authorization. Applications can use sidecar proxies in a service mesh configuration to establish TLS - connections for inbound and outbound connections without being aware of Connect at all. + connections for inbound and outbound connections with Transparent Proxy. * **Service Discovery** - Consul makes it simple for services to register themselves and to discover other services via a DNS or HTTP interface. @@ -37,7 +37,7 @@ Consul provides several key features: Consul runs on Linux, macOS, FreeBSD, Solaris, and Windows and includes an optional [browser based UI](https://demo.consul.io). A commercial version -called [Consul Enterprise](https://www.hashicorp.com/products/consul) is also +called [Consul Enterprise](https://www.consul.io/docs/enterprise) is also available. **Please note**: We take Consul's security and our users' trust very seriously. If you @@ -52,12 +52,11 @@ A few quick start guides are available on the Consul website: * **Minikube install:** https://learn.hashicorp.com/tutorials/consul/kubernetes-minikube * **Kind install:** https://learn.hashicorp.com/tutorials/consul/kubernetes-kind * **Kubernetes install:** https://learn.hashicorp.com/tutorials/consul/kubernetes-deployment-guide +* **Deploy HCP Consul:** https://learn.hashicorp.com/tutorials/consul/hcp-gs-deploy ## Documentation -Full, comprehensive documentation is available on the Consul website: - -https://www.consul.io/docs +Full, comprehensive documentation is available on the Consul website: https://consul.io/docs ## Contributing diff --git a/agent/agent.go b/agent/agent.go index d30ef3281b..3de22c6ae5 100644 --- a/agent/agent.go +++ b/agent/agent.go @@ -24,9 +24,11 @@ import ( "github.com/hashicorp/go-hclog" "github.com/hashicorp/go-memdb" "github.com/hashicorp/go-multierror" + "github.com/hashicorp/hcp-scada-provider/capability" "github.com/hashicorp/raft" "github.com/hashicorp/serf/serf" "golang.org/x/net/http2" + "golang.org/x/net/http2/h2c" "google.golang.org/grpc" "github.com/hashicorp/consul/acl" @@ -40,6 +42,9 @@ import ( "github.com/hashicorp/consul/agent/consul/servercert" "github.com/hashicorp/consul/agent/dns" external "github.com/hashicorp/consul/agent/grpc-external" + grpcDNS "github.com/hashicorp/consul/agent/grpc-external/services/dns" + "github.com/hashicorp/consul/agent/hcp/scada" + libscada "github.com/hashicorp/consul/agent/hcp/scada" "github.com/hashicorp/consul/agent/local" "github.com/hashicorp/consul/agent/proxycfg" proxycfgglue "github.com/hashicorp/consul/agent/proxycfg-glue" @@ -382,6 +387,10 @@ type Agent struct { // xdsServer serves the XDS protocol for configuring Envoy proxies. xdsServer *xds.Server + // scadaProvider is set when HashiCorp Cloud Platform integration is configured and exposes the agent's API over + // an encrypted session to HCP + scadaProvider scada.Provider + // enterpriseAgent embeds fields that we only access in consul-enterprise builds enterpriseAgent } @@ -428,6 +437,7 @@ func New(bd BaseDeps) (*Agent, error) { config: bd.RuntimeConfig, cache: bd.Cache, routineManager: routine.NewManager(bd.Logger), + scadaProvider: bd.HCP.Provider, } // TODO: create rpcClientHealth in BaseDeps once NetRPC is available without Agent @@ -769,6 +779,17 @@ func (a *Agent) Start(ctx context.Context) error { }() } + if a.scadaProvider != nil { + a.scadaProvider.UpdateMeta(map[string]string{ + "consul_server_id": string(a.config.NodeID), + }) + + if err = a.scadaProvider.Start(); err != nil { + a.baseDeps.Logger.Error("scada provider failed to start, some HashiCorp Cloud Platform functionality has been disabled", + "error", err, "resource_id", a.config.Cloud.ResourceID) + } + } + return nil } @@ -900,6 +921,15 @@ func (a *Agent) listenAndServeDNS() error { } }(addr) } + s, _ := NewDNSServer(a) + + grpcDNS.NewServer(grpcDNS.Config{ + Logger: a.logger.Named("grpc-api.dns"), + DNSServeMux: s.mux, + LocalAddr: grpcDNS.LocalAddr{IP: net.IPv4(127, 0, 0, 1), Port: a.config.GRPCPort}, + }).Register(a.externalGRPCServer) + + a.dnsServers = append(a.dnsServers, s) // wait for servers to be up timeout := time.After(time.Second) @@ -954,6 +984,12 @@ func (a *Agent) startListeners(addrs []net.Addr) ([]net.Listener, error) { } l = &tcpKeepAliveListener{l.(*net.TCPListener)} + case *capability.Addr: + l, err = a.scadaProvider.Listen(x.Capability()) + if err != nil { + return nil, err + } + default: closeAll() return nil, fmt.Errorf("unsupported address type %T", addr) @@ -1011,6 +1047,11 @@ func (a *Agent) listenHTTP() ([]apiServer, error) { MaxHeaderBytes: a.config.HTTPMaxHeaderBytes, } + if libscada.IsCapability(l.Addr()) { + // wrap in http2 server handler + httpServer.Handler = h2c.NewHandler(srv.handler(a.config.EnableDebug), &http2.Server{}) + } + // Load the connlimit helper into the server connLimitFn := a.httpConnLimiter.HTTPConnStateFuncWithDefault429Handler(10 * time.Millisecond) @@ -1027,7 +1068,12 @@ func (a *Agent) listenHTTP() ([]apiServer, error) { return nil } - if err := start("http", a.config.HTTPAddrs); err != nil { + httpAddrs := a.config.HTTPAddrs + if a.config.IsCloudEnabled() { + httpAddrs = append(httpAddrs, scada.CAPCoreAPI) + } + + if err := start("http", httpAddrs); err != nil { closeListeners(ln) return nil, err } @@ -1582,6 +1628,11 @@ func (a *Agent) ShutdownAgent() error { a.rpcClientHealth.Close() + // Shutdown SCADA provider + if a.scadaProvider != nil { + a.scadaProvider.Stop() + } + var err error if a.delegate != nil { err = a.delegate.Shutdown() @@ -4187,6 +4238,7 @@ func (a *Agent) registerCache() { a.cache.RegisterType(cachetype.CompiledDiscoveryChainName, &cachetype.CompiledDiscoveryChain{RPC: a}) a.cache.RegisterType(cachetype.GatewayServicesName, &cachetype.GatewayServices{RPC: a}) + a.cache.RegisterType(cachetype.ServiceGatewaysName, &cachetype.ServiceGateways{RPC: a}) a.cache.RegisterType(cachetype.ConfigEntryListName, &cachetype.ConfigEntryList{RPC: a}) @@ -4206,6 +4258,8 @@ func (a *Agent) registerCache() { a.cache.RegisterType(cachetype.PeeredUpstreamsName, &cachetype.PeeredUpstreams{RPC: a}) + a.cache.RegisterType(cachetype.PeeringListName, &cachetype.Peerings{Client: a.rpcClientPeering}) + a.registerEntCache() } @@ -4320,6 +4374,7 @@ func (a *Agent) proxyDataSources() proxycfg.DataSources { InternalServiceDump: proxycfgglue.CacheInternalServiceDump(a.cache), LeafCertificate: proxycfgglue.CacheLeafCertificate(a.cache), PeeredUpstreams: proxycfgglue.CachePeeredUpstreams(a.cache), + PeeringList: proxycfgglue.CachePeeringList(a.cache), PreparedQuery: proxycfgglue.CachePrepraredQuery(a.cache), ResolvedServiceConfig: proxycfgglue.CacheResolvedServiceConfig(a.cache), ServiceList: proxycfgglue.CacheServiceList(a.cache), @@ -4348,6 +4403,7 @@ func (a *Agent) proxyDataSources() proxycfg.DataSources { sources.IntentionUpstreams = proxycfgglue.ServerIntentionUpstreams(deps) sources.IntentionUpstreamsDestination = proxycfgglue.ServerIntentionUpstreamsDestination(deps) sources.InternalServiceDump = proxycfgglue.ServerInternalServiceDump(deps, proxycfgglue.CacheInternalServiceDump(a.cache)) + sources.PeeringList = proxycfgglue.ServerPeeringList(deps) sources.PeeredUpstreams = proxycfgglue.ServerPeeredUpstreams(deps) sources.ResolvedServiceConfig = proxycfgglue.ServerResolvedServiceConfig(deps, proxycfgglue.CacheResolvedServiceConfig(a.cache)) sources.ServiceList = proxycfgglue.ServerServiceList(deps, proxycfgglue.CacheServiceList(a.cache)) diff --git a/agent/agent_endpoint_test.go b/agent/agent_endpoint_test.go index ac4807edd6..6e52157dfe 100644 --- a/agent/agent_endpoint_test.go +++ b/agent/agent_endpoint_test.go @@ -1443,8 +1443,8 @@ func TestAgent_Self(t *testing.T) { } ports = { grpc = -1 - } - `, + grpc_tls = -1 + }`, expectXDS: false, grpcTLS: false, }, @@ -1453,7 +1453,9 @@ func TestAgent_Self(t *testing.T) { node_meta { somekey = "somevalue" } - `, + ports = { + grpc_tls = -1 + }`, expectXDS: true, grpcTLS: false, }, @@ -1461,8 +1463,7 @@ func TestAgent_Self(t *testing.T) { hcl: ` node_meta { somekey = "somevalue" - } - `, + }`, expectXDS: true, grpcTLS: true, }, diff --git a/agent/agent_test.go b/agent/agent_test.go index 65593710eb..1c7671f767 100644 --- a/agent/agent_test.go +++ b/agent/agent_test.go @@ -29,9 +29,11 @@ import ( "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/tcpproxy" "github.com/hashicorp/go-hclog" + "github.com/hashicorp/hcp-scada-provider/capability" "github.com/hashicorp/serf/coordinate" "github.com/hashicorp/serf/serf" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" "golang.org/x/sync/errgroup" "google.golang.org/grpc" @@ -43,6 +45,8 @@ import ( "github.com/hashicorp/consul/agent/config" "github.com/hashicorp/consul/agent/connect" "github.com/hashicorp/consul/agent/consul" + "github.com/hashicorp/consul/agent/hcp" + "github.com/hashicorp/consul/agent/hcp/scada" "github.com/hashicorp/consul/agent/structs" "github.com/hashicorp/consul/agent/token" "github.com/hashicorp/consul/api" @@ -6049,6 +6053,65 @@ peering { }) } +func TestAgent_startListeners_scada(t *testing.T) { + t.Parallel() + pvd := scada.NewMockProvider(t) + c := capability.NewAddr("testcap") + pvd.EXPECT().Listen(c.Capability()).Return(nil, nil).Once() + bd := BaseDeps{ + Deps: consul.Deps{ + Logger: hclog.NewInterceptLogger(nil), + Tokens: new(token.Store), + GRPCConnPool: &fakeGRPCConnPool{}, + HCP: hcp.Deps{ + Provider: pvd, + }, + }, + RuntimeConfig: &config.RuntimeConfig{}, + Cache: cache.New(cache.Options{}), + } + + bd, err := initEnterpriseBaseDeps(bd, nil) + require.NoError(t, err) + + agent, err := New(bd) + require.NoError(t, err) + + _, err = agent.startListeners([]net.Addr{c}) + require.NoError(t, err) +} + +func TestAgent_scadaProvider(t *testing.T) { + pvd := scada.NewMockProvider(t) + + // this listener is used when mocking out the scada provider + l, err := net.Listen("tcp4", fmt.Sprintf("127.0.0.1:%d", freeport.GetOne(t))) + require.NoError(t, err) + defer require.NoError(t, l.Close()) + + pvd.EXPECT().UpdateMeta(mock.Anything).Once() + pvd.EXPECT().Start().Return(nil).Once() + pvd.EXPECT().Listen(scada.CAPCoreAPI.Capability()).Return(l, nil).Once() + pvd.EXPECT().Stop().Return(nil).Once() + pvd.EXPECT().SessionStatus().Return("test") + a := TestAgent{ + OverrideDeps: func(deps *BaseDeps) { + deps.HCP.Provider = pvd + }, + Overrides: ` +cloud { + resource_id = "organization/0b9de9a3-8403-4ca6-aba8-fca752f42100/project/0b9de9a3-8403-4ca6-aba8-fca752f42100/consul.cluster/0b9de9a3-8403-4ca6-aba8-fca752f42100" + client_id = "test" + client_secret = "test" +}`, + } + defer a.Shutdown() + require.NoError(t, a.Start(t)) + + _, err = api.NewClient(&api.Config{Address: l.Addr().String()}) + require.NoError(t, err) +} + func getExpectedCaPoolByFile(t *testing.T) *x509.CertPool { pool := x509.NewCertPool() data, err := ioutil.ReadFile("../test/ca/root.cer") diff --git a/agent/cache-types/mock_PeeringLister_test.go b/agent/cache-types/mock_PeeringLister_test.go new file mode 100644 index 0000000000..f3cb48c24a --- /dev/null +++ b/agent/cache-types/mock_PeeringLister_test.go @@ -0,0 +1,63 @@ +// Code generated by mockery v2.14.0. DO NOT EDIT. + +package cachetype + +import ( + context "context" + + grpc "google.golang.org/grpc" + + mock "github.com/stretchr/testify/mock" + + pbpeering "github.com/hashicorp/consul/proto/pbpeering" +) + +// MockPeeringLister is an autogenerated mock type for the PeeringLister type +type MockPeeringLister struct { + mock.Mock +} + +// PeeringList provides a mock function with given fields: ctx, in, opts +func (_m *MockPeeringLister) PeeringList(ctx context.Context, in *pbpeering.PeeringListRequest, opts ...grpc.CallOption) (*pbpeering.PeeringListResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *pbpeering.PeeringListResponse + if rf, ok := ret.Get(0).(func(context.Context, *pbpeering.PeeringListRequest, ...grpc.CallOption) *pbpeering.PeeringListResponse); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*pbpeering.PeeringListResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *pbpeering.PeeringListRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +type mockConstructorTestingTNewMockPeeringLister interface { + mock.TestingT + Cleanup(func()) +} + +// NewMockPeeringLister creates a new instance of MockPeeringLister. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +func NewMockPeeringLister(t mockConstructorTestingTNewMockPeeringLister) *MockPeeringLister { + mock := &MockPeeringLister{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/agent/cache-types/peerings.go b/agent/cache-types/peerings.go new file mode 100644 index 0000000000..7ecbb183e8 --- /dev/null +++ b/agent/cache-types/peerings.go @@ -0,0 +1,107 @@ +package cachetype + +import ( + "context" + "fmt" + "strconv" + "time" + + external "github.com/hashicorp/consul/agent/grpc-external" + "github.com/hashicorp/consul/proto/pbpeering" + "github.com/mitchellh/hashstructure" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + + "github.com/hashicorp/consul/agent/cache" + "github.com/hashicorp/consul/agent/structs" +) + +// PeeringListName is the recommended name for registration. +const PeeringListName = "peers" + +type PeeringListRequest struct { + Request *pbpeering.PeeringListRequest + structs.QueryOptions +} + +func (r *PeeringListRequest) CacheInfo() cache.RequestInfo { + info := cache.RequestInfo{ + Token: r.Token, + Datacenter: "", + MinIndex: 0, + Timeout: 0, + MustRevalidate: false, + + // OPTIMIZE(peering): Cache.notifyPollingQuery polls at this interval. We need to revisit how that polling works. + // Using an exponential backoff when the result hasn't changed may be preferable. + MaxAge: 1 * time.Second, + } + + v, err := hashstructure.Hash([]interface{}{ + r.Request.Partition, + }, nil) + if err == nil { + // If there is an error, we don't set the key. A blank key forces + // no cache for this request so the request is forwarded directly + // to the server. + info.Key = strconv.FormatUint(v, 10) + } + + return info +} + +// Peerings supports fetching the list of peers for a given partition or wildcard-specifier. +type Peerings struct { + RegisterOptionsNoRefresh + Client PeeringLister +} + +//go:generate mockery --name PeeringLister --inpackage --filename mock_PeeringLister_test.go +type PeeringLister interface { + PeeringList( + ctx context.Context, in *pbpeering.PeeringListRequest, opts ...grpc.CallOption, + ) (*pbpeering.PeeringListResponse, error) +} + +func (t *Peerings) Fetch(_ cache.FetchOptions, req cache.Request) (cache.FetchResult, error) { + var result cache.FetchResult + + // The request should be a PeeringListRequest. + // We do not need to make a copy of this request type like in other cache types + // because the RequestInfo is synthetic. + reqReal, ok := req.(*PeeringListRequest) + if !ok { + return result, fmt.Errorf( + "Internal cache failure: request wrong type: %T", req) + } + + // Always allow stale - there's no point in hitting leader if the request is + // going to be served from cache and end up arbitrarily stale anyway. This + // allows cached service-discover to automatically read scale across all + // servers too. + reqReal.QueryOptions.SetAllowStale(true) + + ctx, err := external.ContextWithQueryOptions(context.Background(), reqReal.QueryOptions) + if err != nil { + return result, err + } + + // Fetch + reply, err := t.Client.PeeringList(ctx, reqReal.Request) + if err != nil { + // Return an empty result if the error is due to peering being disabled. + // This allows mesh gateways to receive an update and confirm that the watch is set. + if e, ok := status.FromError(err); ok && e.Code() == codes.FailedPrecondition { + result.Index = 1 + result.Value = &pbpeering.PeeringListResponse{} + return result, nil + } + return result, err + } + + result.Value = reply + result.Index = reply.Index + + return result, nil +} diff --git a/agent/cache-types/peerings_test.go b/agent/cache-types/peerings_test.go new file mode 100644 index 0000000000..e96e6256e9 --- /dev/null +++ b/agent/cache-types/peerings_test.go @@ -0,0 +1,131 @@ +package cachetype + +import ( + "context" + "testing" + "time" + + "github.com/mitchellh/copystructure" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + grpcstatus "google.golang.org/grpc/status" + + "github.com/hashicorp/consul/agent/cache" + "github.com/hashicorp/consul/proto/pbpeering" +) + +func TestPeerings(t *testing.T) { + client := NewMockPeeringLister(t) + typ := &Peerings{Client: client} + + resp := &pbpeering.PeeringListResponse{ + Index: 48, + Peerings: []*pbpeering.Peering{ + { + Name: "peer1", + ID: "8ac403cf-6834-412f-9dfe-0ac6e69bd89f", + PeerServerAddresses: []string{"1.2.3.4"}, + State: pbpeering.PeeringState_ACTIVE, + }, + }, + } + + // Expect the proper call. + // This also returns the canned response above. + client.On("PeeringList", mock.Anything, mock.Anything). + Return(resp, nil) + + // Fetch and assert against the result. + result, err := typ.Fetch(cache.FetchOptions{}, &PeeringListRequest{ + Request: &pbpeering.PeeringListRequest{}, + }) + require.NoError(t, err) + require.Equal(t, cache.FetchResult{ + Value: resp, + Index: 48, + }, result) +} + +func TestPeerings_PeeringDisabled(t *testing.T) { + client := NewMockPeeringLister(t) + typ := &Peerings{Client: client} + + var resp *pbpeering.PeeringListResponse + + // Expect the proper call, but return the peering disabled error + client.On("PeeringList", mock.Anything, mock.Anything). + Return(resp, grpcstatus.Error(codes.FailedPrecondition, "peering must be enabled to use this endpoint")) + + // Fetch and assert against the result. + result, err := typ.Fetch(cache.FetchOptions{}, &PeeringListRequest{ + Request: &pbpeering.PeeringListRequest{}, + }) + require.NoError(t, err) + require.NotNil(t, result) + require.EqualValues(t, 1, result.Index) + require.NotNil(t, result.Value) +} + +func TestPeerings_badReqType(t *testing.T) { + client := pbpeering.NewPeeringServiceClient(nil) + typ := &Peerings{Client: client} + + // Fetch + _, err := typ.Fetch(cache.FetchOptions{}, cache.TestRequest( + t, cache.RequestInfo{Key: "foo", MinIndex: 64})) + require.Error(t, err) + require.Contains(t, err.Error(), "wrong type") +} + +// This test asserts that we can continuously poll this cache type, given that it doesn't support blocking. +func TestPeerings_MultipleUpdates(t *testing.T) { + c := cache.New(cache.Options{}) + + client := NewMockPeeringLister(t) + + // On each mock client call to PeeringList we will increment the index by 1 + // to simulate new data arriving. + resp := &pbpeering.PeeringListResponse{ + Index: uint64(0), + } + + client.On("PeeringList", mock.Anything, mock.Anything). + Return(func(ctx context.Context, in *pbpeering.PeeringListRequest, opts ...grpc.CallOption) *pbpeering.PeeringListResponse { + resp.Index++ + // Avoids triggering the race detection by copying the output + copyResp, err := copystructure.Copy(resp) + require.NoError(t, err) + output := copyResp.(*pbpeering.PeeringListResponse) + return output + }, nil) + + c.RegisterType(PeeringListName, &Peerings{Client: client}) + + ch := make(chan cache.UpdateEvent) + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + t.Cleanup(cancel) + + require.NoError(t, c.Notify(ctx, PeeringListName, &PeeringListRequest{ + Request: &pbpeering.PeeringListRequest{}, + }, "updates", ch)) + + i := uint64(1) + for { + select { + case <-ctx.Done(): + t.Fatal("context deadline exceeded") + return + case update := <-ch: + // Expect to receive updates for increasing indexes serially. + actual := update.Result.(*pbpeering.PeeringListResponse) + require.Equal(t, i, actual.Index) + i++ + + if i > 3 { + return + } + } + } +} diff --git a/agent/cache-types/trust_bundle.go b/agent/cache-types/trust_bundle.go index 48dad64372..addc65ac94 100644 --- a/agent/cache-types/trust_bundle.go +++ b/agent/cache-types/trust_bundle.go @@ -83,7 +83,12 @@ func (t *TrustBundle) Fetch(_ cache.FetchOptions, req cache.Request) (cache.Fetc reqReal.QueryOptions.SetAllowStale(true) // Fetch - reply, err := t.Client.TrustBundleRead(external.ContextWithToken(context.Background(), reqReal.Token), reqReal.Request) + ctx, err := external.ContextWithQueryOptions(context.Background(), reqReal.QueryOptions) + if err != nil { + return result, err + } + + reply, err := t.Client.TrustBundleRead(ctx, reqReal.Request) if err != nil { return result, err } diff --git a/agent/cache-types/trust_bundle_test.go b/agent/cache-types/trust_bundle_test.go index ee03838aaa..ea36e8d8a7 100644 --- a/agent/cache-types/trust_bundle_test.go +++ b/agent/cache-types/trust_bundle_test.go @@ -5,10 +5,11 @@ import ( "testing" "time" - "github.com/hashicorp/consul/agent/cache" - "github.com/hashicorp/consul/proto/pbpeering" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" + + "github.com/hashicorp/consul/agent/cache" + "github.com/hashicorp/consul/proto/pbpeering" ) func TestTrustBundle(t *testing.T) { @@ -93,11 +94,12 @@ func TestTrustBundle_MultipleUpdates(t *testing.T) { for { select { case <-ctx.Done(): + t.Fatal("context deadline exceeded") return case update := <-ch: // Expect to receive updates for increasing indexes serially. - resp := update.Result.(*pbpeering.TrustBundleReadResponse) - require.Equal(t, i, resp.Index) + actual := update.Result.(*pbpeering.TrustBundleReadResponse) + require.Equal(t, i, actual.Index) i++ if i > 3 { diff --git a/agent/cache-types/trust_bundles.go b/agent/cache-types/trust_bundles.go index eddc8dabbe..47f411f02f 100644 --- a/agent/cache-types/trust_bundles.go +++ b/agent/cache-types/trust_bundles.go @@ -87,7 +87,12 @@ func (t *TrustBundles) Fetch(_ cache.FetchOptions, req cache.Request) (cache.Fet reqReal.QueryOptions.SetAllowStale(true) // Fetch - reply, err := t.Client.TrustBundleListByService(external.ContextWithToken(context.Background(), reqReal.Token), reqReal.Request) + ctx, err := external.ContextWithQueryOptions(context.Background(), reqReal.QueryOptions) + if err != nil { + return result, err + } + + reply, err := t.Client.TrustBundleListByService(ctx, reqReal.Request) if err != nil { // Return an empty result if the error is due to peering being disabled. // This allows mesh gateways to receive an update and confirm that the watch is set. diff --git a/agent/cache-types/trust_bundles_test.go b/agent/cache-types/trust_bundles_test.go index 85248dba1d..733becdd60 100644 --- a/agent/cache-types/trust_bundles_test.go +++ b/agent/cache-types/trust_bundles_test.go @@ -121,6 +121,7 @@ func TestTrustBundles_MultipleUpdates(t *testing.T) { for { select { case <-ctx.Done(): + t.Fatal("context deadline exceeded") return case update := <-ch: // Expect to receive updates for increasing indexes serially. diff --git a/agent/config/builder.go b/agent/config/builder.go index 25a313fc6c..d6b045c93d 100644 --- a/agent/config/builder.go +++ b/agent/config/builder.go @@ -19,6 +19,7 @@ import ( "time" "github.com/armon/go-metrics/prometheus" + hcpconfig "github.com/hashicorp/consul/agent/hcp/config" "github.com/hashicorp/go-bexpr" "github.com/hashicorp/go-hclog" "github.com/hashicorp/go-multierror" @@ -959,6 +960,7 @@ func (b *builder) build() (rt RuntimeConfig, err error) { AutoEncryptIPSAN: autoEncryptIPSAN, AutoEncryptAllowTLS: autoEncryptAllowTLS, AutoConfig: autoConfig, + Cloud: b.cloudConfigVal(c.Cloud), ConnectEnabled: connectEnabled, ConnectCAProvider: connectCAProvider, ConnectCAConfig: connectCAConfig, @@ -1560,6 +1562,7 @@ func (b *builder) checkVal(v *CheckDefinition) *structs.CheckDefinition { Body: stringVal(v.Body), DisableRedirects: boolVal(v.DisableRedirects), TCP: stringVal(v.TCP), + UDP: stringVal(v.UDP), Interval: b.durationVal(fmt.Sprintf("check[%s].interval", id), v.Interval), DockerContainerID: stringVal(v.DockerContainerID), Shell: stringVal(v.Shell), @@ -2446,6 +2449,20 @@ func validateAutoConfigAuthorizer(rt RuntimeConfig) error { return nil } +func (b *builder) cloudConfigVal(v *CloudConfigRaw) (val hcpconfig.CloudConfig) { + if v == nil { + return val + } + + val.ResourceID = stringVal(v.ResourceID) + val.ClientID = stringVal(v.ClientID) + val.ClientSecret = stringVal(v.ClientSecret) + val.AuthURL = stringVal(v.AuthURL) + val.Hostname = stringVal(v.Hostname) + + return val +} + // decodeBytes returns the encryption key decoded. func decodeBytes(key string) ([]byte, error) { return base64.StdEncoding.DecodeString(key) diff --git a/agent/config/builder_test.go b/agent/config/builder_test.go index 1bd6d8653f..abb7be0e1d 100644 --- a/agent/config/builder_test.go +++ b/agent/config/builder_test.go @@ -326,6 +326,24 @@ func TestBuilder_ServiceVal_MultiError(t *testing.T) { require.Contains(t, b.err.Error(), "cannot have both socket path") } +func TestBuilder_ServiceVal_with_Check(t *testing.T) { + b := builder{} + svc := b.serviceVal(&ServiceDefinition{ + Name: strPtr("unbound"), + ID: strPtr("unbound"), + Port: intPtr(12345), + Checks: []CheckDefinition{ + { + Interval: strPtr("5s"), + UDP: strPtr("localhost:53"), + }, + }, + }) + require.NoError(t, b.err) + require.Equal(t, 1, len(svc.Checks)) + require.Equal(t, "localhost:53", svc.Checks[0].UDP) +} + func intPtr(v int) *int { return &v } diff --git a/agent/config/config.go b/agent/config/config.go index de82d98769..af75b89b2d 100644 --- a/agent/config/config.go +++ b/agent/config/config.go @@ -153,6 +153,7 @@ type Config struct { CheckUpdateInterval *string `mapstructure:"check_update_interval"` Checks []CheckDefinition `mapstructure:"checks"` ClientAddr *string `mapstructure:"client_addr"` + Cloud *CloudConfigRaw `mapstructure:"cloud"` ConfigEntries ConfigEntries `mapstructure:"config_entries"` AutoEncrypt AutoEncrypt `mapstructure:"auto_encrypt"` Connect Connect `mapstructure:"connect"` @@ -859,6 +860,14 @@ type RPC struct { EnableStreaming *bool `mapstructure:"enable_streaming"` } +type CloudConfigRaw struct { + ResourceID *string `mapstructure:"resource_id"` + ClientID *string `mapstructure:"client_id"` + ClientSecret *string `mapstructure:"client_secret"` + Hostname *string `mapstructure:"hostname"` + AuthURL *string `mapstructure:"auth_url"` +} + type TLSProtocolConfig struct { CAFile *string `mapstructure:"ca_file"` CAPath *string `mapstructure:"ca_path"` diff --git a/agent/config/runtime.go b/agent/config/runtime.go index ee82ea477e..6e8651779d 100644 --- a/agent/config/runtime.go +++ b/agent/config/runtime.go @@ -13,6 +13,7 @@ import ( "github.com/hashicorp/consul/agent/cache" "github.com/hashicorp/consul/agent/consul" "github.com/hashicorp/consul/agent/dns" + hcpconfig "github.com/hashicorp/consul/agent/hcp/config" "github.com/hashicorp/consul/agent/structs" "github.com/hashicorp/consul/agent/token" "github.com/hashicorp/consul/api" @@ -157,6 +158,11 @@ type RuntimeConfig struct { // hcl: autopilot { upgrade_version_tag = string } AutopilotUpgradeVersionTag string + // Cloud contains configuration for agents to connect to HCP. + // + // hcl: cloud { ... } + Cloud hcpconfig.CloudConfig + // DNSAllowStale is used to enable lookups with stale // data. This gives horizontal read scalability since // any Consul server can service the query instead of @@ -1679,6 +1685,11 @@ func (c *RuntimeConfig) Sanitized() map[string]interface{} { return sanitize("rt", reflect.ValueOf(c)).Interface().(map[string]interface{}) } +// IsCloudEnabled returns true if a cloud.resource_id is set and the server mode is enabled +func (c *RuntimeConfig) IsCloudEnabled() bool { + return c.ServerMode && c.Cloud.ResourceID != "" +} + // isSecret determines whether a field name represents a field which // may contain a secret. func isSecret(name string) bool { diff --git a/agent/config/runtime_test.go b/agent/config/runtime_test.go index 3cff4c198a..30aa3f0ac9 100644 --- a/agent/config/runtime_test.go +++ b/agent/config/runtime_test.go @@ -19,6 +19,7 @@ import ( "github.com/armon/go-metrics/prometheus" "github.com/google/go-cmp/cmp/cmpopts" + hcpconfig "github.com/hashicorp/consul/agent/hcp/config" "github.com/stretchr/testify/require" "github.com/hashicorp/consul/acl" @@ -5989,44 +5990,51 @@ func TestLoad_FullConfig(t *testing.T) { }, ConnectMeshGatewayWANFederationEnabled: false, ConnectServerlessPluginEnabled: true, - DNSAddrs: []net.Addr{tcpAddr("93.95.95.81:7001"), udpAddr("93.95.95.81:7001")}, - DNSARecordLimit: 29907, - DNSAllowStale: true, - DNSDisableCompression: true, - DNSDomain: "7W1xXSqd", - DNSAltDomain: "1789hsd", - DNSEnableTruncate: true, - DNSMaxStale: 29685 * time.Second, - DNSNodeTTL: 7084 * time.Second, - DNSOnlyPassing: true, - DNSPort: 7001, - DNSRecursorStrategy: "sequential", - DNSRecursorTimeout: 4427 * time.Second, - DNSRecursors: []string{"63.38.39.58", "92.49.18.18"}, - DNSSOA: RuntimeSOAConfig{Refresh: 3600, Retry: 600, Expire: 86400, Minttl: 0}, - DNSServiceTTL: map[string]time.Duration{"*": 32030 * time.Second}, - DNSUDPAnswerLimit: 29909, - DNSNodeMetaTXT: true, - DNSUseCache: true, - DNSCacheMaxAge: 5 * time.Minute, - DataDir: dataDir, - Datacenter: "rzo029wg", - DefaultQueryTime: 16743 * time.Second, - DisableAnonymousSignature: true, - DisableCoordinates: true, - DisableHostNodeID: true, - DisableHTTPUnprintableCharFilter: true, - DisableKeyringFile: true, - DisableRemoteExec: true, - DisableUpdateCheck: true, - DiscardCheckOutput: true, - DiscoveryMaxStale: 5 * time.Second, - EnableAgentTLSForChecks: true, - EnableCentralServiceConfig: false, - EnableDebug: true, - EnableRemoteScriptChecks: true, - EnableLocalScriptChecks: true, - EncryptKey: "A4wELWqH", + Cloud: hcpconfig.CloudConfig{ + ResourceID: "N43DsscE", + ClientID: "6WvsDZCP", + ClientSecret: "lCSMHOpB", + Hostname: "DH4bh7aC", + AuthURL: "332nCdR2", + }, + DNSAddrs: []net.Addr{tcpAddr("93.95.95.81:7001"), udpAddr("93.95.95.81:7001")}, + DNSARecordLimit: 29907, + DNSAllowStale: true, + DNSDisableCompression: true, + DNSDomain: "7W1xXSqd", + DNSAltDomain: "1789hsd", + DNSEnableTruncate: true, + DNSMaxStale: 29685 * time.Second, + DNSNodeTTL: 7084 * time.Second, + DNSOnlyPassing: true, + DNSPort: 7001, + DNSRecursorStrategy: "sequential", + DNSRecursorTimeout: 4427 * time.Second, + DNSRecursors: []string{"63.38.39.58", "92.49.18.18"}, + DNSSOA: RuntimeSOAConfig{Refresh: 3600, Retry: 600, Expire: 86400, Minttl: 0}, + DNSServiceTTL: map[string]time.Duration{"*": 32030 * time.Second}, + DNSUDPAnswerLimit: 29909, + DNSNodeMetaTXT: true, + DNSUseCache: true, + DNSCacheMaxAge: 5 * time.Minute, + DataDir: dataDir, + Datacenter: "rzo029wg", + DefaultQueryTime: 16743 * time.Second, + DisableAnonymousSignature: true, + DisableCoordinates: true, + DisableHostNodeID: true, + DisableHTTPUnprintableCharFilter: true, + DisableKeyringFile: true, + DisableRemoteExec: true, + DisableUpdateCheck: true, + DiscardCheckOutput: true, + DiscoveryMaxStale: 5 * time.Second, + EnableAgentTLSForChecks: true, + EnableCentralServiceConfig: false, + EnableDebug: true, + EnableRemoteScriptChecks: true, + EnableLocalScriptChecks: true, + EncryptKey: "A4wELWqH", StaticRuntimeConfig: StaticRuntimeConfig{ EncryptVerifyIncoming: true, EncryptVerifyOutgoing: true, @@ -6771,6 +6779,11 @@ func TestRuntimeConfig_Sanitize(t *testing.T) { EntryFetchMaxBurst: 42, EntryFetchRate: 0.334, }, + Cloud: hcpconfig.CloudConfig{ + ResourceID: "cluster1", + ClientID: "id", + ClientSecret: "secret", + }, ConsulCoordinateUpdatePeriod: 15 * time.Second, RaftProtocol: 3, RetryJoinLAN: []string{ diff --git a/agent/config/testdata/TestRuntimeConfig_Sanitize.golden b/agent/config/testdata/TestRuntimeConfig_Sanitize.golden index b628a18c86..3a29fb0091 100644 --- a/agent/config/testdata/TestRuntimeConfig_Sanitize.golden +++ b/agent/config/testdata/TestRuntimeConfig_Sanitize.golden @@ -124,6 +124,13 @@ } ], "ClientAddrs": [], + "Cloud": { + "AuthURL": "", + "ClientID": "id", + "ClientSecret": "hidden", + "Hostname": "", + "ResourceID": "cluster1" + }, "ConfigEntryBootstrap": [], "ConnectCAConfig": {}, "ConnectCAProvider": "", diff --git a/agent/config/testdata/full-config.hcl b/agent/config/testdata/full-config.hcl index 09e9aabd58..cab2074e7c 100644 --- a/agent/config/testdata/full-config.hcl +++ b/agent/config/testdata/full-config.hcl @@ -201,6 +201,13 @@ auto_encrypt = { ip_san = ["192.168.4.139", "192.168.4.140"] allow_tls = true } +cloud { + resource_id = "N43DsscE" + client_id = "6WvsDZCP" + client_secret = "lCSMHOpB" + hostname = "DH4bh7aC" + auth_url = "332nCdR2" +} connect { ca_provider = "consul" ca_config { diff --git a/agent/config/testdata/full-config.json b/agent/config/testdata/full-config.json index 946b27e73c..f95363f8e4 100644 --- a/agent/config/testdata/full-config.json +++ b/agent/config/testdata/full-config.json @@ -203,6 +203,13 @@ "ip_san": ["192.168.4.139", "192.168.4.140"], "allow_tls": true }, + "cloud": { + "resource_id": "N43DsscE", + "client_id": "6WvsDZCP", + "client_secret": "lCSMHOpB", + "hostname": "DH4bh7aC", + "auth_url": "332nCdR2" + }, "connect": { "ca_provider": "consul", "ca_config": { diff --git a/agent/consul/merge_service_config.go b/agent/configentry/merge_service_config.go similarity index 94% rename from agent/consul/merge_service_config.go rename to agent/configentry/merge_service_config.go index 706e24f4a6..f11d96fcc5 100644 --- a/agent/consul/merge_service_config.go +++ b/agent/configentry/merge_service_config.go @@ -1,4 +1,4 @@ -package consul +package configentry import ( "fmt" @@ -8,18 +8,21 @@ import ( "github.com/imdario/mergo" "github.com/mitchellh/copystructure" - "github.com/hashicorp/consul/agent/configentry" - "github.com/hashicorp/consul/agent/consul/state" + "github.com/hashicorp/consul/acl" "github.com/hashicorp/consul/agent/structs" ) -// mergeNodeServiceWithCentralConfig merges a service instance (NodeService) with the +type StateStore interface { + ReadResolvedServiceConfigEntries(memdb.WatchSet, string, *acl.EnterpriseMeta, []structs.ServiceID, structs.ProxyMode) (uint64, *ResolvedServiceConfigSet, error) +} + +// MergeNodeServiceWithCentralConfig merges a service instance (NodeService) with the // proxy-defaults/global and service-defaults/:service config entries. // This common helper is used by the blocking query function of different RPC endpoints // that need to return a fully resolved service defintion. -func mergeNodeServiceWithCentralConfig( +func MergeNodeServiceWithCentralConfig( ws memdb.WatchSet, - state *state.Store, + state StateStore, args *structs.ServiceSpecificRequest, ns *structs.NodeService, logger hclog.Logger) (uint64, *structs.NodeService, error) { @@ -67,7 +70,7 @@ func mergeNodeServiceWithCentralConfig( ns.ID, err) } - defaults, err := configentry.ComputeResolvedServiceConfig( + defaults, err := ComputeResolvedServiceConfig( configReq, upstreams, false, diff --git a/agent/consul/merge_service_config_test.go b/agent/configentry/merge_service_config_test.go similarity index 99% rename from agent/consul/merge_service_config_test.go rename to agent/configentry/merge_service_config_test.go index a4b88308e4..eb5e97d420 100644 --- a/agent/consul/merge_service_config_test.go +++ b/agent/configentry/merge_service_config_test.go @@ -1,4 +1,4 @@ -package consul +package configentry import ( "testing" diff --git a/agent/configentry/resolve.go b/agent/configentry/resolve.go index f6090e98f0..e3f7e54fbe 100644 --- a/agent/configentry/resolve.go +++ b/agent/configentry/resolve.go @@ -53,6 +53,7 @@ func ComputeResolvedServiceConfig( structs.NewServiceID(args.Name, &args.EnterpriseMeta), ) if serviceConf != nil { + if serviceConf.Expose.Checks { thisReply.Expose.Checks = true } @@ -62,12 +63,6 @@ func ComputeResolvedServiceConfig( if serviceConf.MeshGateway.Mode != structs.MeshGatewayModeDefault { thisReply.MeshGateway.Mode = serviceConf.MeshGateway.Mode } - if serviceConf.Protocol != "" { - if thisReply.ProxyConfig == nil { - thisReply.ProxyConfig = make(map[string]interface{}) - } - thisReply.ProxyConfig["protocol"] = serviceConf.Protocol - } if serviceConf.TransparentProxy.OutboundListenerPort != 0 { thisReply.TransparentProxy.OutboundListenerPort = serviceConf.TransparentProxy.OutboundListenerPort } @@ -81,25 +76,29 @@ func ComputeResolvedServiceConfig( thisReply.Destination = *serviceConf.Destination } + // Populate values for the proxy config map + proxyConf := thisReply.ProxyConfig + if proxyConf == nil { + proxyConf = make(map[string]interface{}) + } + if serviceConf.Protocol != "" { + proxyConf["protocol"] = serviceConf.Protocol + } + if serviceConf.BalanceInboundConnections != "" { + proxyConf["balance_inbound_connections"] = serviceConf.BalanceInboundConnections + } if serviceConf.MaxInboundConnections > 0 { - if thisReply.ProxyConfig == nil { - thisReply.ProxyConfig = map[string]interface{}{} - } - thisReply.ProxyConfig["max_inbound_connections"] = serviceConf.MaxInboundConnections + proxyConf["max_inbound_connections"] = serviceConf.MaxInboundConnections } - if serviceConf.LocalConnectTimeoutMs > 0 { - if thisReply.ProxyConfig == nil { - thisReply.ProxyConfig = map[string]interface{}{} - } - thisReply.ProxyConfig["local_connect_timeout_ms"] = serviceConf.LocalConnectTimeoutMs + proxyConf["local_connect_timeout_ms"] = serviceConf.LocalConnectTimeoutMs } - if serviceConf.LocalRequestTimeoutMs > 0 { - if thisReply.ProxyConfig == nil { - thisReply.ProxyConfig = map[string]interface{}{} - } - thisReply.ProxyConfig["local_request_timeout_ms"] = serviceConf.LocalRequestTimeoutMs + proxyConf["local_request_timeout_ms"] = serviceConf.LocalRequestTimeoutMs + } + // Add the proxy conf to the response if any fields were populated + if len(proxyConf) > 0 { + thisReply.ProxyConfig = proxyConf } thisReply.Meta = serviceConf.Meta diff --git a/agent/configentry/resolve_test.go b/agent/configentry/resolve_test.go index 301472c1c5..a023dca400 100644 --- a/agent/configentry/resolve_test.go +++ b/agent/configentry/resolve_test.go @@ -24,6 +24,26 @@ func Test_ComputeResolvedServiceConfig(t *testing.T) { args args want *structs.ServiceConfigResponse }{ + { + name: "proxy with balanceinboundconnections", + args: args{ + scReq: &structs.ServiceConfigRequest{ + Name: "sid", + }, + entries: &ResolvedServiceConfigSet{ + ServiceDefaults: map[structs.ServiceID]*structs.ServiceConfigEntry{ + sid: { + BalanceInboundConnections: "exact_balance", + }, + }, + }, + }, + want: &structs.ServiceConfigResponse{ + ProxyConfig: map[string]interface{}{ + "balance_inbound_connections": "exact_balance", + }, + }, + }, { name: "proxy with maxinboundsconnections", args: args{ diff --git a/agent/connect/testing_ca.go b/agent/connect/testing_ca.go index 343de77bc4..06d965ce6e 100644 --- a/agent/connect/testing_ca.go +++ b/agent/connect/testing_ca.go @@ -183,8 +183,7 @@ func TestCAWithKeyType(t testing.T, xc *structs.CARoot, keyType string, keyBits return testCA(t, xc, keyType, keyBits, 0) } -func testLeafWithID(t testing.T, spiffeId CertURI, root *structs.CARoot, keyType string, keyBits int, expiration time.Duration) (string, string, error) { - +func testLeafWithID(t testing.T, spiffeId CertURI, dnsSAN string, root *structs.CARoot, keyType string, keyBits int, expiration time.Duration) (string, string, error) { if expiration == 0 { // this is 10 years expiration = 10 * 365 * 24 * time.Hour @@ -238,6 +237,7 @@ func testLeafWithID(t testing.T, spiffeId CertURI, root *structs.CARoot, keyType NotBefore: time.Now(), AuthorityKeyId: testKeyID(t, caSigner.Public()), SubjectKeyId: testKeyID(t, pkSigner.Public()), + DNSNames: []string{dnsSAN}, } // Create the certificate, PEM encode it and return that value. @@ -263,7 +263,7 @@ func TestAgentLeaf(t testing.T, node string, datacenter string, root *structs.CA Agent: node, } - return testLeafWithID(t, spiffeId, root, DefaultPrivateKeyType, DefaultPrivateKeyBits, expiration) + return testLeafWithID(t, spiffeId, "", root, DefaultPrivateKeyType, DefaultPrivateKeyBits, expiration) } func testLeaf(t testing.T, service string, namespace string, root *structs.CARoot, keyType string, keyBits int) (string, string, error) { @@ -275,7 +275,7 @@ func testLeaf(t testing.T, service string, namespace string, root *structs.CARoo Service: service, } - return testLeafWithID(t, spiffeId, root, keyType, keyBits, 0) + return testLeafWithID(t, spiffeId, "", root, keyType, keyBits, 0) } // TestLeaf returns a valid leaf certificate and it's private key for the named @@ -305,7 +305,23 @@ func TestMeshGatewayLeaf(t testing.T, partition string, root *structs.CARoot) (s Datacenter: "dc1", } - certPEM, keyPEM, err := testLeafWithID(t, spiffeId, root, DefaultPrivateKeyType, DefaultPrivateKeyBits, 0) + certPEM, keyPEM, err := testLeafWithID(t, spiffeId, "", root, DefaultPrivateKeyType, DefaultPrivateKeyBits, 0) + if err != nil { + t.Fatalf(err.Error()) + } + return certPEM, keyPEM +} + +func TestServerLeaf(t testing.T, dc string, root *structs.CARoot) (string, string) { + t.Helper() + + spiffeID := &SpiffeIDServer{ + Datacenter: dc, + Host: fmt.Sprintf("%s.consul", TestClusterID), + } + san := PeeringServerSAN(dc, TestTrustDomain) + + certPEM, keyPEM, err := testLeafWithID(t, spiffeID, san, root, DefaultPrivateKeyType, DefaultPrivateKeyBits, 0) if err != nil { t.Fatalf(err.Error()) } diff --git a/agent/consul/catalog_endpoint.go b/agent/consul/catalog_endpoint.go index 696ae314a7..bf0492a7b9 100644 --- a/agent/consul/catalog_endpoint.go +++ b/agent/consul/catalog_endpoint.go @@ -16,6 +16,7 @@ import ( "github.com/hashicorp/consul/acl" "github.com/hashicorp/consul/acl/resolver" + "github.com/hashicorp/consul/agent/configentry" "github.com/hashicorp/consul/agent/consul/state" "github.com/hashicorp/consul/agent/structs" "github.com/hashicorp/consul/ipaddr" @@ -752,7 +753,7 @@ func (c *Catalog) ServiceNodes(args *structs.ServiceSpecificRequest, reply *stru mergedsn := sn ns := sn.ToNodeService() if ns.IsSidecarProxy() || ns.IsGateway() { - cfgIndex, mergedns, err := mergeNodeServiceWithCentralConfig(ws, state, args, ns, c.logger) + cfgIndex, mergedns, err := configentry.MergeNodeServiceWithCentralConfig(ws, state, args, ns, c.logger) if err != nil { return err } @@ -960,7 +961,7 @@ func (c *Catalog) NodeServiceList(args *structs.NodeSpecificRequest, reply *stru Datacenter: args.Datacenter, QueryOptions: args.QueryOptions, } - cfgIndex, mergedns, err = mergeNodeServiceWithCentralConfig(ws, state, &serviceSpecificReq, ns, c.logger) + cfgIndex, mergedns, err = configentry.MergeNodeServiceWithCentralConfig(ws, state, &serviceSpecificReq, ns, c.logger) if err != nil { return err } diff --git a/agent/consul/client_serf.go b/agent/consul/client_serf.go index 7d993c4ac9..50a0844485 100644 --- a/agent/consul/client_serf.go +++ b/agent/consul/client_serf.go @@ -37,11 +37,11 @@ func (c *Client) setupSerf(conf *serf.Config, ch chan serf.Event, path string) ( serfLogger := c.logger. NamedIntercept(logging.Serf). NamedIntercept(logging.LAN). - StandardLoggerIntercept(&hclog.StandardLoggerOptions{InferLevels: true}) + StandardLogger(&hclog.StandardLoggerOptions{InferLevels: true}) memberlistLogger := c.logger. NamedIntercept(logging.Memberlist). NamedIntercept(logging.LAN). - StandardLoggerIntercept(&hclog.StandardLoggerOptions{InferLevels: true}) + StandardLogger(&hclog.StandardLoggerOptions{InferLevels: true}) conf.MemberlistConfig.Logger = memberlistLogger conf.Logger = serfLogger diff --git a/agent/consul/config_test.go b/agent/consul/config_test.go index c536684c0e..8e6c7055dd 100644 --- a/agent/consul/config_test.go +++ b/agent/consul/config_test.go @@ -39,6 +39,7 @@ func TestCloneSerfLANConfig(t *testing.T) { "Ping", "ProtocolVersion", "PushPullInterval", + "QueueCheckInterval", "RequireNodeNames", "SkipInboundLabelCheck", "SuspicionMaxTimeoutMult", diff --git a/agent/consul/grpc_integration_test.go b/agent/consul/grpc_integration_test.go index c94156f96d..fa1ed48896 100644 --- a/agent/consul/grpc_integration_test.go +++ b/agent/consul/grpc_integration_test.go @@ -59,7 +59,9 @@ func TestGRPCIntegration_ConnectCA_Sign(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) t.Cleanup(cancel) - ctx = external.ContextWithToken(ctx, TestDefaultInitialManagementToken) + options := structs.QueryOptions{Token: TestDefaultInitialManagementToken} + ctx, err := external.ContextWithQueryOptions(ctx, options) + require.NoError(t, err) // This would fail if it wasn't forwarded to the leader. rsp, err := client.Sign(ctx, &pbconnectca.SignRequest{ @@ -96,7 +98,9 @@ func TestGRPCIntegration_ServerDiscovery_WatchServers(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) t.Cleanup(cancel) - ctx = external.ContextWithToken(ctx, TestDefaultInitialManagementToken) + options := structs.QueryOptions{Token: TestDefaultInitialManagementToken} + ctx, err := external.ContextWithQueryOptions(ctx, options) + require.NoError(t, err) serverStream, err := client.WatchServers(ctx, &pbserverdiscovery.WatchServersRequest{Wan: false}) require.NoError(t, err) diff --git a/agent/consul/health_endpoint.go b/agent/consul/health_endpoint.go index ee8f328885..28823d9585 100644 --- a/agent/consul/health_endpoint.go +++ b/agent/consul/health_endpoint.go @@ -11,6 +11,7 @@ import ( hashstructure_v2 "github.com/mitchellh/hashstructure/v2" "github.com/hashicorp/consul/acl" + "github.com/hashicorp/consul/agent/configentry" "github.com/hashicorp/consul/agent/consul/state" "github.com/hashicorp/consul/agent/structs" ) @@ -256,7 +257,7 @@ func (h *Health) ServiceNodes(args *structs.ServiceSpecificRequest, reply *struc for _, node := range resolvedNodes { ns := node.Service if ns.IsSidecarProxy() || ns.IsGateway() { - cfgIndex, mergedns, err := mergeNodeServiceWithCentralConfig(ws, state, args, ns, h.logger) + cfgIndex, mergedns, err := configentry.MergeNodeServiceWithCentralConfig(ws, state, args, ns, h.logger) if err != nil { return err } diff --git a/agent/consul/internal_endpoint_test.go b/agent/consul/internal_endpoint_test.go index 698fc56818..e0aa941b90 100644 --- a/agent/consul/internal_endpoint_test.go +++ b/agent/consul/internal_endpoint_test.go @@ -3334,19 +3334,19 @@ func TestInternal_ExportedPeeredServices_ACLEnforcement(t *testing.T) { { Name: "web", Consumers: []structs.ServiceConsumer{ - {PeerName: "peer-1"}, + {Peer: "peer-1"}, }, }, { Name: "db", Consumers: []structs.ServiceConsumer{ - {PeerName: "peer-2"}, + {Peer: "peer-2"}, }, }, { Name: "api", Consumers: []structs.ServiceConsumer{ - {PeerName: "peer-1"}, + {Peer: "peer-1"}, }, }, }, @@ -3405,7 +3405,7 @@ func TestInternal_ExportedPeeredServices_ACLEnforcement(t *testing.T) { ` service "web" { policy = "read" } service "api" { policy = "read" } - service "db" { policy = "deny" } + service "db" { policy = "deny" } `), expect: map[string]structs.ServiceList{ "peer-1": { @@ -3514,19 +3514,19 @@ func TestInternal_ExportedServicesForPeer_ACLEnforcement(t *testing.T) { { Name: "web", Consumers: []structs.ServiceConsumer{ - {PeerName: "peer-1"}, + {Peer: "peer-1"}, }, }, { Name: "db", Consumers: []structs.ServiceConsumer{ - {PeerName: "peer-2"}, + {Peer: "peer-2"}, }, }, { Name: "api", Consumers: []structs.ServiceConsumer{ - {PeerName: "peer-1"}, + {Peer: "peer-1"}, }, }, }, diff --git a/agent/consul/leader_peering_test.go b/agent/consul/leader_peering_test.go index 331e7324ad..40f158a69f 100644 --- a/agent/consul/leader_peering_test.go +++ b/agent/consul/leader_peering_test.go @@ -12,6 +12,7 @@ import ( "time" "github.com/armon/go-metrics" + msgpackrpc "github.com/hashicorp/consul-net-rpc/net-rpc-msgpackrpc" "github.com/hashicorp/go-hclog" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -21,6 +22,7 @@ import ( "google.golang.org/protobuf/proto" "github.com/hashicorp/consul/acl" + "github.com/hashicorp/consul/agent/connect" "github.com/hashicorp/consul/agent/consul/state" "github.com/hashicorp/consul/agent/structs" "github.com/hashicorp/consul/api" @@ -33,27 +35,23 @@ import ( ) func TestLeader_PeeringSync_Lifecycle_ClientDeletion(t *testing.T) { - t.Run("without-tls", func(t *testing.T) { - testLeader_PeeringSync_Lifecycle_ClientDeletion(t, false) - }) - t.Run("with-tls", func(t *testing.T) { - testLeader_PeeringSync_Lifecycle_ClientDeletion(t, true) - }) -} - -func testLeader_PeeringSync_Lifecycle_ClientDeletion(t *testing.T, enableTLS bool) { if testing.Short() { t.Skip("too slow for testing.Short") } + ca := connect.TestCA(t, nil) _, acceptor := testServerWithConfig(t, func(c *Config) { c.NodeName = "acceptor" c.Datacenter = "dc1" c.TLSConfig.Domain = "consul" - if enableTLS { - c.TLSConfig.GRPC.CAFile = "../../test/hostname/CertAuth.crt" - c.TLSConfig.GRPC.CertFile = "../../test/hostname/Bob.crt" - c.TLSConfig.GRPC.KeyFile = "../../test/hostname/Bob.key" + c.GRPCTLSPort = freeport.GetOne(t) + c.CAConfig = &structs.CAConfiguration{ + ClusterID: connect.TestClusterID, + Provider: structs.ConsulCAProvider, + Config: map[string]interface{}{ + "PrivateKey": ca.SigningKey, + "RootCert": ca.RootCert, + }, } }) testrpc.WaitForLeader(t, acceptor.RPC, "dc1") @@ -93,11 +91,6 @@ func testLeader_PeeringSync_Lifecycle_ClientDeletion(t *testing.T, enableTLS boo c.NodeName = "dialer" c.Datacenter = "dc2" c.PrimaryDatacenter = "dc2" - if enableTLS { - c.TLSConfig.GRPC.CAFile = "../../test/hostname/CertAuth.crt" - c.TLSConfig.GRPC.CertFile = "../../test/hostname/Betty.crt" - c.TLSConfig.GRPC.KeyFile = "../../test/hostname/Betty.key" - } }) testrpc.WaitForLeader(t, dialer.RPC, "dc2") @@ -162,28 +155,214 @@ func testLeader_PeeringSync_Lifecycle_ClientDeletion(t *testing.T, enableTLS boo }) } -func TestLeader_PeeringSync_Lifecycle_ServerDeletion(t *testing.T) { - t.Run("without-tls", func(t *testing.T) { - testLeader_PeeringSync_Lifecycle_AcceptorDeletion(t, false) +func TestLeader_PeeringSync_Lifecycle_UnexportWhileDown(t *testing.T) { + if testing.Short() { + t.Skip("too slow for testing.Short") + } + + // Reserve a gRPC port so we can restart the accepting server with the same port. + dialingServerPort := freeport.GetOne(t) + + ca := connect.TestCA(t, nil) + _, acceptor := testServerWithConfig(t, func(c *Config) { + c.NodeName = "acceptor" + c.Datacenter = "dc1" + c.TLSConfig.Domain = "consul" + c.GRPCTLSPort = freeport.GetOne(t) + c.CAConfig = &structs.CAConfiguration{ + ClusterID: connect.TestClusterID, + Provider: structs.ConsulCAProvider, + Config: map[string]interface{}{ + "PrivateKey": ca.SigningKey, + "RootCert": ca.RootCert, + }, + } }) - t.Run("with-tls", func(t *testing.T) { - testLeader_PeeringSync_Lifecycle_AcceptorDeletion(t, true) + testrpc.WaitForLeader(t, acceptor.RPC, "dc1") + + // Create a peering by generating a token + ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) + t.Cleanup(cancel) + + conn, err := grpc.DialContext(ctx, acceptor.config.RPCAddr.String(), + grpc.WithContextDialer(newServerDialer(acceptor.config.RPCAddr.String())), + grpc.WithInsecure(), + grpc.WithBlock()) + require.NoError(t, err) + defer conn.Close() + + acceptorClient := pbpeering.NewPeeringServiceClient(conn) + + req := pbpeering.GenerateTokenRequest{ + PeerName: "my-peer-dialer", + } + resp, err := acceptorClient.GenerateToken(ctx, &req) + require.NoError(t, err) + + tokenJSON, err := base64.StdEncoding.DecodeString(resp.PeeringToken) + require.NoError(t, err) + + var token structs.PeeringToken + require.NoError(t, json.Unmarshal(tokenJSON, &token)) + + // Bring up dialer and establish a peering with acceptor's token so that it attempts to dial. + _, dialer := testServerWithConfig(t, func(c *Config) { + c.NodeName = "dialer" + c.Datacenter = "dc2" + c.PrimaryDatacenter = "dc2" + c.GRPCPort = dialingServerPort + }) + testrpc.WaitForLeader(t, dialer.RPC, "dc2") + + // Create a peering at dialer by establishing a peering with acceptor's token + ctx, cancel = context.WithTimeout(context.Background(), 3*time.Second) + t.Cleanup(cancel) + + conn, err = grpc.DialContext(ctx, dialer.config.RPCAddr.String(), + grpc.WithContextDialer(newServerDialer(dialer.config.RPCAddr.String())), + grpc.WithInsecure(), + grpc.WithBlock()) + require.NoError(t, err) + defer conn.Close() + + dialerClient := pbpeering.NewPeeringServiceClient(conn) + + establishReq := pbpeering.EstablishRequest{ + PeerName: "my-peer-acceptor", + PeeringToken: resp.PeeringToken, + } + _, err = dialerClient.Establish(ctx, &establishReq) + require.NoError(t, err) + + p, err := dialerClient.PeeringRead(ctx, &pbpeering.PeeringReadRequest{Name: "my-peer-acceptor"}) + require.NoError(t, err) + + retry.Run(t, func(r *retry.R) { + status, found := dialer.peerStreamServer.StreamStatus(p.Peering.ID) + require.True(r, found) + require.True(r, status.Connected) + }) + + retry.Run(t, func(r *retry.R) { + status, found := acceptor.peerStreamServer.StreamStatus(p.Peering.PeerID) + require.True(r, found) + require.True(r, status.Connected) + }) + + acceptorCodec := rpcClient(t, acceptor) + { + exportedServices := structs.ConfigEntryRequest{ + Op: structs.ConfigEntryUpsert, + Datacenter: "dc1", + Entry: &structs.ExportedServicesConfigEntry{ + Name: "default", + Services: []structs.ExportedService{ + { + Name: "foo", + Consumers: []structs.ServiceConsumer{{Peer: "my-peer-dialer"}}, + }, + }, + }, + } + var configOutput bool + require.NoError(t, msgpackrpc.CallWithCodec(acceptorCodec, "ConfigEntry.Apply", &exportedServices, &configOutput)) + require.True(t, configOutput) + } + + insertNode := func(i int) { + req := structs.RegisterRequest{ + Datacenter: "dc1", + Node: fmt.Sprintf("node%d", i+1), + Address: fmt.Sprintf("127.0.0.%d", i+1), + NodeMeta: map[string]string{ + "group": fmt.Sprintf("%d", i/5), + "instance_type": "t2.micro", + }, + Service: &structs.NodeService{ + Service: "foo", + Port: 8000, + }, + WriteRequest: structs.WriteRequest{Token: "root"}, + } + + var reply struct{} + if err := msgpackrpc.CallWithCodec(acceptorCodec, "Catalog.Register", &req, &reply); err != nil { + t.Fatalf("err: %v", err) + } + } + + for i := 0; i < 5; i++ { + insertNode(i) + } + + retry.Run(t, func(r *retry.R) { + _, nodes, err := dialer.fsm.State().CheckServiceNodes(nil, "foo", nil, "my-peer-acceptor") + require.NoError(r, err) + require.Len(r, nodes, 5) + }) + + // Shutdown the dialing server. + require.NoError(t, dialer.Shutdown()) + + // Have to manually shut down the gRPC server otherwise it stays bound to the port. + dialer.externalGRPCServer.Stop() + + { + exportedServices := structs.ConfigEntryRequest{ + Op: structs.ConfigEntryUpsert, + Datacenter: "dc1", + Entry: &structs.ExportedServicesConfigEntry{ + Name: "default", + Services: []structs.ExportedService{}, + }, + } + var configOutput bool + require.NoError(t, msgpackrpc.CallWithCodec(acceptorCodec, "ConfigEntry.Apply", &exportedServices, &configOutput)) + require.True(t, configOutput) + } + + // Restart the server by re-using the previous acceptor's data directory and node id. + _, dialerRestart := testServerWithConfig(t, func(c *Config) { + c.NodeName = "dialer" + c.Datacenter = "dc1" + c.TLSConfig.Domain = "consul" + c.GRPCPort = dialingServerPort + c.DataDir = dialer.config.DataDir + c.NodeID = dialer.config.NodeID + }) + + // The dialing peer should eventually reconnect. + retry.Run(t, func(r *retry.R) { + connStreams := dialerRestart.peerStreamServer.ConnectedStreams() + require.Contains(r, connStreams, p.Peering.ID) + }) + + // The un-export results in the foo nodes being deleted. + retry.Run(t, func(r *retry.R) { + _, nodes, err := dialerRestart.fsm.State().CheckServiceNodes(nil, "foo", nil, "my-peer-acceptor") + require.NoError(r, err) + require.Len(r, nodes, 0) }) } -func testLeader_PeeringSync_Lifecycle_AcceptorDeletion(t *testing.T, enableTLS bool) { +func TestLeader_PeeringSync_Lifecycle_ServerDeletion(t *testing.T) { if testing.Short() { t.Skip("too slow for testing.Short") } + ca := connect.TestCA(t, nil) _, acceptor := testServerWithConfig(t, func(c *Config) { c.NodeName = "acceptor" c.Datacenter = "dc1" c.TLSConfig.Domain = "consul" - if enableTLS { - c.TLSConfig.GRPC.CAFile = "../../test/hostname/CertAuth.crt" - c.TLSConfig.GRPC.CertFile = "../../test/hostname/Bob.crt" - c.TLSConfig.GRPC.KeyFile = "../../test/hostname/Bob.key" + c.GRPCTLSPort = freeport.GetOne(t) + c.CAConfig = &structs.CAConfiguration{ + ClusterID: connect.TestClusterID, + Provider: structs.ConsulCAProvider, + Config: map[string]interface{}{ + "PrivateKey": ca.SigningKey, + "RootCert": ca.RootCert, + }, } }) testrpc.WaitForLeader(t, acceptor.RPC, "dc1") @@ -218,11 +397,6 @@ func testLeader_PeeringSync_Lifecycle_AcceptorDeletion(t *testing.T, enableTLS b c.NodeName = "dialer" c.Datacenter = "dc2" c.PrimaryDatacenter = "dc2" - if enableTLS { - c.TLSConfig.GRPC.CAFile = "../../test/hostname/CertAuth.crt" - c.TLSConfig.GRPC.CertFile = "../../test/hostname/Betty.crt" - c.TLSConfig.GRPC.KeyFile = "../../test/hostname/Betty.key" - } }) testrpc.WaitForLeader(t, dialer.RPC, "dc2") @@ -295,7 +469,7 @@ func TestLeader_PeeringSync_FailsForTLSError(t *testing.T) { t.Run("server-name-validation", func(t *testing.T) { testLeader_PeeringSync_failsForTLSError(t, func(token *structs.PeeringToken) { token.ServerName = "wrong.name" - }, `transport: authentication handshake failed: x509: certificate is valid for server.dc1.consul, bob.server.dc1.consul, not wrong.name`) + }, `transport: authentication handshake failed: x509: certificate is valid for server.dc1.peering.11111111-2222-3333-4444-555555555555.consul, not wrong.name`) }) t.Run("bad-ca-roots", func(t *testing.T) { wrongRoot, err := ioutil.ReadFile("../../test/client_certs/rootca.crt") @@ -310,14 +484,20 @@ func TestLeader_PeeringSync_FailsForTLSError(t *testing.T) { func testLeader_PeeringSync_failsForTLSError(t *testing.T, tokenMutateFn func(token *structs.PeeringToken), expectErr string) { require.NotNil(t, tokenMutateFn) + ca := connect.TestCA(t, nil) _, s1 := testServerWithConfig(t, func(c *Config) { c.NodeName = "bob" c.Datacenter = "dc1" c.TLSConfig.Domain = "consul" - - c.TLSConfig.GRPC.CAFile = "../../test/hostname/CertAuth.crt" - c.TLSConfig.GRPC.CertFile = "../../test/hostname/Bob.crt" - c.TLSConfig.GRPC.KeyFile = "../../test/hostname/Bob.key" + c.GRPCTLSPort = freeport.GetOne(t) + c.CAConfig = &structs.CAConfiguration{ + ClusterID: connect.TestClusterID, + Provider: structs.ConsulCAProvider, + Config: map[string]interface{}{ + "PrivateKey": ca.SigningKey, + "RootCert": ca.RootCert, + }, + } }) testrpc.WaitForLeader(t, s1.RPC, "dc1") @@ -360,10 +540,6 @@ func testLeader_PeeringSync_failsForTLSError(t *testing.T, tokenMutateFn func(to c.NodeName = "betty" c.Datacenter = "dc2" c.PrimaryDatacenter = "dc2" - - c.TLSConfig.GRPC.CAFile = "../../test/hostname/CertAuth.crt" - c.TLSConfig.GRPC.CertFile = "../../test/hostname/Betty.crt" - c.TLSConfig.GRPC.KeyFile = "../../test/hostname/Betty.key" }) testrpc.WaitForLeader(t, s2.RPC, "dc2") @@ -402,11 +578,11 @@ func TestLeader_Peering_DeferredDeletion(t *testing.T) { t.Skip("too slow for testing.Short") } - // TODO(peering): Configure with TLS _, s1 := testServerWithConfig(t, func(c *Config) { c.NodeName = "s1.dc1" c.Datacenter = "dc1" c.TLSConfig.Domain = "consul" + c.GRPCTLSPort = freeport.GetOne(t) }) testrpc.WaitForLeader(t, s1.RPC, "dc1") @@ -481,15 +657,21 @@ func TestLeader_Peering_DialerReestablishesConnectionOnError(t *testing.T) { } // Reserve a gRPC port so we can restart the accepting server with the same port. - ports := freeport.GetN(t, 1) - acceptingServerPort := ports[0] + acceptingServerPort := freeport.GetOne(t) + ca := connect.TestCA(t, nil) _, acceptingServer := testServerWithConfig(t, func(c *Config) { c.NodeName = "acceptingServer.dc1" c.Datacenter = "dc1" - c.TLSConfig.Domain = "consul" - c.GRPCPort = acceptingServerPort - c.PeeringEnabled = true + c.GRPCTLSPort = acceptingServerPort + c.CAConfig = &structs.CAConfiguration{ + ClusterID: connect.TestClusterID, + Provider: structs.ConsulCAProvider, + Config: map[string]interface{}{ + "PrivateKey": ca.SigningKey, + "RootCert": ca.RootCert, + }, + } }) testrpc.WaitForLeader(t, acceptingServer.RPC, "dc1") @@ -592,9 +774,17 @@ func TestLeader_Peering_DialerReestablishesConnectionOnError(t *testing.T) { c.NodeName = "acceptingServer.dc1" c.Datacenter = "dc1" c.TLSConfig.Domain = "consul" - c.GRPCPort = acceptingServerPort c.DataDir = acceptingServer.config.DataDir c.NodeID = acceptingServer.config.NodeID + c.GRPCTLSPort = acceptingServerPort + c.CAConfig = &structs.CAConfiguration{ + ClusterID: connect.TestClusterID, + Provider: structs.ConsulCAProvider, + Config: map[string]interface{}{ + "PrivateKey": ca.SigningKey, + "RootCert": ca.RootCert, + }, + } }) testrpc.WaitForLeader(t, acceptingServerRestart.RPC, "dc1") @@ -689,11 +879,19 @@ func TestLeader_Peering_ImportedExportedServicesCount(t *testing.T) { t.Skip("too slow for testing.Short") } + ca := connect.TestCA(t, nil) _, s1 := testServerWithConfig(t, func(c *Config) { c.NodeName = "s1.dc1" c.Datacenter = "dc1" - c.TLSConfig.Domain = "consul" - c.PeeringEnabled = true + c.GRPCTLSPort = freeport.GetOne(t) + c.CAConfig = &structs.CAConfiguration{ + ClusterID: connect.TestClusterID, + Provider: structs.ConsulCAProvider, + Config: map[string]interface{}{ + "PrivateKey": ca.SigningKey, + "RootCert": ca.RootCert, + }, + } }) testrpc.WaitForLeader(t, s1.RPC, "dc1") @@ -818,8 +1016,8 @@ func TestLeader_Peering_ImportedExportedServicesCount(t *testing.T) { name string description string exportedService structs.ExportedServicesConfigEntry - expectedImportedServsCount uint64 - expectedExportedServsCount uint64 + expectedImportedServsCount int + expectedExportedServsCount int } testCases := []testCase{ @@ -833,7 +1031,7 @@ func TestLeader_Peering_ImportedExportedServicesCount(t *testing.T) { Name: structs.WildcardSpecifier, Consumers: []structs.ServiceConsumer{ { - PeerName: "my-peer-s2", + Peer: "my-peer-s2", }, }, }, @@ -861,7 +1059,7 @@ func TestLeader_Peering_ImportedExportedServicesCount(t *testing.T) { Name: "a-service", Consumers: []structs.ServiceConsumer{ { - PeerName: "my-peer-s2", + Peer: "my-peer-s2", }, }, }, @@ -869,7 +1067,7 @@ func TestLeader_Peering_ImportedExportedServicesCount(t *testing.T) { Name: "b-service", Consumers: []structs.ServiceConsumer{ { - PeerName: "my-peer-s2", + Peer: "my-peer-s2", }, }, }, @@ -888,7 +1086,7 @@ func TestLeader_Peering_ImportedExportedServicesCount(t *testing.T) { Name: "a-service", Consumers: []structs.ServiceConsumer{ { - PeerName: "my-peer-s2", + Peer: "my-peer-s2", }, }, }, @@ -907,7 +1105,7 @@ func TestLeader_Peering_ImportedExportedServicesCount(t *testing.T) { Name: "a-service", Consumers: []structs.ServiceConsumer{ { - PeerName: "my-peer-s2", + Peer: "my-peer-s2", }, }, }, @@ -915,7 +1113,7 @@ func TestLeader_Peering_ImportedExportedServicesCount(t *testing.T) { Name: "c-service", Consumers: []structs.ServiceConsumer{ { - PeerName: "my-peer-s2", + Peer: "my-peer-s2", }, }, }, @@ -946,13 +1144,13 @@ func TestLeader_Peering_ImportedExportedServicesCount(t *testing.T) { resp, err := peeringClient2.PeeringRead(context.Background(), &pbpeering.PeeringReadRequest{Name: "my-peer-s1"}) require.NoError(r, err) require.NotNil(r, resp.Peering) - require.Equal(r, tc.expectedImportedServsCount, resp.Peering.ImportedServiceCount) + require.Equal(r, tc.expectedImportedServsCount, len(resp.Peering.StreamStatus.ImportedServices)) // on List resp2, err2 := peeringClient2.PeeringList(context.Background(), &pbpeering.PeeringListRequest{}) require.NoError(r, err2) require.NotEmpty(r, resp2.Peerings) - require.Equal(r, tc.expectedExportedServsCount, resp2.Peerings[0].ImportedServiceCount) + require.Equal(r, tc.expectedExportedServsCount, len(resp2.Peerings[0].StreamStatus.ImportedServices)) }) // Check that exported services count on S1 are what we expect @@ -961,13 +1159,13 @@ func TestLeader_Peering_ImportedExportedServicesCount(t *testing.T) { resp, err := peeringClient.PeeringRead(context.Background(), &pbpeering.PeeringReadRequest{Name: "my-peer-s2"}) require.NoError(r, err) require.NotNil(r, resp.Peering) - require.Equal(r, tc.expectedImportedServsCount, resp.Peering.ExportedServiceCount) + require.Equal(r, tc.expectedImportedServsCount, len(resp.Peering.StreamStatus.ExportedServices)) // on List resp2, err2 := peeringClient.PeeringList(context.Background(), &pbpeering.PeeringListRequest{}) require.NoError(r, err2) require.NotEmpty(r, resp2.Peerings) - require.Equal(r, tc.expectedExportedServsCount, resp2.Peerings[0].ExportedServiceCount) + require.Equal(r, tc.expectedExportedServsCount, len(resp2.Peerings[0].StreamStatus.ExportedServices)) }) }) } @@ -987,11 +1185,19 @@ func TestLeader_PeeringMetrics_emitPeeringMetrics(t *testing.T) { lastIdx = uint64(0) ) - // TODO(peering): Configure with TLS + ca := connect.TestCA(t, nil) _, s1 := testServerWithConfig(t, func(c *Config) { c.NodeName = "s1.dc1" c.Datacenter = "dc1" - c.TLSConfig.Domain = "consul" + c.GRPCTLSPort = freeport.GetOne(t) + c.CAConfig = &structs.CAConfiguration{ + ClusterID: connect.TestClusterID, + Provider: structs.ConsulCAProvider, + Config: map[string]interface{}{ + "PrivateKey": ca.SigningKey, + "RootCert": ca.RootCert, + }, + } }) testrpc.WaitForLeader(t, s1.RPC, "dc1") @@ -1061,17 +1267,21 @@ func TestLeader_PeeringMetrics_emitPeeringMetrics(t *testing.T) { require.NoError(t, err) // mimic tracking exported services - mst1.TrackExportedService(structs.ServiceName{Name: "a-service"}) - mst1.TrackExportedService(structs.ServiceName{Name: "b-service"}) - mst1.TrackExportedService(structs.ServiceName{Name: "c-service"}) + mst1.SetExportedServices([]structs.ServiceName{ + {Name: "a-service"}, + {Name: "b-service"}, + {Name: "c-service"}, + }) // connect the stream mst2, err := s2.peeringServer.Tracker.Connected(s2PeerID2) require.NoError(t, err) // mimic tracking exported services - mst2.TrackExportedService(structs.ServiceName{Name: "d-service"}) - mst2.TrackExportedService(structs.ServiceName{Name: "e-service"}) + mst2.SetExportedServices([]structs.ServiceName{ + {Name: "d-service"}, + {Name: "e-service"}, + }) // pretend that the hearbeat happened mst2.TrackRecvHeartbeat() @@ -1394,10 +1604,20 @@ func Test_Leader_PeeringSync_ServerAddressUpdates(t *testing.T) { maxRetryBackoff = 1 t.Cleanup(func() { maxRetryBackoff = orig }) + ca := connect.TestCA(t, nil) _, acceptor := testServerWithConfig(t, func(c *Config) { c.NodeName = "acceptor" c.Datacenter = "dc1" c.TLSConfig.Domain = "consul" + c.GRPCTLSPort = freeport.GetOne(t) + c.CAConfig = &structs.CAConfiguration{ + ClusterID: connect.TestClusterID, + Provider: structs.ConsulCAProvider, + Config: map[string]interface{}{ + "PrivateKey": ca.SigningKey, + "RootCert": ca.RootCert, + }, + } }) testrpc.WaitForLeader(t, acceptor.RPC, "dc1") diff --git a/agent/consul/leader_test.go b/agent/consul/leader_test.go index 3ba328672e..35bc924b7a 100644 --- a/agent/consul/leader_test.go +++ b/agent/consul/leader_test.go @@ -2,6 +2,7 @@ package consul import ( "bufio" + "encoding/json" "fmt" "io" "os" @@ -1457,7 +1458,7 @@ func TestLeader_ConfigEntryBootstrap_Fail(t *testing.T) { }, }, }, - expectMessage: `Failed to apply configuration entry "service-splitter" / "web": discovery chain "web" uses a protocol "tcp" that does not permit advanced routing or splitting behavior"`, + expectMessage: `Failed to apply configuration entry "service-splitter" / "web": discovery chain "web" uses a protocol "tcp" that does not permit advanced routing or splitting behavior`, }, { name: "service-intentions without migration", @@ -1497,7 +1498,7 @@ func TestLeader_ConfigEntryBootstrap_Fail(t *testing.T) { serverCB: func(c *Config) { c.ConnectEnabled = false }, - expectMessage: `Refusing to apply configuration entry "service-intentions" / "web" because Connect must be enabled to bootstrap intentions"`, + expectMessage: `Refusing to apply configuration entry "service-intentions" / "web" because Connect must be enabled to bootstrap intentions`, }, } @@ -1516,9 +1517,11 @@ func TestLeader_ConfigEntryBootstrap_Fail(t *testing.T) { scan := bufio.NewScanner(pr) for scan.Scan() { line := scan.Text() + lineJson := map[string]interface{}{} + json.Unmarshal([]byte(line), &lineJson) if strings.Contains(line, "failed to establish leadership") { - applyErrorLine = line + applyErrorLine = lineJson["error"].(string) ch <- "" return } @@ -1543,9 +1546,10 @@ func TestLeader_ConfigEntryBootstrap_Fail(t *testing.T) { } logger := hclog.NewInterceptLogger(&hclog.LoggerOptions{ - Name: config.NodeName, - Level: testutil.TestLogLevel, - Output: io.MultiWriter(pw, testutil.NewLogBuffer(t)), + Name: config.NodeName, + Level: testutil.TestLogLevel, + Output: io.MultiWriter(pw, testutil.NewLogBuffer(t)), + JSONFormat: true, }) deps := newDefaultDeps(t, config) diff --git a/agent/consul/options.go b/agent/consul/options.go index afc69b51db..e3fca37e66 100644 --- a/agent/consul/options.go +++ b/agent/consul/options.go @@ -1,13 +1,14 @@ package consul import ( - "github.com/hashicorp/go-hclog" "google.golang.org/grpc" "github.com/hashicorp/consul-net-rpc/net/rpc" + "github.com/hashicorp/go-hclog" "github.com/hashicorp/consul/agent/consul/stream" "github.com/hashicorp/consul/agent/grpc-external/limiter" + "github.com/hashicorp/consul/agent/hcp" "github.com/hashicorp/consul/agent/pool" "github.com/hashicorp/consul/agent/router" "github.com/hashicorp/consul/agent/rpc/middleware" @@ -31,6 +32,10 @@ type Deps struct { GetNetRPCInterceptorFunc func(recorder *middleware.RequestRecorder) rpc.ServerServiceCallInterceptor // NewRequestRecorderFunc provides a middleware.RequestRecorder for the server to use; it cannot be nil NewRequestRecorderFunc func(logger hclog.Logger, isLeader func() bool, localDC string) *middleware.RequestRecorder + + // HCP contains the dependencies required when integrating with the HashiCorp Cloud Platform + HCP hcp.Deps + EnterpriseDeps } diff --git a/agent/consul/peering_backend.go b/agent/consul/peering_backend.go index 5b01b9d040..26c2f19436 100644 --- a/agent/consul/peering_backend.go +++ b/agent/consul/peering_backend.go @@ -9,10 +9,14 @@ import ( "github.com/hashicorp/consul/acl" "github.com/hashicorp/consul/acl/resolver" + "github.com/hashicorp/consul/agent/connect" + "github.com/hashicorp/consul/agent/consul/state" "github.com/hashicorp/consul/agent/consul/stream" "github.com/hashicorp/consul/agent/grpc-external/services/peerstream" "github.com/hashicorp/consul/agent/rpc/peering" "github.com/hashicorp/consul/agent/structs" + "github.com/hashicorp/consul/ipaddr" + "github.com/hashicorp/consul/lib" "github.com/hashicorp/consul/proto/pbpeering" ) @@ -51,15 +55,68 @@ func (b *PeeringBackend) GetLeaderAddress() string { return b.leaderAddr } -// GetAgentCACertificates gets the server's raw CA data from its TLS Configurator. -func (b *PeeringBackend) GetAgentCACertificates() ([]string, error) { - // TODO(peering): handle empty CA pems - return b.srv.tlsConfigurator.GRPCManualCAPems(), nil +// GetTLSMaterials returns the TLS materials for the dialer to dial the acceptor using TLS. +// It returns the server name to validate, and the CA certificate to validate with. +func (b *PeeringBackend) GetTLSMaterials(generatingToken bool) (string, []string, error) { + if generatingToken { + if !b.srv.config.ConnectEnabled { + return "", nil, fmt.Errorf("connect.enabled must be set to true in the server's configuration when generating peering tokens") + } + if b.srv.config.GRPCTLSPort <= 0 && !b.srv.tlsConfigurator.GRPCServerUseTLS() { + return "", nil, fmt.Errorf("TLS for gRPC must be enabled when generating peering tokens") + } + } + + roots, err := b.srv.getCARoots(nil, b.srv.fsm.State()) + if err != nil { + return "", nil, fmt.Errorf("failed to fetch roots: %w", err) + } + if len(roots.Roots) == 0 || roots.TrustDomain == "" { + return "", nil, fmt.Errorf("CA has not finished initializing") + } + + serverName := connect.PeeringServerSAN(b.srv.config.Datacenter, roots.TrustDomain) + + var caPems []string + for _, r := range roots.Roots { + caPems = append(caPems, lib.EnsureTrailingNewline(r.RootCert)) + } + + return serverName, caPems, nil } -// GetServerAddresses looks up server node addresses from the state store. +// GetServerAddresses looks up server or mesh gateway addresses from the state store. func (b *PeeringBackend) GetServerAddresses() ([]string, error) { - state := b.srv.fsm.State() + _, rawEntry, err := b.srv.fsm.State().ConfigEntry(nil, structs.MeshConfig, structs.MeshConfigMesh, acl.DefaultEnterpriseMeta()) + if err != nil { + return nil, fmt.Errorf("failed to read mesh config entry: %w", err) + } + + meshConfig, ok := rawEntry.(*structs.MeshConfigEntry) + if ok && meshConfig.Peering != nil && meshConfig.Peering.PeerThroughMeshGateways { + return meshGatewayAdresses(b.srv.fsm.State()) + } + return serverAddresses(b.srv.fsm.State()) +} + +func meshGatewayAdresses(state *state.Store) ([]string, error) { + _, nodes, err := state.ServiceDump(nil, structs.ServiceKindMeshGateway, true, acl.DefaultEnterpriseMeta(), structs.DefaultPeerKeyword) + if err != nil { + return nil, fmt.Errorf("failed to dump gateway addresses: %w", err) + } + + var addrs []string + for _, node := range nodes { + _, addr, port := node.BestAddress(true) + addrs = append(addrs, ipaddr.FormatAddressPort(addr, port)) + } + if len(addrs) == 0 { + return nil, fmt.Errorf("servers are configured to PeerThroughMeshGateways, but no mesh gateway instances are registered") + } + return addrs, nil +} + +func serverAddresses(state *state.Store) ([]string, error) { _, nodes, err := state.ServiceNodes(nil, "consul", structs.DefaultEnterpriseMetaInDefaultPartition(), structs.DefaultPeerKeyword) if err != nil { return nil, err @@ -86,12 +143,6 @@ func (b *PeeringBackend) GetServerAddresses() ([]string, error) { return addrs, nil } -// GetServerName returns the SNI to be returned in the peering token data which -// will be used by peers when establishing peering connections over TLS. -func (b *PeeringBackend) GetServerName() string { - return b.srv.tlsConfigurator.ServerSNI(b.srv.config.Datacenter, "") -} - // EncodeToken encodes a peering token as a bas64-encoded representation of JSON (for now). func (b *PeeringBackend) EncodeToken(tok *structs.PeeringToken) ([]byte, error) { jsonToken, err := json.Marshal(tok) diff --git a/agent/consul/peering_backend_oss_test.go b/agent/consul/peering_backend_oss_test.go index 3c120d26f7..11466581b3 100644 --- a/agent/consul/peering_backend_oss_test.go +++ b/agent/consul/peering_backend_oss_test.go @@ -11,7 +11,10 @@ import ( "github.com/stretchr/testify/require" gogrpc "google.golang.org/grpc" + "github.com/hashicorp/consul/agent/connect" + "github.com/hashicorp/consul/agent/structs" "github.com/hashicorp/consul/proto/pbpeering" + "github.com/hashicorp/consul/sdk/freeport" "github.com/hashicorp/consul/testrpc" ) @@ -21,9 +24,18 @@ func TestPeeringBackend_RejectsPartition(t *testing.T) { } t.Parallel() + + ca := connect.TestCA(t, nil) _, s1 := testServerWithConfig(t, func(c *Config) { - c.Datacenter = "dc1" - c.Bootstrap = true + c.GRPCTLSPort = freeport.GetOne(t) + c.CAConfig = &structs.CAConfiguration{ + ClusterID: connect.TestClusterID, + Provider: structs.ConsulCAProvider, + Config: map[string]interface{}{ + "PrivateKey": ca.SigningKey, + "RootCert": ca.RootCert, + }, + } }) testrpc.WaitForLeader(t, s1.RPC, "dc1") @@ -55,9 +67,17 @@ func TestPeeringBackend_IgnoresDefaultPartition(t *testing.T) { } t.Parallel() + ca := connect.TestCA(t, nil) _, s1 := testServerWithConfig(t, func(c *Config) { - c.Datacenter = "dc1" - c.Bootstrap = true + c.GRPCTLSPort = freeport.GetOne(t) + c.CAConfig = &structs.CAConfiguration{ + ClusterID: connect.TestClusterID, + Provider: structs.ConsulCAProvider, + Config: map[string]interface{}{ + "PrivateKey": ca.SigningKey, + "RootCert": ca.RootCert, + }, + } }) testrpc.WaitForLeader(t, s1.RPC, "dc1") diff --git a/agent/consul/peering_backend_test.go b/agent/consul/peering_backend_test.go index 7636dc48be..0d834c09a9 100644 --- a/agent/consul/peering_backend_test.go +++ b/agent/consul/peering_backend_test.go @@ -2,34 +2,50 @@ package consul import ( "context" + "fmt" "net" "testing" "time" - "github.com/stretchr/testify/require" gogrpc "google.golang.org/grpc" + "github.com/hashicorp/consul/agent/connect" "github.com/hashicorp/consul/agent/pool" + "github.com/hashicorp/consul/agent/structs" "github.com/hashicorp/consul/proto/pbpeering" "github.com/hashicorp/consul/proto/pbpeerstream" + "github.com/hashicorp/consul/sdk/freeport" "github.com/hashicorp/consul/sdk/testutil" "github.com/hashicorp/consul/testrpc" + "github.com/hashicorp/consul/types" + "github.com/stretchr/testify/require" ) func TestPeeringBackend_ForwardToLeader(t *testing.T) { - t.Parallel() - - _, conf1 := testServerConfig(t) - server1, err := newServer(t, conf1) - require.NoError(t, err) + if testing.Short() { + t.Skip("too slow for testing.Short") + } - _, conf2 := testServerConfig(t) - conf2.Bootstrap = false - server2, err := newServer(t, conf2) - require.NoError(t, err) + ca := connect.TestCA(t, nil) + _, server1 := testServerWithConfig(t, func(c *Config) { + c.GRPCTLSPort = freeport.GetOne(t) + c.CAConfig = &structs.CAConfiguration{ + ClusterID: connect.TestClusterID, + Provider: structs.ConsulCAProvider, + Config: map[string]interface{}{ + "PrivateKey": ca.SigningKey, + "RootCert": ca.RootCert, + }, + } + }) + _, server2 := testServerWithConfig(t, func(c *Config) { + c.Bootstrap = false + }) // Join a 2nd server (not the leader) testrpc.WaitForLeader(t, server1.RPC, "dc1") + testrpc.WaitForActiveCARoot(t, server1.RPC, "dc1", nil) + joinLAN(t, server2, server1) testrpc.WaitForLeader(t, server2.RPC, "dc1") @@ -60,6 +76,83 @@ func TestPeeringBackend_ForwardToLeader(t *testing.T) { }) } +func TestPeeringBackend_GetServerAddresses(t *testing.T) { + if testing.Short() { + t.Skip("too slow for testing.Short") + } + + _, cfg := testServerConfig(t) + cfg.GRPCTLSPort = freeport.GetOne(t) + + srv, err := newServer(t, cfg) + require.NoError(t, err) + testrpc.WaitForLeader(t, srv.RPC, "dc1") + + backend := NewPeeringBackend(srv) + + testutil.RunStep(t, "peer to servers", func(t *testing.T) { + addrs, err := backend.GetServerAddresses() + require.NoError(t, err) + + expect := fmt.Sprintf("127.0.0.1:%d", srv.config.GRPCTLSPort) + require.Equal(t, []string{expect}, addrs) + }) + + testutil.RunStep(t, "existence of mesh config entry is not enough to peer through gateways", func(t *testing.T) { + mesh := structs.MeshConfigEntry{ + // Enable unrelated config. + TransparentProxy: structs.TransparentProxyMeshConfig{ + MeshDestinationsOnly: true, + }, + } + + require.NoError(t, srv.fsm.State().EnsureConfigEntry(1, &mesh)) + addrs, err := backend.GetServerAddresses() + require.NoError(t, err) + + // Still expect server address because PeerThroughMeshGateways was not enabled. + expect := fmt.Sprintf("127.0.0.1:%d", srv.config.GRPCTLSPort) + require.Equal(t, []string{expect}, addrs) + }) + + testutil.RunStep(t, "cannot peer through gateways without registered gateways", func(t *testing.T) { + mesh := structs.MeshConfigEntry{ + Peering: &structs.PeeringMeshConfig{PeerThroughMeshGateways: true}, + } + require.NoError(t, srv.fsm.State().EnsureConfigEntry(1, &mesh)) + + addrs, err := backend.GetServerAddresses() + require.Nil(t, addrs) + testutil.RequireErrorContains(t, err, + "servers are configured to PeerThroughMeshGateways, but no mesh gateway instances are registered") + }) + + testutil.RunStep(t, "peer through mesh gateways", func(t *testing.T) { + reg := structs.RegisterRequest{ + ID: types.NodeID("b5489ca9-f5e9-4dba-a779-61fec4e8e364"), + Node: "gw-node", + Address: "1.2.3.4", + TaggedAddresses: map[string]string{ + structs.TaggedAddressWAN: "172.217.22.14", + }, + Service: &structs.NodeService{ + ID: "mesh-gateway", + Service: "mesh-gateway", + Kind: structs.ServiceKindMeshGateway, + Port: 443, + TaggedAddresses: map[string]structs.ServiceAddress{ + structs.TaggedAddressWAN: {Address: "154.238.12.252", Port: 8443}, + }, + }, + } + require.NoError(t, srv.fsm.State().EnsureRegistration(2, ®)) + + addrs, err := backend.GetServerAddresses() + require.NoError(t, err) + require.Equal(t, []string{"154.238.12.252:8443"}, addrs) + }) +} + func newServerDialer(serverAddr string) func(context.Context, string) (net.Conn, error) { return func(ctx context.Context, addr string) (net.Conn, error) { d := net.Dialer{} @@ -79,19 +172,30 @@ func newServerDialer(serverAddr string) func(context.Context, string) (net.Conn, } func TestPeerStreamService_ForwardToLeader(t *testing.T) { - t.Parallel() - - _, conf1 := testServerConfig(t) - server1, err := newServer(t, conf1) - require.NoError(t, err) + if testing.Short() { + t.Skip("too slow for testing.Short") + } - _, conf2 := testServerConfig(t) - conf2.Bootstrap = false - server2, err := newServer(t, conf2) - require.NoError(t, err) + ca := connect.TestCA(t, nil) + _, server1 := testServerWithConfig(t, func(c *Config) { + c.GRPCTLSPort = freeport.GetOne(t) + c.CAConfig = &structs.CAConfiguration{ + ClusterID: connect.TestClusterID, + Provider: structs.ConsulCAProvider, + Config: map[string]interface{}{ + "PrivateKey": ca.SigningKey, + "RootCert": ca.RootCert, + }, + } + }) + _, server2 := testServerWithConfig(t, func(c *Config) { + c.Bootstrap = false + }) // server1 is leader, server2 follower testrpc.WaitForLeader(t, server1.RPC, "dc1") + testrpc.WaitForActiveCARoot(t, server1.RPC, "dc1", nil) + joinLAN(t, server2, server1) testrpc.WaitForLeader(t, server2.RPC, "dc1") diff --git a/agent/consul/prepared_query/walk_test.go b/agent/consul/prepared_query/walk_test.go index ad71e0fedc..12b71a2e35 100644 --- a/agent/consul/prepared_query/walk_test.go +++ b/agent/consul/prepared_query/walk_test.go @@ -42,7 +42,7 @@ func TestWalk_ServiceQuery(t *testing.T) { ".Tags[0]:tag1", ".Tags[1]:tag2", ".Tags[2]:tag3", - ".PeerName:", + ".Peer:", } expected = append(expected, entMetaWalkFields...) sort.Strings(expected) diff --git a/agent/consul/prepared_query_endpoint.go b/agent/consul/prepared_query_endpoint.go index 7215161f3b..ffa4b5e509 100644 --- a/agent/consul/prepared_query_endpoint.go +++ b/agent/consul/prepared_query_endpoint.go @@ -540,7 +540,7 @@ func (p *PreparedQuery) execute(query *structs.PreparedQuery, f = state.CheckConnectServiceNodes } - _, nodes, err := f(nil, query.Service.Service, &query.Service.EnterpriseMeta, query.Service.PeerName) + _, nodes, err := f(nil, query.Service.Service, &query.Service.EnterpriseMeta, query.Service.Peer) if err != nil { return err } @@ -571,7 +571,7 @@ func (p *PreparedQuery) execute(query *structs.PreparedQuery, reply.DNS = query.DNS // Stamp the result with its this datacenter or peer. - if peerName := query.Service.PeerName; peerName != "" { + if peerName := query.Service.Peer; peerName != "" { reply.PeerName = peerName reply.Datacenter = "" } else { @@ -756,7 +756,7 @@ func queryFailover(q queryServer, query *structs.PreparedQuery, } } - if target.PeerName != "" { + if target.Peer != "" { targets = append(targets, target) } } @@ -777,9 +777,9 @@ func queryFailover(q queryServer, query *structs.PreparedQuery, // Reset PeerName because it may have been set by a previous failover // target. - query.Service.PeerName = target.PeerName + query.Service.Peer = target.Peer dc := target.Datacenter - if target.PeerName != "" { + if target.Peer != "" { dc = q.GetLocalDC() } @@ -798,7 +798,7 @@ func queryFailover(q queryServer, query *structs.PreparedQuery, if err = q.ExecuteRemote(remote, reply); err != nil { q.GetLogger().Warn("Failed querying for service in datacenter", "service", query.Service.Service, - "peerName", query.Service.PeerName, + "peerName", query.Service.Peer, "datacenter", dc, "error", err, ) diff --git a/agent/consul/prepared_query_endpoint_test.go b/agent/consul/prepared_query_endpoint_test.go index ad46ca4cc0..108a568498 100644 --- a/agent/consul/prepared_query_endpoint_test.go +++ b/agent/consul/prepared_query_endpoint_test.go @@ -21,12 +21,14 @@ import ( "github.com/hashicorp/consul-net-rpc/net/rpc" "github.com/hashicorp/consul/acl" + "github.com/hashicorp/consul/agent/connect" grpcexternal "github.com/hashicorp/consul/agent/grpc-external" "github.com/hashicorp/consul/agent/structs" "github.com/hashicorp/consul/agent/structs/aclfilter" tokenStore "github.com/hashicorp/consul/agent/token" "github.com/hashicorp/consul/api" "github.com/hashicorp/consul/proto/pbpeering" + "github.com/hashicorp/consul/sdk/freeport" "github.com/hashicorp/consul/sdk/testutil/retry" "github.com/hashicorp/consul/testrpc" "github.com/hashicorp/consul/types" @@ -88,7 +90,7 @@ func TestPreparedQuery_Apply(t *testing.T) { // Fix that and ensure Targets and NearestN cannot be set at the same time. query.Query.Service.Failover.NearestN = 1 - query.Query.Service.Failover.Targets = []structs.QueryFailoverTarget{{PeerName: "peer"}} + query.Query.Service.Failover.Targets = []structs.QueryFailoverTarget{{Peer: "peer"}} err = msgpackrpc.CallWithCodec(codec, "PreparedQuery.Apply", &query, &reply) if err == nil || !strings.Contains(err.Error(), "Targets cannot be populated with") { t.Fatalf("bad: %v", err) @@ -97,7 +99,7 @@ func TestPreparedQuery_Apply(t *testing.T) { // Fix that and ensure Targets and Datacenters cannot be set at the same time. query.Query.Service.Failover.NearestN = 0 query.Query.Service.Failover.Datacenters = []string{"dc2"} - query.Query.Service.Failover.Targets = []structs.QueryFailoverTarget{{PeerName: "peer"}} + query.Query.Service.Failover.Targets = []structs.QueryFailoverTarget{{Peer: "peer"}} err = msgpackrpc.CallWithCodec(codec, "PreparedQuery.Apply", &query, &reply) if err == nil || !strings.Contains(err.Error(), "Targets cannot be populated with") { t.Fatalf("bad: %v", err) @@ -1463,10 +1465,20 @@ func TestPreparedQuery_Execute(t *testing.T) { s2.tokens.UpdateReplicationToken("root", tokenStore.TokenSourceConfig) + ca := connect.TestCA(t, nil) dir3, s3 := testServerWithConfig(t, func(c *Config) { c.Datacenter = "dc3" c.PrimaryDatacenter = "dc3" c.NodeName = "acceptingServer.dc3" + c.GRPCTLSPort = freeport.GetOne(t) + c.CAConfig = &structs.CAConfiguration{ + ClusterID: connect.TestClusterID, + Provider: structs.ConsulCAProvider, + Config: map[string]interface{}{ + "PrivateKey": ca.SigningKey, + "RootCert": ca.RootCert, + }, + } }) defer os.RemoveAll(dir3) defer s3.Shutdown() @@ -1493,13 +1505,15 @@ func TestPreparedQuery_Execute(t *testing.T) { acceptingPeerName := "my-peer-accepting-server" dialingPeerName := "my-peer-dialing-server" - // Set up peering between dc1 (dailing) and dc3 (accepting) and export the foo service + // Set up peering between dc1 (dialing) and dc3 (accepting) and export the foo service { // Create a peering by generating a token. ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) t.Cleanup(cancel) - ctx = grpcexternal.ContextWithToken(ctx, "root") + options := structs.QueryOptions{Token: "root"} + ctx, err := grpcexternal.ContextWithQueryOptions(ctx, options) + require.NoError(t, err) conn, err := grpc.DialContext(ctx, s3.config.RPCAddr.String(), grpc.WithContextDialer(newServerDialer(s3.config.RPCAddr.String())), @@ -1550,7 +1564,7 @@ func TestPreparedQuery_Execute(t *testing.T) { Services: []structs.ExportedService{ { Name: "foo", - Consumers: []structs.ServiceConsumer{{PeerName: dialingPeerName}}, + Consumers: []structs.ServiceConsumer{{Peer: dialingPeerName}}, }, }, }, @@ -2427,7 +2441,7 @@ func TestPreparedQuery_Execute(t *testing.T) { query.Query.Service.Failover = structs.QueryFailoverOptions{ Targets: []structs.QueryFailoverTarget{ {Datacenter: "dc2"}, - {PeerName: acceptingPeerName}, + {Peer: acceptingPeerName}, }, } require.NoError(t, msgpackrpc.CallWithCodec(codec1, "PreparedQuery.Apply", &query, &query.Query.ID)) @@ -2948,7 +2962,7 @@ func (m *mockQueryServer) GetOtherDatacentersByDistance() ([]string, error) { } func (m *mockQueryServer) ExecuteRemote(args *structs.PreparedQueryExecuteRemoteRequest, reply *structs.PreparedQueryExecuteResponse) error { - peerName := args.Query.Service.PeerName + peerName := args.Query.Service.Peer dc := args.Datacenter if peerName != "" { m.QueryLog = append(m.QueryLog, fmt.Sprintf("peer:%s", peerName)) @@ -3300,15 +3314,15 @@ func TestPreparedQuery_queryFailover(t *testing.T) { // Failover returns data from the first cluster peer with data. query.Service.Failover.Datacenters = nil query.Service.Failover.Targets = []structs.QueryFailoverTarget{ - {PeerName: "cluster-01"}, + {Peer: "cluster-01"}, {Datacenter: "dc44"}, - {PeerName: "cluster-02"}, + {Peer: "cluster-02"}, } { mock := &mockQueryServer{ Datacenters: []string{"dc44"}, QueryFn: func(args *structs.PreparedQueryExecuteRemoteRequest, reply *structs.PreparedQueryExecuteResponse) error { - if args.Query.Service.PeerName == "cluster-02" { + if args.Query.Service.Peer == "cluster-02" { reply.Nodes = nodes() } return nil diff --git a/agent/consul/server.go b/agent/consul/server.go index 991a4535ba..d6e412fe2f 100644 --- a/agent/consul/server.go +++ b/agent/consul/server.go @@ -2,6 +2,7 @@ package consul import ( "context" + "crypto/x509" "errors" "fmt" "io" @@ -17,6 +18,7 @@ import ( "time" "github.com/armon/go-metrics" + "github.com/hashicorp/consul/agent/hcp" connlimit "github.com/hashicorp/go-connlimit" "github.com/hashicorp/go-hclog" "github.com/hashicorp/go-memdb" @@ -60,6 +62,7 @@ import ( "github.com/hashicorp/consul/proto/pbsubscribe" "github.com/hashicorp/consul/tlsutil" "github.com/hashicorp/consul/types" + cslversion "github.com/hashicorp/consul/version" ) // NOTE The "consul.client.rpc" and "consul.client.rpc.exceeded" counters are defined in consul/client.go @@ -379,6 +382,9 @@ type Server struct { // server is able to handle. xdsCapacityController *xdscapacity.Controller + // hcpManager handles pushing server status updates to the HashiCorp Cloud Platform when enabled + hcpManager *hcp.Manager + // embedded struct to hold all the enterprise specific data EnterpriseServer } @@ -448,6 +454,12 @@ func NewServer(config *Config, flat Deps, externalGRPCServer *grpc.Server) (*Ser publisher: flat.EventPublisher, } + s.hcpManager = hcp.NewManager(hcp.ManagerConfig{ + Client: flat.HCP.Client, + StatusFn: s.hcpServerStatus(flat), + Logger: logger.Named("hcp_manager"), + }) + var recorder *middleware.RequestRecorder if flat.NewRequestRecorderFunc != nil { recorder = flat.NewRequestRecorderFunc(serverLogger, s.IsLeader, s.config.Datacenter) @@ -789,6 +801,9 @@ func NewServer(config *Config, flat Deps, externalGRPCServer *grpc.Server) (*Ser // Start the metrics handlers. go s.updateMetrics() + // Now we are setup, configure the HCP manager + go s.hcpManager.Run(&lib.StopChannelContext{StopCh: shutdownCh}) + return s, nil } @@ -1712,6 +1727,9 @@ func (s *Server) trackLeaderChanges() { s.grpcLeaderForwarder.UpdateLeaderAddr(s.config.Datacenter, string(leaderObs.LeaderAddr)) s.peeringBackend.SetLeaderAddress(string(leaderObs.LeaderAddr)) + + // Trigger sending an update to HCP status + s.hcpManager.SendUpdate() case <-s.shutdownCh: s.raft.DeregisterObserver(observer) return @@ -1719,6 +1737,61 @@ func (s *Server) trackLeaderChanges() { } } +// hcpServerStatus is the callback used by the HCP manager to emit status updates to the HashiCorp Cloud Platform when +// enabled. +func (s *Server) hcpServerStatus(deps Deps) hcp.StatusCallback { + return func(ctx context.Context) (status hcp.ServerStatus, err error) { + status.Name = s.config.NodeName + status.ID = string(s.config.NodeID) + status.Version = cslversion.GetHumanVersion() + status.LanAddress = s.config.RPCAdvertise.IP.String() + status.GossipPort = s.config.SerfLANConfig.MemberlistConfig.AdvertisePort + status.RPCPort = s.config.RPCAddr.Port + + tlsCert := s.tlsConfigurator.Cert() + if tlsCert != nil { + status.TLS.Enabled = true + leaf := tlsCert.Leaf + if leaf == nil { + // Parse the leaf cert + leaf, err = x509.ParseCertificate(tlsCert.Certificate[0]) + if err != nil { + // Shouldn't be possible + return + } + } + status.TLS.CertName = leaf.Subject.CommonName + status.TLS.CertSerial = leaf.SerialNumber.String() + status.TLS.CertExpiry = leaf.NotAfter + status.TLS.VerifyIncoming = s.tlsConfigurator.VerifyIncomingRPC() + status.TLS.VerifyOutgoing = s.tlsConfigurator.Base().InternalRPC.VerifyOutgoing + status.TLS.VerifyServerHostname = s.tlsConfigurator.VerifyServerHostname() + } + + status.Raft.IsLeader = s.raft.State() == raft.Leader + _, leaderID := s.raft.LeaderWithID() + status.Raft.KnownLeader = leaderID != "" + status.Raft.AppliedIndex = s.raft.AppliedIndex() + if !status.Raft.IsLeader { + status.Raft.TimeSinceLastContact = time.Since(s.raft.LastContact()) + } + + apState := s.autopilot.GetState() + status.Autopilot.Healthy = apState.Healthy + status.Autopilot.FailureTolerance = apState.FailureTolerance + status.Autopilot.NumServers = len(apState.Servers) + status.Autopilot.NumVoters = len(apState.Voters) + status.Autopilot.MinQuorum = int(s.getAutopilotConfigOrDefault().MinQuorum) + + status.ScadaStatus = "unknown" + if deps.HCP.Provider != nil { + status.ScadaStatus = deps.HCP.Provider.SessionStatus() + } + + return status, nil + } +} + // peersInfoContent is used to help operators understand what happened to the // peers.json file. This is written to a file called peers.info in the same // location. diff --git a/agent/consul/server_serf.go b/agent/consul/server_serf.go index 80f44aedc2..a515589303 100644 --- a/agent/consul/server_serf.go +++ b/agent/consul/server_serf.go @@ -153,11 +153,11 @@ func (s *Server) setupSerfConfig(opts setupSerfOptions) (*serf.Config, error) { serfLogger := s.logger. NamedIntercept(logging.Serf). NamedIntercept(subLoggerName). - StandardLoggerIntercept(&hclog.StandardLoggerOptions{InferLevels: true}) + StandardLogger(&hclog.StandardLoggerOptions{InferLevels: true}) memberlistLogger := s.logger. NamedIntercept(logging.Memberlist). NamedIntercept(subLoggerName). - StandardLoggerIntercept(&hclog.StandardLoggerOptions{InferLevels: true}) + StandardLogger(&hclog.StandardLoggerOptions{InferLevels: true}) conf.MemberlistConfig.Logger = memberlistLogger conf.Logger = serfLogger diff --git a/agent/consul/server_test.go b/agent/consul/server_test.go index 5a3a2dea16..b9f8e7db90 100644 --- a/agent/consul/server_test.go +++ b/agent/consul/server_test.go @@ -1,6 +1,7 @@ package consul import ( + "context" "crypto/tls" "crypto/x509" "fmt" @@ -15,10 +16,12 @@ import ( "github.com/armon/go-metrics" "github.com/google/tcpproxy" + "github.com/hashicorp/consul/agent/hcp" "github.com/hashicorp/go-hclog" - "github.com/hashicorp/go-uuid" + uuid "github.com/hashicorp/go-uuid" "github.com/hashicorp/memberlist" "github.com/hashicorp/raft" + "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" "golang.org/x/time/rate" "google.golang.org/grpc" @@ -229,7 +232,7 @@ func testServerWithConfig(t *testing.T, configOpts ...func(*Config)) (string, *S } // Apply config to copied fields because many tests only set the old - //values. + // values. config.ACLResolverSettings.ACLsEnabled = config.ACLsEnabled config.ACLResolverSettings.NodeName = config.NodeName config.ACLResolverSettings.Datacenter = config.Datacenter @@ -244,15 +247,32 @@ func testServerWithConfig(t *testing.T, configOpts ...func(*Config)) (string, *S }) t.Cleanup(func() { srv.Shutdown() }) - if srv.config.GRPCPort > 0 { + for _, grpcPort := range []int{srv.config.GRPCPort, srv.config.GRPCTLSPort} { + if grpcPort == 0 { + continue + } + // Normally the gRPC server listener is created at the agent level and // passed down into the Server creation. - externalGRPCAddr := fmt.Sprintf("127.0.0.1:%d", srv.config.GRPCPort) - ln, err := net.Listen("tcp", externalGRPCAddr) + ln, err := net.Listen("tcp", fmt.Sprintf("127.0.0.1:%d", grpcPort)) require.NoError(t, err) - // Wrap the listener with TLS - if deps.TLSConfigurator.GRPCServerUseTLS() { + if grpcPort == srv.config.GRPCTLSPort || deps.TLSConfigurator.GRPCServerUseTLS() { + // Set the internally managed server certificate. The cert manager is hooked to the Agent, so we need to bypass that here. + if srv.config.PeeringEnabled && srv.config.ConnectEnabled { + key, _ := srv.config.CAConfig.Config["PrivateKey"].(string) + cert, _ := srv.config.CAConfig.Config["RootCert"].(string) + if key != "" && cert != "" { + ca := &structs.CARoot{ + SigningKey: key, + RootCert: cert, + } + require.NoError(t, deps.TLSConfigurator.UpdateAutoTLSCert(connect.TestServerLeaf(t, srv.config.Datacenter, ca))) + deps.TLSConfigurator.UpdateAutoTLSPeeringServerName(connect.PeeringServerSAN("dc1", connect.TestTrustDomain)) + } + } + + // Wrap the listener with TLS. ln = tls.NewListener(ln, deps.TLSConfigurator.IncomingGRPCConfig()) } @@ -2012,3 +2032,27 @@ func TestServer_Peering_LeadershipCheck(t *testing.T) { // test corollary by transitivity to future-proof against any setup bugs require.NotEqual(t, s2.config.RPCAddr.String(), peeringLeaderAddr) } + +func TestServer_hcpManager(t *testing.T) { + _, conf1 := testServerConfig(t) + conf1.BootstrapExpect = 1 + conf1.RPCAdvertise = &net.TCPAddr{IP: []byte{127, 0, 0, 2}, Port: conf1.RPCAddr.Port} + hcp1 := hcp.NewMockClient(t) + hcp1.EXPECT().PushServerStatus(mock.Anything, mock.MatchedBy(func(status *hcp.ServerStatus) bool { + return status.ID == string(conf1.NodeID) + })).Run(func(ctx context.Context, status *hcp.ServerStatus) { + require.Equal(t, status.LanAddress, "127.0.0.2") + }).Call.Return(nil) + + deps1 := newDefaultDeps(t, conf1) + deps1.HCP.Client = hcp1 + s1, err := newServerWithDeps(t, conf1, deps1) + if err != nil { + t.Fatalf("err: %v", err) + } + defer s1.Shutdown() + require.NotNil(t, s1.hcpManager) + waitForLeaderEstablishment(t, s1) + hcp1.AssertExpectations(t) + +} diff --git a/agent/consul/servercert/manager.go b/agent/consul/servercert/manager.go index d600fa6e6f..dd15e66a8f 100644 --- a/agent/consul/servercert/manager.go +++ b/agent/consul/servercert/manager.go @@ -150,7 +150,6 @@ func (m *CertManager) watchServerToken(ctx context.Context) { // Cancel existing the leaf cert watch and spin up new one any time the server token changes. // The watch needs the current token as set by the leader since certificate signing requests go to the leader. - fmt.Println("canceling and resetting") cancel() notifyCtx, cancel = context.WithCancel(ctx) diff --git a/agent/consul/state/config_entry_oss_test.go b/agent/consul/state/config_entry_oss_test.go index 4d121ba32d..16f153f3b6 100644 --- a/agent/consul/state/config_entry_oss_test.go +++ b/agent/consul/state/config_entry_oss_test.go @@ -63,7 +63,7 @@ func TestStore_peersForService(t *testing.T) { Name: "not-" + queryName, Consumers: []structs.ServiceConsumer{ { - PeerName: "zip", + Peer: "zip", }, }, }, @@ -80,7 +80,7 @@ func TestStore_peersForService(t *testing.T) { Name: "not-" + queryName, Consumers: []structs.ServiceConsumer{ { - PeerName: "zip", + Peer: "zip", }, }, }, @@ -88,10 +88,10 @@ func TestStore_peersForService(t *testing.T) { Name: structs.WildcardSpecifier, Consumers: []structs.ServiceConsumer{ { - PeerName: "bar", + Peer: "bar", }, { - PeerName: "baz", + Peer: "baz", }, }, }, @@ -108,7 +108,7 @@ func TestStore_peersForService(t *testing.T) { Name: queryName, Consumers: []structs.ServiceConsumer{ { - PeerName: "baz", + Peer: "baz", }, }, }, @@ -116,7 +116,7 @@ func TestStore_peersForService(t *testing.T) { Name: structs.WildcardSpecifier, Consumers: []structs.ServiceConsumer{ { - PeerName: "zip", + Peer: "zip", }, }, }, diff --git a/agent/consul/state/config_entry_test.go b/agent/consul/state/config_entry_test.go index 2731899331..e32b185343 100644 --- a/agent/consul/state/config_entry_test.go +++ b/agent/consul/state/config_entry_test.go @@ -1569,7 +1569,7 @@ func TestStore_ConfigEntry_GraphValidation(t *testing.T) { Name: "default", Services: []structs.ExportedService{{ Name: "main", - Consumers: []structs.ServiceConsumer{{PeerName: "my-peer"}}, + Consumers: []structs.ServiceConsumer{{Peer: "my-peer"}}, }}, }, expectErr: `contains cross-datacenter resolver redirect`, @@ -1588,7 +1588,7 @@ func TestStore_ConfigEntry_GraphValidation(t *testing.T) { Name: "default", Services: []structs.ExportedService{{ Name: "*", - Consumers: []structs.ServiceConsumer{{PeerName: "my-peer"}}, + Consumers: []structs.ServiceConsumer{{Peer: "my-peer"}}, }}, }, expectErr: `contains cross-datacenter resolver redirect`, @@ -1609,7 +1609,7 @@ func TestStore_ConfigEntry_GraphValidation(t *testing.T) { Name: "default", Services: []structs.ExportedService{{ Name: "main", - Consumers: []structs.ServiceConsumer{{PeerName: "my-peer"}}, + Consumers: []structs.ServiceConsumer{{Peer: "my-peer"}}, }}, }, expectErr: `contains cross-datacenter failover`, @@ -1630,7 +1630,7 @@ func TestStore_ConfigEntry_GraphValidation(t *testing.T) { Name: "default", Services: []structs.ExportedService{{ Name: "*", - Consumers: []structs.ServiceConsumer{{PeerName: "my-peer"}}, + Consumers: []structs.ServiceConsumer{{Peer: "my-peer"}}, }}, }, expectErr: `contains cross-datacenter failover`, @@ -1641,7 +1641,7 @@ func TestStore_ConfigEntry_GraphValidation(t *testing.T) { Name: "default", Services: []structs.ExportedService{{ Name: "main", - Consumers: []structs.ServiceConsumer{{PeerName: "my-peer"}}, + Consumers: []structs.ServiceConsumer{{Peer: "my-peer"}}, }}, }, }, diff --git a/agent/consul/state/peering.go b/agent/consul/state/peering.go index eef76aa726..0fbf09e7df 100644 --- a/agent/consul/state/peering.go +++ b/agent/consul/state/peering.go @@ -584,10 +584,7 @@ func (s *Store) PeeringWrite(idx uint64, req *pbpeering.PeeringWriteRequest) err if req.Peering.State == pbpeering.PeeringState_UNDEFINED { req.Peering.State = existing.State } - // TODO(peering): Confirm behavior when /peering/token is called more than once. - // We may need to avoid clobbering existing values. - req.Peering.ImportedServiceCount = existing.ImportedServiceCount - req.Peering.ExportedServiceCount = existing.ExportedServiceCount + req.Peering.StreamStatus = nil req.Peering.CreateIndex = existing.CreateIndex req.Peering.ModifyIndex = idx } else { @@ -792,7 +789,7 @@ func exportedServicesForPeerTxn( // Service was covered by a wildcard that was already accounted for continue } - if consumer.PeerName != peering.Name { + if consumer.Peer != peering.Name { continue } sawPeer = true @@ -938,7 +935,7 @@ func listServicesExportedToAnyPeerByConfigEntry( sawPeer := false for _, consumer := range svc.Consumers { - if consumer.PeerName == "" { + if consumer.Peer == "" { continue } sawPeer = true @@ -1310,8 +1307,8 @@ func peersForServiceTxn( } for _, c := range entry.Services[targetIdx].Consumers { - if c.PeerName != "" { - results = append(results, c.PeerName) + if c.Peer != "" { + results = append(results, c.Peer) } } return idx, results, nil diff --git a/agent/consul/state/peering_test.go b/agent/consul/state/peering_test.go index a90727f0eb..e46a041429 100644 --- a/agent/consul/state/peering_test.go +++ b/agent/consul/state/peering_test.go @@ -1686,19 +1686,19 @@ func TestStateStore_ExportedServicesForPeer(t *testing.T) { { Name: "mysql", Consumers: []structs.ServiceConsumer{ - {PeerName: "my-peering"}, + {Peer: "my-peering"}, }, }, { Name: "redis", Consumers: []structs.ServiceConsumer{ - {PeerName: "my-peering"}, + {Peer: "my-peering"}, }, }, { Name: "mongo", Consumers: []structs.ServiceConsumer{ - {PeerName: "my-other-peering"}, + {Peer: "my-other-peering"}, }, }, }, @@ -1758,7 +1758,7 @@ func TestStateStore_ExportedServicesForPeer(t *testing.T) { { Name: "*", Consumers: []structs.ServiceConsumer{ - {PeerName: "my-peering"}, + {Peer: "my-peering"}, }, }, }, @@ -2046,10 +2046,10 @@ func TestStateStore_PeeringsForService(t *testing.T) { Name: "foo", Consumers: []structs.ServiceConsumer{ { - PeerName: "peer1", + Peer: "peer1", }, { - PeerName: "peer2", + Peer: "peer2", }, }, }, @@ -2090,7 +2090,7 @@ func TestStateStore_PeeringsForService(t *testing.T) { Name: "foo", Consumers: []structs.ServiceConsumer{ { - PeerName: "peer1", + Peer: "peer1", }, }, }, @@ -2098,7 +2098,7 @@ func TestStateStore_PeeringsForService(t *testing.T) { Name: "bar", Consumers: []structs.ServiceConsumer{ { - PeerName: "peer2", + Peer: "peer2", }, }, }, @@ -2148,10 +2148,10 @@ func TestStateStore_PeeringsForService(t *testing.T) { Name: "*", Consumers: []structs.ServiceConsumer{ { - PeerName: "peer1", + Peer: "peer1", }, { - PeerName: "peer2", + Peer: "peer2", }, }, }, @@ -2159,7 +2159,7 @@ func TestStateStore_PeeringsForService(t *testing.T) { Name: "bar", Consumers: []structs.ServiceConsumer{ { - PeerName: "peer3", + Peer: "peer3", }, }, }, @@ -2261,7 +2261,7 @@ func TestStore_TrustBundleListByService(t *testing.T) { Name: "foo", Consumers: []structs.ServiceConsumer{ { - PeerName: "peer1", + Peer: "peer1", }, }, }, @@ -2318,7 +2318,7 @@ func TestStore_TrustBundleListByService(t *testing.T) { Name: "foo", Consumers: []structs.ServiceConsumer{ { - PeerName: "peer1", + Peer: "peer1", }, }, }, @@ -2371,10 +2371,10 @@ func TestStore_TrustBundleListByService(t *testing.T) { Name: "foo", Consumers: []structs.ServiceConsumer{ { - PeerName: "peer1", + Peer: "peer1", }, { - PeerName: "peer2", + Peer: "peer2", }, }, }, diff --git a/agent/dns.go b/agent/dns.go index b627f1f4a8..d458928e80 100644 --- a/agent/dns.go +++ b/agent/dns.go @@ -5,16 +5,16 @@ import ( "encoding/hex" "errors" "fmt" + "math" "net" "regexp" "strings" "sync/atomic" "time" + "github.com/armon/go-metrics" "github.com/armon/go-metrics/prometheus" - - metrics "github.com/armon/go-metrics" - radix "github.com/armon/go-radix" + "github.com/armon/go-radix" "github.com/coredns/coredns/plugin/pkg/dnsutil" "github.com/hashicorp/go-hclog" "github.com/miekg/dns" @@ -61,6 +61,13 @@ const ( staleCounterThreshold = 5 * time.Second defaultMaxUDPSize = 512 + + // If a consumer sets a buffer size greater than this amount we will default it down + // to this amount to ensure that consul does respond. Previously if consumer had a larger buffer + // size than 65535 - 60 bytes (maximim 60 bytes for IP header. UDP header will be offset in the + // trimUDP call) consul would fail to respond and the consumer timesout + // the request. + maxUDPDatagramSize = math.MaxUint16 - 68 ) type dnsSOAConfig struct { @@ -139,13 +146,13 @@ func NewDNSServer(a *Agent) (*DNSServer, error) { // Make sure domains are FQDN, make them case insensitive for ServeMux domain := dns.Fqdn(strings.ToLower(a.config.DNSDomain)) altDomain := dns.Fqdn(strings.ToLower(a.config.DNSAltDomain)) - srv := &DNSServer{ agent: a, domain: domain, altDomain: altDomain, logger: a.logger.Named(logging.DNS), defaultEnterpriseMeta: *a.AgentEnterpriseMeta(), + mux: dns.NewServeMux(), } cfg, err := GetDNSConfig(a.config) if err != nil { @@ -153,6 +160,19 @@ func NewDNSServer(a *Agent) (*DNSServer, error) { } srv.config.Store(cfg) + srv.mux.HandleFunc("arpa.", srv.handlePtr) + srv.mux.HandleFunc(srv.domain, srv.handleQuery) + // this is not an empty string check because NewDNSServer will have + // converted the configured alt domain into an FQDN which will ensure that + // the value ends with a ".". Therefore "." is the empty string equivalent + // for originally having no alternate domain set. If there is a reason + // why consul should be configured to handle the root zone I have yet + // to think of it. + if srv.altDomain != "." { + srv.mux.HandleFunc(srv.altDomain, srv.handleQuery) + } + srv.toggleRecursorHandlerFromConfig(cfg) + return srv, nil } @@ -227,22 +247,6 @@ func (cfg *dnsConfig) GetTTLForService(service string) (time.Duration, bool) { } func (d *DNSServer) ListenAndServe(network, addr string, notif func()) error { - cfg := d.config.Load().(*dnsConfig) - - d.mux = dns.NewServeMux() - d.mux.HandleFunc("arpa.", d.handlePtr) - d.mux.HandleFunc(d.domain, d.handleQuery) - // this is not an empty string check because NewDNSServer will have - // converted the configured alt domain into an FQDN which will ensure that - // the value ends with a ".". Therefore "." is the empty string equivalent - // for originally having no alternate domain set. If there is a reason - // why consul should be configured to handle the root zone I have yet - // to think of it. - if d.altDomain != "." { - d.mux.HandleFunc(d.altDomain, d.handleQuery) - } - d.toggleRecursorHandlerFromConfig(cfg) - d.Server = &dns.Server{ Addr: addr, Net: network, @@ -1258,6 +1262,11 @@ func trimUDPResponse(req, resp *dns.Msg, udpAnswerLimit int) (trimmed bool) { maxSize = int(size) } } + // Overriding maxSize as the maxSize cannot be larger than the + // maxUDPDatagram size. Reliability guarantees disappear > than this amount. + if maxSize > maxUDPDatagramSize { + maxSize = maxUDPDatagramSize + } // We avoid some function calls and allocations by only handling the // extra data when necessary. @@ -1286,8 +1295,9 @@ func trimUDPResponse(req, resp *dns.Msg, udpAnswerLimit int) (trimmed bool) { // will allow our responses to be compliant even if some downstream server // uncompresses them. // Even when size is too big for one single record, try to send it anyway - // (useful for 512 bytes messages) - for len(resp.Answer) > 1 && resp.Len() > maxSize-7 { + // (useful for 512 bytes messages). 8 is removed from maxSize to ensure that we account + // for the udp header (8 bytes). + for len(resp.Answer) > 1 && resp.Len() > maxSize-8 { // first try to remove the NS section may be it will truncate enough if len(resp.Ns) != 0 { resp.Ns = []dns.RR{} diff --git a/agent/dns_test.go b/agent/dns_test.go index 9f876eaebb..2f2499a2ef 100644 --- a/agent/dns_test.go +++ b/agent/dns_test.go @@ -3,6 +3,7 @@ package agent import ( "errors" "fmt" + "math" "math/rand" "net" "reflect" @@ -7563,6 +7564,55 @@ func TestDNS_trimUDPResponse_TrimSizeEDNS(t *testing.T) { } } +func TestDNS_trimUDPResponse_TrimSizeMaxSize(t *testing.T) { + t.Parallel() + cfg := loadRuntimeConfig(t, `node_name = "test" data_dir = "a" bind_addr = "127.0.0.1" node_name = "dummy"`) + + resp := &dns.Msg{} + + for i := 0; i < 600; i++ { + target := fmt.Sprintf("ip-10-0-1-%d.node.dc1.consul.", 150+i) + srv := &dns.SRV{ + Hdr: dns.RR_Header{ + Name: "redis-cache-redis.service.consul.", + Rrtype: dns.TypeSRV, + Class: dns.ClassINET, + }, + Target: target, + } + a := &dns.A{ + Hdr: dns.RR_Header{ + Name: target, + Rrtype: dns.TypeA, + Class: dns.ClassINET, + }, + A: net.ParseIP(fmt.Sprintf("10.0.1.%d", 150+i)), + } + + resp.Answer = append(resp.Answer, srv) + resp.Extra = append(resp.Extra, a) + } + + reqEDNS, respEDNS := &dns.Msg{}, &dns.Msg{} + reqEDNS.SetEdns0(math.MaxUint16, true) + respEDNS.Answer = append(respEDNS.Answer, resp.Answer...) + respEDNS.Extra = append(respEDNS.Extra, resp.Extra...) + require.Greater(t, respEDNS.Len(), math.MaxUint16) + t.Logf("length is: %v", respEDNS.Len()) + + if trimmed := trimUDPResponse(reqEDNS, respEDNS, cfg.DNSUDPAnswerLimit); !trimmed { + t.Errorf("expected edns to be trimmed: %#v", resp) + } + require.Greater(t, math.MaxUint16, respEDNS.Len()) + + t.Logf("length is: %v", respEDNS.Len()) + + if len(respEDNS.Answer) == 0 || len(respEDNS.Answer) != len(respEDNS.Extra) { + t.Errorf("bad edns answer length: %#v", resp) + } + +} + func TestDNS_syncExtra(t *testing.T) { t.Parallel() resp := &dns.Msg{ diff --git a/agent/grpc-external/options.go b/agent/grpc-external/options.go new file mode 100644 index 0000000000..851a04cbfe --- /dev/null +++ b/agent/grpc-external/options.go @@ -0,0 +1,58 @@ +package external + +import ( + "context" + "fmt" + + "github.com/hashicorp/consul/agent/structs" + "github.com/mitchellh/mapstructure" + "google.golang.org/grpc/metadata" +) + +// QueryOptionsFromContext returns the query options in the gRPC metadata attached to the +// given context. +func QueryOptionsFromContext(ctx context.Context) (structs.QueryOptions, error) { + options := structs.QueryOptions{} + md, ok := metadata.FromIncomingContext(ctx) + if !ok { + return options, nil + } + + m := map[string]string{} + for k, v := range md { + m[k] = v[0] + } + + config := &mapstructure.DecoderConfig{ + Metadata: nil, + Result: &options, + WeaklyTypedInput: true, + DecodeHook: mapstructure.StringToTimeDurationHookFunc(), + } + + decoder, err := mapstructure.NewDecoder(config) + if err != nil { + return structs.QueryOptions{}, err + } + + err = decoder.Decode(m) + if err != nil { + return structs.QueryOptions{}, err + } + + return options, nil +} + +// ContextWithQueryOptions returns a context with the given query options attached. +func ContextWithQueryOptions(ctx context.Context, options structs.QueryOptions) (context.Context, error) { + md := metadata.MD{} + m := map[string]interface{}{} + err := mapstructure.Decode(options, &m) + if err != nil { + return nil, err + } + for k, v := range m { + md.Set(k, fmt.Sprintf("%v", v)) + } + return metadata.NewOutgoingContext(ctx, md), nil +} diff --git a/agent/grpc-external/options_test.go b/agent/grpc-external/options_test.go new file mode 100644 index 0000000000..f7d6e67be2 --- /dev/null +++ b/agent/grpc-external/options_test.go @@ -0,0 +1,39 @@ +package external + +import ( + "context" + "testing" + "time" + + "github.com/hashicorp/consul/agent/structs" + "github.com/stretchr/testify/require" + "google.golang.org/grpc/metadata" +) + +func TestQueryOptionsFromContextRoundTrip(t *testing.T) { + + expected := structs.QueryOptions{ + Token: "123", + AllowStale: true, + MinQueryIndex: uint64(10), + MaxAge: 1 * time.Hour, + } + + ctx, err := ContextWithQueryOptions(context.Background(), expected) + if err != nil { + t.Fatal(err) + } + + out, ok := metadata.FromOutgoingContext(ctx) + if !ok { + t.Fatalf("cannot get metadata from context") + } + ctx = metadata.NewIncomingContext(ctx, out) + + actual, err := QueryOptionsFromContext(ctx) + if err != nil { + t.Fatal(err) + } + + require.Equal(t, expected, actual) +} diff --git a/agent/grpc-external/services/connectca/sign.go b/agent/grpc-external/services/connectca/sign.go index edd48fe58e..891d8c9889 100644 --- a/agent/grpc-external/services/connectca/sign.go +++ b/agent/grpc-external/services/connectca/sign.go @@ -25,7 +25,10 @@ func (s *Server) Sign(ctx context.Context, req *pbconnectca.SignRequest) (*pbcon logger := s.Logger.Named("sign").With("request_id", external.TraceID()) logger.Trace("request received") - token := external.TokenFromContext(ctx) + options, err := external.QueryOptionsFromContext(ctx) + if err != nil { + return nil, err + } if req.Csr == "" { return nil, status.Error(codes.InvalidArgument, "CSR is required") @@ -43,7 +46,7 @@ func (s *Server) Sign(ctx context.Context, req *pbconnectca.SignRequest) (*pbcon structs.WriteRequest structs.DCSpecificRequest } - rpcInfo.Token = token + rpcInfo.Token = options.Token var rsp *pbconnectca.SignResponse handled, err := s.ForwardRPC(&rpcInfo, func(conn *grpc.ClientConn) error { @@ -62,7 +65,7 @@ func (s *Server) Sign(ctx context.Context, req *pbconnectca.SignRequest) (*pbcon return nil, status.Error(codes.InvalidArgument, err.Error()) } - authz, err := s.ACLResolver.ResolveTokenAndDefaultMeta(token, nil, nil) + authz, err := s.ACLResolver.ResolveTokenAndDefaultMeta(options.Token, nil, nil) if err != nil { return nil, status.Error(codes.Unauthenticated, err.Error()) } diff --git a/agent/grpc-external/services/connectca/watch_roots.go b/agent/grpc-external/services/connectca/watch_roots.go index 9c61f8bdd3..b2ee9f4914 100644 --- a/agent/grpc-external/services/connectca/watch_roots.go +++ b/agent/grpc-external/services/connectca/watch_roots.go @@ -32,7 +32,10 @@ func (s *Server) WatchRoots(_ *pbconnectca.WatchRootsRequest, serverStream pbcon logger.Trace("starting stream") defer logger.Trace("stream closed") - token := external.TokenFromContext(serverStream.Context()) + options, err := external.QueryOptionsFromContext(serverStream.Context()) + if err != nil { + return err + } // Serve the roots from an EventPublisher subscription. If the subscription is // closed due to an ACL change, we'll attempt to re-authorize and resume it to @@ -40,7 +43,7 @@ func (s *Server) WatchRoots(_ *pbconnectca.WatchRootsRequest, serverStream pbcon var idx uint64 for { var err error - idx, err = s.serveRoots(token, idx, serverStream, logger) + idx, err = s.serveRoots(options.Token, idx, serverStream, logger) if errors.Is(err, stream.ErrSubForceClosed) { logger.Trace("subscription force-closed due to an ACL change or snapshot restore, will attempt to re-auth and resume") } else { diff --git a/agent/grpc-external/services/connectca/watch_roots_test.go b/agent/grpc-external/services/connectca/watch_roots_test.go index 2491417bb9..f5c65a6201 100644 --- a/agent/grpc-external/services/connectca/watch_roots_test.go +++ b/agent/grpc-external/services/connectca/watch_roots_test.go @@ -56,7 +56,9 @@ func TestWatchRoots_Success(t *testing.T) { aclResolver.On("ResolveTokenAndDefaultMeta", testACLToken, mock.Anything, mock.Anything). Return(testutils.TestAuthorizerServiceWriteAny(t), nil) - ctx := external.ContextWithToken(context.Background(), testACLToken) + options := structs.QueryOptions{Token: testACLToken} + ctx, err := external.ContextWithQueryOptions(context.Background(), options) + require.NoError(t, err) server := NewServer(Config{ Publisher: publisher, @@ -104,7 +106,9 @@ func TestWatchRoots_InvalidACLToken(t *testing.T) { aclResolver.On("ResolveTokenAndDefaultMeta", mock.Anything, mock.Anything, mock.Anything). Return(resolver.Result{}, acl.ErrNotFound) - ctx := external.ContextWithToken(context.Background(), testACLToken) + options := structs.QueryOptions{Token: testACLToken} + ctx, err := external.ContextWithQueryOptions(context.Background(), options) + require.NoError(t, err) server := NewServer(Config{ Publisher: publisher, @@ -142,7 +146,9 @@ func TestWatchRoots_ACLTokenInvalidated(t *testing.T) { aclResolver.On("ResolveTokenAndDefaultMeta", testACLToken, mock.Anything, mock.Anything). Return(testutils.TestAuthorizerServiceWriteAny(t), nil).Twice() - ctx := external.ContextWithToken(context.Background(), testACLToken) + options := structs.QueryOptions{Token: testACLToken} + ctx, err := external.ContextWithQueryOptions(context.Background(), options) + require.NoError(t, err) server := NewServer(Config{ Publisher: publisher, @@ -210,7 +216,9 @@ func TestWatchRoots_StateStoreAbandoned(t *testing.T) { aclResolver.On("ResolveTokenAndDefaultMeta", testACLToken, mock.Anything, mock.Anything). Return(testutils.TestAuthorizerServiceWriteAny(t), nil) - ctx := external.ContextWithToken(context.Background(), testACLToken) + options := structs.QueryOptions{Token: testACLToken} + ctx, err := external.ContextWithQueryOptions(context.Background(), options) + require.NoError(t, err) server := NewServer(Config{ Publisher: publisher, diff --git a/agent/grpc-external/services/dataplane/get_envoy_bootstrap_params.go b/agent/grpc-external/services/dataplane/get_envoy_bootstrap_params.go index b320559e98..08f53578fc 100644 --- a/agent/grpc-external/services/dataplane/get_envoy_bootstrap_params.go +++ b/agent/grpc-external/services/dataplane/get_envoy_bootstrap_params.go @@ -9,10 +9,11 @@ import ( "google.golang.org/grpc/status" "google.golang.org/protobuf/types/known/structpb" - acl "github.com/hashicorp/consul/acl" + "github.com/hashicorp/consul/acl" + "github.com/hashicorp/consul/agent/configentry" "github.com/hashicorp/consul/agent/consul/state" external "github.com/hashicorp/consul/agent/grpc-external" - structs "github.com/hashicorp/consul/agent/structs" + "github.com/hashicorp/consul/agent/structs" "github.com/hashicorp/consul/proto-public/pbdataplane" ) @@ -22,10 +23,14 @@ func (s *Server) GetEnvoyBootstrapParams(ctx context.Context, req *pbdataplane.G logger.Trace("Started processing request") defer logger.Trace("Finished processing request") - token := external.TokenFromContext(ctx) + options, err := external.QueryOptionsFromContext(ctx) + if err != nil { + return nil, err + } + var authzContext acl.AuthorizerContext entMeta := acl.NewEnterpriseMetaWithPartition(req.GetPartition(), req.GetNamespace()) - authz, err := s.ACLResolver.ResolveTokenAndDefaultMeta(token, &entMeta, &authzContext) + authz, err := s.ACLResolver.ResolveTokenAndDefaultMeta(options.Token, &entMeta, &authzContext) if err != nil { return nil, status.Error(codes.Unauthenticated, err.Error()) } @@ -69,7 +74,24 @@ func (s *Server) GetEnvoyBootstrapParams(ctx context.Context, req *pbdataplane.G NodeId: string(svc.ID), } - bootstrapConfig, err := structpb.NewStruct(svc.ServiceProxy.Config) + // This is awkward because it's designed for different requests, but + // this fakes the ServiceSpecificRequest so that we can reuse code. + _, ns, err := configentry.MergeNodeServiceWithCentralConfig( + nil, + store, + &structs.ServiceSpecificRequest{ + Datacenter: s.Datacenter, + QueryOptions: options, + }, + svc.ToNodeService(), + logger, + ) + if err != nil { + logger.Error("Error merging with central config", "error", err) + return nil, status.Errorf(codes.Unknown, "Error merging central config: %v", err) + } + + bootstrapConfig, err := structpb.NewStruct(ns.Proxy.Config) if err != nil { logger.Error("Error creating the envoy boostrap params config", "error", err) return nil, status.Error(codes.Unknown, "Error creating the envoy boostrap params config") diff --git a/agent/grpc-external/services/dataplane/get_envoy_bootstrap_params_test.go b/agent/grpc-external/services/dataplane/get_envoy_bootstrap_params_test.go index aa42b0bf13..55aeca0e38 100644 --- a/agent/grpc-external/services/dataplane/get_envoy_bootstrap_params_test.go +++ b/agent/grpc-external/services/dataplane/get_envoy_bootstrap_params_test.go @@ -5,29 +5,39 @@ import ( "testing" "github.com/hashicorp/go-hclog" - mock "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" "google.golang.org/protobuf/types/known/structpb" - acl "github.com/hashicorp/consul/acl" - resolver "github.com/hashicorp/consul/acl/resolver" + "github.com/hashicorp/consul/acl" + "github.com/hashicorp/consul/acl/resolver" external "github.com/hashicorp/consul/agent/grpc-external" "github.com/hashicorp/consul/agent/grpc-external/testutils" - structs "github.com/hashicorp/consul/agent/structs" + "github.com/hashicorp/consul/agent/structs" "github.com/hashicorp/consul/proto-public/pbdataplane" "github.com/hashicorp/consul/types" ) const ( testToken = "acl-token-get-envoy-bootstrap-params" + testServiceName = "web" proxyServiceID = "web-proxy" nodeName = "foo" nodeID = "2980b72b-bd9d-9d7b-d4f9-951bf7508d95" proxyConfigKey = "envoy_dogstatsd_url" proxyConfigValue = "udp://127.0.0.1:8125" serverDC = "dc1" + + protocolKey = "protocol" + connectTimeoutKey = "local_connect_timeout_ms" + requestTimeoutKey = "local_request_timeout_ms" + + proxyDefaultsProtocol = "http" + proxyDefaultsRequestTimeout = 1111 + serviceDefaultsProtocol = "tcp" + serviceDefaultsConnectTimeout = 4444 ) func testRegisterRequestProxy(t *testing.T) *structs.RegisterRequest { @@ -43,7 +53,7 @@ func testRegisterRequestProxy(t *testing.T) *structs.RegisterRequest { Address: "127.0.0.2", Port: 2222, Proxy: structs.ConnectProxyConfig{ - DestinationServiceName: "web", + DestinationServiceName: testServiceName, Config: map[string]interface{}{ proxyConfigKey: proxyConfigValue, }, @@ -63,22 +73,64 @@ func testRegisterIngressGateway(t *testing.T) *structs.RegisterRequest { return registerReq } +func testProxyDefaults(t *testing.T) structs.ConfigEntry { + return &structs.ProxyConfigEntry{ + Kind: structs.ProxyDefaults, + Name: structs.ProxyConfigGlobal, + Config: map[string]interface{}{ + protocolKey: proxyDefaultsProtocol, + requestTimeoutKey: proxyDefaultsRequestTimeout, + }, + } +} + +func testServiceDefaults(t *testing.T) structs.ConfigEntry { + return &structs.ServiceConfigEntry{ + Kind: structs.ServiceDefaults, + Name: testServiceName, + Protocol: serviceDefaultsProtocol, + LocalConnectTimeoutMs: serviceDefaultsConnectTimeout, + } +} + +func requireConfigField(t *testing.T, resp *pbdataplane.GetEnvoyBootstrapParamsResponse, key string, value interface{}) { + require.Contains(t, resp.Config.Fields, key) + require.Equal(t, value, resp.Config.Fields[key]) +} + func TestGetEnvoyBootstrapParams_Success(t *testing.T) { type testCase struct { - name string - registerReq *structs.RegisterRequest - nodeID bool + name string + registerReq *structs.RegisterRequest + nodeID bool + proxyDefaults structs.ConfigEntry + serviceDefaults structs.ConfigEntry } run := func(t *testing.T, tc testCase) { store := testutils.TestStateStore(t, nil) - err := store.EnsureRegistration(1, tc.registerReq) + idx := uint64(1) + err := store.EnsureRegistration(idx, tc.registerReq) require.NoError(t, err) + if tc.proxyDefaults != nil { + idx++ + err := store.EnsureConfigEntry(idx, tc.proxyDefaults) + require.NoError(t, err) + } + if tc.serviceDefaults != nil { + idx++ + err := store.EnsureConfigEntry(idx, tc.serviceDefaults) + require.NoError(t, err) + } + aclResolver := &MockACLResolver{} aclResolver.On("ResolveTokenAndDefaultMeta", testToken, mock.Anything, mock.Anything). Return(testutils.TestAuthorizerServiceRead(t, tc.registerReq.Service.ID), nil) - ctx := external.ContextWithToken(context.Background(), testToken) + + options := structs.QueryOptions{Token: testToken} + ctx, err := external.ContextWithQueryOptions(context.Background(), options) + require.NoError(t, err) server := NewServer(Config{ GetStore: func() StateStore { return store }, @@ -106,20 +158,33 @@ func TestGetEnvoyBootstrapParams_Success(t *testing.T) { require.Equal(t, serverDC, resp.Datacenter) require.Equal(t, tc.registerReq.EnterpriseMeta.PartitionOrDefault(), resp.Partition) require.Equal(t, tc.registerReq.EnterpriseMeta.NamespaceOrDefault(), resp.Namespace) - require.Contains(t, resp.Config.Fields, proxyConfigKey) - require.Equal(t, structpb.NewStringValue(proxyConfigValue), resp.Config.Fields[proxyConfigKey]) + requireConfigField(t, resp, proxyConfigKey, structpb.NewStringValue(proxyConfigValue)) require.Equal(t, convertToResponseServiceKind(tc.registerReq.Service.Kind), resp.ServiceKind) require.Equal(t, tc.registerReq.Node, resp.NodeName) require.Equal(t, string(tc.registerReq.ID), resp.NodeId) + + if tc.serviceDefaults != nil && tc.proxyDefaults != nil { + // service-defaults take precedence over proxy-defaults + requireConfigField(t, resp, protocolKey, structpb.NewStringValue(serviceDefaultsProtocol)) + requireConfigField(t, resp, connectTimeoutKey, structpb.NewNumberValue(serviceDefaultsConnectTimeout)) + requireConfigField(t, resp, requestTimeoutKey, structpb.NewNumberValue(proxyDefaultsRequestTimeout)) + } else if tc.serviceDefaults != nil { + requireConfigField(t, resp, protocolKey, structpb.NewStringValue(serviceDefaultsProtocol)) + requireConfigField(t, resp, connectTimeoutKey, structpb.NewNumberValue(serviceDefaultsConnectTimeout)) + } else if tc.proxyDefaults != nil { + requireConfigField(t, resp, protocolKey, structpb.NewStringValue(proxyDefaultsProtocol)) + requireConfigField(t, resp, requestTimeoutKey, structpb.NewNumberValue(proxyDefaultsRequestTimeout)) + } + } testCases := []testCase{ { - name: "lookup service side car proxy by node name", + name: "lookup service sidecar proxy by node name", registerReq: testRegisterRequestProxy(t), }, { - name: "lookup service side car proxy by node ID", + name: "lookup service sidecar proxy by node ID", registerReq: testRegisterRequestProxy(t), nodeID: true, }, @@ -132,6 +197,21 @@ func TestGetEnvoyBootstrapParams_Success(t *testing.T) { registerReq: testRegisterIngressGateway(t), nodeID: true, }, + { + name: "merge proxy defaults for sidecar proxy", + registerReq: testRegisterRequestProxy(t), + proxyDefaults: testProxyDefaults(t), + }, + { + name: "merge service defaults for sidecar proxy", + registerReq: testRegisterRequestProxy(t), + serviceDefaults: testServiceDefaults(t), + }, + { + name: "merge proxy defaults and service defaults for sidecar proxy", + registerReq: testRegisterRequestProxy(t), + serviceDefaults: testServiceDefaults(t), + }, } for _, tc := range testCases { @@ -154,11 +234,14 @@ func TestGetEnvoyBootstrapParams_Error(t *testing.T) { aclResolver.On("ResolveTokenAndDefaultMeta", testToken, mock.Anything, mock.Anything). Return(testutils.TestAuthorizerServiceRead(t, proxyServiceID), nil) - ctx := external.ContextWithToken(context.Background(), testToken) + + options := structs.QueryOptions{Token: testToken} + ctx, err := external.ContextWithQueryOptions(context.Background(), options) + require.NoError(t, err) store := testutils.TestStateStore(t, nil) registerReq := testRegisterRequestProxy(t) - err := store.EnsureRegistration(1, registerReq) + err = store.EnsureRegistration(1, registerReq) require.NoError(t, err) server := NewServer(Config{ @@ -224,8 +307,12 @@ func TestGetEnvoyBootstrapParams_Unauthenticated(t *testing.T) { aclResolver := &MockACLResolver{} aclResolver.On("ResolveTokenAndDefaultMeta", mock.Anything, mock.Anything, mock.Anything). Return(resolver.Result{}, acl.ErrNotFound) - ctx := external.ContextWithToken(context.Background(), testToken) + + options := structs.QueryOptions{Token: testToken} + ctx, err := external.ContextWithQueryOptions(context.Background(), options) + require.NoError(t, err) store := testutils.TestStateStore(t, nil) + server := NewServer(Config{ GetStore: func() StateStore { return store }, Logger: hclog.NewNullLogger(), @@ -243,12 +330,16 @@ func TestGetEnvoyBootstrapParams_PermissionDenied(t *testing.T) { aclResolver := &MockACLResolver{} aclResolver.On("ResolveTokenAndDefaultMeta", testToken, mock.Anything, mock.Anything). Return(testutils.TestAuthorizerDenyAll(t), nil) - ctx := external.ContextWithToken(context.Background(), testToken) + + options := structs.QueryOptions{Token: testToken} + ctx, err := external.ContextWithQueryOptions(context.Background(), options) + require.NoError(t, err) + store := testutils.TestStateStore(t, nil) registerReq := structs.TestRegisterRequestProxy(t) proxyServiceID := "web-sidecar-proxy" registerReq.Service.ID = proxyServiceID - err := store.EnsureRegistration(1, registerReq) + err = store.EnsureRegistration(1, registerReq) require.NoError(t, err) server := NewServer(Config{ diff --git a/agent/grpc-external/services/dataplane/get_supported_features.go b/agent/grpc-external/services/dataplane/get_supported_features.go index 79041aa04a..4d3abc0edd 100644 --- a/agent/grpc-external/services/dataplane/get_supported_features.go +++ b/agent/grpc-external/services/dataplane/get_supported_features.go @@ -19,10 +19,14 @@ func (s *Server) GetSupportedDataplaneFeatures(ctx context.Context, req *pbdatap defer logger.Trace("Finished processing request") // Require the given ACL token to have `service:write` on any service - token := external.TokenFromContext(ctx) + options, err := external.QueryOptionsFromContext(ctx) + if err != nil { + return nil, status.Error(codes.Internal, err.Error()) + } + var authzContext acl.AuthorizerContext entMeta := structs.WildcardEnterpriseMetaInPartition(structs.WildcardSpecifier) - authz, err := s.ACLResolver.ResolveTokenAndDefaultMeta(token, entMeta, &authzContext) + authz, err := s.ACLResolver.ResolveTokenAndDefaultMeta(options.Token, entMeta, &authzContext) if err != nil { return nil, status.Error(codes.Unauthenticated, err.Error()) } diff --git a/agent/grpc-external/services/dataplane/get_supported_features_test.go b/agent/grpc-external/services/dataplane/get_supported_features_test.go index 822fd6b5b4..52ff3f30a5 100644 --- a/agent/grpc-external/services/dataplane/get_supported_features_test.go +++ b/agent/grpc-external/services/dataplane/get_supported_features_test.go @@ -14,6 +14,7 @@ import ( resolver "github.com/hashicorp/consul/acl/resolver" external "github.com/hashicorp/consul/agent/grpc-external" "github.com/hashicorp/consul/agent/grpc-external/testutils" + structs "github.com/hashicorp/consul/agent/structs" "github.com/hashicorp/consul/proto-public/pbdataplane" ) @@ -24,7 +25,11 @@ func TestSupportedDataplaneFeatures_Success(t *testing.T) { aclResolver := &MockACLResolver{} aclResolver.On("ResolveTokenAndDefaultMeta", testACLToken, mock.Anything, mock.Anything). Return(testutils.TestAuthorizerServiceWriteAny(t), nil) - ctx := external.ContextWithToken(context.Background(), testACLToken) + + options := structs.QueryOptions{Token: testACLToken} + ctx, err := external.ContextWithQueryOptions(context.Background(), options) + require.NoError(t, err) + server := NewServer(Config{ Logger: hclog.NewNullLogger(), ACLResolver: aclResolver, @@ -53,7 +58,11 @@ func TestSupportedDataplaneFeatures_Unauthenticated(t *testing.T) { aclResolver := &MockACLResolver{} aclResolver.On("ResolveTokenAndDefaultMeta", mock.Anything, mock.Anything, mock.Anything). Return(resolver.Result{}, acl.ErrNotFound) - ctx := external.ContextWithToken(context.Background(), testACLToken) + + options := structs.QueryOptions{Token: testACLToken} + ctx, err := external.ContextWithQueryOptions(context.Background(), options) + require.NoError(t, err) + server := NewServer(Config{ Logger: hclog.NewNullLogger(), ACLResolver: aclResolver, @@ -70,7 +79,11 @@ func TestSupportedDataplaneFeatures_PermissionDenied(t *testing.T) { aclResolver := &MockACLResolver{} aclResolver.On("ResolveTokenAndDefaultMeta", testACLToken, mock.Anything, mock.Anything). Return(testutils.TestAuthorizerDenyAll(t), nil) - ctx := external.ContextWithToken(context.Background(), testACLToken) + + options := structs.QueryOptions{Token: testACLToken} + ctx, err := external.ContextWithQueryOptions(context.Background(), options) + require.NoError(t, err) + server := NewServer(Config{ Logger: hclog.NewNullLogger(), ACLResolver: aclResolver, diff --git a/agent/grpc-external/services/dataplane/server.go b/agent/grpc-external/services/dataplane/server.go index 4b4aef061e..b665b368a6 100644 --- a/agent/grpc-external/services/dataplane/server.go +++ b/agent/grpc-external/services/dataplane/server.go @@ -4,9 +4,11 @@ import ( "google.golang.org/grpc" "github.com/hashicorp/go-hclog" + "github.com/hashicorp/go-memdb" "github.com/hashicorp/consul/acl" "github.com/hashicorp/consul/acl/resolver" + "github.com/hashicorp/consul/agent/configentry" "github.com/hashicorp/consul/agent/structs" "github.com/hashicorp/consul/proto-public/pbdataplane" ) @@ -25,6 +27,7 @@ type Config struct { type StateStore interface { ServiceNode(string, string, string, *acl.EnterpriseMeta, string) (uint64, *structs.ServiceNode, error) + ReadResolvedServiceConfigEntries(memdb.WatchSet, string, *acl.EnterpriseMeta, []structs.ServiceID, structs.ProxyMode) (uint64, *configentry.ResolvedServiceConfigSet, error) } //go:generate mockery --name ACLResolver --inpackage diff --git a/agent/grpc-external/services/dns/server.go b/agent/grpc-external/services/dns/server.go new file mode 100644 index 0000000000..50b12f5d91 --- /dev/null +++ b/agent/grpc-external/services/dns/server.go @@ -0,0 +1,138 @@ +package dns + +import ( + "context" + "fmt" + "net" + + "github.com/hashicorp/go-hclog" + "github.com/miekg/dns" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/peer" + "google.golang.org/grpc/status" + + "github.com/hashicorp/consul/proto-public/pbdns" +) + +type LocalAddr struct { + IP net.IP + Port int +} + +type Config struct { + Logger hclog.Logger + DNSServeMux *dns.ServeMux + LocalAddr LocalAddr +} + +type Server struct { + Config +} + +func NewServer(cfg Config) *Server { + return &Server{cfg} +} + +func (s *Server) Register(grpcServer *grpc.Server) { + pbdns.RegisterDNSServiceServer(grpcServer, s) +} + +// BufferResponseWriter writes a DNS response to a byte buffer. +type BufferResponseWriter struct { + responseBuffer []byte + LocalAddress net.Addr + RemoteAddress net.Addr + Logger hclog.Logger +} + +// LocalAddr returns the net.Addr of the server +func (b *BufferResponseWriter) LocalAddr() net.Addr { + return b.LocalAddress +} + +// RemoteAddr returns the net.Addr of the client that sent the current request. +func (b *BufferResponseWriter) RemoteAddr() net.Addr { + return b.RemoteAddress +} + +// WriteMsg writes a reply back to the client. +func (b *BufferResponseWriter) WriteMsg(m *dns.Msg) error { + // Pack message to bytes first. + msgBytes, err := m.Pack() + if err != nil { + b.Logger.Error("error packing message", "err", err) + return err + } + b.responseBuffer = msgBytes + return nil +} + +// Write writes a raw buffer back to the client. +func (b *BufferResponseWriter) Write(m []byte) (int, error) { + b.Logger.Debug("Write was called") + return copy(b.responseBuffer, m), nil +} + +// Close closes the connection. +func (b *BufferResponseWriter) Close() error { + // There's nothing for us to do here as we don't handle the connection. + return nil +} + +// TsigStatus returns the status of the Tsig. +func (b *BufferResponseWriter) TsigStatus() error { + // TSIG doesn't apply to this response writer. + return nil +} + +// TsigTimersOnly sets the tsig timers only boolean. +func (b *BufferResponseWriter) TsigTimersOnly(bool) {} + +// Hijack lets the caller take over the connection. +// After a call to Hijack(), the DNS package will not do anything with the connection. { +func (b *BufferResponseWriter) Hijack() {} + +// Query is a gRPC endpoint that will serve dns requests. It will be consumed primarily by the +// consul dataplane to proxy dns requests to consul. +func (s *Server) Query(ctx context.Context, req *pbdns.QueryRequest) (*pbdns.QueryResponse, error) { + pr, ok := peer.FromContext(ctx) + if !ok { + return nil, fmt.Errorf("error retrieving peer information from context") + } + + var local net.Addr + var remote net.Addr + // We do this so that we switch to udp/tcp when handling the request since it will be proxied + // through consul through gRPC and we need to 'fake' the protocol so that the message is trimmed + // according to wether it is UDP or TCP. + switch req.GetProtocol() { + case pbdns.Protocol_PROTOCOL_TCP: + remote = pr.Addr + local = &net.TCPAddr{IP: s.LocalAddr.IP, Port: s.LocalAddr.Port} + case pbdns.Protocol_PROTOCOL_UDP: + remoteAddr := pr.Addr.(*net.TCPAddr) + remote = &net.UDPAddr{IP: remoteAddr.IP, Port: remoteAddr.Port} + local = &net.UDPAddr{IP: s.LocalAddr.IP, Port: s.LocalAddr.Port} + default: + return nil, status.Error(codes.InvalidArgument, fmt.Sprintf("error protocol type not set: %v", req.GetProtocol())) + } + + respWriter := &BufferResponseWriter{ + LocalAddress: local, + RemoteAddress: remote, + Logger: s.Logger, + } + + msg := &dns.Msg{} + err := msg.Unpack(req.Msg) + if err != nil { + s.Logger.Error("error unpacking message", "err", err) + return nil, status.Error(codes.Internal, fmt.Sprintf("failure decoding dns request: %s", err.Error())) + } + s.DNSServeMux.ServeDNS(respWriter, msg) + + queryResponse := &pbdns.QueryResponse{Msg: respWriter.responseBuffer} + + return queryResponse, nil +} diff --git a/agent/grpc-external/services/dns/server_test.go b/agent/grpc-external/services/dns/server_test.go new file mode 100644 index 0000000000..b477fed498 --- /dev/null +++ b/agent/grpc-external/services/dns/server_test.go @@ -0,0 +1,127 @@ +package dns + +import ( + "context" + "errors" + "net" + "testing" + + "github.com/hashicorp/go-hclog" + "github.com/miekg/dns" + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" + "google.golang.org/grpc" + + "github.com/hashicorp/consul/agent/grpc-external/testutils" + "github.com/hashicorp/consul/proto-public/pbdns" +) + +var txtRR = []string{"Hello world"} + +func helloServer(w dns.ResponseWriter, req *dns.Msg) { + m := new(dns.Msg) + m.SetReply(req) + + m.Extra = make([]dns.RR, 1) + m.Extra[0] = &dns.TXT{ + Hdr: dns.RR_Header{Name: m.Question[0].Name, Rrtype: dns.TypeTXT, Class: dns.ClassINET, Ttl: 0}, + Txt: txtRR, + } + w.WriteMsg(m) +} + +func testClient(t *testing.T, server *Server) pbdns.DNSServiceClient { + t.Helper() + + addr := testutils.RunTestServer(t, server) + + conn, err := grpc.DialContext(context.Background(), addr.String(), grpc.WithInsecure()) + require.NoError(t, err) + t.Cleanup(func() { + require.NoError(t, conn.Close()) + }) + + return pbdns.NewDNSServiceClient(conn) +} + +type DNSTestSuite struct { + suite.Suite +} + +func TestDNS_suite(t *testing.T) { + suite.Run(t, new(DNSTestSuite)) +} + +func (s *DNSTestSuite) TestProxy_Success() { + mux := dns.NewServeMux() + mux.Handle(".", dns.HandlerFunc(helloServer)) + server := NewServer(Config{ + Logger: hclog.Default(), + DNSServeMux: mux, + LocalAddr: LocalAddr{ + net.IPv4(127, 0, 0, 1), + 0, + }, + }) + + client := testClient(s.T(), server) + + testCases := map[string]struct { + question string + clientQuery func(qR *pbdns.QueryRequest) + expectedErr error + }{ + + "happy path udp": { + question: "abc.com.", + clientQuery: func(qR *pbdns.QueryRequest) { + qR.Protocol = pbdns.Protocol_PROTOCOL_UDP + }, + }, + "happy path tcp": { + question: "abc.com.", + clientQuery: func(qR *pbdns.QueryRequest) { + qR.Protocol = pbdns.Protocol_PROTOCOL_TCP + }, + }, + "No protocol set": { + question: "abc.com.", + clientQuery: func(qR *pbdns.QueryRequest) {}, + expectedErr: errors.New("error protocol type not set: PROTOCOL_UNSET_UNSPECIFIED"), + }, + "Invalid question": { + question: "notvalid", + clientQuery: func(qR *pbdns.QueryRequest) { + qR.Protocol = pbdns.Protocol_PROTOCOL_UDP + }, + expectedErr: errors.New("failure decoding dns request"), + }, + } + + for name, tc := range testCases { + s.Run(name, func() { + req := dns.Msg{} + req.SetQuestion(tc.question, dns.TypeA) + + bytes, _ := req.Pack() + + clientReq := &pbdns.QueryRequest{Msg: bytes} + tc.clientQuery(clientReq) + clientResp, err := client.Query(context.Background(), clientReq) + if tc.expectedErr != nil { + s.Require().Error(err, "no errror calling gRPC endpoint") + s.Require().ErrorContains(err, tc.expectedErr.Error()) + } else { + s.Require().NoError(err, "error calling gRPC endpoint") + + resp := clientResp.GetMsg() + var dnsResp dns.Msg + + err = dnsResp.Unpack(resp) + s.Require().NoError(err, "error unpacking dns response") + rr := dnsResp.Extra[0].(*dns.TXT) + s.Require().EqualValues(rr.Txt, txtRR) + } + }) + } +} diff --git a/agent/grpc-external/services/peerstream/replication.go b/agent/grpc-external/services/peerstream/replication.go index 20bc3b12a8..9b2a61a5ba 100644 --- a/agent/grpc-external/services/peerstream/replication.go +++ b/agent/grpc-external/services/peerstream/replication.go @@ -23,15 +23,45 @@ import ( /* TODO(peering): - At the start of each peering stream establishment (not initiation, but the - thing that reconnects) we need to do a little bit of light differential - snapshot correction to initially synchronize the local state store. - Then if we ever fail to apply a replication message we should either tear down the entire connection (and thus force a resync on reconnect) or request a resync operation. */ +// makeExportedServiceListResponse handles preparing exported service list updates to the peer cluster. +// Each cache.UpdateEvent will contain all exported services. +func makeExportedServiceListResponse( + mst *MutableStatus, + update cache.UpdateEvent, +) (*pbpeerstream.ReplicationMessage_Response, error) { + exportedService, ok := update.Result.(*pbpeerstream.ExportedServiceList) + if !ok { + return nil, fmt.Errorf("invalid type for exported service list response: %T", update.Result) + } + + any, _, err := marshalToProtoAny[*pbpeerstream.ExportedServiceList](exportedService) + if err != nil { + return nil, fmt.Errorf("failed to marshal: %w", err) + } + + var serviceNames []structs.ServiceName + for _, serviceName := range exportedService.Services { + sn := structs.ServiceNameFromString(serviceName) + serviceNames = append(serviceNames, sn) + } + + mst.SetExportedServices(serviceNames) + + return &pbpeerstream.ReplicationMessage_Response{ + ResourceURL: pbpeerstream.TypeURLExportedServiceList, + // TODO(peering): Nonce management + Nonce: "", + ResourceID: subExportedServiceList, + Operation: pbpeerstream.Operation_OPERATION_UPSERT, + Resource: any, + }, nil +} + // makeServiceResponse handles preparing exported service instance updates to the peer cluster. // Each cache.UpdateEvent will contain all instances for a service name. // If there are no instances in the event, we consider that to be a de-registration. @@ -40,7 +70,6 @@ func makeServiceResponse( update cache.UpdateEvent, ) (*pbpeerstream.ReplicationMessage_Response, error) { serviceName := strings.TrimPrefix(update.CorrelationID, subExportedService) - sn := structs.ServiceNameFromString(serviceName) csn, ok := update.Result.(*pbservice.IndexedCheckServiceNodes) if !ok { return nil, fmt.Errorf("invalid type for service response: %T", update.Result) @@ -54,28 +83,7 @@ func makeServiceResponse( if err != nil { return nil, fmt.Errorf("failed to marshal: %w", err) } - // If no nodes are present then it's due to one of: - // 1. The service is newly registered or exported and yielded a transient empty update. - // 2. All instances of the service were de-registered. - // 3. The service was un-exported. - // - // We don't distinguish when these three things occurred, but it's safe to send a DELETE Op in all cases, so we do that. - // Case #1 is a no-op for the importing peer. - if len(csn.Nodes) == 0 { - mst.RemoveExportedService(sn) - - return &pbpeerstream.ReplicationMessage_Response{ - ResourceURL: pbpeerstream.TypeURLExportedService, - // TODO(peering): Nonce management - Nonce: "", - ResourceID: serviceName, - Operation: pbpeerstream.Operation_OPERATION_DELETE, - }, nil - } - mst.TrackExportedService(sn) - - // If there are nodes in the response, we push them as an UPSERT operation. return &pbpeerstream.ReplicationMessage_Response{ ResourceURL: pbpeerstream.TypeURLExportedService, // TODO(peering): Nonce management @@ -178,17 +186,6 @@ func (s *Server) processResponse( return makeACKReply(resp.ResourceURL, resp.Nonce), nil - case pbpeerstream.Operation_OPERATION_DELETE: - if err := s.handleDelete(peerName, partition, mutableStatus, resp.ResourceURL, resp.ResourceID); err != nil { - return makeNACKReply( - resp.ResourceURL, - resp.Nonce, - code.Code_INTERNAL, - fmt.Sprintf("delete error, ResourceURL: %q, ResourceID: %q: %v", resp.ResourceURL, resp.ResourceID, err), - ), fmt.Errorf("delete error: %w", err) - } - return makeACKReply(resp.ResourceURL, resp.Nonce), nil - default: var errMsg string if op := pbpeerstream.Operation_name[int32(resp.Operation)]; op != "" { @@ -218,6 +215,18 @@ func (s *Server) handleUpsert( } switch resourceURL { + case pbpeerstream.TypeURLExportedServiceList: + export := &pbpeerstream.ExportedServiceList{} + if err := resource.UnmarshalTo(export); err != nil { + return fmt.Errorf("failed to unmarshal resource: %w", err) + } + + err := s.handleUpsertExportedServiceList(mutableStatus, peerName, partition, export) + if err != nil { + return fmt.Errorf("did not update imported services based on the exported service list event: %w", err) + } + + return nil case pbpeerstream.TypeURLExportedService: sn := structs.ServiceNameFromString(resourceID) sn.OverridePartition(partition) @@ -232,8 +241,6 @@ func (s *Server) handleUpsert( return fmt.Errorf("did not increment imported services count for service=%q: %w", sn.String(), err) } - mutableStatus.TrackImportedService(sn) - return nil case pbpeerstream.TypeURLPeeringTrustBundle: @@ -256,6 +263,48 @@ func (s *Server) handleUpsert( } } +func (s *Server) handleUpsertExportedServiceList( + mutableStatus *MutableStatus, + peerName string, + partition string, + export *pbpeerstream.ExportedServiceList, +) error { + exportedServices := make(map[structs.ServiceName]struct{}) + var serviceNames []structs.ServiceName + for _, service := range export.Services { + sn := structs.ServiceNameFromString(service) + sn.OverridePartition(partition) + + // This ensures that we don't delete exported service's sidecars below. + snSidecarProxy := structs.ServiceNameFromString(service + syntheticProxyNameSuffix) + snSidecarProxy.OverridePartition(partition) + + exportedServices[sn] = struct{}{} + exportedServices[snSidecarProxy] = struct{}{} + serviceNames = append(serviceNames, sn) + } + entMeta := structs.NodeEnterpriseMetaInPartition(partition) + + _, serviceList, err := s.GetStore().ServiceList(nil, entMeta, peerName) + if err != nil { + return err + } + + for _, sn := range serviceList { + if _, ok := exportedServices[sn]; !ok { + err := s.handleUpdateService(peerName, partition, sn, nil) + + if err != nil { + return fmt.Errorf("failed to delete unexported service: %w", err) + } + } + } + + mutableStatus.SetImportedServices(serviceNames) + + return nil +} + // handleUpdateService handles both deletion and upsert events for a service. // // On an UPSERT event: @@ -499,32 +548,6 @@ func (s *Server) handleUpsertServerAddrs( return s.Backend.PeeringWrite(req) } -func (s *Server) handleDelete( - peerName string, - partition string, - mutableStatus *MutableStatus, - resourceURL string, - resourceID string, -) error { - switch resourceURL { - case pbpeerstream.TypeURLExportedService: - sn := structs.ServiceNameFromString(resourceID) - sn.OverridePartition(partition) - - err := s.handleUpdateService(peerName, partition, sn, nil) - if err != nil { - return err - } - - mutableStatus.RemoveImportedService(sn) - - return nil - - default: - return fmt.Errorf("unexpected resourceURL: %s", resourceURL) - } -} - func makeACKReply(resourceURL, nonce string) *pbpeerstream.ReplicationMessage { return makeReplicationRequest(&pbpeerstream.ReplicationMessage_Request{ ResourceURL: resourceURL, diff --git a/agent/grpc-external/services/peerstream/server.go b/agent/grpc-external/services/peerstream/server.go index 0f0627cb5f..8a5e33e93c 100644 --- a/agent/grpc-external/services/peerstream/server.go +++ b/agent/grpc-external/services/peerstream/server.go @@ -122,5 +122,7 @@ type StateStore interface { NodeServices(ws memdb.WatchSet, nodeNameOrID string, entMeta *acl.EnterpriseMeta, peerName string) (uint64, *structs.NodeServices, error) CAConfig(ws memdb.WatchSet) (uint64, *structs.CAConfiguration, error) TrustBundleListByService(ws memdb.WatchSet, service, dc string, entMeta acl.EnterpriseMeta) (uint64, []*pbpeering.PeeringTrustBundle, error) + ServiceList(ws memdb.WatchSet, entMeta *acl.EnterpriseMeta, peerName string) (uint64, structs.ServiceList, error) + ConfigEntry(ws memdb.WatchSet, kind, name string, entMeta *acl.EnterpriseMeta) (uint64, structs.ConfigEntry, error) AbandonCh() <-chan struct{} } diff --git a/agent/grpc-external/services/peerstream/stream_resources.go b/agent/grpc-external/services/peerstream/stream_resources.go index bdad21467e..ce6a5a73ed 100644 --- a/agent/grpc-external/services/peerstream/stream_resources.go +++ b/agent/grpc-external/services/peerstream/stream_resources.go @@ -351,8 +351,14 @@ func (s *Server) realHandleStream(streamReq HandleStreamRequest) error { err := streamReq.Stream.Send(msg) sendMutex.Unlock() - if err != nil { - status.TrackSendError(err.Error()) + // We only track send successes and errors for response types because this is meant to track + // resources, not request/ack messages. + if msg.GetResponse() != nil { + if err != nil { + status.TrackSendError(err.Error()) + } else { + status.TrackSendSuccess() + } } return err } @@ -360,6 +366,7 @@ func (s *Server) realHandleStream(streamReq HandleStreamRequest) error { // Subscribe to all relevant resource types. for _, resourceURL := range []string{ pbpeerstream.TypeURLExportedService, + pbpeerstream.TypeURLExportedServiceList, pbpeerstream.TypeURLPeeringTrustBundle, pbpeerstream.TypeURLPeeringServerAddresses, } { @@ -624,6 +631,13 @@ func (s *Server) realHandleStream(streamReq HandleStreamRequest) error { case update := <-subCh: var resp *pbpeerstream.ReplicationMessage_Response switch { + case strings.HasPrefix(update.CorrelationID, subExportedServiceList): + resp, err = makeExportedServiceListResponse(status, update) + if err != nil { + // Log the error and skip this response to avoid locking up peering due to a bad update event. + logger.Error("failed to create exported service list response", "error", err) + continue + } case strings.HasPrefix(update.CorrelationID, subExportedService): resp, err = makeServiceResponse(status, update) if err != nil { @@ -632,9 +646,6 @@ func (s *Server) realHandleStream(streamReq HandleStreamRequest) error { continue } - case strings.HasPrefix(update.CorrelationID, subMeshGateway): - // TODO(Peering): figure out how to sync this separately - case update.CorrelationID == subCARoot: resp, err = makeCARootsResponse(update) if err != nil { diff --git a/agent/grpc-external/services/peerstream/stream_test.go b/agent/grpc-external/services/peerstream/stream_test.go index 977f7d565c..baf437daa4 100644 --- a/agent/grpc-external/services/peerstream/stream_test.go +++ b/agent/grpc-external/services/peerstream/stream_test.go @@ -43,7 +43,6 @@ import ( const ( testPeerID = "caf067a6-f112-4907-9101-d45857d2b149" - testActiveStreamSecretID = "e778c518-f0db-473a-9224-24b357da971d" testPendingStreamSecretID = "522c0daf-2ef2-4dab-bc78-5e04e3daf552" testEstablishmentSecretID = "f6569d37-1c5b-4415-aae5-26f4594f7f60" ) @@ -126,7 +125,7 @@ func TestStreamResources_Server_LeaderBecomesFollower(t *testing.T) { // Receive a subscription from a peer. This message arrives while the // server is a leader and should work. - testutil.RunStep(t, "send subscription request to leader and consume its three requests", func(t *testing.T) { + testutil.RunStep(t, "send subscription request to leader and consume its four requests", func(t *testing.T) { sub := &pbpeerstream.ReplicationMessage{ Payload: &pbpeerstream.ReplicationMessage_Open_{ Open: &pbpeerstream.ReplicationMessage_Open{ @@ -149,6 +148,10 @@ func TestStreamResources_Server_LeaderBecomesFollower(t *testing.T) { msg3, err := client.Recv() require.NoError(t, err) require.NotEmpty(t, msg3) + + msg4, err := client.Recv() + require.NoError(t, err) + require.NotEmpty(t, msg4) }) // The ACK will be a new request but at this point the server is not the @@ -514,13 +517,7 @@ func TestStreamResources_Server_Terminate(t *testing.T) { client := makeClient(t, srv, testPeerID) - // TODO(peering): test fails if we don't drain the stream with this call because the - // server gets blocked sending the termination message. Figure out a way to let - // messages queue and filter replication messages. - receiveRoots, err := client.Recv() - require.NoError(t, err) - require.NotNil(t, receiveRoots.GetResponse()) - require.Equal(t, pbpeerstream.TypeURLPeeringTrustBundle, receiveRoots.GetResponse().ResourceURL) + client.DrainStream(t) testutil.RunStep(t, "new stream gets tracked", func(t *testing.T) { retry.Run(t, func(r *retry.R) { @@ -559,7 +556,7 @@ func TestStreamResources_Server_StreamTracker(t *testing.T) { srv.Tracker.setClock(it.Now) // Set the initial roots and CA configuration. - _, rootA := writeInitialRootsAndCA(t, store) + writeInitialRootsAndCA(t, store) p := writePeeringToBeDialed(t, store, 1, "my-peer") require.Empty(t, p.PeerID, "should be empty if being dialed") @@ -575,6 +572,14 @@ func TestStreamResources_Server_StreamTracker(t *testing.T) { }) var lastSendAck time.Time + var lastSendSuccess time.Time + + client.DrainStream(t) + + // Manually grab the last success time from sending the trust bundle or exported services list. + status, ok := srv.StreamStatus(testPeerID) + require.True(t, ok) + lastSendSuccess = status.LastSendSuccess testutil.RunStep(t, "ack tracked as success", func(t *testing.T) { ack := &pbpeerstream.ReplicationMessage{ @@ -590,12 +595,15 @@ func TestStreamResources_Server_StreamTracker(t *testing.T) { } lastSendAck = it.FutureNow(1) + err := client.Send(ack) require.NoError(t, err) expect := Status{ - Connected: true, - LastAck: lastSendAck, + Connected: true, + LastSendSuccess: lastSendSuccess, + LastAck: lastSendAck, + ExportedServices: []string{}, } retry.Run(t, func(r *retry.R) { @@ -630,10 +638,12 @@ func TestStreamResources_Server_StreamTracker(t *testing.T) { lastNackMsg = "client peer was unable to apply resource: bad bad not good" expect := Status{ - Connected: true, - LastAck: lastSendAck, - LastNack: lastNack, - LastNackMessage: lastNackMsg, + Connected: true, + LastSendSuccess: lastSendSuccess, + LastAck: lastSendAck, + LastNack: lastNack, + LastNackMessage: lastNackMsg, + ExportedServices: []string{}, } retry.Run(t, func(r *retry.R) { @@ -661,27 +671,6 @@ func TestStreamResources_Server_StreamTracker(t *testing.T) { err := client.Send(resp) require.NoError(t, err) - expectRoots := &pbpeerstream.ReplicationMessage{ - Payload: &pbpeerstream.ReplicationMessage_Response_{ - Response: &pbpeerstream.ReplicationMessage_Response{ - ResourceURL: pbpeerstream.TypeURLPeeringTrustBundle, - ResourceID: "roots", - Resource: makeAnyPB(t, &pbpeering.PeeringTrustBundle{ - TrustDomain: connect.TestTrustDomain, - RootPEMs: []string{rootA.RootCert}, - }), - Operation: pbpeerstream.Operation_OPERATION_UPSERT, - }, - }, - } - - roots, err := client.Recv() - require.NoError(t, err) - prototest.AssertDeepEqual(t, expectRoots, roots) - - ack, err := client.Recv() - require.NoError(t, err) - expectAck := &pbpeerstream.ReplicationMessage{ Payload: &pbpeerstream.ReplicationMessage_Request_{ Request: &pbpeerstream.ReplicationMessage_Request{ @@ -690,19 +679,24 @@ func TestStreamResources_Server_StreamTracker(t *testing.T) { }, }, } - prototest.AssertDeepEqual(t, expectAck, ack) - api := structs.NewServiceName("api", nil) + retry.Run(t, func(r *retry.R) { + msg, err := client.Recv() + require.NoError(r, err) + req := msg.GetRequest() + require.NotNil(r, req) + require.Equal(r, pbpeerstream.TypeURLExportedService, req.ResourceURL) + prototest.AssertDeepEqual(t, expectAck, msg) + }) expect := Status{ Connected: true, + LastSendSuccess: lastSendSuccess, LastAck: lastSendAck, LastNack: lastNack, LastNackMessage: lastNackMsg, LastRecvResourceSuccess: lastRecvResourceSuccess, - ImportedServices: map[string]struct{}{ - api.String(): {}, - }, + ExportedServices: []string{}, } retry.Run(t, func(r *retry.R) { @@ -751,19 +745,16 @@ func TestStreamResources_Server_StreamTracker(t *testing.T) { lastRecvErrorMsg = `unsupported operation: "OPERATION_UNSPECIFIED"` - api := structs.NewServiceName("api", nil) - expect := Status{ Connected: true, + LastSendSuccess: lastSendSuccess, LastAck: lastSendAck, LastNack: lastNack, LastNackMessage: lastNackMsg, LastRecvResourceSuccess: lastRecvResourceSuccess, LastRecvError: lastRecvError, LastRecvErrorMessage: lastRecvErrorMsg, - ImportedServices: map[string]struct{}{ - api.String(): {}, - }, + ExportedServices: []string{}, } retry.Run(t, func(r *retry.R) { @@ -783,10 +774,10 @@ func TestStreamResources_Server_StreamTracker(t *testing.T) { lastRecvHeartbeat = it.FutureNow(1) err := client.Send(resp) require.NoError(t, err) - api := structs.NewServiceName("api", nil) expect := Status{ Connected: true, + LastSendSuccess: lastSendSuccess, LastAck: lastSendAck, LastNack: lastNack, LastNackMessage: lastNackMsg, @@ -794,9 +785,7 @@ func TestStreamResources_Server_StreamTracker(t *testing.T) { LastRecvError: lastRecvError, LastRecvErrorMessage: lastRecvErrorMsg, LastRecvHeartbeat: lastRecvHeartbeat, - ImportedServices: map[string]struct{}{ - api.String(): {}, - }, + ExportedServices: []string{}, } retry.Run(t, func(r *retry.R) { @@ -813,11 +802,10 @@ func TestStreamResources_Server_StreamTracker(t *testing.T) { client.Close() - api := structs.NewServiceName("api", nil) - expect := Status{ Connected: false, DisconnectErrorMessage: lastRecvErrorMsg, + LastSendSuccess: lastSendSuccess, LastAck: lastSendAck, LastNack: lastNack, LastNackMessage: lastNackMsg, @@ -826,9 +814,7 @@ func TestStreamResources_Server_StreamTracker(t *testing.T) { LastRecvError: lastRecvError, LastRecvErrorMessage: lastRecvErrorMsg, LastRecvHeartbeat: lastRecvHeartbeat, - ImportedServices: map[string]struct{}{ - api.String(): {}, - }, + ExportedServices: []string{}, } retry.Run(t, func(r *retry.R) { @@ -890,14 +876,14 @@ func TestStreamResources_Server_ServiceUpdates(t *testing.T) { { Name: "mysql", Consumers: []structs.ServiceConsumer{ - {PeerName: "my-peering"}, + {Peer: "my-peering"}, }, }, { // Mongo does not get pushed because it does not have instances registered. Name: "mongo", Consumers: []structs.ServiceConsumer{ - {PeerName: "my-peering"}, + {Peer: "my-peering"}, }, }, }, @@ -908,6 +894,9 @@ func TestStreamResources_Server_ServiceUpdates(t *testing.T) { require.NoError(t, store.EnsureConfigEntry(lastIdx, entry)) expectReplEvents(t, client, + func(t *testing.T, msg *pbpeerstream.ReplicationMessage) { + require.Equal(t, pbpeerstream.TypeURLPeeringServerAddresses, msg.GetRequest().ResourceURL) + }, func(t *testing.T, msg *pbpeerstream.ReplicationMessage) { require.Equal(t, pbpeerstream.TypeURLPeeringTrustBundle, msg.GetResponse().ResourceURL) // Roots tested in TestStreamResources_Server_CARootUpdates @@ -916,15 +905,21 @@ func TestStreamResources_Server_ServiceUpdates(t *testing.T) { // no mongo instances exist require.Equal(t, pbpeerstream.TypeURLExportedService, msg.GetResponse().ResourceURL) require.Equal(t, mongoSN, msg.GetResponse().ResourceID) - require.Equal(t, pbpeerstream.Operation_OPERATION_DELETE, msg.GetResponse().Operation) - require.Nil(t, msg.GetResponse().Resource) + require.Equal(t, pbpeerstream.Operation_OPERATION_UPSERT, msg.GetResponse().Operation) + + var nodes pbpeerstream.ExportedService + require.NoError(t, msg.GetResponse().Resource.UnmarshalTo(&nodes)) + require.Len(t, nodes.Nodes, 0) }, func(t *testing.T, msg *pbpeerstream.ReplicationMessage) { // proxies can't export because no mesh gateway exists yet require.Equal(t, pbpeerstream.TypeURLExportedService, msg.GetResponse().ResourceURL) require.Equal(t, mongoProxySN, msg.GetResponse().ResourceID) - require.Equal(t, pbpeerstream.Operation_OPERATION_DELETE, msg.GetResponse().Operation) - require.Nil(t, msg.GetResponse().Resource) + require.Equal(t, pbpeerstream.Operation_OPERATION_UPSERT, msg.GetResponse().Operation) + + var nodes pbpeerstream.ExportedService + require.NoError(t, msg.GetResponse().Resource.UnmarshalTo(&nodes)) + require.Len(t, nodes.Nodes, 0) }, func(t *testing.T, msg *pbpeerstream.ReplicationMessage) { require.Equal(t, pbpeerstream.TypeURLExportedService, msg.GetResponse().ResourceURL) @@ -939,8 +934,33 @@ func TestStreamResources_Server_ServiceUpdates(t *testing.T) { // proxies can't export because no mesh gateway exists yet require.Equal(t, pbpeerstream.TypeURLExportedService, msg.GetResponse().ResourceURL) require.Equal(t, mysqlProxySN, msg.GetResponse().ResourceID) - require.Equal(t, pbpeerstream.Operation_OPERATION_DELETE, msg.GetResponse().Operation) - require.Nil(t, msg.GetResponse().Resource) + require.Equal(t, pbpeerstream.Operation_OPERATION_UPSERT, msg.GetResponse().Operation) + + var nodes pbpeerstream.ExportedService + require.NoError(t, msg.GetResponse().Resource.UnmarshalTo(&nodes)) + require.Len(t, nodes.Nodes, 0) + }, + // This event happens because this is the first test case and there are + // no exported services when replication is initially set up. + func(t *testing.T, msg *pbpeerstream.ReplicationMessage) { + require.Equal(t, pbpeerstream.TypeURLExportedServiceList, msg.GetResponse().ResourceURL) + require.Equal(t, subExportedServiceList, msg.GetResponse().ResourceID) + require.Equal(t, pbpeerstream.Operation_OPERATION_UPSERT, msg.GetResponse().Operation) + + var exportedServices pbpeerstream.ExportedServiceList + require.NoError(t, msg.GetResponse().Resource.UnmarshalTo(&exportedServices)) + require.ElementsMatch(t, []string{}, exportedServices.Services) + }, + func(t *testing.T, msg *pbpeerstream.ReplicationMessage) { + require.Equal(t, pbpeerstream.TypeURLExportedServiceList, msg.GetResponse().ResourceURL) + require.Equal(t, subExportedServiceList, msg.GetResponse().ResourceID) + require.Equal(t, pbpeerstream.Operation_OPERATION_UPSERT, msg.GetResponse().Operation) + + var exportedServices pbpeerstream.ExportedServiceList + require.NoError(t, msg.GetResponse().Resource.UnmarshalTo(&exportedServices)) + require.ElementsMatch(t, + []string{structs.ServiceName{Name: "mongo"}.String(), structs.ServiceName{Name: "mysql"}.String()}, + exportedServices.Services) }, ) }) @@ -1019,7 +1039,7 @@ func TestStreamResources_Server_ServiceUpdates(t *testing.T) { }) }) - testutil.RunStep(t, "un-exporting mysql leads to a DELETE event for mysql", func(t *testing.T) { + testutil.RunStep(t, "un-exporting mysql leads to an exported service list update", func(t *testing.T) { entry := &structs.ExportedServicesConfigEntry{ Name: "default", Services: []structs.ExportedService{ @@ -1027,7 +1047,7 @@ func TestStreamResources_Server_ServiceUpdates(t *testing.T) { Name: "mongo", Consumers: []structs.ServiceConsumer{ { - PeerName: "my-peering", + Peer: "my-peering", }, }, }, @@ -1042,23 +1062,30 @@ func TestStreamResources_Server_ServiceUpdates(t *testing.T) { retry.Run(t, func(r *retry.R) { msg, err := client.RecvWithTimeout(100 * time.Millisecond) require.NoError(r, err) - require.Equal(r, pbpeerstream.Operation_OPERATION_DELETE, msg.GetResponse().Operation) - require.Equal(r, mysql.Service.CompoundServiceName().String(), msg.GetResponse().ResourceID) - require.Nil(r, msg.GetResponse().Resource) + require.Equal(r, pbpeerstream.TypeURLExportedServiceList, msg.GetResponse().ResourceURL) + require.Equal(t, subExportedServiceList, msg.GetResponse().ResourceID) + require.Equal(t, pbpeerstream.Operation_OPERATION_UPSERT, msg.GetResponse().Operation) + + var exportedServices pbpeerstream.ExportedServiceList + require.NoError(t, msg.GetResponse().Resource.UnmarshalTo(&exportedServices)) + require.Equal(t, []string{structs.ServiceName{Name: "mongo"}.String()}, exportedServices.Services) }) }) testutil.RunStep(t, "deleting the config entry leads to a DELETE event for mongo", func(t *testing.T) { - lastIdx++ err := store.DeleteConfigEntry(lastIdx, structs.ExportedServices, "default", nil) require.NoError(t, err) retry.Run(t, func(r *retry.R) { msg, err := client.RecvWithTimeout(100 * time.Millisecond) require.NoError(r, err) - require.Equal(r, pbpeerstream.Operation_OPERATION_DELETE, msg.GetResponse().Operation) - require.Equal(r, mongo.Service.CompoundServiceName().String(), msg.GetResponse().ResourceID) - require.Nil(r, msg.GetResponse().Resource) + require.Equal(r, pbpeerstream.TypeURLExportedServiceList, msg.GetResponse().ResourceURL) + require.Equal(t, subExportedServiceList, msg.GetResponse().ResourceID) + require.Equal(t, pbpeerstream.Operation_OPERATION_UPSERT, msg.GetResponse().Operation) + + var exportedServices pbpeerstream.ExportedServiceList + require.NoError(t, msg.GetResponse().Resource.UnmarshalTo(&exportedServices)) + require.Len(t, exportedServices.Services, 0) }) }) } @@ -1078,6 +1105,9 @@ func TestStreamResources_Server_CARootUpdates(t *testing.T) { testutil.RunStep(t, "initial CA Roots replication", func(t *testing.T) { expectReplEvents(t, client, + func(t *testing.T, msg *pbpeerstream.ReplicationMessage) { + require.Equal(t, pbpeerstream.TypeURLPeeringServerAddresses, msg.GetRequest().ResourceURL) + }, func(t *testing.T, msg *pbpeerstream.ReplicationMessage) { require.Equal(t, pbpeerstream.TypeURLPeeringTrustBundle, msg.GetResponse().ResourceURL) require.Equal(t, "roots", msg.GetResponse().ResourceID) @@ -1090,6 +1120,15 @@ func TestStreamResources_Server_CARootUpdates(t *testing.T) { expect := connect.SpiffeIDSigningForCluster(clusterID).Host() require.Equal(t, expect, trustBundle.TrustDomain) }, + func(t *testing.T, msg *pbpeerstream.ReplicationMessage) { + require.Equal(t, pbpeerstream.TypeURLExportedServiceList, msg.GetResponse().ResourceURL) + require.Equal(t, subExportedServiceList, msg.GetResponse().ResourceID) + require.Equal(t, pbpeerstream.Operation_OPERATION_UPSERT, msg.GetResponse().Operation) + + var exportedServices pbpeerstream.ExportedServiceList + require.NoError(t, msg.GetResponse().Resource.UnmarshalTo(&exportedServices)) + require.ElementsMatch(t, []string{}, exportedServices.Services) + }, ) }) @@ -1142,13 +1181,7 @@ func TestStreamResources_Server_DisconnectsOnHeartbeatTimeout(t *testing.T) { client := makeClient(t, srv, testPeerID) - // TODO(peering): test fails if we don't drain the stream with this call because the - // server gets blocked sending the termination message. Figure out a way to let - // messages queue and filter replication messages. - receiveRoots, err := client.Recv() - require.NoError(t, err) - require.NotNil(t, receiveRoots.GetResponse()) - require.Equal(t, pbpeerstream.TypeURLPeeringTrustBundle, receiveRoots.GetResponse().ResourceURL) + client.DrainStream(t) testutil.RunStep(t, "new stream gets tracked", func(t *testing.T) { retry.Run(t, func(r *retry.R) { @@ -1190,16 +1223,10 @@ func TestStreamResources_Server_SendsHeartbeats(t *testing.T) { client := makeClient(t, srv, testPeerID) - // TODO(peering): test fails if we don't drain the stream with this call because the - // server gets blocked sending the termination message. Figure out a way to let - // messages queue and filter replication messages. - receiveRoots, err := client.Recv() - require.NoError(t, err) - require.NotNil(t, receiveRoots.GetResponse()) - require.Equal(t, pbpeerstream.TypeURLPeeringTrustBundle, receiveRoots.GetResponse().ResourceURL) - testutil.RunStep(t, "new stream gets tracked", func(t *testing.T) { retry.Run(t, func(r *retry.R) { + _, err := client.Recv() + require.NoError(r, err) status, ok := srv.StreamStatus(testPeerID) require.True(r, ok) require.True(r, status.Connected) @@ -1212,8 +1239,8 @@ func TestStreamResources_Server_SendsHeartbeats(t *testing.T) { Wait: outgoingHeartbeatInterval / 2, }, t, func(r *retry.R) { heartbeat, err := client.Recv() - require.NoError(t, err) - require.NotNil(t, heartbeat.GetHeartbeat()) + require.NoError(r, err) + require.NotNil(r, heartbeat.GetHeartbeat()) }) }) @@ -1223,8 +1250,8 @@ func TestStreamResources_Server_SendsHeartbeats(t *testing.T) { Wait: outgoingHeartbeatInterval / 2, }, t, func(r *retry.R) { heartbeat, err := client.Recv() - require.NoError(t, err) - require.NotNil(t, heartbeat.GetHeartbeat()) + require.NoError(r, err) + require.NotNil(r, heartbeat.GetHeartbeat()) }) }) } @@ -1249,13 +1276,7 @@ func TestStreamResources_Server_KeepsConnectionOpenWithHeartbeat(t *testing.T) { client := makeClient(t, srv, testPeerID) - // TODO(peering): test fails if we don't drain the stream with this call because the - // server gets blocked sending the termination message. Figure out a way to let - // messages queue and filter replication messages. - receiveRoots, err := client.Recv() - require.NoError(t, err) - require.NotNil(t, receiveRoots.GetResponse()) - require.Equal(t, pbpeerstream.TypeURLPeeringTrustBundle, receiveRoots.GetResponse().ResourceURL) + client.DrainStream(t) testutil.RunStep(t, "new stream gets tracked", func(t *testing.T) { retry.Run(t, func(r *retry.R) { @@ -1494,7 +1515,7 @@ func (b *testStreamBackend) CatalogDeregister(req *structs.DeregisterRequest) er return nil } -func Test_makeServiceResponse_ExportedServicesCount(t *testing.T) { +func Test_ExportedServicesCount(t *testing.T) { peerName := "billing" peerID := "1fabcd52-1d46-49b0-b1d8-71559aee47f5" @@ -1510,37 +1531,17 @@ func Test_makeServiceResponse_ExportedServicesCount(t *testing.T) { mst, err := srv.Tracker.Connected(peerID) require.NoError(t, err) - testutil.RunStep(t, "simulate an update to export a service", func(t *testing.T) { - update := cache.UpdateEvent{ - CorrelationID: subExportedService + "api", - Result: &pbservice.IndexedCheckServiceNodes{ - Nodes: []*pbservice.CheckServiceNode{ - { - Service: &pbservice.NodeService{ - ID: "api-1", - Service: "api", - PeerName: peerName, - }, - }, - }, - }} - _, err := makeServiceResponse(mst, update) - require.NoError(t, err) - - require.Equal(t, 1, mst.GetExportedServicesCount()) - }) - - testutil.RunStep(t, "simulate a delete for an exported service", func(t *testing.T) { - update := cache.UpdateEvent{ - CorrelationID: subExportedService + "api", - Result: &pbservice.IndexedCheckServiceNodes{ - Nodes: []*pbservice.CheckServiceNode{}, - }} - _, err := makeServiceResponse(mst, update) - require.NoError(t, err) - - require.Equal(t, 0, mst.GetExportedServicesCount()) - }) + services := []string{"web", "api", "mongo"} + update := cache.UpdateEvent{ + CorrelationID: subExportedServiceList, + Result: &pbpeerstream.ExportedServiceList{ + Services: services, + }} + _, err = makeExportedServiceListResponse(mst, update) + require.NoError(t, err) + // Test the count and contents separately to ensure the count code path is hit. + require.Equal(t, 3, mst.GetExportedServicesCount()) + require.ElementsMatch(t, services, mst.ExportedServices) } func Test_processResponse_Validation(t *testing.T) { @@ -1596,24 +1597,6 @@ func Test_processResponse_Validation(t *testing.T) { }, wantErr: false, }, - { - name: "valid delete", - in: &pbpeerstream.ReplicationMessage_Response{ - ResourceURL: pbpeerstream.TypeURLExportedService, - ResourceID: "api", - Nonce: "1", - Operation: pbpeerstream.Operation_OPERATION_DELETE, - }, - expect: &pbpeerstream.ReplicationMessage{ - Payload: &pbpeerstream.ReplicationMessage_Request_{ - Request: &pbpeerstream.ReplicationMessage_Request{ - ResourceURL: pbpeerstream.TypeURLExportedService, - ResponseNonce: "1", - }, - }, - }, - wantErr: false, - }, { name: "invalid resource url", in: &pbpeerstream.ReplicationMessage_Response{ @@ -1831,7 +1814,7 @@ func expectReplEvents(t *testing.T, client *MockClient, checkFns ...func(t *test } } -func Test_processResponse_handleUpsert_handleDelete(t *testing.T) { +func Test_processResponse_ExportedServiceUpdates(t *testing.T) { srv, store := newTestServer(t, func(c *Config) { backend := c.Backend.(*testStreamBackend) backend.leader = func() bool { @@ -1840,11 +1823,11 @@ func Test_processResponse_handleUpsert_handleDelete(t *testing.T) { }) type testCase struct { - name string - seed []*structs.RegisterRequest - input *pbpeerstream.ExportedService - expect map[string]structs.CheckServiceNodes - expectedImportedServicesCount int + name string + seed []*structs.RegisterRequest + input *pbpeerstream.ExportedService + expect map[string]structs.CheckServiceNodes + exportedServices []string } peerName := "billing" @@ -1871,24 +1854,20 @@ func Test_processResponse_handleUpsert_handleDelete(t *testing.T) { run := func(t *testing.T, tc testCase) { // Seed the local catalog with some data to reconcile against. // and increment the tracker's imported services count + var serviceNames []structs.ServiceName for _, reg := range tc.seed { require.NoError(t, srv.Backend.CatalogRegister(reg)) - mst.TrackImportedService(reg.Service.CompoundServiceName()) - } - - var op pbpeerstream.Operation - if len(tc.input.Nodes) == 0 { - op = pbpeerstream.Operation_OPERATION_DELETE - } else { - op = pbpeerstream.Operation_OPERATION_UPSERT + sn := reg.Service.CompoundServiceName() + serviceNames = append(serviceNames, sn) } + mst.SetImportedServices(serviceNames) in := &pbpeerstream.ReplicationMessage_Response{ ResourceURL: pbpeerstream.TypeURLExportedService, ResourceID: apiSN.String(), Nonce: "1", - Operation: op, + Operation: pbpeerstream.Operation_OPERATION_UPSERT, Resource: makeAnyPB(t, tc.input), } @@ -1896,6 +1875,32 @@ func Test_processResponse_handleUpsert_handleDelete(t *testing.T) { _, err = srv.processResponse(peerName, acl.DefaultPartitionName, mst, in) require.NoError(t, err) + if len(tc.exportedServices) > 0 { + resp := &pbpeerstream.ReplicationMessage_Response{ + ResourceURL: pbpeerstream.TypeURLExportedServiceList, + ResourceID: subExportedServiceList, + Operation: pbpeerstream.Operation_OPERATION_UPSERT, + Resource: makeAnyPB(t, &pbpeerstream.ExportedServiceList{Services: tc.exportedServices}), + } + + // Simulate an update arriving for billing/api. + _, err = srv.processResponse(peerName, acl.DefaultPartitionName, mst, resp) + require.NoError(t, err) + // Test the count and contents separately to ensure the count code path is hit. + require.Equal(t, mst.GetImportedServicesCount(), len(tc.exportedServices)) + require.ElementsMatch(t, mst.ImportedServices, tc.exportedServices) + } + + _, allServices, err := srv.GetStore().ServiceList(nil, &defaultMeta, peerName) + require.NoError(t, err) + + // This ensures that only services specified under tc.expect are stored. It includes + // all exported services plus their sidecar proxies. + for _, svc := range allServices { + _, ok := tc.expect[svc.Name] + require.True(t, ok) + } + for svc, expect := range tc.expect { t.Run(svc, func(t *testing.T) { _, got, err := srv.GetStore().CheckServiceNodes(nil, svc, &defaultMeta, peerName) @@ -1903,14 +1908,12 @@ func Test_processResponse_handleUpsert_handleDelete(t *testing.T) { requireEqualInstances(t, expect, got) }) } - - // assert the imported services count modifications - require.Equal(t, tc.expectedImportedServicesCount, mst.GetImportedServicesCount()) } tt := []testCase{ { - name: "upsert two service instances to the same node", + name: "upsert two service instances to the same node", + exportedServices: []string{"api"}, input: &pbpeerstream.ExportedService{ Nodes: []*pbservice.CheckServiceNode{ { @@ -2039,10 +2042,44 @@ func Test_processResponse_handleUpsert_handleDelete(t *testing.T) { }, }, }, - expectedImportedServicesCount: 1, }, { - name: "upsert two service instances to different nodes", + name: "deleting a service with an empty exported service event", + exportedServices: []string{"api"}, + seed: []*structs.RegisterRequest{ + { + ID: types.NodeID("af913374-68ea-41e5-82e8-6ffd3dffc461"), + Node: "node-foo", + PeerName: peerName, + Service: &structs.NodeService{ + ID: "api-2", + Service: "api", + EnterpriseMeta: defaultMeta, + PeerName: peerName, + }, + Checks: structs.HealthChecks{ + { + Node: "node-foo", + ServiceID: "api-2", + CheckID: types.CheckID("api-2-check"), + PeerName: peerName, + }, + { + Node: "node-foo", + CheckID: types.CheckID("node-foo-check"), + PeerName: peerName, + }, + }, + }, + }, + input: &pbpeerstream.ExportedService{}, + expect: map[string]structs.CheckServiceNodes{ + "api": {}, + }, + }, + { + name: "upsert two service instances to different nodes", + exportedServices: []string{"api"}, input: &pbpeerstream.ExportedService{ Nodes: []*pbservice.CheckServiceNode{ { @@ -2171,31 +2208,31 @@ func Test_processResponse_handleUpsert_handleDelete(t *testing.T) { }, }, }, - expectedImportedServicesCount: 1, }, { - name: "receiving a nil input leads to deleting data in the catalog", + name: "deleting one service name from a node does not delete other service names", + exportedServices: []string{"api", "redis"}, seed: []*structs.RegisterRequest{ { - ID: types.NodeID("c0f97de9-4e1b-4e80-a1c6-cd8725835ab2"), - Node: "node-bar", + ID: types.NodeID("af913374-68ea-41e5-82e8-6ffd3dffc461"), + Node: "node-foo", PeerName: peerName, Service: &structs.NodeService{ - ID: "api-2", - Service: "api", + ID: "redis-2", + Service: "redis", EnterpriseMeta: defaultMeta, PeerName: peerName, }, Checks: structs.HealthChecks{ { - Node: "node-bar", - ServiceID: "api-2", - CheckID: types.CheckID("api-2-check"), + Node: "node-foo", + ServiceID: "redis-2", + CheckID: types.CheckID("redis-2-check"), PeerName: peerName, }, { - Node: "node-bar", - CheckID: types.CheckID("node-bar-check"), + Node: "node-foo", + CheckID: types.CheckID("node-foo-check"), PeerName: peerName, }, }, @@ -2225,14 +2262,46 @@ func Test_processResponse_handleUpsert_handleDelete(t *testing.T) { }, }, }, + // Nil input is for the "api" service. input: &pbpeerstream.ExportedService{}, expect: map[string]structs.CheckServiceNodes{ "api": {}, + // Existing redis service was not affected by deletion. + "redis": { + { + Node: &structs.Node{ + ID: "af913374-68ea-41e5-82e8-6ffd3dffc461", + Node: "node-foo", + Partition: defaultMeta.PartitionOrEmpty(), + PeerName: peerName, + }, + Service: &structs.NodeService{ + ID: "redis-2", + Service: "redis", + EnterpriseMeta: defaultMeta, + PeerName: peerName, + }, + Checks: []*structs.HealthCheck{ + { + CheckID: "node-foo-check", + Node: "node-foo", + EnterpriseMeta: defaultMeta, + PeerName: peerName, + }, + { + CheckID: "redis-2-check", + ServiceID: "redis-2", + Node: "node-foo", + EnterpriseMeta: defaultMeta, + PeerName: peerName, + }, + }, + }, + }, }, - expectedImportedServicesCount: 0, }, { - name: "deleting one service name from a node does not delete other service names", + name: "unexporting a service does not delete other services", seed: []*structs.RegisterRequest{ { ID: types.NodeID("af913374-68ea-41e5-82e8-6ffd3dffc461"), @@ -2258,6 +2327,30 @@ func Test_processResponse_handleUpsert_handleDelete(t *testing.T) { }, }, }, + { + ID: types.NodeID("af913374-68ea-41e5-82e8-6ffd3dffc461"), + Node: "node-foo", + PeerName: peerName, + Service: &structs.NodeService{ + ID: "redis-2-sidecar-proxy", + Service: "redis-sidecar-proxy", + EnterpriseMeta: defaultMeta, + PeerName: peerName, + }, + Checks: structs.HealthChecks{ + { + Node: "node-foo", + ServiceID: "redis-2-sidecar-proxy", + CheckID: types.CheckID("redis-2-sidecar-proxy-check"), + PeerName: peerName, + }, + { + Node: "node-foo", + CheckID: types.CheckID("node-foo-check"), + PeerName: peerName, + }, + }, + }, { ID: types.NodeID("af913374-68ea-41e5-82e8-6ffd3dffc461"), Node: "node-foo", @@ -2282,11 +2375,36 @@ func Test_processResponse_handleUpsert_handleDelete(t *testing.T) { }, }, }, + { + ID: types.NodeID("af913374-68ea-41e5-82e8-6ffd3dffc461"), + Node: "node-foo", + PeerName: peerName, + Service: &structs.NodeService{ + ID: "api-1-sidecar-proxy", + Service: "api-sidecar-proxy", + EnterpriseMeta: defaultMeta, + PeerName: peerName, + }, + Checks: structs.HealthChecks{ + { + Node: "node-foo", + ServiceID: "api-1-sidecar-proxy", + CheckID: types.CheckID("api-1-check"), + PeerName: peerName, + }, + { + Node: "node-foo", + CheckID: types.CheckID("node-foo-sidecar-proxy-check"), + ServiceID: "api-1-sidecar-proxy", + PeerName: peerName, + }, + }, + }, }, // Nil input is for the "api" service. - input: &pbpeerstream.ExportedService{}, + input: &pbpeerstream.ExportedService{}, + exportedServices: []string{"redis"}, expect: map[string]structs.CheckServiceNodes{ - "api": {}, // Existing redis service was not affected by deletion. "redis": { { @@ -2319,11 +2437,42 @@ func Test_processResponse_handleUpsert_handleDelete(t *testing.T) { }, }, }, + "redis-sidecar-proxy": { + { + Node: &structs.Node{ + ID: "af913374-68ea-41e5-82e8-6ffd3dffc461", + Node: "node-foo", + Partition: defaultMeta.PartitionOrEmpty(), + PeerName: peerName, + }, + Service: &structs.NodeService{ + ID: "redis-2-sidecar-proxy", + Service: "redis-sidecar-proxy", + EnterpriseMeta: defaultMeta, + PeerName: peerName, + }, + Checks: []*structs.HealthCheck{ + { + CheckID: "node-foo-check", + Node: "node-foo", + EnterpriseMeta: defaultMeta, + PeerName: peerName, + }, + { + CheckID: "redis-2-sidecar-proxy-check", + ServiceID: "redis-2-sidecar-proxy", + Node: "node-foo", + EnterpriseMeta: defaultMeta, + PeerName: peerName, + }, + }, + }, + }, }, - expectedImportedServicesCount: 1, }, { - name: "service checks are cleaned up when not present in a response", + name: "service checks are cleaned up when not present in a response", + exportedServices: []string{"api"}, seed: []*structs.RegisterRequest{ { ID: types.NodeID("af913374-68ea-41e5-82e8-6ffd3dffc461"), @@ -2391,10 +2540,10 @@ func Test_processResponse_handleUpsert_handleDelete(t *testing.T) { }, }, }, - expectedImportedServicesCount: 2, }, { - name: "node checks are cleaned up when not present in a response", + name: "node checks are cleaned up when not present in a response", + exportedServices: []string{"api", "redis"}, seed: []*structs.RegisterRequest{ { ID: types.NodeID("af913374-68ea-41e5-82e8-6ffd3dffc461"), @@ -2526,10 +2675,10 @@ func Test_processResponse_handleUpsert_handleDelete(t *testing.T) { }, }, }, - expectedImportedServicesCount: 2, }, { - name: "replacing a service instance on a node cleans up the old instance", + name: "replacing a service instance on a node cleans up the old instance", + exportedServices: []string{"api", "redis"}, seed: []*structs.RegisterRequest{ { ID: types.NodeID("af913374-68ea-41e5-82e8-6ffd3dffc461"), @@ -2674,7 +2823,6 @@ func Test_processResponse_handleUpsert_handleDelete(t *testing.T) { }, }, }, - expectedImportedServicesCount: 2, }, } diff --git a/agent/grpc-external/services/peerstream/stream_tracker.go b/agent/grpc-external/services/peerstream/stream_tracker.go index c3108e71e7..daf891d38a 100644 --- a/agent/grpc-external/services/peerstream/stream_tracker.go +++ b/agent/grpc-external/services/peerstream/stream_tracker.go @@ -214,6 +214,9 @@ type Status struct { // LastSendErrorMessage tracks the last error message when sending into the stream. LastSendErrorMessage string + // LastSendSuccess tracks the time we last successfully sent a resource TO the peer. + LastSendSuccess time.Time + // LastRecvHeartbeat tracks when we last received a heartbeat from our peer. LastRecvHeartbeat time.Time @@ -230,9 +233,9 @@ type Status struct { // TODO(peering): consider keeping track of imported and exported services thru raft // ImportedServices keeps track of which service names are imported for the peer - ImportedServices map[string]struct{} + ImportedServices []string // ExportedServices keeps track of which service names a peer asks to export - ExportedServices map[string]struct{} + ExportedServices []string } func (s *Status) GetImportedServicesCount() uint64 { @@ -271,6 +274,12 @@ func (s *MutableStatus) TrackSendError(error string) { s.mu.Unlock() } +func (s *MutableStatus) TrackSendSuccess() { + s.mu.Lock() + s.LastSendSuccess = s.timeNow().UTC() + s.mu.Unlock() +} + // TrackRecvResourceSuccess tracks receiving a replicated resource. func (s *MutableStatus) TrackRecvResourceSuccess() { s.mu.Lock() @@ -345,22 +354,15 @@ func (s *MutableStatus) GetStatus() Status { return copy } -func (s *MutableStatus) RemoveImportedService(sn structs.ServiceName) { +func (s *MutableStatus) SetImportedServices(serviceNames []structs.ServiceName) { s.mu.Lock() defer s.mu.Unlock() - delete(s.ImportedServices, sn.String()) -} + s.ImportedServices = make([]string, len(serviceNames)) -func (s *MutableStatus) TrackImportedService(sn structs.ServiceName) { - s.mu.Lock() - defer s.mu.Unlock() - - if s.ImportedServices == nil { - s.ImportedServices = make(map[string]struct{}) + for i, sn := range serviceNames { + s.ImportedServices[i] = sn.Name } - - s.ImportedServices[sn.String()] = struct{}{} } func (s *MutableStatus) GetImportedServicesCount() int { @@ -370,22 +372,15 @@ func (s *MutableStatus) GetImportedServicesCount() int { return len(s.ImportedServices) } -func (s *MutableStatus) RemoveExportedService(sn structs.ServiceName) { +func (s *MutableStatus) SetExportedServices(serviceNames []structs.ServiceName) { s.mu.Lock() defer s.mu.Unlock() - delete(s.ExportedServices, sn.String()) -} + s.ExportedServices = make([]string, len(serviceNames)) -func (s *MutableStatus) TrackExportedService(sn structs.ServiceName) { - s.mu.Lock() - defer s.mu.Unlock() - - if s.ExportedServices == nil { - s.ExportedServices = make(map[string]struct{}) + for i, sn := range serviceNames { + s.ExportedServices[i] = sn.Name } - - s.ExportedServices[sn.String()] = struct{}{} } func (s *MutableStatus) GetExportedServicesCount() int { diff --git a/agent/grpc-external/services/peerstream/subscription_blocking.go b/agent/grpc-external/services/peerstream/subscription_blocking.go index d11e03d552..b62fe5975d 100644 --- a/agent/grpc-external/services/peerstream/subscription_blocking.go +++ b/agent/grpc-external/services/peerstream/subscription_blocking.go @@ -98,25 +98,25 @@ func (m *subscriptionManager) syncViaBlockingQuery( ws.Add(store.AbandonCh()) ws.Add(ctx.Done()) - if result, err := queryFn(ctx, store, ws); err != nil { + if result, err := queryFn(ctx, store, ws); err != nil && ctx.Err() == nil { logger.Error("failed to sync from query", "error", err) + } else { - // Block for any changes to the state store. - updateCh <- cache.UpdateEvent{ - CorrelationID: correlationID, - Result: result, + select { + case <-ctx.Done(): + return + case updateCh <- cache.UpdateEvent{CorrelationID: correlationID, Result: result}: } - ws.WatchCtx(ctx) - } - if err := waiter.Wait(ctx); err != nil && !errors.Is(err, context.Canceled) && !errors.Is(err, context.DeadlineExceeded) { - logger.Error("failed to wait before re-trying sync", "error", err) + // Block for any changes to the state store. + ws.WatchCtx(ctx) } - select { - case <-ctx.Done(): + err := waiter.Wait(ctx) + if errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) { return - default: + } else if err != nil { + logger.Error("failed to wait before re-trying sync", "error", err) } } } diff --git a/agent/grpc-external/services/peerstream/subscription_manager.go b/agent/grpc-external/services/peerstream/subscription_manager.go index 138449e712..0f2e4bf79c 100644 --- a/agent/grpc-external/services/peerstream/subscription_manager.go +++ b/agent/grpc-external/services/peerstream/subscription_manager.go @@ -6,9 +6,13 @@ import ( "fmt" "strconv" "strings" + "time" "github.com/golang/protobuf/proto" + "github.com/hashicorp/consul/ipaddr" + "github.com/hashicorp/consul/lib/retry" "github.com/hashicorp/go-hclog" + "github.com/hashicorp/go-memdb" "github.com/hashicorp/consul/acl" "github.com/hashicorp/consul/agent/cache" @@ -124,8 +128,6 @@ func (m *subscriptionManager) handleEvent(ctx context.Context, state *subscripti return fmt.Errorf("received error event: %w", u.Err) } - // TODO(peering): on initial stream setup, transmit the list of exported - // services for use in differential DELETE/UPSERT. Akin to streaming's snapshot start/end. switch { case u.CorrelationID == subExportedServiceList: // Everything starts with the exported service list coming from @@ -138,10 +140,20 @@ func (m *subscriptionManager) handleEvent(ctx context.Context, state *subscripti state.exportList = evt pending := &pendingPayload{} - m.syncNormalServices(ctx, state, pending, evt.Services) + m.syncNormalServices(ctx, state, evt.Services) if m.config.ConnectEnabled { m.syncDiscoveryChains(ctx, state, pending, evt.ListAllDiscoveryChains()) } + + err := pending.Add( + exportedServiceListID, + subExportedServiceList, + pbpeerstream.ExportedServiceListFromStruct(evt), + ) + if err != nil { + return err + } + state.sendPendingEvents(ctx, m.logger, pending) // cleanup event versions too @@ -239,16 +251,10 @@ func (m *subscriptionManager) handleEvent(ctx context.Context, state *subscripti pending := &pendingPayload{} - // Directly replicate information about our mesh gateways to the consuming side. - // TODO(peering): should we scrub anything before replicating this? - if err := pending.Add(meshGatewayPayloadID, u.CorrelationID, csn); err != nil { - return err - } - if state.exportList != nil { // Trigger public events for all synthetic discovery chain replies. for chainName, info := range state.connectServices { - m.emitEventForDiscoveryChain(ctx, state, pending, chainName, info) + m.collectPendingEventForDiscoveryChain(ctx, state, pending, chainName, info) } } @@ -435,7 +441,6 @@ func (m *subscriptionManager) subscribeCARoots( func (m *subscriptionManager) syncNormalServices( ctx context.Context, state *subscriptionState, - pending *pendingPayload, services []structs.ServiceName, ) { // seen contains the set of exported service names and is used to reconcile the list of watched services. @@ -464,20 +469,7 @@ func (m *subscriptionManager) syncNormalServices( for svc, cancel := range state.watchedServices { if _, ok := seen[svc]; !ok { cancel() - delete(state.watchedServices, svc) - - // Send an empty event to the stream handler to trigger sending a DELETE message. - // Cancelling the subscription context above is necessary, but does not yield a useful signal on its own. - err := pending.Add( - servicePayloadIDPrefix+svc.String(), - subExportedService+svc.String(), - &pbservice.IndexedCheckServiceNodes{}, - ) - if err != nil { - m.logger.Error("failed to send event for service", "service", svc.String(), "error", err) - continue - } } } } @@ -496,7 +488,7 @@ func (m *subscriptionManager) syncDiscoveryChains( state.connectServices[chainName] = info - m.emitEventForDiscoveryChain(ctx, state, pending, chainName, info) + m.collectPendingEventForDiscoveryChain(ctx, state, pending, chainName, info) } // if it was dropped, try to emit an DELETE event @@ -523,7 +515,7 @@ func (m *subscriptionManager) syncDiscoveryChains( } } -func (m *subscriptionManager) emitEventForDiscoveryChain( +func (m *subscriptionManager) collectPendingEventForDiscoveryChain( ctx context.Context, state *subscriptionState, pending *pendingPayload, @@ -744,32 +736,118 @@ func (m *subscriptionManager) notifyServerAddrUpdates( ctx context.Context, updateCh chan<- cache.UpdateEvent, ) { - // Wait until this is subscribed-to. + // Wait until server address updates are subscribed-to. select { case <-m.serverAddrsSubReady: case <-ctx.Done(): return } + configNotifyCh := m.notifyMeshConfigUpdates(ctx) + + // Intentionally initialized to empty values. + // These are set after the first mesh config entry update arrives. + var queryCtx context.Context + cancel := func() {} + + useGateways := false + for { + select { + case <-ctx.Done(): + cancel() + return + + case event := <-configNotifyCh: + entry, ok := event.Result.(*structs.MeshConfigEntry) + if event.Result != nil && !ok { + m.logger.Error(fmt.Sprintf("saw unexpected type %T for mesh config entry: falling back to pushing direct server addresses", event.Result)) + } + if entry != nil && entry.Peering != nil && entry.Peering.PeerThroughMeshGateways { + useGateways = true + } else { + useGateways = false + } + + // Cancel and re-set watches based on the updated config entry. + cancel() + + queryCtx, cancel = context.WithCancel(ctx) + + if useGateways { + go m.notifyServerMeshGatewayAddresses(queryCtx, updateCh) + } else { + go m.ensureServerAddrSubscription(queryCtx, updateCh) + } + } + } +} + +func (m *subscriptionManager) notifyMeshConfigUpdates(ctx context.Context) <-chan cache.UpdateEvent { + const meshConfigWatch = "mesh-config-entry" + + notifyCh := make(chan cache.UpdateEvent, 1) + go m.syncViaBlockingQuery(ctx, meshConfigWatch, func(ctx_ context.Context, store StateStore, ws memdb.WatchSet) (interface{}, error) { + _, rawEntry, err := store.ConfigEntry(ws, structs.MeshConfig, structs.MeshConfigMesh, acl.DefaultEnterpriseMeta()) + if err != nil { + return nil, fmt.Errorf("failed to get mesh config entry: %w", err) + + } + return rawEntry, nil + }, meshConfigWatch, notifyCh) + + return notifyCh +} + +func (m *subscriptionManager) notifyServerMeshGatewayAddresses(ctx context.Context, updateCh chan<- cache.UpdateEvent) { + m.syncViaBlockingQuery(ctx, "mesh-gateways", func(ctx context.Context, store StateStore, ws memdb.WatchSet) (interface{}, error) { + _, nodes, err := store.ServiceDump(ws, structs.ServiceKindMeshGateway, true, acl.DefaultEnterpriseMeta(), structs.DefaultPeerKeyword) + if err != nil { + return nil, fmt.Errorf("failed to watch mesh gateways services for servers: %w", err) + } + + var gatewayAddrs []string + for _, csn := range nodes { + _, addr, port := csn.BestAddress(true) + gatewayAddrs = append(gatewayAddrs, ipaddr.FormatAddressPort(addr, port)) + } + if len(gatewayAddrs) == 0 { + return nil, errors.New("configured to peer through mesh gateways but no mesh gateways are registered") + } + + // We may return an empty list if there are no gateway addresses. + return &pbpeering.PeeringServerAddresses{ + Addresses: gatewayAddrs, + }, nil + }, subServerAddrs, updateCh) +} + +func (m *subscriptionManager) ensureServerAddrSubscription(ctx context.Context, updateCh chan<- cache.UpdateEvent) { + waiter := &retry.Waiter{ + MinFailures: 1, + Factor: 500 * time.Millisecond, + MaxWait: 60 * time.Second, + Jitter: retry.NewJitter(100), + } + + logger := m.logger.With("queryType", "server-addresses") + var idx uint64 - // TODO(peering): retry logic; fail past a threshold for { var err error - // Typically, this function will block inside `m.subscribeServerAddrs` and only return on error. - // Errors are logged and the watch is retried. + idx, err = m.subscribeServerAddrs(ctx, idx, updateCh) if errors.Is(err, stream.ErrSubForceClosed) { - m.logger.Trace("subscription force-closed due to an ACL change or snapshot restore, will attempt resume") + logger.Trace("subscription force-closed due to an ACL change or snapshot restore, will attempt resume") + } else if !errors.Is(err, context.Canceled) && !errors.Is(err, context.DeadlineExceeded) { - m.logger.Warn("failed to subscribe to server addresses, will attempt resume", "error", err.Error()) - } else { - m.logger.Trace(err.Error()) - } + logger.Warn("failed to subscribe to server addresses, will attempt resume", "error", err.Error()) - select { - case <-ctx.Done(): + } else if err != nil { + logger.Trace(err.Error()) + return + } + if err := waiter.Wait(ctx); err != nil { return - default: } } } @@ -832,17 +910,22 @@ func (m *subscriptionManager) subscribeServerAddrs( grpcAddr := srv.Address + ":" + strconv.Itoa(srv.ExtGRPCPort) serverAddrs = append(serverAddrs, grpcAddr) } - if len(serverAddrs) == 0 { m.logger.Warn("did not find any server addresses with external gRPC ports to publish") continue } - updateCh <- cache.UpdateEvent{ + u := cache.UpdateEvent{ CorrelationID: subServerAddrs, Result: &pbpeering.PeeringServerAddresses{ Addresses: serverAddrs, }, } + + select { + case <-ctx.Done(): + return 0, ctx.Err() + case updateCh <- u: + } } } diff --git a/agent/grpc-external/services/peerstream/subscription_manager_test.go b/agent/grpc-external/services/peerstream/subscription_manager_test.go index d81568f0a4..615d72030e 100644 --- a/agent/grpc-external/services/peerstream/subscription_manager_test.go +++ b/agent/grpc-external/services/peerstream/subscription_manager_test.go @@ -7,6 +7,7 @@ import ( "testing" "time" + "github.com/hashicorp/consul/types" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" @@ -49,17 +50,15 @@ func TestSubscriptionManager_RegisterDeregister(t *testing.T) { subCh := mgr.subscribe(ctx, id, "my-peering", partition) var ( - gatewayCorrID = subMeshGateway + partition - - mysqlCorrID = subExportedService + structs.NewServiceName("mysql", nil).String() - + mysqlCorrID = subExportedService + structs.NewServiceName("mysql", nil).String() mysqlProxyCorrID = subExportedService + structs.NewServiceName("mysql-sidecar-proxy", nil).String() ) // Expect just the empty mesh gateway event to replicate. - expectEvents(t, subCh, func(t *testing.T, got cache.UpdateEvent) { - checkEvent(t, got, gatewayCorrID, 0) - }) + expectEvents(t, subCh, + func(t *testing.T, got cache.UpdateEvent) { + checkExportedServices(t, got, []string{}) + }) // Initially add in L4 failover so that later we can test removing it. We // cannot do the other way around because it would fail validation to @@ -81,19 +80,22 @@ func TestSubscriptionManager_RegisterDeregister(t *testing.T) { { Name: "mysql", Consumers: []structs.ServiceConsumer{ - {PeerName: "my-peering"}, + {Peer: "my-peering"}, }, }, { Name: "mongo", Consumers: []structs.ServiceConsumer{ - {PeerName: "my-other-peering"}, + {Peer: "my-other-peering"}, }, }, }, }) expectEvents(t, subCh, + func(t *testing.T, got cache.UpdateEvent) { + checkExportedServices(t, got, []string{"mysql"}) + }, func(t *testing.T, got cache.UpdateEvent) { checkEvent(t, got, mysqlCorrID, 0) }, @@ -292,17 +294,6 @@ func TestSubscriptionManager_RegisterDeregister(t *testing.T) { }, }, res.Nodes[0]) }, - func(t *testing.T, got cache.UpdateEvent) { - require.Equal(t, gatewayCorrID, got.CorrelationID) - res := got.Result.(*pbservice.IndexedCheckServiceNodes) - require.Equal(t, uint64(0), res.Index) - - require.Len(t, res.Nodes, 1) - prototest.AssertDeepEqual(t, &pbservice.CheckServiceNode{ - Node: pbNode("mgw", "10.1.1.1", partition), - Service: pbService("mesh-gateway", "gateway-1", "gateway", 8443, nil), - }, res.Nodes[0]) - }, ) }) @@ -428,12 +419,25 @@ func TestSubscriptionManager_RegisterDeregister(t *testing.T) { require.Len(t, res.Nodes, 0) }, - func(t *testing.T, got cache.UpdateEvent) { - require.Equal(t, gatewayCorrID, got.CorrelationID) - res := got.Result.(*pbservice.IndexedCheckServiceNodes) - require.Equal(t, uint64(0), res.Index) + ) + }) - require.Len(t, res.Nodes, 0) + testutil.RunStep(t, "unexporting a service emits sends an event", func(t *testing.T) { + backend.ensureConfigEntry(t, &structs.ExportedServicesConfigEntry{ + Name: "default", + Services: []structs.ExportedService{ + { + Name: "mongo", + Consumers: []structs.ServiceConsumer{ + {Peer: "my-other-peering"}, + }, + }, + }, + }) + + expectEvents(t, subCh, + func(t *testing.T, got cache.UpdateEvent) { + checkExportedServices(t, got, []string{}) }, ) }) @@ -478,8 +482,6 @@ func TestSubscriptionManager_InitialSnapshot(t *testing.T) { backend.ensureService(t, "zip", mongo.Service) var ( - gatewayCorrID = subMeshGateway + partition - mysqlCorrID = subExportedService + structs.NewServiceName("mysql", nil).String() mongoCorrID = subExportedService + structs.NewServiceName("mongo", nil).String() chainCorrID = subExportedService + structs.NewServiceName("chain", nil).String() @@ -490,9 +492,10 @@ func TestSubscriptionManager_InitialSnapshot(t *testing.T) { ) // Expect just the empty mesh gateway event to replicate. - expectEvents(t, subCh, func(t *testing.T, got cache.UpdateEvent) { - checkEvent(t, got, gatewayCorrID, 0) - }) + expectEvents(t, subCh, + func(t *testing.T, got cache.UpdateEvent) { + checkExportedServices(t, got, []string{}) + }) // At this point in time we'll have a mesh-gateway notification with no // content stored and handled. @@ -503,25 +506,28 @@ func TestSubscriptionManager_InitialSnapshot(t *testing.T) { { Name: "mysql", Consumers: []structs.ServiceConsumer{ - {PeerName: "my-peering"}, + {Peer: "my-peering"}, }, }, { Name: "mongo", Consumers: []structs.ServiceConsumer{ - {PeerName: "my-peering"}, + {Peer: "my-peering"}, }, }, { Name: "chain", Consumers: []structs.ServiceConsumer{ - {PeerName: "my-peering"}, + {Peer: "my-peering"}, }, }, }, }) expectEvents(t, subCh, + func(t *testing.T, got cache.UpdateEvent) { + checkExportedServices(t, got, []string{"mysql", "chain", "mongo"}) + }, func(t *testing.T, got cache.UpdateEvent) { checkEvent(t, got, chainCorrID, 0) }, @@ -562,9 +568,6 @@ func TestSubscriptionManager_InitialSnapshot(t *testing.T) { func(t *testing.T, got cache.UpdateEvent) { checkEvent(t, got, mysqlProxyCorrID, 1, "mysql-sidecar-proxy", string(structs.ServiceKindConnectProxy)) }, - func(t *testing.T, got cache.UpdateEvent) { - checkEvent(t, got, gatewayCorrID, 1, "gateway", string(structs.ServiceKindMeshGateway)) - }, ) }) } @@ -706,6 +709,102 @@ func TestSubscriptionManager_ServerAddrs(t *testing.T) { }, ) }) + + testutil.RunStep(t, "flipped to peering through mesh gateways", func(t *testing.T) { + require.NoError(t, backend.store.EnsureConfigEntry(1, &structs.MeshConfigEntry{ + Peering: &structs.PeeringMeshConfig{ + PeerThroughMeshGateways: true, + }, + })) + + select { + case <-time.After(100 * time.Millisecond): + case <-subCh: + t.Fatal("expected to time out: no mesh gateways are registered") + } + }) + + testutil.RunStep(t, "registered and received a mesh gateway", func(t *testing.T) { + reg := structs.RegisterRequest{ + ID: types.NodeID("b5489ca9-f5e9-4dba-a779-61fec4e8e364"), + Node: "gw-node", + Address: "1.2.3.4", + TaggedAddresses: map[string]string{ + structs.TaggedAddressWAN: "172.217.22.14", + }, + Service: &structs.NodeService{ + ID: "mesh-gateway", + Service: "mesh-gateway", + Kind: structs.ServiceKindMeshGateway, + Port: 443, + TaggedAddresses: map[string]structs.ServiceAddress{ + structs.TaggedAddressWAN: {Address: "154.238.12.252", Port: 8443}, + }, + }, + } + require.NoError(t, backend.store.EnsureRegistration(2, ®)) + + expectEvents(t, subCh, + func(t *testing.T, got cache.UpdateEvent) { + require.Equal(t, subServerAddrs, got.CorrelationID) + + addrs, ok := got.Result.(*pbpeering.PeeringServerAddresses) + require.True(t, ok) + + require.Equal(t, []string{"154.238.12.252:8443"}, addrs.GetAddresses()) + }, + ) + }) + + testutil.RunStep(t, "registered and received a second mesh gateway", func(t *testing.T) { + reg := structs.RegisterRequest{ + ID: types.NodeID("e4cc0af3-5c09-4ddf-94a9-5840e427bc45"), + Node: "gw-node-2", + Address: "1.2.3.5", + TaggedAddresses: map[string]string{ + structs.TaggedAddressWAN: "172.217.22.15", + }, + Service: &structs.NodeService{ + ID: "mesh-gateway", + Service: "mesh-gateway", + Kind: structs.ServiceKindMeshGateway, + Port: 443, + }, + } + require.NoError(t, backend.store.EnsureRegistration(3, ®)) + + expectEvents(t, subCh, + func(t *testing.T, got cache.UpdateEvent) { + require.Equal(t, subServerAddrs, got.CorrelationID) + + addrs, ok := got.Result.(*pbpeering.PeeringServerAddresses) + require.True(t, ok) + + require.Equal(t, []string{"154.238.12.252:8443", "172.217.22.15:443"}, addrs.GetAddresses()) + }, + ) + }) + + testutil.RunStep(t, "disabled peering through gateways and received server addresses", func(t *testing.T) { + require.NoError(t, backend.store.EnsureConfigEntry(4, &structs.MeshConfigEntry{ + Peering: &structs.PeeringMeshConfig{ + PeerThroughMeshGateways: false, + }, + })) + + expectEvents(t, subCh, + func(t *testing.T, got cache.UpdateEvent) { + require.Equal(t, subServerAddrs, got.CorrelationID) + + addrs, ok := got.Result.(*pbpeering.PeeringServerAddresses) + require.True(t, ok) + + // New subscriptions receive a snapshot from the event publisher. + // At the start of the test the handler registered a mock that only returns a single address. + require.Equal(t, []string{"198.18.0.1:8502"}, addrs.GetAddresses()) + }, + ) + }) } type testSubscriptionBackend struct { @@ -933,6 +1032,23 @@ func checkEvent( } } +func checkExportedServices( + t *testing.T, + got cache.UpdateEvent, + expectedServices []string, +) { + t.Helper() + + var qualifiedServices []string + for _, s := range expectedServices { + qualifiedServices = append(qualifiedServices, structs.ServiceName{Name: s}.String()) + } + + require.Equal(t, subExportedServiceList, got.CorrelationID) + evt := got.Result.(*pbpeerstream.ExportedServiceList) + require.ElementsMatch(t, qualifiedServices, evt.Services) +} + func pbNode(node, addr, partition string) *pbservice.Node { return &pbservice.Node{Node: node, Partition: partition, Address: addr} } diff --git a/agent/grpc-external/services/peerstream/subscription_state.go b/agent/grpc-external/services/peerstream/subscription_state.go index 9e32be5450..a99edae08f 100644 --- a/agent/grpc-external/services/peerstream/subscription_state.go +++ b/agent/grpc-external/services/peerstream/subscription_state.go @@ -96,6 +96,9 @@ func (s *subscriptionState) cleanupEventVersions(logger hclog.Logger) { case id == serverAddrsPayloadID: keep = true + case id == exportedServiceListID: + keep = true + case strings.HasPrefix(id, servicePayloadIDPrefix): name := strings.TrimPrefix(id, servicePayloadIDPrefix) sn := structs.ServiceNameFromString(name) @@ -135,6 +138,7 @@ const ( serverAddrsPayloadID = "server-addrs" caRootsPayloadID = "roots" meshGatewayPayloadID = "mesh-gateway" + exportedServiceListID = "exported-service-list" servicePayloadIDPrefix = "service:" discoveryChainPayloadIDPrefix = "chain:" ) diff --git a/agent/grpc-external/services/peerstream/testing.go b/agent/grpc-external/services/peerstream/testing.go index 1f85b2b78d..4f0297a6c5 100644 --- a/agent/grpc-external/services/peerstream/testing.go +++ b/agent/grpc-external/services/peerstream/testing.go @@ -5,8 +5,10 @@ import ( "fmt" "io" "sync" + "testing" "time" + "github.com/stretchr/testify/require" "google.golang.org/grpc/metadata" "github.com/hashicorp/consul/proto/pbpeerstream" @@ -49,6 +51,24 @@ func NewMockClient(ctx context.Context) *MockClient { } } +// DrainStream reads messages from the stream until both the exported service list and +// trust bundle messages have been read. We do this because their ording is indeterministic. +func (c *MockClient) DrainStream(t *testing.T) { + seen := make(map[string]struct{}) + for len(seen) < 2 { + msg, err := c.Recv() + require.NoError(t, err) + + if r := msg.GetResponse(); r != nil && r.ResourceURL == pbpeerstream.TypeURLExportedServiceList { + seen[pbpeerstream.TypeURLExportedServiceList] = struct{}{} + } + + if r := msg.GetResponse(); r != nil && r.ResourceURL == pbpeerstream.TypeURLPeeringTrustBundle { + seen[pbpeerstream.TypeURLPeeringTrustBundle] = struct{}{} + } + } +} + // MockStream mocks peering.PeeringService_StreamResourcesServer type MockStream struct { sendCh chan *pbpeerstream.ReplicationMessage diff --git a/agent/grpc-external/services/serverdiscovery/watch_servers.go b/agent/grpc-external/services/serverdiscovery/watch_servers.go index 1a119148cc..de3977be80 100644 --- a/agent/grpc-external/services/serverdiscovery/watch_servers.go +++ b/agent/grpc-external/services/serverdiscovery/watch_servers.go @@ -26,15 +26,17 @@ func (s *Server) WatchServers(req *pbserverdiscovery.WatchServersRequest, server logger.Debug("starting stream") defer logger.Trace("stream closed") - token := external.TokenFromContext(serverStream.Context()) - + options, err := external.QueryOptionsFromContext(serverStream.Context()) + if err != nil { + return err + } // Serve the ready servers from an EventPublisher subscription. If the subscription is // closed due to an ACL change, we'll attempt to re-authorize and resume it to // prevent unnecessarily terminating the stream. var idx uint64 for { var err error - idx, err = s.serveReadyServers(token, idx, req, serverStream, logger) + idx, err = s.serveReadyServers(options.Token, idx, req, serverStream, logger) if errors.Is(err, stream.ErrSubForceClosed) { logger.Trace("subscription force-closed due to an ACL change or snapshot restore, will attempt to re-auth and resume") } else { diff --git a/agent/grpc-external/services/serverdiscovery/watch_servers_test.go b/agent/grpc-external/services/serverdiscovery/watch_servers_test.go index 1a73b06689..a57c0f9855 100644 --- a/agent/grpc-external/services/serverdiscovery/watch_servers_test.go +++ b/agent/grpc-external/services/serverdiscovery/watch_servers_test.go @@ -18,6 +18,7 @@ import ( "github.com/hashicorp/consul/agent/consul/stream" external "github.com/hashicorp/consul/agent/grpc-external" "github.com/hashicorp/consul/agent/grpc-external/testutils" + "github.com/hashicorp/consul/agent/structs" "github.com/hashicorp/consul/proto-public/pbserverdiscovery" "github.com/hashicorp/consul/proto/prototest" "github.com/hashicorp/consul/sdk/testutil" @@ -125,7 +126,9 @@ func TestWatchServers_StreamLifeCycle(t *testing.T) { Return(testutils.TestAuthorizerServiceWriteAny(t), nil).Twice() // add the token to the requests context - ctx := external.ContextWithToken(context.Background(), testACLToken) + options := structs.QueryOptions{Token: testACLToken} + ctx, err := external.ContextWithQueryOptions(context.Background(), options) + require.NoError(t, err) // setup the server server := NewServer(Config{ @@ -198,7 +201,9 @@ func TestWatchServers_ACLToken_PermissionDenied(t *testing.T) { Return(testutils.TestAuthorizerDenyAll(t), nil).Once() // add the token to the requests context - ctx := external.ContextWithToken(context.Background(), testACLToken) + options := structs.QueryOptions{Token: testACLToken} + ctx, err := external.ContextWithQueryOptions(context.Background(), options) + require.NoError(t, err) // setup the server server := NewServer(Config{ @@ -229,7 +234,9 @@ func TestWatchServers_ACLToken_Unauthenticated(t *testing.T) { Return(resolver.Result{}, acl.ErrNotFound).Once() // add the token to the requests context - ctx := external.ContextWithToken(context.Background(), testACLToken) + options := structs.QueryOptions{Token: testACLToken} + ctx, err := external.ContextWithQueryOptions(context.Background(), options) + require.NoError(t, err) // setup the server server := NewServer(Config{ diff --git a/agent/grpc-external/token.go b/agent/grpc-external/token.go deleted file mode 100644 index 68006b254e..0000000000 --- a/agent/grpc-external/token.go +++ /dev/null @@ -1,28 +0,0 @@ -package external - -import ( - "context" - - "google.golang.org/grpc/metadata" -) - -const metadataKeyToken = "x-consul-token" - -// TokenFromContext returns the ACL token in the gRPC metadata attached to the -// given context. -func TokenFromContext(ctx context.Context) string { - md, ok := metadata.FromIncomingContext(ctx) - if !ok { - return "" - } - toks, ok := md[metadataKeyToken] - if ok && len(toks) > 0 { - return toks[0] - } - return "" -} - -// ContextWithToken returns a context with the given ACL token attached. -func ContextWithToken(ctx context.Context, token string) context.Context { - return metadata.AppendToOutgoingContext(ctx, metadataKeyToken, token) -} diff --git a/agent/hcp/bootstrap/bootstrap.go b/agent/hcp/bootstrap/bootstrap.go new file mode 100644 index 0000000000..3de7de4350 --- /dev/null +++ b/agent/hcp/bootstrap/bootstrap.go @@ -0,0 +1,305 @@ +// Package bootstrap handles bootstrapping an agent's config from HCP. It must be a +// separate package from other HCP components because it has a dependency on +// agent/config while other components need to be imported and run within the +// server process in agent/consul and that would create a dependency cycle. +package bootstrap + +import ( + "bufio" + "context" + "encoding/json" + "errors" + "fmt" + "io/ioutil" + "os" + "path/filepath" + "strings" + "time" + + "github.com/hashicorp/consul/agent/config" + "github.com/hashicorp/consul/agent/hcp" + "github.com/hashicorp/consul/lib" + "github.com/hashicorp/consul/lib/retry" +) + +const ( + caFileName = "server-tls-cas.pem" + certFileName = "server-tls-cert.pem" + keyFileName = "server-tls-key.pem" + configFileName = "server-config.json" + subDir = "hcp-config" +) + +type ConfigLoader func(source config.Source) (config.LoadResult, error) + +// UI is a shim to allow the agent command to pass in it's mitchelh/cli.UI so we +// can output useful messages to the user during bootstrapping. For example if +// we have to retry several times to bootstrap we don't want the agent to just +// stall with no output which is the case if we just returned all intermediate +// warnings or errors. +type UI interface { + Output(string) + Warn(string) + Info(string) + Error(string) +} + +// MaybeBootstrap will use the passed ConfigLoader to read the existing +// configuration, and if required attempt to bootstrap from HCP. It will retry +// until successful or a terminal error condition is found (e.g. permission +// denied). It must be passed a (CLI) UI implementation so it can deliver progress +// updates to the user, for example if it is waiting to retry for a long period. +func MaybeBootstrap(ctx context.Context, loader ConfigLoader, ui UI) (bool, ConfigLoader, error) { + loader = wrapConfigLoader(loader) + res, err := loader(nil) + if err != nil { + return false, nil, err + } + + // Check to see if this is a server and HCP is configured + + if !res.RuntimeConfig.IsCloudEnabled() { + // Not a server, let agent continue unmodified + return false, loader, nil + } + + ui.Output("Bootstrapping configuration from HCP") + + // See if we have existing config on disk + cfgJSON, ok := loadPersistedBootstrapConfig(res.RuntimeConfig, ui) + + if !ok { + // Fetch from HCP + ui.Info("Fetching configuration from HCP") + cfgJSON, err = doHCPBootstrap(ctx, res.RuntimeConfig, ui) + if err != nil { + return false, nil, fmt.Errorf("failed to bootstrap from HCP: %w", err) + } + ui.Info("Configuration fetched from HCP and saved on local disk") + } else { + ui.Info("Loaded configuration from local disk") + } + + // Create a new loader func to return + newLoader := func(source config.Source) (config.LoadResult, error) { + // Don't allow any further attempts to provide a DefaultSource. This should + // only ever be needed later in client agent AutoConfig code but that should + // be mutually exclusive from this bootstrapping mechanism since this is + // only for servers. If we ever try to change that, this clear failure + // should alert future developers that the assumptions are changing rather + // than quietly not applying the config they expect! + if source != nil { + return config.LoadResult{}, + fmt.Errorf("non-nil config source provided to a loader after HCP bootstrap already provided a DefaultSource") + } + // Otherwise, just call to the loader we were passed with our own additional + // JSON as the source. + s := config.FileSource{ + Name: "HCP Bootstrap", + Format: "json", + Data: cfgJSON, + } + return loader(s) + } + + return true, newLoader, nil +} + +func wrapConfigLoader(loader ConfigLoader) ConfigLoader { + return func(source config.Source) (config.LoadResult, error) { + res, err := loader(source) + if err != nil { + return res, err + } + + if res.RuntimeConfig.Cloud.ResourceID == "" { + res.RuntimeConfig.Cloud.ResourceID = os.Getenv("HCP_RESOURCE_ID") + } + return res, nil + } +} + +func doHCPBootstrap(ctx context.Context, rc *config.RuntimeConfig, ui UI) (string, error) { + w := retry.Waiter{ + MinWait: 1 * time.Second, + MaxWait: 5 * time.Minute, + Jitter: retry.NewJitter(50), + } + + var bsCfg *hcp.BootstrapConfig + + client, err := hcp.NewClient(rc.Cloud) + if err != nil { + return "", err + } + + for { + // Note we don't want to shadow `ctx` here since we need that for the Wait + // below. + reqCtx, cancel := context.WithTimeout(ctx, 5*time.Second) + defer cancel() + + resp, err := client.FetchBootstrap(reqCtx) + if err != nil { + ui.Error(fmt.Sprintf("failed to fetch bootstrap config from HCP, will retry in %s: %s", + w.NextWait().Round(time.Second), err)) + if err := w.Wait(ctx); err != nil { + return "", err + } + // Finished waiting, restart loop + continue + } + bsCfg = resp + break + } + + dataDir := rc.DataDir + shouldPersist := true + if dataDir == "" { + // Agent in dev mode, we still need somewhere to persist the certs + // temporarily though to be able to start up at all since we don't support + // inline certs right now. Use temp dir + tmp, err := os.MkdirTemp(os.TempDir(), "consul-dev-") + if err != nil { + return "", fmt.Errorf("failed to create temp dir for certificates: %w", err) + } + dataDir = tmp + shouldPersist = false + } + + // Persist the TLS cert files from the response since we need to refer to them + // as disk files either way. + if err := persistTLSCerts(dataDir, bsCfg); err != nil { + return "", fmt.Errorf("failed to persist TLS certificates to dir %q: %w", dataDir, err) + } + // Update the config JSON to include those TLS cert files + cfgJSON, err := injectTLSCerts(dataDir, bsCfg.ConsulConfig) + if err != nil { + return "", fmt.Errorf("failed to inject TLS Certs into bootstrap config: %w", err) + } + + // Persist the final config we need to add for restarts. Assuming this wasn't + // a tmp dir to start with. + if shouldPersist { + if err := persistBootstrapConfig(dataDir, cfgJSON); err != nil { + return "", fmt.Errorf("failed to persist bootstrap config to dir %q: %w", dataDir, err) + } + } + + return cfgJSON, nil +} + +func persistTLSCerts(dataDir string, bsCfg *hcp.BootstrapConfig) error { + dir := filepath.Join(dataDir, subDir) + + if bsCfg.TLSCert == "" || bsCfg.TLSCertKey == "" { + return fmt.Errorf("unexpected bootstrap response from HCP: missing TLS information") + } + + // Create a subdir if it's not already there + if err := lib.EnsurePath(dir, true); err != nil { + return err + } + + // Write out CA cert(s). We write them all to one file because Go's x509 + // machinery will read as many certs as it finds from each PEM file provided + // and add them separaetly to the CertPool for validation + f, err := os.OpenFile(filepath.Join(dir, caFileName), os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0600) + if err != nil { + return err + } + bf := bufio.NewWriter(f) + for _, caPEM := range bsCfg.TLSCAs { + bf.WriteString(caPEM + "\n") + } + if err := bf.Flush(); err != nil { + return err + } + if err := f.Close(); err != nil { + return err + } + + if err := ioutil.WriteFile(filepath.Join(dir, certFileName), []byte(bsCfg.TLSCert), 0600); err != nil { + return err + } + + if err := ioutil.WriteFile(filepath.Join(dir, keyFileName), []byte(bsCfg.TLSCertKey), 0600); err != nil { + return err + } + + return nil +} + +func injectTLSCerts(dataDir string, bootstrapJSON string) (string, error) { + // Parse just to a map for now as we only have to inject to a specific place + // and parsing whole Config struct is complicated... + var cfg map[string]interface{} + + if err := json.Unmarshal([]byte(bootstrapJSON), &cfg); err != nil { + return "", err + } + + // Inject TLS cert files + cfg["ca_file"] = filepath.Join(dataDir, subDir, caFileName) + cfg["cert_file"] = filepath.Join(dataDir, subDir, certFileName) + cfg["key_file"] = filepath.Join(dataDir, subDir, keyFileName) + + jsonBs, err := json.Marshal(cfg) + if err != nil { + return "", err + } + + return string(jsonBs), nil +} + +func persistBootstrapConfig(dataDir, cfgJSON string) error { + // Persist the important bits we got from bootstrapping. The TLS certs are + // already persisted, just need to persist the config we are going to add. + name := filepath.Join(dataDir, subDir, configFileName) + return ioutil.WriteFile(name, []byte(cfgJSON), 0600) +} + +func loadPersistedBootstrapConfig(rc *config.RuntimeConfig, ui UI) (string, bool) { + // Check if the files all exist + files := []string{ + filepath.Join(rc.DataDir, subDir, configFileName), + filepath.Join(rc.DataDir, subDir, caFileName), + filepath.Join(rc.DataDir, subDir, certFileName), + filepath.Join(rc.DataDir, subDir, keyFileName), + } + hasSome := false + for _, name := range files { + if _, err := os.Stat(name); errors.Is(err, os.ErrNotExist) { + // At least one required file doesn't exist, failed loading. This is not + // an error though + if hasSome { + ui.Warn("ignoring incomplete local bootstrap config files") + } + return "", false + } + hasSome = true + } + + name := filepath.Join(rc.DataDir, subDir, configFileName) + jsonBs, err := ioutil.ReadFile(name) + if err != nil { + ui.Warn(fmt.Sprintf("failed to read local bootstrap config file, ignoring local files: %s", err)) + return "", false + } + + // Check this looks non-empty at least + jsonStr := strings.TrimSpace(string(jsonBs)) + // 50 is arbitrary but config containing the right secrets would always be + // bigger than this in JSON format so it is a reasonable test that this wasn't + // empty or just an empty JSON object or something. + if len(jsonStr) < 50 { + ui.Warn("ignoring incomplete local bootstrap config files") + return "", false + } + + // TODO we could parse the certificates and check they are still valid here + // and force a reload if not. We could also attempt to parse config and check + // it's all valid just in case the local config was really old and has + // deprecated fields or something? + return jsonStr, true +} diff --git a/agent/hcp/bootstrap/testing.go b/agent/hcp/bootstrap/testing.go new file mode 100644 index 0000000000..b1a05a7e50 --- /dev/null +++ b/agent/hcp/bootstrap/testing.go @@ -0,0 +1,160 @@ +package bootstrap + +import ( + "crypto/rand" + "crypto/x509" + "encoding/base64" + "encoding/json" + "fmt" + "net" + "net/http" + "strings" + + gnmmod "github.com/hashicorp/hcp-sdk-go/clients/cloud-global-network-manager-service/preview/2022-02-15/models" + "github.com/hashicorp/hcp-sdk-go/resource" + + "github.com/hashicorp/consul/agent/hcp" + "github.com/hashicorp/consul/tlsutil" + "github.com/hashicorp/go-uuid" +) + +// TestEndpoint returns an hcp.TestEndpoint to be used in an hcp.MockHCPServer. +func TestEndpoint() hcp.TestEndpoint { + // Memoize data so it's consistent for the life of the test server + data := make(map[string]gnmmod.HashicorpCloudGlobalNetworkManager20220215AgentBootstrapResponse) + + return hcp.TestEndpoint{ + Methods: []string{"GET"}, + PathSuffix: "agent/bootstrap_config", + Handler: func(r *http.Request, cluster resource.Resource) (interface{}, error) { + return handleBootstrap(data, cluster) + }, + } +} + +func handleBootstrap(data map[string]gnmmod.HashicorpCloudGlobalNetworkManager20220215AgentBootstrapResponse, cluster resource.Resource) (interface{}, error) { + resp, ok := data[cluster.ID] + if !ok { + // Create new response + r, err := generateClusterData(cluster) + if err != nil { + return nil, err + } + data[cluster.ID] = r + resp = r + } + return resp, nil +} + +func generateClusterData(cluster resource.Resource) (gnmmod.HashicorpCloudGlobalNetworkManager20220215AgentBootstrapResponse, error) { + resp := gnmmod.HashicorpCloudGlobalNetworkManager20220215AgentBootstrapResponse{ + Cluster: &gnmmod.HashicorpCloudGlobalNetworkManager20220215Cluster{}, + Bootstrap: &gnmmod.HashicorpCloudGlobalNetworkManager20220215ClusterBootstrap{ + ServerTLS: &gnmmod.HashicorpCloudGlobalNetworkManager20220215ServerTLS{}, + }, + } + + CACert, CAKey, err := tlsutil.GenerateCA(tlsutil.CAOpts{}) + if err != nil { + return resp, err + } + + resp.Bootstrap.ServerTLS.CertificateAuthorities = append(resp.Bootstrap.ServerTLS.CertificateAuthorities, CACert) + signer, err := tlsutil.ParseSigner(CAKey) + if err != nil { + return resp, err + } + + cert, priv, err := tlsutil.GenerateCert(tlsutil.CertOpts{ + Signer: signer, + CA: CACert, + Name: "server.dc1.consul", + Days: 30, + DNSNames: []string{"server.dc1.consul", "localhost"}, + IPAddresses: append([]net.IP{}, net.ParseIP("127.0.0.1")), + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth}, + }) + if err != nil { + return resp, err + } + resp.Bootstrap.ServerTLS.Cert = cert + resp.Bootstrap.ServerTLS.PrivateKey = priv + + // Generate Config. We don't use the read config.Config struct because it + // doesn't have `omitempty` which makes the output gross. We only want a tiny + // subset, so we use a map that ends up with the same structure for now. + + // Gossip key + gossipKeyBs := make([]byte, 32) + _, err = rand.Reader.Read(gossipKeyBs) + if err != nil { + return resp, err + } + + retryJoinArgs := map[string]string{ + "provider": "hcp", + "resource_id": cluster.String(), + "client_id": "test_id", + "client_secret": "test_secret", + } + + cfg := map[string]interface{}{ + "encrypt": base64.StdEncoding.EncodeToString(gossipKeyBs), + "encrypt_verify_incoming": true, + "encrypt_verify_outgoing": true, + + // TLS settings (certs will be added by client since we can't put them inline) + "verify_incoming": true, + "verify_outgoing": true, + "verify_server_hostname": true, + "auto_encrypt": map[string]interface{}{ + "allow_tls": true, + }, + + // Enable HTTPS port, disable HTTP + "ports": map[string]interface{}{ + "https": 8501, + "http": -1, + }, + + // RAFT Peers + "bootstrap_expect": 1, + "retry_join": []string{ + mapArgsString(retryJoinArgs), + }, + } + + // ACLs + management, err := uuid.GenerateUUID() + if err != nil { + return resp, err + } + cfg["acl"] = map[string]interface{}{ + "tokens": map[string]interface{}{ + "initial_management": management, + // Also setup the server's own agent token to be the same so it has + // permission to register itself. + "agent": management, + }, + "default_policy": "deny", + "enabled": true, + "enable_token_persistence": true, + } + + // Encode and return a JSON string in the response + jsonBs, err := json.Marshal(cfg) + if err != nil { + return resp, err + } + resp.Bootstrap.ConsulConfig = string(jsonBs) + + return resp, nil +} + +func mapArgsString(m map[string]string) string { + args := make([]string, len(m)) + for k, v := range m { + args = append(args, fmt.Sprintf("%s=%s", k, v)) + } + return strings.Join(args, " ") +} diff --git a/agent/hcp/client.go b/agent/hcp/client.go new file mode 100644 index 0000000000..070814024a --- /dev/null +++ b/agent/hcp/client.go @@ -0,0 +1,217 @@ +package hcp + +import ( + "context" + "fmt" + "strconv" + "time" + + httptransport "github.com/go-openapi/runtime/client" + "github.com/go-openapi/strfmt" + hcpgnm "github.com/hashicorp/hcp-sdk-go/clients/cloud-global-network-manager-service/preview/2022-02-15/client/global_network_manager_service" + gnmmod "github.com/hashicorp/hcp-sdk-go/clients/cloud-global-network-manager-service/preview/2022-02-15/models" + "github.com/hashicorp/hcp-sdk-go/httpclient" + "github.com/hashicorp/hcp-sdk-go/resource" + + "github.com/hashicorp/consul/agent/hcp/config" + "github.com/hashicorp/consul/version" +) + +// Client interface exposes HCP operations that can be invoked by Consul +//go:generate mockery --name Client --with-expecter --inpackage +type Client interface { + FetchBootstrap(ctx context.Context) (*BootstrapConfig, error) + PushServerStatus(ctx context.Context, status *ServerStatus) error + DiscoverServers(ctx context.Context) ([]string, error) +} + +type BootstrapConfig struct { + Name string + BootstrapExpect int + GossipKey string + TLSCert string + TLSCertKey string + TLSCAs []string + ConsulConfig string +} + +type hcpClient struct { + hc *httptransport.Runtime + cfg config.CloudConfig + gnm hcpgnm.ClientService + resource resource.Resource +} + +func NewClient(cfg config.CloudConfig) (Client, error) { + client := &hcpClient{ + cfg: cfg, + } + + var err error + client.resource, err = resource.FromString(cfg.ResourceID) + if err != nil { + return nil, err + } + + client.hc, err = httpClient(cfg) + if err != nil { + return nil, err + } + + client.gnm = hcpgnm.New(client.hc, nil) + return client, nil +} + +func httpClient(c config.CloudConfig) (*httptransport.Runtime, error) { + cfg, err := c.HCPConfig() + if err != nil { + return nil, err + } + + return httpclient.New(httpclient.Config{ + HCPConfig: cfg, + SourceChannel: "consul " + version.GetHumanVersion(), + }) +} + +func (c *hcpClient) FetchBootstrap(ctx context.Context) (*BootstrapConfig, error) { + params := hcpgnm.NewAgentBootstrapConfigParamsWithContext(ctx). + WithID(c.resource.ID). + WithLocationOrganizationID(c.resource.Organization). + WithLocationProjectID(c.resource.Project) + + resp, err := c.gnm.AgentBootstrapConfig(params, nil) + if err != nil { + return nil, err + } + + return bootstrapConfigFromHCP(resp.Payload), nil +} + +func bootstrapConfigFromHCP(res *gnmmod.HashicorpCloudGlobalNetworkManager20220215AgentBootstrapResponse) *BootstrapConfig { + var serverTLS gnmmod.HashicorpCloudGlobalNetworkManager20220215ServerTLS + if res.Bootstrap.ServerTLS != nil { + serverTLS = *res.Bootstrap.ServerTLS + } + + return &BootstrapConfig{ + Name: res.Bootstrap.ID, + BootstrapExpect: int(res.Bootstrap.BootstrapExpect), + GossipKey: res.Bootstrap.GossipKey, + TLSCert: serverTLS.Cert, + TLSCertKey: serverTLS.PrivateKey, + TLSCAs: serverTLS.CertificateAuthorities, + ConsulConfig: res.Bootstrap.ConsulConfig, + } +} + +func (c *hcpClient) PushServerStatus(ctx context.Context, s *ServerStatus) error { + params := hcpgnm.NewAgentPushServerStateParamsWithContext(ctx). + WithID(c.resource.ID). + WithLocationOrganizationID(c.resource.Organization). + WithLocationProjectID(c.resource.Project) + + params.SetBody(&gnmmod.HashicorpCloudGlobalNetworkManager20220215AgentPushServerStateRequest{ + ServerState: serverStatusToHCP(s), + }) + + _, err := c.gnm.AgentPushServerState(params, nil) + return err +} + +type ServerStatus struct { + ID string + Name string + Version string + LanAddress string + GossipPort int + RPCPort int + + Autopilot ServerAutopilot + Raft ServerRaft + TLS ServerTLSInfo + + ScadaStatus string +} + +type ServerAutopilot struct { + FailureTolerance int + Healthy bool + MinQuorum int + NumServers int + NumVoters int +} + +type ServerRaft struct { + IsLeader bool + KnownLeader bool + AppliedIndex uint64 + TimeSinceLastContact time.Duration +} + +type ServerTLSInfo struct { + Enabled bool + CertExpiry time.Time + CertName string + CertSerial string + VerifyIncoming bool + VerifyOutgoing bool + VerifyServerHostname bool +} + +func serverStatusToHCP(s *ServerStatus) *gnmmod.HashicorpCloudGlobalNetworkManager20220215ServerState { + if s == nil { + return nil + } + return &gnmmod.HashicorpCloudGlobalNetworkManager20220215ServerState{ + Autopilot: &gnmmod.HashicorpCloudGlobalNetworkManager20220215AutoPilotInfo{ + FailureTolerance: int32(s.Autopilot.FailureTolerance), + Healthy: s.Autopilot.Healthy, + MinQuorum: int32(s.Autopilot.MinQuorum), + NumServers: int32(s.Autopilot.NumServers), + NumVoters: int32(s.Autopilot.NumVoters), + }, + GossipPort: int32(s.GossipPort), + ID: s.ID, + LanAddress: s.LanAddress, + Name: s.Name, + Raft: &gnmmod.HashicorpCloudGlobalNetworkManager20220215RaftInfo{ + AppliedIndex: strconv.FormatUint(s.Raft.AppliedIndex, 10), + IsLeader: s.Raft.IsLeader, + KnownLeader: s.Raft.KnownLeader, + TimeSinceLastContact: s.Raft.TimeSinceLastContact.String(), + }, + RPCPort: int32(s.RPCPort), + TLS: &gnmmod.HashicorpCloudGlobalNetworkManager20220215TLSInfo{ + CertExpiry: strfmt.DateTime(s.TLS.CertExpiry), + CertName: s.TLS.CertName, + CertSerial: s.TLS.CertSerial, + Enabled: s.TLS.Enabled, + VerifyIncoming: s.TLS.VerifyIncoming, + VerifyOutgoing: s.TLS.VerifyOutgoing, + VerifyServerHostname: s.TLS.VerifyServerHostname, + }, + Version: s.Version, + ScadaStatus: s.ScadaStatus, + } +} + +func (c *hcpClient) DiscoverServers(ctx context.Context) ([]string, error) { + params := hcpgnm.NewAgentDiscoverParamsWithContext(ctx). + WithID(c.resource.ID). + WithLocationOrganizationID(c.resource.Organization). + WithLocationProjectID(c.resource.Project) + + resp, err := c.gnm.AgentDiscover(params, nil) + if err != nil { + return nil, err + } + var servers []string + for _, srv := range resp.Payload.Servers { + if srv != nil { + servers = append(servers, fmt.Sprintf("%s:%d", srv.LanAddress, srv.GossipPort)) + } + } + + return servers, nil +} diff --git a/agent/hcp/config/config.go b/agent/hcp/config/config.go new file mode 100644 index 0000000000..4dc2ffb5f8 --- /dev/null +++ b/agent/hcp/config/config.go @@ -0,0 +1,30 @@ +package config + +import ( + "crypto/tls" + + hcpcfg "github.com/hashicorp/hcp-sdk-go/config" +) + +// CloudConfig defines configuration for connecting to HCP services +type CloudConfig struct { + ResourceID string + ClientID string + ClientSecret string + Hostname string + AuthURL string +} + +func (c *CloudConfig) HCPConfig(opts ...hcpcfg.HCPConfigOption) (hcpcfg.HCPConfig, error) { + if c.ClientID != "" && c.ClientSecret != "" { + opts = append(opts, hcpcfg.WithClientCredentials(c.ClientID, c.ClientSecret)) + } + if c.AuthURL != "" { + opts = append(opts, hcpcfg.WithAuth(c.AuthURL, &tls.Config{})) + } + if c.Hostname != "" { + opts = append(opts, hcpcfg.WithAPI(c.Hostname, &tls.Config{})) + } + opts = append(opts, hcpcfg.FromEnv()) + return hcpcfg.NewHCPConfig(opts...) +} diff --git a/agent/hcp/deps.go b/agent/hcp/deps.go new file mode 100644 index 0000000000..418d02620e --- /dev/null +++ b/agent/hcp/deps.go @@ -0,0 +1,23 @@ +package hcp + +import ( + "github.com/hashicorp/consul/agent/hcp/config" + "github.com/hashicorp/consul/agent/hcp/scada" + "github.com/hashicorp/go-hclog" +) + +// Deps contains the interfaces that the rest of Consul core depends on for HCP integration. +type Deps struct { + Client Client + Provider scada.Provider +} + +func NewDeps(cfg config.CloudConfig, logger hclog.Logger) (d Deps, err error) { + d.Client, err = NewClient(cfg) + if err != nil { + return + } + + d.Provider, err = scada.New(cfg, logger.Named("hcp.scada")) + return +} diff --git a/agent/hcp/discover/discover.go b/agent/hcp/discover/discover.go new file mode 100644 index 0000000000..8707a03a55 --- /dev/null +++ b/agent/hcp/discover/discover.go @@ -0,0 +1,76 @@ +package discover + +import ( + "context" + "fmt" + "log" + "time" + + "github.com/hashicorp/consul/agent/hcp" + "github.com/hashicorp/consul/agent/hcp/config" +) + +type Provider struct { +} + +var ( + defaultTimeout = 5 * time.Second +) + +type providerConfig struct { + config.CloudConfig + + timeout time.Duration +} + +func (p *Provider) Addrs(args map[string]string, l *log.Logger) ([]string, error) { + cfg, err := parseArgs(args) + if err != nil { + return nil, err + } + + client, err := hcp.NewClient(cfg.CloudConfig) + if err != nil { + return nil, err + } + + ctx, cancel := context.WithTimeout(context.Background(), cfg.timeout) + defer cancel() + servers, err := client.DiscoverServers(ctx) + if err != nil { + return nil, err + } + + return servers, nil +} + +func (p *Provider) Help() string { + return "" +} + +func parseArgs(args map[string]string) (cfg providerConfig, err error) { + cfg.timeout = defaultTimeout + + if id, ok := args["resource_id"]; ok { + cfg.ResourceID = id + } else { + err = fmt.Errorf("'resource_id' was not found and is required") + } + + if cid, ok := args["client_id"]; ok { + cfg.ClientID = cid + } + + if csec, ok := args["client_secret"]; ok { + cfg.ClientSecret = csec + } + + if timeoutRaw, ok := args["timeout"]; ok { + timeout, err := time.ParseDuration(timeoutRaw) + if err != nil { + return cfg, err + } + cfg.timeout = timeout + } + return +} diff --git a/agent/hcp/manager.go b/agent/hcp/manager.go new file mode 100644 index 0000000000..9e3624f6af --- /dev/null +++ b/agent/hcp/manager.go @@ -0,0 +1,177 @@ +package hcp + +import ( + "context" + "sync" + "time" + + "github.com/hashicorp/consul/lib" + "github.com/hashicorp/go-hclog" +) + +var ( + defaultManagerMinInterval = 45 * time.Minute + defaultManagerMaxInterval = 75 * time.Minute +) + +type ManagerConfig struct { + Client Client + + StatusFn StatusCallback + MinInterval time.Duration + MaxInterval time.Duration + + Logger hclog.Logger +} + +func (cfg *ManagerConfig) enabled() bool { + return cfg.Client != nil && cfg.StatusFn != nil +} + +func (cfg *ManagerConfig) nextHeartbeat() time.Duration { + min := cfg.MinInterval + if min == 0 { + min = defaultManagerMinInterval + } + + max := cfg.MaxInterval + if max == 0 { + max = defaultManagerMaxInterval + } + if max < min { + max = min + } + return min + lib.RandomStagger(max-min) +} + +type StatusCallback func(context.Context) (ServerStatus, error) + +type Manager struct { + logger hclog.Logger + + cfg ManagerConfig + cfgMu sync.RWMutex + + updateCh chan struct{} + + // testUpdateSent is set by unit tests to signal when the manager's status update has triggered + testUpdateSent chan struct{} +} + +// NewManager returns an initialized Manager with a zero configuration. It won't +// do anything until UpdateConfig is called with a config that provides +// credentials to contact HCP. +func NewManager(cfg ManagerConfig) *Manager { + return &Manager{ + logger: cfg.Logger, + cfg: cfg, + + updateCh: make(chan struct{}, 1), + } +} + +// Run executes the Manager it's designed to be run in its own goroutine for +// the life of a server agent. It should be run even if HCP is not configured +// yet for servers since a config update might configure it later and +// UpdateConfig called. It will effectively do nothing if there are no HCP +// credentials set other than wait for some to be added. +func (m *Manager) Run(ctx context.Context) { + var err error + m.logger.Debug("HCP manager starting") + + // immediately send initial update + select { + case <-ctx.Done(): + return + case <-m.updateCh: // empty the update chan if there is a queued update to prevent repeated update in main loop + err = m.sendUpdate() + default: + err = m.sendUpdate() + } + + // main loop + for { + m.cfgMu.RLock() + cfg := m.cfg + m.cfgMu.RUnlock() + nextUpdate := cfg.nextHeartbeat() + if err != nil { + m.logger.Error("failed to send server status to HCP", "err", err, "next_heartbeat", nextUpdate.String()) + } + + select { + case <-ctx.Done(): + return + + case <-m.updateCh: + err = m.sendUpdate() + + case <-time.After(nextUpdate): + err = m.sendUpdate() + } + } +} + +func (m *Manager) UpdateConfig(cfg ManagerConfig) { + m.cfgMu.Lock() + defer m.cfgMu.Unlock() + old := m.cfg + m.cfg = cfg + if old.enabled() || cfg.enabled() { + // Only log about this if cloud is actually configured or it would be + // confusing. We check both old and new in case we are disabling cloud or + // enabling it or just updating it. + m.logger.Info("updated HCP configuration") + } + + // Send a new status update since we might have just gotten connection details + // for the first time. + m.SendUpdate() +} + +func (m *Manager) SendUpdate() { + m.logger.Debug("HCP triggering status update") + select { + case m.updateCh <- struct{}{}: + // trigger update + default: + // if chan is full then there is already an update triggered that will soon + // be acted on so don't bother blocking. + } +} + +// TODO: we should have retried on failures here with backoff but take into +// account that if a new update is triggered while we are still retrying we +// should not start another retry loop. Something like have a "dirty" flag which +// we mark on first PushUpdate and then a retry timer as well as the interval +// and a "isRetrying" state or something so that we attempt to send update, but +// then fetch fresh info on each attempt to send so if we are already in a retry +// backoff a new push is a no-op. +func (m *Manager) sendUpdate() error { + m.cfgMu.RLock() + cfg := m.cfg + m.cfgMu.RUnlock() + + if !cfg.enabled() { + return nil + } + + if m.testUpdateSent != nil { + defer func() { + select { + case m.testUpdateSent <- struct{}{}: + default: + } + }() + } + + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + + s, err := cfg.StatusFn(ctx) + if err != nil { + return err + } + + return m.cfg.Client.PushServerStatus(ctx, &s) +} diff --git a/agent/hcp/manager_test.go b/agent/hcp/manager_test.go new file mode 100644 index 0000000000..68a4505e99 --- /dev/null +++ b/agent/hcp/manager_test.go @@ -0,0 +1,102 @@ +package hcp + +import ( + "io/ioutil" + "testing" + "time" + + "github.com/hashicorp/go-hclog" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" + "golang.org/x/net/context" +) + +func TestManager_Run(t *testing.T) { + client := NewMockClient(t) + statusF := func(ctx context.Context) (ServerStatus, error) { + return ServerStatus{ID: t.Name()}, nil + } + updateCh := make(chan struct{}, 1) + client.EXPECT().PushServerStatus(mock.Anything, &ServerStatus{ID: t.Name()}).Return(nil).Once() + mgr := NewManager(ManagerConfig{ + Client: client, + Logger: hclog.New(&hclog.LoggerOptions{Output: ioutil.Discard}), + StatusFn: statusF, + }) + mgr.testUpdateSent = updateCh + ctx, cancel := context.WithCancel(context.Background()) + t.Cleanup(cancel) + + go mgr.Run(ctx) + select { + case <-updateCh: + case <-time.After(time.Second): + require.Fail(t, "manager did not send update in expected time") + } + + // Make sure after manager has stopped no more statuses are pushed. + cancel() + mgr.SendUpdate() + client.AssertExpectations(t) +} + +func TestManager_SendUpdate(t *testing.T) { + client := NewMockClient(t) + statusF := func(ctx context.Context) (ServerStatus, error) { + return ServerStatus{ID: t.Name()}, nil + } + updateCh := make(chan struct{}, 1) + + // Expect two calls, once during run startup and again when SendUpdate is called + client.EXPECT().PushServerStatus(mock.Anything, &ServerStatus{ID: t.Name()}).Return(nil).Twice() + mgr := NewManager(ManagerConfig{ + Client: client, + Logger: hclog.New(&hclog.LoggerOptions{Output: ioutil.Discard}), + StatusFn: statusF, + }) + mgr.testUpdateSent = updateCh + go mgr.Run(context.Background()) + select { + case <-updateCh: + case <-time.After(time.Second): + require.Fail(t, "manager did not send update in expected time") + } + mgr.SendUpdate() + select { + case <-updateCh: + case <-time.After(time.Second): + require.Fail(t, "manager did not send update in expected time") + } + client.AssertExpectations(t) +} + +func TestManager_SendUpdate_Periodic(t *testing.T) { + client := NewMockClient(t) + statusF := func(ctx context.Context) (ServerStatus, error) { + return ServerStatus{ID: t.Name()}, nil + } + updateCh := make(chan struct{}, 1) + + // Expect two calls, once during run startup and again when SendUpdate is called + client.EXPECT().PushServerStatus(mock.Anything, &ServerStatus{ID: t.Name()}).Return(nil).Twice() + mgr := NewManager(ManagerConfig{ + Client: client, + Logger: hclog.New(&hclog.LoggerOptions{Output: ioutil.Discard}), + StatusFn: statusF, + MaxInterval: time.Second, + MinInterval: 100 * time.Millisecond, + }) + mgr.testUpdateSent = updateCh + go mgr.Run(context.Background()) + select { + case <-updateCh: + case <-time.After(time.Second): + require.Fail(t, "manager did not send update in expected time") + } + select { + case <-updateCh: + case <-time.After(time.Second): + require.Fail(t, "manager did not send update in expected time") + } + client.AssertExpectations(t) +} diff --git a/agent/hcp/mock_Client.go b/agent/hcp/mock_Client.go new file mode 100644 index 0000000000..ec6129f980 --- /dev/null +++ b/agent/hcp/mock_Client.go @@ -0,0 +1,167 @@ +// Code generated by mockery v2.13.1. DO NOT EDIT. + +package hcp + +import ( + context "context" + + mock "github.com/stretchr/testify/mock" +) + +// MockClient is an autogenerated mock type for the Client type +type MockClient struct { + mock.Mock +} + +type MockClient_Expecter struct { + mock *mock.Mock +} + +func (_m *MockClient) EXPECT() *MockClient_Expecter { + return &MockClient_Expecter{mock: &_m.Mock} +} + +// DiscoverServers provides a mock function with given fields: ctx +func (_m *MockClient) DiscoverServers(ctx context.Context) ([]string, error) { + ret := _m.Called(ctx) + + var r0 []string + if rf, ok := ret.Get(0).(func(context.Context) []string); ok { + r0 = rf(ctx) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]string) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context) error); ok { + r1 = rf(ctx) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// MockClient_DiscoverServers_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'DiscoverServers' +type MockClient_DiscoverServers_Call struct { + *mock.Call +} + +// DiscoverServers is a helper method to define mock.On call +// - ctx context.Context +func (_e *MockClient_Expecter) DiscoverServers(ctx interface{}) *MockClient_DiscoverServers_Call { + return &MockClient_DiscoverServers_Call{Call: _e.mock.On("DiscoverServers", ctx)} +} + +func (_c *MockClient_DiscoverServers_Call) Run(run func(ctx context.Context)) *MockClient_DiscoverServers_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context)) + }) + return _c +} + +func (_c *MockClient_DiscoverServers_Call) Return(_a0 []string, _a1 error) *MockClient_DiscoverServers_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +// FetchBootstrap provides a mock function with given fields: ctx +func (_m *MockClient) FetchBootstrap(ctx context.Context) (*BootstrapConfig, error) { + ret := _m.Called(ctx) + + var r0 *BootstrapConfig + if rf, ok := ret.Get(0).(func(context.Context) *BootstrapConfig); ok { + r0 = rf(ctx) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*BootstrapConfig) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context) error); ok { + r1 = rf(ctx) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// MockClient_FetchBootstrap_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'FetchBootstrap' +type MockClient_FetchBootstrap_Call struct { + *mock.Call +} + +// FetchBootstrap is a helper method to define mock.On call +// - ctx context.Context +func (_e *MockClient_Expecter) FetchBootstrap(ctx interface{}) *MockClient_FetchBootstrap_Call { + return &MockClient_FetchBootstrap_Call{Call: _e.mock.On("FetchBootstrap", ctx)} +} + +func (_c *MockClient_FetchBootstrap_Call) Run(run func(ctx context.Context)) *MockClient_FetchBootstrap_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context)) + }) + return _c +} + +func (_c *MockClient_FetchBootstrap_Call) Return(_a0 *BootstrapConfig, _a1 error) *MockClient_FetchBootstrap_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +// PushServerStatus provides a mock function with given fields: ctx, status +func (_m *MockClient) PushServerStatus(ctx context.Context, status *ServerStatus) error { + ret := _m.Called(ctx, status) + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, *ServerStatus) error); ok { + r0 = rf(ctx, status) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// MockClient_PushServerStatus_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'PushServerStatus' +type MockClient_PushServerStatus_Call struct { + *mock.Call +} + +// PushServerStatus is a helper method to define mock.On call +// - ctx context.Context +// - status *ServerStatus +func (_e *MockClient_Expecter) PushServerStatus(ctx interface{}, status interface{}) *MockClient_PushServerStatus_Call { + return &MockClient_PushServerStatus_Call{Call: _e.mock.On("PushServerStatus", ctx, status)} +} + +func (_c *MockClient_PushServerStatus_Call) Run(run func(ctx context.Context, status *ServerStatus)) *MockClient_PushServerStatus_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(*ServerStatus)) + }) + return _c +} + +func (_c *MockClient_PushServerStatus_Call) Return(_a0 error) *MockClient_PushServerStatus_Call { + _c.Call.Return(_a0) + return _c +} + +type mockConstructorTestingTNewMockClient interface { + mock.TestingT + Cleanup(func()) +} + +// NewMockClient creates a new instance of MockClient. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +func NewMockClient(t mockConstructorTestingTNewMockClient) *MockClient { + mock := &MockClient{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/agent/hcp/scada/capabilities.go b/agent/hcp/scada/capabilities.go new file mode 100644 index 0000000000..ab60251f08 --- /dev/null +++ b/agent/hcp/scada/capabilities.go @@ -0,0 +1,6 @@ +package scada + +import "github.com/hashicorp/hcp-scada-provider/capability" + +// CAPCoreAPI is the capability used to securely expose the Consul HTTP API to HCP +var CAPCoreAPI = capability.NewAddr("core_api") diff --git a/agent/hcp/scada/mock_Provider.go b/agent/hcp/scada/mock_Provider.go new file mode 100644 index 0000000000..744f54e68e --- /dev/null +++ b/agent/hcp/scada/mock_Provider.go @@ -0,0 +1,302 @@ +// Code generated by mockery v2.14.0. DO NOT EDIT. + +package scada + +import ( + net "net" + + mock "github.com/stretchr/testify/mock" + + time "time" +) + +// MockProvider is an autogenerated mock type for the Provider type +type MockProvider struct { + mock.Mock +} + +type MockProvider_Expecter struct { + mock *mock.Mock +} + +func (_m *MockProvider) EXPECT() *MockProvider_Expecter { + return &MockProvider_Expecter{mock: &_m.Mock} +} + +// GetMeta provides a mock function with given fields: +func (_m *MockProvider) GetMeta() map[string]string { + ret := _m.Called() + + var r0 map[string]string + if rf, ok := ret.Get(0).(func() map[string]string); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(map[string]string) + } + } + + return r0 +} + +// MockProvider_GetMeta_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetMeta' +type MockProvider_GetMeta_Call struct { + *mock.Call +} + +// GetMeta is a helper method to define mock.On call +func (_e *MockProvider_Expecter) GetMeta() *MockProvider_GetMeta_Call { + return &MockProvider_GetMeta_Call{Call: _e.mock.On("GetMeta")} +} + +func (_c *MockProvider_GetMeta_Call) Run(run func()) *MockProvider_GetMeta_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *MockProvider_GetMeta_Call) Return(_a0 map[string]string) *MockProvider_GetMeta_Call { + _c.Call.Return(_a0) + return _c +} + +// LastError provides a mock function with given fields: +func (_m *MockProvider) LastError() (time.Time, error) { + ret := _m.Called() + + var r0 time.Time + if rf, ok := ret.Get(0).(func() time.Time); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(time.Time) + } + + var r1 error + if rf, ok := ret.Get(1).(func() error); ok { + r1 = rf() + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// MockProvider_LastError_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'LastError' +type MockProvider_LastError_Call struct { + *mock.Call +} + +// LastError is a helper method to define mock.On call +func (_e *MockProvider_Expecter) LastError() *MockProvider_LastError_Call { + return &MockProvider_LastError_Call{Call: _e.mock.On("LastError")} +} + +func (_c *MockProvider_LastError_Call) Run(run func()) *MockProvider_LastError_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *MockProvider_LastError_Call) Return(_a0 time.Time, _a1 error) *MockProvider_LastError_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +// Listen provides a mock function with given fields: capability +func (_m *MockProvider) Listen(capability string) (net.Listener, error) { + ret := _m.Called(capability) + + var r0 net.Listener + if rf, ok := ret.Get(0).(func(string) net.Listener); ok { + r0 = rf(capability) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(net.Listener) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(string) error); ok { + r1 = rf(capability) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// MockProvider_Listen_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Listen' +type MockProvider_Listen_Call struct { + *mock.Call +} + +// Listen is a helper method to define mock.On call +// - capability string +func (_e *MockProvider_Expecter) Listen(capability interface{}) *MockProvider_Listen_Call { + return &MockProvider_Listen_Call{Call: _e.mock.On("Listen", capability)} +} + +func (_c *MockProvider_Listen_Call) Run(run func(capability string)) *MockProvider_Listen_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(string)) + }) + return _c +} + +func (_c *MockProvider_Listen_Call) Return(_a0 net.Listener, _a1 error) *MockProvider_Listen_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +// SessionStatus provides a mock function with given fields: +func (_m *MockProvider) SessionStatus() string { + ret := _m.Called() + + var r0 string + if rf, ok := ret.Get(0).(func() string); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(string) + } + + return r0 +} + +// MockProvider_SessionStatus_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SessionStatus' +type MockProvider_SessionStatus_Call struct { + *mock.Call +} + +// SessionStatus is a helper method to define mock.On call +func (_e *MockProvider_Expecter) SessionStatus() *MockProvider_SessionStatus_Call { + return &MockProvider_SessionStatus_Call{Call: _e.mock.On("SessionStatus")} +} + +func (_c *MockProvider_SessionStatus_Call) Run(run func()) *MockProvider_SessionStatus_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *MockProvider_SessionStatus_Call) Return(_a0 string) *MockProvider_SessionStatus_Call { + _c.Call.Return(_a0) + return _c +} + +// Start provides a mock function with given fields: +func (_m *MockProvider) Start() error { + ret := _m.Called() + + var r0 error + if rf, ok := ret.Get(0).(func() error); ok { + r0 = rf() + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// MockProvider_Start_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Start' +type MockProvider_Start_Call struct { + *mock.Call +} + +// Start is a helper method to define mock.On call +func (_e *MockProvider_Expecter) Start() *MockProvider_Start_Call { + return &MockProvider_Start_Call{Call: _e.mock.On("Start")} +} + +func (_c *MockProvider_Start_Call) Run(run func()) *MockProvider_Start_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *MockProvider_Start_Call) Return(_a0 error) *MockProvider_Start_Call { + _c.Call.Return(_a0) + return _c +} + +// Stop provides a mock function with given fields: +func (_m *MockProvider) Stop() error { + ret := _m.Called() + + var r0 error + if rf, ok := ret.Get(0).(func() error); ok { + r0 = rf() + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// MockProvider_Stop_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Stop' +type MockProvider_Stop_Call struct { + *mock.Call +} + +// Stop is a helper method to define mock.On call +func (_e *MockProvider_Expecter) Stop() *MockProvider_Stop_Call { + return &MockProvider_Stop_Call{Call: _e.mock.On("Stop")} +} + +func (_c *MockProvider_Stop_Call) Run(run func()) *MockProvider_Stop_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *MockProvider_Stop_Call) Return(_a0 error) *MockProvider_Stop_Call { + _c.Call.Return(_a0) + return _c +} + +// UpdateMeta provides a mock function with given fields: _a0 +func (_m *MockProvider) UpdateMeta(_a0 map[string]string) { + _m.Called(_a0) +} + +// MockProvider_UpdateMeta_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'UpdateMeta' +type MockProvider_UpdateMeta_Call struct { + *mock.Call +} + +// UpdateMeta is a helper method to define mock.On call +// - _a0 map[string]string +func (_e *MockProvider_Expecter) UpdateMeta(_a0 interface{}) *MockProvider_UpdateMeta_Call { + return &MockProvider_UpdateMeta_Call{Call: _e.mock.On("UpdateMeta", _a0)} +} + +func (_c *MockProvider_UpdateMeta_Call) Run(run func(_a0 map[string]string)) *MockProvider_UpdateMeta_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(map[string]string)) + }) + return _c +} + +func (_c *MockProvider_UpdateMeta_Call) Return() *MockProvider_UpdateMeta_Call { + _c.Call.Return() + return _c +} + +type mockConstructorTestingTNewMockProvider interface { + mock.TestingT + Cleanup(func()) +} + +// NewMockProvider creates a new instance of MockProvider. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +func NewMockProvider(t mockConstructorTestingTNewMockProvider) *MockProvider { + mock := &MockProvider{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/agent/hcp/scada/scada.go b/agent/hcp/scada/scada.go new file mode 100644 index 0000000000..b74f762b1a --- /dev/null +++ b/agent/hcp/scada/scada.go @@ -0,0 +1,55 @@ +package scada + +import ( + "fmt" + "net" + + "github.com/hashicorp/consul/agent/hcp/config" + "github.com/hashicorp/go-hclog" + libscada "github.com/hashicorp/hcp-scada-provider" + "github.com/hashicorp/hcp-scada-provider/capability" + "github.com/hashicorp/hcp-sdk-go/resource" +) + +// Provider is the interface used in the rest of Consul core when using SCADA, it is aliased here to the same interface +// provided by the hcp-scada-provider library. If the interfaces needs to be extended in the future it can be done so +// with minimal impact on the rest of the codebase. +// +//go:generate mockery --name Provider --with-expecter --inpackage +type Provider interface { + libscada.SCADAProvider +} + +const ( + scadaConsulServiceKey = "consul" +) + +func New(cfg config.CloudConfig, logger hclog.Logger) (Provider, error) { + resource, err := resource.FromString(cfg.ResourceID) + if err != nil { + return nil, fmt.Errorf("failed to parse cloud resource_id: %w", err) + } + + hcpConfig, err := cfg.HCPConfig() + if err != nil { + return nil, fmt.Errorf("failed to build HCPConfig: %w", err) + } + + pvd, err := libscada.New(&libscada.Config{ + Service: scadaConsulServiceKey, + HCPConfig: hcpConfig, + Resource: *resource.Link(), + Logger: logger, + }) + if err != nil { + return nil, err + } + + return pvd, nil +} + +// IsCapability takes a net.Addr and returns true if it is a SCADA capability.Addr +func IsCapability(a net.Addr) bool { + _, ok := a.(*capability.Addr) + return ok +} diff --git a/agent/hcp/testing.go b/agent/hcp/testing.go new file mode 100644 index 0000000000..e32602777d --- /dev/null +++ b/agent/hcp/testing.go @@ -0,0 +1,177 @@ +package hcp + +import ( + "encoding/json" + "fmt" + "log" + "net/http" + "regexp" + "strings" + "sync" + "time" + + gnmmod "github.com/hashicorp/hcp-sdk-go/clients/cloud-global-network-manager-service/preview/2022-02-15/models" + "github.com/hashicorp/hcp-sdk-go/resource" +) + +type TestEndpoint struct { + Methods []string + PathSuffix string + Handler func(r *http.Request, cluster resource.Resource) (interface{}, error) +} + +type MockHCPServer struct { + mu sync.Mutex + handlers map[string]TestEndpoint + + servers map[string]*gnmmod.HashicorpCloudGlobalNetworkManager20220215Server +} + +var basePathRe = regexp.MustCompile("/global-network-manager/[^/]+/organizations/([^/]+)/projects/([^/]+)/clusters/([^/]+)/([^/]+.*)") + +func NewMockHCPServer() *MockHCPServer { + s := &MockHCPServer{ + handlers: make(map[string]TestEndpoint), + servers: make(map[string]*gnmmod.HashicorpCloudGlobalNetworkManager20220215Server), + } + // Define endpoints in this package + s.AddEndpoint(TestEndpoint{ + Methods: []string{"POST"}, + PathSuffix: "agent/server-state", + Handler: s.handleStatus, + }) + s.AddEndpoint(TestEndpoint{ + Methods: []string{"POST"}, + PathSuffix: "agent/discover", + Handler: s.handleDiscover, + }) + return s +} + +// AddEndpoint allows adding additional endpoints from other packages e.g. +// bootstrap (which can't be merged into one package due to dependency cycles). +// It's not safe to call this concurrently with any other call to AddEndpoint or +// ServeHTTP. +func (s *MockHCPServer) AddEndpoint(e TestEndpoint) { + s.handlers[e.PathSuffix] = e +} + +func (s *MockHCPServer) ServeHTTP(w http.ResponseWriter, r *http.Request) { + s.mu.Lock() + defer s.mu.Unlock() + + if r.URL.Path == "/oauth/token" { + mockTokenResponse(w) + return + } + + matches := basePathRe.FindStringSubmatch(r.URL.Path) + if matches == nil || len(matches) < 5 { + w.WriteHeader(404) + log.Printf("ERROR 404: %s %s\n", r.Method, r.URL.Path) + return + } + + cluster := resource.Resource{ + ID: matches[3], + Type: "cluster", + Organization: matches[1], + Project: matches[2], + } + found := false + var resp interface{} + var err error + for _, e := range s.handlers { + if e.PathSuffix == matches[4] { + found = true + if !enforceMethod(w, r, e.Methods) { + return + } + resp, err = e.Handler(r, cluster) + break + } + } + if !found { + w.WriteHeader(404) + log.Printf("ERROR 404: %s %s\n", r.Method, r.URL.Path) + return + } + if err != nil { + errResponse(w, err) + return + } + + if resp == nil { + // no response body + log.Printf("OK 204: %s %s\n", r.Method, r.URL.Path) + w.WriteHeader(http.StatusNoContent) + return + } + + bs, err := json.MarshalIndent(resp, "", " ") + if err != nil { + errResponse(w, err) + return + } + + log.Printf("OK 200: %s %s\n", r.Method, r.URL.Path) + w.Header().Set("content-type", "application/json") + w.WriteHeader(http.StatusOK) + w.Write(bs) +} + +func enforceMethod(w http.ResponseWriter, r *http.Request, methods []string) bool { + for _, m := range methods { + if strings.EqualFold(r.Method, m) { + return true + } + } + // No match, sent 4xx + w.WriteHeader(http.StatusMethodNotAllowed) + log.Printf("ERROR 405: bad method (not in %v): %s %s\n", methods, r.Method, r.URL.Path) + return false +} + +func mockTokenResponse(w http.ResponseWriter) { + w.WriteHeader(http.StatusOK) + w.Write([]byte(`{access_token: "token", token_type: "Bearer"}`)) +} + +func (s *MockHCPServer) handleStatus(r *http.Request, cluster resource.Resource) (interface{}, error) { + var req gnmmod.HashicorpCloudGlobalNetworkManager20220215AgentPushServerStateRequest + if err := json.NewDecoder(r.Body).Decode(&req); err != nil { + return nil, err + } + status := req.ServerState + log.Printf("STATUS UPDATE: server=%s version=%s leader=%v hasLeader=%v healthy=%v tlsCertExpiryDays=%1.0f", + status.Name, + status.Version, + status.Raft.IsLeader, + status.Raft.KnownLeader, + status.Autopilot.Healthy, + time.Until(time.Time(status.TLS.CertExpiry)).Hours()/24, + ) + s.servers[status.Name] = &gnmmod.HashicorpCloudGlobalNetworkManager20220215Server{ + GossipPort: status.GossipPort, + ID: status.ID, + LanAddress: status.LanAddress, + Name: status.Name, + RPCPort: status.RPCPort, + } + return "{}", nil +} + +func (s *MockHCPServer) handleDiscover(r *http.Request, cluster resource.Resource) (interface{}, error) { + servers := make([]*gnmmod.HashicorpCloudGlobalNetworkManager20220215Server, len(s.servers)) + for _, server := range s.servers { + servers = append(servers, server) + } + + return gnmmod.HashicorpCloudGlobalNetworkManager20220215AgentDiscoverResponse{Servers: servers}, nil +} + +func errResponse(w http.ResponseWriter, err error) { + log.Printf("ERROR 500: %s\n", err) + w.WriteHeader(500) + w.Write([]byte(fmt.Sprintf(`{"error": %q}`, err.Error()))) +} diff --git a/agent/hcp/testserver/main.go b/agent/hcp/testserver/main.go new file mode 100644 index 0000000000..0166d00682 --- /dev/null +++ b/agent/hcp/testserver/main.go @@ -0,0 +1,45 @@ +package main + +import ( + "flag" + "fmt" + "log" + "net/http" + "os" + "os/signal" + "syscall" + + "github.com/hashicorp/consul/agent/hcp" + "github.com/hashicorp/consul/agent/hcp/bootstrap" +) + +var port int + +func main() { + flag.IntVar(&port, "port", 9999, "port to listen on") + flag.Parse() + + s := hcp.NewMockHCPServer() + s.AddEndpoint(bootstrap.TestEndpoint()) + + sigs := make(chan os.Signal, 1) + signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM) + + addr := fmt.Sprintf("127.0.0.1:%d", port) + srv := http.Server{ + Addr: addr, + Handler: s, + } + + log.Printf("Listening on %s\n", addr) + + go func() { + if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed { + log.Fatalf("listen: %s\n", err) + } + }() + + <-sigs + log.Println("Shutting down HTTP server") + srv.Close() +} diff --git a/agent/peering_endpoint.go b/agent/peering_endpoint.go index 6ef7167b26..5632f320fc 100644 --- a/agent/peering_endpoint.go +++ b/agent/peering_endpoint.go @@ -7,6 +7,7 @@ import ( "github.com/hashicorp/consul/acl" external "github.com/hashicorp/consul/agent/grpc-external" + "github.com/hashicorp/consul/agent/structs" "github.com/hashicorp/consul/api" "github.com/hashicorp/consul/lib" "github.com/hashicorp/consul/proto/pbpeering" @@ -42,9 +43,13 @@ func (s *HTTPHandlers) peeringRead(resp http.ResponseWriter, req *http.Request, Partition: entMeta.PartitionOrEmpty(), } - var token string - s.parseToken(req, &token) - ctx := external.ContextWithToken(req.Context(), token) + var dc string + options := structs.QueryOptions{} + s.parse(resp, req, &dc, &options) + ctx, err := external.ContextWithQueryOptions(req.Context(), options) + if err != nil { + return nil, err + } result, err := s.agent.rpcClientPeering.PeeringRead(ctx, &args) if err != nil { @@ -67,9 +72,13 @@ func (s *HTTPHandlers) PeeringList(resp http.ResponseWriter, req *http.Request) Partition: entMeta.PartitionOrEmpty(), } - var token string - s.parseToken(req, &token) - ctx := external.ContextWithToken(req.Context(), token) + var dc string + options := structs.QueryOptions{} + s.parse(resp, req, &dc, &options) + ctx, err := external.ContextWithQueryOptions(req.Context(), options) + if err != nil { + return nil, err + } pbresp, err := s.agent.rpcClientPeering.PeeringList(ctx, &args) if err != nil { @@ -106,7 +115,11 @@ func (s *HTTPHandlers) PeeringGenerateToken(resp http.ResponseWriter, req *http. var token string s.parseToken(req, &token) - ctx := external.ContextWithToken(req.Context(), token) + options := structs.QueryOptions{Token: token} + ctx, err := external.ContextWithQueryOptions(req.Context(), options) + if err != nil { + return nil, err + } out, err := s.agent.rpcClientPeering.GenerateToken(ctx, args) if err != nil { @@ -146,7 +159,11 @@ func (s *HTTPHandlers) PeeringEstablish(resp http.ResponseWriter, req *http.Requ var token string s.parseToken(req, &token) - ctx := external.ContextWithToken(req.Context(), token) + options := structs.QueryOptions{Token: token} + ctx, err := external.ContextWithQueryOptions(req.Context(), options) + if err != nil { + return nil, err + } out, err := s.agent.rpcClientPeering.Establish(ctx, args) if err != nil { @@ -170,9 +187,13 @@ func (s *HTTPHandlers) peeringDelete(resp http.ResponseWriter, req *http.Request var token string s.parseToken(req, &token) - ctx := external.ContextWithToken(req.Context(), token) + options := structs.QueryOptions{Token: token} + ctx, err := external.ContextWithQueryOptions(req.Context(), options) + if err != nil { + return nil, err + } - _, err := s.agent.rpcClientPeering.PeeringDelete(ctx, &args) + _, err = s.agent.rpcClientPeering.PeeringDelete(ctx, &args) if err != nil { return nil, err } diff --git a/agent/peering_endpoint_test.go b/agent/peering_endpoint_test.go index 5555fde108..55722dc3e7 100644 --- a/agent/peering_endpoint_test.go +++ b/agent/peering_endpoint_test.go @@ -22,25 +22,6 @@ import ( "github.com/hashicorp/consul/testrpc" ) -var validCA = ` ------BEGIN CERTIFICATE----- -MIICmDCCAj6gAwIBAgIBBzAKBggqhkjOPQQDAjAWMRQwEgYDVQQDEwtDb25zdWwg -Q0EgNzAeFw0xODA1MjExNjMzMjhaFw0yODA1MTgxNjMzMjhaMBYxFDASBgNVBAMT -C0NvbnN1bCBDQSA3MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAER0qlxjnRcMEr -iSGlH7G7dYU7lzBEmLUSMZkyBbClmyV8+e8WANemjn+PLnCr40If9cmpr7RnC9Qk -GTaLnLiF16OCAXswggF3MA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/ -MGgGA1UdDgRhBF8xZjo5MTpjYTo0MTo4ZjphYzo2NzpiZjo1OTpjMjpmYTo0ZTo3 -NTo1YzpkODpmMDo1NTpkZTpiZTo3NTpiODozMzozMTpkNToyNDpiMDowNDpiMzpl -ODo5Nzo1Yjo3ZTBqBgNVHSMEYzBhgF8xZjo5MTpjYTo0MTo4ZjphYzo2NzpiZjo1 -OTpjMjpmYTo0ZTo3NTo1YzpkODpmMDo1NTpkZTpiZTo3NTpiODozMzozMTpkNToy -NDpiMDowNDpiMzplODo5Nzo1Yjo3ZTA/BgNVHREEODA2hjRzcGlmZmU6Ly8xMjRk -ZjVhMC05ODIwLTc2YzMtOWFhOS02ZjYyMTY0YmExYzIuY29uc3VsMD0GA1UdHgEB -/wQzMDGgLzAtgisxMjRkZjVhMC05ODIwLTc2YzMtOWFhOS02ZjYyMTY0YmExYzIu -Y29uc3VsMAoGCCqGSM49BAMCA0gAMEUCIQDzkkI7R+0U12a+zq2EQhP/n2mHmta+ -fs2hBxWIELGwTAIgLdO7RRw+z9nnxCIA6kNl//mIQb+PGItespiHZKAz74Q= ------END CERTIFICATE----- -` - func TestHTTP_Peering_GenerateToken(t *testing.T) { if testing.Short() { t.Skip("too slow for testing.Short") @@ -50,6 +31,7 @@ func TestHTTP_Peering_GenerateToken(t *testing.T) { a := NewTestAgent(t, "") testrpc.WaitForTestAgent(t, a.RPC, "dc1") + testrpc.WaitForActiveCARoot(t, a.RPC, "dc1", nil) t.Run("No Body", func(t *testing.T) { req, err := http.NewRequest("POST", "/v1/peering/token", nil) @@ -107,9 +89,9 @@ func TestHTTP_Peering_GenerateToken(t *testing.T) { var token structs.PeeringToken require.NoError(t, json.Unmarshal(tokenJSON, &token)) - require.Nil(t, token.CA) - require.Equal(t, []string{fmt.Sprintf("127.0.0.1:%d", a.config.GRPCPort)}, token.ServerAddresses) - require.Equal(t, "server.dc1.consul", token.ServerName) + require.NotNil(t, token.CA) + require.Equal(t, []string{fmt.Sprintf("127.0.0.1:%d", a.config.GRPCTLSPort)}, token.ServerAddresses) + require.Equal(t, "server.dc1.peering.11111111-2222-3333-4444-555555555555.consul", token.ServerName) // The PeerID in the token is randomly generated so we don't assert on its value. require.NotEmpty(t, token.PeerID) @@ -140,9 +122,9 @@ func TestHTTP_Peering_GenerateToken(t *testing.T) { var token structs.PeeringToken require.NoError(t, json.Unmarshal(tokenJSON, &token)) - require.Nil(t, token.CA) + require.NotNil(t, token.CA) require.Equal(t, []string{externalAddress}, token.ServerAddresses) - require.Equal(t, "server.dc1.consul", token.ServerName) + require.Equal(t, "server.dc1.peering.11111111-2222-3333-4444-555555555555.consul", token.ServerName) // The PeerID in the token is randomly generated so we don't assert on its value. require.NotEmpty(t, token.PeerID) @@ -159,6 +141,7 @@ func TestHTTP_Peering_GenerateToken_EdgeCases(t *testing.T) { a := NewTestAgent(t, "") testrpc.WaitForTestAgent(t, a.RPC, "dc1") + testrpc.WaitForActiveCARoot(t, a.RPC, "dc1", nil) body := &pbpeering.GenerateTokenRequest{ PeerName: "peering-a", @@ -219,10 +202,9 @@ func TestHTTP_Peering_Establish(t *testing.T) { t.Skip("too slow for testing.Short") } - t.Parallel() a := NewTestAgent(t, "") - testrpc.WaitForTestAgent(t, a.RPC, "dc1") + testrpc.WaitForActiveCARoot(t, a.RPC, "dc1", nil) t.Run("No Body", func(t *testing.T) { req, err := http.NewRequest("POST", "/v1/peering/establish", nil) @@ -291,14 +273,17 @@ func TestHTTP_Peering_Establish(t *testing.T) { }) require.NoError(t, err) - req, err = http.NewRequest("POST", "/v1/peering/establish", bytes.NewReader(b)) - require.NoError(t, err) - resp = httptest.NewRecorder() - a2.srv.h.ServeHTTP(resp, req) - require.Equal(t, http.StatusOK, resp.Code, "expected 200, got %d: %v", resp.Code, resp.Body.String()) + retry.Run(t, func(r *retry.R) { + req, err = http.NewRequest("POST", "/v1/peering/establish", bytes.NewReader(b)) + require.NoError(r, err) + + resp = httptest.NewRecorder() + a2.srv.h.ServeHTTP(resp, req) + require.Equal(r, http.StatusOK, resp.Code, "expected 200, got %d: %v", resp.Code, resp.Body.String()) - // success response does not currently return a value so {} is correct - require.Equal(t, "{}", resp.Body.String()) + // success response does not currently return a value so {} is correct + require.Equal(r, "{}", resp.Body.String()) + }) }) } @@ -390,9 +375,8 @@ func TestHTTP_Peering_Read(t *testing.T) { require.Equal(t, foo.Peering.Name, apiResp.Name) require.Equal(t, foo.Peering.Meta, apiResp.Meta) - require.Equal(t, uint64(0), apiResp.ImportedServiceCount) - require.Equal(t, uint64(0), apiResp.ExportedServiceCount) - + require.Equal(t, 0, len(apiResp.StreamStatus.ImportedServices)) + require.Equal(t, 0, len(apiResp.StreamStatus.ExportedServices)) }) t.Run("not found", func(t *testing.T) { @@ -414,6 +398,7 @@ func TestHTTP_Peering_Delete(t *testing.T) { a := NewTestAgent(t, "") testrpc.WaitForTestAgent(t, a.RPC, "dc1") + testrpc.WaitForActiveCARoot(t, a.RPC, "dc1", nil) bodyBytes, err := json.Marshal(&pbpeering.GenerateTokenRequest{ PeerName: "foo", @@ -519,8 +504,8 @@ func TestHTTP_Peering_List(t *testing.T) { require.Len(t, apiResp, 2) for _, p := range apiResp { - require.Equal(t, uint64(0), p.ImportedServiceCount) - require.Equal(t, uint64(0), p.ExportedServiceCount) + require.Equal(t, 0, len(p.StreamStatus.ImportedServices)) + require.Equal(t, 0, len(p.StreamStatus.ExportedServices)) } }) } diff --git a/agent/proxycfg-glue/exported_peered_services_test.go b/agent/proxycfg-glue/exported_peered_services_test.go index 6c6bae11e6..e9285d357b 100644 --- a/agent/proxycfg-glue/exported_peered_services_test.go +++ b/agent/proxycfg-glue/exported_peered_services_test.go @@ -36,13 +36,13 @@ func TestServerExportedPeeredServices(t *testing.T) { { Name: "web", Consumers: []structs.ServiceConsumer{ - {PeerName: "peer-1"}, + {Peer: "peer-1"}, }, }, { Name: "db", Consumers: []structs.ServiceConsumer{ - {PeerName: "peer-2"}, + {Peer: "peer-2"}, }, }, }, @@ -78,20 +78,20 @@ func TestServerExportedPeeredServices(t *testing.T) { { Name: "web", Consumers: []structs.ServiceConsumer{ - {PeerName: "peer-1"}, + {Peer: "peer-1"}, }, }, { Name: "db", Consumers: []structs.ServiceConsumer{ - {PeerName: "peer-2"}, + {Peer: "peer-2"}, }, }, { Name: "api", Consumers: []structs.ServiceConsumer{ - {PeerName: "peer-1"}, - {PeerName: "peer-3"}, + {Peer: "peer-1"}, + {Peer: "peer-3"}, }, }, }, diff --git a/agent/proxycfg-glue/glue.go b/agent/proxycfg-glue/glue.go index 6aef1da543..862c09e817 100644 --- a/agent/proxycfg-glue/glue.go +++ b/agent/proxycfg-glue/glue.go @@ -43,6 +43,7 @@ type Store interface { ReadResolvedServiceConfigEntries(ws memdb.WatchSet, serviceName string, entMeta *acl.EnterpriseMeta, upstreamIDs []structs.ServiceID, proxyMode structs.ProxyMode) (uint64, *configentry.ResolvedServiceConfigSet, error) ServiceDiscoveryChain(ws memdb.WatchSet, serviceName string, entMeta *acl.EnterpriseMeta, req discoverychain.CompileRequest) (uint64, *structs.CompiledDiscoveryChain, *configentry.DiscoveryChainSet, error) ServiceDump(ws memdb.WatchSet, kind structs.ServiceKind, useKind bool, entMeta *acl.EnterpriseMeta, peerName string) (uint64, structs.CheckServiceNodes, error) + PeeringList(ws memdb.WatchSet, entMeta acl.EnterpriseMeta) (uint64, []*pbpeering.Peering, error) PeeringTrustBundleRead(ws memdb.WatchSet, q state.Query) (uint64, *pbpeering.PeeringTrustBundle, error) PeeringTrustBundleList(ws memdb.WatchSet, entMeta acl.EnterpriseMeta) (uint64, []*pbpeering.PeeringTrustBundle, error) TrustBundleListByService(ws memdb.WatchSet, service, dc string, entMeta acl.EnterpriseMeta) (uint64, []*pbpeering.PeeringTrustBundle, error) diff --git a/agent/proxycfg-glue/intentions_test.go b/agent/proxycfg-glue/intentions_test.go index 3597109f73..5c4701e642 100644 --- a/agent/proxycfg-glue/intentions_test.go +++ b/agent/proxycfg-glue/intentions_test.go @@ -149,7 +149,9 @@ func (r *staticResolver) SwapAuthorizer(authz acl.Authorizer) { r.authorizer = authz } -func (r *staticResolver) ResolveTokenAndDefaultMeta(token string, entMeta *acl.EnterpriseMeta, authzContext *acl.AuthorizerContext) (resolver.Result, error) { +func (r *staticResolver) ResolveTokenAndDefaultMeta(_ string, entMeta *acl.EnterpriseMeta, authzContext *acl.AuthorizerContext) (resolver.Result, error) { + entMeta.FillAuthzContext(authzContext) + r.mu.Lock() defer r.mu.Unlock() return resolver.Result{Authorizer: r.authorizer}, nil diff --git a/agent/proxycfg-glue/peering_list.go b/agent/proxycfg-glue/peering_list.go new file mode 100644 index 0000000000..296a79edb4 --- /dev/null +++ b/agent/proxycfg-glue/peering_list.go @@ -0,0 +1,58 @@ +package proxycfgglue + +import ( + "context" + + "github.com/hashicorp/go-memdb" + + "github.com/hashicorp/consul/acl" + "github.com/hashicorp/consul/agent/cache" + cachetype "github.com/hashicorp/consul/agent/cache-types" + "github.com/hashicorp/consul/agent/consul/watch" + "github.com/hashicorp/consul/agent/proxycfg" + "github.com/hashicorp/consul/agent/structs" + "github.com/hashicorp/consul/proto/pbpeering" +) + +// CachePeeringList satisfies the proxycfg.PeeringList interface by sourcing +// data from the agent cache. +func CachePeeringList(c *cache.Cache) proxycfg.PeeringList { + return &cacheProxyDataSource[*cachetype.PeeringListRequest]{c, cachetype.PeeringListName} +} + +// ServerPeeringList satisfies the proxycfg.PeeringList interface by sourcing +// data from a blocking query against the server's state store. +func ServerPeeringList(deps ServerDataSourceDeps) proxycfg.PeeringList { + return &serverPeeringList{deps} +} + +type serverPeeringList struct { + deps ServerDataSourceDeps +} + +func (s *serverPeeringList) Notify(ctx context.Context, req *cachetype.PeeringListRequest, correlationID string, ch chan<- proxycfg.UpdateEvent) error { + entMeta := structs.DefaultEnterpriseMetaInPartition(req.Request.Partition) + + return watch.ServerLocalNotify(ctx, correlationID, s.deps.GetStore, + func(ws memdb.WatchSet, store Store) (uint64, *pbpeering.PeeringListResponse, error) { + var authzCtx acl.AuthorizerContext + authz, err := s.deps.ACLResolver.ResolveTokenAndDefaultMeta(req.Token, entMeta, &authzCtx) + if err != nil { + return 0, nil, err + } + if err := authz.ToAllowAuthorizer().PeeringReadAllowed(&authzCtx); err != nil { + return 0, nil, err + } + + index, peerings, err := store.PeeringList(ws, *entMeta) + if err != nil { + return 0, nil, err + } + return index, &pbpeering.PeeringListResponse{ + Index: index, + Peerings: peerings, + }, nil + }, + dispatchBlockingQueryUpdate[*pbpeering.PeeringListResponse](ch), + ) +} diff --git a/agent/proxycfg-glue/peering_list_test.go b/agent/proxycfg-glue/peering_list_test.go new file mode 100644 index 0000000000..7b9aa0088e --- /dev/null +++ b/agent/proxycfg-glue/peering_list_test.go @@ -0,0 +1,119 @@ +package proxycfgglue + +import ( + "context" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/hashicorp/consul/acl" + cachetype "github.com/hashicorp/consul/agent/cache-types" + "github.com/hashicorp/consul/agent/consul/state" + "github.com/hashicorp/consul/agent/proxycfg" + "github.com/hashicorp/consul/proto/pbpeering" + "github.com/hashicorp/consul/sdk/testutil" +) + +func TestServerPeeringList(t *testing.T) { + const ( + index uint64 = 123 + ) + + store := state.NewStateStore(nil) + + req := pbpeering.PeeringWriteRequest{ + Peering: &pbpeering.Peering{ + Name: "peer-01", + ID: "00000000-0000-0000-0000-000000000000", + }, + } + + require.NoError(t, store.PeeringWrite(index, &req)) + + dataSource := ServerPeeringList(ServerDataSourceDeps{ + GetStore: func() Store { return store }, + ACLResolver: newStaticResolver(acl.ManageAll()), + }) + + eventCh := make(chan proxycfg.UpdateEvent) + err := dataSource.Notify(context.Background(), &cachetype.PeeringListRequest{ + Request: &pbpeering.PeeringListRequest{}, + }, "", eventCh) + require.NoError(t, err) + + testutil.RunStep(t, "initial state", func(t *testing.T) { + result := getEventResult[*pbpeering.PeeringListResponse](t, eventCh) + require.Len(t, result.Peerings, 1) + require.Equal(t, "peer-01", result.Peerings[0].Name) + require.Equal(t, index, result.Index) + }) + + testutil.RunStep(t, "add peering", func(t *testing.T) { + req = pbpeering.PeeringWriteRequest{ + Peering: &pbpeering.Peering{ + Name: "peer-02", + ID: "00000000-0000-0000-0000-000000000001", + }, + } + require.NoError(t, store.PeeringWrite(index+1, &req)) + + result := getEventResult[*pbpeering.PeeringListResponse](t, eventCh) + require.Len(t, result.Peerings, 2) + require.Equal(t, "peer-02", result.Peerings[1].Name) + require.Equal(t, index+1, result.Index) + }) +} + +func TestServerPeeringList_ACLEnforcement(t *testing.T) { + const ( + index uint64 = 123 + ) + + store := state.NewStateStore(nil) + + req := pbpeering.PeeringWriteRequest{ + Peering: &pbpeering.Peering{ + Name: "peer-01", + ID: "00000000-0000-0000-0000-000000000000", + }, + } + + require.NoError(t, store.PeeringWrite(index, &req)) + + testutil.RunStep(t, "can read", func(t *testing.T) { + authz := policyAuthorizer(t, ` + peering = "read"`) + dataSource := ServerPeeringList(ServerDataSourceDeps{ + GetStore: func() Store { return store }, + ACLResolver: newStaticResolver(authz), + }) + + eventCh := make(chan proxycfg.UpdateEvent) + err := dataSource.Notify(context.Background(), &cachetype.PeeringListRequest{ + Request: &pbpeering.PeeringListRequest{}, + }, "", eventCh) + require.NoError(t, err) + + result := getEventResult[*pbpeering.PeeringListResponse](t, eventCh) + require.Len(t, result.Peerings, 1) + require.Equal(t, "peer-01", result.Peerings[0].Name) + require.Equal(t, index, result.Index) + }) + + testutil.RunStep(t, "can't read", func(t *testing.T) { + authz := policyAuthorizer(t, ``) + dataSource := ServerPeeringList(ServerDataSourceDeps{ + GetStore: func() Store { return store }, + ACLResolver: newStaticResolver(authz), + }) + + eventCh := make(chan proxycfg.UpdateEvent) + err := dataSource.Notify(context.Background(), &cachetype.PeeringListRequest{ + Request: &pbpeering.PeeringListRequest{}, + }, "", eventCh) + require.NoError(t, err) + + err = getEventError(t, eventCh) + require.Contains(t, err.Error(), "provided token lacks permission 'peering:read'") + }) +} diff --git a/agent/proxycfg-glue/trust_bundle_test.go b/agent/proxycfg-glue/trust_bundle_test.go index a4fb7e05d0..611abe15ac 100644 --- a/agent/proxycfg-glue/trust_bundle_test.go +++ b/agent/proxycfg-glue/trust_bundle_test.go @@ -144,7 +144,7 @@ func TestServerTrustBundleList(t *testing.T) { { Name: serviceName, Consumers: []structs.ServiceConsumer{ - {PeerName: them}, + {Peer: them}, }, }, }, @@ -249,7 +249,7 @@ func TestServerTrustBundleList_ACLEnforcement(t *testing.T) { { Name: serviceName, Consumers: []structs.ServiceConsumer{ - {PeerName: them}, + {Peer: them}, }, }, }, diff --git a/agent/proxycfg/connect_proxy.go b/agent/proxycfg/connect_proxy.go index 2ff1f9ca9f..69764843d6 100644 --- a/agent/proxycfg/connect_proxy.go +++ b/agent/proxycfg/connect_proxy.go @@ -26,6 +26,7 @@ func (s *handlerConnectProxy) initialize(ctx context.Context) (ConfigSnapshot, e snap.ConnectProxy.UpstreamPeerTrustBundles = watch.NewMap[string, *pbpeering.PeeringTrustBundle]() snap.ConnectProxy.WatchedGateways = make(map[UpstreamID]map[string]context.CancelFunc) snap.ConnectProxy.WatchedGatewayEndpoints = make(map[UpstreamID]map[string]structs.CheckServiceNodes) + snap.ConnectProxy.WatchedLocalGWEndpoints = watch.NewMap[string, structs.CheckServiceNodes]() snap.ConnectProxy.WatchedServiceChecks = make(map[structs.ServiceID][]structs.CheckType) snap.ConnectProxy.PreparedQueryEndpoints = make(map[UpstreamID]structs.CheckServiceNodes) snap.ConnectProxy.DestinationsUpstream = watch.NewMap[UpstreamID, *structs.ServiceConfigEntry]() @@ -155,11 +156,6 @@ func (s *handlerConnectProxy) initialize(ctx context.Context) (ConfigSnapshot, e if u.Datacenter != "" { dc = u.Datacenter } - if s.proxyCfg.Mode == structs.ProxyModeTransparent && (dc == "" || dc == s.source.Datacenter) { - // In transparent proxy mode, watches for upstreams in the local DC - // are handled by the IntentionUpstreams and PeeredUpstreams watch. - continue - } // Default the partition and namespace to the namespace of this proxy service. partition := s.proxyID.PartitionOrDefault() @@ -184,7 +180,7 @@ func (s *handlerConnectProxy) initialize(ctx context.Context) (ConfigSnapshot, e switch u.DestinationType { case structs.UpstreamDestTypePreparedQuery: - err = s.dataSources.PreparedQuery.Notify(ctx, &structs.PreparedQueryExecuteRequest{ + err := s.dataSources.PreparedQuery.Notify(ctx, &structs.PreparedQueryExecuteRequest{ Datacenter: dc, QueryOptions: structs.QueryOptions{Token: s.token, MaxAge: defaultPreparedQueryPollInterval}, QueryIDOrName: u.DestinationName, @@ -200,51 +196,14 @@ func (s *handlerConnectProxy) initialize(ctx context.Context) (ConfigSnapshot, e case "": if u.DestinationPeer != "" { - // NOTE: An upstream that points to a peer by definition will - // only ever watch a single catalog query, so a map key of just - // "UID" is sufficient to cover the peer data watches here. - - s.logger.Trace("initializing watch of peered upstream", "upstream", uid) - - snap.ConnectProxy.PeerUpstreamEndpoints.InitWatch(uid, nil) - err := s.dataSources.Health.Notify(ctx, &structs.ServiceSpecificRequest{ - PeerName: uid.Peer, - Datacenter: dc, - QueryOptions: structs.QueryOptions{ - Token: s.token, - }, - ServiceName: u.DestinationName, - Connect: true, - // Note that Identifier doesn't type-prefix for service any more as it's - // the default and makes metrics and other things much cleaner. It's - // simpler for us if we have the type to make things unambiguous. - Source: *s.source, - EnterpriseMeta: uid.EnterpriseMeta, - }, upstreamPeerWatchIDPrefix+uid.String(), s.ch) + err := s.setupWatchesForPeeredUpstream(ctx, snap.ConnectProxy, u, dc) if err != nil { return snap, err } - - // Check whether a watch for this peer exists to avoid duplicates. - if ok := snap.ConnectProxy.UpstreamPeerTrustBundles.IsWatched(uid.Peer); !ok { - peerCtx, cancel := context.WithCancel(ctx) - if err := s.dataSources.TrustBundle.Notify(peerCtx, &cachetype.TrustBundleReadRequest{ - Request: &pbpeering.TrustBundleReadRequest{ - Name: uid.Peer, - Partition: uid.PartitionOrDefault(), - }, - QueryOptions: structs.QueryOptions{Token: s.token}, - }, peerTrustBundleIDPrefix+uid.Peer, s.ch); err != nil { - cancel() - return snap, fmt.Errorf("error while watching trust bundle for peer %q: %w", uid.Peer, err) - } - - snap.ConnectProxy.UpstreamPeerTrustBundles.InitWatch(uid.Peer, cancel) - } continue } - err = s.dataSources.CompiledDiscoveryChain.Notify(ctx, &structs.DiscoveryChainRequest{ + err := s.dataSources.CompiledDiscoveryChain.Notify(ctx, &structs.DiscoveryChainRequest{ Datacenter: s.source.Datacenter, QueryOptions: structs.QueryOptions{Token: s.token}, Name: u.DestinationName, @@ -267,6 +226,76 @@ func (s *handlerConnectProxy) initialize(ctx context.Context) (ConfigSnapshot, e return snap, nil } +func (s *handlerConnectProxy) setupWatchesForPeeredUpstream( + ctx context.Context, + snapConnectProxy configSnapshotConnectProxy, + u structs.Upstream, + dc string, +) error { + uid := NewUpstreamID(&u) + + s.logger.Trace("initializing watch of peered upstream", "upstream", uid) + + // NOTE: An upstream that points to a peer by definition will + // only ever watch a single catalog query, so a map key of just + // "UID" is sufficient to cover the peer data watches here. + snapConnectProxy.PeerUpstreamEndpoints.InitWatch(uid, nil) + err := s.dataSources.Health.Notify(ctx, &structs.ServiceSpecificRequest{ + PeerName: uid.Peer, + Datacenter: dc, + QueryOptions: structs.QueryOptions{ + Token: s.token, + }, + ServiceName: u.DestinationName, + Connect: true, + Source: *s.source, + EnterpriseMeta: uid.EnterpriseMeta, + }, upstreamPeerWatchIDPrefix+uid.String(), s.ch) + if err != nil { + return err + } + + // Check whether a watch for this peer exists to avoid duplicates. + if ok := snapConnectProxy.UpstreamPeerTrustBundles.IsWatched(uid.Peer); !ok { + peerCtx, cancel := context.WithCancel(ctx) + if err := s.dataSources.TrustBundle.Notify(peerCtx, &cachetype.TrustBundleReadRequest{ + Request: &pbpeering.TrustBundleReadRequest{ + Name: uid.Peer, + Partition: uid.PartitionOrDefault(), + }, + QueryOptions: structs.QueryOptions{Token: s.token}, + }, peerTrustBundleIDPrefix+uid.Peer, s.ch); err != nil { + cancel() + return fmt.Errorf("error while watching trust bundle for peer %q: %w", uid.Peer, err) + } + + snapConnectProxy.UpstreamPeerTrustBundles.InitWatch(uid.Peer, cancel) + } + + // If a peered upstream is set to local mesh gw mode, + // set up a watch for them. + if u.MeshGateway.Mode == structs.MeshGatewayModeLocal { + gk := GatewayKey{ + Partition: s.source.NodePartitionOrDefault(), + Datacenter: s.source.Datacenter, + } + if !snapConnectProxy.WatchedLocalGWEndpoints.IsWatched(gk.String()) { + opts := gatewayWatchOpts{ + internalServiceDump: s.dataSources.InternalServiceDump, + notifyCh: s.ch, + source: *s.source, + token: s.token, + key: gk, + } + if err := watchMeshGateway(ctx, opts); err != nil { + return fmt.Errorf("error while watching for local mesh gateway: %w", err) + } + snapConnectProxy.WatchedLocalGWEndpoints.InitWatch(gk.String(), nil) + } + } + return nil +} + func (s *handlerConnectProxy) handleUpdate(ctx context.Context, u UpdateEvent, snap *ConfigSnapshot) error { if u.Err != nil { return fmt.Errorf("error filling agent cache: %v", u.Err) @@ -465,7 +494,7 @@ func (s *handlerConnectProxy) handleUpdate(ctx context.Context, u UpdateEvent, s // Clean up data from services that were not in the update for uid, targets := range snap.ConnectProxy.WatchedUpstreams { - if upstream, ok := snap.ConnectProxy.UpstreamConfig[uid]; ok && upstream.Datacenter != "" && upstream.Datacenter != s.source.Datacenter { + if upstream, ok := snap.ConnectProxy.UpstreamConfig[uid]; ok && !upstream.CentrallyConfigured { continue } if _, ok := seenUpstreams[uid]; !ok { @@ -482,7 +511,7 @@ func (s *handlerConnectProxy) handleUpdate(ctx context.Context, u UpdateEvent, s } } for uid := range snap.ConnectProxy.WatchedUpstreamEndpoints { - if upstream, ok := snap.ConnectProxy.UpstreamConfig[uid]; ok && upstream.Datacenter != "" && upstream.Datacenter != s.source.Datacenter { + if upstream, ok := snap.ConnectProxy.UpstreamConfig[uid]; ok && !upstream.CentrallyConfigured { continue } if _, ok := seenUpstreams[uid]; !ok { @@ -490,7 +519,7 @@ func (s *handlerConnectProxy) handleUpdate(ctx context.Context, u UpdateEvent, s } } for uid, cancelMap := range snap.ConnectProxy.WatchedGateways { - if upstream, ok := snap.ConnectProxy.UpstreamConfig[uid]; ok && upstream.Datacenter != "" && upstream.Datacenter != s.source.Datacenter { + if upstream, ok := snap.ConnectProxy.UpstreamConfig[uid]; ok && !upstream.CentrallyConfigured { continue } if _, ok := seenUpstreams[uid]; !ok { @@ -501,7 +530,7 @@ func (s *handlerConnectProxy) handleUpdate(ctx context.Context, u UpdateEvent, s } } for uid := range snap.ConnectProxy.WatchedGatewayEndpoints { - if upstream, ok := snap.ConnectProxy.UpstreamConfig[uid]; ok && upstream.Datacenter != "" && upstream.Datacenter != s.source.Datacenter { + if upstream, ok := snap.ConnectProxy.UpstreamConfig[uid]; ok && !upstream.CentrallyConfigured { continue } if _, ok := seenUpstreams[uid]; !ok { @@ -509,7 +538,7 @@ func (s *handlerConnectProxy) handleUpdate(ctx context.Context, u UpdateEvent, s } } for uid, cancelFn := range snap.ConnectProxy.WatchedDiscoveryChains { - if upstream, ok := snap.ConnectProxy.UpstreamConfig[uid]; ok && upstream.Datacenter != "" && upstream.Datacenter != s.source.Datacenter { + if upstream, ok := snap.ConnectProxy.UpstreamConfig[uid]; ok && !upstream.CentrallyConfigured { continue } if _, ok := seenUpstreams[uid]; !ok { @@ -533,7 +562,7 @@ func (s *handlerConnectProxy) handleUpdate(ctx context.Context, u UpdateEvent, s // That update event then re-populated the DiscoveryChain map entry, which wouldn't get cleaned up // since there was no known watch for it. for uid := range snap.ConnectProxy.DiscoveryChain { - if upstream, ok := snap.ConnectProxy.UpstreamConfig[uid]; ok && upstream.Datacenter != "" && upstream.Datacenter != s.source.Datacenter { + if upstream, ok := snap.ConnectProxy.UpstreamConfig[uid]; ok && !upstream.CentrallyConfigured { continue } if _, ok := seenUpstreams[uid]; !ok { diff --git a/agent/proxycfg/data_sources.go b/agent/proxycfg/data_sources.go index c012610710..0327a45a02 100644 --- a/agent/proxycfg/data_sources.go +++ b/agent/proxycfg/data_sources.go @@ -103,6 +103,9 @@ type DataSources struct { // notification channel. PeeredUpstreams PeeredUpstreams + // PeeringList provides peering updates on a notification channel. + PeeringList PeeringList + // PreparedQuery provides updates about the results of a prepared query. PreparedQuery PreparedQuery @@ -215,6 +218,11 @@ type PeeredUpstreams interface { Notify(ctx context.Context, req *structs.PartitionSpecificRequest, correlationID string, ch chan<- UpdateEvent) error } +// PeeringList is the interface used to consume updates about peerings in the cluster or partition +type PeeringList interface { + Notify(ctx context.Context, req *cachetype.PeeringListRequest, correlationID string, ch chan<- UpdateEvent) error +} + // PreparedQuery is the interface used to consume updates about the results of // a prepared query. type PreparedQuery interface { diff --git a/agent/proxycfg/ingress_gateway.go b/agent/proxycfg/ingress_gateway.go index 81a4928369..a6549af009 100644 --- a/agent/proxycfg/ingress_gateway.go +++ b/agent/proxycfg/ingress_gateway.go @@ -98,6 +98,9 @@ func (s *handlerIngressGateway) handleUpdate(ctx context.Context, u UpdateEvent, snap.IngressGateway.GatewayConfigLoaded = true snap.IngressGateway.TLSConfig = gatewayConf.TLS + if gatewayConf.Defaults != nil { + snap.IngressGateway.Defaults = *gatewayConf.Defaults + } // Load each listener's config from the config entry so we don't have to // pass listener config through "upstreams" types as that grows. @@ -201,7 +204,7 @@ func makeUpstream(g *structs.GatewayService) structs.Upstream { } func (s *handlerIngressGateway) watchIngressLeafCert(ctx context.Context, snap *ConfigSnapshot) error { - // Note that we DON'T test for TLS.Enabled because we need a leaf cert for the + // Note that we DON'T test for TLS.enabled because we need a leaf cert for the // gateway even without TLS to use as a client cert. if !snap.IngressGateway.GatewayConfigLoaded || !snap.IngressGateway.HostsSet { return nil diff --git a/agent/proxycfg/manager_test.go b/agent/proxycfg/manager_test.go index 2a3cdd15f2..eb382e89d8 100644 --- a/agent/proxycfg/manager_test.go +++ b/agent/proxycfg/manager_test.go @@ -224,6 +224,7 @@ func TestManager_BasicLifecycle(t *testing.T) { WatchedGatewayEndpoints: map[UpstreamID]map[string]structs.CheckServiceNodes{ dbUID: {}, }, + WatchedLocalGWEndpoints: watch.NewMap[string, structs.CheckServiceNodes](), UpstreamConfig: map[UpstreamID]*structs.Upstream{ NewUpstreamID(&upstreams[0]): &upstreams[0], NewUpstreamID(&upstreams[1]): &upstreams[1], @@ -287,6 +288,7 @@ func TestManager_BasicLifecycle(t *testing.T) { WatchedGatewayEndpoints: map[UpstreamID]map[string]structs.CheckServiceNodes{ dbUID: {}, }, + WatchedLocalGWEndpoints: watch.NewMap[string, structs.CheckServiceNodes](), UpstreamConfig: map[UpstreamID]*structs.Upstream{ NewUpstreamID(&upstreams[0]): &upstreams[0], NewUpstreamID(&upstreams[1]): &upstreams[1], diff --git a/agent/proxycfg/mesh_gateway.go b/agent/proxycfg/mesh_gateway.go index 93fffdc31d..1378f9b9bf 100644 --- a/agent/proxycfg/mesh_gateway.go +++ b/agent/proxycfg/mesh_gateway.go @@ -3,11 +3,16 @@ package proxycfg import ( "context" "fmt" + "net" "sort" + "strconv" "strings" "time" + "github.com/hashicorp/go-hclog" + cachetype "github.com/hashicorp/consul/agent/cache-types" + "github.com/hashicorp/consul/agent/proxycfg/internal/watch" "github.com/hashicorp/consul/agent/structs" "github.com/hashicorp/consul/lib/maps" "github.com/hashicorp/consul/logging" @@ -18,9 +23,19 @@ type handlerMeshGateway struct { handlerState } +type peerAddressType string + +const ( + undefinedAddressType peerAddressType = "" + ipAddressType peerAddressType = "ip" + hostnameAddressType peerAddressType = "hostname" +) + // initialize sets up the watches needed based on the current mesh gateway registration func (s *handlerMeshGateway) initialize(ctx context.Context) (ConfigSnapshot, error) { snap := newConfigSnapshotFromServiceInstance(s.serviceInstance, s.stateConfig) + snap.MeshGateway.WatchedLocalServers = watch.NewMap[string, structs.CheckServiceNodes]() + // Watch for root changes err := s.dataSources.CARoots.Notify(ctx, &structs.DCSpecificRequest{ Datacenter: s.source.Datacenter, @@ -76,7 +91,7 @@ func (s *handlerMeshGateway) initialize(ctx context.Context) (ConfigSnapshot, er } if s.proxyID.InDefaultPartition() { - if err := s.initializeCrossDCWatches(ctx); err != nil { + if err := s.initializeCrossDCWatches(ctx, &snap); err != nil { return snap, err } } @@ -117,13 +132,16 @@ func (s *handlerMeshGateway) initialize(ctx context.Context) (ConfigSnapshot, er snap.MeshGateway.ExportedServicesWithPeers = make(map[structs.ServiceName][]string) snap.MeshGateway.DiscoveryChain = make(map[structs.ServiceName]*structs.CompiledDiscoveryChain) snap.MeshGateway.WatchedDiscoveryChains = make(map[structs.ServiceName]context.CancelFunc) + snap.MeshGateway.WatchedPeeringServices = make(map[string]map[structs.ServiceName]context.CancelFunc) + snap.MeshGateway.WatchedPeers = make(map[string]context.CancelFunc) + snap.MeshGateway.PeeringServices = make(map[string]map[structs.ServiceName]PeeringServiceValue) // there is no need to initialize the map of service resolvers as we // fully rebuild it every time we get updates return snap, err } -func (s *handlerMeshGateway) initializeCrossDCWatches(ctx context.Context) error { +func (s *handlerMeshGateway) initializeCrossDCWatches(ctx context.Context, snap *ConfigSnapshot) error { if s.meta[structs.MetaWANFederationKey] == "1" { // Conveniently we can just use this service meta attribute in one // place here to set the machinery in motion and leave the conditional @@ -145,6 +163,7 @@ func (s *handlerMeshGateway) initializeCrossDCWatches(ctx context.Context) error if err != nil { return err } + snap.MeshGateway.WatchedLocalServers.InitWatch(structs.ConsulServiceName, nil) } err := s.dataSources.Datacenters.Notify(ctx, &structs.DatacentersRequest{ @@ -325,7 +344,6 @@ func (s *handlerMeshGateway) handleUpdate(ctx context.Context, u UpdateEvent, sn return fmt.Errorf("invalid type for response: %T", u.Result) } - // Do some initial sanity checks to avoid doing something dumb. for _, csn := range resp.Nodes { if csn.Service.Service != structs.ConsulServiceName { return fmt.Errorf("expected service name %q but got %q", @@ -337,7 +355,7 @@ func (s *handlerMeshGateway) handleUpdate(ctx context.Context, u UpdateEvent, sn } } - snap.MeshGateway.ConsulServers = resp.Nodes + snap.MeshGateway.WatchedLocalServers.Set(structs.ConsulServiceName, resp.Nodes) case exportedServiceListWatchID: exportedServices, ok := u.Result.(*structs.IndexedExportedServiceList) @@ -457,25 +475,221 @@ func (s *handlerMeshGateway) handleUpdate(ctx context.Context, u UpdateEvent, sn } snap.MeshGateway.PeeringTrustBundlesSet = true + wildcardEntMeta := s.proxyID.WithWildcardNamespace() + + // For each peer, fetch the imported services to support mesh gateway local + // mode. + for _, tb := range resp.Bundles { + entMeta := structs.DefaultEnterpriseMetaInDefaultPartition() + + if _, ok := snap.MeshGateway.WatchedPeers[tb.PeerName]; !ok { + ctx, cancel := context.WithCancel(ctx) + + err := s.dataSources.ServiceList.Notify(ctx, &structs.DCSpecificRequest{ + PeerName: tb.PeerName, + QueryOptions: structs.QueryOptions{Token: s.token}, + Source: *s.source, + EnterpriseMeta: *wildcardEntMeta, + }, peeringServiceListWatchID+tb.PeerName, s.ch) + + if err != nil { + meshLogger.Error("failed to register watch for mesh-gateway", + "peer", tb.PeerName, + "partition", entMeta.PartitionOrDefault(), + "error", err, + ) + cancel() + return err + } + snap.MeshGateway.WatchedPeers[tb.PeerName] = cancel + } + } + + for peerName, cancelFn := range snap.MeshGateway.WatchedPeers { + found := false + for _, bundle := range resp.Bundles { + if peerName == bundle.PeerName { + found = true + break + } + } + if !found { + delete(snap.MeshGateway.PeeringServices, peerName) + delete(snap.MeshGateway.WatchedPeers, peerName) + delete(snap.MeshGateway.WatchedPeeringServices, peerName) + cancelFn() + } + } + case meshConfigEntryID: resp, ok := u.Result.(*structs.ConfigEntryResponse) if !ok { return fmt.Errorf("invalid type for response: %T", u.Result) } - if resp.Entry != nil { - meshConf, ok := resp.Entry.(*structs.MeshConfigEntry) - if !ok { - return fmt.Errorf("invalid type for config entry: %T", resp.Entry) - } - snap.MeshGateway.MeshConfig = meshConf - } else { - snap.MeshGateway.MeshConfig = nil + meshConf, ok := resp.Entry.(*structs.MeshConfigEntry) + if resp.Entry != nil && !ok { + return fmt.Errorf("invalid type for config entry: %T", resp.Entry) } + snap.MeshGateway.MeshConfig = meshConf snap.MeshGateway.MeshConfigSet = true + // If we're peering through mesh gateways it means the config entry may be deleted + // or the flag was disabled. Here we clean up related watches if they exist. + if !meshConf.PeerThroughMeshGateways() { + // We avoid canceling server watches when WAN federation is enabled since it + // always requires a watch to the local servers. + if s.meta[structs.MetaWANFederationKey] != "1" { + // If the entry was deleted we cancel watches that may have existed because of + // PeerThroughMeshGateways being set in the past. + snap.MeshGateway.WatchedLocalServers.CancelWatch(structs.ConsulServiceName) + } + if snap.MeshGateway.PeerServersWatchCancel != nil { + snap.MeshGateway.PeerServersWatchCancel() + snap.MeshGateway.PeerServersWatchCancel = nil + + snap.MeshGateway.PeerServers = nil + } + + return nil + } + + // If PeerThroughMeshGateways is enabled, and we are in the default partition, + // we need to start watching the list of peering connections in all partitions + // to set up outbound routes for the control plane. Consul servers are in the default partition, + // so only mesh gateways here have his responsibility. + if snap.ProxyID.InDefaultPartition() && + snap.MeshGateway.PeerServersWatchCancel == nil { + + peeringListCtx, cancel := context.WithCancel(ctx) + err := s.dataSources.PeeringList.Notify(peeringListCtx, &cachetype.PeeringListRequest{ + Request: &pbpeering.PeeringListRequest{ + Partition: structs.WildcardSpecifier, + }, + }, peerServersWatchID, s.ch) + if err != nil { + meshLogger.Error("failed to register watch for peering list", "error", err) + cancel() + return err + } + + snap.MeshGateway.PeerServersWatchCancel = cancel + } + + // We avoid initializing Consul server watches when WAN federation is enabled since it + // always requires server watches. + if s.meta[structs.MetaWANFederationKey] == "1" { + return nil + } + + if snap.MeshGateway.WatchedLocalServers.IsWatched(structs.ConsulServiceName) { + return nil + } + + notifyCtx, cancel := context.WithCancel(ctx) + err := s.dataSources.Health.Notify(notifyCtx, &structs.ServiceSpecificRequest{ + Datacenter: s.source.Datacenter, + QueryOptions: structs.QueryOptions{Token: s.token}, + ServiceName: structs.ConsulServiceName, + }, consulServerListWatchID, s.ch) + if err != nil { + cancel() + return fmt.Errorf("failed to watch local consul servers: %w", err) + } + + snap.MeshGateway.WatchedLocalServers.InitWatch(structs.ConsulServiceName, cancel) + + case peerServersWatchID: + resp, ok := u.Result.(*pbpeering.PeeringListResponse) + if !ok { + return fmt.Errorf("invalid type for response: %T", u.Result) + } + + peerServers := make(map[string]PeerServersValue) + for _, peering := range resp.Peerings { + // We only need to keep track of outbound establish connections + // for mesh gateway. + if !peering.ShouldDial() || !peering.IsActive() { + continue + } + + if existing, ok := peerServers[peering.PeerServerName]; ok && existing.Index >= peering.ModifyIndex { + // Multiple peerings can reference the same set of Consul servers, since there can be + // multiple partitions in a datacenter. Rather than randomly overwriting, we attempt to + // use the latest addresses by checking the Raft index associated with the peering. + continue + } + + hostnames, ips := peerHostnamesAndIPs(meshLogger, peering.Name, peering.PeerServerAddresses) + if len(hostnames) > 0 { + peerServers[peering.PeerServerName] = PeerServersValue{ + Addresses: hostnames, + Index: peering.ModifyIndex, + UseCDS: true, + } + } else if len(ips) > 0 { + peerServers[peering.PeerServerName] = PeerServersValue{ + Addresses: ips, + Index: peering.ModifyIndex, + } + } + } + + snap.MeshGateway.PeerServers = peerServers + default: switch { + case strings.HasPrefix(u.CorrelationID, peeringServiceListWatchID): + services, ok := u.Result.(*structs.IndexedServiceList) + if !ok { + return fmt.Errorf("invalid type for response: %T", u.Result) + } + + peerName := strings.TrimPrefix(u.CorrelationID, peeringServiceListWatchID) + + svcMap := make(map[structs.ServiceName]struct{}) + + if _, ok := snap.MeshGateway.WatchedPeeringServices[peerName]; !ok { + snap.MeshGateway.WatchedPeeringServices[peerName] = make(map[structs.ServiceName]context.CancelFunc) + } + + for _, svc := range services.Services { + // Make sure to add every service to this map, we use it to cancel + // watches below. + svcMap[svc] = struct{}{} + + if _, ok := snap.MeshGateway.WatchedPeeringServices[peerName][svc]; !ok { + ctx, cancel := context.WithCancel(ctx) + err := s.dataSources.Health.Notify(ctx, &structs.ServiceSpecificRequest{ + PeerName: peerName, + QueryOptions: structs.QueryOptions{Token: s.token}, + ServiceName: svc.Name, + Connect: true, + EnterpriseMeta: svc.EnterpriseMeta, + }, fmt.Sprintf("peering-connect-service:%s:%s", peerName, svc.String()), s.ch) + + if err != nil { + meshLogger.Error("failed to register watch for connect-service", + "service", svc.String(), + "error", err, + ) + cancel() + return err + } + snap.MeshGateway.WatchedPeeringServices[peerName][svc] = cancel + } + } + + watchedServices := snap.MeshGateway.WatchedPeeringServices[peerName] + for sn, cancelFn := range watchedServices { + if _, ok := svcMap[sn]; !ok { + meshLogger.Debug("canceling watch for service", "service", sn.String()) + delete(snap.MeshGateway.WatchedPeeringServices[peerName], sn) + delete(snap.MeshGateway.PeeringServices[peerName], sn) + cancelFn() + } + } + case strings.HasPrefix(u.CorrelationID, "connect-service:"): resp, ok := u.Result.(*structs.IndexedCheckServiceNodes) if !ok { @@ -489,6 +703,38 @@ func (s *handlerMeshGateway) handleUpdate(ctx context.Context, u UpdateEvent, sn } else if _, ok := snap.MeshGateway.ServiceGroups[sn]; ok { delete(snap.MeshGateway.ServiceGroups, sn) } + case strings.HasPrefix(u.CorrelationID, "peering-connect-service:"): + resp, ok := u.Result.(*structs.IndexedCheckServiceNodes) + + if !ok { + return fmt.Errorf("invalid type for response: %T", u.Result) + } + + key := strings.TrimPrefix(u.CorrelationID, "peering-connect-service:") + peer, snString, ok := strings.Cut(key, ":") + + if ok { + sn := structs.ServiceNameFromString(snString) + + if len(resp.Nodes) > 0 { + if _, ok := snap.MeshGateway.PeeringServices[peer]; !ok { + snap.MeshGateway.PeeringServices[peer] = make(map[structs.ServiceName]PeeringServiceValue) + } + + if eps := hostnameEndpoints(s.logger, GatewayKey{}, resp.Nodes); len(eps) > 0 { + snap.MeshGateway.PeeringServices[peer][sn] = PeeringServiceValue{ + Nodes: eps, + UseCDS: true, + } + } else { + snap.MeshGateway.PeeringServices[peer][sn] = PeeringServiceValue{ + Nodes: resp.Nodes, + } + } + } else if _, ok := snap.MeshGateway.PeeringServices[peer]; ok { + delete(snap.MeshGateway.PeeringServices[peer], sn) + } + } case strings.HasPrefix(u.CorrelationID, "mesh-gateway:"): resp, ok := u.Result.(*structs.IndexedCheckServiceNodes) @@ -534,3 +780,40 @@ func (s *handlerMeshGateway) handleUpdate(ctx context.Context, u UpdateEvent, sn return nil } + +func peerHostnamesAndIPs(logger hclog.Logger, peerName string, addresses []string) ([]structs.ServiceAddress, []structs.ServiceAddress) { + var ( + hostnames []structs.ServiceAddress + ips []structs.ServiceAddress + ) + + // Sort the input so that the output is also sorted. + sort.Strings(addresses) + + for _, addr := range addresses { + ip, rawPort, splitErr := net.SplitHostPort(addr) + port, convErr := strconv.Atoi(rawPort) + + if splitErr != nil || convErr != nil { + logger.Warn("unable to parse ip and port from peer server address. skipping address.", + "peer", peerName, "address", addr) + } + if net.ParseIP(ip) != nil { + ips = append(ips, structs.ServiceAddress{ + Address: ip, + Port: port, + }) + } else { + hostnames = append(hostnames, structs.ServiceAddress{ + Address: ip, + Port: port, + }) + } + } + + if len(hostnames) > 0 && len(ips) > 0 { + logger.Warn("peer server address list contains mix of hostnames and IP addresses; only hostnames will be passed to Envoy", + "peer", peerName) + } + return hostnames, ips +} diff --git a/agent/proxycfg/snapshot.go b/agent/proxycfg/snapshot.go index 23cb8a9556..aed223fb0a 100644 --- a/agent/proxycfg/snapshot.go +++ b/agent/proxycfg/snapshot.go @@ -58,6 +58,16 @@ type ConfigSnapshotUpstreams struct { // backing endpoints of a mesh gateway. WatchedGatewayEndpoints map[UpstreamID]map[string]structs.CheckServiceNodes + // WatchedLocalGWEndpoints is used to store the backing endpoints of + // a local mesh gateway. Currently, this is used by peered upstreams + // configured with local mesh gateway mode so that they can watch for + // gateway endpoints. + // + // Note that the string form of GatewayKey is used as the key so empty + // fields can be normalized in OSS. + // GatewayKey.String() -> structs.CheckServiceNodes + WatchedLocalGWEndpoints watch.Map[string, structs.CheckServiceNodes] + // UpstreamConfig is a map to an upstream's configuration. UpstreamConfig map[UpstreamID]*structs.Upstream @@ -338,6 +348,17 @@ func (c *configSnapshotTerminatingGateway) isEmpty() bool { !c.MeshConfigSet } +type PeerServersValue struct { + Addresses []structs.ServiceAddress + Index uint64 + UseCDS bool +} + +type PeeringServiceValue struct { + Nodes structs.CheckServiceNodes + UseCDS bool +} + type configSnapshotMeshGateway struct { // WatchedServices is a map of service name to a cancel function. This cancel // function is tied to the watch of connect enabled services for the given @@ -363,6 +384,19 @@ type configSnapshotMeshGateway struct { // service in the local datacenter. ServiceGroups map[structs.ServiceName]structs.CheckServiceNodes + // PeeringServices is a map of peer name -> (map of + // service name -> CheckServiceNodes) and is used to determine the backing + // endpoints of a service on a peer. + PeeringServices map[string]map[structs.ServiceName]PeeringServiceValue + + // WatchedPeeringServices is a map of peer name -> (map of service name -> + // cancel function) and is used to track watches on services within a peer. + WatchedPeeringServices map[string]map[structs.ServiceName]context.CancelFunc + + // WatchedPeers is a map of peer name -> cancel functions. It is used to + // track watches on peers. + WatchedPeers map[string]context.CancelFunc + // ServiceResolvers is a map of service name to an associated // service-resolver config entry for that service. ServiceResolvers map[structs.ServiceName]*structs.ServiceResolverConfigEntry @@ -375,8 +409,11 @@ type configSnapshotMeshGateway struct { // datacenter. FedStateGateways map[string]structs.CheckServiceNodes - // ConsulServers is the list of consul servers in this datacenter. - ConsulServers structs.CheckServiceNodes + // WatchedLocalServers is a map of (structs.ConsulServiceName -> structs.CheckServiceNodes)` + // Mesh gateways can spin up watches for local servers both for + // WAN federation and for peering. This map ensures we only have one + // watch at a time. + WatchedLocalServers watch.Map[string, structs.CheckServiceNodes] // HostnameDatacenters is a map of datacenters to mesh gateway instances with a hostname as the address. // If hostnames are configured they must be provided to Envoy via CDS not EDS. @@ -418,6 +455,13 @@ type configSnapshotMeshGateway struct { // leaf cert watch with different parameters. LeafCertWatchCancel context.CancelFunc + // PeerServers is the map of peering server names to their addresses. + PeerServers map[string]PeerServersValue + + // PeerServersWatchCancel is a CancelFunc to use when resetting the watch + // on all peerings as it is enabled/disabled. + PeerServersWatchCancel context.CancelFunc + // PeeringTrustBundles is the list of trust bundles for peers where // services have been exported to using this mesh gateway. PeeringTrustBundles []*pbpeering.PeeringTrustBundle @@ -556,8 +600,8 @@ func (c *configSnapshotMeshGateway) isEmpty() bool { len(c.ServiceResolvers) == 0 && len(c.GatewayGroups) == 0 && len(c.FedStateGateways) == 0 && - len(c.ConsulServers) == 0 && len(c.HostnameDatacenters) == 0 && + c.WatchedLocalServers.Len() == 0 && c.isEmptyPeering() } @@ -609,6 +653,9 @@ type configSnapshotIngressGateway struct { // Listeners is the original listener config from the ingress-gateway config // entry to save us trying to pass fields through Upstreams Listeners map[IngressListenerKey]structs.IngressListener + + // Defaults is the default configuration for upstream service instances + Defaults structs.IngressServiceConfig } // isEmpty is a test helper @@ -690,8 +737,11 @@ func (s *ConfigSnapshot) Valid() bool { s.TerminatingGateway.MeshConfigSet case structs.ServiceKindMeshGateway: - if s.ServiceMeta[structs.MetaWANFederationKey] == "1" { - if len(s.MeshGateway.ConsulServers) == 0 { + if s.MeshGateway.WatchedLocalServers.Len() == 0 { + if s.ServiceMeta[structs.MetaWANFederationKey] == "1" { + return false + } + if cfg := s.MeshConfig(); cfg.PeerThroughMeshGateways() { return false } } diff --git a/agent/proxycfg/state.go b/agent/proxycfg/state.go index 34d3364356..1ed67d3d54 100644 --- a/agent/proxycfg/state.go +++ b/agent/proxycfg/state.go @@ -25,6 +25,7 @@ const ( peerTrustBundleIDPrefix = "peer-trust-bundle:" intentionsWatchID = "intentions" serviceListWatchID = "service-list" + peeringServiceListWatchID = "peering-service-list:" federationStateListGatewaysWatchID = "federation-state-list-mesh-gateways" consulServerListWatchID = "consul-server-list" datacentersWatchID = "datacenters" @@ -37,6 +38,7 @@ const ( serviceResolverIDPrefix = "service-resolver:" serviceIntentionsIDPrefix = "service-intentions:" intentionUpstreamsID = "intention-upstreams" + peerServersWatchID = "peer-servers" peeredUpstreamsID = "peered-upstreams" intentionUpstreamsDestinationID = "intention-upstreams-destination" upstreamPeerWatchIDPrefix = "upstream-peer:" @@ -476,6 +478,13 @@ type gatewayWatchOpts struct { } func watchMeshGateway(ctx context.Context, opts gatewayWatchOpts) error { + var correlationId string + if opts.upstreamID.Name == "" { + correlationId = fmt.Sprintf("mesh-gateway:%s", opts.key.String()) + } else { + correlationId = fmt.Sprintf("mesh-gateway:%s:%s", opts.key.String(), opts.upstreamID.String()) + } + return opts.internalServiceDump.Notify(ctx, &structs.ServiceDumpRequest{ Datacenter: opts.key.Datacenter, QueryOptions: structs.QueryOptions{Token: opts.token}, @@ -483,5 +492,5 @@ func watchMeshGateway(ctx context.Context, opts gatewayWatchOpts) error { UseServiceKind: true, Source: opts.source, EnterpriseMeta: *structs.DefaultEnterpriseMetaInPartition(opts.key.Partition), - }, fmt.Sprintf("mesh-gateway:%s:%s", opts.key.String(), opts.upstreamID.String()), opts.notifyCh) + }, correlationId, opts.notifyCh) } diff --git a/agent/proxycfg/state_test.go b/agent/proxycfg/state_test.go index f8cf0834ce..e80ea4e63f 100644 --- a/agent/proxycfg/state_test.go +++ b/agent/proxycfg/state_test.go @@ -133,6 +133,7 @@ func recordWatches(sc *stateConfig) *watchRecorder { IntentionUpstreamsDestination: typedWatchRecorder[*structs.ServiceSpecificRequest]{wr}, InternalServiceDump: typedWatchRecorder[*structs.ServiceDumpRequest]{wr}, LeafCertificate: typedWatchRecorder[*cachetype.ConnectCALeafRequest]{wr}, + PeeringList: typedWatchRecorder[*cachetype.PeeringListRequest]{wr}, PeeredUpstreams: typedWatchRecorder[*structs.PartitionSpecificRequest]{wr}, PreparedQuery: typedWatchRecorder[*structs.PreparedQueryExecuteRequest]{wr}, ResolvedServiceConfig: typedWatchRecorder[*structs.ServiceConfigRequest]{wr}, @@ -241,6 +242,14 @@ func genVerifyTrustBundleListWatchForMeshGateway(partition string) verifyWatchRe } } +func genVerifyPeeringListWatchForMeshGateway() verifyWatchRequest { + return func(t testing.TB, request any) { + reqReal, ok := request.(*cachetype.PeeringListRequest) + require.True(t, ok) + require.Equal(t, structs.WildcardSpecifier, reqReal.Request.Partition) + } +} + func genVerifyResolverWatch(expectedService, expectedDatacenter, expectedKind string) verifyWatchRequest { return func(t testing.TB, request any) { reqReal, ok := request.(*structs.ConfigEntryQuery) @@ -779,6 +788,9 @@ func TestState_WatchesAndUpdates(t *testing.T) { Service: "mesh-gateway", Address: "10.0.1.1", Port: 443, + Meta: map[string]string{ + structs.MetaWANFederationKey: "1", + }, }, sourceDC: "dc1", stages: []verificationStage{ @@ -790,6 +802,7 @@ func TestState_WatchesAndUpdates(t *testing.T) { exportedServiceListWatchID: genVerifyDCSpecificWatch("dc1"), meshConfigEntryID: genVerifyMeshConfigWatch("dc1"), peeringTrustBundlesWatchID: genVerifyTrustBundleListWatchForMeshGateway(""), + consulServerListWatchID: genVerifyServiceSpecificPeeredRequest(structs.ConsulServiceName, "", "dc1", "", false), }, verifySnapshot: func(t testing.TB, snap *ConfigSnapshot) { require.False(t, snap.Valid(), "gateway without root is not valid") @@ -826,6 +839,9 @@ func TestState_WatchesAndUpdates(t *testing.T) { require.Empty(t, snap.MeshGateway.ServiceGroups) require.Empty(t, snap.MeshGateway.ServiceResolvers) require.Empty(t, snap.MeshGateway.GatewayGroups) + require.Empty(t, snap.MeshGateway.WatchedPeeringServices) + require.Empty(t, snap.MeshGateway.WatchedPeers) + require.Empty(t, snap.MeshGateway.PeeringServices) }, }, { @@ -893,8 +909,22 @@ func TestState_WatchesAndUpdates(t *testing.T) { }, { CorrelationID: peeringTrustBundlesWatchID, - Result: &pbpeering.TrustBundleListByServiceResponse{ - Bundles: nil, + Result: peerTrustBundles, + }, + { + CorrelationID: peeringServiceListWatchID + "peer-a", + Result: &structs.IndexedServiceList{ + Services: structs.ServiceList{ + {Name: "service-1"}, + }, + }, + }, + { + CorrelationID: peeringServiceListWatchID + "peer-b", + Result: &structs.IndexedServiceList{ + Services: structs.ServiceList{ + {Name: "service-10"}, + }, }, }, }, @@ -902,6 +932,10 @@ func TestState_WatchesAndUpdates(t *testing.T) { require.True(t, snap.Valid(), "gateway with service list is valid") require.Len(t, snap.MeshGateway.WatchedServices, 1) require.True(t, snap.MeshGateway.WatchedServicesSet) + require.Len(t, snap.MeshGateway.WatchedPeers, 2) + require.Len(t, snap.MeshGateway.WatchedPeeringServices, 2) + require.Len(t, snap.MeshGateway.WatchedPeeringServices["peer-a"], 1) + require.Len(t, snap.MeshGateway.WatchedPeeringServices["peer-b"], 1) }, }, { @@ -916,11 +950,33 @@ func TestState_WatchesAndUpdates(t *testing.T) { }, Err: nil, }, + { + CorrelationID: peeringServiceListWatchID + "peer-a", + Result: &structs.IndexedServiceList{ + Services: structs.ServiceList{ + {Name: "service-1"}, + {Name: "service-2"}, + {Name: "service-3"}, + }, + }, + Err: nil, + }, + { + CorrelationID: peeringServiceListWatchID + "peer-b", + Result: &structs.IndexedServiceList{ + Services: structs.ServiceList{}, + }, + Err: nil, + }, }, verifySnapshot: func(t testing.TB, snap *ConfigSnapshot) { require.True(t, snap.Valid(), "gateway with service list is valid") require.Len(t, snap.MeshGateway.WatchedServices, 2) require.True(t, snap.MeshGateway.WatchedServicesSet) + require.Len(t, snap.MeshGateway.WatchedPeers, 2) + require.Len(t, snap.MeshGateway.WatchedPeeringServices, 2) + require.Len(t, snap.MeshGateway.WatchedPeeringServices["peer-a"], 3) + require.Len(t, snap.MeshGateway.WatchedPeeringServices["peer-b"], 0) }, }, { @@ -1015,6 +1071,249 @@ func TestState_WatchesAndUpdates(t *testing.T) { }, }, }, + "mesh-gateway-peering-control-plane": { + ns: structs.NodeService{ + Kind: structs.ServiceKindMeshGateway, + ID: "mesh-gateway", + Service: "mesh-gateway", + Address: "10.0.1.1", + Port: 443, + }, + sourceDC: "dc1", + stages: []verificationStage{ + { + requiredWatches: map[string]verifyWatchRequest{ + datacentersWatchID: verifyDatacentersWatch, + serviceListWatchID: genVerifyDCSpecificWatch("dc1"), + rootsWatchID: genVerifyDCSpecificWatch("dc1"), + exportedServiceListWatchID: genVerifyDCSpecificWatch("dc1"), + meshConfigEntryID: genVerifyMeshConfigWatch("dc1"), + peeringTrustBundlesWatchID: genVerifyTrustBundleListWatchForMeshGateway(""), + }, + verifySnapshot: func(t testing.TB, snap *ConfigSnapshot) { + require.False(t, snap.Valid(), "gateway without root is not valid") + }, + }, + { + events: []UpdateEvent{ + rootWatchEvent(), + { + CorrelationID: meshConfigEntryID, + Result: &structs.ConfigEntryResponse{ + Entry: &structs.MeshConfigEntry{ + Peering: &structs.PeeringMeshConfig{ + PeerThroughMeshGateways: true, + }, + }, + }, + }, + { + CorrelationID: exportedServiceListWatchID, + Result: &structs.IndexedExportedServiceList{ + Services: nil, + }, + }, + { + CorrelationID: serviceListWatchID, + Result: &structs.IndexedServiceList{ + Services: structs.ServiceList{}, + }, + }, + { + CorrelationID: peeringTrustBundlesWatchID, + Result: &pbpeering.TrustBundleListByServiceResponse{ + Bundles: nil, + }, + }, + }, + verifySnapshot: func(t testing.TB, snap *ConfigSnapshot) { + require.Equal(t, indexedRoots, snap.Roots) + require.True(t, snap.MeshGateway.WatchedServicesSet) + require.True(t, snap.MeshGateway.PeeringTrustBundlesSet) + require.True(t, snap.MeshGateway.MeshConfigSet) + + require.True(t, snap.Valid(), "gateway without services is valid") + require.True(t, snap.ConnectProxy.isEmpty()) + }, + }, + { + requiredWatches: map[string]verifyWatchRequest{ + consulServerListWatchID: genVerifyServiceSpecificPeeredRequest(structs.ConsulServiceName, "", "dc1", "", false), + peerServersWatchID: genVerifyPeeringListWatchForMeshGateway(), + }, + events: []UpdateEvent{ + { + CorrelationID: consulServerListWatchID, + Result: &structs.IndexedCheckServiceNodes{ + Nodes: structs.CheckServiceNodes{ + { + Node: &structs.Node{ + Datacenter: "dc1", + Node: "node1", + Address: "127.0.0.1", + }, + Service: &structs.NodeService{ + ID: structs.ConsulServiceID, + Service: structs.ConsulServiceName, + }, + }, + { + Node: &structs.Node{ + Datacenter: "dc1", + Node: "replica1", + Address: "127.0.0.1", + }, + Service: &structs.NodeService{ + ID: structs.ConsulServiceID, + Service: structs.ConsulServiceName, + Meta: map[string]string{"read_replica": "true"}, + }, + }, + }, + }, + Err: nil, + }, + }, + verifySnapshot: func(t testing.TB, snap *ConfigSnapshot) { + require.True(t, snap.Valid()) + require.NotNil(t, snap.MeshGateway.PeerServersWatchCancel) + + servers, ok := snap.MeshGateway.WatchedLocalServers.Get(structs.ConsulServiceName) + require.True(t, ok) + + expect := structs.CheckServiceNodes{ + { + Node: &structs.Node{ + Datacenter: "dc1", + Node: "node1", + Address: "127.0.0.1", + }, + Service: &structs.NodeService{ + ID: structs.ConsulServiceID, + Service: structs.ConsulServiceName, + }, + }, + { + Node: &structs.Node{ + Datacenter: "dc1", + Node: "replica1", + Address: "127.0.0.1", + }, + Service: &structs.NodeService{ + ID: structs.ConsulServiceID, + Service: structs.ConsulServiceName, + Meta: map[string]string{"read_replica": "true"}, + }, + }, + } + require.Equal(t, expect, servers) + }, + }, + { + requiredWatches: map[string]verifyWatchRequest{ + consulServerListWatchID: genVerifyServiceSpecificPeeredRequest(structs.ConsulServiceName, "", "dc1", "", false), + peerServersWatchID: genVerifyPeeringListWatchForMeshGateway(), + }, + events: []UpdateEvent{ + { + CorrelationID: peerServersWatchID, + Result: &pbpeering.PeeringListResponse{ + Peerings: []*pbpeering.Peering{ + { + Name: "peer-bar", + PeerServerName: "server.bar.peering.bar-domain", + PeerServerAddresses: []string{"1.2.3.4:8443", "2.3.4.5:8443"}, + ModifyIndex: 30, + }, + { + Name: "peer-broken", + PeerServerName: "server.broken.peering.broken-domain", + PeerServerAddresses: []string{}, + ModifyIndex: 45, + }, + { + Name: "peer-foo-zap", + PeerServerName: "server.foo.peering.foo-domain", + PeerServerAddresses: []string{"elb.now-aws.com:8443", "1.2.3.4:8443"}, + ModifyIndex: 20, + }, + { + Name: "peer-foo-zip", + PeerServerName: "server.foo.peering.foo-domain", + PeerServerAddresses: []string{"1.2.3.4:8443", "2.3.4.5:8443"}, + ModifyIndex: 12, + }, + }, + }, + Err: nil, + }, + }, + verifySnapshot: func(t testing.TB, snap *ConfigSnapshot) { + require.True(t, snap.Valid()) + require.NotNil(t, snap.MeshGateway.PeerServersWatchCancel) + + expect := map[string]PeerServersValue{ + "server.foo.peering.foo-domain": { + Addresses: []structs.ServiceAddress{{Address: "elb.now-aws.com", Port: 8443}}, + UseCDS: true, + Index: 20, + }, + "server.bar.peering.bar-domain": { + Addresses: []structs.ServiceAddress{{Address: "1.2.3.4", Port: 8443}, {Address: "2.3.4.5", Port: 8443}}, + UseCDS: false, + Index: 30, + }, + } + require.Equal(t, expect, snap.MeshGateway.PeerServers) + }, + }, + { + events: []UpdateEvent{ + { + CorrelationID: meshConfigEntryID, + Result: &structs.ConfigEntryResponse{ + Entry: &structs.MeshConfigEntry{ + Peering: &structs.PeeringMeshConfig{ + PeerThroughMeshGateways: false, + }, + }, + }, + }, + }, + verifySnapshot: func(t testing.TB, snap *ConfigSnapshot) { + require.True(t, snap.Valid()) + require.NotNil(t, snap.MeshConfig()) + + require.Nil(t, snap.MeshGateway.PeerServersWatchCancel) + require.Empty(t, snap.MeshGateway.PeerServers) + + require.False(t, snap.MeshGateway.WatchedLocalServers.IsWatched(structs.ConsulServiceName)) + servers, ok := snap.MeshGateway.WatchedLocalServers.Get(structs.ConsulServiceName) + require.False(t, ok) + require.Empty(t, servers) + }, + }, + { + events: []UpdateEvent{ + { + CorrelationID: meshConfigEntryID, + Result: &structs.ConfigEntryResponse{ + Entry: nil, + }, + }, + }, + verifySnapshot: func(t testing.TB, snap *ConfigSnapshot) { + require.True(t, snap.Valid()) + require.Nil(t, snap.MeshConfig()) + + require.False(t, snap.MeshGateway.WatchedLocalServers.IsWatched(structs.ConsulServiceName)) + servers, ok := snap.MeshGateway.WatchedLocalServers.Get(structs.ConsulServiceName) + require.False(t, ok) + require.Empty(t, servers) + }, + }, + }, + }, "ingress-gateway": { ns: structs.NodeService{ Kind: structs.ServiceKindIngressGateway, diff --git a/agent/proxycfg/testing.go b/agent/proxycfg/testing.go index d5a3d82244..00ca99ab79 100644 --- a/agent/proxycfg/testing.go +++ b/agent/proxycfg/testing.go @@ -280,31 +280,6 @@ func TestUpstreamNodesDC2(t testing.T) structs.CheckServiceNodes { } } -func TestUpstreamNodesPeerCluster01(t testing.T) structs.CheckServiceNodes { - peer := "cluster-01" - service := structs.TestNodeServiceWithNameInPeer(t, "web", peer) - return structs.CheckServiceNodes{ - structs.CheckServiceNode{ - Node: &structs.Node{ - ID: "test1", - Node: "test1", - Address: "10.40.1.1", - PeerName: peer, - }, - Service: service, - }, - structs.CheckServiceNode{ - Node: &structs.Node{ - ID: "test2", - Node: "test2", - Address: "10.40.1.2", - PeerName: peer, - }, - Service: service, - }, - } -} - func TestUpstreamNodesInStatusDC2(t testing.T, status string) structs.CheckServiceNodes { return structs.CheckServiceNodes{ structs.CheckServiceNode{ @@ -772,6 +747,7 @@ func testConfigSnapshotFixture( IntentionUpstreamsDestination: &noopDataSource[*structs.ServiceSpecificRequest]{}, InternalServiceDump: &noopDataSource[*structs.ServiceDumpRequest]{}, LeafCertificate: &noopDataSource[*cachetype.ConnectCALeafRequest]{}, + PeeringList: &noopDataSource[*cachetype.PeeringListRequest]{}, PeeredUpstreams: &noopDataSource[*structs.PartitionSpecificRequest]{}, PreparedQuery: &noopDataSource[*structs.PreparedQueryExecuteRequest]{}, ResolvedServiceConfig: &noopDataSource[*structs.ServiceConfigRequest]{}, @@ -976,6 +952,7 @@ func NewTestDataSources() *TestDataSources { IntentionUpstreamsDestination: NewTestDataSource[*structs.ServiceSpecificRequest, *structs.IndexedServiceList](), InternalServiceDump: NewTestDataSource[*structs.ServiceDumpRequest, *structs.IndexedCheckServiceNodes](), LeafCertificate: NewTestDataSource[*cachetype.ConnectCALeafRequest, *structs.IssuedCert](), + PeeringList: NewTestDataSource[*cachetype.PeeringListRequest, *pbpeering.PeeringListResponse](), PreparedQuery: NewTestDataSource[*structs.PreparedQueryExecuteRequest, *structs.PreparedQueryExecuteResponse](), ResolvedServiceConfig: NewTestDataSource[*structs.ServiceConfigRequest, *structs.ServiceConfigResponse](), ServiceList: NewTestDataSource[*structs.DCSpecificRequest, *structs.IndexedServiceList](), @@ -1002,6 +979,7 @@ type TestDataSources struct { IntentionUpstreamsDestination *TestDataSource[*structs.ServiceSpecificRequest, *structs.IndexedServiceList] InternalServiceDump *TestDataSource[*structs.ServiceDumpRequest, *structs.IndexedCheckServiceNodes] LeafCertificate *TestDataSource[*cachetype.ConnectCALeafRequest, *structs.IssuedCert] + PeeringList *TestDataSource[*cachetype.PeeringListRequest, *pbpeering.PeeringListResponse] PeeredUpstreams *TestDataSource[*structs.PartitionSpecificRequest, *structs.IndexedPeeredServiceList] PreparedQuery *TestDataSource[*structs.PreparedQueryExecuteRequest, *structs.PreparedQueryExecuteResponse] ResolvedServiceConfig *TestDataSource[*structs.ServiceConfigRequest, *structs.ServiceConfigResponse] @@ -1028,6 +1006,7 @@ func (t *TestDataSources) ToDataSources() DataSources { IntentionUpstreamsDestination: t.IntentionUpstreamsDestination, InternalServiceDump: t.InternalServiceDump, LeafCertificate: t.LeafCertificate, + PeeringList: t.PeeringList, PeeredUpstreams: t.PeeredUpstreams, PreparedQuery: t.PreparedQuery, ResolvedServiceConfig: t.ResolvedServiceConfig, diff --git a/agent/proxycfg/testing_ingress_gateway.go b/agent/proxycfg/testing_ingress_gateway.go index 6846bb8a31..bf471051af 100644 --- a/agent/proxycfg/testing_ingress_gateway.go +++ b/agent/proxycfg/testing_ingress_gateway.go @@ -1485,6 +1485,326 @@ func TestConfigSnapshotIngressGateway_SingleTLSListener(t testing.T) *ConfigSnap }) } +func TestConfigSnapshotIngressGateway_SingleTLSListener_GRPC(t testing.T) *ConfigSnapshot { + var ( + s1 = structs.NewServiceName("s1", nil) + s1UID = NewUpstreamIDFromServiceName(s1) + s1Chain = discoverychain.TestCompileConfigEntries(t, "s1", "default", "default", "dc1", connect.TestClusterID+".consul", nil) + + s2 = structs.NewServiceName("s2", nil) + s2UID = NewUpstreamIDFromServiceName(s2) + s2Chain = discoverychain.TestCompileConfigEntries(t, "s2", "default", "default", "dc1", connect.TestClusterID+".consul", nil) + ) + return TestConfigSnapshotIngressGateway(t, true, "grpc", "simple", nil, + func(entry *structs.IngressGatewayConfigEntry) { + entry.Listeners = []structs.IngressListener{ + { + Port: 8080, + Protocol: "grpc", + Services: []structs.IngressService{ + {Name: "s1"}, + }, + }, + { + Port: 8081, + Protocol: "grpc", + Services: []structs.IngressService{ + {Name: "s2"}, + }, + TLS: &structs.GatewayTLSConfig{ + Enabled: true, + TLSMinVersion: types.TLSv1_2, + }, + }, + } + }, []UpdateEvent{ + { + CorrelationID: gatewayServicesWatchID, + Result: &structs.IndexedGatewayServices{ + // One listener should inherit non-TLS gateway config, another + // listener configures TLS with an explicit minimum version + Services: []*structs.GatewayService{ + { + Service: s1, + Port: 8080, + Protocol: "grpc", + }, + { + Service: s2, + Port: 8081, + Protocol: "grpc", + }, + }, + }, + }, + { + CorrelationID: "discovery-chain:" + s1UID.String(), + Result: &structs.DiscoveryChainResponse{ + Chain: s1Chain, + }, + }, + { + CorrelationID: "discovery-chain:" + s2UID.String(), + Result: &structs.DiscoveryChainResponse{ + Chain: s2Chain, + }, + }, + { + CorrelationID: "upstream-target:" + s1Chain.ID() + ":" + s1UID.String(), + Result: &structs.IndexedCheckServiceNodes{ + Nodes: TestUpstreamNodes(t, "s1"), + }, + }, + { + CorrelationID: "upstream-target:" + s2Chain.ID() + ":" + s2UID.String(), + Result: &structs.IndexedCheckServiceNodes{ + Nodes: TestUpstreamNodes(t, "s2"), + }, + }, + }) +} + +func TestConfigSnapshotIngressGateway_SingleTLSListener_HTTP2(t testing.T) *ConfigSnapshot { + var ( + s1 = structs.NewServiceName("s1", nil) + s1UID = NewUpstreamIDFromServiceName(s1) + s1Chain = discoverychain.TestCompileConfigEntries(t, "s1", "default", "default", "dc1", connect.TestClusterID+".consul", nil) + + s2 = structs.NewServiceName("s2", nil) + s2UID = NewUpstreamIDFromServiceName(s2) + s2Chain = discoverychain.TestCompileConfigEntries(t, "s2", "default", "default", "dc1", connect.TestClusterID+".consul", nil) + ) + return TestConfigSnapshotIngressGateway(t, true, "http2", "simple", nil, + func(entry *structs.IngressGatewayConfigEntry) { + entry.Listeners = []structs.IngressListener{ + { + Port: 8080, + Protocol: "http2", + Services: []structs.IngressService{ + {Name: "s1"}, + }, + }, + { + Port: 8081, + Protocol: "http2", + Services: []structs.IngressService{ + {Name: "s2"}, + }, + TLS: &structs.GatewayTLSConfig{ + Enabled: true, + TLSMinVersion: types.TLSv1_2, + }, + }, + } + }, []UpdateEvent{ + { + CorrelationID: gatewayServicesWatchID, + Result: &structs.IndexedGatewayServices{ + // One listener should inherit non-TLS gateway config, another + // listener configures TLS with an explicit minimum version + Services: []*structs.GatewayService{ + { + Service: s1, + Port: 8080, + Protocol: "http2", + }, + { + Service: s2, + Port: 8081, + Protocol: "http2", + }, + }, + }, + }, + { + CorrelationID: "discovery-chain:" + s1UID.String(), + Result: &structs.DiscoveryChainResponse{ + Chain: s1Chain, + }, + }, + { + CorrelationID: "discovery-chain:" + s2UID.String(), + Result: &structs.DiscoveryChainResponse{ + Chain: s2Chain, + }, + }, + { + CorrelationID: "upstream-target:" + s1Chain.ID() + ":" + s1UID.String(), + Result: &structs.IndexedCheckServiceNodes{ + Nodes: TestUpstreamNodes(t, "s1"), + }, + }, + { + CorrelationID: "upstream-target:" + s2Chain.ID() + ":" + s2UID.String(), + Result: &structs.IndexedCheckServiceNodes{ + Nodes: TestUpstreamNodes(t, "s2"), + }, + }, + }) +} + +func TestConfigSnapshotIngressGateway_MultiTLSListener_MixedHTTP2gRPC(t testing.T) *ConfigSnapshot { + var ( + s1 = structs.NewServiceName("s1", nil) + s1UID = NewUpstreamIDFromServiceName(s1) + s1Chain = discoverychain.TestCompileConfigEntries(t, "s1", "default", "default", "dc1", connect.TestClusterID+".consul", nil) + + s2 = structs.NewServiceName("s2", nil) + s2UID = NewUpstreamIDFromServiceName(s2) + s2Chain = discoverychain.TestCompileConfigEntries(t, "s2", "default", "default", "dc1", connect.TestClusterID+".consul", nil) + ) + return TestConfigSnapshotIngressGateway(t, true, "tcp", "simple", nil, + func(entry *structs.IngressGatewayConfigEntry) { + entry.Listeners = []structs.IngressListener{ + { + Port: 8080, + Protocol: "grpc", + Services: []structs.IngressService{ + {Name: "s1"}, + }, + TLS: &structs.GatewayTLSConfig{ + Enabled: true, + TLSMinVersion: types.TLSv1_2, + }, + }, + { + Port: 8081, + Protocol: "http2", + Services: []structs.IngressService{ + {Name: "s2"}, + }, + TLS: &structs.GatewayTLSConfig{ + Enabled: true, + TLSMinVersion: types.TLSv1_2, + }, + }, + } + }, []UpdateEvent{ + { + CorrelationID: gatewayServicesWatchID, + Result: &structs.IndexedGatewayServices{ + // One listener should inherit non-TLS gateway config, another + // listener configures TLS with an explicit minimum version + Services: []*structs.GatewayService{ + { + Service: s1, + Port: 8080, + Protocol: "grpc", + }, + { + Service: s2, + Port: 8081, + Protocol: "http2", + }, + }, + }, + }, + { + CorrelationID: "discovery-chain:" + s1UID.String(), + Result: &structs.DiscoveryChainResponse{ + Chain: s1Chain, + }, + }, + { + CorrelationID: "discovery-chain:" + s2UID.String(), + Result: &structs.DiscoveryChainResponse{ + Chain: s2Chain, + }, + }, + { + CorrelationID: "upstream-target:" + s1Chain.ID() + ":" + s1UID.String(), + Result: &structs.IndexedCheckServiceNodes{ + Nodes: TestUpstreamNodes(t, "s1"), + }, + }, + { + CorrelationID: "upstream-target:" + s2Chain.ID() + ":" + s2UID.String(), + Result: &structs.IndexedCheckServiceNodes{ + Nodes: TestUpstreamNodes(t, "s2"), + }, + }, + }) +} + +func TestConfigSnapshotIngressGateway_GWTLSListener_MixedHTTP2gRPC(t testing.T) *ConfigSnapshot { + var ( + s1 = structs.NewServiceName("s1", nil) + s1UID = NewUpstreamIDFromServiceName(s1) + s1Chain = discoverychain.TestCompileConfigEntries(t, "s1", "default", "default", "dc1", connect.TestClusterID+".consul", nil) + + s2 = structs.NewServiceName("s2", nil) + s2UID = NewUpstreamIDFromServiceName(s2) + s2Chain = discoverychain.TestCompileConfigEntries(t, "s2", "default", "default", "dc1", connect.TestClusterID+".consul", nil) + ) + return TestConfigSnapshotIngressGateway(t, true, "tcp", "simple", nil, + func(entry *structs.IngressGatewayConfigEntry) { + entry.TLS = structs.GatewayTLSConfig{ + Enabled: true, + TLSMinVersion: types.TLSv1_2, + } + entry.Listeners = []structs.IngressListener{ + { + Port: 8080, + Protocol: "grpc", + Services: []structs.IngressService{ + {Name: "s1"}, + }, + }, + { + Port: 8081, + Protocol: "http2", + Services: []structs.IngressService{ + {Name: "s2"}, + }, + }, + } + }, []UpdateEvent{ + { + CorrelationID: gatewayServicesWatchID, + Result: &structs.IndexedGatewayServices{ + // One listener should inherit non-TLS gateway config, another + // listener configures TLS with an explicit minimum version + Services: []*structs.GatewayService{ + { + Service: s1, + Port: 8080, + Protocol: "grpc", + }, + { + Service: s2, + Port: 8081, + Protocol: "http2", + }, + }, + }, + }, + { + CorrelationID: "discovery-chain:" + s1UID.String(), + Result: &structs.DiscoveryChainResponse{ + Chain: s1Chain, + }, + }, + { + CorrelationID: "discovery-chain:" + s2UID.String(), + Result: &structs.DiscoveryChainResponse{ + Chain: s2Chain, + }, + }, + { + CorrelationID: "upstream-target:" + s1Chain.ID() + ":" + s1UID.String(), + Result: &structs.IndexedCheckServiceNodes{ + Nodes: TestUpstreamNodes(t, "s1"), + }, + }, + { + CorrelationID: "upstream-target:" + s2Chain.ID() + ":" + s2UID.String(), + Result: &structs.IndexedCheckServiceNodes{ + Nodes: TestUpstreamNodes(t, "s2"), + }, + }, + }) +} + func TestConfigSnapshotIngressGateway_TLSMixedMinVersionListeners(t testing.T) *ConfigSnapshot { var ( s1 = structs.NewServiceName("s1", nil) diff --git a/agent/proxycfg/testing_mesh_gateway.go b/agent/proxycfg/testing_mesh_gateway.go index f8b6116a26..039807ed61 100644 --- a/agent/proxycfg/testing_mesh_gateway.go +++ b/agent/proxycfg/testing_mesh_gateway.go @@ -476,6 +476,106 @@ func TestConfigSnapshotPeeredMeshGateway(t testing.T, variant string, nsFn func( ) switch variant { + case "control-plane": + extraUpdates = append(extraUpdates, + UpdateEvent{ + CorrelationID: meshConfigEntryID, + Result: &structs.ConfigEntryResponse{ + Entry: &structs.MeshConfigEntry{ + Peering: &structs.PeeringMeshConfig{ + PeerThroughMeshGateways: true, + }, + }, + }, + }, + UpdateEvent{ + CorrelationID: consulServerListWatchID, + Result: &structs.IndexedCheckServiceNodes{ + Nodes: structs.CheckServiceNodes{ + { + Node: &structs.Node{ + Datacenter: "dc1", + Node: "replica", + Address: "127.0.0.10", + }, + Service: &structs.NodeService{ + ID: structs.ConsulServiceID, + Service: structs.ConsulServiceName, + // Read replicas cannot handle peering requests. + Meta: map[string]string{"read_replica": "true"}, + }, + }, + { + Node: &structs.Node{ + Datacenter: "dc1", + Node: "node1", + Address: "127.0.0.1", + }, + Service: &structs.NodeService{ + ID: structs.ConsulServiceID, + Service: structs.ConsulServiceName, + Meta: map[string]string{ + "grpc_port": "8502", + "grpc_tls_port": "8503", + }, + }, + }, + { + Node: &structs.Node{ + Datacenter: "dc1", + Node: "node2", + Address: "127.0.0.2", + }, + Service: &structs.NodeService{ + ID: structs.ConsulServiceID, + Service: structs.ConsulServiceName, + Meta: map[string]string{ + "grpc_port": "8502", + "grpc_tls_port": "8503", + }, + TaggedAddresses: map[string]structs.ServiceAddress{ + // WAN address is not considered for traffic from local gateway to local servers. + structs.TaggedAddressWAN: { + Address: "consul.server.dc1.my-domain", + Port: 10101, + }, + }, + }, + }, + { + Node: &structs.Node{ + Datacenter: "dc1", + Node: "node3", + Address: "127.0.0.3", + }, + Service: &structs.NodeService{ + ID: structs.ConsulServiceID, + Service: structs.ConsulServiceName, + Meta: map[string]string{ + // Peering is not allowed over deprecated non-TLS gRPC port. + "grpc_port": "8502", + }, + }, + }, + { + Node: &structs.Node{ + Datacenter: "dc1", + Node: "node4", + Address: "127.0.0.4", + }, + Service: &structs.NodeService{ + ID: structs.ConsulServiceID, + Service: structs.ConsulServiceName, + Meta: map[string]string{ + // Must have valid gRPC port. + "grpc_tls_port": "bad", + }, + }, + }, + }, + }, + }, + ) case "default-services-http": proxyDefaults := &structs.ProxyConfigEntry{ Config: map[string]interface{}{ @@ -532,6 +632,48 @@ func TestConfigSnapshotPeeredMeshGateway(t testing.T, variant string, nsFn func( }, }, ) + case "imported-services": + peerTrustBundles := TestPeerTrustBundles(t).Bundles + dbSN := structs.NewServiceName("db", nil) + altSN := structs.NewServiceName("alt", nil) + extraUpdates = append(extraUpdates, + UpdateEvent{ + CorrelationID: peeringTrustBundlesWatchID, + Result: &pbpeering.TrustBundleListByServiceResponse{ + Bundles: peerTrustBundles, + }, + }, + UpdateEvent{ + CorrelationID: peeringServiceListWatchID + "peer-a", + Result: &structs.IndexedServiceList{ + Services: []structs.ServiceName{altSN}, + }, + }, + UpdateEvent{ + CorrelationID: peeringServiceListWatchID + "peer-b", + Result: &structs.IndexedServiceList{ + Services: []structs.ServiceName{dbSN}, + }, + }, + UpdateEvent{ + CorrelationID: "peering-connect-service:peer-a:db", + Result: &structs.IndexedCheckServiceNodes{ + Nodes: structs.CheckServiceNodes{ + structs.TestCheckNodeServiceWithNameInPeer(t, "db", "peer-a", "10.40.1.1", false), + structs.TestCheckNodeServiceWithNameInPeer(t, "db", "peer-a", "10.40.1.2", false), + }, + }, + }, + UpdateEvent{ + CorrelationID: "peering-connect-service:peer-b:alt", + Result: &structs.IndexedCheckServiceNodes{ + Nodes: structs.CheckServiceNodes{ + structs.TestCheckNodeServiceWithNameInPeer(t, "alt", "peer-b", "10.40.2.1", false), + structs.TestCheckNodeServiceWithNameInPeer(t, "alt", "peer-b", "10.40.2.2", true), + }, + }, + }, + ) case "chain-and-l7-stuff": entries = []structs.ConfigEntry{ &structs.ProxyConfigEntry{ @@ -631,6 +773,78 @@ func TestConfigSnapshotPeeredMeshGateway(t testing.T, variant string, nsFn func( }, }, ) + case "peer-through-mesh-gateway": + + extraUpdates = append(extraUpdates, + UpdateEvent{ + CorrelationID: meshConfigEntryID, + Result: &structs.ConfigEntryResponse{ + Entry: &structs.MeshConfigEntry{ + Peering: &structs.PeeringMeshConfig{ + PeerThroughMeshGateways: true, + }, + }, + }, + }, + // We add extra entries that should not necessitate any + // xDS changes in Envoy, plus one hostname and one + UpdateEvent{ + CorrelationID: peerServersWatchID, + Result: &pbpeering.PeeringListResponse{ + Peerings: []*pbpeering.Peering{ + // Not active + { + Name: "peer-a", + PeerServerName: connect.PeeringServerSAN("dc2", "f3f41279-001d-42bb-912e-f6103fb036b8"), + PeerServerAddresses: []string{ + "1.2.3.4:5200", + }, + State: pbpeering.PeeringState_TERMINATED, + ModifyIndex: 2, + }, + // No server addresses, so this should only be accepting connections + { + Name: "peer-b", + PeerServerName: connect.PeeringServerSAN("dc2", "0a3f8926-fda9-4274-b6f6-99ee1a43cbda"), + PeerServerAddresses: []string{}, + State: pbpeering.PeeringState_ESTABLISHING, + ModifyIndex: 3, + }, + // This should override the peer-c entry since it has a higher index, even though it is processed earlier. + { + Name: "peer-c-prime", + PeerServerName: connect.PeeringServerSAN("dc2", "6d942ff2-6a78-46f4-a52f-915e26c48797"), + PeerServerAddresses: []string{ + "9.10.11.12:5200", + "13.14.15.16:5200", + }, + State: pbpeering.PeeringState_ESTABLISHING, + ModifyIndex: 20, + }, + // Uses an ip as the address + { + Name: "peer-c", + PeerServerName: connect.PeeringServerSAN("dc2", "6d942ff2-6a78-46f4-a52f-915e26c48797"), + PeerServerAddresses: []string{ + "5.6.7.8:5200", + }, + State: pbpeering.PeeringState_ESTABLISHING, + ModifyIndex: 10, + }, + // Uses a hostname as the address + { + Name: "peer-d", + PeerServerName: connect.PeeringServerSAN("dc3", "f622dc37-7238-4485-ab58-0f53864a9ae5"), + PeerServerAddresses: []string{ + "my-load-balancer-1234567890abcdef.elb.us-east-2.amazonaws.com:8080", + }, + State: pbpeering.PeeringState_ESTABLISHING, + ModifyIndex: 4, + }, + }, + }, + }, + ) default: t.Fatalf("unknown variant: %s", variant) diff --git a/agent/proxycfg/testing_peering.go b/agent/proxycfg/testing_peering.go index 0f20ad6ca6..5efae59207 100644 --- a/agent/proxycfg/testing_peering.go +++ b/agent/proxycfg/testing_peering.go @@ -249,3 +249,131 @@ func TestConfigSnapshotPeeringTProxy(t testing.T) *ConfigSnapshot { }, }) } + +func TestConfigSnapshotPeeringLocalMeshGateway(t testing.T) *ConfigSnapshot { + var ( + paymentsUpstream = structs.Upstream{ + DestinationName: "payments", + DestinationPeer: "cloud", + LocalBindPort: 9090, + MeshGateway: structs.MeshGatewayConfig{Mode: structs.MeshGatewayModeLocal}, + } + paymentsUID = NewUpstreamID(&paymentsUpstream) + + refundsUpstream = structs.Upstream{ + DestinationName: "refunds", + DestinationPeer: "cloud", + LocalBindPort: 9090, + MeshGateway: structs.MeshGatewayConfig{Mode: structs.MeshGatewayModeLocal}, + } + refundsUID = NewUpstreamID(&refundsUpstream) + ) + + const peerTrustDomain = "1c053652-8512-4373-90cf-5a7f6263a994.consul" + + return TestConfigSnapshot(t, func(ns *structs.NodeService) { + ns.Proxy.Upstreams = structs.Upstreams{ + paymentsUpstream, + refundsUpstream, + } + }, []UpdateEvent{ + { + CorrelationID: peerTrustBundleIDPrefix + "cloud", + Result: &pbpeering.TrustBundleReadResponse{ + Bundle: TestPeerTrustBundles(t).Bundles[0], + }, + }, + { + CorrelationID: upstreamPeerWatchIDPrefix + paymentsUID.String(), + Result: &structs.IndexedCheckServiceNodes{ + Nodes: []structs.CheckServiceNode{ + { + Node: &structs.Node{ + Address: "85.252.102.31", + Datacenter: "cloud-dc", + }, + Service: &structs.NodeService{ + Service: "payments-sidecar-proxy", + Kind: structs.ServiceKindConnectProxy, + Port: 443, + TaggedAddresses: map[string]structs.ServiceAddress{ + structs.TaggedAddressLAN: { + Address: "85.252.102.31", + Port: 443, + }, + structs.TaggedAddressWAN: { + Address: "123.us-east-1.elb.notaws.com", + Port: 8443, + }, + }, + Connect: structs.ServiceConnect{ + PeerMeta: &structs.PeeringServiceMeta{ + SNI: []string{ + "payments.default.default.cloud.external." + peerTrustDomain, + }, + SpiffeID: []string{ + "spiffe://" + peerTrustDomain + "/ns/default/dc/cloud-dc/svc/payments", + }, + Protocol: "tcp", + }, + }, + }, + }, + }, + }, + }, + { + CorrelationID: upstreamPeerWatchIDPrefix + refundsUID.String(), + Result: &structs.IndexedCheckServiceNodes{ + Nodes: []structs.CheckServiceNode{ + { + Node: &structs.Node{ + Address: "106.96.90.233", + Datacenter: "cloud-dc", + }, + Service: &structs.NodeService{ + Service: "refunds-sidecar-proxy", + Kind: structs.ServiceKindConnectProxy, + Port: 443, + Connect: structs.ServiceConnect{ + PeerMeta: &structs.PeeringServiceMeta{ + SNI: []string{ + "refunds.default.default.cloud.external." + peerTrustDomain, + }, + SpiffeID: []string{ + "spiffe://" + peerTrustDomain + "/ns/default/dc/cloud-dc/svc/refunds", + }, + Protocol: "tcp", + }, + }, + }, + }, + }, + }, + }, + { + CorrelationID: "mesh-gateway:dc1", + Result: &structs.IndexedCheckServiceNodes{ + Nodes: structs.CheckServiceNodes{ + structs.CheckServiceNode{ + Node: &structs.Node{ + ID: "mesh-gateway", + Node: "mesh-gateway", + Address: "10.0.0.1", + Datacenter: "dc1", + }, + Service: &structs.NodeService{ + Kind: structs.ServiceKindMeshGateway, + Service: "mesh-gateway", + Port: 1234, + TaggedAddresses: map[string]structs.ServiceAddress{ + structs.TaggedAddressWAN: {Address: "172.100.0.14", Port: 8080}, + }, + EnterpriseMeta: *structs.DefaultEnterpriseMetaInDefaultPartition(), + }, + }, + }, + }, + }, + }) +} diff --git a/agent/proxycfg/testing_tproxy.go b/agent/proxycfg/testing_tproxy.go index 2ee3f88256..7aa8321eea 100644 --- a/agent/proxycfg/testing_tproxy.go +++ b/agent/proxycfg/testing_tproxy.go @@ -1,9 +1,10 @@ package proxycfg import ( - "github.com/hashicorp/consul/api" "time" + "github.com/hashicorp/consul/api" + "github.com/mitchellh/go-testing-interface" "github.com/hashicorp/consul/agent/connect" @@ -436,6 +437,75 @@ func TestConfigSnapshotTransparentProxyDialDirectly(t testing.T) *ConfigSnapshot }) } +func TestConfigSnapshotTransparentProxyResolverRedirectUpstream(t testing.T) *ConfigSnapshot { + // Service-Resolver redirect with explicit upstream should spawn an outbound listener. + var ( + db = structs.NewServiceName("db-redir", nil) + dbUID = NewUpstreamIDFromServiceName(db) + dbChain = discoverychain.TestCompileConfigEntries(t, "db-redir", "default", "default", "dc1", connect.TestClusterID+".consul", nil, + &structs.ServiceResolverConfigEntry{ + Kind: structs.ServiceResolver, + Name: "db-redir", + Redirect: &structs.ServiceResolverRedirect{ + Service: "db", + }, + }, + ) + + google = structs.NewServiceName("google", nil) + googleUID = NewUpstreamIDFromServiceName(google) + googleChain = discoverychain.TestCompileConfigEntries(t, "google", "default", "default", "dc1", connect.TestClusterID+".consul", nil) + ) + + return TestConfigSnapshot(t, func(ns *structs.NodeService) { + ns.Proxy.Mode = structs.ProxyModeTransparent + ns.Proxy.Upstreams[0].DestinationName = "db-redir" + }, []UpdateEvent{ + { + CorrelationID: "discovery-chain:" + dbUID.String(), + Result: &structs.DiscoveryChainResponse{ + Chain: dbChain, + }, + }, + { + CorrelationID: intentionUpstreamsID, + Result: &structs.IndexedServiceList{ + Services: structs.ServiceList{ + google, + }, + }, + }, + { + CorrelationID: "discovery-chain:" + googleUID.String(), + Result: &structs.DiscoveryChainResponse{ + Chain: googleChain, + }, + }, + { + CorrelationID: "upstream-target:google.default.default.dc1:" + googleUID.String(), + Result: &structs.IndexedCheckServiceNodes{ + Nodes: []structs.CheckServiceNode{ + { + Node: &structs.Node{ + Address: "8.8.8.8", + Datacenter: "dc1", + }, + Service: &structs.NodeService{ + Service: "google", + Address: "9.9.9.9", + Port: 9090, + TaggedAddresses: map[string]structs.ServiceAddress{ + "virtual": {Address: "10.0.0.1"}, + structs.TaggedAddressVirtualIP: {Address: "240.0.0.1"}, + }, + }, + }, + }, + }, + }, + }) +} + func TestConfigSnapshotTransparentProxyTerminatingGatewayCatalogDestinationsOnly(t testing.T) *ConfigSnapshot { // DiscoveryChain without an UpstreamConfig should yield a // filter chain when in transparent proxy mode diff --git a/agent/proxycfg/testing_upstreams.go b/agent/proxycfg/testing_upstreams.go index afb310c754..bbf541f404 100644 --- a/agent/proxycfg/testing_upstreams.go +++ b/agent/proxycfg/testing_upstreams.go @@ -88,7 +88,7 @@ func setupTestVariationConfigEntriesAndSnapshot( events = append(events, UpdateEvent{ CorrelationID: "upstream-peer:db?peer=cluster-01", Result: &structs.IndexedCheckServiceNodes{ - Nodes: TestUpstreamNodesPeerCluster01(t), + Nodes: structs.CheckServiceNodes{structs.TestCheckNodeServiceWithNameInPeer(t, "db", "cluster-01", "10.40.1.1", false)}, }, }) case "redirect-to-cluster-peer": @@ -106,7 +106,7 @@ func setupTestVariationConfigEntriesAndSnapshot( events = append(events, UpdateEvent{ CorrelationID: "upstream-peer:db?peer=cluster-01", Result: &structs.IndexedCheckServiceNodes{ - Nodes: TestUpstreamNodesPeerCluster01(t), + Nodes: structs.CheckServiceNodes{structs.TestCheckNodeServiceWithNameInPeer(t, "db", "cluster-01", "10.40.1.1", false)}, }, }) case "failover-through-double-remote-gateway-triggered": @@ -692,6 +692,16 @@ func setupTestVariationDiscoveryChain( RetryOnConnectFailure: true, }, }, + { + Match: httpMatch(&structs.ServiceRouteHTTPMatch{ + PathPrefix: "/retry-reset", + }), + Destination: &structs.ServiceRouteDestination{ + Service: "retry-reset", + NumRetries: 15, + RetryOn: []string{"reset"}, + }, + }, { Match: httpMatch(&structs.ServiceRouteHTTPMatch{ PathPrefix: "/retry-codes", @@ -704,11 +714,12 @@ func setupTestVariationDiscoveryChain( }, { Match: httpMatch(&structs.ServiceRouteHTTPMatch{ - PathPrefix: "/retry-both", + PathPrefix: "/retry-all", }), Destination: &structs.ServiceRouteDestination{ - Service: "retry-both", + Service: "retry-all", RetryOnConnectFailure: true, + RetryOn: []string{"5xx", "gateway-error", "reset", "connect-failure", "envoy-ratelimited", "retriable-4xx", "refused-stream", "cancelled", "deadline-exceeded", "internal", "resource-exhausted", "unavailable"}, RetryOnStatusCodes: []uint32{401, 409, 451}, }, }, diff --git a/agent/proxycfg/upstreams.go b/agent/proxycfg/upstreams.go index 94c872e39f..a2dc38b9d6 100644 --- a/agent/proxycfg/upstreams.go +++ b/agent/proxycfg/upstreams.go @@ -191,17 +191,19 @@ func (s *handlerUpstreams) handleUpdateUpstreams(ctx context.Context, u UpdateEv return fmt.Errorf("invalid type for response: %T", u.Result) } correlationID := strings.TrimPrefix(u.CorrelationID, "mesh-gateway:") - key, uidString, ok := removeColonPrefix(correlationID) - if !ok { - return fmt.Errorf("invalid correlation id %q", u.CorrelationID) - } - uid := UpstreamIDFromString(uidString) + key, uidString, ok := strings.Cut(correlationID, ":") + if ok { + // correlationID formatted with an upstreamID + uid := UpstreamIDFromString(uidString) - if _, ok = upstreamsSnapshot.WatchedGatewayEndpoints[uid]; !ok { - upstreamsSnapshot.WatchedGatewayEndpoints[uid] = make(map[string]structs.CheckServiceNodes) + if _, ok = upstreamsSnapshot.WatchedGatewayEndpoints[uid]; !ok { + upstreamsSnapshot.WatchedGatewayEndpoints[uid] = make(map[string]structs.CheckServiceNodes) + } + upstreamsSnapshot.WatchedGatewayEndpoints[uid][key] = resp.Nodes + } else { + // event was for local gateways only + upstreamsSnapshot.WatchedLocalGWEndpoints.Set(key, resp.Nodes) } - upstreamsSnapshot.WatchedGatewayEndpoints[uid][key] = resp.Nodes - default: return fmt.Errorf("unknown correlation ID: %s", u.CorrelationID) } @@ -317,7 +319,7 @@ func (s *handlerUpstreams) resetWatchesFromChain( } case structs.MeshGatewayModeLocal: gk = GatewayKey{ - Partition: s.source.NodePartitionOrDefault(), + Partition: s.proxyID.PartitionOrDefault(), Datacenter: s.source.Datacenter, } } diff --git a/agent/retry_join.go b/agent/retry_join.go index b807697e84..b3ebcf2a9a 100644 --- a/agent/retry_join.go +++ b/agent/retry_join.go @@ -5,6 +5,7 @@ import ( "strings" "time" + discoverhcp "github.com/hashicorp/consul/agent/hcp/discover" discover "github.com/hashicorp/go-discover" discoverk8s "github.com/hashicorp/go-discover/provider/k8s" "github.com/hashicorp/go-hclog" @@ -114,6 +115,7 @@ func newDiscover() (*discover.Discover, error) { providers[k] = v } providers["k8s"] = &discoverk8s.Provider{} + providers["hcp"] = &discoverhcp.Provider{} return discover.New( discover.WithUserAgent(lib.UserAgent()), diff --git a/agent/retry_join_test.go b/agent/retry_join_test.go index 9bc98797c2..e6e2fac779 100644 --- a/agent/retry_join_test.go +++ b/agent/retry_join_test.go @@ -12,7 +12,7 @@ func TestAgentRetryNewDiscover(t *testing.T) { d, err := newDiscover() require.NoError(t, err) expected := []string{ - "aliyun", "aws", "azure", "digitalocean", "gce", "k8s", "linode", + "aliyun", "aws", "azure", "digitalocean", "gce", "hcp", "k8s", "linode", "mdns", "os", "packet", "scaleway", "softlayer", "tencentcloud", "triton", "vsphere", } diff --git a/agent/rpc/peering/service.go b/agent/rpc/peering/service.go index 17b862ffae..6f753c4853 100644 --- a/agent/rpc/peering/service.go +++ b/agent/rpc/peering/service.go @@ -114,16 +114,14 @@ type Backend interface { // partition and namespace from the token. ResolveTokenAndDefaultMeta(token string, entMeta *acl.EnterpriseMeta, authzCtx *acl.AuthorizerContext) (resolver.Result, error) - // GetAgentCACertificates returns the CA certificate to be returned in the peering token data - GetAgentCACertificates() ([]string, error) + // GetTLSMaterials returns the TLS materials for the dialer to dial the acceptor using TLS. + // It returns the server name to validate, and the CA certificate to validate with. + GetTLSMaterials(generatingToken bool) (string, []string, error) - // GetServerAddresses returns the addresses used for establishing a peering connection + // GetServerAddresses returns the addresses used for establishing a peering connection. + // These may be server addresses or mesh gateway addresses if peering through mesh gateways. GetServerAddresses() ([]string, error) - // GetServerName returns the SNI to be returned in the peering token data which - // will be used by peers when establishing peering connections over TLS. - GetServerName() string - // EncodeToken packages a peering token into a slice of bytes. EncodeToken(tok *structs.PeeringToken) ([]byte, error) @@ -209,7 +207,12 @@ func (s *Server) GenerateToken( var authzCtx acl.AuthorizerContext entMeta := structs.DefaultEnterpriseMetaInPartition(req.Partition) - authz, err := s.Backend.ResolveTokenAndDefaultMeta(external.TokenFromContext(ctx), entMeta, &authzCtx) + options, err := external.QueryOptionsFromContext(ctx) + if err != nil { + return nil, err + } + + authz, err := s.Backend.ResolveTokenAndDefaultMeta(options.Token, entMeta, &authzCtx) if err != nil { return nil, err } @@ -218,6 +221,11 @@ func (s *Server) GenerateToken( return nil, err } + serverName, caPEMs, err := s.Backend.GetTLSMaterials(true) + if err != nil { + return nil, err + } + var ( peering *pbpeering.Peering secretID string @@ -285,11 +293,6 @@ func (s *Server) GenerateToken( break } - ca, err := s.Backend.GetAgentCACertificates() - if err != nil { - return nil, err - } - // ServerExternalAddresses must be formatted as addr:port. var serverAddrs []string if len(req.ServerExternalAddresses) > 0 { @@ -304,9 +307,9 @@ func (s *Server) GenerateToken( tok := structs.PeeringToken{ // Store the UUID so that we can do a global search when handling inbound streams. PeerID: peering.ID, - CA: ca, + CA: caPEMs, ServerAddresses: serverAddrs, - ServerName: s.Backend.GetServerName(), + ServerName: serverName, EstablishmentSecret: secretID, } @@ -360,7 +363,12 @@ func (s *Server) Establish( var authzCtx acl.AuthorizerContext entMeta := structs.DefaultEnterpriseMetaInPartition(req.Partition) - authz, err := s.Backend.ResolveTokenAndDefaultMeta(external.TokenFromContext(ctx), entMeta, &authzCtx) + options, err := external.QueryOptionsFromContext(ctx) + if err != nil { + return nil, err + } + + authz, err := s.Backend.ResolveTokenAndDefaultMeta(options.Token, entMeta, &authzCtx) if err != nil { return nil, err } @@ -476,8 +484,13 @@ func (s *Server) validatePeeringLocality(token *structs.PeeringToken, partition // If the token has the same server name as this cluster, but we can't find the peering // in our store, it indicates a naming conflict. - if s.Backend.GetServerName() == token.ServerName && peering == nil { - return fmt.Errorf("conflict - peering token's server name matches the current cluster's server name, %q, but there is no record in the database", s.Backend.GetServerName()) + serverName, _, err := s.Backend.GetTLSMaterials(false) + if err != nil { + return fmt.Errorf("failed to fetch TLS materials: %w", err) + } + + if serverName == token.ServerName && peering == nil { + return fmt.Errorf("conflict - peering token's server name matches the current cluster's server name, %q, but there is no record in the database", serverName) } if peering != nil && acl.EqualPartitions(peering.GetPartition(), partition) { @@ -528,7 +541,11 @@ func (s *Server) PeeringRead(ctx context.Context, req *pbpeering.PeeringReadRequ var authzCtx acl.AuthorizerContext entMeta := structs.DefaultEnterpriseMetaInPartition(req.Partition) - authz, err := s.Backend.ResolveTokenAndDefaultMeta(external.TokenFromContext(ctx), entMeta, &authzCtx) + options, err := external.QueryOptionsFromContext(ctx) + if err != nil { + return nil, err + } + authz, err := s.Backend.ResolveTokenAndDefaultMeta(options.Token, entMeta, &authzCtx) if err != nil { return nil, err } @@ -576,7 +593,12 @@ func (s *Server) PeeringList(ctx context.Context, req *pbpeering.PeeringListRequ var authzCtx acl.AuthorizerContext entMeta := structs.DefaultEnterpriseMetaInPartition(req.Partition) - authz, err := s.Backend.ResolveTokenAndDefaultMeta(external.TokenFromContext(ctx), entMeta, &authzCtx) + options, err := external.QueryOptionsFromContext(ctx) + if err != nil { + return nil, err + } + + authz, err := s.Backend.ResolveTokenAndDefaultMeta(options.Token, entMeta, &authzCtx) if err != nil { return nil, err } @@ -587,7 +609,7 @@ func (s *Server) PeeringList(ctx context.Context, req *pbpeering.PeeringListRequ defer metrics.MeasureSince([]string{"peering", "list"}, time.Now()) - _, peerings, err := s.Backend.Store().PeeringList(nil, *entMeta) + idx, peerings, err := s.Backend.Store().PeeringList(nil, *entMeta) if err != nil { return nil, err } @@ -599,7 +621,7 @@ func (s *Server) PeeringList(ctx context.Context, req *pbpeering.PeeringListRequ cPeerings = append(cPeerings, cp) } - return &pbpeering.PeeringListResponse{Peerings: cPeerings}, nil + return &pbpeering.PeeringListResponse{Peerings: cPeerings, Index: idx}, nil } // TODO(peering): Get rid of this func when we stop using the stream tracker for imported/ exported services and the peering state @@ -610,8 +632,10 @@ func (s *Server) PeeringList(ctx context.Context, req *pbpeering.PeeringListRequ func (s *Server) reconcilePeering(peering *pbpeering.Peering) *pbpeering.Peering { streamState, found := s.Tracker.StreamStatus(peering.ID) if !found { + // TODO(peering): this may be noise on non-leaders s.Logger.Warn("did not find peer in stream tracker; cannot populate imported and"+ " exported services count or reconcile peering state", "peerID", peering.ID) + peering.StreamStatus = &pbpeering.StreamStatus{} return peering } else { cp := copyPeering(peering) @@ -623,9 +647,26 @@ func (s *Server) reconcilePeering(peering *pbpeering.Peering) *pbpeering.Peering cp.State = pbpeering.PeeringState_FAILING } - // add imported & exported services counts - cp.ImportedServiceCount = streamState.GetImportedServicesCount() - cp.ExportedServiceCount = streamState.GetExportedServicesCount() + latest := func(tt ...time.Time) time.Time { + latest := time.Time{} + for _, t := range tt { + if t.After(latest) { + latest = t + } + } + return latest + } + + lastRecv := latest(streamState.LastRecvHeartbeat, streamState.LastRecvError, streamState.LastRecvResourceSuccess) + lastSend := latest(streamState.LastSendError, streamState.LastSendSuccess) + + cp.StreamStatus = &pbpeering.StreamStatus{ + ImportedServices: streamState.ImportedServices, + ExportedServices: streamState.ExportedServices, + LastHeartbeat: structs.TimeToProto(streamState.LastRecvHeartbeat), + LastReceive: structs.TimeToProto(lastRecv), + LastSend: structs.TimeToProto(lastSend), + } return cp } @@ -657,7 +698,12 @@ func (s *Server) PeeringWrite(ctx context.Context, req *pbpeering.PeeringWriteRe var authzCtx acl.AuthorizerContext entMeta := structs.DefaultEnterpriseMetaInPartition(req.Peering.Partition) - authz, err := s.Backend.ResolveTokenAndDefaultMeta(external.TokenFromContext(ctx), entMeta, &authzCtx) + options, err := external.QueryOptionsFromContext(ctx) + if err != nil { + return nil, err + } + + authz, err := s.Backend.ResolveTokenAndDefaultMeta(options.Token, entMeta, &authzCtx) if err != nil { return nil, err } @@ -716,7 +762,12 @@ func (s *Server) PeeringDelete(ctx context.Context, req *pbpeering.PeeringDelete var authzCtx acl.AuthorizerContext entMeta := structs.DefaultEnterpriseMetaInPartition(req.Partition) - authz, err := s.Backend.ResolveTokenAndDefaultMeta(external.TokenFromContext(ctx), entMeta, &authzCtx) + options, err := external.QueryOptionsFromContext(ctx) + if err != nil { + return nil, err + } + + authz, err := s.Backend.ResolveTokenAndDefaultMeta(options.Token, entMeta, &authzCtx) if err != nil { return nil, err } @@ -775,6 +826,11 @@ func (s *Server) TrustBundleRead(ctx context.Context, req *pbpeering.TrustBundle return nil, grpcstatus.Error(codes.InvalidArgument, err.Error()) } + options, err := external.QueryOptionsFromContext(ctx) + if err != nil { + return nil, err + } + var resp *pbpeering.TrustBundleReadResponse handled, err := s.ForwardRPC(&readRequest, func(conn *grpc.ClientConn) error { ctx := external.ForwardMetadataContext(ctx) @@ -790,7 +846,7 @@ func (s *Server) TrustBundleRead(ctx context.Context, req *pbpeering.TrustBundle var authzCtx acl.AuthorizerContext entMeta := structs.DefaultEnterpriseMetaInPartition(req.Partition) - authz, err := s.Backend.ResolveTokenAndDefaultMeta(external.TokenFromContext(ctx), entMeta, &authzCtx) + authz, err := s.Backend.ResolveTokenAndDefaultMeta(options.Token, entMeta, &authzCtx) if err != nil { return nil, err } @@ -845,7 +901,12 @@ func (s *Server) TrustBundleListByService(ctx context.Context, req *pbpeering.Tr var authzCtx acl.AuthorizerContext entMeta := acl.NewEnterpriseMetaWithPartition(req.Partition, req.Namespace) - authz, err := s.Backend.ResolveTokenAndDefaultMeta(external.TokenFromContext(ctx), &entMeta, &authzCtx) + options, err := external.QueryOptionsFromContext(ctx) + if err != nil { + return nil, err + } + + authz, err := s.Backend.ResolveTokenAndDefaultMeta(options.Token, &entMeta, &authzCtx) if err != nil { return nil, err } diff --git a/agent/rpc/peering/service_test.go b/agent/rpc/peering/service_test.go index 5472d081b2..ae822c24aa 100644 --- a/agent/rpc/peering/service_test.go +++ b/agent/rpc/peering/service_test.go @@ -2,6 +2,7 @@ package peering_test import ( "context" + "crypto/tls" "encoding/base64" "encoding/json" "fmt" @@ -19,6 +20,7 @@ import ( grpcstatus "google.golang.org/grpc/status" "github.com/hashicorp/consul/acl" + "github.com/hashicorp/consul/agent/connect" "github.com/hashicorp/consul/agent/consul" "github.com/hashicorp/consul/agent/consul/state" "github.com/hashicorp/consul/agent/consul/stream" @@ -62,6 +64,7 @@ func generateTooManyMetaKeys() map[string]string { func TestPeeringService_GenerateToken(t *testing.T) { dir := testutil.TempDir(t, "consul") + signer, _, _ := tlsutil.GeneratePrivateKey() ca, _, _ := tlsutil.GenerateCA(tlsutil.CAOpts{Signer: signer}) cafile := path.Join(dir, "cacert.pem") @@ -97,10 +100,14 @@ func TestPeeringService_GenerateToken(t *testing.T) { token := &structs.PeeringToken{} require.NoError(t, json.Unmarshal(tokenJSON, token)) - require.Equal(t, "server.dc1.consul", token.ServerName) + require.Equal(t, "server.dc1.peering.11111111-2222-3333-4444-555555555555.consul", token.ServerName) require.Len(t, token.ServerAddresses, 1) require.Equal(t, s.PublicGRPCAddr, token.ServerAddresses[0]) - require.Equal(t, []string{ca}, token.CA) + + // The roots utilized should be the ConnectCA roots and not the ones manually configured. + _, roots, err := s.Server.FSM().State().CARoots(nil) + require.NoError(t, err) + require.Equal(t, []string{roots.Active().RootCert}, token.CA) require.NotEmpty(t, token.EstablishmentSecret) secret = token.EstablishmentSecret @@ -165,6 +172,7 @@ func TestPeeringService_GenerateToken(t *testing.T) { func TestPeeringService_GenerateTokenExternalAddress(t *testing.T) { dir := testutil.TempDir(t, "consul") + signer, _, _ := tlsutil.GeneratePrivateKey() ca, _, _ := tlsutil.GenerateCA(tlsutil.CAOpts{Signer: signer}) cafile := path.Join(dir, "cacert.pem") @@ -191,10 +199,14 @@ func TestPeeringService_GenerateTokenExternalAddress(t *testing.T) { token := &structs.PeeringToken{} require.NoError(t, json.Unmarshal(tokenJSON, token)) - require.Equal(t, "server.dc1.consul", token.ServerName) + require.Equal(t, "server.dc1.peering.11111111-2222-3333-4444-555555555555.consul", token.ServerName) require.Len(t, token.ServerAddresses, 1) require.Equal(t, externalAddress, token.ServerAddresses[0]) - require.Equal(t, []string{ca}, token.CA) + + // The roots utilized should be the ConnectCA roots and not the ones manually configured. + _, roots, err := s.Server.FSM().State().CARoots(nil) + require.NoError(t, err) + require.Equal(t, []string{roots.Active().RootCert}, token.CA) } func TestPeeringService_GenerateToken_ACLEnforcement(t *testing.T) { @@ -217,7 +229,10 @@ func TestPeeringService_GenerateToken_ACLEnforcement(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) t.Cleanup(cancel) - _, err := client.GenerateToken(external.ContextWithToken(ctx, tc.token), tc.req) + options := structs.QueryOptions{Token: tc.token} + ctx, err := external.ContextWithQueryOptions(ctx, options) + require.NoError(t, err) + _, err = client.GenerateToken(ctx, tc.req) if tc.expectErr != "" { require.Contains(t, err.Error(), tc.expectErr) return @@ -382,9 +397,13 @@ func TestPeeringService_Establish_serverNameConflict(t *testing.T) { // Manufacture token to have the same server name but a PeerID not in the store. id, err := uuid.GenerateUUID() require.NoError(t, err, "could not generate uuid") + + serverName, _, err := s.Server.GetPeeringBackend().GetTLSMaterials(true) + require.NoError(t, err) + peeringToken := structs.PeeringToken{ ServerAddresses: []string{"1.2.3.4:8502"}, - ServerName: s.Server.GetPeeringBackend().GetServerName(), + ServerName: serverName, EstablishmentSecret: "foo", PeerID: id, } @@ -406,12 +425,15 @@ func TestPeeringService_Establish_serverNameConflict(t *testing.T) { func TestPeeringService_Establish(t *testing.T) { // TODO(peering): see note on newTestServer, refactor to not use this - s1 := newTestServer(t, nil) + s1 := newTestServer(t, func(conf *consul.Config) { + conf.NodeName = "s1" + }) client1 := pbpeering.NewPeeringServiceClient(s1.ClientConn(t)) s2 := newTestServer(t, func(conf *consul.Config) { + conf.NodeName = "s2" conf.Datacenter = "dc2" - conf.GRPCPort = 5301 + conf.PrimaryDatacenter = "dc2" }) client2 := pbpeering.NewPeeringServiceClient(s2.ClientConn(t)) @@ -427,8 +449,10 @@ func TestPeeringService_Establish(t *testing.T) { var peerID string testutil.RunStep(t, "peering can be established from token", func(t *testing.T) { - _, err = client2.Establish(ctx, &pbpeering.EstablishRequest{PeerName: "my-peer-s1", PeeringToken: tokenResp.PeeringToken}) - require.NoError(t, err) + retry.Run(t, func(r *retry.R) { + _, err = client2.Establish(ctx, &pbpeering.EstablishRequest{PeerName: "my-peer-s1", PeeringToken: tokenResp.PeeringToken}) + require.NoError(r, err) + }) ctx, cancel = context.WithTimeout(context.Background(), 10*time.Second) t.Cleanup(cancel) @@ -491,7 +515,10 @@ func TestPeeringService_Establish_ACLEnforcement(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) t.Cleanup(cancel) - _, err := client.Establish(external.ContextWithToken(ctx, tc.token), tc.req) + options := structs.QueryOptions{Token: tc.token} + ctx, err := external.ContextWithQueryOptions(ctx, options) + require.NoError(t, err) + _, err = client.Establish(ctx, tc.req) if tc.expectErr != "" { require.Contains(t, err.Error(), tc.expectErr) return @@ -538,14 +565,12 @@ func TestPeeringService_Read(t *testing.T) { // insert peering directly to state store p := &pbpeering.Peering{ - ID: testUUID(t), - Name: "foo", - State: pbpeering.PeeringState_ESTABLISHING, - PeerCAPems: nil, - PeerServerName: "test", - PeerServerAddresses: []string{"addr1"}, - ImportedServiceCount: 0, - ExportedServiceCount: 0, + ID: testUUID(t), + Name: "foo", + State: pbpeering.PeeringState_ESTABLISHING, + PeerCAPems: nil, + PeerServerName: "test", + PeerServerAddresses: []string{"addr1"}, } err := s.Server.FSM().State().PeeringWrite(10, &pbpeering.PeeringWriteRequest{Peering: p}) require.NoError(t, err) @@ -601,14 +626,12 @@ func TestPeeringService_Read_ACLEnforcement(t *testing.T) { // insert peering directly to state store p := &pbpeering.Peering{ - ID: testUUID(t), - Name: "foo", - State: pbpeering.PeeringState_ESTABLISHING, - PeerCAPems: nil, - PeerServerName: "test", - PeerServerAddresses: []string{"addr1"}, - ImportedServiceCount: 0, - ExportedServiceCount: 0, + ID: testUUID(t), + Name: "foo", + State: pbpeering.PeeringState_ESTABLISHING, + PeerCAPems: nil, + PeerServerName: "test", + PeerServerAddresses: []string{"addr1"}, } err := s.Server.FSM().State().PeeringWrite(10, &pbpeering.PeeringWriteRequest{Peering: p}) require.NoError(t, err) @@ -626,7 +649,10 @@ func TestPeeringService_Read_ACLEnforcement(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) t.Cleanup(cancel) - resp, err := client.PeeringRead(external.ContextWithToken(ctx, tc.token), tc.req) + options := structs.QueryOptions{Token: tc.token} + ctx, err := external.ContextWithQueryOptions(ctx, options) + require.NoError(t, err) + resp, err := client.PeeringRead(ctx, tc.req) if tc.expectErr != "" { require.Contains(t, err.Error(), tc.expectErr) return @@ -737,7 +763,10 @@ func TestPeeringService_Delete_ACLEnforcement(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) t.Cleanup(cancel) - _, err = client.PeeringDelete(external.ContextWithToken(ctx, tc.token), tc.req) + options := structs.QueryOptions{Token: tc.token} + ctx, err := external.ContextWithQueryOptions(ctx, options) + require.NoError(t, err) + _, err = client.PeeringDelete(ctx, tc.req) if tc.expectErr != "" { require.Contains(t, err.Error(), tc.expectErr) return @@ -782,25 +811,21 @@ func TestPeeringService_List(t *testing.T) { // Note that the state store holds reference to the underlying // variables; do not modify them after writing. foo := &pbpeering.Peering{ - ID: testUUID(t), - Name: "foo", - State: pbpeering.PeeringState_ESTABLISHING, - PeerCAPems: nil, - PeerServerName: "fooservername", - PeerServerAddresses: []string{"addr1"}, - ImportedServiceCount: 0, - ExportedServiceCount: 0, + ID: testUUID(t), + Name: "foo", + State: pbpeering.PeeringState_ESTABLISHING, + PeerCAPems: nil, + PeerServerName: "fooservername", + PeerServerAddresses: []string{"addr1"}, } require.NoError(t, s.Server.FSM().State().PeeringWrite(10, &pbpeering.PeeringWriteRequest{Peering: foo})) bar := &pbpeering.Peering{ - ID: testUUID(t), - Name: "bar", - State: pbpeering.PeeringState_ACTIVE, - PeerCAPems: nil, - PeerServerName: "barservername", - PeerServerAddresses: []string{"addr1"}, - ImportedServiceCount: 0, - ExportedServiceCount: 0, + ID: testUUID(t), + Name: "bar", + State: pbpeering.PeeringState_ACTIVE, + PeerCAPems: nil, + PeerServerName: "barservername", + PeerServerAddresses: []string{"addr1"}, } require.NoError(t, s.Server.FSM().State().PeeringWrite(15, &pbpeering.PeeringWriteRequest{Peering: bar})) @@ -814,6 +839,7 @@ func TestPeeringService_List(t *testing.T) { expect := &pbpeering.PeeringListResponse{ Peerings: []*pbpeering.Peering{bar, foo}, + Index: 15, } prototest.AssertDeepEqual(t, expect, resp) } @@ -828,25 +854,21 @@ func TestPeeringService_List_ACLEnforcement(t *testing.T) { // insert peering directly to state store foo := &pbpeering.Peering{ - ID: testUUID(t), - Name: "foo", - State: pbpeering.PeeringState_ESTABLISHING, - PeerCAPems: nil, - PeerServerName: "fooservername", - PeerServerAddresses: []string{"addr1"}, - ImportedServiceCount: 0, - ExportedServiceCount: 0, + ID: testUUID(t), + Name: "foo", + State: pbpeering.PeeringState_ESTABLISHING, + PeerCAPems: nil, + PeerServerName: "fooservername", + PeerServerAddresses: []string{"addr1"}, } require.NoError(t, s.Server.FSM().State().PeeringWrite(10, &pbpeering.PeeringWriteRequest{Peering: foo})) bar := &pbpeering.Peering{ - ID: testUUID(t), - Name: "bar", - State: pbpeering.PeeringState_ACTIVE, - PeerCAPems: nil, - PeerServerName: "barservername", - PeerServerAddresses: []string{"addr1"}, - ImportedServiceCount: 0, - ExportedServiceCount: 0, + ID: testUUID(t), + Name: "bar", + State: pbpeering.PeeringState_ACTIVE, + PeerCAPems: nil, + PeerServerName: "barservername", + PeerServerAddresses: []string{"addr1"}, } require.NoError(t, s.Server.FSM().State().PeeringWrite(15, &pbpeering.PeeringWriteRequest{Peering: bar})) @@ -862,7 +884,10 @@ func TestPeeringService_List_ACLEnforcement(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) t.Cleanup(cancel) - resp, err := client.PeeringList(external.ContextWithToken(ctx, tc.token), &pbpeering.PeeringListRequest{}) + options := structs.QueryOptions{Token: tc.token} + ctx, err := external.ContextWithQueryOptions(ctx, options) + require.NoError(t, err) + resp, err := client.PeeringList(ctx, &pbpeering.PeeringListRequest{}) if tc.expectErr != "" { require.Contains(t, err.Error(), tc.expectErr) return @@ -880,6 +905,7 @@ func TestPeeringService_List_ACLEnforcement(t *testing.T) { token: testTokenPeeringReadSecret, expect: &pbpeering.PeeringListResponse{ Peerings: []*pbpeering.Peering{bar, foo}, + Index: 15, }, }, } @@ -950,7 +976,10 @@ func TestPeeringService_TrustBundleRead_ACLEnforcement(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) t.Cleanup(cancel) - resp, err := client.TrustBundleRead(external.ContextWithToken(ctx, tc.token), tc.req) + options := structs.QueryOptions{Token: tc.token} + ctx, err := external.ContextWithQueryOptions(ctx, options) + require.NoError(t, err) + resp, err := client.TrustBundleRead(ctx, tc.req) if tc.expectErr != "" { require.Contains(t, err.Error(), tc.expectErr) return @@ -1053,10 +1082,10 @@ func TestPeeringService_TrustBundleListByService(t *testing.T) { Name: "api", Consumers: []structs.ServiceConsumer{ { - PeerName: "foo", + Peer: "foo", }, { - PeerName: "bar", + Peer: "bar", }, }, }, @@ -1064,7 +1093,7 @@ func TestPeeringService_TrustBundleListByService(t *testing.T) { Name: "web", Consumers: []structs.ServiceConsumer{ { - PeerName: "baz", + Peer: "baz", }, }, }, @@ -1089,9 +1118,7 @@ func TestPeeringService_TrustBundleListByService(t *testing.T) { } func TestPeeringService_validatePeer(t *testing.T) { - s1 := newTestServer(t, func(c *consul.Config) { - c.SerfLANConfig.MemberlistConfig.AdvertiseAddr = "127.0.0.1" - }) + s1 := newTestServer(t, nil) client1 := pbpeering.NewPeeringServiceClient(s1.ClientConn(t)) ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) @@ -1105,8 +1132,8 @@ func TestPeeringService_validatePeer(t *testing.T) { }) s2 := newTestServer(t, func(conf *consul.Config) { - conf.GRPCPort = 5301 conf.Datacenter = "dc2" + conf.PrimaryDatacenter = "dc2" }) client2 := pbpeering.NewPeeringServiceClient(s2.ClientConn(t)) @@ -1258,7 +1285,7 @@ func TestPeeringService_TrustBundleListByService_ACLEnforcement(t *testing.T) { Name: "api", Consumers: []structs.ServiceConsumer{ { - PeerName: "foo", + Peer: "foo", }, }, }, @@ -1283,7 +1310,10 @@ func TestPeeringService_TrustBundleListByService_ACLEnforcement(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) t.Cleanup(cancel) - resp, err := client.TrustBundleListByService(external.ContextWithToken(ctx, tc.token), tc.req) + options := structs.QueryOptions{Token: tc.token} + ctx, err := external.ContextWithQueryOptions(ctx, options) + require.NoError(t, err) + resp, err := client.TrustBundleListByService(ctx, tc.req) if tc.expectErr != "" { require.Contains(t, err.Error(), tc.expectErr) return @@ -1353,7 +1383,18 @@ func newTestServer(t *testing.T, cb func(conf *consul.Config)) testingServer { conf.PrimaryDatacenter = "dc1" conf.ConnectEnabled = true - conf.GRPCPort = ports[3] + ca := connect.TestCA(t, nil) + conf.CAConfig = &structs.CAConfiguration{ + ClusterID: connect.TestClusterID, + Provider: structs.ConsulCAProvider, + Config: map[string]interface{}{ + "PrivateKey": ca.SigningKey, + "RootCert": ca.RootCert, + "LeafCertTTL": "72h", + "IntermediateCertTTL": "288h", + }, + } + conf.GRPCTLSPort = ports[3] nodeID, err := uuid.GenerateUUID() if err != nil { @@ -1372,27 +1413,34 @@ func newTestServer(t *testing.T, cb func(conf *consul.Config)) testingServer { conf.ACLResolverSettings.Datacenter = conf.Datacenter conf.ACLResolverSettings.EnterpriseMeta = *conf.AgentEnterpriseMeta() - externalGRPCServer := gogrpc.NewServer() - deps := newDefaultDeps(t, conf) + externalGRPCServer := external.NewServer(deps.Logger) + server, err := consul.NewServer(conf, deps, externalGRPCServer) require.NoError(t, err) t.Cleanup(func() { require.NoError(t, server.Shutdown()) }) + require.NoError(t, deps.TLSConfigurator.UpdateAutoTLSCert(connect.TestServerLeaf(t, conf.Datacenter, ca))) + deps.TLSConfigurator.UpdateAutoTLSPeeringServerName(connect.PeeringServerSAN(conf.Datacenter, connect.TestTrustDomain)) + // Normally the gRPC server listener is created at the agent level and // passed down into the Server creation. - grpcAddr := fmt.Sprintf("127.0.0.1:%d", conf.GRPCPort) + grpcAddr := fmt.Sprintf("127.0.0.1:%d", conf.GRPCTLSPort) ln, err := net.Listen("tcp", grpcAddr) require.NoError(t, err) + + ln = tls.NewListener(ln, deps.TLSConfigurator.IncomingGRPCConfig()) + go func() { _ = externalGRPCServer.Serve(ln) }() t.Cleanup(externalGRPCServer.Stop) testrpc.WaitForLeader(t, server.RPC, conf.Datacenter) + testrpc.WaitForActiveCARoot(t, server.RPC, conf.Datacenter, nil) return testingServer{ Server: server, diff --git a/agent/rpc/peering/validate.go b/agent/rpc/peering/validate.go index 340e4c5ad7..af415102b1 100644 --- a/agent/rpc/peering/validate.go +++ b/agent/rpc/peering/validate.go @@ -38,9 +38,7 @@ func validatePeeringToken(tok *structs.PeeringToken) error { } } - // TODO(peering): validate name matches SNI? - // TODO(peering): validate name well formed? - if tok.ServerName == "" { + if len(tok.CA) > 0 && tok.ServerName == "" { return errPeeringTokenEmptyServerName } diff --git a/agent/service_manager.go b/agent/service_manager.go index f9f449874b..290102cb12 100644 --- a/agent/service_manager.go +++ b/agent/service_manager.go @@ -8,7 +8,7 @@ import ( "github.com/hashicorp/consul/agent/cache" cachetype "github.com/hashicorp/consul/agent/cache-types" - "github.com/hashicorp/consul/agent/consul" + "github.com/hashicorp/consul/agent/configentry" "github.com/hashicorp/consul/agent/structs" ) @@ -145,7 +145,7 @@ func (w *serviceConfigWatch) register(ctx context.Context) error { // Merge the local registration with the central defaults and update this service // in the local state. - merged, err := consul.MergeServiceConfig(serviceDefaults, w.registration.Service) + merged, err := configentry.MergeServiceConfig(serviceDefaults, w.registration.Service) if err != nil { return err } @@ -275,7 +275,7 @@ func (w *serviceConfigWatch) handleUpdate(ctx context.Context, event cache.Updat // Merge the local registration with the central defaults and update this service // in the local state. - merged, err := consul.MergeServiceConfig(serviceDefaults, w.registration.Service) + merged, err := configentry.MergeServiceConfig(serviceDefaults, w.registration.Service) if err != nil { return err } diff --git a/agent/setup.go b/agent/setup.go index 25353e1ac9..4fdeab213e 100644 --- a/agent/setup.go +++ b/agent/setup.go @@ -8,6 +8,7 @@ import ( "time" "github.com/armon/go-metrics/prometheus" + "github.com/hashicorp/consul/agent/hcp" "github.com/hashicorp/go-hclog" "google.golang.org/grpc/grpclog" @@ -153,6 +154,12 @@ func NewBaseDeps(configLoader ConfigLoader, logOut io.Writer) (BaseDeps, error) d.EventPublisher = stream.NewEventPublisher(10 * time.Second) d.XDSStreamLimiter = limiter.NewSessionLimiter() + if cfg.IsCloudEnabled() { + d.HCP, err = hcp.NewDeps(cfg.Cloud, d.Logger) + if err != nil { + return d, err + } + } return d, nil } diff --git a/agent/structs/config_entry.go b/agent/structs/config_entry.go index 72efbffce3..5be0a50898 100644 --- a/agent/structs/config_entry.go +++ b/agent/structs/config_entry.go @@ -38,6 +38,8 @@ const ( MeshConfigMesh string = "mesh" DefaultServiceProtocol = "tcp" + + ConnectionExactBalance = "exact_balance" ) var AllConfigEntryKinds = []string{ @@ -98,19 +100,20 @@ type WarningConfigEntry interface { // ServiceConfiguration is the top-level struct for the configuration of a service // across the entire cluster. type ServiceConfigEntry struct { - Kind string - Name string - Protocol string - Mode ProxyMode `json:",omitempty"` - TransparentProxy TransparentProxyConfig `json:",omitempty" alias:"transparent_proxy"` - MeshGateway MeshGatewayConfig `json:",omitempty" alias:"mesh_gateway"` - Expose ExposeConfig `json:",omitempty"` - ExternalSNI string `json:",omitempty" alias:"external_sni"` - UpstreamConfig *UpstreamConfiguration `json:",omitempty" alias:"upstream_config"` - Destination *DestinationConfig `json:",omitempty"` - MaxInboundConnections int `json:",omitempty" alias:"max_inbound_connections"` - LocalConnectTimeoutMs int `json:",omitempty" alias:"local_connect_timeout_ms"` - LocalRequestTimeoutMs int `json:",omitempty" alias:"local_request_timeout_ms"` + Kind string + Name string + Protocol string + Mode ProxyMode `json:",omitempty"` + TransparentProxy TransparentProxyConfig `json:",omitempty" alias:"transparent_proxy"` + MeshGateway MeshGatewayConfig `json:",omitempty" alias:"mesh_gateway"` + Expose ExposeConfig `json:",omitempty"` + ExternalSNI string `json:",omitempty" alias:"external_sni"` + UpstreamConfig *UpstreamConfiguration `json:",omitempty" alias:"upstream_config"` + Destination *DestinationConfig `json:",omitempty"` + MaxInboundConnections int `json:",omitempty" alias:"max_inbound_connections"` + LocalConnectTimeoutMs int `json:",omitempty" alias:"local_connect_timeout_ms"` + LocalRequestTimeoutMs int `json:",omitempty" alias:"local_request_timeout_ms"` + BalanceInboundConnections string `json:",omitempty" alias:"balance_inbound_connections"` Meta map[string]string `json:",omitempty"` acl.EnterpriseMeta `hcl:",squash" mapstructure:",squash"` @@ -183,6 +186,10 @@ func (e *ServiceConfigEntry) Validate() error { validationErr := validateConfigEntryMeta(e.Meta) + if !isValidConnectionBalance(e.BalanceInboundConnections) { + validationErr = multierror.Append(validationErr, fmt.Errorf("invalid value for balance_inbound_connections: %v", e.BalanceInboundConnections)) + } + // External endpoints are invalid with an existing service's upstream configuration if e.UpstreamConfig != nil && e.Destination != nil { validationErr = multierror.Append(validationErr, errors.New("UpstreamConfig and Destination are mutually exclusive for service defaults")) @@ -800,6 +807,10 @@ type UpstreamConfig struct { // MeshGatewayConfig controls how Mesh Gateways are configured and used MeshGateway MeshGatewayConfig `json:",omitempty" alias:"mesh_gateway" ` + + // BalanceOutboundConnections indicates how the proxy should attempt to distribute + // connections across worker threads. Only used by envoy proxies. + BalanceOutboundConnections string `json:",omitempty" alias:"balance_outbound_connections"` } func (cfg UpstreamConfig) Clone() UpstreamConfig { @@ -848,6 +859,9 @@ func (cfg UpstreamConfig) MergeInto(dst map[string]interface{}) { if cfg.PassiveHealthCheck != nil { dst["passive_health_check"] = cfg.PassiveHealthCheck } + if cfg.BalanceOutboundConnections != "" { + dst["balance_outbound_connections"] = cfg.BalanceOutboundConnections + } } func (cfg *UpstreamConfig) NormalizeWithoutName() error { @@ -917,6 +931,10 @@ func (cfg UpstreamConfig) validate(named bool) error { } } + if !isValidConnectionBalance(cfg.BalanceOutboundConnections) { + validationErr = multierror.Append(validationErr, fmt.Errorf("invalid value for balance_outbound_connections: %v", cfg.BalanceOutboundConnections)) + } + return validationErr } @@ -1222,3 +1240,7 @@ func validateConfigEntryMeta(meta map[string]string) error { type ConfigEntryDeleteResponse struct { Deleted bool } + +func isValidConnectionBalance(s string) bool { + return s == "" || s == ConnectionExactBalance +} diff --git a/agent/structs/config_entry_discoverychain.go b/agent/structs/config_entry_discoverychain.go index 7867602cae..69800d9939 100644 --- a/agent/structs/config_entry_discoverychain.go +++ b/agent/structs/config_entry_discoverychain.go @@ -228,6 +228,12 @@ func (e *ServiceRouterConfigEntry) Validate() error { if route.Destination.PrefixRewrite != "" && !eligibleForPrefixRewrite { return fmt.Errorf("Route[%d] cannot make use of PrefixRewrite without configuring either PathExact or PathPrefix", i) } + + for _, r := range route.Destination.RetryOn { + if !isValidRetryCondition(r) { + return fmt.Errorf("Route[%d] contains an invalid retry condition: %q", i, r) + } + } } } @@ -251,6 +257,26 @@ func isValidHTTPMethod(method string) bool { } } +func isValidRetryCondition(retryOn string) bool { + switch retryOn { + case "5xx", + "gateway-error", + "reset", + "connect-failure", + "envoy-ratelimited", + "retriable-4xx", + "refused-stream", + "cancelled", + "deadline-exceeded", + "internal", + "resource-exhausted", + "unavailable": + return true + default: + return false + } +} + func (e *ServiceRouterConfigEntry) CanRead(authz acl.Authorizer) error { return canReadDiscoveryChain(e, authz) } @@ -409,6 +435,10 @@ type ServiceRouteDestination struct { // 4 failure bubbling up to layer 7. RetryOnConnectFailure bool `json:",omitempty" alias:"retry_on_connect_failure"` + // RetryOn allows setting envoy specific conditions when a request should + // be automatically retried. + RetryOn []string `json:",omitempty" alias:"retry_on"` + // RetryOnStatusCodes is a flat list of http response status codes that are // eligible for retry. This again should be feasible in any reasonable proxy. RetryOnStatusCodes []uint32 `json:",omitempty" alias:"retry_on_status_codes"` @@ -455,7 +485,7 @@ func (e *ServiceRouteDestination) UnmarshalJSON(data []byte) error { } func (d *ServiceRouteDestination) HasRetryFeatures() bool { - return d.NumRetries > 0 || d.RetryOnConnectFailure || len(d.RetryOnStatusCodes) > 0 + return d.NumRetries > 0 || d.RetryOnConnectFailure || len(d.RetryOnStatusCodes) > 0 || len(d.RetryOn) > 0 } // ServiceSplitterConfigEntry defines how incoming requests are split across diff --git a/agent/structs/config_entry_discoverychain_test.go b/agent/structs/config_entry_discoverychain_test.go index 0d6691fa02..dbd766744b 100644 --- a/agent/structs/config_entry_discoverychain_test.go +++ b/agent/structs/config_entry_discoverychain_test.go @@ -2054,6 +2054,43 @@ func TestServiceRouterConfigEntry(t *testing.T) { }))), validateErr: "Methods contains \"GET\" more than once", }, + //////////////// + { + name: "route with no match with retry condition", + entry: makerouter(ServiceRoute{ + Match: nil, + Destination: &ServiceRouteDestination{ + Service: "other", + RetryOn: []string{ + "5xx", + "gateway-error", + "reset", + "connect-failure", + "envoy-ratelimited", + "retriable-4xx", + "refused-stream", + "cancelled", + "deadline-exceeded", + "internal", + "resource-exhausted", + "unavailable", + }, + }, + }), + }, + { + name: "route with no match with invalid retry condition", + entry: makerouter(ServiceRoute{ + Match: nil, + Destination: &ServiceRouteDestination{ + Service: "other", + RetryOn: []string{ + "invalid-retry-condition", + }, + }, + }), + validateErr: "contains an invalid retry condition: \"invalid-retry-condition\"", + }, } for _, tc := range cases { @@ -2122,3 +2159,22 @@ func TestIsProtocolHTTPLike(t *testing.T) { assert.True(t, IsProtocolHTTPLike("http2")) assert.True(t, IsProtocolHTTPLike("grpc")) } + +func TestIsValidRetryCondition(t *testing.T) { + assert.False(t, isValidRetryCondition("")) + assert.False(t, isValidRetryCondition("retriable-headers")) + assert.False(t, isValidRetryCondition("retriable-status-codes")) + + assert.True(t, isValidRetryCondition("5xx")) + assert.True(t, isValidRetryCondition("gateway-error")) + assert.True(t, isValidRetryCondition("reset")) + assert.True(t, isValidRetryCondition("connect-failure")) + assert.True(t, isValidRetryCondition("envoy-ratelimited")) + assert.True(t, isValidRetryCondition("retriable-4xx")) + assert.True(t, isValidRetryCondition("refused-stream")) + assert.True(t, isValidRetryCondition("cancelled")) + assert.True(t, isValidRetryCondition("deadline-exceeded")) + assert.True(t, isValidRetryCondition("internal")) + assert.True(t, isValidRetryCondition("resource-exhausted")) + assert.True(t, isValidRetryCondition("unavailable")) +} diff --git a/agent/structs/config_entry_export_oss_test.go b/agent/structs/config_entry_export_oss_test.go index 4015f5d714..19ce3d05f6 100644 --- a/agent/structs/config_entry_export_oss_test.go +++ b/agent/structs/config_entry_export_oss_test.go @@ -17,7 +17,7 @@ func TestExportedServicesConfigEntry_OSS(t *testing.T) { Name: "web", Consumers: []ServiceConsumer{ { - PeerName: "bar", + Peer: "bar", }, }, }, @@ -31,7 +31,7 @@ func TestExportedServicesConfigEntry_OSS(t *testing.T) { Namespace: "", Consumers: []ServiceConsumer{ { - PeerName: "bar", + Peer: "bar", }, }, }, diff --git a/agent/structs/config_entry_exports.go b/agent/structs/config_entry_exports.go index c3051fc371..81c62e1236 100644 --- a/agent/structs/config_entry_exports.go +++ b/agent/structs/config_entry_exports.go @@ -35,14 +35,14 @@ type ExportedService struct { } // ServiceConsumer represents a downstream consumer of the service to be exported. -// At most one of Partition or PeerName must be specified. +// At most one of Partition or Peer must be specified. type ServiceConsumer struct { // Partition is the admin partition to export the service to. - // Deprecated: PeerName should be used for both remote peers and local partitions. + // Deprecated: Peer should be used for both remote peers and local partitions. Partition string `json:",omitempty"` - // PeerName is the name of the peer to export the service to. - PeerName string `json:",omitempty" alias:"peer_name"` + // Peer is the name of the peer to export the service to. + Peer string `json:",omitempty" alias:"peer_name"` } func (e *ExportedServicesConfigEntry) ToMap() map[string]map[string][]string { @@ -130,13 +130,13 @@ func (e *ExportedServicesConfigEntry) Validate() error { return fmt.Errorf("Services[%d]: must have at least one consumer", i) } for j, consumer := range svc.Consumers { - if consumer.PeerName != "" && consumer.Partition != "" { - return fmt.Errorf("Services[%d].Consumers[%d]: must define at most one of PeerName or Partition", i, j) + if consumer.Peer != "" && consumer.Partition != "" { + return fmt.Errorf("Services[%d].Consumers[%d]: must define at most one of Peer or Partition", i, j) } if consumer.Partition == WildcardSpecifier { return fmt.Errorf("Services[%d].Consumers[%d]: exporting to all partitions (wildcard) is not supported", i, j) } - if consumer.PeerName == WildcardSpecifier { + if consumer.Peer == WildcardSpecifier { return fmt.Errorf("Services[%d].Consumers[%d]: exporting to all peers (wildcard) is not supported", i, j) } } diff --git a/agent/structs/config_entry_exports_test.go b/agent/structs/config_entry_exports_test.go index db0aadb91a..e1c58cea65 100644 --- a/agent/structs/config_entry_exports_test.go +++ b/agent/structs/config_entry_exports_test.go @@ -60,10 +60,10 @@ func TestExportedServicesConfigEntry(t *testing.T) { Name: "web", Consumers: []ServiceConsumer{ { - PeerName: "foo", + Peer: "foo", }, { - PeerName: "*", + Peer: "*", }, }, }, @@ -80,13 +80,13 @@ func TestExportedServicesConfigEntry(t *testing.T) { Consumers: []ServiceConsumer{ { Partition: "foo", - PeerName: "bar", + Peer: "bar", }, }, }, }, }, - validateErr: `Services[0].Consumers[0]: must define at most one of PeerName or Partition`, + validateErr: `Services[0].Consumers[0]: must define at most one of Peer or Partition`, }, } diff --git a/agent/structs/config_entry_gateways.go b/agent/structs/config_entry_gateways.go index c0abcd59db..3e05eec911 100644 --- a/agent/structs/config_entry_gateways.go +++ b/agent/structs/config_entry_gateways.go @@ -31,11 +31,20 @@ type IngressGatewayConfigEntry struct { // what services to associated to those ports. Listeners []IngressListener + // Defaults contains default configuration for all upstream service instances + Defaults *IngressServiceConfig `json:",omitempty"` + Meta map[string]string `json:",omitempty"` acl.EnterpriseMeta `hcl:",squash" mapstructure:",squash"` RaftIndex } +type IngressServiceConfig struct { + MaxConnections uint32 + MaxPendingRequests uint32 + MaxConcurrentRequests uint32 +} + type IngressListener struct { // Port declares the port on which the ingress gateway should listen for traffic. Port int @@ -90,6 +99,10 @@ type IngressService struct { RequestHeaders *HTTPHeaderModifiers `json:",omitempty" alias:"request_headers"` ResponseHeaders *HTTPHeaderModifiers `json:",omitempty" alias:"response_headers"` + MaxConnections uint32 `json:",omitempty" alias:"max_connections"` + MaxPendingRequests uint32 `json:",omitempty" alias:"max_pending_requests"` + MaxConcurrentRequests uint32 `json:",omitempty" alias:"max_concurrent_requests"` + Meta map[string]string `json:",omitempty"` acl.EnterpriseMeta `hcl:",squash" mapstructure:",squash"` } diff --git a/agent/structs/config_entry_mesh.go b/agent/structs/config_entry_mesh.go index 4880a3692d..b599dbd537 100644 --- a/agent/structs/config_entry_mesh.go +++ b/agent/structs/config_entry_mesh.go @@ -155,6 +155,13 @@ func (e *MeshConfigEntry) MarshalJSON() ([]byte, error) { return json.Marshal(source) } +func (e *MeshConfigEntry) PeerThroughMeshGateways() bool { + if e == nil || e.Peering == nil { + return false + } + return e.Peering.PeerThroughMeshGateways +} + func validateMeshDirectionalTLSConfig(cfg *MeshDirectionalTLSConfig) error { if cfg == nil { return nil diff --git a/agent/structs/config_entry_mesh_test.go b/agent/structs/config_entry_mesh_test.go new file mode 100644 index 0000000000..b618bf55bd --- /dev/null +++ b/agent/structs/config_entry_mesh_test.go @@ -0,0 +1,46 @@ +package structs + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestMeshConfigEntry_PeerThroughMeshGateways(t *testing.T) { + tests := map[string]struct { + input *MeshConfigEntry + want bool + }{ + "nil entry": { + input: nil, + want: false, + }, + "nil peering config": { + input: &MeshConfigEntry{ + Peering: nil, + }, + want: false, + }, + "not peering through gateways": { + input: &MeshConfigEntry{ + Peering: &PeeringMeshConfig{ + PeerThroughMeshGateways: false, + }, + }, + want: false, + }, + "peering through gateways": { + input: &MeshConfigEntry{ + Peering: &PeeringMeshConfig{ + PeerThroughMeshGateways: true, + }, + }, + want: true, + }, + } + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + assert.Equalf(t, tc.want, tc.input.PeerThroughMeshGateways(), "PeerThroughMeshGateways()") + }) + } +} diff --git a/agent/structs/config_entry_test.go b/agent/structs/config_entry_test.go index 7a699417f0..887f1d68fd 100644 --- a/agent/structs/config_entry_test.go +++ b/agent/structs/config_entry_test.go @@ -340,6 +340,7 @@ func TestDecodeConfigEntry(t *testing.T) { "moreconfig" { "moar" = "config" } + "balance_inbound_connections" = "exact_balance" } mesh_gateway { mode = "remote" @@ -358,6 +359,7 @@ func TestDecodeConfigEntry(t *testing.T) { "moreconfig" { "moar" = "config" } + "balance_inbound_connections" = "exact_balance" } MeshGateway { Mode = "remote" @@ -376,6 +378,7 @@ func TestDecodeConfigEntry(t *testing.T) { "moreconfig": map[string]interface{}{ "moar": "config", }, + "balance_inbound_connections": "exact_balance", }, MeshGateway: MeshGatewayConfig{ Mode: MeshGatewayModeRemote, @@ -396,6 +399,7 @@ func TestDecodeConfigEntry(t *testing.T) { mesh_gateway { mode = "remote" } + balance_inbound_connections = "exact_balance" upstream_config { overrides = [ { @@ -415,6 +419,7 @@ func TestDecodeConfigEntry(t *testing.T) { defaults { connect_timeout_ms = 5 protocol = "http" + balance_outbound_connections = "exact_balance" envoy_listener_json = "foo" envoy_cluster_json = "bar" limits { @@ -437,6 +442,7 @@ func TestDecodeConfigEntry(t *testing.T) { MeshGateway { Mode = "remote" } + BalanceInboundConnections = "exact_balance" UpstreamConfig { Overrides = [ { @@ -463,6 +469,7 @@ func TestDecodeConfigEntry(t *testing.T) { MaxPendingRequests = 4 MaxConcurrentRequests = 5 } + BalanceOutboundConnections = "exact_balance" } } `, @@ -478,6 +485,7 @@ func TestDecodeConfigEntry(t *testing.T) { MeshGateway: MeshGatewayConfig{ Mode: MeshGatewayModeRemote, }, + BalanceInboundConnections: "exact_balance", UpstreamConfig: &UpstreamConfiguration{ Overrides: []*UpstreamConfig{ { @@ -502,6 +510,7 @@ func TestDecodeConfigEntry(t *testing.T) { MaxPendingRequests: intPointer(4), MaxConcurrentRequests: intPointer(5), }, + BalanceOutboundConnections: "exact_balance", }, }, }, @@ -1942,7 +1951,7 @@ func TestDecodeConfigEntry(t *testing.T) { Partition = "baz" }, { - PeerName = "flarm" + Peer = "flarm" } ] }, @@ -1975,7 +1984,7 @@ func TestDecodeConfigEntry(t *testing.T) { Partition: "baz", }, { - PeerName: "flarm", + Peer: "flarm", }, }, }, @@ -2651,6 +2660,44 @@ func TestServiceConfigEntry(t *testing.T) { }, validateErr: "Duplicate address", }, + "validate: invalid inbound connection balance": { + entry: &ServiceConfigEntry{ + Kind: ServiceDefaults, + Name: "external", + Protocol: "http", + BalanceInboundConnections: "invalid", + }, + validateErr: "invalid value for balance_inbound_connections", + }, + "validate: invalid default outbound connection balance": { + entry: &ServiceConfigEntry{ + Kind: ServiceDefaults, + Name: "external", + Protocol: "http", + UpstreamConfig: &UpstreamConfiguration{ + Defaults: &UpstreamConfig{ + BalanceOutboundConnections: "invalid", + }, + }, + }, + validateErr: "invalid value for balance_outbound_connections", + }, + "validate: invalid override outbound connection balance": { + entry: &ServiceConfigEntry{ + Kind: ServiceDefaults, + Name: "external", + Protocol: "http", + UpstreamConfig: &UpstreamConfiguration{ + Overrides: []*UpstreamConfig{ + { + Name: "upstream", + BalanceOutboundConnections: "invalid", + }, + }, + }, + }, + validateErr: "invalid value for balance_outbound_connections", + }, } testConfigEntryNormalizeAndValidate(t, cases) } @@ -2665,10 +2712,11 @@ func TestUpstreamConfig_MergeInto(t *testing.T) { { name: "kitchen sink", source: UpstreamConfig{ - EnvoyListenerJSON: "foo", - EnvoyClusterJSON: "bar", - ConnectTimeoutMs: 5, - Protocol: "http", + BalanceOutboundConnections: "exact_balance", + EnvoyListenerJSON: "foo", + EnvoyClusterJSON: "bar", + ConnectTimeoutMs: 5, + Protocol: "http", Limits: &UpstreamLimits{ MaxConnections: intPointer(3), MaxPendingRequests: intPointer(4), @@ -2682,10 +2730,11 @@ func TestUpstreamConfig_MergeInto(t *testing.T) { }, destination: make(map[string]interface{}), want: map[string]interface{}{ - "envoy_listener_json": "foo", - "envoy_cluster_json": "bar", - "connect_timeout_ms": 5, - "protocol": "http", + "balance_outbound_connections": "exact_balance", + "envoy_listener_json": "foo", + "envoy_cluster_json": "bar", + "connect_timeout_ms": 5, + "protocol": "http", "limits": &UpstreamLimits{ MaxConnections: intPointer(3), MaxPendingRequests: intPointer(4), @@ -2701,10 +2750,11 @@ func TestUpstreamConfig_MergeInto(t *testing.T) { { name: "kitchen sink override of destination", source: UpstreamConfig{ - EnvoyListenerJSON: "foo", - EnvoyClusterJSON: "bar", - ConnectTimeoutMs: 5, - Protocol: "http", + BalanceOutboundConnections: "exact_balance", + EnvoyListenerJSON: "foo", + EnvoyClusterJSON: "bar", + ConnectTimeoutMs: 5, + Protocol: "http", Limits: &UpstreamLimits{ MaxConnections: intPointer(3), MaxPendingRequests: intPointer(4), @@ -2717,10 +2767,11 @@ func TestUpstreamConfig_MergeInto(t *testing.T) { MeshGateway: MeshGatewayConfig{Mode: MeshGatewayModeRemote}, }, destination: map[string]interface{}{ - "envoy_listener_json": "zip", - "envoy_cluster_json": "zap", - "connect_timeout_ms": 10, - "protocol": "grpc", + "balance_outbound_connections": "", + "envoy_listener_json": "zip", + "envoy_cluster_json": "zap", + "connect_timeout_ms": 10, + "protocol": "grpc", "limits": &UpstreamLimits{ MaxConnections: intPointer(10), MaxPendingRequests: intPointer(11), @@ -2733,10 +2784,11 @@ func TestUpstreamConfig_MergeInto(t *testing.T) { "mesh_gateway": MeshGatewayConfig{Mode: MeshGatewayModeLocal}, }, want: map[string]interface{}{ - "envoy_listener_json": "foo", - "envoy_cluster_json": "bar", - "connect_timeout_ms": 5, - "protocol": "http", + "balance_outbound_connections": "exact_balance", + "envoy_listener_json": "foo", + "envoy_cluster_json": "bar", + "connect_timeout_ms": 5, + "protocol": "http", "limits": &UpstreamLimits{ MaxConnections: intPointer(3), MaxPendingRequests: intPointer(4), @@ -2753,10 +2805,11 @@ func TestUpstreamConfig_MergeInto(t *testing.T) { name: "empty source leaves destination intact", source: UpstreamConfig{}, destination: map[string]interface{}{ - "envoy_listener_json": "zip", - "envoy_cluster_json": "zap", - "connect_timeout_ms": 10, - "protocol": "grpc", + "balance_outbound_connections": "exact_balance", + "envoy_listener_json": "zip", + "envoy_cluster_json": "zap", + "connect_timeout_ms": 10, + "protocol": "grpc", "limits": &UpstreamLimits{ MaxConnections: intPointer(10), MaxPendingRequests: intPointer(11), @@ -2770,10 +2823,11 @@ func TestUpstreamConfig_MergeInto(t *testing.T) { "mesh_gateway": MeshGatewayConfig{Mode: MeshGatewayModeLocal}, }, want: map[string]interface{}{ - "envoy_listener_json": "zip", - "envoy_cluster_json": "zap", - "connect_timeout_ms": 10, - "protocol": "grpc", + "balance_outbound_connections": "exact_balance", + "envoy_listener_json": "zip", + "envoy_cluster_json": "zap", + "connect_timeout_ms": 10, + "protocol": "grpc", "limits": &UpstreamLimits{ MaxConnections: intPointer(10), MaxPendingRequests: intPointer(11), diff --git a/agent/structs/prepared_query.go b/agent/structs/prepared_query.go index cd8ec574b2..b46145113e 100644 --- a/agent/structs/prepared_query.go +++ b/agent/structs/prepared_query.go @@ -42,8 +42,8 @@ func (f *QueryFailoverOptions) AsTargets() []QueryFailoverTarget { } type QueryFailoverTarget struct { - // PeerName specifies a peer to try during failover. - PeerName string + // Peer specifies a peer to try during failover. + Peer string // Datacenter specifies a datacenter to try during failover. Datacenter string @@ -105,9 +105,9 @@ type ServiceQuery struct { // should be directly next to their services so this isn't an issue. Connect bool - // If not empty, PeerName represents the peer that the service + // If not empty, Peer represents the peer that the service // was imported from. - PeerName string + Peer string // EnterpriseMeta is the embedded enterprise metadata acl.EnterpriseMeta `hcl:",squash" mapstructure:",squash"` diff --git a/agent/structs/structs.go b/agent/structs/structs.go index b3b8325674..e7fdd39701 100644 --- a/agent/structs/structs.go +++ b/agent/structs/structs.go @@ -252,22 +252,22 @@ type RPCInfo interface { type QueryOptions struct { // Token is the ACL token ID. If not provided, the 'anonymous' // token is assumed for backwards compatibility. - Token string + Token string `mapstructure:"x-consul-token,omitempty"` // If set, wait until query exceeds given index. Must be provided // with MaxQueryTime. - MinQueryIndex uint64 + MinQueryIndex uint64 `mapstructure:"min-query-index,omitempty"` // Provided with MinQueryIndex to wait for change. - MaxQueryTime time.Duration + MaxQueryTime time.Duration `mapstructure:"max-query-time,omitempty"` // If set, any follower can service the request. Results // may be arbitrarily stale. - AllowStale bool + AllowStale bool `mapstructure:"allow-stale,omitempty"` // If set, the leader must verify leadership prior to // servicing the request. Prevents a stale read. - RequireConsistent bool + RequireConsistent bool `mapstructure:"require-consistent,omitempty"` // If set, the local agent may respond with an arbitrarily stale locally // cached response. The semantics differ from AllowStale since the agent may @@ -276,12 +276,12 @@ type QueryOptions struct { // provide additional bounds on the last contact time from the leader. It's // expected that servers that are partitioned are noticed and replaced in a // timely way by operators while the same may not be true for client agents. - UseCache bool + UseCache bool `mapstructure:"use-cache,omitempty"` // If set and AllowStale is true, will try first a stale // read, and then will perform a consistent read if stale // read is older than value. - MaxStaleDuration time.Duration + MaxStaleDuration time.Duration `mapstructure:"max-stale-duration,omitempty"` // MaxAge limits how old a cached value will be returned if UseCache is true. // If there is a cached response that is older than the MaxAge, it is treated @@ -290,30 +290,30 @@ type QueryOptions struct { // StaleIfError to a longer duration to change this behavior. It is ignored // if the endpoint supports background refresh caching. See // https://www.consul.io/api/index.html#agent-caching for more details. - MaxAge time.Duration + MaxAge time.Duration `mapstructure:"max-age,omitempty"` // MustRevalidate forces the agent to fetch a fresh version of a cached // resource or at least validate that the cached version is still fresh. It is // implied by either max-age=0 or must-revalidate Cache-Control headers. It // only makes sense when UseCache is true. We store it since MaxAge = 0 is the // default unset value. - MustRevalidate bool + MustRevalidate bool `mapstructure:"must-revalidate,omitempty"` // StaleIfError specifies how stale the client will accept a cached response // if the servers are unavailable to fetch a fresh one. Only makes sense when // UseCache is true and MaxAge is set to a lower, non-zero value. It is // ignored if the endpoint supports background refresh caching. See // https://www.consul.io/api/index.html#agent-caching for more details. - StaleIfError time.Duration + StaleIfError time.Duration `mapstructure:"stale-if-error,omitempty"` // Filter specifies the go-bexpr filter expression to be used for // filtering the data prior to returning a response - Filter string + Filter string `mapstructure:"filter,omitempty"` // AllowNotModifiedResponse indicates that if the MinIndex matches the // QueryMeta.Index, the response can be left empty and QueryMeta.NotModified // will be set to true to indicate the result of the query has not changed. - AllowNotModifiedResponse bool + AllowNotModifiedResponse bool `mapstructure:"allow-not-modified-response,omitempty"` } // IsRead is always true for QueryOption. diff --git a/agent/structs/testing_catalog.go b/agent/structs/testing_catalog.go index f026f6091e..1131690511 100644 --- a/agent/structs/testing_catalog.go +++ b/agent/structs/testing_catalog.go @@ -55,24 +55,47 @@ func TestNodeServiceWithName(t testing.T, name string) *NodeService { const peerTrustDomain = "1c053652-8512-4373-90cf-5a7f6263a994.consul" -func TestNodeServiceWithNameInPeer(t testing.T, name string, peer string) *NodeService { - service := "payments" - return &NodeService{ - Kind: ServiceKindTypical, - Service: name, - Port: 8080, +func TestCheckNodeServiceWithNameInPeer(t testing.T, name, peer, ip string, useHostname bool) CheckServiceNode { + service := &NodeService{ + Kind: ServiceKindTypical, + Service: name, + Port: 8080, + PeerName: peer, Connect: ServiceConnect{ PeerMeta: &PeeringServiceMeta{ SNI: []string{ - service + ".default.default." + peer + ".external." + peerTrustDomain, + name + ".default.default." + peer + ".external." + peerTrustDomain, }, SpiffeID: []string{ - "spiffe://" + peerTrustDomain + "/ns/default/dc/" + peer + "-dc/svc/" + service, + "spiffe://" + peerTrustDomain + "/ns/default/dc/" + peer + "-dc/svc/" + name, }, Protocol: "tcp", }, }, } + + if useHostname { + service.TaggedAddresses = map[string]ServiceAddress{ + TaggedAddressLAN: { + Address: ip, + Port: 443, + }, + TaggedAddressWAN: { + Address: name + ".us-east-1.elb.notaws.com", + Port: 8443, + }, + } + } + + return CheckServiceNode{ + Node: &Node{ + ID: "test1", + Node: "test1", + Address: ip, + Datacenter: "cloud-dc", + }, + Service: service, + } } // TestNodeServiceProxy returns a *NodeService representing a valid diff --git a/agent/testagent.go b/agent/testagent.go index ea5afff81d..2d34ba198b 100644 --- a/agent/testagent.go +++ b/agent/testagent.go @@ -85,6 +85,9 @@ type TestAgent struct { // non-user settable configurations Overrides string + // allows the BaseDeps to be modified before starting the embedded agent + OverrideDeps func(deps *BaseDeps) + // Agent is the embedded consul agent. // It is valid after Start(). *Agent @@ -187,7 +190,7 @@ func (a *TestAgent) Start(t *testing.T) error { Name: name, }) - portsConfig := randomPortsSource(t, a.UseHTTPS, a.UseGRPCTLS) + portsConfig := randomPortsSource(t, a.UseHTTPS) // Create NodeID outside the closure, so that it does not change testHCLConfig := TestConfigHCL(NodeID()) @@ -234,6 +237,10 @@ func (a *TestAgent) Start(t *testing.T) error { } a.Config = bd.RuntimeConfig + if a.OverrideDeps != nil { + a.OverrideDeps(&bd) + } + agent, err := New(bd) if err != nil { return fmt.Errorf("Error creating agent: %s", err) @@ -405,7 +412,7 @@ func (a *TestAgent) consulConfig() *consul.Config { // chance of port conflicts for concurrently executed test binaries. // Instead of relying on one set of ports to be sufficient we retry // starting the agent with different ports on port conflict. -func randomPortsSource(t *testing.T, useHTTPS bool, useGRPCTLS bool) string { +func randomPortsSource(t *testing.T, useHTTPS bool) string { ports := freeport.GetN(t, 8) var http, https int @@ -417,15 +424,6 @@ func randomPortsSource(t *testing.T, useHTTPS bool, useGRPCTLS bool) string { https = -1 } - var grpc, grpcTLS int - if useGRPCTLS { - grpc = -1 - grpcTLS = ports[7] - } else { - grpc = ports[6] - grpcTLS = -1 - } - return ` ports = { dns = ` + strconv.Itoa(ports[0]) + ` @@ -434,8 +432,8 @@ func randomPortsSource(t *testing.T, useHTTPS bool, useGRPCTLS bool) string { serf_lan = ` + strconv.Itoa(ports[3]) + ` serf_wan = ` + strconv.Itoa(ports[4]) + ` server = ` + strconv.Itoa(ports[5]) + ` - grpc = ` + strconv.Itoa(grpc) + ` - grpc_tls = ` + strconv.Itoa(grpcTLS) + ` + grpc = ` + strconv.Itoa(ports[6]) + ` + grpc_tls = ` + strconv.Itoa(ports[7]) + ` } ` } diff --git a/agent/ui_endpoint_test.go b/agent/ui_endpoint_test.go index 5ba9ca833d..0e8f9163a8 100644 --- a/agent/ui_endpoint_test.go +++ b/agent/ui_endpoint_test.go @@ -779,7 +779,7 @@ func TestUIExportedServices(t *testing.T) { Name: "api", Consumers: []structs.ServiceConsumer{ { - PeerName: "peer1", + Peer: "peer1", }, }, }, diff --git a/agent/xds/clusters.go b/agent/xds/clusters.go index a425f829ee..d0be284afb 100644 --- a/agent/xds/clusters.go +++ b/agent/xds/clusters.go @@ -345,8 +345,11 @@ func destinationSpecificServiceName(name string, address string) string { func (s *ResourceGenerator) clustersFromSnapshotMeshGateway(cfgSnap *proxycfg.ConfigSnapshot) ([]proto.Message, error) { keys := cfgSnap.MeshGateway.GatewayKeys() - // 1 cluster per remote dc/partition + 1 cluster per local service (this is a lower bound - all subset specific clusters will be appended) - clusters := make([]proto.Message, 0, len(keys)+len(cfgSnap.MeshGateway.ServiceGroups)) + // Allocation count (this is a lower bound - all subset specific clusters will be appended): + // 1 cluster per remote dc/partition + // 1 cluster per local service + // 1 cluster per unique peer server (control plane traffic) + clusters := make([]proto.Message, 0, len(keys)+len(cfgSnap.MeshGateway.ServiceGroups)+len(cfgSnap.MeshGateway.PeerServers)) // Generate the remote clusters for _, key := range keys { @@ -386,7 +389,8 @@ func (s *ResourceGenerator) clustersFromSnapshotMeshGateway(cfgSnap *proxycfg.Co } // And for the current datacenter, send all flavors appropriately. - for _, srv := range cfgSnap.MeshGateway.ConsulServers { + servers, _ := cfgSnap.MeshGateway.WatchedLocalServers.Get(structs.ConsulServiceName) + for _, srv := range servers { opts := clusterOpts{ name: cfgSnap.ServerSNIFn(cfgSnap.Datacenter, srv.Node.Node), } @@ -395,6 +399,21 @@ func (s *ResourceGenerator) clustersFromSnapshotMeshGateway(cfgSnap *proxycfg.Co } } + // Create a single cluster for local servers to be dialed by peers. + // When peering through gateways we load balance across the local servers. They cannot be addressed individually. + if cfg := cfgSnap.MeshConfig(); cfg.PeerThroughMeshGateways() { + servers, _ := cfgSnap.MeshGateway.WatchedLocalServers.Get(structs.ConsulServiceName) + + // Peering control-plane traffic can only ever be handled by the local leader. + // We avoid routing to read replicas since they will never be Raft voters. + if haveVoters(servers) { + cluster := s.makeGatewayCluster(cfgSnap, clusterOpts{ + name: connect.PeeringServerSAN(cfgSnap.Datacenter, cfgSnap.Roots.TrustDomain), + }) + clusters = append(clusters, cluster) + } + } + // generate the per-service/subset clusters c, err := s.makeGatewayServiceClusters(cfgSnap, cfgSnap.MeshGateway.ServiceGroups, cfgSnap.MeshGateway.ServiceResolvers) if err != nil { @@ -402,6 +421,13 @@ func (s *ResourceGenerator) clustersFromSnapshotMeshGateway(cfgSnap *proxycfg.Co } clusters = append(clusters, c...) + // generate the outgoing clusters for imported peer services. + c, err = s.makeGatewayOutgoingClusterPeeringServiceClusters(cfgSnap) + if err != nil { + return nil, err + } + clusters = append(clusters, c...) + // Generate per-target clusters for all exported discovery chains. c, err = s.makeExportedUpstreamClustersForMeshGateway(cfgSnap) if err != nil { @@ -409,6 +435,54 @@ func (s *ResourceGenerator) clustersFromSnapshotMeshGateway(cfgSnap *proxycfg.Co } clusters = append(clusters, c...) + // Generate one cluster for each unique peer server for control plane traffic + c, err = s.makePeerServerClusters(cfgSnap) + if err != nil { + return nil, err + } + clusters = append(clusters, c...) + + return clusters, nil +} + +func haveVoters(servers structs.CheckServiceNodes) bool { + for _, srv := range servers { + if isReplica := srv.Service.Meta["read_replica"]; isReplica == "true" { + continue + } + return true + } + return false +} + +func (s *ResourceGenerator) makePeerServerClusters(cfgSnap *proxycfg.ConfigSnapshot) ([]proto.Message, error) { + if cfgSnap.Kind != structs.ServiceKindMeshGateway { + return nil, fmt.Errorf("unsupported gateway kind %q", cfgSnap.Kind) + } + + clusters := make([]proto.Message, 0, len(cfgSnap.MeshGateway.PeerServers)) + + // Peer server names are assumed to already be formatted in SNI notation: + // server..peering. + for name, servers := range cfgSnap.MeshGateway.PeerServers { + if len(servers.Addresses) == 0 { + continue + } + + var cluster *envoy_cluster_v3.Cluster + if servers.UseCDS { + cluster = s.makeExternalHostnameCluster(cfgSnap, clusterOpts{ + name: name, + addresses: servers.Addresses, + }) + } else { + cluster = s.makeGatewayCluster(cfgSnap, clusterOpts{ + name: name, + }) + } + clusters = append(clusters, cluster) + } + return clusters, nil } @@ -533,6 +607,62 @@ func (s *ResourceGenerator) makeGatewayServiceClusters( return clusters, nil } +func (s *ResourceGenerator) makeGatewayOutgoingClusterPeeringServiceClusters(cfgSnap *proxycfg.ConfigSnapshot) ([]proto.Message, error) { + if cfgSnap.Kind != structs.ServiceKindMeshGateway { + return nil, fmt.Errorf("unsupported gateway kind %q", cfgSnap.Kind) + } + + var clusters []proto.Message + + for _, serviceGroups := range cfgSnap.MeshGateway.PeeringServices { + for sn, serviceGroup := range serviceGroups { + if len(serviceGroup.Nodes) == 0 { + continue + } + + node := serviceGroup.Nodes[0] + if node.Service == nil { + return nil, fmt.Errorf("couldn't get SNI for peered service %s", sn.String()) + } + // This uses the SNI in the accepting cluster peer so the remote mesh + // gateway can distinguish between an exported service as opposed to the + // usual mesh gateway route for a service. + clusterName := node.Service.Connect.PeerMeta.PrimarySNI() + + opts := clusterOpts{ + name: clusterName, + isRemote: true, + } + cluster := s.makeGatewayCluster(cfgSnap, opts) + + if serviceGroup.UseCDS { + configureClusterWithHostnames( + s.Logger, + cluster, + "", /*TODO:make configurable?*/ + serviceGroup.Nodes, + true, /*isRemote*/ + false, /*onlyPassing*/ + ) + } else { + cluster.ClusterDiscoveryType = &envoy_cluster_v3.Cluster_Type{Type: envoy_cluster_v3.Cluster_EDS} + cluster.EdsClusterConfig = &envoy_cluster_v3.Cluster_EdsClusterConfig{ + EdsConfig: &envoy_core_v3.ConfigSource{ + ResourceApiVersion: envoy_core_v3.ApiVersion_V3, + ConfigSourceSpecifier: &envoy_core_v3.ConfigSource_Ads{ + Ads: &envoy_core_v3.AggregatedConfigSource{}, + }, + }, + } + } + + clusters = append(clusters, cluster) + } + } + + return clusters, nil +} + func (s *ResourceGenerator) makeDestinationClusters(cfgSnap *proxycfg.ConfigSnapshot) ([]proto.Message, error) { serviceConfigs := cfgSnap.TerminatingGateway.ServiceConfigs @@ -544,16 +674,20 @@ func (s *ResourceGenerator) makeDestinationClusters(cfgSnap *proxycfg.ConfigSnap for _, address := range dest.Addresses { opts := clusterOpts{ - name: clusterNameForDestination(cfgSnap, svcName.Name, address, svcName.NamespaceOrDefault(), svcName.PartitionOrDefault()), - address: address, - port: dest.Port, + name: clusterNameForDestination(cfgSnap, svcName.Name, address, svcName.NamespaceOrDefault(), svcName.PartitionOrDefault()), + addresses: []structs.ServiceAddress{ + { + Address: address, + Port: dest.Port, + }, + }, } var cluster *envoy_cluster_v3.Cluster if structs.IsIP(address) { - cluster = s.makeTerminatingIPCluster(cfgSnap, opts) + cluster = s.makeExternalIPCluster(cfgSnap, opts) } else { - cluster = s.makeTerminatingHostnameCluster(cfgSnap, opts) + cluster = s.makeExternalHostnameCluster(cfgSnap, opts) } if err := s.injectGatewayDestinationAddons(cfgSnap, cluster, svcName); err != nil { return nil, err @@ -630,7 +764,7 @@ func (s *ResourceGenerator) injectGatewayDestinationAddons(cfgSnap *proxycfg.Con func (s *ResourceGenerator) clustersFromSnapshotIngressGateway(cfgSnap *proxycfg.ConfigSnapshot) ([]proto.Message, error) { var clusters []proto.Message createdClusters := make(map[proxycfg.UpstreamID]bool) - for _, upstreams := range cfgSnap.IngressGateway.Upstreams { + for listenerKey, upstreams := range cfgSnap.IngressGateway.Upstreams { for _, u := range upstreams { uid := proxycfg.NewUpstreamID(&u) @@ -658,6 +792,7 @@ func (s *ResourceGenerator) clustersFromSnapshotIngressGateway(cfgSnap *proxycfg } for _, c := range upstreamClusters { + s.configIngressUpstreamCluster(c, cfgSnap, listenerKey, &u) clusters = append(clusters, c) } createdClusters[uid] = true @@ -666,6 +801,50 @@ func (s *ResourceGenerator) clustersFromSnapshotIngressGateway(cfgSnap *proxycfg return clusters, nil } +func (s *ResourceGenerator) configIngressUpstreamCluster(c *envoy_cluster_v3.Cluster, cfgSnap *proxycfg.ConfigSnapshot, listenerKey proxycfg.IngressListenerKey, u *structs.Upstream) { + var threshold *envoy_cluster_v3.CircuitBreakers_Thresholds + setThresholdLimit := func(limitType string, limit int) { + if limit <= 0 { + return + } + + if threshold == nil { + threshold = &envoy_cluster_v3.CircuitBreakers_Thresholds{} + } + + switch limitType { + case "max_connections": + threshold.MaxConnections = makeUint32Value(limit) + case "max_pending_requests": + threshold.MaxPendingRequests = makeUint32Value(limit) + case "max_requests": + threshold.MaxRequests = makeUint32Value(limit) + } + } + + setThresholdLimit("max_connections", int(cfgSnap.IngressGateway.Defaults.MaxConnections)) + setThresholdLimit("max_pending_requests", int(cfgSnap.IngressGateway.Defaults.MaxPendingRequests)) + setThresholdLimit("max_requests", int(cfgSnap.IngressGateway.Defaults.MaxConcurrentRequests)) + + // Adjust the limit for upstream service + // Lookup listener and service config details from ingress gateway + // definition. + var svc *structs.IngressService + if lCfg, ok := cfgSnap.IngressGateway.Listeners[listenerKey]; ok { + svc = findIngressServiceMatchingUpstream(lCfg, *u) + } + + if svc != nil { + setThresholdLimit("max_connections", int(svc.MaxConnections)) + setThresholdLimit("max_pending_requests", int(svc.MaxPendingRequests)) + setThresholdLimit("max_requests", int(svc.MaxConcurrentRequests)) + } + + if threshold != nil { + c.CircuitBreakers.Thresholds = []*envoy_cluster_v3.CircuitBreakers_Thresholds{threshold} + } +} + func (s *ResourceGenerator) makeAppCluster(cfgSnap *proxycfg.ConfigSnapshot, name, pathProtocol string, port int) (*envoy_cluster_v3.Cluster, error) { var c *envoy_cluster_v3.Cluster var err error @@ -794,7 +973,11 @@ func (s *ResourceGenerator) makeUpstreamClusterForPeerService( useEDS := true if _, ok := cfgSnap.ConnectProxy.PeerUpstreamEndpointsUseHostnames[uid]; ok { - useEDS = false + // If we're using local mesh gw, the fact that upstreams use hostnames don't matter. + // If we're not using local mesh gw, then resort to CDS. + if upstreamConfig.MeshGateway.Mode != structs.MeshGatewayModeLocal { + useEDS = false + } } // If none of the service instances are addressed by a hostname we @@ -1300,6 +1483,11 @@ func makeClusterFromUserConfig(configJSON string) (*envoy_cluster_v3.Cluster, er return &c, err } +type addressPair struct { + host string + port int +} + type clusterOpts struct { // name for the cluster name string @@ -1316,9 +1504,9 @@ type clusterOpts struct { // hostnameEndpoints is a list of endpoints with a hostname as their address hostnameEndpoints structs.CheckServiceNodes - // Corresponds to a valid ip/port in a Destination - address string - port int + // Corresponds to a valid address/port pairs to be routed externally + // these addresses will be embedded in the cluster configuration and will never use EDS + addresses []structs.ServiceAddress } // makeGatewayCluster creates an Envoy cluster for a mesh or terminating gateway @@ -1446,8 +1634,9 @@ func configureClusterWithHostnames( } } -// makeTerminatingIPCluster creates an Envoy cluster for a terminating gateway with an ip destination -func (s *ResourceGenerator) makeTerminatingIPCluster(snap *proxycfg.ConfigSnapshot, opts clusterOpts) *envoy_cluster_v3.Cluster { +// makeExternalIPCluster creates an Envoy cluster for routing to IP addresses outside of Consul +// This is used by terminating gateways for Destinations +func (s *ResourceGenerator) makeExternalIPCluster(snap *proxycfg.ConfigSnapshot, opts clusterOpts) *envoy_cluster_v3.Cluster { cfg, err := ParseGatewayConfig(snap.Proxy.Config) if err != nil { // Don't hard fail on a config typo, just warn. The parse func returns @@ -1467,8 +1656,10 @@ func (s *ResourceGenerator) makeTerminatingIPCluster(snap *proxycfg.ConfigSnapsh ClusterDiscoveryType: &envoy_cluster_v3.Cluster_Type{Type: envoy_cluster_v3.Cluster_STATIC}, } - endpoints := []*envoy_endpoint_v3.LbEndpoint{ - makeEndpoint(opts.address, opts.port), + endpoints := make([]*envoy_endpoint_v3.LbEndpoint, 0, len(opts.addresses)) + + for _, pair := range opts.addresses { + endpoints = append(endpoints, makeEndpoint(pair.Address, pair.Port)) } cluster.LoadAssignment = &envoy_endpoint_v3.ClusterLoadAssignment{ @@ -1482,8 +1673,9 @@ func (s *ResourceGenerator) makeTerminatingIPCluster(snap *proxycfg.ConfigSnapsh return cluster } -// makeTerminatingHostnameCluster creates an Envoy cluster for a terminating gateway with a hostname destination -func (s *ResourceGenerator) makeTerminatingHostnameCluster(snap *proxycfg.ConfigSnapshot, opts clusterOpts) *envoy_cluster_v3.Cluster { +// makeExternalHostnameCluster creates an Envoy cluster for hostname endpoints that will be resolved with DNS +// This is used by both terminating gateways for Destinations, and Mesh Gateways for peering control plane traffice +func (s *ResourceGenerator) makeExternalHostnameCluster(snap *proxycfg.ConfigSnapshot, opts clusterOpts) *envoy_cluster_v3.Cluster { cfg, err := ParseGatewayConfig(snap.Proxy.Config) if err != nil { // Don't hard fail on a config typo, just warn. The parse func returns @@ -1505,16 +1697,20 @@ func (s *ResourceGenerator) makeTerminatingHostnameCluster(snap *proxycfg.Config rate := 10 * time.Second cluster.DnsRefreshRate = durationpb.New(rate) - address := makeAddress(opts.address, opts.port) + endpoints := make([]*envoy_endpoint_v3.LbEndpoint, 0, len(opts.addresses)) + + for _, pair := range opts.addresses { + address := makeAddress(pair.Address, pair.Port) - endpoints := []*envoy_endpoint_v3.LbEndpoint{ - { + endpoint := &envoy_endpoint_v3.LbEndpoint{ HostIdentifier: &envoy_endpoint_v3.LbEndpoint_Endpoint{ Endpoint: &envoy_endpoint_v3.Endpoint{ Address: address, }, }, - }, + } + + endpoints = append(endpoints, endpoint) } cluster.LoadAssignment = &envoy_endpoint_v3.ClusterLoadAssignment{ diff --git a/agent/xds/clusters_test.go b/agent/xds/clusters_test.go index de0fefcd72..26c3731a18 100644 --- a/agent/xds/clusters_test.go +++ b/agent/xds/clusters_test.go @@ -493,6 +493,45 @@ func TestClustersFromSnapshot(t *testing.T) { "simple", nil, nil, nil) }, }, + { + name: "ingress-with-service-max-connections", + create: func(t testinf.T) *proxycfg.ConfigSnapshot { + return proxycfg.TestConfigSnapshotIngressGateway(t, true, "tcp", + "simple", nil, + func(entry *structs.IngressGatewayConfigEntry) { + entry.Listeners[0].Services[0].MaxConnections = 4096 + }, nil) + }, + }, + { + name: "ingress-with-defaults-service-max-connections", + create: func(t testinf.T) *proxycfg.ConfigSnapshot { + return proxycfg.TestConfigSnapshotIngressGateway(t, true, "tcp", + "simple", nil, + func(entry *structs.IngressGatewayConfigEntry) { + entry.Defaults = &structs.IngressServiceConfig{ + MaxConnections: 2048, + MaxPendingRequests: 512, + MaxConcurrentRequests: 4096, + } + }, nil) + }, + }, + { + name: "ingress-with-overwrite-defaults-service-max-connections", + create: func(t testinf.T) *proxycfg.ConfigSnapshot { + return proxycfg.TestConfigSnapshotIngressGateway(t, true, "tcp", + "simple", nil, + func(entry *structs.IngressGatewayConfigEntry) { + entry.Defaults = &structs.IngressServiceConfig{ + MaxConnections: 2048, + MaxPendingRequests: 512, + } + entry.Listeners[0].Services[0].MaxConnections = 4096 + entry.Listeners[0].Services[0].MaxPendingRequests = 2048 + }, nil) + }, + }, { name: "ingress-with-chain-external-sni", create: func(t testinf.T) *proxycfg.ConfigSnapshot { diff --git a/agent/xds/config.go b/agent/xds/config.go index 0736fb44ca..8f3bc93805 100644 --- a/agent/xds/config.go +++ b/agent/xds/config.go @@ -68,6 +68,10 @@ type ProxyConfig struct { // MaxInboundConnections is the maximum number of inbound connections to // the proxy. If not set, the default is 0 (no limit). MaxInboundConnections int `mapstructure:"max_inbound_connections"` + + // BalanceInboundConnections indicates how the proxy should attempt to distribute + // connections across worker threads. Only used by envoy proxies. + BalanceInboundConnections string `json:",omitempty" alias:"balance_inbound_connections"` } // ParseProxyConfig returns the ProxyConfig parsed from the an opaque map. If an diff --git a/agent/xds/config_test.go b/agent/xds/config_test.go index d683e61d9e..574449f1ee 100644 --- a/agent/xds/config_test.go +++ b/agent/xds/config_test.go @@ -157,6 +157,17 @@ func TestParseProxyConfig(t *testing.T) { Protocol: "tcp", }, }, + { + name: "balance inbound connections override, string", + input: map[string]interface{}{ + "balance_inbound_connections": "exact_balance", + }, + want: ProxyConfig{ + LocalConnectTimeoutMs: 5000, + Protocol: "tcp", + BalanceInboundConnections: "exact_balance", + }, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { diff --git a/agent/xds/delta.go b/agent/xds/delta.go index aa038214c2..e87a3fd99c 100644 --- a/agent/xds/delta.go +++ b/agent/xds/delta.go @@ -282,7 +282,12 @@ func (s *Server) processDelta(stream ADSDeltaStream, reqCh <-chan *envoy_discove // Start watching config for that proxy var err error - stateCh, watchCancel, err = s.CfgSrc.Watch(proxyID, nodeName, external.TokenFromContext(stream.Context())) + options, err := external.QueryOptionsFromContext(stream.Context()) + if err != nil { + return status.Errorf(codes.Internal, "failed to watch proxy service: %s", err) + } + + stateCh, watchCancel, err = s.CfgSrc.Watch(proxyID, nodeName, options.Token) if err != nil { return status.Errorf(codes.Internal, "failed to watch proxy service: %s", err) } diff --git a/agent/xds/endpoints.go b/agent/xds/endpoints.go index b5588ce649..2a699ed002 100644 --- a/agent/xds/endpoints.go +++ b/agent/xds/endpoints.go @@ -3,13 +3,13 @@ package xds import ( "errors" "fmt" + "strconv" envoy_cluster_v3 "github.com/envoyproxy/go-control-plane/envoy/config/cluster/v3" envoy_core_v3 "github.com/envoyproxy/go-control-plane/envoy/config/core/v3" envoy_endpoint_v3 "github.com/envoyproxy/go-control-plane/envoy/config/endpoint/v3" - "github.com/golang/protobuf/proto" - bexpr "github.com/hashicorp/go-bexpr" + "github.com/hashicorp/go-bexpr" "github.com/hashicorp/consul/agent/connect" "github.com/hashicorp/consul/agent/proxycfg" @@ -108,7 +108,6 @@ func (s *ResourceGenerator) endpointsFromSnapshotConnectProxy(cfgSnap *proxycfg. clusterName := generatePeeredClusterName(uid, tbs) loadAssignment, err := s.makeUpstreamLoadAssignmentForPeerService(cfgSnap, clusterName, uid) - if err != nil { return nil, err } @@ -196,7 +195,12 @@ func (s *ResourceGenerator) endpointsFromSnapshotTerminatingGateway(cfgSnap *pro func (s *ResourceGenerator) endpointsFromSnapshotMeshGateway(cfgSnap *proxycfg.ConfigSnapshot) ([]proto.Message, error) { keys := cfgSnap.MeshGateway.GatewayKeys() - resources := make([]proto.Message, 0, len(keys)+len(cfgSnap.MeshGateway.ServiceGroups)) + + // Allocation count (this is a lower bound - all subset specific clusters will be appended): + // 1 cluster per remote dc/partition + // 1 cluster per local service + // 1 cluster per unique peer server (control plane traffic) + resources := make([]proto.Message, 0, len(keys)+len(cfgSnap.MeshGateway.ServiceGroups)+len(cfgSnap.MeshGateway.PeerServers)) for _, key := range keys { if key.Matches(cfgSnap.Datacenter, cfgSnap.ProxyID.PartitionOrDefault()) { @@ -249,7 +253,8 @@ func (s *ResourceGenerator) endpointsFromSnapshotMeshGateway(cfgSnap *proxycfg.C cfgSnap.ServerSNIFn != nil { var allServersLbEndpoints []*envoy_endpoint_v3.LbEndpoint - for _, srv := range cfgSnap.MeshGateway.ConsulServers { + servers, _ := cfgSnap.MeshGateway.WatchedLocalServers.Get(structs.ConsulServiceName) + for _, srv := range servers { clusterName := cfgSnap.ServerSNIFn(cfgSnap.Datacenter, srv.Node.Node) _, addr, port := srv.BestAddress(false /*wan*/) @@ -284,6 +289,51 @@ func (s *ResourceGenerator) endpointsFromSnapshotMeshGateway(cfgSnap *proxycfg.C }) } + // Create endpoints for the cluster where local servers will be dialed by peers. + // When peering through gateways we load balance across the local servers. They cannot be addressed individually. + if cfg := cfgSnap.MeshConfig(); cfg.PeerThroughMeshGateways() { + var serverEndpoints []*envoy_endpoint_v3.LbEndpoint + + servers, _ := cfgSnap.MeshGateway.WatchedLocalServers.Get(structs.ConsulServiceName) + for _, srv := range servers { + if isReplica := srv.Service.Meta["read_replica"]; isReplica == "true" { + // Peering control-plane traffic can only ever be handled by the local leader. + // We avoid routing to read replicas since they will never be Raft voters. + continue + } + + _, addr, _ := srv.BestAddress(false) + portStr, ok := srv.Service.Meta["grpc_tls_port"] + if !ok { + s.Logger.Warn("peering is enabled but local server %q does not have the required gRPC TLS port configured", + "server", srv.Node.Node) + continue + } + port, err := strconv.Atoi(portStr) + if err != nil { + s.Logger.Error("peering is enabled but local server has invalid gRPC TLS port", + "server", srv.Node.Node, "port", portStr, "error", err) + continue + } + + serverEndpoints = append(serverEndpoints, &envoy_endpoint_v3.LbEndpoint{ + HostIdentifier: &envoy_endpoint_v3.LbEndpoint_Endpoint{ + Endpoint: &envoy_endpoint_v3.Endpoint{ + Address: makeAddress(addr, port), + }, + }, + }) + } + if len(serverEndpoints) > 0 { + resources = append(resources, &envoy_endpoint_v3.ClusterLoadAssignment{ + ClusterName: connect.PeeringServerSAN(cfgSnap.Datacenter, cfgSnap.Roots.TrustDomain), + Endpoints: []*envoy_endpoint_v3.LocalityLbEndpoints{{ + LbEndpoints: serverEndpoints, + }}, + }) + } + } + // Generate the endpoints for each service and its subsets e, err := s.endpointsFromServicesAndResolvers(cfgSnap, cfgSnap.MeshGateway.ServiceGroups, cfgSnap.MeshGateway.ServiceResolvers) if err != nil { @@ -298,6 +348,20 @@ func (s *ResourceGenerator) endpointsFromSnapshotMeshGateway(cfgSnap *proxycfg.C } resources = append(resources, e...) + // generate the outgoing endpoints for imported peer services. + e, err = s.makeEndpointsForOutgoingPeeredServices(cfgSnap) + if err != nil { + return nil, err + } + resources = append(resources, e...) + + // Generate the endpoints for peer server control planes. + e, err = s.makePeerServerEndpointsForMeshGateway(cfgSnap) + if err != nil { + return nil, err + } + resources = append(resources, e...) + return resources, nil } @@ -354,6 +418,72 @@ func (s *ResourceGenerator) endpointsFromServicesAndResolvers( return resources, nil } +func (s *ResourceGenerator) makeEndpointsForOutgoingPeeredServices( + cfgSnap *proxycfg.ConfigSnapshot, +) ([]proto.Message, error) { + var resources []proto.Message + + // generate the endpoints for the linked service groups + for _, serviceGroups := range cfgSnap.MeshGateway.PeeringServices { + for sn, serviceGroup := range serviceGroups { + if serviceGroup.UseCDS || len(serviceGroup.Nodes) == 0 { + continue + } + + node := serviceGroup.Nodes[0] + if node.Service == nil { + return nil, fmt.Errorf("couldn't get SNI for peered service %s", sn.String()) + } + // This uses the SNI in the accepting cluster peer so the remote mesh + // gateway can distinguish between an exported service as opposed to the + // usual mesh gateway route for a service. + clusterName := node.Service.Connect.PeerMeta.PrimarySNI() + + groups := []loadAssignmentEndpointGroup{{Endpoints: serviceGroup.Nodes, OnlyPassing: false}} + + la := makeLoadAssignment( + clusterName, + groups, + cfgSnap.Locality, + ) + resources = append(resources, la) + } + } + + return resources, nil +} + +func (s *ResourceGenerator) makePeerServerEndpointsForMeshGateway(cfgSnap *proxycfg.ConfigSnapshot) ([]proto.Message, error) { + resources := make([]proto.Message, 0, len(cfgSnap.MeshGateway.PeerServers)) + + // Peer server names are assumed to already be formatted in SNI notation: + // server..peering. + for name, servers := range cfgSnap.MeshGateway.PeerServers { + if servers.UseCDS || len(servers.Addresses) == 0 { + continue + } + + es := make([]*envoy_endpoint_v3.LbEndpoint, 0, len(servers.Addresses)) + + for _, address := range servers.Addresses { + es = append(es, makeEndpoint(address.Address, address.Port)) + } + + cla := &envoy_endpoint_v3.ClusterLoadAssignment{ + ClusterName: name, + Endpoints: []*envoy_endpoint_v3.LocalityLbEndpoints{ + { + LbEndpoints: es, + }, + }, + } + + resources = append(resources, cla) + } + + return resources, nil +} + func (s *ResourceGenerator) endpointsFromSnapshotIngressGateway(cfgSnap *proxycfg.ConfigSnapshot) ([]proto.Message, error) { var resources []proto.Message createdClusters := make(map[proxycfg.UpstreamID]bool) @@ -416,6 +546,25 @@ func (s *ResourceGenerator) makeUpstreamLoadAssignmentForPeerService(cfgSnap *pr return la, err } + upstream := cfgSnap.ConnectProxy.UpstreamConfig[uid] + // If an upstream is configured with local mesh gw mode, we make a load assignment + // from the gateway endpoints instead of those of the upstreams. + if upstream != nil && upstream.MeshGateway.Mode == structs.MeshGatewayModeLocal { + localGw, ok := cfgSnap.ConnectProxy.WatchedLocalGWEndpoints.Get(cfgSnap.Locality.String()) + if !ok { + // local GW is not ready; return early + return la, nil + } + la = makeLoadAssignment( + clusterName, + []loadAssignmentEndpointGroup{ + {Endpoints: localGw}, + }, + cfgSnap.Locality, + ) + return la, nil + } + // Also skip peer instances with a hostname as their address. EDS // cannot resolve hostnames, so we provide them through CDS instead. if _, ok := upstreamsSnapshot.PeerUpstreamEndpointsUseHostnames[uid]; ok { diff --git a/agent/xds/envoy_versioning_test.go b/agent/xds/envoy_versioning_test.go index b0e9c0dba1..6fc1e57eae 100644 --- a/agent/xds/envoy_versioning_test.go +++ b/agent/xds/envoy_versioning_test.go @@ -135,9 +135,9 @@ func TestDetermineSupportedProxyFeaturesFromString(t *testing.T) { } */ for _, v := range []string{ - "1.20.0", "1.20.1", "1.20.2", "1.20.3", "1.20.4", "1.20.5", "1.20.6", - "1.21.0", "1.21.1", "1.21.2", "1.21.3", "1.21.4", - "1.22.0", "1.22.1", "1.22.2", + "1.20.0", "1.20.1", "1.20.2", "1.20.3", "1.20.4", "1.20.5", "1.20.6", "1.20.7", + "1.21.0", "1.21.1", "1.21.2", "1.21.3", "1.21.4", "1.21.5", + "1.22.0", "1.22.1", "1.22.2", "1.22.3", "1.22.4", "1.22.5", "1.23.0", "1.23.1", } { cases[v] = testcase{expect: supportedProxyFeatures{}} diff --git a/agent/xds/listeners.go b/agent/xds/listeners.go index cfea25cbc1..4c23d07059 100644 --- a/agent/xds/listeners.go +++ b/agent/xds/listeners.go @@ -190,6 +190,7 @@ func (s *ResourceGenerator) listenersFromSnapshotConnectProxy(cfgSnap *proxycfg. } upstreamListener := makeListener(uid.EnvoyID(), upstreamCfg, envoy_core_v3.TrafficDirection_OUTBOUND) + s.injectConnectionBalanceConfig(cfg.BalanceOutboundConnections, upstreamListener) upstreamListener.FilterChains = []*envoy_listener_v3.FilterChain{ filterChain, } @@ -274,7 +275,7 @@ func (s *ResourceGenerator) listenersFromSnapshotConnectProxy(cfgSnap *proxycfg. return nil } configuredPorts[svcConfig.Destination.Port] = struct{}{} - const name = "~http" //name used for the shared route name + const name = "~http" // name used for the shared route name routeName := clusterNameForDestination(cfgSnap, name, fmt.Sprintf("%d", svcConfig.Destination.Port), svcConfig.NamespaceOrDefault(), svcConfig.PartitionOrDefault()) filterChain, err := s.makeUpstreamFilterChain(filterChainOpts{ routeName: routeName, @@ -385,6 +386,8 @@ func (s *ResourceGenerator) listenersFromSnapshotConnectProxy(cfgSnap *proxycfg. } upstreamListener := makeListener(uid.EnvoyID(), upstreamCfg, envoy_core_v3.TrafficDirection_OUTBOUND) + s.injectConnectionBalanceConfig(cfg.BalanceOutboundConnections, upstreamListener) + upstreamListener.FilterChains = []*envoy_listener_v3.FilterChain{ filterChain, } @@ -559,6 +562,7 @@ func (s *ResourceGenerator) listenersFromSnapshotConnectProxy(cfgSnap *proxycfg. } upstreamListener := makeListener(uid.EnvoyID(), u, envoy_core_v3.TrafficDirection_OUTBOUND) + s.injectConnectionBalanceConfig(cfg.BalanceOutboundConnections, upstreamListener) filterChain, err := s.makeUpstreamFilterChain(filterChainOpts{ // TODO (SNI partition) add partition for upstream SNI @@ -905,6 +909,19 @@ func makeListenerFromUserConfig(configJSON string) (*envoy_listener_v3.Listener, return &l, nil } +func (s *ResourceGenerator) injectConnectionBalanceConfig(balanceType string, listener *envoy_listener_v3.Listener) { + switch balanceType { + case "": + // Default with no balancing. + case structs.ConnectionExactBalance: + listener.ConnectionBalanceConfig = &envoy_listener_v3.Listener_ConnectionBalanceConfig{ + BalanceType: &envoy_listener_v3.Listener_ConnectionBalanceConfig_ExactBalance_{}, + } + default: + s.Logger.Warn("ignoring invalid connection balance option", "value", balanceType) + } +} + // Ensure that the first filter in each filter chain of a public listener is // the authz filter to prevent unauthorized access. func (s *ResourceGenerator) injectConnectFilters(cfgSnap *proxycfg.ConfigSnapshot, listener *envoy_listener_v3.Listener) error { @@ -1056,6 +1073,19 @@ func (s *ResourceGenerator) injectConnectTLSForPublicListener(cfgSnap *proxycfg. return nil } +func getAlpnProtocols(protocol string) []string { + var alpnProtocols []string + + switch protocol { + case "grpc", "http2": + alpnProtocols = append(alpnProtocols, "h2", "http/1.1") + case "http": + alpnProtocols = append(alpnProtocols, "http/1.1") + } + + return alpnProtocols +} + func createDownstreamTransportSocketForConnectTLS(cfgSnap *proxycfg.ConfigSnapshot, peerBundles []*pbpeering.PeeringTrustBundle) (*envoy_core_v3.TransportSocket, error) { switch cfgSnap.Kind { case structs.ServiceKindConnectProxy: @@ -1064,6 +1094,10 @@ func createDownstreamTransportSocketForConnectTLS(cfgSnap *proxycfg.ConfigSnapsh return nil, fmt.Errorf("cannot inject peering trust bundles for kind %q", cfgSnap.Kind) } + // Determine listener protocol type from configured service protocol. Don't hard fail on a config typo, + //The parse func returns default config if there is an error, so it's safe to continue. + cfg, _ := ParseProxyConfig(cfgSnap.Proxy.Config) + // Create TLS validation context for mTLS with leaf certificate and root certs. tlsContext := makeCommonTLSContext( cfgSnap.Leaf(), @@ -1071,6 +1105,11 @@ func createDownstreamTransportSocketForConnectTLS(cfgSnap *proxycfg.ConfigSnapsh makeTLSParametersFromProxyTLSConfig(cfgSnap.MeshConfigTLSIncoming()), ) + if tlsContext != nil { + // Configure alpn protocols on CommonTLSContext + tlsContext.AlpnProtocols = getAlpnProtocols(cfg.Protocol) + } + // Inject peering trust bundles if this service is exported to peered clusters. if len(peerBundles) > 0 { spiffeConfig, err := makeSpiffeValidatorConfig( @@ -1221,6 +1260,7 @@ func (s *ResourceGenerator) makeInboundListener(cfgSnap *proxycfg.ConfigSnapshot } l = makePortListener(name, addr, port, envoy_core_v3.TrafficDirection_INBOUND) + s.injectConnectionBalanceConfig(cfg.BalanceInboundConnections, l) var tracing *envoy_http_v3.HttpConnectionManager_Tracing if cfg.ListenerTracingJSON != "" { @@ -1713,6 +1753,10 @@ func (s *ResourceGenerator) makeMeshGatewayListener(name, addr string, port int, }) } + // -------- + // WAN Federation over mesh gateways + // -------- + if cfgSnap.ProxyID.InDefaultPartition() && cfgSnap.ServiceMeta[structs.MetaWANFederationKey] == "1" && cfgSnap.ServerSNIFn != nil { @@ -1739,7 +1783,8 @@ func (s *ResourceGenerator) makeMeshGatewayListener(name, addr string, port int, } // Wildcard all flavors to each server. - for _, srv := range cfgSnap.MeshGateway.ConsulServers { + servers, _ := cfgSnap.MeshGateway.WatchedLocalServers.Get(structs.ConsulServiceName) + for _, srv := range servers { clusterName := cfgSnap.ServerSNIFn(cfgSnap.Datacenter, srv.Node.Node) filterName := fmt.Sprintf("%s.%s", name, cfgSnap.Datacenter) @@ -1750,7 +1795,7 @@ func (s *ResourceGenerator) makeMeshGatewayListener(name, addr string, port int, l.FilterChains = append(l.FilterChains, &envoy_listener_v3.FilterChain{ FilterChainMatch: &envoy_listener_v3.FilterChainMatch{ - ServerNames: []string{fmt.Sprintf("%s", clusterName)}, + ServerNames: []string{clusterName}, }, Filters: []*envoy_listener_v3.Filter{ dcTCPProxy, @@ -1759,6 +1804,61 @@ func (s *ResourceGenerator) makeMeshGatewayListener(name, addr string, port int, } } + // -------- + // Peering control plane + // -------- + + // Create a single filter chain for local servers to be dialed by peers. + // When peering through gateways we load balance across the local servers. They cannot be addressed individually. + if cfgSnap.MeshConfig().PeerThroughMeshGateways() { + servers, _ := cfgSnap.MeshGateway.WatchedLocalServers.Get(structs.ConsulServiceName) + + // Peering control-plane traffic can only ever be handled by the local leader. + // We avoid routing to read replicas since they will never be Raft voters. + if haveVoters(servers) { + clusterName := connect.PeeringServerSAN(cfgSnap.Datacenter, cfgSnap.Roots.TrustDomain) + filterName := fmt.Sprintf("%s.%s", name, cfgSnap.Datacenter) + + filter, err := makeTCPProxyFilter(filterName, clusterName, "mesh_gateway_local_peering_server.") + if err != nil { + return nil, err + } + + l.FilterChains = append(l.FilterChains, &envoy_listener_v3.FilterChain{ + FilterChainMatch: &envoy_listener_v3.FilterChainMatch{ + ServerNames: []string{clusterName}, + }, + Filters: []*envoy_listener_v3.Filter{ + filter, + }, + }) + } + } + + // Create a filter chain per outbound peer server cluster. Listen for the SNI provided + // as the peer's ServerName. + var peerServerFilterChains []*envoy_listener_v3.FilterChain + for name := range cfgSnap.MeshGateway.PeerServers { + + dcTCPProxy, err := makeTCPProxyFilter(name, name, "mesh_gateway_remote_peering_servers.") + if err != nil { + return nil, err + } + + peerServerFilterChains = append(peerServerFilterChains, &envoy_listener_v3.FilterChain{ + FilterChainMatch: makeSNIFilterChainMatch(name), + Filters: []*envoy_listener_v3.Filter{ + dcTCPProxy, + }, + }) + } + + // Sort so the output is stable and the listener doesn't get drained + sort.Slice(peerServerFilterChains, func(i, j int) bool { + return peerServerFilterChains[i].FilterChainMatch.ServerNames[0] < peerServerFilterChains[j].FilterChainMatch.ServerNames[0] + }) + l.FilterChains = append(l.FilterChains, peerServerFilterChains...) + // This needs to get tacked on at the end as it has no // matching and will act as a catch all l.FilterChains = append(l.FilterChains, sniClusterChain) @@ -1998,6 +2098,10 @@ func (s *ResourceGenerator) getAndModifyUpstreamConfigForPeeredListener( cfg.ConnectTimeoutMs = 5000 } + if cfg.MeshGateway.Mode == "" && u != nil { + cfg.MeshGateway = u.MeshGateway + } + return cfg } diff --git a/agent/xds/listeners_ingress.go b/agent/xds/listeners_ingress.go index ba2019b435..2b9be1de1b 100644 --- a/agent/xds/listeners_ingress.go +++ b/agent/xds/listeners_ingress.go @@ -149,6 +149,9 @@ func makeDownstreamTLSContextFromSnapshotListenerConfig(cfgSnap *proxycfg.Config } if tlsContext != nil { + // Configure alpn protocols on TLSContext + tlsContext.AlpnProtocols = getAlpnProtocols(listenerCfg.Protocol) + downstreamContext = &envoy_tls_v3.DownstreamTlsContext{ CommonTlsContext: tlsContext, RequireClientCertificate: &wrappers.BoolValue{Value: false}, @@ -325,8 +328,14 @@ func makeSDSOverrideFilterChains(cfgSnap *proxycfg.ConfigSnapshot, return nil, err } + commonTlsContext := makeCommonTLSContextFromGatewayServiceTLSConfig(*svc.TLS) + if commonTlsContext != nil { + // Configure alpn protocols on TLSContext + commonTlsContext.AlpnProtocols = getAlpnProtocols(listenerCfg.Protocol) + } + tlsContext := &envoy_tls_v3.DownstreamTlsContext{ - CommonTlsContext: makeCommonTLSContextFromGatewayServiceTLSConfig(*svc.TLS), + CommonTlsContext: commonTlsContext, RequireClientCertificate: &wrappers.BoolValue{Value: false}, } diff --git a/agent/xds/listeners_test.go b/agent/xds/listeners_test.go index 1112222f3f..ed9dca5a9f 100644 --- a/agent/xds/listeners_test.go +++ b/agent/xds/listeners_test.go @@ -7,6 +7,8 @@ import ( "testing" "text/template" + "github.com/stretchr/testify/assert" + envoy_listener_v3 "github.com/envoyproxy/go-control-plane/envoy/config/listener/v3" testinf "github.com/mitchellh/go-testing-interface" "github.com/stretchr/testify/require" @@ -116,6 +118,14 @@ func TestListenersFromSnapshot(t *testing.T) { }) }, }, + { + name: "grpc-public-listener", + create: func(t testinf.T) *proxycfg.ConfigSnapshot { + return proxycfg.TestConfigSnapshot(t, func(ns *structs.NodeService) { + ns.Proxy.Config["protocol"] = "grpc" + }, nil) + }, + }, { name: "listener-bind-address", create: func(t testinf.T) *proxycfg.ConfigSnapshot { @@ -160,6 +170,30 @@ func TestListenersFromSnapshot(t *testing.T) { }, nil) }, }, + { + name: "http2-public-listener", + create: func(t testinf.T) *proxycfg.ConfigSnapshot { + return proxycfg.TestConfigSnapshot(t, func(ns *structs.NodeService) { + ns.Proxy.Config["protocol"] = "http2" + }, nil) + }, + }, + { + name: "listener-balance-inbound-connections", + create: func(t testinf.T) *proxycfg.ConfigSnapshot { + return proxycfg.TestConfigSnapshot(t, func(ns *structs.NodeService) { + ns.Proxy.Config["balance_inbound_connections"] = "exact_balance" + }, nil) + }, + }, + { + name: "listener-balance-outbound-connections-bind-port", + create: func(t testinf.T) *proxycfg.ConfigSnapshot { + return proxycfg.TestConfigSnapshot(t, func(ns *structs.NodeService) { + ns.Proxy.Upstreams[0].Config["balance_outbound_connections"] = "exact_balance" + }, nil) + }, + }, { name: "http-public-listener", create: func(t testinf.T) *proxycfg.ConfigSnapshot { @@ -756,10 +790,30 @@ func TestListenersFromSnapshot(t *testing.T) { name: "ingress-with-sds-service-level-mixed-no-tls", create: proxycfg.TestConfigSnapshotIngressGatewaySDS_MixedNoTLS, }, + { + name: "ingress-with-grpc-single-tls-listener", + create: proxycfg.TestConfigSnapshotIngressGateway_SingleTLSListener_GRPC, + }, + { + name: "ingress-with-http2-single-tls-listener", + create: proxycfg.TestConfigSnapshotIngressGateway_SingleTLSListener_HTTP2, + }, + { + name: "ingress-with-http2-and-grpc-multiple-tls-listener", + create: proxycfg.TestConfigSnapshotIngressGateway_MultiTLSListener_MixedHTTP2gRPC, + }, + { + name: "ingress-with-http2-and-grpc-multiple-tls-listener", + create: proxycfg.TestConfigSnapshotIngressGateway_GWTLSListener_MixedHTTP2gRPC, + }, { name: "transparent-proxy-http-upstream", create: proxycfg.TestConfigSnapshotTransparentProxyHTTPUpstream, }, + { + name: "transparent-proxy-with-resolver-redirect-upstream", + create: proxycfg.TestConfigSnapshotTransparentProxyResolverRedirectUpstream, + }, { name: "transparent-proxy-catalog-destinations-only", create: proxycfg.TestConfigSnapshotTransparentProxyCatalogDestinationsOnly, @@ -1158,3 +1212,38 @@ func TestResolveListenerSDSConfig(t *testing.T) { } } + +func TestGetAlpnProtocols(t *testing.T) { + tests := map[string]struct { + protocol string + want []string + }{ + "http": { + protocol: "http", + want: []string{"http/1.1"}, + }, + "http2": { + protocol: "http2", + want: []string{"h2", "http/1.1"}, + }, + "grpc": { + protocol: "grpc", + want: []string{"h2", "http/1.1"}, + }, + "tcp": { + protocol: "", + want: nil, + }, + "empty": { + protocol: "", + want: nil, + }, + } + + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + got := getAlpnProtocols(tc.protocol) + assert.Equal(t, tc.want, got) + }) + } +} diff --git a/agent/xds/proxysupport/proxysupport.go b/agent/xds/proxysupport/proxysupport.go index 80befe05ca..97981197d7 100644 --- a/agent/xds/proxysupport/proxysupport.go +++ b/agent/xds/proxysupport/proxysupport.go @@ -8,7 +8,7 @@ package proxysupport // see: https://www.consul.io/docs/connect/proxies/envoy#supported-versions var EnvoyVersions = []string{ "1.23.1", - "1.22.2", - "1.21.4", - "1.20.6", + "1.22.5", + "1.21.5", + "1.20.7", } diff --git a/agent/xds/resources_test.go b/agent/xds/resources_test.go index 53274d7193..9c707cd5b4 100644 --- a/agent/xds/resources_test.go +++ b/agent/xds/resources_test.go @@ -156,6 +156,10 @@ func TestAllResourcesFromSnapshot(t *testing.T) { name: "transparent-proxy-with-peered-upstreams", create: proxycfg.TestConfigSnapshotPeeringTProxy, }, + { + name: "local-mesh-gateway-with-peered-upstreams", + create: proxycfg.TestConfigSnapshotPeeringLocalMeshGateway, + }, } tests = append(tests, getConnectProxyTransparentProxyGoldenTestCases()...) tests = append(tests, getMeshGatewayPeeringGoldenTestCases()...) @@ -215,6 +219,24 @@ func getMeshGatewayPeeringGoldenTestCases() []goldenTestCase { return proxycfg.TestConfigSnapshotPeeredMeshGateway(t, "chain-and-l7-stuff", nil, nil) }, }, + { + name: "mesh-gateway-peering-control-plane", + create: func(t testinf.T) *proxycfg.ConfigSnapshot { + return proxycfg.TestConfigSnapshotPeeredMeshGateway(t, "control-plane", nil, nil) + }, + }, + { + name: "mesh-gateway-with-imported-peered-services", + create: func(t testinf.T) *proxycfg.ConfigSnapshot { + return proxycfg.TestConfigSnapshotPeeredMeshGateway(t, "imported-services", nil, nil) + }, + }, + { + name: "mesh-gateway-with-peer-through-mesh-gateway-enabled", + create: func(t testinf.T) *proxycfg.ConfigSnapshot { + return proxycfg.TestConfigSnapshotPeeredMeshGateway(t, "peer-through-mesh-gateway", nil, nil) + }, + }, } } diff --git a/agent/xds/routes.go b/agent/xds/routes.go index 9bbf3ff712..0ca73f6fd2 100644 --- a/agent/xds/routes.go +++ b/agent/xds/routes.go @@ -578,25 +578,7 @@ func (s *ResourceGenerator) makeUpstreamRouteForDiscoveryChain( } if destination.HasRetryFeatures() { - retryPolicy := &envoy_route_v3.RetryPolicy{} - if destination.NumRetries > 0 { - retryPolicy.NumRetries = makeUint32Value(int(destination.NumRetries)) - } - - // The RetryOn magic values come from: https://www.envoyproxy.io/docs/envoy/v1.10.0/configuration/http_filters/router_filter#config-http-filters-router-x-envoy-retry-on - if destination.RetryOnConnectFailure { - retryPolicy.RetryOn = "connect-failure" - } - if len(destination.RetryOnStatusCodes) > 0 { - if retryPolicy.RetryOn != "" { - retryPolicy.RetryOn = retryPolicy.RetryOn + ",retriable-status-codes" - } else { - retryPolicy.RetryOn = "retriable-status-codes" - } - retryPolicy.RetriableStatusCodes = destination.RetryOnStatusCodes - } - - routeAction.Route.RetryPolicy = retryPolicy + routeAction.Route.RetryPolicy = getRetryPolicyForDestination(destination) } if err := injectHeaderManipToRoute(destination, route); err != nil { @@ -663,6 +645,43 @@ func (s *ResourceGenerator) makeUpstreamRouteForDiscoveryChain( return host, nil } +func getRetryPolicyForDestination(destination *structs.ServiceRouteDestination) *envoy_route_v3.RetryPolicy { + retryPolicy := &envoy_route_v3.RetryPolicy{} + if destination.NumRetries > 0 { + retryPolicy.NumRetries = makeUint32Value(int(destination.NumRetries)) + } + + // The RetryOn magic values come from: https://www.envoyproxy.io/docs/envoy/v1.10.0/configuration/http_filters/router_filter#config-http-filters-router-x-envoy-retry-on + var retryStrings []string + + if len(destination.RetryOn) > 0 { + retryStrings = append(retryStrings, destination.RetryOn...) + } + + if destination.RetryOnConnectFailure { + // connect-failure can be enabled by either adding connect-failure to the RetryOn list or by using the legacy RetryOnConnectFailure option + // Check that it's not already in the RetryOn list, so we don't set it twice + connectFailureExists := false + for _, r := range retryStrings { + if r == "connect-failure" { + connectFailureExists = true + } + } + if !connectFailureExists { + retryStrings = append(retryStrings, "connect-failure") + } + } + + if len(destination.RetryOnStatusCodes) > 0 { + retryStrings = append(retryStrings, "retriable-status-codes") + retryPolicy.RetriableStatusCodes = destination.RetryOnStatusCodes + } + + retryPolicy.RetryOn = strings.Join(retryStrings, ",") + + return retryPolicy +} + func makeRouteMatchForDiscoveryRoute(discoveryRoute *structs.DiscoveryRoute) *envoy_route_v3.RouteMatch { match := discoveryRoute.Definition.Match if match == nil || match.IsEmpty() { diff --git a/agent/xds/server.go b/agent/xds/server.go index 74f386fc54..7252a6b877 100644 --- a/agent/xds/server.go +++ b/agent/xds/server.go @@ -204,7 +204,12 @@ func (s *Server) Register(srv *grpc.Server) { } func (s *Server) authenticate(ctx context.Context) (acl.Authorizer, error) { - authz, err := s.ResolveToken(external.TokenFromContext(ctx)) + options, err := external.QueryOptionsFromContext(ctx) + if err != nil { + return nil, status.Errorf(codes.Internal, "error fetching options from context: %v", err) + } + + authz, err := s.ResolveToken(options.Token) if acl.IsErrNotFound(err) { return nil, status.Errorf(codes.Unauthenticated, "unauthenticated: %v", err) } else if acl.IsErrPermissionDenied(err) { diff --git a/agent/xds/testdata/clusters/connect-proxy-with-chain-and-failover-to-cluster-peer.latest.golden b/agent/xds/testdata/clusters/connect-proxy-with-chain-and-failover-to-cluster-peer.latest.golden index 61de6b2e29..78d23803d1 100644 --- a/agent/xds/testdata/clusters/connect-proxy-with-chain-and-failover-to-cluster-peer.latest.golden +++ b/agent/xds/testdata/clusters/connect-proxy-with-chain-and-failover-to-cluster-peer.latest.golden @@ -66,12 +66,12 @@ }, "matchSubjectAltNames": [ { - "exact": "spiffe://1c053652-8512-4373-90cf-5a7f6263a994.consul/ns/default/dc/cluster-01-dc/svc/payments" + "exact": "spiffe://1c053652-8512-4373-90cf-5a7f6263a994.consul/ns/default/dc/cluster-01-dc/svc/db" } ] } }, - "sni": "payments.default.default.cluster-01.external.1c053652-8512-4373-90cf-5a7f6263a994.consul" + "sni": "db.default.default.cluster-01.external.1c053652-8512-4373-90cf-5a7f6263a994.consul" } } }, diff --git a/agent/xds/testdata/clusters/connect-proxy-with-chain-and-redirect-to-cluster-peer.latest.golden b/agent/xds/testdata/clusters/connect-proxy-with-chain-and-redirect-to-cluster-peer.latest.golden index 0bd578a1c0..a797d7e891 100644 --- a/agent/xds/testdata/clusters/connect-proxy-with-chain-and-redirect-to-cluster-peer.latest.golden +++ b/agent/xds/testdata/clusters/connect-proxy-with-chain-and-redirect-to-cluster-peer.latest.golden @@ -49,12 +49,12 @@ }, "matchSubjectAltNames": [ { - "exact": "spiffe://1c053652-8512-4373-90cf-5a7f6263a994.consul/ns/default/dc/cluster-01-dc/svc/payments" + "exact": "spiffe://1c053652-8512-4373-90cf-5a7f6263a994.consul/ns/default/dc/cluster-01-dc/svc/db" } ] } }, - "sni": "payments.default.default.cluster-01.external.1c053652-8512-4373-90cf-5a7f6263a994.consul" + "sni": "db.default.default.cluster-01.external.1c053652-8512-4373-90cf-5a7f6263a994.consul" } } }, diff --git a/agent/xds/testdata/clusters/ingress-with-chain-and-failover-to-cluster-peer.latest.golden b/agent/xds/testdata/clusters/ingress-with-chain-and-failover-to-cluster-peer.latest.golden index 94521dc8f6..5f75c93a3d 100644 --- a/agent/xds/testdata/clusters/ingress-with-chain-and-failover-to-cluster-peer.latest.golden +++ b/agent/xds/testdata/clusters/ingress-with-chain-and-failover-to-cluster-peer.latest.golden @@ -66,12 +66,12 @@ }, "matchSubjectAltNames": [ { - "exact": "spiffe://1c053652-8512-4373-90cf-5a7f6263a994.consul/ns/default/dc/cluster-01-dc/svc/payments" + "exact": "spiffe://1c053652-8512-4373-90cf-5a7f6263a994.consul/ns/default/dc/cluster-01-dc/svc/db" } ] } }, - "sni": "payments.default.default.cluster-01.external.1c053652-8512-4373-90cf-5a7f6263a994.consul" + "sni": "db.default.default.cluster-01.external.1c053652-8512-4373-90cf-5a7f6263a994.consul" } } }, diff --git a/agent/xds/testdata/clusters/ingress-with-defaults-service-max-connections.latest.golden b/agent/xds/testdata/clusters/ingress-with-defaults-service-max-connections.latest.golden new file mode 100644 index 0000000000..24bb5835d7 --- /dev/null +++ b/agent/xds/testdata/clusters/ingress-with-defaults-service-max-connections.latest.golden @@ -0,0 +1,71 @@ +{ + "versionInfo": "00000001", + "resources": [ + { + "@type": "type.googleapis.com/envoy.config.cluster.v3.Cluster", + "name": "db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul", + "altStatName": "db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul", + "type": "EDS", + "edsClusterConfig": { + "edsConfig": { + "ads": { + + }, + "resourceApiVersion": "V3" + } + }, + "connectTimeout": "33s", + "circuitBreakers": { + "thresholds": [ + { + "maxConnections": 2048, + "maxPendingRequests": 512, + "maxRequests": 4096 + } + ] + }, + "outlierDetection": { + + }, + "commonLbConfig": { + "healthyPanicThreshold": { + + } + }, + "transportSocket": { + "name": "tls", + "typedConfig": { + "@type": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext", + "commonTlsContext": { + "tlsParams": { + + }, + "tlsCertificates": [ + { + "certificateChain": { + "inlineString": "-----BEGIN CERTIFICATE-----\nMIICjDCCAjKgAwIBAgIIC5llxGV1gB8wCgYIKoZIzj0EAwIwFDESMBAGA1UEAxMJ\nVGVzdCBDQSAyMB4XDTE5MDMyMjEzNTgyNloXDTI5MDMyMjEzNTgyNlowDjEMMAoG\nA1UEAxMDd2ViMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEADPv1RHVNRfa2VKR\nAB16b6rZnEt7tuhaxCFpQXPj7M2omb0B9Favq5E0ivpNtv1QnFhxtPd7d5k4e+T7\nSkW1TaOCAXIwggFuMA4GA1UdDwEB/wQEAwIDuDAdBgNVHSUEFjAUBggrBgEFBQcD\nAgYIKwYBBQUHAwEwDAYDVR0TAQH/BAIwADBoBgNVHQ4EYQRfN2Q6MDc6ODc6M2E6\nNDA6MTk6NDc6YzM6NWE6YzA6YmE6NjI6ZGY6YWY6NGI6ZDQ6MDU6MjU6NzY6M2Q6\nNWE6OGQ6MTY6OGQ6Njc6NWU6MmU6YTA6MzQ6N2Q6ZGM6ZmYwagYDVR0jBGMwYYBf\nZDE6MTE6MTE6YWM6MmE6YmE6OTc6YjI6M2Y6YWM6N2I6YmQ6ZGE6YmU6YjE6OGE6\nZmM6OWE6YmE6YjU6YmM6ODM6ZTc6NWU6NDE6NmY6ZjI6NzM6OTU6NTg6MGM6ZGIw\nWQYDVR0RBFIwUIZOc3BpZmZlOi8vMTExMTExMTEtMjIyMi0zMzMzLTQ0NDQtNTU1\nNTU1NTU1NTU1LmNvbnN1bC9ucy9kZWZhdWx0L2RjL2RjMS9zdmMvd2ViMAoGCCqG\nSM49BAMCA0gAMEUCIGC3TTvvjj76KMrguVyFf4tjOqaSCRie3nmHMRNNRav7AiEA\npY0heYeK9A6iOLrzqxSerkXXQyj5e9bE4VgUnxgPU6g=\n-----END CERTIFICATE-----\n" + }, + "privateKey": { + "inlineString": "-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEIMoTkpRggp3fqZzFKh82yS4LjtJI+XY+qX/7DefHFrtdoAoGCCqGSM49\nAwEHoUQDQgAEADPv1RHVNRfa2VKRAB16b6rZnEt7tuhaxCFpQXPj7M2omb0B9Fav\nq5E0ivpNtv1QnFhxtPd7d5k4e+T7SkW1TQ==\n-----END EC PRIVATE KEY-----\n" + } + } + ], + "validationContext": { + "trustedCa": { + "inlineString": "-----BEGIN CERTIFICATE-----\nMIICXDCCAgKgAwIBAgIICpZq70Z9LyUwCgYIKoZIzj0EAwIwFDESMBAGA1UEAxMJ\nVGVzdCBDQSAyMB4XDTE5MDMyMjEzNTgyNloXDTI5MDMyMjEzNTgyNlowFDESMBAG\nA1UEAxMJVGVzdCBDQSAyMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEIhywH1gx\nAsMwuF3ukAI5YL2jFxH6Usnma1HFSfVyxbXX1/uoZEYrj8yCAtdU2yoHETyd+Zx2\nThhRLP79pYegCaOCATwwggE4MA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTAD\nAQH/MGgGA1UdDgRhBF9kMToxMToxMTphYzoyYTpiYTo5NzpiMjozZjphYzo3Yjpi\nZDpkYTpiZTpiMTo4YTpmYzo5YTpiYTpiNTpiYzo4MzplNzo1ZTo0MTo2ZjpmMjo3\nMzo5NTo1ODowYzpkYjBqBgNVHSMEYzBhgF9kMToxMToxMTphYzoyYTpiYTo5Nzpi\nMjozZjphYzo3YjpiZDpkYTpiZTpiMTo4YTpmYzo5YTpiYTpiNTpiYzo4MzplNzo1\nZTo0MTo2ZjpmMjo3Mzo5NTo1ODowYzpkYjA/BgNVHREEODA2hjRzcGlmZmU6Ly8x\nMTExMTExMS0yMjIyLTMzMzMtNDQ0NC01NTU1NTU1NTU1NTUuY29uc3VsMAoGCCqG\nSM49BAMCA0gAMEUCICOY0i246rQHJt8o8Oya0D5PLL1FnmsQmQqIGCi31RwnAiEA\noR5f6Ku+cig2Il8T8LJujOp2/2A72QcHZA57B13y+8o=\n-----END CERTIFICATE-----\n" + }, + "matchSubjectAltNames": [ + { + "exact": "spiffe://11111111-2222-3333-4444-555555555555.consul/ns/default/dc/dc1/svc/db" + } + ] + } + }, + "sni": "db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul" + } + } + } + ], + "typeUrl": "type.googleapis.com/envoy.config.cluster.v3.Cluster", + "nonce": "00000001" +} \ No newline at end of file diff --git a/agent/xds/testdata/clusters/ingress-with-overwrite-defaults-service-max-connections.latest.golden b/agent/xds/testdata/clusters/ingress-with-overwrite-defaults-service-max-connections.latest.golden new file mode 100644 index 0000000000..2ea7759503 --- /dev/null +++ b/agent/xds/testdata/clusters/ingress-with-overwrite-defaults-service-max-connections.latest.golden @@ -0,0 +1,70 @@ +{ + "versionInfo": "00000001", + "resources": [ + { + "@type": "type.googleapis.com/envoy.config.cluster.v3.Cluster", + "name": "db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul", + "altStatName": "db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul", + "type": "EDS", + "edsClusterConfig": { + "edsConfig": { + "ads": { + + }, + "resourceApiVersion": "V3" + } + }, + "connectTimeout": "33s", + "circuitBreakers": { + "thresholds": [ + { + "maxConnections": 4096, + "maxPendingRequests": 2048 + } + ] + }, + "outlierDetection": { + + }, + "commonLbConfig": { + "healthyPanicThreshold": { + + } + }, + "transportSocket": { + "name": "tls", + "typedConfig": { + "@type": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext", + "commonTlsContext": { + "tlsParams": { + + }, + "tlsCertificates": [ + { + "certificateChain": { + "inlineString": "-----BEGIN CERTIFICATE-----\nMIICjDCCAjKgAwIBAgIIC5llxGV1gB8wCgYIKoZIzj0EAwIwFDESMBAGA1UEAxMJ\nVGVzdCBDQSAyMB4XDTE5MDMyMjEzNTgyNloXDTI5MDMyMjEzNTgyNlowDjEMMAoG\nA1UEAxMDd2ViMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEADPv1RHVNRfa2VKR\nAB16b6rZnEt7tuhaxCFpQXPj7M2omb0B9Favq5E0ivpNtv1QnFhxtPd7d5k4e+T7\nSkW1TaOCAXIwggFuMA4GA1UdDwEB/wQEAwIDuDAdBgNVHSUEFjAUBggrBgEFBQcD\nAgYIKwYBBQUHAwEwDAYDVR0TAQH/BAIwADBoBgNVHQ4EYQRfN2Q6MDc6ODc6M2E6\nNDA6MTk6NDc6YzM6NWE6YzA6YmE6NjI6ZGY6YWY6NGI6ZDQ6MDU6MjU6NzY6M2Q6\nNWE6OGQ6MTY6OGQ6Njc6NWU6MmU6YTA6MzQ6N2Q6ZGM6ZmYwagYDVR0jBGMwYYBf\nZDE6MTE6MTE6YWM6MmE6YmE6OTc6YjI6M2Y6YWM6N2I6YmQ6ZGE6YmU6YjE6OGE6\nZmM6OWE6YmE6YjU6YmM6ODM6ZTc6NWU6NDE6NmY6ZjI6NzM6OTU6NTg6MGM6ZGIw\nWQYDVR0RBFIwUIZOc3BpZmZlOi8vMTExMTExMTEtMjIyMi0zMzMzLTQ0NDQtNTU1\nNTU1NTU1NTU1LmNvbnN1bC9ucy9kZWZhdWx0L2RjL2RjMS9zdmMvd2ViMAoGCCqG\nSM49BAMCA0gAMEUCIGC3TTvvjj76KMrguVyFf4tjOqaSCRie3nmHMRNNRav7AiEA\npY0heYeK9A6iOLrzqxSerkXXQyj5e9bE4VgUnxgPU6g=\n-----END CERTIFICATE-----\n" + }, + "privateKey": { + "inlineString": "-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEIMoTkpRggp3fqZzFKh82yS4LjtJI+XY+qX/7DefHFrtdoAoGCCqGSM49\nAwEHoUQDQgAEADPv1RHVNRfa2VKRAB16b6rZnEt7tuhaxCFpQXPj7M2omb0B9Fav\nq5E0ivpNtv1QnFhxtPd7d5k4e+T7SkW1TQ==\n-----END EC PRIVATE KEY-----\n" + } + } + ], + "validationContext": { + "trustedCa": { + "inlineString": "-----BEGIN CERTIFICATE-----\nMIICXDCCAgKgAwIBAgIICpZq70Z9LyUwCgYIKoZIzj0EAwIwFDESMBAGA1UEAxMJ\nVGVzdCBDQSAyMB4XDTE5MDMyMjEzNTgyNloXDTI5MDMyMjEzNTgyNlowFDESMBAG\nA1UEAxMJVGVzdCBDQSAyMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEIhywH1gx\nAsMwuF3ukAI5YL2jFxH6Usnma1HFSfVyxbXX1/uoZEYrj8yCAtdU2yoHETyd+Zx2\nThhRLP79pYegCaOCATwwggE4MA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTAD\nAQH/MGgGA1UdDgRhBF9kMToxMToxMTphYzoyYTpiYTo5NzpiMjozZjphYzo3Yjpi\nZDpkYTpiZTpiMTo4YTpmYzo5YTpiYTpiNTpiYzo4MzplNzo1ZTo0MTo2ZjpmMjo3\nMzo5NTo1ODowYzpkYjBqBgNVHSMEYzBhgF9kMToxMToxMTphYzoyYTpiYTo5Nzpi\nMjozZjphYzo3YjpiZDpkYTpiZTpiMTo4YTpmYzo5YTpiYTpiNTpiYzo4MzplNzo1\nZTo0MTo2ZjpmMjo3Mzo5NTo1ODowYzpkYjA/BgNVHREEODA2hjRzcGlmZmU6Ly8x\nMTExMTExMS0yMjIyLTMzMzMtNDQ0NC01NTU1NTU1NTU1NTUuY29uc3VsMAoGCCqG\nSM49BAMCA0gAMEUCICOY0i246rQHJt8o8Oya0D5PLL1FnmsQmQqIGCi31RwnAiEA\noR5f6Ku+cig2Il8T8LJujOp2/2A72QcHZA57B13y+8o=\n-----END CERTIFICATE-----\n" + }, + "matchSubjectAltNames": [ + { + "exact": "spiffe://11111111-2222-3333-4444-555555555555.consul/ns/default/dc/dc1/svc/db" + } + ] + } + }, + "sni": "db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul" + } + } + } + ], + "typeUrl": "type.googleapis.com/envoy.config.cluster.v3.Cluster", + "nonce": "00000001" +} \ No newline at end of file diff --git a/agent/xds/testdata/clusters/ingress-with-service-max-connections.latest.golden b/agent/xds/testdata/clusters/ingress-with-service-max-connections.latest.golden new file mode 100644 index 0000000000..3ac392d401 --- /dev/null +++ b/agent/xds/testdata/clusters/ingress-with-service-max-connections.latest.golden @@ -0,0 +1,69 @@ +{ + "versionInfo": "00000001", + "resources": [ + { + "@type": "type.googleapis.com/envoy.config.cluster.v3.Cluster", + "name": "db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul", + "altStatName": "db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul", + "type": "EDS", + "edsClusterConfig": { + "edsConfig": { + "ads": { + + }, + "resourceApiVersion": "V3" + } + }, + "connectTimeout": "33s", + "circuitBreakers": { + "thresholds": [ + { + "maxConnections": 4096 + } + ] + }, + "outlierDetection": { + + }, + "commonLbConfig": { + "healthyPanicThreshold": { + + } + }, + "transportSocket": { + "name": "tls", + "typedConfig": { + "@type": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext", + "commonTlsContext": { + "tlsParams": { + + }, + "tlsCertificates": [ + { + "certificateChain": { + "inlineString": "-----BEGIN CERTIFICATE-----\nMIICjDCCAjKgAwIBAgIIC5llxGV1gB8wCgYIKoZIzj0EAwIwFDESMBAGA1UEAxMJ\nVGVzdCBDQSAyMB4XDTE5MDMyMjEzNTgyNloXDTI5MDMyMjEzNTgyNlowDjEMMAoG\nA1UEAxMDd2ViMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEADPv1RHVNRfa2VKR\nAB16b6rZnEt7tuhaxCFpQXPj7M2omb0B9Favq5E0ivpNtv1QnFhxtPd7d5k4e+T7\nSkW1TaOCAXIwggFuMA4GA1UdDwEB/wQEAwIDuDAdBgNVHSUEFjAUBggrBgEFBQcD\nAgYIKwYBBQUHAwEwDAYDVR0TAQH/BAIwADBoBgNVHQ4EYQRfN2Q6MDc6ODc6M2E6\nNDA6MTk6NDc6YzM6NWE6YzA6YmE6NjI6ZGY6YWY6NGI6ZDQ6MDU6MjU6NzY6M2Q6\nNWE6OGQ6MTY6OGQ6Njc6NWU6MmU6YTA6MzQ6N2Q6ZGM6ZmYwagYDVR0jBGMwYYBf\nZDE6MTE6MTE6YWM6MmE6YmE6OTc6YjI6M2Y6YWM6N2I6YmQ6ZGE6YmU6YjE6OGE6\nZmM6OWE6YmE6YjU6YmM6ODM6ZTc6NWU6NDE6NmY6ZjI6NzM6OTU6NTg6MGM6ZGIw\nWQYDVR0RBFIwUIZOc3BpZmZlOi8vMTExMTExMTEtMjIyMi0zMzMzLTQ0NDQtNTU1\nNTU1NTU1NTU1LmNvbnN1bC9ucy9kZWZhdWx0L2RjL2RjMS9zdmMvd2ViMAoGCCqG\nSM49BAMCA0gAMEUCIGC3TTvvjj76KMrguVyFf4tjOqaSCRie3nmHMRNNRav7AiEA\npY0heYeK9A6iOLrzqxSerkXXQyj5e9bE4VgUnxgPU6g=\n-----END CERTIFICATE-----\n" + }, + "privateKey": { + "inlineString": "-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEIMoTkpRggp3fqZzFKh82yS4LjtJI+XY+qX/7DefHFrtdoAoGCCqGSM49\nAwEHoUQDQgAEADPv1RHVNRfa2VKRAB16b6rZnEt7tuhaxCFpQXPj7M2omb0B9Fav\nq5E0ivpNtv1QnFhxtPd7d5k4e+T7SkW1TQ==\n-----END EC PRIVATE KEY-----\n" + } + } + ], + "validationContext": { + "trustedCa": { + "inlineString": "-----BEGIN CERTIFICATE-----\nMIICXDCCAgKgAwIBAgIICpZq70Z9LyUwCgYIKoZIzj0EAwIwFDESMBAGA1UEAxMJ\nVGVzdCBDQSAyMB4XDTE5MDMyMjEzNTgyNloXDTI5MDMyMjEzNTgyNlowFDESMBAG\nA1UEAxMJVGVzdCBDQSAyMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEIhywH1gx\nAsMwuF3ukAI5YL2jFxH6Usnma1HFSfVyxbXX1/uoZEYrj8yCAtdU2yoHETyd+Zx2\nThhRLP79pYegCaOCATwwggE4MA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTAD\nAQH/MGgGA1UdDgRhBF9kMToxMToxMTphYzoyYTpiYTo5NzpiMjozZjphYzo3Yjpi\nZDpkYTpiZTpiMTo4YTpmYzo5YTpiYTpiNTpiYzo4MzplNzo1ZTo0MTo2ZjpmMjo3\nMzo5NTo1ODowYzpkYjBqBgNVHSMEYzBhgF9kMToxMToxMTphYzoyYTpiYTo5Nzpi\nMjozZjphYzo3YjpiZDpkYTpiZTpiMTo4YTpmYzo5YTpiYTpiNTpiYzo4MzplNzo1\nZTo0MTo2ZjpmMjo3Mzo5NTo1ODowYzpkYjA/BgNVHREEODA2hjRzcGlmZmU6Ly8x\nMTExMTExMS0yMjIyLTMzMzMtNDQ0NC01NTU1NTU1NTU1NTUuY29uc3VsMAoGCCqG\nSM49BAMCA0gAMEUCICOY0i246rQHJt8o8Oya0D5PLL1FnmsQmQqIGCi31RwnAiEA\noR5f6Ku+cig2Il8T8LJujOp2/2A72QcHZA57B13y+8o=\n-----END CERTIFICATE-----\n" + }, + "matchSubjectAltNames": [ + { + "exact": "spiffe://11111111-2222-3333-4444-555555555555.consul/ns/default/dc/dc1/svc/db" + } + ] + } + }, + "sni": "db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul" + } + } + } + ], + "typeUrl": "type.googleapis.com/envoy.config.cluster.v3.Cluster", + "nonce": "00000001" +} \ No newline at end of file diff --git a/agent/xds/testdata/clusters/local-mesh-gateway-with-peered-upstreams.latest.golden b/agent/xds/testdata/clusters/local-mesh-gateway-with-peered-upstreams.latest.golden new file mode 100644 index 0000000000..956c43e42f --- /dev/null +++ b/agent/xds/testdata/clusters/local-mesh-gateway-with-peered-upstreams.latest.golden @@ -0,0 +1,146 @@ +{ + "versionInfo": "00000001", + "resources": [ + { + "@type": "type.googleapis.com/envoy.config.cluster.v3.Cluster", + "name": "local_app", + "type": "STATIC", + "connectTimeout": "5s", + "loadAssignment": { + "clusterName": "local_app", + "endpoints": [ + { + "lbEndpoints": [ + { + "endpoint": { + "address": { + "socketAddress": { + "address": "127.0.0.1", + "portValue": 8080 + } + } + } + } + ] + } + ] + } + }, + { + "@type": "type.googleapis.com/envoy.config.cluster.v3.Cluster", + "name": "payments.default.cloud.external.1c053652-8512-4373-90cf-5a7f6263a994.consul", + "type": "EDS", + "edsClusterConfig": { + "edsConfig": { + "ads": { + + }, + "resourceApiVersion": "V3" + } + }, + "connectTimeout": "5s", + "circuitBreakers": { + + }, + "outlierDetection": { + "maxEjectionPercent": 100 + }, + "commonLbConfig": { + "healthyPanicThreshold": { + + } + }, + "transportSocket": { + "name": "tls", + "typedConfig": { + "@type": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext", + "commonTlsContext": { + "tlsParams": { + + }, + "tlsCertificates": [ + { + "certificateChain": { + "inlineString": "-----BEGIN CERTIFICATE-----\nMIICjDCCAjKgAwIBAgIIC5llxGV1gB8wCgYIKoZIzj0EAwIwFDESMBAGA1UEAxMJ\nVGVzdCBDQSAyMB4XDTE5MDMyMjEzNTgyNloXDTI5MDMyMjEzNTgyNlowDjEMMAoG\nA1UEAxMDd2ViMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEADPv1RHVNRfa2VKR\nAB16b6rZnEt7tuhaxCFpQXPj7M2omb0B9Favq5E0ivpNtv1QnFhxtPd7d5k4e+T7\nSkW1TaOCAXIwggFuMA4GA1UdDwEB/wQEAwIDuDAdBgNVHSUEFjAUBggrBgEFBQcD\nAgYIKwYBBQUHAwEwDAYDVR0TAQH/BAIwADBoBgNVHQ4EYQRfN2Q6MDc6ODc6M2E6\nNDA6MTk6NDc6YzM6NWE6YzA6YmE6NjI6ZGY6YWY6NGI6ZDQ6MDU6MjU6NzY6M2Q6\nNWE6OGQ6MTY6OGQ6Njc6NWU6MmU6YTA6MzQ6N2Q6ZGM6ZmYwagYDVR0jBGMwYYBf\nZDE6MTE6MTE6YWM6MmE6YmE6OTc6YjI6M2Y6YWM6N2I6YmQ6ZGE6YmU6YjE6OGE6\nZmM6OWE6YmE6YjU6YmM6ODM6ZTc6NWU6NDE6NmY6ZjI6NzM6OTU6NTg6MGM6ZGIw\nWQYDVR0RBFIwUIZOc3BpZmZlOi8vMTExMTExMTEtMjIyMi0zMzMzLTQ0NDQtNTU1\nNTU1NTU1NTU1LmNvbnN1bC9ucy9kZWZhdWx0L2RjL2RjMS9zdmMvd2ViMAoGCCqG\nSM49BAMCA0gAMEUCIGC3TTvvjj76KMrguVyFf4tjOqaSCRie3nmHMRNNRav7AiEA\npY0heYeK9A6iOLrzqxSerkXXQyj5e9bE4VgUnxgPU6g=\n-----END CERTIFICATE-----\n" + }, + "privateKey": { + "inlineString": "-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEIMoTkpRggp3fqZzFKh82yS4LjtJI+XY+qX/7DefHFrtdoAoGCCqGSM49\nAwEHoUQDQgAEADPv1RHVNRfa2VKRAB16b6rZnEt7tuhaxCFpQXPj7M2omb0B9Fav\nq5E0ivpNtv1QnFhxtPd7d5k4e+T7SkW1TQ==\n-----END EC PRIVATE KEY-----\n" + } + } + ], + "validationContext": { + "trustedCa": { + "inlineString": "-----BEGIN CERTIFICATE-----\nMIICczCCAdwCCQC3BLnEmLCrSjANBgkqhkiG9w0BAQsFADB+MQswCQYDVQQGEwJV\nUzELMAkGA1UECAwCQVoxEjAQBgNVBAcMCUZsYWdzdGFmZjEMMAoGA1UECgwDRm9v\nMRAwDgYDVQQLDAdleGFtcGxlMQ8wDQYDVQQDDAZwZWVyLWExHTAbBgkqhkiG9w0B\nCQEWDmZvb0BwZWVyLWEuY29tMB4XDTIyMDUyNjAxMDQ0NFoXDTIzMDUyNjAxMDQ0\nNFowfjELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkFaMRIwEAYDVQQHDAlGbGFnc3Rh\nZmYxDDAKBgNVBAoMA0ZvbzEQMA4GA1UECwwHZXhhbXBsZTEPMA0GA1UEAwwGcGVl\nci1hMR0wGwYJKoZIhvcNAQkBFg5mb29AcGVlci1hLmNvbTCBnzANBgkqhkiG9w0B\nAQEFAAOBjQAwgYkCgYEA2zFYGTbXDAntT5pLTpZ2+VTiqx4J63VRJH1kdu11f0FV\nc2jl1pqCuYDbQXknDU0Pv1Q5y0+nSAihD2KqGS571r+vHQiPtKYPYRqPEe9FzAhR\n2KhWH6v/tk5DG1HqOjV9/zWRKB12gdFNZZqnw/e7NjLNq3wZ2UAwxXip5uJ8uwMC\nAwEAATANBgkqhkiG9w0BAQsFAAOBgQC/CJ9Syf4aL91wZizKTejwouRYoWv4gRAk\nyto45ZcNMHfJ0G2z+XAMl9ZbQsLgXmzAx4IM6y5Jckq8pKC4PEijCjlKTktLHlEy\n0ggmFxtNB1tid2NC8dOzcQ3l45+gDjDqdILhAvLDjlAIebdkqVqb2CfFNW/I2CQH\nZAuKN1aoKA==\n-----END CERTIFICATE-----\n" + }, + "matchSubjectAltNames": [ + { + "exact": "spiffe://1c053652-8512-4373-90cf-5a7f6263a994.consul/ns/default/dc/cloud-dc/svc/payments" + } + ] + } + }, + "sni": "payments.default.default.cloud.external.1c053652-8512-4373-90cf-5a7f6263a994.consul" + } + } + }, + { + "@type": "type.googleapis.com/envoy.config.cluster.v3.Cluster", + "name": "refunds.default.cloud.external.1c053652-8512-4373-90cf-5a7f6263a994.consul", + "type": "EDS", + "edsClusterConfig": { + "edsConfig": { + "ads": { + + }, + "resourceApiVersion": "V3" + } + }, + "connectTimeout": "5s", + "circuitBreakers": { + + }, + "outlierDetection": { + "maxEjectionPercent": 100 + }, + "commonLbConfig": { + "healthyPanicThreshold": { + + } + }, + "transportSocket": { + "name": "tls", + "typedConfig": { + "@type": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext", + "commonTlsContext": { + "tlsParams": { + + }, + "tlsCertificates": [ + { + "certificateChain": { + "inlineString": "-----BEGIN CERTIFICATE-----\nMIICjDCCAjKgAwIBAgIIC5llxGV1gB8wCgYIKoZIzj0EAwIwFDESMBAGA1UEAxMJ\nVGVzdCBDQSAyMB4XDTE5MDMyMjEzNTgyNloXDTI5MDMyMjEzNTgyNlowDjEMMAoG\nA1UEAxMDd2ViMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEADPv1RHVNRfa2VKR\nAB16b6rZnEt7tuhaxCFpQXPj7M2omb0B9Favq5E0ivpNtv1QnFhxtPd7d5k4e+T7\nSkW1TaOCAXIwggFuMA4GA1UdDwEB/wQEAwIDuDAdBgNVHSUEFjAUBggrBgEFBQcD\nAgYIKwYBBQUHAwEwDAYDVR0TAQH/BAIwADBoBgNVHQ4EYQRfN2Q6MDc6ODc6M2E6\nNDA6MTk6NDc6YzM6NWE6YzA6YmE6NjI6ZGY6YWY6NGI6ZDQ6MDU6MjU6NzY6M2Q6\nNWE6OGQ6MTY6OGQ6Njc6NWU6MmU6YTA6MzQ6N2Q6ZGM6ZmYwagYDVR0jBGMwYYBf\nZDE6MTE6MTE6YWM6MmE6YmE6OTc6YjI6M2Y6YWM6N2I6YmQ6ZGE6YmU6YjE6OGE6\nZmM6OWE6YmE6YjU6YmM6ODM6ZTc6NWU6NDE6NmY6ZjI6NzM6OTU6NTg6MGM6ZGIw\nWQYDVR0RBFIwUIZOc3BpZmZlOi8vMTExMTExMTEtMjIyMi0zMzMzLTQ0NDQtNTU1\nNTU1NTU1NTU1LmNvbnN1bC9ucy9kZWZhdWx0L2RjL2RjMS9zdmMvd2ViMAoGCCqG\nSM49BAMCA0gAMEUCIGC3TTvvjj76KMrguVyFf4tjOqaSCRie3nmHMRNNRav7AiEA\npY0heYeK9A6iOLrzqxSerkXXQyj5e9bE4VgUnxgPU6g=\n-----END CERTIFICATE-----\n" + }, + "privateKey": { + "inlineString": "-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEIMoTkpRggp3fqZzFKh82yS4LjtJI+XY+qX/7DefHFrtdoAoGCCqGSM49\nAwEHoUQDQgAEADPv1RHVNRfa2VKRAB16b6rZnEt7tuhaxCFpQXPj7M2omb0B9Fav\nq5E0ivpNtv1QnFhxtPd7d5k4e+T7SkW1TQ==\n-----END EC PRIVATE KEY-----\n" + } + } + ], + "validationContext": { + "trustedCa": { + "inlineString": "-----BEGIN CERTIFICATE-----\nMIICczCCAdwCCQC3BLnEmLCrSjANBgkqhkiG9w0BAQsFADB+MQswCQYDVQQGEwJV\nUzELMAkGA1UECAwCQVoxEjAQBgNVBAcMCUZsYWdzdGFmZjEMMAoGA1UECgwDRm9v\nMRAwDgYDVQQLDAdleGFtcGxlMQ8wDQYDVQQDDAZwZWVyLWExHTAbBgkqhkiG9w0B\nCQEWDmZvb0BwZWVyLWEuY29tMB4XDTIyMDUyNjAxMDQ0NFoXDTIzMDUyNjAxMDQ0\nNFowfjELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkFaMRIwEAYDVQQHDAlGbGFnc3Rh\nZmYxDDAKBgNVBAoMA0ZvbzEQMA4GA1UECwwHZXhhbXBsZTEPMA0GA1UEAwwGcGVl\nci1hMR0wGwYJKoZIhvcNAQkBFg5mb29AcGVlci1hLmNvbTCBnzANBgkqhkiG9w0B\nAQEFAAOBjQAwgYkCgYEA2zFYGTbXDAntT5pLTpZ2+VTiqx4J63VRJH1kdu11f0FV\nc2jl1pqCuYDbQXknDU0Pv1Q5y0+nSAihD2KqGS571r+vHQiPtKYPYRqPEe9FzAhR\n2KhWH6v/tk5DG1HqOjV9/zWRKB12gdFNZZqnw/e7NjLNq3wZ2UAwxXip5uJ8uwMC\nAwEAATANBgkqhkiG9w0BAQsFAAOBgQC/CJ9Syf4aL91wZizKTejwouRYoWv4gRAk\nyto45ZcNMHfJ0G2z+XAMl9ZbQsLgXmzAx4IM6y5Jckq8pKC4PEijCjlKTktLHlEy\n0ggmFxtNB1tid2NC8dOzcQ3l45+gDjDqdILhAvLDjlAIebdkqVqb2CfFNW/I2CQH\nZAuKN1aoKA==\n-----END CERTIFICATE-----\n" + }, + "matchSubjectAltNames": [ + { + "exact": "spiffe://1c053652-8512-4373-90cf-5a7f6263a994.consul/ns/default/dc/cloud-dc/svc/refunds" + } + ] + } + }, + "sni": "refunds.default.default.cloud.external.1c053652-8512-4373-90cf-5a7f6263a994.consul" + } + } + } + ], + "typeUrl": "type.googleapis.com/envoy.config.cluster.v3.Cluster", + "nonce": "00000001" +} \ No newline at end of file diff --git a/agent/xds/testdata/clusters/mesh-gateway-peering-control-plane.latest.golden b/agent/xds/testdata/clusters/mesh-gateway-peering-control-plane.latest.golden new file mode 100644 index 0000000000..a16659b28a --- /dev/null +++ b/agent/xds/testdata/clusters/mesh-gateway-peering-control-plane.latest.golden @@ -0,0 +1,24 @@ +{ + "versionInfo": "00000001", + "resources": [ + { + "@type": "type.googleapis.com/envoy.config.cluster.v3.Cluster", + "name": "server.dc1.peering.11111111-2222-3333-4444-555555555555.consul", + "type": "EDS", + "edsClusterConfig": { + "edsConfig": { + "ads": { + + }, + "resourceApiVersion": "V3" + } + }, + "connectTimeout": "5s", + "outlierDetection": { + + } + } + ], + "typeUrl": "type.googleapis.com/envoy.config.cluster.v3.Cluster", + "nonce": "00000001" +} \ No newline at end of file diff --git a/agent/xds/testdata/clusters/mesh-gateway-with-imported-peered-services.latest.golden b/agent/xds/testdata/clusters/mesh-gateway-with-imported-peered-services.latest.golden new file mode 100644 index 0000000000..27761e8114 --- /dev/null +++ b/agent/xds/testdata/clusters/mesh-gateway-with-imported-peered-services.latest.golden @@ -0,0 +1,64 @@ +{ + "versionInfo": "00000001", + "resources": [ + { + "@type": "type.googleapis.com/envoy.config.cluster.v3.Cluster", + "name": "alt.default.default.peer-b.external.1c053652-8512-4373-90cf-5a7f6263a994.consul", + "type": "LOGICAL_DNS", + "edsClusterConfig": { + "edsConfig": { + "ads": { + + }, + "resourceApiVersion": "V3" + } + }, + "connectTimeout": "5s", + "loadAssignment": { + "clusterName": "alt.default.default.peer-b.external.1c053652-8512-4373-90cf-5a7f6263a994.consul", + "endpoints": [ + { + "lbEndpoints": [ + { + "endpoint": { + "address": { + "socketAddress": { + "address": "alt.us-east-1.elb.notaws.com", + "portValue": 8443 + } + } + }, + "healthStatus": "HEALTHY", + "loadBalancingWeight": 1 + } + ] + } + ] + }, + "dnsRefreshRate": "10s", + "dnsLookupFamily": "V4_ONLY", + "outlierDetection": { + + } + }, + { + "@type": "type.googleapis.com/envoy.config.cluster.v3.Cluster", + "name": "db.default.default.peer-a.external.1c053652-8512-4373-90cf-5a7f6263a994.consul", + "type": "EDS", + "edsClusterConfig": { + "edsConfig": { + "ads": { + + }, + "resourceApiVersion": "V3" + } + }, + "connectTimeout": "5s", + "outlierDetection": { + + } + } + ], + "typeUrl": "type.googleapis.com/envoy.config.cluster.v3.Cluster", + "nonce": "00000001" +} \ No newline at end of file diff --git a/agent/xds/testdata/clusters/mesh-gateway-with-peer-through-mesh-gateway-enabled.latest.golden b/agent/xds/testdata/clusters/mesh-gateway-with-peer-through-mesh-gateway-enabled.latest.golden new file mode 100644 index 0000000000..5b04edcac5 --- /dev/null +++ b/agent/xds/testdata/clusters/mesh-gateway-with-peer-through-mesh-gateway-enabled.latest.golden @@ -0,0 +1,54 @@ +{ + "versionInfo": "00000001", + "resources": [ + { + "@type": "type.googleapis.com/envoy.config.cluster.v3.Cluster", + "name": "server.dc2.peering.6d942ff2-6a78-46f4-a52f-915e26c48797", + "type": "EDS", + "edsClusterConfig": { + "edsConfig": { + "ads": { + + }, + "resourceApiVersion": "V3" + } + }, + "connectTimeout": "5s", + "outlierDetection": { + + } + }, + { + "@type": "type.googleapis.com/envoy.config.cluster.v3.Cluster", + "name": "server.dc3.peering.f622dc37-7238-4485-ab58-0f53864a9ae5", + "type": "LOGICAL_DNS", + "connectTimeout": "5s", + "loadAssignment": { + "clusterName": "server.dc3.peering.f622dc37-7238-4485-ab58-0f53864a9ae5", + "endpoints": [ + { + "lbEndpoints": [ + { + "endpoint": { + "address": { + "socketAddress": { + "address": "my-load-balancer-1234567890abcdef.elb.us-east-2.amazonaws.com", + "portValue": 8080 + } + } + } + } + ] + } + ] + }, + "dnsRefreshRate": "10s", + "dnsLookupFamily": "V4_ONLY", + "outlierDetection": { + + } + } + ], + "typeUrl": "type.googleapis.com/envoy.config.cluster.v3.Cluster", + "nonce": "00000001" +} \ No newline at end of file diff --git a/agent/xds/testdata/endpoints/connect-proxy-with-chain-and-failover-to-cluster-peer.latest.golden b/agent/xds/testdata/endpoints/connect-proxy-with-chain-and-failover-to-cluster-peer.latest.golden index feaea90551..5b767ac861 100644 --- a/agent/xds/testdata/endpoints/connect-proxy-with-chain-and-failover-to-cluster-peer.latest.golden +++ b/agent/xds/testdata/endpoints/connect-proxy-with-chain-and-failover-to-cluster-peer.latest.golden @@ -18,18 +18,6 @@ }, "healthStatus": "HEALTHY", "loadBalancingWeight": 1 - }, - { - "endpoint": { - "address": { - "socketAddress": { - "address": "10.40.1.2", - "portValue": 8080 - } - } - }, - "healthStatus": "HEALTHY", - "loadBalancingWeight": 1 } ] } diff --git a/agent/xds/testdata/endpoints/connect-proxy-with-chain-and-redirect-to-cluster-peer.latest.golden b/agent/xds/testdata/endpoints/connect-proxy-with-chain-and-redirect-to-cluster-peer.latest.golden index 830d3941ec..8cb6ce20a0 100644 --- a/agent/xds/testdata/endpoints/connect-proxy-with-chain-and-redirect-to-cluster-peer.latest.golden +++ b/agent/xds/testdata/endpoints/connect-proxy-with-chain-and-redirect-to-cluster-peer.latest.golden @@ -18,18 +18,6 @@ }, "healthStatus": "HEALTHY", "loadBalancingWeight": 1 - }, - { - "endpoint": { - "address": { - "socketAddress": { - "address": "10.40.1.2", - "portValue": 8080 - } - } - }, - "healthStatus": "HEALTHY", - "loadBalancingWeight": 1 } ] } diff --git a/agent/xds/testdata/endpoints/ingress-with-chain-and-failover-to-cluster-peer.latest.golden b/agent/xds/testdata/endpoints/ingress-with-chain-and-failover-to-cluster-peer.latest.golden index c799a5a0cc..d919f81952 100644 --- a/agent/xds/testdata/endpoints/ingress-with-chain-and-failover-to-cluster-peer.latest.golden +++ b/agent/xds/testdata/endpoints/ingress-with-chain-and-failover-to-cluster-peer.latest.golden @@ -18,18 +18,6 @@ }, "healthStatus": "HEALTHY", "loadBalancingWeight": 1 - }, - { - "endpoint": { - "address": { - "socketAddress": { - "address": "10.40.1.2", - "portValue": 8080 - } - } - }, - "healthStatus": "HEALTHY", - "loadBalancingWeight": 1 } ] } diff --git a/agent/xds/testdata/endpoints/local-mesh-gateway-with-peered-upstreams.latest.golden b/agent/xds/testdata/endpoints/local-mesh-gateway-with-peered-upstreams.latest.golden new file mode 100644 index 0000000000..760d3b3972 --- /dev/null +++ b/agent/xds/testdata/endpoints/local-mesh-gateway-with-peered-upstreams.latest.golden @@ -0,0 +1,51 @@ +{ + "versionInfo": "00000001", + "resources": [ + { + "@type": "type.googleapis.com/envoy.config.endpoint.v3.ClusterLoadAssignment", + "clusterName": "payments.default.cloud.external.1c053652-8512-4373-90cf-5a7f6263a994.consul", + "endpoints": [ + { + "lbEndpoints": [ + { + "endpoint": { + "address": { + "socketAddress": { + "address": "10.0.0.1", + "portValue": 1234 + } + } + }, + "healthStatus": "HEALTHY", + "loadBalancingWeight": 1 + } + ] + } + ] + }, + { + "@type": "type.googleapis.com/envoy.config.endpoint.v3.ClusterLoadAssignment", + "clusterName": "refunds.default.cloud.external.1c053652-8512-4373-90cf-5a7f6263a994.consul", + "endpoints": [ + { + "lbEndpoints": [ + { + "endpoint": { + "address": { + "socketAddress": { + "address": "10.0.0.1", + "portValue": 1234 + } + } + }, + "healthStatus": "HEALTHY", + "loadBalancingWeight": 1 + } + ] + } + ] + } + ], + "typeUrl": "type.googleapis.com/envoy.config.endpoint.v3.ClusterLoadAssignment", + "nonce": "00000001" +} \ No newline at end of file diff --git a/agent/xds/testdata/endpoints/mesh-gateway-peering-control-plane.latest.golden b/agent/xds/testdata/endpoints/mesh-gateway-peering-control-plane.latest.golden new file mode 100644 index 0000000000..58ce9101bc --- /dev/null +++ b/agent/xds/testdata/endpoints/mesh-gateway-peering-control-plane.latest.golden @@ -0,0 +1,37 @@ +{ + "versionInfo": "00000001", + "resources": [ + { + "@type": "type.googleapis.com/envoy.config.endpoint.v3.ClusterLoadAssignment", + "clusterName": "server.dc1.peering.11111111-2222-3333-4444-555555555555.consul", + "endpoints": [ + { + "lbEndpoints": [ + { + "endpoint": { + "address": { + "socketAddress": { + "address": "127.0.0.1", + "portValue": 8503 + } + } + } + }, + { + "endpoint": { + "address": { + "socketAddress": { + "address": "127.0.0.2", + "portValue": 8503 + } + } + } + } + ] + } + ] + } + ], + "typeUrl": "type.googleapis.com/envoy.config.endpoint.v3.ClusterLoadAssignment", + "nonce": "00000001" +} \ No newline at end of file diff --git a/agent/xds/testdata/endpoints/mesh-gateway-with-imported-peered-services.latest.golden b/agent/xds/testdata/endpoints/mesh-gateway-with-imported-peered-services.latest.golden new file mode 100644 index 0000000000..efb3c588be --- /dev/null +++ b/agent/xds/testdata/endpoints/mesh-gateway-with-imported-peered-services.latest.golden @@ -0,0 +1,41 @@ +{ + "versionInfo": "00000001", + "resources": [ + { + "@type": "type.googleapis.com/envoy.config.endpoint.v3.ClusterLoadAssignment", + "clusterName": "db.default.default.peer-a.external.1c053652-8512-4373-90cf-5a7f6263a994.consul", + "endpoints": [ + { + "lbEndpoints": [ + { + "endpoint": { + "address": { + "socketAddress": { + "address": "10.40.1.1", + "portValue": 8080 + } + } + }, + "healthStatus": "HEALTHY", + "loadBalancingWeight": 1 + }, + { + "endpoint": { + "address": { + "socketAddress": { + "address": "10.40.1.2", + "portValue": 8080 + } + } + }, + "healthStatus": "HEALTHY", + "loadBalancingWeight": 1 + } + ] + } + ] + } + ], + "typeUrl": "type.googleapis.com/envoy.config.endpoint.v3.ClusterLoadAssignment", + "nonce": "00000001" +} \ No newline at end of file diff --git a/agent/xds/testdata/endpoints/mesh-gateway-with-peer-through-mesh-gateway-enabled.latest.golden b/agent/xds/testdata/endpoints/mesh-gateway-with-peer-through-mesh-gateway-enabled.latest.golden new file mode 100644 index 0000000000..475659df2e --- /dev/null +++ b/agent/xds/testdata/endpoints/mesh-gateway-with-peer-through-mesh-gateway-enabled.latest.golden @@ -0,0 +1,37 @@ +{ + "versionInfo": "00000001", + "resources": [ + { + "@type": "type.googleapis.com/envoy.config.endpoint.v3.ClusterLoadAssignment", + "clusterName": "server.dc2.peering.6d942ff2-6a78-46f4-a52f-915e26c48797", + "endpoints": [ + { + "lbEndpoints": [ + { + "endpoint": { + "address": { + "socketAddress": { + "address": "13.14.15.16", + "portValue": 5200 + } + } + } + }, + { + "endpoint": { + "address": { + "socketAddress": { + "address": "9.10.11.12", + "portValue": 5200 + } + } + } + } + ] + } + ] + } + ], + "typeUrl": "type.googleapis.com/envoy.config.endpoint.v3.ClusterLoadAssignment", + "nonce": "00000001" +} \ No newline at end of file diff --git a/agent/xds/testdata/listeners/custom-public-listener-http-2.latest.golden b/agent/xds/testdata/listeners/custom-public-listener-http-2.latest.golden index 9c7640202f..ef2e541e7b 100644 --- a/agent/xds/testdata/listeners/custom-public-listener-http-2.latest.golden +++ b/agent/xds/testdata/listeners/custom-public-listener-http-2.latest.golden @@ -60,6 +60,9 @@ "typedConfig": { "@type": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext", "commonTlsContext": { + "alpnProtocols": [ + "http/1.1" + ], "tlsParams": { }, @@ -138,4 +141,4 @@ ], "typeUrl": "type.googleapis.com/envoy.config.listener.v3.Listener", "nonce": "00000001" -} \ No newline at end of file +} diff --git a/agent/xds/testdata/listeners/custom-public-listener-http-missing.latest.golden b/agent/xds/testdata/listeners/custom-public-listener-http-missing.latest.golden index 99c8f31391..972f7e89c9 100644 --- a/agent/xds/testdata/listeners/custom-public-listener-http-missing.latest.golden +++ b/agent/xds/testdata/listeners/custom-public-listener-http-missing.latest.golden @@ -37,6 +37,9 @@ "typedConfig": { "@type": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext", "commonTlsContext": { + "alpnProtocols": [ + "http/1.1" + ], "tlsParams": { }, @@ -115,4 +118,4 @@ ], "typeUrl": "type.googleapis.com/envoy.config.listener.v3.Listener", "nonce": "00000001" -} \ No newline at end of file +} diff --git a/agent/xds/testdata/listeners/custom-public-listener-http.latest.golden b/agent/xds/testdata/listeners/custom-public-listener-http.latest.golden index 9c7640202f..73d9c3f446 100644 --- a/agent/xds/testdata/listeners/custom-public-listener-http.latest.golden +++ b/agent/xds/testdata/listeners/custom-public-listener-http.latest.golden @@ -60,6 +60,9 @@ "typedConfig": { "@type": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext", "commonTlsContext": { + "alpnProtocols": [ + "http/1.1" + ], "tlsParams": { }, @@ -138,4 +141,4 @@ ], "typeUrl": "type.googleapis.com/envoy.config.listener.v3.Listener", "nonce": "00000001" -} \ No newline at end of file +} diff --git a/agent/xds/testdata/listeners/custom-trace-listener.latest.golden b/agent/xds/testdata/listeners/custom-trace-listener.latest.golden index 5fce12bb73..48256d27fc 100644 --- a/agent/xds/testdata/listeners/custom-trace-listener.latest.golden +++ b/agent/xds/testdata/listeners/custom-trace-listener.latest.golden @@ -148,6 +148,9 @@ "typedConfig": { "@type": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext", "commonTlsContext": { + "alpnProtocols": [ + "http/1.1" + ], "tlsParams": { }, @@ -177,4 +180,4 @@ ], "typeUrl": "type.googleapis.com/envoy.config.listener.v3.Listener", "nonce": "00000001" -} \ No newline at end of file +} diff --git a/agent/xds/testdata/listeners/grpc-public-listener.latest.golden b/agent/xds/testdata/listeners/grpc-public-listener.latest.golden new file mode 100644 index 0000000000..277ecdec0f --- /dev/null +++ b/agent/xds/testdata/listeners/grpc-public-listener.latest.golden @@ -0,0 +1,179 @@ +{ + "versionInfo": "00000001", + "resources": [ + { + "@type": "type.googleapis.com/envoy.config.listener.v3.Listener", + "name": "db:127.0.0.1:9191", + "address": { + "socketAddress": { + "address": "127.0.0.1", + "portValue": 9191 + } + }, + "filterChains": [ + { + "filters": [ + { + "name": "envoy.filters.network.tcp_proxy", + "typedConfig": { + "@type": "type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy", + "statPrefix": "upstream.db.default.default.dc1", + "cluster": "db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul" + } + } + ] + } + ], + "trafficDirection": "OUTBOUND" + }, + { + "@type": "type.googleapis.com/envoy.config.listener.v3.Listener", + "name": "prepared_query:geo-cache:127.10.10.10:8181", + "address": { + "socketAddress": { + "address": "127.10.10.10", + "portValue": 8181 + } + }, + "filterChains": [ + { + "filters": [ + { + "name": "envoy.filters.network.tcp_proxy", + "typedConfig": { + "@type": "type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy", + "statPrefix": "upstream.prepared_query_geo-cache", + "cluster": "geo-cache.default.dc1.query.11111111-2222-3333-4444-555555555555.consul" + } + } + ] + } + ], + "trafficDirection": "OUTBOUND" + }, + { + "@type": "type.googleapis.com/envoy.config.listener.v3.Listener", + "name": "public_listener:0.0.0.0:9999", + "address": { + "socketAddress": { + "address": "0.0.0.0", + "portValue": 9999 + } + }, + "filterChains": [ + { + "filters": [ + { + "name": "envoy.filters.network.http_connection_manager", + "typedConfig": { + "@type": "type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager", + "statPrefix": "public_listener", + "routeConfig": { + "name": "public_listener", + "virtualHosts": [ + { + "name": "public_listener", + "domains": [ + "*" + ], + "routes": [ + { + "match": { + "prefix": "/" + }, + "route": { + "cluster": "local_app" + } + } + ] + } + ] + }, + "httpFilters": [ + { + "name": "envoy.filters.http.grpc_stats", + "typedConfig": { + "@type": "type.googleapis.com/envoy.extensions.filters.http.grpc_stats.v3.FilterConfig", + "statsForAllMethods": true + } + }, + { + "name": "envoy.filters.http.grpc_http1_bridge", + "typedConfig": { + "@type": "type.googleapis.com/envoy.extensions.filters.http.grpc_http1_bridge.v3.Config" + } + }, + { + "name": "envoy.filters.http.rbac", + "typedConfig": { + "@type": "type.googleapis.com/envoy.extensions.filters.http.rbac.v3.RBAC", + "rules": { + + } + } + }, + { + "name": "envoy.filters.http.router", + "typedConfig": { + "@type": "type.googleapis.com/envoy.extensions.filters.http.router.v3.Router" + } + } + ], + "tracing": { + "randomSampling": { + + } + }, + "forwardClientCertDetails": "APPEND_FORWARD", + "http2ProtocolOptions": { + + }, + "setCurrentClientCertDetails": { + "subject": true, + "cert": true, + "chain": true, + "dns": true, + "uri": true + } + } + } + ], + "transportSocket": { + "name": "tls", + "typedConfig": { + "@type": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext", + "commonTlsContext": { + "alpnProtocols": [ + "h2", + "http/1.1" + ], + "tlsParams": { + + }, + "tlsCertificates": [ + { + "certificateChain": { + "inlineString": "-----BEGIN CERTIFICATE-----\nMIICjDCCAjKgAwIBAgIIC5llxGV1gB8wCgYIKoZIzj0EAwIwFDESMBAGA1UEAxMJ\nVGVzdCBDQSAyMB4XDTE5MDMyMjEzNTgyNloXDTI5MDMyMjEzNTgyNlowDjEMMAoG\nA1UEAxMDd2ViMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEADPv1RHVNRfa2VKR\nAB16b6rZnEt7tuhaxCFpQXPj7M2omb0B9Favq5E0ivpNtv1QnFhxtPd7d5k4e+T7\nSkW1TaOCAXIwggFuMA4GA1UdDwEB/wQEAwIDuDAdBgNVHSUEFjAUBggrBgEFBQcD\nAgYIKwYBBQUHAwEwDAYDVR0TAQH/BAIwADBoBgNVHQ4EYQRfN2Q6MDc6ODc6M2E6\nNDA6MTk6NDc6YzM6NWE6YzA6YmE6NjI6ZGY6YWY6NGI6ZDQ6MDU6MjU6NzY6M2Q6\nNWE6OGQ6MTY6OGQ6Njc6NWU6MmU6YTA6MzQ6N2Q6ZGM6ZmYwagYDVR0jBGMwYYBf\nZDE6MTE6MTE6YWM6MmE6YmE6OTc6YjI6M2Y6YWM6N2I6YmQ6ZGE6YmU6YjE6OGE6\nZmM6OWE6YmE6YjU6YmM6ODM6ZTc6NWU6NDE6NmY6ZjI6NzM6OTU6NTg6MGM6ZGIw\nWQYDVR0RBFIwUIZOc3BpZmZlOi8vMTExMTExMTEtMjIyMi0zMzMzLTQ0NDQtNTU1\nNTU1NTU1NTU1LmNvbnN1bC9ucy9kZWZhdWx0L2RjL2RjMS9zdmMvd2ViMAoGCCqG\nSM49BAMCA0gAMEUCIGC3TTvvjj76KMrguVyFf4tjOqaSCRie3nmHMRNNRav7AiEA\npY0heYeK9A6iOLrzqxSerkXXQyj5e9bE4VgUnxgPU6g=\n-----END CERTIFICATE-----\n" + }, + "privateKey": { + "inlineString": "-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEIMoTkpRggp3fqZzFKh82yS4LjtJI+XY+qX/7DefHFrtdoAoGCCqGSM49\nAwEHoUQDQgAEADPv1RHVNRfa2VKRAB16b6rZnEt7tuhaxCFpQXPj7M2omb0B9Fav\nq5E0ivpNtv1QnFhxtPd7d5k4e+T7SkW1TQ==\n-----END EC PRIVATE KEY-----\n" + } + } + ], + "validationContext": { + "trustedCa": { + "inlineString": "-----BEGIN CERTIFICATE-----\nMIICXDCCAgKgAwIBAgIICpZq70Z9LyUwCgYIKoZIzj0EAwIwFDESMBAGA1UEAxMJ\nVGVzdCBDQSAyMB4XDTE5MDMyMjEzNTgyNloXDTI5MDMyMjEzNTgyNlowFDESMBAG\nA1UEAxMJVGVzdCBDQSAyMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEIhywH1gx\nAsMwuF3ukAI5YL2jFxH6Usnma1HFSfVyxbXX1/uoZEYrj8yCAtdU2yoHETyd+Zx2\nThhRLP79pYegCaOCATwwggE4MA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTAD\nAQH/MGgGA1UdDgRhBF9kMToxMToxMTphYzoyYTpiYTo5NzpiMjozZjphYzo3Yjpi\nZDpkYTpiZTpiMTo4YTpmYzo5YTpiYTpiNTpiYzo4MzplNzo1ZTo0MTo2ZjpmMjo3\nMzo5NTo1ODowYzpkYjBqBgNVHSMEYzBhgF9kMToxMToxMTphYzoyYTpiYTo5Nzpi\nMjozZjphYzo3YjpiZDpkYTpiZTpiMTo4YTpmYzo5YTpiYTpiNTpiYzo4MzplNzo1\nZTo0MTo2ZjpmMjo3Mzo5NTo1ODowYzpkYjA/BgNVHREEODA2hjRzcGlmZmU6Ly8x\nMTExMTExMS0yMjIyLTMzMzMtNDQ0NC01NTU1NTU1NTU1NTUuY29uc3VsMAoGCCqG\nSM49BAMCA0gAMEUCICOY0i246rQHJt8o8Oya0D5PLL1FnmsQmQqIGCi31RwnAiEA\noR5f6Ku+cig2Il8T8LJujOp2/2A72QcHZA57B13y+8o=\n-----END CERTIFICATE-----\n" + } + } + }, + "requireClientCertificate": true + } + } + } + ], + "trafficDirection": "INBOUND" + } + ], + "typeUrl": "type.googleapis.com/envoy.config.listener.v3.Listener", + "nonce": "00000001" +} diff --git a/agent/xds/testdata/listeners/http-listener-with-timeouts.latest.golden b/agent/xds/testdata/listeners/http-listener-with-timeouts.latest.golden index de384eef54..e7f74d80ac 100644 --- a/agent/xds/testdata/listeners/http-listener-with-timeouts.latest.golden +++ b/agent/xds/testdata/listeners/http-listener-with-timeouts.latest.golden @@ -128,6 +128,9 @@ "typedConfig": { "@type": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext", "commonTlsContext": { + "alpnProtocols": [ + "http/1.1" + ], "tlsParams": { }, diff --git a/agent/xds/testdata/listeners/http-public-listener-no-xfcc.latest.golden b/agent/xds/testdata/listeners/http-public-listener-no-xfcc.latest.golden index d0a676eff2..18b9826ba6 100644 --- a/agent/xds/testdata/listeners/http-public-listener-no-xfcc.latest.golden +++ b/agent/xds/testdata/listeners/http-public-listener-no-xfcc.latest.golden @@ -119,6 +119,9 @@ "typedConfig": { "@type": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext", "commonTlsContext": { + "alpnProtocols": [ + "http/1.1" + ], "tlsParams": { }, diff --git a/agent/xds/testdata/listeners/http-public-listener.latest.golden b/agent/xds/testdata/listeners/http-public-listener.latest.golden index 45f052872e..fd663b1d27 100644 --- a/agent/xds/testdata/listeners/http-public-listener.latest.golden +++ b/agent/xds/testdata/listeners/http-public-listener.latest.golden @@ -127,6 +127,9 @@ "typedConfig": { "@type": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext", "commonTlsContext": { + "alpnProtocols": [ + "http/1.1" + ], "tlsParams": { }, diff --git a/agent/xds/testdata/listeners/http2-public-listener.latest.golden b/agent/xds/testdata/listeners/http2-public-listener.latest.golden new file mode 100644 index 0000000000..294920c0c6 --- /dev/null +++ b/agent/xds/testdata/listeners/http2-public-listener.latest.golden @@ -0,0 +1,166 @@ +{ + "versionInfo": "00000001", + "resources": [ + { + "@type": "type.googleapis.com/envoy.config.listener.v3.Listener", + "name": "db:127.0.0.1:9191", + "address": { + "socketAddress": { + "address": "127.0.0.1", + "portValue": 9191 + } + }, + "filterChains": [ + { + "filters": [ + { + "name": "envoy.filters.network.tcp_proxy", + "typedConfig": { + "@type": "type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy", + "statPrefix": "upstream.db.default.default.dc1", + "cluster": "db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul" + } + } + ] + } + ], + "trafficDirection": "OUTBOUND" + }, + { + "@type": "type.googleapis.com/envoy.config.listener.v3.Listener", + "name": "prepared_query:geo-cache:127.10.10.10:8181", + "address": { + "socketAddress": { + "address": "127.10.10.10", + "portValue": 8181 + } + }, + "filterChains": [ + { + "filters": [ + { + "name": "envoy.filters.network.tcp_proxy", + "typedConfig": { + "@type": "type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy", + "statPrefix": "upstream.prepared_query_geo-cache", + "cluster": "geo-cache.default.dc1.query.11111111-2222-3333-4444-555555555555.consul" + } + } + ] + } + ], + "trafficDirection": "OUTBOUND" + }, + { + "@type": "type.googleapis.com/envoy.config.listener.v3.Listener", + "name": "public_listener:0.0.0.0:9999", + "address": { + "socketAddress": { + "address": "0.0.0.0", + "portValue": 9999 + } + }, + "filterChains": [ + { + "filters": [ + { + "name": "envoy.filters.network.http_connection_manager", + "typedConfig": { + "@type": "type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager", + "statPrefix": "public_listener", + "routeConfig": { + "name": "public_listener", + "virtualHosts": [ + { + "name": "public_listener", + "domains": [ + "*" + ], + "routes": [ + { + "match": { + "prefix": "/" + }, + "route": { + "cluster": "local_app" + } + } + ] + } + ] + }, + "httpFilters": [ + { + "name": "envoy.filters.http.rbac", + "typedConfig": { + "@type": "type.googleapis.com/envoy.extensions.filters.http.rbac.v3.RBAC", + "rules": { + + } + } + }, + { + "name": "envoy.filters.http.router", + "typedConfig": { + "@type": "type.googleapis.com/envoy.extensions.filters.http.router.v3.Router" + } + } + ], + "tracing": { + "randomSampling": { + + } + }, + "forwardClientCertDetails": "APPEND_FORWARD", + "http2ProtocolOptions": { + + }, + "setCurrentClientCertDetails": { + "subject": true, + "cert": true, + "chain": true, + "dns": true, + "uri": true + } + } + } + ], + "transportSocket": { + "name": "tls", + "typedConfig": { + "@type": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext", + "commonTlsContext": { + "alpnProtocols": [ + "h2", + "http/1.1" + ], + "tlsParams": { + + }, + "tlsCertificates": [ + { + "certificateChain": { + "inlineString": "-----BEGIN CERTIFICATE-----\nMIICjDCCAjKgAwIBAgIIC5llxGV1gB8wCgYIKoZIzj0EAwIwFDESMBAGA1UEAxMJ\nVGVzdCBDQSAyMB4XDTE5MDMyMjEzNTgyNloXDTI5MDMyMjEzNTgyNlowDjEMMAoG\nA1UEAxMDd2ViMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEADPv1RHVNRfa2VKR\nAB16b6rZnEt7tuhaxCFpQXPj7M2omb0B9Favq5E0ivpNtv1QnFhxtPd7d5k4e+T7\nSkW1TaOCAXIwggFuMA4GA1UdDwEB/wQEAwIDuDAdBgNVHSUEFjAUBggrBgEFBQcD\nAgYIKwYBBQUHAwEwDAYDVR0TAQH/BAIwADBoBgNVHQ4EYQRfN2Q6MDc6ODc6M2E6\nNDA6MTk6NDc6YzM6NWE6YzA6YmE6NjI6ZGY6YWY6NGI6ZDQ6MDU6MjU6NzY6M2Q6\nNWE6OGQ6MTY6OGQ6Njc6NWU6MmU6YTA6MzQ6N2Q6ZGM6ZmYwagYDVR0jBGMwYYBf\nZDE6MTE6MTE6YWM6MmE6YmE6OTc6YjI6M2Y6YWM6N2I6YmQ6ZGE6YmU6YjE6OGE6\nZmM6OWE6YmE6YjU6YmM6ODM6ZTc6NWU6NDE6NmY6ZjI6NzM6OTU6NTg6MGM6ZGIw\nWQYDVR0RBFIwUIZOc3BpZmZlOi8vMTExMTExMTEtMjIyMi0zMzMzLTQ0NDQtNTU1\nNTU1NTU1NTU1LmNvbnN1bC9ucy9kZWZhdWx0L2RjL2RjMS9zdmMvd2ViMAoGCCqG\nSM49BAMCA0gAMEUCIGC3TTvvjj76KMrguVyFf4tjOqaSCRie3nmHMRNNRav7AiEA\npY0heYeK9A6iOLrzqxSerkXXQyj5e9bE4VgUnxgPU6g=\n-----END CERTIFICATE-----\n" + }, + "privateKey": { + "inlineString": "-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEIMoTkpRggp3fqZzFKh82yS4LjtJI+XY+qX/7DefHFrtdoAoGCCqGSM49\nAwEHoUQDQgAEADPv1RHVNRfa2VKRAB16b6rZnEt7tuhaxCFpQXPj7M2omb0B9Fav\nq5E0ivpNtv1QnFhxtPd7d5k4e+T7SkW1TQ==\n-----END EC PRIVATE KEY-----\n" + } + } + ], + "validationContext": { + "trustedCa": { + "inlineString": "-----BEGIN CERTIFICATE-----\nMIICXDCCAgKgAwIBAgIICpZq70Z9LyUwCgYIKoZIzj0EAwIwFDESMBAGA1UEAxMJ\nVGVzdCBDQSAyMB4XDTE5MDMyMjEzNTgyNloXDTI5MDMyMjEzNTgyNlowFDESMBAG\nA1UEAxMJVGVzdCBDQSAyMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEIhywH1gx\nAsMwuF3ukAI5YL2jFxH6Usnma1HFSfVyxbXX1/uoZEYrj8yCAtdU2yoHETyd+Zx2\nThhRLP79pYegCaOCATwwggE4MA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTAD\nAQH/MGgGA1UdDgRhBF9kMToxMToxMTphYzoyYTpiYTo5NzpiMjozZjphYzo3Yjpi\nZDpkYTpiZTpiMTo4YTpmYzo5YTpiYTpiNTpiYzo4MzplNzo1ZTo0MTo2ZjpmMjo3\nMzo5NTo1ODowYzpkYjBqBgNVHSMEYzBhgF9kMToxMToxMTphYzoyYTpiYTo5Nzpi\nMjozZjphYzo3YjpiZDpkYTpiZTpiMTo4YTpmYzo5YTpiYTpiNTpiYzo4MzplNzo1\nZTo0MTo2ZjpmMjo3Mzo5NTo1ODowYzpkYjA/BgNVHREEODA2hjRzcGlmZmU6Ly8x\nMTExMTExMS0yMjIyLTMzMzMtNDQ0NC01NTU1NTU1NTU1NTUuY29uc3VsMAoGCCqG\nSM49BAMCA0gAMEUCICOY0i246rQHJt8o8Oya0D5PLL1FnmsQmQqIGCi31RwnAiEA\noR5f6Ku+cig2Il8T8LJujOp2/2A72QcHZA57B13y+8o=\n-----END CERTIFICATE-----\n" + } + } + }, + "requireClientCertificate": true + } + } + } + ], + "trafficDirection": "INBOUND" + } + ], + "typeUrl": "type.googleapis.com/envoy.config.listener.v3.Listener", + "nonce": "00000001" +} diff --git a/agent/xds/testdata/listeners/ingress-with-grpc-single-tls-listener.latest.golden b/agent/xds/testdata/listeners/ingress-with-grpc-single-tls-listener.latest.golden new file mode 100644 index 0000000000..dba9a8d669 --- /dev/null +++ b/agent/xds/testdata/listeners/ingress-with-grpc-single-tls-listener.latest.golden @@ -0,0 +1,162 @@ +{ + "versionInfo": "00000001", + "resources": [ + { + "@type": "type.googleapis.com/envoy.config.listener.v3.Listener", + "name": "grpc:1.2.3.4:8080", + "address": { + "socketAddress": { + "address": "1.2.3.4", + "portValue": 8080 + } + }, + "filterChains": [ + { + "filters": [ + { + "name": "envoy.filters.network.http_connection_manager", + "typedConfig": { + "@type": "type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager", + "statPrefix": "ingress_upstream_8080", + "rds": { + "configSource": { + "ads": { + + }, + "resourceApiVersion": "V3" + }, + "routeConfigName": "8080" + }, + "http2ProtocolOptions": { + + }, + "httpFilters": [ + { + "name": "envoy.filters.http.grpc_stats", + "typedConfig": { + "@type": "type.googleapis.com/envoy.extensions.filters.http.grpc_stats.v3.FilterConfig", + "statsForAllMethods": true + } + }, + { + "name": "envoy.filters.http.grpc_http1_bridge", + "typedConfig": { + "@type": "type.googleapis.com/envoy.extensions.filters.http.grpc_http1_bridge.v3.Config" + } + }, + { + "name": "envoy.filters.http.router", + "typedConfig": { + "@type": "type.googleapis.com/envoy.extensions.filters.http.router.v3.Router" + } + } + ], + "tracing": { + "randomSampling": { + + } + } + } + } + ] + } + ], + "trafficDirection": "OUTBOUND" + }, + { + "@type": "type.googleapis.com/envoy.config.listener.v3.Listener", + "name": "grpc:1.2.3.4:8081", + "address": { + "socketAddress": { + "address": "1.2.3.4", + "portValue": 8081 + } + }, + "filterChains": [ + { + "filters": [ + { + "name": "envoy.filters.network.http_connection_manager", + "typedConfig": { + "@type": "type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager", + "statPrefix": "ingress_upstream_8081", + "rds": { + "configSource": { + "ads": { + + }, + "resourceApiVersion": "V3" + }, + "routeConfigName": "8081" + }, + "http2ProtocolOptions": { + + }, + "httpFilters": [ + { + "name": "envoy.filters.http.grpc_stats", + "typedConfig": { + "@type": "type.googleapis.com/envoy.extensions.filters.http.grpc_stats.v3.FilterConfig", + "statsForAllMethods": true + } + }, + { + "name": "envoy.filters.http.grpc_http1_bridge", + "typedConfig": { + "@type": "type.googleapis.com/envoy.extensions.filters.http.grpc_http1_bridge.v3.Config" + } + }, + { + "name": "envoy.filters.http.router", + "typedConfig": { + "@type": "type.googleapis.com/envoy.extensions.filters.http.router.v3.Router" + } + } + ], + "tracing": { + "randomSampling": { + + } + } + } + } + ], + "transportSocket": { + "name": "tls", + "typedConfig": { + "@type": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext", + "commonTlsContext": { + "alpnProtocols": [ + "h2", + "http/1.1" + ], + "tlsParams": { + "tlsMinimumProtocolVersion": "TLSv1_2" + }, + "tlsCertificates": [ + { + "certificateChain": { + "inlineString": "-----BEGIN CERTIFICATE-----\nMIICjDCCAjKgAwIBAgIIC5llxGV1gB8wCgYIKoZIzj0EAwIwFDESMBAGA1UEAxMJ\nVGVzdCBDQSAyMB4XDTE5MDMyMjEzNTgyNloXDTI5MDMyMjEzNTgyNlowDjEMMAoG\nA1UEAxMDd2ViMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEADPv1RHVNRfa2VKR\nAB16b6rZnEt7tuhaxCFpQXPj7M2omb0B9Favq5E0ivpNtv1QnFhxtPd7d5k4e+T7\nSkW1TaOCAXIwggFuMA4GA1UdDwEB/wQEAwIDuDAdBgNVHSUEFjAUBggrBgEFBQcD\nAgYIKwYBBQUHAwEwDAYDVR0TAQH/BAIwADBoBgNVHQ4EYQRfN2Q6MDc6ODc6M2E6\nNDA6MTk6NDc6YzM6NWE6YzA6YmE6NjI6ZGY6YWY6NGI6ZDQ6MDU6MjU6NzY6M2Q6\nNWE6OGQ6MTY6OGQ6Njc6NWU6MmU6YTA6MzQ6N2Q6ZGM6ZmYwagYDVR0jBGMwYYBf\nZDE6MTE6MTE6YWM6MmE6YmE6OTc6YjI6M2Y6YWM6N2I6YmQ6ZGE6YmU6YjE6OGE6\nZmM6OWE6YmE6YjU6YmM6ODM6ZTc6NWU6NDE6NmY6ZjI6NzM6OTU6NTg6MGM6ZGIw\nWQYDVR0RBFIwUIZOc3BpZmZlOi8vMTExMTExMTEtMjIyMi0zMzMzLTQ0NDQtNTU1\nNTU1NTU1NTU1LmNvbnN1bC9ucy9kZWZhdWx0L2RjL2RjMS9zdmMvd2ViMAoGCCqG\nSM49BAMCA0gAMEUCIGC3TTvvjj76KMrguVyFf4tjOqaSCRie3nmHMRNNRav7AiEA\npY0heYeK9A6iOLrzqxSerkXXQyj5e9bE4VgUnxgPU6g=\n-----END CERTIFICATE-----\n" + }, + "privateKey": { + "inlineString": "-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEIMoTkpRggp3fqZzFKh82yS4LjtJI+XY+qX/7DefHFrtdoAoGCCqGSM49\nAwEHoUQDQgAEADPv1RHVNRfa2VKRAB16b6rZnEt7tuhaxCFpQXPj7M2omb0B9Fav\nq5E0ivpNtv1QnFhxtPd7d5k4e+T7SkW1TQ==\n-----END EC PRIVATE KEY-----\n" + } + } + ], + "validationContext": { + "trustedCa": { + "inlineString": "-----BEGIN CERTIFICATE-----\nMIICXDCCAgKgAwIBAgIICpZq70Z9LyUwCgYIKoZIzj0EAwIwFDESMBAGA1UEAxMJ\nVGVzdCBDQSAyMB4XDTE5MDMyMjEzNTgyNloXDTI5MDMyMjEzNTgyNlowFDESMBAG\nA1UEAxMJVGVzdCBDQSAyMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEIhywH1gx\nAsMwuF3ukAI5YL2jFxH6Usnma1HFSfVyxbXX1/uoZEYrj8yCAtdU2yoHETyd+Zx2\nThhRLP79pYegCaOCATwwggE4MA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTAD\nAQH/MGgGA1UdDgRhBF9kMToxMToxMTphYzoyYTpiYTo5NzpiMjozZjphYzo3Yjpi\nZDpkYTpiZTpiMTo4YTpmYzo5YTpiYTpiNTpiYzo4MzplNzo1ZTo0MTo2ZjpmMjo3\nMzo5NTo1ODowYzpkYjBqBgNVHSMEYzBhgF9kMToxMToxMTphYzoyYTpiYTo5Nzpi\nMjozZjphYzo3YjpiZDpkYTpiZTpiMTo4YTpmYzo5YTpiYTpiNTpiYzo4MzplNzo1\nZTo0MTo2ZjpmMjo3Mzo5NTo1ODowYzpkYjA/BgNVHREEODA2hjRzcGlmZmU6Ly8x\nMTExMTExMS0yMjIyLTMzMzMtNDQ0NC01NTU1NTU1NTU1NTUuY29uc3VsMAoGCCqG\nSM49BAMCA0gAMEUCICOY0i246rQHJt8o8Oya0D5PLL1FnmsQmQqIGCi31RwnAiEA\noR5f6Ku+cig2Il8T8LJujOp2/2A72QcHZA57B13y+8o=\n-----END CERTIFICATE-----\n" + } + } + }, + "requireClientCertificate": false + } + } + } + ], + "trafficDirection": "OUTBOUND" + } + ], + "typeUrl": "type.googleapis.com/envoy.config.listener.v3.Listener", + "nonce": "00000001" +} \ No newline at end of file diff --git a/agent/xds/testdata/listeners/ingress-with-http2-and-grpc-multiple-tls-listener.latest.golden b/agent/xds/testdata/listeners/ingress-with-http2-and-grpc-multiple-tls-listener.latest.golden new file mode 100644 index 0000000000..ecd5312064 --- /dev/null +++ b/agent/xds/testdata/listeners/ingress-with-http2-and-grpc-multiple-tls-listener.latest.golden @@ -0,0 +1,180 @@ +{ + "versionInfo": "00000001", + "resources": [ + { + "@type": "type.googleapis.com/envoy.config.listener.v3.Listener", + "name": "grpc:1.2.3.4:8080", + "address": { + "socketAddress": { + "address": "1.2.3.4", + "portValue": 8080 + } + }, + "filterChains": [ + { + "filters": [ + { + "name": "envoy.filters.network.http_connection_manager", + "typedConfig": { + "@type": "type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager", + "statPrefix": "ingress_upstream_8080", + "rds": { + "configSource": { + "ads": { + + }, + "resourceApiVersion": "V3" + }, + "routeConfigName": "8080" + }, + "http2ProtocolOptions": { + + }, + "httpFilters": [ + { + "name": "envoy.filters.http.grpc_stats", + "typedConfig": { + "@type": "type.googleapis.com/envoy.extensions.filters.http.grpc_stats.v3.FilterConfig", + "statsForAllMethods": true + } + }, + { + "name": "envoy.filters.http.grpc_http1_bridge", + "typedConfig": { + "@type": "type.googleapis.com/envoy.extensions.filters.http.grpc_http1_bridge.v3.Config" + } + }, + { + "name": "envoy.filters.http.router", + "typedConfig": { + "@type": "type.googleapis.com/envoy.extensions.filters.http.router.v3.Router" + } + } + ], + "tracing": { + "randomSampling": { + + } + } + } + } + ], + "transportSocket": { + "name": "tls", + "typedConfig": { + "@type": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext", + "commonTlsContext": { + "alpnProtocols": [ + "h2", + "http/1.1" + ], + "tlsParams": { + "tlsMinimumProtocolVersion": "TLSv1_2" + }, + "tlsCertificates": [ + { + "certificateChain": { + "inlineString": "-----BEGIN CERTIFICATE-----\nMIICjDCCAjKgAwIBAgIIC5llxGV1gB8wCgYIKoZIzj0EAwIwFDESMBAGA1UEAxMJ\nVGVzdCBDQSAyMB4XDTE5MDMyMjEzNTgyNloXDTI5MDMyMjEzNTgyNlowDjEMMAoG\nA1UEAxMDd2ViMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEADPv1RHVNRfa2VKR\nAB16b6rZnEt7tuhaxCFpQXPj7M2omb0B9Favq5E0ivpNtv1QnFhxtPd7d5k4e+T7\nSkW1TaOCAXIwggFuMA4GA1UdDwEB/wQEAwIDuDAdBgNVHSUEFjAUBggrBgEFBQcD\nAgYIKwYBBQUHAwEwDAYDVR0TAQH/BAIwADBoBgNVHQ4EYQRfN2Q6MDc6ODc6M2E6\nNDA6MTk6NDc6YzM6NWE6YzA6YmE6NjI6ZGY6YWY6NGI6ZDQ6MDU6MjU6NzY6M2Q6\nNWE6OGQ6MTY6OGQ6Njc6NWU6MmU6YTA6MzQ6N2Q6ZGM6ZmYwagYDVR0jBGMwYYBf\nZDE6MTE6MTE6YWM6MmE6YmE6OTc6YjI6M2Y6YWM6N2I6YmQ6ZGE6YmU6YjE6OGE6\nZmM6OWE6YmE6YjU6YmM6ODM6ZTc6NWU6NDE6NmY6ZjI6NzM6OTU6NTg6MGM6ZGIw\nWQYDVR0RBFIwUIZOc3BpZmZlOi8vMTExMTExMTEtMjIyMi0zMzMzLTQ0NDQtNTU1\nNTU1NTU1NTU1LmNvbnN1bC9ucy9kZWZhdWx0L2RjL2RjMS9zdmMvd2ViMAoGCCqG\nSM49BAMCA0gAMEUCIGC3TTvvjj76KMrguVyFf4tjOqaSCRie3nmHMRNNRav7AiEA\npY0heYeK9A6iOLrzqxSerkXXQyj5e9bE4VgUnxgPU6g=\n-----END CERTIFICATE-----\n" + }, + "privateKey": { + "inlineString": "-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEIMoTkpRggp3fqZzFKh82yS4LjtJI+XY+qX/7DefHFrtdoAoGCCqGSM49\nAwEHoUQDQgAEADPv1RHVNRfa2VKRAB16b6rZnEt7tuhaxCFpQXPj7M2omb0B9Fav\nq5E0ivpNtv1QnFhxtPd7d5k4e+T7SkW1TQ==\n-----END EC PRIVATE KEY-----\n" + } + } + ], + "validationContext": { + "trustedCa": { + "inlineString": "-----BEGIN CERTIFICATE-----\nMIICXDCCAgKgAwIBAgIICpZq70Z9LyUwCgYIKoZIzj0EAwIwFDESMBAGA1UEAxMJ\nVGVzdCBDQSAyMB4XDTE5MDMyMjEzNTgyNloXDTI5MDMyMjEzNTgyNlowFDESMBAG\nA1UEAxMJVGVzdCBDQSAyMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEIhywH1gx\nAsMwuF3ukAI5YL2jFxH6Usnma1HFSfVyxbXX1/uoZEYrj8yCAtdU2yoHETyd+Zx2\nThhRLP79pYegCaOCATwwggE4MA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTAD\nAQH/MGgGA1UdDgRhBF9kMToxMToxMTphYzoyYTpiYTo5NzpiMjozZjphYzo3Yjpi\nZDpkYTpiZTpiMTo4YTpmYzo5YTpiYTpiNTpiYzo4MzplNzo1ZTo0MTo2ZjpmMjo3\nMzo5NTo1ODowYzpkYjBqBgNVHSMEYzBhgF9kMToxMToxMTphYzoyYTpiYTo5Nzpi\nMjozZjphYzo3YjpiZDpkYTpiZTpiMTo4YTpmYzo5YTpiYTpiNTpiYzo4MzplNzo1\nZTo0MTo2ZjpmMjo3Mzo5NTo1ODowYzpkYjA/BgNVHREEODA2hjRzcGlmZmU6Ly8x\nMTExMTExMS0yMjIyLTMzMzMtNDQ0NC01NTU1NTU1NTU1NTUuY29uc3VsMAoGCCqG\nSM49BAMCA0gAMEUCICOY0i246rQHJt8o8Oya0D5PLL1FnmsQmQqIGCi31RwnAiEA\noR5f6Ku+cig2Il8T8LJujOp2/2A72QcHZA57B13y+8o=\n-----END CERTIFICATE-----\n" + } + } + }, + "requireClientCertificate": false + } + } + } + ], + "trafficDirection": "OUTBOUND" + }, + { + "@type": "type.googleapis.com/envoy.config.listener.v3.Listener", + "name": "http2:1.2.3.4:8081", + "address": { + "socketAddress": { + "address": "1.2.3.4", + "portValue": 8081 + } + }, + "filterChains": [ + { + "filters": [ + { + "name": "envoy.filters.network.http_connection_manager", + "typedConfig": { + "@type": "type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager", + "statPrefix": "ingress_upstream_8081", + "rds": { + "configSource": { + "ads": { + + }, + "resourceApiVersion": "V3" + }, + "routeConfigName": "8081" + }, + "http2ProtocolOptions": { + + }, + "httpFilters": [ + { + "name": "envoy.filters.http.router", + "typedConfig": { + "@type": "type.googleapis.com/envoy.extensions.filters.http.router.v3.Router" + } + } + ], + "tracing": { + "randomSampling": { + + } + } + } + } + ], + "transportSocket": { + "name": "tls", + "typedConfig": { + "@type": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext", + "commonTlsContext": { + "alpnProtocols": [ + "h2", + "http/1.1" + ], + "tlsParams": { + "tlsMinimumProtocolVersion": "TLSv1_2" + }, + "tlsCertificates": [ + { + "certificateChain": { + "inlineString": "-----BEGIN CERTIFICATE-----\nMIICjDCCAjKgAwIBAgIIC5llxGV1gB8wCgYIKoZIzj0EAwIwFDESMBAGA1UEAxMJ\nVGVzdCBDQSAyMB4XDTE5MDMyMjEzNTgyNloXDTI5MDMyMjEzNTgyNlowDjEMMAoG\nA1UEAxMDd2ViMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEADPv1RHVNRfa2VKR\nAB16b6rZnEt7tuhaxCFpQXPj7M2omb0B9Favq5E0ivpNtv1QnFhxtPd7d5k4e+T7\nSkW1TaOCAXIwggFuMA4GA1UdDwEB/wQEAwIDuDAdBgNVHSUEFjAUBggrBgEFBQcD\nAgYIKwYBBQUHAwEwDAYDVR0TAQH/BAIwADBoBgNVHQ4EYQRfN2Q6MDc6ODc6M2E6\nNDA6MTk6NDc6YzM6NWE6YzA6YmE6NjI6ZGY6YWY6NGI6ZDQ6MDU6MjU6NzY6M2Q6\nNWE6OGQ6MTY6OGQ6Njc6NWU6MmU6YTA6MzQ6N2Q6ZGM6ZmYwagYDVR0jBGMwYYBf\nZDE6MTE6MTE6YWM6MmE6YmE6OTc6YjI6M2Y6YWM6N2I6YmQ6ZGE6YmU6YjE6OGE6\nZmM6OWE6YmE6YjU6YmM6ODM6ZTc6NWU6NDE6NmY6ZjI6NzM6OTU6NTg6MGM6ZGIw\nWQYDVR0RBFIwUIZOc3BpZmZlOi8vMTExMTExMTEtMjIyMi0zMzMzLTQ0NDQtNTU1\nNTU1NTU1NTU1LmNvbnN1bC9ucy9kZWZhdWx0L2RjL2RjMS9zdmMvd2ViMAoGCCqG\nSM49BAMCA0gAMEUCIGC3TTvvjj76KMrguVyFf4tjOqaSCRie3nmHMRNNRav7AiEA\npY0heYeK9A6iOLrzqxSerkXXQyj5e9bE4VgUnxgPU6g=\n-----END CERTIFICATE-----\n" + }, + "privateKey": { + "inlineString": "-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEIMoTkpRggp3fqZzFKh82yS4LjtJI+XY+qX/7DefHFrtdoAoGCCqGSM49\nAwEHoUQDQgAEADPv1RHVNRfa2VKRAB16b6rZnEt7tuhaxCFpQXPj7M2omb0B9Fav\nq5E0ivpNtv1QnFhxtPd7d5k4e+T7SkW1TQ==\n-----END EC PRIVATE KEY-----\n" + } + } + ], + "validationContext": { + "trustedCa": { + "inlineString": "-----BEGIN CERTIFICATE-----\nMIICXDCCAgKgAwIBAgIICpZq70Z9LyUwCgYIKoZIzj0EAwIwFDESMBAGA1UEAxMJ\nVGVzdCBDQSAyMB4XDTE5MDMyMjEzNTgyNloXDTI5MDMyMjEzNTgyNlowFDESMBAG\nA1UEAxMJVGVzdCBDQSAyMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEIhywH1gx\nAsMwuF3ukAI5YL2jFxH6Usnma1HFSfVyxbXX1/uoZEYrj8yCAtdU2yoHETyd+Zx2\nThhRLP79pYegCaOCATwwggE4MA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTAD\nAQH/MGgGA1UdDgRhBF9kMToxMToxMTphYzoyYTpiYTo5NzpiMjozZjphYzo3Yjpi\nZDpkYTpiZTpiMTo4YTpmYzo5YTpiYTpiNTpiYzo4MzplNzo1ZTo0MTo2ZjpmMjo3\nMzo5NTo1ODowYzpkYjBqBgNVHSMEYzBhgF9kMToxMToxMTphYzoyYTpiYTo5Nzpi\nMjozZjphYzo3YjpiZDpkYTpiZTpiMTo4YTpmYzo5YTpiYTpiNTpiYzo4MzplNzo1\nZTo0MTo2ZjpmMjo3Mzo5NTo1ODowYzpkYjA/BgNVHREEODA2hjRzcGlmZmU6Ly8x\nMTExMTExMS0yMjIyLTMzMzMtNDQ0NC01NTU1NTU1NTU1NTUuY29uc3VsMAoGCCqG\nSM49BAMCA0gAMEUCICOY0i246rQHJt8o8Oya0D5PLL1FnmsQmQqIGCi31RwnAiEA\noR5f6Ku+cig2Il8T8LJujOp2/2A72QcHZA57B13y+8o=\n-----END CERTIFICATE-----\n" + } + } + }, + "requireClientCertificate": false + } + } + } + ], + "trafficDirection": "OUTBOUND" + } + ], + "typeUrl": "type.googleapis.com/envoy.config.listener.v3.Listener", + "nonce": "00000001" +} diff --git a/agent/xds/testdata/listeners/ingress-with-http2-single-tls-listener.latest.golden b/agent/xds/testdata/listeners/ingress-with-http2-single-tls-listener.latest.golden new file mode 100644 index 0000000000..32cd89dfb6 --- /dev/null +++ b/agent/xds/testdata/listeners/ingress-with-http2-single-tls-listener.latest.golden @@ -0,0 +1,136 @@ +{ + "versionInfo": "00000001", + "resources": [ + { + "@type": "type.googleapis.com/envoy.config.listener.v3.Listener", + "name": "http2:1.2.3.4:8080", + "address": { + "socketAddress": { + "address": "1.2.3.4", + "portValue": 8080 + } + }, + "filterChains": [ + { + "filters": [ + { + "name": "envoy.filters.network.http_connection_manager", + "typedConfig": { + "@type": "type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager", + "statPrefix": "ingress_upstream_8080", + "rds": { + "configSource": { + "ads": { + + }, + "resourceApiVersion": "V3" + }, + "routeConfigName": "8080" + }, + "http2ProtocolOptions": { + + }, + "httpFilters": [ + { + "name": "envoy.filters.http.router", + "typedConfig": { + "@type": "type.googleapis.com/envoy.extensions.filters.http.router.v3.Router" + } + } + ], + "tracing": { + "randomSampling": { + + } + } + } + } + ] + } + ], + "trafficDirection": "OUTBOUND" + }, + { + "@type": "type.googleapis.com/envoy.config.listener.v3.Listener", + "name": "http2:1.2.3.4:8081", + "address": { + "socketAddress": { + "address": "1.2.3.4", + "portValue": 8081 + } + }, + "filterChains": [ + { + "filters": [ + { + "name": "envoy.filters.network.http_connection_manager", + "typedConfig": { + "@type": "type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager", + "statPrefix": "ingress_upstream_8081", + "rds": { + "configSource": { + "ads": { + + }, + "resourceApiVersion": "V3" + }, + "routeConfigName": "8081" + }, + "http2ProtocolOptions": { + + }, + "httpFilters": [ + { + "name": "envoy.filters.http.router", + "typedConfig": { + "@type": "type.googleapis.com/envoy.extensions.filters.http.router.v3.Router" + } + } + ], + "tracing": { + "randomSampling": { + + } + } + } + } + ], + "transportSocket": { + "name": "tls", + "typedConfig": { + "@type": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext", + "commonTlsContext": { + "alpnProtocols": [ + "h2", + "http/1.1" + ], + "tlsParams": { + "tlsMinimumProtocolVersion": "TLSv1_2" + }, + "tlsCertificates": [ + { + "certificateChain": { + "inlineString": "-----BEGIN CERTIFICATE-----\nMIICjDCCAjKgAwIBAgIIC5llxGV1gB8wCgYIKoZIzj0EAwIwFDESMBAGA1UEAxMJ\nVGVzdCBDQSAyMB4XDTE5MDMyMjEzNTgyNloXDTI5MDMyMjEzNTgyNlowDjEMMAoG\nA1UEAxMDd2ViMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEADPv1RHVNRfa2VKR\nAB16b6rZnEt7tuhaxCFpQXPj7M2omb0B9Favq5E0ivpNtv1QnFhxtPd7d5k4e+T7\nSkW1TaOCAXIwggFuMA4GA1UdDwEB/wQEAwIDuDAdBgNVHSUEFjAUBggrBgEFBQcD\nAgYIKwYBBQUHAwEwDAYDVR0TAQH/BAIwADBoBgNVHQ4EYQRfN2Q6MDc6ODc6M2E6\nNDA6MTk6NDc6YzM6NWE6YzA6YmE6NjI6ZGY6YWY6NGI6ZDQ6MDU6MjU6NzY6M2Q6\nNWE6OGQ6MTY6OGQ6Njc6NWU6MmU6YTA6MzQ6N2Q6ZGM6ZmYwagYDVR0jBGMwYYBf\nZDE6MTE6MTE6YWM6MmE6YmE6OTc6YjI6M2Y6YWM6N2I6YmQ6ZGE6YmU6YjE6OGE6\nZmM6OWE6YmE6YjU6YmM6ODM6ZTc6NWU6NDE6NmY6ZjI6NzM6OTU6NTg6MGM6ZGIw\nWQYDVR0RBFIwUIZOc3BpZmZlOi8vMTExMTExMTEtMjIyMi0zMzMzLTQ0NDQtNTU1\nNTU1NTU1NTU1LmNvbnN1bC9ucy9kZWZhdWx0L2RjL2RjMS9zdmMvd2ViMAoGCCqG\nSM49BAMCA0gAMEUCIGC3TTvvjj76KMrguVyFf4tjOqaSCRie3nmHMRNNRav7AiEA\npY0heYeK9A6iOLrzqxSerkXXQyj5e9bE4VgUnxgPU6g=\n-----END CERTIFICATE-----\n" + }, + "privateKey": { + "inlineString": "-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEIMoTkpRggp3fqZzFKh82yS4LjtJI+XY+qX/7DefHFrtdoAoGCCqGSM49\nAwEHoUQDQgAEADPv1RHVNRfa2VKRAB16b6rZnEt7tuhaxCFpQXPj7M2omb0B9Fav\nq5E0ivpNtv1QnFhxtPd7d5k4e+T7SkW1TQ==\n-----END EC PRIVATE KEY-----\n" + } + } + ], + "validationContext": { + "trustedCa": { + "inlineString": "-----BEGIN CERTIFICATE-----\nMIICXDCCAgKgAwIBAgIICpZq70Z9LyUwCgYIKoZIzj0EAwIwFDESMBAGA1UEAxMJ\nVGVzdCBDQSAyMB4XDTE5MDMyMjEzNTgyNloXDTI5MDMyMjEzNTgyNlowFDESMBAG\nA1UEAxMJVGVzdCBDQSAyMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEIhywH1gx\nAsMwuF3ukAI5YL2jFxH6Usnma1HFSfVyxbXX1/uoZEYrj8yCAtdU2yoHETyd+Zx2\nThhRLP79pYegCaOCATwwggE4MA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTAD\nAQH/MGgGA1UdDgRhBF9kMToxMToxMTphYzoyYTpiYTo5NzpiMjozZjphYzo3Yjpi\nZDpkYTpiZTpiMTo4YTpmYzo5YTpiYTpiNTpiYzo4MzplNzo1ZTo0MTo2ZjpmMjo3\nMzo5NTo1ODowYzpkYjBqBgNVHSMEYzBhgF9kMToxMToxMTphYzoyYTpiYTo5Nzpi\nMjozZjphYzo3YjpiZDpkYTpiZTpiMTo4YTpmYzo5YTpiYTpiNTpiYzo4MzplNzo1\nZTo0MTo2ZjpmMjo3Mzo5NTo1ODowYzpkYjA/BgNVHREEODA2hjRzcGlmZmU6Ly8x\nMTExMTExMS0yMjIyLTMzMzMtNDQ0NC01NTU1NTU1NTU1NTUuY29uc3VsMAoGCCqG\nSM49BAMCA0gAMEUCICOY0i246rQHJt8o8Oya0D5PLL1FnmsQmQqIGCi31RwnAiEA\noR5f6Ku+cig2Il8T8LJujOp2/2A72QcHZA57B13y+8o=\n-----END CERTIFICATE-----\n" + } + } + }, + "requireClientCertificate": false + } + } + } + ], + "trafficDirection": "OUTBOUND" + } + ], + "typeUrl": "type.googleapis.com/envoy.config.listener.v3.Listener", + "nonce": "00000001" +} diff --git a/agent/xds/testdata/listeners/ingress-with-sds-listener+service-level.latest.golden b/agent/xds/testdata/listeners/ingress-with-sds-listener+service-level.latest.golden index 02bcf8d36e..d5c616666f 100644 --- a/agent/xds/testdata/listeners/ingress-with-sds-listener+service-level.latest.golden +++ b/agent/xds/testdata/listeners/ingress-with-sds-listener+service-level.latest.golden @@ -53,6 +53,9 @@ "typedConfig": { "@type": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext", "commonTlsContext": { + "alpnProtocols": [ + "http/1.1" + ], "tlsParams": { }, @@ -118,6 +121,9 @@ "typedConfig": { "@type": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext", "commonTlsContext": { + "alpnProtocols": [ + "http/1.1" + ], "tlsParams": { }, diff --git a/agent/xds/testdata/listeners/ingress-with-sds-listener-gw-level-http.latest.golden b/agent/xds/testdata/listeners/ingress-with-sds-listener-gw-level-http.latest.golden index 5e197a36e5..81057c493a 100644 --- a/agent/xds/testdata/listeners/ingress-with-sds-listener-gw-level-http.latest.golden +++ b/agent/xds/testdata/listeners/ingress-with-sds-listener-gw-level-http.latest.golden @@ -48,6 +48,9 @@ "typedConfig": { "@type": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext", "commonTlsContext": { + "alpnProtocols": [ + "http/1.1" + ], "tlsParams": { }, diff --git a/agent/xds/testdata/listeners/ingress-with-sds-service-level-mixed-no-tls.latest.golden b/agent/xds/testdata/listeners/ingress-with-sds-service-level-mixed-no-tls.latest.golden index bb017e85d6..9ff02fcdc3 100644 --- a/agent/xds/testdata/listeners/ingress-with-sds-service-level-mixed-no-tls.latest.golden +++ b/agent/xds/testdata/listeners/ingress-with-sds-service-level-mixed-no-tls.latest.golden @@ -53,6 +53,9 @@ "typedConfig": { "@type": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext", "commonTlsContext": { + "alpnProtocols": [ + "http/1.1" + ], "tlsParams": { }, diff --git a/agent/xds/testdata/listeners/ingress-with-sds-service-level.latest.golden b/agent/xds/testdata/listeners/ingress-with-sds-service-level.latest.golden index d89cb9eefb..a15bed8d15 100644 --- a/agent/xds/testdata/listeners/ingress-with-sds-service-level.latest.golden +++ b/agent/xds/testdata/listeners/ingress-with-sds-service-level.latest.golden @@ -53,6 +53,9 @@ "typedConfig": { "@type": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext", "commonTlsContext": { + "alpnProtocols": [ + "http/1.1" + ], "tlsParams": { }, @@ -123,6 +126,9 @@ "typedConfig": { "@type": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext", "commonTlsContext": { + "alpnProtocols": [ + "http/1.1" + ], "tlsParams": { }, diff --git a/agent/xds/testdata/listeners/ingress-with-single-tls-listener.latest.golden b/agent/xds/testdata/listeners/ingress-with-single-tls-listener.latest.golden index 17fef5d07e..94c8280d0e 100644 --- a/agent/xds/testdata/listeners/ingress-with-single-tls-listener.latest.golden +++ b/agent/xds/testdata/listeners/ingress-with-single-tls-listener.latest.golden @@ -94,6 +94,9 @@ "typedConfig": { "@type": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext", "commonTlsContext": { + "alpnProtocols": [ + "http/1.1" + ], "tlsParams": { "tlsMinimumProtocolVersion": "TLSv1_2" }, diff --git a/agent/xds/testdata/listeners/ingress-with-tls-min-version-listeners-gateway-defaults.latest.golden b/agent/xds/testdata/listeners/ingress-with-tls-min-version-listeners-gateway-defaults.latest.golden index af876f8857..910e60f868 100644 --- a/agent/xds/testdata/listeners/ingress-with-tls-min-version-listeners-gateway-defaults.latest.golden +++ b/agent/xds/testdata/listeners/ingress-with-tls-min-version-listeners-gateway-defaults.latest.golden @@ -48,6 +48,9 @@ "typedConfig": { "@type": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext", "commonTlsContext": { + "alpnProtocols": [ + "http/1.1" + ], "tlsParams": { "tlsMinimumProtocolVersion": "TLSv1_2" }, @@ -121,6 +124,9 @@ "typedConfig": { "@type": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext", "commonTlsContext": { + "alpnProtocols": [ + "http/1.1" + ], "tlsParams": { "tlsMinimumProtocolVersion": "TLSv1_2" }, @@ -194,6 +200,9 @@ "typedConfig": { "@type": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext", "commonTlsContext": { + "alpnProtocols": [ + "http/1.1" + ], "tlsParams": { "tlsMinimumProtocolVersion": "TLSv1_2" }, @@ -267,6 +276,9 @@ "typedConfig": { "@type": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext", "commonTlsContext": { + "alpnProtocols": [ + "http/1.1" + ], "tlsParams": { }, @@ -340,6 +352,9 @@ "typedConfig": { "@type": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext", "commonTlsContext": { + "alpnProtocols": [ + "http/1.1" + ], "tlsParams": { "tlsMinimumProtocolVersion": "TLSv1_2" }, diff --git a/agent/xds/testdata/listeners/ingress-with-tls-mixed-listeners.latest.golden b/agent/xds/testdata/listeners/ingress-with-tls-mixed-listeners.latest.golden index e504650b3e..9c944fd6af 100644 --- a/agent/xds/testdata/listeners/ingress-with-tls-mixed-listeners.latest.golden +++ b/agent/xds/testdata/listeners/ingress-with-tls-mixed-listeners.latest.golden @@ -48,6 +48,9 @@ "typedConfig": { "@type": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext", "commonTlsContext": { + "alpnProtocols": [ + "http/1.1" + ], "tlsParams": { }, diff --git a/agent/xds/testdata/listeners/ingress-with-tls-mixed-min-version-listeners.latest.golden b/agent/xds/testdata/listeners/ingress-with-tls-mixed-min-version-listeners.latest.golden index 1347394a2f..e8d23c4ffc 100644 --- a/agent/xds/testdata/listeners/ingress-with-tls-mixed-min-version-listeners.latest.golden +++ b/agent/xds/testdata/listeners/ingress-with-tls-mixed-min-version-listeners.latest.golden @@ -48,6 +48,9 @@ "typedConfig": { "@type": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext", "commonTlsContext": { + "alpnProtocols": [ + "http/1.1" + ], "tlsParams": { "tlsMinimumProtocolVersion": "TLSv1_2" }, @@ -121,6 +124,9 @@ "typedConfig": { "@type": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext", "commonTlsContext": { + "alpnProtocols": [ + "http/1.1" + ], "tlsParams": { "tlsMinimumProtocolVersion": "TLSv1_0" }, @@ -194,6 +200,9 @@ "typedConfig": { "@type": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext", "commonTlsContext": { + "alpnProtocols": [ + "http/1.1" + ], "tlsParams": { "tlsMinimumProtocolVersion": "TLSv1_3" }, diff --git a/agent/xds/testdata/listeners/listener-balance-inbound-connections.latest.golden b/agent/xds/testdata/listeners/listener-balance-inbound-connections.latest.golden new file mode 100644 index 0000000000..c041226404 --- /dev/null +++ b/agent/xds/testdata/listeners/listener-balance-inbound-connections.latest.golden @@ -0,0 +1,124 @@ +{ + "versionInfo": "00000001", + "resources": [ + { + "@type": "type.googleapis.com/envoy.config.listener.v3.Listener", + "name": "db:127.0.0.1:9191", + "address": { + "socketAddress": { + "address": "127.0.0.1", + "portValue": 9191 + } + }, + "filterChains": [ + { + "filters": [ + { + "name": "envoy.filters.network.tcp_proxy", + "typedConfig": { + "@type": "type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy", + "statPrefix": "upstream.db.default.default.dc1", + "cluster": "db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul" + } + } + ] + } + ], + "trafficDirection": "OUTBOUND" + }, + { + "@type": "type.googleapis.com/envoy.config.listener.v3.Listener", + "name": "prepared_query:geo-cache:127.10.10.10:8181", + "address": { + "socketAddress": { + "address": "127.10.10.10", + "portValue": 8181 + } + }, + "filterChains": [ + { + "filters": [ + { + "name": "envoy.filters.network.tcp_proxy", + "typedConfig": { + "@type": "type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy", + "statPrefix": "upstream.prepared_query_geo-cache", + "cluster": "geo-cache.default.dc1.query.11111111-2222-3333-4444-555555555555.consul" + } + } + ] + } + ], + "trafficDirection": "OUTBOUND" + }, + { + "@type": "type.googleapis.com/envoy.config.listener.v3.Listener", + "name": "public_listener:0.0.0.0:9999", + "address": { + "socketAddress": { + "address": "0.0.0.0", + "portValue": 9999 + } + }, + "filterChains": [ + { + "filters": [ + { + "name": "envoy.filters.network.rbac", + "typedConfig": { + "@type": "type.googleapis.com/envoy.extensions.filters.network.rbac.v3.RBAC", + "rules": { + + }, + "statPrefix": "connect_authz" + } + }, + { + "name": "envoy.filters.network.tcp_proxy", + "typedConfig": { + "@type": "type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy", + "statPrefix": "public_listener", + "cluster": "local_app" + } + } + ], + "transportSocket": { + "name": "tls", + "typedConfig": { + "@type": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext", + "commonTlsContext": { + "tlsParams": { + + }, + "tlsCertificates": [ + { + "certificateChain": { + "inlineString": "-----BEGIN CERTIFICATE-----\nMIICjDCCAjKgAwIBAgIIC5llxGV1gB8wCgYIKoZIzj0EAwIwFDESMBAGA1UEAxMJ\nVGVzdCBDQSAyMB4XDTE5MDMyMjEzNTgyNloXDTI5MDMyMjEzNTgyNlowDjEMMAoG\nA1UEAxMDd2ViMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEADPv1RHVNRfa2VKR\nAB16b6rZnEt7tuhaxCFpQXPj7M2omb0B9Favq5E0ivpNtv1QnFhxtPd7d5k4e+T7\nSkW1TaOCAXIwggFuMA4GA1UdDwEB/wQEAwIDuDAdBgNVHSUEFjAUBggrBgEFBQcD\nAgYIKwYBBQUHAwEwDAYDVR0TAQH/BAIwADBoBgNVHQ4EYQRfN2Q6MDc6ODc6M2E6\nNDA6MTk6NDc6YzM6NWE6YzA6YmE6NjI6ZGY6YWY6NGI6ZDQ6MDU6MjU6NzY6M2Q6\nNWE6OGQ6MTY6OGQ6Njc6NWU6MmU6YTA6MzQ6N2Q6ZGM6ZmYwagYDVR0jBGMwYYBf\nZDE6MTE6MTE6YWM6MmE6YmE6OTc6YjI6M2Y6YWM6N2I6YmQ6ZGE6YmU6YjE6OGE6\nZmM6OWE6YmE6YjU6YmM6ODM6ZTc6NWU6NDE6NmY6ZjI6NzM6OTU6NTg6MGM6ZGIw\nWQYDVR0RBFIwUIZOc3BpZmZlOi8vMTExMTExMTEtMjIyMi0zMzMzLTQ0NDQtNTU1\nNTU1NTU1NTU1LmNvbnN1bC9ucy9kZWZhdWx0L2RjL2RjMS9zdmMvd2ViMAoGCCqG\nSM49BAMCA0gAMEUCIGC3TTvvjj76KMrguVyFf4tjOqaSCRie3nmHMRNNRav7AiEA\npY0heYeK9A6iOLrzqxSerkXXQyj5e9bE4VgUnxgPU6g=\n-----END CERTIFICATE-----\n" + }, + "privateKey": { + "inlineString": "-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEIMoTkpRggp3fqZzFKh82yS4LjtJI+XY+qX/7DefHFrtdoAoGCCqGSM49\nAwEHoUQDQgAEADPv1RHVNRfa2VKRAB16b6rZnEt7tuhaxCFpQXPj7M2omb0B9Fav\nq5E0ivpNtv1QnFhxtPd7d5k4e+T7SkW1TQ==\n-----END EC PRIVATE KEY-----\n" + } + } + ], + "validationContext": { + "trustedCa": { + "inlineString": "-----BEGIN CERTIFICATE-----\nMIICXDCCAgKgAwIBAgIICpZq70Z9LyUwCgYIKoZIzj0EAwIwFDESMBAGA1UEAxMJ\nVGVzdCBDQSAyMB4XDTE5MDMyMjEzNTgyNloXDTI5MDMyMjEzNTgyNlowFDESMBAG\nA1UEAxMJVGVzdCBDQSAyMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEIhywH1gx\nAsMwuF3ukAI5YL2jFxH6Usnma1HFSfVyxbXX1/uoZEYrj8yCAtdU2yoHETyd+Zx2\nThhRLP79pYegCaOCATwwggE4MA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTAD\nAQH/MGgGA1UdDgRhBF9kMToxMToxMTphYzoyYTpiYTo5NzpiMjozZjphYzo3Yjpi\nZDpkYTpiZTpiMTo4YTpmYzo5YTpiYTpiNTpiYzo4MzplNzo1ZTo0MTo2ZjpmMjo3\nMzo5NTo1ODowYzpkYjBqBgNVHSMEYzBhgF9kMToxMToxMTphYzoyYTpiYTo5Nzpi\nMjozZjphYzo3YjpiZDpkYTpiZTpiMTo4YTpmYzo5YTpiYTpiNTpiYzo4MzplNzo1\nZTo0MTo2ZjpmMjo3Mzo5NTo1ODowYzpkYjA/BgNVHREEODA2hjRzcGlmZmU6Ly8x\nMTExMTExMS0yMjIyLTMzMzMtNDQ0NC01NTU1NTU1NTU1NTUuY29uc3VsMAoGCCqG\nSM49BAMCA0gAMEUCICOY0i246rQHJt8o8Oya0D5PLL1FnmsQmQqIGCi31RwnAiEA\noR5f6Ku+cig2Il8T8LJujOp2/2A72QcHZA57B13y+8o=\n-----END CERTIFICATE-----\n" + } + } + }, + "requireClientCertificate": true + } + } + } + ], + "trafficDirection": "INBOUND", + "connectionBalanceConfig": { + "exactBalance": { + + } + } + } + ], + "typeUrl": "type.googleapis.com/envoy.config.listener.v3.Listener", + "nonce": "00000001" +} \ No newline at end of file diff --git a/agent/xds/testdata/listeners/listener-balance-outbound-connections-bind-port.latest.golden b/agent/xds/testdata/listeners/listener-balance-outbound-connections-bind-port.latest.golden new file mode 100644 index 0000000000..889873f554 --- /dev/null +++ b/agent/xds/testdata/listeners/listener-balance-outbound-connections-bind-port.latest.golden @@ -0,0 +1,124 @@ +{ + "versionInfo": "00000001", + "resources": [ + { + "@type": "type.googleapis.com/envoy.config.listener.v3.Listener", + "name": "db:127.0.0.1:9191", + "address": { + "socketAddress": { + "address": "127.0.0.1", + "portValue": 9191 + } + }, + "filterChains": [ + { + "filters": [ + { + "name": "envoy.filters.network.tcp_proxy", + "typedConfig": { + "@type": "type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy", + "statPrefix": "upstream.db.default.default.dc1", + "cluster": "db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul" + } + } + ] + } + ], + "trafficDirection": "OUTBOUND", + "connectionBalanceConfig": { + "exactBalance": { + + } + } + }, + { + "@type": "type.googleapis.com/envoy.config.listener.v3.Listener", + "name": "prepared_query:geo-cache:127.10.10.10:8181", + "address": { + "socketAddress": { + "address": "127.10.10.10", + "portValue": 8181 + } + }, + "filterChains": [ + { + "filters": [ + { + "name": "envoy.filters.network.tcp_proxy", + "typedConfig": { + "@type": "type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy", + "statPrefix": "upstream.prepared_query_geo-cache", + "cluster": "geo-cache.default.dc1.query.11111111-2222-3333-4444-555555555555.consul" + } + } + ] + } + ], + "trafficDirection": "OUTBOUND" + }, + { + "@type": "type.googleapis.com/envoy.config.listener.v3.Listener", + "name": "public_listener:0.0.0.0:9999", + "address": { + "socketAddress": { + "address": "0.0.0.0", + "portValue": 9999 + } + }, + "filterChains": [ + { + "filters": [ + { + "name": "envoy.filters.network.rbac", + "typedConfig": { + "@type": "type.googleapis.com/envoy.extensions.filters.network.rbac.v3.RBAC", + "rules": { + + }, + "statPrefix": "connect_authz" + } + }, + { + "name": "envoy.filters.network.tcp_proxy", + "typedConfig": { + "@type": "type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy", + "statPrefix": "public_listener", + "cluster": "local_app" + } + } + ], + "transportSocket": { + "name": "tls", + "typedConfig": { + "@type": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext", + "commonTlsContext": { + "tlsParams": { + + }, + "tlsCertificates": [ + { + "certificateChain": { + "inlineString": "-----BEGIN CERTIFICATE-----\nMIICjDCCAjKgAwIBAgIIC5llxGV1gB8wCgYIKoZIzj0EAwIwFDESMBAGA1UEAxMJ\nVGVzdCBDQSAyMB4XDTE5MDMyMjEzNTgyNloXDTI5MDMyMjEzNTgyNlowDjEMMAoG\nA1UEAxMDd2ViMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEADPv1RHVNRfa2VKR\nAB16b6rZnEt7tuhaxCFpQXPj7M2omb0B9Favq5E0ivpNtv1QnFhxtPd7d5k4e+T7\nSkW1TaOCAXIwggFuMA4GA1UdDwEB/wQEAwIDuDAdBgNVHSUEFjAUBggrBgEFBQcD\nAgYIKwYBBQUHAwEwDAYDVR0TAQH/BAIwADBoBgNVHQ4EYQRfN2Q6MDc6ODc6M2E6\nNDA6MTk6NDc6YzM6NWE6YzA6YmE6NjI6ZGY6YWY6NGI6ZDQ6MDU6MjU6NzY6M2Q6\nNWE6OGQ6MTY6OGQ6Njc6NWU6MmU6YTA6MzQ6N2Q6ZGM6ZmYwagYDVR0jBGMwYYBf\nZDE6MTE6MTE6YWM6MmE6YmE6OTc6YjI6M2Y6YWM6N2I6YmQ6ZGE6YmU6YjE6OGE6\nZmM6OWE6YmE6YjU6YmM6ODM6ZTc6NWU6NDE6NmY6ZjI6NzM6OTU6NTg6MGM6ZGIw\nWQYDVR0RBFIwUIZOc3BpZmZlOi8vMTExMTExMTEtMjIyMi0zMzMzLTQ0NDQtNTU1\nNTU1NTU1NTU1LmNvbnN1bC9ucy9kZWZhdWx0L2RjL2RjMS9zdmMvd2ViMAoGCCqG\nSM49BAMCA0gAMEUCIGC3TTvvjj76KMrguVyFf4tjOqaSCRie3nmHMRNNRav7AiEA\npY0heYeK9A6iOLrzqxSerkXXQyj5e9bE4VgUnxgPU6g=\n-----END CERTIFICATE-----\n" + }, + "privateKey": { + "inlineString": "-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEIMoTkpRggp3fqZzFKh82yS4LjtJI+XY+qX/7DefHFrtdoAoGCCqGSM49\nAwEHoUQDQgAEADPv1RHVNRfa2VKRAB16b6rZnEt7tuhaxCFpQXPj7M2omb0B9Fav\nq5E0ivpNtv1QnFhxtPd7d5k4e+T7SkW1TQ==\n-----END EC PRIVATE KEY-----\n" + } + } + ], + "validationContext": { + "trustedCa": { + "inlineString": "-----BEGIN CERTIFICATE-----\nMIICXDCCAgKgAwIBAgIICpZq70Z9LyUwCgYIKoZIzj0EAwIwFDESMBAGA1UEAxMJ\nVGVzdCBDQSAyMB4XDTE5MDMyMjEzNTgyNloXDTI5MDMyMjEzNTgyNlowFDESMBAG\nA1UEAxMJVGVzdCBDQSAyMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEIhywH1gx\nAsMwuF3ukAI5YL2jFxH6Usnma1HFSfVyxbXX1/uoZEYrj8yCAtdU2yoHETyd+Zx2\nThhRLP79pYegCaOCATwwggE4MA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTAD\nAQH/MGgGA1UdDgRhBF9kMToxMToxMTphYzoyYTpiYTo5NzpiMjozZjphYzo3Yjpi\nZDpkYTpiZTpiMTo4YTpmYzo5YTpiYTpiNTpiYzo4MzplNzo1ZTo0MTo2ZjpmMjo3\nMzo5NTo1ODowYzpkYjBqBgNVHSMEYzBhgF9kMToxMToxMTphYzoyYTpiYTo5Nzpi\nMjozZjphYzo3YjpiZDpkYTpiZTpiMTo4YTpmYzo5YTpiYTpiNTpiYzo4MzplNzo1\nZTo0MTo2ZjpmMjo3Mzo5NTo1ODowYzpkYjA/BgNVHREEODA2hjRzcGlmZmU6Ly8x\nMTExMTExMS0yMjIyLTMzMzMtNDQ0NC01NTU1NTU1NTU1NTUuY29uc3VsMAoGCCqG\nSM49BAMCA0gAMEUCICOY0i246rQHJt8o8Oya0D5PLL1FnmsQmQqIGCi31RwnAiEA\noR5f6Ku+cig2Il8T8LJujOp2/2A72QcHZA57B13y+8o=\n-----END CERTIFICATE-----\n" + } + } + }, + "requireClientCertificate": true + } + } + } + ], + "trafficDirection": "INBOUND" + } + ], + "typeUrl": "type.googleapis.com/envoy.config.listener.v3.Listener", + "nonce": "00000001" +} \ No newline at end of file diff --git a/agent/xds/testdata/listeners/local-mesh-gateway-with-peered-upstreams.latest.golden b/agent/xds/testdata/listeners/local-mesh-gateway-with-peered-upstreams.latest.golden new file mode 100644 index 0000000000..d062f6dd51 --- /dev/null +++ b/agent/xds/testdata/listeners/local-mesh-gateway-with-peered-upstreams.latest.golden @@ -0,0 +1,119 @@ +{ + "versionInfo": "00000001", + "resources": [ + { + "@type": "type.googleapis.com/envoy.config.listener.v3.Listener", + "name": "payments?peer=cloud:127.0.0.1:9090", + "address": { + "socketAddress": { + "address": "127.0.0.1", + "portValue": 9090 + } + }, + "filterChains": [ + { + "filters": [ + { + "name": "envoy.filters.network.tcp_proxy", + "typedConfig": { + "@type": "type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy", + "statPrefix": "upstream_peered.payments.default.cloud", + "cluster": "payments.default.cloud.external.1c053652-8512-4373-90cf-5a7f6263a994.consul" + } + } + ] + } + ], + "trafficDirection": "OUTBOUND" + }, + { + "@type": "type.googleapis.com/envoy.config.listener.v3.Listener", + "name": "public_listener:0.0.0.0:9999", + "address": { + "socketAddress": { + "address": "0.0.0.0", + "portValue": 9999 + } + }, + "filterChains": [ + { + "filters": [ + { + "name": "envoy.filters.network.rbac", + "typedConfig": { + "@type": "type.googleapis.com/envoy.extensions.filters.network.rbac.v3.RBAC", + "rules": { + + }, + "statPrefix": "connect_authz" + } + }, + { + "name": "envoy.filters.network.tcp_proxy", + "typedConfig": { + "@type": "type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy", + "statPrefix": "public_listener", + "cluster": "local_app" + } + } + ], + "transportSocket": { + "name": "tls", + "typedConfig": { + "@type": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext", + "commonTlsContext": { + "tlsParams": { + + }, + "tlsCertificates": [ + { + "certificateChain": { + "inlineString": "-----BEGIN CERTIFICATE-----\nMIICjDCCAjKgAwIBAgIIC5llxGV1gB8wCgYIKoZIzj0EAwIwFDESMBAGA1UEAxMJ\nVGVzdCBDQSAyMB4XDTE5MDMyMjEzNTgyNloXDTI5MDMyMjEzNTgyNlowDjEMMAoG\nA1UEAxMDd2ViMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEADPv1RHVNRfa2VKR\nAB16b6rZnEt7tuhaxCFpQXPj7M2omb0B9Favq5E0ivpNtv1QnFhxtPd7d5k4e+T7\nSkW1TaOCAXIwggFuMA4GA1UdDwEB/wQEAwIDuDAdBgNVHSUEFjAUBggrBgEFBQcD\nAgYIKwYBBQUHAwEwDAYDVR0TAQH/BAIwADBoBgNVHQ4EYQRfN2Q6MDc6ODc6M2E6\nNDA6MTk6NDc6YzM6NWE6YzA6YmE6NjI6ZGY6YWY6NGI6ZDQ6MDU6MjU6NzY6M2Q6\nNWE6OGQ6MTY6OGQ6Njc6NWU6MmU6YTA6MzQ6N2Q6ZGM6ZmYwagYDVR0jBGMwYYBf\nZDE6MTE6MTE6YWM6MmE6YmE6OTc6YjI6M2Y6YWM6N2I6YmQ6ZGE6YmU6YjE6OGE6\nZmM6OWE6YmE6YjU6YmM6ODM6ZTc6NWU6NDE6NmY6ZjI6NzM6OTU6NTg6MGM6ZGIw\nWQYDVR0RBFIwUIZOc3BpZmZlOi8vMTExMTExMTEtMjIyMi0zMzMzLTQ0NDQtNTU1\nNTU1NTU1NTU1LmNvbnN1bC9ucy9kZWZhdWx0L2RjL2RjMS9zdmMvd2ViMAoGCCqG\nSM49BAMCA0gAMEUCIGC3TTvvjj76KMrguVyFf4tjOqaSCRie3nmHMRNNRav7AiEA\npY0heYeK9A6iOLrzqxSerkXXQyj5e9bE4VgUnxgPU6g=\n-----END CERTIFICATE-----\n" + }, + "privateKey": { + "inlineString": "-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEIMoTkpRggp3fqZzFKh82yS4LjtJI+XY+qX/7DefHFrtdoAoGCCqGSM49\nAwEHoUQDQgAEADPv1RHVNRfa2VKRAB16b6rZnEt7tuhaxCFpQXPj7M2omb0B9Fav\nq5E0ivpNtv1QnFhxtPd7d5k4e+T7SkW1TQ==\n-----END EC PRIVATE KEY-----\n" + } + } + ], + "validationContext": { + "trustedCa": { + "inlineString": "-----BEGIN CERTIFICATE-----\nMIICXDCCAgKgAwIBAgIICpZq70Z9LyUwCgYIKoZIzj0EAwIwFDESMBAGA1UEAxMJ\nVGVzdCBDQSAyMB4XDTE5MDMyMjEzNTgyNloXDTI5MDMyMjEzNTgyNlowFDESMBAG\nA1UEAxMJVGVzdCBDQSAyMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEIhywH1gx\nAsMwuF3ukAI5YL2jFxH6Usnma1HFSfVyxbXX1/uoZEYrj8yCAtdU2yoHETyd+Zx2\nThhRLP79pYegCaOCATwwggE4MA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTAD\nAQH/MGgGA1UdDgRhBF9kMToxMToxMTphYzoyYTpiYTo5NzpiMjozZjphYzo3Yjpi\nZDpkYTpiZTpiMTo4YTpmYzo5YTpiYTpiNTpiYzo4MzplNzo1ZTo0MTo2ZjpmMjo3\nMzo5NTo1ODowYzpkYjBqBgNVHSMEYzBhgF9kMToxMToxMTphYzoyYTpiYTo5Nzpi\nMjozZjphYzo3YjpiZDpkYTpiZTpiMTo4YTpmYzo5YTpiYTpiNTpiYzo4MzplNzo1\nZTo0MTo2ZjpmMjo3Mzo5NTo1ODowYzpkYjA/BgNVHREEODA2hjRzcGlmZmU6Ly8x\nMTExMTExMS0yMjIyLTMzMzMtNDQ0NC01NTU1NTU1NTU1NTUuY29uc3VsMAoGCCqG\nSM49BAMCA0gAMEUCICOY0i246rQHJt8o8Oya0D5PLL1FnmsQmQqIGCi31RwnAiEA\noR5f6Ku+cig2Il8T8LJujOp2/2A72QcHZA57B13y+8o=\n-----END CERTIFICATE-----\n" + } + } + }, + "requireClientCertificate": true + } + } + } + ], + "trafficDirection": "INBOUND" + }, + { + "@type": "type.googleapis.com/envoy.config.listener.v3.Listener", + "name": "refunds?peer=cloud:127.0.0.1:9090", + "address": { + "socketAddress": { + "address": "127.0.0.1", + "portValue": 9090 + } + }, + "filterChains": [ + { + "filters": [ + { + "name": "envoy.filters.network.tcp_proxy", + "typedConfig": { + "@type": "type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy", + "statPrefix": "upstream_peered.refunds.default.cloud", + "cluster": "refunds.default.cloud.external.1c053652-8512-4373-90cf-5a7f6263a994.consul" + } + } + ] + } + ], + "trafficDirection": "OUTBOUND" + } + ], + "typeUrl": "type.googleapis.com/envoy.config.listener.v3.Listener", + "nonce": "00000001" +} \ No newline at end of file diff --git a/agent/xds/testdata/listeners/mesh-gateway-peering-control-plane.latest.golden b/agent/xds/testdata/listeners/mesh-gateway-peering-control-plane.latest.golden new file mode 100644 index 0000000000..5989bcb930 --- /dev/null +++ b/agent/xds/testdata/listeners/mesh-gateway-peering-control-plane.latest.golden @@ -0,0 +1,62 @@ +{ + "versionInfo": "00000001", + "resources": [ + { + "@type": "type.googleapis.com/envoy.config.listener.v3.Listener", + "name": "default:1.2.3.4:8443", + "address": { + "socketAddress": { + "address": "1.2.3.4", + "portValue": 8443 + } + }, + "filterChains": [ + { + "filterChainMatch": { + "serverNames": [ + "server.dc1.peering.11111111-2222-3333-4444-555555555555.consul" + ] + }, + "filters": [ + { + "name": "envoy.filters.network.tcp_proxy", + "typedConfig": { + "@type": "type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy", + "statPrefix": "mesh_gateway_local_peering_server.default.dc1", + "cluster": "server.dc1.peering.11111111-2222-3333-4444-555555555555.consul" + } + } + ] + }, + { + "filters": [ + { + "name": "envoy.filters.network.sni_cluster", + "typedConfig": { + "@type": "type.googleapis.com/envoy.extensions.filters.network.sni_cluster.v3.SniCluster" + } + }, + { + "name": "envoy.filters.network.tcp_proxy", + "typedConfig": { + "@type": "type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy", + "statPrefix": "mesh_gateway_local.default", + "cluster": "" + } + } + ] + } + ], + "listenerFilters": [ + { + "name": "envoy.filters.listener.tls_inspector", + "typedConfig": { + "@type": "type.googleapis.com/envoy.extensions.filters.listener.tls_inspector.v3.TlsInspector" + } + } + ] + } + ], + "typeUrl": "type.googleapis.com/envoy.config.listener.v3.Listener", + "nonce": "00000001" +} \ No newline at end of file diff --git a/agent/xds/testdata/listeners/mesh-gateway-with-imported-peered-services.latest.golden b/agent/xds/testdata/listeners/mesh-gateway-with-imported-peered-services.latest.golden new file mode 100644 index 0000000000..40d5b919bd --- /dev/null +++ b/agent/xds/testdata/listeners/mesh-gateway-with-imported-peered-services.latest.golden @@ -0,0 +1,45 @@ +{ + "versionInfo": "00000001", + "resources": [ + { + "@type": "type.googleapis.com/envoy.config.listener.v3.Listener", + "name": "default:1.2.3.4:8443", + "address": { + "socketAddress": { + "address": "1.2.3.4", + "portValue": 8443 + } + }, + "filterChains": [ + { + "filters": [ + { + "name": "envoy.filters.network.sni_cluster", + "typedConfig": { + "@type": "type.googleapis.com/envoy.extensions.filters.network.sni_cluster.v3.SniCluster" + } + }, + { + "name": "envoy.filters.network.tcp_proxy", + "typedConfig": { + "@type": "type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy", + "statPrefix": "mesh_gateway_local.default", + "cluster": "" + } + } + ] + } + ], + "listenerFilters": [ + { + "name": "envoy.filters.listener.tls_inspector", + "typedConfig": { + "@type": "type.googleapis.com/envoy.extensions.filters.listener.tls_inspector.v3.TlsInspector" + } + } + ] + } + ], + "typeUrl": "type.googleapis.com/envoy.config.listener.v3.Listener", + "nonce": "00000001" +} \ No newline at end of file diff --git a/agent/xds/testdata/listeners/mesh-gateway-with-peer-through-mesh-gateway-enabled.latest.golden b/agent/xds/testdata/listeners/mesh-gateway-with-peer-through-mesh-gateway-enabled.latest.golden new file mode 100644 index 0000000000..eba604091b --- /dev/null +++ b/agent/xds/testdata/listeners/mesh-gateway-with-peer-through-mesh-gateway-enabled.latest.golden @@ -0,0 +1,79 @@ +{ + "versionInfo": "00000001", + "resources": [ + { + "@type": "type.googleapis.com/envoy.config.listener.v3.Listener", + "name": "default:1.2.3.4:8443", + "address": { + "socketAddress": { + "address": "1.2.3.4", + "portValue": 8443 + } + }, + "filterChains": [ + { + "filterChainMatch": { + "serverNames": [ + "server.dc2.peering.6d942ff2-6a78-46f4-a52f-915e26c48797" + ] + }, + "filters": [ + { + "name": "envoy.filters.network.tcp_proxy", + "typedConfig": { + "@type": "type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy", + "statPrefix": "mesh_gateway_remote_peering_servers.server.dc2.peering.6d942ff2-6a78-46f4-a52f-915e26c48797", + "cluster": "server.dc2.peering.6d942ff2-6a78-46f4-a52f-915e26c48797" + } + } + ] + }, + { + "filterChainMatch": { + "serverNames": [ + "server.dc3.peering.f622dc37-7238-4485-ab58-0f53864a9ae5" + ] + }, + "filters": [ + { + "name": "envoy.filters.network.tcp_proxy", + "typedConfig": { + "@type": "type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy", + "statPrefix": "mesh_gateway_remote_peering_servers.server.dc3.peering.f622dc37-7238-4485-ab58-0f53864a9ae5", + "cluster": "server.dc3.peering.f622dc37-7238-4485-ab58-0f53864a9ae5" + } + } + ] + }, + { + "filters": [ + { + "name": "envoy.filters.network.sni_cluster", + "typedConfig": { + "@type": "type.googleapis.com/envoy.extensions.filters.network.sni_cluster.v3.SniCluster" + } + }, + { + "name": "envoy.filters.network.tcp_proxy", + "typedConfig": { + "@type": "type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy", + "statPrefix": "mesh_gateway_local.default", + "cluster": "" + } + } + ] + } + ], + "listenerFilters": [ + { + "name": "envoy.filters.listener.tls_inspector", + "typedConfig": { + "@type": "type.googleapis.com/envoy.extensions.filters.listener.tls_inspector.v3.TlsInspector" + } + } + ] + } + ], + "typeUrl": "type.googleapis.com/envoy.config.listener.v3.Listener", + "nonce": "00000001" +} \ No newline at end of file diff --git a/agent/xds/testdata/listeners/transparent-proxy-with-resolver-redirect-upstream.latest.golden b/agent/xds/testdata/listeners/transparent-proxy-with-resolver-redirect-upstream.latest.golden new file mode 100644 index 0000000000..cbde9b76e5 --- /dev/null +++ b/agent/xds/testdata/listeners/transparent-proxy-with-resolver-redirect-upstream.latest.golden @@ -0,0 +1,176 @@ +{ + "versionInfo": "00000001", + "resources": [ + { + "@type": "type.googleapis.com/envoy.config.listener.v3.Listener", + "name": "db-redir:127.0.0.1:9191", + "address": { + "socketAddress": { + "address": "127.0.0.1", + "portValue": 9191 + } + }, + "filterChains": [ + { + "filters": [ + { + "name": "envoy.filters.network.tcp_proxy", + "typedConfig": { + "@type": "type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy", + "statPrefix": "upstream.db-redir.default.default.dc1", + "cluster": "db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul" + } + } + ] + } + ], + "trafficDirection": "OUTBOUND" + }, + { + "@type": "type.googleapis.com/envoy.config.listener.v3.Listener", + "name": "outbound_listener:127.0.0.1:15001", + "address": { + "socketAddress": { + "address": "127.0.0.1", + "portValue": 15001 + } + }, + "filterChains": [ + { + "filterChainMatch": { + "prefixRanges": [ + { + "addressPrefix": "10.0.0.1", + "prefixLen": 32 + }, + { + "addressPrefix": "240.0.0.1", + "prefixLen": 32 + } + ] + }, + "filters": [ + { + "name": "envoy.filters.network.tcp_proxy", + "typedConfig": { + "@type": "type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy", + "statPrefix": "upstream.google.default.default.dc1", + "cluster": "google.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul" + } + } + ] + } + ], + "defaultFilterChain": { + "filters": [ + { + "name": "envoy.filters.network.tcp_proxy", + "typedConfig": { + "@type": "type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy", + "statPrefix": "upstream.original-destination", + "cluster": "original-destination" + } + } + ] + }, + "listenerFilters": [ + { + "name": "envoy.filters.listener.original_dst", + "typedConfig": { + "@type": "type.googleapis.com/envoy.extensions.filters.listener.original_dst.v3.OriginalDst" + } + } + ], + "trafficDirection": "OUTBOUND" + }, + { + "@type": "type.googleapis.com/envoy.config.listener.v3.Listener", + "name": "prepared_query:geo-cache:127.10.10.10:8181", + "address": { + "socketAddress": { + "address": "127.10.10.10", + "portValue": 8181 + } + }, + "filterChains": [ + { + "filters": [ + { + "name": "envoy.filters.network.tcp_proxy", + "typedConfig": { + "@type": "type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy", + "statPrefix": "upstream.prepared_query_geo-cache", + "cluster": "geo-cache.default.dc1.query.11111111-2222-3333-4444-555555555555.consul" + } + } + ] + } + ], + "trafficDirection": "OUTBOUND" + }, + { + "@type": "type.googleapis.com/envoy.config.listener.v3.Listener", + "name": "public_listener:0.0.0.0:9999", + "address": { + "socketAddress": { + "address": "0.0.0.0", + "portValue": 9999 + } + }, + "filterChains": [ + { + "filters": [ + { + "name": "envoy.filters.network.rbac", + "typedConfig": { + "@type": "type.googleapis.com/envoy.extensions.filters.network.rbac.v3.RBAC", + "rules": { + + }, + "statPrefix": "connect_authz" + } + }, + { + "name": "envoy.filters.network.tcp_proxy", + "typedConfig": { + "@type": "type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy", + "statPrefix": "public_listener", + "cluster": "local_app" + } + } + ], + "transportSocket": { + "name": "tls", + "typedConfig": { + "@type": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext", + "commonTlsContext": { + "tlsParams": { + + }, + "tlsCertificates": [ + { + "certificateChain": { + "inlineString": "-----BEGIN CERTIFICATE-----\nMIICjDCCAjKgAwIBAgIIC5llxGV1gB8wCgYIKoZIzj0EAwIwFDESMBAGA1UEAxMJ\nVGVzdCBDQSAyMB4XDTE5MDMyMjEzNTgyNloXDTI5MDMyMjEzNTgyNlowDjEMMAoG\nA1UEAxMDd2ViMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEADPv1RHVNRfa2VKR\nAB16b6rZnEt7tuhaxCFpQXPj7M2omb0B9Favq5E0ivpNtv1QnFhxtPd7d5k4e+T7\nSkW1TaOCAXIwggFuMA4GA1UdDwEB/wQEAwIDuDAdBgNVHSUEFjAUBggrBgEFBQcD\nAgYIKwYBBQUHAwEwDAYDVR0TAQH/BAIwADBoBgNVHQ4EYQRfN2Q6MDc6ODc6M2E6\nNDA6MTk6NDc6YzM6NWE6YzA6YmE6NjI6ZGY6YWY6NGI6ZDQ6MDU6MjU6NzY6M2Q6\nNWE6OGQ6MTY6OGQ6Njc6NWU6MmU6YTA6MzQ6N2Q6ZGM6ZmYwagYDVR0jBGMwYYBf\nZDE6MTE6MTE6YWM6MmE6YmE6OTc6YjI6M2Y6YWM6N2I6YmQ6ZGE6YmU6YjE6OGE6\nZmM6OWE6YmE6YjU6YmM6ODM6ZTc6NWU6NDE6NmY6ZjI6NzM6OTU6NTg6MGM6ZGIw\nWQYDVR0RBFIwUIZOc3BpZmZlOi8vMTExMTExMTEtMjIyMi0zMzMzLTQ0NDQtNTU1\nNTU1NTU1NTU1LmNvbnN1bC9ucy9kZWZhdWx0L2RjL2RjMS9zdmMvd2ViMAoGCCqG\nSM49BAMCA0gAMEUCIGC3TTvvjj76KMrguVyFf4tjOqaSCRie3nmHMRNNRav7AiEA\npY0heYeK9A6iOLrzqxSerkXXQyj5e9bE4VgUnxgPU6g=\n-----END CERTIFICATE-----\n" + }, + "privateKey": { + "inlineString": "-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEIMoTkpRggp3fqZzFKh82yS4LjtJI+XY+qX/7DefHFrtdoAoGCCqGSM49\nAwEHoUQDQgAEADPv1RHVNRfa2VKRAB16b6rZnEt7tuhaxCFpQXPj7M2omb0B9Fav\nq5E0ivpNtv1QnFhxtPd7d5k4e+T7SkW1TQ==\n-----END EC PRIVATE KEY-----\n" + } + } + ], + "validationContext": { + "trustedCa": { + "inlineString": "-----BEGIN CERTIFICATE-----\nMIICXDCCAgKgAwIBAgIICpZq70Z9LyUwCgYIKoZIzj0EAwIwFDESMBAGA1UEAxMJ\nVGVzdCBDQSAyMB4XDTE5MDMyMjEzNTgyNloXDTI5MDMyMjEzNTgyNlowFDESMBAG\nA1UEAxMJVGVzdCBDQSAyMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEIhywH1gx\nAsMwuF3ukAI5YL2jFxH6Usnma1HFSfVyxbXX1/uoZEYrj8yCAtdU2yoHETyd+Zx2\nThhRLP79pYegCaOCATwwggE4MA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTAD\nAQH/MGgGA1UdDgRhBF9kMToxMToxMTphYzoyYTpiYTo5NzpiMjozZjphYzo3Yjpi\nZDpkYTpiZTpiMTo4YTpmYzo5YTpiYTpiNTpiYzo4MzplNzo1ZTo0MTo2ZjpmMjo3\nMzo5NTo1ODowYzpkYjBqBgNVHSMEYzBhgF9kMToxMToxMTphYzoyYTpiYTo5Nzpi\nMjozZjphYzo3YjpiZDpkYTpiZTpiMTo4YTpmYzo5YTpiYTpiNTpiYzo4MzplNzo1\nZTo0MTo2ZjpmMjo3Mzo5NTo1ODowYzpkYjA/BgNVHREEODA2hjRzcGlmZmU6Ly8x\nMTExMTExMS0yMjIyLTMzMzMtNDQ0NC01NTU1NTU1NTU1NTUuY29uc3VsMAoGCCqG\nSM49BAMCA0gAMEUCICOY0i246rQHJt8o8Oya0D5PLL1FnmsQmQqIGCi31RwnAiEA\noR5f6Ku+cig2Il8T8LJujOp2/2A72QcHZA57B13y+8o=\n-----END CERTIFICATE-----\n" + } + } + }, + "requireClientCertificate": true + } + } + } + ], + "trafficDirection": "INBOUND" + } + ], + "typeUrl": "type.googleapis.com/envoy.config.listener.v3.Listener", + "nonce": "00000001" +} \ No newline at end of file diff --git a/agent/xds/testdata/routes/connect-proxy-with-chain-and-router.latest.golden b/agent/xds/testdata/routes/connect-proxy-with-chain-and-router.latest.golden index 5f48cd9720..adc6611352 100644 --- a/agent/xds/testdata/routes/connect-proxy-with-chain-and-router.latest.golden +++ b/agent/xds/testdata/routes/connect-proxy-with-chain-and-router.latest.golden @@ -286,6 +286,18 @@ } } }, + { + "match": { + "prefix": "/retry-reset" + }, + "route": { + "cluster": "retry-reset.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul", + "retryPolicy": { + "retryOn": "reset", + "numRetries": 15 + } + } + }, { "match": { "prefix": "/retry-codes" @@ -305,12 +317,12 @@ }, { "match": { - "prefix": "/retry-both" + "prefix": "/retry-all" }, "route": { - "cluster": "retry-both.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul", + "cluster": "retry-all.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul", "retryPolicy": { - "retryOn": "connect-failure,retriable-status-codes", + "retryOn": "5xx,gateway-error,reset,connect-failure,envoy-ratelimited,retriable-4xx,refused-stream,cancelled,deadline-exceeded,internal,resource-exhausted,unavailable,retriable-status-codes", "retriableStatusCodes": [ 401, 409, diff --git a/agent/xds/testdata/routes/ingress-with-chain-and-router-header-manip.latest.golden b/agent/xds/testdata/routes/ingress-with-chain-and-router-header-manip.latest.golden index 4f4ade54cb..eaca45d8ef 100644 --- a/agent/xds/testdata/routes/ingress-with-chain-and-router-header-manip.latest.golden +++ b/agent/xds/testdata/routes/ingress-with-chain-and-router-header-manip.latest.golden @@ -287,6 +287,18 @@ } } }, + { + "match": { + "prefix": "/retry-reset" + }, + "route": { + "cluster": "retry-reset.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul", + "retryPolicy": { + "retryOn": "reset", + "numRetries": 15 + } + } + }, { "match": { "prefix": "/retry-codes" @@ -306,12 +318,12 @@ }, { "match": { - "prefix": "/retry-both" + "prefix": "/retry-all" }, "route": { - "cluster": "retry-both.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul", + "cluster": "retry-all.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul", "retryPolicy": { - "retryOn": "connect-failure,retriable-status-codes", + "retryOn": "5xx,gateway-error,reset,connect-failure,envoy-ratelimited,retriable-4xx,refused-stream,cancelled,deadline-exceeded,internal,resource-exhausted,unavailable,retriable-status-codes", "retriableStatusCodes": [ 401, 409, diff --git a/agent/xds/testdata/routes/ingress-with-chain-and-router.latest.golden b/agent/xds/testdata/routes/ingress-with-chain-and-router.latest.golden index 06a8dcc840..a34ea1f68f 100644 --- a/agent/xds/testdata/routes/ingress-with-chain-and-router.latest.golden +++ b/agent/xds/testdata/routes/ingress-with-chain-and-router.latest.golden @@ -287,6 +287,18 @@ } } }, + { + "match": { + "prefix": "/retry-reset" + }, + "route": { + "cluster": "retry-reset.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul", + "retryPolicy": { + "retryOn": "reset", + "numRetries": 15 + } + } + }, { "match": { "prefix": "/retry-codes" @@ -306,12 +318,12 @@ }, { "match": { - "prefix": "/retry-both" + "prefix": "/retry-all" }, "route": { - "cluster": "retry-both.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul", + "cluster": "retry-all.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul", "retryPolicy": { - "retryOn": "connect-failure,retriable-status-codes", + "retryOn": "5xx,gateway-error,reset,connect-failure,envoy-ratelimited,retriable-4xx,refused-stream,cancelled,deadline-exceeded,internal,resource-exhausted,unavailable,retriable-status-codes", "retriableStatusCodes": [ 401, 409, diff --git a/agent/xds/testdata/routes/local-mesh-gateway-with-peered-upstreams.latest.golden b/agent/xds/testdata/routes/local-mesh-gateway-with-peered-upstreams.latest.golden new file mode 100644 index 0000000000..9c050cbe6b --- /dev/null +++ b/agent/xds/testdata/routes/local-mesh-gateway-with-peered-upstreams.latest.golden @@ -0,0 +1,5 @@ +{ + "versionInfo": "00000001", + "typeUrl": "type.googleapis.com/envoy.config.route.v3.RouteConfiguration", + "nonce": "00000001" +} \ No newline at end of file diff --git a/agent/xds/testdata/routes/mesh-gateway-peering-control-plane.latest.golden b/agent/xds/testdata/routes/mesh-gateway-peering-control-plane.latest.golden new file mode 100644 index 0000000000..9c050cbe6b --- /dev/null +++ b/agent/xds/testdata/routes/mesh-gateway-peering-control-plane.latest.golden @@ -0,0 +1,5 @@ +{ + "versionInfo": "00000001", + "typeUrl": "type.googleapis.com/envoy.config.route.v3.RouteConfiguration", + "nonce": "00000001" +} \ No newline at end of file diff --git a/agent/xds/testdata/routes/mesh-gateway-with-imported-peered-services.latest.golden b/agent/xds/testdata/routes/mesh-gateway-with-imported-peered-services.latest.golden new file mode 100644 index 0000000000..9c050cbe6b --- /dev/null +++ b/agent/xds/testdata/routes/mesh-gateway-with-imported-peered-services.latest.golden @@ -0,0 +1,5 @@ +{ + "versionInfo": "00000001", + "typeUrl": "type.googleapis.com/envoy.config.route.v3.RouteConfiguration", + "nonce": "00000001" +} \ No newline at end of file diff --git a/agent/xds/testdata/routes/mesh-gateway-with-peer-through-mesh-gateway-enabled.latest.golden b/agent/xds/testdata/routes/mesh-gateway-with-peer-through-mesh-gateway-enabled.latest.golden new file mode 100644 index 0000000000..9c050cbe6b --- /dev/null +++ b/agent/xds/testdata/routes/mesh-gateway-with-peer-through-mesh-gateway-enabled.latest.golden @@ -0,0 +1,5 @@ +{ + "versionInfo": "00000001", + "typeUrl": "type.googleapis.com/envoy.config.route.v3.RouteConfiguration", + "nonce": "00000001" +} \ No newline at end of file diff --git a/api/api.go b/api/api.go index c92546b50c..fcb7c37306 100644 --- a/api/api.go +++ b/api/api.go @@ -111,6 +111,9 @@ type QueryOptions struct { // by the Config Datacenter string + // Providing a peer name in the query option + Peer string + // AllowStale allows any Consul server (non-leader) to service // a read. This allows for lower latency and higher throughput AllowStale bool @@ -812,6 +815,9 @@ func (r *request) setQueryOptions(q *QueryOptions) { if q.Datacenter != "" { r.params.Set("dc", q.Datacenter) } + if q.Peer != "" { + r.params.Set("peer", q.Peer) + } if q.AllowStale { r.params.Set("stale", "") } diff --git a/api/api_test.go b/api/api_test.go index a95a4044c5..e1fabbb135 100644 --- a/api/api_test.go +++ b/api/api_test.go @@ -96,6 +96,10 @@ func makeClientWithConfig( if server.Config.Bootstrap { server.WaitForLeader(t) } + connectEnabled := server.Config.Connect["enabled"] + if enabled, ok := connectEnabled.(bool); ok && server.Config.Server && enabled { + server.WaitForActiveCARoot(t) + } conf.Address = server.HTTPAddr @@ -763,6 +767,7 @@ func TestAPI_SetQueryOptions(t *testing.T) { Namespace: "operator", Partition: "asdf", Datacenter: "foo", + Peer: "dc10", AllowStale: true, RequireConsistent: true, WaitIndex: 1000, @@ -779,6 +784,9 @@ func TestAPI_SetQueryOptions(t *testing.T) { if r.params.Get("partition") != "asdf" { t.Fatalf("bad: %v", r.params) } + if r.params.Get("peer") != "dc10" { + t.Fatalf("bad: %v", r.params) + } if r.params.Get("dc") != "foo" { t.Fatalf("bad: %v", r.params) } diff --git a/api/config_entry.go b/api/config_entry.go index acdb5bfa86..b1827fb595 100644 --- a/api/config_entry.go +++ b/api/config_entry.go @@ -177,6 +177,10 @@ type UpstreamConfig struct { // MeshGatewayConfig controls how Mesh Gateways are configured and used MeshGateway MeshGatewayConfig `json:",omitempty" alias:"mesh_gateway" ` + + // BalanceOutboundConnections indicates that the proxy should attempt to evenly distribute + // outbound connections across worker threads. Only used by envoy proxies. + BalanceOutboundConnections string `json:",omitempty" alias:"balance_outbound_connections"` } // DestinationConfig represents a virtual service, i.e. one that is external to Consul @@ -223,24 +227,25 @@ type UpstreamLimits struct { } type ServiceConfigEntry struct { - Kind string - Name string - Partition string `json:",omitempty"` - Namespace string `json:",omitempty"` - Protocol string `json:",omitempty"` - Mode ProxyMode `json:",omitempty"` - TransparentProxy *TransparentProxyConfig `json:",omitempty" alias:"transparent_proxy"` - MeshGateway MeshGatewayConfig `json:",omitempty" alias:"mesh_gateway"` - Expose ExposeConfig `json:",omitempty"` - ExternalSNI string `json:",omitempty" alias:"external_sni"` - UpstreamConfig *UpstreamConfiguration `json:",omitempty" alias:"upstream_config"` - Destination *DestinationConfig `json:",omitempty"` - MaxInboundConnections int `json:",omitempty" alias:"max_inbound_connections"` - LocalConnectTimeoutMs int `json:",omitempty" alias:"local_connect_timeout_ms"` - LocalRequestTimeoutMs int `json:",omitempty" alias:"local_request_timeout_ms"` - Meta map[string]string `json:",omitempty"` - CreateIndex uint64 - ModifyIndex uint64 + Kind string + Name string + Partition string `json:",omitempty"` + Namespace string `json:",omitempty"` + Protocol string `json:",omitempty"` + Mode ProxyMode `json:",omitempty"` + TransparentProxy *TransparentProxyConfig `json:",omitempty" alias:"transparent_proxy"` + MeshGateway MeshGatewayConfig `json:",omitempty" alias:"mesh_gateway"` + Expose ExposeConfig `json:",omitempty"` + ExternalSNI string `json:",omitempty" alias:"external_sni"` + UpstreamConfig *UpstreamConfiguration `json:",omitempty" alias:"upstream_config"` + Destination *DestinationConfig `json:",omitempty"` + MaxInboundConnections int `json:",omitempty" alias:"max_inbound_connections"` + LocalConnectTimeoutMs int `json:",omitempty" alias:"local_connect_timeout_ms"` + LocalRequestTimeoutMs int `json:",omitempty" alias:"local_request_timeout_ms"` + BalanceInboundConnections string `json:",omitempty" alias:"balance_inbound_connections"` + Meta map[string]string `json:",omitempty"` + CreateIndex uint64 + ModifyIndex uint64 } func (s *ServiceConfigEntry) GetKind() string { return s.Kind } diff --git a/api/config_entry_discoverychain.go b/api/config_entry_discoverychain.go index f827708ee2..734f9454e4 100644 --- a/api/config_entry_discoverychain.go +++ b/api/config_entry_discoverychain.go @@ -72,6 +72,7 @@ type ServiceRouteDestination struct { NumRetries uint32 `json:",omitempty" alias:"num_retries"` RetryOnConnectFailure bool `json:",omitempty" alias:"retry_on_connect_failure"` RetryOnStatusCodes []uint32 `json:",omitempty" alias:"retry_on_status_codes"` + RetryOn []string `json:",omitempty" alias:"retry_on"` RequestHeaders *HTTPHeaderModifiers `json:",omitempty" alias:"request_headers"` ResponseHeaders *HTTPHeaderModifiers `json:",omitempty" alias:"response_headers"` } diff --git a/api/config_entry_discoverychain_test.go b/api/config_entry_discoverychain_test.go index 8facb72e13..521494f176 100644 --- a/api/config_entry_discoverychain_test.go +++ b/api/config_entry_discoverychain_test.go @@ -272,6 +272,18 @@ func TestAPI_ConfigEntry_DiscoveryChain(t *testing.T) { NumRetries: 5, RetryOnConnectFailure: true, RetryOnStatusCodes: []uint32{500, 503, 401}, + RetryOn: []string{ + "gateway-error", + "reset", + "envoy-ratelimited", + "retriable-4xx", + "refused-stream", + "cancelled", + "deadline-exceeded", + "internal", + "resource-exhausted", + "unavailable", + }, RequestHeaders: &HTTPHeaderModifiers{ Set: map[string]string{ "x-foo": "bar", diff --git a/api/config_entry_exports.go b/api/config_entry_exports.go index 0827e5816b..11cc8b19e7 100644 --- a/api/config_entry_exports.go +++ b/api/config_entry_exports.go @@ -50,8 +50,8 @@ type ServiceConsumer struct { // Deprecated: PeerName should be used for both remote peers and local partitions. Partition string `json:",omitempty"` - // PeerName is the name of the peer to export the service to. - PeerName string `json:",omitempty" alias:"peer_name"` + // Peer is the name of the peer to export the service to. + Peer string `json:",omitempty" alias:"peer_name"` } func (e *ExportedServicesConfigEntry) GetKind() string { return ExportedServices } diff --git a/api/config_entry_exports_test.go b/api/config_entry_exports_test.go index 4a6f3c7a25..fb0c620a02 100644 --- a/api/config_entry_exports_test.go +++ b/api/config_entry_exports_test.go @@ -51,7 +51,7 @@ func TestAPI_ConfigEntries_ExportedServices(t *testing.T) { Namespace: defaultNamespace, Consumers: []ServiceConsumer{ { - PeerName: "alpha", + Peer: "alpha", }, }, }, diff --git a/api/config_entry_gateways.go b/api/config_entry_gateways.go index 56d949ea57..63b323e6ba 100644 --- a/api/config_entry_gateways.go +++ b/api/config_entry_gateways.go @@ -27,6 +27,9 @@ type IngressGatewayConfigEntry struct { Meta map[string]string `json:",omitempty"` + // Defaults is default configuration for all upstream services + Defaults *IngressServiceConfig `json:",omitempty"` + // CreateIndex is the Raft index this entry was created at. This is a // read-only field. CreateIndex uint64 @@ -37,6 +40,12 @@ type IngressGatewayConfigEntry struct { ModifyIndex uint64 } +type IngressServiceConfig struct { + MaxConnections *uint32 + MaxPendingRequests *uint32 + MaxConcurrentRequests *uint32 +} + type GatewayTLSConfig struct { // Indicates that TLS should be enabled for this gateway service. Enabled bool @@ -124,6 +133,10 @@ type IngressService struct { // Allow HTTP header manipulation to be configured. RequestHeaders *HTTPHeaderModifiers `json:",omitempty" alias:"request_headers"` ResponseHeaders *HTTPHeaderModifiers `json:",omitempty" alias:"response_headers"` + + MaxConnections *uint32 `json:",omitempty" alias:"max_connections"` + MaxPendingRequests *uint32 `json:",omitempty" alias:"max_pending_requests"` + MaxConcurrentRequests *uint32 `json:",omitempty" alias:"max_concurrent_requests"` } func (i *IngressGatewayConfigEntry) GetKind() string { return i.Kind } diff --git a/api/config_entry_gateways_test.go b/api/config_entry_gateways_test.go index 0e2acd728d..0602be24fe 100644 --- a/api/config_entry_gateways_test.go +++ b/api/config_entry_gateways_test.go @@ -29,6 +29,10 @@ func TestAPI_ConfigEntries_IngressGateway(t *testing.T) { Enabled: true, TLSMinVersion: "TLSv1_2", }, + Defaults: &IngressServiceConfig{ + MaxConnections: uint32Pointer(2048), + MaxPendingRequests: uint32Pointer(4096), + }, } global := &ProxyConfigEntry{ @@ -93,6 +97,9 @@ func TestAPI_ConfigEntries_IngressGateway(t *testing.T) { CertResource: "bar", }, }, + MaxConnections: uint32Pointer(5120), + MaxPendingRequests: uint32Pointer(512), + MaxConcurrentRequests: uint32Pointer(2048), }, }, TLS: &GatewayTLSConfig{ @@ -168,6 +175,9 @@ func TestAPI_ConfigEntries_IngressGateway(t *testing.T) { require.True(t, ok) require.Equal(t, ingress2.Kind, readIngress.Kind) require.Equal(t, ingress2.Name, readIngress.Name) + require.Equal(t, *ingress2.Defaults.MaxConnections, *readIngress.Defaults.MaxConnections) + require.Equal(t, uint32(4096), *readIngress.Defaults.MaxPendingRequests) + require.Equal(t, uint32(0), *readIngress.Defaults.MaxConcurrentRequests) require.Len(t, readIngress.Listeners, 1) require.Len(t, readIngress.Listeners[0].Services, 1) // Set namespace and partition to blank so that OSS and ent can utilize the same tests diff --git a/api/config_entry_test.go b/api/config_entry_test.go index 1502111d83..376ad6182a 100644 --- a/api/config_entry_test.go +++ b/api/config_entry_test.go @@ -104,9 +104,10 @@ func TestAPI_ConfigEntries(t *testing.T) { "foo": "bar", "gir": "zim", }, - MaxInboundConnections: 5, - LocalConnectTimeoutMs: 5000, - LocalRequestTimeoutMs: 7000, + MaxInboundConnections: 5, + BalanceInboundConnections: "exact_balance", + LocalConnectTimeoutMs: 5000, + LocalRequestTimeoutMs: 7000, } dest := &DestinationConfig{ @@ -148,6 +149,7 @@ func TestAPI_ConfigEntries(t *testing.T) { require.Equal(t, service.Meta, readService.Meta) require.Equal(t, service.Meta, readService.GetMeta()) require.Equal(t, service.MaxInboundConnections, readService.MaxInboundConnections) + require.Equal(t, service.BalanceInboundConnections, readService.BalanceInboundConnections) require.Equal(t, service.LocalConnectTimeoutMs, readService.LocalConnectTimeoutMs) require.Equal(t, service.LocalRequestTimeoutMs, readService.LocalRequestTimeoutMs) @@ -446,6 +448,7 @@ func TestDecodeConfigEntry(t *testing.T) { "OutboundListenerPort": 808, "DialedDirectly": true }, + "BalanceInboundConnections": "exact_balance", "UpstreamConfig": { "Overrides": [ { @@ -454,7 +457,8 @@ func TestDecodeConfigEntry(t *testing.T) { "MaxFailures": 3, "Interval": "2s", "EnforcingConsecutive5xx": 60 - } + }, + "BalanceOutboundConnections": "exact_balance" }, { "Name": "finance--billing", @@ -498,6 +502,7 @@ func TestDecodeConfigEntry(t *testing.T) { OutboundListenerPort: 808, DialedDirectly: true, }, + BalanceInboundConnections: "exact_balance", UpstreamConfig: &UpstreamConfiguration{ Overrides: []*UpstreamConfig{ { @@ -507,6 +512,7 @@ func TestDecodeConfigEntry(t *testing.T) { Interval: 2 * time.Second, EnforcingConsecutive5xx: uint32Pointer(60), }, + BalanceOutboundConnections: "exact_balance", }, { Name: "finance--billing", diff --git a/api/go.mod b/api/go.mod index 64093899a0..61eedbd2bc 100644 --- a/api/go.mod +++ b/api/go.mod @@ -18,8 +18,7 @@ require ( github.com/hashicorp/go-sockaddr v1.0.2 // indirect github.com/hashicorp/go-uuid v1.0.2 github.com/hashicorp/golang-lru v0.5.4 // indirect - github.com/hashicorp/memberlist v0.3.1 // indirect - github.com/hashicorp/serf v0.9.7 + github.com/hashicorp/serf v0.10.1 github.com/kr/pretty v0.2.1 // indirect github.com/kr/text v0.2.0 // indirect github.com/mitchellh/mapstructure v1.4.1 diff --git a/api/go.sum b/api/go.sum index c47deef054..5c271b27b3 100644 --- a/api/go.sum +++ b/api/go.sum @@ -74,11 +74,10 @@ github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+l github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= github.com/hashicorp/mdns v1.0.4/go.mod h1:mtBihi+LeNXGtG8L9dX59gAEa12BDtBQSp4v/YAJqrc= -github.com/hashicorp/memberlist v0.3.0/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE= -github.com/hashicorp/memberlist v0.3.1 h1:MXgUXLqva1QvpVEDQW1IQLG0wivQAtmFlHRQ+1vWZfM= -github.com/hashicorp/memberlist v0.3.1/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE= -github.com/hashicorp/serf v0.9.7 h1:hkdgbqizGQHuU5IPqYM1JdSMV8nKfpuOnZYXssk9muY= -github.com/hashicorp/serf v0.9.7/go.mod h1:TXZNMjZQijwlDvp+r0b63xZ45H7JmCmgg4gpTwn9UV4= +github.com/hashicorp/memberlist v0.5.0 h1:EtYPN8DpAURiapus508I4n9CzHs2W+8NZGbmmR/prTM= +github.com/hashicorp/memberlist v0.5.0/go.mod h1:yvyXLpo0QaGE59Y7hDTsTzDD25JYBZ4mHgHUZ8lrOI0= +github.com/hashicorp/serf v0.10.1 h1:Z1H2J60yRKvfDYAOZLd2MU0ND4AH/WDz7xYHDWQsIPY= +github.com/hashicorp/serf v0.10.1/go.mod h1:yL2t6BqATOLGc5HF7qbFkTfXoPIY0WZdWHfEvMqbG+4= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= @@ -191,8 +190,9 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20220412211240-33da011f77ad h1:ntjMns5wyP/fN65tdBD4g8J5w8n015+iIIs9rtjXkY0= golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10 h1:WIoqL4EROvwiPdUtaip4VcDdpZ4kha7wBWZrbVKCIZg= +golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= diff --git a/api/peering.go b/api/peering.go index 7a98ba9363..7c4d184565 100644 --- a/api/peering.go +++ b/api/peering.go @@ -62,16 +62,27 @@ type Peering struct { PeerServerName string `json:",omitempty"` // PeerServerAddresses contains all the connection addresses for the remote peer. PeerServerAddresses []string `json:",omitempty"` - // ImportedServiceCount is the count of how many services are imported from this peering. - ImportedServiceCount uint64 - // ExportedServiceCount is the count of how many services are exported to this peering. - ExportedServiceCount uint64 + // StreamStatus contains information computed on read based on the state of the stream. + StreamStatus PeeringStreamStatus // CreateIndex is the Raft index at which the Peering was created. CreateIndex uint64 // ModifyIndex is the latest Raft index at which the Peering. was modified. ModifyIndex uint64 } +type PeeringStreamStatus struct { + // ImportedServices is the list of services imported from this peering. + ImportedServices []string + // ExportedServices is the list of services exported to this peering. + ExportedServices []string + // LastHeartbeat represents when the last heartbeat message was received. + LastHeartbeat time.Time + // LastReceive represents when any message was last received, regardless of success or error. + LastReceive time.Time + // LastSend represents when any message was last sent, regardless of success or error. + LastSend time.Time +} + type PeeringReadResponse struct { Peering *Peering } diff --git a/api/peering_test.go b/api/peering_test.go index 9c299b7a21..7a45397a44 100644 --- a/api/peering_test.go +++ b/api/peering_test.go @@ -26,8 +26,7 @@ func peerExistsInPeerListings(peer *Peering, peerings []*Peering) bool { (peer.State == aPeer.State) && (peer.CreateIndex == aPeer.CreateIndex) && (peer.ModifyIndex == aPeer.ModifyIndex) && - (peer.ImportedServiceCount == aPeer.ImportedServiceCount) && - (peer.ExportedServiceCount == aPeer.ExportedServiceCount) + (reflect.DeepEqual(peer.StreamStatus, aPeer.StreamStatus)) if isEqual { return true @@ -42,7 +41,6 @@ func TestAPI_Peering_ACLDeny(t *testing.T) { serverConfig.ACL.Tokens.InitialManagement = "root" serverConfig.ACL.Enabled = true serverConfig.ACL.DefaultPolicy = "deny" - serverConfig.Ports.GRPC = 5300 }) defer s1.Stop() @@ -50,7 +48,6 @@ func TestAPI_Peering_ACLDeny(t *testing.T) { serverConfig.ACL.Tokens.InitialManagement = "root" serverConfig.ACL.Enabled = true serverConfig.ACL.DefaultPolicy = "deny" - serverConfig.Ports.GRPC = 5301 serverConfig.Datacenter = "dc2" }) defer s2.Stop() @@ -263,7 +260,7 @@ func TestAPI_Peering_GenerateToken_ExternalAddresses(t *testing.T) { func TestAPI_Peering_GenerateToken_Read_Establish_Delete(t *testing.T) { t.Parallel() - c, s := makeClient(t) // this is "dc1" + c, s := makeClientWithConfig(t, nil, nil) // this is "dc1" defer s.Stop() s.WaitForSerfCheck(t) diff --git a/api/prepared_query.go b/api/prepared_query.go index 7e0518f580..753aeb0ea4 100644 --- a/api/prepared_query.go +++ b/api/prepared_query.go @@ -21,8 +21,8 @@ type QueryFailoverOptions struct { type QueryDatacenterOptions = QueryFailoverOptions type QueryFailoverTarget struct { - // PeerName specifies a peer to try during failover. - PeerName string + // Peer specifies a peer to try during failover. + Peer string // Datacenter specifies a datacenter to try during failover. Datacenter string diff --git a/build-support/scripts/devtools.sh b/build-support/scripts/devtools.sh index 7e15215028..18b07d8b9e 100755 --- a/build-support/scripts/devtools.sh +++ b/build-support/scripts/devtools.sh @@ -60,7 +60,9 @@ function proto_tools_install { local buf_version local mog_version local protoc_go_inject_tag_version + local mockery_version + mockery_version="$(make --no-print-directory print-MOCKERY_VERSION)" protoc_gen_go_version="$(grep github.com/golang/protobuf go.mod | awk '{print $2}')" protoc_gen_go_grpc_version="$(make --no-print-directory print-PROTOC_GEN_GO_GRPC_VERSION)" mog_version="$(make --no-print-directory print-MOG_VERSION)" @@ -71,6 +73,12 @@ function proto_tools_install { # echo "mog: ${mog_version}" # echo "tag: ${protoc_go_inject_tag_version}" + install_versioned_tool \ + 'mockery' \ + 'github.com/vektra/mockery/v2' \ + "${mockery_version}" \ + 'github.com/vektra/mockery/v2' + install_versioned_tool \ 'buf' \ 'github.com/bufbuild/buf' \ @@ -128,15 +136,6 @@ function lint_install { } function tools_install { - local mockery_version - - mockery_version="$(make --no-print-directory print-MOCKERY_VERSION)" - - install_versioned_tool \ - 'mockery' \ - 'github.com/vektra/mockery/v2' \ - "${mockery_version}" \ - 'github.com/vektra/mockery/v2' lint_install proto_tools_install diff --git a/command/agent/agent.go b/command/agent/agent.go index ae455297a6..8b6a900a90 100644 --- a/command/agent/agent.go +++ b/command/agent/agent.go @@ -18,6 +18,7 @@ import ( "github.com/hashicorp/consul/agent" "github.com/hashicorp/consul/agent/config" + hcpbootstrap "github.com/hashicorp/consul/agent/hcp/bootstrap" "github.com/hashicorp/consul/command/cli" "github.com/hashicorp/consul/command/flags" "github.com/hashicorp/consul/lib" @@ -152,7 +153,7 @@ func (c *cmd) startupJoinWan(agent *agent.Agent, cfg *config.RuntimeConfig) erro func (c *cmd) run(args []string) int { ui := &mcli.PrefixedUi{ OutputPrefix: "==> ", - InfoPrefix: " ", + InfoPrefix: " ", // Note that startupLogger also uses this prefix ErrorPrefix: "==> ", Ui: c.ui, } @@ -175,6 +176,29 @@ func (c *cmd) run(args []string) int { c.configLoadOpts.DefaultConfig = source return config.Load(c.configLoadOpts) } + + // wait for signal + signalCh := make(chan os.Signal, 10) + signal.Notify(signalCh, os.Interrupt, syscall.SIGTERM, syscall.SIGHUP, syscall.SIGPIPE) + + ctx, cancel := context.WithCancel(context.Background()) + + // startup logger is a shim since we need to be about to log both before and + // after logging is setup properly but before agent has started fully. This + // takes care of that! + suLogger := newStartupLogger() + go handleStartupSignals(ctx, cancel, signalCh, suLogger) + + // See if we need to bootstrap config from HCP before we go any further with + // agent startup. We override loader with the one returned as it may be + // modified to include HCP-provided config. + var err error + _, loader, err = hcpbootstrap.MaybeBootstrap(ctx, loader, ui) + if err != nil { + ui.Error(err.Error()) + return 1 + } + bd, err := agent.NewBaseDeps(loader, logGate) if err != nil { ui.Error(err.Error()) @@ -187,6 +211,9 @@ func (c *cmd) run(args []string) int { return 1 } + // Upgrade our startupLogger to use the real logger now we have it + suLogger.SetLogger(c.logger) + config := bd.RuntimeConfig if config.Logging.LogJSON { // Hide all non-error output when JSON logging is enabled. @@ -229,38 +256,6 @@ func (c *cmd) run(args []string) int { ui.Output("Log data will now stream in as it occurs:\n") logGate.Flush() - // wait for signal - signalCh := make(chan os.Signal, 10) - signal.Notify(signalCh, os.Interrupt, syscall.SIGTERM, syscall.SIGHUP, syscall.SIGPIPE) - - ctx, cancel := context.WithCancel(context.Background()) - - go func() { - for { - var sig os.Signal - select { - case s := <-signalCh: - sig = s - case <-ctx.Done(): - return - } - - switch sig { - case syscall.SIGPIPE: - continue - - case syscall.SIGHUP: - err := fmt.Errorf("cannot reload before agent started") - c.logger.Error("Caught", "signal", sig, "error", err) - - default: - c.logger.Info("Caught", "signal", sig) - cancel() - return - } - } - }() - err = agent.Start(ctx) signal.Stop(signalCh) cancel() @@ -362,6 +357,32 @@ func (c *cmd) run(args []string) int { } } +func handleStartupSignals(ctx context.Context, cancel func(), signalCh chan os.Signal, logger *startupLogger) { + for { + var sig os.Signal + select { + case s := <-signalCh: + sig = s + case <-ctx.Done(): + return + } + + switch sig { + case syscall.SIGPIPE: + continue + + case syscall.SIGHUP: + err := fmt.Errorf("cannot reload before agent started") + logger.Error("Caught", "signal", sig, "error", err) + + default: + logger.Info("Caught", "signal", sig) + cancel() + return + } + } +} + func (c *cmd) Synopsis() string { return synopsis } diff --git a/command/agent/startup_logger.go b/command/agent/startup_logger.go new file mode 100644 index 0000000000..f632b307f0 --- /dev/null +++ b/command/agent/startup_logger.go @@ -0,0 +1,64 @@ +package agent + +import ( + "sync" + + "github.com/hashicorp/go-hclog" +) + +// startupLogger is a shim that allows signal handling (and anything else) to +// log to an appropriate output throughout several startup phases. Initially +// when bootstrapping from HCP we need to log caught signals direct to the UI +// output since logging is not setup yet and won't be if we are interrupted +// before we try to start the agent itself. Later, during agent.Start we could +// block retrieving auto TLS or auto-config from servers so need to handle +// signals, but in this case logging has already started so we should log the +// signal event to the logger. +type startupLogger struct { + mu sync.Mutex + logger hclog.Logger +} + +func newStartupLogger() *startupLogger { + return &startupLogger{ + // Start off just using defaults for hclog since this is too early to have + // parsed logging config even and we just want to get _something_ out to the + // user. + logger: hclog.New(&hclog.LoggerOptions{ + Name: "agent.startup", + // Nothing else output in UI has a time prefix until logging is properly + // setup so use the same prefix as other "Info" lines to make it look less + // strange. Note one less space than in PrefixedUI since hclog puts a + // space between the time prefix and log line already. + TimeFormat: " ", + }), + } +} + +func (l *startupLogger) SetLogger(logger hclog.Logger) { + l.mu.Lock() + defer l.mu.Unlock() + + l.logger = logger +} + +func (l *startupLogger) Info(msg string, args ...interface{}) { + l.mu.Lock() + defer l.mu.Unlock() + + l.logger.Info(msg, args...) +} + +func (l *startupLogger) Warn(msg string, args ...interface{}) { + l.mu.Lock() + defer l.mu.Unlock() + + l.logger.Warn(msg, args...) +} + +func (l *startupLogger) Error(msg string, args ...interface{}) { + l.mu.Lock() + defer l.mu.Unlock() + + l.logger.Error(msg, args...) +} diff --git a/command/helpers/helpers_test.go b/command/helpers/helpers_test.go index bbf6492961..d4f426b364 100644 --- a/command/helpers/helpers_test.go +++ b/command/helpers/helpers_test.go @@ -715,7 +715,7 @@ func TestParseConfigEntry(t *testing.T) { }, "destination": { "addresses": [ - "10.0.0.0", + "10.0.0.0", "10.0.0.1" ], "port": 443 @@ -741,7 +741,7 @@ func TestParseConfigEntry(t *testing.T) { }, "Destination": { "Addresses": [ - "10.0.0.0", + "10.0.0.0", "10.0.0.1" ], "Port": 443 @@ -2911,7 +2911,7 @@ func TestParseConfigEntry(t *testing.T) { Partition = "baz" }, { - PeerName = "flarm" + Peer = "flarm" } ] }, @@ -2982,7 +2982,7 @@ func TestParseConfigEntry(t *testing.T) { "Partition": "baz" }, { - "PeerName": "flarm" + "Peer": "flarm" } ] }, @@ -3016,7 +3016,7 @@ func TestParseConfigEntry(t *testing.T) { Partition: "baz", }, { - PeerName: "flarm", + Peer: "flarm", }, }, }, diff --git a/command/peering/establish/establish_test.go b/command/peering/establish/establish_test.go index 4d1d83e364..f24ac2a265 100644 --- a/command/peering/establish/establish_test.go +++ b/command/peering/establish/establish_test.go @@ -11,6 +11,7 @@ import ( "github.com/hashicorp/consul/agent" "github.com/hashicorp/consul/api" + "github.com/hashicorp/consul/sdk/testutil/retry" "github.com/hashicorp/consul/testrpc" ) @@ -84,10 +85,12 @@ func TestEstablishCommand(t *testing.T) { fmt.Sprintf("-peering-token=%s", res.PeeringToken), } - code := cmd.Run(args) - require.Equal(t, 0, code) - output := ui.OutputWriter.String() - require.Contains(t, output, "Success") + retry.Run(t, func(r *retry.R) { + code := cmd.Run(args) + require.Equal(r, 0, code) + output := ui.OutputWriter.String() + require.Contains(r, output, "Success") + }) }) t.Run("establish connection with options", func(t *testing.T) { @@ -107,12 +110,14 @@ func TestEstablishCommand(t *testing.T) { "-meta=region=us-west-1", } - code := cmd.Run(args) - require.Equal(t, 0, code) - output := ui.OutputWriter.String() - require.Contains(t, output, "Success") + retry.Run(t, func(r *retry.R) { + code := cmd.Run(args) + require.Equal(r, 0, code) + output := ui.OutputWriter.String() + require.Contains(r, output, "Success") + }) - //Meta + // Meta peering, _, err := dialingClient.Peerings().Read(context.Background(), "bar", &api.QueryOptions{}) require.NoError(t, err) diff --git a/command/peering/generate/generate_test.go b/command/peering/generate/generate_test.go index c745976104..b23d664d84 100644 --- a/command/peering/generate/generate_test.go +++ b/command/peering/generate/generate_test.go @@ -78,7 +78,7 @@ func TestGenerateCommand(t *testing.T) { require.Equal(t, 0, code) token, err := base64.StdEncoding.DecodeString(ui.OutputWriter.String()) require.NoError(t, err, "error decoding token") - require.Contains(t, string(token), "\"ServerName\":\"server.dc1.consul\"") + require.Contains(t, string(token), "\"ServerName\":\"server.dc1.peering.11111111-2222-3333-4444-555555555555.consul\"") }) t.Run("generate token with options", func(t *testing.T) { @@ -97,13 +97,13 @@ func TestGenerateCommand(t *testing.T) { require.Equal(t, 0, code) token, err := base64.StdEncoding.DecodeString(ui.OutputWriter.String()) require.NoError(t, err, "error decoding token") - require.Contains(t, string(token), "\"ServerName\":\"server.dc1.consul\"") + require.Contains(t, string(token), "\"ServerName\":\"server.dc1.peering.11111111-2222-3333-4444-555555555555.consul\"") - //ServerExternalAddresses + // ServerExternalAddresses require.Contains(t, string(token), "1.2.3.4") require.Contains(t, string(token), "5.6.7.8") - //Meta + // Meta peering, _, err := client.Peerings().Read(context.Background(), "bar", &api.QueryOptions{}) require.NoError(t, err) @@ -136,6 +136,6 @@ func TestGenerateCommand(t *testing.T) { token, err := base64.StdEncoding.DecodeString(outputRes.PeeringToken) require.NoError(t, err, "error decoding token") - require.Contains(t, string(token), "\"ServerName\":\"server.dc1.consul\"") + require.Contains(t, string(token), "\"ServerName\":\"server.dc1.peering.11111111-2222-3333-4444-555555555555.consul\"") }) } diff --git a/command/peering/list/list.go b/command/peering/list/list.go index c445e3d57a..05e481f23f 100644 --- a/command/peering/list/list.go +++ b/command/peering/list/list.go @@ -90,6 +90,7 @@ func (c *cmd) Run(args []string) int { } result := make([]string, 0, len(list)) + // TODO(peering): consider adding more StreamStatus fields here header := "Name\x1fState\x1fImported Svcs\x1fExported Svcs\x1fMeta" result = append(result, header) for _, peer := range list { @@ -99,7 +100,7 @@ func (c *cmd) Run(args []string) int { } meta := strings.Join(metaPairs, ",") line := fmt.Sprintf("%s\x1f%s\x1f%d\x1f%d\x1f%s", - peer.Name, peer.State, peer.ImportedServiceCount, peer.ExportedServiceCount, meta) + peer.Name, peer.State, len(peer.StreamStatus.ImportedServices), len(peer.StreamStatus.ExportedServices), meta) result = append(result, line) } @@ -123,7 +124,7 @@ const ( Usage: consul peering list [options] List all peering connections. The results will be filtered according - to ACL policy configuration. + to ACL policy configuration. Example: diff --git a/command/peering/read/read.go b/command/peering/read/read.go index c8340e19bc..345de80cec 100644 --- a/command/peering/read/read.go +++ b/command/peering/read/read.go @@ -130,9 +130,12 @@ func formatPeering(peering *api.Peering) string { } buffer.WriteString("\n") - buffer.WriteString(fmt.Sprintf("Imported Services: %d\n", peering.ImportedServiceCount)) - buffer.WriteString(fmt.Sprintf("Exported Services: %d\n", peering.ExportedServiceCount)) - + buffer.WriteString(fmt.Sprintf("Imported Services: %d\n", len(peering.StreamStatus.ImportedServices))) + buffer.WriteString(fmt.Sprintf("Exported Services: %d\n", len(peering.StreamStatus.ExportedServices))) + buffer.WriteString("\n") + buffer.WriteString(fmt.Sprintf("Last Heartbeat: %v\n", peering.StreamStatus.LastHeartbeat)) + buffer.WriteString(fmt.Sprintf("Last Send: %v\n", peering.StreamStatus.LastSend)) + buffer.WriteString(fmt.Sprintf("Last Receive: %v\n", peering.StreamStatus.LastReceive)) buffer.WriteString("\n") buffer.WriteString(fmt.Sprintf("Create Index: %d\n", peering.CreateIndex)) buffer.WriteString(fmt.Sprintf("Modify Index: %d\n", peering.ModifyIndex)) diff --git a/command/peering/read/read_test.go b/command/peering/read/read_test.go index fe19e11000..aac0e9e2f1 100644 --- a/command/peering/read/read_test.go +++ b/command/peering/read/read_test.go @@ -109,6 +109,9 @@ func TestReadCommand(t *testing.T) { require.Contains(t, output, "env=production") require.Contains(t, output, "Imported Services") require.Contains(t, output, "Exported Services") + require.Contains(t, output, "Last Heartbeat") + require.Contains(t, output, "Last Send") + require.Contains(t, output, "Last Receive") }) t.Run("read with json", func(t *testing.T) { diff --git a/docs/service-discovery/health-checks.md b/docs/service-discovery/health-checks.md index 8e1251cc53..dc6c42950d 100644 --- a/docs/service-discovery/health-checks.md +++ b/docs/service-discovery/health-checks.md @@ -24,10 +24,11 @@ be reviewed and tested. on `config.Config` in [agent/config/config.go]. 5. Config [Service.Checks](https://www.consul.io/docs/discovery/services) - the `Checks` and `Check` fields on `ServiceDefinition` in [agent/config/config.go]. -6. CLI [consul services register](https://www.consul.io/commands/services/register) - the +6. The returned fields of `ServiceDefinition` in [agent/config/builder.go]. +7. CLI [consul services register](https://www.consul.io/commands/services/register) - the `Checks` and `Check` fields on `api.AgentServiceRegistration`. The entrypoint is `ServicesFromFiles` in [command/services/config.go]. -7. API [/v1/txn](https://www.consul.io/api-docs/txn) - the `Transaction` API allows for registering a check. +8. API [/v1/txn](https://www.consul.io/api-docs/txn) - the `Transaction` API allows for registering a check. [agent/catalog_endpoint.go]: https://github.com/hashicorp/consul/blob/main/agent/catalog_endpoint.go diff --git a/go.mod b/go.mod index 70d58e70dd..3bb513fa0f 100644 --- a/go.mod +++ b/go.mod @@ -21,6 +21,8 @@ require ( github.com/docker/go-connections v0.3.0 github.com/envoyproxy/go-control-plane v0.10.1 github.com/fsnotify/fsnotify v1.5.1 + github.com/go-openapi/runtime v0.19.24 + github.com/go-openapi/strfmt v0.20.0 github.com/golang/protobuf v1.5.0 github.com/google/go-cmp v0.5.8 github.com/google/gofuzz v1.2.0 @@ -37,7 +39,7 @@ require ( github.com/hashicorp/go-cleanhttp v0.5.1 github.com/hashicorp/go-connlimit v0.3.0 github.com/hashicorp/go-discover v0.0.0-20220411141802-20db45f7f0f9 - github.com/hashicorp/go-hclog v0.14.1 + github.com/hashicorp/go-hclog v1.2.1 github.com/hashicorp/go-memdb v1.3.2 github.com/hashicorp/go-multierror v1.1.1 github.com/hashicorp/go-raftchunking v0.6.2 @@ -47,15 +49,17 @@ require ( github.com/hashicorp/go-version v1.2.1 github.com/hashicorp/golang-lru v0.5.4 github.com/hashicorp/hcl v1.0.0 + github.com/hashicorp/hcp-scada-provider v0.1.0 + github.com/hashicorp/hcp-sdk-go v0.23.1-0.20220921131124-49168300a7dc github.com/hashicorp/hil v0.0.0-20200423225030-a18a1cd20038 - github.com/hashicorp/memberlist v0.4.0 + github.com/hashicorp/memberlist v0.5.0 github.com/hashicorp/raft v1.3.9 github.com/hashicorp/raft-autopilot v0.1.6 github.com/hashicorp/raft-boltdb/v2 v2.2.2 - github.com/hashicorp/serf v0.10.0 + github.com/hashicorp/serf v0.10.1 github.com/hashicorp/vault/api v1.0.5-0.20200717191844-f687267c8086 github.com/hashicorp/vault/sdk v0.1.14-0.20200519221838-e0cfd64bc267 - github.com/hashicorp/yamux v0.0.0-20210826001029-26ff87cf9493 + github.com/hashicorp/yamux v0.0.0-20211028200310-0bc27b27de87 github.com/imdario/mergo v0.3.13 github.com/kr/text v0.2.0 github.com/miekg/dns v1.1.41 @@ -78,11 +82,11 @@ require ( go.uber.org/goleak v1.1.10 golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a golang.org/x/net v0.0.0-20211216030914-fe4d6282115f - golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d + golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58 golang.org/x/sync v0.0.0-20210220032951-036812b2e83c golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10 golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e - google.golang.org/genproto v0.0.0-20200623002339-fbb79eadd5eb + google.golang.org/genproto v0.0.0-20200825200019-8632dd797987 google.golang.org/grpc v1.37.1 google.golang.org/protobuf v1.27.1 gopkg.in/square/go-jose.v2 v2.5.1 @@ -93,7 +97,7 @@ require ( ) require ( - cloud.google.com/go v0.59.0 // indirect + cloud.google.com/go v0.65.0 // indirect github.com/Azure/azure-sdk-for-go v44.0.0+incompatible // indirect github.com/Azure/go-autorest v14.2.0+incompatible // indirect github.com/Azure/go-autorest/autorest v0.11.18 // indirect @@ -107,6 +111,9 @@ require ( github.com/Azure/go-autorest/tracing v0.6.0 // indirect github.com/DataDog/datadog-go v3.2.0+incompatible // indirect github.com/Microsoft/go-winio v0.4.3 // indirect + github.com/PuerkitoBio/purell v1.1.1 // indirect + github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect + github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/bgentry/speakeasy v0.1.0 // indirect github.com/boltdb/bolt v1.3.1 // indirect @@ -120,10 +127,20 @@ require ( github.com/digitalocean/godo v1.10.0 // indirect github.com/dimchansky/utfbom v1.1.0 // indirect github.com/envoyproxy/protoc-gen-validate v0.1.0 // indirect - github.com/fatih/color v1.9.0 // indirect + github.com/fatih/color v1.13.0 // indirect github.com/form3tech-oss/jwt-go v3.2.2+incompatible // indirect github.com/frankban/quicktest v1.11.0 // indirect github.com/go-ole/go-ole v1.2.6 // indirect + github.com/go-openapi/analysis v0.20.0 // indirect + github.com/go-openapi/errors v0.20.1 // indirect + github.com/go-openapi/jsonpointer v0.19.5 // indirect + github.com/go-openapi/jsonreference v0.19.5 // indirect + github.com/go-openapi/loads v0.20.2 // indirect + github.com/go-openapi/spec v0.20.3 // indirect + github.com/go-openapi/swag v0.19.14 // indirect + github.com/go-openapi/validate v0.20.2 // indirect + github.com/go-ozzo/ozzo-validation v3.6.0+incompatible // indirect + github.com/go-stack/stack v1.8.0 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e // indirect github.com/golang/snappy v0.0.1 // indirect @@ -134,21 +151,25 @@ require ( github.com/gophercloud/gophercloud v0.1.0 // indirect github.com/hashicorp/errwrap v1.0.0 // indirect github.com/hashicorp/go-immutable-radix v1.3.0 // indirect - github.com/hashicorp/go-msgpack v0.5.5 // indirect + github.com/hashicorp/go-msgpack v1.1.5 // indirect github.com/hashicorp/go-retryablehttp v0.6.7 // indirect github.com/hashicorp/go-rootcerts v1.0.2 // indirect github.com/hashicorp/mdns v1.0.4 // indirect + github.com/hashicorp/net-rpc-msgpackrpc v0.0.0-20151116020338-a14192a58a69 // indirect github.com/hashicorp/raft-boltdb v0.0.0-20211202195631-7d34b9fb3f42 // indirect github.com/hashicorp/vic v1.5.1-0.20190403131502-bbfe86ec9443 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect + github.com/josharian/intern v1.0.0 // indirect github.com/joyent/triton-go v1.7.1-0.20200416154420-6801d15b779f // indirect github.com/json-iterator/go v1.1.9 // indirect github.com/konsorten/go-windows-terminal-sequences v1.0.2 // indirect github.com/linode/linodego v0.7.1 // indirect github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect - github.com/mattn/go-colorable v0.1.6 // indirect - github.com/mattn/go-isatty v0.0.12 // indirect + github.com/mailru/easyjson v0.7.6 // indirect + github.com/mattn/go-colorable v0.1.12 // indirect + github.com/mattn/go-isatty v0.0.14 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect + github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.1 // indirect @@ -166,6 +187,7 @@ require ( github.com/ryanuber/go-glob v1.0.0 // indirect github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 // indirect github.com/sirupsen/logrus v1.4.2 // indirect + github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 // indirect github.com/softlayer/softlayer-go v0.0.0-20180806151055-260589d94c7d // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/stretchr/objx v0.4.0 // indirect @@ -175,17 +197,18 @@ require ( github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926 // indirect github.com/vmware/govmomi v0.18.0 // indirect github.com/yusufpapurcu/wmi v1.2.2 // indirect - go.opencensus.io v0.22.3 // indirect + go.mongodb.org/mongo-driver v1.4.6 // indirect + go.opencensus.io v0.22.4 // indirect go.opentelemetry.io/proto/otlp v0.7.0 // indirect golang.org/x/lint v0.0.0-20200302205851-738671d3881b // indirect golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 // indirect golang.org/x/text v0.3.6 // indirect golang.org/x/tools v0.1.0 // indirect - google.golang.org/api v0.28.0 // indirect + google.golang.org/api v0.30.0 // indirect google.golang.org/appengine v1.6.6 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/resty.v1 v1.12.0 // indirect - gopkg.in/yaml.v2 v2.2.8 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect k8s.io/klog v1.0.0 // indirect k8s.io/utils v0.0.0-20200324210504-a9aa75ae1b89 // indirect diff --git a/go.sum b/go.sum index a5fdbdd250..997d179ec8 100644 --- a/go.sum +++ b/go.sum @@ -11,8 +11,9 @@ cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6 cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= -cloud.google.com/go v0.59.0 h1:BM3svUDU3itpc2m5cu5wCyThIYNDlFlts9GASw31GW8= -cloud.google.com/go v0.59.0/go.mod h1:qJxNOVCRTxHfwLhvDxxSI9vQc1zI59b9pEglp1Iv60E= +cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= +cloud.google.com/go v0.65.0 h1:Dg9iHVQfrhq82rUNu9ZxUDrJLaxFUe/HlCVaLyRruq8= +cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= @@ -29,6 +30,7 @@ cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiy cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= +cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/Azure/azure-sdk-for-go v44.0.0+incompatible h1:e82Yv2HNpS0kuyeCrV29OPKvEiqfs2/uJHic3/3iKdg= github.com/Azure/azure-sdk-for-go v44.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= @@ -77,13 +79,20 @@ github.com/NYTimes/gziphandler v1.0.1 h1:iLrQrdwjDd52kHDA5op2UBJFjmOb9g+7scBan4R github.com/NYTimes/gziphandler v1.0.1/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= +github.com/PuerkitoBio/purell v1.1.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= +github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI= +github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= +github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M= +github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/abdullin/seq v0.0.0-20160510034733-d5467c17e7af h1:DBNMBMuMiWYu0b+8KMJuWmfCkcxl09JwdlqwDZZ6U14= github.com/abdullin/seq v0.0.0-20160510034733-d5467c17e7af/go.mod h1:5Jv4cbFiHJMsVxt52+i0Ha45fjshj6wxYr1r19tB9bw= +github.com/agnivade/levenshtein v1.0.1/go.mod h1:CURSv5d9Uaml+FovSIICkLbAUZ9S4RqaHDIsdSBg7lM= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e h1:QEF07wC0T1rKkctt1RINW/+RMTVmiwxETico2l3gxJA= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= @@ -97,8 +106,15 @@ github.com/armon/go-metrics v0.3.10/go.mod h1:4O98XIr/9W0sxpJ8UaYkvjk10Iff7SnFrb github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/armon/go-radix v1.0.0 h1:F4z6KzEeeQIMeLFa97iZU6vupzoecKdU5TX24SNppXI= github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= +github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= +github.com/asaskevich/govalidator v0.0.0-20200108200545-475eaeb16496/go.mod h1:oGkLhpf+kjZl6xBf758TQhh5XrAeiJv/7FRz/2spLIg= +github.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535/go.mod h1:oGkLhpf+kjZl6xBf758TQhh5XrAeiJv/7FRz/2spLIg= +github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef h1:46PFijGLmAjMPwCCCo7Jf0W6f9slllCkkv7vyc1yOSg= +github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= github.com/aws/aws-sdk-go v1.25.37/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/aws/aws-sdk-go v1.25.41/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= +github.com/aws/aws-sdk-go v1.34.28/go.mod h1:H7NKnBqNVzoTJpGfLrQkkD+ytBA93eiDYi/+8rV9s48= github.com/aws/aws-sdk-go v1.42.34 h1:fqGAiKmCSRY1rEa4G9VqgkKKbNmLKYq5dKmLtQkvYi8= github.com/aws/aws-sdk-go v1.42.34/go.mod h1:OGr6lGMAKGlG9CVrYnWYDKIyb829c6EVBRjxqjmPepc= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= @@ -155,6 +171,8 @@ github.com/dnaeon/go-vcr v1.0.1 h1:r8L/HqC0Hje5AXMu1ooW8oyQyOFv4GxqpL0nRP7SLLY= github.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E= github.com/docker/go-connections v0.3.0 h1:3lOnM9cSzgGwx8VfK/NGOW5fLQ0GjIlCkaktF+n1M6o= github.com/docker/go-connections v0.3.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= +github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= @@ -170,8 +188,9 @@ github.com/envoyproxy/protoc-gen-validate v0.1.0 h1:EQciDnbrYxy13PgWoY8AqoxGiPrp github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= -github.com/fatih/color v1.9.0 h1:8xPHl4/q1VyqGIPif1F+1V3Y3lSmrq01EabUW3CoW5s= github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= +github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= +github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= github.com/form3tech-oss/jwt-go v3.2.2+incompatible h1:TcekIExNqud5crz4xD2pavyTgWiPvpYe4Xau31I0PRk= github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= @@ -182,6 +201,8 @@ github.com/fsnotify/fsnotify v1.5.1 h1:mZcQUHVQUQWoPXXtuf9yuEXKudkV2sx1E06UadKWp github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU= github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= +github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= github.com/go-asn1-ber/asn1-ber v1.3.1/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= @@ -194,14 +215,132 @@ github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= +github.com/go-openapi/analysis v0.0.0-20180825180245-b006789cd277/go.mod h1:k70tL6pCuVxPJOHXQ+wIac1FUrvNkHolPie/cLEU6hI= +github.com/go-openapi/analysis v0.17.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik= +github.com/go-openapi/analysis v0.18.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik= +github.com/go-openapi/analysis v0.19.2/go.mod h1:3P1osvZa9jKjb8ed2TPng3f0i/UY9snX6gxi44djMjk= +github.com/go-openapi/analysis v0.19.4/go.mod h1:3P1osvZa9jKjb8ed2TPng3f0i/UY9snX6gxi44djMjk= +github.com/go-openapi/analysis v0.19.5/go.mod h1:hkEAkxagaIvIP7VTn8ygJNkd4kAYON2rCu0v0ObL0AU= +github.com/go-openapi/analysis v0.19.10/go.mod h1:qmhS3VNFxBlquFJ0RGoDtylO9y4pgTAUNE9AEEMdlJQ= +github.com/go-openapi/analysis v0.19.16/go.mod h1:GLInF007N83Ad3m8a/CbQ5TPzdnGT7workfHwuVjNVk= +github.com/go-openapi/analysis v0.20.0 h1:UN09o0kNhleunxW7LR+KnltD0YrJ8FF03pSqvAN3Vro= +github.com/go-openapi/analysis v0.20.0/go.mod h1:BMchjvaHDykmRMsK40iPtvyOfFdMMxlOmQr9FBZk+Og= +github.com/go-openapi/errors v0.17.0/go.mod h1:LcZQpmvG4wyF5j4IhA73wkLFQg+QJXOQHVjmcZxhka0= +github.com/go-openapi/errors v0.18.0/go.mod h1:LcZQpmvG4wyF5j4IhA73wkLFQg+QJXOQHVjmcZxhka0= +github.com/go-openapi/errors v0.19.2/go.mod h1:qX0BLWsyaKfvhluLejVpVNwNRdXZhEbTA4kxxpKBC94= +github.com/go-openapi/errors v0.19.3/go.mod h1:qX0BLWsyaKfvhluLejVpVNwNRdXZhEbTA4kxxpKBC94= +github.com/go-openapi/errors v0.19.6/go.mod h1:cM//ZKUKyO06HSwqAelJ5NsEMMcpa6VpXe8DOa1Mi1M= +github.com/go-openapi/errors v0.19.7/go.mod h1:cM//ZKUKyO06HSwqAelJ5NsEMMcpa6VpXe8DOa1Mi1M= +github.com/go-openapi/errors v0.19.8/go.mod h1:cM//ZKUKyO06HSwqAelJ5NsEMMcpa6VpXe8DOa1Mi1M= +github.com/go-openapi/errors v0.19.9/go.mod h1:cM//ZKUKyO06HSwqAelJ5NsEMMcpa6VpXe8DOa1Mi1M= +github.com/go-openapi/errors v0.20.1 h1:j23mMDtRxMwIobkpId7sWh7Ddcx4ivaoqUbfXx5P+a8= +github.com/go-openapi/errors v0.20.1/go.mod h1:cM//ZKUKyO06HSwqAelJ5NsEMMcpa6VpXe8DOa1Mi1M= github.com/go-openapi/jsonpointer v0.0.0-20160704185906-46af16f9f7b1/go.mod h1:+35s3my2LFTysnkMfxsJBAMHj/DoqoB9knIWoYG/Vk0= +github.com/go-openapi/jsonpointer v0.17.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M= +github.com/go-openapi/jsonpointer v0.18.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M= +github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg= +github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= +github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY= +github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= github.com/go-openapi/jsonreference v0.0.0-20160704190145-13c6e3589ad9/go.mod h1:W3Z9FmVs9qj+KR4zFKmDPGiLdk1D9Rlm7cyMvf57TTg= +github.com/go-openapi/jsonreference v0.17.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I= +github.com/go-openapi/jsonreference v0.18.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I= +github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc= +github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8= +github.com/go-openapi/jsonreference v0.19.5 h1:1WJP/wi4OjB4iV8KVbH73rQaoialJrqv8gitZLxGLtM= +github.com/go-openapi/jsonreference v0.19.5/go.mod h1:RdybgQwPxbL4UEjuAruzK1x3nE69AqPYEJeo/TWfEeg= +github.com/go-openapi/loads v0.17.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU= +github.com/go-openapi/loads v0.18.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU= +github.com/go-openapi/loads v0.19.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU= +github.com/go-openapi/loads v0.19.2/go.mod h1:QAskZPMX5V0C2gvfkGZzJlINuP7Hx/4+ix5jWFxsNPs= +github.com/go-openapi/loads v0.19.3/go.mod h1:YVfqhUCdahYwR3f3iiwQLhicVRvLlU/WO5WPaZvcvSI= +github.com/go-openapi/loads v0.19.5/go.mod h1:dswLCAdonkRufe/gSUC3gN8nTSaB9uaS2es0x5/IbjY= +github.com/go-openapi/loads v0.19.6/go.mod h1:brCsvE6j8mnbmGBh103PT/QLHfbyDxA4hsKvYBNEGVc= +github.com/go-openapi/loads v0.19.7/go.mod h1:brCsvE6j8mnbmGBh103PT/QLHfbyDxA4hsKvYBNEGVc= +github.com/go-openapi/loads v0.20.0/go.mod h1:2LhKquiE513rN5xC6Aan6lYOSddlL8Mp20AW9kpviM4= +github.com/go-openapi/loads v0.20.2 h1:z5p5Xf5wujMxS1y8aP+vxwW5qYT2zdJBbXKmQUG3lcc= +github.com/go-openapi/loads v0.20.2/go.mod h1:hTVUotJ+UonAMMZsvakEgmWKgtulweO9vYP2bQYKA/o= +github.com/go-openapi/runtime v0.0.0-20180920151709-4f900dc2ade9/go.mod h1:6v9a6LTXWQCdL8k1AO3cvqx5OtZY/Y9wKTgaoP6YRfA= +github.com/go-openapi/runtime v0.19.0/go.mod h1:OwNfisksmmaZse4+gpV3Ne9AyMOlP1lt4sK4FXt0O64= +github.com/go-openapi/runtime v0.19.4/go.mod h1:X277bwSUBxVlCYR3r7xgZZGKVvBd/29gLDlFGtJ8NL4= +github.com/go-openapi/runtime v0.19.15/go.mod h1:dhGWCTKRXlAfGnQG0ONViOZpjfg0m2gUt9nTQPQZuoo= +github.com/go-openapi/runtime v0.19.16/go.mod h1:5P9104EJgYcizotuXhEuUrzVc+j1RiSjahULvYmlv98= +github.com/go-openapi/runtime v0.19.24 h1:TqagMVlRAOTwllE/7hNKx6rQ10O6T8ZzeJdMjSTKaD4= +github.com/go-openapi/runtime v0.19.24/go.mod h1:Lm9YGCeecBnUUkFTxPC4s1+lwrkJ0pthx8YvyjCfkgk= github.com/go-openapi/spec v0.0.0-20160808142527-6aced65f8501/go.mod h1:J8+jY1nAiCcj+friV/PDoE1/3eeccG9LYBs0tYvLOWc= +github.com/go-openapi/spec v0.17.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI= +github.com/go-openapi/spec v0.18.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI= +github.com/go-openapi/spec v0.19.2/go.mod h1:sCxk3jxKgioEJikev4fgkNmwS+3kuYdJtcsZsD5zxMY= +github.com/go-openapi/spec v0.19.3/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo= +github.com/go-openapi/spec v0.19.6/go.mod h1:Hm2Jr4jv8G1ciIAo+frC/Ft+rR2kQDh8JHKHb3gWUSk= +github.com/go-openapi/spec v0.19.8/go.mod h1:Hm2Jr4jv8G1ciIAo+frC/Ft+rR2kQDh8JHKHb3gWUSk= +github.com/go-openapi/spec v0.19.15/go.mod h1:+81FIL1JwC5P3/Iuuozq3pPE9dXdIEGxFutcFKaVbmU= +github.com/go-openapi/spec v0.20.0/go.mod h1:+81FIL1JwC5P3/Iuuozq3pPE9dXdIEGxFutcFKaVbmU= +github.com/go-openapi/spec v0.20.1/go.mod h1:93x7oh+d+FQsmsieroS4cmR3u0p/ywH649a3qwC9OsQ= +github.com/go-openapi/spec v0.20.3 h1:uH9RQ6vdyPSs2pSy9fL8QPspDF2AMIMPtmK5coSSjtQ= +github.com/go-openapi/spec v0.20.3/go.mod h1:gG4F8wdEDN+YPBMVnzE85Rbhf+Th2DTvA9nFPQ5AYEg= +github.com/go-openapi/strfmt v0.17.0/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pLP5l494TcyU= +github.com/go-openapi/strfmt v0.18.0/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pLP5l494TcyU= +github.com/go-openapi/strfmt v0.19.0/go.mod h1:+uW+93UVvGGq2qGaZxdDeJqSAqBqBdl+ZPMF/cC8nDY= +github.com/go-openapi/strfmt v0.19.2/go.mod h1:0yX7dbo8mKIvc3XSKp7MNfxw4JytCfCD6+bY1AVL9LU= +github.com/go-openapi/strfmt v0.19.3/go.mod h1:0yX7dbo8mKIvc3XSKp7MNfxw4JytCfCD6+bY1AVL9LU= +github.com/go-openapi/strfmt v0.19.4/go.mod h1:eftuHTlB/dI8Uq8JJOyRlieZf+WkkxUuk0dgdHXr2Qk= +github.com/go-openapi/strfmt v0.19.5/go.mod h1:eftuHTlB/dI8Uq8JJOyRlieZf+WkkxUuk0dgdHXr2Qk= +github.com/go-openapi/strfmt v0.19.11/go.mod h1:UukAYgTaQfqJuAFlNxxMWNvMYiwiXtLsF2VwmoFtbtc= +github.com/go-openapi/strfmt v0.20.0 h1:l2omNtmNbMc39IGptl9BuXBEKcZfS8zjrTsPKTiJiDM= +github.com/go-openapi/strfmt v0.20.0/go.mod h1:UukAYgTaQfqJuAFlNxxMWNvMYiwiXtLsF2VwmoFtbtc= github.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87/go.mod h1:DXUve3Dpr1UfpPtxFw+EFuQ41HhCWZfha5jSVRG7C7I= +github.com/go-openapi/swag v0.17.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg= +github.com/go-openapi/swag v0.18.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg= +github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= +github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= +github.com/go-openapi/swag v0.19.7/go.mod h1:ao+8BpOPyKdpQz3AOJfbeEVpLmWAvlT1IfTe5McPyhY= +github.com/go-openapi/swag v0.19.9/go.mod h1:ao+8BpOPyKdpQz3AOJfbeEVpLmWAvlT1IfTe5McPyhY= +github.com/go-openapi/swag v0.19.12/go.mod h1:eFdyEBkTdoAf/9RXBvj4cr1nH7GD8Kzo5HTt47gr72M= +github.com/go-openapi/swag v0.19.13/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= +github.com/go-openapi/swag v0.19.14 h1:gm3vOOXfiuw5i9p5N9xJvfjvuofpyvLA9Wr6QfK5Fng= +github.com/go-openapi/swag v0.19.14/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= +github.com/go-openapi/validate v0.18.0/go.mod h1:Uh4HdOzKt19xGIGm1qHf/ofbX1YQ4Y+MYsct2VUrAJ4= +github.com/go-openapi/validate v0.19.2/go.mod h1:1tRCw7m3jtI8eNWEEliiAqUIcBztB2KDnRCRMUi7GTA= +github.com/go-openapi/validate v0.19.3/go.mod h1:90Vh6jjkTn+OT1Eefm0ZixWNFjhtOH7vS9k0lo6zwJo= +github.com/go-openapi/validate v0.19.10/go.mod h1:RKEZTUWDkxKQxN2jDT7ZnZi2bhZlbNMAuKvKB+IaGx8= +github.com/go-openapi/validate v0.19.12/go.mod h1:Rzou8hA/CBw8donlS6WNEUQupNvUZ0waH08tGe6kAQ4= +github.com/go-openapi/validate v0.19.15/go.mod h1:tbn/fdOwYHgrhPBzidZfJC2MIVvs9GA7monOmWBbeCI= +github.com/go-openapi/validate v0.20.1/go.mod h1:b60iJT+xNNLfaQJUqLI7946tYiFEOuE9E4k54HpKcJ0= +github.com/go-openapi/validate v0.20.2 h1:AhqDegYV3J3iQkMPJSXkvzymHKMTw0BST3RK3hTT4ts= +github.com/go-openapi/validate v0.20.2/go.mod h1:e7OJoKNgd0twXZwIn0A43tHbvIcr/rZIVCbJBpTUoY0= +github.com/go-ozzo/ozzo-validation v3.6.0+incompatible h1:msy24VGS42fKO9K1vLz82/GeYW1cILu7Nuuj1N3BBkE= +github.com/go-ozzo/ozzo-validation v3.6.0+incompatible/go.mod h1:gsEKFIVnabGBt6mXmxK0MoFy+cZoTJY6mu5Ll3LVLBU= +github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= +github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-test/deep v1.0.2-0.20181118220953-042da051cf31/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= github.com/go-test/deep v1.0.2 h1:onZX1rnHT3Wv6cqNgYyFOOlgVKJrksuCMCRvJStbMYw= github.com/go-test/deep v1.0.2/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= +github.com/gobuffalo/attrs v0.0.0-20190224210810-a9411de4debd/go.mod h1:4duuawTqi2wkkpB4ePgWMaai6/Kc6WEz83bhFwpHzj0= +github.com/gobuffalo/depgen v0.0.0-20190329151759-d478694a28d3/go.mod h1:3STtPUQYuzV0gBVOY3vy6CfMm/ljR4pABfrTeHNLHUY= +github.com/gobuffalo/depgen v0.1.0/go.mod h1:+ifsuy7fhi15RWncXQQKjWS9JPkdah5sZvtHc2RXGlg= +github.com/gobuffalo/envy v1.6.15/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI= +github.com/gobuffalo/envy v1.7.0/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI= +github.com/gobuffalo/flect v0.1.0/go.mod h1:d2ehjJqGOH/Kjqcoz+F7jHTBbmDb38yXA598Hb50EGs= +github.com/gobuffalo/flect v0.1.1/go.mod h1:8JCgGVbRjJhVgD6399mQr4fx5rRfGKVzFjbj6RE/9UI= +github.com/gobuffalo/flect v0.1.3/go.mod h1:8JCgGVbRjJhVgD6399mQr4fx5rRfGKVzFjbj6RE/9UI= +github.com/gobuffalo/genny v0.0.0-20190329151137-27723ad26ef9/go.mod h1:rWs4Z12d1Zbf19rlsn0nurr75KqhYp52EAGGxTbBhNk= +github.com/gobuffalo/genny v0.0.0-20190403191548-3ca520ef0d9e/go.mod h1:80lIj3kVJWwOrXWWMRzzdhW3DsrdjILVil/SFKBzF28= +github.com/gobuffalo/genny v0.1.0/go.mod h1:XidbUqzak3lHdS//TPu2OgiFB+51Ur5f7CSnXZ/JDvo= +github.com/gobuffalo/genny v0.1.1/go.mod h1:5TExbEyY48pfunL4QSXxlDOmdsD44RRq4mVZ0Ex28Xk= +github.com/gobuffalo/gitgen v0.0.0-20190315122116-cc086187d211/go.mod h1:vEHJk/E9DmhejeLeNt7UVvlSGv3ziL+djtTr3yyzcOw= +github.com/gobuffalo/gogen v0.0.0-20190315121717-8f38393713f5/go.mod h1:V9QVDIxsgKNZs6L2IYiGR8datgMhB577vzTDqypH360= +github.com/gobuffalo/gogen v0.1.0/go.mod h1:8NTelM5qd8RZ15VjQTFkAW6qOMx5wBbW4dSCS3BY8gg= +github.com/gobuffalo/gogen v0.1.1/go.mod h1:y8iBtmHmGc4qa3urIyo1shvOD8JftTtfcKi+71xfDNE= +github.com/gobuffalo/logger v0.0.0-20190315122211-86e12af44bc2/go.mod h1:QdxcLw541hSGtBnhUc4gaNIXRjiDppFGaDqzbrBd3v8= +github.com/gobuffalo/mapi v1.0.1/go.mod h1:4VAGh89y6rVOvm5A8fKFxYG+wIW6LO1FMTG9hnKStFc= +github.com/gobuffalo/mapi v1.0.2/go.mod h1:4VAGh89y6rVOvm5A8fKFxYG+wIW6LO1FMTG9hnKStFc= +github.com/gobuffalo/packd v0.0.0-20190315124812-a385830c7fc0/go.mod h1:M2Juc+hhDXf/PnmBANFCqx4DM3wRbgDvnVWeG2RIxq4= +github.com/gobuffalo/packd v0.1.0/go.mod h1:M2Juc+hhDXf/PnmBANFCqx4DM3wRbgDvnVWeG2RIxq4= +github.com/gobuffalo/packr/v2 v2.0.9/go.mod h1:emmyGweYTm6Kdper+iywB6YK5YzuKchGtJQZ0Odn4pQ= +github.com/gobuffalo/packr/v2 v2.2.0/go.mod h1:CaAwI0GPIAv+5wKLtv8Afwl+Cm78K/I/VCm/3ptBN+0= +github.com/gobuffalo/syncx v0.0.0-20190224160051-33c29581e754/go.mod h1:HhnNqWY95UYwwW3uSASeV7vtgYkT2t16hJgV3AEPUpw= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= @@ -220,6 +359,7 @@ github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFU github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= github.com/golang/protobuf v0.0.0-20161109072736-4bd1920723d7/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= @@ -248,6 +388,7 @@ github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= @@ -262,19 +403,22 @@ github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/ github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200507031123-427632fa3b1c/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22 h1:ub2sxhs2A0HRa2dWHavvmWxiVGXNfE9wI+gcTMwED8A= github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/tcpproxy v0.0.0-20180808230851-dfa16c61dad2 h1:AtvtonGEH/fZK0XPNNBdB6swgy7Iudfx88wzyIpwqJ8= github.com/google/tcpproxy v0.0.0-20180808230851-dfa16c61dad2/go.mod h1:DavVbd41y+b7ukKDmlnPR4nGYmkWXR6vHUkjQNiHPBs= +github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM= @@ -313,8 +457,9 @@ github.com/hashicorp/go-hclog v0.0.0-20180709165350-ff2cf002a8dd/go.mod h1:9bjs9 github.com/hashicorp/go-hclog v0.9.1/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= github.com/hashicorp/go-hclog v0.12.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= -github.com/hashicorp/go-hclog v0.14.1 h1:nQcJDQwIAGnmoUWp8ubocEX40cCml/17YkF6csQLReU= github.com/hashicorp/go-hclog v0.14.1/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= +github.com/hashicorp/go-hclog v1.2.1 h1:YQsLlGDJgwhXFpucSPyVbCBviQtjlHv3jLTlp8YmtEw= +github.com/hashicorp/go-hclog v1.2.1/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= github.com/hashicorp/go-immutable-radix v1.3.0 h1:8exGP7ego3OmkfksihtSouGMZ+hQrhxx+FVELeXpVPE= github.com/hashicorp/go-immutable-radix v1.3.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= @@ -322,8 +467,9 @@ github.com/hashicorp/go-kms-wrapping/entropy v0.1.0/go.mod h1:d1g9WGtAunDNpek8jU github.com/hashicorp/go-memdb v1.3.2 h1:RBKHOsnSszpU6vxq80LzC2BaQjuuvoyaQbkLTf7V7g8= github.com/hashicorp/go-memdb v1.3.2/go.mod h1:Mluclgwib3R93Hk5fxEfiRhB+6Dar64wWh71LpNSe3g= github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= -github.com/hashicorp/go-msgpack v0.5.5 h1:i9R9JSrqIz0QVLz3sz+i3YJdT7TTSLcfLLzJi9aZTuI= github.com/hashicorp/go-msgpack v0.5.5/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= +github.com/hashicorp/go-msgpack v1.1.5 h1:9byZdVjKTe5mce63pRVNP1L7UAmdHOTEMGehn6KvJWs= +github.com/hashicorp/go-msgpack v1.1.5/go.mod h1:gWVc3sv/wbDmR3rQsj1CAktEZzoz1YNK9NfGLXJ69/4= github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA= github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= @@ -355,16 +501,20 @@ github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+l github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/hashicorp/hcp-scada-provider v0.1.0 h1:FSjTw7EBl6GJFv5533harm1vw15OaEYodNGHde908MI= +github.com/hashicorp/hcp-scada-provider v0.1.0/go.mod h1:8Pp3pBLzZ9DL56OHSbf55qhh+TpvmXBuR5cJx9jcdcA= +github.com/hashicorp/hcp-sdk-go v0.23.1-0.20220921131124-49168300a7dc h1:on26TCKYnX7JzZCtwkR/LWHSqMu40PoZ6h/0e6Pq8ug= +github.com/hashicorp/hcp-sdk-go v0.23.1-0.20220921131124-49168300a7dc/go.mod h1:/9UoDY2FYYA8lFaKBb2HmM/jKYZGANmf65q9QRc/cVw= github.com/hashicorp/hil v0.0.0-20200423225030-a18a1cd20038 h1:n9J0rwVWXDpNd5iZnwY7w4WZyq53/rROeI7OVvLW8Ok= github.com/hashicorp/hil v0.0.0-20200423225030-a18a1cd20038/go.mod h1:n2TSygSNwsLJ76m8qFXTSc7beTb+auJxYdqrnoqwZWE= github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= github.com/hashicorp/mdns v1.0.1/go.mod h1:4gW7WsVCke5TE7EPeYliwHlRUyBtfCwuFwuMg2DmyNY= github.com/hashicorp/mdns v1.0.4 h1:sY0CMhFmjIPDMlTB+HfymFHCaYLhgifZ0QhjaYKD/UQ= github.com/hashicorp/mdns v1.0.4/go.mod h1:mtBihi+LeNXGtG8L9dX59gAEa12BDtBQSp4v/YAJqrc= -github.com/hashicorp/memberlist v0.3.0/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE= -github.com/hashicorp/memberlist v0.3.1/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE= -github.com/hashicorp/memberlist v0.4.0 h1:k3uda5gZcltmafuFF+UFqNEl5PrH+yPZ4zkjp1f/H/8= -github.com/hashicorp/memberlist v0.4.0/go.mod h1:yvyXLpo0QaGE59Y7hDTsTzDD25JYBZ4mHgHUZ8lrOI0= +github.com/hashicorp/memberlist v0.5.0 h1:EtYPN8DpAURiapus508I4n9CzHs2W+8NZGbmmR/prTM= +github.com/hashicorp/memberlist v0.5.0/go.mod h1:yvyXLpo0QaGE59Y7hDTsTzDD25JYBZ4mHgHUZ8lrOI0= +github.com/hashicorp/net-rpc-msgpackrpc v0.0.0-20151116020338-a14192a58a69 h1:lc3c72qGlIMDqQpQH82Y4vaglRMMFdJbziYWriR4UcE= +github.com/hashicorp/net-rpc-msgpackrpc v0.0.0-20151116020338-a14192a58a69/go.mod h1:/z+jUGRBlwVpUZfjute9jWaF6/HuhjuFQuL1YXzVD1Q= github.com/hashicorp/raft v1.1.0/go.mod h1:4Ak7FSPnuvmb0GV6vgIAJ4vYT4bek9bb6Q+7HVbyzqM= github.com/hashicorp/raft v1.1.1/go.mod h1:vPAJM8Asw6u8LxC3eJCUZmRP/E4QmUGE1R7g7k8sG/8= github.com/hashicorp/raft v1.2.0/go.mod h1:vPAJM8Asw6u8LxC3eJCUZmRP/E4QmUGE1R7g7k8sG/8= @@ -378,9 +528,8 @@ github.com/hashicorp/raft-boltdb v0.0.0-20211202195631-7d34b9fb3f42 h1:Ye8SofeDH github.com/hashicorp/raft-boltdb v0.0.0-20211202195631-7d34b9fb3f42/go.mod h1:wcXL8otVu5cpJVLjcmq7pmfdRCdaP+xnvu7WQcKJAhs= github.com/hashicorp/raft-boltdb/v2 v2.2.2 h1:rlkPtOllgIcKLxVT4nutqlTH2NRFn+tO1wwZk/4Dxqw= github.com/hashicorp/raft-boltdb/v2 v2.2.2/go.mod h1:N8YgaZgNJLpZC+h+by7vDu5rzsRgONThTEeUS3zWbfY= -github.com/hashicorp/serf v0.9.7/go.mod h1:TXZNMjZQijwlDvp+r0b63xZ45H7JmCmgg4gpTwn9UV4= -github.com/hashicorp/serf v0.10.0 h1:89qvvpfMQnz6c2y4pv7j2vUUmeT1+5TSZMexuTbtsPs= -github.com/hashicorp/serf v0.10.0/go.mod h1:bXN03oZc5xlH46k/K1qTrpXb9ERKyY1/i/N5mxvgrZw= +github.com/hashicorp/serf v0.10.1 h1:Z1H2J60yRKvfDYAOZLd2MU0ND4AH/WDz7xYHDWQsIPY= +github.com/hashicorp/serf v0.10.1/go.mod h1:yL2t6BqATOLGc5HF7qbFkTfXoPIY0WZdWHfEvMqbG+4= github.com/hashicorp/vault/api v1.0.5-0.20200717191844-f687267c8086 h1:OKsyxKi2sNmqm1Gv93adf2AID2FOBFdCbbZn9fGtIdg= github.com/hashicorp/vault/api v1.0.5-0.20200717191844-f687267c8086/go.mod h1:R3Umvhlxi2TN7Ex2hzOowyeNb+SfbVWI973N+ctaFMk= github.com/hashicorp/vault/sdk v0.1.14-0.20200519221838-e0cfd64bc267 h1:e1ok06zGrWJW91rzRroyl5nRNqraaBe4d5hiKcVZuHM= @@ -388,8 +537,8 @@ github.com/hashicorp/vault/sdk v0.1.14-0.20200519221838-e0cfd64bc267/go.mod h1:W github.com/hashicorp/vic v1.5.1-0.20190403131502-bbfe86ec9443 h1:O/pT5C1Q3mVXMyuqg7yuAWUg/jMZR1/0QTzTRdNR6Uw= github.com/hashicorp/vic v1.5.1-0.20190403131502-bbfe86ec9443/go.mod h1:bEpDU35nTu0ey1EXjwNwPjI9xErAsoOCmcMb9GKvyxo= github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= -github.com/hashicorp/yamux v0.0.0-20210826001029-26ff87cf9493 h1:brI5vBRUlAlM34VFmnLPwjnCL/FxAJp9XvOdX6Zt+XE= -github.com/hashicorp/yamux v0.0.0-20210826001029-26ff87cf9493/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ= +github.com/hashicorp/yamux v0.0.0-20211028200310-0bc27b27de87 h1:xixZ2bWeofWV68J+x6AzmKuVM/JWCQwkWm6GW/MUR6I= +github.com/hashicorp/yamux v0.0.0-20211028200310-0bc27b27de87/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= @@ -407,7 +556,10 @@ github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9Y 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= github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= +github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= +github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/joyent/triton-go v0.0.0-20180628001255-830d2b111e62/go.mod h1:U+RSyWxWd04xTqnuOQxnai7XGS2PrPY2cfGoDKtMHjA= github.com/joyent/triton-go v1.7.1-0.20200416154420-6801d15b779f h1:ENpDacvnr8faw5ugQmEF1QYk+f/Y9lXFvuYmRxykago= github.com/joyent/triton-go v1.7.1-0.20200416154420-6801d15b779f/go.mod h1:KDSfL7qe5ZfQqvlDMkVjCztbmcpp/c8M77vhQP8ZPvk= @@ -418,10 +570,13 @@ github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/u github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/karrick/godirwalk v1.8.0/go.mod h1:H5KPZjojv4lE+QYImBI8xVtrBRgYrIVsaRPx4tDPEn4= +github.com/karrick/godirwalk v1.10.3/go.mod h1:RoGL9dQei4vP9ilrpETWE8CLOZ1kiN0LhBygSwrAsHA= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/compress v1.9.5/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.2 h1:DB17ag19krx9CFsz4o3enTrPXyIXCl+2iCXH/aMAp9s= github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= @@ -431,6 +586,7 @@ github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfn github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= @@ -441,16 +597,28 @@ github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.7.1/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs= +github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA= +github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/markbates/oncer v0.0.0-20181203154359-bf2de49a0be2/go.mod h1:Ld9puTsIW75CHf65OeIOkyKbteujpZVXDpWK6YGZbxE= +github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= -github.com/mattn/go-colorable v0.1.6 h1:6Su7aK7lXmJ/U79bYtBjLNaha4Fs1Rg9plHpcH+vvnE= github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40= +github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= -github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= +github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= @@ -461,6 +629,8 @@ github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJys github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= github.com/mitchellh/cli v1.1.0 h1:tEElEatulEHDeedTxwckzyYMA5c86fbmNIUL1hBIiTg= github.com/mitchellh/cli v1.1.0/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI= +github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db h1:62I3jR2EmQ4l5rM/4FEfDWcRD+abF5XlKShorW5LRoQ= +github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db/go.mod h1:l0dey0ia/Uv7NcFFVbCLtqEBQbrT4OCwCSKTEv6enCw= github.com/mitchellh/copystructure v1.0.0 h1:Laisrj+bAB6b/yJwB5Bt3ITZhGJdqmxquMKeZ+mmkFQ= github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= @@ -477,6 +647,8 @@ github.com/mitchellh/hashstructure/v2 v2.0.2/go.mod h1:MG3aRVU/N29oo/V/IhBX8GR/z github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.3.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/mapstructure v1.3.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/mapstructure v1.4.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/mapstructure v1.4.1 h1:CpVNEelQCZBooIPDn+AR3NpivK/TIKU8bDxdASFVQag= github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/pointerstructure v1.2.1 h1:ZhBBeX8tSlRpu/FFhXH4RC4OJzFlqsQhoHZAz4x7TIw= @@ -490,11 +662,14 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJ github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc= github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= github.com/nicolai86/scaleway-sdk v1.10.2-0.20180628010248-798f60e20bb2 h1:BQ1HW7hr4IVovMwWg0E0PYcyW8CzqDcVmaew9cujU4s= github.com/nicolai86/scaleway-sdk v1.10.2-0.20180628010248-798f60e20bb2/go.mod h1:TLb2Sg7HQcgGdloNxkrmtgDNR9uVYF3lfdFIN4Ro6Sk= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/olekukonko/tablewriter v0.0.0-20180130162743-b8a9be070da4/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= @@ -510,7 +685,10 @@ github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0Mw github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= +github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +github.com/pelletier/go-toml v1.4.0/go.mod h1:PN7xzY2wHTK0K9p34ErDQMlFxa51Fk0OUruD3k1mMwo= +github.com/pelletier/go-toml v1.7.0/go.mod h1:vwGMzjaWMwyfHwgIBhI2YUM4fB6nL6lVAvS1LBMMhTE= github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= github.com/pierrec/lz4 v2.5.2+incompatible h1:WCjObylUIOlKy/+7Abdn34TLIkXiA4UWUMhxq9m9ZXI= @@ -558,6 +736,8 @@ github.com/renier/xmlrpc v0.0.0-20170708154548-ce4a1a486c03 h1:Wdi9nwnhFNAlseAOe github.com/renier/xmlrpc v0.0.0-20170708154548-ce4a1a486c03/go.mod h1:gRAiPF5C5Nd0eyyRdqIu9qTiFSoZzpTq727b5B8fkkU= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= +github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.2.2/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rs/zerolog v1.4.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= @@ -572,13 +752,18 @@ github.com/sean-/conswriter v0.0.0-20180208195008-f5ae3917a627/go.mod h1:7zjs06q github.com/sean-/pager v0.0.0-20180208200047-666be9bf53b5/go.mod h1:BeybITEsBEg6qbIiqJ6/Bqeq25bCLbL7YFmpaFfJDuM= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 h1:nn5Wsu0esKSJiIVhscUtVbo7ada43DJhG55ua/hjS5I= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= +github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= github.com/shirou/gopsutil/v3 v3.22.8 h1:a4s3hXogo5mE2PfdfJIonDbstO/P+9JszdfhAHSzD9Y= github.com/shirou/gopsutil/v3 v3.22.8/go.mod h1:s648gW4IywYzUfE/KjXxUsqrqx/T2xO5VqOXxONeRfI= github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= github.com/sirupsen/logrus v1.0.6/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.4.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 h1:JIAuq3EEf9cgbU6AtGPK4CTG3Zf6CKMNqf0MHTggAUA= +github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966/go.mod h1:sUM3LWHvSMaG192sy56D9F7CNvL7jUJVXoqM1QKLnog= github.com/softlayer/softlayer-go v0.0.0-20180806151055-260589d94c7d h1:bVQRCxQvfjNUeRqaY/uT0tFuvuFY0ulgnczuR684Xic= github.com/softlayer/softlayer-go v0.0.0-20180806151055-260589d94c7d/go.mod h1:Cw4GTlQccdRGSEf6KiMju767x0NEHE0YIVPJSaXjlsw= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= @@ -587,6 +772,7 @@ github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B github.com/spf13/afero v1.2.1/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= @@ -597,6 +783,7 @@ github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DM github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= github.com/stretchr/objx v0.4.0 h1:M2gUjqZET1qApGOWNSnZ49BAIMX4F/1plDv3+l31EJ4= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= @@ -606,10 +793,13 @@ github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5 github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/tencentcloud/tencentcloud-sdk-go v1.0.162 h1:8fDzz4GuVg4skjY2B0nMN7h6uN61EDVkuLyI2+qGHhI= github.com/tencentcloud/tencentcloud-sdk-go v1.0.162/go.mod h1:asUz5BPXxgoPGaRgZaVm1iGcUAuHyYUo1nXqKa83cvI= +github.com/tidwall/pretty v1.0.0 h1:HsD+QiTn7sK6flMKIvNmpqz1qrpP3Ps6jOKIKMooyg4= +github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= github.com/tklauser/go-sysconf v0.3.10 h1:IJ1AZGZRWbY8T5Vfk04D9WOA5WSejdflXxP03OUqALw= github.com/tklauser/go-sysconf v0.3.10/go.mod h1:C8XykCvCb+Gn0oNCWPIlcb0RuglQTYaQ2hGm7jmxEFk= github.com/tklauser/numcpus v0.4.0 h1:E53Dm1HjH1/R2/aoCtXtPgzmElmn51aOkhCFSuZq//o= @@ -619,23 +809,36 @@ github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926 h1:G3dpKMzFDjgEh2q1Z github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= +github.com/vektah/gqlparser v1.1.2/go.mod h1:1ycwN7Ij5njmMkPPAOaRFY4rET2Enx7IkVv3vaXspKw= github.com/vmware/govmomi v0.18.0 h1:f7QxSmP7meCtoAmiKZogvVbLInT+CZx6Px6K5rYsJZo= github.com/vmware/govmomi v0.18.0/go.mod h1:URlwyTFZX72RmxtxuaFL2Uj3fD1JTvZdx59bHWk6aFU= +github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c/go.mod h1:lB8K/P019DLNhemzwFU4jHLhdvlE6uDZjXFejJXr49I= +github.com/xdg/stringprep v0.0.0-20180714160509-73f8eece6fdc/go.mod h1:Jhud4/sHMO4oL310DaZAKk9ZaJ08SJfe+sJh0HrGL1Y= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yusufpapurcu/wmi v1.2.2 h1:KBNDSne4vP5mbSWnJbO+51IMOXJB67QiYCSBrubbPRg= github.com/yusufpapurcu/wmi v1.2.2/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/bbolt v1.3.5 h1:XAzx9gjCb0Rxj7EoqcClPD1d5ZBxZJk0jbuoPHenBt0= go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ= +go.mongodb.org/mongo-driver v1.0.3/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= +go.mongodb.org/mongo-driver v1.1.1/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= +go.mongodb.org/mongo-driver v1.3.0/go.mod h1:MSWZXKOynuguX+JSvwP8i+58jYCXxbia8HS3gZBapIE= +go.mongodb.org/mongo-driver v1.3.4/go.mod h1:MSWZXKOynuguX+JSvwP8i+58jYCXxbia8HS3gZBapIE= +go.mongodb.org/mongo-driver v1.4.3/go.mod h1:WcMNYLx/IlOxLe6JRJiv2uXuCz6zBLndR4SoGjYphSc= +go.mongodb.org/mongo-driver v1.4.4/go.mod h1:WcMNYLx/IlOxLe6JRJiv2uXuCz6zBLndR4SoGjYphSc= +go.mongodb.org/mongo-driver v1.4.6 h1:rh7GdYmDrb8AQSkF8yteAus8qYOgOASWDOv1BWqBXkU= +go.mongodb.org/mongo-driver v1.4.6/go.mod h1:WcMNYLx/IlOxLe6JRJiv2uXuCz6zBLndR4SoGjYphSc= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.3 h1:8sGtKOrtQqkN1bp2AtX+misvLIlOmsEsNd+9NIcPEm8= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.4 h1:LYy1Hy3MJdrCdMwwzxA/dRok4ejH+RwNGbuoD9fCjto= +go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opentelemetry.io/proto/otlp v0.7.0 h1:rwOQPCuKAKmwGKq2aVNnYIibI6wnV7EvzgfTCzcdGg8= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= @@ -648,10 +851,14 @@ golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnf golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190320223903-b7391e95e576/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190418165655-df01cb2cc480/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= +golang.org/x/crypto v0.0.0-20190422162423-af44ce270edf/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190530122614-20be4c3c3ed5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190617133340-57b3e21c3d56/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= @@ -694,6 +901,7 @@ golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73r golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181005035420-146acd28ed58/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -701,6 +909,7 @@ golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73r golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190320064053-1272bf9dcd53/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= @@ -708,7 +917,9 @@ golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -723,8 +934,14 @@ golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/ golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200602114024-627f9648deb9/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210410081132-afb366fc7cd1/go.mod h1:9tjilg8BloeKEkVJvy7fQ90B1CfIiPueXVOjqfkSzI8= golang.org/x/net v0.0.0-20211216030914-fe4d6282115f h1:hEYJvxw1lSnWIl8X9ofsYMklzaDs90JI2az5YMd4fPM= @@ -733,15 +950,18 @@ golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAG 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= golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d h1:TzXSXBo42m9gQenoE3b9BGiEpg5IG2JkU5FkPIawgtw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58 h1:Mj83v+wSRNEar42a/MQgxk9X42TdEmrOl9i+y8WbxLo= +golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= 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= golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190412183630-56d357773e84/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -759,14 +979,18 @@ golang.org/x/sys v0.0.0-20190209173611-3b5209105503/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190321052220-f7bb7a8bee54/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190419153524-e8e3143a4f4a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190515120540-06a5c4944438/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190523142557-0e01d883c5c5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190531175056-4c3a928424d2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -791,6 +1015,7 @@ golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -799,8 +1024,10 @@ golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10 h1:WIoqL4EROvwiPdUtaip4VcDdpZ4kha7wBWZrbVKCIZg= golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -812,6 +1039,8 @@ 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= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -825,14 +1054,22 @@ golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGm golang.org/x/tools v0.0.0-20181011042414-1f849cf54d09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190125232054-d66bd3c5d5a6/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190329151228-23e29df326fe/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190416151739-9c9e1878f421/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190420181800-aa740d480789/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190424220101-1e8e1cfdf96b/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190531172133-b3315ee88b7d/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190617190820-da514acc4774/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190624222133-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= @@ -862,8 +1099,11 @@ golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWc golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200622203043-20e05c1c8ffa/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.0 h1:po9/4sTYwZU9lPhi1tOrb4hCv3qrhiQ77LZfGa2OjwY= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= @@ -884,8 +1124,10 @@ google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/ google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= -google.golang.org/api v0.28.0 h1:jMF5hhVfMkTZwHW1SDpKq5CkgWLXOb31Foaca9Zr3oM= google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= +google.golang.org/api v0.30.0 h1:yfrXXP61wVuLb0vBcG6qaOoIoqYEzOQS8jum51jkv2w= +google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= 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= @@ -919,8 +1161,11 @@ google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfG google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto v0.0.0-20200623002339-fbb79eadd5eb h1:PUcq6RTy8Gp9xukBme8m2+2Z8pQCmJ7TbPpQd6xNDvk= -google.golang.org/genproto v0.0.0-20200623002339-fbb79eadd5eb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= +google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200825200019-8632dd797987 h1:PDIOdWxZ8eRizhKa1AAvY53xsvLB1cWorMjslvY3VA8= +google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 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= @@ -934,6 +1179,8 @@ google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8 google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= +google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.37.1 h1:ARnQJNWxGyYJpdf/JXscNlQr/uv607ZPU9Z7ogHi+iI= @@ -955,8 +1202,9 @@ gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4 gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= 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-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2/go.mod h1:Xk6kEKp8OKb+X14hQBKWaSkCsqBpgog8nAV2xsGOxlo= @@ -973,9 +1221,14 @@ gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/lib/retry/retry.go b/lib/retry/retry.go index 59a979fbcc..72ea79afa5 100644 --- a/lib/retry/retry.go +++ b/lib/retry/retry.go @@ -96,7 +96,9 @@ func (w *Waiter) Failures() int { // Every call to Wait increments the failures count, so Reset must be called // after Wait when there wasn't a failure. // -// Wait will return ctx.Err() if the context is cancelled. +// The only non-nil error that Wait returns will come from ctx.Err(), +// such as when the context is canceled. This makes it suitable for +// long-running routines that do not get re-initialized, such as replication. func (w *Waiter) Wait(ctx context.Context) error { w.failures++ timer := time.NewTimer(w.delay()) @@ -108,3 +110,10 @@ func (w *Waiter) Wait(ctx context.Context) error { return nil } } + +// NextWait returns the period the next call to Wait with block for assuming +// it's context is not cancelled. It's useful for informing a user how long +// it will be before the next attempt is made. +func (w *Waiter) NextWait() time.Duration { + return w.delay() +} diff --git a/proto-public/pbdns/dns.pb.binary.go b/proto-public/pbdns/dns.pb.binary.go new file mode 100644 index 0000000000..486c4ff887 --- /dev/null +++ b/proto-public/pbdns/dns.pb.binary.go @@ -0,0 +1,28 @@ +// Code generated by protoc-gen-go-binary. DO NOT EDIT. +// source: proto-public/pbdns/dns.proto + +package pbdns + +import ( + "github.com/golang/protobuf/proto" +) + +// MarshalBinary implements encoding.BinaryMarshaler +func (msg *QueryRequest) MarshalBinary() ([]byte, error) { + return proto.Marshal(msg) +} + +// UnmarshalBinary implements encoding.BinaryUnmarshaler +func (msg *QueryRequest) UnmarshalBinary(b []byte) error { + return proto.Unmarshal(b, msg) +} + +// MarshalBinary implements encoding.BinaryMarshaler +func (msg *QueryResponse) MarshalBinary() ([]byte, error) { + return proto.Marshal(msg) +} + +// UnmarshalBinary implements encoding.BinaryUnmarshaler +func (msg *QueryResponse) UnmarshalBinary(b []byte) error { + return proto.Unmarshal(b, msg) +} diff --git a/proto-public/pbdns/dns.pb.go b/proto-public/pbdns/dns.pb.go new file mode 100644 index 0000000000..cf136c2bd3 --- /dev/null +++ b/proto-public/pbdns/dns.pb.go @@ -0,0 +1,298 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.26.0-rc.1 +// protoc (unknown) +// source: proto-public/pbdns/dns.proto + +package pbdns + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type Protocol int32 + +const ( + Protocol_PROTOCOL_UNSET_UNSPECIFIED Protocol = 0 + Protocol_PROTOCOL_TCP Protocol = 1 + Protocol_PROTOCOL_UDP Protocol = 2 +) + +// Enum value maps for Protocol. +var ( + Protocol_name = map[int32]string{ + 0: "PROTOCOL_UNSET_UNSPECIFIED", + 1: "PROTOCOL_TCP", + 2: "PROTOCOL_UDP", + } + Protocol_value = map[string]int32{ + "PROTOCOL_UNSET_UNSPECIFIED": 0, + "PROTOCOL_TCP": 1, + "PROTOCOL_UDP": 2, + } +) + +func (x Protocol) Enum() *Protocol { + p := new(Protocol) + *p = x + return p +} + +func (x Protocol) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (Protocol) Descriptor() protoreflect.EnumDescriptor { + return file_proto_public_pbdns_dns_proto_enumTypes[0].Descriptor() +} + +func (Protocol) Type() protoreflect.EnumType { + return &file_proto_public_pbdns_dns_proto_enumTypes[0] +} + +func (x Protocol) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use Protocol.Descriptor instead. +func (Protocol) EnumDescriptor() ([]byte, []int) { + return file_proto_public_pbdns_dns_proto_rawDescGZIP(), []int{0} +} + +type QueryRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // msg is the DNS request message. + Msg []byte `protobuf:"bytes,1,opt,name=msg,proto3" json:"msg,omitempty"` + // protocol is the protocol of the request + Protocol Protocol `protobuf:"varint,2,opt,name=protocol,proto3,enum=hashicorp.consul.dns.Protocol" json:"protocol,omitempty"` +} + +func (x *QueryRequest) Reset() { + *x = QueryRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_public_pbdns_dns_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *QueryRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*QueryRequest) ProtoMessage() {} + +func (x *QueryRequest) ProtoReflect() protoreflect.Message { + mi := &file_proto_public_pbdns_dns_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use QueryRequest.ProtoReflect.Descriptor instead. +func (*QueryRequest) Descriptor() ([]byte, []int) { + return file_proto_public_pbdns_dns_proto_rawDescGZIP(), []int{0} +} + +func (x *QueryRequest) GetMsg() []byte { + if x != nil { + return x.Msg + } + return nil +} + +func (x *QueryRequest) GetProtocol() Protocol { + if x != nil { + return x.Protocol + } + return Protocol_PROTOCOL_UNSET_UNSPECIFIED +} + +type QueryResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // msg is the DNS reply message. + Msg []byte `protobuf:"bytes,1,opt,name=msg,proto3" json:"msg,omitempty"` +} + +func (x *QueryResponse) Reset() { + *x = QueryResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_public_pbdns_dns_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *QueryResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*QueryResponse) ProtoMessage() {} + +func (x *QueryResponse) ProtoReflect() protoreflect.Message { + mi := &file_proto_public_pbdns_dns_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use QueryResponse.ProtoReflect.Descriptor instead. +func (*QueryResponse) Descriptor() ([]byte, []int) { + return file_proto_public_pbdns_dns_proto_rawDescGZIP(), []int{1} +} + +func (x *QueryResponse) GetMsg() []byte { + if x != nil { + return x.Msg + } + return nil +} + +var File_proto_public_pbdns_dns_proto protoreflect.FileDescriptor + +var file_proto_public_pbdns_dns_proto_rawDesc = []byte{ + 0x0a, 0x1c, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2d, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x2f, 0x70, + 0x62, 0x64, 0x6e, 0x73, 0x2f, 0x64, 0x6e, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x14, + 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, + 0x2e, 0x64, 0x6e, 0x73, 0x22, 0x5c, 0x0a, 0x0c, 0x51, 0x75, 0x65, 0x72, 0x79, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x6d, 0x73, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x0c, 0x52, 0x03, 0x6d, 0x73, 0x67, 0x12, 0x3a, 0x0a, 0x08, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, + 0x6f, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1e, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, + 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x64, 0x6e, 0x73, 0x2e, + 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x52, 0x08, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, + 0x6f, 0x6c, 0x22, 0x21, 0x0a, 0x0d, 0x51, 0x75, 0x65, 0x72, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x6d, 0x73, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, + 0x52, 0x03, 0x6d, 0x73, 0x67, 0x2a, 0x4e, 0x0a, 0x08, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, + 0x6c, 0x12, 0x1e, 0x0a, 0x1a, 0x50, 0x52, 0x4f, 0x54, 0x4f, 0x43, 0x4f, 0x4c, 0x5f, 0x55, 0x4e, + 0x53, 0x45, 0x54, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, + 0x00, 0x12, 0x10, 0x0a, 0x0c, 0x50, 0x52, 0x4f, 0x54, 0x4f, 0x43, 0x4f, 0x4c, 0x5f, 0x54, 0x43, + 0x50, 0x10, 0x01, 0x12, 0x10, 0x0a, 0x0c, 0x50, 0x52, 0x4f, 0x54, 0x4f, 0x43, 0x4f, 0x4c, 0x5f, + 0x55, 0x44, 0x50, 0x10, 0x02, 0x32, 0x60, 0x0a, 0x0a, 0x44, 0x4e, 0x53, 0x53, 0x65, 0x72, 0x76, + 0x69, 0x63, 0x65, 0x12, 0x52, 0x0a, 0x05, 0x51, 0x75, 0x65, 0x72, 0x79, 0x12, 0x22, 0x2e, 0x68, + 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, + 0x64, 0x6e, 0x73, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x23, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, + 0x73, 0x75, 0x6c, 0x2e, 0x64, 0x6e, 0x73, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0xc6, 0x01, 0x0a, 0x18, 0x63, 0x6f, 0x6d, 0x2e, + 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, + 0x2e, 0x64, 0x6e, 0x73, 0x42, 0x08, 0x44, 0x6e, 0x73, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, + 0x5a, 0x2e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x68, 0x61, 0x73, + 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2f, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2f, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x2d, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x2f, 0x70, 0x62, 0x64, 0x6e, 0x73, + 0xa2, 0x02, 0x03, 0x48, 0x43, 0x44, 0xaa, 0x02, 0x14, 0x48, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, + 0x72, 0x70, 0x2e, 0x43, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x44, 0x6e, 0x73, 0xca, 0x02, 0x14, + 0x48, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x5c, 0x43, 0x6f, 0x6e, 0x73, 0x75, 0x6c, + 0x5c, 0x44, 0x6e, 0x73, 0xe2, 0x02, 0x20, 0x48, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, + 0x5c, 0x43, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x5c, 0x44, 0x6e, 0x73, 0x5c, 0x47, 0x50, 0x42, 0x4d, + 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0xea, 0x02, 0x16, 0x48, 0x61, 0x73, 0x68, 0x69, 0x63, + 0x6f, 0x72, 0x70, 0x3a, 0x3a, 0x43, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x3a, 0x3a, 0x44, 0x6e, 0x73, + 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_proto_public_pbdns_dns_proto_rawDescOnce sync.Once + file_proto_public_pbdns_dns_proto_rawDescData = file_proto_public_pbdns_dns_proto_rawDesc +) + +func file_proto_public_pbdns_dns_proto_rawDescGZIP() []byte { + file_proto_public_pbdns_dns_proto_rawDescOnce.Do(func() { + file_proto_public_pbdns_dns_proto_rawDescData = protoimpl.X.CompressGZIP(file_proto_public_pbdns_dns_proto_rawDescData) + }) + return file_proto_public_pbdns_dns_proto_rawDescData +} + +var file_proto_public_pbdns_dns_proto_enumTypes = make([]protoimpl.EnumInfo, 1) +var file_proto_public_pbdns_dns_proto_msgTypes = make([]protoimpl.MessageInfo, 2) +var file_proto_public_pbdns_dns_proto_goTypes = []interface{}{ + (Protocol)(0), // 0: hashicorp.consul.dns.Protocol + (*QueryRequest)(nil), // 1: hashicorp.consul.dns.QueryRequest + (*QueryResponse)(nil), // 2: hashicorp.consul.dns.QueryResponse +} +var file_proto_public_pbdns_dns_proto_depIdxs = []int32{ + 0, // 0: hashicorp.consul.dns.QueryRequest.protocol:type_name -> hashicorp.consul.dns.Protocol + 1, // 1: hashicorp.consul.dns.DNSService.Query:input_type -> hashicorp.consul.dns.QueryRequest + 2, // 2: hashicorp.consul.dns.DNSService.Query:output_type -> hashicorp.consul.dns.QueryResponse + 2, // [2:3] is the sub-list for method output_type + 1, // [1:2] is the sub-list for method input_type + 1, // [1:1] is the sub-list for extension type_name + 1, // [1:1] is the sub-list for extension extendee + 0, // [0:1] is the sub-list for field type_name +} + +func init() { file_proto_public_pbdns_dns_proto_init() } +func file_proto_public_pbdns_dns_proto_init() { + if File_proto_public_pbdns_dns_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_proto_public_pbdns_dns_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*QueryRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_public_pbdns_dns_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*QueryResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_proto_public_pbdns_dns_proto_rawDesc, + NumEnums: 1, + NumMessages: 2, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_proto_public_pbdns_dns_proto_goTypes, + DependencyIndexes: file_proto_public_pbdns_dns_proto_depIdxs, + EnumInfos: file_proto_public_pbdns_dns_proto_enumTypes, + MessageInfos: file_proto_public_pbdns_dns_proto_msgTypes, + }.Build() + File_proto_public_pbdns_dns_proto = out.File + file_proto_public_pbdns_dns_proto_rawDesc = nil + file_proto_public_pbdns_dns_proto_goTypes = nil + file_proto_public_pbdns_dns_proto_depIdxs = nil +} diff --git a/proto-public/pbdns/dns.proto b/proto-public/pbdns/dns.proto new file mode 100644 index 0000000000..bcf3e8d06f --- /dev/null +++ b/proto-public/pbdns/dns.proto @@ -0,0 +1,28 @@ +syntax = "proto3"; + +package hashicorp.consul.dns; + +option go_package = "github.com/hashicorp/consul/proto-public/pbdns"; + +service DNSService { + // Query sends a DNS request over to Consul server and returns a DNS reply message. + rpc Query(QueryRequest) returns (QueryResponse) {} +} + +enum Protocol { + PROTOCOL_UNSET_UNSPECIFIED = 0; + PROTOCOL_TCP = 1; + PROTOCOL_UDP = 2; +} + +message QueryRequest { + // msg is the DNS request message. + bytes msg = 1; + // protocol is the protocol of the request + Protocol protocol = 2; +} + +message QueryResponse { + // msg is the DNS reply message. + bytes msg = 1; +} diff --git a/proto-public/pbdns/dns_grpc.pb.go b/proto-public/pbdns/dns_grpc.pb.go new file mode 100644 index 0000000000..85ecc8d48d --- /dev/null +++ b/proto-public/pbdns/dns_grpc.pb.go @@ -0,0 +1,105 @@ +// Code generated by protoc-gen-go-grpc. DO NOT EDIT. +// versions: +// - protoc-gen-go-grpc v1.2.0 +// - protoc (unknown) +// source: proto-public/pbdns/dns.proto + +package pbdns + +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.32.0 or later. +const _ = grpc.SupportPackageIsVersion7 + +// DNSServiceClient is the client API for DNSService 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. +type DNSServiceClient interface { + // Query sends a DNS request over to Consul server and returns a DNS reply message. + Query(ctx context.Context, in *QueryRequest, opts ...grpc.CallOption) (*QueryResponse, error) +} + +type dNSServiceClient struct { + cc grpc.ClientConnInterface +} + +func NewDNSServiceClient(cc grpc.ClientConnInterface) DNSServiceClient { + return &dNSServiceClient{cc} +} + +func (c *dNSServiceClient) Query(ctx context.Context, in *QueryRequest, opts ...grpc.CallOption) (*QueryResponse, error) { + out := new(QueryResponse) + err := c.cc.Invoke(ctx, "/hashicorp.consul.dns.DNSService/Query", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// DNSServiceServer is the server API for DNSService service. +// All implementations should embed UnimplementedDNSServiceServer +// for forward compatibility +type DNSServiceServer interface { + // Query sends a DNS request over to Consul server and returns a DNS reply message. + Query(context.Context, *QueryRequest) (*QueryResponse, error) +} + +// UnimplementedDNSServiceServer should be embedded to have forward compatible implementations. +type UnimplementedDNSServiceServer struct { +} + +func (UnimplementedDNSServiceServer) Query(context.Context, *QueryRequest) (*QueryResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method Query not implemented") +} + +// UnsafeDNSServiceServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to DNSServiceServer will +// result in compilation errors. +type UnsafeDNSServiceServer interface { + mustEmbedUnimplementedDNSServiceServer() +} + +func RegisterDNSServiceServer(s grpc.ServiceRegistrar, srv DNSServiceServer) { + s.RegisterService(&DNSService_ServiceDesc, srv) +} + +func _DNSService_Query_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(QueryRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(DNSServiceServer).Query(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/hashicorp.consul.dns.DNSService/Query", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(DNSServiceServer).Query(ctx, req.(*QueryRequest)) + } + return interceptor(ctx, in, info, handler) +} + +// DNSService_ServiceDesc is the grpc.ServiceDesc for DNSService service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var DNSService_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "hashicorp.consul.dns.DNSService", + HandlerType: (*DNSServiceServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "Query", + Handler: _DNSService_Query_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "proto-public/pbdns/dns.proto", +} diff --git a/proto-public/pbdns/mock_DNSServiceClient.go b/proto-public/pbdns/mock_DNSServiceClient.go new file mode 100644 index 0000000000..a11f1e963e --- /dev/null +++ b/proto-public/pbdns/mock_DNSServiceClient.go @@ -0,0 +1,58 @@ +// Code generated by mockery v2.12.2. DO NOT EDIT. + +package pbdns + +import ( + context "context" + + grpc "google.golang.org/grpc" + + mock "github.com/stretchr/testify/mock" + + testing "testing" +) + +// MockDNSServiceClient is an autogenerated mock type for the DNSServiceClient type +type MockDNSServiceClient struct { + mock.Mock +} + +// Query provides a mock function with given fields: ctx, in, opts +func (_m *MockDNSServiceClient) Query(ctx context.Context, in *QueryRequest, opts ...grpc.CallOption) (*QueryResponse, error) { + _va := make([]interface{}, len(opts)) + for _i := range opts { + _va[_i] = opts[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, in) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *QueryResponse + if rf, ok := ret.Get(0).(func(context.Context, *QueryRequest, ...grpc.CallOption) *QueryResponse); ok { + r0 = rf(ctx, in, opts...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*QueryResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *QueryRequest, ...grpc.CallOption) error); ok { + r1 = rf(ctx, in, opts...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// NewMockDNSServiceClient creates a new instance of MockDNSServiceClient. It also registers the testing.TB interface on the mock and a cleanup function to assert the mocks expectations. +func NewMockDNSServiceClient(t testing.TB) *MockDNSServiceClient { + mock := &MockDNSServiceClient{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/proto-public/pbdns/mock_DNSServiceServer.go b/proto-public/pbdns/mock_DNSServiceServer.go new file mode 100644 index 0000000000..97b98dddbd --- /dev/null +++ b/proto-public/pbdns/mock_DNSServiceServer.go @@ -0,0 +1,48 @@ +// Code generated by mockery v2.12.2. DO NOT EDIT. + +package pbdns + +import ( + context "context" + testing "testing" + + mock "github.com/stretchr/testify/mock" +) + +// MockDNSServiceServer is an autogenerated mock type for the DNSServiceServer type +type MockDNSServiceServer struct { + mock.Mock +} + +// Query provides a mock function with given fields: _a0, _a1 +func (_m *MockDNSServiceServer) Query(_a0 context.Context, _a1 *QueryRequest) (*QueryResponse, error) { + ret := _m.Called(_a0, _a1) + + var r0 *QueryResponse + if rf, ok := ret.Get(0).(func(context.Context, *QueryRequest) *QueryResponse); ok { + r0 = rf(_a0, _a1) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*QueryResponse) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *QueryRequest) error); ok { + r1 = rf(_a0, _a1) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// NewMockDNSServiceServer creates a new instance of MockDNSServiceServer. It also registers the testing.TB interface on the mock and a cleanup function to assert the mocks expectations. +func NewMockDNSServiceServer(t testing.TB) *MockDNSServiceServer { + mock := &MockDNSServiceServer{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/proto-public/pbdns/mock_UnsafeDNSServiceServer.go b/proto-public/pbdns/mock_UnsafeDNSServiceServer.go new file mode 100644 index 0000000000..a56e55bcb6 --- /dev/null +++ b/proto-public/pbdns/mock_UnsafeDNSServiceServer.go @@ -0,0 +1,29 @@ +// Code generated by mockery v2.12.2. DO NOT EDIT. + +package pbdns + +import ( + testing "testing" + + mock "github.com/stretchr/testify/mock" +) + +// MockUnsafeDNSServiceServer is an autogenerated mock type for the UnsafeDNSServiceServer type +type MockUnsafeDNSServiceServer struct { + mock.Mock +} + +// mustEmbedUnimplementedDNSServiceServer provides a mock function with given fields: +func (_m *MockUnsafeDNSServiceServer) mustEmbedUnimplementedDNSServiceServer() { + _m.Called() +} + +// NewMockUnsafeDNSServiceServer creates a new instance of MockUnsafeDNSServiceServer. It also registers the testing.TB interface on the mock and a cleanup function to assert the mocks expectations. +func NewMockUnsafeDNSServiceServer(t testing.TB) *MockUnsafeDNSServiceServer { + mock := &MockUnsafeDNSServiceServer{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/proto/pbconfigentry/config_entry.gen.go b/proto/pbconfigentry/config_entry.gen.go index 540c956227..bfdce5e5c0 100644 --- a/proto/pbconfigentry/config_entry.gen.go +++ b/proto/pbconfigentry/config_entry.gen.go @@ -141,6 +141,11 @@ func IngressGatewayToStructs(s *IngressGateway, t *structs.IngressGatewayConfigE } } } + if s.Defaults != nil { + var x structs.IngressServiceConfig + IngressServiceConfigToStructs(s.Defaults, &x) + t.Defaults = &x + } t.Meta = s.Meta } func IngressGatewayFromStructs(t *structs.IngressGatewayConfigEntry, s *IngressGateway) { @@ -162,6 +167,11 @@ func IngressGatewayFromStructs(t *structs.IngressGatewayConfigEntry, s *IngressG } } } + if t.Defaults != nil { + var x IngressServiceConfig + IngressServiceConfigFromStructs(t.Defaults, &x) + s.Defaults = &x + } s.Meta = t.Meta } func IngressListenerToStructs(s *IngressListener, t *structs.IngressListener) { @@ -227,6 +237,9 @@ func IngressServiceToStructs(s *IngressService, t *structs.IngressService) { HTTPHeaderModifiersToStructs(s.ResponseHeaders, &x) t.ResponseHeaders = &x } + t.MaxConnections = s.MaxConnections + t.MaxPendingRequests = s.MaxPendingRequests + t.MaxConcurrentRequests = s.MaxConcurrentRequests t.Meta = s.Meta t.EnterpriseMeta = enterpriseMetaToStructs(s.EnterpriseMeta) } @@ -251,9 +264,28 @@ func IngressServiceFromStructs(t *structs.IngressService, s *IngressService) { HTTPHeaderModifiersFromStructs(t.ResponseHeaders, &x) s.ResponseHeaders = &x } + s.MaxConnections = t.MaxConnections + s.MaxPendingRequests = t.MaxPendingRequests + s.MaxConcurrentRequests = t.MaxConcurrentRequests s.Meta = t.Meta s.EnterpriseMeta = enterpriseMetaFromStructs(t.EnterpriseMeta) } +func IngressServiceConfigToStructs(s *IngressServiceConfig, t *structs.IngressServiceConfig) { + if s == nil { + return + } + t.MaxConnections = s.MaxConnections + t.MaxPendingRequests = s.MaxPendingRequests + t.MaxConcurrentRequests = s.MaxConcurrentRequests +} +func IngressServiceConfigFromStructs(t *structs.IngressServiceConfig, s *IngressServiceConfig) { + if s == nil { + return + } + s.MaxConnections = t.MaxConnections + s.MaxPendingRequests = t.MaxPendingRequests + s.MaxConcurrentRequests = t.MaxConcurrentRequests +} func IntentionHTTPHeaderPermissionToStructs(s *IntentionHTTPHeaderPermission, t *structs.IntentionHTTPHeaderPermission) { if s == nil { return diff --git a/proto/pbconfigentry/config_entry.pb.binary.go b/proto/pbconfigentry/config_entry.pb.binary.go index 1fccef2b98..af897e925b 100644 --- a/proto/pbconfigentry/config_entry.pb.binary.go +++ b/proto/pbconfigentry/config_entry.pb.binary.go @@ -187,6 +187,16 @@ func (msg *IngressGateway) UnmarshalBinary(b []byte) error { return proto.Unmarshal(b, msg) } +// MarshalBinary implements encoding.BinaryMarshaler +func (msg *IngressServiceConfig) MarshalBinary() ([]byte, error) { + return proto.Marshal(msg) +} + +// UnmarshalBinary implements encoding.BinaryUnmarshaler +func (msg *IngressServiceConfig) UnmarshalBinary(b []byte) error { + return proto.Unmarshal(b, msg) +} + // MarshalBinary implements encoding.BinaryMarshaler func (msg *GatewayTLSConfig) MarshalBinary() ([]byte, error) { return proto.Marshal(msg) diff --git a/proto/pbconfigentry/config_entry.pb.go b/proto/pbconfigentry/config_entry.pb.go index b58b29be4f..aaeb6ce9f2 100644 --- a/proto/pbconfigentry/config_entry.pb.go +++ b/proto/pbconfigentry/config_entry.pb.go @@ -1461,9 +1461,10 @@ type IngressGateway struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - TLS *GatewayTLSConfig `protobuf:"bytes,1,opt,name=TLS,proto3" json:"TLS,omitempty"` - Listeners []*IngressListener `protobuf:"bytes,2,rep,name=Listeners,proto3" json:"Listeners,omitempty"` - Meta map[string]string `protobuf:"bytes,3,rep,name=Meta,proto3" json:"Meta,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` + TLS *GatewayTLSConfig `protobuf:"bytes,1,opt,name=TLS,proto3" json:"TLS,omitempty"` + Listeners []*IngressListener `protobuf:"bytes,2,rep,name=Listeners,proto3" json:"Listeners,omitempty"` + Meta map[string]string `protobuf:"bytes,3,rep,name=Meta,proto3" json:"Meta,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` + Defaults *IngressServiceConfig `protobuf:"bytes,4,opt,name=Defaults,proto3" json:"Defaults,omitempty"` } func (x *IngressGateway) Reset() { @@ -1519,6 +1520,81 @@ func (x *IngressGateway) GetMeta() map[string]string { return nil } +func (x *IngressGateway) GetDefaults() *IngressServiceConfig { + if x != nil { + return x.Defaults + } + return nil +} + +// mog annotation: +// +// target=github.com/hashicorp/consul/agent/structs.IngressServiceConfig +// output=config_entry.gen.go +// name=Structs +type IngressServiceConfig struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + MaxConnections uint32 `protobuf:"varint,1,opt,name=MaxConnections,proto3" json:"MaxConnections,omitempty"` + MaxPendingRequests uint32 `protobuf:"varint,2,opt,name=MaxPendingRequests,proto3" json:"MaxPendingRequests,omitempty"` + MaxConcurrentRequests uint32 `protobuf:"varint,3,opt,name=MaxConcurrentRequests,proto3" json:"MaxConcurrentRequests,omitempty"` +} + +func (x *IngressServiceConfig) Reset() { + *x = IngressServiceConfig{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_pbconfigentry_config_entry_proto_msgTypes[18] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *IngressServiceConfig) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*IngressServiceConfig) ProtoMessage() {} + +func (x *IngressServiceConfig) ProtoReflect() protoreflect.Message { + mi := &file_proto_pbconfigentry_config_entry_proto_msgTypes[18] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use IngressServiceConfig.ProtoReflect.Descriptor instead. +func (*IngressServiceConfig) Descriptor() ([]byte, []int) { + return file_proto_pbconfigentry_config_entry_proto_rawDescGZIP(), []int{18} +} + +func (x *IngressServiceConfig) GetMaxConnections() uint32 { + if x != nil { + return x.MaxConnections + } + return 0 +} + +func (x *IngressServiceConfig) GetMaxPendingRequests() uint32 { + if x != nil { + return x.MaxPendingRequests + } + return 0 +} + +func (x *IngressServiceConfig) GetMaxConcurrentRequests() uint32 { + if x != nil { + return x.MaxConcurrentRequests + } + return 0 +} + // mog annotation: // // target=github.com/hashicorp/consul/agent/structs.GatewayTLSConfig @@ -1542,7 +1618,7 @@ type GatewayTLSConfig struct { func (x *GatewayTLSConfig) Reset() { *x = GatewayTLSConfig{} if protoimpl.UnsafeEnabled { - mi := &file_proto_pbconfigentry_config_entry_proto_msgTypes[18] + mi := &file_proto_pbconfigentry_config_entry_proto_msgTypes[19] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1555,7 +1631,7 @@ func (x *GatewayTLSConfig) String() string { func (*GatewayTLSConfig) ProtoMessage() {} func (x *GatewayTLSConfig) ProtoReflect() protoreflect.Message { - mi := &file_proto_pbconfigentry_config_entry_proto_msgTypes[18] + mi := &file_proto_pbconfigentry_config_entry_proto_msgTypes[19] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1568,7 +1644,7 @@ func (x *GatewayTLSConfig) ProtoReflect() protoreflect.Message { // Deprecated: Use GatewayTLSConfig.ProtoReflect.Descriptor instead. func (*GatewayTLSConfig) Descriptor() ([]byte, []int) { - return file_proto_pbconfigentry_config_entry_proto_rawDescGZIP(), []int{18} + return file_proto_pbconfigentry_config_entry_proto_rawDescGZIP(), []int{19} } func (x *GatewayTLSConfig) GetEnabled() bool { @@ -1623,7 +1699,7 @@ type GatewayTLSSDSConfig struct { func (x *GatewayTLSSDSConfig) Reset() { *x = GatewayTLSSDSConfig{} if protoimpl.UnsafeEnabled { - mi := &file_proto_pbconfigentry_config_entry_proto_msgTypes[19] + mi := &file_proto_pbconfigentry_config_entry_proto_msgTypes[20] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1636,7 +1712,7 @@ func (x *GatewayTLSSDSConfig) String() string { func (*GatewayTLSSDSConfig) ProtoMessage() {} func (x *GatewayTLSSDSConfig) ProtoReflect() protoreflect.Message { - mi := &file_proto_pbconfigentry_config_entry_proto_msgTypes[19] + mi := &file_proto_pbconfigentry_config_entry_proto_msgTypes[20] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1649,7 +1725,7 @@ func (x *GatewayTLSSDSConfig) ProtoReflect() protoreflect.Message { // Deprecated: Use GatewayTLSSDSConfig.ProtoReflect.Descriptor instead. func (*GatewayTLSSDSConfig) Descriptor() ([]byte, []int) { - return file_proto_pbconfigentry_config_entry_proto_rawDescGZIP(), []int{19} + return file_proto_pbconfigentry_config_entry_proto_rawDescGZIP(), []int{20} } func (x *GatewayTLSSDSConfig) GetClusterName() string { @@ -1686,7 +1762,7 @@ type IngressListener struct { func (x *IngressListener) Reset() { *x = IngressListener{} if protoimpl.UnsafeEnabled { - mi := &file_proto_pbconfigentry_config_entry_proto_msgTypes[20] + mi := &file_proto_pbconfigentry_config_entry_proto_msgTypes[21] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1699,7 +1775,7 @@ func (x *IngressListener) String() string { func (*IngressListener) ProtoMessage() {} func (x *IngressListener) ProtoReflect() protoreflect.Message { - mi := &file_proto_pbconfigentry_config_entry_proto_msgTypes[20] + mi := &file_proto_pbconfigentry_config_entry_proto_msgTypes[21] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1712,7 +1788,7 @@ func (x *IngressListener) ProtoReflect() protoreflect.Message { // Deprecated: Use IngressListener.ProtoReflect.Descriptor instead. func (*IngressListener) Descriptor() ([]byte, []int) { - return file_proto_pbconfigentry_config_entry_proto_rawDescGZIP(), []int{20} + return file_proto_pbconfigentry_config_entry_proto_rawDescGZIP(), []int{21} } func (x *IngressListener) GetPort() int32 { @@ -1760,13 +1836,16 @@ type IngressService struct { ResponseHeaders *HTTPHeaderModifiers `protobuf:"bytes,5,opt,name=ResponseHeaders,proto3" json:"ResponseHeaders,omitempty"` Meta map[string]string `protobuf:"bytes,6,rep,name=Meta,proto3" json:"Meta,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` // mog: func-to=enterpriseMetaToStructs func-from=enterpriseMetaFromStructs - EnterpriseMeta *pbcommon.EnterpriseMeta `protobuf:"bytes,7,opt,name=EnterpriseMeta,proto3" json:"EnterpriseMeta,omitempty"` + EnterpriseMeta *pbcommon.EnterpriseMeta `protobuf:"bytes,7,opt,name=EnterpriseMeta,proto3" json:"EnterpriseMeta,omitempty"` + MaxConnections uint32 `protobuf:"varint,8,opt,name=MaxConnections,proto3" json:"MaxConnections,omitempty"` + MaxPendingRequests uint32 `protobuf:"varint,9,opt,name=MaxPendingRequests,proto3" json:"MaxPendingRequests,omitempty"` + MaxConcurrentRequests uint32 `protobuf:"varint,10,opt,name=MaxConcurrentRequests,proto3" json:"MaxConcurrentRequests,omitempty"` } func (x *IngressService) Reset() { *x = IngressService{} if protoimpl.UnsafeEnabled { - mi := &file_proto_pbconfigentry_config_entry_proto_msgTypes[21] + mi := &file_proto_pbconfigentry_config_entry_proto_msgTypes[22] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1779,7 +1858,7 @@ func (x *IngressService) String() string { func (*IngressService) ProtoMessage() {} func (x *IngressService) ProtoReflect() protoreflect.Message { - mi := &file_proto_pbconfigentry_config_entry_proto_msgTypes[21] + mi := &file_proto_pbconfigentry_config_entry_proto_msgTypes[22] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1792,7 +1871,7 @@ func (x *IngressService) ProtoReflect() protoreflect.Message { // Deprecated: Use IngressService.ProtoReflect.Descriptor instead. func (*IngressService) Descriptor() ([]byte, []int) { - return file_proto_pbconfigentry_config_entry_proto_rawDescGZIP(), []int{21} + return file_proto_pbconfigentry_config_entry_proto_rawDescGZIP(), []int{22} } func (x *IngressService) GetName() string { @@ -1844,6 +1923,27 @@ func (x *IngressService) GetEnterpriseMeta() *pbcommon.EnterpriseMeta { return nil } +func (x *IngressService) GetMaxConnections() uint32 { + if x != nil { + return x.MaxConnections + } + return 0 +} + +func (x *IngressService) GetMaxPendingRequests() uint32 { + if x != nil { + return x.MaxPendingRequests + } + return 0 +} + +func (x *IngressService) GetMaxConcurrentRequests() uint32 { + if x != nil { + return x.MaxConcurrentRequests + } + return 0 +} + // mog annotation: // // target=github.com/hashicorp/consul/agent/structs.GatewayServiceTLSConfig @@ -1860,7 +1960,7 @@ type GatewayServiceTLSConfig struct { func (x *GatewayServiceTLSConfig) Reset() { *x = GatewayServiceTLSConfig{} if protoimpl.UnsafeEnabled { - mi := &file_proto_pbconfigentry_config_entry_proto_msgTypes[22] + mi := &file_proto_pbconfigentry_config_entry_proto_msgTypes[23] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1873,7 +1973,7 @@ func (x *GatewayServiceTLSConfig) String() string { func (*GatewayServiceTLSConfig) ProtoMessage() {} func (x *GatewayServiceTLSConfig) ProtoReflect() protoreflect.Message { - mi := &file_proto_pbconfigentry_config_entry_proto_msgTypes[22] + mi := &file_proto_pbconfigentry_config_entry_proto_msgTypes[23] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1886,7 +1986,7 @@ func (x *GatewayServiceTLSConfig) ProtoReflect() protoreflect.Message { // Deprecated: Use GatewayServiceTLSConfig.ProtoReflect.Descriptor instead. func (*GatewayServiceTLSConfig) Descriptor() ([]byte, []int) { - return file_proto_pbconfigentry_config_entry_proto_rawDescGZIP(), []int{22} + return file_proto_pbconfigentry_config_entry_proto_rawDescGZIP(), []int{23} } func (x *GatewayServiceTLSConfig) GetSDS() *GatewayTLSSDSConfig { @@ -1914,7 +2014,7 @@ type HTTPHeaderModifiers struct { func (x *HTTPHeaderModifiers) Reset() { *x = HTTPHeaderModifiers{} if protoimpl.UnsafeEnabled { - mi := &file_proto_pbconfigentry_config_entry_proto_msgTypes[23] + mi := &file_proto_pbconfigentry_config_entry_proto_msgTypes[24] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1927,7 +2027,7 @@ func (x *HTTPHeaderModifiers) String() string { func (*HTTPHeaderModifiers) ProtoMessage() {} func (x *HTTPHeaderModifiers) ProtoReflect() protoreflect.Message { - mi := &file_proto_pbconfigentry_config_entry_proto_msgTypes[23] + mi := &file_proto_pbconfigentry_config_entry_proto_msgTypes[24] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1940,7 +2040,7 @@ func (x *HTTPHeaderModifiers) ProtoReflect() protoreflect.Message { // Deprecated: Use HTTPHeaderModifiers.ProtoReflect.Descriptor instead. func (*HTTPHeaderModifiers) Descriptor() ([]byte, []int) { - return file_proto_pbconfigentry_config_entry_proto_rawDescGZIP(), []int{23} + return file_proto_pbconfigentry_config_entry_proto_rawDescGZIP(), []int{24} } func (x *HTTPHeaderModifiers) GetAdd() map[string]string { @@ -1982,7 +2082,7 @@ type ServiceIntentions struct { func (x *ServiceIntentions) Reset() { *x = ServiceIntentions{} if protoimpl.UnsafeEnabled { - mi := &file_proto_pbconfigentry_config_entry_proto_msgTypes[24] + mi := &file_proto_pbconfigentry_config_entry_proto_msgTypes[25] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1995,7 +2095,7 @@ func (x *ServiceIntentions) String() string { func (*ServiceIntentions) ProtoMessage() {} func (x *ServiceIntentions) ProtoReflect() protoreflect.Message { - mi := &file_proto_pbconfigentry_config_entry_proto_msgTypes[24] + mi := &file_proto_pbconfigentry_config_entry_proto_msgTypes[25] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2008,7 +2108,7 @@ func (x *ServiceIntentions) ProtoReflect() protoreflect.Message { // Deprecated: Use ServiceIntentions.ProtoReflect.Descriptor instead. func (*ServiceIntentions) Descriptor() ([]byte, []int) { - return file_proto_pbconfigentry_config_entry_proto_rawDescGZIP(), []int{24} + return file_proto_pbconfigentry_config_entry_proto_rawDescGZIP(), []int{25} } func (x *ServiceIntentions) GetSources() []*SourceIntention { @@ -2058,7 +2158,7 @@ type SourceIntention struct { func (x *SourceIntention) Reset() { *x = SourceIntention{} if protoimpl.UnsafeEnabled { - mi := &file_proto_pbconfigentry_config_entry_proto_msgTypes[25] + mi := &file_proto_pbconfigentry_config_entry_proto_msgTypes[26] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2071,7 +2171,7 @@ func (x *SourceIntention) String() string { func (*SourceIntention) ProtoMessage() {} func (x *SourceIntention) ProtoReflect() protoreflect.Message { - mi := &file_proto_pbconfigentry_config_entry_proto_msgTypes[25] + mi := &file_proto_pbconfigentry_config_entry_proto_msgTypes[26] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2084,7 +2184,7 @@ func (x *SourceIntention) ProtoReflect() protoreflect.Message { // Deprecated: Use SourceIntention.ProtoReflect.Descriptor instead. func (*SourceIntention) Descriptor() ([]byte, []int) { - return file_proto_pbconfigentry_config_entry_proto_rawDescGZIP(), []int{25} + return file_proto_pbconfigentry_config_entry_proto_rawDescGZIP(), []int{26} } func (x *SourceIntention) GetName() string { @@ -2189,7 +2289,7 @@ type IntentionPermission struct { func (x *IntentionPermission) Reset() { *x = IntentionPermission{} if protoimpl.UnsafeEnabled { - mi := &file_proto_pbconfigentry_config_entry_proto_msgTypes[26] + mi := &file_proto_pbconfigentry_config_entry_proto_msgTypes[27] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2202,7 +2302,7 @@ func (x *IntentionPermission) String() string { func (*IntentionPermission) ProtoMessage() {} func (x *IntentionPermission) ProtoReflect() protoreflect.Message { - mi := &file_proto_pbconfigentry_config_entry_proto_msgTypes[26] + mi := &file_proto_pbconfigentry_config_entry_proto_msgTypes[27] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2215,7 +2315,7 @@ func (x *IntentionPermission) ProtoReflect() protoreflect.Message { // Deprecated: Use IntentionPermission.ProtoReflect.Descriptor instead. func (*IntentionPermission) Descriptor() ([]byte, []int) { - return file_proto_pbconfigentry_config_entry_proto_rawDescGZIP(), []int{26} + return file_proto_pbconfigentry_config_entry_proto_rawDescGZIP(), []int{27} } func (x *IntentionPermission) GetAction() IntentionAction { @@ -2252,7 +2352,7 @@ type IntentionHTTPPermission struct { func (x *IntentionHTTPPermission) Reset() { *x = IntentionHTTPPermission{} if protoimpl.UnsafeEnabled { - mi := &file_proto_pbconfigentry_config_entry_proto_msgTypes[27] + mi := &file_proto_pbconfigentry_config_entry_proto_msgTypes[28] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2265,7 +2365,7 @@ func (x *IntentionHTTPPermission) String() string { func (*IntentionHTTPPermission) ProtoMessage() {} func (x *IntentionHTTPPermission) ProtoReflect() protoreflect.Message { - mi := &file_proto_pbconfigentry_config_entry_proto_msgTypes[27] + mi := &file_proto_pbconfigentry_config_entry_proto_msgTypes[28] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2278,7 +2378,7 @@ func (x *IntentionHTTPPermission) ProtoReflect() protoreflect.Message { // Deprecated: Use IntentionHTTPPermission.ProtoReflect.Descriptor instead. func (*IntentionHTTPPermission) Descriptor() ([]byte, []int) { - return file_proto_pbconfigentry_config_entry_proto_rawDescGZIP(), []int{27} + return file_proto_pbconfigentry_config_entry_proto_rawDescGZIP(), []int{28} } func (x *IntentionHTTPPermission) GetPathExact() string { @@ -2338,7 +2438,7 @@ type IntentionHTTPHeaderPermission struct { func (x *IntentionHTTPHeaderPermission) Reset() { *x = IntentionHTTPHeaderPermission{} if protoimpl.UnsafeEnabled { - mi := &file_proto_pbconfigentry_config_entry_proto_msgTypes[28] + mi := &file_proto_pbconfigentry_config_entry_proto_msgTypes[29] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2351,7 +2451,7 @@ func (x *IntentionHTTPHeaderPermission) String() string { func (*IntentionHTTPHeaderPermission) ProtoMessage() {} func (x *IntentionHTTPHeaderPermission) ProtoReflect() protoreflect.Message { - mi := &file_proto_pbconfigentry_config_entry_proto_msgTypes[28] + mi := &file_proto_pbconfigentry_config_entry_proto_msgTypes[29] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2364,7 +2464,7 @@ func (x *IntentionHTTPHeaderPermission) ProtoReflect() protoreflect.Message { // Deprecated: Use IntentionHTTPHeaderPermission.ProtoReflect.Descriptor instead. func (*IntentionHTTPHeaderPermission) Descriptor() ([]byte, []int) { - return file_proto_pbconfigentry_config_entry_proto_rawDescGZIP(), []int{28} + return file_proto_pbconfigentry_config_entry_proto_rawDescGZIP(), []int{29} } func (x *IntentionHTTPHeaderPermission) GetName() string { @@ -2692,7 +2792,7 @@ var file_proto_pbconfigentry_config_entry_proto_rawDesc = []byte{ 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x03, 0x54, 0x54, 0x4c, 0x12, 0x12, 0x0a, 0x04, 0x50, 0x61, 0x74, 0x68, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x50, 0x61, - 0x74, 0x68, 0x22, 0xbf, 0x02, 0x0a, 0x0e, 0x49, 0x6e, 0x67, 0x72, 0x65, 0x73, 0x73, 0x47, 0x61, + 0x74, 0x68, 0x22, 0x98, 0x03, 0x0a, 0x0e, 0x49, 0x6e, 0x67, 0x72, 0x65, 0x73, 0x73, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x12, 0x49, 0x0a, 0x03, 0x54, 0x4c, 0x53, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x37, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x63, @@ -2708,11 +2808,27 @@ var file_proto_pbconfigentry_config_entry_proto_rawDesc = []byte{ 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x2e, 0x49, 0x6e, 0x67, 0x72, 0x65, 0x73, 0x73, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x2e, 0x4d, 0x65, 0x74, 0x61, - 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x04, 0x4d, 0x65, 0x74, 0x61, 0x1a, 0x37, 0x0a, 0x09, 0x4d, - 0x65, 0x74, 0x61, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, - 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, - 0x3a, 0x02, 0x38, 0x01, 0x22, 0xea, 0x01, 0x0a, 0x10, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, + 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x04, 0x4d, 0x65, 0x74, 0x61, 0x12, 0x57, 0x0a, 0x08, 0x44, + 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x3b, 0x2e, + 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, + 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, + 0x65, 0x6e, 0x74, 0x72, 0x79, 0x2e, 0x49, 0x6e, 0x67, 0x72, 0x65, 0x73, 0x73, 0x53, 0x65, 0x72, + 0x76, 0x69, 0x63, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x08, 0x44, 0x65, 0x66, 0x61, + 0x75, 0x6c, 0x74, 0x73, 0x1a, 0x37, 0x0a, 0x09, 0x4d, 0x65, 0x74, 0x61, 0x45, 0x6e, 0x74, 0x72, + 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, + 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0xa4, 0x01, + 0x0a, 0x14, 0x49, 0x6e, 0x67, 0x72, 0x65, 0x73, 0x73, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, + 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x26, 0x0a, 0x0e, 0x4d, 0x61, 0x78, 0x43, 0x6f, 0x6e, + 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0e, + 0x4d, 0x61, 0x78, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x2e, + 0x0a, 0x12, 0x4d, 0x61, 0x78, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x12, 0x4d, 0x61, 0x78, 0x50, + 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x73, 0x12, 0x34, + 0x0a, 0x15, 0x4d, 0x61, 0x78, 0x43, 0x6f, 0x6e, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x15, 0x4d, + 0x61, 0x78, 0x43, 0x6f, 0x6e, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x73, 0x22, 0xea, 0x01, 0x0a, 0x10, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x54, 0x4c, 0x53, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x18, 0x0a, 0x07, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x4c, 0x0a, 0x03, 0x53, 0x44, 0x53, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, @@ -2747,7 +2863,7 @@ var file_proto_pbconfigentry_config_entry_proto_rawDesc = []byte{ 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x2e, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x54, 0x4c, 0x53, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x03, 0x54, 0x4c, 0x53, - 0x22, 0xbe, 0x04, 0x0a, 0x0e, 0x49, 0x6e, 0x67, 0x72, 0x65, 0x73, 0x73, 0x53, 0x65, 0x72, 0x76, + 0x22, 0xcc, 0x05, 0x0a, 0x0e, 0x49, 0x6e, 0x67, 0x72, 0x65, 0x73, 0x73, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x48, 0x6f, 0x73, 0x74, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x05, 0x48, 0x6f, 0x73, 0x74, 0x73, 0x12, 0x50, 0x0a, @@ -2779,174 +2895,183 @@ var file_proto_pbconfigentry_config_entry_proto_rawDesc = []byte{ 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x45, 0x6e, 0x74, 0x65, 0x72, 0x70, 0x72, 0x69, 0x73, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x0e, 0x45, 0x6e, 0x74, 0x65, 0x72, 0x70, - 0x72, 0x69, 0x73, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x1a, 0x37, 0x0a, 0x09, 0x4d, 0x65, 0x74, 0x61, - 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, - 0x01, 0x22, 0x67, 0x0a, 0x17, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x53, 0x65, 0x72, 0x76, - 0x69, 0x63, 0x65, 0x54, 0x4c, 0x53, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x4c, 0x0a, 0x03, - 0x53, 0x44, 0x53, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x3a, 0x2e, 0x68, 0x61, 0x73, 0x68, - 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, - 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x65, 0x6e, 0x74, 0x72, - 0x79, 0x2e, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x54, 0x4c, 0x53, 0x53, 0x44, 0x53, 0x43, - 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x03, 0x53, 0x44, 0x53, 0x22, 0xcb, 0x02, 0x0a, 0x13, 0x48, - 0x54, 0x54, 0x50, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x4d, 0x6f, 0x64, 0x69, 0x66, 0x69, 0x65, - 0x72, 0x73, 0x12, 0x55, 0x0a, 0x03, 0x41, 0x64, 0x64, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, - 0x43, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, + 0x72, 0x69, 0x73, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x12, 0x26, 0x0a, 0x0e, 0x4d, 0x61, 0x78, 0x43, + 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0d, + 0x52, 0x0e, 0x4d, 0x61, 0x78, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, + 0x12, 0x2e, 0x0a, 0x12, 0x4d, 0x61, 0x78, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x73, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x12, 0x4d, 0x61, + 0x78, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x73, + 0x12, 0x34, 0x0a, 0x15, 0x4d, 0x61, 0x78, 0x43, 0x6f, 0x6e, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, + 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x73, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0d, 0x52, + 0x15, 0x4d, 0x61, 0x78, 0x43, 0x6f, 0x6e, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x73, 0x1a, 0x37, 0x0a, 0x09, 0x4d, 0x65, 0x74, 0x61, 0x45, 0x6e, + 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, + 0x67, 0x0a, 0x17, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, + 0x65, 0x54, 0x4c, 0x53, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x4c, 0x0a, 0x03, 0x53, 0x44, + 0x53, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x3a, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, + 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, + 0x6e, 0x61, 0x6c, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x2e, + 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x54, 0x4c, 0x53, 0x53, 0x44, 0x53, 0x43, 0x6f, 0x6e, + 0x66, 0x69, 0x67, 0x52, 0x03, 0x53, 0x44, 0x53, 0x22, 0xcb, 0x02, 0x0a, 0x13, 0x48, 0x54, 0x54, + 0x50, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x4d, 0x6f, 0x64, 0x69, 0x66, 0x69, 0x65, 0x72, 0x73, + 0x12, 0x55, 0x0a, 0x03, 0x41, 0x64, 0x64, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x43, 0x2e, + 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, + 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, + 0x65, 0x6e, 0x74, 0x72, 0x79, 0x2e, 0x48, 0x54, 0x54, 0x50, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, + 0x4d, 0x6f, 0x64, 0x69, 0x66, 0x69, 0x65, 0x72, 0x73, 0x2e, 0x41, 0x64, 0x64, 0x45, 0x6e, 0x74, + 0x72, 0x79, 0x52, 0x03, 0x41, 0x64, 0x64, 0x12, 0x55, 0x0a, 0x03, 0x53, 0x65, 0x74, 0x18, 0x02, + 0x20, 0x03, 0x28, 0x0b, 0x32, 0x43, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, + 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, + 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x2e, 0x48, 0x54, 0x54, + 0x50, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x4d, 0x6f, 0x64, 0x69, 0x66, 0x69, 0x65, 0x72, 0x73, + 0x2e, 0x53, 0x65, 0x74, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x03, 0x53, 0x65, 0x74, 0x12, 0x16, + 0x0a, 0x06, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x06, + 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x1a, 0x36, 0x0a, 0x08, 0x41, 0x64, 0x64, 0x45, 0x6e, 0x74, + 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x1a, 0x36, + 0x0a, 0x08, 0x53, 0x65, 0x74, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, + 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, + 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, + 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0xf6, 0x01, 0x0a, 0x11, 0x53, 0x65, 0x72, 0x76, 0x69, + 0x63, 0x65, 0x49, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x50, 0x0a, 0x07, + 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x36, 0x2e, + 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, + 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, + 0x65, 0x6e, 0x74, 0x72, 0x79, 0x2e, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x49, 0x6e, 0x74, 0x65, + 0x6e, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x07, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x12, 0x56, + 0x0a, 0x04, 0x4d, 0x65, 0x74, 0x61, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x42, 0x2e, 0x68, + 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, + 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x65, + 0x6e, 0x74, 0x72, 0x79, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x49, 0x6e, 0x74, 0x65, + 0x6e, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x45, 0x6e, 0x74, 0x72, 0x79, + 0x52, 0x04, 0x4d, 0x65, 0x74, 0x61, 0x1a, 0x37, 0x0a, 0x09, 0x4d, 0x65, 0x74, 0x61, 0x45, 0x6e, + 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, + 0xa6, 0x06, 0x0a, 0x0f, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x49, 0x6e, 0x74, 0x65, 0x6e, 0x74, + 0x69, 0x6f, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x04, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x4e, 0x0a, 0x06, 0x41, 0x63, 0x74, 0x69, 0x6f, + 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x36, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, + 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, + 0x6e, 0x61, 0x6c, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x2e, + 0x49, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x69, 0x6f, 0x6e, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, + 0x06, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x5c, 0x0a, 0x0b, 0x50, 0x65, 0x72, 0x6d, 0x69, + 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x3a, 0x2e, 0x68, + 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, + 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x65, + 0x6e, 0x74, 0x72, 0x79, 0x2e, 0x49, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x65, + 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x0b, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, + 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x1e, 0x0a, 0x0a, 0x50, 0x72, 0x65, 0x63, 0x65, 0x64, 0x65, + 0x6e, 0x63, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0a, 0x50, 0x72, 0x65, 0x63, 0x65, + 0x64, 0x65, 0x6e, 0x63, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x4c, 0x65, 0x67, 0x61, 0x63, 0x79, 0x49, + 0x44, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x4c, 0x65, 0x67, 0x61, 0x63, 0x79, 0x49, + 0x44, 0x12, 0x4e, 0x0a, 0x04, 0x54, 0x79, 0x70, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0e, 0x32, + 0x3a, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x63, 0x6f, 0x6e, 0x66, - 0x69, 0x67, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x2e, 0x48, 0x54, 0x54, 0x50, 0x48, 0x65, 0x61, 0x64, - 0x65, 0x72, 0x4d, 0x6f, 0x64, 0x69, 0x66, 0x69, 0x65, 0x72, 0x73, 0x2e, 0x41, 0x64, 0x64, 0x45, - 0x6e, 0x74, 0x72, 0x79, 0x52, 0x03, 0x41, 0x64, 0x64, 0x12, 0x55, 0x0a, 0x03, 0x53, 0x65, 0x74, - 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x43, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, - 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, - 0x61, 0x6c, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x2e, 0x48, - 0x54, 0x54, 0x50, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x4d, 0x6f, 0x64, 0x69, 0x66, 0x69, 0x65, - 0x72, 0x73, 0x2e, 0x53, 0x65, 0x74, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x03, 0x53, 0x65, 0x74, - 0x12, 0x16, 0x0a, 0x06, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, - 0x52, 0x06, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x1a, 0x36, 0x0a, 0x08, 0x41, 0x64, 0x64, 0x45, - 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, - 0x1a, 0x36, 0x0a, 0x08, 0x53, 0x65, 0x74, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, + 0x69, 0x67, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x2e, 0x49, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x69, 0x6f, + 0x6e, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x54, 0x79, 0x70, + 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, + 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, + 0x69, 0x6f, 0x6e, 0x12, 0x66, 0x0a, 0x0a, 0x4c, 0x65, 0x67, 0x61, 0x63, 0x79, 0x4d, 0x65, 0x74, + 0x61, 0x18, 0x08, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x46, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, + 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, + 0x6e, 0x61, 0x6c, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x2e, + 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x49, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x69, 0x6f, 0x6e, 0x2e, + 0x4c, 0x65, 0x67, 0x61, 0x63, 0x79, 0x4d, 0x65, 0x74, 0x61, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, + 0x0a, 0x4c, 0x65, 0x67, 0x61, 0x63, 0x79, 0x4d, 0x65, 0x74, 0x61, 0x12, 0x46, 0x0a, 0x10, 0x4c, + 0x65, 0x67, 0x61, 0x63, 0x79, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x18, + 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, + 0x70, 0x52, 0x10, 0x4c, 0x65, 0x67, 0x61, 0x63, 0x79, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, + 0x69, 0x6d, 0x65, 0x12, 0x46, 0x0a, 0x10, 0x4c, 0x65, 0x67, 0x61, 0x63, 0x79, 0x55, 0x70, 0x64, + 0x61, 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, + 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, + 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x10, 0x4c, 0x65, 0x67, 0x61, 0x63, + 0x79, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x58, 0x0a, 0x0e, 0x45, + 0x6e, 0x74, 0x65, 0x72, 0x70, 0x72, 0x69, 0x73, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x18, 0x0b, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x30, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, + 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, + 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x45, 0x6e, 0x74, 0x65, 0x72, 0x70, 0x72, 0x69, 0x73, + 0x65, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x0e, 0x45, 0x6e, 0x74, 0x65, 0x72, 0x70, 0x72, 0x69, 0x73, + 0x65, 0x4d, 0x65, 0x74, 0x61, 0x12, 0x12, 0x0a, 0x04, 0x50, 0x65, 0x65, 0x72, 0x18, 0x0c, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x04, 0x50, 0x65, 0x65, 0x72, 0x1a, 0x3d, 0x0a, 0x0f, 0x4c, 0x65, 0x67, + 0x61, 0x63, 0x79, 0x4d, 0x65, 0x74, 0x61, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, - 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0xf6, 0x01, 0x0a, 0x11, 0x53, 0x65, 0x72, - 0x76, 0x69, 0x63, 0x65, 0x49, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x50, - 0x0a, 0x07, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, - 0x36, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, - 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x63, 0x6f, 0x6e, 0x66, - 0x69, 0x67, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x2e, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x49, 0x6e, - 0x74, 0x65, 0x6e, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x07, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, - 0x12, 0x56, 0x0a, 0x04, 0x4d, 0x65, 0x74, 0x61, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x42, - 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, - 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, - 0x67, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x49, 0x6e, - 0x74, 0x65, 0x6e, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x45, 0x6e, 0x74, - 0x72, 0x79, 0x52, 0x04, 0x4d, 0x65, 0x74, 0x61, 0x1a, 0x37, 0x0a, 0x09, 0x4d, 0x65, 0x74, 0x61, - 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, - 0x01, 0x22, 0xa6, 0x06, 0x0a, 0x0f, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x49, 0x6e, 0x74, 0x65, - 0x6e, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x04, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x4e, 0x0a, 0x06, 0x41, 0x63, 0x74, - 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x36, 0x2e, 0x68, 0x61, 0x73, 0x68, - 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, - 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x65, 0x6e, 0x74, 0x72, - 0x79, 0x2e, 0x49, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x69, 0x6f, 0x6e, 0x41, 0x63, 0x74, 0x69, 0x6f, - 0x6e, 0x52, 0x06, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x5c, 0x0a, 0x0b, 0x50, 0x65, 0x72, - 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x3a, - 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, - 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, - 0x67, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x2e, 0x49, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x69, 0x6f, 0x6e, - 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x0b, 0x50, 0x65, 0x72, 0x6d, - 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x1e, 0x0a, 0x0a, 0x50, 0x72, 0x65, 0x63, 0x65, - 0x64, 0x65, 0x6e, 0x63, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0a, 0x50, 0x72, 0x65, - 0x63, 0x65, 0x64, 0x65, 0x6e, 0x63, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x4c, 0x65, 0x67, 0x61, 0x63, - 0x79, 0x49, 0x44, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x4c, 0x65, 0x67, 0x61, 0x63, - 0x79, 0x49, 0x44, 0x12, 0x4e, 0x0a, 0x04, 0x54, 0x79, 0x70, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, - 0x0e, 0x32, 0x3a, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, - 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x63, 0x6f, - 0x6e, 0x66, 0x69, 0x67, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x2e, 0x49, 0x6e, 0x74, 0x65, 0x6e, 0x74, - 0x69, 0x6f, 0x6e, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x54, - 0x79, 0x70, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, - 0x6f, 0x6e, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, - 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x66, 0x0a, 0x0a, 0x4c, 0x65, 0x67, 0x61, 0x63, 0x79, 0x4d, - 0x65, 0x74, 0x61, 0x18, 0x08, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x46, 0x2e, 0x68, 0x61, 0x73, 0x68, - 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, - 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x65, 0x6e, 0x74, 0x72, - 0x79, 0x2e, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x49, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x69, 0x6f, - 0x6e, 0x2e, 0x4c, 0x65, 0x67, 0x61, 0x63, 0x79, 0x4d, 0x65, 0x74, 0x61, 0x45, 0x6e, 0x74, 0x72, - 0x79, 0x52, 0x0a, 0x4c, 0x65, 0x67, 0x61, 0x63, 0x79, 0x4d, 0x65, 0x74, 0x61, 0x12, 0x46, 0x0a, - 0x10, 0x4c, 0x65, 0x67, 0x61, 0x63, 0x79, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x69, 0x6d, - 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, - 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, - 0x61, 0x6d, 0x70, 0x52, 0x10, 0x4c, 0x65, 0x67, 0x61, 0x63, 0x79, 0x43, 0x72, 0x65, 0x61, 0x74, - 0x65, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x46, 0x0a, 0x10, 0x4c, 0x65, 0x67, 0x61, 0x63, 0x79, 0x55, - 0x70, 0x64, 0x61, 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, - 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x10, 0x4c, 0x65, 0x67, - 0x61, 0x63, 0x79, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x58, 0x0a, - 0x0e, 0x45, 0x6e, 0x74, 0x65, 0x72, 0x70, 0x72, 0x69, 0x73, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x18, - 0x0b, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x30, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, - 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, - 0x6c, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x45, 0x6e, 0x74, 0x65, 0x72, 0x70, 0x72, - 0x69, 0x73, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x0e, 0x45, 0x6e, 0x74, 0x65, 0x72, 0x70, 0x72, - 0x69, 0x73, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x12, 0x12, 0x0a, 0x04, 0x50, 0x65, 0x65, 0x72, 0x18, - 0x0c, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x50, 0x65, 0x65, 0x72, 0x1a, 0x3d, 0x0a, 0x0f, 0x4c, - 0x65, 0x67, 0x61, 0x63, 0x79, 0x4d, 0x65, 0x74, 0x61, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, - 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, - 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0xb9, 0x01, 0x0a, 0x13, 0x49, - 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, - 0x6f, 0x6e, 0x12, 0x4e, 0x0a, 0x06, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x0e, 0x32, 0x36, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, - 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x63, - 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x2e, 0x49, 0x6e, 0x74, 0x65, 0x6e, - 0x74, 0x69, 0x6f, 0x6e, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x06, 0x41, 0x63, 0x74, 0x69, - 0x6f, 0x6e, 0x12, 0x52, 0x0a, 0x04, 0x48, 0x54, 0x54, 0x50, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x3e, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, + 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0xb9, 0x01, 0x0a, 0x13, 0x49, 0x6e, 0x74, + 0x65, 0x6e, 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, + 0x12, 0x4e, 0x0a, 0x06, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, + 0x32, 0x36, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x2e, 0x49, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x69, - 0x6f, 0x6e, 0x48, 0x54, 0x54, 0x50, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, - 0x52, 0x04, 0x48, 0x54, 0x54, 0x50, 0x22, 0xed, 0x01, 0x0a, 0x17, 0x49, 0x6e, 0x74, 0x65, 0x6e, - 0x74, 0x69, 0x6f, 0x6e, 0x48, 0x54, 0x54, 0x50, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, - 0x6f, 0x6e, 0x12, 0x1c, 0x0a, 0x09, 0x50, 0x61, 0x74, 0x68, 0x45, 0x78, 0x61, 0x63, 0x74, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x50, 0x61, 0x74, 0x68, 0x45, 0x78, 0x61, 0x63, 0x74, - 0x12, 0x1e, 0x0a, 0x0a, 0x50, 0x61, 0x74, 0x68, 0x50, 0x72, 0x65, 0x66, 0x69, 0x78, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x50, 0x61, 0x74, 0x68, 0x50, 0x72, 0x65, 0x66, 0x69, 0x78, - 0x12, 0x1c, 0x0a, 0x09, 0x50, 0x61, 0x74, 0x68, 0x52, 0x65, 0x67, 0x65, 0x78, 0x18, 0x03, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x09, 0x50, 0x61, 0x74, 0x68, 0x52, 0x65, 0x67, 0x65, 0x78, 0x12, 0x5c, - 0x0a, 0x06, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x44, + 0x6f, 0x6e, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x06, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, + 0x12, 0x52, 0x0a, 0x04, 0x48, 0x54, 0x54, 0x50, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x3e, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x2e, 0x49, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x69, 0x6f, 0x6e, - 0x48, 0x54, 0x54, 0x50, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, - 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x06, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x12, 0x18, 0x0a, 0x07, - 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, 0x4d, - 0x65, 0x74, 0x68, 0x6f, 0x64, 0x73, 0x22, 0xc1, 0x01, 0x0a, 0x1d, 0x49, 0x6e, 0x74, 0x65, 0x6e, - 0x74, 0x69, 0x6f, 0x6e, 0x48, 0x54, 0x54, 0x50, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x50, 0x65, - 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x4e, 0x61, 0x6d, 0x65, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x18, 0x0a, 0x07, - 0x50, 0x72, 0x65, 0x73, 0x65, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x50, - 0x72, 0x65, 0x73, 0x65, 0x6e, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x45, 0x78, 0x61, 0x63, 0x74, 0x18, - 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x45, 0x78, 0x61, 0x63, 0x74, 0x12, 0x16, 0x0a, 0x06, - 0x50, 0x72, 0x65, 0x66, 0x69, 0x78, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x50, 0x72, - 0x65, 0x66, 0x69, 0x78, 0x12, 0x16, 0x0a, 0x06, 0x53, 0x75, 0x66, 0x66, 0x69, 0x78, 0x18, 0x05, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x53, 0x75, 0x66, 0x66, 0x69, 0x78, 0x12, 0x14, 0x0a, 0x05, - 0x52, 0x65, 0x67, 0x65, 0x78, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x52, 0x65, 0x67, - 0x65, 0x78, 0x12, 0x16, 0x0a, 0x06, 0x49, 0x6e, 0x76, 0x65, 0x72, 0x74, 0x18, 0x07, 0x20, 0x01, - 0x28, 0x08, 0x52, 0x06, 0x49, 0x6e, 0x76, 0x65, 0x72, 0x74, 0x2a, 0x77, 0x0a, 0x04, 0x4b, 0x69, - 0x6e, 0x64, 0x12, 0x0f, 0x0a, 0x0b, 0x4b, 0x69, 0x6e, 0x64, 0x55, 0x6e, 0x6b, 0x6e, 0x6f, 0x77, - 0x6e, 0x10, 0x00, 0x12, 0x12, 0x0a, 0x0e, 0x4b, 0x69, 0x6e, 0x64, 0x4d, 0x65, 0x73, 0x68, 0x43, - 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x10, 0x01, 0x12, 0x17, 0x0a, 0x13, 0x4b, 0x69, 0x6e, 0x64, 0x53, - 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x52, 0x65, 0x73, 0x6f, 0x6c, 0x76, 0x65, 0x72, 0x10, 0x02, - 0x12, 0x16, 0x0a, 0x12, 0x4b, 0x69, 0x6e, 0x64, 0x49, 0x6e, 0x67, 0x72, 0x65, 0x73, 0x73, 0x47, - 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x10, 0x03, 0x12, 0x19, 0x0a, 0x15, 0x4b, 0x69, 0x6e, 0x64, - 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x49, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x69, 0x6f, 0x6e, - 0x73, 0x10, 0x04, 0x2a, 0x26, 0x0a, 0x0f, 0x49, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x69, 0x6f, 0x6e, - 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x08, 0x0a, 0x04, 0x44, 0x65, 0x6e, 0x79, 0x10, 0x00, - 0x12, 0x09, 0x0a, 0x05, 0x41, 0x6c, 0x6c, 0x6f, 0x77, 0x10, 0x01, 0x2a, 0x21, 0x0a, 0x13, 0x49, - 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x54, 0x79, - 0x70, 0x65, 0x12, 0x0a, 0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x10, 0x00, 0x42, 0xa6, - 0x02, 0x0a, 0x29, 0x63, 0x6f, 0x6d, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, - 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, - 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x42, 0x10, 0x43, 0x6f, - 0x6e, 0x66, 0x69, 0x67, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, - 0x5a, 0x2f, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x68, 0x61, 0x73, - 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2f, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2f, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x2f, 0x70, 0x62, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x65, 0x6e, 0x74, 0x72, - 0x79, 0xa2, 0x02, 0x04, 0x48, 0x43, 0x49, 0x43, 0xaa, 0x02, 0x25, 0x48, 0x61, 0x73, 0x68, 0x69, - 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x43, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x49, 0x6e, 0x74, 0x65, - 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x65, 0x6e, 0x74, 0x72, 0x79, - 0xca, 0x02, 0x25, 0x48, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x5c, 0x43, 0x6f, 0x6e, - 0x73, 0x75, 0x6c, 0x5c, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x5c, 0x43, 0x6f, 0x6e, - 0x66, 0x69, 0x67, 0x65, 0x6e, 0x74, 0x72, 0x79, 0xe2, 0x02, 0x31, 0x48, 0x61, 0x73, 0x68, 0x69, - 0x63, 0x6f, 0x72, 0x70, 0x5c, 0x43, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x5c, 0x49, 0x6e, 0x74, 0x65, - 0x72, 0x6e, 0x61, 0x6c, 0x5c, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x65, 0x6e, 0x74, 0x72, 0x79, - 0x5c, 0x47, 0x50, 0x42, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0xea, 0x02, 0x28, 0x48, - 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x3a, 0x3a, 0x43, 0x6f, 0x6e, 0x73, 0x75, 0x6c, - 0x3a, 0x3a, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x3a, 0x3a, 0x43, 0x6f, 0x6e, 0x66, - 0x69, 0x67, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x48, 0x54, 0x54, 0x50, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x04, + 0x48, 0x54, 0x54, 0x50, 0x22, 0xed, 0x01, 0x0a, 0x17, 0x49, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x69, + 0x6f, 0x6e, 0x48, 0x54, 0x54, 0x50, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, + 0x12, 0x1c, 0x0a, 0x09, 0x50, 0x61, 0x74, 0x68, 0x45, 0x78, 0x61, 0x63, 0x74, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x09, 0x50, 0x61, 0x74, 0x68, 0x45, 0x78, 0x61, 0x63, 0x74, 0x12, 0x1e, + 0x0a, 0x0a, 0x50, 0x61, 0x74, 0x68, 0x50, 0x72, 0x65, 0x66, 0x69, 0x78, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x0a, 0x50, 0x61, 0x74, 0x68, 0x50, 0x72, 0x65, 0x66, 0x69, 0x78, 0x12, 0x1c, + 0x0a, 0x09, 0x50, 0x61, 0x74, 0x68, 0x52, 0x65, 0x67, 0x65, 0x78, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x09, 0x50, 0x61, 0x74, 0x68, 0x52, 0x65, 0x67, 0x65, 0x78, 0x12, 0x5c, 0x0a, 0x06, + 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x44, 0x2e, 0x68, + 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, + 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x65, + 0x6e, 0x74, 0x72, 0x79, 0x2e, 0x49, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x69, 0x6f, 0x6e, 0x48, 0x54, + 0x54, 0x50, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, + 0x6f, 0x6e, 0x52, 0x06, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x12, 0x18, 0x0a, 0x07, 0x4d, 0x65, + 0x74, 0x68, 0x6f, 0x64, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, 0x4d, 0x65, 0x74, + 0x68, 0x6f, 0x64, 0x73, 0x22, 0xc1, 0x01, 0x0a, 0x1d, 0x49, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x69, + 0x6f, 0x6e, 0x48, 0x54, 0x54, 0x50, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x50, 0x65, 0x72, 0x6d, + 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x50, 0x72, + 0x65, 0x73, 0x65, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x50, 0x72, 0x65, + 0x73, 0x65, 0x6e, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x45, 0x78, 0x61, 0x63, 0x74, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x05, 0x45, 0x78, 0x61, 0x63, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x50, 0x72, + 0x65, 0x66, 0x69, 0x78, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x50, 0x72, 0x65, 0x66, + 0x69, 0x78, 0x12, 0x16, 0x0a, 0x06, 0x53, 0x75, 0x66, 0x66, 0x69, 0x78, 0x18, 0x05, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x06, 0x53, 0x75, 0x66, 0x66, 0x69, 0x78, 0x12, 0x14, 0x0a, 0x05, 0x52, 0x65, + 0x67, 0x65, 0x78, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x52, 0x65, 0x67, 0x65, 0x78, + 0x12, 0x16, 0x0a, 0x06, 0x49, 0x6e, 0x76, 0x65, 0x72, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x08, + 0x52, 0x06, 0x49, 0x6e, 0x76, 0x65, 0x72, 0x74, 0x2a, 0x77, 0x0a, 0x04, 0x4b, 0x69, 0x6e, 0x64, + 0x12, 0x0f, 0x0a, 0x0b, 0x4b, 0x69, 0x6e, 0x64, 0x55, 0x6e, 0x6b, 0x6e, 0x6f, 0x77, 0x6e, 0x10, + 0x00, 0x12, 0x12, 0x0a, 0x0e, 0x4b, 0x69, 0x6e, 0x64, 0x4d, 0x65, 0x73, 0x68, 0x43, 0x6f, 0x6e, + 0x66, 0x69, 0x67, 0x10, 0x01, 0x12, 0x17, 0x0a, 0x13, 0x4b, 0x69, 0x6e, 0x64, 0x53, 0x65, 0x72, + 0x76, 0x69, 0x63, 0x65, 0x52, 0x65, 0x73, 0x6f, 0x6c, 0x76, 0x65, 0x72, 0x10, 0x02, 0x12, 0x16, + 0x0a, 0x12, 0x4b, 0x69, 0x6e, 0x64, 0x49, 0x6e, 0x67, 0x72, 0x65, 0x73, 0x73, 0x47, 0x61, 0x74, + 0x65, 0x77, 0x61, 0x79, 0x10, 0x03, 0x12, 0x19, 0x0a, 0x15, 0x4b, 0x69, 0x6e, 0x64, 0x53, 0x65, + 0x72, 0x76, 0x69, 0x63, 0x65, 0x49, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x10, + 0x04, 0x2a, 0x26, 0x0a, 0x0f, 0x49, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x69, 0x6f, 0x6e, 0x41, 0x63, + 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x08, 0x0a, 0x04, 0x44, 0x65, 0x6e, 0x79, 0x10, 0x00, 0x12, 0x09, + 0x0a, 0x05, 0x41, 0x6c, 0x6c, 0x6f, 0x77, 0x10, 0x01, 0x2a, 0x21, 0x0a, 0x13, 0x49, 0x6e, 0x74, + 0x65, 0x6e, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x54, 0x79, 0x70, 0x65, + 0x12, 0x0a, 0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x10, 0x00, 0x42, 0xa6, 0x02, 0x0a, + 0x29, 0x63, 0x6f, 0x6d, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, + 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x63, + 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x42, 0x10, 0x43, 0x6f, 0x6e, 0x66, + 0x69, 0x67, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x2f, + 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x68, 0x61, 0x73, 0x68, 0x69, + 0x63, 0x6f, 0x72, 0x70, 0x2f, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2f, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x2f, 0x70, 0x62, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x65, 0x6e, 0x74, 0x72, 0x79, 0xa2, + 0x02, 0x04, 0x48, 0x43, 0x49, 0x43, 0xaa, 0x02, 0x25, 0x48, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, + 0x72, 0x70, 0x2e, 0x43, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e, + 0x61, 0x6c, 0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x65, 0x6e, 0x74, 0x72, 0x79, 0xca, 0x02, + 0x25, 0x48, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x5c, 0x43, 0x6f, 0x6e, 0x73, 0x75, + 0x6c, 0x5c, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x5c, 0x43, 0x6f, 0x6e, 0x66, 0x69, + 0x67, 0x65, 0x6e, 0x74, 0x72, 0x79, 0xe2, 0x02, 0x31, 0x48, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, + 0x72, 0x70, 0x5c, 0x43, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x5c, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e, + 0x61, 0x6c, 0x5c, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x5c, 0x47, + 0x50, 0x42, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0xea, 0x02, 0x28, 0x48, 0x61, 0x73, + 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x3a, 0x3a, 0x43, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x3a, 0x3a, + 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x3a, 0x3a, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, + 0x65, 0x6e, 0x74, 0x72, 0x79, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -2962,7 +3087,7 @@ func file_proto_pbconfigentry_config_entry_proto_rawDescGZIP() []byte { } var file_proto_pbconfigentry_config_entry_proto_enumTypes = make([]protoimpl.EnumInfo, 3) -var file_proto_pbconfigentry_config_entry_proto_msgTypes = make([]protoimpl.MessageInfo, 39) +var file_proto_pbconfigentry_config_entry_proto_msgTypes = make([]protoimpl.MessageInfo, 40) var file_proto_pbconfigentry_config_entry_proto_goTypes = []interface{}{ (Kind)(0), // 0: hashicorp.consul.internal.configentry.Kind (IntentionAction)(0), // 1: hashicorp.consul.internal.configentry.IntentionAction @@ -2985,92 +3110,94 @@ var file_proto_pbconfigentry_config_entry_proto_goTypes = []interface{}{ (*HashPolicy)(nil), // 18: hashicorp.consul.internal.configentry.HashPolicy (*CookieConfig)(nil), // 19: hashicorp.consul.internal.configentry.CookieConfig (*IngressGateway)(nil), // 20: hashicorp.consul.internal.configentry.IngressGateway - (*GatewayTLSConfig)(nil), // 21: hashicorp.consul.internal.configentry.GatewayTLSConfig - (*GatewayTLSSDSConfig)(nil), // 22: hashicorp.consul.internal.configentry.GatewayTLSSDSConfig - (*IngressListener)(nil), // 23: hashicorp.consul.internal.configentry.IngressListener - (*IngressService)(nil), // 24: hashicorp.consul.internal.configentry.IngressService - (*GatewayServiceTLSConfig)(nil), // 25: hashicorp.consul.internal.configentry.GatewayServiceTLSConfig - (*HTTPHeaderModifiers)(nil), // 26: hashicorp.consul.internal.configentry.HTTPHeaderModifiers - (*ServiceIntentions)(nil), // 27: hashicorp.consul.internal.configentry.ServiceIntentions - (*SourceIntention)(nil), // 28: hashicorp.consul.internal.configentry.SourceIntention - (*IntentionPermission)(nil), // 29: hashicorp.consul.internal.configentry.IntentionPermission - (*IntentionHTTPPermission)(nil), // 30: hashicorp.consul.internal.configentry.IntentionHTTPPermission - (*IntentionHTTPHeaderPermission)(nil), // 31: hashicorp.consul.internal.configentry.IntentionHTTPHeaderPermission - nil, // 32: hashicorp.consul.internal.configentry.MeshConfig.MetaEntry - nil, // 33: hashicorp.consul.internal.configentry.ServiceResolver.SubsetsEntry - nil, // 34: hashicorp.consul.internal.configentry.ServiceResolver.FailoverEntry - nil, // 35: hashicorp.consul.internal.configentry.ServiceResolver.MetaEntry - nil, // 36: hashicorp.consul.internal.configentry.IngressGateway.MetaEntry - nil, // 37: hashicorp.consul.internal.configentry.IngressService.MetaEntry - nil, // 38: hashicorp.consul.internal.configentry.HTTPHeaderModifiers.AddEntry - nil, // 39: hashicorp.consul.internal.configentry.HTTPHeaderModifiers.SetEntry - nil, // 40: hashicorp.consul.internal.configentry.ServiceIntentions.MetaEntry - nil, // 41: hashicorp.consul.internal.configentry.SourceIntention.LegacyMetaEntry - (*pbcommon.EnterpriseMeta)(nil), // 42: hashicorp.consul.internal.common.EnterpriseMeta - (*pbcommon.RaftIndex)(nil), // 43: hashicorp.consul.internal.common.RaftIndex - (*durationpb.Duration)(nil), // 44: google.protobuf.Duration - (*timestamppb.Timestamp)(nil), // 45: google.protobuf.Timestamp + (*IngressServiceConfig)(nil), // 21: hashicorp.consul.internal.configentry.IngressServiceConfig + (*GatewayTLSConfig)(nil), // 22: hashicorp.consul.internal.configentry.GatewayTLSConfig + (*GatewayTLSSDSConfig)(nil), // 23: hashicorp.consul.internal.configentry.GatewayTLSSDSConfig + (*IngressListener)(nil), // 24: hashicorp.consul.internal.configentry.IngressListener + (*IngressService)(nil), // 25: hashicorp.consul.internal.configentry.IngressService + (*GatewayServiceTLSConfig)(nil), // 26: hashicorp.consul.internal.configentry.GatewayServiceTLSConfig + (*HTTPHeaderModifiers)(nil), // 27: hashicorp.consul.internal.configentry.HTTPHeaderModifiers + (*ServiceIntentions)(nil), // 28: hashicorp.consul.internal.configentry.ServiceIntentions + (*SourceIntention)(nil), // 29: hashicorp.consul.internal.configentry.SourceIntention + (*IntentionPermission)(nil), // 30: hashicorp.consul.internal.configentry.IntentionPermission + (*IntentionHTTPPermission)(nil), // 31: hashicorp.consul.internal.configentry.IntentionHTTPPermission + (*IntentionHTTPHeaderPermission)(nil), // 32: hashicorp.consul.internal.configentry.IntentionHTTPHeaderPermission + nil, // 33: hashicorp.consul.internal.configentry.MeshConfig.MetaEntry + nil, // 34: hashicorp.consul.internal.configentry.ServiceResolver.SubsetsEntry + nil, // 35: hashicorp.consul.internal.configentry.ServiceResolver.FailoverEntry + nil, // 36: hashicorp.consul.internal.configentry.ServiceResolver.MetaEntry + nil, // 37: hashicorp.consul.internal.configentry.IngressGateway.MetaEntry + nil, // 38: hashicorp.consul.internal.configentry.IngressService.MetaEntry + nil, // 39: hashicorp.consul.internal.configentry.HTTPHeaderModifiers.AddEntry + nil, // 40: hashicorp.consul.internal.configentry.HTTPHeaderModifiers.SetEntry + nil, // 41: hashicorp.consul.internal.configentry.ServiceIntentions.MetaEntry + nil, // 42: hashicorp.consul.internal.configentry.SourceIntention.LegacyMetaEntry + (*pbcommon.EnterpriseMeta)(nil), // 43: hashicorp.consul.internal.common.EnterpriseMeta + (*pbcommon.RaftIndex)(nil), // 44: hashicorp.consul.internal.common.RaftIndex + (*durationpb.Duration)(nil), // 45: google.protobuf.Duration + (*timestamppb.Timestamp)(nil), // 46: google.protobuf.Timestamp } var file_proto_pbconfigentry_config_entry_proto_depIdxs = []int32{ 0, // 0: hashicorp.consul.internal.configentry.ConfigEntry.Kind:type_name -> hashicorp.consul.internal.configentry.Kind - 42, // 1: hashicorp.consul.internal.configentry.ConfigEntry.EnterpriseMeta:type_name -> hashicorp.consul.internal.common.EnterpriseMeta - 43, // 2: hashicorp.consul.internal.configentry.ConfigEntry.RaftIndex:type_name -> hashicorp.consul.internal.common.RaftIndex + 43, // 1: hashicorp.consul.internal.configentry.ConfigEntry.EnterpriseMeta:type_name -> hashicorp.consul.internal.common.EnterpriseMeta + 44, // 2: hashicorp.consul.internal.configentry.ConfigEntry.RaftIndex:type_name -> hashicorp.consul.internal.common.RaftIndex 4, // 3: hashicorp.consul.internal.configentry.ConfigEntry.MeshConfig:type_name -> hashicorp.consul.internal.configentry.MeshConfig 10, // 4: hashicorp.consul.internal.configentry.ConfigEntry.ServiceResolver:type_name -> hashicorp.consul.internal.configentry.ServiceResolver 20, // 5: hashicorp.consul.internal.configentry.ConfigEntry.IngressGateway:type_name -> hashicorp.consul.internal.configentry.IngressGateway - 27, // 6: hashicorp.consul.internal.configentry.ConfigEntry.ServiceIntentions:type_name -> hashicorp.consul.internal.configentry.ServiceIntentions + 28, // 6: hashicorp.consul.internal.configentry.ConfigEntry.ServiceIntentions:type_name -> hashicorp.consul.internal.configentry.ServiceIntentions 5, // 7: hashicorp.consul.internal.configentry.MeshConfig.TransparentProxy:type_name -> hashicorp.consul.internal.configentry.TransparentProxyMeshConfig 6, // 8: hashicorp.consul.internal.configentry.MeshConfig.TLS:type_name -> hashicorp.consul.internal.configentry.MeshTLSConfig 8, // 9: hashicorp.consul.internal.configentry.MeshConfig.HTTP:type_name -> hashicorp.consul.internal.configentry.MeshHTTPConfig - 32, // 10: hashicorp.consul.internal.configentry.MeshConfig.Meta:type_name -> hashicorp.consul.internal.configentry.MeshConfig.MetaEntry + 33, // 10: hashicorp.consul.internal.configentry.MeshConfig.Meta:type_name -> hashicorp.consul.internal.configentry.MeshConfig.MetaEntry 9, // 11: hashicorp.consul.internal.configentry.MeshConfig.Peering:type_name -> hashicorp.consul.internal.configentry.PeeringMeshConfig 7, // 12: hashicorp.consul.internal.configentry.MeshTLSConfig.Incoming:type_name -> hashicorp.consul.internal.configentry.MeshDirectionalTLSConfig 7, // 13: hashicorp.consul.internal.configentry.MeshTLSConfig.Outgoing:type_name -> hashicorp.consul.internal.configentry.MeshDirectionalTLSConfig - 33, // 14: hashicorp.consul.internal.configentry.ServiceResolver.Subsets:type_name -> hashicorp.consul.internal.configentry.ServiceResolver.SubsetsEntry + 34, // 14: hashicorp.consul.internal.configentry.ServiceResolver.Subsets:type_name -> hashicorp.consul.internal.configentry.ServiceResolver.SubsetsEntry 12, // 15: hashicorp.consul.internal.configentry.ServiceResolver.Redirect:type_name -> hashicorp.consul.internal.configentry.ServiceResolverRedirect - 34, // 16: hashicorp.consul.internal.configentry.ServiceResolver.Failover:type_name -> hashicorp.consul.internal.configentry.ServiceResolver.FailoverEntry - 44, // 17: hashicorp.consul.internal.configentry.ServiceResolver.ConnectTimeout:type_name -> google.protobuf.Duration + 35, // 16: hashicorp.consul.internal.configentry.ServiceResolver.Failover:type_name -> hashicorp.consul.internal.configentry.ServiceResolver.FailoverEntry + 45, // 17: hashicorp.consul.internal.configentry.ServiceResolver.ConnectTimeout:type_name -> google.protobuf.Duration 15, // 18: hashicorp.consul.internal.configentry.ServiceResolver.LoadBalancer:type_name -> hashicorp.consul.internal.configentry.LoadBalancer - 35, // 19: hashicorp.consul.internal.configentry.ServiceResolver.Meta:type_name -> hashicorp.consul.internal.configentry.ServiceResolver.MetaEntry + 36, // 19: hashicorp.consul.internal.configentry.ServiceResolver.Meta:type_name -> hashicorp.consul.internal.configentry.ServiceResolver.MetaEntry 14, // 20: hashicorp.consul.internal.configentry.ServiceResolverFailover.Targets:type_name -> hashicorp.consul.internal.configentry.ServiceResolverFailoverTarget 16, // 21: hashicorp.consul.internal.configentry.LoadBalancer.RingHashConfig:type_name -> hashicorp.consul.internal.configentry.RingHashConfig 17, // 22: hashicorp.consul.internal.configentry.LoadBalancer.LeastRequestConfig:type_name -> hashicorp.consul.internal.configentry.LeastRequestConfig 18, // 23: hashicorp.consul.internal.configentry.LoadBalancer.HashPolicies:type_name -> hashicorp.consul.internal.configentry.HashPolicy 19, // 24: hashicorp.consul.internal.configentry.HashPolicy.CookieConfig:type_name -> hashicorp.consul.internal.configentry.CookieConfig - 44, // 25: hashicorp.consul.internal.configentry.CookieConfig.TTL:type_name -> google.protobuf.Duration - 21, // 26: hashicorp.consul.internal.configentry.IngressGateway.TLS:type_name -> hashicorp.consul.internal.configentry.GatewayTLSConfig - 23, // 27: hashicorp.consul.internal.configentry.IngressGateway.Listeners:type_name -> hashicorp.consul.internal.configentry.IngressListener - 36, // 28: hashicorp.consul.internal.configentry.IngressGateway.Meta:type_name -> hashicorp.consul.internal.configentry.IngressGateway.MetaEntry - 22, // 29: hashicorp.consul.internal.configentry.GatewayTLSConfig.SDS:type_name -> hashicorp.consul.internal.configentry.GatewayTLSSDSConfig - 24, // 30: hashicorp.consul.internal.configentry.IngressListener.Services:type_name -> hashicorp.consul.internal.configentry.IngressService - 21, // 31: hashicorp.consul.internal.configentry.IngressListener.TLS:type_name -> hashicorp.consul.internal.configentry.GatewayTLSConfig - 25, // 32: hashicorp.consul.internal.configentry.IngressService.TLS:type_name -> hashicorp.consul.internal.configentry.GatewayServiceTLSConfig - 26, // 33: hashicorp.consul.internal.configentry.IngressService.RequestHeaders:type_name -> hashicorp.consul.internal.configentry.HTTPHeaderModifiers - 26, // 34: hashicorp.consul.internal.configentry.IngressService.ResponseHeaders:type_name -> hashicorp.consul.internal.configentry.HTTPHeaderModifiers - 37, // 35: hashicorp.consul.internal.configentry.IngressService.Meta:type_name -> hashicorp.consul.internal.configentry.IngressService.MetaEntry - 42, // 36: hashicorp.consul.internal.configentry.IngressService.EnterpriseMeta:type_name -> hashicorp.consul.internal.common.EnterpriseMeta - 22, // 37: hashicorp.consul.internal.configentry.GatewayServiceTLSConfig.SDS:type_name -> hashicorp.consul.internal.configentry.GatewayTLSSDSConfig - 38, // 38: hashicorp.consul.internal.configentry.HTTPHeaderModifiers.Add:type_name -> hashicorp.consul.internal.configentry.HTTPHeaderModifiers.AddEntry - 39, // 39: hashicorp.consul.internal.configentry.HTTPHeaderModifiers.Set:type_name -> hashicorp.consul.internal.configentry.HTTPHeaderModifiers.SetEntry - 28, // 40: hashicorp.consul.internal.configentry.ServiceIntentions.Sources:type_name -> hashicorp.consul.internal.configentry.SourceIntention - 40, // 41: hashicorp.consul.internal.configentry.ServiceIntentions.Meta:type_name -> hashicorp.consul.internal.configentry.ServiceIntentions.MetaEntry - 1, // 42: hashicorp.consul.internal.configentry.SourceIntention.Action:type_name -> hashicorp.consul.internal.configentry.IntentionAction - 29, // 43: hashicorp.consul.internal.configentry.SourceIntention.Permissions:type_name -> hashicorp.consul.internal.configentry.IntentionPermission - 2, // 44: hashicorp.consul.internal.configentry.SourceIntention.Type:type_name -> hashicorp.consul.internal.configentry.IntentionSourceType - 41, // 45: hashicorp.consul.internal.configentry.SourceIntention.LegacyMeta:type_name -> hashicorp.consul.internal.configentry.SourceIntention.LegacyMetaEntry - 45, // 46: hashicorp.consul.internal.configentry.SourceIntention.LegacyCreateTime:type_name -> google.protobuf.Timestamp - 45, // 47: hashicorp.consul.internal.configentry.SourceIntention.LegacyUpdateTime:type_name -> google.protobuf.Timestamp - 42, // 48: hashicorp.consul.internal.configentry.SourceIntention.EnterpriseMeta:type_name -> hashicorp.consul.internal.common.EnterpriseMeta - 1, // 49: hashicorp.consul.internal.configentry.IntentionPermission.Action:type_name -> hashicorp.consul.internal.configentry.IntentionAction - 30, // 50: hashicorp.consul.internal.configentry.IntentionPermission.HTTP:type_name -> hashicorp.consul.internal.configentry.IntentionHTTPPermission - 31, // 51: hashicorp.consul.internal.configentry.IntentionHTTPPermission.Header:type_name -> hashicorp.consul.internal.configentry.IntentionHTTPHeaderPermission - 11, // 52: hashicorp.consul.internal.configentry.ServiceResolver.SubsetsEntry.value:type_name -> hashicorp.consul.internal.configentry.ServiceResolverSubset - 13, // 53: hashicorp.consul.internal.configentry.ServiceResolver.FailoverEntry.value:type_name -> hashicorp.consul.internal.configentry.ServiceResolverFailover - 54, // [54:54] is the sub-list for method output_type - 54, // [54:54] is the sub-list for method input_type - 54, // [54:54] is the sub-list for extension type_name - 54, // [54:54] is the sub-list for extension extendee - 0, // [0:54] is the sub-list for field type_name + 45, // 25: hashicorp.consul.internal.configentry.CookieConfig.TTL:type_name -> google.protobuf.Duration + 22, // 26: hashicorp.consul.internal.configentry.IngressGateway.TLS:type_name -> hashicorp.consul.internal.configentry.GatewayTLSConfig + 24, // 27: hashicorp.consul.internal.configentry.IngressGateway.Listeners:type_name -> hashicorp.consul.internal.configentry.IngressListener + 37, // 28: hashicorp.consul.internal.configentry.IngressGateway.Meta:type_name -> hashicorp.consul.internal.configentry.IngressGateway.MetaEntry + 21, // 29: hashicorp.consul.internal.configentry.IngressGateway.Defaults:type_name -> hashicorp.consul.internal.configentry.IngressServiceConfig + 23, // 30: hashicorp.consul.internal.configentry.GatewayTLSConfig.SDS:type_name -> hashicorp.consul.internal.configentry.GatewayTLSSDSConfig + 25, // 31: hashicorp.consul.internal.configentry.IngressListener.Services:type_name -> hashicorp.consul.internal.configentry.IngressService + 22, // 32: hashicorp.consul.internal.configentry.IngressListener.TLS:type_name -> hashicorp.consul.internal.configentry.GatewayTLSConfig + 26, // 33: hashicorp.consul.internal.configentry.IngressService.TLS:type_name -> hashicorp.consul.internal.configentry.GatewayServiceTLSConfig + 27, // 34: hashicorp.consul.internal.configentry.IngressService.RequestHeaders:type_name -> hashicorp.consul.internal.configentry.HTTPHeaderModifiers + 27, // 35: hashicorp.consul.internal.configentry.IngressService.ResponseHeaders:type_name -> hashicorp.consul.internal.configentry.HTTPHeaderModifiers + 38, // 36: hashicorp.consul.internal.configentry.IngressService.Meta:type_name -> hashicorp.consul.internal.configentry.IngressService.MetaEntry + 43, // 37: hashicorp.consul.internal.configentry.IngressService.EnterpriseMeta:type_name -> hashicorp.consul.internal.common.EnterpriseMeta + 23, // 38: hashicorp.consul.internal.configentry.GatewayServiceTLSConfig.SDS:type_name -> hashicorp.consul.internal.configentry.GatewayTLSSDSConfig + 39, // 39: hashicorp.consul.internal.configentry.HTTPHeaderModifiers.Add:type_name -> hashicorp.consul.internal.configentry.HTTPHeaderModifiers.AddEntry + 40, // 40: hashicorp.consul.internal.configentry.HTTPHeaderModifiers.Set:type_name -> hashicorp.consul.internal.configentry.HTTPHeaderModifiers.SetEntry + 29, // 41: hashicorp.consul.internal.configentry.ServiceIntentions.Sources:type_name -> hashicorp.consul.internal.configentry.SourceIntention + 41, // 42: hashicorp.consul.internal.configentry.ServiceIntentions.Meta:type_name -> hashicorp.consul.internal.configentry.ServiceIntentions.MetaEntry + 1, // 43: hashicorp.consul.internal.configentry.SourceIntention.Action:type_name -> hashicorp.consul.internal.configentry.IntentionAction + 30, // 44: hashicorp.consul.internal.configentry.SourceIntention.Permissions:type_name -> hashicorp.consul.internal.configentry.IntentionPermission + 2, // 45: hashicorp.consul.internal.configentry.SourceIntention.Type:type_name -> hashicorp.consul.internal.configentry.IntentionSourceType + 42, // 46: hashicorp.consul.internal.configentry.SourceIntention.LegacyMeta:type_name -> hashicorp.consul.internal.configentry.SourceIntention.LegacyMetaEntry + 46, // 47: hashicorp.consul.internal.configentry.SourceIntention.LegacyCreateTime:type_name -> google.protobuf.Timestamp + 46, // 48: hashicorp.consul.internal.configentry.SourceIntention.LegacyUpdateTime:type_name -> google.protobuf.Timestamp + 43, // 49: hashicorp.consul.internal.configentry.SourceIntention.EnterpriseMeta:type_name -> hashicorp.consul.internal.common.EnterpriseMeta + 1, // 50: hashicorp.consul.internal.configentry.IntentionPermission.Action:type_name -> hashicorp.consul.internal.configentry.IntentionAction + 31, // 51: hashicorp.consul.internal.configentry.IntentionPermission.HTTP:type_name -> hashicorp.consul.internal.configentry.IntentionHTTPPermission + 32, // 52: hashicorp.consul.internal.configentry.IntentionHTTPPermission.Header:type_name -> hashicorp.consul.internal.configentry.IntentionHTTPHeaderPermission + 11, // 53: hashicorp.consul.internal.configentry.ServiceResolver.SubsetsEntry.value:type_name -> hashicorp.consul.internal.configentry.ServiceResolverSubset + 13, // 54: hashicorp.consul.internal.configentry.ServiceResolver.FailoverEntry.value:type_name -> hashicorp.consul.internal.configentry.ServiceResolverFailover + 55, // [55:55] is the sub-list for method output_type + 55, // [55:55] is the sub-list for method input_type + 55, // [55:55] is the sub-list for extension type_name + 55, // [55:55] is the sub-list for extension extendee + 0, // [0:55] is the sub-list for field type_name } func init() { file_proto_pbconfigentry_config_entry_proto_init() } @@ -3296,7 +3423,7 @@ func file_proto_pbconfigentry_config_entry_proto_init() { } } file_proto_pbconfigentry_config_entry_proto_msgTypes[18].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GatewayTLSConfig); i { + switch v := v.(*IngressServiceConfig); i { case 0: return &v.state case 1: @@ -3308,7 +3435,7 @@ func file_proto_pbconfigentry_config_entry_proto_init() { } } file_proto_pbconfigentry_config_entry_proto_msgTypes[19].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GatewayTLSSDSConfig); i { + switch v := v.(*GatewayTLSConfig); i { case 0: return &v.state case 1: @@ -3320,7 +3447,7 @@ func file_proto_pbconfigentry_config_entry_proto_init() { } } file_proto_pbconfigentry_config_entry_proto_msgTypes[20].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*IngressListener); i { + switch v := v.(*GatewayTLSSDSConfig); i { case 0: return &v.state case 1: @@ -3332,7 +3459,7 @@ func file_proto_pbconfigentry_config_entry_proto_init() { } } file_proto_pbconfigentry_config_entry_proto_msgTypes[21].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*IngressService); i { + switch v := v.(*IngressListener); i { case 0: return &v.state case 1: @@ -3344,7 +3471,7 @@ func file_proto_pbconfigentry_config_entry_proto_init() { } } file_proto_pbconfigentry_config_entry_proto_msgTypes[22].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GatewayServiceTLSConfig); i { + switch v := v.(*IngressService); i { case 0: return &v.state case 1: @@ -3356,7 +3483,7 @@ func file_proto_pbconfigentry_config_entry_proto_init() { } } file_proto_pbconfigentry_config_entry_proto_msgTypes[23].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*HTTPHeaderModifiers); i { + switch v := v.(*GatewayServiceTLSConfig); i { case 0: return &v.state case 1: @@ -3368,7 +3495,7 @@ func file_proto_pbconfigentry_config_entry_proto_init() { } } file_proto_pbconfigentry_config_entry_proto_msgTypes[24].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ServiceIntentions); i { + switch v := v.(*HTTPHeaderModifiers); i { case 0: return &v.state case 1: @@ -3380,7 +3507,7 @@ func file_proto_pbconfigentry_config_entry_proto_init() { } } file_proto_pbconfigentry_config_entry_proto_msgTypes[25].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*SourceIntention); i { + switch v := v.(*ServiceIntentions); i { case 0: return &v.state case 1: @@ -3392,7 +3519,7 @@ func file_proto_pbconfigentry_config_entry_proto_init() { } } file_proto_pbconfigentry_config_entry_proto_msgTypes[26].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*IntentionPermission); i { + switch v := v.(*SourceIntention); i { case 0: return &v.state case 1: @@ -3404,7 +3531,7 @@ func file_proto_pbconfigentry_config_entry_proto_init() { } } file_proto_pbconfigentry_config_entry_proto_msgTypes[27].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*IntentionHTTPPermission); i { + switch v := v.(*IntentionPermission); i { case 0: return &v.state case 1: @@ -3416,6 +3543,18 @@ func file_proto_pbconfigentry_config_entry_proto_init() { } } file_proto_pbconfigentry_config_entry_proto_msgTypes[28].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*IntentionHTTPPermission); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_pbconfigentry_config_entry_proto_msgTypes[29].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*IntentionHTTPHeaderPermission); i { case 0: return &v.state @@ -3440,7 +3579,7 @@ func file_proto_pbconfigentry_config_entry_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_proto_pbconfigentry_config_entry_proto_rawDesc, NumEnums: 3, - NumMessages: 39, + NumMessages: 40, NumExtensions: 0, NumServices: 0, }, diff --git a/proto/pbconfigentry/config_entry.proto b/proto/pbconfigentry/config_entry.proto index dd4dc049e3..fba12fb150 100644 --- a/proto/pbconfigentry/config_entry.proto +++ b/proto/pbconfigentry/config_entry.proto @@ -228,6 +228,18 @@ message IngressGateway { GatewayTLSConfig TLS = 1; repeated IngressListener Listeners = 2; map Meta = 3; + IngressServiceConfig Defaults = 4; +} + +// mog annotation: +// +// target=github.com/hashicorp/consul/agent/structs.IngressServiceConfig +// output=config_entry.gen.go +// name=Structs +message IngressServiceConfig { + uint32 MaxConnections = 1; + uint32 MaxPendingRequests = 2; + uint32 MaxConcurrentRequests = 3; } // mog annotation: @@ -283,6 +295,9 @@ message IngressService { map Meta = 6; // mog: func-to=enterpriseMetaToStructs func-from=enterpriseMetaFromStructs common.EnterpriseMeta EnterpriseMeta = 7; + uint32 MaxConnections = 8; + uint32 MaxPendingRequests = 9; + uint32 MaxConcurrentRequests = 10; } // mog annotation: diff --git a/proto/pbpeering/peering.gen.go b/proto/pbpeering/peering.gen.go index 5707e3b6c5..fb69f53959 100644 --- a/proto/pbpeering/peering.gen.go +++ b/proto/pbpeering/peering.gen.go @@ -76,8 +76,7 @@ func PeeringToAPI(s *Peering, t *api.Peering) { t.PeerCAPems = s.PeerCAPems t.PeerServerName = s.PeerServerName t.PeerServerAddresses = s.PeerServerAddresses - t.ImportedServiceCount = s.ImportedServiceCount - t.ExportedServiceCount = s.ExportedServiceCount + t.StreamStatus = StreamStatusToAPI(s.StreamStatus) t.CreateIndex = s.CreateIndex t.ModifyIndex = s.ModifyIndex } @@ -95,8 +94,7 @@ func PeeringFromAPI(t *api.Peering, s *Peering) { s.PeerCAPems = t.PeerCAPems s.PeerServerName = t.PeerServerName s.PeerServerAddresses = t.PeerServerAddresses - s.ImportedServiceCount = t.ImportedServiceCount - s.ExportedServiceCount = t.ExportedServiceCount + s.StreamStatus = StreamStatusFromAPI(t.StreamStatus) s.CreateIndex = t.CreateIndex s.ModifyIndex = t.ModifyIndex } diff --git a/proto/pbpeering/peering.go b/proto/pbpeering/peering.go index 74f5a52f08..47b6ac000f 100644 --- a/proto/pbpeering/peering.go +++ b/proto/pbpeering/peering.go @@ -142,6 +142,26 @@ func PeeringStateFromAPI(t api.PeeringState) PeeringState { } } +func StreamStatusToAPI(status *StreamStatus) api.PeeringStreamStatus { + return api.PeeringStreamStatus{ + ImportedServices: status.ImportedServices, + ExportedServices: status.ExportedServices, + LastHeartbeat: structs.TimeFromProto(status.LastHeartbeat), + LastReceive: structs.TimeFromProto(status.LastReceive), + LastSend: structs.TimeFromProto(status.LastSend), + } +} + +func StreamStatusFromAPI(status api.PeeringStreamStatus) *StreamStatus { + return &StreamStatus{ + ImportedServices: status.ImportedServices, + ExportedServices: status.ExportedServices, + LastHeartbeat: structs.TimeToProto(status.LastHeartbeat), + LastReceive: structs.TimeToProto(status.LastReceive), + LastSend: structs.TimeToProto(status.LastSend), + } +} + func (p *Peering) IsActive() bool { if p == nil || p.State == PeeringState_TERMINATED { return false diff --git a/proto/pbpeering/peering.pb.binary.go b/proto/pbpeering/peering.pb.binary.go index 499e312260..444c667a11 100644 --- a/proto/pbpeering/peering.pb.binary.go +++ b/proto/pbpeering/peering.pb.binary.go @@ -97,6 +97,16 @@ func (msg *Peering) UnmarshalBinary(b []byte) error { return proto.Unmarshal(b, msg) } +// MarshalBinary implements encoding.BinaryMarshaler +func (msg *StreamStatus) MarshalBinary() ([]byte, error) { + return proto.Marshal(msg) +} + +// UnmarshalBinary implements encoding.BinaryUnmarshaler +func (msg *StreamStatus) UnmarshalBinary(b []byte) error { + return proto.Unmarshal(b, msg) +} + // MarshalBinary implements encoding.BinaryMarshaler func (msg *PeeringTrustBundle) MarshalBinary() ([]byte, error) { return proto.Marshal(msg) diff --git a/proto/pbpeering/peering.pb.go b/proto/pbpeering/peering.pb.go index 8fdff02468..ac1a931061 100644 --- a/proto/pbpeering/peering.pb.go +++ b/proto/pbpeering/peering.pb.go @@ -320,10 +320,10 @@ type Peering struct { PeerServerName string `protobuf:"bytes,9,opt,name=PeerServerName,proto3" json:"PeerServerName,omitempty"` // PeerServerAddresses contains all the the connection addresses for the remote peer. PeerServerAddresses []string `protobuf:"bytes,10,rep,name=PeerServerAddresses,proto3" json:"PeerServerAddresses,omitempty"` - // ImportedServiceCount is the count of how many services are imported from this peering. - ImportedServiceCount uint64 `protobuf:"varint,13,opt,name=ImportedServiceCount,proto3" json:"ImportedServiceCount,omitempty"` - // ExportedServiceCount is the count of how many services are exported to this peering. - ExportedServiceCount uint64 `protobuf:"varint,14,opt,name=ExportedServiceCount,proto3" json:"ExportedServiceCount,omitempty"` + // StreamStatus contains information computed on read based on the state of the stream. + // + // mog: func-to=StreamStatusToAPI func-from=StreamStatusFromAPI + StreamStatus *StreamStatus `protobuf:"bytes,13,opt,name=StreamStatus,proto3" json:"StreamStatus,omitempty"` // CreateIndex is the Raft index at which the Peering was created. // @gotags: bexpr:"-" CreateIndex uint64 `protobuf:"varint,11,opt,name=CreateIndex,proto3" json:"CreateIndex,omitempty" bexpr:"-"` @@ -434,18 +434,11 @@ func (x *Peering) GetPeerServerAddresses() []string { return nil } -func (x *Peering) GetImportedServiceCount() uint64 { - if x != nil { - return x.ImportedServiceCount - } - return 0 -} - -func (x *Peering) GetExportedServiceCount() uint64 { +func (x *Peering) GetStreamStatus() *StreamStatus { if x != nil { - return x.ExportedServiceCount + return x.StreamStatus } - return 0 + return nil } func (x *Peering) GetCreateIndex() uint64 { @@ -462,6 +455,91 @@ func (x *Peering) GetModifyIndex() uint64 { return 0 } +// StreamStatus represents information about an active peering stream. +type StreamStatus struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // ImportedServices is the list of services imported from this peering. + ImportedServices []string `protobuf:"bytes,1,rep,name=ImportedServices,proto3" json:"ImportedServices,omitempty"` + // ExportedServices is the list of services exported to this peering. + ExportedServices []string `protobuf:"bytes,2,rep,name=ExportedServices,proto3" json:"ExportedServices,omitempty"` + // LastHeartbeat represents when the last heartbeat message was received. + LastHeartbeat *timestamppb.Timestamp `protobuf:"bytes,3,opt,name=LastHeartbeat,proto3" json:"LastHeartbeat,omitempty"` + // LastReceive represents when any message was last received, regardless of success or error. + LastReceive *timestamppb.Timestamp `protobuf:"bytes,4,opt,name=LastReceive,proto3" json:"LastReceive,omitempty"` + // LastSend represents when any message was last sent, regardless of success or error. + LastSend *timestamppb.Timestamp `protobuf:"bytes,5,opt,name=LastSend,proto3" json:"LastSend,omitempty"` +} + +func (x *StreamStatus) Reset() { + *x = StreamStatus{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_pbpeering_peering_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *StreamStatus) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*StreamStatus) ProtoMessage() {} + +func (x *StreamStatus) ProtoReflect() protoreflect.Message { + mi := &file_proto_pbpeering_peering_proto_msgTypes[3] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use StreamStatus.ProtoReflect.Descriptor instead. +func (*StreamStatus) Descriptor() ([]byte, []int) { + return file_proto_pbpeering_peering_proto_rawDescGZIP(), []int{3} +} + +func (x *StreamStatus) GetImportedServices() []string { + if x != nil { + return x.ImportedServices + } + return nil +} + +func (x *StreamStatus) GetExportedServices() []string { + if x != nil { + return x.ExportedServices + } + return nil +} + +func (x *StreamStatus) GetLastHeartbeat() *timestamppb.Timestamp { + if x != nil { + return x.LastHeartbeat + } + return nil +} + +func (x *StreamStatus) GetLastReceive() *timestamppb.Timestamp { + if x != nil { + return x.LastReceive + } + return nil +} + +func (x *StreamStatus) GetLastSend() *timestamppb.Timestamp { + if x != nil { + return x.LastSend + } + return nil +} + // PeeringTrustBundle holds the trust information for validating requests from a peer. type PeeringTrustBundle struct { state protoimpl.MessageState @@ -490,7 +568,7 @@ type PeeringTrustBundle struct { func (x *PeeringTrustBundle) Reset() { *x = PeeringTrustBundle{} if protoimpl.UnsafeEnabled { - mi := &file_proto_pbpeering_peering_proto_msgTypes[3] + mi := &file_proto_pbpeering_peering_proto_msgTypes[4] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -503,7 +581,7 @@ func (x *PeeringTrustBundle) String() string { func (*PeeringTrustBundle) ProtoMessage() {} func (x *PeeringTrustBundle) ProtoReflect() protoreflect.Message { - mi := &file_proto_pbpeering_peering_proto_msgTypes[3] + mi := &file_proto_pbpeering_peering_proto_msgTypes[4] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -516,7 +594,7 @@ func (x *PeeringTrustBundle) ProtoReflect() protoreflect.Message { // Deprecated: Use PeeringTrustBundle.ProtoReflect.Descriptor instead. func (*PeeringTrustBundle) Descriptor() ([]byte, []int) { - return file_proto_pbpeering_peering_proto_rawDescGZIP(), []int{3} + return file_proto_pbpeering_peering_proto_rawDescGZIP(), []int{4} } func (x *PeeringTrustBundle) GetTrustDomain() string { @@ -581,7 +659,7 @@ type PeeringServerAddresses struct { func (x *PeeringServerAddresses) Reset() { *x = PeeringServerAddresses{} if protoimpl.UnsafeEnabled { - mi := &file_proto_pbpeering_peering_proto_msgTypes[4] + mi := &file_proto_pbpeering_peering_proto_msgTypes[5] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -594,7 +672,7 @@ func (x *PeeringServerAddresses) String() string { func (*PeeringServerAddresses) ProtoMessage() {} func (x *PeeringServerAddresses) ProtoReflect() protoreflect.Message { - mi := &file_proto_pbpeering_peering_proto_msgTypes[4] + mi := &file_proto_pbpeering_peering_proto_msgTypes[5] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -607,7 +685,7 @@ func (x *PeeringServerAddresses) ProtoReflect() protoreflect.Message { // Deprecated: Use PeeringServerAddresses.ProtoReflect.Descriptor instead. func (*PeeringServerAddresses) Descriptor() ([]byte, []int) { - return file_proto_pbpeering_peering_proto_rawDescGZIP(), []int{4} + return file_proto_pbpeering_peering_proto_rawDescGZIP(), []int{5} } func (x *PeeringServerAddresses) GetAddresses() []string { @@ -630,7 +708,7 @@ type PeeringReadRequest struct { func (x *PeeringReadRequest) Reset() { *x = PeeringReadRequest{} if protoimpl.UnsafeEnabled { - mi := &file_proto_pbpeering_peering_proto_msgTypes[5] + mi := &file_proto_pbpeering_peering_proto_msgTypes[6] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -643,7 +721,7 @@ func (x *PeeringReadRequest) String() string { func (*PeeringReadRequest) ProtoMessage() {} func (x *PeeringReadRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_pbpeering_peering_proto_msgTypes[5] + mi := &file_proto_pbpeering_peering_proto_msgTypes[6] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -656,7 +734,7 @@ func (x *PeeringReadRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use PeeringReadRequest.ProtoReflect.Descriptor instead. func (*PeeringReadRequest) Descriptor() ([]byte, []int) { - return file_proto_pbpeering_peering_proto_rawDescGZIP(), []int{5} + return file_proto_pbpeering_peering_proto_rawDescGZIP(), []int{6} } func (x *PeeringReadRequest) GetName() string { @@ -684,7 +762,7 @@ type PeeringReadResponse struct { func (x *PeeringReadResponse) Reset() { *x = PeeringReadResponse{} if protoimpl.UnsafeEnabled { - mi := &file_proto_pbpeering_peering_proto_msgTypes[6] + mi := &file_proto_pbpeering_peering_proto_msgTypes[7] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -697,7 +775,7 @@ func (x *PeeringReadResponse) String() string { func (*PeeringReadResponse) ProtoMessage() {} func (x *PeeringReadResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_pbpeering_peering_proto_msgTypes[6] + mi := &file_proto_pbpeering_peering_proto_msgTypes[7] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -710,7 +788,7 @@ func (x *PeeringReadResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use PeeringReadResponse.ProtoReflect.Descriptor instead. func (*PeeringReadResponse) Descriptor() ([]byte, []int) { - return file_proto_pbpeering_peering_proto_rawDescGZIP(), []int{6} + return file_proto_pbpeering_peering_proto_rawDescGZIP(), []int{7} } func (x *PeeringReadResponse) GetPeering() *Peering { @@ -732,7 +810,7 @@ type PeeringListRequest struct { func (x *PeeringListRequest) Reset() { *x = PeeringListRequest{} if protoimpl.UnsafeEnabled { - mi := &file_proto_pbpeering_peering_proto_msgTypes[7] + mi := &file_proto_pbpeering_peering_proto_msgTypes[8] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -745,7 +823,7 @@ func (x *PeeringListRequest) String() string { func (*PeeringListRequest) ProtoMessage() {} func (x *PeeringListRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_pbpeering_peering_proto_msgTypes[7] + mi := &file_proto_pbpeering_peering_proto_msgTypes[8] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -758,7 +836,7 @@ func (x *PeeringListRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use PeeringListRequest.ProtoReflect.Descriptor instead. func (*PeeringListRequest) Descriptor() ([]byte, []int) { - return file_proto_pbpeering_peering_proto_rawDescGZIP(), []int{7} + return file_proto_pbpeering_peering_proto_rawDescGZIP(), []int{8} } func (x *PeeringListRequest) GetPartition() string { @@ -774,12 +852,13 @@ type PeeringListResponse struct { unknownFields protoimpl.UnknownFields Peerings []*Peering `protobuf:"bytes,1,rep,name=Peerings,proto3" json:"Peerings,omitempty"` + Index uint64 `protobuf:"varint,2,opt,name=Index,proto3" json:"Index,omitempty"` } func (x *PeeringListResponse) Reset() { *x = PeeringListResponse{} if protoimpl.UnsafeEnabled { - mi := &file_proto_pbpeering_peering_proto_msgTypes[8] + mi := &file_proto_pbpeering_peering_proto_msgTypes[9] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -792,7 +871,7 @@ func (x *PeeringListResponse) String() string { func (*PeeringListResponse) ProtoMessage() {} func (x *PeeringListResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_pbpeering_peering_proto_msgTypes[8] + mi := &file_proto_pbpeering_peering_proto_msgTypes[9] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -805,7 +884,7 @@ func (x *PeeringListResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use PeeringListResponse.ProtoReflect.Descriptor instead. func (*PeeringListResponse) Descriptor() ([]byte, []int) { - return file_proto_pbpeering_peering_proto_rawDescGZIP(), []int{8} + return file_proto_pbpeering_peering_proto_rawDescGZIP(), []int{9} } func (x *PeeringListResponse) GetPeerings() []*Peering { @@ -815,6 +894,13 @@ func (x *PeeringListResponse) GetPeerings() []*Peering { return nil } +func (x *PeeringListResponse) GetIndex() uint64 { + if x != nil { + return x.Index + } + return 0 +} + type PeeringWriteRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -832,7 +918,7 @@ type PeeringWriteRequest struct { func (x *PeeringWriteRequest) Reset() { *x = PeeringWriteRequest{} if protoimpl.UnsafeEnabled { - mi := &file_proto_pbpeering_peering_proto_msgTypes[9] + mi := &file_proto_pbpeering_peering_proto_msgTypes[10] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -845,7 +931,7 @@ func (x *PeeringWriteRequest) String() string { func (*PeeringWriteRequest) ProtoMessage() {} func (x *PeeringWriteRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_pbpeering_peering_proto_msgTypes[9] + mi := &file_proto_pbpeering_peering_proto_msgTypes[10] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -858,7 +944,7 @@ func (x *PeeringWriteRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use PeeringWriteRequest.ProtoReflect.Descriptor instead. func (*PeeringWriteRequest) Descriptor() ([]byte, []int) { - return file_proto_pbpeering_peering_proto_rawDescGZIP(), []int{9} + return file_proto_pbpeering_peering_proto_rawDescGZIP(), []int{10} } func (x *PeeringWriteRequest) GetPeering() *Peering { @@ -892,7 +978,7 @@ type PeeringWriteResponse struct { func (x *PeeringWriteResponse) Reset() { *x = PeeringWriteResponse{} if protoimpl.UnsafeEnabled { - mi := &file_proto_pbpeering_peering_proto_msgTypes[10] + mi := &file_proto_pbpeering_peering_proto_msgTypes[11] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -905,7 +991,7 @@ func (x *PeeringWriteResponse) String() string { func (*PeeringWriteResponse) ProtoMessage() {} func (x *PeeringWriteResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_pbpeering_peering_proto_msgTypes[10] + mi := &file_proto_pbpeering_peering_proto_msgTypes[11] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -918,7 +1004,7 @@ func (x *PeeringWriteResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use PeeringWriteResponse.ProtoReflect.Descriptor instead. func (*PeeringWriteResponse) Descriptor() ([]byte, []int) { - return file_proto_pbpeering_peering_proto_rawDescGZIP(), []int{10} + return file_proto_pbpeering_peering_proto_rawDescGZIP(), []int{11} } type PeeringDeleteRequest struct { @@ -933,7 +1019,7 @@ type PeeringDeleteRequest struct { func (x *PeeringDeleteRequest) Reset() { *x = PeeringDeleteRequest{} if protoimpl.UnsafeEnabled { - mi := &file_proto_pbpeering_peering_proto_msgTypes[11] + mi := &file_proto_pbpeering_peering_proto_msgTypes[12] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -946,7 +1032,7 @@ func (x *PeeringDeleteRequest) String() string { func (*PeeringDeleteRequest) ProtoMessage() {} func (x *PeeringDeleteRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_pbpeering_peering_proto_msgTypes[11] + mi := &file_proto_pbpeering_peering_proto_msgTypes[12] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -959,7 +1045,7 @@ func (x *PeeringDeleteRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use PeeringDeleteRequest.ProtoReflect.Descriptor instead. func (*PeeringDeleteRequest) Descriptor() ([]byte, []int) { - return file_proto_pbpeering_peering_proto_rawDescGZIP(), []int{11} + return file_proto_pbpeering_peering_proto_rawDescGZIP(), []int{12} } func (x *PeeringDeleteRequest) GetName() string { @@ -985,7 +1071,7 @@ type PeeringDeleteResponse struct { func (x *PeeringDeleteResponse) Reset() { *x = PeeringDeleteResponse{} if protoimpl.UnsafeEnabled { - mi := &file_proto_pbpeering_peering_proto_msgTypes[12] + mi := &file_proto_pbpeering_peering_proto_msgTypes[13] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -998,7 +1084,7 @@ func (x *PeeringDeleteResponse) String() string { func (*PeeringDeleteResponse) ProtoMessage() {} func (x *PeeringDeleteResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_pbpeering_peering_proto_msgTypes[12] + mi := &file_proto_pbpeering_peering_proto_msgTypes[13] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1011,7 +1097,7 @@ func (x *PeeringDeleteResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use PeeringDeleteResponse.ProtoReflect.Descriptor instead. func (*PeeringDeleteResponse) Descriptor() ([]byte, []int) { - return file_proto_pbpeering_peering_proto_rawDescGZIP(), []int{12} + return file_proto_pbpeering_peering_proto_rawDescGZIP(), []int{13} } type TrustBundleListByServiceRequest struct { @@ -1028,7 +1114,7 @@ type TrustBundleListByServiceRequest struct { func (x *TrustBundleListByServiceRequest) Reset() { *x = TrustBundleListByServiceRequest{} if protoimpl.UnsafeEnabled { - mi := &file_proto_pbpeering_peering_proto_msgTypes[13] + mi := &file_proto_pbpeering_peering_proto_msgTypes[14] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1041,7 +1127,7 @@ func (x *TrustBundleListByServiceRequest) String() string { func (*TrustBundleListByServiceRequest) ProtoMessage() {} func (x *TrustBundleListByServiceRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_pbpeering_peering_proto_msgTypes[13] + mi := &file_proto_pbpeering_peering_proto_msgTypes[14] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1054,7 +1140,7 @@ func (x *TrustBundleListByServiceRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use TrustBundleListByServiceRequest.ProtoReflect.Descriptor instead. func (*TrustBundleListByServiceRequest) Descriptor() ([]byte, []int) { - return file_proto_pbpeering_peering_proto_rawDescGZIP(), []int{13} + return file_proto_pbpeering_peering_proto_rawDescGZIP(), []int{14} } func (x *TrustBundleListByServiceRequest) GetServiceName() string { @@ -1097,7 +1183,7 @@ type TrustBundleListByServiceResponse struct { func (x *TrustBundleListByServiceResponse) Reset() { *x = TrustBundleListByServiceResponse{} if protoimpl.UnsafeEnabled { - mi := &file_proto_pbpeering_peering_proto_msgTypes[14] + mi := &file_proto_pbpeering_peering_proto_msgTypes[15] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1110,7 +1196,7 @@ func (x *TrustBundleListByServiceResponse) String() string { func (*TrustBundleListByServiceResponse) ProtoMessage() {} func (x *TrustBundleListByServiceResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_pbpeering_peering_proto_msgTypes[14] + mi := &file_proto_pbpeering_peering_proto_msgTypes[15] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1123,7 +1209,7 @@ func (x *TrustBundleListByServiceResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use TrustBundleListByServiceResponse.ProtoReflect.Descriptor instead. func (*TrustBundleListByServiceResponse) Descriptor() ([]byte, []int) { - return file_proto_pbpeering_peering_proto_rawDescGZIP(), []int{14} + return file_proto_pbpeering_peering_proto_rawDescGZIP(), []int{15} } func (x *TrustBundleListByServiceResponse) GetIndex() uint64 { @@ -1152,7 +1238,7 @@ type TrustBundleReadRequest struct { func (x *TrustBundleReadRequest) Reset() { *x = TrustBundleReadRequest{} if protoimpl.UnsafeEnabled { - mi := &file_proto_pbpeering_peering_proto_msgTypes[15] + mi := &file_proto_pbpeering_peering_proto_msgTypes[16] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1165,7 +1251,7 @@ func (x *TrustBundleReadRequest) String() string { func (*TrustBundleReadRequest) ProtoMessage() {} func (x *TrustBundleReadRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_pbpeering_peering_proto_msgTypes[15] + mi := &file_proto_pbpeering_peering_proto_msgTypes[16] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1178,7 +1264,7 @@ func (x *TrustBundleReadRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use TrustBundleReadRequest.ProtoReflect.Descriptor instead. func (*TrustBundleReadRequest) Descriptor() ([]byte, []int) { - return file_proto_pbpeering_peering_proto_rawDescGZIP(), []int{15} + return file_proto_pbpeering_peering_proto_rawDescGZIP(), []int{16} } func (x *TrustBundleReadRequest) GetName() string { @@ -1207,7 +1293,7 @@ type TrustBundleReadResponse struct { func (x *TrustBundleReadResponse) Reset() { *x = TrustBundleReadResponse{} if protoimpl.UnsafeEnabled { - mi := &file_proto_pbpeering_peering_proto_msgTypes[16] + mi := &file_proto_pbpeering_peering_proto_msgTypes[17] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1220,7 +1306,7 @@ func (x *TrustBundleReadResponse) String() string { func (*TrustBundleReadResponse) ProtoMessage() {} func (x *TrustBundleReadResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_pbpeering_peering_proto_msgTypes[16] + mi := &file_proto_pbpeering_peering_proto_msgTypes[17] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1233,7 +1319,7 @@ func (x *TrustBundleReadResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use TrustBundleReadResponse.ProtoReflect.Descriptor instead. func (*TrustBundleReadResponse) Descriptor() ([]byte, []int) { - return file_proto_pbpeering_peering_proto_rawDescGZIP(), []int{16} + return file_proto_pbpeering_peering_proto_rawDescGZIP(), []int{17} } func (x *TrustBundleReadResponse) GetIndex() uint64 { @@ -1262,7 +1348,7 @@ type PeeringTerminateByIDRequest struct { func (x *PeeringTerminateByIDRequest) Reset() { *x = PeeringTerminateByIDRequest{} if protoimpl.UnsafeEnabled { - mi := &file_proto_pbpeering_peering_proto_msgTypes[17] + mi := &file_proto_pbpeering_peering_proto_msgTypes[18] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1275,7 +1361,7 @@ func (x *PeeringTerminateByIDRequest) String() string { func (*PeeringTerminateByIDRequest) ProtoMessage() {} func (x *PeeringTerminateByIDRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_pbpeering_peering_proto_msgTypes[17] + mi := &file_proto_pbpeering_peering_proto_msgTypes[18] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1288,7 +1374,7 @@ func (x *PeeringTerminateByIDRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use PeeringTerminateByIDRequest.ProtoReflect.Descriptor instead. func (*PeeringTerminateByIDRequest) Descriptor() ([]byte, []int) { - return file_proto_pbpeering_peering_proto_rawDescGZIP(), []int{17} + return file_proto_pbpeering_peering_proto_rawDescGZIP(), []int{18} } func (x *PeeringTerminateByIDRequest) GetID() string { @@ -1307,7 +1393,7 @@ type PeeringTerminateByIDResponse struct { func (x *PeeringTerminateByIDResponse) Reset() { *x = PeeringTerminateByIDResponse{} if protoimpl.UnsafeEnabled { - mi := &file_proto_pbpeering_peering_proto_msgTypes[18] + mi := &file_proto_pbpeering_peering_proto_msgTypes[19] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1320,7 +1406,7 @@ func (x *PeeringTerminateByIDResponse) String() string { func (*PeeringTerminateByIDResponse) ProtoMessage() {} func (x *PeeringTerminateByIDResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_pbpeering_peering_proto_msgTypes[18] + mi := &file_proto_pbpeering_peering_proto_msgTypes[19] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1333,7 +1419,7 @@ func (x *PeeringTerminateByIDResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use PeeringTerminateByIDResponse.ProtoReflect.Descriptor instead. func (*PeeringTerminateByIDResponse) Descriptor() ([]byte, []int) { - return file_proto_pbpeering_peering_proto_rawDescGZIP(), []int{18} + return file_proto_pbpeering_peering_proto_rawDescGZIP(), []int{19} } type PeeringTrustBundleWriteRequest struct { @@ -1347,7 +1433,7 @@ type PeeringTrustBundleWriteRequest struct { func (x *PeeringTrustBundleWriteRequest) Reset() { *x = PeeringTrustBundleWriteRequest{} if protoimpl.UnsafeEnabled { - mi := &file_proto_pbpeering_peering_proto_msgTypes[19] + mi := &file_proto_pbpeering_peering_proto_msgTypes[20] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1360,7 +1446,7 @@ func (x *PeeringTrustBundleWriteRequest) String() string { func (*PeeringTrustBundleWriteRequest) ProtoMessage() {} func (x *PeeringTrustBundleWriteRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_pbpeering_peering_proto_msgTypes[19] + mi := &file_proto_pbpeering_peering_proto_msgTypes[20] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1373,7 +1459,7 @@ func (x *PeeringTrustBundleWriteRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use PeeringTrustBundleWriteRequest.ProtoReflect.Descriptor instead. func (*PeeringTrustBundleWriteRequest) Descriptor() ([]byte, []int) { - return file_proto_pbpeering_peering_proto_rawDescGZIP(), []int{19} + return file_proto_pbpeering_peering_proto_rawDescGZIP(), []int{20} } func (x *PeeringTrustBundleWriteRequest) GetPeeringTrustBundle() *PeeringTrustBundle { @@ -1392,7 +1478,7 @@ type PeeringTrustBundleWriteResponse struct { func (x *PeeringTrustBundleWriteResponse) Reset() { *x = PeeringTrustBundleWriteResponse{} if protoimpl.UnsafeEnabled { - mi := &file_proto_pbpeering_peering_proto_msgTypes[20] + mi := &file_proto_pbpeering_peering_proto_msgTypes[21] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1405,7 +1491,7 @@ func (x *PeeringTrustBundleWriteResponse) String() string { func (*PeeringTrustBundleWriteResponse) ProtoMessage() {} func (x *PeeringTrustBundleWriteResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_pbpeering_peering_proto_msgTypes[20] + mi := &file_proto_pbpeering_peering_proto_msgTypes[21] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1418,7 +1504,7 @@ func (x *PeeringTrustBundleWriteResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use PeeringTrustBundleWriteResponse.ProtoReflect.Descriptor instead. func (*PeeringTrustBundleWriteResponse) Descriptor() ([]byte, []int) { - return file_proto_pbpeering_peering_proto_rawDescGZIP(), []int{20} + return file_proto_pbpeering_peering_proto_rawDescGZIP(), []int{21} } type PeeringTrustBundleDeleteRequest struct { @@ -1433,7 +1519,7 @@ type PeeringTrustBundleDeleteRequest struct { func (x *PeeringTrustBundleDeleteRequest) Reset() { *x = PeeringTrustBundleDeleteRequest{} if protoimpl.UnsafeEnabled { - mi := &file_proto_pbpeering_peering_proto_msgTypes[21] + mi := &file_proto_pbpeering_peering_proto_msgTypes[22] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1446,7 +1532,7 @@ func (x *PeeringTrustBundleDeleteRequest) String() string { func (*PeeringTrustBundleDeleteRequest) ProtoMessage() {} func (x *PeeringTrustBundleDeleteRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_pbpeering_peering_proto_msgTypes[21] + mi := &file_proto_pbpeering_peering_proto_msgTypes[22] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1459,7 +1545,7 @@ func (x *PeeringTrustBundleDeleteRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use PeeringTrustBundleDeleteRequest.ProtoReflect.Descriptor instead. func (*PeeringTrustBundleDeleteRequest) Descriptor() ([]byte, []int) { - return file_proto_pbpeering_peering_proto_rawDescGZIP(), []int{21} + return file_proto_pbpeering_peering_proto_rawDescGZIP(), []int{22} } func (x *PeeringTrustBundleDeleteRequest) GetName() string { @@ -1485,7 +1571,7 @@ type PeeringTrustBundleDeleteResponse struct { func (x *PeeringTrustBundleDeleteResponse) Reset() { *x = PeeringTrustBundleDeleteResponse{} if protoimpl.UnsafeEnabled { - mi := &file_proto_pbpeering_peering_proto_msgTypes[22] + mi := &file_proto_pbpeering_peering_proto_msgTypes[23] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1498,7 +1584,7 @@ func (x *PeeringTrustBundleDeleteResponse) String() string { func (*PeeringTrustBundleDeleteResponse) ProtoMessage() {} func (x *PeeringTrustBundleDeleteResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_pbpeering_peering_proto_msgTypes[22] + mi := &file_proto_pbpeering_peering_proto_msgTypes[23] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1511,7 +1597,7 @@ func (x *PeeringTrustBundleDeleteResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use PeeringTrustBundleDeleteResponse.ProtoReflect.Descriptor instead. func (*PeeringTrustBundleDeleteResponse) Descriptor() ([]byte, []int) { - return file_proto_pbpeering_peering_proto_rawDescGZIP(), []int{22} + return file_proto_pbpeering_peering_proto_rawDescGZIP(), []int{23} } // mog annotation: @@ -1539,7 +1625,7 @@ type GenerateTokenRequest struct { func (x *GenerateTokenRequest) Reset() { *x = GenerateTokenRequest{} if protoimpl.UnsafeEnabled { - mi := &file_proto_pbpeering_peering_proto_msgTypes[23] + mi := &file_proto_pbpeering_peering_proto_msgTypes[24] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1552,7 +1638,7 @@ func (x *GenerateTokenRequest) String() string { func (*GenerateTokenRequest) ProtoMessage() {} func (x *GenerateTokenRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_pbpeering_peering_proto_msgTypes[23] + mi := &file_proto_pbpeering_peering_proto_msgTypes[24] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1565,7 +1651,7 @@ func (x *GenerateTokenRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GenerateTokenRequest.ProtoReflect.Descriptor instead. func (*GenerateTokenRequest) Descriptor() ([]byte, []int) { - return file_proto_pbpeering_peering_proto_rawDescGZIP(), []int{23} + return file_proto_pbpeering_peering_proto_rawDescGZIP(), []int{24} } func (x *GenerateTokenRequest) GetPeerName() string { @@ -1614,7 +1700,7 @@ type GenerateTokenResponse struct { func (x *GenerateTokenResponse) Reset() { *x = GenerateTokenResponse{} if protoimpl.UnsafeEnabled { - mi := &file_proto_pbpeering_peering_proto_msgTypes[24] + mi := &file_proto_pbpeering_peering_proto_msgTypes[25] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1627,7 +1713,7 @@ func (x *GenerateTokenResponse) String() string { func (*GenerateTokenResponse) ProtoMessage() {} func (x *GenerateTokenResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_pbpeering_peering_proto_msgTypes[24] + mi := &file_proto_pbpeering_peering_proto_msgTypes[25] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1640,7 +1726,7 @@ func (x *GenerateTokenResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GenerateTokenResponse.ProtoReflect.Descriptor instead. func (*GenerateTokenResponse) Descriptor() ([]byte, []int) { - return file_proto_pbpeering_peering_proto_rawDescGZIP(), []int{24} + return file_proto_pbpeering_peering_proto_rawDescGZIP(), []int{25} } func (x *GenerateTokenResponse) GetPeeringToken() string { @@ -1673,7 +1759,7 @@ type EstablishRequest struct { func (x *EstablishRequest) Reset() { *x = EstablishRequest{} if protoimpl.UnsafeEnabled { - mi := &file_proto_pbpeering_peering_proto_msgTypes[25] + mi := &file_proto_pbpeering_peering_proto_msgTypes[26] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1686,7 +1772,7 @@ func (x *EstablishRequest) String() string { func (*EstablishRequest) ProtoMessage() {} func (x *EstablishRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_pbpeering_peering_proto_msgTypes[25] + mi := &file_proto_pbpeering_peering_proto_msgTypes[26] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1699,7 +1785,7 @@ func (x *EstablishRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use EstablishRequest.ProtoReflect.Descriptor instead. func (*EstablishRequest) Descriptor() ([]byte, []int) { - return file_proto_pbpeering_peering_proto_rawDescGZIP(), []int{25} + return file_proto_pbpeering_peering_proto_rawDescGZIP(), []int{26} } func (x *EstablishRequest) GetPeerName() string { @@ -1744,7 +1830,7 @@ type EstablishResponse struct { func (x *EstablishResponse) Reset() { *x = EstablishResponse{} if protoimpl.UnsafeEnabled { - mi := &file_proto_pbpeering_peering_proto_msgTypes[26] + mi := &file_proto_pbpeering_peering_proto_msgTypes[27] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1757,7 +1843,7 @@ func (x *EstablishResponse) String() string { func (*EstablishResponse) ProtoMessage() {} func (x *EstablishResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_pbpeering_peering_proto_msgTypes[26] + mi := &file_proto_pbpeering_peering_proto_msgTypes[27] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1770,7 +1856,7 @@ func (x *EstablishResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use EstablishResponse.ProtoReflect.Descriptor instead. func (*EstablishResponse) Descriptor() ([]byte, []int) { - return file_proto_pbpeering_peering_proto_rawDescGZIP(), []int{26} + return file_proto_pbpeering_peering_proto_rawDescGZIP(), []int{27} } // GenerateTokenRequest encodes a request to persist a peering establishment @@ -1788,7 +1874,7 @@ type SecretsWriteRequest_GenerateTokenRequest struct { func (x *SecretsWriteRequest_GenerateTokenRequest) Reset() { *x = SecretsWriteRequest_GenerateTokenRequest{} if protoimpl.UnsafeEnabled { - mi := &file_proto_pbpeering_peering_proto_msgTypes[27] + mi := &file_proto_pbpeering_peering_proto_msgTypes[28] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1801,7 +1887,7 @@ func (x *SecretsWriteRequest_GenerateTokenRequest) String() string { func (*SecretsWriteRequest_GenerateTokenRequest) ProtoMessage() {} func (x *SecretsWriteRequest_GenerateTokenRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_pbpeering_peering_proto_msgTypes[27] + mi := &file_proto_pbpeering_peering_proto_msgTypes[28] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1842,7 +1928,7 @@ type SecretsWriteRequest_ExchangeSecretRequest struct { func (x *SecretsWriteRequest_ExchangeSecretRequest) Reset() { *x = SecretsWriteRequest_ExchangeSecretRequest{} if protoimpl.UnsafeEnabled { - mi := &file_proto_pbpeering_peering_proto_msgTypes[28] + mi := &file_proto_pbpeering_peering_proto_msgTypes[29] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1855,7 +1941,7 @@ func (x *SecretsWriteRequest_ExchangeSecretRequest) String() string { func (*SecretsWriteRequest_ExchangeSecretRequest) ProtoMessage() {} func (x *SecretsWriteRequest_ExchangeSecretRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_pbpeering_peering_proto_msgTypes[28] + mi := &file_proto_pbpeering_peering_proto_msgTypes[29] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1901,7 +1987,7 @@ type SecretsWriteRequest_PromotePendingRequest struct { func (x *SecretsWriteRequest_PromotePendingRequest) Reset() { *x = SecretsWriteRequest_PromotePendingRequest{} if protoimpl.UnsafeEnabled { - mi := &file_proto_pbpeering_peering_proto_msgTypes[29] + mi := &file_proto_pbpeering_peering_proto_msgTypes[30] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1914,7 +2000,7 @@ func (x *SecretsWriteRequest_PromotePendingRequest) String() string { func (*SecretsWriteRequest_PromotePendingRequest) ProtoMessage() {} func (x *SecretsWriteRequest_PromotePendingRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_pbpeering_peering_proto_msgTypes[29] + mi := &file_proto_pbpeering_peering_proto_msgTypes[30] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1953,7 +2039,7 @@ type SecretsWriteRequest_EstablishRequest struct { func (x *SecretsWriteRequest_EstablishRequest) Reset() { *x = SecretsWriteRequest_EstablishRequest{} if protoimpl.UnsafeEnabled { - mi := &file_proto_pbpeering_peering_proto_msgTypes[30] + mi := &file_proto_pbpeering_peering_proto_msgTypes[31] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1966,7 +2052,7 @@ func (x *SecretsWriteRequest_EstablishRequest) String() string { func (*SecretsWriteRequest_EstablishRequest) ProtoMessage() {} func (x *SecretsWriteRequest_EstablishRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_pbpeering_peering_proto_msgTypes[30] + mi := &file_proto_pbpeering_peering_proto_msgTypes[31] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2001,7 +2087,7 @@ type PeeringSecrets_Establishment struct { func (x *PeeringSecrets_Establishment) Reset() { *x = PeeringSecrets_Establishment{} if protoimpl.UnsafeEnabled { - mi := &file_proto_pbpeering_peering_proto_msgTypes[31] + mi := &file_proto_pbpeering_peering_proto_msgTypes[32] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2014,7 +2100,7 @@ func (x *PeeringSecrets_Establishment) String() string { func (*PeeringSecrets_Establishment) ProtoMessage() {} func (x *PeeringSecrets_Establishment) ProtoReflect() protoreflect.Message { - mi := &file_proto_pbpeering_peering_proto_msgTypes[31] + mi := &file_proto_pbpeering_peering_proto_msgTypes[32] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2060,7 +2146,7 @@ type PeeringSecrets_Stream struct { func (x *PeeringSecrets_Stream) Reset() { *x = PeeringSecrets_Stream{} if protoimpl.UnsafeEnabled { - mi := &file_proto_pbpeering_peering_proto_msgTypes[32] + mi := &file_proto_pbpeering_peering_proto_msgTypes[33] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2073,7 +2159,7 @@ func (x *PeeringSecrets_Stream) String() string { func (*PeeringSecrets_Stream) ProtoMessage() {} func (x *PeeringSecrets_Stream) ProtoReflect() protoreflect.Message { - mi := &file_proto_pbpeering_peering_proto_msgTypes[32] + mi := &file_proto_pbpeering_peering_proto_msgTypes[33] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2189,7 +2275,7 @@ var file_proto_pbpeering_peering_proto_rawDesc = []byte{ 0x41, 0x63, 0x74, 0x69, 0x76, 0x65, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x49, 0x44, 0x12, 0x28, 0x0a, 0x0f, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x49, 0x44, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, - 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x49, 0x44, 0x22, 0x8d, 0x05, 0x0a, 0x07, 0x50, 0x65, 0x65, + 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x49, 0x44, 0x22, 0xfa, 0x04, 0x0a, 0x07, 0x50, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x12, 0x0e, 0x0a, 0x02, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x49, 0x44, 0x12, 0x12, 0x0a, 0x04, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x50, 0x61, 0x72, 0x74, @@ -2216,274 +2302,293 @@ var file_proto_pbpeering_peering_proto_rawDesc = []byte{ 0x65, 0x12, 0x30, 0x0a, 0x13, 0x50, 0x65, 0x65, 0x72, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x18, 0x0a, 0x20, 0x03, 0x28, 0x09, 0x52, 0x13, 0x50, 0x65, 0x65, 0x72, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, - 0x73, 0x65, 0x73, 0x12, 0x32, 0x0a, 0x14, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x53, - 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x0d, 0x20, 0x01, 0x28, - 0x04, 0x52, 0x14, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x53, 0x65, 0x72, 0x76, 0x69, - 0x63, 0x65, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x32, 0x0a, 0x14, 0x45, 0x78, 0x70, 0x6f, 0x72, - 0x74, 0x65, 0x64, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x18, - 0x0e, 0x20, 0x01, 0x28, 0x04, 0x52, 0x14, 0x45, 0x78, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x53, - 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x20, 0x0a, 0x0b, 0x43, - 0x72, 0x65, 0x61, 0x74, 0x65, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x04, - 0x52, 0x0b, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x20, 0x0a, - 0x0b, 0x4d, 0x6f, 0x64, 0x69, 0x66, 0x79, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x0c, 0x20, 0x01, - 0x28, 0x04, 0x52, 0x0b, 0x4d, 0x6f, 0x64, 0x69, 0x66, 0x79, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x1a, - 0x37, 0x0a, 0x09, 0x4d, 0x65, 0x74, 0x61, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, - 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, - 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, - 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0xfe, 0x01, 0x0a, 0x12, 0x50, 0x65, 0x65, - 0x72, 0x69, 0x6e, 0x67, 0x54, 0x72, 0x75, 0x73, 0x74, 0x42, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x12, - 0x20, 0x0a, 0x0b, 0x54, 0x72, 0x75, 0x73, 0x74, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x54, 0x72, 0x75, 0x73, 0x74, 0x44, 0x6f, 0x6d, 0x61, 0x69, - 0x6e, 0x12, 0x1a, 0x0a, 0x08, 0x50, 0x65, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x08, 0x50, 0x65, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1c, 0x0a, - 0x09, 0x50, 0x61, 0x72, 0x74, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x09, 0x50, 0x61, 0x72, 0x74, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1a, 0x0a, 0x08, 0x52, - 0x6f, 0x6f, 0x74, 0x50, 0x45, 0x4d, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x09, 0x52, 0x08, 0x52, - 0x6f, 0x6f, 0x74, 0x50, 0x45, 0x4d, 0x73, 0x12, 0x2c, 0x0a, 0x11, 0x45, 0x78, 0x70, 0x6f, 0x72, - 0x74, 0x65, 0x64, 0x50, 0x61, 0x72, 0x74, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x05, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x11, 0x45, 0x78, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x50, 0x61, 0x72, 0x74, - 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x20, 0x0a, 0x0b, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x49, - 0x6e, 0x64, 0x65, 0x78, 0x18, 0x06, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x43, 0x72, 0x65, 0x61, - 0x74, 0x65, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x20, 0x0a, 0x0b, 0x4d, 0x6f, 0x64, 0x69, 0x66, - 0x79, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x07, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x4d, 0x6f, - 0x64, 0x69, 0x66, 0x79, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x22, 0x36, 0x0a, 0x16, 0x50, 0x65, 0x65, - 0x72, 0x69, 0x6e, 0x67, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, - 0x73, 0x65, 0x73, 0x12, 0x1c, 0x0a, 0x09, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, - 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x09, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, - 0x73, 0x22, 0x46, 0x0a, 0x12, 0x50, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x61, 0x64, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x4e, 0x61, 0x6d, 0x65, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x50, - 0x61, 0x72, 0x74, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, - 0x50, 0x61, 0x72, 0x74, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x5b, 0x0a, 0x13, 0x50, 0x65, 0x65, - 0x72, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x61, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x12, 0x44, 0x0a, 0x07, 0x50, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x73, 0x65, 0x73, 0x12, 0x53, 0x0a, 0x0c, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x53, 0x74, 0x61, + 0x74, 0x75, 0x73, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2f, 0x2e, 0x68, 0x61, 0x73, 0x68, + 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, + 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x70, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x2e, 0x53, 0x74, + 0x72, 0x65, 0x61, 0x6d, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x0c, 0x53, 0x74, 0x72, 0x65, + 0x61, 0x6d, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x20, 0x0a, 0x0b, 0x43, 0x72, 0x65, 0x61, + 0x74, 0x65, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x43, + 0x72, 0x65, 0x61, 0x74, 0x65, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x20, 0x0a, 0x0b, 0x4d, 0x6f, + 0x64, 0x69, 0x66, 0x79, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x04, 0x52, + 0x0b, 0x4d, 0x6f, 0x64, 0x69, 0x66, 0x79, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x1a, 0x37, 0x0a, 0x09, + 0x4d, 0x65, 0x74, 0x61, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, + 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, + 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x9e, 0x02, 0x0a, 0x0c, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, + 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x2a, 0x0a, 0x10, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, + 0x65, 0x64, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, + 0x52, 0x10, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, + 0x65, 0x73, 0x12, 0x2a, 0x0a, 0x10, 0x45, 0x78, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x53, 0x65, + 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x10, 0x45, 0x78, + 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x12, 0x40, + 0x0a, 0x0d, 0x4c, 0x61, 0x73, 0x74, 0x48, 0x65, 0x61, 0x72, 0x74, 0x62, 0x65, 0x61, 0x74, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, + 0x70, 0x52, 0x0d, 0x4c, 0x61, 0x73, 0x74, 0x48, 0x65, 0x61, 0x72, 0x74, 0x62, 0x65, 0x61, 0x74, + 0x12, 0x3c, 0x0a, 0x0b, 0x4c, 0x61, 0x73, 0x74, 0x52, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x18, + 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, + 0x70, 0x52, 0x0b, 0x4c, 0x61, 0x73, 0x74, 0x52, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x12, 0x36, + 0x0a, 0x08, 0x4c, 0x61, 0x73, 0x74, 0x53, 0x65, 0x6e, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, + 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x08, 0x4c, 0x61, + 0x73, 0x74, 0x53, 0x65, 0x6e, 0x64, 0x22, 0xfe, 0x01, 0x0a, 0x12, 0x50, 0x65, 0x65, 0x72, 0x69, + 0x6e, 0x67, 0x54, 0x72, 0x75, 0x73, 0x74, 0x42, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x12, 0x20, 0x0a, + 0x0b, 0x54, 0x72, 0x75, 0x73, 0x74, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x0b, 0x54, 0x72, 0x75, 0x73, 0x74, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, + 0x1a, 0x0a, 0x08, 0x50, 0x65, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x08, 0x50, 0x65, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x50, + 0x61, 0x72, 0x74, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, + 0x50, 0x61, 0x72, 0x74, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1a, 0x0a, 0x08, 0x52, 0x6f, 0x6f, + 0x74, 0x50, 0x45, 0x4d, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x09, 0x52, 0x08, 0x52, 0x6f, 0x6f, + 0x74, 0x50, 0x45, 0x4d, 0x73, 0x12, 0x2c, 0x0a, 0x11, 0x45, 0x78, 0x70, 0x6f, 0x72, 0x74, 0x65, + 0x64, 0x50, 0x61, 0x72, 0x74, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x11, 0x45, 0x78, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x50, 0x61, 0x72, 0x74, 0x69, 0x74, + 0x69, 0x6f, 0x6e, 0x12, 0x20, 0x0a, 0x0b, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x49, 0x6e, 0x64, + 0x65, 0x78, 0x18, 0x06, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, + 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x20, 0x0a, 0x0b, 0x4d, 0x6f, 0x64, 0x69, 0x66, 0x79, 0x49, + 0x6e, 0x64, 0x65, 0x78, 0x18, 0x07, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x4d, 0x6f, 0x64, 0x69, + 0x66, 0x79, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x22, 0x36, 0x0a, 0x16, 0x50, 0x65, 0x65, 0x72, 0x69, + 0x6e, 0x67, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, + 0x73, 0x12, 0x1c, 0x0a, 0x09, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x18, 0x01, + 0x20, 0x03, 0x28, 0x09, 0x52, 0x09, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x22, + 0x46, 0x0a, 0x12, 0x50, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x61, 0x64, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x04, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x50, 0x61, 0x72, + 0x74, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x50, 0x61, + 0x72, 0x74, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x5b, 0x0a, 0x13, 0x50, 0x65, 0x65, 0x72, 0x69, + 0x6e, 0x67, 0x52, 0x65, 0x61, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x44, + 0x0a, 0x07, 0x50, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x2a, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, + 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x70, 0x65, 0x65, 0x72, + 0x69, 0x6e, 0x67, 0x2e, 0x50, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x52, 0x07, 0x50, 0x65, 0x65, + 0x72, 0x69, 0x6e, 0x67, 0x22, 0x32, 0x0a, 0x12, 0x50, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x4c, + 0x69, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1c, 0x0a, 0x09, 0x50, 0x61, + 0x72, 0x74, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x50, + 0x61, 0x72, 0x74, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x73, 0x0a, 0x13, 0x50, 0x65, 0x65, 0x72, + 0x69, 0x6e, 0x67, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x46, 0x0a, 0x08, 0x50, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x70, 0x65, - 0x65, 0x72, 0x69, 0x6e, 0x67, 0x2e, 0x50, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x52, 0x07, 0x50, - 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x22, 0x32, 0x0a, 0x12, 0x50, 0x65, 0x65, 0x72, 0x69, 0x6e, - 0x67, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1c, 0x0a, 0x09, - 0x50, 0x61, 0x72, 0x74, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x09, 0x50, 0x61, 0x72, 0x74, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x5d, 0x0a, 0x13, 0x50, 0x65, - 0x65, 0x72, 0x69, 0x6e, 0x67, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x12, 0x46, 0x0a, 0x08, 0x50, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x73, 0x18, 0x01, 0x20, - 0x03, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, + 0x65, 0x72, 0x69, 0x6e, 0x67, 0x2e, 0x50, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x52, 0x08, 0x50, + 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x49, 0x6e, 0x64, 0x65, 0x78, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x05, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x22, 0xca, 0x02, + 0x0a, 0x13, 0x50, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x57, 0x72, 0x69, 0x74, 0x65, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x44, 0x0a, 0x07, 0x50, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, + 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, + 0x61, 0x6c, 0x2e, 0x70, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x2e, 0x50, 0x65, 0x65, 0x72, 0x69, + 0x6e, 0x67, 0x52, 0x07, 0x50, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x12, 0x5e, 0x0a, 0x0e, 0x53, + 0x65, 0x63, 0x72, 0x65, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x36, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, - 0x70, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x2e, 0x50, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x52, - 0x08, 0x50, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x73, 0x22, 0xca, 0x02, 0x0a, 0x13, 0x50, 0x65, + 0x70, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x2e, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x73, 0x57, + 0x72, 0x69, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x52, 0x0e, 0x53, 0x65, 0x63, + 0x72, 0x65, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x54, 0x0a, 0x04, 0x4d, + 0x65, 0x74, 0x61, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x40, 0x2e, 0x68, 0x61, 0x73, 0x68, + 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, + 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x70, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x2e, 0x50, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x57, 0x72, 0x69, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x12, 0x44, 0x0a, 0x07, 0x50, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, - 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x70, - 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x2e, 0x50, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x52, 0x07, - 0x50, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x12, 0x5e, 0x0a, 0x0e, 0x53, 0x65, 0x63, 0x72, 0x65, - 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x36, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, - 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x70, 0x65, 0x65, 0x72, - 0x69, 0x6e, 0x67, 0x2e, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x73, 0x57, 0x72, 0x69, 0x74, 0x65, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x52, 0x0e, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x73, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x54, 0x0a, 0x04, 0x4d, 0x65, 0x74, 0x61, 0x18, - 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x40, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, - 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, - 0x6c, 0x2e, 0x70, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x2e, 0x50, 0x65, 0x65, 0x72, 0x69, 0x6e, - 0x67, 0x57, 0x72, 0x69, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x4d, 0x65, - 0x74, 0x61, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x04, 0x4d, 0x65, 0x74, 0x61, 0x1a, 0x37, 0x0a, - 0x09, 0x4d, 0x65, 0x74, 0x61, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, - 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, - 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, - 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x16, 0x0a, 0x14, 0x50, 0x65, 0x65, 0x72, 0x69, 0x6e, - 0x67, 0x57, 0x72, 0x69, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x48, - 0x0a, 0x14, 0x50, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x50, 0x61, - 0x72, 0x74, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x50, - 0x61, 0x72, 0x74, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x17, 0x0a, 0x15, 0x50, 0x65, 0x65, 0x72, - 0x69, 0x6e, 0x67, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x22, 0x93, 0x01, 0x0a, 0x1f, 0x54, 0x72, 0x75, 0x73, 0x74, 0x42, 0x75, 0x6e, 0x64, 0x6c, - 0x65, 0x4c, 0x69, 0x73, 0x74, 0x42, 0x79, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x20, 0x0a, 0x0b, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, - 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x53, 0x65, 0x72, 0x76, - 0x69, 0x63, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x4e, 0x61, 0x6d, 0x65, 0x73, - 0x70, 0x61, 0x63, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x4e, 0x61, 0x6d, 0x65, - 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x50, 0x61, 0x72, 0x74, 0x69, 0x74, 0x69, - 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x50, 0x61, 0x72, 0x74, 0x69, 0x74, - 0x69, 0x6f, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x4b, 0x69, 0x6e, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x04, 0x4b, 0x69, 0x6e, 0x64, 0x22, 0x89, 0x01, 0x0a, 0x20, 0x54, 0x72, 0x75, 0x73, - 0x74, 0x42, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x4c, 0x69, 0x73, 0x74, 0x42, 0x79, 0x53, 0x65, 0x72, - 0x76, 0x69, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x14, 0x0a, 0x05, - 0x49, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x05, 0x49, 0x6e, 0x64, - 0x65, 0x78, 0x12, 0x4f, 0x0a, 0x07, 0x42, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x73, 0x18, 0x02, 0x20, - 0x03, 0x28, 0x0b, 0x32, 0x35, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, + 0x74, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x04, 0x4d, 0x65, 0x74, + 0x61, 0x1a, 0x37, 0x0a, 0x09, 0x4d, 0x65, 0x74, 0x61, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, + 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, + 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x16, 0x0a, 0x14, 0x50, 0x65, + 0x65, 0x72, 0x69, 0x6e, 0x67, 0x57, 0x72, 0x69, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x22, 0x48, 0x0a, 0x14, 0x50, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x44, 0x65, 0x6c, + 0x65, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x4e, 0x61, + 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1c, + 0x0a, 0x09, 0x50, 0x61, 0x72, 0x74, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x09, 0x50, 0x61, 0x72, 0x74, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x17, 0x0a, 0x15, + 0x50, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x93, 0x01, 0x0a, 0x1f, 0x54, 0x72, 0x75, 0x73, 0x74, 0x42, + 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x4c, 0x69, 0x73, 0x74, 0x42, 0x79, 0x53, 0x65, 0x72, 0x76, 0x69, + 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x20, 0x0a, 0x0b, 0x53, 0x65, 0x72, + 0x76, 0x69, 0x63, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, + 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x4e, + 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, + 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x50, 0x61, 0x72, + 0x74, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x50, 0x61, + 0x72, 0x74, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x4b, 0x69, 0x6e, 0x64, 0x18, + 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x4b, 0x69, 0x6e, 0x64, 0x22, 0x89, 0x01, 0x0a, 0x20, + 0x54, 0x72, 0x75, 0x73, 0x74, 0x42, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x4c, 0x69, 0x73, 0x74, 0x42, + 0x79, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x14, 0x0a, 0x05, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, + 0x05, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x4f, 0x0a, 0x07, 0x42, 0x75, 0x6e, 0x64, 0x6c, 0x65, + 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x35, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, + 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, + 0x6e, 0x61, 0x6c, 0x2e, 0x70, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x2e, 0x50, 0x65, 0x65, 0x72, + 0x69, 0x6e, 0x67, 0x54, 0x72, 0x75, 0x73, 0x74, 0x42, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x52, 0x07, + 0x42, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x73, 0x22, 0x4a, 0x0a, 0x16, 0x54, 0x72, 0x75, 0x73, 0x74, + 0x42, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x52, 0x65, 0x61, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x12, 0x12, 0x0a, 0x04, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x04, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x50, 0x61, 0x72, 0x74, 0x69, 0x74, 0x69, + 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x50, 0x61, 0x72, 0x74, 0x69, 0x74, + 0x69, 0x6f, 0x6e, 0x22, 0x7e, 0x0a, 0x17, 0x54, 0x72, 0x75, 0x73, 0x74, 0x42, 0x75, 0x6e, 0x64, + 0x6c, 0x65, 0x52, 0x65, 0x61, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x14, + 0x0a, 0x05, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x05, 0x49, + 0x6e, 0x64, 0x65, 0x78, 0x12, 0x4d, 0x0a, 0x06, 0x42, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x35, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, + 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, + 0x2e, 0x70, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x2e, 0x50, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, + 0x54, 0x72, 0x75, 0x73, 0x74, 0x42, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x52, 0x06, 0x42, 0x75, 0x6e, + 0x64, 0x6c, 0x65, 0x22, 0x2d, 0x0a, 0x1b, 0x50, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x54, 0x65, + 0x72, 0x6d, 0x69, 0x6e, 0x61, 0x74, 0x65, 0x42, 0x79, 0x49, 0x44, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, + 0x49, 0x44, 0x22, 0x1e, 0x0a, 0x1c, 0x50, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x54, 0x65, 0x72, + 0x6d, 0x69, 0x6e, 0x61, 0x74, 0x65, 0x42, 0x79, 0x49, 0x44, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x22, 0x87, 0x01, 0x0a, 0x1e, 0x50, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x54, 0x72, + 0x75, 0x73, 0x74, 0x42, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x57, 0x72, 0x69, 0x74, 0x65, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x65, 0x0a, 0x12, 0x50, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, + 0x54, 0x72, 0x75, 0x73, 0x74, 0x42, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x35, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, + 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x70, 0x65, + 0x65, 0x72, 0x69, 0x6e, 0x67, 0x2e, 0x50, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x54, 0x72, 0x75, + 0x73, 0x74, 0x42, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x52, 0x12, 0x50, 0x65, 0x65, 0x72, 0x69, 0x6e, + 0x67, 0x54, 0x72, 0x75, 0x73, 0x74, 0x42, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x22, 0x21, 0x0a, 0x1f, + 0x50, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x54, 0x72, 0x75, 0x73, 0x74, 0x42, 0x75, 0x6e, 0x64, + 0x6c, 0x65, 0x57, 0x72, 0x69, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, + 0x53, 0x0a, 0x1f, 0x50, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x54, 0x72, 0x75, 0x73, 0x74, 0x42, + 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x04, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x50, 0x61, 0x72, 0x74, 0x69, 0x74, + 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x50, 0x61, 0x72, 0x74, 0x69, + 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x22, 0x0a, 0x20, 0x50, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x54, + 0x72, 0x75, 0x73, 0x74, 0x42, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x9a, 0x02, 0x0a, 0x14, 0x47, 0x65, 0x6e, + 0x65, 0x72, 0x61, 0x74, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x50, 0x65, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x08, 0x50, 0x65, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1c, 0x0a, + 0x09, 0x50, 0x61, 0x72, 0x74, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x09, 0x50, 0x61, 0x72, 0x74, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x55, 0x0a, 0x04, 0x4d, + 0x65, 0x74, 0x61, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x41, 0x2e, 0x68, 0x61, 0x73, 0x68, + 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, + 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x70, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x2e, 0x47, 0x65, + 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x04, 0x4d, 0x65, + 0x74, 0x61, 0x12, 0x38, 0x0a, 0x17, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x45, 0x78, 0x74, 0x65, + 0x72, 0x6e, 0x61, 0x6c, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x18, 0x06, 0x20, + 0x03, 0x28, 0x09, 0x52, 0x17, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x45, 0x78, 0x74, 0x65, 0x72, + 0x6e, 0x61, 0x6c, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x1a, 0x37, 0x0a, 0x09, + 0x4d, 0x65, 0x74, 0x61, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, + 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, + 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x3b, 0x0a, 0x15, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, + 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x22, + 0x0a, 0x0c, 0x50, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x50, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x54, 0x6f, 0x6b, + 0x65, 0x6e, 0x22, 0xfc, 0x01, 0x0a, 0x10, 0x45, 0x73, 0x74, 0x61, 0x62, 0x6c, 0x69, 0x73, 0x68, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x50, 0x65, 0x65, 0x72, 0x4e, + 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x50, 0x65, 0x65, 0x72, 0x4e, + 0x61, 0x6d, 0x65, 0x12, 0x22, 0x0a, 0x0c, 0x50, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x54, 0x6f, + 0x6b, 0x65, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x50, 0x65, 0x65, 0x72, 0x69, + 0x6e, 0x67, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x1c, 0x0a, 0x09, 0x50, 0x61, 0x72, 0x74, 0x69, + 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x50, 0x61, 0x72, 0x74, + 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x51, 0x0a, 0x04, 0x4d, 0x65, 0x74, 0x61, 0x18, 0x04, 0x20, + 0x03, 0x28, 0x0b, 0x32, 0x3d, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, - 0x70, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x2e, 0x50, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x54, - 0x72, 0x75, 0x73, 0x74, 0x42, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x52, 0x07, 0x42, 0x75, 0x6e, 0x64, - 0x6c, 0x65, 0x73, 0x22, 0x4a, 0x0a, 0x16, 0x54, 0x72, 0x75, 0x73, 0x74, 0x42, 0x75, 0x6e, 0x64, - 0x6c, 0x65, 0x52, 0x65, 0x61, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, - 0x04, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x4e, 0x61, 0x6d, - 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x50, 0x61, 0x72, 0x74, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x50, 0x61, 0x72, 0x74, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x22, - 0x7e, 0x0a, 0x17, 0x54, 0x72, 0x75, 0x73, 0x74, 0x42, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x52, 0x65, - 0x61, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x49, 0x6e, - 0x64, 0x65, 0x78, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x05, 0x49, 0x6e, 0x64, 0x65, 0x78, - 0x12, 0x4d, 0x0a, 0x06, 0x42, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x35, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, - 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x70, 0x65, 0x65, - 0x72, 0x69, 0x6e, 0x67, 0x2e, 0x50, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x54, 0x72, 0x75, 0x73, - 0x74, 0x42, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x52, 0x06, 0x42, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x22, - 0x2d, 0x0a, 0x1b, 0x50, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x54, 0x65, 0x72, 0x6d, 0x69, 0x6e, - 0x61, 0x74, 0x65, 0x42, 0x79, 0x49, 0x44, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, - 0x0a, 0x02, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x49, 0x44, 0x22, 0x1e, - 0x0a, 0x1c, 0x50, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x54, 0x65, 0x72, 0x6d, 0x69, 0x6e, 0x61, - 0x74, 0x65, 0x42, 0x79, 0x49, 0x44, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x87, - 0x01, 0x0a, 0x1e, 0x50, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x54, 0x72, 0x75, 0x73, 0x74, 0x42, - 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x57, 0x72, 0x69, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x12, 0x65, 0x0a, 0x12, 0x50, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x54, 0x72, 0x75, 0x73, - 0x74, 0x42, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x35, 0x2e, - 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, - 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x70, 0x65, 0x65, 0x72, 0x69, 0x6e, - 0x67, 0x2e, 0x50, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x54, 0x72, 0x75, 0x73, 0x74, 0x42, 0x75, - 0x6e, 0x64, 0x6c, 0x65, 0x52, 0x12, 0x50, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x54, 0x72, 0x75, - 0x73, 0x74, 0x42, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x22, 0x21, 0x0a, 0x1f, 0x50, 0x65, 0x65, 0x72, - 0x69, 0x6e, 0x67, 0x54, 0x72, 0x75, 0x73, 0x74, 0x42, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x57, 0x72, - 0x69, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x53, 0x0a, 0x1f, 0x50, - 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x54, 0x72, 0x75, 0x73, 0x74, 0x42, 0x75, 0x6e, 0x64, 0x6c, - 0x65, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, - 0x0a, 0x04, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x4e, 0x61, - 0x6d, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x50, 0x61, 0x72, 0x74, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x50, 0x61, 0x72, 0x74, 0x69, 0x74, 0x69, 0x6f, 0x6e, - 0x22, 0x22, 0x0a, 0x20, 0x50, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x54, 0x72, 0x75, 0x73, 0x74, - 0x42, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x9a, 0x02, 0x0a, 0x14, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, - 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, - 0x08, 0x50, 0x65, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x08, 0x50, 0x65, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x50, 0x61, 0x72, - 0x74, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x50, 0x61, - 0x72, 0x74, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x55, 0x0a, 0x04, 0x4d, 0x65, 0x74, 0x61, 0x18, - 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x41, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, - 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, - 0x6c, 0x2e, 0x70, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x2e, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, - 0x74, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x4d, - 0x65, 0x74, 0x61, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x04, 0x4d, 0x65, 0x74, 0x61, 0x12, 0x38, - 0x0a, 0x17, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, - 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, 0x09, 0x52, - 0x17, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x41, - 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x1a, 0x37, 0x0a, 0x09, 0x4d, 0x65, 0x74, 0x61, + 0x70, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x2e, 0x45, 0x73, 0x74, 0x61, 0x62, 0x6c, 0x69, 0x73, + 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x45, 0x6e, 0x74, + 0x72, 0x79, 0x52, 0x04, 0x4d, 0x65, 0x74, 0x61, 0x1a, 0x37, 0x0a, 0x09, 0x4d, 0x65, 0x74, 0x61, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, - 0x01, 0x22, 0x3b, 0x0a, 0x15, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x54, 0x6f, 0x6b, - 0x65, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x22, 0x0a, 0x0c, 0x50, 0x65, - 0x65, 0x72, 0x69, 0x6e, 0x67, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x0c, 0x50, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0xfc, - 0x01, 0x0a, 0x10, 0x45, 0x73, 0x74, 0x61, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x50, 0x65, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x50, 0x65, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x12, - 0x22, 0x0a, 0x0c, 0x50, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x50, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x54, 0x6f, - 0x6b, 0x65, 0x6e, 0x12, 0x1c, 0x0a, 0x09, 0x50, 0x61, 0x72, 0x74, 0x69, 0x74, 0x69, 0x6f, 0x6e, - 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x50, 0x61, 0x72, 0x74, 0x69, 0x74, 0x69, 0x6f, - 0x6e, 0x12, 0x51, 0x0a, 0x04, 0x4d, 0x65, 0x74, 0x61, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, - 0x3d, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, - 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x70, 0x65, 0x65, 0x72, - 0x69, 0x6e, 0x67, 0x2e, 0x45, 0x73, 0x74, 0x61, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x04, - 0x4d, 0x65, 0x74, 0x61, 0x1a, 0x37, 0x0a, 0x09, 0x4d, 0x65, 0x74, 0x61, 0x45, 0x6e, 0x74, 0x72, - 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, - 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x13, 0x0a, - 0x11, 0x45, 0x73, 0x74, 0x61, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x2a, 0x73, 0x0a, 0x0c, 0x50, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x53, 0x74, 0x61, - 0x74, 0x65, 0x12, 0x0d, 0x0a, 0x09, 0x55, 0x4e, 0x44, 0x45, 0x46, 0x49, 0x4e, 0x45, 0x44, 0x10, - 0x00, 0x12, 0x0b, 0x0a, 0x07, 0x50, 0x45, 0x4e, 0x44, 0x49, 0x4e, 0x47, 0x10, 0x01, 0x12, 0x10, - 0x0a, 0x0c, 0x45, 0x53, 0x54, 0x41, 0x42, 0x4c, 0x49, 0x53, 0x48, 0x49, 0x4e, 0x47, 0x10, 0x02, - 0x12, 0x0a, 0x0a, 0x06, 0x41, 0x43, 0x54, 0x49, 0x56, 0x45, 0x10, 0x03, 0x12, 0x0b, 0x0a, 0x07, - 0x46, 0x41, 0x49, 0x4c, 0x49, 0x4e, 0x47, 0x10, 0x04, 0x12, 0x0c, 0x0a, 0x08, 0x44, 0x45, 0x4c, - 0x45, 0x54, 0x49, 0x4e, 0x47, 0x10, 0x05, 0x12, 0x0e, 0x0a, 0x0a, 0x54, 0x45, 0x52, 0x4d, 0x49, - 0x4e, 0x41, 0x54, 0x45, 0x44, 0x10, 0x06, 0x32, 0xc0, 0x08, 0x0a, 0x0e, 0x50, 0x65, 0x65, 0x72, - 0x69, 0x6e, 0x67, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x82, 0x01, 0x0a, 0x0d, 0x47, - 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x37, 0x2e, 0x68, - 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, - 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x70, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, - 0x2e, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x38, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, + 0x01, 0x22, 0x13, 0x0a, 0x11, 0x45, 0x73, 0x74, 0x61, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2a, 0x73, 0x0a, 0x0c, 0x50, 0x65, 0x65, 0x72, 0x69, 0x6e, + 0x67, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x0d, 0x0a, 0x09, 0x55, 0x4e, 0x44, 0x45, 0x46, 0x49, + 0x4e, 0x45, 0x44, 0x10, 0x00, 0x12, 0x0b, 0x0a, 0x07, 0x50, 0x45, 0x4e, 0x44, 0x49, 0x4e, 0x47, + 0x10, 0x01, 0x12, 0x10, 0x0a, 0x0c, 0x45, 0x53, 0x54, 0x41, 0x42, 0x4c, 0x49, 0x53, 0x48, 0x49, + 0x4e, 0x47, 0x10, 0x02, 0x12, 0x0a, 0x0a, 0x06, 0x41, 0x43, 0x54, 0x49, 0x56, 0x45, 0x10, 0x03, + 0x12, 0x0b, 0x0a, 0x07, 0x46, 0x41, 0x49, 0x4c, 0x49, 0x4e, 0x47, 0x10, 0x04, 0x12, 0x0c, 0x0a, + 0x08, 0x44, 0x45, 0x4c, 0x45, 0x54, 0x49, 0x4e, 0x47, 0x10, 0x05, 0x12, 0x0e, 0x0a, 0x0a, 0x54, + 0x45, 0x52, 0x4d, 0x49, 0x4e, 0x41, 0x54, 0x45, 0x44, 0x10, 0x06, 0x32, 0xc0, 0x08, 0x0a, 0x0e, + 0x50, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x82, + 0x01, 0x0a, 0x0d, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, + 0x12, 0x37, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, + 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x70, 0x65, 0x65, + 0x72, 0x69, 0x6e, 0x67, 0x2e, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x54, 0x6f, 0x6b, + 0x65, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x38, 0x2e, 0x68, 0x61, 0x73, 0x68, + 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, + 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x70, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x2e, 0x47, 0x65, + 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x76, 0x0a, 0x09, 0x45, 0x73, 0x74, 0x61, 0x62, 0x6c, 0x69, 0x73, 0x68, + 0x12, 0x33, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, + 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x70, 0x65, 0x65, + 0x72, 0x69, 0x6e, 0x67, 0x2e, 0x45, 0x73, 0x74, 0x61, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x34, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, - 0x6c, 0x2e, 0x70, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x2e, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, - 0x74, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, - 0x76, 0x0a, 0x09, 0x45, 0x73, 0x74, 0x61, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x12, 0x33, 0x2e, 0x68, + 0x6c, 0x2e, 0x70, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x2e, 0x45, 0x73, 0x74, 0x61, 0x62, 0x6c, + 0x69, 0x73, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x7c, 0x0a, 0x0b, 0x50, + 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x61, 0x64, 0x12, 0x35, 0x2e, 0x68, 0x61, 0x73, + 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, + 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x70, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x2e, 0x50, + 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x61, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x36, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, + 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x70, 0x65, + 0x65, 0x72, 0x69, 0x6e, 0x67, 0x2e, 0x50, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x61, + 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x7c, 0x0a, 0x0b, 0x50, 0x65, 0x65, + 0x72, 0x69, 0x6e, 0x67, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x35, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, + 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, + 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x70, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x2e, 0x50, 0x65, 0x65, + 0x72, 0x69, 0x6e, 0x67, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x36, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, + 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x70, 0x65, 0x65, 0x72, + 0x69, 0x6e, 0x67, 0x2e, 0x50, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x4c, 0x69, 0x73, 0x74, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x82, 0x01, 0x0a, 0x0d, 0x50, 0x65, 0x65, 0x72, + 0x69, 0x6e, 0x67, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x12, 0x37, 0x2e, 0x68, 0x61, 0x73, 0x68, + 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, + 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x70, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x2e, 0x50, 0x65, + 0x65, 0x72, 0x69, 0x6e, 0x67, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x38, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, + 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x70, + 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x2e, 0x50, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x44, 0x65, + 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x7f, 0x0a, 0x0c, + 0x50, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x57, 0x72, 0x69, 0x74, 0x65, 0x12, 0x36, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x70, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, - 0x2e, 0x45, 0x73, 0x74, 0x61, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x1a, 0x34, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, - 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x70, 0x65, - 0x65, 0x72, 0x69, 0x6e, 0x67, 0x2e, 0x45, 0x73, 0x74, 0x61, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x7c, 0x0a, 0x0b, 0x50, 0x65, 0x65, 0x72, 0x69, - 0x6e, 0x67, 0x52, 0x65, 0x61, 0x64, 0x12, 0x35, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, - 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, - 0x61, 0x6c, 0x2e, 0x70, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x2e, 0x50, 0x65, 0x65, 0x72, 0x69, - 0x6e, 0x67, 0x52, 0x65, 0x61, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x36, 0x2e, - 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, - 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x70, 0x65, 0x65, 0x72, 0x69, 0x6e, - 0x67, 0x2e, 0x50, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x61, 0x64, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x7c, 0x0a, 0x0b, 0x50, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, - 0x4c, 0x69, 0x73, 0x74, 0x12, 0x35, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, + 0x2e, 0x50, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x57, 0x72, 0x69, 0x74, 0x65, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x37, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x70, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x2e, 0x50, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, - 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x36, 0x2e, 0x68, 0x61, - 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, - 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x70, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x2e, - 0x50, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x12, 0x82, 0x01, 0x0a, 0x0d, 0x50, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x44, - 0x65, 0x6c, 0x65, 0x74, 0x65, 0x12, 0x37, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, - 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, - 0x6c, 0x2e, 0x70, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x2e, 0x50, 0x65, 0x65, 0x72, 0x69, 0x6e, - 0x67, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x38, - 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, - 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x70, 0x65, 0x65, 0x72, 0x69, - 0x6e, 0x67, 0x2e, 0x50, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x7f, 0x0a, 0x0c, 0x50, 0x65, 0x65, 0x72, - 0x69, 0x6e, 0x67, 0x57, 0x72, 0x69, 0x74, 0x65, 0x12, 0x36, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, - 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, - 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x70, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x2e, 0x50, 0x65, 0x65, - 0x72, 0x69, 0x6e, 0x67, 0x57, 0x72, 0x69, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x37, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, - 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x70, 0x65, 0x65, - 0x72, 0x69, 0x6e, 0x67, 0x2e, 0x50, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x57, 0x72, 0x69, 0x74, - 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0xa3, 0x01, 0x0a, 0x18, 0x54, 0x72, - 0x75, 0x73, 0x74, 0x42, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x4c, 0x69, 0x73, 0x74, 0x42, 0x79, 0x53, - 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x42, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, - 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, - 0x61, 0x6c, 0x2e, 0x70, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x2e, 0x54, 0x72, 0x75, 0x73, 0x74, - 0x42, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x4c, 0x69, 0x73, 0x74, 0x42, 0x79, 0x53, 0x65, 0x72, 0x76, - 0x69, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x43, 0x2e, 0x68, 0x61, 0x73, + 0x57, 0x72, 0x69, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0xa3, 0x01, + 0x0a, 0x18, 0x54, 0x72, 0x75, 0x73, 0x74, 0x42, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x4c, 0x69, 0x73, + 0x74, 0x42, 0x79, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x42, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x70, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x2e, 0x54, 0x72, 0x75, 0x73, 0x74, 0x42, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x4c, 0x69, 0x73, 0x74, 0x42, 0x79, - 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, - 0x88, 0x01, 0x0a, 0x0f, 0x54, 0x72, 0x75, 0x73, 0x74, 0x42, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x52, - 0x65, 0x61, 0x64, 0x12, 0x39, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, - 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, - 0x70, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x2e, 0x54, 0x72, 0x75, 0x73, 0x74, 0x42, 0x75, 0x6e, - 0x64, 0x6c, 0x65, 0x52, 0x65, 0x61, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x3a, + 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x43, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x70, 0x65, 0x65, 0x72, 0x69, - 0x6e, 0x67, 0x2e, 0x54, 0x72, 0x75, 0x73, 0x74, 0x42, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x52, 0x65, - 0x61, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x8a, 0x02, 0x0a, 0x25, 0x63, - 0x6f, 0x6d, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, - 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x70, 0x65, 0x65, - 0x72, 0x69, 0x6e, 0x67, 0x42, 0x0c, 0x50, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x50, 0x72, 0x6f, - 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x2b, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, - 0x2f, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2f, 0x63, 0x6f, 0x6e, 0x73, 0x75, - 0x6c, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x70, 0x62, 0x70, 0x65, 0x65, 0x72, 0x69, 0x6e, - 0x67, 0xa2, 0x02, 0x04, 0x48, 0x43, 0x49, 0x50, 0xaa, 0x02, 0x21, 0x48, 0x61, 0x73, 0x68, 0x69, - 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x43, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x49, 0x6e, 0x74, 0x65, - 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x50, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0xca, 0x02, 0x21, 0x48, - 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x5c, 0x43, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x5c, - 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x5c, 0x50, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, - 0xe2, 0x02, 0x2d, 0x48, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x5c, 0x43, 0x6f, 0x6e, + 0x6e, 0x67, 0x2e, 0x54, 0x72, 0x75, 0x73, 0x74, 0x42, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x4c, 0x69, + 0x73, 0x74, 0x42, 0x79, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x88, 0x01, 0x0a, 0x0f, 0x54, 0x72, 0x75, 0x73, 0x74, 0x42, 0x75, 0x6e, + 0x64, 0x6c, 0x65, 0x52, 0x65, 0x61, 0x64, 0x12, 0x39, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, + 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, + 0x6e, 0x61, 0x6c, 0x2e, 0x70, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x2e, 0x54, 0x72, 0x75, 0x73, + 0x74, 0x42, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x52, 0x65, 0x61, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x3a, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, + 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x70, + 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x2e, 0x54, 0x72, 0x75, 0x73, 0x74, 0x42, 0x75, 0x6e, 0x64, + 0x6c, 0x65, 0x52, 0x65, 0x61, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x8a, + 0x02, 0x0a, 0x25, 0x63, 0x6f, 0x6d, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, + 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, + 0x2e, 0x70, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x42, 0x0c, 0x50, 0x65, 0x65, 0x72, 0x69, 0x6e, + 0x67, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x2b, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, + 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2f, 0x63, + 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x70, 0x62, 0x70, 0x65, + 0x65, 0x72, 0x69, 0x6e, 0x67, 0xa2, 0x02, 0x04, 0x48, 0x43, 0x49, 0x50, 0xaa, 0x02, 0x21, 0x48, + 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x43, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, + 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x50, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, + 0xca, 0x02, 0x21, 0x48, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x5c, 0x43, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x5c, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x5c, 0x50, 0x65, 0x65, - 0x72, 0x69, 0x6e, 0x67, 0x5c, 0x47, 0x50, 0x42, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, - 0xea, 0x02, 0x24, 0x48, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x3a, 0x3a, 0x43, 0x6f, - 0x6e, 0x73, 0x75, 0x6c, 0x3a, 0x3a, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x3a, 0x3a, - 0x50, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x72, 0x69, 0x6e, 0x67, 0xe2, 0x02, 0x2d, 0x48, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, + 0x5c, 0x43, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x5c, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, + 0x5c, 0x50, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x5c, 0x47, 0x50, 0x42, 0x4d, 0x65, 0x74, 0x61, + 0x64, 0x61, 0x74, 0x61, 0xea, 0x02, 0x24, 0x48, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, + 0x3a, 0x3a, 0x43, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x3a, 0x3a, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e, + 0x61, 0x6c, 0x3a, 0x3a, 0x50, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x62, 0x06, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x33, } var ( @@ -2499,89 +2604,94 @@ func file_proto_pbpeering_peering_proto_rawDescGZIP() []byte { } var file_proto_pbpeering_peering_proto_enumTypes = make([]protoimpl.EnumInfo, 1) -var file_proto_pbpeering_peering_proto_msgTypes = make([]protoimpl.MessageInfo, 37) +var file_proto_pbpeering_peering_proto_msgTypes = make([]protoimpl.MessageInfo, 38) var file_proto_pbpeering_peering_proto_goTypes = []interface{}{ (PeeringState)(0), // 0: hashicorp.consul.internal.peering.PeeringState (*SecretsWriteRequest)(nil), // 1: hashicorp.consul.internal.peering.SecretsWriteRequest (*PeeringSecrets)(nil), // 2: hashicorp.consul.internal.peering.PeeringSecrets (*Peering)(nil), // 3: hashicorp.consul.internal.peering.Peering - (*PeeringTrustBundle)(nil), // 4: hashicorp.consul.internal.peering.PeeringTrustBundle - (*PeeringServerAddresses)(nil), // 5: hashicorp.consul.internal.peering.PeeringServerAddresses - (*PeeringReadRequest)(nil), // 6: hashicorp.consul.internal.peering.PeeringReadRequest - (*PeeringReadResponse)(nil), // 7: hashicorp.consul.internal.peering.PeeringReadResponse - (*PeeringListRequest)(nil), // 8: hashicorp.consul.internal.peering.PeeringListRequest - (*PeeringListResponse)(nil), // 9: hashicorp.consul.internal.peering.PeeringListResponse - (*PeeringWriteRequest)(nil), // 10: hashicorp.consul.internal.peering.PeeringWriteRequest - (*PeeringWriteResponse)(nil), // 11: hashicorp.consul.internal.peering.PeeringWriteResponse - (*PeeringDeleteRequest)(nil), // 12: hashicorp.consul.internal.peering.PeeringDeleteRequest - (*PeeringDeleteResponse)(nil), // 13: hashicorp.consul.internal.peering.PeeringDeleteResponse - (*TrustBundleListByServiceRequest)(nil), // 14: hashicorp.consul.internal.peering.TrustBundleListByServiceRequest - (*TrustBundleListByServiceResponse)(nil), // 15: hashicorp.consul.internal.peering.TrustBundleListByServiceResponse - (*TrustBundleReadRequest)(nil), // 16: hashicorp.consul.internal.peering.TrustBundleReadRequest - (*TrustBundleReadResponse)(nil), // 17: hashicorp.consul.internal.peering.TrustBundleReadResponse - (*PeeringTerminateByIDRequest)(nil), // 18: hashicorp.consul.internal.peering.PeeringTerminateByIDRequest - (*PeeringTerminateByIDResponse)(nil), // 19: hashicorp.consul.internal.peering.PeeringTerminateByIDResponse - (*PeeringTrustBundleWriteRequest)(nil), // 20: hashicorp.consul.internal.peering.PeeringTrustBundleWriteRequest - (*PeeringTrustBundleWriteResponse)(nil), // 21: hashicorp.consul.internal.peering.PeeringTrustBundleWriteResponse - (*PeeringTrustBundleDeleteRequest)(nil), // 22: hashicorp.consul.internal.peering.PeeringTrustBundleDeleteRequest - (*PeeringTrustBundleDeleteResponse)(nil), // 23: hashicorp.consul.internal.peering.PeeringTrustBundleDeleteResponse - (*GenerateTokenRequest)(nil), // 24: hashicorp.consul.internal.peering.GenerateTokenRequest - (*GenerateTokenResponse)(nil), // 25: hashicorp.consul.internal.peering.GenerateTokenResponse - (*EstablishRequest)(nil), // 26: hashicorp.consul.internal.peering.EstablishRequest - (*EstablishResponse)(nil), // 27: hashicorp.consul.internal.peering.EstablishResponse - (*SecretsWriteRequest_GenerateTokenRequest)(nil), // 28: hashicorp.consul.internal.peering.SecretsWriteRequest.GenerateTokenRequest - (*SecretsWriteRequest_ExchangeSecretRequest)(nil), // 29: hashicorp.consul.internal.peering.SecretsWriteRequest.ExchangeSecretRequest - (*SecretsWriteRequest_PromotePendingRequest)(nil), // 30: hashicorp.consul.internal.peering.SecretsWriteRequest.PromotePendingRequest - (*SecretsWriteRequest_EstablishRequest)(nil), // 31: hashicorp.consul.internal.peering.SecretsWriteRequest.EstablishRequest - (*PeeringSecrets_Establishment)(nil), // 32: hashicorp.consul.internal.peering.PeeringSecrets.Establishment - (*PeeringSecrets_Stream)(nil), // 33: hashicorp.consul.internal.peering.PeeringSecrets.Stream - nil, // 34: hashicorp.consul.internal.peering.Peering.MetaEntry - nil, // 35: hashicorp.consul.internal.peering.PeeringWriteRequest.MetaEntry - nil, // 36: hashicorp.consul.internal.peering.GenerateTokenRequest.MetaEntry - nil, // 37: hashicorp.consul.internal.peering.EstablishRequest.MetaEntry - (*timestamppb.Timestamp)(nil), // 38: google.protobuf.Timestamp + (*StreamStatus)(nil), // 4: hashicorp.consul.internal.peering.StreamStatus + (*PeeringTrustBundle)(nil), // 5: hashicorp.consul.internal.peering.PeeringTrustBundle + (*PeeringServerAddresses)(nil), // 6: hashicorp.consul.internal.peering.PeeringServerAddresses + (*PeeringReadRequest)(nil), // 7: hashicorp.consul.internal.peering.PeeringReadRequest + (*PeeringReadResponse)(nil), // 8: hashicorp.consul.internal.peering.PeeringReadResponse + (*PeeringListRequest)(nil), // 9: hashicorp.consul.internal.peering.PeeringListRequest + (*PeeringListResponse)(nil), // 10: hashicorp.consul.internal.peering.PeeringListResponse + (*PeeringWriteRequest)(nil), // 11: hashicorp.consul.internal.peering.PeeringWriteRequest + (*PeeringWriteResponse)(nil), // 12: hashicorp.consul.internal.peering.PeeringWriteResponse + (*PeeringDeleteRequest)(nil), // 13: hashicorp.consul.internal.peering.PeeringDeleteRequest + (*PeeringDeleteResponse)(nil), // 14: hashicorp.consul.internal.peering.PeeringDeleteResponse + (*TrustBundleListByServiceRequest)(nil), // 15: hashicorp.consul.internal.peering.TrustBundleListByServiceRequest + (*TrustBundleListByServiceResponse)(nil), // 16: hashicorp.consul.internal.peering.TrustBundleListByServiceResponse + (*TrustBundleReadRequest)(nil), // 17: hashicorp.consul.internal.peering.TrustBundleReadRequest + (*TrustBundleReadResponse)(nil), // 18: hashicorp.consul.internal.peering.TrustBundleReadResponse + (*PeeringTerminateByIDRequest)(nil), // 19: hashicorp.consul.internal.peering.PeeringTerminateByIDRequest + (*PeeringTerminateByIDResponse)(nil), // 20: hashicorp.consul.internal.peering.PeeringTerminateByIDResponse + (*PeeringTrustBundleWriteRequest)(nil), // 21: hashicorp.consul.internal.peering.PeeringTrustBundleWriteRequest + (*PeeringTrustBundleWriteResponse)(nil), // 22: hashicorp.consul.internal.peering.PeeringTrustBundleWriteResponse + (*PeeringTrustBundleDeleteRequest)(nil), // 23: hashicorp.consul.internal.peering.PeeringTrustBundleDeleteRequest + (*PeeringTrustBundleDeleteResponse)(nil), // 24: hashicorp.consul.internal.peering.PeeringTrustBundleDeleteResponse + (*GenerateTokenRequest)(nil), // 25: hashicorp.consul.internal.peering.GenerateTokenRequest + (*GenerateTokenResponse)(nil), // 26: hashicorp.consul.internal.peering.GenerateTokenResponse + (*EstablishRequest)(nil), // 27: hashicorp.consul.internal.peering.EstablishRequest + (*EstablishResponse)(nil), // 28: hashicorp.consul.internal.peering.EstablishResponse + (*SecretsWriteRequest_GenerateTokenRequest)(nil), // 29: hashicorp.consul.internal.peering.SecretsWriteRequest.GenerateTokenRequest + (*SecretsWriteRequest_ExchangeSecretRequest)(nil), // 30: hashicorp.consul.internal.peering.SecretsWriteRequest.ExchangeSecretRequest + (*SecretsWriteRequest_PromotePendingRequest)(nil), // 31: hashicorp.consul.internal.peering.SecretsWriteRequest.PromotePendingRequest + (*SecretsWriteRequest_EstablishRequest)(nil), // 32: hashicorp.consul.internal.peering.SecretsWriteRequest.EstablishRequest + (*PeeringSecrets_Establishment)(nil), // 33: hashicorp.consul.internal.peering.PeeringSecrets.Establishment + (*PeeringSecrets_Stream)(nil), // 34: hashicorp.consul.internal.peering.PeeringSecrets.Stream + nil, // 35: hashicorp.consul.internal.peering.Peering.MetaEntry + nil, // 36: hashicorp.consul.internal.peering.PeeringWriteRequest.MetaEntry + nil, // 37: hashicorp.consul.internal.peering.GenerateTokenRequest.MetaEntry + nil, // 38: hashicorp.consul.internal.peering.EstablishRequest.MetaEntry + (*timestamppb.Timestamp)(nil), // 39: google.protobuf.Timestamp } var file_proto_pbpeering_peering_proto_depIdxs = []int32{ - 28, // 0: hashicorp.consul.internal.peering.SecretsWriteRequest.generate_token:type_name -> hashicorp.consul.internal.peering.SecretsWriteRequest.GenerateTokenRequest - 29, // 1: hashicorp.consul.internal.peering.SecretsWriteRequest.exchange_secret:type_name -> hashicorp.consul.internal.peering.SecretsWriteRequest.ExchangeSecretRequest - 30, // 2: hashicorp.consul.internal.peering.SecretsWriteRequest.promote_pending:type_name -> hashicorp.consul.internal.peering.SecretsWriteRequest.PromotePendingRequest - 31, // 3: hashicorp.consul.internal.peering.SecretsWriteRequest.establish:type_name -> hashicorp.consul.internal.peering.SecretsWriteRequest.EstablishRequest - 32, // 4: hashicorp.consul.internal.peering.PeeringSecrets.establishment:type_name -> hashicorp.consul.internal.peering.PeeringSecrets.Establishment - 33, // 5: hashicorp.consul.internal.peering.PeeringSecrets.stream:type_name -> hashicorp.consul.internal.peering.PeeringSecrets.Stream - 38, // 6: hashicorp.consul.internal.peering.Peering.DeletedAt:type_name -> google.protobuf.Timestamp - 34, // 7: hashicorp.consul.internal.peering.Peering.Meta:type_name -> hashicorp.consul.internal.peering.Peering.MetaEntry + 29, // 0: hashicorp.consul.internal.peering.SecretsWriteRequest.generate_token:type_name -> hashicorp.consul.internal.peering.SecretsWriteRequest.GenerateTokenRequest + 30, // 1: hashicorp.consul.internal.peering.SecretsWriteRequest.exchange_secret:type_name -> hashicorp.consul.internal.peering.SecretsWriteRequest.ExchangeSecretRequest + 31, // 2: hashicorp.consul.internal.peering.SecretsWriteRequest.promote_pending:type_name -> hashicorp.consul.internal.peering.SecretsWriteRequest.PromotePendingRequest + 32, // 3: hashicorp.consul.internal.peering.SecretsWriteRequest.establish:type_name -> hashicorp.consul.internal.peering.SecretsWriteRequest.EstablishRequest + 33, // 4: hashicorp.consul.internal.peering.PeeringSecrets.establishment:type_name -> hashicorp.consul.internal.peering.PeeringSecrets.Establishment + 34, // 5: hashicorp.consul.internal.peering.PeeringSecrets.stream:type_name -> hashicorp.consul.internal.peering.PeeringSecrets.Stream + 39, // 6: hashicorp.consul.internal.peering.Peering.DeletedAt:type_name -> google.protobuf.Timestamp + 35, // 7: hashicorp.consul.internal.peering.Peering.Meta:type_name -> hashicorp.consul.internal.peering.Peering.MetaEntry 0, // 8: hashicorp.consul.internal.peering.Peering.State:type_name -> hashicorp.consul.internal.peering.PeeringState - 3, // 9: hashicorp.consul.internal.peering.PeeringReadResponse.Peering:type_name -> hashicorp.consul.internal.peering.Peering - 3, // 10: hashicorp.consul.internal.peering.PeeringListResponse.Peerings:type_name -> hashicorp.consul.internal.peering.Peering - 3, // 11: hashicorp.consul.internal.peering.PeeringWriteRequest.Peering:type_name -> hashicorp.consul.internal.peering.Peering - 1, // 12: hashicorp.consul.internal.peering.PeeringWriteRequest.SecretsRequest:type_name -> hashicorp.consul.internal.peering.SecretsWriteRequest - 35, // 13: hashicorp.consul.internal.peering.PeeringWriteRequest.Meta:type_name -> hashicorp.consul.internal.peering.PeeringWriteRequest.MetaEntry - 4, // 14: hashicorp.consul.internal.peering.TrustBundleListByServiceResponse.Bundles:type_name -> hashicorp.consul.internal.peering.PeeringTrustBundle - 4, // 15: hashicorp.consul.internal.peering.TrustBundleReadResponse.Bundle:type_name -> hashicorp.consul.internal.peering.PeeringTrustBundle - 4, // 16: hashicorp.consul.internal.peering.PeeringTrustBundleWriteRequest.PeeringTrustBundle:type_name -> hashicorp.consul.internal.peering.PeeringTrustBundle - 36, // 17: hashicorp.consul.internal.peering.GenerateTokenRequest.Meta:type_name -> hashicorp.consul.internal.peering.GenerateTokenRequest.MetaEntry - 37, // 18: hashicorp.consul.internal.peering.EstablishRequest.Meta:type_name -> hashicorp.consul.internal.peering.EstablishRequest.MetaEntry - 24, // 19: hashicorp.consul.internal.peering.PeeringService.GenerateToken:input_type -> hashicorp.consul.internal.peering.GenerateTokenRequest - 26, // 20: hashicorp.consul.internal.peering.PeeringService.Establish:input_type -> hashicorp.consul.internal.peering.EstablishRequest - 6, // 21: hashicorp.consul.internal.peering.PeeringService.PeeringRead:input_type -> hashicorp.consul.internal.peering.PeeringReadRequest - 8, // 22: hashicorp.consul.internal.peering.PeeringService.PeeringList:input_type -> hashicorp.consul.internal.peering.PeeringListRequest - 12, // 23: hashicorp.consul.internal.peering.PeeringService.PeeringDelete:input_type -> hashicorp.consul.internal.peering.PeeringDeleteRequest - 10, // 24: hashicorp.consul.internal.peering.PeeringService.PeeringWrite:input_type -> hashicorp.consul.internal.peering.PeeringWriteRequest - 14, // 25: hashicorp.consul.internal.peering.PeeringService.TrustBundleListByService:input_type -> hashicorp.consul.internal.peering.TrustBundleListByServiceRequest - 16, // 26: hashicorp.consul.internal.peering.PeeringService.TrustBundleRead:input_type -> hashicorp.consul.internal.peering.TrustBundleReadRequest - 25, // 27: hashicorp.consul.internal.peering.PeeringService.GenerateToken:output_type -> hashicorp.consul.internal.peering.GenerateTokenResponse - 27, // 28: hashicorp.consul.internal.peering.PeeringService.Establish:output_type -> hashicorp.consul.internal.peering.EstablishResponse - 7, // 29: hashicorp.consul.internal.peering.PeeringService.PeeringRead:output_type -> hashicorp.consul.internal.peering.PeeringReadResponse - 9, // 30: hashicorp.consul.internal.peering.PeeringService.PeeringList:output_type -> hashicorp.consul.internal.peering.PeeringListResponse - 13, // 31: hashicorp.consul.internal.peering.PeeringService.PeeringDelete:output_type -> hashicorp.consul.internal.peering.PeeringDeleteResponse - 11, // 32: hashicorp.consul.internal.peering.PeeringService.PeeringWrite:output_type -> hashicorp.consul.internal.peering.PeeringWriteResponse - 15, // 33: hashicorp.consul.internal.peering.PeeringService.TrustBundleListByService:output_type -> hashicorp.consul.internal.peering.TrustBundleListByServiceResponse - 17, // 34: hashicorp.consul.internal.peering.PeeringService.TrustBundleRead:output_type -> hashicorp.consul.internal.peering.TrustBundleReadResponse - 27, // [27:35] is the sub-list for method output_type - 19, // [19:27] is the sub-list for method input_type - 19, // [19:19] is the sub-list for extension type_name - 19, // [19:19] is the sub-list for extension extendee - 0, // [0:19] is the sub-list for field type_name + 4, // 9: hashicorp.consul.internal.peering.Peering.StreamStatus:type_name -> hashicorp.consul.internal.peering.StreamStatus + 39, // 10: hashicorp.consul.internal.peering.StreamStatus.LastHeartbeat:type_name -> google.protobuf.Timestamp + 39, // 11: hashicorp.consul.internal.peering.StreamStatus.LastReceive:type_name -> google.protobuf.Timestamp + 39, // 12: hashicorp.consul.internal.peering.StreamStatus.LastSend:type_name -> google.protobuf.Timestamp + 3, // 13: hashicorp.consul.internal.peering.PeeringReadResponse.Peering:type_name -> hashicorp.consul.internal.peering.Peering + 3, // 14: hashicorp.consul.internal.peering.PeeringListResponse.Peerings:type_name -> hashicorp.consul.internal.peering.Peering + 3, // 15: hashicorp.consul.internal.peering.PeeringWriteRequest.Peering:type_name -> hashicorp.consul.internal.peering.Peering + 1, // 16: hashicorp.consul.internal.peering.PeeringWriteRequest.SecretsRequest:type_name -> hashicorp.consul.internal.peering.SecretsWriteRequest + 36, // 17: hashicorp.consul.internal.peering.PeeringWriteRequest.Meta:type_name -> hashicorp.consul.internal.peering.PeeringWriteRequest.MetaEntry + 5, // 18: hashicorp.consul.internal.peering.TrustBundleListByServiceResponse.Bundles:type_name -> hashicorp.consul.internal.peering.PeeringTrustBundle + 5, // 19: hashicorp.consul.internal.peering.TrustBundleReadResponse.Bundle:type_name -> hashicorp.consul.internal.peering.PeeringTrustBundle + 5, // 20: hashicorp.consul.internal.peering.PeeringTrustBundleWriteRequest.PeeringTrustBundle:type_name -> hashicorp.consul.internal.peering.PeeringTrustBundle + 37, // 21: hashicorp.consul.internal.peering.GenerateTokenRequest.Meta:type_name -> hashicorp.consul.internal.peering.GenerateTokenRequest.MetaEntry + 38, // 22: hashicorp.consul.internal.peering.EstablishRequest.Meta:type_name -> hashicorp.consul.internal.peering.EstablishRequest.MetaEntry + 25, // 23: hashicorp.consul.internal.peering.PeeringService.GenerateToken:input_type -> hashicorp.consul.internal.peering.GenerateTokenRequest + 27, // 24: hashicorp.consul.internal.peering.PeeringService.Establish:input_type -> hashicorp.consul.internal.peering.EstablishRequest + 7, // 25: hashicorp.consul.internal.peering.PeeringService.PeeringRead:input_type -> hashicorp.consul.internal.peering.PeeringReadRequest + 9, // 26: hashicorp.consul.internal.peering.PeeringService.PeeringList:input_type -> hashicorp.consul.internal.peering.PeeringListRequest + 13, // 27: hashicorp.consul.internal.peering.PeeringService.PeeringDelete:input_type -> hashicorp.consul.internal.peering.PeeringDeleteRequest + 11, // 28: hashicorp.consul.internal.peering.PeeringService.PeeringWrite:input_type -> hashicorp.consul.internal.peering.PeeringWriteRequest + 15, // 29: hashicorp.consul.internal.peering.PeeringService.TrustBundleListByService:input_type -> hashicorp.consul.internal.peering.TrustBundleListByServiceRequest + 17, // 30: hashicorp.consul.internal.peering.PeeringService.TrustBundleRead:input_type -> hashicorp.consul.internal.peering.TrustBundleReadRequest + 26, // 31: hashicorp.consul.internal.peering.PeeringService.GenerateToken:output_type -> hashicorp.consul.internal.peering.GenerateTokenResponse + 28, // 32: hashicorp.consul.internal.peering.PeeringService.Establish:output_type -> hashicorp.consul.internal.peering.EstablishResponse + 8, // 33: hashicorp.consul.internal.peering.PeeringService.PeeringRead:output_type -> hashicorp.consul.internal.peering.PeeringReadResponse + 10, // 34: hashicorp.consul.internal.peering.PeeringService.PeeringList:output_type -> hashicorp.consul.internal.peering.PeeringListResponse + 14, // 35: hashicorp.consul.internal.peering.PeeringService.PeeringDelete:output_type -> hashicorp.consul.internal.peering.PeeringDeleteResponse + 12, // 36: hashicorp.consul.internal.peering.PeeringService.PeeringWrite:output_type -> hashicorp.consul.internal.peering.PeeringWriteResponse + 16, // 37: hashicorp.consul.internal.peering.PeeringService.TrustBundleListByService:output_type -> hashicorp.consul.internal.peering.TrustBundleListByServiceResponse + 18, // 38: hashicorp.consul.internal.peering.PeeringService.TrustBundleRead:output_type -> hashicorp.consul.internal.peering.TrustBundleReadResponse + 31, // [31:39] is the sub-list for method output_type + 23, // [23:31] is the sub-list for method input_type + 23, // [23:23] is the sub-list for extension type_name + 23, // [23:23] is the sub-list for extension extendee + 0, // [0:23] is the sub-list for field type_name } func init() { file_proto_pbpeering_peering_proto_init() } @@ -2627,7 +2737,7 @@ func file_proto_pbpeering_peering_proto_init() { } } file_proto_pbpeering_peering_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*PeeringTrustBundle); i { + switch v := v.(*StreamStatus); i { case 0: return &v.state case 1: @@ -2639,7 +2749,7 @@ func file_proto_pbpeering_peering_proto_init() { } } file_proto_pbpeering_peering_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*PeeringServerAddresses); i { + switch v := v.(*PeeringTrustBundle); i { case 0: return &v.state case 1: @@ -2651,7 +2761,7 @@ func file_proto_pbpeering_peering_proto_init() { } } file_proto_pbpeering_peering_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*PeeringReadRequest); i { + switch v := v.(*PeeringServerAddresses); i { case 0: return &v.state case 1: @@ -2663,7 +2773,7 @@ func file_proto_pbpeering_peering_proto_init() { } } file_proto_pbpeering_peering_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*PeeringReadResponse); i { + switch v := v.(*PeeringReadRequest); i { case 0: return &v.state case 1: @@ -2675,7 +2785,7 @@ func file_proto_pbpeering_peering_proto_init() { } } file_proto_pbpeering_peering_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*PeeringListRequest); i { + switch v := v.(*PeeringReadResponse); i { case 0: return &v.state case 1: @@ -2687,7 +2797,7 @@ func file_proto_pbpeering_peering_proto_init() { } } file_proto_pbpeering_peering_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*PeeringListResponse); i { + switch v := v.(*PeeringListRequest); i { case 0: return &v.state case 1: @@ -2699,7 +2809,7 @@ func file_proto_pbpeering_peering_proto_init() { } } file_proto_pbpeering_peering_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*PeeringWriteRequest); i { + switch v := v.(*PeeringListResponse); i { case 0: return &v.state case 1: @@ -2711,7 +2821,7 @@ func file_proto_pbpeering_peering_proto_init() { } } file_proto_pbpeering_peering_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*PeeringWriteResponse); i { + switch v := v.(*PeeringWriteRequest); i { case 0: return &v.state case 1: @@ -2723,7 +2833,7 @@ func file_proto_pbpeering_peering_proto_init() { } } file_proto_pbpeering_peering_proto_msgTypes[11].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*PeeringDeleteRequest); i { + switch v := v.(*PeeringWriteResponse); i { case 0: return &v.state case 1: @@ -2735,7 +2845,7 @@ func file_proto_pbpeering_peering_proto_init() { } } file_proto_pbpeering_peering_proto_msgTypes[12].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*PeeringDeleteResponse); i { + switch v := v.(*PeeringDeleteRequest); i { case 0: return &v.state case 1: @@ -2747,7 +2857,7 @@ func file_proto_pbpeering_peering_proto_init() { } } file_proto_pbpeering_peering_proto_msgTypes[13].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*TrustBundleListByServiceRequest); i { + switch v := v.(*PeeringDeleteResponse); i { case 0: return &v.state case 1: @@ -2759,7 +2869,7 @@ func file_proto_pbpeering_peering_proto_init() { } } file_proto_pbpeering_peering_proto_msgTypes[14].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*TrustBundleListByServiceResponse); i { + switch v := v.(*TrustBundleListByServiceRequest); i { case 0: return &v.state case 1: @@ -2771,7 +2881,7 @@ func file_proto_pbpeering_peering_proto_init() { } } file_proto_pbpeering_peering_proto_msgTypes[15].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*TrustBundleReadRequest); i { + switch v := v.(*TrustBundleListByServiceResponse); i { case 0: return &v.state case 1: @@ -2783,7 +2893,7 @@ func file_proto_pbpeering_peering_proto_init() { } } file_proto_pbpeering_peering_proto_msgTypes[16].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*TrustBundleReadResponse); i { + switch v := v.(*TrustBundleReadRequest); i { case 0: return &v.state case 1: @@ -2795,7 +2905,7 @@ func file_proto_pbpeering_peering_proto_init() { } } file_proto_pbpeering_peering_proto_msgTypes[17].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*PeeringTerminateByIDRequest); i { + switch v := v.(*TrustBundleReadResponse); i { case 0: return &v.state case 1: @@ -2807,7 +2917,7 @@ func file_proto_pbpeering_peering_proto_init() { } } file_proto_pbpeering_peering_proto_msgTypes[18].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*PeeringTerminateByIDResponse); i { + switch v := v.(*PeeringTerminateByIDRequest); i { case 0: return &v.state case 1: @@ -2819,7 +2929,7 @@ func file_proto_pbpeering_peering_proto_init() { } } file_proto_pbpeering_peering_proto_msgTypes[19].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*PeeringTrustBundleWriteRequest); i { + switch v := v.(*PeeringTerminateByIDResponse); i { case 0: return &v.state case 1: @@ -2831,7 +2941,7 @@ func file_proto_pbpeering_peering_proto_init() { } } file_proto_pbpeering_peering_proto_msgTypes[20].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*PeeringTrustBundleWriteResponse); i { + switch v := v.(*PeeringTrustBundleWriteRequest); i { case 0: return &v.state case 1: @@ -2843,7 +2953,7 @@ func file_proto_pbpeering_peering_proto_init() { } } file_proto_pbpeering_peering_proto_msgTypes[21].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*PeeringTrustBundleDeleteRequest); i { + switch v := v.(*PeeringTrustBundleWriteResponse); i { case 0: return &v.state case 1: @@ -2855,7 +2965,7 @@ func file_proto_pbpeering_peering_proto_init() { } } file_proto_pbpeering_peering_proto_msgTypes[22].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*PeeringTrustBundleDeleteResponse); i { + switch v := v.(*PeeringTrustBundleDeleteRequest); i { case 0: return &v.state case 1: @@ -2867,7 +2977,7 @@ func file_proto_pbpeering_peering_proto_init() { } } file_proto_pbpeering_peering_proto_msgTypes[23].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GenerateTokenRequest); i { + switch v := v.(*PeeringTrustBundleDeleteResponse); i { case 0: return &v.state case 1: @@ -2879,7 +2989,7 @@ func file_proto_pbpeering_peering_proto_init() { } } file_proto_pbpeering_peering_proto_msgTypes[24].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GenerateTokenResponse); i { + switch v := v.(*GenerateTokenRequest); i { case 0: return &v.state case 1: @@ -2891,7 +3001,7 @@ func file_proto_pbpeering_peering_proto_init() { } } file_proto_pbpeering_peering_proto_msgTypes[25].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*EstablishRequest); i { + switch v := v.(*GenerateTokenResponse); i { case 0: return &v.state case 1: @@ -2903,7 +3013,7 @@ func file_proto_pbpeering_peering_proto_init() { } } file_proto_pbpeering_peering_proto_msgTypes[26].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*EstablishResponse); i { + switch v := v.(*EstablishRequest); i { case 0: return &v.state case 1: @@ -2915,7 +3025,7 @@ func file_proto_pbpeering_peering_proto_init() { } } file_proto_pbpeering_peering_proto_msgTypes[27].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*SecretsWriteRequest_GenerateTokenRequest); i { + switch v := v.(*EstablishResponse); i { case 0: return &v.state case 1: @@ -2927,7 +3037,7 @@ func file_proto_pbpeering_peering_proto_init() { } } file_proto_pbpeering_peering_proto_msgTypes[28].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*SecretsWriteRequest_ExchangeSecretRequest); i { + switch v := v.(*SecretsWriteRequest_GenerateTokenRequest); i { case 0: return &v.state case 1: @@ -2939,7 +3049,7 @@ func file_proto_pbpeering_peering_proto_init() { } } file_proto_pbpeering_peering_proto_msgTypes[29].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*SecretsWriteRequest_PromotePendingRequest); i { + switch v := v.(*SecretsWriteRequest_ExchangeSecretRequest); i { case 0: return &v.state case 1: @@ -2951,7 +3061,7 @@ func file_proto_pbpeering_peering_proto_init() { } } file_proto_pbpeering_peering_proto_msgTypes[30].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*SecretsWriteRequest_EstablishRequest); i { + switch v := v.(*SecretsWriteRequest_PromotePendingRequest); i { case 0: return &v.state case 1: @@ -2963,7 +3073,7 @@ func file_proto_pbpeering_peering_proto_init() { } } file_proto_pbpeering_peering_proto_msgTypes[31].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*PeeringSecrets_Establishment); i { + switch v := v.(*SecretsWriteRequest_EstablishRequest); i { case 0: return &v.state case 1: @@ -2975,6 +3085,18 @@ func file_proto_pbpeering_peering_proto_init() { } } file_proto_pbpeering_peering_proto_msgTypes[32].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*PeeringSecrets_Establishment); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_pbpeering_peering_proto_msgTypes[33].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*PeeringSecrets_Stream); i { case 0: return &v.state @@ -2999,7 +3121,7 @@ func file_proto_pbpeering_peering_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_proto_pbpeering_peering_proto_rawDesc, NumEnums: 1, - NumMessages: 37, + NumMessages: 38, NumExtensions: 0, NumServices: 1, }, diff --git a/proto/pbpeering/peering.proto b/proto/pbpeering/peering.proto index cc37c6041c..773d04c636 100644 --- a/proto/pbpeering/peering.proto +++ b/proto/pbpeering/peering.proto @@ -183,11 +183,10 @@ message Peering { // PeerServerAddresses contains all the the connection addresses for the remote peer. repeated string PeerServerAddresses = 10; - // ImportedServiceCount is the count of how many services are imported from this peering. - uint64 ImportedServiceCount = 13; - - // ExportedServiceCount is the count of how many services are exported to this peering. - uint64 ExportedServiceCount = 14; + // StreamStatus contains information computed on read based on the state of the stream. + // + // mog: func-to=StreamStatusToAPI func-from=StreamStatusFromAPI + StreamStatus StreamStatus = 13; // CreateIndex is the Raft index at which the Peering was created. // @gotags: bexpr:"-" @@ -198,6 +197,24 @@ message Peering { uint64 ModifyIndex = 12; } +// StreamStatus represents information about an active peering stream. +message StreamStatus { + // ImportedServices is the list of services imported from this peering. + repeated string ImportedServices = 1; + + // ExportedServices is the list of services exported to this peering. + repeated string ExportedServices = 2; + + // LastHeartbeat represents when the last heartbeat message was received. + google.protobuf.Timestamp LastHeartbeat = 3; + + // LastReceive represents when any message was last received, regardless of success or error. + google.protobuf.Timestamp LastReceive = 4; + + // LastSend represents when any message was last sent, regardless of success or error. + google.protobuf.Timestamp LastSend = 5; +} + // PeeringTrustBundle holds the trust information for validating requests from a peer. message PeeringTrustBundle { // TrustDomain is the domain for the bundle, example.com, foo.bar.gov for example. Note that this must not have a prefix such as "spiffe://". @@ -248,6 +265,7 @@ message PeeringListRequest { message PeeringListResponse { repeated Peering Peerings = 1; + uint64 Index = 2; } message PeeringWriteRequest { diff --git a/proto/pbpeerstream/convert.go b/proto/pbpeerstream/convert.go index b0df6c42aa..67ddb636fd 100644 --- a/proto/pbpeerstream/convert.go +++ b/proto/pbpeerstream/convert.go @@ -23,3 +23,15 @@ func (s *ExportedService) CheckServiceNodesToStruct() ([]structs.CheckServiceNod } return resp, nil } + +func ExportedServiceListFromStruct(e *structs.ExportedServiceList) *ExportedServiceList { + services := make([]string, 0, len(e.Services)) + + for _, s := range e.Services { + services = append(services, s.String()) + } + + return &ExportedServiceList{ + Services: services, + } +} diff --git a/proto/pbpeerstream/peerstream.pb.binary.go b/proto/pbpeerstream/peerstream.pb.binary.go index c9c599ae4c..04531399a1 100644 --- a/proto/pbpeerstream/peerstream.pb.binary.go +++ b/proto/pbpeerstream/peerstream.pb.binary.go @@ -87,6 +87,16 @@ func (msg *ExportedService) UnmarshalBinary(b []byte) error { return proto.Unmarshal(b, msg) } +// MarshalBinary implements encoding.BinaryMarshaler +func (msg *ExportedServiceList) MarshalBinary() ([]byte, error) { + return proto.Marshal(msg) +} + +// UnmarshalBinary implements encoding.BinaryUnmarshaler +func (msg *ExportedServiceList) UnmarshalBinary(b []byte) error { + return proto.Unmarshal(b, msg) +} + // MarshalBinary implements encoding.BinaryMarshaler func (msg *ExchangeSecretRequest) MarshalBinary() ([]byte, error) { return proto.Marshal(msg) diff --git a/proto/pbpeerstream/peerstream.pb.go b/proto/pbpeerstream/peerstream.pb.go index e3617ab0bf..18ef38fa5c 100644 --- a/proto/pbpeerstream/peerstream.pb.go +++ b/proto/pbpeerstream/peerstream.pb.go @@ -30,10 +30,6 @@ const ( Operation_OPERATION_UNSPECIFIED Operation = 0 // UPSERT represents a create or update event. Operation_OPERATION_UPSERT Operation = 1 - // DELETE indicates the resource should be deleted. - // In DELETE operations no Resource will be returned. - // Deletion by an importing peer must be done with the type URL and ID. - Operation_OPERATION_DELETE Operation = 2 ) // Enum value maps for Operation. @@ -41,12 +37,10 @@ var ( Operation_name = map[int32]string{ 0: "OPERATION_UNSPECIFIED", 1: "OPERATION_UPSERT", - 2: "OPERATION_DELETE", } Operation_value = map[string]int32{ "OPERATION_UNSPECIFIED": 0, "OPERATION_UPSERT": 1, - "OPERATION_DELETE": 2, } ) @@ -297,6 +291,55 @@ func (x *ExportedService) GetNodes() []*pbservice.CheckServiceNode { return nil } +// ExportedServiceList is one of the types of data returned via peer stream replication. +type ExportedServiceList struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // The identifiers for the services being exported. + Services []string `protobuf:"bytes,1,rep,name=Services,proto3" json:"Services,omitempty"` +} + +func (x *ExportedServiceList) Reset() { + *x = ExportedServiceList{} + if protoimpl.UnsafeEnabled { + mi := &file_proto_pbpeerstream_peerstream_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ExportedServiceList) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ExportedServiceList) ProtoMessage() {} + +func (x *ExportedServiceList) ProtoReflect() protoreflect.Message { + mi := &file_proto_pbpeerstream_peerstream_proto_msgTypes[3] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ExportedServiceList.ProtoReflect.Descriptor instead. +func (*ExportedServiceList) Descriptor() ([]byte, []int) { + return file_proto_pbpeerstream_peerstream_proto_rawDescGZIP(), []int{3} +} + +func (x *ExportedServiceList) GetServices() []string { + if x != nil { + return x.Services + } + return nil +} + type ExchangeSecretRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -312,7 +355,7 @@ type ExchangeSecretRequest struct { func (x *ExchangeSecretRequest) Reset() { *x = ExchangeSecretRequest{} if protoimpl.UnsafeEnabled { - mi := &file_proto_pbpeerstream_peerstream_proto_msgTypes[3] + mi := &file_proto_pbpeerstream_peerstream_proto_msgTypes[4] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -325,7 +368,7 @@ func (x *ExchangeSecretRequest) String() string { func (*ExchangeSecretRequest) ProtoMessage() {} func (x *ExchangeSecretRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_pbpeerstream_peerstream_proto_msgTypes[3] + mi := &file_proto_pbpeerstream_peerstream_proto_msgTypes[4] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -338,7 +381,7 @@ func (x *ExchangeSecretRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ExchangeSecretRequest.ProtoReflect.Descriptor instead. func (*ExchangeSecretRequest) Descriptor() ([]byte, []int) { - return file_proto_pbpeerstream_peerstream_proto_rawDescGZIP(), []int{3} + return file_proto_pbpeerstream_peerstream_proto_rawDescGZIP(), []int{4} } func (x *ExchangeSecretRequest) GetPeerID() string { @@ -368,7 +411,7 @@ type ExchangeSecretResponse struct { func (x *ExchangeSecretResponse) Reset() { *x = ExchangeSecretResponse{} if protoimpl.UnsafeEnabled { - mi := &file_proto_pbpeerstream_peerstream_proto_msgTypes[4] + mi := &file_proto_pbpeerstream_peerstream_proto_msgTypes[5] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -381,7 +424,7 @@ func (x *ExchangeSecretResponse) String() string { func (*ExchangeSecretResponse) ProtoMessage() {} func (x *ExchangeSecretResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_pbpeerstream_peerstream_proto_msgTypes[4] + mi := &file_proto_pbpeerstream_peerstream_proto_msgTypes[5] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -394,7 +437,7 @@ func (x *ExchangeSecretResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ExchangeSecretResponse.ProtoReflect.Descriptor instead. func (*ExchangeSecretResponse) Descriptor() ([]byte, []int) { - return file_proto_pbpeerstream_peerstream_proto_rawDescGZIP(), []int{4} + return file_proto_pbpeerstream_peerstream_proto_rawDescGZIP(), []int{5} } func (x *ExchangeSecretResponse) GetStreamSecret() string { @@ -420,7 +463,7 @@ type ReplicationMessage_Open struct { func (x *ReplicationMessage_Open) Reset() { *x = ReplicationMessage_Open{} if protoimpl.UnsafeEnabled { - mi := &file_proto_pbpeerstream_peerstream_proto_msgTypes[5] + mi := &file_proto_pbpeerstream_peerstream_proto_msgTypes[6] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -433,7 +476,7 @@ func (x *ReplicationMessage_Open) String() string { func (*ReplicationMessage_Open) ProtoMessage() {} func (x *ReplicationMessage_Open) ProtoReflect() protoreflect.Message { - mi := &file_proto_pbpeerstream_peerstream_proto_msgTypes[5] + mi := &file_proto_pbpeerstream_peerstream_proto_msgTypes[6] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -487,7 +530,7 @@ type ReplicationMessage_Request struct { func (x *ReplicationMessage_Request) Reset() { *x = ReplicationMessage_Request{} if protoimpl.UnsafeEnabled { - mi := &file_proto_pbpeerstream_peerstream_proto_msgTypes[6] + mi := &file_proto_pbpeerstream_peerstream_proto_msgTypes[7] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -500,7 +543,7 @@ func (x *ReplicationMessage_Request) String() string { func (*ReplicationMessage_Request) ProtoMessage() {} func (x *ReplicationMessage_Request) ProtoReflect() protoreflect.Message { - mi := &file_proto_pbpeerstream_peerstream_proto_msgTypes[6] + mi := &file_proto_pbpeerstream_peerstream_proto_msgTypes[7] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -566,7 +609,7 @@ type ReplicationMessage_Response struct { func (x *ReplicationMessage_Response) Reset() { *x = ReplicationMessage_Response{} if protoimpl.UnsafeEnabled { - mi := &file_proto_pbpeerstream_peerstream_proto_msgTypes[7] + mi := &file_proto_pbpeerstream_peerstream_proto_msgTypes[8] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -579,7 +622,7 @@ func (x *ReplicationMessage_Response) String() string { func (*ReplicationMessage_Response) ProtoMessage() {} func (x *ReplicationMessage_Response) ProtoReflect() protoreflect.Message { - mi := &file_proto_pbpeerstream_peerstream_proto_msgTypes[7] + mi := &file_proto_pbpeerstream_peerstream_proto_msgTypes[8] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -641,7 +684,7 @@ type ReplicationMessage_Terminated struct { func (x *ReplicationMessage_Terminated) Reset() { *x = ReplicationMessage_Terminated{} if protoimpl.UnsafeEnabled { - mi := &file_proto_pbpeerstream_peerstream_proto_msgTypes[8] + mi := &file_proto_pbpeerstream_peerstream_proto_msgTypes[9] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -654,7 +697,7 @@ func (x *ReplicationMessage_Terminated) String() string { func (*ReplicationMessage_Terminated) ProtoMessage() {} func (x *ReplicationMessage_Terminated) ProtoReflect() protoreflect.Message { - mi := &file_proto_pbpeerstream_peerstream_proto_msgTypes[8] + mi := &file_proto_pbpeerstream_peerstream_proto_msgTypes[9] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -680,7 +723,7 @@ type ReplicationMessage_Heartbeat struct { func (x *ReplicationMessage_Heartbeat) Reset() { *x = ReplicationMessage_Heartbeat{} if protoimpl.UnsafeEnabled { - mi := &file_proto_pbpeerstream_peerstream_proto_msgTypes[9] + mi := &file_proto_pbpeerstream_peerstream_proto_msgTypes[10] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -693,7 +736,7 @@ func (x *ReplicationMessage_Heartbeat) String() string { func (*ReplicationMessage_Heartbeat) ProtoMessage() {} func (x *ReplicationMessage_Heartbeat) ProtoReflect() protoreflect.Message { - mi := &file_proto_pbpeerstream_peerstream_proto_msgTypes[9] + mi := &file_proto_pbpeerstream_peerstream_proto_msgTypes[10] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -793,60 +836,62 @@ var file_proto_pbpeerstream_peerstream_proto_rawDesc = []byte{ 0x28, 0x0b, 0x32, 0x33, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2e, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x53, 0x65, 0x72, 0x76, - 0x69, 0x63, 0x65, 0x4e, 0x6f, 0x64, 0x65, 0x52, 0x05, 0x4e, 0x6f, 0x64, 0x65, 0x73, 0x22, 0x61, - 0x0a, 0x15, 0x45, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x50, 0x65, 0x65, 0x72, 0x49, - 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x50, 0x65, 0x65, 0x72, 0x49, 0x44, 0x12, - 0x30, 0x0a, 0x13, 0x45, 0x73, 0x74, 0x61, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x6d, 0x65, 0x6e, 0x74, - 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x13, 0x45, 0x73, - 0x74, 0x61, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x6d, 0x65, 0x6e, 0x74, 0x53, 0x65, 0x63, 0x72, 0x65, - 0x74, 0x22, 0x3c, 0x0a, 0x16, 0x45, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x53, 0x65, 0x63, - 0x72, 0x65, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x22, 0x0a, 0x0c, 0x53, - 0x74, 0x72, 0x65, 0x61, 0x6d, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x0c, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x2a, - 0x52, 0x0a, 0x09, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x19, 0x0a, 0x15, - 0x4f, 0x50, 0x45, 0x52, 0x41, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, - 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x14, 0x0a, 0x10, 0x4f, 0x50, 0x45, 0x52, 0x41, - 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x55, 0x50, 0x53, 0x45, 0x52, 0x54, 0x10, 0x01, 0x12, 0x14, 0x0a, - 0x10, 0x4f, 0x50, 0x45, 0x52, 0x41, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x44, 0x45, 0x4c, 0x45, 0x54, - 0x45, 0x10, 0x02, 0x32, 0xad, 0x02, 0x0a, 0x11, 0x50, 0x65, 0x65, 0x72, 0x53, 0x74, 0x72, 0x65, - 0x61, 0x6d, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x89, 0x01, 0x0a, 0x0f, 0x53, 0x74, - 0x72, 0x65, 0x61, 0x6d, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x12, 0x38, 0x2e, - 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, - 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x70, 0x65, 0x65, 0x72, 0x73, 0x74, - 0x72, 0x65, 0x61, 0x6d, 0x2e, 0x52, 0x65, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x1a, 0x38, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, - 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, - 0x6e, 0x61, 0x6c, 0x2e, 0x70, 0x65, 0x65, 0x72, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x2e, 0x52, - 0x65, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, - 0x65, 0x28, 0x01, 0x30, 0x01, 0x12, 0x8b, 0x01, 0x0a, 0x0e, 0x45, 0x78, 0x63, 0x68, 0x61, 0x6e, - 0x67, 0x65, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x12, 0x3b, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, - 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, - 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x70, 0x65, 0x65, 0x72, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x2e, - 0x45, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x3c, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, + 0x69, 0x63, 0x65, 0x4e, 0x6f, 0x64, 0x65, 0x52, 0x05, 0x4e, 0x6f, 0x64, 0x65, 0x73, 0x22, 0x31, + 0x0a, 0x13, 0x45, 0x78, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, + 0x65, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, + 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x08, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, + 0x73, 0x22, 0x61, 0x0a, 0x15, 0x45, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x53, 0x65, 0x63, + 0x72, 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x50, 0x65, + 0x65, 0x72, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x50, 0x65, 0x65, 0x72, + 0x49, 0x44, 0x12, 0x30, 0x0a, 0x13, 0x45, 0x73, 0x74, 0x61, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x6d, + 0x65, 0x6e, 0x74, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x13, 0x45, 0x73, 0x74, 0x61, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x6d, 0x65, 0x6e, 0x74, 0x53, 0x65, + 0x63, 0x72, 0x65, 0x74, 0x22, 0x3c, 0x0a, 0x16, 0x45, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, + 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x22, + 0x0a, 0x0c, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x53, 0x65, 0x63, 0x72, + 0x65, 0x74, 0x2a, 0x3c, 0x0a, 0x09, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, + 0x19, 0x0a, 0x15, 0x4f, 0x50, 0x45, 0x52, 0x41, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x55, 0x4e, 0x53, + 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x14, 0x0a, 0x10, 0x4f, 0x50, + 0x45, 0x52, 0x41, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x55, 0x50, 0x53, 0x45, 0x52, 0x54, 0x10, 0x01, + 0x32, 0xad, 0x02, 0x0a, 0x11, 0x50, 0x65, 0x65, 0x72, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x53, + 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x89, 0x01, 0x0a, 0x0f, 0x53, 0x74, 0x72, 0x65, 0x61, + 0x6d, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x12, 0x38, 0x2e, 0x68, 0x61, 0x73, + 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, + 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x70, 0x65, 0x65, 0x72, 0x73, 0x74, 0x72, 0x65, 0x61, + 0x6d, 0x2e, 0x52, 0x65, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4d, 0x65, 0x73, + 0x73, 0x61, 0x67, 0x65, 0x1a, 0x38, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, + 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, + 0x2e, 0x70, 0x65, 0x65, 0x72, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x2e, 0x52, 0x65, 0x70, 0x6c, + 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x28, 0x01, + 0x30, 0x01, 0x12, 0x8b, 0x01, 0x0a, 0x0e, 0x45, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x53, + 0x65, 0x63, 0x72, 0x65, 0x74, 0x12, 0x3b, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x70, 0x65, 0x65, 0x72, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x2e, 0x45, 0x78, 0x63, - 0x68, 0x61, 0x6e, 0x67, 0x65, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x42, 0x9f, 0x02, 0x0a, 0x28, 0x63, 0x6f, 0x6d, 0x2e, 0x68, 0x61, 0x73, 0x68, - 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, - 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x70, 0x65, 0x65, 0x72, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, - 0x42, 0x0f, 0x50, 0x65, 0x65, 0x72, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x50, 0x72, 0x6f, 0x74, - 0x6f, 0x50, 0x01, 0x5a, 0x2e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, - 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2f, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, - 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x70, 0x62, 0x70, 0x65, 0x65, 0x72, 0x73, 0x74, 0x72, - 0x65, 0x61, 0x6d, 0xa2, 0x02, 0x04, 0x48, 0x43, 0x49, 0x50, 0xaa, 0x02, 0x24, 0x48, 0x61, 0x73, - 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x43, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x49, 0x6e, - 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x50, 0x65, 0x65, 0x72, 0x73, 0x74, 0x72, 0x65, 0x61, - 0x6d, 0xca, 0x02, 0x24, 0x48, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x5c, 0x43, 0x6f, - 0x6e, 0x73, 0x75, 0x6c, 0x5c, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x5c, 0x50, 0x65, - 0x65, 0x72, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0xe2, 0x02, 0x30, 0x48, 0x61, 0x73, 0x68, 0x69, - 0x63, 0x6f, 0x72, 0x70, 0x5c, 0x43, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x5c, 0x49, 0x6e, 0x74, 0x65, - 0x72, 0x6e, 0x61, 0x6c, 0x5c, 0x50, 0x65, 0x65, 0x72, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x5c, - 0x47, 0x50, 0x42, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0xea, 0x02, 0x27, 0x48, 0x61, - 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x3a, 0x3a, 0x43, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x3a, - 0x3a, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x3a, 0x3a, 0x50, 0x65, 0x65, 0x72, 0x73, - 0x74, 0x72, 0x65, 0x61, 0x6d, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x68, 0x61, 0x6e, 0x67, 0x65, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x3c, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, + 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x70, + 0x65, 0x65, 0x72, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x2e, 0x45, 0x78, 0x63, 0x68, 0x61, 0x6e, + 0x67, 0x65, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x42, 0x9f, 0x02, 0x0a, 0x28, 0x63, 0x6f, 0x6d, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, + 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, + 0x61, 0x6c, 0x2e, 0x70, 0x65, 0x65, 0x72, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x42, 0x0f, 0x50, + 0x65, 0x65, 0x72, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, + 0x5a, 0x2e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x68, 0x61, 0x73, + 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2f, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2f, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x2f, 0x70, 0x62, 0x70, 0x65, 0x65, 0x72, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, + 0xa2, 0x02, 0x04, 0x48, 0x43, 0x49, 0x50, 0xaa, 0x02, 0x24, 0x48, 0x61, 0x73, 0x68, 0x69, 0x63, + 0x6f, 0x72, 0x70, 0x2e, 0x43, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x49, 0x6e, 0x74, 0x65, 0x72, + 0x6e, 0x61, 0x6c, 0x2e, 0x50, 0x65, 0x65, 0x72, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0xca, 0x02, + 0x24, 0x48, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x5c, 0x43, 0x6f, 0x6e, 0x73, 0x75, + 0x6c, 0x5c, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x5c, 0x50, 0x65, 0x65, 0x72, 0x73, + 0x74, 0x72, 0x65, 0x61, 0x6d, 0xe2, 0x02, 0x30, 0x48, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, + 0x70, 0x5c, 0x43, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x5c, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, + 0x6c, 0x5c, 0x50, 0x65, 0x65, 0x72, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x5c, 0x47, 0x50, 0x42, + 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0xea, 0x02, 0x27, 0x48, 0x61, 0x73, 0x68, 0x69, + 0x63, 0x6f, 0x72, 0x70, 0x3a, 0x3a, 0x43, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x3a, 0x3a, 0x49, 0x6e, + 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x3a, 0x3a, 0x50, 0x65, 0x65, 0x72, 0x73, 0x74, 0x72, 0x65, + 0x61, 0x6d, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -862,37 +907,38 @@ func file_proto_pbpeerstream_peerstream_proto_rawDescGZIP() []byte { } var file_proto_pbpeerstream_peerstream_proto_enumTypes = make([]protoimpl.EnumInfo, 1) -var file_proto_pbpeerstream_peerstream_proto_msgTypes = make([]protoimpl.MessageInfo, 10) +var file_proto_pbpeerstream_peerstream_proto_msgTypes = make([]protoimpl.MessageInfo, 11) var file_proto_pbpeerstream_peerstream_proto_goTypes = []interface{}{ (Operation)(0), // 0: hashicorp.consul.internal.peerstream.Operation (*ReplicationMessage)(nil), // 1: hashicorp.consul.internal.peerstream.ReplicationMessage (*LeaderAddress)(nil), // 2: hashicorp.consul.internal.peerstream.LeaderAddress (*ExportedService)(nil), // 3: hashicorp.consul.internal.peerstream.ExportedService - (*ExchangeSecretRequest)(nil), // 4: hashicorp.consul.internal.peerstream.ExchangeSecretRequest - (*ExchangeSecretResponse)(nil), // 5: hashicorp.consul.internal.peerstream.ExchangeSecretResponse - (*ReplicationMessage_Open)(nil), // 6: hashicorp.consul.internal.peerstream.ReplicationMessage.Open - (*ReplicationMessage_Request)(nil), // 7: hashicorp.consul.internal.peerstream.ReplicationMessage.Request - (*ReplicationMessage_Response)(nil), // 8: hashicorp.consul.internal.peerstream.ReplicationMessage.Response - (*ReplicationMessage_Terminated)(nil), // 9: hashicorp.consul.internal.peerstream.ReplicationMessage.Terminated - (*ReplicationMessage_Heartbeat)(nil), // 10: hashicorp.consul.internal.peerstream.ReplicationMessage.Heartbeat - (*pbservice.CheckServiceNode)(nil), // 11: hashicorp.consul.internal.service.CheckServiceNode - (*pbstatus.Status)(nil), // 12: hashicorp.consul.internal.status.Status - (*anypb.Any)(nil), // 13: google.protobuf.Any + (*ExportedServiceList)(nil), // 4: hashicorp.consul.internal.peerstream.ExportedServiceList + (*ExchangeSecretRequest)(nil), // 5: hashicorp.consul.internal.peerstream.ExchangeSecretRequest + (*ExchangeSecretResponse)(nil), // 6: hashicorp.consul.internal.peerstream.ExchangeSecretResponse + (*ReplicationMessage_Open)(nil), // 7: hashicorp.consul.internal.peerstream.ReplicationMessage.Open + (*ReplicationMessage_Request)(nil), // 8: hashicorp.consul.internal.peerstream.ReplicationMessage.Request + (*ReplicationMessage_Response)(nil), // 9: hashicorp.consul.internal.peerstream.ReplicationMessage.Response + (*ReplicationMessage_Terminated)(nil), // 10: hashicorp.consul.internal.peerstream.ReplicationMessage.Terminated + (*ReplicationMessage_Heartbeat)(nil), // 11: hashicorp.consul.internal.peerstream.ReplicationMessage.Heartbeat + (*pbservice.CheckServiceNode)(nil), // 12: hashicorp.consul.internal.service.CheckServiceNode + (*pbstatus.Status)(nil), // 13: hashicorp.consul.internal.status.Status + (*anypb.Any)(nil), // 14: google.protobuf.Any } var file_proto_pbpeerstream_peerstream_proto_depIdxs = []int32{ - 6, // 0: hashicorp.consul.internal.peerstream.ReplicationMessage.open:type_name -> hashicorp.consul.internal.peerstream.ReplicationMessage.Open - 7, // 1: hashicorp.consul.internal.peerstream.ReplicationMessage.request:type_name -> hashicorp.consul.internal.peerstream.ReplicationMessage.Request - 8, // 2: hashicorp.consul.internal.peerstream.ReplicationMessage.response:type_name -> hashicorp.consul.internal.peerstream.ReplicationMessage.Response - 9, // 3: hashicorp.consul.internal.peerstream.ReplicationMessage.terminated:type_name -> hashicorp.consul.internal.peerstream.ReplicationMessage.Terminated - 10, // 4: hashicorp.consul.internal.peerstream.ReplicationMessage.heartbeat:type_name -> hashicorp.consul.internal.peerstream.ReplicationMessage.Heartbeat - 11, // 5: hashicorp.consul.internal.peerstream.ExportedService.Nodes:type_name -> hashicorp.consul.internal.service.CheckServiceNode - 12, // 6: hashicorp.consul.internal.peerstream.ReplicationMessage.Request.Error:type_name -> hashicorp.consul.internal.status.Status - 13, // 7: hashicorp.consul.internal.peerstream.ReplicationMessage.Response.Resource:type_name -> google.protobuf.Any + 7, // 0: hashicorp.consul.internal.peerstream.ReplicationMessage.open:type_name -> hashicorp.consul.internal.peerstream.ReplicationMessage.Open + 8, // 1: hashicorp.consul.internal.peerstream.ReplicationMessage.request:type_name -> hashicorp.consul.internal.peerstream.ReplicationMessage.Request + 9, // 2: hashicorp.consul.internal.peerstream.ReplicationMessage.response:type_name -> hashicorp.consul.internal.peerstream.ReplicationMessage.Response + 10, // 3: hashicorp.consul.internal.peerstream.ReplicationMessage.terminated:type_name -> hashicorp.consul.internal.peerstream.ReplicationMessage.Terminated + 11, // 4: hashicorp.consul.internal.peerstream.ReplicationMessage.heartbeat:type_name -> hashicorp.consul.internal.peerstream.ReplicationMessage.Heartbeat + 12, // 5: hashicorp.consul.internal.peerstream.ExportedService.Nodes:type_name -> hashicorp.consul.internal.service.CheckServiceNode + 13, // 6: hashicorp.consul.internal.peerstream.ReplicationMessage.Request.Error:type_name -> hashicorp.consul.internal.status.Status + 14, // 7: hashicorp.consul.internal.peerstream.ReplicationMessage.Response.Resource:type_name -> google.protobuf.Any 0, // 8: hashicorp.consul.internal.peerstream.ReplicationMessage.Response.operation:type_name -> hashicorp.consul.internal.peerstream.Operation 1, // 9: hashicorp.consul.internal.peerstream.PeerStreamService.StreamResources:input_type -> hashicorp.consul.internal.peerstream.ReplicationMessage - 4, // 10: hashicorp.consul.internal.peerstream.PeerStreamService.ExchangeSecret:input_type -> hashicorp.consul.internal.peerstream.ExchangeSecretRequest + 5, // 10: hashicorp.consul.internal.peerstream.PeerStreamService.ExchangeSecret:input_type -> hashicorp.consul.internal.peerstream.ExchangeSecretRequest 1, // 11: hashicorp.consul.internal.peerstream.PeerStreamService.StreamResources:output_type -> hashicorp.consul.internal.peerstream.ReplicationMessage - 5, // 12: hashicorp.consul.internal.peerstream.PeerStreamService.ExchangeSecret:output_type -> hashicorp.consul.internal.peerstream.ExchangeSecretResponse + 6, // 12: hashicorp.consul.internal.peerstream.PeerStreamService.ExchangeSecret:output_type -> hashicorp.consul.internal.peerstream.ExchangeSecretResponse 11, // [11:13] is the sub-list for method output_type 9, // [9:11] is the sub-list for method input_type 9, // [9:9] is the sub-list for extension type_name @@ -943,7 +989,7 @@ func file_proto_pbpeerstream_peerstream_proto_init() { } } file_proto_pbpeerstream_peerstream_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ExchangeSecretRequest); i { + switch v := v.(*ExportedServiceList); i { case 0: return &v.state case 1: @@ -955,7 +1001,7 @@ func file_proto_pbpeerstream_peerstream_proto_init() { } } file_proto_pbpeerstream_peerstream_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ExchangeSecretResponse); i { + switch v := v.(*ExchangeSecretRequest); i { case 0: return &v.state case 1: @@ -967,7 +1013,7 @@ func file_proto_pbpeerstream_peerstream_proto_init() { } } file_proto_pbpeerstream_peerstream_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ReplicationMessage_Open); i { + switch v := v.(*ExchangeSecretResponse); i { case 0: return &v.state case 1: @@ -979,7 +1025,7 @@ func file_proto_pbpeerstream_peerstream_proto_init() { } } file_proto_pbpeerstream_peerstream_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ReplicationMessage_Request); i { + switch v := v.(*ReplicationMessage_Open); i { case 0: return &v.state case 1: @@ -991,7 +1037,7 @@ func file_proto_pbpeerstream_peerstream_proto_init() { } } file_proto_pbpeerstream_peerstream_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ReplicationMessage_Response); i { + switch v := v.(*ReplicationMessage_Request); i { case 0: return &v.state case 1: @@ -1003,7 +1049,7 @@ func file_proto_pbpeerstream_peerstream_proto_init() { } } file_proto_pbpeerstream_peerstream_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ReplicationMessage_Terminated); i { + switch v := v.(*ReplicationMessage_Response); i { case 0: return &v.state case 1: @@ -1015,6 +1061,18 @@ func file_proto_pbpeerstream_peerstream_proto_init() { } } file_proto_pbpeerstream_peerstream_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ReplicationMessage_Terminated); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_proto_pbpeerstream_peerstream_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*ReplicationMessage_Heartbeat); i { case 0: return &v.state @@ -1040,7 +1098,7 @@ func file_proto_pbpeerstream_peerstream_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_proto_pbpeerstream_peerstream_proto_rawDesc, NumEnums: 1, - NumMessages: 10, + NumMessages: 11, NumExtensions: 0, NumServices: 1, }, diff --git a/proto/pbpeerstream/peerstream.proto b/proto/pbpeerstream/peerstream.proto index 6cb2df439e..914774c797 100644 --- a/proto/pbpeerstream/peerstream.proto +++ b/proto/pbpeerstream/peerstream.proto @@ -96,11 +96,6 @@ enum Operation { // UPSERT represents a create or update event. OPERATION_UPSERT = 1; - - // DELETE indicates the resource should be deleted. - // In DELETE operations no Resource will be returned. - // Deletion by an importing peer must be done with the type URL and ID. - OPERATION_DELETE = 2; } // LeaderAddress is sent when the peering service runs on a consul node @@ -115,6 +110,12 @@ message ExportedService { repeated hashicorp.consul.internal.service.CheckServiceNode Nodes = 1; } +// ExportedServiceList is one of the types of data returned via peer stream replication. +message ExportedServiceList { + // The identifiers for the services being exported. + repeated string Services = 1; +} + message ExchangeSecretRequest { // PeerID is the ID of the peering, as determined by the cluster that generated the // peering token. diff --git a/proto/pbpeerstream/types.go b/proto/pbpeerstream/types.go index 4bf114c0ed..b492caf2b0 100644 --- a/proto/pbpeerstream/types.go +++ b/proto/pbpeerstream/types.go @@ -4,13 +4,14 @@ const ( apiTypePrefix = "type.googleapis.com/" TypeURLExportedService = apiTypePrefix + "hashicorp.consul.internal.peerstream.ExportedService" + TypeURLExportedServiceList = apiTypePrefix + "hashicorp.consul.internal.peerstream.ExportedServiceList" TypeURLPeeringTrustBundle = apiTypePrefix + "hashicorp.consul.internal.peering.PeeringTrustBundle" TypeURLPeeringServerAddresses = apiTypePrefix + "hashicorp.consul.internal.peering.PeeringServerAddresses" ) func KnownTypeURL(s string) bool { switch s { - case TypeURLExportedService, TypeURLPeeringTrustBundle, TypeURLPeeringServerAddresses: + case TypeURLExportedService, TypeURLExportedServiceList, TypeURLPeeringTrustBundle, TypeURLPeeringServerAddresses: return true } return false diff --git a/sdk/testutil/server.go b/sdk/testutil/server.go index a657919adc..928b84299b 100644 --- a/sdk/testutil/server.go +++ b/sdk/testutil/server.go @@ -52,6 +52,7 @@ type TestPortConfig struct { SerfWan int `json:"serf_wan,omitempty"` Server int `json:"server,omitempty"` GRPC int `json:"grpc,omitempty"` + GRPCTLS int `json:"grpc_tls,omitempty"` ProxyMinPort int `json:"proxy_min_port,omitempty"` ProxyMaxPort int `json:"proxy_max_port,omitempty"` } @@ -156,7 +157,7 @@ func defaultServerConfig(t TestingTB) *TestServerConfig { panic(err) } - ports := freeport.GetN(t, 7) + ports := freeport.GetN(t, 8) logBuffer := NewLogBuffer(t) @@ -180,6 +181,7 @@ func defaultServerConfig(t TestingTB) *TestServerConfig { SerfWan: ports[4], Server: ports[5], GRPC: ports[6], + GRPCTLS: ports[7], }, ReadyTimeout: 10 * time.Second, StopTimeout: 10 * time.Second, @@ -224,11 +226,12 @@ type TestServer struct { cmd *exec.Cmd Config *TestServerConfig - HTTPAddr string - HTTPSAddr string - LANAddr string - WANAddr string - GRPCAddr string + HTTPAddr string + HTTPSAddr string + LANAddr string + WANAddr string + GRPCAddr string + GRPCTLSAddr string HTTPClient *http.Client @@ -302,11 +305,12 @@ func NewTestServerConfigT(t TestingTB, cb ServerConfigCallback) (*TestServer, er Config: cfg, cmd: cmd, - HTTPAddr: httpAddr, - HTTPSAddr: fmt.Sprintf("127.0.0.1:%d", cfg.Ports.HTTPS), - LANAddr: fmt.Sprintf("127.0.0.1:%d", cfg.Ports.SerfLan), - WANAddr: fmt.Sprintf("127.0.0.1:%d", cfg.Ports.SerfWan), - GRPCAddr: fmt.Sprintf("127.0.0.1:%d", cfg.Ports.GRPC), + HTTPAddr: httpAddr, + HTTPSAddr: fmt.Sprintf("127.0.0.1:%d", cfg.Ports.HTTPS), + LANAddr: fmt.Sprintf("127.0.0.1:%d", cfg.Ports.SerfLan), + WANAddr: fmt.Sprintf("127.0.0.1:%d", cfg.Ports.SerfWan), + GRPCAddr: fmt.Sprintf("127.0.0.1:%d", cfg.Ports.GRPC), + GRPCTLSAddr: fmt.Sprintf("127.0.0.1:%d", cfg.Ports.GRPCTLS), HTTPClient: client, diff --git a/test/integration/connect/envoy/case-cfg-resolver-cluster-peering-failover/alpha/config_entries.hcl b/test/integration/connect/envoy/case-cfg-resolver-cluster-peering-failover/alpha/config_entries.hcl index 64d0117020..e1f1178887 100644 --- a/test/integration/connect/envoy/case-cfg-resolver-cluster-peering-failover/alpha/config_entries.hcl +++ b/test/integration/connect/envoy/case-cfg-resolver-cluster-peering-failover/alpha/config_entries.hcl @@ -16,7 +16,7 @@ config_entries { name = "s2" consumers = [ { - peer_name = "alpha-to-primary" + peer = "alpha-to-primary" } ] } diff --git a/test/integration/connect/envoy/case-cfg-router-features/config_entries.hcl b/test/integration/connect/envoy/case-cfg-router-features/config_entries.hcl index 5eaafeda96..8009882072 100644 --- a/test/integration/connect/envoy/case-cfg-router-features/config_entries.hcl +++ b/test/integration/connect/envoy/case-cfg-router-features/config_entries.hcl @@ -68,6 +68,7 @@ config_entries { destination { service_subset = "v2" retry_on_connect_failure = true # TODO: test + retry_on = ["reset"] # TODO: test retry_on_status_codes = [500, 512] # TODO: test } }, diff --git a/test/integration/connect/envoy/case-cfg-splitter-cluster-peering/alpha/base.hcl b/test/integration/connect/envoy/case-cfg-splitter-cluster-peering/alpha/base.hcl new file mode 100644 index 0000000000..f81ab0edd6 --- /dev/null +++ b/test/integration/connect/envoy/case-cfg-splitter-cluster-peering/alpha/base.hcl @@ -0,0 +1,5 @@ +primary_datacenter = "alpha" +log_level = "trace" +peering { + enabled = true +} diff --git a/test/integration/connect/envoy/case-cfg-splitter-cluster-peering/alpha/config_entries.hcl b/test/integration/connect/envoy/case-cfg-splitter-cluster-peering/alpha/config_entries.hcl new file mode 100644 index 0000000000..64d0117020 --- /dev/null +++ b/test/integration/connect/envoy/case-cfg-splitter-cluster-peering/alpha/config_entries.hcl @@ -0,0 +1,26 @@ +config_entries { + bootstrap = [ + { + kind = "proxy-defaults" + name = "global" + + config { + protocol = "tcp" + } + }, + { + kind = "exported-services" + name = "default" + services = [ + { + name = "s2" + consumers = [ + { + peer_name = "alpha-to-primary" + } + ] + } + ] + } + ] +} diff --git a/test/integration/connect/envoy/case-cfg-splitter-cluster-peering/alpha/service_gateway.hcl b/test/integration/connect/envoy/case-cfg-splitter-cluster-peering/alpha/service_gateway.hcl new file mode 100644 index 0000000000..bcdcb2e8b3 --- /dev/null +++ b/test/integration/connect/envoy/case-cfg-splitter-cluster-peering/alpha/service_gateway.hcl @@ -0,0 +1,5 @@ +services { + name = "mesh-gateway" + kind = "mesh-gateway" + port = 4432 +} diff --git a/test/integration/connect/envoy/case-cfg-splitter-cluster-peering/alpha/service_s1.hcl b/test/integration/connect/envoy/case-cfg-splitter-cluster-peering/alpha/service_s1.hcl new file mode 100644 index 0000000000..e97ec23666 --- /dev/null +++ b/test/integration/connect/envoy/case-cfg-splitter-cluster-peering/alpha/service_s1.hcl @@ -0,0 +1 @@ +# We don't want an s1 service in this peer diff --git a/test/integration/connect/envoy/case-cfg-splitter-cluster-peering/alpha/service_s2.hcl b/test/integration/connect/envoy/case-cfg-splitter-cluster-peering/alpha/service_s2.hcl new file mode 100644 index 0000000000..14bf414fdc --- /dev/null +++ b/test/integration/connect/envoy/case-cfg-splitter-cluster-peering/alpha/service_s2.hcl @@ -0,0 +1,10 @@ +services { + name = "s2" + port = 8181 + checks = [] + connect { + sidecar_service { + checks = [] + } + } +} diff --git a/test/integration/connect/envoy/case-cfg-splitter-cluster-peering/alpha/setup.sh b/test/integration/connect/envoy/case-cfg-splitter-cluster-peering/alpha/setup.sh new file mode 100644 index 0000000000..820506ea9b --- /dev/null +++ b/test/integration/connect/envoy/case-cfg-splitter-cluster-peering/alpha/setup.sh @@ -0,0 +1,11 @@ +#!/bin/bash + +set -euo pipefail + +register_services alpha + +gen_envoy_bootstrap s2 19002 alpha +gen_envoy_bootstrap mesh-gateway 19003 alpha true + +wait_for_config_entry proxy-defaults global alpha +wait_for_config_entry exported-services default alpha diff --git a/test/integration/connect/envoy/case-cfg-splitter-cluster-peering/alpha/verify.bats b/test/integration/connect/envoy/case-cfg-splitter-cluster-peering/alpha/verify.bats new file mode 100644 index 0000000000..d2229b2974 --- /dev/null +++ b/test/integration/connect/envoy/case-cfg-splitter-cluster-peering/alpha/verify.bats @@ -0,0 +1,27 @@ +#!/usr/bin/env bats + +load helpers + +@test "s2 proxy is running correct version" { + assert_envoy_version 19002 +} + +@test "s2 proxy admin is up on :19002" { + retry_default curl -f -s localhost:19002/stats -o /dev/null +} + +@test "gateway-alpha proxy admin is up on :19003" { + retry_default curl -f -s localhost:19003/stats -o /dev/null +} + +@test "s2 proxy listener should be up and have right cert" { + assert_proxy_presents_cert_uri localhost:21000 s2 alpha +} + +@test "s2 proxy should be healthy" { + assert_service_has_healthy_instances s2 1 alpha +} + +@test "gateway-alpha should be up and listening" { + retry_long nc -z consul-alpha-client:4432 +} diff --git a/test/integration/connect/envoy/case-cfg-splitter-cluster-peering/bind.hcl b/test/integration/connect/envoy/case-cfg-splitter-cluster-peering/bind.hcl new file mode 100644 index 0000000000..f54393f03e --- /dev/null +++ b/test/integration/connect/envoy/case-cfg-splitter-cluster-peering/bind.hcl @@ -0,0 +1,2 @@ +bind_addr = "0.0.0.0" +advertise_addr = "{{ GetInterfaceIP \"eth0\" }}" \ No newline at end of file diff --git a/test/integration/connect/envoy/case-cfg-splitter-cluster-peering/capture.sh b/test/integration/connect/envoy/case-cfg-splitter-cluster-peering/capture.sh new file mode 100644 index 0000000000..ab90eb425a --- /dev/null +++ b/test/integration/connect/envoy/case-cfg-splitter-cluster-peering/capture.sh @@ -0,0 +1,6 @@ +#!/bin/bash + +snapshot_envoy_admin localhost:19000 s1 primary || true +snapshot_envoy_admin localhost:19001 s2 primary || true +snapshot_envoy_admin localhost:19002 s2 alpha || true +snapshot_envoy_admin localhost:19003 mesh-gateway alpha || true diff --git a/test/integration/connect/envoy/case-cfg-splitter-cluster-peering/primary/base.hcl b/test/integration/connect/envoy/case-cfg-splitter-cluster-peering/primary/base.hcl new file mode 100644 index 0000000000..c1e134d5a2 --- /dev/null +++ b/test/integration/connect/envoy/case-cfg-splitter-cluster-peering/primary/base.hcl @@ -0,0 +1,3 @@ +peering { + enabled = true +} diff --git a/test/integration/connect/envoy/case-cfg-splitter-cluster-peering/primary/config_entries.hcl b/test/integration/connect/envoy/case-cfg-splitter-cluster-peering/primary/config_entries.hcl new file mode 100644 index 0000000000..a3970b0548 --- /dev/null +++ b/test/integration/connect/envoy/case-cfg-splitter-cluster-peering/primary/config_entries.hcl @@ -0,0 +1,53 @@ +config_entries { + bootstrap { + kind = "proxy-defaults" + name = "global" + + config { + protocol = "http" + } + } + + bootstrap { + kind = "service-splitter" + name = "split-s2" + splits = [ + { + Weight = 50 + Service = "local-s2" + ResponseHeaders { + Set { + "x-test-split" = "primary" + } + } + }, + { + Weight = 50 + Service = "peer-s2" + ResponseHeaders { + Set { + "x-test-split" = "alpha" + } + } + }, + ] + } + + bootstrap { + kind = "service-resolver" + name = "local-s2" + redirect = { + service = "s2" + } + } + + bootstrap { + kind = "service-resolver" + name = "peer-s2" + + redirect = { + service = "s2" + peer = "primary-to-alpha" + } + } +} diff --git a/test/integration/connect/envoy/case-cfg-splitter-cluster-peering/primary/service_s1.hcl b/test/integration/connect/envoy/case-cfg-splitter-cluster-peering/primary/service_s1.hcl new file mode 100644 index 0000000000..3709b91364 --- /dev/null +++ b/test/integration/connect/envoy/case-cfg-splitter-cluster-peering/primary/service_s1.hcl @@ -0,0 +1,22 @@ +services { + name = "s1" + port = 8080 + checks = [] + connect { + sidecar_service { + checks = [] + proxy { + upstreams = [ + { + destination_name = "split-s2" + local_bind_port = 5000 + }, + { + destination_name = "peer-s2" + local_bind_port = 5001 + } + ] + } + } + } +} diff --git a/test/integration/connect/envoy/case-cfg-splitter-cluster-peering/primary/service_s2.hcl b/test/integration/connect/envoy/case-cfg-splitter-cluster-peering/primary/service_s2.hcl new file mode 100644 index 0000000000..14bf414fdc --- /dev/null +++ b/test/integration/connect/envoy/case-cfg-splitter-cluster-peering/primary/service_s2.hcl @@ -0,0 +1,10 @@ +services { + name = "s2" + port = 8181 + checks = [] + connect { + sidecar_service { + checks = [] + } + } +} diff --git a/test/integration/connect/envoy/case-cfg-splitter-cluster-peering/primary/setup.sh b/test/integration/connect/envoy/case-cfg-splitter-cluster-peering/primary/setup.sh new file mode 100644 index 0000000000..c65cc31e49 --- /dev/null +++ b/test/integration/connect/envoy/case-cfg-splitter-cluster-peering/primary/setup.sh @@ -0,0 +1,10 @@ +#!/bin/bash + +set -euo pipefail + +register_services primary + +gen_envoy_bootstrap s1 19000 primary +gen_envoy_bootstrap s2 19001 primary + +wait_for_config_entry proxy-defaults global diff --git a/test/integration/connect/envoy/case-cfg-splitter-cluster-peering/primary/verify.bats b/test/integration/connect/envoy/case-cfg-splitter-cluster-peering/primary/verify.bats new file mode 100644 index 0000000000..718dac6e7c --- /dev/null +++ b/test/integration/connect/envoy/case-cfg-splitter-cluster-peering/primary/verify.bats @@ -0,0 +1,74 @@ +#!/usr/bin/env bats + +load helpers + +@test "s1 proxy is running correct version" { + assert_envoy_version 19000 +} + +@test "s1 proxy admin is up" { + retry_default curl -f -s localhost:19000/stats -o /dev/null +} + +@test "s2 proxy admin is up" { + retry_default curl -f -s localhost:19001/stats -o /dev/null +} + +@test "s1 proxy listener should be up and have right cert" { + assert_proxy_presents_cert_uri localhost:21000 s1 +} + +@test "s2 proxies should be healthy in primary" { + assert_service_has_healthy_instances s2 1 primary +} + +@test "s2 proxies should be healthy in alpha" { + assert_service_has_healthy_instances s2 1 alpha +} + +@test "gateway-alpha should be up and listening" { + retry_long nc -z consul-alpha-client:4432 +} + +@test "peer the two clusters together" { + create_peering primary alpha +} + +@test "s2 alpha proxies should be healthy in primary" { + assert_service_has_healthy_instances s2 1 primary "" "" primary-to-alpha +} + +@test "s1 upstream should have healthy endpoints for s2 primary and alpha" { + assert_upstream_has_endpoints_in_status 127.0.0.1:19000 s2.default.primary.internal HEALTHY 1 + assert_upstream_has_endpoints_in_status 127.0.0.1:19000 s2.default.primary-to-alpha.external HEALTHY 1 +} + +@test "s1 upstream should be split between peer and local dc" { + retry_long assert_url_header "http://127.0.0.1:5000/" "x-test-split" "primary" + [ "$status" -eq 0 ] + retry_long assert_url_header "http://127.0.0.1:5000/" "x-test-split" "alpha" + [ "$status" -eq 0 ] + retry_long assert_expected_fortio_name s2 127.0.0.1 5000 + retry_long assert_expected_fortio_name s2-alpha 127.0.0.1 5000 +} + +@test "s1 upstream made 2 connections to primary s2 split" { + retry_long assert_envoy_metric_at_least 127.0.0.1:19000 "cluster.s2.default.primary.internal.*upstream_rq_total" 1 +} + +@test "s1 upstream made 2 connections to alpha s2 split" { + retry_long assert_envoy_metric_at_least 127.0.0.1:19000 "cluster.s2.default.primary-to-alpha.external.*upstream_rq_total" 1 +} + +@test "reset envoy statistics" { + reset_envoy_metrics 127.0.0.1:19000 + retry_long assert_envoy_metric 127.0.0.1:19000 "cluster.s2.default.primary-to-alpha.external.*upstream_rq_total" 0 +} + +@test "s1 upstream should be able to connect to s2 via peer-s2" { + assert_expected_fortio_name s2-alpha 127.0.0.1 5001 +} + +@test "s1 upstream made 1 connection to s2 via peer-s2" { + retry_long assert_envoy_metric_at_least 127.0.0.1:19000 "http.upstream.peer-s2.default.default.primary.rq_total" 1 +} diff --git a/test/integration/connect/envoy/case-cfg-splitter-cluster-peering/vars.sh b/test/integration/connect/envoy/case-cfg-splitter-cluster-peering/vars.sh new file mode 100644 index 0000000000..6a07e33a4a --- /dev/null +++ b/test/integration/connect/envoy/case-cfg-splitter-cluster-peering/vars.sh @@ -0,0 +1,4 @@ +#!/bin/bash + +export REQUIRED_SERVICES="s1 s1-sidecar-proxy s2 s2-sidecar-proxy s2-alpha s2-sidecar-proxy-alpha gateway-alpha" +export REQUIRE_PEERS=1 diff --git a/test/integration/connect/envoy/case-cfg-splitter-peering-ingress-gateways/alpha/base.hcl b/test/integration/connect/envoy/case-cfg-splitter-peering-ingress-gateways/alpha/base.hcl new file mode 100644 index 0000000000..f81ab0edd6 --- /dev/null +++ b/test/integration/connect/envoy/case-cfg-splitter-peering-ingress-gateways/alpha/base.hcl @@ -0,0 +1,5 @@ +primary_datacenter = "alpha" +log_level = "trace" +peering { + enabled = true +} diff --git a/test/integration/connect/envoy/case-cfg-splitter-peering-ingress-gateways/alpha/config_entries.hcl b/test/integration/connect/envoy/case-cfg-splitter-peering-ingress-gateways/alpha/config_entries.hcl new file mode 100644 index 0000000000..6c186ecae0 --- /dev/null +++ b/test/integration/connect/envoy/case-cfg-splitter-peering-ingress-gateways/alpha/config_entries.hcl @@ -0,0 +1,34 @@ +config_entries { + bootstrap = [ + { + kind = "proxy-defaults" + name = "global" + + config { + protocol = "tcp" + } + }, + { + kind = "exported-services" + name = "default" + services = [ + { + name = "s1" + consumers = [ + { + peer_name = "alpha-to-primary" + } + ] + }, + { + name = "s2" + consumers = [ + { + peer_name = "alpha-to-primary" + } + ] + } + ] + } + ] +} diff --git a/test/integration/connect/envoy/case-cfg-splitter-peering-ingress-gateways/alpha/service_gateway.hcl b/test/integration/connect/envoy/case-cfg-splitter-peering-ingress-gateways/alpha/service_gateway.hcl new file mode 100644 index 0000000000..bcdcb2e8b3 --- /dev/null +++ b/test/integration/connect/envoy/case-cfg-splitter-peering-ingress-gateways/alpha/service_gateway.hcl @@ -0,0 +1,5 @@ +services { + name = "mesh-gateway" + kind = "mesh-gateway" + port = 4432 +} diff --git a/test/integration/connect/envoy/case-cfg-splitter-peering-ingress-gateways/alpha/service_s1.hcl b/test/integration/connect/envoy/case-cfg-splitter-peering-ingress-gateways/alpha/service_s1.hcl new file mode 100644 index 0000000000..99fbc26eed --- /dev/null +++ b/test/integration/connect/envoy/case-cfg-splitter-peering-ingress-gateways/alpha/service_s1.hcl @@ -0,0 +1,10 @@ +services { + name = "s1" + port = 8080 + checks = [] + connect { + sidecar_service { + checks = [] + } + } +} diff --git a/test/integration/connect/envoy/case-cfg-splitter-peering-ingress-gateways/alpha/service_s2.hcl b/test/integration/connect/envoy/case-cfg-splitter-peering-ingress-gateways/alpha/service_s2.hcl new file mode 100644 index 0000000000..14bf414fdc --- /dev/null +++ b/test/integration/connect/envoy/case-cfg-splitter-peering-ingress-gateways/alpha/service_s2.hcl @@ -0,0 +1,10 @@ +services { + name = "s2" + port = 8181 + checks = [] + connect { + sidecar_service { + checks = [] + } + } +} diff --git a/test/integration/connect/envoy/case-cfg-splitter-peering-ingress-gateways/alpha/setup.sh b/test/integration/connect/envoy/case-cfg-splitter-peering-ingress-gateways/alpha/setup.sh new file mode 100644 index 0000000000..e6d27d5d8d --- /dev/null +++ b/test/integration/connect/envoy/case-cfg-splitter-peering-ingress-gateways/alpha/setup.sh @@ -0,0 +1,12 @@ +#!/bin/bash + +set -euo pipefail + +wait_for_config_entry proxy-defaults global alpha +wait_for_config_entry exported-services default alpha + +register_services alpha + +gen_envoy_bootstrap s1 19001 alpha +gen_envoy_bootstrap s2 19002 alpha +gen_envoy_bootstrap mesh-gateway 19003 alpha true diff --git a/test/integration/connect/envoy/case-cfg-splitter-peering-ingress-gateways/alpha/verify.bats b/test/integration/connect/envoy/case-cfg-splitter-peering-ingress-gateways/alpha/verify.bats new file mode 100644 index 0000000000..9841f8ca24 --- /dev/null +++ b/test/integration/connect/envoy/case-cfg-splitter-peering-ingress-gateways/alpha/verify.bats @@ -0,0 +1,34 @@ +#!/usr/bin/env bats + +load helpers + +@test "proxies are running correct version" { + assert_envoy_version 19001 + assert_envoy_version 19002 +} + +@test "s1 proxy admin is up on :19001" { + retry_default curl -f -s localhost:19001/stats -o /dev/null +} + +@test "s2 proxy admin is up on :19002" { + retry_default curl -f -s localhost:19002/stats -o /dev/null +} + +@test "gateway-alpha proxy admin is up on :19003" { + retry_default curl -f -s localhost:19003/stats -o /dev/null +} + +@test "proxy listeners should be up and have right cert" { + assert_proxy_presents_cert_uri localhost:21000 s1 alpha + assert_proxy_presents_cert_uri localhost:21001 s2 alpha +} + +@test "s1 and s2 proxies should be healthy" { + assert_service_has_healthy_instances s1 1 alpha + assert_service_has_healthy_instances s2 1 alpha +} + +@test "gateway-alpha should be up and listening" { + retry_long nc -z consul-alpha-client:4432 +} diff --git a/test/integration/connect/envoy/case-cfg-splitter-peering-ingress-gateways/bind.hcl b/test/integration/connect/envoy/case-cfg-splitter-peering-ingress-gateways/bind.hcl new file mode 100644 index 0000000000..f54393f03e --- /dev/null +++ b/test/integration/connect/envoy/case-cfg-splitter-peering-ingress-gateways/bind.hcl @@ -0,0 +1,2 @@ +bind_addr = "0.0.0.0" +advertise_addr = "{{ GetInterfaceIP \"eth0\" }}" \ No newline at end of file diff --git a/test/integration/connect/envoy/case-cfg-splitter-peering-ingress-gateways/capture.sh b/test/integration/connect/envoy/case-cfg-splitter-peering-ingress-gateways/capture.sh new file mode 100644 index 0000000000..f6b2e2cf12 --- /dev/null +++ b/test/integration/connect/envoy/case-cfg-splitter-peering-ingress-gateways/capture.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +snapshot_envoy_admin localhost:20000 ingress-gateway primary || true +snapshot_envoy_admin localhost:19000 s1 primary || true +snapshot_envoy_admin localhost:19001 s1 alpha || true +snapshot_envoy_admin localhost:19002 s2 alpha || true +snapshot_envoy_admin localhost:19003 mesh-gateway alpha || true \ No newline at end of file diff --git a/test/integration/connect/envoy/case-cfg-splitter-peering-ingress-gateways/primary/base.hcl b/test/integration/connect/envoy/case-cfg-splitter-peering-ingress-gateways/primary/base.hcl new file mode 100644 index 0000000000..c1e134d5a2 --- /dev/null +++ b/test/integration/connect/envoy/case-cfg-splitter-peering-ingress-gateways/primary/base.hcl @@ -0,0 +1,3 @@ +peering { + enabled = true +} diff --git a/test/integration/connect/envoy/case-cfg-splitter-peering-ingress-gateways/primary/config_entries.hcl b/test/integration/connect/envoy/case-cfg-splitter-peering-ingress-gateways/primary/config_entries.hcl new file mode 100644 index 0000000000..0b38ad6ed4 --- /dev/null +++ b/test/integration/connect/envoy/case-cfg-splitter-peering-ingress-gateways/primary/config_entries.hcl @@ -0,0 +1,88 @@ +config_entries { + + bootstrap { + kind = "proxy-defaults" + name = "global" + config { + protocol = "http" + } + } + + bootstrap { + kind = "ingress-gateway" + name = "ingress-gateway" + listeners = [ + { + protocol = "http" + port = 9999 + services = [ + { + name = "peer-s2" + } + ] + }, + { + protocol = "http" + port = 10000 + services = [ + { + name = "peer-s1" + } + ] + }, + { + protocol = "http" + port = 10001 + services = [ + { + name = "s1" + } + ] + }, + { + protocol = "http" + port = 10002 + services = [ + { + name = "split" + } + ] + } + ] + } + + bootstrap { + kind = "service-resolver" + name = "peer-s1" + + redirect = { + service = "s1" + peer = "primary-to-alpha" + } + } + + bootstrap { + kind = "service-resolver" + name = "peer-s2" + + redirect = { + service = "s2" + peer = "primary-to-alpha" + } + } + + bootstrap { + kind = "service-splitter" + name = "split" + splits = [ + { + Weight = 50 + Service = "peer-s1" + }, + { + Weight = 50 + Service = "peer-s2" + }, + ] + } +} diff --git a/test/integration/connect/envoy/case-cfg-splitter-peering-ingress-gateways/primary/service_ingress.hcl b/test/integration/connect/envoy/case-cfg-splitter-peering-ingress-gateways/primary/service_ingress.hcl new file mode 100644 index 0000000000..781ef1851b --- /dev/null +++ b/test/integration/connect/envoy/case-cfg-splitter-peering-ingress-gateways/primary/service_ingress.hcl @@ -0,0 +1,4 @@ +services { + name = "ingress-gateway" + kind = "ingress-gateway" +} diff --git a/test/integration/connect/envoy/case-cfg-splitter-peering-ingress-gateways/primary/setup.sh b/test/integration/connect/envoy/case-cfg-splitter-peering-ingress-gateways/primary/setup.sh new file mode 100644 index 0000000000..b92dfc15e6 --- /dev/null +++ b/test/integration/connect/envoy/case-cfg-splitter-peering-ingress-gateways/primary/setup.sh @@ -0,0 +1,14 @@ +#!/bin/bash + +set -eEuo pipefail + +# wait for bootstrap to apply config entries +wait_for_config_entry ingress-gateway ingress-gateway +wait_for_config_entry proxy-defaults global +wait_for_config_entry service-resolver peer-s1 +wait_for_config_entry service-resolver peer-s2 + +register_services primary + +gen_envoy_bootstrap ingress-gateway 20000 primary true +gen_envoy_bootstrap s1 19000 primary \ No newline at end of file diff --git a/test/integration/connect/envoy/case-cfg-splitter-peering-ingress-gateways/primary/verify.bats b/test/integration/connect/envoy/case-cfg-splitter-peering-ingress-gateways/primary/verify.bats new file mode 100644 index 0000000000..e12e7058ec --- /dev/null +++ b/test/integration/connect/envoy/case-cfg-splitter-peering-ingress-gateways/primary/verify.bats @@ -0,0 +1,70 @@ +#!/usr/bin/env bats + +load helpers + + +@test "ingress-primary proxy admin is up" { + retry_default curl -f -s localhost:20000/stats -o /dev/null +} + +@test "s1 proxy admin is up" { + retry_default curl -f -s localhost:19000/stats -o /dev/null +} + +@test "services should be healthy in primary" { + assert_service_has_healthy_instances s1 1 alpha +} + +@test "services should be healthy in alpha" { + assert_service_has_healthy_instances s1 1 alpha + assert_service_has_healthy_instances s2 1 alpha +} + +@test "mesh-gateway should have healthy endpoints" { + assert_upstream_has_endpoints_in_status consul-alpha-client:19003 s1 HEALTHY 1 + assert_upstream_has_endpoints_in_status consul-alpha-client:19003 s2 HEALTHY 1 +} + +@test "peer the two clusters together" { + create_peering primary alpha +} + +@test "s1 alpha proxies should be healthy in primary" { + assert_service_has_healthy_instances s1 1 primary "" "" primary-to-alpha +} + +@test "s2 alpha proxies should be healthy in primary" { + assert_service_has_healthy_instances s2 1 primary "" "" primary-to-alpha +} + +@test "ingress should have healthy endpoints to alpha s1 and s2" { + assert_upstream_has_endpoints_in_status 127.0.0.1:20000 s1.default.primary-to-alpha.external HEALTHY 1 + assert_upstream_has_endpoints_in_status 127.0.0.1:20000 s2.default.primary-to-alpha.external HEALTHY 1 +} + +@test "requests through ingress should proxy to alpha" { + assert_expected_fortio_name s1-alpha peer-s1.ingress.consul 10000 + assert_expected_fortio_name s2-alpha peer-s2.ingress.consul 9999 +} + +@test "ingress made 1 connection to alpha s1" { + assert_envoy_metric_at_least 127.0.0.1:20000 "cluster.s1.default.primary-to-alpha.external.*cx_total" 1 +} + +@test "ingress made 1 connection to alpha s2" { + assert_envoy_metric_at_least 127.0.0.1:20000 "cluster.s2.default.primary-to-alpha.external.*cx_total" 1 +} + +@test "no requests contacted primary s1" { + assert_envoy_metric 127.0.0.1:19000 "http.public_listener.rq_total" 0 +} + +@test "requests through ingress should proxy to primary s1" { + assert_expected_fortio_name s1 s1.ingress.consul 10001 + assert_envoy_metric 127.0.0.1:19000 "http.public_listener.rq_total" 1 +} + +@test "requests through ingress to splitter should go to alpha" { + retry_long assert_expected_fortio_name s1-alpha split.ingress.consul 10002 + retry_long assert_expected_fortio_name s2-alpha split.ingress.consul 10002 +} \ No newline at end of file diff --git a/test/integration/connect/envoy/case-cfg-splitter-peering-ingress-gateways/vars.sh b/test/integration/connect/envoy/case-cfg-splitter-peering-ingress-gateways/vars.sh new file mode 100644 index 0000000000..9f0aefa402 --- /dev/null +++ b/test/integration/connect/envoy/case-cfg-splitter-peering-ingress-gateways/vars.sh @@ -0,0 +1,4 @@ +#!/bin/bash + +export REQUIRED_SERVICES="s1 s1-sidecar-proxy s1-alpha s1-sidecar-proxy-alpha s2-alpha s2-sidecar-proxy-alpha gateway-alpha ingress-gateway-primary" +export REQUIRE_PEERS=1 \ No newline at end of file diff --git a/test/integration/connect/envoy/case-cross-peers-http-router/alpha/config_entries.hcl b/test/integration/connect/envoy/case-cross-peers-http-router/alpha/config_entries.hcl index 9a01d60fd2..54941a9032 100644 --- a/test/integration/connect/envoy/case-cross-peers-http-router/alpha/config_entries.hcl +++ b/test/integration/connect/envoy/case-cross-peers-http-router/alpha/config_entries.hcl @@ -29,7 +29,7 @@ config_entries { name = "s2" consumers = [ { - peer_name = "alpha-to-primary" + peer = "alpha-to-primary" } ] } diff --git a/test/integration/connect/envoy/case-cross-peers-http/alpha/config_entries.hcl b/test/integration/connect/envoy/case-cross-peers-http/alpha/config_entries.hcl index 2d50ef0fb4..a46dc7ee2d 100644 --- a/test/integration/connect/envoy/case-cross-peers-http/alpha/config_entries.hcl +++ b/test/integration/connect/envoy/case-cross-peers-http/alpha/config_entries.hcl @@ -16,7 +16,7 @@ config_entries { name = "s2" consumers = [ { - peer_name = "alpha-to-primary" + peer = "alpha-to-primary" } ] } diff --git a/test/integration/connect/envoy/case-cross-peers-resolver-redirect-tcp/alpha/config_entries.hcl b/test/integration/connect/envoy/case-cross-peers-resolver-redirect-tcp/alpha/config_entries.hcl index edf5d0bb5a..4356f4ba8c 100644 --- a/test/integration/connect/envoy/case-cross-peers-resolver-redirect-tcp/alpha/config_entries.hcl +++ b/test/integration/connect/envoy/case-cross-peers-resolver-redirect-tcp/alpha/config_entries.hcl @@ -23,7 +23,7 @@ config_entries { name = "s2" consumers = [ { - peer_name = "alpha-to-primary" + peer = "alpha-to-primary" } ] } diff --git a/test/integration/connect/envoy/case-cross-peers/alpha/config_entries.hcl b/test/integration/connect/envoy/case-cross-peers/alpha/config_entries.hcl index 64d0117020..e1f1178887 100644 --- a/test/integration/connect/envoy/case-cross-peers/alpha/config_entries.hcl +++ b/test/integration/connect/envoy/case-cross-peers/alpha/config_entries.hcl @@ -16,7 +16,7 @@ config_entries { name = "s2" consumers = [ { - peer_name = "alpha-to-primary" + peer = "alpha-to-primary" } ] } diff --git a/test/integration/connect/envoy/case-cross-peers/primary/service_s1.hcl b/test/integration/connect/envoy/case-cross-peers/primary/service_s1.hcl index 0e3dcbc3ed..af0773c29a 100644 --- a/test/integration/connect/envoy/case-cross-peers/primary/service_s1.hcl +++ b/test/integration/connect/envoy/case-cross-peers/primary/service_s1.hcl @@ -9,6 +9,9 @@ services { destination_name = "s2" destination_peer = "primary-to-alpha" local_bind_port = 5000 + mesh_gateway { + mode = "local" + } } ] } diff --git a/test/integration/connect/envoy/case-cross-peers/primary/verify.bats b/test/integration/connect/envoy/case-cross-peers/primary/verify.bats index 93837be619..af45d2ad3f 100644 --- a/test/integration/connect/envoy/case-cross-peers/primary/verify.bats +++ b/test/integration/connect/envoy/case-cross-peers/primary/verify.bats @@ -55,3 +55,7 @@ load helpers @test "s1 upstream made 1 connection to s2" { assert_envoy_metric_at_least 127.0.0.1:19000 "cluster.s2.default.primary-to-alpha.external.*cx_total" 1 } + +@test "s1 upstream made 1 connection to s2 through the primary mesh gateway" { + assert_envoy_metric_at_least 127.0.0.1:19001 "cluster.s2.default.default.alpha-to-primary.external.*cx_total" 1 +} diff --git a/test/integration/connect/envoy/case-ingress-gateway-multiple-services/config_entries.hcl b/test/integration/connect/envoy/case-ingress-gateway-multiple-services/config_entries.hcl index b5fbaf2241..c1c7c58320 100644 --- a/test/integration/connect/envoy/case-ingress-gateway-multiple-services/config_entries.hcl +++ b/test/integration/connect/envoy/case-ingress-gateway-multiple-services/config_entries.hcl @@ -11,6 +11,11 @@ config_entries { kind = "ingress-gateway" name = "ingress-gateway" + Defaults { + MaxConnections = 10 + MaxPendingRequests = 20 + MaxConcurrentRequests = 30 + } listeners = [ { port = 9999 @@ -28,6 +33,9 @@ config_entries { { name = "s1" hosts = ["test.example.com"] + MaxConnections = 100 + MaxPendingRequests = 200 + MaxConcurrentRequests = 300 } ] } diff --git a/test/integration/connect/envoy/case-ingress-gateway-multiple-services/verify.bats b/test/integration/connect/envoy/case-ingress-gateway-multiple-services/verify.bats index 17cd62f2e9..97c712a7f9 100644 --- a/test/integration/connect/envoy/case-ingress-gateway-multiple-services/verify.bats +++ b/test/integration/connect/envoy/case-ingress-gateway-multiple-services/verify.bats @@ -23,11 +23,28 @@ load helpers } @test "ingress-gateway should have healthy endpoints for s1" { - assert_upstream_has_endpoints_in_status 127.0.0.1:20000 s1 HEALTHY 1 + assert_upstream_has_endpoints_in_status 127.0.0.1:20000 s1 HEALTHY 1 } @test "ingress-gateway should have healthy endpoints for s2" { - assert_upstream_has_endpoints_in_status 127.0.0.1:20000 s2 HEALTHY 1 + assert_upstream_has_endpoints_in_status 127.0.0.1:20000 s2 HEALTHY 1 +} + +@test "s2 proxy should have been configured with connection threshold from defaults" { + CLUSTER_THRESHOLD=$(get_envoy_cluster_config 127.0.0.1:20000 s2.default.primary | jq '.circuit_breakers.thresholds[0]') + echo $CLUSTER_THRESHOLD + + MAX_CONNS=$(echo $CLUSTER_THRESHOLD | jq --raw-output '.max_connections') + MAX_PENDING_REQS=$(echo $CLUSTER_THRESHOLD | jq --raw-output '.max_pending_requests') + MAX_REQS=$(echo $CLUSTER_THRESHOLD | jq --raw-output '.max_requests') + + echo "MAX_CONNS = $MAX_CONNS" + echo "MAX_PENDING_REQS = $MAX_PENDING_REQS" + echo "MAX_REQS = $MAX_REQS" + + [ "$MAX_CONNS" = "10" ] + [ "$MAX_PENDING_REQS" = "20" ] + [ "$MAX_REQS" = "30" ] } @test "ingress should be able to connect to s1 using Host header" { diff --git a/test/integration/connect/envoy/case-ingress-gateway-peering-failover/alpha/base.hcl b/test/integration/connect/envoy/case-ingress-gateway-peering-failover/alpha/base.hcl new file mode 100644 index 0000000000..f81ab0edd6 --- /dev/null +++ b/test/integration/connect/envoy/case-ingress-gateway-peering-failover/alpha/base.hcl @@ -0,0 +1,5 @@ +primary_datacenter = "alpha" +log_level = "trace" +peering { + enabled = true +} diff --git a/test/integration/connect/envoy/case-ingress-gateway-peering-failover/alpha/config_entries.hcl b/test/integration/connect/envoy/case-ingress-gateway-peering-failover/alpha/config_entries.hcl new file mode 100644 index 0000000000..64d0117020 --- /dev/null +++ b/test/integration/connect/envoy/case-ingress-gateway-peering-failover/alpha/config_entries.hcl @@ -0,0 +1,26 @@ +config_entries { + bootstrap = [ + { + kind = "proxy-defaults" + name = "global" + + config { + protocol = "tcp" + } + }, + { + kind = "exported-services" + name = "default" + services = [ + { + name = "s2" + consumers = [ + { + peer_name = "alpha-to-primary" + } + ] + } + ] + } + ] +} diff --git a/test/integration/connect/envoy/case-ingress-gateway-peering-failover/alpha/service_gateway.hcl b/test/integration/connect/envoy/case-ingress-gateway-peering-failover/alpha/service_gateway.hcl new file mode 100644 index 0000000000..bcdcb2e8b3 --- /dev/null +++ b/test/integration/connect/envoy/case-ingress-gateway-peering-failover/alpha/service_gateway.hcl @@ -0,0 +1,5 @@ +services { + name = "mesh-gateway" + kind = "mesh-gateway" + port = 4432 +} diff --git a/test/integration/connect/envoy/case-ingress-gateway-peering-failover/alpha/service_s1.hcl b/test/integration/connect/envoy/case-ingress-gateway-peering-failover/alpha/service_s1.hcl new file mode 100644 index 0000000000..e97ec23666 --- /dev/null +++ b/test/integration/connect/envoy/case-ingress-gateway-peering-failover/alpha/service_s1.hcl @@ -0,0 +1 @@ +# We don't want an s1 service in this peer diff --git a/test/integration/connect/envoy/case-ingress-gateway-peering-failover/alpha/service_s2.hcl b/test/integration/connect/envoy/case-ingress-gateway-peering-failover/alpha/service_s2.hcl new file mode 100644 index 0000000000..14bf414fdc --- /dev/null +++ b/test/integration/connect/envoy/case-ingress-gateway-peering-failover/alpha/service_s2.hcl @@ -0,0 +1,10 @@ +services { + name = "s2" + port = 8181 + checks = [] + connect { + sidecar_service { + checks = [] + } + } +} diff --git a/test/integration/connect/envoy/case-ingress-gateway-peering-failover/alpha/setup.sh b/test/integration/connect/envoy/case-ingress-gateway-peering-failover/alpha/setup.sh new file mode 100644 index 0000000000..820506ea9b --- /dev/null +++ b/test/integration/connect/envoy/case-ingress-gateway-peering-failover/alpha/setup.sh @@ -0,0 +1,11 @@ +#!/bin/bash + +set -euo pipefail + +register_services alpha + +gen_envoy_bootstrap s2 19002 alpha +gen_envoy_bootstrap mesh-gateway 19003 alpha true + +wait_for_config_entry proxy-defaults global alpha +wait_for_config_entry exported-services default alpha diff --git a/test/integration/connect/envoy/case-ingress-gateway-peering-failover/alpha/verify.bats b/test/integration/connect/envoy/case-ingress-gateway-peering-failover/alpha/verify.bats new file mode 100644 index 0000000000..d2229b2974 --- /dev/null +++ b/test/integration/connect/envoy/case-ingress-gateway-peering-failover/alpha/verify.bats @@ -0,0 +1,27 @@ +#!/usr/bin/env bats + +load helpers + +@test "s2 proxy is running correct version" { + assert_envoy_version 19002 +} + +@test "s2 proxy admin is up on :19002" { + retry_default curl -f -s localhost:19002/stats -o /dev/null +} + +@test "gateway-alpha proxy admin is up on :19003" { + retry_default curl -f -s localhost:19003/stats -o /dev/null +} + +@test "s2 proxy listener should be up and have right cert" { + assert_proxy_presents_cert_uri localhost:21000 s2 alpha +} + +@test "s2 proxy should be healthy" { + assert_service_has_healthy_instances s2 1 alpha +} + +@test "gateway-alpha should be up and listening" { + retry_long nc -z consul-alpha-client:4432 +} diff --git a/test/integration/connect/envoy/case-ingress-gateway-peering-failover/bind.hcl b/test/integration/connect/envoy/case-ingress-gateway-peering-failover/bind.hcl new file mode 100644 index 0000000000..f54393f03e --- /dev/null +++ b/test/integration/connect/envoy/case-ingress-gateway-peering-failover/bind.hcl @@ -0,0 +1,2 @@ +bind_addr = "0.0.0.0" +advertise_addr = "{{ GetInterfaceIP \"eth0\" }}" \ No newline at end of file diff --git a/test/integration/connect/envoy/case-ingress-gateway-peering-failover/capture.sh b/test/integration/connect/envoy/case-ingress-gateway-peering-failover/capture.sh new file mode 100644 index 0000000000..c137691059 --- /dev/null +++ b/test/integration/connect/envoy/case-ingress-gateway-peering-failover/capture.sh @@ -0,0 +1,6 @@ +#!/bin/bash + +snapshot_envoy_admin localhost:20000 ingress-gateway primary || true +snapshot_envoy_admin localhost:19001 s2 primary || true +snapshot_envoy_admin localhost:19002 s2 alpha || true +snapshot_envoy_admin localhost:19003 mesh-gateway alpha || true diff --git a/test/integration/connect/envoy/case-ingress-gateway-peering-failover/primary/base.hcl b/test/integration/connect/envoy/case-ingress-gateway-peering-failover/primary/base.hcl new file mode 100644 index 0000000000..c1e134d5a2 --- /dev/null +++ b/test/integration/connect/envoy/case-ingress-gateway-peering-failover/primary/base.hcl @@ -0,0 +1,3 @@ +peering { + enabled = true +} diff --git a/test/integration/connect/envoy/case-ingress-gateway-peering-failover/primary/config_entries.hcl b/test/integration/connect/envoy/case-ingress-gateway-peering-failover/primary/config_entries.hcl new file mode 100644 index 0000000000..8be1a8ccad --- /dev/null +++ b/test/integration/connect/envoy/case-ingress-gateway-peering-failover/primary/config_entries.hcl @@ -0,0 +1,47 @@ +config_entries { + bootstrap { + kind = "proxy-defaults" + name = "global" + + config { + protocol = "tcp" + } + } + + bootstrap { + kind = "ingress-gateway" + name = "ingress-gateway" + listeners = [ + { + protocol = "tcp" + port = 10000 + services = [ + { + name = "s2" + } + ] + } + ] + } + + bootstrap { + kind = "service-resolver" + name = "s2" + + failover = { + "*" = { + targets = [{peer = "primary-to-alpha"}] + } + } + } + + bootstrap { + kind = "service-resolver" + name = "virtual-s2" + + redirect = { + service = "s2" + peer = "primary-to-alpha" + } + } +} diff --git a/test/integration/connect/envoy/case-ingress-gateway-peering-failover/primary/service_ingress.hcl b/test/integration/connect/envoy/case-ingress-gateway-peering-failover/primary/service_ingress.hcl new file mode 100644 index 0000000000..781ef1851b --- /dev/null +++ b/test/integration/connect/envoy/case-ingress-gateway-peering-failover/primary/service_ingress.hcl @@ -0,0 +1,4 @@ +services { + name = "ingress-gateway" + kind = "ingress-gateway" +} diff --git a/test/integration/connect/envoy/case-ingress-gateway-peering-failover/primary/service_s1.hcl b/test/integration/connect/envoy/case-ingress-gateway-peering-failover/primary/service_s1.hcl new file mode 100644 index 0000000000..aca546a5d6 --- /dev/null +++ b/test/integration/connect/envoy/case-ingress-gateway-peering-failover/primary/service_s1.hcl @@ -0,0 +1 @@ +# We don't need an s1 because requests go through the gateway and not a sidecar. \ No newline at end of file diff --git a/test/integration/connect/envoy/case-ingress-gateway-peering-failover/primary/service_s2.hcl b/test/integration/connect/envoy/case-ingress-gateway-peering-failover/primary/service_s2.hcl new file mode 100644 index 0000000000..14bf414fdc --- /dev/null +++ b/test/integration/connect/envoy/case-ingress-gateway-peering-failover/primary/service_s2.hcl @@ -0,0 +1,10 @@ +services { + name = "s2" + port = 8181 + checks = [] + connect { + sidecar_service { + checks = [] + } + } +} diff --git a/test/integration/connect/envoy/case-ingress-gateway-peering-failover/primary/setup.sh b/test/integration/connect/envoy/case-ingress-gateway-peering-failover/primary/setup.sh new file mode 100644 index 0000000000..5577a4e8dc --- /dev/null +++ b/test/integration/connect/envoy/case-ingress-gateway-peering-failover/primary/setup.sh @@ -0,0 +1,13 @@ +#!/bin/bash + +set -euo pipefail + +wait_for_config_entry ingress-gateway ingress-gateway +wait_for_config_entry proxy-defaults global +wait_for_config_entry service-resolver s2 +wait_for_config_entry service-resolver virtual-s2 + +register_services primary + +gen_envoy_bootstrap ingress-gateway 20000 primary true +gen_envoy_bootstrap s2 19001 primary \ No newline at end of file diff --git a/test/integration/connect/envoy/case-ingress-gateway-peering-failover/primary/verify.bats b/test/integration/connect/envoy/case-ingress-gateway-peering-failover/primary/verify.bats new file mode 100644 index 0000000000..9429a9d60e --- /dev/null +++ b/test/integration/connect/envoy/case-ingress-gateway-peering-failover/primary/verify.bats @@ -0,0 +1,77 @@ +#!/usr/bin/env bats + +load helpers + +@test "s2 proxy is running correct version" { + assert_envoy_version 19001 +} + +@test "ingress-primary proxy admin is up" { + retry_default curl -f -s localhost:20000/stats -o /dev/null +} + +@test "services should be healthy in primary" { + assert_service_has_healthy_instances s2 1 alpha +} + +@test "services should be healthy in alpha" { + assert_service_has_healthy_instances s2 1 alpha +} + +@test "mesh-gateway should have healthy endpoints" { + assert_upstream_has_endpoints_in_status consul-alpha-client:19003 s2 HEALTHY 1 +} + +@test "peer the two clusters together" { + create_peering primary alpha +} + +@test "s2 alpha proxies should be healthy in primary" { + assert_service_has_healthy_instances s2 1 primary "" "" primary-to-alpha +} + +# Failover + +@test "s1 upstream should have healthy endpoints for s2 in both primary and failover" { + assert_upstream_has_endpoints_in_status 127.0.0.1:20000 failover-target~s2.default.primary.internal HEALTHY 1 + assert_upstream_has_endpoints_in_status 127.0.0.1:20000 failover-target~s2.default.primary-to-alpha.external HEALTHY 1 +} + + +@test "ingress-gateway should be able to connect to s2" { + assert_expected_fortio_name s2 127.0.0.1 10000 +} + +@test "s1 upstream made 1 connection" { + assert_envoy_metric_at_least 127.0.0.1:20000 "cluster.failover-target~s2.default.primary.internal.*cx_total" 1 +} + +@test "terminate instance of s2 primary envoy which should trigger failover to s2 alpha when the tcp check fails" { + kill_envoy s2 primary +} + +@test "s2 proxies should be unhealthy in primary" { + assert_service_has_healthy_instances s2 0 primary +} + + +@test "s1 upstream should have healthy endpoints for s2 in the failover cluster peer" { + assert_upstream_has_endpoints_in_status 127.0.0.1:20000 failover-target~s2.default.primary.internal UNHEALTHY 1 + assert_upstream_has_endpoints_in_status 127.0.0.1:20000 failover-target~s2.default.primary-to-alpha.external HEALTHY 1 +} + +@test "reset envoy statistics for failover" { + reset_envoy_metrics 127.0.0.1:20000 +} + +@test "gateway-alpha should have healthy endpoints for s2" { + assert_upstream_has_endpoints_in_status consul-alpha-client:19003 exported~s2.default.alpha HEALTHY 1 +} + +@test "s1 upstream should be able to connect to s2 in the failover cluster peer" { + assert_expected_fortio_name s2-alpha 127.0.0.1 10000 +} + +@test "s1 upstream made 1 connection to s2 through the cluster peer" { + assert_envoy_metric_at_least 127.0.0.1:20000 "cluster.failover-target~s2.default.primary-to-alpha.external.*cx_total" 1 +} diff --git a/test/integration/connect/envoy/case-ingress-gateway-peering-failover/vars.sh b/test/integration/connect/envoy/case-ingress-gateway-peering-failover/vars.sh new file mode 100644 index 0000000000..3c598404d3 --- /dev/null +++ b/test/integration/connect/envoy/case-ingress-gateway-peering-failover/vars.sh @@ -0,0 +1,4 @@ +#!/bin/bash + +export REQUIRED_SERVICES="s2 s2-sidecar-proxy s2-alpha s2-sidecar-proxy-alpha gateway-alpha ingress-gateway-primary" +export REQUIRE_PEERS=1 diff --git a/test/integration/connect/envoy/case-ingress-gateway-simple/config_entries.hcl b/test/integration/connect/envoy/case-ingress-gateway-simple/config_entries.hcl index dfc7bc7b93..88a76594a8 100644 --- a/test/integration/connect/envoy/case-ingress-gateway-simple/config_entries.hcl +++ b/test/integration/connect/envoy/case-ingress-gateway-simple/config_entries.hcl @@ -2,7 +2,11 @@ config_entries { bootstrap { kind = "ingress-gateway" name = "ingress-gateway" - + Defaults { + MaxConnections = 10 + MaxPendingRequests = 20 + MaxConcurrentRequests = 30 + } listeners = [ { port = 9999 @@ -10,6 +14,8 @@ config_entries { services = [ { name = "s1" + MaxConnections = 100 + MaxPendingRequests = 200 } ] } diff --git a/test/integration/connect/envoy/case-ingress-gateway-simple/verify.bats b/test/integration/connect/envoy/case-ingress-gateway-simple/verify.bats index 73c09773d5..9d0735b42c 100644 --- a/test/integration/connect/envoy/case-ingress-gateway-simple/verify.bats +++ b/test/integration/connect/envoy/case-ingress-gateway-simple/verify.bats @@ -19,7 +19,24 @@ load helpers } @test "ingress-gateway should have healthy endpoints for s1" { - assert_upstream_has_endpoints_in_status 127.0.0.1:20000 s1 HEALTHY 1 + assert_upstream_has_endpoints_in_status 127.0.0.1:20000 s1 HEALTHY 1 +} + +@test "s1 proxy should have been configured with connection threshold from defaults and service" { + CLUSTER_THRESHOLD=$(get_envoy_cluster_config 127.0.0.1:20000 s1.default.primary | jq '.circuit_breakers.thresholds[0]') + echo $CLUSTER_THRESHOLD + + MAX_CONNS=$(echo $CLUSTER_THRESHOLD | jq --raw-output '.max_connections') + MAX_PENDING_REQS=$(echo $CLUSTER_THRESHOLD | jq --raw-output '.max_pending_requests') + MAX_REQS=$(echo $CLUSTER_THRESHOLD | jq --raw-output '.max_requests') + + echo "MAX_CONNS = $MAX_CONNS" + echo "MAX_PENDING_REQS = $MAX_PENDING_REQS" + echo "MAX_REQS = $MAX_REQS" + + [ "$MAX_CONNS" = "100" ] + [ "$MAX_PENDING_REQS" = "200" ] + [ "$MAX_REQS" = "30" ] } @test "ingress should be able to connect to s1 via configured port" { diff --git a/test/integration/connect/envoy/consul-base-cfg/base.hcl b/test/integration/connect/envoy/consul-base-cfg/base.hcl index 241261c1f8..884117c5ce 100644 --- a/test/integration/connect/envoy/consul-base-cfg/base.hcl +++ b/test/integration/connect/envoy/consul-base-cfg/base.hcl @@ -1,2 +1,2 @@ primary_datacenter = "primary" -log_level = "trace" +log_level = "trace" \ No newline at end of file diff --git a/test/integration/connect/envoy/consul-base-cfg/peering_server.hcl b/test/integration/connect/envoy/consul-base-cfg/peering_server.hcl new file mode 100644 index 0000000000..ccbba6939c --- /dev/null +++ b/test/integration/connect/envoy/consul-base-cfg/peering_server.hcl @@ -0,0 +1,6 @@ +ports { + grpc_tls = 8503 +} +connect { + enabled = true +} \ No newline at end of file diff --git a/test/integration/connect/envoy/helpers.bash b/test/integration/connect/envoy/helpers.bash index d7fe0ae024..760eaea1a8 100755 --- a/test/integration/connect/envoy/helpers.bash +++ b/test/integration/connect/envoy/helpers.bash @@ -51,34 +51,6 @@ function retry_long { retry 30 1 "$@" } -function echored { - tput setaf 1 - tput bold - echo $@ - tput sgr0 -} - -function echogreen { - tput setaf 2 - tput bold - echo $@ - tput sgr0 -} - -function echoyellow { - tput setaf 3 - tput bold - echo $@ - tput sgr0 -} - -function echoblue { - tput setaf 4 - tput bold - echo $@ - tput sgr0 -} - function is_set { # Arguments: # $1 - string value to check its truthiness @@ -1025,3 +997,23 @@ function varsub { sed -i "s/\${$v}/${!v}/g" $file done } + +function get_url_header { + local URL=$1 + local HEADER=$2 + run curl -s -f -X GET -I "${URL}" + [ "$status" == 0 ] + RESP=$(echo "$output" | tr -d '\r') + RESP=$(echo "$RESP" | grep -E "^${HEADER}: ") + RESP=$(echo "$RESP" | sed "s/^${HEADER}: //g") + echo "$RESP" +} + +function assert_url_header { + local URL=$1 + local HEADER=$2 + local VALUE=$3 + run get_url_header "$URL" "$HEADER" + [ "$status" == 0 ] + [ "$VALUE" = "$output" ] +} \ No newline at end of file diff --git a/test/integration/connect/envoy/run-tests.sh b/test/integration/connect/envoy/run-tests.sh index 43e0925c6c..d32092e7a9 100755 --- a/test/integration/connect/envoy/run-tests.sh +++ b/test/integration/connect/envoy/run-tests.sh @@ -104,6 +104,13 @@ function init_workdir { mv workdir/${CLUSTER}/consul/server.hcl workdir/${CLUSTER}/consul-server/server.hcl fi + if test -f "workdir/${CLUSTER}/consul/peering_server.hcl" -a $REQUIRE_PEERS = "1" + then + mv workdir/${CLUSTER}/consul/peering_server.hcl workdir/${CLUSTER}/consul-server/peering_server.hcl + else + rm workdir/${CLUSTER}/consul/peering_server.hcl + fi + # copy the ca-certs for SDS so we can verify the right ones are served mkdir -p workdir/test-sds-server/certs cp test-sds-server/certs/ca-root.crt workdir/test-sds-server/certs/ca-root.crt @@ -216,11 +223,6 @@ function start_consul { docker_kill_rm consul-${DC}-server docker_kill_rm consul-${DC} - server_grpc_port="-1" - if is_set $REQUIRE_PEERS; then - server_grpc_port="8502" - fi - docker run -d --name envoy_consul-${DC}-server_1 \ --net=envoy-tests \ $WORKDIR_SNIPPET \ @@ -231,7 +233,6 @@ function start_consul { agent -dev -datacenter "${DC}" \ -config-dir "/workdir/${DC}/consul" \ -config-dir "/workdir/${DC}/consul-server" \ - -grpc-port $server_grpc_port \ -client "0.0.0.0" \ -bind "0.0.0.0" >/dev/null @@ -354,10 +355,10 @@ function verify { $(network_snippet $CLUSTER) \ $(aws_snippet) \ bats-verify \ - --pretty /workdir/${CLUSTER}/bats ; then - echogreen "✓ PASS" + --formatter tap /workdir/${CLUSTER}/bats ; then + echo "✓ PASS" else - echored "⨯ FAIL" + echo "⨯ FAIL" res=1 fi @@ -472,7 +473,7 @@ function run_tests { # Allow vars.sh to set a reason to skip this test case based on the ENV if [ "$SKIP_CASE" != "" ] ; then - echoyellow "SKIPPING CASE: $SKIP_CASE" + echo "SKIPPING CASE: $SKIP_CASE" return 0 fi diff --git a/test/integration/consul-container/go.mod b/test/integration/consul-container/go.mod index c07df6629a..917e708e05 100644 --- a/test/integration/consul-container/go.mod +++ b/test/integration/consul-container/go.mod @@ -35,7 +35,7 @@ require ( github.com/hashicorp/go-immutable-radix v1.3.1 // indirect github.com/hashicorp/go-rootcerts v1.0.2 // indirect github.com/hashicorp/golang-lru v0.5.4 // indirect - github.com/hashicorp/serf v0.9.8 // indirect + github.com/hashicorp/serf v0.10.1 // indirect github.com/magiconair/properties v1.8.5 // indirect github.com/mattn/go-colorable v0.1.8 // indirect github.com/mattn/go-isatty v0.0.12 // indirect @@ -47,13 +47,13 @@ require ( github.com/morikuni/aec v0.0.0-20170113033406-39771216ff4c // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.0.2 // indirect - github.com/opencontainers/runc v1.0.2 // indirect + github.com/opencontainers/runc v1.1.4 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/sirupsen/logrus v1.8.1 // indirect go.opencensus.io v0.22.3 // indirect golang.org/x/net v0.0.0-20211216030914-fe4d6282115f // indirect - golang.org/x/sys v0.0.0-20220412211240-33da011f77ad // indirect + golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10 // indirect google.golang.org/genproto v0.0.0-20201110150050-8816d57aaa9a // indirect google.golang.org/grpc v1.41.0 // indirect google.golang.org/protobuf v1.27.1 // indirect diff --git a/test/integration/consul-container/go.sum b/test/integration/consul-container/go.sum index 5c555e45dd..643599e21f 100644 --- a/test/integration/consul-container/go.sum +++ b/test/integration/consul-container/go.sum @@ -103,6 +103,7 @@ github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghf github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/checkpoint-restore/go-criu/v4 v4.1.0/go.mod h1:xUQBLp4RLc5zJtWY++yjOoMoB5lihDt7fai+75m+rGw= github.com/checkpoint-restore/go-criu/v5 v5.0.0/go.mod h1:cfwC0EG7HMUenopBsUf9d89JlCLQIfgVcNsNN0t6T2M= +github.com/checkpoint-restore/go-criu/v5 v5.3.0/go.mod h1:E/eQpaFtUKGOOSEBZgmKAcn+zUUwWxqcaKZlF54wK8E= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= @@ -111,6 +112,7 @@ github.com/cilium/ebpf v0.0.0-20200702112145-1c8d4c9ef775/go.mod h1:7cR51M8ViRLI github.com/cilium/ebpf v0.2.0/go.mod h1:To2CFviqOWL/M0gIMsvSMlqe7em/l1ALkX1PyjrX2Qs= github.com/cilium/ebpf v0.4.0/go.mod h1:4tRaxcgiL706VnOzHOdBlY8IEAIdxINsQBcU4xJJXRs= github.com/cilium/ebpf v0.6.2/go.mod h1:4tRaxcgiL706VnOzHOdBlY8IEAIdxINsQBcU4xJJXRs= +github.com/cilium/ebpf v0.7.0/go.mod h1:/oI2+1shJiTGAMgl6/RgJr36Eo1jzrRcAWbcXO2usCA= github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag= github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= @@ -139,6 +141,7 @@ github.com/containerd/console v0.0.0-20181022165439-0650fd9eeb50/go.mod h1:Tj/on github.com/containerd/console v0.0.0-20191206165004-02ecf6a7291e/go.mod h1:8Pf4gM6VEbTNRIT26AyyU7hxdQU3MvAvxVI0sc00XBE= github.com/containerd/console v1.0.1/go.mod h1:XUsP6YE/mKtz6bxc+I8UiKKTP04qjQL4qcS3XoQ5xkw= github.com/containerd/console v1.0.2/go.mod h1:ytZPjGgY2oeTkAONYafi2kSj0aYggsf8acV1PGKCbzQ= +github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U= github.com/containerd/containerd v1.2.10/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= github.com/containerd/containerd v1.3.0-beta.2.0.20190828155532-0293cbd26c69/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= github.com/containerd/containerd v1.3.0/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= @@ -228,6 +231,7 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3 github.com/creack/pty v1.1.11 h1:07n33Z8lZxZ2qwegKbObQohDhXDQxiMMz1NOUGYlesw= github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/cyphar/filepath-securejoin v0.2.2/go.mod h1:FpkQEhXnPnOthhzymB7CGsFk2G9VLXONKD9G7QGMM+4= +github.com/cyphar/filepath-securejoin v0.2.3/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= github.com/d2g/dhcp4 v0.0.0-20170904100407-a1d1b6c41b1c/go.mod h1:Ct2BUK8SB0YC1SMSibvLzxjeJLnrYEVLULFNiHY9YfQ= github.com/d2g/dhcp4client v1.0.0/go.mod h1:j0hNfjhrt2SxUOw55nL0ATM/z4Yt3t2Kd1mW34z5W5s= github.com/d2g/dhcp4server v0.0.0-20181031114812-7d4a0a7f59a5/go.mod h1:Eo87+Kg/IX2hfWJfwxMzLyuSZyxSoAug2nGa1G2QAi8= @@ -309,6 +313,7 @@ github.com/godbus/dbus v0.0.0-20180201030542-885f9cc04c9c/go.mod h1:/YcGZj5zSblf github.com/godbus/dbus v0.0.0-20190422162347-ade71ed3457e/go.mod h1:bBOAhwG1umN6/6ZUMtDFBMQR8jRg9O75tm9K00oMsK4= github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/godbus/dbus/v5 v5.0.6/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gogo/googleapis v1.2.0/go.mod h1:Njal3psf3qN6dwBtQfUmBZh2ybovJ0tlu3o/AC7HYjU= github.com/gogo/googleapis v1.4.0/go.mod h1:5YRNX2z1oM5gXdAkurHa942MDgEJyk02w4OecKY87+c= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= @@ -437,12 +442,10 @@ github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= github.com/hashicorp/mdns v1.0.4/go.mod h1:mtBihi+LeNXGtG8L9dX59gAEa12BDtBQSp4v/YAJqrc= -github.com/hashicorp/memberlist v0.3.0/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE= -github.com/hashicorp/memberlist v0.3.1 h1:MXgUXLqva1QvpVEDQW1IQLG0wivQAtmFlHRQ+1vWZfM= -github.com/hashicorp/memberlist v0.3.1/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE= -github.com/hashicorp/serf v0.9.7/go.mod h1:TXZNMjZQijwlDvp+r0b63xZ45H7JmCmgg4gpTwn9UV4= -github.com/hashicorp/serf v0.9.8 h1:JGklO/2Drf1QGa312EieQN3zhxQ+aJg6pG+aC3MFaVo= -github.com/hashicorp/serf v0.9.8/go.mod h1:TXZNMjZQijwlDvp+r0b63xZ45H7JmCmgg4gpTwn9UV4= +github.com/hashicorp/memberlist v0.5.0 h1:EtYPN8DpAURiapus508I4n9CzHs2W+8NZGbmmR/prTM= +github.com/hashicorp/memberlist v0.5.0/go.mod h1:yvyXLpo0QaGE59Y7hDTsTzDD25JYBZ4mHgHUZ8lrOI0= +github.com/hashicorp/serf v0.10.1 h1:Z1H2J60yRKvfDYAOZLd2MU0ND4AH/WDz7xYHDWQsIPY= +github.com/hashicorp/serf v0.10.1/go.mod h1:yL2t6BqATOLGc5HF7qbFkTfXoPIY0WZdWHfEvMqbG+4= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= @@ -575,8 +578,9 @@ github.com/opencontainers/runc v0.1.1/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59P github.com/opencontainers/runc v1.0.0-rc8.0.20190926000215-3e425f80a8c9/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= github.com/opencontainers/runc v1.0.0-rc9/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= github.com/opencontainers/runc v1.0.0-rc93/go.mod h1:3NOsor4w32B2tC0Zbl8Knk4Wg84SM2ImC1fxBuqJ/H0= -github.com/opencontainers/runc v1.0.2 h1:opHZMaswlyxz1OuGpBE53Dwe4/xF7EZTY0A2L/FpCOg= github.com/opencontainers/runc v1.0.2/go.mod h1:aTaHFFwQXuA71CiyxOdFFIorAoemI04suvGRQFzWTD0= +github.com/opencontainers/runc v1.1.4 h1:nRCz/8sKg6K6jgYAFLDlXzPeITBZJyX28DBVhWD+5dg= +github.com/opencontainers/runc v1.1.4/go.mod h1:1J5XiS+vdZ3wCyZybsuxXZWGrgSr8fFJHLXuG2PsnNg= github.com/opencontainers/runtime-spec v0.1.2-0.20190507144316-5b71a03e2700/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= github.com/opencontainers/runtime-spec v1.0.1/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= github.com/opencontainers/runtime-spec v1.0.2-0.20190207185410-29686dbc5559/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= @@ -587,6 +591,7 @@ github.com/opencontainers/runtime-tools v0.0.0-20181011054405-1d69bd0f9c39/go.mo github.com/opencontainers/selinux v1.6.0/go.mod h1:VVGKuOLlE7v4PJyT6h7mNWvq1rzqiriPsEqVhc+svHE= github.com/opencontainers/selinux v1.8.0/go.mod h1:RScLhm78qiWa2gbVCcGkC7tCGdgk3ogry1nUQF8Evvo= github.com/opencontainers/selinux v1.8.2/go.mod h1:MUIHuUEvKB1wtJjQdOyYRgOnLD2xAPP8dBsCoU0KuF8= +github.com/opencontainers/selinux v1.10.0/go.mod h1:2i0OySw99QjzBBQByd1Gr9gSjvuho1lHsJxIJ3gGbJI= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY= github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= @@ -645,6 +650,7 @@ github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdh github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 h1:nn5Wsu0esKSJiIVhscUtVbo7ada43DJhG55ua/hjS5I= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/seccomp/libseccomp-golang v0.9.1/go.mod h1:GbW5+tmTXfcxTToHLXlScSlAvWlF4P2Ca7zGrPiEpWo= +github.com/seccomp/libseccomp-golang v0.9.2-0.20220502022130-f33da4d89646/go.mod h1:JA8cRccbGaA1s33RQf7Y1+q9gHmZX1yB/z9WDN1C6fg= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.0.4-0.20170822132746-89742aefa4b2/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc= github.com/sirupsen/logrus v1.0.6/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc= @@ -909,10 +915,13 @@ golang.org/x/sys v0.0.0-20210426230700-d19ff857e887/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210906170528-6f6e22806c34/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211116061358-0a5406a5449c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220412211240-33da011f77ad h1:ntjMns5wyP/fN65tdBD4g8J5w8n015+iIIs9rtjXkY0= golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10 h1:WIoqL4EROvwiPdUtaip4VcDdpZ4kha7wBWZrbVKCIZg= +golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 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= diff --git a/ui/packages/consul-ui/.nvmrc b/ui/.nvmrc similarity index 100% rename from ui/packages/consul-ui/.nvmrc rename to ui/.nvmrc diff --git a/ui/packages/consul-lock-sessions/app/templates/dc/nodes/show/sessions.hbs b/ui/packages/consul-lock-sessions/app/templates/dc/nodes/show/sessions.hbs index b6cb1107a7..cdb31f6c65 100644 --- a/ui/packages/consul-lock-sessions/app/templates/dc/nodes/show/sessions.hbs +++ b/ui/packages/consul-lock-sessions/app/templates/dc/nodes/show/sessions.hbs @@ -79,6 +79,7 @@ as |route|> {{t 'routes.dc.nodes.show.sessions.empty.body' + canUseACLs=(can "use acls") htmlSafe=true }} diff --git a/ui/packages/consul-nspaces/app/templates/dc/nspaces/index.hbs b/ui/packages/consul-nspaces/app/templates/dc/nspaces/index.hbs index ec008d528a..e9c6360643 100644 --- a/ui/packages/consul-nspaces/app/templates/dc/nspaces/index.hbs +++ b/ui/packages/consul-nspaces/app/templates/dc/nspaces/index.hbs @@ -105,20 +105,15 @@ as |route|> >

- {{#if (gt items.length 0)}} - No namespaces found - {{else}} - Welcome to Namespaces - {{/if}} + {{t 'routes.dc.namespaces.index.empty.header' + items=items.length}}

- {{#if (gt items.length 0)}} - No namespaces where found matching that search, or you may not have access to view the namespaces you are searching for. - {{else}} - There don't seem to be any namespaces, or you may not have access to view namespaces yet. - {{/if}} + {{t 'routes.dc.namespaces.index.empty.body' + items=items.length + canUseACLs=(can 'use acls')}}

diff --git a/ui/packages/consul-partitions/app/templates/dc/partitions/index.hbs b/ui/packages/consul-partitions/app/templates/dc/partitions/index.hbs index 57609854cc..5b589c2767 100644 --- a/ui/packages/consul-partitions/app/templates/dc/partitions/index.hbs +++ b/ui/packages/consul-partitions/app/templates/dc/partitions/index.hbs @@ -111,20 +111,15 @@ as |route|> >

- {{#if (gt items.length 0)}} - No partitions found - {{else}} - Welcome to Partitions - {{/if}} + {{t 'routes.dc.partitions.index.empty.header' + items=items.length}}

- {{#if (gt items.length 0)}} - No partitions where found matching that search, or you may not have access to view the namespaces you are searching for. - {{else}} - There don't seem to be any partitions, or you may not have access to view partitions yet. - {{/if}} + {{t 'routes.dc.partitions.index.empty.body' + items=items.length + canUseACLs=(can 'use acls')}}

diff --git a/ui/packages/consul-peerings/app/components/consul/peer/list/index.hbs b/ui/packages/consul-peerings/app/components/consul/peer/list/index.hbs index 1295810fa0..d31e1fa15a 100644 --- a/ui/packages/consul-peerings/app/components/consul/peer/list/index.hbs +++ b/ui/packages/consul-peerings/app/components/consul/peer/list/index.hbs @@ -46,7 +46,7 @@ as |item index|> {{#if (can "write peer" item=item)}} diff --git a/ui/packages/consul-peerings/app/components/consul/peer/list/test-support.js b/ui/packages/consul-peerings/app/components/consul/peer/list/test-support.js index effb966f97..126e444b9b 100644 --- a/ui/packages/consul-peerings/app/components/consul/peer/list/test-support.js +++ b/ui/packages/consul-peerings/app/components/consul/peer/list/test-support.js @@ -10,9 +10,10 @@ export const selectors = { }, } }; -export default (collection, isPresent, attribute) => () => { +export default (collection, isPresent, attribute, actions) => () => { return collection(`${selectors.$} ${selectors.collection.$}`, { peer: isPresent(selectors.collection.peer.$), name: attribute('data-test-peer', selectors.collection.peer.name.$), + ...actions(['regenerate', 'delete']), }); }; diff --git a/ui/packages/consul-peerings/app/templates/dc/peers/index.hbs b/ui/packages/consul-peerings/app/templates/dc/peers/index.hbs index bdde408158..75ee9afa77 100644 --- a/ui/packages/consul-peerings/app/templates/dc/peers/index.hbs +++ b/ui/packages/consul-peerings/app/templates/dc/peers/index.hbs @@ -73,6 +73,7 @@ as |sort filters items|}} @aria={{hash label="Add peer connection" }} + class="peer-create-modal" as |modal|> {{did-insert (set this 'create' modal)}} @@ -193,21 +194,20 @@ as |sort filters items|}} >

- {{#if (gt items.length 0)}} - No peers found - {{else}} - Welcome to Peers - {{/if}} + {{t 'routes.dc.peers.index.empty.header' + items=items.length + }}

- {{#if (gt items.length 0)}} -

No peers where found matching that search, or you may not have access to view the peers you are searching for.

- {{else}} -

- Cluster peering is the recommended way to connect services across or within Consul datacenters. Peering is a one-to-one relationship in which each peer is either a open-source Consul datacenter or a Consul enterprise admin partition. There don't seem to be any peers for this {{if (can "use partitions") "admin partition" "datacenter"}}, or you may not have the peering:read permissions to access this view. -

- {{/if}} +

+ {{t 'routes.dc.peers.index.empty.body' + items=items.length + canUsePartitions=(can "use partitions") + canUseACLs=(can "use acls") + htmlSafe=true + }} +

  • +
  • {{#if (gt @dcs.length 1)}} - + as |disclosure| + > + {{@dc.Name}} -

    - Datacenters shown in this dropdown are available through WAN Federation. -

    - - - DATACENTERS - - {{#each menu.items as |item|}} - - + Datacenters shown in this dropdown are available through WAN Federation. +

    + + + DATACENTERS + + {{#each menu.items as |item|}} + + - {{item.Name}} + ) + }} + > + {{item.Name}} {{#if item.Primary}} Primary {{/if}} {{#if item.Local}} Local {{/if}} - - - {{/each}} - +
    +
    + {{/each}} +
    {{else}} -
    +
    {{@dcs.firstObject.Name}} - {{#if (env 'CONSUL_HCP_MANAGED_RUNTIME')}} - Self-managed - {{/if}} + {{#let (env 'CONSUL_HCP_MANAGED_RUNTIME') as |managedRuntime|}} + {{#if managedRuntime}} + {{capitalize managedRuntime}} + {{/if}} + {{/let}}
    {{/if}} -
  • - + \ No newline at end of file diff --git a/ui/packages/consul-ui/app/styles/app.scss b/ui/packages/consul-ui/app/styles/app.scss index c2ee814d83..78af922efc 100644 --- a/ui/packages/consul-ui/app/styles/app.scss +++ b/ui/packages/consul-ui/app/styles/app.scss @@ -1,5 +1,11 @@ @charset 'utf-8'; +/* css for hds */ +@import '@hashicorp/design-system-components'; + +/* tailwind before all the customizations */ +@import 'tailwind'; + /* all variables and custom queries including generic base variables */ @import 'variables'; diff --git a/ui/packages/consul-ui/app/styles/tailwind.scss b/ui/packages/consul-ui/app/styles/tailwind.scss new file mode 100644 index 0000000000..b5c61c9567 --- /dev/null +++ b/ui/packages/consul-ui/app/styles/tailwind.scss @@ -0,0 +1,3 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; diff --git a/ui/packages/consul-ui/app/templates/dc/intentions/index.hbs b/ui/packages/consul-ui/app/templates/dc/intentions/index.hbs index f847151a14..88406a1c43 100644 --- a/ui/packages/consul-ui/app/templates/dc/intentions/index.hbs +++ b/ui/packages/consul-ui/app/templates/dc/intentions/index.hbs @@ -114,6 +114,7 @@ as |route|> {{t 'routes.dc.intentions.index.empty.body' items=items.length + canUseACLs=(can "use acls") htmlSafe=true }} diff --git a/ui/packages/consul-ui/app/templates/dc/kv/edit.hbs b/ui/packages/consul-ui/app/templates/dc/kv/edit.hbs index 2bdd511f5e..282b670a40 100644 --- a/ui/packages/consul-ui/app/templates/dc/kv/edit.hbs +++ b/ui/packages/consul-ui/app/templates/dc/kv/edit.hbs @@ -63,6 +63,7 @@ as |parts|}} {{! We push on a '' here so make sure we get a trailing slash/separator }}
  • {{/let}} {{/let}} - \ No newline at end of file + diff --git a/ui/packages/consul-ui/app/templates/dc/kv/index.hbs b/ui/packages/consul-ui/app/templates/dc/kv/index.hbs index 97858ca953..ec0dc58497 100644 --- a/ui/packages/consul-ui/app/templates/dc/kv/index.hbs +++ b/ui/packages/consul-ui/app/templates/dc/kv/index.hbs @@ -108,7 +108,7 @@ as |sort filters parent items|}}
    1. Key / Values
    2. {{#each (slice 0 -2 (split parent.Key '/')) as |breadcrumb index|}} -
    3. {{breadcrumb}}
    4. +
    5. {{breadcrumb}}
    6. {{/each}}
    @@ -186,6 +186,7 @@ as |sort filters parent items|}} {{t 'routes.dc.kv.index.empty.body' items=items.length + canUseACLs=(can "use acls") htmlSafe=true }} @@ -207,4 +208,4 @@ as |sort filters parent items|}} {{/let}} - \ No newline at end of file + diff --git a/ui/packages/consul-ui/app/templates/dc/nodes/index.hbs b/ui/packages/consul-ui/app/templates/dc/nodes/index.hbs index 4afaefd5bd..a2096231f8 100644 --- a/ui/packages/consul-ui/app/templates/dc/nodes/index.hbs +++ b/ui/packages/consul-ui/app/templates/dc/nodes/index.hbs @@ -103,9 +103,22 @@ as |route|> {{t 'routes.dc.nodes.index.empty.body' items=items.length + canUseACLs=(can 'use acls') htmlSafe=true }} + +
  • + +
    @@ -115,4 +128,4 @@ as |route|>
    - \ No newline at end of file + diff --git a/ui/packages/consul-ui/app/templates/dc/services/index.hbs b/ui/packages/consul-ui/app/templates/dc/services/index.hbs index 6b8a13f6e8..c5d53cf301 100644 --- a/ui/packages/consul-ui/app/templates/dc/services/index.hbs +++ b/ui/packages/consul-ui/app/templates/dc/services/index.hbs @@ -113,6 +113,7 @@ as |sort filters items partition nspace|}} {{t 'routes.dc.services.index.empty.body' items=items.length + canUseACLs=(can "use acls") htmlSafe=true }} diff --git a/ui/packages/consul-ui/app/templates/dc/services/instance.hbs b/ui/packages/consul-ui/app/templates/dc/services/instance.hbs index 7c0755fed2..884e4eb01f 100644 --- a/ui/packages/consul-ui/app/templates/dc/services/instance.hbs +++ b/ui/packages/consul-ui/app/templates/dc/services/instance.hbs @@ -129,7 +129,9 @@ as |item|}}
    1. All Services
    2. -
    3. Service ({{item.Service.Service}})
    4. +
    5. + Service ({{item.Service.Service}}) +
    @@ -149,10 +151,12 @@ as |item|}}
    Service Name
    {{item.Service.Service}}
    -
    -
    Node Name
    -
    {{item.Node.Node}}
    -
    + {{#unless item.Node.Meta.synthetic-node}} +
    +
    Node Name
    +
    {{item.Node.Node}}
    +
    + {{/unless}} {{#if item.Service.PeerName}}
    Peer Name
    @@ -192,4 +196,4 @@ as |item|}} {{/let}} - \ No newline at end of file + diff --git a/ui/packages/consul-ui/app/templates/dc/services/show/intentions/index.hbs b/ui/packages/consul-ui/app/templates/dc/services/show/intentions/index.hbs index 776910103f..763e47e63a 100644 --- a/ui/packages/consul-ui/app/templates/dc/services/show/intentions/index.hbs +++ b/ui/packages/consul-ui/app/templates/dc/services/show/intentions/index.hbs @@ -102,6 +102,7 @@ as |route|> {{t 'routes.dc.services.show.intentions.index.empty.body' items=items.length + canUseACLs=(can 'use acls') htmlSafe=true }} diff --git a/ui/packages/consul-ui/config/environment.js b/ui/packages/consul-ui/config/environment.js index 66d06aa60a..4913c4cc82 100644 --- a/ui/packages/consul-ui/config/environment.js +++ b/ui/packages/consul-ui/config/environment.js @@ -96,6 +96,7 @@ module.exports = function (environment, $ = process.env) { CONSUL_DOCS_URL: 'https://www.consul.io/docs', CONSUL_DOCS_LEARN_URL: 'https://learn.hashicorp.com', CONSUL_DOCS_API_URL: 'https://www.consul.io/api', + CONSUL_DOCS_DEVELOPER_URL: 'https://developer.hashicorp.com/consul/docs', CONSUL_COPYRIGHT_URL: 'https://www.hashicorp.com', }); switch (true) { diff --git a/ui/packages/consul-ui/docs/hds.mdx b/ui/packages/consul-ui/docs/hds.mdx new file mode 100644 index 0000000000..5e67bc7633 --- /dev/null +++ b/ui/packages/consul-ui/docs/hds.mdx @@ -0,0 +1,102 @@ +--- +title: Hashicorp Design System +--- + +# Hashicorp Design System + +The application integrates setup that make it possible to use the [Hashicorp Design System (HDS)](https://github.com/hashicorp/design-system) in the application. + +## Design Tokens + +HDS ships a set of [design tokens](https://design-system-components-hashicorp.vercel.app/foundations/tokens), which are implemented as CSS custom properties. +To make it easy to work with these design tokens without having to work with verbose CSS properties all over the place, we have setup a [TailwindCSS](https://tailwindcss.com/)-configuration that makes the color tokens +available via functional [utility classes](https://tailwindcss.com/docs/customizing-colors). + +### Colors + +You can work with any color from the HDS by prefixing colors with the `hds-`-prefix. The `hds-`-prefix makes it easy to see what classes are auto-generated from HDS. When we wanted to color a header tag with the `consul-brand`-color we could do it like this for example: + +```hbs preview-template +

    + HDS is awesome +

    +``` + +Regular tailwindCss colors aren't available. You can only use colors defined in HDS with this pattern. + +```hbs preview-template +

    + TailwindCSS colors won't work +

    +``` + +### Other tokens + +Other tokens than colors are available via the provided `hds`-[helper-classes](https://design-system-components-hashicorp.vercel.app/foundations/typography) made available via `@hashicorp/design-system-tokens`. + +You for example would work with HDS' typography tokens in this way: + +```hbs preview-template +

    + A paragraph styled via HDS. +

    +``` + +### Combining HDS and Tailwind + +Because we are working with a customized tailwind configuration it is easy to combine HDS design tokens with regular tailwind utility classes: + +```hbs preview-template + +``` + +### Components + +All components from Hashicorp Design System are available for you to use. Here's an example that shows how to implement a navigation bar with HDS and Tailwind in combination. + +```hbs preview-template + +``` diff --git a/ui/packages/consul-ui/ember-cli-build.js b/ui/packages/consul-ui/ember-cli-build.js index 4fe7919075..aa5d243074 100644 --- a/ui/packages/consul-ui/ember-cli-build.js +++ b/ui/packages/consul-ui/ember-cli-build.js @@ -111,6 +111,29 @@ module.exports = function (defaults, $ = process.env) { overwrite: true, } ); + // we switched to postcss - because ember-cli-postcss only operates on the + // styles tree we need to make sure we write the css files from "sub-apps" + // into `app/styles` manually and prefix them with `consul-ui` because that + // is what the codebase expects from before when using ember-cli-sass. + trees.styles = mergeTrees( + [ + new Funnel('app/styles', { include: ['**/*.{scss,css}'] }), + new Funnel('app', { include: ['components/**/*.{scss,css}'], destDir: 'consul-ui' }), + ].concat( + apps + .filter((item) => exists(`${item.path}/app`)) + .map( + (item) => + new Funnel(`${item.path}/app`, { + include: ['**/*.{scss,css}'], + destDir: 'consul-ui', + }) + ) + ), + { + overwrite: true, + } + ); trees.vendor = mergeTrees( [new Funnel('vendor')].concat(apps.map((item) => new Funnel(`${item.path}/vendor`))) ); @@ -132,6 +155,30 @@ module.exports = function (defaults, $ = process.env) { 'ember-cli-babel': { includePolyfill: true, }, + postcssOptions: { + compile: { + extension: 'scss', + plugins: [ + { + module: require('@csstools/postcss-sass'), + options: { + includePaths: [ + '../../node_modules/@hashicorp/design-system-tokens/dist/products/css', + ], + }, + }, + { + module: require('tailwindcss'), + options: { + config: './tailwind.config.js', + }, + }, + { + module: require('autoprefixer'), + }, + ], + }, + }, 'ember-cli-string-helpers': { only: [ 'capitalize', diff --git a/ui/packages/consul-ui/lib/startup/templates/body.html.js b/ui/packages/consul-ui/lib/startup/templates/body.html.js index 4aeb0f9a20..5ce4af49d1 100644 --- a/ui/packages/consul-ui/lib/startup/templates/body.html.js +++ b/ui/packages/consul-ui/lib/startup/templates/body.html.js @@ -71,6 +71,7 @@ ${ {{end}} {{if .HCPEnabled}} + {{end}} ` diff --git a/ui/packages/consul-ui/mock-api/v1/health/service/_ b/ui/packages/consul-ui/mock-api/v1/health/service/_ index 1a9bbedc25..aaf9a1e0f1 100644 --- a/ui/packages/consul-ui/mock-api/v1/health/service/_ +++ b/ui/packages/consul-ui/mock-api/v1/health/service/_ @@ -18,6 +18,16 @@ const proxy = service.indexOf('-proxy') const sidecar = service.indexOf('-sidecar-proxy') const id = (proxy !== -1 ? service.slice(0, -6) + '-with-id-proxy' : service + '-with-id'); + const externalSource = fake.helpers.randomize([ + 'consul-api-gateway', + 'vault', + 'nomad', + 'terraform', + 'kubernetes', + 'aws', + 'lambda', + '' + ]); let kind = ''; switch(true) { case service.endsWith('-mesh-gateway'): @@ -42,7 +52,10 @@ "Address":"${ip}", "Datacenter":"dc1", "TaggedAddresses":{"lan":"${ip}","wan":"${ip}"}, - "Meta":{"${service}-network-segment":""}, + "Meta":{ + "${service}-network-segment":"", + "synthetic-node":${externalSource === 'kubernetes' ? "true" : "false"} + }, ${typeof location.search.peer !== 'undefined' ? ` "PeerName": "${location.search.peer}", ` : ``} @@ -87,16 +100,7 @@ ${typeof location.search.partition !== 'undefined' ? ` ${ fake.random.number({min: 1, max: 10}) > 2 ? ` "Meta": { "consul-dashboard-url": "${fake.internet.protocol()}://${fake.internet.domainName()}/?id={{Service}}", - "external-source": "${fake.helpers.randomize([ - 'consul-api-gateway', - 'vault', - 'nomad', - 'terraform', - 'kubernetes', - 'aws', - 'lambda', - '' - ])}" + "external-source": "${externalSource}" }, ` : ` "Meta": null, diff --git a/ui/packages/consul-ui/package.json b/ui/packages/consul-ui/package.json index bd1ea42d4a..da781d9928 100644 --- a/ui/packages/consul-ui/package.json +++ b/ui/packages/consul-ui/package.json @@ -60,16 +60,21 @@ "@babel/helper-call-delegate": "^7.10.1", "@babel/plugin-proposal-class-properties": "^7.10.1", "@babel/plugin-proposal-object-rest-spread": "^7.5.5", + "@csstools/postcss-sass": "^5.0.1", "@docfy/ember": "^0.4.1", "@ember/optional-features": "^2.0.0", "@ember/render-modifiers": "^1.0.2", "@ember/test-helpers": "^2.1.4", "@glimmer/component": "^1.0.3", "@glimmer/tracking": "^1.0.3", - "@hashicorp/ember-cli-api-double": "^3.1.0", + "@hashicorp/design-system-components": "^1.0.4", + "@hashicorp/design-system-tokens": "^1.0.0", + "@hashicorp/ember-cli-api-double": "^4.0.0", + "@hashicorp/ember-flight-icons": "^2.0.12", "@lit/reactive-element": "^1.2.1", "@xstate/fsm": "^1.4.0", "a11y-dialog": "^6.0.1", + "autoprefixer": "^10.4.8", "babel-eslint": "^10.0.3", "babel-loader": "^8.1.0", "babel-plugin-ember-modules-api-polyfill": "^3.2.0", @@ -88,6 +93,7 @@ "consul-nspaces": "*", "consul-partitions": "*", "consul-peerings": "*", + "css": "^3.0.0", "css.escape": "^1.5.1", "d3-array": "^2.8.0", "d3-scale": "^3.2.3", @@ -98,7 +104,7 @@ "deepmerge": "^4.2.2", "ember-array-fns": "^1.4.0", "ember-assign-helper": "^0.3.0", - "ember-auto-import": "^1.10.1", + "ember-auto-import": "^2.4.2", "ember-can": "^4.2.0", "ember-changeset-conditional-validations": "^0.6.0", "ember-changeset-validations": "~3.9.0", @@ -112,12 +118,12 @@ "ember-cli-htmlbars": "^5.3.1", "ember-cli-inject-live-reload": "^2.0.2", "ember-cli-page-object": "^1.17.10", - "ember-cli-sass": "^10.0.1", + "ember-cli-postcss": "^8.1.0", "ember-cli-sri": "^2.1.1", "ember-cli-string-helpers": "^5.0.0", "ember-cli-template-lint": "^2.0.1", "ember-cli-terser": "^4.0.1", - "ember-cli-yadda": "^0.6.0", + "ember-cli-yadda": "^0.7.0", "ember-collection": "^1.0.0-alpha.9", "ember-compatibility-helpers": "^1.2.5", "ember-composable-helpers": "^5.0.0", @@ -179,12 +185,14 @@ "remark-autolink-headings": "^6.0.1", "remark-hbs": "^0.4.0", "sass": "^1.28.0", + "tailwindcss": "^3.1.8", "tape": "^5.0.1", "text-encoding": "^0.7.0", "tippy.js": "^6.2.7", "torii": "^0.10.1", "unist-util-visit": "^2.0.3", - "wayfarer": "^7.0.1" + "wayfarer": "^7.0.1", + "webpack": "^5.74.0" }, "engines": { "node": ">=10 <=14" @@ -200,5 +208,8 @@ "lib/custom-element", "lib/startup" ] + }, + "volta": { + "node": "14.20.1" } } diff --git a/ui/packages/consul-ui/tailwind.config.js b/ui/packages/consul-ui/tailwind.config.js new file mode 100644 index 0000000000..d10c682315 --- /dev/null +++ b/ui/packages/consul-ui/tailwind.config.js @@ -0,0 +1,45 @@ +/** + * A function that parses the `tokens.css`-file from `@hashicorp/design-system-tokens` + * and creates a map out of it that can be used to build up a color configuration + * in `tailwind.config.js`. + * + * @param {string} tokensPath - The path to `tokens.css` from `@hashicorp/design-system-tokens` + * @returns { { [string]: string } } An object that contains color names as keys and color values as values. + */ +function colorMapFromTokens(tokensPath) { + const css = require('css'); + const path = require('path'); + const fs = require('fs'); + + const hdsTokensPath = path.join(__dirname, tokensPath); + + const tokensFile = fs.readFileSync(hdsTokensPath, { + encoding: 'utf8', + flag: 'r', + }); + + const ast = css.parse(tokensFile); + const rootVars = ast.stylesheet.rules.filter((r) => r.type !== 'comment')[0]; + + // filter out all colors and then create a map out of them + const vars = rootVars.declarations.filter((d) => d.type !== 'comment'); + const colorPropertyNameCleanupRegex = /^--token-color-(palette-)?/; + const colors = vars.filter((d) => d.property.match(/^--token-color-/)); + + return colors.reduce((acc, d) => { + acc[d.property.replace(colorPropertyNameCleanupRegex, 'hds-')] = d.value; + return acc; + }, {}); +} + +/** @type {import('tailwindcss').Config} */ +module.exports = { + content: ['./app/**/*.{html,js,hbs,mdx}', './docs/**/*.{html,js,hbs,mdx}'], + theme: { + colors: colorMapFromTokens( + '../../node_modules/@hashicorp/design-system-tokens/dist/products/css/tokens.css' + ), + extend: {}, + }, + plugins: [], +}; diff --git a/ui/packages/consul-ui/tests/acceptance/dc/intentions/index.feature b/ui/packages/consul-ui/tests/acceptance/dc/intentions/index.feature index d5d888f670..ba4bbe3566 100644 --- a/ui/packages/consul-ui/tests/acceptance/dc/intentions/index.feature +++ b/ui/packages/consul-ui/tests/acceptance/dc/intentions/index.feature @@ -71,3 +71,28 @@ Feature: dc / intentions / index --- Then the url should be /dc-1/intentions Then I don't see customResourceNotice on the intentionList + Scenario: Viewing an empty intentions page with acl enabled + Given 1 datacenter model with the value "dc-1" + And 0 intention models + When I visit the intentions page for yaml + --- + dc: dc-1 + --- + Then the url should be /dc-1/intentions + And the title should be "Intentions - Consul" + Then I see 0 intention models on the intentionList component + And I see the text "There don't seem to be any Intentions in this Consul cluster, or you may not have intentions:read permissions access to this view." in ".empty-state p" + And I see the "[data-test-empty-state-login]" element + Scenario: Viewing an empty intentions page with acl disabled + Given ACLs are disabled + Given 1 datacenter model with the value "dc-1" + And 0 intention models + When I visit the intentions page for yaml + --- + dc: dc-1 + --- + Then the url should be /dc-1/intentions + And the title should be "Intentions - Consul" + Then I see 0 intention models on the intentionList component + And I see the text "There don't seem to be any Intentions in this Consul cluster." in ".empty-state p" + And I don't see the "[data-test-empty-state-login]" element diff --git a/ui/packages/consul-ui/tests/acceptance/dc/nodes/index.feature b/ui/packages/consul-ui/tests/acceptance/dc/nodes/index.feature index 735c222e9e..beb759c61e 100644 --- a/ui/packages/consul-ui/tests/acceptance/dc/nodes/index.feature +++ b/ui/packages/consul-ui/tests/acceptance/dc/nodes/index.feature @@ -95,3 +95,16 @@ Feature: dc / nodes / index --- And I see 1 node model And I see 1 node model with the name "node-02" + Scenario: Viewing an empty nodes page with acl enabled + Given 1 datacenter model with the value "dc-1" + And 0 nodes models + When I visit the nodes page for yaml + --- + dc: dc-1 + --- + Then the url should be /dc-1/nodes + And the title should be "Nodes - Consul" + Then I see 0 node models + And I see the text "There don't seem to be any registered Nodes in this Consul cluster, or you may not have service:read and node:read permissions access to this view." in ".empty-state p" + And I see the "[data-test-empty-state-login]" element + diff --git a/ui/packages/consul-ui/tests/acceptance/dc/nodes/sessions/list.feature b/ui/packages/consul-ui/tests/acceptance/dc/nodes/sessions/list.feature index 916781d18c..54db6bbd4c 100644 --- a/ui/packages/consul-ui/tests/acceptance/dc/nodes/sessions/list.feature +++ b/ui/packages/consul-ui/tests/acceptance/dc/nodes/sessions/list.feature @@ -51,3 +51,36 @@ Feature: dc / nodes / sessions / list: List Lock Sessions - 18ms - 15s --- + Scenario: Given 0 sessions with ACLs enabled + Given 1 datacenter model with the value "dc1" + And 1 node model from yaml + --- + ID: node-0 + --- + And 0 session models + When I visit the node page for yaml + --- + dc: dc1 + node: node-0 + --- + And I click lockSessions on the tabs + Then I see lockSessionsIsSelected on the tabs + And I see the text "Consul provides a session mechanism which can be used to build distributed locks. Sessions act as a binding layer between Nodes, Health Checks, and Key/Value data. There are currently no Lock Sessions present, or you may not have key:read or session:read permissions." in ".empty-state p" + And I see the "[data-test-empty-state-login]" element + Scenario: Given 0 sessions with ACLs disabled + Given ACLs are disabled + Given 1 datacenter model with the value "dc1" + And 1 node model from yaml + --- + ID: node-0 + --- + And 0 session models + When I visit the node page for yaml + --- + dc: dc1 + node: node-0 + --- + And I click lockSessions on the tabs + Then I see lockSessionsIsSelected on the tabs + And I see the text "Consul provides a session mechanism which can be used to build distributed locks. Sessions act as a binding layer between Nodes, Health Checks, and Key/Value data. There are currently no Lock Sessions present." in ".empty-state p" + And I don't see the "[data-test-empty-state-login]" element diff --git a/ui/packages/consul-ui/tests/acceptance/dc/peers/create.feature b/ui/packages/consul-ui/tests/acceptance/dc/peers/create.feature new file mode 100644 index 0000000000..4cfe29db70 --- /dev/null +++ b/ui/packages/consul-ui/tests/acceptance/dc/peers/create.feature @@ -0,0 +1,43 @@ +@setupApplicationTest +Feature: dc / peers / create: Peer Create Token + Scenario: + Given 1 datacenter model with the value "dc-1" + And the url "/v1/peering/token" responds with from yaml + --- + body: + PeeringToken: an-encoded-token + --- + When I visit the peers page for yaml + --- + dc: dc-1 + --- + Then the url should be /dc-1/peers + And I click create + Then I fill in with yaml + --- + Name: new-peer + --- + When I click ".peer-create-modal .modal-dialog-footer button" + Then a POST request was made to "/v1/peering/token" from yaml + --- + body: + PeerName: new-peer + --- + Then I see the text "an-encoded-token" in ".consul-peer-form-generate code" + When I click ".consul-peer-form-generate button[type=reset]" + And the url "/v1/peering/token" responds with from yaml + --- + body: + PeeringToken: another-encoded-token + --- + Then I fill in with yaml + --- + Name: another-new-peer + --- + When I click ".peer-create-modal .modal-dialog-footer button" + Then a POST request was made to "/v1/peering/token" from yaml + --- + body: + PeerName: another-new-peer + --- + Then I see the text "another-encoded-token" in ".consul-peer-form-generate code" diff --git a/ui/packages/consul-ui/tests/acceptance/dc/peers/delete.feature b/ui/packages/consul-ui/tests/acceptance/dc/peers/delete.feature new file mode 100644 index 0000000000..7cd6b4b312 --- /dev/null +++ b/ui/packages/consul-ui/tests/acceptance/dc/peers/delete.feature @@ -0,0 +1,47 @@ +@setupApplicationTest +Feature: dc / peers / delete: Deleting items with confirmations, success and error notifications + Background: + Given 1 datacenter model with the value "datacenter" + Scenario: Deleting a peer model from the listing page + Given 1 peer model from yaml + --- + Name: peer-name + State: ACTIVE + --- + When I visit the peers page for yaml + --- + dc: datacenter + --- + And I click actions on the peers + And I click delete on the peers + And I click confirmDelete on the peers + Then a DELETE request was made to "/v1/peering/peer-name" + And "[data-notification]" has the "notification-delete" class + And "[data-notification]" has the "success" class + Scenario: Deleting a peer from the peer listing page with error + Given 1 peer model from yaml + --- + Name: peer-name + State: ACTIVE + --- + When I visit the peers page for yaml + --- + dc: datacenter + --- + Given the url "/v1/peering/peer-name" responds with a 500 status + And I click actions on the peers + And I click delete on the peers + And I click confirmDelete on the peers + And "[data-notification]" has the "notification-update" class + And "[data-notification]" has the "error" class + Scenario: A Peer currently deleting cannot be deleted + Given 1 peer model from yaml + --- + Name: peer-name + State: DELETING + --- + When I visit the peers page for yaml + --- + dc: datacenter + --- + And I don't see actions on the peers diff --git a/ui/packages/consul-ui/tests/acceptance/dc/peers/establish.feature b/ui/packages/consul-ui/tests/acceptance/dc/peers/establish.feature new file mode 100644 index 0000000000..f037d45725 --- /dev/null +++ b/ui/packages/consul-ui/tests/acceptance/dc/peers/establish.feature @@ -0,0 +1,30 @@ +@setupApplicationTest +Feature: dc / peers / establish: Peer Establish Peering + Scenario: + Given 1 datacenter model with the value "dc-1" + And the url "/v1/peering/token" responds with from yaml + --- + body: + PeeringToken: an-encoded-token + --- + When I visit the peers page for yaml + --- + dc: dc-1 + --- + Then the url should be /dc-1/peers + And I click create + When I click "[data-test-tab=tab_establish-peering] button" + Then I fill in with yaml + --- + Name: new-peer + Token: an-encoded-token + --- + When I click ".peer-create-modal .modal-dialog-footer button" + Then a POST request was made to "/v1/peering/establish" from yaml + --- + body: + PeerName: new-peer + PeeringToken: an-encoded-token + --- + And "[data-notification]" has the "notification-update" class + And "[data-notification]" has the "success" class diff --git a/ui/packages/consul-ui/tests/acceptance/dc/peers/index.feature b/ui/packages/consul-ui/tests/acceptance/dc/peers/index.feature index a0a0bd06e3..8937384ae2 100644 --- a/ui/packages/consul-ui/tests/acceptance/dc/peers/index.feature +++ b/ui/packages/consul-ui/tests/acceptance/dc/peers/index.feature @@ -34,4 +34,21 @@ Feature: dc / peers / index: Peers List --- And I see 1 peer model And I see 1 peer model with the name "a-peer" - + Scenario: Empty state searching peers + Then I fill in with yaml + --- + s: no-match + --- + And I see 0 peer model + Then I see the text "No peers found" in ".empty-state h2" + Then I see the text "No peers were found matching that search, or you may not have the peering:read permissions to access this view." in ".empty-state p" + And I see the "[data-test-empty-state-login]" element + Scenario: Empty state searching peers with ACLs disabled + And ACLs are disabled + Then I fill in with yaml + --- + s: no-match + --- + And I see 0 peer model + Then I see the text "No peers found" in ".empty-state h2" + Then I see the text "No peers were found matching that search." in ".empty-state p" diff --git a/ui/packages/consul-ui/tests/acceptance/dc/peers/regenerate.feature b/ui/packages/consul-ui/tests/acceptance/dc/peers/regenerate.feature new file mode 100644 index 0000000000..a925f7887b --- /dev/null +++ b/ui/packages/consul-ui/tests/acceptance/dc/peers/regenerate.feature @@ -0,0 +1,21 @@ +@setupApplicationTest +Feature: dc / peers / regenerate: Regenerate Peer Token + Scenario: + Given 1 datacenter model with the value "datacenter" + And 1 peer model from yaml + --- + Name: peer-name + State: ACTIVE + --- + And the url "/v1/peering/token" responds with from yaml + --- + body: + PeeringToken: an-encoded-token + --- + When I visit the peers page for yaml + --- + dc: datacenter + --- + And I click actions on the peers + And I click regenerate on the peers + Then I see the text "an-encoded-token" in ".consul-peer-form-generate code" diff --git a/ui/packages/consul-ui/tests/acceptance/dc/services/index.feature b/ui/packages/consul-ui/tests/acceptance/dc/services/index.feature index 0007127d64..ecfbc80323 100644 --- a/ui/packages/consul-ui/tests/acceptance/dc/services/index.feature +++ b/ui/packages/consul-ui/tests/acceptance/dc/services/index.feature @@ -165,3 +165,28 @@ Feature: dc / services / index: List Services Then I see 2 service models And I don't see associatedServiceCount on the services.0 And I see associatedServiceCount on the services.1 + Scenario: Viewing the services index page with no services and ACLs enabled + Given 1 datacenter model with the value "dc-1" + And 0 service models + When I visit the services page for yaml + --- + dc: dc-1 + --- + Then the url should be /dc-1/services + And the title should be "Services - Consul" + Then I see 0 service models + And I see the text "There don't seem to be any registered services in this Consul cluster, or you may not have service:read and node:read access to this view. Use Terraform, Kubernetes CRDs, Vault, or the Consul CLI to register Services." in ".empty-state p" + And I see the "[data-test-empty-state-login]" element + Scenario: Viewing the services index page with no services and ACLs disabled + Given ACLs are disabled + Given 1 datacenter model with the value "dc-1" + And 0 service models + When I visit the services page for yaml + --- + dc: dc-1 + --- + Then the url should be /dc-1/services + And the title should be "Services - Consul" + Then I see 0 service models + And I see the text "There don't seem to be any registered services in this Consul cluster." in ".empty-state p" + And I don't see the "[data-test-empty-state-login]" element diff --git a/ui/packages/consul-ui/tests/acceptance/dc/services/instances/show.feature b/ui/packages/consul-ui/tests/acceptance/dc/services/instances/show.feature index bb45b5a0b3..55b87cedb1 100644 --- a/ui/packages/consul-ui/tests/acceptance/dc/services/instances/show.feature +++ b/ui/packages/consul-ui/tests/acceptance/dc/services/instances/show.feature @@ -2,7 +2,7 @@ Feature: dc / services / instances / show: Show Service Instance Background: Given 1 datacenter model with the value "dc1" - And 2 instance models from yaml + And 3 instance models from yaml --- - Service: ID: service-0-with-id @@ -45,6 +45,15 @@ Feature: dc / services / instances / show: Show Service Instance ServiceID: "" Output: Output of check Status: critical + - Service: + ID: service-2-with-id + Meta: + external-source: kubernetes + synthetic-node: true + Node: + Node: node-2 + Meta: + synthetic-node: true --- Scenario: A Service instance has no Proxy Given 1 proxy model from yaml @@ -62,6 +71,7 @@ Feature: dc / services / instances / show: Show Service Instance --- Then the url should be /dc1/services/service-0/instances/another-node/service-1-with-id/health-checks Then I see externalSource like "nomad" + And I see the text "another-node" in "[data-test-service-instance-node-name]" And I don't see upstreams on the tabs And I see healthChecksIsSelected on the tabs @@ -115,3 +125,14 @@ Feature: dc / services / instances / show: Show Service Instance --- Then the url should be /dc1/services/service-0/instances/node-0/service-0-with-id/health-checks And I don't see proxy on the tabs + Scenario: A Service instance with a synthetic node does not display the node name + When I visit the instance page for yaml + --- + dc: dc1 + service: service-2 + node: node-2 + id: service-2-with-id + --- + Then the url should be /dc1/services/service-2/instances/node-2/service-2-with-id/health-checks + Then I see externalSource like "kubernetes" + And I don't see the text "node-2" in "[data-test-service-instance-node-name]" diff --git a/ui/packages/consul-ui/tests/acceptance/steps/dc/peers/create-steps.js b/ui/packages/consul-ui/tests/acceptance/steps/dc/peers/create-steps.js new file mode 100644 index 0000000000..06173e1e01 --- /dev/null +++ b/ui/packages/consul-ui/tests/acceptance/steps/dc/peers/create-steps.js @@ -0,0 +1,10 @@ +import steps from '../../steps'; + +// step definitions that are shared between features should be moved to the +// tests/acceptance/steps/steps.js file + +export default function (assert) { + return steps(assert).then('I should find a file', function () { + assert.ok(true, this.step); + }); +} diff --git a/ui/packages/consul-ui/tests/acceptance/steps/dc/peers/delete-steps.js b/ui/packages/consul-ui/tests/acceptance/steps/dc/peers/delete-steps.js new file mode 100644 index 0000000000..06173e1e01 --- /dev/null +++ b/ui/packages/consul-ui/tests/acceptance/steps/dc/peers/delete-steps.js @@ -0,0 +1,10 @@ +import steps from '../../steps'; + +// step definitions that are shared between features should be moved to the +// tests/acceptance/steps/steps.js file + +export default function (assert) { + return steps(assert).then('I should find a file', function () { + assert.ok(true, this.step); + }); +} diff --git a/ui/packages/consul-ui/tests/acceptance/steps/dc/peers/establish-steps.js b/ui/packages/consul-ui/tests/acceptance/steps/dc/peers/establish-steps.js new file mode 100644 index 0000000000..06173e1e01 --- /dev/null +++ b/ui/packages/consul-ui/tests/acceptance/steps/dc/peers/establish-steps.js @@ -0,0 +1,10 @@ +import steps from '../../steps'; + +// step definitions that are shared between features should be moved to the +// tests/acceptance/steps/steps.js file + +export default function (assert) { + return steps(assert).then('I should find a file', function () { + assert.ok(true, this.step); + }); +} diff --git a/ui/packages/consul-ui/tests/acceptance/steps/dc/peers/regenerate-steps.js b/ui/packages/consul-ui/tests/acceptance/steps/dc/peers/regenerate-steps.js new file mode 100644 index 0000000000..06173e1e01 --- /dev/null +++ b/ui/packages/consul-ui/tests/acceptance/steps/dc/peers/regenerate-steps.js @@ -0,0 +1,10 @@ +import steps from '../../steps'; + +// step definitions that are shared between features should be moved to the +// tests/acceptance/steps/steps.js file + +export default function (assert) { + return steps(assert).then('I should find a file', function () { + assert.ok(true, this.step); + }); +} diff --git a/ui/packages/consul-ui/tests/pages.js b/ui/packages/consul-ui/tests/pages.js index 6bded9444a..c3ceb2af58 100644 --- a/ui/packages/consul-ui/tests/pages.js +++ b/ui/packages/consul-ui/tests/pages.js @@ -111,7 +111,7 @@ const consulNspaceList = consulNspaceListFactory( text, morePopoverMenu ); -const consulPeerList = consulPeerListFactory(collection, isPresent, attribute); +const consulPeerList = consulPeerListFactory(collection, isPresent, attribute, morePopoverMenu); const consulKvList = consulKvListFactory(collection, clickable, attribute, deletable); const consulTokenList = consulTokenListFactory( collection, diff --git a/ui/packages/consul-ui/translations/routes/en-us.yaml b/ui/packages/consul-ui/translations/routes/en-us.yaml index d2f5ef0adb..e6ed415227 100644 --- a/ui/packages/consul-ui/translations/routes/en-us.yaml +++ b/ui/packages/consul-ui/translations/routes/en-us.yaml @@ -66,8 +66,13 @@ dc: {items, select, 0 {There don't seem to be any registered Nodes in this Consul cluster} other {No Nodes were found matching your search} - }, or you may not have service:read and node:read permissions access to this view. + }{canUseACLs, select, + true {, or you may not have service:read and node:read permissions access to this view.} + other {.} + }

    + documentation: Documentation on Nodes + learn: Take the tutorial show: rtt: title: Round Trip Time @@ -79,7 +84,10 @@ dc: header: Welcome to Lock Sessions body: |

    - Consul provides a session mechanism which can be used to build distributed locks. Sessions act as a binding layer between Nodes, Health Checks, and Key/Value data. There are currently no Lock Sessions present, or you may not have key:read or session:read permissions. + Consul provides a session mechanism which can be used to build distributed locks. Sessions act as a binding layer between Nodes, Health Checks, and Key/Value data. There are currently no Lock Sessions present{canUseACLs, select, + true {, or you may not have key:read or session:read permissions.} + other {.} + }

    services: title: Service Instances @@ -107,6 +115,23 @@ dc:

    peers: index: + empty: + header: | + {items, select, + 0 {Welcome to Peers} + other {No peers found} + } + body: | + {items, select, + 0 {Cluster peering is the recommended way to connect services across or within Consul datacenters. Peering is a one-to-one relationship in which each peer is either a open-source Consul datacenter or a Consul enterprise admin partition. There don't seem to be any peers for this {canUsePartitions, select, + true {admin partition} + other {datacenter} + }} + other {No peers were found matching that search} + }{canUseACLs, select, + true {, or you may not have the peering:read permissions to access this view.} + other {.} + } detail: imported: count: | @@ -116,6 +141,44 @@ dc: count: | {count} exported services tooltip: The number of services exported from {name} + partitions: + index: + empty: + header: | + {items, select, + 0 {Welcome to Partitions} + other {No partitions found} + } + body: | + {items, select, + 0 {There don't seem to be any partitions{canUseACLs, select, + true {, or you may not have access to view partitions yet.} + other {.} + }} + other {No partitions were found matching that search{canUseACLs, select, + true {, or you may not have access to view the namesapces you are searching} + other {.} + }} + } + namespaces: + index: + empty: + header: | + {items, select, + 0 {Welcome to Namespaces} + other {No namespaces found} + } + body: | + {items, select, + 0 {No namespaces were found matching that search{canUseACLs, select, + true {, or you may not have access to view the namespaces you are searching for.} + other {.} + }} + other {There don't seem to be any namespaces{canUseACLs, select, + true {, or you may not have access to view namespaces yet.} + other {.} + }} + } services: index: empty: @@ -129,7 +192,10 @@ dc: {items, select, 0 {There don't seem to be any registered services in this Consul cluster} other {No Services were found matching your search} - }, or you may not have service:read and node:read access to this view. Use Terraform, Kubernetes CRDs, Vault, or the Consul CLI to register Services. + }{canUseACLs, select, + true {, or you may not have service:read and node:read access to this view. Use Terraform, Kubernetes CRDs, Vault, or the Consul CLI to register Services.} + other {.} + }

    instance: exposedpaths: @@ -185,25 +251,25 @@ dc: header: Limited Access body: This service may have dependencies you won’t see because you don’t have access to them. default-allow: - header: Intentions are set to default allow - body: Your Intention settings are currently set to default allow. This means that this view will show connections to every service in your cluster. We recommend changing your Intention settings to default deny and creating specific Intentions for upstream and downstream services for this view to be useful. + header: Restrict which services can connect + body: Your current ACL settings allow all services to connect to each other. Either create a deny intention between all services, or set your default ACL policy to deny to improve your security posture and make this topology view reflect the actual upstreams and downstreams of this service. footer: |

    - Edit Intentions + Create a wildcard deny Intention

    wildcard-intention: - header: Permissive Intention - body: One or more of your Intentions are set to allow traffic to and/or from all other services in a namespace. This Topology view will show all of those connections if that remains unchanged. We recommend setting more specific Intentions for upstream and downstream services to make this visualization more useful. + header: Restrict which services can connect + body: There is currently a wildcard Intention that allows all services to connect to each other. Change the action of that Intention to deny to improve your security posture and have this topology view reflect the actual upstreams and downstreams of this service. footer: |

    - Edit Intentions + Edit wildcard intentions

    not-defined-intention: - header: Connections are not explicitly defined - body: There appears to be an Intention allowing traffic, but the services are unable to communicate until that connection is enabled by defining an explicit upstream or proxies are set to 'transparent' mode. + header: Add upstream to allow traffic + body: An Intention was defined that allows traffic between services, but those services are unable to communicate. Define an explicit upstream in the service definition or enable transparent proxy to fix this. footer: |

    - Read the documentation + Learn how to add upstreams

    no-dependencies: header: No dependencies @@ -213,12 +279,19 @@ dc: Read the documentation

    acls-disabled: - header: Enable ACLs - body: This connect-native service may have dependencies, but Consul isn't aware of them when ACLs are disabled. Enable ACLs to make this view more useful. + header: Restrict which services can connect + body: Your current ACL settings allow all services to connect to each other. Either create a deny intention between all services, or enable ACLs and set your default ACL policy to deny to improve your security posture and make this topology view reflect the actual upstreams and downstreams of this service. footer: |

    Read the documentation

    + no-intentions: + header: Add Intention to allow traffic + body: There is an upstream registered for this service, but that upstream cannot receive traffic without creating an allow intention. + footer: | +

    + Edit Intentions +

    intentions: index: empty: @@ -232,7 +305,10 @@ dc: {items, select, 0 {There don't seem to be any Intentions in this Consul cluster} other {No Intentions were found matching your search} - }, or you may not have intentions:read permissions access to this view. + }{canUseACLs, select, + true {, or you may not have intentions:read permissions access to this view.} + other {.} + }

    instances: @@ -290,7 +366,10 @@ dc: {items, select, 0 {There don't seem to be any Intentions in this Consul cluster} other {No Intentions were found matching your search} - }, or you may not have intentions:read permissions access to this view. + }{canUseACLs, select, + true {, or you may not have intentions:read permissions access to this view.} + other {.} + }

    kv: index: @@ -305,7 +384,10 @@ dc: {items, select, 0 {There don't seem to be any K/V pairs in this Consul cluster yet} other {No K/V pairs were found matching your search} - }, or you may not have key:read permissions access to this view. + }{canUseACLs, select, + true {, or you may not have key:read permissions access to this view.} + other {.} + }

    acls: tokens: @@ -356,15 +438,16 @@ dc: auth-methods: show: binding-rules: - empty: - header: No Binding Rules - body: | -

    - Binding rules allow an operator to express a systematic way of automatically linking roles and service identities to newly created tokens without operator intervention. -

    + index: + empty: + header: No binding rules + body: | +

    + Binding rules allow an operator to express a systematic way of automatically linking roles and service identities to newly created tokens without operator intervention. +

    nspace-rules: empty: - header: No Namespace Rules + header: No namespace rules body: |

    A set of rules that can control which namespace tokens created via this auth method will be created within. Unlike binding rules, the first matching namespace rule wins. diff --git a/ui/yarn.lock b/ui/yarn.lock index 3596af6d5a..2b8e0eca8e 100644 --- a/ui/yarn.lock +++ b/ui/yarn.lock @@ -48,7 +48,7 @@ resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.17.0.tgz#86850b8597ea6962089770952075dcaabb8dba34" integrity sha512-392byTlpGWXMv4FbyWw3sAZ/FrW/DrwqLGXpy0mbyNe9Taqv1mg9yON5/o0cnr8XYCkFTZbC1eV+c+LAROgrng== -"@babel/compat-data@^7.18.8": +"@babel/compat-data@^7.17.7", "@babel/compat-data@^7.18.8": version "7.18.13" resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.18.13.tgz#6aff7b350a1e8c3e40b029e46cbe78e24a913483" integrity sha512-5yUzC5LqyTFp2HLmDoxGQelcdYgSpP9xsnMWBphAscOdFrHSAVbLNzWiy32sVNDqJRDiJK6klfDnAgu6PAGSHw== @@ -75,7 +75,7 @@ semver "^6.3.0" source-map "^0.5.0" -"@babel/core@^7.12.9": +"@babel/core@^7.12.9", "@babel/core@^7.16.7": version "7.18.13" resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.18.13.tgz#9be8c44512751b05094a4d3ab05fc53a47ce00ac" integrity sha512-ZisbOvRRusFktksHSG6pjj1CSvkPkcZq/KHD45LAkVP/oiHJkNBZWfpvlLmX8OtHDG8IuzsFlVRWo08w7Qxn0A== @@ -137,6 +137,13 @@ dependencies: "@babel/types" "^7.16.7" +"@babel/helper-annotate-as-pure@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.18.6.tgz#eaa49f6f80d5a33f9a5dd2276e6d6e451be0a6bb" + integrity sha512-duORpUiYrEpzKIop6iNbjnwKLAKnJ47csTyRACyEmWj0QdUrm5aqNJGHSSEQSUAvNW0ojX0dOmK9dZduvkfeXA== + dependencies: + "@babel/types" "^7.18.6" + "@babel/helper-builder-binary-assignment-operator-visitor@^7.12.13": version "7.12.13" resolved "https://registry.yarnpkg.com/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.12.13.tgz#6bc20361c88b0a74d05137a65cac8d3cbf6f61fc" @@ -153,6 +160,14 @@ "@babel/helper-explode-assignable-expression" "^7.16.7" "@babel/types" "^7.16.7" +"@babel/helper-builder-binary-assignment-operator-visitor@^7.18.6": + version "7.18.9" + resolved "https://registry.yarnpkg.com/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.18.9.tgz#acd4edfd7a566d1d51ea975dff38fd52906981bb" + integrity sha512-yFQ0YCHoIqarl8BCRwBL8ulYUaZpz3bNsA7oFepAzee+8/+ImtADXNOmO5vJvsPff3qi+hvpkY/NYBTrBQgdNw== + dependencies: + "@babel/helper-explode-assignable-expression" "^7.18.6" + "@babel/types" "^7.18.9" + "@babel/helper-call-delegate@^7.10.1": version "7.12.13" resolved "https://registry.yarnpkg.com/@babel/helper-call-delegate/-/helper-call-delegate-7.12.13.tgz#119ef367451f90bed006c685816ba60fc33fee78" @@ -181,7 +196,7 @@ browserslist "^4.17.5" semver "^6.3.0" -"@babel/helper-compilation-targets@^7.18.9": +"@babel/helper-compilation-targets@^7.17.7", "@babel/helper-compilation-targets@^7.18.9": version "7.18.9" resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.18.9.tgz#69e64f57b524cde3e5ff6cc5a9f4a387ee5563bf" integrity sha512-tzLCyVmqUiFlcFoAPLA/gL9TeYrF61VLNtb+hvkuVaB5SUjW7jcfrglBIX1vUIoT7CLP3bBlIMeyEsIl2eFQNg== @@ -215,6 +230,19 @@ "@babel/helper-replace-supers" "^7.16.7" "@babel/helper-split-export-declaration" "^7.16.7" +"@babel/helper-create-class-features-plugin@^7.18.6", "@babel/helper-create-class-features-plugin@^7.18.9": + version "7.18.13" + resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.18.13.tgz#63e771187bd06d234f95fdf8bd5f8b6429de6298" + integrity sha512-hDvXp+QYxSRL+23mpAlSGxHMDyIGChm0/AwTfTAAK5Ufe40nCsyNdaYCGuK91phn/fVu9kqayImRDkvNAgdrsA== + dependencies: + "@babel/helper-annotate-as-pure" "^7.18.6" + "@babel/helper-environment-visitor" "^7.18.9" + "@babel/helper-function-name" "^7.18.9" + "@babel/helper-member-expression-to-functions" "^7.18.9" + "@babel/helper-optimise-call-expression" "^7.18.6" + "@babel/helper-replace-supers" "^7.18.9" + "@babel/helper-split-export-declaration" "^7.18.6" + "@babel/helper-create-regexp-features-plugin@^7.12.13": version "7.12.17" resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.12.17.tgz#a2ac87e9e319269ac655b8d4415e94d38d663cb7" @@ -231,6 +259,14 @@ "@babel/helper-annotate-as-pure" "^7.16.7" regexpu-core "^5.0.1" +"@babel/helper-create-regexp-features-plugin@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.18.6.tgz#3e35f4e04acbbf25f1b3534a657610a000543d3c" + integrity sha512-7LcpH1wnQLGrI+4v+nPp+zUvIkF9x0ddv1Hkdue10tg3gmRnLy97DXh4STiOf1qeIInyD69Qv5kKSZzKD8B/7A== + dependencies: + "@babel/helper-annotate-as-pure" "^7.18.6" + regexpu-core "^5.1.0" + "@babel/helper-define-polyfill-provider@^0.1.5": version "0.1.5" resolved "https://registry.yarnpkg.com/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.1.5.tgz#3c2f91b7971b9fc11fe779c945c014065dea340e" @@ -259,6 +295,18 @@ resolve "^1.14.2" semver "^6.1.2" +"@babel/helper-define-polyfill-provider@^0.3.2": + version "0.3.2" + resolved "https://registry.yarnpkg.com/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.3.2.tgz#bd10d0aca18e8ce012755395b05a79f45eca5073" + integrity sha512-r9QJJ+uDWrd+94BSPcP6/de67ygLtvVy6cK4luE6MOuDsZIdoaPBnfSpbO/+LTifjPckbKXRuI9BB/Z2/y3iTg== + dependencies: + "@babel/helper-compilation-targets" "^7.17.7" + "@babel/helper-plugin-utils" "^7.16.7" + debug "^4.1.1" + lodash.debounce "^4.0.8" + resolve "^1.14.2" + semver "^6.1.2" + "@babel/helper-environment-visitor@^7.16.7": version "7.16.7" resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.16.7.tgz#ff484094a839bde9d89cd63cba017d7aae80ecd7" @@ -285,6 +333,13 @@ dependencies: "@babel/types" "^7.16.7" +"@babel/helper-explode-assignable-expression@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.18.6.tgz#41f8228ef0a6f1a036b8dfdfec7ce94f9a6bc096" + integrity sha512-eyAYAsQmB80jNfg4baAtLeWAQHfHFiR483rzFK+BhETlGZaQC9bsfrugfXDCbRHLQbIA7U5NxhhOxN7p/dWIcg== + dependencies: + "@babel/types" "^7.18.6" + "@babel/helper-function-name@^7.12.13": version "7.12.13" resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.12.13.tgz#93ad656db3c3c2232559fd7b2c3dbdcbe0eb377a" @@ -361,6 +416,13 @@ dependencies: "@babel/types" "^7.16.7" +"@babel/helper-member-expression-to-functions@^7.18.9": + version "7.18.9" + resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.18.9.tgz#1531661e8375af843ad37ac692c132841e2fd815" + integrity sha512-RxifAh2ZoVU67PyKIO4AMi1wTenGfMR/O/ae0CCRqwgBAt5v7xjdtRw7UoSbsreKrQn5t7r89eruK/9JjYHuDg== + dependencies: + "@babel/types" "^7.18.9" + "@babel/helper-module-imports@^7.12.13", "@babel/helper-module-imports@^7.8.3": version "7.12.13" resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.12.13.tgz#ec67e4404f41750463e455cc3203f6a32e93fcb0" @@ -439,6 +501,13 @@ dependencies: "@babel/types" "^7.16.7" +"@babel/helper-optimise-call-expression@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.18.6.tgz#9369aa943ee7da47edab2cb4e838acf09d290ffe" + integrity sha512-HP59oD9/fEHQkdcbgFCnbmgH5vIQTJbxh2yf+CdM89/glUNnuzr87Q8GIjGEnOktTROemO0Pe0iPAYbqZuOUiA== + dependencies: + "@babel/types" "^7.18.6" + "@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.13.0", "@babel/helper-plugin-utils@^7.8.0", "@babel/helper-plugin-utils@^7.8.3": version "7.13.0" resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.13.0.tgz#806526ce125aed03373bc416a828321e3a6a33af" @@ -449,7 +518,7 @@ resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.16.7.tgz#aa3a8ab4c3cceff8e65eb9e73d87dc4ff320b2f5" integrity sha512-Qg3Nk7ZxpgMrsox6HreY1ZNKdBq7K72tDSliA6dCl5f007jR4ne8iD5UzuNnCJH2xBf2BEEVGr+/OL6Gdp7RxA== -"@babel/helper-plugin-utils@^7.18.6": +"@babel/helper-plugin-utils@^7.18.6", "@babel/helper-plugin-utils@^7.18.9": version "7.18.9" resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.18.9.tgz#4b8aea3b069d8cb8a72cdfe28ddf5ceca695ef2f" integrity sha512-aBXPT3bmtLryXaoJLyYPXPlSD4p1ld9aYeR+sJNOZjJJGiOpb+fKfh3NkcCu7J54nUJwCERPBExCCpyCOHnu/w== @@ -472,6 +541,16 @@ "@babel/helper-wrap-function" "^7.16.8" "@babel/types" "^7.16.8" +"@babel/helper-remap-async-to-generator@^7.18.6", "@babel/helper-remap-async-to-generator@^7.18.9": + version "7.18.9" + resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.18.9.tgz#997458a0e3357080e54e1d79ec347f8a8cd28519" + integrity sha512-dI7q50YKd8BAv3VEfgg7PS7yD3Rtbi2J1XMXaalXO0W0164hYLnh8zpjRS0mte9MfVp/tltvr/cfdXPvJr1opA== + dependencies: + "@babel/helper-annotate-as-pure" "^7.18.6" + "@babel/helper-environment-visitor" "^7.18.9" + "@babel/helper-wrap-function" "^7.18.9" + "@babel/types" "^7.18.9" + "@babel/helper-replace-supers@^7.12.13", "@babel/helper-replace-supers@^7.13.0": version "7.13.0" resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.13.0.tgz#6034b7b51943094cb41627848cb219cb02be1d24" @@ -493,6 +572,17 @@ "@babel/traverse" "^7.16.7" "@babel/types" "^7.16.7" +"@babel/helper-replace-supers@^7.18.6", "@babel/helper-replace-supers@^7.18.9": + version "7.18.9" + resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.18.9.tgz#1092e002feca980fbbb0bd4d51b74a65c6a500e6" + integrity sha512-dNsWibVI4lNT6HiuOIBr1oyxo40HvIVmbwPUm3XZ7wMh4k2WxrxTqZwSqw/eEmXDS9np0ey5M2bz9tBmO9c+YQ== + dependencies: + "@babel/helper-environment-visitor" "^7.18.9" + "@babel/helper-member-expression-to-functions" "^7.18.9" + "@babel/helper-optimise-call-expression" "^7.18.6" + "@babel/traverse" "^7.18.9" + "@babel/types" "^7.18.9" + "@babel/helper-simple-access@^7.12.13": version "7.12.13" resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.12.13.tgz#8478bcc5cacf6aa1672b251c1d2dde5ccd61a6c4" @@ -528,6 +618,13 @@ dependencies: "@babel/types" "^7.16.0" +"@babel/helper-skip-transparent-expression-wrappers@^7.18.9": + version "7.18.9" + resolved "https://registry.yarnpkg.com/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.18.9.tgz#778d87b3a758d90b471e7b9918f34a9a02eb5818" + integrity sha512-imytd2gHi3cJPsybLRbmFrF7u5BIEuI2cNheyKi3/iOBC63kNn3q8Crn2xVuESli0aM4KYsyEqKyS7lFL8YVtw== + dependencies: + "@babel/types" "^7.18.9" + "@babel/helper-split-export-declaration@^7.12.13": version "7.12.13" resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.12.13.tgz#e9430be00baf3e88b0e13e6f9d4eaf2136372b05" @@ -604,6 +701,16 @@ "@babel/traverse" "^7.16.8" "@babel/types" "^7.16.8" +"@babel/helper-wrap-function@^7.18.9": + version "7.18.11" + resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.18.11.tgz#bff23ace436e3f6aefb61f85ffae2291c80ed1fb" + integrity sha512-oBUlbv+rjZLh2Ks9SKi4aL7eKaAXBWleHzU89mP0G6BMUlRxSckk9tSIkgDGydhgFxHuGSlBQZfnaD47oBEB7w== + dependencies: + "@babel/helper-function-name" "^7.18.9" + "@babel/template" "^7.18.10" + "@babel/traverse" "^7.18.11" + "@babel/types" "^7.18.10" + "@babel/helpers@^7.13.10": version "7.13.10" resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.13.10.tgz#fd8e2ba7488533cdeac45cc158e9ebca5e3c7df8" @@ -671,6 +778,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.16.7" +"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.18.6.tgz#da5b8f9a580acdfbe53494dba45ea389fb09a4d2" + integrity sha512-Dgxsyg54Fx1d4Nge8UnvTrED63vrwOdPmyvPzlNN/boaliRP54pm3pGzZD1SJUwrBA+Cs/xdG8kXX6Mn/RfISQ== + dependencies: + "@babel/helper-plugin-utils" "^7.18.6" + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@^7.16.7": version "7.16.7" resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.16.7.tgz#cc001234dfc139ac45f6bcf801866198c8c72ff9" @@ -680,6 +794,15 @@ "@babel/helper-skip-transparent-expression-wrappers" "^7.16.0" "@babel/plugin-proposal-optional-chaining" "^7.16.7" +"@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@^7.18.9": + version "7.18.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.18.9.tgz#a11af19aa373d68d561f08e0a57242350ed0ec50" + integrity sha512-AHrP9jadvH7qlOj6PINbgSuphjQUAK7AOT7DPjBo9EHoLhQTnnK5u45e1Hd4DbSQEO9nqPWtQ89r+XEOWFScKg== + dependencies: + "@babel/helper-plugin-utils" "^7.18.9" + "@babel/helper-skip-transparent-expression-wrappers" "^7.18.9" + "@babel/plugin-proposal-optional-chaining" "^7.18.9" + "@babel/plugin-proposal-async-generator-functions@^7.13.8": version "7.13.8" resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.13.8.tgz#87aacb574b3bc4b5603f6fe41458d72a5a2ec4b1" @@ -698,6 +821,16 @@ "@babel/helper-remap-async-to-generator" "^7.16.8" "@babel/plugin-syntax-async-generators" "^7.8.4" +"@babel/plugin-proposal-async-generator-functions@^7.18.10": + version "7.18.10" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.18.10.tgz#85ea478c98b0095c3e4102bff3b67d306ed24952" + integrity sha512-1mFuY2TOsR1hxbjCo4QL+qlIjV07p4H4EUYw2J/WCqsvFV6V9X9z9YhXbWndc/4fw+hYGlDT7egYxliMp5O6Ew== + dependencies: + "@babel/helper-environment-visitor" "^7.18.9" + "@babel/helper-plugin-utils" "^7.18.9" + "@babel/helper-remap-async-to-generator" "^7.18.9" + "@babel/plugin-syntax-async-generators" "^7.8.4" + "@babel/plugin-proposal-class-properties@^7.1.0", "@babel/plugin-proposal-class-properties@^7.10.1", "@babel/plugin-proposal-class-properties@^7.13.0": version "7.13.0" resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.13.0.tgz#146376000b94efd001e57a40a88a525afaab9f37" @@ -714,6 +847,14 @@ "@babel/helper-create-class-features-plugin" "^7.16.7" "@babel/helper-plugin-utils" "^7.16.7" +"@babel/plugin-proposal-class-properties@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.18.6.tgz#b110f59741895f7ec21a6fff696ec46265c446a3" + integrity sha512-cumfXOF0+nzZrrN8Rf0t7M+tF6sZc7vhQwYQck9q1/5w2OExlD+b4v4RpMJFaV1Z7WcDRgO6FqvxqxGlwo+RHQ== + dependencies: + "@babel/helper-create-class-features-plugin" "^7.18.6" + "@babel/helper-plugin-utils" "^7.18.6" + "@babel/plugin-proposal-class-static-block@^7.16.7": version "7.17.6" resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-class-static-block/-/plugin-proposal-class-static-block-7.17.6.tgz#164e8fd25f0d80fa48c5a4d1438a6629325ad83c" @@ -723,6 +864,15 @@ "@babel/helper-plugin-utils" "^7.16.7" "@babel/plugin-syntax-class-static-block" "^7.14.5" +"@babel/plugin-proposal-class-static-block@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-class-static-block/-/plugin-proposal-class-static-block-7.18.6.tgz#8aa81d403ab72d3962fc06c26e222dacfc9b9020" + integrity sha512-+I3oIiNxrCpup3Gi8n5IGMwj0gOCAjcJUSQEcotNnCCPMEnixawOQ+KeJPlgfjzx+FKQ1QSyZOWe7wmoJp7vhw== + dependencies: + "@babel/helper-create-class-features-plugin" "^7.18.6" + "@babel/helper-plugin-utils" "^7.18.6" + "@babel/plugin-syntax-class-static-block" "^7.14.5" + "@babel/plugin-proposal-decorators@^7.13.5": version "7.13.5" resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.13.5.tgz#d28071457a5ba8ee1394b23e38d5dcf32ea20ef7" @@ -732,6 +882,17 @@ "@babel/helper-plugin-utils" "^7.13.0" "@babel/plugin-syntax-decorators" "^7.12.13" +"@babel/plugin-proposal-decorators@^7.16.7": + version "7.18.10" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.18.10.tgz#788650d01e518a8a722eb8b3055dd9d73ecb7a35" + integrity sha512-wdGTwWF5QtpTY/gbBtQLAiCnoxfD4qMbN87NYZle1dOZ9Os8Y6zXcKrIaOU8W+TIvFUWVGG9tUgNww3CjXRVVw== + dependencies: + "@babel/helper-create-class-features-plugin" "^7.18.9" + "@babel/helper-plugin-utils" "^7.18.9" + "@babel/helper-replace-supers" "^7.18.9" + "@babel/helper-split-export-declaration" "^7.18.6" + "@babel/plugin-syntax-decorators" "^7.18.6" + "@babel/plugin-proposal-dynamic-import@^7.13.8": version "7.13.8" resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.13.8.tgz#876a1f6966e1dec332e8c9451afda3bebcdf2e1d" @@ -748,6 +909,14 @@ "@babel/helper-plugin-utils" "^7.16.7" "@babel/plugin-syntax-dynamic-import" "^7.8.3" +"@babel/plugin-proposal-dynamic-import@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.18.6.tgz#72bcf8d408799f547d759298c3c27c7e7faa4d94" + integrity sha512-1auuwmK+Rz13SJj36R+jqFPMJWyKEDd7lLSdOj4oJK0UTgGueSAtkrCvz9ewmgyU/P941Rv2fQwZJN8s6QruXw== + dependencies: + "@babel/helper-plugin-utils" "^7.18.6" + "@babel/plugin-syntax-dynamic-import" "^7.8.3" + "@babel/plugin-proposal-export-namespace-from@^7.12.13": version "7.12.13" resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.12.13.tgz#393be47a4acd03fa2af6e3cde9b06e33de1b446d" @@ -764,6 +933,14 @@ "@babel/helper-plugin-utils" "^7.16.7" "@babel/plugin-syntax-export-namespace-from" "^7.8.3" +"@babel/plugin-proposal-export-namespace-from@^7.18.9": + version "7.18.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.18.9.tgz#5f7313ab348cdb19d590145f9247540e94761203" + integrity sha512-k1NtHyOMvlDDFeb9G5PhUXuGj8m/wiwojgQVEhJ/fsVsMCpLyOP4h0uGEjYJKrRI+EVPlb5Jk+Gt9P97lOGwtA== + dependencies: + "@babel/helper-plugin-utils" "^7.18.9" + "@babel/plugin-syntax-export-namespace-from" "^7.8.3" + "@babel/plugin-proposal-json-strings@^7.13.8": version "7.13.8" resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.13.8.tgz#bf1fb362547075afda3634ed31571c5901afef7b" @@ -780,6 +957,14 @@ "@babel/helper-plugin-utils" "^7.16.7" "@babel/plugin-syntax-json-strings" "^7.8.3" +"@babel/plugin-proposal-json-strings@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.18.6.tgz#7e8788c1811c393aff762817e7dbf1ebd0c05f0b" + integrity sha512-lr1peyn9kOdbYc0xr0OdHTZ5FMqS6Di+H0Fz2I/JwMzGmzJETNeOFq2pBySw6X/KFL5EWDjlJuMsUGRFb8fQgQ== + dependencies: + "@babel/helper-plugin-utils" "^7.18.6" + "@babel/plugin-syntax-json-strings" "^7.8.3" + "@babel/plugin-proposal-logical-assignment-operators@^7.13.8": version "7.13.8" resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.13.8.tgz#93fa78d63857c40ce3c8c3315220fd00bfbb4e1a" @@ -796,6 +981,14 @@ "@babel/helper-plugin-utils" "^7.16.7" "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4" +"@babel/plugin-proposal-logical-assignment-operators@^7.18.9": + version "7.18.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.18.9.tgz#8148cbb350483bf6220af06fa6db3690e14b2e23" + integrity sha512-128YbMpjCrP35IOExw2Fq+x55LMP42DzhOhX2aNNIdI9avSWl2PI0yuBWarr3RYpZBSPtabfadkH2yeRiMD61Q== + dependencies: + "@babel/helper-plugin-utils" "^7.18.9" + "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4" + "@babel/plugin-proposal-nullish-coalescing-operator@^7.13.8", "@babel/plugin-proposal-nullish-coalescing-operator@^7.4.4": version "7.13.8" resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.13.8.tgz#3730a31dafd3c10d8ccd10648ed80a2ac5472ef3" @@ -812,6 +1005,14 @@ "@babel/helper-plugin-utils" "^7.16.7" "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3" +"@babel/plugin-proposal-nullish-coalescing-operator@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.18.6.tgz#fdd940a99a740e577d6c753ab6fbb43fdb9467e1" + integrity sha512-wQxQzxYeJqHcfppzBDnm1yAY0jSRkUXR2z8RePZYrKwMKgMlE8+Z6LUno+bd6LvbGh8Gltvy74+9pIYkr+XkKA== + dependencies: + "@babel/helper-plugin-utils" "^7.18.6" + "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3" + "@babel/plugin-proposal-numeric-separator@^7.12.13": version "7.12.13" resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.12.13.tgz#bd9da3188e787b5120b4f9d465a8261ce67ed1db" @@ -828,6 +1029,14 @@ "@babel/helper-plugin-utils" "^7.16.7" "@babel/plugin-syntax-numeric-separator" "^7.10.4" +"@babel/plugin-proposal-numeric-separator@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.18.6.tgz#899b14fbafe87f053d2c5ff05b36029c62e13c75" + integrity sha512-ozlZFogPqoLm8WBr5Z8UckIoE4YQ5KESVcNudyXOR8uqIkliTEgJ3RoketfG6pmzLdeZF0H/wjE9/cCEitBl7Q== + dependencies: + "@babel/helper-plugin-utils" "^7.18.6" + "@babel/plugin-syntax-numeric-separator" "^7.10.4" + "@babel/plugin-proposal-object-rest-spread@^7.13.8", "@babel/plugin-proposal-object-rest-spread@^7.5.5": version "7.13.8" resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.13.8.tgz#5d210a4d727d6ce3b18f9de82cc99a3964eed60a" @@ -850,6 +1059,17 @@ "@babel/plugin-syntax-object-rest-spread" "^7.8.3" "@babel/plugin-transform-parameters" "^7.16.7" +"@babel/plugin-proposal-object-rest-spread@^7.18.9": + version "7.18.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.18.9.tgz#f9434f6beb2c8cae9dfcf97d2a5941bbbf9ad4e7" + integrity sha512-kDDHQ5rflIeY5xl69CEqGEZ0KY369ehsCIEbTGb4siHG5BE9sga/T0r0OUwyZNLMmZE79E1kbsqAjwFCW4ds6Q== + dependencies: + "@babel/compat-data" "^7.18.8" + "@babel/helper-compilation-targets" "^7.18.9" + "@babel/helper-plugin-utils" "^7.18.9" + "@babel/plugin-syntax-object-rest-spread" "^7.8.3" + "@babel/plugin-transform-parameters" "^7.18.8" + "@babel/plugin-proposal-optional-catch-binding@^7.13.8": version "7.13.8" resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.13.8.tgz#3ad6bd5901506ea996fc31bdcf3ccfa2bed71107" @@ -866,6 +1086,14 @@ "@babel/helper-plugin-utils" "^7.16.7" "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" +"@babel/plugin-proposal-optional-catch-binding@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.18.6.tgz#f9400d0e6a3ea93ba9ef70b09e72dd6da638a2cb" + integrity sha512-Q40HEhs9DJQyaZfUjjn6vE8Cv4GmMHCYuMGIWUnlxH6400VGxOuwWsPt4FxXxJkC/5eOzgn0z21M9gMT4MOhbw== + dependencies: + "@babel/helper-plugin-utils" "^7.18.6" + "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" + "@babel/plugin-proposal-optional-chaining@^7.13.8", "@babel/plugin-proposal-optional-chaining@^7.6.0": version "7.13.8" resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.13.8.tgz#e39df93efe7e7e621841babc197982e140e90756" @@ -884,6 +1112,15 @@ "@babel/helper-skip-transparent-expression-wrappers" "^7.16.0" "@babel/plugin-syntax-optional-chaining" "^7.8.3" +"@babel/plugin-proposal-optional-chaining@^7.18.9": + version "7.18.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.18.9.tgz#e8e8fe0723f2563960e4bf5e9690933691915993" + integrity sha512-v5nwt4IqBXihxGsW2QmCWMDS3B3bzGIk/EQVZz2ei7f3NJl8NzAJVvUmpDW5q1CRNY+Beb/k58UAH1Km1N411w== + dependencies: + "@babel/helper-plugin-utils" "^7.18.9" + "@babel/helper-skip-transparent-expression-wrappers" "^7.18.9" + "@babel/plugin-syntax-optional-chaining" "^7.8.3" + "@babel/plugin-proposal-private-methods@^7.13.0": version "7.13.0" resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.13.0.tgz#04bd4c6d40f6e6bbfa2f57e2d8094bad900ef787" @@ -900,6 +1137,14 @@ "@babel/helper-create-class-features-plugin" "^7.16.10" "@babel/helper-plugin-utils" "^7.16.7" +"@babel/plugin-proposal-private-methods@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.18.6.tgz#5209de7d213457548a98436fa2882f52f4be6bea" + integrity sha512-nutsvktDItsNn4rpGItSNV2sz1XwS+nfU0Rg8aCx3W3NOKVzdMjJRu0O5OkgDp3ZGICSTbgRpxZoWsxoKRvbeA== + dependencies: + "@babel/helper-create-class-features-plugin" "^7.18.6" + "@babel/helper-plugin-utils" "^7.18.6" + "@babel/plugin-proposal-private-property-in-object@^7.16.5", "@babel/plugin-proposal-private-property-in-object@^7.16.7": version "7.16.7" resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.16.7.tgz#b0b8cef543c2c3d57e59e2c611994861d46a3fce" @@ -910,6 +1155,16 @@ "@babel/helper-plugin-utils" "^7.16.7" "@babel/plugin-syntax-private-property-in-object" "^7.14.5" +"@babel/plugin-proposal-private-property-in-object@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.18.6.tgz#a64137b232f0aca3733a67eb1a144c192389c503" + integrity sha512-9Rysx7FOctvT5ouj5JODjAFAkgGoudQuLPamZb0v1TGLpapdNaftzifU8NTWQm0IRjqoYypdrSmyWgkocDQ8Dw== + dependencies: + "@babel/helper-annotate-as-pure" "^7.18.6" + "@babel/helper-create-class-features-plugin" "^7.18.6" + "@babel/helper-plugin-utils" "^7.18.6" + "@babel/plugin-syntax-private-property-in-object" "^7.14.5" + "@babel/plugin-proposal-unicode-property-regex@^7.12.13", "@babel/plugin-proposal-unicode-property-regex@^7.4.4": version "7.12.13" resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.12.13.tgz#bebde51339be829c17aaaaced18641deb62b39ba" @@ -926,6 +1181,14 @@ "@babel/helper-create-regexp-features-plugin" "^7.16.7" "@babel/helper-plugin-utils" "^7.16.7" +"@babel/plugin-proposal-unicode-property-regex@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.18.6.tgz#af613d2cd5e643643b65cded64207b15c85cb78e" + integrity sha512-2BShG/d5yoZyXZfVePH91urL5wTG6ASZU9M4o03lKK8u8UW1y08OMttBSOADTcJrnPMpvDXRG3G8fyLh4ovs8w== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.18.6" + "@babel/helper-plugin-utils" "^7.18.6" + "@babel/plugin-syntax-async-generators@^7.8.4": version "7.8.4" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz#a983fb1aeb2ec3f6ed042a210f640e90e786fe0d" @@ -954,6 +1217,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.12.13" +"@babel/plugin-syntax-decorators@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-decorators/-/plugin-syntax-decorators-7.18.6.tgz#2e45af22835d0b0f8665da2bfd4463649ce5dbc1" + integrity sha512-fqyLgjcxf/1yhyZ6A+yo1u9gJ7eleFQod2lkaUsF9DQ7sbbY3Ligym3L0+I2c0WmqNKDpoD9UTb1AKP3qRMOAQ== + dependencies: + "@babel/helper-plugin-utils" "^7.18.6" + "@babel/plugin-syntax-dynamic-import@^7.8.3": version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz#62bf98b2da3cd21d626154fc96ee5b3cb68eacb3" @@ -968,6 +1238,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.8.3" +"@babel/plugin-syntax-import-assertions@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.18.6.tgz#cd6190500a4fa2fe31990a963ffab4b63e4505e4" + integrity sha512-/DU3RXad9+bZwrgWJQKbr39gYbJpLJHezqEzRzi/BHRlJ9zsQb4CK2CA/5apllXNomwA1qHwzvHl+AdEmC5krQ== + dependencies: + "@babel/helper-plugin-utils" "^7.18.6" + "@babel/plugin-syntax-json-strings@^7.8.3": version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz#01ca21b668cd8218c9e640cb6dd88c5412b2c96a" @@ -1059,6 +1336,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.16.7" +"@babel/plugin-transform-arrow-functions@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.18.6.tgz#19063fcf8771ec7b31d742339dac62433d0611fe" + integrity sha512-9S9X9RUefzrsHZmKMbDXxweEH+YlE8JJEuat9FdvW9Qh1cw7W64jELCtWNkPBPX5En45uy28KGvA/AySqUh8CQ== + dependencies: + "@babel/helper-plugin-utils" "^7.18.6" + "@babel/plugin-transform-async-to-generator@^7.13.0": version "7.13.0" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.13.0.tgz#8e112bf6771b82bf1e974e5e26806c5c99aa516f" @@ -1077,6 +1361,15 @@ "@babel/helper-plugin-utils" "^7.16.7" "@babel/helper-remap-async-to-generator" "^7.16.8" +"@babel/plugin-transform-async-to-generator@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.18.6.tgz#ccda3d1ab9d5ced5265fdb13f1882d5476c71615" + integrity sha512-ARE5wZLKnTgPW7/1ftQmSi1CmkqqHo2DNmtztFhvgtOWSDfq0Cq9/9L+KnZNYSNrydBekhW3rwShduf59RoXag== + dependencies: + "@babel/helper-module-imports" "^7.18.6" + "@babel/helper-plugin-utils" "^7.18.6" + "@babel/helper-remap-async-to-generator" "^7.18.6" + "@babel/plugin-transform-block-scoped-functions@^7.12.13": version "7.12.13" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.12.13.tgz#a9bf1836f2a39b4eb6cf09967739de29ea4bf4c4" @@ -1091,6 +1384,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.16.7" +"@babel/plugin-transform-block-scoped-functions@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.18.6.tgz#9187bf4ba302635b9d70d986ad70f038726216a8" + integrity sha512-ExUcOqpPWnliRcPqves5HJcJOvHvIIWfuS4sroBUenPuMdmW+SMHDakmtS7qOo13sVppmUijqeTv7qqGsvURpQ== + dependencies: + "@babel/helper-plugin-utils" "^7.18.6" + "@babel/plugin-transform-block-scoping@^7.12.13", "@babel/plugin-transform-block-scoping@^7.8.3": version "7.12.13" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.12.13.tgz#f36e55076d06f41dfd78557ea039c1b581642e61" @@ -1105,6 +1405,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.16.7" +"@babel/plugin-transform-block-scoping@^7.18.9": + version "7.18.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.18.9.tgz#f9b7e018ac3f373c81452d6ada8bd5a18928926d" + integrity sha512-5sDIJRV1KtQVEbt/EIBwGy4T01uYIo4KRB3VUqzkhrAIOGx7AoctL9+Ux88btY0zXdDyPJ9mW+bg+v+XEkGmtw== + dependencies: + "@babel/helper-plugin-utils" "^7.18.9" + "@babel/plugin-transform-classes@^7.13.0": version "7.13.0" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.13.0.tgz#0265155075c42918bf4d3a4053134176ad9b533b" @@ -1132,6 +1439,20 @@ "@babel/helper-split-export-declaration" "^7.16.7" globals "^11.1.0" +"@babel/plugin-transform-classes@^7.18.9": + version "7.18.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.18.9.tgz#90818efc5b9746879b869d5ce83eb2aa48bbc3da" + integrity sha512-EkRQxsxoytpTlKJmSPYrsOMjCILacAjtSVkd4gChEe2kXjFCun3yohhW5I7plXJhCemM0gKsaGMcO8tinvCA5g== + dependencies: + "@babel/helper-annotate-as-pure" "^7.18.6" + "@babel/helper-environment-visitor" "^7.18.9" + "@babel/helper-function-name" "^7.18.9" + "@babel/helper-optimise-call-expression" "^7.18.6" + "@babel/helper-plugin-utils" "^7.18.9" + "@babel/helper-replace-supers" "^7.18.9" + "@babel/helper-split-export-declaration" "^7.18.6" + globals "^11.1.0" + "@babel/plugin-transform-computed-properties@^7.13.0": version "7.13.0" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.13.0.tgz#845c6e8b9bb55376b1fa0b92ef0bdc8ea06644ed" @@ -1146,6 +1467,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.16.7" +"@babel/plugin-transform-computed-properties@^7.18.9": + version "7.18.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.18.9.tgz#2357a8224d402dad623caf6259b611e56aec746e" + integrity sha512-+i0ZU1bCDymKakLxn5srGHrsAPRELC2WIbzwjLhHW9SIE1cPYkLCL0NlnXMZaM1vhfgA2+M7hySk42VBvrkBRw== + dependencies: + "@babel/helper-plugin-utils" "^7.18.9" + "@babel/plugin-transform-destructuring@^7.13.0": version "7.13.0" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.13.0.tgz#c5dce270014d4e1ebb1d806116694c12b7028963" @@ -1160,6 +1488,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.16.7" +"@babel/plugin-transform-destructuring@^7.18.9": + version "7.18.13" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.18.13.tgz#9e03bc4a94475d62b7f4114938e6c5c33372cbf5" + integrity sha512-TodpQ29XekIsex2A+YJPj5ax2plkGa8YYY6mFjCohk/IG9IY42Rtuj1FuDeemfg2ipxIFLzPeA83SIBnlhSIow== + dependencies: + "@babel/helper-plugin-utils" "^7.18.9" + "@babel/plugin-transform-dotall-regex@^7.12.13", "@babel/plugin-transform-dotall-regex@^7.4.4": version "7.12.13" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.12.13.tgz#3f1601cc29905bfcb67f53910f197aeafebb25ad" @@ -1176,6 +1511,14 @@ "@babel/helper-create-regexp-features-plugin" "^7.16.7" "@babel/helper-plugin-utils" "^7.16.7" +"@babel/plugin-transform-dotall-regex@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.18.6.tgz#b286b3e7aae6c7b861e45bed0a2fafd6b1a4fef8" + integrity sha512-6S3jpun1eEbAxq7TdjLotAsl4WpQI9DxfkycRcKrjhQYzU87qpXdknpBg/e+TdcMehqGnLFi7tnFUBR02Vq6wg== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.18.6" + "@babel/helper-plugin-utils" "^7.18.6" + "@babel/plugin-transform-duplicate-keys@^7.12.13": version "7.12.13" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.12.13.tgz#6f06b87a8b803fd928e54b81c258f0a0033904de" @@ -1190,6 +1533,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.16.7" +"@babel/plugin-transform-duplicate-keys@^7.18.9": + version "7.18.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.18.9.tgz#687f15ee3cdad6d85191eb2a372c4528eaa0ae0e" + integrity sha512-d2bmXCtZXYc59/0SanQKbiWINadaJXqtvIQIzd4+hNwkWBgyCd5F/2t1kXoUdvPMrxzPvhK6EMQRROxsue+mfw== + dependencies: + "@babel/helper-plugin-utils" "^7.18.9" + "@babel/plugin-transform-exponentiation-operator@^7.12.13": version "7.12.13" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.12.13.tgz#4d52390b9a273e651e4aba6aee49ef40e80cd0a1" @@ -1206,6 +1556,14 @@ "@babel/helper-builder-binary-assignment-operator-visitor" "^7.16.7" "@babel/helper-plugin-utils" "^7.16.7" +"@babel/plugin-transform-exponentiation-operator@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.18.6.tgz#421c705f4521888c65e91fdd1af951bfefd4dacd" + integrity sha512-wzEtc0+2c88FVR34aQmiz56dxEkxr2g8DQb/KfaFa1JYXOFVsbhvAonFN6PwVWj++fKmku8NP80plJ5Et4wqHw== + dependencies: + "@babel/helper-builder-binary-assignment-operator-visitor" "^7.18.6" + "@babel/helper-plugin-utils" "^7.18.6" + "@babel/plugin-transform-for-of@^7.13.0": version "7.13.0" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.13.0.tgz#c799f881a8091ac26b54867a845c3e97d2696062" @@ -1220,6 +1578,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.16.7" +"@babel/plugin-transform-for-of@^7.18.8": + version "7.18.8" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.18.8.tgz#6ef8a50b244eb6a0bdbad0c7c61877e4e30097c1" + integrity sha512-yEfTRnjuskWYo0k1mHUqrVWaZwrdq8AYbfrpqULOJOaucGSp4mNMVps+YtA8byoevxS/urwU75vyhQIxcCgiBQ== + dependencies: + "@babel/helper-plugin-utils" "^7.18.6" + "@babel/plugin-transform-function-name@^7.12.13": version "7.12.13" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.12.13.tgz#bb024452f9aaed861d374c8e7a24252ce3a50051" @@ -1237,6 +1602,15 @@ "@babel/helper-function-name" "^7.16.7" "@babel/helper-plugin-utils" "^7.16.7" +"@babel/plugin-transform-function-name@^7.18.9": + version "7.18.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.18.9.tgz#cc354f8234e62968946c61a46d6365440fc764e0" + integrity sha512-WvIBoRPaJQ5yVHzcnJFor7oS5Ls0PYixlTYE63lCj2RtdQEl15M68FXQlxnG6wdraJIXRdR7KI+hQ7q/9QjrCQ== + dependencies: + "@babel/helper-compilation-targets" "^7.18.9" + "@babel/helper-function-name" "^7.18.9" + "@babel/helper-plugin-utils" "^7.18.9" + "@babel/plugin-transform-literals@^7.12.13": version "7.12.13" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-literals/-/plugin-transform-literals-7.12.13.tgz#2ca45bafe4a820197cf315794a4d26560fe4bdb9" @@ -1251,6 +1625,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.16.7" +"@babel/plugin-transform-literals@^7.18.9": + version "7.18.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-literals/-/plugin-transform-literals-7.18.9.tgz#72796fdbef80e56fba3c6a699d54f0de557444bc" + integrity sha512-IFQDSRoTPnrAIrI5zoZv73IFeZu2dhu6irxQjY9rNjTT53VmKg9fenjvoiOWOkJ6mm4jKVPtdMzBY98Fp4Z4cg== + dependencies: + "@babel/helper-plugin-utils" "^7.18.9" + "@babel/plugin-transform-member-expression-literals@^7.12.13": version "7.12.13" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.12.13.tgz#5ffa66cd59b9e191314c9f1f803b938e8c081e40" @@ -1265,7 +1646,14 @@ dependencies: "@babel/helper-plugin-utils" "^7.16.7" -"@babel/plugin-transform-modules-amd@^7.12.1": +"@babel/plugin-transform-member-expression-literals@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.18.6.tgz#ac9fdc1a118620ac49b7e7a5d2dc177a1bfee88e" + integrity sha512-qSF1ihLGO3q+/g48k85tUjD033C29TNTVB2paCwZPVmOsjn9pClvYYrM2VeJpBY2bcNkuny0YUyTNRyRxJ54KA== + dependencies: + "@babel/helper-plugin-utils" "^7.18.6" + +"@babel/plugin-transform-modules-amd@^7.12.1", "@babel/plugin-transform-modules-amd@^7.18.6": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.18.6.tgz#8c91f8c5115d2202f277549848874027d7172d21" integrity sha512-Pra5aXsmTsOnjM3IajS8rTaLCy++nGM4v3YR4esk5PCsyg9z8NA5oQLwxzMUtDBd8F+UmVza3VxoAaWCbzH1rg== @@ -1312,6 +1700,16 @@ "@babel/helper-simple-access" "^7.16.7" babel-plugin-dynamic-import-node "^2.3.3" +"@babel/plugin-transform-modules-commonjs@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.18.6.tgz#afd243afba166cca69892e24a8fd8c9f2ca87883" + integrity sha512-Qfv2ZOWikpvmedXQJDSbxNqy7Xr/j2Y8/KfijM0iJyKkBTmWuvCA1yeH1yDM7NJhBW/2aXxeucLj6i80/LAJ/Q== + dependencies: + "@babel/helper-module-transforms" "^7.18.6" + "@babel/helper-plugin-utils" "^7.18.6" + "@babel/helper-simple-access" "^7.18.6" + babel-plugin-dynamic-import-node "^2.3.3" + "@babel/plugin-transform-modules-systemjs@^7.13.8": version "7.13.8" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.13.8.tgz#6d066ee2bff3c7b3d60bf28dec169ad993831ae3" @@ -1334,6 +1732,17 @@ "@babel/helper-validator-identifier" "^7.16.7" babel-plugin-dynamic-import-node "^2.3.3" +"@babel/plugin-transform-modules-systemjs@^7.18.9": + version "7.18.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.18.9.tgz#545df284a7ac6a05125e3e405e536c5853099a06" + integrity sha512-zY/VSIbbqtoRoJKo2cDTewL364jSlZGvn0LKOf9ntbfxOvjfmyrdtEEOAdswOswhZEb8UH3jDkCKHd1sPgsS0A== + dependencies: + "@babel/helper-hoist-variables" "^7.18.6" + "@babel/helper-module-transforms" "^7.18.9" + "@babel/helper-plugin-utils" "^7.18.9" + "@babel/helper-validator-identifier" "^7.18.6" + babel-plugin-dynamic-import-node "^2.3.3" + "@babel/plugin-transform-modules-umd@^7.13.0": version "7.13.0" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.13.0.tgz#8a3d96a97d199705b9fd021580082af81c06e70b" @@ -1350,6 +1759,14 @@ "@babel/helper-module-transforms" "^7.16.7" "@babel/helper-plugin-utils" "^7.16.7" +"@babel/plugin-transform-modules-umd@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.18.6.tgz#81d3832d6034b75b54e62821ba58f28ed0aab4b9" + integrity sha512-dcegErExVeXcRqNtkRU/z8WlBLnvD4MRnHgNs3MytRO1Mn1sHRyhbcpYbVMGclAqOjdW+9cfkdZno9dFdfKLfQ== + dependencies: + "@babel/helper-module-transforms" "^7.18.6" + "@babel/helper-plugin-utils" "^7.18.6" + "@babel/plugin-transform-named-capturing-groups-regex@^7.12.13": version "7.12.13" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.12.13.tgz#2213725a5f5bbbe364b50c3ba5998c9599c5c9d9" @@ -1364,6 +1781,14 @@ dependencies: "@babel/helper-create-regexp-features-plugin" "^7.16.7" +"@babel/plugin-transform-named-capturing-groups-regex@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.18.6.tgz#c89bfbc7cc6805d692f3a49bc5fc1b630007246d" + integrity sha512-UmEOGF8XgaIqD74bC8g7iV3RYj8lMf0Bw7NJzvnS9qQhM4mg+1WHKotUIdjxgD2RGrgFLZZPCFPFj3P/kVDYhg== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.18.6" + "@babel/helper-plugin-utils" "^7.18.6" + "@babel/plugin-transform-new-target@^7.12.13": version "7.12.13" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.12.13.tgz#e22d8c3af24b150dd528cbd6e685e799bf1c351c" @@ -1378,6 +1803,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.16.7" +"@babel/plugin-transform-new-target@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.18.6.tgz#d128f376ae200477f37c4ddfcc722a8a1b3246a8" + integrity sha512-DjwFA/9Iu3Z+vrAn+8pBUGcjhxKguSMlsFqeCKbhb9BAV756v0krzVK04CRDi/4aqmk8BsHb4a/gFcaA5joXRw== + dependencies: + "@babel/helper-plugin-utils" "^7.18.6" + "@babel/plugin-transform-object-assign@^7.8.3": version "7.12.13" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-assign/-/plugin-transform-object-assign-7.12.13.tgz#d9b9200a69e03403a813e44a933ad9f4bddfd050" @@ -1401,6 +1833,14 @@ "@babel/helper-plugin-utils" "^7.16.7" "@babel/helper-replace-supers" "^7.16.7" +"@babel/plugin-transform-object-super@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.18.6.tgz#fb3c6ccdd15939b6ff7939944b51971ddc35912c" + integrity sha512-uvGz6zk+pZoS1aTZrOvrbj6Pp/kK2mp45t2B+bTDre2UgsZZ8EZLSJtUg7m/no0zOJUWgFONpB7Zv9W2tSaFlA== + dependencies: + "@babel/helper-plugin-utils" "^7.18.6" + "@babel/helper-replace-supers" "^7.18.6" + "@babel/plugin-transform-parameters@^7.13.0": version "7.13.0" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.13.0.tgz#8fa7603e3097f9c0b7ca1a4821bc2fb52e9e5007" @@ -1415,6 +1855,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.16.7" +"@babel/plugin-transform-parameters@^7.18.8": + version "7.18.8" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.18.8.tgz#ee9f1a0ce6d78af58d0956a9378ea3427cccb48a" + integrity sha512-ivfbE3X2Ss+Fj8nnXvKJS6sjRG4gzwPMsP+taZC+ZzEGjAYlvENixmt1sZ5Ca6tWls+BlKSGKPJ6OOXvXCbkFg== + dependencies: + "@babel/helper-plugin-utils" "^7.18.6" + "@babel/plugin-transform-property-literals@^7.12.13": version "7.12.13" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.12.13.tgz#4e6a9e37864d8f1b3bc0e2dce7bf8857db8b1a81" @@ -1429,6 +1876,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.16.7" +"@babel/plugin-transform-property-literals@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.18.6.tgz#e22498903a483448e94e032e9bbb9c5ccbfc93a3" + integrity sha512-cYcs6qlgafTud3PAzrrRNbQtfpQ8+y/+M5tKmksS9+M1ckbH6kzY8MrexEM9mcA6JDsukE19iIRvAyYl463sMg== + dependencies: + "@babel/helper-plugin-utils" "^7.18.6" + "@babel/plugin-transform-regenerator@^7.12.13": version "7.12.13" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.12.13.tgz#b628bcc9c85260ac1aeb05b45bde25210194a2f5" @@ -1443,6 +1897,14 @@ dependencies: regenerator-transform "^0.14.2" +"@babel/plugin-transform-regenerator@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.18.6.tgz#585c66cb84d4b4bf72519a34cfce761b8676ca73" + integrity sha512-poqRI2+qiSdeldcz4wTSTXBRryoq3Gc70ye7m7UD5Ww0nE29IXqMl6r7Nd15WBgRd74vloEMlShtH6CKxVzfmQ== + dependencies: + "@babel/helper-plugin-utils" "^7.18.6" + regenerator-transform "^0.15.0" + "@babel/plugin-transform-reserved-words@^7.12.13": version "7.12.13" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.12.13.tgz#7d9988d4f06e0fe697ea1d9803188aa18b472695" @@ -1457,6 +1919,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.16.7" +"@babel/plugin-transform-reserved-words@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.18.6.tgz#b1abd8ebf8edaa5f7fe6bbb8d2133d23b6a6f76a" + integrity sha512-oX/4MyMoypzHjFrT1CdivfKZ+XvIPMFXwwxHp/r0Ddy2Vuomt4HDFGmft1TAY2yiTKiNSsh3kjBAzcM8kSdsjA== + dependencies: + "@babel/helper-plugin-utils" "^7.18.6" + "@babel/plugin-transform-runtime@^7.12.1", "@babel/plugin-transform-runtime@^7.13.9": version "7.13.10" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.13.10.tgz#a1e40d22e2bf570c591c9c7e5ab42d6bf1e419e1" @@ -1483,6 +1952,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.16.7" +"@babel/plugin-transform-shorthand-properties@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.18.6.tgz#6d6df7983d67b195289be24909e3f12a8f664dc9" + integrity sha512-eCLXXJqv8okzg86ywZJbRn19YJHU4XUa55oz2wbHhaQVn/MM+XhukiT7SYqp/7o00dg52Rj51Ny+Ecw4oyoygw== + dependencies: + "@babel/helper-plugin-utils" "^7.18.6" + "@babel/plugin-transform-spread@^7.13.0": version "7.13.0" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.13.0.tgz#84887710e273c1815ace7ae459f6f42a5d31d5fd" @@ -1499,6 +1975,14 @@ "@babel/helper-plugin-utils" "^7.16.7" "@babel/helper-skip-transparent-expression-wrappers" "^7.16.0" +"@babel/plugin-transform-spread@^7.18.9": + version "7.18.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.18.9.tgz#6ea7a6297740f381c540ac56caf75b05b74fb664" + integrity sha512-39Q814wyoOPtIB/qGopNIL9xDChOE1pNU0ZY5dO0owhiVt/5kFm4li+/bBtwc7QotG0u5EPzqhZdjMtmqBqyQA== + dependencies: + "@babel/helper-plugin-utils" "^7.18.9" + "@babel/helper-skip-transparent-expression-wrappers" "^7.18.9" + "@babel/plugin-transform-sticky-regex@^7.12.13": version "7.12.13" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.12.13.tgz#760ffd936face73f860ae646fb86ee82f3d06d1f" @@ -1513,6 +1997,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.16.7" +"@babel/plugin-transform-sticky-regex@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.18.6.tgz#c6706eb2b1524028e317720339583ad0f444adcc" + integrity sha512-kfiDrDQ+PBsQDO85yj1icueWMfGfJFKN1KCkndygtu/C9+XUfydLC8Iv5UYJqRwy4zk8EcplRxEOeLyjq1gm6Q== + dependencies: + "@babel/helper-plugin-utils" "^7.18.6" + "@babel/plugin-transform-template-literals@^7.13.0": version "7.13.0" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.13.0.tgz#a36049127977ad94438dee7443598d1cefdf409d" @@ -1527,6 +2018,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.16.7" +"@babel/plugin-transform-template-literals@^7.18.9": + version "7.18.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.18.9.tgz#04ec6f10acdaa81846689d63fae117dd9c243a5e" + integrity sha512-S8cOWfT82gTezpYOiVaGHrCbhlHgKhQt8XH5ES46P2XWmX92yisoZywf5km75wv5sYcXDUCLMmMxOLCtthDgMA== + dependencies: + "@babel/helper-plugin-utils" "^7.18.9" + "@babel/plugin-transform-typeof-symbol@^7.12.13": version "7.12.13" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.12.13.tgz#785dd67a1f2ea579d9c2be722de8c84cb85f5a7f" @@ -1541,6 +2039,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.16.7" +"@babel/plugin-transform-typeof-symbol@^7.18.9": + version "7.18.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.18.9.tgz#c8cea68263e45addcd6afc9091429f80925762c0" + integrity sha512-SRfwTtF11G2aemAZWivL7PD+C9z52v9EvMqH9BuYbabyPuKUvSWks3oCg6041pT925L4zVFqaVBeECwsmlguEw== + dependencies: + "@babel/helper-plugin-utils" "^7.18.9" + "@babel/plugin-transform-typescript@^7.13.0": version "7.13.0" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.13.0.tgz#4a498e1f3600342d2a9e61f60131018f55774853" @@ -1590,6 +2095,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.16.7" +"@babel/plugin-transform-unicode-escapes@^7.18.10": + version "7.18.10" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.18.10.tgz#1ecfb0eda83d09bbcb77c09970c2dd55832aa246" + integrity sha512-kKAdAI+YzPgGY/ftStBFXTI1LZFju38rYThnfMykS+IXy8BVx+res7s2fxf1l8I35DV2T97ezo6+SGrXz6B3iQ== + dependencies: + "@babel/helper-plugin-utils" "^7.18.9" + "@babel/plugin-transform-unicode-regex@^7.12.13": version "7.12.13" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.12.13.tgz#b52521685804e155b1202e83fc188d34bb70f5ac" @@ -1606,6 +2118,14 @@ "@babel/helper-create-regexp-features-plugin" "^7.16.7" "@babel/helper-plugin-utils" "^7.16.7" +"@babel/plugin-transform-unicode-regex@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.18.6.tgz#194317225d8c201bbae103364ffe9e2cea36cdca" + integrity sha512-gE7A6Lt7YLnNOL3Pb9BNeZvi+d8l7tcRrG4+pwJjK9hD2xX4mEvjlQW60G9EEmfXVYRPv9VRQcyegIVHCql/AA== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.18.6" + "@babel/helper-plugin-utils" "^7.18.6" + "@babel/polyfill@^7.11.5": version "7.12.1" resolved "https://registry.yarnpkg.com/@babel/polyfill/-/polyfill-7.12.1.tgz#1f2d6371d1261bbd961f3c5d5909150e12d0bd96" @@ -1768,6 +2288,87 @@ core-js-compat "^3.20.2" semver "^6.3.0" +"@babel/preset-env@^7.16.7": + version "7.18.10" + resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.18.10.tgz#83b8dfe70d7eea1aae5a10635ab0a5fe60dfc0f4" + integrity sha512-wVxs1yjFdW3Z/XkNfXKoblxoHgbtUF7/l3PvvP4m02Qz9TZ6uZGxRVYjSQeR87oQmHco9zWitW5J82DJ7sCjvA== + dependencies: + "@babel/compat-data" "^7.18.8" + "@babel/helper-compilation-targets" "^7.18.9" + "@babel/helper-plugin-utils" "^7.18.9" + "@babel/helper-validator-option" "^7.18.6" + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression" "^7.18.6" + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining" "^7.18.9" + "@babel/plugin-proposal-async-generator-functions" "^7.18.10" + "@babel/plugin-proposal-class-properties" "^7.18.6" + "@babel/plugin-proposal-class-static-block" "^7.18.6" + "@babel/plugin-proposal-dynamic-import" "^7.18.6" + "@babel/plugin-proposal-export-namespace-from" "^7.18.9" + "@babel/plugin-proposal-json-strings" "^7.18.6" + "@babel/plugin-proposal-logical-assignment-operators" "^7.18.9" + "@babel/plugin-proposal-nullish-coalescing-operator" "^7.18.6" + "@babel/plugin-proposal-numeric-separator" "^7.18.6" + "@babel/plugin-proposal-object-rest-spread" "^7.18.9" + "@babel/plugin-proposal-optional-catch-binding" "^7.18.6" + "@babel/plugin-proposal-optional-chaining" "^7.18.9" + "@babel/plugin-proposal-private-methods" "^7.18.6" + "@babel/plugin-proposal-private-property-in-object" "^7.18.6" + "@babel/plugin-proposal-unicode-property-regex" "^7.18.6" + "@babel/plugin-syntax-async-generators" "^7.8.4" + "@babel/plugin-syntax-class-properties" "^7.12.13" + "@babel/plugin-syntax-class-static-block" "^7.14.5" + "@babel/plugin-syntax-dynamic-import" "^7.8.3" + "@babel/plugin-syntax-export-namespace-from" "^7.8.3" + "@babel/plugin-syntax-import-assertions" "^7.18.6" + "@babel/plugin-syntax-json-strings" "^7.8.3" + "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4" + "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3" + "@babel/plugin-syntax-numeric-separator" "^7.10.4" + "@babel/plugin-syntax-object-rest-spread" "^7.8.3" + "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" + "@babel/plugin-syntax-optional-chaining" "^7.8.3" + "@babel/plugin-syntax-private-property-in-object" "^7.14.5" + "@babel/plugin-syntax-top-level-await" "^7.14.5" + "@babel/plugin-transform-arrow-functions" "^7.18.6" + "@babel/plugin-transform-async-to-generator" "^7.18.6" + "@babel/plugin-transform-block-scoped-functions" "^7.18.6" + "@babel/plugin-transform-block-scoping" "^7.18.9" + "@babel/plugin-transform-classes" "^7.18.9" + "@babel/plugin-transform-computed-properties" "^7.18.9" + "@babel/plugin-transform-destructuring" "^7.18.9" + "@babel/plugin-transform-dotall-regex" "^7.18.6" + "@babel/plugin-transform-duplicate-keys" "^7.18.9" + "@babel/plugin-transform-exponentiation-operator" "^7.18.6" + "@babel/plugin-transform-for-of" "^7.18.8" + "@babel/plugin-transform-function-name" "^7.18.9" + "@babel/plugin-transform-literals" "^7.18.9" + "@babel/plugin-transform-member-expression-literals" "^7.18.6" + "@babel/plugin-transform-modules-amd" "^7.18.6" + "@babel/plugin-transform-modules-commonjs" "^7.18.6" + "@babel/plugin-transform-modules-systemjs" "^7.18.9" + "@babel/plugin-transform-modules-umd" "^7.18.6" + "@babel/plugin-transform-named-capturing-groups-regex" "^7.18.6" + "@babel/plugin-transform-new-target" "^7.18.6" + "@babel/plugin-transform-object-super" "^7.18.6" + "@babel/plugin-transform-parameters" "^7.18.8" + "@babel/plugin-transform-property-literals" "^7.18.6" + "@babel/plugin-transform-regenerator" "^7.18.6" + "@babel/plugin-transform-reserved-words" "^7.18.6" + "@babel/plugin-transform-shorthand-properties" "^7.18.6" + "@babel/plugin-transform-spread" "^7.18.9" + "@babel/plugin-transform-sticky-regex" "^7.18.6" + "@babel/plugin-transform-template-literals" "^7.18.9" + "@babel/plugin-transform-typeof-symbol" "^7.18.9" + "@babel/plugin-transform-unicode-escapes" "^7.18.10" + "@babel/plugin-transform-unicode-regex" "^7.18.6" + "@babel/preset-modules" "^0.1.5" + "@babel/types" "^7.18.10" + babel-plugin-polyfill-corejs2 "^0.3.2" + babel-plugin-polyfill-corejs3 "^0.5.3" + babel-plugin-polyfill-regenerator "^0.4.0" + core-js-compat "^3.22.1" + semver "^6.3.0" + "@babel/preset-modules@^0.1.4": version "0.1.4" resolved "https://registry.yarnpkg.com/@babel/preset-modules/-/preset-modules-0.1.4.tgz#362f2b68c662842970fdb5e254ffc8fc1c2e415e" @@ -1862,7 +2463,7 @@ debug "^4.1.0" globals "^11.1.0" -"@babel/traverse@^7.18.13", "@babel/traverse@^7.18.9": +"@babel/traverse@^7.18.11", "@babel/traverse@^7.18.13", "@babel/traverse@^7.18.9": version "7.18.13" resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.18.13.tgz#5ab59ef51a997b3f10c4587d648b9696b6cb1a68" integrity sha512-N6kt9X1jRMLPxxxPYWi7tgvJRH/rtoU+dbKAPDM44RFHiMH8igdsaSBgFeskhSl/kLWLDUvIh1RXCrTmg0/zvA== @@ -1917,6 +2518,20 @@ resolved "https://registry.yarnpkg.com/@colors/colors/-/colors-1.5.0.tgz#bb504579c1cae923e6576a4f5da43d25f97bdbd9" integrity sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ== +"@csstools/postcss-sass@^5.0.1": + version "5.0.1" + resolved "https://registry.yarnpkg.com/@csstools/postcss-sass/-/postcss-sass-5.0.1.tgz#d5e67b4c81f1b634d722148fbe0e871b71b762a5" + integrity sha512-GgQAOe6KfABEIHGh9KFqn/7sX2Dmx554PElvyhRFNADo2QV2N/CzlS+QHrrJmVJzaBn829f4JFcOd67mmYb5Eg== + dependencies: + "@csstools/sass-import-resolve" "^1.0.0" + sass "^1.49.7" + source-map "~0.7.3" + +"@csstools/sass-import-resolve@^1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@csstools/sass-import-resolve/-/sass-import-resolve-1.0.0.tgz#32c3cdb2f7af3cd8f0dca357b592e7271f3831b5" + integrity sha512-pH4KCsbtBLLe7eqUrw8brcuFO8IZlN36JjdKlOublibVdAIPHCzEnpBWOVUXK5sCf+DpBi8ZtuWtjF0srybdeA== + "@docfy/core@^0.4.2": version "0.4.2" resolved "https://registry.yarnpkg.com/@docfy/core/-/core-0.4.2.tgz#6e33378d0bee4e612a9d66bb98050bd08bcd1aad" @@ -2188,6 +2803,14 @@ ember-cli-version-checker "^5.1.2" semver "^7.3.5" +"@embroider/addon-shim@^1.5.0": + version "1.8.3" + resolved "https://registry.yarnpkg.com/@embroider/addon-shim/-/addon-shim-1.8.3.tgz#2368510b8ce42d50d02cb3289c32e260dfa34bd9" + integrity sha512-7pyHwzT6ESXc3nZsB8rfnirLkUhQWdvj6CkYH+0MUPN74mX4rslf7pnBqZE/KZkW3uBIaBYvU8fxi0hcKC/Paw== + dependencies: + "@embroider/shared-internals" "^1.8.3" + semver "^7.3.5" + "@embroider/core@0.33.0", "@embroider/core@^0.33.0": version "0.33.0" resolved "https://registry.yarnpkg.com/@embroider/core/-/core-0.33.0.tgz#0fb1752d6e34ea45368e65c42e13220a57ffae76" @@ -2321,7 +2944,7 @@ resolve "^1.20.0" semver "^7.3.2" -"@embroider/macros@1.8.3", "@embroider/macros@^1.6.0": +"@embroider/macros@1.8.3", "@embroider/macros@^1.0.0", "@embroider/macros@^1.6.0": version "1.8.3" resolved "https://registry.yarnpkg.com/@embroider/macros/-/macros-1.8.3.tgz#2f0961ab8871f6ad819630208031d705b357757e" integrity sha512-gnIOfTL/pUkoD6oI7JyWOqXlVIUgZM+CnbH10/YNtZr2K0hij9eZQMdgjOZZVgN0rKOFw9dIREqc1ygrJHRYQA== @@ -2361,7 +2984,7 @@ semver "^7.3.5" typescript-memoize "^1.0.1" -"@embroider/shared-internals@1.8.3", "@embroider/shared-internals@^1.0.0": +"@embroider/shared-internals@1.8.3", "@embroider/shared-internals@^1.0.0", "@embroider/shared-internals@^1.8.3": version "1.8.3" resolved "https://registry.yarnpkg.com/@embroider/shared-internals/-/shared-internals-1.8.3.tgz#52d868dc80016e9fe983552c0e516f437bf9b9f9" integrity sha512-N5Gho6Qk8z5u+mxLCcMYAoQMbN4MmH+z2jXwQHVs859bxuZTxwF6kKtsybDAASCtd2YGxEmzcc1Ja/wM28824w== @@ -2648,21 +3271,55 @@ faker "^4.1.0" js-yaml "^3.13.1" -"@hashicorp/ember-cli-api-double@^3.1.0": - version "3.1.2" - resolved "https://registry.yarnpkg.com/@hashicorp/ember-cli-api-double/-/ember-cli-api-double-3.1.2.tgz#0eaee116da1431ed63e55eea9ff9c28028cf9f8c" - integrity sha512-4j4JxIHBeo5KjTfcEAIrzjtiWBTxPzTTBMiigNgKAIWAtO6Hz58LQ6kDJl8MN52kSq2uSBlFJpp6aQhfwJaPtw== +"@hashicorp/design-system-components@^1.0.4": + version "1.0.4" + resolved "https://registry.yarnpkg.com/@hashicorp/design-system-components/-/design-system-components-1.0.4.tgz#e258cad1a41b00db3363db25bfdafaa598326b98" + integrity sha512-aaOncgPH4yDEvQuFfOa/cwAOttxwbaEdaENEl+88EOi/HLUe0mdS2HgpC96w3sWhedE/xylCgSHz0DemIj5dJQ== + dependencies: + "@hashicorp/design-system-tokens" "^1.0.0" + "@hashicorp/ember-flight-icons" "^2.0.12" + ember-auto-import "^2.4.1" + ember-cli-babel "^7.26.11" + ember-cli-htmlbars "^6.0.1" + ember-cli-sass "^10.0.1" + ember-keyboard "^8.1.0" + ember-named-blocks-polyfill "^0.2.5" + sass "^1.43.4" + +"@hashicorp/design-system-tokens@^1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@hashicorp/design-system-tokens/-/design-system-tokens-1.0.0.tgz#06ab55873ef444b0958a5192db310278c6501f0b" + integrity sha512-akziX9jiHnQ8KfJA6s8l+98Ukz30C5Lw7BpSPeTduOmdOlJv1uP7w4TV0hC6VIDMDrJrxIF5Y/HnpSCdQGlxQA== + +"@hashicorp/ember-cli-api-double@^4.0.0": + version "4.0.0" + resolved "https://registry.yarnpkg.com/@hashicorp/ember-cli-api-double/-/ember-cli-api-double-4.0.0.tgz#fd6181229c589b4db93f1784d022db064c61ec76" + integrity sha512-ana8k6MjyXgwflAgVJxrgab1vKz/v6212HOHO6Gsz6NaDwzQt0DUuT4dB3VpbiZ6K8D1M7xd0znsmnvR795goA== dependencies: "@hashicorp/api-double" "^1.6.1" array-range "^1.0.1" broccoli-file-creator "^2.1.1" broccoli-merge-trees "^3.0.2" - ember-auto-import "^1.5.3" - ember-cli-babel "^6.6.0" + ember-auto-import "^2.4.2" + ember-cli-babel "^7.26.11" merge-options "^1.0.1" pretender "^3.2.0" recursive-readdir-sync "^1.0.6" +"@hashicorp/ember-flight-icons@^2.0.12": + version "2.0.12" + resolved "https://registry.yarnpkg.com/@hashicorp/ember-flight-icons/-/ember-flight-icons-2.0.12.tgz#788adf7a4fedc468d612d35b604255df948f4012" + integrity sha512-8fHPGaSpMkr5dLWaruwbq9INwZCi2EyTof/TR/dL8PN4UbCuY+KXNqG0lLIKNGFFTj09B1cO303m5GUfKKDGKQ== + dependencies: + "@hashicorp/flight-icons" "^2.10.0" + ember-cli-babel "^7.26.11" + ember-cli-htmlbars "^6.0.1" + +"@hashicorp/flight-icons@^2.10.0": + version "2.10.0" + resolved "https://registry.yarnpkg.com/@hashicorp/flight-icons/-/flight-icons-2.10.0.tgz#24b03043bacda16e505200e6591dfef896ddacf1" + integrity sha512-jYUA0M6Tz+4RAudil+GW/fHbhZPcKCiIZZAguBDviqbLneMkMgPOBgbXWCGWsEQ1fJzP2cXbUaio8L0aQZPWQw== + "@humanwhocodes/config-array@^0.5.0": version "0.5.0" resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.5.0.tgz#1407967d4c6eecd7388f83acf1eaf4d0c6e58ef9" @@ -2733,7 +3390,7 @@ resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz#add4c98d341472a289190b424efbdb096991bb24" integrity sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw== -"@jridgewell/trace-mapping@^0.3.9": +"@jridgewell/trace-mapping@^0.3.14", "@jridgewell/trace-mapping@^0.3.9": version "0.3.15" resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.15.tgz#aba35c48a38d3fd84b37e66c9c0423f9744f9774" integrity sha512-oWZNOULl+UbhsgB51uuZzglikfIKSUBO/M9W2OfEjn7cmqoAiCgmv9lyACTUacZwBz0ITnJ2NqjU8Tx0DHL88g== @@ -2883,11 +3540,32 @@ resolved "https://registry.yarnpkg.com/@types/cors/-/cors-2.8.12.tgz#6b2c510a7ad7039e98e7b8d3d6598f4359e5c080" integrity sha512-vt+kDhq/M2ayberEtJcIN/hxXy1Pk+59g2FV/ZQceeaTyCtCucjL2Q7FXlFjtWn4n15KCr1NE2lNNFhp0lEThw== +"@types/eslint-scope@^3.7.3": + version "3.7.4" + resolved "https://registry.yarnpkg.com/@types/eslint-scope/-/eslint-scope-3.7.4.tgz#37fc1223f0786c39627068a12e94d6e6fc61de16" + integrity sha512-9K4zoImiZc3HlIp6AVUDE4CWYx22a+lhSZMYNpbjW04+YF0KWj4pJXnEMjdnFTiQibFFmElcsasJXDbdI/EPhA== + dependencies: + "@types/eslint" "*" + "@types/estree" "*" + +"@types/eslint@*": + version "8.4.6" + resolved "https://registry.yarnpkg.com/@types/eslint/-/eslint-8.4.6.tgz#7976f054c1bccfcf514bff0564c0c41df5c08207" + integrity sha512-/fqTbjxyFUaYNO7VcW5g+4npmqVACz1bB7RTHYuLj+PRjw9hrCwrUXVQFpChUS0JsyEFvMZ7U/PfmvWgxJhI9g== + dependencies: + "@types/estree" "*" + "@types/json-schema" "*" + "@types/estree@*": version "0.0.46" resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.46.tgz#0fb6bfbbeabd7a30880504993369c4bf1deab1fe" integrity sha512-laIjwTQaD+5DukBZaygQ79K1Z0jb1bPEMRrkXSLjtCcZm+abyp5YbrqpSLzD42FwWW6gK/aS4NYpJ804nG2brg== +"@types/estree@^0.0.51": + version "0.0.51" + resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.51.tgz#cfd70924a25a3fd32b218e5e420e6897e1ac4f40" + integrity sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ== + "@types/express-serve-static-core@^4.17.18": version "4.17.19" resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.17.19.tgz#00acfc1632e729acac4f1530e9e16f6dd1508a1d" @@ -2936,6 +3614,11 @@ dependencies: "@types/unist" "*" +"@types/json-schema@*", "@types/json-schema@^7.0.8", "@types/json-schema@^7.0.9": + version "7.0.11" + resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.11.tgz#d421b6c527a3037f7c84433fd2c4229e016863d3" + integrity sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ== + "@types/json-schema@^7.0.5": version "7.0.7" resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.7.tgz#98a993516c859eb0d5c4c8f098317a9ea68db9ad" @@ -2958,6 +3641,11 @@ resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d" integrity sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA== +"@types/minimatch@^3.0.4": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.5.tgz#1001cc5e6a3704b83c236027e77f2f58ea010f40" + integrity sha512-Klz949h02Gz2uZCMGwDUSDS1YBlTdDDgbWHi+81l29tQALUtvz4rAYi5uoVhE5Lagoq6DeqAUlbrHvW/mXDgdQ== + "@types/node@*": version "14.14.35" resolved "https://registry.yarnpkg.com/@types/node/-/node-14.14.35.tgz#42c953a4e2b18ab931f72477e7012172f4ffa313" @@ -3009,6 +3697,14 @@ resolved "https://registry.yarnpkg.com/@types/unist/-/unist-2.0.3.tgz#9c088679876f374eb5983f150d4787aa6fb32d7e" integrity sha512-FvUupuM3rlRsRtCN+fDudtmytGO6iHJuuRKS1Ss0pG5z8oX0diNEw94UEL7hgDbpN94rgaK5R7sWm6RrSkZuAQ== +"@webassemblyjs/ast@1.11.1": + version "1.11.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.11.1.tgz#2bfd767eae1a6996f432ff7e8d7fc75679c0b6a7" + integrity sha512-ukBh14qFLjxTQNTXocdyksN5QdM28S1CxHt2rdskFyL+xFV7VremuBLVbmCePj+URalXBENx/9Lm7lnhihtCSw== + dependencies: + "@webassemblyjs/helper-numbers" "1.11.1" + "@webassemblyjs/helper-wasm-bytecode" "1.11.1" + "@webassemblyjs/ast@1.9.0": version "1.9.0" resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.9.0.tgz#bd850604b4042459a5a41cd7d338cbed695ed964" @@ -3018,16 +3714,31 @@ "@webassemblyjs/helper-wasm-bytecode" "1.9.0" "@webassemblyjs/wast-parser" "1.9.0" +"@webassemblyjs/floating-point-hex-parser@1.11.1": + version "1.11.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.1.tgz#f6c61a705f0fd7a6aecaa4e8198f23d9dc179e4f" + integrity sha512-iGRfyc5Bq+NnNuX8b5hwBrRjzf0ocrJPI6GWFodBFzmFnyvrQ83SHKhmilCU/8Jv67i4GJZBMhEzltxzcNagtQ== + "@webassemblyjs/floating-point-hex-parser@1.9.0": version "1.9.0" resolved "https://registry.yarnpkg.com/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.9.0.tgz#3c3d3b271bddfc84deb00f71344438311d52ffb4" integrity sha512-TG5qcFsS8QB4g4MhrxK5TqfdNe7Ey/7YL/xN+36rRjl/BlGE/NcBvJcqsRgCP6Z92mRE+7N50pRIi8SmKUbcQA== +"@webassemblyjs/helper-api-error@1.11.1": + version "1.11.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.1.tgz#1a63192d8788e5c012800ba6a7a46c705288fd16" + integrity sha512-RlhS8CBCXfRUR/cwo2ho9bkheSXG0+NwooXcc3PAILALf2QLdFyj7KGsKRbVc95hZnhnERon4kW/D3SZpp6Tcg== + "@webassemblyjs/helper-api-error@1.9.0": version "1.9.0" resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-api-error/-/helper-api-error-1.9.0.tgz#203f676e333b96c9da2eeab3ccef33c45928b6a2" integrity sha512-NcMLjoFMXpsASZFxJ5h2HZRcEhDkvnNFOAKneP5RbKRzaWJN36NC4jqQHKwStIhGXu5mUWlUUk7ygdtrO8lbmw== +"@webassemblyjs/helper-buffer@1.11.1": + version "1.11.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.1.tgz#832a900eb444884cde9a7cad467f81500f5e5ab5" + integrity sha512-gwikF65aDNeeXa8JxXa2BAk+REjSyhrNC9ZwdT0f8jc4dQQeDQ7G4m0f2QCLPJiMTTO6wfDmRmj/pW0PsUvIcA== + "@webassemblyjs/helper-buffer@1.9.0": version "1.9.0" resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-buffer/-/helper-buffer-1.9.0.tgz#a1442d269c5feb23fcbc9ef759dac3547f29de00" @@ -3052,11 +3763,35 @@ dependencies: "@webassemblyjs/ast" "1.9.0" +"@webassemblyjs/helper-numbers@1.11.1": + version "1.11.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.1.tgz#64d81da219fbbba1e3bd1bfc74f6e8c4e10a62ae" + integrity sha512-vDkbxiB8zfnPdNK9Rajcey5C0w+QJugEglN0of+kmO8l7lDb77AnlKYQF7aarZuCrv+l0UvqL+68gSDr3k9LPQ== + dependencies: + "@webassemblyjs/floating-point-hex-parser" "1.11.1" + "@webassemblyjs/helper-api-error" "1.11.1" + "@xtuc/long" "4.2.2" + +"@webassemblyjs/helper-wasm-bytecode@1.11.1": + version "1.11.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.1.tgz#f328241e41e7b199d0b20c18e88429c4433295e1" + integrity sha512-PvpoOGiJwXeTrSf/qfudJhwlvDQxFgelbMqtq52WWiXC6Xgg1IREdngmPN3bs4RoO83PnL/nFrxucXj1+BX62Q== + "@webassemblyjs/helper-wasm-bytecode@1.9.0": version "1.9.0" resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.9.0.tgz#4fed8beac9b8c14f8c58b70d124d549dd1fe5790" integrity sha512-R7FStIzyNcd7xKxCZH5lE0Bqy+hGTwS3LJjuv1ZVxd9O7eHCedSdrId/hMOd20I+v8wDXEn+bjfKDLzTepoaUw== +"@webassemblyjs/helper-wasm-section@1.11.1": + version "1.11.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.1.tgz#21ee065a7b635f319e738f0dd73bfbda281c097a" + integrity sha512-10P9No29rYX1j7F3EVPX3JvGPQPae+AomuSTPiF9eBQeChHI6iqjMIwR9JmOJXwpnn/oVGDk7I5IlskuMwU/pg== + dependencies: + "@webassemblyjs/ast" "1.11.1" + "@webassemblyjs/helper-buffer" "1.11.1" + "@webassemblyjs/helper-wasm-bytecode" "1.11.1" + "@webassemblyjs/wasm-gen" "1.11.1" + "@webassemblyjs/helper-wasm-section@1.9.0": version "1.9.0" resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.9.0.tgz#5a4138d5a6292ba18b04c5ae49717e4167965346" @@ -3067,6 +3802,13 @@ "@webassemblyjs/helper-wasm-bytecode" "1.9.0" "@webassemblyjs/wasm-gen" "1.9.0" +"@webassemblyjs/ieee754@1.11.1": + version "1.11.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/ieee754/-/ieee754-1.11.1.tgz#963929e9bbd05709e7e12243a099180812992614" + integrity sha512-hJ87QIPtAMKbFq6CGTkZYJivEwZDbQUgYd3qKSadTNOhVY7p+gfP6Sr0lLRVTaG1JjFj+r3YchoqRYxNH3M0GQ== + dependencies: + "@xtuc/ieee754" "^1.2.0" + "@webassemblyjs/ieee754@1.9.0": version "1.9.0" resolved "https://registry.yarnpkg.com/@webassemblyjs/ieee754/-/ieee754-1.9.0.tgz#15c7a0fbaae83fb26143bbacf6d6df1702ad39e4" @@ -3074,6 +3816,13 @@ dependencies: "@xtuc/ieee754" "^1.2.0" +"@webassemblyjs/leb128@1.11.1": + version "1.11.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/leb128/-/leb128-1.11.1.tgz#ce814b45574e93d76bae1fb2644ab9cdd9527aa5" + integrity sha512-BJ2P0hNZ0u+Th1YZXJpzW6miwqQUGcIHT1G/sf72gLVD9DZ5AdYTqPNbHZh6K1M5VmKvFXwGSWZADz+qBWxeRw== + dependencies: + "@xtuc/long" "4.2.2" + "@webassemblyjs/leb128@1.9.0": version "1.9.0" resolved "https://registry.yarnpkg.com/@webassemblyjs/leb128/-/leb128-1.9.0.tgz#f19ca0b76a6dc55623a09cffa769e838fa1e1c95" @@ -3081,11 +3830,30 @@ dependencies: "@xtuc/long" "4.2.2" +"@webassemblyjs/utf8@1.11.1": + version "1.11.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/utf8/-/utf8-1.11.1.tgz#d1f8b764369e7c6e6bae350e854dec9a59f0a3ff" + integrity sha512-9kqcxAEdMhiwQkHpkNiorZzqpGrodQQ2IGrHHxCy+Ozng0ofyMA0lTqiLkVs1uzTRejX+/O0EOT7KxqVPuXosQ== + "@webassemblyjs/utf8@1.9.0": version "1.9.0" resolved "https://registry.yarnpkg.com/@webassemblyjs/utf8/-/utf8-1.9.0.tgz#04d33b636f78e6a6813227e82402f7637b6229ab" integrity sha512-GZbQlWtopBTP0u7cHrEx+73yZKrQoBMpwkGEIqlacljhXCkVM1kMQge/Mf+csMJAjEdSwhOyLAS0AoR3AG5P8w== +"@webassemblyjs/wasm-edit@1.11.1": + version "1.11.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.1.tgz#ad206ebf4bf95a058ce9880a8c092c5dec8193d6" + integrity sha512-g+RsupUC1aTHfR8CDgnsVRVZFJqdkFHpsHMfJuWQzWU3tvnLC07UqHICfP+4XyL2tnr1amvl1Sdp06TnYCmVkA== + dependencies: + "@webassemblyjs/ast" "1.11.1" + "@webassemblyjs/helper-buffer" "1.11.1" + "@webassemblyjs/helper-wasm-bytecode" "1.11.1" + "@webassemblyjs/helper-wasm-section" "1.11.1" + "@webassemblyjs/wasm-gen" "1.11.1" + "@webassemblyjs/wasm-opt" "1.11.1" + "@webassemblyjs/wasm-parser" "1.11.1" + "@webassemblyjs/wast-printer" "1.11.1" + "@webassemblyjs/wasm-edit@1.9.0": version "1.9.0" resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-edit/-/wasm-edit-1.9.0.tgz#3fe6d79d3f0f922183aa86002c42dd256cfee9cf" @@ -3100,6 +3868,17 @@ "@webassemblyjs/wasm-parser" "1.9.0" "@webassemblyjs/wast-printer" "1.9.0" +"@webassemblyjs/wasm-gen@1.11.1": + version "1.11.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.1.tgz#86c5ea304849759b7d88c47a32f4f039ae3c8f76" + integrity sha512-F7QqKXwwNlMmsulj6+O7r4mmtAlCWfO/0HdgOxSklZfQcDu0TpLiD1mRt/zF25Bk59FIjEuGAIyn5ei4yMfLhA== + dependencies: + "@webassemblyjs/ast" "1.11.1" + "@webassemblyjs/helper-wasm-bytecode" "1.11.1" + "@webassemblyjs/ieee754" "1.11.1" + "@webassemblyjs/leb128" "1.11.1" + "@webassemblyjs/utf8" "1.11.1" + "@webassemblyjs/wasm-gen@1.9.0": version "1.9.0" resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-gen/-/wasm-gen-1.9.0.tgz#50bc70ec68ded8e2763b01a1418bf43491a7a49c" @@ -3111,6 +3890,16 @@ "@webassemblyjs/leb128" "1.9.0" "@webassemblyjs/utf8" "1.9.0" +"@webassemblyjs/wasm-opt@1.11.1": + version "1.11.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.1.tgz#657b4c2202f4cf3b345f8a4c6461c8c2418985f2" + integrity sha512-VqnkNqnZlU5EB64pp1l7hdm3hmQw7Vgqa0KF/KCNO9sIpI6Fk6brDEiX+iCOYrvMuBWDws0NkTOxYEb85XQHHw== + dependencies: + "@webassemblyjs/ast" "1.11.1" + "@webassemblyjs/helper-buffer" "1.11.1" + "@webassemblyjs/wasm-gen" "1.11.1" + "@webassemblyjs/wasm-parser" "1.11.1" + "@webassemblyjs/wasm-opt@1.9.0": version "1.9.0" resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-opt/-/wasm-opt-1.9.0.tgz#2211181e5b31326443cc8112eb9f0b9028721a61" @@ -3121,6 +3910,18 @@ "@webassemblyjs/wasm-gen" "1.9.0" "@webassemblyjs/wasm-parser" "1.9.0" +"@webassemblyjs/wasm-parser@1.11.1": + version "1.11.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.1.tgz#86ca734534f417e9bd3c67c7a1c75d8be41fb199" + integrity sha512-rrBujw+dJu32gYB7/Lup6UhdkPx9S9SnobZzRVL7VcBH9Bt9bCBLEuX/YXOOtBsOZ4NQrRykKhffRWHvigQvOA== + dependencies: + "@webassemblyjs/ast" "1.11.1" + "@webassemblyjs/helper-api-error" "1.11.1" + "@webassemblyjs/helper-wasm-bytecode" "1.11.1" + "@webassemblyjs/ieee754" "1.11.1" + "@webassemblyjs/leb128" "1.11.1" + "@webassemblyjs/utf8" "1.11.1" + "@webassemblyjs/wasm-parser@1.9.0": version "1.9.0" resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-parser/-/wasm-parser-1.9.0.tgz#9d48e44826df4a6598294aa6c87469d642fff65e" @@ -3145,6 +3946,14 @@ "@webassemblyjs/helper-fsm" "1.9.0" "@xtuc/long" "4.2.2" +"@webassemblyjs/wast-printer@1.11.1": + version "1.11.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-printer/-/wast-printer-1.11.1.tgz#d0c73beda8eec5426f10ae8ef55cee5e7084c2f0" + integrity sha512-IQboUWM4eKzWW+N/jij2sRatKMh99QEelo3Eb2q0qXkvPRISAj8Qxtmw5itwqK+TTkBuUIE45AxYPToqPtL5gg== + dependencies: + "@webassemblyjs/ast" "1.11.1" + "@xtuc/long" "4.2.2" + "@webassemblyjs/wast-printer@1.9.0": version "1.9.0" resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-printer/-/wast-printer-1.9.0.tgz#4935d54c85fef637b00ce9f52377451d00d47899" @@ -3212,12 +4021,26 @@ acorn-globals@^6.0.0: acorn "^7.1.1" acorn-walk "^7.1.1" +acorn-import-assertions@^1.7.6: + version "1.8.0" + resolved "https://registry.yarnpkg.com/acorn-import-assertions/-/acorn-import-assertions-1.8.0.tgz#ba2b5939ce62c238db6d93d81c9b111b29b855e9" + integrity sha512-m7VZ3jwz4eK6A4Vtt8Ew1/mNbP24u0FhdyfA7fSvnJR6LMdfOYnmuIrrJAgrYfYJ10F/otaHTtrtrtmHdMNzEw== + acorn-jsx@^5.3.1: version "5.3.1" resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.1.tgz#fc8661e11b7ac1539c47dbfea2e72b3af34d267b" integrity sha512-K0Ptm/47OKfQRpNQ2J/oIN/3QYiK6FwW+eJbILhsdxh2WTLdl+30o8aGdTbm5JbffpFFAg/g+zi1E+jvJha5ng== -acorn-walk@^7.1.1: +acorn-node@^1.8.2: + version "1.8.2" + resolved "https://registry.yarnpkg.com/acorn-node/-/acorn-node-1.8.2.tgz#114c95d64539e53dede23de8b9d96df7c7ae2af8" + integrity sha512-8mt+fslDufLYntIoPAaIMUe/lrbrehIiwmR3t2k9LljIzoigEPF27eLk2hy8zSGzmR/ogr7zbRKINMo1u0yh5A== + dependencies: + acorn "^7.0.0" + acorn-walk "^7.0.0" + xtend "^4.0.2" + +acorn-walk@^7.0.0, acorn-walk@^7.1.1: version "7.2.0" resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-7.2.0.tgz#0de889a601203909b0fbe07b8938dc21d2e967bc" integrity sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA== @@ -3227,7 +4050,7 @@ acorn@^6.4.1: resolved "https://registry.yarnpkg.com/acorn/-/acorn-6.4.2.tgz#35866fd710528e92de10cf06016498e47e39e1e6" integrity sha512-XtGIhXwF8YM8bJhGxG5kXgjkEuNGLTkoYqVE+KMR+aspr4KGYmKYg7yUe3KghyQ9yheNwLnjmzh/7+gfDBmHCQ== -acorn@^7.1.0, acorn@^7.1.1, acorn@^7.4.0: +acorn@^7.0.0, acorn@^7.1.0, acorn@^7.1.1, acorn@^7.4.0: version "7.4.1" resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa" integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A== @@ -3237,7 +4060,7 @@ acorn@^8.0.5: resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.1.0.tgz#52311fd7037ae119cbb134309e901aa46295b3fe" integrity sha512-LWCF/Wn0nfHOmJ9rzQApGnxnvgfROzGilS8936rqN/lfcYkY9MYZzdMqN+2NJ4SlTc+m5HiSa+kNfDtI64dwUA== -acorn@^8.5.0: +acorn@^8.5.0, acorn@^8.7.1: version "8.8.0" resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.8.0.tgz#88c0187620435c7f6015803f5539dae05a9dbea8" integrity sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w== @@ -3255,12 +4078,26 @@ ajv-errors@^1.0.0: resolved "https://registry.yarnpkg.com/ajv-errors/-/ajv-errors-1.0.1.tgz#f35986aceb91afadec4102fbd85014950cefa64d" integrity sha512-DCRfO/4nQ+89p/RK43i8Ezd41EqdGIU4ld7nGF8OQ14oc/we5rEntLCUa7+jrn3nn83BosfwZA0wb4pon2o8iQ== +ajv-formats@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/ajv-formats/-/ajv-formats-2.1.1.tgz#6e669400659eb74973bbf2e33327180a0996b520" + integrity sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA== + dependencies: + ajv "^8.0.0" + ajv-keywords@^3.1.0, ajv-keywords@^3.4.1, ajv-keywords@^3.5.2: version "3.5.2" resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.5.2.tgz#31f29da5ab6e00d1c2d329acf7b5929614d5014d" integrity sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ== -ajv@^6.1.0, ajv@^6.10.0, ajv@^6.10.2, ajv@^6.12.3, ajv@^6.12.4: +ajv-keywords@^5.0.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-5.1.0.tgz#69d4d385a4733cdbeab44964a1170a88f87f0e16" + integrity sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw== + dependencies: + fast-deep-equal "^3.1.3" + +ajv@^6.1.0, ajv@^6.10.0, ajv@^6.10.2, ajv@^6.12.3, ajv@^6.12.4, ajv@^6.12.5: version "6.12.6" resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== @@ -3270,7 +4107,7 @@ ajv@^6.1.0, ajv@^6.10.0, ajv@^6.10.2, ajv@^6.12.3, ajv@^6.12.4: json-schema-traverse "^0.4.1" uri-js "^4.2.2" -ajv@^8.0.1: +ajv@^8.0.0, ajv@^8.0.1, ajv@^8.8.0: version "8.11.0" resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.11.0.tgz#977e91dd96ca669f54a11e23e378e33b884a565f" integrity sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg== @@ -3378,6 +4215,13 @@ ansi-styles@~1.0.0: resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-1.0.0.tgz#cb102df1c56f5123eab8b67cd7b98027a0279178" integrity sha1-yxAt8cVvUSPquLZ817mAJ6AnkXg= +ansi-to-html@^0.6.15: + version "0.6.15" + resolved "https://registry.yarnpkg.com/ansi-to-html/-/ansi-to-html-0.6.15.tgz#ac6ad4798a00f6aa045535d7f6a9cb9294eebea7" + integrity sha512-28ijx2aHJGdzbs+O5SNQF65r6rrKYnkuwTYm8lZlChuoJ9P1vVzIpWO20sQTqTPDXYp6NFwk326vApTtLVFXpQ== + dependencies: + entities "^2.0.0" + ansi-to-html@^0.6.6: version "0.6.14" resolved "https://registry.yarnpkg.com/ansi-to-html/-/ansi-to-html-0.6.14.tgz#65fe6d08bba5dd9db33f44a20aec331e0010dad8" @@ -3406,6 +4250,14 @@ anymatch@~3.1.1: normalize-path "^3.0.0" picomatch "^2.0.4" +anymatch@~3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.2.tgz#c0557c096af32f106198f4f4e2a383537e378716" + integrity sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg== + dependencies: + normalize-path "^3.0.0" + picomatch "^2.0.4" + aot-test-generators@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/aot-test-generators/-/aot-test-generators-0.1.0.tgz#43f0f615f97cb298d7919c1b0b4e6b7310b03cd0" @@ -3439,6 +4291,11 @@ are-we-there-yet@~1.1.2: delegates "^1.0.0" readable-stream "^2.0.6" +arg@^5.0.2: + version "5.0.2" + resolved "https://registry.yarnpkg.com/arg/-/arg-5.0.2.tgz#c81433cc427c92c4dcf4865142dbca6f15acd59c" + integrity sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg== + argparse@^1.0.7: version "1.0.10" resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" @@ -3641,6 +4498,18 @@ atob@^2.1.2: resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9" integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg== +autoprefixer@^10.4.8: + version "10.4.8" + resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-10.4.8.tgz#92c7a0199e1cfb2ad5d9427bd585a3d75895b9e5" + integrity sha512-75Jr6Q/XpTqEf6D2ltS5uMewJIx5irCU1oBYJrWjFenq/m12WRRrz6g15L1EIoYvPLXTbEry7rDOwrcYNj77xw== + dependencies: + browserslist "^4.21.3" + caniuse-lite "^1.0.30001373" + fraction.js "^4.2.0" + normalize-range "^0.1.2" + picocolors "^1.0.0" + postcss-value-parser "^4.2.0" + available-typed-arrays@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.2.tgz#6b098ca9d8039079ee3f77f7b783c4480ba513f5" @@ -3937,7 +4806,7 @@ babel-plugin-htmlbars-inline-precompile@^5.0.0: dependencies: babel-plugin-ember-modules-api-polyfill "^3.4.0" -babel-plugin-htmlbars-inline-precompile@^5.3.0: +babel-plugin-htmlbars-inline-precompile@^5.2.1, babel-plugin-htmlbars-inline-precompile@^5.3.0: version "5.3.1" resolved "https://registry.yarnpkg.com/babel-plugin-htmlbars-inline-precompile/-/babel-plugin-htmlbars-inline-precompile-5.3.1.tgz#5ba272e2e4b6221522401f5f1d98a73b1de38787" integrity sha512-QWjjFgSKtSRIcsBhJmEwS2laIdrA6na8HAlc/pEAhjHgQsah/gMiBFRZvbQTy//hWxR4BMwV7/Mya7q5H8uHeA== @@ -3999,6 +4868,15 @@ babel-plugin-polyfill-corejs2@^0.3.0: "@babel/helper-define-polyfill-provider" "^0.3.1" semver "^6.1.1" +babel-plugin-polyfill-corejs2@^0.3.2: + version "0.3.2" + resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.3.2.tgz#e4c31d4c89b56f3cf85b92558954c66b54bd972d" + integrity sha512-LPnodUl3lS0/4wN3Rb+m+UK8s7lj2jcLRrjho4gLw+OJs+I4bvGXshINesY5xx/apM+biTnQ9reDI8yj+0M5+Q== + dependencies: + "@babel/compat-data" "^7.17.7" + "@babel/helper-define-polyfill-provider" "^0.3.2" + semver "^6.1.1" + babel-plugin-polyfill-corejs3@^0.1.3: version "0.1.7" resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.1.7.tgz#80449d9d6f2274912e05d9e182b54816904befd0" @@ -4015,6 +4893,14 @@ babel-plugin-polyfill-corejs3@^0.5.0: "@babel/helper-define-polyfill-provider" "^0.3.1" core-js-compat "^3.21.0" +babel-plugin-polyfill-corejs3@^0.5.3: + version "0.5.3" + resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.5.3.tgz#d7e09c9a899079d71a8b670c6181af56ec19c5c7" + integrity sha512-zKsXDh0XjnrUEW0mxIHLfjBfnXSMr5Q/goMe/fxpQnLm07mcOZiIZHBNWCMx60HmdvjxfXcalac0tfFg0wqxyw== + dependencies: + "@babel/helper-define-polyfill-provider" "^0.3.2" + core-js-compat "^3.21.0" + babel-plugin-polyfill-regenerator@^0.1.2: version "0.1.6" resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.1.6.tgz#0fe06a026fe0faa628ccc8ba3302da0a6ce02f3f" @@ -4029,6 +4915,13 @@ babel-plugin-polyfill-regenerator@^0.3.0: dependencies: "@babel/helper-define-polyfill-provider" "^0.3.1" +babel-plugin-polyfill-regenerator@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.4.0.tgz#8f51809b6d5883e07e71548d75966ff7635527fe" + integrity sha512-RW1cnryiADFeHmfLS+WW/G431p1PsW5qdRdz0SDRi7TKcUgc7Oh/uXkT7MZ/+tGsT1BkczEAmD5XjUyJ5SWDTw== + dependencies: + "@babel/helper-define-polyfill-provider" "^0.3.2" + babel-plugin-strip-function-call@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/babel-plugin-strip-function-call/-/babel-plugin-strip-function-call-1.0.2.tgz#374a68b5648e16e2b6d1effd280c3abc88648e3a" @@ -4581,7 +5474,7 @@ braces@^2.3.1, braces@^2.3.2: split-string "^3.0.2" to-regex "^3.0.1" -braces@^3.0.1, braces@~3.0.2: +braces@^3.0.1, braces@^3.0.2, braces@~3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== @@ -4845,33 +5738,33 @@ broccoli-funnel@^2.0.0, broccoli-funnel@^2.0.1, broccoli-funnel@^2.0.2: symlink-or-copy "^1.0.0" walk-sync "^0.3.1" -broccoli-funnel@^3.0.1, broccoli-funnel@^3.0.2, broccoli-funnel@^3.0.3: - version "3.0.3" - resolved "https://registry.yarnpkg.com/broccoli-funnel/-/broccoli-funnel-3.0.3.tgz#26fd42632471f67a91f4770d1987118087219937" - integrity sha512-LPzZ91BwStoHZXdXHQAJeYORl189OrRKM5NdIi86SDU9wZ4s/3lV1PRFOiobDT/jKM10voM7CDzfvicHbCYxAQ== +broccoli-funnel@^3.0.0, broccoli-funnel@^3.0.5, broccoli-funnel@^3.0.8: + version "3.0.8" + resolved "https://registry.yarnpkg.com/broccoli-funnel/-/broccoli-funnel-3.0.8.tgz#f5b62e2763c3918026a15a3c833edc889971279b" + integrity sha512-ng4eIhPYiXqMw6SyGoxPHR3YAwEd2lr9FgBI1CyTbspl4txZovOsmzFkMkGAlu88xyvYXJqHiM2crfLa65T1BQ== dependencies: array-equal "^1.0.0" - blank-object "^1.0.1" - broccoli-plugin "^4.0.1" + broccoli-plugin "^4.0.7" debug "^4.1.1" - fast-ordered-set "^1.0.0" fs-tree-diff "^2.0.1" heimdalljs "^0.2.0" minimatch "^3.0.0" - path-posix "^1.0.0" walk-sync "^2.0.2" -broccoli-funnel@^3.0.5, broccoli-funnel@^3.0.8: - version "3.0.8" - resolved "https://registry.yarnpkg.com/broccoli-funnel/-/broccoli-funnel-3.0.8.tgz#f5b62e2763c3918026a15a3c833edc889971279b" - integrity sha512-ng4eIhPYiXqMw6SyGoxPHR3YAwEd2lr9FgBI1CyTbspl4txZovOsmzFkMkGAlu88xyvYXJqHiM2crfLa65T1BQ== +broccoli-funnel@^3.0.1, broccoli-funnel@^3.0.2, broccoli-funnel@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/broccoli-funnel/-/broccoli-funnel-3.0.3.tgz#26fd42632471f67a91f4770d1987118087219937" + integrity sha512-LPzZ91BwStoHZXdXHQAJeYORl189OrRKM5NdIi86SDU9wZ4s/3lV1PRFOiobDT/jKM10voM7CDzfvicHbCYxAQ== dependencies: array-equal "^1.0.0" - broccoli-plugin "^4.0.7" + blank-object "^1.0.1" + broccoli-plugin "^4.0.1" debug "^4.1.1" + fast-ordered-set "^1.0.0" fs-tree-diff "^2.0.1" heimdalljs "^0.2.0" minimatch "^3.0.0" + path-posix "^1.0.0" walk-sync "^2.0.2" broccoli-kitchen-sink-helpers@^0.2.5: @@ -5018,6 +5911,23 @@ broccoli-persistent-filter@^2.1.0, broccoli-persistent-filter@^2.2.1, broccoli-p sync-disk-cache "^1.3.3" walk-sync "^1.0.0" +broccoli-persistent-filter@^3.1.1: + version "3.1.3" + resolved "https://registry.yarnpkg.com/broccoli-persistent-filter/-/broccoli-persistent-filter-3.1.3.tgz#aca815bf3e3b0247bd0a7b567fdb0d0e08c99cc2" + integrity sha512-Q+8iezprZzL9voaBsDY3rQVl7c7H5h+bvv8SpzCZXPZgfBFCbx7KFQ2c3rZR6lW5k4Kwoqt7jG+rZMUg67Gwxw== + dependencies: + async-disk-cache "^2.0.0" + async-promise-queue "^1.0.3" + broccoli-plugin "^4.0.3" + fs-tree-diff "^2.0.0" + hash-for-dep "^1.5.0" + heimdalljs "^0.2.1" + heimdalljs-logger "^0.1.7" + promise-map-series "^0.2.1" + rimraf "^3.0.0" + symlink-or-copy "^1.0.1" + sync-disk-cache "^2.0.0" + broccoli-persistent-filter@^3.1.2: version "3.1.2" resolved "https://registry.yarnpkg.com/broccoli-persistent-filter/-/broccoli-persistent-filter-3.1.2.tgz#41da6b9577be09a170ecde185f2c5a6099f99c4e" @@ -5104,6 +6014,29 @@ broccoli-plugin@^4.0.5, broccoli-plugin@^4.0.7: rimraf "^3.0.2" symlink-or-copy "^1.3.1" +broccoli-postcss-single@^5.0.1: + version "5.0.2" + resolved "https://registry.yarnpkg.com/broccoli-postcss-single/-/broccoli-postcss-single-5.0.2.tgz#f23661b3011494d8a2dbd8ff39eb394e80313682" + integrity sha512-r4eWtz/5uihtHwOszViWwV6weJr9VryvaqtVo1DOh4gL+TbTyU+NX+Y+t9TqUw99OtuivMz4uHLLH7zZECbZmw== + dependencies: + broccoli-caching-writer "^3.0.3" + include-path-searcher "^0.1.0" + minimist ">=1.2.5" + mkdirp "^1.0.3" + object-assign "^4.1.1" + postcss "^8.1.4" + +broccoli-postcss@^6.0.1: + version "6.1.0" + resolved "https://registry.yarnpkg.com/broccoli-postcss/-/broccoli-postcss-6.1.0.tgz#1e15c5e8a65a984544224f083cbd1e6763691b60" + integrity sha512-I8+DHq5xcCBHU0PpCtDMayAmSUVx07CqAquUpdlNUHckXeD//cUFf4aFQllnZBhF8Z86YLhuA+j7qvCYYgBXRg== + dependencies: + broccoli-funnel "^3.0.0" + broccoli-persistent-filter "^3.1.1" + minimist ">=1.2.5" + object-assign "^4.1.1" + postcss "^8.1.4" + broccoli-rollup@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/broccoli-rollup/-/broccoli-rollup-4.1.1.tgz#7531a24d88ddab9f1bace1c6ee6e6ca74a38d36f" @@ -5332,7 +6265,7 @@ browserslist@^4.17.5, browserslist@^4.19.1: node-releases "^2.0.2" picocolors "^1.0.0" -browserslist@^4.20.2: +browserslist@^4.20.2, browserslist@^4.21.3: version "4.21.3" resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.21.3.tgz#5df277694eb3c48bc5c4b05af3e8b7e09c5a6d1a" integrity sha512-898rgRXLAyRkM1GryrrBHGkqA5hlpkV5MhtZwg9QXeiyLUYs2k00Un05aX5l2/yJIOObYKOpS2JNo8nJDE7fWQ== @@ -5469,6 +6402,11 @@ callsites@^3.0.0, callsites@^3.1.0: resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== +camelcase-css@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/camelcase-css/-/camelcase-css-2.0.1.tgz#ee978f6947914cc30c6b44741b6ed1df7f043fd5" + integrity sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA== + camelcase@^5.3.1: version "5.3.1" resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" @@ -5491,10 +6429,10 @@ caniuse-lite@^1.0.30001312: resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001312.tgz#e11eba4b87e24d22697dae05455d5aea28550d5f" integrity sha512-Wiz1Psk2MEK0pX3rUzWaunLTZzqS2JYZFzNKqAiJGiuxIjRPLgV6+VDPOg6lQOUxmDwhTlh198JsTTi8Hzw6aQ== -caniuse-lite@^1.0.30001370: - version "1.0.30001390" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001390.tgz#158a43011e7068ef7fc73590e9fd91a7cece5e7f" - integrity sha512-sS4CaUM+/+vqQUlCvCJ2WtDlV81aWtHhqeEVkLokVJJa3ViN4zDxAGfq9R8i1m90uGHxo99cy10Od+lvn3hf0g== +caniuse-lite@^1.0.30001370, caniuse-lite@^1.0.30001373: + version "1.0.30001384" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001384.tgz#029527c2d781a3cfef13fa63b3a78a6088e35973" + integrity sha512-BBWt57kqWbc0GYZXb47wTXpmAgqr5LSibPzNjk/AWMdmJMQhLqOl3c/Kd4OAU/tu4NLfYkMx8Tlq3RVBkOBolQ== capture-exit@^2.0.0: version "2.0.0" @@ -5618,6 +6556,21 @@ charm@^1.0.0: optionalDependencies: fsevents "~2.3.1" +"chokidar@>=3.0.0 <4.0.0", chokidar@^3.5.3: + version "3.5.3" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd" + integrity sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw== + dependencies: + anymatch "~3.1.2" + braces "~3.0.2" + glob-parent "~5.1.2" + is-binary-path "~2.1.0" + is-glob "~4.0.1" + normalize-path "~3.0.0" + readdirp "~3.6.0" + optionalDependencies: + fsevents "~2.3.2" + chokidar@^2.1.8: version "2.1.8" resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-2.1.8.tgz#804b3a7b6a99358c3c5c61e71d8728f041cff917" @@ -5832,7 +6785,7 @@ color-name@1.1.3: resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU= -color-name@~1.1.4: +color-name@^1.1.4, color-name@~1.1.4: version "1.1.4" resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== @@ -6083,6 +7036,14 @@ core-js-compat@^3.20.2, core-js-compat@^3.21.0: browserslist "^4.19.1" semver "7.0.0" +core-js-compat@^3.22.1: + version "3.25.0" + resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.25.0.tgz#489affbfbf9cb3fa56192fe2dd9ebaee985a66c5" + integrity sha512-extKQM0g8/3GjFx9US12FAgx8KJawB7RCQ5y8ipYLbmfzEzmFRWdDjIlxDx82g7ygcNG85qMVUSRyABouELdow== + dependencies: + browserslist "^4.21.3" + semver "7.0.0" + core-js-compat@^3.8.1, core-js-compat@^3.9.0: version "3.9.1" resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.9.1.tgz#4e572acfe90aff69d76d8c37759d21a5c59bb455" @@ -6200,6 +7161,22 @@ crypto-random-string@^2.0.0: resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-2.0.0.tgz#ef2a7a966ec11083388369baa02ebead229b30d5" integrity sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA== +css-loader@^5.2.0: + version "5.2.7" + resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-5.2.7.tgz#9b9f111edf6fb2be5dc62525644cbc9c232064ae" + integrity sha512-Q7mOvpBNBG7YrVGMxRxcBJZFL75o+cH2abNASdibkj/fffYD8qWbInZrD0S9ccI6vZclF3DsHE7njGlLtaHbhg== + dependencies: + icss-utils "^5.1.0" + loader-utils "^2.0.0" + postcss "^8.2.15" + postcss-modules-extract-imports "^3.0.0" + postcss-modules-local-by-default "^4.0.0" + postcss-modules-scope "^3.0.0" + postcss-modules-values "^4.0.0" + postcss-value-parser "^4.1.0" + schema-utils "^3.0.0" + semver "^7.3.5" + css-tree@^2.0.4: version "2.2.1" resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-2.2.1.tgz#36115d382d60afd271e377f9c5f67d02bd48c032" @@ -6213,6 +7190,20 @@ css.escape@^1.5.1: resolved "https://registry.yarnpkg.com/css.escape/-/css.escape-1.5.1.tgz#42e27d4fa04ae32f931a4b4d4191fa9cddee97cb" integrity sha1-QuJ9T6BK4y+TGktNQZH6nN3ul8s= +css@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/css/-/css-3.0.0.tgz#4447a4d58fdd03367c516ca9f64ae365cee4aa5d" + integrity sha512-DG9pFfwOrzc+hawpmqX/dHYHJG+Bsdb0klhyi1sDneOgGOXy9wQIC8hzyVp1e4NRYDBdxcylvywPkkXCHAzTyQ== + dependencies: + inherits "^2.0.4" + source-map "^0.6.1" + source-map-resolve "^0.6.0" + +cssesc@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee" + integrity sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg== + cssom@^0.4.4: version "0.4.4" resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.4.4.tgz#5a66cf93d2d0b661d80bf6a44fb65f5c2e4e0a10" @@ -6513,6 +7504,15 @@ detect-newline@3.1.0: resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-3.1.0.tgz#576f5dfc63ae1a192ff192d8ad3af6308991b651" integrity sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA== +detective@^5.2.1: + version "5.2.1" + resolved "https://registry.yarnpkg.com/detective/-/detective-5.2.1.tgz#6af01eeda11015acb0e73f933242b70f24f91034" + integrity sha512-v9XE1zRnz1wRtgurGu0Bs8uHKFSTdteYZNbIPFVhUZ39L/S79ppMpdmVOZAnoz1jfEFodc48n6MX483Xo3t1yw== + dependencies: + acorn-node "^1.8.2" + defined "^1.0.0" + minimist "^1.2.6" + dezalgo@^1.0.0: version "1.0.3" resolved "https://registry.yarnpkg.com/dezalgo/-/dezalgo-1.0.3.tgz#7f742de066fc748bc8db820569dddce49bf0d456" @@ -6521,6 +7521,11 @@ dezalgo@^1.0.0: asap "^2.0.0" wrappy "1" +didyoumean@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/didyoumean/-/didyoumean-1.2.2.tgz#989346ffe9e839b4555ecf5666edea0d3e8ad037" + integrity sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw== + diff@^4.0.2: version "4.0.2" resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" @@ -6542,6 +7547,11 @@ dir-glob@^3.0.1: dependencies: path-type "^4.0.0" +dlv@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/dlv/-/dlv-1.1.3.tgz#5c198a8a11453596e751494d49874bc7732f2e79" + integrity sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA== + doctoc@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/doctoc/-/doctoc-2.0.0.tgz#3c5c51ba89acb9b8e1924cc429500d6de2dfb90e" @@ -6674,9 +7684,9 @@ electron-to-chromium@^1.3.47, electron-to-chromium@^1.3.649: integrity sha512-Ix+zDUAXWZuUzqKdhkgN5dP7ZM+IwMG4yAGFGDLpGJP/3vNEEwuHG1LIhtXUfW0FFV0j38t5PUv2n/3MFSRviQ== electron-to-chromium@^1.4.202: - version "1.4.241" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.241.tgz#5aa03ab94db590d8269f4518157c24b1efad34d6" - integrity sha512-e7Wsh4ilaioBZ5bMm6+F4V5c11dh56/5Jwz7Hl5Tu1J7cnB+Pqx5qIF2iC7HPpfyQMqGSvvLP5bBAIDd2gAtGw== + version "1.4.233" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.233.tgz#aa142e45468bda111b88abc9cc59d573b75d6a60" + integrity sha512-ejwIKXTg1wqbmkcRJh9Ur3hFGHFDZDw1POzdsVrB2WZjgRuRMHIQQKNpe64N/qh3ZtH2otEoRoS+s6arAAuAAw== electron-to-chromium@^1.4.71: version "1.4.75" @@ -6775,10 +7785,46 @@ ember-auto-import@^1.5.2, ember-auto-import@^1.5.3, ember-auto-import@^1.6.0: resolve-package-path "^3.1.0" rimraf "^2.6.2" semver "^7.3.4" - symlink-or-copy "^1.2.0" + symlink-or-copy "^1.2.0" + typescript-memoize "^1.0.0-alpha.3" + walk-sync "^0.3.3" + webpack "^4.43.0" + +ember-auto-import@^2.2.3, ember-auto-import@^2.4.1, ember-auto-import@^2.4.2: + version "2.4.2" + resolved "https://registry.yarnpkg.com/ember-auto-import/-/ember-auto-import-2.4.2.tgz#d4d3bc6885a11cf124f606f5c37169bdf76e37ae" + integrity sha512-REh+1aJWpTkvN42a/ga41OuRpUsSW7UQfPr2wPtYx56o/xoSNhVBXejy7yV9ObrkN7gogz6fs2xZwih5cOwpYg== + dependencies: + "@babel/core" "^7.16.7" + "@babel/plugin-proposal-class-properties" "^7.16.7" + "@babel/plugin-proposal-decorators" "^7.16.7" + "@babel/preset-env" "^7.16.7" + "@embroider/macros" "^1.0.0" + "@embroider/shared-internals" "^1.0.0" + babel-loader "^8.0.6" + babel-plugin-ember-modules-api-polyfill "^3.5.0" + babel-plugin-htmlbars-inline-precompile "^5.2.1" + babel-plugin-syntax-dynamic-import "^6.18.0" + broccoli-debug "^0.6.4" + broccoli-funnel "^3.0.8" + broccoli-merge-trees "^4.2.0" + broccoli-plugin "^4.0.0" + broccoli-source "^3.0.0" + css-loader "^5.2.0" + debug "^4.3.1" + fs-extra "^10.0.0" + fs-tree-diff "^2.0.0" + handlebars "^4.3.1" + js-string-escape "^1.0.1" + lodash "^4.17.19" + mini-css-extract-plugin "^2.5.2" + parse5 "^6.0.1" + resolve "^1.20.0" + resolve-package-path "^4.0.3" + semver "^7.3.4" + style-loader "^2.0.0" typescript-memoize "^1.0.0-alpha.3" - walk-sync "^0.3.3" - webpack "^4.43.0" + walk-sync "^3.0.0" ember-basic-dropdown@3.0.21, ember-basic-dropdown@^3.0.16: version "3.0.21" @@ -6900,7 +7946,7 @@ ember-cli-babel@^6.0.0, ember-cli-babel@^6.0.0-beta.4, ember-cli-babel@^6.11.0, ember-cli-version-checker "^2.1.2" semver "^5.5.0" -ember-cli-babel@^7.13.2, ember-cli-babel@^7.26.1, ember-cli-babel@^7.26.3, ember-cli-babel@^7.26.5, ember-cli-babel@^7.4.0: +ember-cli-babel@^7.13.2, ember-cli-babel@^7.26.1, ember-cli-babel@^7.26.11, ember-cli-babel@^7.26.3, ember-cli-babel@^7.26.5, ember-cli-babel@^7.4.0: version "7.26.11" resolved "https://registry.yarnpkg.com/ember-cli-babel/-/ember-cli-babel-7.26.11.tgz#50da0fe4dcd99aada499843940fec75076249a9f" integrity sha512-JJYeYjiz/JTn34q7F5DSOjkkZqy8qwFOOxXfE6pe9yEJqWGu4qErKxlz8I22JoVEQ/aBUO+OcKTpmctvykM9YA== @@ -7114,6 +8160,26 @@ ember-cli-htmlbars@^6.0.0: strip-bom "^4.0.0" walk-sync "^2.2.0" +ember-cli-htmlbars@^6.0.1: + version "6.1.0" + resolved "https://registry.yarnpkg.com/ember-cli-htmlbars/-/ember-cli-htmlbars-6.1.0.tgz#97150c2a6f9a981475599d74817df66f5d816c2b" + integrity sha512-kT+MA2JsNLk10HpxAjpB5HHtR0WCoxZEUwLsy/BBww5lXmsrf34QzmTw7SL6DabZVxs+YCb9RhU9KTmFygGxCg== + dependencies: + "@ember/edition-utils" "^1.2.0" + babel-plugin-ember-template-compilation "^1.0.0" + babel-plugin-htmlbars-inline-precompile "^5.3.0" + broccoli-debug "^0.6.5" + broccoli-persistent-filter "^3.1.2" + broccoli-plugin "^4.0.3" + ember-cli-version-checker "^5.1.2" + fs-tree-diff "^2.0.1" + hash-for-dep "^1.5.1" + heimdalljs-logger "^0.1.10" + js-string-escape "^1.0.1" + semver "^7.3.4" + silent-error "^1.1.1" + walk-sync "^2.2.0" + ember-cli-inject-live-reload@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/ember-cli-inject-live-reload/-/ember-cli-inject-live-reload-2.0.2.tgz#95edb543b386239d35959e5ea9579f5382976ac7" @@ -7170,6 +8236,17 @@ ember-cli-path-utils@^1.0.0: resolved "https://registry.yarnpkg.com/ember-cli-path-utils/-/ember-cli-path-utils-1.0.0.tgz#4e39af8b55301cddc5017739b77a804fba2071ed" integrity sha1-Tjmvi1UwHN3FAXc5t3qAT7ogce0= +ember-cli-postcss@^8.1.0: + version "8.1.0" + resolved "https://registry.yarnpkg.com/ember-cli-postcss/-/ember-cli-postcss-8.1.0.tgz#a7bbcb7799f3fb9c908247de30c0bee312e0399b" + integrity sha512-GkvMgM/GMoSi5H1xl+cp/nudQJ1uT49cgvFOx1anjhFiWDvym5Okq83JOhfbUcUAOurHjMLWugDmpAaxDM8KyA== + dependencies: + broccoli-merge-trees "^4.2.0" + broccoli-postcss "^6.0.1" + broccoli-postcss-single "^5.0.1" + ember-cli-babel "^7.26.11" + merge "^2.1.1" + ember-cli-preprocess-registry@^3.3.0: version "3.3.0" resolved "https://registry.yarnpkg.com/ember-cli-preprocess-registry/-/ember-cli-preprocess-registry-3.3.0.tgz#685837a314fbe57224bd54b189f4b9c23907a2de" @@ -7319,6 +8396,22 @@ ember-cli-typescript@^4.0.0, ember-cli-typescript@^4.1.0: stagehand "^1.0.0" walk-sync "^2.2.0" +ember-cli-typescript@^5.0.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/ember-cli-typescript/-/ember-cli-typescript-5.1.0.tgz#460eb848564e29d64f2b36b2a75bbe98172b72a4" + integrity sha512-wEZfJPkjqFEZAxOYkiXsDrJ1HY75e/6FoGhQFg8oNFJeGYpIS/3W0tgyl1aRkSEEN1NRlWocDubJ4aZikT+RTA== + dependencies: + ansi-to-html "^0.6.15" + broccoli-stew "^3.0.0" + debug "^4.0.0" + execa "^4.0.0" + fs-extra "^9.0.1" + resolve "^1.5.0" + rsvp "^4.8.1" + semver "^7.3.2" + stagehand "^1.0.0" + walk-sync "^2.2.0" + ember-cli-version-checker@^2.1.0, ember-cli-version-checker@^2.1.2: version "2.2.0" resolved "https://registry.yarnpkg.com/ember-cli-version-checker/-/ember-cli-version-checker-2.2.0.tgz#47771b731fe0962705e27c8199a9e3825709f3b3" @@ -7353,16 +8446,17 @@ ember-cli-version-checker@^5.0.1, ember-cli-version-checker@^5.1.1, ember-cli-ve semver "^7.3.4" silent-error "^1.1.1" -ember-cli-yadda@^0.6.0: - version "0.6.0" - resolved "https://registry.yarnpkg.com/ember-cli-yadda/-/ember-cli-yadda-0.6.0.tgz#9a01b55091c4a61f58109af890990381eba77a60" - integrity sha512-lD+u9RwoKDtrSdHIF9vIrNtPosZHNVvjF4s0uWzh2nkOxM+qu+cdeEAQ3RPQvNaEj6fnLgR2tcPjS/28eqnE9Q== +ember-cli-yadda@^0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/ember-cli-yadda/-/ember-cli-yadda-0.7.0.tgz#eff5583a886cd752343f74f485a4485ea6e37366" + integrity sha512-NdolJAdNEOIHdTR8pbv8I6LUvP6QHAHvIdFSQP1kzqEqdFurFT7E7Vl5XqG45puNDty5FSTptdZkc+zrP9C8HQ== dependencies: - broccoli-funnel "^3.0.3" + broccoli-funnel "^3.0.8" broccoli-persistent-filter "^3.1.2" - ember-auto-import "^1.6.0" - ember-cli-babel "^7.22.1" - ember-cli-htmlbars "^5.3.1" + ember-auto-import "^2.2.3" + ember-cli-babel "^7.26.6" + ember-cli-htmlbars "^5.7.1" + qunit "^2.16.0" yadda "*" ember-cli@~3.24.0: @@ -7719,6 +8813,16 @@ ember-intl@^5.5.1: mkdirp "^1.0.4" silent-error "^1.1.1" +ember-keyboard@^8.1.0: + version "8.2.0" + resolved "https://registry.yarnpkg.com/ember-keyboard/-/ember-keyboard-8.2.0.tgz#d11fa7f0443606b7c1850bbd8253274a00046e11" + integrity sha512-h2kuS2irtIyvNbAMkGDlDTB4TPXwgmC6Nu9bIuGWoCjkGdgJbUg0VegfyRJ1TlxbIHlAelbqVpE8UhfgY5wEag== + dependencies: + "@embroider/addon-shim" "^1.5.0" + ember-destroyable-polyfill "^2.0.3" + ember-modifier "^2.1.2 || ^3.1.0 || ^4.0.0" + ember-modifier-manager-polyfill "^1.2.0" + ember-load-initializers@^2.1.2: version "2.1.2" resolved "https://registry.yarnpkg.com/ember-load-initializers/-/ember-load-initializers-2.1.2.tgz#8a47a656c1f64f9b10cecdb4e22a9d52ad9c7efa" @@ -7777,6 +8881,17 @@ ember-modifier@^2.1.0, ember-modifier@^2.1.1: ember-destroyable-polyfill "^2.0.2" ember-modifier-manager-polyfill "^1.2.0" +"ember-modifier@^2.1.2 || ^3.1.0 || ^4.0.0": + version "3.2.7" + resolved "https://registry.yarnpkg.com/ember-modifier/-/ember-modifier-3.2.7.tgz#f2d35b7c867cbfc549e1acd8d8903c5ecd02ea4b" + integrity sha512-ezcPQhH8jUfcJQbbHji4/ZG/h0yyj1jRDknfYue/ypQS8fM8LrGcCMo0rjDZLzL1Vd11InjNs3BD7BdxFlzGoA== + dependencies: + ember-cli-babel "^7.26.6" + ember-cli-normalize-entity-name "^1.0.0" + ember-cli-string-utils "^1.1.0" + ember-cli-typescript "^5.0.0" + ember-compatibility-helpers "^1.2.5" + ember-named-blocks-polyfill@^0.2.3: version "0.2.4" resolved "https://registry.yarnpkg.com/ember-named-blocks-polyfill/-/ember-named-blocks-polyfill-0.2.4.tgz#f5f30711ee89244927b55aae7fa9630edaadc974" @@ -7785,6 +8900,14 @@ ember-named-blocks-polyfill@^0.2.3: ember-cli-babel "^7.19.0" ember-cli-version-checker "^5.1.1" +ember-named-blocks-polyfill@^0.2.5: + version "0.2.5" + resolved "https://registry.yarnpkg.com/ember-named-blocks-polyfill/-/ember-named-blocks-polyfill-0.2.5.tgz#d5841406277026a221f479c815cfbac6cdcaeecb" + integrity sha512-OVMxzkfqJrEvmiky7gFzmuTaImCGm7DOudHWTdMBPO7E+dQSunrcRsJMgO9ZZ56suqBIz/yXbEURrmGS+avHxA== + dependencies: + ember-cli-babel "^7.19.0" + ember-cli-version-checker "^5.1.1" + ember-native-dom-helpers@^0.7.0: version "0.7.0" resolved "https://registry.yarnpkg.com/ember-native-dom-helpers/-/ember-native-dom-helpers-0.7.0.tgz#98a87c11a391cec5c12382a4857e59ea2fb4b00a" @@ -8156,6 +9279,14 @@ enhanced-resolve@^4.0.0, enhanced-resolve@^4.5.0: memory-fs "^0.5.0" tapable "^1.0.0" +enhanced-resolve@^5.10.0: + version "5.10.0" + resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.10.0.tgz#0dc579c3bb2a1032e357ac45b8f3a6f3ad4fb1e6" + integrity sha512-T0yTFjdpldGY8PmuXXR0PyQ1ufZpEGiHVrp7zHKB7jdR4qlmZHhONVM5AQOAWXuF/w3dnHbEQVrNptJgt7F+cQ== + dependencies: + graceful-fs "^4.2.4" + tapable "^2.2.0" + enquirer@^2.3.5, enquirer@^2.3.6: version "2.3.6" resolved "https://registry.yarnpkg.com/enquirer/-/enquirer-2.3.6.tgz#2a7fe5dd634a1e4125a975ec994ff5456dc3734d" @@ -8271,6 +9402,11 @@ es-get-iterator@^1.1.1: is-string "^1.0.5" isarray "^2.0.5" +es-module-lexer@^0.9.0: + version "0.9.3" + resolved "https://registry.yarnpkg.com/es-module-lexer/-/es-module-lexer-0.9.3.tgz#6f13db00cc38417137daf74366f535c8eb438f19" + integrity sha512-1HQ2M2sPtxwnvOvT1ZClHyQDiggdNjURWpY2we6aMKCQiUVxTmVs2UYPLIrD84sS+kMdUwfBSylbJPwNnBrnHQ== + es-to-primitive@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.1.tgz#e55cd4c9cdc188bcefb03b366c736323fc5c898a" @@ -8358,6 +9494,14 @@ eslint-plugin-prettier@^3.3.1: dependencies: prettier-linter-helpers "^1.0.0" +eslint-scope@5.1.1, eslint-scope@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c" + integrity sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw== + dependencies: + esrecurse "^4.3.0" + estraverse "^4.1.1" + eslint-scope@^4.0.3: version "4.0.3" resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-4.0.3.tgz#ca03833310f6889a3264781aa82e63eb9cfe7848" @@ -8366,14 +9510,6 @@ eslint-scope@^4.0.3: esrecurse "^4.1.0" estraverse "^4.1.1" -eslint-scope@^5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c" - integrity sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw== - dependencies: - esrecurse "^4.3.0" - estraverse "^4.1.1" - eslint-utils@^2.0.0, eslint-utils@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-2.1.0.tgz#d2de5e03424e707dc10c74068ddedae708741b27" @@ -8517,7 +9653,7 @@ events-to-array@^1.0.1: resolved "https://registry.yarnpkg.com/events-to-array/-/events-to-array-1.1.2.tgz#2d41f563e1fe400ed4962fe1a4d5c6a7539df7f6" integrity sha1-LUH1Y+H+QA7Uli/hpNXGp1Od9/Y= -events@^3.0.0: +events@^3.0.0, events@^3.2.0: version "3.3.0" resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400" integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q== @@ -8772,6 +9908,17 @@ fast-glob@^3.0.3, fast-glob@^3.1.1, fast-glob@^3.2.5: micromatch "^4.0.2" picomatch "^2.2.1" +fast-glob@^3.2.11: + version "3.2.11" + resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.11.tgz#a1172ad95ceb8a16e20caa5c5e56480e5129c1d9" + integrity sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew== + dependencies: + "@nodelib/fs.stat" "^2.0.2" + "@nodelib/fs.walk" "^1.2.3" + glob-parent "^5.1.2" + merge2 "^1.3.0" + micromatch "^4.0.4" + fast-json-stable-stringify@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" @@ -9142,6 +10289,11 @@ forwarded@~0.1.2: resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.1.2.tgz#98c23dab1175657b8c0573e8ceccd91b0ff18c84" integrity sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ= +fraction.js@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/fraction.js/-/fraction.js-4.2.0.tgz#448e5109a313a3527f5a3ab2119ec4cf0e0e2950" + integrity sha512-MhLuK+2gUcnZe8ZHlaaINnQLl0xRIGRfcGk2yl8xoQAfHrSsL3rYu6FCmBdkdbhc9EPlwyGHewaRsvwRMJtAlA== + fragment-cache@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/fragment-cache/-/fragment-cache-0.2.1.tgz#4290fad27f13e89be7f33799c6bc5a0abfff0d19" @@ -9172,6 +10324,15 @@ fs-extra@^0.24.0: path-is-absolute "^1.0.0" rimraf "^2.2.8" +fs-extra@^10.0.0: + version "10.1.0" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-10.1.0.tgz#02873cfbc4084dde127eaa5f9905eef2325d1abf" + integrity sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ== + dependencies: + graceful-fs "^4.2.0" + jsonfile "^6.0.1" + universalify "^2.0.0" + fs-extra@^4.0.2, fs-extra@^4.0.3: version "4.0.3" resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-4.0.3.tgz#0d852122e5bc5beb453fb028e9c0c9bf36340c94" @@ -9305,7 +10466,7 @@ fsevents@^1.2.7: bindings "^1.5.0" nan "^2.12.1" -fsevents@~2.3.1: +fsevents@~2.3.1, fsevents@~2.3.2: version "2.3.2" resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== @@ -9451,18 +10612,30 @@ glob-parent@^3.1.0: is-glob "^3.1.0" path-dirname "^1.0.0" -glob-parent@^5.1.0, glob-parent@^5.1.2, glob-parent@~5.1.0: +glob-parent@^5.1.0, glob-parent@^5.1.2, glob-parent@~5.1.0, glob-parent@~5.1.2: version "5.1.2" resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== dependencies: is-glob "^4.0.1" +glob-parent@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-6.0.2.tgz#6d237d99083950c79290f24c7642a3de9a28f9e3" + integrity sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A== + dependencies: + is-glob "^4.0.3" + glob-to-regexp@^0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.3.0.tgz#8c5a1494d2066c570cc3bfe4496175acc4d502ab" integrity sha1-jFoUlNIGbFcMw7/kSWF1rMTVAqs= +glob-to-regexp@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz#c75297087c851b9a578bd217dd59a92f59fe546e" + integrity sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw== + glob@^5.0.10: version "5.0.15" resolved "https://registry.yarnpkg.com/glob/-/glob-5.0.15.tgz#1bc936b9e02f4a603fcc222ecf7633d30b8b93b1" @@ -9578,6 +10751,11 @@ graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.3 resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.6.tgz#ff040b2b0853b23c3d31027523706f1885d76bee" integrity sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ== +graceful-fs@^4.2.4, graceful-fs@^4.2.9: + version "4.2.10" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.10.tgz#147d3a006da4ca3ce14728c7aefc287c367d7a6c" + integrity sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA== + "graceful-readlink@>= 1.0.0": version "1.0.1" resolved "https://registry.yarnpkg.com/graceful-readlink/-/graceful-readlink-1.0.1.tgz#4cafad76bc62f02fa039b2f94e9a3dd3a391a725" @@ -9979,6 +11157,11 @@ iconv-lite@0.4.24, iconv-lite@^0.4.24: dependencies: safer-buffer ">= 2.1.2 < 3" +icss-utils@^5.0.0, icss-utils@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/icss-utils/-/icss-utils-5.1.0.tgz#c6be6858abd013d768e98366ae47e25d5887b1ae" + integrity sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA== + ieee754@^1.1.13, ieee754@^1.1.4: version "1.2.1" resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" @@ -9999,6 +11182,11 @@ ignore@^5.1.1, ignore@^5.1.4: resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.1.8.tgz#f150a8b50a34289b33e22f5889abd4d8016f0e57" integrity sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw== +immutable@^4.0.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/immutable/-/immutable-4.1.0.tgz#f795787f0db780183307b9eb2091fcac1f6fafef" + integrity sha512-oNkuqVTA8jqG1Q6c+UglTOD1xhC1BtjKI7XkCXRkZHrN5m18/XsnUp8Q89GkQO/z+0WjonSvl0FLhDYftp46nQ== + import-fresh@^3.0.0, import-fresh@^3.2.1: version "3.3.0" resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b" @@ -10361,6 +11549,13 @@ is-glob@^4.0.0, is-glob@^4.0.1, is-glob@~4.0.1: dependencies: is-extglob "^2.1.1" +is-glob@^4.0.3: + version "4.0.3" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" + integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== + dependencies: + is-extglob "^2.1.1" + is-hexadecimal@^1.0.0: version "1.0.4" resolved "https://registry.yarnpkg.com/is-hexadecimal/-/is-hexadecimal-1.0.4.tgz#cc35c97588da4bd49a8eedd6bc4082d44dcb23a7" @@ -10668,6 +11863,15 @@ ivy-codemirror@^2.1.0: ember-cli-babel "^6.0.0" ember-cli-node-assets "^0.2.2" +jest-worker@^27.4.5: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-27.5.1.tgz#8d146f0900e8973b106b6f73cc1e9a8cb86f8db0" + integrity sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg== + dependencies: + "@types/node" "*" + merge-stream "^2.0.0" + supports-color "^8.0.0" + jquery@^3.4.1, jquery@^3.5.0: version "3.6.0" resolved "https://registry.yarnpkg.com/jquery/-/jquery-3.6.0.tgz#c72a09f15c1bdce142f49dbf1170bdf8adac2470" @@ -10765,7 +11969,7 @@ json-parse-better-errors@^1.0.1, json-parse-better-errors@^1.0.2: resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9" integrity sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw== -json-parse-even-better-errors@^2.3.0: +json-parse-even-better-errors@^2.3.0, json-parse-even-better-errors@^2.3.1: version "2.3.1" resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d" integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== @@ -10949,6 +12153,11 @@ license-checker@^25.0.1: spdx-satisfies "^4.0.0" treeify "^1.1.0" +lilconfig@^2.0.5, lilconfig@^2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-2.0.6.tgz#32a384558bd58af3d4c6e077dd1ad1d397bc69d4" + integrity sha512-9JROoBW7pobfsx+Sq2JsASvCo6Pfo6WWoUW79HuB1BCoBXD4PLWJPqDF6fNj67pqBYTbAHkE57M1kS/+L1neOg== + line-column@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/line-column/-/line-column-1.0.2.tgz#d25af2936b6f4849172b312e4792d1d987bc34a2" @@ -11032,6 +12241,11 @@ loader-runner@^2.4.0: resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-2.4.0.tgz#ed47066bfe534d7e84c4c7b9998c2a75607d9357" integrity sha512-Jsmr89RcXGIwivFY21FcRrisYZfvLMTWx5kOLc+JTxtpBOG6xML0vzbc6SEQG2FO9/4Fc3wW4LVcB5DmGflaRw== +loader-runner@^4.2.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-4.3.0.tgz#c1b4a163b99f614830353b16755e7149ac2314e1" + integrity sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg== + loader-utils@^1.2.3, loader-utils@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.4.0.tgz#c579b5e34cb34b1a74edc6c1fb36bfa371d5a613" @@ -11041,6 +12255,15 @@ loader-utils@^1.2.3, loader-utils@^1.4.0: emojis-list "^3.0.0" json5 "^1.0.1" +loader-utils@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-2.0.2.tgz#d6e3b4fb81870721ae4e0868ab11dd638368c129" + integrity sha512-TM57VeHptv569d/GKh6TAYdzKblwDNiumOdkFnejjD0XwTH87K90w3O7AiJRqdQoXygvi1VQTJTLGhJl7WqA7A== + dependencies: + big.js "^5.2.2" + emojis-list "^3.0.0" + json5 "^2.1.2" + loader.js@^4.7.0: version "4.7.0" resolved "https://registry.yarnpkg.com/loader.js/-/loader.js-4.7.0.tgz#a1a52902001c83631efde9688b8ab3799325ef1f" @@ -11681,6 +12904,11 @@ merge2@^1.2.3, merge2@^1.3.0: resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== +merge@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/merge/-/merge-2.1.1.tgz#59ef4bf7e0b3e879186436e8481c06a6c162ca98" + integrity sha512-jz+Cfrg9GWOZbQAnDQ4hlVnQky+341Yk5ru8bZSe6sIDTCIg8n9i/u7hSQGSVOF3C7lH6mGtqjkiT9G4wFLL0w== + methods@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" @@ -11773,6 +13001,14 @@ micromatch@^4.0.2: braces "^3.0.1" picomatch "^2.0.5" +micromatch@^4.0.4: + version "4.0.5" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.5.tgz#bc8999a7cbbf77cdc89f132f6e467051b49090c6" + integrity sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA== + dependencies: + braces "^3.0.2" + picomatch "^2.3.1" + miller-rabin@^4.0.0: version "4.0.1" resolved "https://registry.yarnpkg.com/miller-rabin/-/miller-rabin-4.0.1.tgz#f080351c865b0dc562a8462966daa53543c78a4d" @@ -11786,6 +13022,11 @@ mime-db@1.46.0, "mime-db@>= 1.43.0 < 2": resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.46.0.tgz#6267748a7f799594de3cbc8cde91def349661cee" integrity sha512-svXaP8UQRZ5K7or+ZmfNhg2xX3yKDMUzqadsSqi4NCH/KomcH75MAMYAGVlvXn4+b/xOPhS3I2uHKRUzvjY7BQ== +mime-db@1.52.0: + version "1.52.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" + integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== + mime-types@^2.1.12, mime-types@^2.1.18, mime-types@^2.1.26, mime-types@~2.1.19, mime-types@~2.1.24: version "2.1.29" resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.29.tgz#1d4ab77da64b91f5f72489df29236563754bb1b2" @@ -11793,6 +13034,13 @@ mime-types@^2.1.12, mime-types@^2.1.18, mime-types@^2.1.26, mime-types@~2.1.19, dependencies: mime-db "1.46.0" +mime-types@^2.1.27: + version "2.1.35" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" + integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== + dependencies: + mime-db "1.52.0" + mime@1.6.0: version "1.6.0" resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" @@ -11808,6 +13056,13 @@ mimic-fn@^2.1.0: resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== +mini-css-extract-plugin@^2.5.2: + version "2.6.1" + resolved "https://registry.yarnpkg.com/mini-css-extract-plugin/-/mini-css-extract-plugin-2.6.1.tgz#9a1251d15f2035c342d99a468ab9da7a0451b71e" + integrity sha512-wd+SD57/K6DiV7jIR34P+s3uckTRuQvx0tKPcvjFlrEylk6P4mQ2KSWk1hblj1Kxaqok7LogKOieygXqBczNlg== + dependencies: + schema-utils "^4.0.0" + minimalistic-assert@^1.0.0, minimalistic-assert@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7" @@ -11825,6 +13080,11 @@ minimalistic-crypto-utils@^1.0.1: dependencies: brace-expansion "^1.1.7" +minimist@>=1.2.5, minimist@^1.2.6: + version "1.2.6" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.6.tgz#8637a5b759ea0d6e98702cfb3a9283323c93af44" + integrity sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q== + minimist@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.2.1.tgz#827ba4e7593464e7c221e8c5bed930904ee2c455" @@ -11835,11 +13095,6 @@ minimist@^1.1.1, minimist@^1.2.0, minimist@^1.2.5, minimist@~1.2.5: resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== -minimist@^1.2.6: - version "1.2.6" - resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.6.tgz#8637a5b759ea0d6e98702cfb3a9283323c93af44" - integrity sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q== - minipass@^2.2.0: version "2.9.0" resolved "https://registry.yarnpkg.com/minipass/-/minipass-2.9.0.tgz#e713762e7d3e32fed803115cf93e04bca9fcc9a6" @@ -11875,7 +13130,7 @@ mixin-deep@^1.2.0: mkdirp@^0.3.5: version "0.3.5" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.3.5.tgz#de3e5f8961c88c787ee1368df849ac4413eca8d7" - integrity sha1-3j5fiWHIjHh+4TaN+EmsRBPsqNc= + integrity sha512-8OCq0De/h9ZxseqzCH8Kw/Filf5pF/vMI6+BH7Lu0jXz2pqYCjTAQRolSxRIi+Ax+oCCjlxoJMP0YQ4XlrQNHg== mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@^0.5.3, mkdirp@^0.5.5: version "0.5.5" @@ -11891,7 +13146,7 @@ mkdirp@^0.5.6: dependencies: minimist "^1.2.6" -mkdirp@^1.0.4: +mkdirp@^1.0.3, mkdirp@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== @@ -11981,6 +13236,11 @@ nanoassert@^1.1.0: resolved "https://registry.yarnpkg.com/nanoassert/-/nanoassert-1.1.0.tgz#4f3152e09540fde28c76f44b19bbcd1d5a42478d" integrity sha1-TzFS4JVA/eKMdvRLGbvNHVpCR40= +nanoid@^3.3.4: + version "3.3.4" + resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.4.tgz#730b67e3cd09e2deacf03c027c81c9d9dbc5e8ab" + integrity sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw== + nanomatch@^1.2.9: version "1.2.13" resolved "https://registry.yarnpkg.com/nanomatch/-/nanomatch-1.2.13.tgz#b87a8aa4fc0de8fe6be88895b38983ff265bd119" @@ -12008,7 +13268,7 @@ negotiator@0.6.2: resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.2.tgz#feacf7ccf525a77ae9634436a64883ffeca346fb" integrity sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw== -neo-async@^2.5.0, neo-async@^2.6.0, neo-async@^2.6.1: +neo-async@^2.5.0, neo-async@^2.6.0, neo-async@^2.6.1, neo-async@^2.6.2: version "2.6.2" resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f" integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw== @@ -12179,6 +13439,11 @@ normalize-path@^3.0.0, normalize-path@~3.0.0: resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== +normalize-range@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/normalize-range/-/normalize-range-0.1.2.tgz#2d10c06bdfd312ea9777695a4d28439456b75942" + integrity sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA== + npm-git-info@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/npm-git-info/-/npm-git-info-1.0.3.tgz#a933c42ec321e80d3646e0d6e844afe94630e1d5" @@ -12277,7 +13542,7 @@ object-assign@4.1.1, object-assign@^4, object-assign@^4.1.0, object-assign@^4.1. object-assign@^2.0.0: version "2.1.1" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-2.1.1.tgz#43c36e5d569ff8e4816c4efa8be02d26967c18aa" - integrity sha1-Q8NuXVaf+OSBbE76i+AtJpZ8GKo= + integrity sha512-CdsOUYIh5wIiozhJ3rLQgmUTgcyzFwZZrqhkKhODMoGtPKM+wt0h0CNIoauJWMsS9822EdzPsF/6mb4nLvPN5g== object-copy@^0.1.0: version "0.1.0" @@ -12293,6 +13558,11 @@ object-hash@^1.3.1: resolved "https://registry.yarnpkg.com/object-hash/-/object-hash-1.3.1.tgz#fde452098a951cb145f039bb7d455449ddc126df" integrity sha512-OSuu/pU4ENM9kmREg0BdNrUDIl1heYa4mBZacJc+vVWz4GtAwu7jO8s4AIt2aGRUTqxykpWzI3Oqnsm13tTMDA== +object-hash@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/object-hash/-/object-hash-3.0.0.tgz#73f97f753e7baffc0e2cc9d6e079079744ac82e9" + integrity sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw== + object-inspect@^1.11.0: version "1.11.0" resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.11.0.tgz#9dceb146cedd4148a0d9e51ab88d34cf509922b1" @@ -12646,7 +13916,7 @@ parse-static-imports@^1.1.0: resolved "https://registry.yarnpkg.com/parse-static-imports/-/parse-static-imports-1.1.0.tgz#ae2f18f18da1a993080ae406a5219455c0bbad5d" integrity sha512-HlxrZcISCblEV0lzXmAHheH/8qEkKgmqkdxyHTPbSqsTUV8GzqmN1L+SSti+VbNPfbBO3bYLPHDiUs2avbAdbA== -parse5@6.0.1: +parse5@6.0.1, parse5@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/parse5/-/parse5-6.0.1.tgz#e1a1c085c569b3dc08321184f19a39cc27f7c30b" integrity sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw== @@ -12773,11 +14043,21 @@ picomatch@^2.0.4, picomatch@^2.0.5, picomatch@^2.2.1: resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.2.2.tgz#21f333e9b6b8eaff02468f5146ea406d345f4dad" integrity sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg== +picomatch@^2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" + integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== + pidtree@^0.3.0: version "0.3.1" resolved "https://registry.yarnpkg.com/pidtree/-/pidtree-0.3.1.tgz#ef09ac2cc0533df1f3250ccf2c4d366b0d12114a" integrity sha512-qQbW94hLHEqCg7nhby4yRC7G2+jYHY4Rguc2bjw7Uug4GIJuu1tvf2uHaZv5Q8zdt+WKJ6qK1FOI6amaWUo5FA== +pify@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c" + integrity sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog== + pify@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/pify/-/pify-3.0.0.tgz#e5a4acd2c101fdf3d9a4d07f0dbc4db49dd28176" @@ -12856,6 +14136,96 @@ posix-character-classes@^0.1.0: resolved "https://registry.yarnpkg.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab" integrity sha1-AerA/jta9xoqbAL+q7jB/vfgDqs= +postcss-import@^14.1.0: + version "14.1.0" + resolved "https://registry.yarnpkg.com/postcss-import/-/postcss-import-14.1.0.tgz#a7333ffe32f0b8795303ee9e40215dac922781f0" + integrity sha512-flwI+Vgm4SElObFVPpTIT7SU7R3qk2L7PyduMcokiaVKuWv9d/U+Gm/QAd8NDLuykTWTkcrjOeD2Pp1rMeBTGw== + dependencies: + postcss-value-parser "^4.0.0" + read-cache "^1.0.0" + resolve "^1.1.7" + +postcss-js@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/postcss-js/-/postcss-js-4.0.0.tgz#31db79889531b80dc7bc9b0ad283e418dce0ac00" + integrity sha512-77QESFBwgX4irogGVPgQ5s07vLvFqWr228qZY+w6lW599cRlK/HmnlivnnVUxkjHnCu4J16PDMHcH+e+2HbvTQ== + dependencies: + camelcase-css "^2.0.1" + +postcss-load-config@^3.1.4: + version "3.1.4" + resolved "https://registry.yarnpkg.com/postcss-load-config/-/postcss-load-config-3.1.4.tgz#1ab2571faf84bb078877e1d07905eabe9ebda855" + integrity sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg== + dependencies: + lilconfig "^2.0.5" + yaml "^1.10.2" + +postcss-modules-extract-imports@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz#cda1f047c0ae80c97dbe28c3e76a43b88025741d" + integrity sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw== + +postcss-modules-local-by-default@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.0.tgz#ebbb54fae1598eecfdf691a02b3ff3b390a5a51c" + integrity sha512-sT7ihtmGSF9yhm6ggikHdV0hlziDTX7oFoXtuVWeDd3hHObNkcHRo9V3yg7vCAY7cONyxJC/XXCmmiHHcvX7bQ== + dependencies: + icss-utils "^5.0.0" + postcss-selector-parser "^6.0.2" + postcss-value-parser "^4.1.0" + +postcss-modules-scope@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/postcss-modules-scope/-/postcss-modules-scope-3.0.0.tgz#9ef3151456d3bbfa120ca44898dfca6f2fa01f06" + integrity sha512-hncihwFA2yPath8oZ15PZqvWGkWf+XUfQgUGamS4LqoP1anQLOsOJw0vr7J7IwLpoY9fatA2qiGUGmuZL0Iqlg== + dependencies: + postcss-selector-parser "^6.0.4" + +postcss-modules-values@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz#d7c5e7e68c3bb3c9b27cbf48ca0bb3ffb4602c9c" + integrity sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ== + dependencies: + icss-utils "^5.0.0" + +postcss-nested@5.0.6: + version "5.0.6" + resolved "https://registry.yarnpkg.com/postcss-nested/-/postcss-nested-5.0.6.tgz#466343f7fc8d3d46af3e7dba3fcd47d052a945bc" + integrity sha512-rKqm2Fk0KbA8Vt3AdGN0FB9OBOMDVajMG6ZCf/GoHgdxUJ4sBFp0A/uMIRm+MJUdo33YXEtjqIz8u7DAp8B7DA== + dependencies: + postcss-selector-parser "^6.0.6" + +postcss-selector-parser@^6.0.10, postcss-selector-parser@^6.0.2, postcss-selector-parser@^6.0.4, postcss-selector-parser@^6.0.6: + version "6.0.10" + resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz#79b61e2c0d1bfc2602d549e11d0876256f8df88d" + integrity sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w== + dependencies: + cssesc "^3.0.0" + util-deprecate "^1.0.2" + +postcss-value-parser@^4.0.0, postcss-value-parser@^4.1.0, postcss-value-parser@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz#723c09920836ba6d3e5af019f92bc0971c02e514" + integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ== + +postcss@^8.1.4, postcss@^8.4.14: + version "8.4.16" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.16.tgz#33a1d675fac39941f5f445db0de4db2b6e01d43c" + integrity sha512-ipHE1XBvKzm5xI7hiHCZJCSugxvsdq2mPnsq5+UF+VHCjiBvtDrlxJfMBToWaP9D5XlgNmcFGqoHmUn0EYEaRQ== + dependencies: + nanoid "^3.3.4" + picocolors "^1.0.0" + source-map-js "^1.0.2" + +postcss@^8.2.15: + version "8.4.17" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.17.tgz#f87863ec7cd353f81f7ab2dec5d67d861bbb1be5" + integrity sha512-UNxNOLQydcOFi41yHNMcKRZ39NeXlr8AxGuZJsdub8vIb12fHzcq37DTU/QtbI6WLxNg2gF9Z+8qtRwTj1UI1Q== + dependencies: + nanoid "^3.3.4" + picocolors "^1.0.0" + source-map-js "^1.0.2" + prelude-ls@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" @@ -13061,6 +14431,11 @@ queue-microtask@^1.2.2: resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.2.tgz#abf64491e6ecf0f38a6502403d4cda04f372dfd3" integrity sha512-dB15eXv3p2jDlbOiNLyMabYg1/sXvppd8DP2J3EOCQ0AkuSXCW2tP7mnVouVLJKgUMY6yP0kcQDVpLCN13h4Xg== +quick-lru@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-5.1.1.tgz#366493e6b3e42a3a6885e2e99d18f80fb7a8c932" + integrity sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA== + quick-temp@^0.1.2, quick-temp@^0.1.3, quick-temp@^0.1.5, quick-temp@^0.1.8: version "0.1.8" resolved "https://registry.yarnpkg.com/quick-temp/-/quick-temp-0.1.8.tgz#bab02a242ab8fb0dd758a3c9776b32f9a5d94408" @@ -13080,7 +14455,7 @@ qunit-dom@^1.6.0: ember-cli-babel "^7.23.0" ember-cli-version-checker "^5.1.1" -qunit@^2.13.0: +qunit@^2.13.0, qunit@^2.16.0: version "2.19.1" resolved "https://registry.yarnpkg.com/qunit/-/qunit-2.19.1.tgz#eb1afd188da9e47f07c13aa70461a1d9c4505490" integrity sha512-gSGuw0vErE/rNjnlBW/JmE7NNubBlGrDPQvsug32ejYhcVFuZec9yoU0+C30+UgeCGwq6Ap89K65dMGo+kDGZQ== @@ -13137,6 +14512,13 @@ react-is@^17.0.1: resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.1.tgz#5b3531bd76a645a4c9fb6e693ed36419e3301339" integrity sha512-NAnt2iGDXohE5LI7uBnLnqvLQMtzhkiAOLXTmv+qnF9Ky7xAPcX8Up/xWIhxvLVGJvuLiNc4xQLtuqDRzb4fSA== +read-cache@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/read-cache/-/read-cache-1.0.0.tgz#e664ef31161166c9751cdbe8dbcf86b5fb58f774" + integrity sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA== + dependencies: + pify "^2.3.0" + read-installed@~4.0.3: version "4.0.3" resolved "https://registry.yarnpkg.com/read-installed/-/read-installed-4.0.3.tgz#ff9b8b67f187d1e4c29b9feb31f6b223acd19067" @@ -13228,6 +14610,13 @@ readdirp@~3.5.0: dependencies: picomatch "^2.2.1" +readdirp@~3.6.0: + version "3.6.0" + resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7" + integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA== + dependencies: + picomatch "^2.2.1" + recast@^0.18.1: version "0.18.10" resolved "https://registry.yarnpkg.com/recast/-/recast-0.18.10.tgz#605ebbe621511eb89b6356a7e224bff66ed91478" @@ -13314,6 +14703,13 @@ regenerator-transform@^0.14.2: dependencies: "@babel/runtime" "^7.8.4" +regenerator-transform@^0.15.0: + version "0.15.0" + resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.15.0.tgz#cbd9ead5d77fae1a48d957cf889ad0586adb6537" + integrity sha512-LsrGtPmbYg19bcPHwdtmXwbW+TqNvtY4riE3P83foeHRroMbH6/2ddFBfab3t7kbzc7v7p4wbkIecHImqt0QNg== + dependencies: + "@babel/runtime" "^7.8.4" + regex-not@^1.0.0, regex-not@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/regex-not/-/regex-not-1.0.2.tgz#1f4ece27e00b0b65e0247a6810e6a85d83a5752c" @@ -13368,6 +14764,18 @@ regexpu-core@^5.0.1: unicode-match-property-ecmascript "^2.0.0" unicode-match-property-value-ecmascript "^2.0.0" +regexpu-core@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-5.1.0.tgz#2f8504c3fd0ebe11215783a41541e21c79942c6d" + integrity sha512-bb6hk+xWd2PEOkj5It46A16zFMs2mv86Iwpdu94la4S3sJ7C973h2dHpYKwIBGaWSO7cIRJ+UX0IeMaWcO4qwA== + dependencies: + regenerate "^1.4.2" + regenerate-unicode-properties "^10.0.1" + regjsgen "^0.6.0" + regjsparser "^0.8.2" + unicode-match-property-ecmascript "^2.0.0" + unicode-match-property-value-ecmascript "^2.0.0" + regjsgen@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/regjsgen/-/regjsgen-0.2.0.tgz#6c016adeac554f75823fe37ac05b92d5a4edb1f7" @@ -13648,7 +15056,7 @@ resolve-package-path@^3.1.0: path-root "^0.1.1" resolve "^1.17.0" -resolve-package-path@^4.0.1: +resolve-package-path@^4.0.1, resolve-package-path@^4.0.3: version "4.0.3" resolved "https://registry.yarnpkg.com/resolve-package-path/-/resolve-package-path-4.0.3.tgz#31dab6897236ea6613c72b83658d88898a9040aa" integrity sha512-SRpNAPW4kewOaNUt8VPqhJ0UMxawMwzJD8V7m1cJfdSTK9ieZwS6K7Dabsm4bmLFM96Z5Y/UznrpG5kt1im8yA== @@ -13676,7 +15084,7 @@ resolve@^1.1.7, resolve@^1.10.0, resolve@^1.10.1, resolve@^1.11.1, resolve@^1.12 is-core-module "^2.2.0" path-parse "^1.0.6" -resolve@^1.19.0: +resolve@^1.19.0, resolve@^1.22.1: version "1.22.1" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.1.tgz#27cb2ebb53f91abb49470a928bba7558066ac177" integrity sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw== @@ -13866,6 +15274,24 @@ sass@^1.28.0: dependencies: chokidar ">=2.0.0 <4.0.0" +sass@^1.43.4: + version "1.54.6" + resolved "https://registry.yarnpkg.com/sass/-/sass-1.54.6.tgz#5a12c268db26555c335028e355d6b7b1a5b9b4c8" + integrity sha512-DUqJjR2WxXBcZjRSZX5gCVyU+9fuC2qDfFzoKX9rV4rCOcec5mPtEafTcfsyL3YJuLONjWylBne+uXVh5rrmFw== + dependencies: + chokidar ">=3.0.0 <4.0.0" + immutable "^4.0.0" + source-map-js ">=0.6.2 <2.0.0" + +sass@^1.49.7: + version "1.54.5" + resolved "https://registry.yarnpkg.com/sass/-/sass-1.54.5.tgz#93708f5560784f6ff2eab8542ade021a4a947b3a" + integrity sha512-p7DTOzxkUPa/63FU0R3KApkRHwcVZYC0PLnLm5iyZACyp15qSi32x7zVUhRdABAATmkALqgGrjCJAcWvobmhHw== + dependencies: + chokidar ">=3.0.0 <4.0.0" + immutable "^4.0.0" + source-map-js ">=0.6.2 <2.0.0" + saxes@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/saxes/-/saxes-5.0.1.tgz#eebab953fa3b7608dbe94e5dadb15c888fa6696d" @@ -13891,6 +15317,25 @@ schema-utils@^2.6.5: ajv "^6.12.4" ajv-keywords "^3.5.2" +schema-utils@^3.0.0, schema-utils@^3.1.0, schema-utils@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-3.1.1.tgz#bc74c4b6b6995c1d88f76a8b77bea7219e0c8281" + integrity sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw== + dependencies: + "@types/json-schema" "^7.0.8" + ajv "^6.12.5" + ajv-keywords "^3.5.2" + +schema-utils@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-4.0.0.tgz#60331e9e3ae78ec5d16353c467c34b3a0a1d3df7" + integrity sha512-1edyXKgh6XnJsJSQ8mKWXnN/BVaIbFMLpouRUrXgVq7WYne5kw3MW7UPhO44uRXQSIpTSXoJbmrR2X0w9kUTyg== + dependencies: + "@types/json-schema" "^7.0.9" + ajv "^8.8.0" + ajv-formats "^2.1.1" + ajv-keywords "^5.0.0" + select@^1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/select/-/select-1.1.2.tgz#0e7350acdec80b1108528786ec1d4418d11b396d" @@ -13961,6 +15406,13 @@ serialize-javascript@^4.0.0: dependencies: randombytes "^2.1.0" +serialize-javascript@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-6.0.0.tgz#efae5d88f45d7924141da8b5c3a7a7e663fefeb8" + integrity sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag== + dependencies: + randombytes "^2.1.0" + serve-static@1.14.1: version "1.14.1" resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.14.1.tgz#666e636dc4f010f7ef29970a88a674320898b2f9" @@ -14204,7 +15656,7 @@ source-list-map@^2.0.0: resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-2.0.1.tgz#3993bd873bfc48479cca9ea3a547835c7c154b34" integrity sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw== -source-map-js@^1.0.1: +"source-map-js@>=0.6.2 <2.0.0", source-map-js@^1.0.1, source-map-js@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c" integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw== @@ -14220,6 +15672,14 @@ source-map-resolve@^0.5.0: source-map-url "^0.4.0" urix "^0.1.0" +source-map-resolve@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/source-map-resolve/-/source-map-resolve-0.6.0.tgz#3d9df87e236b53f16d01e58150fc7711138e5ed2" + integrity sha512-KXBr9d/fO/bWo97NXsPIAW1bFSBOuCnjbNTBMO7N59hsv5i9yzRDfcYwwt0l04+VqnKC+EwzvJZIP/qkuMgR/w== + dependencies: + atob "^2.1.2" + decode-uri-component "^0.2.0" + source-map-support@^0.4.15: version "0.4.18" resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.4.18.tgz#0286a6de8be42641338594e97ccea75f0a2c585f" @@ -14277,6 +15737,11 @@ source-map@~0.1.x: dependencies: amdefine ">=0.0.4" +source-map@~0.7.3: + version "0.7.4" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.4.tgz#a9bbe705c9d8846f4e08ff6765acf0f1b0898656" + integrity sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA== + sourcemap-codec@^1.4.4: version "1.4.8" resolved "https://registry.yarnpkg.com/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz#ea804bd94857402e6992d05a38ef1ae35a9ab4c4" @@ -14659,6 +16124,14 @@ structured-source@^3.0.2: dependencies: boundary "^1.0.1" +style-loader@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/style-loader/-/style-loader-2.0.0.tgz#9669602fd4690740eaaec137799a03addbbc393c" + integrity sha512-Z0gYUJmzZ6ZdRUqpg1r8GsaFKypE+3xAzuFeMuoHgjc9KZv3wMyCRjQIWEbhoFSq7+7yoHXySDJyyWQaPajeiQ== + dependencies: + loader-utils "^2.0.0" + schema-utils "^3.0.0" + styled_string@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/styled_string/-/styled_string-0.0.1.tgz#d22782bd81295459bc4f1df18c4bad8e94dd124a" @@ -14690,6 +16163,13 @@ supports-color@^7.1.0: dependencies: has-flag "^4.0.0" +supports-color@^8.0.0: + version "8.1.1" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c" + integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== + dependencies: + has-flag "^4.0.0" + supports-preserve-symlinks-flag@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" @@ -14738,6 +16218,34 @@ table@^6.0.9: string-width "^4.2.3" strip-ansi "^6.0.1" +tailwindcss@^3.1.8: + version "3.1.8" + resolved "https://registry.yarnpkg.com/tailwindcss/-/tailwindcss-3.1.8.tgz#4f8520550d67a835d32f2f4021580f9fddb7b741" + integrity sha512-YSneUCZSFDYMwk+TGq8qYFdCA3yfBRdBlS7txSq0LUmzyeqRe3a8fBQzbz9M3WS/iFT4BNf/nmw9mEzrnSaC0g== + dependencies: + arg "^5.0.2" + chokidar "^3.5.3" + color-name "^1.1.4" + detective "^5.2.1" + didyoumean "^1.2.2" + dlv "^1.1.3" + fast-glob "^3.2.11" + glob-parent "^6.0.2" + is-glob "^4.0.3" + lilconfig "^2.0.6" + normalize-path "^3.0.0" + object-hash "^3.0.0" + picocolors "^1.0.0" + postcss "^8.4.14" + postcss-import "^14.1.0" + postcss-js "^4.0.0" + postcss-load-config "^3.1.4" + postcss-nested "5.0.6" + postcss-selector-parser "^6.0.10" + postcss-value-parser "^4.2.0" + quick-lru "^5.1.1" + resolve "^1.22.1" + tap-parser@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/tap-parser/-/tap-parser-7.0.0.tgz#54db35302fda2c2ccc21954ad3be22b2cba42721" @@ -14752,6 +16260,11 @@ tapable@^1.0.0, tapable@^1.1.3: resolved "https://registry.yarnpkg.com/tapable/-/tapable-1.1.3.tgz#a1fccc06b58db61fd7a45da2da44f5f3a3e67ba2" integrity sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA== +tapable@^2.1.1, tapable@^2.2.0: + version "2.2.1" + resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.2.1.tgz#1967a73ef4060a82f12ab96af86d52fdb76eeca0" + integrity sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ== + tape@^5.0.1: version "5.2.2" resolved "https://registry.yarnpkg.com/tape/-/tape-5.2.2.tgz#a98475ecf30aa0ed2a89c36439bb9438d24d2184" @@ -14798,6 +16311,17 @@ terser-webpack-plugin@^1.4.3: webpack-sources "^1.4.0" worker-farm "^1.7.0" +terser-webpack-plugin@^5.1.3: + version "5.3.6" + resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-5.3.6.tgz#5590aec31aa3c6f771ce1b1acca60639eab3195c" + integrity sha512-kfLFk+PoLUQIbLmB1+PZDMRSZS99Mp+/MHqDNmMA6tOItzRt+Npe3E+fsMs5mfcM0wCtrrdU387UnV+vnSffXQ== + dependencies: + "@jridgewell/trace-mapping" "^0.3.14" + jest-worker "^27.4.5" + schema-utils "^3.1.1" + serialize-javascript "^6.0.0" + terser "^5.14.1" + terser@^4.1.2: version "4.8.0" resolved "https://registry.yarnpkg.com/terser/-/terser-4.8.0.tgz#63056343d7c70bb29f3af665865a46fe03a0df17" @@ -14807,7 +16331,7 @@ terser@^4.1.2: source-map "~0.6.1" source-map-support "~0.5.12" -terser@^5.3.0: +terser@^5.14.1, terser@^5.3.0: version "5.15.0" resolved "https://registry.yarnpkg.com/terser/-/terser-5.15.0.tgz#e16967894eeba6e1091509ec83f0c60e179f2425" integrity sha512-L1BJiXVmheAQQy+as0oF3Pwtlo4s3Wi1X2zNZ2NxOB4wx9bdS9Vk67XQENLFdLYGCK/Z2di53mTj/hBafR+dTA== @@ -15492,9 +17016,9 @@ upath@^1.1.1: integrity sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg== update-browserslist-db@^1.0.5: - version "1.0.7" - resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.7.tgz#16279639cff1d0f800b14792de43d97df2d11b7d" - integrity sha512-iN/XYesmZ2RmmWAiI4Z5rq0YqSiv0brj9Ce9CfhNE4xIW2h+MFxcgkxIzZ+ShkFPUkjU3gQ+3oypadD3RAMtrg== + version "1.0.5" + resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.5.tgz#be06a5eedd62f107b7c19eb5bcefb194411abf38" + integrity sha512-dteFFpCyvuDdr9S/ff1ISkKt/9YZxKjI9WlRR99c180GaztJtRa/fn18FdxGVKVsnPY7/a/FDN68mcvUmP4U7Q== dependencies: escalade "^3.1.1" picocolors "^1.0.0" @@ -15714,6 +17238,16 @@ walk-sync@^2.0.0, walk-sync@^2.0.2, walk-sync@^2.1.0, walk-sync@^2.2.0: matcher-collection "^2.0.0" minimatch "^3.0.4" +walk-sync@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/walk-sync/-/walk-sync-3.0.0.tgz#67f882925021e20569a1edd560b8da31da8d171c" + integrity sha512-41TvKmDGVpm2iuH7o+DAOt06yyu/cSHpX3uzAwetzASvlNtVddgIjXIb2DfB/Wa20B1Jo86+1Dv1CraSU7hWdw== + dependencies: + "@types/minimatch" "^3.0.4" + ensure-posix-path "^1.1.0" + matcher-collection "^2.0.1" + minimatch "^3.0.4" + walker@~1.0.5: version "1.0.7" resolved "https://registry.yarnpkg.com/walker/-/walker-1.0.7.tgz#2f7f9b8fd10d677262b18a884e28d19618e028fb" @@ -15749,6 +17283,14 @@ watchpack@^1.7.4: chokidar "^3.4.1" watchpack-chokidar2 "^2.0.1" +watchpack@^2.4.0: + version "2.4.0" + resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-2.4.0.tgz#fa33032374962c78113f93c7f2fb4c54c9862a5d" + integrity sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg== + dependencies: + glob-to-regexp "^0.4.1" + graceful-fs "^4.1.2" + wayfarer@^7.0.1: version "7.0.1" resolved "https://registry.yarnpkg.com/wayfarer/-/wayfarer-7.0.1.tgz#17a64d351d49f9d3d6c508155867df7658184ce3" @@ -15786,6 +17328,11 @@ webpack-sources@^1.4.0, webpack-sources@^1.4.1: source-list-map "^2.0.0" source-map "~0.6.1" +webpack-sources@^3.2.3: + version "3.2.3" + resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-3.2.3.tgz#2d4daab8451fd4b240cc27055ff6a0c2ccea0cde" + integrity sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w== + webpack@^4.43.0: version "4.46.0" resolved "https://registry.yarnpkg.com/webpack/-/webpack-4.46.0.tgz#bf9b4404ea20a073605e0a011d188d77cb6ad542" @@ -15815,6 +17362,36 @@ webpack@^4.43.0: watchpack "^1.7.4" webpack-sources "^1.4.1" +webpack@^5.74.0: + version "5.74.0" + resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.74.0.tgz#02a5dac19a17e0bb47093f2be67c695102a55980" + integrity sha512-A2InDwnhhGN4LYctJj6M1JEaGL7Luj6LOmyBHjcI8529cm5p6VXiTIW2sn6ffvEAKmveLzvu4jrihwXtPojlAA== + dependencies: + "@types/eslint-scope" "^3.7.3" + "@types/estree" "^0.0.51" + "@webassemblyjs/ast" "1.11.1" + "@webassemblyjs/wasm-edit" "1.11.1" + "@webassemblyjs/wasm-parser" "1.11.1" + acorn "^8.7.1" + acorn-import-assertions "^1.7.6" + browserslist "^4.14.5" + chrome-trace-event "^1.0.2" + enhanced-resolve "^5.10.0" + es-module-lexer "^0.9.0" + eslint-scope "5.1.1" + events "^3.2.0" + glob-to-regexp "^0.4.1" + graceful-fs "^4.2.9" + json-parse-even-better-errors "^2.3.1" + loader-runner "^4.2.0" + mime-types "^2.1.27" + neo-async "^2.6.2" + schema-utils "^3.1.0" + tapable "^2.1.1" + terser-webpack-plugin "^5.1.3" + watchpack "^2.4.0" + webpack-sources "^3.2.3" + websocket-driver@>=0.5.1: version "0.7.4" resolved "https://registry.yarnpkg.com/websocket-driver/-/websocket-driver-0.7.4.tgz#89ad5295bbf64b480abcba31e4953aca706f5760" @@ -16051,7 +17628,7 @@ xmlhttprequest-ssl@^1.6.3: resolved "https://registry.yarnpkg.com/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.6.3.tgz#03b713873b01659dfa2c1c5d056065b27ddc2de6" integrity sha512-3XfeQE/wNkvrIktn2Kf0869fC0BN6UpydVasGIeSm2B1Llihf7/0UfZM+eCkOw3P7bP4+qPgqhm7ZoxuJtFU0Q== -xtend@^4.0.0, xtend@^4.0.1, xtend@~4.0.1: +xtend@^4.0.0, xtend@^4.0.1, xtend@^4.0.2, xtend@~4.0.1: version "4.0.2" resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== @@ -16089,7 +17666,7 @@ yam@^1.0.0: fs-extra "^4.0.2" lodash.merge "^4.6.0" -yaml@^1.10.0, yaml@^1.9.2: +yaml@^1.10.0, yaml@^1.10.2, yaml@^1.9.2: version "1.10.2" resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b" integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg== diff --git a/website/content/api-docs/catalog.mdx b/website/content/api-docs/catalog.mdx index 98a6a81aac..5ce5fb021e 100644 --- a/website/content/api-docs/catalog.mdx +++ b/website/content/api-docs/catalog.mdx @@ -270,7 +270,7 @@ The table below shows this endpoint's support for | Blocking Queries | Consistency Modes | Agent Caching | ACL Required | | ---------------- | ----------------- | ------------- | ------------ | -| `NO` | `none` | `none` | `none` | +| `NO` | `none` | `simple` | `none` | The corresponding CLI command is [`consul catalog datacenters`](/commands/catalog/datacenters). @@ -401,7 +401,7 @@ The table below shows this endpoint's support for | Blocking Queries | Consistency Modes | Agent Caching | ACL Required | | ---------------- | ----------------- | ------------- | -------------- | -| `YES` | `all` | `none` | `service:read` | +| `YES` | `all` | `simple` | `service:read` | The corresponding CLI command is [`consul catalog services`](/commands/catalog/services). diff --git a/website/content/api-docs/query.mdx b/website/content/api-docs/query.mdx index 521719eabc..5e10a3d2d8 100644 --- a/website/content/api-docs/query.mdx +++ b/website/content/api-docs/query.mdx @@ -207,7 +207,7 @@ The table below shows this endpoint's support for service instances in the local datacenter. This option cannot be used with `NearestN` or `Datacenters`. - - `PeerName` `(string: "")` - Specifies a [cluster peer](/docs/connect/cluster-peering) to use for + - `Peer` `(string: "")` - Specifies a [cluster peer](/docs/connect/cluster-peering) to use for failover. - `Datacenter` `(string: "")` - Specifies a WAN federated datacenter to forward the diff --git a/website/content/docs/agent/config/config-files.mdx b/website/content/docs/agent/config/config-files.mdx index be94d0bf53..430069628c 100644 --- a/website/content/docs/agent/config/config-files.mdx +++ b/website/content/docs/agent/config/config-files.mdx @@ -1063,6 +1063,7 @@ Valid time units are 'ns', 'us' (or 'µs'), 'ms', 's', 'm', 'h'." - `enabled` ((#connect_enabled)) (Defaults to `false`) Controls whether Connect features are enabled on this agent. Should be enabled on all servers in the cluster in order for Connect to function properly. + Will be set to `true` automatically if `auto_config.enabled` or `auto_encrypt.allow_tls` is `true`. - `enable_mesh_gateway_wan_federation` ((#connect_enable_mesh_gateway_wan_federation)) (Defaults to `false`) Controls whether cross-datacenter federation traffic between servers is funneled through mesh gateways. This was added in Consul 1.8.0. diff --git a/website/content/docs/agent/telemetry.mdx b/website/content/docs/agent/telemetry.mdx index 75ac638a03..e52176c05c 100644 --- a/website/content/docs/agent/telemetry.mdx +++ b/website/content/docs/agent/telemetry.mdx @@ -697,15 +697,15 @@ agent. The table below describes the additional metrics exported by the proxy. | Metric | Description | Unit | Type | | ------------------------------------- | ----------------------------------------------------------------------| ------ | ------- | -| `consul.peering.exported_services` | Counts the number of services exported to a peer cluster. | count | gauge | +| `consul.peering.exported_services` | Counts the number of services exported with [exported service configuration entries](/docs/connect/config-entries/exported-services) to a peer cluster. | count | gauge | | `consul.peering.healthy` | Tracks the health of a peering connection as reported by the server. If Consul detects errors while sending or receiving from a peer which do not recover within a reasonable time, this metric returns 0. Healthy connections return 1. | health | gauge | ### Labels Consul attaches the following labels to metric values. -| Label Name | Description | Possible values | -| ------------------------------------- | ---------------------------------------------------------------------- | ------------------------------------------ | -| `peer_name` | The name of the peering on the reporting cluster or leader. | Any defined peer name in the cluster | -| `peer_id` | The ID of a peer connected to the reporting cluster or leader. | Any UUID | +| Label Name | Description | Possible values | +| ------------------------------------- | -------------------------------------------------------------------------------- | ----------------------------------------- | +| `peer_name` | The name of the peering on the reporting cluster or leader. | Any defined peer name in the cluster | +| `peer_id` | The ID of a peer connected to the reporting cluster or leader. | Any UUID | | `partition` | Name of the partition that the peering is created in. | Any defined partition name in the cluster | diff --git a/website/content/docs/api-gateway/tech-specs.mdx b/website/content/docs/api-gateway/tech-specs.mdx index f99abd5495..d4ae15b200 100644 --- a/website/content/docs/api-gateway/tech-specs.mdx +++ b/website/content/docs/api-gateway/tech-specs.mdx @@ -48,7 +48,7 @@ The following table lists API Gateway limitations related to specific Consul fea | Consul Feature | Limitation | | -------------- | ---------- | -| [Admin partitions](/docs/enterprise/admin-partitions) | You can only deploy Consul API Gateway into the `default` admin partition. | +| [Admin partitions](/docs/enterprise/admin-partitions) | You can only deploy Consul API Gateway into the `default` admin partition and it can only route to other services within that partition, i.e. you cannot route to services in other admin partitions. | | Datacenter federation | If you are connecting multiple Consul datacenters to create a federated network, you can only deploy Consul API Gateway in the `primary` datacenter. | | Routing between datacenters | If you are connecting multiple Consul datacenters to create a federated network, API Gateway can only route traffic to Services in the local datacenter. However, API Gateway can route to Services in other Kubernetes clusters when they are in the same Consul datacenter. Refer to [Single Consul Datacenter in Multiple Kubernetes Clusters](/docs/k8s/deployment-configurations/single-dc-multi-k8s) for more details. | diff --git a/website/content/docs/architecture/index.mdx b/website/content/docs/architecture/index.mdx index d8ad4839af..e75137ca7b 100644 --- a/website/content/docs/architecture/index.mdx +++ b/website/content/docs/architecture/index.mdx @@ -5,83 +5,110 @@ description: >- Consul datacenters consist of clusters of server agents (control plane) and client agents deployed alongside service instances (dataplane). Learn how these components and their different communication methods make Consul possible. --- -# Consul Internals Overview - -Consul is a complex system that has many different moving parts. To help -users and developers of Consul form a mental model of how it works, this -page documents the system architecture. - --> Before describing the architecture, we recommend reading the -[glossary](/docs/install/glossary) of terms to help -clarify what is being discussed. - -The architecture concepts in this document can be used with the [Reference Architecture guide](https://learn.hashicorp.com/tutorials/consul/reference-architecture?in=consul/production-deploy#deployment-system-requirements) when deploying Consul in production. - -## 10,000 foot view - -From a 10,000 foot altitude the architecture of Consul looks like this: - -[![Consul Architecture](/img/consul-arch.png)](/img/consul-arch.png) - -Let's break down this image and describe each piece. First of all, we can see -that there are two datacenters, labeled "one" and "two". Consul has first -class support for [multiple datacenters](https://learn.hashicorp.com/consul/security-networking/datacenters) and -expects this to be the common case. - -Within each datacenter, we have a mixture of clients and servers. It is expected -that there will be between three to five servers. This strikes a balance between -availability in the case of failure and performance, as consensus gets progressively -slower as more machines are added. However, there is no limit to the number of clients, -and they can easily scale into the thousands or tens of thousands. - -All the agents that are in a datacenter participate in a [gossip protocol](/docs/architecture/gossip). -This means there is a gossip pool that contains all the agents for a given datacenter. This serves -a few purposes: first, there is no need to configure clients with the addresses of servers; -discovery is done automatically. Second, the work of detecting agent failures -is not placed on the servers but is distributed. This makes failure detection much more -scalable than naive heartbeating schemes. It also provides failure detection for the nodes; if the agent is not reachable, then the node may have experienced a failure. Thirdly, it is used as a messaging layer to notify -when important events such as leader election take place. - -The servers in each datacenter are all part of a single Raft peer set. This means that -they work together to elect a single leader, a selected server which has extra duties. The leader -is responsible for processing all queries and transactions. Transactions must also be replicated to -all peers as part of the [consensus protocol](/docs/architecture/consensus). Because of this -requirement, when a non-leader server receives an RPC request, it forwards it to the cluster leader. - -The server agents also operate as part of a WAN gossip pool. This pool is different from the LAN pool -as it is optimized for the higher latency of the internet and is expected to contain only -other Consul server agents. The purpose of this pool is to allow datacenters to discover each -other in a low-touch manner. Bringing a new datacenter online is as easy as joining the existing -WAN gossip pool. Because the servers are all operating in this pool, it also enables cross-datacenter -requests. When a server receives a request for a different datacenter, it forwards it to a random -server in the correct datacenter. That server may then forward to the local leader. - -This results in a very low coupling between datacenters, but because of failure detection, -connection caching and multiplexing, cross-datacenter requests are relatively fast and reliable. - -In general, data is not replicated between different Consul datacenters. When a -request is made for a resource in another datacenter, the local Consul servers forward -an RPC request to the remote Consul servers for that resource and return the results. -If the remote datacenter is not available, then those resources will also not be -available, but that won't otherwise affect the local datacenter. There are some special -situations where a limited subset of data can be replicated, such as with Consul's built-in -[ACL replication](https://learn.hashicorp.com/tutorials/consul/access-control-replication-multiple-datacenters) capability, or -external tools like [consul-replicate](https://github.com/hashicorp/consul-replicate). - -In some places, client agents may cache data from the servers to make it -available locally for performance and reliability. Examples include Connect -certificates and intentions which allow the client agent to make local decisions -about inbound connection requests without a round trip to the servers. Some API -endpoints also support optional result caching. This helps reliability because -the local agent can continue to respond to some queries like service-discovery -or Connect authorization from cache even if the connection to the servers is -disrupted or the servers are temporarily unavailable. - -## Getting in depth - -At this point we've covered the high level architecture of Consul, but there are many -more details for each of the subsystems. The [consensus protocol](/docs/architecture/consensus) is -documented in detail as is the [gossip protocol](/docs/architecture/gossip). The [documentation](/docs/security) -for the security model and protocols used are also available. - -For other details, either consult the code, ask in IRC, or reach out to the mailing list. +# Consul Architecture + +This topic provides an overview of the Consul architecture. We recommend reviewing the Consul [glossary](/docs/install/glossary) as a companion to this topic to help you become familiar with HashiCorp terms. + +> Refer to the [Reference Architecture tutorial](https://learn.hashicorp.com/tutorials/consul/reference-architecture) for hands-on guidance about deploying Consul in production. + +## Introduction + +Consul provides a control plane that enables you to register, access, and secure services deployed across your network. The _control plane_ is the part of the network infrastructure that maintains a central registry to track services and their respective IP addresses. + +When using Consul’s service mesh capabilities, Consul dynamically configures sidecar and gateway proxies in the request path, which enables you to authorize service-to-service connections, route requests to healthy service instances, and enforce mTLS encryption without modifying your service’s code. This ensures that communication remains performant and reliable. Refer to [Service Mesh Proxy Overview](/docs/connect/proxies) for an overview of sidecar proxies. + +![Diagram of the Consul control plane](/img/consul-arch/consul-arch-overview-control-plane.svg) + +## Datacenters + +The Consul control plane contains one or more _datacenters_. A datacenter is the smallest unit of Consul infrastructure that can perform basic Consul operations. A datacenter contains at least one [Consul server agent](#server-agents), but a real-world deployment contains three or five server agents and several [Consul client agents](#client-agents). You can create multiple datacenters and allow nodes in different datacenters to interact with each other. Refer to [Bootstrap a Datacenter](/docs/install/bootstrapping) for information about how to create a datacenter. + +### Clusters + +A collection of Consul agents that are aware of each other is called a _cluster_. The terms _datacenter_ and _cluster_ are often used interchangeably. In some cases, however, _cluster_ refers only to Consul server agents, such as in [HCP Consul](https://cloud.hashicorp.com/consul). In other contexts, such as the [_admin partitions_](/docs/enterprise/admin-partitions) feature included with Consul Enterprise, a cluster may refer to collection of client agents. + +## Agents + +You can run the Consul binary to start Consul _agents_, which are daemons that implement Consul control plane functionality. You can start agents as servers or clients. Refer to [Consul Agent](/docs/agent) for additional information. + +### Server agents + +Consul server agents store all state information, including service and node IP addresses, health checks, and configuration. We recommend deploying three or five servers in a cluster. The more servers you deploy, the greater the resilience and availability in the event of a failure. More servers, however, slow down [consensus](#consensus-protocol), which is a critical server function that enables Consul to efficiently and effectively process information. + +#### Consensus protocol + +Consul clusters elect a single server to be the _leader_ through a process called _consensus_. The leader processes all queries and transactions, which prevents conflicting updates in clusters containing multiple servers. + +Servers that are not currently acting as the cluster leader are called _followers_. Followers forward requests from client agents to the cluster leader. The leader replicates the requests to all other servers in the cluster. Replication ensures that if the leader is unavailable, other servers in the cluster can elect another leader without losing any data. + +Consul servers establish consensus using the Raft algorithm on port `8300`. Refer to [Consensus Protocol](/docs/architecture/consensus) for additional information. + +![Diagram of the Consul control plane consensus traffic](/img/consul-arch/consul-arch-overview-consensus.svg) + +### Client agents + +Consul clients report node and service health status to the Consul cluster. In a typical deployment, you must run client agents on every compute node in your datacenter. Clients use remote procedure calls (RPC) to interact with servers. By default, clients send RPC requests to the servers on port `8300`. + +There are no limits to the number of client agents or services you can use with Consul, but production deployments should distribute services across multiple Consul datacenters. Using a multi-datacenter deployment enhances infrastructure resilience and limits control plane issues. We recommend deploying a maximum of 5,000 client agents per datacenter. Some large organizations have deployed tens of thousands of client agents and hundreds of thousands of service instances across a multi-datacenter deployment. Refer to [Cross-datacenter requests](#cross-datacenter-requests) for additional information. + +## LAN gossip pool + +Client and server agents participate in a LAN gossip pool so that they can distribute and perform node [health checks](/docs/discovery/checks). Agents in the pool propagate the health check information across the cluster. Agent gossip communication occurs on port `8301` using UDP. Agent gossip falls back to TCP if UDP is not available. Refer to [Gossip Protocol](/docs/architecture/gossip) for additional information. + +The following simplified diagram shows the interactions between servers and clients. + + + + + +![Diagram of the Consul LAN gossip pool](/img/consul-arch/consul-arch-overview-lan-gossip-pool.svg) + + + + +![Diagram of RPC communication between Consul agents](/img/consul-arch/consul-arch-overview-rpc.svg) + + + + +## Cross-datacenter requests + +Each Consul datacenter maintains its own catalog of services and their health. By default, the information is not replicated across datacenters. WAN federation and cluster peering are two multi-datacenter deployment models that enable service connectivity across datacenters. + +### WAN federation + +WAN federation is an approach for connecting multiple Consul datacenters. It requires you to designate a _primary datacenter_ that contains authoritative information about all datacenters, including service mesh configurations and access control list (ACL) resources. + +In this model, when a client agent requests a resource in a remote secondary datacenter, a local Consul server forwards the RPC request to a remote Consul server that has access to the resource. A remote server sends the results to the local server. If the remote datacenter is unavailable, its resources are also unavailable. By default, WAN-federated servers send cross-datacenter requests over TCP on port `8300`. + +You can configure control plane and data plane traffic to go through mesh gateways, which simplifies networking requirements. + +> **Hands-on**: To enable services to communicate across datacenters when the ACL system is enabled, refer to the [ACL Replication for Multiple Datacenters](https://learn.hashicorp.com/tutorials/consul/access-control-replication-multiple-datacenters) tutorial. + +#### WAN gossip pool + +Servers may also participate in a WAN gossip pool, which is optimized for greater latency imposed by the Internet. The pool enables servers to exchange information, such as their addresses and health, and gracefully handle loss of connectivity in the event of a failure. + +In the following diagram, the servers in each data center participate in a WAN gossip pool by sending data over TCP/UDP on port `8302`. Refer to [Gossip Protocol](/docs/architecture/gossip) for additional information. + + + + + +![Diagram of the Consul LAN gossip pool](/img/consul-arch/consul-arch-overview-wan-gossip-cross-cluster.svg) + + + + +![Diagram of RPC communication between Consul agents](/img/consul-arch/consul-arch-overview-remote-dc-forwarding-cross-cluster.svg) + + + + +### Cluster peering (beta) + +You can create peering connections between two or more independent clusters so that services deployed to different datacenters or admin partitions can communicate. An [admin partition](/docs/enterprise/admin-partitions) is a feature in Consul Enterprise that enables you to define isolated network regions that use the same Consul servers. In the cluster peering model, you create a token in one of the datacenters or partitions and configure another datacenter or partition to present the token to establish the connection. + +-> **Cluster peering is currently in beta:** Functionality associated with cluster peering is subject to change. You should never use the beta release in secure environments or production scenarios. Features in beta may have performance issues, scaling issues, and limited support. + +Refer to [What is Cluster Peering?](/docs/connect/cluster-peering) for additional information. \ No newline at end of file diff --git a/website/content/docs/connect/ca/vault.mdx b/website/content/docs/connect/ca/vault.mdx index 5023cb2bde..26cc893463 100644 --- a/website/content/docs/connect/ca/vault.mdx +++ b/website/content/docs/connect/ca/vault.mdx @@ -227,6 +227,11 @@ path "/sys/mounts/connect_inter" { capabilities = [ "read" ] } +# Needed for Consul 1.11+ +path "/sys/mounts/connect_inter/tune" { + capabilities = [ "update" ] +} + path "/connect_root/" { capabilities = [ "read" ] } @@ -275,6 +280,11 @@ path "/sys/mounts/connect_inter" { capabilities = [ "create", "read", "update", "delete", "list" ] } +# Needed for Consul 1.11+ +path "/sys/mounts/connect_inter/tune" { + capabilities = [ "update" ] +} + path "/connect_root/*" { capabilities = [ "create", "read", "update", "delete", "list" ] } diff --git a/website/content/docs/connect/cluster-peering/create-manage-peering.mdx b/website/content/docs/connect/cluster-peering/create-manage-peering.mdx index 916ef33da8..d410bfc613 100644 --- a/website/content/docs/connect/cluster-peering/create-manage-peering.mdx +++ b/website/content/docs/connect/cluster-peering/create-manage-peering.mdx @@ -4,6 +4,7 @@ page_title: Cluster Peering - Create and Manage Connections description: >- Generate a peering token to establish communication, export services, and authorize requests for cluster peering connections. Learn how to create, list, read, check, and delete peering connections. --- + # Create and Manage Cluster Peering Connections @@ -33,7 +34,7 @@ To begin the cluster peering process, generate a peering token in one of your cl Every time you generate a peering token, a single-use establishment secret is embedded in the token. Because regenerating a peering token invalidates the previously generated secret, you must use the most recently created token to establish peering connections. - + In `cluster-01`, use the [`/peering/token` endpoint](/api-docs/peering#generate-a-peering-token) to issue a request for a peering token. @@ -57,7 +58,7 @@ Create a JSON file that contains the first cluster's name and the peering token. - + In `cluster-01`, use the [`consul peering generate-token` command](/commands/operator/generate-token) to issue a request for a peering token. @@ -70,13 +71,13 @@ Save this value to a file or clipboard to be used in the next step on `cluster-0 - + 1. In the Consul UI for the datacenter associated with `cluster-01`, click **Peers**. 1. Click **Add peer connection**. 1. In the **Generate token** tab, enter `cluster-02` in the **Name of peer** field. 1. Click the **Generate token** button. -1. Copy the token before you proceed. Be careful not to lose the token, as you cannot view the token again after leaving this screen. If you lose your token, you must generate a new one. +1. Copy the token before you proceed. You cannot view it again after leaving this screen. If you lose your token, you must generate a new one. @@ -86,7 +87,7 @@ Save this value to a file or clipboard to be used in the next step on `cluster-0 Next, use the peering token to establish a secure connection between the clusters. - + In one of the client agents in "cluster-02," use `peering_token.json` and the [`/peering/establish` endpoint](/api-docs/peering#establish-a-peering-connection) to establish the peering connection. This endpoint does not generate an output unless there is an error. @@ -94,14 +95,13 @@ In one of the client agents in "cluster-02," use `peering_token.json` and the [` $ curl --request POST --data @peering_token.json http://127.0.0.1:8500/v1/peering/establish ``` -When you connect server agents through cluster peering, they peer their default partitions. To establish peering connections for other partitions through server agents, you must add the `Partition` field to `peering_token.json` and specify the partitions you want to peer. For additional configuration information, refer to [Cluster Peering - HTTP API](/api-docs/peering). - +When you connect server agents through cluster peering, their default behavior is to peer to the `default` partition. To establish peering connections for other partitions through server agents, you must add the `Partition` field to `peering_token.json` and specify the partitions you want to peer. For additional configuration information, refer to [Cluster Peering - HTTP API](/api-docs/peering). You can dial the `peering/establish` endpoint once per peering token. Peering tokens cannot be reused after being used to establish a connection. If you need to re-establish a connection, you must generate a new peering token. - + In one of the client agents in "cluster-02," issue the [`consul peering establish` command](/commands/peering/establish) and specify the token generated in the previous step. The command establishes the peering connection. The commands prints "Successfully established peering connection with cluster-01" after the connection is established. @@ -120,7 +120,7 @@ If you need to re-establish a connection, you must generate a new peering token. - + 1. In the Consul UI for the datacenter associated with `cluster 02`, click **Peers** and then **Add peer connection**. 1. Click **Establish peering**. @@ -152,7 +152,7 @@ Services = [ { ## The peer name to reference in config is the one set ## during the peering process. - PeerName = "cluster-02" + Peer = "cluster-02" } ] } @@ -209,7 +209,7 @@ After you establish a peering connection, you can get a list of all active peeri You can list all active peering connections in a cluster. - + After you establish a peering connection, [query the `/peerings/` endpoint](/api-docs/peering#list-all-peerings) to get a list of all peering connections. For example, the following command requests a list of all peering connections on `localhost` and returns the information as a series of JSON objects: @@ -245,7 +245,7 @@ $ curl http://127.0.0.1:8500/v1/peerings ``` - + After you establish a peering connection, run the [`consul peering list`](/commands/peering/list) command to get a list of all peering connections. For example, the following command requests a list of all peering connections and returns the information in a table: @@ -259,7 +259,7 @@ cluster-03 PENDING 0 0 ``` - + In the Consul UI, click **Peers**. The UI lists peering connections you created for clusters in a datacenter. @@ -272,7 +272,7 @@ The name that appears in the list is the name of the cluster in a different data You can get information about individual peering connections between clusters. - + After you establish a peering connection, [query the `/peering/` endpoint](/api-docs/peering#read-a-peering-connection) to get peering information about for a specific cluster. For example, the following command requests peering connection information for "cluster-02" and returns the info as a JSON object: @@ -294,7 +294,7 @@ $ curl http://127.0.0.1:8500/v1/peering/cluster-02 ``` - + After you establish a peering connection, run the [`consul peering read`](/commands/peering/list) command to get peering information about for a specific cluster. For example, the following command requests peering connection information for "cluster-02": @@ -323,7 +323,7 @@ Modify Index: 89 ``` - + In the Consul UI, click **Peers**. The UI lists peering connections you created for clusters in that datacenter. Click the name of a peered cluster to view additional details about the peering connection. @@ -347,7 +347,7 @@ A successful query includes service information in the output. You can disconnect the peered clusters by deleting their connection. Deleting a peering connection stops data replication to the peer and deletes imported data, including services and CA certificates. - + In "cluster-01," request the deletion through the [`/peering/ endpoint`](/api-docs/peering#delete-a-peering-connection). @@ -356,7 +356,7 @@ $ curl --request DELETE http://127.0.0.1:8500/v1/peering/cluster-02 ``` - + In "cluster-01," request the deletion through the [`consul peering delete`](/commands/peering/list) command. @@ -367,7 +367,7 @@ Successfully submitted peering connection, cluster-02, for deletion ``` - + In the Consul UI, click **Peers**. The UI lists peering connections you created for clusters in that datacenter. @@ -375,3 +375,152 @@ Next to the name of the peer, click **More** (three horizontal dots) and then ** + +## L7 traffic management between peers + +The following sections describe how to enable L7 traffic management features between peered clusters. + +### Service resolvers for redirects and failover + +As of Consul v1.14, you can use [dynamic traffic management](/consul/docs/connect/l7-traffic) to configure your service mesh so that services automatically failover and redirect between peers. The following examples update the [`service-resolver` config entry](/docs/connect/config-entries/) in `cluster-01` so that Consul redirects traffic intended for the `frontend` service to a backup instance in peer `cluster-02` when it detects multiple connection failures. + + + +```hcl +Kind = "service-resolver" +Name = "frontend" +ConnectTimeout = "15s" +Failover = { + "*" = { + Targets = [ + {Peer = "cluster-02"} + ] + } +} +``` + +```yaml +apiVersion: consul.hashicorp.com/v1alpha1 +kind: ServiceResolver +metadata: + name: frontend +spec: + connectTimeout: 15s + failover: + '*': + targets: + - peer: 'cluster-02' + service: 'frontend' + namespace: 'default' +``` + +```json +{ + "ConnectTimeout": "15s", + "Kind": "service-resolver", + "Name": "frontend", + "Failover": { + "*": { + "Targets": [ + { + "Peer": "cluster-02" + } + ] + } + }, + "CreateIndex": 250, + "ModifyIndex": 250 +} +``` + + + +### Service splitters and custom routes + +The `service-splitter` and `service-router` configuration entry kinds do not support directly targeting a service instance hosted on a peer. To split or route traffic to a service on a peer, you must combine the definition with a `service-resolver` configuration entry that defines the service hosted on the peer as an upstream service. For example, to split traffic evenly between `frontend` services hosted on peers, first define the desired behavior locally: + + + +```hcl +Kind = "service-splitter" +Name = "frontend" +Splits = [ + { + Weight = 50 + ## defaults to service with same name as configuration entry ("frontend") + }, + { + Weight = 50 + Service = "frontend-peer" + }, +] +``` + +```yaml +apiVersion: consul.hashicorp.com/v1alpha1 +kind: ServiceSplitter +metadata: + name: frontend +spec: + splits: + - weight: 50 + ## defaults to service with same name as configuration entry ("web") + - weight: 50 + service: frontend-peer +``` + +```json +{ + "Kind": "service-splitter", + "Name": "frontend", + "Splits": [ + { + "Weight": 50 + }, + { + "Weight": 50, + "Service": "frontend-peer" + } + ] +} +``` + + + +Then, create a local `service-resolver` configuration entry named `frontend-peer` and define a redirect targeting the peer and its service: + + + +```hcl +Kind = "service-resolver" +Name = "frontend-peer" +Redirect { + Service = frontend + Peer = "cluster-02" +} +``` + +```yaml +apiVersion: consul.hashicorp.com/v1alpha1 +kind: ServiceResolver +metadata: + name: frontend-peer +spec: + redirect: + peer: 'cluster-02' + service: 'frontend' +``` + +```json +{ + "Kind": "service-resolver", + "Name": "frontend-peer", + "Redirect": { + "Service": "frontend", + "Peer": "cluster-02" + } +} +``` + + + \ No newline at end of file diff --git a/website/content/docs/connect/cluster-peering/index.mdx b/website/content/docs/connect/cluster-peering/index.mdx index f216fb780c..76cbf1de0d 100644 --- a/website/content/docs/connect/cluster-peering/index.mdx +++ b/website/content/docs/connect/cluster-peering/index.mdx @@ -13,17 +13,20 @@ You can create peering connections between two or more independent clusters so t ## Overview -Cluster peering allows Consul clusters in different datacenters to communicate with each other. The cluster peering process consists of the following steps: +Cluster peering is a process that allows Consul clusters to communicate with each other. The cluster peering process consists of the following steps: + 1. Create a peering token in one cluster. 1. Use the peering token to establish peering with a second cluster. 1. Export services between clusters. 1. Create intentions to authorize services for peers. -For detailed instructions on setting up cluster peering, refer to [Create and Manage Peering Connections](/docs/connect/cluster-peering/create-manage-peering). +For detailed instructions on establishing cluster peering connections, refer to [Create and Manage Peering Connections](/docs/connect/cluster-peering/create-manage-peering). + +> To learn how to peer clusters and connect services across peers in AWS Elastic Kubernetes Service (EKS) environments, complete the [Consul Cluster Peering on Kubernetes tutorial](https://learn.hashicorp.com/tutorials/consul/cluster-peering-aws?utm_source=docs). ### Differences between WAN federation and cluster peering -WAN federation and cluster peering are different ways to connect clusters. The most important distinction is that WAN federation assumes clusters are owned by the same operators, so it maintains and replicates global states such as ACLs and configuration entries. As a result, WAN federation requires a _primary datacenter_ to serve as an authority for replicated data. +WAN federation and cluster peering are different ways to connect Consul deployments. WAN federation connects multiple datacenters to make them function as if they were a single cluster, while cluster peering treats each datacenter as a separate cluster. As a result, WAN federation requires a primary datacenter to maintain and replicate global states such as ACLs and configuration entries, but cluster peering does not. Regardless of whether you connect your clusters through WAN federation or cluster peering, human and machine users can use either method to discover services in other clusters or dial them through the service mesh. @@ -34,14 +37,16 @@ Regardless of whether you connect your clusters through WAN federation or cluste | Connects clusters owned by different operators | ❌ | ✅ | | Functions without declaring primary datacenter | ❌ | ✅ | | Replicates exported services for service discovery | ❌ | ✅ | +| Gossip protocol: Requires LAN gossip only | ❌ | ✅ | | Forwards service requests for service discovery | ✅ | ❌ | | Shares key/value stores | ✅ | ❌ | -| Uses gossip protocol | ✅ | ❌ | ## Beta release features and constraints -The cluster peering beta includes the following features and functionality: +The cluster peering beta release includes the following features and functionality: +- **Consul v1.14 beta only**: Dynamic traffic control with a service resolver config entry can target failover and redirects to service instances in a peered cluster. +- Consul datacenters that are already federated stay federated. You do not need to migrate WAN federated clusters to cluster peering. - Mesh gateways for _service to service traffic_ between clusters are available. For more information on configuring mesh gateways across peers, refer to [Service-to-service Traffic Across Peered Clusters](/docs/connect/gateways/mesh-gateway/service-to-service-traffic-peers). - You can generate peering tokens, establish, list, read, and delete peerings, and manage intentions for peering connections with both the API and the UI. - You can configure [transparent proxies](/docs/connect/transparent-proxy) for peered services. @@ -51,9 +56,8 @@ Not all features and functionality are available in the beta release. In particu - Mesh gateways for _server to server traffic_ are not available. - Services with node, instance, and check definitions totaling more than 4MB cannot be exported to a peer. -- Dynamic routing features such as splits, custom routes, and redirects cannot target services in a peered cluster. -- Configuring service failover across peers is not supported for service mesh. -- Consul datacenters that are already federated stay federated. You do not need to migrate WAN federated clusters to cluster peering. +- The `service-splitter` and `service-router` configuration entry kinds cannot directly target a peer. To split or route traffic to a service instance on a peer, you must supplement your desired dynamic routing definition with a `service-resolver` config entry on the dialing cluster. Refer to [L7 traffic management between peers](/docs/connect/cluster-peering/create-manage-peering#L7-traffic) for more information. - The `consul intention` CLI command is not supported. To manage intentions that specify services in peered clusters, use [configuration entries](/docs/connect/config-entries/service-intentions). - Accessing key/value stores across peers is not supported. - Because non-Enterprise Consul instances are restricted to the `default` namespace, Consul Enterprise instances cannot export services from outside of the `default` namespace to non-Enterprise peers. +- Cross-cluster mesh gateways are supported in `remote` mode only. diff --git a/website/content/docs/connect/cluster-peering/k8s.mdx b/website/content/docs/connect/cluster-peering/k8s.mdx index c12c7774d2..8b565e2204 100644 --- a/website/content/docs/connect/cluster-peering/k8s.mdx +++ b/website/content/docs/connect/cluster-peering/k8s.mdx @@ -18,39 +18,41 @@ The following CRDs are used to create and manage a peering connection: - `PeeringAcceptor`: Generates a peering token and accepts an incoming peering connection. - `PeeringDialer`: Uses a peering token to make an outbound peering connection with the cluster that generated the token. +As of Consul v1.14, you can also [implement service failovers and redirects to control traffic](/consul/docs/connect/l7-traffic) between peers. + +> To learn how to peer clusters and connect services across peers in AWS Elastic Kubernetes Service (EKS) environments, complete the [Consul Cluster Peering on Kubernetes tutorial](https://learn.hashicorp.com/tutorials/consul/cluster-peering-aws?utm_source=docs). + ## Prerequisites You must implement the following requirements to create and use cluster peering connections with Kubernetes: -- Consul version 1.13.1 or later + +- Consul v1.13.1 or later - At least two Kubernetes clusters - The installation must be running on Consul on Kubernetes version 0.47.1 or later -### Prepare for install +### Prepare for installation + +Complete the following procedure after you have provisioned a Kubernetes cluster and set up your kubeconfig file to manage access to multiple Kubernetes clusters. -1. After provisioning a Kubernetes cluster and setting up your kubeconfig file to manage access to multiple Kubernetes clusters, export the Kubernetes context names for future use with `kubectl`. For more information on how to use kubeconfig and contexts, refer to [Configure access to multiple clusters](https://kubernetes.io/docs/tasks/access-application-cluster/configure-access-multiple-clusters/) on the Kubernetes documentation website. +1. Use the `kubectl` command to export the Kubernetes context names and then set them to variables for future use. For more information on how to use kubeconfig and contexts, refer to the [Kubernetes docs on configuring access to multiple clusters](https://kubernetes.io/docs/tasks/access-application-cluster/configure-access-multiple-clusters/). You can use the following methods to get the context names for your clusters: - * Issue the `kubectl config current-context` command to get the context for the cluster you are currently in. - * Issue the `kubectl config get-contexts` command to get all configured contexts in your kubeconfig file. + - Use the `kubectl config current-context` command to get the context for the cluster you are currently in. + - Use the `kubectl config get-contexts` command to get all configured contexts in your kubeconfig file. ```shell-session $ export CLUSTER1_CONTEXT= $ export CLUSTER2_CONTEXT= ``` -1. To establish cluster peering through Kubernetes, create a `values.yaml` file with the following Helm values. - - With these values, - the servers in each cluster will be exposed over a Kubernetes Load balancer service. This service can be customized - using [`server.exposeService`](/docs/k8s/helm#v-server-exposeservice). - - When generating a peering token from one of the clusters, Consul uses the address(es) of the load balancer in the peering token so that the peering stream goes through the load balancer in front of the servers. For customizing the addresses used in the peering token, refer to [`global.peering.tokenGeneration`](/docs/k8s/helm#v-global-peering-tokengeneration). +1. To establish cluster peering through Kubernetes, create a `values.yaml` file with the following Helm values. ```yaml global: + name: consul image: "hashicorp/consul:1.13.1" peering: enabled: true @@ -70,12 +72,16 @@ You must implement the following requirements to create and use cluster peering ``` + + These Helm values configure the servers in each cluster so that they expose ports over a Kubernetes load balancer service. For additional configuration options, refer to [`server.exposeService`](/docs/k8s/helm#v-server-exposeservice). + + When generating a peering token from one of the clusters, Consul includes a load balancer address in the token so that the peering stream goes through the load balancer in front of the servers. For additional configuration options, refer to [`global.peering.tokenGeneration`](/docs/k8s/helm#v-global-peering-tokengeneration). ### Install Consul on Kubernetes -1. Install Consul on Kubernetes on each Kubernetes cluster by applying `values.yaml` using the Helm CLI. +Install Consul on Kubernetes by using the CLI to apply `values.yaml` to each cluster. - 1. Install Consul on Kubernetes on `cluster-01` + 1. In `cluster-01`, run the following commands: ```shell-session $ export HELM_RELEASE_NAME=cluster-01 @@ -84,7 +90,8 @@ You must implement the following requirements to create and use cluster peering ```shell-session $ helm install ${HELM_RELEASE_NAME} hashicorp/consul --create-namespace --namespace consul --version "0.47.1" --values values.yaml --kube-context $CLUSTER1_CONTEXT ``` - 1. Install Consul on Kubernetes on `cluster-02` + + 1. In `cluster-02`, run the following commands: ```shell-session $ export HELM_RELEASE_NAME=cluster-02 @@ -94,9 +101,13 @@ You must implement the following requirements to create and use cluster peering $ helm install ${HELM_RELEASE_NAME} hashicorp/consul --create-namespace --namespace consul --version "0.47.1" --values values.yaml --kube-context $CLUSTER2_CONTEXT ``` -## Create a peering token +## Create a peering connection for Consul on Kubernetes + +To peer Kubernetes clusters running Consul, you need to create a peering token and share it with the other cluster. Complete the following steps to create the peer connection. + +### Create a peering token -To peer Kubernetes clusters running Consul, you need to create a peering token and share it with the other cluster. As part of the peering process, the peer names for each respective cluster within the peering are established by using the `metadata.name` values for the `PeeringAcceptor` and `PeeringDialer` CRDs. +Peers identify each other using the `metadata.name` values you establish when creating the `PeeringAcceptor` and `PeeringDialer` CRDs. 1. In `cluster-01`, create the `PeeringAcceptor` custom resource. @@ -129,7 +140,7 @@ To peer Kubernetes clusters running Consul, you need to create a peering token a $ kubectl --context $CLUSTER1_CONTEXT get secret peering-token --output yaml > peering-token.yaml ``` -## Establish a peering connection between clusters +### Establish a peering connection between clusters 1. Apply the peering token to the second cluster. @@ -137,7 +148,7 @@ To peer Kubernetes clusters running Consul, you need to create a peering token a $ kubectl --context $CLUSTER2_CONTEXT apply --filename peering-token.yaml ``` -1. In `cluster-02`, create the `PeeringDialer` custom resource. +1. In `cluster-02`, create the `PeeringDialer` custom resource. @@ -162,9 +173,11 @@ To peer Kubernetes clusters running Consul, you need to create a peering token a $ kubectl --context $CLUSTER2_CONTEXT apply --filename dialer.yaml ``` -## Export services between clusters +### Export services between clusters -1. For the service in "cluster-02" that you want to export, add the following [annotation](/docs/k8s/annotations-and-labels) to your service's pods. +The examples described in this section demonstrate how to export a service named `backend`. You should change instances of `backend` in the example code to the name of the service you want to export. + +1. For the service in `cluster-02` that you want to export, add the following [annotations](/docs/k8s/annotations-and-labels) to your service's pods. @@ -188,7 +201,7 @@ To peer Kubernetes clusters running Consul, you need to create a peering token a metadata: name: backend --- - # deployment for backend + # Deployment for backend apiVersion: apps/v1 kind: Deployment metadata: @@ -242,13 +255,13 @@ To peer Kubernetes clusters running Consul, you need to create a peering token a -1. Apply the service file and the `ExportedServices` resource for the second cluster. +1. Apply the service file and the `ExportedServices` resource to the second cluster. ```shell-session $ kubectl apply --context $CLUSTER2_CONTEXT --filename backend.yaml --filename exportedsvc.yaml ``` -## Authorize services for peers +### Authorize services for peers 1. Create service intentions for the second cluster. @@ -277,7 +290,7 @@ To peer Kubernetes clusters running Consul, you need to create a peering token a $ kubectl --context $CLUSTER2_CONTEXT apply --filename intention.yml ``` -1. For the services in `cluster-01` that you want to access the "backend," add the following annotations to the service file. To dial the upstream service from an application, ensure that the requests are sent to the correct DNS name as specified in [Service Virtual IP Lookups](/docs/discovery/dns#service-virtual-ip-lookups). +1. For the services in `cluster-01` that you want to access `backend`, add the following annotations to the service file. To dial the upstream service from an application, ensure that the requests are sent to the correct DNS name as specified in [Service Virtual IP Lookups](/docs/discovery/dns#service-virtual-ip-lookups). @@ -349,10 +362,11 @@ To peer Kubernetes clusters running Consul, you need to create a peering token a $ kubectl --context $CLUSTER1_CONTEXT apply --filename frontend.yaml ``` -1. Run the following command in `frontend` and check the output to confirm that you peered your clusters successfully. +1. Run the following command in `frontend` and then check the output to confirm that you peered your clusters successfully. ```shell-session $ kubectl --context $CLUSTER1_CONTEXT exec -it $(kubectl --context $CLUSTER1_CONTEXT get pod -l app=frontend -o name) -- curl localhost:9090 + { "name": "frontend", "uri": "/", @@ -400,9 +414,9 @@ $ curl "localhost:8500/v1/health/connect/backend?peer=cluster-02" ## Recreate or reset a peering connection -To recreate or reset the peering connection, you need to generate a new peering token on the cluster where you created the `PeeringAcceptor` (in this example, `cluster-01`). +To recreate or reset the peering connection, you need to generate a new peering token from the cluster where you created the `PeeringAcceptor`. -1. You can do this by creating or updating the annotation `consul.hashicorp.com/peering-version` on the `PeeringAcceptor`. If the annotation already exists, update its value to a version that is higher. +1. In the `PeeringAcceptor` CRD, add the annotation `consul.hashicorp.com/peering-version`. If the annotation already exists, update its value to a higher version. @@ -423,6 +437,37 @@ To recreate or reset the peering connection, you need to generate a new peering -1. Once you have done this, repeat the steps in the peering process. This includes saving your peering token so that you can export it to the other cluster. This will re-establish peering with the updated token. +1. After updating `PeeringAcceptor`, repeat the following steps to create a peering connection: + 1. [Create a peering token](#create-a-peering-token) + 1. [Establish a peering connection between clusters](#establish-a-peering-connection-between-clusters) + 1. [Export services between clusters](#export-services-between-clusters) + 1. [Authorize services for peers](#authorize-services-for-peers) + + Your peering connection is re-established with the updated token. + +~> **Note:** The only way to create or set a new peering token is to manually adjust the value of the annotation `consul.hashicorp.com/peering-version`. Creating a new token causes the previous token to expire. + +## Traffic management between peers + +As of Consul v1.14, you can use [dynamic traffic management](/consul/docs/connect/l7-traffic) to configure your service mesh so that services automatically failover and redirect between peers. + +To configure automatic service failovers and redirect, edit the `ServiceResolver` CRD so that traffic resolves to a backup service instance on a peer. The following example updates the `ServiceResolver` CRD in `cluster-01` so that Consul redirects traffic intended for the `frontend` service to a backup instance in `cluster-02` when it detects multiple connection failures to the primary instance. + + + +```yaml +apiVersion: consul.hashicorp.com/v1alpha1 +kind: ServiceResolver +metadata: + name: frontend +spec: + connectTimeout: 15s + failover: + '*': + targets: + - peer: 'cluster-02' + service: 'backup' + namespace: 'default' +``` -~> **Note:** A new peering token is only generated upon manually setting and updating the value of the annotation `consul.hashicorp.com/peering-version`. Creating a new token will cause the previous token to expire. + diff --git a/website/content/docs/connect/config-entries/exported-services.mdx b/website/content/docs/connect/config-entries/exported-services.mdx index 0aa37e711e..5c6dd2a09d 100644 --- a/website/content/docs/connect/config-entries/exported-services.mdx +++ b/website/content/docs/connect/config-entries/exported-services.mdx @@ -46,7 +46,7 @@ Services = [ Name = "" Consumers = [ { - PeerName = "" + Peer = "" } ] } @@ -73,7 +73,7 @@ spec: "Name": "", "Consumers": [ { - "PeerName": "" + "Peer": "" } ] } @@ -96,7 +96,7 @@ Services = [ Namespace = "" Consumers = [ { - PeerName = "" + Peer = "" } ] } @@ -126,7 +126,7 @@ spec: "Namespace": "" "Consumers": [ { - "PeerName": "" + "Peer": "" } ] } @@ -215,8 +215,8 @@ The `Services` parameter contains a list of one or more parameters that specify The `Consumers` parameter contains a list of one or more parameters that specify the destination cluster for an exported service. Each item in the `Consumers` list must contain exactly one of the following parameters: -- `PeerName`: Specifies the name of the peered cluster to export the service to. -A asterisk wildcard (`*`) cannot be specified as the `PeerName`. Added in Consul 1.13.0. +- `Peer`: Specifies the name of the peered cluster to export the service to. +A asterisk wildcard (`*`) cannot be specified as the `Peer`. Added in Consul 1.13.0. - `Partition`: Specifies an admin partition in the datacenter to export the service to. A asterisk wildcard (`*`) cannot be specified as the `Partition`. @@ -242,7 +242,7 @@ Services = [ Name = "payments" Consumers = [ { - PeerName = "web-shop" + Peer = "web-shop" }, ] }, @@ -250,7 +250,7 @@ Services = [ Name = "refunds" Consumers = [ { - PeerName = "web-shop" + Peer = "web-shop" } ] } @@ -280,7 +280,7 @@ spec: "Name": "payments", "Consumers": [ { - "PeerName": "web-shop" + "Peer": "web-shop" }, ], }, @@ -288,7 +288,7 @@ spec: "Name": "refunds", "Consumers": [ { - "PeerName": "web-shop" + "Peer": "web-shop" } ] } @@ -315,7 +315,7 @@ Services = [ Namespace = "billing" Consumers = [ { - PeerName = "web-shop" + Peer = "web-shop" }, ] }, @@ -324,7 +324,7 @@ Services = [ Namespace = "billing" Consumers = [ { - PeerName = "web-shop" + Peer = "web-shop" } ] } @@ -358,7 +358,7 @@ spec: "Namespace": "billing" "Consumers": [ { - "PeerName": "web-shop" + "Peer": "web-shop" }, ], }, @@ -367,7 +367,7 @@ spec: "Namespace": "billing", "Consumers": [ { - "PeerName": "web-shop" + "Peer": "web-shop" } ] } @@ -475,10 +475,10 @@ Services = [ Name = "*" Consumers = [ { - PeerName = "monitoring" + Peer = "monitoring" }, { - PeerName = "platform" + Peer = "platform" } ] } @@ -507,10 +507,10 @@ spec: "Namespace": "*" "Consumers": [ { - "PeerName": "monitoring" + "Peer": "monitoring" }, { - "PeerName": "platform" + "Peer": "platform" } ] } @@ -537,10 +537,10 @@ Services = [ Namespace = "*" Consumers = [ { - PeerName = "monitoring" + Peer = "monitoring" }, { - PeerName = "platform" + Peer = "platform" } ] } @@ -571,10 +571,10 @@ spec: "Namespace": "*" "Consumers": [ { - "PeerName": "monitoring" + "Peer": "monitoring" }, { - "PeerName": "platform" + "Peer": "platform" } ] } @@ -677,4 +677,4 @@ An ACL token with `service:write` permissions is required for the cluster the qu Exports are available to all services in the consumer cluster. In the previous example, any service with `write` permissions for the `frontend` partition can read exports. -For additional information, refer to [Health HTTP Endpoint](/api-docs/health). +For additional information, refer to [Health HTTP Endpoint](/api-docs/health). \ No newline at end of file diff --git a/website/content/docs/connect/config-entries/ingress-gateway.mdx b/website/content/docs/connect/config-entries/ingress-gateway.mdx index e9a520a7f1..785dff91a4 100644 --- a/website/content/docs/connect/config-entries/ingress-gateway.mdx +++ b/website/content/docs/connect/config-entries/ingress-gateway.mdx @@ -328,6 +328,7 @@ In the following example, two listeners are configured on an ingress gateway nam - The first listener is configured to listen on port `8080` and uses a wildcard (`*`) to proxy traffic to all services in the datacenter. - The second listener exposes the `api` and `web` services on port `4567` at user-provided hosts. - TLS is enabled on every listener. +- The `max_connections` of the ingress gateway proxy to each upstream cluster is set to 4096. The Consul Enterprise version implements the following additional configurations: @@ -346,6 +347,10 @@ TLS { Enabled = true } +Defaults { + MaxConnections = 4096 +} + Listeners = [ { Port = 8080 @@ -1041,6 +1046,38 @@ You can specify the following parameters to configure ingress gateway configurat }, ], }, + { + name: 'Defaults', + type: 'IngressServiceConfig: ', + description: `Default configuration that applies to all upstreams.`, + children: [ + { + name: 'MaxConnections', + type: 'int: 0', + description: `The maximum number of connections a service instance + will be allowed to establish against the given upstream. Use this to limit + HTTP/1.1 traffic, since HTTP/1.1 has a request per connection. + If not specified, it uses the default value. For example, 1024 for Envoy proxy.`, + }, + { + name: 'MaxPendingRequests', + type: 'int: 0', + description: `The maximum number of requests that will be queued + while waiting for a connection to be established. For this configuration to + be respected, a L7 protocol must be defined in the \`protocol\` field. + If not specified, it uses the default value. For example, 1024 for Envoy proxy.`, + }, + { + name: 'MaxConcurrentRequests', + type: 'int: 0', + description: `The maximum number of concurrent requests that + will be allowed at a single point in time. Use this to limit HTTP/2 traffic, + since HTTP/2 has many requests per connection. For this configuration to be + respected, a L7 protocol must be defined in the \`protocol\` field. + If not specified, it uses the default value. For example, 1024 for Envoy proxy.`, + }, + ], + }, { name: 'Listeners', type: 'array: )', @@ -1156,6 +1193,21 @@ You can specify the following parameters to configure ingress gateway configurat }, ], }, + { + name: 'MaxConnections', + type: 'int: 0', + description: 'overrides for the [`Defaults` field](#available-fields)', + }, + { + name: 'MaxPendingRequests', + type: 'int: 0', + description: 'overrides for the [`Defaults` field](#available-fields)', + }, + { + name: 'MaxConcurrentRequests', + type: 'int: 0', + description: 'overrides for the [`Defaults` field](#available-fields)', + }, ], }, { diff --git a/website/content/docs/connect/config-entries/service-defaults.mdx b/website/content/docs/connect/config-entries/service-defaults.mdx index 17920ca971..0ba3d56f52 100644 --- a/website/content/docs/connect/config-entries/service-defaults.mdx +++ b/website/content/docs/connect/config-entries/service-defaults.mdx @@ -355,6 +355,15 @@ represents a location outside the Consul cluster. They can be dialed directly wh [\`service-intentions\`](/docs/connect/config-entries/service-intentions). Supported values are one of \`tcp\`, \`http\`, \`http2\`, or \`grpc\`.`, }, + { + name: 'BalanceInboundConnections', + type: `string: ""`, + description: `Sets the strategy for allocating inbound connections to the service across proxy threads. + The only supported value is \`exact_balance\`. By default, no connection balancing is used. + Refer to the + [Envoy Connection Balance config](https://cloudnative.to/envoy/api-v3/config/listener/v3/listener.proto.html#config-listener-v3-listener-connectionbalanceconfig) + for details.` + }, { name: 'Mode', type: `string: ""`, @@ -445,6 +454,15 @@ represents a location outside the Consul cluster. They can be dialed directly wh }, ], }, + { + name: 'BalanceOutboundConnections', + type: `string: ""`, + description: `Sets the strategy for allocating outbound connections from the upstream across proxy threads. + The only supported value is \`exact_balance\`. By default, no connection balancing is used. + Refer to the + [Envoy Connection Balance config](https://cloudnative.to/envoy/api-v3/config/listener/v3/listener.proto.html#config-listener-v3-listener-connectionbalanceconfig) + for details.` + }, { name: 'Limits', type: 'Limits: ', @@ -587,6 +605,15 @@ represents a location outside the Consul cluster. They can be dialed directly wh }, ], }, + { + name: 'BalanceOutboundConnections', + type: `string: ""`, + description: `Sets the strategy for allocating outbound connections from the upstream across proxy threads. + The only supported value is \`exact_balance\`. By default, no connection balancing is used. + Refer to the + [Envoy Connection Balance config](https://cloudnative.to/envoy/api-v3/config/listener/v3/listener.proto.html#config-listener-v3-listener-connectionbalanceconfig) + for details.` + }, { name: 'Limits', type: 'Limits: ', diff --git a/website/content/docs/connect/config-entries/service-router.mdx b/website/content/docs/connect/config-entries/service-router.mdx index 29ea6f046a..5f3722f51e 100644 --- a/website/content/docs/connect/config-entries/service-router.mdx +++ b/website/content/docs/connect/config-entries/service-router.mdx @@ -303,7 +303,7 @@ Routes = [ Match{ HTTP { PathPrefix = "/coffees" - } + } } Destination { @@ -311,13 +311,14 @@ Routes = [ RequestTimeout = "10s" NumRetries = 3 RetryOnConnectFailure = true + RetryOn = ["reset"] } }, { Match{ HTTP { PathPrefix = "/orders" - } + } } Destination { @@ -325,6 +326,7 @@ Routes = [ RequestTimeout = "10s" NumRetries = 3 RetryOnConnectFailure = true + RetryOn = ["reset"] } } ] @@ -345,6 +347,7 @@ spec: requestTimeout: 10s numRetries: 3 retryOnConnectFailure: true + retryOn: ['reset'] - match: http: pathExact: /orders @@ -353,7 +356,7 @@ spec: requestTimeout: 10s numRetries: 3 retryOnConnectFailure: true - + retryOn: ['reset'] ``` @@ -372,6 +375,7 @@ spec: "NumRetries": 3, "RequestTimeout": "10s", "RetryOnConnectFailure": true, + "RetryOn": ["reset"], "Service": "procurement" } }, @@ -385,6 +389,7 @@ spec: "NumRetries": 3, "RequestTimeout": "10s", "RetryOnConnectFailure": true, + "RetryOn": ["reset"], "Service": "procurement" } } @@ -702,6 +707,13 @@ spec: type: 'bool: false', description: 'Allows for connection failure errors to trigger a retry.', }, + { + name: 'RetryOn', + type: 'array', + description: `Allows Consul to retry requests when they meet one of the following sets of conditions: + \`5xx\`, \`gateway-error\`, \`reset\`, \`connect-failure\`, \`envoy-ratelimited\`, \`retriable-4xx\`, + \`refused-stream\`, \`cancelled\`, \`deadline-exceeded\`, \`internal\`, \`resource-exhausted\`, or \`unavailable\``, + }, { name: 'RetryOnStatusCodes', type: 'array', diff --git a/website/content/docs/connect/dataplane/consul-dataplane.mdx b/website/content/docs/connect/dataplane/consul-dataplane.mdx new file mode 100644 index 0000000000..d432aadaee --- /dev/null +++ b/website/content/docs/connect/dataplane/consul-dataplane.mdx @@ -0,0 +1,130 @@ +--- +layout: docs +page_title: Consul Dataplane CLI Reference +description: >- + Consul Dataplane runs as a separate binary controlled with the `consul-dataplane` CLI command. Learn how to use this command to configure your dataplane on Kubernetes with this reference guide and example code. +--- + +# Consul Dataplane CLI Reference + +The `consul-dataplane` command interacts with the binary for [simplified service mesh with Consul Dataplane](/consul/docs/k8s/dataplane/index). Use this command to install Consul Dataplane, configure its Envoy proxies, and secure Dataplane deployments. + +## Usage + +Usage: `consul-dataplane [options]` + +### Requirements + +Consul Dataplane requires servers running Consul version `v1.14-beta+`. To find a specific version of Consul, refer to [Hashicorp's Official Release Channels](https://www.hashicorp.com/official-release-channels). + +### Startup + +The following options are required when starting `consul-dataplane` with the CLI: + +- `-addresses` +- `-service-node-name` +- `-proxy-service-id` + +### Command Options + +- `-addresses` - Consul server gRPC addresses. Can be a DNS name or an executable command. Refer to [go-netaddrs](https://github.com/hashicorp/go-netaddrs#summary) for details and examples. +- `-ca-certs` - The path to a file or directory containing CA certificates used to verify the server's certificate. +- `-credential-type` - The type of credentials used to authenticate with Consul servers, either `"static"` or `"login"`. +- `-envoy-admin-bind-address` - The address the Envoy admin server is available on. Default is `"127.0.0.1"`. +- `-envoy-admin-bind-port` - The port the Envoy admin server is available on. Default is `19000`. +- `-envoy-concurrency` - The number of worker threads that Envoy uses. Default is `2`. +- `-envoy-ready-bind-address` - The address Envoy's readiness probe is available on. +- `-grpc-port` - The Consul server gRPC port to which consul-dataplane connects. Default is `8502`. +- `-log-json` - Enables log messages in JSON format. Default is `false`. +- `-log-level` - Log level of the messages to print. Available log levels are `"trace"`, `"debug"`, `"info"`, `"warn"`, and `"error"`. Default is `"info"`. +- `-login-auth-method` - The auth method used to log in. +- `-login-bearer-token` - The bearer token presented to the auth method. +- `-login-bearer-token-path` - The path to a file containing the bearer token presented to the auth method. +- `-login-datacenter` - The datacenter containing the auth method. +- `-login-meta` - A set of key/value pairs to attach to the ACL token. Each pair is formatted as `=`. This flag may be passed multiple times. +- `-login-namespace` - The Consul Enterprise namespace containing the auth method. +- `-login-partition` - The Consul Enterprise partition containing the auth method. +- `-proxy-service-id` - The proxy service instance's ID. +- `-server-watch-disabled` - Prevent `consul-dataplane` from consuming the server update stream. Use this flag when Consul servers are behind a load balancer. Default is `false`. +- `-service-namespace` - The Consul Enterprise namespace in which the proxy service instance is registered. +- `-service-node-id` - The ID of the Consul node to which the proxy service instance is registered. +- `-service-node-name` - The name of the Consul node to which the proxy service instance is registered. +- `-service-partition` - The Consul Enterprise partition in which the proxy service instance is registered. +- `-static-token` - The ACL token used to authenticate requests to Consul servers when `-credential-type` is set to `"static"`. +- `-telemetry-use-central-config` - Controls whether the proxy applies the central telemetry configuration. Default is `true`. +- `-tls-cert` - The path to a client certificate file. This flag is required if `tls.grpc.verify_incoming` is enabled on the server. +- `-tls-disabled` - Communicate with Consul servers over a plaintext connection. Useful for testing, but not recommended for production. Default is `false`. +- `-tls-insecure-skip-verify` - Do not verify the server's certificate. Useful for testing, but not recommended for production. Default is `false`. +- `-tls-key` - The path to a client private key file. This flag is required if `tls.grpc.verify_incoming` is enabled on the server. +- `-tls-server-name` - The hostname to expect in the server certificate's subject. This flag is required if `-addresses` is not a DNS name. +- `-version` - Print the current version of `consul-dataplane`. +- `-xds-bind-addr` - The address the Envoy xDS server is available on. Default is `"127.0.0.1"`. + +## Examples + +### DNS + +Consul Dataplane resolves a domain name to discover Consul server IP addresses. + + ```shell-session + $ consul-dataplane -addresses my.consul.example.com + ``` + +### Executable Command + +Consul Dataplane runs a script that, on success, returns one or more IP addresses separated by whitespace. + + ```shell-session + $ ./my-script.sh + 172.20.0.1 + 172.20.0.2 + 172.20.0.3 + + $ consul-dataplane -addresses "exec=./my-script.sh" + ``` + +### Go Discover Nodes for Cloud Providers + +The [`go-discover`](https://github.com/hashicorp/go-discover) binary is included in the `hashicorp/consul-dataplane` image for use with this mode of server discovery, which functions in + a way similar to [Cloud Auto-join](/consul/docs/install/cloud-auto-join). The + following example demonstrates how to use the `go-discover` binary with Consul Dataplane. + + ```shell-session + $ consul-dataplane -addresses "exec=discover -q addrs provider=aws region=us-west-2 tag_key=consul-server tag_value=true" + ``` + +### Static token + +A static ACL token is passed to Consul Dataplane. + + ```shell-session + $ consul-dataplane -credential-type "static"` -static-token "12345678-90ab-cdef-0000-12345678abcd" + ``` + +### Auth method login + +Consul Dataplane logs in to one of Consul's supported [auth methods](/consul/docs/security/acl/auth-methods). + + ```shell-session + $ consul-dataplane -credential-type "login" + -login-auth-method \ + -login-bearer-token \ ## Or -login-bearer-token-path + -login-datacenter \ + -login-meta key1=val1 -login-meta key2=val2 \ + -login-namespace \ + -login-partition + ``` + +### Consul Servers Behind a Load Balancer + +When Consul servers are behind a load balancer, you must pass `-server-watch-disabled` to Consul +Dataplane. + +```shell-session +$ consul-dataplane -server-watch-disabled +``` + +By default, Consul Dataplane opens a server watch stream to a Consul server, which enables the server +to inform Consul Dataplane of new or different Consul server addresses. However, if Consul Dataplane +is connecting through a load balancer, then it must ignore the Consul server addresses that are +returned from the server watch stream. \ No newline at end of file diff --git a/website/content/docs/connect/dataplane/index.mdx b/website/content/docs/connect/dataplane/index.mdx new file mode 100644 index 0000000000..9101fc6b29 --- /dev/null +++ b/website/content/docs/connect/dataplane/index.mdx @@ -0,0 +1,78 @@ +--- +layout: docs +page_title: Simplified Service Mesh with Consul Dataplane +description: >- + Consul Dataplane removes the need to a run client agent for service discovery and service mesh by leveraging orchestrator functions. Learn about Consul Dataplane, how it can lower latency for Consul on Kubernetes, and how it enables Consul support for AWS Fargate and GKE Autopilot. +--- + +# Simplified Service Mesh with Consul Dataplane + +~> **Consul Dataplane is currently in beta:** Functionality associated with Consul Dataplane is subject to change. You should never use the beta release in secure environments or production scenarios. Features in beta may have performance issues, scaling issues, and limited support. + +This topic provides an overview of Consul Dataplane, a lightweight process for managing Envoy proxies introduced in Consul v1.14.0. Consul Dataplane removes the need to run client agents on every node in a cluster for service discovery and service mesh. Instead, Consul deploys sidecar proxies that provide lower latency, support additional runtimes, and integrate with cloud infrastructure providers. + +Consul Dataplane requires servers running Consul v1.14.0-beta1+. + +## What is Consul Dataplane? + +In standard deployments, Consul uses a control plane that contains both *server agents* and *client agents*. Server agents maintain the service catalog and service mesh, including its security and consistency, while client agents manage communications between service instances, their sidecar proxies, and the servers. While this model is optimal for applications deployed on virtual machines or bare metal servers, orchestrators such as Kubernetes already include components called *kubelets* that support health checking and service location functions typically provided by the client agent. + +Consul Dataplane manages Envoy proxies and leaves responsibility for other functions to the orchestrator. As a result, it removes the need to run client agents on every pod. In addition, services no longer need to be reregistered to a local client agent after restarting a service instance, as a client agent’s lack of access to persistent data storage in Kubernetes deployments is no longer an issue. + +## Benefits + +**Fewer networking requirements**: Without client agents, Consul does not require bidirectional network connectivity across multiple protocols to enable gossip communication. Instead, it requires a single gRPC connection to the Consul servers, which significantly simplifies requirements for the operator. + +**Simplified set up**: Because there are no client agents to engage in gossip, you do not have to generate and distribute a gossip encryption key to agents during the initial bootstrapping process. Securing agent communication also becomes simpler, with fewer tokens to track, distribute, and rotate. + +**Additional environment and runtime support**: Current Consul on Kubernetes deployments require using `hostPorts` and `DaemonSets` for client agents, which limits Consul’s ability to be deployed in environments where those features are not supported. As a result, Consul Dataplane supports AWS Fargate and GKE Autopilot. + +**Easier upgrades**: With Consul Dataplane, updating Consul to a new version no longer requires upgrading client agents. Consul Dataplane also has better compatibility across Consul server versions, so the process to upgrade Consul servers becomes easier. + +## Get started + +To get started with Consul Dataplane, use the following reference resources: + +- For `consul-dataplane` commands and usage examples, including required flags for startup, refer to the [`consul-dataplane` CLI reference](/docs/connect/dataplane/consul-dataplane). +- For Helm chart information, refer to the [Helm Chart reference](/docs/k8s/helm). +- For Envoy, Consul, and Consul Dataplane version compatibility, refer to the [Envoy compatibility matrix](/docs/connect/proxies/envoy). + +### Installation + +To install the beta release of Consul Dataplane, set `VERSION` to `1.0.0-beta` and then follow the instructions to install a specific version of Consul [with the Helm Chart](/docs/k8s/installation/install#install-consul) or [with the Consul-k8s CLI](/docs/k8s/installation/install-cli#install-a-previous-version). + +#### Helm + +```shell-session +$ export VERSION=1.0.0-beta +$ helm install consul hashicorp/consul --set global.name=consul --version ${VERSION} --create-namespace --namespace consul +``` + +#### Consul-k8s CLI + +```shell-session +$ export VERSION=1.0.0-beta && \ + curl --location "https://releases.hashicorp.com/consul-k8s/${VERSION}/consul-k8s_${VERSION}_darwin_amd64.zip" --output consul-k8s-cli.zip +``` + +## Beta release features + +The beta release of Consul Dataplane supports the following features: + +- Single and multi-cluster installations, including those with WAN federation, cluster peering, and admin partitions are supported. +- Ingress, terminating, and mesh gateways are supported. +- Running Consul service mesh in AWS Fargate and GKE Autopilot is supported. +- xDS load balancing is supported. +- Servers running in Kubernetes and servers external to Kubernetes are both supported. + +Integration with HCP Consul is being tested in an invitation-only closed beta. HCP Consul support for Dataplane will be available for all users in a future release. + +### Technical Constraints + +Be aware of the following limitations and recommendations for Consul Dataplane: + +- Metrics and telemetry are not currently available for services deployed with Dataplane. +- Consul API Gateway is not currently supported. +- Transparent proxies are not supported. +- Using `proxy-defaults` and `service-defaults` to configure default proxy behavior is not supported. +- Consul Dataplane is not supported on Windows. diff --git a/website/content/docs/connect/gateways/mesh-gateway/service-to-service-traffic-peers.mdx b/website/content/docs/connect/gateways/mesh-gateway/service-to-service-traffic-peers.mdx index ad961fbf68..c971c0a275 100644 --- a/website/content/docs/connect/gateways/mesh-gateway/service-to-service-traffic-peers.mdx +++ b/website/content/docs/connect/gateways/mesh-gateway/service-to-service-traffic-peers.mdx @@ -11,7 +11,7 @@ description: >- Mesh gateways are required for you to route service mesh traffic between different Consul clusters. Clusters can reside in different clouds or runtime environments where general interconnectivity between all services in all clusters is not feasible. -Unlike mesh gateways for datacenters and partitions, mesh gateways for cluster peering decrypt data to HTTP services within the mTLS session. Data must be decrypted in order to evaluate and apply dynamic routing rules at the destination cluster, which reduces coupling between peers. +Unlike mesh gateways for datacenters and partitions, mesh gateways between peers terminate mTLS sessions to decrypt data to HTTP services and then re-encrypt traffic to send to services. Data must be decrypted in order to evaluate and apply dynamic routing rules at the destination cluster, which reduces coupling between peers. ## Prerequisites diff --git a/website/content/docs/connect/proxies/envoy.mdx b/website/content/docs/connect/proxies/envoy.mdx index 4c6acef39a..88f996f124 100644 --- a/website/content/docs/connect/proxies/envoy.mdx +++ b/website/content/docs/connect/proxies/envoy.mdx @@ -33,17 +33,29 @@ Envoy must be run with the `--max-obj-name-len` option set to `256` or greater f The following matrix describes Envoy compatibility for the currently supported **n-2 major Consul releases**. For previous Consul version compatibility please view the respective versioned docs for this page. +### Envoy and Consul Client Agent + Consul supports **four major Envoy releases** at the beginning of each major Consul release. Consul maintains compatibility with Envoy patch releases for each major version so that users can benefit from bug and security fixes in Envoy. As a policy, Consul will add support for a new major versions of Envoy in a Consul major release. Support for newer versions of Envoy will not be added to existing releases. | Consul Version | Compatible Envoy Versions | | ------------------- | -----------------------------------------------------------------------------------| -| 1.13.x | 1.23.1, 1.22.2, 1.21.4, 1.20.6 | -| 1.12.x | 1.22.2, 1.21.4, 1.20.6, 1.19.5 | -| 1.11.x | 1.20.6, 1.19.5, 1.18.6, 1.17.41 | +| 1.13.x | 1.23.1, 1.22.5, 1.21.5, 1.20.7 | +| 1.12.x | 1.22.5, 1.21.5, 1.20.7, 1.19.5 | +| 1.11.x | 1.20.7, 1.19.5, 1.18.6, 1.17.41 | 1. Envoy 1.20.1 and earlier are vulnerable to [CVE-2022-21654](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2022-21654) and [CVE-2022-21655](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2022-21655). Both CVEs were patched in Envoy versions 1.18.6, 1.19.3, and 1.20.2. Envoy 1.16.x and older releases are no longer supported (see [HCSEC-2022-07](https://discuss.hashicorp.com/t/hcsec-2022-07-consul-s-connect-service-mesh-affected-by-recent-envoy-security-releases/36332)). Consul 1.9.x clusters should be upgraded to 1.10.x and Envoy upgraded to the latest supported Envoy version for that release, 1.18.6. +### Envoy and Consul Dataplane + +~> **Note:** Consul Dataplane is currently in beta. + +Consul Dataplane is a feature introduced in Consul v1.14. Because each version of Consul Dataplane supports one specific version of Envoy, you must use the following versions of Consul, Consul Dataplane, and Envoy together. + +| Consul Version | Consul Dataplane Version | Bundled Envoy Version | +| ------------------- | ------------------------ | ---------------------- | +| 1.14.x | 1.0.x | 1.23.x | + ## Getting Started To get started with Envoy and see a working example you can follow the [Using @@ -112,7 +124,7 @@ Envoy requires an initial bootstrap configuration file. The easiest way to create this is using the [`consul connect envoy` command](/commands/connect/envoy). The command can either output the bootstrap configuration directly to stdout, or generate the configuration and issue an `exec` command -to the Envoy binary as a convenience wrapper. +to the Envoy binary as a convenience wrapper. For more information about using `exec` to bootstrap Envoy, refer to [Exec Security Details](/consul/commands/connect/envoy#exec-security-details). Because some Envoy configuration options, such as metrics and tracing sinks, can only be specified via the bootstrap configuration, Connect as of Consul 1.5.0 adds @@ -174,6 +186,67 @@ definition](/docs/connect/registration/service-registration) or The [Advanced Configuration](#advanced-configuration) section describes additional configurations that allow incremental or complete control over the bootstrap configuration generated. +### Bootstrap Envoy on Windows VMs + +> Complete the [Connect Services on Windows Workloads to Consul Service Mesh tutorial](https://learn.hashicorp.com/tutorials/consul/consul-windows-workloads?utm_source=docs) to learn how to deploy Consul and use its service mesh on Windows VMs. + +If you are running Consul on a Windows VM, attempting to bootstrap Envoy with the `consul connect envoy` command returns the following output: + +```shell-session hideClipboard +Directly running Envoy is only supported on linux and macOS since envoy itself doesn't build on other plataforms currently. +Use the -bootstrap option to generate the JSON to use when running envoy on a supported OS or via a container or VM. +``` + +To bootstrap Envoy on Windows VMs, you must generate the bootstrap configuration as a .json file and then manually edit it to add both your ACL token and a valid access log path. + +To generate the bootstrap configuration file, add the `-bootstrap` option to the command and then save the output to a file: + +```shell-session +$ consul connect envoy -bootstrap > bootstrap.json +``` + +Then, open `bootstrap.json` and update the following sections with your ACL token and log path. + + + +```json + "admin": { + "access_log_path": "/dev/null", + "address": { + "socket_address": { + "address": "127.0.0.1", + "port_value": 19000 + } + } + }, + ## ... + "dynamic_resources": { + ## ... + "ads_config": { + ## ... + "grpc_services": { + "initial_metadata": [ + { + "key": "x-consul-token", + "value": "" + } + ], + ## ... + } + } + } +} +``` + + +To complete the bootstrap process, start Envoy and include the path to `bootstrap.json`: + +```shell-session +$ envoy -c bootstrap.json +``` + +~> **Security Note**: The bootstrap JSON contains the ACL token and should be handled as a secret. Because this token authorizes the identity of any service it has `service:write` permissions for, it can be used to access upstream services. + ## Dynamic Configuration Consul automatically generates Envoy's dynamic configuration based on its @@ -254,6 +327,14 @@ defaults that are inherited by all services. specified, inherits the Envoy default for route timeouts (15s). A value of 0 will disable request timeouts. +- `balance_inbound_connections` - The strategy used for balancing inbound connections + across Envoy worker threads. Consul service mesh Envoy integration supports the + following `balance_inbound_connections` values: + + - `""` - Empty string (default). No connection balancing strategy is used. Consul does not balance inbound connections. + - `exact_balance` - Inbound connections to the service use the + [Envoy Exact Balance Strategy.](https://cloudnative.to/envoy/api-v3/config/listener/v3/listener.proto.html#config-listener-v3-listener-connectionbalanceconfig-exactbalance) + ### Proxy Upstream Config Options The following configuration items may be overridden directly in the @@ -310,9 +391,17 @@ definition](/docs/connect/registration/service-registration) or load balancer. - `max_failures` - The number of consecutive failures which cause a host to be removed from the load balancer. - - `enforcing_consecutive_5xx` - The % chance that a host will be actually ejected + - `enforcing_consecutive_5xx` - The % chance that a host will be actually ejected when an outlier status is detected through consecutive 5xx. +- `balance_outbound_connections` - Specifies the strategy for balancing outbound connections + across Envoy worker threads. Consul service mesh Envoy integration supports the + following `balance_outbound_connections` values: + + - `""` - Empty string (default). No connection balancing strategy is used. Consul does not balance outbound connections. + - `exact_balance` - Outbound connections from the upstream use the + [Envoy Exact Balance Strategy.](https://cloudnative.to/envoy/api-v3/config/listener/v3/listener.proto.html#config-listener-v3-listener-connectionbalanceconfig-exactbalance) + ### Gateway Options These fields may also be overridden explicitly in the [proxy service @@ -762,7 +851,7 @@ definition](/docs/connect/registration/service-registration) or -- `envoy_listener_tracing_json` - Specifies a [tracing +- `envoy_listener_tracing_json` - Specifies a [tracing configuration](https://www.envoyproxy.io/docs/envoy/latest/api-v3/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto#envoy-v3-api-msg-extensions-filters-network-http-connection-manager-v3-httpconnectionmanager-tracing) to be inserted in the proxy's public and upstreams listeners. diff --git a/website/content/docs/consul-vs-other/api-gateway-compare.mdx b/website/content/docs/consul-vs-other/api-gateway-compare.mdx new file mode 100644 index 0000000000..126f027ff6 --- /dev/null +++ b/website/content/docs/consul-vs-other/api-gateway-compare.mdx @@ -0,0 +1,16 @@ +--- +layout: docs +page_title: Consul Compared to Other API Gateways +description: >- + The Consul API Gateway is an implementation of the Kubernetes Gateway API that provides a single entry point that routes public requests to services within the service mesh. +--- + +# Consul Compared to Other API Gateways + +**Examples**: Kong Gateway, Apigee, Mulesoft, Gravitee + +The [Consul API Gateway](/docs/api-gateway) is an implementation of the [Kubernetes Gateway API](https://gateway-api.sigs.k8s.io/). Traditionally, API gateways are used for two things: _client traffic management_ and _API lifecycle management_. + +Client traffic management refers to an API gateway's role in controlling the point of entry for public traffic into a given environment, also known as _managing north-south traffic_. The Consul API Gateway is deployed alongside Consul service mesh and is responsible for routing inbound client requests to the mesh based on defined routes. For a full list of supported traffic management features, refer to the [Consul API Gateway documentation](/docs/api-gateway). + +API lifecycle management refers to how application developers use an API gateway to deploy, iterate, and manage versions of an API. At this time, the Consul API Gateway does not support API lifecycle management. diff --git a/website/content/docs/consul-vs-other/config-management-compare.mdx b/website/content/docs/consul-vs-other/config-management-compare.mdx new file mode 100644 index 0000000000..14de4933c4 --- /dev/null +++ b/website/content/docs/consul-vs-other/config-management-compare.mdx @@ -0,0 +1,23 @@ +--- +layout: docs +page_title: Consul Compared to Other Configuration Management Tools +description: >- + Chef, Puppet, and other configuration management tools build service discovery mechanisms by querying global state and constructing configuration files on each node during a periodic convergence run. +--- + +# Consul Compared to Other Configuration Management Tools + +**Examples**: Chef, Puppet + +There are many configuration management tools, however, they typically focus on static provisioning. Consul enables you to dynamically configure your services based on service and node state. Both static and dynamic configuration are important and work well together. Since Consul offers a number of different capabilities, there are times when its functionality overlaps with other configuration management tools. + +For example, Chef and Puppet are configuration management tools that can build service discovery mechanisms. However, they only support configuration information that is static. As a result, the time it takes to implement updates depends on the frequency of conversion runs (several minutes to hours). Additionally, these tools do not let you incorporate the system state in the configuration. This could lead to load balancers sending traffic to unhealthy nodes, further exacerbating issues. Supporting multiple datacenters is also challenging with these tools, since a central group of servers must manage all datacenters. + +Consul's service discovery layer is specifically designed to dynamically track and respond to your service's state. By using the integrated health checking, Consul can route traffic away from unhealthy nodes, allowing systems and services to gracefully recover. In addition, Consul’s service discovery layer works with Terraform. Consul-Terraform-Sync (CTS) automates updates to network infrastructure based on dynamic changes to each service. For example, as services scale up or down, CTS can trigger Terraform to update firewalls or load balancers to reflect the latest changes. Also, since each datacenter runs independently, supporting multiple datacenters is no different than supporting a single datacenter. + +Consul is not a replacement for other configuration management tools. These tools are still critical for setting up applications, including Consul. Static provisioning is best managed by existing tools, while Consul enables you to leverage dynamic configuration and service discovery. + +By separating configuration management and cluster management tools, you can take advantage of simpler workflows: +- Periodic runs are no longer required for service or configuration changes. +- Chef recipes and Puppet manifests are simpler because they do not require a global state. +- Infrastructure can become immutable because configuration management runs do not require global state. diff --git a/website/content/docs/consul-vs-other/dns-tools-compare.mdx b/website/content/docs/consul-vs-other/dns-tools-compare.mdx new file mode 100644 index 0000000000..f4fdf981d4 --- /dev/null +++ b/website/content/docs/consul-vs-other/dns-tools-compare.mdx @@ -0,0 +1,16 @@ +--- +layout: docs +page_title: Consul Compared to Other DNS Tools +description: >- + Service discovery is one of Consul's foundational capabilities. Consul is platform agnostic, which allows it to discover services across multiple runtimes and cloud providers including VMs, bare-metal, Kubernetes, Nomad, EKS, AKS, ECS, and Lambda. +--- + + +# Consul Compared to Other DNS Tools + +**Examples**: NS1, AWS Route53, AzureDNS, Cloudflare DNS + +Consul was originally designed as a centralized service registry for any cloud environment that dynamically tracks services as they are added, changed, or removed within a compute infrastructure. Consul maintains a catalog of these registered services and their attributes, such as IP addresses or service name. For more information, refer to [What is Service Discovery?(/docs/intro/usecases/what-is-service-discovery). + +As a result, Consul can also provide basic DNS functionality, including [lookups, alternate domains, and access controls](/docs/discovery/dns). Since Consul is platform agnostic, you can retrieve service information across both cloud and on-premises data centers. Consul does not natively support some advanced DNS capabilities, such as filters or advanced routing logic. However, you can integrate Consul with existing DNS solutions, such as [NS1](https://help.ns1.com/hc/en-us/articles/360039417093-NS1-Consul-Integration-Overview) and [DNSimple](https://blog.dnsimple.com/2022/05/consul-integration/), to support these advanced capabilities. + diff --git a/website/content/docs/consul-vs-other/index.mdx b/website/content/docs/consul-vs-other/index.mdx new file mode 100644 index 0000000000..b20e9df3de --- /dev/null +++ b/website/content/docs/consul-vs-other/index.mdx @@ -0,0 +1,15 @@ +--- +layout: docs +page_title: Why Choose Consul? +description: >- + Consul is a service networking platform that centralizes service discovery, enables zero trust networking with service mesh, automates network infrastructure, and controls access to mesh services with the Consul API Gateway. Compare Consul with other software that provide similar capabilities with one or more of the core use cases. +--- + +# Why Choose Consul? + +HashiCorp Consul is a service networking platform that encompasses multiple capabilities to secure and simplify network service management. These capabilities include service mesh, service discovery, configuration management, and API gateway functionality. While competing products offer a few of these core capabilities, Consul is developed to address all four. The topics in this section provide a general overview of how Consul’s capabilities compare to some other tools on the market. Visit the following pages to read more about how: + +- [Consul compares with other service meshes](/docs/consul-vs-other/service-mesh-compare) +- [Consul compares with other DNS tools](/docs/consul-vs-other/dns-tools-compare) +- [Consul compares with other configuration management tools](/docs/consul-vs-other/config-management-compare) +- [Consul compares with other API Gateways](/docs/consul-vs-other/api-gateway-compare) diff --git a/website/content/docs/consul-vs-other/service-mesh-compare.mdx b/website/content/docs/consul-vs-other/service-mesh-compare.mdx new file mode 100644 index 0000000000..b0848d2b90 --- /dev/null +++ b/website/content/docs/consul-vs-other/service-mesh-compare.mdx @@ -0,0 +1,18 @@ +--- +layout: docs +page_title: Consul compared to other service meshes +description: >- + Consul's service mesh provides zero trust networking based on service identities to authorize, authenticate, and encrypt network services. Consul's service mesh can also provide advanced traffic management capabilties. Although there are many similar capabilities between Consul and other providers like Istio, Solo, Linkerd, Kong, Tetrate, and AWS App Mesh, we highlight the main differentiating factors for help customers compare. +--- + +# Consul compared to other service mesh software + +**Examples**: Istio, Solo Gloo Mesh, Linkerd, Kong/Kuma, AWS App Mesh + +Consul’s service mesh allows organizations to securely connect and manage their network services across multiple different environments. Using Envoy as the sidecar proxy attached to every service, Consul ensures that all service-to-service communication is authorized, authenticated, and encrypted. Consul includes traffic management capabilities like load balancing and traffic splitting, which help developers perform canary testing, A/B tests, and blue/green deployments. Consul also includes health check and observability features. + +Consul is platform agnostic — it supports any runtime (Kubernetes, EKS, AKS, GKE, VMs, ECS, Lambda, Nomad) and any cloud provider (AWS, Microsoft Azure, GCP, private clouds). This makes it one of the most flexible service discovery and service mesh platforms. While other service mesh software provides support for multiple runtimes for the data plane, they require you to run the control plane solely on Kubernetes. With Consul, you can run both the control plane and data plane in different runtimes. + +Consul also has several unique integrations with Vault, an industry standard for secrets management. Operators have the option to use Consul’s built-in certificate authority, or leverage Vault’s PKI engine to generate and store TLS certificates for both the data plane and control plane. In addition, Consul can automatically rotate the TLS certificates on both the data plane and control plane without requiring any type of restarts. This lets you rotate the certificates more frequently without incurring additional management burden on operators. +When deploying Consul on Kubernetes, you can store sensitive data including licenses, ACL tokens, and TLS certificates centrally Vault instead of Kubernetes secrets. Vault is much more secure than Kubernetes secrets because it automatically encrypts all data, provides advanced access controls to secrets, and provides centralized governance for all secrets. + diff --git a/website/content/docs/ecs/index.mdx b/website/content/docs/ecs/index.mdx index 3e70e8a7a1..8dc22fccca 100644 --- a/website/content/docs/ecs/index.mdx +++ b/website/content/docs/ecs/index.mdx @@ -30,7 +30,7 @@ For a detailed architecture overview, see the [Architecture](/docs/ecs/architect There are several ways to get started with Consul with ECS. -- The [Serverless Consul Service Mesh with ECS and HCP](https://learn.hashicorp.com/tutorials/consul/consul-ecs-hcp?utm_source=docs) learn guide shows how to use Terraform to run Consul service mesh applications on ECS with managed Consul servers running in HashiCorp Cloud Platform (HCP). +- The [Serverless Consul Service Mesh with ECS and HCP](https://learn.hashicorp.com/tutorials/cloud/consul-ecs-hcp?utm_source=docs) learn guide shows how to use Terraform to run Consul service mesh applications on ECS with managed Consul servers running in HashiCorp Cloud Platform (HCP). - The [Service Mesh with ECS and Consul on EC2](https://learn.hashicorp.com/tutorials/consul/consul-ecs-ec2?utm_source=docs) learn guide shows how to use Terraform to run Consul service mesh applications on ECS with Consul servers running on EC2 instances. - The [Consul with Dev Server on Fargate](https://registry.terraform.io/modules/hashicorp/consul-ecs/aws/latest/examples/dev-server-fargate) example installation deploys a sample application in ECS using the Fargate launch type. - The [Consul with Dev Server on EC2](https://registry.terraform.io/modules/hashicorp/consul-ecs/aws/latest/examples/dev-server-ec2) example installation deploys a sample application in ECS using the EC2 launch type. diff --git a/website/content/docs/enterprise/admin-partitions.mdx b/website/content/docs/enterprise/admin-partitions.mdx index 690ec7ec05..53a89eaddf 100644 --- a/website/content/docs/enterprise/admin-partitions.mdx +++ b/website/content/docs/enterprise/admin-partitions.mdx @@ -104,6 +104,7 @@ One of the primary use cases for admin partitions is for enabling a service mesh - If you are deploying Consul servers on Kubernetes, then ensure that the Consul servers are deployed within the same Kubernetes cluster. Consul servers may be deployed external to Kubernetes and configured using the `externalServers` stanza. - Consul clients deployed on the same Kubernetes cluster as the Consul Servers must use the `default` partition. If the clients are required to run on a non-default partition, then the clients must be deployed in a separate Kubernetes cluster. +- For Kubernetes clusters that join the Consul datacenter as admin partitions, ensure that a unique `global.name` value is assigned for the corresponding Helm `values.yaml` file. - A Consul Enterprise license must be installed on each Kubernetes cluster. - The helm chart for consul-k8s v0.39.0 or greater. - Consul 1.11.1-ent or greater. @@ -116,7 +117,7 @@ One of the primary use cases for admin partitions is for enabling a service mesh ## Usage -This section describes how to deploy Consul admin partitions to Kubernetes clusters. Refer to the [admin partition CLI documentation](/commands/admin-partition) for information about command line usage. +This section describes how to deploy Consul admin partitions to Kubernetes clusters. Refer to the [admin partition CLI documentation](/commands/partition) for information about command line usage. ### Deploying Consul with Admin Partitions on Kubernetes @@ -171,7 +172,7 @@ Verify that your Consul deployment meets the [Kubernetes Requirements](#kubernet enableConsulNamespaces: true tls: enabled: true - image: hashicorp/consul-enterprise:1.12.0-ent + image: hashicorp/consul-enterprise:1.13.2-ent adminPartitions: enabled: true acls: @@ -203,7 +204,7 @@ Verify that your Consul deployment meets the [Kubernetes Requirements](#kubernet 1. Install the Consul server(s) using the values file created in the previous step: ```shell-session - $ helm install ${HELM_RELEASE_SERVER} hashicorp/consul --version "0.43.0" --create-namespace --namespace consul --values server.yaml + $ helm install ${HELM_RELEASE_SERVER} hashicorp/consul --version "0.49.0" --create-namespace --namespace consul --values server.yaml ``` 1. After the server starts, get the external IP address for partition service so that it can be added to the client configuration. The IP address is used to bootstrap connectivity between servers and clients. @@ -248,7 +249,7 @@ Verify that your Consul deployment meets the [Kubernetes Requirements](#kubernet ``` 1. Create the workload configuration for client nodes in your cluster. Create a configuration for each admin partition. - In the following example, the external IP address and the Kubernetes authentication method IP address from the previous steps have been applied. Also, ensure a unique global name is assigned. + In the following example, the external IP address and the Kubernetes authentication method IP address from the previous steps have been applied. Also, ensure a unique `global.name` value is assigned. @@ -259,7 +260,7 @@ Verify that your Consul deployment meets the [Kubernetes Requirements](#kubernet name: client enabled: false enableConsulNamespaces: true - image: hashicorp/consul-enterprise:1.12.0-ent + image: hashicorp/consul-enterprise:1.13.2-ent adminPartitions: enabled: true name: clients @@ -308,7 +309,7 @@ Verify that your Consul deployment meets the [Kubernetes Requirements](#kubernet 1. Install the workload client clusters: ```shell-session - $ helm install ${HELM_RELEASE_CLIENT} hashicorp/consul --version "0.43.0" --create-namespace --namespace consul --values client.yaml + $ helm install ${HELM_RELEASE_CLIENT} hashicorp/consul --version "0.49.0" --create-namespace --namespace consul --values client.yaml ``` ### Verifying the Deployment diff --git a/website/content/docs/enterprise/license/faq.mdx b/website/content/docs/enterprise/license/faq.mdx index 9650b24e03..5bb096d08e 100644 --- a/website/content/docs/enterprise/license/faq.mdx +++ b/website/content/docs/enterprise/license/faq.mdx @@ -64,7 +64,7 @@ can operate without a license. ## Q: Is there a grace period when licenses expire? -**Yes—Consul will continue normal operation after license *expiration*** as defined in +**Yes—Consul will continue normal operation and can be restarted after license *expiration*** as defined in [`expiration_time`](/api-docs/operator/license#getting-the-consul-license). As license expiration is approached or passed, Consul will issue warnings in the system logs. @@ -105,13 +105,11 @@ Visit [consul.io/trial](https://www.hashicorp.com/products/consul/trial) for a f ## Q: How can I renew a license? -Contact your organization's HashiCorp account team or email support-softwaredelivery@hashicorp.com -for information on how to renew your organization's enterprise license. +Contact your organization's [HashiCorp account team](https://support.hashicorp.com/hc/en-us) for information on how to renew your organization's enterprise license. ## Q: I'm an existing enterprise customer but don't have my license, how can I get it? -Contact your organization's HashiCorp account team or email support-softwaredelivery@hashicorp.com -for information on how to renew your organization's enterprise license. +Contact your organization's [HashiCorp account team](https://support.hashicorp.com/hc/en-us) for information on how to renew your organization's enterprise license. ## Q: Are the license files locked to a specific cluster? @@ -161,7 +159,7 @@ Once you have the license then create a Kubernetes secret containing the license ### VM -1. Acquire a valid Consul Enterprise license. If you are an existing HashiCorp enterprise customer you may contact your organization's customer success manager (CSM) or email support-softwaredelivery@hashicorp.com for information on how to get your organization's enterprise license. +1. Acquire a valid Consul Enterprise license. If you are an existing HashiCorp enterprise customer you may contact your organization's [customer success manager](https://support.hashicorp.com/hc/en-us) (CSM) for information on how to get your organization's enterprise license. 1. Store the license in a secure location on disk. 1. Set up the necessary configuration so that when Consul Enterprise reboots it will have the required license. This could be via the client agent configuration file or an environment variable. Visit the [Enterprise License Tutorial](https://learn.hashicorp.com/tutorials/consul/hashicorp-enterprise-license?utm_source=docs) for detailed steps on how to install the license key. @@ -169,7 +167,7 @@ Once you have the license then create a Kubernetes secret containing the license ### Kubernetes -1. Acquire a valid Consul Enterprise license. If you are an existing HashiCorp enterprise customer you may contact your organization's customer success manager (CSM) or email support-softwaredelivery@hashicorp.com for information on how to get your organization's enterprise license. +1. Acquire a valid Consul Enterprise license. If you are an existing HashiCorp enterprise customer you may contact your organization's [customer success manager](https://support.hashicorp.com/hc/en-us) (CSM) for information on how to get your organization's enterprise license. 1. Set up the necessary configuration so that when Consul Enterprise reboots it will have the required license. This could be via the client agent configuration file or an environment variable. Visit the [Enterprise License Tutorial](https://learn.hashicorp.com/tutorials/consul/hashicorp-enterprise-license?utm_source=docs) for detailed steps on how to install the license key. 1. Proceed with the `helm` [upgrade instructions](/docs/k8s/upgrade) diff --git a/website/content/docs/integrate/partnerships.mdx b/website/content/docs/integrate/partnerships.mdx index de7d8a6308..1d58a212a1 100644 --- a/website/content/docs/integrate/partnerships.mdx +++ b/website/content/docs/integrate/partnerships.mdx @@ -7,7 +7,7 @@ description: >- # Consul Integration Program -The HashiCorp Consul Integration Program enables prospective partners to build integrations with HashiCorp Consul that are reviewed and verified by HashiCorp. Consul can be consumed in three ways: +The HashiCorp Consul Integration Program enables prospective partners to build integrations with HashiCorp Consul that are reviewed and verified by HashiCorp. You can integrate with any of the following Consul versions: - **Self-Managed**. Open source, always free - **HashiCorp Cloud Platform (HCP)**. A hosted version of Consul managed in the cloud @@ -35,9 +35,9 @@ By leveraging Consul's RESTful HTTP API system, prospective partners are able to **Infrastructure**: There are two integration options in this category: natively through a direct integration with Consul or via Consul-Terraform-Sync (CTS). By leveraging Consul's powerful **Network Infrastructure Automation (NIA)*** capabilities through CTS, changes in an infrastructure are seamlessly automated when Consul detects a change in its service catalog. For example, these integrations could be used to automate IP updates of load balancers or firewall security policies by leveraging Consul service discovery. --> **Network Infrastructure Automation (NIA)***: These integrations leverage Consul's service catalog to seamlessly integrate with Consul-Terraform-Sync (CTS) to automate changes in network infrastructure via a publisher-subscriber method. More details can be found [here](/docs/integrate/nia-integration). +-> **Network Infrastructure Automation (NIA)***: These integrations leverage Consul's service catalog to seamlessly integrate with Consul-Terraform-Sync (CTS) to automate changes in network infrastructure via a publisher-subscriber method. Refer to the [NIA documentation](/docs/integrate/nia-integration) for details. -**HCP Consul**: HCP Consul is secure by default and offers an enterprise-level service level agreement (SLA) to deploy an organization's most important applications. Sign up for HCP Consul is free and available [here](https://cloud.hashicorp.com/products/consul). +**HCP Consul**: HCP Consul is secure by default and offers an out-of-the-box service mesh solution to streamline operations without the hassle of managing Consul servers. [Sign up for a free HCP Consul account](https://cloud.hashicorp.com/products/consul). **Consul integration verification badges**: Partners will be issued the Consul Enterprise badge for integrations that work with [Consul Enterprise features](https://www.consul.io/docs/enterprise) such as namespaces. Partners will be issued the HCP Consul badge for integrations validated to work with [HCP Consul](https://cloud.hashicorp.com/docs/consul/features). Each badge would be displayed on HashiCorp's partner page as well as be available for posting on the partner's own website to provide better visibility and differentiation of the integration for joint customers. @@ -83,35 +83,42 @@ Here are links to resources, documentation, examples and best practices to guide #### Data Plane: -**Proxy** +**Application Performance Monitoring (APM)** -- [How to Integrate a Sidecar Proxy Documentation](/docs/connect/proxies/integrate) -- [Example of Envoy Integration](/docs/connect/proxies/envoy) +- [Consul Telemetry Documentation](/docs/agent/telemetry) +- [Monitoring Consul with Datadog APM](https://www.datadoghq.com/blog/consul-datadog/) +- [Monitor HCP Consul with New Relic Instant Observability](https://github.com/newrelic-experimental/hashicorp-quickstart-annex/blob/main/hcp-consul/README.md) +- [HCP Consul and CloudFabrix AIOps Integration](https://bot-docs.cloudfabrix.io/Bots/consul/?h=consul) +- [Consul and SnappyFlow Full Stack Observability](https://docs.snappyflow.io/docs/integrations/hcp_consul) -**API Gateway** +**Network Performance Monitoring (NPM)** -- [Ambassador Integration documentation](https://learn.hashicorp.com/tutorials/consul/service-mesh-gateway-ambassador?utm_source=docs) -- [F5 Terminating Gateway Integration Documentation](https://www.hashicorp.com/integrations/f5-networks/consul) -- [Traefik Integration with Consul Service Mesh](https://traefik.io/blog/integrating-consul-connect-service-mesh-with-traefik-2-5/) -- [Kong's Ingress Controller Integration with Consul](https://www.hashicorp.com/integrations/kong/consul) +- [Datadog NPM now supports Consul networking](https://www.datadoghq.com/blog/monitor-consul-with-datadog-npm/) -**Application Performance Monitoring (APM)** +**OpenTelemetry Integrations** -- [Consul Telemetry Documentation](/docs/agent/telemetry) -- [Monitoring Consul with Datadog APM](https://www.datadoghq.com/blog/consul-datadog/) -- [Monitoring Consul with Dynatrace APM](https://www.dynatrace.com/news/blog/automatic-intelligent-observability-into-your-hashicorp-consul-service-mesh/) -- [Monitoring Consul with New Relic APM](https://newrelic.com/instant-observability/consul/b65825cc-faee-47b5-8d7c-6d60d6ab3c59) -- [Monitoring HCP Consul with New Relic APM](https://newrelic.com/instant-observability/hcp-consul/bc99ad15-7aba-450e-8236-6ea667d50cae) +- [Splunk SignalFX OpenTelemetry integration with Consul](https://docs.splunk.com/Observability/gdi/consul/consul.html) +- [Ship HashiCorp Consul metrics with OpenTelemetry to Logz.io](https://docs.logz.io/shipping/prometheus-sources/consul.html) +- [Ingest Consul metrics through OpenTelemetry into Lightstep Observability](https://docs.lightstep.com/docs/ingest-metrics-consul) -**Logging** +**Logging and Alerts** -- [Monitor Consul with Logz.io](https://www.hashicorp.com/integrations/logz-io/consul) -- [Monitor Consul with Splunk SignalFx](https://www.hashicorp.com/integrations/splunksignalfx/consul) +- [Consul Integration with iLert](https://docs.ilert.com/integrations/consul) +- [Consul Integration with PagerDuty](https://www.pagerduty.com/docs/guides/consul-integration-guide/) +- [Monitor Consul with Zabbix](https://www.zabbix.com/integrations/hashicorp_consul#consul) + +**API Gateway and Ingress Controller** + +- [F5 Terminating Gateway Integration Documentation](https://www.hashicorp.com/integrations/f5-networks/consul) +- [Traefik Integration with Consul Service Mesh](https://traefik.io/blog/integrating-consul-connect-service-mesh-with-traefik-2-5/) +- [Kong's Ingress Controller Integration with Consul](https://www.hashicorp.com/integrations/kong/consul) +- [Configuring Ingress Controllers with Consul-on-Kubernetes](https://www.consul.io/docs/k8s/connect/ingress-controllers) +- [Introduction to Consul Transparent Proxy](https://www.consul.io/docs/connect/transparent-proxy) +- [Getting Started with Transparent Proxy](https://www.hashicorp.com/blog/transparent-proxy-on-consul-service-mesh) #### Platform: -- [Consul-AWS for AWS Cloud Map](https://learn.hashicorp.com/tutorials/consul/sync-aws-services?utm_source=docs) -- [Consul Integration with AWS ECS](/docs/ecs/get-started/install) +- [Deploy Consul on Red Hat OpenShift](https://learn.hashicorp.com/tutorials/consul/kubernetes-openshift-red-hat) - [Consul Integration with Layer5 Meshery](https://www.hashicorp.com/integrations/layer5-io/consul) - [Consul Integration with VMware Tanzu Application Service](https://learn.hashicorp.com/tutorials/consul/sync-pivotal-cloud-services?utm_source=docs) @@ -121,7 +128,7 @@ Here are links to resources, documentation, examples and best practices to guide **Firewalls** - **Network Infrastructure Automation (using CTS):** + **Network Infrastructure Automation:** - [Automated Firewalling with Check Point](https://www.hashicorp.com/integrations/checkpoint-software/consul) - [Automated Firewalling with Palo Alto Networks](https://www.hashicorp.com/integrations/pan/consul) @@ -137,30 +144,40 @@ Here are links to resources, documentation, examples and best practices to guide - [Load Balancing with NGINX and Consul Template](https://learn.hashicorp.com/tutorials/consul/load-balancing-nginx?utm_source=docs) - [Load Balancing with HAProxy Service Discovery](https://learn.hashicorp.com/tutorials/consul/load-balancing-haproxy?utm_source=docs) - **Network Infrastructure Automation \(using CTS\):** + **Network Infrastructure Automation:** - - [Automate F5 BIG-IP with Consul NIA](https://learn.hashicorp.com/tutorials/consul/consul-terraform-sync-f5-bigip-fast?utm_source=docs) + - [Zero-Touch Configuration of Secure Apps across BIG-IP Tenants using CTS](https://community.f5.com/t5/technical-articles/zero-touch-configuration-of-secure-apps-across-big-ip-tenants/ta-p/300190) - [Automate VMware Advanced Load Balancers (Avi) with Consul NIA](https://www.hashicorp.com/integrations/_vmware/consul) -**Application Delivery Controllers \(ADC\):** +**Application Delivery Controllers \(ADC\)** - [Automate A10 ADC with Consul NIA](https://learn.hashicorp.com/tutorials/consul/consul-terraform-sync-a10-adc?utm_source=docs) - [Automate Citrix ADC with Consul NIA](https://www.hashicorp.com/integrations/citrix-adc/consul) +**Domain Name Service (DNS) Automation** + +- [Automate DNSimple public facing DNS records with Consul NIA](https://registry.terraform.io/modules/dnsimple/cts/dnsimple/latest) +- [Automate NS1 managed DNS with Consul NIA](https://github.com/ns1-terraform/terraform-ns1-record-sync-nia) + +**No-Code/Low-Code** + +- [Automate Consul Deployments with Sophos Factory Pipelines](https://community.sophos.com/sophos-factory/f/recommended-reads/136639/deploy-hashicorp-consul-from-sophos-factory) + ### 3. Develop and Test The only knowledge necessary to write a plugin is basic command-line skills and knowledge of the [Go programming language](http://www.golang.org). Use the plugin interface to develop your integration. All integrations should contain unit and acceptance testing. -**HCP Consul**: The process to configure a testing instance of HCP consul [is very simple](https://learn.hashicorp.com/tutorials/cloud/consul-introduction?utm_source=docs). HCP has been designed as a HashiCorp managed service so configuration is minimal as only Consul client agents need to be installed. Furthermore, HashiCorp provides all new users an initial credit which should last approximately 2 months using a [development cluster](https://cloud.hashicorp.com/products/consul/pricing). When deployed with AWS free tier services, there should be no cost beyond the time spent by the designated tester. +**HCP Consul**: As a managed service, minimal configuration is required to deploy HCP Consul server clusters. You only need to install Consul client agents. Furthermore, HashiCorp provides all new users an initial credit, which provides approximately two months worth of [development cluster](https://cloud.hashicorp.com/products/consul/pricing) access. When deployed with AWS or Azure free tier services, there should be no cost beyond the time spent by the designated tester. Refer to the [Deploy HCP Consul tutorial](https://learn.hashicorp.com/tutorials/cloud/consul-introduction?utm_source=docs) for details on getting started. -Please note that HCP Consul is currently only deployed on AWS so the partner's application should be able to be deployed or run in AWS. For more information, please refer to [Peering an HVN to an AWS VPC for HCP Consul](https://www.youtube.com/watch?v=vuKjkIGYZlU). +HCP Consul is currently only deployed on AWS and Microsoft Azure, so your application can be deployed to or run in AWS or Azure. #### HCP Consul Resource Links: - [Getting Started with HCP Consul](https://learn.hashicorp.com/tutorials/cloud/consul-introduction?utm_source=docs) -- [Peering an HVN to a VPC for HCP Consul](https://www.youtube.com/watch?v=vuKjkIGYZlU) -- [Connecting a Consul Client to HCP Consul](https://learn.hashicorp.com/tutorials/cloud/consul-client-aws-ec2?utm_source=docs) -- [Monitoring HCP Consul with Datadog](https://docs.datadoghq.com/integrations/guide/hcp-consul/) +- [HCP Consul End-to-End Deployment](https://learn.hashicorp.com/tutorials/cloud/consul-end-to-end-overview?in=consul/cloud-deploy-automation) +- [Deploy HCP Consul with EKS using Terraform](https://learn.hashicorp.com/tutorials/cloud/consul-end-to-end-eks?in=consul/cloud-deploy-automation) +- [HCP Consul Deployment Automation](https://learn.hashicorp.com/collections/consul/cloud-deploy-automation) +- [HCP Consul documentation]( https://cloud.hashicorp.com/docs/consul/usage) **Consul Enterprise**: An integration qualifies for Consul Enterprise when it is tested and compatible with Consul Enterprise Namespaces. diff --git a/website/content/docs/k8s/deployment-configurations/vault/data-integration/bootstrap-token.mdx b/website/content/docs/k8s/deployment-configurations/vault/data-integration/bootstrap-token.mdx index 29cc159cdb..2a05959a9f 100644 --- a/website/content/docs/k8s/deployment-configurations/vault/data-integration/bootstrap-token.mdx +++ b/website/content/docs/k8s/deployment-configurations/vault/data-integration/bootstrap-token.mdx @@ -70,7 +70,7 @@ To find out the service account name of the Consul server-acl-init job (i.e. the you can run the following `helm template` command with your Consul on Kubernetes values file: ```shell-session -$ helm template --release-name ${RELEASE_NAME} -s templates/server-acl-init-serviceaccount.yaml hashicorp/consul +$ helm template --release-name ${RELEASE_NAME} -s templates/server-acl-init-serviceaccount.yaml hashicorp/consul -f values.yaml ``` ## Update Consul on Kubernetes Helm chart diff --git a/website/content/docs/k8s/deployment-configurations/vault/data-integration/connect-ca.mdx b/website/content/docs/k8s/deployment-configurations/vault/data-integration/connect-ca.mdx index 57b002607b..d5da53f87c 100644 --- a/website/content/docs/k8s/deployment-configurations/vault/data-integration/connect-ca.mdx +++ b/website/content/docs/k8s/deployment-configurations/vault/data-integration/connect-ca.mdx @@ -51,7 +51,7 @@ To find out the service account name of the Consul server, you can run: ```shell-session -$ helm template --release-name ${RELEASE_NAME} --show-only templates/server-serviceaccount.yaml hashicorp/consul +$ helm template --release-name ${RELEASE_NAME} --show-only templates/server-serviceaccount.yaml hashicorp/consul -f values.yaml ``` ## Update Consul on Kubernetes Helm chart diff --git a/website/content/docs/k8s/deployment-configurations/vault/data-integration/enterprise-license.mdx b/website/content/docs/k8s/deployment-configurations/vault/data-integration/enterprise-license.mdx index f65c3930fd..f0afb0c9b9 100644 --- a/website/content/docs/k8s/deployment-configurations/vault/data-integration/enterprise-license.mdx +++ b/website/content/docs/k8s/deployment-configurations/vault/data-integration/enterprise-license.mdx @@ -79,12 +79,12 @@ you can run the following `helm template` commands with your Consul on Kubernete - Generate Consul server service account name ```shell-session - $ helm template --release-name ${RELEASE_NAME} -s templates/server-serviceaccount.yaml hashicorp/consul + $ helm template --release-name ${RELEASE_NAME} -s templates/server-serviceaccount.yaml hashicorp/consul -f values.yaml ``` - Generate Consul client service account name ```shell-session - $ helm template --release-name ${RELEASE_NAME} -s templates/client-serviceaccount.yaml hashicorp/consul + $ helm template --release-name ${RELEASE_NAME} -s templates/client-serviceaccount.yaml hashicorp/consul -f values.yaml ``` ## Update Consul on Kubernetes Helm chart. diff --git a/website/content/docs/k8s/deployment-configurations/vault/data-integration/gossip.mdx b/website/content/docs/k8s/deployment-configurations/vault/data-integration/gossip.mdx index 6ed5cc50b1..52955a100b 100644 --- a/website/content/docs/k8s/deployment-configurations/vault/data-integration/gossip.mdx +++ b/website/content/docs/k8s/deployment-configurations/vault/data-integration/gossip.mdx @@ -78,12 +78,12 @@ you can run the following `helm template` commands with your Consul on Kubernete - Generate Consul server service account name ```shell-session - $ helm template --release-name ${RELEASE_NAME} -s templates/server-serviceaccount.yaml hashicorp/consul + $ helm template --release-name ${RELEASE_NAME} -s templates/server-serviceaccount.yaml hashicorp/consul -f values.yaml ``` - Generate Consul client service account name ```shell-session - $ helm template --release-name ${RELEASE_NAME} -s templates/client-serviceaccount.yaml hashicorp/consul + $ helm template --release-name ${RELEASE_NAME} -s templates/client-serviceaccount.yaml hashicorp/consul -f values.yaml ``` ## Update Consul on Kubernetes Helm chart diff --git a/website/content/docs/k8s/deployment-configurations/vault/data-integration/partition-token.mdx b/website/content/docs/k8s/deployment-configurations/vault/data-integration/partition-token.mdx index 88463e89e0..5770054faa 100644 --- a/website/content/docs/k8s/deployment-configurations/vault/data-integration/partition-token.mdx +++ b/website/content/docs/k8s/deployment-configurations/vault/data-integration/partition-token.mdx @@ -7,7 +7,7 @@ description: >- # Storing the ACL Partition Token in Vault -This topic describes how to configure the Consul Helm chart to use an ACL partition token stored in Vault. +This topic describes how to configure the Consul Helm chart to use an ACL partition token stored in Vault when using [Admin Partitions](/docs/enterprise/admin-partitions) in Consul Enterprise. ## Overview Complete the steps outlined in the [Data Integration](/docs/k8s/installation/vault/data-integration) section to use an ACL partition token stored in Vault. @@ -60,24 +60,24 @@ $ vault policy write partition-token-policy partition-token-policy.hcl Next, you will create Kubernetes auth roles for the Consul `server-acl-init` job: ```shell-session -$ vault write auth/kubernetes/role/consul-server-acl-init \ +$ vault write auth/kubernetes/role/consul-partition-init \ bound_service_account_names= \ bound_service_account_namespaces= \ policies=partition-token-policy \ ttl=1h ``` -To find out the service account name of the Consul server, +To find out the service account name of the `partition-init` job, you can run the following `helm template` command with your Consul on Kubernetes values file: ```shell-session -$ helm template --release-name ${RELEASE_NAME} -s templates/server-acl-init-serviceaccount.yaml hashicorp/consul +$ helm template --release-name ${RELEASE_NAME} -s templates/partition-init-serviceaccount.yaml hashicorp/consul -f values.yaml ``` ## Update Consul on Kubernetes Helm chart Now that you have configured Vault, you can configure the Consul Helm chart to -use the ACL partition token key in Vault: +use the ACL partition token key in Vault and the service account for the Partitions role. @@ -87,6 +87,7 @@ global: vault: enabled: true manageSystemACLsRole: consul-server-acl-init + adminPartitionsRole: consul-partition-init acls: partitionToken: secretName: secret/data/consul/partition-token diff --git a/website/content/docs/k8s/deployment-configurations/vault/data-integration/server-tls.mdx b/website/content/docs/k8s/deployment-configurations/vault/data-integration/server-tls.mdx index 902684365b..cff4ce4939 100644 --- a/website/content/docs/k8s/deployment-configurations/vault/data-integration/server-tls.mdx +++ b/website/content/docs/k8s/deployment-configurations/vault/data-integration/server-tls.mdx @@ -138,7 +138,7 @@ this is required for the Consul components to communicate with the Consul server you can run: ```shell-session - $ helm template --release-name ${RELEASE_NAME} --show-only templates/server-serviceaccount.yaml hashicorp/consul + $ helm template --release-name ${RELEASE_NAME} --show-only templates/server-serviceaccount.yaml hashicorp/consul -f values.yaml ``` Role for Consul clients: @@ -153,7 +153,7 @@ this is required for the Consul components to communicate with the Consul server To find out the service account name of the Consul client, use the command below. ```shell-session - $ helm template --release-name ${RELEASE_NAME} --show-only templates/client-serviceaccount.yaml hashicorp/consul + $ helm template --release-name ${RELEASE_NAME} --show-only templates/client-serviceaccount.yaml hashicorp/consul -f values.yaml ``` Role for CA components: diff --git a/website/content/docs/k8s/deployment-configurations/vault/data-integration/snapshot-agent-config.mdx b/website/content/docs/k8s/deployment-configurations/vault/data-integration/snapshot-agent-config.mdx index 3f03b45c3a..2e1500a680 100644 --- a/website/content/docs/k8s/deployment-configurations/vault/data-integration/snapshot-agent-config.mdx +++ b/website/content/docs/k8s/deployment-configurations/vault/data-integration/snapshot-agent-config.mdx @@ -70,7 +70,7 @@ To find out the service account name of the Consul snapshot agent, you can run the following `helm template` command with your Consul on Kubernetes values file: ```shell-session -$ helm template --release-name ${RELEASE_NAME} -s templates/client-snapshot-agent-serviceaccount.yaml hashicorp/consul +$ helm template --release-name ${RELEASE_NAME} -s templates/client-snapshot-agent-serviceaccount.yaml hashicorp/consul -f values.yaml ``` ## Update Consul on Kubernetes Helm chart diff --git a/website/content/docs/k8s/deployment-configurations/vault/data-integration/webhook-certs.mdx b/website/content/docs/k8s/deployment-configurations/vault/data-integration/webhook-certs.mdx index 7aa4c016aa..ec85209eee 100644 --- a/website/content/docs/k8s/deployment-configurations/vault/data-integration/webhook-certs.mdx +++ b/website/content/docs/k8s/deployment-configurations/vault/data-integration/webhook-certs.mdx @@ -169,7 +169,7 @@ this is required for the Consul components to communicate with the Consul server you can run: ```shell-session - $ helm template --release-name ${RELEASE_NAME} --show-only templates/controller-serviceaccount.yaml hashicorp/consul + $ helm template --release-name ${RELEASE_NAME} --show-only templates/controller-serviceaccount.yaml hashicorp/consul -f values.yaml ``` Role for Consul connect inject webhooks: @@ -184,7 +184,7 @@ this is required for the Consul components to communicate with the Consul server To find out the service account name of the Consul connect inject, use the command below. ```shell-session - $ helm template --release-name ${RELEASE_NAME} --show-only templates/connect-inject-serviceaccount.yaml hashicorp/consul + $ helm template --release-name ${RELEASE_NAME} --show-only templates/connect-inject-serviceaccount.yaml hashicorp/consul -f values.yaml ``` ## Update Consul on Kubernetes Helm chart diff --git a/website/content/docs/k8s/deployment-configurations/vault/systems-integration.mdx b/website/content/docs/k8s/deployment-configurations/vault/systems-integration.mdx index b51cb5c583..9c5ac5c5ba 100644 --- a/website/content/docs/k8s/deployment-configurations/vault/systems-integration.mdx +++ b/website/content/docs/k8s/deployment-configurations/vault/systems-integration.mdx @@ -128,11 +128,13 @@ A minimal valid installation of Vault Kubernetes must include the Agent Injector ```shell-session $ cat <> vault-injector.yaml # vault-injector.yaml +global: + enabled: true + externalVaultAddr: ${VAULT_ADDR} server: enabled: false injector: enabled: true - externalVaultAddr: ${VAULT_ADDR} authPath: auth/${VAULT_AUTH_METHOD_NAME} EOF ``` diff --git a/website/content/docs/k8s/deployment-configurations/vault/wan-federation.mdx b/website/content/docs/k8s/deployment-configurations/vault/wan-federation.mdx index ad31da349c..5da355201a 100644 --- a/website/content/docs/k8s/deployment-configurations/vault/wan-federation.mdx +++ b/website/content/docs/k8s/deployment-configurations/vault/wan-federation.mdx @@ -675,4 +675,4 @@ Repeat the following steps for each datacenter in the cluster: ## Next steps You have completed the process of federating the secondary datacenter (dc2) with the primary datacenter (dc1) using Vault as the Secrets backend. To validate that everything is configured properly, please confirm that all pods within both datacenters are in a running state. -For further detail on specific Consul secrets that are available to be stored in Vault, please checkout the detailed information in the [Data Integration](/docs/website/content/docs/k8s/installation/vault/data-integration) section of the [Vault as a Secrets Backend](/docs/website/content/docs/k8s/installation/vault) area of the Consul on Kubernetes documentation. +For further detail on specific Consul secrets that are available to be stored in Vault, please checkout the detailed information in the [Data Integration](/docs/k8s/deployment-configurations/vault/data-integration) section of the [Vault as a Secrets Backend](/docs/k8s/deployment-configurations/vault) area of the Consul on Kubernetes documentation. diff --git a/website/content/docs/k8s/helm.mdx b/website/content/docs/k8s/helm.mdx index 230ad60c0b..4044106eee 100644 --- a/website/content/docs/k8s/helm.mdx +++ b/website/content/docs/k8s/helm.mdx @@ -559,6 +559,9 @@ Use these links to navigate to a particular top-level stanza. connect-injected sidecar proxies and mesh, terminating, and ingress gateways. See https://www.consul.io/docs/connect/proxies/envoy for full compatibility matrix between Consul and Envoy. + - `imageConsulDataplane` ((#v-global-imageconsuldataplane)) (`string: hashicorp/consul-dataplane:`) - The name (and tag) of the consul-dataplane Docker image used for the + connect-injected sidecar proxies and mesh, terminating, and ingress gateways. + - `openshift` ((#v-global-openshift)) - Configuration for running this Helm chart on the Red Hat OpenShift platform. This Helm chart currently supports OpenShift v4.x+. @@ -568,6 +571,16 @@ Use these links to navigate to a particular top-level stanza. - `consulAPITimeout` ((#v-global-consulapitimeout)) (`string: 5s`) - The time in seconds that the consul API client will wait for a response from the API before cancelling the request. + - `cloud` ((#v-global-cloud)) - Enables installing an HCP Consul self-managed cluster. + Requires Consul v1.14+. + + - `enabled` ((#v-global-cloud-enabled)) (`boolean: false`) - If true, the Helm chart will enable the installation of an HCP Consul + self-managed cluster. + + - `secretName` ((#v-global-cloud-secretname)) (`string: null`) - The name of the Kubernetes secret that holds the HCP cloud configuration. + It contains the HCP service principal client_id and client_secret as well + as the HCP resource_id. + ### server ((#h-server)) - `server` ((#v-server)) - Server, when enabled, configures a server cluster to run. This should @@ -581,7 +594,7 @@ Use these links to navigate to a particular top-level stanza. - `image` ((#v-server-image)) (`string: null`) - The name of the Docker image (including any tag) for the containers running Consul server agents. - - `replicas` ((#v-server-replicas)) (`integer: 3`) - The number of server agents to run. This determines the fault tolerance of + - `replicas` ((#v-server-replicas)) (`integer: 1`) - The number of server agents to run. This determines the fault tolerance of the cluster. Please see the deployment table (https://consul.io/docs/internals/consensus#deployment-table) for more information. @@ -931,7 +944,7 @@ Use these links to navigate to a particular top-level stanza. - `httpsPort` ((#v-externalservers-httpsport)) (`integer: 8501`) - The HTTPS port of the Consul servers. - - `grpcPort` ((#v-externalservers-grpcport)) (`integer: 8503`) - The GRPC port of the Consul servers. + - `grpcPort` ((#v-externalservers-grpcport)) (`integer: 8502`) - The GRPC port of the Consul servers. - `tlsServerName` ((#v-externalservers-tlsservername)) (`string: null`) - The server name to use as the SNI host header when connecting with HTTPS. @@ -959,7 +972,7 @@ Use these links to navigate to a particular top-level stanza. - `client` ((#v-client)) - Values that configure running a Consul client on Kubernetes nodes. - - `enabled` ((#v-client-enabled)) (`boolean: global.enabled`) - If true, the chart will install all + - `enabled` ((#v-client-enabled)) (`boolean: false`) - If true, the chart will install all the resources necessary for a Consul client on every Kubernetes node. This _does not_ require `server.enabled`, since the agents can be configured to join an external cluster. @@ -1479,7 +1492,7 @@ Use these links to navigate to a particular top-level stanza. - `aclSyncToken` ((#v-synccatalog-aclsynctoken)) - Refers to a Kubernetes secret that you have created that contains an ACL token for your Consul cluster which allows the sync process the correct - permissions. This is only needed if ACLs are managed manually within the Consul cluster. + permissions. This is only needed if ACLs are managed manually within the Consul cluster, i.e. `global.acls.manageSystemACLs` is `false`. - `secretName` ((#v-synccatalog-aclsynctoken-secretname)) (`string: null`) - The name of the Kubernetes secret that holds the acl sync token. @@ -1542,7 +1555,7 @@ Use these links to navigate to a particular top-level stanza. - `connectInject` ((#v-connectinject)) - Configures the automatic Connect sidecar injector. - - `enabled` ((#v-connectinject-enabled)) (`boolean: false`) - True if you want to enable connect injection. Set to "-" to inherit from + - `enabled` ((#v-connectinject-enabled)) (`boolean: true`) - True if you want to enable connect injection. Set to "-" to inherit from global.enabled. - `replicas` ((#v-connectinject-replicas)) (`integer: 2`) - The number of deployment replicas. @@ -1598,6 +1611,17 @@ Use these links to navigate to a particular top-level stanza. - `cniNetDir` ((#v-connectinject-cni-cninetdir)) (`string: /etc/cni/net.d`) - Location on the kubernetes node of all CNI configuration. Should be the absolute path and start with a '/' + - `multus` ((#v-connectinject-cni-multus)) (`string: false`) - If multus CNI plugin is enabled with consul-cni. When enabled, consul-cni will not be installed as a chained + CNI plugin. Instead, a NetworkAttachementDefinition CustomResourceDefinition (CRD) will be created in the helm + release namespace. Following multus plugin standards, an annotation is required in order for the consul-cni plugin + to be executed and for your service to be added to the Consul Service Mesh. + + Add the annotation `'k8s.v1.cni.cncf.io/networks': '[{ "name":"consul-cni","namespace": "consul" }]'` to your pod + to use the default installed NetworkAttachementDefinition CRD. + + Please refer to the [Multus Quickstart Guide](https://github.com/k8snetworkplumbingwg/multus-cni/blob/master/docs/quickstart.md) + for more information about using multus. + - `resources` ((#v-connectinject-cni-resources)) (`map`) - The resource settings for CNI installer daemonset. - `resourceQuota` ((#v-connectinject-cni-resourcequota)) - Resource quotas for running the daemonset as system critical pods @@ -1850,7 +1874,7 @@ Use these links to navigate to a particular top-level stanza. Requires consul >= 1.8.4. ServiceIntentions require consul 1.9+. - - `enabled` ((#v-controller-enabled)) (`boolean: false`) - Enables the controller for managing custom resources. + - `enabled` ((#v-controller-enabled)) (`boolean: true`) - Enables the controller for managing custom resources. - `replicas` ((#v-controller-replicas)) (`integer: 1`) - The number of deployment replicas. @@ -1947,8 +1971,6 @@ Use these links to navigate to a particular top-level stanza. - `service` ((#v-meshgateway-service)) - The service option configures the Service that fronts the Gateway Deployment. - - `enabled` ((#v-meshgateway-service-enabled)) (`boolean: true`) - Whether to create a Service or not. - - `type` ((#v-meshgateway-service-type)) (`string: LoadBalancer`) - Type of service, ex. LoadBalancer, ClusterIP. - `port` ((#v-meshgateway-service-port)) (`integer: 443`) - Port that the service will be exposed on. @@ -2003,8 +2025,6 @@ Use these links to navigate to a particular top-level stanza. NOTE: The use of a YAML string is deprecated. Instead, set directly as a YAML map. - - `initCopyConsulContainer` ((#v-meshgateway-initcopyconsulcontainer)) (`map`) - The resource settings for the `copy-consul-bin` init container. - - `initServiceInitContainer` ((#v-meshgateway-initserviceinitcontainer)) (`map`) - The resource settings for the `service-init` init container. - `affinity` ((#v-meshgateway-affinity)) (`string`) - By default, we set an anti-affinity so that two gateway pods won't be @@ -2105,8 +2125,6 @@ Use these links to navigate to a particular top-level stanza. - `resources` ((#v-ingressgateways-defaults-resources)) (`map`) - Resource limits for all ingress gateway pods - - `initCopyConsulContainer` ((#v-ingressgateways-defaults-initcopyconsulcontainer)) (`map`) - The resource settings for the `copy-consul-bin` init container. - - `affinity` ((#v-ingressgateways-defaults-affinity)) (`string`) - By default, we set an anti-affinity so that two of the same gateway pods won't be on the same node. NOTE: Gateways require that Consul client agents are also running on the nodes alongside each gateway pod. @@ -2198,8 +2216,6 @@ Use these links to navigate to a particular top-level stanza. - `resources` ((#v-terminatinggateways-defaults-resources)) (`map`) - Resource limits for all terminating gateway pods - - `initCopyConsulContainer` ((#v-terminatinggateways-defaults-initcopyconsulcontainer)) (`map`) - The resource settings for the `copy-consul-bin` init container. - - `affinity` ((#v-terminatinggateways-defaults-affinity)) (`string`) - By default, we set an anti-affinity so that two of the same gateway pods won't be on the same node. NOTE: Gateways require that Consul client agents are also running on the nodes alongside each gateway pod. diff --git a/website/content/docs/k8s/installation/install-cli.mdx b/website/content/docs/k8s/installation/install-cli.mdx index b7261a9569..f195784c0a 100644 --- a/website/content/docs/k8s/installation/install-cli.mdx +++ b/website/content/docs/k8s/installation/install-cli.mdx @@ -11,6 +11,7 @@ description: >- This topic describes how to install Consul on Kubernetes using the Consul K8s CLI tool. The Consul K8s CLI tool enables you to quickly install and interact with Consul on Kubernetes. Use the Consul K8s CLI tool to install Consul on Kubernetes if you are deploying a single cluster. We recommend using the [Helm chart installation method](/docs/k8s/installation/install) if you are installing Consul on Kubernetes for multi-cluster deployments that involve cross-partition or cross datacenter communication. + ## Introduction If it is your first time installing Consul on Kubernetes, then you must first install the Consul K8s CLI tool. You can install Consul on Kubernetes using the Consul K8s tool after installing the CLI. diff --git a/website/content/docs/k8s/installation/install.mdx b/website/content/docs/k8s/installation/install.mdx index 319cada6d5..54c67c0581 100644 --- a/website/content/docs/k8s/installation/install.mdx +++ b/website/content/docs/k8s/installation/install.mdx @@ -9,6 +9,7 @@ description: >- This topic describes how to install Consul on Kubernetes using the official Consul Helm chart. For instruction on how to install Consul on Kubernetes using the Consul K8s CLI, refer to [Installing the Consul K8s CLI](/docs/k8s/installation/install-cli). + ## Introduction We recommend using the Consul Helm chart to install Consul on Kubernetes for multi-cluster installations that involve cross-partition or cross datacenter communication. The Helm chart installs and configures all necessary components to run Consul. The configuration enables you to run a server cluster, a client cluster, or both. @@ -119,7 +120,7 @@ Because the plugin is executed by the local Kubernetes kubelet, the plugin alrea The Consul Helm Chart is responsible for installing the Consul CNI plugin. To configure the plugin to be installed, add the following configuration to your `values.yaml` file: - + @@ -151,6 +152,24 @@ connectInject: ``` + + +```yaml +global: + name: consul + openshift: + enabled: true +connectInject: + enabled: true + cni: + enabled: true + logLevel: info + multus: true + cniBinDir: "/var/lib/cni/bin" + cniNetDir: "/etc/kubernetes/cni/net.d" +``` + + @@ -160,6 +179,7 @@ The following table describes the available CNI plugin options: | --- | --- | --- | | `cni.enabled` | Boolean value that enables or disables the CNI plugin. If `true`, the plugin is responsible for redirecting traffic in the service mesh. If `false`, redirection is handled by the `connect-inject init` container. | `false` | | `cni.logLevel` | String value that specifies the log level for the installer and plugin. You can specify the following values: `info`, `debug`, `error`. | `info` | +| `cni.multus` | Boolean value that enables multus CNI plugin support. If `true`, multus will be enabled. If `false`, Consul CNI will operate as a chained plugin. | `false` | | `cni.cniBinDir` | String value that specifies the location on the Kubernetes node where the CNI plugin is installed. | `/opt/cni/bin` | | `cni.cniNetDir` | String value that specifies the location on the Kubernetes node for storing the CNI configuration. | `/etc/cni/net.d` | diff --git a/website/content/docs/lambda/index.mdx b/website/content/docs/lambda/index.mdx index 8177c73efe..71f1bce5bd 100644 --- a/website/content/docs/lambda/index.mdx +++ b/website/content/docs/lambda/index.mdx @@ -7,26 +7,30 @@ description: >- # Consul and AWS Lambda Overview -Lambda functions are programs or scripts that run in AWS Lambda. The functions process events and return responses. Refer to the [AWS Lambda website](https://aws.amazon.com/lambda/) for additional information. +You can configure Consul to allow services in your mesh to invoke Lambda functions, as well as allow Lambda functions to invoke services in your mesh. Lambda functions are programs or scripts that run in AWS Lambda. Refer to the [AWS Lambda website](https://aws.amazon.com/lambda/) for additional information. -## How AWS Lambda Functions on Consul Work +## Register Lambda functions into Consul -You can register AWS Lambda functions in Consul and invoke them from mesh services. -### Registering Lambda Functions +The first step is to register your Lambda functions into Consul. We recommend using the [Lambda registrator module](https://github.com/hashicorp/terraform-aws-consul-lambda/tree/main/modules/lambda-registrator) to automatically synchronize Lambda functions into Consul. You can also manually register Lambda functions into Consul if you are unable to use the Lambda registrator. -Registering AWS Lambda functions into Consul requires [registering a service](/docs/discovery/services) -and storing a [service defaults configuration entry](/docs/connect/config-entries/service-defaults) -into Consul. +Refer to [Lambda Function Registration Requirements](/docs/lambda/registration/index) for additional information about registering Lambda functions into Consul. -We recommend using [Lambda registrator](https://github.com/hashicorp/terraform-aws-consul-lambda-registrator) -to automatically synchronize Lambda functions into Consul. Lambda functions can -also be manually registered into Consul when using Lambda registrator is not possible. +## Invoke Lambda functions from Consul service mesh -See the [Registration page](/docs/lambda/registration) for more information -about registering Lambda functions into Consul. +After registering AWS Lambda functions, you can invoke Lambda functions from the Consul service mesh through terminating gateways (recommended) or directly from connected proxies. -### Invoking Lambda Functions from Consul Service Mesh +Refer to [Invoke Lambda Functions from Services](/docs/lambda/invocation) for details. -Lambda functions can be invoked by any mesh service directly from connect proxies or -through terminating gateways. The [Invocation page](/docs/lambda/invocation) -explains how to invoke Lambda functions from Consul service mesh services. +## Invoke mesh services from Lambda function + +~> **Lambda-to-mesh functionality is currently in beta**: Functionality associated with beta features are subject to change. You should never use the beta release in secure environments or production scenarios. Features in beta may have performance issues, scaling issues, and limited support. + +You can also add the `consul-lambda-extension` plugin as a layer in your Lambda functions, which enables them to send requests to services in the mesh. The plugin starts a lightweight sidecar proxy that directs requests from Lambda functions to [mesh gateways](docs/connect/gateways#mesh-gateways). The gateways route traffic to the destination service to complete the request. + +![Invoke mesh service from Lambda function](/img/invoke-service-from-lambda-flow.svg) + +Refer to [Invoke Services from Lambda Functions](/docs/lambda/invoke-from-lambda) for additional information about registering Lambda functions into Consul. + +Consul mesh gateways are required to send requests from Lambda functions to mesh services. Refer to [Mesh Gateways between Datacenters](/docs/connect/gateways/mesh-gateway/service-to-service-traffic-datacenters) for additional information. + +Note that L7 traffic management features are not supported. As a result, requests from Lambda functions ignore service routes and splitters. \ No newline at end of file diff --git a/website/content/docs/lambda/invocation.mdx b/website/content/docs/lambda/invocation.mdx index 7e7cef1acd..73751f8d6d 100644 --- a/website/content/docs/lambda/invocation.mdx +++ b/website/content/docs/lambda/invocation.mdx @@ -5,7 +5,7 @@ description: >- You can invoke an Amazon Web Services Lambda function in your Consul service mesh by configuring terminating gateways or sidecar proxies. Learn how to declare a registered function as an upstream and why we recommend using terminating gateways with Lambda. --- -# Invoke Lambda Functions +# Invoke Lambda Functions from Mesh Services This topic describes how to invoke AWS Lambda functions from the Consul service mesh. diff --git a/website/content/docs/lambda/invoke-from-lambda.mdx b/website/content/docs/lambda/invoke-from-lambda.mdx new file mode 100644 index 0000000000..495e7e92a9 --- /dev/null +++ b/website/content/docs/lambda/invoke-from-lambda.mdx @@ -0,0 +1,273 @@ +--- +layout: docs +page_title: Invoke Services from Lambda Functions +description: >- + This topic describes how to invoke services in the mesh from Lambda functions registered with Consul. +--- + +# Invoke Services from Lambda Functions + +This topic describes how to invoke services in the mesh from Lambda functions registered with Consul. + +~> **Lambda-to-mesh functionality is currently in beta**: Functionality associated with beta features are subject to change. You should never use the beta release in secure environments or production scenarios. Features in beta may have performance issues, scaling issues, and limited support. + +## Introduction + +The following steps describe the process: + +1. Deploy the destination service and mesh gateway. +1. Deploy the Lambda extension layer. +1. Deploy the Lambda registrator. +1. Write the Lambda function code. +1. Deploy the Lambda function. +1. Invoke the Lambda function. + +You must add the `consul-lambda-extension` extension as a Lambda layer to enable Lambda functions to send requests to mesh services. Refer to the [AWS Lambda documentation](https://docs.aws.amazon.com/lambda/latest/dg/invocation-layers.html) for instructions on how to add layers to your Lambda functions. + +The layer runs an external Lambda extension that starts a sidecar proxy. The proxy listens on one port for each upstream service and upgrades the outgoing connections to mTLS. It then proxies the requests through to [mesh gateways](/docs/connect/gateways#mesh-gateways). + +## Prerequisites + +You must deploy the destination services and mesh gateway prior to deploying your Lambda service with the `consul-lambda-extension` layer. + +### Deploy the destination service + +There are several methods for deploying services to Consul service mesh. The following example configuration deploys a service named `static-server` with Consul on Kubernetes. + +```yaml +kind: Service +apiVersion: v1 +metadata: + # Specifies the service name in Consul. + name: static-server +spec: + selector: + app: static-server + ports: + - protocol: TCP + port: 80 + targetPort: 8080 +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: static-server +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: static-server +spec: + replicas: 1 + selector: + matchLabels: + app: static-server + template: + metadata: + name: static-server + labels: + app: static-server + annotations: + 'consul.hashicorp.com/connect-inject': 'true' + spec: + containers: + - name: static-server + image: hashicorp/http-echo:latest + args: + - -text="hello world" + - -listen=:8080 + ports: + - containerPort: 8080 + name: http + serviceAccountName: static-server +``` + +### Deploy the mesh gateway + +The mesh gateway must be running and registered to the Lambda function’s Consul datacenter. Refer to the following documentation and tutorials for instructions: + +- [Mesh Gateways between Datacenters](/docs/connect/gateways/mesh-gateway/service-to-service-traffic-datacenters) +- [Mesh Gateways between Admin Partitions](/docs/connect/gateways/mesh-gateway/service-to-service-traffic-partitions) +- [Mesh Gateways between Peered Clusters](/docs/connect/gateways/mesh-gateway/service-to-service-traffic-peers) +- [Connect Services Across Datacenters with Mesh Gateways](https://developer.hashicorp.com/consul/tutorials/developer-mesh/service-mesh-gateways) + +## Deploy the Lambda extension layer + +The `consul-lambda-extension` extension runs during the `Init` phase of the Lambda function execution. The extension retrieves the data that the Lambda registrator has been configured to store from AWS Parameter Store and creates a lightweight TCP proxy. The proxy creates a local listener for each upstream defined in the `CONSUL_SERVICE_UPSTREAMS` environment variable. + +The extension periodically retrieves the data from the AWS Parameter Store so that the function can process requests. When the Lambda function receives a shutdown event, the extension also stops. + +1. Download the `consul-lambda-extension` extension from [releases.hashicorp.com](https://releases.hashicorp.com/): + + ```shell-session + curl -o consul-lambda-extension__linux_amd64.zip https://releases.hashicorp.com/consul-lambda//consul-lambda-extension__linux_amd64.zip + ``` +1. Create the AWS Lambda layer in the same AWS region as the Lambda function. You can create the layer manually using the AWS CLI or AWS Console, but we recommend using Terraform: + + + + ```hcl + resource "aws_lambda_layer_version" "consul_lambda_extension" { + layer_name = "consul-lambda-extension" + filename = "consul-lambda-extension__linux_amd64.zip" + source_code_hash = filebase64sha256("consul-lambda-extension__linux_amd64.zip") + description = "Consul service mesh extension for AWS Lambda" + } + ``` + + + +## Deploy the Lambda registrator + +Configure and deploy the Lambda registrator. Refer to the [registrator configuration documentation](/docs/lambda/registration/automate#configuration) and the [registrator deployment documentation](/docs/lambda/registration/automate#deploy-the-lambda-registrator) for instructions. + +## Write the Lambda function code + +Refer to the [AWS Lambda documentation](https://docs.aws.amazon.com/lambda/latest/dg/getting-started.html) for instructions on how to write a Lambda function. In the following example, the function calls an upstream service on port `2345`: + + +```go +package main + +import ( + "context" + "io" + "fmt" + "net/http" + "github.com/aws/aws-lambda-go/lambda" +) + +type Response struct { + StatusCode int `json:"statusCode"` + Body string `json:"body"` +} + +func HandleRequest(ctx context.Context, _ interface{}) (Response, error) { + resp, err := http.Get("http://localhost:2345") + fmt.Println("Got response", resp) + if err != nil { + return Response{StatusCode: 500, Body: "Something bad happened"}, err + } + + if resp.StatusCode != 200 { + return Response{StatusCode: resp.StatusCode, Body: resp.Status}, err + } + + defer resp.Body.Close() + + b, err := io.ReadAll(resp.Body) + if err != nil { + return Response{StatusCode: 500, Body: "Error decoding body"}, err + } + + return Response{StatusCode: 200, Body: string(b)}, nil +} + +func main() { + lambda.Start(HandleRequest) +} +``` + +## Deploy the Lambda function + +1. Create and apply an IAM policy that allows the Lambda function’s role to fetch the Lambda extension’s data from the AWS Parameter Store. The following example, creates an IAM role for the Lambda function, creates an IAM policy with the necessary permissions and attaches the policy to the role: + + + + ```hcl + resource "aws_iam_role" "lambda" { + name = "lambda-role" + + assume_role_policy = < + +1. Configure and deploy the Lambda function. Refer to the [Lambda extension configuration](#lambda-extension-configuration) reference for information about all available options. There are several methods for deploying Lambda functions. The following example uses Terraform to deploy a function that can invoke the `static-server` upstream service using mTLS data stored under the `/lambda_extension_data` prefix: + + + + ```hcl + resource "aws_lambda_function" "example" { + … + function_name = "lambda" + role = aws_iam_role.lambda.arn + tags = { + "serverless.consul.hashicorp.com/v1alpha1/lambda/enabled" = "true" + } + variables = { + environment = { + CONSUL_MESH_GATEWAY_URI = var.mesh_gateway_http_addr + CONSUL_SERVICE_UPSTREAMS = "static-server:2345:dc1" + CONSUL_EXTENSION_DATA_PREFIX = "/lambda_extension_data" + } + } + layers = [aws_lambda_layer_version.consul_lambda_extension.arn] + ``` + + + +1. Run the `terraform apply` command and Consul automatically configures a service for the Lambda function. + +### Lambda extension configuration + +Define the following environment variables in your Lambda functions to configure the Lambda extension. The variables apply to each Lambda function in your environment: + +| Variable | Description | Default | +| --- | --- | --- | +| `CONSUL_MESH_GATEWAY_URI` | Specifies the URI where the mesh gateways that the plugin makes requests are running. The mesh gateway should be registered in the same Consul datacenter and partition that the service is running in. For optimal performance, this mesh gateway should run in the same AWS region. | none | +| `CONSUL_EXTENSION_DATA_PREFIX` | Specifies the prefix that the plugin pulls configuration data from. The data must be located in the following directory:
    `“${CONSUL_EXTENSION_DATA_PREFIX}/${CONSUL_SERVICE_PARTITION}/${CONSUL_SERVICE_NAMESPACE}/”` | none | +| `CONSUL_SERVICE_NAMESPACE` | Specifies the Consul namespace the service is registered into. | `default` | +| `CONSUL_SERVICE_PARTITION` | Specifies the Consul partition the service is registered into. | `default` | +| `CONSUL_REFRESH_FREQUENCY` | Specifies the amount of time the extension waits before re-pulling data from the Parameter Store. Use [Go `time.Duration`](https://pkg.go.dev/time@go1.19.1#ParseDuration) string values, for example, `”30s”`.
    The time is added to the duration configured in the Lambda registrator `sync_frequency_in_minutes` configuration. Refer to [Lambda registrator configuration options](/docs/lambda/registration/automate#lambda-registrator-configuration-options). The combined configurations determine how stale the data may become. Lambda functions can run for up to 14 hours, so we recommend configuring a value that results in acceptable staleness for certificates. | `“5m”` | +| `CONSUL_SERVICE_UPSTREAMS` | Specifies a comma-separated list of upstream services that the Lambda function can call. Specify the value as an unlabelled annotation according to the [`consul.hashicorp.com/connect-service-upstreams` annotation format](/docs/k8s/annotations-and-labels#consul-hashicorp-com-connect-service-upstreams) in Consul on Kubernetes. For example, `"[service-name]:[port]:[optional-datacenter]"` | none | + +## Invoke the Lambda function + +If _intentions_ are enabled in the Consul service mesh, you must create an intention that allows the Lambda function's Consul service to invoke all upstream services prior to invoking the Lambda function. Refer to [Service Mesh Intentions](/docs/connect/intentions) for additional information. + +There are several ways to invoke Lambda functions. In the following example, the `aws lambda invoke` CLI command invokes the function: + +```shell-session +$ aws lambda invoke --function-name lambda /dev/stdout | cat +``` diff --git a/website/content/docs/lambda/registration/automate.mdx b/website/content/docs/lambda/registration/automate.mdx new file mode 100644 index 0000000000..45e085b310 --- /dev/null +++ b/website/content/docs/lambda/registration/automate.mdx @@ -0,0 +1,190 @@ +--- +layout: docs +page_title: Automate Lambda Function Registeration +description: >- + Register AWS Lambda functions with Consul service mesh using the Consul Lambda registrator. The Consul Lambda registrator automates Lambda function registration. +--- + +# Automate Lambda Function Registeration + +This topic describes how to automate Lambda function registration using Consul's Lambda registrator module for Terraform. + +## Introduction + +You can deploy the Lambda registrator to your environment to automatically register and deregister Lambda functions with Consul based on the function's tags. Refer to the [AWS Lambda tags documentation](https://docs.aws.amazon.com/lambda/latest/dg/configuration-tags.html) to learn about tags. + +The registrator also stores and periodically updates information required to make mTLS requests to upstream services in the AWS parameter store. This enables Lambda functions to invoke mesh services. Refer to [Invoke Services from Lambda Functions](/docs/lambda/invoke-from-lambda) for additional information. + +The registrator runs as a Lambda function that is invoked by AWS EventBridge. Refer to the [AWS EventBridge documentation](https://docs.aws.amazon.com/eventbridge/latest/userguide/eb-what-is.html) for additional information. + +EventBridge invokes the registrator using either [AWS CloudTrail](https://docs.aws.amazon.com/lambda/latest/dg/logging-using-cloudtrail.html) to synchronize with Consul in real-time or in scheduled intervals. + +CloudTrail events typically synchronize updates, registration, and deregistration within one minute, but events may occasionally be delayed. + +Scheduled events fully synchronize functions between Lambda and Consul to prevent entropy. By default, EventBridge triggers a full sync every five minutes. + +The following diagram shows the flow of events from EventBridge into Consul: + + + +![Lambda Registrator Architecture](/img/lambda_registrator_architecture.svg) + + + +1. EventBridge invokes the Lambda registrator based on CloudTrail Lambda events or a schedule. +1. Lambda registrator determines how to reconcile Lambda's control plane state + with Consul state and ensures they are in sync by registering, updating, and + deregistering Lambda services. + +## Requirements + +Verify that your environment meets the requirements specified in [Lambda Function Registration Requirements](/docs/lambda/registration/index). + +## Configuration + +The Lambda registrator stores data in the AWS Parameter Store. You can configure the type of data stored and how to store it. + +### Optional: Store the CA certificate in Parameter Store + +When Lambda registrator makes a request to Consul's [HTTP API](/api-docs) over HTTPS and the Consul API is signed by a custom CA, Lambda registrator uses the CA certificate stored in AWS Parameter Store to verify the authenticity of the Consul API. Refer to the [Parameter Store documentation](https://docs.aws.amazon.com/systems-manager/latest/userguide/systems-manager-parameter-store.html) for additional information. + +You can apply the following Terraform configuration to store Consul's server CA in Parameter Store: + +```hcl +resource "aws_ssm_parameter" "ca-cert" { + name = "/lambda-registrator/ca-cert" + type = "SecureString" + value = +} +``` + +### Optional: Store the ACL token in Parameter Store + +If [Consul access control lists (ACLs)](/docs/security/acl) are enabled, Lambda registrator must present an ACL token stored in Parameter Store to access resources. You can use the Consul CLI, API, or the Terraform provider to facilitate the ACL workflow. The following procedure describes how to create and store a token from the command line: + +1. Create an ACL policy that includes the following rule: + + + + ```hcl + service_prefix "" { + policy = "write" + } + ``` + + + +1. Run `consul acl policy create` to create the policy. The following example creates a policy called `lambda-registrator-policy` containing permissions specified in `rules.hcl`: + ```shell-session + $ consul acl policy create -name "lambda-registrator-policy" -rules @rules.hcl + ``` + +1. Issue the `consul acl token create` command to create the token. The following example creates a token linked to the `lambda-registrator-policy` policy: + ```shell-session + $ consul acl token create -policy-name "lambda-registrator-policy" + ``` + +1. Store the token in Parameter Store by applying the following Terraform: + ```hcl + resource "aws_ssm_parameter" "acl-token" { + name = "/lambda-registrator/acl-token" + type = "SecureString" + value = + } + ``` + +### Optional: Store extension data in Parameter Store + +If you want to enable Lambda functions to invoke services in the mesh, then you must specify a non-empty string in the `consul_extension_data_prefix` configuration. The string represents a path in the AWS Parameter Store so that the registrator can store data necessary for making mTLS requests to upstream services and update the data periodically. If the path does not exist it will be created. + +Lambda registrator encrypts and stores all data for Lambda functions in the AWS Parameter Store according to the [Lambda registrator configuration options](#lambda-registrator-configuration-options). The data is stored in the following directory as a `SecureString` type: + +`${consul_extension_data_prefix}/${}/${}/${}` + +The registrator also requires the following IAM permissions to access the parameter store: + +```json +{ + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": ["ssm:PutParameter","ssm:DeleteParameter"], + "Resource": "arn:aws:ssm:us-east-2:123456789012:parameter/${var.consul_extension_data_prefix}/*" + }, + ] +} +``` + +### Lambda Registrator configuration options + +| Name | Description | +| - | - | +| `name` | Specifies the name of the Lambda function associated with the Lambda registrator. The name is also used to construct the Identity and Access Management (IAM) role and policy names used by the Lambda function. | +| `sync_frequency_in_minutes` | Specifies the interval in minutes that EventBridge uses to trigger a full synchronization. Default is `10`. | +| `timeout` | The maximum number of seconds Lambda registrator can run per invocation before timing out. | +| `consul_http_addr` | Specifies the address of the Consul API client. | +| `consul_datacenter` | Specifies the Consul datacenter to synchronize with AWS Lambda state data. By default, the Lambda registrator manages Lambda services for all Consul datacenters. When configured for a specific datacenter, Lambda registrator only manages Lambda services with a matching datacenter tag. Refer to [Supported tags](#supported-tags) for additional information. | +| `consul_extension_data_prefix` | Specifies the path prefix in the AWS Parameter Store under which the registrator manages mTLS data. If Lambda functions call mesh services, the value must be set to a non-empty string starting with `/`. | +| `consul_ca_cert_path` | Specifies the path to the CA certificate stored in the AWS Parameter Store. When Lambda registrator makes an HTTPS request to Consul's API and the Consul API is signed by a custom CA, Lambda registrator uses this CA certificate to verify the authenticity of the Consul API. At startup, Lambda registrator pulls the CA certificate at this path from Parameter Store, writes the certificate to the filesystem and stores the path of that file in `CONSUL_CACERT`. Also refer to [Optional: Store the CA Certificate in Parameter Store](#optional-store-the-ca-certificate-in-parameter-store).| +| `consul_http_token_path` | Specifies the path to the ACL token stored in AWS Parameter Store that Lambda registrator presents to access resources. This parameter is only required when ACLs are enabled for the Consul server. It is used to fetch an ACL token from Parameter Store and is stored in the `CONSUL_HTTP_TOKEN` environment variable. Also refer tp [Optional: Store the ACL Token in Parameter Store](#optional-store-the-acl-token-in-parameter-store).| +| `node_name` | The Consul node name that Lambdas are registered to. Defaults to `lambdas`. | +| `enterprise` | Determines if the Consul server at `consul_http_addr` is running open source Consul or Consul Enterprise. | +| `partitions` | The partitions that Lambda registrator manages. | + +## Deploy the Lambda registrator + +1. Create a Terraform configuration and specify the `lambda-registrator` module. In the following example, the Lambda registrator is deployed to `https://consul.example.com:8501`. Refer to [the Lambda registrator module documentation](https://registry.terraform.io/modules/hashicorp/consul-lambda-registrator/aws/0.1.0-beta1/submodules/lambda-registrator) for additional usage information: + ```hcl + module "lambda-registrator" { + source = "hashicorp/consul-lambda/consul-lambda-registrator" + version = "x.y.z" + name = "consul-lambda-registrator" + consul_http_addr = “https://aecfe39d629774e348a9844439f5e3c1-1471365273.us-east-1.elb.amazonaws.com:8501” + ca_cert_path = aws_ssm_parameter.ca-cert.name + http_token_path = aws_ssm_parameter.acl-token.name + consul_extension_data_prefix = “/lambda_extension_data” + } + ``` + +1. Deploy Lambda registrator with `terraform apply`. + + +## Register Lambda functions + +Lambda registrator registers Lambda functions into Consul, regardless of how the functions are +deployed. The following procedure describes how to register Lambda functions with the Lambda registrator using Terraform, but you can also deploy a Lambda function with CloudFormation, the AWS user +interface, or Cloud Development Kit (CDK): + +1. Add the `aws_lambda_function` resource to your Terraform configuration and specify the name of the Lambda function. +1. Add a `tags` block to the resource and specify the tags you want to use to register the function (refer to [Supported tags](#supported-tags)). + +In the following example, the `example` Lambda function is registered using the `enabled`, `payload-passthrough`, and `invocation-mode` tags: + +```hcl +resource "aws_lambda_function" "example" { + … + function_name = "lambda" + tags = { + "serverless.consul.hashicorp.com/v1alpha1/lambda/enabled" = "true" + "serverless.consul.hashicorp.com/v1alpha1/lambda/payload-passthrough" = "true" + "serverless.consul.hashicorp.com/v1alpha1/lambda/invocation-mode" = "ASYNCHRONOUS" + } +} +``` + +### Supported tags + +The following tags are supported. The path prefix for all tags is `serverless.consul.hashicorp.com/v1alpha1/lambda`. For example, specify the following tag to enable the Lambda registrator to sync the Lambda with Consul: + +`serverless.consul.hashicorp.com/v1alpha1/lambda/enabled`. + +| Tag | Description | +| - | - | +| `/enabled` | Enables the Lambda registrator to sync the Lambda with Consul. | +| `/payload-passthrough` | Determines if the body Envoy receives is converted to JSON or directly passed to Lambda. This attribute is optional and defaults to `false`. | +| `/invocation-mode` | Specifies the [Lambda invocation mode](https://docs.aws.amazon.com/lambda/latest/operatorguide/invocation-modes.html) Consul uses to invoke the Lambda. The default is `SYNCHRONOUS`, but `ASYNCHRONOUS` invocations are also supported. | +| `/datacenter` | Specifies the Consul datacenter in which to register the service. The default is the datacenter configured for Lambda registrator. | +| `/namespace` | Specifies the Consul namespace the service is registered in. Default is `default` if `enterprise` is enabled. | +| `/partition` | Specifies the Consul partition the service is registered in. Defaults is `default` if `enterprise` is enabled. | +| `/aliases` | Specifies a `+`-separated string of Lambda aliases that are registered into Consul. For example, if set to `dev+staging+prod`, the `dev`, `staging`, and `prod` aliases of the Lambda function are registered into Consul. | diff --git a/website/content/docs/lambda/registration/manual.mdx b/website/content/docs/lambda/registration/manual.mdx new file mode 100644 index 0000000000..bcf58c4b66 --- /dev/null +++ b/website/content/docs/lambda/registration/manual.mdx @@ -0,0 +1,84 @@ +--- +layout: docs +page_title: Manual Lambda Function Registration +description: >- + Register AWS Lambda functions with Consul service mesh using the Consul Lambda registrator. The Consul Lambda registrator automates Lambda function registration. +--- + +# Manual Lambda Function Registration + +This topic describes how to manually register Lambda functions into Consul. Refer to [Automate Lambda Function Registration](/docs/lambda/registration/automate) for information about using the Lambda registrator to automate registration. + +## Requirements + +Verify that your environment meets the requirements specified in [Lambda Function Registration Requirements](/docs/lambda/registration/index). + +To manually register Lambda functions so that mesh services can invoke them, you must create and apply a service registration configuration for the Lambda function and write a [service defaults configuration entry](/docs/connect/config-entries/service-defaults) for the function. + +## Register a Lambda function + +You can manually register Lambda functions if you are unable to automate the process using the Lambda registrator. + +1. Create a configuration for registering the service. You can copy the following example and replace `` with your Consul service name for the Lambda function: + + + + ```json + { + "Node": "lambdas", + "SkipNodeUpdate": true, + "NodeMeta": { + "external-node": "true", + "external-probe": "true" + }, + "Service": { + "Service": "" + } + } + ``` + + + +1. Save the configuration to `lambda.json`. + +1. Send the configuration to the `catalog/register` API endpoint to register the service, for example: + ```shell-session + $ curl --request PUT --data @lambda.json localhost:8500/v1/catalog/register + ``` + +1. Create the `service-defaults` configuration entry and include the AWS tags used to invoke the Lambda function in the `Meta` field (refer to [Supported `Meta` fields](#supported-meta-fields). The following example creates a `service-defaults` configuration entry named `lambda`: + + + + ```hcl + Kind = "service-defaults" + Name = "lambda" + Protocol = "http" + Meta = { + "serverless.consul.hashicorp.com/v1alpha1/lambda/enabled" = "true" + "serverless.consul.hashicorp.com/v1alpha1/lambda/arn" = "" + "serverless.consul.hashicorp.com/v1alpha1/lambda/payload-passthrough" = "true" + "serverless.consul.hashicorp.com/v1alpha1/lambda/region" = "us-east-2" + } + ``` + + + +1. Issue the `consul config write` command to store the configuration entry. For example: + ```shell-session + $ consul config write lambda-service-defaults.hcl + ``` + +### Supported `Meta` fields + +The following tags are supported. The path prefix for all tags is `serverless.consul.hashicorp.com/v1alpha1/lambda`. For example, specify the following tag to enable Consul to configure the service as an AWS Lambda function: + +`serverless.consul.hashicorp.com/v1alpha1/lambda/enabled`. + +| Tag | Description | +| --- | --- | +| `/enabled` | Determines if Consul configures the service as an AWS Lambda. | +| `/payload-passthrough` | Determines if the body Envoy receives is converted to JSON or directly passed to Lambda. | +| `/arn` | Specifies the [AWS ARN](https://docs.aws.amazon.com/general/latest/gr/aws-arns-and-namespaces.html) for the service's Lambda. | +| `/invocation-mode` | Determines if Consul configures the Lambda to be invoked using the `synchronous` or `asynchronous` [invocation mode](https://docs.aws.amazon.com/lambda/latest/operatorguide/invocation-modes.html). | +| `/region` | Specifies the AWS region the Lambda is running in. | diff --git a/website/content/docs/release-notes/consul-k8s/v0_49_x.mdx b/website/content/docs/release-notes/consul-k8s/v0_49_x.mdx new file mode 100644 index 0000000000..5f9725cd2a --- /dev/null +++ b/website/content/docs/release-notes/consul-k8s/v0_49_x.mdx @@ -0,0 +1,46 @@ +--- +layout: docs +page_title: 0.49.x +description: >- + Consul on Kubernetes release notes for version 0.49.x +--- + +# Consul on Kubernetes 0.49.0 + +## Release Highlights + +- **Consul CNI Plugin - OpenShift support**: Support for OpenShift and Multus CNI plugin [[GH-1527](https://github.com/hashicorp/consul-k8s/pull/1527)] + +- **Consul API Gateway secondary datacenter support**: Use global ACL auth method to provision ACL tokens for API Gateway in secondary datacenter and Set primary datacenter flag when deploying controller into secondary datacenter with federation enabled [[GH-1481](https://github.com/hashicorp/consul-k8s/pull/1481)] + +- **Cluster Peering**: Utilize new `use_auto_cert` value to gRPC TLS config when auto-encrypt is enabled. [[GH-1541](https://github.com/hashicorp/consul-k8s/pull/1541)] + +- **Service tag annotation improvements**: Support escaped commas in service tag annotations for pods which use consul.hashicorp.com/connect-service-tags or consul.hashicorp.com/service-tags. [[GH-1532](https://github.com/hashicorp/consul-k8s/pull/1532)] + +- **MaxInboundConnections in service-defaults CRD**: Add support for MaxInboundConnections on the Service Defaults CRD. [[GH-1437](https://github.com/hashicorp/consul-k8s/pull/1437)] + +## Supported Software + +- Consul 1.11.x, Consul 1.12.x and Consul 1.13.1+ +- Kubernetes 1.19-1.24 +- Kubectl 1.19+ +- Helm 3.2+ +- Envoy proxy support is determined by the Consul version deployed. Refer to + [Envoy Integration](/docs/connect/proxies/envoy) for details. + +## Upgrading + +For detailed information on upgrading, please refer to the [Upgrades page](/docs/k8s/upgrade) + +## Known Issues +The following issues are know to exist in the v0.49.0 release: + +- Kubernetes 1.25 is not supported as the [Pod Security Admission controller](https://kubernetes.io/blog/2022/08/25/pod-security-admission-stable/) is currently not supported by Consul K8s. + +## Changelogs + +The changelogs for this major release version and any maintenance versions are listed below. + +~> **Note:** The following link takes you to the changelogs on the GitHub website. + +- [0.49.0](https://github.com/hashicorp/consul-k8s/releases/tag/v0.49.0) diff --git a/website/content/docs/upgrading/upgrade-specific.mdx b/website/content/docs/upgrading/upgrade-specific.mdx index 705c233f59..de67ad6d86 100644 --- a/website/content/docs/upgrading/upgrade-specific.mdx +++ b/website/content/docs/upgrading/upgrade-specific.mdx @@ -45,6 +45,7 @@ review the following guidances relevant to your deployment: - [All service mesh deployments](#all-service-mesh-deployments) - [Service mesh deployments using auto-encrypt or auto-config](#service-mesh-deployments-using-auto-encrypt-or-auto-config) - [Service mesh deployments without the HTTPS port enabled on Consul agents](#service-mesh-deployments-without-the-https-port-enabled-on-consul-agents) +- [All service mesh deployments using the Vault CA provider](#modify-vault-policy-for-vault-ca-provider) #### All service mesh deployments @@ -170,6 +171,38 @@ such as with flags or environment variables like [`-ca-file`](/commands/connect/envoy#ca-file) and [`CONSUL_CACERT`](/commands#consul_cacert). +#### Modify Vault policy for Vault CA provider + +If using the Vault CA provider, +you must modify the Vault policy used by Consul to interact with Vault +so that certificates required for service mesh operation can still be generated. +The policy must include the `update` capability on the intermediate PKI's tune mount configuration endpoint +at path `/sys/mounts//tune`. +Refer to the [Vault CA provider documentation](/docs/connect/ca/vault#vault-acl-policies) +for updated example Vault policies for use with Vault-managed or Consul-managed PKI paths. + +You are using the Vault CA provider if either of the following configurations exists: +- The Consul server agent configuration option [`connect.ca_provider`](/docs/agent/config/config-files#connect_ca_provider) is set to `vault`, or +- The Consul on Kubernetes Helm Chart [`global.secretsBackend.vault.connectCA`](/docs/k8s/helm#v-global-secretsbackend-vault-connectca) value is configured. + +Though this guidance is listed in the 1.13.x section, it applies to all of the following release series: +- Consul 1.13.x: applies to 1.13.2+ +- Consul 1.12.x: applies to 1.12.5+ +- Consul 1.11.x: applies to 1.11.9+ + +Those affected Consul versions contain a +[bugfix that allows the intermediate CA's TTL configuration to be modified](https://github.com/hashicorp/consul/pull/14516). +The bugfix requires the `update` capability to tune that configuration. +Without the `update` capability, those affected Consul versions +cannot provide services with the certificates they need to participate in the mesh. +In an upcoming patch for each of those release series, +we will restore the intermediate CA's ability to provide certificates even without the `update` capability on the tune configuration endpoint, +though the `update` capability will still be needed to modify the CA's TTL configuration. + +We recommend modifying the Vault policy before upgrading to Consul 1.11 or later +to ensure your organization does not accidentally miss this guidance when performing subsequent upgrades, +such as to the latest patch within a release series. + ### 1.9 Telemetry Compatibility #### Removing configuration options @@ -178,7 +211,17 @@ The [`disable_compat_19`](/docs/agent/options#telemetry-disable_compat_1.9) tele In prior Consul versions (1.10.x through 1.11.x), the config defaulted to `false`. In 1.12.x it defaulted to `true`. If you were using this flag, you must remove it before upgrading. -## Consul 1.12.0 +### Modify Vault Policy for Vault CA Provider + +Follow the same guidance as provided in the +[1.13 upgrade section for modifying the Vault policy if using the Vault CA provider](#modify-vault-policy-for-vault-ca-provider). + +## Consul 1.12.x ((#consul-1-12-0)) + +### Modify Vault Policy for Vault CA Provider + +Follow the same guidance as provided in the +[1.13 upgrade section for modifying the Vault policy if using the Vault CA provider](#modify-vault-policy-for-vault-ca-provider). ### 1.9 Telemetry Compatibility @@ -216,7 +259,7 @@ be replaced with the new [`tls` stanza](/docs/agent/config/config-files#tls-conf - `verify_outgoing` - `verify_server_hostname` -## Consul 1.11.0 +## Consul 1.11.x ((#consul-1-11-0)) ### 1.10 Compatibility Consul Enterprise versions 1.10.0 through 1.10.4 contain a latent bug that @@ -291,6 +334,11 @@ When upgrading to Consul 1.10, you must ensure that the Envoy sidecars are restarted and bootstrapped using a version of the Consul CLI >= 1.10. This ensures your sidecars are supported by Consul 1.11. +### Modify Vault Policy for Vault CA Provider + +Follow the same guidance as provided in the +[1.13 upgrade section for modifying the Vault policy if using the Vault CA provider](#modify-vault-policy-for-vault-ca-provider). + ## Consul 1.10.0 ### Licensing Changes diff --git a/website/package-lock.json b/website/package-lock.json index ec5efe7b6d..75f0b6faaa 100644 --- a/website/package-lock.json +++ b/website/package-lock.json @@ -11,6 +11,7 @@ "@hashicorp/platform-cli": "^1.2.0", "dart-linkcheck": "2.0.15", "husky": "4.3.8", + "next": "^12.3.1", "prettier": "2.2.1" }, "engines": { @@ -589,34 +590,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@hapi/accept": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/@hapi/accept/-/accept-5.0.2.tgz", - "integrity": "sha512-CmzBx/bXUR8451fnZRuZAJRlzgm0Jgu5dltTX/bszmR2lheb9BpyN47Q1RbaGTsvFzn0PXAEs+lXDKfshccYZw==", - "dev": true, - "peer": true, - "dependencies": { - "@hapi/boom": "9.x.x", - "@hapi/hoek": "9.x.x" - } - }, - "node_modules/@hapi/boom": { - "version": "9.1.3", - "resolved": "https://registry.npmjs.org/@hapi/boom/-/boom-9.1.3.tgz", - "integrity": "sha512-RlrGyZ603hE/eRTZtTltocRm50HHmrmL3kGOP0SQ9MasazlW1mt/fkv4C5P/6rnpFXjwld/POFX1C8tMZE3ldg==", - "dev": true, - "peer": true, - "dependencies": { - "@hapi/hoek": "9.x.x" - } - }, - "node_modules/@hapi/hoek": { - "version": "9.2.0", - "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.2.0.tgz", - "integrity": "sha512-sqKVVVOe5ivCaXDWivIJYVSaEgdQK9ul7a4Kity5Iw7u9+wBAPbX1RMSnLLmp7O4Vzj0WOWwMAJsTL00xwaNug==", - "dev": true, - "peer": true - }, "node_modules/@hashicorp/platform-cli": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@hashicorp/platform-cli/-/platform-cli-1.2.0.tgz", @@ -1052,19 +1025,11 @@ "node": ">= 10.14.2" } }, - "node_modules/@napi-rs/triples": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@napi-rs/triples/-/triples-1.0.3.tgz", - "integrity": "sha512-jDJTpta+P4p1NZTFVLHJ/TLFVYVcOqv6l8xwOeBKNPMgY/zDYH/YH7SJbvrr/h1RcS9GzbPcLKGzpuK9cV56UA==", - "dev": true, - "peer": true - }, "node_modules/@next/env": { - "version": "11.1.2", - "resolved": "https://registry.npmjs.org/@next/env/-/env-11.1.2.tgz", - "integrity": "sha512-+fteyVdQ7C/OoulfcF6vd1Yk0FEli4453gr8kSFbU8sKseNSizYq6df5MKz/AjwLptsxrUeIkgBdAzbziyJ3mA==", - "dev": true, - "peer": true + "version": "12.3.1", + "resolved": "https://registry.npmjs.org/@next/env/-/env-12.3.1.tgz", + "integrity": "sha512-9P9THmRFVKGKt9DYqeC2aKIxm8rlvkK38V1P1sRE7qyoPBIs8l9oo79QoSdPtOWfzkbDAVUqvbQGgTMsb8BtJg==", + "dev": true }, "node_modules/@next/eslint-plugin-next": { "version": "11.1.2", @@ -1075,160 +1040,212 @@ "glob": "7.1.7" } }, - "node_modules/@next/polyfill-module": { - "version": "11.1.2", - "resolved": "https://registry.npmjs.org/@next/polyfill-module/-/polyfill-module-11.1.2.tgz", - "integrity": "sha512-xZmixqADM3xxtqBV0TpAwSFzWJP0MOQzRfzItHXf1LdQHWb0yofHHC+7eOrPFic8+ZGz5y7BdPkkgR1S25OymA==", + "node_modules/@next/swc-android-arm-eabi": { + "version": "12.3.1", + "resolved": "https://registry.npmjs.org/@next/swc-android-arm-eabi/-/swc-android-arm-eabi-12.3.1.tgz", + "integrity": "sha512-i+BvKA8tB//srVPPQxIQN5lvfROcfv4OB23/L1nXznP+N/TyKL8lql3l7oo2LNhnH66zWhfoemg3Q4VJZSruzQ==", + "cpu": [ + "arm" + ], "dev": true, - "peer": true + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } }, - "node_modules/@next/react-dev-overlay": { - "version": "11.1.2", - "resolved": "https://registry.npmjs.org/@next/react-dev-overlay/-/react-dev-overlay-11.1.2.tgz", - "integrity": "sha512-rDF/mGY2NC69mMg2vDqzVpCOlWqnwPUXB2zkARhvknUHyS6QJphPYv9ozoPJuoT/QBs49JJd9KWaAzVBvq920A==", + "node_modules/@next/swc-android-arm64": { + "version": "12.3.1", + "resolved": "https://registry.npmjs.org/@next/swc-android-arm64/-/swc-android-arm64-12.3.1.tgz", + "integrity": "sha512-CmgU2ZNyBP0rkugOOqLnjl3+eRpXBzB/I2sjwcGZ7/Z6RcUJXK5Evz+N0ucOxqE4cZ3gkTeXtSzRrMK2mGYV8Q==", + "cpu": [ + "arm64" + ], "dev": true, - "peer": true, - "dependencies": { - "@babel/code-frame": "7.12.11", - "anser": "1.4.9", - "chalk": "4.0.0", - "classnames": "2.2.6", - "css.escape": "1.5.1", - "data-uri-to-buffer": "3.0.1", - "platform": "1.3.6", - "shell-quote": "1.7.2", - "source-map": "0.8.0-beta.0", - "stacktrace-parser": "0.1.10", - "strip-ansi": "6.0.0" - }, - "peerDependencies": { - "react": "^17.0.2", - "react-dom": "^17.0.2" + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" } }, - "node_modules/@next/react-dev-overlay/node_modules/@babel/code-frame": { - "version": "7.12.11", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.11.tgz", - "integrity": "sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw==", + "node_modules/@next/swc-darwin-arm64": { + "version": "12.3.1", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-12.3.1.tgz", + "integrity": "sha512-hT/EBGNcu0ITiuWDYU9ur57Oa4LybD5DOQp4f22T6zLfpoBMfBibPtR8XktXmOyFHrL/6FC2p9ojdLZhWhvBHg==", + "cpu": [ + "arm64" + ], "dev": true, - "peer": true, - "dependencies": { - "@babel/highlight": "^7.10.4" + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" } }, - "node_modules/@next/react-dev-overlay/node_modules/chalk": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.0.0.tgz", - "integrity": "sha512-N9oWFcegS0sFr9oh1oz2d7Npos6vNoWW9HvtCg5N1KRFpUhaAhvTv5Y58g880fZaEYSNm3qDz8SU1UrGvp+n7A==", + "node_modules/@next/swc-darwin-x64": { + "version": "12.3.1", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-12.3.1.tgz", + "integrity": "sha512-9S6EVueCVCyGf2vuiLiGEHZCJcPAxglyckTZcEwLdJwozLqN0gtS0Eq0bQlGS3dH49Py/rQYpZ3KVWZ9BUf/WA==", + "cpu": [ + "x64" + ], "dev": true, - "peer": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "node": ">= 10" } }, - "node_modules/@next/react-dev-overlay/node_modules/classnames": { - "version": "2.2.6", - "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.2.6.tgz", - "integrity": "sha512-JR/iSQOSt+LQIWwrwEzJ9uk0xfN3mTVYMwt1Ir5mUcSN6pU+V4zQFFaJsclJbPuAUQH+yfWef6tm7l1quW3C8Q==", + "node_modules/@next/swc-freebsd-x64": { + "version": "12.3.1", + "resolved": "https://registry.npmjs.org/@next/swc-freebsd-x64/-/swc-freebsd-x64-12.3.1.tgz", + "integrity": "sha512-qcuUQkaBZWqzM0F1N4AkAh88lLzzpfE6ImOcI1P6YeyJSsBmpBIV8o70zV+Wxpc26yV9vpzb+e5gCyxNjKJg5Q==", + "cpu": [ + "x64" + ], "dev": true, - "peer": true + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10" + } }, - "node_modules/@next/react-dev-overlay/node_modules/source-map": { - "version": "0.8.0-beta.0", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.8.0-beta.0.tgz", - "integrity": "sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==", + "node_modules/@next/swc-linux-arm-gnueabihf": { + "version": "12.3.1", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm-gnueabihf/-/swc-linux-arm-gnueabihf-12.3.1.tgz", + "integrity": "sha512-diL9MSYrEI5nY2wc/h/DBewEDUzr/DqBjIgHJ3RUNtETAOB3spMNHvJk2XKUDjnQuluLmFMloet9tpEqU2TT9w==", + "cpu": [ + "arm" + ], "dev": true, - "peer": true, - "dependencies": { - "whatwg-url": "^7.0.0" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">= 8" + "node": ">= 10" } }, - "node_modules/@next/react-refresh-utils": { - "version": "11.1.2", - "resolved": "https://registry.npmjs.org/@next/react-refresh-utils/-/react-refresh-utils-11.1.2.tgz", - "integrity": "sha512-hsoJmPfhVqjZ8w4IFzoo8SyECVnN+8WMnImTbTKrRUHOVJcYMmKLL7xf7T0ft00tWwAl/3f3Q3poWIN2Ueql/Q==", + "node_modules/@next/swc-linux-arm64-gnu": { + "version": "12.3.1", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-12.3.1.tgz", + "integrity": "sha512-o/xB2nztoaC7jnXU3Q36vGgOolJpsGG8ETNjxM1VAPxRwM7FyGCPHOMk1XavG88QZSQf+1r+POBW0tLxQOJ9DQ==", + "cpu": [ + "arm64" + ], "dev": true, - "peer": true, - "peerDependencies": { - "react-refresh": "0.8.3", - "webpack": "^4 || ^5" - }, - "peerDependenciesMeta": { - "webpack": { - "optional": true - } + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" } }, - "node_modules/@next/swc-darwin-arm64": { - "version": "11.1.2", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-11.1.2.tgz", - "integrity": "sha512-hZuwOlGOwBZADA8EyDYyjx3+4JGIGjSHDHWrmpI7g5rFmQNltjlbaefAbiU5Kk7j3BUSDwt30quJRFv3nyJQ0w==", - "cpu": ["arm64"], + "node_modules/@next/swc-linux-arm64-musl": { + "version": "12.3.1", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-12.3.1.tgz", + "integrity": "sha512-2WEasRxJzgAmP43glFNhADpe8zB7kJofhEAVNbDJZANp+H4+wq+/cW1CdDi8DqjkShPEA6/ejJw+xnEyDID2jg==", + "cpu": [ + "arm64" + ], "dev": true, "optional": true, - "os": ["darwin"], - "peer": true, + "os": [ + "linux" + ], "engines": { "node": ">= 10" } }, - "node_modules/@next/swc-darwin-x64": { - "version": "11.1.2", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-11.1.2.tgz", - "integrity": "sha512-PGOp0E1GisU+EJJlsmJVGE+aPYD0Uh7zqgsrpD3F/Y3766Ptfbe1lEPPWnRDl+OzSSrSrX1lkyM/Jlmh5OwNvA==", - "cpu": ["x64"], + "node_modules/@next/swc-linux-x64-gnu": { + "version": "12.3.1", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-12.3.1.tgz", + "integrity": "sha512-JWEaMyvNrXuM3dyy9Pp5cFPuSSvG82+yABqsWugjWlvfmnlnx9HOQZY23bFq3cNghy5V/t0iPb6cffzRWylgsA==", + "cpu": [ + "x64" + ], "dev": true, "optional": true, - "os": ["darwin"], - "peer": true, + "os": [ + "linux" + ], "engines": { "node": ">= 10" } }, - "node_modules/@next/swc-linux-x64-gnu": { - "version": "11.1.2", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-11.1.2.tgz", - "integrity": "sha512-YcDHTJjn/8RqvyJVB6pvEKXihDcdrOwga3GfMv/QtVeLphTouY4BIcEUfrG5+26Nf37MP1ywN3RRl1TxpurAsQ==", - "cpu": ["x64"], + "node_modules/@next/swc-linux-x64-musl": { + "version": "12.3.1", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-12.3.1.tgz", + "integrity": "sha512-xoEWQQ71waWc4BZcOjmatuvPUXKTv6MbIFzpm4LFeCHsg2iwai0ILmNXf81rJR+L1Wb9ifEke2sQpZSPNz1Iyg==", + "cpu": [ + "x64" + ], "dev": true, "optional": true, - "os": ["linux"], - "peer": true, + "os": [ + "linux" + ], "engines": { "node": ">= 10" } }, - "node_modules/@next/swc-win32-x64-msvc": { - "version": "11.1.2", - "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-11.1.2.tgz", - "integrity": "sha512-e/pIKVdB+tGQYa1cW3sAeHm8gzEri/HYLZHT4WZojrUxgWXqx8pk7S7Xs47uBcFTqBDRvK3EcQpPLf3XdVsDdg==", - "cpu": ["x64"], + "node_modules/@next/swc-win32-arm64-msvc": { + "version": "12.3.1", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-12.3.1.tgz", + "integrity": "sha512-hswVFYQYIeGHE2JYaBVtvqmBQ1CppplQbZJS/JgrVI3x2CurNhEkmds/yqvDONfwfbttTtH4+q9Dzf/WVl3Opw==", + "cpu": [ + "arm64" + ], "dev": true, "optional": true, - "os": ["win32"], - "peer": true, + "os": [ + "win32" + ], "engines": { "node": ">= 10" } }, - "node_modules/@node-rs/helper": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@node-rs/helper/-/helper-1.2.1.tgz", - "integrity": "sha512-R5wEmm8nbuQU0YGGmYVjEc0OHtYsuXdpRG+Ut/3wZ9XAvQWyThN08bTh2cBJgoZxHQUPtvRfeQuxcAgLuiBISg==", + "node_modules/@next/swc-win32-ia32-msvc": { + "version": "12.3.1", + "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-12.3.1.tgz", + "integrity": "sha512-Kny5JBehkTbKPmqulr5i+iKntO5YMP+bVM8Hf8UAmjSMVo3wehyLVc9IZkNmcbxi+vwETnQvJaT5ynYBkJ9dWA==", + "cpu": [ + "ia32" + ], "dev": true, - "peer": true, - "dependencies": { - "@napi-rs/triples": "^1.0.3" + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-x64-msvc": { + "version": "12.3.1", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-12.3.1.tgz", + "integrity": "sha512-W1ijvzzg+kPEX6LAc+50EYYSEo0FVu7dmTE+t+DM4iOLqgGHoW9uYSz9wCVdkXOEEMP9xhXfGpcSxsfDucyPkA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" } }, "node_modules/@nodelib/fs.scandir": { @@ -1320,6 +1337,21 @@ "postcss-syntax": ">=0.36.2" } }, + "node_modules/@swc/helpers": { + "version": "0.4.11", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.4.11.tgz", + "integrity": "sha512-rEUrBSGIoSFuYxwBYtlUFMlE2CwGhmW+w9355/5oduSw8e5h2+Tj4UrAGNNgP9915++wj5vkQo0UuOBqOAq4nw==", + "dev": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@swc/helpers/node_modules/tslib": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", + "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==", + "dev": true + }, "node_modules/@tootallnate/once": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", @@ -1737,13 +1769,6 @@ "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/anser": { - "version": "1.4.9", - "resolved": "https://registry.npmjs.org/anser/-/anser-1.4.9.tgz", - "integrity": "sha512-AI+BjTeGt2+WFk4eWcqbQ7snZpDBt8SaLlj0RT2h5xfdWaiy51OjYvqwMrNzJLGy8iOAL6nKDITWO+rd4MkYEA==", - "dev": true, - "peer": true - }, "node_modules/ansi-colors": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", @@ -1952,39 +1977,6 @@ "node": ">=0.10.0" } }, - "node_modules/asn1.js": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-5.4.1.tgz", - "integrity": "sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA==", - "dev": true, - "peer": true, - "dependencies": { - "bn.js": "^4.0.0", - "inherits": "^2.0.1", - "minimalistic-assert": "^1.0.0", - "safer-buffer": "^2.1.0" - } - }, - "node_modules/asn1.js/node_modules/bn.js": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", - "dev": true, - "peer": true - }, - "node_modules/assert": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/assert/-/assert-2.0.0.tgz", - "integrity": "sha512-se5Cd+js9dXJnu6Ag2JFc00t+HmHOen+8Q+L7O9zI0PqQXr20uk2J0XQqMxZEeo5U50o8Nvmmx7dZrl+Ufr35A==", - "dev": true, - "peer": true, - "dependencies": { - "es6-object-assign": "^1.1.0", - "is-nan": "^1.2.1", - "object-is": "^1.0.1", - "util": "^0.12.0" - } - }, "node_modules/assign-symbols": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", @@ -1995,16 +1987,6 @@ "node": ">=0.10.0" } }, - "node_modules/ast-types": { - "version": "0.13.2", - "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.13.2.tgz", - "integrity": "sha512-uWMHxJxtfj/1oZClOxDEV1sQ1HCDkA4MG8Gr69KKeBjEVH0R84WlejZ0y2DcwyBlpAEMltmVYkVgqfLFb2oyiA==", - "dev": true, - "peer": true, - "engines": { - "node": ">=4" - } - }, "node_modules/ast-types-flow": { "version": "0.0.7", "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.7.tgz", @@ -2177,19 +2159,6 @@ "node": ">=6" } }, - "node_modules/available-typed-arrays": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.4.tgz", - "integrity": "sha512-SA5mXJWrId1TaQjfxUYghbqQ/hYioKmLJvPJyDuYRtXXenFNMjj4hSSt1Cf1xsuXSXrtxrVC5Ot4eU6cOtBDdA==", - "dev": true, - "peer": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/axe-core": { "version": "4.3.1", "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.3.1.tgz", @@ -2407,54 +2376,6 @@ "node": ">=0.10.0" } }, - "node_modules/base64-js": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "peer": true - }, - "node_modules/big.js": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", - "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", - "dev": true, - "peer": true, - "engines": { - "node": "*" - } - }, - "node_modules/binary-extensions": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", - "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", - "dev": true, - "peer": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/bn.js": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.0.tgz", - "integrity": "sha512-D7iWRBvnZE8ecXiLj/9wbxH7Tk79fAh8IHaTNq1RWRixsS02W+5qS+iE9yq6RYl0asXx5tw0bLhmT5pIfbSquw==", - "dev": true, - "peer": true - }, "node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -2477,13 +2398,6 @@ "node": ">=8" } }, - "node_modules/brorand": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", - "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=", - "dev": true, - "peer": true - }, "node_modules/browser-process-hrtime": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz", @@ -2491,149 +2405,49 @@ "dev": true, "peer": true }, - "node_modules/browserify-aes": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", - "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==", - "dev": true, - "peer": true, - "dependencies": { - "buffer-xor": "^1.0.3", - "cipher-base": "^1.0.0", - "create-hash": "^1.1.0", - "evp_bytestokey": "^1.0.3", - "inherits": "^2.0.1", - "safe-buffer": "^5.0.1" - } - }, - "node_modules/browserify-cipher": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/browserify-cipher/-/browserify-cipher-1.0.1.tgz", - "integrity": "sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w==", + "node_modules/browserslist": { + "version": "4.16.6", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.16.6.tgz", + "integrity": "sha512-Wspk/PqO+4W9qp5iUTJsa1B/QrYn1keNCcEP5OvP7WBwT4KaDly0uONYmC6Xa3Z5IqnUgS0KcgLYu1l74x0ZXQ==", "dev": true, - "peer": true, "dependencies": { - "browserify-aes": "^1.0.4", - "browserify-des": "^1.0.0", - "evp_bytestokey": "^1.0.0" + "caniuse-lite": "^1.0.30001219", + "colorette": "^1.2.2", + "electron-to-chromium": "^1.3.723", + "escalade": "^3.1.1", + "node-releases": "^1.1.71" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" } }, - "node_modules/browserify-des": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/browserify-des/-/browserify-des-1.0.2.tgz", - "integrity": "sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A==", + "node_modules/bs-logger": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz", + "integrity": "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==", "dev": true, - "peer": true, "dependencies": { - "cipher-base": "^1.0.1", - "des.js": "^1.0.0", - "inherits": "^2.0.1", - "safe-buffer": "^5.1.2" + "fast-json-stable-stringify": "2.x" + }, + "engines": { + "node": ">= 6" } }, - "node_modules/browserify-rsa": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.1.0.tgz", - "integrity": "sha512-AdEER0Hkspgno2aR97SAf6vi0y0k8NuOpGnVH3O99rcA5Q6sh8QxcngtHuJ6uXwnfAXNM4Gn1Gb7/MV1+Ymbog==", + "node_modules/bser": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", + "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", "dev": true, "peer": true, "dependencies": { - "bn.js": "^5.0.0", - "randombytes": "^2.0.1" - } - }, - "node_modules/browserify-sign": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.2.1.tgz", - "integrity": "sha512-/vrA5fguVAKKAVTNJjgSm1tRQDHUU6DbwO9IROu/0WAzC8PKhucDSh18J0RMvVeHAn5puMd+QHC2erPRNf8lmg==", - "dev": true, - "peer": true, - "dependencies": { - "bn.js": "^5.1.1", - "browserify-rsa": "^4.0.1", - "create-hash": "^1.2.0", - "create-hmac": "^1.1.7", - "elliptic": "^6.5.3", - "inherits": "^2.0.4", - "parse-asn1": "^5.1.5", - "readable-stream": "^3.6.0", - "safe-buffer": "^5.2.0" - } - }, - "node_modules/browserify-sign/node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "peer": true - }, - "node_modules/browserify-zlib": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.2.0.tgz", - "integrity": "sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA==", - "dev": true, - "peer": true, - "dependencies": { - "pako": "~1.0.5" - } - }, - "node_modules/browserslist": { - "version": "4.16.6", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.16.6.tgz", - "integrity": "sha512-Wspk/PqO+4W9qp5iUTJsa1B/QrYn1keNCcEP5OvP7WBwT4KaDly0uONYmC6Xa3Z5IqnUgS0KcgLYu1l74x0ZXQ==", - "dev": true, - "dependencies": { - "caniuse-lite": "^1.0.30001219", - "colorette": "^1.2.2", - "electron-to-chromium": "^1.3.723", - "escalade": "^3.1.1", - "node-releases": "^1.1.71" - }, - "bin": { - "browserslist": "cli.js" - }, - "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - } - }, - "node_modules/bs-logger": { - "version": "0.2.6", - "resolved": "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz", - "integrity": "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==", - "dev": true, - "dependencies": { - "fast-json-stable-stringify": "2.x" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/bser": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", - "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", - "dev": true, - "peer": true, - "dependencies": { - "node-int64": "^0.4.0" + "node-int64": "^0.4.0" } }, "node_modules/buffer-from": { @@ -2642,30 +2456,6 @@ "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", "dev": true }, - "node_modules/buffer-xor": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz", - "integrity": "sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk=", - "dev": true, - "peer": true - }, - "node_modules/builtin-status-codes": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz", - "integrity": "sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug=", - "dev": true, - "peer": true - }, - "node_modules/bytes": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", - "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==", - "dev": true, - "peer": true, - "engines": { - "node": ">= 0.8" - } - }, "node_modules/cache-base": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", @@ -2736,14 +2526,20 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001246", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001246.tgz", - "integrity": "sha512-Tc+ff0Co/nFNbLOrziBXmMVtpt9S2c2Y+Z9Nk9Khj09J+0zR9ejvIW5qkZAErCbOrVODCx/MN+GpB5FNBs5GFA==", + "version": "1.0.30001416", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001416.tgz", + "integrity": "sha512-06wzzdAkCPZO+Qm4e/eNghZBDfVNDsCgw33T27OwBH9unE9S478OYw//Q2L7Npf/zBzs7rjZOszIFQkwQKAEqA==", "dev": true, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - } + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + } + ] }, "node_modules/capture-exit": { "version": "2.0.0", @@ -2820,45 +2616,12 @@ "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", "dev": true }, - "node_modules/chokidar": { - "version": "3.5.1", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.1.tgz", - "integrity": "sha512-9+s+Od+W0VJJzawDma/gvBNQqkTiqYTWLuZoyAsivsI4AaWTCzHG06/TMjsf1cYe9Cb97UCEhjz7HvnPk2p/tw==", - "dev": true, - "peer": true, - "dependencies": { - "anymatch": "~3.1.1", - "braces": "~3.0.2", - "glob-parent": "~5.1.0", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.5.0" - }, - "engines": { - "node": ">= 8.10.0" - }, - "optionalDependencies": { - "fsevents": "~2.3.1" - } - }, "node_modules/ci-info": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", "dev": true }, - "node_modules/cipher-base": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz", - "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==", - "dev": true, - "peer": true, - "dependencies": { - "inherits": "^2.0.1", - "safe-buffer": "^5.0.1" - } - }, "node_modules/cjs-module-lexer": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-0.6.0.tgz", @@ -3150,13 +2913,6 @@ "node": ">= 10" } }, - "node_modules/commondir": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", - "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=", - "dev": true, - "peer": true - }, "node_modules/compare-versions": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/compare-versions/-/compare-versions-3.6.0.tgz", @@ -3176,20 +2932,6 @@ "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", "dev": true }, - "node_modules/console-browserify": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.2.0.tgz", - "integrity": "sha512-ZMkYO/LkF17QvCPqM0gxw8yUzigAOZOSWSHg91FH6orS7vcEj5dVZTidN2fQ14yBSdg97RqhSNwLUXInd52OTA==", - "dev": true, - "peer": true - }, - "node_modules/constants-browserify": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/constants-browserify/-/constants-browserify-1.0.0.tgz", - "integrity": "sha1-wguW2MYXdIqvHBYCF2DNJ/y4y3U=", - "dev": true, - "peer": true - }, "node_modules/convert-source-map": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.7.0.tgz", @@ -3220,13 +2962,6 @@ "url": "https://opencollective.com/core-js" } }, - "node_modules/core-util-is": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", - "dev": true, - "peer": true - }, "node_modules/cosmiconfig": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.0.0.tgz", @@ -3243,53 +2978,6 @@ "node": ">=10" } }, - "node_modules/create-ecdh": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.4.tgz", - "integrity": "sha512-mf+TCx8wWc9VpuxfP2ht0iSISLZnt0JgWlrOKZiNqyUZWnjIaCIVNQArMHnCZKfEYRg6IM7A+NeJoN8gf/Ws0A==", - "dev": true, - "peer": true, - "dependencies": { - "bn.js": "^4.1.0", - "elliptic": "^6.5.3" - } - }, - "node_modules/create-ecdh/node_modules/bn.js": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", - "dev": true, - "peer": true - }, - "node_modules/create-hash": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", - "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", - "dev": true, - "peer": true, - "dependencies": { - "cipher-base": "^1.0.1", - "inherits": "^2.0.1", - "md5.js": "^1.3.4", - "ripemd160": "^2.0.1", - "sha.js": "^2.4.0" - } - }, - "node_modules/create-hmac": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", - "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==", - "dev": true, - "peer": true, - "dependencies": { - "cipher-base": "^1.0.3", - "create-hash": "^1.1.0", - "inherits": "^2.0.1", - "ripemd160": "^2.0.0", - "safe-buffer": "^5.0.1", - "sha.js": "^2.4.8" - } - }, "node_modules/cross-fetch": { "version": "3.1.4", "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.4.tgz", @@ -3313,36 +3001,6 @@ "node": ">= 8" } }, - "node_modules/crypto-browserify": { - "version": "3.12.0", - "resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.12.0.tgz", - "integrity": "sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg==", - "dev": true, - "peer": true, - "dependencies": { - "browserify-cipher": "^1.0.0", - "browserify-sign": "^4.0.0", - "create-ecdh": "^4.0.0", - "create-hash": "^1.1.0", - "create-hmac": "^1.1.0", - "diffie-hellman": "^5.0.0", - "inherits": "^2.0.1", - "pbkdf2": "^3.0.3", - "public-encrypt": "^4.0.0", - "randombytes": "^2.0.0", - "randomfill": "^1.0.3" - }, - "engines": { - "node": "*" - } - }, - "node_modules/css.escape": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz", - "integrity": "sha1-QuJ9T6BK4y+TGktNQZH6nN3ul8s=", - "dev": true, - "peer": true - }, "node_modules/cssesc": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", @@ -3355,37 +3013,6 @@ "node": ">=4" } }, - "node_modules/cssnano-preset-simple": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/cssnano-preset-simple/-/cssnano-preset-simple-3.0.0.tgz", - "integrity": "sha512-vxQPeoMRqUT3c/9f0vWeVa2nKQIHFpogtoBvFdW4GQ3IvEJ6uauCP6p3Y5zQDLFcI7/+40FTgX12o7XUL0Ko+w==", - "dev": true, - "peer": true, - "dependencies": { - "caniuse-lite": "^1.0.30001202" - }, - "peerDependencies": { - "postcss": "^8.2.15" - } - }, - "node_modules/cssnano-simple": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/cssnano-simple/-/cssnano-simple-3.0.0.tgz", - "integrity": "sha512-oU3ueli5Dtwgh0DyeohcIEE00QVfbPR3HzyXdAl89SfnQG3y0/qcpfLVW+jPIh3/rgMZGwuW96rejZGaYE9eUg==", - "dev": true, - "peer": true, - "dependencies": { - "cssnano-preset-simple": "^3.0.0" - }, - "peerDependencies": { - "postcss": "^8.2.15" - }, - "peerDependenciesMeta": { - "postcss": { - "optional": true - } - } - }, "node_modules/cssom": { "version": "0.4.4", "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.4.4.tgz", @@ -3430,16 +3057,6 @@ "linkcheck-win": "bin/linkcheck-win" } }, - "node_modules/data-uri-to-buffer": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-3.0.1.tgz", - "integrity": "sha512-WboRycPNsVw3B3TL559F7kuBUM4d8CgMEvk6xEJlOp7OBPjt6G7z8WMWlD2rOFZLk6OYfFIUGsCOWzcQH9K2og==", - "dev": true, - "peer": true, - "engines": { - "node": ">= 6" - } - }, "node_modules/data-urls": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-2.0.0.tgz", @@ -3599,27 +3216,6 @@ "node": ">=0.4.0" } }, - "node_modules/depd": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", - "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=", - "dev": true, - "peer": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/des.js": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.0.1.tgz", - "integrity": "sha512-Q0I4pfFrv2VPd34/vfLrFOoRmlYj3OV50i7fskps1jZWK1kApMWWT9G6RRUeYedLcBDIhnSDaUvJMb3AhUlaEA==", - "dev": true, - "peer": true, - "dependencies": { - "inherits": "^2.0.1", - "minimalistic-assert": "^1.0.0" - } - }, "node_modules/detect-newline": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", @@ -3640,25 +3236,6 @@ "node": ">= 10.14.2" } }, - "node_modules/diffie-hellman": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz", - "integrity": "sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==", - "dev": true, - "peer": true, - "dependencies": { - "bn.js": "^4.1.0", - "miller-rabin": "^4.0.0", - "randombytes": "^2.0.0" - } - }, - "node_modules/diffie-hellman/node_modules/bn.js": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", - "dev": true, - "peer": true - }, "node_modules/dir-glob": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", @@ -3714,19 +3291,6 @@ "url": "https://github.com/fb55/entities?sponsor=1" } }, - "node_modules/domain-browser": { - "version": "4.19.0", - "resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-4.19.0.tgz", - "integrity": "sha512-fRA+BaAWOR/yr/t7T9E9GJztHPeFjj8U35ajyAjCDtAAnTn1Rc1f6W6VGPJrO1tkQv9zWu+JRof7z6oQtiYVFQ==", - "dev": true, - "peer": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://bevry.me/fund" - } - }, "node_modules/domelementtype": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz", @@ -3796,29 +3360,6 @@ "integrity": "sha512-WmCgAeURsMFiyoJ646eUaJQ7GNfvMRLXo+GamUyKVNEM4MqTAsXyC0f38JEB4N3BtbD0tlAKozGP5E2T9K3YGg==", "dev": true }, - "node_modules/elliptic": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz", - "integrity": "sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==", - "dev": true, - "peer": true, - "dependencies": { - "bn.js": "^4.11.9", - "brorand": "^1.1.0", - "hash.js": "^1.0.0", - "hmac-drbg": "^1.0.1", - "inherits": "^2.0.4", - "minimalistic-assert": "^1.0.1", - "minimalistic-crypto-utils": "^1.0.1" - } - }, - "node_modules/elliptic/node_modules/bn.js": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", - "dev": true, - "peer": true - }, "node_modules/emittery": { "version": "0.7.2", "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.7.2.tgz", @@ -3838,29 +3379,6 @@ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "dev": true }, - "node_modules/encoding": { - "version": "0.1.13", - "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz", - "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==", - "dev": true, - "peer": true, - "dependencies": { - "iconv-lite": "^0.6.2" - } - }, - "node_modules/encoding/node_modules/iconv-lite": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", - "dev": true, - "peer": true, - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/end-of-stream": { "version": "1.4.4", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", @@ -3945,13 +3463,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/es6-object-assign": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/es6-object-assign/-/es6-object-assign-1.1.0.tgz", - "integrity": "sha1-wsNYJlYkfDnqEHyx5mUrb58kUjw=", - "dev": true, - "peer": true - }, "node_modules/escalade": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", @@ -4916,41 +4427,10 @@ "node": ">=0.10.0" } }, - "node_modules/etag": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=", - "dev": true, - "peer": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/events": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", - "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", - "dev": true, - "peer": true, - "engines": { - "node": ">=0.8.x" - } - }, - "node_modules/evp_bytestokey": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz", - "integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==", - "dev": true, - "peer": true, - "dependencies": { - "md5.js": "^1.3.4", - "safe-buffer": "^5.1.1" - } - }, - "node_modules/exec-sh": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/exec-sh/-/exec-sh-0.3.6.tgz", - "integrity": "sha512-nQn+hI3yp+oD0huYhKwvYI32+JFeq+XkNcD1GAo3Y/MjxsfVGmrrzrnzjWiNY6f+pUCP440fThsFh5gZrRAU/w==", + "node_modules/exec-sh": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/exec-sh/-/exec-sh-0.3.6.tgz", + "integrity": "sha512-nQn+hI3yp+oD0huYhKwvYI32+JFeq+XkNcD1GAo3Y/MjxsfVGmrrzrnzjWiNY6f+pUCP440fThsFh5gZrRAU/w==", "dev": true, "peer": true }, @@ -5382,129 +4862,6 @@ "node": ">=8" } }, - "node_modules/find-cache-dir": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.1.tgz", - "integrity": "sha512-t2GDMt3oGC/v+BMwzmllWDuJF/xcDtE5j/fCGbqDD7OLuJkj0cfh1YSA5VKPvwMeLFLNDBkwOKZ2X85jGLVftQ==", - "dev": true, - "peer": true, - "dependencies": { - "commondir": "^1.0.1", - "make-dir": "^3.0.2", - "pkg-dir": "^4.1.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/avajs/find-cache-dir?sponsor=1" - } - }, - "node_modules/find-cache-dir/node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "peer": true, - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/find-cache-dir/node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "peer": true, - "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/find-cache-dir/node_modules/make-dir": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", - "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", - "dev": true, - "peer": true, - "dependencies": { - "semver": "^6.0.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/find-cache-dir/node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "peer": true, - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/find-cache-dir/node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "peer": true, - "dependencies": { - "p-limit": "^2.2.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/find-cache-dir/node_modules/p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true, - "peer": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/find-cache-dir/node_modules/pkg-dir": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", - "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", - "dev": true, - "peer": true, - "dependencies": { - "find-up": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/find-cache-dir/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true, - "peer": true, - "bin": { - "semver": "bin/semver.js" - } - }, "node_modules/find-up": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", @@ -5565,13 +4922,6 @@ "node": ">=0.10.0" } }, - "node_modules/foreach": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/foreach/-/foreach-2.0.5.tgz", - "integrity": "sha1-C+4AUBiusmDQo6865ljdATbsG5k=", - "dev": true, - "peer": true - }, "node_modules/form-data": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz", @@ -5627,7 +4977,9 @@ "dev": true, "hasInstallScript": true, "optional": true, - "os": ["darwin"], + "os": [ + "darwin" + ], "peer": true, "engines": { "node": "^8.16.0 || ^10.6.0 || >=11.0.0" @@ -5678,16 +5030,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/get-orientation": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/get-orientation/-/get-orientation-1.1.2.tgz", - "integrity": "sha512-/pViTfifW+gBbh/RnlFYHINvELT9Znt+SYyDKAUL6uV6By019AK/s+i9XP4jSwq7lwP38Fd8HVeTxym3+hkwmQ==", - "dev": true, - "peer": true, - "dependencies": { - "stream-parser": "^0.3.1" - } - }, "node_modules/get-own-enumerable-property-symbols": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.2.tgz", @@ -5774,13 +5116,6 @@ "node": ">= 6" } }, - "node_modules/glob-to-regexp": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", - "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", - "dev": true, - "peer": true - }, "node_modules/global-modules": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-2.0.0.tgz", @@ -6032,75 +5367,6 @@ "node": ">=0.10.0" } }, - "node_modules/hash-base": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.1.0.tgz", - "integrity": "sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA==", - "dev": true, - "peer": true, - "dependencies": { - "inherits": "^2.0.4", - "readable-stream": "^3.6.0", - "safe-buffer": "^5.2.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/hash-base/node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "peer": true - }, - "node_modules/hash.js": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", - "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", - "dev": true, - "peer": true, - "dependencies": { - "inherits": "^2.0.3", - "minimalistic-assert": "^1.0.1" - } - }, - "node_modules/he": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", - "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", - "dev": true, - "peer": true, - "bin": { - "he": "bin/he" - } - }, - "node_modules/hmac-drbg": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", - "integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=", - "dev": true, - "peer": true, - "dependencies": { - "hash.js": "^1.0.3", - "minimalistic-assert": "^1.0.0", - "minimalistic-crypto-utils": "^1.0.1" - } - }, "node_modules/hosted-git-info": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.0.2.tgz", @@ -6171,13 +5437,6 @@ "node": ">= 6" } }, - "node_modules/https-browserify": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz", - "integrity": "sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM=", - "dev": true, - "peer": true - }, "node_modules/https-proxy-agent": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", @@ -6244,27 +5503,6 @@ "node": ">=0.10.0" } }, - "node_modules/ieee754": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "peer": true - }, "node_modules/ignore": { "version": "5.1.8", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.8.tgz", @@ -6274,22 +5512,6 @@ "node": ">= 4" } }, - "node_modules/image-size": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/image-size/-/image-size-1.0.0.tgz", - "integrity": "sha512-JLJ6OwBfO1KcA+TvJT+v8gbE6iWbj24LyDNFgFEN0lzegn6cC6a/p3NIDaepMsJjQjlUWqIC7wJv8lBFxPNjcw==", - "dev": true, - "peer": true, - "dependencies": { - "queue": "6.0.2" - }, - "bin": { - "image-size": "bin/image-size.js" - }, - "engines": { - "node": ">=12.0.0" - } - }, "node_modules/import-fresh": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", @@ -6538,22 +5760,6 @@ "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/is-arguments": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.0.tgz", - "integrity": "sha512-1Ij4lOMPl/xB5kBDn7I+b2ttPMKa8szhEIrXDuXQD/oe3HJLTLhqhgGspwgyGd6MOywBUqVvYicF72lkgDnIHg==", - "dev": true, - "peer": true, - "dependencies": { - "call-bind": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/is-arrayish": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", @@ -6569,19 +5775,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, - "peer": true, - "dependencies": { - "binary-extensions": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/is-boolean-object": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.1.tgz", @@ -6759,19 +5952,6 @@ "node": ">=6" } }, - "node_modules/is-generator-function": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.9.tgz", - "integrity": "sha512-ZJ34p1uvIfptHCN7sFTjGibB9/oBg17sHqzDLfuwhvmN/qLVvIQXRQ8licZQ35WJ8KuEQt/etnnzQFI9C9Ue/A==", - "dev": true, - "peer": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/is-glob": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", @@ -6794,23 +5974,6 @@ "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/is-nan": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/is-nan/-/is-nan-1.3.2.tgz", - "integrity": "sha512-E+zBKpQ2t6MEo1VsonYmluk9NxGrbzpeeLC2xIViuO2EjU2xsXsBPwTr3Ykv9l08UYEVEdWeRZNouaZqF6RN0w==", - "dev": true, - "peer": true, - "dependencies": { - "call-bind": "^1.0.0", - "define-properties": "^1.1.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/is-negative-zero": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.1.tgz", @@ -6943,26 +6106,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-typed-array": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.5.tgz", - "integrity": "sha512-S+GRDgJlR3PyEbsX/Fobd9cqpZBuvUS+8asRqYDMLCb2qMzt1oz5m5oxQCxOgUDxiWsOVNi4yaF+/uvdlHlYug==", - "dev": true, - "peer": true, - "dependencies": { - "available-typed-arrays": "^1.0.2", - "call-bind": "^1.0.2", - "es-abstract": "^1.18.0-next.2", - "foreach": "^2.0.5", - "has-symbols": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/is-typedarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", @@ -7845,37 +6988,6 @@ "node": ">= 10.14.2" } }, - "node_modules/jest-worker": { - "version": "27.0.0-next.5", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.0.0-next.5.tgz", - "integrity": "sha512-mk0umAQ5lT+CaOJ+Qp01N6kz48sJG2kr2n1rX0koqKf6FIygQV0qLOdN9SCYID4IVeSigDOcPeGLozdMLYfb5g==", - "dev": true, - "peer": true, - "dependencies": { - "@types/node": "*", - "merge-stream": "^2.0.0", - "supports-color": "^8.0.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-worker/node_modules/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, - "peer": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" - } - }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -8333,13 +7445,6 @@ "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=", "dev": true }, - "node_modules/lodash.sortby": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", - "integrity": "sha1-7dFMgk4sycHgsKG0K7UhBRakJDg=", - "dev": true, - "peer": true - }, "node_modules/lodash.truncate": { "version": "4.4.2", "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", @@ -8506,18 +7611,6 @@ "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/md5.js": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", - "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==", - "dev": true, - "peer": true, - "dependencies": { - "hash-base": "^3.0.0", - "inherits": "^2.0.1", - "safe-buffer": "^5.1.2" - } - }, "node_modules/mdast-util-from-markdown": { "version": "0.8.5", "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-0.8.5.tgz", @@ -8648,27 +7741,6 @@ "node": ">=8.6" } }, - "node_modules/miller-rabin": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/miller-rabin/-/miller-rabin-4.0.1.tgz", - "integrity": "sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA==", - "dev": true, - "peer": true, - "dependencies": { - "bn.js": "^4.0.0", - "brorand": "^1.0.1" - }, - "bin": { - "miller-rabin": "bin/miller-rabin" - } - }, - "node_modules/miller-rabin/node_modules/bn.js": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", - "dev": true, - "peer": true - }, "node_modules/mime-db": { "version": "1.48.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.48.0.tgz", @@ -8708,20 +7780,6 @@ "node": ">=4" } }, - "node_modules/minimalistic-assert": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", - "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", - "dev": true, - "peer": true - }, - "node_modules/minimalistic-crypto-utils": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", - "integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=", - "dev": true, - "peer": true - }, "node_modules/minimatch": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", @@ -8806,11 +7864,10 @@ "dev": true }, "node_modules/nanoid": { - "version": "3.1.23", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.23.tgz", - "integrity": "sha512-FiB0kzdP0FFVGDKlRLEQ1BgDzU87dy5NnzjeW9YZNt+/c3+q82EQDUwniSAUxp/F0gFNI1ZhKU1FqYsMuqZVnw==", + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz", + "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==", "dev": true, - "peer": true, "bin": { "nanoid": "bin/nanoid.cjs" }, @@ -8841,16 +7898,6 @@ "node": ">=0.10.0" } }, - "node_modules/native-url": { - "version": "0.3.4", - "resolved": "https://registry.npmjs.org/native-url/-/native-url-0.3.4.tgz", - "integrity": "sha512-6iM8R99ze45ivyH8vybJ7X0yekIcPf5GgLV5K0ENCbmRcaRIDoj37BC8iLEmaaBfqqb8enuZ5p0uhY+lVAbAcA==", - "dev": true, - "peer": true, - "dependencies": { - "querystring": "^0.2.0" - } - }, "node_modules/natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", @@ -8858,80 +7905,44 @@ "dev": true }, "node_modules/next": { - "version": "11.1.2", - "resolved": "https://registry.npmjs.org/next/-/next-11.1.2.tgz", - "integrity": "sha512-azEYL0L+wFjv8lstLru3bgvrzPvK0P7/bz6B/4EJ9sYkXeW8r5Bjh78D/Ol7VOg0EIPz0CXoe72hzAlSAXo9hw==", - "dev": true, - "peer": true, - "dependencies": { - "@babel/runtime": "7.15.3", - "@hapi/accept": "5.0.2", - "@next/env": "11.1.2", - "@next/polyfill-module": "11.1.2", - "@next/react-dev-overlay": "11.1.2", - "@next/react-refresh-utils": "11.1.2", - "@node-rs/helper": "1.2.1", - "assert": "2.0.0", - "ast-types": "0.13.2", - "browserify-zlib": "0.2.0", - "browserslist": "4.16.6", - "buffer": "5.6.0", - "caniuse-lite": "^1.0.30001228", - "chalk": "2.4.2", - "chokidar": "3.5.1", - "constants-browserify": "1.0.0", - "crypto-browserify": "3.12.0", - "cssnano-simple": "3.0.0", - "domain-browser": "4.19.0", - "encoding": "0.1.13", - "etag": "1.8.1", - "find-cache-dir": "3.3.1", - "get-orientation": "1.1.2", - "https-browserify": "1.0.0", - "image-size": "1.0.0", - "jest-worker": "27.0.0-next.5", - "native-url": "0.3.4", - "node-fetch": "2.6.1", - "node-html-parser": "1.4.9", - "node-libs-browser": "^2.2.1", - "os-browserify": "0.3.0", - "p-limit": "3.1.0", - "path-browserify": "1.0.1", - "pnp-webpack-plugin": "1.6.4", - "postcss": "8.2.15", - "process": "0.11.10", - "querystring-es3": "0.2.1", - "raw-body": "2.4.1", - "react-is": "17.0.2", - "react-refresh": "0.8.3", - "stream-browserify": "3.0.0", - "stream-http": "3.1.1", - "string_decoder": "1.3.0", - "styled-jsx": "4.0.1", - "timers-browserify": "2.0.12", - "tty-browserify": "0.0.1", - "use-subscription": "1.5.1", - "util": "0.12.4", - "vm-browserify": "1.1.2", - "watchpack": "2.1.1" + "version": "12.3.1", + "resolved": "https://registry.npmjs.org/next/-/next-12.3.1.tgz", + "integrity": "sha512-l7bvmSeIwX5lp07WtIiP9u2ytZMv7jIeB8iacR28PuUEFG5j0HGAPnMqyG5kbZNBG2H7tRsrQ4HCjuMOPnANZw==", + "dev": true, + "dependencies": { + "@next/env": "12.3.1", + "@swc/helpers": "0.4.11", + "caniuse-lite": "^1.0.30001406", + "postcss": "8.4.14", + "styled-jsx": "5.0.7", + "use-sync-external-store": "1.2.0" }, "bin": { "next": "dist/bin/next" }, "engines": { - "node": ">=12.0.0" + "node": ">=12.22.0" }, "optionalDependencies": { - "@next/swc-darwin-arm64": "11.1.2", - "@next/swc-darwin-x64": "11.1.2", - "@next/swc-linux-x64-gnu": "11.1.2", - "@next/swc-win32-x64-msvc": "11.1.2" + "@next/swc-android-arm-eabi": "12.3.1", + "@next/swc-android-arm64": "12.3.1", + "@next/swc-darwin-arm64": "12.3.1", + "@next/swc-darwin-x64": "12.3.1", + "@next/swc-freebsd-x64": "12.3.1", + "@next/swc-linux-arm-gnueabihf": "12.3.1", + "@next/swc-linux-arm64-gnu": "12.3.1", + "@next/swc-linux-arm64-musl": "12.3.1", + "@next/swc-linux-x64-gnu": "12.3.1", + "@next/swc-linux-x64-musl": "12.3.1", + "@next/swc-win32-arm64-msvc": "12.3.1", + "@next/swc-win32-ia32-msvc": "12.3.1", + "@next/swc-win32-x64-msvc": "12.3.1" }, "peerDependencies": { "fibers": ">= 3.1.0", - "node-sass": "^4.0.0 || ^5.0.0", - "react": "^17.0.2", - "react-dom": "^17.0.2", + "node-sass": "^6.0.0 || ^7.0.0", + "react": "^17.0.2 || ^18.0.0-0", + "react-dom": "^17.0.2 || ^18.0.0-0", "sass": "^1.3.0" }, "peerDependenciesMeta": { @@ -8946,92 +7957,6 @@ } } }, - "node_modules/next/node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "peer": true, - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/next/node_modules/buffer": { - "version": "5.6.0", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.6.0.tgz", - "integrity": "sha512-/gDYp/UtU0eA1ys8bOs9J6a+E/KWIY+DZ+Q2WESNUA0jFRsJOc0SNUO6xJ5SGA1xueg3NL65W6s+NY5l9cunuw==", - "dev": true, - "peer": true, - "dependencies": { - "base64-js": "^1.0.2", - "ieee754": "^1.1.4" - } - }, - "node_modules/next/node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "peer": true, - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/next/node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "peer": true, - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/next/node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", - "dev": true, - "peer": true - }, - "node_modules/next/node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true, - "peer": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/next/node_modules/react-is": { - "version": "17.0.2", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", - "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", - "dev": true, - "peer": true - }, - "node_modules/next/node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "peer": true, - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/nice-try": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", @@ -9048,16 +7973,6 @@ "node": "4.x || >=6.0.0" } }, - "node_modules/node-html-parser": { - "version": "1.4.9", - "resolved": "https://registry.npmjs.org/node-html-parser/-/node-html-parser-1.4.9.tgz", - "integrity": "sha512-UVcirFD1Bn0O+TSmloHeHqZZCxHjvtIeGdVdGMhyZ8/PWlEiZaZ5iJzR189yKZr8p0FXN58BUeC7RHRkf/KYGw==", - "dev": true, - "peer": true, - "dependencies": { - "he": "1.2.0" - } - }, "node_modules/node-int64": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", @@ -9065,185 +7980,6 @@ "dev": true, "peer": true }, - "node_modules/node-libs-browser": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/node-libs-browser/-/node-libs-browser-2.2.1.tgz", - "integrity": "sha512-h/zcD8H9kaDZ9ALUWwlBUDo6TKF8a7qBSCSEGfjTVIYeqsioSKaAX+BN7NgiMGp6iSIXZ3PxgCu8KS3b71YK5Q==", - "dev": true, - "peer": true, - "dependencies": { - "assert": "^1.1.1", - "browserify-zlib": "^0.2.0", - "buffer": "^4.3.0", - "console-browserify": "^1.1.0", - "constants-browserify": "^1.0.0", - "crypto-browserify": "^3.11.0", - "domain-browser": "^1.1.1", - "events": "^3.0.0", - "https-browserify": "^1.0.0", - "os-browserify": "^0.3.0", - "path-browserify": "0.0.1", - "process": "^0.11.10", - "punycode": "^1.2.4", - "querystring-es3": "^0.2.0", - "readable-stream": "^2.3.3", - "stream-browserify": "^2.0.1", - "stream-http": "^2.7.2", - "string_decoder": "^1.0.0", - "timers-browserify": "^2.0.4", - "tty-browserify": "0.0.0", - "url": "^0.11.0", - "util": "^0.11.0", - "vm-browserify": "^1.0.1" - } - }, - "node_modules/node-libs-browser/node_modules/assert": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/assert/-/assert-1.5.0.tgz", - "integrity": "sha512-EDsgawzwoun2CZkCgtxJbv392v4nbk9XDD06zI+kQYoBM/3RBWLlEyJARDOmhAAosBjWACEkKL6S+lIZtcAubA==", - "dev": true, - "peer": true, - "dependencies": { - "object-assign": "^4.1.1", - "util": "0.10.3" - } - }, - "node_modules/node-libs-browser/node_modules/assert/node_modules/util": { - "version": "0.10.3", - "resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz", - "integrity": "sha1-evsa/lCAUkZInj23/g7TeTNqwPk=", - "dev": true, - "peer": true, - "dependencies": { - "inherits": "2.0.1" - } - }, - "node_modules/node-libs-browser/node_modules/buffer": { - "version": "4.9.2", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.2.tgz", - "integrity": "sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg==", - "dev": true, - "peer": true, - "dependencies": { - "base64-js": "^1.0.2", - "ieee754": "^1.1.4", - "isarray": "^1.0.0" - } - }, - "node_modules/node-libs-browser/node_modules/domain-browser": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-1.2.0.tgz", - "integrity": "sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA==", - "dev": true, - "peer": true, - "engines": { - "node": ">=0.4", - "npm": ">=1.2" - } - }, - "node_modules/node-libs-browser/node_modules/inherits": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz", - "integrity": "sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE=", - "dev": true, - "peer": true - }, - "node_modules/node-libs-browser/node_modules/path-browserify": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-0.0.1.tgz", - "integrity": "sha512-BapA40NHICOS+USX9SN4tyhq+A2RrN/Ws5F0Z5aMHDp98Fl86lX8Oti8B7uN93L4Ifv4fHOEA+pQw87gmMO/lQ==", - "dev": true, - "peer": true - }, - "node_modules/node-libs-browser/node_modules/punycode": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", - "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", - "dev": true, - "peer": true - }, - "node_modules/node-libs-browser/node_modules/readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "dev": true, - "peer": true, - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "node_modules/node-libs-browser/node_modules/readable-stream/node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true, - "peer": true - }, - "node_modules/node-libs-browser/node_modules/readable-stream/node_modules/string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "peer": true, - "dependencies": { - "safe-buffer": "~5.1.0" - } - }, - "node_modules/node-libs-browser/node_modules/stream-browserify": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.2.tgz", - "integrity": "sha512-nX6hmklHs/gr2FuxYDltq8fJA1GDlxKQCz8O/IM4atRqBH8OORmBNgfvW5gG10GT/qQ9u0CzIvr2X5Pkt6ntqg==", - "dev": true, - "peer": true, - "dependencies": { - "inherits": "~2.0.1", - "readable-stream": "^2.0.2" - } - }, - "node_modules/node-libs-browser/node_modules/stream-http": { - "version": "2.8.3", - "resolved": "https://registry.npmjs.org/stream-http/-/stream-http-2.8.3.tgz", - "integrity": "sha512-+TSkfINHDo4J+ZobQLWiMouQYB+UVYFttRA94FpEzzJ7ZdqcL4uUUQ7WkdkI4DSozGmgBUE/a47L+38PenXhUw==", - "dev": true, - "peer": true, - "dependencies": { - "builtin-status-codes": "^3.0.0", - "inherits": "^2.0.1", - "readable-stream": "^2.3.6", - "to-arraybuffer": "^1.0.0", - "xtend": "^4.0.0" - } - }, - "node_modules/node-libs-browser/node_modules/tty-browserify": { - "version": "0.0.0", - "resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.0.tgz", - "integrity": "sha1-oVe6QC2iTpv5V/mqadUk7tQpAaY=", - "dev": true, - "peer": true - }, - "node_modules/node-libs-browser/node_modules/util": { - "version": "0.11.1", - "resolved": "https://registry.npmjs.org/util/-/util-0.11.1.tgz", - "integrity": "sha512-HShAsny+zS2TZfaXxD9tYj4HQGlBezXZMZuM/S5PKLLoZkShZiGk9o5CzukI1LVHZvjdvZ2Sj1aW/Ndn2NB/HQ==", - "dev": true, - "peer": true, - "dependencies": { - "inherits": "2.0.3" - } - }, - "node_modules/node-libs-browser/node_modules/util/node_modules/inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", - "dev": true, - "peer": true - }, "node_modules/node-notifier": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/node-notifier/-/node-notifier-8.0.2.tgz", @@ -9458,23 +8194,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/object-is": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.5.tgz", - "integrity": "sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw==", - "dev": true, - "peer": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/object-keys": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", @@ -9656,13 +8375,6 @@ "node": ">= 0.8.0" } }, - "node_modules/os-browserify": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/os-browserify/-/os-browserify-0.3.0.tgz", - "integrity": "sha1-hUNzx/XCMVkU/Jv8a9gjj92h7Cc=", - "dev": true, - "peer": true - }, "node_modules/os-tmpdir": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", @@ -9749,13 +8461,6 @@ "node": ">=4" } }, - "node_modules/pako": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", - "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", - "dev": true, - "peer": true - }, "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -9768,20 +8473,6 @@ "node": ">=6" } }, - "node_modules/parse-asn1": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.6.tgz", - "integrity": "sha512-RnZRo1EPU6JBnra2vGHj0yhp6ebyjBZpmUCLHWiFhxlzvBCCpAuZ7elsBp1PVAbQN0/04VD/19rfzlBSwLstMw==", - "dev": true, - "peer": true, - "dependencies": { - "asn1.js": "^5.2.0", - "browserify-aes": "^1.0.0", - "evp_bytestokey": "^1.0.0", - "pbkdf2": "^3.0.3", - "safe-buffer": "^5.1.1" - } - }, "node_modules/parse-entities": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-2.0.0.tgz", @@ -9835,13 +8526,6 @@ "node": ">=0.10.0" } }, - "node_modules/path-browserify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz", - "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==", - "dev": true, - "peer": true - }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -9884,22 +8568,11 @@ "node": ">=8" } }, - "node_modules/pbkdf2": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.2.tgz", - "integrity": "sha512-iuh7L6jA7JEGu2WxDwtQP1ddOpaJNC4KlDEFfdQajSGgGPNi4OyDc2R7QnbY2bR9QjBVGwgvTdNJZoE7RaxUMA==", - "dev": true, - "peer": true, - "dependencies": { - "create-hash": "^1.1.2", - "create-hmac": "^1.1.4", - "ripemd160": "^2.0.1", - "safe-buffer": "^5.0.1", - "sha.js": "^2.4.8" - }, - "engines": { - "node": ">=0.12" - } + "node_modules/picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "dev": true }, "node_modules/picomatch": { "version": "2.3.0", @@ -10085,13 +8758,6 @@ "node": ">=4" } }, - "node_modules/platform": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/platform/-/platform-1.3.6.tgz", - "integrity": "sha512-fnWVljUchTro6RiCFvCXBbNhJc2NijN7oIQxbwsyL0buWJPG85v81ehlHI9fXrJsMNgTofEoWIQeClKpgxFLrg==", - "dev": true, - "peer": true - }, "node_modules/please-upgrade-node": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/please-upgrade-node/-/please-upgrade-node-3.2.0.tgz", @@ -10101,19 +8767,6 @@ "semver-compare": "^1.0.0" } }, - "node_modules/pnp-webpack-plugin": { - "version": "1.6.4", - "resolved": "https://registry.npmjs.org/pnp-webpack-plugin/-/pnp-webpack-plugin-1.6.4.tgz", - "integrity": "sha512-7Wjy+9E3WwLOEL30D+m8TSTF7qJJUJLONBnwQp0518siuMxUQUbgZwssaFX+QKlZkjHZcw/IpZCt/H0srrntSg==", - "dev": true, - "peer": true, - "dependencies": { - "ts-pnp": "^1.1.6" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/posix-character-classes": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", @@ -10125,22 +8778,27 @@ } }, "node_modules/postcss": { - "version": "8.2.15", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.2.15.tgz", - "integrity": "sha512-2zO3b26eJD/8rb106Qu2o7Qgg52ND5HPjcyQiK2B98O388h43A448LCslC0dI2P97wCAQRJsFvwTRcXxTKds+Q==", + "version": "8.4.14", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.14.tgz", + "integrity": "sha512-E398TUmfAYFPBSdzgeieK2Y1+1cpdxJx8yXbK/m57nRhKSmk1GB2tO4lbLBtlkfPQTDKfe4Xqv1ASWPpayPEig==", "dev": true, - "peer": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + } + ], "dependencies": { - "colorette": "^1.2.2", - "nanoid": "^3.1.23", - "source-map": "^0.6.1" + "nanoid": "^3.3.4", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" }, "engines": { "node": "^10 || ^12 || >=14" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" } }, "node_modules/postcss-html": { @@ -10876,16 +9534,6 @@ "node": ">=6" } }, - "node_modules/postcss/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -10942,23 +9590,6 @@ "dev": true, "peer": true }, - "node_modules/process": { - "version": "0.11.10", - "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", - "integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI=", - "dev": true, - "peer": true, - "engines": { - "node": ">= 0.6.0" - } - }, - "node_modules/process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", - "dev": true, - "peer": true - }, "node_modules/progress": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", @@ -11000,28 +9631,6 @@ "dev": true, "peer": true }, - "node_modules/public-encrypt": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.3.tgz", - "integrity": "sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q==", - "dev": true, - "peer": true, - "dependencies": { - "bn.js": "^4.1.0", - "browserify-rsa": "^4.0.0", - "create-hash": "^1.1.0", - "parse-asn1": "^5.0.0", - "randombytes": "^2.0.1", - "safe-buffer": "^5.1.2" - } - }, - "node_modules/public-encrypt/node_modules/bn.js": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", - "dev": true, - "peer": true - }, "node_modules/pump": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", @@ -11042,37 +9651,6 @@ "node": ">=6" } }, - "node_modules/querystring": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.1.tgz", - "integrity": "sha512-wkvS7mL/JMugcup3/rMitHmd9ecIGd2lhFhK9N3UUQ450h66d1r3Y9nvXzQAW1Lq+wyx61k/1pfKS5KuKiyEbg==", - "deprecated": "The querystring API is considered Legacy. new code should use the URLSearchParams API instead.", - "dev": true, - "peer": true, - "engines": { - "node": ">=0.4.x" - } - }, - "node_modules/querystring-es3": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/querystring-es3/-/querystring-es3-0.2.1.tgz", - "integrity": "sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM=", - "dev": true, - "peer": true, - "engines": { - "node": ">=0.4.x" - } - }, - "node_modules/queue": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/queue/-/queue-6.0.2.tgz", - "integrity": "sha512-iHZWu+q3IdFZFX36ro/lKBkSvfkztY5Y7HMiPlOUjhupPcG2JMfst2KKEpu5XndviX/3UhFbRngUPNKtgvtZiA==", - "dev": true, - "peer": true, - "dependencies": { - "inherits": "~2.0.3" - } - }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -11102,87 +9680,31 @@ "node": ">=8" } }, - "node_modules/randombytes": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", - "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "dev": true, - "peer": true, - "dependencies": { - "safe-buffer": "^5.1.0" - } - }, - "node_modules/randomfill": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/randomfill/-/randomfill-1.0.4.tgz", - "integrity": "sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw==", - "dev": true, - "peer": true, - "dependencies": { - "randombytes": "^2.0.5", - "safe-buffer": "^5.1.0" - } - }, - "node_modules/raw-body": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.1.tgz", - "integrity": "sha512-9WmIKF6mkvA0SLmA2Knm9+qj89e+j1zqgyn8aXGd7+nAduPoqgI9lO57SAZNn/Byzo5P7JhXTyg9PzaJbH73bA==", - "dev": true, - "peer": true, - "dependencies": { - "bytes": "3.1.0", - "http-errors": "1.7.3", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/raw-body/node_modules/http-errors": { - "version": "1.7.3", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.3.tgz", - "integrity": "sha512-ZTTX0MWrsQ2ZAhA1cejAwDLycFsd7I7nVtnkT3Ol0aqodaKW+0CTZDQ1uBv5whptCnc8e8HeRRJxRs0kmm/Qfw==", - "dev": true, - "peer": true, - "dependencies": { - "depd": "~1.1.2", - "inherits": "2.0.4", - "setprototypeof": "1.1.1", - "statuses": ">= 1.5.0 < 2", - "toidentifier": "1.0.0" - }, - "engines": { - "node": ">= 0.6" - } - }, "node_modules/react": { - "version": "17.0.2", - "resolved": "https://registry.npmjs.org/react/-/react-17.0.2.tgz", - "integrity": "sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA==", + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", + "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", "dev": true, "peer": true, "dependencies": { - "loose-envify": "^1.1.0", - "object-assign": "^4.1.1" + "loose-envify": "^1.1.0" }, "engines": { "node": ">=0.10.0" } }, "node_modules/react-dom": { - "version": "17.0.2", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-17.0.2.tgz", - "integrity": "sha512-s4h96KtLDUQlsENhMn1ar8t2bEa+q/YAtj8pPPdIjPDGBDIVNsrD9aXNWqspUe6AzKCIG0C1HZZLqLV7qpOBGA==", + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", + "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==", "dev": true, "peer": true, "dependencies": { "loose-envify": "^1.1.0", - "object-assign": "^4.1.1", - "scheduler": "^0.20.2" + "scheduler": "^0.23.0" }, "peerDependencies": { - "react": "17.0.2" + "react": "^18.2.0" } }, "node_modules/react-is": { @@ -11191,16 +9713,6 @@ "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", "dev": true }, - "node_modules/react-refresh": { - "version": "0.8.3", - "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.8.3.tgz", - "integrity": "sha512-X8jZHc7nCMjaCqoU+V2I0cOhNW+QMBwSUkeXnTi8IPe6zaRWfn60ZzvFDZqWPfmSJfjub7dDW1SP0jaHWLu/hg==", - "dev": true, - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/read-pkg": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", @@ -11611,17 +10123,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/ripemd160": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz", - "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==", - "dev": true, - "peer": true, - "dependencies": { - "hash-base": "^3.0.0", - "inherits": "^2.0.1" - } - }, "node_modules/rivet-graphql": { "version": "0.3.1", "resolved": "https://registry.npmjs.org/rivet-graphql/-/rivet-graphql-0.3.1.tgz", @@ -12035,14 +10536,13 @@ } }, "node_modules/scheduler": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.20.2.tgz", - "integrity": "sha512-2eWfGgAqqWFGqtdMmcL5zCMK1U8KlXv8SQFGglL3CEtd0aDVDWgeF/YoCmvln55m5zSk3J/20hTaSBeSObsQDQ==", + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz", + "integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==", "dev": true, "peer": true, "dependencies": { - "loose-envify": "^1.1.0", - "object-assign": "^4.1.1" + "loose-envify": "^1.1.0" } }, "node_modules/semver": { @@ -12114,34 +10614,6 @@ "node": ">=0.10.0" } }, - "node_modules/setimmediate": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", - "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=", - "dev": true, - "peer": true - }, - "node_modules/setprototypeof": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", - "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==", - "dev": true, - "peer": true - }, - "node_modules/sha.js": { - "version": "2.4.11", - "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", - "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", - "dev": true, - "peer": true, - "dependencies": { - "inherits": "^2.0.1", - "safe-buffer": "^5.0.1" - }, - "bin": { - "sha.js": "bin.js" - } - }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -12163,13 +10635,6 @@ "node": ">=8" } }, - "node_modules/shell-quote": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.7.2.tgz", - "integrity": "sha512-mRz/m/JVscCrkMyPqHc/bczi3OQHkLTqXHEFu0zDhK/qfv3UcOA4SVmRCLmos4bhjr9ekVQubj/R7waKapmiQg==", - "dev": true, - "peer": true - }, "node_modules/shellwords": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/shellwords/-/shellwords-0.1.1.tgz", @@ -12542,6 +11007,15 @@ "node": ">=0.10.0" } }, + "node_modules/source-map-js": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", + "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/source-map-resolve": { "version": "0.5.3", "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.3.tgz", @@ -12669,29 +11143,6 @@ "node": ">=8" } }, - "node_modules/stacktrace-parser": { - "version": "0.1.10", - "resolved": "https://registry.npmjs.org/stacktrace-parser/-/stacktrace-parser-0.1.10.tgz", - "integrity": "sha512-KJP1OCML99+8fhOHxwwzyWrlUuVX5GQ0ZpJTd1DFXhdkrvg1szxfHhawXUZ3g9TkXORQd4/WG68jMlQZ2p8wlg==", - "dev": true, - "peer": true, - "dependencies": { - "type-fest": "^0.7.1" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/stacktrace-parser/node_modules/type-fest": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.7.1.tgz", - "integrity": "sha512-Ne2YiiGN8bmrmJJEuTWTLJR32nh/JdL1+PSicowtNb0WFpn59GK8/lfD61bVtzguz7b3PBt74nxpv/Pw5po5Rg==", - "dev": true, - "peer": true, - "engines": { - "node": ">=8" - } - }, "node_modules/static-extend": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", @@ -12803,67 +11254,6 @@ "node": ">=0.10.0" } }, - "node_modules/statuses": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", - "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=", - "dev": true, - "peer": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/stream-browserify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-3.0.0.tgz", - "integrity": "sha512-H73RAHsVBapbim0tU2JwwOiXUj+fikfiaoYAKHF3VJfA0pe2BCzkhAHBlLG6REzE+2WNZcxOXjK7lkso+9euLA==", - "dev": true, - "peer": true, - "dependencies": { - "inherits": "~2.0.4", - "readable-stream": "^3.5.0" - } - }, - "node_modules/stream-http": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/stream-http/-/stream-http-3.1.1.tgz", - "integrity": "sha512-S7OqaYu0EkFpgeGFb/NPOoPLxFko7TPqtEeFg5DXPB4v/KETHG0Ln6fRFrNezoelpaDKmycEmmZ81cC9DAwgYg==", - "dev": true, - "peer": true, - "dependencies": { - "builtin-status-codes": "^3.0.0", - "inherits": "^2.0.4", - "readable-stream": "^3.6.0", - "xtend": "^4.0.2" - } - }, - "node_modules/stream-parser": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/stream-parser/-/stream-parser-0.3.1.tgz", - "integrity": "sha1-FhhUhpRCACGhGC/wrxkRwSl2F3M=", - "dev": true, - "peer": true, - "dependencies": { - "debug": "2" - } - }, - "node_modules/stream-parser/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "peer": true, - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/stream-parser/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true, - "peer": true - }, "node_modules/string_decoder": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", @@ -12902,13 +11292,6 @@ "node": ">=0.6.19" } }, - "node_modules/string-hash": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/string-hash/-/string-hash-1.1.3.tgz", - "integrity": "sha1-6Kr8CsGFW0Zmkp7X3RJ1311sgRs=", - "dev": true, - "peer": true - }, "node_modules/string-length": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", @@ -13079,101 +11462,29 @@ "dev": true }, "node_modules/styled-jsx": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-4.0.1.tgz", - "integrity": "sha512-Gcb49/dRB1k8B4hdK8vhW27Rlb2zujCk1fISrizCcToIs+55B4vmUM0N9Gi4nnVfFZWe55jRdWpAqH1ldAKWvQ==", + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.0.7.tgz", + "integrity": "sha512-b3sUzamS086YLRuvnaDigdAewz1/EFYlHpYBP5mZovKEdQQOIIYq8lApylub3HHZ6xFjV051kkGU7cudJmrXEA==", "dev": true, - "peer": true, - "dependencies": { - "@babel/plugin-syntax-jsx": "7.14.5", - "@babel/types": "7.15.0", - "convert-source-map": "1.7.0", - "loader-utils": "1.2.3", - "source-map": "0.7.3", - "string-hash": "1.1.3", - "stylis": "3.5.4", - "stylis-rule-sheet": "0.0.10" - }, "engines": { "node": ">= 12.0.0" }, "peerDependencies": { - "react": ">= 16.8.0 || 17.x.x || 18.x.x" + "react": ">= 16.8.0 || 17.x.x || ^18.0.0-0" }, "peerDependenciesMeta": { "@babel/core": { "optional": true + }, + "babel-plugin-macros": { + "optional": true } } }, - "node_modules/styled-jsx/node_modules/@babel/plugin-syntax-jsx": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.14.5.tgz", - "integrity": "sha512-ohuFIsOMXJnbOMRfX7/w7LocdR6R7whhuRD4ax8IipLcLPlZGJKkBxgHp++U4N/vKyU16/YDQr2f5seajD3jIw==", - "dev": true, - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/styled-jsx/node_modules/emojis-list": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-2.1.0.tgz", - "integrity": "sha1-TapNnbAPmBmIDHn6RXrlsJof04k=", - "dev": true, - "peer": true, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/styled-jsx/node_modules/json5": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", - "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", - "dev": true, - "peer": true, - "dependencies": { - "minimist": "^1.2.0" - }, - "bin": { - "json5": "lib/cli.js" - } - }, - "node_modules/styled-jsx/node_modules/loader-utils": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.2.3.tgz", - "integrity": "sha512-fkpz8ejdnEMG3s37wGL07iSBDg99O9D5yflE9RGNH3hRdx9SOwYfnGYdZOUIZitN8E+E2vkq3MUMYMvPYl5ZZA==", - "dev": true, - "peer": true, - "dependencies": { - "big.js": "^5.2.2", - "emojis-list": "^2.0.0", - "json5": "^1.0.1" - }, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/styled-jsx/node_modules/source-map": { - "version": "0.7.3", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", - "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==", - "dev": true, - "peer": true, - "engines": { - "node": ">= 8" - } - }, - "node_modules/stylelint": { - "version": "13.8.0", - "resolved": "https://registry.npmjs.org/stylelint/-/stylelint-13.8.0.tgz", - "integrity": "sha512-iHH3dv3UI23SLDrH4zMQDjLT9/dDIz/IpoFeuNxZmEx86KtfpjDOscxLTFioQyv+2vQjPlRZnK0UoJtfxLICXQ==", + "node_modules/stylelint": { + "version": "13.8.0", + "resolved": "https://registry.npmjs.org/stylelint/-/stylelint-13.8.0.tgz", + "integrity": "sha512-iHH3dv3UI23SLDrH4zMQDjLT9/dDIz/IpoFeuNxZmEx86KtfpjDOscxLTFioQyv+2vQjPlRZnK0UoJtfxLICXQ==", "dev": true, "dependencies": { "@stylelint/postcss-css-in-js": "^0.37.2", @@ -13547,23 +11858,6 @@ "node": ">=6" } }, - "node_modules/stylis": { - "version": "3.5.4", - "resolved": "https://registry.npmjs.org/stylis/-/stylis-3.5.4.tgz", - "integrity": "sha512-8/3pSmthWM7lsPBKv7NXkzn2Uc9W7NotcwGNpJaa3k7WMM1XDCA4MgT5k/8BIexd5ydZdboXtU90XH9Ec4Bv/Q==", - "dev": true, - "peer": true - }, - "node_modules/stylis-rule-sheet": { - "version": "0.0.10", - "resolved": "https://registry.npmjs.org/stylis-rule-sheet/-/stylis-rule-sheet-0.0.10.tgz", - "integrity": "sha512-nTbZoaqoBnmK+ptANthb10ZRZOGC+EmTLLUxeYIuHNkEKcmKgXX1XWKkUBT2Ac4es3NybooPe0SmvKdhKJZAuw==", - "dev": true, - "peer": true, - "peerDependencies": { - "stylis": "^3.5.0" - } - }, "node_modules/sugarss": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/sugarss/-/sugarss-2.0.0.tgz", @@ -13820,19 +12114,6 @@ "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", "dev": true }, - "node_modules/timers-browserify": { - "version": "2.0.12", - "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-2.0.12.tgz", - "integrity": "sha512-9phl76Cqm6FhSX9Xe1ZUAMLtm1BLkKj2Qd5ApyWkXzsMRaA7dgr81kf4wJmQf/hAvg8EEyJxDo3du/0KlhPiKQ==", - "dev": true, - "peer": true, - "dependencies": { - "setimmediate": "^1.0.4" - }, - "engines": { - "node": ">=0.6.0" - } - }, "node_modules/tlds": { "version": "1.221.1", "resolved": "https://registry.npmjs.org/tlds/-/tlds-1.221.1.tgz", @@ -13861,13 +12142,6 @@ "dev": true, "peer": true }, - "node_modules/to-arraybuffer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz", - "integrity": "sha1-fSKbH8xjfkZsoIEYCDanqr/4P0M=", - "dev": true, - "peer": true - }, "node_modules/to-fast-properties": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", @@ -13938,16 +12212,6 @@ "node": ">=8.0" } }, - "node_modules/toidentifier": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", - "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==", - "dev": true, - "peer": true, - "engines": { - "node": ">=0.6" - } - }, "node_modules/tough-cookie": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.0.0.tgz", @@ -13973,16 +12237,6 @@ "node": ">= 4.0.0" } }, - "node_modules/tr46": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz", - "integrity": "sha1-qLE/1r/SSJUZZ0zN5VujaTtwbQk=", - "dev": true, - "peer": true, - "dependencies": { - "punycode": "^2.1.0" - } - }, "node_modules/trim-newlines": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-3.0.1.tgz", @@ -14030,21 +12284,6 @@ "typescript": ">=3.8 <5.0" } }, - "node_modules/ts-pnp": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/ts-pnp/-/ts-pnp-1.2.0.tgz", - "integrity": "sha512-csd+vJOb/gkzvcCHgTGSChYpy5f1/XKNsmvBGO4JXS+z1v2HobugDz4s1IeFXM3wZB44uczs+eazB5Q/ccdhQw==", - "dev": true, - "peer": true, - "engines": { - "node": ">=6" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, "node_modules/tsconfig-paths": { "version": "3.11.0", "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.11.0.tgz", @@ -14090,13 +12329,6 @@ "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" } }, - "node_modules/tty-browserify": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.1.tgz", - "integrity": "sha512-C3TaO7K81YvjCgQH9Q1S3R3P3BtN3RIM8n+OvX4il1K1zgE8ZhI0op7kClgkxtutIE8hQrcrHBXvIheqKUUCxw==", - "dev": true, - "peer": true - }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -14254,16 +12486,6 @@ "node": ">= 10.0.0" } }, - "node_modules/unpipe": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=", - "dev": true, - "peer": true, - "engines": { - "node": ">= 0.8" - } - }, "node_modules/unset-value": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", @@ -14333,17 +12555,6 @@ "dev": true, "peer": true }, - "node_modules/url": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/url/-/url-0.11.0.tgz", - "integrity": "sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE=", - "dev": true, - "peer": true, - "dependencies": { - "punycode": "1.3.2", - "querystring": "0.2.0" - } - }, "node_modules/url-regex": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/url-regex/-/url-regex-5.0.0.tgz", @@ -14357,24 +12568,6 @@ "node": ">=8" } }, - "node_modules/url/node_modules/punycode": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", - "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=", - "dev": true, - "peer": true - }, - "node_modules/url/node_modules/querystring": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", - "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=", - "deprecated": "The querystring API is considered Legacy. new code should use the URLSearchParams API instead.", - "dev": true, - "peer": true, - "engines": { - "node": ">=0.4.x" - } - }, "node_modules/use": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", @@ -14385,32 +12578,13 @@ "node": ">=0.10.0" } }, - "node_modules/use-subscription": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/use-subscription/-/use-subscription-1.5.1.tgz", - "integrity": "sha512-Xv2a1P/yReAjAbhylMfFplFKj9GssgTwN7RlcTxBujFQcloStWNDQdc4g4NRWH9xS4i/FDk04vQBptAXoF3VcA==", + "node_modules/use-sync-external-store": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz", + "integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==", "dev": true, - "peer": true, - "dependencies": { - "object-assign": "^4.1.1" - }, "peerDependencies": { - "react": "^16.8.0 || ^17.0.0" - } - }, - "node_modules/util": { - "version": "0.12.4", - "resolved": "https://registry.npmjs.org/util/-/util-0.12.4.tgz", - "integrity": "sha512-bxZ9qtSlGUWSOy9Qa9Xgk11kSslpuZwaxCg4sNIDj6FLucDab2JxnHwyNTCpHMtK1MjoQiWQ6DiUMZYbSrO+Sw==", - "dev": true, - "peer": true, - "dependencies": { - "inherits": "^2.0.3", - "is-arguments": "^1.0.4", - "is-generator-function": "^1.0.7", - "is-typed-array": "^1.1.3", - "safe-buffer": "^5.1.2", - "which-typed-array": "^1.1.2" + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" } }, "node_modules/util-deprecate": { @@ -14490,13 +12664,6 @@ "url": "https://opencollective.com/unified" } }, - "node_modules/vm-browserify": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-1.1.2.tgz", - "integrity": "sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ==", - "dev": true, - "peer": true - }, "node_modules/w3c-hr-time": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz", @@ -14530,27 +12697,6 @@ "makeerror": "1.0.12" } }, - "node_modules/watchpack": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.1.1.tgz", - "integrity": "sha512-Oo7LXCmc1eE1AjyuSBmtC3+Wy4HcV8PxWh2kP6fOl8yTlNS7r0K9l1ao2lrrUza7V39Y3D/BbJgY8VeSlc5JKw==", - "dev": true, - "peer": true, - "dependencies": { - "glob-to-regexp": "^0.4.1", - "graceful-fs": "^4.1.2" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/webidl-conversions": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz", - "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==", - "dev": true, - "peer": true - }, "node_modules/whatwg-encoding": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz", @@ -14568,18 +12714,6 @@ "dev": true, "peer": true }, - "node_modules/whatwg-url": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-7.1.0.tgz", - "integrity": "sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==", - "dev": true, - "peer": true, - "dependencies": { - "lodash.sortby": "^4.7.0", - "tr46": "^1.0.1", - "webidl-conversions": "^4.0.2" - } - }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -14624,28 +12758,6 @@ "integrity": "sha1-Zws6+8VS4LVd9rd4DKdGFfI60cs=", "dev": true }, - "node_modules/which-typed-array": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.4.tgz", - "integrity": "sha512-49E0SpUe90cjpoc7BOJwyPHRqSAd12c10Qm2amdEZrJPCY2NDxaW01zHITrem+rnETY3dwrbH3UUrUwagfCYDA==", - "dev": true, - "peer": true, - "dependencies": { - "available-typed-arrays": "^1.0.2", - "call-bind": "^1.0.0", - "es-abstract": "^1.18.0-next.1", - "foreach": "^2.0.5", - "function-bind": "^1.1.1", - "has-symbols": "^1.0.1", - "is-typed-array": "^1.1.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/word-wrap": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", @@ -14704,16 +12816,6 @@ "dev": true, "peer": true }, - "node_modules/xtend": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", - "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", - "dev": true, - "peer": true, - "engines": { - "node": ">=0.4" - } - }, "node_modules/y18n": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", @@ -15344,34 +13446,6 @@ } } }, - "@hapi/accept": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/@hapi/accept/-/accept-5.0.2.tgz", - "integrity": "sha512-CmzBx/bXUR8451fnZRuZAJRlzgm0Jgu5dltTX/bszmR2lheb9BpyN47Q1RbaGTsvFzn0PXAEs+lXDKfshccYZw==", - "dev": true, - "peer": true, - "requires": { - "@hapi/boom": "9.x.x", - "@hapi/hoek": "9.x.x" - } - }, - "@hapi/boom": { - "version": "9.1.3", - "resolved": "https://registry.npmjs.org/@hapi/boom/-/boom-9.1.3.tgz", - "integrity": "sha512-RlrGyZ603hE/eRTZtTltocRm50HHmrmL3kGOP0SQ9MasazlW1mt/fkv4C5P/6rnpFXjwld/POFX1C8tMZE3ldg==", - "dev": true, - "peer": true, - "requires": { - "@hapi/hoek": "9.x.x" - } - }, - "@hapi/hoek": { - "version": "9.2.0", - "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.2.0.tgz", - "integrity": "sha512-sqKVVVOe5ivCaXDWivIJYVSaEgdQK9ul7a4Kity5Iw7u9+wBAPbX1RMSnLLmp7O4Vzj0WOWwMAJsTL00xwaNug==", - "dev": true, - "peer": true - }, "@hashicorp/platform-cli": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@hashicorp/platform-cli/-/platform-cli-1.2.0.tgz", @@ -15738,19 +13812,11 @@ "chalk": "^4.0.0" } }, - "@napi-rs/triples": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@napi-rs/triples/-/triples-1.0.3.tgz", - "integrity": "sha512-jDJTpta+P4p1NZTFVLHJ/TLFVYVcOqv6l8xwOeBKNPMgY/zDYH/YH7SJbvrr/h1RcS9GzbPcLKGzpuK9cV56UA==", - "dev": true, - "peer": true - }, "@next/env": { - "version": "11.1.2", - "resolved": "https://registry.npmjs.org/@next/env/-/env-11.1.2.tgz", - "integrity": "sha512-+fteyVdQ7C/OoulfcF6vd1Yk0FEli4453gr8kSFbU8sKseNSizYq6df5MKz/AjwLptsxrUeIkgBdAzbziyJ3mA==", - "dev": true, - "peer": true + "version": "12.3.1", + "resolved": "https://registry.npmjs.org/@next/env/-/env-12.3.1.tgz", + "integrity": "sha512-9P9THmRFVKGKt9DYqeC2aKIxm8rlvkK38V1P1sRE7qyoPBIs8l9oo79QoSdPtOWfzkbDAVUqvbQGgTMsb8BtJg==", + "dev": true }, "@next/eslint-plugin-next": { "version": "11.1.2", @@ -15761,122 +13827,96 @@ "glob": "7.1.7" } }, - "@next/polyfill-module": { - "version": "11.1.2", - "resolved": "https://registry.npmjs.org/@next/polyfill-module/-/polyfill-module-11.1.2.tgz", - "integrity": "sha512-xZmixqADM3xxtqBV0TpAwSFzWJP0MOQzRfzItHXf1LdQHWb0yofHHC+7eOrPFic8+ZGz5y7BdPkkgR1S25OymA==", - "dev": true, - "peer": true - }, - "@next/react-dev-overlay": { - "version": "11.1.2", - "resolved": "https://registry.npmjs.org/@next/react-dev-overlay/-/react-dev-overlay-11.1.2.tgz", - "integrity": "sha512-rDF/mGY2NC69mMg2vDqzVpCOlWqnwPUXB2zkARhvknUHyS6QJphPYv9ozoPJuoT/QBs49JJd9KWaAzVBvq920A==", + "@next/swc-android-arm-eabi": { + "version": "12.3.1", + "resolved": "https://registry.npmjs.org/@next/swc-android-arm-eabi/-/swc-android-arm-eabi-12.3.1.tgz", + "integrity": "sha512-i+BvKA8tB//srVPPQxIQN5lvfROcfv4OB23/L1nXznP+N/TyKL8lql3l7oo2LNhnH66zWhfoemg3Q4VJZSruzQ==", "dev": true, - "peer": true, - "requires": { - "@babel/code-frame": "7.12.11", - "anser": "1.4.9", - "chalk": "4.0.0", - "classnames": "2.2.6", - "css.escape": "1.5.1", - "data-uri-to-buffer": "3.0.1", - "platform": "1.3.6", - "shell-quote": "1.7.2", - "source-map": "0.8.0-beta.0", - "stacktrace-parser": "0.1.10", - "strip-ansi": "6.0.0" - }, - "dependencies": { - "@babel/code-frame": { - "version": "7.12.11", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.11.tgz", - "integrity": "sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw==", - "dev": true, - "peer": true, - "requires": { - "@babel/highlight": "^7.10.4" - } - }, - "chalk": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.0.0.tgz", - "integrity": "sha512-N9oWFcegS0sFr9oh1oz2d7Npos6vNoWW9HvtCg5N1KRFpUhaAhvTv5Y58g880fZaEYSNm3qDz8SU1UrGvp+n7A==", - "dev": true, - "peer": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "classnames": { - "version": "2.2.6", - "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.2.6.tgz", - "integrity": "sha512-JR/iSQOSt+LQIWwrwEzJ9uk0xfN3mTVYMwt1Ir5mUcSN6pU+V4zQFFaJsclJbPuAUQH+yfWef6tm7l1quW3C8Q==", - "dev": true, - "peer": true - }, - "source-map": { - "version": "0.8.0-beta.0", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.8.0-beta.0.tgz", - "integrity": "sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==", - "dev": true, - "peer": true, - "requires": { - "whatwg-url": "^7.0.0" - } - } - } + "optional": true }, - "@next/react-refresh-utils": { - "version": "11.1.2", - "resolved": "https://registry.npmjs.org/@next/react-refresh-utils/-/react-refresh-utils-11.1.2.tgz", - "integrity": "sha512-hsoJmPfhVqjZ8w4IFzoo8SyECVnN+8WMnImTbTKrRUHOVJcYMmKLL7xf7T0ft00tWwAl/3f3Q3poWIN2Ueql/Q==", + "@next/swc-android-arm64": { + "version": "12.3.1", + "resolved": "https://registry.npmjs.org/@next/swc-android-arm64/-/swc-android-arm64-12.3.1.tgz", + "integrity": "sha512-CmgU2ZNyBP0rkugOOqLnjl3+eRpXBzB/I2sjwcGZ7/Z6RcUJXK5Evz+N0ucOxqE4cZ3gkTeXtSzRrMK2mGYV8Q==", "dev": true, - "peer": true, - "requires": {} + "optional": true }, "@next/swc-darwin-arm64": { - "version": "11.1.2", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-11.1.2.tgz", - "integrity": "sha512-hZuwOlGOwBZADA8EyDYyjx3+4JGIGjSHDHWrmpI7g5rFmQNltjlbaefAbiU5Kk7j3BUSDwt30quJRFv3nyJQ0w==", + "version": "12.3.1", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-12.3.1.tgz", + "integrity": "sha512-hT/EBGNcu0ITiuWDYU9ur57Oa4LybD5DOQp4f22T6zLfpoBMfBibPtR8XktXmOyFHrL/6FC2p9ojdLZhWhvBHg==", "dev": true, - "optional": true, - "peer": true + "optional": true }, "@next/swc-darwin-x64": { - "version": "11.1.2", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-11.1.2.tgz", - "integrity": "sha512-PGOp0E1GisU+EJJlsmJVGE+aPYD0Uh7zqgsrpD3F/Y3766Ptfbe1lEPPWnRDl+OzSSrSrX1lkyM/Jlmh5OwNvA==", + "version": "12.3.1", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-12.3.1.tgz", + "integrity": "sha512-9S6EVueCVCyGf2vuiLiGEHZCJcPAxglyckTZcEwLdJwozLqN0gtS0Eq0bQlGS3dH49Py/rQYpZ3KVWZ9BUf/WA==", "dev": true, - "optional": true, - "peer": true + "optional": true + }, + "@next/swc-freebsd-x64": { + "version": "12.3.1", + "resolved": "https://registry.npmjs.org/@next/swc-freebsd-x64/-/swc-freebsd-x64-12.3.1.tgz", + "integrity": "sha512-qcuUQkaBZWqzM0F1N4AkAh88lLzzpfE6ImOcI1P6YeyJSsBmpBIV8o70zV+Wxpc26yV9vpzb+e5gCyxNjKJg5Q==", + "dev": true, + "optional": true + }, + "@next/swc-linux-arm-gnueabihf": { + "version": "12.3.1", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm-gnueabihf/-/swc-linux-arm-gnueabihf-12.3.1.tgz", + "integrity": "sha512-diL9MSYrEI5nY2wc/h/DBewEDUzr/DqBjIgHJ3RUNtETAOB3spMNHvJk2XKUDjnQuluLmFMloet9tpEqU2TT9w==", + "dev": true, + "optional": true + }, + "@next/swc-linux-arm64-gnu": { + "version": "12.3.1", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-12.3.1.tgz", + "integrity": "sha512-o/xB2nztoaC7jnXU3Q36vGgOolJpsGG8ETNjxM1VAPxRwM7FyGCPHOMk1XavG88QZSQf+1r+POBW0tLxQOJ9DQ==", + "dev": true, + "optional": true + }, + "@next/swc-linux-arm64-musl": { + "version": "12.3.1", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-12.3.1.tgz", + "integrity": "sha512-2WEasRxJzgAmP43glFNhADpe8zB7kJofhEAVNbDJZANp+H4+wq+/cW1CdDi8DqjkShPEA6/ejJw+xnEyDID2jg==", + "dev": true, + "optional": true }, "@next/swc-linux-x64-gnu": { - "version": "11.1.2", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-11.1.2.tgz", - "integrity": "sha512-YcDHTJjn/8RqvyJVB6pvEKXihDcdrOwga3GfMv/QtVeLphTouY4BIcEUfrG5+26Nf37MP1ywN3RRl1TxpurAsQ==", + "version": "12.3.1", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-12.3.1.tgz", + "integrity": "sha512-JWEaMyvNrXuM3dyy9Pp5cFPuSSvG82+yABqsWugjWlvfmnlnx9HOQZY23bFq3cNghy5V/t0iPb6cffzRWylgsA==", "dev": true, - "optional": true, - "peer": true + "optional": true }, - "@next/swc-win32-x64-msvc": { - "version": "11.1.2", - "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-11.1.2.tgz", - "integrity": "sha512-e/pIKVdB+tGQYa1cW3sAeHm8gzEri/HYLZHT4WZojrUxgWXqx8pk7S7Xs47uBcFTqBDRvK3EcQpPLf3XdVsDdg==", + "@next/swc-linux-x64-musl": { + "version": "12.3.1", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-12.3.1.tgz", + "integrity": "sha512-xoEWQQ71waWc4BZcOjmatuvPUXKTv6MbIFzpm4LFeCHsg2iwai0ILmNXf81rJR+L1Wb9ifEke2sQpZSPNz1Iyg==", "dev": true, - "optional": true, - "peer": true + "optional": true }, - "@node-rs/helper": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@node-rs/helper/-/helper-1.2.1.tgz", - "integrity": "sha512-R5wEmm8nbuQU0YGGmYVjEc0OHtYsuXdpRG+Ut/3wZ9XAvQWyThN08bTh2cBJgoZxHQUPtvRfeQuxcAgLuiBISg==", + "@next/swc-win32-arm64-msvc": { + "version": "12.3.1", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-12.3.1.tgz", + "integrity": "sha512-hswVFYQYIeGHE2JYaBVtvqmBQ1CppplQbZJS/JgrVI3x2CurNhEkmds/yqvDONfwfbttTtH4+q9Dzf/WVl3Opw==", "dev": true, - "peer": true, - "requires": { - "@napi-rs/triples": "^1.0.3" - } + "optional": true + }, + "@next/swc-win32-ia32-msvc": { + "version": "12.3.1", + "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-12.3.1.tgz", + "integrity": "sha512-Kny5JBehkTbKPmqulr5i+iKntO5YMP+bVM8Hf8UAmjSMVo3wehyLVc9IZkNmcbxi+vwETnQvJaT5ynYBkJ9dWA==", + "dev": true, + "optional": true + }, + "@next/swc-win32-x64-msvc": { + "version": "12.3.1", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-12.3.1.tgz", + "integrity": "sha512-W1ijvzzg+kPEX6LAc+50EYYSEo0FVu7dmTE+t+DM4iOLqgGHoW9uYSz9wCVdkXOEEMP9xhXfGpcSxsfDucyPkA==", + "dev": true, + "optional": true }, "@nodelib/fs.scandir": { "version": "2.1.5", @@ -15949,6 +13989,23 @@ "unist-util-find-all-after": "^3.0.2" } }, + "@swc/helpers": { + "version": "0.4.11", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.4.11.tgz", + "integrity": "sha512-rEUrBSGIoSFuYxwBYtlUFMlE2CwGhmW+w9355/5oduSw8e5h2+Tj4UrAGNNgP9915++wj5vkQo0UuOBqOAq4nw==", + "dev": true, + "requires": { + "tslib": "^2.4.0" + }, + "dependencies": { + "tslib": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", + "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==", + "dev": true + } + } + }, "@tootallnate/once": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", @@ -16268,13 +14325,6 @@ "uri-js": "^4.2.2" } }, - "anser": { - "version": "1.4.9", - "resolved": "https://registry.npmjs.org/anser/-/anser-1.4.9.tgz", - "integrity": "sha512-AI+BjTeGt2+WFk4eWcqbQ7snZpDBt8SaLlj0RT2h5xfdWaiy51OjYvqwMrNzJLGy8iOAL6nKDITWO+rd4MkYEA==", - "dev": true, - "peer": true - }, "ansi-colors": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", @@ -16419,41 +14469,6 @@ "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=", "dev": true }, - "asn1.js": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-5.4.1.tgz", - "integrity": "sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA==", - "dev": true, - "peer": true, - "requires": { - "bn.js": "^4.0.0", - "inherits": "^2.0.1", - "minimalistic-assert": "^1.0.0", - "safer-buffer": "^2.1.0" - }, - "dependencies": { - "bn.js": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", - "dev": true, - "peer": true - } - } - }, - "assert": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/assert/-/assert-2.0.0.tgz", - "integrity": "sha512-se5Cd+js9dXJnu6Ag2JFc00t+HmHOen+8Q+L7O9zI0PqQXr20uk2J0XQqMxZEeo5U50o8Nvmmx7dZrl+Ufr35A==", - "dev": true, - "peer": true, - "requires": { - "es6-object-assign": "^1.1.0", - "is-nan": "^1.2.1", - "object-is": "^1.0.1", - "util": "^0.12.0" - } - }, "assign-symbols": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", @@ -16461,13 +14476,6 @@ "dev": true, "peer": true }, - "ast-types": { - "version": "0.13.2", - "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.13.2.tgz", - "integrity": "sha512-uWMHxJxtfj/1oZClOxDEV1sQ1HCDkA4MG8Gr69KKeBjEVH0R84WlejZ0y2DcwyBlpAEMltmVYkVgqfLFb2oyiA==", - "dev": true, - "peer": true - }, "ast-types-flow": { "version": "0.0.7", "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.7.tgz", @@ -16600,13 +14608,6 @@ } } }, - "available-typed-arrays": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.4.tgz", - "integrity": "sha512-SA5mXJWrId1TaQjfxUYghbqQ/hYioKmLJvPJyDuYRtXXenFNMjj4hSSt1Cf1xsuXSXrtxrVC5Ot4eU6cOtBDdA==", - "dev": true, - "peer": true - }, "axe-core": { "version": "4.3.1", "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.3.1.tgz", @@ -16780,34 +14781,6 @@ } } }, - "base64-js": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "dev": true, - "peer": true - }, - "big.js": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", - "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", - "dev": true, - "peer": true - }, - "binary-extensions": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", - "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", - "dev": true, - "peer": true - }, - "bn.js": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.0.tgz", - "integrity": "sha512-D7iWRBvnZE8ecXiLj/9wbxH7Tk79fAh8IHaTNq1RWRixsS02W+5qS+iE9yq6RYl0asXx5tw0bLhmT5pIfbSquw==", - "dev": true, - "peer": true - }, "brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -16827,13 +14800,6 @@ "fill-range": "^7.0.1" } }, - "brorand": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", - "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=", - "dev": true, - "peer": true - }, "browser-process-hrtime": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz", @@ -16841,94 +14807,6 @@ "dev": true, "peer": true }, - "browserify-aes": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", - "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==", - "dev": true, - "peer": true, - "requires": { - "buffer-xor": "^1.0.3", - "cipher-base": "^1.0.0", - "create-hash": "^1.1.0", - "evp_bytestokey": "^1.0.3", - "inherits": "^2.0.1", - "safe-buffer": "^5.0.1" - } - }, - "browserify-cipher": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/browserify-cipher/-/browserify-cipher-1.0.1.tgz", - "integrity": "sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w==", - "dev": true, - "peer": true, - "requires": { - "browserify-aes": "^1.0.4", - "browserify-des": "^1.0.0", - "evp_bytestokey": "^1.0.0" - } - }, - "browserify-des": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/browserify-des/-/browserify-des-1.0.2.tgz", - "integrity": "sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A==", - "dev": true, - "peer": true, - "requires": { - "cipher-base": "^1.0.1", - "des.js": "^1.0.0", - "inherits": "^2.0.1", - "safe-buffer": "^5.1.2" - } - }, - "browserify-rsa": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.1.0.tgz", - "integrity": "sha512-AdEER0Hkspgno2aR97SAf6vi0y0k8NuOpGnVH3O99rcA5Q6sh8QxcngtHuJ6uXwnfAXNM4Gn1Gb7/MV1+Ymbog==", - "dev": true, - "peer": true, - "requires": { - "bn.js": "^5.0.0", - "randombytes": "^2.0.1" - } - }, - "browserify-sign": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.2.1.tgz", - "integrity": "sha512-/vrA5fguVAKKAVTNJjgSm1tRQDHUU6DbwO9IROu/0WAzC8PKhucDSh18J0RMvVeHAn5puMd+QHC2erPRNf8lmg==", - "dev": true, - "peer": true, - "requires": { - "bn.js": "^5.1.1", - "browserify-rsa": "^4.0.1", - "create-hash": "^1.2.0", - "create-hmac": "^1.1.7", - "elliptic": "^6.5.3", - "inherits": "^2.0.4", - "parse-asn1": "^5.1.5", - "readable-stream": "^3.6.0", - "safe-buffer": "^5.2.0" - }, - "dependencies": { - "safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true, - "peer": true - } - } - }, - "browserify-zlib": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.2.0.tgz", - "integrity": "sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA==", - "dev": true, - "peer": true, - "requires": { - "pako": "~1.0.5" - } - }, "browserslist": { "version": "4.16.6", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.16.6.tgz", @@ -16967,27 +14845,6 @@ "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", "dev": true }, - "buffer-xor": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz", - "integrity": "sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk=", - "dev": true, - "peer": true - }, - "builtin-status-codes": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz", - "integrity": "sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug=", - "dev": true, - "peer": true - }, - "bytes": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", - "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==", - "dev": true, - "peer": true - }, "cache-base": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", @@ -17040,9 +14897,9 @@ } }, "caniuse-lite": { - "version": "1.0.30001246", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001246.tgz", - "integrity": "sha512-Tc+ff0Co/nFNbLOrziBXmMVtpt9S2c2Y+Z9Nk9Khj09J+0zR9ejvIW5qkZAErCbOrVODCx/MN+GpB5FNBs5GFA==", + "version": "1.0.30001416", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001416.tgz", + "integrity": "sha512-06wzzdAkCPZO+Qm4e/eNghZBDfVNDsCgw33T27OwBH9unE9S478OYw//Q2L7Npf/zBzs7rjZOszIFQkwQKAEqA==", "dev": true }, "capture-exit": { @@ -17096,40 +14953,12 @@ "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", "dev": true }, - "chokidar": { - "version": "3.5.1", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.1.tgz", - "integrity": "sha512-9+s+Od+W0VJJzawDma/gvBNQqkTiqYTWLuZoyAsivsI4AaWTCzHG06/TMjsf1cYe9Cb97UCEhjz7HvnPk2p/tw==", - "dev": true, - "peer": true, - "requires": { - "anymatch": "~3.1.1", - "braces": "~3.0.2", - "fsevents": "~2.3.1", - "glob-parent": "~5.1.0", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.5.0" - } - }, "ci-info": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", "dev": true }, - "cipher-base": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz", - "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==", - "dev": true, - "peer": true, - "requires": { - "inherits": "^2.0.1", - "safe-buffer": "^5.0.1" - } - }, "cjs-module-lexer": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-0.6.0.tgz", @@ -17367,13 +15196,6 @@ "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", "dev": true }, - "commondir": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", - "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=", - "dev": true, - "peer": true - }, "compare-versions": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/compare-versions/-/compare-versions-3.6.0.tgz", @@ -17393,20 +15215,6 @@ "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", "dev": true }, - "console-browserify": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.2.0.tgz", - "integrity": "sha512-ZMkYO/LkF17QvCPqM0gxw8yUzigAOZOSWSHg91FH6orS7vcEj5dVZTidN2fQ14yBSdg97RqhSNwLUXInd52OTA==", - "dev": true, - "peer": true - }, - "constants-browserify": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/constants-browserify/-/constants-browserify-1.0.0.tgz", - "integrity": "sha1-wguW2MYXdIqvHBYCF2DNJ/y4y3U=", - "dev": true, - "peer": true - }, "convert-source-map": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.7.0.tgz", @@ -17429,13 +15237,6 @@ "integrity": "sha512-D42L7RYh1J2grW8ttxoY1+17Y4wXZeKe7uyplAI3FkNQyI5OgBIAjUfFiTPfL1rs0qLpxaabITNbjKl1Sp82tA==", "dev": true }, - "core-util-is": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", - "dev": true, - "peer": true - }, "cosmiconfig": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.0.0.tgz", @@ -17449,55 +15250,6 @@ "yaml": "^1.10.0" } }, - "create-ecdh": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.4.tgz", - "integrity": "sha512-mf+TCx8wWc9VpuxfP2ht0iSISLZnt0JgWlrOKZiNqyUZWnjIaCIVNQArMHnCZKfEYRg6IM7A+NeJoN8gf/Ws0A==", - "dev": true, - "peer": true, - "requires": { - "bn.js": "^4.1.0", - "elliptic": "^6.5.3" - }, - "dependencies": { - "bn.js": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", - "dev": true, - "peer": true - } - } - }, - "create-hash": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", - "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", - "dev": true, - "peer": true, - "requires": { - "cipher-base": "^1.0.1", - "inherits": "^2.0.1", - "md5.js": "^1.3.4", - "ripemd160": "^2.0.1", - "sha.js": "^2.4.0" - } - }, - "create-hmac": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", - "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==", - "dev": true, - "peer": true, - "requires": { - "cipher-base": "^1.0.3", - "create-hash": "^1.1.0", - "inherits": "^2.0.1", - "ripemd160": "^2.0.0", - "safe-buffer": "^5.0.1", - "sha.js": "^2.4.8" - } - }, "cross-fetch": { "version": "3.1.4", "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.4.tgz", @@ -17518,59 +15270,12 @@ "which": "^2.0.1" } }, - "crypto-browserify": { - "version": "3.12.0", - "resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.12.0.tgz", - "integrity": "sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg==", - "dev": true, - "peer": true, - "requires": { - "browserify-cipher": "^1.0.0", - "browserify-sign": "^4.0.0", - "create-ecdh": "^4.0.0", - "create-hash": "^1.1.0", - "create-hmac": "^1.1.0", - "diffie-hellman": "^5.0.0", - "inherits": "^2.0.1", - "pbkdf2": "^3.0.3", - "public-encrypt": "^4.0.0", - "randombytes": "^2.0.0", - "randomfill": "^1.0.3" - } - }, - "css.escape": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz", - "integrity": "sha1-QuJ9T6BK4y+TGktNQZH6nN3ul8s=", - "dev": true, - "peer": true - }, "cssesc": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", "dev": true }, - "cssnano-preset-simple": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/cssnano-preset-simple/-/cssnano-preset-simple-3.0.0.tgz", - "integrity": "sha512-vxQPeoMRqUT3c/9f0vWeVa2nKQIHFpogtoBvFdW4GQ3IvEJ6uauCP6p3Y5zQDLFcI7/+40FTgX12o7XUL0Ko+w==", - "dev": true, - "peer": true, - "requires": { - "caniuse-lite": "^1.0.30001202" - } - }, - "cssnano-simple": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/cssnano-simple/-/cssnano-simple-3.0.0.tgz", - "integrity": "sha512-oU3ueli5Dtwgh0DyeohcIEE00QVfbPR3HzyXdAl89SfnQG3y0/qcpfLVW+jPIh3/rgMZGwuW96rejZGaYE9eUg==", - "dev": true, - "peer": true, - "requires": { - "cssnano-preset-simple": "^3.0.0" - } - }, "cssom": { "version": "0.4.4", "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.4.4.tgz", @@ -17609,13 +15314,6 @@ "integrity": "sha512-ZMvxkAyEpBTvBFk+DPjcK0ObNy8GM4gmrGG1qIu0EXb/zj25vjRWNnhLHKZw4JlOLo02oWlwDeqo98GuBlJcIg==", "dev": true }, - "data-uri-to-buffer": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-3.0.1.tgz", - "integrity": "sha512-WboRycPNsVw3B3TL559F7kuBUM4d8CgMEvk6xEJlOp7OBPjt6G7z8WMWlD2rOFZLk6OYfFIUGsCOWzcQH9K2og==", - "dev": true, - "peer": true - }, "data-urls": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-2.0.0.tgz", @@ -17738,24 +15436,6 @@ "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", "dev": true }, - "depd": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", - "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=", - "dev": true, - "peer": true - }, - "des.js": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.0.1.tgz", - "integrity": "sha512-Q0I4pfFrv2VPd34/vfLrFOoRmlYj3OV50i7fskps1jZWK1kApMWWT9G6RRUeYedLcBDIhnSDaUvJMb3AhUlaEA==", - "dev": true, - "peer": true, - "requires": { - "inherits": "^2.0.1", - "minimalistic-assert": "^1.0.0" - } - }, "detect-newline": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", @@ -17770,27 +15450,6 @@ "dev": true, "peer": true }, - "diffie-hellman": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz", - "integrity": "sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==", - "dev": true, - "peer": true, - "requires": { - "bn.js": "^4.1.0", - "miller-rabin": "^4.0.0", - "randombytes": "^2.0.0" - }, - "dependencies": { - "bn.js": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", - "dev": true, - "peer": true - } - } - }, "dir-glob": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", @@ -17833,13 +15492,6 @@ } } }, - "domain-browser": { - "version": "4.19.0", - "resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-4.19.0.tgz", - "integrity": "sha512-fRA+BaAWOR/yr/t7T9E9GJztHPeFjj8U35ajyAjCDtAAnTn1Rc1f6W6VGPJrO1tkQv9zWu+JRof7z6oQtiYVFQ==", - "dev": true, - "peer": true - }, "domelementtype": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz", @@ -17899,31 +15551,6 @@ "integrity": "sha512-WmCgAeURsMFiyoJ646eUaJQ7GNfvMRLXo+GamUyKVNEM4MqTAsXyC0f38JEB4N3BtbD0tlAKozGP5E2T9K3YGg==", "dev": true }, - "elliptic": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz", - "integrity": "sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==", - "dev": true, - "peer": true, - "requires": { - "bn.js": "^4.11.9", - "brorand": "^1.1.0", - "hash.js": "^1.0.0", - "hmac-drbg": "^1.0.1", - "inherits": "^2.0.4", - "minimalistic-assert": "^1.0.1", - "minimalistic-crypto-utils": "^1.0.1" - }, - "dependencies": { - "bn.js": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", - "dev": true, - "peer": true - } - } - }, "emittery": { "version": "0.7.2", "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.7.2.tgz", @@ -17937,28 +15564,6 @@ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "dev": true }, - "encoding": { - "version": "0.1.13", - "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz", - "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==", - "dev": true, - "peer": true, - "requires": { - "iconv-lite": "^0.6.2" - }, - "dependencies": { - "iconv-lite": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", - "dev": true, - "peer": true, - "requires": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - } - } - } - }, "end-of-stream": { "version": "1.4.4", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", @@ -18028,13 +15633,6 @@ "is-symbol": "^1.0.2" } }, - "es6-object-assign": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/es6-object-assign/-/es6-object-assign-1.1.0.tgz", - "integrity": "sha1-wsNYJlYkfDnqEHyx5mUrb58kUjw=", - "dev": true, - "peer": true - }, "escalade": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", @@ -18746,31 +16344,6 @@ "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", "dev": true }, - "etag": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=", - "dev": true, - "peer": true - }, - "events": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", - "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", - "dev": true, - "peer": true - }, - "evp_bytestokey": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz", - "integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==", - "dev": true, - "peer": true, - "requires": { - "md5.js": "^1.3.4", - "safe-buffer": "^5.1.1" - } - }, "exec-sh": { "version": "0.3.6", "resolved": "https://registry.npmjs.org/exec-sh/-/exec-sh-0.3.6.tgz", @@ -19135,99 +16708,10 @@ "to-regex-range": "^5.0.1" } }, - "find-cache-dir": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.1.tgz", - "integrity": "sha512-t2GDMt3oGC/v+BMwzmllWDuJF/xcDtE5j/fCGbqDD7OLuJkj0cfh1YSA5VKPvwMeLFLNDBkwOKZ2X85jGLVftQ==", - "dev": true, - "peer": true, - "requires": { - "commondir": "^1.0.1", - "make-dir": "^3.0.2", - "pkg-dir": "^4.1.0" - }, - "dependencies": { - "find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "peer": true, - "requires": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - } - }, - "locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "peer": true, - "requires": { - "p-locate": "^4.1.0" - } - }, - "make-dir": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", - "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", - "dev": true, - "peer": true, - "requires": { - "semver": "^6.0.0" - } - }, - "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "peer": true, - "requires": { - "p-try": "^2.0.0" - } - }, - "p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "peer": true, - "requires": { - "p-limit": "^2.2.0" - } - }, - "p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true, - "peer": true - }, - "pkg-dir": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", - "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", - "dev": true, - "peer": true, - "requires": { - "find-up": "^4.0.0" - } - }, - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true, - "peer": true - } - } - }, - "find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", "dev": true, "requires": { "locate-path": "^6.0.0", @@ -19266,13 +16750,6 @@ "dev": true, "peer": true }, - "foreach": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/foreach/-/foreach-2.0.5.tgz", - "integrity": "sha1-C+4AUBiusmDQo6865ljdATbsG5k=", - "dev": true, - "peer": true - }, "form-data": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz", @@ -19356,16 +16833,6 @@ "has-symbols": "^1.0.1" } }, - "get-orientation": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/get-orientation/-/get-orientation-1.1.2.tgz", - "integrity": "sha512-/pViTfifW+gBbh/RnlFYHINvELT9Znt+SYyDKAUL6uV6By019AK/s+i9XP4jSwq7lwP38Fd8HVeTxym3+hkwmQ==", - "dev": true, - "peer": true, - "requires": { - "stream-parser": "^0.3.1" - } - }, "get-own-enumerable-property-symbols": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.2.tgz", @@ -19425,13 +16892,6 @@ "is-glob": "^4.0.1" } }, - "glob-to-regexp": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", - "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", - "dev": true, - "peer": true - }, "global-modules": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-2.0.0.tgz", @@ -19626,57 +17086,6 @@ } } }, - "hash-base": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.1.0.tgz", - "integrity": "sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA==", - "dev": true, - "peer": true, - "requires": { - "inherits": "^2.0.4", - "readable-stream": "^3.6.0", - "safe-buffer": "^5.2.0" - }, - "dependencies": { - "safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true, - "peer": true - } - } - }, - "hash.js": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", - "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", - "dev": true, - "peer": true, - "requires": { - "inherits": "^2.0.3", - "minimalistic-assert": "^1.0.1" - } - }, - "he": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", - "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", - "dev": true, - "peer": true - }, - "hmac-drbg": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", - "integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=", - "dev": true, - "peer": true, - "requires": { - "hash.js": "^1.0.3", - "minimalistic-assert": "^1.0.0", - "minimalistic-crypto-utils": "^1.0.1" - } - }, "hosted-git-info": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.0.2.tgz", @@ -19735,13 +17144,6 @@ "debug": "4" } }, - "https-browserify": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz", - "integrity": "sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM=", - "dev": true, - "peer": true - }, "https-proxy-agent": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", @@ -19787,29 +17189,12 @@ "safer-buffer": ">= 2.1.2 < 3" } }, - "ieee754": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "dev": true, - "peer": true - }, "ignore": { "version": "5.1.8", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.8.tgz", "integrity": "sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw==", "dev": true }, - "image-size": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/image-size/-/image-size-1.0.0.tgz", - "integrity": "sha512-JLJ6OwBfO1KcA+TvJT+v8gbE6iWbj24LyDNFgFEN0lzegn6cC6a/p3NIDaepMsJjQjlUWqIC7wJv8lBFxPNjcw==", - "dev": true, - "peer": true, - "requires": { - "queue": "6.0.2" - } - }, "import-fresh": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", @@ -19995,16 +17380,6 @@ "is-decimal": "^1.0.0" } }, - "is-arguments": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.0.tgz", - "integrity": "sha512-1Ij4lOMPl/xB5kBDn7I+b2ttPMKa8szhEIrXDuXQD/oe3HJLTLhqhgGspwgyGd6MOywBUqVvYicF72lkgDnIHg==", - "dev": true, - "peer": true, - "requires": { - "call-bind": "^1.0.0" - } - }, "is-arrayish": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", @@ -20017,16 +17392,6 @@ "integrity": "sha512-0JV5+SOCQkIdzjBK9buARcV804Ddu7A0Qet6sHi3FimE9ne6m4BGQZfRn+NZiXbBk4F4XmHfDZIipLj9pX8dSA==", "dev": true }, - "is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, - "peer": true, - "requires": { - "binary-extensions": "^2.0.0" - } - }, "is-boolean-object": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.1.tgz", @@ -20132,13 +17497,6 @@ "dev": true, "peer": true }, - "is-generator-function": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.9.tgz", - "integrity": "sha512-ZJ34p1uvIfptHCN7sFTjGibB9/oBg17sHqzDLfuwhvmN/qLVvIQXRQ8licZQ35WJ8KuEQt/etnnzQFI9C9Ue/A==", - "dev": true, - "peer": true - }, "is-glob": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", @@ -20154,17 +17512,6 @@ "integrity": "sha512-gyPJuv83bHMpocVYoqof5VDiZveEoGoFL8m3BXNb2VW8Xs+rz9kqO8LOQ5DH6EsuvilT1ApazU0pyl+ytbPtlw==", "dev": true }, - "is-nan": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/is-nan/-/is-nan-1.3.2.tgz", - "integrity": "sha512-E+zBKpQ2t6MEo1VsonYmluk9NxGrbzpeeLC2xIViuO2EjU2xsXsBPwTr3Ykv9l08UYEVEdWeRZNouaZqF6RN0w==", - "dev": true, - "peer": true, - "requires": { - "call-bind": "^1.0.0", - "define-properties": "^1.1.3" - } - }, "is-negative-zero": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.1.tgz", @@ -20249,20 +17596,6 @@ "has-symbols": "^1.0.2" } }, - "is-typed-array": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.5.tgz", - "integrity": "sha512-S+GRDgJlR3PyEbsX/Fobd9cqpZBuvUS+8asRqYDMLCb2qMzt1oz5m5oxQCxOgUDxiWsOVNi4yaF+/uvdlHlYug==", - "dev": true, - "peer": true, - "requires": { - "available-typed-arrays": "^1.0.2", - "call-bind": "^1.0.2", - "es-abstract": "^1.18.0-next.2", - "foreach": "^2.0.5", - "has-symbols": "^1.0.1" - } - }, "is-typedarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", @@ -20972,30 +18305,6 @@ "string-length": "^4.0.1" } }, - "jest-worker": { - "version": "27.0.0-next.5", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.0.0-next.5.tgz", - "integrity": "sha512-mk0umAQ5lT+CaOJ+Qp01N6kz48sJG2kr2n1rX0koqKf6FIygQV0qLOdN9SCYID4IVeSigDOcPeGLozdMLYfb5g==", - "dev": true, - "peer": true, - "requires": { - "@types/node": "*", - "merge-stream": "^2.0.0", - "supports-color": "^8.0.0" - }, - "dependencies": { - "supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, - "peer": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, "js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -21341,13 +18650,6 @@ "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=", "dev": true }, - "lodash.sortby": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", - "integrity": "sha1-7dFMgk4sycHgsKG0K7UhBRakJDg=", - "dev": true, - "peer": true - }, "lodash.truncate": { "version": "4.4.2", "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", @@ -21469,18 +18771,6 @@ "integrity": "sha512-APMBEanjybaPzUrfqU0IMU5I0AswKMH7k8OTLs0vvV4KZpExkTkY87nR/zpbuTPj+gARop7aGUbl11pnDfW6xg==", "dev": true }, - "md5.js": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", - "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==", - "dev": true, - "peer": true, - "requires": { - "hash-base": "^3.0.0", - "inherits": "^2.0.1", - "safe-buffer": "^5.1.2" - } - }, "mdast-util-from-markdown": { "version": "0.8.5", "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-0.8.5.tgz", @@ -21573,26 +18863,6 @@ "picomatch": "^2.2.3" } }, - "miller-rabin": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/miller-rabin/-/miller-rabin-4.0.1.tgz", - "integrity": "sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA==", - "dev": true, - "peer": true, - "requires": { - "bn.js": "^4.0.0", - "brorand": "^1.0.1" - }, - "dependencies": { - "bn.js": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", - "dev": true, - "peer": true - } - } - }, "mime-db": { "version": "1.48.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.48.0.tgz", @@ -21620,20 +18890,6 @@ "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", "dev": true }, - "minimalistic-assert": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", - "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", - "dev": true, - "peer": true - }, - "minimalistic-crypto-utils": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", - "integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=", - "dev": true, - "peer": true - }, "minimatch": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", @@ -21702,11 +18958,10 @@ "dev": true }, "nanoid": { - "version": "3.1.23", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.23.tgz", - "integrity": "sha512-FiB0kzdP0FFVGDKlRLEQ1BgDzU87dy5NnzjeW9YZNt+/c3+q82EQDUwniSAUxp/F0gFNI1ZhKU1FqYsMuqZVnw==", - "dev": true, - "peer": true + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz", + "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==", + "dev": true }, "nanomatch": { "version": "1.2.13", @@ -21728,16 +18983,6 @@ "to-regex": "^3.0.1" } }, - "native-url": { - "version": "0.3.4", - "resolved": "https://registry.npmjs.org/native-url/-/native-url-0.3.4.tgz", - "integrity": "sha512-6iM8R99ze45ivyH8vybJ7X0yekIcPf5GgLV5K0ENCbmRcaRIDoj37BC8iLEmaaBfqqb8enuZ5p0uhY+lVAbAcA==", - "dev": true, - "peer": true, - "requires": { - "querystring": "^0.2.0" - } - }, "natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", @@ -21745,142 +18990,30 @@ "dev": true }, "next": { - "version": "11.1.2", - "resolved": "https://registry.npmjs.org/next/-/next-11.1.2.tgz", - "integrity": "sha512-azEYL0L+wFjv8lstLru3bgvrzPvK0P7/bz6B/4EJ9sYkXeW8r5Bjh78D/Ol7VOg0EIPz0CXoe72hzAlSAXo9hw==", - "dev": true, - "peer": true, - "requires": { - "@babel/runtime": "7.15.3", - "@hapi/accept": "5.0.2", - "@next/env": "11.1.2", - "@next/polyfill-module": "11.1.2", - "@next/react-dev-overlay": "11.1.2", - "@next/react-refresh-utils": "11.1.2", - "@next/swc-darwin-arm64": "11.1.2", - "@next/swc-darwin-x64": "11.1.2", - "@next/swc-linux-x64-gnu": "11.1.2", - "@next/swc-win32-x64-msvc": "11.1.2", - "@node-rs/helper": "1.2.1", - "assert": "2.0.0", - "ast-types": "0.13.2", - "browserify-zlib": "0.2.0", - "browserslist": "4.16.6", - "buffer": "5.6.0", - "caniuse-lite": "^1.0.30001228", - "chalk": "2.4.2", - "chokidar": "3.5.1", - "constants-browserify": "1.0.0", - "crypto-browserify": "3.12.0", - "cssnano-simple": "3.0.0", - "domain-browser": "4.19.0", - "encoding": "0.1.13", - "etag": "1.8.1", - "find-cache-dir": "3.3.1", - "get-orientation": "1.1.2", - "https-browserify": "1.0.0", - "image-size": "1.0.0", - "jest-worker": "27.0.0-next.5", - "native-url": "0.3.4", - "node-fetch": "2.6.1", - "node-html-parser": "1.4.9", - "node-libs-browser": "^2.2.1", - "os-browserify": "0.3.0", - "p-limit": "3.1.0", - "path-browserify": "1.0.1", - "pnp-webpack-plugin": "1.6.4", - "postcss": "8.2.15", - "process": "0.11.10", - "querystring-es3": "0.2.1", - "raw-body": "2.4.1", - "react-is": "17.0.2", - "react-refresh": "0.8.3", - "stream-browserify": "3.0.0", - "stream-http": "3.1.1", - "string_decoder": "1.3.0", - "styled-jsx": "4.0.1", - "timers-browserify": "2.0.12", - "tty-browserify": "0.0.1", - "use-subscription": "1.5.1", - "util": "0.12.4", - "vm-browserify": "1.1.2", - "watchpack": "2.1.1" - }, - "dependencies": { - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "peer": true, - "requires": { - "color-convert": "^1.9.0" - } - }, - "buffer": { - "version": "5.6.0", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.6.0.tgz", - "integrity": "sha512-/gDYp/UtU0eA1ys8bOs9J6a+E/KWIY+DZ+Q2WESNUA0jFRsJOc0SNUO6xJ5SGA1xueg3NL65W6s+NY5l9cunuw==", - "dev": true, - "peer": true, - "requires": { - "base64-js": "^1.0.2", - "ieee754": "^1.1.4" - } - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "peer": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "peer": true, - "requires": { - "color-name": "1.1.3" - } - }, - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", - "dev": true, - "peer": true - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true, - "peer": true - }, - "react-is": { - "version": "17.0.2", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", - "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", - "dev": true, - "peer": true - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "peer": true, - "requires": { - "has-flag": "^3.0.0" - } - } + "version": "12.3.1", + "resolved": "https://registry.npmjs.org/next/-/next-12.3.1.tgz", + "integrity": "sha512-l7bvmSeIwX5lp07WtIiP9u2ytZMv7jIeB8iacR28PuUEFG5j0HGAPnMqyG5kbZNBG2H7tRsrQ4HCjuMOPnANZw==", + "dev": true, + "requires": { + "@next/env": "12.3.1", + "@next/swc-android-arm-eabi": "12.3.1", + "@next/swc-android-arm64": "12.3.1", + "@next/swc-darwin-arm64": "12.3.1", + "@next/swc-darwin-x64": "12.3.1", + "@next/swc-freebsd-x64": "12.3.1", + "@next/swc-linux-arm-gnueabihf": "12.3.1", + "@next/swc-linux-arm64-gnu": "12.3.1", + "@next/swc-linux-arm64-musl": "12.3.1", + "@next/swc-linux-x64-gnu": "12.3.1", + "@next/swc-linux-x64-musl": "12.3.1", + "@next/swc-win32-arm64-msvc": "12.3.1", + "@next/swc-win32-ia32-msvc": "12.3.1", + "@next/swc-win32-x64-msvc": "12.3.1", + "@swc/helpers": "0.4.11", + "caniuse-lite": "^1.0.30001406", + "postcss": "8.4.14", + "styled-jsx": "5.0.7", + "use-sync-external-store": "1.2.0" } }, "nice-try": { @@ -21896,16 +19029,6 @@ "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==", "dev": true }, - "node-html-parser": { - "version": "1.4.9", - "resolved": "https://registry.npmjs.org/node-html-parser/-/node-html-parser-1.4.9.tgz", - "integrity": "sha512-UVcirFD1Bn0O+TSmloHeHqZZCxHjvtIeGdVdGMhyZ8/PWlEiZaZ5iJzR189yKZr8p0FXN58BUeC7RHRkf/KYGw==", - "dev": true, - "peer": true, - "requires": { - "he": "1.2.0" - } - }, "node-int64": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", @@ -21913,189 +19036,6 @@ "dev": true, "peer": true }, - "node-libs-browser": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/node-libs-browser/-/node-libs-browser-2.2.1.tgz", - "integrity": "sha512-h/zcD8H9kaDZ9ALUWwlBUDo6TKF8a7qBSCSEGfjTVIYeqsioSKaAX+BN7NgiMGp6iSIXZ3PxgCu8KS3b71YK5Q==", - "dev": true, - "peer": true, - "requires": { - "assert": "^1.1.1", - "browserify-zlib": "^0.2.0", - "buffer": "^4.3.0", - "console-browserify": "^1.1.0", - "constants-browserify": "^1.0.0", - "crypto-browserify": "^3.11.0", - "domain-browser": "^1.1.1", - "events": "^3.0.0", - "https-browserify": "^1.0.0", - "os-browserify": "^0.3.0", - "path-browserify": "0.0.1", - "process": "^0.11.10", - "punycode": "^1.2.4", - "querystring-es3": "^0.2.0", - "readable-stream": "^2.3.3", - "stream-browserify": "^2.0.1", - "stream-http": "^2.7.2", - "string_decoder": "^1.0.0", - "timers-browserify": "^2.0.4", - "tty-browserify": "0.0.0", - "url": "^0.11.0", - "util": "^0.11.0", - "vm-browserify": "^1.0.1" - }, - "dependencies": { - "assert": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/assert/-/assert-1.5.0.tgz", - "integrity": "sha512-EDsgawzwoun2CZkCgtxJbv392v4nbk9XDD06zI+kQYoBM/3RBWLlEyJARDOmhAAosBjWACEkKL6S+lIZtcAubA==", - "dev": true, - "peer": true, - "requires": { - "object-assign": "^4.1.1", - "util": "0.10.3" - }, - "dependencies": { - "util": { - "version": "0.10.3", - "resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz", - "integrity": "sha1-evsa/lCAUkZInj23/g7TeTNqwPk=", - "dev": true, - "peer": true, - "requires": { - "inherits": "2.0.1" - } - } - } - }, - "buffer": { - "version": "4.9.2", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.2.tgz", - "integrity": "sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg==", - "dev": true, - "peer": true, - "requires": { - "base64-js": "^1.0.2", - "ieee754": "^1.1.4", - "isarray": "^1.0.0" - } - }, - "domain-browser": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-1.2.0.tgz", - "integrity": "sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA==", - "dev": true, - "peer": true - }, - "inherits": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz", - "integrity": "sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE=", - "dev": true, - "peer": true - }, - "path-browserify": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-0.0.1.tgz", - "integrity": "sha512-BapA40NHICOS+USX9SN4tyhq+A2RrN/Ws5F0Z5aMHDp98Fl86lX8Oti8B7uN93L4Ifv4fHOEA+pQw87gmMO/lQ==", - "dev": true, - "peer": true - }, - "punycode": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", - "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", - "dev": true, - "peer": true - }, - "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "dev": true, - "peer": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - }, - "dependencies": { - "inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true, - "peer": true - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "peer": true, - "requires": { - "safe-buffer": "~5.1.0" - } - } - } - }, - "stream-browserify": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.2.tgz", - "integrity": "sha512-nX6hmklHs/gr2FuxYDltq8fJA1GDlxKQCz8O/IM4atRqBH8OORmBNgfvW5gG10GT/qQ9u0CzIvr2X5Pkt6ntqg==", - "dev": true, - "peer": true, - "requires": { - "inherits": "~2.0.1", - "readable-stream": "^2.0.2" - } - }, - "stream-http": { - "version": "2.8.3", - "resolved": "https://registry.npmjs.org/stream-http/-/stream-http-2.8.3.tgz", - "integrity": "sha512-+TSkfINHDo4J+ZobQLWiMouQYB+UVYFttRA94FpEzzJ7ZdqcL4uUUQ7WkdkI4DSozGmgBUE/a47L+38PenXhUw==", - "dev": true, - "peer": true, - "requires": { - "builtin-status-codes": "^3.0.0", - "inherits": "^2.0.1", - "readable-stream": "^2.3.6", - "to-arraybuffer": "^1.0.0", - "xtend": "^4.0.0" - } - }, - "tty-browserify": { - "version": "0.0.0", - "resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.0.tgz", - "integrity": "sha1-oVe6QC2iTpv5V/mqadUk7tQpAaY=", - "dev": true, - "peer": true - }, - "util": { - "version": "0.11.1", - "resolved": "https://registry.npmjs.org/util/-/util-0.11.1.tgz", - "integrity": "sha512-HShAsny+zS2TZfaXxD9tYj4HQGlBezXZMZuM/S5PKLLoZkShZiGk9o5CzukI1LVHZvjdvZ2Sj1aW/Ndn2NB/HQ==", - "dev": true, - "peer": true, - "requires": { - "inherits": "2.0.3" - }, - "dependencies": { - "inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", - "dev": true, - "peer": true - } - } - } - } - }, "node-notifier": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/node-notifier/-/node-notifier-8.0.2.tgz", @@ -22274,17 +19214,6 @@ "integrity": "sha512-jp7ikS6Sd3GxQfZJPyH3cjcbJF6GZPClgdV+EFygjFLQ5FmW/dRUnTd9PQ9k0JhoNDabWFbpF1yCdSWCC6gexg==", "dev": true }, - "object-is": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.5.tgz", - "integrity": "sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw==", - "dev": true, - "peer": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3" - } - }, "object-keys": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", @@ -22415,13 +19344,6 @@ "word-wrap": "^1.2.3" } }, - "os-browserify": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/os-browserify/-/os-browserify-0.3.0.tgz", - "integrity": "sha1-hUNzx/XCMVkU/Jv8a9gjj92h7Cc=", - "dev": true, - "peer": true - }, "os-tmpdir": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", @@ -22475,13 +19397,6 @@ "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", "dev": true }, - "pako": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", - "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", - "dev": true, - "peer": true - }, "parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -22491,20 +19406,6 @@ "callsites": "^3.0.0" } }, - "parse-asn1": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.6.tgz", - "integrity": "sha512-RnZRo1EPU6JBnra2vGHj0yhp6ebyjBZpmUCLHWiFhxlzvBCCpAuZ7elsBp1PVAbQN0/04VD/19rfzlBSwLstMw==", - "dev": true, - "peer": true, - "requires": { - "asn1.js": "^5.2.0", - "browserify-aes": "^1.0.0", - "evp_bytestokey": "^1.0.0", - "pbkdf2": "^3.0.3", - "safe-buffer": "^5.1.1" - } - }, "parse-entities": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-2.0.0.tgz", @@ -22545,13 +19446,6 @@ "dev": true, "peer": true }, - "path-browserify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz", - "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==", - "dev": true, - "peer": true - }, "path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -22582,19 +19476,11 @@ "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", "dev": true }, - "pbkdf2": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.2.tgz", - "integrity": "sha512-iuh7L6jA7JEGu2WxDwtQP1ddOpaJNC4KlDEFfdQajSGgGPNi4OyDc2R7QnbY2bR9QjBVGwgvTdNJZoE7RaxUMA==", - "dev": true, - "peer": true, - "requires": { - "create-hash": "^1.1.2", - "create-hmac": "^1.1.4", - "ripemd160": "^2.0.1", - "safe-buffer": "^5.0.1", - "sha.js": "^2.4.8" - } + "picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "dev": true }, "picomatch": { "version": "2.3.0", @@ -22733,13 +19619,6 @@ } } }, - "platform": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/platform/-/platform-1.3.6.tgz", - "integrity": "sha512-fnWVljUchTro6RiCFvCXBbNhJc2NijN7oIQxbwsyL0buWJPG85v81ehlHI9fXrJsMNgTofEoWIQeClKpgxFLrg==", - "dev": true, - "peer": true - }, "please-upgrade-node": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/please-upgrade-node/-/please-upgrade-node-3.2.0.tgz", @@ -22749,16 +19628,6 @@ "semver-compare": "^1.0.0" } }, - "pnp-webpack-plugin": { - "version": "1.6.4", - "resolved": "https://registry.npmjs.org/pnp-webpack-plugin/-/pnp-webpack-plugin-1.6.4.tgz", - "integrity": "sha512-7Wjy+9E3WwLOEL30D+m8TSTF7qJJUJLONBnwQp0518siuMxUQUbgZwssaFX+QKlZkjHZcw/IpZCt/H0srrntSg==", - "dev": true, - "peer": true, - "requires": { - "ts-pnp": "^1.1.6" - } - }, "posix-character-classes": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", @@ -22767,24 +19636,14 @@ "peer": true }, "postcss": { - "version": "8.2.15", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.2.15.tgz", - "integrity": "sha512-2zO3b26eJD/8rb106Qu2o7Qgg52ND5HPjcyQiK2B98O388h43A448LCslC0dI2P97wCAQRJsFvwTRcXxTKds+Q==", + "version": "8.4.14", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.14.tgz", + "integrity": "sha512-E398TUmfAYFPBSdzgeieK2Y1+1cpdxJx8yXbK/m57nRhKSmk1GB2tO4lbLBtlkfPQTDKfe4Xqv1ASWPpayPEig==", "dev": true, - "peer": true, "requires": { - "colorette": "^1.2.2", - "nanoid": "^3.1.23", - "source-map": "^0.6.1" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "peer": true - } + "nanoid": "^3.3.4", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" } }, "postcss-html": { @@ -23415,20 +20274,6 @@ } } }, - "process": { - "version": "0.11.10", - "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", - "integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI=", - "dev": true, - "peer": true - }, - "process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", - "dev": true, - "peer": true - }, "progress": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", @@ -23464,30 +20309,6 @@ "dev": true, "peer": true }, - "public-encrypt": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.3.tgz", - "integrity": "sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q==", - "dev": true, - "peer": true, - "requires": { - "bn.js": "^4.1.0", - "browserify-rsa": "^4.0.0", - "create-hash": "^1.1.0", - "parse-asn1": "^5.0.0", - "randombytes": "^2.0.1", - "safe-buffer": "^5.1.2" - }, - "dependencies": { - "bn.js": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", - "dev": true, - "peer": true - } - } - }, "pump": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", @@ -23499,36 +20320,12 @@ "once": "^1.3.1" } }, - "punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", - "dev": true - }, - "querystring": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.1.tgz", - "integrity": "sha512-wkvS7mL/JMugcup3/rMitHmd9ecIGd2lhFhK9N3UUQ450h66d1r3Y9nvXzQAW1Lq+wyx61k/1pfKS5KuKiyEbg==", - "dev": true, - "peer": true - }, - "querystring-es3": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/querystring-es3/-/querystring-es3-0.2.1.tgz", - "integrity": "sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM=", - "dev": true, - "peer": true - }, - "queue": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/queue/-/queue-6.0.2.tgz", - "integrity": "sha512-iHZWu+q3IdFZFX36ro/lKBkSvfkztY5Y7HMiPlOUjhupPcG2JMfst2KKEpu5XndviX/3UhFbRngUPNKtgvtZiA==", - "dev": true, - "peer": true, - "requires": { - "inherits": "~2.0.3" - } - }, + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "dev": true + }, "queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -23541,77 +20338,25 @@ "integrity": "sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==", "dev": true }, - "randombytes": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", - "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "dev": true, - "peer": true, - "requires": { - "safe-buffer": "^5.1.0" - } - }, - "randomfill": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/randomfill/-/randomfill-1.0.4.tgz", - "integrity": "sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw==", - "dev": true, - "peer": true, - "requires": { - "randombytes": "^2.0.5", - "safe-buffer": "^5.1.0" - } - }, - "raw-body": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.1.tgz", - "integrity": "sha512-9WmIKF6mkvA0SLmA2Knm9+qj89e+j1zqgyn8aXGd7+nAduPoqgI9lO57SAZNn/Byzo5P7JhXTyg9PzaJbH73bA==", - "dev": true, - "peer": true, - "requires": { - "bytes": "3.1.0", - "http-errors": "1.7.3", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" - }, - "dependencies": { - "http-errors": { - "version": "1.7.3", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.3.tgz", - "integrity": "sha512-ZTTX0MWrsQ2ZAhA1cejAwDLycFsd7I7nVtnkT3Ol0aqodaKW+0CTZDQ1uBv5whptCnc8e8HeRRJxRs0kmm/Qfw==", - "dev": true, - "peer": true, - "requires": { - "depd": "~1.1.2", - "inherits": "2.0.4", - "setprototypeof": "1.1.1", - "statuses": ">= 1.5.0 < 2", - "toidentifier": "1.0.0" - } - } - } - }, "react": { - "version": "17.0.2", - "resolved": "https://registry.npmjs.org/react/-/react-17.0.2.tgz", - "integrity": "sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA==", + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", + "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", "dev": true, "peer": true, "requires": { - "loose-envify": "^1.1.0", - "object-assign": "^4.1.1" + "loose-envify": "^1.1.0" } }, "react-dom": { - "version": "17.0.2", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-17.0.2.tgz", - "integrity": "sha512-s4h96KtLDUQlsENhMn1ar8t2bEa+q/YAtj8pPPdIjPDGBDIVNsrD9aXNWqspUe6AzKCIG0C1HZZLqLV7qpOBGA==", + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", + "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==", "dev": true, "peer": true, "requires": { "loose-envify": "^1.1.0", - "object-assign": "^4.1.1", - "scheduler": "^0.20.2" + "scheduler": "^0.23.0" } }, "react-is": { @@ -23620,13 +20365,6 @@ "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", "dev": true }, - "react-refresh": { - "version": "0.8.3", - "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.8.3.tgz", - "integrity": "sha512-X8jZHc7nCMjaCqoU+V2I0cOhNW+QMBwSUkeXnTi8IPe6zaRWfn60ZzvFDZqWPfmSJfjub7dDW1SP0jaHWLu/hg==", - "dev": true, - "peer": true - }, "read-pkg": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", @@ -23933,17 +20671,6 @@ "glob": "^7.1.3" } }, - "ripemd160": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz", - "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==", - "dev": true, - "peer": true, - "requires": { - "hash-base": "^3.0.0", - "inherits": "^2.0.1" - } - }, "rivet-graphql": { "version": "0.3.1", "resolved": "https://registry.npmjs.org/rivet-graphql/-/rivet-graphql-0.3.1.tgz", @@ -24275,14 +21002,13 @@ } }, "scheduler": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.20.2.tgz", - "integrity": "sha512-2eWfGgAqqWFGqtdMmcL5zCMK1U8KlXv8SQFGglL3CEtd0aDVDWgeF/YoCmvln55m5zSk3J/20hTaSBeSObsQDQ==", + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz", + "integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==", "dev": true, "peer": true, "requires": { - "loose-envify": "^1.1.0", - "object-assign": "^4.1.1" + "loose-envify": "^1.1.0" } }, "semver": { @@ -24338,31 +21064,6 @@ } } }, - "setimmediate": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", - "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=", - "dev": true, - "peer": true - }, - "setprototypeof": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", - "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==", - "dev": true, - "peer": true - }, - "sha.js": { - "version": "2.4.11", - "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", - "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", - "dev": true, - "peer": true, - "requires": { - "inherits": "^2.0.1", - "safe-buffer": "^5.0.1" - } - }, "shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -24378,13 +21079,6 @@ "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", "dev": true }, - "shell-quote": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.7.2.tgz", - "integrity": "sha512-mRz/m/JVscCrkMyPqHc/bczi3OQHkLTqXHEFu0zDhK/qfv3UcOA4SVmRCLmos4bhjr9ekVQubj/R7waKapmiQg==", - "dev": true, - "peer": true - }, "shellwords": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/shellwords/-/shellwords-0.1.1.tgz", @@ -24697,6 +21391,12 @@ "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", "dev": true }, + "source-map-js": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", + "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", + "dev": true + }, "source-map-resolve": { "version": "0.5.3", "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.3.tgz", @@ -24811,25 +21511,6 @@ } } }, - "stacktrace-parser": { - "version": "0.1.10", - "resolved": "https://registry.npmjs.org/stacktrace-parser/-/stacktrace-parser-0.1.10.tgz", - "integrity": "sha512-KJP1OCML99+8fhOHxwwzyWrlUuVX5GQ0ZpJTd1DFXhdkrvg1szxfHhawXUZ3g9TkXORQd4/WG68jMlQZ2p8wlg==", - "dev": true, - "peer": true, - "requires": { - "type-fest": "^0.7.1" - }, - "dependencies": { - "type-fest": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.7.1.tgz", - "integrity": "sha512-Ne2YiiGN8bmrmJJEuTWTLJR32nh/JdL1+PSicowtNb0WFpn59GK8/lfD61bVtzguz7b3PBt74nxpv/Pw5po5Rg==", - "dev": true, - "peer": true - } - } - }, "static-extend": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", @@ -24923,66 +21604,6 @@ } } }, - "statuses": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", - "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=", - "dev": true, - "peer": true - }, - "stream-browserify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-3.0.0.tgz", - "integrity": "sha512-H73RAHsVBapbim0tU2JwwOiXUj+fikfiaoYAKHF3VJfA0pe2BCzkhAHBlLG6REzE+2WNZcxOXjK7lkso+9euLA==", - "dev": true, - "peer": true, - "requires": { - "inherits": "~2.0.4", - "readable-stream": "^3.5.0" - } - }, - "stream-http": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/stream-http/-/stream-http-3.1.1.tgz", - "integrity": "sha512-S7OqaYu0EkFpgeGFb/NPOoPLxFko7TPqtEeFg5DXPB4v/KETHG0Ln6fRFrNezoelpaDKmycEmmZ81cC9DAwgYg==", - "dev": true, - "peer": true, - "requires": { - "builtin-status-codes": "^3.0.0", - "inherits": "^2.0.4", - "readable-stream": "^3.6.0", - "xtend": "^4.0.2" - } - }, - "stream-parser": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/stream-parser/-/stream-parser-0.3.1.tgz", - "integrity": "sha1-FhhUhpRCACGhGC/wrxkRwSl2F3M=", - "dev": true, - "peer": true, - "requires": { - "debug": "2" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "peer": true, - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true, - "peer": true - } - } - }, "string_decoder": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", @@ -25006,13 +21627,6 @@ "integrity": "sha512-a1uQGz7IyVy9YwhqjZIZu1c8JO8dNIe20xBmSS6qu9kv++k3JGzCVmprbNN5Kn+BgzD5E7YYwg1CcjuJMRNsvg==", "dev": true }, - "string-hash": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/string-hash/-/string-hash-1.1.3.tgz", - "integrity": "sha1-6Kr8CsGFW0Zmkp7X3RJ1311sgRs=", - "dev": true, - "peer": true - }, "string-length": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", @@ -25143,69 +21757,11 @@ "dev": true }, "styled-jsx": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-4.0.1.tgz", - "integrity": "sha512-Gcb49/dRB1k8B4hdK8vhW27Rlb2zujCk1fISrizCcToIs+55B4vmUM0N9Gi4nnVfFZWe55jRdWpAqH1ldAKWvQ==", + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.0.7.tgz", + "integrity": "sha512-b3sUzamS086YLRuvnaDigdAewz1/EFYlHpYBP5mZovKEdQQOIIYq8lApylub3HHZ6xFjV051kkGU7cudJmrXEA==", "dev": true, - "peer": true, - "requires": { - "@babel/plugin-syntax-jsx": "7.14.5", - "@babel/types": "7.15.0", - "convert-source-map": "1.7.0", - "loader-utils": "1.2.3", - "source-map": "0.7.3", - "string-hash": "1.1.3", - "stylis": "3.5.4", - "stylis-rule-sheet": "0.0.10" - }, - "dependencies": { - "@babel/plugin-syntax-jsx": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.14.5.tgz", - "integrity": "sha512-ohuFIsOMXJnbOMRfX7/w7LocdR6R7whhuRD4ax8IipLcLPlZGJKkBxgHp++U4N/vKyU16/YDQr2f5seajD3jIw==", - "dev": true, - "peer": true, - "requires": { - "@babel/helper-plugin-utils": "^7.14.5" - } - }, - "emojis-list": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-2.1.0.tgz", - "integrity": "sha1-TapNnbAPmBmIDHn6RXrlsJof04k=", - "dev": true, - "peer": true - }, - "json5": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", - "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", - "dev": true, - "peer": true, - "requires": { - "minimist": "^1.2.0" - } - }, - "loader-utils": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.2.3.tgz", - "integrity": "sha512-fkpz8ejdnEMG3s37wGL07iSBDg99O9D5yflE9RGNH3hRdx9SOwYfnGYdZOUIZitN8E+E2vkq3MUMYMvPYl5ZZA==", - "dev": true, - "peer": true, - "requires": { - "big.js": "^5.2.2", - "emojis-list": "^2.0.0", - "json5": "^1.0.1" - } - }, - "source-map": { - "version": "0.7.3", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", - "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==", - "dev": true, - "peer": true - } - } + "requires": {} }, "stylelint": { "version": "13.8.0", @@ -25495,21 +22051,6 @@ "postcss-values-parser": "^3.2.1" } }, - "stylis": { - "version": "3.5.4", - "resolved": "https://registry.npmjs.org/stylis/-/stylis-3.5.4.tgz", - "integrity": "sha512-8/3pSmthWM7lsPBKv7NXkzn2Uc9W7NotcwGNpJaa3k7WMM1XDCA4MgT5k/8BIexd5ydZdboXtU90XH9Ec4Bv/Q==", - "dev": true, - "peer": true - }, - "stylis-rule-sheet": { - "version": "0.0.10", - "resolved": "https://registry.npmjs.org/stylis-rule-sheet/-/stylis-rule-sheet-0.0.10.tgz", - "integrity": "sha512-nTbZoaqoBnmK+ptANthb10ZRZOGC+EmTLLUxeYIuHNkEKcmKgXX1XWKkUBT2Ac4es3NybooPe0SmvKdhKJZAuw==", - "dev": true, - "peer": true, - "requires": {} - }, "sugarss": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/sugarss/-/sugarss-2.0.0.tgz", @@ -25719,16 +22260,6 @@ "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", "dev": true }, - "timers-browserify": { - "version": "2.0.12", - "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-2.0.12.tgz", - "integrity": "sha512-9phl76Cqm6FhSX9Xe1ZUAMLtm1BLkKj2Qd5ApyWkXzsMRaA7dgr81kf4wJmQf/hAvg8EEyJxDo3du/0KlhPiKQ==", - "dev": true, - "peer": true, - "requires": { - "setimmediate": "^1.0.4" - } - }, "tlds": { "version": "1.221.1", "resolved": "https://registry.npmjs.org/tlds/-/tlds-1.221.1.tgz", @@ -25751,13 +22282,6 @@ "dev": true, "peer": true }, - "to-arraybuffer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz", - "integrity": "sha1-fSKbH8xjfkZsoIEYCDanqr/4P0M=", - "dev": true, - "peer": true - }, "to-fast-properties": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", @@ -25815,13 +22339,6 @@ "is-number": "^7.0.0" } }, - "toidentifier": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", - "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==", - "dev": true, - "peer": true - }, "tough-cookie": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.0.0.tgz", @@ -25843,16 +22360,6 @@ } } }, - "tr46": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz", - "integrity": "sha1-qLE/1r/SSJUZZ0zN5VujaTtwbQk=", - "dev": true, - "peer": true, - "requires": { - "punycode": "^2.1.0" - } - }, "trim-newlines": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-3.0.1.tgz", @@ -25883,13 +22390,6 @@ "yargs-parser": "20.x" } }, - "ts-pnp": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/ts-pnp/-/ts-pnp-1.2.0.tgz", - "integrity": "sha512-csd+vJOb/gkzvcCHgTGSChYpy5f1/XKNsmvBGO4JXS+z1v2HobugDz4s1IeFXM3wZB44uczs+eazB5Q/ccdhQw==", - "dev": true, - "peer": true - }, "tsconfig-paths": { "version": "3.11.0", "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.11.0.tgz", @@ -25928,13 +22428,6 @@ "tslib": "^1.8.1" } }, - "tty-browserify": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.1.tgz", - "integrity": "sha512-C3TaO7K81YvjCgQH9Q1S3R3P3BtN3RIM8n+OvX4il1K1zgE8ZhI0op7kClgkxtutIE8hQrcrHBXvIheqKUUCxw==", - "dev": true, - "peer": true - }, "type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -26050,13 +22543,6 @@ "integrity": "sha512-rb6X1W158d7pRQBg5gkR8uPaSfiids68LTJQYOtEUhoJUWBdaQHsuT/EUduxXYxcrt4r5PJ4fuHW1MHT6p0qug==", "dev": true }, - "unpipe": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=", - "dev": true, - "peer": true - }, "unset-value": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", @@ -26117,33 +22603,6 @@ "dev": true, "peer": true }, - "url": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/url/-/url-0.11.0.tgz", - "integrity": "sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE=", - "dev": true, - "peer": true, - "requires": { - "punycode": "1.3.2", - "querystring": "0.2.0" - }, - "dependencies": { - "punycode": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", - "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=", - "dev": true, - "peer": true - }, - "querystring": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", - "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=", - "dev": true, - "peer": true - } - } - }, "url-regex": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/url-regex/-/url-regex-5.0.0.tgz", @@ -26161,30 +22620,12 @@ "dev": true, "peer": true }, - "use-subscription": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/use-subscription/-/use-subscription-1.5.1.tgz", - "integrity": "sha512-Xv2a1P/yReAjAbhylMfFplFKj9GssgTwN7RlcTxBujFQcloStWNDQdc4g4NRWH9xS4i/FDk04vQBptAXoF3VcA==", - "dev": true, - "peer": true, - "requires": { - "object-assign": "^4.1.1" - } - }, - "util": { - "version": "0.12.4", - "resolved": "https://registry.npmjs.org/util/-/util-0.12.4.tgz", - "integrity": "sha512-bxZ9qtSlGUWSOy9Qa9Xgk11kSslpuZwaxCg4sNIDj6FLucDab2JxnHwyNTCpHMtK1MjoQiWQ6DiUMZYbSrO+Sw==", + "use-sync-external-store": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz", + "integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==", "dev": true, - "peer": true, - "requires": { - "inherits": "^2.0.3", - "is-arguments": "^1.0.4", - "is-generator-function": "^1.0.7", - "is-typed-array": "^1.1.3", - "safe-buffer": "^5.1.2", - "which-typed-array": "^1.1.2" - } + "requires": {} }, "util-deprecate": { "version": "1.0.2", @@ -26251,13 +22692,6 @@ "unist-util-stringify-position": "^2.0.0" } }, - "vm-browserify": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-1.1.2.tgz", - "integrity": "sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ==", - "dev": true, - "peer": true - }, "w3c-hr-time": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz", @@ -26288,24 +22722,6 @@ "makeerror": "1.0.12" } }, - "watchpack": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.1.1.tgz", - "integrity": "sha512-Oo7LXCmc1eE1AjyuSBmtC3+Wy4HcV8PxWh2kP6fOl8yTlNS7r0K9l1ao2lrrUza7V39Y3D/BbJgY8VeSlc5JKw==", - "dev": true, - "peer": true, - "requires": { - "glob-to-regexp": "^0.4.1", - "graceful-fs": "^4.1.2" - } - }, - "webidl-conversions": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz", - "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==", - "dev": true, - "peer": true - }, "whatwg-encoding": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz", @@ -26323,18 +22739,6 @@ "dev": true, "peer": true }, - "whatwg-url": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-7.1.0.tgz", - "integrity": "sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==", - "dev": true, - "peer": true, - "requires": { - "lodash.sortby": "^4.7.0", - "tr46": "^1.0.1", - "webidl-conversions": "^4.0.2" - } - }, "which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -26370,22 +22774,6 @@ "integrity": "sha1-Zws6+8VS4LVd9rd4DKdGFfI60cs=", "dev": true }, - "which-typed-array": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.4.tgz", - "integrity": "sha512-49E0SpUe90cjpoc7BOJwyPHRqSAd12c10Qm2amdEZrJPCY2NDxaW01zHITrem+rnETY3dwrbH3UUrUwagfCYDA==", - "dev": true, - "peer": true, - "requires": { - "available-typed-arrays": "^1.0.2", - "call-bind": "^1.0.0", - "es-abstract": "^1.18.0-next.1", - "foreach": "^2.0.5", - "function-bind": "^1.1.1", - "has-symbols": "^1.0.1", - "is-typed-array": "^1.1.3" - } - }, "word-wrap": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", @@ -26435,13 +22823,6 @@ "dev": true, "peer": true }, - "xtend": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", - "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", - "dev": true, - "peer": true - }, "y18n": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", diff --git a/website/package.json b/website/package.json index b01884c918..cfc4fa07c7 100644 --- a/website/package.json +++ b/website/package.json @@ -3,11 +3,11 @@ "description": "Description of your website", "version": "0.0.1", "author": "HashiCorp", - "dependencies": {}, "devDependencies": { "@hashicorp/platform-cli": "^1.2.0", "dart-linkcheck": "2.0.15", "husky": "4.3.8", + "next": "^12.3.1", "prettier": "2.2.1" }, "husky": { diff --git a/website/public/img/consul-arch.png b/website/public/img/consul-arch.png deleted file mode 100644 index 281b03dee5fa8ac6dcf84a57839ae8e44eed1e59..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 115945 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 \ No newline at end of file diff --git a/website/public/img/consul-arch/consul-arch-overview-control-plane.svg b/website/public/img/consul-arch/consul-arch-overview-control-plane.svg new file mode 100644 index 0000000000..51dc39d226 --- /dev/null +++ b/website/public/img/consul-arch/consul-arch-overview-control-plane.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/website/public/img/consul-arch/consul-arch-overview-lan-gossip-pool.svg b/website/public/img/consul-arch/consul-arch-overview-lan-gossip-pool.svg new file mode 100644 index 0000000000..b800523724 --- /dev/null +++ b/website/public/img/consul-arch/consul-arch-overview-lan-gossip-pool.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/website/public/img/consul-arch/consul-arch-overview-remote-dc-forwarding-cross-cluster.svg b/website/public/img/consul-arch/consul-arch-overview-remote-dc-forwarding-cross-cluster.svg new file mode 100644 index 0000000000..8bd2535657 --- /dev/null +++ b/website/public/img/consul-arch/consul-arch-overview-remote-dc-forwarding-cross-cluster.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/website/public/img/consul-arch/consul-arch-overview-rpc.svg b/website/public/img/consul-arch/consul-arch-overview-rpc.svg new file mode 100644 index 0000000000..68aac6c6e3 --- /dev/null +++ b/website/public/img/consul-arch/consul-arch-overview-rpc.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/website/public/img/consul-arch/consul-arch-overview-wan-gossip-cross-cluster.svg b/website/public/img/consul-arch/consul-arch-overview-wan-gossip-cross-cluster.svg new file mode 100644 index 0000000000..b36ab70ba4 --- /dev/null +++ b/website/public/img/consul-arch/consul-arch-overview-wan-gossip-cross-cluster.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/website/public/img/consul_ecosystem_diagram2.png b/website/public/img/consul_ecosystem_diagram2.png index f4cb4ad74262ca56d752ebd2bbb78fa890c8963b..a57e1bea3a419ca14b3a7d9eb002954027321c0d 100644 GIT binary patch literal 69365 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

  • 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Ν% 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�#ieR`g(_j20xPN9jQMKG~5^)QbFE4ZSz+kGe9&SCoRb zal~I57PSAJ0|kqJ>O7Wc(Q5Ss5FWz*ML_>I`wa%Txpt=&7~ub@hgm3e;~ERIzY6gc zJ}oZ|_yVTQ|3z1;Mv-DZoINZJDZ{~^pV0*m_+Rw80-$3#K|}p}|4lT6Ks?l5zI-{> z*Fh2j!LR>EBmh(uh6h=~HdRTZH|I+NOe-p?s@~qkFVpXF>XcEK<#;QiEdUZZ-c@}D zC@Z{rmkboqGGR3B2U$8tdT>8q1)#oFakvFUx-2gGFEfDI&$-AZu#N+wYPrVi+f<0r z;Ag?vgTDblGvcuELqJL+tB?(FT%kYfJA*O8f;oI<-3d!}K!b68d9qwPal=48b=yGa zcd^QC1H~0<_eOS1FHtW~H|+DgC`iM_0Lr&>Eg*mxD1G^*{{Vh>J1~DeH?onKA55a} zS-ualizJMUj9iriGPWT^sCHy{_>VD&WifJCVpy4Cmk-og(&`DpZ4IsX{a2+DQ2+LL zedw+V=~jn()24N9D<(h{iO5HhbOC20c5}fhzuN$o-WaEL1OD^t^wI#MuwWzO zA{IpziDnu-**`L!s)<(}KR?v@>Wj9p!T1$`?4oy!r^~%lcv< z03z1ZV^}}03UvJOiDvM<5o|D6^r$ug*X4G_DTHOP-bQxzPae}J*%>5nqnxj=f!_1= z>#k)S)s=b7wA%)0c)1<}{^zZ!BW~?P#m&3hgFA@-b)p&t!Gh5}@by$dUA{urH0b{W DPHr`~ diff --git a/website/public/img/invoke-service-from-lambda-flow.svg b/website/public/img/invoke-service-from-lambda-flow.svg new file mode 100644 index 0000000000..b67e54c6e7 --- /dev/null +++ b/website/public/img/invoke-service-from-lambda-flow.svg @@ -0,0 +1 @@ + \ No newline at end of file