Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

resource: enforce lowercase resource names #19218

Merged
merged 2 commits into from
Oct 16, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .changelog/19218.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:improvement
resource: lowercase names enforced for v2 resources only.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this considered a breaking change? Also could we incorporate this language in the release note?

With this change we will only accept lowercase alphanumeric characters and - , and names that start and end with an alphanumeric character. All other characters will be considered incompatible.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This isn't considered breaking since there is nothing existing at a GA level to break (v2 resources are in beta and v1 resources are not affected). Since this is only on the main branch and not tagged to be back ported to 1.17, it should go out in the 1.18 time frame.

```
3 changes: 2 additions & 1 deletion agent/grpc-external/services/resource/delete.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"context"
"errors"
"fmt"
"strings"
"time"

"github.com/oklog/ulid/v2"
Expand Down Expand Up @@ -175,5 +176,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, deleteId.Uid)
return fmt.Sprintf("tombstone-%v-%v", deleteId.Name, strings.ToLower(deleteId.Uid))
}
94 changes: 77 additions & 17 deletions agent/grpc-external/services/resource/delete_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ package resource

import (
"context"
"strings"
"testing"

"github.com/stretchr/testify/mock"
Expand All @@ -22,39 +23,98 @@ import (
func TestDelete_InputValidation(t *testing.T) {
server := testServer(t)
client := testClient(t, server)

demo.RegisterTypes(server.Registry)

testCases := map[string]func(artistId, recordLabelId *pbresource.ID) *pbresource.ID{
"no id": func(artistId, recordLabelId *pbresource.ID) *pbresource.ID {
return nil
type testCase struct {
modFn func(artistId, recordLabelId *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",
},
"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",
},
"no type": func(artistId, _ *pbresource.ID) *pbresource.ID {
artistId.Type = nil
return artistId
"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 name": func(artistId, _ *pbresource.ID) *pbresource.ID {
artistId.Name = ""
return artistId
"namespace mixed case": {
modFn: func(artistId, _ *pbresource.ID) *pbresource.ID {
artistId.Tenancy.Namespace = "Default"
return artistId
},
errContains: "id.tenancy.namespace invalid",
},
"partition scoped resource with namespace": func(_, recordLabelId *pbresource.ID) *pbresource.ID {
recordLabelId.Tenancy.Namespace = "ishouldnothaveanamespace"
return recordLabelId
"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",
},
}
for desc, modFn := range testCases {
for desc, tc := range testCases {
t.Run(desc, func(t *testing.T) {
recordLabel, err := demo.GenerateV1RecordLabel("LoonyTunes")
recordLabel, err := demo.GenerateV1RecordLabel("looney-tunes")
require.NoError(t, err)

artist, err := demo.GenerateV2Artist()
require.NoError(t, err)

req := &pbresource.DeleteRequest{Id: modFn(artist.Id, recordLabel.Id), Version: ""}
req := &pbresource.DeleteRequest{Id: tc.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)
})
}
}
Expand Down Expand Up @@ -129,7 +189,7 @@ func TestDelete_Success(t *testing.T) {
server, client, ctx := testDeps(t)
demo.RegisterTypes(server.Registry)

recordLabel, err := demo.GenerateV1RecordLabel("LoonyTunes")
recordLabel, err := demo.GenerateV1RecordLabel("looney-tunes")
require.NoError(t, err)
writeRsp, err := client.Write(ctx, &pbresource.WriteRequest{Resource: recordLabel})
require.NoError(t, err)
Expand Down
5 changes: 3 additions & 2 deletions agent/grpc-external/services/resource/list.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,8 +100,9 @@ func (s *Server) validateListRequest(req *pbresource.ListRequest) (*resource.Reg
return nil, err
}

// Lowercase
resource.Normalize(req.Tenancy)
if err := validateWildcardTenancy(req.Tenancy, req.NamePrefix); err != nil {
return nil, err
}

// Error when partition scoped and namespace not empty.
if reg.Scope == resource.ScopePartition && req.Tenancy.Namespace != "" {
Expand Down
3 changes: 0 additions & 3 deletions agent/grpc-external/services/resource/list_by_owner.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,9 +105,6 @@ 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(
Expand Down
Loading