Add mutate hook to `Write` endpoint (#16958)
This commit is contained in:
parent
ad5a4201d5
commit
f9311318e1
|
@ -73,6 +73,10 @@ func (s *Server) Write(ctx context.Context, req *pbresource.WriteRequest) (*pbre
|
||||||
return nil, status.Error(codes.InvalidArgument, err.Error())
|
return nil, status.Error(codes.InvalidArgument, err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err = reg.Mutate(req.Resource); err != nil {
|
||||||
|
return nil, status.Errorf(codes.Internal, "failed mutate hook: %v", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
// At the storage backend layer, all writes are CAS operations.
|
// At the storage backend layer, all writes are CAS operations.
|
||||||
//
|
//
|
||||||
// This makes it possible to *safely* do things like keeping the Uid stable
|
// This makes it possible to *safely* do things like keeping the Uid stable
|
||||||
|
|
|
@ -114,6 +114,31 @@ func TestWrite_ACLs(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestWrite_Mutate(t *testing.T) {
|
||||||
|
server := testServer(t)
|
||||||
|
client := testClient(t, server)
|
||||||
|
demo.Register(server.Registry)
|
||||||
|
|
||||||
|
artist, err := demo.GenerateV2Artist()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
artistData := &pbdemov2.Artist{}
|
||||||
|
artist.Data.UnmarshalTo(artistData)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// mutate hook sets genre to disco when unspecified
|
||||||
|
artistData.Genre = pbdemov2.Genre_GENRE_UNSPECIFIED
|
||||||
|
artist.Data.MarshalFrom(artistData)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
rsp, err := client.Write(testContext(t), &pbresource.WriteRequest{Resource: artist})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// verify mutate hook set genre to disco
|
||||||
|
require.NoError(t, rsp.Resource.Data.UnmarshalTo(artistData))
|
||||||
|
require.Equal(t, pbdemov2.Genre_GENRE_DISCO, artistData.Genre)
|
||||||
|
}
|
||||||
|
|
||||||
func TestWrite_ResourceCreation_Success(t *testing.T) {
|
func TestWrite_ResourceCreation_Success(t *testing.T) {
|
||||||
server := testServer(t)
|
server := testServer(t)
|
||||||
client := testClient(t, server)
|
client := testClient(t, server)
|
||||||
|
|
|
@ -108,6 +108,19 @@ func Register(r resource.Registry) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mutateV2ArtistFn := func(res *pbresource.Resource) error {
|
||||||
|
// Not a realistic use for this hook, but set genre if not specified
|
||||||
|
artist := &pbdemov2.Artist{}
|
||||||
|
if err := anypb.UnmarshalTo(res.Data, artist, proto.UnmarshalOptions{}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if artist.Genre == pbdemov2.Genre_GENRE_UNSPECIFIED {
|
||||||
|
artist.Genre = pbdemov2.Genre_GENRE_DISCO
|
||||||
|
return res.Data.MarshalFrom(artist)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
r.Register(resource.Registration{
|
r.Register(resource.Registration{
|
||||||
Type: TypeV1Artist,
|
Type: TypeV1Artist,
|
||||||
Proto: &pbdemov1.Artist{},
|
Proto: &pbdemov1.Artist{},
|
||||||
|
@ -138,6 +151,7 @@ func Register(r resource.Registry) {
|
||||||
List: makeListACL(TypeV2Artist),
|
List: makeListACL(TypeV2Artist),
|
||||||
},
|
},
|
||||||
Validate: validateV2ArtistFn,
|
Validate: validateV2ArtistFn,
|
||||||
|
Mutate: mutateV2ArtistFn,
|
||||||
})
|
})
|
||||||
|
|
||||||
r.Register(resource.Registration{
|
r.Register(resource.Registration{
|
||||||
|
|
|
@ -35,6 +35,9 @@ type Registration struct {
|
||||||
// check for required fields).
|
// check for required fields).
|
||||||
Validate func(*pbresource.Resource) error
|
Validate func(*pbresource.Resource) error
|
||||||
|
|
||||||
|
// Mutate is called to fill out any autogenerated fields (e.g. UUIDs).
|
||||||
|
Mutate func(*pbresource.Resource) error
|
||||||
|
|
||||||
// In the future, we'll add hooks, the controller etc. here.
|
// In the future, we'll add hooks, the controller etc. here.
|
||||||
// TODO: https://github.com/hashicorp/consul/pull/16622#discussion_r1134515909
|
// TODO: https://github.com/hashicorp/consul/pull/16622#discussion_r1134515909
|
||||||
}
|
}
|
||||||
|
@ -109,6 +112,11 @@ func (r *TypeRegistry) Register(registration Registration) {
|
||||||
registration.Validate = func(resource *pbresource.Resource) error { return nil }
|
registration.Validate = func(resource *pbresource.Resource) error { return nil }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// default mutate to a no-op
|
||||||
|
if registration.Mutate == nil {
|
||||||
|
registration.Mutate = func(resource *pbresource.Resource) error { return nil }
|
||||||
|
}
|
||||||
|
|
||||||
r.registrations[key] = registration
|
r.registrations[key] = registration
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -61,10 +61,7 @@ func TestRegister(t *testing.T) {
|
||||||
|
|
||||||
func TestRegister_Defaults(t *testing.T) {
|
func TestRegister_Defaults(t *testing.T) {
|
||||||
r := resource.NewRegistry()
|
r := resource.NewRegistry()
|
||||||
r.Register(resource.Registration{
|
r.Register(resource.Registration{Type: demo.TypeV2Artist})
|
||||||
Type: demo.TypeV2Artist,
|
|
||||||
// intentionally don't provide ACLs so defaults kick in
|
|
||||||
})
|
|
||||||
artist, err := demo.GenerateV2Artist()
|
artist, err := demo.GenerateV2Artist()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
@ -85,6 +82,9 @@ func TestRegister_Defaults(t *testing.T) {
|
||||||
|
|
||||||
// verify default validate is a no-op
|
// verify default validate is a no-op
|
||||||
require.NoError(t, reg.Validate(nil))
|
require.NoError(t, reg.Validate(nil))
|
||||||
|
|
||||||
|
// verify default mutate is a no-op
|
||||||
|
require.NoError(t, reg.Mutate(nil))
|
||||||
}
|
}
|
||||||
|
|
||||||
func assertRegisterPanics(t *testing.T, registerFn func(reg resource.Registration), registration resource.Registration, panicString string) {
|
func assertRegisterPanics(t *testing.T, registerFn func(reg resource.Registration), registration resource.Registration, panicString string) {
|
||||||
|
|
Loading…
Reference in New Issue