diff --git a/CHANGELOG.md b/CHANGELOG.md
index 2c036d21b..900813dd7 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -3,6 +3,7 @@
## [master](https://github.com/arangodb/kube-arangodb/tree/master) (N/A)
- (Bugfix) (Platform) Increase memory limit for Inventory
- (Feature) (LM) Inventory Generator
+- (Feature) (License) Activation CLI
## [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 2ec8432a3..9b827fc76 100644
--- a/docs/cli/arangodb_operator_platform.md
+++ b/docs/cli/arangodb_operator_platform.md
@@ -14,7 +14,7 @@ Usage:
Available Commands:
completion Generate the autocompletion script for the specified shell
help Help about any command
- license License Package related operations
+ license License related Operations
package Release Package related operations
Flags:
@@ -41,6 +41,7 @@ Available Commands:
import Imports the package from the ZIP format
install Installs the specified setup of the platform
merge Merges definitions into single file
+ registry Points all images to the new registry
Flags:
-h, --help help for package
@@ -96,13 +97,16 @@ Global Flags:
[START_INJECT]: # (arangodb_operator_platform_license_cmd)
```
-License Package related operations
+License related Operations
Usage:
arangodb_operator_platform license [command]
Available Commands:
+ activate Activates the License on ArangoDB Endpoint
+ generate Generate the License
inventory Inventory Generator
+ secret Creates Platform Secret with Registry credentials
Flags:
-h, --help help for license
@@ -138,3 +142,79 @@ Global Flags:
-n, --namespace string Kubernetes Namespace (default "default")
```
[END_INJECT]: # (arangodb_operator_platform_license_inventory_cmd)
+
+# ArangoDB Operator Platform License Activate Command
+
+[START_INJECT]: # (arangodb_operator_platform_license_activate_cmd)
+```
+Activates the License on ArangoDB Endpoint
+
+Usage:
+ arangodb_operator_platform license activate [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 activate
+ --license.client.id string LicenseManager Client ID
+ --license.client.secret string LicenseManager Client Secret
+ --license.client.stage strings LicenseManager Stages (default [prd])
+ --license.endpoint string LicenseManager Endpoint (default "license.arango.ai")
+ --license.interval duration Interval of the license synchronization
+
+Global Flags:
+ --kubeconfig string Kubernetes Config File
+ -n, --namespace string Kubernetes Namespace (default "default")
+```
+[END_INJECT]: # (arangodb_operator_platform_license_activate_cmd)
+
+# ArangoDB Operator Platform License Generate Command
+
+[START_INJECT]: # (arangodb_operator_platform_license_generate_cmd)
+```
+Generate the License
+
+Usage:
+ arangodb_operator_platform license generate [flags]
+
+Flags:
+ --deployment.id string Deployment ID
+ -h, --help help for generate
+ --inventory string Path to the Inventory File
+ --license.client.id string LicenseManager Client ID
+ --license.client.secret string LicenseManager Client Secret
+ --license.client.stage strings LicenseManager Stages (default [prd])
+ --license.endpoint string LicenseManager Endpoint (default "license.arango.ai")
+
+Global Flags:
+ --kubeconfig string Kubernetes Config File
+ -n, --namespace string Kubernetes Namespace (default "default")
+```
+[END_INJECT]: # (arangodb_operator_platform_license_generate_cmd)
+
+# ArangoDB Operator Platform License Secret Command
+
+[START_INJECT]: # (arangodb_operator_platform_license_secret_cmd)
+```
+Creates Platform Secret with Registry credentials
+
+Usage:
+ arangodb_operator_platform license secret [flags]
+
+Flags:
+ -h, --help help for secret
+ --license.client.id string LicenseManager Client ID
+ --license.client.secret string LicenseManager Client Secret
+ --license.client.stage strings LicenseManager Stages (default [prd])
+ --license.endpoint string LicenseManager Endpoint (default "license.arango.ai")
+ --secret string Kubernetes Secret Name
+
+Global Flags:
+ --kubeconfig string Kubernetes Config File
+ -n, --namespace string Kubernetes Namespace (default "default")
+```
+[END_INJECT]: # (arangodb_operator_platform_license_secret_cmd)
diff --git a/internal/docs_test.go b/internal/docs_test.go
index e9340f82f..099233844 100644
--- a/internal/docs_test.go
+++ b/internal/docs_test.go
@@ -78,8 +78,8 @@ func (d DocDefinitions) RenderMarkdown(t *testing.T, repositoryPath string) []by
els += 1
- write(t, out, "### %s\n\n", el.Path)
- write(t, out, "Type: `%s` [\\[ref\\]](%s/%s#L%d)\n\n", el.Type, repositoryPath, el.File, el.Line)
+ writef(t, out, "### %s\n\n", el.Path)
+ writef(t, out, "Type: `%s` [\\[ref\\]](%s/%s#L%d)\n\n", el.Type, repositoryPath, el.File, el.Line)
if grade := el.Grade; grade != nil {
switch grade.Grade {
@@ -88,7 +88,7 @@ func (d DocDefinitions) RenderMarkdown(t *testing.T, repositoryPath string) []by
write(t, out, "> ***DEPRECATED***\n")
write(t, out, "> \n")
for _, line := range grade.Message {
- write(t, out, "> **%s**\n", line)
+ writef(t, out, "> **%s**\n", line)
}
write(t, out, "\n")
case DocDefinitionGradeAlpha:
@@ -96,7 +96,7 @@ func (d DocDefinitions) RenderMarkdown(t *testing.T, repositoryPath string) []by
write(t, out, "> ***ALPHA***\n")
write(t, out, "> \n")
for _, line := range grade.Message {
- write(t, out, "> **%s**\n", line)
+ writef(t, out, "> **%s**\n", line)
}
write(t, out, "\n")
case DocDefinitionGradeBeta:
@@ -104,7 +104,7 @@ func (d DocDefinitions) RenderMarkdown(t *testing.T, repositoryPath string) []by
write(t, out, "> ***BETA***\n")
write(t, out, "> \n")
for _, line := range grade.Message {
- write(t, out, "> **%s**\n", line)
+ writef(t, out, "> **%s**\n", line)
}
write(t, out, "\n")
}
@@ -112,20 +112,20 @@ func (d DocDefinitions) RenderMarkdown(t *testing.T, repositoryPath string) []by
if d := el.Important; d != nil {
write(t, out, "> [!IMPORTANT]\n")
- write(t, out, "> **%s**\n\n", *d)
+ writef(t, out, "> **%s**\n\n", *d)
}
if d := el.Required; d != nil {
if *d == "" {
write(t, out, "This field is **required**\n\n")
} else {
- write(t, out, "This field is **required**: %s\n\n", *d)
+ writef(t, out, "This field is **required**: %s\n\n", *d)
}
}
if len(el.Docs) > 0 {
for _, doc := range el.Docs {
- write(t, out, "%s\n", doc)
+ writef(t, out, "%s\n", doc)
}
write(t, out, "\n")
}
@@ -136,9 +136,9 @@ func (d DocDefinitions) RenderMarkdown(t *testing.T, repositoryPath string) []by
for _, link := range el.Links {
z := goStrings.Split(link, "|")
if len(z) == 1 {
- write(t, out, "* [Documentation](%s)\n", z[0])
+ writef(t, out, "* [Documentation](%s)\n", z[0])
} else if len(z) == 2 {
- write(t, out, "* [%s](%s)\n", z[0], z[1])
+ writef(t, out, "* [%s](%s)\n", z[0], z[1])
} else {
require.Fail(t, "Invalid link format")
}
@@ -151,7 +151,7 @@ func (d DocDefinitions) RenderMarkdown(t *testing.T, repositoryPath string) []by
write(t, out, "Example:\n")
write(t, out, "```yaml\n")
for _, example := range el.Example {
- write(t, out, "%s\n", example)
+ writef(t, out, "%s\n", example)
}
write(t, out, "```\n\n")
}
@@ -167,9 +167,9 @@ func (d DocDefinitions) RenderMarkdown(t *testing.T, repositoryPath string) []by
}
if len(z) == 1 {
- write(t, out, "* %s\n", snip)
+ writef(t, out, "* %s\n", snip)
} else if len(z) == 2 {
- write(t, out, "* %s - %s\n", snip, z[1])
+ writef(t, out, "* %s - %s\n", snip, z[1])
} else {
require.Fail(t, "Invalid enum format")
}
@@ -177,7 +177,7 @@ func (d DocDefinitions) RenderMarkdown(t *testing.T, repositoryPath string) []by
write(t, out, "\n")
} else {
if d := el.Default; d != nil {
- write(t, out, "Default Value: `%s`\n\n", *d)
+ writef(t, out, "Default Value: `%s`\n\n", *d)
}
}
@@ -185,7 +185,7 @@ func (d DocDefinitions) RenderMarkdown(t *testing.T, repositoryPath string) []by
if *d == "" {
write(t, out, "This field is **immutable**\n\n")
} else {
- write(t, out, "This field is **immutable**: %s\n\n", *d)
+ writef(t, out, "This field is **immutable**: %s\n\n", *d)
}
}
}
@@ -545,10 +545,10 @@ func generateDocs(t *testing.T, objects map[string]map[string]interface{}, field
"title": objName,
"parent": apiIndexPageTitle,
})
- write(t, out, "# API Reference for %s\n\n", objName)
+ writef(t, out, "# API Reference for %s\n\n", objName)
util.IterateSorted(renderSections, func(name string, section []byte) {
- write(t, out, "## %s\n\n", util.BoolSwitch(name == "", "Object", name))
+ writef(t, out, "## %s\n\n", util.BoolSwitch(name == "", "Object", name))
_, err = out.Write(section)
require.NoError(t, err)
@@ -558,7 +558,12 @@ func generateDocs(t *testing.T, objects map[string]map[string]interface{}, field
return outPaths
}
-func write(t *testing.T, out io.Writer, format string, args ...interface{}) {
+func write(t *testing.T, out io.Writer, format string) {
+ _, err := out.Write([]byte(format))
+ require.NoError(t, err)
+}
+
+func writef(t *testing.T, out io.Writer, format string, args ...interface{}) {
_, err := out.Write([]byte(fmt.Sprintf(format, args...)))
require.NoError(t, err)
}
diff --git a/internal/readme_cli.go b/internal/readme_cli.go
index 4bbb17eab..85889018f 100644
--- a/internal/readme_cli.go
+++ b/internal/readme_cli.go
@@ -121,6 +121,30 @@ func GenerateCLIArangoDBOperatorPlatformReadme(root string) error {
readmeSections["arangodb_operator_platform_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", "activate"); err != nil {
+ return err
+ } else {
+ readmeSections["arangodb_operator_platform_license_activate_cmd"] = section
+ }
+
+ if section, err := GenerateHelpQuoted(cmd, "license", "generate"); err != nil {
+ return err
+ } else {
+ readmeSections["arangodb_operator_platform_license_generate_cmd"] = section
+ }
+
+ if section, err := GenerateHelpQuoted(cmd, "license", "secret"); err != nil {
+ return err
+ } else {
+ readmeSections["arangodb_operator_platform_license_secret_cmd"] = section
+ }
+
if section, err := GenerateHelpQuoted(cmd, "package"); err != nil {
return err
} else {
diff --git a/pkg/license/manager/client.go b/pkg/license/manager/client.go
new file mode 100644
index 000000000..9f3dbafe7
--- /dev/null
+++ b/pkg/license/manager/client.go
@@ -0,0 +1,100 @@
+//
+// 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 manager
+
+import (
+ "context"
+ "fmt"
+ goHttp "net/http"
+ "time"
+
+ "github.com/arangodb/go-driver"
+ "github.com/arangodb/go-driver/http"
+
+ "github.com/arangodb/kube-arangodb/pkg/platform/inventory"
+ "github.com/arangodb/kube-arangodb/pkg/util"
+ "github.com/arangodb/kube-arangodb/pkg/util/arangod"
+ ugrpc "github.com/arangodb/kube-arangodb/pkg/util/grpc"
+ operatorHTTP "github.com/arangodb/kube-arangodb/pkg/util/http"
+)
+
+func NewClient(endpoint, id, key string, mods ...util.Mod[goHttp.Transport]) (Client, error) {
+ transport := operatorHTTP.Transport(mods...)
+
+ stageEndpoint := fmt.Sprintf("https://%s", endpoint)
+
+ connConfig := http.ConnectionConfig{
+ Transport: transport,
+ DontFollowRedirect: true,
+ Endpoints: []string{stageEndpoint},
+ }
+
+ conn, err := http.NewConnection(connConfig)
+ if err != nil {
+ return nil, err
+ }
+
+ conn, err = conn.SetAuthentication(driver.BasicAuthentication(id, key))
+ if err != nil {
+ return nil, err
+ }
+
+ return NewClientFromConn(conn), nil
+}
+
+func NewClientFromConn(conn driver.Connection) Client {
+ return client{
+ conn: conn,
+ }
+}
+
+type Client interface {
+ License(ctx context.Context, req LicenseRequest) (LicenseResponse, error)
+
+ Registry(ctx context.Context) (RegistryResponse, error)
+}
+
+type LicenseRequest struct {
+ DeploymentID *string `json:"deployment_id,omitempty"`
+ TTL *time.Duration `json:"ttl,omitempty"`
+ Inventory *ugrpc.Object[*inventory.Spec] `json:"inventory,omitempty"`
+}
+
+type LicenseResponse struct {
+ ID string `json:"id"`
+ License string `json:"license"`
+}
+
+type RegistryResponse struct {
+ Token string `json:"token"`
+}
+
+type client struct {
+ conn driver.Connection
+}
+
+func (c client) License(ctx context.Context, req LicenseRequest) (LicenseResponse, error) {
+ return arangod.PostRequest[LicenseRequest, LicenseResponse](ctx, c.conn, req, "_api", "v1", "license").AcceptCode(200).Response()
+}
+
+func (c client) Registry(ctx context.Context) (RegistryResponse, error) {
+ return arangod.GetRequest[RegistryResponse](ctx, c.conn, "_api", "v1", "registry", "token").AcceptCode(200).Response()
+}
diff --git a/pkg/license/manager/registry.go b/pkg/license/manager/registry.go
new file mode 100644
index 000000000..0c66317c2
--- /dev/null
+++ b/pkg/license/manager/registry.go
@@ -0,0 +1,63 @@
+//
+// 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 manager
+
+import (
+ "encoding/base64"
+ "fmt"
+
+ "github.com/arangodb/kube-arangodb/pkg/util/errors"
+)
+
+type Registry struct {
+ Auths map[string]RegistryAuth `json:"auths,omitempty"`
+}
+
+type RegistryAuth struct {
+ Client string `json:"client"`
+ Auth string `json:"auth,omitempty"`
+}
+
+func NewRegistryAuth(endpoint, username, password string, stages ...Stage) (*Registry, error) {
+ if len(stages) == 0 {
+ return nil, errors.Errorf("Enable Auth for at least one stage")
+ }
+
+ var r Registry
+
+ r.Auths = map[string]RegistryAuth{}
+
+ ra := RegistryAuth{
+ Client: username,
+ Auth: base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("%s:%s", username, password))),
+ }
+
+ for _, s := range stages {
+ domain, err := s.RegistryDomain(endpoint)
+ if err != nil {
+ return nil, err
+ }
+
+ r.Auths[domain] = ra
+ }
+
+ return &r, nil
+}
diff --git a/pkg/license/manager/stage.go b/pkg/license/manager/stage.go
new file mode 100644
index 000000000..8774a73fd
--- /dev/null
+++ b/pkg/license/manager/stage.go
@@ -0,0 +1,70 @@
+//
+// 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 manager
+
+import (
+ "fmt"
+ goStrings "strings"
+
+ "github.com/arangodb/kube-arangodb/pkg/util"
+ "github.com/arangodb/kube-arangodb/pkg/util/errors"
+)
+
+type Stage int
+
+func ParseStages(s ...string) []Stage {
+ return util.FormatList(s, func(a string) Stage {
+ return ParseStage(a)
+ })
+}
+
+func ParseStage(s string) Stage {
+ switch goStrings.ToLower(s) {
+ case "dev":
+ return StageDev
+ case "qa":
+ return StageQA
+ case "prd":
+ return StagePrd
+ default:
+ return StageUnknown
+ }
+}
+
+const (
+ StageUnknown Stage = iota
+ StageDev
+ StageQA
+ StagePrd
+)
+
+func (s Stage) RegistryDomain(domain string) (string, error) {
+ switch s {
+ case StageDev:
+ return fmt.Sprintf("dev.registry.%s", domain), nil
+ case StageQA:
+ return fmt.Sprintf("qa.registry.%s", domain), nil
+ case StagePrd:
+ return fmt.Sprintf("registry.%s", domain), nil
+ }
+
+ return "", errors.Errorf("invalid stage")
+}
diff --git a/pkg/platform/flags.go b/pkg/platform/flags.go
index 97e61af0f..2ca528436 100644
--- a/pkg/platform/flags.go
+++ b/pkg/platform/flags.go
@@ -22,6 +22,7 @@ package platform
import (
"os"
+ "time"
sharedApi "github.com/arangodb/kube-arangodb/pkg/apis/shared/v1"
"github.com/arangodb/kube-arangodb/pkg/util"
@@ -45,6 +46,24 @@ var (
Persistent: true,
}
+ flagSecret = cli.Flag[string]{
+ Name: "secret",
+ Description: "Kubernetes Secret Name",
+ Default: "",
+ Check: func(in string) error {
+ if in == "" {
+ return nil
+ }
+
+ if err := sharedApi.IsValidName(in); err != nil {
+ return errors.Errorf("Invalid secret name: %s", err.Error())
+ }
+
+ return nil
+ },
+ Persistent: true,
+ }
+
flagPlatformName = cli.Flag[string]{
Name: "platform.name",
Description: "Kubernetes Platform Name (name of the ArangoDeployment)",
@@ -59,6 +78,33 @@ var (
},
}
+ flagInventory = cli.Flag[string]{
+ Name: "inventory",
+ Description: "Path to the Inventory File",
+ Default: "",
+ Persistent: true,
+ Check: func(in string) error {
+ if in == "" {
+ return nil
+ }
+ _, err := os.Stat(in)
+ if err != nil {
+ return err
+ }
+ return nil
+ },
+ }
+
+ flagDeploymentID = cli.Flag[string]{
+ Name: "deployment.id",
+ Description: "Deployment ID",
+ Default: "",
+ Persistent: false,
+ Check: func(in string) error {
+ return nil
+ },
+ }
+
flagOutput = cli.Flag[string]{
Name: "output",
Short: "o",
@@ -100,6 +146,8 @@ var (
},
}
+ flagLicenseManager = cli.NewLicenseManager("license")
+
flagDeployment = cli.NewDeployment("arango")
flagValues = cli.Flag[[]string]{
@@ -142,4 +190,17 @@ var (
Description: "List of boosted registries",
Default: nil,
}
+
+ flagActivateInterval = cli.Flag[time.Duration]{
+ Name: "license.interval",
+ Description: "Interval of the license synchronization",
+ Default: 0,
+ Persistent: false,
+ Check: func(in time.Duration) error {
+ if in < 0 {
+ return errors.New("License Generation Interval cannot be negative")
+ }
+ return nil
+ },
+ }
)
diff --git a/pkg/platform/license.go b/pkg/platform/license.go
index 81fdd663d..6cac81698 100644
--- a/pkg/platform/license.go
+++ b/pkg/platform/license.go
@@ -21,16 +21,26 @@
package platform
import (
+ goHttp "net/http"
+ "reflect"
+
+ "github.com/pkg/errors"
"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/globals"
)
func license() (*cobra.Command, error) {
var cmd cobra.Command
cmd.Use = "license"
- cmd.Short = "License Package related operations"
+ cmd.Short = "License related Operations"
if err := cli.RegisterFlags(&cmd); err != nil {
return nil, err
@@ -38,9 +48,68 @@ func license() (*cobra.Command, error) {
if err := withRegisterCommand(&cmd,
licenseInventory,
+ licenseSecret,
+ licenseActivate,
+ licenseGenerate,
); err != nil {
return nil, err
}
return &cmd, nil
}
+
+func buildInventory(cmd *cobra.Command) (*inventory.Spec, error) {
+ logger.Info("Connecting to the server...")
+
+ conn, err := flagDeployment.Connection(cmd)
+ if err != nil {
+ return nil, 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 nil, err
+ }
+
+ logger.Info("Discovered Arango %s (%s)", resp.Version, resp.License)
+
+ obj, err := inventory.FetchInventory(cmd.Context(), logger, 8, conn)
+
+ if err != nil {
+ return nil, 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 nil, errors.Errorf("Expected to find a single ARANGO_DEPLOYMENT ID")
+ }
+
+ tz, err := did[0].GetValue().Type()
+ if err != nil {
+ return nil, err
+ }
+
+ if tz != reflect.TypeFor[string]() {
+ return nil, errors.Errorf("Expected to find type for ARANGO_DEPLOYMENT ID")
+ }
+
+ return &inventory.Spec{
+ DeploymentId: did[0].GetValue().GetStr(),
+ Items: obj,
+ }, nil
+}
diff --git a/pkg/platform/license_activate.go b/pkg/platform/license_activate.go
new file mode 100644
index 000000000..f608b4783
--- /dev/null
+++ b/pkg/platform/license_activate.go
@@ -0,0 +1,130 @@
+//
+// 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 (
+ "time"
+
+ "github.com/spf13/cobra"
+
+ "github.com/arangodb/kube-arangodb/pkg/deployment/client"
+ "github.com/arangodb/kube-arangodb/pkg/license/manager"
+ "github.com/arangodb/kube-arangodb/pkg/logging"
+ "github.com/arangodb/kube-arangodb/pkg/util"
+ "github.com/arangodb/kube-arangodb/pkg/util/cli"
+ ugrpc "github.com/arangodb/kube-arangodb/pkg/util/grpc"
+)
+
+func licenseActivate() (*cobra.Command, error) {
+ var cmd cobra.Command
+
+ cmd.Use = "activate"
+ cmd.Short = "Activates the License on ArangoDB Endpoint"
+
+ if err := cli.RegisterFlags(&cmd, flagLicenseManager, flagActivateInterval, flagDeployment); err != nil {
+ return nil, err
+ }
+
+ cmd.RunE = getRunner().With(licenseActivateRun).Run
+
+ return &cmd, nil
+}
+
+func licenseActivateRun(cmd *cobra.Command, args []string) error {
+ mc, err := flagLicenseManager.Client(cmd)
+ if err != nil {
+ return err
+ }
+
+ del, err := flagActivateInterval.Get(cmd)
+ if err != nil {
+ return err
+ }
+
+ if del == 0 {
+ logger.Info("Activate Once")
+
+ return licenseActivateExecute(cmd, logger, mc)
+ }
+
+ intervalT := time.NewTicker(del)
+ defer intervalT.Stop()
+
+ logger.Dur("interval", del).Info("Activate In interval")
+
+ for {
+ if err := licenseActivateExecute(cmd, logger, mc); err != nil {
+ return err
+ }
+
+ select {
+ case <-intervalT.C:
+ continue
+ case <-cmd.Context().Done():
+ return nil
+ }
+ }
+}
+
+func licenseActivateExecute(cmd *cobra.Command, logger logging.Logger, mc manager.Client) error {
+ conn, err := flagDeployment.Connection(cmd)
+ if err != nil {
+ return err
+ }
+
+ c := client.NewClient(conn, logger)
+
+ inv, err := buildInventory(cmd)
+ if err != nil {
+ return err
+ }
+
+ l := logger.Str("DeploymentID", inv.DeploymentId)
+
+ l.Info("Discovered DeploymentID")
+
+ l.Info("Generating License")
+
+ lic, err := mc.License(cmd.Context(), manager.LicenseRequest{
+ DeploymentID: util.NewType(inv.DeploymentId),
+ Inventory: util.NewType(ugrpc.NewObject(inv)),
+ })
+ if err != nil {
+ return err
+ }
+
+ l = l.Str("LicenseID", lic.ID)
+
+ l.Info("Activating license...")
+
+ if err := c.SetLicense(cmd.Context(), lic.License, true); err != nil {
+ return err
+ }
+
+ nlic, err := c.GetLicense(cmd.Context())
+ if err != nil {
+ return err
+ }
+
+ l.Str("hash", nlic.Hash).Info("Activated!")
+
+ return nil
+}
diff --git a/pkg/platform/license_generate.go b/pkg/platform/license_generate.go
new file mode 100644
index 000000000..660e20943
--- /dev/null
+++ b/pkg/platform/license_generate.go
@@ -0,0 +1,92 @@
+//
+// 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 (
+ "fmt"
+ "os"
+
+ "github.com/spf13/cobra"
+
+ "github.com/arangodb/kube-arangodb/pkg/license/manager"
+ "github.com/arangodb/kube-arangodb/pkg/platform/inventory"
+ "github.com/arangodb/kube-arangodb/pkg/util"
+ "github.com/arangodb/kube-arangodb/pkg/util/cli"
+ ugrpc "github.com/arangodb/kube-arangodb/pkg/util/grpc"
+)
+
+func licenseGenerate() (*cobra.Command, error) {
+ var cmd cobra.Command
+
+ cmd.Use = "generate"
+ cmd.Short = "Generate the License"
+
+ if err := cli.RegisterFlags(&cmd, flagLicenseManager, flagDeploymentID, flagInventory); err != nil {
+ return nil, err
+ }
+
+ cmd.RunE = getRunner().With(licenseGenerateRun).Run
+
+ return &cmd, nil
+}
+
+func licenseGenerateRun(cmd *cobra.Command, args []string) error {
+ mc, err := flagLicenseManager.Client(cmd)
+ if err != nil {
+ return err
+ }
+
+ did, err := flagDeploymentID.Get(cmd)
+ if err != nil {
+ return err
+ }
+
+ var inv *inventory.Spec
+
+ if invFile, err := flagInventory.Get(cmd); err != nil {
+ return err
+ } else if invFile != "" {
+ inv, err = ugrpc.UnmarshalFile[*inventory.Spec](invFile)
+ if err != nil {
+ return err
+ }
+ }
+
+ l := logger.Str("ClusterID", did)
+
+ l.Info("Generating License")
+
+ lic, err := mc.License(cmd.Context(), manager.LicenseRequest{
+ DeploymentID: util.NewType(did),
+ Inventory: util.NewType(ugrpc.NewObject(inv)),
+ })
+ if err != nil {
+ return err
+ }
+
+ l = l.Str("LicenseID", lic.ID)
+
+ l.Info("License Generated and printed to STDERR")
+
+ fmt.Fprint(os.Stderr, lic.License)
+
+ return nil
+}
diff --git a/pkg/platform/license_inventory.go b/pkg/platform/license_inventory.go
index 67df98a45..1ab78e859 100644
--- a/pkg/platform/license_inventory.go
+++ b/pkg/platform/license_inventory.go
@@ -21,22 +21,13 @@
package platform
import (
- goHttp "net/http"
"os"
- "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) {
@@ -59,57 +50,12 @@ func licenseInventoryRun(cmd *cobra.Command, args []string) error {
return errors.Errorf("Invalid arguments")
}
- 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)
-
+ inv, err := buildInventory(cmd)
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,
- })
+ d, err := ugrpc.Marshal(inv)
if err != nil {
return err
}
diff --git a/pkg/platform/license_secret.go b/pkg/platform/license_secret.go
new file mode 100644
index 000000000..75e8080f0
--- /dev/null
+++ b/pkg/platform/license_secret.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 platform
+
+import (
+ "encoding/json"
+ "fmt"
+ "os"
+
+ "github.com/spf13/cobra"
+ core "k8s.io/api/core/v1"
+ meta "k8s.io/apimachinery/pkg/apis/meta/v1"
+ "sigs.k8s.io/yaml"
+
+ "github.com/arangodb/kube-arangodb/pkg/license/manager"
+ "github.com/arangodb/kube-arangodb/pkg/util/cli"
+ "github.com/arangodb/kube-arangodb/pkg/util/k8sutil/kerrors"
+)
+
+func licenseSecret() (*cobra.Command, error) {
+ var cmd cobra.Command
+
+ cmd.Use = "secret"
+ cmd.Short = "Creates Platform Secret with Registry credentials"
+
+ if err := cli.RegisterFlags(&cmd, flagSecret, flagLicenseManager); err != nil {
+ return nil, err
+ }
+
+ cmd.RunE = getRunner().With(licenseSecretRun).Run
+
+ return &cmd, nil
+}
+
+func licenseSecretRun(cmd *cobra.Command, args []string) error {
+ client, err := getKubernetesClient(cmd)
+ if err != nil {
+ return err
+ }
+
+ name, err := flagSecret.Get(cmd)
+ if err != nil {
+ return err
+ }
+
+ namespace, err := flagNamespace.Get(cmd)
+ if err != nil {
+ return err
+ }
+
+ stages, err := flagLicenseManager.Stages(cmd)
+ if err != nil {
+ return err
+ }
+
+ id, err := flagLicenseManager.ClientID(cmd)
+ if err != nil {
+ return err
+ }
+
+ endpoint, err := flagLicenseManager.Endpoint(cmd)
+ if err != nil {
+ return err
+ }
+
+ mc, err := flagLicenseManager.Client(cmd)
+ if err != nil {
+ return err
+ }
+
+ secret, err := mc.Registry(cmd.Context())
+ if err != nil {
+ return err
+ }
+
+ logger.Info("Creating new Registry Token")
+
+ r, err := manager.NewRegistryAuth(endpoint, id, secret.Token, manager.ParseStages(stages...)...)
+ if err != nil {
+ return err
+ }
+
+ logger.Info("New Registry Token Created")
+
+ data, err := json.Marshal(r)
+ if err != nil {
+ return err
+ }
+
+ if name != "" {
+ sClient := client.Kubernetes().CoreV1().Secrets(namespace)
+
+ l := logger.Str("namespace", namespace).Str("secret", name)
+
+ if s, err := sClient.Get(cmd.Context(), name, meta.GetOptions{}); err != nil {
+ if !kerrors.IsNotFound(err) {
+ return err
+ }
+
+ l.Info("Secret not found, creating")
+
+ if _, err := sClient.Create(cmd.Context(), &core.Secret{
+ ObjectMeta: meta.ObjectMeta{
+ Name: name,
+ Namespace: namespace,
+ },
+ Type: core.SecretTypeDockerConfigJson,
+ Data: map[string][]byte{
+ ".dockerconfigjson": data,
+ },
+ }, meta.CreateOptions{}); err != nil {
+ return err
+ }
+
+ l.Info("Secret Created")
+ } else {
+ l.Info("Secret found, updating")
+
+ s.Data = map[string][]byte{
+ ".dockerconfigjson": data,
+ }
+
+ if _, err := sClient.Update(cmd.Context(), s, meta.UpdateOptions{}); err != nil {
+ return err
+ }
+
+ l.Info("Secret Updated")
+ }
+ } else {
+ resp, err := yaml.Marshal(&core.Secret{
+ ObjectMeta: meta.ObjectMeta{
+ Name: "name",
+ },
+ Type: core.SecretTypeDockerConfigJson,
+ Data: map[string][]byte{
+ ".dockerconfigjson": data,
+ },
+ })
+ if err != nil {
+ return err
+ }
+
+ logger.Info("Create Secret Manually. Secret printed to STDERR")
+
+ fmt.Fprint(os.Stderr, string(resp))
+ }
+
+ return nil
+}
diff --git a/pkg/platform/pack/export.go b/pkg/platform/pack/export.go
index 8df3c4c82..131483f5d 100644
--- a/pkg/platform/pack/export.go
+++ b/pkg/platform/pack/export.go
@@ -168,7 +168,6 @@ func (r *exportPackageSet) exportPackage(name string, spec helm.PackageSpec) exe
h.RunAsync(ctx, r.exportImage(v))
v.Registry = nil
- v.Kind = ""
chartProto.Images[k] = v
}
diff --git a/pkg/platform/pack/import.go b/pkg/platform/pack/import.go
index 00395c1ef..a724990fb 100644
--- a/pkg/platform/pack/import.go
+++ b/pkg/platform/pack/import.go
@@ -103,10 +103,6 @@ func (i *importPackageSet) run(p Proto) executor.RunFunc {
h.RunAsync(ctx, i.importManifest(src, v))
}
- type valuesInterface struct {
- Images ProtoImages `json:"images,omitempty"`
- }
-
h.WaitForSubThreads(t)
for k, v := range p.Charts {
@@ -120,7 +116,7 @@ func (i *importPackageSet) run(p Proto) executor.RunFunc {
pkgS.Chart = data
pkgS.Version = v.Version
- var versions valuesInterface
+ var versions ProtoValues
versions.Images = map[string]ProtoImage{}
diff --git a/pkg/platform/pack/proto.go b/pkg/platform/pack/proto.go
index d9a9acb10..ba25f3a21 100644
--- a/pkg/platform/pack/proto.go
+++ b/pkg/platform/pack/proto.go
@@ -20,7 +20,11 @@
package pack
-import "fmt"
+import (
+ "fmt"
+
+ "github.com/arangodb/kube-arangodb/pkg/util"
+)
type Proto struct {
Charts ProtoCharts `json:"charts,omitempty"`
@@ -36,17 +40,21 @@ type ProtoChart struct {
Images ProtoImages `json:"images,omitempty"`
}
+type ProtoValues struct {
+ Images ProtoImages `json:"images,omitempty"`
+}
+
type ProtoImages map[string]ProtoImage
type ProtoImage struct {
Registry *string `json:"registry,omitempty"`
- Image string `json:"image"`
- Tag string `json:"tag"`
- Kind string `json:"kind,omitempty"`
+ Image string `json:"image,omitempty"`
+ Tag string `json:"tag,omitempty"`
+ Kind *string `json:"kind,omitempty"`
}
func (p ProtoImage) IsTest() bool {
- return p.Kind == "Test"
+ return util.OptionalType(p.Kind, "") == "Test"
}
func (p ProtoImage) GetShortImage() string {
diff --git a/pkg/platform/pack/registry.go b/pkg/platform/pack/registry.go
new file mode 100644
index 000000000..56c19c8ef
--- /dev/null
+++ b/pkg/platform/pack/registry.go
@@ -0,0 +1,143 @@
+//
+// 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 pack
+
+import (
+ "context"
+ "sync"
+
+ "github.com/arangodb/kube-arangodb/pkg/logging"
+ "github.com/arangodb/kube-arangodb/pkg/util"
+ "github.com/arangodb/kube-arangodb/pkg/util/errors"
+ "github.com/arangodb/kube-arangodb/pkg/util/executor"
+ "github.com/arangodb/kube-arangodb/pkg/util/k8sutil/helm"
+)
+
+func Registry(ctx context.Context, registry string, m helm.ChartManager, p helm.Package) (helm.Package, error) {
+ i := ®istryExport{
+ registry: registry,
+ m: m,
+ }
+
+ if err := executor.Run(ctx, logger, 8, i.run(p)); err != nil {
+ return helm.Package{}, err
+ }
+
+ return i.p, nil
+}
+
+type registryExport struct {
+ lock sync.Mutex
+
+ registry string
+
+ p helm.Package
+
+ m helm.ChartManager
+}
+
+func (i *registryExport) run(p helm.Package) executor.RunFunc {
+ return func(ctx context.Context, log logging.Logger, t executor.Thread, h executor.Handler) error {
+ for k, v := range p.Packages {
+ h.RunAsync(ctx, i.exportPackage(k, v))
+ }
+
+ return nil
+ }
+}
+
+func (i *registryExport) exportPackage(name string, spec helm.PackageSpec) executor.RunFunc {
+ return func(ctx context.Context, log logging.Logger, t executor.Thread, h executor.Handler) error {
+ var pkgS helm.PackageSpec
+
+ repo, ok := i.m.Get(name)
+ if !ok {
+ return errors.Errorf("Chart `%s` not found", name)
+ }
+
+ ver, ok := repo.Get(spec.Version)
+ if !ok {
+ return errors.Errorf("Chart `%s=%s` not found", name, spec.Version)
+ }
+
+ c, err := ver.Get(ctx)
+ if err != nil {
+ return err
+ }
+
+ pkgS.Version = spec.Version
+
+ chartData, err := c.Get()
+ if err != nil {
+ return err
+ }
+
+ type valuesInterface struct {
+ Images ProtoImages `json:"images,omitempty"`
+ }
+
+ protoImages, err := util.JSONRemarshal[map[string]any, valuesInterface](chartData.Chart().Values)
+ if err != nil {
+ return err
+ }
+
+ var versions ProtoValues
+
+ versions.Images = map[string]ProtoImage{}
+
+ for k, v := range protoImages.Images {
+ if v.IsTest() {
+ logger.Str("image", v.GetImage()).Info("Skip Test Image")
+ continue
+ }
+
+ versions.Images[k] = ProtoImage{
+ Registry: util.NewType(i.registry),
+ }
+ }
+
+ vData, err := helm.NewValues(versions)
+ if err != nil {
+ return err
+ }
+
+ pkgS.Overrides = vData
+
+ i.withPackage(func(in helm.Package) helm.Package {
+ if in.Packages == nil {
+ in.Packages = map[string]helm.PackageSpec{}
+ }
+
+ in.Packages[name] = pkgS
+
+ return in
+ })
+
+ return nil
+ }
+}
+
+func (i *registryExport) withPackage(mod util.ModR[helm.Package]) {
+ i.lock.Lock()
+ defer i.lock.Unlock()
+
+ i.p = mod(i.p)
+}
diff --git a/pkg/platform/package.go b/pkg/platform/package.go
index 38f85aec7..228b8cced 100644
--- a/pkg/platform/package.go
+++ b/pkg/platform/package.go
@@ -42,6 +42,7 @@ func pkg() (*cobra.Command, error) {
packageExport,
packageImport,
packageMerge,
+ packageRegistry,
); err != nil {
return nil, err
}
diff --git a/pkg/platform/package_registry.go b/pkg/platform/package_registry.go
new file mode 100644
index 000000000..1bb098779
--- /dev/null
+++ b/pkg/platform/package_registry.go
@@ -0,0 +1,83 @@
+//
+// 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 (
+ "bytes"
+ "os"
+
+ "github.com/spf13/cobra"
+ "sigs.k8s.io/yaml"
+
+ "github.com/arangodb/kube-arangodb/pkg/platform/pack"
+ "github.com/arangodb/kube-arangodb/pkg/util/cli"
+ "github.com/arangodb/kube-arangodb/pkg/util/errors"
+)
+
+func packageRegistry() (*cobra.Command, error) {
+ var cmd cobra.Command
+
+ cmd.Use = "registry [flags] registry package output"
+ cmd.Short = "Points all images to the new registry"
+
+ if err := cli.RegisterFlags(&cmd, flagPlatformEndpoint); err != nil {
+ return nil, err
+ }
+
+ cmd.RunE = getRunner().With(packageRegistryRun).Run
+
+ return &cmd, nil
+}
+
+func packageRegistryRun(cmd *cobra.Command, args []string) error {
+ if len(args) != 3 {
+ return errors.Errorf("Invalid arguments")
+ }
+
+ registry := args[0]
+
+ pkg, err := getHelmPackages(args[1])
+ if err != nil {
+ logger.Err(err).Error("Unable to read the file")
+ return err
+ }
+
+ out := args[2]
+
+ cm, err := getChartManager(cmd)
+ if err != nil {
+ return err
+ }
+
+ p, err := pack.Registry(cmd.Context(), registry, cm, pkg)
+ if err != nil {
+ return err
+ }
+
+ data, err := yaml.Marshal(p)
+ if err != nil {
+ return err
+ }
+
+ data = bytes.Join([][]byte{[]byte("---\n\n"), data}, nil)
+
+ return os.WriteFile(out, data, 0644)
+}
diff --git a/pkg/platform/runner.go b/pkg/platform/runner.go
index 837162e83..ea38761ae 100644
--- a/pkg/platform/runner.go
+++ b/pkg/platform/runner.go
@@ -30,12 +30,15 @@ func getRunner() cli.Runner {
logging.Runner,
cli.ValidateFlags(
flagNamespace,
+ flagSecret,
flagPlatformName,
flagPlatformEndpoint,
flagOutput,
flagUpgradeVersions,
flagAll,
flagValues,
+ flagDeployment,
+ flagLicenseManager,
),
}
}
diff --git a/pkg/util/cli/flag.go b/pkg/util/cli/flag.go
index a5179e82e..0b82561e3 100644
--- a/pkg/util/cli/flag.go
+++ b/pkg/util/cli/flag.go
@@ -22,6 +22,7 @@ package cli
import (
"reflect"
+ "time"
"github.com/spf13/cobra"
@@ -101,24 +102,37 @@ func (f Flag[T]) Register(cmd *cobra.Command) error {
flags = cmd.PersistentFlags()
}
- v := reflect.ValueOf(f.Default).Interface()
- if s, ok := v.(string); ok {
+ v := reflect.TypeOf(f.Default)
+
+ z := reflect.ValueOf(f.Default).Interface()
+
+ if v == util.TypeOf[string]() {
+ v := z.(string)
+ if short := f.Short; short == "" {
+ flags.String(f.Name, v, f.Description)
+ } else {
+ flags.StringP(f.Name, short, v, f.Description)
+ }
+ } else if v == util.TypeOf[bool]() {
+ v := z.(bool)
if short := f.Short; short == "" {
- flags.String(f.Name, s, f.Description)
+ flags.Bool(f.Name, v, f.Description)
} else {
- flags.StringP(f.Name, short, s, f.Description)
+ flags.BoolP(f.Name, short, v, f.Description)
}
- } else if s, ok := v.(bool); ok {
+ } else if v == util.TypeOf[[]string]() {
+ v := z.([]string)
if short := f.Short; short == "" {
- flags.Bool(f.Name, s, f.Description)
+ flags.StringSlice(f.Name, v, f.Description)
} else {
- flags.BoolP(f.Name, short, s, f.Description)
+ flags.StringSliceP(f.Name, short, v, f.Description)
}
- } else if s, ok := v.([]string); ok {
+ } else if v == util.TypeOf[time.Duration]() {
+ v := z.(time.Duration)
if short := f.Short; short == "" {
- flags.StringSlice(f.Name, s, f.Description)
+ flags.Duration(f.Name, v, f.Description)
} else {
- flags.StringSliceP(f.Name, short, s, f.Description)
+ flags.DurationP(f.Name, short, v, f.Description)
}
} else {
return errors.Errorf("Unsupported type for kind: %s", reflect.ValueOf(f.Default).Type().String())
@@ -140,8 +154,10 @@ func (f Flag[T]) Register(cmd *cobra.Command) error {
}
func (f Flag[T]) Get(cmd *cobra.Command) (T, error) {
- v := reflect.ValueOf(f.Default).Interface()
- if _, ok := v.(string); ok {
+
+ v := reflect.TypeOf(f.Default)
+
+ if v == util.TypeOf[string]() {
v, err := cmd.Flags().GetString(f.Name)
if err != nil {
return util.Default[T](), err
@@ -153,7 +169,7 @@ func (f Flag[T]) Get(cmd *cobra.Command) (T, error) {
}
return q, nil
- } else if _, ok := v.([]string); ok {
+ } else if v == util.TypeOf[[]string]() {
v, err := cmd.Flags().GetStringSlice(f.Name)
if err != nil {
return util.Default[T](), err
@@ -165,7 +181,7 @@ func (f Flag[T]) Get(cmd *cobra.Command) (T, error) {
}
return q, nil
- } else if _, ok := v.(bool); ok {
+ } else if v == util.TypeOf[bool]() {
v, err := cmd.Flags().GetBool(f.Name)
if err != nil {
return util.Default[T](), err
@@ -176,6 +192,18 @@ func (f Flag[T]) Get(cmd *cobra.Command) (T, error) {
return util.Default[T](), errors.Errorf("Unable to parse type for kind: %s", reflect.ValueOf(f.Default).Type().String())
}
+ return q, nil
+ } else if v == util.TypeOf[time.Duration]() {
+ v, err := cmd.Flags().GetDuration(f.Name)
+ if err != nil {
+ return util.Default[T](), err
+ }
+
+ q, ok := reflect.ValueOf(v).Interface().(T)
+ if !ok {
+ return util.Default[T](), errors.Errorf("Unable to parse type for kind: %s", reflect.ValueOf(f.Default).Type().String())
+ }
+
return q, nil
} else {
return util.Default[T](), errors.Errorf("Unsupported type for kind: %s", reflect.ValueOf(f.Default).Type().String())
diff --git a/pkg/util/cli/lm.client.go b/pkg/util/cli/lm.client.go
new file mode 100644
index 000000000..09fc845ea
--- /dev/null
+++ b/pkg/util/cli/lm.client.go
@@ -0,0 +1,50 @@
+//
+// 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"
+
+type licenseManagerClient struct {
+ clientID Flag[string]
+ stages Flag[[]string]
+ clientSecret Flag[string]
+}
+
+func (l licenseManagerClient) GetName() string {
+ return "client"
+}
+
+func (l licenseManagerClient) Register(cmd *cobra.Command) error {
+ return RegisterFlags(
+ cmd,
+ l.clientID,
+ l.clientSecret,
+ l.stages,
+ )
+}
+
+func (l licenseManagerClient) Validate(cmd *cobra.Command) error {
+ return ValidateFlags(
+ l.clientID,
+ l.clientSecret,
+ l.stages,
+ )(cmd, nil)
+}
diff --git a/pkg/util/cli/lm.go b/pkg/util/cli/lm.go
new file mode 100644
index 000000000..614bdc0b9
--- /dev/null
+++ b/pkg/util/cli/lm.go
@@ -0,0 +1,164 @@
+//
+// 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/google/uuid"
+ "github.com/spf13/cobra"
+
+ "github.com/arangodb/kube-arangodb/pkg/license/manager"
+ "github.com/arangodb/kube-arangodb/pkg/util/errors"
+)
+
+func NewLicenseManager(prefix string) LicenseManager {
+ return licenseManager{
+ endpoint: Flag[string]{
+ Name: fmt.Sprintf("%s.endpoint", prefix),
+ Default: "license.arango.ai",
+ Description: "LicenseManager Endpoint",
+ Check: func(in string) error {
+ if len(in) == 0 {
+ return errors.Errorf("empty endpoint")
+ }
+
+ return nil
+ },
+ },
+
+ client: licenseManagerClient{
+ clientID: Flag[string]{
+ Name: fmt.Sprintf("%s.client.id", prefix),
+ Description: "LicenseManager Client ID",
+ Default: "",
+ Persistent: false,
+ Check: func(in string) error {
+ if in == "" {
+ return errors.New("Platform Client ID is required")
+ }
+
+ return nil
+ },
+ },
+
+ stages: Flag[[]string]{
+ Name: fmt.Sprintf("%s.client.stage", prefix),
+ Description: "LicenseManager Stages",
+ Default: []string{"prd"},
+ Persistent: false,
+ Check: func(in []string) error {
+ if len(in) == 0 {
+ return errors.New("At least one stage needs to be defined")
+ }
+
+ return nil
+ },
+ },
+
+ clientSecret: Flag[string]{
+ Name: "license.client.secret",
+ Description: "LicenseManager Client Secret",
+ Default: "",
+ Persistent: false,
+ Check: func(in string) error {
+ if _, err := uuid.Parse(in); err != nil {
+ return err
+ }
+
+ return nil
+ },
+ },
+ },
+ }
+}
+
+type LicenseManager interface {
+ FlagRegisterer
+
+ Endpoint(cmd *cobra.Command) (string, error)
+ Stages(cmd *cobra.Command) ([]string, error)
+
+ ClientID(cmd *cobra.Command) (string, error)
+ ClientSecret(cmd *cobra.Command) (string, error)
+
+ Client(cmd *cobra.Command) (manager.Client, error)
+}
+
+type licenseManager struct {
+ endpoint Flag[string]
+
+ client licenseManagerClient
+}
+
+func (l licenseManager) Endpoint(cmd *cobra.Command) (string, error) {
+ return l.endpoint.Get(cmd)
+}
+
+func (l licenseManager) Stages(cmd *cobra.Command) ([]string, error) {
+ return l.client.stages.Get(cmd)
+}
+
+func (l licenseManager) ClientID(cmd *cobra.Command) (string, error) {
+ return l.client.clientID.Get(cmd)
+}
+
+func (l licenseManager) ClientSecret(cmd *cobra.Command) (string, error) {
+ return l.client.clientSecret.Get(cmd)
+}
+
+func (l licenseManager) GetName() string {
+ return "lm"
+}
+
+func (l licenseManager) Client(cmd *cobra.Command) (manager.Client, error) {
+ endpoint, err := l.endpoint.Get(cmd)
+ if err != nil {
+ return nil, err
+ }
+
+ cid, err := l.client.clientID.Get(cmd)
+ if err != nil {
+ return nil, err
+ }
+
+ cs, err := l.client.clientSecret.Get(cmd)
+ if err != nil {
+ return nil, err
+ }
+
+ return manager.NewClient(endpoint, cid, cs)
+}
+
+func (l licenseManager) Register(cmd *cobra.Command) error {
+ return RegisterFlags(
+ cmd,
+ l.endpoint,
+ l.client,
+ )
+}
+
+func (l licenseManager) Validate(cmd *cobra.Command) error {
+ return ValidateFlags(
+ l.endpoint,
+ l.client,
+ )(cmd, nil)
+}
diff --git a/pkg/util/grpc/marshal.go b/pkg/util/grpc/marshal.go
index 0be0a59f1..384ed2ae2 100644
--- a/pkg/util/grpc/marshal.go
+++ b/pkg/util/grpc/marshal.go
@@ -21,6 +21,8 @@
package grpc
import (
+ "os"
+
"google.golang.org/protobuf/encoding/protojson"
"google.golang.org/protobuf/proto"
"sigs.k8s.io/yaml"
@@ -79,3 +81,12 @@ func Unmarshal[T proto.Message](data []byte, opts ...util.Mod[protojson.Unmarsha
return v, nil
}
+
+func UnmarshalFile[T proto.Message](path string, opts ...util.Mod[protojson.UnmarshalOptions]) (T, error) {
+ data, err := os.ReadFile(path)
+ if err != nil {
+ return util.Default[T](), err
+ }
+
+ return Unmarshal[T](data, opts...)
+}
diff --git a/pkg/util/grpc/object.go b/pkg/util/grpc/object.go
index 2831b1be9..d19449540 100644
--- a/pkg/util/grpc/object.go
+++ b/pkg/util/grpc/object.go
@@ -22,6 +22,10 @@ package grpc
import "google.golang.org/protobuf/proto"
+func NewObject[IN proto.Message](in IN) Object[IN] {
+ return Object[IN]{Object: in}
+}
+
type Object[IN proto.Message] struct {
Object IN
}