From d818d7b0967b7f7adabb8586c12e234e0af2d02d Mon Sep 17 00:00:00 2001 From: freddygv Date: Thu, 22 Sep 2022 19:24:13 -0600 Subject: [PATCH 001/172] Manage local server watches depending on mesh cfg Routing peering control plane traffic through mesh gateways can be enabled or disabled at runtime with the mesh config entry. This commit updates proxycfg to add or cancel watches for local servers depending on this central config. Note that WAN federation over mesh gateways is determined by a service metadata flag, and any updates to the gateway service registration will force the creation of a new snapshot. If enabled, WAN-fed over mesh gateways will trigger a local server watch on initialize(). Because of this we will only add/remove server watches if WAN federation over mesh gateways is disabled. --- agent/proxycfg/mesh_gateway.go | 63 +++++++++-- agent/proxycfg/snapshot.go | 16 ++- agent/proxycfg/state_test.go | 184 +++++++++++++++++++++++++++++++++ agent/xds/clusters.go | 3 +- agent/xds/endpoints.go | 3 +- agent/xds/listeners.go | 5 +- 6 files changed, 254 insertions(+), 20 deletions(-) diff --git a/agent/proxycfg/mesh_gateway.go b/agent/proxycfg/mesh_gateway.go index 93fffdc31d..6de49b69e8 100644 --- a/agent/proxycfg/mesh_gateway.go +++ b/agent/proxycfg/mesh_gateway.go @@ -8,6 +8,7 @@ import ( "time" 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" @@ -21,6 +22,8 @@ type handlerMeshGateway struct { // 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.WatchedConsulServers = watch.NewMap[string, structs.CheckServiceNodes]() + // Watch for root changes err := s.dataSources.CARoots.Notify(ctx, &structs.DCSpecificRequest{ Datacenter: s.source.Datacenter, @@ -76,7 +79,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 } } @@ -123,7 +126,7 @@ func (s *handlerMeshGateway) initialize(ctx context.Context) (ConfigSnapshot, er 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 +148,7 @@ func (s *handlerMeshGateway) initializeCrossDCWatches(ctx context.Context) error if err != nil { return err } + snap.MeshGateway.WatchedConsulServers.InitWatch(structs.ConsulServiceName, nil) } err := s.dataSources.Datacenters.Notify(ctx, &structs.DatacentersRequest{ @@ -325,7 +329,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 +340,7 @@ func (s *handlerMeshGateway) handleUpdate(ctx context.Context, u UpdateEvent, sn } } - snap.MeshGateway.ConsulServers = resp.Nodes + snap.MeshGateway.WatchedConsulServers.Set(structs.ConsulServiceName, resp.Nodes) case exportedServiceListWatchID: exportedServices, ok := u.Result.(*structs.IndexedExportedServiceList) @@ -463,17 +466,55 @@ func (s *handlerMeshGateway) handleUpdate(ctx context.Context, u UpdateEvent, sn 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 { + if resp.Entry == nil { snap.MeshGateway.MeshConfig = nil + + // We avoid managing server watches when WAN federation is enabled since it + // always requires server watches. + 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.WatchedConsulServers.CancelWatch(structs.ConsulServiceName) + } + + snap.MeshGateway.MeshConfigSet = true + return nil } + + meshConf, ok := resp.Entry.(*structs.MeshConfigEntry) + if !ok { + return fmt.Errorf("invalid type for config entry: %T", resp.Entry) + } + snap.MeshGateway.MeshConfig = meshConf snap.MeshGateway.MeshConfigSet = true + // We avoid managing Consul server watches when WAN federation is enabled since it + // always requires server watches. + if s.meta[structs.MetaWANFederationKey] == "1" { + return nil + } + + if meshConf.Peering == nil || !meshConf.Peering.PeerThroughMeshGateways { + snap.MeshGateway.WatchedConsulServers.CancelWatch(structs.ConsulServiceName) + return nil + } + if snap.MeshGateway.WatchedConsulServers.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.WatchedConsulServers.InitWatch(structs.ConsulServiceName, cancel) + default: switch { case strings.HasPrefix(u.CorrelationID, "connect-service:"): diff --git a/agent/proxycfg/snapshot.go b/agent/proxycfg/snapshot.go index 23cb8a9556..130977a8c6 100644 --- a/agent/proxycfg/snapshot.go +++ b/agent/proxycfg/snapshot.go @@ -375,8 +375,11 @@ type configSnapshotMeshGateway struct { // datacenter. FedStateGateways map[string]structs.CheckServiceNodes - // ConsulServers is the list of consul servers in this datacenter. - ConsulServers structs.CheckServiceNodes + // WatchedConsulServers 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. + WatchedConsulServers 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. @@ -556,8 +559,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.WatchedConsulServers.Len() == 0 && c.isEmptyPeering() } @@ -690,8 +693,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.WatchedConsulServers.Len() == 0 { + if s.ServiceMeta[structs.MetaWANFederationKey] == "1" { + return false + } + if cfg := s.MeshConfig(); cfg != nil && cfg.Peering != nil && cfg.Peering.PeerThroughMeshGateways { return false } } diff --git a/agent/proxycfg/state_test.go b/agent/proxycfg/state_test.go index f8cf0834ce..3add369a8e 100644 --- a/agent/proxycfg/state_test.go +++ b/agent/proxycfg/state_test.go @@ -779,6 +779,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 +793,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") @@ -1015,6 +1019,186 @@ 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), + }, + 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()) + + servers, ok := snap.MeshGateway.WatchedConsulServers.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) + }, + }, + { + 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.False(t, snap.MeshGateway.WatchedConsulServers.IsWatched(structs.ConsulServiceName)) + servers, ok := snap.MeshGateway.WatchedConsulServers.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.WatchedConsulServers.IsWatched(structs.ConsulServiceName)) + servers, ok := snap.MeshGateway.WatchedConsulServers.Get(structs.ConsulServiceName) + require.False(t, ok) + require.Empty(t, servers) + }, + }, + }, + }, "ingress-gateway": { ns: structs.NodeService{ Kind: structs.ServiceKindIngressGateway, diff --git a/agent/xds/clusters.go b/agent/xds/clusters.go index a425f829ee..2889868bb5 100644 --- a/agent/xds/clusters.go +++ b/agent/xds/clusters.go @@ -386,7 +386,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.WatchedConsulServers.Get(structs.ConsulServiceName) + for _, srv := range servers { opts := clusterOpts{ name: cfgSnap.ServerSNIFn(cfgSnap.Datacenter, srv.Node.Node), } diff --git a/agent/xds/endpoints.go b/agent/xds/endpoints.go index b5588ce649..d3083979b8 100644 --- a/agent/xds/endpoints.go +++ b/agent/xds/endpoints.go @@ -249,7 +249,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.WatchedConsulServers.Get(structs.ConsulServiceName) + for _, srv := range servers { clusterName := cfgSnap.ServerSNIFn(cfgSnap.Datacenter, srv.Node.Node) _, addr, port := srv.BestAddress(false /*wan*/) diff --git a/agent/xds/listeners.go b/agent/xds/listeners.go index cfea25cbc1..d74d44ab87 100644 --- a/agent/xds/listeners.go +++ b/agent/xds/listeners.go @@ -274,7 +274,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, @@ -1739,7 +1739,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.WatchedConsulServers.Get(structs.ConsulServiceName) + for _, srv := range servers { clusterName := cfgSnap.ServerSNIFn(cfgSnap.Datacenter, srv.Node.Node) filterName := fmt.Sprintf("%s.%s", name, cfgSnap.Datacenter) From aa4709ab74f8feda5363df7390deeb8cb77f7d0a Mon Sep 17 00:00:00 2001 From: Derek Menteer <105233703+hashi-derek@users.noreply.github.com> Date: Mon, 26 Sep 2022 11:29:06 -0500 Subject: [PATCH 002/172] Add envoy connection balancing. (#14616) Add envoy connection balancing config. --- .changelog/14616.txt | 3 + agent/configentry/resolve.go | 39 +++--- agent/configentry/resolve_test.go | 20 +++ agent/structs/config_entry.go | 48 +++++-- agent/structs/config_entry_test.go | 110 ++++++++++++---- agent/xds/config.go | 4 + agent/xds/config_test.go | 11 ++ agent/xds/listeners.go | 18 +++ agent/xds/listeners_test.go | 16 +++ ...-balance-inbound-connections.latest.golden | 122 ++++++++++++++++++ ...tbound-connections-bind-port.latest.golden | 122 ++++++++++++++++++ api/config_entry.go | 41 +++--- api/config_entry_test.go | 14 +- .../config-entries/service-defaults.mdx | 27 ++++ .../content/docs/connect/proxies/envoy.mdx | 16 +++ 15 files changed, 528 insertions(+), 83 deletions(-) create mode 100644 .changelog/14616.txt 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 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/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/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_test.go b/agent/structs/config_entry_test.go index 7a699417f0..6aca9af4ea 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", }, }, }, @@ -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/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/listeners.go b/agent/xds/listeners.go index cfea25cbc1..9f9136bf3f 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, } @@ -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 { @@ -1221,6 +1238,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 != "" { diff --git a/agent/xds/listeners_test.go b/agent/xds/listeners_test.go index 1112222f3f..39ac2eac08 100644 --- a/agent/xds/listeners_test.go +++ b/agent/xds/listeners_test.go @@ -160,6 +160,22 @@ func TestListenersFromSnapshot(t *testing.T) { }, 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 { 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..9c8b0a5817 --- /dev/null +++ b/agent/xds/testdata/listeners/listener-balance-inbound-connections.latest.golden @@ -0,0 +1,122 @@ +{ + "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..1181ff019d --- /dev/null +++ b/agent/xds/testdata/listeners/listener-balance-outbound-connections-bind-port.latest.golden @@ -0,0 +1,122 @@ +{ + "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/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_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/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/proxies/envoy.mdx b/website/content/docs/connect/proxies/envoy.mdx index 4c6acef39a..86856a6f95 100644 --- a/website/content/docs/connect/proxies/envoy.mdx +++ b/website/content/docs/connect/proxies/envoy.mdx @@ -254,6 +254,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 @@ -313,6 +321,14 @@ definition](/docs/connect/registration/service-registration) or - `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 From 09d2ae9e0448c8e925f72e3da709f5e39ae6ef12 Mon Sep 17 00:00:00 2001 From: trujillo-adam Date: Mon, 26 Sep 2022 11:21:08 -0700 Subject: [PATCH 003/172] refresh of the Consul architecture overview page --- website/content/docs/architecture/index.mdx | 153 ++++++++++-------- 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 + 8 files changed, 96 insertions(+), 63 deletions(-) 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 diff --git a/website/content/docs/architecture/index.mdx b/website/content/docs/architecture/index.mdx index d8ad4839af..c579d8edab 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 Architecture -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. +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. --> Before describing the architecture, we recommend reading the -[glossary](/docs/install/glossary) of terms to help -clarify what is being discussed. +> Refer to the [Reference Architecture tutorial](https://learn.hashicorp.com/tutorials/consul/reference-architecture) for hands-on guidance about deploying Consul in production. -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. +## Introduction -## 10,000 foot view +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. -From a 10,000 foot altitude the architecture of Consul looks like this: +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. -[![Consul Architecture](/img/consul-arch.png)](/img/consul-arch.png) +![Diagram of the Consul control plane](/img/consul-arch/consul-arch-overview-control-plane.svg) -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. +## Datacenters -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. +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. -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. +### Clusters -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. +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. -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. +## Agents -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. +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. -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). +### Server agents -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. +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. -## Getting in depth +#### Consensus protocol -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. +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. -For other details, either consult the code, ask in IRC, or reach out to the mailing list. +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 refers to designating 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) + + + + +### Peering clusters (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/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 From 1c1b0994b843b6946872fc91dd78e34db52d748d Mon Sep 17 00:00:00 2001 From: Nick Ethier Date: Mon, 26 Sep 2022 14:58:15 -0400 Subject: [PATCH 004/172] add HCP integration component (#14723) * add HCP integration * lint: use non-deprecated logging interface --- .changelog/14723.txt | 3 + agent/agent.go | 43 ++- agent/agent_test.go | 65 ++++ agent/config/builder.go | 16 + 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 + agent/consul/client_serf.go | 4 +- agent/consul/leader_test.go | 16 +- agent/consul/options.go | 7 +- agent/consul/server.go | 73 +++++ agent/consul/server_serf.go | 4 +- agent/consul/server_test.go | 29 +- 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/proxycfg/ingress_gateway.go | 2 +- agent/retry_join.go | 2 + agent/retry_join_test.go | 2 +- agent/setup.go | 7 + agent/testagent.go | 7 + command/agent/agent.go | 87 +++-- command/agent/startup_logger.go | 64 ++++ go.mod | 47 ++- go.sum | 292 +++++++++++++++-- lib/retry/retry.go | 7 + 40 files changed, 2633 insertions(+), 116 deletions(-) create mode 100644 .changelog/14723.txt 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 command/agent/startup_logger.go 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/agent/agent.go b/agent/agent.go index d30ef3281b..e5101ab886 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,8 @@ import ( "github.com/hashicorp/consul/agent/consul/servercert" "github.com/hashicorp/consul/agent/dns" external "github.com/hashicorp/consul/agent/grpc-external" + "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 +386,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 +436,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 +778,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 } @@ -954,6 +974,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 +1037,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 +1058,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 +1618,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() diff --git a/agent/agent_test.go b/agent/agent_test.go index 65593710eb..f54275eb73 100644 --- a/agent/agent_test.go +++ b/agent/agent_test.go @@ -28,10 +28,14 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/tcpproxy" + "github.com/hashicorp/consul/agent/hcp" + "github.com/hashicorp/consul/agent/hcp/scada" "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" @@ -6049,6 +6053,67 @@ 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) { + t.Parallel() + + 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").Once() + 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/config/builder.go b/agent/config/builder.go index 25a313fc6c..1650df0ca8 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, @@ -2446,6 +2448,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/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/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/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/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..287dca871b 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" @@ -2012,3 +2015,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/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/proxycfg/ingress_gateway.go b/agent/proxycfg/ingress_gateway.go index 81a4928369..b21bc8738b 100644 --- a/agent/proxycfg/ingress_gateway.go +++ b/agent/proxycfg/ingress_gateway.go @@ -201,7 +201,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/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/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/testagent.go b/agent/testagent.go index ea5afff81d..2fb4438ddb 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 @@ -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) 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/go.mod b/go.mod index 70d58e70dd..5459d09f33 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,6 +49,8 @@ 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/raft v1.3.9 @@ -55,7 +59,7 @@ require ( github.com/hashicorp/serf v0.10.0 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..eef538621b 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,6 +501,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/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= @@ -365,6 +515,8 @@ github.com/hashicorp/memberlist v0.3.0/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOn 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/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= @@ -388,8 +540,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 +559,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 +573,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 +589,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 +600,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 +632,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 +650,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 +665,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 +688,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 +739,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 +755,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 +775,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 +786,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 +796,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 +812,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 +854,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 +904,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 +912,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 +920,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 +937,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 +953,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 +982,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 +1018,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 +1027,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 +1042,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 +1057,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 +1102,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 +1127,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 +1164,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 +1182,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 +1205,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 +1224,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..8f52768d8a 100644 --- a/lib/retry/retry.go +++ b/lib/retry/retry.go @@ -108,3 +108,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() +} From 0edf2f69d00f594c923623b6d644021eb378834b Mon Sep 17 00:00:00 2001 From: David Fleming Date: Tue, 27 Sep 2022 02:22:49 -0400 Subject: [PATCH 005/172] Fix Link: Consul Enterprise Admin Partitions - Usage - CLI (#14755) Admin partition CLI documentation was pointing at /commands/admin-partition. Updated to point at /commands/partition. https://www.consul.io/commands/admin-partition returns not found. --- website/content/docs/enterprise/admin-partitions.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/content/docs/enterprise/admin-partitions.mdx b/website/content/docs/enterprise/admin-partitions.mdx index 690ec7ec05..90dd2bfea6 100644 --- a/website/content/docs/enterprise/admin-partitions.mdx +++ b/website/content/docs/enterprise/admin-partitions.mdx @@ -116,7 +116,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 From 126f77f40df3f788aa38747f79cd843a68152f65 Mon Sep 17 00:00:00 2001 From: David Yu Date: Tue, 27 Sep 2022 00:35:59 -0700 Subject: [PATCH 006/172] docs: update to Vault secrets backend for partition init service account and Helm values for injector (#14745) * docs: update to Vault secrets backend --- .../vault/data-integration/partition-token.mdx | 11 ++++++----- .../vault/systems-integration.mdx | 4 +++- 2 files changed, 9 insertions(+), 6 deletions(-) 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..88b8d9785b 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 ``` ## 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/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 ``` From 6570d5f004f427e3bda078fd8cb29c509b678482 Mon Sep 17 00:00:00 2001 From: Eric Haberkorn Date: Tue, 27 Sep 2022 09:49:28 -0400 Subject: [PATCH 007/172] Enable outbound peered requests to go through local mesh gateway (#14763) --- agent/proxycfg/connect_proxy.go | 114 +++++++++----- agent/proxycfg/manager_test.go | 2 + agent/proxycfg/snapshot.go | 10 ++ agent/proxycfg/state.go | 9 +- agent/proxycfg/testing_peering.go | 128 +++++++++++++++ agent/proxycfg/upstreams.go | 20 +-- agent/xds/clusters.go | 6 +- agent/xds/endpoints.go | 22 ++- agent/xds/listeners.go | 6 +- agent/xds/resources_test.go | 4 + ...ateway-with-peered-upstreams.latest.golden | 146 ++++++++++++++++++ ...ateway-with-peered-upstreams.latest.golden | 51 ++++++ ...ateway-with-peered-upstreams.latest.golden | 119 ++++++++++++++ ...ateway-with-peered-upstreams.latest.golden | 5 + 14 files changed, 588 insertions(+), 54 deletions(-) create mode 100644 agent/xds/testdata/clusters/local-mesh-gateway-with-peered-upstreams.latest.golden create mode 100644 agent/xds/testdata/endpoints/local-mesh-gateway-with-peered-upstreams.latest.golden create mode 100644 agent/xds/testdata/listeners/local-mesh-gateway-with-peered-upstreams.latest.golden create mode 100644 agent/xds/testdata/routes/local-mesh-gateway-with-peered-upstreams.latest.golden diff --git a/agent/proxycfg/connect_proxy.go b/agent/proxycfg/connect_proxy.go index 2ff1f9ca9f..dfa6d0b032 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]() @@ -184,7 +185,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 +201,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 +231,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) 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/snapshot.go b/agent/proxycfg/snapshot.go index 23cb8a9556..6a8f7e584e 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 diff --git a/agent/proxycfg/state.go b/agent/proxycfg/state.go index 34d3364356..e069e80ee2 100644 --- a/agent/proxycfg/state.go +++ b/agent/proxycfg/state.go @@ -476,6 +476,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 +490,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/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/upstreams.go b/agent/proxycfg/upstreams.go index 94c872e39f..7f52924dd2 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) } diff --git a/agent/xds/clusters.go b/agent/xds/clusters.go index a425f829ee..02e6ac2b47 100644 --- a/agent/xds/clusters.go +++ b/agent/xds/clusters.go @@ -794,7 +794,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 diff --git a/agent/xds/endpoints.go b/agent/xds/endpoints.go index b5588ce649..54d3248e54 100644 --- a/agent/xds/endpoints.go +++ b/agent/xds/endpoints.go @@ -9,7 +9,7 @@ import ( 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 } @@ -416,6 +415,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/listeners.go b/agent/xds/listeners.go index 9f9136bf3f..a554895d28 100644 --- a/agent/xds/listeners.go +++ b/agent/xds/listeners.go @@ -275,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, @@ -2016,6 +2016,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/resources_test.go b/agent/xds/resources_test.go index 53274d7193..7423f198db 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()...) 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/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/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/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 From 4cbf63bb5dfe16ee4b766e01718258999f24b668 Mon Sep 17 00:00:00 2001 From: boruszak Date: Tue, 27 Sep 2022 10:25:25 -0500 Subject: [PATCH 008/172] consul-dataplane command + main Dataplane pages --- website/content/commands/consul-dataplane.mdx | 126 ++++++++++++++++++ website/content/docs/k8s/dataplane.mdx | 59 ++++++++ 2 files changed, 185 insertions(+) create mode 100644 website/content/commands/consul-dataplane.mdx create mode 100644 website/content/docs/k8s/dataplane.mdx diff --git a/website/content/commands/consul-dataplane.mdx b/website/content/commands/consul-dataplane.mdx new file mode 100644 index 0000000000..e91740e031 --- /dev/null +++ b/website/content/commands/consul-dataplane.mdx @@ -0,0 +1,126 @@ +--- +layout: commands +page_title: 'Commands: Consul Dataplane' +--- + +# Consul Dataplane + +Command: `consul-dataplane` + +The `consul-dataplane` command interacts with the binary for [simplified service mesh with Consul Dataplane](/consul/docs/k8s/dataplane). Use this command to install Consul Dataplane, configure its Envoy proxies, and secure Dataplane deployments. + +## Usage + +Usage: `consul-dataplane [options]` + +### 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/k8s/dataplane.mdx b/website/content/docs/k8s/dataplane.mdx new file mode 100644 index 0000000000..1816419a65 --- /dev/null +++ b/website/content/docs/k8s/dataplane.mdx @@ -0,0 +1,59 @@ +--- +layout: docs +page_title: Simplified Service Mesh with Consul Dataplane +description: >- + +--- + +# 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. + +## 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 doesn’t need to ensure 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, as well as runtimes that mix Kubernetes and VM deployments. + +**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](/consul/commands/consul-dataplane). +- For Helm chart information, refer to [the Helm Chart reference](/consul/docs/k8s/helm). +- For Envoy, Consul, and Consul Dataplane version compatibility, refer to the [Envoy compatibility matrix](/consul/docs/k8s/compatibility). + +## 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. +- Secrets Discovery Service (SDS) is not supported. +- If using EKS Fargate, we recommend that you use external Consul servers. +- Consul Dataplane is not supported on Windows. From 30999a0c4733da29ed51f75786386915c27d9150 Mon Sep 17 00:00:00 2001 From: boruszak Date: Tue, 27 Sep 2022 11:17:17 -0500 Subject: [PATCH 009/172] k8s installation pages updates --- website/content/docs/k8s/installation/install-cli.mdx | 2 ++ website/content/docs/k8s/installation/install.mdx | 2 ++ 2 files changed, 4 insertions(+) diff --git a/website/content/docs/k8s/installation/install-cli.mdx b/website/content/docs/k8s/installation/install-cli.mdx index b7261a9569..19308cb7fd 100644 --- a/website/content/docs/k8s/installation/install-cli.mdx +++ b/website/content/docs/k8s/installation/install-cli.mdx @@ -11,6 +11,8 @@ 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. +To use Consul Dataplane with Kubernetes, you must separately install the `consul-dataplane` binary. For more information, refer to [Simplified Service Mesh with Consul Dataplane](/consul/docs/k8s/dataplane). + ## 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..b13d2dca8b 100644 --- a/website/content/docs/k8s/installation/install.mdx +++ b/website/content/docs/k8s/installation/install.mdx @@ -9,6 +9,8 @@ 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). +To use Consul Dataplane with Kubernetes, you must separately install the `consul-dataplane` binary. For more information, refer to [Simplified Service Mesh with Consul Dataplane](/consul/docs/k8s/dataplane). + ## 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. From 99e0d23226abc60542d645733c39f77b069b0a1d Mon Sep 17 00:00:00 2001 From: Jared Kirschner <85913323+jkirschner-hashicorp@users.noreply.github.com> Date: Tue, 27 Sep 2022 12:24:56 -0400 Subject: [PATCH 010/172] docs: clarify license behavior on restart --- website/content/docs/enterprise/license/faq.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/content/docs/enterprise/license/faq.mdx b/website/content/docs/enterprise/license/faq.mdx index 9650b24e03..7150e2fea1 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. From 2c7d2f9f8a153f7f532dd2e6ed9d9f619c3c8c51 Mon Sep 17 00:00:00 2001 From: boruszak Date: Tue, 27 Sep 2022 11:27:11 -0500 Subject: [PATCH 011/172] Minor fixes --- website/content/docs/k8s/dataplane.mdx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/website/content/docs/k8s/dataplane.mdx b/website/content/docs/k8s/dataplane.mdx index 1816419a65..b3994341b7 100644 --- a/website/content/docs/k8s/dataplane.mdx +++ b/website/content/docs/k8s/dataplane.mdx @@ -2,7 +2,7 @@ layout: docs page_title: Simplified Service Mesh with Consul Dataplane description: >- - + Consul Dataplane removes the need to run client or 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 @@ -31,8 +31,8 @@ Consul Dataplane manages Envoy proxies and leaves responsibility for other funct 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](/consul/commands/consul-dataplane). -- For Helm chart information, refer to [the Helm Chart reference](/consul/docs/k8s/helm). +- For `consul-dataplane` commands and usage examples, including required flags for startup, refer to the [`consul-dataplane` CLI reference](/consul/commands/consul-dataplane). +- For Helm chart information, refer to the [Helm Chart reference](/consul/docs/k8s/helm). - For Envoy, Consul, and Consul Dataplane version compatibility, refer to the [Envoy compatibility matrix](/consul/docs/k8s/compatibility). ## Beta release features From 575cf10062704899e1558abc885755bc9367c32e Mon Sep 17 00:00:00 2001 From: boruszak Date: Tue, 27 Sep 2022 11:30:08 -0500 Subject: [PATCH 012/172] CLI Page nav listing --- website/data/commands-nav-data.json | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/website/data/commands-nav-data.json b/website/data/commands-nav-data.json index 3a3bb0609b..c74c00c1ff 100644 --- a/website/data/commands-nav-data.json +++ b/website/data/commands-nav-data.json @@ -252,6 +252,10 @@ } ] }, + { + "title": "consul-dataplane", + "path": "consul-dataplane" + }, { "title": "debug", "path": "debug" From a93745c1ffb458981eb4883929f2dde6ee91e1e0 Mon Sep 17 00:00:00 2001 From: boruszak Date: Tue, 27 Sep 2022 11:32:39 -0500 Subject: [PATCH 013/172] Main page nav add --- website/data/docs-nav-data.json | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/website/data/docs-nav-data.json b/website/data/docs-nav-data.json index 6b94e594bd..c79a84a5d4 100644 --- a/website/data/docs-nav-data.json +++ b/website/data/docs-nav-data.json @@ -949,6 +949,10 @@ "title": "Annotations and Labels", "path": "k8s/annotations-and-labels" }, + { + "title": "Consul Dataplane", + "path": "k8s/dataplane" + }, { "title": "Consul DNS", "path": "k8s/dns" From 84f8b87967e084406d36946472f8b00ba788983a Mon Sep 17 00:00:00 2001 From: Luke Kysow <1034429+lkysow@users.noreply.github.com> Date: Tue, 27 Sep 2022 09:51:12 -0700 Subject: [PATCH 014/172] Update k8s.mdx (#14765) --- website/content/docs/connect/cluster-peering/k8s.mdx | 1 + 1 file changed, 1 insertion(+) diff --git a/website/content/docs/connect/cluster-peering/k8s.mdx b/website/content/docs/connect/cluster-peering/k8s.mdx index c12c7774d2..75a90bb1de 100644 --- a/website/content/docs/connect/cluster-peering/k8s.mdx +++ b/website/content/docs/connect/cluster-peering/k8s.mdx @@ -51,6 +51,7 @@ You must implement the following requirements to create and use cluster peering ```yaml global: + name: consul image: "hashicorp/consul:1.13.1" peering: enabled: true From 4638753168dafcfa244a23cbc121e2ed696b3864 Mon Sep 17 00:00:00 2001 From: Luke Kysow <1034429+lkysow@users.noreply.github.com> Date: Tue, 27 Sep 2022 13:04:59 -0700 Subject: [PATCH 015/172] Make defaulting behaviour of connect.enabled clear (#14768) --- website/content/docs/agent/config/config-files.mdx | 1 + 1 file changed, 1 insertion(+) 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. From b0cbecae7d7f2796419ea52137f02c1a7a178715 Mon Sep 17 00:00:00 2001 From: boruszak Date: Tue, 27 Sep 2022 15:16:39 -0500 Subject: [PATCH 016/172] Bootstrap Consul on Windows VMs instructions --- .../content/docs/connect/proxies/envoy.mdx | 29 ++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/website/content/docs/connect/proxies/envoy.mdx b/website/content/docs/connect/proxies/envoy.mdx index 86856a6f95..0cb53fa7fe 100644 --- a/website/content/docs/connect/proxies/envoy.mdx +++ b/website/content/docs/connect/proxies/envoy.mdx @@ -112,7 +112,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 +174,33 @@ 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 Consul on Windows VMs + +If you are running Consul on a Windows VM, 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. + +First, add the `-bootstrap` option to the command and save the output to a file: + +```shell-session +$ consul connect envoy -bootstrap > bootstrap.json +``` + +Then, open `bootstrap.json` and add your ACL token and log path to the file. + +To complete the bootstrap process, start Envoy and include the path to `bootstrap.json`: + +```shell-session +envoy -c +``` + +~> **Tip**: The `bootstrap.json` file contains your ACL token. Because the file is no longer needed after bootstrapping is complete, delete it to protect your network. + ## Dynamic Configuration Consul automatically generates Envoy's dynamic configuration based on its From b0fc58474ab3d49bc7b0d5546ecbf4f2e3b943e7 Mon Sep 17 00:00:00 2001 From: boruszak Date: Tue, 27 Sep 2022 15:42:05 -0500 Subject: [PATCH 017/172] Fixes --- website/content/docs/connect/proxies/envoy.mdx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/website/content/docs/connect/proxies/envoy.mdx b/website/content/docs/connect/proxies/envoy.mdx index 0cb53fa7fe..65912ed171 100644 --- a/website/content/docs/connect/proxies/envoy.mdx +++ b/website/content/docs/connect/proxies/envoy.mdx @@ -176,7 +176,7 @@ The [Advanced Configuration](#advanced-configuration) section describes addition ### Bootstrap Consul on Windows VMs -If you are running Consul on a Windows VM, the `consul connect envoy` command returns the following output: +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. @@ -196,10 +196,10 @@ Then, open `bootstrap.json` and add your ACL token and log path to the file. To complete the bootstrap process, start Envoy and include the path to `bootstrap.json`: ```shell-session -envoy -c +envoy -c bootstrap.json ``` -~> **Tip**: The `bootstrap.json` file contains your ACL token. Because the file is no longer needed after bootstrapping is complete, delete it to protect your network. +~> **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 From 347e2295fb7e3722b15e8dca3ed701d38bd899d4 Mon Sep 17 00:00:00 2001 From: boruszak Date: Tue, 27 Sep 2022 16:15:26 -0500 Subject: [PATCH 018/172] EnterpriseAlert inline fix --- website/content/commands/consul-dataplane.mdx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/website/content/commands/consul-dataplane.mdx b/website/content/commands/consul-dataplane.mdx index e91740e031..a2b3359070 100644 --- a/website/content/commands/consul-dataplane.mdx +++ b/website/content/commands/consul-dataplane.mdx @@ -38,14 +38,14 @@ The following options are required when starting `consul-dataplane` with the CLI - `-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. +- `-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-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. +- `-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. From d1826c026fdc0cd2a5cf0b2ba7111e734d4b6df0 Mon Sep 17 00:00:00 2001 From: David Yu Date: Tue, 27 Sep 2022 16:18:43 -0700 Subject: [PATCH 019/172] README: Consul Readme improvements (#14773) * README: Consul Readme improvements Co-authored-by: Tu Nguyen --- README.md | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) 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 From 6615c60e57dfa7c7093fe983fc4f8adde168b3d4 Mon Sep 17 00:00:00 2001 From: trujillo-adam Date: Tue, 27 Sep 2022 20:00:51 -0700 Subject: [PATCH 020/172] added docs for invoking services from lambda functions --- website/content/docs/lambda/index.mdx | 48 ++- website/content/docs/lambda/invocation.mdx | 2 +- .../docs/lambda/invoke-from-lambda.mdx | 301 ++++++++++++++++++ website/content/docs/lambda/registration.mdx | 290 ----------------- .../docs/lambda/registration/automate.mdx | 190 +++++++++++ .../docs/lambda/registration/index.mdx | 78 +++++ .../docs/lambda/registration/manual.mdx | 84 +++++ website/data/docs-nav-data.json | 21 +- .../img/invoke-service-from-lambda-flow.svg | 1 + 9 files changed, 705 insertions(+), 310 deletions(-) create mode 100644 website/content/docs/lambda/invoke-from-lambda.mdx delete mode 100644 website/content/docs/lambda/registration.mdx create mode 100644 website/content/docs/lambda/registration/automate.mdx create mode 100644 website/content/docs/lambda/registration/index.mdx create mode 100644 website/content/docs/lambda/registration/manual.mdx create mode 100644 website/public/img/invoke-service-from-lambda-flow.svg diff --git a/website/content/docs/lambda/index.mdx b/website/content/docs/lambda/index.mdx index 90b716ae41..51f46d6ee6 100644 --- a/website/content/docs/lambda/index.mdx +++ b/website/content/docs/lambda/index.mdx @@ -6,28 +6,42 @@ description: >- section documents the process of integrating AWS Lambda with Consul services. --- -# AWS Lambda +# 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 [Register Lambda Functions Overview](TODO) 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 connect proxies. -### Invoking Lambda Functions from Consul Service Mesh +Refer to Invoke Lambda Functions from Services 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 service from Lambda function + +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 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](TODO) for additional information about registering Lambda functions into Consul. + +## Cross-datacenter communication + +You can use the following Consul features to send cross-datacenter requests between Lambda functions and mesh services. + +### Mesh gateway WAN federation + +Mesh gateways enable you to route traffic to services within and across Consul datacenters. WAN federation refers to designating a _primary datacenter_ that contains authoritative information about all datacenters, including service mesh configurations and access control list (ACL) resources. Refer to [Mesh Gateways for WAN Federation](/docs/connect/gateways/mesh-gateway/wan-federation-via-mesh-gateways) for additional information. + +Note that mesh gateways do not implement L7 traffic management by default. As a result, requests from Lambda functions ignore service routes and splitters. + +#### Admin partitions + +If admin partitions are enabled and the datacenters are federated across the WAN using mesh gateways, then you can only route requests from Lambda functions by applying an [`exported-services`](/docs/connect/config-entries/exported-services) configuration entry to export their service instances. This is required even if the upstream for the Lambda function is in the same admin partition. Otherwise, Consul does not populate the mesh gateways with the routing information. + +You can also use the [admin partitions](/docs/enterprise/partitions) feature included with Consul Enterprise to define separate administrative areas within a datacenter. If admin partitions are not enabled and the datacenters are federated across the WAN using mesh gateways, then you can route all services through the mesh gateways by default. You do not need to use the [`exported-services`](/docs/connect/config-entries/exported-services) configuration entry to export service instances. \ No newline at end of file diff --git a/website/content/docs/lambda/invocation.mdx b/website/content/docs/lambda/invocation.mdx index 4789c0adac..7bc91af239 100644 --- a/website/content/docs/lambda/invocation.mdx +++ b/website/content/docs/lambda/invocation.mdx @@ -5,7 +5,7 @@ description: >- This topic describes how to invoke AWS Lambda functions from the Consul service mesh. --- -# 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..6b00b347ca --- /dev/null +++ b/website/content/docs/lambda/invoke-from-lambda.mdx @@ -0,0 +1,301 @@ +--- +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. + +## Introduction + +The following steps describe the process: + +1. Deploy the services you want to allow Lambda to invoke. +1. (Optional) Enable L7 traffic management in the local datacenter. +1. Deploy the mesh gateway +1. Deploy the Lambda registrator +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 Lambdas 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. It’s not required, but you can also enable L7 traffic management in the local datacenter prior to implementing 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 +``` + +### Enable L7 traffic management (optional) + +Mesh gateways do not implement L7 traffic management features, but you can enable L7 in the local data center so that your service can use service resolvers, splitters, and routers. + +1. Define an `exported-services` configuration entry. Refer to [Exported Services](/docs/connect/config-entries/exported-services) for additional information. The following example exports `static-server` service instances to a peered cluster specified in the `PeerName` field. + + + + ```hcl + Kind = "exported-services" + Name = "default" + Services = [ + { + Name = "static-server" + Consumers = [ + { + PeerName = "" + } + ] + } + ] + ``` + + + +1. Apply the configuration using the Consul CLI or by using a custom resource definition (CRD) if Consul is running on Kubernetes. The following example shows the command line usage: + + ```shell-session + $ consul config write static-server-configuration-entry.hcl + ``` +### 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. + +When the Lambda function is invoked, the extension 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: + + ```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. You can create the layer manually using the AWS CLI or AWS Console, but we recommend using Terraform: + + + + ``` + 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 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 function configuration](#lambda-function-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. Issue the `terraform apply` command and Consul automatically configures a service for the Lambda function. + + +### Lambda function configuration + +Define the following environment variables to configure each Lambda function. The configurations 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. We recommend configuring an acceptable value to preview stale certificates. | `“5m”` | +| `CONSUL_SERVICE_UPSTREAMS` | Specifies the 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 + +You can create an _intention_ in Consul prior to invoking the Lambda function. Intentions define access control for services in the mesh. 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-registrator-2345 /dev/stdout | cat +``` diff --git a/website/content/docs/lambda/registration.mdx b/website/content/docs/lambda/registration.mdx deleted file mode 100644 index 9fe9ba0da5..0000000000 --- a/website/content/docs/lambda/registration.mdx +++ /dev/null @@ -1,290 +0,0 @@ ---- -layout: docs -page_title: Register Lambda Functions -description: >- - This topic describes how to register AWS Lambda functions with Consul service mesh. ---- - -# Register Lambda Functions - -You can either manually register AWS Lambda functions with Consul or use the [Lambda registrator](https://github.com/hashicorp/terraform-aws-consul-lambda-registrator) -to automatically synchronize Lambda state into Consul. - -To manually register AWS Lambda functions into Consul, you must register a service into Consul and then write a [service defaults configuration entry](/docs/connect/config-entries/service-defaults) for the Lambda. - -The registrator automatically registers, reconfigures, and deregisters Lambdas based on the -Lambda function's tags (refer to the [AWS tag configuration documentation](https://docs.aws.amazon.com/lambda/latest/dg/configuration-tags.html) for details about tags). - -We recommend using the Lambda registrator when possible so that you can keep the configuration entry up to date. - -## Requirements - -- Consul 1.12.1 and later - -## Prerequisites - -Complete the following prerequisites prior to registering your Lambda functions. You only need to perform these steps once. - -### Enable the Serverless Plugin - -Add the following configuration to all Consul clients: - -`connect { enable_serverless_plugin = true, connect = true }` - -Refer to the [`enable_serverless_plugin`](/docs/agent/config/config-files#connect_enable_serverless_plugin) configuration documentation for additional information. - -### Configure IAM Permissions for Envoy - -The Envoy proxy that invokes Lambda must have the `lambda:InvokeFunction` AWS IAM -permissions. In the following example, the IAM policy -enables an IAM user or role to invoke the `example` Lambda function: - -```json -{ - "Version": "2012-10-17", - "Statement": [ - { - "Sid": "Invoke", - "Effect": "Allow", - "Action": [ - "lambda:InvokeFunction" - ], - "Resource": "arn:aws:lambda:us-east-1:123456789012:function:example" - } - ] -} -``` - -Define AWS IAM credentials in environment variables, EC2 metadata or -ECS metadata. On [AWS EKS](https://aws.amazon.com/eks/), associate an IAM role with the proxy's `ServiceAccount`. Refer to the [AWS IAM roles for service accounts](https://docs.aws.amazon.com/eks/latest/userguide/iam-roles-for-service-accounts.html) documentation for instructions. - -### Optional: Set up a Terminating Gateway - -If you intend to invoke Lambda services through a terminating gateway, the gateway must be registered and running in the Consul datacenter. Refer to the following documentation and tutorials for instructions on how to set up a terminating gateway: - -- [Terminating gateways documentation](/docs/connect/gateways#terminating-gateways) -- [Terminating gateways on Kubernetes documentation](/docs/k8s/connect/terminating-gateways) -- [Connect External Services to Consul With Terminating Gateways tutorial](https://learn.hashicorp.com/tutorials/consul/teminating-gateways-connect-external-services) - -To register a Lambda service with a terminating gateway, add the service to the -`Services` field of the terminating gateway's `terminating-gateway` -configuration entry. - -### Optional: Run a Mesh Gateway - -You can set up a mesh gateway so that you can invoke Lambda services across datacenters and admin partitions. The mesh gateway must be running and registered in the relevant Consul datacenters and partitions. Refer to the following documentation and tutorials for instructions on how to set up mesh gateways: - -- [Mesh gateway documentation](/docs/connect/gateways#mesh-gateways) -- [Connect Services Across Datacenters with Mesh Gateways tutorial](https://learn.hashicorp.com/tutorials/consul/service-mesh-gateways) -- [Secure Service Mesh Communication Across Kubernetes Clusters tutorial](https://learn.hashicorp.com/tutorials/consul/kubernetes-mesh-gateways?utm_source=docs?in=consul/kubernetes) - -When using admin partitions, you must add Lambda services to the `Services` -field of [the `exported-services` configuration -entry](/docs/connect/config-entries/exported-services). - -## Automatic Lambda Function Registration - -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 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](https://docs.aws.amazon.com/eventbridge/latest/userguide/eb-create-rule-schedule.html). - -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. - -### Deploy 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-registrator/aws//modules/lambda-registrator" - name = "consul-lambda-registrator" - consul_http_addr = "https://consul.example.com:8501" - ca_cert_path = aws_ssm_parameter.ca-cert.name - http_token_path = aws_ssm_parameter.acl-token.name - } - ``` - -1. Deploy Lambda registrator with `terraform apply`. - -#### 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 (refer to the [Parameter Store documentation](https://docs.aws.amazon.com/systems-manager/latest/userguide/systems-manager-parameter-store.html) for additional information) to verify the authenticity of the Consul API. 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. Issue `consul acl policy create` command 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 = - } - ``` - -#### Lambda Registrator Configuration Options - -| Name | Description | -| - | - | -| `name` | Specifies the name 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. | -| `schedule_frequency_in_minutes` | Specifies the interval in minutes that EventBridge uses to trigger a full synchronization. Default is `5`. | -| `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_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 see [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 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 see [Optional: Store the ACL Token in Parameter Store](#optional-store-the-acl-token-in-parameter-store)| -| `node_name` | The Consul node name that Lambdas will be registered to. This defaults to `lambdas`. | -| `enterprise` | Determines if the Consul server at `consul_http_addr` is running open source or enterprise. | -| `partitions` | The partitions that Lambda registrator manages. | - -### 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/alpha/lambda/payload-passthrough" = "true" - "serverless.consul.hashicorp.com/alpha/lambda/invocation-mode" = "ASYNCHRONOUS" - } -} -``` - -#### Supported Tags - -The following tags are supported. In all cases, the `` should be -`serverless.consul.hashicorp.com/v1alpha1/lambda`. For example, -`serverless.consul.hashicorp.com/v1alpha1/lambda/enabled`. - -| Tag | Description | -| - | - | -| `/enabled` | Determines if Lambda registrator will sync the Lambda into 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. | -| `/namespace` | Specifies the Consul namespace the service will be registered in. Default is `default` if `enterprise` is enabled. | -| `/partition` | Specifies the Consul partition the service will be registered in. Defaults is `default` if `enterprise` is enabled. | -| `/aliases` | Specifies a `+`-separated string of Lambda aliases that will be registered into Consul. For example, if set to `dev+staging+prod`, the `dev`, `staging`, and `prod` aliases of the Lambda function will be registered into Consul. | - -## Manual Configuration - -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 (see [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. In all cases, the `` should be -`serverless.consul.hashicorp.com/v1alpha1/lambda`. For example, -`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/lambda/registration/automate.mdx b/website/content/docs/lambda/registration/automate.mdx new file mode 100644 index 0000000000..0c8d82d183 --- /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 the Consul 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](TODO) 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 module 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 (refer to the [Parameter Store documentation](https://docs.aws.amazon.com/systems-manager/latest/userguide/systems-manager-parameter-store.html) for additional information) to verify the authenticity of the Consul API. 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. Issue `consul acl policy create` command 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: + +`${var.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 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. | +| `schedule_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 see [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 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 see [Optional: Store the ACL Token in Parameter Store](#optional-store-the-acl-token-in-parameter-store)| +| `node_name` | The Consul node name that Lambdas will be registered to. This defaults to `lambdas`. | +| `enterprise` | Determines if the Consul server at `consul_http_addr` is running open source or 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/alpha/lambda/payload-passthrough" = "true" + "serverless.consul.hashicorp.com/alpha/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 will be registered in. Default is `default` if `enterprise` is enabled. | +| `/partition` | Specifies the Consul partition the service will be registered in. Defaults is `default` if `enterprise` is enabled. | +| `/aliases` | Specifies a `+`-separated string of Lambda aliases that will be registered into Consul. For example, if set to `dev+staging+prod`, the `dev`, `staging`, and `prod` aliases of the Lambda function will be registered into Consul. | diff --git a/website/content/docs/lambda/registration/index.mdx b/website/content/docs/lambda/registration/index.mdx new file mode 100644 index 0000000000..eaa797728f --- /dev/null +++ b/website/content/docs/lambda/registration/index.mdx @@ -0,0 +1,78 @@ +--- +layout: docs +page_title: Lambda Function Registration Requirements +description: >- + This topic provides an overview of how to register AWS Lambda functions with Consul service mesh and describes the requirements and prerequisites for registering Lambda functions with Consul. +--- +# Lambda Function Registration Requirements + +Verify that your network meets the requirements and that you have completed the prerequisites before registering Lambda functions. + +## Introduction + +You can either manually register AWS Lambda functions with Consul or use the Lambda registrator to automatically synchronize Lambda state into Consul. We recommend using the Lambda registrator when possible so that you can keep the configuration entry up to date. The registrator automatically registers, reconfigures, and deregisters Lambdas based on the Lambda function's tags. + +## Requirements + +Consul 1.12.1 and later + +## Prerequisites + +Complete the following prerequisites prior to registering your Lambda functions. You only need to perform these steps once. + +### Enable the Serverless Plugin + +Add the following configuration to all Consul clients: + +`connect { enable_serverless_plugin = true, connect = true }` + +Refer to the [`enable_serverless_plugin`](/docs/agent/config/config-files#connect_enable_serverless_plugin) configuration documentation for additional information. + +### Configure IAM Permissions for Envoy + +The Envoy proxy that invokes Lambda must have the `lambda:InvokeFunction` AWS IAM +permissions. In the following example, the IAM policy +enables an IAM user or role to invoke the `example` Lambda function: + +```json +{ + "Version": "2012-10-17", + "Statement": [ + { + "Sid": "Invoke", + "Effect": "Allow", + "Action": [ + "lambda:InvokeFunction" + ], + "Resource": "arn:aws:lambda:us-east-1:123456789012:function:example" + } + ] +} +``` + +Define AWS IAM credentials in environment variables, EC2 metadata or +ECS metadata. On [AWS EKS](https://aws.amazon.com/eks/), associate an IAM role with the proxy's `ServiceAccount`. Refer to the [AWS IAM roles for service accounts](https://docs.aws.amazon.com/eks/latest/userguide/iam-roles-for-service-accounts.html) documentation for instructions. + +### Optional: Set up a Terminating Gateway + +If you intend to invoke Lambda services through a terminating gateway, the gateway must be registered and running in the Consul datacenter. Refer to the following documentation and tutorials for instructions on how to set up a terminating gateway: + +- [Terminating gateways documentation](/docs/connect/gateways#terminating-gateways) +- [Terminating gateways on Kubernetes documentation](/docs/k8s/connect/terminating-gateways) +- [Connect External Services to Consul With Terminating Gateways tutorial](https://learn.hashicorp.com/tutorials/consul/teminating-gateways-connect-external-services) + +To register a Lambda service with a terminating gateway, add the service to the +`Services` field of the terminating gateway's `terminating-gateway` +configuration entry. + +### Optional: Run a Mesh Gateway + +You can set up a mesh gateway so that you can invoke Lambda services across datacenters and admin partitions. The mesh gateway must be running and registered in the relevant Consul datacenters and partitions. Refer to the following documentation and tutorials for instructions on how to set up mesh gateways: + +- [Mesh gateway documentation](/docs/connect/gateways#mesh-gateways) +- [Connect Services Across Datacenters with Mesh Gateways tutorial](https://learn.hashicorp.com/tutorials/consul/service-mesh-gateways) +- [Secure Service Mesh Communication Across Kubernetes Clusters tutorial](https://learn.hashicorp.com/tutorials/consul/kubernetes-mesh-gateways?utm_source=docs?in=consul/kubernetes) + +When using admin partitions, you must add Lambda services to the `Services` +field of [the `exported-services` configuration +entry](/docs/connect/config-entries/exported-services). \ No newline at end of file diff --git a/website/content/docs/lambda/registration/manual.mdx b/website/content/docs/lambda/registration/manual.mdx new file mode 100644 index 0000000000..01f475ecbc --- /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 (see [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/data/docs-nav-data.json b/website/data/docs-nav-data.json index 6b94e594bd..955f51cb5e 100644 --- a/website/data/docs-nav-data.json +++ b/website/data/docs-nav-data.json @@ -1095,11 +1095,28 @@ }, { "title": "Register Lambda Functions", - "path": "lambda/registration" + "routes":[ + { + "title": "Requirements", + "path": "lambda/registration" + }, + { + "title": "Automate Registration", + "path": "lambda/registration/automate" + }, + { + "title": "Manual Registration", + "path": "lambda/registration/manual" + } + ] }, { - "title": "Invoke Lambda Functions", + "title": "Invoke Lambda Functions from Services", "path": "lambda/invocation" + }, + { + "title": "Invoke Services from Lambda Functions", + "path": "lambda/invoke-from-lambda" } ] }, 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 From 67c421682254c87cd18663e58e0ff5be93f7d6e6 Mon Sep 17 00:00:00 2001 From: trujillo-adam Date: Tue, 27 Sep 2022 20:01:57 -0700 Subject: [PATCH 021/172] missed unsaved changes --- website/content/docs/lambda/registration/automate.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/content/docs/lambda/registration/automate.mdx b/website/content/docs/lambda/registration/automate.mdx index 0c8d82d183..16f04e3cf3 100644 --- a/website/content/docs/lambda/registration/automate.mdx +++ b/website/content/docs/lambda/registration/automate.mdx @@ -121,7 +121,7 @@ The registrator also requires the following IAM permissions to access the parame | Name | Description | | - | - | | `name` | Specifies the name 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. | -| `schedule_frequency_in_minutes` | Specifies the interval in minutes that EventBridge uses to trigger a full synchronization. Default is `10`. | +| `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. | From 38a873b114dcbf4a758608963c267638411e9f38 Mon Sep 17 00:00:00 2001 From: boruszak Date: Wed, 28 Sep 2022 11:03:30 -0500 Subject: [PATCH 022/172] CLI reference page relocation --- .../k8s/dataplane}/consul-dataplane.mdx | 12 ++++++------ .../docs/k8s/{dataplane.mdx => dataplane/index.mdx} | 2 +- website/data/commands-nav-data.json | 4 ---- website/data/docs-nav-data.json | 11 ++++++++++- 4 files changed, 17 insertions(+), 12 deletions(-) rename website/content/{commands => docs/k8s/dataplane}/consul-dataplane.mdx (92%) rename website/content/docs/k8s/{dataplane.mdx => dataplane/index.mdx} (98%) diff --git a/website/content/commands/consul-dataplane.mdx b/website/content/docs/k8s/dataplane/consul-dataplane.mdx similarity index 92% rename from website/content/commands/consul-dataplane.mdx rename to website/content/docs/k8s/dataplane/consul-dataplane.mdx index a2b3359070..9ae5d41dab 100644 --- a/website/content/commands/consul-dataplane.mdx +++ b/website/content/docs/k8s/dataplane/consul-dataplane.mdx @@ -1,13 +1,13 @@ --- -layout: commands -page_title: 'Commands: Consul Dataplane' +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 +# Consul Dataplane CLI Reference -Command: `consul-dataplane` - -The `consul-dataplane` command interacts with the binary for [simplified service mesh with Consul Dataplane](/consul/docs/k8s/dataplane). Use this command to install Consul Dataplane, configure its Envoy proxies, and secure Dataplane deployments. +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 diff --git a/website/content/docs/k8s/dataplane.mdx b/website/content/docs/k8s/dataplane/index.mdx similarity index 98% rename from website/content/docs/k8s/dataplane.mdx rename to website/content/docs/k8s/dataplane/index.mdx index b3994341b7..704e94f2e3 100644 --- a/website/content/docs/k8s/dataplane.mdx +++ b/website/content/docs/k8s/dataplane/index.mdx @@ -31,7 +31,7 @@ Consul Dataplane manages Envoy proxies and leaves responsibility for other funct 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](/consul/commands/consul-dataplane). +- For `consul-dataplane` commands and usage examples, including required flags for startup, refer to the [`consul-dataplane` CLI reference](/consul/docs/dataplane/consul-dataplane). - For Helm chart information, refer to the [Helm Chart reference](/consul/docs/k8s/helm). - For Envoy, Consul, and Consul Dataplane version compatibility, refer to the [Envoy compatibility matrix](/consul/docs/k8s/compatibility). diff --git a/website/data/commands-nav-data.json b/website/data/commands-nav-data.json index c74c00c1ff..3a3bb0609b 100644 --- a/website/data/commands-nav-data.json +++ b/website/data/commands-nav-data.json @@ -252,10 +252,6 @@ } ] }, - { - "title": "consul-dataplane", - "path": "consul-dataplane" - }, { "title": "debug", "path": "debug" diff --git a/website/data/docs-nav-data.json b/website/data/docs-nav-data.json index c79a84a5d4..0d8297806c 100644 --- a/website/data/docs-nav-data.json +++ b/website/data/docs-nav-data.json @@ -951,7 +951,16 @@ }, { "title": "Consul Dataplane", - "path": "k8s/dataplane" + "routes": [ + { + "title": "Overview", + "path": "k8s/dataplane/index" + }, + { + "title": "CLI Reference", + "path": "k8s/dataplane/consul-dataplane" + } + ] }, { "title": "Consul DNS", From d3a3d8f91596c9cbf243ab9394bef60795bf85e8 Mon Sep 17 00:00:00 2001 From: boruszak Date: Wed, 28 Sep 2022 11:10:45 -0500 Subject: [PATCH 023/172] nav fix --- website/data/docs-nav-data.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/data/docs-nav-data.json b/website/data/docs-nav-data.json index 0d8297806c..ed29e50888 100644 --- a/website/data/docs-nav-data.json +++ b/website/data/docs-nav-data.json @@ -954,7 +954,7 @@ "routes": [ { "title": "Overview", - "path": "k8s/dataplane/index" + "path": "k8s/dataplane" }, { "title": "CLI Reference", From 142141f2f044c376c21a3355fc330cdca7ba45c7 Mon Sep 17 00:00:00 2001 From: Adam Rowan <92474478+bear359@users.noreply.github.com> Date: Wed, 28 Sep 2022 10:27:34 -0600 Subject: [PATCH 024/172] Updating Consul Integration Program Updating Consul Integration Program to include additional links to better server as a one-stop solution for Partners wanting to integrate with Consul. --- .../content/docs/integrate/partnerships.mdx | 78 +++++++++++-------- 1 file changed, 47 insertions(+), 31 deletions(-) diff --git a/website/content/docs/integrate/partnerships.mdx b/website/content/docs/integrate/partnerships.mdx index de7d8a6308..2a545cb1ce 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. Integrations can be done with any of the three Consul versions: - **Self-Managed**. Open source, always free - **HashiCorp Cloud Platform (HCP)**. A hosted version of Consul managed in the cloud @@ -37,7 +37,7 @@ By leveraging Consul's RESTful HTTP API system, prospective partners are able to -> **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). -**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 HCP Consul is free and available [here](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** - -- [How to Integrate a Sidecar Proxy Documentation](/docs/connect/proxies/integrate) -- [Example of Envoy Integration](/docs/connect/proxies/envoy) - -**API Gateway** - -- [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) - **Application Performance Monitoring (APM)** - [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) +- [Monitor HCP Consul with New Relic Instant Observability](https://github.com/newrelic-experimental/hashicorp-quickstart-annex/blob/main/hcp-consul/README.md) +- [HCP Consul & 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) -**Logging** +**Network Performance Monitoring (NPM)** -- [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) +- [Datadog NPM now supports Consul networking](https://www.datadoghq.com/blog/monitor-consul-with-datadog-npm/) + +**OpenTelemetry Integrations** + +- [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 & Alerting** + +- [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 & 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,39 @@ 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**: 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 or Azure free tier services, there should be no cost beyond the time spent by the designated tester. -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). +Please note that HCP Consul is currently only deployed on AWS and Microsoft Azure so the partner’s application should be able to be deployed 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) **Consul Enterprise**: An integration qualifies for Consul Enterprise when it is tested and compatible with Consul Enterprise Namespaces. From 444c10850584d1a4fd92388e9028060f549cd216 Mon Sep 17 00:00:00 2001 From: Adam Rowan <92474478+bear359@users.noreply.github.com> Date: Wed, 28 Sep 2022 10:34:35 -0600 Subject: [PATCH 025/172] Uploading updated Consul Ecosystem image Uploading updated Consul Ecosystem image from Design --- .../public/img/consul_ecosystem_diagram2.png | Bin 68144 -> 69365 bytes 1 file changed, 0 insertions(+), 0 deletions(-) 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 zcmeFYcT^MW|35esK`iLii+}OqMm@B_cMftdjSd`>#J?Y0l#5q^E^x`A~#&+<+{t;eF1YQTchL7-o0v)? zA5<-`i&I>Ibi+x8i>QduI^r%k8qF#t9V?IAy zgE2iMD>F9(?#%b5$Xe(vX16mSvemS`iv<9&+*8{B?%cQq9NODavE+5#+r}mz1NioK z5>f%Ny{+pf{*JvZaP~Z~cSiuRzj##kwjDQxVtMv^CbW~Xy7M+P$qgsVodsyfILw8r2yLixANz8^41PkThIuwgXN0Fi zbuG)X3%aG#yVxg}7q!;A@=er5Cy;nhx%K-%!?H6?Ry7*tsV{v0|zfJ2T6{ld%~k=HAN4QY%rIyS(u476uMqJd`B=AP8p)N0qgj3b{F5M-bB#O_eX{h_5Mb` znK>oZJp0AW&qE*QI-u@KK&;s?U#4NofkbOtq@~ZWZA$UTTz{4t(L}jztje%lhaiVr z(+nBEm6dPpixN4Lb)(!R(IIOQb)duM)Wii*1Zvq3r;(7mJ>v0(4Yp!HRRG?9z`>U92}IeSg>+m4{K!eoFj`yfoMR zKv@9rt&w|&Ofr-tH9eFe2Uw&S?sVsFU41Q!{4FFy zBHcdj0aN%!^7pE|=#`YMzx35Cn3wCUz6;b;O$W?);s(vWPHt>uB}oWt$(#BS_Vwf8 zKKY$UTRxhvEh%OusPr)6%}a@oO@C?W)FUMEhE9vb3lKQdtrF^mkPgE!WUz(w_+x;k z9MjuQ_hlCIMX(O%P* z=A5kt-r?_jnGM(u%*vJQCqcXeC|()JpF)7uCCi9{bjg?SwmG(kwh+Q3#MkBRJ(mnZ zgKnUjMb-rW<1A7ny)De2+*O{IofS>G48A6q=P!!1(bMWI>~@B$x_$}m<~YO+F+P~= z=|W#6U+Je>73c3ZG1!NOtk^%(CnA&YY}Fpt5}HL$l-rhn=nFP!%+?$9#KUaTFtLDN zO4N<<{2?JXYGKLRi(pcCVN?OMD^@(^{I&wmnyPVoj&{gH@*`W)4t8>VeQA^FUH_dIMU!qcr8z4Qf zI;*!^gHe*?f>*Fv{9r&EpL*48G$Ic=gP^D%nflyoey448#O`ieLPUvACF}@Zu5>N$ zf&53*kMDLs>9{n^CuJO!`@Z;a$mz)C&pRaQ@qGWzVdbryYW-)TTEC|iXp7tS-P%Bh z{~thoC^PZP_a_07o6f|V(9-9=%)}cm-;CrgQ3)k$XGKsj-@jqQgH|pgAqXc@kS{^o zw8FnEZ1eVN4gqJdP%hF!^F=;1`i*)R=65`AvBC6cYfnK9Q0v2NEQm62Ny_(XctP=f zk!pgte%B8>tjyH(m28V36h=i2@&SaOljzt@id{SYci7AZ3LR2WZZWTpZ2oNRmdaUJ zM1D|BD@Q=2$VV}2janX`Z5)n{jf*oAO?UHWH6hxD^6KU}nwJ>dB}7Yty28LV603%- z(wkO=NWr6-=B5rBe~$7_Yy=J+>&Z|TJ8wT2bm|zsdhO&Jqja~aKl+0Qf7CN0=TywM zy&^-uYookt6pAMV&l^YO zw2q}OaE0s$Z|k0a5wfZ8Ojcpm9wB1q#%1@#>$D$}I5Eom`}z2Jz&~>GaMkrP$3Tyg z%9%^px{Ex4T3gswxhxx(!b=xz?vNX*I!T?{>*E{CXCT%IuUM>b*eI&JdUs&)v93TL z$#Z&)^&(g?KgN47XX^COpV@lsq8!)8w4wQkoVvtx(@~76izp%Xx2iLb>+u}^L_;Q} zq9zoZDRKy=8l;X;>}IUhpOrD+v7k=tn5tD$QefNQn{2bk2Tre+uf2wnDW2zoHiw+^)t z6Tf3|@Gv91Y`rJ8oxh-7_;7z~dgsC5{O2801%EN#EhZa^9_O_P2Isw%T94g2!%G3; zI2)~^+i2oaR_K1uuXOF$6^NGeLZNkSdU-yap+>t>E}s|IdZVSf`swRVo!{9s@q`SP zMqJh5+iE9D9leZnrFt@bY}0&qGo&`jjDV~{mmqGzT#u)RP72(C`S&Mvo&1fW9XUTf zAl7Do?BQaZ(hnDb8m=>y2=L2AO2$&O9mRH*R9uRE54N}f(3P3kP_A%X1 ze}2||_Iqu2p|-(24leDr^)(#7=%977RBz~OvF8j$`L~*Kh{ewhk7jIn2C?$e#D6Yr z`umsnW30?7^pR6Lu8%Erj=AVfd+BZ{-e<;4KasI9ulEag`3H$^BT-L5|SGO6R9 z2@Q#lZW3?!^}9Ue16oXge!SW{L{-i`V5OwyFl zg9;}Z22?I^u2y&=zc@#y@@B(rQWOe5i|FYkQ95<&{ni>sl|rtVIHNtoGASoceO@9T z1SG7JB2S1P8DkMd;KD_euwSCI`a`-ulz@)ALB}+OjRe|-wr*d!6|Ik8ukRLwp8w!y z_z&`0rrl1qTs-D@L@EZ|5FvX$DLW+~P3lYX2W^NlGb&B0`5SVk>D#O0vd!?I%^xx% z(oHa4s^#5xohrvy2OZQ$%{A34J~pq=G6(|^)miz^sfOmes_R$!RBN75jXX5SVl7H1FK!czg zpI}UTIYvZgRUQDO7x88qCc7#_PSAv}^Zqz?2yNghr}{ES;?whPTG8PcwYJcsc^@d-i1KJ?4nC!AfB^4n9UHAV66SXp{zkP;`iVXe z7>+-HgD*<;EQU#LqGVqa_K62n7m8emq-&R7Itz5@fB@7i^k|(`@|j=<&zt@!aw8ys z7^qqmlX1IWyoV^tM&+dg1(rs?c3X-Ei+MkiD(C<7?XB%mxH+@e)XQcPmsStGVEA-0 zqWc14)Q$|XfWN^p-}3?33eF~qR##lse$+oH2QNZ>ux}W3Xp}1oOxns;Yw5i|+&kM` zy*(h@rM@0Bpp!-!Q?;OKp1r(>izTl77n1hj`4>t5-vQyb|63gIzYL!9WKH`nd;VA$ zF~qL>J}P9ZZLLo}B_BN#L7nFX67u&j|1NG1?CEYyx^rUoK&{}{mUvO+Pz#;k|ID*E zD=jsV+rPU@%;kTOnt~T2N|+!J4>`T_%ecj}b@l5(PP;y%UybVr#cuxtHDlhTt} zT_ElbBEqN>K6jyd@M_v))uF|q{Cqg6ZhMW5V`Oo>5aMe&rxEMJE94B9yzM-uJS)36 zddIsB1=r1sW-n0}m_CM^PqX@V+f9qfQU4f$W;f6re+Mi3g-uCbvT#l)nrk zW~q5!5cz2M>HTH2OE*Jl^u66Bi85zc(fcblXyd;af!rMyOQ{`Gj_(if*Pqu~R4$Fy|Okj!lO%}hACFD>cToAS?~$2@Qb0c|<# z6$lKPiW4>rP`mD+K896hS!5?Fw3{kpm18Eb%0sf!t8bYXhFbhh>i%v%d?OvYaF$`c zpsY{z)Jj*r48%TL2RUM_$hm$KF$dN{u6K`6qj|1&!yOl#X~3{vb&-bO-LKftnIJ^O ziIO0s^``}lkc&6dAa4WYHWhX?D}t$68@D|ogWUiPn zaO37akg!%>G?-PA(j(day>QOEp0@*e{HAMGe%XZ;2oK>7%;(Py~54wo566rwbILH`}0%2@N{hM2br+z zt&q%&)#Xyouo7i!F`vY3vo0M>SL0((H|XA=c%d%m*v!FaJhPkM`M^maJ@t$-k7rrG zf~>LQr_-ybUlkp&XLui0_KDfzZw7?p=o-qECm#7a(FAw&MMQ|nYWBlvsKHuu!xz@a zo%eA9i%@l;S0*igD-5kOB=OOO$dH~OKf^U`O=X%cG8TyaeRpqA$lnyRvx8ok)_EP> zLH^_gE{FP)di4%}D9Dd0NV_*7lTti2X!Bknu33w356n93uHbjsA&eI-WymF(Q^LmC!M_1;p%-{%D;56kU66eFQ|X9RP6M{dB#e5FB2$nf~e z;WVJW`np+~&#Br4b>^#|GSWY_&70F_TMw%{!rsp8I>n`J={|YHkq|Z^&^2VsvBB?U zzrb`!VKZ=VjqAflbTww7X{3d-~VN`WsyPp(75CU9TWqX6*%FXk> zl7N)a{?^jaXb%45JbXMuy*5`Q;=)Z+*LzA#1jD|5CHn(%#Vnh2{KiB^sQAD0X7M?o zI)CW<@!@fXP6gtIJi3Z+S0M?GNUBe$H)W@rEa)2qa+)Q^g_uBW=h=Txba5w^(Dxl? zi>|(DuSw^3FS>@j*+8pYjHHZ}@xd-0b4@3pM;|I4{~&vm=yC8_^N|3lNM~x2wfV&O zX#9r4AT7>zvAD=EJ*Ip#I7{Tj1mm*_RT)r8*jo#5pQ=WtL&?`48ZGe=CS25c?h~J$ zIRO)SOHNU#yV(O#%rrH5A!Fd1PDPqXI{4Qe(Buhk+qO*0Nm&@jUOaYNR-)P>)ULcj zG>xqBX~5{zV2S5-7iwa^FXAIc8m0im5w`bDrM z6r*vTkxrf%bbmVDZ+xmYols-x13?mRI*+P97+f<4^4~TiU{=Fzeish|e#(1`y~NY! zIW5PE)9$D;bQBxSCo?Gv*3{}_yEncL`Ba_3r(}=+Sh-B=9aEnY_^1kS){9p-?OaX^eP@PF)6~YXI=vBsC)`@1 zHvKZt8BFdfl*{9)YkrR~`Z+~)a(g5Lb6GqpXcMy-i7DY==p4*SRRF@bdjU$)z_Nw= z-CJ6rC@kR{yB&&)0qoxZ;l>D>uL~8kO;!%`n~@Bg9?sQ)*SQB*(v7>>!t0-%^t4e} z;~{p;<4P`2E1`HM^p5Z)&Zz#*=uUgErnP{f)^*CuQ^*i!w8l)hyK0Ye+yOpPutbeh%c~v%#Q7s6ej%<}>}tot<_r>+o~oTLM`n7Wp%D$c9D?Ln9?R!0#m! zQC~^#&_Q9dJzH9xD!JL7pl-`lv79m>Z7*oBVq!Q1l7B}N&k`h^k|={9O&+3hz^*=l z`ekSgg~B84xf(<4BS3}Peeei24!sdGg#PJwWAN%d%~8gJJt^rW6I7H#w;oI?1qf2P z!?nA79S`|CRPK}?nQg4a@cD@nJQLNZ`atE4|CUMozdGW_-bhNkONw4EB<}H9!P8N- z&n|_7?BZXNQqfGUZfR8pgr}%A?K6Ai=3nL7sVyR8Eo?f1T1Ctao6*{W59zW@qj`aj zOrcn&-C>d1ajP16;Xhxk^vdQh1NKf+w)tAYUUdk` zle_7h1wHVY=b08tb#^>T`8}vMq@%W_x(^ULBGOUmB#_8Vw2K~c%MkwTAv&J642`pr z4h0u*e{q|Np+mJf&#jX{RyNk5A<|a1q6=S}-$iji_Cef(z9gxSwy04H{hh~Q zyW4P)FA@-ORw~QtwWh>3L169-sL8TJzgn6xdd?<2t~IGs%IG6K2b4j=*5`JWvwM`6 zPeIn^^`7k#G9kMr5wVq{Tw3ph3H6-BFsJ-K#z$Aj#6;}@9-bqzIT3dl5emw`V4zbBP zD&~qwP#al9OZk2p(f9#F6;}9F9FIslqdBUuc0N=pnAkS!c8V$a`y9VOEKwHls=`aw zMF{qF{2~1Ch~f!eK2tln80r~y!uJs5Vp$qcIit6Di7|=kU(f_QzlY$V6%nqPPwop> zC?KE2QiY!IyzK2QqpWJgm1pQt;-p8TDP<)a+(GveNqz~zYp#HpD36NoC@Q8Aq!~xn zM@9vB+2BE5SoP1lGFo5!v<$}GZs-5tA28}t)!LHy&ml>tAoeLBixXE^V6C+u5)1b7 zpZ#F@&Gsp24P8IP*X@LHt(HgBJNa1vVVTFmJ*E0Kw32d_o9Io8dBW<7h;kPY%lfC( zVJ5~|po6sW=@rniaSW(ehjHR$U}tUvITT4AmG$GieD`YnTiz%6GHyoEWH!WW@DSC5MOmpy9c-#7j2`6TPCWbptn0wv=uk?$oKfF@m*YBRMhlt?n zHI2!(yqLT0r~ynWf)De)11Ik0WW`~A3G1)Vq>u4h96SefRq8gEO1V_Z7z7j((Tdd; zu8vrD@5pK0?5cvyJ8G7_F%>8OyBf;|*qnmtObY&8oYM zo&U$|s>CQ>e{AhVXqJC8Wq43bD2B)N9@rf}XHML;Q~SHT9E@tB+klE&WPi>+hD`B` z_AK+Rvr{)@`3L95jDrq|XO9_cYn8vcbZnP*?@i6BsOk2Bfd}88Qa{R&(&zV=5P=v1 z)Qkf&GHK26{FN}@a`pA9v_8_BrXk+c<7AVQQ1_Y@c&2Nc2Z6bBfLAeG*W4Gps6VmD zC-(Ed4fh`cTIiJV3QoH+o`XId;}ZVkMHxsvW{r5OO*Pz8!?(y8`#KVCbP>VMi{+ov zwoj3M*4d19ZX3N~Ms(%{G^HrO!(kx&fp59a4L0{g{JMCx{Hx&W#}krQMS(65T&tN; zAK_2ppSmThAEim>fNIipG-VF-NNR*_?0mQ?6<5aJrc9cj1-vScu@_8ITfe{tWfRs9vXqdu`Q9B;oIr;-r1jso--5c7y)acXeiia&zBx4_a6b(bRr{A=q{mR4ae;sQ&Gt)Jl{oG#* zUs@$I7=G4Rs$taef@TGpBNwZ68@8E!$%wtl3ykr1NW6l>&LCr4?dHPYJZ z%}+XhQnW~ZbYmsu(v7Y^%MRt)-fA`F#5IpJ3Rk$Lu)mU!(RVa?03zJeV- zKq7$rpy0lOY=`Vjv(+sN_I>)l7r;ZD>kbPN&KE#><3w^8{1n9!D6B83t(@-P{cyd{A#~U z+=CN%eE?wInl8@0j4JK^qk$i}=iASJf>k`n7|Fe%K6<@N^Lr1z5cV&nIwZ1|FYB^@ z6{e7|iB3v*{aTZe;E`Li08Y6rgqqS-+^6B@FF%eE@`|rBGrNgexwHIAHgA$kynEZv z;neO8TqoP)m6?h^Pb~2)cmd#Q;n}V9Y~o*ihLk8mQ2G(THmQ!!{VVp_vCZ^H*S=2p z$r3&rM67w4CR1h-B!w#!4IOu=7tgEzWpoTdy@y!qG*jDatzaL&aXF07t@{9Q5q}j; zH$n5aFHy5&H&r6na7nq{e?))wG1uG=D46V1+56$$(cLF7JItr=yu7okI?OSvetz>$uvXxb;ePVTQQ14pS1>=Ho+Lw`(7(DW2PwTksDQ69y|6KF~ zZb$WQ+e$bL#IM+@E-l)ODj&0QhqXS}I5q zmtCh_t#Xvp=u5txe91&optIPMSR?@|o4cRLoZ z?`%>JeXt@D1}ze;%MNnaqB5%dr9KMdsh{UmZN?EWwe;j`FEJD4PUdm9fc|88*qH;g z(?G0k*Fjpriuh~|WK$0Eh&E^_MYz>CdE%OD>_CrqOcxLN-RxyIFTD5|jlZ}d>>?gO z&AhZYc!Aojm|<07KG0@H3NKnw%pQ8H{?;j@-vy%I8@{#~vXVl6WKa3frh98u)eQ_7 zgjg%G+H7fL6`|Ov15C6xII!NR!|nU;>Rsqi$98C@X)w)A1jry1b>2GK<`-?7tnNoHEL3yyD&Gj33UEmCSo*{Qac}9khjZsL?`gW?eMhd&iDu2 z0dWO~@xhPbl4Ht*H5utGH_Q#tAoh|^RANR%JwP=SdAlJwlGE99L` z7VeV5eocCchfKa#MjrnMWqkj%mt&P)+!fTrXB(CcPEyL)@CR?RISsV#&@B_(Dahn8 zxtJ;SBFS6oYg->~UVR&2Lg@6z(Lk)6a->KVxEMD8tcD%Kc`XO7OgF6*WjN#8^$Y^b zI{xBgLFlc)F@#*$@_2=vciv}h!u0d@kQkjwThBv^n`F(cJu}~c3JZvIMElU}rQP3c zloqJRDsn>4XZAbH6cXDjd@bU7=@i>X6> zBNQ1o&Rg=m*TRE2TJ_wM=a2U!yTvP&C@k(lt(_R>jLsDv1wbHEnsmnN>}dvDTXI1M z2y7cU24+R3d>$T=?H@Fi6dp?X5*GS0YN@OGuejqS^n$`AO=13yb*4*kF|_HBINekG zuLBod>K2-cPOmDh6#DY;s2FZtSh*OXpPnKtBm;P12dz*UTF-(ANdbAs*cWc120Sy) z1A~bz>E{7sS!2m@{Mkf&Y=d=KI-$Bn!yoY!9rWC{O7B>Uzd>W=^Fp8Aac9aF__B3{ z`#KZ_Z$(a}eD{nV-7UEJ zU0b;L4R%%Ml98Nyh7I1Y%F)HQ28wvYX!dB;d@PX(A8f2^oe_F2>f`?Y(V`nVJQK86 zRPJyIH1iSYS6R1 z@Ol!s$;xO+_O3sO}~VCbK>Zp>Q)xvKl5F6`LO{6)?M+qDT=bHHJUdOA%j+C zWM5@o2pUY|Rj9>bI8ongvTDD7}(qkvr(K}q-xvZAgC! z;hmOoTUE96)T#Iwaq2%2L)X}#k5kX%K2!Af?^zM(-E0Yq~Xodo# z(0z&z;dd%A(KBYfRlvBrPB50dr{05q|Hqi){~GCz-bjyHgh)z=;=*ES_gnfOdF==S zp)Y`NK?I!@P2Z)HHoG*m)#u)5%uI53%mN~;y1o8~Y7XQV&M|<<-D#L`wnEhMK)YOa z*D_dK%u;c-wt6QU1ILBAo*YZk!aNL1fV5zVa+~cTb&z>@}kWHnSgW=Cjj`9@xxhuo-qvBdWxG@m^a* z1Lvn``{ZYM0H|2CpfSuU$!vFozBVrGLlSLm7*yFJaNI(7P~0L5{yz;33_K zD$2hId|NpZp{CG9!Vr&2&i(-fof7oF8A%V1Tq^g2{2L)!^X&M`a8 zFDyyz&@A*$-yheQG_4uR=VjG7doT!j3^=V;WLeWAJQlBvnXGb@It#fiesm&9!)(DV zIk#y!rz#SoHlSS_1qKcvA6&jJ2QLmX1fGu{8Wj^v;79pDM2&bIM@uJ2XZ}PxLe-6@!pB03a7lqk1RgGtFh~IAgJQ_!ke(Fwv$n zzeQA}@$&?`Rvv-AYUv6^4?&%v^LLZC=VJ zFuC>qWN(spl%Cxp)I(4-&-s--*m?&30Q}8hHXTYIX6ufJ^{Nge zj%)MIDrB>-$YBeJK8_o@LPu%pb*1PlTO)pXHeE{0P)ooRmJEdVcyA+r(^cn^G0ght zZOqa*HA0s@ss!Ifq_+j=DrJZEMh(gR$paLR^8U;$+G)QD}8aXTFS?bS}A z0z4lXCz-yKDwS$jtY%HrmZrL~@8IHOjAu?E;^G(Da2w@*i&wDg(@nudZI3KmoM*;F zVII1*gs>%Oe_c1RR6m~z!!{Gvkg`pW%>i6NRM#RyZEgZHw0UoWB_1O-mkfo8tVxwd zx_Rd@>}gu6sw?5kpSy*pH5kJ)XdO~>OxOwm^|+$MMoNuULE>Zu$a?d~o_BIz_p+%+ zrbRJ?4XE!an4lgg;?|4)*4rJJ@>dahY`Mp5IYqlFpnB1&;U~ow+;JJQ9I(VT+tkRy z$aapoX}6P6eGR!c+Vn(dk;OGX8*4_w(+l(CjV+I6akhm4qBg ztY~2Eqd!?1n@evtvMwIAr7mnS73-mNRw*eGPUi#S)gp8SC~u1cVjbwK{nRIf5aQPi zjNS*tvjvnLzH;&IQ=|>=thBBxv|o-Vu*F?hF~{%7AC+NBln}UW_TQl=X;hLx_Gs~_ z=%45lLa`73jPkJxijL{Mf$3#s|_z+aaghV;4HAK<<2V{wJUt=W zkC%B3KNWpJqHfmf&ACKxk#|VmZ+unX7!vMyPXPAQZzKzGD@Tc-GC09B8*o*~Q0?h6 zx6>8Ny?|b#QnL-9Qr^RXmFo``897oe=X0v_yDP8ymulY`L`sFvK1nRx%Pp^P^#XRV z5th8$b$aM8KU*h|c{dd4&m!X7XIkgE)csPbxjxj89ldN3U?{SE9O8%V3$AKc>2)tY z9at?-FYC9uHoIKepqoXN)Qi=lX$i5rl*rS(%t_ClOn0KYsM!6U#%7Bhaecau$QJke z?;hmut2=t~F4KcnBw2bcKhaa?jzwVtLoFXBmdDiv^(_t}7TdJ1k6X8`x@&PqVS8UJ zd4H^NweH<(;-&-dj+>3!d>QrlfLh7@WX+TD#aH(<^OY;^(fu;*j=rmpaYB^Igb44L z8S335yVxDP>*JZr#XB)hqtLOsQK&~#m0_f1F=KJaB6>)821F$r7C$p{aUha8o!K4| z1?Nj}i3}>0cF*v8rs|c6mefi3e9-S}YX}D{aw6V+&}{}+UeHbdeN9q$F;wgJ#f*eb z#`#CO7jD`F<+*P-QDz}5`LymPM3*F1fC8pjg<_2ro>hAaqm}ro3MUQv+|ys@sh++o z@K$`YO4|hndDYD1H}(dD(iO_B0-H9z>fd9Z#+`Rq_jtNy|O78D9vY{tip zXre|!wsm|C*M3GstL7VG?0FN>enaV6<5kj(L*`*~46vQ1`E zKuD33(6m;+oRNBf6slmmVu#{uLf~D;IDF*j*I;7&67TFEz8w7Esei^vBeES@uX9Te zEvT-_+dKy=Sl&S^3*&s+?pdGnPqGe9m3($+-FqJ+qnPg~tM*8r}o95$(0COj?R4v3G$H%GGXZIa!>Er-S$@;?7)*JK5OS~+TMuOy@8=MjIV?~>HMDe9Tni0+D`pEEZ3JoN&ajY zdD&uK_kFkgT%&rX;slF`&SeFqx**z3TO{R?Fj@kM$5m)Ttf-A=uA#N{-~6fpi*PV} zgI+JXevieCc)#c5Hh9wZg{hrh1Vw(mRr{vp7x)RHe*mD#0Fg}0(nyhSr5&z*BP2Vl z%!S)}fyS*jUkIs~7`PYYM2g5=Y8ag)Xl30aRg;*=m!`64$H&_BxF6p6h|mSjhslEm z!loq+qOqpA6A`)X-T_()^-iT^gucgCI(DkwTd>U^h;(^Sr30dTNoUVx%Yb?g>YIcz zqpObT&8i&QjL=3<12`BlRjDOMed_OyN|phnxaTqBo~nAxe6q0@hJ?R#Z8x7oZd0RT zXq%U~SXywqPH3e5+?!-~57vMHU&+OEz+0Z|&SGhn12_49Q8R~a-Devqrg2A$*7xuY)Rrl$b zsPDcUu$GWHGc$PV`$M$F*4l$ovwv4ObrIl*c+xzVmhr2qfG4p3l-2yZM-e3Nt+$u1 zku=Y^zm#^5PdLsK`^|^v(luHEC4PMXcI1jWI)OJ;BDqkRKXm=|exgPJ0klqVcyov@d7BxHMHCnmtSfcz7}Vy`bKUlMeg?$#;!zG%3!sU?1TpfX;fBD=+Ubck}dt(ue>XC#mDZx zahg>b1A$TgZyLh(Zr7iqUB=>t7N_C|PM%HWcx{i(Xt=-J$T`=bin8}00Kj(c@mo(I zVoirUMr$tj-SV6~EMy2}xeQ`ZsbHLX#Vtx;%ky39bySxlJ74?@@$1dEc*Dr2|0@94qg}&!QLJ6m3w-88^>@c zFXV}(p?-y*bGb3$&I`mI1=Jpji^(26)E1KzXJoFTE{}Bb&^B(XPm^p%wwDqdOjEAv z>?ATr_+n`p;QVY%IY9EUoWFyPZAF9geM1P-IqZnVg)RL`yA86-Q@@pmC^DnIGDKc+Ks`H_sVW zRR8wUIIx_!UQu#LLKc;=@3BAnpi9uXKt*~osL+)00sDDW1UOqeP^8rlzHLNl{z_7--4gaN1wb;~#BV&BLNG_b9--6|w%?L?c0-eqaz(VYyKPR9zSy#25 z*hMyh#kDNZrj*+MH12sz{2?#N8|q4knnhKcA1xnuzs`dYOS0pFC+GbtdROL3{uJo# zhKGUcO7CL0-q29KJ_&R&5rQk(%C2F*_a9D7sw~{9`MD% z9srh4@~BMo#)cnHtJlg=;-kqJ%)ITEth35mO1L7XlZc;S?CBQmNmo3;9D?XZyUNtJ z+up8dvdI)%O-tII_jAG@eXmGCPN?oF=ksFKem@+0B){whKf9SJt{?$2T|0pP7>X z1VL>Vml;SpHpEd)Ya_a6WZstVHHn&p>(U-J4tpW%+BhxOC9CZ5H0T; z@Fa<1>ACa}-5O8wrHLzgRtmfwpM&1GQ^9mqbGpr$jBSj@CXoxtXv=Mz;n4O4a?n$! z5(c+pinWGjI-0{GhV(egPqxYeG+;CA;=MeZM0nnE9wx*iz}S!5-1g!A!j7^FM&|($ z6GZOw$gL{LHQ60(PFY$nn;5U;HaYF+^&qG$0@9={8pXEAwjmAZxbT((7 zanM;CO?ND0vGut%riB67xkD|Xa5Odqr>@DZ2#DN6Az+2uUu&G!p`MSDvKEmp^H)ms z)u=;bPx!S_j_|(`X#EQ+tr##e8KXY%-_}BOY{v=4t*OS>9?_BI)n65Q&d$%Hc2B zJMpxgvW2EIg=SYS#roQ3z{0|;{>qZ#Hkr1NDrx4aG@P7BG}eFbK8zX5lhw+{ZExVP z1^sr3))a!$A~!28q9vQ`z=Gyo5$I#csqS79&@R>eTBLgnmfo7^NgO1vfa}PLIviH~ zhNrHY+#oq(*jOoxpt|m6Fe8$WpB=JfpBy@g_Q*+Q`H^5jPI(h<4c|&hSOnpDB?EMG zr5>-7io3mmS~LIU*#J*q8w56H0^!L9wxL(?m}u&lFwTosdhUZ#+^@9U3XDNy8zY5q z>K6^Q>>)Ds1@#7L0W{6NSTiXw;vEz{#-cte8je3T8W;t{v`+1si|1ogH#;| znsiH*diJdjLh;NUS`Y5+^`*?}Bu%iKS0F;>SwqeAAb&OL_W;*>lsk`yX_myoHj5Tw zZ)okhlOkcQNBv@fm4n6gDUA2b?QmQ3RH#M8bl5L-U9?FJ((A!umagzvo@<>g~FGuen0`$q_r^E<9 zr(#E`ii`B@Ws^!Pj_2JS=Ru|?6!h*QeemkOY09a9>z~z+{2CO`yk>tdmB^cvvTe9| z@b9+VkG(qt6XCPIM|^#c1P3@?MUJlcDi2Ftc;8;4Yk_j%M0mB8jmPn#;wA^*n@4W? zAv6QDuGhB3?AX(qC#ox&Jaz`><}u74MKtNWQH2p+nT)o;*=E96oG43jVN&XL6M8h1 zc$qD%KhDeBRYAn`<55b5y4%gf85B(x-^o~fV{m^M7QvCA!n`?Rv~A{qI}!geZ^PNX z^?PW`PGuHT>ra~ZFtYP!QvC+gaYFSWNux;8IyvNAO&B6Ol=#r4z49RJU=nKFkklA@ z6N|*8|Bi-vGX3(mO$z;!Hm_IljoOa?UbIu!%937mSprb?Ch`FNa3v<0}IB&XfJ}ZfCPj&y+f+ArDC5 z9X~sf2f(W`n6%y;$fJ&D@?C&BaBoKM|D5WKYt(oqchO0;KVcxzl)V zSl)(7oDU`B10n|L{>q2SYL!A^`8!5&83oj_%H1!Z&Sl8vV~NC=a$H(0!0^{Asg8TH zapvQ3hZk16I{gaY8a6HDI<;b8T2WOwx-GUWL&y1AJ(Sf>#709{smok*!&e!wuOSr@ zFYmcLWJvkqq(8>F;hSfq1fZvXPU)5S#2xU?(o7IbtnrC^nW27(q151KQH?-vx(4~6 zoth8?i;kBu5?^i#$sOQ76IPl?d)RlL#FCN z@tY?Ul&;&M%l*xRA$M6X{0UdbD7L0BNV0946i&FXCfKC~9V(gKU_Q=r@lwE*&~>B{ zbnHJ!CUnxzKe9kgHIyg@~#2 zyZGBld)qqYgqVNbWc1a}hc5xR~fF#SEoGaJm zs8X}Z8_PqbO4L_AQ%OS3-E&B<_m960Io}GiE~`{U*y5;#UQb^>B7qoy-w1=J>`bBg zg%Gk&Pkcyn2jFmUe{iuEhfJ=&OEdv#Nt=rldr6zyz`!wZFjZl{+6J z@~i>UWCdg6yxup%YcBK$6(bULGE@*|X)$K)U^5qPu8*txoOzvCCe^3C{-4G1Hsl1y zxq<3XKFO~42&ycy3fD924m|Z82!iT}N^AJMcXZ@>8+*Aa#ooF$b-Zfd^j@-Dj7JMB zGk?M2391K!msC0Y(!lbTdF6?fqd)S!w1Be zUsZ>yJ1^7z*66rQ9Z?zrvI@NwuA%WJC`2fFQUmP|r_kcPC5iiP`Q+V15H18B8=<5Z z+S94^iLd;Ek4vm%(>FC&OF`|L5+Du7owYR3>S?2j4|ZV(Y4X|Q;8kQya^elMr!mHm z*V})S#Vlj)u&$)7GCry|<>iHpDZgR8>Oe}|g^f#Md`j!5nz|$NkFY>P_bGdzk{*FH%fYzXwF_SctDbmUVIV(={3&)z z4-^|in8LS}7{&NbwDlM7|K9&fQ}1Ch(OXnU9lhFK`n7S5I;xeP^2zElOr}&nBnNBP z+oQKHI)C%lSfTP}>*f=yf>@cpH~DA&JufXccCPrEk&O2Ov9(`$Vii!aH&IzXx7CLF zqM3IpA8S#)bDh2p1(762GlTCsD|@pugI#oNiXMjpnr$NK^FUG@uE0)(rF_EdwClrK z8-;fy1Q7jj@=Zzj;toH6D$sgr@*@P7%G#SOPx5p}w5qYHu@FbNzD`wENn+CChWOb^ zMEpIXU*hK19`;Ege_UBBv3qN?pxbAh`fD^WUnmGi)ptJz^Um=dTR_`cw%x+`ro04j znMRvBJ~wFH{|^_$5IcQ8gulH|t980%Ehhfa4=$fV&U1Ca2^uzLKC_0?GX+(X)h)dr zu&^R~gda2UQ%vI6gwq(GFfGP^z$cSIy=Z=Y_jKQn9=30Op3=}#ZVVc*x?@_VkyyWX z`dC&>6QMuuOg37ktczV<0B>8^_0kr3JYJsso;>Fc(rm(3l84-vE=5X_y&5KR(KYq4 z`h(LknZb}MS)+Qm>NO;c+HrCq1U6w-fUgXOR>%m`g6DFADQ1t#NVf4^n+2$s#Ke-t z4Ekl<9IpTK)eam@cj%YCWxS;ij}#sL^H!jmh&u~yhe>7?I2s@l*@xo50Io!>D?97Q zNvtHO!+h`Vof91}uMvCAp}re^Yd7p2-7IQv6bIv;G_Mp@-#d2lkV4tMT!+^%<+pa( zRW@7~Z>czN@Hf`GxGrkI+0I%O+c=nyjq<|>b4nu#V+Fo?10jAz(++P|W}FV)8%pax zoxFf0>9{MyNQRAxi@`&6Zr!at6D24*VgfvzZr>BYe??w(e>>tLpTk#m&Ub>J3Y6q} zcb+Ci;a~}hbbD(*nQl@p-WTYbrIl62l37mfbCt2lPQ}jUZ-{8)!>{SS1a)O&&xH-# z1;Z6sbc;UR&}ZT%eP$=9I`-t5uwCtK(hhIGX?4#wcKUZV2eYlKyKiS-6LXAtrXcMx zrin}R%eEJr>f4?_k2x^W5e%8~Ej1V$vG&iuSz!BBjSIyWwVsQ!aNt(D5bKd9gj=_5 zYp1l+<}^UJGC@aYj+!2|Vaaw@T5B0dnE6=6)Z0vnv=5lfxX{C}F-`||g4! z!9SehJ2p=$DVB?qDzQ;w?Tf`bY7H-hIv?B(6#~7z_SgCL3z$6uNc{Jyj)3P8< zC+vrJ;O-5(SgUIJBXlb1A5)B@pv|^!a#ScxW#O@fsKnrJc_7hBKBcVS4Vt-IZ|wgD z<^eQdfZiJK-F@Fng)Aenx}DF}mr&8?G3IQsuHx;!3Jinz?N#rId^mtzmk!kInSYeh zR!TlA!Pt`s;Bg;jdNd14?Cv zIx36U2o~2qXq{f-EWyK$!=JS>x zkPNB2xO(1Uc7yS1hqQ;jKK#k&k#_aP$T5E!>w=IlvQEo)!7Q>sZ&Mi@fIbf}{B(my zBN#Tps;5;=S^-T5b$r>*bMw;Q4;1w8-K}{txnKkce2wuCXf^4~t?BrcGSat)acH_B zLyf;MuL+?D1qnv>`p?LO&L4j*Qw*8OkwN%B!sXpBMymx0|JOae|LdL~3?9YAd@AWm z8+pKP>mEami2-+c`3M-?&;NaJ&r8Ieb`~cp*>*+48Q&?=t(0^tO6bnq{~~9~A9Cgc zMRt$&1G(Wkyi7>WHyjU7fjGu?tD>Tg@TV{&a_}}kfp=xs#0;3P#_JY_^40iMem!nN zxaJs=xT-8z>SN0H$KHA{Gd-HB`L`h1=zAM?=+6nveA3Q}CUR&31FtuFIFUrTD43`k zFwsoGME3y`g-v_O@n{~DwYT9NzfV3IncUtMLOrnb$qnvtR zdU*nL-zN`RyrEOJr$sSS6U@i*#-a<`FOCVZ<-pruZH|Oz_h^B}VZn&?As}Iu%M^qA z;WY-kKr@X8fPe2wNC9+cfis@VQMk~D58FWcK6At}H_U+yaib*HyfIwdVkC>VNW0Vs9cNs;GLmW_@`Ug(b(*WpOD$@(W zaN7ycjU&(jKvYo^c~m%folfo@l>(g*d>-3I%HPG~Jpx*y0SWY$lH!p-ZUdt6D z*K>rSgjho=DI<$wH31aw(W z=)8-(eS7T1M>r)gL+wes0K7q6lh=(d4!l0l zNDc)`lRGVxp&FVY+>T@@xB0?F&}9oy5AGhmWqOR8(ka`r#*+TM4Q1u^l=uy$5e$ZHmGh5CeGv zIS-e!s&attky|$0cuoLNYG)eFmMX}?pqkUbXKF(0p->L01rR0a07$F z8mQ)=(0RRogw|sp&0qoi8qm=Ofd9~J#2OdT%w9uVt?va@UIq17zRNr6r6!UTd_CP0 zbQoXoSR+|VxJHKp!M8L|gkpL{(}nIg{0$NYv3XSfZ;bvroxcg@TLF3$)Ixd+@F`F< zd-J~?8~4A<8wR`u|L+ib|K}Tp0LfT$W-ot9Sg=B27pFHu!GO)j0aBR)V*P(rz5m}@ zyZ!&~LQIVcfjX!6YY~R0?oCuplC03<|be`+YEp@oSC>x(83IRBI?LDh;Le<3hd&&FqWPifHsJOxkCzuICgt`MRvG=^AfTWb^^s~chU5U@>#6>bBfsU5Jah1-8l)gNKB(frmt zT$c3Al%Eqe+caQiUuO`Bt54QQ7=DvUF>oy?R|&N9{_An?^$Dfh>*Ac>O=}4HqWqyR zj@w^7fsZu>BmBqqA--r&l1mO;WPm@D2;i)J30-~i1^77VDF9}4eSX-zA3|!Pn?tA} zxf14>g+zLk2eY4Pk|HqU#qj%qHgl>(_4G!!xRr7+>H?>#Se*&xQj!Yw;4E}&@d%eb^KhcS~P*l z(%LnpgzDx3l_|U-7%3!)A|9=UAttw=S}8s6{RO@l&>AV2-U;%N5OzCw4JSWK&P?Y{ z&l7bMxO9G41arL8{jHr6cF)5ne;x-ILHA`|c}9=Oww3{9IpWaT_&M9`=5Md8>1XA1 z!J-c_6e3R?_G@OF544wq?)Nqq3WJd2F4M8tne9D8A4k4b@W}{SXNHSeJG)q%M_Q|h zza5#F4`^nPCl%-vYd_pAikoI>@M~OtWTvCfm;IWm$EzhYvs2JB%w0(vN-c&nE#NNlYe*>br{WtIq)D%02 zLJ_NXM}j;MbH_uP$^#crhu?rW|L&GzTND!_>_aGPnPdQWl#J5j#c&oI0NoFemc40-49KN@BvzWi>Wisg3+u zIDecMr9`!Tg4w{qo1mNg&yM=SprG6%9tof00Z;%%u-WcoqCr>zoIH@uo=OC2@dlO+ zfP!}R>;@3^($hEy;7uC02oUr(*+$|97ju&4KV$Rt@Ty)p%igkbfN$0nDSdLNH-ZJg z**EiDQ&iv(*%Cq7iXuH-g+L|ZW4|u2F1=O6#^Fd#7|o{MxpeS!)o-9s8s_8B9a%tM zxPs2xWC7I+|Ja7c1DG9af4XkiPmZ|1)vc1pahvM}i*PN3^Weu>S=>!-ZcT%jx1Y&w zebGSZEM775OZU&`YvY~Gvst--1oYshT3PZePfy~v|iRk`hOG;U3!8@r*A1Q@|9(~2fxiJ$WFBdM5PN=7A1-mag#puR`G*Mcu z)#ZJ!bm*QyENnz{1xwtw+Iy?+<*(MNmWalr{k~n1mi@W-I(v*J=`CjS$Cdc0R?jAP zOJ*DZK4|4ulC9=Zz=Yt3q<+_&ZKeToL3OhPcp+6+T%a6hRJk2bOca_jxsB|EHoM^J zJ6Q!TymRZ^K97VVx@Py&K7iL0<#9mq&fz_p6bzqN_Q10m%V(G1kKFfx#BuzE?vjCS z-W35ne~6>^v7YjyeUT=f82t^elU7 zo=FA}hnoR(T#F=R)e+Mtp>R#riXRlv()(v8{mHp-p?&8yU93n-ajPMOH z444P2swzPAa(TL!Q4yhi>x3V;W~4%CYENVqC~G<|EYY)#b&mXh%7-K#kvi`0?E zm?Ip%);`XuE77vp(QYTKcJklb@j3wF(Bwo<0HHsYt&bYy72d6gse8F%x;RMzdV63s zsH9d``1woxvv{R>S7XnnN}y|~4R6=>|T_&N;; zbIvIScM!&hNQel7!Wb(xDzn3_lFkY*%?}7lVYVDPaD~1hwvGYYCYStWUhEI1SVInr z`yUUtTd8TS8V!zLK^_{YQt8%I@wf;gJZ|2@s8PNzseW1QtYXLjznm5&l0xF=% zcOUgS&AKVSPPsOIUHXZW&|R5}zQ@GM_61iwA}NmsU#y6L9P&4|@lO0%Yj)Y)@rL;R zh)4`%OfEV;)&7r-hKuE0SqJK*vJgqM3Yj4^IXIN$FyioP8jwd*$^t-3VJIqCt|V^e z!7j1K?V>XhJ+!WilvZjV*L3!AB{T8vXzQ6AMDt%)WL%9W{)u}gl2;a^IyBX>XQuff z=lr;gchgAsC6eVEO@-o8DuC7NyB%^a0#jdoT1mb~!Jv|qb=+v-5;*+#_hEjv`zQco zun**NDzhfVZhktfJGa?>e&cXr&p3+5Xb3qZko&O12MN5v%r!MIH`z8cn`x`C5A+yx z4;d}e4kR}LU%kFR`jg#G{L`Ix$>c?uHn{169VTnf1Sj7!>h7y%%S7}&NgJF{=)?XV zkN8{`->8@$5^hIU+tj7oca`MdkTJ!pp2rnem-j_fxLBUrcTOm5S;@dV|7X@!Bcd8N+j%+cGXE92e3@e%IQspb_#*gTDC>*inigL8glMO+}PdPBTp|5 zsGHQPI$_h$|fSl@bYro+TT;LcfPp( z3d}H_Eu_9;CIa;;bm4hBb>iOF+Xm`p?&(g5E=6|%GGx$e%-G)hp$Y8uiEP=n3cF6; zLMa#(1>!U)d>ggnA=7hS(N!sWLvGv&*q4Q-sqF{QR_L8pP67!#30P1B#_-~<4N!Z` zE#oI=>lzBZ%L5!m?@#RcZTCn_bY|RPr;+k&k`cWq@8H^}D+7k2hlbvL2+hF>h3)8; zC+|5a?_-b}{06UosDXql&{p%!zj$6gB#Yl4eUh3N{aNIvzu6#i?X3cd{GYAyAeVUr zu=XY+q>~}7WL&0lIA&wavAKc>p2~MUFJh?>@^#dw6)aD8%ZS=Ea)sIfeSOBcF^1 zB()xR#PN!uL2QyS_jB6}~3PYBdAc?Zz{Z(<`Cm6HkT4G`ozzxgAOXnz6Mg6ob5S_GxI+b_9hc zT9k4;#Jvgw7>3;yXQjJEM|`{Zku4%#WsMdiVO54XE+MwbEp;V$EwIu2#!Ba8y~GD% z#^q+iV{YT3=7E2|enF7ENEHh+ISTCEk zK2yQFt#Vxe++fcX&WMDPy}&#KHdwhbBaqq}6_BBTlPKo+i^O}9TS)uXxJd+`N8IM~ zSQP$kN~1)>(njt&N~~J)Ab`}>jC!HNuc)x`$~OAtGvECfqO-`fY8fWkWleoh z>ej0~d^q>Lz$z3dm`_^V8S+`W&^dR^`f}HEoP;ugGtKYii7o?9o#XZ*dGpj8x6Zlm zj)qZfY*r~4&qbq|*oy?ll-aJ5d;)(yJ1KCGuBG}<8~Vsj59TN(n&4bG1+?sbJN3-` z_@nI_dW1wJ2aHDvg*15(wgz)iAx~aiK9?==a*?IEWX@;_;tw~=t$(;pxP4={(_*pR z+V)>BM5`sOvy^9n?N81g`tf>U*sydm)V(;kBpSd*&Ovo;8kQ^nu1)KWAdfcydxV&7 zCX}5=*y!8;@@XpG(^%ajdvZ3Rd_v8&@jSyYPKKyTZLJpI75vdgW%n%BogU)lLmC%K zgq5sZMozut4x7QcZ=1c(>%QnDEBZnRG}Qr@Z{GjaniL=`1OheK*M2z838r|>p*c#3K`iu#+KeH3jGT4@^plGCHm?eD@mUdR388w~Ph`}jN$mEoNy2a5nd zDyr3z1r&S(?z?sYfDV3N6(-vXcqxG}Nx14s6c})<14(}+*jX0DTg6i2lkQ5~=a0xi z%h-UM7!j;g0603@A%DIXwcypAzs#rtM?z!gCSpsE*qnr&mH4uBik9wERn z4Q@Ze8`uSirtnzK<~NvOV>j!MmsCe!%ny!yuh0hEQW&h6JC+O{b3?B$f1#f>c!{CX zLSsPfkk&auQjPEoLx2k6vIaQP7@GqSYCN=&E%10&pZtuC&dmVP0aF?R;0r?}N>zVY z$F==&utx;$Q+II${TF}z3hKpiLQ-ICr~7V>Fy>y!Ci}MO2qmA-#Vmy+VF6%%!*uiw z03Qz2z5;9@aKDPqdSVp|<)gv%HfNvR0OfWchc^!=lD*U_t5mNlCxuSuVo#p{ve1l|=y5^Ot3D4$X=`J5N+sJrcrt4Wwz>_%l$bFD8_t zf5lS}0|=MU293&#Lis};>?dDtlTNm6jCBj*QpIZs%qD9ZiwD4(4tR=>2Mc1h6RLjCV&`6N@$D+2gA!ca1^9@ z6|^r*A1K`ct-cj00ir>e<(R4csLA1}4Jca)cKJ9kmT!OJC0iY(?(gcYZ$0Q(?g=vhxMvl3ZTZO_j0WLENbJDKUidT<$Ps~Rsst~@H;FtRu;@6R$wZ7vc{XOV zkUE7o+`b}Uf>}WTEX`~;M~=@##5%^DzG2~+zp|iZ7!9-mfM(VO>fw})347%crg?;X z(1w=#UiU!*QXIH>GNnTdbTsvGx#$BRViw;lp!sAutRiu)4j~9?VgBaq0ODS+fZ1PP zO0_~?6m{i)VfO*yFu$~myZaJc0U2!ZCt~Nc1HiH@vl}l_y?U-}w_)5i8o=TWcR`8z z)7FWY_%e3471R<4qJzR71M>;n_wl55w%rc6z96Utj)0JFhg~5Ew3A}?<2L4T9)EQz zzF(S_NMyr9Zrs@I*0}ro!vmV%;}iaV8*Q_p@HN*94)b!n&v~uzT=MG3yGzx&dxKW1 z=7&3W`vvB|AcJ)5_g--Mt1|z4pWNT~$h|t-T@N+W+@3!YBK-Dzbl_+>Y-{Oh|Dk+l z{Z>9v4^xWJFwihCpg4}aku|`YTg75gEkrt*Kw$#}1f6$31z^CxJ-KqIm1M`z;@{vjZs1Z@G&lXFfXP8>|TBRB)9|Hk?A8~AZo zL8ARX|L(UD4Cc@AzF6>`KgVUW*PuTUYk<$PP6{yb{~W5JN%~ z{^y@}&-||&|KE{zf@UH)8EGU&h8=vLdQ$A4|{cii{C)GShy z(WZgOU;riv5K4Qq*t_>OO+BL>biVj{>^3;;*}U4H?-i9Kc}S)11-&&bG6TsO9{zK( z{it-j0SHtgIS2mp|J@eJmp}iy@BhEn?0+!!-75@w->}A-go?#)ukAlRy@?F#RZBea zK8X(9u==)5EZBe6v2wQ-fiVCwe_`ClOs%1X@)em(WI8`Yu(JJ@f7t0>91xX$iDN-G zaO>tuI!((STu37&ogxN6F(9~?Gj6mnzNUOdLK*Hy8w}XnPyGs}3OE(fjf}6+LZ|1p zt)R+5ydJqzVE8@%(4z`JNCoKl47g+Utz!-aO(%igh8Gx>m56e?ofEVSmkt+ z(fIoaK_-NO(ozs@M#j(QLWkC2L`hTOQk{M{g46jW`+|O6LT( zNWx#3DQHkwlT&3{;%~f%;;xC#nlyg-IQrSkQoJ5k9qY*Hj#V;98_H-sjB1vRSQEUO zE5JK-7o>1m)8aO^RQb69%~$%`Ni#~})e_mEevSA8<#8k!%14rgOz)-Px!2dIWa{a_ zgIaq!u8B_d9zs&QPcnov<0sNHaJ`0e%5V+Eo z?0{#@;t1^gQ-!25B=wt1+q*ogt$r*%Hi_sDb&cmDlKo?A4L7?wUmQ9sd|jzcw9sq% zcg$iZwxolc541Jy7mVS(;;H)&F5Hy3(sdiodDqbqlJni&=G>iMrrzdzejqHS?U0q7 z`S9HQmm!f+lBZH6;654M!RVXs-*qKn{NZQ)OP+JLj@`Or3nmblQ(jPRYJo|^lG|~J z#6m33P+{kFg|;(A+9Q6q!z;8K8dF1GWT7_%`sak4sHbO7!j49B?2k?if9lk+kI7Ov z?mjXuus*NV(}L=<`5*&{iaj8 z_ObfUS1BH6V6Gw5)Q63gKl1q-;t}zd9=t5nQ&sV;wG{PedjKf^=={A6;u)^~KHZXC z21gDLs7ITNZ^}mJLAO{Ys#$uu{D-0dYg>uLnjj7mpjS_`J(Lx^56vwaZhMA9qCI z5hrzr!sUsRmYlLxBYjuH)a_9HuO~=#1I%lkjrm>xTj(B{A5p{;$PDXNQWGm5Y_;qs zBOWg9ZQy|zBIrYVen8ETbHh5Ool%*Qw_B?Xx>r}d_-)7}U1YR5n{FLE{$V~F_bI=s%%c!WSH z_{Jw2!vZt1at@un&=Rv$h()YI7Jmff&?=P!4PK;$f2PPCPL5Go%6qL+~QQ3ao zO9!-~GfqTX^h5&U_B}h&{SIYAE&(y2ZC~4uIhP_;5#U)e@^QqF@a9zUw1IWHKH7n; z9|&DZ+(OkqzC7{$`D6?4R7)1k;fPD_9!01gGtYyPrG8r3YbNwy5XA9cTsq#6x;wB@2pY0N#~U*#~-RnhIyzLYh~}r|!JDQPWWbQ7NIt z3ZE?6GApOv zEwC&fT2e}{7b|QJKCLG#XFT^&+-sYzbID-{X*A)5tFy!Ih_3Ld(A>TBXR9Q2&&=$QKOPsguoq*{Y3v}k63v0vA%qRHEt<4apT&N-Z&2ha@1t0 z!u-p8kNTG+J@{C8AIAA9m@*kgwU{lBO;n3ZOH-OS41c>hK@3&7yL4|{#}vCk@j{2% z`yo9v#|mg4rg2V-4zaSs>@#tP;JZfj;$WowoLJt#?kBC$LEYX@SAHN8?@mg_6pg@`+FR)DoSI4@dhWqQCWT=R$g_&%X6muOO0o<#>0saP>Wv zq@l!fae<%jZZXB;4Z5xKSY7|r;fy*NPy2{7p|y$%4j=F#+R+wUcpbFEKmP`?=e$Px zy8ICnjB0v4j|CJB^osy;Ti({@q}qnEsk*Pijs&vRr4O=fIW$afYTP8kDZ;#K?8EoU ztni7>BoyrNa+SgA@jw=PF`cf69k#BU)@3mlq11uF{o5BE^pK}N6XRb7OuTrSI=zyC zF;yop<-j9#1>?rJ6eok0%G>;zva2=7fnS$D$yb^n?~Hr2;j^ZRJaB=99f8S)_76$g zpo_+*+i9Y(%E!FJcv$r>p7otCzpr-f{_(GLe6E{SEs`7v->u@ZqxdC^7Uw5v*45AM z3lC*$o++wb#EFIX81G>{((iTc!8WTMCaPI{Yp>msD8ufg=qN?uv#slm95YdKX^ z1jVfaYlFWM9O$4%|Jc{uGVop(pTs}YRu3iX=)yf3iW#hmzyi!zDR&iy^T#U%r%>{t z=;P60wZ2iD4$o6$&?8}_7f@NcE`VPegh=8Y1OdN*uTB}=3R)3+SWgDfE;9e#DS zK^gev$dT_Uyl7tD`Tclfz`0U!k*eT%VO69R}u^Z&K-aRk^k@KQ^n(h;jAe zbBl4_COhEOv{oSe+ak|?o)%xhELIcaL)CU;e^_AY^UJ9@6q%LcT3>t!=Vgt3mzZa&7L-SRX zk9(1P(6Nc;OzMqs+VbCy={o1E?CJ|3ZP($$p9|s=Ht`{iJ#m&w@}y7}gLZ4SPs^Vu z8=PB()c_}+^R8m&;S+I+?}=~S<@PUPs91+mA85Jx9(u+CpL|G;vsUGH;%HNR^Tp0mTXDZ zfy+xr6APsjs_P81y|cC{i73C()p~1_UF?1J+?9X*e|wvZ$aXE6tAYHqdj`}X?!*|aKlXJcO`C~oLgyov zIMX4mx73|3lx4_dyfJ1bH^B)VvB2I$3963heEY*4ripi3hTMU`&;40>p?&*up4KhW zZft#1t9vR^@qp0|=P!4O4(N(sI?px<3;uc3aP9bv^6M}n`bTPSvqX^7a|428SrGab zMy9K+b^Fm1#Ao**?p9?5DbhbBQ1|(Ui$?H_(1YghJ`$(O z`l7L%`T_=IacsI)BF^tW#k?Qt9V*~5slXW$9E{O^T^(2>h{^_1&|TSajBWo`kq ze!o8(^UIoVk}*t6gA^5TTrscT>|783@V!uBj~xHs&?|{Uy>k0~=4m2oNR2 z%^Wp5zH`FL0-prC%-yXw=*m^HEHsV)!6}HAApeZ?N z)H}{}yQ3kZ;(I)pl4~q#J!h80=rYOtdCn!H(1ZK^s|Ge$oV$)U%0F%gxk4PdCzBx> zqK9vz$Am{i-j;@`eN+QczJ7n@3Uj&-dk9(k^ez*VrZu&Rl_+KkG zao3aJFC<7lp)bB0C|@ePRe4%SQYoh6_1j_HfUjpT=XkD-)sd5KRA|D9rZYn#-}L$B zQXa%x6V6eF^kQ#sc3u8eU(?o5<+wUxc`RCK%ZyZ^%Z{18NsiBt<G24^;=65r_2T6q|FIW;O`ZDe<7o2Tyu7jcbK2j* zfj1>?741rhh=q}U#4=q7I4+v@q1RNbtfb*g?r>$Ugry;q6!`63IQ^9L2-9!yR${yf zWZ8p$yy^K*^f80xxyOsa$1agciwG;_^pBCRFowPPGr{_#V`jN8!~1JbI-vNAwvwk- zcEa)r6n+Xg7Y4YBewxrl@n)@^l}dc=?Z1KG@5Mq9k@LO*42C zPKwT)pBgr|)g0|Svezp8!x*We=6k{E&#{jxa&8s7SB6e51}8%tCSpF(Z`$*y<8Rb2 zIy}`0YFbV>wtED-)s^wD*+s^!)a)s%DV)jQN|J|AaiZ8=ZCj<0*Tosm^QQBxMl@G^ zDBVxN4G0~q#-~@DBTIsp42(+>ap^CaC1zvPbeiuPoulE;VV=;z5QFM|ll$(@e{W8) zRJ5B5e4qK&q=*C~&;5)2u|eySa|ASXRS_7;+5PNxwV!!c3^ebJTly|OJ19+nEVTRQjb!aU7(sjz~rje;= zx?bREjra14Q$8_Lh5lV`m-;V$$sSooGd@HYS{i;P+Ls+6kwhylylaptmk*eDt;Nx; zxVdti?w`6X@J-4rLG0$7jaS^;sx9#63yROv(p^IGEb`81+JkMoE~f6NiF`5jacnCz zK6xx~G%-PV83_fqV2A52RR0~MW!j9^(hG-9?B0$X_*_JLo^HV{coU@hv;VpQKJ@=$7YC5PEw24Ef>9j3sp zdnz$2ueT^nt}JyOv(0Gtb8M5#{XWnlX|hPz;7ezQ_)o;T&)(ek&d{j zHghbqEevgTh!BUixc0nD4+;(0yuQKdhw^`|eox77#D7a`()PbpS-6U;8=}kB(?1Vt zh0Ayw7ddoQHAG%F#hu7osR2%`da|C?3-1l-?H+Jvj<- z-AGYF*lX@M;VusC9U+!nMY?5^;~yYhv`VA*Z*}G)zEnq|RUL^++)8SydnrYdr#JA4 zCywM0vR$0jE9GKiGR&lWgDIJrp2)}MQ-c!m z+*A8Tehz>9H%rBI!mo>+|E@+cRxx!wr z>i6@!6_tVaAIJU)xe+mP{4ba}>_QnmaZEy`>uQ`FJHH1*$}9SwXEpzk=3uuuM!#I0 zn*}A8G@(^25@4p#^w7#PCV5&ti3g*28%5%qePBA55Nl;I=`5jN@Wy)Zw`!BD;ksX> zY1)!>B`KY9CNEm}>V-Py+}LN2C)1ATbgsp-)_HCtPQ#*AwmFCGX}WxxOlo|JkQjr6 z4A37k8d$Ax1}`?g`Wq_YB-*tj5vrdED+V%3FNGCO+`nKed}DuCaC(wjL)+pvD05_Z z8VSkI_!l)Y+Mc+Iz`VZLF_<9{@T%fww&ve%ZV8VduO1g>m^e+yJ69`)Pz|#5gT35C z0q~~hayLA-J^a+-on41aI+)Yp(Td_#e$D=*`B0}f{ZrfG1hc-q{=}DSI>pqy)_KHI zqvHpQPP2s8u?vWMvc8*+!)I@HEwy^_=9wXD;W?8%%hFVTbYADbY*)5Mg?laXCNbS# z&v;40!MxO49A= zKx*|jNVQ_dQ1shr48Rse7bsVak3Pu;kR`abYIdY2uuJl;`CIM~tY1N*rir&}!oW47 z;E!S{BYQ_;?jEmKjSfjh80)^k7Ax;Jz_HG}Hnysmt$A0?TFso1YWSGVnjUjsdDMYQ zCzjN@KO4t|TbMg`)L_AnY^;1pg$cil(YJ?JbzyI=FKNUa+IyJqY-u}Jc!uN;;6vdG ze?h(z_Wo+a$?bx15_jB9jl}K)-Cs5WQbx`%Mb_6-^@R3q1x^CFqV}V&Ks)c3T@@7+ zxC@T|OWNp9c>H2qc7Q?1z3zz}PmN$_6(onM-j^sSc6R{EJxEOOmGLyGcEN9uxo`lG zuC)6gBJ$^7AkXzbc{)lJNN2o%Gq=lquUZH*cb;fZftcNbbd7_|o3nKm!^^<0->3r!HfW${H0V%$xzC=7vewD)lKjsn= z1NsPAF&XWF)p1tZY;LcKCTD3bHWo))o+r{U+=~3@cw<=1vFCOXZ_iCsv4vNws!E$k}bbMVb3tU6uxVn#Z*!deSswRNm@c}Pz5XQ>D^ z7D@tVLaM99{m>Md+ar4XY|GlH=(Wh$gxSD+EQd73C)M@)sqc1cK_+gS39C_yF`V4< z)GE22rk&?pk*D%L&!*lU!D5dZ=-ZyWQ`1Ic#Xg4QFbgrY%paZJ4)XQ-31y!@H6e7&zA zGzlEH#28^hgo*@t+VfY&CTLi9N#YAxhWpf{Et@GlL)aq)151YBCFbG zzI-NBiI=mx7I&G-9nnp*RLqOnC7{`NK}#3^n%)~1GRby~qLMQEDA<;5gF?AS=WILQTk#5zzR`^{M*P5A zZL_IUUXGWtxioC`^EZ!x0k+owJHB5I{yk3_7e|9z%Z7fY!f3oZ|l7@_f2%c@I5Qi z=LLTWZMX=sz}@GQ;Yx)S`KPMdd*blTrON(cI4JT&|0s z94&J7EHjYp(A~O)v@qyac|m0uV3P=$W$3(XoitNaDhyH$=*M<%AkK8(nIW4JppD@6 z(z5}**lx-iKObZ9LQ*LW!^I@FvZ)h15}z)WZMu8D$_!Ona2(0u5HVS+?6>@Vw6<`H z7Hu)!zgz2TbsvjKABg|7`rwz`M~jSk6v6Na#=xI)NwmHy>zS^NHQsR}s%IntcbBkC>oWobr8eW?XraK3qVg8`rnf79GBzOL6*N@0O{zx z(RH-(V?5P+F%)VYchVdxqgPj_Qe}iy->djG1<$+})%UXO<6{qF8LIhCL0skb388(S zo-5Hy@hauBc_XAVsgU!A@z`t_d}Ob3$WlCZn!8X#bRl3}vwTBQNn6tULe`P!44Hl9CnJ^pC?j>PByev80}^H} zOQOFW+&*1WxBWT8^2ou-u$#R%^*RP`nOF149=#SbvO0$}oZy+i-M>xDH6H$fR77^K zTq`3$=_td@NxPrp4#AI*3S_i6To;*ZW2_(O_5%XT(ut|hdt_zdLFxad)`#BkjmlqV zd-s-&x4v-G3wd**I>LRB+2_%T9MKsyhMyU~ndyKfy+$lC^Qz-3E?5+Moa(d5c%aB`qZy z&`xA-;nW9D|GNm2gychT)f}c)EU96IW^zYqJQf=*F=_aq48MYI;PD!eJe-k*IQ)%u z7l@7a2(>aWqh=Iucwcu;+kPV3wpO{_qCh(gN9ocGxXp}+o#WVYt~8yMit5Z5iGz=Q zd?S4NAIqfxxeCWmkXwNsj%TiYkVP3X124$P9eHtcn&`l5w04b$AnLIu zpUE(cf9I3nK=oQtGdu~?S-c|(M5!8G(I;UeE5pwu0;jOj_YtW-_ z%|+pss*ZGLWai*0y$^xOQ=UWW{~LR685U*N^$%lQDoQ9QNUJa)A}O6J0@Bi5B3&X< zLyTS`9m){W2uLH{41+L$gbdvw12`~r3`6to;kxhpx!;fPr}y~(kB3iiaK>J1uf5K- zeyg|}LzM~_eSLcli&qRTPqAqggQ=wH-<$x~-ud@kxZTu5fgDZZ6JKq>Sj?MFPE_Z< zFKnxYk{_Z+|8ZwR>|T5G$7MEpnIV(Gk1M29LUu24-GA=^q&8ovZa>#<4pV z>K3W1?HT?wL@P`07D&Nl#+0{C*g`(By_ZnrH_K?^2pF(uOqXy7tR6 z_8y?V;|F7FUVYfzOp-QPlN>Eq(bF^P+L77{i_1g%m|<(BH5f-Ihc`Xj0!7%{AM380 zgMEl0bU#9~of6wbdz_o*trv~q%0kh_SFPkyOCYs6K$M-^mx#b zRd(K+^tZB0y?>9uU=2ZPcaVC3K$@Ub zP2a2Xr3Z90iY;2<&W59G#+$!lnKTUI1xf{Y{7uT)TU4S0)FortZn<|aU+VN8Y2D{+Bp= zf20XMW5fsd728?~l2{+t6C2^of>}u1zSDu^49(otlT>?+6XAI)N!aP?h#3r|W2Q>^G=t7DMhgoa2d`Esi|VVM z9O`umLr*{hrCZMRt5w}2i2@Vf0=9%kj0dJ71w|%b%ZiXXt(!bsE*u%FJMCbJW zj#3-MJNSqkLK#ts)N$VWp=gm-jd|Xoq1xoJkw>ek{P6zgtXJ^$yQ_p!Dl3CSebW1? zReEd=bmBZTp3eG=z7G)3f{Kgs1KfsPXX9r%Cb!VMp|{8@S+IOQGht@S36(|HOwRKc z7rujLR&nne#l=3@Jo*(-;ibK$3%@zeuPN9sYwC&^7>LW8*?03uu)=-mmsK=vErSN; zFxBfSn<&n18>1~j)R9?fWB-{0jT9C%hra4L?QK)D|0hZ7bS+-&3WQ6(|;PE1-chN#(29^@!9=?0e?Pt0E;&4PJpnV!Eb$OO?)WUSb}PZwx@h6#f73?4Pr+jef$*X7X5Ynf?D{9wjErKv-EbnZZBF1 zH{o_HZF&%R2v(X@1nnAyu~P#*}u*R+?2(?8L1bWpE^I`l97z$%d}|P?G1NHxtKPD8?h1 zw8Gyda$ePWRQJJ0txB76^}*2S?8X%}`l)xp4wXKe_?&J5r@w2<+=r!7a#lu%exVx1 z$AIgbQsp!;ybZ&L>(E`mU#Yy9tt3zO=v*N|0M}=~UVwUK7&YdVBG>T}Qs6|VRMxgd z_04;M>8OrwHAi&6z%OgMU)rw7v~nD_D>+g9AnCJBR~)xnr~RT-rR0Q}VK&r`HrA_m zyKH*kMA#l2bh3x3UJ4<~5RNYPmxD>2dt)P9MIRVaCjtzHE7f&Rj*^t62;Jmv%NHGP zBRh2%y)Yb#DHafaIP_~yl?LfmM0-gfnZv&He!+1;YOK3yvEif z$4@Ys(=-IKYUDOu+`vK5*HpZiMzV|WhrhCuzvr%v2`IwkcGv!ZHnxTMsfs7Wdlj%I zjIRq|+_~$Et}WpWl#n7>zG9#CHOi_x1uFeLcqF|2{ngh5cA#cjjie(yz@zlFQ&fjA z$D)jp{(f692nVPi7GI2fmjT5 z>fh%q46?^bZ!~pi)Z(0cKx-mqV%58(ay)vtudrmRdLMrErdD3)=r zW0l}Z80j)P!S+%$Ng)3?yfmenuGtS1>)}p5ZZ!)S=5EP4xj#3hS zaE=R%g-5-hYEm+%=Qdl<3&=Y*MQA1IUurI{j;^=sFjFVh$7@zWJCrUF>P_;O9P-&* z<}|O5Us6_zq9-!tMRW_{yl%0nkn=W`p8ey3?sppNGZ_b|27_V4=>G2(NIR(zPv4W} zss5|>Uyw@!LhSE&4JP168uuO)Y!39n671eRzq?Z}G}0j92r!EP zi*3alcs^CG;_H;{-*MeNjipvA`@Hxsbv z4Vmt@O;8G@hiJ;dkp_S3NCn^aA#e*hh%Lv8vkqX4c zfasWUwJ@Ctk^IBL`HWWhZj`evss-a`T#RF*Nkd?f?onYCU$m+ES!iu&RDO<77Qv(I zGXi!&DAT)#oWAITdILa8b}#)DOBq=Y-BoB{5-*m-zJx?TSr3y0d8PIudg>a!VfFLr zC-@;3xk+=K`pLsYYkfBIuyFC2#Oo@Tim+222i<<*I7W@P%)R_sU&_&jX^P1tsUhqz zE<}mB}$kBXqJRY#t7kjc;5R4 z9QJVem`l-RZU$=RwYvr1h+T=R0=^Zv%M-7$E3EWT~`uq`(rRJ~$C`haQWmZtbUlncv{ScmPD`9I&ULm7jiVMe^zVX*Q6^>jL?JLIXPz~X}wN2b$nK7u4 z5$tU0QH)EMSRQn}&60L3D)wV!WpjH{Dy9B(=E#3b!pT9C{kKthNITmmL6{2WZJp6~ z$;x>eBBD7-F2+SLcl|bvY#eq&cI}8%?+}1%(TJ7LWvYiRBIdRe^E5?RNFJZRoylK# z@wH);`ZA||%pJxyw%3hEkCUI_5mq|V7Nex*kZ0w#hkob14tH0jU|a=CyniYJDu$lz zcCOH_b`=>_dsO$acRdrnT705u~3#GK6KFT7L&DONGu(j%W%7BMqZKL1QHJL_Zv zYqOlQrkJPYK>E|-#relwy3N0;WY(4(D~d6x0OR#AK{s~!I3+hVro(HrqADsnHnwnv z*?rLa{MKD%&w~1}*{KJ|)mqmWqT#5U#otmy_G*B!gjYbEW&3>H1(c_HJSg3W)5Q&IJT2WWyW(%y^NMWm30mwrfNOFC=8uM6_Tm$j~ zqd0x!HaLfybb#HhY;;5p!u3guZPxv5)wp{t5H>2L?+WKr^gqZrHA@s_B8EBgIvaIE zE+ahJAb+pA>x>fNw=lQeOsGKqWDu_C59D2u$3|Gn?L|(L8$%XD*l$^8V7!3Y1O7)V z=)#afii(%IAq9JnZux!BkMdE|wuWRUv2wvqxbyh)tda>_>&yqzyQL|W+=i4)Ee1SO z4V(tBsris7^44$8#<^+^sSWdWOlF}SPpF!plqna6sAr(k?dy+1{iZMF6IReHI!ZB% zUwXw*-=92?$v}&kX3M9=NGY^QH8>@tA}>8)9>8f%;1>%C2upis$8(QUnCPXHS&GGhYp#&H(y~|*)!-bPbZM#Il@~dW2ITIhVJk? zk(OpN(jKF#WQ2Q0H`*Fs%BAJ(VoQ6V52ao3y%?kA#F&pu$x(40)3W%+q?;3=OK>aG z>Bmv?cT2Z(Bd4E} z=?t(m%yF8ivWnB_&2rVPdb(VQ{nO`jIE{w-j|CJ)H$sbmouB3#68z9VsS;T+BZ*WO z(nx~D;-!gA1J@kNrxijx%-ElPgFIYfaB-Vn#t}Q`$mRsDiP{_E{Ah*Eb@5NCOcYDM zSx#SqTv141Q;`;lX3fKi$F(tO2`qXHx;lnGsB)6Z!%HCK{g^>|z%QMc`%Hmg_EE0p zmR`(79bWU;hqpx_U+3ss6Mu8_l70`FI1+Xlui(6i2vYAkn66jCINYoD-~C861VupI zqw3a*K`sVBPr#2YC*)BCwDu1~AogKZN7s%EHC0mv{sm?PdX7u1AfUv5-G_5|UFqaN z^P194J??_9BlnjayZU@MmnPMz^bkP>D$hlTF8kTT-jU8KD`jc8=qVrM(-ub8$t(V9 zc$G_)97m#y9?Dj^lyfn^Q*h5oPg9rDNTz^t#m>j~e8bh)SG|qB(t~9>Tlb1@KAWzK znjn_aDQS=fRRp{?p5&^qU?c8wPEDkbv{4ZKH0DfDf;h zwlgOUTpOVpa@`uSC+r$Qf3UyVUeDCL4nHgHgV^;~5My8+mP?Si;>>Gt_&0u>AK71!UW2#~Nr-Tj7OW1kxqo1T9hVI{j4xtE`y@&Kq%2;^ z+}LV7BbcOZo><3I5fWYc0G%#CuC)8$$z(wEHC19<7W}-k1B#y{n+kI0pUX-9w+isz zvI1B^bBW}3DZq`S7{%Y-Z}8ZAA?mTBFACkU6&>Fv>@ByqY|gdAkv?F=ALTWghgZUA zc$7|%uQo9yuVx`ktX6?B`ss0(Z>dKl@`Yo_ANY+*4iDcmBEP z-uREKulhN1VKg5hIouRB$*%JH9tZvNr;o!$pHbh4|8fz<*?SM0N(UVNAYw@O#oTLj zqo1OzMwEtTRK}xtUP_zP(q#Gh7}o#1?JN}jMF|iz*ap4A1SVrlwqcGwUTe-6pb3K5 zBu{%kytYmI@QQLAw`yPz&*G8ON%J&Lu-HL=sdR33ah8?iRO*c=qtshbM)6*|n6-(t zdNpNrGkV=*Z)<(;KcVlI9y4Zy1S_kY$K3pK9=yi<0f?OO@d@4b{M8*W<>P_QJLIsx z&tp&r3~C=8edwR9tk3#6AAATwvX?0Xk0opb7@~1cDtyPbN}vl$Q0mH?uEFg0c=$@;x(!3yA4N5q9s1+I5*uQ<6AvA zN!!O&7yjoNSHW^Xq5*h@ndw+1SLKfy+f6%*g<9-e`fzMyVdy7m2Ku@t)SHDtH3 z;Zgay+O@y0Ey)R51j`EEa1Bv>oP1s@5){g+5DRXVG$_C7d90Gv6S{YfOr<^!n?e;W z8Ny2oFU^k?peeA)?z)f5Zv@%i!%)HBiT?EoM2$$aLcmr(Rfu9HpGbr8?2qIM|2Dv; z(e^ajI(>QeNW%0guMEN0>v(i?ZGBO0&=GYvF}McBc1P{p+y7Ah9dWq(k=D#RFGoH- zFyuSpt)G6yE7vct^11gq%O#?xpIQGn+E?VgXMa`r+J87lLM%UP(gtSSoq-;c`ev?r z8wMR5_uHP2v;hEC3J_LYmP5Vh|8s{Tqy=nPNEaZLDyNC@-<%FX2;S^*T1@|b zd3q~5WSu)k4iQ`hTUU@0`b#gl_Fkchn`zVD^&pf55O351Sapcjom;?RK9ks;-s^o> z4@nk^Mvs!i`bRy6w|KnfS}jhRKYslc$s)n*aWud5+I%E+Z9VRxDb?_C`Lz}>+2ewI zkSH5PQg96$563xIrR4Lq|E42dIihV9q>wX;`{o|jm-8Ak^wci?&8O-iUC-LEHMu=? zI^e~~joDzslo$ke!GpW3S=H3{JTS|u_WmZZ z-2_pTzg(vuu3c|BdA(6N(tOf`Jb*C`zSjQGKXNszPn649Qthx1gPQfa9<(3awRwl9 z;I3Nx2hqRo{d;HVyXnJx2&>C>qvX^PZMB%Txp5(E&@CG^bi56wgf3f(!V+cm=z9Eq z@PIXtBcN(XRK9tr|3#*9|3AmtX7_p`^7Rw_vbT2nZ`%4xyvF)V%zreOyc%2Tn4#e| zn5=KGY;XSDq9A_COc_MMOr}7|L=i;62qv4Yhg*~8^ohpXlQw#646PMncoAEg`p|== zDT`_+IY!~GiZ+Yz8b=z80F&$ZLlh`W`r-j7r+*fKqLqlC!rxRX-|1ek*dHs1hQ+_7 z4|5mY!Tossdgf#Um+CkBxAKT5ebywcl3T+-)Aa5hp`}mlg7Khj!+b`Vkyxgv>!|U- z%^-eES#7o$k0r{^?M6-$=f4?i`>rANX&6l!jc%%snm%IJnBLveV`pP_V`olgakt8_ z@{Nw@da|3W(e)ts*HzcstUMeo6|Gj`qA6qp%sG$${?|Eg`objX#kb&!YM6LSjz^Mms*9?P_wV>92nbzyE+88UFlfWH~<4UdxAMlt{6Q5v9EplP+~8V)@<=4P2SR{NRp7e zhE}2xBy|;)N@N65`Ua|^Y1|e(u*Ba%)GaSAfm^N!`o2EgM!N^y$hYZ`@fr9gNN;36 zG%nPkvhil4@`GZ3I%#mrQvV=vF9)nNF<^;m@rn^dHk1)7iZ?pHr%bhdpuahI)v@}o z6tq`I*|(|F^|6ug*-X^zTRVFq3zh^pi9(RmmDNV93p4=yOU&yl@jtR zUnL6q#)u}XsfH0HHYJgnD6!(IhRGmJMf=J5+qr$4Pt z@=<_K;&re%DpjI9hAIXVIpGh^hCPNM0wThPBZEr6xRnuc~ipE_- z0(OWtnU)U`_fP_HxIHVIS;X?3u{rz^@f|8~;+8fGo*jt;peJtkruzB#Sd*DGLamy1 z49gr12-wW_$Uu#|6tfXr#9JD&QGi_^M+S-H+(tE%nBQr%PPZm&e_KBO{w3*0jV~xs zU(jW=8H7fo+ZW=^n03Dw@-Ah7j89?{_0~!rAF6Qhq+}L_mPl5OLJ^_qBr4*%O||BL zB%5Cc3iMKX;QKU)q5Fk)MbcuTgUYh*x9)R6etE}Axn72aP6PclyVW99f!UjF@^uTj z#ZbPE#qj}tHk9l8bIjmLF_JU#(4@*N&VP8T;NjuP;rhB*;1;vt$puQu>xs6EuD6q7 zY6h{kX?ZVOsq(y?LHAW9?3ikjuzOnZm%^>1!Ue847v%9STNQWPG5Q0ea!Y?eO*o!9 zM|@<@?fr?Fu5Ze2@sQ1*Q;*fkk-&Uk$>qL5HxRYrCn}apL@qWJXk@zfMYmccW8`yB z%-9p&AMB;dW+qBC*tvd@cg=kj7)`BImfiY|iuE8VT2IL__S=YEE%a5ZFS4sAlc1TQ z*3pzrr@+|8ewXJK?MXx4C?~wT{s31{FB30@mfl2BPo=TIjy9Fh$Ulh=e~ykpkW`7P zBT2+XO?B(QYs4)Hh#uWChgxwRE`QzvOb!0}A})$ILT#f<(Z@N~!HW^Ss7`&x?-$@W zp4&5(?LyO^ewwHV^oP@*4w#?(J=Q5+%p$`q|JBa*wwC78j{M6kz9l;jF{7F;H3D5# zD3@5Pc*XNZ`@t1Kp4b#mVIES+NYQX`X5~t+bGh||E62@4NN!~+9YA$bks#oDy?6#h z*)_%abFsn;;=M$MGUQ0}7r*i`IGN03c<#MJr?px{ zE=#H9<_DwSPzoj%AOY;H7W5EXKVCUV4>8cb0Y(ykOh`y50pLZ6Rae~WWBdrAL9H|? zUcVAlNtacsa2u_=&iE02DULtT(2UX7h*t0FfbLm6Af3#b-kl3>(I1%8ta6?b$9h^p zO&;{zWMN;OT#ncK<7o%e3g0Ja6t*i$LvFADtR&U411Z0g$d0n1#t00%Bp+<+jHS+n zQo|ZU(cX6XlcLIDHrgYovl9aHhtt*b8EoMna!X54L*WdUA?e|PF^21cfdN%U0~o&S zX=mx#uK{t+@dI!A%#edhpr^hJ!iOEPKRij5*zow4os@RmQA+;T&eZ;-#U8=t{Cnd-O{! zDOiN5tZ$Fd^JIINUPQv4IMWR1`I68uK?xSL}Ai5QfX}$dd zL)(2EuPhB|(<&GQIkO2{U0q>)b_4S@f=bEskhvq9C2DsA40E~xmIah5QwMrW$sgBF zj?hP*vfJU`M?|-rc-@i4(hI5`t9}!ZYM)zvYXF)-s48zbnBD&q^JSPk%HQSL#G5Ts znO`$t(IlO~v)Ebc{VF?^);?ALi*Tgz!7PMyhVNJ6+pgUB>@=Z<#Tz5mcvx3K5zdAq zilM+zL1r02zCIGZdm!RW;^4Dm>ObsrSQ}D4L#YhD3N#|4E{*$tG^M67C>=wW4zJq{ z_yg{8$KuWnoZyL+qI9?Y8{<%EE|{eXwmV<;U=A={Wc=vUsc1*Cu(6EV%LUcz*2@Xi z{^C3<8Wn|hg0{&AeZq1gF*nsu$|K;1{4nb3`?V_y2U$a$A}92PiedwM4z9zydkc#; zXtN}pLz5B9BoaXvDmAAM+*;`EnRAX}cFXqY(shz5waE=Y@gmSNivzPQs@!A!gA|Lh6eg{t=L z-&^BP9l18zZf}|~GxP4J(t|^*Z%*pIZYUSziS@{z3ag3SPXg4wfBY`@mQ|8Tl|{1p zUB3Tl%qB6rm1r+8?de|F=};#t^BODT<8h5JbC@sK)UUFzuVgJ}&Qx=}MZOU3L&*J6 z;G{D*%zBuSz0mQ&ARWCe_F4OBnnNyPDLz-g$f% zB{gzuM7}-NEZdJVL;+^&x9wbV6A*m3=d?R>?&$FT*d#jQ_rC}RmX)74bmck0M#gd{ zcmJZco9i7mPeppwJh>y;jH%@*WR)&qpBE*KuwWI{N z@7gN<))0r<3iHr#8{ZBr0w(4_BO&C2xXV41tFh2IN1^!s@s6Phwk2;Bzn-~pxK0B7uKu{!8#O~w&{C|Fz@LDRQP1nQUffZ>h0x7s2_!S#^pAlbJX1;*)cDAS)J#LjjF#NmJ2m^lF8_7jSzlo zSQf6ZnSSWZr>*xp+)X{T`Sp^S!3s0CcvmE^w^`1@(4g2!S$A-s4pquuh}K(9i_ zJ;vdATzB{-(nT{1krt~~DjM!k6(8%dHuUCbGs)uuhw2akdqfJ#hye7?_Z4#xbASxY zq1Dh#zke<{?uh$Q#oJMB^%9m$6%rP4uM+TOh5VvFkhYJTr&7L%>dfbzA)#Qdc9 zXW>{3h1#|q@!4~86ey&USEfBfS=7~JyJ&dd==&LxQHRC7cMzGsS+Dea{5qlLW|Y(n zW|*p52TFp~n8bax$~6vQ;xK6ceZNwS=N9X3!ULK@XN1hmjt{jC_P$Y7lmc?4jJhi% zN+!jo(w7A$oN-r;>d@`Ycok}f=^!@t=NiG#w36&5RvlOZ^1u=x9YS{xzeMzuMUyCf zBIDXOvbiVxeJCNv(WOqDak3tHxV@w${dSNbpw+#f>d*$r=(jh4LL*`YOYaU_MO!QV zz3-(%3szT_%gkxtno9@TZKUVw!89oZ$ENXQv0J%tT8 z9Bop|D7gMX7Yz^SBUaqq3^()Hw(rbvU{ED#75q^wq&k=WLzOhQVFe$f6!hV~LCasX z5jF9_>mAmz{o8x;wublAVi>nOM1}TnDkl_)B)hUths;%%#l+Pb`)UBP#s@pu=qiko zpxC}&iknj)HEPxFd<`$>G(9I_4)>l&ytxD=S0qOW#tzV;l zWPGL$WsTLp4(ZTNo4mhyt6Z_D-`R8?PlcaIAGz7;g3M$O{0i*$0-z%zI9tQjC2=mt z>@)bf3@bg8ROAy-!ns?X_O7=*SN(cUENnxBW2d^E*~hVGH+$&z_qyrS#`Zxe-X~{v zeL972!8!I9lW!k%K{H@goFAhx>l`N zivjrGqAFOp`oT(APl9wKa|ruPsKHa!qi1d>1pc%Je8^N`NCU>oE@cbaxh%R3H(SE% zETf0*=rR3-jUlm)_Q`ZCJ<`!{_oRH)X3Ss{YuPQ+J|o$cm^E}N#XVM(wQ&bydB^3A z2ou9DQ+j7GXHOb{JBo;(NW+SaiZyf)MI((o=URuZ^_6<{{MJVkMq49Pj7n76d44#b znlZA;RDV4M^_Ruf zkTby?PO4?iv3X^_Mdo5y!vCPkmT2PsHp*I76K;F4rZ;mvDErmqMTAO#L3JeB2)BH$lZlq zqf{XiJB&hmg&fW%F)LN6a)#=z+;Sg416{o~ z-;zjOsr1P`TSKH}4$1ub({1~U8mzcNR^p5H!d-6W`u@yyCJXPc!SD@N)P%#24G@lq z8X6Z3-sXw`XVZS6qG1<}=ohf4C~}C;t#xq`u4(tRNSDZ^+njDHr6!~^%PZoZfTS>4T#!KBUE?gE}YsY%8zb7Gc zIHo3?oKhhixX?`x@+3e^tkI6yz0q#hf<;{Y* zA(l)iQev%!HD>6)$O=;<8Zz;}gMGfnE_GOA?smElE(j?#x3wHc(1+VmH}5D%)HP(K zO)Eq+mI;L~mk?t0tY={Rp?nYKLR<20(jG{zxP@8%==51!kE`Hi;I)b?LIpyZxF{|& zg-*Q!*XRQbNNrqK@k?t+ea79D6UKsVZH@lQ`{&~2+JkX79gfSQ?P<25)1q)>|375S znN}Rf+5R-FQg5Pw@`Ocw6hOq6H@eMFg@ZofF7Eh%-SyZim&&vqzpQRl`aif%`u`+K zjL$FjWzKS-B~y+X%xC|8J%&a|{bnmbaM^DR#qOzQKC6P#8@T)tP>io*lJXTjnrCU8 zI6h_IZG81gDhOb?_=Nqs0eIO;HoXmFj{$8@6sr~98egm$C)T~~d`mTyh!ivG!c2I0 zave-ACuM0wm06X(RE^Y1_xLB^X=6dsODjb72c5=Z3OmMUIZqaV7@`$d+-0(9uDs9+ z@xaVod-Y#U0}A7|$@8L&*3tQjq$L`3HjHheGLgFDFOIWq&!E)V+j9LQ1#=RQ;Y27U zog(HP$)ytCu@$nuATvK9bMaMQ4+R1?)+W+_^uFsAI!}OxDnB0}LT*nJnTcp4F|&!x ze5rqoE`zZC-R7x~N}Lq$x=LVVD3q+|wrtC15UeGFlo=sJERdg+c>4Kr`ylVI=xdKB z9?DpH-H4$1Gw|V&Pku>7#D~pafYz?O>E-<6HXuA_ zv9ttgwmeq7_|2gJ`FS(ksbj$iaNgmVGn}_5?+mV;$U77TIS~4;!c|j%I8VU z#UrXNZ{%CuefZ})>$qHtf>2mZE^SDfBK>OR;m3HW{iW}w{L`kg05Y~eL(P{4&rtIL zyMD~g#Uv_Z7|q+YHxDhfc}P@B`>ZY9e;AYsk~sD6i{I)otl??c>g4J;^;{cciYwA* z?lmO_IEz;%2KMjymFfP*2{|-p3>CD9-Uu47(Rba8OgpYkJ%t^vpYrU4)g()+O3KjO zRm(xM5z*m2w6V3o_SHe>f3&E<0xD2e7!^ zVdYsUK{#X>3Jv2w*W2%83zUJ_%&-5s94n~AdUQPm_O_`8YoLP3BJr=){WOn|KeEUFsFBBJX~ zIaO!Aybclj=QgRUuo1Qs5-)5gWsj0Z)|mcg>!wj-tO5Z7Vze&4iij{f#aEq$3vPj1 z?ry+CZLj3n_w;!sOS)!iJVL2!A|7K(nAp$aixL&wM3zWYND)~{1cS)l2Y$-0O8&+E zU4Bj-cA&)$Pq^cn>Dmzuk!txHgmOjq$BE2*vsFoC=F)spAd#^9VZeN6!->r^&<9a4 z;S&7lf5i^lCJ%PJ1OY8TJ29YD@QZjjI7S4|=%FJn&alzfTNKQtsXNWt9CbV}PW4%< zO_k5Ie{x`I4i6She&`fI3{DtiLx}Hsl`wqvu4a+D=0A0d42QH_dg!ADsT^tinAK&D z-=ZJR=HD8PlV^1mr5k4^E>b|nh`4PXe5=Ic|0&2tw&{wk03ChQuSy@nT2|kp45gp7 zQ^Bq0jKUz!M&w|XT?8>;B)NY{p<0+1d6cj_fU4_*NF;i0r=t|e0OrJ zXxTbKsm&@U6(%@KCNE3wg}=R(_F)$p7^>ESeat8 za>=_vXYewp0epPH;DV<68s=`4d<2USGhV;uwZ+ytQ4iVWqzu;uey0CBzcM$7KJb9B zG27BH_wzGr9=Tj*TUFqUy(Tk#_|WwrL^gyXDUn1NWyRYQ_3K&k?mgm3Y6It?_cczg z!Umlx22Z_{L?4DTseIXbHE?1$K(x?VbFO5G)I6%DFmiit@kY+j4WfkL!SPeMg1@6V zry4ZW$#Dnv1)uf5KRr^+Z5l|)%oRiS3tAn(Xs6C$=P-LB`1odwZmiMQPsViXGR zXQr3Vo3Yckntsu#m~}Ueo~8gcbboL||JXs+hK@>H8f9^ac}+###E*&Io;%C)03eZu z!EF*1HlgI57wUB8#{ZKrLu^J?Gj-PF11#=;Th9Mq-F-igsOtFcntIKC-1s?MG}{4# z3ix6rW=Iby8Lxm36>+FUQ zm)o=6F1@?CF&_m>J|J*`1&!9YTGni>>IH(4a#6gyJpt(84jSMg_5)Qu4CwY7_^A=2|y$F_X)lKD1GL_!bWF4l{5eN%_fCQFS-xW!FOv8Ia0I zQU-Q!G>_n7eDH+Yuy>pIutoPWj*T22{~TzO*?y-USpQ>ss;1lZU|P5gC=b6ZvvH2y z=K{D;z}7z;?Cu3Aq*f02MiBEn_;qnb=^b1RfNQV86(pZl+b9YCHvT&jGm4Ey;3m{_ z63as7vARm^sh;V+2lSLEEr0E#>~~fp5m?VnPY3Y#-E-lkP%u!_=Y$vsB|ay8w5VVbnZj+ja4_%Xh2IZM}ELnlgA$eR{bGfARG5GE54W?e3t*NIDPr-l^5|UO- zzujdDMq)c2en3dU@gae51l!+Q_~*vGdbgt~z;nPbB30n$f5&h9e`LkG5W=EoBenFQ z0!$D~sGd3L*l51Sdy%A7G?MREFbuG?VyuCh>D+C&=g|rpe{?zlJM}zw-vO9Kv(rAO z%6r?q{`>@Qq{o!|YzxcDhy`(T9`i=BsyxE5I5iRMz}XDA$k!IiTeuicze+rxvmGp_ zHG|u{^*jBq(NdlRUY8mFb@`8fE(c93*rTD2u}&3XsRbh>Us}(U0N7!3Lm>0|FB~X8 zya-3NxT9rGk6xpvJcCX*M^4T9w-*LrdqEaXSkF|`?ZwO)QW8MJR4}aK+)NIrg9o`d z{FU`CGg#S3+~?HJ$rg?4dEKdLllQ}-*$bG(WwndlWV{d&}N#Oen4o6K8bvwp--+dqk^8(?v8uMKz3QvmomUf8L%nIqiz}XAD4s1I(6N86x`wO&O8t zy;+iXb5{l;_S)Gnhk`%}jWm;lzo*wV5W<6(-J{2OO}USJq2> zpWCGNt6!aQV{d@ya3D(0*p9GxX9`HQ2BebzLn<*-yy=tF!J=ogf^%IS)|38eUI&*p zQpHSLXJiFl4SS0mLkQi#sQYL1?DO2FbiH{@Og> zP2ai+G&gm(MBfcQ-Getrm@r;;v$*#UiG=|+X?`C1ps*#EdUM3lca5YIHog|_P?$#=NI?gdrh#J^1HNU z8~9SIJ$xKB^XmUamo$+s@QrT@c(Hy_h2_7V5(Xxw^?HEZ<$pxz|Igx-O1?9BuBczD z<60Rj=h)u|2ooi(nXfBJcHe6C%=Gbo)r2!RN$J2k0GFoUgIMCdKwe$+s}(-#GGa%P znuR!{=rp`E8-hJ8Dh+tjfy9m_>7&Pl85Uue>Et8dUqU0GkKqf7vvQo?E6XFrl-qxP zDKi)77Ul04`j|}1b|Gg28Ja&(O5Z;C8ju2Iu@^IxIX;{wG*;J7AMC%s%zVYYe!EAa zUdCvvfV)EG*ye5PExj0M%5EjX`u3sk?T7vqYtsG|zgoi6p6neLGTfN^<8wOgyZsBW zIOd6m&jb7Q{- zF)R7&uA9ZeY|GnOV3K1aPMM=GGW0g#w?F1+NDU{!#9#iS@XGgrOZVV3E{Q(w&S9whQGP|_qR1zN_f zA3H!afDDVBNd%v+((X6NOP&0$Y0rLx8N{^2mLX!4v6>r|&`L*qO}BCd=hDr1lNV@N zZkLHFCktz9b}((tVCaod+(l-#&v{m({(WVY-v^qMAQkUligNo`Iga&5q=5+CLm}bi zx{%&W{sB#bI{}w_9d%n$+}?`ssz2ffL-~NC-8nK}$@YbwJnr^)!(}bfstU!i9UpFG zQn561ZN)2)S>N*`!*|uDIcpg?URD}QVN#I?dJAL+DHXqv)$9%Qf1bIiW(F({>6uCO zwH)F1XQ0bPz>Ix};{W#M64ch`R+xwE)6L=$E(6Kcqs{Ru@zJK-+)IrnX>Ym1IW!0H zU5gFWX4Ho!x^$kCt_q$4-Jt$9PP|9Nar9I8rQA18gSBitEm{>tcEyCQUI+K0e(7Kr zwI#{#+fbPula@F=53zCDy0}-W(RAxZQ%yZo$ehO0`BjHiYQMArf-r1*f?XUx0k2lm z*{kV09JduXR)ckAEEgiB+Yg`2Om%kCYTW$bpVHaeNRJabz1a3ANao0ba4tr}ru#Y! z{(-1-;LyfJ!DO{XhdkyK-hK{o^7m_}_-cWbeW>F^Rnf+N$}?lM!%k|cn(Q~M7*zEJ z!V`PVmmqXd%VMZD6|nvoFUyEHDm${>XqIXo_0h&NG7*~CSCRAW6vm25DPI_1o9tT!y7 zCB#`qDhmZDVg5^O^jrwx)dLf2L_z9=xMFLwd!PR{7P)6Qz3!o764^6lH}b)2hQ`Tl zZ2j)RnyxjX;&g67`?#T8y8gio6TYtDxK~kE&oHV-b$O5wg1j6vCe!jySJUta==J?n zL7d{ePcTZ#370X=-sovJDccfnb{)TVe?V3ay)6|0I_mZytK&q-5wM}{))9{(e})Q* ziny<-wu$u1YSY>x)JPG8jvrOCX3bbdNvIWjwY3_=j4f&f zv56qYoA!C$bDlrn^PZDGa=z#Oe(&pE*L~gBeSJQc9;}a?O~~vpcA-(m6-0uFPn<7X zRbzL#1mv|I&}O);TcDT5MR9GP+dxRT(M5JXb#Xf3)HP+4Nk-!-r=Ai=Ilkfpjg9e| z^P%SS;rxqcVIh%Em|^*W;lhDe`0^j#jg^Nz>e%Nmt)fgWR-+VK*CBW<6SKJcACWY_ zI_wm=SJwbFiu;by`C&e|XJsuoX>H?pxvpVh$@fsAN~%)ecy+FZ?KS?JBOQ70;Q2_o zYH2g*`T^u+s=_B%pXn!ejIzAnHMi#Nys(T=S>*JAt0;CX^L=>_#Y5(Jzk7<`xI?km z)IYW%Iw#)jJ1m|KSfwedmFZRNczXj>Q?XziID#K`&tk7gi2EhBpEmirVlepHVZDu1 zy}RkMWX8xK-T>Q=xU4LS&Qnt%TrBzp_kbpYog!4%A{rnG7z2muNa4zwTZCvD&b-oN-Ya}aX7~LOuK;%_UkmydmX-K@KE%Na)9HcUSAhQ87 z@=-G-y<~l8)m+7Pl8(fj+%Z9Yu86$7nttO{m+DW0^H1&6#FhNeqJtn0Gn+xD1yeqi z{+BBe_=$1crz~QD74Q9`pG!1$l~bq2>XO0nyKPy{X+Bdubt$*znnDd2{v4t0IM_QT zX-*J|6X64?I`ZMCeeK2@<=m9fF6u)Yc@;hwPgsM6U4LPjl|zlQl0OoxD12@=rN|Q` zD?2#Y_xv5Y`_jzq+274Dz~KN!XCko*3UZ&#?muiMAVK(D(3+s68g`qL-C+wEA#Aq` z7v3B$HpgzogG~5!Gw~$07H2cP36YN)XXmX%XRV9s+V|6X{e>^g+C%ZRPc>~bw5w2_ zy0$CLD#o?1O%_TYj~6_SUp8Botk+y`-q9&ips_4S{INi2E#W$^!Y3>6k4J@TS{q_riKrD5hoiXdf35@?U#BTAN6|k(P1YWVf9I94ww8gHo3kl;Ig9m zuc6iQ2A0Y45vOb~1SO6x;v0B-wkVt#Hp3*NQ?|Lq-7NJgT?FIvF&T~5-ufe82{ZVnF8B>-ny_UR)Pw*k(MdNM>5~$?^J!*84O_)C?q4bJ z_=jDi3;JD@*B_fWzm?`^zuKfM0>s&gr_kS{(TnzZll3CVdpQSaiMu2+(*;eIT!X zMCoiGrGe8bdE3>?;!V!i>HcS{_X3yYF}>k&_hiugafY^!01gkcDcF_T-w@oGci7aK zetAQ(_3{1637Y;T2%0yBV23_Q#$UR0hyEq*`P3u5t>+HgDpJu`8#}VLIOS`wZfVMd z6^o7zQ|t1F!8;vDBBgA2UsCOd9abphn5es9lXr_=DNyThW%9S^!jOO2%9iPnAV7-V z4S=<4ny$6i5fV9ZG@LXV)?+F*(5w?rdL31Eh^Z41Yh58sh}aCOVKJ=Bq2kbIPXh&Y zNrqoe0l%J-eU1?*8*69hQE_4p z0WaICJQ82^!=FK46$l3w|I(skPgA*;f2_`O?=I*had(J3rmi*mK04RXk&RA;)nc5q zqt3hB;MS;suGGO-P5+wvfY(%^GS)6RHHIYtx_M3>Qy(ES;F2*bi(?8UoV54eN0jtG zc8D$)sox2{A@`pZ^VLtm(M$`42R%){z8S~JGzJI75MJ!@q(0IDo3eWr z*CuOFCwO-JqYRNg$#tS0TY$)_>5sJysr4A$rs`DW1!*)&&BSdP*!1)rzn#k$@$%?` z25Y^orj_bYr_{=>RGK$&roZ2_(EBU-Rb=V!$4s@K?BcC>GAe=&&Sad~i9>m={m$tJ zn2{IBio#)ouB8)uE-GeU>5BcqC|*;xy?-WAyN4#8Io2B ziKRSe=$B`xfKRq}frNK=+f*WWKA4oWB#1A}gdG*%8pyN0uam`unB<)hmA6Bje*Khm zRmwA`X3T;8th+2q8RsYn(}jwm-tN#=ri*= zi&(LDBrXlqwg_mIAMRwuwVDwV_eE^XP82_ixfJarmz?^`-^A*^GGnH(fHyVWk1M%o zHi6D7PL>BErCef@oGBv$;MqfOK#NEuXSV;Ii-?;-@wb3j<4whf@NeYk{*M zw1Bq}4UR>-O6a6D6}!8+6V;d_MSjUDe%ePZDl11EA7)COn9SXa;!$$Zv<(`oev}Bf zhbG48y&pr(JPyp!nE;`(%i|^PB4d-E>}nbnORcFSSEkYZD6B=nJ_NL{1YHqL`ia>$ zP*bqhYg1K14SA#qM-E~n1wO8hbV(U)WZ<1g4(IuuJil9NPFD(jOF~13sR`ops{C$Q zwLW0TcXF5BQ2vgV3B%ncj(jo>z7K&-{um3^>nH3YT!{7iOj}}<#9q=#dU_%kC0FYa zRm|F>K6+osYuq=b#;JNwCs3z+VR>?$lDnsq;SCx++z%=V$|31B+}$FM;B-Kwn=`Ev2n_()e;1HRW$P~{GLU(s}I1Pc5@t_P7xPI6G8!OWwYJU&MtOOn`xjTuL z`Q#M!CV$KfN+d4@hAZ|T3=E6}Bn%&vA_P#G)ngeNYM6kkDCWscTq1 zS(lDJyBKAY{gVuJtpx|j#4k(YtoNK)X_U@~WZk)n`ZW+?e);~P5d}6*7Fw%4e7d_? zmZu@wAN=z3N;I+o8WCIydGxfy=*aTe1~=?O>Zw{gf^7#vr4UCUtO9%rzl92Uwk zC0;f1EQ5ug1{#1+L9qb5=G4M{}Yzz$TVc_9MkVQ^6KagFnkt%WkpL0>Ro;zSTI*kyq ziwbBz6v#MC6PbHjd>-X(ZJFw7Nflc+PBT9Dkhr%$ZcD(qRfgK+ms~f?m0yKl-r%*c zKCe%H+SA{D-Ey_fk2e*C+~I8)<%f zoqmKlp*v4YHO$x4A@`Ox{9)<8e&*}0y0#Q@py%BT{!uvT-aTwMj7Fpl@Wi#$ENNS( zjGk>5*Ijw`b%R@edX^{1VJV_6uHx{!jqoi|ntA}Z*5nCC9hbwDk}?h^*a7GO1$$oO zF^!M%!?C();Z{+`Yt)8NXa)*jH7hFl)m0w&tKG35BW{+hfybErkYD4fH0yzJ=6=8f zO9uH%Bc=<)vZ;~9@6fOa(lZ8UHes7I{bFn4Aj^-tSGs- z(-dOA<(c9ufZy4D`~!DbJc{1YF2Mh|{LGRwwN#QAq4}btHo6uOtr(1Ar<%t(j?X&u z;fmd1CAgc|^0Aew3&L`{#|21o*}}fSPY2$SkHOk>F>|Judj08sZS#NdX+n85L3o1K zY4rFqo$p_tq`u@VjPiQhkYA!r%Q)&><>zd|Emxm?|2I}zBn>WfIA8iwOF7SH#8ky% z!-in=T+{kkMG9Eh{$_(e=kqY@Nw{d{lO!Tx@N$(g{Nh(t-JJ6}1#`B4jxk)-nahJm zZA@HWRCxmsA6zUvJ^(Zad5APovl>4aO&6lPflZXsK2CuLg9qtddBr52d#HtrIhv#a zMxl^`2g96$3hOnwjYoYZ86k7o*W@B9dotH;l1I3DQ_ZGUPJSv2Cf+pqUbA|(#r39V ztFv7F&NMFhURx+Ku>q<0U1v!rf3AfcEqPmWwt8aE!pg>0k{+$NhS56&hR)*Dv4!Qq zP~||W$|7V;bNJB43AYfqA93R7pcF4h!#n{ZL8E_i8UjKL(wwx08mnHM)p;2n3KB_B zEmaWNUXf~}0bJG%jZHLYz612YPc`4|6%M<=D@H{g4xFqj?Qe8C3ve|{vV)|nH{gvT z7lw$aVB?~^r&G=})=ImL(l7*`gw+L`7VI~qB`yse=um%oRIe!>Tlk#x+EIUs7*+6mlI@x=@Y1aFmqNr2^Pj3{&01l#G@VV6L~v`uLlw2wLpA8q2- zu*^C*0=SHlH$(CV{QXIIG>uDxdYzU}@FsmEaHzA2$-^A_jBxWB!E{eqA&O@oA3}*i zwbuYnDw3SAyR69jkFotS64JJs4BB*8h0paPa|HZc>8Kr934P>7Es1_)H3HFY(njMI z1w>qS0qK%!^K?WCaf+pjl?Y^VY{mR5Jh7x1asWr}A1Ce(LNu(72$XW%N*gMiW5?B* zL>VtZxW8RkJ2?awne8)58sO0A9>*YjsZKeyJ_+wUK0T+8<4OTmy!r^Q_6C$6n6_ z6=JlW?7yhdI=mtrc#r2JKY(qH+kbAGE-6G?QL0VZifmr2xrXC(Ap(w75RE#A_qsoq zfyP|ek=^*ZykRt+G&jA9s6XCYcC<1ce~t*KQmJ%PJ^~LT#I-&ib}d#;`f+OwmhCvrFry3Urw!ekeWs^R zZI%La)i1hgmmJtfWzNOdY!MPyqYi}L_N*+KUDTRu*1z2vi?Q}(5WCYKvR&yhyn{2^ ze$*uhx&dYB5lH19)sCXL44Nxe`y?YJWx3YHR=%*o&+EZO z*GzP9*vwEh)ttav(a)MxBqhso`Dk zb$H{{hjJXg!zASsJ=`MzjbKu0YY*oXjbql7dMcYCxMrHgCPzyKr^ZmTFnjpXEJk&O zv#1Oj<)bZRDrKJAUV8a{l3QQ0GGQh>py1&5sV6%@q4thv+^Fb1R9M!Qrj_;;=f>SW z_`!Pz^Bnr&rHYuaJV^73tcet)4LN+FXQfA3?DWI|?znOQW~;QmKolrd@GstZGO7RU z>A`%X^&IX_6UEn3;L+?h>z2Prlfjn-Dh=CLy3?t^^9`>ET+9C$GgYHe{8N4k<3SMk zwN12^6hw>W1~=!Vfz6xu^FQqsH;X9EiTGB)Hp{YcH+A2WP7$?D$?i&VLL@R`;$yGz zM>VVK==Fmd5f<8Q!{2y^H>M26nqZaI=SpcFlSZadFOW(GYLlXuOPUT>Xsi@m`|Swx zf9|?Rc7*dZOCZJcH%e-k@$6Scj@5W6hcHJ-&<^~< z%gVr~^TRBWr;E5dTEEPs1l#hQ;)554JEH>FoxTPz@Y+P*MC2uke!y9bn@qT8>?o1M zr#!kCGN{n8x-Q}vW7>(jc|X;j{C@J=iy!F@yr(JRZ;&Qu%yw&+M3Bm>`+L$kBj!>| z`(-cNKCbV~lH#t5rv14svYoufRB3Mc4iMonYLflPV|+VEPZ2O#arvv(uK9)?;2V0& zLwR7R1+Nwb)%M8~W=2c9lV+Vb_gK#Y)`vIcJuhowv#$|eIXB-H&%xo~t6#`WLmP=K znBNny50LhxY*WMUotK-$uiK|_-56ULD4edW^hF0OxU zA8m>`ZFAfcc~#xkw>*hil!yrdK&9{yENnmts1K|<_Kq#p1$XuQTjnL8_GW@FWPR+^ zf+?h6=rvDAi)BR;E~q4Rf{)O#(z@Z&0lBPo$oAXb7Q5_m*6|Lb@ zJzIXj)s*qBp7FK+5KWxkiC?|{oGHQc;TL|!Lf?J|#$Mt67b5FU$}{nnz0k!FJ))5k zV(DZuBAV>m*&)X8rB?DMV{Bwd)-Fr#Rp(%IxF`}F1gCi0_eP&R+yggl0=J!Vr!+)d zCC_sZfD8+PcbhoT102fx;Q1M;iT4X-lcV{3-T@*y7Pn(;&FZHtLtWFCeKv%VGN{lu?O+fFU2A}k!-lS({FHh2NQNo7jO^FZe4i5C*}is}f)aVAo#w%jIn z`!DE|KOL%OPiLAIQTKyG|E7M${j5})oR2y)s4OP~{xRLQ3`!iU&JJD+EzG6DbkC5o z*BD_i6xXMJ^n%db+z7n_oFr2JME7@PbK z3k;~dn%)Id%6QXEYQxNQx(0G+LgcpQVu>jn%+oCa{s{kd%GOcqOCMwyI6nCrWm2xZ)44y)3Ug zv{iOJU;>l+qN3}1{H!+eoFswz!KTSup>E&SET;|5lyBGTsJuq+U*N_yA*T(tu8VS>W;7j5szIO*lh837~fv=b~@- zD-|Xi!ff>NCDw#wBwge=P?0ST0-mvY-Ieuxl`&b)tJR;C;s}j2hlWmC3aBg|kS-f( z7JPDJthH*s%fzqQa?w_6x+IX<8-II3f?=)^8Spf+vn3F_WT5v#AVJGw*juyww zP+!I72jwM`Ju5!Vu}>Ti7eC){6D5Uc#|c@9mQ3X6-N;4A9{PN$X@PB-y403A^sDe` zzvlf;_HIg=HxJS!c)4Z>8VIXn{^g3oLJHIu@Z~9z5;3F&U~VJ#uoX4LGPxQrx=~QO z{iLx0>4zL=FMN3tm%Y|ODp0>lWaJ(1Hr;+QeYz`;Gm9Rg`_}vRo#DwqA2-q0U*Yw- ze$4PxkHgJ}5~9{=XB4;U@v861AG%eh(|VPe85w>YA(rXYe<;j&(nSVYo$=kOcU@`G zs{dRh&ZiLTE-j(L9A2r?G+0>>I9JGoP_6%+XEUKU72N2+$*`8VuoyW}b0^)?paWzz zD?(353smg{dA~`K%_R}7E1KMlqKj)6Uu8H;F^A*+#kAJVORVDq%41yAs-QpHEbU0KsE|SZP2Y$RC}Z==#rI!Ae}y`$1daf$FU_N%w`CM9 z)-Hb1QKEQ|m1mEO#jbv*ZiG{!$R0r+3hka{#wr%5jZ|}t^p*uFfP+*y;v0Hi1pjZ4 zk<^2oCTYh*`R@5L7wh{5I?(#BJQOQXa9m$ahL4C~33eGOR49gAg`u2z_4N znwd6IYU7I@Oy4-aCR*K*Em!*`tcidN59$UlkBy8$vo>@r=A71~ZpBWW1_`2rUP-9RO}ni}0`({a|-D zvwrrj82mvoxWKw#p2RfEfhH0)NOhIg0gswNnY<$ya{3l|#HP7IV%ly+cC((xC$0xQHRjpst_t3HU|oFr7m(kgsAKs)gAj;pD3ZulWdS9;|S-BurQ1sj=W$ZLGSs% zz7yS5^N*dEL`DYuLt~A_cYc1Edz!yvR^N4+lK7L_0~T*3T*dw(0g~FTlYUyIi5YO1 zwUw=w;Xa?5j5w@eRO-Yg^dTS$n~_j);L7^UGbSg1FS6`tQsDRIFRU3T53Zw3OLNb@ zMr~>%3S3_BD``u`9bTfKPp$$gfVi09j^fTILT3Vdwv@^};1#AdY`<&q3HZyF8a*x^ zmAD!vyGQ9l%jnM|cD-9=1vg|Uho3+3dY2Y--R;R$GS79H8&87pYvs4GjD0S3S*@!k zbLY%2QV7Ap$pG!w8>3!c*W4m!qRGxy)nqq9_Jt|nsEP7KjJFu6r&j(YxAKHkthCa7 zLzom(2)i>hs`l%Ml*>1W;$S252(X>4-H3kv7M?kr<%p>jofx%aIN>G z&yq4m&Spn%lf94Fef2Y8_bC?O`iYKjE-Mv@A^o?frlvoeFC}jt6_}IH{ zCZVQ*fCbk}{?VW}=QBDA-|*AhBJ$%+Mr3`J@&o|;+RW)}4&5*g!HK2L?WsaDZ z`f+z9dI#UoO{o}j_dUpq4+Y3%@8#a*9qyEMs*p+8^S)c16VuqEXISRToI7kicdn`!&Y zt2uYK*$Mb+Z%13U0j42zr)AWRH7QL4P`0jS-4r+$Xx+p%;X1nqOt-B!R$e8uJV;v_S{Dq3l|zataURfw&=R)?wGz@`JHs!QzlEdf`((bY?$5a z15@=QMfv&&3bvHkx+kPych7Xdq{h9Iyblbuk!4s4Y65scko>gXx!;5zA*R#urk zy2O%a1kM#E)Y$XqKI8CemsA~+ENNXd1(mWpw1*}Qeg`TrN7e4t|Mc&s`!l#~g3NU* zdAVvh-1TE^P*9cJ)-xchjJ2PQfNm#DfRTMU9aE05dss=tAqf+Du(z`&vG=7s#O?r^ z@BWf@%YGeA>RXUToiTIYa)u4D_Lb|@a14w)PJYW8R2jnfUz|v{RlTDf#U_PCu88ee zW%k{HPm!b$H&I`Sw8IYhz9ydEbf+<~egeOB8$G))b)0=L( zwsI>R#BhC&$ioH z7{2L&I8}JQ3TsRlQPcwNHuy^N_wS^N$$y7wG!Uj!79Htm7T5B zXkgZ{saKT>1XaFZ7HHV83{0G2cBLK0c-qn5?>@;uanpWb5!(8iNOq&*U*CueJJ$i< zoS{D6yMm421+AuO<5v=VBrH{4HFTM!YLn*Hzf4=YM@4q#M$c~TZ_Nc@xj2dM6!J$d zOqZp-f(4Cc8Zg`uIGAjeBY690@LwFXC=yHNO7o5nAugt&t4dC5x#^N<27k6h7Ypi( z;3Avayz(+v)bQ@YKgfuna+UVb0Z8qnza8?@eN@JTc|dKiZl+>2a0Yu)G*%2$#DJTP zPqOjMnX+SgRmP#u)z&^Qn>nHp(aJ6A~K3i^{#3wP}1W@FVc=!Buu}n>=9RSyd7oUt7w$_ zOhFG_+mEgF>1D~3{J&DOvUo~oO3fC40FpjJHQz+FLn9w2$tRd`!gQ3Q@`-q+kB z!*#j>FDhWD7?!}%wGAr$q_|YesD2(PD^oWMBa5`BuVoJ7;@@4d>&{EA1h_AHDG{h5 z(8kx!zxm=SUgi2c&U_X*cRxmqqje*<9h#)^th?1;T7(=~{=h%s2}*6o+;zo!p+$-~ z+*oc86fIjfFcfp`uE>}ZK|lsZ0AsTT?4N@$qGwwEE_IO4E8Wn?8JD7?SyY=PS8CdY zEDbmn^td#by^ENTi)rH&;9P+&=osU4k z&$IRvh~!P3yuZNok`Zba2CB0^C(kvfq`m3q_OlajsxloxY7UG?h;gRiYULu59WMDz zd;g>GU%@Tae8gDfMHmA5>1R%j%sw3`FF$yCcEL-OxmyHE=NI4;7@+7x_V-~DJikwa zxKd88(ZAd?8_HTBq-(;Wx7iz<*dBFkj%VVYd{b}|7%Z`%pZSWS>0-?5zJj;H%JddV zWAzuMjI|90${zP@HCEhD{-~+Pnz=~&EKy?MrHTB)(&yzKep8maQd-lpsr@VLb=YRQ zSR;o+C?@?0>>QB~bBn0eOGU3;PwBQmUq>ddRge7i{qOb$dl@~Qy=N?t^jucdVq3_( zzdvQL>{o>+BqQBQ%Qi^tL+8ma{-FXrFrxWe4GWycumWu+>)0nLoapEYCN+m+JU7tz^nHvwkF3oHJWDHL+upX|Ib! zLm02^)VcEd+kDFi+q3A!yr7Oq40LXvr61v&j3O@XC3-MObSZ5-4g*Q0!HQd_B5}B$ z$UwV1-QA>$iEUz`tGbz6Sm#qVy9woAj+H~bWYaaBx4bBMc2UEGJYUbJQIW>IQPz;| z_-dOb5x>q0fA#xH8zbKVM|@Gx!9p_SZISe?Up)5A3lYanhiFUhf0Ki`i{q(jC_^d2*KCm=odNUc3~esbD0xakaI z6>F%v)$+dkJe^B1lk}TamJ5$s6so5>-^OIu7DB5U<6_h2QF~~xJqwA47(s^k6G><4t)88*@IU@7YcuH#c{?kVXp zJr8O)_iPwG3Thx5A;@BJW>KO5a0LSp1htNv3)rnv#}2JG4{NxM-Zg8J0=46Io236^ zYDPTZXnp=x-GcdKP*n7zP7PGLy9*GA60j^PdNG9Bww4BuNtnGqYff zDPiRSX4kkW0XnIIngbk4bt#JfX3FjS1r{99;>0uZ#4Rw~6NDeX5y?Z?xi)gRSsp9A zE%p$y1+Gf&e9(vq`c`Fb1CMZ!oN}}$4eZ(N9SCD*$XommHzfA7{kr3Y_)`FXjMO|? z&$mUS&8CF$(QWS%EBv*wKK~qL&A7^R;hVJa{_|VT`a3cIdgWCA?s|V7=e?8p(<6V& zFgwth{^wsD+poXo1xS8V(_C zyc5zKX{Xyf8?@ae>Z@|PKNsVu?R*J3SAay+QBP@Lg+O!cp6~JGMjYI~p^p!9eHrZ? zctc3DPL3%=YN;z;w1Ae1)c5)k4WCi4(L`OJo^lF5KR^gRJ=U4XiVBh28FNBr;LDG# zr2eN~QaXW|c2ereO|uZS>Mm_Qfgk=3yP<1X`UmK@yIg?G#Mn{;6#lnT$!2z%UoHML z-Nzv2h6=))azJDCcwLyT{^`?Z+jW};7g>w4V=I;at%UDyCE?sH&)>e#Xg=D;F4I1* zL}Spk5f#7p^&E`css155(W2tm>c;=P28sFGCoTUoVAVj{l=y!hn*YBsKk=s40RKBL h2Beih%e|*`@u;D&eCZ^&Kle|Xk98lFJhXlLe*g=v8p%SlyM5|rOVy$Za!Wg(#;0Rk0$B)Blb0p8z6KG${vfe4#!ezDpd zGEG6C>$DfoB-GvYH{x-<$)qu@VqV*|6r=<+cRe+f)i;c#ZWSF%ZfkBZhvH?9`j81d zApd#X_hS3@&*SMY`|-;gioYr+10Fu4=M%W5BdQb<5~>z@y)^c?`g%Dl`s>#x_tRP$ z+NNeiaY)nddfQ*vB{GIm@xPlt?agv^eH@xX4Ie_6$`7F*Ya~PRJ%QKIfrrXq45tb> zLp_kPVhwm@e)&ZlrrY@p7N<&PJ)12Hi%Vfvz~(_dA>l#3)X4P9q2)n_rf}c3wwZO^ zo>q|i#+Ll<>NI$Metx45pOcfbeP$*R8yoxCNrc&?imIyQ`bb`?bQrk`5?M&2(~uzI z#gb+=5EdRDecIB}@+;0s|JAG5pEl25zI-4iCiYTEsa%TXxttt>fq}tGMa43y4{w~D z9>st9L@e|ZJC|tA&(H5cGXW7%MoP*9p&*=*5Dx(X0nP{)ty0*1R#v$(1|A-b^rCmK zrYmR6uMtG+6GbM$xf5TTo1cx>`FMR@=gjm|%T8g0JY{EB>gfmwxFz({8mWXnr6VIV z3x0JUiiu*apmp7v)K1{-@yPAebz2$_3kiuj1^$telB$amId+SrupoUZs#}phLfdp( z%6~1ElnxfKNn1=*eh~OO-4>WUi6{vWS&6qpiioE0Y2rv5b+gC zXP=-~<(x&jd^F)uV49BDe458vP@}rk#2oL`BjDBTFj4j-PrF!VK3Bh1r*W(9-2S`k zoBeLS&MVvTk`6+8l%Pv?v)Ec@YV$*ZhNDE!wXClrIHmSesH?MecA;-yA!XSx zzA&AvM_ylUzvFy2J?X*e(?X^do#C_7fICPZOsy*Fvziv8Dj|V&xQ_W4DRT5`yv*(g z8`$tUd)3N=jxQ(f(Ht775XG!e6uhF;1~Xt3Oe15`7FjBly;OI;^~LEpoU13(^n})H zgX8w~(hr9X;;^gBbGh-Q$)lsAZ|fk(_(l<&`_p%1sf#cd`n8@RkDc1u=-$<6dFPT$ zd|1yE{AU4XhWN2Qd6M?pp;p8Gjh3DZ*^^g=zM_{0YLyZXY)1=?T;)F_14K?{{kh*= zdH1J&6lF1LeKvQy+@d$(?oq!i-3u6X7kxS|6n>va&v0k9;V$pn3wIQU38eOHE$84j zYeAnyq7UP~3VgQJXSao7aJ`^j(V=D+q7aLaXjBxtbmnk>g;|Wzd&-E%;~Dhf10IJ1 zjgTwbyZN9OwX^RD{>VfgitcxD-L?M^5%JZ`khr_9_GsM3_()M;F?x@(B!qjNtt43$OD2aZL6x!nVJWY!+qg=nWjwy&S`(zN*pDHUi zTI2EUPA=h%Ukx>*^K?pI_LMFszMCd=B(m;^6g{)7lp(%uA`&ZEeuWv_ZKG2H#=&c5 zZvJua9saXg#67(k+!6l3sKJD*?@ymJmT@Qq(oRP>kp@z&HL?G7+61I4|f ztVWyTrC-J+wUp%|oIlZS(XiL-yl-y1M3>4;k^SS*C+4P=$<`;U8HsjnkasQ&Z39__+YVxP*qNc4I7|eYN zs-Q^H92=^tZh}`lR!bkHCsz_Vj1Ca8$;z4E?-h%w&=4^dnA`kl5#O~-CT(Rz-6|O5 z;!M()jDv$?Fm{~Aa*x9}U~;m|cFZ_f-7uk9!R{*u>+z%$QSIT#>pkvpMpEIxcjues zcX4YD2ITG%L2TPU(W??H+J@rb1wU?xaG$JjT;%jwGn_?S2T|F7vhvj_QAw3~G0v)) z`w5|@7M$Vei+xM!5b?E#apH%4)r)WbS=@}sxyHaF-A;6avi{rC#a{-a^)J3KhTM~; zO)dis#rQS38tYN$yBm4r{UjMeq%08yP9#bAKZLopwbSS#AaA_mz_MfSh<+kPs2i+b za;L4A6a_fVeE>GmoV5x{YvWnu)jZa%()JTO>y|h@a^kJU#Ya4j6&8k9S_T@EpABdG z(q?~tQT)85<3!{R_|RjnrtDHKNRKwv2Xo~T^83`Ko5OyT<@-HCO5qG=Gn)j{=>Z1s zE)Rtjsg`rAy>0WYLorz?+$b?T4GC*Ook;^_&|gNFo-rZ10Y z*aus4lR9p-Lf*gd-#rB`Jc^u~d3XJ_L}4Ev^+NfzIbz*~%zhMfSz~AzDZKZ*606*L z_+@E7>UO<}`Pi+Yuq&t$EwhLm+09)*`4aZ_ z_Fj+Iv1^2^zCHxp8SukcQP!129C;EJ!HrZI7|j+3=iN&+!w<>}IH~?R+$QcjCE-ho z++@buP$USW=N2FPD7^0(W#)?}gUKOa+~GstEfO9pZM%PN+d~*3#1swF5e}21j*C24 zDuapPStQgjk%nayTXHW!LP z2$Rt7C0zMfDp)osD@?TAvt8Znxzmv%NkowW^d-*#)taE^m)_=nIIgRLv@@nz4tgEZ&Jhi=hG=|$V^r}Ig-{9D< z?$HVcd*4t#k^@%t~ zS1Y8$%oB~UB^0b@;~-A&U~yLOISZa--Ht0e@>82^T$YJAM`BOjT~E-mYWi6T~Z3gReQHi=F-<5z=e{VGo%aMY9MF|M%e%0I|Im3Oma) zqW!}yXZc%tREkK%H2780HxLdM%1S}u!F)rZ@h9+6p3`#A-5_^&TrvdDx253+j7ohU zaf6_zev-HS=|E=}hIJgi5Yi9skMPcjH7sW3uLN-eHV717!{X-2Mfr{d@X7q6`Lmr3 z&`pt*yxp0W`zXQ#5|{UG6c1|DG8EsiBf_Z#R2%v1CoKUeMo4Q2;5WoEmaZ5)p0E(Q z^dl-~wv)X+x=?7lrnn}3hD_H0j7u2pp8i@!VRq4(G&_YEm$vTSa61|+{BBE*h@#LY zw-rPp;~kul3Gt*NBvl)m&kC=eLlPRl6`v%7zwIu`#cV&IBzpQY_96J2gbuNN9v6AWHJ==lDQ9YQOevDa5epjozC-&?Z9YhdS$FjgFMT!#h6i29s8&!Z22 zWPO(-;_|(Rzw!yc4PhTUY{Cvg8Y|quD{u^EBu_##UjUn-MtXcwBCUS?Jllq@#M>lo z415Na^bfk?ud$?9W}_X5Tjt)n4Z7^uK?mvBXzlp=o9%+k-LEf?A9V@ex@TCY%=K}d zWK{?g=%)<2)G6AC6L2ye_pNiS(9|2`PP#`vv2r$B#{Ms=ityt?GbdeZ`S_VfW+#09 z8YFQ8>48n`5UZf=YPe8c3#>)qE^rh3sohV^)os8T?xNn4!|2EMb55x$(722gh@wg~ zBi2kfFm)PMZ+9GtN|msrvu-6)$!!cia|ACdQ4^$cl1Zx&QhPI?T3)J`^5+#PVYM_F z5;=0y)ellBe-%qjQGdUWOXI@s)=}(9lHv2-ClmNWc8~+P?4H5`zeX6z^mV%XeeaC~ zGc%r~Y}&nCG}_PhTXjtik&gqNwX_PUv@n z>cVe+cW3uF&$i@E70%DIm!0tnztlKYZ{&GJW?E z5kN3P!@|B;f$QkP-J_zSI3rF^S~(hbN^$V&BR)M9T@Nbh^0p9Oj!}o6yzJ-|yJkzw zm6MYzmSRO$b)W5E++x_%e*PSFI))gyjaIp^ZjKeV5z@1;vHjGmcI~L@Dx;+D@}|1i zdB1Bati4FM%&Ft^qv%hc41+a&*<}r>;&+NZl|(V_wcg%aIp402ad=Suwx<3sBEISR zPeuq~VvOKQlD{PYA2=hnu@k+vD)%Ccmzi=6MTxfo-Vn12z(LJAn6)#2=e1CTgoNZy z{tiikO%XfyguvlyGj%?!-ED3@7TqYbwnFssY9Xx0y^Yq3trj2&uHpEGwB5(Sy8=S{ zUjzI4`d)U@y=kZan%;f} zyEHUUjBY}{{K9#IL;Yo$_YcyUJ1xSocjOnU(U~va&q=tqMbfl{*8)i5fp^8*{iW{D zgx145-KNb=d@q3A{Pl~40Xg=sh_0sh5&np|f6@C*q=wJ5zIk1QY{IiQW&Eh>e!~ zPYvgrL+-kc8U^=PxvXy}e|8{an1ojVGq0PzOYh^o3?E#MbBYYG8!eQWR}cOlBTa@x zrVhaPChrd-kN}GRjA)0Ga6dLa8+q;U)rzULFd!fRfSKuK>m5vqB~nLAez>%s_z_Zy z%0fKeC&;r|f1(ZI&qTlJ9GmYPMN7-ehqDo>EjGgo9qLYHtZRc{ROY=|Aag>;LRh}x zRIG*dtiIPK&-Vm?1IknF?d>&#jA`2C48!T2uTGcMR8>C*%sBOm+$AR_20D}x>$=5& z3ZP)umtS37b$vbz5MaQiqRpS{!6vmW;-$6pJsuryO}$=fP)29Y9@~{qG493*UtjEV zI&^+};`%}Q)hjrS=Ss5k>0->Fnv#gLlGtSlUa0}-va^SO0a~?_LSROTt^j!Hi?jR& z;f4)U3yaPgAkd-+#V(LpFsbhQs2BBDXExe=@;|YHZp#pL{{p@1AF>LMZWto2;&N+6 zCkNeiULR2yc&?xT*ejjU?r39-(+>IsCBKUqvZpMzhEnh0Dh%UQpUj6@b;WX&c!X(i zX_NfN&41=YwuaP{(g1DYWj!A}mqbZe1$Eyj6i_G(D-cB8A?bs|xE&nb_ z=7kV4lAgYoAkM!y#2DOlYYo2#^pILU^At81ih6#*tB?r_B35YJoG6d+=qttOvw{&v z|LqOK=wv0GYS&HE@q`8}E_QkFMP3Qux#^f(ZHB?AYPT3G?hHX(j{EF!tt327nOV;! z0Y*MampPBLA0bF%?=BZ){O@7J-E(B5s`^ChfsvW{rdBWtyAhQ_T)nR z-`XPFKLl$<9&FDf*3{OTQSvyYlM^J-Q8p@waYH4=b@2Qh!3?sI(No1JkexaGwp(y^#gTXYViAr+!HoEsyUXqA!=V05OXENe z=l4rGHLYMCn{+xVlz!0^plbxl3pKDxBZx|!75n5vG2ab^jAZD%cN$*$jHBP<5SVIX zmNaWzaSF^Gd4D|UHDuw4#o|fyhrn10Dkmtc0idZuns!f>1dJI%TWakOOr=B128SPR z5g}r?>dusD{&UBC2!LG#{`0O8$GfGoKiV9ZZl-=iD(@yhzjX2=IBC=7$TrWxIZD?2 z+is=!v*2Hm>O8M$?Ej7JH<$nKe~1?Ek2KIS+7_oUZ#lYkbGzIC0G>b3N51C{zRK{7 zRkVn25DZ@oLHl~5qSJdj*mNnj^1r7)wLK1DQ)-#w85~Mu4#maZckLh6GMHKbv@2XomL}=4{j>KA5-uM)ZWa}g?%y9oySc~XJ;CU`Mpk_}>5`^5g^lqBW zFQ4{YH=n-JWQ(&&z_uprE}eUU;VvU5!? zG3V0&>mcDzaM^8-VkkHRC{E%T;3pz40UfE%zx-NtM0@mJ+6zbi^_s7&T9Hr9HsqR9 zXS;i@0V#{N>20BKvN;VA=W=2G$-$sfaAmbQ>K8e?{>3fYS&wseW11#a(iSi}V!7mz z+&5vn86lBxS_VF3N}CBg%}@Ex`DVPGo*Nk%rPQ9%N^5ItXC)@i*woNf%+W=}tD>NL z$WPzDR}PEn(%u$}zHB>peA-C@4gBL_X}QvNwB$t%$Rb9mvLEY0j~8sm7yz&)j3CK) zYEb80;zQ%b=9>43X$VczAjt6G!917OpvdQoi;Gxbhc2So?tJk}>!li&d5VU;{TAW5 zvd2RpLa};?DT9OaAqw$o`M~~{0N6+Xc+A6za)-o*^Y@IiQl=8_ZpC3_K$cr zifXQ}&dc}f+~502B{p%Tq^8yv^Gz{4W@8H%JKrc4+O5mOO8bP#|#7XZ~%C0a)IpTDf0 z|ML;hZRh_tZ^~$ZP6DA#iJqSRNWCc)lJ5u%!qf@@29#1-of>zJ?(bZ|)9wqfvy+97 ztBLQfk}E3sg=8*)RKV_y@Sh;<53fL@#pj(@x2m_xq$m@Hs0+S;6Rg<;6+epw%1U_h7Sp zW}r2Kssn?G87VMOGI)FX2u8&lPAIg22yttpaZ|Rn&5f6wQUPm;n3GMjsSS}6KM9i zUueA|AajOXUF<6fuKxV;a7&s(@7dhkoGbqMN6HFDxmtL|;rd8Sq7Xn^)1sqEy&}B~ z$7Md!KG_8{e_amubAKS0($Ue83;<`;)7papPmV5eSln_$@Fs$nM0@0pIwe8R{==)m z3n(>8iAZqee%0mCL>j09fW4ane+|?tydO>)ea@opKwd@V{9SF1pUtn07{AWW&eV~-*I{!rH6Fz@ zIzm_H+hPJ@0O)xsEiLV8xv31+;`+v>{h%8IfTWC*6L+B!`%J%m;giBFKOqqJYcM8X zPsmCVYsPy&VzT|+=_18<`{z2J{k)FGEFKvg^YAyo`MFEq59esfHF%Gi#ca9(EDRF6ohB#Q0SoiMlMx^nrtnRWia(AHaU$Y%R3eoPDP6k}orZqhBxT+xCZ}W2 zeEb4!C)80owc`Vyq107^Ngr7<)R>YxD2c33e5PD5V_7%tB;eBV@*r`2&8=qT_aCFy zp+KDY)>8%(p)5_K;(xls{FFCewVwY%bg!ME)8T*^8h;AFIfr@4wn#mpzuss&TX(j` zD!~5sY?VpqC#QGU>J8AveQy#eATvL|4ZvFV?)`>_iVO_|kVpyH2R@yk=gD|h=Abs}x$cPT$+ndS+KmBV?fDH-A zE|IZnK0jM}ySTi}N=lZ|w1~9pF-7uyE_mrWdlpiGN$>lR|Cn7Z(faH#@=!|m% zRzb~PC|ekoqxlt(eo>^*Mfx4Y^IYaQ>NgU^@&oa$AV4e7!~IzxxQuAki1LZMxpoaV zwb5X@!k5)-@pUw?bLrM?xvY=22Sz_3Left{lS%7vdwv+~5ifq^H zKdYFz+-V|8u$x7UL$`w@8D|T!R__V9=F&7_`v@664I-qm4p#StHd*rg#7aJ0Dj8HH zS$_Rm=a-*Ueav!#S0t^+pF8(vmA|;v&2`-N$b;mMsaSx6uX5o1D}=(Y=DZuEP{r4O z#!GD$b(={FSm9`2H;MU>(y~my3BHOeV4yEMt}nAZxc>2d8XXdFFiPi{tOB)o9=pts zAL-|Se)57}TpB+-p}d^WWoz>L-$WZZx4xDK4$Qi}kj=V;cq?~R@)*BM>NtxblHWLy z{je?O2l2a+Ty4&X18;BdW|G-bQ$s*pzH4#A)bz+0^8oooRtF*zL21@oB#wi-B(<_+ z1kp|*r#8wIxMl9cULUSirx8@%TMVNnJn=c6V)`DyNOlqEH(KjePLqnQZdS#cIu9zf zo6saA{uYe<@Ko}1eg zI99TzH65@(4($+9_f5y6&&L`(9vJe!USCEzjJ^jx22y%s^yHTJr1*6#py|9DfJV*C z#{>Vg@*BW}X8PjdzBH;>DB6B@l5OT^BoBXkK(ll3O{zq1^xtM|qUaS90t6nBw^=T`>$bq9YBUpFGDeYBMSut zc~9h2iWNbitxtb5Y_cO)?iEGWZ zV$nfHFm!#r!ywcCE1!L{)mI?4Tqsz_pF(9l*u52A*%ZqJtZkkP#LE-t!$$kMQ}Ymi zQR`Tlnci$PT6n0cK^I$>?iEBY1PUkzy^nSBcg_L8%yi(>awhB zq>S`y!GhOyaAmp@Q*dxLlAWDBtN&SqtOg25c{Jk!aPEnVz2xRVT38JeF`pe%=B7mA z-8e4LrN-_bd(mQz;T*|AYJEs}WM668BPxE<>~@=jVJmr5g`ZHA3l_n0!0!R=yw~M* zmhYg{5a#zHlAFzfII$Q;Dh#8n4yMFqrR!EZ4z311!>FH19%!MZgWV)F9`!JU5cTB( zKB}z>_~@P2HEb;#S-QnG3G%z@=glNa?IZ%}9EOl7+Bk;9_#}4Lc>t@(xjyf<%7PFo z^q&ZmXFQ$}w39EnJ-k2Lq?sd%%s0^F(7SJ#1cKlhj>qd3n7Ba*gYfhibz=ez=gaJz z-o5685SH38XBbBJy=gnf5|SC7;)kv{g)wR^3q~0B$(2pbl#Vv|a#WWX+Tzl^1whjpx^csmfF`tc8o|mL^AQ8_sjG%EjhWr9VKOHF3r(oIW93^6+ zW1d*SW+zJiokjxo=O=$t32=TlW2y09P-%M z@kZbK-?;(Bdw87gxjmT+p`r=33G=9A(`Qnq01#V~&AYC3@xG5_JUDRf_crA3Y#uaA z@4M>hH<&8_#pF3reog-Hs&RPo3^y%hT#^jU{+l;Pq7nUvxA)_SkeM7ZvHMeb-h>@C z(vyCNc4NP!>xAcl#N{lPlnaA{&DHmB zC!E=e;ykIn{gpuY^yqPRQ_kn&$8xgCyswWY&~w6^G*MG7vw4Kj?zT7XrFaRk>o4VO zB&FM|6lEb)b3FK&QAjtKR#x**Ya4d!_kSPogR%lF9>$FX72IuLWj`^u2p%GO(t%E{ zj2_G!B=`t(?@xIflC1q}20}P%L_k-%T=K_-zQ^Hi)q7lU%^r3-Y`1@vqJVOY4#2(O z*5ME|t8O9NYsR9etDWyZs-QZ^v`3WuzHa7@ zp2k5cB;&FF#`P}qdDjs`$|*DtDKLsToWMB9l;?Wh9+l)w3<@v)w@SP5 z>E&wb&wr?2k8g~ZVuEnxZ#TB&v28#{{nEQMMDb)8jip8sjP)oYNjGo5VMaLPpHE()74T~cQ7oGqZ2(+jz2iy8gCkjB_Flw}=U`=Qm$6uNY6uPxq z)WhPiwyU*&nTB}N;;>)kh7ePvY95z=jJV->S3A8vI2-h~n%4n>aoD*V05kf8&?Nq*+G(=0jd>i9n{XW5R=U1(IsCa4hfUItW-<0WzC1tP*3{LF zv)Xi`kdJ6_8vm|l6|x%AxvvRh{GTQE3~aW0*( zYnmrEZe0 zA6S>3PpyZuHDW%Bo~`5q!ii^e0>B3SYA!Q(n+K}~xFrS|@?lD~=^~pApwH(7?C z{9zi+4@PkDkaL!rECl;%wrLZCueJoqSDGZ@0gX*G1lqB5%xVcRe3RJWEl!u&%CJMi z^}F9yQTPZk4z{@CFyh&uc(yJjlesfa4AlWYL0o5}^9Om2ZnE4ww&EJKeQkspPZ|kp z2#-!WY2Ww2U@cvn?FrAAK(FZ=yG6?^JSMl_LYVC4nrJIV8qYD-g#|X^DIMea8+BR) z77-eI6w6B#eO|=JPqHOna**^5=1K_k@z75v6uvGh6yfWL9#DrjHad2gEdF|GTKOvw zXV8_C07H?_6`L`Y%SLWJqi94=)0`Lj<&^|m^LfDVw?N#I32ha&041a0Xjbi4=D*}0 z&)y;5yVbZG&~=c=Hc17f#VM)b>~OBhNv2;&5=n(f5|i1o4t2A)#zx;sv}rbH9v#gb zu`#)a%DO-9#KofRDQ4T~CAl%qw?poA;Tz+;D^zY5Yic~5&p!vr-?N_X(`)FkVPo^x zp#zwin+t%8b3XW^?6tZMwcFp{<3`Ua82Yq$es#G=;B4sA+{k(Tt8l7TJ19rnp-|sg zpjtIahE}1`(vxTMH>LF@sp0b|I=;^6mI$`ko?dOY2k0eo@}Bht)8F%kBdNJ6N87EY zw+KA;hzZWI;+q=F7^opiKrW$SwXsnFSHTZqjWFocRwPd78fR1cDxpC6O@fQn8v1EO zjE(f}N`VP~Kp@Hwq=_I%?;p&{@{FiXIJK@ZBkt$ zw2lwcMmSnl$(siL(pA7k3GaUT^P8P5&Lr>EidN{1#yx)Fa63bKMWS03Slw5d$vi&l zjE-xjVE=*&D)9=P+;`EBXi=$X{HzEug&j@XwPSK6C}Vq z>Po~iKa?P+{Q$H0g`8?cq|z-s|F&PnjVW07F9)g}sAJhCojrdKU0==^lc&dDW&$;N z!A#!*Gt5_TMhw`Rr88`N>kMOq8qf+*1+k?dyR)|BN>_%?#Kl!0h1XD$(tBqfo?Pg! z&6QLJR_N>DmwUzhES`~~l)8uCvg&H=Dq@Ml5MM)0?!Dq}Y@}iG$ly?TzWF_p;Cjm= ziHayX0!FNQcEiUmxR~2}{X?P+KTXE9zu+Jh(DxfIX3yS7T?GYJbiP7ZSd0z=b|hUx ze0bibeU8KRmMlk%fJi$tgsa;-U5Nz>LSPcEdm;9?n9Rb#;^^oAvu(!1E1jF z<#7$HiT6m|U=RkNWNqL%gbiXBu^-DTS$@|EHq$0Ky3J|khie@$ht)IbWH=99qOZxm z`g&6mf;4IVQsgAwK*Kf(-Ep==cTW$Sio3I1N1G);c85OhWU;9cLoKl2g2LJhK~Vp` zyU@7DHTCAn0{DSPu0l{gJwyC#jCSS=DYbi%xQ&{HpoB?|cr+zivWCMIZi;``giwBy zKv2_!a=j)t@cnSCJ++15fJEM7!TMAgNE^NpVs@PRwVRh+$@nzm@M8S4Fj}qbN4D!w~Vp1-jC#=brF0P;(P^zLh`6MW%{O z9^5*-%RebAu;~iz+z|eqQUH&q9W)Vg4idK`0VoTOQFH#7`5+8j+7>G#>`RU{^rw2n zlo!&9Hd5z!D>dpMJo{iW_T@ftK#i}2R5w3F^gi?ywBQLNxTKEKLKLH^!GBoy;PndK z%d2cdo`SEXl$|2^5i6l7CB({$kpK`CupAF?{7}+_usd5oqZ@<`MeR?Bts@OvV+`He>{W6!>2qv_X8!OZKzMh4MU$>4 zdqkOP2Bns1G8%{TJDJGR8;Q?-KpeQ;vaIsIBg^#>t4$;lrwJgpTd@mxbGp9a92{VB zDDx-B+Sv;Ph64KIi_blNc&iIrGY8MwtUkH?kO`ZXNdQV9m&&L@rhDa$+7A;5;~bA9 z#Q6uJJ6+M}T;U7zNFZW}a!y#ksj&%c{Z#>Fj=;$W3*)7vh#2%KLl`G{nS5Q@kgrQl+QLa3HHORw4U4cI=Sw0zHTAbv*IQFQq{Hi<>V zbYOYVNSKc6vX2ZI`oQm`{Io;DoGg*uIVJ$mEZI?x=SzzxSkqA@RdAd z@EGqh6l8yuDS*uD5Z|Pm&+^L=N*}SrC1|^gp$M1fCzwnB2={&5#G^@b&WQx2Lmd0$ zm+?`2M$CxoH{|*remW!ayr@k6_#fx@ztf2p-Ld2o7XB ziETlah_lb}BWpZ`Ck$wp4~u;l>$lo)<|_1I&q<6bh-NQQit-V{@}<=asX#RpE+1@C zepm4^hKcVWxIqEyQXO_!;dzNybSRf@5RSzY(g7K` z;3YVgK2|%HBEyesO6a~Yh=w`bk89#J83O5EpNQpySERJUJ9q06J1Suxr#t=Q{Xf)D zb957Ei@n|CPu9KmpUf`_Kb*jQTirbEK3OawXx&}rQlFA zIL^l0lr^9Zi8x8$W;}D)EPB_35Hv8gL8T*@ zpSwikmQTbL5yFAAMCB8@rEYfH#2}KeeCBsPg*Dt8HP}BRcQTNG{iswjLHd*AQq6sG zLi?vMKLHIH)=D^=Fj{_Ki_mH6q~%PBNqPdg`;)q>dti8rItd z5Cor!)|?B7c}1S9G%F;)a0QxaC)G{Rr~tcdZLa(&d6qk=sYIK0x6h%!#u$4Y8_^TX zyVOTI(xU@CNR^6R%?Xe+6(KNUe%8+o@dqtL*o`oqSNWN|-=Ee#U%ka+f;h3n2CoPO zMrGu0`#HU*`bYM$6>ew`B+wSP+n<$CGgL8a~1a6a7m+Dxq|hPn3Pafe1LBZ;8>oif_`) z9ek@?x!pUCs1Cm68$N>`v_D1NRT9us>iawRxHSv} zKD~V%RNY+prBL{*_v4&hvcd;>PDfA1>H665FPD|8{S@adUrMXx0LStObYoU@{0ed_ z>hz!8lwHnOxRPuAKTuBd-?Y%`c8UGe*-%fs&`01**Hzw+^5c(FH8r#=18JQ`N49BL zE2BjVU*l#9TsFrKKiW@K`Lx#KP85@x)vpd^sTqi7nD18x?yrl~)m4ucM?>E4pD6b1 z7dR@o9j*!HDg5AF9xDd=yOvm{SEV~$`no)+M_+H{H?Q4FXqjDmP7a@k+o663U#i-M zo=e6fIRf|9_=G#fSQhc>%_uSZ-g1TA#%EqS1aP@0GYfN2joB@rX zGx}6xXsmPng;1#KM_ta7&kQwHu6pelZoQ7Ma1YKv*Xk;-7x~w_7QaEgv9MK5% zEhvfJ)Ht%$dje&Z$EXiVv`m#cajtzogZI|X|Iu`u3s zon=(~#4{kb%>Ad8>XddoM9{-y%3c<5vn-|{LXn?g<;Q}&FipCO?YdMP0`k5joBZUb z(^Ks^Hpi!~@@WYxr99O6n>k#8nkpL(;}_?0qTH>cx04>zUgdJ+_Xx=xY^xWMN= zjrZFHcYw+R;hxCsoAp~5Rnb`Ob^IyPyUAhrjMK_|ch8)wzjQ+9=`F=lAff*uCI({% z4-92olhZL!j4|l{2Gb>)XYzoX+lgu%5!BfmMBjf5h%5qz`{6LS#0<8j;cg^ z{+z*~pza_tuQ$9uU>0K_k@wWDJE)StWqn$t(g~CAvtbGBjE6+wC^x?Cdx$&wdes!J z$6KcdINoZ%ELdTnX>gm;rr6=i*bf2>ZIoWMbM06{8uPira(?`|yW4tQnryG=1+vd6 z&5dcB_J}RbZ)GHJV#+r9OZDN3^z@Xc_irL=3SegeRFcT8sp`bRJTE0)qWy%blyHab{0YyaH94AE&w~>Gn^wN% z>s9NI<$1Z)d}Nb8x8)`Tb^?*U90!8~uk$uS)o4$DI^w0Htr?&|3={MBDSJoLW2su~ zx`UW;31F}=0;sMVpt#~gbscz--h*1;j3FTe(&y}ufgQLUbWM(TD0chlWkC!NJKpdVumtnSNUX7B@h={TZ4bx>vR>%b6-d}kdUQq{>q(QJD z`H4WgI1oi#4nTd&1TwtdK=m{%1kkzv9`KHcy!lHunFV$m8W$;Pr_0}4N(3C7hzR@V zj)71AekF8Z<^S+yU;uw_D*5}pzwQ0!(oLcEzzQ5N#Q7hfY=p)JmlXyiQ~oi>PH7Do zlK-Xr*btaF(ln#hk*q3M_R61!r%(AZM`B+*-Wi41+Yck5*dpzHmBspCcXT-u@&|}}Ip5J)dg0a5e!(QiF zzAwOAZd@l&esGbUC+N0yaeI9Nn8_1&m&0^@AJ5LqqSTmU`8qpV(kRcbtARsqdA;G=I4DNHQ-&jD%c33gN3}cl z^Tsqi+;NU9^Tw~)Dt~P2$_`K1j;IJkgjNhY$bO`4FGv_lWm+~!tQ!jch!wCu;6wQ} zraYj+++cFsre*KL!lWp}@r1U3y)-3Rr$+m4hA)ZdI?1$2ilaqF1O#K z@c1L&+wN2Ki7TH>Qy~A`MJto{9P{e5=Ga@#{513;Q~2GsPfO`U%S6s-&W~fUvmQR6 zbe8R+#6xtQ@GUk?(>sGdOnp{g7hBe>JHj#|Cc9&1T;0l+z!~dmF9!PtcaH|2$)JsT}7fnk+;H8 z+JoFTlc6_;Ys=GqJMXqwGS$YB{UL0>^IMlwt28{(Kk}r9J6oxIjbCxt`x>P$szqWU zX!BqOciU=vb85U!ng!T0LsiLi3nNztuUYnh^=WeB@i$o0>bJX>m(u1`0iEb^8JbY_ z??n|9=Reiu(kjBxe@-U=_0xZM)tk?Wr2aYC#T*)(3{>0RTxw~$`QI3B9y4$B&oLKL z>0QJ>$3#L?+~NN}ZZ7qQTY1*y8R2~(j{Vm#{01is1gfOA6P)eacZ4R3USSVOP_$e(&MqK{^XeA&E|hbF{3_^X`?TG{zJxx?hERtN=(J4>QhC5@}?>84C~_V(h!30 zf3H-|EPb$H0ai}Icy0m9umvSg2ZLL-ooKb1+Q0SyTC!w0)h_3597U%!pzOEu%{RWwwvb`F`ff2*KQXBE5n<0a)`RHBQ5l!n`C<*Vc~eu^K)lsD63ewzMS zMsFW`e1@B4kIQyP{UzNVBv8Kq@b4k?@Qc46`~0!c8&n?F^L(nADcaML9vf7B$`hqs zm-SNxQ{#KY7ZG~#zvz0)usVWnOE?LE1P>M*g1g&+Ai>=&xCIFAZXvk4yK8WFcXxLU zesB)*HF;<5$er&G58bD$y1S~nYVWo8TJ>sPA>qt-aP9(KR?|K#m@ML#Iy}#YsJyC7 z=(4UkWlg`HI%Un#P7VwmrOFn-%(9tqhLOorC{ikJqKTE(T#B53wSV5QSyRVJ<)s2b+6 z;IJ9fiOVu%fTy8V0Y z2V$MLV#nVX0{p0J38U)eHt3!Zgy6JCvOM?p%2-T-)a3zrF>r`4Vzp)mH+Nq6-VbVAo4>_l-l&5 zFl`6L732JS4ATvz;=inE(z7Axj-R~)EVkwl!;uuj4O%o+j;c8rxg8!W zh4(;+3m++c%nNW8>&5d-lLWBya^x_2VSE4qrXnTfz)=n77jbmk6Cnw;EraDh{#lH? zVc8TT1rsF{!7hqGCo0GP>+y^!g`#4Woi~X^RI!al2^Q5j@eQ0Ilnv|7!7VDl2-v0m zHx|r*XB+Br_{8o^?We`#EdUTwN8-^yiVwb6>jD&ym*J^g7c^Rqf571|#y>*ZzIi@rXGWIvPo8RM=MP}+DGte+6@mU_Bd0NW zQR$TD25RR3T_WU?hy2hfuG7$3C!J^7_+R%y8f0Tk6L5{lE4;`+jZ>M4RQ|8x54v*h&h*akJsrl5YN6eJhGU>2S z?3t>(SoWMC>(ikAZo2#(CgP+j?(D1E}|pO5WKtb-Fws2s@3zyX*vjaf`z!Gqrh$2#NDKp3XFS{SIjd**buPO4^H@Ui~TB<~>#`6|p|P zuavnjq;P*Zyrp$U!!Tedgd|_s-Dw=~{HnUj(xf)$FWeLR-=a~1*EotO*;m!ar%u3d zG3RDif_K!)@J!b5J6o#i=HJSNxRDapToFh5hyGMsgjx?H+NB~OuZIvo@C|M_`=lqVW<+4N> z%1NuYikvR4I1gA^rZj;(E`^f-;)PJ*D{NVl7t6XbN)A%`yOiIqtrcfDN8YJ(zXs8{ ze!}@>y38Pz|5(%X1L5&W!SI9@WD=RrJ%nS%U^#jSw@4hx7pX@A)9)xCfkZv)Nx4-# zi0(d2ql9&Inr0y+jLnJe&VWe(MGmf2P#(p_t=HbB+({nX@#?pSYw%?DyT+k`B4PYU z1YW|VNnt0!YJF?I
  • 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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`~ From 84b0f408fa086644be9696eaae8d7eea84593118 Mon Sep 17 00:00:00 2001 From: malizz Date: Wed, 28 Sep 2022 09:56:59 -0700 Subject: [PATCH 026/172] Support Stale Queries for Trust Bundle Lookups (#14724) * initial commit * add tags, add conversations * add test for query options utility functions * update previous tests * fix test * don't error out on empty context * add changelog * update decode config --- .changelog/14724.txt | 3 + agent/cache-types/trust_bundle.go | 8 ++- agent/cache-types/trust_bundles.go | 8 ++- agent/consul/grpc_integration_test.go | 8 ++- agent/consul/prepared_query_endpoint_test.go | 6 +- 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 | 8 ++- .../get_envoy_bootstrap_params_test.go | 26 +++++++-- .../dataplane/get_supported_features.go | 8 ++- .../dataplane/get_supported_features_test.go | 19 +++++- .../services/serverdiscovery/watch_servers.go | 8 ++- .../serverdiscovery/watch_servers_test.go | 13 ++++- agent/grpc-external/token.go | 28 --------- agent/peering_endpoint.go | 41 +++++++++---- agent/rpc/peering/service.go | 55 +++++++++++++++--- agent/rpc/peering/service_test.go | 35 ++++++++--- agent/structs/structs.go | 24 ++++---- agent/xds/delta.go | 7 ++- agent/xds/server.go | 7 ++- 23 files changed, 340 insertions(+), 101 deletions(-) create mode 100644 .changelog/14724.txt create mode 100644 agent/grpc-external/options.go create mode 100644 agent/grpc-external/options_test.go delete mode 100644 agent/grpc-external/token.go 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/agent/cache-types/trust_bundle.go b/agent/cache-types/trust_bundle.go index 48dad64372..b9db20645c 100644 --- a/agent/cache-types/trust_bundle.go +++ b/agent/cache-types/trust_bundle.go @@ -83,7 +83,13 @@ 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) + options := structs.QueryOptions{Token: reqReal.Token} + ctx, err := external.ContextWithQueryOptions(context.Background(), options) + 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_bundles.go b/agent/cache-types/trust_bundles.go index eddc8dabbe..d3c1f404c6 100644 --- a/agent/cache-types/trust_bundles.go +++ b/agent/cache-types/trust_bundles.go @@ -87,7 +87,13 @@ 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) + options := structs.QueryOptions{Token: reqReal.Token} + ctx, err := external.ContextWithQueryOptions(context.Background(), options) + 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/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/prepared_query_endpoint_test.go b/agent/consul/prepared_query_endpoint_test.go index ad46ca4cc0..de45f0819e 100644 --- a/agent/consul/prepared_query_endpoint_test.go +++ b/agent/consul/prepared_query_endpoint_test.go @@ -1493,13 +1493,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())), 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..4456e361b3 100644 --- a/agent/grpc-external/services/dataplane/get_envoy_bootstrap_params.go +++ b/agent/grpc-external/services/dataplane/get_envoy_bootstrap_params.go @@ -22,10 +22,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()) } 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..230f95e818 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 @@ -78,7 +78,10 @@ func TestGetEnvoyBootstrapParams_Success(t *testing.T) { 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 }, @@ -154,11 +157,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 +230,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 +253,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/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/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/rpc/peering/service.go b/agent/rpc/peering/service.go index 17b862ffae..65d508f9c0 100644 --- a/agent/rpc/peering/service.go +++ b/agent/rpc/peering/service.go @@ -209,7 +209,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 } @@ -360,7 +365,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 } @@ -528,7 +538,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 +590,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 } @@ -657,7 +676,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 +740,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 +804,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 +824,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 +879,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..8f11ebd147 100644 --- a/agent/rpc/peering/service_test.go +++ b/agent/rpc/peering/service_test.go @@ -217,7 +217,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 @@ -491,7 +494,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 @@ -626,7 +632,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 +746,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 @@ -862,7 +874,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 @@ -950,7 +965,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 @@ -1283,7 +1301,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 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/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/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) { From 69f40df548d898534d402e288df0dc653e8cd8a0 Mon Sep 17 00:00:00 2001 From: cskh Date: Wed, 28 Sep 2022 14:56:46 -0400 Subject: [PATCH 027/172] =?UTF-8?q?feat(ingress=20gateway:=20support=20con?= =?UTF-8?q?figuring=20limits=20in=20ingress-gateway=20c=E2=80=A6=20(#14749?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat(ingress gateway: support configuring limits in ingress-gateway config entry - a new Defaults field with max_connections, max_pending_connections, max_requests is added to ingress gateway config entry - new field max_connections, max_pending_connections, max_requests in individual services to overwrite the value in Default - added unit test and integration test - updated doc Co-authored-by: Chris S. Kim Co-authored-by: Jeff Boruszak <104028618+boruszak@users.noreply.github.com> Co-authored-by: Dan Stough --- .changelog/14749.txt | 3 + agent/proxycfg/ingress_gateway.go | 3 + agent/proxycfg/snapshot.go | 3 + agent/structs/config_entry_gateways.go | 13 + agent/xds/clusters.go | 47 +- agent/xds/clusters_test.go | 39 + ...ults-service-max-connections.latest.golden | 71 ++ ...ults-service-max-connections.latest.golden | 70 ++ ...with-service-max-connections.latest.golden | 69 ++ api/config_entry_gateways.go | 13 + api/config_entry_gateways_test.go | 10 + 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 + .../config_entries.hcl | 8 + .../verify.bats | 38 +- .../config-entries/ingress-gateway.mdx | 52 ++ 18 files changed, 915 insertions(+), 286 deletions(-) create mode 100644 .changelog/14749.txt 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 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/agent/proxycfg/ingress_gateway.go b/agent/proxycfg/ingress_gateway.go index b21bc8738b..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. diff --git a/agent/proxycfg/snapshot.go b/agent/proxycfg/snapshot.go index 6a8f7e584e..6ccfbe58a5 100644 --- a/agent/proxycfg/snapshot.go +++ b/agent/proxycfg/snapshot.go @@ -619,6 +619,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 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/xds/clusters.go b/agent/xds/clusters.go index 02e6ac2b47..fd5629c355 100644 --- a/agent/xds/clusters.go +++ b/agent/xds/clusters.go @@ -630,7 +630,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 +658,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 +667,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 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/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..08d2c471e5 --- /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..61101551cd --- /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..6ca8d60c6a --- /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/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/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/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..faaefae235 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,45 @@ 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 max_connections in services" { + 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" = "300" ] } @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 max_connections using 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/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)', + }, ], }, { From ff9348f877e15c46660a197a71799b411868c48e Mon Sep 17 00:00:00 2001 From: boruszak Date: Wed, 28 Sep 2022 13:57:04 -0500 Subject: [PATCH 028/172] Mike Morris clean up requests --- .../cluster-peering/create-manage-peering.mdx | 9 ++++----- .../docs/connect/cluster-peering/index.mdx | 17 +++++++++-------- .../service-to-service-traffic-peers.mdx | 2 +- 3 files changed, 14 insertions(+), 14 deletions(-) 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..343edfd6f3 100644 --- a/website/content/docs/connect/cluster-peering/create-manage-peering.mdx +++ b/website/content/docs/connect/cluster-peering/create-manage-peering.mdx @@ -94,8 +94,7 @@ 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. @@ -123,9 +122,9 @@ 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**. -1. In the **Name of peer** field, enter `cluster-01`. Then paste the peering token in the **Token** field. -1. Click **Add peer**. +2. Click **Establish peering**. +3. In the **Name of peer** field, enter `cluster-01`. Then paste the peering token in the **Token** field. +4. Click **Add peer**. diff --git a/website/content/docs/connect/cluster-peering/index.mdx b/website/content/docs/connect/cluster-peering/index.mdx index f216fb780c..c457f5ff4b 100644 --- a/website/content/docs/connect/cluster-peering/index.mdx +++ b/website/content/docs/connect/cluster-peering/index.mdx @@ -13,17 +13,18 @@ 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. +2. Use the peering token to establish peering with a second cluster. +3. Export services between clusters. +4. 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). ### 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 +35,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: +- 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. @@ -52,8 +55,6 @@ 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 `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. 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 From 6f52816680b4387d92702e037f49cd82afa89bbb Mon Sep 17 00:00:00 2001 From: boruszak Date: Wed, 28 Sep 2022 14:00:21 -0500 Subject: [PATCH 029/172] Kyle Rarey - requested constraints added --- website/content/docs/connect/cluster-peering/index.mdx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/website/content/docs/connect/cluster-peering/index.mdx b/website/content/docs/connect/cluster-peering/index.mdx index c457f5ff4b..567dda3307 100644 --- a/website/content/docs/connect/cluster-peering/index.mdx +++ b/website/content/docs/connect/cluster-peering/index.mdx @@ -58,3 +58,5 @@ Not all features and functionality are available in the beta release. In particu - 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 traffic forwarding does not support `http` type services. +- Cross-cluster mesh gateways are supported in `remote` mode only. From 6fd154ec733dfd8eb90ffccd58075c9c1a42a78d Mon Sep 17 00:00:00 2001 From: boruszak Date: Wed, 28 Sep 2022 14:13:38 -0500 Subject: [PATCH 030/172] Tab groupings --- .../cluster-peering/create-manage-peering.mdx | 38 +++++++++---------- 1 file changed, 19 insertions(+), 19 deletions(-) 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 343edfd6f3..7d61d67890 100644 --- a/website/content/docs/connect/cluster-peering/create-manage-peering.mdx +++ b/website/content/docs/connect/cluster-peering/create-manage-peering.mdx @@ -33,7 +33,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 +57,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 +70,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. +2. Click **Add peer connection**. +3. In the **Generate token** tab, enter `cluster-02` in the **Name of peer** field. +4. Click the **Generate token** button. +5. 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. @@ -86,7 +86,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. @@ -100,7 +100,7 @@ You can dial the `peering/establish` endpoint once per peering token. Peering to - + 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. @@ -119,7 +119,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**. 2. Click **Establish peering**. @@ -208,7 +208,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: @@ -244,7 +244,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: @@ -258,7 +258,7 @@ cluster-03 PENDING 0 0 ``` - + In the Consul UI, click **Peers**. The UI lists peering connections you created for clusters in a datacenter. @@ -271,7 +271,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: @@ -293,7 +293,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": @@ -322,7 +322,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. @@ -346,7 +346,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). @@ -355,7 +355,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. @@ -366,7 +366,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. From 3e2a650e5f5bcb81f2fe6d239d9a5f32ca3884e0 Mon Sep 17 00:00:00 2001 From: boruszak Date: Wed, 28 Sep 2022 15:42:21 -0500 Subject: [PATCH 031/172] k8s page updates --- .../docs/connect/cluster-peering/k8s.mdx | 92 +++++++++++++------ 1 file changed, 62 insertions(+), 30 deletions(-) diff --git a/website/content/docs/connect/cluster-peering/k8s.mdx b/website/content/docs/connect/cluster-peering/k8s.mdx index 75a90bb1de..bf24093cf2 100644 --- a/website/content/docs/connect/cluster-peering/k8s.mdx +++ b/website/content/docs/connect/cluster-peering/k8s.mdx @@ -18,34 +18,31 @@ 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. + ## 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 -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. After you provision a Kubernetes cluster and set up your kubeconfig file to manage access to multiple Kubernetes clusters, 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. @@ -71,12 +68,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 @@ -85,7 +86,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 @@ -95,9 +97,11 @@ 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. 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. +### Create a peering token + +To peer Kubernetes clusters running Consul, you need to create a peering token and share it with the other cluster. 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. @@ -130,7 +134,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. @@ -138,7 +142,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. @@ -163,9 +167,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. +~> **Tip**: The examples 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. @@ -189,7 +195,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: @@ -243,13 +249,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. @@ -278,7 +284,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). @@ -350,10 +356,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": "/", @@ -401,9 +408,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. @@ -424,6 +431,31 @@ 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. +2. After you update `PeeringAcceptor`, repeat the steps to create a peering connection, including saving a new peering token and exporting it to the other cluster. Your peering connection is re-established with the updated token. -~> **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. +~> **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 features 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-02` so that Consul redirects traffic intended for the `backend` service to a backup instance in `cluster-01` when it detects multiple connection failures to the primary instance. + + + +```yaml +apiVersion: consul.hashicorp.com/v1alpha1 +kind: ServiceResolver +metadata: + name: backend +spec: + connectTimeout: 15s + failover: + '*': + targets: + - peer: 'cluster-01' + service: 'backup' + namespace: 'default' +``` + + \ No newline at end of file From 66f8c65e898caa8508ad11fdde3315cd3ca62b5d Mon Sep 17 00:00:00 2001 From: boruszak Date: Wed, 28 Sep 2022 15:52:03 -0500 Subject: [PATCH 032/172] Traffic Management --- .../cluster-peering/create-manage-peering.mdx | 55 +++++++++++++++++++ .../docs/connect/cluster-peering/k8s.mdx | 8 +-- 2 files changed, 59 insertions(+), 4 deletions(-) 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 7d61d67890..7f82c2c326 100644 --- a/website/content/docs/connect/cluster-peering/create-manage-peering.mdx +++ b/website/content/docs/connect/cluster-peering/create-manage-peering.mdx @@ -374,3 +374,58 @@ Next to the name of the peer, click **More** (three horizontal dots) and then ** + +## Traffic nanagement 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. The following examples updates the [ServiceResolver 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: 'backup' + namespace: 'default' +``` + +```json +{ + "ConnectTimeout": "15s", + "Kind": "service-resolver", + "Name": "frontend", + "Failover": { + "*": { + "Targets": [ + { + "Peer": "cluster-02" + } + ] + } + }, + "CreateIndex": 250, + "ModifyIndex": 250 +} +``` + + \ No newline at end of file diff --git a/website/content/docs/connect/cluster-peering/k8s.mdx b/website/content/docs/connect/cluster-peering/k8s.mdx index bf24093cf2..e9b42a8663 100644 --- a/website/content/docs/connect/cluster-peering/k8s.mdx +++ b/website/content/docs/connect/cluster-peering/k8s.mdx @@ -437,9 +437,9 @@ To recreate or reset the peering connection, you need to generate a new peering ## Traffic management between peers -As of Consul v1.14, you can use dynamic traffic management features to configure your service mesh so that services automatically failover and redirect 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-02` so that Consul redirects traffic intended for the `backend` service to a backup instance in `cluster-01` when it detects multiple connection failures to the primary instance. +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. @@ -447,13 +447,13 @@ To configure automatic service failovers and redirect, edit the `ServiceResolver apiVersion: consul.hashicorp.com/v1alpha1 kind: ServiceResolver metadata: - name: backend + name: frontend spec: connectTimeout: 15s failover: '*': targets: - - peer: 'cluster-01' + - peer: 'cluster-02' service: 'backup' namespace: 'default' ``` From 2589f07659343d8763658d0b39bb7710716b3d2a Mon Sep 17 00:00:00 2001 From: boruszak Date: Wed, 28 Sep 2022 16:03:32 -0500 Subject: [PATCH 033/172] Fixes --- website/content/docs/connect/cluster-peering/index.mdx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/website/content/docs/connect/cluster-peering/index.mdx b/website/content/docs/connect/cluster-peering/index.mdx index 567dda3307..0bae34e173 100644 --- a/website/content/docs/connect/cluster-peering/index.mdx +++ b/website/content/docs/connect/cluster-peering/index.mdx @@ -20,7 +20,7 @@ Cluster peering is a process that allows Consul clusters to communicate with eac 3. Export services between clusters. 4. 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). ### Differences between WAN federation and cluster peering @@ -39,11 +39,11 @@ Regardless of whether you connect your clusters through WAN federation or cluste | Forwards service requests for service discovery | ✅ | ❌ | | Shares key/value stores | ✅ | ❌ | - ## 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**: Dyanmic traffic control with a `ServiceResolver` 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. @@ -54,7 +54,7 @@ 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. +- Some dynamic routing features, such as splits and custom routes, cannot target services in a peered cluster. - 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. From efcb466e38e70a52f0239231ba559d8bbfb6701e Mon Sep 17 00:00:00 2001 From: boruszak Date: Wed, 28 Sep 2022 16:09:23 -0500 Subject: [PATCH 034/172] Typo fix --- .../docs/connect/cluster-peering/create-manage-peering.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 7f82c2c326..2dedce8fe4 100644 --- a/website/content/docs/connect/cluster-peering/create-manage-peering.mdx +++ b/website/content/docs/connect/cluster-peering/create-manage-peering.mdx @@ -375,7 +375,7 @@ Next to the name of the peer, click **More** (three horizontal dots) and then ** -## Traffic nanagement between peers +## 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. The following examples updates the [ServiceResolver 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. From 055bb88ee58d7c843255f443e60b13674d0b5bc2 Mon Sep 17 00:00:00 2001 From: boruszak Date: Wed, 28 Sep 2022 16:10:36 -0500 Subject: [PATCH 035/172] Typo fix --- website/content/docs/connect/cluster-peering/index.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/content/docs/connect/cluster-peering/index.mdx b/website/content/docs/connect/cluster-peering/index.mdx index 0bae34e173..a8d60b1407 100644 --- a/website/content/docs/connect/cluster-peering/index.mdx +++ b/website/content/docs/connect/cluster-peering/index.mdx @@ -43,7 +43,7 @@ Regardless of whether you connect your clusters through WAN federation or cluste The cluster peering beta release includes the following features and functionality: -- **Consul v1.14 beta only**: Dyanmic traffic control with a `ServiceResolver` config entry can target failover and redirects to service instances in a peered cluster. +- **Consul v1.14 beta only**: Dynanmic traffic control with a `ServiceResolver` 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. From dfd5903c8e9f7f85dd1af6fa58c1620b784ffeb9 Mon Sep 17 00:00:00 2001 From: boruszak Date: Wed, 28 Sep 2022 16:19:24 -0500 Subject: [PATCH 036/172] Typo fix --- website/content/docs/connect/cluster-peering/index.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/content/docs/connect/cluster-peering/index.mdx b/website/content/docs/connect/cluster-peering/index.mdx index a8d60b1407..67aea76561 100644 --- a/website/content/docs/connect/cluster-peering/index.mdx +++ b/website/content/docs/connect/cluster-peering/index.mdx @@ -43,7 +43,7 @@ Regardless of whether you connect your clusters through WAN federation or cluste The cluster peering beta release includes the following features and functionality: -- **Consul v1.14 beta only**: Dynanmic traffic control with a `ServiceResolver` config entry can target failover and redirects to service instances in a peered cluster. +- **Consul v1.14 beta only**: Dynamic traffic control with a `ServiceResolver` 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. From fc5fdc27d04901519d2f47fa6343d9ded5133c77 Mon Sep 17 00:00:00 2001 From: trujillo-adam Date: Wed, 28 Sep 2022 20:29:05 -0700 Subject: [PATCH 037/172] applied feedback from review --- website/content/docs/lambda/index.mdx | 8 ++-- .../docs/lambda/invoke-from-lambda.mdx | 40 +++++++++---------- .../docs/lambda/registration/automate.mdx | 2 +- .../docs/lambda/registration/index.mdx | 4 +- 4 files changed, 28 insertions(+), 26 deletions(-) diff --git a/website/content/docs/lambda/index.mdx b/website/content/docs/lambda/index.mdx index 51f46d6ee6..07cb818b8c 100644 --- a/website/content/docs/lambda/index.mdx +++ b/website/content/docs/lambda/index.mdx @@ -14,7 +14,7 @@ You can configure Consul to allow services in your mesh to invoke Lambda functio 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. -Refer to [Register Lambda Functions Overview](TODO) for additional information about registering Lambda functions into Consul. +Refer to [Lambda Function Registration Requirements](/docs/lambda/registration/index) for additional information about registering Lambda functions into Consul. ## Invoke Lambda functions from Consul service mesh @@ -22,13 +22,13 @@ After registering AWS Lambda functions, you can invoke Lambda functions from the Refer to Invoke Lambda Functions from Services for details. -## Invoke mesh service from Lambda function +## Invoke mesh services from Lambda function -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 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. +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](TODO) for additional information about registering Lambda functions into Consul. +Refer to [Invoke Services from Lambda Functions](/docs/lambda/invoke-from-lambda) for additional information about registering Lambda functions into Consul. ## Cross-datacenter communication diff --git a/website/content/docs/lambda/invoke-from-lambda.mdx b/website/content/docs/lambda/invoke-from-lambda.mdx index 6b00b347ca..0ef85ec1bb 100644 --- a/website/content/docs/lambda/invoke-from-lambda.mdx +++ b/website/content/docs/lambda/invoke-from-lambda.mdx @@ -13,11 +13,11 @@ This topic describes how to invoke services in the mesh from Lambda functions re The following steps describe the process: -1. Deploy the services you want to allow Lambda to invoke. +1. Deploy the services you want to allow the Lambda function to invoke. 1. (Optional) Enable L7 traffic management in the local datacenter. -1. Deploy the mesh gateway -1. Deploy the Lambda registrator -1. Invoke the Lambda function +1. Deploy the mesh gateway. +1. Deploy the Lambda registrator. +1. Invoke the 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 Lambdas documentation](https://docs.aws.amazon.com/lambda/latest/dg/invocation-layers.html) for instructions on how to add layers to your Lambda functions. @@ -120,27 +120,27 @@ The mesh gateway must be running and registered to the Lambda function’s Consu ## 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 `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. -When the Lambda function is invoked, the extension 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. +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: ```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. You can create the layer manually using the AWS CLI or AWS Console, but we recommend using Terraform: +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: - ``` - 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" - } - ``` + ``` + 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" + } + ``` @@ -197,7 +197,7 @@ func main() { ## 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 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: +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: @@ -250,7 +250,7 @@ func main() { ``` -1. Configure and deploy the Lambda function. Refer to the [Lambda function configuration](#lambda-function-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: +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: @@ -277,9 +277,9 @@ func main() { 1. Issue the `terraform apply` command and Consul automatically configures a service for the Lambda function. -### Lambda function configuration +### Lambda extension configuration -Define the following environment variables to configure each Lambda function. The configurations apply to each Lambda function in your environment: +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 | | --- | --- | --- | @@ -287,7 +287,7 @@ Define the following environment variables to configure each Lambda function. T | `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. We recommend configuring an acceptable value to preview stale certificates. | `“5m”` | +| `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 the 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 diff --git a/website/content/docs/lambda/registration/automate.mdx b/website/content/docs/lambda/registration/automate.mdx index 16f04e3cf3..c45ef6899c 100644 --- a/website/content/docs/lambda/registration/automate.mdx +++ b/website/content/docs/lambda/registration/automate.mdx @@ -13,7 +13,7 @@ This topic describes how to automate Lambda function registration using the Cons 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](TODO) for additional information. +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. diff --git a/website/content/docs/lambda/registration/index.mdx b/website/content/docs/lambda/registration/index.mdx index eaa797728f..1a8ad973cb 100644 --- a/website/content/docs/lambda/registration/index.mdx +++ b/website/content/docs/lambda/registration/index.mdx @@ -65,8 +65,10 @@ To register a Lambda service with a terminating gateway, add the service to the `Services` field of the terminating gateway's `terminating-gateway` configuration entry. -### Optional: Run a Mesh Gateway +### Run a Mesh Gateway +A mesh gateway is required to enable Lambda functions to invoke mesh services, but optional to enable services to invoke Lambda functions. + You can set up a mesh gateway so that you can invoke Lambda services across datacenters and admin partitions. The mesh gateway must be running and registered in the relevant Consul datacenters and partitions. Refer to the following documentation and tutorials for instructions on how to set up mesh gateways: - [Mesh gateway documentation](/docs/connect/gateways#mesh-gateways) From 816a652795ab02670d8b49900c7a74298b73f6be Mon Sep 17 00:00:00 2001 From: Adam Rowan <92474478+bear359@users.noreply.github.com> Date: Thu, 29 Sep 2022 09:04:27 -0600 Subject: [PATCH 038/172] Update website/content/docs/integrate/partnerships.mdx Co-authored-by: trujillo-adam <47586768+trujillo-adam@users.noreply.github.com> --- website/content/docs/integrate/partnerships.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/content/docs/integrate/partnerships.mdx b/website/content/docs/integrate/partnerships.mdx index 2a545cb1ce..f5b248b987 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. Integrations can be done with any of the three Consul versions: +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 From bb5b46ac8a7f4b80780981d0314321dc63c24c7b Mon Sep 17 00:00:00 2001 From: Adam Rowan <92474478+bear359@users.noreply.github.com> Date: Thu, 29 Sep 2022 09:04:43 -0600 Subject: [PATCH 039/172] Update website/content/docs/integrate/partnerships.mdx Co-authored-by: trujillo-adam <47586768+trujillo-adam@users.noreply.github.com> --- website/content/docs/integrate/partnerships.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/content/docs/integrate/partnerships.mdx b/website/content/docs/integrate/partnerships.mdx index f5b248b987..17f1f0079e 100644 --- a/website/content/docs/integrate/partnerships.mdx +++ b/website/content/docs/integrate/partnerships.mdx @@ -37,7 +37,7 @@ By leveraging Consul's RESTful HTTP API system, prospective partners are able to -> **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). -**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 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. From a77d17f2d5db12222a008b7e2eb6983dc1c96bd0 Mon Sep 17 00:00:00 2001 From: Adam Rowan <92474478+bear359@users.noreply.github.com> Date: Thu, 29 Sep 2022 09:04:59 -0600 Subject: [PATCH 040/172] Update website/content/docs/integrate/partnerships.mdx Co-authored-by: trujillo-adam <47586768+trujillo-adam@users.noreply.github.com> --- website/content/docs/integrate/partnerships.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/content/docs/integrate/partnerships.mdx b/website/content/docs/integrate/partnerships.mdx index 17f1f0079e..2dc5831b0b 100644 --- a/website/content/docs/integrate/partnerships.mdx +++ b/website/content/docs/integrate/partnerships.mdx @@ -35,7 +35,7 @@ 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 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). From 4fdcd0ad0e1aca44f8d6c8394f526abf1d70e40d Mon Sep 17 00:00:00 2001 From: Adam Rowan <92474478+bear359@users.noreply.github.com> Date: Thu, 29 Sep 2022 09:05:11 -0600 Subject: [PATCH 041/172] Update website/content/docs/integrate/partnerships.mdx Co-authored-by: trujillo-adam <47586768+trujillo-adam@users.noreply.github.com> --- website/content/docs/integrate/partnerships.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/content/docs/integrate/partnerships.mdx b/website/content/docs/integrate/partnerships.mdx index 2dc5831b0b..356c962ef8 100644 --- a/website/content/docs/integrate/partnerships.mdx +++ b/website/content/docs/integrate/partnerships.mdx @@ -88,7 +88,7 @@ Here are links to resources, documentation, examples and best practices to guide - [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 & CloudFabrix AIOps Integration](https://bot-docs.cloudfabrix.io/Bots/consul/?h=consul) +- [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) **Network Performance Monitoring (NPM)** From c04d66c05792c9b20714a88c4d60d2b925cf36d1 Mon Sep 17 00:00:00 2001 From: Adam Rowan <92474478+bear359@users.noreply.github.com> Date: Thu, 29 Sep 2022 09:05:21 -0600 Subject: [PATCH 042/172] Update website/content/docs/integrate/partnerships.mdx Co-authored-by: trujillo-adam <47586768+trujillo-adam@users.noreply.github.com> --- website/content/docs/integrate/partnerships.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/content/docs/integrate/partnerships.mdx b/website/content/docs/integrate/partnerships.mdx index 356c962ef8..c7aaa9fb21 100644 --- a/website/content/docs/integrate/partnerships.mdx +++ b/website/content/docs/integrate/partnerships.mdx @@ -101,7 +101,7 @@ Here are links to resources, documentation, examples and best practices to guide - [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 & Alerting** +**Logging and Alerts** - [Consul Integration with iLert](https://docs.ilert.com/integrations/consul) - [Consul Integration with PagerDuty](https://www.pagerduty.com/docs/guides/consul-integration-guide/) From 23d92eae5935a2e6d6c196bbdfa674eb67124133 Mon Sep 17 00:00:00 2001 From: Adam Rowan <92474478+bear359@users.noreply.github.com> Date: Thu, 29 Sep 2022 09:05:28 -0600 Subject: [PATCH 043/172] Update website/content/docs/integrate/partnerships.mdx Co-authored-by: trujillo-adam <47586768+trujillo-adam@users.noreply.github.com> --- website/content/docs/integrate/partnerships.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/content/docs/integrate/partnerships.mdx b/website/content/docs/integrate/partnerships.mdx index c7aaa9fb21..40e2890e1f 100644 --- a/website/content/docs/integrate/partnerships.mdx +++ b/website/content/docs/integrate/partnerships.mdx @@ -107,7 +107,7 @@ Here are links to resources, documentation, examples and best practices to guide - [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 & Ingress Controller** +**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/) From b8f2f0b2f063a79a85bcd3311e7d52acb91eac56 Mon Sep 17 00:00:00 2001 From: Adam Rowan <92474478+bear359@users.noreply.github.com> Date: Thu, 29 Sep 2022 09:05:55 -0600 Subject: [PATCH 044/172] Update website/content/docs/integrate/partnerships.mdx Co-authored-by: trujillo-adam <47586768+trujillo-adam@users.noreply.github.com> --- website/content/docs/integrate/partnerships.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/content/docs/integrate/partnerships.mdx b/website/content/docs/integrate/partnerships.mdx index 40e2890e1f..f2ea69b268 100644 --- a/website/content/docs/integrate/partnerships.mdx +++ b/website/content/docs/integrate/partnerships.mdx @@ -167,7 +167,7 @@ Here are links to resources, documentation, examples and best practices to guide 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 or Azure 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 and Microsoft Azure so the partner’s application should be able to be deployed or run in AWS or Azure. From e410bf50c8b400a30a69e309de305ab8746d2187 Mon Sep 17 00:00:00 2001 From: Adam Rowan <92474478+bear359@users.noreply.github.com> Date: Thu, 29 Sep 2022 09:06:10 -0600 Subject: [PATCH 045/172] Update website/content/docs/integrate/partnerships.mdx Co-authored-by: trujillo-adam <47586768+trujillo-adam@users.noreply.github.com> --- website/content/docs/integrate/partnerships.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/content/docs/integrate/partnerships.mdx b/website/content/docs/integrate/partnerships.mdx index f2ea69b268..f98c4deb24 100644 --- a/website/content/docs/integrate/partnerships.mdx +++ b/website/content/docs/integrate/partnerships.mdx @@ -169,7 +169,7 @@ The only knowledge necessary to write a plugin is basic command-line skills and **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 and Microsoft Azure so the partner’s application should be able to be deployed or run in AWS or Azure. +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: From 15ef06f7426fb36e834f0e162cd150e2fbfdcefa Mon Sep 17 00:00:00 2001 From: Adam Rowan <92474478+bear359@users.noreply.github.com> Date: Thu, 29 Sep 2022 09:06:31 -0600 Subject: [PATCH 046/172] Update website/content/docs/integrate/partnerships.mdx Co-authored-by: trujillo-adam <47586768+trujillo-adam@users.noreply.github.com> --- website/content/docs/integrate/partnerships.mdx | 1 + 1 file changed, 1 insertion(+) diff --git a/website/content/docs/integrate/partnerships.mdx b/website/content/docs/integrate/partnerships.mdx index f98c4deb24..1d58a212a1 100644 --- a/website/content/docs/integrate/partnerships.mdx +++ b/website/content/docs/integrate/partnerships.mdx @@ -177,6 +177,7 @@ HCP Consul is currently only deployed on AWS and Microsoft Azure, so your applic - [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. From 8056242f1615d6b04883083660ca1b3fc5ed9ccd Mon Sep 17 00:00:00 2001 From: John Cowen Date: Thu, 29 Sep 2022 17:38:41 +0100 Subject: [PATCH 047/172] ui: Add more acceptance tests for cluster peering (#14707) * ui: Add more acceptance tests for cluster peering * Lint --- .../app/components/consul/peer/list/index.hbs | 2 +- .../consul/peer/list/test-support.js | 3 +- .../app/templates/dc/peers/index.hbs | 1 + .../tests/acceptance/dc/peers/create.feature | 43 +++++++++++++++++ .../tests/acceptance/dc/peers/delete.feature | 47 +++++++++++++++++++ .../acceptance/dc/peers/establish.feature | 30 ++++++++++++ .../acceptance/dc/peers/regenerate.feature | 21 +++++++++ .../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 +- 12 files changed, 186 insertions(+), 3 deletions(-) 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 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..2370fa76cf 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)}} 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/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/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, From cf6faa4d59fa1c4da3489ba9ac7158769a28173b Mon Sep 17 00:00:00 2001 From: John Cowen Date: Thu, 29 Sep 2022 17:39:06 +0100 Subject: [PATCH 048/172] ui: Amends to existing topology notice/banner texts (#14527) * ui: Amends to existing topology notice/banner texts * Changelog * Update ui/packages/consul-ui/translations/routes/en-us.yaml Co-authored-by: Jared Kirschner <85913323+jkirschner-hashicorp@users.noreply.github.com> Co-authored-by: Jared Kirschner <85913323+jkirschner-hashicorp@users.noreply.github.com> --- .changelog/14527.txt | 3 ++ .../consul-ui/translations/routes/en-us.yaml | 29 ++++++++++++------- 2 files changed, 21 insertions(+), 11 deletions(-) create mode 100644 .changelog/14527.txt 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/ui/packages/consul-ui/translations/routes/en-us.yaml b/ui/packages/consul-ui/translations/routes/en-us.yaml index d2f5ef0adb..69178bb5e8 100644 --- a/ui/packages/consul-ui/translations/routes/en-us.yaml +++ b/ui/packages/consul-ui/translations/routes/en-us.yaml @@ -185,25 +185,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 +213,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: From 38b5367d42b1b16ae86cc593439c5594099708b8 Mon Sep 17 00:00:00 2001 From: John Cowen Date: Thu, 29 Sep 2022 17:41:57 +0100 Subject: [PATCH 049/172] ui: Move nvmrc to the root of the workspace (#14567) --- ui/{packages/consul-ui => }/.nvmrc | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename ui/{packages/consul-ui => }/.nvmrc (100%) 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 From 4ba260958c04323c133d86773e301b2ea6809b93 Mon Sep 17 00:00:00 2001 From: Ashwin Venkatesh Date: Thu, 29 Sep 2022 13:19:04 -0400 Subject: [PATCH 050/172] bug: watch local mesh gateways in non-default partitions with agentless (#14799) --- agent/proxycfg/upstreams.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/agent/proxycfg/upstreams.go b/agent/proxycfg/upstreams.go index 7f52924dd2..a2dc38b9d6 100644 --- a/agent/proxycfg/upstreams.go +++ b/agent/proxycfg/upstreams.go @@ -319,7 +319,7 @@ func (s *handlerUpstreams) resetWatchesFromChain( } case structs.MeshGatewayModeLocal: gk = GatewayKey{ - Partition: s.source.NodePartitionOrDefault(), + Partition: s.proxyID.PartitionOrDefault(), Datacenter: s.source.Datacenter, } } From df7b7a6b3dc953ed3e864fd64dc281dfe7dd26e7 Mon Sep 17 00:00:00 2001 From: nrichu-hcp Date: Thu, 29 Sep 2022 13:58:43 -0400 Subject: [PATCH 051/172] draft release notes --- .../docs/release-notes/consul-k8s/v0_49_x.mdx | 66 +++++++++++++++++++ website/data/docs-nav-data.json | 4 ++ 2 files changed, 70 insertions(+) create mode 100644 website/content/docs/release-notes/consul-k8s/v0_49_x.mdx 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..9393c19f9c --- /dev/null +++ b/website/content/docs/release-notes/consul-k8s/v0_49_x.mdx @@ -0,0 +1,66 @@ +--- +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**: This release introduces the Consul CNI Plugin for Consul on Kubernetes, to allow for configuring traffic redirection rules without escalated container privileges such as `CAP_NET_ADMIN`. Refer to [Enable the Consul CNI Plugin](/docs/k8s/installation/install#enable-the-consul-cni-plugin) for more details. The Consul CNI Plugin is supported for Consul K8s 0.49.0+ and Consul 1.13.1+. + +- **Kubernetes 1.24 Support**: Add support for Kubernetes 1.24 where ServiceAccounts no longer have long-term JWT tokens. [[GH-1431](https://github.com/hashicorp/consul-k8s/pull/1431)] + +- **MaxInboundConnections in service-defaults CRD**: Add support for MaxInboundConnections on the Service Defaults CRD. [[GH-1437](https://github.com/hashicorp/consul-k8s/pull/1437)] + +- **API Gateway: ACL auth when using WAN Federation**: Configure ACL auth for controller correctly when deployed in secondary datacenter with federation enabled [[GH-1462](https://github.com/hashicorp/consul-k8s/pull/1462)] + +## What has Changed + +- **Kubernetes 1.24 Support for multiport applications require Kubernetes secrets**: Users deploying multiple services to the same Pod (multiport) on Kubernetes 1.24+ must also deploy a Kubernetes secret for each ServiceAccount associated with the Consul service. The name of the Secret must match the ServiceAccount name and be of type `kubernetes.io/service-account-token` +Example: + + ```yaml + apiVersion: v1 + kind: Secret + metadata: + name: svc1 + annotations: + kubernetes.io/service-account.name: svc1 + type: kubernetes.io/service-account-token + --- + apiVersion: v1 + kind: Secret + metadata: + name: svc2 + annotations: + kubernetes.io/service-account.name: svc2 + type: kubernetes.io/service-account-token + ``` + +## Supported Software + +- Consul 1.11.x, Consul 1.12.x and Consul 1.13.1+ +- Kubernetes 1.19-1.24 +- Kubectl 1.19+ +- 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: + +- Consul CNI Plugin currently does not support RedHat OpenShift as the CNI Plugin Daemonset requires additional SecurityContextConstraint objects to run on OpenShift. Support for OpenShift will be added in an upcoming release. + +## 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/data/docs-nav-data.json b/website/data/docs-nav-data.json index 6b94e594bd..5004b0c949 100644 --- a/website/data/docs-nav-data.json +++ b/website/data/docs-nav-data.json @@ -141,6 +141,10 @@ { "title": "Consul K8s", "routes": [ + { + "title": "v0.49.x", + "path": "release-notes/consul-k8s/v0_49_x" + }, { "title": "v0.48.x", "path": "release-notes/consul-k8s/v0_48_x" From 4d1fb3b11b3bf2766f9bd6cfe6bf84984d500b9b Mon Sep 17 00:00:00 2001 From: trujillo-adam Date: Thu, 29 Sep 2022 11:13:19 -0700 Subject: [PATCH 052/172] applied additional feedback from review --- website/content/docs/lambda/index.mdx | 16 +------- .../docs/lambda/invoke-from-lambda.mdx | 34 +---------------- .../docs/lambda/registration/automate.mdx | 4 +- .../docs/lambda/registration/index.mdx | 37 +++++++++++-------- 4 files changed, 28 insertions(+), 63 deletions(-) diff --git a/website/content/docs/lambda/index.mdx b/website/content/docs/lambda/index.mdx index 07cb818b8c..4f299fd9f7 100644 --- a/website/content/docs/lambda/index.mdx +++ b/website/content/docs/lambda/index.mdx @@ -30,18 +30,6 @@ You can also add the `consul-lambda-extension` plugin as a layer in your Lambda Refer to [Invoke Services from Lambda Functions](/docs/lambda/invoke-from-lambda) for additional information about registering Lambda functions into Consul. -## Cross-datacenter communication +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. -You can use the following Consul features to send cross-datacenter requests between Lambda functions and mesh services. - -### Mesh gateway WAN federation - -Mesh gateways enable you to route traffic to services within and across Consul datacenters. WAN federation refers to designating a _primary datacenter_ that contains authoritative information about all datacenters, including service mesh configurations and access control list (ACL) resources. Refer to [Mesh Gateways for WAN Federation](/docs/connect/gateways/mesh-gateway/wan-federation-via-mesh-gateways) for additional information. - -Note that mesh gateways do not implement L7 traffic management by default. As a result, requests from Lambda functions ignore service routes and splitters. - -#### Admin partitions - -If admin partitions are enabled and the datacenters are federated across the WAN using mesh gateways, then you can only route requests from Lambda functions by applying an [`exported-services`](/docs/connect/config-entries/exported-services) configuration entry to export their service instances. This is required even if the upstream for the Lambda function is in the same admin partition. Otherwise, Consul does not populate the mesh gateways with the routing information. - -You can also use the [admin partitions](/docs/enterprise/partitions) feature included with Consul Enterprise to define separate administrative areas within a datacenter. If admin partitions are not enabled and the datacenters are federated across the WAN using mesh gateways, then you can route all services through the mesh gateways by default. You do not need to use the [`exported-services`](/docs/connect/config-entries/exported-services) configuration entry to export service instances. \ No newline at end of file +Note that mesh gateways do not implement L7 traffic management by default. 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/invoke-from-lambda.mdx b/website/content/docs/lambda/invoke-from-lambda.mdx index 0ef85ec1bb..78c9a87671 100644 --- a/website/content/docs/lambda/invoke-from-lambda.mdx +++ b/website/content/docs/lambda/invoke-from-lambda.mdx @@ -14,7 +14,6 @@ This topic describes how to invoke services in the mesh from Lambda functions re The following steps describe the process: 1. Deploy the services you want to allow the Lambda function to invoke. -1. (Optional) Enable L7 traffic management in the local datacenter. 1. Deploy the mesh gateway. 1. Deploy the Lambda registrator. 1. Invoke the the Lambda function. @@ -25,7 +24,7 @@ The layer runs an external Lambda extension that starts a sidecar proxy. The pro ## Prerequisites -You must deploy the destination services and mesh gateway prior to deploying your Lambda service with the `consul-lambda-extension` layer. It’s not required, but you can also enable L7 traffic management in the local datacenter prior to implementing the `consul-lambda-extension` layer. +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 @@ -79,36 +78,7 @@ spec: serviceAccountName: static-server ``` -### Enable L7 traffic management (optional) -Mesh gateways do not implement L7 traffic management features, but you can enable L7 in the local data center so that your service can use service resolvers, splitters, and routers. - -1. Define an `exported-services` configuration entry. Refer to [Exported Services](/docs/connect/config-entries/exported-services) for additional information. The following example exports `static-server` service instances to a peered cluster specified in the `PeerName` field. - - - - ```hcl - Kind = "exported-services" - Name = "default" - Services = [ - { - Name = "static-server" - Consumers = [ - { - PeerName = "" - } - ] - } - ] - ``` - - - -1. Apply the configuration using the Consul CLI or by using a custom resource definition (CRD) if Consul is running on Kubernetes. The following example shows the command line usage: - - ```shell-session - $ consul config write static-server-configuration-entry.hcl - ``` ### 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: @@ -292,7 +262,7 @@ Define the following environment variables in your Lambda functions to configure ## Invoke the Lambda function -You can create an _intention_ in Consul prior to invoking the Lambda function. Intentions define access control for services in the mesh. Refer to [Service Mesh Intentions](/docs/connect/intentions) for additional information. +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.: diff --git a/website/content/docs/lambda/registration/automate.mdx b/website/content/docs/lambda/registration/automate.mdx index c45ef6899c..ac607f414f 100644 --- a/website/content/docs/lambda/registration/automate.mdx +++ b/website/content/docs/lambda/registration/automate.mdx @@ -7,7 +7,7 @@ description: >- # Automate Lambda Function Registeration -This topic describes how to automate Lambda function registration using the Consul Lambda registrator module for Terraform. +This topic describes how to automate Lambda function registration using Consul's Lambda registrator module for Terraform. ## Introduction @@ -43,7 +43,7 @@ Verify that your environment meets the requirements specified in [Lambda Functio ## Configuration -The Lambda registrator module stores data in the AWS parameter store. You can configure the type of data stored and how to store it. +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 diff --git a/website/content/docs/lambda/registration/index.mdx b/website/content/docs/lambda/registration/index.mdx index 1a8ad973cb..9588a4e28f 100644 --- a/website/content/docs/lambda/registration/index.mdx +++ b/website/content/docs/lambda/registration/index.mdx @@ -6,7 +6,7 @@ description: >- --- # Lambda Function Registration Requirements -Verify that your network meets the requirements and that you have completed the prerequisites before registering Lambda functions. +Verify that your environment meets the requirements and that you have completed the prerequisites before registering Lambda functions. ## Introduction @@ -53,23 +53,16 @@ enables an IAM user or role to invoke the `example` Lambda function: Define AWS IAM credentials in environment variables, EC2 metadata or ECS metadata. On [AWS EKS](https://aws.amazon.com/eks/), associate an IAM role with the proxy's `ServiceAccount`. Refer to the [AWS IAM roles for service accounts](https://docs.aws.amazon.com/eks/latest/userguide/iam-roles-for-service-accounts.html) documentation for instructions. -### Optional: Set up a Terminating Gateway +### Mesh gateway -If you intend to invoke Lambda services through a terminating gateway, the gateway must be registered and running in the Consul datacenter. Refer to the following documentation and tutorials for instructions on how to set up a terminating gateway: +A mesh gateway is required in the following scenarios: -- [Terminating gateways documentation](/docs/connect/gateways#terminating-gateways) -- [Terminating gateways on Kubernetes documentation](/docs/k8s/connect/terminating-gateways) -- [Connect External Services to Consul With Terminating Gateways tutorial](https://learn.hashicorp.com/tutorials/consul/teminating-gateways-connect-external-services) +* Invoking mesh services from Lambda functions +* Invoking Lambda functions from a service deployed to a separate Consul data center -To register a Lambda service with a terminating gateway, add the service to the -`Services` field of the terminating gateway's `terminating-gateway` -configuration entry. - -### Run a Mesh Gateway - -A mesh gateway is required to enable Lambda functions to invoke mesh services, but optional to enable services to invoke Lambda functions. +Mesh gateways are optional for enabling services to invoke Lambda functions if they are in the same datacenter. -You can set up a mesh gateway so that you can invoke Lambda services across datacenters and admin partitions. The mesh gateway must be running and registered in the relevant Consul datacenters and partitions. Refer to the following documentation and tutorials for instructions on how to set up mesh gateways: +The mesh gateway must be running and registered in the relevant Consul datacenters and admin partitions. Refer to the following documentation and tutorials for instructions on how to set up mesh gateways: - [Mesh gateway documentation](/docs/connect/gateways#mesh-gateways) - [Connect Services Across Datacenters with Mesh Gateways tutorial](https://learn.hashicorp.com/tutorials/consul/service-mesh-gateways) @@ -77,4 +70,18 @@ You can set up a mesh gateway so that you can invoke Lambda services across data When using admin partitions, you must add Lambda services to the `Services` field of [the `exported-services` configuration -entry](/docs/connect/config-entries/exported-services). \ No newline at end of file +entry](/docs/connect/config-entries/exported-services). + +### Optional: Terminating gateway + +A terminating gateway is an access point in a Consul datacenter to an external service or node. Terminating gateways are optional when invoking Lambda functions from a mesh service, but they do not play a role when invoking services from Lambda functions. + +Refer to the following documentation and tutorials for instructions on how to set up a terminating gateway: + +- [Terminating gateways documentation](/docs/connect/gateways#terminating-gateways) +- [Terminating gateways on Kubernetes documentation](/docs/k8s/connect/terminating-gateways) +- [Connect External Services to Consul With Terminating Gateways tutorial](https://learn.hashicorp.com/tutorials/consul/teminating-gateways-connect-external-services) + +To register a Lambda service with a terminating gateway, add the service to the +`Services` field of the terminating gateway's `terminating-gateway` +configuration entry. \ No newline at end of file From 12ffd608c052c65e7ec8a8f429bf168a679c3533 Mon Sep 17 00:00:00 2001 From: trujillo-adam <47586768+trujillo-adam@users.noreply.github.com> Date: Thu, 29 Sep 2022 11:40:59 -0700 Subject: [PATCH 053/172] Apply suggestions from code review Co-authored-by: Jeff Boruszak <104028618+boruszak@users.noreply.github.com> --- website/content/docs/architecture/index.mdx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/website/content/docs/architecture/index.mdx b/website/content/docs/architecture/index.mdx index c579d8edab..e75137ca7b 100644 --- a/website/content/docs/architecture/index.mdx +++ b/website/content/docs/architecture/index.mdx @@ -77,7 +77,7 @@ Each Consul datacenter maintains its own catalog of services and their health. B ### WAN federation -WAN federation refers to designating a _primary datacenter_ that contains authoritative information about all datacenters, including service mesh configurations and access control list (ACL) resources. +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`. @@ -105,7 +105,7 @@ In the following diagram, the servers in each data center participate in a WAN g
    -### Peering clusters (beta) +### 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. From 80e51ff907b2b68ac42e0d29773e480d07e59bd1 Mon Sep 17 00:00:00 2001 From: Eric Haberkorn Date: Thu, 29 Sep 2022 15:37:19 -0400 Subject: [PATCH 054/172] Add exported services event to cluster peering replication. (#14797) --- .changelog/14797.txt | 3 + agent/consul/leader_peering_test.go | 211 ++++- agent/consul/state/peering.go | 2 + .../services/peerstream/replication.go | 153 ++-- .../services/peerstream/server.go | 1 + .../services/peerstream/stream_resources.go | 8 + .../services/peerstream/stream_test.go | 804 ++++++++++-------- .../services/peerstream/stream_tracker.go | 34 +- .../peerstream/subscription_manager.go | 28 +- .../peerstream/subscription_manager_test.go | 64 +- .../services/peerstream/subscription_state.go | 4 + .../services/peerstream/testing.go | 20 + agent/peering_endpoint_test.go | 4 + agent/rpc/peering/service.go | 4 +- agent/rpc/peering/service_test.go | 84 +- api/peering.go | 4 + api/peering_test.go | 4 +- command/peering/list/list.go | 4 +- command/peering/read/read.go | 4 +- proto/pbpeering/peering.gen.go | 4 + proto/pbpeering/peering.pb.go | 524 ++++++------ proto/pbpeering/peering.proto | 6 + 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 +- website/content/docs/agent/telemetry.mdx | 10 +- 28 files changed, 1408 insertions(+), 874 deletions(-) create mode 100644 .changelog/14797.txt 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/agent/consul/leader_peering_test.go b/agent/consul/leader_peering_test.go index 331e7324ad..35d6edc108 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" @@ -162,6 +163,186 @@ func testLeader_PeeringSync_Lifecycle_ClientDeletion(t *testing.T, enableTLS boo }) } +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. + ports := freeport.GetN(t, 1) + dialingServerPort := ports[0] + + _, acceptor := testServerWithConfig(t, func(c *Config) { + c.NodeName = "acceptor" + c.Datacenter = "dc1" + c.TLSConfig.Domain = "consul" + }) + 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 = "dc1" + c.GRPCPort = dialingServerPort + }) + testrpc.WaitForLeader(t, dialer.RPC, "dc1") + + // 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{{PeerName: "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_ServerDeletion(t *testing.T) { t.Run("without-tls", func(t *testing.T) { testLeader_PeeringSync_Lifecycle_AcceptorDeletion(t, false) @@ -818,8 +999,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{ @@ -946,13 +1127,15 @@ 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, int(resp.Peering.ImportedServiceCount)) + require.Equal(r, tc.expectedImportedServsCount, len(resp.Peering.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, int(resp2.Peerings[0].ImportedServiceCount)) + require.Equal(r, tc.expectedExportedServsCount, len(resp2.Peerings[0].ImportedServices)) }) // Check that exported services count on S1 are what we expect @@ -961,13 +1144,15 @@ 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, int(resp.Peering.ExportedServiceCount)) + require.Equal(r, tc.expectedImportedServsCount, len(resp.Peering.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, int(resp2.Peerings[0].ExportedServiceCount)) + require.Equal(r, tc.expectedExportedServsCount, len(resp2.Peerings[0].ExportedServices)) }) }) } @@ -1061,17 +1246,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() diff --git a/agent/consul/state/peering.go b/agent/consul/state/peering.go index eef76aa726..ce7b80c39c 100644 --- a/agent/consul/state/peering.go +++ b/agent/consul/state/peering.go @@ -588,6 +588,8 @@ func (s *Store) PeeringWrite(idx uint64, req *pbpeering.PeeringWriteRequest) err // We may need to avoid clobbering existing values. req.Peering.ImportedServiceCount = existing.ImportedServiceCount req.Peering.ExportedServiceCount = existing.ExportedServiceCount + req.Peering.ImportedServices = existing.ImportedServices + req.Peering.ExportedServices = existing.ExportedServices req.Peering.CreateIndex = existing.CreateIndex req.Peering.ModifyIndex = idx } else { 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..7c04ec82c3 100644 --- a/agent/grpc-external/services/peerstream/server.go +++ b/agent/grpc-external/services/peerstream/server.go @@ -122,5 +122,6 @@ 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) 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..b42f17c5ce 100644 --- a/agent/grpc-external/services/peerstream/stream_resources.go +++ b/agent/grpc-external/services/peerstream/stream_resources.go @@ -360,6 +360,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 +625,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 { diff --git a/agent/grpc-external/services/peerstream/stream_test.go b/agent/grpc-external/services/peerstream/stream_test.go index 977f7d565c..93a0efebb3 100644 --- a/agent/grpc-external/services/peerstream/stream_test.go +++ b/agent/grpc-external/services/peerstream/stream_test.go @@ -126,7 +126,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 +149,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 +518,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 +557,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") @@ -576,6 +574,8 @@ func TestStreamResources_Server_StreamTracker(t *testing.T) { var lastSendAck time.Time + client.DrainStream(t) + testutil.RunStep(t, "ack tracked as success", func(t *testing.T) { ack := &pbpeerstream.ReplicationMessage{ Payload: &pbpeerstream.ReplicationMessage_Request_{ @@ -594,8 +594,9 @@ func TestStreamResources_Server_StreamTracker(t *testing.T) { require.NoError(t, err) expect := Status{ - Connected: true, - LastAck: lastSendAck, + Connected: true, + LastAck: lastSendAck, + ExportedServices: []string{}, } retry.Run(t, func(r *retry.R) { @@ -630,10 +631,11 @@ 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, + LastAck: lastSendAck, + LastNack: lastNack, + LastNackMessage: lastNackMsg, + ExportedServices: []string{}, } retry.Run(t, func(r *retry.R) { @@ -661,27 +663,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,9 +671,15 @@ 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, @@ -700,9 +687,7 @@ func TestStreamResources_Server_StreamTracker(t *testing.T) { LastNack: lastNack, LastNackMessage: lastNackMsg, LastRecvResourceSuccess: lastRecvResourceSuccess, - ImportedServices: map[string]struct{}{ - api.String(): {}, - }, + ExportedServices: []string{}, } retry.Run(t, func(r *retry.R) { @@ -751,8 +736,6 @@ func TestStreamResources_Server_StreamTracker(t *testing.T) { lastRecvErrorMsg = `unsupported operation: "OPERATION_UNSPECIFIED"` - api := structs.NewServiceName("api", nil) - expect := Status{ Connected: true, LastAck: lastSendAck, @@ -761,9 +744,7 @@ func TestStreamResources_Server_StreamTracker(t *testing.T) { LastRecvResourceSuccess: lastRecvResourceSuccess, LastRecvError: lastRecvError, LastRecvErrorMessage: lastRecvErrorMsg, - ImportedServices: map[string]struct{}{ - api.String(): {}, - }, + ExportedServices: []string{}, } retry.Run(t, func(r *retry.R) { @@ -783,7 +764,6 @@ 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, @@ -794,9 +774,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,8 +791,6 @@ func TestStreamResources_Server_StreamTracker(t *testing.T) { client.Close() - api := structs.NewServiceName("api", nil) - expect := Status{ Connected: false, DisconnectErrorMessage: lastRecvErrorMsg, @@ -826,9 +802,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) { @@ -908,6 +882,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 +893,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 +922,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 +1027,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{ @@ -1042,23 +1050,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 +1093,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 +1108,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 +1169,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 +1211,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 +1227,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 +1238,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 +1264,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 +1503,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 +1519,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 +1585,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 +1802,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 +1811,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 +1842,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 +1863,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 +1896,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,146 +2030,14 @@ func Test_processResponse_handleUpsert_handleDelete(t *testing.T) { }, }, }, - expectedImportedServicesCount: 1, }, { - name: "upsert two service instances to different nodes", - input: &pbpeerstream.ExportedService{ - Nodes: []*pbservice.CheckServiceNode{ - { - Node: &pbservice.Node{ - ID: "af913374-68ea-41e5-82e8-6ffd3dffc461", - Node: "node-foo", - Partition: remoteMeta.Partition, - PeerName: peerName, - }, - Service: &pbservice.NodeService{ - ID: "api-1", - Service: "api", - EnterpriseMeta: remoteMeta, - PeerName: peerName, - }, - Checks: []*pbservice.HealthCheck{ - { - CheckID: "node-foo-check", - Node: "node-foo", - EnterpriseMeta: remoteMeta, - PeerName: peerName, - }, - { - CheckID: "api-1-check", - ServiceID: "api-1", - Node: "node-foo", - EnterpriseMeta: remoteMeta, - PeerName: peerName, - }, - }, - }, - { - Node: &pbservice.Node{ - ID: "c0f97de9-4e1b-4e80-a1c6-cd8725835ab2", - Node: "node-bar", - Partition: remoteMeta.Partition, - PeerName: peerName, - }, - Service: &pbservice.NodeService{ - ID: "api-2", - Service: "api", - EnterpriseMeta: remoteMeta, - PeerName: peerName, - }, - Checks: []*pbservice.HealthCheck{ - { - CheckID: "node-bar-check", - Node: "node-bar", - EnterpriseMeta: remoteMeta, - PeerName: peerName, - }, - { - CheckID: "api-2-check", - ServiceID: "api-2", - Node: "node-bar", - EnterpriseMeta: remoteMeta, - PeerName: peerName, - }, - }, - }, - }, - }, - expect: map[string]structs.CheckServiceNodes{ - "api": { - { - Node: &structs.Node{ - ID: "c0f97de9-4e1b-4e80-a1c6-cd8725835ab2", - Node: "node-bar", - Partition: defaultMeta.PartitionOrEmpty(), - PeerName: peerName, - }, - Service: &structs.NodeService{ - ID: "api-2", - Service: "api", - EnterpriseMeta: defaultMeta, - PeerName: peerName, - }, - Checks: []*structs.HealthCheck{ - { - CheckID: "node-bar-check", - Node: "node-bar", - EnterpriseMeta: defaultMeta, - PeerName: peerName, - }, - { - CheckID: "api-2-check", - ServiceID: "api-2", - Node: "node-bar", - EnterpriseMeta: defaultMeta, - PeerName: peerName, - }, - }, - }, - { - Node: &structs.Node{ - ID: "af913374-68ea-41e5-82e8-6ffd3dffc461", - Node: "node-foo", - - // The remote billing-ap partition is overwritten for all resources with the local default. - Partition: defaultMeta.PartitionOrEmpty(), - - // The name of the peer "billing" is attached as well. - PeerName: peerName, - }, - Service: &structs.NodeService{ - ID: "api-1", - Service: "api", - EnterpriseMeta: defaultMeta, - PeerName: peerName, - }, - Checks: []*structs.HealthCheck{ - { - CheckID: "node-foo-check", - Node: "node-foo", - EnterpriseMeta: defaultMeta, - PeerName: peerName, - }, - { - CheckID: "api-1-check", - ServiceID: "api-1", - Node: "node-foo", - EnterpriseMeta: defaultMeta, - PeerName: peerName, - }, - }, - }, - }, - }, - expectedImportedServicesCount: 1, - }, - { - name: "receiving a nil input leads to deleting data in the catalog", + name: "deleting a service with an empty exported service event", + exportedServices: []string{"api"}, 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", @@ -2188,35 +2047,11 @@ func Test_processResponse_handleUpsert_handleDelete(t *testing.T) { }, Checks: structs.HealthChecks{ { - Node: "node-bar", + Node: "node-foo", ServiceID: "api-2", CheckID: types.CheckID("api-2-check"), PeerName: peerName, }, - { - Node: "node-bar", - CheckID: types.CheckID("node-bar-check"), - PeerName: peerName, - }, - }, - }, - { - ID: types.NodeID("af913374-68ea-41e5-82e8-6ffd3dffc461"), - Node: "node-foo", - PeerName: peerName, - Service: &structs.NodeService{ - ID: "api-1", - Service: "api", - EnterpriseMeta: defaultMeta, - PeerName: peerName, - }, - Checks: structs.HealthChecks{ - { - Node: "node-foo", - ServiceID: "api-1", - CheckID: types.CheckID("api-1-check"), - PeerName: peerName, - }, { Node: "node-foo", CheckID: types.CheckID("node-foo-check"), @@ -2229,10 +2064,142 @@ func Test_processResponse_handleUpsert_handleDelete(t *testing.T) { expect: map[string]structs.CheckServiceNodes{ "api": {}, }, - expectedImportedServicesCount: 0, }, { - name: "deleting one service name from a node does not delete other service names", + name: "upsert two service instances to different nodes", + exportedServices: []string{"api"}, + input: &pbpeerstream.ExportedService{ + Nodes: []*pbservice.CheckServiceNode{ + { + Node: &pbservice.Node{ + ID: "af913374-68ea-41e5-82e8-6ffd3dffc461", + Node: "node-foo", + Partition: remoteMeta.Partition, + PeerName: peerName, + }, + Service: &pbservice.NodeService{ + ID: "api-1", + Service: "api", + EnterpriseMeta: remoteMeta, + PeerName: peerName, + }, + Checks: []*pbservice.HealthCheck{ + { + CheckID: "node-foo-check", + Node: "node-foo", + EnterpriseMeta: remoteMeta, + PeerName: peerName, + }, + { + CheckID: "api-1-check", + ServiceID: "api-1", + Node: "node-foo", + EnterpriseMeta: remoteMeta, + PeerName: peerName, + }, + }, + }, + { + Node: &pbservice.Node{ + ID: "c0f97de9-4e1b-4e80-a1c6-cd8725835ab2", + Node: "node-bar", + Partition: remoteMeta.Partition, + PeerName: peerName, + }, + Service: &pbservice.NodeService{ + ID: "api-2", + Service: "api", + EnterpriseMeta: remoteMeta, + PeerName: peerName, + }, + Checks: []*pbservice.HealthCheck{ + { + CheckID: "node-bar-check", + Node: "node-bar", + EnterpriseMeta: remoteMeta, + PeerName: peerName, + }, + { + CheckID: "api-2-check", + ServiceID: "api-2", + Node: "node-bar", + EnterpriseMeta: remoteMeta, + PeerName: peerName, + }, + }, + }, + }, + }, + expect: map[string]structs.CheckServiceNodes{ + "api": { + { + Node: &structs.Node{ + ID: "c0f97de9-4e1b-4e80-a1c6-cd8725835ab2", + Node: "node-bar", + Partition: defaultMeta.PartitionOrEmpty(), + PeerName: peerName, + }, + Service: &structs.NodeService{ + ID: "api-2", + Service: "api", + EnterpriseMeta: defaultMeta, + PeerName: peerName, + }, + Checks: []*structs.HealthCheck{ + { + CheckID: "node-bar-check", + Node: "node-bar", + EnterpriseMeta: defaultMeta, + PeerName: peerName, + }, + { + CheckID: "api-2-check", + ServiceID: "api-2", + Node: "node-bar", + EnterpriseMeta: defaultMeta, + PeerName: peerName, + }, + }, + }, + { + Node: &structs.Node{ + ID: "af913374-68ea-41e5-82e8-6ffd3dffc461", + Node: "node-foo", + + // The remote billing-ap partition is overwritten for all resources with the local default. + Partition: defaultMeta.PartitionOrEmpty(), + + // The name of the peer "billing" is attached as well. + PeerName: peerName, + }, + Service: &structs.NodeService{ + ID: "api-1", + Service: "api", + EnterpriseMeta: defaultMeta, + PeerName: peerName, + }, + Checks: []*structs.HealthCheck{ + { + CheckID: "node-foo-check", + Node: "node-foo", + EnterpriseMeta: defaultMeta, + PeerName: peerName, + }, + { + CheckID: "api-1-check", + ServiceID: "api-1", + Node: "node-foo", + EnterpriseMeta: defaultMeta, + PeerName: peerName, + }, + }, + }, + }, + }, + }, + { + name: "deleting one service name from a node does not delete other service names", + exportedServices: []string{"api", "redis"}, seed: []*structs.RegisterRequest{ { ID: types.NodeID("af913374-68ea-41e5-82e8-6ffd3dffc461"), @@ -2320,10 +2287,180 @@ func Test_processResponse_handleUpsert_handleDelete(t *testing.T) { }, }, }, - expectedImportedServicesCount: 1, }, { - name: "service checks are cleaned up when not present in a response", + name: "unexporting a service does not delete other services", + seed: []*structs.RegisterRequest{ + { + ID: types.NodeID("af913374-68ea-41e5-82e8-6ffd3dffc461"), + Node: "node-foo", + PeerName: peerName, + Service: &structs.NodeService{ + ID: "redis-2", + Service: "redis", + EnterpriseMeta: defaultMeta, + PeerName: peerName, + }, + Checks: structs.HealthChecks{ + { + Node: "node-foo", + ServiceID: "redis-2", + CheckID: types.CheckID("redis-2-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", + 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", + PeerName: peerName, + Service: &structs.NodeService{ + ID: "api-1", + Service: "api", + EnterpriseMeta: defaultMeta, + PeerName: peerName, + }, + Checks: structs.HealthChecks{ + { + Node: "node-foo", + ServiceID: "api-1", + CheckID: types.CheckID("api-1-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", + 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{}, + exportedServices: []string{"redis"}, + expect: map[string]structs.CheckServiceNodes{ + // 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, + }, + }, + }, + }, + "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, + }, + }, + }, + }, + }, + }, + { + 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 +2528,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 +2663,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 +2811,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..ccadc23c42 100644 --- a/agent/grpc-external/services/peerstream/stream_tracker.go +++ b/agent/grpc-external/services/peerstream/stream_tracker.go @@ -230,9 +230,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 { @@ -345,22 +345,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 +363,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_manager.go b/agent/grpc-external/services/peerstream/subscription_manager.go index 138449e712..c761c6c611 100644 --- a/agent/grpc-external/services/peerstream/subscription_manager.go +++ b/agent/grpc-external/services/peerstream/subscription_manager.go @@ -124,8 +124,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 +136,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 @@ -435,7 +443,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 +471,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 - } } } } diff --git a/agent/grpc-external/services/peerstream/subscription_manager_test.go b/agent/grpc-external/services/peerstream/subscription_manager_test.go index d81568f0a4..e7363f43db 100644 --- a/agent/grpc-external/services/peerstream/subscription_manager_test.go +++ b/agent/grpc-external/services/peerstream/subscription_manager_test.go @@ -57,9 +57,14 @@ func TestSubscriptionManager_RegisterDeregister(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{}) + }, + func(t *testing.T, got cache.UpdateEvent) { + checkEvent(t, got, gatewayCorrID, 0) + }, + ) // 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 @@ -94,6 +99,9 @@ func TestSubscriptionManager_RegisterDeregister(t *testing.T) { }) 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) }, @@ -437,6 +445,26 @@ func TestSubscriptionManager_RegisterDeregister(t *testing.T) { }, ) }) + + 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{ + {PeerName: "my-other-peering"}, + }, + }, + }, + }) + + expectEvents(t, subCh, + func(t *testing.T, got cache.UpdateEvent) { + checkExportedServices(t, got, []string{}) + }, + ) + }) } func TestSubscriptionManager_InitialSnapshot(t *testing.T) { @@ -490,9 +518,13 @@ 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{}) + }, + func(t *testing.T, got cache.UpdateEvent) { + checkEvent(t, got, gatewayCorrID, 0) + }) // At this point in time we'll have a mesh-gateway notification with no // content stored and handled. @@ -522,6 +554,9 @@ func TestSubscriptionManager_InitialSnapshot(t *testing.T) { }) 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) }, @@ -933,6 +968,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/peering_endpoint_test.go b/agent/peering_endpoint_test.go index 5555fde108..0f5692bad1 100644 --- a/agent/peering_endpoint_test.go +++ b/agent/peering_endpoint_test.go @@ -392,6 +392,8 @@ func TestHTTP_Peering_Read(t *testing.T) { require.Equal(t, uint64(0), apiResp.ImportedServiceCount) require.Equal(t, uint64(0), apiResp.ExportedServiceCount) + require.Equal(t, 0, len(apiResp.ImportedServices)) + require.Equal(t, 0, len(apiResp.ExportedServices)) }) @@ -521,6 +523,8 @@ func TestHTTP_Peering_List(t *testing.T) { for _, p := range apiResp { require.Equal(t, uint64(0), p.ImportedServiceCount) require.Equal(t, uint64(0), p.ExportedServiceCount) + require.Equal(t, 0, len(p.ImportedServices)) + require.Equal(t, 0, len(p.ExportedServices)) } }) } diff --git a/agent/rpc/peering/service.go b/agent/rpc/peering/service.go index 65d508f9c0..e8c468a788 100644 --- a/agent/rpc/peering/service.go +++ b/agent/rpc/peering/service.go @@ -642,7 +642,9 @@ func (s *Server) reconcilePeering(peering *pbpeering.Peering) *pbpeering.Peering cp.State = pbpeering.PeeringState_FAILING } - // add imported & exported services counts + // add imported & exported services + cp.ImportedServices = streamState.ImportedServices + cp.ExportedServices = streamState.ExportedServices cp.ImportedServiceCount = streamState.GetImportedServicesCount() cp.ExportedServiceCount = streamState.GetExportedServicesCount() diff --git a/agent/rpc/peering/service_test.go b/agent/rpc/peering/service_test.go index 8f11ebd147..c25f88614b 100644 --- a/agent/rpc/peering/service_test.go +++ b/agent/rpc/peering/service_test.go @@ -544,14 +544,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) @@ -607,14 +605,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) @@ -794,25 +790,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})) @@ -840,25 +832,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})) diff --git a/api/peering.go b/api/peering.go index 7a98ba9363..5911311bb3 100644 --- a/api/peering.go +++ b/api/peering.go @@ -66,6 +66,10 @@ type Peering struct { ImportedServiceCount uint64 // ExportedServiceCount is the count of how many services are exported to this peering. ExportedServiceCount uint64 + // ImportedServices is the list of services imported from this peering. + ImportedServices []string + // ExportedServices is the list of services exported to this peering. + ExportedServices []string // 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. diff --git a/api/peering_test.go b/api/peering_test.go index 9c299b7a21..9b7974ea44 100644 --- a/api/peering_test.go +++ b/api/peering_test.go @@ -27,7 +27,9 @@ func peerExistsInPeerListings(peer *Peering, peerings []*Peering) bool { (peer.CreateIndex == aPeer.CreateIndex) && (peer.ModifyIndex == aPeer.ModifyIndex) && (peer.ImportedServiceCount == aPeer.ImportedServiceCount) && - (peer.ExportedServiceCount == aPeer.ExportedServiceCount) + (peer.ExportedServiceCount == aPeer.ExportedServiceCount) && + reflect.DeepEqual(peer.ImportedServices, aPeer.ImportedServices) && + reflect.DeepEqual(peer.ExportedServices, aPeer.ExportedServices) if isEqual { return true diff --git a/command/peering/list/list.go b/command/peering/list/list.go index c445e3d57a..ac53c51db3 100644 --- a/command/peering/list/list.go +++ b/command/peering/list/list.go @@ -99,7 +99,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.ImportedServices), len(peer.ExportedServices), meta) result = append(result, line) } @@ -123,7 +123,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..66854e01c3 100644 --- a/command/peering/read/read.go +++ b/command/peering/read/read.go @@ -130,8 +130,8 @@ 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.ImportedServices))) + buffer.WriteString(fmt.Sprintf("Exported Services: %d\n", len(peering.ExportedServices))) buffer.WriteString("\n") buffer.WriteString(fmt.Sprintf("Create Index: %d\n", peering.CreateIndex)) diff --git a/proto/pbpeering/peering.gen.go b/proto/pbpeering/peering.gen.go index 5707e3b6c5..0164489baa 100644 --- a/proto/pbpeering/peering.gen.go +++ b/proto/pbpeering/peering.gen.go @@ -78,6 +78,8 @@ func PeeringToAPI(s *Peering, t *api.Peering) { t.PeerServerAddresses = s.PeerServerAddresses t.ImportedServiceCount = s.ImportedServiceCount t.ExportedServiceCount = s.ExportedServiceCount + t.ImportedServices = s.ImportedServices + t.ExportedServices = s.ExportedServices t.CreateIndex = s.CreateIndex t.ModifyIndex = s.ModifyIndex } @@ -97,6 +99,8 @@ func PeeringFromAPI(t *api.Peering, s *Peering) { s.PeerServerAddresses = t.PeerServerAddresses s.ImportedServiceCount = t.ImportedServiceCount s.ExportedServiceCount = t.ExportedServiceCount + s.ImportedServices = t.ImportedServices + s.ExportedServices = t.ExportedServices s.CreateIndex = t.CreateIndex s.ModifyIndex = t.ModifyIndex } diff --git a/proto/pbpeering/peering.pb.go b/proto/pbpeering/peering.pb.go index 8fdff02468..bd4d28d1b1 100644 --- a/proto/pbpeering/peering.pb.go +++ b/proto/pbpeering/peering.pb.go @@ -324,6 +324,10 @@ type Peering struct { 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"` + // ImportedServices is the list of services imported from this peering. + ImportedServices []string `protobuf:"bytes,15,rep,name=ImportedServices,proto3" json:"ImportedServices,omitempty"` + // ExportedServices is the list of services exported to this peering. + ExportedServices []string `protobuf:"bytes,16,rep,name=ExportedServices,proto3" json:"ExportedServices,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:"-"` @@ -448,6 +452,20 @@ func (x *Peering) GetExportedServiceCount() uint64 { return 0 } +func (x *Peering) GetImportedServices() []string { + if x != nil { + return x.ImportedServices + } + return nil +} + +func (x *Peering) GetExportedServices() []string { + if x != nil { + return x.ExportedServices + } + return nil +} + func (x *Peering) GetCreateIndex() uint64 { if x != nil { return x.CreateIndex @@ -2189,7 +2207,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, 0xe5, 0x05, 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, @@ -2222,268 +2240,274 @@ var file_proto_pbpeering_peering_proto_rawDesc = []byte{ 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, - 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, - 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, - 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, + 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x2a, 0x0a, 0x10, 0x49, + 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x18, + 0x0f, 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, 0x10, 0x20, 0x03, 0x28, + 0x09, 0x52, 0x10, 0x45, 0x78, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x53, 0x65, 0x72, 0x76, 0x69, + 0x63, 0x65, 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, 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, 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, + 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, 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, 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, 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, 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, + 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, 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, 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, - 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, 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, + 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, 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, + 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, 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, - 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, + 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, 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, 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, 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, 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, + 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, 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, - 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, 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, 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, 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, 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 ( diff --git a/proto/pbpeering/peering.proto b/proto/pbpeering/peering.proto index cc37c6041c..4025356e0b 100644 --- a/proto/pbpeering/peering.proto +++ b/proto/pbpeering/peering.proto @@ -189,6 +189,12 @@ message Peering { // ExportedServiceCount is the count of how many services are exported to this peering. uint64 ExportedServiceCount = 14; + // ImportedServices is the list of services imported from this peering. + repeated string ImportedServices = 15; + + // ExportedServices is the list of services exported to this peering. + repeated string ExportedServices = 16; + // CreateIndex is the Raft index at which the Peering was created. // @gotags: bexpr:"-" uint64 CreateIndex = 11; 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/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 | From 622ccb6ce32f17aac5c9e76b3e561d79d2f847c0 Mon Sep 17 00:00:00 2001 From: boruszak Date: Thu, 29 Sep 2022 14:48:09 -0500 Subject: [PATCH 055/172] Fixes --- website/content/docs/k8s/dataplane/consul-dataplane.mdx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/website/content/docs/k8s/dataplane/consul-dataplane.mdx b/website/content/docs/k8s/dataplane/consul-dataplane.mdx index 9ae5d41dab..d432aadaee 100644 --- a/website/content/docs/k8s/dataplane/consul-dataplane.mdx +++ b/website/content/docs/k8s/dataplane/consul-dataplane.mdx @@ -13,6 +13,10 @@ The `consul-dataplane` command interacts with the binary for [simplified service 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: From 35303f5f79c2bbe1f02bca1a1622d6667fc00dbc Mon Sep 17 00:00:00 2001 From: boruszak Date: Thu, 29 Sep 2022 14:52:31 -0500 Subject: [PATCH 056/172] Page location/nav edits --- .../dataplane/consul-dataplane.mdx | 0 .../docs/{k8s => connect}/dataplane/index.mdx | 2 + website/data/docs-nav-data.json | 39 ++++++++++++------- 3 files changed, 28 insertions(+), 13 deletions(-) rename website/content/docs/{k8s => connect}/dataplane/consul-dataplane.mdx (100%) rename website/content/docs/{k8s => connect}/dataplane/index.mdx (98%) diff --git a/website/content/docs/k8s/dataplane/consul-dataplane.mdx b/website/content/docs/connect/dataplane/consul-dataplane.mdx similarity index 100% rename from website/content/docs/k8s/dataplane/consul-dataplane.mdx rename to website/content/docs/connect/dataplane/consul-dataplane.mdx diff --git a/website/content/docs/k8s/dataplane/index.mdx b/website/content/docs/connect/dataplane/index.mdx similarity index 98% rename from website/content/docs/k8s/dataplane/index.mdx rename to website/content/docs/connect/dataplane/index.mdx index 704e94f2e3..2e6f5a5d64 100644 --- a/website/content/docs/k8s/dataplane/index.mdx +++ b/website/content/docs/connect/dataplane/index.mdx @@ -11,6 +11,8 @@ description: >- 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-beta+. + ## 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. diff --git a/website/data/docs-nav-data.json b/website/data/docs-nav-data.json index ed29e50888..21d44760cc 100644 --- a/website/data/docs-nav-data.json +++ b/website/data/docs-nav-data.json @@ -552,6 +552,19 @@ } ] }, + { + "title": "Consul Dataplane", + "routes": [ + { + "title": "Overview", + "path": "connect/dataplane" + }, + { + "title": "CLI Reference", + "path": "connect/dataplane/consul-dataplane" + } + ] + }, { "title": "Security", "routes": [ @@ -949,19 +962,6 @@ "title": "Annotations and Labels", "path": "k8s/annotations-and-labels" }, - { - "title": "Consul Dataplane", - "routes": [ - { - "title": "Overview", - "path": "k8s/dataplane" - }, - { - "title": "CLI Reference", - "path": "k8s/dataplane/consul-dataplane" - } - ] - }, { "title": "Consul DNS", "path": "k8s/dns" @@ -979,6 +979,19 @@ } ] }, + { + "title": "Consul Dataplane", + "routes": [ + { + "title": "Overview", + "path": "connect/dataplane" + }, + { + "title": "CLI Reference", + "path": "connect/dataplane/consul-dataplane" + } + ] + }, { "title": "Operations", "routes": [ From edacda006ae5d4804bb8af15de197a389fedaff5 Mon Sep 17 00:00:00 2001 From: boruszak Date: Thu, 29 Sep 2022 15:01:21 -0500 Subject: [PATCH 057/172] nav fix --- website/data/docs-nav-data.json | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/website/data/docs-nav-data.json b/website/data/docs-nav-data.json index 21d44760cc..893ed427b2 100644 --- a/website/data/docs-nav-data.json +++ b/website/data/docs-nav-data.json @@ -532,6 +532,19 @@ "title": "Security", "path": "connect/security", "hidden": true + }, + { + "title": "Consul Dataplane", + "routes": [ + { + "title": "Overview", + "path": "connect/dataplane" + }, + { + "title": "CLI Reference", + "path": "connect/dataplane/consul-dataplane" + } + ] } ] }, @@ -552,19 +565,6 @@ } ] }, - { - "title": "Consul Dataplane", - "routes": [ - { - "title": "Overview", - "path": "connect/dataplane" - }, - { - "title": "CLI Reference", - "path": "connect/dataplane/consul-dataplane" - } - ] - }, { "title": "Security", "routes": [ From 641b35650b398ecdb1001b49a7aab8384702071f Mon Sep 17 00:00:00 2001 From: boruszak Date: Thu, 29 Sep 2022 15:15:04 -0500 Subject: [PATCH 058/172] Fixing nav fixes --- website/data/docs-nav-data.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/website/data/docs-nav-data.json b/website/data/docs-nav-data.json index 893ed427b2..871144e7f1 100644 --- a/website/data/docs-nav-data.json +++ b/website/data/docs-nav-data.json @@ -984,11 +984,11 @@ "routes": [ { "title": "Overview", - "path": "connect/dataplane" + "href": "/docs/connect/dataplane" }, { "title": "CLI Reference", - "path": "connect/dataplane/consul-dataplane" + "href": "/docs/connect/dataplane/consul-dataplane" } ] }, From c847ee24469a6034e4679c5d67dd64cb4daeb626 Mon Sep 17 00:00:00 2001 From: nrichu-hcp Date: Thu, 29 Sep 2022 17:12:12 -0400 Subject: [PATCH 059/172] finalized 0.49 release notes --- .../docs/release-notes/consul-k8s/v0_49_x.mdx | 36 +++++-------------- 1 file changed, 8 insertions(+), 28 deletions(-) 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 index 9393c19f9c..8593af4d50 100644 --- a/website/content/docs/release-notes/consul-k8s/v0_49_x.mdx +++ b/website/content/docs/release-notes/consul-k8s/v0_49_x.mdx @@ -9,42 +9,22 @@ description: >- ## Release Highlights -- **Consul CNI Plugin**: This release introduces the Consul CNI Plugin for Consul on Kubernetes, to allow for configuring traffic redirection rules without escalated container privileges such as `CAP_NET_ADMIN`. Refer to [Enable the Consul CNI Plugin](/docs/k8s/installation/install#enable-the-consul-cni-plugin) for more details. The Consul CNI Plugin is supported for Consul K8s 0.49.0+ and Consul 1.13.1+. +- **Consul CNI Plugin - OpenShift support**: Support for OpenShift and Multus CNI plugin [GH-1527] -- **Kubernetes 1.24 Support**: Add support for Kubernetes 1.24 where ServiceAccounts no longer have long-term JWT tokens. [[GH-1431](https://github.com/hashicorp/consul-k8s/pull/1431)] +- **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] + +- **Cluster Peering**: pass new use_auto_cert value to gRPC TLS config when auto-encrypt is enabled. [GH-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] - **MaxInboundConnections in service-defaults CRD**: Add support for MaxInboundConnections on the Service Defaults CRD. [[GH-1437](https://github.com/hashicorp/consul-k8s/pull/1437)] -- **API Gateway: ACL auth when using WAN Federation**: Configure ACL auth for controller correctly when deployed in secondary datacenter with federation enabled [[GH-1462](https://github.com/hashicorp/consul-k8s/pull/1462)] - -## What has Changed - -- **Kubernetes 1.24 Support for multiport applications require Kubernetes secrets**: Users deploying multiple services to the same Pod (multiport) on Kubernetes 1.24+ must also deploy a Kubernetes secret for each ServiceAccount associated with the Consul service. The name of the Secret must match the ServiceAccount name and be of type `kubernetes.io/service-account-token` -Example: - - ```yaml - apiVersion: v1 - kind: Secret - metadata: - name: svc1 - annotations: - kubernetes.io/service-account.name: svc1 - type: kubernetes.io/service-account-token - --- - apiVersion: v1 - kind: Secret - metadata: - name: svc2 - annotations: - kubernetes.io/service-account.name: svc2 - type: kubernetes.io/service-account-token - ``` - ## 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. @@ -55,7 +35,7 @@ For detailed information on upgrading, please refer to the [Upgrades page](/docs ## Known Issues The following issues are know to exist in the v0.49.0 release: -- Consul CNI Plugin currently does not support RedHat OpenShift as the CNI Plugin Daemonset requires additional SecurityContextConstraint objects to run on OpenShift. Support for OpenShift will be added in an upcoming 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 not supported by Consul K8s. ## Changelogs From a5f93069ea1a155b66c763eaa5207ea76f51c8d0 Mon Sep 17 00:00:00 2001 From: Chris Chapman Date: Thu, 29 Sep 2022 21:14:16 -0700 Subject: [PATCH 060/172] Adding DNS proxy proto --- proto-public/pbdns/dns.proto | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 proto-public/pbdns/dns.proto 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; +} From c4a73a2f60373268a12f0ec8bfc566af59ad7b20 Mon Sep 17 00:00:00 2001 From: Chris Chapman Date: Thu, 29 Sep 2022 21:16:23 -0700 Subject: [PATCH 061/172] Generate protobufs --- proto-public/pbdns/dns.pb.binary.go | 28 +++ proto-public/pbdns/dns.pb.go | 298 ++++++++++++++++++++++++++++ proto-public/pbdns/dns_grpc.pb.go | 105 ++++++++++ 3 files changed, 431 insertions(+) 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_grpc.pb.go 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_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", +} From 2c6ef4189300c2487e085d4f74a91ddb265e2de6 Mon Sep 17 00:00:00 2001 From: Chris Chapman Date: Thu, 29 Sep 2022 21:17:30 -0700 Subject: [PATCH 062/172] Add mocks for probuf generation --- GNUmakefile | 15 ++++++++++++++- build-support/scripts/devtools.sh | 17 ++++++++--------- 2 files changed, 22 insertions(+), 10 deletions(-) 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/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 From 29fe2bd87c282e98b12e41923bb38c9dd228d661 Mon Sep 17 00:00:00 2001 From: Chris Chapman Date: Thu, 29 Sep 2022 21:18:40 -0700 Subject: [PATCH 063/172] Regenerate protos with mocks --- proto-public/pbdns/mock_DNSServiceClient.go | 58 +++++++++++++++++++ proto-public/pbdns/mock_DNSServiceServer.go | 48 +++++++++++++++ .../pbdns/mock_UnsafeDNSServiceServer.go | 29 ++++++++++ 3 files changed, 135 insertions(+) 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 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 +} From 7bc9cad180edd701e3fcf90efb8995d564506a80 Mon Sep 17 00:00:00 2001 From: Chris Chapman Date: Thu, 29 Sep 2022 21:19:51 -0700 Subject: [PATCH 064/172] Adding grpc handler for dns proxy --- agent/grpc-external/services/dns/server.go | 136 ++++++++++++++++++ .../grpc-external/services/dns/server_test.go | 127 ++++++++++++++++ 2 files changed, 263 insertions(+) create mode 100644 agent/grpc-external/services/dns/server.go create mode 100644 agent/grpc-external/services/dns/server_test.go diff --git a/agent/grpc-external/services/dns/server.go b/agent/grpc-external/services/dns/server.go new file mode 100644 index 0000000000..b853f118cb --- /dev/null +++ b/agent/grpc-external/services/dns/server.go @@ -0,0 +1,136 @@ +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 Local struct { + IP net.IP + Port int +} + +type Config struct { + Logger hclog.Logger + DNSServeMux *dns.ServeMux + LocalAddress Local +} + +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.Info("Write was called") + copy(b.ResponseBuffer, m) + return len(b.ResponseBuffer), 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() {} + +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 to get the correct response + switch req.GetProtocol() { + case pbdns.Protocol_PROTOCOL_TCP: + remote = pr.Addr + local = &net.TCPAddr{IP: s.LocalAddress.IP, Port: s.LocalAddress.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.LocalAddress.IP, Port: s.LocalAddress.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..e8107b46e8 --- /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, + LocalAddress: Local{ + 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) + } + }) + } +} From 81e267171b8b997ae88c9371387da32dfa5d63f7 Mon Sep 17 00:00:00 2001 From: Chris Chapman Date: Thu, 29 Sep 2022 21:44:45 -0700 Subject: [PATCH 065/172] Bind a dns mux handler to gRPC proxy --- agent/agent.go | 10 +++++++++ agent/dns.go | 54 ++++++++++++++++++++++++++++------------------- agent/dns_test.go | 50 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 92 insertions(+), 22 deletions(-) diff --git a/agent/agent.go b/agent/agent.go index e5101ab886..38ca5e4c3a 100644 --- a/agent/agent.go +++ b/agent/agent.go @@ -42,6 +42,7 @@ 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" @@ -920,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, + LocalAddress: grpcDNS.Local{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) 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{ From f22c79f5c226df2c8242e6e9221e5d29f342b1ef Mon Sep 17 00:00:00 2001 From: Chris Chapman Date: Thu, 29 Sep 2022 22:20:34 -0700 Subject: [PATCH 066/172] Adding changelog --- .changelog/14811.txt | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .changelog/14811.txt 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. +``` From 76d4f6e309f24b407850e9068c11a73e1c274188 Mon Sep 17 00:00:00 2001 From: Jeff Boruszak <104028618+boruszak@users.noreply.github.com> Date: Fri, 30 Sep 2022 09:29:20 -0500 Subject: [PATCH 067/172] Apply suggestions from code review Co-authored-by: trujillo-adam <47586768+trujillo-adam@users.noreply.github.com> --- website/content/docs/connect/dataplane/index.mdx | 2 +- website/content/docs/k8s/installation/install-cli.mdx | 1 - website/content/docs/k8s/installation/install.mdx | 1 - 3 files changed, 1 insertion(+), 3 deletions(-) diff --git a/website/content/docs/connect/dataplane/index.mdx b/website/content/docs/connect/dataplane/index.mdx index 2e6f5a5d64..641acaeef3 100644 --- a/website/content/docs/connect/dataplane/index.mdx +++ b/website/content/docs/connect/dataplane/index.mdx @@ -21,7 +21,7 @@ Consul Dataplane manages Envoy proxies and leaves responsibility for other funct ## Benefits -**Fewer networking requirements**: Without client agents, Consul doesn’t need to ensure 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. +**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. diff --git a/website/content/docs/k8s/installation/install-cli.mdx b/website/content/docs/k8s/installation/install-cli.mdx index 19308cb7fd..f195784c0a 100644 --- a/website/content/docs/k8s/installation/install-cli.mdx +++ b/website/content/docs/k8s/installation/install-cli.mdx @@ -11,7 +11,6 @@ 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. -To use Consul Dataplane with Kubernetes, you must separately install the `consul-dataplane` binary. For more information, refer to [Simplified Service Mesh with Consul Dataplane](/consul/docs/k8s/dataplane). ## Introduction diff --git a/website/content/docs/k8s/installation/install.mdx b/website/content/docs/k8s/installation/install.mdx index b13d2dca8b..91ab4513ee 100644 --- a/website/content/docs/k8s/installation/install.mdx +++ b/website/content/docs/k8s/installation/install.mdx @@ -9,7 +9,6 @@ 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). -To use Consul Dataplane with Kubernetes, you must separately install the `consul-dataplane` binary. For more information, refer to [Simplified Service Mesh with Consul Dataplane](/consul/docs/k8s/dataplane). ## Introduction From 10cfc9bbb47520384f84562e295a7f9c40c5f844 Mon Sep 17 00:00:00 2001 From: boruszak Date: Fri, 30 Sep 2022 09:51:58 -0500 Subject: [PATCH 068/172] Codeblock Added --- .../content/docs/connect/proxies/envoy.mdx | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/website/content/docs/connect/proxies/envoy.mdx b/website/content/docs/connect/proxies/envoy.mdx index 65912ed171..3f0fd36548 100644 --- a/website/content/docs/connect/proxies/envoy.mdx +++ b/website/content/docs/connect/proxies/envoy.mdx @@ -192,6 +192,36 @@ $ consul connect envoy -bootstrap > bootstrap.json ``` Then, open `bootstrap.json` and add your ACL token and log path to the file. + + +```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`: From ebc069aeb66b6c68d6d0d8a0cfaa4b96299aa875 Mon Sep 17 00:00:00 2001 From: Jeff Boruszak <104028618+boruszak@users.noreply.github.com> Date: Fri, 30 Sep 2022 09:52:53 -0500 Subject: [PATCH 069/172] Apply suggestions from code review Co-authored-by: Tu Nguyen Co-authored-by: trujillo-adam <47586768+trujillo-adam@users.noreply.github.com> --- website/content/docs/connect/proxies/envoy.mdx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/website/content/docs/connect/proxies/envoy.mdx b/website/content/docs/connect/proxies/envoy.mdx index 3f0fd36548..5728b34fb9 100644 --- a/website/content/docs/connect/proxies/envoy.mdx +++ b/website/content/docs/connect/proxies/envoy.mdx @@ -174,7 +174,7 @@ 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 Consul on Windows VMs +### Bootstrap Envoy 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: @@ -191,7 +191,7 @@ First, add the `-bootstrap` option to the command and save the output to a file: $ consul connect envoy -bootstrap > bootstrap.json ``` -Then, open `bootstrap.json` and add your ACL token and log path to the file. +Then, open `bootstrap.json` and update the following sections with your ACL token and log path. ```json @@ -226,7 +226,7 @@ Then, open `bootstrap.json` and add your ACL token and log path to the file. To complete the bootstrap process, start Envoy and include the path to `bootstrap.json`: ```shell-session -envoy -c bootstrap.json +$ 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. From 2d72feffdad6ad670951609376a8c74ec619d3ef Mon Sep 17 00:00:00 2001 From: Jeff Boruszak <104028618+boruszak@users.noreply.github.com> Date: Fri, 30 Sep 2022 09:58:03 -0500 Subject: [PATCH 070/172] Update website/content/docs/connect/proxies/envoy.mdx --- website/content/docs/connect/proxies/envoy.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/content/docs/connect/proxies/envoy.mdx b/website/content/docs/connect/proxies/envoy.mdx index 5728b34fb9..b55418095e 100644 --- a/website/content/docs/connect/proxies/envoy.mdx +++ b/website/content/docs/connect/proxies/envoy.mdx @@ -185,7 +185,7 @@ Use the -bootstrap option to generate the JSON to use when running envoy on a su 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. -First, add the `-bootstrap` option to the command and save the output to a file: +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 From 57154548f872893b857d73789140cd4abc0adbcb Mon Sep 17 00:00:00 2001 From: Jeff Boruszak <104028618+boruszak@users.noreply.github.com> Date: Fri, 30 Sep 2022 09:59:21 -0500 Subject: [PATCH 071/172] Update website/content/docs/connect/proxies/envoy.mdx --- website/content/docs/connect/proxies/envoy.mdx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/website/content/docs/connect/proxies/envoy.mdx b/website/content/docs/connect/proxies/envoy.mdx index b55418095e..efc83395b6 100644 --- a/website/content/docs/connect/proxies/envoy.mdx +++ b/website/content/docs/connect/proxies/envoy.mdx @@ -192,6 +192,7 @@ $ consul connect envoy -bootstrap > bootstrap.json ``` Then, open `bootstrap.json` and update the following sections with your ACL token and log path. + ```json @@ -221,9 +222,6 @@ Then, open `bootstrap.json` and update the following sections with your ACL toke } } } -``` - -To complete the bootstrap process, start Envoy and include the path to `bootstrap.json`: ```shell-session $ envoy -c bootstrap.json From 722881fb4032981f29d628b3d3b38c0fcddc1cc8 Mon Sep 17 00:00:00 2001 From: Jeff Boruszak <104028618+boruszak@users.noreply.github.com> Date: Fri, 30 Sep 2022 10:01:43 -0500 Subject: [PATCH 072/172] Update website/content/docs/connect/proxies/envoy.mdx --- website/content/docs/connect/proxies/envoy.mdx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/website/content/docs/connect/proxies/envoy.mdx b/website/content/docs/connect/proxies/envoy.mdx index efc83395b6..b6eae4678b 100644 --- a/website/content/docs/connect/proxies/envoy.mdx +++ b/website/content/docs/connect/proxies/envoy.mdx @@ -222,6 +222,10 @@ Then, open `bootstrap.json` and update the following sections with your ACL toke } } } +``` + + +To complete the bootstrap process, start Envoy and include the path to `bootstrap.json`: ```shell-session $ envoy -c bootstrap.json From 848e989df963d1fbdc132e5f14bc7f6ff9cf7191 Mon Sep 17 00:00:00 2001 From: nrichu-hcp <105801716+nrichu-hcp@users.noreply.github.com> Date: Fri, 30 Sep 2022 11:22:03 -0400 Subject: [PATCH 073/172] Update website/content/docs/release-notes/consul-k8s/v0_49_x.mdx Co-authored-by: David Yu --- website/content/docs/release-notes/consul-k8s/v0_49_x.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 index 8593af4d50..2faeec0e97 100644 --- a/website/content/docs/release-notes/consul-k8s/v0_49_x.mdx +++ b/website/content/docs/release-notes/consul-k8s/v0_49_x.mdx @@ -13,7 +13,7 @@ description: >- - **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] -- **Cluster Peering**: pass new use_auto_cert value to gRPC TLS config when auto-encrypt is enabled. [GH-1541] +- **Cluster Peering**: Utilize new `use_auto_cert` value to gRPC TLS config when auto-encrypt is enabled. [GH-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] From 560eb2b4a9e4a399549c34d048a08de28c64926b Mon Sep 17 00:00:00 2001 From: nrichu-hcp <105801716+nrichu-hcp@users.noreply.github.com> Date: Fri, 30 Sep 2022 11:22:12 -0400 Subject: [PATCH 074/172] Update website/content/docs/release-notes/consul-k8s/v0_49_x.mdx Co-authored-by: David Yu --- website/content/docs/release-notes/consul-k8s/v0_49_x.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 index 2faeec0e97..4c4bb2f089 100644 --- a/website/content/docs/release-notes/consul-k8s/v0_49_x.mdx +++ b/website/content/docs/release-notes/consul-k8s/v0_49_x.mdx @@ -35,7 +35,7 @@ For detailed information on upgrading, please refer to the [Upgrades page](/docs ## 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 not supported by Consul K8s. +- 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 From b95ccf166e73a46b911c341fe69b4a9dc6dbec21 Mon Sep 17 00:00:00 2001 From: nrichu-hcp Date: Fri, 30 Sep 2022 11:29:14 -0400 Subject: [PATCH 075/172] added missing links per davids advice --- website/content/docs/release-notes/consul-k8s/v0_49_x.mdx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) 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 index 4c4bb2f089..3a38f7f4f2 100644 --- a/website/content/docs/release-notes/consul-k8s/v0_49_x.mdx +++ b/website/content/docs/release-notes/consul-k8s/v0_49_x.mdx @@ -9,13 +9,13 @@ description: >- ## Release Highlights -- **Consul CNI Plugin - OpenShift support**: Support for OpenShift and Multus CNI plugin [GH-1527] +- **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] +- **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] +- **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] +- **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)] From d4f994a43390c8096e2fb2ed057bb32786a8dd7d Mon Sep 17 00:00:00 2001 From: nrichu-hcp <105801716+nrichu-hcp@users.noreply.github.com> Date: Fri, 30 Sep 2022 11:38:10 -0400 Subject: [PATCH 076/172] Update website/content/docs/release-notes/consul-k8s/v0_49_x.mdx Co-authored-by: David Yu --- website/content/docs/release-notes/consul-k8s/v0_49_x.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 index 3a38f7f4f2..d3e3ef98ce 100644 --- a/website/content/docs/release-notes/consul-k8s/v0_49_x.mdx +++ b/website/content/docs/release-notes/consul-k8s/v0_49_x.mdx @@ -9,7 +9,7 @@ description: >- ## 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 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) From 1daf4dcabae6487ffa389df99ac28e46c6a1b4d5 Mon Sep 17 00:00:00 2001 From: nrichu-hcp <105801716+nrichu-hcp@users.noreply.github.com> Date: Fri, 30 Sep 2022 11:38:20 -0400 Subject: [PATCH 077/172] Update website/content/docs/release-notes/consul-k8s/v0_49_x.mdx Co-authored-by: David Yu --- website/content/docs/release-notes/consul-k8s/v0_49_x.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 index d3e3ef98ce..7b3df22c28 100644 --- a/website/content/docs/release-notes/consul-k8s/v0_49_x.mdx +++ b/website/content/docs/release-notes/consul-k8s/v0_49_x.mdx @@ -11,7 +11,7 @@ description: >- - **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) +- **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) From f04cdeb9915dc3ce58ee69acb7c731688bd2c677 Mon Sep 17 00:00:00 2001 From: nrichu-hcp <105801716+nrichu-hcp@users.noreply.github.com> Date: Fri, 30 Sep 2022 11:38:29 -0400 Subject: [PATCH 078/172] Update website/content/docs/release-notes/consul-k8s/v0_49_x.mdx Co-authored-by: David Yu --- website/content/docs/release-notes/consul-k8s/v0_49_x.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 index 7b3df22c28..35658e4761 100644 --- a/website/content/docs/release-notes/consul-k8s/v0_49_x.mdx +++ b/website/content/docs/release-notes/consul-k8s/v0_49_x.mdx @@ -13,7 +13,7 @@ description: >- - **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) +- **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) From a9b8b1e48f7386e4619fa85b6f38ba6ff53a8ce0 Mon Sep 17 00:00:00 2001 From: nrichu-hcp <105801716+nrichu-hcp@users.noreply.github.com> Date: Fri, 30 Sep 2022 11:38:36 -0400 Subject: [PATCH 079/172] Update website/content/docs/release-notes/consul-k8s/v0_49_x.mdx Co-authored-by: David Yu --- website/content/docs/release-notes/consul-k8s/v0_49_x.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 index 35658e4761..5f9725cd2a 100644 --- a/website/content/docs/release-notes/consul-k8s/v0_49_x.mdx +++ b/website/content/docs/release-notes/consul-k8s/v0_49_x.mdx @@ -15,7 +15,7 @@ description: >- - **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) +- **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)] From 7f8971d77faa307f99ee6d602fc0c20f7e86d43a Mon Sep 17 00:00:00 2001 From: DanStough Date: Fri, 30 Sep 2022 11:33:49 -0400 Subject: [PATCH 080/172] chore: fix flakey scada provider test --- agent/agent_test.go | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/agent/agent_test.go b/agent/agent_test.go index f54275eb73..1c7671f767 100644 --- a/agent/agent_test.go +++ b/agent/agent_test.go @@ -28,8 +28,6 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/tcpproxy" - "github.com/hashicorp/consul/agent/hcp" - "github.com/hashicorp/consul/agent/hcp/scada" "github.com/hashicorp/go-hclog" "github.com/hashicorp/hcp-scada-provider/capability" "github.com/hashicorp/serf/coordinate" @@ -47,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" @@ -6082,8 +6082,6 @@ func TestAgent_startListeners_scada(t *testing.T) { } func TestAgent_scadaProvider(t *testing.T) { - t.Parallel() - pvd := scada.NewMockProvider(t) // this listener is used when mocking out the scada provider @@ -6095,7 +6093,7 @@ func TestAgent_scadaProvider(t *testing.T) { 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").Once() + pvd.EXPECT().SessionStatus().Return("test") a := TestAgent{ OverrideDeps: func(deps *BaseDeps) { deps.HCP.Provider = pvd From eb5713ccbc884930b0e962971c8d894e76969cdd Mon Sep 17 00:00:00 2001 From: David Yu Date: Fri, 30 Sep 2022 09:13:44 -0700 Subject: [PATCH 081/172] docs: admin partition docs improvements (#14774) * docs: Update admin partitions versions and add requirement for unique `global.name` Co-authored-by: trujillo-adam <47586768+trujillo-adam@users.noreply.github.com> --- website/content/docs/enterprise/admin-partitions.mdx | 11 ++++++----- .../vault/data-integration/bootstrap-token.mdx | 2 +- .../vault/data-integration/connect-ca.mdx | 2 +- .../vault/data-integration/enterprise-license.mdx | 4 ++-- .../vault/data-integration/gossip.mdx | 4 ++-- .../vault/data-integration/partition-token.mdx | 2 +- .../vault/data-integration/server-tls.mdx | 4 ++-- .../vault/data-integration/snapshot-agent-config.mdx | 2 +- .../vault/data-integration/webhook-certs.mdx | 4 ++-- 9 files changed, 18 insertions(+), 17 deletions(-) diff --git a/website/content/docs/enterprise/admin-partitions.mdx b/website/content/docs/enterprise/admin-partitions.mdx index 90dd2bfea6..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. @@ -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/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 88b8d9785b..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 @@ -71,7 +71,7 @@ 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/partition-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 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 From a05563b788176b035cebfab2ff5cc8d260963519 Mon Sep 17 00:00:00 2001 From: Chris Chapman Date: Fri, 30 Sep 2022 09:35:01 -0700 Subject: [PATCH 082/172] Update comment --- agent/grpc-external/services/dns/server.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/agent/grpc-external/services/dns/server.go b/agent/grpc-external/services/dns/server.go index b853f118cb..cef3a1073e 100644 --- a/agent/grpc-external/services/dns/server.go +++ b/agent/grpc-external/services/dns/server.go @@ -103,7 +103,8 @@ func (s *Server) Query(ctx context.Context, req *pbdns.QueryRequest) (*pbdns.Que 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 to get the correct response + // 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 From 0af2c3639e69b582547ede4a80fca68a86fb54a2 Mon Sep 17 00:00:00 2001 From: Michael Klein Date: Fri, 30 Sep 2022 20:32:02 +0200 Subject: [PATCH 083/172] fix link back to hcp not showing up (#14812) --- ui/packages/consul-ui/lib/startup/templates/body.html.js | 1 + 1 file changed, 1 insertion(+) 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}} ` From 3faaa02a0c28992057d79dc047ad56dd3ff1244f Mon Sep 17 00:00:00 2001 From: Curt Bushko Date: Fri, 30 Sep 2022 14:52:15 -0400 Subject: [PATCH 084/172] Add OpenShift CNI to list of install options for kubernetes (#14793) --- .../content/docs/k8s/installation/install.mdx | 21 ++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/website/content/docs/k8s/installation/install.mdx b/website/content/docs/k8s/installation/install.mdx index 319cada6d5..79e7b69b2f 100644 --- a/website/content/docs/k8s/installation/install.mdx +++ b/website/content/docs/k8s/installation/install.mdx @@ -119,7 +119,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 +151,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 +178,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` | From 443dc35c31ca786a0ac39f89f979921776833944 Mon Sep 17 00:00:00 2001 From: Jeff Boruszak <104028618+boruszak@users.noreply.github.com> Date: Fri, 30 Sep 2022 14:00:37 -0500 Subject: [PATCH 085/172] Apply suggestions from code review Co-authored-by: Eric Haberkorn --- website/content/docs/connect/cluster-peering/index.mdx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/website/content/docs/connect/cluster-peering/index.mdx b/website/content/docs/connect/cluster-peering/index.mdx index 67aea76561..d59b0557b0 100644 --- a/website/content/docs/connect/cluster-peering/index.mdx +++ b/website/content/docs/connect/cluster-peering/index.mdx @@ -43,7 +43,7 @@ Regardless of whether you connect your clusters through WAN federation or cluste The cluster peering beta release includes the following features and functionality: -- **Consul v1.14 beta only**: Dynamic traffic control with a `ServiceResolver` config entry can target failover and redirects to service instances in a peered cluster. +- **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. @@ -58,5 +58,4 @@ Not all features and functionality are available in the beta release. In particu - 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 traffic forwarding does not support `http` type services. - Cross-cluster mesh gateways are supported in `remote` mode only. From 6866e89630e5448d0b1a600ae5d7130fbbb57cca Mon Sep 17 00:00:00 2001 From: boruszak Date: Fri, 30 Sep 2022 14:00:55 -0500 Subject: [PATCH 086/172] service-resolver alignment --- .../docs/connect/cluster-peering/create-manage-peering.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 2dedce8fe4..acb632856c 100644 --- a/website/content/docs/connect/cluster-peering/create-manage-peering.mdx +++ b/website/content/docs/connect/cluster-peering/create-manage-peering.mdx @@ -405,7 +405,7 @@ spec: '*': targets: - peer: 'cluster-02' - service: 'backup' + service: 'frontend' namespace: 'default' ``` From a74d826b0e526f12add8cfbae31136206dd08bbb Mon Sep 17 00:00:00 2001 From: David Yu Date: Fri, 30 Sep 2022 12:17:16 -0700 Subject: [PATCH 087/172] updated 0.49.x helm docs (#14824) --- website/content/docs/k8s/helm.mdx | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/website/content/docs/k8s/helm.mdx b/website/content/docs/k8s/helm.mdx index 230ad60c0b..54e116f6fd 100644 --- a/website/content/docs/k8s/helm.mdx +++ b/website/content/docs/k8s/helm.mdx @@ -1479,7 +1479,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. @@ -1598,6 +1598,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 From 80e3f15d479e7e56c9ec3ddd4145afdc72e95390 Mon Sep 17 00:00:00 2001 From: boruszak Date: Fri, 30 Sep 2022 15:32:43 -0500 Subject: [PATCH 088/172] Dynamic routing clarifications --- .../cluster-peering/create-manage-peering.mdx | 93 ++++++++++++++++++- .../docs/connect/cluster-peering/index.mdx | 2 +- 2 files changed, 93 insertions(+), 2 deletions(-) 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 acb632856c..39c9f476c0 100644 --- a/website/content/docs/connect/cluster-peering/create-manage-peering.mdx +++ b/website/content/docs/connect/cluster-peering/create-manage-peering.mdx @@ -377,7 +377,9 @@ Next to the name of the peer, click **More** (three horizontal dots) and then ** ## 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. The following examples updates the [ServiceResolver 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. +### 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. @@ -428,4 +430,93 @@ spec: } ``` + + +### Service splitters and custom routes + +The `service-splitter` and `service-router` config 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` config 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 config 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 config 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` config 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 d59b0557b0..9680f2c51d 100644 --- a/website/content/docs/connect/cluster-peering/index.mdx +++ b/website/content/docs/connect/cluster-peering/index.mdx @@ -54,7 +54,7 @@ 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. -- Some dynamic routing features, such as splits and custom routes, cannot target services in a peered cluster. +- The `service-splitter` and `service-router` config 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. - 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. From 46bea72212d29ead4bf7b6882ca93a073da73473 Mon Sep 17 00:00:00 2001 From: Chris Chapman Date: Fri, 30 Sep 2022 14:51:12 -0700 Subject: [PATCH 089/172] Making suggested changes --- agent/agent.go | 6 ++--- agent/grpc-external/services/dns/server.go | 23 +++++++++---------- .../grpc-external/services/dns/server_test.go | 2 +- 3 files changed, 15 insertions(+), 16 deletions(-) diff --git a/agent/agent.go b/agent/agent.go index 38ca5e4c3a..ff41544ef5 100644 --- a/agent/agent.go +++ b/agent/agent.go @@ -924,9 +924,9 @@ func (a *Agent) listenAndServeDNS() error { s, _ := NewDNSServer(a) grpcDNS.NewServer(grpcDNS.Config{ - Logger: a.logger.Named("grpc-api.dns"), - DNSServeMux: s.mux, - LocalAddress: grpcDNS.Local{IP: net.IPv4(127, 0, 0, 1), Port: a.config.GRPCPort}, + 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) diff --git a/agent/grpc-external/services/dns/server.go b/agent/grpc-external/services/dns/server.go index cef3a1073e..bdcf382961 100644 --- a/agent/grpc-external/services/dns/server.go +++ b/agent/grpc-external/services/dns/server.go @@ -15,15 +15,15 @@ import ( "github.com/hashicorp/consul/proto-public/pbdns" ) -type Local struct { +type LocalAddr struct { IP net.IP Port int } type Config struct { - Logger hclog.Logger - DNSServeMux *dns.ServeMux - LocalAddress Local + Logger hclog.Logger + DNSServeMux *dns.ServeMux + LocalAddr LocalAddr } type Server struct { @@ -40,7 +40,7 @@ func (s *Server) Register(grpcServer *grpc.Server) { // BufferResponseWriter writes a DNS response to a byte buffer. type BufferResponseWriter struct { - ResponseBuffer []byte + responseBuffer []byte LocalAddress net.Addr RemoteAddress net.Addr Logger hclog.Logger @@ -64,15 +64,14 @@ func (b *BufferResponseWriter) WriteMsg(m *dns.Msg) error { b.Logger.Error("error packing message", "err", err) return err } - b.ResponseBuffer = msgBytes + b.responseBuffer = msgBytes return nil } // Write writes a raw buffer back to the client. func (b *BufferResponseWriter) Write(m []byte) (int, error) { - b.Logger.Info("Write was called") - copy(b.ResponseBuffer, m) - return len(b.ResponseBuffer), nil + b.Logger.Debug("Write was called") + return copy(b.responseBuffer, m), nil } // Close closes the connection. @@ -108,11 +107,11 @@ func (s *Server) Query(ctx context.Context, req *pbdns.QueryRequest) (*pbdns.Que switch req.GetProtocol() { case pbdns.Protocol_PROTOCOL_TCP: remote = pr.Addr - local = &net.TCPAddr{IP: s.LocalAddress.IP, Port: s.LocalAddress.Port} + 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.LocalAddress.IP, Port: s.LocalAddress.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())) } @@ -131,7 +130,7 @@ func (s *Server) Query(ctx context.Context, req *pbdns.QueryRequest) (*pbdns.Que } s.DNSServeMux.ServeDNS(respWriter, msg) - queryResponse := &pbdns.QueryResponse{Msg: respWriter.ResponseBuffer} + 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 index e8107b46e8..b477fed498 100644 --- a/agent/grpc-external/services/dns/server_test.go +++ b/agent/grpc-external/services/dns/server_test.go @@ -58,7 +58,7 @@ func (s *DNSTestSuite) TestProxy_Success() { server := NewServer(Config{ Logger: hclog.Default(), DNSServeMux: mux, - LocalAddress: Local{ + LocalAddr: LocalAddr{ net.IPv4(127, 0, 0, 1), 0, }, From d7b5351b6629469680c29e1c773580322ee46f2d Mon Sep 17 00:00:00 2001 From: Chris Chapman Date: Fri, 30 Sep 2022 15:03:33 -0700 Subject: [PATCH 090/172] Making suggested comments --- agent/grpc-external/services/dns/server.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/agent/grpc-external/services/dns/server.go b/agent/grpc-external/services/dns/server.go index bdcf382961..50b12f5d91 100644 --- a/agent/grpc-external/services/dns/server.go +++ b/agent/grpc-external/services/dns/server.go @@ -93,6 +93,8 @@ func (b *BufferResponseWriter) TsigTimersOnly(bool) {} // 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 { From 70865aa0d679d2aeab70c7b9e47631644bff734c Mon Sep 17 00:00:00 2001 From: trujillo-adam Date: Sat, 1 Oct 2022 13:09:36 -0700 Subject: [PATCH 091/172] applied feedback from review --- website/content/docs/lambda/index.mdx | 6 ++- .../docs/lambda/invoke-from-lambda.mdx | 41 ++++++++++--------- .../docs/lambda/registration/automate.mdx | 6 +-- website/data/docs-nav-data.json | 12 +++--- 4 files changed, 35 insertions(+), 30 deletions(-) diff --git a/website/content/docs/lambda/index.mdx b/website/content/docs/lambda/index.mdx index 4f299fd9f7..008d6f6a54 100644 --- a/website/content/docs/lambda/index.mdx +++ b/website/content/docs/lambda/index.mdx @@ -20,10 +20,12 @@ Refer to [Lambda Function Registration Requirements](/docs/lambda/registration/i After registering AWS Lambda functions, you can invoke Lambda functions from the Consul service mesh through terminating gateways (recommended) or directly from connect proxies. -Refer to Invoke Lambda Functions from Services for details. +Refer to [Invoke Lambda Functions from Services](/docs/lambda/invocation) for details. ## 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) @@ -32,4 +34,4 @@ Refer to [Invoke Services from Lambda Functions](/docs/lambda/invoke-from-lambda 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 mesh gateways do not implement L7 traffic management by default. As a result, requests from Lambda functions ignore service routes and splitters. \ No newline at end of file +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/invoke-from-lambda.mdx b/website/content/docs/lambda/invoke-from-lambda.mdx index 78c9a87671..bd51d2e3dd 100644 --- a/website/content/docs/lambda/invoke-from-lambda.mdx +++ b/website/content/docs/lambda/invoke-from-lambda.mdx @@ -9,16 +9,20 @@ description: >- 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 services you want to allow the Lambda function to invoke. -1. Deploy the mesh gateway. +1. Deploy the destination service and mesh gateway. +1. Deploy the Lambda extension layer 1. Deploy the Lambda registrator. -1. Invoke the the Lambda function. +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 Lambdas documentation](https://docs.aws.amazon.com/lambda/latest/dg/invocation-layers.html) for instructions on how to add layers to your Lambda functions. +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). @@ -78,7 +82,6 @@ spec: 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: @@ -101,16 +104,16 @@ The extension periodically retrieves the data from the AWS Parameter Store so th ``` 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: - + - ``` - 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" - } - ``` + ```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" + } + ``` @@ -234,9 +237,9 @@ func main() { } 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” + 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] @@ -258,7 +261,7 @@ Define the following environment variables in your Lambda functions to configure | `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 the 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 | +| `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 @@ -267,5 +270,5 @@ If _intentions_ are enabled in the Consul service mesh, you must create an inten 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-registrator-2345 /dev/stdout | cat +$ 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 index ac607f414f..09a9cd0ea6 100644 --- a/website/content/docs/lambda/registration/automate.mdx +++ b/website/content/docs/lambda/registration/automate.mdx @@ -98,7 +98,7 @@ If you want to enable Lambda functions to invoke services in the mesh, then you 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: -`${var.consul_extension_data_prefix}/${}/${}/${}` +`${consul_extension_data_prefix}/${}/${}/${}` The registrator also requires the following IAM permissions to access the parameter store: @@ -167,8 +167,8 @@ resource "aws_lambda_function" "example" { function_name = "lambda" tags = { "serverless.consul.hashicorp.com/v1alpha1/lambda/enabled" = "true" - "serverless.consul.hashicorp.com/alpha/lambda/payload-passthrough" = "true" - "serverless.consul.hashicorp.com/alpha/lambda/invocation-mode" = "ASYNCHRONOUS" + "serverless.consul.hashicorp.com/v1alpha1/lambda/payload-passthrough" = "true" + "serverless.consul.hashicorp.com/v1alpha1/lambda/invocation-mode" = "ASYNCHRONOUS" } } ``` diff --git a/website/data/docs-nav-data.json b/website/data/docs-nav-data.json index 955f51cb5e..db55a71b02 100644 --- a/website/data/docs-nav-data.json +++ b/website/data/docs-nav-data.json @@ -1083,11 +1083,6 @@ }, { "title": "AWS Lambda", - "badge": { - "text": "BETA", - "type": "outlined", - "color": "neutral" - }, "routes": [ { "title": "Overview", @@ -1116,7 +1111,12 @@ }, { "title": "Invoke Services from Lambda Functions", - "path": "lambda/invoke-from-lambda" + "path": "lambda/invoke-from-lambda", + "badge": { + "text": "BETA", + "type": "outlined", + "color": "neutral" + } } ] }, From b530c140326aec4fa7ef830e76e38bf3a49fbda3 Mon Sep 17 00:00:00 2001 From: Jeff Boruszak <104028618+boruszak@users.noreply.github.com> Date: Mon, 3 Oct 2022 10:51:24 -0500 Subject: [PATCH 092/172] Update website/content/docs/connect/dataplane/index.mdx Co-authored-by: Riddhi Shah --- website/content/docs/connect/dataplane/index.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/content/docs/connect/dataplane/index.mdx b/website/content/docs/connect/dataplane/index.mdx index 641acaeef3..d2ce2a4f63 100644 --- a/website/content/docs/connect/dataplane/index.mdx +++ b/website/content/docs/connect/dataplane/index.mdx @@ -2,7 +2,7 @@ layout: docs page_title: Simplified Service Mesh with Consul Dataplane description: >- - Consul Dataplane removes the need to run client or 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. + 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 From eb9895422d62a9058cc45e3d97687dda5cc990dc Mon Sep 17 00:00:00 2001 From: Jeff Boruszak <104028618+boruszak@users.noreply.github.com> Date: Mon, 3 Oct 2022 10:58:43 -0500 Subject: [PATCH 093/172] Apply suggestions from code review Co-authored-by: Riddhi Shah --- website/content/docs/connect/dataplane/index.mdx | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/website/content/docs/connect/dataplane/index.mdx b/website/content/docs/connect/dataplane/index.mdx index d2ce2a4f63..c89e18e6d8 100644 --- a/website/content/docs/connect/dataplane/index.mdx +++ b/website/content/docs/connect/dataplane/index.mdx @@ -11,7 +11,7 @@ description: >- 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-beta+. +Consul Dataplane requires servers running Consul v1.14.0-beta1+. ## What is Consul Dataplane? @@ -25,7 +25,7 @@ Consul Dataplane manages Envoy proxies and leaves responsibility for other funct **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, as well as runtimes that mix Kubernetes and VM deployments. +**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. @@ -35,7 +35,7 @@ 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](/consul/docs/dataplane/consul-dataplane). - For Helm chart information, refer to the [Helm Chart reference](/consul/docs/k8s/helm). -- For Envoy, Consul, and Consul Dataplane version compatibility, refer to the [Envoy compatibility matrix](/consul/docs/k8s/compatibility). +- For Envoy, Consul, and Consul Dataplane version compatibility, refer to the [Envoy compatibility matrix](/docs/connect/procies/envoy). ## Beta release features @@ -56,6 +56,5 @@ 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. -- Secrets Discovery Service (SDS) is not supported. - If using EKS Fargate, we recommend that you use external Consul servers. - Consul Dataplane is not supported on Windows. From d80601e96e672cc880fa06ad3523dec2f103c9c9 Mon Sep 17 00:00:00 2001 From: boruszak Date: Mon, 3 Oct 2022 11:14:34 -0500 Subject: [PATCH 094/172] proxy default behavior constraint --- website/content/docs/connect/dataplane/index.mdx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/website/content/docs/connect/dataplane/index.mdx b/website/content/docs/connect/dataplane/index.mdx index c89e18e6d8..6a38f55411 100644 --- a/website/content/docs/connect/dataplane/index.mdx +++ b/website/content/docs/connect/dataplane/index.mdx @@ -56,5 +56,6 @@ 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. -- If using EKS Fargate, we recommend that you use external Consul servers. +- If using EKS Fargate, we recommend that you use external Consul servers. +- Using `proxy-defaults` and `service-defaults` to configure default proxy behavior is not supported. - Consul Dataplane is not supported on Windows. From 80bbf8995b2541f5e52df95514b1a92dd1093fda Mon Sep 17 00:00:00 2001 From: boruszak Date: Mon, 3 Oct 2022 11:15:12 -0500 Subject: [PATCH 095/172] Removed external server constraint --- website/content/docs/connect/dataplane/index.mdx | 1 - 1 file changed, 1 deletion(-) diff --git a/website/content/docs/connect/dataplane/index.mdx b/website/content/docs/connect/dataplane/index.mdx index 6a38f55411..e0b0c50fa8 100644 --- a/website/content/docs/connect/dataplane/index.mdx +++ b/website/content/docs/connect/dataplane/index.mdx @@ -56,6 +56,5 @@ 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. -- If using EKS Fargate, we recommend that you use external Consul servers. - Using `proxy-defaults` and `service-defaults` to configure default proxy behavior is not supported. - Consul Dataplane is not supported on Windows. From d1d9fd0a5cf46809c10026da689a2726ab7a1332 Mon Sep 17 00:00:00 2001 From: Jeff Boruszak <104028618+boruszak@users.noreply.github.com> Date: Mon, 3 Oct 2022 11:16:00 -0500 Subject: [PATCH 096/172] Update website/content/docs/connect/dataplane/index.mdx Co-authored-by: Riddhi Shah --- website/content/docs/connect/dataplane/index.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/content/docs/connect/dataplane/index.mdx b/website/content/docs/connect/dataplane/index.mdx index e0b0c50fa8..2fe2c26749 100644 --- a/website/content/docs/connect/dataplane/index.mdx +++ b/website/content/docs/connect/dataplane/index.mdx @@ -35,7 +35,7 @@ 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](/consul/docs/dataplane/consul-dataplane). - For Helm chart information, refer to the [Helm Chart reference](/consul/docs/k8s/helm). -- For Envoy, Consul, and Consul Dataplane version compatibility, refer to the [Envoy compatibility matrix](/docs/connect/procies/envoy). +- For Envoy, Consul, and Consul Dataplane version compatibility, refer to the [Envoy compatibility matrix](/docs/connect/proxies/envoy). ## Beta release features From de04344e479e022d9c5cda3aee8e658e81391891 Mon Sep 17 00:00:00 2001 From: Luke Kysow <1034429+lkysow@users.noreply.github.com> Date: Mon, 3 Oct 2022 09:21:42 -0700 Subject: [PATCH 097/172] Update tech-specs.mdx (#14840) --- website/content/docs/api-gateway/tech-specs.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/content/docs/api-gateway/tech-specs.mdx b/website/content/docs/api-gateway/tech-specs.mdx index 7471d2fd85..ee0500c7a5 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. | From 69189e0381a3cb45538ac38da8d8a95ccb0f0d12 Mon Sep 17 00:00:00 2001 From: Jeff Boruszak <104028618+boruszak@users.noreply.github.com> Date: Mon, 3 Oct 2022 11:36:48 -0500 Subject: [PATCH 098/172] Update website/content/docs/connect/cluster-peering/index.mdx --- website/content/docs/connect/cluster-peering/index.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/content/docs/connect/cluster-peering/index.mdx b/website/content/docs/connect/cluster-peering/index.mdx index 9680f2c51d..a1de37709f 100644 --- a/website/content/docs/connect/cluster-peering/index.mdx +++ b/website/content/docs/connect/cluster-peering/index.mdx @@ -54,7 +54,7 @@ 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. -- The `service-splitter` and `service-router` config 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. +- The `service-splitter` and `service-router` config 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. From 12aa3cac4a123eb07744cf21fd693993223f3779 Mon Sep 17 00:00:00 2001 From: Jeff Boruszak <104028618+boruszak@users.noreply.github.com> Date: Mon, 3 Oct 2022 11:38:43 -0500 Subject: [PATCH 099/172] Apply suggestions from code review --- .../docs/connect/cluster-peering/create-manage-peering.mdx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 39c9f476c0..06271323a2 100644 --- a/website/content/docs/connect/cluster-peering/create-manage-peering.mdx +++ b/website/content/docs/connect/cluster-peering/create-manage-peering.mdx @@ -375,9 +375,9 @@ Next to the name of the peer, click **More** (three horizontal dots) and then **
    -## Traffic management between peers +## L7 traffic management between peers -### Redirects and failover +### 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. From 4ff9d475b0bfae4946f51b6c8e70fd7c45fb423f Mon Sep 17 00:00:00 2001 From: freddygv Date: Tue, 20 Sep 2022 07:46:20 -0600 Subject: [PATCH 100/172] Return mesh gateway addrs if peering through mgw --- agent/consul/peering_backend.go | 35 ++++++++++- agent/consul/peering_backend_test.go | 88 +++++++++++++++++++++++++++- agent/rpc/peering/service.go | 3 +- 3 files changed, 121 insertions(+), 5 deletions(-) diff --git a/agent/consul/peering_backend.go b/agent/consul/peering_backend.go index 5b01b9d040..8ed8a6079f 100644 --- a/agent/consul/peering_backend.go +++ b/agent/consul/peering_backend.go @@ -9,10 +9,12 @@ import ( "github.com/hashicorp/consul/acl" "github.com/hashicorp/consul/acl/resolver" + "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/proto/pbpeering" ) @@ -57,9 +59,38 @@ func (b *PeeringBackend) GetAgentCACertificates() ([]string, error) { return b.srv.tlsConfigurator.GRPCManualCAPems(), 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 diff --git a/agent/consul/peering_backend_test.go b/agent/consul/peering_backend_test.go index 7636dc48be..07de504ec9 100644 --- a/agent/consul/peering_backend_test.go +++ b/agent/consul/peering_backend_test.go @@ -2,10 +2,13 @@ package consul import ( "context" + "fmt" "net" "testing" "time" + "github.com/hashicorp/consul/agent/structs" + "github.com/hashicorp/consul/types" "github.com/stretchr/testify/require" gogrpc "google.golang.org/grpc" @@ -17,7 +20,9 @@ import ( ) func TestPeeringBackend_ForwardToLeader(t *testing.T) { - t.Parallel() + if testing.Short() { + t.Skip("too slow for testing.Short") + } _, conf1 := testServerConfig(t) server1, err := newServer(t, conf1) @@ -60,6 +65,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 = 8505 + + 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,7 +161,9 @@ func newServerDialer(serverAddr string) func(context.Context, string) (net.Conn, } func TestPeerStreamService_ForwardToLeader(t *testing.T) { - t.Parallel() + if testing.Short() { + t.Skip("too slow for testing.Short") + } _, conf1 := testServerConfig(t) server1, err := newServer(t, conf1) diff --git a/agent/rpc/peering/service.go b/agent/rpc/peering/service.go index e8c468a788..d9ddbdc6c3 100644 --- a/agent/rpc/peering/service.go +++ b/agent/rpc/peering/service.go @@ -117,7 +117,8 @@ type Backend interface { // GetAgentCACertificates returns the CA certificate to be returned in the peering token data GetAgentCACertificates() ([]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 From a8c4d6bc5554ffd4eaa4d75b05e6bb2d88ba5e5b Mon Sep 17 00:00:00 2001 From: freddygv Date: Wed, 21 Sep 2022 09:55:19 -0600 Subject: [PATCH 101/172] Share mgw addrs in peering stream if needed This commit adds handling so that the replication stream considers whether the user intends to peer through mesh gateways. The subscription will return server or mesh gateway addresses depending on the mesh configuration setting. These watches can be updated at runtime by modifying the mesh config entry. --- agent/consul/peering_backend_test.go | 3 +- agent/consul/servercert/manager.go | 1 - .../services/peerstream/server.go | 1 + .../services/peerstream/stream_resources.go | 3 - .../peerstream/subscription_blocking.go | 24 +-- .../peerstream/subscription_manager.go | 143 ++++++++++++++---- .../peerstream/subscription_manager_test.go | 134 +++++++++++----- lib/retry/retry.go | 4 +- 8 files changed, 233 insertions(+), 80 deletions(-) diff --git a/agent/consul/peering_backend_test.go b/agent/consul/peering_backend_test.go index 07de504ec9..2d9b9f029d 100644 --- a/agent/consul/peering_backend_test.go +++ b/agent/consul/peering_backend_test.go @@ -8,6 +8,7 @@ import ( "time" "github.com/hashicorp/consul/agent/structs" + "github.com/hashicorp/consul/sdk/freeport" "github.com/hashicorp/consul/types" "github.com/stretchr/testify/require" gogrpc "google.golang.org/grpc" @@ -71,7 +72,7 @@ func TestPeeringBackend_GetServerAddresses(t *testing.T) { } _, cfg := testServerConfig(t) - cfg.GRPCTLSPort = 8505 + cfg.GRPCTLSPort = freeport.GetOne(t) srv, err := newServer(t, cfg) require.NoError(t, err) 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/grpc-external/services/peerstream/server.go b/agent/grpc-external/services/peerstream/server.go index 7c04ec82c3..8a5e33e93c 100644 --- a/agent/grpc-external/services/peerstream/server.go +++ b/agent/grpc-external/services/peerstream/server.go @@ -123,5 +123,6 @@ type StateStore interface { 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 b42f17c5ce..5369812dba 100644 --- a/agent/grpc-external/services/peerstream/stream_resources.go +++ b/agent/grpc-external/services/peerstream/stream_resources.go @@ -640,9 +640,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/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}: } + + // Block for any changes to the state store. 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) - } - - 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 c761c6c611..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" @@ -247,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) } } @@ -490,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 @@ -517,7 +515,7 @@ func (m *subscriptionManager) syncDiscoveryChains( } } -func (m *subscriptionManager) emitEventForDiscoveryChain( +func (m *subscriptionManager) collectPendingEventForDiscoveryChain( ctx context.Context, state *subscriptionState, pending *pendingPayload, @@ -738,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 } - 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") - } 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()) - } + 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 + for { + var err error + + idx, err = m.subscribeServerAddrs(ctx, idx, updateCh) + if errors.Is(err, stream.ErrSubForceClosed) { + 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) { + logger.Warn("failed to subscribe to server addresses, will attempt resume", "error", err.Error()) + + } else if err != nil { + logger.Trace(err.Error()) + return + } + if err := waiter.Wait(ctx); err != nil { return - default: } } } @@ -826,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 e7363f43db..065551f9db 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,10 +50,7 @@ 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() ) @@ -60,11 +58,7 @@ func TestSubscriptionManager_RegisterDeregister(t *testing.T) { expectEvents(t, subCh, func(t *testing.T, got cache.UpdateEvent) { checkExportedServices(t, got, []string{}) - }, - func(t *testing.T, got cache.UpdateEvent) { - checkEvent(t, got, gatewayCorrID, 0) - }, - ) + }) // 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 @@ -300,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]) - }, ) }) @@ -434,13 +417,6 @@ func TestSubscriptionManager_RegisterDeregister(t *testing.T) { res := got.Result.(*pbservice.IndexedCheckServiceNodes) require.Equal(t, uint64(0), res.Index) - 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) }, ) @@ -506,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() @@ -521,9 +495,6 @@ func TestSubscriptionManager_InitialSnapshot(t *testing.T) { expectEvents(t, subCh, func(t *testing.T, got cache.UpdateEvent) { checkExportedServices(t, got, []string{}) - }, - func(t *testing.T, got cache.UpdateEvent) { - checkEvent(t, got, gatewayCorrID, 0) }) // At this point in time we'll have a mesh-gateway notification with no @@ -597,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)) - }, ) }) } @@ -741,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 { diff --git a/lib/retry/retry.go b/lib/retry/retry.go index 8f52768d8a..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()) From f4731bf0dff6edf0a76bad2f0ef7082775447dba Mon Sep 17 00:00:00 2001 From: boruszak Date: Mon, 3 Oct 2022 12:52:07 -0500 Subject: [PATCH 102/172] Installation isntructions --- .../connect/dataplane/consul-dataplane.mdx | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/website/content/docs/connect/dataplane/consul-dataplane.mdx b/website/content/docs/connect/dataplane/consul-dataplane.mdx index d432aadaee..1ee344e6bf 100644 --- a/website/content/docs/connect/dataplane/consul-dataplane.mdx +++ b/website/content/docs/connect/dataplane/consul-dataplane.mdx @@ -17,6 +17,24 @@ Usage: `consul-dataplane [options]` 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). +### 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=0.39.0 && \ + curl --location "https://releases.hashicorp.com/consul-k8s/${VERSION}/consul-k8s_${VERSION}_darwin_amd64.zip" --output consul-k8s-cli.zip +``` + ### Startup The following options are required when starting `consul-dataplane` with the CLI: From dc61cc191482c8c62291401269533ecf80583bb6 Mon Sep 17 00:00:00 2001 From: Jeff Boruszak <104028618+boruszak@users.noreply.github.com> Date: Mon, 3 Oct 2022 12:55:55 -0500 Subject: [PATCH 103/172] Update website/content/docs/connect/dataplane/consul-dataplane.mdx Co-authored-by: David Yu --- website/content/docs/connect/dataplane/consul-dataplane.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/content/docs/connect/dataplane/consul-dataplane.mdx b/website/content/docs/connect/dataplane/consul-dataplane.mdx index 1ee344e6bf..95dd895634 100644 --- a/website/content/docs/connect/dataplane/consul-dataplane.mdx +++ b/website/content/docs/connect/dataplane/consul-dataplane.mdx @@ -31,7 +31,7 @@ $ helm install consul hashicorp/consul --set global.name=consul --version ${VERS #### Consul-k8s CLI ```shell-session -$ export VERSION=0.39.0 && \ +$ 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 ``` From b15d41534fd3a08833604ebd5b102e11ee32ccff Mon Sep 17 00:00:00 2001 From: freddygv Date: Thu, 22 Sep 2022 21:14:25 -0600 Subject: [PATCH 104/172] Update xds generation for peering over mesh gws This commit adds the xDS resources needed for INBOUND traffic from peer clusters: - 1 filter chain for all inbound peering requests. - 1 cluster for all inbound peering requests. - 1 endpoint per voting server with the gRPC TLS port configured. There is one filter chain and cluster because unlike with WAN federation, peer clusters will not attempt to dial individual servers. Peer clusters will only dial the local mesh gateway addresses. --- agent/proxycfg/mesh_gateway.go | 2 +- agent/proxycfg/snapshot.go | 2 +- agent/proxycfg/testing_mesh_gateway.go | 100 ++++++++++++++++++ agent/structs/config_entry_mesh.go | 7 ++ agent/structs/config_entry_mesh_test.go | 46 ++++++++ agent/xds/clusters.go | 25 +++++ agent/xds/endpoints.go | 46 +++++++- agent/xds/listeners.go | 29 ++++- agent/xds/resources_test.go | 6 ++ ...ateway-peering-control-plane.latest.golden | 24 +++++ ...ateway-peering-control-plane.latest.golden | 37 +++++++ ...ateway-peering-control-plane.latest.golden | 62 +++++++++++ ...ateway-peering-control-plane.latest.golden | 5 + 13 files changed, 387 insertions(+), 4 deletions(-) create mode 100644 agent/structs/config_entry_mesh_test.go create mode 100644 agent/xds/testdata/clusters/mesh-gateway-peering-control-plane.latest.golden create mode 100644 agent/xds/testdata/endpoints/mesh-gateway-peering-control-plane.latest.golden create mode 100644 agent/xds/testdata/listeners/mesh-gateway-peering-control-plane.latest.golden create mode 100644 agent/xds/testdata/routes/mesh-gateway-peering-control-plane.latest.golden diff --git a/agent/proxycfg/mesh_gateway.go b/agent/proxycfg/mesh_gateway.go index 6de49b69e8..bd9f140423 100644 --- a/agent/proxycfg/mesh_gateway.go +++ b/agent/proxycfg/mesh_gateway.go @@ -494,7 +494,7 @@ func (s *handlerMeshGateway) handleUpdate(ctx context.Context, u UpdateEvent, sn return nil } - if meshConf.Peering == nil || !meshConf.Peering.PeerThroughMeshGateways { + if !meshConf.PeerThroughMeshGateways() { snap.MeshGateway.WatchedConsulServers.CancelWatch(structs.ConsulServiceName) return nil } diff --git a/agent/proxycfg/snapshot.go b/agent/proxycfg/snapshot.go index 130977a8c6..90765ff35d 100644 --- a/agent/proxycfg/snapshot.go +++ b/agent/proxycfg/snapshot.go @@ -697,7 +697,7 @@ func (s *ConfigSnapshot) Valid() bool { if s.ServiceMeta[structs.MetaWANFederationKey] == "1" { return false } - if cfg := s.MeshConfig(); cfg != nil && cfg.Peering != nil && cfg.Peering.PeerThroughMeshGateways { + if cfg := s.MeshConfig(); cfg.PeerThroughMeshGateways() { return false } } diff --git a/agent/proxycfg/testing_mesh_gateway.go b/agent/proxycfg/testing_mesh_gateway.go index f8b6116a26..d5868d58e6 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{}{ 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/xds/clusters.go b/agent/xds/clusters.go index 2889868bb5..9332b15f9e 100644 --- a/agent/xds/clusters.go +++ b/agent/xds/clusters.go @@ -396,6 +396,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.WatchedConsulServers.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 { @@ -413,6 +428,16 @@ func (s *ResourceGenerator) clustersFromSnapshotMeshGateway(cfgSnap *proxycfg.Co 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 +} + // clustersFromSnapshotTerminatingGateway returns the xDS API representation of the "clusters" // for a terminating gateway. This will include 1 cluster per Destination associated with this terminating gateway. func (s *ResourceGenerator) clustersFromSnapshotTerminatingGateway(cfgSnap *proxycfg.ConfigSnapshot) ([]proto.Message, error) { diff --git a/agent/xds/endpoints.go b/agent/xds/endpoints.go index d3083979b8..fce9a7fefa 100644 --- a/agent/xds/endpoints.go +++ b/agent/xds/endpoints.go @@ -3,11 +3,11 @@ 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" @@ -285,6 +285,50 @@ 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.WatchedConsulServers.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), + }, + }, + }) + } + + 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 { diff --git a/agent/xds/listeners.go b/agent/xds/listeners.go index d74d44ab87..f46953e7fe 100644 --- a/agent/xds/listeners.go +++ b/agent/xds/listeners.go @@ -1751,7 +1751,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, @@ -1760,6 +1760,33 @@ func (s *ResourceGenerator) makeMeshGatewayListener(name, addr string, port int, } } + // 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.WatchedConsulServers.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, + }, + }) + } + } + // 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) diff --git a/agent/xds/resources_test.go b/agent/xds/resources_test.go index 53274d7193..913080aae6 100644 --- a/agent/xds/resources_test.go +++ b/agent/xds/resources_test.go @@ -215,6 +215,12 @@ 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) + }, + }, } } 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/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/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/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 From 64dcb64d31d822b1a5f1df01e499dd68ac62434f Mon Sep 17 00:00:00 2001 From: Jeff Boruszak <104028618+boruszak@users.noreply.github.com> Date: Mon, 3 Oct 2022 13:54:08 -0500 Subject: [PATCH 105/172] Update website/content/docs/connect/proxies/envoy.mdx --- website/content/docs/connect/proxies/envoy.mdx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/website/content/docs/connect/proxies/envoy.mdx b/website/content/docs/connect/proxies/envoy.mdx index b6eae4678b..b1082ee838 100644 --- a/website/content/docs/connect/proxies/envoy.mdx +++ b/website/content/docs/connect/proxies/envoy.mdx @@ -176,6 +176,8 @@ The [Advanced Configuration](#advanced-configuration) section describes addition ### Bootstrap Envoy on Windows VMs +> Complete the [Connect Services on Windows Workloads to Consul Service Mesh tutorial](/tutorials/consul/consul-on-windows-workloads) 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 From c1fedba08e1ac332dabb604b9e958072dbbf4c52 Mon Sep 17 00:00:00 2001 From: Jeff Boruszak <104028618+boruszak@users.noreply.github.com> Date: Mon, 3 Oct 2022 13:59:41 -0500 Subject: [PATCH 106/172] Update website/content/docs/connect/proxies/envoy.mdx --- website/content/docs/connect/proxies/envoy.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/content/docs/connect/proxies/envoy.mdx b/website/content/docs/connect/proxies/envoy.mdx index b1082ee838..0d5067e5d0 100644 --- a/website/content/docs/connect/proxies/envoy.mdx +++ b/website/content/docs/connect/proxies/envoy.mdx @@ -176,7 +176,7 @@ The [Advanced Configuration](#advanced-configuration) section describes addition ### Bootstrap Envoy on Windows VMs -> Complete the [Connect Services on Windows Workloads to Consul Service Mesh tutorial](/tutorials/consul/consul-on-windows-workloads) to learn how to deploy Consul and use its service mesh on Windows VMs. +> Complete the [Connect Services on Windows Workloads to Consul Service Mesh tutorial](https://learn.hashicorp.com/tutorials/consul/consul-on-windows-workloads) 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: From 6b4d15f23399267bbc34af3d3e5496383cf9ac4b Mon Sep 17 00:00:00 2001 From: Jeff Boruszak <104028618+boruszak@users.noreply.github.com> Date: Mon, 3 Oct 2022 14:03:00 -0500 Subject: [PATCH 107/172] Update website/content/docs/connect/proxies/envoy.mdx --- website/content/docs/connect/proxies/envoy.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/content/docs/connect/proxies/envoy.mdx b/website/content/docs/connect/proxies/envoy.mdx index 0d5067e5d0..0e7f843227 100644 --- a/website/content/docs/connect/proxies/envoy.mdx +++ b/website/content/docs/connect/proxies/envoy.mdx @@ -176,7 +176,7 @@ The [Advanced Configuration](#advanced-configuration) section describes addition ### Bootstrap Envoy on Windows VMs -> Complete the [Connect Services on Windows Workloads to Consul Service Mesh tutorial](https://learn.hashicorp.com/tutorials/consul/consul-on-windows-workloads) to learn how to deploy Consul and use its service mesh on Windows VMs. +> Complete the [Connect Services on Windows Workloads to Consul Service Mesh tutorial](https://learn.hashicorp.com/tutorials/consul/consul-on-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: From 6eefbe0e9d579868d7b90ec1355f1a63847a61e4 Mon Sep 17 00:00:00 2001 From: boruszak Date: Mon, 3 Oct 2022 14:08:57 -0500 Subject: [PATCH 108/172] Tutorial links --- website/content/docs/connect/cluster-peering/index.mdx | 2 ++ website/content/docs/connect/cluster-peering/k8s.mdx | 2 ++ 2 files changed, 4 insertions(+) diff --git a/website/content/docs/connect/cluster-peering/index.mdx b/website/content/docs/connect/cluster-peering/index.mdx index a1de37709f..a3be7df95a 100644 --- a/website/content/docs/connect/cluster-peering/index.mdx +++ b/website/content/docs/connect/cluster-peering/index.mdx @@ -22,6 +22,8 @@ Cluster peering is a process that allows Consul clusters to communicate with eac 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 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. diff --git a/website/content/docs/connect/cluster-peering/k8s.mdx b/website/content/docs/connect/cluster-peering/k8s.mdx index e9b42a8663..55dcbcc4fb 100644 --- a/website/content/docs/connect/cluster-peering/k8s.mdx +++ b/website/content/docs/connect/cluster-peering/k8s.mdx @@ -20,6 +20,8 @@ The following CRDs are used to create and manage a peering connection: 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: From 71b14a13fc9fb2b9f1f1200764bfe9f6803ff944 Mon Sep 17 00:00:00 2001 From: boruszak Date: Mon, 3 Oct 2022 14:18:59 -0500 Subject: [PATCH 109/172] PeerName changed to Peer - fix --- .../docs/connect/cluster-peering/create-manage-peering.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 06271323a2..266dd45ba4 100644 --- a/website/content/docs/connect/cluster-peering/create-manage-peering.mdx +++ b/website/content/docs/connect/cluster-peering/create-manage-peering.mdx @@ -151,7 +151,7 @@ Services = [ { ## The peer name to reference in config is the one set ## during the peering process. - PeerName = "cluster-02" + Peer = "cluster-02" } ] } From 622804ad5b64c51dd8b08a84507bf7a914ee43c3 Mon Sep 17 00:00:00 2001 From: cskh Date: Mon, 3 Oct 2022 16:55:05 -0400 Subject: [PATCH 110/172] fix flaky integration test (#14843) --- .../verify.bats | 19 +------------------ .../config_entries.hcl | 8 +++++++- .../case-ingress-gateway-simple/verify.bats | 19 ++++++++++++++++++- 3 files changed, 26 insertions(+), 20 deletions(-) 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 faaefae235..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 @@ -26,28 +26,11 @@ load helpers assert_upstream_has_endpoints_in_status 127.0.0.1:20000 s1 HEALTHY 1 } -@test "s1 proxy should have been configured with max_connections in services" { - 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" = "300" ] -} - @test "ingress-gateway should have healthy endpoints for s2" { assert_upstream_has_endpoints_in_status 127.0.0.1:20000 s2 HEALTHY 1 } -@test "s2 proxy should have been configured with max_connections using defaults" { +@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 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" { From 035e126fae0bb9518700099704b3f82260bb7dd6 Mon Sep 17 00:00:00 2001 From: Evan Culver Date: Mon, 3 Oct 2022 16:49:24 -0700 Subject: [PATCH 111/172] ci: Fix changelog-checker GHA workflow (#14842) --- .github/workflows/changelog-checker.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) 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 From f5cb922dd0217bd1faa13fbec068b84f9618cc66 Mon Sep 17 00:00:00 2001 From: vanphan24 <89482663+vanphan24@users.noreply.github.com> Date: Mon, 3 Oct 2022 19:32:43 -0700 Subject: [PATCH 112/172] first commit with overview page (#14827) * Why Choose Consul Co-authored-by: David Yu Co-authored-by: Jeff Boruszak <104028618+boruszak@users.noreply.github.com> Co-authored-by: trujillo-adam <47586768+trujillo-adam@users.noreply.github.com> Co-authored-by: Tu Nguyen --- .../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/data/docs-nav-data.json | 27 ++++++++++++++++++- 6 files changed, 114 insertions(+), 1 deletion(-) 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 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..f53f45ba72 --- /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 documentation](/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/data/docs-nav-data.json b/website/data/docs-nav-data.json index 5004b0c949..ea18908512 100644 --- a/website/data/docs-nav-data.json +++ b/website/data/docs-nav-data.json @@ -3,6 +3,31 @@ "title": "What is Consul?", "path": "intro" }, + { + "title": "Why Choose Consul?", + "routes": [ + { + "title": "Overview", + "path": "consul-vs-other" + }, + { + "title": "Service Meshes", + "path": "consul-vs-other/service-mesh-compare" + }, + { + "title": "DNS Tools", + "path": "consul-vs-other/dns-tools-compare" + }, + { + "title": "Configuration Management Tools", + "path": "consul-vs-other/config-management-compare" + }, + { + "title": "API Gateways", + "path": "consul-vs-other/api-gateway-compare" + } + ] + }, { "title": "Core Concepts", "routes": [ @@ -1395,4 +1420,4 @@ "path": "guides", "hidden": true } -] \ No newline at end of file +] From aa4b44c27fe6fdc5c1080353ce4ff6d11d037546 Mon Sep 17 00:00:00 2001 From: boruszak Date: Tue, 4 Oct 2022 09:07:37 -0500 Subject: [PATCH 113/172] Move Installation instructions --- .../content/docs/connect/dataplane/index.mdx | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/website/content/docs/connect/dataplane/index.mdx b/website/content/docs/connect/dataplane/index.mdx index 2fe2c26749..da1df67c4a 100644 --- a/website/content/docs/connect/dataplane/index.mdx +++ b/website/content/docs/connect/dataplane/index.mdx @@ -37,6 +37,24 @@ To get started with Consul Dataplane, use the following reference resources: - For Helm chart information, refer to the [Helm Chart reference](/consul/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: From 9e7c83a1290eacdd474fd9688ae4d0dbe12bf08b Mon Sep 17 00:00:00 2001 From: Jeff Boruszak <104028618+boruszak@users.noreply.github.com> Date: Tue, 4 Oct 2022 09:26:07 -0500 Subject: [PATCH 114/172] Apply suggestions from code review Co-authored-by: trujillo-adam <47586768+trujillo-adam@users.noreply.github.com> --- .../cluster-peering/create-manage-peering.mdx | 18 +++++++++--------- .../docs/connect/cluster-peering/index.mdx | 8 ++++---- .../docs/connect/cluster-peering/k8s.mdx | 18 ++++++++++++++---- 3 files changed, 27 insertions(+), 17 deletions(-) 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 266dd45ba4..80dcdb35e5 100644 --- a/website/content/docs/connect/cluster-peering/create-manage-peering.mdx +++ b/website/content/docs/connect/cluster-peering/create-manage-peering.mdx @@ -73,10 +73,10 @@ 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**. -2. Click **Add peer connection**. -3. In the **Generate token** tab, enter `cluster-02` in the **Name of peer** field. -4. Click the **Generate token** button. -5. 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. 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. You cannot view it again after leaving this screen. If you lose your token, you must generate a new one. @@ -376,7 +376,7 @@ 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. @@ -434,7 +434,7 @@ spec: ### Service splitters and custom routes -The `service-splitter` and `service-router` config 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` config 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: +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: @@ -444,7 +444,7 @@ Name = "frontend" Splits = [ { Weight = 50 - ## defaults to service with same name as config entry ("frontend") + ## defaults to service with same name as configuration entry ("frontend") }, { Weight = 50 @@ -461,7 +461,7 @@ metadata: spec: splits: - weight: 50 - ## defaults to service with same name as config entry ("web") + ## defaults to service with same name as configuration entry ("web") - weight: 50 service: frontend-peer ``` @@ -484,7 +484,7 @@ spec: -Then, create a local `service-resolver` config entry named `frontend-peer` and define a redirect targeting the peer and its service: +Then, create a local `service-resolver` configuration entry named `frontend-peer` and define a redirect targeting the peer and its service: diff --git a/website/content/docs/connect/cluster-peering/index.mdx b/website/content/docs/connect/cluster-peering/index.mdx index a3be7df95a..76cbf1de0d 100644 --- a/website/content/docs/connect/cluster-peering/index.mdx +++ b/website/content/docs/connect/cluster-peering/index.mdx @@ -16,9 +16,9 @@ You can create peering connections between two or more independent clusters so t 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. -2. Use the peering token to establish peering with a second cluster. -3. Export services between clusters. -4. Create intentions to authorize services for peers. +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 establishing cluster peering connections, refer to [Create and Manage Peering Connections](/docs/connect/cluster-peering/create-manage-peering). @@ -56,7 +56,7 @@ 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. -- The `service-splitter` and `service-router` config 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 `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. diff --git a/website/content/docs/connect/cluster-peering/k8s.mdx b/website/content/docs/connect/cluster-peering/k8s.mdx index 55dcbcc4fb..c4f5d51c96 100644 --- a/website/content/docs/connect/cluster-peering/k8s.mdx +++ b/website/content/docs/connect/cluster-peering/k8s.mdx @@ -32,7 +32,9 @@ You must implement the following requirements to create and use cluster peering ### Prepare for installation -1. After you provision a Kubernetes cluster and set up your kubeconfig file to manage access to multiple Kubernetes clusters, 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/). +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. 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: @@ -101,9 +103,11 @@ Install Consul on Kubernetes by using the CLI to apply `values.yaml` to each clu ## 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. Peers identify each other using the `metadata.name` values you establish when creating 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. @@ -171,7 +175,7 @@ To peer Kubernetes clusters running Consul, you need to create a peering token a ### Export services between clusters -~> **Tip**: The examples 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. +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. @@ -433,7 +437,13 @@ To recreate or reset the peering connection, you need to generate a new peering -2. After you update `PeeringAcceptor`, repeat the steps to create a peering connection, including saving a new peering token and exporting it to the other cluster. Your peering connection is re-established with the updated token. +2. 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. From 99639b1316e610ae08fd13d171d71dc6b09b247b Mon Sep 17 00:00:00 2001 From: boruszak Date: Tue, 4 Oct 2022 09:29:16 -0500 Subject: [PATCH 115/172] Installation instructions moved --- .../connect/dataplane/consul-dataplane.mdx | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/website/content/docs/connect/dataplane/consul-dataplane.mdx b/website/content/docs/connect/dataplane/consul-dataplane.mdx index 95dd895634..d432aadaee 100644 --- a/website/content/docs/connect/dataplane/consul-dataplane.mdx +++ b/website/content/docs/connect/dataplane/consul-dataplane.mdx @@ -17,24 +17,6 @@ Usage: `consul-dataplane [options]` 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). -### 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 -``` - ### Startup The following options are required when starting `consul-dataplane` with the CLI: From 6d0f58ffe5ab14df1ce16d09f6032a46afa0d46e Mon Sep 17 00:00:00 2001 From: boruszak Date: Tue, 4 Oct 2022 09:31:31 -0500 Subject: [PATCH 116/172] Minor fixes --- website/content/docs/connect/cluster-peering/k8s.mdx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/website/content/docs/connect/cluster-peering/k8s.mdx b/website/content/docs/connect/cluster-peering/k8s.mdx index c4f5d51c96..8b565e2204 100644 --- a/website/content/docs/connect/cluster-peering/k8s.mdx +++ b/website/content/docs/connect/cluster-peering/k8s.mdx @@ -437,9 +437,9 @@ To recreate or reset the peering connection, you need to generate a new peering -2. 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. 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) @@ -470,4 +470,4 @@ spec: namespace: 'default' ``` - \ No newline at end of file + From 6a7cda41a6d5cf108d813425ac5d5a2134505ec7 Mon Sep 17 00:00:00 2001 From: trujillo-adam <47586768+trujillo-adam@users.noreply.github.com> Date: Tue, 4 Oct 2022 07:39:37 -0700 Subject: [PATCH 117/172] Apply suggestions from code review Co-authored-by: Jeff Boruszak <104028618+boruszak@users.noreply.github.com> --- website/content/docs/lambda/index.mdx | 4 +-- .../docs/lambda/invoke-from-lambda.mdx | 11 ++++---- .../docs/lambda/registration/automate.mdx | 28 +++++++++---------- .../docs/lambda/registration/index.mdx | 8 +++--- .../docs/lambda/registration/manual.mdx | 4 +-- 5 files changed, 27 insertions(+), 28 deletions(-) diff --git a/website/content/docs/lambda/index.mdx b/website/content/docs/lambda/index.mdx index 008d6f6a54..4bb6454852 100644 --- a/website/content/docs/lambda/index.mdx +++ b/website/content/docs/lambda/index.mdx @@ -8,7 +8,7 @@ description: >- # AWS Lambda Overview -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. +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. ## Register Lambda functions into Consul @@ -18,7 +18,7 @@ Refer to [Lambda Function Registration Requirements](/docs/lambda/registration/i ## Invoke Lambda functions from Consul service mesh -After registering AWS Lambda functions, you can invoke Lambda functions from the Consul service mesh through terminating gateways (recommended) or directly from connect proxies. +After registering AWS Lambda functions, you can invoke Lambda functions from the Consul service mesh through terminating gateways (recommended) or directly from connected proxies. Refer to [Invoke Lambda Functions from Services](/docs/lambda/invocation) for details. diff --git a/website/content/docs/lambda/invoke-from-lambda.mdx b/website/content/docs/lambda/invoke-from-lambda.mdx index bd51d2e3dd..495e7e92a9 100644 --- a/website/content/docs/lambda/invoke-from-lambda.mdx +++ b/website/content/docs/lambda/invoke-from-lambda.mdx @@ -16,7 +16,7 @@ This topic describes how to invoke services in the mesh from Lambda functions re The following steps describe the process: 1. Deploy the destination service and mesh gateway. -1. Deploy the Lambda extension layer +1. Deploy the Lambda extension layer. 1. Deploy the Lambda registrator. 1. Write the Lambda function code. 1. Deploy the Lambda function. @@ -86,7 +86,7 @@ spec: 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 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) @@ -97,7 +97,7 @@ The `consul-lambda-extension` extension runs during the `Init` phase of the Lamb 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: +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 @@ -247,8 +247,7 @@ func main() { -1. Issue the `terraform apply` command and Consul automatically configures a service for the Lambda function. - +1. Run the `terraform apply` command and Consul automatically configures a service for the Lambda function. ### Lambda extension configuration @@ -267,7 +266,7 @@ Define the following environment variables in your Lambda functions to configure 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.: +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 index 09a9cd0ea6..45e085b310 100644 --- a/website/content/docs/lambda/registration/automate.mdx +++ b/website/content/docs/lambda/registration/automate.mdx @@ -25,7 +25,6 @@ Scheduled events fully synchronize functions between Lambda and Consul to preven The following diagram shows the flow of events from EventBridge into Consul: - ![Lambda Registrator Architecture](/img/lambda_registrator_architecture.svg) @@ -43,11 +42,13 @@ Verify that your environment meets the requirements specified in [Lambda Functio ## Configuration -The Lambda registrator stores data in the AWS parameter store. You can configure the type of data stored and how to store it. +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 (refer to the [Parameter Store documentation](https://docs.aws.amazon.com/systems-manager/latest/userguide/systems-manager-parameter-store.html) for additional information) to verify the authenticity of the Consul API. You can apply the following Terraform configuration to store Consul's server CA 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" { @@ -73,7 +74,7 @@ If [Consul access control lists (ACLs)](/docs/security/acl) are enabled, Lambda -1. Issue `consul acl policy create` command to create the policy. The following example creates a policy called `lambda-registrator-policy` containing permissions specified in `rules.hcl`: +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 ``` @@ -102,7 +103,6 @@ Lambda registrator encrypts and stores all data for Lambda functions in the AWS The registrator also requires the following IAM permissions to access the parameter store: - ```json { "Version": "2012-10-17", @@ -120,17 +120,17 @@ The registrator also requires the following IAM permissions to access the parame | Name | Description | | - | - | -| `name` | Specifies the name 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. | +| `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 see [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 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 see [Optional: Store the ACL Token in Parameter Store](#optional-store-the-acl-token-in-parameter-store)| -| `node_name` | The Consul node name that Lambdas will be registered to. This defaults to `lambdas`. | -| `enterprise` | Determines if the Consul server at `consul_http_addr` is running open source or enterprise. | -| `partitions` | The partitions that Lambda registrator manages. | +| `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 @@ -185,6 +185,6 @@ The following tags are supported. The path prefix for all tags is `serverless.co | `/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 will be registered in. Default is `default` if `enterprise` is enabled. | -| `/partition` | Specifies the Consul partition the service will be registered in. Defaults is `default` if `enterprise` is enabled. | -| `/aliases` | Specifies a `+`-separated string of Lambda aliases that will be registered into Consul. For example, if set to `dev+staging+prod`, the `dev`, `staging`, and `prod` aliases of the Lambda function will be registered into Consul. | +| `/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/index.mdx b/website/content/docs/lambda/registration/index.mdx index 9588a4e28f..8b64afd81a 100644 --- a/website/content/docs/lambda/registration/index.mdx +++ b/website/content/docs/lambda/registration/index.mdx @@ -14,7 +14,7 @@ You can either manually register AWS Lambda functions with Consul or use the Lam ## Requirements -Consul 1.12.1 and later +Consul v1.12.1 and later ## Prerequisites @@ -50,15 +50,15 @@ enables an IAM user or role to invoke the `example` Lambda function: } ``` -Define AWS IAM credentials in environment variables, EC2 metadata or +Define AWS IAM credentials in environment variables, EC2 metadata, or ECS metadata. On [AWS EKS](https://aws.amazon.com/eks/), associate an IAM role with the proxy's `ServiceAccount`. Refer to the [AWS IAM roles for service accounts](https://docs.aws.amazon.com/eks/latest/userguide/iam-roles-for-service-accounts.html) documentation for instructions. ### Mesh gateway A mesh gateway is required in the following scenarios: -* Invoking mesh services from Lambda functions -* Invoking Lambda functions from a service deployed to a separate Consul data center +- Invoking mesh services from Lambda functions +- Invoking Lambda functions from a service deployed to a separate Consul datacenter Mesh gateways are optional for enabling services to invoke Lambda functions if they are in the same datacenter. diff --git a/website/content/docs/lambda/registration/manual.mdx b/website/content/docs/lambda/registration/manual.mdx index 01f475ecbc..bcf58c4b66 100644 --- a/website/content/docs/lambda/registration/manual.mdx +++ b/website/content/docs/lambda/registration/manual.mdx @@ -46,7 +46,7 @@ You can manually register Lambda functions if you are unable to automate the pro $ 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 (see [Supported `Meta` fields](#supported-meta-fields). The following example creates a `service-defaults` configuration entry named `lambda`: +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`: @@ -71,7 +71,7 @@ You can manually register Lambda functions if you are unable to automate the pro ### 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: +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`. From 631e7c1d7b49783a07580cf14980ab9166638dfd Mon Sep 17 00:00:00 2001 From: boruszak Date: Tue, 4 Oct 2022 09:49:10 -0500 Subject: [PATCH 118/172] Tab groups fix --- .../content/docs/connect/config-entries/exported-services.mdx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/website/content/docs/connect/config-entries/exported-services.mdx b/website/content/docs/connect/config-entries/exported-services.mdx index 0aa37e711e..67308cf509 100644 --- a/website/content/docs/connect/config-entries/exported-services.mdx +++ b/website/content/docs/connect/config-entries/exported-services.mdx @@ -4,6 +4,7 @@ page_title: Exported Services - Configuration Entry Reference description: >- An exported services configuration entry defines the availability of a cluster's services to cluster peers and local admin partitions. Learn about `""exported-services""` config entry parameters and exporting services to other datacenters. --- + # Exported Services Configuration Entry @@ -678,3 +679,5 @@ 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). + + \ No newline at end of file From b455e0d5c83c016e48d62effae56b39a957e9aea Mon Sep 17 00:00:00 2001 From: boruszak Date: Tue, 4 Oct 2022 10:00:53 -0500 Subject: [PATCH 119/172] Tabs fix again --- .../docs/connect/cluster-peering/create-manage-peering.mdx | 4 +++- .../docs/connect/config-entries/exported-services.mdx | 5 +---- 2 files changed, 4 insertions(+), 5 deletions(-) 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 80dcdb35e5..1a6b682000 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 @@ -519,4 +520,5 @@ spec: } ``` - \ No newline at end of file + + \ No newline at end of file diff --git a/website/content/docs/connect/config-entries/exported-services.mdx b/website/content/docs/connect/config-entries/exported-services.mdx index 67308cf509..0ed093f2de 100644 --- a/website/content/docs/connect/config-entries/exported-services.mdx +++ b/website/content/docs/connect/config-entries/exported-services.mdx @@ -4,7 +4,6 @@ page_title: Exported Services - Configuration Entry Reference description: >- An exported services configuration entry defines the availability of a cluster's services to cluster peers and local admin partitions. Learn about `""exported-services""` config entry parameters and exporting services to other datacenters. --- - # Exported Services Configuration Entry @@ -678,6 +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). - - \ No newline at end of file +For additional information, refer to [Health HTTP Endpoint](/api-docs/health). \ No newline at end of file From cf796ce3305b6f9cf8bb81879c8b98bf1a362036 Mon Sep 17 00:00:00 2001 From: boruszak Date: Tue, 4 Oct 2022 10:20:14 -0500 Subject: [PATCH 120/172] More group fix attempts --- .../cluster-peering/create-manage-peering.mdx | 119 +++++++++--------- 1 file changed, 60 insertions(+), 59 deletions(-) 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 1a6b682000..330c8a705b 100644 --- a/website/content/docs/connect/cluster-peering/create-manage-peering.mdx +++ b/website/content/docs/connect/cluster-peering/create-manage-peering.mdx @@ -34,52 +34,52 @@ 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. + In `cluster-01`, use the [`/peering/token` endpoint](/api-docs/peering#generate-a-peering-token) to issue a request for a peering token. -```shell-session -$ curl --request POST --data '{"PeerName":"cluster-02"}' --url http://localhost:8500/v1/peering/token -``` + ```shell-session + $ curl --request POST --data '{"PeerName":"cluster-02"}' --url http://localhost:8500/v1/peering/token + ``` -The CLI outputs the peering token, which is a base64-encoded string containing the token details. + The CLI outputs the peering token, which is a base64-encoded string containing the token details. -Create a JSON file that contains the first cluster's name and the peering token. + Create a JSON file that contains the first cluster's name and the peering token. - + -```json -{ - "PeerName": "cluster-01", - "PeeringToken": "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJhZG1pbiIsImF1ZCI6IlNvbHIifQ.5T7L_L1MPfQ_5FjKGa1fTPqrzwK4bNSM812nW6oyjb8" -} -``` + ```json + { + "PeerName": "cluster-01", + "PeeringToken": "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJhZG1pbiIsImF1ZCI6IlNvbHIifQ.5T7L_L1MPfQ_5FjKGa1fTPqrzwK4bNSM812nW6oyjb8" + } + ``` - - + + - + -In `cluster-01`, use the [`consul peering generate-token` command](/commands/operator/generate-token) to issue a request for a peering token. + In `cluster-01`, use the [`consul peering generate-token` command](/commands/operator/generate-token) to issue a request for a peering token. -```shell-session -$ consul peering generate-token -name cluster-02 -``` + ```shell-session + $ consul peering generate-token -name cluster-02 + ``` -The CLI outputs the peering token, which is a base64-encoded string containing the token details. -Save this value to a file or clipboard to be used in the next step on `cluster-02`. + The CLI outputs the peering token, which is a base64-encoded string containing the token details. + Save this value to a file or clipboard to be used in the next step on `cluster-02`. - + - + -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. You cannot view it again after leaving this screen. If you lose your token, you must generate a new one. + 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. You cannot view it again after leaving this screen. If you lose your token, you must generate a new one. - + ### Establish a connection between clusters @@ -87,47 +87,47 @@ 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. + 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. + + ```shell-session + $ curl --request POST --data @peering_token.json http://127.0.0.1:8500/v1/peering/establish + ``` -```shell-session -$ curl --request POST --data @peering_token.json http://127.0.0.1:8500/v1/peering/establish -``` + 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). -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. -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. -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. + ```shell-session + $ consul peering establish -name cluster-01 -peering-token token-from-generate + ``` -```shell-session -$ consul peering establish -name cluster-01 -peering-token token-from-generate -``` + 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` flag to the `establish` command and specify the partitions you want to peer. + For additional configuration information, refer to [`consul peering establish` command](/commands/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` flag to the `establish` command and specify the partitions you want to peer. -For additional configuration information, refer to [`consul peering establish` command](/commands/peering/establish) . + You can run the `peering establish` command 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. -You can run the `peering establish` command 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. + - + - + 1. In the Consul UI for the datacenter associated with `cluster 02`, click **Peers** and then **Add peer connection**. + 1. Click **Establish peering**. + 1. In the **Name of peer** field, enter `cluster-01`. Then paste the peering token in the **Token** field. + 1. Click **Add peer**. -1. In the Consul UI for the datacenter associated with `cluster 02`, click **Peers** and then **Add peer connection**. -2. Click **Establish peering**. -3. In the **Name of peer** field, enter `cluster-01`. Then paste the peering token in the **Token** field. -4. Click **Add peer**. - - + ### Export services between clusters @@ -521,4 +521,5 @@ spec: ``` + \ No newline at end of file From 960c42854b2b0bd063278efab964b2203fc8a3d7 Mon Sep 17 00:00:00 2001 From: Luke Kysow <1034429+lkysow@users.noreply.github.com> Date: Tue, 4 Oct 2022 08:35:19 -0700 Subject: [PATCH 121/172] Remove terminal colouring from test output so it is (#14810) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit more readable in CI. ``` Running primary verification step for case-ingress-gateway-multiple-services... �[34;1mverify.bats �[0m�[1G ingress proxy admin is up on :20000�[K�[75G 1/12�[2G�[1G ✓ ingress proxy admin is up on :20000�[K �[0m�[1G s1 proxy admin is up on :19000�[K�[75G 2/12�[2G�[1G ✓ s1 proxy admin is up on :19000�[K �[0m�[1G s2 proxy admin is up on :19001�[K�[75G 3/12�[2G�[1G ✓ s2 proxy admin is up on :19001�[K �[0m�[1G s1 proxy listener should be up and have right cert�[K�[75G 4/12�[2G�[1G ✓ s1 proxy listener should be up and have right cert�[K �[0m�[1G s2 proxy listener should be up and have right cert�[K�[75G 5/12�[2G�[1G ✓ s2 proxy listener should be up and have right cert�[K �[0m�[1G ingress-gateway should have healthy endpoints for s1�[K�[75G 6/12�[2G�[31;1m�[1G ✗ ingress-gateway should have healthy endpoints for s1�[K �[0m�[31;22m (from function `assert_upstream_has_endpoints_in_status' in file /workdir/primary/bats/helpers.bash, line 385, ``` versus ``` Running primary verification step for case-ingress-gateway-multiple-services... 1..12 ok 1 ingress proxy admin is up on :20000 ok 2 s1 proxy admin is up on :19000 ok 3 s2 proxy admin is up on :19001 ok 4 s1 proxy listener should be up and have right cert ok 5 s2 proxy listener should be up and have right cert not ok 6 ingress-gateway should have healthy endpoints for s1 not ok 7 s1 proxy should have been configured with max_connections in services ok 8 ingress-gateway should have healthy endpoints for s2 ``` --- test/integration/connect/envoy/helpers.bash | 28 --------------------- test/integration/connect/envoy/run-tests.sh | 8 +++--- 2 files changed, 4 insertions(+), 32 deletions(-) diff --git a/test/integration/connect/envoy/helpers.bash b/test/integration/connect/envoy/helpers.bash index d7fe0ae024..a5d0320538 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 diff --git a/test/integration/connect/envoy/run-tests.sh b/test/integration/connect/envoy/run-tests.sh index 43e0925c6c..7ea41527e5 100755 --- a/test/integration/connect/envoy/run-tests.sh +++ b/test/integration/connect/envoy/run-tests.sh @@ -354,10 +354,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 +472,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 From 9792f9ea26367e009cf1a34c0855cb9500e8b810 Mon Sep 17 00:00:00 2001 From: boruszak Date: Tue, 4 Oct 2022 10:34:18 -0500 Subject: [PATCH 122/172] Reverts + fix --- .../cluster-peering/create-manage-peering.mdx | 119 +++++++++--------- 1 file changed, 59 insertions(+), 60 deletions(-) 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 330c8a705b..1a6b682000 100644 --- a/website/content/docs/connect/cluster-peering/create-manage-peering.mdx +++ b/website/content/docs/connect/cluster-peering/create-manage-peering.mdx @@ -34,52 +34,52 @@ 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. +In `cluster-01`, use the [`/peering/token` endpoint](/api-docs/peering#generate-a-peering-token) to issue a request for a peering token. - ```shell-session - $ curl --request POST --data '{"PeerName":"cluster-02"}' --url http://localhost:8500/v1/peering/token - ``` +```shell-session +$ curl --request POST --data '{"PeerName":"cluster-02"}' --url http://localhost:8500/v1/peering/token +``` - The CLI outputs the peering token, which is a base64-encoded string containing the token details. +The CLI outputs the peering token, which is a base64-encoded string containing the token details. - Create a JSON file that contains the first cluster's name and the peering token. +Create a JSON file that contains the first cluster's name and the peering token. - + - ```json - { - "PeerName": "cluster-01", - "PeeringToken": "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJhZG1pbiIsImF1ZCI6IlNvbHIifQ.5T7L_L1MPfQ_5FjKGa1fTPqrzwK4bNSM812nW6oyjb8" - } - ``` +```json +{ + "PeerName": "cluster-01", + "PeeringToken": "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJhZG1pbiIsImF1ZCI6IlNvbHIifQ.5T7L_L1MPfQ_5FjKGa1fTPqrzwK4bNSM812nW6oyjb8" +} +``` - - + + - + - In `cluster-01`, use the [`consul peering generate-token` command](/commands/operator/generate-token) to issue a request for a peering token. +In `cluster-01`, use the [`consul peering generate-token` command](/commands/operator/generate-token) to issue a request for a peering token. - ```shell-session - $ consul peering generate-token -name cluster-02 - ``` +```shell-session +$ consul peering generate-token -name cluster-02 +``` - The CLI outputs the peering token, which is a base64-encoded string containing the token details. - Save this value to a file or clipboard to be used in the next step on `cluster-02`. +The CLI outputs the peering token, which is a base64-encoded string containing the token details. +Save this value to a file or clipboard to be used in the next step on `cluster-02`. - + - + - 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. You cannot view it again after leaving this screen. If you lose your token, you must generate a new one. +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. You cannot view it again after leaving this screen. If you lose your token, you must generate a new one. - + ### Establish a connection between clusters @@ -87,47 +87,47 @@ Every time you generate a peering token, a single-use establishment secret is em 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. - - ```shell-session - $ curl --request POST --data @peering_token.json http://127.0.0.1:8500/v1/peering/establish - ``` +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. - 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). +```shell-session +$ curl --request POST --data @peering_token.json http://127.0.0.1:8500/v1/peering/establish +``` - 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. +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. + - ```shell-session - $ consul peering establish -name cluster-01 -peering-token token-from-generate - ``` +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. - 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` flag to the `establish` command and specify the partitions you want to peer. - For additional configuration information, refer to [`consul peering establish` command](/commands/peering/establish) . +```shell-session +$ consul peering establish -name cluster-01 -peering-token token-from-generate +``` - You can run the `peering establish` command 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. +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` flag to the `establish` command and specify the partitions you want to peer. +For additional configuration information, refer to [`consul peering establish` command](/commands/peering/establish) . - +You can run the `peering establish` command 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. - + - 1. In the Consul UI for the datacenter associated with `cluster 02`, click **Peers** and then **Add peer connection**. - 1. Click **Establish peering**. - 1. In the **Name of peer** field, enter `cluster-01`. Then paste the peering token in the **Token** field. - 1. Click **Add peer**. + - +1. In the Consul UI for the datacenter associated with `cluster 02`, click **Peers** and then **Add peer connection**. +2. Click **Establish peering**. +3. In the **Name of peer** field, enter `cluster-01`. Then paste the peering token in the **Token** field. +4. Click **Add peer**. + + ### Export services between clusters @@ -521,5 +521,4 @@ spec: ``` - \ No newline at end of file From c1f71e3ef82c5c0c121de10cf280f082a32599e2 Mon Sep 17 00:00:00 2001 From: boruszak Date: Tue, 4 Oct 2022 10:34:42 -0500 Subject: [PATCH 123/172] list --- .../connect/cluster-peering/create-manage-peering.mdx | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) 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 1a6b682000..d410bfc613 100644 --- a/website/content/docs/connect/cluster-peering/create-manage-peering.mdx +++ b/website/content/docs/connect/cluster-peering/create-manage-peering.mdx @@ -123,9 +123,9 @@ 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**. -2. Click **Establish peering**. -3. In the **Name of peer** field, enter `cluster-01`. Then paste the peering token in the **Token** field. -4. Click **Add peer**. +1. Click **Establish peering**. +1. In the **Name of peer** field, enter `cluster-01`. Then paste the peering token in the **Token** field. +1. Click **Add peer**. @@ -377,7 +377,9 @@ 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. From 213b50b17085ffcad271518fd3591f9a53082d93 Mon Sep 17 00:00:00 2001 From: boruszak Date: Tue, 4 Oct 2022 12:19:24 -0500 Subject: [PATCH 124/172] Tutorial link fix --- website/content/docs/connect/proxies/envoy.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/content/docs/connect/proxies/envoy.mdx b/website/content/docs/connect/proxies/envoy.mdx index 0e7f843227..d505795cdd 100644 --- a/website/content/docs/connect/proxies/envoy.mdx +++ b/website/content/docs/connect/proxies/envoy.mdx @@ -176,7 +176,7 @@ The [Advanced Configuration](#advanced-configuration) section describes addition ### Bootstrap Envoy on Windows VMs -> Complete the [Connect Services on Windows Workloads to Consul Service Mesh tutorial](https://learn.hashicorp.com/tutorials/consul/consul-on-windows-workloads?utm_source=docs) to learn how to deploy Consul and use its service mesh on Windows VMs. +> Complete the [Connect Services on Windows Workloads to Consul Service Mesh tutorial](https://learn.hashicorp.com/tutorials/consul/developer-mesh/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: From 904ab64a572d5da39bd4ddfbb0195eb413da161a Mon Sep 17 00:00:00 2001 From: boruszak Date: Tue, 4 Oct 2022 12:42:59 -0500 Subject: [PATCH 125/172] Link fix --- website/content/docs/connect/proxies/envoy.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/content/docs/connect/proxies/envoy.mdx b/website/content/docs/connect/proxies/envoy.mdx index d505795cdd..7c745fc097 100644 --- a/website/content/docs/connect/proxies/envoy.mdx +++ b/website/content/docs/connect/proxies/envoy.mdx @@ -176,7 +176,7 @@ The [Advanced Configuration](#advanced-configuration) section describes addition ### Bootstrap Envoy on Windows VMs -> Complete the [Connect Services on Windows Workloads to Consul Service Mesh tutorial](https://learn.hashicorp.com/tutorials/consul/developer-mesh/consul-windows-workloads?utm_source=docs) to learn how to deploy Consul and use its service mesh 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: From c9236128bfb9bc6e8a5062864711a37847a6ba32 Mon Sep 17 00:00:00 2001 From: Ashlee M Boyer <43934258+ashleemboyer@users.noreply.github.com> Date: Tue, 4 Oct 2022 14:00:32 -0400 Subject: [PATCH 126/172] Fixing broken links --- website/content/docs/ecs/index.mdx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/website/content/docs/ecs/index.mdx b/website/content/docs/ecs/index.mdx index 711c9f77b0..66c6ad6210 100644 --- a/website/content/docs/ecs/index.mdx +++ b/website/content/docs/ecs/index.mdx @@ -31,8 +31,8 @@ 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 [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 [Serverless Consul Service Mesh with ECS and HCP](https://learn.hashicorp.com/tutorials/cloud/consul-ecs-hcp?in=consul/cloud-integrations&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?in=consul/cloud-integrations&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. From bf2edc4414f61742a12d1f41ae33416f85238fcb Mon Sep 17 00:00:00 2001 From: Ashlee M Boyer <43934258+ashleemboyer@users.noreply.github.com> Date: Tue, 4 Oct 2022 14:13:57 -0400 Subject: [PATCH 127/172] Remove unneeded in params --- website/content/docs/ecs/index.mdx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/website/content/docs/ecs/index.mdx b/website/content/docs/ecs/index.mdx index 66c6ad6210..7609c6963e 100644 --- a/website/content/docs/ecs/index.mdx +++ b/website/content/docs/ecs/index.mdx @@ -31,8 +31,8 @@ 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/cloud/consul-ecs-hcp?in=consul/cloud-integrations&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?in=consul/cloud-integrations&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 [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. From 1b565444beb0685b191d78cd31ba5450feb3c430 Mon Sep 17 00:00:00 2001 From: Eric Haberkorn Date: Tue, 4 Oct 2022 14:46:15 -0400 Subject: [PATCH 128/172] Rename `PeerName` to `Peer` on prepared queries and exported services (#14854) --- .changelog/14854.txt | 3 ++ agent/consul/internal_endpoint_test.go | 14 +++--- agent/consul/leader_peering_test.go | 14 +++--- agent/consul/prepared_query/walk_test.go | 2 +- agent/consul/prepared_query_endpoint.go | 12 ++--- agent/consul/prepared_query_endpoint_test.go | 16 +++---- agent/consul/state/config_entry_oss_test.go | 12 ++--- agent/consul/state/config_entry_test.go | 10 ++--- agent/consul/state/peering.go | 8 ++-- agent/consul/state/peering_test.go | 30 ++++++------- .../services/peerstream/stream_test.go | 6 +-- .../peerstream/subscription_manager_test.go | 12 ++--- .../exported_peered_services_test.go | 12 ++--- agent/proxycfg-glue/trust_bundle_test.go | 4 +- agent/rpc/peering/service_test.go | 8 ++-- 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_test.go | 4 +- agent/structs/prepared_query.go | 8 ++-- agent/ui_endpoint_test.go | 2 +- api/config_entry_exports.go | 4 +- api/config_entry_exports_test.go | 2 +- api/prepared_query.go | 4 +- command/helpers/helpers_test.go | 10 ++--- .../alpha/config_entries.hcl | 2 +- .../alpha/config_entries.hcl | 2 +- .../alpha/config_entries.hcl | 2 +- .../alpha/config_entries.hcl | 2 +- .../case-cross-peers/alpha/config_entries.hcl | 2 +- website/content/api-docs/query.mdx | 2 +- .../config-entries/exported-services.mdx | 44 +++++++++---------- 32 files changed, 141 insertions(+), 138 deletions(-) create mode 100644 .changelog/14854.txt 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/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 35d6edc108..7df1a4b065 100644 --- a/agent/consul/leader_peering_test.go +++ b/agent/consul/leader_peering_test.go @@ -257,7 +257,7 @@ func TestLeader_PeeringSync_Lifecycle_UnexportWhileDown(t *testing.T) { Services: []structs.ExportedService{ { Name: "foo", - Consumers: []structs.ServiceConsumer{{PeerName: "my-peer-dialer"}}, + Consumers: []structs.ServiceConsumer{{Peer: "my-peer-dialer"}}, }, }, }, @@ -1014,7 +1014,7 @@ func TestLeader_Peering_ImportedExportedServicesCount(t *testing.T) { Name: structs.WildcardSpecifier, Consumers: []structs.ServiceConsumer{ { - PeerName: "my-peer-s2", + Peer: "my-peer-s2", }, }, }, @@ -1042,7 +1042,7 @@ func TestLeader_Peering_ImportedExportedServicesCount(t *testing.T) { Name: "a-service", Consumers: []structs.ServiceConsumer{ { - PeerName: "my-peer-s2", + Peer: "my-peer-s2", }, }, }, @@ -1050,7 +1050,7 @@ func TestLeader_Peering_ImportedExportedServicesCount(t *testing.T) { Name: "b-service", Consumers: []structs.ServiceConsumer{ { - PeerName: "my-peer-s2", + Peer: "my-peer-s2", }, }, }, @@ -1069,7 +1069,7 @@ func TestLeader_Peering_ImportedExportedServicesCount(t *testing.T) { Name: "a-service", Consumers: []structs.ServiceConsumer{ { - PeerName: "my-peer-s2", + Peer: "my-peer-s2", }, }, }, @@ -1088,7 +1088,7 @@ func TestLeader_Peering_ImportedExportedServicesCount(t *testing.T) { Name: "a-service", Consumers: []structs.ServiceConsumer{ { - PeerName: "my-peer-s2", + Peer: "my-peer-s2", }, }, }, @@ -1096,7 +1096,7 @@ func TestLeader_Peering_ImportedExportedServicesCount(t *testing.T) { Name: "c-service", Consumers: []structs.ServiceConsumer{ { - PeerName: "my-peer-s2", + Peer: "my-peer-s2", }, }, }, 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 de45f0819e..07e9801a68 100644 --- a/agent/consul/prepared_query_endpoint_test.go +++ b/agent/consul/prepared_query_endpoint_test.go @@ -88,7 +88,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 +97,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) @@ -1552,7 +1552,7 @@ func TestPreparedQuery_Execute(t *testing.T) { Services: []structs.ExportedService{ { Name: "foo", - Consumers: []structs.ServiceConsumer{{PeerName: dialingPeerName}}, + Consumers: []structs.ServiceConsumer{{Peer: dialingPeerName}}, }, }, }, @@ -2429,7 +2429,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)) @@ -2950,7 +2950,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)) @@ -3302,15 +3302,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/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 ce7b80c39c..80c23efcd8 100644 --- a/agent/consul/state/peering.go +++ b/agent/consul/state/peering.go @@ -794,7 +794,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 @@ -940,7 +940,7 @@ func listServicesExportedToAnyPeerByConfigEntry( sawPeer := false for _, consumer := range svc.Consumers { - if consumer.PeerName == "" { + if consumer.Peer == "" { continue } sawPeer = true @@ -1312,8 +1312,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/grpc-external/services/peerstream/stream_test.go b/agent/grpc-external/services/peerstream/stream_test.go index 93a0efebb3..156efa4759 100644 --- a/agent/grpc-external/services/peerstream/stream_test.go +++ b/agent/grpc-external/services/peerstream/stream_test.go @@ -864,14 +864,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"}, }, }, }, @@ -1035,7 +1035,7 @@ func TestStreamResources_Server_ServiceUpdates(t *testing.T) { Name: "mongo", Consumers: []structs.ServiceConsumer{ { - PeerName: "my-peering", + Peer: "my-peering", }, }, }, diff --git a/agent/grpc-external/services/peerstream/subscription_manager_test.go b/agent/grpc-external/services/peerstream/subscription_manager_test.go index 065551f9db..615d72030e 100644 --- a/agent/grpc-external/services/peerstream/subscription_manager_test.go +++ b/agent/grpc-external/services/peerstream/subscription_manager_test.go @@ -80,13 +80,13 @@ 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"}, }, }, }, @@ -429,7 +429,7 @@ func TestSubscriptionManager_RegisterDeregister(t *testing.T) { { Name: "mongo", Consumers: []structs.ServiceConsumer{ - {PeerName: "my-other-peering"}, + {Peer: "my-other-peering"}, }, }, }, @@ -506,19 +506,19 @@ 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"}, }, }, }, 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/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/rpc/peering/service_test.go b/agent/rpc/peering/service_test.go index c25f88614b..3d04981f4f 100644 --- a/agent/rpc/peering/service_test.go +++ b/agent/rpc/peering/service_test.go @@ -1059,10 +1059,10 @@ func TestPeeringService_TrustBundleListByService(t *testing.T) { Name: "api", Consumers: []structs.ServiceConsumer{ { - PeerName: "foo", + Peer: "foo", }, { - PeerName: "bar", + Peer: "bar", }, }, }, @@ -1070,7 +1070,7 @@ func TestPeeringService_TrustBundleListByService(t *testing.T) { Name: "web", Consumers: []structs.ServiceConsumer{ { - PeerName: "baz", + Peer: "baz", }, }, }, @@ -1264,7 +1264,7 @@ func TestPeeringService_TrustBundleListByService_ACLEnforcement(t *testing.T) { Name: "api", Consumers: []structs.ServiceConsumer{ { - PeerName: "foo", + Peer: "foo", }, }, }, 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_test.go b/agent/structs/config_entry_test.go index 6aca9af4ea..887f1d68fd 100644 --- a/agent/structs/config_entry_test.go +++ b/agent/structs/config_entry_test.go @@ -1951,7 +1951,7 @@ func TestDecodeConfigEntry(t *testing.T) { Partition = "baz" }, { - PeerName = "flarm" + Peer = "flarm" } ] }, @@ -1984,7 +1984,7 @@ func TestDecodeConfigEntry(t *testing.T) { Partition: "baz", }, { - PeerName: "flarm", + Peer: "flarm", }, }, }, 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/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/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/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/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/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-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/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/connect/config-entries/exported-services.mdx b/website/content/docs/connect/config-entries/exported-services.mdx index 0ed093f2de..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" } ] } From 678adb315424738fe4c99676ae0bafd8a2065c0a Mon Sep 17 00:00:00 2001 From: Derek Menteer <105233703+hashi-derek@users.noreply.github.com> Date: Tue, 4 Oct 2022 13:51:04 -0500 Subject: [PATCH 129/172] Add peering integration tests (#14836) Add peering integration tests. --- .../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/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 + test/integration/connect/envoy/helpers.bash | 20 +++++ 49 files changed, 843 insertions(+) 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 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-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/helpers.bash b/test/integration/connect/envoy/helpers.bash index a5d0320538..760eaea1a8 100755 --- a/test/integration/connect/envoy/helpers.bash +++ b/test/integration/connect/envoy/helpers.bash @@ -997,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 From f64ff29d43eadceb00eaad18826ad0c68ed700f9 Mon Sep 17 00:00:00 2001 From: Kyle Schochenmaier Date: Tue, 4 Oct 2022 14:59:53 -0500 Subject: [PATCH 130/172] update helm docs for consul-k8s 1.0.0-beta1 (#14875) --- website/content/docs/k8s/helm.mdx | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/website/content/docs/k8s/helm.mdx b/website/content/docs/k8s/helm.mdx index 54e116f6fd..9b63b721fa 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 @@ -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. @@ -1958,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. @@ -2014,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 @@ -2116,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. @@ -2209,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. From 23e52a10400032bec781b2ea46623ebddb9663e1 Mon Sep 17 00:00:00 2001 From: Paul Glass Date: Tue, 4 Oct 2022 15:02:28 -0500 Subject: [PATCH 131/172] docs: Consul Dataplane Version Compatibility (#14710) Co-authored-by: David Yu Co-authored-by: Jeff Boruszak <104028618+boruszak@users.noreply.github.com> --- website/content/docs/connect/proxies/envoy.mdx | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/website/content/docs/connect/proxies/envoy.mdx b/website/content/docs/connect/proxies/envoy.mdx index 7c745fc097..51db30ff97 100644 --- a/website/content/docs/connect/proxies/envoy.mdx +++ b/website/content/docs/connect/proxies/envoy.mdx @@ -33,6 +33,8 @@ 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 | @@ -44,6 +46,16 @@ Consul supports **four major Envoy releases** at the beginning of each major Con 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 @@ -379,7 +391,7 @@ 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 @@ -839,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. From a3be5a5a825c93f996138da21340f123a709f259 Mon Sep 17 00:00:00 2001 From: Evan Culver Date: Tue, 4 Oct 2022 13:15:01 -0700 Subject: [PATCH 132/172] connect: Bump Envoy 1.20 to 1.20.7, 1.21 to 1.21.5 and 1.22 to 1.22.5 (#14831) --- .changelog/14831.txt | 3 +++ .circleci/config.yml | 6 +++--- agent/xds/envoy_versioning_test.go | 6 +++--- agent/xds/proxysupport/proxysupport.go | 6 +++--- website/content/docs/connect/proxies/envoy.mdx | 6 +++--- 5 files changed, 15 insertions(+), 12 deletions(-) create mode 100644 .changelog/14831.txt 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/.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/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/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/website/content/docs/connect/proxies/envoy.mdx b/website/content/docs/connect/proxies/envoy.mdx index 51db30ff97..88f996f124 100644 --- a/website/content/docs/connect/proxies/envoy.mdx +++ b/website/content/docs/connect/proxies/envoy.mdx @@ -39,9 +39,9 @@ Consul supports **four major Envoy releases** at the beginning of each major Con | 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. From 710e010594ece935023f31307f66fd30d37201b3 Mon Sep 17 00:00:00 2001 From: Nathan Coleman Date: Tue, 4 Oct 2022 17:34:32 -0400 Subject: [PATCH 134/172] Update documentation link to improve readability --- website/content/docs/consul-vs-other/api-gateway-compare.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/content/docs/consul-vs-other/api-gateway-compare.mdx b/website/content/docs/consul-vs-other/api-gateway-compare.mdx index f53f45ba72..ef010cd646 100644 --- a/website/content/docs/consul-vs-other/api-gateway-compare.mdx +++ b/website/content/docs/consul-vs-other/api-gateway-compare.mdx @@ -9,7 +9,7 @@ description: >- **Examples**: Kong Gateway, Apigee, Mulesoft, Gravitee -The [Consul API Gateway documentation](/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_. +The Consul API Gateway ([documentation](/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). From e22d57524032b5aa5b5d2db148f14a67fdb085a9 Mon Sep 17 00:00:00 2001 From: Nathan Coleman Date: Tue, 4 Oct 2022 17:35:58 -0400 Subject: [PATCH 135/172] Use consistent casing for "Consul API Gateway" vs. "API gateway" --- website/content/docs/consul-vs-other/api-gateway-compare.mdx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/website/content/docs/consul-vs-other/api-gateway-compare.mdx b/website/content/docs/consul-vs-other/api-gateway-compare.mdx index ef010cd646..cc0eb6fcaf 100644 --- a/website/content/docs/consul-vs-other/api-gateway-compare.mdx +++ b/website/content/docs/consul-vs-other/api-gateway-compare.mdx @@ -9,8 +9,8 @@ description: >- **Examples**: Kong Gateway, Apigee, Mulesoft, Gravitee -The Consul API Gateway ([documentation](/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_. +The Consul API Gateway ([documentation](/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. +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. From 86722af89f550c9e0a774222f3f440055e2270a3 Mon Sep 17 00:00:00 2001 From: Nathan Coleman Date: Tue, 4 Oct 2022 18:05:03 -0400 Subject: [PATCH 136/172] Update website/content/docs/consul-vs-other/api-gateway-compare.mdx Co-authored-by: Jeff Boruszak <104028618+boruszak@users.noreply.github.com> --- website/content/docs/consul-vs-other/api-gateway-compare.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/content/docs/consul-vs-other/api-gateway-compare.mdx b/website/content/docs/consul-vs-other/api-gateway-compare.mdx index cc0eb6fcaf..57eccde06d 100644 --- a/website/content/docs/consul-vs-other/api-gateway-compare.mdx +++ b/website/content/docs/consul-vs-other/api-gateway-compare.mdx @@ -9,7 +9,7 @@ description: >- **Examples**: Kong Gateway, Apigee, Mulesoft, Gravitee -The Consul API Gateway ([documentation](/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_. +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). From 90db6f4fd04c93776bcfbb168271af0d594b78c3 Mon Sep 17 00:00:00 2001 From: Nathan Coleman Date: Tue, 4 Oct 2022 19:41:16 -0400 Subject: [PATCH 137/172] Update website/content/docs/consul-vs-other/api-gateway-compare.mdx Co-authored-by: trujillo-adam <47586768+trujillo-adam@users.noreply.github.com> --- website/content/docs/consul-vs-other/api-gateway-compare.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/content/docs/consul-vs-other/api-gateway-compare.mdx b/website/content/docs/consul-vs-other/api-gateway-compare.mdx index 57eccde06d..126f027ff6 100644 --- a/website/content/docs/consul-vs-other/api-gateway-compare.mdx +++ b/website/content/docs/consul-vs-other/api-gateway-compare.mdx @@ -9,7 +9,7 @@ description: >- **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_. +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). From 79a541fd7d407e0b87aed6681b391e453f650012 Mon Sep 17 00:00:00 2001 From: John Murret Date: Tue, 4 Oct 2022 17:51:37 -0600 Subject: [PATCH 138/172] Upgrade serf to v0.10.1 and memberlist to v0.5.0 to get memberlist size metrics and broadcast queue depth metric (#14873) * updating to serf v0.10.1 and memberlist v0.5.0 to get memberlist size metrics and memberlist broadcast queue depth metric * update changelog * update changelog * correcting changelog * adding "QueueCheckInterval" for memberlist to test * updating integration test containers to grab latest api --- .changelog/14873.txt | 3 +++ agent/consul/config_test.go | 1 + api/go.mod | 3 +-- api/go.sum | 12 ++++++------ go.mod | 4 ++-- go.sum | 11 ++++------- test/integration/consul-container/go.mod | 6 +++--- test/integration/consul-container/go.sum | 25 ++++++++++++++++-------- 8 files changed, 37 insertions(+), 28 deletions(-) create mode 100644 .changelog/14873.txt 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/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/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/go.mod b/go.mod index 5459d09f33..3bb513fa0f 100644 --- a/go.mod +++ b/go.mod @@ -52,11 +52,11 @@ require ( 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-20211028200310-0bc27b27de87 diff --git a/go.sum b/go.sum index eef538621b..997d179ec8 100644 --- a/go.sum +++ b/go.sum @@ -511,10 +511,8 @@ github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO 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= @@ -530,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= 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= From 6fbe799178157973d5d8e37250010ac67807cc09 Mon Sep 17 00:00:00 2001 From: Michael Klein Date: Wed, 5 Oct 2022 11:48:03 +0200 Subject: [PATCH 140/172] Allow managed-runtime badge to be dynamic (#14853) --- .../consul/datacenter/selector/index.hbs | 81 +++++++++---------- 1 file changed, 39 insertions(+), 42 deletions(-) diff --git a/ui/packages/consul-ui/app/components/consul/datacenter/selector/index.hbs b/ui/packages/consul-ui/app/components/consul/datacenter/selector/index.hbs index 72b44e3a9b..16bc038f65 100644 --- a/ui/packages/consul-ui/app/components/consul/datacenter/selector/index.hbs +++ b/ui/packages/consul-ui/app/components/consul/datacenter/selector/index.hbs @@ -1,67 +1,64 @@ -
  • +
  • {{#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 From 995671ff6f49d6aa76daeb16c3eac37b5f9631a8 Mon Sep 17 00:00:00 2001 From: cskh Date: Wed, 5 Oct 2022 10:04:08 -0400 Subject: [PATCH 141/172] fix(api): missing peer name in query option (#14835) --- api/api.go | 6 ++++++ api/api_test.go | 4 ++++ 2 files changed, 10 insertions(+) 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..3574240d1a 100644 --- a/api/api_test.go +++ b/api/api_test.go @@ -763,6 +763,7 @@ func TestAPI_SetQueryOptions(t *testing.T) { Namespace: "operator", Partition: "asdf", Datacenter: "foo", + Peer: "dc10", AllowStale: true, RequireConsistent: true, WaitIndex: 1000, @@ -779,6 +780,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) } From f650aa0044554e7d2de3db3917dcef0cac9a2baf Mon Sep 17 00:00:00 2001 From: Tu Nguyen Date: Wed, 5 Oct 2022 09:54:49 -0700 Subject: [PATCH 142/172] fix broken links (#14892) --- .../docs/k8s/deployment-configurations/vault/wan-federation.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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. From 13da2c5fad69ad47b67adbdd31d71f123b8bbdea Mon Sep 17 00:00:00 2001 From: Alex Oskotsky Date: Wed, 5 Oct 2022 13:06:44 -0400 Subject: [PATCH 143/172] Add the ability to retry on reset connection to service-routers (#12890) --- agent/proxycfg/testing_upstreams.go | 15 ++++- agent/structs/config_entry_discoverychain.go | 32 ++++++++++- .../config_entry_discoverychain_test.go | 56 ++++++++++++++++++ agent/xds/routes.go | 57 ++++++++++++------- ...-proxy-with-chain-and-router.latest.golden | 18 +++++- ...hain-and-router-header-manip.latest.golden | 18 +++++- ...ngress-with-chain-and-router.latest.golden | 18 +++++- api/config_entry_discoverychain.go | 1 + api/config_entry_discoverychain_test.go | 12 ++++ .../config_entries.hcl | 1 + .../connect/config-entries/service-router.mdx | 18 +++++- 11 files changed, 212 insertions(+), 34 deletions(-) diff --git a/agent/proxycfg/testing_upstreams.go b/agent/proxycfg/testing_upstreams.go index afb310c754..25679c6929 100644 --- a/agent/proxycfg/testing_upstreams.go +++ b/agent/proxycfg/testing_upstreams.go @@ -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/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/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/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/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/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/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', From 2811925417c8d3939d95777855f090d9c2a6d899 Mon Sep 17 00:00:00 2001 From: Matt Keeler Date: Wed, 5 Oct 2022 13:35:07 -0400 Subject: [PATCH 144/172] Add changelog entry for #12890 --- .changelog/12890.txt | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .changelog/12890.txt 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. +``` From 3638dc13fb6b9001bbe687faab42b895491f5bbc Mon Sep 17 00:00:00 2001 From: Tyler Wendlandt Date: Wed, 5 Oct 2022 13:21:34 -0600 Subject: [PATCH 145/172] ui: Wrap service names on show and instance routes (#14771) * Wrap service names on show and instance routes Moves the trailing type/kind/actions to the second row of the header no matter what length the service name is. Wraps service name text. * Change grid format of AppView globally * Add tooltips to the last element of breadcrumbs --- .../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-ui/app/templates/dc/kv/edit.hbs | 3 +- .../consul-ui/app/templates/dc/kv/index.hbs | 4 +-- .../app/templates/dc/services/instance.hbs | 6 ++-- 8 files changed, 54 insertions(+), 23 deletions(-) diff --git a/ui/packages/consul-ui/app/components/app-view/index.hbs b/ui/packages/consul-ui/app/components/app-view/index.hbs index c40e715eba..7ce4444549 100644 --- a/ui/packages/consul-ui/app/components/app-view/index.hbs +++ b/ui/packages/consul-ui/app/components/app-view/index.hbs @@ -13,14 +13,16 @@
    - - {{yield}} - -
    - - +
    + {{yield}} - + +
    +
    + + + {{yield}} +
    diff --git a/ui/packages/consul-ui/app/components/app-view/index.scss b/ui/packages/consul-ui/app/components/app-view/index.scss index 6d1d3059c6..430d1de0bb 100644 --- a/ui/packages/consul-ui/app/components/app-view/index.scss +++ b/ui/packages/consul-ui/app/components/app-view/index.scss @@ -10,6 +10,9 @@ %app-view-header .title { @extend %app-view-title; } +%app-view-title .title-left-container { + @extend %app-view-title-left-container; +} %app-view-header .actions { @extend %app-view-actions; } diff --git a/ui/packages/consul-ui/app/components/app-view/layout.scss b/ui/packages/consul-ui/app/components/app-view/layout.scss index f08c050969..76e7e4f230 100644 --- a/ui/packages/consul-ui/app/components/app-view/layout.scss +++ b/ui/packages/consul-ui/app/components/app-view/layout.scss @@ -1,25 +1,38 @@ /* layout */ %app-view-title { - display: flex; - align-items: center; - white-space: nowrap; + display: grid; + grid-template-columns: 1fr auto; + grid-template-areas: "title actions"; position: relative; z-index: 5; } +%app-view-title-left-container { + grid-area: title; + display: flex; + flex-wrap: wrap; + align-items: center; + white-space: normal; +} +%app-view-title-left-container > :first-child { + flex-basis: 100%; +} + +%app-view-title-left-container > :not(:first-child) { + margin-right: 8px; +} + %app-view-actions { + grid-area: actions; + align-self: end; display: flex; align-items: flex-start; margin-left: auto; + margin-top: 9px; } + /* units */ %app-view-title { - padding-bottom: 0.2em; -} -%app-view-title > :not(:last-child) { - margin-right: 8px; -} -%app-view-actions { - margin-top: 9px; + padding-bottom: 1.4em; } /* content */ diff --git a/ui/packages/consul-ui/app/components/app-view/skin.scss b/ui/packages/consul-ui/app/components/app-view/skin.scss index d9e85982e2..d9c4c3d451 100644 --- a/ui/packages/consul-ui/app/components/app-view/skin.scss +++ b/ui/packages/consul-ui/app/components/app-view/skin.scss @@ -1,4 +1,4 @@ -%app-view-title > *:first-child { +%app-view-title-left-container > *:first-child { @extend %h100; } %app-view-title { diff --git a/ui/packages/consul-ui/app/components/breadcrumbs/layout.scss b/ui/packages/consul-ui/app/components/breadcrumbs/layout.scss index d24dd0b748..6e17d0cec2 100644 --- a/ui/packages/consul-ui/app/components/breadcrumbs/layout.scss +++ b/ui/packages/consul-ui/app/components/breadcrumbs/layout.scss @@ -1,6 +1,14 @@ +%breadcrumbs { + display: grid; + grid-auto-flow: column; + white-space: nowrap; + overflow: hidden; +} + %breadcrumbs > li { list-style-type: none; display: inline-flex; + overflow: hidden } %breadcrumb-milestone::before { margin-right: 4px; @@ -10,6 +18,8 @@ /* as the separator is a '/' the left margin needs */ /* to be different from the right in order to center it */ margin-left: 6px; + overflow: hidden; + text-overflow: ellipsis; } %breadcrumb::before { margin-right: 8px; 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..f6e8ed120c 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}}
    @@ -207,4 +207,4 @@ as |sort filters parent items|}} {{/let}} - \ No newline at end of file + 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..a2ddf18d6b 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}}) +
    @@ -192,4 +194,4 @@ as |item|}} {{/let}} - \ No newline at end of file + From a279d2d3292431c5f2314194abe09bd8c6c51c41 Mon Sep 17 00:00:00 2001 From: Derek Menteer <105233703+hashi-derek@users.noreply.github.com> Date: Wed, 5 Oct 2022 14:38:25 -0500 Subject: [PATCH 146/172] Fix explicit tproxy listeners with discovery chains. (#14751) Fix explicit tproxy listeners with discovery chains. --- .changelog/14751.txt | 3 + agent/proxycfg/connect_proxy.go | 17 +- agent/proxycfg/testing_tproxy.go | 72 ++++++- agent/xds/listeners_test.go | 4 + ...h-resolver-redirect-upstream.latest.golden | 176 ++++++++++++++++++ 5 files changed, 260 insertions(+), 12 deletions(-) create mode 100644 .changelog/14751.txt create mode 100644 agent/xds/testdata/listeners/transparent-proxy-with-resolver-redirect-upstream.latest.golden 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/agent/proxycfg/connect_proxy.go b/agent/proxycfg/connect_proxy.go index dfa6d0b032..69764843d6 100644 --- a/agent/proxycfg/connect_proxy.go +++ b/agent/proxycfg/connect_proxy.go @@ -156,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() @@ -499,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 { @@ -516,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 { @@ -524,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 { @@ -535,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 { @@ -543,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 { @@ -567,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/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/xds/listeners_test.go b/agent/xds/listeners_test.go index 39ac2eac08..ab67cc6933 100644 --- a/agent/xds/listeners_test.go +++ b/agent/xds/listeners_test.go @@ -776,6 +776,10 @@ func TestListenersFromSnapshot(t *testing.T) { 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, 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..3d0826b75c --- /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", + "cluster": "db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul", + "statPrefix": "upstream.db-redir.default.default.dc1" + } + } + ] + } + ], + "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 From c1b5f34fb732c36bcd79649bb927d89c3ed97fdc Mon Sep 17 00:00:00 2001 From: cskh Date: Wed, 5 Oct 2022 15:57:21 -0400 Subject: [PATCH 147/172] fix: missing UDP field in checkType (#14885) * fix: missing UDP field in checkType * Add changelog * Update doc --- .changelog/14885.txt | 4 ++++ agent/config/builder.go | 1 + agent/config/builder_test.go | 18 ++++++++++++++++++ docs/service-discovery/health-checks.md | 5 +++-- 4 files changed, 26 insertions(+), 2 deletions(-) create mode 100644 .changelog/14885.txt 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/agent/config/builder.go b/agent/config/builder.go index 1650df0ca8..d6b045c93d 100644 --- a/agent/config/builder.go +++ b/agent/config/builder.go @@ -1562,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), 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/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 From 1633cf20eab36babd972694b2e956baf47777141 Mon Sep 17 00:00:00 2001 From: Eric Haberkorn Date: Thu, 6 Oct 2022 09:54:14 -0400 Subject: [PATCH 148/172] Make the mesh gateway changes to allow `local` mode for cluster peering data plane traffic (#14817) Make the mesh gateway changes to allow `local` mode for cluster peering data plane traffic --- .changelog/14817.txt | 3 + agent/proxycfg/mesh_gateway.go | 132 ++++++++++++++++++ agent/proxycfg/snapshot.go | 18 +++ agent/proxycfg/state.go | 1 + agent/proxycfg/state_test.go | 47 ++++++- agent/proxycfg/testing.go | 25 ---- agent/proxycfg/testing_mesh_gateway.go | 42 ++++++ agent/proxycfg/testing_upstreams.go | 4 +- agent/structs/testing_catalog.go | 39 ++++-- agent/xds/clusters.go | 63 +++++++++ agent/xds/endpoints.go | 42 ++++++ agent/xds/resources_test.go | 6 + ...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 | 2 +- ...ults-service-max-connections.latest.golden | 2 +- ...with-service-max-connections.latest.golden | 2 +- ...ith-imported-peered-services.latest.golden | 64 +++++++++ ...and-failover-to-cluster-peer.latest.golden | 12 -- ...and-redirect-to-cluster-peer.latest.golden | 12 -- ...and-failover-to-cluster-peer.latest.golden | 12 -- ...ith-imported-peered-services.latest.golden | 41 ++++++ ...-balance-inbound-connections.latest.golden | 4 +- ...tbound-connections-bind-port.latest.golden | 4 +- ...ith-imported-peered-services.latest.golden | 45 ++++++ ...ith-imported-peered-services.latest.golden | 5 + .../case-cross-peers/primary/service_s1.hcl | 3 + .../case-cross-peers/primary/verify.bats | 4 + 29 files changed, 562 insertions(+), 84 deletions(-) create mode 100644 .changelog/14817.txt create mode 100644 agent/xds/testdata/clusters/mesh-gateway-with-imported-peered-services.latest.golden create mode 100644 agent/xds/testdata/endpoints/mesh-gateway-with-imported-peered-services.latest.golden create mode 100644 agent/xds/testdata/listeners/mesh-gateway-with-imported-peered-services.latest.golden create mode 100644 agent/xds/testdata/routes/mesh-gateway-with-imported-peered-services.latest.golden 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/agent/proxycfg/mesh_gateway.go b/agent/proxycfg/mesh_gateway.go index bd9f140423..9b134c3e60 100644 --- a/agent/proxycfg/mesh_gateway.go +++ b/agent/proxycfg/mesh_gateway.go @@ -120,6 +120,9 @@ 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 @@ -460,6 +463,52 @@ 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 { @@ -517,6 +566,57 @@ func (s *handlerMeshGateway) handleUpdate(ctx context.Context, u UpdateEvent, sn 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 { @@ -530,6 +630,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) diff --git a/agent/proxycfg/snapshot.go b/agent/proxycfg/snapshot.go index 4a0bbc1b70..a52d568cce 100644 --- a/agent/proxycfg/snapshot.go +++ b/agent/proxycfg/snapshot.go @@ -348,6 +348,11 @@ func (c *configSnapshotTerminatingGateway) isEmpty() bool { !c.MeshConfigSet } +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 @@ -373,6 +378,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 diff --git a/agent/proxycfg/state.go b/agent/proxycfg/state.go index e069e80ee2..52df574289 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" diff --git a/agent/proxycfg/state_test.go b/agent/proxycfg/state_test.go index 3add369a8e..7e5675e970 100644 --- a/agent/proxycfg/state_test.go +++ b/agent/proxycfg/state_test.go @@ -830,6 +830,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) }, }, { @@ -897,8 +900,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"}, + }, }, }, }, @@ -906,6 +923,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) }, }, { @@ -920,11 +941,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) }, }, { diff --git a/agent/proxycfg/testing.go b/agent/proxycfg/testing.go index d5a3d82244..6382deb66b 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{ diff --git a/agent/proxycfg/testing_mesh_gateway.go b/agent/proxycfg/testing_mesh_gateway.go index d5868d58e6..49205fe149 100644 --- a/agent/proxycfg/testing_mesh_gateway.go +++ b/agent/proxycfg/testing_mesh_gateway.go @@ -632,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{ diff --git a/agent/proxycfg/testing_upstreams.go b/agent/proxycfg/testing_upstreams.go index 25679c6929..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": 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/xds/clusters.go b/agent/xds/clusters.go index 6e34ce2e13..4604c69e82 100644 --- a/agent/xds/clusters.go +++ b/agent/xds/clusters.go @@ -418,6 +418,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 { @@ -559,6 +566,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 diff --git a/agent/xds/endpoints.go b/agent/xds/endpoints.go index c92a785267..6a6f8c844c 100644 --- a/agent/xds/endpoints.go +++ b/agent/xds/endpoints.go @@ -342,6 +342,13 @@ 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...) + return resources, nil } @@ -398,6 +405,41 @@ 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) endpointsFromSnapshotIngressGateway(cfgSnap *proxycfg.ConfigSnapshot) ([]proto.Message, error) { var resources []proto.Message createdClusters := make(map[proxycfg.UpstreamID]bool) diff --git a/agent/xds/resources_test.go b/agent/xds/resources_test.go index 6666b64548..f7c6e9d75d 100644 --- a/agent/xds/resources_test.go +++ b/agent/xds/resources_test.go @@ -225,6 +225,12 @@ func getMeshGatewayPeeringGoldenTestCases() []goldenTestCase { 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) + }, + }, } } 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 index 08d2c471e5..24bb5835d7 100644 --- 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 @@ -16,7 +16,7 @@ }, "connectTimeout": "33s", "circuitBreakers": { - "thresholds":[ + "thresholds": [ { "maxConnections": 2048, "maxPendingRequests": 512, 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 index 61101551cd..2ea7759503 100644 --- 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 @@ -16,7 +16,7 @@ }, "connectTimeout": "33s", "circuitBreakers": { - "thresholds":[ + "thresholds": [ { "maxConnections": 4096, "maxPendingRequests": 2048 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 index 6ca8d60c6a..3ac392d401 100644 --- a/agent/xds/testdata/clusters/ingress-with-service-max-connections.latest.golden +++ b/agent/xds/testdata/clusters/ingress-with-service-max-connections.latest.golden @@ -16,7 +16,7 @@ }, "connectTimeout": "33s", "circuitBreakers": { - "thresholds":[ + "thresholds": [ { "maxConnections": 4096 } 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/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/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/listeners/listener-balance-inbound-connections.latest.golden b/agent/xds/testdata/listeners/listener-balance-inbound-connections.latest.golden index 9c8b0a5817..c041226404 100644 --- a/agent/xds/testdata/listeners/listener-balance-inbound-connections.latest.golden +++ b/agent/xds/testdata/listeners/listener-balance-inbound-connections.latest.golden @@ -113,7 +113,9 @@ ], "trafficDirection": "INBOUND", "connectionBalanceConfig": { - "exactBalance": {} + "exactBalance": { + + } } } ], 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 index 1181ff019d..889873f554 100644 --- 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 @@ -26,7 +26,9 @@ ], "trafficDirection": "OUTBOUND", "connectionBalanceConfig": { - "exactBalance": {} + "exactBalance": { + + } } }, { 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/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/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 +} From 62a66a32d7baf68aa7100307a2186e06eb9d56ff Mon Sep 17 00:00:00 2001 From: Michael Klein Date: Thu, 6 Oct 2022 17:17:20 +0200 Subject: [PATCH 149/172] ui: Setup Hashicorp Design System for usage in consul-ui (#14394) * Use postcss instead of ember-cli-sass This will make it possible to work with tailwindcss. * configure postcss to compile sass * add "sub-app" css into app/styles tree * pin node@14 via volta Only used by people that use volta * Install tailwind and autoprefixer * Create tailwind config * Use tailwind via postcss * Fix: tailwind changes current styling When adding tailwind to the bottom of app.scss we apparently change the way the application looks. We will import it first to make sure we don't change the current styling of the application right now. * Automatic import of HDS colors in tailwind * Install @hashicorp/design-system-components * install add-on * setup postcss scss pipeline to include tokens css * import add-on css * Install ember-auto-import v2 HDS depends on v2 of ember-auto-import so we need to upgrade. * Upgrade ember-cli-yadda v0.6.0 of ember-cli-yadda adds configuration for webpack. This configuration is incompatible with webpack v5 which ember-auto-import v2 is using. We need to upgrade ember-cli-yadda to the latest version that fixes this incompatability with auto-import v2 * Install ember-flight-icons HDS components are using the addon internally. * Document HDS usage in engineering docs * Upgrade ember-cli-api-double * fix new linting errors --- ui/packages/consul-ui/.eslintrc.js | 1 + ui/packages/consul-ui/app/styles/app.scss | 6 + .../consul-ui/app/styles/tailwind.scss | 3 + ui/packages/consul-ui/docs/hds.mdx | 102 + ui/packages/consul-ui/ember-cli-build.js | 47 + ui/packages/consul-ui/package.json | 21 +- ui/packages/consul-ui/tailwind.config.js | 45 + ui/yarn.lock | 1747 ++++++++++++++++- 8 files changed, 1882 insertions(+), 90 deletions(-) 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 diff --git a/ui/packages/consul-ui/.eslintrc.js b/ui/packages/consul-ui/.eslintrc.js index a8103eb0dd..4875b0b2f2 100644 --- a/ui/packages/consul-ui/.eslintrc.js +++ b/ui/packages/consul-ui/.eslintrc.js @@ -45,6 +45,7 @@ module.exports = { { files: [ '.eslintrc.js', + 'tailwind.config.js', '.docfy-config.js', '.prettierrc.js', '.template-lintrc.js', 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/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/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/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,6 +5738,19 @@ 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.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" + broccoli-plugin "^4.0.7" + debug "^4.1.1" + fs-tree-diff "^2.0.1" + heimdalljs "^0.2.0" + minimatch "^3.0.0" + walk-sync "^2.0.2" + 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" @@ -4861,19 +5767,6 @@ broccoli-funnel@^3.0.1, broccoli-funnel@^3.0.2, broccoli-funnel@^3.0.3: 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== - dependencies: - array-equal "^1.0.0" - broccoli-plugin "^4.0.7" - debug "^4.1.1" - fs-tree-diff "^2.0.1" - heimdalljs "^0.2.0" - minimatch "^3.0.0" - walk-sync "^2.0.2" - broccoli-kitchen-sink-helpers@^0.2.5: version "0.2.9" resolved "https://registry.yarnpkg.com/broccoli-kitchen-sink-helpers/-/broccoli-kitchen-sink-helpers-0.2.9.tgz#a5e0986ed8d76fb5984b68c3f0450d3a96e36ecc" @@ -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" @@ -6780,6 +7790,42 @@ ember-auto-import@^1.5.2, ember-auto-import@^1.5.3, ember-auto-import@^1.6.0: 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 "^3.0.0" + ember-basic-dropdown@3.0.21, ember-basic-dropdown@^3.0.16: version "3.0.21" resolved "https://registry.yarnpkg.com/ember-basic-dropdown/-/ember-basic-dropdown-3.0.21.tgz#5711d071966919c9578d2d5ac2c6dcadbb5ea0e0" @@ -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== From f0be55df86e5cf9e5e5e633c2e3081cdbb439af6 Mon Sep 17 00:00:00 2001 From: Tyler Wendlandt Date: Thu, 6 Oct 2022 11:01:49 -0600 Subject: [PATCH 150/172] ui: Update empty-state copy throughout app (#14721) * Update empty-state copy throughout app Update empty-states throughout the app to only include mentions of ACLs if the user has ACLs enabled. * Update peers empty state copy Flip the empty state copy logic for peers. Small typo fixes on other empty states. * Update Node empty state with docs * Update intentions empty state Make ACL copy dependent on if acls are enabled. * Update Nodes empty state learn copy * Fix binding rule copy key --- .../app/templates/dc/nodes/show/sessions.hbs | 1 + .../app/templates/dc/nspaces/index.hbs | 15 +-- .../app/templates/dc/partitions/index.hbs | 15 +-- .../app/templates/dc/peers/index.hbs | 23 ++-- .../app/templates/dc/intentions/index.hbs | 1 + .../consul-ui/app/templates/dc/kv/index.hbs | 1 + .../app/templates/dc/nodes/index.hbs | 15 ++- .../app/templates/dc/services/index.hbs | 1 + .../dc/services/show/intentions/index.hbs | 1 + ui/packages/consul-ui/config/environment.js | 1 + .../acceptance/dc/intentions/index.feature | 25 +++++ .../tests/acceptance/dc/nodes/index.feature | 13 +++ .../acceptance/dc/nodes/sessions/list.feature | 33 ++++++ .../tests/acceptance/dc/peers/index.feature | 19 +++- .../acceptance/dc/services/index.feature | 25 +++++ .../consul-ui/translations/routes/en-us.yaml | 102 +++++++++++++++--- 16 files changed, 244 insertions(+), 47 deletions(-) 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/templates/dc/peers/index.hbs b/ui/packages/consul-peerings/app/templates/dc/peers/index.hbs index 2370fa76cf..75ee9afa77 100644 --- a/ui/packages/consul-peerings/app/templates/dc/peers/index.hbs +++ b/ui/packages/consul-peerings/app/templates/dc/peers/index.hbs @@ -194,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 + }} +

  • + + @@ -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/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/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/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/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/translations/routes/en-us.yaml b/ui/packages/consul-ui/translations/routes/en-us.yaml index 69178bb5e8..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: @@ -239,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: @@ -297,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: @@ -312,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: @@ -363,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. From 1ade1de38b29209f79d87a3220c63bc746e3198e Mon Sep 17 00:00:00 2001 From: HashiBot <62622282+hashibot-web@users.noreply.github.com> Date: Thu, 6 Oct 2022 14:15:47 -0500 Subject: [PATCH 151/172] website: upgrade next version (#14906) Co-authored-by: Bryce Kalow --- website/package-lock.json | 4529 ++++--------------------------------- website/package.json | 2 +- 2 files changed, 456 insertions(+), 4075 deletions(-) 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,162 +1040,214 @@ "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 - }, - "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==", - "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" - } - }, - "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==", - "dev": true, - "peer": true, - "dependencies": { - "@babel/highlight": "^7.10.4" - } - }, - "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==", - "dev": true, - "peer": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, + "optional": true, + "os": [ + "android" + ], "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-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 - }, - "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==", - "dev": true, - "peer": true, - "dependencies": { - "whatwg-url": "^7.0.0" - }, + "optional": true, + "os": [ + "android" + ], "engines": { - "node": ">= 8" - } - }, - "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==", - "dev": true, - "peer": true, - "peerDependencies": { - "react-refresh": "0.8.3", - "webpack": "^4 || ^5" - }, - "peerDependenciesMeta": { - "webpack": { - "optional": true - } + "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"], + "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, "optional": true, - "os": ["darwin"], - "peer": true, + "os": [ + "darwin" + ], "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"], + "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, "optional": true, - "os": ["darwin"], - "peer": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "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, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10" + } + }, + "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, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "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, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "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": [ + "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"], + "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": ["linux"], - "peer": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "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" + ], + "engines": { + "node": ">= 10" + } + }, + "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" + ], + "engines": { + "node": ">= 10" + } + }, + "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, + "optional": true, + "os": [ + "win32" + ], "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"], + "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"], - "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==", - "dev": true, - "peer": true, - "dependencies": { - "@napi-rs/triples": "^1.0.3" - } - }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -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,106 +2405,6 @@ "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==", - "dev": true, - "peer": true, - "dependencies": { - "browserify-aes": "^1.0.4", - "browserify-des": "^1.0.0", - "evp_bytestokey": "^1.0.0" - } - }, - "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==", - "dev": true, - "peer": true, - "dependencies": { - "cipher-base": "^1.0.1", - "des.js": "^1.0.0", - "inherits": "^2.0.1", - "safe-buffer": "^5.1.2" - } - }, - "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==", - "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", @@ -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,37 +4427,6 @@ "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", @@ -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==", + "version": "12.3.1", + "resolved": "https://registry.npmjs.org/next/-/next-12.3.1.tgz", + "integrity": "sha512-l7bvmSeIwX5lp07WtIiP9u2ytZMv7jIeB8iacR28PuUEFG5j0HGAPnMqyG5kbZNBG2H7tRsrQ4HCjuMOPnANZw==", "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" + "@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,97 +11462,25 @@ "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", @@ -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==", + "@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 + "optional": 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-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": { - "@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" - } - } - } - }, - "@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==", - "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-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 + }, + "@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, + "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": "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==", + "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, - "peer": 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==", - "dev": true, - "peer": true, - "requires": { - "@napi-rs/triples": "^1.0.3" - } + "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,95 +16708,6 @@ "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", @@ -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==", + "version": "12.3.1", + "resolved": "https://registry.npmjs.org/next/-/next-12.3.1.tgz", + "integrity": "sha512-l7bvmSeIwX5lp07WtIiP9u2ytZMv7jIeB8iacR28PuUEFG5j0HGAPnMqyG5kbZNBG2H7tRsrQ4HCjuMOPnANZw==", "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" - } - } + "@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", @@ -23505,30 +20326,6 @@ "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" - } - }, "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==", + "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": { - "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==", - "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": { From 2603c0da520b5b30d81f02562e4c5bb9b8938c36 Mon Sep 17 00:00:00 2001 From: Jared Kirschner Date: Wed, 5 Oct 2022 11:53:29 -0700 Subject: [PATCH 152/172] docs: vault ca provider patch upgrade guidance --- CHANGELOG.md | 12 +++++ website/content/docs/connect/ca/vault.mdx | 10 ++++ .../docs/upgrading/upgrade-specific.mdx | 52 ++++++++++++++++++- 3 files changed, 72 insertions(+), 2 deletions(-) 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/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/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 From 97ad73ad246e4d523e2ebe3d1ef372ed5f625668 Mon Sep 17 00:00:00 2001 From: Tim Rosenblatt Date: Thu, 6 Oct 2022 19:23:02 -0700 Subject: [PATCH 153/172] Fixes broken URLs in Dataplane docs (#14910) --- website/content/docs/connect/dataplane/index.mdx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/website/content/docs/connect/dataplane/index.mdx b/website/content/docs/connect/dataplane/index.mdx index da1df67c4a..9101fc6b29 100644 --- a/website/content/docs/connect/dataplane/index.mdx +++ b/website/content/docs/connect/dataplane/index.mdx @@ -33,8 +33,8 @@ Consul Dataplane manages Envoy proxies and leaves responsibility for other funct 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](/consul/docs/dataplane/consul-dataplane). -- For Helm chart information, refer to the [Helm Chart reference](/consul/docs/k8s/helm). +- 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 From 2c349bb126df09931c4ae5404daf1a1f0ff5a6d4 Mon Sep 17 00:00:00 2001 From: Tyler Wendlandt Date: Fri, 7 Oct 2022 04:01:34 -0600 Subject: [PATCH 154/172] ui: Remove node name from agentless service instance (#14903) * [NET-949]: Remove node name from agentless instance * Add changelog entry --- .changelog/14903.txt | 3 +++ .../app/templates/dc/services/instance.hbs | 10 ++++--- .../consul-ui/mock-api/v1/health/service/_ | 26 +++++++++++-------- .../dc/services/instances/show.feature | 23 +++++++++++++++- 4 files changed, 46 insertions(+), 16 deletions(-) create mode 100644 .changelog/14903.txt 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/ui/packages/consul-ui/app/templates/dc/services/instance.hbs b/ui/packages/consul-ui/app/templates/dc/services/instance.hbs index a2ddf18d6b..884e4eb01f 100644 --- a/ui/packages/consul-ui/app/templates/dc/services/instance.hbs +++ b/ui/packages/consul-ui/app/templates/dc/services/instance.hbs @@ -151,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
    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/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]" From 77ab28c5c7530d683e4d797a7fc591b67d824ab0 Mon Sep 17 00:00:00 2001 From: DanStough Date: Mon, 26 Sep 2022 12:50:17 -0400 Subject: [PATCH 155/172] feat: xDS updates for peerings control plane through mesh gw --- agent/agent.go | 5 + 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_test.go | 10 +- agent/cache-types/trust_bundles_test.go | 1 + 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/data_sources.go | 8 + agent/proxycfg/mesh_gateway.go | 145 +++++- agent/proxycfg/snapshot.go | 21 +- agent/proxycfg/state.go | 1 + agent/proxycfg/state_test.go | 82 +++- agent/proxycfg/testing.go | 4 + agent/proxycfg/testing_mesh_gateway.go | 72 +++ agent/rpc/peering/service.go | 4 +- agent/rpc/peering/service_test.go | 2 + agent/xds/clusters.go | 102 ++++- agent/xds/endpoints.go | 64 ++- agent/xds/listeners.go | 40 +- agent/xds/resources_test.go | 6 + ...through-mesh-gateway-enabled.latest.golden | 54 +++ ...through-mesh-gateway-enabled.latest.golden | 37 ++ ...through-mesh-gateway-enabled.latest.golden | 79 ++++ ...h-resolver-redirect-upstream.latest.golden | 4 +- ...through-mesh-gateway-enabled.latest.golden | 5 + proto/pbpeering/peering.pb.go | 415 +++++++++--------- proto/pbpeering/peering.proto | 1 + 30 files changed, 1373 insertions(+), 272 deletions(-) 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 create mode 100644 agent/proxycfg-glue/peering_list.go create mode 100644 agent/proxycfg-glue/peering_list_test.go create mode 100644 agent/xds/testdata/clusters/mesh-gateway-with-peer-through-mesh-gateway-enabled.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/mesh-gateway-with-peer-through-mesh-gateway-enabled.latest.golden create mode 100644 agent/xds/testdata/routes/mesh-gateway-with-peer-through-mesh-gateway-enabled.latest.golden diff --git a/agent/agent.go b/agent/agent.go index ff41544ef5..3de22c6ae5 100644 --- a/agent/agent.go +++ b/agent/agent.go @@ -4238,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}) @@ -4257,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() } @@ -4371,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), @@ -4399,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/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_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_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/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/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/mesh_gateway.go b/agent/proxycfg/mesh_gateway.go index 9b134c3e60..eca57febf0 100644 --- a/agent/proxycfg/mesh_gateway.go +++ b/agent/proxycfg/mesh_gateway.go @@ -3,10 +3,14 @@ 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" @@ -19,10 +23,18 @@ 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.WatchedConsulServers = watch.NewMap[string, structs.CheckServiceNodes]() + snap.MeshGateway.WatchedLocalServers = watch.NewMap[string, structs.CheckServiceNodes]() // Watch for root changes err := s.dataSources.CARoots.Notify(ctx, &structs.DCSpecificRequest{ @@ -151,7 +163,7 @@ func (s *handlerMeshGateway) initializeCrossDCWatches(ctx context.Context, snap if err != nil { return err } - snap.MeshGateway.WatchedConsulServers.InitWatch(structs.ConsulServiceName, nil) + snap.MeshGateway.WatchedLocalServers.InitWatch(structs.ConsulServiceName, nil) } err := s.dataSources.Datacenters.Notify(ctx, &structs.DatacentersRequest{ @@ -343,7 +355,7 @@ func (s *handlerMeshGateway) handleUpdate(ctx context.Context, u UpdateEvent, sn } } - snap.MeshGateway.WatchedConsulServers.Set(structs.ConsulServiceName, resp.Nodes) + snap.MeshGateway.WatchedLocalServers.Set(structs.ConsulServiceName, resp.Nodes) case exportedServiceListWatchID: exportedServices, ok := u.Result.(*structs.IndexedExportedServiceList) @@ -515,26 +527,54 @@ func (s *handlerMeshGateway) handleUpdate(ctx context.Context, u UpdateEvent, sn return fmt.Errorf("invalid type for response: %T", u.Result) } - if resp.Entry == nil { - 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 - // We avoid managing server watches when WAN federation is enabled since it + if !meshConf.PeerThroughMeshGateways() { + // We avoid canceling server watches when WAN federation is enabled since it // always requires server watches. 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.WatchedConsulServers.CancelWatch(structs.ConsulServiceName) + snap.MeshGateway.WatchedLocalServers.CancelWatch(structs.ConsulServiceName) + } + if snap.MeshGateway.PeerServersWatchCancel != nil { + snap.MeshGateway.PeerServersWatchCancel() + snap.MeshGateway.PeerServersWatchCancel = nil + + snap.MeshGateway.PeerServers = nil } - snap.MeshGateway.MeshConfigSet = true return nil } - meshConf, ok := resp.Entry.(*structs.MeshConfigEntry) - if !ok { - return fmt.Errorf("invalid type for config entry: %T", resp.Entry) + // 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 meshConf.PeerThroughMeshGateways() && + 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 } - snap.MeshGateway.MeshConfig = meshConf + snap.MeshGateway.MeshConfigSet = true // We avoid managing Consul server watches when WAN federation is enabled since it @@ -544,10 +584,10 @@ func (s *handlerMeshGateway) handleUpdate(ctx context.Context, u UpdateEvent, sn } if !meshConf.PeerThroughMeshGateways() { - snap.MeshGateway.WatchedConsulServers.CancelWatch(structs.ConsulServiceName) + snap.MeshGateway.WatchedLocalServers.CancelWatch(structs.ConsulServiceName) return nil } - if snap.MeshGateway.WatchedConsulServers.IsWatched(structs.ConsulServiceName) { + if snap.MeshGateway.WatchedLocalServers.IsWatched(structs.ConsulServiceName) { return nil } @@ -562,7 +602,45 @@ func (s *handlerMeshGateway) handleUpdate(ctx context.Context, u UpdateEvent, sn return fmt.Errorf("failed to watch local consul servers: %w", err) } - snap.MeshGateway.WatchedConsulServers.InitWatch(structs.ConsulServiceName, cancel) + 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 { @@ -707,3 +785,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 a52d568cce..aed223fb0a 100644 --- a/agent/proxycfg/snapshot.go +++ b/agent/proxycfg/snapshot.go @@ -348,6 +348,12 @@ 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 @@ -403,11 +409,11 @@ type configSnapshotMeshGateway struct { // datacenter. FedStateGateways map[string]structs.CheckServiceNodes - // WatchedConsulServers is a map of (structs.ConsulServiceName -> 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. - WatchedConsulServers watch.Map[string, structs.CheckServiceNodes] + 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. @@ -449,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 @@ -588,7 +601,7 @@ func (c *configSnapshotMeshGateway) isEmpty() bool { len(c.GatewayGroups) == 0 && len(c.FedStateGateways) == 0 && len(c.HostnameDatacenters) == 0 && - c.WatchedConsulServers.Len() == 0 && + c.WatchedLocalServers.Len() == 0 && c.isEmptyPeering() } @@ -724,7 +737,7 @@ func (s *ConfigSnapshot) Valid() bool { s.TerminatingGateway.MeshConfigSet case structs.ServiceKindMeshGateway: - if s.MeshGateway.WatchedConsulServers.Len() == 0 { + if s.MeshGateway.WatchedLocalServers.Len() == 0 { if s.ServiceMeta[structs.MetaWANFederationKey] == "1" { return false } diff --git a/agent/proxycfg/state.go b/agent/proxycfg/state.go index 52df574289..1ed67d3d54 100644 --- a/agent/proxycfg/state.go +++ b/agent/proxycfg/state.go @@ -38,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:" diff --git a/agent/proxycfg/state_test.go b/agent/proxycfg/state_test.go index 7e5675e970..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) @@ -1130,6 +1139,7 @@ func TestState_WatchesAndUpdates(t *testing.T) { { requiredWatches: map[string]verifyWatchRequest{ consulServerListWatchID: genVerifyServiceSpecificPeeredRequest(structs.ConsulServiceName, "", "dc1", "", false), + peerServersWatchID: genVerifyPeeringListWatchForMeshGateway(), }, events: []UpdateEvent{ { @@ -1166,8 +1176,9 @@ func TestState_WatchesAndUpdates(t *testing.T) { }, verifySnapshot: func(t testing.TB, snap *ConfigSnapshot) { require.True(t, snap.Valid()) + require.NotNil(t, snap.MeshGateway.PeerServersWatchCancel) - servers, ok := snap.MeshGateway.WatchedConsulServers.Get(structs.ConsulServiceName) + servers, ok := snap.MeshGateway.WatchedLocalServers.Get(structs.ConsulServiceName) require.True(t, ok) expect := structs.CheckServiceNodes{ @@ -1198,6 +1209,64 @@ func TestState_WatchesAndUpdates(t *testing.T) { 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{ { @@ -1215,8 +1284,11 @@ func TestState_WatchesAndUpdates(t *testing.T) { require.True(t, snap.Valid()) require.NotNil(t, snap.MeshConfig()) - require.False(t, snap.MeshGateway.WatchedConsulServers.IsWatched(structs.ConsulServiceName)) - servers, ok := snap.MeshGateway.WatchedConsulServers.Get(structs.ConsulServiceName) + 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) }, @@ -1234,8 +1306,8 @@ func TestState_WatchesAndUpdates(t *testing.T) { require.True(t, snap.Valid()) require.Nil(t, snap.MeshConfig()) - require.False(t, snap.MeshGateway.WatchedConsulServers.IsWatched(structs.ConsulServiceName)) - servers, ok := snap.MeshGateway.WatchedConsulServers.Get(structs.ConsulServiceName) + 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) }, diff --git a/agent/proxycfg/testing.go b/agent/proxycfg/testing.go index 6382deb66b..00ca99ab79 100644 --- a/agent/proxycfg/testing.go +++ b/agent/proxycfg/testing.go @@ -747,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]{}, @@ -951,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](), @@ -977,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] @@ -1003,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_mesh_gateway.go b/agent/proxycfg/testing_mesh_gateway.go index 49205fe149..039807ed61 100644 --- a/agent/proxycfg/testing_mesh_gateway.go +++ b/agent/proxycfg/testing_mesh_gateway.go @@ -773,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/rpc/peering/service.go b/agent/rpc/peering/service.go index d9ddbdc6c3..daef4ef3a0 100644 --- a/agent/rpc/peering/service.go +++ b/agent/rpc/peering/service.go @@ -607,7 +607,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 } @@ -619,7 +619,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 diff --git a/agent/rpc/peering/service_test.go b/agent/rpc/peering/service_test.go index 3d04981f4f..e0d29b2725 100644 --- a/agent/rpc/peering/service_test.go +++ b/agent/rpc/peering/service_test.go @@ -818,6 +818,7 @@ func TestPeeringService_List(t *testing.T) { expect := &pbpeering.PeeringListResponse{ Peerings: []*pbpeering.Peering{bar, foo}, + Index: 15, } prototest.AssertDeepEqual(t, expect, resp) } @@ -883,6 +884,7 @@ func TestPeeringService_List_ACLEnforcement(t *testing.T) { token: testTokenPeeringReadSecret, expect: &pbpeering.PeeringListResponse{ Peerings: []*pbpeering.Peering{bar, foo}, + Index: 15, }, }, } diff --git a/agent/xds/clusters.go b/agent/xds/clusters.go index 4604c69e82..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,7 @@ func (s *ResourceGenerator) clustersFromSnapshotMeshGateway(cfgSnap *proxycfg.Co } // And for the current datacenter, send all flavors appropriately. - servers, _ := cfgSnap.MeshGateway.WatchedConsulServers.Get(structs.ConsulServiceName) + servers, _ := cfgSnap.MeshGateway.WatchedLocalServers.Get(structs.ConsulServiceName) for _, srv := range servers { opts := clusterOpts{ name: cfgSnap.ServerSNIFn(cfgSnap.Datacenter, srv.Node.Node), @@ -399,7 +402,7 @@ 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.WatchedConsulServers.Get(structs.ConsulServiceName) + 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. @@ -432,6 +435,13 @@ 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 } @@ -445,6 +455,37 @@ func haveVoters(servers structs.CheckServiceNodes) bool { 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 +} + // clustersFromSnapshotTerminatingGateway returns the xDS API representation of the "clusters" // for a terminating gateway. This will include 1 cluster per Destination associated with this terminating gateway. func (s *ResourceGenerator) clustersFromSnapshotTerminatingGateway(cfgSnap *proxycfg.ConfigSnapshot) ([]proto.Message, error) { @@ -633,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 @@ -1438,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 @@ -1454,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 @@ -1584,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 @@ -1605,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{ @@ -1620,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 @@ -1643,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)) - endpoints := []*envoy_endpoint_v3.LbEndpoint{ - { + for _, pair := range opts.addresses { + address := makeAddress(pair.Address, pair.Port) + + 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/endpoints.go b/agent/xds/endpoints.go index 6a6f8c844c..2a699ed002 100644 --- a/agent/xds/endpoints.go +++ b/agent/xds/endpoints.go @@ -195,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()) { @@ -248,7 +253,7 @@ func (s *ResourceGenerator) endpointsFromSnapshotMeshGateway(cfgSnap *proxycfg.C cfgSnap.ServerSNIFn != nil { var allServersLbEndpoints []*envoy_endpoint_v3.LbEndpoint - servers, _ := cfgSnap.MeshGateway.WatchedConsulServers.Get(structs.ConsulServiceName) + servers, _ := cfgSnap.MeshGateway.WatchedLocalServers.Get(structs.ConsulServiceName) for _, srv := range servers { clusterName := cfgSnap.ServerSNIFn(cfgSnap.Datacenter, srv.Node.Node) @@ -289,7 +294,7 @@ func (s *ResourceGenerator) endpointsFromSnapshotMeshGateway(cfgSnap *proxycfg.C if cfg := cfgSnap.MeshConfig(); cfg.PeerThroughMeshGateways() { var serverEndpoints []*envoy_endpoint_v3.LbEndpoint - servers, _ := cfgSnap.MeshGateway.WatchedConsulServers.Get(structs.ConsulServiceName) + 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. @@ -319,13 +324,14 @@ func (s *ResourceGenerator) endpointsFromSnapshotMeshGateway(cfgSnap *proxycfg.C }, }) } - - resources = append(resources, &envoy_endpoint_v3.ClusterLoadAssignment{ - ClusterName: connect.PeeringServerSAN(cfgSnap.Datacenter, cfgSnap.Roots.TrustDomain), - Endpoints: []*envoy_endpoint_v3.LocalityLbEndpoints{{ - LbEndpoints: serverEndpoints, - }}, - }) + 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 @@ -349,6 +355,13 @@ func (s *ResourceGenerator) endpointsFromSnapshotMeshGateway(cfgSnap *proxycfg.C } 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 } @@ -440,6 +453,37 @@ func (s *ResourceGenerator) makeEndpointsForOutgoingPeeredServices( 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) diff --git a/agent/xds/listeners.go b/agent/xds/listeners.go index e8f976fa88..3a35431926 100644 --- a/agent/xds/listeners.go +++ b/agent/xds/listeners.go @@ -1731,6 +1731,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 { @@ -1757,7 +1761,7 @@ func (s *ResourceGenerator) makeMeshGatewayListener(name, addr string, port int, } // Wildcard all flavors to each server. - servers, _ := cfgSnap.MeshGateway.WatchedConsulServers.Get(structs.ConsulServiceName) + servers, _ := cfgSnap.MeshGateway.WatchedLocalServers.Get(structs.ConsulServiceName) for _, srv := range servers { clusterName := cfgSnap.ServerSNIFn(cfgSnap.Datacenter, srv.Node.Node) @@ -1778,10 +1782,14 @@ func (s *ResourceGenerator) makeMeshGatewayListener(name, addr string, port int, } } - // Create a single cluster for local servers to be dialed by peers. + // -------- + // 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 cfg := cfgSnap.MeshConfig(); cfg.PeerThroughMeshGateways() { - servers, _ := cfgSnap.MeshGateway.WatchedConsulServers.Get(structs.ConsulServiceName) + 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. @@ -1805,6 +1813,30 @@ func (s *ResourceGenerator) makeMeshGatewayListener(name, addr string, port int, } } + // 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) diff --git a/agent/xds/resources_test.go b/agent/xds/resources_test.go index f7c6e9d75d..9c707cd5b4 100644 --- a/agent/xds/resources_test.go +++ b/agent/xds/resources_test.go @@ -231,6 +231,12 @@ func getMeshGatewayPeeringGoldenTestCases() []goldenTestCase { 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/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/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/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 index 3d0826b75c..cbde9b76e5 100644 --- 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 @@ -17,8 +17,8 @@ "name": "envoy.filters.network.tcp_proxy", "typedConfig": { "@type": "type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy", - "cluster": "db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul", - "statPrefix": "upstream.db-redir.default.default.dc1" + "statPrefix": "upstream.db-redir.default.default.dc1", + "cluster": "db.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul" } } ] 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/proto/pbpeering/peering.pb.go b/proto/pbpeering/peering.pb.go index bd4d28d1b1..913f13600a 100644 --- a/proto/pbpeering/peering.pb.go +++ b/proto/pbpeering/peering.pb.go @@ -792,6 +792,7 @@ 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() { @@ -833,6 +834,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 @@ -2287,227 +2295,228 @@ var file_proto_pbpeering_peering_proto_rawDesc = []byte{ 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, + 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, 0x08, 0x50, 0x65, 0x65, 0x72, 0x69, 0x6e, 0x67, - 0x73, 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, 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, + 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, 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, + 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, 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, 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, + 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, 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, 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, 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, + 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, 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, + 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, 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, 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, + 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, 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, 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, - 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, + 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, 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, + 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, 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, 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, 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, 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, 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 ( diff --git a/proto/pbpeering/peering.proto b/proto/pbpeering/peering.proto index 4025356e0b..a9a0ff1126 100644 --- a/proto/pbpeering/peering.proto +++ b/proto/pbpeering/peering.proto @@ -254,6 +254,7 @@ message PeeringListRequest { message PeeringListResponse { repeated Peering Peerings = 1; + uint64 Index = 2; } message PeeringWriteRequest { From d54db254216c639dfb40652eda3df5110c831ffd Mon Sep 17 00:00:00 2001 From: freddygv Date: Thu, 6 Oct 2022 11:14:55 -0600 Subject: [PATCH 156/172] Use existing query options to build ctx --- agent/cache-types/trust_bundle.go | 3 +-- agent/cache-types/trust_bundles.go | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/agent/cache-types/trust_bundle.go b/agent/cache-types/trust_bundle.go index b9db20645c..addc65ac94 100644 --- a/agent/cache-types/trust_bundle.go +++ b/agent/cache-types/trust_bundle.go @@ -83,8 +83,7 @@ func (t *TrustBundle) Fetch(_ cache.FetchOptions, req cache.Request) (cache.Fetc reqReal.QueryOptions.SetAllowStale(true) // Fetch - options := structs.QueryOptions{Token: reqReal.Token} - ctx, err := external.ContextWithQueryOptions(context.Background(), options) + ctx, err := external.ContextWithQueryOptions(context.Background(), reqReal.QueryOptions) if err != nil { return result, err } diff --git a/agent/cache-types/trust_bundles.go b/agent/cache-types/trust_bundles.go index d3c1f404c6..47f411f02f 100644 --- a/agent/cache-types/trust_bundles.go +++ b/agent/cache-types/trust_bundles.go @@ -87,8 +87,7 @@ func (t *TrustBundles) Fetch(_ cache.FetchOptions, req cache.Request) (cache.Fet reqReal.QueryOptions.SetAllowStale(true) // Fetch - options := structs.QueryOptions{Token: reqReal.Token} - ctx, err := external.ContextWithQueryOptions(context.Background(), options) + ctx, err := external.ContextWithQueryOptions(context.Background(), reqReal.QueryOptions) if err != nil { return result, err } From 5f97223822f59d3669d299b315342c16f416f2f7 Mon Sep 17 00:00:00 2001 From: freddygv Date: Fri, 7 Oct 2022 08:54:37 -0600 Subject: [PATCH 157/172] Simplify mgw watch mgmt --- agent/proxycfg/mesh_gateway.go | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/agent/proxycfg/mesh_gateway.go b/agent/proxycfg/mesh_gateway.go index eca57febf0..1378f9b9bf 100644 --- a/agent/proxycfg/mesh_gateway.go +++ b/agent/proxycfg/mesh_gateway.go @@ -534,9 +534,11 @@ func (s *handlerMeshGateway) handleUpdate(ctx context.Context, u UpdateEvent, sn 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 server watches. + // 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. @@ -556,8 +558,7 @@ func (s *handlerMeshGateway) handleUpdate(ctx context.Context, u UpdateEvent, sn // 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 meshConf.PeerThroughMeshGateways() && - snap.ProxyID.InDefaultPartition() && + if snap.ProxyID.InDefaultPartition() && snap.MeshGateway.PeerServersWatchCancel == nil { peeringListCtx, cancel := context.WithCancel(ctx) @@ -575,18 +576,12 @@ func (s *handlerMeshGateway) handleUpdate(ctx context.Context, u UpdateEvent, sn snap.MeshGateway.PeerServersWatchCancel = cancel } - snap.MeshGateway.MeshConfigSet = true - - // We avoid managing Consul server watches when WAN federation is enabled since it + // 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 !meshConf.PeerThroughMeshGateways() { - snap.MeshGateway.WatchedLocalServers.CancelWatch(structs.ConsulServiceName) - return nil - } if snap.MeshGateway.WatchedLocalServers.IsWatched(structs.ConsulServiceName) { return nil } From fac3ddc857b1a88ff3e6b2a7580eb07e4c14af3b Mon Sep 17 00:00:00 2001 From: freddygv Date: Wed, 28 Sep 2022 21:27:11 -0600 Subject: [PATCH 158/172] Use internal server certificate for peering TLS A previous commit introduced an internally-managed server certificate to use for peering-related purposes. Now the peering token has been updated to match that behavior: - The server name matches the structure of the server cert - The CA PEMs correspond to the Connect CA Note that if Conect is disabled, and by extension the Connect CA, we fall back to the previous behavior of returning the manually configured certs and local server SNI. Several tests were updated to use the gRPC TLS port since they enable Connect by default. This means that the peering token will embed the Connect CA, and the dialer will expect a TLS listener. --- agent/agent_endpoint_test.go | 11 ++-- agent/connect/testing_ca.go | 26 ++++++-- agent/consul/leader_peering_test.go | 72 +++++++++++++++------ agent/consul/peering_backend.go | 45 ++++++++++--- agent/consul/server_test.go | 29 +++++++-- agent/peering_endpoint_test.go | 52 ++++++--------- agent/rpc/peering/service.go | 24 +++---- agent/rpc/peering/service_test.go | 71 +++++++++++++++----- agent/rpc/peering/validate.go | 4 +- agent/testagent.go | 17 ++--- api/api_test.go | 4 ++ api/peering_test.go | 4 +- command/peering/establish/establish_test.go | 23 ++++--- command/peering/generate/generate_test.go | 10 +-- sdk/testutil/server.go | 26 ++++---- 15 files changed, 267 insertions(+), 151 deletions(-) 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/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/leader_peering_test.go b/agent/consul/leader_peering_test.go index 7df1a4b065..df7332300b 100644 --- a/agent/consul/leader_peering_test.go +++ b/agent/consul/leader_peering_test.go @@ -22,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,29 +34,53 @@ import ( "github.com/hashicorp/consul/types" ) +type tlsMode byte + +const ( + tlsModeNone tlsMode = iota + tlsModeManual + tlsModeAuto +) + func TestLeader_PeeringSync_Lifecycle_ClientDeletion(t *testing.T) { t.Run("without-tls", func(t *testing.T) { - testLeader_PeeringSync_Lifecycle_ClientDeletion(t, false) + testLeader_PeeringSync_Lifecycle_ClientDeletion(t, tlsModeNone) }) - t.Run("with-tls", func(t *testing.T) { - testLeader_PeeringSync_Lifecycle_ClientDeletion(t, true) + t.Run("manual-tls", func(t *testing.T) { + testLeader_PeeringSync_Lifecycle_ClientDeletion(t, tlsModeManual) + }) + t.Run("auto-tls", func(t *testing.T) { + testLeader_PeeringSync_Lifecycle_ClientDeletion(t, tlsModeAuto) }) } -func testLeader_PeeringSync_Lifecycle_ClientDeletion(t *testing.T, enableTLS bool) { +func testLeader_PeeringSync_Lifecycle_ClientDeletion(t *testing.T, mode tlsMode) { 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 { + if mode == tlsModeManual { + c.ConnectEnabled = false c.TLSConfig.GRPC.CAFile = "../../test/hostname/CertAuth.crt" c.TLSConfig.GRPC.CertFile = "../../test/hostname/Bob.crt" c.TLSConfig.GRPC.KeyFile = "../../test/hostname/Bob.key" } + if mode == tlsModeAuto { + 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") @@ -94,11 +119,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") @@ -345,27 +365,43 @@ func TestLeader_PeeringSync_Lifecycle_UnexportWhileDown(t *testing.T) { func TestLeader_PeeringSync_Lifecycle_ServerDeletion(t *testing.T) { t.Run("without-tls", func(t *testing.T) { - testLeader_PeeringSync_Lifecycle_AcceptorDeletion(t, false) + testLeader_PeeringSync_Lifecycle_AcceptorDeletion(t, tlsModeNone) }) - t.Run("with-tls", func(t *testing.T) { - testLeader_PeeringSync_Lifecycle_AcceptorDeletion(t, true) + t.Run("manual-tls", func(t *testing.T) { + testLeader_PeeringSync_Lifecycle_AcceptorDeletion(t, tlsModeManual) + }) + t.Run("auto-tls", func(t *testing.T) { + testLeader_PeeringSync_Lifecycle_AcceptorDeletion(t, tlsModeAuto) }) } -func testLeader_PeeringSync_Lifecycle_AcceptorDeletion(t *testing.T, enableTLS bool) { +func testLeader_PeeringSync_Lifecycle_AcceptorDeletion(t *testing.T, mode tlsMode) { 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 { + if mode == tlsModeManual { + c.ConnectEnabled = false c.TLSConfig.GRPC.CAFile = "../../test/hostname/CertAuth.crt" c.TLSConfig.GRPC.CertFile = "../../test/hostname/Bob.crt" c.TLSConfig.GRPC.KeyFile = "../../test/hostname/Bob.key" } + if mode == tlsModeAuto { + 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") @@ -399,11 +435,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") @@ -496,6 +527,7 @@ func testLeader_PeeringSync_failsForTLSError(t *testing.T, tokenMutateFn func(to c.Datacenter = "dc1" c.TLSConfig.Domain = "consul" + c.ConnectEnabled = false c.TLSConfig.GRPC.CAFile = "../../test/hostname/CertAuth.crt" c.TLSConfig.GRPC.CertFile = "../../test/hostname/Bob.crt" c.TLSConfig.GRPC.KeyFile = "../../test/hostname/Bob.key" diff --git a/agent/consul/peering_backend.go b/agent/consul/peering_backend.go index 8ed8a6079f..d0f7e31658 100644 --- a/agent/consul/peering_backend.go +++ b/agent/consul/peering_backend.go @@ -9,12 +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" ) @@ -53,10 +55,39 @@ 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() (string, []string, error) { + // Do not send TLS materials to the dialer if we to not have TLS configured for gRPC. + if b.srv.config.GRPCTLSPort <= 0 && !b.srv.tlsConfigurator.GRPCServerUseTLS() { + return "", nil, nil + } + + // If the Connect CA is not in use we rely on the manually configured certs. + // Otherwise we rely on the internally managed server certificate. + if !b.srv.config.ConnectEnabled { + serverName := b.srv.tlsConfigurator.ServerSNI(b.srv.config.Datacenter, "") + caPems := b.srv.tlsConfigurator.GRPCManualCAPems() + + return serverName, caPems, nil + } + + 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 { + 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 or mesh gateway addresses from the state store. @@ -117,12 +148,6 @@ func serverAddresses(state *state.Store) ([]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/server_test.go b/agent/consul/server_test.go index 287dca871b..b9f8e7db90 100644 --- a/agent/consul/server_test.go +++ b/agent/consul/server_test.go @@ -232,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 @@ -247,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()) } diff --git a/agent/peering_endpoint_test.go b/agent/peering_endpoint_test.go index 0f5692bad1..c7006a9259 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) - // success response does not currently return a value so {} is correct - require.Equal(t, "{}", resp.Body.String()) + 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(r, "{}", resp.Body.String()) + }) }) } @@ -416,6 +401,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", diff --git a/agent/rpc/peering/service.go b/agent/rpc/peering/service.go index d9ddbdc6c3..cb72fe84cb 100644 --- a/agent/rpc/peering/service.go +++ b/agent/rpc/peering/service.go @@ -114,17 +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() (string, []string, error) // 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) @@ -291,7 +288,7 @@ func (s *Server) GenerateToken( break } - ca, err := s.Backend.GetAgentCACertificates() + serverName, caPEMs, err := s.Backend.GetTLSMaterials() if err != nil { return nil, err } @@ -310,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, } @@ -487,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() + if err != nil { + return fmt.Errorf("failed to fetch TLS materials: %w", err) + } + + if serverName != "" && token.ServerName != "" && 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) { diff --git a/agent/rpc/peering/service_test.go b/agent/rpc/peering/service_test.go index 3d04981f4f..e20472a352 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) { @@ -385,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() + 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, } @@ -409,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)) @@ -430,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) @@ -1095,9 +1116,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) @@ -1111,8 +1130,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)) @@ -1362,7 +1381,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 { @@ -1381,27 +1411,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/testagent.go b/agent/testagent.go index 2fb4438ddb..2d34ba198b 100644 --- a/agent/testagent.go +++ b/agent/testagent.go @@ -190,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()) @@ -412,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 @@ -424,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]) + ` @@ -441,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/api/api_test.go b/api/api_test.go index 3574240d1a..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 diff --git a/api/peering_test.go b/api/peering_test.go index 9b7974ea44..eb58f22e40 100644 --- a/api/peering_test.go +++ b/api/peering_test.go @@ -44,7 +44,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() @@ -52,7 +51,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() @@ -265,7 +263,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/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/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, From 3034df6a5c15ee05c3b6b143777883e2b2ff0e14 Mon Sep 17 00:00:00 2001 From: freddygv Date: Thu, 29 Sep 2022 15:49:58 -0600 Subject: [PATCH 159/172] Require Connect and TLS to generate peering tokens By requiring Connect and a gRPC TLS listener we can automatically configure TLS for all peering control-plane traffic. --- agent/consul/leader_peering_test.go | 155 +++++++++--------- agent/consul/peering_backend.go | 23 +-- agent/consul/peering_backend_oss_test.go | 28 +++- agent/consul/peering_backend_test.go | 59 ++++--- agent/consul/prepared_query_endpoint_test.go | 12 ++ .../services/peerstream/stream_test.go | 1 - agent/rpc/peering/service.go | 16 +- agent/rpc/peering/service_test.go | 2 +- .../connect/envoy/consul-base-cfg/base.hcl | 2 +- .../envoy/consul-base-cfg/peering_server.hcl | 6 + test/integration/connect/envoy/run-tests.sh | 13 +- 11 files changed, 185 insertions(+), 132 deletions(-) create mode 100644 test/integration/connect/envoy/consul-base-cfg/peering_server.hcl diff --git a/agent/consul/leader_peering_test.go b/agent/consul/leader_peering_test.go index df7332300b..1e04f0f43a 100644 --- a/agent/consul/leader_peering_test.go +++ b/agent/consul/leader_peering_test.go @@ -43,18 +43,6 @@ const ( ) func TestLeader_PeeringSync_Lifecycle_ClientDeletion(t *testing.T) { - t.Run("without-tls", func(t *testing.T) { - testLeader_PeeringSync_Lifecycle_ClientDeletion(t, tlsModeNone) - }) - t.Run("manual-tls", func(t *testing.T) { - testLeader_PeeringSync_Lifecycle_ClientDeletion(t, tlsModeManual) - }) - t.Run("auto-tls", func(t *testing.T) { - testLeader_PeeringSync_Lifecycle_ClientDeletion(t, tlsModeAuto) - }) -} - -func testLeader_PeeringSync_Lifecycle_ClientDeletion(t *testing.T, mode tlsMode) { if testing.Short() { t.Skip("too slow for testing.Short") } @@ -64,22 +52,14 @@ func testLeader_PeeringSync_Lifecycle_ClientDeletion(t *testing.T, mode tlsMode) c.NodeName = "acceptor" c.Datacenter = "dc1" c.TLSConfig.Domain = "consul" - if mode == tlsModeManual { - c.ConnectEnabled = false - c.TLSConfig.GRPC.CAFile = "../../test/hostname/CertAuth.crt" - c.TLSConfig.GRPC.CertFile = "../../test/hostname/Bob.crt" - c.TLSConfig.GRPC.KeyFile = "../../test/hostname/Bob.key" - } - if mode == tlsModeAuto { - c.CAConfig = &structs.CAConfiguration{ - ClusterID: connect.TestClusterID, - Provider: structs.ConsulCAProvider, - Config: map[string]interface{}{ - "PrivateKey": ca.SigningKey, - "RootCert": ca.RootCert, - }, - } - + 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") @@ -364,18 +344,6 @@ func TestLeader_PeeringSync_Lifecycle_UnexportWhileDown(t *testing.T) { } func TestLeader_PeeringSync_Lifecycle_ServerDeletion(t *testing.T) { - t.Run("without-tls", func(t *testing.T) { - testLeader_PeeringSync_Lifecycle_AcceptorDeletion(t, tlsModeNone) - }) - t.Run("manual-tls", func(t *testing.T) { - testLeader_PeeringSync_Lifecycle_AcceptorDeletion(t, tlsModeManual) - }) - t.Run("auto-tls", func(t *testing.T) { - testLeader_PeeringSync_Lifecycle_AcceptorDeletion(t, tlsModeAuto) - }) -} - -func testLeader_PeeringSync_Lifecycle_AcceptorDeletion(t *testing.T, mode tlsMode) { if testing.Short() { t.Skip("too slow for testing.Short") } @@ -385,22 +353,14 @@ func testLeader_PeeringSync_Lifecycle_AcceptorDeletion(t *testing.T, mode tlsMod c.NodeName = "acceptor" c.Datacenter = "dc1" c.TLSConfig.Domain = "consul" - if mode == tlsModeManual { - c.ConnectEnabled = false - c.TLSConfig.GRPC.CAFile = "../../test/hostname/CertAuth.crt" - c.TLSConfig.GRPC.CertFile = "../../test/hostname/Bob.crt" - c.TLSConfig.GRPC.KeyFile = "../../test/hostname/Bob.key" - } - if mode == tlsModeAuto { - c.CAConfig = &structs.CAConfiguration{ - ClusterID: connect.TestClusterID, - Provider: structs.ConsulCAProvider, - Config: map[string]interface{}{ - "PrivateKey": ca.SigningKey, - "RootCert": ca.RootCert, - }, - } - + 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") @@ -507,7 +467,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") @@ -522,15 +482,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.ConnectEnabled = false - 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") @@ -573,10 +538,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") @@ -615,11 +576,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") @@ -694,15 +655,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") @@ -805,9 +772,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") @@ -902,11 +877,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") @@ -1204,11 +1187,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") @@ -1615,10 +1606,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/peering_backend.go b/agent/consul/peering_backend.go index d0f7e31658..26c2f19436 100644 --- a/agent/consul/peering_backend.go +++ b/agent/consul/peering_backend.go @@ -57,26 +57,21 @@ func (b *PeeringBackend) GetLeaderAddress() string { // 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() (string, []string, error) { - // Do not send TLS materials to the dialer if we to not have TLS configured for gRPC. - if b.srv.config.GRPCTLSPort <= 0 && !b.srv.tlsConfigurator.GRPCServerUseTLS() { - return "", nil, nil - } - - // If the Connect CA is not in use we rely on the manually configured certs. - // Otherwise we rely on the internally managed server certificate. - if !b.srv.config.ConnectEnabled { - serverName := b.srv.tlsConfigurator.ServerSNI(b.srv.config.Datacenter, "") - caPems := b.srv.tlsConfigurator.GRPCManualCAPems() - - return serverName, caPems, nil +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 { + if len(roots.Roots) == 0 || roots.TrustDomain == "" { return "", nil, fmt.Errorf("CA has not finished initializing") } 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 2d9b9f029d..0d834c09a9 100644 --- a/agent/consul/peering_backend_test.go +++ b/agent/consul/peering_backend_test.go @@ -7,17 +7,18 @@ import ( "testing" "time" - "github.com/hashicorp/consul/agent/structs" - "github.com/hashicorp/consul/sdk/freeport" - "github.com/hashicorp/consul/types" - "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) { @@ -25,17 +26,26 @@ func TestPeeringBackend_ForwardToLeader(t *testing.T) { t.Skip("too slow for testing.Short") } - _, conf1 := testServerConfig(t) - server1, err := newServer(t, conf1) - require.NoError(t, err) - - _, 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") @@ -166,17 +176,26 @@ func TestPeerStreamService_ForwardToLeader(t *testing.T) { t.Skip("too slow for testing.Short") } - _, conf1 := testServerConfig(t) - server1, err := newServer(t, conf1) - require.NoError(t, err) - - _, 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_endpoint_test.go b/agent/consul/prepared_query_endpoint_test.go index 07e9801a68..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" @@ -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() diff --git a/agent/grpc-external/services/peerstream/stream_test.go b/agent/grpc-external/services/peerstream/stream_test.go index 156efa4759..da167bd2a6 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" ) diff --git a/agent/rpc/peering/service.go b/agent/rpc/peering/service.go index cb72fe84cb..77a35d164b 100644 --- a/agent/rpc/peering/service.go +++ b/agent/rpc/peering/service.go @@ -116,7 +116,7 @@ type Backend interface { // 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() (string, []string, error) + GetTLSMaterials(generatingToken bool) (string, []string, error) // GetServerAddresses returns the addresses used for establishing a peering connection. // These may be server addresses or mesh gateway addresses if peering through mesh gateways. @@ -221,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 @@ -288,11 +293,6 @@ func (s *Server) GenerateToken( break } - serverName, caPEMs, err := s.Backend.GetTLSMaterials() - if err != nil { - return nil, err - } - // ServerExternalAddresses must be formatted as addr:port. var serverAddrs []string if len(req.ServerExternalAddresses) > 0 { @@ -484,12 +484,12 @@ 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. - serverName, _, err := s.Backend.GetTLSMaterials() + serverName, _, err := s.Backend.GetTLSMaterials(false) if err != nil { return fmt.Errorf("failed to fetch TLS materials: %w", err) } - if serverName != "" && token.ServerName != "" && serverName == token.ServerName && peering == nil { + 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) } diff --git a/agent/rpc/peering/service_test.go b/agent/rpc/peering/service_test.go index e20472a352..dcae022172 100644 --- a/agent/rpc/peering/service_test.go +++ b/agent/rpc/peering/service_test.go @@ -398,7 +398,7 @@ func TestPeeringService_Establish_serverNameConflict(t *testing.T) { id, err := uuid.GenerateUUID() require.NoError(t, err, "could not generate uuid") - serverName, _, err := s.Server.GetPeeringBackend().GetTLSMaterials() + serverName, _, err := s.Server.GetPeeringBackend().GetTLSMaterials(true) require.NoError(t, err) peeringToken := structs.PeeringToken{ 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/run-tests.sh b/test/integration/connect/envoy/run-tests.sh index 7ea41527e5..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 From 79da55a4b96961b10f8d2a69433cc2c624d42193 Mon Sep 17 00:00:00 2001 From: freddygv Date: Fri, 7 Oct 2022 09:22:49 -0600 Subject: [PATCH 160/172] Ensure lines were modified It's possible that the output of the diff contains surrounding lines that were not modified. This change filters further to lines that were added or removed. --- .github/scripts/metrics_checker.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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." From 7d4da6eb2291207aa97b819e0238ecab55b3d61d Mon Sep 17 00:00:00 2001 From: freddygv Date: Fri, 7 Oct 2022 09:34:16 -0600 Subject: [PATCH 161/172] Fixup test --- agent/consul/leader_peering_test.go | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/agent/consul/leader_peering_test.go b/agent/consul/leader_peering_test.go index 1e04f0f43a..43e7b533db 100644 --- a/agent/consul/leader_peering_test.go +++ b/agent/consul/leader_peering_test.go @@ -34,14 +34,6 @@ import ( "github.com/hashicorp/consul/types" ) -type tlsMode byte - -const ( - tlsModeNone tlsMode = iota - tlsModeManual - tlsModeAuto -) - func TestLeader_PeeringSync_Lifecycle_ClientDeletion(t *testing.T) { if testing.Short() { t.Skip("too slow for testing.Short") @@ -169,13 +161,22 @@ func TestLeader_PeeringSync_Lifecycle_UnexportWhileDown(t *testing.T) { } // Reserve a gRPC port so we can restart the accepting server with the same port. - ports := freeport.GetN(t, 1) - dialingServerPort := ports[0] + 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, + }, + } }) testrpc.WaitForLeader(t, acceptor.RPC, "dc1") @@ -207,10 +208,11 @@ func TestLeader_PeeringSync_Lifecycle_UnexportWhileDown(t *testing.T) { // 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 = "dc1" + c.Datacenter = "dc2" + c.PrimaryDatacenter = "dc2" c.GRPCPort = dialingServerPort }) - testrpc.WaitForLeader(t, dialer.RPC, "dc1") + 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) From 7851b30aadb57fbb0f35b80426ea46e821b313ae Mon Sep 17 00:00:00 2001 From: freddygv Date: Fri, 7 Oct 2022 09:54:08 -0600 Subject: [PATCH 162/172] Add changelog entry --- .changelog/14796.txt | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .changelog/14796.txt 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 +``` From 2f1845a4fad5454f6ca1a36b6d469399285e6f4b Mon Sep 17 00:00:00 2001 From: Kyle Schochenmaier Date: Fri, 7 Oct 2022 18:07:57 -0500 Subject: [PATCH 163/172] update helm docs (#14912) --- website/content/docs/k8s/helm.mdx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/website/content/docs/k8s/helm.mdx b/website/content/docs/k8s/helm.mdx index 9b63b721fa..4044106eee 100644 --- a/website/content/docs/k8s/helm.mdx +++ b/website/content/docs/k8s/helm.mdx @@ -594,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. @@ -1555,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. @@ -1874,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. From 58c8a10b98d125531f9ca3b677eeb95f816382b9 Mon Sep 17 00:00:00 2001 From: Geoffrey Grosenbach <26+topfunky@users.noreply.github.com> Date: Fri, 7 Oct 2022 16:29:38 -0700 Subject: [PATCH 164/172] Fix outdated support email address (#14907) The software delivery support email address is no longer valid. This replaces it with a link to the official support website. --- website/content/docs/enterprise/license/faq.mdx | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/website/content/docs/enterprise/license/faq.mdx b/website/content/docs/enterprise/license/faq.mdx index 7150e2fea1..5bb096d08e 100644 --- a/website/content/docs/enterprise/license/faq.mdx +++ b/website/content/docs/enterprise/license/faq.mdx @@ -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) From c0c187f1c59b7cf10ce27e911dd07eca30676b48 Mon Sep 17 00:00:00 2001 From: Paul Glass Date: Mon, 10 Oct 2022 12:40:27 -0500 Subject: [PATCH 165/172] Merge central config for GetEnvoyBootstrapParams (#14869) This fixes GetEnvoyBootstrapParams to merge in proxy-defaults and service-defaults. Co-authored-by: Dan Upton --- .changelog/14869.txt | 3 + .../merge_service_config.go | 17 +-- .../merge_service_config_test.go | 2 +- agent/consul/catalog_endpoint.go | 5 +- agent/consul/health_endpoint.go | 3 +- .../dataplane/get_envoy_bootstrap_params.go | 24 +++- .../get_envoy_bootstrap_params_test.go | 103 +++++++++++++++--- .../services/dataplane/server.go | 3 + agent/service_manager.go | 6 +- 9 files changed, 136 insertions(+), 30 deletions(-) create mode 100644 .changelog/14869.txt rename agent/{consul => configentry}/merge_service_config.go (94%) rename agent/{consul => configentry}/merge_service_config_test.go (99%) 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/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/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/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/grpc-external/services/dataplane/get_envoy_bootstrap_params.go b/agent/grpc-external/services/dataplane/get_envoy_bootstrap_params.go index 4456e361b3..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" ) @@ -73,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 230f95e818..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,18 +73,57 @@ 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) @@ -109,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, }, @@ -135,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 { 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/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 } From 7770be3d5732f419bfb031f1e77038228a35f406 Mon Sep 17 00:00:00 2001 From: cskh Date: Mon, 10 Oct 2022 14:38:04 -0400 Subject: [PATCH 166/172] docs: fix missing agent caching method (#14928) --- website/content/api-docs/catalog.mdx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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). From b0a4c5c563a4fb58d73ff4d4565a79b45ef6130e Mon Sep 17 00:00:00 2001 From: "Chris S. Kim" Date: Fri, 23 Sep 2022 17:51:41 -0400 Subject: [PATCH 167/172] Include stream-related information in peering endpoints --- agent/consul/leader_peering_test.go | 12 +- agent/consul/state/peering.go | 7 +- .../services/peerstream/stream_resources.go | 2 + .../services/peerstream/stream_tracker.go | 9 + agent/peering_endpoint_test.go | 13 +- agent/rpc/peering/service.go | 25 +- api/peering.go | 23 +- api/peering_test.go | 5 +- command/peering/list/list.go | 3 +- command/peering/read/read.go | 9 +- command/peering/read/read_test.go | 3 + proto/pbpeering/peering.gen.go | 10 +- proto/pbpeering/peering.go | 20 + proto/pbpeering/peering.pb.binary.go | 10 + proto/pbpeering/peering.pb.go | 1041 +++++++++-------- proto/pbpeering/peering.proto | 33 +- 16 files changed, 686 insertions(+), 539 deletions(-) diff --git a/agent/consul/leader_peering_test.go b/agent/consul/leader_peering_test.go index 43e7b533db..40f158a69f 100644 --- a/agent/consul/leader_peering_test.go +++ b/agent/consul/leader_peering_test.go @@ -1144,15 +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, int(resp.Peering.ImportedServiceCount)) - require.Equal(r, tc.expectedImportedServsCount, len(resp.Peering.ImportedServices)) + 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, int(resp2.Peerings[0].ImportedServiceCount)) - require.Equal(r, tc.expectedExportedServsCount, len(resp2.Peerings[0].ImportedServices)) + require.Equal(r, tc.expectedExportedServsCount, len(resp2.Peerings[0].StreamStatus.ImportedServices)) }) // Check that exported services count on S1 are what we expect @@ -1161,15 +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, int(resp.Peering.ExportedServiceCount)) - require.Equal(r, tc.expectedImportedServsCount, len(resp.Peering.ExportedServices)) + 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, int(resp2.Peerings[0].ExportedServiceCount)) - require.Equal(r, tc.expectedExportedServsCount, len(resp2.Peerings[0].ExportedServices)) + require.Equal(r, tc.expectedExportedServsCount, len(resp2.Peerings[0].StreamStatus.ExportedServices)) }) }) } diff --git a/agent/consul/state/peering.go b/agent/consul/state/peering.go index 80c23efcd8..0fbf09e7df 100644 --- a/agent/consul/state/peering.go +++ b/agent/consul/state/peering.go @@ -584,12 +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.ImportedServices = existing.ImportedServices - req.Peering.ExportedServices = existing.ExportedServices + req.Peering.StreamStatus = nil req.Peering.CreateIndex = existing.CreateIndex req.Peering.ModifyIndex = idx } else { diff --git a/agent/grpc-external/services/peerstream/stream_resources.go b/agent/grpc-external/services/peerstream/stream_resources.go index 5369812dba..ffe87dd682 100644 --- a/agent/grpc-external/services/peerstream/stream_resources.go +++ b/agent/grpc-external/services/peerstream/stream_resources.go @@ -353,6 +353,8 @@ func (s *Server) realHandleStream(streamReq HandleStreamRequest) error { if err != nil { status.TrackSendError(err.Error()) + } else { + status.TrackSendSuccess() } return err } diff --git a/agent/grpc-external/services/peerstream/stream_tracker.go b/agent/grpc-external/services/peerstream/stream_tracker.go index ccadc23c42..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 @@ -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() diff --git a/agent/peering_endpoint_test.go b/agent/peering_endpoint_test.go index c7006a9259..55722dc3e7 100644 --- a/agent/peering_endpoint_test.go +++ b/agent/peering_endpoint_test.go @@ -375,11 +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.ImportedServices)) - require.Equal(t, 0, len(apiResp.ExportedServices)) - + require.Equal(t, 0, len(apiResp.StreamStatus.ImportedServices)) + require.Equal(t, 0, len(apiResp.StreamStatus.ExportedServices)) }) t.Run("not found", func(t *testing.T) { @@ -507,10 +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.ImportedServices)) - require.Equal(t, 0, len(p.ExportedServices)) + require.Equal(t, 0, len(p.StreamStatus.ImportedServices)) + require.Equal(t, 0, len(p.StreamStatus.ExportedServices)) } }) } diff --git a/agent/rpc/peering/service.go b/agent/rpc/peering/service.go index aa3e4412ca..0c18a57845 100644 --- a/agent/rpc/peering/service.go +++ b/agent/rpc/peering/service.go @@ -645,11 +645,26 @@ func (s *Server) reconcilePeering(peering *pbpeering.Peering) *pbpeering.Peering cp.State = pbpeering.PeeringState_FAILING } - // add imported & exported services - cp.ImportedServices = streamState.ImportedServices - cp.ExportedServices = streamState.ExportedServices - 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 } diff --git a/api/peering.go b/api/peering.go index 5911311bb3..7c4d184565 100644 --- a/api/peering.go +++ b/api/peering.go @@ -62,20 +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 - // ImportedServices is the list of services imported from this peering. - ImportedServices []string - // ExportedServices is the list of services exported to this peering. - ExportedServices []string + // 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 eb58f22e40..7a45397a44 100644 --- a/api/peering_test.go +++ b/api/peering_test.go @@ -26,10 +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.ImportedServices, aPeer.ImportedServices) && - reflect.DeepEqual(peer.ExportedServices, aPeer.ExportedServices) + (reflect.DeepEqual(peer.StreamStatus, aPeer.StreamStatus)) if isEqual { return true diff --git a/command/peering/list/list.go b/command/peering/list/list.go index ac53c51db3..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, len(peer.ImportedServices), len(peer.ExportedServices), meta) + peer.Name, peer.State, len(peer.StreamStatus.ImportedServices), len(peer.StreamStatus.ExportedServices), meta) result = append(result, line) } diff --git a/command/peering/read/read.go b/command/peering/read/read.go index 66854e01c3..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", len(peering.ImportedServices))) - buffer.WriteString(fmt.Sprintf("Exported Services: %d\n", len(peering.ExportedServices))) - + 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/proto/pbpeering/peering.gen.go b/proto/pbpeering/peering.gen.go index 0164489baa..fb69f53959 100644 --- a/proto/pbpeering/peering.gen.go +++ b/proto/pbpeering/peering.gen.go @@ -76,10 +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.ImportedServices = s.ImportedServices - t.ExportedServices = s.ExportedServices + t.StreamStatus = StreamStatusToAPI(s.StreamStatus) t.CreateIndex = s.CreateIndex t.ModifyIndex = s.ModifyIndex } @@ -97,10 +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.ImportedServices = t.ImportedServices - s.ExportedServices = t.ExportedServices + 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 913f13600a..ac1a931061 100644 --- a/proto/pbpeering/peering.pb.go +++ b/proto/pbpeering/peering.pb.go @@ -320,14 +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"` - // ImportedServices is the list of services imported from this peering. - ImportedServices []string `protobuf:"bytes,15,rep,name=ImportedServices,proto3" json:"ImportedServices,omitempty"` - // ExportedServices is the list of services exported to this peering. - ExportedServices []string `protobuf:"bytes,16,rep,name=ExportedServices,proto3" json:"ExportedServices,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:"-"` @@ -438,30 +434,9 @@ func (x *Peering) GetPeerServerAddresses() []string { return nil } -func (x *Peering) GetImportedServiceCount() uint64 { +func (x *Peering) GetStreamStatus() *StreamStatus { if x != nil { - return x.ImportedServiceCount - } - return 0 -} - -func (x *Peering) GetExportedServiceCount() uint64 { - if x != nil { - return x.ExportedServiceCount - } - return 0 -} - -func (x *Peering) GetImportedServices() []string { - if x != nil { - return x.ImportedServices - } - return nil -} - -func (x *Peering) GetExportedServices() []string { - if x != nil { - return x.ExportedServices + return x.StreamStatus } return nil } @@ -480,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 @@ -508,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) } @@ -521,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 { @@ -534,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 { @@ -599,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) } @@ -612,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 { @@ -625,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 { @@ -648,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) } @@ -661,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 { @@ -674,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 { @@ -702,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) } @@ -715,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 { @@ -728,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 { @@ -750,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) } @@ -763,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 { @@ -776,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 { @@ -798,7 +858,7 @@ type PeeringListResponse struct { 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) } @@ -811,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 { @@ -824,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 { @@ -858,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) } @@ -871,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 { @@ -884,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 { @@ -918,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) } @@ -931,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 { @@ -944,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 { @@ -959,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) } @@ -972,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 { @@ -985,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 { @@ -1011,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) } @@ -1024,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 { @@ -1037,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 { @@ -1054,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) } @@ -1067,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 { @@ -1080,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 { @@ -1123,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) } @@ -1136,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 { @@ -1149,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 { @@ -1178,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) } @@ -1191,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 { @@ -1204,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 { @@ -1233,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) } @@ -1246,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 { @@ -1259,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 { @@ -1288,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) } @@ -1301,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 { @@ -1314,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 { @@ -1333,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) } @@ -1346,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 { @@ -1359,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 { @@ -1373,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) } @@ -1386,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 { @@ -1399,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 { @@ -1418,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) } @@ -1431,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 { @@ -1444,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 { @@ -1459,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) } @@ -1472,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 { @@ -1485,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 { @@ -1511,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) } @@ -1524,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 { @@ -1537,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: @@ -1565,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) } @@ -1578,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 { @@ -1591,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 { @@ -1640,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) } @@ -1653,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 { @@ -1666,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 { @@ -1699,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) } @@ -1712,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 { @@ -1725,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 { @@ -1770,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) } @@ -1783,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 { @@ -1796,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 @@ -1814,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) } @@ -1827,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 { @@ -1868,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) } @@ -1881,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 { @@ -1927,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) } @@ -1940,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 { @@ -1979,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) } @@ -1992,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 { @@ -2027,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) } @@ -2040,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 { @@ -2086,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) } @@ -2099,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 { @@ -2215,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, 0xe5, 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, @@ -2242,281 +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, 0x2a, 0x0a, 0x10, 0x49, - 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x18, - 0x0f, 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, 0x10, 0x20, 0x03, 0x28, - 0x09, 0x52, 0x10, 0x45, 0x78, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x53, 0x65, 0x72, 0x76, 0x69, - 0x63, 0x65, 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, + 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, 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, 0x0b, 0x32, 0x2a, 0x2e, 0x68, 0x61, 0x73, 0x68, + 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, 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, 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, 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, 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, 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, + 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, 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, + 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, 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, + 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, 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, + 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, 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, + 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, 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, 0x70, 0x2e, + 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, 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, + 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, 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, 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, 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, + 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, 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, 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, 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, + 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, 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, 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, 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, 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, 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, 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, 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, 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, + 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, 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 ( @@ -2532,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() } @@ -2660,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: @@ -2672,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: @@ -2684,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: @@ -2696,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: @@ -2708,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: @@ -2720,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: @@ -2732,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: @@ -2744,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: @@ -2756,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: @@ -2768,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: @@ -2780,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: @@ -2792,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: @@ -2804,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: @@ -2816,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: @@ -2828,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: @@ -2840,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: @@ -2852,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: @@ -2864,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: @@ -2876,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: @@ -2888,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: @@ -2900,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: @@ -2912,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: @@ -2924,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: @@ -2936,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: @@ -2948,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: @@ -2960,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: @@ -2972,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: @@ -2984,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: @@ -2996,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: @@ -3008,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 @@ -3032,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 a9a0ff1126..773d04c636 100644 --- a/proto/pbpeering/peering.proto +++ b/proto/pbpeering/peering.proto @@ -183,17 +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; - - // ImportedServices is the list of services imported from this peering. - repeated string ImportedServices = 15; - - // ExportedServices is the list of services exported to this peering. - repeated string ExportedServices = 16; + // 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:"-" @@ -204,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://". From 4f4112662edb860c40c0c7937c08754991209045 Mon Sep 17 00:00:00 2001 From: "Chris S. Kim" Date: Fri, 23 Sep 2022 18:44:26 -0400 Subject: [PATCH 168/172] Fix nil pointer --- agent/rpc/peering/service.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/agent/rpc/peering/service.go b/agent/rpc/peering/service.go index 0c18a57845..6f753c4853 100644 --- a/agent/rpc/peering/service.go +++ b/agent/rpc/peering/service.go @@ -632,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) From bf72df7b0ec982a6ce32edc62ff61b36c59fc621 Mon Sep 17 00:00:00 2001 From: freddygv Date: Mon, 10 Oct 2022 13:18:08 -0600 Subject: [PATCH 169/172] Fixup test --- .../services/peerstream/stream_resources.go | 12 ++++++++---- .../services/peerstream/stream_test.go | 13 +++++++++++++ 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/agent/grpc-external/services/peerstream/stream_resources.go b/agent/grpc-external/services/peerstream/stream_resources.go index ffe87dd682..ce6a5a73ed 100644 --- a/agent/grpc-external/services/peerstream/stream_resources.go +++ b/agent/grpc-external/services/peerstream/stream_resources.go @@ -351,10 +351,14 @@ func (s *Server) realHandleStream(streamReq HandleStreamRequest) error { err := streamReq.Stream.Send(msg) sendMutex.Unlock() - if err != nil { - status.TrackSendError(err.Error()) - } else { - status.TrackSendSuccess() + // 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 } diff --git a/agent/grpc-external/services/peerstream/stream_test.go b/agent/grpc-external/services/peerstream/stream_test.go index da167bd2a6..baf437daa4 100644 --- a/agent/grpc-external/services/peerstream/stream_test.go +++ b/agent/grpc-external/services/peerstream/stream_test.go @@ -572,9 +572,15 @@ 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{ Payload: &pbpeerstream.ReplicationMessage_Request_{ @@ -589,11 +595,13 @@ func TestStreamResources_Server_StreamTracker(t *testing.T) { } lastSendAck = it.FutureNow(1) + err := client.Send(ack) require.NoError(t, err) expect := Status{ Connected: true, + LastSendSuccess: lastSendSuccess, LastAck: lastSendAck, ExportedServices: []string{}, } @@ -631,6 +639,7 @@ func TestStreamResources_Server_StreamTracker(t *testing.T) { expect := Status{ Connected: true, + LastSendSuccess: lastSendSuccess, LastAck: lastSendAck, LastNack: lastNack, LastNackMessage: lastNackMsg, @@ -682,6 +691,7 @@ func TestStreamResources_Server_StreamTracker(t *testing.T) { expect := Status{ Connected: true, + LastSendSuccess: lastSendSuccess, LastAck: lastSendAck, LastNack: lastNack, LastNackMessage: lastNackMsg, @@ -737,6 +747,7 @@ func TestStreamResources_Server_StreamTracker(t *testing.T) { expect := Status{ Connected: true, + LastSendSuccess: lastSendSuccess, LastAck: lastSendAck, LastNack: lastNack, LastNackMessage: lastNackMsg, @@ -766,6 +777,7 @@ func TestStreamResources_Server_StreamTracker(t *testing.T) { expect := Status{ Connected: true, + LastSendSuccess: lastSendSuccess, LastAck: lastSendAck, LastNack: lastNack, LastNackMessage: lastNackMsg, @@ -793,6 +805,7 @@ func TestStreamResources_Server_StreamTracker(t *testing.T) { expect := Status{ Connected: false, DisconnectErrorMessage: lastRecvErrorMsg, + LastSendSuccess: lastSendSuccess, LastAck: lastSendAck, LastNack: lastNack, LastNackMessage: lastNackMsg, From b16a69d16f2a17e01534bebbd15cfda585902d71 Mon Sep 17 00:00:00 2001 From: freddygv Date: Mon, 10 Oct 2022 13:35:12 -0600 Subject: [PATCH 170/172] Add changelog entry --- .changelog/14747.txt | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .changelog/14747.txt 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 From b8bd7a305862a540a0447c3ac0d23850f8ab454e Mon Sep 17 00:00:00 2001 From: James Oulman Date: Mon, 10 Oct 2022 16:13:56 -0400 Subject: [PATCH 171/172] Configure Envoy alpn_protocols based on service protocol (#14356) * Configure Envoy alpn_protocols based on service protocol * define alpnProtocols in a more standard way * http2 protocol should be h2 only * formatting * add test for getAlpnProtocol() * create changelog entry * change scope is connect-proxy * ignore errors on ParseProxyConfig; fixes linter * add tests for grpc and http2 public listeners * remove newlines from PR * Add alpn_protocol configuration for ingress gateway * Guard against nil tlsContext * add ingress gateway w/ TLS tests for gRPC and HTTP2 * getAlpnProtocols: add TCP protocol test * add tests for ingress gateway with grpc/http2 and per-listener TLS config * add tests for ingress gateway with grpc/http2 and per-listener TLS config * add Gateway level TLS config with mixed protocol listeners to validate ALPN * update changelog to include ingress-gateway * add http/1.1 to http2 ALPN * go fmt * fix test on custom-trace-listener --- .changelog/14356.txt | 1 + agent/proxycfg/testing_ingress_gateway.go | 320 ++++++++++++++++++ agent/xds/listeners.go | 22 ++ agent/xds/listeners_ingress.go | 11 +- agent/xds/listeners_test.go | 69 ++++ ...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 + 25 files changed, 1318 insertions(+), 5 deletions(-) create mode 100644 .changelog/14356.txt 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 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/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/xds/listeners.go b/agent/xds/listeners.go index 3a35431926..4c23d07059 100644 --- a/agent/xds/listeners.go +++ b/agent/xds/listeners.go @@ -1073,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: @@ -1081,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(), @@ -1088,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( 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 ab67cc6933..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,14 @@ 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 { @@ -772,6 +790,22 @@ 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, @@ -1178,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/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" }, From 2bb28467905ee5d2329ed72e3210f57c75c820e5 Mon Sep 17 00:00:00 2001 From: Mariano Asselborn Date: Tue, 11 Oct 2022 10:27:06 -0400 Subject: [PATCH 172/172] Enable ironbank integration (#14931) --- .release/ci.hcl | 13 +++++++++++++ 1 file changed, 13 insertions(+) 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" + } +}