From 5337f9dbb8a5e82253cc47b84890379e48ecb66c Mon Sep 17 00:00:00 2001 From: ajanikow <12255597+ajanikow@users.noreply.github.com> Date: Mon, 20 Oct 2025 10:22:57 +0000 Subject: [PATCH 1/3] [Feature] [LM] Inventory CLI --- CHANGELOG.md | 1 + docs/cli/arangodb_operator_platform.md | 68 +++ internal/readme_cli.go | 12 + pkg/apis/shared/validate.go | 5 + pkg/deployment/client/client.go | 2 + pkg/deployment/client/id.go | 35 ++ pkg/platform/flags.go | 2 + pkg/platform/installer.go | 1 + pkg/platform/inventory/aql.go | 103 ++++ .../inventory/fetcher.aql.timestamp.go | 30 ++ .../inventory/fetcher.deployment.id.go | 65 +++ pkg/platform/inventory/fetcher.server.mode.go | 60 +++ .../inventory/fetcher.server.version.go | 56 +++ pkg/platform/inventory/global.go | 27 + pkg/platform/inventory/inventory.go | 108 ++++ pkg/platform/inventory/inventory.pb.go | 466 ++++++++++++++++++ pkg/platform/inventory/inventory.proto | 69 +++ pkg/platform/inventory/queries/timestamp.aql | 6 + pkg/platform/inventory/types.go | 127 +++++ pkg/platform/inventory/types_test.go | 73 +++ pkg/platform/license.go | 46 ++ pkg/platform/license_inventory.go | 113 +++++ pkg/util/arangod/request.go | 167 +++++++ pkg/util/cli/deployment.auth.basic.go | 66 +++ pkg/util/cli/deployment.auth.token.go | 61 +++ pkg/util/cli/deployment.go | 214 ++++++++ pkg/util/executor/executor.go | 11 +- pkg/util/executor/threader.go | 15 +- pkg/util/grpc/object.go | 42 ++ 29 files changed, 2038 insertions(+), 13 deletions(-) create mode 100644 pkg/deployment/client/id.go create mode 100644 pkg/platform/inventory/aql.go create mode 100644 pkg/platform/inventory/fetcher.aql.timestamp.go create mode 100644 pkg/platform/inventory/fetcher.deployment.id.go create mode 100644 pkg/platform/inventory/fetcher.server.mode.go create mode 100644 pkg/platform/inventory/fetcher.server.version.go create mode 100644 pkg/platform/inventory/global.go create mode 100644 pkg/platform/inventory/inventory.go create mode 100644 pkg/platform/inventory/inventory.pb.go create mode 100644 pkg/platform/inventory/inventory.proto create mode 100644 pkg/platform/inventory/queries/timestamp.aql create mode 100644 pkg/platform/inventory/types.go create mode 100644 pkg/platform/inventory/types_test.go create mode 100644 pkg/platform/license.go create mode 100644 pkg/platform/license_inventory.go create mode 100644 pkg/util/arangod/request.go create mode 100644 pkg/util/cli/deployment.auth.basic.go create mode 100644 pkg/util/cli/deployment.auth.token.go create mode 100644 pkg/util/cli/deployment.go create mode 100644 pkg/util/grpc/object.go diff --git a/CHANGELOG.md b/CHANGELOG.md index ff0a38982..2c036d21b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## [master](https://github.com/arangodb/kube-arangodb/tree/master) (N/A) - (Bugfix) (Platform) Increase memory limit for Inventory +- (Feature) (LM) Inventory Generator ## [1.3.1](https://github.com/arangodb/kube-arangodb/tree/1.3.1) (2025-10-07) - (Documentation) Add ArangoPlatformStorage Docs & Examples diff --git a/docs/cli/arangodb_operator_platform.md b/docs/cli/arangodb_operator_platform.md index f7e217f33..a4c0886b8 100644 --- a/docs/cli/arangodb_operator_platform.md +++ b/docs/cli/arangodb_operator_platform.md @@ -4,6 +4,28 @@ parent: Binaries title: arangodb_operator_platform --- +# ArangoDB Operator Platform Command + +[START_INJECT]: # (arangodb_operator_platform_cmd) +``` +Usage: + arangodb_operator_platform [command] + +Available Commands: + completion Generate the autocompletion script for the specified shell + help Help about any command + license License Package related operations + package Release Package related operations + +Flags: + -h, --help help for arangodb_operator_platform + --kubeconfig string Kubernetes Config File + -n, --namespace string Kubernetes Namespace (default "default") + +Use "arangodb_operator_platform [command] --help" for more information about a command. +``` +[END_INJECT]: # (arangodb_operator_platform_cmd) + # ArangoDB Operator Platform Package Command [START_INJECT]: # (arangodb_operator_platform_package_cmd) @@ -70,3 +92,49 @@ Global Flags: ``` [END_INJECT]: # (arangodb_operator_platform_package_install_cmd) +# ArangoDB Operator Platform License Command + +[START_INJECT]: # (arangodb_operator_platform_license_cmd) +``` +License Package related operations + +Usage: + arangodb_operator_platform license [command] + +Available Commands: + inventory Inventory Generator + +Flags: + -h, --help help for license + +Global Flags: + --kubeconfig string Kubernetes Config File + -n, --namespace string Kubernetes Namespace (default "default") + +Use "arangodb_operator_platform license [command] --help" for more information about a command. +``` +[END_INJECT]: # (arangodb_operator_platform_license_cmd) + +# ArangoDB Operator Platform License Inventory Command + +[START_INJECT]: # (arangodb_operator_platform_license_inventory_cmd) +``` +Inventory Generator + +Usage: + arangodb_operator_platform license inventory [flags] + +Flags: + --arango.authentication string Arango Endpoint Auth Method. One of: Disabled, Basic, Token (default "Disabled") + --arango.basic.password string Arango Password for Basic Authentication + --arango.basic.username string Arango Username for Basic Authentication + --arango.endpoint strings Arango Endpoint + --arango.insecure Arango Endpoint Insecure + --arango.token string Arango JWT Token for Authentication + -h, --help help for inventory + +Global Flags: + --kubeconfig string Kubernetes Config File + -n, --namespace string Kubernetes Namespace (default "default") +``` +[END_INJECT]: # (arangodb_operator_platform_license_inventory_cmd) diff --git a/internal/readme_cli.go b/internal/readme_cli.go index 766807c2d..4bbb17eab 100644 --- a/internal/readme_cli.go +++ b/internal/readme_cli.go @@ -139,6 +139,18 @@ func GenerateCLIArangoDBOperatorPlatformReadme(root string) error { readmeSections["arangodb_operator_platform_package_install_cmd"] = section } + if section, err := GenerateHelpQuoted(cmd, "license"); err != nil { + return err + } else { + readmeSections["arangodb_operator_platform_license_cmd"] = section + } + + if section, err := GenerateHelpQuoted(cmd, "license", "inventory"); err != nil { + return err + } else { + readmeSections["arangodb_operator_platform_license_inventory_cmd"] = section + } + if err := pretty.ReplaceSectionsInFile(path.Join(root, "docs", "cli", "arangodb_operator_platform.md"), readmeSections); err != nil { return err } diff --git a/pkg/apis/shared/validate.go b/pkg/apis/shared/validate.go index b5b20c156..a018181ed 100644 --- a/pkg/apis/shared/validate.go +++ b/pkg/apis/shared/validate.go @@ -200,6 +200,11 @@ func ValidateRequiredNotEmptyPath[T any](path string, in *T) error { return PrefixResourceError(path, ValidateRequiredNotEmpty(in)) } +// ValidatePath Validates object +func ValidatePath[T any](path string, in T, validator func(T) error) error { + return PrefixResourceErrors(path, validator(in)) +} + // ValidateRequiredPath Validates object and required not nil value func ValidateRequiredPath[T any](path string, in *T, validator func(T) error) error { return PrefixResourceErrors(path, ValidateRequired(in, validator)) diff --git a/pkg/deployment/client/client.go b/pkg/deployment/client/client.go index 113007182..f172d4b20 100644 --- a/pkg/deployment/client/client.go +++ b/pkg/deployment/client/client.go @@ -59,6 +59,8 @@ type Client interface { Compact(ctx context.Context, request *CompactRequest) error Inventory(ctx context.Context) (*Inventory, error) + + DeploymentID(ctx context.Context) (DeploymentID, error) } type client struct { diff --git a/pkg/deployment/client/id.go b/pkg/deployment/client/id.go new file mode 100644 index 000000000..e936afde2 --- /dev/null +++ b/pkg/deployment/client/id.go @@ -0,0 +1,35 @@ +// +// DISCLAIMER +// +// Copyright 2025 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 client + +import ( + "context" + + "github.com/arangodb/kube-arangodb/pkg/util/arangod" +) + +type DeploymentID struct { + Id string `json:"id"` +} + +func (c *client) DeploymentID(ctx context.Context) (DeploymentID, error) { + return arangod.GetRequest[DeploymentID](ctx, c.c, "_admin", "deployment", "id").AcceptCode(200).Response() +} diff --git a/pkg/platform/flags.go b/pkg/platform/flags.go index e869ebf4b..97e61af0f 100644 --- a/pkg/platform/flags.go +++ b/pkg/platform/flags.go @@ -100,6 +100,8 @@ var ( }, } + flagDeployment = cli.NewDeployment("arango") + flagValues = cli.Flag[[]string]{ Name: "values", Short: "f", diff --git a/pkg/platform/installer.go b/pkg/platform/installer.go index 669025837..414a319fe 100644 --- a/pkg/platform/installer.go +++ b/pkg/platform/installer.go @@ -44,6 +44,7 @@ func installer() (*cobra.Command, error) { if err := withRegisterCommand(&cmd, pkg, + license, ); err != nil { return nil, err } diff --git a/pkg/platform/inventory/aql.go b/pkg/platform/inventory/aql.go new file mode 100644 index 000000000..5133f8e7e --- /dev/null +++ b/pkg/platform/inventory/aql.go @@ -0,0 +1,103 @@ +// +// DISCLAIMER +// +// Copyright 2025 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 inventory + +import ( + "context" + "time" + + "github.com/arangodb/go-driver" + "github.com/arangodb/go-driver/util/connection/wrappers/async" + + "github.com/arangodb/kube-arangodb/pkg/logging" + "github.com/arangodb/kube-arangodb/pkg/util/errors" + "github.com/arangodb/kube-arangodb/pkg/util/executor" + ugrpc "github.com/arangodb/kube-arangodb/pkg/util/grpc" +) + +func ExecuteAQL(db string, aql string, bind map[string]any) Executor { + return func(conn driver.Connection, out chan<- *Item) executor.RunFunc { + return func(ctx context.Context, log logging.Logger, t executor.Thread, h executor.Handler) error { + c, err := driver.NewClient(driver.ClientConfig{Connection: async.NewConnectionAsyncWrapper(conn)}) + if err != nil { + return err + } + + d, err := c.Database(ctx, db) + if err != nil { + return err + } + + nctx := driver.WithAsync(ctx) + + _, err = d.Query(nctx, aql, bind) + if err == nil { + return errors.Errorf("Async execution of the query should be prepared") + } + + jobId, ok := async.IsAsyncJobInProgress(err) + if !ok { + return errors.Wrapf(err, "Async execution of the query should be prepared") + } + + var cursor driver.Cursor + + for { + zctx := driver.WithAsyncID(ctx, jobId) + + query, err := d.Query(zctx, aql, bind) + if err != nil { + _, ok := async.IsAsyncJobInProgress(err) + if !ok { + return errors.Wrapf(err, "Async execution of the query should be prepared") + } + + t.Wait(125 * time.Millisecond) + + continue + } + + cursor = query + break + } + + for { + var ret ugrpc.Object[*Item] + + if _, err := cursor.ReadDocument(ctx, &ret); err != nil { + if driver.IsNoMoreDocuments(err) { + break + } + + return err + } + + if err := ret.Object.Validate(); err != nil { + return err + } + + out <- ret.Object + } + + return nil + } + } +} diff --git a/pkg/platform/inventory/fetcher.aql.timestamp.go b/pkg/platform/inventory/fetcher.aql.timestamp.go new file mode 100644 index 000000000..24b5a88b3 --- /dev/null +++ b/pkg/platform/inventory/fetcher.aql.timestamp.go @@ -0,0 +1,30 @@ +// +// DISCLAIMER +// +// Copyright 2025 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 inventory + +import _ "embed" + +//go:embed queries/timestamp.aql +var queryTimestampAQL string + +func init() { + global.MustRegister("aql.timestamp", ExecuteAQL("_system", queryTimestampAQL, nil)) +} diff --git a/pkg/platform/inventory/fetcher.deployment.id.go b/pkg/platform/inventory/fetcher.deployment.id.go new file mode 100644 index 000000000..ee6635ba1 --- /dev/null +++ b/pkg/platform/inventory/fetcher.deployment.id.go @@ -0,0 +1,65 @@ +// +// DISCLAIMER +// +// Copyright 2025 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 inventory + +import ( + "context" + goHttp "net/http" + + "github.com/arangodb/go-driver" + + "github.com/arangodb/kube-arangodb/pkg/deployment/client" + "github.com/arangodb/kube-arangodb/pkg/logging" + "github.com/arangodb/kube-arangodb/pkg/util/arangod" + "github.com/arangodb/kube-arangodb/pkg/util/errors" + "github.com/arangodb/kube-arangodb/pkg/util/executor" + "github.com/arangodb/kube-arangodb/pkg/util/globals" +) + +func init() { + global.MustRegister("deployment.id", func(conn driver.Connection, out chan<- *Item) executor.RunFunc { + return func(ctx context.Context, log logging.Logger, t executor.Thread, h executor.Handler) error { + if handler := arangod.GetRequestWithTimeout[client.DeploymentID](ctx, globals.GetGlobals().Timeouts().ArangoD().Get(), conn, "_admin", "deployment", "id"); handler.Code() == goHttp.StatusOK { + resp, err := handler.Response() + if err != nil { + return err + } + + return errors.Errors( + Produce(out, "ARANGO_DEPLOYMENT", map[string]string{ + "detail": "id", + }, resp.Id), + ) + } + + log.Warn("Fallback to the ClusterHealth Endpoint") + + health, err := arangod.GetRequestWithTimeout[driver.ClusterHealth](ctx, globals.GetGlobals().Timeouts().ArangoD().Get(), conn, "_admin", "cluster", "health").AcceptCode(goHttp.StatusOK).Response() + if err != nil { + return err + } + + return errors.Errors( + Produce(out, "ARANGO_DEPLOYMENT_ID", nil, health.ID), + ) + } + }) +} diff --git a/pkg/platform/inventory/fetcher.server.mode.go b/pkg/platform/inventory/fetcher.server.mode.go new file mode 100644 index 000000000..59e5251f1 --- /dev/null +++ b/pkg/platform/inventory/fetcher.server.mode.go @@ -0,0 +1,60 @@ +// +// DISCLAIMER +// +// Copyright 2025 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 inventory + +import ( + "context" + goHttp "net/http" + + "github.com/arangodb/go-driver" + + "github.com/arangodb/kube-arangodb/pkg/logging" + "github.com/arangodb/kube-arangodb/pkg/util/arangod" + "github.com/arangodb/kube-arangodb/pkg/util/errors" + "github.com/arangodb/kube-arangodb/pkg/util/executor" + "github.com/arangodb/kube-arangodb/pkg/util/globals" +) + +func init() { + global.MustRegister("server.mode", func(conn driver.Connection, out chan<- *Item) executor.RunFunc { + return func(ctx context.Context, log logging.Logger, t executor.Thread, h executor.Handler) error { + resp := arangod.GetRequestWithTimeout[driver.ClusterHealth](ctx, globals.GetGlobals().Timeouts().ArangoD().Get(), conn, "_admin", "cluster", "health") + + if resp.Code() == goHttp.StatusOK { + return errors.Errors( + Produce[string](out, "ARANGO_DEPLOYMENT", map[string]string{ + "detail": "mode", + }, "CLUSTER"), + ) + } + + if resp.Code() == goHttp.StatusForbidden { + return errors.Errors( + Produce[string](out, "ARANGO_DEPLOYMENT", map[string]string{ + "detail": "mode", + }, "SINGLE"), + ) + } + + return resp.AcceptCode(goHttp.StatusOK).Evaluate() + } + }) +} diff --git a/pkg/platform/inventory/fetcher.server.version.go b/pkg/platform/inventory/fetcher.server.version.go new file mode 100644 index 000000000..3fcc7d283 --- /dev/null +++ b/pkg/platform/inventory/fetcher.server.version.go @@ -0,0 +1,56 @@ +// +// DISCLAIMER +// +// Copyright 2025 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 inventory + +import ( + "context" + goHttp "net/http" + + "github.com/arangodb/go-driver" + + "github.com/arangodb/kube-arangodb/pkg/logging" + "github.com/arangodb/kube-arangodb/pkg/util/arangod" + "github.com/arangodb/kube-arangodb/pkg/util/errors" + "github.com/arangodb/kube-arangodb/pkg/util/executor" + "github.com/arangodb/kube-arangodb/pkg/util/globals" +) + +func init() { + global.MustRegister("server.info", func(conn driver.Connection, out chan<- *Item) executor.RunFunc { + return func(ctx context.Context, log logging.Logger, t executor.Thread, h executor.Handler) error { + resp, err := arangod.GetRequestWithTimeout[driver.VersionInfo](ctx, globals.GetGlobals().Timeouts().ArangoD().Get(), conn, "_api", "version"). + AcceptCode(goHttp.StatusOK). + Response() + if err != nil { + return err + } + + return errors.Errors( + Produce[string](out, "ARANGO_DEPLOYMENT", map[string]string{ + "detail": "version", + }, string(resp.Version)), + Produce(out, "ARANGO_DEPLOYMENT", map[string]string{ + "detail": "license", + }, resp.License), + ) + } + }) +} diff --git a/pkg/platform/inventory/global.go b/pkg/platform/inventory/global.go new file mode 100644 index 000000000..d508a8fa4 --- /dev/null +++ b/pkg/platform/inventory/global.go @@ -0,0 +1,27 @@ +// +// DISCLAIMER +// +// Copyright 2025 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 inventory + +import "github.com/arangodb/kube-arangodb/pkg/util" + +var ( + global = util.NewRegisterer[string, Executor]() +) diff --git a/pkg/platform/inventory/inventory.go b/pkg/platform/inventory/inventory.go new file mode 100644 index 000000000..51aec29b0 --- /dev/null +++ b/pkg/platform/inventory/inventory.go @@ -0,0 +1,108 @@ +// +// DISCLAIMER +// +// Copyright 2025 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 inventory + +import ( + "context" + + "github.com/arangodb/go-driver" + + shared "github.com/arangodb/kube-arangodb/pkg/apis/shared" + "github.com/arangodb/kube-arangodb/pkg/logging" + "github.com/arangodb/kube-arangodb/pkg/util/errors" + "github.com/arangodb/kube-arangodb/pkg/util/executor" +) + +type Items []*Item + +func FetchInventory(ctx context.Context, logger logging.Logger, threads int, conn driver.Connection) (Items, error) { + var out []*Item + done := make(chan struct{}) + in := make(chan *Item) + + go func() { + defer close(done) + + for z := range in { + if z == nil { + continue + } + + out = append(out, z) + } + }() + + if err := executor.Run(ctx, logger, threads, runExecution(conn, in)); err != nil { + return nil, err + } + + close(in) + + <-done + + return out, nil +} + +func runExecution(conn driver.Connection, out chan<- *Item) executor.RunFunc { + return func(ctx context.Context, log logging.Logger, t executor.Thread, h executor.Handler) error { + for _, executor := range global.Items() { + log.Str("name", executor.K).Info("Starting executor") + q := executor.V(conn, out) + + h.RunAsync(ctx, q) + } + + h.WaitForSubThreads(t) + + return nil + } +} + +type Executor func(conn driver.Connection, out chan<- *Item) executor.RunFunc + +func (i *Item) Validate() error { + if i == nil { + return errors.Errorf("Item is not provided") + } + + return errors.Errors( + shared.ValidatePath("type", i.Type, func(s string) error { + if s == "" { + return errors.Errorf("Type cannot be empty") + } + + return nil + }), + shared.ValidateRequiredInterfacePath("value", i.Value), + ) +} + +func (i *ItemValue) Validate() error { + if i == nil { + return errors.Errorf("Item Value is not provided") + } + + if i.GetValue() == nil { + return errors.Errorf("Item Value is not provided") + } + + return nil +} diff --git a/pkg/platform/inventory/inventory.pb.go b/pkg/platform/inventory/inventory.pb.go new file mode 100644 index 000000000..b0a7e281b --- /dev/null +++ b/pkg/platform/inventory/inventory.pb.go @@ -0,0 +1,466 @@ +// Inventory Type Definition + +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.26.0 +// protoc v3.21.1 +// source: pkg/platform/inventory/inventory.proto + +package inventory + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + durationpb "google.golang.org/protobuf/types/known/durationpb" + 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) +) + +// Defines inventory spec +type Spec struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Deployment ID + DeploymentId string `protobuf:"bytes,1,opt,name=deployment_id,json=deploymentId,proto3" json:"deployment_id,omitempty"` + // Items + Items []*Item `protobuf:"bytes,2,rep,name=items,proto3" json:"items,omitempty"` +} + +func (x *Spec) Reset() { + *x = Spec{} + if protoimpl.UnsafeEnabled { + mi := &file_pkg_platform_inventory_inventory_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Spec) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Spec) ProtoMessage() {} + +func (x *Spec) ProtoReflect() protoreflect.Message { + mi := &file_pkg_platform_inventory_inventory_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 Spec.ProtoReflect.Descriptor instead. +func (*Spec) Descriptor() ([]byte, []int) { + return file_pkg_platform_inventory_inventory_proto_rawDescGZIP(), []int{0} +} + +func (x *Spec) GetDeploymentId() string { + if x != nil { + return x.DeploymentId + } + return "" +} + +func (x *Spec) GetItems() []*Item { + if x != nil { + return x.Items + } + return nil +} + +// Defines inventory item +type Item struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Item Type + Type string `protobuf:"bytes,1,opt,name=type,proto3" json:"type,omitempty"` + // Dimensions + Dimensions map[string]string `protobuf:"bytes,2,rep,name=dimensions,proto3" json:"dimensions,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` + // Value of the Item + Value *ItemValue `protobuf:"bytes,3,opt,name=value,proto3" json:"value,omitempty"` +} + +func (x *Item) Reset() { + *x = Item{} + if protoimpl.UnsafeEnabled { + mi := &file_pkg_platform_inventory_inventory_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Item) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Item) ProtoMessage() {} + +func (x *Item) ProtoReflect() protoreflect.Message { + mi := &file_pkg_platform_inventory_inventory_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 Item.ProtoReflect.Descriptor instead. +func (*Item) Descriptor() ([]byte, []int) { + return file_pkg_platform_inventory_inventory_proto_rawDescGZIP(), []int{1} +} + +func (x *Item) GetType() string { + if x != nil { + return x.Type + } + return "" +} + +func (x *Item) GetDimensions() map[string]string { + if x != nil { + return x.Dimensions + } + return nil +} + +func (x *Item) GetValue() *ItemValue { + if x != nil { + return x.Value + } + return nil +} + +// Value of the Item +type ItemValue struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // One of the Values + // + // Types that are assignable to Value: + // + // *ItemValue_Str + // *ItemValue_Dec + // *ItemValue_Num + // *ItemValue_Bool + // *ItemValue_Time + // *ItemValue_Duration + // *ItemValue_LongNum + Value isItemValue_Value `protobuf_oneof:"value"` +} + +func (x *ItemValue) Reset() { + *x = ItemValue{} + if protoimpl.UnsafeEnabled { + mi := &file_pkg_platform_inventory_inventory_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ItemValue) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ItemValue) ProtoMessage() {} + +func (x *ItemValue) ProtoReflect() protoreflect.Message { + mi := &file_pkg_platform_inventory_inventory_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 ItemValue.ProtoReflect.Descriptor instead. +func (*ItemValue) Descriptor() ([]byte, []int) { + return file_pkg_platform_inventory_inventory_proto_rawDescGZIP(), []int{2} +} + +func (m *ItemValue) GetValue() isItemValue_Value { + if m != nil { + return m.Value + } + return nil +} + +func (x *ItemValue) GetStr() string { + if x, ok := x.GetValue().(*ItemValue_Str); ok { + return x.Str + } + return "" +} + +func (x *ItemValue) GetDec() float32 { + if x, ok := x.GetValue().(*ItemValue_Dec); ok { + return x.Dec + } + return 0 +} + +func (x *ItemValue) GetNum() int32 { + if x, ok := x.GetValue().(*ItemValue_Num); ok { + return x.Num + } + return 0 +} + +func (x *ItemValue) GetBool() bool { + if x, ok := x.GetValue().(*ItemValue_Bool); ok { + return x.Bool + } + return false +} + +func (x *ItemValue) GetTime() *timestamppb.Timestamp { + if x, ok := x.GetValue().(*ItemValue_Time); ok { + return x.Time + } + return nil +} + +func (x *ItemValue) GetDuration() *durationpb.Duration { + if x, ok := x.GetValue().(*ItemValue_Duration); ok { + return x.Duration + } + return nil +} + +func (x *ItemValue) GetLongNum() int64 { + if x, ok := x.GetValue().(*ItemValue_LongNum); ok { + return x.LongNum + } + return 0 +} + +type isItemValue_Value interface { + isItemValue_Value() +} + +type ItemValue_Str struct { + // String + Str string `protobuf:"bytes,1,opt,name=str,proto3,oneof"` +} + +type ItemValue_Dec struct { + // Float64 + Dec float32 `protobuf:"fixed32,2,opt,name=dec,proto3,oneof"` +} + +type ItemValue_Num struct { + // Int + Num int32 `protobuf:"varint,3,opt,name=num,proto3,oneof"` +} + +type ItemValue_Bool struct { + // Boolean + Bool bool `protobuf:"varint,4,opt,name=bool,proto3,oneof"` +} + +type ItemValue_Time struct { + // Time + Time *timestamppb.Timestamp `protobuf:"bytes,5,opt,name=time,proto3,oneof"` +} + +type ItemValue_Duration struct { + // Duration + Duration *durationpb.Duration `protobuf:"bytes,6,opt,name=duration,proto3,oneof"` +} + +type ItemValue_LongNum struct { + // Int64 + LongNum int64 `protobuf:"varint,7,opt,name=long_num,json=longNum,proto3,oneof"` +} + +func (*ItemValue_Str) isItemValue_Value() {} + +func (*ItemValue_Dec) isItemValue_Value() {} + +func (*ItemValue_Num) isItemValue_Value() {} + +func (*ItemValue_Bool) isItemValue_Value() {} + +func (*ItemValue_Time) isItemValue_Value() {} + +func (*ItemValue_Duration) isItemValue_Value() {} + +func (*ItemValue_LongNum) isItemValue_Value() {} + +var File_pkg_platform_inventory_inventory_proto protoreflect.FileDescriptor + +var file_pkg_platform_inventory_inventory_proto_rawDesc = []byte{ + 0x0a, 0x26, 0x70, 0x6b, 0x67, 0x2f, 0x70, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x2f, 0x69, + 0x6e, 0x76, 0x65, 0x6e, 0x74, 0x6f, 0x72, 0x79, 0x2f, 0x69, 0x6e, 0x76, 0x65, 0x6e, 0x74, 0x6f, + 0x72, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x05, 0x74, 0x79, 0x70, 0x65, 0x73, 0x1a, + 0x1e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, + 0x2f, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 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, + 0x22, 0x4e, 0x0a, 0x04, 0x53, 0x70, 0x65, 0x63, 0x12, 0x23, 0x0a, 0x0d, 0x64, 0x65, 0x70, 0x6c, + 0x6f, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x0c, 0x64, 0x65, 0x70, 0x6c, 0x6f, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x21, 0x0a, + 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0b, 0x2e, 0x74, + 0x79, 0x70, 0x65, 0x73, 0x2e, 0x49, 0x74, 0x65, 0x6d, 0x52, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, + 0x22, 0xbe, 0x01, 0x0a, 0x04, 0x49, 0x74, 0x65, 0x6d, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, + 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x3b, 0x0a, + 0x0a, 0x64, 0x69, 0x6d, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, + 0x0b, 0x32, 0x1b, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x49, 0x74, 0x65, 0x6d, 0x2e, 0x44, + 0x69, 0x6d, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0a, + 0x64, 0x69, 0x6d, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x26, 0x0a, 0x05, 0x76, 0x61, + 0x6c, 0x75, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x74, 0x79, 0x70, 0x65, + 0x73, 0x2e, 0x49, 0x74, 0x65, 0x6d, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x05, 0x76, 0x61, 0x6c, + 0x75, 0x65, 0x1a, 0x3d, 0x0a, 0x0f, 0x44, 0x69, 0x6d, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x73, + 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, + 0x01, 0x22, 0xee, 0x01, 0x0a, 0x09, 0x49, 0x74, 0x65, 0x6d, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, + 0x12, 0x0a, 0x03, 0x73, 0x74, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x03, + 0x73, 0x74, 0x72, 0x12, 0x12, 0x0a, 0x03, 0x64, 0x65, 0x63, 0x18, 0x02, 0x20, 0x01, 0x28, 0x02, + 0x48, 0x00, 0x52, 0x03, 0x64, 0x65, 0x63, 0x12, 0x12, 0x0a, 0x03, 0x6e, 0x75, 0x6d, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x05, 0x48, 0x00, 0x52, 0x03, 0x6e, 0x75, 0x6d, 0x12, 0x14, 0x0a, 0x04, 0x62, + 0x6f, 0x6f, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x48, 0x00, 0x52, 0x04, 0x62, 0x6f, 0x6f, + 0x6c, 0x12, 0x30, 0x0a, 0x04, 0x74, 0x69, 0x6d, 0x65, 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, 0x48, 0x00, 0x52, 0x04, 0x74, + 0x69, 0x6d, 0x65, 0x12, 0x37, 0x0a, 0x08, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, + 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x48, 0x00, 0x52, 0x08, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1b, 0x0a, 0x08, + 0x6c, 0x6f, 0x6e, 0x67, 0x5f, 0x6e, 0x75, 0x6d, 0x18, 0x07, 0x20, 0x01, 0x28, 0x03, 0x48, 0x00, + 0x52, 0x07, 0x6c, 0x6f, 0x6e, 0x67, 0x4e, 0x75, 0x6d, 0x42, 0x07, 0x0a, 0x05, 0x76, 0x61, 0x6c, + 0x75, 0x65, 0x42, 0x3a, 0x5a, 0x38, 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, 0x70, 0x6b, 0x67, 0x2f, 0x70, 0x6c, 0x61, 0x74, + 0x66, 0x6f, 0x72, 0x6d, 0x2f, 0x69, 0x6e, 0x76, 0x65, 0x6e, 0x74, 0x6f, 0x72, 0x79, 0x62, 0x06, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_pkg_platform_inventory_inventory_proto_rawDescOnce sync.Once + file_pkg_platform_inventory_inventory_proto_rawDescData = file_pkg_platform_inventory_inventory_proto_rawDesc +) + +func file_pkg_platform_inventory_inventory_proto_rawDescGZIP() []byte { + file_pkg_platform_inventory_inventory_proto_rawDescOnce.Do(func() { + file_pkg_platform_inventory_inventory_proto_rawDescData = protoimpl.X.CompressGZIP(file_pkg_platform_inventory_inventory_proto_rawDescData) + }) + return file_pkg_platform_inventory_inventory_proto_rawDescData +} + +var file_pkg_platform_inventory_inventory_proto_msgTypes = make([]protoimpl.MessageInfo, 4) +var file_pkg_platform_inventory_inventory_proto_goTypes = []interface{}{ + (*Spec)(nil), // 0: types.Spec + (*Item)(nil), // 1: types.Item + (*ItemValue)(nil), // 2: types.ItemValue + nil, // 3: types.Item.DimensionsEntry + (*timestamppb.Timestamp)(nil), // 4: google.protobuf.Timestamp + (*durationpb.Duration)(nil), // 5: google.protobuf.Duration +} +var file_pkg_platform_inventory_inventory_proto_depIdxs = []int32{ + 1, // 0: types.Spec.items:type_name -> types.Item + 3, // 1: types.Item.dimensions:type_name -> types.Item.DimensionsEntry + 2, // 2: types.Item.value:type_name -> types.ItemValue + 4, // 3: types.ItemValue.time:type_name -> google.protobuf.Timestamp + 5, // 4: types.ItemValue.duration:type_name -> google.protobuf.Duration + 5, // [5:5] is the sub-list for method output_type + 5, // [5:5] is the sub-list for method input_type + 5, // [5:5] is the sub-list for extension type_name + 5, // [5:5] is the sub-list for extension extendee + 0, // [0:5] is the sub-list for field type_name +} + +func init() { file_pkg_platform_inventory_inventory_proto_init() } +func file_pkg_platform_inventory_inventory_proto_init() { + if File_pkg_platform_inventory_inventory_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_pkg_platform_inventory_inventory_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Spec); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_pkg_platform_inventory_inventory_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Item); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_pkg_platform_inventory_inventory_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ItemValue); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + file_pkg_platform_inventory_inventory_proto_msgTypes[2].OneofWrappers = []interface{}{ + (*ItemValue_Str)(nil), + (*ItemValue_Dec)(nil), + (*ItemValue_Num)(nil), + (*ItemValue_Bool)(nil), + (*ItemValue_Time)(nil), + (*ItemValue_Duration)(nil), + (*ItemValue_LongNum)(nil), + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_pkg_platform_inventory_inventory_proto_rawDesc, + NumEnums: 0, + NumMessages: 4, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_pkg_platform_inventory_inventory_proto_goTypes, + DependencyIndexes: file_pkg_platform_inventory_inventory_proto_depIdxs, + MessageInfos: file_pkg_platform_inventory_inventory_proto_msgTypes, + }.Build() + File_pkg_platform_inventory_inventory_proto = out.File + file_pkg_platform_inventory_inventory_proto_rawDesc = nil + file_pkg_platform_inventory_inventory_proto_goTypes = nil + file_pkg_platform_inventory_inventory_proto_depIdxs = nil +} diff --git a/pkg/platform/inventory/inventory.proto b/pkg/platform/inventory/inventory.proto new file mode 100644 index 000000000..2da294aef --- /dev/null +++ b/pkg/platform/inventory/inventory.proto @@ -0,0 +1,69 @@ +// +// DISCLAIMER +// +// Copyright 2025 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 +// + +// Inventory Type Definition + +syntax = "proto3"; + +package types; + +import "google/protobuf/duration.proto"; +import "google/protobuf/timestamp.proto"; + +option go_package = "github.com/arangodb/kube-arangodb/pkg/platform/inventory"; + +// Defines inventory spec +message Spec { + // Deployment ID + string deployment_id = 1; + // Items + repeated Item items = 2; +} + +// Defines inventory item +message Item { + // Item Type + string type = 1; + // Dimensions + map dimensions = 2; + // Value of the Item + ItemValue value = 3; +} + +// Value of the Item +message ItemValue { + // One of the Values + oneof value { + // String + string str = 1; + // Float64 + float dec = 2; + // Int + int32 num = 3; + // Boolean + bool bool = 4; + // Time + google.protobuf.Timestamp time = 5; + // Duration + google.protobuf.Duration duration = 6; + // Int64 + int64 long_num = 7; + } +} diff --git a/pkg/platform/inventory/queries/timestamp.aql b/pkg/platform/inventory/queries/timestamp.aql new file mode 100644 index 000000000..0c1c50cf4 --- /dev/null +++ b/pkg/platform/inventory/queries/timestamp.aql @@ -0,0 +1,6 @@ +RETURN { + "type": "ARANGO_EXECUTION_TIMESTAMP", + "value": { + "time": DATE_ISO8601(DATE_NOW()) + } +} \ No newline at end of file diff --git a/pkg/platform/inventory/types.go b/pkg/platform/inventory/types.go new file mode 100644 index 000000000..b9862df1f --- /dev/null +++ b/pkg/platform/inventory/types.go @@ -0,0 +1,127 @@ +// +// DISCLAIMER +// +// Copyright 2025 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 inventory + +import ( + "reflect" + "time" + + "google.golang.org/protobuf/types/known/durationpb" + "google.golang.org/protobuf/types/known/timestamppb" + + "github.com/arangodb/kube-arangodb/pkg/util/errors" +) + +func Produce[T any](out chan<- *Item, key string, dimensions map[string]string, v T) error { + val, err := AsItemValue[T](v) + if err != nil { + return err + } + + q := &Item{ + Type: key, + Dimensions: dimensions, + Value: &ItemValue{Value: val}, + } + + if err := q.Validate(); err != nil { + return err + } + + out <- q + + return nil +} + +func (i *ItemValue) Type() (reflect.Type, error) { + switch i.Value.(type) { + case *ItemValue_Str: + return reflect.TypeFor[string](), nil + case *ItemValue_Num: + return reflect.TypeFor[int32](), nil + case *ItemValue_LongNum: + return reflect.TypeFor[int64](), nil + case *ItemValue_Bool: + return reflect.TypeFor[bool](), nil + case *ItemValue_Duration: + return reflect.TypeFor[time.Duration](), nil + case *ItemValue_Dec: + return reflect.TypeFor[float32](), nil + case *ItemValue_Time: + return reflect.TypeFor[time.Time](), nil + default: + return nil, errors.Errorf("Unknown Type: %T", i.Value) + } +} + +func AsItemValue[T any](in T) (isItemValue_Value, error) { + v := reflect.ValueOf(in).Interface() + + if reflect.TypeFor[string]() == reflect.TypeFor[T]() { + t, ok := v.(string) + if !ok { + return nil, errors.Errorf("expected type %T, got %T", t, v) + } + return &ItemValue_Str{Str: t}, nil + } + + if reflect.TypeFor[float32]() == reflect.TypeFor[T]() { + t, ok := v.(float32) + if !ok { + return nil, errors.Errorf("expected type %T, got %T", t, v) + } + return &ItemValue_Dec{Dec: t}, nil + } + + if reflect.TypeFor[int32]() == reflect.TypeFor[T]() { + t, ok := v.(int32) + if !ok { + return nil, errors.Errorf("expected type %T, got %T", t, v) + } + return &ItemValue_Num{Num: t}, nil + } + + if reflect.TypeFor[int64]() == reflect.TypeFor[T]() { + t, ok := v.(int64) + if !ok { + return nil, errors.Errorf("expected type %T, got %T", t, v) + } + return &ItemValue_LongNum{LongNum: t}, nil + } + + if reflect.TypeFor[time.Time]() == reflect.TypeFor[T]() { + t, ok := v.(time.Time) + if !ok { + return nil, errors.Errorf("expected type %T, got %T", t, v) + } + return &ItemValue_Time{Time: timestamppb.New(t.Truncate(time.Second))}, nil + } + + if reflect.TypeFor[time.Duration]() == reflect.TypeFor[T]() { + t, ok := v.(time.Duration) + if !ok { + return nil, errors.Errorf("expected type %T, got %T", t, v) + } + return &ItemValue_Duration{Duration: durationpb.New(t)}, nil + } + + return nil, errors.Errorf("not supported type: %T", in) +} diff --git a/pkg/platform/inventory/types_test.go b/pkg/platform/inventory/types_test.go new file mode 100644 index 000000000..46fecda3d --- /dev/null +++ b/pkg/platform/inventory/types_test.go @@ -0,0 +1,73 @@ +// +// DISCLAIMER +// +// Copyright 2025 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 inventory + +import ( + "fmt" + "reflect" + "testing" + "time" + + "github.com/stretchr/testify/require" + + ugrpc "github.com/arangodb/kube-arangodb/pkg/util/grpc" +) + +func testAsItemValue[T any](t *testing.T, in T, ok bool) { + t.Run(reflect.TypeFor[T]().String(), func(t *testing.T) { + if ok { + v, err := AsItemValue[T](in) + require.NoError(t, err) + require.NotNil(t, v) + + o := &ItemValue{Value: v} + + z, err := ugrpc.Marshal(o) + require.NoError(t, err) + + t.Log(string(z)) + + q, err := ugrpc.Unmarshal[*ItemValue](z) + require.NoError(t, err) + + require.EqualValues(t, v, q.GetValue()) + + tz, err := o.Type() + require.NoError(t, err) + + require.Equal(t, reflect.TypeFor[T](), tz) + } else { + _, err := AsItemValue[T](in) + require.Error(t, err) + require.EqualError(t, err, fmt.Sprintf("not supported type: %T", in)) + } + }) +} + +func Test_AsItemValue(t *testing.T) { + testAsItemValue(t, "test", true) + testAsItemValue[int32](t, 1, true) + testAsItemValue[int64](t, 1, true) + testAsItemValue[float32](t, 1.55, true) + testAsItemValue[time.Time](t, time.Now(), true) + testAsItemValue[time.Duration](t, time.Hour, true) + testAsItemValue[float64](t, 1.6, false) +} diff --git a/pkg/platform/license.go b/pkg/platform/license.go new file mode 100644 index 000000000..81fdd663d --- /dev/null +++ b/pkg/platform/license.go @@ -0,0 +1,46 @@ +// +// DISCLAIMER +// +// Copyright 2025 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 platform + +import ( + "github.com/spf13/cobra" + + "github.com/arangodb/kube-arangodb/pkg/util/cli" +) + +func license() (*cobra.Command, error) { + var cmd cobra.Command + + cmd.Use = "license" + cmd.Short = "License Package related operations" + + if err := cli.RegisterFlags(&cmd); err != nil { + return nil, err + } + + if err := withRegisterCommand(&cmd, + licenseInventory, + ); err != nil { + return nil, err + } + + return &cmd, nil +} diff --git a/pkg/platform/license_inventory.go b/pkg/platform/license_inventory.go new file mode 100644 index 000000000..374cf3d41 --- /dev/null +++ b/pkg/platform/license_inventory.go @@ -0,0 +1,113 @@ +// +// DISCLAIMER +// +// Copyright 2025 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 platform + +import ( + goHttp "net/http" + "reflect" + + "github.com/spf13/cobra" + + "github.com/arangodb/go-driver" + + "github.com/arangodb/kube-arangodb/pkg/platform/inventory" + "github.com/arangodb/kube-arangodb/pkg/util" + "github.com/arangodb/kube-arangodb/pkg/util/arangod" + "github.com/arangodb/kube-arangodb/pkg/util/cli" + "github.com/arangodb/kube-arangodb/pkg/util/errors" + "github.com/arangodb/kube-arangodb/pkg/util/globals" + ugrpc "github.com/arangodb/kube-arangodb/pkg/util/grpc" + "github.com/arangodb/kube-arangodb/pkg/util/shutdown" +) + +func licenseInventory() (*cobra.Command, error) { + var cmd cobra.Command + + cmd.Use = "inventory" + cmd.Short = "Inventory Generator" + + if err := cli.RegisterFlags(&cmd, flagDeployment); err != nil { + return nil, err + } + + cmd.RunE = getRunner().With(licenseInventoryRun).Run + + return &cmd, nil +} + +func licenseInventoryRun(cmd *cobra.Command, args []string) error { + conn, err := flagDeployment.Connection(cmd) + if err != nil { + return err + } + + resp, err := arangod.GetRequestWithTimeout[driver.VersionInfo](cmd.Context(), globals.GetGlobals().Timeouts().ArangoD().Get(), conn, "_api", "version"). + AcceptCode(goHttp.StatusOK). + Response() + if err != nil { + return err + } + + logger.Info("Discovered Arango %s (%s)", resp.Version, resp.License) + + obj, err := inventory.FetchInventory(shutdown.Context(), logger, 8, conn) + + if err != nil { + return err + } + + obj = util.FilterList(obj, func(item *inventory.Item) bool { + return item != nil + }) + + did := util.FilterList(obj, util.MultiFilterList( + func(item *inventory.Item) bool { + return item.Type == "ARANGO_DEPLOYMENT" + }, + func(item *inventory.Item) bool { + v, ok := item.Dimensions["detail"] + return ok && v == "id" + }, + )) + + if len(did) != 1 { + return errors.Errorf("Expected to find a single ARANGO_DEPLOYMENT ID") + } + + tz, err := did[0].GetValue().Type() + if err != nil { + return err + } + + if tz != reflect.TypeFor[string]() { + return errors.Errorf("Expected to find type for ARANGO_DEPLOYMENT ID") + } + + d, err := ugrpc.Marshal(&inventory.Spec{ + DeploymentId: did[0].GetValue().GetStr(), + Items: obj, + }) + if err != nil { + return err + } + + return render(cmd, string(d)) +} diff --git a/pkg/util/arangod/request.go b/pkg/util/arangod/request.go new file mode 100644 index 000000000..450a57405 --- /dev/null +++ b/pkg/util/arangod/request.go @@ -0,0 +1,167 @@ +// +// DISCLAIMER +// +// Copyright 2025 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 arangod + +import ( + "context" + "encoding/json" + "fmt" + goHttp "net/http" + goStrings "strings" + "time" + + "github.com/arangodb/go-driver" + + "github.com/arangodb/kube-arangodb/pkg/util" + "github.com/arangodb/kube-arangodb/pkg/util/errors" +) + +type Response[OUT any] interface { + AcceptCode(codes ...int) Response[OUT] + + Response() (OUT, error) + + Code() int + + Evaluate() error +} + +func NewResponseError[OUT any](err error) Response[OUT] { + return responseError[OUT]{err: err} +} + +type responseError[OUT any] struct { + err error +} + +func (r responseError[OUT]) Code() int { + return goHttp.StatusInternalServerError +} + +func (r responseError[OUT]) Response() (OUT, error) { + return util.Default[OUT](), r.err +} + +func (r responseError[OUT]) Evaluate() error { + return r.err +} + +func (r responseError[OUT]) AcceptCode(codes ...int) Response[OUT] { + return r +} + +type response[OUT any] struct { + resp driver.Response +} + +func (r response[OUT]) Code() int { + return r.resp.StatusCode() +} + +func (r response[OUT]) AcceptCode(codes ...int) Response[OUT] { + for _, code := range codes { + if r.resp.StatusCode() == code { + return r + } + } + + var data string + var obj = map[string]interface{}{} + if err := r.resp.ParseBody("", &obj); err != nil { + data = fmt.Sprintf("Error: %s", err.Error()) + } else { + if dz, err := json.Marshal(obj); err != nil { + data = fmt.Sprintf("Error: %s", err.Error()) + } else { + data = fmt.Sprintf("Data: %s", string(dz)) + } + } + + return NewResponseError[OUT](errors.Errorf("Code %d not allowed in expected status codes: %s. Body: %s", r.resp.StatusCode(), goStrings.Join(util.FormatList(codes, func(a int) string { + return fmt.Sprintf("%d", a) + }), ", "), data)) +} + +func (r response[OUT]) Response() (OUT, error) { + var d OUT + + if err := r.resp.ParseBody("", &d); err != nil { + return util.Default[OUT](), err + } + + return d, nil +} + +func (r response[OUT]) Evaluate() error { + return nil +} + +func newPath(path ...string) string { + return fmt.Sprintf("/%s", goStrings.Join(path, "/")) +} + +func GetRequestWithTimeout[OUT any](ctx context.Context, timeout time.Duration, conn driver.Connection, path ...string) Response[OUT] { + nctx, cancel := context.WithTimeout(ctx, timeout) + defer cancel() + + return GetRequest[OUT](nctx, conn, path...) +} + +func GetRequest[OUT any](ctx context.Context, conn driver.Connection, path ...string) Response[OUT] { + req, err := conn.NewRequest(goHttp.MethodGet, newPath(path...)) + if err != nil { + return NewResponseError[OUT](err) + } + + resp, err := conn.Do(ctx, req) + if err != nil { + return NewResponseError[OUT](err) + } + + return response[OUT]{resp: resp} +} + +func PostRequestWithTimeout[IN, OUT any](ctx context.Context, timeout time.Duration, conn driver.Connection, body IN, path ...string) Response[OUT] { + nctx, cancel := context.WithTimeout(ctx, timeout) + defer cancel() + + return PostRequest[IN, OUT](nctx, conn, body, path...) +} + +func PostRequest[IN, OUT any](ctx context.Context, conn driver.Connection, body IN, path ...string) Response[OUT] { + req, err := conn.NewRequest(goHttp.MethodPost, newPath(path...)) + if err != nil { + return NewResponseError[OUT](err) + } + + if r, err := req.SetBody(body); err != nil { + return NewResponseError[OUT](err) + } else { + req = r + } + + resp, err := conn.Do(ctx, req) + if err != nil { + return NewResponseError[OUT](err) + } + + return response[OUT]{resp: resp} +} diff --git a/pkg/util/cli/deployment.auth.basic.go b/pkg/util/cli/deployment.auth.basic.go new file mode 100644 index 000000000..87987d7dc --- /dev/null +++ b/pkg/util/cli/deployment.auth.basic.go @@ -0,0 +1,66 @@ +// +// DISCLAIMER +// +// Copyright 2025 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 cli + +import ( + "github.com/spf13/cobra" + + "github.com/arangodb/go-driver" +) + +type deploymentBasicAuth struct { + username Flag[string] + password Flag[string] +} + +func (d deploymentBasicAuth) GetName() string { + return "basic" +} + +func (d deploymentBasicAuth) Validate(cmd *cobra.Command) error { + return nil +} + +func (d deploymentBasicAuth) Register(cmd *cobra.Command) error { + return RegisterFlags( + cmd, + d.username, + d.password, + ) +} + +func (d deploymentBasicAuth) Authentication(cmd *cobra.Command) (driver.Authentication, error) { + if err := ValidateFlags(d.username, d.password)(cmd, nil); err != nil { + return nil, err + } + + username, err := d.username.Get(cmd) + if err != nil { + return nil, err + } + + password, err := d.password.Get(cmd) + if err != nil { + return nil, err + } + + return driver.BasicAuthentication(username, password), nil +} diff --git a/pkg/util/cli/deployment.auth.token.go b/pkg/util/cli/deployment.auth.token.go new file mode 100644 index 000000000..6290f3ae6 --- /dev/null +++ b/pkg/util/cli/deployment.auth.token.go @@ -0,0 +1,61 @@ +// +// DISCLAIMER +// +// Copyright 2025 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 cli + +import ( + "fmt" + + "github.com/spf13/cobra" + + "github.com/arangodb/go-driver" +) + +type deploymentTokenAuth struct { + token Flag[string] +} + +func (d deploymentTokenAuth) GetName() string { + return "token" +} + +func (d deploymentTokenAuth) Validate(cmd *cobra.Command) error { + return nil +} + +func (d deploymentTokenAuth) Register(cmd *cobra.Command) error { + return RegisterFlags( + cmd, + d.token, + ) +} + +func (d deploymentTokenAuth) Authentication(cmd *cobra.Command) (driver.Authentication, error) { + if err := ValidateFlags(d.token)(cmd, nil); err != nil { + return nil, err + } + + token, err := d.token.Get(cmd) + if err != nil { + return nil, err + } + + return driver.RawAuthentication(fmt.Sprintf("bearer %s", token)), nil +} diff --git a/pkg/util/cli/deployment.go b/pkg/util/cli/deployment.go new file mode 100644 index 000000000..2d0d234af --- /dev/null +++ b/pkg/util/cli/deployment.go @@ -0,0 +1,214 @@ +// +// DISCLAIMER +// +// Copyright 2025 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 cli + +import ( + "crypto/tls" + "fmt" + "net/url" + "slices" + goStrings "strings" + + "github.com/spf13/cobra" + + "github.com/arangodb/go-driver" + "github.com/arangodb/go-driver/http" + + "github.com/arangodb/kube-arangodb/pkg/util/errors" +) + +func NewDeployment(prefix string) Deployment { + return deployment{ + endpoint: Flag[[]string]{ + Name: fmt.Sprintf("%s.endpoint", prefix), + Description: "Arango Endpoint", + Check: func(in []string) error { + if len(in) == 0 { + return errors.Errorf("empty endpoint list") + } + + for _, z := range in { + if _, err := url.Parse(z); err != nil { + return errors.WithMessage(err, "invalid endpoint") + } + } + + return nil + }, + }, + + insecure: Flag[bool]{ + Name: fmt.Sprintf("%s.insecure", prefix), + Description: "Arango Endpoint Insecure", + }, + + authentication: Flag[string]{ + Name: fmt.Sprintf("%s.authentication", prefix), + Description: "Arango Endpoint Auth Method. One of: Disabled, Basic, Token", + Default: "Disabled", + Check: func(in string) error { + allowed := []string{"Disabled", "Basic", "Token"} + if !slices.Contains(allowed, in) { + return errors.Errorf("invalid auth method: %s. Allowed: %s", in, goStrings.Join(allowed, ", ")) + } + return nil + }, + }, + + basic: deploymentBasicAuth{ + + username: Flag[string]{ + Name: fmt.Sprintf("%s.basic.username", prefix), + Description: "Arango Username for Basic Authentication", + Default: "", + Check: func(in string) error { + if in == "" { + return errors.Errorf("empty username") + } + + return nil + }, + }, + + password: Flag[string]{ + Name: fmt.Sprintf("%s.basic.password", prefix), + Description: "Arango Password for Basic Authentication", + Default: "", + Check: func(in string) error { + if in == "" { + return errors.Errorf("empty password") + } + + return nil + }, + }, + }, + + token: deploymentTokenAuth{ + token: Flag[string]{ + Name: fmt.Sprintf("%s.token", prefix), + Description: "Arango JWT Token for Authentication", + Default: "", + Check: func(in string) error { + if in == "" { + return errors.Errorf("empty token") + } + + return nil + }, + }, + }, + } +} + +type Deployment interface { + FlagRegisterer + + Connection(cmd *cobra.Command) (driver.Connection, error) + Authentication(cmd *cobra.Command) (driver.Authentication, error) +} + +type deployment struct { + prefix string + + endpoint Flag[[]string] + insecure Flag[bool] + authentication Flag[string] + + basic deploymentBasicAuth + token deploymentTokenAuth +} + +func (d deployment) GetName() string { + return d.prefix +} + +func (d deployment) Register(cmd *cobra.Command) error { + return RegisterFlags( + cmd, + d.endpoint, + d.insecure, + d.authentication, + d.basic, + d.token, + ) +} + +func (d deployment) Validate(cmd *cobra.Command) error { + return ValidateFlags( + d.endpoint, + )(cmd, nil) +} + +func (d deployment) Connection(cmd *cobra.Command) (driver.Connection, error) { + var t tls.Config + + if insecure, err := d.insecure.Get(cmd); err != nil { + return nil, err + } else { + t.InsecureSkipVerify = insecure + } + + endpoint, err := d.endpoint.Get(cmd) + if err != nil { + return nil, err + } + + conn, err := http.NewConnection(http.ConnectionConfig{ + Endpoints: endpoint, + TLSConfig: &t, + }) + if err != nil { + return nil, err + } + + auth, err := d.Authentication(cmd) + if err != nil { + return nil, err + } + + if auth != nil { + conn, err = conn.SetAuthentication(auth) + if err != nil { + return nil, err + } + } + + return conn, nil +} + +func (d deployment) Authentication(cmd *cobra.Command) (driver.Authentication, error) { + auth, err := d.authentication.Get(cmd) + if err != nil { + return nil, err + } + + switch auth { + case "Disabled": + return nil, nil + case "Basic": + return d.basic.Authentication(cmd) + case "Token": + return d.token.Authentication(cmd) + default: + return nil, errors.Errorf("invalid auth method: %s", auth) + } +} diff --git a/pkg/util/executor/executor.go b/pkg/util/executor/executor.go index 86029d80a..c5670756c 100644 --- a/pkg/util/executor/executor.go +++ b/pkg/util/executor/executor.go @@ -79,9 +79,6 @@ func (h *handler) Timeout(ctx context.Context, t Thread, f RunFunc, timeout, int timeoutT := time.NewTimer(timeout) defer timeoutT.Stop() - intervalT := time.NewTicker(interval) - defer intervalT.Stop() - for { err := f(ctx, h.log, t, h) if err != nil { @@ -92,28 +89,24 @@ func (h *handler) Timeout(ctx context.Context, t Thread, f RunFunc, timeout, int return err } - t.Release() + t.Wait(interval) select { case <-timeoutT.C: return os.ErrDeadlineExceeded case <-ctx.Done(): return os.ErrDeadlineExceeded - case <-intervalT.C: - continue } } } func (h *handler) WaitForSubThreads(t Thread) { for { - t.Release() + t.Wait(10 * time.Millisecond) if h.subThreadsCompleted() { return } - - time.Sleep(10 * time.Millisecond) } } diff --git a/pkg/util/executor/threader.go b/pkg/util/executor/threader.go index 7816e1db2..d014dd065 100644 --- a/pkg/util/executor/threader.go +++ b/pkg/util/executor/threader.go @@ -22,6 +22,7 @@ package executor import ( "sync" + "time" ) func NewThreadManager(threads int) ThreadManager { @@ -58,8 +59,8 @@ func (t *threadManager) Acquire() Thread { type Thread interface { ID() ThreadID + Wait(dur time.Duration) Release() - Wait() } type thread struct { @@ -84,16 +85,22 @@ func (t *thread) Release() { return } - t.released = true - t.parent.threads <- t.id + + t.released = true } -func (t *thread) Wait() { +func (t *thread) Wait(dur time.Duration) { t.lock.Lock() defer t.lock.Unlock() + if t.released { + return + } + t.parent.threads <- t.id + time.Sleep(dur) + t.id = <-t.parent.threads } diff --git a/pkg/util/grpc/object.go b/pkg/util/grpc/object.go new file mode 100644 index 000000000..2831b1be9 --- /dev/null +++ b/pkg/util/grpc/object.go @@ -0,0 +1,42 @@ +// +// DISCLAIMER +// +// Copyright 2025 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 grpc + +import "google.golang.org/protobuf/proto" + +type Object[IN proto.Message] struct { + Object IN +} + +func (obj Object[IN]) MarshalJSON() ([]byte, error) { + return Marshal[IN](obj.Object) +} + +func (obj *Object[IN]) UnmarshalJSON(data []byte) error { + z, err := Unmarshal[IN](data) + if err != nil { + return err + } + + obj.Object = z + + return nil +} From 378b4fe08664335ff7f7d388f12474b570d3dc72 Mon Sep 17 00:00:00 2001 From: ajanikow <12255597+ajanikow@users.noreply.github.com> Date: Mon, 20 Oct 2025 10:58:35 +0000 Subject: [PATCH 2/3] Iter --- docs/cli/arangodb_operator_platform.md | 2 +- pkg/platform/license_inventory.go | 9 +++++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/docs/cli/arangodb_operator_platform.md b/docs/cli/arangodb_operator_platform.md index a4c0886b8..2ec8432a3 100644 --- a/docs/cli/arangodb_operator_platform.md +++ b/docs/cli/arangodb_operator_platform.md @@ -122,7 +122,7 @@ Use "arangodb_operator_platform license [command] --help" for more information a Inventory Generator Usage: - arangodb_operator_platform license inventory [flags] + arangodb_operator_platform license inventory [flags] output Flags: --arango.authentication string Arango Endpoint Auth Method. One of: Disabled, Basic, Token (default "Disabled") diff --git a/pkg/platform/license_inventory.go b/pkg/platform/license_inventory.go index 374cf3d41..67df98a45 100644 --- a/pkg/platform/license_inventory.go +++ b/pkg/platform/license_inventory.go @@ -22,6 +22,7 @@ package platform import ( goHttp "net/http" + "os" "reflect" "github.com/spf13/cobra" @@ -41,7 +42,7 @@ import ( func licenseInventory() (*cobra.Command, error) { var cmd cobra.Command - cmd.Use = "inventory" + cmd.Use = "inventory [flags] output" cmd.Short = "Inventory Generator" if err := cli.RegisterFlags(&cmd, flagDeployment); err != nil { @@ -54,6 +55,10 @@ func licenseInventory() (*cobra.Command, error) { } func licenseInventoryRun(cmd *cobra.Command, args []string) error { + if len(args) != 1 { + return errors.Errorf("Invalid arguments") + } + conn, err := flagDeployment.Connection(cmd) if err != nil { return err @@ -109,5 +114,5 @@ func licenseInventoryRun(cmd *cobra.Command, args []string) error { return err } - return render(cmd, string(d)) + return os.WriteFile(args[0], d, 0600) } From 5f4f4794860fd4a196cc572103e697245d8b133e Mon Sep 17 00:00:00 2001 From: ajanikow <12255597+ajanikow@users.noreply.github.com> Date: Mon, 20 Oct 2025 12:07:14 +0000 Subject: [PATCH 3/3] Iter --- pkg/platform/inventory/inventory.pb.go | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/pkg/platform/inventory/inventory.pb.go b/pkg/platform/inventory/inventory.pb.go index b0a7e281b..cb209d37e 100644 --- a/pkg/platform/inventory/inventory.pb.go +++ b/pkg/platform/inventory/inventory.pb.go @@ -1,3 +1,23 @@ +// +// DISCLAIMER +// +// Copyright 2025 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 +// + // Inventory Type Definition // Code generated by protoc-gen-go. DO NOT EDIT.