diff --git a/.changelog/17160.txt b/.changelog/17160.txt new file mode 100644 index 000000000000..666a6e8f252c --- /dev/null +++ b/.changelog/17160.txt @@ -0,0 +1,3 @@ +```release-note:bug +Fix a bug that wrongly trims domains when there is an overlap with DC name. +``` diff --git a/.changelog/17780.txt b/.changelog/17780.txt new file mode 100644 index 000000000000..b90925a8b9fd --- /dev/null +++ b/.changelog/17780.txt @@ -0,0 +1,3 @@ +```release-note:feature +cli: `consul watch` command uses `-filter` expression to filter response from checks, services, nodes, and service. +``` diff --git a/.github/workflows/oss-merge-trigger.yml b/.github/workflows/oss-merge-trigger.yml index 4a4fdaa208e3..9146f7bc2214 100644 --- a/.github/workflows/oss-merge-trigger.yml +++ b/.github/workflows/oss-merge-trigger.yml @@ -8,7 +8,7 @@ on: - closed branches: - main - - 'release/*.*.x' + - release/** jobs: trigger-oss-merge: @@ -26,4 +26,4 @@ jobs: curl -H "Authorization: token $GH_PAT" \ -H 'Accept: application/json' \ -d "{\"event_type\": \"oss-merge\", \"client_payload\": {\"git-ref\": \"${GIT_REF}\", \"git-sha\": \"${GIT_SHA}\", \"git-actor\": \"${GIT_ACTOR}\" }}" \ - "https://api.github.com/repos/hashicorp/consul-enterprise/dispatches" \ No newline at end of file + "https://api.github.com/repos/hashicorp/consul-enterprise/dispatches" diff --git a/CHANGELOG.md b/CHANGELOG.md index ef4edc700404..dc70d8b08abf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,97 @@ +## 1.15.4 (June 26, 2023) +FEATURES: + +* cli: `consul operator raft list-peers` command shows the number of commits each follower is trailing the leader by to aid in troubleshooting. [[GH-17582](https://github.com/hashicorp/consul/issues/17582)] +* server: **(Enterprise Only)** allow automatic license utilization reporting. [[GH-5102](https://github.com/hashicorp/consul/issues/5102)] + +IMPROVEMENTS: + +* connect: update supported envoy versions to 1.22.11, 1.23.9, 1.24.7, 1.25.6 [[GH-17545](https://github.com/hashicorp/consul/issues/17545)] +* debug: change default setting of consul debug command. now default duration is 5ms and default log level is 'TRACE' [[GH-17596](https://github.com/hashicorp/consul/issues/17596)] +* fix metric names in /docs/agent/telemetry [[GH-17577](https://github.com/hashicorp/consul/issues/17577)] +* gateway: Change status condition reason for invalid certificate on a listener from "Accepted" to "ResolvedRefs". [[GH-17115](https://github.com/hashicorp/consul/issues/17115)] +* systemd: set service type to notify. [[GH-16845](https://github.com/hashicorp/consul/issues/16845)] + +BUG FIXES: + +* cache: fix a few minor goroutine leaks in leaf certs and the agent cache [[GH-17636](https://github.com/hashicorp/consul/issues/17636)] +* docs: fix list of telemetry metrics [[GH-17593](https://github.com/hashicorp/consul/issues/17593)] +* gateways: **(Enterprise only)** Fixed a bug in API gateways where gateway configuration objects in non-default partitions did not reconcile properly. [[GH-17581](https://github.com/hashicorp/consul/issues/17581)] +* gateways: Fixed a bug in API gateways where binding a route that only targets a service imported from a peer results + in the programmed gateway having no routes. [[GH-17609](https://github.com/hashicorp/consul/issues/17609)] +* gateways: Fixed a bug where API gateways were not being taken into account in determining xDS rate limits. [[GH-17631](https://github.com/hashicorp/consul/issues/17631)] +* http: fixed API endpoint `PUT /acl/token/:AccessorID` (update token), no longer requires `AccessorID` in the request body. Web UI can now update tokens. [[GH-17739](https://github.com/hashicorp/consul/issues/17739)] +* namespaces: **(Enterprise only)** fixes a bug where agent health checks stop syncing for all services on a node if the namespace of any service has been removed from the server. +* namespaces: **(Enterprise only)** fixes a bug where namespaces are stuck in a deferred deletion state indefinitely under some conditions. + Also fixes the Consul query metadata present in the HTTP headers of the namespace read and list endpoints. +* peering: Fix a bug that caused server agents to continue cleaning up peering resources even after loss of leadership. [[GH-17483](https://github.com/hashicorp/consul/issues/17483)] +* xds: Fixed a bug where modifying ACLs on a token being actively used for an xDS connection caused all xDS updates to fail. [[GH-17566](https://github.com/hashicorp/consul/issues/17566)] + +## 1.14.8 (June 26, 2023) + +SECURITY: + +* Update to UBI base image to 9.2. [[GH-17513](https://github.com/hashicorp/consul/issues/17513)] + +FEATURES: + +* cli: `consul operator raft list-peers` command shows the number of commits each follower is trailing the leader by to aid in troubleshooting. [[GH-17582](https://github.com/hashicorp/consul/issues/17582)] +* server: **(Enterprise Only)** allow automatic license utilization reporting. [[GH-5102](https://github.com/hashicorp/consul/issues/5102)] + +IMPROVEMENTS: + +* connect: update supported envoy versions to 1.21.6, 1.22.11, 1.23.9, 1.24.7 [[GH-17547](https://github.com/hashicorp/consul/issues/17547)] +* debug: change default setting of consul debug command. now default duration is 5ms and default log level is 'TRACE' [[GH-17596](https://github.com/hashicorp/consul/issues/17596)] +* fix metric names in /docs/agent/telemetry [[GH-17577](https://github.com/hashicorp/consul/issues/17577)] +* peering: gRPC queries for TrustBundleList, TrustBundleRead, PeeringList, and PeeringRead now support blocking semantics, + reducing network and CPU demand. + The HTTP APIs for Peering List and Read have been updated to support blocking. [[GH-17426](https://github.com/hashicorp/consul/issues/17426)] +* raft: Remove expensive reflection from raft/mesh hot path [[GH-16552](https://github.com/hashicorp/consul/issues/16552)] +* systemd: set service type to notify. [[GH-16845](https://github.com/hashicorp/consul/issues/16845)] + +BUG FIXES: + +* cache: fix a few minor goroutine leaks in leaf certs and the agent cache [[GH-17636](https://github.com/hashicorp/consul/issues/17636)] +* connect: reverts #17317 fix that caused a downstream error for Ingress/Mesh/Terminating GWs when their respective config entry does not already exist. [[GH-17541](https://github.com/hashicorp/consul/issues/17541)] +* namespaces: **(Enterprise only)** fixes a bug where agent health checks stop syncing for all services on a node if the namespace of any service has been removed from the server. +* namespaces: **(Enterprise only)** fixes a bug where namespaces are stuck in a deferred deletion state indefinitely under some conditions. + Also fixes the Consul query metadata present in the HTTP headers of the namespace read and list endpoints. +* namespaces: adjusts the return type from HTTP list API to return the `api` module representation of a namespace. + This fixes an error with the `consul namespace list` command when a namespace has a deferred deletion timestamp. +* peering: Fix a bug that caused server agents to continue cleaning up peering resources even after loss of leadership. [[GH-17483](https://github.com/hashicorp/consul/issues/17483)] +* peering: Fix issue where modifying the list of exported services did not correctly replicate changes for services that exist in a non-default namespace. [[GH-17456](https://github.com/hashicorp/consul/issues/17456)] + +## 1.13.9 (June 26, 2023) +BREAKING CHANGES: + +* connect: Disable peering by default in connect proxies for Consul 1.13. This change was made to prevent inefficient polling + queries from having a negative impact on server performance. Peering in Consul 1.13 is an experimental feature and is not + recommended for use in production environments. If you still wish to use the experimental peering feature, ensure + [`peering.enabled = true`](https://developer.hashicorp.com/consul/docs/v1.13.x/agent/config/config-files#peering_enabled) + is set on all clients and servers. [[GH-17731](https://github.com/hashicorp/consul/issues/17731)] + +SECURITY: + +* Update to UBI base image to 9.2. [[GH-17513](https://github.com/hashicorp/consul/issues/17513)] + +FEATURES: + +* server: **(Enterprise Only)** allow automatic license utilization reporting. [[GH-5102](https://github.com/hashicorp/consul/issues/5102)] + +IMPROVEMENTS: + +* debug: change default setting of consul debug command. now default duration is 5ms and default log level is 'TRACE' [[GH-17596](https://github.com/hashicorp/consul/issues/17596)] +* systemd: set service type to notify. [[GH-16845](https://github.com/hashicorp/consul/issues/16845)] + +BUG FIXES: + +* cache: fix a few minor goroutine leaks in leaf certs and the agent cache [[GH-17636](https://github.com/hashicorp/consul/issues/17636)] +* namespaces: **(Enterprise only)** fixes a bug where namespaces are stuck in a deferred deletion state indefinitely under some conditions. + Also fixes the Consul query metadata present in the HTTP headers of the namespace read and list endpoints. +* namespaces: adjusts the return type from HTTP list API to return the `api` module representation of a namespace. + This fixes an error with the `consul namespace list` command when a namespace has a deferred deletion timestamp. +* peering: Fix a bug that caused server agents to continue cleaning up peering resources even after loss of leadership. [[GH-17483](https://github.com/hashicorp/consul/issues/17483)] + ## 1.16.0-rc1 (June 12, 2023) BREAKING CHANGES: diff --git a/agent/consul/health_endpoint_test.go b/agent/consul/health_endpoint_test.go index cd37b5ec4c9b..21a83ea90db2 100644 --- a/agent/consul/health_endpoint_test.go +++ b/agent/consul/health_endpoint_test.go @@ -1767,5 +1767,11 @@ func TestHealth_RPC_Filter(t *testing.T) { out = new(structs.IndexedHealthChecks) require.NoError(t, msgpackrpc.CallWithCodec(codec, "Health.ChecksInState", &args, out)) require.Len(t, out.HealthChecks, 1) + + args.State = api.HealthAny + args.Filter = "connect in ServiceTags and v2 in ServiceTags" + out = new(structs.IndexedHealthChecks) + require.NoError(t, msgpackrpc.CallWithCodec(codec, "Health.ChecksInState", &args, out)) + require.Len(t, out.HealthChecks, 1) }) } diff --git a/agent/dns.go b/agent/dns.go index cb1e3c310d0c..5804dc97dd8e 100644 --- a/agent/dns.go +++ b/agent/dns.go @@ -1055,7 +1055,7 @@ func (d *DNSServer) trimDomain(query string) string { longer, shorter = shorter, longer } - if strings.HasSuffix(query, longer) { + if strings.HasSuffix(query, "."+strings.TrimLeft(longer, ".")) { return strings.TrimSuffix(query, longer) } return strings.TrimSuffix(query, shorter) diff --git a/agent/dns_test.go b/agent/dns_test.go index 46a7e758c7f1..ef5364964dd3 100644 --- a/agent/dns_test.go +++ b/agent/dns_test.go @@ -7071,6 +7071,45 @@ func TestDNS_AltDomains_Overlap(t *testing.T) { } } +func TestDNS_AltDomain_DCName_Overlap(t *testing.T) { + if testing.Short() { + t.Skip("too slow for testing.Short") + } + + // this tests the DC name overlap with the consul domain/alt-domain + // we should get response when DC suffix is a prefix of consul alt-domain + t.Parallel() + a := NewTestAgent(t, ` + datacenter = "dc-test" + node_name = "test-node" + alt_domain = "test.consul." + `) + defer a.Shutdown() + testrpc.WaitForLeader(t, a.RPC, "dc-test") + + questions := []string{ + "test-node.node.dc-test.consul.", + "test-node.node.dc-test.test.consul.", + } + + for _, question := range questions { + m := new(dns.Msg) + m.SetQuestion(question, dns.TypeA) + + c := new(dns.Client) + in, _, err := c.Exchange(m, a.DNSAddr()) + if err != nil { + t.Fatalf("err: %v", err) + } + + require.Len(t, in.Answer, 1) + + aRec, ok := in.Answer[0].(*dns.A) + require.True(t, ok) + require.Equal(t, aRec.A.To4().String(), "127.0.0.1") + } +} + func TestDNS_PreparedQuery_AllowStale(t *testing.T) { if testing.Short() { t.Skip("too slow for testing.Short") diff --git a/agent/grpc-external/services/resource/list_by_owner_test.go b/agent/grpc-external/services/resource/list_by_owner_test.go index 19fe799caf08..218971a050da 100644 --- a/agent/grpc-external/services/resource/list_by_owner_test.go +++ b/agent/grpc-external/services/resource/list_by_owner_test.go @@ -74,7 +74,7 @@ func TestListByOwner_TypeNotRegistered(t *testing.T) { }) require.Error(t, err) require.Equal(t, codes.InvalidArgument.String(), status.Code(err).String()) - require.Contains(t, err.Error(), "resource type demo.v2.artist not registered") + require.Contains(t, err.Error(), "resource type demo.v2.Artist not registered") } func TestListByOwner_Empty(t *testing.T) { @@ -126,7 +126,7 @@ func TestListByOwner_Many(t *testing.T) { } func TestListByOwner_ACL_PerTypeDenied(t *testing.T) { - authz := AuthorizerFrom(t, `key_prefix "resource/demo.v2.album/" { policy = "deny" }`) + authz := AuthorizerFrom(t, `key_prefix "resource/demo.v2.Album/" { policy = "deny" }`) _, rsp, err := roundTripListByOwner(t, authz) // verify resource filtered out, hence no results @@ -135,7 +135,7 @@ func TestListByOwner_ACL_PerTypeDenied(t *testing.T) { } func TestListByOwner_ACL_PerTypeAllowed(t *testing.T) { - authz := AuthorizerFrom(t, `key_prefix "resource/demo.v2.album/" { policy = "read" }`) + authz := AuthorizerFrom(t, `key_prefix "resource/demo.v2.Album/" { policy = "read" }`) album, rsp, err := roundTripListByOwner(t, authz) // verify resource not filtered out diff --git a/agent/grpc-external/services/resource/list_test.go b/agent/grpc-external/services/resource/list_test.go index 7d102b090ce0..4d6b50951b75 100644 --- a/agent/grpc-external/services/resource/list_test.go +++ b/agent/grpc-external/services/resource/list_test.go @@ -58,7 +58,7 @@ func TestList_TypeNotFound(t *testing.T) { }) require.Error(t, err) require.Equal(t, codes.InvalidArgument.String(), status.Code(err).String()) - require.Contains(t, err.Error(), "resource type demo.v2.artist not registered") + require.Contains(t, err.Error(), "resource type demo.v2.Artist not registered") } func TestList_Empty(t *testing.T) { @@ -178,7 +178,7 @@ func TestList_ACL_ListAllowed_ReadDenied(t *testing.T) { // allow list, deny read authz := AuthorizerFrom(t, demo.ArtistV2ListPolicy, - `key_prefix "resource/demo.v2.artist/" { policy = "deny" }`) + `key_prefix "resource/demo.v2.Artist/" { policy = "deny" }`) _, rsp, err := roundTripList(t, authz) // verify resource filtered out by key:read denied hence no results diff --git a/agent/grpc-external/services/resource/read_test.go b/agent/grpc-external/services/resource/read_test.go index 237895eacc55..cca911ec15b5 100644 --- a/agent/grpc-external/services/resource/read_test.go +++ b/agent/grpc-external/services/resource/read_test.go @@ -71,7 +71,7 @@ func TestRead_TypeNotFound(t *testing.T) { _, err = client.Read(context.Background(), &pbresource.ReadRequest{Id: artist.Id}) require.Error(t, err) require.Equal(t, codes.InvalidArgument.String(), status.Code(err).String()) - require.Contains(t, err.Error(), "resource type demo.v2.artist not registered") + require.Contains(t, err.Error(), "resource type demo.v2.Artist not registered") } func TestRead_ResourceNotFound(t *testing.T) { diff --git a/agent/grpc-external/services/resource/watch_test.go b/agent/grpc-external/services/resource/watch_test.go index 687fe0d0679f..95695f295ebd 100644 --- a/agent/grpc-external/services/resource/watch_test.go +++ b/agent/grpc-external/services/resource/watch_test.go @@ -66,7 +66,7 @@ func TestWatchList_TypeNotFound(t *testing.T) { err = mustGetError(t, rspCh) require.Equal(t, codes.InvalidArgument.String(), status.Code(err).String()) - require.Contains(t, err.Error(), "resource type demo.v2.artist not registered") + require.Contains(t, err.Error(), "resource type demo.v2.Artist not registered") } func TestWatchList_GroupVersionMatches(t *testing.T) { @@ -172,7 +172,7 @@ func TestWatchList_ACL_ListAllowed_ReadDenied(t *testing.T) { // allow list, deny read authz := AuthorizerFrom(t, ` key_prefix "resource/" { policy = "list" } - key_prefix "resource/demo.v2.artist/" { policy = "deny" } + key_prefix "resource/demo.v2.Artist/" { policy = "deny" } `) rspCh, _ := roundTripACL(t, authz) @@ -187,7 +187,7 @@ func TestWatchList_ACL_ListAllowed_ReadAllowed(t *testing.T) { // allow list, allow read authz := AuthorizerFrom(t, ` key_prefix "resource/" { policy = "list" } - key_prefix "resource/demo.v2.artist/" { policy = "read" } + key_prefix "resource/demo.v2.Artist/" { policy = "read" } `) rspCh, artist := roundTripACL(t, authz) diff --git a/agent/grpc-external/services/resource/write_status_test.go b/agent/grpc-external/services/resource/write_status_test.go index f65c7918ff79..aa26330176df 100644 --- a/agent/grpc-external/services/resource/write_status_test.go +++ b/agent/grpc-external/services/resource/write_status_test.go @@ -180,7 +180,7 @@ func TestWriteStatus_TypeNotFound(t *testing.T) { _, err = client.WriteStatus(testContext(t), validWriteStatusRequest(t, res)) require.Error(t, err) require.Equal(t, codes.InvalidArgument.String(), status.Code(err).String()) - require.Contains(t, err.Error(), "resource type demo.v2.artist not registered") + require.Contains(t, err.Error(), "resource type demo.v2.Artist not registered") } func TestWriteStatus_ResourceNotFound(t *testing.T) { diff --git a/agent/grpc-external/services/resource/write_test.go b/agent/grpc-external/services/resource/write_test.go index 3da4ec478a52..4ec25ee26c0c 100644 --- a/agent/grpc-external/services/resource/write_test.go +++ b/agent/grpc-external/services/resource/write_test.go @@ -151,7 +151,7 @@ func TestWrite_TypeNotFound(t *testing.T) { _, err = client.Write(testContext(t), &pbresource.WriteRequest{Resource: res}) require.Error(t, err) require.Equal(t, codes.InvalidArgument.String(), status.Code(err).String()) - require.Contains(t, err.Error(), "resource type demo.v2.artist not registered") + require.Contains(t, err.Error(), "resource type demo.v2.Artist not registered") } func TestWrite_ACLs(t *testing.T) { diff --git a/api/watch/funcs.go b/api/watch/funcs.go index 3c057aa66421..0d0f6e100c2b 100644 --- a/api/watch/funcs.go +++ b/api/watch/funcs.go @@ -92,13 +92,20 @@ func keyPrefixWatch(params map[string]interface{}) (WatcherFunc, error) { // servicesWatch is used to watch the list of available services func servicesWatch(params map[string]interface{}) (WatcherFunc, error) { stale := false + filter := "" if err := assignValueBool(params, "stale", &stale); err != nil { return nil, err } + if err := assignValue(params, "filter", &filter); err != nil { + return nil, err + } fn := func(p *Plan) (BlockingParamVal, interface{}, error) { catalog := p.client.Catalog() opts := makeQueryOptionsWithContext(p, stale) + if filter != "" { + opts.Filter = filter + } defer p.cancelFunc() services, meta, err := catalog.Services(&opts) if err != nil { @@ -112,13 +119,20 @@ func servicesWatch(params map[string]interface{}) (WatcherFunc, error) { // nodesWatch is used to watch the list of available nodes func nodesWatch(params map[string]interface{}) (WatcherFunc, error) { stale := false + filter := "" if err := assignValueBool(params, "stale", &stale); err != nil { return nil, err } + if err := assignValue(params, "filter", &filter); err != nil { + return nil, err + } fn := func(p *Plan) (BlockingParamVal, interface{}, error) { catalog := p.client.Catalog() opts := makeQueryOptionsWithContext(p, stale) + if filter != "" { + opts.Filter = filter + } defer p.cancelFunc() nodes, meta, err := catalog.Nodes(&opts) if err != nil { @@ -132,9 +146,13 @@ func nodesWatch(params map[string]interface{}) (WatcherFunc, error) { // serviceWatch is used to watch a specific service for changes func serviceWatch(params map[string]interface{}) (WatcherFunc, error) { stale := false + filter := "" if err := assignValueBool(params, "stale", &stale); err != nil { return nil, err } + if err := assignValue(params, "filter", &filter); err != nil { + return nil, err + } var ( service string @@ -158,6 +176,9 @@ func serviceWatch(params map[string]interface{}) (WatcherFunc, error) { fn := func(p *Plan) (BlockingParamVal, interface{}, error) { health := p.client.Health() opts := makeQueryOptionsWithContext(p, stale) + if filter != "" { + opts.Filter = filter + } defer p.cancelFunc() nodes, meta, err := health.ServiceMultipleTags(service, tags, passingOnly, &opts) if err != nil { @@ -175,13 +196,16 @@ func checksWatch(params map[string]interface{}) (WatcherFunc, error) { return nil, err } - var service, state string + var service, state, filter string if err := assignValue(params, "service", &service); err != nil { return nil, err } if err := assignValue(params, "state", &state); err != nil { return nil, err } + if err := assignValue(params, "filter", &filter); err != nil { + return nil, err + } if service != "" && state != "" { return nil, fmt.Errorf("Cannot specify service and state") } @@ -196,6 +220,9 @@ func checksWatch(params map[string]interface{}) (WatcherFunc, error) { var checks []*consulapi.HealthCheck var meta *consulapi.QueryMeta var err error + if filter != "" { + opts.Filter = filter + } if state != "" { checks, meta, err = health.State(state, &opts) } else { diff --git a/api/watch/funcs_test.go b/api/watch/funcs_test.go index d972def6ac7d..4bd79a59c14f 100644 --- a/api/watch/funcs_test.go +++ b/api/watch/funcs_test.go @@ -378,6 +378,82 @@ func TestServicesWatch(t *testing.T) { } +func TestServicesWatch_Filter(t *testing.T) { + t.Parallel() + c, s := makeClient(t) + defer s.Stop() + + s.WaitForSerfCheck(t) + + var ( + wakeups []map[string][]string + notifyCh = make(chan struct{}) + ) + + plan := mustParse(t, `{"type":"services", "filter":"b in ServiceTags and a in ServiceTags"}`) + plan.Handler = func(idx uint64, raw interface{}) { + if raw == nil { + return // ignore + } + v, ok := raw.(map[string][]string) + if !ok { + return // ignore + } + wakeups = append(wakeups, v) + notifyCh <- struct{}{} + } + + // Register some services + { + agent := c.Agent() + + // we don't want to find this + reg := &api.AgentServiceRegistration{ + ID: "foo", + Name: "foo", + Tags: []string{"b"}, + } + if err := agent.ServiceRegister(reg); err != nil { + t.Fatalf("err: %v", err) + } + + // // we want to find this + reg = &api.AgentServiceRegistration{ + ID: "bar", + Name: "bar", + Tags: []string{"a", "b"}, + } + if err := agent.ServiceRegister(reg); err != nil { + t.Fatalf("err: %v", err) + } + } + + var wg sync.WaitGroup + wg.Add(1) + go func() { + defer wg.Done() + if err := plan.Run(s.HTTPAddr); err != nil { + t.Errorf("err: %v", err) + } + }() + defer plan.Stop() + + // Wait for second wakeup. + <-notifyCh + + plan.Stop() + wg.Wait() + + require.Len(t, wakeups, 1) + + { + v := wakeups[0] + require.Len(t, v, 1) + _, ok := v["bar"] + require.True(t, ok) + } +} + func TestNodesWatch(t *testing.T) { t.Parallel() c, s := makeClient(t) @@ -453,6 +529,82 @@ func TestNodesWatch(t *testing.T) { } } +func TestNodesWatch_Filter(t *testing.T) { + t.Parallel() + c, s := makeClient(t) + defer s.Stop() + + s.WaitForSerfCheck(t) // wait for AE to sync + + var ( + wakeups [][]*api.Node + notifyCh = make(chan struct{}) + ) + + plan := mustParse(t, `{"type":"nodes", "filter":"Node == foo"}`) + plan.Handler = func(idx uint64, raw interface{}) { + if raw == nil { + return // ignore + } + v, ok := raw.([]*api.Node) + if !ok { + return // ignore + } + wakeups = append(wakeups, v) + notifyCh <- struct{}{} + } + + // Register 2 nodes + { + catalog := c.Catalog() + + // we want to find this node + reg := &api.CatalogRegistration{ + Node: "foo", + Address: "1.1.1.1", + Datacenter: "dc1", + } + if _, err := catalog.Register(reg, nil); err != nil { + t.Fatalf("err: %v", err) + } + + // we don't want to find this node + reg = &api.CatalogRegistration{ + Node: "bar", + Address: "2.2.2.2", + Datacenter: "dc1", + } + if _, err := catalog.Register(reg, nil); err != nil { + t.Fatalf("err: %v", err) + } + } + + var wg sync.WaitGroup + wg.Add(1) + // Start the watch nodes plan + go func() { + defer wg.Done() + if err := plan.Run(s.HTTPAddr); err != nil { + t.Errorf("err: %v", err) + } + }() + defer plan.Stop() + + // Wait for first wakeup. + <-notifyCh + + plan.Stop() + wg.Wait() + + require.Len(t, wakeups, 1) + + { + v := wakeups[0] + require.Len(t, v, 1) + require.Equal(t, "foo", v[0].Node) + } +} + func TestServiceWatch(t *testing.T) { t.Parallel() c, s := makeClient(t) @@ -616,6 +768,94 @@ func TestServiceMultipleTagsWatch(t *testing.T) { } } +func TestServiceWatch_Filter(t *testing.T) { + t.Parallel() + c, s := makeClient(t) + defer s.Stop() + + s.WaitForSerfCheck(t) + + var ( + wakeups [][]*api.ServiceEntry + notifyCh = make(chan struct{}) + ) + + plan := mustParse(t, `{"type":"service", "service":"foo", "filter":"bar in Service.Tags and buzz in Service.Tags"}`) + plan.Handler = func(idx uint64, raw interface{}) { + if raw == nil { + return // ignore + } + v, ok := raw.([]*api.ServiceEntry) + if !ok { + return // ignore + } + + wakeups = append(wakeups, v) + notifyCh <- struct{}{} + } + + // register some services + { + agent := c.Agent() + + // we do not want to find this one. + reg := &api.AgentServiceRegistration{ + ID: "foobarbiff", + Name: "foo", + Tags: []string{"bar", "biff"}, + } + if err := agent.ServiceRegister(reg); err != nil { + t.Fatalf("err: %v", err) + } + + // we do not want to find this one. + reg = &api.AgentServiceRegistration{ + ID: "foobuzzbiff", + Name: "foo", + Tags: []string{"buzz", "biff"}, + } + if err := agent.ServiceRegister(reg); err != nil { + t.Fatalf("err: %v", err) + } + + // we want to find this one + reg = &api.AgentServiceRegistration{ + ID: "foobarbuzzbiff", + Name: "foo", + Tags: []string{"bar", "buzz", "biff"}, + } + if err := agent.ServiceRegister(reg); err != nil { + t.Fatalf("err: %v", err) + } + } + + var wg sync.WaitGroup + wg.Add(1) + go func() { + defer wg.Done() + if err := plan.Run(s.HTTPAddr); err != nil { + t.Errorf("err: %v", err) + } + }() + defer plan.Stop() + + // Wait for second wakeup. + <-notifyCh + + plan.Stop() + wg.Wait() + + require.Len(t, wakeups, 1) + + { + v := wakeups[0] + require.Len(t, v, 1) + + require.Equal(t, "foobarbuzzbiff", v[0].Service.ID) + require.ElementsMatch(t, []string{"bar", "buzz", "biff"}, v[0].Service.Tags) + } +} + func TestChecksWatch_State(t *testing.T) { t.Parallel() c, s := makeClient(t) @@ -772,6 +1012,294 @@ func TestChecksWatch_Service(t *testing.T) { } } +func TestChecksWatch_Service_Filter(t *testing.T) { + t.Parallel() + c, s := makeClient(t) + defer s.Stop() + + s.WaitForSerfCheck(t) + + var ( + wakeups [][]*api.HealthCheck + notifyCh = make(chan struct{}) + ) + + plan := mustParse(t, `{"type":"checks", "filter":"b in ServiceTags and a in ServiceTags"}`) + plan.Handler = func(idx uint64, raw interface{}) { + if raw == nil { + return // ignore + } + v, ok := raw.([]*api.HealthCheck) + if !ok { + return // ignore + } + wakeups = append(wakeups, v) + notifyCh <- struct{}{} + } + + var wg sync.WaitGroup + wg.Add(1) + go func() { + defer wg.Done() + if err := plan.Run(s.HTTPAddr); err != nil { + t.Errorf("err: %v", err) + } + }() + defer plan.Stop() + + // Wait for first wakeup. + <-notifyCh + { + catalog := c.Catalog() + reg := &api.CatalogRegistration{ + Node: "foobar", + Address: "1.1.1.1", + Datacenter: "dc1", + Service: &api.AgentService{ + ID: "foobar", + Service: "foobar", + Tags: []string{"a", "b"}, + }, + Check: &api.AgentCheck{ + Node: "foobar", + CheckID: "foobar", + Name: "foobar", + Status: api.HealthPassing, + ServiceID: "foobar", + }, + } + if _, err := catalog.Register(reg, nil); err != nil { + t.Fatalf("err: %v", err) + } + } + + // Wait for second wakeup. + <-notifyCh + + plan.Stop() + wg.Wait() + + require.Len(t, wakeups, 2) + + { + v := wakeups[0] + require.Len(t, v, 0) + } + { + v := wakeups[1] + require.Len(t, v, 1) + require.Equal(t, "foobar", v[0].CheckID) + } +} + +func TestChecksWatch_Filter(t *testing.T) { + t.Parallel() + c, s := makeClient(t) + defer s.Stop() + + s.WaitForSerfCheck(t) + + var ( + wakeups [][]*api.HealthCheck + notifyCh = make(chan struct{}) + ) + + plan := mustParse(t, `{"type":"checks", "filter":"b in ServiceTags and a in ServiceTags"}`) + plan.Handler = func(idx uint64, raw interface{}) { + if raw == nil { + return // ignore + } + v, ok := raw.([]*api.HealthCheck) + if !ok { + return // ignore + } + wakeups = append(wakeups, v) + notifyCh <- struct{}{} + } + + var wg sync.WaitGroup + wg.Add(1) + go func() { + defer wg.Done() + if err := plan.Run(s.HTTPAddr); err != nil { + t.Errorf("err: %v", err) + } + }() + defer plan.Stop() + + // Wait for first wakeup. + <-notifyCh + { + catalog := c.Catalog() + + // we don't want to find this one + reg := &api.CatalogRegistration{ + Node: "foo", + Address: "1.1.1.1", + Datacenter: "dc1", + Service: &api.AgentService{ + ID: "foo", + Service: "foo", + Tags: []string{"a"}, + }, + Check: &api.AgentCheck{ + Node: "foo", + CheckID: "foo", + Name: "foo", + Status: api.HealthPassing, + ServiceID: "foo", + }, + } + if _, err := catalog.Register(reg, nil); err != nil { + t.Fatalf("err: %v", err) + } + + // we want to find this one + reg = &api.CatalogRegistration{ + Node: "bar", + Address: "2.2.2.2", + Datacenter: "dc1", + Service: &api.AgentService{ + ID: "bar", + Service: "bar", + Tags: []string{"a", "b"}, + }, + Check: &api.AgentCheck{ + Node: "bar", + CheckID: "bar", + Name: "bar", + Status: api.HealthPassing, + ServiceID: "bar", + }, + } + if _, err := catalog.Register(reg, nil); err != nil { + t.Fatalf("err: %v", err) + } + } + + // Wait for second wakeup. + <-notifyCh + + plan.Stop() + wg.Wait() + + require.Len(t, wakeups, 2) + + { + v := wakeups[0] + require.Len(t, v, 0) + } + { + v := wakeups[1] + require.Len(t, v, 1) + require.Equal(t, "bar", v[0].CheckID) + } +} + +func TestChecksWatch_Filter_by_ServiceNameStatus(t *testing.T) { + t.Parallel() + c, s := makeClient(t) + defer s.Stop() + + s.WaitForSerfCheck(t) + + var ( + wakeups [][]*api.HealthCheck + notifyCh = make(chan struct{}) + ) + + plan := mustParse(t, `{"type":"checks", "filter":"ServiceName == bar and Status == critical"}`) + plan.Handler = func(idx uint64, raw interface{}) { + if raw == nil { + return // ignore + } + v, ok := raw.([]*api.HealthCheck) + if !ok { + return // ignore + } + wakeups = append(wakeups, v) + notifyCh <- struct{}{} + } + + var wg sync.WaitGroup + wg.Add(1) + go func() { + defer wg.Done() + if err := plan.Run(s.HTTPAddr); err != nil { + t.Errorf("err: %v", err) + } + }() + defer plan.Stop() + + // Wait for first wakeup. + <-notifyCh + { + catalog := c.Catalog() + + // we don't want to find this one + reg := &api.CatalogRegistration{ + Node: "foo", + Address: "1.1.1.1", + Datacenter: "dc1", + Service: &api.AgentService{ + ID: "foo", + Service: "foo", + Tags: []string{"a"}, + }, + Check: &api.AgentCheck{ + Node: "foo", + CheckID: "foo", + Name: "foo", + Status: api.HealthPassing, + ServiceID: "foo", + }, + } + if _, err := catalog.Register(reg, nil); err != nil { + t.Fatalf("err: %v", err) + } + + // we want to find this one + reg = &api.CatalogRegistration{ + Node: "bar", + Address: "2.2.2.2", + Datacenter: "dc1", + Service: &api.AgentService{ + ID: "bar", + Service: "bar", + Tags: []string{"a", "b"}, + }, + Check: &api.AgentCheck{ + Node: "bar", + CheckID: "bar", + Name: "bar", + Status: api.HealthCritical, + ServiceID: "bar", + }, + } + if _, err := catalog.Register(reg, nil); err != nil { + t.Fatalf("err: %v", err) + } + } + + // Wait for second wakeup. + <-notifyCh + + plan.Stop() + wg.Wait() + + require.Len(t, wakeups, 2) + + { + v := wakeups[0] + require.Len(t, v, 0) + } + { + v := wakeups[1] + require.Len(t, v, 1) + require.Equal(t, "bar", v[0].CheckID) + } +} + func TestEventWatch(t *testing.T) { t.Parallel() c, s := makeClient(t) diff --git a/build-support/scripts/protobuf.sh b/build-support/scripts/protobuf.sh index 420d66d6a11b..f7b8ce559487 100755 --- a/build-support/scripts/protobuf.sh +++ b/build-support/scripts/protobuf.sh @@ -72,6 +72,10 @@ function main { status "Generated gRPC rate limit mapping file" + generate_protoset_file + + status "Generated protoset file" + return 0 } @@ -152,5 +156,11 @@ function generate_rate_limit_mappings { } } +function generate_protoset_file { + local pkg_dir="${SOURCE_DIR}/pkg" + mkdir -p "$pkg_dir" + print_run buf build -o "${pkg_dir}/consul.protoset" +} + main "$@" exit $? diff --git a/command/watch/watch.go b/command/watch/watch.go index 791f93a57cd4..b187fa369ab4 100644 --- a/command/watch/watch.go +++ b/command/watch/watch.go @@ -45,6 +45,7 @@ type cmd struct { state string name string shell bool + filter string } func (c *cmd) init() { @@ -71,6 +72,7 @@ func (c *cmd) init() { "Specifies the states to watch. Optional for 'checks' type.") c.flags.StringVar(&c.name, "name", "", "Specifies an event name to watch. Only for 'event' type.") + c.flags.StringVar(&c.filter, "filter", "", "Filter to use with the request") c.http = &flags.HTTPFlags{} flags.Merge(c.flags, c.http.ClientFlags()) @@ -128,6 +130,9 @@ func (c *cmd) Run(args []string) int { if c.service != "" { params["service"] = c.service } + if c.filter != "" { + params["filter"] = c.filter + } if len(c.tag) > 0 { params["tag"] = c.tag } diff --git a/go.mod b/go.mod index 81231ff9117f..f85e23b0b3c7 100644 --- a/go.mod +++ b/go.mod @@ -21,7 +21,7 @@ require ( github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e github.com/armon/go-metrics v0.4.1 github.com/armon/go-radix v1.0.0 - github.com/aws/aws-sdk-go v1.42.34 + github.com/aws/aws-sdk-go v1.44.289 github.com/coredns/coredns v1.6.6 github.com/coreos/go-oidc v2.1.0+incompatible github.com/docker/go-connections v0.4.0 diff --git a/go.sum b/go.sum index 3fa9a355c100..9f38d4eb658e 100644 --- a/go.sum +++ b/go.sum @@ -161,8 +161,8 @@ github.com/aws/aws-sdk-go v1.23.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN github.com/aws/aws-sdk-go v1.25.41/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/aws/aws-sdk-go v1.25.48/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/aws/aws-sdk-go v1.30.27/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0= -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/aws/aws-sdk-go v1.44.289 h1:5CVEjiHFvdiVlKPBzv0rjG4zH/21W/onT18R5AH/qx0= +github.com/aws/aws-sdk-go v1.44.289/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f/go.mod h1:AuiFmCCPBSrqvVMvuqFuk0qogytodnVFVSN5CeJB8Gc= github.com/benbjohnson/immutable v0.4.0 h1:CTqXbEerYso8YzVPxmWxh2gnoRQbbB9X1quUC8+vGZA= github.com/benbjohnson/immutable v0.4.0/go.mod h1:iAr8OjJGLnLmVUr9MZ/rz4PWUy6Ouc2JLYuMArmvAJM= @@ -1098,6 +1098,7 @@ github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yusufpapurcu/wmi v1.2.2 h1:KBNDSne4vP5mbSWnJbO+51IMOXJB67QiYCSBrubbPRg= github.com/yusufpapurcu/wmi v1.2.2/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= @@ -1167,6 +1168,7 @@ golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.1.0 h1:MDRAIl0xIo9Io2xV565hzXHw3zVseKrJKodhohM5CjU= golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= @@ -1208,6 +1210,7 @@ golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.10.0 h1:lFO9qtOdlre5W1jxS3r/4szv2/6iXxScdzjoBMXNhYk= golang.org/x/mod v0.10.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -1266,9 +1269,10 @@ golang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1 golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20211216030914-fe4d6282115f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -1303,6 +1307,7 @@ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.2.0 h1:PUR+T4wwASmuSTYdKjYHI5TD22Wy5ogLU5qZCOLxBrI= golang.org/x/sync v0.2.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20170830134202-bb24a47a89ea/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -1397,13 +1402,16 @@ golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBc 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-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.8.0 h1:n5xxQn2i3PC0yLAbjTpNT85q/Kgzcr2gIoX9OrJUols= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -1417,6 +1425,7 @@ 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/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/time v0.0.0-20161028155119-f51c12702a4d/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -1494,6 +1503,7 @@ golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.9.1 h1:8WMNJAz3zrtPmnYC7ISf5dEn3MT0gY7jBJfw27yrrLo= golang.org/x/tools v0.9.1/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/internal/controller/api_test.go b/internal/controller/api_test.go index 2006664b20fb..e80a2d7d7133 100644 --- a/internal/controller/api_test.go +++ b/internal/controller/api_test.go @@ -190,7 +190,7 @@ func TestController_String(t *testing.T) { WithPlacement(controller.PlacementEachServer) require.Equal(t, - `, placement="each-server">`, + `, placement="each-server">`, ctrl.String(), ) } @@ -201,7 +201,7 @@ func TestController_NoReconciler(t *testing.T) { ctrl := controller.ForType(demo.TypeV2Artist) require.PanicsWithValue(t, - `cannot register controller without a reconciler , placement="singleton">`, + `cannot register controller without a reconciler , placement="singleton">`, func() { mgr.Register(ctrl) }) } diff --git a/internal/resource/authz_oss.go b/internal/resource/authz_oss.go new file mode 100644 index 000000000000..014318f22897 --- /dev/null +++ b/internal/resource/authz_oss.go @@ -0,0 +1,17 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +//go:build !consulent +// +build !consulent + +package resource + +import ( + "github.com/hashicorp/consul/acl" + "github.com/hashicorp/consul/proto-public/pbresource" +) + +// AuthorizerContext builds an ACL AuthorizerContext for the given tenancy. +func AuthorizerContext(t *pbresource.Tenancy) *acl.AuthorizerContext { + return &acl.AuthorizerContext{Peer: t.PeerName} +} diff --git a/internal/resource/demo/demo.go b/internal/resource/demo/demo.go index 842b75739bc4..20ad89c962c4 100644 --- a/internal/resource/demo/demo.go +++ b/internal/resource/demo/demo.go @@ -33,36 +33,36 @@ var ( TypeV1Artist = &pbresource.Type{ Group: "demo", GroupVersion: "v1", - Kind: "artist", + Kind: "Artist", } // TypeV1Album represents a collection of an artist's songs. TypeV1Album = &pbresource.Type{ Group: "demo", GroupVersion: "v1", - Kind: "album", + Kind: "Album", } // TypeV2Artist represents a musician or group of musicians. TypeV2Artist = &pbresource.Type{ Group: "demo", GroupVersion: "v2", - Kind: "artist", + Kind: "Artist", } // TypeV2Album represents a collection of an artist's songs. TypeV2Album = &pbresource.Type{ Group: "demo", GroupVersion: "v2", - Kind: "album", + Kind: "Album", } ) const ( - ArtistV1ReadPolicy = `key_prefix "resource/demo.v1.artist/" { policy = "read" }` - ArtistV1WritePolicy = `key_prefix "resource/demo.v1.artist/" { policy = "write" }` - ArtistV2ReadPolicy = `key_prefix "resource/demo.v2.artist/" { policy = "read" }` - ArtistV2WritePolicy = `key_prefix "resource/demo.v2.artist/" { policy = "write" }` + ArtistV1ReadPolicy = `key_prefix "resource/demo.v1.Artist/" { policy = "read" }` + ArtistV1WritePolicy = `key_prefix "resource/demo.v1.Artist/" { policy = "write" }` + ArtistV2ReadPolicy = `key_prefix "resource/demo.v2.Artist/" { policy = "read" }` + ArtistV2WritePolicy = `key_prefix "resource/demo.v2.Artist/" { policy = "write" }` ArtistV2ListPolicy = `key_prefix "resource/" { policy = "list" }` ) diff --git a/internal/resource/registry.go b/internal/resource/registry.go index 232e3998d4e2..0004acfff4c6 100644 --- a/internal/resource/registry.go +++ b/internal/resource/registry.go @@ -5,6 +5,7 @@ package resource import ( "fmt" + "regexp" "sync" "google.golang.org/protobuf/proto" @@ -13,6 +14,12 @@ import ( "github.com/hashicorp/consul/proto-public/pbresource" ) +var ( + groupRegexp = regexp.MustCompile(`^[a-z][a-z\d_]+$`) + groupVersionRegexp = regexp.MustCompile(`^v([a-z\d]+)?\d$`) + kindRegexp = regexp.MustCompile(`^[A-Z][A-Za-z\d]+$`) +) + type Registry interface { // Register the given resource type and its hooks. Register(reg Registration) @@ -82,14 +89,23 @@ func NewRegistry() Registry { } func (r *TypeRegistry) Register(registration Registration) { - r.lock.Lock() - defer r.lock.Unlock() - typ := registration.Type if typ.Group == "" || typ.GroupVersion == "" || typ.Kind == "" { panic("type field(s) cannot be empty") } + switch { + case !groupRegexp.MatchString(typ.Group): + panic(fmt.Sprintf("Type.Group must be in snake_case. Got: %q", typ.Group)) + case !groupVersionRegexp.MatchString(typ.GroupVersion): + panic(fmt.Sprintf("Type.GroupVersion must be lowercase, start with `v`, and end with a number (e.g. `v2` or `v1alpha1`). Got: %q", typ.Group)) + case !kindRegexp.MatchString(typ.Kind): + panic(fmt.Sprintf("Type.Kind must be in PascalCase. Got: %q", typ.Kind)) + } + + r.lock.Lock() + defer r.lock.Unlock() + key := ToGVK(registration.Type) if _, ok := r.registrations[key]; ok { panic(fmt.Sprintf("resource type %s already registered", key)) diff --git a/internal/resource/registry_test.go b/internal/resource/registry_test.go index 7979d618c470..c9d1777159f8 100644 --- a/internal/resource/registry_test.go +++ b/internal/resource/registry_test.go @@ -28,36 +28,9 @@ func TestRegister(t *testing.T) { require.True(t, proto.Equal(demo.TypeV2Artist, actual.Type)) // register existing should panic - require.PanicsWithValue(t, "resource type demo.v2.artist already registered", func() { + require.PanicsWithValue(t, "resource type demo.v2.Artist already registered", func() { r.Register(reg) }) - - // type missing required fields should panic - testcases := map[string]*pbresource.Type{ - "empty group": { - Group: "", - GroupVersion: "v2", - Kind: "artist", - }, - "empty group version": { - Group: "", - GroupVersion: "v2", - Kind: "artist", - }, - "empty kind": { - Group: "demo", - GroupVersion: "v2", - Kind: "", - }, - } - - for desc, typ := range testcases { - t.Run(desc, func(t *testing.T) { - require.PanicsWithValue(t, "type field(s) cannot be empty", func() { - r.Register(resource.Registration{Type: typ}) - }) - }) - } } func TestRegister_Defaults(t *testing.T) { @@ -102,7 +75,7 @@ func TestResolve(t *testing.T) { serviceType := &pbresource.Type{ Group: "mesh", GroupVersion: "v1", - Kind: "service", + Kind: "Service", } // not found @@ -115,3 +88,89 @@ func TestResolve(t *testing.T) { assert.True(t, ok) assert.Equal(t, registration.Type, serviceType) } + +func TestRegister_TypeValidation(t *testing.T) { + registry := resource.NewRegistry() + + testCases := map[string]struct { + fn func(*pbresource.Type) + valid bool + }{ + "Valid": {valid: true}, + "Group empty": { + fn: func(t *pbresource.Type) { t.Group = "" }, + valid: false, + }, + "Group PascalCase": { + fn: func(t *pbresource.Type) { t.Group = "Foo" }, + valid: false, + }, + "Group kebab-case": { + fn: func(t *pbresource.Type) { t.Group = "foo-bar" }, + valid: false, + }, + "Group snake_case": { + fn: func(t *pbresource.Type) { t.Group = "foo_bar" }, + valid: true, + }, + "GroupVersion empty": { + fn: func(t *pbresource.Type) { t.GroupVersion = "" }, + valid: false, + }, + "GroupVersion snake_case": { + fn: func(t *pbresource.Type) { t.GroupVersion = "v_1" }, + valid: false, + }, + "GroupVersion kebab-case": { + fn: func(t *pbresource.Type) { t.GroupVersion = "v-1" }, + valid: false, + }, + "GroupVersion no leading v": { + fn: func(t *pbresource.Type) { t.GroupVersion = "1" }, + valid: false, + }, + "GroupVersion no trailing number": { + fn: func(t *pbresource.Type) { t.GroupVersion = "OnePointOh" }, + valid: false, + }, + "Kind PascalCase with numbers": { + fn: func(t *pbresource.Type) { t.Kind = "Number1" }, + valid: true, + }, + "Kind camelCase": { + fn: func(t *pbresource.Type) { t.Kind = "barBaz" }, + valid: false, + }, + "Kind snake_case": { + fn: func(t *pbresource.Type) { t.Kind = "bar_baz" }, + valid: false, + }, + "Kind empty": { + fn: func(t *pbresource.Type) { t.Kind = "" }, + valid: false, + }, + } + for desc, tc := range testCases { + t.Run(desc, func(t *testing.T) { + reg := func() { + typ := &pbresource.Type{ + Group: "foo", + GroupVersion: "v1", + Kind: "Bar", + } + if tc.fn != nil { + tc.fn(typ) + } + registry.Register(resource.Registration{ + Type: typ, + }) + } + + if tc.valid { + require.NotPanics(t, reg) + } else { + require.Panics(t, reg) + } + }) + } +} diff --git a/internal/resource/tombstone.go b/internal/resource/tombstone.go index 289aec2d5161..6d0285c602de 100644 --- a/internal/resource/tombstone.go +++ b/internal/resource/tombstone.go @@ -6,6 +6,6 @@ var ( TypeV1Tombstone = &pbresource.Type{ Group: "internal", GroupVersion: "v1", - Kind: "tombstone", + Kind: "Tombstone", } ) diff --git a/website/content/commands/debug.mdx b/website/content/commands/debug.mdx index bebbe955a294..1514158ff907 100644 --- a/website/content/commands/debug.mdx +++ b/website/content/commands/debug.mdx @@ -80,7 +80,7 @@ information when `debug` is running. By default, it captures all information. | `members` | A list of all the WAN and LAN members in the cluster. | | `metrics` | Metrics from the in-memory metrics endpoint in the target, captured at the interval. | | `logs` | `TRACE` level logs for the target agent, captured for the duration. | -| `pprof` | Golang heap, CPU, goroutine, and trace profiling. CPU and traces are captured for `duration` in a single file while heap and goroutine are separate snapshots for each `interval`. This information is not retrieved unless [`enable_debug`](/consul/docs/agent/config/config-files#enable_debug) is set to `true` on the target agent or ACLs are enable and an ACL token with `operator:read` is provided. | +| `pprof` | Golang heap, CPU, goroutine, and trace profiling. CPU and traces are captured for `duration` in a single file while heap and goroutine are separate snapshots for each `interval`. This information is not retrieved unless [`enable_debug`](/consul/docs/agent/config/config-files#enable_debug) is set to `true` on the target agent or ACLs are enabled and an ACL token with `operator:read` is provided. | ## Examples diff --git a/website/content/commands/watch.mdx b/website/content/commands/watch.mdx index da32cdefdc4d..806864dae953 100644 --- a/website/content/commands/watch.mdx +++ b/website/content/commands/watch.mdx @@ -53,6 +53,11 @@ or optionally provided. There is more documentation on watch - `-type` - Watch type. Required, one of "`key`, `keyprefix`, `services`, `nodes`, `service`, `checks`, or `event`. +- `-filter=` - Expression to use for filtering the results. Optional for + `checks` `nodes`, `services`, and `service` type. + See the [`/catalog/nodes` API documentation](/consul/api-docs/catalog#filtering) for a + description of what is filterable. + #### API Options @include 'http_api_options_client.mdx' diff --git a/website/content/docs/agent/config/config-files.mdx b/website/content/docs/agent/config/config-files.mdx index 4183a5a7d213..1b382341e467 100644 --- a/website/content/docs/agent/config/config-files.mdx +++ b/website/content/docs/agent/config/config-files.mdx @@ -472,8 +472,7 @@ Refer to the [formatting specification](https://golang.org/pkg/time/#ParseDurati that match a registering service instance. If it finds any, the agent will merge the centralized defaults with the service instance configuration. This allows for things like service protocol or proxy configuration to be defined centrally and inherited by any affected service registrations. This defaults to `false` in versions of Consul prior to 1.9.0, and defaults to `true` in Consul 1.9.0 and later. -- `enable_debug` When set, enables some additional debugging features. Currently, this is only used to - access runtime profiling HTTP endpoints, which are available with an `operator:read` ACL regardless of the value of `enable_debug`. +- `enable_debug` (boolean, default is `false`): When set to `true`, enables Consul to report additional debugging information, including runtime profiling (`pprof`) data. This setting is only required for clusters without ACL [enabled](#acl_enabled). If you change this setting, you must restart the agent for the change to take effect. - `enable_script_checks` Equivalent to the [`-enable-script-checks` command-line flag](/consul/docs/agent/config/cli-flags#_enable_script_checks). diff --git a/website/content/docs/api-gateway/upgrades.mdx b/website/content/docs/api-gateway/upgrades.mdx index 31bc1ec82374..2728db807f37 100644 --- a/website/content/docs/api-gateway/upgrades.mdx +++ b/website/content/docs/api-gateway/upgrades.mdx @@ -65,6 +65,8 @@ If you are able to tolerate downtime for your applications, you should delete pr $ kubectl apply -f apigw-installation.yaml ``` +1. Create `ServiceIntentions` allowing `Gateways` to communicate with any backend services that they route to. Refer to [Service intentions configuration entry reference](/consul/docs/connect/config-entries/service-intentions) for additional information. + 1. Change any existing `Gateways` to reference the new `GatewayClass` `consul`. Refer to [gatewayClass](/consul/docs/api-gateway/configuration/gateway#gatewayclassname) for additional information. 1. After updating all of your `gateway` configurations to use the new controller, you can complete the upgrade again and completely remove the `apiGateway` block to remove the old controller. @@ -99,6 +101,8 @@ If you are unable to tolerate any downtime, you can complete the following steps $ kubectl apply -f apigw-installation.yaml ``` +1. Create `ServiceIntentions` allowing `Gateways` to communicate with any backend services that they route to. Refer to [Service intentions configuration entry reference](/consul/docs/connect/config-entries/service-intentions) for additional information. + 1. Change any existing `Gateways` to reference the new `GatewayClass` `consul`. Refer to [gatewayClass](/consul/docs/api-gateway/configuration/gateway#gatewayclassname) for additional information. 1. After updating all of your `gateway` configurations to use the new controller, you can remove the `apiGateway` block from the Helm chart and rerun it. This completely removes the old gateway controller. diff --git a/website/content/docs/api-gateway/usage/errors.mdx b/website/content/docs/api-gateway/usage/errors.mdx index c873c55812db..ba2c40f6f240 100644 --- a/website/content/docs/api-gateway/usage/errors.mdx +++ b/website/content/docs/api-gateway/usage/errors.mdx @@ -58,3 +58,18 @@ The installation process typically fails after this error message is generated. **Resolution:** Install the required CRDs. Refer to the [Consul API Gateway installation instructions](/consul/docs/api-gateway/install#installation) for instructions. + +## Operation cannot be fulfilled, the object has been modified + +``` +{"error": "Operation cannot be fulfilled on gatewayclassconfigs.consul.hashicorp.com \"consul-api-gateway\": the object has been modified; please apply your changes to the latest version and try again"} + +``` +**Conditions:** +This error occurs when the gateway controller attempts to update an object that has been modified previously. It is a normal part of running the controller and will resolve itself by automatically retrying. + +**Impact:** +Excessive error logs are produced, but there is no impact to the functionality of the controller. + +**Resolution:** +No action needs to be taken to resolve this issue. diff --git a/website/content/docs/concepts/service-mesh.mdx b/website/content/docs/concepts/service-mesh.mdx index 334a6639f1ca..2e793f2441c8 100644 --- a/website/content/docs/concepts/service-mesh.mdx +++ b/website/content/docs/concepts/service-mesh.mdx @@ -21,8 +21,8 @@ Some of the benefits of a service mesh include; - automatic failover - traffic management - encryption -- observability and traceability, -- authentication and authorization, +- observability and traceability +- authentication and authorization - network automation A common use case for leveraging a service mesh is to achieve a [_zero trust_ model](https://www.consul.io/use-cases/zero-trust-networking). diff --git a/website/content/docs/connect/config-entries/service-resolver.mdx b/website/content/docs/connect/config-entries/service-resolver.mdx index dcea59805499..b4218d6d0614 100644 --- a/website/content/docs/connect/config-entries/service-resolver.mdx +++ b/website/content/docs/connect/config-entries/service-resolver.mdx @@ -35,12 +35,14 @@ The following list outlines field hierarchy, language-specific data types, and r - [`ServiceSubset`](#redirect-servicesubset): string - [`Namespace`](#redirect-namespace): string - [`Partition`](#redirect-partition): string | `default` + - [`SamenessGroup`](#redirect-samenessgroup): string - [`Datacenter`](#redirect-datacenter): list - [`Peer`](#redirect-peer): string - [`Failover`](#failover): map - [`Service`](#failover-service): string - [`ServiceSubset`](#failover-servicesubset): string - [`Namespace`](#failover-namespace): string + - [`SamenessGroup`](#failover-samenessgroup): string - [`Datacenters`](#failover-datacenters): list - [`Targets`](#failover-targets): list - [`Service`](#failover-targets-service): string @@ -87,12 +89,14 @@ The following list outlines field hierarchy, language-specific data types, and r - [`serviceSubset`](#spec-redirect-servicesubset): string - [`namespace`](#spec-redirect-namespace): string - [`partition`](#spec-redirect-partition): string + - [`samenessGroup`](#spec-redirect-samenessgroup): string - [`datacenter`](#spec-redirect-datacenter): string - [`peer`](#spec-redirect-peer): string - [`failover`](#spec-failover): map - [`service`](#spec-failover-service): string - [`serviceSubset`](#spec-failover-servicesubset): string - [`namespace`](#spec-failover-namespace): string + - [`samenessGroup`](#spec-failover-samenessgroup): string - [`datacenters`](#spec-failover-datacenters): string - [`targets`](#spec-failover-targets): list - [`service`](#spec-failover-targets-service): string @@ -157,11 +161,12 @@ Redirect = { ServiceSubset = "" Namespace = "" Partition = "" + SamenessGroup = "" Datacenter = "" Peer = "" } -Failover = { ## requires at least one of the following: Service, ServiceSubset, Namespace, Targets, Datacenters +Failover = { ## requires at least one of the following: Service, ServiceSubset, Namespace, Targets, Datacenters, SamenessGroup = { Targets = [ { Service = "" }, @@ -239,11 +244,12 @@ LoadBalancer = { "ServiceSubset":"", "Namespace":"", "Partition":"", + "SamenessGroup":"", "Datacenter":"", "Peer":"" }, - "Failover":{ // requires at least one of the following": Service, ServiceSubset, Namespace, Targets, Datacenters + "Failover":{ // requires at least one of the following": Service, ServiceSubset, Namespace, Targets, Datacenters, SamenessGroup "":{ "Targets":[ {"Service":""}, @@ -314,8 +320,9 @@ spec: servicesubset: namespace: partition: + samenessGroup: peer: - failover: # requires at least one of the following: service, serviceSubset, namespace, targets, datacenters + failover: # requires at least one of the following: service, serviceSubset, namespace, targets, datacenters, samenessGroup : targets: - service: @@ -465,6 +472,7 @@ Specifies redirect instructions for local service traffic so that services deplo - [`ServiceSubset`](#redirect-servicesubset) - [`Namespace`](#redirect-namespace) - [`Partition`](#redirect-partition) + - [`SamenessGroup`](#redirect-samenessgroup) - [`Datacenter`](#redirect-datacenter) - [`Peer`](#redirect-peer) @@ -504,6 +512,14 @@ Specifies the admin partition at the redirect’s destination that resolves loca - Default: None - Data type: String +### `Redirect{}.SamenessGroup` + +Specifies the sameness group at the redirect’s destination that resolves local upstream requests. + +#### Values + +- Default: None +- Data type: String ### `Redirect{}.Datacenter` @@ -529,7 +545,7 @@ Specifies controls for rerouting traffic to an alternate pool of service instanc This parameter is a map, and its key is the name of the local service subset that resolves to another location when it fails. You can specify a `"*"` wildcard to apply failovers to any subset. -`Service`, `ServiceSubset`, `Namespace`, `Targets`, and `Datacenters` cannot all be empty at the same time. +`Service`, `ServiceSubset`, `Namespace`, `Targets`, `SamenessGroup`, and `Datacenters` cannot all be empty at the same time. #### Values @@ -538,6 +554,7 @@ This parameter is a map, and its key is the name of the local service subset tha - [`Service`](#failover-service) - [`ServiceSubset`](#failover-servicesubset) - [`Namespace`](#failover-namespace) + - [`SamenessGroup`](#failover-samenessgroup) - [`Datacenters`](#failover-datacenters) - [`Targets`](#failover-targets) @@ -568,6 +585,15 @@ Specifies the namespace at the failover location where the failover services are - Default: None - Data type: String +### `Failover{}.SamenessGroup` + +Specifies the sameness group at the failover location where the failover services are deployed. + +#### Values + +- Default: None +- Data type: String + ### `Failover{}.Datacenters` Specifies an ordered list of datacenters at the failover location to attempt connections to during a failover scenario. When Consul cannot establish a connection with the first datacenter in the list, it proceeds sequentially until establishing a connection with another datacenter. @@ -907,6 +933,7 @@ Specifies redirect instructions for local service traffic so that services deplo - [`serviceSubset`](#spec-redirect-servicesubset) - [`namespace`](#spec-redirect-namespace) - [`partition`](#spec-redirect-partition) + - [`samenessGroup`](#spec-redirect-samenessgroup) - [`datacenter`](#spec-redirect-datacenter) - [`peer`](#spec-redirect-peer) @@ -946,6 +973,15 @@ Specifies the admin partition at the redirect’s destination that resolves loca - Default: None - Data type: String +### `spec.redirect.samenessGroup` + +Specifies the sameness group at the redirect’s destination that resolves local upstream requests. + +#### Values + +- Default: None +- Data type: String + ### `spec.redirect.datacenter` @@ -971,7 +1007,7 @@ Specifies controls for rerouting traffic to an alternate pool of service instanc This parameter is a map, and its key is the name of the local service subset that resolves to another location when it fails. You can specify a `"*"` wildcard to apply failovers to any subset. -`service`, `serviceSubset`, `namespace`, `targets`, and `datacenters` cannot all be empty at the same time. +`service`, `serviceSubset`, `namespace`, `targets`, `samenessGroup`, and `datacenters` cannot all be empty at the same time. #### Values @@ -980,6 +1016,7 @@ This parameter is a map, and its key is the name of the local service subset tha - [`service`](#spec-failover-service) - [`serviceSubset`](#spec-failover-servicesubset) - [`namespace`](#spec-failover-namespace) + - [`samenessGroup`](#spec-failover-samenessgroup) - [`datacenters`](#spec-failover-datacenters) - [`targets`](#spec-failover-targets) @@ -1010,6 +1047,15 @@ Specifies the namespace at the failover location where the failover services are - Default: None - Data type: String +### `spec.failover.samenessGroup` + +Specifies the sameness group at the failover location where the failover services are deployed. + +#### Values + +- Default: None +- Data type: String + ### `spec.failover.datacenters` Specifies an ordered list of datacenters at the failover location to attempt connections to during a failover scenario. When Consul cannot establish a connection with the first datacenter in the list, it proceeds sequentially until establishing a connection with another datacenter. diff --git a/website/content/docs/connect/gateways/api-gateway/configuration/http-route.mdx b/website/content/docs/connect/gateways/api-gateway/configuration/http-route.mdx index 997e2bbf692e..02d2725ad698 100644 --- a/website/content/docs/connect/gateways/api-gateway/configuration/http-route.mdx +++ b/website/content/docs/connect/gateways/api-gateway/configuration/http-route.mdx @@ -533,6 +533,11 @@ Specifies the HTTP method to match. Specifies type of match for the path: `"exact"`, `"prefix"`, or `"regex"`. +If set to `prefix`, Consul uses simple string matching to identify incoming request prefixes. For example, if the route is configured to match incoming requests to services prefixed with `/dev`, then the gateway would match requests to `/dev-` and `/deviate` and route to the upstream. + +This deviates from the +[Kubernetes Gateway API specification](https://gateway-api.sigs.k8s.io/references/spec/#gateway.networking.k8s.io%2fv1beta1.PathMatchType), which matches on full path elements. In the previous example, _only_ requests to `/dev` or `/dev/` would match. + #### Values - Default: none diff --git a/website/content/docs/k8s/upgrade/index.mdx b/website/content/docs/k8s/upgrade/index.mdx index 529815df9769..e92ec12be2aa 100644 --- a/website/content/docs/k8s/upgrade/index.mdx +++ b/website/content/docs/k8s/upgrade/index.mdx @@ -219,7 +219,7 @@ In earlier versions, Consul on Kubernetes used client agents in its deployments. If you upgrade Consul from a version that uses client agents to a version the uses dataplanes, complete the following steps to upgrade your deployment safely and without downtime. -1. Before you upgrade, edit your Helm chart to enable Consul client agents by setting `client.enabled` and `client.updateStrategy`: +1. Before you upgrade, edit your Helm chart configuration to enable Consul client agents by setting `client.enabled` and `client.updateStrategy`: ```yaml filename="values.yaml" client: @@ -228,6 +228,18 @@ If you upgrade Consul from a version that uses client agents to a version the us type: OnDelete ``` +1. Update the `connect-injector` to not log out on restart +to make sure that the ACL tokens used by existing services are still valid during the migration to `consul-dataplane`. +Note that you must remove the token manually after completing the migration. + + The following command triggers the deployment rollout. Wait for the rollout to complete before proceeding to next step. + + ```bash + kubectl config set-context --current --namespace= + INJECTOR_DEPLOYMENT=$(kg deploy -l "component=connect-injector" -o=jsonpath='{.items[0].metadata.name}') + kubectl patch deploy $INJECTOR_DEPLOYMENT --type='json' -p='[{"op": "remove", "path": "/spec/template/spec/containers/0/lifecycle"}]' + ``` + 1. Follow our [recommended procedures to upgrade servers](#upgrade-consul-servers) on Kubernetes deployments to upgrade Helm values for the new version of Consul. 1. Run `kubectl rollout restart` to restart your service mesh applications. Restarting service mesh application causes Kubernetes to re-inject them with the webhook for dataplanes.