From 25d636e2b5f704eddb073993ab0f9084eb46fbbe Mon Sep 17 00:00:00 2001 From: Adam Janikowski <12255597+ajanikow@users.noreply.github.com> Date: Mon, 26 Aug 2024 14:57:50 +0200 Subject: [PATCH] [Feature] ConfigV1 Integration Service (#1704) --- .golangci.yaml | 4 + CHANGELOG.md | 1 + Makefile | 14 +- README.md | 2 +- .../authentication/v1/implementation_test.go | 9 +- integrations/config/v1/config.go | 40 ++ integrations/config/v1/config_test.go | 80 +++ integrations/config/v1/consts.go | 25 + .../config/v1/definition/config.pb.go | 652 ++++++++++++++++++ .../config/v1/definition/config.proto | 106 +++ .../config/v1/definition/config_grpc.pb.go | 178 +++++ integrations/config/v1/file_details_test.go | 179 +++++ integrations/config/v1/impl.go | 197 ++++++ integrations/config/v1/impl_darwin.go | 80 +++ integrations/config/v1/impl_linux.go | 80 +++ integrations/config/v1/logger.go | 25 + integrations/config/v1/module_details_test.go | 192 ++++++ integrations/config/v1/module_test.go | 75 ++ integrations/config/v1/service_test.go | 46 ++ integrations/scheduler/v1/consts.go | 25 + pkg/integrations/authentication_v1.go | 2 +- pkg/integrations/authorization_v0.go | 2 +- pkg/integrations/config_v1.go | 77 +++ pkg/integrations/envoy_auth_v3.go | 2 +- pkg/integrations/register.go | 24 +- pkg/integrations/scheduler_v1.go | 4 +- pkg/integrations/shutdown_v1.go | 2 +- pkg/integrations/storage_v1.go | 4 +- pkg/ml/storage/consts.go | 23 + pkg/upgrade/upgrade_test.go | 4 +- pkg/util/checksum.go | 23 + pkg/util/dict.go | 29 +- pkg/util/registerer.go | 71 ++ pkg/util/strings/strings.go | 17 + pkg/util/svc/error.go | 21 +- pkg/util/tests/path.go | 95 +++ pkg/util/tests/tgrpc/grpc.go | 32 + 37 files changed, 2385 insertions(+), 57 deletions(-) create mode 100644 integrations/config/v1/config.go create mode 100644 integrations/config/v1/config_test.go create mode 100644 integrations/config/v1/consts.go create mode 100644 integrations/config/v1/definition/config.pb.go create mode 100644 integrations/config/v1/definition/config.proto create mode 100644 integrations/config/v1/definition/config_grpc.pb.go create mode 100644 integrations/config/v1/file_details_test.go create mode 100644 integrations/config/v1/impl.go create mode 100644 integrations/config/v1/impl_darwin.go create mode 100644 integrations/config/v1/impl_linux.go create mode 100644 integrations/config/v1/logger.go create mode 100644 integrations/config/v1/module_details_test.go create mode 100644 integrations/config/v1/module_test.go create mode 100644 integrations/config/v1/service_test.go create mode 100644 integrations/scheduler/v1/consts.go create mode 100644 pkg/integrations/config_v1.go create mode 100644 pkg/ml/storage/consts.go create mode 100644 pkg/util/registerer.go create mode 100644 pkg/util/tests/path.go diff --git a/.golangci.yaml b/.golangci.yaml index 406b638b5..a95470b9b 100644 --- a/.golangci.yaml +++ b/.golangci.yaml @@ -99,6 +99,10 @@ linters-settings: alias: pbAuthorizationV0 - pkg: github.com/arangodb/kube-arangodb/integrations/authorization/v0 alias: pbImplAuthorizationV0 + - pkg: github.com/arangodb/kube-arangodb/integrations/config/v1/definition + alias: pbConfigV1 + - pkg: github.com/arangodb/kube-arangodb/integrations/config/v1 + alias: pbImplConfigV1 - pkg: github.com/arangodb/kube-arangodb/integrations/shared/v1/definition alias: pbSharedV1 - pkg: github.com/arangodb/kube-arangodb/integrations/shared/v1 diff --git a/CHANGELOG.md b/CHANGELOG.md index ab2099914..af0c001b6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ - (Maintenance) Switch to ubuntu:24.04 base image - (Feature) Gateway Group for ArangoDeployment - (Feature) Gateway config loader +- (Feature) ConfigV1 Integration Service ## [1.2.42](https://github.com/arangodb/kube-arangodb/tree/1.2.42) (2024-07-23) - (Maintenance) Go 1.22.4 & Kubernetes 1.29.6 libraries diff --git a/Makefile b/Makefile index d33c24fc9..b576c01ec 100644 --- a/Makefile +++ b/Makefile @@ -726,19 +726,9 @@ manifest-verify-helm-ee: manifests-verify-env-reset .PHONY: run-unit-tests run-unit-tests: $(SOURCES) go test --count=1 --tags "$(GOBUILDTAGS)" $(TESTVERBOSEOPTIONS) \ - $(REPOPATH)/pkg/apis/shared/... \ - $(REPOPATH)/pkg/apis/backup/... \ - $(REPOPATH)/pkg/apis/deployment/... \ - $(REPOPATH)/pkg/apis/replication/... \ - $(REPOPATH)/pkg/apis/storage/... \ - $(REPOPATH)/pkg/apis/ml/... \ - $(REPOPATH)/pkg/deployment/... \ - $(REPOPATH)/pkg/storage/... \ - $(REPOPATH)/pkg/crd/... \ - $(REPOPATH)/pkg/util/... \ - $(REPOPATH)/pkg/generated/metric_descriptions/... \ + $(REPOPATH)/pkg/... \ $(REPOPATH)/cmd/... \ - $(REPOPATH)/pkg/handlers/... + $(REPOPATH)/integrations/... # Release building diff --git a/README.md b/README.md index f863b2e4e..f22305d20 100644 --- a/README.md +++ b/README.md @@ -182,7 +182,7 @@ Flags: --kubernetes.max-batch-size int Size of batch during objects read (default 256) --kubernetes.qps float32 Number of queries per second for k8s API (default 15) --log.format string Set log format. Allowed values: 'pretty', 'JSON'. If empty, default format is used (default "pretty") - --log.level stringArray Set log levels in format or =. Possible loggers: action, agency, api-server, assertion, backup-operator, chaos-monkey, crd, deployment, deployment-ci, deployment-reconcile, deployment-replication, deployment-resilience, deployment-resources, deployment-storage, deployment-storage-pc, deployment-storage-service, http, inspector, integrations, k8s-client, monitor, networking-route-operator, operator, operator-arangojob-handler, operator-v2, operator-v2-event, operator-v2-worker, panics, pod_compare, root, root-event-recorder, server, server-authentication (default [info]) + --log.level stringArray Set log levels in format or =. Possible loggers: action, agency, api-server, assertion, backup-operator, chaos-monkey, crd, deployment, deployment-ci, deployment-reconcile, deployment-replication, deployment-resilience, deployment-resources, deployment-storage, deployment-storage-pc, deployment-storage-service, http, inspector, integration-config-v1, integrations, k8s-client, monitor, networking-route-operator, operator, operator-arangojob-handler, operator-v2, operator-v2-event, operator-v2-worker, panics, pod_compare, root, root-event-recorder, server, server-authentication (default [info]) --log.sampling If true, operator will try to minimize duplication of logging events (default true) --memory-limit uint Define memory limit for hard shutdown and the dump of goroutines. Used for testing --metrics.excluded-prefixes stringArray List of the excluded metrics prefixes diff --git a/integrations/authentication/v1/implementation_test.go b/integrations/authentication/v1/implementation_test.go index 20b8271d3..0502cbd39 100644 --- a/integrations/authentication/v1/implementation_test.go +++ b/integrations/authentication/v1/implementation_test.go @@ -40,10 +40,11 @@ func Test_Basic(t *testing.T) { ctx, c := context.WithCancel(context.Background()) defer c() - s, err := newInternal(ctx, Configuration{ - Path: directory, - TTL: time.Duration(0), - }) + s, err := newInternal(ctx, NewConfiguration().With(func(c Configuration) Configuration { + c.Path = directory + c.TTL = 0 + return c + })) require.NoError(t, err) // Create token diff --git a/integrations/config/v1/config.go b/integrations/config/v1/config.go new file mode 100644 index 000000000..3497f380c --- /dev/null +++ b/integrations/config/v1/config.go @@ -0,0 +1,40 @@ +// +// DISCLAIMER +// +// Copyright 2024 ArangoDB GmbH, Cologne, Germany +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// Copyright holder is ArangoDB GmbH, Cologne, Germany +// + +package v1 + +type Config struct { + Modules ModuleDefinitions `json:"modules,omitempty"` +} + +func (c *Config) Init() { + for k := range c.Modules { + m := c.Modules[k] + m.Name = k + c.Modules[k] = m + } +} + +type ModuleDefinitions map[string]ModuleDefinition + +type ModuleDefinition struct { + Name string `json:"-"` + Path string `json:"path"` +} diff --git a/integrations/config/v1/config_test.go b/integrations/config/v1/config_test.go new file mode 100644 index 000000000..d4ce2da7b --- /dev/null +++ b/integrations/config/v1/config_test.go @@ -0,0 +1,80 @@ +// +// DISCLAIMER +// +// Copyright 2024 ArangoDB GmbH, Cologne, Germany +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// Copyright holder is ArangoDB GmbH, Cologne, Germany +// + +package v1 + +import ( + "fmt" + "os" + "testing" + + "github.com/stretchr/testify/require" +) + +func Test_ConfigCreation(t *testing.T) { + create := func(config Config) error { + _, err := New(config) + return err + } + + dir := t.TempDir() + + require.NoError(t, os.WriteFile(fmt.Sprintf("%s/file", dir), []byte{}, 0644)) + + require.EqualError(t, create(Config{}), "Requires at least 1 module") + + require.EqualError(t, create(Config{ + Modules: map[string]ModuleDefinition{ + "test": {}, + }, + }), "Path for module `test` cannot be empty") + + require.EqualError(t, create(Config{ + Modules: map[string]ModuleDefinition{ + "test": { + Path: "some/relative/path", + }, + }, + }), "Path `some/relative/path` for module `test` needs to be absolute") + + require.EqualError(t, create(Config{ + Modules: map[string]ModuleDefinition{ + "test": { + Path: fmt.Sprintf("%s/non-existent", dir), + }, + }, + }), fmt.Sprintf("Path `%s/non-existent` for module `test` does not exists", dir)) + + require.EqualError(t, create(Config{ + Modules: map[string]ModuleDefinition{ + "test": { + Path: fmt.Sprintf("%s/file", dir), + }, + }, + }), fmt.Sprintf("Path `%s/file` for module `test` is not a directory", dir)) + + require.NoError(t, create(Config{ + Modules: map[string]ModuleDefinition{ + "test": { + Path: dir, + }, + }, + })) +} diff --git a/integrations/config/v1/consts.go b/integrations/config/v1/consts.go new file mode 100644 index 000000000..f936a513e --- /dev/null +++ b/integrations/config/v1/consts.go @@ -0,0 +1,25 @@ +// +// DISCLAIMER +// +// Copyright 2024 ArangoDB GmbH, Cologne, Germany +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// Copyright holder is ArangoDB GmbH, Cologne, Germany +// + +package v1 + +const ( + Name = "config.v1" +) diff --git a/integrations/config/v1/definition/config.pb.go b/integrations/config/v1/definition/config.pb.go new file mode 100644 index 000000000..eb38dd889 --- /dev/null +++ b/integrations/config/v1/definition/config.pb.go @@ -0,0 +1,652 @@ +// +// DISCLAIMER +// +// Copyright 2024 ArangoDB GmbH, Cologne, Germany +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// Copyright holder is ArangoDB GmbH, Cologne, Germany +// + +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.26.0 +// protoc v3.21.1 +// source: integrations/config/v1/definition/config.proto + +package definition + +import ( + definition "github.com/arangodb/kube-arangodb/integrations/shared/v1/definition" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + timestamppb "google.golang.org/protobuf/types/known/timestamppb" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +// ConfigV1 Modules Call Response +type ConfigV1ModulesResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // List of registered modules + Modules []string `protobuf:"bytes,1,rep,name=modules,proto3" json:"modules,omitempty"` +} + +func (x *ConfigV1ModulesResponse) Reset() { + *x = ConfigV1ModulesResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_integrations_config_v1_definition_config_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ConfigV1ModulesResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ConfigV1ModulesResponse) ProtoMessage() {} + +func (x *ConfigV1ModulesResponse) ProtoReflect() protoreflect.Message { + mi := &file_integrations_config_v1_definition_config_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ConfigV1ModulesResponse.ProtoReflect.Descriptor instead. +func (*ConfigV1ModulesResponse) Descriptor() ([]byte, []int) { + return file_integrations_config_v1_definition_config_proto_rawDescGZIP(), []int{0} +} + +func (x *ConfigV1ModulesResponse) GetModules() []string { + if x != nil { + return x.Modules + } + return nil +} + +// ConfigV1 ModuleDetails Call Request +type ConfigV1ModuleDetailsRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Name of the module + Module string `protobuf:"bytes,1,opt,name=module,proto3" json:"module,omitempty"` + // Define if checksum of module should be returned + Checksum *bool `protobuf:"varint,2,opt,name=checksum,proto3,oneof" json:"checksum,omitempty"` +} + +func (x *ConfigV1ModuleDetailsRequest) Reset() { + *x = ConfigV1ModuleDetailsRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_integrations_config_v1_definition_config_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ConfigV1ModuleDetailsRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ConfigV1ModuleDetailsRequest) ProtoMessage() {} + +func (x *ConfigV1ModuleDetailsRequest) ProtoReflect() protoreflect.Message { + mi := &file_integrations_config_v1_definition_config_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ConfigV1ModuleDetailsRequest.ProtoReflect.Descriptor instead. +func (*ConfigV1ModuleDetailsRequest) Descriptor() ([]byte, []int) { + return file_integrations_config_v1_definition_config_proto_rawDescGZIP(), []int{1} +} + +func (x *ConfigV1ModuleDetailsRequest) GetModule() string { + if x != nil { + return x.Module + } + return "" +} + +func (x *ConfigV1ModuleDetailsRequest) GetChecksum() bool { + if x != nil && x.Checksum != nil { + return *x.Checksum + } + return false +} + +// ConfigV1 ModuleDetails Call Response +type ConfigV1ModuleDetailsResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Name of the module + Module string `protobuf:"bytes,1,opt,name=module,proto3" json:"module,omitempty"` + // List of the files + Files []*ConfigV1File `protobuf:"bytes,2,rep,name=files,proto3" json:"files,omitempty"` + // Sha256Sum of the module (if requested) + Checksum *string `protobuf:"bytes,3,opt,name=checksum,proto3,oneof" json:"checksum,omitempty"` +} + +func (x *ConfigV1ModuleDetailsResponse) Reset() { + *x = ConfigV1ModuleDetailsResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_integrations_config_v1_definition_config_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ConfigV1ModuleDetailsResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ConfigV1ModuleDetailsResponse) ProtoMessage() {} + +func (x *ConfigV1ModuleDetailsResponse) ProtoReflect() protoreflect.Message { + mi := &file_integrations_config_v1_definition_config_proto_msgTypes[2] + 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 ConfigV1ModuleDetailsResponse.ProtoReflect.Descriptor instead. +func (*ConfigV1ModuleDetailsResponse) Descriptor() ([]byte, []int) { + return file_integrations_config_v1_definition_config_proto_rawDescGZIP(), []int{2} +} + +func (x *ConfigV1ModuleDetailsResponse) GetModule() string { + if x != nil { + return x.Module + } + return "" +} + +func (x *ConfigV1ModuleDetailsResponse) GetFiles() []*ConfigV1File { + if x != nil { + return x.Files + } + return nil +} + +func (x *ConfigV1ModuleDetailsResponse) GetChecksum() string { + if x != nil && x.Checksum != nil { + return *x.Checksum + } + return "" +} + +// ConfigV1 ModuleDetails Call Request +type ConfigV1FileDetailsRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Name of the module + Module string `protobuf:"bytes,1,opt,name=module,proto3" json:"module,omitempty"` + // Name of the file + File string `protobuf:"bytes,2,opt,name=file,proto3" json:"file,omitempty"` + // Define if checksum of module should be returned + Checksum *bool `protobuf:"varint,3,opt,name=checksum,proto3,oneof" json:"checksum,omitempty"` +} + +func (x *ConfigV1FileDetailsRequest) Reset() { + *x = ConfigV1FileDetailsRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_integrations_config_v1_definition_config_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ConfigV1FileDetailsRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ConfigV1FileDetailsRequest) ProtoMessage() {} + +func (x *ConfigV1FileDetailsRequest) ProtoReflect() protoreflect.Message { + mi := &file_integrations_config_v1_definition_config_proto_msgTypes[3] + 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 ConfigV1FileDetailsRequest.ProtoReflect.Descriptor instead. +func (*ConfigV1FileDetailsRequest) Descriptor() ([]byte, []int) { + return file_integrations_config_v1_definition_config_proto_rawDescGZIP(), []int{3} +} + +func (x *ConfigV1FileDetailsRequest) GetModule() string { + if x != nil { + return x.Module + } + return "" +} + +func (x *ConfigV1FileDetailsRequest) GetFile() string { + if x != nil { + return x.File + } + return "" +} + +func (x *ConfigV1FileDetailsRequest) GetChecksum() bool { + if x != nil && x.Checksum != nil { + return *x.Checksum + } + return false +} + +// ConfigV1 ModuleDetails Call Response +type ConfigV1FileDetailsResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Name of the module + Module string `protobuf:"bytes,1,opt,name=module,proto3" json:"module,omitempty"` + // Spec of the file + File *ConfigV1File `protobuf:"bytes,3,opt,name=file,proto3" json:"file,omitempty"` +} + +func (x *ConfigV1FileDetailsResponse) Reset() { + *x = ConfigV1FileDetailsResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_integrations_config_v1_definition_config_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ConfigV1FileDetailsResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ConfigV1FileDetailsResponse) ProtoMessage() {} + +func (x *ConfigV1FileDetailsResponse) ProtoReflect() protoreflect.Message { + mi := &file_integrations_config_v1_definition_config_proto_msgTypes[4] + 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 ConfigV1FileDetailsResponse.ProtoReflect.Descriptor instead. +func (*ConfigV1FileDetailsResponse) Descriptor() ([]byte, []int) { + return file_integrations_config_v1_definition_config_proto_rawDescGZIP(), []int{4} +} + +func (x *ConfigV1FileDetailsResponse) GetModule() string { + if x != nil { + return x.Module + } + return "" +} + +func (x *ConfigV1FileDetailsResponse) GetFile() *ConfigV1File { + if x != nil { + return x.File + } + return nil +} + +// Information about configuration file +type ConfigV1File struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Relative path of the config file + Path string `protobuf:"bytes,1,opt,name=path,proto3" json:"path,omitempty"` + // Size of the config file in bytes + Size int64 `protobuf:"varint,2,opt,name=size,proto3" json:"size,omitempty"` + // Sha256Sum of the file (if requested) + Checksum *string `protobuf:"bytes,3,opt,name=checksum,proto3,oneof" json:"checksum,omitempty"` + // Timestamp of the file creation + CreatedAt *timestamppb.Timestamp `protobuf:"bytes,4,opt,name=created_at,json=createdAt,proto3" json:"created_at,omitempty"` + // Timestamp of the file update + UpdatedAt *timestamppb.Timestamp `protobuf:"bytes,5,opt,name=updated_at,json=updatedAt,proto3" json:"updated_at,omitempty"` +} + +func (x *ConfigV1File) Reset() { + *x = ConfigV1File{} + if protoimpl.UnsafeEnabled { + mi := &file_integrations_config_v1_definition_config_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ConfigV1File) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ConfigV1File) ProtoMessage() {} + +func (x *ConfigV1File) ProtoReflect() protoreflect.Message { + mi := &file_integrations_config_v1_definition_config_proto_msgTypes[5] + 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 ConfigV1File.ProtoReflect.Descriptor instead. +func (*ConfigV1File) Descriptor() ([]byte, []int) { + return file_integrations_config_v1_definition_config_proto_rawDescGZIP(), []int{5} +} + +func (x *ConfigV1File) GetPath() string { + if x != nil { + return x.Path + } + return "" +} + +func (x *ConfigV1File) GetSize() int64 { + if x != nil { + return x.Size + } + return 0 +} + +func (x *ConfigV1File) GetChecksum() string { + if x != nil && x.Checksum != nil { + return *x.Checksum + } + return "" +} + +func (x *ConfigV1File) GetCreatedAt() *timestamppb.Timestamp { + if x != nil { + return x.CreatedAt + } + return nil +} + +func (x *ConfigV1File) GetUpdatedAt() *timestamppb.Timestamp { + if x != nil { + return x.UpdatedAt + } + return nil +} + +var File_integrations_config_v1_definition_config_proto protoreflect.FileDescriptor + +var file_integrations_config_v1_definition_config_proto_rawDesc = []byte{ + 0x0a, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2f, 0x63, + 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2f, 0x76, 0x31, 0x2f, 0x64, 0x65, 0x66, 0x69, 0x6e, 0x69, 0x74, + 0x69, 0x6f, 0x6e, 0x2f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x12, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x1a, 0x1f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, + 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, + 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x2d, 0x69, 0x6e, 0x74, 0x65, 0x67, + 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2f, 0x73, 0x68, 0x61, 0x72, 0x65, 0x64, 0x2f, 0x76, + 0x31, 0x2f, 0x64, 0x65, 0x66, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x65, 0x6d, 0x70, + 0x74, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x33, 0x0a, 0x17, 0x43, 0x6f, 0x6e, 0x66, + 0x69, 0x67, 0x56, 0x31, 0x4d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x73, 0x18, 0x01, + 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x73, 0x22, 0x64, 0x0a, + 0x1c, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x56, 0x31, 0x4d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x44, + 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x16, 0x0a, + 0x06, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x6d, + 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x12, 0x1f, 0x0a, 0x08, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x73, 0x75, + 0x6d, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x48, 0x00, 0x52, 0x08, 0x63, 0x68, 0x65, 0x63, 0x6b, + 0x73, 0x75, 0x6d, 0x88, 0x01, 0x01, 0x42, 0x0b, 0x0a, 0x09, 0x5f, 0x63, 0x68, 0x65, 0x63, 0x6b, + 0x73, 0x75, 0x6d, 0x22, 0x91, 0x01, 0x0a, 0x1d, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x56, 0x31, + 0x4d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x12, 0x2a, 0x0a, + 0x05, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x63, + 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x56, 0x31, 0x46, 0x69, + 0x6c, 0x65, 0x52, 0x05, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x12, 0x1f, 0x0a, 0x08, 0x63, 0x68, 0x65, + 0x63, 0x6b, 0x73, 0x75, 0x6d, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x08, 0x63, + 0x68, 0x65, 0x63, 0x6b, 0x73, 0x75, 0x6d, 0x88, 0x01, 0x01, 0x42, 0x0b, 0x0a, 0x09, 0x5f, 0x63, + 0x68, 0x65, 0x63, 0x6b, 0x73, 0x75, 0x6d, 0x22, 0x76, 0x0a, 0x1a, 0x43, 0x6f, 0x6e, 0x66, 0x69, + 0x67, 0x56, 0x31, 0x46, 0x69, 0x6c, 0x65, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x12, 0x12, 0x0a, + 0x04, 0x66, 0x69, 0x6c, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x66, 0x69, 0x6c, + 0x65, 0x12, 0x1f, 0x0a, 0x08, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x73, 0x75, 0x6d, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x08, 0x48, 0x00, 0x52, 0x08, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x73, 0x75, 0x6d, 0x88, + 0x01, 0x01, 0x42, 0x0b, 0x0a, 0x09, 0x5f, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x73, 0x75, 0x6d, 0x22, + 0x5f, 0x0a, 0x1b, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x56, 0x31, 0x46, 0x69, 0x6c, 0x65, 0x44, + 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x16, + 0x0a, 0x06, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, + 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x12, 0x28, 0x0a, 0x04, 0x66, 0x69, 0x6c, 0x65, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x43, 0x6f, + 0x6e, 0x66, 0x69, 0x67, 0x56, 0x31, 0x46, 0x69, 0x6c, 0x65, 0x52, 0x04, 0x66, 0x69, 0x6c, 0x65, + 0x22, 0xda, 0x01, 0x0a, 0x0c, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x56, 0x31, 0x46, 0x69, 0x6c, + 0x65, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, 0x74, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x04, 0x70, 0x61, 0x74, 0x68, 0x12, 0x12, 0x0a, 0x04, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x03, 0x52, 0x04, 0x73, 0x69, 0x7a, 0x65, 0x12, 0x1f, 0x0a, 0x08, 0x63, 0x68, 0x65, + 0x63, 0x6b, 0x73, 0x75, 0x6d, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x08, 0x63, + 0x68, 0x65, 0x63, 0x6b, 0x73, 0x75, 0x6d, 0x88, 0x01, 0x01, 0x12, 0x39, 0x0a, 0x0a, 0x63, 0x72, + 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, + 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, + 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x63, 0x72, 0x65, 0x61, + 0x74, 0x65, 0x64, 0x41, 0x74, 0x12, 0x39, 0x0a, 0x0a, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, + 0x5f, 0x61, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, + 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, + 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, + 0x42, 0x0b, 0x0a, 0x09, 0x5f, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x73, 0x75, 0x6d, 0x32, 0xfb, 0x01, + 0x0a, 0x08, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x56, 0x31, 0x12, 0x39, 0x0a, 0x07, 0x4d, 0x6f, + 0x64, 0x75, 0x6c, 0x65, 0x73, 0x12, 0x0d, 0x2e, 0x73, 0x68, 0x61, 0x72, 0x65, 0x64, 0x2e, 0x45, + 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x1f, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x43, 0x6f, + 0x6e, 0x66, 0x69, 0x67, 0x56, 0x31, 0x4d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x73, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x5c, 0x0a, 0x0d, 0x4d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x44, + 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x12, 0x24, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, + 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x56, 0x31, 0x4d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x44, 0x65, + 0x74, 0x61, 0x69, 0x6c, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x25, 0x2e, 0x63, + 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x56, 0x31, 0x4d, 0x6f, + 0x64, 0x75, 0x6c, 0x65, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x56, 0x0a, 0x0b, 0x46, 0x69, 0x6c, 0x65, 0x44, 0x65, 0x74, 0x61, 0x69, + 0x6c, 0x73, 0x12, 0x22, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x43, 0x6f, 0x6e, 0x66, + 0x69, 0x67, 0x56, 0x31, 0x46, 0x69, 0x6c, 0x65, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, + 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x56, 0x31, 0x46, 0x69, 0x6c, 0x65, 0x44, 0x65, 0x74, 0x61, + 0x69, 0x6c, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x45, 0x5a, 0x43, 0x67, + 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x61, 0x72, 0x61, 0x6e, 0x67, 0x6f, + 0x64, 0x62, 0x2f, 0x6b, 0x75, 0x62, 0x65, 0x2d, 0x61, 0x72, 0x61, 0x6e, 0x67, 0x6f, 0x64, 0x62, + 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2f, 0x63, 0x6f, + 0x6e, 0x66, 0x69, 0x67, 0x2f, 0x76, 0x31, 0x2f, 0x64, 0x65, 0x66, 0x69, 0x6e, 0x69, 0x74, 0x69, + 0x6f, 0x6e, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_integrations_config_v1_definition_config_proto_rawDescOnce sync.Once + file_integrations_config_v1_definition_config_proto_rawDescData = file_integrations_config_v1_definition_config_proto_rawDesc +) + +func file_integrations_config_v1_definition_config_proto_rawDescGZIP() []byte { + file_integrations_config_v1_definition_config_proto_rawDescOnce.Do(func() { + file_integrations_config_v1_definition_config_proto_rawDescData = protoimpl.X.CompressGZIP(file_integrations_config_v1_definition_config_proto_rawDescData) + }) + return file_integrations_config_v1_definition_config_proto_rawDescData +} + +var file_integrations_config_v1_definition_config_proto_msgTypes = make([]protoimpl.MessageInfo, 6) +var file_integrations_config_v1_definition_config_proto_goTypes = []interface{}{ + (*ConfigV1ModulesResponse)(nil), // 0: config.ConfigV1ModulesResponse + (*ConfigV1ModuleDetailsRequest)(nil), // 1: config.ConfigV1ModuleDetailsRequest + (*ConfigV1ModuleDetailsResponse)(nil), // 2: config.ConfigV1ModuleDetailsResponse + (*ConfigV1FileDetailsRequest)(nil), // 3: config.ConfigV1FileDetailsRequest + (*ConfigV1FileDetailsResponse)(nil), // 4: config.ConfigV1FileDetailsResponse + (*ConfigV1File)(nil), // 5: config.ConfigV1File + (*timestamppb.Timestamp)(nil), // 6: google.protobuf.Timestamp + (*definition.Empty)(nil), // 7: shared.Empty +} +var file_integrations_config_v1_definition_config_proto_depIdxs = []int32{ + 5, // 0: config.ConfigV1ModuleDetailsResponse.files:type_name -> config.ConfigV1File + 5, // 1: config.ConfigV1FileDetailsResponse.file:type_name -> config.ConfigV1File + 6, // 2: config.ConfigV1File.created_at:type_name -> google.protobuf.Timestamp + 6, // 3: config.ConfigV1File.updated_at:type_name -> google.protobuf.Timestamp + 7, // 4: config.ConfigV1.Modules:input_type -> shared.Empty + 1, // 5: config.ConfigV1.ModuleDetails:input_type -> config.ConfigV1ModuleDetailsRequest + 3, // 6: config.ConfigV1.FileDetails:input_type -> config.ConfigV1FileDetailsRequest + 0, // 7: config.ConfigV1.Modules:output_type -> config.ConfigV1ModulesResponse + 2, // 8: config.ConfigV1.ModuleDetails:output_type -> config.ConfigV1ModuleDetailsResponse + 4, // 9: config.ConfigV1.FileDetails:output_type -> config.ConfigV1FileDetailsResponse + 7, // [7:10] is the sub-list for method output_type + 4, // [4:7] is the sub-list for method input_type + 4, // [4:4] is the sub-list for extension type_name + 4, // [4:4] is the sub-list for extension extendee + 0, // [0:4] is the sub-list for field type_name +} + +func init() { file_integrations_config_v1_definition_config_proto_init() } +func file_integrations_config_v1_definition_config_proto_init() { + if File_integrations_config_v1_definition_config_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_integrations_config_v1_definition_config_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ConfigV1ModulesResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_integrations_config_v1_definition_config_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ConfigV1ModuleDetailsRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_integrations_config_v1_definition_config_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ConfigV1ModuleDetailsResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_integrations_config_v1_definition_config_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ConfigV1FileDetailsRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_integrations_config_v1_definition_config_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ConfigV1FileDetailsResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_integrations_config_v1_definition_config_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ConfigV1File); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + file_integrations_config_v1_definition_config_proto_msgTypes[1].OneofWrappers = []interface{}{} + file_integrations_config_v1_definition_config_proto_msgTypes[2].OneofWrappers = []interface{}{} + file_integrations_config_v1_definition_config_proto_msgTypes[3].OneofWrappers = []interface{}{} + file_integrations_config_v1_definition_config_proto_msgTypes[5].OneofWrappers = []interface{}{} + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_integrations_config_v1_definition_config_proto_rawDesc, + NumEnums: 0, + NumMessages: 6, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_integrations_config_v1_definition_config_proto_goTypes, + DependencyIndexes: file_integrations_config_v1_definition_config_proto_depIdxs, + MessageInfos: file_integrations_config_v1_definition_config_proto_msgTypes, + }.Build() + File_integrations_config_v1_definition_config_proto = out.File + file_integrations_config_v1_definition_config_proto_rawDesc = nil + file_integrations_config_v1_definition_config_proto_goTypes = nil + file_integrations_config_v1_definition_config_proto_depIdxs = nil +} diff --git a/integrations/config/v1/definition/config.proto b/integrations/config/v1/definition/config.proto new file mode 100644 index 000000000..72c280549 --- /dev/null +++ b/integrations/config/v1/definition/config.proto @@ -0,0 +1,106 @@ +// +// DISCLAIMER +// +// Copyright 2024 ArangoDB GmbH, Cologne, Germany +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// Copyright holder is ArangoDB GmbH, Cologne, Germany +// + +syntax = "proto3"; + +package config; + +import "google/protobuf/timestamp.proto"; + +import "integrations/shared/v1/definition/empty.proto"; + +option go_package = "github.com/arangodb/kube-arangodb/integrations/config/v1/definition"; + +// ConfigV1 Service implementation +service ConfigV1 { + rpc Modules(shared.Empty) returns (ConfigV1ModulesResponse); + rpc ModuleDetails(ConfigV1ModuleDetailsRequest) returns (ConfigV1ModuleDetailsResponse); + rpc FileDetails(ConfigV1FileDetailsRequest) returns (ConfigV1FileDetailsResponse); +} + +// Calls + +// ConfigV1 Modules Call Response +message ConfigV1ModulesResponse { + // List of registered modules + repeated string modules = 1; +} + +// ConfigV1 ModuleDetails Call Request +message ConfigV1ModuleDetailsRequest { + // Name of the module + string module = 1; + + // Define if checksum of module should be returned + optional bool checksum = 2; +} + +// ConfigV1 ModuleDetails Call Response +message ConfigV1ModuleDetailsResponse { + // Name of the module + string module = 1; + + // List of the files + repeated ConfigV1File files = 2; + + // Sha256Sum of the module (if requested) + optional string checksum = 3; +} + +// ConfigV1 ModuleDetails Call Request +message ConfigV1FileDetailsRequest { + // Name of the module + string module = 1; + + // Name of the file + string file = 2; + + // Define if checksum of module should be returned + optional bool checksum = 3; +} + +// ConfigV1 ModuleDetails Call Response +message ConfigV1FileDetailsResponse { + // Name of the module + string module = 1; + + // Spec of the file + ConfigV1File file = 3; +} + +// Types + +// Information about configuration file +message ConfigV1File { + // Relative path of the config file + string path = 1; + + // Size of the config file in bytes + int64 size = 2; + + // Sha256Sum of the file (if requested) + optional string checksum = 3; + + // Timestamp of the file creation + google.protobuf.Timestamp created_at = 4; + + // Timestamp of the file update + google.protobuf.Timestamp updated_at = 5; +} \ No newline at end of file diff --git a/integrations/config/v1/definition/config_grpc.pb.go b/integrations/config/v1/definition/config_grpc.pb.go new file mode 100644 index 000000000..61215d82f --- /dev/null +++ b/integrations/config/v1/definition/config_grpc.pb.go @@ -0,0 +1,178 @@ +// Code generated by protoc-gen-go-grpc. DO NOT EDIT. +// versions: +// - protoc-gen-go-grpc v1.2.0 +// - protoc v3.21.1 +// source: integrations/config/v1/definition/config.proto + +package definition + +import ( + context "context" + definition "github.com/arangodb/kube-arangodb/integrations/shared/v1/definition" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" +) + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +// Requires gRPC-Go v1.32.0 or later. +const _ = grpc.SupportPackageIsVersion7 + +// ConfigV1Client is the client API for ConfigV1 service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +type ConfigV1Client interface { + Modules(ctx context.Context, in *definition.Empty, opts ...grpc.CallOption) (*ConfigV1ModulesResponse, error) + ModuleDetails(ctx context.Context, in *ConfigV1ModuleDetailsRequest, opts ...grpc.CallOption) (*ConfigV1ModuleDetailsResponse, error) + FileDetails(ctx context.Context, in *ConfigV1FileDetailsRequest, opts ...grpc.CallOption) (*ConfigV1FileDetailsResponse, error) +} + +type configV1Client struct { + cc grpc.ClientConnInterface +} + +func NewConfigV1Client(cc grpc.ClientConnInterface) ConfigV1Client { + return &configV1Client{cc} +} + +func (c *configV1Client) Modules(ctx context.Context, in *definition.Empty, opts ...grpc.CallOption) (*ConfigV1ModulesResponse, error) { + out := new(ConfigV1ModulesResponse) + err := c.cc.Invoke(ctx, "/config.ConfigV1/Modules", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *configV1Client) ModuleDetails(ctx context.Context, in *ConfigV1ModuleDetailsRequest, opts ...grpc.CallOption) (*ConfigV1ModuleDetailsResponse, error) { + out := new(ConfigV1ModuleDetailsResponse) + err := c.cc.Invoke(ctx, "/config.ConfigV1/ModuleDetails", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *configV1Client) FileDetails(ctx context.Context, in *ConfigV1FileDetailsRequest, opts ...grpc.CallOption) (*ConfigV1FileDetailsResponse, error) { + out := new(ConfigV1FileDetailsResponse) + err := c.cc.Invoke(ctx, "/config.ConfigV1/FileDetails", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// ConfigV1Server is the server API for ConfigV1 service. +// All implementations must embed UnimplementedConfigV1Server +// for forward compatibility +type ConfigV1Server interface { + Modules(context.Context, *definition.Empty) (*ConfigV1ModulesResponse, error) + ModuleDetails(context.Context, *ConfigV1ModuleDetailsRequest) (*ConfigV1ModuleDetailsResponse, error) + FileDetails(context.Context, *ConfigV1FileDetailsRequest) (*ConfigV1FileDetailsResponse, error) + mustEmbedUnimplementedConfigV1Server() +} + +// UnimplementedConfigV1Server must be embedded to have forward compatible implementations. +type UnimplementedConfigV1Server struct { +} + +func (UnimplementedConfigV1Server) Modules(context.Context, *definition.Empty) (*ConfigV1ModulesResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method Modules not implemented") +} +func (UnimplementedConfigV1Server) ModuleDetails(context.Context, *ConfigV1ModuleDetailsRequest) (*ConfigV1ModuleDetailsResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method ModuleDetails not implemented") +} +func (UnimplementedConfigV1Server) FileDetails(context.Context, *ConfigV1FileDetailsRequest) (*ConfigV1FileDetailsResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method FileDetails not implemented") +} +func (UnimplementedConfigV1Server) mustEmbedUnimplementedConfigV1Server() {} + +// UnsafeConfigV1Server may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to ConfigV1Server will +// result in compilation errors. +type UnsafeConfigV1Server interface { + mustEmbedUnimplementedConfigV1Server() +} + +func RegisterConfigV1Server(s grpc.ServiceRegistrar, srv ConfigV1Server) { + s.RegisterService(&ConfigV1_ServiceDesc, srv) +} + +func _ConfigV1_Modules_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(definition.Empty) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ConfigV1Server).Modules(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/config.ConfigV1/Modules", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ConfigV1Server).Modules(ctx, req.(*definition.Empty)) + } + return interceptor(ctx, in, info, handler) +} + +func _ConfigV1_ModuleDetails_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ConfigV1ModuleDetailsRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ConfigV1Server).ModuleDetails(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/config.ConfigV1/ModuleDetails", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ConfigV1Server).ModuleDetails(ctx, req.(*ConfigV1ModuleDetailsRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _ConfigV1_FileDetails_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ConfigV1FileDetailsRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ConfigV1Server).FileDetails(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/config.ConfigV1/FileDetails", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ConfigV1Server).FileDetails(ctx, req.(*ConfigV1FileDetailsRequest)) + } + return interceptor(ctx, in, info, handler) +} + +// ConfigV1_ServiceDesc is the grpc.ServiceDesc for ConfigV1 service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var ConfigV1_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "config.ConfigV1", + HandlerType: (*ConfigV1Server)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "Modules", + Handler: _ConfigV1_Modules_Handler, + }, + { + MethodName: "ModuleDetails", + Handler: _ConfigV1_ModuleDetails_Handler, + }, + { + MethodName: "FileDetails", + Handler: _ConfigV1_FileDetails_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "integrations/config/v1/definition/config.proto", +} diff --git a/integrations/config/v1/file_details_test.go b/integrations/config/v1/file_details_test.go new file mode 100644 index 000000000..4d434697d --- /dev/null +++ b/integrations/config/v1/file_details_test.go @@ -0,0 +1,179 @@ +// +// DISCLAIMER +// +// Copyright 2024 ArangoDB GmbH, Cologne, Germany +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// Copyright holder is ArangoDB GmbH, Cologne, Germany +// + +package v1 + +import ( + "context" + "testing" + + "github.com/stretchr/testify/require" + "google.golang.org/grpc/codes" + + pbConfigV1 "github.com/arangodb/kube-arangodb/integrations/config/v1/definition" + "github.com/arangodb/kube-arangodb/pkg/util" + "github.com/arangodb/kube-arangodb/pkg/util/tests" + "github.com/arangodb/kube-arangodb/pkg/util/tests/tgrpc" +) + +func Test_Files_Details_Missing(t *testing.T) { + ctx, c := context.WithCancel(context.Background()) + defer c() + + dir := t.TempDir() + + client := Client(t, ctx, Config{ + Modules: ModuleDefinitions{ + "test": { + Path: dir, + }, + }, + }) + + require.NotNil(t, client) + + _, err := client.FileDetails(ctx, &pbConfigV1.ConfigV1FileDetailsRequest{ + Module: "test", + File: "non-existent", + }) + tgrpc.AsGRPCError(t, err).Code(t, codes.NotFound).Errorf(t, "File `non-existent` not found within module `test`") +} + +func Test_Files_Details_Empty(t *testing.T) { + ctx, c := context.WithCancel(context.Background()) + defer c() + + dir := t.TempDir() + + tests.NewFileGenerator(t, dir).FileR(t, "file", 0) + + client := Client(t, ctx, Config{ + Modules: ModuleDefinitions{ + "test": { + Path: dir, + }, + }, + }) + + require.NotNil(t, client) + + resp, err := client.FileDetails(ctx, &pbConfigV1.ConfigV1FileDetailsRequest{ + Module: "test", + File: "file", + }) + require.NoError(t, err) + require.Equal(t, "test", resp.GetModule()) + require.NotNil(t, resp.GetFile()) + require.Equal(t, "file", resp.GetFile().GetPath()) + require.EqualValues(t, 0, resp.GetFile().GetSize()) + require.Empty(t, resp.GetFile().GetChecksum()) +} + +func Test_Files_Details_Empty_WC(t *testing.T) { + ctx, c := context.WithCancel(context.Background()) + defer c() + + dir := t.TempDir() + + tests.NewFileGenerator(t, dir).FileR(t, "file", 0) + + client := Client(t, ctx, Config{ + Modules: ModuleDefinitions{ + "test": { + Path: dir, + }, + }, + }) + + require.NotNil(t, client) + + resp, err := client.FileDetails(ctx, &pbConfigV1.ConfigV1FileDetailsRequest{ + Module: "test", + File: "file", + Checksum: util.NewType(true), + }) + require.NoError(t, err) + require.Equal(t, "test", resp.GetModule()) + require.NotNil(t, resp.GetFile()) + require.Equal(t, "file", resp.GetFile().GetPath()) + require.EqualValues(t, 0, resp.GetFile().GetSize()) + require.Equal(t, "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", resp.GetFile().GetChecksum()) +} + +func Test_Files_Details_Data(t *testing.T) { + ctx, c := context.WithCancel(context.Background()) + defer c() + + dir := t.TempDir() + + tests.NewFileGenerator(t, dir).File(t, "file", []byte("DATA")) + + client := Client(t, ctx, Config{ + Modules: ModuleDefinitions{ + "test": { + Path: dir, + }, + }, + }) + + require.NotNil(t, client) + + resp, err := client.FileDetails(ctx, &pbConfigV1.ConfigV1FileDetailsRequest{ + Module: "test", + File: "file", + }) + require.NoError(t, err) + require.Equal(t, "test", resp.GetModule()) + require.NotNil(t, resp.GetFile()) + require.Equal(t, "file", resp.GetFile().GetPath()) + require.EqualValues(t, 4, resp.GetFile().GetSize()) + require.Empty(t, resp.GetFile().GetChecksum()) +} + +func Test_Files_Details_Data_WC(t *testing.T) { + ctx, c := context.WithCancel(context.Background()) + defer c() + + dir := t.TempDir() + + tests.NewFileGenerator(t, dir).File(t, "file", []byte("DATA")) + + client := Client(t, ctx, Config{ + Modules: ModuleDefinitions{ + "test": { + Path: dir, + }, + }, + }) + + require.NotNil(t, client) + + resp, err := client.FileDetails(ctx, &pbConfigV1.ConfigV1FileDetailsRequest{ + Module: "test", + File: "file", + Checksum: util.NewType(true), + }) + require.NoError(t, err) + require.Equal(t, "test", resp.GetModule()) + require.NotNil(t, resp.GetFile()) + require.Equal(t, "file", resp.GetFile().GetPath()) + require.EqualValues(t, 4, resp.GetFile().GetSize()) + require.Equal(t, "c97c29c7a71b392b437ee03fd17f09bb10b75e879466fc0eb757b2c4a78ac938", resp.GetFile().GetChecksum()) +} diff --git a/integrations/config/v1/impl.go b/integrations/config/v1/impl.go new file mode 100644 index 000000000..bad17c564 --- /dev/null +++ b/integrations/config/v1/impl.go @@ -0,0 +1,197 @@ +// +// DISCLAIMER +// +// Copyright 2024 ArangoDB GmbH, Cologne, Germany +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// Copyright holder is ArangoDB GmbH, Cologne, Germany +// + +package v1 + +import ( + "context" + "fmt" + "io/fs" + "os" + "path" + "path/filepath" + "sort" + "strings" + + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + + pbConfigV1 "github.com/arangodb/kube-arangodb/integrations/config/v1/definition" + pbSharedV1 "github.com/arangodb/kube-arangodb/integrations/shared/v1/definition" + "github.com/arangodb/kube-arangodb/pkg/util" + "github.com/arangodb/kube-arangodb/pkg/util/errors" + "github.com/arangodb/kube-arangodb/pkg/util/svc" +) + +func New(config Config) (svc.Handler, error) { + config.Init() + + if len(config.Modules) == 0 { + return nil, errors.Errorf("Requires at least 1 module") + } + + for module, moduleConfig := range config.Modules { + if moduleConfig.Path == "" { + return nil, errors.Errorf("Path for module `%s` cannot be empty", module) + } + + if !path.IsAbs(moduleConfig.Path) { + return nil, errors.Errorf("Path `%s` for module `%s` needs to be absolute", moduleConfig.Path, module) + } + + info, err := os.Stat(moduleConfig.Path) + if err != nil { + if errors.Is(err, os.ErrNotExist) { + return nil, errors.Errorf("Path `%s` for module `%s` does not exists", moduleConfig.Path, module) + } + + return nil, errors.Wrapf(err, "Path `%s` for module `%s` received unknown error", moduleConfig.Path, module) + } + + if !info.IsDir() { + return nil, errors.Errorf("Path `%s` for module `%s` is not a directory", moduleConfig.Path, module) + } + } + + return &impl{ + config: config, + }, nil +} + +var _ pbConfigV1.ConfigV1Server = &impl{} +var _ svc.Handler = &impl{} + +type impl struct { + pbConfigV1.UnsafeConfigV1Server + + config Config +} + +func (i *impl) Name() string { + return Name +} + +func (i *impl) Health() svc.HealthState { + return svc.Healthy +} + +func (i *impl) Register(registrar *grpc.Server) { + pbConfigV1.RegisterConfigV1Server(registrar, i) +} + +func (i *impl) Modules(ctx context.Context, empty *pbSharedV1.Empty) (*pbConfigV1.ConfigV1ModulesResponse, error) { + res := &pbConfigV1.ConfigV1ModulesResponse{} + + res.Modules = util.SortKeys(i.config.Modules) + + return res, nil +} + +func (i *impl) ModuleDetails(ctx context.Context, request *pbConfigV1.ConfigV1ModuleDetailsRequest) (*pbConfigV1.ConfigV1ModuleDetailsResponse, error) { + if request.GetModule() == "" { + return nil, status.Errorf(codes.InvalidArgument, "Module name cannot be empty") + } + + module, ok := i.config.Modules[request.GetModule()] + if !ok { + return nil, status.Errorf(codes.NotFound, "Module `%s` not found", request.GetModule()) + } + + var resp pbConfigV1.ConfigV1ModuleDetailsResponse + + resp.Module = request.GetModule() + + var files []*pbConfigV1.ConfigV1File + + if err := filepath.Walk(module.Path, func(p string, info fs.FileInfo, err error) error { + if err != nil { + return err + } + + if !info.Mode().IsRegular() { + return nil + } + + if !strings.HasPrefix(p, fmt.Sprintf("%s/", module.Path)) { + return nil + } + + f, err := i.fileDetails(module, strings.TrimPrefix(p, fmt.Sprintf("%s/", module.Path)), request.GetChecksum()) + if err != nil { + return err + } + + files = append(files, f) + + return nil + }); err != nil { + if gErr, ok := svc.AsGRPCErrorStatus(err); ok { + return nil, gErr + } + return nil, status.Errorf(codes.Internal, "Unable to list directory for module `%s`", request.GetModule()) + } + + sort.Slice(files, func(i, j int) bool { + return files[i].GetPath() < files[j].GetPath() + }) + + resp.Files = files + + if request.GetChecksum() { + checksums := make([]string, len(files)) + for id := range files { + checksums[id] = fmt.Sprintf("%s:%s", files[id].GetPath(), files[id].GetChecksum()) + } + + resp.Checksum = util.NewType(util.SHA256FromStringArray(checksums...)) + } + + return &resp, nil +} + +func (i *impl) FileDetails(ctx context.Context, request *pbConfigV1.ConfigV1FileDetailsRequest) (*pbConfigV1.ConfigV1FileDetailsResponse, error) { + if request.GetModule() == "" { + return nil, status.Errorf(codes.InvalidArgument, "Module name cannot be empty") + } + + module, ok := i.config.Modules[request.GetModule()] + if !ok { + return nil, status.Errorf(codes.NotFound, "Module `%s` not found", request.GetModule()) + } + + if request.GetFile() == "" { + return nil, status.Errorf(codes.InvalidArgument, "File name cannot be empty") + } + + if request.GetFile() == "" { + return nil, status.Errorf(codes.NotFound, "File name cannot be empty") + } + + f, err := i.fileDetails(module, request.GetFile(), request.GetChecksum()) + if err != nil { + return nil, err + } + + return &pbConfigV1.ConfigV1FileDetailsResponse{ + Module: request.GetModule(), + File: f, + }, nil +} diff --git a/integrations/config/v1/impl_darwin.go b/integrations/config/v1/impl_darwin.go new file mode 100644 index 000000000..6e265afa7 --- /dev/null +++ b/integrations/config/v1/impl_darwin.go @@ -0,0 +1,80 @@ +// +// DISCLAIMER +// +// Copyright 2024 ArangoDB GmbH, Cologne, Germany +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// Copyright holder is ArangoDB GmbH, Cologne, Germany +// + +package v1 + +import ( + "fmt" + "os" + "path" + "strings" + "syscall" + "time" + + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + "google.golang.org/protobuf/types/known/timestamppb" + + pbConfigV1 "github.com/arangodb/kube-arangodb/integrations/config/v1/definition" + "github.com/arangodb/kube-arangodb/pkg/util" + "github.com/arangodb/kube-arangodb/pkg/util/errors" +) + +func (i *impl) fileDetails(module ModuleDefinition, file string, checksum bool) (*pbConfigV1.ConfigV1File, error) { + expectedPath := path.Clean(path.Join(module.Path, file)) + + if !strings.HasPrefix(expectedPath, fmt.Sprintf("%s/", module.Path)) { + return nil, status.Errorf(codes.InvalidArgument, "File name cannot be empty") + } + + stat, err := os.Stat(expectedPath) + if err != nil { + if errors.Is(err, os.ErrNotExist) { + return nil, status.Errorf(codes.NotFound, "File `%s` not found within module `%s`", file, module.Name) + } + + logger.Err(err).Str("module", module.Name).Str("file", file).Str("real-path", expectedPath).Warn("Unable to get file") + return nil, status.Errorf(codes.Internal, "Unable to list directory for module `%s`", module.Name) + } + + finfo, ok := stat.Sys().(*syscall.Stat_t) + if !ok { + logger.Str("module", module.Name).Str("file", file).Str("real-path", expectedPath).Warn("Invalid Stat Pointer for file") + return nil, status.Errorf(codes.Internal, "Fetch of file `%s` within module `%s` failed", file, module.Name) + } + + var f pbConfigV1.ConfigV1File + + f.Path = strings.TrimPrefix(expectedPath, fmt.Sprintf("%s/", module.Path)) + f.Size = finfo.Size + f.CreatedAt = timestamppb.New(time.Unix(finfo.Ctimespec.Sec, finfo.Ctimespec.Nsec)) + f.UpdatedAt = timestamppb.New(time.Unix(finfo.Mtimespec.Sec, finfo.Mtimespec.Nsec)) + + if checksum { + c, err := util.SHA256FromFile(expectedPath) + if err != nil { + logger.Str("module", module.Name).Str("file", file).Str("real-path", expectedPath).Warn("Unable to get file checksum") + return nil, status.Errorf(codes.Internal, "Unable to calculate checksum of file `%s` within module `%s` failed", file, module.Name) + } + f.Checksum = util.NewType(c) + } + + return &f, nil +} diff --git a/integrations/config/v1/impl_linux.go b/integrations/config/v1/impl_linux.go new file mode 100644 index 000000000..cc5091702 --- /dev/null +++ b/integrations/config/v1/impl_linux.go @@ -0,0 +1,80 @@ +// +// DISCLAIMER +// +// Copyright 2024 ArangoDB GmbH, Cologne, Germany +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// Copyright holder is ArangoDB GmbH, Cologne, Germany +// + +package v1 + +import ( + "fmt" + "os" + "path" + "strings" + "syscall" + "time" + + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + "google.golang.org/protobuf/types/known/timestamppb" + + pbConfigV1 "github.com/arangodb/kube-arangodb/integrations/config/v1/definition" + "github.com/arangodb/kube-arangodb/pkg/util" + "github.com/arangodb/kube-arangodb/pkg/util/errors" +) + +func (i *impl) fileDetails(module ModuleDefinition, file string, checksum bool) (*pbConfigV1.ConfigV1File, error) { + expectedPath := path.Clean(path.Join(module.Path, file)) + + if !strings.HasPrefix(expectedPath, fmt.Sprintf("%s/", module.Path)) { + return nil, status.Errorf(codes.InvalidArgument, "File name cannot be empty") + } + + stat, err := os.Stat(expectedPath) + if err != nil { + if errors.Is(err, os.ErrNotExist) { + return nil, status.Errorf(codes.NotFound, "File `%s` not found within module `%s`", file, module.Name) + } + + logger.Err(err).Str("module", module.Name).Str("file", file).Str("real-path", expectedPath).Warn("Unable to get file") + return nil, status.Errorf(codes.Internal, "Unable to list directory for module `%s`", module.Name) + } + + finfo, ok := stat.Sys().(*syscall.Stat_t) + if !ok { + logger.Str("module", module.Name).Str("file", file).Str("real-path", expectedPath).Warn("Invalid Stat Pointer for file") + return nil, status.Errorf(codes.Internal, "Fetch of file `%s` within module `%s` failed", file, module.Name) + } + + var f pbConfigV1.ConfigV1File + + f.Path = strings.TrimPrefix(expectedPath, fmt.Sprintf("%s/", module.Path)) + f.Size = finfo.Size + f.CreatedAt = timestamppb.New(time.Unix(finfo.Ctim.Sec, finfo.Ctim.Nsec)) + f.UpdatedAt = timestamppb.New(time.Unix(finfo.Mtim.Sec, finfo.Mtim.Nsec)) + + if checksum { + c, err := util.SHA256FromFile(expectedPath) + if err != nil { + logger.Str("module", module.Name).Str("file", file).Str("real-path", expectedPath).Warn("Unable to get file checksum") + return nil, status.Errorf(codes.Internal, "Unable to calculate checksum of file `%s` within module `%s` failed", file, module.Name) + } + f.Checksum = util.NewType(c) + } + + return &f, nil +} diff --git a/integrations/config/v1/logger.go b/integrations/config/v1/logger.go new file mode 100644 index 000000000..bf8b37ec4 --- /dev/null +++ b/integrations/config/v1/logger.go @@ -0,0 +1,25 @@ +// +// DISCLAIMER +// +// Copyright 2024 ArangoDB GmbH, Cologne, Germany +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// Copyright holder is ArangoDB GmbH, Cologne, Germany +// + +package v1 + +import "github.com/arangodb/kube-arangodb/pkg/logging" + +var logger = logging.Global().RegisterAndGetLogger("integration-config-v1", logging.Info) diff --git a/integrations/config/v1/module_details_test.go b/integrations/config/v1/module_details_test.go new file mode 100644 index 000000000..7f78c83e6 --- /dev/null +++ b/integrations/config/v1/module_details_test.go @@ -0,0 +1,192 @@ +// +// DISCLAIMER +// +// Copyright 2024 ArangoDB GmbH, Cologne, Germany +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// Copyright holder is ArangoDB GmbH, Cologne, Germany +// + +package v1 + +import ( + "context" + "testing" + + "github.com/stretchr/testify/require" + "google.golang.org/grpc/codes" + + pbConfigV1 "github.com/arangodb/kube-arangodb/integrations/config/v1/definition" + "github.com/arangodb/kube-arangodb/pkg/util" + "github.com/arangodb/kube-arangodb/pkg/util/tests" + "github.com/arangodb/kube-arangodb/pkg/util/tests/tgrpc" +) + +func Test_Modules_Details_Empty(t *testing.T) { + ctx, c := context.WithCancel(context.Background()) + defer c() + + dir := t.TempDir() + + client := Client(t, ctx, Config{ + Modules: ModuleDefinitions{ + "test": { + Path: dir, + }, + }, + }) + + require.NotNil(t, client) + + _, err := client.ModuleDetails(ctx, &pbConfigV1.ConfigV1ModuleDetailsRequest{}) + tgrpc.AsGRPCError(t, err).Code(t, codes.InvalidArgument).Errorf(t, "Module name cannot be empty") +} + +func Test_Modules_Details_NotFound(t *testing.T) { + ctx, c := context.WithCancel(context.Background()) + defer c() + + dir := t.TempDir() + + client := Client(t, ctx, Config{ + Modules: ModuleDefinitions{ + "test": { + Path: dir, + }, + }, + }) + + require.NotNil(t, client) + + _, err := client.ModuleDetails(ctx, &pbConfigV1.ConfigV1ModuleDetailsRequest{ + Module: "some", + }) + tgrpc.AsGRPCError(t, err).Code(t, codes.NotFound).Errorf(t, "Module `some` not found") +} + +func Test_Modules_Details_Exists_EmptyFiles(t *testing.T) { + ctx, c := context.WithCancel(context.Background()) + defer c() + + dir := t.TempDir() + + client := Client(t, ctx, Config{ + Modules: ModuleDefinitions{ + "test": { + Path: dir, + }, + }, + }) + + require.NotNil(t, client) + + module, err := client.ModuleDetails(ctx, &pbConfigV1.ConfigV1ModuleDetailsRequest{ + Module: "test", + }) + require.NoError(t, err) + require.Equal(t, "test", module.GetModule()) + require.Len(t, module.GetFiles(), 0) + require.Equal(t, "", module.GetChecksum()) +} + +func Test_Modules_Details_Exists_EmptyFiles_WithChecksum(t *testing.T) { + ctx, c := context.WithCancel(context.Background()) + defer c() + + dir := t.TempDir() + + client := Client(t, ctx, Config{ + Modules: ModuleDefinitions{ + "test": { + Path: dir, + }, + }, + }) + + require.NotNil(t, client) + + module, err := client.ModuleDetails(ctx, &pbConfigV1.ConfigV1ModuleDetailsRequest{ + Module: "test", + Checksum: util.NewType(true), + }) + require.NoError(t, err) + require.Equal(t, "test", module.GetModule()) + require.Len(t, module.GetFiles(), 0) + require.Equal(t, "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", module.GetChecksum()) +} + +func Test_Modules_Details_Exists_SomeFiles(t *testing.T) { + ctx, c := context.WithCancel(context.Background()) + defer c() + + dir := t.TempDir() + + tests.NewFileGenerator(t, dir). + FileR(t, "test", 128). + Directory(t, "sub").FileR(t, "test", 128) + + client := Client(t, ctx, Config{ + Modules: ModuleDefinitions{ + "test": { + Path: dir, + }, + }, + }) + + require.NotNil(t, client) + + module, err := client.ModuleDetails(ctx, &pbConfigV1.ConfigV1ModuleDetailsRequest{ + Module: "test", + }) + require.NoError(t, err) + require.Equal(t, "test", module.GetModule()) + require.Len(t, module.GetFiles(), 2) + files := module.GetFiles() + require.Equal(t, "sub/test", files[0].GetPath()) + require.Equal(t, "test", files[1].GetPath()) + require.Equal(t, "", module.GetChecksum()) +} + +func Test_Modules_Details_Exists_SomeFiles_WithChecksum(t *testing.T) { + ctx, c := context.WithCancel(context.Background()) + defer c() + + dir := t.TempDir() + + tests.NewFileGenerator(t, dir). + File(t, "test", []byte("DATA")). + Directory(t, "sub").File(t, "test", []byte("DATA2")) + + client := Client(t, ctx, Config{ + Modules: ModuleDefinitions{ + "test": { + Path: dir, + }, + }, + }) + + require.NotNil(t, client) + + module, err := client.ModuleDetails(ctx, &pbConfigV1.ConfigV1ModuleDetailsRequest{ + Module: "test", + Checksum: util.NewType(true), + }) + require.NoError(t, err) + require.Equal(t, "test", module.GetModule()) + require.Len(t, module.GetFiles(), 2) + files := module.GetFiles() + require.Equal(t, "sub/test", files[0].GetPath()) + require.Equal(t, "test", files[1].GetPath()) + require.Equal(t, "e357414aec56cf8e5e3988b53b766049521a1f4920ad72462d48ebbe16942915", module.GetChecksum()) +} diff --git a/integrations/config/v1/module_test.go b/integrations/config/v1/module_test.go new file mode 100644 index 000000000..8c89ae255 --- /dev/null +++ b/integrations/config/v1/module_test.go @@ -0,0 +1,75 @@ +// +// DISCLAIMER +// +// Copyright 2024 ArangoDB GmbH, Cologne, Germany +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// Copyright holder is ArangoDB GmbH, Cologne, Germany +// + +package v1 + +import ( + "context" + "testing" + + "github.com/stretchr/testify/require" + + pbSharedV1 "github.com/arangodb/kube-arangodb/integrations/shared/v1/definition" +) + +func Test_Modules_Single(t *testing.T) { + ctx, c := context.WithCancel(context.Background()) + defer c() + + dir := t.TempDir() + + client := Client(t, ctx, Config{ + Modules: ModuleDefinitions{ + "test": { + Path: dir, + }, + }, + }) + + require.NotNil(t, client) + + modules, err := client.Modules(ctx, &pbSharedV1.Empty{}) + require.NoError(t, err) + require.Len(t, modules.GetModules(), 1) +} + +func Test_Modules_Multi(t *testing.T) { + ctx, c := context.WithCancel(context.Background()) + defer c() + + dir := t.TempDir() + + client := Client(t, ctx, Config{ + Modules: ModuleDefinitions{ + "test": { + Path: dir, + }, + "test2": { + Path: dir, + }, + }, + }) + + require.NotNil(t, client) + + modules, err := client.Modules(ctx, &pbSharedV1.Empty{}) + require.NoError(t, err) + require.Len(t, modules.GetModules(), 2) +} diff --git a/integrations/config/v1/service_test.go b/integrations/config/v1/service_test.go new file mode 100644 index 000000000..2e85595a5 --- /dev/null +++ b/integrations/config/v1/service_test.go @@ -0,0 +1,46 @@ +// +// DISCLAIMER +// +// Copyright 2024 ArangoDB GmbH, Cologne, Germany +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// Copyright holder is ArangoDB GmbH, Cologne, Germany +// + +package v1 + +import ( + "context" + "testing" + + "github.com/stretchr/testify/require" + + pbConfigV1 "github.com/arangodb/kube-arangodb/integrations/config/v1/definition" + "github.com/arangodb/kube-arangodb/pkg/util/svc" + "github.com/arangodb/kube-arangodb/pkg/util/tests/tgrpc" +) + +func Client(t *testing.T, ctx context.Context, config Config) pbConfigV1.ConfigV1Client { + h, err := New(config) + require.NoError(t, err) + local := svc.NewService(svc.Configuration{ + Address: "127.0.0.1:0", + }, h) + + start := local.Start(ctx) + + client := tgrpc.NewGRPCClient(t, ctx, pbConfigV1.NewConfigV1Client, start.Address()) + + return client +} diff --git a/integrations/scheduler/v1/consts.go b/integrations/scheduler/v1/consts.go new file mode 100644 index 000000000..b34e8635d --- /dev/null +++ b/integrations/scheduler/v1/consts.go @@ -0,0 +1,25 @@ +// +// DISCLAIMER +// +// Copyright 2024 ArangoDB GmbH, Cologne, Germany +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// Copyright holder is ArangoDB GmbH, Cologne, Germany +// + +package v1 + +const ( + Name = "scheduler.v1" +) diff --git a/pkg/integrations/authentication_v1.go b/pkg/integrations/authentication_v1.go index 4387abc63..3de449d76 100644 --- a/pkg/integrations/authentication_v1.go +++ b/pkg/integrations/authentication_v1.go @@ -31,7 +31,7 @@ import ( ) func init() { - register(func() Integration { + registerer.Register(pbAuthenticationV1.Name, func() Integration { return &authenticationV1{} }) } diff --git a/pkg/integrations/authorization_v0.go b/pkg/integrations/authorization_v0.go index 7cb0efce1..d63ab33d5 100644 --- a/pkg/integrations/authorization_v0.go +++ b/pkg/integrations/authorization_v0.go @@ -31,7 +31,7 @@ import ( ) func init() { - register(func() Integration { + registerer.Register(pbAuthorizationV0.Name, func() Integration { return &authorizationV0{} }) } diff --git a/pkg/integrations/config_v1.go b/pkg/integrations/config_v1.go new file mode 100644 index 000000000..582605a03 --- /dev/null +++ b/pkg/integrations/config_v1.go @@ -0,0 +1,77 @@ +// +// DISCLAIMER +// +// Copyright 2024 ArangoDB GmbH, Cologne, Germany +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// Copyright holder is ArangoDB GmbH, Cologne, Germany +// + +package integrations + +import ( + "context" + + "github.com/spf13/cobra" + + pbImplConfigV1 "github.com/arangodb/kube-arangodb/integrations/config/v1" + "github.com/arangodb/kube-arangodb/pkg/util/errors" + "github.com/arangodb/kube-arangodb/pkg/util/strings" + "github.com/arangodb/kube-arangodb/pkg/util/svc" +) + +func init() { + registerer.Register(pbImplConfigV1.Name, func() Integration { + return &configV1{} + }) +} + +type configV1 struct { + modules []string +} + +func (a *configV1) Register(cmd *cobra.Command, arg ArgGen) error { + f := cmd.Flags() + + f.StringSliceVar(&a.modules, arg("module"), nil, "Module in the reference =") + + return nil +} + +func (a *configV1) Handler(ctx context.Context) (svc.Handler, error) { + var cfg pbImplConfigV1.Config + + cfg.Modules = map[string]pbImplConfigV1.ModuleDefinition{} + + for _, module := range a.modules { + l := strings.SplitN(module, "=", 2) + if len(l) != 2 { + return nil, errors.Errorf("Invalid module definition: %s", module) + } + + cfg.Modules[l[0]] = pbImplConfigV1.ModuleDefinition{ + Path: l[1], + } + } + + return pbImplConfigV1.New(cfg) +} + +func (a *configV1) Name() string { + return pbImplConfigV1.Name +} + +func (a *configV1) Description() string { + return "Enable ConfigV1 Integration Service" +} diff --git a/pkg/integrations/envoy_auth_v3.go b/pkg/integrations/envoy_auth_v3.go index 104ff1f89..aadfabacc 100644 --- a/pkg/integrations/envoy_auth_v3.go +++ b/pkg/integrations/envoy_auth_v3.go @@ -30,7 +30,7 @@ import ( ) func init() { - register(func() Integration { + registerer.Register(pbImplEnvoyAuthV3.Name, func() Integration { return &envoyAuthV3{} }) } diff --git a/pkg/integrations/register.go b/pkg/integrations/register.go index 807687e23..367eff288 100644 --- a/pkg/integrations/register.go +++ b/pkg/integrations/register.go @@ -23,26 +23,16 @@ package integrations import ( "fmt" "sort" - "sync" "github.com/spf13/cobra" + "github.com/arangodb/kube-arangodb/pkg/util" "github.com/arangodb/kube-arangodb/pkg/util/errors" "github.com/arangodb/kube-arangodb/pkg/util/shutdown" "github.com/arangodb/kube-arangodb/pkg/util/svc" ) -var ( - lock sync.Mutex - registered []Factory -) - -func register(i Factory) { - lock.Lock() - defer lock.Unlock() - - registered = append(registered, i) -} +var registerer = util.NewRegisterer[string, Factory]() func Register(cmd *cobra.Command) error { var c configuration @@ -65,13 +55,9 @@ type configuration struct { } func (c *configuration) Register(cmd *cobra.Command) error { - lock.Lock() - defer lock.Unlock() - - c.registered = make([]Integration, len(registered)) - for id := range registered { - c.registered[id] = registered[id]() - } + c.registered = util.FormatList(registerer.Items(), func(a util.KV[string, Factory]) Integration { + return a.V() + }) sort.Slice(c.registered, func(i, j int) bool { return c.registered[i].Name() < c.registered[j].Name() diff --git a/pkg/integrations/scheduler_v1.go b/pkg/integrations/scheduler_v1.go index 34cb3c565..e6d906797 100644 --- a/pkg/integrations/scheduler_v1.go +++ b/pkg/integrations/scheduler_v1.go @@ -33,7 +33,7 @@ import ( ) func init() { - register(func() Integration { + registerer.Register(pbImplSchedulerV1.Name, func() Integration { return &schedulerV1{} }) } @@ -43,7 +43,7 @@ type schedulerV1 struct { } func (b *schedulerV1) Name() string { - return "scheduler.v1" + return pbImplSchedulerV1.Name } func (b *schedulerV1) Description() string { diff --git a/pkg/integrations/shutdown_v1.go b/pkg/integrations/shutdown_v1.go index 13c518fa0..926a66fc2 100644 --- a/pkg/integrations/shutdown_v1.go +++ b/pkg/integrations/shutdown_v1.go @@ -31,7 +31,7 @@ import ( ) func init() { - register(func() Integration { + registerer.Register(pbImplShutdownV1.Name, func() Integration { return &shutdownV1{} }) } diff --git a/pkg/integrations/storage_v1.go b/pkg/integrations/storage_v1.go index f613086b9..ffa351f4e 100644 --- a/pkg/integrations/storage_v1.go +++ b/pkg/integrations/storage_v1.go @@ -30,7 +30,7 @@ import ( ) func init() { - register(func() Integration { + registerer.Register(storage.Name, func() Integration { return &storageV1{} }) } @@ -40,7 +40,7 @@ type storageV1 struct { } func (b *storageV1) Name() string { - return "storage.v1" + return storage.Name } func (b *storageV1) Description() string { diff --git a/pkg/ml/storage/consts.go b/pkg/ml/storage/consts.go new file mode 100644 index 000000000..a170c7072 --- /dev/null +++ b/pkg/ml/storage/consts.go @@ -0,0 +1,23 @@ +// +// DISCLAIMER +// +// Copyright 2024 ArangoDB GmbH, Cologne, Germany +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// Copyright holder is ArangoDB GmbH, Cologne, Germany +// + +package storage + +const Name = "storage.v1" diff --git a/pkg/upgrade/upgrade_test.go b/pkg/upgrade/upgrade_test.go index abd653f49..219883877 100644 --- a/pkg/upgrade/upgrade_test.go +++ b/pkg/upgrade/upgrade_test.go @@ -1,7 +1,7 @@ // // DISCLAIMER // -// Copyright 2016-2022 ArangoDB GmbH, Cologne, Germany +// Copyright 2016-2024 ArangoDB GmbH, Cologne, Germany // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -79,7 +79,7 @@ func Test_Verify_WrongOrder(t *testing.T) { ) _, err := u.Execute(api.ArangoDeployment{}, nil, nil) - require.EqualError(t, err, "Invalid version in 1.1.1 - got 3, expected 2") + require.EqualError(t, err, "Invalid version in 1.1.1 - got 3, expected 1") }) t.Run("Valid multi version", func(t *testing.T) { var u Upgrades diff --git a/pkg/util/checksum.go b/pkg/util/checksum.go index 41028698e..144bbe291 100644 --- a/pkg/util/checksum.go +++ b/pkg/util/checksum.go @@ -24,6 +24,8 @@ import ( "crypto/md5" "crypto/sha256" "fmt" + "io" + "os" "k8s.io/apimachinery/pkg/util/json" @@ -56,6 +58,27 @@ func SHA256(data []byte) string { return fmt.Sprintf("%0x", sha256.Sum256(data)) } +func SHA256FromFile(file string) (string, error) { + in, err := os.OpenFile(file, os.O_RDONLY, 0644) + if err != nil { + return "", err + } + + defer in.Close() + + return SHA256FromIO(in) +} + +func SHA256FromIO(in io.Reader) (string, error) { + c := sha256.New() + + if _, err := io.CopyBuffer(c, in, make([]byte, 4096)); err != nil { + return "", err + } + + return fmt.Sprintf("%0x", c.Sum(nil)), nil +} + func MD5FromString(data string) string { return MD5([]byte(data)) } diff --git a/pkg/util/dict.go b/pkg/util/dict.go index 7643d36fa..edcd37fc4 100644 --- a/pkg/util/dict.go +++ b/pkg/util/dict.go @@ -22,7 +22,6 @@ package util import ( "maps" - "reflect" "sort" ) @@ -53,24 +52,34 @@ func Sort[IN any](in []IN, cmp func(i, j IN) bool) []IN { return r } -func SortKeys(m interface{}) []string { - if m == nil { - return []string{} +func MapValues[K comparable, V any](m map[K]V) []V { + r := make([]V, 0, len(m)) + + for k := range m { + r = append(r, m[k]) } - q := reflect.ValueOf(m).MapKeys() + return r +} - r := make([]string, len(q)) +func MapKeys[K comparable, V any](m map[K]V) []K { + r := make([]K, 0, len(m)) - for id, v := range q { - r[id] = v.String() + for k := range m { + r = append(r, k) } - sort.Strings(r) - return r } +func SortKeys[V any](m map[string]V) []string { + keys := MapKeys(m) + + sort.Strings(keys) + + return keys +} + func CopyFullMap[K comparable, V any](src map[K]V) map[K]V { if src == nil { return nil diff --git a/pkg/util/registerer.go b/pkg/util/registerer.go new file mode 100644 index 000000000..ad9f6d01f --- /dev/null +++ b/pkg/util/registerer.go @@ -0,0 +1,71 @@ +// +// DISCLAIMER +// +// Copyright 2024 ArangoDB GmbH, Cologne, Germany +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// Copyright holder is ArangoDB GmbH, Cologne, Germany +// + +package util + +import "sync" + +func NewRegisterer[K comparable, V any]() Registerer[K, V] { + return ®isterer[K, V]{ + items: make(map[K]V), + } +} + +type Registerer[K comparable, V any] interface { + Register(key K, value V) bool + MustRegister(key K, value V) + + Items() []KV[K, V] +} + +type registerer[K comparable, V any] struct { + lock sync.Mutex + + items map[K]V +} + +func (r *registerer[K, V]) Register(key K, value V) bool { + r.lock.Lock() + defer r.lock.Unlock() + + if _, ok := r.items[key]; ok { + return false + } + + r.items[key] = value + + return true +} + +func (r *registerer[K, V]) MustRegister(key K, value V) { + r.lock.Lock() + defer r.lock.Unlock() + + if !r.Register(key, value) { + panic("Unable to register item") + } +} + +func (r *registerer[K, V]) Items() []KV[K, V] { + r.lock.Lock() + defer r.lock.Unlock() + + return Extract(r.items) +} diff --git a/pkg/util/strings/strings.go b/pkg/util/strings/strings.go index 52fa89a11..dd848a761 100644 --- a/pkg/util/strings/strings.go +++ b/pkg/util/strings/strings.go @@ -130,6 +130,23 @@ func Split(s, sep string) []string { return strings.Split(s, sep) } +// SplitN slices s into substrings separated by sep and returns a slice of +// the substrings between those separators. +// +// The count determines the number of substrings to return: +// +// n > 0: at most n substrings; the last substring will be the unsplit remainder. +// n == 0: the result is nil (zero substrings) +// n < 0: all substrings +// +// Edge cases for s and sep (for example, empty strings) are handled +// as described in the documentation for [Split]. +// +// To split around the first instance of a separator, see Cut. +func SplitN(s, sep string, n int) []string { + return strings.SplitN(s, sep, n) +} + // ToLower returns s with all Unicode letters mapped to their lower case. func ToLower(s string) string { return strings.ToLower(s) diff --git a/pkg/util/svc/error.go b/pkg/util/svc/error.go index a7c9ed475..da183f5b7 100644 --- a/pkg/util/svc/error.go +++ b/pkg/util/svc/error.go @@ -20,7 +20,12 @@ package svc -import "context" +import ( + "context" + "errors" + + "google.golang.org/grpc/status" +) type serviceError struct { error @@ -45,3 +50,17 @@ func (p serviceError) Update(key string, state HealthState) { func (p serviceError) Start(ctx context.Context) ServiceStarter { return p } + +type GRPCErrorStatus interface { + error + + GRPCStatus() *status.Status +} + +func AsGRPCErrorStatus(err error) (GRPCErrorStatus, bool) { + var v GRPCErrorStatus + if errors.As(err, &v) { + return v, true + } + return nil, false +} diff --git a/pkg/util/tests/path.go b/pkg/util/tests/path.go new file mode 100644 index 000000000..c1078a656 --- /dev/null +++ b/pkg/util/tests/path.go @@ -0,0 +1,95 @@ +// +// DISCLAIMER +// +// Copyright 2024 ArangoDB GmbH, Cologne, Germany +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// Copyright holder is ArangoDB GmbH, Cologne, Germany +// + +package tests + +import ( + "os" + "path" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/arangodb/kube-arangodb/pkg/util" + "github.com/arangodb/kube-arangodb/pkg/util/errors" +) + +type FileGenerator interface { + Parent(t *testing.T) FileGenerator + + Directory(t *testing.T, name string) FileGenerator + + File(t *testing.T, name string, data []byte) FileGenerator + + FileR(t *testing.T, name string, size int) FileGenerator +} + +type fileGenerator struct { + root string + path string +} + +func (f fileGenerator) FileR(t *testing.T, name string, size int) FileGenerator { + var data = make([]byte, size) + + _, err := util.Rand().Read(data) + require.NoError(t, err) + + return f.File(t, name, data) +} + +func (f fileGenerator) Parent(t *testing.T) FileGenerator { + require.NotEqual(t, f.root, f.path, "Unable to jump above root") + + return fileGenerator{ + root: f.root, + path: path.Dir(f.path), + } +} + +func (f fileGenerator) Directory(t *testing.T, name string) FileGenerator { + np := path.Join(f.path, name) + + if err := os.Mkdir(np, 0755); err != nil { + if !errors.Is(err, os.ErrExist) { + require.NoError(t, err) + } + } + + return fileGenerator{ + root: f.root, + path: np, + } +} + +func (f fileGenerator) File(t *testing.T, name string, data []byte) FileGenerator { + require.NoError(t, os.WriteFile(path.Join(f.path, name), data, 0644)) + return f +} + +func NewFileGenerator(t *testing.T, root string) FileGenerator { + if err := os.Mkdir(root, 0755); err != nil { + if !errors.Is(err, os.ErrExist) { + require.NoError(t, err) + } + } + + return fileGenerator{root: root, path: root} +} diff --git a/pkg/util/tests/tgrpc/grpc.go b/pkg/util/tests/tgrpc/grpc.go index 7ba422855..dacb456a5 100644 --- a/pkg/util/tests/tgrpc/grpc.go +++ b/pkg/util/tests/tgrpc/grpc.go @@ -22,11 +22,16 @@ package tgrpc import ( "context" + "fmt" "testing" "github.com/stretchr/testify/require" "google.golang.org/grpc" + "google.golang.org/grpc/codes" "google.golang.org/grpc/credentials/insecure" + "google.golang.org/grpc/status" + + "github.com/arangodb/kube-arangodb/pkg/util/svc" ) func NewGRPCClient[T any](t *testing.T, ctx context.Context, in func(cc grpc.ClientConnInterface) T, addr string, opts ...grpc.DialOption) T { @@ -51,3 +56,30 @@ func NewGRPCConn(t *testing.T, ctx context.Context, addr string, opts ...grpc.Di return conn } + +type ErrorStatusValidator interface { + Code(t *testing.T, code codes.Code) ErrorStatusValidator + Errorf(t *testing.T, msg string, args ...interface{}) ErrorStatusValidator +} + +type errorStatusValidator struct { + st *status.Status +} + +func (e errorStatusValidator) Errorf(t *testing.T, msg string, args ...interface{}) ErrorStatusValidator { + require.Equal(t, e.st.Message(), fmt.Sprintf(msg, args...)) + return e +} + +func (e errorStatusValidator) Code(t *testing.T, code codes.Code) ErrorStatusValidator { + require.Equal(t, code, e.st.Code()) + return e +} + +func AsGRPCError(t *testing.T, err error) ErrorStatusValidator { + v, ok := svc.AsGRPCErrorStatus(err) + require.True(t, ok) + st := v.GRPCStatus() + require.NotNil(t, st) + return errorStatusValidator{st: st} +}