From 3611fed97a0ac093ac770743d0ab8cef0bf31ae7 Mon Sep 17 00:00:00 2001 From: Iryna Shustava Date: Tue, 17 Oct 2023 21:34:00 +0000 Subject: [PATCH] backport of commit 6350a814dbda8a83ea2d23a40b07addd35c53671 --- .changelog/19218.txt | 3 - .changelog/_7406.txt | 3 + .github/workflows/build.yml | 27 ++- ...t-1.17.x.yaml => nightly-test-1.13.x.yaml} | 6 +- .github/workflows/test-integrations.yml | 4 +- .../grpc-external/services/resource/delete.go | 3 +- .../services/resource/delete_test.go | 94 ++------ agent/grpc-external/services/resource/list.go | 5 +- .../services/resource/list_by_owner.go | 3 + .../services/resource/list_by_owner_test.go | 158 ++++--------- .../services/resource/list_test.go | 60 +---- .../services/resource/read_test.go | 169 ++++---------- .../grpc-external/services/resource/server.go | 73 +----- .../services/resource/server_test.go | 28 +++ .../grpc-external/services/resource/watch.go | 5 +- .../services/resource/watch_test.go | 59 +---- .../services/resource/write_status.go | 13 +- .../services/resource/write_status_test.go | 173 ++++---------- .../services/resource/write_test.go | 213 ++++++------------ agent/xds/endpoints.go | 20 +- agent/xds/listeners_apigateway.go | 12 +- ...eway-with-multiple-hostnames.latest.golden | 34 +-- api/config_entry_rate_limit_ip.go | 8 +- internal/catalog/catalogtest/run_test.go | 2 +- .../catalogtest/test_integration_v2beta1.go | 4 +- .../sidecarproxy/builder/expose_paths.go | 26 ++- .../testdata/source/l7-expose-paths.golden | 24 +- .../local-and-inbound-connections.golden | 24 +- .../controllers/xds/controller_test.go | 5 +- internal/resource/demo/controller.go | 2 +- internal/resource/demo/demo.go | 2 +- internal/resource/http/http_test.go | 12 +- internal/resource/resource.go | 22 -- internal/resource/tenancy.go | 17 ++ internal/tenancy/exports.go | 10 +- internal/tenancy/internal/types/namespace.go | 17 +- .../tenancy/internal/types/namespace_test.go | 20 +- internal/tenancy/internal/types/types.go | 6 +- .../namespace.pb.binary.go | 4 +- .../pbtenancy/v1alpha1/namespace.pb.go | 172 ++++++++++++++ .../{v2beta1 => v1alpha1}/namespace.proto | 2 +- .../namespace_deepcopy.gen.go | 2 +- .../namespace_json.gen.go | 2 +- .../resource_types.gen.go | 4 +- .../pbtenancy/v2beta1/namespace.pb.go | 171 -------------- .../test/catalog/catalog_test.go | 2 +- version/VERSION | 2 +- .../control-plane-request-limit.mdx | 56 ++--- .../docs/ecs/reference/consul-server-json.mdx | 131 +++++++++-- .../content/docs/k8s/multiport/configure.mdx | 55 +++-- website/content/docs/k8s/multiport/index.mdx | 2 +- .../services/discovery/dns-configuration.mdx | 2 +- 52 files changed, 795 insertions(+), 1178 deletions(-) delete mode 100644 .changelog/19218.txt create mode 100644 .changelog/_7406.txt rename .github/workflows/{nightly-test-1.17.x.yaml => nightly-test-1.13.x.yaml} (98%) delete mode 100644 internal/resource/resource.go rename proto-public/pbtenancy/{v2beta1 => v1alpha1}/namespace.pb.binary.go (84%) create mode 100644 proto-public/pbtenancy/v1alpha1/namespace.pb.go rename proto-public/pbtenancy/{v2beta1 => v1alpha1}/namespace.proto (91%) rename proto-public/pbtenancy/{v2beta1 => v1alpha1}/namespace_deepcopy.gen.go (97%) rename proto-public/pbtenancy/{v2beta1 => v1alpha1}/namespace_json.gen.go (96%) rename proto-public/pbtenancy/{v2beta1 => v1alpha1}/resource_types.gen.go (87%) delete mode 100644 proto-public/pbtenancy/v2beta1/namespace.pb.go diff --git a/.changelog/19218.txt b/.changelog/19218.txt deleted file mode 100644 index a3dde32317b47..0000000000000 --- a/.changelog/19218.txt +++ /dev/null @@ -1,3 +0,0 @@ -```release-note:improvement -resource: lowercase names enforced for v2 resources only. -``` \ No newline at end of file diff --git a/.changelog/_7406.txt b/.changelog/_7406.txt new file mode 100644 index 0000000000000..b020a3a0e601b --- /dev/null +++ b/.changelog/_7406.txt @@ -0,0 +1,3 @@ +```release-note:bug +server: **(Enterprise Only)** Fixed an issue where snake case keys were rejected when configuring the control-plane-request-limit config entry +``` \ No newline at end of file diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 5aade8fadfd6b..4c2afc5caf555 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -28,6 +28,8 @@ jobs: shared-ldflags: ${{ steps.shared-ldflags.outputs.shared-ldflags }} steps: - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 + # action-set-product-version implicitly sets fields like 'product-version' using version/VERSION + # https://github.com/hashicorp/actions-set-product-version - name: set product version id: set-product-version uses: hashicorp/actions-set-product-version@v1 @@ -35,7 +37,6 @@ jobs: id: get-product-version run: | CONSUL_DATE=$(build-support/scripts/build-date.sh) - ## TODO: This assumes `make version` outputs 1.1.1+ent-prerel echo "product-date=${CONSUL_DATE}" >> "$GITHUB_OUTPUT" - name: Set shared -ldflags @@ -299,8 +300,10 @@ jobs: # This naming convention will be used ONLY for per-commit dev images - name: Set docker dev tag run: | - version="${{ env.version }}" - echo "dev_tag=${version%.*}-dev" >> $GITHUB_ENV + echo "full_dev_tag=${{ env.version }}" + echo "full_dev_tag=${{ env.version }}" >> $GITHUB_ENV + echo "minor_dev_tag=$(echo ${{ env.version }}| sed -E 's/([0-9]+\.[0-9]+)\.[0-9]+(-[0-9a-zA-Z\+\.]+)?$/\1\2/')" + echo "minor_dev_tag=$(echo ${{ env.version }}| sed -E 's/([0-9]+\.[0-9]+)\.[0-9]+(-[0-9a-zA-Z\+\.]+)?$/\1\2/')" >> $GITHUB_ENV - name: Docker Build (Action) uses: hashicorp/actions-docker-build@v1 @@ -312,8 +315,10 @@ jobs: docker.io/hashicorp/${{env.repo}}:${{env.version}} public.ecr.aws/hashicorp/${{env.repo}}:${{env.version}} dev_tags: | - docker.io/hashicorppreview/${{ env.repo }}:${{ env.dev_tag }} - docker.io/hashicorppreview/${{ env.repo }}:${{ env.dev_tag }}-${{ github.sha }} + docker.io/hashicorppreview/${{ env.repo }}:${{ env.full_dev_tag }} + docker.io/hashicorppreview/${{ env.repo }}:${{ env.full_dev_tag }}-${{ github.sha }} + docker.io/hashicorppreview/${{ env.repo }}:${{ env.minor_dev_tag }} + docker.io/hashicorppreview/${{ env.repo }}:${{ env.minor_dev_tag }}-${{ github.sha }} smoke_test: .github/scripts/verify_docker.sh v${{ env.version }} build-docker-ubi-redhat: @@ -353,8 +358,10 @@ jobs: # This naming convention will be used ONLY for per-commit dev images - name: Set docker dev tag run: | - version="${{ env.version }}" - echo "dev_tag=${version%.*}-dev" >> $GITHUB_ENV + echo "full_dev_tag=${{ env.version }}" + echo "full_dev_tag=${{ env.version }}" >> $GITHUB_ENV + echo "minor_dev_tag=$(echo ${{ env.version }}| sed -E 's/([0-9]+\.[0-9]+)\.[0-9]+(-[0-9a-zA-Z\+\.]+)?$/\1\2/')" + echo "minor_dev_tag=$(echo ${{ env.version }}| sed -E 's/([0-9]+\.[0-9]+)\.[0-9]+(-[0-9a-zA-Z\+\.]+)?$/\1\2/')" >> $GITHUB_ENV - uses: hashicorp/actions-docker-build@v1 with: @@ -365,8 +372,10 @@ jobs: docker.io/hashicorp/${{env.repo}}:${{env.version}}-ubi public.ecr.aws/hashicorp/${{env.repo}}:${{env.version}}-ubi dev_tags: | - docker.io/hashicorppreview/${{ env.repo }}:${{ env.dev_tag }}-ubi - docker.io/hashicorppreview/${{ env.repo }}:${{ env.dev_tag }}-ubi-${{ github.sha }} + docker.io/hashicorppreview/${{ env.repo }}:${{ env.full_dev_tag }}-ubi + docker.io/hashicorppreview/${{ env.repo }}:${{ env.full_dev_tag }}-ubi-${{ github.sha }} + docker.io/hashicorppreview/${{ env.repo }}:${{ env.minor_dev_tag }}-ubi + docker.io/hashicorppreview/${{ env.repo }}:${{ env.minor_dev_tag }}-ubi-${{ github.sha }} smoke_test: .github/scripts/verify_docker.sh v${{ env.version }} verify-linux: diff --git a/.github/workflows/nightly-test-1.17.x.yaml b/.github/workflows/nightly-test-1.13.x.yaml similarity index 98% rename from .github/workflows/nightly-test-1.17.x.yaml rename to .github/workflows/nightly-test-1.13.x.yaml index df6f8d946f83c..f314a475dfbd7 100644 --- a/.github/workflows/nightly-test-1.17.x.yaml +++ b/.github/workflows/nightly-test-1.13.x.yaml @@ -1,7 +1,7 @@ # Copyright (c) HashiCorp, Inc. # SPDX-License-Identifier: MPL-2.0 -name: Nightly Frontend Test 1.17.x +name: Nightly Frontend Test 1.13.x on: schedule: - cron: '0 4 * * *' @@ -9,8 +9,8 @@ on: env: EMBER_PARTITION_TOTAL: 4 # Has to be changed in tandem with the matrix.partition - BRANCH: "release/1.17.x" - BRANCH_NAME: "release-1.17.x" # Used for naming artifacts + BRANCH: "release/1.13.x" + BRANCH_NAME: "release-1.13.x" # Used for naming artifacts GOPRIVATE: github.com/hashicorp # Required for enterprise deps jobs: diff --git a/.github/workflows/test-integrations.yml b/.github/workflows/test-integrations.yml index 28dcf701fdf13..d2a7b8c3759da 100644 --- a/.github/workflows/test-integrations.yml +++ b/.github/workflows/test-integrations.yml @@ -80,7 +80,7 @@ jobs: contents: read strategy: matrix: - nomad-version: ['v1.6.1', 'v1.5.8', 'v1.4.12'] + nomad-version: ['v1.6.2', 'v1.5.9', 'v1.4.13'] steps: - name: Checkout Nomad uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 @@ -159,7 +159,7 @@ jobs: contents: read strategy: matrix: - vault-version: ["1.14.1", "1.13.5", "1.12.9", "1.11.12"] + vault-version: ["1.15.0", "1.14.4", "1.13.8", "1.12.11"] env: VAULT_BINARY_VERSION: ${{ matrix.vault-version }} steps: diff --git a/agent/grpc-external/services/resource/delete.go b/agent/grpc-external/services/resource/delete.go index a2d3bec995d40..2f30e27f983fb 100644 --- a/agent/grpc-external/services/resource/delete.go +++ b/agent/grpc-external/services/resource/delete.go @@ -7,7 +7,6 @@ import ( "context" "errors" "fmt" - "strings" "time" "github.com/oklog/ulid/v2" @@ -176,5 +175,5 @@ func (s *Server) validateDeleteRequest(req *pbresource.DeleteRequest) (*resource // name by embedding the resources's Uid in the name. func tombstoneName(deleteId *pbresource.ID) string { // deleteId.Name is just included for easier identification - return fmt.Sprintf("tombstone-%v-%v", deleteId.Name, strings.ToLower(deleteId.Uid)) + return fmt.Sprintf("tombstone-%v-%v", deleteId.Name, deleteId.Uid) } diff --git a/agent/grpc-external/services/resource/delete_test.go b/agent/grpc-external/services/resource/delete_test.go index 3bdbb0581d106..5f5d7d7e21920 100644 --- a/agent/grpc-external/services/resource/delete_test.go +++ b/agent/grpc-external/services/resource/delete_test.go @@ -5,7 +5,6 @@ package resource import ( "context" - "strings" "testing" "github.com/stretchr/testify/mock" @@ -23,98 +22,39 @@ import ( func TestDelete_InputValidation(t *testing.T) { server := testServer(t) client := testClient(t, server) - demo.RegisterTypes(server.Registry) - type testCase struct { - modFn func(artistId, recordLabelId *pbresource.ID) *pbresource.ID - errContains string - } + demo.RegisterTypes(server.Registry) - testCases := map[string]testCase{ - "no id": { - modFn: func(_, _ *pbresource.ID) *pbresource.ID { - return nil - }, - errContains: "id is required", - }, - "no type": { - modFn: func(artistId, _ *pbresource.ID) *pbresource.ID { - artistId.Type = nil - return artistId - }, - errContains: "id.type is required", - }, - "no name": { - modFn: func(artistId, _ *pbresource.ID) *pbresource.ID { - artistId.Name = "" - return artistId - }, - errContains: "id.name invalid", - }, - "mixed case name": { - modFn: func(artistId, _ *pbresource.ID) *pbresource.ID { - artistId.Name = "DepecheMode" - return artistId - }, - errContains: "id.name invalid", - }, - "name too long": { - modFn: func(artistId, _ *pbresource.ID) *pbresource.ID { - artistId.Name = strings.Repeat("n", resource.MaxNameLength+1) - return artistId - }, - errContains: "id.name invalid", - }, - "partition mixed case": { - modFn: func(artistId, _ *pbresource.ID) *pbresource.ID { - artistId.Tenancy.Partition = "Default" - return artistId - }, - errContains: "id.tenancy.partition invalid", + testCases := map[string]func(artistId, recordLabelId *pbresource.ID) *pbresource.ID{ + "no id": func(artistId, recordLabelId *pbresource.ID) *pbresource.ID { + return nil }, - "partition name too long": { - modFn: func(artistId, _ *pbresource.ID) *pbresource.ID { - artistId.Tenancy.Partition = strings.Repeat("p", resource.MaxNameLength+1) - return artistId - }, - errContains: "id.tenancy.partition invalid", + "no type": func(artistId, _ *pbresource.ID) *pbresource.ID { + artistId.Type = nil + return artistId }, - "namespace mixed case": { - modFn: func(artistId, _ *pbresource.ID) *pbresource.ID { - artistId.Tenancy.Namespace = "Default" - return artistId - }, - errContains: "id.tenancy.namespace invalid", + "no name": func(artistId, _ *pbresource.ID) *pbresource.ID { + artistId.Name = "" + return artistId }, - "namespace name too long": { - modFn: func(artistId, _ *pbresource.ID) *pbresource.ID { - artistId.Tenancy.Namespace = strings.Repeat("n", resource.MaxNameLength+1) - return artistId - }, - errContains: "id.tenancy.namespace invalid", - }, - "partition scoped resource with namespace": { - modFn: func(_, recordLabelId *pbresource.ID) *pbresource.ID { - recordLabelId.Tenancy.Namespace = "ishouldnothaveanamespace" - return recordLabelId - }, - errContains: "cannot have a namespace", + "partition scoped resource with namespace": func(_, recordLabelId *pbresource.ID) *pbresource.ID { + recordLabelId.Tenancy.Namespace = "ishouldnothaveanamespace" + return recordLabelId }, } - for desc, tc := range testCases { + for desc, modFn := range testCases { t.Run(desc, func(t *testing.T) { - recordLabel, err := demo.GenerateV1RecordLabel("looney-tunes") + recordLabel, err := demo.GenerateV1RecordLabel("LoonyTunes") require.NoError(t, err) artist, err := demo.GenerateV2Artist() require.NoError(t, err) - req := &pbresource.DeleteRequest{Id: tc.modFn(artist.Id, recordLabel.Id), Version: ""} + req := &pbresource.DeleteRequest{Id: modFn(artist.Id, recordLabel.Id), Version: ""} _, err = client.Delete(testContext(t), req) require.Error(t, err) require.Equal(t, codes.InvalidArgument.String(), status.Code(err).String()) - require.ErrorContains(t, err, tc.errContains) }) } } @@ -189,7 +129,7 @@ func TestDelete_Success(t *testing.T) { server, client, ctx := testDeps(t) demo.RegisterTypes(server.Registry) - recordLabel, err := demo.GenerateV1RecordLabel("looney-tunes") + recordLabel, err := demo.GenerateV1RecordLabel("LoonyTunes") require.NoError(t, err) writeRsp, err := client.Write(ctx, &pbresource.WriteRequest{Resource: recordLabel}) require.NoError(t, err) diff --git a/agent/grpc-external/services/resource/list.go b/agent/grpc-external/services/resource/list.go index befb619eec53b..c1ecb253448ce 100644 --- a/agent/grpc-external/services/resource/list.go +++ b/agent/grpc-external/services/resource/list.go @@ -100,9 +100,8 @@ func (s *Server) validateListRequest(req *pbresource.ListRequest) (*resource.Reg return nil, err } - if err := validateWildcardTenancy(req.Tenancy, req.NamePrefix); err != nil { - return nil, err - } + // Lowercase + resource.Normalize(req.Tenancy) // Error when partition scoped and namespace not empty. if reg.Scope == resource.ScopePartition && req.Tenancy.Namespace != "" { diff --git a/agent/grpc-external/services/resource/list_by_owner.go b/agent/grpc-external/services/resource/list_by_owner.go index a9b1754498fe1..2310a5b50eda2 100644 --- a/agent/grpc-external/services/resource/list_by_owner.go +++ b/agent/grpc-external/services/resource/list_by_owner.go @@ -105,6 +105,9 @@ func (s *Server) validateListByOwnerRequest(req *pbresource.ListByOwnerRequest) return nil, err } + // Lowercase + resource.Normalize(req.Owner.Tenancy) + // Error when partition scoped and namespace not empty. if reg.Scope == resource.ScopePartition && req.Owner.Tenancy.Namespace != "" { return nil, status.Errorf( 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 78024e68d0fb2..11c6027c0b642 100644 --- a/agent/grpc-external/services/resource/list_by_owner_test.go +++ b/agent/grpc-external/services/resource/list_by_owner_test.go @@ -6,7 +6,6 @@ package resource import ( "context" "fmt" - "strings" "testing" "github.com/hashicorp/consul/acl" @@ -14,7 +13,6 @@ import ( "github.com/hashicorp/consul/internal/resource/demo" "github.com/hashicorp/consul/proto-public/pbresource" "github.com/hashicorp/consul/proto/private/prototest" - "github.com/oklog/ulid/v2" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" @@ -28,104 +26,41 @@ func TestListByOwner_InputValidation(t *testing.T) { client := testClient(t, server) demo.RegisterTypes(server.Registry) - type testCase struct { - modFn func(artistId, recordlabelId *pbresource.ID) *pbresource.ID - errContains string - } - testCases := map[string]testCase{ - "no owner": { - modFn: func(artistId, recordLabelId *pbresource.ID) *pbresource.ID { - return nil - }, - errContains: "owner is required", - }, - "no type": { - modFn: func(artistId, _ *pbresource.ID) *pbresource.ID { - artistId.Type = nil - return artistId - }, - errContains: "owner.type is required", - }, - "no name": { - modFn: func(artistId, _ *pbresource.ID) *pbresource.ID { - artistId.Name = "" - return artistId - }, - errContains: "owner.name invalid", - }, - "name mixed case": { - modFn: func(artistId, _ *pbresource.ID) *pbresource.ID { - artistId.Name = "U2" - return artistId - }, - errContains: "owner.name invalid", + testCases := map[string]func(artistId, recordlabelId *pbresource.ID) *pbresource.ID{ + "no owner": func(artistId, recordLabelId *pbresource.ID) *pbresource.ID { + return nil }, - "name too long": { - modFn: func(artistId, _ *pbresource.ID) *pbresource.ID { - artistId.Name = strings.Repeat("n", resource.MaxNameLength+1) - return artistId - }, - errContains: "owner.name invalid", + "no type": func(artistId, _ *pbresource.ID) *pbresource.ID { + artistId.Type = nil + return artistId }, - "partition mixed case": { - modFn: func(artistId, _ *pbresource.ID) *pbresource.ID { - artistId.Tenancy.Partition = "Default" - return artistId - }, - errContains: "owner.tenancy.partition invalid", + "no name": func(artistId, _ *pbresource.ID) *pbresource.ID { + artistId.Name = "" + return artistId }, - "partition too long": { - modFn: func(artistId, _ *pbresource.ID) *pbresource.ID { - artistId.Tenancy.Partition = strings.Repeat("p", resource.MaxNameLength+1) - return artistId - }, - errContains: "owner.tenancy.partition invalid", + "no uid": func(artistId, _ *pbresource.ID) *pbresource.ID { + artistId.Uid = "" + return artistId }, - "namespace mixed case": { - modFn: func(artistId, _ *pbresource.ID) *pbresource.ID { - artistId.Tenancy.Namespace = "Default" - return artistId - }, - errContains: "owner.tenancy.namespace invalid", - }, - "namespace too long": { - modFn: func(artistId, _ *pbresource.ID) *pbresource.ID { - artistId.Tenancy.Namespace = strings.Repeat("n", resource.MaxNameLength+1) - return artistId - }, - errContains: "owner.tenancy.namespace invalid", - }, - "no uid": { - modFn: func(artistId, _ *pbresource.ID) *pbresource.ID { - artistId.Uid = "" - return artistId - }, - errContains: "owner uid is required", - }, - "partition scope with non-empty namespace": { - modFn: func(_, recordLabelId *pbresource.ID) *pbresource.ID { - recordLabelId.Uid = ulid.Make().String() - recordLabelId.Tenancy.Namespace = "ishouldnothaveanamespace" - return recordLabelId - }, - errContains: "cannot have a namespace", + "partition scope with non-empty namespace": func(_, recordLabelId *pbresource.ID) *pbresource.ID { + recordLabelId.Tenancy.Namespace = "ishouldnothaveanamespace" + return recordLabelId }, } - for desc, tc := range testCases { + for desc, modFn := range testCases { t.Run(desc, func(t *testing.T) { artist, err := demo.GenerateV2Artist() require.NoError(t, err) - recordLabel, err := demo.GenerateV1RecordLabel("looney-tunes") + recordLabel, err := demo.GenerateV1RecordLabel("LoonyTunes") require.NoError(t, err) // Each test case picks which resource to use based on the resource type's scope. - req := &pbresource.ListByOwnerRequest{Owner: tc.modFn(artist.Id, recordLabel.Id)} + req := &pbresource.ListByOwnerRequest{Owner: modFn(artist.Id, recordLabel.Id)} _, err = client.ListByOwner(testContext(t), req) require.Error(t, err) require.Equal(t, codes.InvalidArgument.String(), status.Code(err).String()) - require.ErrorContains(t, err, tc.errContains) }) } } @@ -196,46 +131,33 @@ func TestListByOwner_Many(t *testing.T) { } func TestListByOwner_OwnerTenancyDoesNotExist(t *testing.T) { - type testCase struct { - modFn func(artistId, recordlabelId *pbresource.ID) *pbresource.ID - errContains string - } - tenancyCases := map[string]testCase{ - "partition not found when namespace scoped": { - modFn: func(artistId, _ *pbresource.ID) *pbresource.ID { - id := clone(artistId) - id.Uid = "doesnotmatter" - id.Tenancy.Partition = "boguspartition" - return id - }, - errContains: "partition not found", + tenancyCases := map[string]func(artistId, recordlabelId *pbresource.ID) *pbresource.ID{ + "partition not found when namespace scoped": func(artistId, _ *pbresource.ID) *pbresource.ID { + id := clone(artistId) + id.Uid = "doesnotmatter" + id.Tenancy.Partition = "boguspartition" + return id }, - "namespace not found when namespace scoped": { - modFn: func(artistId, _ *pbresource.ID) *pbresource.ID { - id := clone(artistId) - id.Uid = "doesnotmatter" - id.Tenancy.Namespace = "bogusnamespace" - return id - }, - errContains: "namespace not found", + "namespace not found when namespace scoped": func(artistId, _ *pbresource.ID) *pbresource.ID { + id := clone(artistId) + id.Uid = "doesnotmatter" + id.Tenancy.Namespace = "bogusnamespace" + return id }, - "partition not found when partition scoped": { - modFn: func(_, recordLabelId *pbresource.ID) *pbresource.ID { - id := clone(recordLabelId) - id.Uid = "doesnotmatter" - id.Tenancy.Partition = "boguspartition" - return id - }, - errContains: "partition not found", + "partition not found when partition scoped": func(_, recordLabelId *pbresource.ID) *pbresource.ID { + id := clone(recordLabelId) + id.Uid = "doesnotmatter" + id.Tenancy.Partition = "boguspartition" + return id }, } - for desc, tc := range tenancyCases { + for desc, modFn := range tenancyCases { t.Run(desc, func(t *testing.T) { server := testServer(t) demo.RegisterTypes(server.Registry) client := testClient(t, server) - recordLabel, err := demo.GenerateV1RecordLabel("looney-tunes") + recordLabel, err := demo.GenerateV1RecordLabel("LoonyTunes") require.NoError(t, err) recordLabel, err = server.Backend.WriteCAS(testContext(t), recordLabel) require.NoError(t, err) @@ -245,11 +167,11 @@ func TestListByOwner_OwnerTenancyDoesNotExist(t *testing.T) { artist, err = server.Backend.WriteCAS(testContext(t), artist) require.NoError(t, err) - // Verify non-existant tenancy units in owner err with invalid arg. - _, err = client.ListByOwner(testContext(t), &pbresource.ListByOwnerRequest{Owner: tc.modFn(artist.Id, recordLabel.Id)}) + // Verify non-existant tenancy units in owner err with not found. + _, err = client.ListByOwner(testContext(t), &pbresource.ListByOwnerRequest{Owner: modFn(artist.Id, recordLabel.Id)}) require.Error(t, err) require.Equal(t, codes.InvalidArgument.String(), status.Code(err).String()) - require.ErrorContains(t, err, tc.errContains) + require.Contains(t, err.Error(), "resource not found") }) } } @@ -262,7 +184,7 @@ func TestListByOwner_Tenancy_Defaults_And_Normalization(t *testing.T) { client := testClient(t, server) // Create partition scoped recordLabel. - recordLabel, err := demo.GenerateV1RecordLabel("looney-tunes") + recordLabel, err := demo.GenerateV1RecordLabel("LoonyTunes") require.NoError(t, err) rsp1, err := client.Write(testContext(t), &pbresource.WriteRequest{Resource: recordLabel}) require.NoError(t, err) diff --git a/agent/grpc-external/services/resource/list_test.go b/agent/grpc-external/services/resource/list_test.go index a80685b0834ea..64026b7d34e59 100644 --- a/agent/grpc-external/services/resource/list_test.go +++ b/agent/grpc-external/services/resource/list_test.go @@ -6,7 +6,6 @@ package resource import ( "context" "fmt" - "strings" "testing" "github.com/hashicorp/consul/acl" @@ -27,66 +26,28 @@ import ( func TestList_InputValidation(t *testing.T) { server := testServer(t) client := testClient(t, server) - demo.RegisterTypes(server.Registry) - type testCase struct { - modReqFn func(req *pbresource.ListRequest) - errContains string - } + demo.RegisterTypes(server.Registry) - testCases := map[string]testCase{ - "no type": { - modReqFn: func(req *pbresource.ListRequest) { req.Type = nil }, - errContains: "type is required", - }, - "no tenancy": { - modReqFn: func(req *pbresource.ListRequest) { req.Tenancy = nil }, - errContains: "tenancy is required", - }, - "partition mixed case": { - modReqFn: func(req *pbresource.ListRequest) { req.Tenancy.Partition = "Default" }, - errContains: "tenancy.partition invalid", - }, - "partition too long": { - modReqFn: func(req *pbresource.ListRequest) { - req.Tenancy.Partition = strings.Repeat("p", resource.MaxNameLength+1) - }, - errContains: "tenancy.partition invalid", - }, - "namespace mixed case": { - modReqFn: func(req *pbresource.ListRequest) { req.Tenancy.Namespace = "Default" }, - errContains: "tenancy.namespace invalid", - }, - "namespace too long": { - modReqFn: func(req *pbresource.ListRequest) { - req.Tenancy.Namespace = strings.Repeat("n", resource.MaxNameLength+1) - }, - errContains: "tenancy.namespace invalid", - }, - "name_prefix mixed case": { - modReqFn: func(req *pbresource.ListRequest) { req.NamePrefix = "Violator" }, - errContains: "name_prefix invalid", - }, - "partitioned resource provides non-empty namespace": { - modReqFn: func(req *pbresource.ListRequest) { - req.Type = demo.TypeV1RecordLabel - req.Tenancy.Namespace = "bad" - }, - errContains: "cannot have a namespace", + testCases := map[string]func(*pbresource.ListRequest){ + "no type": func(req *pbresource.ListRequest) { req.Type = nil }, + "no tenancy": func(req *pbresource.ListRequest) { req.Tenancy = nil }, + "partitioned resource provides non-empty namespace": func(req *pbresource.ListRequest) { + req.Type = demo.TypeV1RecordLabel + req.Tenancy.Namespace = "bad" }, } - for desc, tc := range testCases { + for desc, modFn := range testCases { t.Run(desc, func(t *testing.T) { req := &pbresource.ListRequest{ Type: demo.TypeV2Album, Tenancy: resource.DefaultNamespacedTenancy(), } - tc.modReqFn(req) + modFn(req) _, err := client.List(testContext(t), req) require.Error(t, err) require.Equal(t, codes.InvalidArgument.String(), status.Code(err).String()) - require.ErrorContains(t, err, tc.errContains) }) } } @@ -165,7 +126,7 @@ func TestList_Tenancy_Defaults_And_Normalization(t *testing.T) { client := testClient(t, server) // Write partition scoped record label - recordLabel, err := demo.GenerateV1RecordLabel("looney-tunes") + recordLabel, err := demo.GenerateV1RecordLabel("LooneyTunes") require.NoError(t, err) recordLabelRsp, err := client.Write(ctx, &pbresource.WriteRequest{Resource: recordLabel}) require.NoError(t, err) @@ -189,6 +150,7 @@ func TestList_Tenancy_Defaults_And_Normalization(t *testing.T) { prototest.AssertDeepEqual(t, artistRsp.Resource, listRsp.Resources[0]) } }) + } } diff --git a/agent/grpc-external/services/resource/read_test.go b/agent/grpc-external/services/resource/read_test.go index 2afdfeab0e1ea..2601689bc6c4b 100644 --- a/agent/grpc-external/services/resource/read_test.go +++ b/agent/grpc-external/services/resource/read_test.go @@ -6,7 +6,6 @@ package resource import ( "context" "fmt" - "strings" "sync" "testing" @@ -35,114 +34,46 @@ func TestRead_InputValidation(t *testing.T) { tenancy.RegisterTypes(server.Registry) demo.RegisterTypes(server.Registry) - type testCase struct { - modFn func(artistId, recordlabelId, executiveId *pbresource.ID) *pbresource.ID - errContains string - } - - testCases := map[string]testCase{ - "no id": { - modFn: func(_, _, _ *pbresource.ID) *pbresource.ID { - return nil - }, - errContains: "id is required", - }, - "no type": { - modFn: func(artistId, _, _ *pbresource.ID) *pbresource.ID { - artistId.Type = nil - return artistId - }, - errContains: "id.type is required", - }, - "no name": { - modFn: func(artistId, _, _ *pbresource.ID) *pbresource.ID { - artistId.Name = "" - return artistId - }, - errContains: "id.name invalid", - }, - "name is mixed case": { - modFn: func(artistId, _, _ *pbresource.ID) *pbresource.ID { - artistId.Name = "MixedCaseNotAllowed" - return artistId - }, - errContains: "id.name invalid", + testCases := map[string]func(artistId, recordlabelId, executiveId *pbresource.ID) *pbresource.ID{ + "no id": func(_, _, _ *pbresource.ID) *pbresource.ID { return nil }, + "no type": func(artistId, _, _ *pbresource.ID) *pbresource.ID { + artistId.Type = nil + return artistId }, - "name too long": { - modFn: func(artistId, _, _ *pbresource.ID) *pbresource.ID { - artistId.Name = strings.Repeat("a", resource.MaxNameLength+1) - return artistId - }, - errContains: "id.name invalid", + "no name": func(artistId, _, _ *pbresource.ID) *pbresource.ID { + artistId.Name = "" + return artistId }, - "partition is mixed case": { - modFn: func(artistId, _, _ *pbresource.ID) *pbresource.ID { - artistId.Tenancy.Partition = "Default" - return artistId - }, - errContains: "id.tenancy.partition invalid", + "partition scope with non-empty namespace": func(_, recordLabelId, _ *pbresource.ID) *pbresource.ID { + recordLabelId.Tenancy.Namespace = "ishouldnothaveanamespace" + return recordLabelId }, - "partition too long": { - modFn: func(artistId, _, _ *pbresource.ID) *pbresource.ID { - artistId.Tenancy.Partition = strings.Repeat("p", resource.MaxNameLength+1) - return artistId - }, - errContains: "id.tenancy.partition invalid", + "cluster scope with non-empty partition": func(_, _, executiveId *pbresource.ID) *pbresource.ID { + executiveId.Tenancy = &pbresource.Tenancy{Partition: resource.DefaultPartitionName} + return executiveId }, - "namespace is mixed case": { - modFn: func(artistId, _, _ *pbresource.ID) *pbresource.ID { - artistId.Tenancy.Namespace = "Default" - return artistId - }, - errContains: "id.tenancy.namespace invalid", - }, - "namespace too long": { - modFn: func(artistId, _, _ *pbresource.ID) *pbresource.ID { - artistId.Tenancy.Namespace = strings.Repeat("n", resource.MaxNameLength+1) - return artistId - }, - errContains: "id.tenancy.namespace invalid", - }, - "partition scope with non-empty namespace": { - modFn: func(_, recordLabelId, _ *pbresource.ID) *pbresource.ID { - recordLabelId.Tenancy.Namespace = "ishouldnothaveanamespace" - return recordLabelId - }, - errContains: "cannot have a namespace", - }, - "cluster scope with non-empty partition": { - modFn: func(_, _, executiveId *pbresource.ID) *pbresource.ID { - executiveId.Tenancy = &pbresource.Tenancy{Partition: resource.DefaultPartitionName} - return executiveId - }, - errContains: "cannot have a partition", - }, - "cluster scope with non-empty namespace": { - modFn: func(_, _, executiveId *pbresource.ID) *pbresource.ID { - executiveId.Tenancy = &pbresource.Tenancy{Namespace: resource.DefaultNamespaceName} - return executiveId - }, - errContains: "cannot have a namespace", + "cluster scope with non-empty namespace": func(_, _, executiveId *pbresource.ID) *pbresource.ID { + executiveId.Tenancy = &pbresource.Tenancy{Namespace: resource.DefaultNamespaceName} + return executiveId }, } - for desc, tc := range testCases { + for desc, modFn := range testCases { t.Run(desc, func(t *testing.T) { artist, err := demo.GenerateV2Artist() require.NoError(t, err) - recordLabel, err := demo.GenerateV1RecordLabel("looney-tunes") + recordLabel, err := demo.GenerateV1RecordLabel("LoonyTunes") require.NoError(t, err) - executive, err := demo.GenerateV1Executive("music-man", "CEO") + executive, err := demo.GenerateV1Executive("MusicMan", "CEO") require.NoError(t, err) // Each test case picks which resource to use based on the resource type's scope. - req := &pbresource.ReadRequest{Id: tc.modFn(artist.Id, recordLabel.Id, executive.Id)} + req := &pbresource.ReadRequest{Id: modFn(artist.Id, recordLabel.Id, executive.Id)} _, err = client.Read(testContext(t), req) require.Error(t, err) require.Equal(t, codes.InvalidArgument.String(), status.Code(err).String()) - require.ErrorContains(t, err, tc.errContains) }) } } @@ -163,50 +94,34 @@ func TestRead_TypeNotFound(t *testing.T) { func TestRead_ResourceNotFound(t *testing.T) { for desc, tc := range readTestCases() { t.Run(desc, func(t *testing.T) { - type tenancyCase struct { - modFn func(artistId, recordlabelId *pbresource.ID) *pbresource.ID - errContains string - } - tenancyCases := map[string]tenancyCase{ - "resource not found by name": { - modFn: func(artistId, _ *pbresource.ID) *pbresource.ID { - artistId.Name = "bogusname" - return artistId - }, - errContains: "resource not found", + tenancyCases := map[string]func(artistId, recordlabelId *pbresource.ID) *pbresource.ID{ + "resource not found by name": func(artistId, _ *pbresource.ID) *pbresource.ID { + artistId.Name = "bogusname" + return artistId }, - "partition not found when namespace scoped": { - modFn: func(artistId, _ *pbresource.ID) *pbresource.ID { - id := clone(artistId) - id.Tenancy.Partition = "boguspartition" - return id - }, - errContains: "partition not found", + "partition not found when namespace scoped": func(artistId, _ *pbresource.ID) *pbresource.ID { + id := clone(artistId) + id.Tenancy.Partition = "boguspartition" + return id }, - "namespace not found when namespace scoped": { - modFn: func(artistId, _ *pbresource.ID) *pbresource.ID { - id := clone(artistId) - id.Tenancy.Namespace = "bogusnamespace" - return id - }, - errContains: "namespace not found", + "namespace not found when namespace scoped": func(artistId, _ *pbresource.ID) *pbresource.ID { + id := clone(artistId) + id.Tenancy.Namespace = "bogusnamespace" + return id }, - "partition not found when partition scoped": { - modFn: func(_, recordLabelId *pbresource.ID) *pbresource.ID { - id := clone(recordLabelId) - id.Tenancy.Partition = "boguspartition" - return id - }, - errContains: "partition not found", + "partition not found when partition scoped": func(_, recordLabelId *pbresource.ID) *pbresource.ID { + id := clone(recordLabelId) + id.Tenancy.Partition = "boguspartition" + return id }, } - for tenancyDesc, tenancyCase := range tenancyCases { + for tenancyDesc, modFn := range tenancyCases { t.Run(tenancyDesc, func(t *testing.T) { server := testServer(t) demo.RegisterTypes(server.Registry) client := testClient(t, server) - recordLabel, err := demo.GenerateV1RecordLabel("looney-tunes") + recordLabel, err := demo.GenerateV1RecordLabel("LoonyTunes") require.NoError(t, err) recordLabel, err = server.Backend.WriteCAS(tc.ctx, recordLabel) require.NoError(t, err) @@ -217,10 +132,10 @@ func TestRead_ResourceNotFound(t *testing.T) { require.NoError(t, err) // Each tenancy test case picks which resource to use based on the resource type's scope. - _, err = client.Read(tc.ctx, &pbresource.ReadRequest{Id: tenancyCase.modFn(artist.Id, recordLabel.Id)}) + _, err = client.Read(tc.ctx, &pbresource.ReadRequest{Id: modFn(artist.Id, recordLabel.Id)}) require.Error(t, err) require.Equal(t, codes.NotFound.String(), status.Code(err).String()) - require.ErrorContains(t, err, tenancyCase.errContains) + require.Contains(t, err.Error(), "resource not found") }) } }) @@ -261,7 +176,7 @@ func TestRead_Success(t *testing.T) { demo.RegisterTypes(server.Registry) client := testClient(t, server) - recordLabel, err := demo.GenerateV1RecordLabel("looney-tunes") + recordLabel, err := demo.GenerateV1RecordLabel("LoonyTunes") require.NoError(t, err) recordLabel, err = server.Backend.WriteCAS(tc.ctx, recordLabel) require.NoError(t, err) diff --git a/agent/grpc-external/services/resource/server.go b/agent/grpc-external/services/resource/server.go index 1084fd3860a17..5fc5a01fafd77 100644 --- a/agent/grpc-external/services/resource/server.go +++ b/agent/grpc-external/services/resource/server.go @@ -5,7 +5,6 @@ package resource import ( "context" - "strings" "github.com/hashicorp/go-hclog" "google.golang.org/grpc" @@ -130,12 +129,16 @@ func isGRPCStatusError(err error) bool { } func validateId(id *pbresource.ID, errorPrefix string) error { - if id.Type == nil { - return status.Errorf(codes.InvalidArgument, "%s.type is required", errorPrefix) + var field string + switch { + case id.Type == nil: + field = "type" + case id.Name == "": + field = "name" } - if err := resource.ValidateName(id.Name); err != nil { - return status.Errorf(codes.InvalidArgument, "%s.name invalid: %v", errorPrefix, err) + if field != "" { + return status.Errorf(codes.InvalidArgument, "%s.%s is required", errorPrefix, field) } // Better UX: Allow callers to pass in nil tenancy. Defaulting and inheritance of tenancy @@ -149,61 +152,7 @@ func validateId(id *pbresource.ID, errorPrefix string) error { } } - if id.Tenancy.Partition != "" { - if err := resource.ValidateName(id.Tenancy.Partition); err != nil { - return status.Errorf(codes.InvalidArgument, "%s.tenancy.partition invalid: %v", errorPrefix, err) - } - } - if id.Tenancy.Namespace != "" { - if err := resource.ValidateName(id.Tenancy.Namespace); err != nil { - return status.Errorf(codes.InvalidArgument, "%s.tenancy.namespace invalid: %v", errorPrefix, err) - } - } - // TODO(spatel): NET-5475 - Remove as part of peer_name moving to PeerTenancy - if id.Tenancy.PeerName == "" { - id.Tenancy.PeerName = resource.DefaultPeerName - } - - return nil -} - -func validateRef(ref *pbresource.Reference, errorPrefix string) error { - if ref.Type == nil { - return status.Errorf(codes.InvalidArgument, "%s.type is required", errorPrefix) - } - if err := resource.ValidateName(ref.Name); err != nil { - return status.Errorf(codes.InvalidArgument, "%s.name invalid: %v", errorPrefix, err) - } - if err := resource.ValidateName(ref.Tenancy.Partition); err != nil { - return status.Errorf(codes.InvalidArgument, "%s.tenancy.partition invalid: %v", errorPrefix, err) - } - if err := resource.ValidateName(ref.Tenancy.Namespace); err != nil { - return status.Errorf(codes.InvalidArgument, "%s.tenancy.namespace invalid: %v", errorPrefix, err) - } - return nil -} - -func validateWildcardTenancy(tenancy *pbresource.Tenancy, namePrefix string) error { - // Partition has to be a valid name if not wildcard or empty - if tenancy.Partition != "" && tenancy.Partition != "*" { - if err := resource.ValidateName(tenancy.Partition); err != nil { - return status.Errorf(codes.InvalidArgument, "tenancy.partition invalid: %v", err) - } - } - - // Namespace has to be a valid name if not wildcard or empty - if tenancy.Namespace != "" && tenancy.Namespace != "*" { - if err := resource.ValidateName(tenancy.Namespace); err != nil { - return status.Errorf(codes.InvalidArgument, "tenancy.namespace invalid: %v", err) - } - } - - // Not doing a strict resource name validation here because the prefix can be - // something like "foo-" which is a valid prefix but not valid resource name. - // relax validation to just check for lowercasing - if namePrefix != strings.ToLower(namePrefix) { - return status.Errorf(codes.InvalidArgument, "name_prefix invalid: must be lowercase alphanumeric, got: %v", namePrefix) - } + resource.Normalize(id.Tenancy) return nil } @@ -216,7 +165,7 @@ func v1TenancyExists(reg *resource.Registration, v1Bridge TenancyBridge, tenancy case err != nil: return err case !exists: - return status.Errorf(errCode, "partition not found: %v", tenancy.Partition) + return status.Errorf(errCode, "partition resource not found: %v", tenancy.Partition) } } @@ -226,7 +175,7 @@ func v1TenancyExists(reg *resource.Registration, v1Bridge TenancyBridge, tenancy case err != nil: return err case !exists: - return status.Errorf(errCode, "namespace not found: %v", tenancy.Namespace) + return status.Errorf(errCode, "namespace resource not found: %v", tenancy.Namespace) } } return nil diff --git a/agent/grpc-external/services/resource/server_test.go b/agent/grpc-external/services/resource/server_test.go index ffe7df52c401c..99add64971218 100644 --- a/agent/grpc-external/services/resource/server_test.go +++ b/agent/grpc-external/services/resource/server_test.go @@ -6,6 +6,7 @@ package resource import ( "context" "fmt" + "strings" "testing" "github.com/stretchr/testify/mock" @@ -165,6 +166,14 @@ func wildcardTenancyCases() map[string]struct { PeerName: "local", }, }, + "namespaced type with uppercase partition and namespace": { + typ: demo.TypeV2Artist, + tenancy: &pbresource.Tenancy{ + Partition: "DEFAULT", + Namespace: "DEFAULT", + PeerName: "local", + }, + }, "namespaced type with wildcard partition and empty namespace": { typ: demo.TypeV2Artist, tenancy: &pbresource.Tenancy{ @@ -189,6 +198,14 @@ func wildcardTenancyCases() map[string]struct { PeerName: "local", }, }, + "partitioned type with uppercase partition": { + typ: demo.TypeV1RecordLabel, + tenancy: &pbresource.Tenancy{ + Partition: "DEFAULT", + Namespace: "", + PeerName: "local", + }, + }, "partitioned type with wildcard partition": { typ: demo.TypeV1RecordLabel, tenancy: &pbresource.Tenancy{ @@ -207,6 +224,12 @@ func tenancyCases() map[string]func(artistId, recordlabelId *pbresource.ID) *pbr "namespaced resource provides nonempty partition and namespace": func(artistId, recordLabelId *pbresource.ID) *pbresource.ID { return artistId }, + "namespaced resource provides uppercase partition and namespace": func(artistId, _ *pbresource.ID) *pbresource.ID { + id := clone(artistId) + id.Tenancy.Partition = strings.ToUpper(artistId.Tenancy.Partition) + id.Tenancy.Namespace = strings.ToUpper(artistId.Tenancy.Namespace) + return id + }, "namespaced resource inherits tokens partition when empty": func(artistId, _ *pbresource.ID) *pbresource.ID { id := clone(artistId) id.Tenancy.Partition = "" @@ -231,6 +254,11 @@ func tenancyCases() map[string]func(artistId, recordlabelId *pbresource.ID) *pbr "partitioned resource provides nonempty partition": func(_, recordLabelId *pbresource.ID) *pbresource.ID { return recordLabelId }, + "partitioned resource provides uppercase partition": func(_, recordLabelId *pbresource.ID) *pbresource.ID { + id := clone(recordLabelId) + id.Tenancy.Partition = strings.ToUpper(recordLabelId.Tenancy.Partition) + return id + }, "partitioned resource inherits tokens partition when empty": func(_, recordLabelId *pbresource.ID) *pbresource.ID { id := clone(recordLabelId) id.Tenancy.Partition = "" diff --git a/agent/grpc-external/services/resource/watch.go b/agent/grpc-external/services/resource/watch.go index 44b0a83caa942..f20d3f00f875a 100644 --- a/agent/grpc-external/services/resource/watch.go +++ b/agent/grpc-external/services/resource/watch.go @@ -110,9 +110,8 @@ func (s *Server) validateWatchListRequest(req *pbresource.WatchListRequest) (*re return nil, err } - if err := validateWildcardTenancy(req.Tenancy, req.NamePrefix); err != nil { - return nil, err - } + // Lowercase + resource.Normalize(req.Tenancy) // Error when partition scoped and namespace not empty. if reg.Scope == resource.ScopePartition && req.Tenancy.Namespace != "" { diff --git a/agent/grpc-external/services/resource/watch_test.go b/agent/grpc-external/services/resource/watch_test.go index 5e5590d3f9fdc..051264441bbc8 100644 --- a/agent/grpc-external/services/resource/watch_test.go +++ b/agent/grpc-external/services/resource/watch_test.go @@ -7,7 +7,6 @@ import ( "context" "errors" "io" - "strings" "testing" "time" @@ -28,61 +27,24 @@ import ( func TestWatchList_InputValidation(t *testing.T) { server := testServer(t) client := testClient(t, server) - demo.RegisterTypes(server.Registry) - type testCase struct { - modFn func(*pbresource.WatchListRequest) - errContains string - } + demo.RegisterTypes(server.Registry) - testCases := map[string]testCase{ - "no type": { - modFn: func(req *pbresource.WatchListRequest) { req.Type = nil }, - errContains: "type is required", - }, - "no tenancy": { - modFn: func(req *pbresource.WatchListRequest) { req.Tenancy = nil }, - errContains: "tenancy is required", - }, - "partition mixed case": { - modFn: func(req *pbresource.WatchListRequest) { req.Tenancy.Partition = "Default" }, - errContains: "tenancy.partition invalid", - }, - "partition too long": { - modFn: func(req *pbresource.WatchListRequest) { - req.Tenancy.Partition = strings.Repeat("p", resource.MaxNameLength+1) - }, - errContains: "tenancy.partition invalid", - }, - "namespace mixed case": { - modFn: func(req *pbresource.WatchListRequest) { req.Tenancy.Namespace = "Default" }, - errContains: "tenancy.namespace invalid", - }, - "namespace too long": { - modFn: func(req *pbresource.WatchListRequest) { - req.Tenancy.Namespace = strings.Repeat("n", resource.MaxNameLength+1) - }, - errContains: "tenancy.namespace invalid", - }, - "name_prefix mixed case": { - modFn: func(req *pbresource.WatchListRequest) { req.NamePrefix = "Smashing" }, - errContains: "name_prefix invalid", - }, - "partitioned type provides non-empty namespace": { - modFn: func(req *pbresource.WatchListRequest) { - req.Type = demo.TypeV1RecordLabel - req.Tenancy.Namespace = "bad" - }, - errContains: "cannot have a namespace", + testCases := map[string]func(*pbresource.WatchListRequest){ + "no type": func(req *pbresource.WatchListRequest) { req.Type = nil }, + "no tenancy": func(req *pbresource.WatchListRequest) { req.Tenancy = nil }, + "partitioned type provides non-empty namespace": func(req *pbresource.WatchListRequest) { + req.Type = demo.TypeV1RecordLabel + req.Tenancy.Namespace = "bad" }, } - for desc, tc := range testCases { + for desc, modFn := range testCases { t.Run(desc, func(t *testing.T) { req := &pbresource.WatchListRequest{ Type: demo.TypeV2Album, Tenancy: resource.DefaultNamespacedTenancy(), } - tc.modFn(req) + modFn(req) stream, err := client.WatchList(testContext(t), req) require.NoError(t, err) @@ -90,7 +52,6 @@ func TestWatchList_InputValidation(t *testing.T) { _, err = stream.Recv() require.Error(t, err) require.Equal(t, codes.InvalidArgument.String(), status.Code(err).String()) - require.ErrorContains(t, err, tc.errContains) }) } } @@ -175,7 +136,7 @@ func TestWatchList_Tenancy_Defaults_And_Normalization(t *testing.T) { rspCh := handleResourceStream(t, stream) // Testcase will pick one of recordLabel or artist based on scope of type. - recordLabel, err := demo.GenerateV1RecordLabel("looney-tunes") + recordLabel, err := demo.GenerateV1RecordLabel("LooneyTunes") require.NoError(t, err) artist, err := demo.GenerateV2Artist() require.NoError(t, err) diff --git a/agent/grpc-external/services/resource/write_status.go b/agent/grpc-external/services/resource/write_status.go index 993c8382e2c49..0d3b68bb08766 100644 --- a/agent/grpc-external/services/resource/write_status.go +++ b/agent/grpc-external/services/resource/write_status.go @@ -178,17 +178,8 @@ func (s *Server) validateWriteStatusRequest(req *pbresource.WriteStatusRequest) } } - if err := validateId(req.Id, "id"); err != nil { - return nil, err - } - - for i, condition := range req.Status.Conditions { - if condition.Resource != nil { - if err := validateRef(condition.Resource, fmt.Sprintf("status.conditions[%d].resource", i)); err != nil { - return nil, err - } - } - } + // Lowercase + resource.Normalize(req.Id.Tenancy) // Check type exists. reg, err := s.resolveType(req.Id.Type) diff --git a/agent/grpc-external/services/resource/write_status_test.go b/agent/grpc-external/services/resource/write_status_test.go index 1ddf738632365..5b71983475d94 100644 --- a/agent/grpc-external/services/resource/write_status_test.go +++ b/agent/grpc-external/services/resource/write_status_test.go @@ -74,155 +74,64 @@ func TestWriteStatus_InputValidation(t *testing.T) { demo.RegisterTypes(server.Registry) testCases := map[string]struct { - typ *pbresource.Type - modFn func(req *pbresource.WriteStatusRequest) - errContains string + typ *pbresource.Type + modFn func(req *pbresource.WriteStatusRequest) }{ "no id": { - typ: demo.TypeV2Artist, - modFn: func(req *pbresource.WriteStatusRequest) { req.Id = nil }, - errContains: "id is required", + typ: demo.TypeV2Artist, + modFn: func(req *pbresource.WriteStatusRequest) { req.Id = nil }, }, "no type": { - typ: demo.TypeV2Artist, - modFn: func(req *pbresource.WriteStatusRequest) { req.Id.Type = nil }, - errContains: "id.type is required", + typ: demo.TypeV2Artist, + modFn: func(req *pbresource.WriteStatusRequest) { req.Id.Type = nil }, }, "no name": { - typ: demo.TypeV2Artist, - modFn: func(req *pbresource.WriteStatusRequest) { req.Id.Name = "" }, - errContains: "id.name is required", + typ: demo.TypeV2Artist, + modFn: func(req *pbresource.WriteStatusRequest) { req.Id.Name = "" }, }, "no uid": { - typ: demo.TypeV2Artist, - modFn: func(req *pbresource.WriteStatusRequest) { req.Id.Uid = "" }, - errContains: "id.uid is required", - }, - "name mixed case": { - typ: demo.TypeV2Artist, - modFn: func(req *pbresource.WriteStatusRequest) { req.Id.Name = "U2" }, - errContains: "id.name invalid", - }, - "name too long": { - typ: demo.TypeV2Artist, - modFn: func(req *pbresource.WriteStatusRequest) { - req.Id.Name = strings.Repeat("a", resource.MaxNameLength+1) - }, - errContains: "id.name invalid", - }, - "partition mixed case": { - typ: demo.TypeV2Artist, - modFn: func(req *pbresource.WriteStatusRequest) { req.Id.Tenancy.Partition = "Default" }, - errContains: "id.tenancy.partition invalid", - }, - "partition too long": { - typ: demo.TypeV2Artist, - modFn: func(req *pbresource.WriteStatusRequest) { - req.Id.Tenancy.Partition = strings.Repeat("p", resource.MaxNameLength+1) - }, - errContains: "id.tenancy.partition invalid", - }, - "namespace mixed case": { - typ: demo.TypeV2Artist, - modFn: func(req *pbresource.WriteStatusRequest) { req.Id.Tenancy.Namespace = "Default" }, - errContains: "id.tenancy.namespace invalid", - }, - "namespace too long": { - typ: demo.TypeV2Artist, - modFn: func(req *pbresource.WriteStatusRequest) { - req.Id.Tenancy.Namespace = strings.Repeat("n", resource.MaxNameLength+1) - }, - errContains: "id.tenancy.namespace invalid", + typ: demo.TypeV2Artist, + modFn: func(req *pbresource.WriteStatusRequest) { req.Id.Uid = "" }, }, "no key": { - typ: demo.TypeV2Artist, - modFn: func(req *pbresource.WriteStatusRequest) { req.Key = "" }, - errContains: "key is required", + typ: demo.TypeV2Artist, + modFn: func(req *pbresource.WriteStatusRequest) { req.Key = "" }, }, "no status": { - typ: demo.TypeV2Artist, - modFn: func(req *pbresource.WriteStatusRequest) { req.Status = nil }, - errContains: "status is required", + typ: demo.TypeV2Artist, + modFn: func(req *pbresource.WriteStatusRequest) { req.Status = nil }, }, "no observed generation": { - typ: demo.TypeV2Artist, - modFn: func(req *pbresource.WriteStatusRequest) { req.Status.ObservedGeneration = "" }, - errContains: "status.observed_generation is required", + typ: demo.TypeV2Artist, + modFn: func(req *pbresource.WriteStatusRequest) { req.Status.ObservedGeneration = "" }, }, "bad observed generation": { - typ: demo.TypeV2Artist, - modFn: func(req *pbresource.WriteStatusRequest) { req.Status.ObservedGeneration = "bogus" }, - errContains: "status.observed_generation is not valid", + typ: demo.TypeV2Artist, + modFn: func(req *pbresource.WriteStatusRequest) { req.Status.ObservedGeneration = "bogus" }, }, "no condition type": { - typ: demo.TypeV2Artist, - modFn: func(req *pbresource.WriteStatusRequest) { req.Status.Conditions[0].Type = "" }, - errContains: "status.conditions[0].type is required", + typ: demo.TypeV2Artist, + modFn: func(req *pbresource.WriteStatusRequest) { req.Status.Conditions[0].Type = "" }, }, "no reference type": { - typ: demo.TypeV2Artist, - modFn: func(req *pbresource.WriteStatusRequest) { req.Status.Conditions[0].Resource.Type = nil }, - errContains: "status.conditions[0].resource.type is required", + typ: demo.TypeV2Artist, + modFn: func(req *pbresource.WriteStatusRequest) { req.Status.Conditions[0].Resource.Type = nil }, }, "no reference tenancy": { - typ: demo.TypeV2Artist, - modFn: func(req *pbresource.WriteStatusRequest) { req.Status.Conditions[0].Resource.Tenancy = nil }, - errContains: "status.conditions[0].resource.tenancy is required", + typ: demo.TypeV2Artist, + modFn: func(req *pbresource.WriteStatusRequest) { req.Status.Conditions[0].Resource.Tenancy = nil }, }, "no reference name": { - typ: demo.TypeV2Artist, - modFn: func(req *pbresource.WriteStatusRequest) { req.Status.Conditions[0].Resource.Name = "" }, - errContains: "status.conditions[0].resource.name is required", - }, - "reference name mixed case": { - typ: demo.TypeV2Artist, - modFn: func(req *pbresource.WriteStatusRequest) { req.Status.Conditions[0].Resource.Name = "U2" }, - errContains: "status.conditions[0].resource.name invalid", - }, - "reference name too long": { - typ: demo.TypeV2Artist, - modFn: func(req *pbresource.WriteStatusRequest) { - req.Status.Conditions[0].Resource.Name = strings.Repeat("r", resource.MaxNameLength+1) - }, - errContains: "status.conditions[0].resource.name invalid", - }, - "reference partition mixed case": { - typ: demo.TypeV2Artist, - modFn: func(req *pbresource.WriteStatusRequest) { - req.Status.Conditions[0].Resource.Tenancy.Partition = "Default" - }, - errContains: "status.conditions[0].resource.tenancy.partition invalid", - }, - "reference partition too long": { - typ: demo.TypeV2Artist, - modFn: func(req *pbresource.WriteStatusRequest) { - req.Status.Conditions[0].Resource.Tenancy.Partition = strings.Repeat("p", resource.MaxNameLength+1) - }, - errContains: "status.conditions[0].resource.tenancy.partition invalid", - }, - "reference namespace mixed case": { - typ: demo.TypeV2Artist, - modFn: func(req *pbresource.WriteStatusRequest) { - req.Status.Conditions[0].Resource.Tenancy.Namespace = "Default" - }, - errContains: "status.conditions[0].resource.tenancy.namespace invalid", - }, - "reference namespace too long": { - typ: demo.TypeV2Artist, - modFn: func(req *pbresource.WriteStatusRequest) { - req.Status.Conditions[0].Resource.Tenancy.Namespace = strings.Repeat("n", resource.MaxNameLength+1) - }, - errContains: "status.conditions[0].resource.tenancy.namespace invalid", + typ: demo.TypeV2Artist, + modFn: func(req *pbresource.WriteStatusRequest) { req.Status.Conditions[0].Resource.Name = "" }, }, "updated at provided": { - typ: demo.TypeV2Artist, - modFn: func(req *pbresource.WriteStatusRequest) { req.Status.UpdatedAt = timestamppb.Now() }, - errContains: "status.updated_at is automatically set and cannot be provided", + typ: demo.TypeV2Artist, + modFn: func(req *pbresource.WriteStatusRequest) { req.Status.UpdatedAt = timestamppb.Now() }, }, "partition scoped type provides namespace in tenancy": { - typ: demo.TypeV1RecordLabel, - modFn: func(req *pbresource.WriteStatusRequest) { req.Id.Tenancy.Namespace = "bad" }, - errContains: "cannot have a namespace", + typ: demo.TypeV1RecordLabel, + modFn: func(req *pbresource.WriteStatusRequest) { req.Id.Tenancy.Namespace = "bad" }, }, } for desc, tc := range testCases { @@ -233,7 +142,7 @@ func TestWriteStatus_InputValidation(t *testing.T) { case resource.EqualType(demo.TypeV2Artist, tc.typ): res, err = demo.GenerateV2Artist() case resource.EqualType(demo.TypeV1RecordLabel, tc.typ): - res, err = demo.GenerateV1RecordLabel("looney-tunes") + res, err = demo.GenerateV1RecordLabel("Looney Tunes") default: t.Fatal("unsupported type", tc.typ) } @@ -248,7 +157,6 @@ func TestWriteStatus_InputValidation(t *testing.T) { _, err = client.WriteStatus(testContext(t), req) require.Error(t, err) require.Equal(t, codes.InvalidArgument.String(), status.Code(err).String()) - require.ErrorContains(t, err, tc.errContains) }) } } @@ -302,6 +210,13 @@ func TestWriteStatus_Tenancy_Defaults(t *testing.T) { scope: resource.ScopeNamespace, modFn: func(req *pbresource.WriteStatusRequest) {}, }, + "namespaced resource provides uppercase partition and namespace": { + scope: resource.ScopeNamespace, + modFn: func(req *pbresource.WriteStatusRequest) { + req.Id.Tenancy.Partition = strings.ToUpper(req.Id.Tenancy.Partition) + req.Id.Tenancy.Namespace = strings.ToUpper(req.Id.Tenancy.Namespace) + }, + }, "namespaced resource inherits tokens partition when empty": { scope: resource.ScopeNamespace, modFn: func(req *pbresource.WriteStatusRequest) { req.Id.Tenancy.Partition = "" }, @@ -325,6 +240,12 @@ func TestWriteStatus_Tenancy_Defaults(t *testing.T) { scope: resource.ScopePartition, modFn: func(req *pbresource.WriteStatusRequest) {}, }, + "partitioned resource provides uppercase partition": { + scope: resource.ScopePartition, + modFn: func(req *pbresource.WriteStatusRequest) { + req.Id.Tenancy.Partition = strings.ToUpper(req.Id.Tenancy.Partition) + }, + }, "partitioned resource inherits tokens partition when empty": { scope: resource.ScopePartition, modFn: func(req *pbresource.WriteStatusRequest) { req.Id.Tenancy.Partition = "" }, @@ -342,7 +263,7 @@ func TestWriteStatus_Tenancy_Defaults(t *testing.T) { case resource.ScopeNamespace: res, err = demo.GenerateV2Artist() case resource.ScopePartition: - res, err = demo.GenerateV1RecordLabel("looney-tunes") + res, err = demo.GenerateV1RecordLabel("Looney Tunes") } require.NoError(t, err) @@ -359,7 +280,7 @@ func TestWriteStatus_Tenancy_Defaults(t *testing.T) { require.NoError(t, err) res = rsp.Resource - // Re-read resource and verify status successfully written (not nil) + // Re-read resoruce and verify status successfully written (not nil) _, err = client.Read(testContext(t), &pbresource.ReadRequest{Id: res.Id}) require.NoError(t, err) res = rsp.Resource @@ -406,7 +327,7 @@ func TestWriteStatus_Tenancy_NotFound(t *testing.T) { case resource.ScopeNamespace: res, err = demo.GenerateV2Artist() case resource.ScopePartition: - res, err = demo.GenerateV1RecordLabel("looney-tunes") + res, err = demo.GenerateV1RecordLabel("Looney Tunes") } require.NoError(t, err) diff --git a/agent/grpc-external/services/resource/write_test.go b/agent/grpc-external/services/resource/write_test.go index 9f7704b52b97c..3828ff9753f2d 100644 --- a/agent/grpc-external/services/resource/write_test.go +++ b/agent/grpc-external/services/resource/write_test.go @@ -29,123 +29,54 @@ import ( func TestWrite_InputValidation(t *testing.T) { server := testServer(t) client := testClient(t, server) - demo.RegisterTypes(server.Registry) - type testCase struct { - modFn func(artist, recordLabel *pbresource.Resource) *pbresource.Resource - errContains string - } + demo.RegisterTypes(server.Registry) - testCases := map[string]testCase{ - "no resource": { - modFn: func(_, _ *pbresource.Resource) *pbresource.Resource { - return nil - }, - errContains: "resource is required", - }, - "no id": { - modFn: func(artist, _ *pbresource.Resource) *pbresource.Resource { - artist.Id = nil - return artist - }, - errContains: "resource.id is required", - }, - "no type": { - modFn: func(artist, _ *pbresource.Resource) *pbresource.Resource { - artist.Id.Type = nil - return artist - }, - errContains: "resource.id.type is required", - }, - "no name": { - modFn: func(artist, _ *pbresource.Resource) *pbresource.Resource { - artist.Id.Name = "" - return artist - }, - errContains: "resource.id.name invalid", - }, - "name is mixed case": { - modFn: func(artist, _ *pbresource.Resource) *pbresource.Resource { - artist.Id.Name = "MixedCaseNotAllowed" - return artist - }, - errContains: "resource.id.name invalid", + testCases := map[string]func(artist, recordLabel *pbresource.Resource) *pbresource.Resource{ + "no resource": func(artist, recordLabel *pbresource.Resource) *pbresource.Resource { return nil }, + "no id": func(artist, _ *pbresource.Resource) *pbresource.Resource { + artist.Id = nil + return artist }, - "name too long": { - modFn: func(artist, _ *pbresource.Resource) *pbresource.Resource { - artist.Id.Name = strings.Repeat("a", resource.MaxNameLength+1) - return artist - }, - errContains: "resource.id.name invalid", - }, - "wrong data type": { - modFn: func(artist, _ *pbresource.Resource) *pbresource.Resource { - var err error - artist.Data, err = anypb.New(&pbdemov2.Album{}) - require.NoError(t, err) - return artist - }, - errContains: "resource.data is of wrong type", - }, - "partition is mixed case": { - modFn: func(artist, _ *pbresource.Resource) *pbresource.Resource { - artist.Id.Tenancy.Partition = "Default" - return artist - }, - errContains: "resource.id.tenancy.partition invalid", - }, - "partition too long": { - modFn: func(artist, _ *pbresource.Resource) *pbresource.Resource { - artist.Id.Tenancy.Partition = strings.Repeat("p", resource.MaxNameLength+1) - return artist - }, - errContains: "resource.id.tenancy.partition invalid", + "no type": func(artist, _ *pbresource.Resource) *pbresource.Resource { + artist.Id.Type = nil + return artist }, - "namespace is mixed case": { - modFn: func(artist, _ *pbresource.Resource) *pbresource.Resource { - artist.Id.Tenancy.Namespace = "Default" - return artist - }, - errContains: "resource.id.tenancy.namespace invalid", + "no name": func(artist, _ *pbresource.Resource) *pbresource.Resource { + artist.Id.Name = "" + return artist }, - "namespace too long": { - modFn: func(artist, _ *pbresource.Resource) *pbresource.Resource { - artist.Id.Tenancy.Namespace = strings.Repeat("n", resource.MaxNameLength+1) - return artist - }, - errContains: "resource.id.tenancy.namespace invalid", + "wrong data type": func(artist, _ *pbresource.Resource) *pbresource.Resource { + var err error + artist.Data, err = anypb.New(&pbdemov2.Album{}) + require.NoError(t, err) + return artist }, - "fail validation hook": { - modFn: func(artist, _ *pbresource.Resource) *pbresource.Resource { - buffer := &pbdemov2.Artist{} - require.NoError(t, artist.Data.UnmarshalTo(buffer)) - buffer.Name = "" // name cannot be empty - require.NoError(t, artist.Data.MarshalFrom(buffer)) - return artist - }, - errContains: "artist.name required", + "fail validation hook": func(artist, _ *pbresource.Resource) *pbresource.Resource { + buffer := &pbdemov2.Artist{} + require.NoError(t, artist.Data.UnmarshalTo(buffer)) + buffer.Name = "" // name cannot be empty + require.NoError(t, artist.Data.MarshalFrom(buffer)) + return artist }, - "partition scope with non-empty namespace": { - modFn: func(_, recordLabel *pbresource.Resource) *pbresource.Resource { - recordLabel.Id.Tenancy.Namespace = "bogus" - return recordLabel - }, - errContains: "cannot have a namespace", + "partition scope with non-empty namespace": func(_, recordLabel *pbresource.Resource) *pbresource.Resource { + recordLabel.Id.Tenancy.Namespace = "bogus" + return recordLabel }, + // TODO(spatel): add cluster scope tests when we have an actual cluster scoped resource (e.g. partition) } - for desc, tc := range testCases { + for desc, modFn := range testCases { t.Run(desc, func(t *testing.T) { artist, err := demo.GenerateV2Artist() require.NoError(t, err) - recordLabel, err := demo.GenerateV1RecordLabel("looney-tunes") + recordLabel, err := demo.GenerateV1RecordLabel("LoonyTunes") require.NoError(t, err) - req := &pbresource.WriteRequest{Resource: tc.modFn(artist, recordLabel)} + req := &pbresource.WriteRequest{Resource: modFn(artist, recordLabel)} _, err = client.Write(testContext(t), req) require.Error(t, err) require.Equal(t, codes.InvalidArgument.String(), status.Code(err).String()) - require.ErrorContains(t, err, tc.errContains) }) } } @@ -153,6 +84,7 @@ func TestWrite_InputValidation(t *testing.T) { func TestWrite_OwnerValidation(t *testing.T) { server := testServer(t) client := testClient(t, server) + demo.RegisterTypes(server.Registry) type testCase struct { @@ -162,49 +94,15 @@ func TestWrite_OwnerValidation(t *testing.T) { testCases := map[string]testCase{ "no owner type": { modReqFn: func(req *pbresource.WriteRequest) { req.Resource.Owner.Type = nil }, - errorContains: "resource.owner.type is required", + errorContains: "resource.owner.type", }, "no owner tenancy": { modReqFn: func(req *pbresource.WriteRequest) { req.Resource.Owner.Tenancy = nil }, - errorContains: "resource.owner does not exist", + errorContains: "resource.owner", }, "no owner name": { modReqFn: func(req *pbresource.WriteRequest) { req.Resource.Owner.Name = "" }, - errorContains: "resource.owner.name invalid", - }, - "mixed case owner name": { - modReqFn: func(req *pbresource.WriteRequest) { req.Resource.Owner.Name = strings.ToUpper(req.Resource.Owner.Name) }, - errorContains: "resource.owner.name invalid", - }, - "owner name too long": { - modReqFn: func(req *pbresource.WriteRequest) { - req.Resource.Owner.Name = strings.Repeat("a", resource.MaxNameLength+1) - }, - errorContains: "resource.owner.name invalid", - }, - "owner partition is mixed case": { - modReqFn: func(req *pbresource.WriteRequest) { - req.Resource.Owner.Tenancy.Partition = "Default" - }, - errorContains: "resource.owner.tenancy.partition invalid", - }, - "owner partition too long": { - modReqFn: func(req *pbresource.WriteRequest) { - req.Resource.Owner.Tenancy.Partition = strings.Repeat("p", resource.MaxNameLength+1) - }, - errorContains: "resource.owner.tenancy.partition invalid", - }, - "owner namespace is mixed case": { - modReqFn: func(req *pbresource.WriteRequest) { - req.Resource.Owner.Tenancy.Namespace = "Default" - }, - errorContains: "resource.owner.tenancy.namespace invalid", - }, - "owner namespace too long": { - modReqFn: func(req *pbresource.WriteRequest) { - req.Resource.Owner.Tenancy.Namespace = strings.Repeat("n", resource.MaxNameLength+1) - }, - errorContains: "resource.owner.tenancy.namespace invalid", + errorContains: "resource.owner.name", }, } for desc, tc := range testCases { @@ -317,6 +215,14 @@ func TestWrite_Create_Success(t *testing.T) { }, expectedTenancy: resource.DefaultNamespacedTenancy(), }, + "namespaced resource provides uppercase partition and namespace": { + modFn: func(artist, _ *pbresource.Resource) *pbresource.Resource { + artist.Id.Tenancy.Partition = strings.ToUpper(artist.Id.Tenancy.Partition) + artist.Id.Tenancy.Namespace = strings.ToUpper(artist.Id.Tenancy.Namespace) + return artist + }, + expectedTenancy: resource.DefaultNamespacedTenancy(), + }, "namespaced resource inherits tokens partition when empty": { modFn: func(artist, _ *pbresource.Resource) *pbresource.Resource { artist.Id.Tenancy.Partition = "" @@ -360,6 +266,13 @@ func TestWrite_Create_Success(t *testing.T) { }, expectedTenancy: resource.DefaultPartitionedTenancy(), }, + "partitioned resource provides uppercase partition": { + modFn: func(_, recordLabel *pbresource.Resource) *pbresource.Resource { + recordLabel.Id.Tenancy.Partition = strings.ToUpper(recordLabel.Id.Tenancy.Partition) + return recordLabel + }, + expectedTenancy: resource.DefaultPartitionedTenancy(), + }, "partitioned resource inherits tokens partition when empty": { modFn: func(_, recordLabel *pbresource.Resource) *pbresource.Resource { recordLabel.Id.Tenancy.Partition = "" @@ -390,7 +303,7 @@ func TestWrite_Create_Success(t *testing.T) { client := testClient(t, server) demo.RegisterTypes(server.Registry) - recordLabel, err := demo.GenerateV1RecordLabel("looney-tunes") + recordLabel, err := demo.GenerateV1RecordLabel("LoonyTunes") require.NoError(t, err) artist, err := demo.GenerateV2Artist() @@ -418,7 +331,7 @@ func TestWrite_Create_Tenancy_NotFound(t *testing.T) { return artist }, errCode: codes.InvalidArgument, - errContains: "partition not found", + errContains: "partition", }, "namespaced resource provides nonexistant namespace": { modFn: func(artist, _ *pbresource.Resource) *pbresource.Resource { @@ -426,7 +339,7 @@ func TestWrite_Create_Tenancy_NotFound(t *testing.T) { return artist }, errCode: codes.InvalidArgument, - errContains: "namespace not found", + errContains: "namespace", }, "partitioned resource provides nonexistant partition": { modFn: func(_, recordLabel *pbresource.Resource) *pbresource.Resource { @@ -434,7 +347,7 @@ func TestWrite_Create_Tenancy_NotFound(t *testing.T) { return recordLabel }, errCode: codes.InvalidArgument, - errContains: "partition not found", + errContains: "partition", }, } for desc, tc := range testCases { @@ -443,7 +356,7 @@ func TestWrite_Create_Tenancy_NotFound(t *testing.T) { client := testClient(t, server) demo.RegisterTypes(server.Registry) - recordLabel, err := demo.GenerateV1RecordLabel("looney-tunes") + recordLabel, err := demo.GenerateV1RecordLabel("LoonyTunes") require.NoError(t, err) artist, err := demo.GenerateV2Artist() @@ -465,22 +378,22 @@ func TestWrite_Tenancy_MarkedForDeletion(t *testing.T) { }{ "namespaced resources partition marked for deletion": { modFn: func(artist, _ *pbresource.Resource, mockTenancyBridge *MockTenancyBridge) *pbresource.Resource { - mockTenancyBridge.On("IsPartitionMarkedForDeletion", "ap1").Return(true, nil) + mockTenancyBridge.On("IsPartitionMarkedForDeletion", "part1").Return(true, nil) return artist }, errContains: "partition marked for deletion", }, "namespaced resources namespace marked for deletion": { modFn: func(artist, _ *pbresource.Resource, mockTenancyBridge *MockTenancyBridge) *pbresource.Resource { - mockTenancyBridge.On("IsPartitionMarkedForDeletion", "ap1").Return(false, nil) - mockTenancyBridge.On("IsNamespaceMarkedForDeletion", "ap1", "ns1").Return(true, nil) + mockTenancyBridge.On("IsPartitionMarkedForDeletion", "part1").Return(false, nil) + mockTenancyBridge.On("IsNamespaceMarkedForDeletion", "part1", "ns1").Return(true, nil) return artist }, errContains: "namespace marked for deletion", }, "partitioned resources partition marked for deletion": { modFn: func(_, recordLabel *pbresource.Resource, mockTenancyBridge *MockTenancyBridge) *pbresource.Resource { - mockTenancyBridge.On("IsPartitionMarkedForDeletion", "ap1").Return(true, nil) + mockTenancyBridge.On("IsPartitionMarkedForDeletion", "part1").Return(true, nil) return recordLabel }, errContains: "partition marked for deletion", @@ -491,18 +404,18 @@ func TestWrite_Tenancy_MarkedForDeletion(t *testing.T) { server := testServer(t) client := testClient(t, server) demo.RegisterTypes(server.Registry) - recordLabel, err := demo.GenerateV1RecordLabel("looney-tunes") + recordLabel, err := demo.GenerateV1RecordLabel("LoonyTunes") require.NoError(t, err) - recordLabel.Id.Tenancy.Partition = "ap1" + recordLabel.Id.Tenancy.Partition = "part1" artist, err := demo.GenerateV2Artist() require.NoError(t, err) - artist.Id.Tenancy.Partition = "ap1" + artist.Id.Tenancy.Partition = "part1" artist.Id.Tenancy.Namespace = "ns1" mockTenancyBridge := &MockTenancyBridge{} - mockTenancyBridge.On("PartitionExists", "ap1").Return(true, nil) - mockTenancyBridge.On("NamespaceExists", "ap1", "ns1").Return(true, nil) + mockTenancyBridge.On("PartitionExists", "part1").Return(true, nil) + mockTenancyBridge.On("NamespaceExists", "part1", "ns1").Return(true, nil) server.TenancyBridge = mockTenancyBridge _, err = client.Write(testContext(t), &pbresource.WriteRequest{Resource: tc.modFn(artist, recordLabel, mockTenancyBridge)}) diff --git a/agent/xds/endpoints.go b/agent/xds/endpoints.go index ff486f3228a8a..2fb0a4a1df598 100644 --- a/agent/xds/endpoints.go +++ b/agent/xds/endpoints.go @@ -880,13 +880,7 @@ func makeLoadAssignment(logger hclog.Logger, cfgSnap *proxycfg.ConfigSnapshot, c Endpoints: make([]*envoy_endpoint_v3.LocalityLbEndpoints, 0, len(endpointGroups)), } - if len(endpointGroups) > 1 { - cla.Policy = &envoy_endpoint_v3.ClusterLoadAssignment_Policy{ - // We choose such a large value here that the failover math should - // in effect not happen until zero instances are healthy. - OverprovisioningFactor: response.MakeUint32Value(100000), - } - } + setFullFailoverProvisioningFactor := len(endpointGroups) > 1 var priority uint32 @@ -897,6 +891,10 @@ func makeLoadAssignment(logger hclog.Logger, cfgSnap *proxycfg.ConfigSnapshot, c continue } + if len(endpointsByLocality) > 1 { + setFullFailoverProvisioningFactor = true + } + for _, endpoints := range endpointsByLocality { es := make([]*envoy_endpoint_v3.LbEndpoint, 0, len(endpointGroup.Endpoints)) @@ -930,6 +928,14 @@ func makeLoadAssignment(logger hclog.Logger, cfgSnap *proxycfg.ConfigSnapshot, c } } + if setFullFailoverProvisioningFactor { + cla.Policy = &envoy_endpoint_v3.ClusterLoadAssignment_Policy{ + // We choose such a large value here that the failover math should + // in effect not happen until zero instances are healthy. + OverprovisioningFactor: response.MakeUint32Value(100000), + } + } + return cla } diff --git a/agent/xds/listeners_apigateway.go b/agent/xds/listeners_apigateway.go index a4611895e2906..771a482972033 100644 --- a/agent/xds/listeners_apigateway.go +++ b/agent/xds/listeners_apigateway.go @@ -152,7 +152,11 @@ func (s *ResourceGenerator) makeAPIGatewayListeners(address string, cfgSnap *pro routes := make([]*structs.HTTPRouteConfigEntry, 0, len(readyListener.routeReferences)) for _, routeRef := range maps.Keys(readyListener.routeReferences) { - route, _ := cfgSnap.APIGateway.HTTPRoutes.Get(routeRef) + route, ok := cfgSnap.APIGateway.HTTPRoutes.Get(routeRef) + if !ok { + return nil, fmt.Errorf("missing route for routeRef %s:%s", routeRef.Kind, routeRef.Name) + } + routes = append(routes, route) } consolidatedRoutes := discoverychain.ConsolidateHTTPRoutes(cfgSnap.APIGateway.GatewayConfig, &readyListener.listenerCfg, routes...) @@ -297,11 +301,9 @@ func getReadyListeners(cfgSnap *proxycfg.ConfigSnapshot) map[string]readyListene continue } - routeKey := l.Name + routeRef.String() - for _, upstream := range routeUpstreamsForListener { // Insert or update readyListener for the listener to include this upstream - r, ok := ready[routeKey] + r, ok := ready[l.Name] if !ok { r = readyListener{ listenerKey: listenerKey, @@ -312,7 +314,7 @@ func getReadyListeners(cfgSnap *proxycfg.ConfigSnapshot) map[string]readyListene } r.routeReferences[routeRef] = struct{}{} r.upstreams = append(r.upstreams, upstream) - ready[routeKey] = r + ready[l.Name] = r } } } diff --git a/agent/xds/testdata/routes/api-gateway-with-multiple-hostnames.latest.golden b/agent/xds/testdata/routes/api-gateway-with-multiple-hostnames.latest.golden index 9e96457f3c3b9..b268a5e5ac5e6 100644 --- a/agent/xds/testdata/routes/api-gateway-with-multiple-hostnames.latest.golden +++ b/agent/xds/testdata/routes/api-gateway-with-multiple-hostnames.latest.golden @@ -8,52 +8,38 @@ "virtualHosts": [ { "domains": [ - "backend.example.com", - "backend.example.com:8080" + "frontend.example.com", + "frontend.example.com:8080" ], - "name": "api-gateway-http-5a84e719", + "name": "api-gateway-http-54620b06", "routes": [ { "match": { "prefix": "/" }, "route": { - "cluster": "backend.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul" + "cluster": "frontend.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul" } } ] - } - ] - }, - { - "@type": "type.googleapis.com/envoy.config.route.v3.RouteConfiguration", - "name": "8080", - "validateClusters": true, - "virtualHosts": [ + }, { "domains": [ - "frontend.example.com", - "frontend.example.com:8080" + "backend.example.com", + "backend.example.com:8080" ], - "name": "api-gateway-http-54620b06", + "name": "api-gateway-http-5a84e719", "routes": [ { "match": { "prefix": "/" }, "route": { - "cluster": "frontend.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul" + "cluster": "backend.default.dc1.internal.11111111-2222-3333-4444-555555555555.consul" } } ] - } - ] - }, - { - "@type": "type.googleapis.com/envoy.config.route.v3.RouteConfiguration", - "name": "8080", - "validateClusters": true, - "virtualHosts": [ + }, { "domains": [ "*.example.com", diff --git a/api/config_entry_rate_limit_ip.go b/api/config_entry_rate_limit_ip.go index 8df7d4c98e772..7af2a2658f2e1 100644 --- a/api/config_entry_rate_limit_ip.go +++ b/api/config_entry_rate_limit_ip.go @@ -4,8 +4,8 @@ package api type ReadWriteRatesConfig struct { - ReadRate float64 - WriteRate float64 + ReadRate float64 `alias:"read_rate"` + WriteRate float64 `alias:"write_rate"` } type RateLimitIPConfigEntry struct { @@ -16,8 +16,8 @@ type RateLimitIPConfigEntry struct { Meta map[string]string `json:",omitempty"` // overall limits - ReadRate float64 - WriteRate float64 + ReadRate float64 `alias:"read_rate"` + WriteRate float64 `alias:"write_rate"` //limits specific to a type of call ACL *ReadWriteRatesConfig `json:",omitempty"` // OperationCategoryACL OperationCategory = "ACL" diff --git a/internal/catalog/catalogtest/run_test.go b/internal/catalog/catalogtest/run_test.go index 5a6e1e62e3548..2c12785bbb0be 100644 --- a/internal/catalog/catalogtest/run_test.go +++ b/internal/catalog/catalogtest/run_test.go @@ -38,7 +38,7 @@ func runInMemResourceServiceAndControllers(t *testing.T, deps controllers.Depend func TestControllers_Integration(t *testing.T) { client := runInMemResourceServiceAndControllers(t, catalog.DefaultControllerDependencies()) - RunCatalogV2Beta1IntegrationTest(t, client) + RunCatalogV1Alpha1IntegrationTest(t, client) } func TestControllers_Lifecycle(t *testing.T) { diff --git a/internal/catalog/catalogtest/test_integration_v2beta1.go b/internal/catalog/catalogtest/test_integration_v2beta1.go index 79ffea7e79539..9f83ab3655912 100644 --- a/internal/catalog/catalogtest/test_integration_v2beta1.go +++ b/internal/catalog/catalogtest/test_integration_v2beta1.go @@ -26,7 +26,7 @@ var ( testData embed.FS ) -// RunCatalogV2Beta1IntegrationTest will push up a bunch of catalog related data and then +// RunCatalogV1Alpha1IntegrationTest will push up a bunch of catalog related data and then // verify that all the expected reconciliations happened correctly. This test is // intended to exercise a large swathe of behavior of the overall catalog package. // Besides just controller reconciliation behavior, the intent is also to verify @@ -38,7 +38,7 @@ var ( // is another RunCatalogIntegrationTestLifeCycle function that can be used for those // purposes. The two are distinct so that the data being published and the assertions // made against the system can be reused in upgrade tests. -func RunCatalogV2Beta1IntegrationTest(t *testing.T, client pbresource.ResourceServiceClient) { +func RunCatalogV1Alpha1IntegrationTest(t *testing.T, client pbresource.ResourceServiceClient) { t.Helper() PublishCatalogV2Beta1IntegrationTestData(t, client) diff --git a/internal/mesh/internal/controllers/sidecarproxy/builder/expose_paths.go b/internal/mesh/internal/controllers/sidecarproxy/builder/expose_paths.go index 6215ab540ca54..8ae2033cc5e98 100644 --- a/internal/mesh/internal/controllers/sidecarproxy/builder/expose_paths.go +++ b/internal/mesh/internal/controllers/sidecarproxy/builder/expose_paths.go @@ -29,7 +29,7 @@ func (b *Builder) buildExposePaths(workload *pbcatalog.Workload) { } func (b *Builder) addExposePathsListener(workload *pbcatalog.Workload, exposePath *pbmesh.ExposePath) *ListenerBuilder { - listenerName := fmt.Sprintf("exposed_path_%s", exposePathName(exposePath)) + listenerName := exposePathListenerName(exposePath) listener := &pbproxystate.Listener{ Name: listenerName, @@ -44,7 +44,7 @@ func (b *Builder) addExposePathsListener(workload *pbcatalog.Workload, exposePat listener.BindAddress = &pbproxystate.Listener_HostPort{ HostPort: &pbproxystate.HostPortAddress{ Host: meshAddress.Host, - Port: exposePath.LocalPathPort, + Port: exposePath.ListenerPort, }, } @@ -55,7 +55,7 @@ func (b *ListenerBuilder) addExposePathsRouter(exposePath *pbmesh.ExposePath) *L if b.listener == nil { return b } - destinationName := exposePathDestinationName(exposePath) + destinationName := exposePathRouteName(exposePath) var l7Protocol pbproxystate.L7Protocol @@ -88,7 +88,7 @@ func (b *ListenerBuilder) addExposePathsRouter(exposePath *pbmesh.ExposePath) *L } func (b *Builder) addExposePathsRoute(exposePath *pbmesh.ExposePath, clusterName string) *Builder { - routeName := exposePathDestinationName(exposePath) + routeName := exposePathRouteName(exposePath) routeRule := &pbproxystate.RouteRule{ Match: &pbproxystate.RouteMatch{ PathMatch: &pbproxystate.PathMatch{ @@ -120,12 +120,22 @@ func (b *Builder) addExposePathsRoute(exposePath *pbmesh.ExposePath, clusterName func exposePathName(exposePath *pbmesh.ExposePath) string { r := regexp.MustCompile(`[^a-zA-Z0-9]+`) - return r.ReplaceAllString(exposePath.Path, "") + // The regex removes anything not a letter or number from the path. + path := r.ReplaceAllString(exposePath.Path, "") + return path } -func exposePathDestinationName(exposePath *pbmesh.ExposePath) string { - path := exposePathName(exposePath) - return fmt.Sprintf("exposed_path_filter_%s_%d", path, exposePath.ListenerPort) +func exposePathListenerName(exposePath *pbmesh.ExposePath) string { + // The path could be empty, so the unique name for this exposed path is the path and listener port. + pathPort := fmt.Sprintf("%s%d", exposePathName(exposePath), exposePath.ListenerPort) + listenerName := fmt.Sprintf("exposed_path_%s", pathPort) + return listenerName +} + +func exposePathRouteName(exposePath *pbmesh.ExposePath) string { + // The path could be empty, so the unique name for this exposed path is the path and listener port. + pathPort := fmt.Sprintf("%s%d", exposePathName(exposePath), exposePath.ListenerPort) + return fmt.Sprintf("exposed_path_route_%s", pathPort) } func exposePathClusterName(exposePath *pbmesh.ExposePath) string { diff --git a/internal/mesh/internal/controllers/sidecarproxy/builder/testdata/source/l7-expose-paths.golden b/internal/mesh/internal/controllers/sidecarproxy/builder/testdata/source/l7-expose-paths.golden index df8af168212da..2d62699ecfbf9 100644 --- a/internal/mesh/internal/controllers/sidecarproxy/builder/testdata/source/l7-expose-paths.golden +++ b/internal/mesh/internal/controllers/sidecarproxy/builder/testdata/source/l7-expose-paths.golden @@ -107,16 +107,16 @@ "direction": "DIRECTION_INBOUND", "hostPort": { "host": "10.0.0.1", - "port": 9090 + "port": 1234 }, - "name": "exposed_path_health", + "name": "exposed_path_health1234", "routers": [ { "l7": { "route": { - "name": "exposed_path_filter_health_1234" + "name": "exposed_path_route_health1234" }, - "statPrefix": "exposed_path_filter_health_1234", + "statPrefix": "exposed_path_route_health1234", "staticRoute": true } } @@ -126,17 +126,17 @@ "direction": "DIRECTION_INBOUND", "hostPort": { "host": "10.0.0.1", - "port": 9091 + "port": 1235 }, - "name": "exposed_path_GetHealth", + "name": "exposed_path_GetHealth1235", "routers": [ { "l7": { "protocol": "L7_PROTOCOL_HTTP2", "route": { - "name": "exposed_path_filter_GetHealth_1235" + "name": "exposed_path_route_GetHealth1235" }, - "statPrefix": "exposed_path_filter_GetHealth_1235", + "statPrefix": "exposed_path_route_GetHealth1235", "staticRoute": true } } @@ -144,13 +144,13 @@ } ], "routes": { - "exposed_path_filter_GetHealth_1235": { + "exposed_path_route_GetHealth1235": { "virtualHosts": [ { "domains": [ "*" ], - "name": "exposed_path_filter_GetHealth_1235", + "name": "exposed_path_route_GetHealth1235", "routeRules": [ { "destination": { @@ -168,13 +168,13 @@ } ] }, - "exposed_path_filter_health_1234": { + "exposed_path_route_health1234": { "virtualHosts": [ { "domains": [ "*" ], - "name": "exposed_path_filter_health_1234", + "name": "exposed_path_route_health1234", "routeRules": [ { "destination": { diff --git a/internal/mesh/internal/controllers/sidecarproxy/builder/testdata/source/local-and-inbound-connections.golden b/internal/mesh/internal/controllers/sidecarproxy/builder/testdata/source/local-and-inbound-connections.golden index 169a4969aefda..77d52fd90b04c 100644 --- a/internal/mesh/internal/controllers/sidecarproxy/builder/testdata/source/local-and-inbound-connections.golden +++ b/internal/mesh/internal/controllers/sidecarproxy/builder/testdata/source/local-and-inbound-connections.golden @@ -169,16 +169,16 @@ "direction": "DIRECTION_INBOUND", "hostPort": { "host": "10.0.0.1", - "port": 9090 + "port": 1234 }, - "name": "exposed_path_health", + "name": "exposed_path_health1234", "routers": [ { "l7": { "route": { - "name": "exposed_path_filter_health_1234" + "name": "exposed_path_route_health1234" }, - "statPrefix": "exposed_path_filter_health_1234", + "statPrefix": "exposed_path_route_health1234", "staticRoute": true } } @@ -188,17 +188,17 @@ "direction": "DIRECTION_INBOUND", "hostPort": { "host": "10.0.0.1", - "port": 9091 + "port": 1235 }, - "name": "exposed_path_GetHealth", + "name": "exposed_path_GetHealth1235", "routers": [ { "l7": { "protocol": "L7_PROTOCOL_HTTP2", "route": { - "name": "exposed_path_filter_GetHealth_1235" + "name": "exposed_path_route_GetHealth1235" }, - "statPrefix": "exposed_path_filter_GetHealth_1235", + "statPrefix": "exposed_path_route_GetHealth1235", "staticRoute": true } } @@ -206,13 +206,13 @@ } ], "routes": { - "exposed_path_filter_GetHealth_1235": { + "exposed_path_route_GetHealth1235": { "virtualHosts": [ { "domains": [ "*" ], - "name": "exposed_path_filter_GetHealth_1235", + "name": "exposed_path_route_GetHealth1235", "routeRules": [ { "destination": { @@ -230,13 +230,13 @@ } ] }, - "exposed_path_filter_health_1234": { + "exposed_path_route_health1234": { "virtualHosts": [ { "domains": [ "*" ], - "name": "exposed_path_filter_health_1234", + "name": "exposed_path_route_health1234", "routeRules": [ { "destination": { diff --git a/internal/mesh/internal/controllers/xds/controller_test.go b/internal/mesh/internal/controllers/xds/controller_test.go index 8d4799e2da28d..6bb5f85c9908f 100644 --- a/internal/mesh/internal/controllers/xds/controller_test.go +++ b/internal/mesh/internal/controllers/xds/controller_test.go @@ -237,10 +237,7 @@ func (suite *xdsControllerTestSuite) TestReconcile_ReadEndpointError() { require.Error(suite.T(), err) // Assert on the status reflecting endpoint couldn't be read. - suite.client.RequireStatusCondition(suite.T(), fooProxyStateTemplate.Id, ControllerName, status.ConditionRejectedErrorReadingEndpoints( - status.KeyFromID(badID), - "rpc error: code = InvalidArgument desc = id.name invalid: a resource name must consist of lower case alphanumeric characters or '-', must start and end with an alphanumeric character and be less than 64 characters, got: \"\"", - )) + suite.client.RequireStatusCondition(suite.T(), fooProxyStateTemplate.Id, ControllerName, status.ConditionRejectedErrorReadingEndpoints(status.KeyFromID(badID), "rpc error: code = InvalidArgument desc = id.name is required")) } // This test is a happy path creation test to make sure pbproxystate.Endpoints are created in the computed diff --git a/internal/resource/demo/controller.go b/internal/resource/demo/controller.go index a8757fcae2624..7f1bba902ea51 100644 --- a/internal/resource/demo/controller.go +++ b/internal/resource/demo/controller.go @@ -71,7 +71,7 @@ func (r *artistReconciler) Reconcile(ctx context.Context, rt controller.Runtime, actualAlbums, err := rt.Client.List(ctx, &pbresource.ListRequest{ Type: TypeV2Album, Tenancy: res.Id.Tenancy, - NamePrefix: fmt.Sprintf("%s-", res.Id.Name), + NamePrefix: fmt.Sprintf("%s/", res.Id.Name), }) if err != nil { return err diff --git a/internal/resource/demo/demo.go b/internal/resource/demo/demo.go index 12fced6718e61..8e978c9fb49ab 100644 --- a/internal/resource/demo/demo.go +++ b/internal/resource/demo/demo.go @@ -354,7 +354,7 @@ func generateV2Album(artistID *pbresource.ID, rand *rand.Rand) (*pbresource.Reso Id: &pbresource.ID{ Type: TypeV2Album, Tenancy: clone(artistID.Tenancy), - Name: fmt.Sprintf("%s-%s-%s", artistID.Name, strings.ToLower(adjective), strings.ToLower(noun)), + Name: fmt.Sprintf("%s/%s-%s", artistID.Name, strings.ToLower(adjective), strings.ToLower(noun)), }, Owner: artistID, Data: data, diff --git a/internal/resource/http/http_test.go b/internal/resource/http/http_test.go index 46d2af363590a..50f50fbe39488 100644 --- a/internal/resource/http/http_test.go +++ b/internal/resource/http/http_test.go @@ -42,7 +42,7 @@ func TestResourceHandler_InputValidation(t *testing.T) { request *http.Request response *httptest.ResponseRecorder expectedResponseCode int - responseBodyContains string + expectedErrorMessage string } client := svctest.RunResourceService(t, demo.RegisterTypes) resourceHandler := resourceHandler{ @@ -72,7 +72,7 @@ func TestResourceHandler_InputValidation(t *testing.T) { `)), response: httptest.NewRecorder(), expectedResponseCode: http.StatusBadRequest, - responseBodyContains: "resource.id.name invalid", + expectedErrorMessage: "rpc error: code = InvalidArgument desc = resource.id.name is required", }, { description: "wrong schema", @@ -89,21 +89,21 @@ func TestResourceHandler_InputValidation(t *testing.T) { `)), response: httptest.NewRecorder(), expectedResponseCode: http.StatusBadRequest, - responseBodyContains: "Request body didn't follow the resource schema", + expectedErrorMessage: "Request body didn't follow the resource schema", }, { description: "invalid request body", request: httptest.NewRequest("PUT", "/keith-urban?partition=default&peer_name=local&namespace=default", strings.NewReader("bad-input")), response: httptest.NewRecorder(), expectedResponseCode: http.StatusBadRequest, - responseBodyContains: "Request body format is invalid", + expectedErrorMessage: "Request body format is invalid", }, { description: "no id", request: httptest.NewRequest("DELETE", "/?partition=default&peer_name=local&namespace=default", strings.NewReader("")), response: httptest.NewRecorder(), expectedResponseCode: http.StatusBadRequest, - responseBodyContains: "id.name invalid", + expectedErrorMessage: "rpc error: code = InvalidArgument desc = id.name is required", }, } @@ -119,7 +119,7 @@ func TestResourceHandler_InputValidation(t *testing.T) { require.NoError(t, err) require.Equal(t, tc.expectedResponseCode, tc.response.Result().StatusCode) - require.Contains(t, string(b), tc.responseBodyContains) + require.Equal(t, tc.expectedErrorMessage, string(b)) }) } } diff --git a/internal/resource/resource.go b/internal/resource/resource.go deleted file mode 100644 index b5100f002955f..0000000000000 --- a/internal/resource/resource.go +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: BUSL-1.1 - -package resource - -import ( - "fmt" - "strings" - - "github.com/hashicorp/consul/agent/dns" -) - -const MaxNameLength = 63 - -// ValidateName returns an error a name is not a valid resource name. -// The error will contain reference to what constitutes a valid resource name. -func ValidateName(name string) error { - if !dns.IsValidLabel(name) || strings.ToLower(name) != name || len(name) > MaxNameLength { - return fmt.Errorf("a resource name must consist of lower case alphanumeric characters or '-', must start and end with an alphanumeric character and be less than %d characters, got: %q", MaxNameLength+1, name) - } - return nil -} diff --git a/internal/resource/tenancy.go b/internal/resource/tenancy.go index 99756d503ff38..126e12413f6a8 100644 --- a/internal/resource/tenancy.go +++ b/internal/resource/tenancy.go @@ -5,6 +5,7 @@ package resource import ( "fmt" + "strings" "google.golang.org/protobuf/proto" @@ -61,6 +62,20 @@ func (s Scope) String() string { panic(fmt.Sprintf("string mapping missing for scope %v", int(s))) } +// Normalize lowercases the partition and namespace. +func Normalize(tenancy *pbresource.Tenancy) { + if tenancy == nil { + return + } + tenancy.Partition = strings.ToLower(tenancy.Partition) + tenancy.Namespace = strings.ToLower(tenancy.Namespace) + + // TODO(spatel): NET-5475 - Remove as part of peer_name moving to PeerTenancy + if tenancy.PeerName == "" { + tenancy.PeerName = DefaultPeerName + } +} + // DefaultClusteredTenancy returns the default tenancy for a cluster scoped resource. func DefaultClusteredTenancy() *pbresource.Tenancy { return &pbresource.Tenancy{ @@ -141,6 +156,7 @@ func defaultTenancy(itemTenancy, parentTenancy, scopeTenancy *pbresource.Tenancy if itemTenancy.PeerName == "" { itemTenancy.PeerName = DefaultPeerName } + Normalize(itemTenancy) if parentTenancy != nil { // Recursively normalize this tenancy as well. @@ -151,6 +167,7 @@ func defaultTenancy(itemTenancy, parentTenancy, scopeTenancy *pbresource.Tenancy if parentTenancy == nil { parentTenancy = scopeTenancy } + Normalize(parentTenancy) if !equalOrEmpty(itemTenancy.PeerName, DefaultPeerName) { panic("peering is not supported yet for resource tenancies") diff --git a/internal/tenancy/exports.go b/internal/tenancy/exports.go index 934f895955d03..aadd7efb59beb 100644 --- a/internal/tenancy/exports.go +++ b/internal/tenancy/exports.go @@ -11,14 +11,14 @@ import ( var ( // API Group Information - APIGroup = types.GroupName - VersionV2Beta1 = types.VersionV2Beta1 - CurrentVersion = types.CurrentVersion + APIGroup = types.GroupName + VersionV1Alpha1 = types.VersionV1Alpha1 + CurrentVersion = types.CurrentVersion // Resource Kind Names. - NamespaceKind = types.NamespaceKind - NamespaceV2Beta1Type = types.NamespaceV2Beta1Type + NamespaceKind = types.NamespaceKind + NamespaceV1Alpha1Type = types.NamespaceV1Alpha1Type ) // RegisterTypes adds all resource types within the "tenancy" API group diff --git a/internal/tenancy/internal/types/namespace.go b/internal/tenancy/internal/types/namespace.go index 1bb016bf3c4d2..4bc95d1505f7d 100644 --- a/internal/tenancy/internal/types/namespace.go +++ b/internal/tenancy/internal/types/namespace.go @@ -5,12 +5,11 @@ package types import ( "fmt" - "strings" - "github.com/hashicorp/consul/agent/dns" "github.com/hashicorp/consul/internal/resource" "github.com/hashicorp/consul/proto-public/pbresource" - pbtenancy "github.com/hashicorp/consul/proto-public/pbtenancy/v2beta1" + tenancyv1alpha1 "github.com/hashicorp/consul/proto-public/pbtenancy/v1alpha1" + "strings" ) const ( @@ -18,18 +17,18 @@ const ( ) var ( - NamespaceV2Beta1Type = &pbresource.Type{ + NamespaceV1Alpha1Type = &pbresource.Type{ Group: GroupName, - GroupVersion: VersionV2Beta1, + GroupVersion: VersionV1Alpha1, Kind: NamespaceKind, } - NamespaceType = NamespaceV2Beta1Type + NamespaceType = NamespaceV1Alpha1Type ) func RegisterNamespace(r resource.Registry) { r.Register(resource.Registration{ - Type: NamespaceType, - Proto: &pbtenancy.Namespace{}, + Type: NamespaceV1Alpha1Type, + Proto: &tenancyv1alpha1.Namespace{}, Scope: resource.ScopePartition, Validate: ValidateNamespace, Mutate: MutateNamespace, @@ -43,7 +42,7 @@ func MutateNamespace(res *pbresource.Resource) error { } func ValidateNamespace(res *pbresource.Resource) error { - var ns pbtenancy.Namespace + var ns tenancyv1alpha1.Namespace if err := res.Data.UnmarshalTo(&ns); err != nil { return resource.NewErrDataParse(&ns, err) diff --git a/internal/tenancy/internal/types/namespace_test.go b/internal/tenancy/internal/types/namespace_test.go index 2c68097d4cde6..b64f86d5212bc 100644 --- a/internal/tenancy/internal/types/namespace_test.go +++ b/internal/tenancy/internal/types/namespace_test.go @@ -6,14 +6,13 @@ package types import ( "context" "errors" - "testing" - svctest "github.com/hashicorp/consul/agent/grpc-external/services/resource/testing" rtest "github.com/hashicorp/consul/internal/resource/resourcetest" pbcatalog "github.com/hashicorp/consul/proto-public/pbcatalog/v2beta1" "github.com/hashicorp/consul/proto/private/prototest" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" + "testing" "github.com/stretchr/testify/require" "google.golang.org/protobuf/reflect/protoreflect" @@ -21,13 +20,13 @@ import ( "github.com/hashicorp/consul/internal/resource" "github.com/hashicorp/consul/proto-public/pbresource" - pbtenancy "github.com/hashicorp/consul/proto-public/pbtenancy/v2beta1" + pbtenancy "github.com/hashicorp/consul/proto-public/pbtenancy/v1alpha1" ) func createNamespaceResource(t *testing.T, data protoreflect.ProtoMessage) *pbresource.Resource { res := &pbresource.Resource{ Id: &pbresource.ID{ - Type: NamespaceV2Beta1Type, + Type: NamespaceV1Alpha1Type, Tenancy: resource.DefaultPartitionedTenancy(), Name: "ns1234", }, @@ -189,6 +188,19 @@ func TestDelete_Success(t *testing.T) { } +func TestRead_MixedCases_Success(t *testing.T) { + client := svctest.RunResourceService(t, Register) + client = rtest.NewClient(client) + + res := rtest.Resource(NamespaceType, "nS1"). + WithData(t, validNamespace()).Write(t, client) + + readRsp, err := client.Read(context.Background(), &pbresource.ReadRequest{Id: res.Id}) + require.NoError(t, err) + prototest.AssertDeepEqual(t, res.Id, readRsp.Resource.Id) + +} + func validNamespace() *pbtenancy.Namespace { return &pbtenancy.Namespace{ Description: "ns namespace", diff --git a/internal/tenancy/internal/types/types.go b/internal/tenancy/internal/types/types.go index 5955ade8a5d7b..be0a615153fd0 100644 --- a/internal/tenancy/internal/types/types.go +++ b/internal/tenancy/internal/types/types.go @@ -4,7 +4,7 @@ package types const ( - GroupName = "tenancy" - VersionV2Beta1 = "v2beta1" - CurrentVersion = VersionV2Beta1 + GroupName = "tenancy" + VersionV1Alpha1 = "v1alpha1" + CurrentVersion = VersionV1Alpha1 ) diff --git a/proto-public/pbtenancy/v2beta1/namespace.pb.binary.go b/proto-public/pbtenancy/v1alpha1/namespace.pb.binary.go similarity index 84% rename from proto-public/pbtenancy/v2beta1/namespace.pb.binary.go rename to proto-public/pbtenancy/v1alpha1/namespace.pb.binary.go index 1884a0943b0da..f6097062d32dc 100644 --- a/proto-public/pbtenancy/v2beta1/namespace.pb.binary.go +++ b/proto-public/pbtenancy/v1alpha1/namespace.pb.binary.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go-binary. DO NOT EDIT. -// source: pbtenancy/v2beta1/namespace.proto +// source: pbtenancy/v1alpha1/namespace.proto -package tenancyv2beta1 +package tenancyv1alpha1 import ( "google.golang.org/protobuf/proto" diff --git a/proto-public/pbtenancy/v1alpha1/namespace.pb.go b/proto-public/pbtenancy/v1alpha1/namespace.pb.go new file mode 100644 index 0000000000000..e7fec0b5d0086 --- /dev/null +++ b/proto-public/pbtenancy/v1alpha1/namespace.pb.go @@ -0,0 +1,172 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.30.0 +// protoc (unknown) +// source: pbtenancy/v1alpha1/namespace.proto + +package tenancyv1alpha1 + +import ( + _ "github.com/hashicorp/consul/proto-public/pbresource" + 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) +) + +// The name of the Namespace is in the outer Resource.ID.Name. +// It must be unique within a partition and must be a +// DNS hostname. There are also other reserved names that may not be used. +type Namespace struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Description is where the user puts any information they want + // about the namespace. It is not used internally. + Description string `protobuf:"bytes,1,opt,name=description,proto3" json:"description,omitempty"` +} + +func (x *Namespace) Reset() { + *x = Namespace{} + if protoimpl.UnsafeEnabled { + mi := &file_pbtenancy_v1alpha1_namespace_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Namespace) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Namespace) ProtoMessage() {} + +func (x *Namespace) ProtoReflect() protoreflect.Message { + mi := &file_pbtenancy_v1alpha1_namespace_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 Namespace.ProtoReflect.Descriptor instead. +func (*Namespace) Descriptor() ([]byte, []int) { + return file_pbtenancy_v1alpha1_namespace_proto_rawDescGZIP(), []int{0} +} + +func (x *Namespace) GetDescription() string { + if x != nil { + return x.Description + } + return "" +} + +var File_pbtenancy_v1alpha1_namespace_proto protoreflect.FileDescriptor + +var file_pbtenancy_v1alpha1_namespace_proto_rawDesc = []byte{ + 0x0a, 0x22, 0x70, 0x62, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x63, 0x79, 0x2f, 0x76, 0x31, 0x61, 0x6c, + 0x70, 0x68, 0x61, 0x31, 0x2f, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x2e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x21, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, + 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x63, 0x79, 0x2e, 0x76, + 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x1a, 0x1c, 0x70, 0x62, 0x72, 0x65, 0x73, 0x6f, 0x75, + 0x72, 0x63, 0x65, 0x2f, 0x61, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x35, 0x0a, 0x09, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, + 0x63, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, + 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, + 0x74, 0x69, 0x6f, 0x6e, 0x3a, 0x06, 0xa2, 0x93, 0x04, 0x02, 0x08, 0x02, 0x42, 0xab, 0x02, 0x0a, + 0x25, 0x63, 0x6f, 0x6d, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, + 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x63, 0x79, 0x2e, 0x76, 0x31, + 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x42, 0x0e, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, + 0x65, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x4b, 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, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x63, 0x79, 0x2f, 0x76, 0x31, 0x61, + 0x6c, 0x70, 0x68, 0x61, 0x31, 0x3b, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x63, 0x79, 0x76, 0x31, 0x61, + 0x6c, 0x70, 0x68, 0x61, 0x31, 0xa2, 0x02, 0x03, 0x48, 0x43, 0x54, 0xaa, 0x02, 0x21, 0x48, 0x61, + 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x43, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x54, + 0x65, 0x6e, 0x61, 0x6e, 0x63, 0x79, 0x2e, 0x56, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0xca, + 0x02, 0x21, 0x48, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x5c, 0x43, 0x6f, 0x6e, 0x73, + 0x75, 0x6c, 0x5c, 0x54, 0x65, 0x6e, 0x61, 0x6e, 0x63, 0x79, 0x5c, 0x56, 0x31, 0x61, 0x6c, 0x70, + 0x68, 0x61, 0x31, 0xe2, 0x02, 0x2d, 0x48, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x5c, + 0x43, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x5c, 0x54, 0x65, 0x6e, 0x61, 0x6e, 0x63, 0x79, 0x5c, 0x56, + 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 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, 0x54, 0x65, 0x6e, 0x61, 0x6e, 0x63, 0x79, + 0x3a, 0x3a, 0x56, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x33, +} + +var ( + file_pbtenancy_v1alpha1_namespace_proto_rawDescOnce sync.Once + file_pbtenancy_v1alpha1_namespace_proto_rawDescData = file_pbtenancy_v1alpha1_namespace_proto_rawDesc +) + +func file_pbtenancy_v1alpha1_namespace_proto_rawDescGZIP() []byte { + file_pbtenancy_v1alpha1_namespace_proto_rawDescOnce.Do(func() { + file_pbtenancy_v1alpha1_namespace_proto_rawDescData = protoimpl.X.CompressGZIP(file_pbtenancy_v1alpha1_namespace_proto_rawDescData) + }) + return file_pbtenancy_v1alpha1_namespace_proto_rawDescData +} + +var file_pbtenancy_v1alpha1_namespace_proto_msgTypes = make([]protoimpl.MessageInfo, 1) +var file_pbtenancy_v1alpha1_namespace_proto_goTypes = []interface{}{ + (*Namespace)(nil), // 0: hashicorp.consul.tenancy.v1alpha1.Namespace +} +var file_pbtenancy_v1alpha1_namespace_proto_depIdxs = []int32{ + 0, // [0:0] is the sub-list for method output_type + 0, // [0:0] is the sub-list for method input_type + 0, // [0:0] is the sub-list for extension type_name + 0, // [0:0] is the sub-list for extension extendee + 0, // [0:0] is the sub-list for field type_name +} + +func init() { file_pbtenancy_v1alpha1_namespace_proto_init() } +func file_pbtenancy_v1alpha1_namespace_proto_init() { + if File_pbtenancy_v1alpha1_namespace_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_pbtenancy_v1alpha1_namespace_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Namespace); 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_pbtenancy_v1alpha1_namespace_proto_rawDesc, + NumEnums: 0, + NumMessages: 1, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_pbtenancy_v1alpha1_namespace_proto_goTypes, + DependencyIndexes: file_pbtenancy_v1alpha1_namespace_proto_depIdxs, + MessageInfos: file_pbtenancy_v1alpha1_namespace_proto_msgTypes, + }.Build() + File_pbtenancy_v1alpha1_namespace_proto = out.File + file_pbtenancy_v1alpha1_namespace_proto_rawDesc = nil + file_pbtenancy_v1alpha1_namespace_proto_goTypes = nil + file_pbtenancy_v1alpha1_namespace_proto_depIdxs = nil +} diff --git a/proto-public/pbtenancy/v2beta1/namespace.proto b/proto-public/pbtenancy/v1alpha1/namespace.proto similarity index 91% rename from proto-public/pbtenancy/v2beta1/namespace.proto rename to proto-public/pbtenancy/v1alpha1/namespace.proto index 6d4a739f6e21f..e90b10c1e573c 100644 --- a/proto-public/pbtenancy/v2beta1/namespace.proto +++ b/proto-public/pbtenancy/v1alpha1/namespace.proto @@ -1,6 +1,6 @@ syntax = "proto3"; -package hashicorp.consul.tenancy.v2beta1; +package hashicorp.consul.tenancy.v1alpha1; import "pbresource/annotations.proto"; diff --git a/proto-public/pbtenancy/v2beta1/namespace_deepcopy.gen.go b/proto-public/pbtenancy/v1alpha1/namespace_deepcopy.gen.go similarity index 97% rename from proto-public/pbtenancy/v2beta1/namespace_deepcopy.gen.go rename to proto-public/pbtenancy/v1alpha1/namespace_deepcopy.gen.go index 2384004c869f3..97af531ab3330 100644 --- a/proto-public/pbtenancy/v2beta1/namespace_deepcopy.gen.go +++ b/proto-public/pbtenancy/v1alpha1/namespace_deepcopy.gen.go @@ -1,5 +1,5 @@ // Code generated by protoc-gen-deepcopy. DO NOT EDIT. -package tenancyv2beta1 +package tenancyv1alpha1 import ( proto "google.golang.org/protobuf/proto" diff --git a/proto-public/pbtenancy/v2beta1/namespace_json.gen.go b/proto-public/pbtenancy/v1alpha1/namespace_json.gen.go similarity index 96% rename from proto-public/pbtenancy/v2beta1/namespace_json.gen.go rename to proto-public/pbtenancy/v1alpha1/namespace_json.gen.go index 4ad7901c16c3c..9df4de9df7199 100644 --- a/proto-public/pbtenancy/v2beta1/namespace_json.gen.go +++ b/proto-public/pbtenancy/v1alpha1/namespace_json.gen.go @@ -1,5 +1,5 @@ // Code generated by protoc-json-shim. DO NOT EDIT. -package tenancyv2beta1 +package tenancyv1alpha1 import ( protojson "google.golang.org/protobuf/encoding/protojson" diff --git a/proto-public/pbtenancy/v2beta1/resource_types.gen.go b/proto-public/pbtenancy/v1alpha1/resource_types.gen.go similarity index 87% rename from proto-public/pbtenancy/v2beta1/resource_types.gen.go rename to proto-public/pbtenancy/v1alpha1/resource_types.gen.go index b0c3040408234..f1b6f70cf1038 100644 --- a/proto-public/pbtenancy/v2beta1/resource_types.gen.go +++ b/proto-public/pbtenancy/v1alpha1/resource_types.gen.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-resource-types. DO NOT EDIT. -package tenancyv2beta1 +package tenancyv1alpha1 import ( "github.com/hashicorp/consul/proto-public/pbresource" @@ -8,7 +8,7 @@ import ( const ( GroupName = "tenancy" - Version = "v2beta1" + Version = "v1alpha1" NamespaceKind = "Namespace" ) diff --git a/proto-public/pbtenancy/v2beta1/namespace.pb.go b/proto-public/pbtenancy/v2beta1/namespace.pb.go deleted file mode 100644 index 2118814a68a7f..0000000000000 --- a/proto-public/pbtenancy/v2beta1/namespace.pb.go +++ /dev/null @@ -1,171 +0,0 @@ -// Code generated by protoc-gen-go. DO NOT EDIT. -// versions: -// protoc-gen-go v1.30.0 -// protoc (unknown) -// source: pbtenancy/v2beta1/namespace.proto - -package tenancyv2beta1 - -import ( - _ "github.com/hashicorp/consul/proto-public/pbresource" - 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) -) - -// The name of the Namespace is in the outer Resource.ID.Name. -// It must be unique within a partition and must be a -// DNS hostname. There are also other reserved names that may not be used. -type Namespace struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - // Description is where the user puts any information they want - // about the namespace. It is not used internally. - Description string `protobuf:"bytes,1,opt,name=description,proto3" json:"description,omitempty"` -} - -func (x *Namespace) Reset() { - *x = Namespace{} - if protoimpl.UnsafeEnabled { - mi := &file_pbtenancy_v2beta1_namespace_proto_msgTypes[0] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *Namespace) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*Namespace) ProtoMessage() {} - -func (x *Namespace) ProtoReflect() protoreflect.Message { - mi := &file_pbtenancy_v2beta1_namespace_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 Namespace.ProtoReflect.Descriptor instead. -func (*Namespace) Descriptor() ([]byte, []int) { - return file_pbtenancy_v2beta1_namespace_proto_rawDescGZIP(), []int{0} -} - -func (x *Namespace) GetDescription() string { - if x != nil { - return x.Description - } - return "" -} - -var File_pbtenancy_v2beta1_namespace_proto protoreflect.FileDescriptor - -var file_pbtenancy_v2beta1_namespace_proto_rawDesc = []byte{ - 0x0a, 0x21, 0x70, 0x62, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x63, 0x79, 0x2f, 0x76, 0x32, 0x62, 0x65, - 0x74, 0x61, 0x31, 0x2f, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x2e, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x12, 0x20, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, - 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x63, 0x79, 0x2e, 0x76, 0x32, - 0x62, 0x65, 0x74, 0x61, 0x31, 0x1a, 0x1c, 0x70, 0x62, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, - 0x65, 0x2f, 0x61, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x22, 0x35, 0x0a, 0x09, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, - 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, - 0x6f, 0x6e, 0x3a, 0x06, 0xa2, 0x93, 0x04, 0x02, 0x08, 0x02, 0x42, 0xa4, 0x02, 0x0a, 0x24, 0x63, - 0x6f, 0x6d, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, - 0x73, 0x75, 0x6c, 0x2e, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x63, 0x79, 0x2e, 0x76, 0x32, 0x62, 0x65, - 0x74, 0x61, 0x31, 0x42, 0x0e, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x50, 0x72, - 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x49, 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, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x63, 0x79, 0x2f, 0x76, 0x32, 0x62, 0x65, 0x74, 0x61, - 0x31, 0x3b, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x63, 0x79, 0x76, 0x32, 0x62, 0x65, 0x74, 0x61, 0x31, - 0xa2, 0x02, 0x03, 0x48, 0x43, 0x54, 0xaa, 0x02, 0x20, 0x48, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, - 0x72, 0x70, 0x2e, 0x43, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x54, 0x65, 0x6e, 0x61, 0x6e, 0x63, - 0x79, 0x2e, 0x56, 0x32, 0x62, 0x65, 0x74, 0x61, 0x31, 0xca, 0x02, 0x20, 0x48, 0x61, 0x73, 0x68, - 0x69, 0x63, 0x6f, 0x72, 0x70, 0x5c, 0x43, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x5c, 0x54, 0x65, 0x6e, - 0x61, 0x6e, 0x63, 0x79, 0x5c, 0x56, 0x32, 0x62, 0x65, 0x74, 0x61, 0x31, 0xe2, 0x02, 0x2c, 0x48, - 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x5c, 0x43, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x5c, - 0x54, 0x65, 0x6e, 0x61, 0x6e, 0x63, 0x79, 0x5c, 0x56, 0x32, 0x62, 0x65, 0x74, 0x61, 0x31, 0x5c, - 0x47, 0x50, 0x42, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0xea, 0x02, 0x23, 0x48, 0x61, - 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x3a, 0x3a, 0x43, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x3a, - 0x3a, 0x54, 0x65, 0x6e, 0x61, 0x6e, 0x63, 0x79, 0x3a, 0x3a, 0x56, 0x32, 0x62, 0x65, 0x74, 0x61, - 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, -} - -var ( - file_pbtenancy_v2beta1_namespace_proto_rawDescOnce sync.Once - file_pbtenancy_v2beta1_namespace_proto_rawDescData = file_pbtenancy_v2beta1_namespace_proto_rawDesc -) - -func file_pbtenancy_v2beta1_namespace_proto_rawDescGZIP() []byte { - file_pbtenancy_v2beta1_namespace_proto_rawDescOnce.Do(func() { - file_pbtenancy_v2beta1_namespace_proto_rawDescData = protoimpl.X.CompressGZIP(file_pbtenancy_v2beta1_namespace_proto_rawDescData) - }) - return file_pbtenancy_v2beta1_namespace_proto_rawDescData -} - -var file_pbtenancy_v2beta1_namespace_proto_msgTypes = make([]protoimpl.MessageInfo, 1) -var file_pbtenancy_v2beta1_namespace_proto_goTypes = []interface{}{ - (*Namespace)(nil), // 0: hashicorp.consul.tenancy.v2beta1.Namespace -} -var file_pbtenancy_v2beta1_namespace_proto_depIdxs = []int32{ - 0, // [0:0] is the sub-list for method output_type - 0, // [0:0] is the sub-list for method input_type - 0, // [0:0] is the sub-list for extension type_name - 0, // [0:0] is the sub-list for extension extendee - 0, // [0:0] is the sub-list for field type_name -} - -func init() { file_pbtenancy_v2beta1_namespace_proto_init() } -func file_pbtenancy_v2beta1_namespace_proto_init() { - if File_pbtenancy_v2beta1_namespace_proto != nil { - return - } - if !protoimpl.UnsafeEnabled { - file_pbtenancy_v2beta1_namespace_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Namespace); 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_pbtenancy_v2beta1_namespace_proto_rawDesc, - NumEnums: 0, - NumMessages: 1, - NumExtensions: 0, - NumServices: 0, - }, - GoTypes: file_pbtenancy_v2beta1_namespace_proto_goTypes, - DependencyIndexes: file_pbtenancy_v2beta1_namespace_proto_depIdxs, - MessageInfos: file_pbtenancy_v2beta1_namespace_proto_msgTypes, - }.Build() - File_pbtenancy_v2beta1_namespace_proto = out.File - file_pbtenancy_v2beta1_namespace_proto_rawDesc = nil - file_pbtenancy_v2beta1_namespace_proto_goTypes = nil - file_pbtenancy_v2beta1_namespace_proto_depIdxs = nil -} diff --git a/test/integration/consul-container/test/catalog/catalog_test.go b/test/integration/consul-container/test/catalog/catalog_test.go index 5be52792d8218..b6e821e1f1f7f 100644 --- a/test/integration/consul-container/test/catalog/catalog_test.go +++ b/test/integration/consul-container/test/catalog/catalog_test.go @@ -29,7 +29,7 @@ func TestCatalog(t *testing.T) { client := pbresource.NewResourceServiceClient(followers[0].GetGRPCConn()) t.Run("one-shot", func(t *testing.T) { - catalogtest.RunCatalogV2Beta1IntegrationTest(t, client) + catalogtest.RunCatalogV1Alpha1IntegrationTest(t, client) }) t.Run("lifecycle", func(t *testing.T) { diff --git a/version/VERSION b/version/VERSION index ee017091ff37b..ee8855caa4a79 100644 --- a/version/VERSION +++ b/version/VERSION @@ -1 +1 @@ -1.18.0-dev +1.17.0-dev diff --git a/website/content/docs/connect/config-entries/control-plane-request-limit.mdx b/website/content/docs/connect/config-entries/control-plane-request-limit.mdx index c89e87832dbda..d6e828e67028e 100644 --- a/website/content/docs/connect/config-entries/control-plane-request-limit.mdx +++ b/website/content/docs/connect/config-entries/control-plane-request-limit.mdx @@ -40,48 +40,48 @@ When every field is defined, a control plane request limit configuration entry h ```hcl -kind = "control-plane-request-limit" -name = "" +Kind = "control-plane-request-limit" +Name = "" -mode = "permissive" +Mode = "permissive" -read_rate = 100 -write_rate = 100 +ReadRate = 100 +WriteRate = 100 -kv = { - read_rate = 100 - write_rate = 100 +KV = { + ReadRate = 100 + WriteRate = 100 } -acl = { - read_rate = 100 - write_rate = 100 +ACL = { + ReadRate = 100 + WriteRate = 100 } -catalog = { - read_rate = 100 - write_rate = 100 +Catalog = { + ReadRate = 100 + WriteRate = 100 } ``` ```json { - "kind": "control-plane-request-limit", - "name": "", - "mode": "permissive", - "read_rate": 100, - "write_rate": 100, - "kv": { - "read_rate": 100, - "write_rate": 100 + "Kind": "control-plane-request-limit", + "Name": "", + "Mode": "permissive", + "ReadRate": 100, + "WriteRate": 100, + "KV": { + "ReadRate": 100, + "WriteRate": 100 }, - "acl": { - "read_rate": 100, - "write_rate": 100 + "ACL": { + "ReadRate": 100, + "WriteRate": 100 }, - "catalog": { - "read_rate": 100, - "write_rate": 100 + "Catalog": { + "ReadRate": 100, + "WriteRate": 100 } } ``` diff --git a/website/content/docs/ecs/reference/consul-server-json.mdx b/website/content/docs/ecs/reference/consul-server-json.mdx index c88450646995f..30e41c207fa34 100644 --- a/website/content/docs/ecs/reference/consul-server-json.mdx +++ b/website/content/docs/ecs/reference/consul-server-json.mdx @@ -8,24 +8,113 @@ description: Learn about the fields available in the JSON scheme for configuring This topic provides reference information about the JSON schema used to build the `config.tf` file. Refer to [Configure Consul server settings](/consul/docs/ecs/deploy/terraform#configure-consul-server-settings) for information about how Consul on ECS uses the JSON schema. -```json -`consulServers` - `hosts`: string - `skipServerWatch`: boolean; set to true if the Consul server is already behind a load balancer - `defaults`: map - applies to both gRPC and HTTP - `caCertFile`: string path to the certificate .pem file - `tlsServerName`: string name of the TLS server - `tls`: boolean that enables TLS - `grpc`: map - overrides for gRPC traffic - `port`: number specifying the port for gRPC communication - `caCertFile`: string path to the certificate .pem file for authorizing gRPC - `tlsServerName`: string name of the TLS server - `tls`: boolean that enables TLS for gRPC - `http`: map - overrides for HTTP traffic - `https`: boolean that enables HTTPS - `port`: number specifying the port for HTTPS communication - `caCertFile`: string path to the certificate .pem file for authorizing HTTPS - `tlsServerName`: string name of the TLS server - `tls`: boolean that enables TLS for HTTPS - -``` +## Configuration model + +The following list describes the attributes, data types, and default values, if any, in the `config.tf` file. Click on a value to learn more about the attribute. + +- [`consulServers`](#consulservers): map + - [`hosts`](#consulservers-hosts): string + - [`skipServerWatch`](#consulservers-hosts): boolean | `false` + - [`defaults`](#consulservers-defaults): map + - [`caCertFile`](#consulservers-defaults): string + - [`tlsServerName`](#consulservers-defaults): string + - [`tls`](#consulservers-defaults): boolean | `false` + - [`grpc`](#consulservers-grpc): map + - [`port`](#consulservers-grpc): number + - [`caCertFile`](#consulservers-grpc): string + - [`tlsServerName`](#consulservers-grpc): string + - [`tls`](#consulservers-grpc): boolean | `false` + - [`http`](#consulservers-http): map + - [`https`](#consulservers-http): boolean | `false` + - [`port`](#consulservers-http): number + - [`caCertFile`](#consulservers-http): string + - [`tlsServerName`](#consulservers-http): string + - [`tls`](#consulservers-http): boolean | `false` + +## Specification + +This section provides details about the attribes in the `config.tf` file. + +### `consulServers` + +Parent-level attribute containing all of the server configurations. All other configuraitons in the file are children of the `consulServers` attribute. + +#### Values + +- Default: None +- Data type: Map + + +### `consulServers.hosts` + +Map that contains the `skipServerWatch` configuration for Consul server hosts. + +#### Values + +- Default: None +- Data type: Map + +### `consulServers.hosts.skipServerWatch` + +Boolean that disables watches on the Consul server. Set to `true` if the Consul server is already behind a load balancer. + +#### Values + +- Default: `false` +- Data type: Boolean + +### `consulServers.defaults` + +Map of default server configurations. Defaults apply to gRPC and HTTP traffic. + +#### Values + +- Default: None +- Data type: Map + +The following table describes the attributes available in the `defaults` configuration: + +| Attribute | Description | Data type | Default | +| --- | --- | --- | --- | +| `caCertFile` | Specifies the path to the certificate .pem file. | String | None | +| `tlsServerName` | Specifies the name of the TLS server. | String | None | +| `tls` | Enables TLS on the server. | Boolean | `false` | + + +### `consulServers.grpc` + +Map of server configuration for gRPC traffic that override attributes defined in `consulServers.defaults`. + +#### Values + +- Default: None +- Data type: Map + +The following table describes the attributes available in the `grpc` configuration: + +| Attribute | Description | Data type | Default | +| --- | --- | --- | --- | +| `port` | Specifies the port number for gRPC communication. | Number | None | +| `caCertFile` | Specifies the path to the certificate .pem file. | String | None | +| `tlsServerName` | Specifies the name of the TLS server. | String | None | +| `tls` | Enables TLS for gRPC traffic on the server. | Boolean | `false` | + +### `consulServers.http` + +Map of server configuration for HTTP traffic that override attributes defined in `consulServers.defaults`. + +#### Values + +- Default: None +- Data type: Map + +The following table describes the attributes available in the `grpc` configuration: + +| Attribute | Description | Data type | Default | +| --- | --- | --- | --- | +| `https` | Enables HTTPS. | Boolean | `false` | +| `port` | Specifies the port number for HTTPS communication. | Number | None | +| `caCertFile` | Specifies the path to the certificate .pem file. | String | None | +| `tlsServerName` | Specifies the name of the TLS server. | String | None | +| `tls` | Enables TLS for HTTPS traffic on the server. | Boolean | `false` | + diff --git a/website/content/docs/k8s/multiport/configure.mdx b/website/content/docs/k8s/multiport/configure.mdx index d53ea521f3402..991a0817fb79b 100644 --- a/website/content/docs/k8s/multiport/configure.mdx +++ b/website/content/docs/k8s/multiport/configure.mdx @@ -59,7 +59,13 @@ Then install Consul to your Kubernetes cluster using either the `consul-k8s` CLI +For platforms other than Mac OSX amd64, refer to [Install a previous version](/consul/docs/k8s/installation/install-cli#install-a-previous-version) for instructions on how to install a specific version of the `consul-k8s` CLI prior to running `consul-k8s install`. + ```shell-session +$ export VERSION=1.3.0-rc1 && \ + curl --location "https://releases.hashicorp.com/consul-k8s/${VERSION}/consul-k8s_${VERSION}_darwin_amd64.zip" --output consul-k8s-cli.zip +$ unzip -o consul-k8s-cli.zip -d ~/consul-k8s +$ export PATH=$PATH:$HOME/consul-k8s $ consul-k8s install -config-file=values.yaml ``` @@ -68,7 +74,7 @@ $ consul-k8s install -config-file=values.yaml ```shell-session -$ helm install consul hashicorp/consul --create-namespace --namespace consul --values values.yaml +$ helm install consul hashicorp/consul --create-namespace --namespace consul --version 1.3.0-rc1 --values values.yaml ``` @@ -281,14 +287,14 @@ spec: To apply these services to your Kubernetes deployment and register them with Consul, run the following command: ```shell-session -$ kubectl apply -f api.yaml -f web.yaml --namespace consul +$ kubectl apply -f api.yaml -f web.yaml ``` ## Configure traffic permissions Consul uses traffic permissions to validate communication between services based on L4 identity. In the beta release of the v2 catalog API, traffic permissions allow all services by default. In order to verify that services function correctly on each port, create CRDs that deny traffic to each port. -The following examples create Consul CRDs that allow traffic to only one port of the multi-port service. Each resource separately denies `web` permission when it is a source of traffic to one of the services. These traffic permissions work with either method for defining a multi-port service. +The following examples create Consul CRDs that allow traffic to only one port of the multi-port service. Each resource separately denies `web` permission when it is a source of traffic to one of the services. These traffic permissions work with either method for defining a multi-port service. When following the instructions on this page, apply these permissions individually when you validate the ports. @@ -338,15 +344,18 @@ spec: To open a shell to the `web` container, you need the name of the Pod it currently runs on. Run the following command to return a list of Pods: ```shell-session -$ kubectl get pods --namespace consul +$ kubectl get pods NAME READY STATUS RESTARTS AGE api-5784b54bcc-tp98l 3/3 Running 0 6m55s -consul-connect-injector-54865fbcbf-sfjsl 1/1 Running 0 8m33s -consul-server-0 1/1 Running 0 8m33s -consul-webhook-cert-manager-666676bd5b-cdbxc 1/1 Running 0 8m33s web-6dcbd684bc-gk8n5 2/2 Running 0 6m55s ``` +Set environment variables to remember the pod name for the web workload for use in future commands. + +```shell-session +$ export WEB_POD=web-6dcbd684bc-gk8n5 +``` + ### Validate both ports Use the `web` Pod's name to open a shell session and test the `api` service on port 80. @@ -356,14 +365,14 @@ Use the `web` Pod's name to open a shell session and test the `api` service on p ```shell-session -$ kubectl exec -it web-6dcbd684bc-gk8n5 -c web --namespace consul -- curl api:80 +$ kubectl exec -it ${WEB_POD} -c web -- curl api:80 hello world ``` Then test the `api` service on port 90. ```shell-session -$ kubectl exec -it web-6dcbd684bc-gk8n5 -c web --namespace consul -- curl api:90 +$ kubectl exec -it ${WEB_POD} -c web -- curl api:90 hello world from 9090 admin ``` @@ -372,14 +381,14 @@ hello world from 9090 admin ```shell-session -$ kubectl exec -it web-6dcbd684bc-gk8n5 -c web --namespace consul -- curl api:80 +$ kubectl exec -it ${WEB_POD} -c web -- curl api:80 hello world ``` Then test the `api-admin` service on port 90. ```shell-session -$ kubectl exec -it web-6dcbd684bc-gk8n5 -c web --namespace consul -- curl api-admin:90 +$ kubectl exec -it ${WEB_POD} -c web --namespace consul -- curl api-admin:90 hello world from 9090 admin ``` @@ -391,7 +400,7 @@ hello world from 9090 admin Apply the CRD to allow traffic to port 80 only: ```shell-session -$ kubectl apply -f deny-90.yaml --namespace consul +$ kubectl apply -f deny-90.yaml ``` @@ -401,14 +410,14 @@ $ kubectl apply -f deny-90.yaml --namespace consul Then, open a shell session in the `web` container and test the `api` service on port 80. ```shell-session -$ kubectl exec -it web-6dcbd684bc-gk8n5 -c web --namespace consul -- curl api:80 +$ kubectl exec -it ${WEB_POD} -c web -- curl api:80 hello world ``` Test the `api` service on port 90. This command should fail, indicating that the traffic permission is in effect. ```shell-session -$ kubectl exec -it web-6dcbd684bc-gk8n5 -c web --namespace consul -- curl api:90 +$ kubectl exec -it ${WEB_POD} -c web -- curl api:90 ``` @@ -418,14 +427,14 @@ $ kubectl exec -it web-6dcbd684bc-gk8n5 -c web --namespace consul -- curl api:90 Then, open a shell session in the `web` container and test the `api` service on port 80. ```shell-session -$ kubectl exec -it web-6dcbd684bc-gk8n5 -c web --namespace consul -- curl api:80 +$ kubectl exec -it ${WEB_POD} -c web -- curl api:80 hello world ``` Test the `admin` service on port 90. This command should fail, indicating that the traffic permission is in effect. ```shell-session -$ kubectl exec -it web-6dcbd684bc-gk8n5 -c web --namespace consul -- curl api-admin:90 +$ kubectl exec -it ${WEB_POD} -c web -- curl api-admin:90 ``` @@ -434,7 +443,7 @@ $ kubectl exec -it web-6dcbd684bc-gk8n5 -c web --namespace consul -- curl api-ad Before testing the other port, remove the `TrafficPermissions` CRD. ```shell-session -$ kubectl delete -f deny-90.yaml --namespace consul +$ kubectl delete -f deny-90.yaml ``` ### Validate port 90 @@ -442,7 +451,7 @@ $ kubectl delete -f deny-90.yaml --namespace consul Apply the CRD to allow traffic to port 90 only: ```shell-session -$ kubectl apply -f deny-80.yaml --namespace consul +$ kubectl apply -f deny-80.yaml ``` @@ -452,14 +461,14 @@ $ kubectl apply -f deny-80.yaml --namespace consul Then, open a shell session in the `web` container and test the `api` service on port 90. ```shell-session -$ kubectl exec -it web-6dcbd684bc-gk8n5 -c web --namespace consul -- curl api:90 +$ kubectl exec -it ${WEB_POD} -c web -- curl api:90 hello world from 9090 admin ``` Test the `api` service on port 80. This command should fail, indicating that the traffic permission is in effect. ```shell-session -$ kubectl exec -it web-6dcbd684bc-gk8n5 -c web --namespace consul -- curl api:80 +$ kubectl exec -it ${WEB_POD} -c web -- curl api:80 ``` @@ -469,15 +478,15 @@ $ kubectl exec -it web-6dcbd684bc-gk8n5 -c web --namespace consul -- curl api:80 Then, open a shell session in the `web` container and test the `api-admin` service on port 90. ```shell-session -$ kubectl exec -it web-6dcbd684bc-gk8n5 -c web --namespace consul -- curl api-admin:90 +$ kubectl exec -it ${WEB_POD} -c web -- curl api-admin:90 hello world from 9090 admin ``` Test the `api` service on port 80. This command should fail, indicating that the traffic permission is in effect. ```shell-session -$ kubectl exec -it web-6dcbd684bc-gk8n5 -c web --namespace consul -- curl api:80 +$ kubectl exec -it ${WEB_POD} -c web -- curl api:80 ``` - \ No newline at end of file + diff --git a/website/content/docs/k8s/multiport/index.mdx b/website/content/docs/k8s/multiport/index.mdx index b8ed8e990cfe3..0cdc6fca92aa0 100644 --- a/website/content/docs/k8s/multiport/index.mdx +++ b/website/content/docs/k8s/multiport/index.mdx @@ -64,6 +64,6 @@ Be aware of the following constraints and technical limitations on using multi-p - The v2 catalog API beta does not support connections with client agents. It is only available for Kubernetes deployments, which use [Consul dataplanes](/consul/docs/connect/dataplane) instead of client agents. - The v1 and v2 catalog APIs cannot run concurrently. - The Consul UI does not support multi-port services or the v2 catalog API in this release. You must disable the UI in the Helm chart in order to use the v2 catalog API. -- HCP Consul does not support multi-port services or the v2 catalog API in this release. +- HCP Consul does not support multi-port services or the v2 catalog API in this release. You cannot [link a self-managed cluster to HCP Consul](/hcp/docs/consul/self-managed) to access its UI or view observability metrics when it uses the v2 catalog. - The v2 catalog API does not support ACLs in the beta release. - We do not recommend updating existing clusters to enable the v2 catalog in this release. To use the v2 catalog, deploy a new Consul cluster. \ No newline at end of file diff --git a/website/content/docs/services/discovery/dns-configuration.mdx b/website/content/docs/services/discovery/dns-configuration.mdx index 5a0d890e79bff..0a10edecdd9d0 100644 --- a/website/content/docs/services/discovery/dns-configuration.mdx +++ b/website/content/docs/services/discovery/dns-configuration.mdx @@ -16,7 +16,7 @@ The Consul DNS is the primary interface for querying records when Consul service By default, the Consul DNS listens for queries at `127.0.0.1:8600` and uses the `consul` domain. Specify the following parameters in the agent configuration to determine DNS behavior when querying services: - [`client_addr`](/consul/docs/agent/config/config-files#client_addr) -- [`ports.dns`](/consul/docs/agent/config/config-files#dns_port) : Consul does not use port `53`, which is typically reserved for the default port for DNS resolvers, by default because it requires an escalated privilege to bind to. +- [`ports.dns`](/consul/docs/agent/config/config-files#dns_port) - [`recursors`](/consul/docs/agent/config/config-files#recursors) - [`domain`](/consul/docs/agent/config/config-files#domain) - [`alt_domain`](/consul/docs/agent/config/config-files#alt_domain)