From 9ef1741327e43f173cbd6b9ab7baeec169db8bab Mon Sep 17 00:00:00 2001 From: "Ahmad N. F" Date: Wed, 10 Jul 2024 10:45:27 +0700 Subject: [PATCH] feat: resource changelog tracking & API (#252) * feat: add resource changelog API service & repo * feat: unit test for service * feat: move to a separate service * feat: add handler & proto * feat: reorganize functions & track resource updates * feat: fix metric counter & fix job changelog query * feat: skip when there's no changes * feat: update queries structure * feat: fix query & handler function name * fix: handler naming * feat: update on code review * feat: remove sorting from get query * fix: lint * feat: update proton commit --- Makefile | 2 +- core/job/service/changelog_service.go | 12 +- core/job/service/job_service.go | 33 - core/resource/changelog.go | 22 + core/resource/handler/v1beta1/resource.go | 55 +- .../resource/handler/v1beta1/resource_test.go | 224 ++++-- core/resource/service/changelog_service.go | 48 ++ .../service/changelog_service_test.go | 130 ++++ internal/store/postgres/job/job_repository.go | 4 +- internal/store/postgres/resource/adapter.go | 47 ++ .../store/postgres/resource/repository.go | 124 ++++ .../postgres/resource/repository_test.go | 49 ++ .../optimus/core/v1beta1/resource.pb.go | 647 +++++++++++++----- .../optimus/core/v1beta1/resource.pb.gw.go | 119 ++++ .../core/v1beta1/resource.swagger.json | 76 ++ .../optimus/core/v1beta1/resource_grpc.pb.go | 38 + server/optimus.go | 3 +- 17 files changed, 1373 insertions(+), 260 deletions(-) create mode 100644 core/resource/changelog.go create mode 100644 core/resource/service/changelog_service.go create mode 100644 core/resource/service/changelog_service_test.go create mode 100644 internal/store/postgres/resource/adapter.go diff --git a/Makefile b/Makefile index 6805ac438f..eb786bdfcb 100644 --- a/Makefile +++ b/Makefile @@ -5,7 +5,7 @@ NAME = "github.com/goto/optimus" LAST_COMMIT := $(shell git rev-parse --short HEAD) LAST_TAG := "$(shell git rev-list --tags --max-count=1)" OPMS_VERSION := "$(shell git describe --tags ${LAST_TAG})-next" -PROTON_COMMIT := "0278f0402ee9d1b5f392e03c7f45160879924d78" +PROTON_COMMIT := "01ffd0ca223431ae24a8de44827de0d96afef9b2" .PHONY: build test test-ci generate-proto unit-test-ci integration-test vet coverage clean install lint diff --git a/core/job/service/changelog_service.go b/core/job/service/changelog_service.go index b301bdfbd0..7b8d968080 100644 --- a/core/job/service/changelog_service.go +++ b/core/job/service/changelog_service.go @@ -3,10 +3,18 @@ package service import ( "context" + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/promauto" + "github.com/goto/optimus/core/job" "github.com/goto/optimus/core/tenant" ) +var getChangelogFailures = promauto.NewCounterVec(prometheus.CounterOpts{ + Name: "get_changelog_errors", + Help: "errors occurred in get changelog", +}, []string{"project", "job", "error"}) + type ChangeLogService struct { jobRepo JobRepository } @@ -20,10 +28,6 @@ func (cl *ChangeLogService) GetChangelog(ctx context.Context, projectName tenant err.Error(), ).Inc() } - getChangelogFeatureAdoption.WithLabelValues( - projectName.String(), - jobName.String(), - ).Inc() return changelog, err } diff --git a/core/job/service/job_service.go b/core/job/service/job_service.go index cf150ee527..64c49ebe5a 100644 --- a/core/job/service/job_service.go +++ b/core/job/service/job_service.go @@ -9,8 +9,6 @@ import ( "github.com/goto/salt/log" "github.com/kushsharma/parallel" - "github.com/prometheus/client_golang/prometheus" - "github.com/prometheus/client_golang/prometheus/promauto" "github.com/goto/optimus/core/event" "github.com/goto/optimus/core/event/moderator" @@ -38,20 +36,6 @@ const ( projectConfigPrefix = "GLOBAL__" ) -var ( - - // right now this is done to capture the feature adoption - getChangelogFeatureAdoption = promauto.NewCounterVec(prometheus.CounterOpts{ - Name: "get_changelog_total", - Help: "number of requests received for viewing changelog", - }, []string{"project", "job"}) - - getChangelogFailures = promauto.NewCounterVec(prometheus.CounterOpts{ - Name: "get_changelog_errors", - Help: "errors occurred in get changelog", - }, []string{"project", "job", "error"}) -) - type JobService struct { jobRepo JobRepository upstreamRepo UpstreamRepository @@ -453,23 +437,6 @@ func (j *JobService) ChangeNamespace(ctx context.Context, jobTenant, jobNewTenan return nil } -func (j *JobService) GetChangelog(ctx context.Context, projectName tenant.ProjectName, jobName job.Name) ([]*job.ChangeLog, error) { - changelog, err := j.jobRepo.GetChangelog(ctx, projectName, jobName) - if err != nil { - getChangelogFailures.WithLabelValues( - projectName.String(), - jobName.String(), - err.Error(), - ).Inc() - } - getChangelogFeatureAdoption.WithLabelValues( - projectName.String(), - jobName.String(), - ).Inc() - - return changelog, err -} - func (j *JobService) Get(ctx context.Context, jobTenant tenant.Tenant, jobName job.Name) (*job.Job, error) { jobs, err := j.GetByFilter(ctx, filter.WithString(filter.ProjectName, jobTenant.ProjectName().String()), diff --git a/core/resource/changelog.go b/core/resource/changelog.go new file mode 100644 index 0000000000..aa2e12be55 --- /dev/null +++ b/core/resource/changelog.go @@ -0,0 +1,22 @@ +package resource + +import "time" + +const ( + EntityResourceChangelog = "resource_changelog" + + ChangelogChangeTypeCreate = "create" + ChangelogChangeTypeUpdate = "update" + ChangelogChangeTypeDelete = "delete" +) + +type Change struct { + Property string + Diff string +} + +type ChangeLog struct { + Change []Change + Type string + Time time.Time +} diff --git a/core/resource/handler/v1beta1/resource.go b/core/resource/handler/v1beta1/resource.go index 1d8c8d00f6..c90b1c2579 100644 --- a/core/resource/handler/v1beta1/resource.go +++ b/core/resource/handler/v1beta1/resource.go @@ -35,9 +35,14 @@ type ResourceService interface { SyncResources(ctx context.Context, tnnt tenant.Tenant, store resource.Store, names []string) (*resource.SyncResponse, error) } +type ResourceChangeLogService interface { + GetChangelogs(ctx context.Context, projectName tenant.ProjectName, resourceName resource.Name) ([]*resource.ChangeLog, error) +} + type ResourceHandler struct { - l log.Logger - service ResourceService + l log.Logger + service ResourceService + changelogService ResourceChangeLogService pb.UnimplementedResourceServiceServer } @@ -424,6 +429,29 @@ func (rh ResourceHandler) ApplyResources(ctx context.Context, req *pb.ApplyResou return &pb.ApplyResourcesResponse{Statuses: respStatuses}, nil } +func (rh ResourceHandler) GetResourceChangelogs(ctx context.Context, req *pb.GetResourceChangelogsRequest) (*pb.GetResourceChangelogsResponse, error) { + projectName, err := tenant.ProjectNameFrom(req.GetProjectName()) + if err != nil { + return nil, errors.GRPCErr(err, "invalid project name") + } + + resourceName, err := resource.NameFrom(req.GetResourceName()) + if err != nil { + return nil, errors.GRPCErr(err, "invalid resource name") + } + + changelogs, err := rh.changelogService.GetChangelogs(ctx, projectName, resourceName) + if err != nil { + return nil, errors.GRPCErr(err, fmt.Sprintf("unable to get changelog for resource %s", resourceName.String())) + } + + responseChangelogs := make([]*pb.ResourceChangelog, len(changelogs)) + for i, changelog := range changelogs { + responseChangelogs[i] = toChangelogProto(changelog) + } + return &pb.GetResourceChangelogsResponse{History: responseChangelogs}, nil +} + func writeError(logWriter writer.LogWriter, err error) { if err == nil { return @@ -523,9 +551,26 @@ func raiseResourceDatastoreEventMetric(jobTenant tenant.Tenant, datastoreName, r }).Inc() } -func NewResourceHandler(l log.Logger, resourceService ResourceService) *ResourceHandler { +func toChangelogProto(cl *resource.ChangeLog) *pb.ResourceChangelog { + pbChange := &pb.ResourceChangelog{ + EventType: cl.Type, + Timestamp: cl.Time.String(), + } + + pbChange.Change = make([]*pb.ResourceChange, len(cl.Change)) + for i, change := range cl.Change { + pbChange.Change[i] = &pb.ResourceChange{ + AttributeName: change.Property, + Diff: change.Diff, + } + } + return pbChange +} + +func NewResourceHandler(l log.Logger, resourceService ResourceService, changelogService ResourceChangeLogService) *ResourceHandler { return &ResourceHandler{ - l: l, - service: resourceService, + l: l, + service: resourceService, + changelogService: changelogService, } } diff --git a/core/resource/handler/v1beta1/resource_test.go b/core/resource/handler/v1beta1/resource_test.go index 15a361875d..65ae160e33 100644 --- a/core/resource/handler/v1beta1/resource_test.go +++ b/core/resource/handler/v1beta1/resource_test.go @@ -5,6 +5,7 @@ import ( "errors" "io" "testing" + "time" "github.com/goto/salt/log" "github.com/stretchr/testify/assert" @@ -27,7 +28,7 @@ func TestResourceHandler(t *testing.T) { t.Run("DeployResourceSpecification", func(t *testing.T) { t.Run("returns error when client sends error", func(t *testing.T) { service := new(resourceService) - handler := v1beta1.NewResourceHandler(logger, service) + handler := v1beta1.NewResourceHandler(logger, service, nil) stream := new(resourceStreamMock) stream.On("Context").Return(ctx) @@ -40,7 +41,7 @@ func TestResourceHandler(t *testing.T) { }) t.Run("returns error when tenant is invalid", func(t *testing.T) { service := new(resourceService) - handler := v1beta1.NewResourceHandler(logger, service) + handler := v1beta1.NewResourceHandler(logger, service, nil) req := &pb.DeployResourceSpecificationRequest{ ProjectName: "", @@ -64,7 +65,7 @@ func TestResourceHandler(t *testing.T) { }) t.Run("returns error when store is invalid", func(t *testing.T) { service := new(resourceService) - handler := v1beta1.NewResourceHandler(logger, service) + handler := v1beta1.NewResourceHandler(logger, service, nil) req := &pb.DeployResourceSpecificationRequest{ ProjectName: "proj", @@ -91,7 +92,7 @@ func TestResourceHandler(t *testing.T) { service.On("Deploy", ctx, mock.Anything, resource.Bigquery, mock.Anything, mock.Anything).Return(nil) defer service.AssertExpectations(t) - handler := v1beta1.NewResourceHandler(logger, service) + handler := v1beta1.NewResourceHandler(logger, service, nil) res1 := pb.ResourceSpecification{ Version: 1, @@ -129,7 +130,7 @@ func TestResourceHandler(t *testing.T) { Return(errors.New("error in batch")) defer service.AssertExpectations(t) - handler := v1beta1.NewResourceHandler(logger, service) + handler := v1beta1.NewResourceHandler(logger, service, nil) spec, _ := structpb.NewStruct(map[string]any{"description": "spec"}) res1 := pb.ResourceSpecification{ @@ -167,7 +168,7 @@ func TestResourceHandler(t *testing.T) { service.On("Deploy", mock.Anything, tnnt, resource.Bigquery, mock.Anything, mock.Anything).Return(nil) defer service.AssertExpectations(t) - handler := v1beta1.NewResourceHandler(logger, service) + handler := v1beta1.NewResourceHandler(logger, service, nil) spec, _ := structpb.NewStruct(map[string]any{"description": "spec"}) res1 := pb.ResourceSpecification{ @@ -202,7 +203,7 @@ func TestResourceHandler(t *testing.T) { t.Run("ListResourceSpecification", func(t *testing.T) { t.Run("returns error when store is invalid", func(t *testing.T) { service := new(resourceService) - handler := v1beta1.NewResourceHandler(logger, service) + handler := v1beta1.NewResourceHandler(logger, service, nil) req := &pb.ListResourceSpecificationRequest{ ProjectName: "proj", @@ -216,7 +217,7 @@ func TestResourceHandler(t *testing.T) { }) t.Run("returns error when tenant is invalid", func(t *testing.T) { service := new(resourceService) - handler := v1beta1.NewResourceHandler(logger, service) + handler := v1beta1.NewResourceHandler(logger, service, nil) req := &pb.ListResourceSpecificationRequest{ ProjectName: "", @@ -235,7 +236,7 @@ func TestResourceHandler(t *testing.T) { Return(nil, errors.New("error in getAll")) defer service.AssertExpectations(t) - handler := v1beta1.NewResourceHandler(logger, service) + handler := v1beta1.NewResourceHandler(logger, service, nil) req := &pb.ListResourceSpecificationRequest{ ProjectName: "proj", @@ -253,7 +254,7 @@ func TestResourceHandler(t *testing.T) { Return([]*resource.Resource{{}}, nil) defer service.AssertExpectations(t) - handler := v1beta1.NewResourceHandler(logger, service) + handler := v1beta1.NewResourceHandler(logger, service, nil) req := &pb.ListResourceSpecificationRequest{ ProjectName: "proj", @@ -277,7 +278,7 @@ func TestResourceHandler(t *testing.T) { Return([]*resource.Resource{dbRes}, nil) defer service.AssertExpectations(t) - handler := v1beta1.NewResourceHandler(logger, service) + handler := v1beta1.NewResourceHandler(logger, service, nil) req := &pb.ListResourceSpecificationRequest{ ProjectName: "proj", @@ -295,7 +296,7 @@ func TestResourceHandler(t *testing.T) { t.Run("CreateResource", func(t *testing.T) { t.Run("returns error when tenant is invalid", func(t *testing.T) { service := new(resourceService) - handler := v1beta1.NewResourceHandler(logger, service) + handler := v1beta1.NewResourceHandler(logger, service, nil) createReq := &pb.CreateResourceRequest{ ProjectName: "", @@ -311,7 +312,7 @@ func TestResourceHandler(t *testing.T) { }) t.Run("returns error when store is invalid", func(t *testing.T) { service := new(resourceService) - handler := v1beta1.NewResourceHandler(logger, service) + handler := v1beta1.NewResourceHandler(logger, service, nil) createReq := &pb.CreateResourceRequest{ ProjectName: "proj", @@ -327,7 +328,7 @@ func TestResourceHandler(t *testing.T) { }) t.Run("returns error when spec is invalid", func(t *testing.T) { service := new(resourceService) - handler := v1beta1.NewResourceHandler(logger, service) + handler := v1beta1.NewResourceHandler(logger, service, nil) createReq := &pb.CreateResourceRequest{ ProjectName: "proj", @@ -347,7 +348,7 @@ func TestResourceHandler(t *testing.T) { }) t.Run("returns error when resource is nil", func(t *testing.T) { service := new(resourceService) - handler := v1beta1.NewResourceHandler(logger, service) + handler := v1beta1.NewResourceHandler(logger, service, nil) createReq := &pb.CreateResourceRequest{ ProjectName: "proj", @@ -363,7 +364,7 @@ func TestResourceHandler(t *testing.T) { }) t.Run("returns error when kind is empty", func(t *testing.T) { service := new(resourceService) - handler := v1beta1.NewResourceHandler(logger, service) + handler := v1beta1.NewResourceHandler(logger, service, nil) spec, _ := structpb.NewStruct(map[string]interface{}{"a": "b"}) createReq := &pb.CreateResourceRequest{ @@ -384,7 +385,7 @@ func TestResourceHandler(t *testing.T) { }) t.Run("returns error when name is invalid", func(t *testing.T) { service := new(resourceService) - handler := v1beta1.NewResourceHandler(logger, service) + handler := v1beta1.NewResourceHandler(logger, service, nil) spec, _ := structpb.NewStruct(map[string]interface{}{"a": "b"}) createReq := &pb.CreateResourceRequest{ @@ -409,7 +410,7 @@ func TestResourceHandler(t *testing.T) { service.On("Create", ctx, mock.Anything).Return(errors.New("validation failure")) defer service.AssertExpectations(t) - handler := v1beta1.NewResourceHandler(logger, service) + handler := v1beta1.NewResourceHandler(logger, service, nil) spec, _ := structpb.NewStruct(map[string]interface{}{"a": "b"}) createReq := &pb.CreateResourceRequest{ @@ -434,7 +435,7 @@ func TestResourceHandler(t *testing.T) { service.On("Create", ctx, mock.Anything).Return(nil) defer service.AssertExpectations(t) - handler := v1beta1.NewResourceHandler(logger, service) + handler := v1beta1.NewResourceHandler(logger, service, nil) spec, _ := structpb.NewStruct(map[string]interface{}{"description": "test"}) createReq := &pb.CreateResourceRequest{ @@ -456,7 +457,7 @@ func TestResourceHandler(t *testing.T) { t.Run("ReadResource", func(t *testing.T) { t.Run("returns error when name is invalid", func(t *testing.T) { service := new(resourceService) - handler := v1beta1.NewResourceHandler(logger, service) + handler := v1beta1.NewResourceHandler(logger, service, nil) req := &pb.ReadResourceRequest{ ResourceName: "", @@ -472,7 +473,7 @@ func TestResourceHandler(t *testing.T) { }) t.Run("returns error when store is invalid", func(t *testing.T) { service := new(resourceService) - handler := v1beta1.NewResourceHandler(logger, service) + handler := v1beta1.NewResourceHandler(logger, service, nil) req := &pb.ReadResourceRequest{ ProjectName: "proj", @@ -488,7 +489,7 @@ func TestResourceHandler(t *testing.T) { }) t.Run("returns error when tenant is invalid", func(t *testing.T) { service := new(resourceService) - handler := v1beta1.NewResourceHandler(logger, service) + handler := v1beta1.NewResourceHandler(logger, service, nil) req := &pb.ReadResourceRequest{ ProjectName: "", @@ -508,7 +509,7 @@ func TestResourceHandler(t *testing.T) { service.On("Get", ctx, mock.Anything, resource.Bigquery, name).Return(nil, errors.New("failure")) defer service.AssertExpectations(t) - handler := v1beta1.NewResourceHandler(logger, service) + handler := v1beta1.NewResourceHandler(logger, service, nil) req := &pb.ReadResourceRequest{ ProjectName: "proj", @@ -528,7 +529,7 @@ func TestResourceHandler(t *testing.T) { service.On("Get", ctx, mock.Anything, resource.Bigquery, name).Return(&resource.Resource{}, nil) defer service.AssertExpectations(t) - handler := v1beta1.NewResourceHandler(logger, service) + handler := v1beta1.NewResourceHandler(logger, service, nil) req := &pb.ReadResourceRequest{ ProjectName: "proj", @@ -553,7 +554,7 @@ func TestResourceHandler(t *testing.T) { service.On("Get", ctx, mock.Anything, resource.Bigquery, name).Return(dbRes, nil) defer service.AssertExpectations(t) - handler := v1beta1.NewResourceHandler(logger, service) + handler := v1beta1.NewResourceHandler(logger, service, nil) req := &pb.ReadResourceRequest{ ProjectName: "proj", @@ -578,7 +579,7 @@ func TestResourceHandler(t *testing.T) { service.On("Get", ctx, mock.Anything, resource.Bigquery, name).Return(dbRes, nil) defer service.AssertExpectations(t) - handler := v1beta1.NewResourceHandler(logger, service) + handler := v1beta1.NewResourceHandler(logger, service, nil) req := &pb.ReadResourceRequest{ ProjectName: "proj", @@ -597,7 +598,7 @@ func TestResourceHandler(t *testing.T) { t.Run("UpdateResource", func(t *testing.T) { t.Run("returns error when tenant is invalid", func(t *testing.T) { service := new(resourceService) - handler := v1beta1.NewResourceHandler(logger, service) + handler := v1beta1.NewResourceHandler(logger, service, nil) req := &pb.UpdateResourceRequest{ ProjectName: "", @@ -613,7 +614,7 @@ func TestResourceHandler(t *testing.T) { }) t.Run("returns error when store is invalid", func(t *testing.T) { service := new(resourceService) - handler := v1beta1.NewResourceHandler(logger, service) + handler := v1beta1.NewResourceHandler(logger, service, nil) req := &pb.UpdateResourceRequest{ ProjectName: "proj", @@ -629,7 +630,7 @@ func TestResourceHandler(t *testing.T) { }) t.Run("returns error when resource is nil", func(t *testing.T) { service := new(resourceService) - handler := v1beta1.NewResourceHandler(logger, service) + handler := v1beta1.NewResourceHandler(logger, service, nil) req := &pb.UpdateResourceRequest{ ProjectName: "proj", @@ -645,7 +646,7 @@ func TestResourceHandler(t *testing.T) { }) t.Run("returns error when kind is empty", func(t *testing.T) { service := new(resourceService) - handler := v1beta1.NewResourceHandler(logger, service) + handler := v1beta1.NewResourceHandler(logger, service, nil) spec, _ := structpb.NewStruct(map[string]interface{}{"a": "b"}) req := &pb.UpdateResourceRequest{ @@ -669,7 +670,7 @@ func TestResourceHandler(t *testing.T) { service.On("Update", ctx, mock.Anything, mock.Anything).Return(errors.New("validation failure")) defer service.AssertExpectations(t) - handler := v1beta1.NewResourceHandler(logger, service) + handler := v1beta1.NewResourceHandler(logger, service, nil) spec, _ := structpb.NewStruct(map[string]interface{}{"a": "b"}) req := &pb.UpdateResourceRequest{ @@ -694,7 +695,7 @@ func TestResourceHandler(t *testing.T) { service.On("Update", ctx, mock.Anything, mock.Anything).Return(nil) defer service.AssertExpectations(t) - handler := v1beta1.NewResourceHandler(logger, service) + handler := v1beta1.NewResourceHandler(logger, service, nil) spec, _ := structpb.NewStruct(map[string]interface{}{"description": "test"}) req := &pb.UpdateResourceRequest{ @@ -716,7 +717,7 @@ func TestResourceHandler(t *testing.T) { t.Run("UpsertResource", func(t *testing.T) { t.Run("returns error when tenant is invalid", func(t *testing.T) { service := new(resourceService) - handler := v1beta1.NewResourceHandler(logger, service) + handler := v1beta1.NewResourceHandler(logger, service, nil) req := &pb.UpsertResourceRequest{ ProjectName: "", @@ -732,7 +733,7 @@ func TestResourceHandler(t *testing.T) { }) t.Run("returns error when store is invalid", func(t *testing.T) { service := new(resourceService) - handler := v1beta1.NewResourceHandler(logger, service) + handler := v1beta1.NewResourceHandler(logger, service, nil) req := &pb.UpsertResourceRequest{ ProjectName: "proj", @@ -748,7 +749,7 @@ func TestResourceHandler(t *testing.T) { }) t.Run("returns error when resource is nil", func(t *testing.T) { service := new(resourceService) - handler := v1beta1.NewResourceHandler(logger, service) + handler := v1beta1.NewResourceHandler(logger, service, nil) req := &pb.UpsertResourceRequest{ ProjectName: "proj", @@ -763,7 +764,7 @@ func TestResourceHandler(t *testing.T) { }) t.Run("not process a resource when kind is empty", func(t *testing.T) { service := new(resourceService) - handler := v1beta1.NewResourceHandler(logger, service) + handler := v1beta1.NewResourceHandler(logger, service, nil) spec, _ := structpb.NewStruct(map[string]interface{}{"a": "b"}) req := &pb.UpsertResourceRequest{ @@ -789,7 +790,7 @@ func TestResourceHandler(t *testing.T) { service.On("Upsert", ctx, mock.Anything, mock.Anything).Return(errors.New("validation failure")) defer service.AssertExpectations(t) - handler := v1beta1.NewResourceHandler(logger, service) + handler := v1beta1.NewResourceHandler(logger, service, nil) spec, _ := structpb.NewStruct(map[string]interface{}{"a": "b"}) req := &pb.UpsertResourceRequest{ @@ -815,7 +816,7 @@ func TestResourceHandler(t *testing.T) { service.On("Upsert", ctx, mock.Anything, mock.Anything).Return(nil).Once() defer service.AssertExpectations(t) - handler := v1beta1.NewResourceHandler(logger, service) + handler := v1beta1.NewResourceHandler(logger, service, nil) spec, _ := structpb.NewStruct(map[string]interface{}{"a": "b"}) req := &pb.UpsertResourceRequest{ @@ -847,7 +848,7 @@ func TestResourceHandler(t *testing.T) { service.On("Upsert", ctx, mock.Anything, mock.Anything).Return(nil) defer service.AssertExpectations(t) - handler := v1beta1.NewResourceHandler(logger, service) + handler := v1beta1.NewResourceHandler(logger, service, nil) spec, _ := structpb.NewStruct(map[string]interface{}{"description": "test"}) req := &pb.UpsertResourceRequest{ @@ -872,7 +873,7 @@ func TestResourceHandler(t *testing.T) { service.On("Upsert", ctx, mock.Anything, mock.Anything).Return(nil).Twice() defer service.AssertExpectations(t) - handler := v1beta1.NewResourceHandler(logger, service) + handler := v1beta1.NewResourceHandler(logger, service, nil) spec, _ := structpb.NewStruct(map[string]interface{}{"description": "test"}) req := &pb.UpsertResourceRequest{ @@ -902,7 +903,7 @@ func TestResourceHandler(t *testing.T) { t.Run("ApplyResource", func(t *testing.T) { t.Run("returns error when tenant is invalid", func(t *testing.T) { service := new(resourceService) - handler := v1beta1.NewResourceHandler(logger, service) + handler := v1beta1.NewResourceHandler(logger, service, nil) req := &pb.ApplyResourcesRequest{ ProjectName: "", @@ -918,7 +919,7 @@ func TestResourceHandler(t *testing.T) { }) t.Run("returns error when store is invalid", func(t *testing.T) { service := new(resourceService) - handler := v1beta1.NewResourceHandler(logger, service) + handler := v1beta1.NewResourceHandler(logger, service, nil) req := &pb.ApplyResourcesRequest{ ProjectName: "proj", @@ -934,7 +935,7 @@ func TestResourceHandler(t *testing.T) { }) t.Run("returns error when resource names are empty", func(t *testing.T) { service := new(resourceService) - handler := v1beta1.NewResourceHandler(logger, service) + handler := v1beta1.NewResourceHandler(logger, service, nil) req := &pb.ApplyResourcesRequest{ ProjectName: "proj", @@ -955,7 +956,7 @@ func TestResourceHandler(t *testing.T) { service.On("SyncResources", ctx, tnnt, resource.Bigquery, names).Return(nil, errors.New("something went wrong")) defer service.AssertExpectations(t) - handler := v1beta1.NewResourceHandler(logger, service) + handler := v1beta1.NewResourceHandler(logger, service, nil) req := &pb.ApplyResourcesRequest{ ProjectName: "proj", @@ -977,7 +978,7 @@ func TestResourceHandler(t *testing.T) { &resource.SyncResponse{ResourceNames: names}, nil) defer service.AssertExpectations(t) - handler := v1beta1.NewResourceHandler(logger, service) + handler := v1beta1.NewResourceHandler(logger, service, nil) req := &pb.ApplyResourcesRequest{ ProjectName: "proj", @@ -1001,7 +1002,7 @@ func TestResourceHandler(t *testing.T) { t.Run("success", func(t *testing.T) { var ( service = new(resourceService) - handler = v1beta1.NewResourceHandler(logger, service) + handler = v1beta1.NewResourceHandler(logger, service, nil) req = &pb.DeleteResourceRequest{ ProjectName: tnnt.ProjectName().String(), NamespaceName: tnnt.NamespaceName().String(), @@ -1030,7 +1031,7 @@ func TestResourceHandler(t *testing.T) { t.Run("success with force", func(t *testing.T) { var ( service = new(resourceService) - handler = v1beta1.NewResourceHandler(logger, service) + handler = v1beta1.NewResourceHandler(logger, service, nil) req = &pb.DeleteResourceRequest{ ProjectName: tnnt.ProjectName().String(), NamespaceName: tnnt.NamespaceName().String(), @@ -1060,7 +1061,7 @@ func TestResourceHandler(t *testing.T) { t.Run("return error when delete", func(t *testing.T) { var ( service = new(resourceService) - handler = v1beta1.NewResourceHandler(logger, service) + handler = v1beta1.NewResourceHandler(logger, service, nil) req = &pb.DeleteResourceRequest{ ProjectName: tnnt.ProjectName().String(), NamespaceName: tnnt.NamespaceName().String(), @@ -1088,7 +1089,7 @@ func TestResourceHandler(t *testing.T) { t.Run("return error when resource store unknown", func(t *testing.T) { var ( service = new(resourceService) - handler = v1beta1.NewResourceHandler(logger, service) + handler = v1beta1.NewResourceHandler(logger, service, nil) req = &pb.DeleteResourceRequest{ ProjectName: tnnt.ProjectName().String(), NamespaceName: tnnt.NamespaceName().String(), @@ -1106,7 +1107,7 @@ func TestResourceHandler(t *testing.T) { t.Run("return error when tenant invalid", func(t *testing.T) { var ( service = new(resourceService) - handler = v1beta1.NewResourceHandler(logger, service) + handler = v1beta1.NewResourceHandler(logger, service, nil) req = &pb.DeleteResourceRequest{ ProjectName: tnnt.ProjectName().String(), NamespaceName: "", @@ -1122,6 +1123,79 @@ func TestResourceHandler(t *testing.T) { assert.Nil(t, res) }) }) + + t.Run("GetResourceChangelogs", func(t *testing.T) { + resourceName := "project.dataset.test_table" + + t.Run("successfully get all changelogs for the resource", func(t *testing.T) { + var ( + changelogService = newResourceChangeLogService(t) + handler = v1beta1.NewResourceHandler(logger, nil, changelogService) + req = &pb.GetResourceChangelogsRequest{ + ProjectName: tnnt.ProjectName().String(), + ResourceName: resourceName, + } + date = time.Date(2024, 6, 30, 10, 0, 0, 0, time.UTC) + + resourceChangelogs = []*resource.ChangeLog{ + { + Type: "update", + Time: date.Add(2 * time.Hour), + Change: []resource.Change{ + { + Property: "metadata.Version", + Diff: "- 2\n+ 3", + }, + }, + }, + { + Type: "update", + Time: date, + Change: []resource.Change{ + { + Property: "metadata.Description", + Diff: "- a table used to get the booking\n+ detail of gofood booking", + }, + }, + }, + } + + expectedChangelogs = &pb.GetResourceChangelogsResponse{ + History: []*pb.ResourceChangelog{ + { + EventType: "update", + Timestamp: "2024-06-30 12:00:00 +0000 UTC", + Change: []*pb.ResourceChange{ + { + AttributeName: "metadata.Version", + Diff: "- 2\n+ 3", + }, + }, + }, + { + EventType: "update", + Timestamp: "2024-06-30 10:00:00 +0000 UTC", + Change: []*pb.ResourceChange{ + { + AttributeName: "metadata.Description", + Diff: "- a table used to get the booking\n+ detail of gofood booking", + }, + }, + }, + }, + } + ) + + defer changelogService.AssertExpectations(t) + + changelogService.On("GetChangelogs", ctx, tnnt.ProjectName(), resource.Name(resourceName)).Return(resourceChangelogs, nil) + + res, err := handler.GetResourceChangelogs(ctx, req) + assert.NoError(t, err) + assert.NotNil(t, res) + assert.Equal(t, expectedChangelogs, res) + }) + }) } type resourceService struct { @@ -1230,3 +1304,53 @@ func (*resourceStreamMock) SendMsg(interface{}) error { func (*resourceStreamMock) RecvMsg(interface{}) error { panic("not supported") } + +// ResourceChangeLogService is an autogenerated mock type for the ResourceChangeLogService type +type ResourceChangeLogService struct { + mock.Mock +} + +// GetChangelogs provides a mock function with given fields: ctx, projectName, resourceName +func (_m *ResourceChangeLogService) GetChangelogs(ctx context.Context, projectName tenant.ProjectName, resourceName resource.Name) ([]*resource.ChangeLog, error) { + ret := _m.Called(ctx, projectName, resourceName) + + if len(ret) == 0 { + panic("no return value specified for GetChangelogs") + } + + var r0 []*resource.ChangeLog + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, tenant.ProjectName, resource.Name) ([]*resource.ChangeLog, error)); ok { + return rf(ctx, projectName, resourceName) + } + if rf, ok := ret.Get(0).(func(context.Context, tenant.ProjectName, resource.Name) []*resource.ChangeLog); ok { + r0 = rf(ctx, projectName, resourceName) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]*resource.ChangeLog) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, tenant.ProjectName, resource.Name) error); ok { + r1 = rf(ctx, projectName, resourceName) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// newResourceChangeLogService creates a new instance of ResourceChangeLogService. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func newResourceChangeLogService(t interface { + mock.TestingT + Cleanup(func()) +}, +) *ResourceChangeLogService { + mock := &ResourceChangeLogService{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/core/resource/service/changelog_service.go b/core/resource/service/changelog_service.go new file mode 100644 index 0000000000..5fd210dc2b --- /dev/null +++ b/core/resource/service/changelog_service.go @@ -0,0 +1,48 @@ +package service + +import ( + "context" + + "github.com/goto/salt/log" + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/promauto" + + "github.com/goto/optimus/core/resource" + "github.com/goto/optimus/core/tenant" +) + +type ChangelogRepository interface { + GetChangelogs(ctx context.Context, projectName tenant.ProjectName, resourceName resource.Name) ([]*resource.ChangeLog, error) +} + +// right now this is done to capture the feature adoption +var getChangelogFailures = promauto.NewCounterVec(prometheus.CounterOpts{ + Name: "get_resource_changelog_errors", + Help: "errors occurred in get resource changelog", +}, []string{"project", "resource", "error"}) + +type ChangelogService struct { + repo ChangelogRepository + logger log.Logger +} + +func NewChangelogService(logger log.Logger, repo ChangelogRepository) *ChangelogService { + return &ChangelogService{ + repo: repo, + logger: logger, + } +} + +func (cs ChangelogService) GetChangelogs(ctx context.Context, projectName tenant.ProjectName, resourceName resource.Name) ([]*resource.ChangeLog, error) { + changelogs, err := cs.repo.GetChangelogs(ctx, projectName, resourceName) + if err != nil { + cs.logger.Error("error getting changelog for resource [%s]: %s", resourceName.String(), err) + getChangelogFailures.WithLabelValues( + projectName.String(), + resourceName.String(), + err.Error(), + ).Inc() + } + + return changelogs, err +} diff --git a/core/resource/service/changelog_service_test.go b/core/resource/service/changelog_service_test.go new file mode 100644 index 0000000000..1ef6696066 --- /dev/null +++ b/core/resource/service/changelog_service_test.go @@ -0,0 +1,130 @@ +package service_test + +import ( + "context" + "errors" + "testing" + "time" + + "github.com/goto/salt/log" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + + "github.com/goto/optimus/core/resource" + "github.com/goto/optimus/core/resource/service" + "github.com/goto/optimus/core/tenant" +) + +func TestChangelogService(t *testing.T) { + ctx := context.Background() + logger := log.NewLogrus() + tnnt, tenantErr := tenant.NewTenant("project_test", "namespace_tes") + assert.NoError(t, tenantErr) + + t.Run("GetChangelogs", func(t *testing.T) { + projectName := tnnt.ProjectName() + resourceName := "project.dataset.table" + + t.Run("success fetching resource changelogs", func(t *testing.T) { + var ( + repo = newChangelogRepository(t) + rscService = service.NewChangelogService(logger, repo) + now = time.Now() + resourceChangelogs = []*resource.ChangeLog{ + { + Type: "update", + Time: now.Add(2 * time.Hour), + Change: []resource.Change{ + { + Property: "metadata.Version", + Diff: "- 2\n+ 3", + }, + }, + }, + { + Type: "update", + Time: now, + Change: []resource.Change{ + { + Property: "metadata.Description", + Diff: "- a table used to get the booking\n+ detail of gofood booking", + }, + }, + }, + } + ) + defer repo.AssertExpectations(t) + + repo.On("GetChangelogs", ctx, projectName, resource.Name(resourceName)).Return(resourceChangelogs, nil).Once() + + actualChangelogs, err := rscService.GetChangelogs(ctx, projectName, resource.Name(resourceName)) + assert.NoError(t, err) + assert.NotNil(t, actualChangelogs) + assert.Equal(t, resourceChangelogs, actualChangelogs) + }) + + t.Run("error fetching resource changelogs", func(t *testing.T) { + var ( + repo = newChangelogRepository(t) + rscService = service.NewChangelogService(logger, repo) + ) + defer repo.AssertExpectations(t) + + repo.On("GetChangelogs", ctx, projectName, resource.Name(resourceName)).Return(nil, errors.New("error")).Once() + + actualChangelogs, err := rscService.GetChangelogs(ctx, projectName, resource.Name(resourceName)) + assert.Error(t, err) + assert.Nil(t, actualChangelogs) + }) + }) +} + +// ChangelogRepository is an autogenerated mock type for the ChangelogRepository type +type ChangelogRepository struct { + mock.Mock +} + +// GetChangelogs provides a mock function with given fields: ctx, projectName, resourceName +func (_m *ChangelogRepository) GetChangelogs(ctx context.Context, projectName tenant.ProjectName, resourceName resource.Name) ([]*resource.ChangeLog, error) { + ret := _m.Called(ctx, projectName, resourceName) + + if len(ret) == 0 { + panic("no return value specified for GetChangelogs") + } + + var r0 []*resource.ChangeLog + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, tenant.ProjectName, resource.Name) ([]*resource.ChangeLog, error)); ok { + return rf(ctx, projectName, resourceName) + } + if rf, ok := ret.Get(0).(func(context.Context, tenant.ProjectName, resource.Name) []*resource.ChangeLog); ok { + r0 = rf(ctx, projectName, resourceName) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]*resource.ChangeLog) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, tenant.ProjectName, resource.Name) error); ok { + r1 = rf(ctx, projectName, resourceName) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// NewChangelogRepository creates a new instance of ChangelogRepository. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func newChangelogRepository(t interface { + mock.TestingT + Cleanup(func()) +}, +) *ChangelogRepository { + mock := &ChangelogRepository{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/internal/store/postgres/job/job_repository.go b/internal/store/postgres/job/job_repository.go index a6b13e174e..e28859cdaa 100644 --- a/internal/store/postgres/job/job_repository.go +++ b/internal/store/postgres/job/job_repository.go @@ -308,9 +308,9 @@ func (j JobRepository) insertChangelog(ctx context.Context, jobName job.Name, pr func (j JobRepository) GetChangelog(ctx context.Context, projectName tenant.ProjectName, jobName job.Name) ([]*job.ChangeLog, error) { me := errors.NewMultiError("get change log errors") - getChangeLogQuery := `select changes, change_type, created_at from changeLog where project_name = $1 and name = $2;` + getChangeLogQuery := `select changes, change_type, created_at from changeLog where project_name = $1 and name = $2 and entity_type = $3;` - rows, err := j.db.Query(ctx, getChangeLogQuery, projectName, jobName) + rows, err := j.db.Query(ctx, getChangeLogQuery, projectName, jobName, "job") if err != nil { return nil, errors.Wrap(job.EntityJob, "error while changeLog for job: "+projectName.String()+"/"+jobName.String(), err) } diff --git a/internal/store/postgres/resource/adapter.go b/internal/store/postgres/resource/adapter.go new file mode 100644 index 0000000000..57d518d0b0 --- /dev/null +++ b/internal/store/postgres/resource/adapter.go @@ -0,0 +1,47 @@ +package resource + +import ( + "time" + + "github.com/jackc/pgx/v5" + + "github.com/goto/optimus/core/resource" + "github.com/goto/optimus/internal/errors" +) + +type Change struct { + Property string `json:"attribute_name"` + Diff string `json:"diff"` +} + +type ChangeLog struct { + Change []Change + Type string + Time time.Time +} + +func FromChangelogRow(row pgx.Row) (*ChangeLog, error) { + var cl ChangeLog + err := row.Scan(&cl.Change, &cl.Type, &cl.Time) + if err != nil { + if errors.Is(err, pgx.ErrNoRows) { + return nil, errors.NotFound(resource.EntityResourceChangelog, "changelog not found") + } + return nil, errors.Wrap(resource.EntityResourceChangelog, "error in reading row for changelog", err) + } + return &cl, nil +} + +func fromStorageChangelog(changeLog *ChangeLog) *resource.ChangeLog { + resourceChangeLog := resource.ChangeLog{ + Type: changeLog.Type, + Time: changeLog.Time, + } + + resourceChangeLog.Change = make([]resource.Change, len(changeLog.Change)) + for i, change := range changeLog.Change { + resourceChangeLog.Change[i].Property = change.Property + resourceChangeLog.Change[i].Diff = change.Diff + } + return &resourceChangeLog +} diff --git a/internal/store/postgres/resource/repository.go b/internal/store/postgres/resource/repository.go index a596842cc7..0ed2a70bfd 100644 --- a/internal/store/postgres/resource/repository.go +++ b/internal/store/postgres/resource/repository.go @@ -2,21 +2,39 @@ package resource import ( "context" + "encoding/json" "fmt" "github.com/jackc/pgx/v5" "github.com/jackc/pgx/v5/pgxpool" + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/promauto" "github.com/goto/optimus/core/resource" "github.com/goto/optimus/core/tenant" "github.com/goto/optimus/internal/errors" + "github.com/goto/optimus/internal/utils" ) const ( columnsToStore = `full_name, kind, store, status, urn, project_name, namespace_name, metadata, spec, created_at, updated_at` resourceColumns = `id, ` + columnsToStore + + changelogColumnsToStore = `entity_type, name, project_name, change_type, changes, created_at` + changelogColumnsToFetch = `changes, change_type, created_at` ) +var changelogMetrics = promauto.NewCounterVec(prometheus.CounterOpts{ + Name: "resource_repository_changelog_metrics_error", + Help: "success or failure metrics", +}, []string{"project", "namespace", "name", "msg"}) + +var changelogIgnoredFields = map[string]struct{}{ + "ID": {}, + "CreatedAt": {}, + "UpdatedAt": {}, +} + type Repository struct { db *pgxpool.Pool } @@ -37,6 +55,87 @@ func (r Repository) Create(ctx context.Context, resourceModel *resource.Resource } func (r Repository) Update(ctx context.Context, resourceModel *resource.Resource) error { + existing, err := r.ReadByFullName(ctx, resourceModel.Tenant(), resourceModel.Store(), resourceModel.FullName(), true) + if err != nil { + return err + } + + if err := r.doUpdate(ctx, resourceModel); err != nil { + return err + } + + if err := r.computeAndPersistChangeLog(ctx, existing, resourceModel); err != nil { + // do not return the error + changelogMetrics.WithLabelValues( + resourceModel.Tenant().ProjectName().String(), + resourceModel.Tenant().NamespaceName().String(), + resourceModel.Name().String(), + err.Error(), + ).Inc() + } + + return nil +} + +func (r Repository) computeAndPersistChangeLog(ctx context.Context, existing, incoming *resource.Resource) error { + existingModel := FromResourceToModel(existing) + incomingModel := FromResourceToModel(incoming) + + changes, err := getResourceDiffs(existingModel, incomingModel) + if err != nil { + return err + } + + if len(changes) == 0 { + return nil + } + + return r.insertChangelog(ctx, incoming.Tenant().ProjectName(), incoming.Name(), changes, resource.ChangelogChangeTypeUpdate) +} + +func (r Repository) insertChangelog(ctx context.Context, projectName tenant.ProjectName, resourceName resource.Name, changes []Change, changeType string) error { + changesEncoded, err := json.Marshal(changes) + if err != nil { + return err + } + + insertChangeLogQuery := `INSERT INTO changelog (` + changelogColumnsToStore + `) VALUES ($1, $2, $3, $4, $5, NOW());` + + res, err := r.db.Exec(ctx, insertChangeLogQuery, resource.EntityResource, resourceName, projectName, + changeType, string(changesEncoded)) + if err != nil { + return errors.Wrap(resource.EntityResourceChangelog, "unable to insert resource changelog", err) + } + + if res.RowsAffected() == 0 { + return errors.InternalError(resource.EntityResourceChangelog, "unable to insert resource changelog: rows affected 0", nil) + } + + return err +} + +func getResourceDiffs(existing, incoming *Resource) ([]Change, error) { + var changes []Change + diff, err := utils.GetDiffs(*existing, *incoming, nil) + if err != nil { + return changes, err + } + + for _, d := range diff { + if _, ignored := changelogIgnoredFields[d.Field]; ignored { + continue + } + + changes = append(changes, Change{ + Property: d.Field, + Diff: d.Diff, + }) + } + + return changes, nil +} + +func (r Repository) doUpdate(ctx context.Context, resourceModel *resource.Resource) error { res := FromResourceToModel(resourceModel) updateResource := `UPDATE resource SET kind=$1, status=$2, urn=$3, metadata=$4, spec=$5, updated_at=now() @@ -223,3 +322,28 @@ func (r Repository) UpdateStatus(ctx context.Context, resources ...*resource.Res return multiErr.ToErr() } + +func (r Repository) GetChangelogs(ctx context.Context, projectName tenant.ProjectName, resourceName resource.Name) ([]*resource.ChangeLog, error) { + getChangeLogQuery := ` + SELECT ` + changelogColumnsToFetch + ` FROM changelog + WHERE project_name = $1 AND name = $2 AND entity_type = $3;` + + rows, err := r.db.Query(ctx, getChangeLogQuery, projectName, resourceName, "resource") + if err != nil { + return nil, errors.Wrap(resource.EntityResource, fmt.Sprintf("error while fetching changeLog for resource [%s/%s]", projectName.String(), resourceName.String()), err) + } + defer rows.Close() + + me := errors.NewMultiError("get change log errors") + var changeLog []*resource.ChangeLog + for rows.Next() { + log, err := FromChangelogRow(rows) + if err != nil { + me.Append(err) + continue + } + changeLog = append(changeLog, fromStorageChangelog(log)) + } + + return changeLog, me.ToErr() +} diff --git a/internal/store/postgres/resource/repository_test.go b/internal/store/postgres/resource/repository_test.go index be151eb662..ba7b6606da 100644 --- a/internal/store/postgres/resource/repository_test.go +++ b/internal/store/postgres/resource/repository_test.go @@ -377,6 +377,55 @@ func TestPostgresResourceRepository(t *testing.T) { assert.Nil(t, storedResources) }) }) + + t.Run("GetChangelogs", func(t *testing.T) { + t.Run("return empty for a resource", func(t *testing.T) { + pool := dbSetup() + repository := repoResource.NewRepository(pool) + + resourceName := "project.dataset.table" + + changelogs, err := repository.GetChangelogs(ctx, tnnt.ProjectName(), serviceResource.Name(resourceName)) + assert.NoError(t, err) + assert.Len(t, changelogs, 0) + }) + + t.Run("return changelogs for a specific resource", func(t *testing.T) { + pool := dbSetup() + repository := repoResource.NewRepository(pool) + + resourceName := serviceResource.Name("project.dataset.table") + changelogs := []*repoResource.ChangeLog{ + { + Type: "update", + Change: []repoResource.Change{ + { + Property: "metadata", + Diff: "- + ", + }, + }, + }, + } + insertTestResourceChangelog(pool, resourceName, tnnt.ProjectName(), changelogs) + + actualChangelogs, err := repository.GetChangelogs(ctx, tnnt.ProjectName(), resourceName) + assert.NoError(t, err) + assert.Len(t, actualChangelogs, len(changelogs)) + }) + }) +} + +func insertTestResourceChangelog(pool *pgxpool.Pool, resourceName serviceResource.Name, projectName tenant.ProjectName, changelogs []*repoResource.ChangeLog) { + ctx := context.Background() + query := `INSERT INTO changelog ( entity_type , name , project_name , change_type , changes , created_at ) + VALUES ($1, $2, $3, $4, $5, NOW())` + + for _, cl := range changelogs { + _, err := pool.Exec(ctx, query, "resource", resourceName, projectName, cl.Type, cl.Change) + if err != nil { + panic(err) + } + } } func dbSetup() *pgxpool.Pool { diff --git a/protos/gotocompany/optimus/core/v1beta1/resource.pb.go b/protos/gotocompany/optimus/core/v1beta1/resource.pb.go index 7188997e13..0498091749 100644 --- a/protos/gotocompany/optimus/core/v1beta1/resource.pb.go +++ b/protos/gotocompany/optimus/core/v1beta1/resource.pb.go @@ -1292,6 +1292,226 @@ func (x *DeleteResourceResponse) GetDownstreamJobs() []string { return nil } +type GetResourceChangelogsRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ProjectName string `protobuf:"bytes,1,opt,name=project_name,json=projectName,proto3" json:"project_name,omitempty"` + ResourceName string `protobuf:"bytes,2,opt,name=resource_name,json=resourceName,proto3" json:"resource_name,omitempty"` +} + +func (x *GetResourceChangelogsRequest) Reset() { + *x = GetResourceChangelogsRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_gotocompany_optimus_core_v1beta1_resource_proto_msgTypes[20] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GetResourceChangelogsRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetResourceChangelogsRequest) ProtoMessage() {} + +func (x *GetResourceChangelogsRequest) ProtoReflect() protoreflect.Message { + mi := &file_gotocompany_optimus_core_v1beta1_resource_proto_msgTypes[20] + 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 GetResourceChangelogsRequest.ProtoReflect.Descriptor instead. +func (*GetResourceChangelogsRequest) Descriptor() ([]byte, []int) { + return file_gotocompany_optimus_core_v1beta1_resource_proto_rawDescGZIP(), []int{20} +} + +func (x *GetResourceChangelogsRequest) GetProjectName() string { + if x != nil { + return x.ProjectName + } + return "" +} + +func (x *GetResourceChangelogsRequest) GetResourceName() string { + if x != nil { + return x.ResourceName + } + return "" +} + +type ResourceChange struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + AttributeName string `protobuf:"bytes,1,opt,name=attribute_name,json=attributeName,proto3" json:"attribute_name,omitempty"` + Diff string `protobuf:"bytes,2,opt,name=diff,proto3" json:"diff,omitempty"` +} + +func (x *ResourceChange) Reset() { + *x = ResourceChange{} + if protoimpl.UnsafeEnabled { + mi := &file_gotocompany_optimus_core_v1beta1_resource_proto_msgTypes[21] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ResourceChange) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ResourceChange) ProtoMessage() {} + +func (x *ResourceChange) ProtoReflect() protoreflect.Message { + mi := &file_gotocompany_optimus_core_v1beta1_resource_proto_msgTypes[21] + 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 ResourceChange.ProtoReflect.Descriptor instead. +func (*ResourceChange) Descriptor() ([]byte, []int) { + return file_gotocompany_optimus_core_v1beta1_resource_proto_rawDescGZIP(), []int{21} +} + +func (x *ResourceChange) GetAttributeName() string { + if x != nil { + return x.AttributeName + } + return "" +} + +func (x *ResourceChange) GetDiff() string { + if x != nil { + return x.Diff + } + return "" +} + +type ResourceChangelog struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + EventType string `protobuf:"bytes,1,opt,name=event_type,json=eventType,proto3" json:"event_type,omitempty"` + Timestamp string `protobuf:"bytes,2,opt,name=timestamp,proto3" json:"timestamp,omitempty"` + Change []*ResourceChange `protobuf:"bytes,3,rep,name=change,proto3" json:"change,omitempty"` +} + +func (x *ResourceChangelog) Reset() { + *x = ResourceChangelog{} + if protoimpl.UnsafeEnabled { + mi := &file_gotocompany_optimus_core_v1beta1_resource_proto_msgTypes[22] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ResourceChangelog) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ResourceChangelog) ProtoMessage() {} + +func (x *ResourceChangelog) ProtoReflect() protoreflect.Message { + mi := &file_gotocompany_optimus_core_v1beta1_resource_proto_msgTypes[22] + 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 ResourceChangelog.ProtoReflect.Descriptor instead. +func (*ResourceChangelog) Descriptor() ([]byte, []int) { + return file_gotocompany_optimus_core_v1beta1_resource_proto_rawDescGZIP(), []int{22} +} + +func (x *ResourceChangelog) GetEventType() string { + if x != nil { + return x.EventType + } + return "" +} + +func (x *ResourceChangelog) GetTimestamp() string { + if x != nil { + return x.Timestamp + } + return "" +} + +func (x *ResourceChangelog) GetChange() []*ResourceChange { + if x != nil { + return x.Change + } + return nil +} + +type GetResourceChangelogsResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + History []*ResourceChangelog `protobuf:"bytes,1,rep,name=history,proto3" json:"history,omitempty"` +} + +func (x *GetResourceChangelogsResponse) Reset() { + *x = GetResourceChangelogsResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_gotocompany_optimus_core_v1beta1_resource_proto_msgTypes[23] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GetResourceChangelogsResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetResourceChangelogsResponse) ProtoMessage() {} + +func (x *GetResourceChangelogsResponse) ProtoReflect() protoreflect.Message { + mi := &file_gotocompany_optimus_core_v1beta1_resource_proto_msgTypes[23] + 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 GetResourceChangelogsResponse.ProtoReflect.Descriptor instead. +func (*GetResourceChangelogsResponse) Descriptor() ([]byte, []int) { + return file_gotocompany_optimus_core_v1beta1_resource_proto_rawDescGZIP(), []int{23} +} + +func (x *GetResourceChangelogsResponse) GetHistory() []*ResourceChangelog { + if x != nil { + return x.History + } + return nil +} + type ApplyResourcesResponse_ResourceStatus struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -1305,7 +1525,7 @@ type ApplyResourcesResponse_ResourceStatus struct { func (x *ApplyResourcesResponse_ResourceStatus) Reset() { *x = ApplyResourcesResponse_ResourceStatus{} if protoimpl.UnsafeEnabled { - mi := &file_gotocompany_optimus_core_v1beta1_resource_proto_msgTypes[22] + mi := &file_gotocompany_optimus_core_v1beta1_resource_proto_msgTypes[26] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1318,7 +1538,7 @@ func (x *ApplyResourcesResponse_ResourceStatus) String() string { func (*ApplyResourcesResponse_ResourceStatus) ProtoMessage() {} func (x *ApplyResourcesResponse_ResourceStatus) ProtoReflect() protoreflect.Message { - mi := &file_gotocompany_optimus_core_v1beta1_resource_proto_msgTypes[22] + mi := &file_gotocompany_optimus_core_v1beta1_resource_proto_msgTypes[26] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1591,154 +1811,197 @@ var file_gotocompany_optimus_core_v1beta1_resource_proto_rawDesc = []byte{ 0x74, 0x65, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x27, 0x0a, 0x0f, 0x64, 0x6f, 0x77, 0x6e, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x5f, 0x6a, 0x6f, 0x62, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0e, 0x64, 0x6f, 0x77, - 0x6e, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x4a, 0x6f, 0x62, 0x73, 0x32, 0x83, 0x11, 0x0a, 0x0f, - 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, - 0xb0, 0x01, 0x0a, 0x1b, 0x44, 0x65, 0x70, 0x6c, 0x6f, 0x79, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, - 0x63, 0x65, 0x53, 0x70, 0x65, 0x63, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, - 0x44, 0x2e, 0x67, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x6e, 0x79, 0x2e, 0x6f, 0x70, + 0x6e, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x4a, 0x6f, 0x62, 0x73, 0x22, 0x66, 0x0a, 0x1c, 0x47, + 0x65, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, + 0x6c, 0x6f, 0x67, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x70, + 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x0b, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x23, + 0x0a, 0x0d, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x4e, + 0x61, 0x6d, 0x65, 0x22, 0x4b, 0x0a, 0x0e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x43, + 0x68, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x25, 0x0a, 0x0e, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, + 0x74, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x61, + 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x12, 0x0a, 0x04, + 0x64, 0x69, 0x66, 0x66, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x64, 0x69, 0x66, 0x66, + 0x22, 0x9a, 0x01, 0x0a, 0x11, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x43, 0x68, 0x61, + 0x6e, 0x67, 0x65, 0x6c, 0x6f, 0x67, 0x12, 0x1d, 0x0a, 0x0a, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x5f, + 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x65, 0x76, 0x65, 0x6e, + 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, + 0x6d, 0x70, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, + 0x61, 0x6d, 0x70, 0x12, 0x48, 0x0a, 0x06, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x18, 0x03, 0x20, + 0x03, 0x28, 0x0b, 0x32, 0x30, 0x2e, 0x67, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x6e, + 0x79, 0x2e, 0x6f, 0x70, 0x74, 0x69, 0x6d, 0x75, 0x73, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x76, + 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x43, + 0x68, 0x61, 0x6e, 0x67, 0x65, 0x52, 0x06, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x22, 0x6e, 0x0a, + 0x1d, 0x47, 0x65, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x43, 0x68, 0x61, 0x6e, + 0x67, 0x65, 0x6c, 0x6f, 0x67, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4d, + 0x0a, 0x07, 0x68, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, + 0x33, 0x2e, 0x67, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x6e, 0x79, 0x2e, 0x6f, 0x70, 0x74, 0x69, 0x6d, 0x75, 0x73, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, - 0x61, 0x31, 0x2e, 0x44, 0x65, 0x70, 0x6c, 0x6f, 0x79, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, - 0x65, 0x53, 0x70, 0x65, 0x63, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x45, 0x2e, 0x67, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6d, 0x70, - 0x61, 0x6e, 0x79, 0x2e, 0x6f, 0x70, 0x74, 0x69, 0x6d, 0x75, 0x73, 0x2e, 0x63, 0x6f, 0x72, 0x65, - 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x44, 0x65, 0x70, 0x6c, 0x6f, 0x79, 0x52, - 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x53, 0x70, 0x65, 0x63, 0x69, 0x66, 0x69, 0x63, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x28, 0x01, - 0x30, 0x01, 0x12, 0x8c, 0x02, 0x0a, 0x19, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, - 0x72, 0x63, 0x65, 0x53, 0x70, 0x65, 0x63, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x12, 0x42, 0x2e, 0x67, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x6e, 0x79, 0x2e, 0x6f, - 0x70, 0x74, 0x69, 0x6d, 0x75, 0x73, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x76, 0x31, 0x62, 0x65, - 0x74, 0x61, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, - 0x53, 0x70, 0x65, 0x63, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x43, 0x2e, 0x67, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6d, 0x70, 0x61, - 0x6e, 0x79, 0x2e, 0x6f, 0x70, 0x74, 0x69, 0x6d, 0x75, 0x73, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, - 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x6f, + 0x61, 0x31, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x67, + 0x65, 0x6c, 0x6f, 0x67, 0x52, 0x07, 0x68, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x32, 0xea, 0x12, + 0x0a, 0x0f, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, + 0x65, 0x12, 0xb0, 0x01, 0x0a, 0x1b, 0x44, 0x65, 0x70, 0x6c, 0x6f, 0x79, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x53, 0x70, 0x65, 0x63, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x66, 0x82, 0xd3, 0xe4, 0x93, 0x02, - 0x60, 0x12, 0x5e, 0x2f, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2f, 0x70, 0x72, 0x6f, 0x6a, + 0x6e, 0x12, 0x44, 0x2e, 0x67, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x6e, 0x79, 0x2e, + 0x6f, 0x70, 0x74, 0x69, 0x6d, 0x75, 0x73, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x76, 0x31, 0x62, + 0x65, 0x74, 0x61, 0x31, 0x2e, 0x44, 0x65, 0x70, 0x6c, 0x6f, 0x79, 0x52, 0x65, 0x73, 0x6f, 0x75, + 0x72, 0x63, 0x65, 0x53, 0x70, 0x65, 0x63, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x45, 0x2e, 0x67, 0x6f, 0x74, 0x6f, 0x63, 0x6f, + 0x6d, 0x70, 0x61, 0x6e, 0x79, 0x2e, 0x6f, 0x70, 0x74, 0x69, 0x6d, 0x75, 0x73, 0x2e, 0x63, 0x6f, + 0x72, 0x65, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x44, 0x65, 0x70, 0x6c, 0x6f, + 0x79, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x53, 0x70, 0x65, 0x63, 0x69, 0x66, 0x69, + 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, + 0x28, 0x01, 0x30, 0x01, 0x12, 0x8c, 0x02, 0x0a, 0x19, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, + 0x6f, 0x75, 0x72, 0x63, 0x65, 0x53, 0x70, 0x65, 0x63, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x12, 0x42, 0x2e, 0x67, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x6e, 0x79, + 0x2e, 0x6f, 0x70, 0x74, 0x69, 0x6d, 0x75, 0x73, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x76, 0x31, + 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, + 0x63, 0x65, 0x53, 0x70, 0x65, 0x63, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x43, 0x2e, 0x67, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6d, + 0x70, 0x61, 0x6e, 0x79, 0x2e, 0x6f, 0x70, 0x74, 0x69, 0x6d, 0x75, 0x73, 0x2e, 0x63, 0x6f, 0x72, + 0x65, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, + 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x53, 0x70, 0x65, 0x63, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x66, 0x82, 0xd3, 0xe4, + 0x93, 0x02, 0x60, 0x12, 0x5e, 0x2f, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2f, 0x70, 0x72, + 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x2f, 0x7b, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x6e, + 0x61, 0x6d, 0x65, 0x7d, 0x2f, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x2f, 0x7b, + 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x7d, 0x2f, + 0x64, 0x61, 0x74, 0x61, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2f, 0x7b, 0x64, 0x61, 0x74, 0x61, 0x73, + 0x74, 0x6f, 0x72, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x7d, 0x2f, 0x72, 0x65, 0x73, 0x6f, 0x75, + 0x72, 0x63, 0x65, 0x12, 0xee, 0x01, 0x0a, 0x0e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x52, 0x65, + 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x37, 0x2e, 0x67, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6d, + 0x70, 0x61, 0x6e, 0x79, 0x2e, 0x6f, 0x70, 0x74, 0x69, 0x6d, 0x75, 0x73, 0x2e, 0x63, 0x6f, 0x72, + 0x65, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, + 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x38, 0x2e, 0x67, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x6e, 0x79, 0x2e, 0x6f, 0x70, + 0x74, 0x69, 0x6d, 0x75, 0x73, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, + 0x61, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, + 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x69, 0x82, 0xd3, 0xe4, 0x93, 0x02, + 0x63, 0x22, 0x5e, 0x2f, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2f, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x2f, 0x7b, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x7d, 0x2f, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x2f, 0x7b, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x7d, 0x2f, 0x64, 0x61, 0x74, 0x61, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2f, 0x7b, 0x64, 0x61, 0x74, 0x61, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x7d, 0x2f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, - 0x65, 0x12, 0xee, 0x01, 0x0a, 0x0e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x6f, - 0x75, 0x72, 0x63, 0x65, 0x12, 0x37, 0x2e, 0x67, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6d, 0x70, 0x61, - 0x6e, 0x79, 0x2e, 0x6f, 0x70, 0x74, 0x69, 0x6d, 0x75, 0x73, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, - 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x52, 0x65, - 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x38, 0x2e, - 0x67, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x6e, 0x79, 0x2e, 0x6f, 0x70, 0x74, 0x69, - 0x6d, 0x75, 0x73, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, - 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x69, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x63, 0x22, - 0x5e, 0x2f, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2f, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, - 0x74, 0x2f, 0x7b, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x7d, - 0x2f, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x2f, 0x7b, 0x6e, 0x61, 0x6d, 0x65, - 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x7d, 0x2f, 0x64, 0x61, 0x74, 0x61, - 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2f, 0x7b, 0x64, 0x61, 0x74, 0x61, 0x73, 0x74, 0x6f, 0x72, 0x65, - 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x7d, 0x2f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x3a, - 0x01, 0x2a, 0x12, 0xf5, 0x01, 0x0a, 0x0c, 0x52, 0x65, 0x61, 0x64, 0x52, 0x65, 0x73, 0x6f, 0x75, - 0x72, 0x63, 0x65, 0x12, 0x35, 0x2e, 0x67, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x6e, - 0x79, 0x2e, 0x6f, 0x70, 0x74, 0x69, 0x6d, 0x75, 0x73, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x76, - 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x52, 0x65, 0x61, 0x64, 0x52, 0x65, 0x73, 0x6f, 0x75, - 0x72, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x36, 0x2e, 0x67, 0x6f, 0x74, - 0x6f, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x6e, 0x79, 0x2e, 0x6f, 0x70, 0x74, 0x69, 0x6d, 0x75, 0x73, - 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x52, 0x65, - 0x61, 0x64, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x22, 0x76, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x70, 0x12, 0x6e, 0x2f, 0x76, 0x31, 0x62, + 0x65, 0x3a, 0x01, 0x2a, 0x12, 0xf5, 0x01, 0x0a, 0x0c, 0x52, 0x65, 0x61, 0x64, 0x52, 0x65, 0x73, + 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x35, 0x2e, 0x67, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6d, 0x70, + 0x61, 0x6e, 0x79, 0x2e, 0x6f, 0x70, 0x74, 0x69, 0x6d, 0x75, 0x73, 0x2e, 0x63, 0x6f, 0x72, 0x65, + 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x52, 0x65, 0x61, 0x64, 0x52, 0x65, 0x73, + 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x36, 0x2e, 0x67, + 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x6e, 0x79, 0x2e, 0x6f, 0x70, 0x74, 0x69, 0x6d, + 0x75, 0x73, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, + 0x52, 0x65, 0x61, 0x64, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x76, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x70, 0x12, 0x6e, 0x2f, 0x76, + 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2f, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x2f, 0x7b, + 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x7d, 0x2f, 0x6e, 0x61, + 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x2f, 0x7b, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, + 0x63, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x7d, 0x2f, 0x64, 0x61, 0x74, 0x61, 0x73, 0x74, 0x6f, + 0x72, 0x65, 0x2f, 0x7b, 0x64, 0x61, 0x74, 0x61, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x5f, 0x6e, 0x61, + 0x6d, 0x65, 0x7d, 0x2f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2f, 0x7b, 0x72, 0x65, + 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x7d, 0x12, 0xee, 0x01, 0x0a, + 0x0e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, + 0x37, 0x2e, 0x67, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x6e, 0x79, 0x2e, 0x6f, 0x70, + 0x74, 0x69, 0x6d, 0x75, 0x73, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, + 0x61, 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, + 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x38, 0x2e, 0x67, 0x6f, 0x74, 0x6f, 0x63, + 0x6f, 0x6d, 0x70, 0x61, 0x6e, 0x79, 0x2e, 0x6f, 0x70, 0x74, 0x69, 0x6d, 0x75, 0x73, 0x2e, 0x63, + 0x6f, 0x72, 0x65, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, + 0x74, 0x65, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x22, 0x69, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x63, 0x1a, 0x5e, 0x2f, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2f, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x2f, 0x7b, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x7d, 0x2f, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x2f, 0x7b, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x7d, 0x2f, 0x64, 0x61, 0x74, 0x61, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2f, 0x7b, 0x64, 0x61, 0x74, 0x61, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, - 0x7d, 0x2f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2f, 0x7b, 0x72, 0x65, 0x73, 0x6f, - 0x75, 0x72, 0x63, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x7d, 0x12, 0xee, 0x01, 0x0a, 0x0e, 0x55, - 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x37, 0x2e, - 0x67, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x6e, 0x79, 0x2e, 0x6f, 0x70, 0x74, 0x69, - 0x6d, 0x75, 0x73, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, - 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x38, 0x2e, 0x67, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6d, - 0x70, 0x61, 0x6e, 0x79, 0x2e, 0x6f, 0x70, 0x74, 0x69, 0x6d, 0x75, 0x73, 0x2e, 0x63, 0x6f, 0x72, - 0x65, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, - 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x22, 0x69, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x63, 0x1a, 0x5e, 0x2f, 0x76, 0x31, 0x62, 0x65, 0x74, - 0x61, 0x31, 0x2f, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x2f, 0x7b, 0x70, 0x72, 0x6f, 0x6a, - 0x65, 0x63, 0x74, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x7d, 0x2f, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, - 0x61, 0x63, 0x65, 0x2f, 0x7b, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x6e, - 0x61, 0x6d, 0x65, 0x7d, 0x2f, 0x64, 0x61, 0x74, 0x61, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2f, 0x7b, - 0x64, 0x61, 0x74, 0x61, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x7d, 0x2f, - 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x3a, 0x01, 0x2a, 0x12, 0xf6, 0x01, 0x0a, 0x0e, - 0x55, 0x70, 0x73, 0x65, 0x72, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x37, + 0x7d, 0x2f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x3a, 0x01, 0x2a, 0x12, 0xf6, 0x01, + 0x0a, 0x0e, 0x55, 0x70, 0x73, 0x65, 0x72, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, + 0x12, 0x37, 0x2e, 0x67, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x6e, 0x79, 0x2e, 0x6f, + 0x70, 0x74, 0x69, 0x6d, 0x75, 0x73, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x76, 0x31, 0x62, 0x65, + 0x74, 0x61, 0x31, 0x2e, 0x55, 0x70, 0x73, 0x65, 0x72, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, + 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x38, 0x2e, 0x67, 0x6f, 0x74, 0x6f, + 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x6e, 0x79, 0x2e, 0x6f, 0x70, 0x74, 0x69, 0x6d, 0x75, 0x73, 0x2e, + 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x55, 0x70, 0x73, + 0x65, 0x72, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x22, 0x71, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x6b, 0x22, 0x66, 0x2f, 0x76, 0x31, + 0x62, 0x65, 0x74, 0x61, 0x31, 0x2f, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x2f, 0x7b, 0x70, + 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x7d, 0x2f, 0x6e, 0x61, 0x6d, + 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x2f, 0x7b, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, + 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x7d, 0x2f, 0x64, 0x61, 0x74, 0x61, 0x73, 0x74, 0x6f, 0x72, + 0x65, 0x2f, 0x7b, 0x64, 0x61, 0x74, 0x61, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x5f, 0x6e, 0x61, 0x6d, + 0x65, 0x7d, 0x2f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x2d, 0x75, 0x70, 0x73, + 0x65, 0x72, 0x74, 0x3a, 0x01, 0x2a, 0x12, 0xfb, 0x01, 0x0a, 0x0e, 0x44, 0x65, 0x6c, 0x65, 0x74, + 0x65, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x37, 0x2e, 0x67, 0x6f, 0x74, 0x6f, + 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x6e, 0x79, 0x2e, 0x6f, 0x70, 0x74, 0x69, 0x6d, 0x75, 0x73, 0x2e, + 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x44, 0x65, 0x6c, + 0x65, 0x74, 0x65, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x38, 0x2e, 0x67, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x6e, 0x79, + 0x2e, 0x6f, 0x70, 0x74, 0x69, 0x6d, 0x75, 0x73, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x76, 0x31, + 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x73, 0x6f, + 0x75, 0x72, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x76, 0x82, 0xd3, + 0xe4, 0x93, 0x02, 0x70, 0x2a, 0x6e, 0x2f, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2f, 0x70, + 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x2f, 0x7b, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x5f, + 0x6e, 0x61, 0x6d, 0x65, 0x7d, 0x2f, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x2f, + 0x7b, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x7d, + 0x2f, 0x64, 0x61, 0x74, 0x61, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2f, 0x7b, 0x64, 0x61, 0x74, 0x61, + 0x73, 0x74, 0x6f, 0x72, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x7d, 0x2f, 0x72, 0x65, 0x73, 0x6f, + 0x75, 0x72, 0x63, 0x65, 0x2f, 0x7b, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x6e, + 0x61, 0x6d, 0x65, 0x7d, 0x12, 0xe4, 0x01, 0x0a, 0x17, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x52, + 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, + 0x12, 0x40, 0x2e, 0x67, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x6e, 0x79, 0x2e, 0x6f, + 0x70, 0x74, 0x69, 0x6d, 0x75, 0x73, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x76, 0x31, 0x62, 0x65, + 0x74, 0x61, 0x31, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, + 0x63, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x41, 0x2e, 0x67, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x6e, 0x79, + 0x2e, 0x6f, 0x70, 0x74, 0x69, 0x6d, 0x75, 0x73, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x76, 0x31, + 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x52, 0x65, 0x73, 0x6f, + 0x75, 0x72, 0x63, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x44, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x3e, 0x22, 0x39, 0x2f, + 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2f, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x2f, + 0x7b, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x7d, 0x2f, 0x63, + 0x68, 0x61, 0x6e, 0x67, 0x65, 0x2d, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2d, 0x6e, + 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x3a, 0x01, 0x2a, 0x12, 0xf5, 0x01, 0x0a, 0x0e, + 0x41, 0x70, 0x70, 0x6c, 0x79, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x12, 0x37, 0x2e, 0x67, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x6e, 0x79, 0x2e, 0x6f, 0x70, 0x74, 0x69, 0x6d, 0x75, 0x73, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, - 0x31, 0x2e, 0x55, 0x70, 0x73, 0x65, 0x72, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, + 0x31, 0x2e, 0x41, 0x70, 0x70, 0x6c, 0x79, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x38, 0x2e, 0x67, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x6e, 0x79, 0x2e, 0x6f, 0x70, 0x74, 0x69, 0x6d, 0x75, 0x73, 0x2e, 0x63, 0x6f, - 0x72, 0x65, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x55, 0x70, 0x73, 0x65, 0x72, - 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x22, 0x71, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x6b, 0x22, 0x66, 0x2f, 0x76, 0x31, 0x62, 0x65, + 0x72, 0x65, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x41, 0x70, 0x70, 0x6c, 0x79, + 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x22, 0x70, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x6a, 0x22, 0x65, 0x2f, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2f, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x2f, 0x7b, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x7d, 0x2f, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x2f, 0x7b, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x7d, 0x2f, 0x64, 0x61, 0x74, 0x61, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2f, 0x7b, 0x64, 0x61, 0x74, 0x61, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x7d, - 0x2f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x2d, 0x75, 0x70, 0x73, 0x65, 0x72, - 0x74, 0x3a, 0x01, 0x2a, 0x12, 0xfb, 0x01, 0x0a, 0x0e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, - 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x37, 0x2e, 0x67, 0x6f, 0x74, 0x6f, 0x63, 0x6f, - 0x6d, 0x70, 0x61, 0x6e, 0x79, 0x2e, 0x6f, 0x70, 0x74, 0x69, 0x6d, 0x75, 0x73, 0x2e, 0x63, 0x6f, - 0x72, 0x65, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, - 0x65, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x38, 0x2e, 0x67, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x6e, 0x79, 0x2e, 0x6f, - 0x70, 0x74, 0x69, 0x6d, 0x75, 0x73, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x76, 0x31, 0x62, 0x65, - 0x74, 0x61, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, - 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x76, 0x82, 0xd3, 0xe4, 0x93, - 0x02, 0x70, 0x2a, 0x6e, 0x2f, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2f, 0x70, 0x72, 0x6f, - 0x6a, 0x65, 0x63, 0x74, 0x2f, 0x7b, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x6e, 0x61, - 0x6d, 0x65, 0x7d, 0x2f, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x2f, 0x7b, 0x6e, - 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x7d, 0x2f, 0x64, - 0x61, 0x74, 0x61, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2f, 0x7b, 0x64, 0x61, 0x74, 0x61, 0x73, 0x74, - 0x6f, 0x72, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x7d, 0x2f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, - 0x63, 0x65, 0x2f, 0x7b, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x6e, 0x61, 0x6d, - 0x65, 0x7d, 0x12, 0xe4, 0x01, 0x0a, 0x17, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x52, 0x65, 0x73, - 0x6f, 0x75, 0x72, 0x63, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x40, - 0x2e, 0x67, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x6e, 0x79, 0x2e, 0x6f, 0x70, 0x74, - 0x69, 0x6d, 0x75, 0x73, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, - 0x31, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, - 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x41, 0x2e, 0x67, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x6e, 0x79, 0x2e, 0x6f, - 0x70, 0x74, 0x69, 0x6d, 0x75, 0x73, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x76, 0x31, 0x62, 0x65, - 0x74, 0x61, 0x31, 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, - 0x63, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x22, 0x44, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x3e, 0x22, 0x39, 0x2f, 0x76, 0x31, - 0x62, 0x65, 0x74, 0x61, 0x31, 0x2f, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x2f, 0x7b, 0x70, - 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x7d, 0x2f, 0x63, 0x68, 0x61, - 0x6e, 0x67, 0x65, 0x2d, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2d, 0x6e, 0x61, 0x6d, - 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x3a, 0x01, 0x2a, 0x12, 0xf5, 0x01, 0x0a, 0x0e, 0x41, 0x70, - 0x70, 0x6c, 0x79, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x12, 0x37, 0x2e, 0x67, - 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x6e, 0x79, 0x2e, 0x6f, 0x70, 0x74, 0x69, 0x6d, - 0x75, 0x73, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, - 0x41, 0x70, 0x70, 0x6c, 0x79, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x38, 0x2e, 0x67, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6d, 0x70, - 0x61, 0x6e, 0x79, 0x2e, 0x6f, 0x70, 0x74, 0x69, 0x6d, 0x75, 0x73, 0x2e, 0x63, 0x6f, 0x72, 0x65, - 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x41, 0x70, 0x70, 0x6c, 0x79, 0x52, 0x65, - 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, - 0x70, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x6a, 0x22, 0x65, 0x2f, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, - 0x31, 0x2f, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x2f, 0x7b, 0x70, 0x72, 0x6f, 0x6a, 0x65, - 0x63, 0x74, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x7d, 0x2f, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, - 0x63, 0x65, 0x2f, 0x7b, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x6e, 0x61, - 0x6d, 0x65, 0x7d, 0x2f, 0x64, 0x61, 0x74, 0x61, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2f, 0x7b, 0x64, - 0x61, 0x74, 0x61, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x7d, 0x2f, 0x72, - 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x2d, 0x61, 0x70, 0x70, 0x6c, 0x79, 0x3a, 0x01, - 0x2a, 0x42, 0xa4, 0x01, 0x0a, 0x1e, 0x63, 0x6f, 0x6d, 0x2e, 0x67, 0x6f, 0x74, 0x6f, 0x63, 0x6f, - 0x6d, 0x70, 0x61, 0x6e, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x6e, 0x2e, 0x6f, 0x70, 0x74, - 0x69, 0x6d, 0x75, 0x73, 0x42, 0x16, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x53, 0x65, - 0x72, 0x76, 0x69, 0x63, 0x65, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x72, 0x50, 0x01, 0x5a, 0x1e, - 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x6f, 0x74, 0x6f, 0x2f, - 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x6e, 0x2f, 0x6f, 0x70, 0x74, 0x69, 0x6d, 0x75, 0x73, 0x92, 0x41, - 0x47, 0x12, 0x05, 0x32, 0x03, 0x30, 0x2e, 0x31, 0x1a, 0x0e, 0x31, 0x32, 0x37, 0x2e, 0x30, 0x2e, - 0x30, 0x2e, 0x31, 0x3a, 0x39, 0x31, 0x30, 0x30, 0x22, 0x04, 0x2f, 0x61, 0x70, 0x69, 0x2a, 0x01, - 0x01, 0x72, 0x25, 0x0a, 0x23, 0x4f, 0x70, 0x74, 0x69, 0x6d, 0x75, 0x73, 0x20, 0x52, 0x65, 0x73, - 0x6f, 0x75, 0x72, 0x63, 0x65, 0x20, 0x4d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, - 0x20, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x2f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x2d, 0x61, 0x70, 0x70, 0x6c, 0x79, + 0x3a, 0x01, 0x2a, 0x12, 0xe4, 0x01, 0x0a, 0x15, 0x47, 0x65, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, + 0x72, 0x63, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x6c, 0x6f, 0x67, 0x73, 0x12, 0x3e, 0x2e, + 0x67, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x6e, 0x79, 0x2e, 0x6f, 0x70, 0x74, 0x69, + 0x6d, 0x75, 0x73, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, + 0x2e, 0x47, 0x65, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x43, 0x68, 0x61, 0x6e, + 0x67, 0x65, 0x6c, 0x6f, 0x67, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x3f, 0x2e, + 0x67, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x6e, 0x79, 0x2e, 0x6f, 0x70, 0x74, 0x69, + 0x6d, 0x75, 0x73, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, + 0x2e, 0x47, 0x65, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x43, 0x68, 0x61, 0x6e, + 0x67, 0x65, 0x6c, 0x6f, 0x67, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x4a, + 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x44, 0x12, 0x42, 0x2f, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, + 0x2f, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x2f, 0x7b, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, + 0x74, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x7d, 0x2f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, + 0x2f, 0x7b, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x7d, + 0x2f, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x6c, 0x6f, 0x67, 0x42, 0xa4, 0x01, 0x0a, 0x1e, 0x63, + 0x6f, 0x6d, 0x2e, 0x67, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x6e, 0x79, 0x2e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x6e, 0x2e, 0x6f, 0x70, 0x74, 0x69, 0x6d, 0x75, 0x73, 0x42, 0x16, 0x52, + 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x4d, 0x61, + 0x6e, 0x61, 0x67, 0x65, 0x72, 0x50, 0x01, 0x5a, 0x1e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, + 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x6f, 0x74, 0x6f, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x6e, 0x2f, + 0x6f, 0x70, 0x74, 0x69, 0x6d, 0x75, 0x73, 0x92, 0x41, 0x47, 0x12, 0x05, 0x32, 0x03, 0x30, 0x2e, + 0x31, 0x1a, 0x0e, 0x31, 0x32, 0x37, 0x2e, 0x30, 0x2e, 0x30, 0x2e, 0x31, 0x3a, 0x39, 0x31, 0x30, + 0x30, 0x22, 0x04, 0x2f, 0x61, 0x70, 0x69, 0x2a, 0x01, 0x01, 0x72, 0x25, 0x0a, 0x23, 0x4f, 0x70, + 0x74, 0x69, 0x6d, 0x75, 0x73, 0x20, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x20, 0x4d, + 0x61, 0x6e, 0x61, 0x67, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x20, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, + 0x65, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -1753,7 +2016,7 @@ func file_gotocompany_optimus_core_v1beta1_resource_proto_rawDescGZIP() []byte { return file_gotocompany_optimus_core_v1beta1_resource_proto_rawDescData } -var file_gotocompany_optimus_core_v1beta1_resource_proto_msgTypes = make([]protoimpl.MessageInfo, 23) +var file_gotocompany_optimus_core_v1beta1_resource_proto_msgTypes = make([]protoimpl.MessageInfo, 27) var file_gotocompany_optimus_core_v1beta1_resource_proto_goTypes = []interface{}{ (*DeployResourceSpecificationRequest)(nil), // 0: gotocompany.optimus.core.v1beta1.DeployResourceSpecificationRequest (*DeployResourceSpecificationResponse)(nil), // 1: gotocompany.optimus.core.v1beta1.DeployResourceSpecificationResponse @@ -1775,48 +2038,56 @@ var file_gotocompany_optimus_core_v1beta1_resource_proto_goTypes = []interface{} (*ApplyResourcesResponse)(nil), // 17: gotocompany.optimus.core.v1beta1.ApplyResourcesResponse (*DeleteResourceRequest)(nil), // 18: gotocompany.optimus.core.v1beta1.DeleteResourceRequest (*DeleteResourceResponse)(nil), // 19: gotocompany.optimus.core.v1beta1.DeleteResourceResponse - nil, // 20: gotocompany.optimus.core.v1beta1.ResourceSpecification.AssetsEntry - nil, // 21: gotocompany.optimus.core.v1beta1.ResourceSpecification.LabelsEntry - (*ApplyResourcesResponse_ResourceStatus)(nil), // 22: gotocompany.optimus.core.v1beta1.ApplyResourcesResponse.ResourceStatus - (*Log)(nil), // 23: gotocompany.optimus.core.v1beta1.Log - (*structpb.Struct)(nil), // 24: google.protobuf.Struct + (*GetResourceChangelogsRequest)(nil), // 20: gotocompany.optimus.core.v1beta1.GetResourceChangelogsRequest + (*ResourceChange)(nil), // 21: gotocompany.optimus.core.v1beta1.ResourceChange + (*ResourceChangelog)(nil), // 22: gotocompany.optimus.core.v1beta1.ResourceChangelog + (*GetResourceChangelogsResponse)(nil), // 23: gotocompany.optimus.core.v1beta1.GetResourceChangelogsResponse + nil, // 24: gotocompany.optimus.core.v1beta1.ResourceSpecification.AssetsEntry + nil, // 25: gotocompany.optimus.core.v1beta1.ResourceSpecification.LabelsEntry + (*ApplyResourcesResponse_ResourceStatus)(nil), // 26: gotocompany.optimus.core.v1beta1.ApplyResourcesResponse.ResourceStatus + (*Log)(nil), // 27: gotocompany.optimus.core.v1beta1.Log + (*structpb.Struct)(nil), // 28: google.protobuf.Struct } var file_gotocompany_optimus_core_v1beta1_resource_proto_depIdxs = []int32{ 13, // 0: gotocompany.optimus.core.v1beta1.DeployResourceSpecificationRequest.resources:type_name -> gotocompany.optimus.core.v1beta1.ResourceSpecification - 23, // 1: gotocompany.optimus.core.v1beta1.DeployResourceSpecificationResponse.log_status:type_name -> gotocompany.optimus.core.v1beta1.Log + 27, // 1: gotocompany.optimus.core.v1beta1.DeployResourceSpecificationResponse.log_status:type_name -> gotocompany.optimus.core.v1beta1.Log 13, // 2: gotocompany.optimus.core.v1beta1.ListResourceSpecificationResponse.resources:type_name -> gotocompany.optimus.core.v1beta1.ResourceSpecification 13, // 3: gotocompany.optimus.core.v1beta1.CreateResourceRequest.resource:type_name -> gotocompany.optimus.core.v1beta1.ResourceSpecification 13, // 4: gotocompany.optimus.core.v1beta1.ReadResourceResponse.resource:type_name -> gotocompany.optimus.core.v1beta1.ResourceSpecification 13, // 5: gotocompany.optimus.core.v1beta1.UpdateResourceRequest.resource:type_name -> gotocompany.optimus.core.v1beta1.ResourceSpecification 13, // 6: gotocompany.optimus.core.v1beta1.UpsertResourceRequest.resources:type_name -> gotocompany.optimus.core.v1beta1.ResourceSpecification 12, // 7: gotocompany.optimus.core.v1beta1.UpsertResourceResponse.results:type_name -> gotocompany.optimus.core.v1beta1.ResourceStatus - 24, // 8: gotocompany.optimus.core.v1beta1.ResourceSpecification.spec:type_name -> google.protobuf.Struct - 20, // 9: gotocompany.optimus.core.v1beta1.ResourceSpecification.assets:type_name -> gotocompany.optimus.core.v1beta1.ResourceSpecification.AssetsEntry - 21, // 10: gotocompany.optimus.core.v1beta1.ResourceSpecification.labels:type_name -> gotocompany.optimus.core.v1beta1.ResourceSpecification.LabelsEntry - 22, // 11: gotocompany.optimus.core.v1beta1.ApplyResourcesResponse.statuses:type_name -> gotocompany.optimus.core.v1beta1.ApplyResourcesResponse.ResourceStatus - 0, // 12: gotocompany.optimus.core.v1beta1.ResourceService.DeployResourceSpecification:input_type -> gotocompany.optimus.core.v1beta1.DeployResourceSpecificationRequest - 2, // 13: gotocompany.optimus.core.v1beta1.ResourceService.ListResourceSpecification:input_type -> gotocompany.optimus.core.v1beta1.ListResourceSpecificationRequest - 4, // 14: gotocompany.optimus.core.v1beta1.ResourceService.CreateResource:input_type -> gotocompany.optimus.core.v1beta1.CreateResourceRequest - 6, // 15: gotocompany.optimus.core.v1beta1.ResourceService.ReadResource:input_type -> gotocompany.optimus.core.v1beta1.ReadResourceRequest - 8, // 16: gotocompany.optimus.core.v1beta1.ResourceService.UpdateResource:input_type -> gotocompany.optimus.core.v1beta1.UpdateResourceRequest - 10, // 17: gotocompany.optimus.core.v1beta1.ResourceService.UpsertResource:input_type -> gotocompany.optimus.core.v1beta1.UpsertResourceRequest - 18, // 18: gotocompany.optimus.core.v1beta1.ResourceService.DeleteResource:input_type -> gotocompany.optimus.core.v1beta1.DeleteResourceRequest - 14, // 19: gotocompany.optimus.core.v1beta1.ResourceService.ChangeResourceNamespace:input_type -> gotocompany.optimus.core.v1beta1.ChangeResourceNamespaceRequest - 16, // 20: gotocompany.optimus.core.v1beta1.ResourceService.ApplyResources:input_type -> gotocompany.optimus.core.v1beta1.ApplyResourcesRequest - 1, // 21: gotocompany.optimus.core.v1beta1.ResourceService.DeployResourceSpecification:output_type -> gotocompany.optimus.core.v1beta1.DeployResourceSpecificationResponse - 3, // 22: gotocompany.optimus.core.v1beta1.ResourceService.ListResourceSpecification:output_type -> gotocompany.optimus.core.v1beta1.ListResourceSpecificationResponse - 5, // 23: gotocompany.optimus.core.v1beta1.ResourceService.CreateResource:output_type -> gotocompany.optimus.core.v1beta1.CreateResourceResponse - 7, // 24: gotocompany.optimus.core.v1beta1.ResourceService.ReadResource:output_type -> gotocompany.optimus.core.v1beta1.ReadResourceResponse - 9, // 25: gotocompany.optimus.core.v1beta1.ResourceService.UpdateResource:output_type -> gotocompany.optimus.core.v1beta1.UpdateResourceResponse - 11, // 26: gotocompany.optimus.core.v1beta1.ResourceService.UpsertResource:output_type -> gotocompany.optimus.core.v1beta1.UpsertResourceResponse - 19, // 27: gotocompany.optimus.core.v1beta1.ResourceService.DeleteResource:output_type -> gotocompany.optimus.core.v1beta1.DeleteResourceResponse - 15, // 28: gotocompany.optimus.core.v1beta1.ResourceService.ChangeResourceNamespace:output_type -> gotocompany.optimus.core.v1beta1.ChangeResourceNamespaceResponse - 17, // 29: gotocompany.optimus.core.v1beta1.ResourceService.ApplyResources:output_type -> gotocompany.optimus.core.v1beta1.ApplyResourcesResponse - 21, // [21:30] is the sub-list for method output_type - 12, // [12:21] is the sub-list for method input_type - 12, // [12:12] is the sub-list for extension type_name - 12, // [12:12] is the sub-list for extension extendee - 0, // [0:12] is the sub-list for field type_name + 28, // 8: gotocompany.optimus.core.v1beta1.ResourceSpecification.spec:type_name -> google.protobuf.Struct + 24, // 9: gotocompany.optimus.core.v1beta1.ResourceSpecification.assets:type_name -> gotocompany.optimus.core.v1beta1.ResourceSpecification.AssetsEntry + 25, // 10: gotocompany.optimus.core.v1beta1.ResourceSpecification.labels:type_name -> gotocompany.optimus.core.v1beta1.ResourceSpecification.LabelsEntry + 26, // 11: gotocompany.optimus.core.v1beta1.ApplyResourcesResponse.statuses:type_name -> gotocompany.optimus.core.v1beta1.ApplyResourcesResponse.ResourceStatus + 21, // 12: gotocompany.optimus.core.v1beta1.ResourceChangelog.change:type_name -> gotocompany.optimus.core.v1beta1.ResourceChange + 22, // 13: gotocompany.optimus.core.v1beta1.GetResourceChangelogsResponse.history:type_name -> gotocompany.optimus.core.v1beta1.ResourceChangelog + 0, // 14: gotocompany.optimus.core.v1beta1.ResourceService.DeployResourceSpecification:input_type -> gotocompany.optimus.core.v1beta1.DeployResourceSpecificationRequest + 2, // 15: gotocompany.optimus.core.v1beta1.ResourceService.ListResourceSpecification:input_type -> gotocompany.optimus.core.v1beta1.ListResourceSpecificationRequest + 4, // 16: gotocompany.optimus.core.v1beta1.ResourceService.CreateResource:input_type -> gotocompany.optimus.core.v1beta1.CreateResourceRequest + 6, // 17: gotocompany.optimus.core.v1beta1.ResourceService.ReadResource:input_type -> gotocompany.optimus.core.v1beta1.ReadResourceRequest + 8, // 18: gotocompany.optimus.core.v1beta1.ResourceService.UpdateResource:input_type -> gotocompany.optimus.core.v1beta1.UpdateResourceRequest + 10, // 19: gotocompany.optimus.core.v1beta1.ResourceService.UpsertResource:input_type -> gotocompany.optimus.core.v1beta1.UpsertResourceRequest + 18, // 20: gotocompany.optimus.core.v1beta1.ResourceService.DeleteResource:input_type -> gotocompany.optimus.core.v1beta1.DeleteResourceRequest + 14, // 21: gotocompany.optimus.core.v1beta1.ResourceService.ChangeResourceNamespace:input_type -> gotocompany.optimus.core.v1beta1.ChangeResourceNamespaceRequest + 16, // 22: gotocompany.optimus.core.v1beta1.ResourceService.ApplyResources:input_type -> gotocompany.optimus.core.v1beta1.ApplyResourcesRequest + 20, // 23: gotocompany.optimus.core.v1beta1.ResourceService.GetResourceChangelogs:input_type -> gotocompany.optimus.core.v1beta1.GetResourceChangelogsRequest + 1, // 24: gotocompany.optimus.core.v1beta1.ResourceService.DeployResourceSpecification:output_type -> gotocompany.optimus.core.v1beta1.DeployResourceSpecificationResponse + 3, // 25: gotocompany.optimus.core.v1beta1.ResourceService.ListResourceSpecification:output_type -> gotocompany.optimus.core.v1beta1.ListResourceSpecificationResponse + 5, // 26: gotocompany.optimus.core.v1beta1.ResourceService.CreateResource:output_type -> gotocompany.optimus.core.v1beta1.CreateResourceResponse + 7, // 27: gotocompany.optimus.core.v1beta1.ResourceService.ReadResource:output_type -> gotocompany.optimus.core.v1beta1.ReadResourceResponse + 9, // 28: gotocompany.optimus.core.v1beta1.ResourceService.UpdateResource:output_type -> gotocompany.optimus.core.v1beta1.UpdateResourceResponse + 11, // 29: gotocompany.optimus.core.v1beta1.ResourceService.UpsertResource:output_type -> gotocompany.optimus.core.v1beta1.UpsertResourceResponse + 19, // 30: gotocompany.optimus.core.v1beta1.ResourceService.DeleteResource:output_type -> gotocompany.optimus.core.v1beta1.DeleteResourceResponse + 15, // 31: gotocompany.optimus.core.v1beta1.ResourceService.ChangeResourceNamespace:output_type -> gotocompany.optimus.core.v1beta1.ChangeResourceNamespaceResponse + 17, // 32: gotocompany.optimus.core.v1beta1.ResourceService.ApplyResources:output_type -> gotocompany.optimus.core.v1beta1.ApplyResourcesResponse + 23, // 33: gotocompany.optimus.core.v1beta1.ResourceService.GetResourceChangelogs:output_type -> gotocompany.optimus.core.v1beta1.GetResourceChangelogsResponse + 24, // [24:34] is the sub-list for method output_type + 14, // [14:24] is the sub-list for method input_type + 14, // [14:14] is the sub-list for extension type_name + 14, // [14:14] is the sub-list for extension extendee + 0, // [0:14] is the sub-list for field type_name } func init() { file_gotocompany_optimus_core_v1beta1_resource_proto_init() } @@ -2066,7 +2337,55 @@ func file_gotocompany_optimus_core_v1beta1_resource_proto_init() { return nil } } + file_gotocompany_optimus_core_v1beta1_resource_proto_msgTypes[20].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetResourceChangelogsRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_gotocompany_optimus_core_v1beta1_resource_proto_msgTypes[21].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ResourceChange); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } file_gotocompany_optimus_core_v1beta1_resource_proto_msgTypes[22].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ResourceChangelog); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_gotocompany_optimus_core_v1beta1_resource_proto_msgTypes[23].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetResourceChangelogsResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_gotocompany_optimus_core_v1beta1_resource_proto_msgTypes[26].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*ApplyResourcesResponse_ResourceStatus); i { case 0: return &v.state @@ -2085,7 +2404,7 @@ func file_gotocompany_optimus_core_v1beta1_resource_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_gotocompany_optimus_core_v1beta1_resource_proto_rawDesc, NumEnums: 0, - NumMessages: 23, + NumMessages: 27, NumExtensions: 0, NumServices: 1, }, diff --git a/protos/gotocompany/optimus/core/v1beta1/resource.pb.gw.go b/protos/gotocompany/optimus/core/v1beta1/resource.pb.gw.go index 159cb06ba9..724ad560a3 100644 --- a/protos/gotocompany/optimus/core/v1beta1/resource.pb.gw.go +++ b/protos/gotocompany/optimus/core/v1beta1/resource.pb.gw.go @@ -865,6 +865,78 @@ func local_request_ResourceService_ApplyResources_0(ctx context.Context, marshal } +func request_ResourceService_GetResourceChangelogs_0(ctx context.Context, marshaler runtime.Marshaler, client ResourceServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq GetResourceChangelogsRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["project_name"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "project_name") + } + + protoReq.ProjectName, err = runtime.String(val) + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "project_name", err) + } + + val, ok = pathParams["resource_name"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "resource_name") + } + + protoReq.ResourceName, err = runtime.String(val) + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "resource_name", err) + } + + msg, err := client.GetResourceChangelogs(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_ResourceService_GetResourceChangelogs_0(ctx context.Context, marshaler runtime.Marshaler, server ResourceServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq GetResourceChangelogsRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["project_name"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "project_name") + } + + protoReq.ProjectName, err = runtime.String(val) + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "project_name", err) + } + + val, ok = pathParams["resource_name"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "resource_name") + } + + protoReq.ResourceName, err = runtime.String(val) + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "resource_name", err) + } + + msg, err := server.GetResourceChangelogs(ctx, &protoReq) + return msg, metadata, err + +} + // RegisterResourceServiceHandlerServer registers the http handlers for service ResourceService to "mux". // UnaryRPC :call ResourceServiceServer directly. // StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906. @@ -1055,6 +1127,29 @@ func RegisterResourceServiceHandlerServer(ctx context.Context, mux *runtime.Serv }) + mux.Handle("GET", pattern_ResourceService_GetResourceChangelogs_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/gotocompany.optimus.core.v1beta1.ResourceService/GetResourceChangelogs", runtime.WithHTTPPathPattern("/v1beta1/project/{project_name}/resource/{resource_name}/changelog")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_ResourceService_GetResourceChangelogs_0(rctx, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_ResourceService_GetResourceChangelogs_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + return nil } @@ -1256,6 +1351,26 @@ func RegisterResourceServiceHandlerClient(ctx context.Context, mux *runtime.Serv }) + mux.Handle("GET", pattern_ResourceService_GetResourceChangelogs_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req, "/gotocompany.optimus.core.v1beta1.ResourceService/GetResourceChangelogs", runtime.WithHTTPPathPattern("/v1beta1/project/{project_name}/resource/{resource_name}/changelog")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_ResourceService_GetResourceChangelogs_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_ResourceService_GetResourceChangelogs_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + return nil } @@ -1275,6 +1390,8 @@ var ( pattern_ResourceService_ChangeResourceNamespace_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 1, 0, 4, 1, 5, 2, 2, 3}, []string{"v1beta1", "project", "project_name", "change-resource-namespace"}, "")) pattern_ResourceService_ApplyResources_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 1, 0, 4, 1, 5, 2, 2, 3, 1, 0, 4, 1, 5, 4, 2, 5, 1, 0, 4, 1, 5, 6, 2, 7}, []string{"v1beta1", "project", "project_name", "namespace", "namespace_name", "datastore", "datastore_name", "resources-apply"}, "")) + + pattern_ResourceService_GetResourceChangelogs_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 1, 0, 4, 1, 5, 2, 2, 3, 1, 0, 4, 1, 5, 4, 2, 5}, []string{"v1beta1", "project", "project_name", "resource", "resource_name", "changelog"}, "")) ) var ( @@ -1293,4 +1410,6 @@ var ( forward_ResourceService_ChangeResourceNamespace_0 = runtime.ForwardResponseMessage forward_ResourceService_ApplyResources_0 = runtime.ForwardResponseMessage + + forward_ResourceService_GetResourceChangelogs_0 = runtime.ForwardResponseMessage ) diff --git a/protos/gotocompany/optimus/core/v1beta1/resource.swagger.json b/protos/gotocompany/optimus/core/v1beta1/resource.swagger.json index 2a5ce1de08..47db41e854 100644 --- a/protos/gotocompany/optimus/core/v1beta1/resource.swagger.json +++ b/protos/gotocompany/optimus/core/v1beta1/resource.swagger.json @@ -444,6 +444,43 @@ "ResourceService" ] } + }, + "/v1beta1/project/{projectName}/resource/{resourceName}/changelog": { + "get": { + "summary": "GetResourceChangelogs get all the change logs for a specific resource", + "operationId": "ResourceService_GetResourceChangelogs", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/v1beta1GetResourceChangelogsResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "parameters": [ + { + "name": "projectName", + "in": "path", + "required": true, + "type": "string" + }, + { + "name": "resourceName", + "in": "path", + "required": true, + "type": "string" + } + ], + "tags": [ + "ResourceService" + ] + } } }, "definitions": { @@ -557,6 +594,17 @@ } } }, + "v1beta1GetResourceChangelogsResponse": { + "type": "object", + "properties": { + "history": { + "type": "array", + "items": { + "$ref": "#/definitions/v1beta1ResourceChangelog" + } + } + } + }, "v1beta1Level": { "type": "string", "enum": [ @@ -606,6 +654,34 @@ } } }, + "v1beta1ResourceChange": { + "type": "object", + "properties": { + "attributeName": { + "type": "string" + }, + "diff": { + "type": "string" + } + } + }, + "v1beta1ResourceChangelog": { + "type": "object", + "properties": { + "eventType": { + "type": "string" + }, + "timestamp": { + "type": "string" + }, + "change": { + "type": "array", + "items": { + "$ref": "#/definitions/v1beta1ResourceChange" + } + } + } + }, "v1beta1ResourceSpecification": { "type": "object", "properties": { diff --git a/protos/gotocompany/optimus/core/v1beta1/resource_grpc.pb.go b/protos/gotocompany/optimus/core/v1beta1/resource_grpc.pb.go index 8934b9265c..30ec2e4db4 100644 --- a/protos/gotocompany/optimus/core/v1beta1/resource_grpc.pb.go +++ b/protos/gotocompany/optimus/core/v1beta1/resource_grpc.pb.go @@ -42,6 +42,8 @@ type ResourceServiceClient interface { ChangeResourceNamespace(ctx context.Context, in *ChangeResourceNamespaceRequest, opts ...grpc.CallOption) (*ChangeResourceNamespaceResponse, error) // apply a resource from optimus to datastore ApplyResources(ctx context.Context, in *ApplyResourcesRequest, opts ...grpc.CallOption) (*ApplyResourcesResponse, error) + // GetResourceChangelogs get all the change logs for a specific resource + GetResourceChangelogs(ctx context.Context, in *GetResourceChangelogsRequest, opts ...grpc.CallOption) (*GetResourceChangelogsResponse, error) } type resourceServiceClient struct { @@ -155,6 +157,15 @@ func (c *resourceServiceClient) ApplyResources(ctx context.Context, in *ApplyRes return out, nil } +func (c *resourceServiceClient) GetResourceChangelogs(ctx context.Context, in *GetResourceChangelogsRequest, opts ...grpc.CallOption) (*GetResourceChangelogsResponse, error) { + out := new(GetResourceChangelogsResponse) + err := c.cc.Invoke(ctx, "/gotocompany.optimus.core.v1beta1.ResourceService/GetResourceChangelogs", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + // ResourceServiceServer is the server API for ResourceService service. // All implementations must embed UnimplementedResourceServiceServer // for forward compatibility @@ -179,6 +190,8 @@ type ResourceServiceServer interface { ChangeResourceNamespace(context.Context, *ChangeResourceNamespaceRequest) (*ChangeResourceNamespaceResponse, error) // apply a resource from optimus to datastore ApplyResources(context.Context, *ApplyResourcesRequest) (*ApplyResourcesResponse, error) + // GetResourceChangelogs get all the change logs for a specific resource + GetResourceChangelogs(context.Context, *GetResourceChangelogsRequest) (*GetResourceChangelogsResponse, error) mustEmbedUnimplementedResourceServiceServer() } @@ -213,6 +226,9 @@ func (UnimplementedResourceServiceServer) ChangeResourceNamespace(context.Contex func (UnimplementedResourceServiceServer) ApplyResources(context.Context, *ApplyResourcesRequest) (*ApplyResourcesResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method ApplyResources not implemented") } +func (UnimplementedResourceServiceServer) GetResourceChangelogs(context.Context, *GetResourceChangelogsRequest) (*GetResourceChangelogsResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetResourceChangelogs not implemented") +} func (UnimplementedResourceServiceServer) mustEmbedUnimplementedResourceServiceServer() {} // UnsafeResourceServiceServer may be embedded to opt out of forward compatibility for this service. @@ -396,6 +412,24 @@ func _ResourceService_ApplyResources_Handler(srv interface{}, ctx context.Contex return interceptor(ctx, in, info, handler) } +func _ResourceService_GetResourceChangelogs_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetResourceChangelogsRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ResourceServiceServer).GetResourceChangelogs(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/gotocompany.optimus.core.v1beta1.ResourceService/GetResourceChangelogs", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ResourceServiceServer).GetResourceChangelogs(ctx, req.(*GetResourceChangelogsRequest)) + } + return interceptor(ctx, in, info, handler) +} + // ResourceService_ServiceDesc is the grpc.ServiceDesc for ResourceService service. // It's only intended for direct use with grpc.RegisterService, // and not to be introspected or modified (even as a copy) @@ -435,6 +469,10 @@ var ResourceService_ServiceDesc = grpc.ServiceDesc{ MethodName: "ApplyResources", Handler: _ResourceService_ApplyResources_Handler, }, + { + MethodName: "GetResourceChangelogs", + Handler: _ResourceService_GetResourceChangelogs_Handler, + }, }, Streams: []grpc.StreamDesc{ { diff --git a/server/optimus.go b/server/optimus.go index 2d7be39da5..c2355f7a02 100644 --- a/server/optimus.go +++ b/server/optimus.go @@ -377,6 +377,7 @@ func (s *OptimusServer) setupHandlers() error { // Resource Bounded Context primaryResourceService := rService.NewResourceService(s.logger, resourceRepository, jJobService, resourceManager, s.eventHandler, jJobService) backupService := rService.NewBackupService(backupRepository, resourceRepository, resourceManager, s.logger) + resourceChangeLogService := rService.NewChangelogService(s.logger, resourceRepository) // Register datastore bqClientProvider := bqStore.NewClientProvider() @@ -389,7 +390,7 @@ func (s *OptimusServer) setupHandlers() error { pb.RegisterNamespaceServiceServer(s.grpcServer, tHandler.NewNamespaceHandler(s.logger, tNamespaceService)) // Resource Handler - pb.RegisterResourceServiceServer(s.grpcServer, rHandler.NewResourceHandler(s.logger, primaryResourceService)) + pb.RegisterResourceServiceServer(s.grpcServer, rHandler.NewResourceHandler(s.logger, primaryResourceService, resourceChangeLogService)) pb.RegisterJobRunServiceServer(s.grpcServer, schedulerHandler.NewJobRunHandler(s.logger, newJobRunService, eventsService))