diff --git a/agent/grpc-external/services/resource/write.go b/agent/grpc-external/services/resource/write.go index d50ca1dd8..3a365cee3 100644 --- a/agent/grpc-external/services/resource/write.go +++ b/agent/grpc-external/services/resource/write.go @@ -107,6 +107,7 @@ func (s *Server) Write(ctx context.Context, req *pbresource.WriteRequest) (*pbre // // - CAS failures will be retried by retryCAS anyway. So the read-modify-write // cycle should eventually succeed. + var mismatchError storage.GroupVersionMismatchError existing, err := s.Backend.Read(ctx, storage.EventualConsistency, input.Id) switch { // Create path. @@ -146,7 +147,11 @@ func (s *Server) Write(ctx context.Context, req *pbresource.WriteRequest) (*pbre // TODO(spatel): Revisit owner<->resource tenancy rules post-1.16 // Update path. - case err == nil: + case err == nil || errors.As(err, &mismatchError): + // Allow writes that update GroupVersion. + if mismatchError.Stored != nil { + existing = mismatchError.Stored + } // Use the stored ID because it includes the Uid. // // Generally, users won't provide the Uid but controllers will, because diff --git a/agent/grpc-external/services/resource/write_test.go b/agent/grpc-external/services/resource/write_test.go index b2960a924..9b94ecae8 100644 --- a/agent/grpc-external/services/resource/write_test.go +++ b/agent/grpc-external/services/resource/write_test.go @@ -19,6 +19,7 @@ import ( "github.com/hashicorp/consul/internal/resource/demo" "github.com/hashicorp/consul/internal/storage" "github.com/hashicorp/consul/proto-public/pbresource" + pbdemov1 "github.com/hashicorp/consul/proto/private/pbdemo/v1" pbdemov2 "github.com/hashicorp/consul/proto/private/pbdemo/v2" ) @@ -392,6 +393,36 @@ func TestWrite_Update_NoUid(t *testing.T) { require.NoError(t, err) } +func TestWrite_Update_GroupVersion(t *testing.T) { + server := testServer(t) + client := testClient(t, server) + + demo.RegisterTypes(server.Registry) + + res, err := demo.GenerateV2Artist() + require.NoError(t, err) + + rsp1, err := client.Write(testContext(t), &pbresource.WriteRequest{Resource: res}) + require.NoError(t, err) + + res = rsp1.Resource + res.Id.Type = demo.TypeV1Artist + + // translate artistV2 to artistV1 + var artistV2 pbdemov2.Artist + require.NoError(t, res.Data.UnmarshalTo(&artistV2)) + artistV1 := &pbdemov1.Artist{ + Name: artistV2.Name, + Description: "some awesome band", + Genre: pbdemov1.Genre_GENRE_JAZZ, + GroupMembers: int32(len(artistV2.GroupMembers)), + } + res.Data.MarshalFrom(artistV1) + + _, err = client.Write(testContext(t), &pbresource.WriteRequest{Resource: res}) + require.NoError(t, err) +} + func TestWrite_NonCASUpdate_Success(t *testing.T) { server := testServer(t) client := testClient(t, server)