Resource `Write` endpoint (#16786)

This commit is contained in:
Dan Upton 2023-04-06 10:40:04 +01:00 committed by GitHub
parent ad3a68a040
commit 4fa2537b3b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 1691 additions and 133 deletions

View File

@ -51,7 +51,7 @@ import (
"github.com/hashicorp/consul/agent/grpc-external/services/connectca" "github.com/hashicorp/consul/agent/grpc-external/services/connectca"
"github.com/hashicorp/consul/agent/grpc-external/services/dataplane" "github.com/hashicorp/consul/agent/grpc-external/services/dataplane"
"github.com/hashicorp/consul/agent/grpc-external/services/peerstream" "github.com/hashicorp/consul/agent/grpc-external/services/peerstream"
"github.com/hashicorp/consul/agent/grpc-external/services/resource" resourcegrpc "github.com/hashicorp/consul/agent/grpc-external/services/resource"
"github.com/hashicorp/consul/agent/grpc-external/services/serverdiscovery" "github.com/hashicorp/consul/agent/grpc-external/services/serverdiscovery"
agentgrpc "github.com/hashicorp/consul/agent/grpc-internal" agentgrpc "github.com/hashicorp/consul/agent/grpc-internal"
"github.com/hashicorp/consul/agent/grpc-internal/services/subscribe" "github.com/hashicorp/consul/agent/grpc-internal/services/subscribe"
@ -65,6 +65,8 @@ import (
"github.com/hashicorp/consul/agent/rpc/peering" "github.com/hashicorp/consul/agent/rpc/peering"
"github.com/hashicorp/consul/agent/structs" "github.com/hashicorp/consul/agent/structs"
"github.com/hashicorp/consul/agent/token" "github.com/hashicorp/consul/agent/token"
"github.com/hashicorp/consul/internal/resource"
"github.com/hashicorp/consul/internal/resource/demo"
"github.com/hashicorp/consul/internal/storage" "github.com/hashicorp/consul/internal/storage"
raftstorage "github.com/hashicorp/consul/internal/storage/raft" raftstorage "github.com/hashicorp/consul/internal/storage/raft"
"github.com/hashicorp/consul/lib" "github.com/hashicorp/consul/lib"
@ -1196,7 +1198,6 @@ func (s *Server) setupRPC() error {
// Initialize and register services on external gRPC server. // Initialize and register services on external gRPC server.
func (s *Server) setupExternalGRPC(config *Config, backend storage.Backend, logger hclog.Logger) { func (s *Server) setupExternalGRPC(config *Config, backend storage.Backend, logger hclog.Logger) {
s.externalACLServer = aclgrpc.NewServer(aclgrpc.Config{ s.externalACLServer = aclgrpc.NewServer(aclgrpc.Config{
ACLsEnabled: s.config.ACLsEnabled, ACLsEnabled: s.config.ACLsEnabled,
ForwardRPC: func(info structs.RPCInfo, fn func(*grpc.ClientConn) error) (bool, error) { ForwardRPC: func(info structs.RPCInfo, fn func(*grpc.ClientConn) error) (bool, error) {
@ -1261,8 +1262,16 @@ func (s *Server) setupExternalGRPC(config *Config, backend storage.Backend, logg
}) })
s.peerStreamServer.Register(s.externalGRPCServer) s.peerStreamServer.Register(s.externalGRPCServer)
resource.NewServer(resource.Config{ registry := resource.NewRegistry()
Backend: backend,
if s.config.DevMode {
demo.Register(registry)
}
resourcegrpc.NewServer(resourcegrpc.Config{
Registry: registry,
Backend: backend,
Logger: logger.Named("grpc-api.resource"),
}).Register(s.externalGRPCServer) }).Register(s.externalGRPCServer)
} }

View File

@ -9,6 +9,7 @@ import (
"testing" "testing"
"github.com/hashicorp/consul/internal/resource" "github.com/hashicorp/consul/internal/resource"
"github.com/hashicorp/consul/internal/resource/demo"
"github.com/hashicorp/consul/internal/storage" "github.com/hashicorp/consul/internal/storage"
"github.com/hashicorp/consul/proto-public/pbresource" "github.com/hashicorp/consul/proto-public/pbresource"
"github.com/hashicorp/consul/proto/private/prototest" "github.com/hashicorp/consul/proto/private/prototest"
@ -25,25 +26,25 @@ func TestList_TypeNotFound(t *testing.T) {
client := testClient(t, server) client := testClient(t, server)
_, err := client.List(context.Background(), &pbresource.ListRequest{ _, err := client.List(context.Background(), &pbresource.ListRequest{
Type: typev1, Type: demo.TypeV2Artist,
Tenancy: tenancy, Tenancy: demo.TenancyDefault,
NamePrefix: "", NamePrefix: "",
}) })
require.Error(t, err) require.Error(t, err)
require.Equal(t, codes.InvalidArgument.String(), status.Code(err).String()) require.Equal(t, codes.InvalidArgument.String(), status.Code(err).String())
require.Contains(t, err.Error(), "resource type mesh/v1/service not registered") require.Contains(t, err.Error(), "resource type demo/v2/artist not registered")
} }
func TestList_Empty(t *testing.T) { func TestList_Empty(t *testing.T) {
for desc, tc := range listTestCases() { for desc, tc := range listTestCases() {
t.Run(desc, func(t *testing.T) { t.Run(desc, func(t *testing.T) {
server := testServer(t) server := testServer(t)
demo.Register(server.Registry)
client := testClient(t, server) client := testClient(t, server)
server.registry.Register(resource.Registration{Type: typev1})
rsp, err := client.List(tc.ctx, &pbresource.ListRequest{ rsp, err := client.List(tc.ctx, &pbresource.ListRequest{
Type: typev1, Type: demo.TypeV1Artist,
Tenancy: tenancy, Tenancy: demo.TenancyDefault,
NamePrefix: "", NamePrefix: "",
}) })
require.NoError(t, err) require.NoError(t, err)
@ -56,26 +57,25 @@ func TestList_Many(t *testing.T) {
for desc, tc := range listTestCases() { for desc, tc := range listTestCases() {
t.Run(desc, func(t *testing.T) { t.Run(desc, func(t *testing.T) {
server := testServer(t) server := testServer(t)
demo.Register(server.Registry)
client := testClient(t, server) client := testClient(t, server)
server.registry.Register(resource.Registration{Type: typev1})
resources := make([]*pbresource.Resource, 10) resources := make([]*pbresource.Resource, 10)
for i := 0; i < len(resources); i++ { for i := 0; i < len(resources); i++ {
r := &pbresource.Resource{ artist, err := demo.GenerateV2Artist()
Id: &pbresource.ID{ require.NoError(t, err)
Uid: fmt.Sprintf("uid%d", i),
Name: fmt.Sprintf("name%d", i), // Prevent test flakes if the generated names collide.
Type: typev1, artist.Id.Name = fmt.Sprintf("%s-%d", artist.Id.Name, i)
Tenancy: tenancy, _, err = server.Backend.WriteCAS(tc.ctx, artist)
}, require.NoError(t, err)
Version: "",
} resources[i] = artist
server.Backend.WriteCAS(tc.ctx, r)
} }
rsp, err := client.List(tc.ctx, &pbresource.ListRequest{ rsp, err := client.List(tc.ctx, &pbresource.ListRequest{
Type: typev1, Type: demo.TypeV2Artist,
Tenancy: tenancy, Tenancy: demo.TenancyDefault,
NamePrefix: "", NamePrefix: "",
}) })
require.NoError(t, err) require.NoError(t, err)
@ -88,13 +88,18 @@ func TestList_GroupVersionMismatch(t *testing.T) {
for desc, tc := range listTestCases() { for desc, tc := range listTestCases() {
t.Run(desc, func(t *testing.T) { t.Run(desc, func(t *testing.T) {
server := testServer(t) server := testServer(t)
demo.Register(server.Registry)
client := testClient(t, server) client := testClient(t, server)
server.registry.Register(resource.Registration{Type: typev1})
server.Backend.WriteCAS(tc.ctx, &pbresource.Resource{Id: id2}) artist, err := demo.GenerateV2Artist()
require.NoError(t, err)
_, err = server.Backend.WriteCAS(tc.ctx, artist)
require.NoError(t, err)
rsp, err := client.List(tc.ctx, &pbresource.ListRequest{ rsp, err := client.List(tc.ctx, &pbresource.ListRequest{
Type: typev1, Type: demo.TypeV1Artist,
Tenancy: tenancy, Tenancy: artist.Id.Tenancy,
NamePrefix: "", NamePrefix: "",
}) })
require.NoError(t, err) require.NoError(t, err)
@ -109,18 +114,21 @@ func TestList_VerifyReadConsistencyArg(t *testing.T) {
t.Run(desc, func(t *testing.T) { t.Run(desc, func(t *testing.T) {
mockBackend := NewMockBackend(t) mockBackend := NewMockBackend(t)
server := NewServer(Config{ server := NewServer(Config{
registry: resource.NewRegistry(), Registry: resource.NewRegistry(),
Backend: mockBackend, Backend: mockBackend,
}) })
server.registry.Register(resource.Registration{Type: typev1}) demo.Register(server.Registry)
resource1 := &pbresource.Resource{Id: id1, Version: "1"}
artist, err := demo.GenerateV2Artist()
require.NoError(t, err)
mockBackend.On("List", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything). mockBackend.On("List", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).
Return([]*pbresource.Resource{resource1}, nil) Return([]*pbresource.Resource{artist}, nil)
client := testClient(t, server) client := testClient(t, server)
rsp, err := client.List(tc.ctx, &pbresource.ListRequest{Type: typev1, Tenancy: tenancy, NamePrefix: ""}) rsp, err := client.List(tc.ctx, &pbresource.ListRequest{Type: artist.Id.Type, Tenancy: artist.Id.Tenancy, NamePrefix: ""})
require.NoError(t, err) require.NoError(t, err)
prototest.AssertDeepEqual(t, resource1, rsp.Resources[0]) prototest.AssertDeepEqual(t, artist, rsp.Resources[0])
mockBackend.AssertCalled(t, "List", mock.Anything, tc.consistency, mock.Anything, mock.Anything, mock.Anything) mockBackend.AssertCalled(t, "List", mock.Anything, tc.consistency, mock.Anything, mock.Anything, mock.Anything)
}) })
} }

View File

@ -14,29 +14,36 @@ import (
"google.golang.org/grpc/status" "google.golang.org/grpc/status"
"github.com/hashicorp/consul/internal/resource" "github.com/hashicorp/consul/internal/resource"
"github.com/hashicorp/consul/internal/resource/demo"
"github.com/hashicorp/consul/internal/storage" "github.com/hashicorp/consul/internal/storage"
"github.com/hashicorp/consul/proto-public/pbresource" "github.com/hashicorp/consul/proto-public/pbresource"
"github.com/hashicorp/consul/proto/private/prototest" "github.com/hashicorp/consul/proto/private/prototest"
) )
func TestRead_TypeNotFound(t *testing.T) { func TestRead_TypeNotFound(t *testing.T) {
server := NewServer(Config{registry: resource.NewRegistry()}) server := NewServer(Config{Registry: resource.NewRegistry()})
client := testClient(t, server) client := testClient(t, server)
_, err := client.Read(context.Background(), &pbresource.ReadRequest{Id: id1}) artist, err := demo.GenerateV2Artist()
require.NoError(t, err)
_, err = client.Read(context.Background(), &pbresource.ReadRequest{Id: artist.Id})
require.Error(t, err) require.Error(t, err)
require.Equal(t, codes.InvalidArgument.String(), status.Code(err).String()) require.Equal(t, codes.InvalidArgument.String(), status.Code(err).String())
require.Contains(t, err.Error(), "resource type mesh/v1/service not registered") require.Contains(t, err.Error(), "resource type demo/v2/artist not registered")
} }
func TestRead_ResourceNotFound(t *testing.T) { func TestRead_ResourceNotFound(t *testing.T) {
for desc, tc := range readTestCases() { for desc, tc := range readTestCases() {
t.Run(desc, func(t *testing.T) { t.Run(desc, func(t *testing.T) {
server := testServer(t) server := testServer(t)
server.registry.Register(resource.Registration{Type: typev1}) demo.Register(server.Registry)
client := testClient(t, server) client := testClient(t, server)
_, err := client.Read(tc.ctx, &pbresource.ReadRequest{Id: id1}) artist, err := demo.GenerateV2Artist()
require.NoError(t, err)
_, err = client.Read(tc.ctx, &pbresource.ReadRequest{Id: artist.Id})
require.Error(t, err) require.Error(t, err)
require.Equal(t, codes.NotFound.String(), status.Code(err).String()) require.Equal(t, codes.NotFound.String(), status.Code(err).String())
require.Contains(t, err.Error(), "resource not found") require.Contains(t, err.Error(), "resource not found")
@ -48,15 +55,19 @@ func TestRead_GroupVersionMismatch(t *testing.T) {
for desc, tc := range readTestCases() { for desc, tc := range readTestCases() {
t.Run(desc, func(t *testing.T) { t.Run(desc, func(t *testing.T) {
server := testServer(t) server := testServer(t)
server.registry.Register(resource.Registration{Type: typev1}) demo.Register(server.Registry)
server.registry.Register(resource.Registration{Type: typev2})
client := testClient(t, server) client := testClient(t, server)
resource1 := &pbresource.Resource{Id: id1, Version: ""} artist, err := demo.GenerateV2Artist()
_, err := server.Backend.WriteCAS(tc.ctx, resource1)
require.NoError(t, err) require.NoError(t, err)
_, err = client.Read(tc.ctx, &pbresource.ReadRequest{Id: id2}) _, err = server.Backend.WriteCAS(tc.ctx, artist)
require.NoError(t, err)
id := clone(artist.Id)
id.Type = demo.TypeV1Artist
_, err = client.Read(tc.ctx, &pbresource.ReadRequest{Id: id})
require.Error(t, err) require.Error(t, err)
require.Equal(t, codes.InvalidArgument.String(), status.Code(err).String()) require.Equal(t, codes.InvalidArgument.String(), status.Code(err).String())
require.Contains(t, err.Error(), "resource was requested with GroupVersion") require.Contains(t, err.Error(), "resource was requested with GroupVersion")
@ -68,13 +79,16 @@ func TestRead_Success(t *testing.T) {
for desc, tc := range readTestCases() { for desc, tc := range readTestCases() {
t.Run(desc, func(t *testing.T) { t.Run(desc, func(t *testing.T) {
server := testServer(t) server := testServer(t)
server.registry.Register(resource.Registration{Type: typev1}) demo.Register(server.Registry)
client := testClient(t, server) client := testClient(t, server)
resource1 := &pbresource.Resource{Id: id1, Version: ""}
resource1, err := server.Backend.WriteCAS(tc.ctx, resource1) artist, err := demo.GenerateV2Artist()
require.NoError(t, err) require.NoError(t, err)
rsp, err := client.Read(tc.ctx, &pbresource.ReadRequest{Id: id1}) resource1, err := server.Backend.WriteCAS(tc.ctx, artist)
require.NoError(t, err)
rsp, err := client.Read(tc.ctx, &pbresource.ReadRequest{Id: artist.Id})
require.NoError(t, err) require.NoError(t, err)
prototest.AssertDeepEqual(t, resource1, rsp.Resource) prototest.AssertDeepEqual(t, resource1, rsp.Resource)
}) })
@ -87,17 +101,20 @@ func TestRead_VerifyReadConsistencyArg(t *testing.T) {
t.Run(desc, func(t *testing.T) { t.Run(desc, func(t *testing.T) {
mockBackend := NewMockBackend(t) mockBackend := NewMockBackend(t)
server := NewServer(Config{ server := NewServer(Config{
registry: resource.NewRegistry(), Registry: resource.NewRegistry(),
Backend: mockBackend, Backend: mockBackend,
}) })
server.registry.Register(resource.Registration{Type: typev1}) demo.Register(server.Registry)
resource1 := &pbresource.Resource{Id: id1, Version: "1"}
mockBackend.On("Read", mock.Anything, mock.Anything, mock.Anything).Return(resource1, nil) artist, err := demo.GenerateV2Artist()
require.NoError(t, err)
mockBackend.On("Read", mock.Anything, mock.Anything, mock.Anything).Return(artist, nil)
client := testClient(t, server) client := testClient(t, server)
rsp, err := client.Read(tc.ctx, &pbresource.ReadRequest{Id: id1}) rsp, err := client.Read(tc.ctx, &pbresource.ReadRequest{Id: artist.Id})
require.NoError(t, err) require.NoError(t, err)
prototest.AssertDeepEqual(t, resource1, rsp.Resource) prototest.AssertDeepEqual(t, artist, rsp.Resource)
mockBackend.AssertCalled(t, "Read", mock.Anything, tc.consistency, mock.Anything) mockBackend.AssertCalled(t, "Read", mock.Anything, tc.consistency, mock.Anything)
}) })
} }

View File

@ -6,10 +6,12 @@ package resource
import ( import (
"context" "context"
"github.com/hashicorp/go-hclog"
"google.golang.org/grpc" "google.golang.org/grpc"
"google.golang.org/grpc/codes" "google.golang.org/grpc/codes"
"google.golang.org/grpc/metadata" "google.golang.org/grpc/metadata"
"google.golang.org/grpc/status" "google.golang.org/grpc/status"
"google.golang.org/protobuf/proto"
"github.com/hashicorp/consul/internal/resource" "github.com/hashicorp/consul/internal/resource"
"github.com/hashicorp/consul/internal/storage" "github.com/hashicorp/consul/internal/storage"
@ -21,7 +23,8 @@ type Server struct {
} }
type Config struct { type Config struct {
registry Registry Logger hclog.Logger
Registry Registry
// Backend is the storage backend that will be used for resource persistence. // Backend is the storage backend that will be used for resource persistence.
Backend Backend Backend Backend
@ -47,11 +50,6 @@ func (s *Server) Register(grpcServer *grpc.Server) {
pbresource.RegisterResourceServiceServer(grpcServer, s) pbresource.RegisterResourceServiceServer(grpcServer, s)
} }
func (s *Server) Write(ctx context.Context, req *pbresource.WriteRequest) (*pbresource.WriteResponse, error) {
// TODO
return &pbresource.WriteResponse{}, nil
}
func (s *Server) WriteStatus(ctx context.Context, req *pbresource.WriteStatusRequest) (*pbresource.WriteStatusResponse, error) { func (s *Server) WriteStatus(ctx context.Context, req *pbresource.WriteStatusRequest) (*pbresource.WriteStatusResponse, error) {
// TODO // TODO
return &pbresource.WriteStatusResponse{}, nil return &pbresource.WriteStatusResponse{}, nil
@ -64,7 +62,7 @@ func (s *Server) Delete(ctx context.Context, req *pbresource.DeleteRequest) (*pb
//nolint:unparam //nolint:unparam
func (s *Server) resolveType(typ *pbresource.Type) (*resource.Registration, error) { func (s *Server) resolveType(typ *pbresource.Type) (*resource.Registration, error) {
v, ok := s.registry.Resolve(typ) v, ok := s.Registry.Resolve(typ)
if ok { if ok {
return &v, nil return &v, nil
} }
@ -90,3 +88,5 @@ func readConsistencyFrom(ctx context.Context) storage.ReadConsistency {
} }
return storage.EventualConsistency return storage.EventualConsistency
} }
func clone[T proto.Message](v T) T { return proto.Clone(v).(T) }

View File

@ -5,25 +5,21 @@ package resource
import ( import (
"context" "context"
"fmt"
"testing" "testing"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"google.golang.org/grpc" "google.golang.org/grpc"
"google.golang.org/protobuf/types/known/anypb"
"github.com/hashicorp/consul/agent/grpc-external/testutils" "github.com/hashicorp/consul/agent/grpc-external/testutils"
"github.com/hashicorp/consul/internal/resource" "github.com/hashicorp/consul/internal/resource"
"github.com/hashicorp/consul/internal/storage/inmem" "github.com/hashicorp/consul/internal/storage/inmem"
"github.com/hashicorp/consul/proto-public/pbresource" "github.com/hashicorp/consul/proto-public/pbresource"
pbdemov2 "github.com/hashicorp/consul/proto/private/pbdemo/v2"
"github.com/hashicorp/consul/sdk/testutil"
) )
func TestWrite_TODO(t *testing.T) {
server := testServer(t)
client := testClient(t, server)
resp, err := client.Write(context.Background(), &pbresource.WriteRequest{})
require.NoError(t, err)
require.NotNil(t, resp)
}
func TestWriteStatus_TODO(t *testing.T) { func TestWriteStatus_TODO(t *testing.T) {
server := testServer(t) server := testServer(t)
client := testClient(t, server) client := testClient(t, server)
@ -47,8 +43,11 @@ func testServer(t *testing.T) *Server {
require.NoError(t, err) require.NoError(t, err)
go backend.Run(testContext(t)) go backend.Run(testContext(t))
registry := resource.NewRegistry() return NewServer(Config{
return NewServer(Config{registry: registry, Backend: backend}) Logger: testutil.Logger(t),
Registry: resource.NewRegistry(),
Backend: backend,
})
} }
func testClient(t *testing.T, server *Server) pbresource.ResourceServiceClient { func testClient(t *testing.T, server *Server) pbresource.ResourceServiceClient {
@ -67,46 +66,24 @@ func testClient(t *testing.T, server *Server) pbresource.ResourceServiceClient {
} }
func testContext(t *testing.T) context.Context { func testContext(t *testing.T) context.Context {
t.Helper()
ctx, cancel := context.WithCancel(context.Background()) ctx, cancel := context.WithCancel(context.Background())
t.Cleanup(cancel) t.Cleanup(cancel)
return ctx return ctx
} }
var ( func modifyArtist(t *testing.T, res *pbresource.Resource) *pbresource.Resource {
tenancy = &pbresource.Tenancy{ t.Helper()
Partition: "default",
Namespace: "default", var artist pbdemov2.Artist
PeerName: "local", require.NoError(t, res.Data.UnmarshalTo(&artist))
} artist.Name = fmt.Sprintf("The artist formerly known as %s", artist.Name)
typev1 = &pbresource.Type{
Group: "mesh", data, err := anypb.New(&artist)
GroupVersion: "v1", require.NoError(t, err)
Kind: "service",
} res = clone(res)
typev2 = &pbresource.Type{ res.Data = data
Group: "mesh", return res
GroupVersion: "v2", }
Kind: "service",
}
id1 = &pbresource.ID{
Uid: "abcd",
Name: "billing",
Type: typev1,
Tenancy: tenancy,
}
id2 = &pbresource.ID{
Uid: "abcd",
Name: "billing",
Type: typev2,
Tenancy: tenancy,
}
resourcev1 = &pbresource.Resource{
Id: &pbresource.ID{
Uid: "someUid",
Name: "someName",
Type: typev1,
Tenancy: tenancy,
},
Version: "",
}
)

View File

@ -10,14 +10,13 @@ import (
"testing" "testing"
"time" "time"
"github.com/hashicorp/consul/internal/resource" "github.com/hashicorp/consul/internal/resource/demo"
"github.com/hashicorp/consul/proto-public/pbresource" "github.com/hashicorp/consul/proto-public/pbresource"
"github.com/hashicorp/consul/proto/private/prototest" "github.com/hashicorp/consul/proto/private/prototest"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"google.golang.org/grpc/codes" "google.golang.org/grpc/codes"
"google.golang.org/grpc/status" "google.golang.org/grpc/status"
"google.golang.org/protobuf/proto"
) )
func TestWatchList_TypeNotFound(t *testing.T) { func TestWatchList_TypeNotFound(t *testing.T) {
@ -26,8 +25,8 @@ func TestWatchList_TypeNotFound(t *testing.T) {
client := testClient(t, server) client := testClient(t, server)
stream, err := client.WatchList(context.Background(), &pbresource.WatchListRequest{ stream, err := client.WatchList(context.Background(), &pbresource.WatchListRequest{
Type: typev1, Type: demo.TypeV2Artist,
Tenancy: tenancy, Tenancy: demo.TenancyDefault,
NamePrefix: "", NamePrefix: "",
}) })
require.NoError(t, err) require.NoError(t, err)
@ -35,34 +34,37 @@ func TestWatchList_TypeNotFound(t *testing.T) {
err = mustGetError(t, rspCh) err = mustGetError(t, rspCh)
require.Equal(t, codes.InvalidArgument.String(), status.Code(err).String()) require.Equal(t, codes.InvalidArgument.String(), status.Code(err).String())
require.Contains(t, err.Error(), "resource type mesh/v1/service not registered") require.Contains(t, err.Error(), "resource type demo/v2/artist not registered")
} }
func TestWatchList_GroupVersionMatches(t *testing.T) { func TestWatchList_GroupVersionMatches(t *testing.T) {
t.Parallel() t.Parallel()
server := testServer(t) server := testServer(t)
client := testClient(t, server) client := testClient(t, server)
server.registry.Register(resource.Registration{Type: typev1}) demo.Register(server.Registry)
ctx := context.Background() ctx := context.Background()
// create a watch // create a watch
stream, err := client.WatchList(ctx, &pbresource.WatchListRequest{ stream, err := client.WatchList(ctx, &pbresource.WatchListRequest{
Type: typev1, Type: demo.TypeV2Artist,
Tenancy: tenancy, Tenancy: demo.TenancyDefault,
NamePrefix: "", NamePrefix: "",
}) })
require.NoError(t, err) require.NoError(t, err)
rspCh := handleResourceStream(t, stream) rspCh := handleResourceStream(t, stream)
artist, err := demo.GenerateV2Artist()
require.NoError(t, err)
// insert and verify upsert event received // insert and verify upsert event received
r1, err := server.Backend.WriteCAS(ctx, resourcev1) r1, err := server.Backend.WriteCAS(ctx, artist)
require.NoError(t, err) require.NoError(t, err)
rsp := mustGetResource(t, rspCh) rsp := mustGetResource(t, rspCh)
require.Equal(t, pbresource.WatchEvent_OPERATION_UPSERT, rsp.Operation) require.Equal(t, pbresource.WatchEvent_OPERATION_UPSERT, rsp.Operation)
prototest.AssertDeepEqual(t, r1, rsp.Resource) prototest.AssertDeepEqual(t, r1, rsp.Resource)
// update and verify upsert event received // update and verify upsert event received
r2 := clone(r1) r2 := modifyArtist(t, r1)
r2, err = server.Backend.WriteCAS(ctx, r2) r2, err = server.Backend.WriteCAS(ctx, r2)
require.NoError(t, err) require.NoError(t, err)
rsp = mustGetResource(t, rspCh) rsp = mustGetResource(t, rspCh)
@ -77,27 +79,29 @@ func TestWatchList_GroupVersionMatches(t *testing.T) {
} }
func TestWatchList_GroupVersionMismatch(t *testing.T) { func TestWatchList_GroupVersionMismatch(t *testing.T) {
// Given a watch on typev2 that only differs from typev1 by GroupVersion // Given a watch on TypeArtistV1 that only differs from TypeArtistV2 by GroupVersion
// When a resource of typev1 is created/updated/deleted // When a resource of TypeArtistV2 is created/updated/deleted
// Then no watch events should be emitted // Then no watch events should be emitted
t.Parallel() t.Parallel()
server := testServer(t) server := testServer(t)
demo.Register(server.Registry)
client := testClient(t, server) client := testClient(t, server)
ctx := context.Background() ctx := context.Background()
server.registry.Register(resource.Registration{Type: typev1})
server.registry.Register(resource.Registration{Type: typev2})
// create a watch for typev2 // create a watch for TypeArtistV1
stream, err := client.WatchList(ctx, &pbresource.WatchListRequest{ stream, err := client.WatchList(ctx, &pbresource.WatchListRequest{
Type: typev2, Type: demo.TypeV1Artist,
Tenancy: tenancy, Tenancy: demo.TenancyDefault,
NamePrefix: "", NamePrefix: "",
}) })
require.NoError(t, err) require.NoError(t, err)
rspCh := handleResourceStream(t, stream) rspCh := handleResourceStream(t, stream)
artist, err := demo.GenerateV2Artist()
require.NoError(t, err)
// insert // insert
r1, err := server.Backend.WriteCAS(ctx, resourcev1) r1, err := server.Backend.WriteCAS(ctx, artist)
require.NoError(t, err) require.NoError(t, err)
// update // update
@ -176,5 +180,3 @@ type resourceOrError struct {
rsp *pbresource.WatchEvent rsp *pbresource.WatchEvent
err error err error
} }
func clone[T proto.Message](v T) T { return proto.Clone(v).(T) }

View File

@ -0,0 +1,185 @@
package resource
import (
"context"
"errors"
"strings"
"time"
"github.com/oklog/ulid/v2"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"github.com/hashicorp/consul/internal/storage"
"github.com/hashicorp/consul/lib/retry"
"github.com/hashicorp/consul/proto-public/pbresource"
)
func (s *Server) Write(ctx context.Context, req *pbresource.WriteRequest) (*pbresource.WriteResponse, error) {
if err := validateWriteRequiredFields(req); err != nil {
return nil, err
}
reg, err := s.resolveType(req.Resource.Id.Type)
if err != nil {
return nil, err
}
// Check the user sent the correct type of data.
if !req.Resource.Data.MessageIs(reg.Proto) {
got := strings.TrimPrefix(req.Resource.Data.TypeUrl, "type.googleapis.com/")
return nil, status.Errorf(
codes.InvalidArgument,
"resource.data is of wrong type (expected=%q, got=%q)",
reg.Proto.ProtoReflect().Descriptor().FullName(),
got,
)
}
// At the storage backend layer, all writes are CAS operations.
//
// This makes it possible to *safely* do things like keeping the Uid stable
// across writes, carrying statuses over, and passing the current version of
// the resource to hooks, without restricting ourselves to only using the more
// feature-rich storage systems that support "patch" updates etc. natively.
//
// Although CAS semantics are useful for machine users like controllers, human
// users generally don't need them. If the user is performing a non-CAS write,
// we read the current version, and automatically retry if the CAS write fails.
var result *pbresource.Resource
err = s.retryCAS(ctx, req.Resource.Version, func() error {
input := clone(req.Resource)
// We read with EventualConsistency here because:
//
// - In the common case, individual resources are written infrequently, and
// when using the Raft backend followers are generally within a few hundred
// milliseconds of the leader, so the first read will probably return the
// current version.
//
// - StrongConsistency is expensive. In the Raft backend, it involves a round
// of heartbeats to verify cluster leadership (in addition to the write's
// log replication).
//
// - CAS failures will be retried by retryCAS anyway. So the read-modify-write
// cycle should eventually succeed.
existing, err := s.Backend.Read(ctx, storage.EventualConsistency, input.Id)
switch {
// Create path.
case errors.Is(err, storage.ErrNotFound):
input.Id.Uid = ulid.Make().String()
// TODO: Prevent setting statuses in this endpoint.
// Update path.
case err == nil:
// Use the stored ID because it includes the Uid.
//
// Generally, users won't provide the Uid but controllers will, because
// controllers need to operate on a specific "incarnation" of a resource
// as opposed to an older/newer resource with the same name, whereas users
// just want to update the current resource.
input.Id = existing.Id
// User is doing a non-CAS write, use the current version.
if input.Version == "" {
input.Version = existing.Version
}
// Check the stored version matches the user-given version.
//
// Although CAS operations are implemented "for real" at the storage backend
// layer, we must check the version here too to prevent a scenario where:
//
// - Current resource version is `v2`
// - User passes version `v2`
// - Read returns stale version `v1`
// - We carry `v1`'s statuses over (effectively overwriting `v2`'s statuses)
// - CAS operation succeeds anyway because user-given version is current
//
// TODO(boxofrad): add a test for this once the status field has been added.
if input.Version != existing.Version {
return storage.ErrCASFailure
}
// TODO: Carry over the statuses here.
default:
return err
}
input.Generation = ulid.Make().String()
result, err = s.Backend.WriteCAS(ctx, input)
return err
})
switch {
case errors.Is(err, storage.ErrCASFailure):
return nil, status.Error(codes.Aborted, err.Error())
case errors.Is(err, storage.ErrWrongUid):
return nil, status.Error(codes.FailedPrecondition, err.Error())
case err != nil:
return nil, status.Errorf(codes.Internal, "failed to write resource: %v", err.Error())
}
return &pbresource.WriteResponse{Resource: result}, nil
}
// retryCAS retries the given operation with exponential backoff if the user
// didn't provide a version. This is intended to hide failures when the user
// isn't intentionally performing a CAS operation (all writes are, by design,
// CAS operations at the storage backend layer).
func (s *Server) retryCAS(ctx context.Context, vsn string, cas func() error) error {
if vsn != "" {
return cas()
}
const maxAttempts = 5
// These parameters are fairly arbitrary, so if you find better ones then go
// ahead and swap them out! In general, we want to wait long enough to smooth
// over small amounts of storage replication lag, but not so long that we make
// matters worse by holding onto load.
backoff := &retry.Waiter{
MinWait: 50 * time.Millisecond,
MaxWait: 1 * time.Second,
Jitter: retry.NewJitter(50),
Factor: 75 * time.Millisecond,
}
var err error
for i := 1; i <= maxAttempts; i++ {
if err = cas(); !errors.Is(err, storage.ErrCASFailure) {
break
}
if backoff.Wait(ctx) != nil {
break
}
s.Logger.Trace("retrying failed CAS operation", "failure_count", i)
}
return err
}
func validateWriteRequiredFields(req *pbresource.WriteRequest) error {
var field string
switch {
case req.Resource == nil:
field = "resource"
case req.Resource.Id == nil:
field = "resource.id"
case req.Resource.Id.Type == nil:
field = "resource.id.type"
case req.Resource.Id.Tenancy == nil:
field = "resource.id.tenancy"
case req.Resource.Id.Name == "":
field = "resource.id.name"
case req.Resource.Data == nil:
field = "resource.data"
}
if field == "" {
return nil
}
return status.Errorf(codes.InvalidArgument, "%s is required", field)
}

View File

@ -0,0 +1,250 @@
package resource
import (
"context"
"sync"
"testing"
"github.com/stretchr/testify/require"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"google.golang.org/protobuf/types/known/anypb"
"github.com/hashicorp/consul/internal/resource/demo"
"github.com/hashicorp/consul/internal/storage"
"github.com/hashicorp/consul/proto-public/pbresource"
pbdemov2 "github.com/hashicorp/consul/proto/private/pbdemo/v2"
)
func TestWrite_InputValidation(t *testing.T) {
server := testServer(t)
client := testClient(t, server)
demo.Register(server.Registry)
testCases := map[string]func(*pbresource.WriteRequest){
"no resource": func(req *pbresource.WriteRequest) { req.Resource = nil },
"no id": func(req *pbresource.WriteRequest) { req.Resource.Id = nil },
"no type": func(req *pbresource.WriteRequest) { req.Resource.Id.Type = nil },
"no tenancy": func(req *pbresource.WriteRequest) { req.Resource.Id.Tenancy = nil },
"no name": func(req *pbresource.WriteRequest) { req.Resource.Id.Name = "" },
"no data": func(req *pbresource.WriteRequest) { req.Resource.Data = nil },
"wrong data type": func(req *pbresource.WriteRequest) {
var err error
req.Resource.Data, err = anypb.New(&pbdemov2.Album{})
require.NoError(t, err)
},
}
for desc, modFn := range testCases {
t.Run(desc, func(t *testing.T) {
res, err := demo.GenerateV2Artist()
require.NoError(t, err)
req := &pbresource.WriteRequest{Resource: res}
modFn(req)
_, err = client.Write(testContext(t), req)
require.Error(t, err)
require.Equal(t, codes.InvalidArgument.String(), status.Code(err).String())
})
}
}
func TestWrite_TypeNotFound(t *testing.T) {
server := testServer(t)
client := testClient(t, server)
res, err := demo.GenerateV2Artist()
require.NoError(t, err)
_, err = client.Write(testContext(t), &pbresource.WriteRequest{Resource: res})
require.Error(t, err)
require.Equal(t, codes.InvalidArgument.String(), status.Code(err).String())
require.Contains(t, err.Error(), "resource type demo/v2/artist not registered")
}
func TestWrite_ResourceCreation(t *testing.T) {
server := testServer(t)
client := testClient(t, server)
demo.Register(server.Registry)
res, err := demo.GenerateV2Artist()
require.NoError(t, err)
rsp, err := client.Write(testContext(t), &pbresource.WriteRequest{Resource: res})
require.NoError(t, err)
require.NotEmpty(t, rsp.Resource.Version, "resource should have version")
require.NotEmpty(t, rsp.Resource.Id.Uid, "resource id should have uid")
require.NotEmpty(t, rsp.Resource.Generation, "resource should have generation")
}
func TestWrite_CASUpdate_Success(t *testing.T) {
server := testServer(t)
client := testClient(t, server)
demo.Register(server.Registry)
res, err := demo.GenerateV2Artist()
require.NoError(t, err)
rsp1, err := client.Write(testContext(t), &pbresource.WriteRequest{Resource: res})
require.NoError(t, err)
rsp2, err := client.Write(testContext(t), &pbresource.WriteRequest{
Resource: modifyArtist(t, rsp1.Resource),
})
require.NoError(t, err)
require.Equal(t, rsp1.Resource.Id.Uid, rsp2.Resource.Id.Uid)
require.NotEqual(t, rsp1.Resource.Version, rsp2.Resource.Version)
require.NotEqual(t, rsp1.Resource.Generation, rsp2.Resource.Generation)
}
func TestWrite_CASUpdate_Failure(t *testing.T) {
server := testServer(t)
client := testClient(t, server)
demo.Register(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 = modifyArtist(t, rsp1.Resource)
res.Version = "wrong-version"
_, err = client.Write(testContext(t), &pbresource.WriteRequest{Resource: res})
require.Error(t, err)
require.Equal(t, codes.Aborted.String(), status.Code(err).String())
require.Contains(t, err.Error(), "CAS operation failed")
}
func TestWrite_Update_WrongUid(t *testing.T) {
server := testServer(t)
client := testClient(t, server)
demo.Register(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 = modifyArtist(t, rsp1.Resource)
res.Id.Uid = "wrong-uid"
_, err = client.Write(testContext(t), &pbresource.WriteRequest{Resource: res})
require.Error(t, err)
require.Equal(t, codes.FailedPrecondition.String(), status.Code(err).String())
require.Contains(t, err.Error(), "uid doesn't match")
}
func TestWrite_Update_NoUid(t *testing.T) {
server := testServer(t)
client := testClient(t, server)
demo.Register(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 = modifyArtist(t, rsp1.Resource)
res.Id.Uid = ""
_, 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)
demo.Register(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 = modifyArtist(t, rsp1.Resource)
res.Version = ""
rsp2, err := client.Write(testContext(t), &pbresource.WriteRequest{Resource: res})
require.NoError(t, err)
require.NotEmpty(t, rsp2.Resource.Version)
require.NotEqual(t, rsp1.Resource.Version, rsp2.Resource.Version)
}
func TestWrite_NonCASUpdate_Retry(t *testing.T) {
server := testServer(t)
client := testClient(t, server)
demo.Register(server.Registry)
res, err := demo.GenerateV2Artist()
require.NoError(t, err)
rsp1, err := client.Write(testContext(t), &pbresource.WriteRequest{Resource: res})
require.NoError(t, err)
// Simulate conflicting writes by blocking the RPC after it has read the
// current version of the resource, but before it tries to make a write.
backend := &blockOnceBackend{
Backend: server.Backend,
readCh: make(chan struct{}),
blockCh: make(chan struct{}),
}
server.Backend = backend
errCh := make(chan error)
go func() {
res := modifyArtist(t, rsp1.Resource)
res.Version = ""
_, err := client.Write(testContext(t), &pbresource.WriteRequest{Resource: res})
errCh <- err
}()
// Wait for the read, to ensure the Write in the goroutine above has read the
// current version of the resource.
<-backend.readCh
// Update the resource.
res = modifyArtist(t, rsp1.Resource)
_, err = backend.WriteCAS(testContext(t), res)
require.NoError(t, err)
// Unblock the read.
close(backend.blockCh)
// Check that the write succeeded anyway because of a retry.
require.NoError(t, <-errCh)
}
type blockOnceBackend struct {
storage.Backend
once sync.Once
readCh chan struct{}
blockCh chan struct{}
}
func (b *blockOnceBackend) Read(ctx context.Context, consistency storage.ReadConsistency, id *pbresource.ID) (*pbresource.Resource, error) {
res, err := b.Backend.Read(ctx, consistency, id)
b.once.Do(func() {
close(b.readCh)
<-b.blockCh
})
return res, err
}

1
go.mod
View File

@ -85,6 +85,7 @@ require (
github.com/mitchellh/mapstructure v1.5.0 github.com/mitchellh/mapstructure v1.5.0
github.com/mitchellh/pointerstructure v1.2.1 github.com/mitchellh/pointerstructure v1.2.1
github.com/mitchellh/reflectwalk v1.0.2 github.com/mitchellh/reflectwalk v1.0.2
github.com/oklog/ulid/v2 v2.1.0
github.com/olekukonko/tablewriter v0.0.4 github.com/olekukonko/tablewriter v0.0.4
github.com/patrickmn/go-cache v2.1.0+incompatible github.com/patrickmn/go-cache v2.1.0+incompatible
github.com/pkg/errors v0.9.1 github.com/pkg/errors v0.9.1

3
go.sum
View File

@ -821,6 +821,8 @@ github.com/oklog/run v1.0.0 h1:Ru7dDtJNOyC66gQ5dQmaCa0qIsAUFY3sFpK1Xk8igrw=
github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA=
github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4= github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4=
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
github.com/oklog/ulid/v2 v2.1.0 h1:+9lhoxAP56we25tyYETBBY1YLA2SaoLvUFgrP2miPJU=
github.com/oklog/ulid/v2 v2.1.0/go.mod h1:rcEKHmBBKfef9DhnvX7y1HZBYxjXb0cP5ExxNsTT1QQ=
github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
github.com/olekukonko/tablewriter v0.0.0-20180130162743-b8a9be070da4/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= github.com/olekukonko/tablewriter v0.0.0-20180130162743-b8a9be070da4/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
github.com/olekukonko/tablewriter v0.0.1/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= github.com/olekukonko/tablewriter v0.0.1/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
@ -850,6 +852,7 @@ github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0Mw
github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
github.com/pborman/getopt v0.0.0-20170112200414-7148bc3a4c30/go.mod h1:85jBQOZwpVEaDAr341tbn15RS4fCAsIst0qp7i8ex1o=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/pelletier/go-toml v1.7.0/go.mod h1:vwGMzjaWMwyfHwgIBhI2YUM4fB6nL6lVAvS1LBMMhTE= github.com/pelletier/go-toml v1.7.0/go.mod h1:vwGMzjaWMwyfHwgIBhI2YUM4fB6nL6lVAvS1LBMMhTE=
github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU=

View File

@ -0,0 +1,202 @@
// Package demo includes fake resource types for working on Consul's generic
// state storage without having to refer to specific features.
package demo
import (
"fmt"
"math/rand"
"strings"
"time"
"google.golang.org/protobuf/types/known/anypb"
"github.com/hashicorp/consul/internal/resource"
"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"
)
var (
// TenancyDefault contains the default values for all tenancy units.
TenancyDefault = &pbresource.Tenancy{
Partition: "default",
PeerName: "local",
Namespace: "default",
}
// TypeV1Artist represents a musician or group of musicians.
TypeV1Artist = &pbresource.Type{
Group: "demo",
GroupVersion: "v1",
Kind: "artist",
}
// TypeV1Album represents a collection of an artist's songs.
TypeV1Album = &pbresource.Type{
Group: "demo",
GroupVersion: "v1",
Kind: "album",
}
// TypeV2Artist represents a musician or group of musicians.
TypeV2Artist = &pbresource.Type{
Group: "demo",
GroupVersion: "v2",
Kind: "artist",
}
// TypeV2Album represents a collection of an artist's songs.
TypeV2Album = &pbresource.Type{
Group: "demo",
GroupVersion: "v2",
Kind: "album",
}
)
// Register demo types. Should only be called in tests and dev mode.
func Register(r resource.Registry) {
r.Register(resource.Registration{
Type: TypeV1Artist,
Proto: &pbdemov1.Artist{},
})
r.Register(resource.Registration{
Type: TypeV1Album,
Proto: &pbdemov1.Album{},
})
r.Register(resource.Registration{
Type: TypeV2Artist,
Proto: &pbdemov2.Artist{},
})
r.Register(resource.Registration{
Type: TypeV2Album,
Proto: &pbdemov2.Album{},
})
}
// GenerateV2Artist generates a random Artist resource.
func GenerateV2Artist() (*pbresource.Resource, error) {
adjective := adjectives[rand.Intn(len(adjectives))]
noun := nouns[rand.Intn(len(nouns))]
numMembers := rand.Intn(5) + 1
groupMembers := make(map[string]string, numMembers)
for i := 0; i < numMembers; i++ {
groupMembers[members[rand.Intn(len(members))]] = instruments[rand.Intn(len(instruments))]
}
data, err := anypb.New(&pbdemov2.Artist{
Name: fmt.Sprintf("%s %s", adjective, noun),
Genre: randomGenre(),
GroupMembers: groupMembers,
})
if err != nil {
return nil, err
}
return &pbresource.Resource{
Id: &pbresource.ID{
Type: TypeV2Artist,
Tenancy: TenancyDefault,
Name: fmt.Sprintf("%s-%s", strings.ToLower(adjective), strings.ToLower(noun)),
},
Data: data,
Metadata: map[string]string{
"generated_at": time.Now().Format(time.RFC3339),
},
}, nil
}
// GenerateV2Album generates a random Album resource, owned by the Artist with
// the given ID.
func GenerateV2Album(artistID *pbresource.ID) (*pbresource.Resource, error) {
adjective := adjectives[rand.Intn(len(adjectives))]
noun := nouns[rand.Intn(len(nouns))]
numTracks := 3 + rand.Intn(3)
tracks := make([]string, numTracks)
for i := 0; i < numTracks; i++ {
words := nouns
if i%3 == 0 {
words = adjectives
}
tracks[i] = words[rand.Intn(len(words))]
}
data, err := anypb.New(&pbdemov2.Album{
Title: fmt.Sprintf("%s %s", adjective, noun),
YearOfRelease: int32(1990 + rand.Intn(time.Now().Year()-1990)),
CriticallyAclaimed: rand.Int()%2 == 0,
Tracks: tracks,
})
if err != nil {
return nil, err
}
return &pbresource.Resource{
Id: &pbresource.ID{
Type: TypeV2Album,
Tenancy: artistID.Tenancy,
Name: fmt.Sprintf("%s/%s-%s", artistID.Name, strings.ToLower(adjective), strings.ToLower(noun)),
},
Owner: artistID,
Data: data,
Metadata: map[string]string{
"generated_at": time.Now().Format(time.RFC3339),
},
}, nil
}
func randomGenre() pbdemov2.Genre {
return pbdemov2.Genre(rand.Intn(len(pbdemov1.Genre_name)-1) + 1)
}
var (
adjectives = []string{
"Purple",
"Angry",
"Euphoric",
"Unexpected",
"Cheesy",
"Rancid",
"Pleasant",
"Mumbling",
"Enlightened",
}
nouns = []string{
"Speakerphone",
"Fox",
"Guppy",
"Smile",
"Emacs",
"Grapefruit",
"Engineer",
"Basketball",
}
members = []string{
"Owl",
"Tiger",
"Beetle",
"Lion",
"Chicken",
"Snake",
"Monkey",
"Kitten",
"Hound",
}
instruments = []string{
"Guitar",
"Bass",
"Lead Vocals",
"Backing Vocals",
"Drums",
"Synthesizer",
"Triangle",
"Standing by the stage looking cool",
}
)

View File

@ -7,6 +7,8 @@ import (
"fmt" "fmt"
"sync" "sync"
"google.golang.org/protobuf/proto"
"github.com/hashicorp/consul/proto-public/pbresource" "github.com/hashicorp/consul/proto-public/pbresource"
) )
@ -22,6 +24,9 @@ type Registration struct {
// Type is the GVK of the resource type. // Type is the GVK of the resource type.
Type *pbresource.Type Type *pbresource.Type
// Proto is the resource's protobuf message type.
Proto proto.Message
// 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
} }

View File

@ -120,15 +120,15 @@ func (s *Store) WriteCAS(res *pbresource.Resource, vsn string) error {
if existing != nil { if existing != nil {
existingRes := existing.(*pbresource.Resource) existingRes := existing.(*pbresource.Resource)
// Ensure CAS semantics.
if existingRes.Version != vsn {
return storage.ErrCASFailure
}
// Uid is immutable. // Uid is immutable.
if existingRes.Id.Uid != res.Id.Uid { if existingRes.Id.Uid != res.Id.Uid {
return storage.ErrWrongUid return storage.ErrWrongUid
} }
// Ensure CAS semantics.
if existingRes.Version != vsn {
return storage.ErrCASFailure
}
} }
if err := tx.Insert(tableNameResources, res); err != nil { if err := tx.Insert(tableNameResources, res); err != nil {

View File

@ -0,0 +1,28 @@
// Code generated by protoc-gen-go-binary. DO NOT EDIT.
// source: private/pbdemo/v1/demo.proto
package demov1
import (
"google.golang.org/protobuf/proto"
)
// MarshalBinary implements encoding.BinaryMarshaler
func (msg *Artist) MarshalBinary() ([]byte, error) {
return proto.Marshal(msg)
}
// UnmarshalBinary implements encoding.BinaryUnmarshaler
func (msg *Artist) UnmarshalBinary(b []byte) error {
return proto.Unmarshal(b, msg)
}
// MarshalBinary implements encoding.BinaryMarshaler
func (msg *Album) MarshalBinary() ([]byte, error) {
return proto.Marshal(msg)
}
// UnmarshalBinary implements encoding.BinaryUnmarshaler
func (msg *Album) UnmarshalBinary(b []byte) error {
return proto.Unmarshal(b, msg)
}

View File

@ -0,0 +1,387 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.28.1
// protoc (unknown)
// source: private/pbdemo/v1/demo.proto
// This package contains fake resource types, which are useful for working on
// Consul's generic storage APIs.
package demov1
import (
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)
)
type Genre int32
const (
Genre_GENRE_UNSPECIFIED Genre = 0
Genre_GENRE_JAZZ Genre = 1
Genre_GENRE_FOLK Genre = 2
Genre_GENRE_POP Genre = 3
Genre_GENRE_METAL Genre = 4
Genre_GENRE_PUNK Genre = 5
Genre_GENRE_BLUES Genre = 6
Genre_GENRE_R_AND_B Genre = 7
Genre_GENRE_COUNTRY Genre = 8
Genre_GENRE_DISCO Genre = 9
Genre_GENRE_SKA Genre = 10
Genre_GENRE_HIP_HOP Genre = 11
Genre_GENRE_INDIE Genre = 12
)
// Enum value maps for Genre.
var (
Genre_name = map[int32]string{
0: "GENRE_UNSPECIFIED",
1: "GENRE_JAZZ",
2: "GENRE_FOLK",
3: "GENRE_POP",
4: "GENRE_METAL",
5: "GENRE_PUNK",
6: "GENRE_BLUES",
7: "GENRE_R_AND_B",
8: "GENRE_COUNTRY",
9: "GENRE_DISCO",
10: "GENRE_SKA",
11: "GENRE_HIP_HOP",
12: "GENRE_INDIE",
}
Genre_value = map[string]int32{
"GENRE_UNSPECIFIED": 0,
"GENRE_JAZZ": 1,
"GENRE_FOLK": 2,
"GENRE_POP": 3,
"GENRE_METAL": 4,
"GENRE_PUNK": 5,
"GENRE_BLUES": 6,
"GENRE_R_AND_B": 7,
"GENRE_COUNTRY": 8,
"GENRE_DISCO": 9,
"GENRE_SKA": 10,
"GENRE_HIP_HOP": 11,
"GENRE_INDIE": 12,
}
)
func (x Genre) Enum() *Genre {
p := new(Genre)
*p = x
return p
}
func (x Genre) String() string {
return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
}
func (Genre) Descriptor() protoreflect.EnumDescriptor {
return file_private_pbdemo_v1_demo_proto_enumTypes[0].Descriptor()
}
func (Genre) Type() protoreflect.EnumType {
return &file_private_pbdemo_v1_demo_proto_enumTypes[0]
}
func (x Genre) Number() protoreflect.EnumNumber {
return protoreflect.EnumNumber(x)
}
// Deprecated: Use Genre.Descriptor instead.
func (Genre) EnumDescriptor() ([]byte, []int) {
return file_private_pbdemo_v1_demo_proto_rawDescGZIP(), []int{0}
}
type Artist struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
Description string `protobuf:"bytes,2,opt,name=description,proto3" json:"description,omitempty"`
Genre Genre `protobuf:"varint,3,opt,name=genre,proto3,enum=hashicorp.consul.internal.demo.v1.Genre" json:"genre,omitempty"`
GroupMembers int32 `protobuf:"varint,4,opt,name=group_members,json=groupMembers,proto3" json:"group_members,omitempty"`
}
func (x *Artist) Reset() {
*x = Artist{}
if protoimpl.UnsafeEnabled {
mi := &file_private_pbdemo_v1_demo_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *Artist) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*Artist) ProtoMessage() {}
func (x *Artist) ProtoReflect() protoreflect.Message {
mi := &file_private_pbdemo_v1_demo_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 Artist.ProtoReflect.Descriptor instead.
func (*Artist) Descriptor() ([]byte, []int) {
return file_private_pbdemo_v1_demo_proto_rawDescGZIP(), []int{0}
}
func (x *Artist) GetName() string {
if x != nil {
return x.Name
}
return ""
}
func (x *Artist) GetDescription() string {
if x != nil {
return x.Description
}
return ""
}
func (x *Artist) GetGenre() Genre {
if x != nil {
return x.Genre
}
return Genre_GENRE_UNSPECIFIED
}
func (x *Artist) GetGroupMembers() int32 {
if x != nil {
return x.GroupMembers
}
return 0
}
type Album struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
YearOfRelease int32 `protobuf:"varint,2,opt,name=year_of_release,json=yearOfRelease,proto3" json:"year_of_release,omitempty"`
CriticallyAclaimed bool `protobuf:"varint,3,opt,name=critically_aclaimed,json=criticallyAclaimed,proto3" json:"critically_aclaimed,omitempty"`
Tracks []string `protobuf:"bytes,4,rep,name=tracks,proto3" json:"tracks,omitempty"`
}
func (x *Album) Reset() {
*x = Album{}
if protoimpl.UnsafeEnabled {
mi := &file_private_pbdemo_v1_demo_proto_msgTypes[1]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *Album) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*Album) ProtoMessage() {}
func (x *Album) ProtoReflect() protoreflect.Message {
mi := &file_private_pbdemo_v1_demo_proto_msgTypes[1]
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 Album.ProtoReflect.Descriptor instead.
func (*Album) Descriptor() ([]byte, []int) {
return file_private_pbdemo_v1_demo_proto_rawDescGZIP(), []int{1}
}
func (x *Album) GetName() string {
if x != nil {
return x.Name
}
return ""
}
func (x *Album) GetYearOfRelease() int32 {
if x != nil {
return x.YearOfRelease
}
return 0
}
func (x *Album) GetCriticallyAclaimed() bool {
if x != nil {
return x.CriticallyAclaimed
}
return false
}
func (x *Album) GetTracks() []string {
if x != nil {
return x.Tracks
}
return nil
}
var File_private_pbdemo_v1_demo_proto protoreflect.FileDescriptor
var file_private_pbdemo_v1_demo_proto_rawDesc = []byte{
0x0a, 0x1c, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x2f, 0x70, 0x62, 0x64, 0x65, 0x6d, 0x6f,
0x2f, 0x76, 0x31, 0x2f, 0x64, 0x65, 0x6d, 0x6f, 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, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x64, 0x65, 0x6d, 0x6f, 0x2e, 0x76,
0x31, 0x22, 0xa3, 0x01, 0x0a, 0x06, 0x41, 0x72, 0x74, 0x69, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04,
0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65,
0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18,
0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69,
0x6f, 0x6e, 0x12, 0x3e, 0x0a, 0x05, 0x67, 0x65, 0x6e, 0x72, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28,
0x0e, 0x32, 0x28, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f,
0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x64, 0x65,
0x6d, 0x6f, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x6e, 0x72, 0x65, 0x52, 0x05, 0x67, 0x65, 0x6e,
0x72, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x5f, 0x6d, 0x65, 0x6d, 0x62,
0x65, 0x72, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0c, 0x67, 0x72, 0x6f, 0x75, 0x70,
0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x22, 0x8c, 0x01, 0x0a, 0x05, 0x41, 0x6c, 0x62, 0x75,
0x6d, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52,
0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x26, 0x0a, 0x0f, 0x79, 0x65, 0x61, 0x72, 0x5f, 0x6f, 0x66,
0x5f, 0x72, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0d,
0x79, 0x65, 0x61, 0x72, 0x4f, 0x66, 0x52, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x12, 0x2f, 0x0a,
0x13, 0x63, 0x72, 0x69, 0x74, 0x69, 0x63, 0x61, 0x6c, 0x6c, 0x79, 0x5f, 0x61, 0x63, 0x6c, 0x61,
0x69, 0x6d, 0x65, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x12, 0x63, 0x72, 0x69, 0x74,
0x69, 0x63, 0x61, 0x6c, 0x6c, 0x79, 0x41, 0x63, 0x6c, 0x61, 0x69, 0x6d, 0x65, 0x64, 0x12, 0x16,
0x0a, 0x06, 0x74, 0x72, 0x61, 0x63, 0x6b, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x09, 0x52, 0x06,
0x74, 0x72, 0x61, 0x63, 0x6b, 0x73, 0x2a, 0xe9, 0x01, 0x0a, 0x05, 0x47, 0x65, 0x6e, 0x72, 0x65,
0x12, 0x15, 0x0a, 0x11, 0x47, 0x45, 0x4e, 0x52, 0x45, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43,
0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x0e, 0x0a, 0x0a, 0x47, 0x45, 0x4e, 0x52, 0x45,
0x5f, 0x4a, 0x41, 0x5a, 0x5a, 0x10, 0x01, 0x12, 0x0e, 0x0a, 0x0a, 0x47, 0x45, 0x4e, 0x52, 0x45,
0x5f, 0x46, 0x4f, 0x4c, 0x4b, 0x10, 0x02, 0x12, 0x0d, 0x0a, 0x09, 0x47, 0x45, 0x4e, 0x52, 0x45,
0x5f, 0x50, 0x4f, 0x50, 0x10, 0x03, 0x12, 0x0f, 0x0a, 0x0b, 0x47, 0x45, 0x4e, 0x52, 0x45, 0x5f,
0x4d, 0x45, 0x54, 0x41, 0x4c, 0x10, 0x04, 0x12, 0x0e, 0x0a, 0x0a, 0x47, 0x45, 0x4e, 0x52, 0x45,
0x5f, 0x50, 0x55, 0x4e, 0x4b, 0x10, 0x05, 0x12, 0x0f, 0x0a, 0x0b, 0x47, 0x45, 0x4e, 0x52, 0x45,
0x5f, 0x42, 0x4c, 0x55, 0x45, 0x53, 0x10, 0x06, 0x12, 0x11, 0x0a, 0x0d, 0x47, 0x45, 0x4e, 0x52,
0x45, 0x5f, 0x52, 0x5f, 0x41, 0x4e, 0x44, 0x5f, 0x42, 0x10, 0x07, 0x12, 0x11, 0x0a, 0x0d, 0x47,
0x45, 0x4e, 0x52, 0x45, 0x5f, 0x43, 0x4f, 0x55, 0x4e, 0x54, 0x52, 0x59, 0x10, 0x08, 0x12, 0x0f,
0x0a, 0x0b, 0x47, 0x45, 0x4e, 0x52, 0x45, 0x5f, 0x44, 0x49, 0x53, 0x43, 0x4f, 0x10, 0x09, 0x12,
0x0d, 0x0a, 0x09, 0x47, 0x45, 0x4e, 0x52, 0x45, 0x5f, 0x53, 0x4b, 0x41, 0x10, 0x0a, 0x12, 0x11,
0x0a, 0x0d, 0x47, 0x45, 0x4e, 0x52, 0x45, 0x5f, 0x48, 0x49, 0x50, 0x5f, 0x48, 0x4f, 0x50, 0x10,
0x0b, 0x12, 0x0f, 0x0a, 0x0b, 0x47, 0x45, 0x4e, 0x52, 0x45, 0x5f, 0x49, 0x4e, 0x44, 0x49, 0x45,
0x10, 0x0c, 0x42, 0x97, 0x02, 0x0a, 0x25, 0x63, 0x6f, 0x6d, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69,
0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65,
0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x64, 0x65, 0x6d, 0x6f, 0x2e, 0x76, 0x31, 0x42, 0x09, 0x44, 0x65,
0x6d, 0x6f, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x3a, 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, 0x2f, 0x70, 0x72, 0x69,
0x76, 0x61, 0x74, 0x65, 0x2f, 0x70, 0x62, 0x64, 0x65, 0x6d, 0x6f, 0x2f, 0x76, 0x31, 0x3b, 0x64,
0x65, 0x6d, 0x6f, 0x76, 0x31, 0xa2, 0x02, 0x04, 0x48, 0x43, 0x49, 0x44, 0xaa, 0x02, 0x21, 0x48,
0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x43, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e,
0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x44, 0x65, 0x6d, 0x6f, 0x2e, 0x56, 0x31,
0xca, 0x02, 0x21, 0x48, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x5c, 0x43, 0x6f, 0x6e,
0x73, 0x75, 0x6c, 0x5c, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x5c, 0x44, 0x65, 0x6d,
0x6f, 0x5c, 0x56, 0x31, 0xe2, 0x02, 0x2d, 0x48, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70,
0x5c, 0x43, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x5c, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c,
0x5c, 0x44, 0x65, 0x6d, 0x6f, 0x5c, 0x56, 0x31, 0x5c, 0x47, 0x50, 0x42, 0x4d, 0x65, 0x74, 0x61,
0x64, 0x61, 0x74, 0x61, 0xea, 0x02, 0x25, 0x48, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70,
0x3a, 0x3a, 0x43, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x3a, 0x3a, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e,
0x61, 0x6c, 0x3a, 0x3a, 0x44, 0x65, 0x6d, 0x6f, 0x3a, 0x3a, 0x56, 0x31, 0x62, 0x06, 0x70, 0x72,
0x6f, 0x74, 0x6f, 0x33,
}
var (
file_private_pbdemo_v1_demo_proto_rawDescOnce sync.Once
file_private_pbdemo_v1_demo_proto_rawDescData = file_private_pbdemo_v1_demo_proto_rawDesc
)
func file_private_pbdemo_v1_demo_proto_rawDescGZIP() []byte {
file_private_pbdemo_v1_demo_proto_rawDescOnce.Do(func() {
file_private_pbdemo_v1_demo_proto_rawDescData = protoimpl.X.CompressGZIP(file_private_pbdemo_v1_demo_proto_rawDescData)
})
return file_private_pbdemo_v1_demo_proto_rawDescData
}
var file_private_pbdemo_v1_demo_proto_enumTypes = make([]protoimpl.EnumInfo, 1)
var file_private_pbdemo_v1_demo_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
var file_private_pbdemo_v1_demo_proto_goTypes = []interface{}{
(Genre)(0), // 0: hashicorp.consul.internal.demo.v1.Genre
(*Artist)(nil), // 1: hashicorp.consul.internal.demo.v1.Artist
(*Album)(nil), // 2: hashicorp.consul.internal.demo.v1.Album
}
var file_private_pbdemo_v1_demo_proto_depIdxs = []int32{
0, // 0: hashicorp.consul.internal.demo.v1.Artist.genre:type_name -> hashicorp.consul.internal.demo.v1.Genre
1, // [1:1] is the sub-list for method output_type
1, // [1:1] is the sub-list for method input_type
1, // [1:1] is the sub-list for extension type_name
1, // [1:1] is the sub-list for extension extendee
0, // [0:1] is the sub-list for field type_name
}
func init() { file_private_pbdemo_v1_demo_proto_init() }
func file_private_pbdemo_v1_demo_proto_init() {
if File_private_pbdemo_v1_demo_proto != nil {
return
}
if !protoimpl.UnsafeEnabled {
file_private_pbdemo_v1_demo_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*Artist); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_private_pbdemo_v1_demo_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*Album); 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_private_pbdemo_v1_demo_proto_rawDesc,
NumEnums: 1,
NumMessages: 2,
NumExtensions: 0,
NumServices: 0,
},
GoTypes: file_private_pbdemo_v1_demo_proto_goTypes,
DependencyIndexes: file_private_pbdemo_v1_demo_proto_depIdxs,
EnumInfos: file_private_pbdemo_v1_demo_proto_enumTypes,
MessageInfos: file_private_pbdemo_v1_demo_proto_msgTypes,
}.Build()
File_private_pbdemo_v1_demo_proto = out.File
file_private_pbdemo_v1_demo_proto_rawDesc = nil
file_private_pbdemo_v1_demo_proto_goTypes = nil
file_private_pbdemo_v1_demo_proto_depIdxs = nil
}

View File

@ -0,0 +1,35 @@
syntax = "proto3";
// This package contains fake resource types, which are useful for working on
// Consul's generic storage APIs.
package hashicorp.consul.internal.demo.v1;
message Artist {
string name = 1;
string description = 2;
Genre genre = 3;
int32 group_members = 4;
}
enum Genre {
GENRE_UNSPECIFIED = 0;
GENRE_JAZZ = 1;
GENRE_FOLK = 2;
GENRE_POP = 3;
GENRE_METAL = 4;
GENRE_PUNK = 5;
GENRE_BLUES = 6;
GENRE_R_AND_B = 7;
GENRE_COUNTRY = 8;
GENRE_DISCO = 9;
GENRE_SKA = 10;
GENRE_HIP_HOP = 11;
GENRE_INDIE = 12;
}
message Album {
string name = 1;
int32 year_of_release = 2;
bool critically_aclaimed = 3;
repeated string tracks = 4;
}

View File

@ -0,0 +1,28 @@
// Code generated by protoc-gen-go-binary. DO NOT EDIT.
// source: private/pbdemo/v2/demo.proto
package demov2
import (
"google.golang.org/protobuf/proto"
)
// MarshalBinary implements encoding.BinaryMarshaler
func (msg *Artist) MarshalBinary() ([]byte, error) {
return proto.Marshal(msg)
}
// UnmarshalBinary implements encoding.BinaryUnmarshaler
func (msg *Artist) UnmarshalBinary(b []byte) error {
return proto.Unmarshal(b, msg)
}
// MarshalBinary implements encoding.BinaryMarshaler
func (msg *Album) MarshalBinary() ([]byte, error) {
return proto.Marshal(msg)
}
// UnmarshalBinary implements encoding.BinaryUnmarshaler
func (msg *Album) UnmarshalBinary(b []byte) error {
return proto.Unmarshal(b, msg)
}

View File

@ -0,0 +1,387 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.28.1
// protoc (unknown)
// source: private/pbdemo/v2/demo.proto
// This package contains fake resource types, which are useful for working on
// Consul's generic storage APIs.
package demov2
import (
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)
)
type Genre int32
const (
Genre_GENRE_UNSPECIFIED Genre = 0
Genre_GENRE_JAZZ Genre = 1
Genre_GENRE_FOLK Genre = 2
Genre_GENRE_POP Genre = 3
Genre_GENRE_METAL Genre = 4
Genre_GENRE_PUNK Genre = 5
Genre_GENRE_BLUES Genre = 6
Genre_GENRE_R_AND_B Genre = 7
Genre_GENRE_COUNTRY Genre = 8
Genre_GENRE_DISCO Genre = 9
Genre_GENRE_SKA Genre = 10
Genre_GENRE_HIP_HOP Genre = 11
Genre_GENRE_INDIE Genre = 12
)
// Enum value maps for Genre.
var (
Genre_name = map[int32]string{
0: "GENRE_UNSPECIFIED",
1: "GENRE_JAZZ",
2: "GENRE_FOLK",
3: "GENRE_POP",
4: "GENRE_METAL",
5: "GENRE_PUNK",
6: "GENRE_BLUES",
7: "GENRE_R_AND_B",
8: "GENRE_COUNTRY",
9: "GENRE_DISCO",
10: "GENRE_SKA",
11: "GENRE_HIP_HOP",
12: "GENRE_INDIE",
}
Genre_value = map[string]int32{
"GENRE_UNSPECIFIED": 0,
"GENRE_JAZZ": 1,
"GENRE_FOLK": 2,
"GENRE_POP": 3,
"GENRE_METAL": 4,
"GENRE_PUNK": 5,
"GENRE_BLUES": 6,
"GENRE_R_AND_B": 7,
"GENRE_COUNTRY": 8,
"GENRE_DISCO": 9,
"GENRE_SKA": 10,
"GENRE_HIP_HOP": 11,
"GENRE_INDIE": 12,
}
)
func (x Genre) Enum() *Genre {
p := new(Genre)
*p = x
return p
}
func (x Genre) String() string {
return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
}
func (Genre) Descriptor() protoreflect.EnumDescriptor {
return file_private_pbdemo_v2_demo_proto_enumTypes[0].Descriptor()
}
func (Genre) Type() protoreflect.EnumType {
return &file_private_pbdemo_v2_demo_proto_enumTypes[0]
}
func (x Genre) Number() protoreflect.EnumNumber {
return protoreflect.EnumNumber(x)
}
// Deprecated: Use Genre.Descriptor instead.
func (Genre) EnumDescriptor() ([]byte, []int) {
return file_private_pbdemo_v2_demo_proto_rawDescGZIP(), []int{0}
}
type Artist struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
Genre Genre `protobuf:"varint,2,opt,name=genre,proto3,enum=hashicorp.consul.internal.demo.v2.Genre" json:"genre,omitempty"`
GroupMembers map[string]string `protobuf:"bytes,3,rep,name=group_members,json=groupMembers,proto3" json:"group_members,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
}
func (x *Artist) Reset() {
*x = Artist{}
if protoimpl.UnsafeEnabled {
mi := &file_private_pbdemo_v2_demo_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *Artist) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*Artist) ProtoMessage() {}
func (x *Artist) ProtoReflect() protoreflect.Message {
mi := &file_private_pbdemo_v2_demo_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 Artist.ProtoReflect.Descriptor instead.
func (*Artist) Descriptor() ([]byte, []int) {
return file_private_pbdemo_v2_demo_proto_rawDescGZIP(), []int{0}
}
func (x *Artist) GetName() string {
if x != nil {
return x.Name
}
return ""
}
func (x *Artist) GetGenre() Genre {
if x != nil {
return x.Genre
}
return Genre_GENRE_UNSPECIFIED
}
func (x *Artist) GetGroupMembers() map[string]string {
if x != nil {
return x.GroupMembers
}
return nil
}
type Album struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Title string `protobuf:"bytes,1,opt,name=title,proto3" json:"title,omitempty"`
YearOfRelease int32 `protobuf:"varint,2,opt,name=year_of_release,json=yearOfRelease,proto3" json:"year_of_release,omitempty"`
CriticallyAclaimed bool `protobuf:"varint,3,opt,name=critically_aclaimed,json=criticallyAclaimed,proto3" json:"critically_aclaimed,omitempty"`
Tracks []string `protobuf:"bytes,4,rep,name=tracks,proto3" json:"tracks,omitempty"`
}
func (x *Album) Reset() {
*x = Album{}
if protoimpl.UnsafeEnabled {
mi := &file_private_pbdemo_v2_demo_proto_msgTypes[1]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *Album) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*Album) ProtoMessage() {}
func (x *Album) ProtoReflect() protoreflect.Message {
mi := &file_private_pbdemo_v2_demo_proto_msgTypes[1]
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 Album.ProtoReflect.Descriptor instead.
func (*Album) Descriptor() ([]byte, []int) {
return file_private_pbdemo_v2_demo_proto_rawDescGZIP(), []int{1}
}
func (x *Album) GetTitle() string {
if x != nil {
return x.Title
}
return ""
}
func (x *Album) GetYearOfRelease() int32 {
if x != nil {
return x.YearOfRelease
}
return 0
}
func (x *Album) GetCriticallyAclaimed() bool {
if x != nil {
return x.CriticallyAclaimed
}
return false
}
func (x *Album) GetTracks() []string {
if x != nil {
return x.Tracks
}
return nil
}
var File_private_pbdemo_v2_demo_proto protoreflect.FileDescriptor
var file_private_pbdemo_v2_demo_proto_rawDesc = []byte{
0x0a, 0x1c, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x2f, 0x70, 0x62, 0x64, 0x65, 0x6d, 0x6f,
0x2f, 0x76, 0x32, 0x2f, 0x64, 0x65, 0x6d, 0x6f, 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, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x64, 0x65, 0x6d, 0x6f, 0x2e, 0x76,
0x32, 0x22, 0xff, 0x01, 0x0a, 0x06, 0x41, 0x72, 0x74, 0x69, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04,
0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65,
0x12, 0x3e, 0x0a, 0x05, 0x67, 0x65, 0x6e, 0x72, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32,
0x28, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73,
0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x64, 0x65, 0x6d, 0x6f,
0x2e, 0x76, 0x32, 0x2e, 0x47, 0x65, 0x6e, 0x72, 0x65, 0x52, 0x05, 0x67, 0x65, 0x6e, 0x72, 0x65,
0x12, 0x60, 0x0a, 0x0d, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x5f, 0x6d, 0x65, 0x6d, 0x62, 0x65, 0x72,
0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x3b, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63,
0x6f, 0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72,
0x6e, 0x61, 0x6c, 0x2e, 0x64, 0x65, 0x6d, 0x6f, 0x2e, 0x76, 0x32, 0x2e, 0x41, 0x72, 0x74, 0x69,
0x73, 0x74, 0x2e, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x45,
0x6e, 0x74, 0x72, 0x79, 0x52, 0x0c, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x4d, 0x65, 0x6d, 0x62, 0x65,
0x72, 0x73, 0x1a, 0x3f, 0x0a, 0x11, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x4d, 0x65, 0x6d, 0x62, 0x65,
0x72, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01,
0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c,
0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a,
0x02, 0x38, 0x01, 0x22, 0x8e, 0x01, 0x0a, 0x05, 0x41, 0x6c, 0x62, 0x75, 0x6d, 0x12, 0x14, 0x0a,
0x05, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x74, 0x69,
0x74, 0x6c, 0x65, 0x12, 0x26, 0x0a, 0x0f, 0x79, 0x65, 0x61, 0x72, 0x5f, 0x6f, 0x66, 0x5f, 0x72,
0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0d, 0x79, 0x65,
0x61, 0x72, 0x4f, 0x66, 0x52, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x12, 0x2f, 0x0a, 0x13, 0x63,
0x72, 0x69, 0x74, 0x69, 0x63, 0x61, 0x6c, 0x6c, 0x79, 0x5f, 0x61, 0x63, 0x6c, 0x61, 0x69, 0x6d,
0x65, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x12, 0x63, 0x72, 0x69, 0x74, 0x69, 0x63,
0x61, 0x6c, 0x6c, 0x79, 0x41, 0x63, 0x6c, 0x61, 0x69, 0x6d, 0x65, 0x64, 0x12, 0x16, 0x0a, 0x06,
0x74, 0x72, 0x61, 0x63, 0x6b, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x09, 0x52, 0x06, 0x74, 0x72,
0x61, 0x63, 0x6b, 0x73, 0x2a, 0xe9, 0x01, 0x0a, 0x05, 0x47, 0x65, 0x6e, 0x72, 0x65, 0x12, 0x15,
0x0a, 0x11, 0x47, 0x45, 0x4e, 0x52, 0x45, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46,
0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x0e, 0x0a, 0x0a, 0x47, 0x45, 0x4e, 0x52, 0x45, 0x5f, 0x4a,
0x41, 0x5a, 0x5a, 0x10, 0x01, 0x12, 0x0e, 0x0a, 0x0a, 0x47, 0x45, 0x4e, 0x52, 0x45, 0x5f, 0x46,
0x4f, 0x4c, 0x4b, 0x10, 0x02, 0x12, 0x0d, 0x0a, 0x09, 0x47, 0x45, 0x4e, 0x52, 0x45, 0x5f, 0x50,
0x4f, 0x50, 0x10, 0x03, 0x12, 0x0f, 0x0a, 0x0b, 0x47, 0x45, 0x4e, 0x52, 0x45, 0x5f, 0x4d, 0x45,
0x54, 0x41, 0x4c, 0x10, 0x04, 0x12, 0x0e, 0x0a, 0x0a, 0x47, 0x45, 0x4e, 0x52, 0x45, 0x5f, 0x50,
0x55, 0x4e, 0x4b, 0x10, 0x05, 0x12, 0x0f, 0x0a, 0x0b, 0x47, 0x45, 0x4e, 0x52, 0x45, 0x5f, 0x42,
0x4c, 0x55, 0x45, 0x53, 0x10, 0x06, 0x12, 0x11, 0x0a, 0x0d, 0x47, 0x45, 0x4e, 0x52, 0x45, 0x5f,
0x52, 0x5f, 0x41, 0x4e, 0x44, 0x5f, 0x42, 0x10, 0x07, 0x12, 0x11, 0x0a, 0x0d, 0x47, 0x45, 0x4e,
0x52, 0x45, 0x5f, 0x43, 0x4f, 0x55, 0x4e, 0x54, 0x52, 0x59, 0x10, 0x08, 0x12, 0x0f, 0x0a, 0x0b,
0x47, 0x45, 0x4e, 0x52, 0x45, 0x5f, 0x44, 0x49, 0x53, 0x43, 0x4f, 0x10, 0x09, 0x12, 0x0d, 0x0a,
0x09, 0x47, 0x45, 0x4e, 0x52, 0x45, 0x5f, 0x53, 0x4b, 0x41, 0x10, 0x0a, 0x12, 0x11, 0x0a, 0x0d,
0x47, 0x45, 0x4e, 0x52, 0x45, 0x5f, 0x48, 0x49, 0x50, 0x5f, 0x48, 0x4f, 0x50, 0x10, 0x0b, 0x12,
0x0f, 0x0a, 0x0b, 0x47, 0x45, 0x4e, 0x52, 0x45, 0x5f, 0x49, 0x4e, 0x44, 0x49, 0x45, 0x10, 0x0c,
0x42, 0x97, 0x02, 0x0a, 0x25, 0x63, 0x6f, 0x6d, 0x2e, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f,
0x72, 0x70, 0x2e, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e,
0x61, 0x6c, 0x2e, 0x64, 0x65, 0x6d, 0x6f, 0x2e, 0x76, 0x32, 0x42, 0x09, 0x44, 0x65, 0x6d, 0x6f,
0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x3a, 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, 0x2f, 0x70, 0x72, 0x69, 0x76, 0x61,
0x74, 0x65, 0x2f, 0x70, 0x62, 0x64, 0x65, 0x6d, 0x6f, 0x2f, 0x76, 0x32, 0x3b, 0x64, 0x65, 0x6d,
0x6f, 0x76, 0x32, 0xa2, 0x02, 0x04, 0x48, 0x43, 0x49, 0x44, 0xaa, 0x02, 0x21, 0x48, 0x61, 0x73,
0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x43, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x2e, 0x49, 0x6e,
0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x44, 0x65, 0x6d, 0x6f, 0x2e, 0x56, 0x32, 0xca, 0x02,
0x21, 0x48, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x5c, 0x43, 0x6f, 0x6e, 0x73, 0x75,
0x6c, 0x5c, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x5c, 0x44, 0x65, 0x6d, 0x6f, 0x5c,
0x56, 0x32, 0xe2, 0x02, 0x2d, 0x48, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x5c, 0x43,
0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x5c, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x5c, 0x44,
0x65, 0x6d, 0x6f, 0x5c, 0x56, 0x32, 0x5c, 0x47, 0x50, 0x42, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61,
0x74, 0x61, 0xea, 0x02, 0x25, 0x48, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x3a, 0x3a,
0x43, 0x6f, 0x6e, 0x73, 0x75, 0x6c, 0x3a, 0x3a, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c,
0x3a, 0x3a, 0x44, 0x65, 0x6d, 0x6f, 0x3a, 0x3a, 0x56, 0x32, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74,
0x6f, 0x33,
}
var (
file_private_pbdemo_v2_demo_proto_rawDescOnce sync.Once
file_private_pbdemo_v2_demo_proto_rawDescData = file_private_pbdemo_v2_demo_proto_rawDesc
)
func file_private_pbdemo_v2_demo_proto_rawDescGZIP() []byte {
file_private_pbdemo_v2_demo_proto_rawDescOnce.Do(func() {
file_private_pbdemo_v2_demo_proto_rawDescData = protoimpl.X.CompressGZIP(file_private_pbdemo_v2_demo_proto_rawDescData)
})
return file_private_pbdemo_v2_demo_proto_rawDescData
}
var file_private_pbdemo_v2_demo_proto_enumTypes = make([]protoimpl.EnumInfo, 1)
var file_private_pbdemo_v2_demo_proto_msgTypes = make([]protoimpl.MessageInfo, 3)
var file_private_pbdemo_v2_demo_proto_goTypes = []interface{}{
(Genre)(0), // 0: hashicorp.consul.internal.demo.v2.Genre
(*Artist)(nil), // 1: hashicorp.consul.internal.demo.v2.Artist
(*Album)(nil), // 2: hashicorp.consul.internal.demo.v2.Album
nil, // 3: hashicorp.consul.internal.demo.v2.Artist.GroupMembersEntry
}
var file_private_pbdemo_v2_demo_proto_depIdxs = []int32{
0, // 0: hashicorp.consul.internal.demo.v2.Artist.genre:type_name -> hashicorp.consul.internal.demo.v2.Genre
3, // 1: hashicorp.consul.internal.demo.v2.Artist.group_members:type_name -> hashicorp.consul.internal.demo.v2.Artist.GroupMembersEntry
2, // [2:2] is the sub-list for method output_type
2, // [2:2] is the sub-list for method input_type
2, // [2:2] is the sub-list for extension type_name
2, // [2:2] is the sub-list for extension extendee
0, // [0:2] is the sub-list for field type_name
}
func init() { file_private_pbdemo_v2_demo_proto_init() }
func file_private_pbdemo_v2_demo_proto_init() {
if File_private_pbdemo_v2_demo_proto != nil {
return
}
if !protoimpl.UnsafeEnabled {
file_private_pbdemo_v2_demo_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*Artist); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_private_pbdemo_v2_demo_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*Album); 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_private_pbdemo_v2_demo_proto_rawDesc,
NumEnums: 1,
NumMessages: 3,
NumExtensions: 0,
NumServices: 0,
},
GoTypes: file_private_pbdemo_v2_demo_proto_goTypes,
DependencyIndexes: file_private_pbdemo_v2_demo_proto_depIdxs,
EnumInfos: file_private_pbdemo_v2_demo_proto_enumTypes,
MessageInfos: file_private_pbdemo_v2_demo_proto_msgTypes,
}.Build()
File_private_pbdemo_v2_demo_proto = out.File
file_private_pbdemo_v2_demo_proto_rawDesc = nil
file_private_pbdemo_v2_demo_proto_goTypes = nil
file_private_pbdemo_v2_demo_proto_depIdxs = nil
}

View File

@ -0,0 +1,34 @@
syntax = "proto3";
// This package contains fake resource types, which are useful for working on
// Consul's generic storage APIs.
package hashicorp.consul.internal.demo.v2;
message Artist {
string name = 1;
Genre genre = 2;
map<string, string> group_members = 3;
}
enum Genre {
GENRE_UNSPECIFIED = 0;
GENRE_JAZZ = 1;
GENRE_FOLK = 2;
GENRE_POP = 3;
GENRE_METAL = 4;
GENRE_PUNK = 5;
GENRE_BLUES = 6;
GENRE_R_AND_B = 7;
GENRE_COUNTRY = 8;
GENRE_DISCO = 9;
GENRE_SKA = 10;
GENRE_HIP_HOP = 11;
GENRE_INDIE = 12;
}
message Album {
string title = 1;
int32 year_of_release = 2;
bool critically_aclaimed = 3;
repeated string tracks = 4;
}