From 783503851e943e46fd2aacb5b407babf369c1651 Mon Sep 17 00:00:00 2001 From: Christian Richter Date: Tue, 2 Dec 2025 12:08:33 +0100 Subject: [PATCH 01/82] migrate activitylog from urfave/cli to spf13/cobra Signed-off-by: Christian Richter --- pkg/clihelper/app.go | 13 +++++++++++++ services/activitylog/pkg/command/health.go | 14 +++++++------- services/activitylog/pkg/command/root.go | 18 +++++++++--------- services/activitylog/pkg/command/server.go | 19 +++++++++---------- services/activitylog/pkg/command/version.go | 13 ++++++------- 5 files changed, 44 insertions(+), 33 deletions(-) diff --git a/pkg/clihelper/app.go b/pkg/clihelper/app.go index d7fffa7876..814b32326a 100644 --- a/pkg/clihelper/app.go +++ b/pkg/clihelper/app.go @@ -1,10 +1,14 @@ package clihelper import ( + "fmt" + "github.com/opencloud-eu/opencloud/pkg/version" + "github.com/spf13/cobra" "github.com/urfave/cli/v2" ) +// DefaultApp provides some default settings for the cli app func DefaultApp(app *cli.App) *cli.App { // version info app.Version = version.String @@ -24,3 +28,12 @@ func DefaultApp(app *cli.App) *cli.App { return app } + +// DefaultAppCobra is a wrapper for DefaultApp that adds Cobra specific settings +func DefaultAppCobra(app *cobra.Command) *cobra.Command { + // TODO: when migration is done this has to become DefaultApp + // version info + app.Version = fmt.Sprintf("%s (%s <%s>) (%s)", version.String, "OpenCloud GmbH", "support@opencloud.eu", version.Compiled()) + + return app +} diff --git a/services/activitylog/pkg/command/health.go b/services/activitylog/pkg/command/health.go index 6e0704ace0..e39cbc2f15 100644 --- a/services/activitylog/pkg/command/health.go +++ b/services/activitylog/pkg/command/health.go @@ -2,16 +2,16 @@ package command import ( "github.com/opencloud-eu/opencloud/services/activitylog/pkg/config" - "github.com/urfave/cli/v2" + "github.com/spf13/cobra" ) // Health is the entrypoint for the health command. -func Health(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: "health", - Usage: "Check health status", - Action: func(c *cli.Context) error { - // Not implemented +func Health(cfg *config.Config) *cobra.Command { + return &cobra.Command{ + Use: "health", + Short: "Check health status", + RunE: func(cmd *cobra.Command, args []string) error { + // not implemented return nil }, } diff --git a/services/activitylog/pkg/command/root.go b/services/activitylog/pkg/command/root.go index 0a4e2f822c..c61aec8809 100644 --- a/services/activitylog/pkg/command/root.go +++ b/services/activitylog/pkg/command/root.go @@ -5,12 +5,12 @@ import ( "github.com/opencloud-eu/opencloud/pkg/clihelper" "github.com/opencloud-eu/opencloud/services/activitylog/pkg/config" - "github.com/urfave/cli/v2" + "github.com/spf13/cobra" ) // GetCommands provides all commands for this service -func GetCommands(cfg *config.Config) cli.Commands { - return []*cli.Command{ +func GetCommands(cfg *config.Config) []*cobra.Command { + return []*cobra.Command{ // start this service Server(cfg), @@ -24,11 +24,11 @@ func GetCommands(cfg *config.Config) cli.Commands { // Execute is the entry point for the activitylog command. func Execute(cfg *config.Config) error { - app := clihelper.DefaultApp(&cli.App{ - Name: "activitylog", - Usage: "starts activitylog service", - Commands: GetCommands(cfg), + app := clihelper.DefaultAppCobra(&cobra.Command{ + Use: "activitylog", + Short: "starts activitylog service", }) - - return app.RunContext(cfg.Context, os.Args) + app.AddCommand(GetCommands(cfg)...) + app.SetArgs(os.Args[1:]) + return app.ExecuteContext(cfg.Context) } diff --git a/services/activitylog/pkg/command/server.go b/services/activitylog/pkg/command/server.go index ae925779b0..ec86ee9b4f 100644 --- a/services/activitylog/pkg/command/server.go +++ b/services/activitylog/pkg/command/server.go @@ -9,7 +9,7 @@ import ( "github.com/opencloud-eu/reva/v2/pkg/events/stream" "github.com/opencloud-eu/reva/v2/pkg/rgrpc/todo/pool" "github.com/opencloud-eu/reva/v2/pkg/store" - "github.com/urfave/cli/v2" + "github.com/spf13/cobra" microstore "go-micro.dev/v4/store" "github.com/opencloud-eu/opencloud/pkg/config/configlog" @@ -47,24 +47,23 @@ var _registeredEvents = []events.Unmarshaller{ } // Server is the entrypoint for the server command. -func Server(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: "server", - Usage: fmt.Sprintf("start the %s service without runtime (unsupervised mode)", cfg.Service.Name), - Category: "server", - Before: func(c *cli.Context) error { +func Server(cfg *config.Config) *cobra.Command { + return &cobra.Command{ + Use: "server", + Short: fmt.Sprintf("start the %s service without runtime (unsupervised mode)", cfg.Service.Name), + PreRunE: func(cmd *cobra.Command, args []string) error { return configlog.ReturnFatal(parser.ParseConfig(cfg)) }, - Action: func(c *cli.Context) error { + RunE: func(cmd *cobra.Command, args []string) error { logger := logging.Configure(cfg.Service.Name, cfg.Log) - tracerProvider, err := tracing.GetTraceProvider(c.Context, cfg.Commons.TracesExporter, cfg.Service.Name) + tracerProvider, err := tracing.GetTraceProvider(cmd.Context(), cfg.Commons.TracesExporter, cfg.Service.Name) if err != nil { logger.Error().Err(err).Msg("Failed to initialize tracer") return err } gr := run.Group{} - ctx, cancel := context.WithCancel(c.Context) + ctx, cancel := context.WithCancel(cmd.Context()) mtrcs := metrics.New() mtrcs.BuildInfo.WithLabelValues(version.GetString()).Set(1) diff --git a/services/activitylog/pkg/command/version.go b/services/activitylog/pkg/command/version.go index 6ba2fd2f7f..2d8d88570a 100644 --- a/services/activitylog/pkg/command/version.go +++ b/services/activitylog/pkg/command/version.go @@ -2,16 +2,15 @@ package command import ( "github.com/opencloud-eu/opencloud/services/activitylog/pkg/config" - "github.com/urfave/cli/v2" + "github.com/spf13/cobra" ) // Version prints the service versions of all running instances. -func Version(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: "version", - Usage: "print the version of this binary and the running service instances", - Category: "info", - Action: func(c *cli.Context) error { +func Version(cfg *config.Config) *cobra.Command { + return &cobra.Command{ + Use: "version", + Short: "Print the version of this binary and the running service instances", + RunE: func(cmd *cobra.Command, args []string) error { // not implemented return nil }, From 0053f3b864126927c318e6194d14f9f3b98395fc Mon Sep 17 00:00:00 2001 From: Christian Richter Date: Tue, 2 Dec 2025 12:23:39 +0100 Subject: [PATCH 02/82] migrate antivirus from urfave/cli to spf13/cobra Signed-off-by: Christian Richter --- services/antivirus/pkg/command/health.go | 15 +++++++-------- services/antivirus/pkg/command/root.go | 18 +++++++++--------- services/antivirus/pkg/command/server.go | 22 +++++++++++----------- services/antivirus/pkg/command/version.go | 13 ++++++------- 4 files changed, 33 insertions(+), 35 deletions(-) diff --git a/services/antivirus/pkg/command/health.go b/services/antivirus/pkg/command/health.go index 909457ea0a..937f7b2df8 100644 --- a/services/antivirus/pkg/command/health.go +++ b/services/antivirus/pkg/command/health.go @@ -5,23 +5,22 @@ import ( "net/http" "github.com/opencloud-eu/opencloud/pkg/log" + "github.com/spf13/cobra" "github.com/opencloud-eu/opencloud/pkg/config/configlog" "github.com/opencloud-eu/opencloud/services/antivirus/pkg/config" "github.com/opencloud-eu/opencloud/services/antivirus/pkg/config/parser" - "github.com/urfave/cli/v2" ) // Health is the entrypoint for the health command. -func Health(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: "health", - Usage: "check health status", - Category: "info", - Before: func(c *cli.Context) error { +func Health(cfg *config.Config) *cobra.Command { + return &cobra.Command{ + Use: "health", + Short: "check health status", + PreRunE: func(cmd *cobra.Command, args []string) error { return configlog.ReturnError(parser.ParseConfig(cfg)) }, - Action: func(c *cli.Context) error { + RunE: func(cmd *cobra.Command, args []string) error { logger := log.NewLogger( log.Name(cfg.Service.Name), log.Level(cfg.Log.Level), diff --git a/services/antivirus/pkg/command/root.go b/services/antivirus/pkg/command/root.go index 12a11bc36b..ecea2c6043 100644 --- a/services/antivirus/pkg/command/root.go +++ b/services/antivirus/pkg/command/root.go @@ -5,12 +5,12 @@ import ( "github.com/opencloud-eu/opencloud/pkg/clihelper" "github.com/opencloud-eu/opencloud/services/antivirus/pkg/config" - "github.com/urfave/cli/v2" + "github.com/spf13/cobra" ) // GetCommands provides all commands for this service -func GetCommands(cfg *config.Config) cli.Commands { - return []*cli.Command{ +func GetCommands(cfg *config.Config) []*cobra.Command { + return []*cobra.Command{ Server(cfg), Health(cfg), Version(cfg), @@ -19,11 +19,11 @@ func GetCommands(cfg *config.Config) cli.Commands { // Execute is the entry point for the antivirus command. func Execute(cfg *config.Config) error { - app := clihelper.DefaultApp(&cli.App{ - Name: "antivirus", - Usage: "Antivirus service for OpenCloud", - Commands: GetCommands(cfg), + app := clihelper.DefaultAppCobra(&cobra.Command{ + Use: "antivirus", + Short: "Antivirus service for OpenCloud", }) - - return app.RunContext(cfg.Context, os.Args) + app.AddCommand(GetCommands(cfg)...) + app.SetArgs(os.Args[1:]) + return app.ExecuteContext(cfg.Context) } diff --git a/services/antivirus/pkg/command/server.go b/services/antivirus/pkg/command/server.go index 24566d431b..dd6aa6e223 100644 --- a/services/antivirus/pkg/command/server.go +++ b/services/antivirus/pkg/command/server.go @@ -3,10 +3,9 @@ package command import ( "context" "fmt" + "os" "os/signal" - "github.com/urfave/cli/v2" - "github.com/opencloud-eu/opencloud/pkg/config/configlog" "github.com/opencloud-eu/opencloud/pkg/log" "github.com/opencloud-eu/opencloud/pkg/runner" @@ -15,18 +14,18 @@ import ( "github.com/opencloud-eu/opencloud/services/antivirus/pkg/config/parser" "github.com/opencloud-eu/opencloud/services/antivirus/pkg/server/debug" "github.com/opencloud-eu/opencloud/services/antivirus/pkg/service" + "github.com/spf13/cobra" ) // Server is the entrypoint for the server command. -func Server(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: "server", - Usage: fmt.Sprintf("start the %s service without runtime (unsupervised mode)", cfg.Service.Name), - Category: "server", - Before: func(c *cli.Context) error { +func Server(cfg *config.Config) *cobra.Command { + return &cobra.Command{ + Use: "server", + Short: fmt.Sprintf("start the %s service without runtime (unsupervised mode)", cfg.Service.Name), + PreRunE: func(cmd *cobra.Command, args []string) error { return configlog.ReturnFatal(parser.ParseConfig(cfg)) }, - Action: func(c *cli.Context) error { + RunE: func(cmd *cobra.Command, args []string) error { var cancel context.CancelFunc if cfg.Context == nil { cfg.Context, cancel = signal.NotifyContext(context.Background(), runner.StopSignals...) @@ -42,7 +41,7 @@ func Server(cfg *config.Config) *cli.Command { log.File(cfg.Log.File), ) - traceProvider, err := tracing.GetTraceProvider(c.Context, cfg.Commons.TracesExporter, cfg.Service.Name) + traceProvider, err := tracing.GetTraceProvider(cmd.Context(), cfg.Commons.TracesExporter, cfg.Service.Name) if err != nil { return err } @@ -51,7 +50,8 @@ func Server(cfg *config.Config) *cli.Command { { svc, err := service.NewAntivirus(cfg, logger, traceProvider) if err != nil { - return cli.Exit(err.Error(), 1) + fmt.Errorf("failed to initialize antivirus service: %v", err) + os.Exit(1) } gr.Add(runner.New(cfg.Service.Name+".svc", func() error { diff --git a/services/antivirus/pkg/command/version.go b/services/antivirus/pkg/command/version.go index d287fc3925..a6b8590754 100644 --- a/services/antivirus/pkg/command/version.go +++ b/services/antivirus/pkg/command/version.go @@ -4,18 +4,17 @@ import ( "fmt" "github.com/opencloud-eu/opencloud/pkg/version" + "github.com/spf13/cobra" "github.com/opencloud-eu/opencloud/services/antivirus/pkg/config" - "github.com/urfave/cli/v2" ) // Version prints the service versions of all running instances. -func Version(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: "version", - Usage: "print the version of this binary and the running service instances", - Category: "info", - Action: func(c *cli.Context) error { +func Version(cfg *config.Config) *cobra.Command { + return &cobra.Command{ + Use: "version", + Short: "print the version of this binary and the running service instances", + RunE: func(cmd *cobra.Command, args []string) error { fmt.Println("Version: " + version.GetString()) fmt.Printf("Compiled: %s\n", version.Compiled()) fmt.Println("") From 81e6b3e19861f80cfbbda42bbd808a3d052b21f3 Mon Sep 17 00:00:00 2001 From: Christian Richter Date: Tue, 2 Dec 2025 12:29:16 +0100 Subject: [PATCH 03/82] migrate app-provider from urfave/cli to spf13/cobra Signed-off-by: Christian Richter --- services/app-provider/pkg/command/health.go | 15 +++++++-------- services/app-provider/pkg/command/root.go | 17 +++++++++-------- services/app-provider/pkg/command/server.go | 17 ++++++++--------- services/app-provider/pkg/command/version.go | 15 +++++++-------- 4 files changed, 31 insertions(+), 33 deletions(-) diff --git a/services/app-provider/pkg/command/health.go b/services/app-provider/pkg/command/health.go index 3954f26db8..9668bd4cd8 100644 --- a/services/app-provider/pkg/command/health.go +++ b/services/app-provider/pkg/command/health.go @@ -8,19 +8,18 @@ import ( "github.com/opencloud-eu/opencloud/services/app-provider/pkg/config" "github.com/opencloud-eu/opencloud/services/app-provider/pkg/config/parser" "github.com/opencloud-eu/opencloud/services/app-provider/pkg/logging" - "github.com/urfave/cli/v2" + "github.com/spf13/cobra" ) // Health is the entrypoint for the health command. -func Health(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: "health", - Usage: "check health status", - Category: "info", - Before: func(c *cli.Context) error { +func Health(cfg *config.Config) *cobra.Command { + return &cobra.Command{ + Use: "health", + Short: "check health status", + PreRunE: func(cmd *cobra.Command, args []string) error { return configlog.ReturnError(parser.ParseConfig(cfg)) }, - Action: func(c *cli.Context) error { + RunE: func(cmd *cobra.Command, args []string) error { logger := logging.Configure(cfg.Service.Name, cfg.Log) resp, err := http.Get( diff --git a/services/app-provider/pkg/command/root.go b/services/app-provider/pkg/command/root.go index a7d33df1f1..052e17d76a 100644 --- a/services/app-provider/pkg/command/root.go +++ b/services/app-provider/pkg/command/root.go @@ -5,12 +5,12 @@ import ( "github.com/opencloud-eu/opencloud/pkg/clihelper" "github.com/opencloud-eu/opencloud/services/app-provider/pkg/config" - "github.com/urfave/cli/v2" + "github.com/spf13/cobra" ) // GetCommands provides all commands for this service -func GetCommands(cfg *config.Config) cli.Commands { - return []*cli.Command{ +func GetCommands(cfg *config.Config) []*cobra.Command { + return []*cobra.Command{ // start this service Server(cfg), @@ -24,11 +24,12 @@ func GetCommands(cfg *config.Config) cli.Commands { // Execute is the entry point for the opencloud app-provider command. func Execute(cfg *config.Config) error { - app := clihelper.DefaultApp(&cli.App{ - Name: "app-provider", - Usage: "Provide apps for OpenCloud", - Commands: GetCommands(cfg), + app := clihelper.DefaultAppCobra(&cobra.Command{ + Use: "app-provider", + Short: "Provide apps for OpenCloud", }) + app.AddCommand(GetCommands(cfg)...) + app.SetArgs(os.Args[1:]) - return app.RunContext(cfg.Context, os.Args) + return app.ExecuteContext(cfg.Context) } diff --git a/services/app-provider/pkg/command/server.go b/services/app-provider/pkg/command/server.go index ddef630133..a615dd0c4f 100644 --- a/services/app-provider/pkg/command/server.go +++ b/services/app-provider/pkg/command/server.go @@ -6,7 +6,7 @@ import ( "os/signal" "github.com/opencloud-eu/reva/v2/cmd/revad/runtime" - "github.com/urfave/cli/v2" + "github.com/spf13/cobra" "github.com/opencloud-eu/opencloud/pkg/config/configlog" "github.com/opencloud-eu/opencloud/pkg/registry" @@ -21,17 +21,16 @@ import ( ) // Server is the entry point for the server command. -func Server(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: "server", - Usage: fmt.Sprintf("start the %s service without runtime (unsupervised mode)", cfg.Service.Name), - Category: "server", - Before: func(c *cli.Context) error { +func Server(cfg *config.Config) *cobra.Command { + return &cobra.Command{ + Use: "server", + Short: fmt.Sprintf("start the %s service without runtime (unsupervised mode)", cfg.Service.Name), + PreRunE: func(cmd *cobra.Command, args []string) error { return configlog.ReturnFatal(parser.ParseConfig(cfg)) }, - Action: func(c *cli.Context) error { + RunE: func(cmd *cobra.Command, args []string) error { logger := logging.Configure(cfg.Service.Name, cfg.Log) - traceProvider, err := tracing.GetTraceProvider(c.Context, cfg.Commons.TracesExporter, cfg.Service.Name) + traceProvider, err := tracing.GetTraceProvider(cmd.Context(), cfg.Commons.TracesExporter, cfg.Service.Name) if err != nil { return err } diff --git a/services/app-provider/pkg/command/version.go b/services/app-provider/pkg/command/version.go index f4bf84b634..ce8847eb9b 100644 --- a/services/app-provider/pkg/command/version.go +++ b/services/app-provider/pkg/command/version.go @@ -6,20 +6,19 @@ import ( "github.com/opencloud-eu/opencloud/pkg/registry" "github.com/opencloud-eu/opencloud/pkg/version" + "github.com/opencloud-eu/opencloud/services/app-provider/pkg/config" "github.com/olekukonko/tablewriter" "github.com/olekukonko/tablewriter/tw" - "github.com/opencloud-eu/opencloud/services/app-provider/pkg/config" - "github.com/urfave/cli/v2" + "github.com/spf13/cobra" ) // Version prints the service versions of all running instances. -func Version(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: "version", - Usage: "print the version of this binary and the running service instances", - Category: "info", - Action: func(c *cli.Context) error { +func Version(cfg *config.Config) *cobra.Command { + return &cobra.Command{ + Use: "version", + Short: "print the version of this binary and the running service instances", + RunE: func(cmd *cobra.Command, args []string) error { fmt.Println("Version: " + version.GetString()) fmt.Printf("Compiled: %s\n", version.Compiled()) fmt.Println("") From eb6d2dc0c4df303672a81952271285cc02d1d772 Mon Sep 17 00:00:00 2001 From: Christian Richter Date: Tue, 2 Dec 2025 12:36:00 +0100 Subject: [PATCH 04/82] migrate app-registry from urfave/cli to spf13/cobra Signed-off-by: Christian Richter --- services/app-registry/pkg/command/health.go | 15 +++++++-------- services/app-registry/pkg/command/root.go | 17 +++++++++-------- services/app-registry/pkg/command/server.go | 17 ++++++++--------- services/app-registry/pkg/command/version.go | 15 +++++++-------- 4 files changed, 31 insertions(+), 33 deletions(-) diff --git a/services/app-registry/pkg/command/health.go b/services/app-registry/pkg/command/health.go index 88d87bdf15..e4389443eb 100644 --- a/services/app-registry/pkg/command/health.go +++ b/services/app-registry/pkg/command/health.go @@ -8,19 +8,18 @@ import ( "github.com/opencloud-eu/opencloud/services/app-registry/pkg/config" "github.com/opencloud-eu/opencloud/services/app-registry/pkg/config/parser" "github.com/opencloud-eu/opencloud/services/app-registry/pkg/logging" - "github.com/urfave/cli/v2" + "github.com/spf13/cobra" ) // Health is the entrypoint for the health command. -func Health(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: "health", - Usage: "check health status", - Category: "info", - Before: func(c *cli.Context) error { +func Health(cfg *config.Config) *cobra.Command { + return &cobra.Command{ + Use: "health", + Short: "check health status", + PreRunE: func(cmd *cobra.Command, args []string) error { return configlog.ReturnError(parser.ParseConfig(cfg)) }, - Action: func(c *cli.Context) error { + RunE: func(cmd *cobra.Command, args []string) error { logger := logging.Configure(cfg.Service.Name, cfg.Log) resp, err := http.Get( diff --git a/services/app-registry/pkg/command/root.go b/services/app-registry/pkg/command/root.go index 471427d93f..d30cdff5f2 100644 --- a/services/app-registry/pkg/command/root.go +++ b/services/app-registry/pkg/command/root.go @@ -5,12 +5,12 @@ import ( "github.com/opencloud-eu/opencloud/pkg/clihelper" "github.com/opencloud-eu/opencloud/services/app-registry/pkg/config" - "github.com/urfave/cli/v2" + "github.com/spf13/cobra" ) // GetCommands provides all commands for this service -func GetCommands(cfg *config.Config) cli.Commands { - return []*cli.Command{ +func GetCommands(cfg *config.Config) []*cobra.Command { + return []*cobra.Command{ // start this service Server(cfg), @@ -24,11 +24,12 @@ func GetCommands(cfg *config.Config) cli.Commands { // Execute is the entry point for the opencloud app-registry command. func Execute(cfg *config.Config) error { - app := clihelper.DefaultApp(&cli.App{ - Name: "app-registry", - Usage: "Provide an app registry for OpenCloud", - Commands: GetCommands(cfg), + app := clihelper.DefaultAppCobra(&cobra.Command{ + Use: "app-registry", + Short: "Provide an app registry for OpenCloud", }) + app.AddCommand(GetCommands(cfg)...) + app.SetArgs(os.Args[1:]) - return app.RunContext(cfg.Context, os.Args) + return app.ExecuteContext(cfg.Context) } diff --git a/services/app-registry/pkg/command/server.go b/services/app-registry/pkg/command/server.go index 7958ed977c..4b11c872ba 100644 --- a/services/app-registry/pkg/command/server.go +++ b/services/app-registry/pkg/command/server.go @@ -16,21 +16,20 @@ import ( "github.com/opencloud-eu/opencloud/services/app-registry/pkg/revaconfig" "github.com/opencloud-eu/opencloud/services/app-registry/pkg/server/debug" "github.com/opencloud-eu/reva/v2/cmd/revad/runtime" - "github.com/urfave/cli/v2" + "github.com/spf13/cobra" ) // Server is the entry point for the server command. -func Server(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: "server", - Usage: fmt.Sprintf("start the %s service without runtime (unsupervised mode)", cfg.Service.Name), - Category: "server", - Before: func(c *cli.Context) error { +func Server(cfg *config.Config) *cobra.Command { + return &cobra.Command{ + Use: "server", + Short: fmt.Sprintf("start the %s service without runtime (unsupervised mode)", cfg.Service.Name), + PreRunE: func(cmd *cobra.Command, args []string) error { return configlog.ReturnFatal(parser.ParseConfig(cfg)) }, - Action: func(c *cli.Context) error { + RunE: func(cmd *cobra.Command, args []string) error { logger := logging.Configure(cfg.Service.Name, cfg.Log) - traceProvider, err := tracing.GetTraceProvider(c.Context, cfg.Commons.TracesExporter, cfg.Service.Name) + traceProvider, err := tracing.GetTraceProvider(cmd.Context(), cfg.Commons.TracesExporter, cfg.Service.Name) if err != nil { return err } diff --git a/services/app-registry/pkg/command/version.go b/services/app-registry/pkg/command/version.go index 4ee4afde7d..03683c6c71 100644 --- a/services/app-registry/pkg/command/version.go +++ b/services/app-registry/pkg/command/version.go @@ -6,20 +6,19 @@ import ( "github.com/opencloud-eu/opencloud/pkg/registry" "github.com/opencloud-eu/opencloud/pkg/version" + "github.com/opencloud-eu/opencloud/services/app-registry/pkg/config" "github.com/olekukonko/tablewriter" "github.com/olekukonko/tablewriter/tw" - "github.com/opencloud-eu/opencloud/services/app-registry/pkg/config" - "github.com/urfave/cli/v2" + "github.com/spf13/cobra" ) // Version prints the service versions of all running instances. -func Version(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: "version", - Usage: "print the version of this binary and the running service instances", - Category: "info", - Action: func(c *cli.Context) error { +func Version(cfg *config.Config) *cobra.Command { + return &cobra.Command{ + Use: "version", + Short: "print the version of this binary and the running service instances", + RunE: func(cmd *cobra.Command, args []string) error { fmt.Println("Version: " + version.GetString()) fmt.Printf("Compiled: %s\n", version.Compiled()) fmt.Println("") From 4bcc4b9ab31b2d8e495a26ec755bbbeef30ed40f Mon Sep 17 00:00:00 2001 From: Christian Richter Date: Tue, 2 Dec 2025 12:42:04 +0100 Subject: [PATCH 05/82] migrate audit from urfave/cli to spf13/cobra Signed-off-by: Christian Richter --- services/audit/pkg/command/health.go | 12 ++++++------ services/audit/pkg/command/root.go | 17 +++++++++-------- services/audit/pkg/command/server.go | 21 ++++++++++----------- services/audit/pkg/command/version.go | 13 ++++++------- 4 files changed, 31 insertions(+), 32 deletions(-) diff --git a/services/audit/pkg/command/health.go b/services/audit/pkg/command/health.go index b4e5e23da2..59958f7828 100644 --- a/services/audit/pkg/command/health.go +++ b/services/audit/pkg/command/health.go @@ -2,15 +2,15 @@ package command import ( "github.com/opencloud-eu/opencloud/services/audit/pkg/config" - "github.com/urfave/cli/v2" + "github.com/spf13/cobra" ) // Health is the entrypoint for the health command. -func Health(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: "health", - Usage: "Check health status", - Action: func(c *cli.Context) error { +func Health(cfg *config.Config) *cobra.Command { + return &cobra.Command{ + Use: "health", + Short: "Check health status", + RunE: func(cmd *cobra.Command, args []string) error { // Not implemented return nil }, diff --git a/services/audit/pkg/command/root.go b/services/audit/pkg/command/root.go index db06c145c5..447ab92706 100644 --- a/services/audit/pkg/command/root.go +++ b/services/audit/pkg/command/root.go @@ -5,12 +5,12 @@ import ( "github.com/opencloud-eu/opencloud/pkg/clihelper" "github.com/opencloud-eu/opencloud/services/audit/pkg/config" - "github.com/urfave/cli/v2" + "github.com/spf13/cobra" ) // GetCommands provides all commands for this service -func GetCommands(cfg *config.Config) cli.Commands { - return []*cli.Command{ +func GetCommands(cfg *config.Config) []*cobra.Command { + return []*cobra.Command{ // start this service Server(cfg), @@ -24,11 +24,12 @@ func GetCommands(cfg *config.Config) cli.Commands { // Execute is the entry point for the audit command. func Execute(cfg *config.Config) error { - app := clihelper.DefaultApp(&cli.App{ - Name: "audit", - Usage: "starts audit service", - Commands: GetCommands(cfg), + app := clihelper.DefaultAppCobra(&cobra.Command{ + Use: "audit", + Short: "starts audit service", }) + app.AddCommand(GetCommands(cfg)...) + app.SetArgs(os.Args[1:]) - return app.RunContext(cfg.Context, os.Args) + return app.ExecuteContext(cfg.Context) } diff --git a/services/audit/pkg/command/server.go b/services/audit/pkg/command/server.go index 1a8bce1a53..4ce05241da 100644 --- a/services/audit/pkg/command/server.go +++ b/services/audit/pkg/command/server.go @@ -5,10 +5,6 @@ import ( "fmt" "os/signal" - "github.com/opencloud-eu/reva/v2/pkg/events" - "github.com/opencloud-eu/reva/v2/pkg/events/stream" - "github.com/urfave/cli/v2" - "github.com/opencloud-eu/opencloud/pkg/config/configlog" "github.com/opencloud-eu/opencloud/pkg/generators" "github.com/opencloud-eu/opencloud/pkg/runner" @@ -18,18 +14,21 @@ import ( "github.com/opencloud-eu/opencloud/services/audit/pkg/server/debug" svc "github.com/opencloud-eu/opencloud/services/audit/pkg/service" "github.com/opencloud-eu/opencloud/services/audit/pkg/types" + "github.com/opencloud-eu/reva/v2/pkg/events" + "github.com/opencloud-eu/reva/v2/pkg/events/stream" + + "github.com/spf13/cobra" ) // Server is the entrypoint for the server command. -func Server(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: "server", - Usage: fmt.Sprintf("start the %s service without runtime (unsupervised mode)", cfg.Service.Name), - Category: "server", - Before: func(c *cli.Context) error { +func Server(cfg *config.Config) *cobra.Command { + return &cobra.Command{ + Use: "server", + Short: fmt.Sprintf("start the %s service without runtime (unsupervised mode)", cfg.Service.Name), + PreRunE: func(cmd *cobra.Command, args []string) error { return configlog.ReturnFatal(parser.ParseConfig(cfg)) }, - Action: func(c *cli.Context) error { + RunE: func(cmd *cobra.Command, args []string) error { var cancel context.CancelFunc if cfg.Context == nil { cfg.Context, cancel = signal.NotifyContext(context.Background(), runner.StopSignals...) diff --git a/services/audit/pkg/command/version.go b/services/audit/pkg/command/version.go index 52b33d8e88..fbcfa24aa6 100644 --- a/services/audit/pkg/command/version.go +++ b/services/audit/pkg/command/version.go @@ -2,16 +2,15 @@ package command import ( "github.com/opencloud-eu/opencloud/services/audit/pkg/config" - "github.com/urfave/cli/v2" + "github.com/spf13/cobra" ) // Version prints the service versions of all running instances. -func Version(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: "version", - Usage: "print the version of this binary and the running service instances", - Category: "info", - Action: func(c *cli.Context) error { +func Version(cfg *config.Config) *cobra.Command { + return &cobra.Command{ + Use: "version", + Short: "print the version of this binary and the running service instances", + RunE: func(cmd *cobra.Command, args []string) error { // not implemented return nil }, From 78168a3a21571d3f6688bc9297cd7bd2fe513af9 Mon Sep 17 00:00:00 2001 From: Christian Richter Date: Tue, 2 Dec 2025 12:59:26 +0100 Subject: [PATCH 06/82] migrate auth-app from urfave/cli to spf13/cobra Signed-off-by: Christian Richter --- services/auth-app/pkg/command/create.go | 56 ++++++++++++------------ services/auth-app/pkg/command/health.go | 16 +++---- services/auth-app/pkg/command/root.go | 18 ++++---- services/auth-app/pkg/command/server.go | 18 ++++---- services/auth-app/pkg/command/version.go | 15 +++---- 5 files changed, 62 insertions(+), 61 deletions(-) diff --git a/services/auth-app/pkg/command/create.go b/services/auth-app/pkg/command/create.go index a037e04aa1..0d81535ab0 100644 --- a/services/auth-app/pkg/command/create.go +++ b/services/auth-app/pkg/command/create.go @@ -6,14 +6,10 @@ import ( authpb "github.com/cs3org/go-cs3apis/cs3/auth/provider/v1beta1" "github.com/opencloud-eu/reva/v2/pkg/auth/scope" + "github.com/spf13/cobra" "time" - applicationsv1beta1 "github.com/cs3org/go-cs3apis/cs3/auth/applications/v1beta1" - gatewayv1beta1 "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1" - userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" - rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" - typesv1beta1 "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" "github.com/opencloud-eu/opencloud/pkg/config/configlog" "github.com/opencloud-eu/opencloud/pkg/registry" "github.com/opencloud-eu/opencloud/pkg/tracing" @@ -21,33 +17,25 @@ import ( "github.com/opencloud-eu/opencloud/services/auth-app/pkg/config/parser" ctxpkg "github.com/opencloud-eu/reva/v2/pkg/ctx" "github.com/opencloud-eu/reva/v2/pkg/rgrpc/todo/pool" - "github.com/urfave/cli/v2" + + applicationsv1beta1 "github.com/cs3org/go-cs3apis/cs3/auth/applications/v1beta1" + gatewayv1beta1 "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1" + userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" + rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" + typesv1beta1 "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" "google.golang.org/grpc/metadata" ) // Create is the entrypoint for the app auth create command -func Create(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: "create", - Usage: "create an app auth token for a user", - Category: "maintenance", - Flags: []cli.Flag{ - &cli.StringFlag{ - Name: "user-name", - Value: "", - Usage: "user to create the app-token for", - }, - &cli.StringFlag{ - Name: "expiration", - Value: "72h", - Usage: "expiration of the app password, e.g. 72h, 1h, 1m, 1s. Default is 72h.", - }, - }, - Before: func(_ *cli.Context) error { +func Create(cfg *config.Config) *cobra.Command { + createCmd := &cobra.Command{ + Use: "create", + Short: "create an app auth token for a user", + PreRunE: func(cmd *cobra.Command, args []string) error { return configlog.ReturnError(parser.ParseConfig(cfg)) }, - Action: func(c *cli.Context) error { - traceProvider, err := tracing.GetTraceProvider(c.Context, cfg.Commons.TracesExporter, cfg.Service.Name) + RunE: func(cmd *cobra.Command, args []string) error { + traceProvider, err := tracing.GetTraceProvider(cmd.Context(), cfg.Commons.TracesExporter, cfg.Service.Name) if err != nil { return err } @@ -68,7 +56,7 @@ func Create(cfg *config.Config) *cli.Command { return err } - userName := c.String("user-name") + userName := cmd.Flag("user-name").Value.String() if userName == "" { fmt.Printf("Username to create app token for: ") if _, err := fmt.Scanln(&userName); err != nil { @@ -97,7 +85,7 @@ func Create(cfg *config.Config) *cli.Command { return err } - expiry, err := time.ParseDuration(c.String("expiration")) + expiry, err := time.ParseDuration(cmd.Flag("expiration").Value.String()) if err != nil { return err } @@ -121,4 +109,16 @@ func Create(cfg *config.Config) *cli.Command { return nil }, } + createCmd.Flags().String( + "user-name", + "", + "user to create the app-token for", + ) + createCmd.Flags().String( + "expiration", + "72h", + "expiration of the app password, e.g. 72h, 1h, 1m, 1s. Default is 72h.", + ) + + return createCmd } diff --git a/services/auth-app/pkg/command/health.go b/services/auth-app/pkg/command/health.go index 627038b59a..fac1ec71a6 100644 --- a/services/auth-app/pkg/command/health.go +++ b/services/auth-app/pkg/command/health.go @@ -8,19 +8,19 @@ import ( "github.com/opencloud-eu/opencloud/services/auth-app/pkg/config" "github.com/opencloud-eu/opencloud/services/auth-app/pkg/config/parser" "github.com/opencloud-eu/opencloud/services/auth-app/pkg/logging" - "github.com/urfave/cli/v2" + + "github.com/spf13/cobra" ) // Health is the entrypoint for the health command. -func Health(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: "health", - Usage: "check health status", - Category: "info", - Before: func(_ *cli.Context) error { +func Health(cfg *config.Config) *cobra.Command { + return &cobra.Command{ + Use: "health", + Short: "check health status", + PreRunE: func(cmd *cobra.Command, args []string) error { return configlog.ReturnError(parser.ParseConfig(cfg)) }, - Action: func(_ *cli.Context) error { + RunE: func(cmd *cobra.Command, args []string) error { logger := logging.Configure(cfg.Service.Name, cfg.Log) resp, err := http.Get( diff --git a/services/auth-app/pkg/command/root.go b/services/auth-app/pkg/command/root.go index 3371c99df4..f05588e095 100644 --- a/services/auth-app/pkg/command/root.go +++ b/services/auth-app/pkg/command/root.go @@ -5,12 +5,13 @@ import ( "github.com/opencloud-eu/opencloud/pkg/clihelper" "github.com/opencloud-eu/opencloud/services/auth-app/pkg/config" - "github.com/urfave/cli/v2" + + "github.com/spf13/cobra" ) // GetCommands provides all commands for this service -func GetCommands(cfg *config.Config) cli.Commands { - return []*cli.Command{ +func GetCommands(cfg *config.Config) []*cobra.Command { + return []*cobra.Command{ // start this service Server(cfg), @@ -25,11 +26,12 @@ func GetCommands(cfg *config.Config) cli.Commands { // Execute is the entry point for the opencloud auth-app command. func Execute(cfg *config.Config) error { - app := clihelper.DefaultApp(&cli.App{ - Name: "auth-app", - Usage: "Provide app authentication for OpenCloud", - Commands: GetCommands(cfg), + app := clihelper.DefaultAppCobra(&cobra.Command{ + Use: "auth-app", + Short: "Provide app authentication for OpenCloud", }) + app.AddCommand(GetCommands(cfg)...) + app.SetArgs(os.Args[1:]) - return app.RunContext(cfg.Context, os.Args) + return app.ExecuteContext(cfg.Context) } diff --git a/services/auth-app/pkg/command/server.go b/services/auth-app/pkg/command/server.go index 2c3bb774f6..276bef46dd 100644 --- a/services/auth-app/pkg/command/server.go +++ b/services/auth-app/pkg/command/server.go @@ -20,25 +20,25 @@ import ( "github.com/opencloud-eu/opencloud/services/auth-app/pkg/server/http" "github.com/opencloud-eu/reva/v2/cmd/revad/runtime" "github.com/opencloud-eu/reva/v2/pkg/rgrpc/todo/pool" - "github.com/urfave/cli/v2" + + "github.com/spf13/cobra" ) // Server is the entry point for the server command. -func Server(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: "server", - Usage: fmt.Sprintf("start the %s service without runtime (unsupervised mode)", cfg.Service.Name), - Category: "server", - Before: func(_ *cli.Context) error { +func Server(cfg *config.Config) *cobra.Command { + return &cobra.Command{ + Use: "server", + Short: fmt.Sprintf("start the %s service without runtime (unsupervised mode)", cfg.Service.Name), + PreRunE: func(cmd *cobra.Command, args []string) error { return configlog.ReturnFatal(parser.ParseConfig(cfg)) }, - Action: func(c *cli.Context) error { + RunE: func(cmd *cobra.Command, args []string) error { if cfg.AllowImpersonation { fmt.Println("WARNING: Impersonation is enabled. Admins can impersonate all users.") } logger := logging.Configure(cfg.Service.Name, cfg.Log) - traceProvider, err := tracing.GetTraceProvider(c.Context, cfg.Commons.TracesExporter, cfg.Service.Name) + traceProvider, err := tracing.GetTraceProvider(cmd.Context(), cfg.Commons.TracesExporter, cfg.Service.Name) if err != nil { return err } diff --git a/services/auth-app/pkg/command/version.go b/services/auth-app/pkg/command/version.go index 42b2c757ab..c6df8f5892 100644 --- a/services/auth-app/pkg/command/version.go +++ b/services/auth-app/pkg/command/version.go @@ -6,20 +6,19 @@ import ( "github.com/opencloud-eu/opencloud/pkg/registry" "github.com/opencloud-eu/opencloud/pkg/version" + "github.com/opencloud-eu/opencloud/services/auth-app/pkg/config" "github.com/olekukonko/tablewriter" "github.com/olekukonko/tablewriter/tw" - "github.com/opencloud-eu/opencloud/services/auth-app/pkg/config" - "github.com/urfave/cli/v2" + "github.com/spf13/cobra" ) // Version prints the service versions of all running instances. -func Version(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: "version", - Usage: "print the version of this binary and the running service instances", - Category: "info", - Action: func(c *cli.Context) error { +func Version(cfg *config.Config) *cobra.Command { + return &cobra.Command{ + Use: "version", + Short: "print the version of this binary and the running service instances", + RunE: func(cmd *cobra.Command, args []string) error { fmt.Println("Version: " + version.GetString()) fmt.Printf("Compiled: %s\n", version.Compiled()) fmt.Println("") From 8dc3877314d1798d2adc980cf6beb83130e32820 Mon Sep 17 00:00:00 2001 From: Christian Richter Date: Tue, 2 Dec 2025 13:04:14 +0100 Subject: [PATCH 07/82] migrate auth-basic from urfave/cli to spf13/cobra Signed-off-by: Christian Richter --- services/auth-basic/pkg/command/health.go | 16 ++++++++-------- services/auth-basic/pkg/command/root.go | 17 +++++++++-------- services/auth-basic/pkg/command/server.go | 18 +++++++++--------- services/auth-basic/pkg/command/version.go | 15 +++++++-------- 4 files changed, 33 insertions(+), 33 deletions(-) diff --git a/services/auth-basic/pkg/command/health.go b/services/auth-basic/pkg/command/health.go index c75463dd97..86c475cd8c 100644 --- a/services/auth-basic/pkg/command/health.go +++ b/services/auth-basic/pkg/command/health.go @@ -8,19 +8,19 @@ import ( "github.com/opencloud-eu/opencloud/services/auth-basic/pkg/config" "github.com/opencloud-eu/opencloud/services/auth-basic/pkg/config/parser" "github.com/opencloud-eu/opencloud/services/auth-basic/pkg/logging" - "github.com/urfave/cli/v2" + + "github.com/spf13/cobra" ) // Health is the entrypoint for the health command. -func Health(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: "health", - Usage: "check health status", - Category: "info", - Before: func(c *cli.Context) error { +func Health(cfg *config.Config) *cobra.Command { + return &cobra.Command{ + Use: "health", + Short: "check health status", + PreRunE: func(cmd *cobra.Command, args []string) error { return configlog.ReturnError(parser.ParseConfig(cfg)) }, - Action: func(c *cli.Context) error { + RunE: func(cmd *cobra.Command, args []string) error { logger := logging.Configure(cfg.Service.Name, cfg.Log) resp, err := http.Get( diff --git a/services/auth-basic/pkg/command/root.go b/services/auth-basic/pkg/command/root.go index 466a891451..3c1299ed2a 100644 --- a/services/auth-basic/pkg/command/root.go +++ b/services/auth-basic/pkg/command/root.go @@ -5,12 +5,12 @@ import ( "github.com/opencloud-eu/opencloud/pkg/clihelper" "github.com/opencloud-eu/opencloud/services/auth-basic/pkg/config" - "github.com/urfave/cli/v2" + "github.com/spf13/cobra" ) // GetCommands provides all commands for this service -func GetCommands(cfg *config.Config) cli.Commands { - return []*cli.Command{ +func GetCommands(cfg *config.Config) []*cobra.Command { + return []*cobra.Command{ // start this service Server(cfg), @@ -24,11 +24,12 @@ func GetCommands(cfg *config.Config) cli.Commands { // Execute is the entry point for the opencloud auth-basic command. func Execute(cfg *config.Config) error { - app := clihelper.DefaultApp(&cli.App{ - Name: "auth-basic", - Usage: "Provide basic authentication for OpenCloud", - Commands: GetCommands(cfg), + app := clihelper.DefaultAppCobra(&cobra.Command{ + Use: "auth-basic", + Short: "Provide basic authentication for OpenCloud", }) + app.AddCommand(GetCommands(cfg)...) + app.SetArgs(os.Args[1:]) - return app.RunContext(cfg.Context, os.Args) + return app.ExecuteContext(cfg.Context) } diff --git a/services/auth-basic/pkg/command/server.go b/services/auth-basic/pkg/command/server.go index 44d3281500..e690533e14 100644 --- a/services/auth-basic/pkg/command/server.go +++ b/services/auth-basic/pkg/command/server.go @@ -17,21 +17,21 @@ import ( "github.com/opencloud-eu/opencloud/services/auth-basic/pkg/revaconfig" "github.com/opencloud-eu/opencloud/services/auth-basic/pkg/server/debug" "github.com/opencloud-eu/reva/v2/cmd/revad/runtime" - "github.com/urfave/cli/v2" + + "github.com/spf13/cobra" ) // Server is the entry point for the server command. -func Server(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: "server", - Usage: fmt.Sprintf("start the %s service without runtime (unsupervised mode)", cfg.Service.Name), - Category: "server", - Before: func(c *cli.Context) error { +func Server(cfg *config.Config) *cobra.Command { + return &cobra.Command{ + Use: "server", + Short: fmt.Sprintf("start the %s service without runtime (unsupervised mode)", cfg.Service.Name), + PreRunE: func(cmd *cobra.Command, args []string) error { return configlog.ReturnFatal(parser.ParseConfig(cfg)) }, - Action: func(c *cli.Context) error { + RunE: func(cmd *cobra.Command, args []string) error { logger := logging.Configure(cfg.Service.Name, cfg.Log) - traceProvider, err := tracing.GetTraceProvider(c.Context, cfg.Commons.TracesExporter, cfg.Service.Name) + traceProvider, err := tracing.GetTraceProvider(cmd.Context(), cfg.Commons.TracesExporter, cfg.Service.Name) if err != nil { return err } diff --git a/services/auth-basic/pkg/command/version.go b/services/auth-basic/pkg/command/version.go index 1bf553f60a..9166fbf1f0 100644 --- a/services/auth-basic/pkg/command/version.go +++ b/services/auth-basic/pkg/command/version.go @@ -6,20 +6,19 @@ import ( "github.com/opencloud-eu/opencloud/pkg/registry" "github.com/opencloud-eu/opencloud/pkg/version" + "github.com/opencloud-eu/opencloud/services/auth-basic/pkg/config" "github.com/olekukonko/tablewriter" "github.com/olekukonko/tablewriter/tw" - "github.com/opencloud-eu/opencloud/services/auth-basic/pkg/config" - "github.com/urfave/cli/v2" + "github.com/spf13/cobra" ) // Version prints the service versions of all running instances. -func Version(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: "version", - Usage: "print the version of this binary and the running service instances", - Category: "info", - Action: func(c *cli.Context) error { +func Version(cfg *config.Config) *cobra.Command { + return &cobra.Command{ + Use: "version", + Short: "print the version of this binary and the running service instances", + RunE: func(cmd *cobra.Command, args []string) error { fmt.Println("Version: " + version.GetString()) fmt.Printf("Compiled: %s\n", version.Compiled()) fmt.Println("") From af4c0b8d0cc69f1b3c12abab6409216a169af07a Mon Sep 17 00:00:00 2001 From: Christian Richter Date: Tue, 2 Dec 2025 13:08:58 +0100 Subject: [PATCH 08/82] migrate auth-bearer from urfave/cli to spf13/cobra Signed-off-by: Christian Richter --- services/auth-bearer/pkg/command/health.go | 16 ++++++++-------- services/auth-bearer/pkg/command/root.go | 18 ++++++++++-------- services/auth-bearer/pkg/command/server.go | 18 +++++++++--------- services/auth-bearer/pkg/command/version.go | 15 +++++++-------- 4 files changed, 34 insertions(+), 33 deletions(-) diff --git a/services/auth-bearer/pkg/command/health.go b/services/auth-bearer/pkg/command/health.go index 8620c9e068..967dedd48a 100644 --- a/services/auth-bearer/pkg/command/health.go +++ b/services/auth-bearer/pkg/command/health.go @@ -8,19 +8,19 @@ import ( "github.com/opencloud-eu/opencloud/services/auth-bearer/pkg/config" "github.com/opencloud-eu/opencloud/services/auth-bearer/pkg/config/parser" "github.com/opencloud-eu/opencloud/services/auth-bearer/pkg/logging" - "github.com/urfave/cli/v2" + + "github.com/spf13/cobra" ) // Health is the entrypoint for the health command. -func Health(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: "health", - Usage: "check health status", - Category: "info", - Before: func(c *cli.Context) error { +func Health(cfg *config.Config) *cobra.Command { + return &cobra.Command{ + Use: "health", + Short: "check health status", + PreRunE: func(cmd *cobra.Command, args []string) error { return configlog.ReturnError(parser.ParseConfig(cfg)) }, - Action: func(c *cli.Context) error { + RunE: func(cmd *cobra.Command, args []string) error { logger := logging.Configure(cfg.Service.Name, cfg.Log) resp, err := http.Get( diff --git a/services/auth-bearer/pkg/command/root.go b/services/auth-bearer/pkg/command/root.go index e42138dbb7..f79741f599 100644 --- a/services/auth-bearer/pkg/command/root.go +++ b/services/auth-bearer/pkg/command/root.go @@ -7,13 +7,14 @@ import ( "github.com/opencloud-eu/opencloud/pkg/clihelper" occfg "github.com/opencloud-eu/opencloud/pkg/config" "github.com/opencloud-eu/opencloud/services/auth-bearer/pkg/config" + + "github.com/spf13/cobra" "github.com/thejerf/suture/v4" - "github.com/urfave/cli/v2" ) // GetCommands provides all commands for this service -func GetCommands(cfg *config.Config) cli.Commands { - return []*cli.Command{ +func GetCommands(cfg *config.Config) []*cobra.Command { + return []*cobra.Command{ // start this service Server(cfg), @@ -27,13 +28,14 @@ func GetCommands(cfg *config.Config) cli.Commands { // Execute is the entry point for the opencloud auth-bearer command. func Execute(cfg *config.Config) error { - app := clihelper.DefaultApp(&cli.App{ - Name: "auth-bearer", - Usage: "Provide bearer authentication for OpenCloud", - Commands: GetCommands(cfg), + app := clihelper.DefaultAppCobra(&cobra.Command{ + Use: "auth-bearer", + Short: "Provide bearer authentication for OpenCloud", }) + app.AddCommand(GetCommands(cfg)...) + app.SetArgs(os.Args[1:]) - return app.RunContext(cfg.Context, os.Args) + return app.ExecuteContext(cfg.Context) } // SutureService allows for the accounts command to be embedded and supervised by a suture supervisor tree. diff --git a/services/auth-bearer/pkg/command/server.go b/services/auth-bearer/pkg/command/server.go index 172fb9be76..aeab79824f 100644 --- a/services/auth-bearer/pkg/command/server.go +++ b/services/auth-bearer/pkg/command/server.go @@ -16,21 +16,21 @@ import ( "github.com/opencloud-eu/opencloud/services/auth-bearer/pkg/revaconfig" "github.com/opencloud-eu/opencloud/services/auth-bearer/pkg/server/debug" "github.com/opencloud-eu/reva/v2/cmd/revad/runtime" - "github.com/urfave/cli/v2" + + "github.com/spf13/cobra" ) // Server is the entry point for the server command. -func Server(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: "server", - Usage: fmt.Sprintf("start the %s service without runtime (unsupervised mode)", cfg.Service.Name), - Category: "server", - Before: func(c *cli.Context) error { +func Server(cfg *config.Config) *cobra.Command { + return &cobra.Command{ + Use: "server", + Short: fmt.Sprintf("start the %s service without runtime (unsupervised mode)", cfg.Service.Name), + PreRunE: func(cmd *cobra.Command, args []string) error { return configlog.ReturnFatal(parser.ParseConfig(cfg)) }, - Action: func(c *cli.Context) error { + RunE: func(cmd *cobra.Command, args []string) error { logger := logging.Configure(cfg.Service.Name, cfg.Log) - traceProvider, err := tracing.GetTraceProvider(c.Context, cfg.Commons.TracesExporter, cfg.Service.Name) + traceProvider, err := tracing.GetTraceProvider(cmd.Context(), cfg.Commons.TracesExporter, cfg.Service.Name) if err != nil { return err } diff --git a/services/auth-bearer/pkg/command/version.go b/services/auth-bearer/pkg/command/version.go index 28c40cb015..2ed6a7402c 100644 --- a/services/auth-bearer/pkg/command/version.go +++ b/services/auth-bearer/pkg/command/version.go @@ -6,20 +6,19 @@ import ( "github.com/opencloud-eu/opencloud/pkg/registry" "github.com/opencloud-eu/opencloud/pkg/version" + "github.com/opencloud-eu/opencloud/services/auth-bearer/pkg/config" + "github.com/spf13/cobra" "github.com/olekukonko/tablewriter" "github.com/olekukonko/tablewriter/tw" - "github.com/opencloud-eu/opencloud/services/auth-bearer/pkg/config" - "github.com/urfave/cli/v2" ) // Version prints the service versions of all running instances. -func Version(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: "version", - Usage: "print the version of this binary and the running services instances", - Category: "info", - Action: func(c *cli.Context) error { +func Version(cfg *config.Config) *cobra.Command { + return &cobra.Command{ + Use: "version", + Short: "print the version of this binary and the running services instances", + RunE: func(cmd *cobra.Command, args []string) error { fmt.Println("Version: " + version.GetString()) fmt.Printf("Compiled: %s\n", version.Compiled()) fmt.Println("") From e14a5c6b9a927a6156b561d1b49533fa77b8637d Mon Sep 17 00:00:00 2001 From: Christian Richter Date: Tue, 2 Dec 2025 13:12:24 +0100 Subject: [PATCH 09/82] migrate auth-machine from urfave/cli to spf13/cobra Signed-off-by: Christian Richter --- services/auth-machine/pkg/command/health.go | 16 ++++++++-------- services/auth-machine/pkg/command/root.go | 18 ++++++++++-------- services/auth-machine/pkg/command/server.go | 18 +++++++++--------- services/auth-machine/pkg/command/version.go | 15 +++++++-------- 4 files changed, 34 insertions(+), 33 deletions(-) diff --git a/services/auth-machine/pkg/command/health.go b/services/auth-machine/pkg/command/health.go index 2f9f3f2a03..3051626034 100644 --- a/services/auth-machine/pkg/command/health.go +++ b/services/auth-machine/pkg/command/health.go @@ -8,19 +8,19 @@ import ( "github.com/opencloud-eu/opencloud/services/auth-machine/pkg/config" "github.com/opencloud-eu/opencloud/services/auth-machine/pkg/config/parser" "github.com/opencloud-eu/opencloud/services/auth-machine/pkg/logging" - "github.com/urfave/cli/v2" + + "github.com/spf13/cobra" ) // Health is the entrypoint for the health command. -func Health(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: "health", - Usage: "check health status", - Category: "info", - Before: func(c *cli.Context) error { +func Health(cfg *config.Config) *cobra.Command { + return &cobra.Command{ + Use: "health", + Short: "check health status", + PreRunE: func(cmd *cobra.Command, args []string) error { return configlog.ReturnError(parser.ParseConfig(cfg)) }, - Action: func(c *cli.Context) error { + RunE: func(cmd *cobra.Command, args []string) error { logger := logging.Configure(cfg.Service.Name, cfg.Log) resp, err := http.Get( diff --git a/services/auth-machine/pkg/command/root.go b/services/auth-machine/pkg/command/root.go index 1015c6d0cd..918cb998e2 100644 --- a/services/auth-machine/pkg/command/root.go +++ b/services/auth-machine/pkg/command/root.go @@ -5,12 +5,13 @@ import ( "github.com/opencloud-eu/opencloud/pkg/clihelper" "github.com/opencloud-eu/opencloud/services/auth-machine/pkg/config" - "github.com/urfave/cli/v2" + + "github.com/spf13/cobra" ) // GetCommands provides all commands for this service -func GetCommands(cfg *config.Config) cli.Commands { - return []*cli.Command{ +func GetCommands(cfg *config.Config) []*cobra.Command { + return []*cobra.Command{ // start this service Server(cfg), @@ -24,11 +25,12 @@ func GetCommands(cfg *config.Config) cli.Commands { // Execute is the entry point for the opencloud auth-machine command. func Execute(cfg *config.Config) error { - app := clihelper.DefaultApp(&cli.App{ - Name: "auth-machine", - Usage: "Provide machine authentication for OpenCloud", - Commands: GetCommands(cfg), + app := clihelper.DefaultAppCobra(&cobra.Command{ + Use: "auth-machine", + Short: "Provide machine authentication for OpenCloud", }) + app.AddCommand(GetCommands(cfg)...) + app.SetArgs(os.Args[1:]) - return app.RunContext(cfg.Context, os.Args) + return app.ExecuteContext(cfg.Context) } diff --git a/services/auth-machine/pkg/command/server.go b/services/auth-machine/pkg/command/server.go index 4cbab5be6b..3821939fb4 100644 --- a/services/auth-machine/pkg/command/server.go +++ b/services/auth-machine/pkg/command/server.go @@ -16,21 +16,21 @@ import ( "github.com/opencloud-eu/opencloud/services/auth-machine/pkg/revaconfig" "github.com/opencloud-eu/opencloud/services/auth-machine/pkg/server/debug" "github.com/opencloud-eu/reva/v2/cmd/revad/runtime" - "github.com/urfave/cli/v2" + + "github.com/spf13/cobra" ) // Server is the entry point for the server command. -func Server(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: "server", - Usage: fmt.Sprintf("start the %s service without runtime (unsupervised mode)", cfg.Service.Name), - Category: "server", - Before: func(c *cli.Context) error { +func Server(cfg *config.Config) *cobra.Command { + return &cobra.Command{ + Use: "server", + Short: fmt.Sprintf("start the %s service without runtime (unsupervised mode)", cfg.Service.Name), + PreRunE: func(cmd *cobra.Command, args []string) error { return configlog.ReturnFatal(parser.ParseConfig(cfg)) }, - Action: func(c *cli.Context) error { + RunE: func(cmd *cobra.Command, args []string) error { logger := logging.Configure(cfg.Service.Name, cfg.Log) - traceProvider, err := tracing.GetTraceProvider(c.Context, cfg.Commons.TracesExporter, cfg.Service.Name) + traceProvider, err := tracing.GetTraceProvider(cmd.Context(), cfg.Commons.TracesExporter, cfg.Service.Name) if err != nil { return err } diff --git a/services/auth-machine/pkg/command/version.go b/services/auth-machine/pkg/command/version.go index 2f7659b5c2..8a51357874 100644 --- a/services/auth-machine/pkg/command/version.go +++ b/services/auth-machine/pkg/command/version.go @@ -6,20 +6,19 @@ import ( "github.com/opencloud-eu/opencloud/pkg/registry" "github.com/opencloud-eu/opencloud/pkg/version" + "github.com/opencloud-eu/opencloud/services/auth-machine/pkg/config" "github.com/olekukonko/tablewriter" "github.com/olekukonko/tablewriter/tw" - "github.com/opencloud-eu/opencloud/services/auth-machine/pkg/config" - "github.com/urfave/cli/v2" + "github.com/spf13/cobra" ) // Version prints the service versions of all running instances. -func Version(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: "version", - Usage: "print the version of this binary and the running service instances", - Category: "info", - Action: func(c *cli.Context) error { +func Version(cfg *config.Config) *cobra.Command { + return &cobra.Command{ + Use: "version", + Short: "print the version of this binary and the running service instances", + RunE: func(cmd *cobra.Command, args []string) error { fmt.Println("Version: " + version.GetString()) fmt.Printf("Compiled: %s\n", version.Compiled()) fmt.Println("") From eb8a19abba09c34b9deb4f52d73adecce64b1253 Mon Sep 17 00:00:00 2001 From: Christian Richter Date: Tue, 2 Dec 2025 13:16:30 +0100 Subject: [PATCH 10/82] migrate auth-service from urfave/cli to spf13/cobra Signed-off-by: Christian Richter --- services/auth-service/pkg/command/health.go | 16 ++++++++-------- services/auth-service/pkg/command/root.go | 18 ++++++++++-------- services/auth-service/pkg/command/server.go | 18 +++++++++--------- services/auth-service/pkg/command/version.go | 15 +++++++-------- 4 files changed, 34 insertions(+), 33 deletions(-) diff --git a/services/auth-service/pkg/command/health.go b/services/auth-service/pkg/command/health.go index c2820d8230..d9124a91ac 100644 --- a/services/auth-service/pkg/command/health.go +++ b/services/auth-service/pkg/command/health.go @@ -8,19 +8,19 @@ import ( "github.com/opencloud-eu/opencloud/services/auth-service/pkg/config" "github.com/opencloud-eu/opencloud/services/auth-service/pkg/config/parser" "github.com/opencloud-eu/opencloud/services/auth-service/pkg/logging" - "github.com/urfave/cli/v2" + + "github.com/spf13/cobra" ) // Health is the entrypoint for the health command. -func Health(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: "health", - Usage: "check health status", - Category: "info", - Before: func(c *cli.Context) error { +func Health(cfg *config.Config) *cobra.Command { + return &cobra.Command{ + Use: "health", + Short: "check health status", + PreRunE: func(cmd *cobra.Command, args []string) error { return configlog.ReturnError(parser.ParseConfig(cfg)) }, - Action: func(c *cli.Context) error { + RunE: func(cmd *cobra.Command, args []string) error { logger := logging.Configure(cfg.Service.Name, cfg.Log) resp, err := http.Get( diff --git a/services/auth-service/pkg/command/root.go b/services/auth-service/pkg/command/root.go index fe7f98ae30..0d36a806a3 100644 --- a/services/auth-service/pkg/command/root.go +++ b/services/auth-service/pkg/command/root.go @@ -5,12 +5,13 @@ import ( "github.com/opencloud-eu/opencloud/pkg/clihelper" "github.com/opencloud-eu/opencloud/services/auth-service/pkg/config" - "github.com/urfave/cli/v2" + + "github.com/spf13/cobra" ) // GetCommands provides all commands for this service -func GetCommands(cfg *config.Config) cli.Commands { - return []*cli.Command{ +func GetCommands(cfg *config.Config) []*cobra.Command { + return []*cobra.Command{ // start this service Server(cfg), @@ -24,11 +25,12 @@ func GetCommands(cfg *config.Config) cli.Commands { // Execute is the entry point for the opencloud auth-service command. func Execute(cfg *config.Config) error { - app := clihelper.DefaultApp(&cli.App{ - Name: "auth-service", - Usage: "Provide service authentication for OpenCloud", - Commands: GetCommands(cfg), + app := clihelper.DefaultAppCobra(&cobra.Command{ + Use: "auth-service", + Short: "Provide service authentication for OpenCloud", }) + app.AddCommand(GetCommands(cfg)...) + app.SetArgs(os.Args[1:]) - return app.RunContext(cfg.Context, os.Args) + return app.ExecuteContext(cfg.Context) } diff --git a/services/auth-service/pkg/command/server.go b/services/auth-service/pkg/command/server.go index 1ab9fe87af..b6a4aab21b 100644 --- a/services/auth-service/pkg/command/server.go +++ b/services/auth-service/pkg/command/server.go @@ -16,21 +16,21 @@ import ( "github.com/opencloud-eu/opencloud/services/auth-service/pkg/revaconfig" "github.com/opencloud-eu/opencloud/services/auth-service/pkg/server/debug" "github.com/opencloud-eu/reva/v2/cmd/revad/runtime" - "github.com/urfave/cli/v2" + + "github.com/spf13/cobra" ) // Server is the entry point for the server command. -func Server(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: "server", - Usage: fmt.Sprintf("start the %s service without runtime (unsupervised mode)", cfg.Service.Name), - Category: "server", - Before: func(c *cli.Context) error { +func Server(cfg *config.Config) *cobra.Command { + return &cobra.Command{ + Use: "server", + Short: fmt.Sprintf("start the %s service without runtime (unsupervised mode)", cfg.Service.Name), + PreRunE: func(cmd *cobra.Command, args []string) error { return configlog.ReturnFatal(parser.ParseConfig(cfg)) }, - Action: func(c *cli.Context) error { + RunE: func(cmd *cobra.Command, args []string) error { logger := logging.Configure(cfg.Service.Name, cfg.Log) - traceProvider, err := tracing.GetTraceProvider(c.Context, cfg.Commons.TracesExporter, cfg.Service.Name) + traceProvider, err := tracing.GetTraceProvider(cmd.Context(), cfg.Commons.TracesExporter, cfg.Service.Name) if err != nil { return err } diff --git a/services/auth-service/pkg/command/version.go b/services/auth-service/pkg/command/version.go index 0f3656b3dd..15410209e2 100644 --- a/services/auth-service/pkg/command/version.go +++ b/services/auth-service/pkg/command/version.go @@ -6,20 +6,19 @@ import ( "github.com/opencloud-eu/opencloud/pkg/registry" "github.com/opencloud-eu/opencloud/pkg/version" + "github.com/opencloud-eu/opencloud/services/auth-service/pkg/config" "github.com/olekukonko/tablewriter" "github.com/olekukonko/tablewriter/tw" - "github.com/opencloud-eu/opencloud/services/auth-service/pkg/config" - "github.com/urfave/cli/v2" + "github.com/spf13/cobra" ) // Version prints the service versions of all running instances. -func Version(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: "version", - Usage: "print the version of this binary and the running service instances", - Category: "info", - Action: func(c *cli.Context) error { +func Version(cfg *config.Config) *cobra.Command { + return &cobra.Command{ + Use: "version", + Short: "print the version of this binary and the running service instances", + RunE: func(cmd *cobra.Command, args []string) error { fmt.Println("Version: " + version.GetString()) fmt.Printf("Compiled: %s\n", version.Compiled()) fmt.Println("") From f4526d4bc878e3c4efe6b3eb9d96a36de1c24516 Mon Sep 17 00:00:00 2001 From: Christian Richter Date: Tue, 2 Dec 2025 13:20:57 +0100 Subject: [PATCH 11/82] migrate clientlog from urfave/cli to spf13/cobra Signed-off-by: Christian Richter --- services/clientlog/pkg/command/health.go | 13 ++++++------ services/clientlog/pkg/command/root.go | 18 ++++++++-------- services/clientlog/pkg/command/server.go | 25 +++++++++++------------ services/clientlog/pkg/command/version.go | 14 ++++++------- 4 files changed, 36 insertions(+), 34 deletions(-) diff --git a/services/clientlog/pkg/command/health.go b/services/clientlog/pkg/command/health.go index a120eaf505..379b3416ed 100644 --- a/services/clientlog/pkg/command/health.go +++ b/services/clientlog/pkg/command/health.go @@ -2,15 +2,16 @@ package command import ( "github.com/opencloud-eu/opencloud/services/clientlog/pkg/config" - "github.com/urfave/cli/v2" + + "github.com/spf13/cobra" ) // Health is the entrypoint for the health command. -func Health(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: "health", - Usage: "Check health status", - Action: func(c *cli.Context) error { +func Health(cfg *config.Config) *cobra.Command { + return &cobra.Command{ + Use: "health", + Short: "Check health status", + RunE: func(cmd *cobra.Command, args []string) error { // Not implemented return nil }, diff --git a/services/clientlog/pkg/command/root.go b/services/clientlog/pkg/command/root.go index 8cfc0d1e57..38d8ea9e10 100644 --- a/services/clientlog/pkg/command/root.go +++ b/services/clientlog/pkg/command/root.go @@ -5,12 +5,13 @@ import ( "github.com/opencloud-eu/opencloud/pkg/clihelper" "github.com/opencloud-eu/opencloud/services/clientlog/pkg/config" - "github.com/urfave/cli/v2" + + "github.com/spf13/cobra" ) // GetCommands provides all commands for this service -func GetCommands(cfg *config.Config) cli.Commands { - return []*cli.Command{ +func GetCommands(cfg *config.Config) []*cobra.Command { + return []*cobra.Command{ // start this service Server(cfg), @@ -24,11 +25,12 @@ func GetCommands(cfg *config.Config) cli.Commands { // Execute is the entry point for the clientlog command. func Execute(cfg *config.Config) error { - app := clihelper.DefaultApp(&cli.App{ - Name: "clientlog", - Usage: "starts clientlog service", - Commands: GetCommands(cfg), + app := clihelper.DefaultAppCobra(&cobra.Command{ + Use: "clientlog", + Short: "starts clientlog service", }) + app.AddCommand(GetCommands(cfg)...) + app.SetArgs(os.Args[1:]) - return app.RunContext(cfg.Context, os.Args) + return app.ExecuteContext(cfg.Context) } diff --git a/services/clientlog/pkg/command/server.go b/services/clientlog/pkg/command/server.go index 41bc8b24da..6156255837 100644 --- a/services/clientlog/pkg/command/server.go +++ b/services/clientlog/pkg/command/server.go @@ -5,11 +5,6 @@ import ( "fmt" "os/signal" - "github.com/opencloud-eu/reva/v2/pkg/events" - "github.com/opencloud-eu/reva/v2/pkg/events/stream" - "github.com/opencloud-eu/reva/v2/pkg/rgrpc/todo/pool" - "github.com/urfave/cli/v2" - "github.com/opencloud-eu/opencloud/pkg/config/configlog" "github.com/opencloud-eu/opencloud/pkg/generators" "github.com/opencloud-eu/opencloud/pkg/registry" @@ -22,6 +17,11 @@ import ( "github.com/opencloud-eu/opencloud/services/clientlog/pkg/metrics" "github.com/opencloud-eu/opencloud/services/clientlog/pkg/server/debug" "github.com/opencloud-eu/opencloud/services/clientlog/pkg/service" + "github.com/opencloud-eu/reva/v2/pkg/events" + "github.com/opencloud-eu/reva/v2/pkg/events/stream" + "github.com/opencloud-eu/reva/v2/pkg/rgrpc/todo/pool" + + "github.com/spf13/cobra" ) // all events we care about @@ -47,17 +47,16 @@ var _registeredEvents = []events.Unmarshaller{ } // Server is the entrypoint for the server command. -func Server(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: "server", - Usage: fmt.Sprintf("start the %s service without runtime (unsupervised mode)", cfg.Service.Name), - Category: "server", - Before: func(c *cli.Context) error { +func Server(cfg *config.Config) *cobra.Command { + return &cobra.Command{ + Use: "server", + Short: fmt.Sprintf("start the %s service without runtime (unsupervised mode)", cfg.Service.Name), + PreRunE: func(cmd *cobra.Command, args []string) error { return configlog.ReturnFatal(parser.ParseConfig(cfg)) }, - Action: func(c *cli.Context) error { + RunE: func(cmd *cobra.Command, args []string) error { logger := logging.Configure(cfg.Service.Name, cfg.Log) - tracerProvider, err := tracing.GetTraceProvider(c.Context, cfg.Commons.TracesExporter, cfg.Service.Name) + tracerProvider, err := tracing.GetTraceProvider(cmd.Context(), cfg.Commons.TracesExporter, cfg.Service.Name) if err != nil { return err } diff --git a/services/clientlog/pkg/command/version.go b/services/clientlog/pkg/command/version.go index 284b5fa181..bdeec714d1 100644 --- a/services/clientlog/pkg/command/version.go +++ b/services/clientlog/pkg/command/version.go @@ -2,16 +2,16 @@ package command import ( "github.com/opencloud-eu/opencloud/services/clientlog/pkg/config" - "github.com/urfave/cli/v2" + + "github.com/spf13/cobra" ) // Version prints the service versions of all running instances. -func Version(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: "version", - Usage: "print the version of this binary and the running service instances", - Category: "info", - Action: func(c *cli.Context) error { +func Version(cfg *config.Config) *cobra.Command { + return &cobra.Command{ + Use: "version", + Short: "print the version of this binary and the running service instances", + RunE: func(cmd *cobra.Command, args []string) error { // not implemented return nil }, From e6114853ea8c0aa5f991fdcc605b44d709ba0bf9 Mon Sep 17 00:00:00 2001 From: Christian Richter Date: Tue, 2 Dec 2025 14:24:14 +0100 Subject: [PATCH 12/82] migrate collaboration from urfave/cli to spf13/cobra Signed-off-by: Christian Richter --- .../collaboration/cmd/collaboration/main.go | 19 +++++++++++++++++ services/collaboration/pkg/command/health.go | 16 +++++++------- services/collaboration/pkg/command/root.go | 18 +++++++++------- services/collaboration/pkg/command/server.go | 21 +++++++++---------- services/collaboration/pkg/command/version.go | 15 +++++++------ 5 files changed, 54 insertions(+), 35 deletions(-) create mode 100644 services/collaboration/cmd/collaboration/main.go diff --git a/services/collaboration/cmd/collaboration/main.go b/services/collaboration/cmd/collaboration/main.go new file mode 100644 index 0000000000..64571354fe --- /dev/null +++ b/services/collaboration/cmd/collaboration/main.go @@ -0,0 +1,19 @@ +package main + +import ( + "context" + "os" + "os/signal" + "syscall" + + "github.com/opencloud-eu/opencloud/services/activitylog/pkg/command" + "github.com/opencloud-eu/opencloud/services/activitylog/pkg/config/defaults" +) + +func main() { + cfg := defaults.DefaultConfig() + cfg.Context, _ = signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT, syscall.SIGHUP) + if err := command.Execute(cfg); err != nil { + os.Exit(1) + } +} diff --git a/services/collaboration/pkg/command/health.go b/services/collaboration/pkg/command/health.go index e85436ea0f..35e8414d4f 100644 --- a/services/collaboration/pkg/command/health.go +++ b/services/collaboration/pkg/command/health.go @@ -8,19 +8,19 @@ import ( "github.com/opencloud-eu/opencloud/services/collaboration/pkg/config" "github.com/opencloud-eu/opencloud/services/collaboration/pkg/config/parser" "github.com/opencloud-eu/opencloud/services/collaboration/pkg/logging" - "github.com/urfave/cli/v2" + + "github.com/spf13/cobra" ) // Health is the entrypoint for the health command. -func Health(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: "health", - Usage: "check health status", - Category: "info", - Before: func(c *cli.Context) error { +func Health(cfg *config.Config) *cobra.Command { + return &cobra.Command{ + Use: "health", + Short: "check health status", + PreRunE: func(cmd *cobra.Command, args []string) error { return configlog.ReturnError(parser.ParseConfig(cfg)) }, - Action: func(c *cli.Context) error { + RunE: func(cmd *cobra.Command, args []string) error { logger := logging.Configure(cfg.Service.Name, cfg.Log) resp, err := http.Get( diff --git a/services/collaboration/pkg/command/root.go b/services/collaboration/pkg/command/root.go index 402bba75f9..779287dba4 100644 --- a/services/collaboration/pkg/command/root.go +++ b/services/collaboration/pkg/command/root.go @@ -5,12 +5,13 @@ import ( "github.com/opencloud-eu/opencloud/pkg/clihelper" "github.com/opencloud-eu/opencloud/services/collaboration/pkg/config" - "github.com/urfave/cli/v2" + + "github.com/spf13/cobra" ) // GetCommands provides all commands for this service -func GetCommands(cfg *config.Config) cli.Commands { - return []*cli.Command{ +func GetCommands(cfg *config.Config) []*cobra.Command { + return []*cobra.Command{ Server(cfg), Health(cfg), Version(cfg), @@ -19,11 +20,12 @@ func GetCommands(cfg *config.Config) cli.Commands { // Execute is the entry point for the antivirus command. func Execute(cfg *config.Config) error { - app := clihelper.DefaultApp(&cli.App{ - Name: "collaboration", - Usage: "Serve WOPI for OpenCloud", - Commands: GetCommands(cfg), + app := clihelper.DefaultAppCobra(&cobra.Command{ + Use: "collaboration", + Short: "Serve WOPI for OpenCloud", }) + app.AddCommand(GetCommands(cfg)...) + app.SetArgs(os.Args[1:]) - return app.RunContext(cfg.Context, os.Args) + return app.ExecuteContext(cfg.Context) } diff --git a/services/collaboration/pkg/command/server.go b/services/collaboration/pkg/command/server.go index e6f3e135dc..ea47dabe33 100644 --- a/services/collaboration/pkg/command/server.go +++ b/services/collaboration/pkg/command/server.go @@ -7,9 +7,6 @@ import ( "os/signal" "time" - "github.com/urfave/cli/v2" - microstore "go-micro.dev/v4/store" - "github.com/opencloud-eu/opencloud/pkg/config/configlog" "github.com/opencloud-eu/opencloud/pkg/registry" "github.com/opencloud-eu/opencloud/pkg/runner" @@ -24,20 +21,22 @@ import ( "github.com/opencloud-eu/opencloud/services/collaboration/pkg/server/http" "github.com/opencloud-eu/reva/v2/pkg/rgrpc/todo/pool" "github.com/opencloud-eu/reva/v2/pkg/store" + + "github.com/spf13/cobra" + microstore "go-micro.dev/v4/store" ) // Server is the entrypoint for the server command. -func Server(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: "server", - Usage: fmt.Sprintf("start the %s service without runtime (unsupervised mode)", cfg.Service.Name), - Category: "server", - Before: func(c *cli.Context) error { +func Server(cfg *config.Config) *cobra.Command { + return &cobra.Command{ + Use: "server", + Short: fmt.Sprintf("start the %s service without runtime (unsupervised mode)", cfg.Service.Name), + PreRunE: func(cmd *cobra.Command, args []string) error { return configlog.ReturnFatal(parser.ParseConfig(cfg)) }, - Action: func(c *cli.Context) error { + RunE: func(cmd *cobra.Command, args []string) error { logger := logging.Configure(cfg.Service.Name, cfg.Log) - traceProvider, err := tracing.GetTraceProvider(c.Context, cfg.Commons.TracesExporter, cfg.Service.Name) + traceProvider, err := tracing.GetTraceProvider(cmd.Context(), cfg.Commons.TracesExporter, cfg.Service.Name) if err != nil { return err } diff --git a/services/collaboration/pkg/command/version.go b/services/collaboration/pkg/command/version.go index 42ebc90ea5..d651c48bd9 100644 --- a/services/collaboration/pkg/command/version.go +++ b/services/collaboration/pkg/command/version.go @@ -6,20 +6,19 @@ import ( "github.com/opencloud-eu/opencloud/pkg/registry" "github.com/opencloud-eu/opencloud/pkg/version" + "github.com/opencloud-eu/opencloud/services/collaboration/pkg/config" "github.com/olekukonko/tablewriter" "github.com/olekukonko/tablewriter/tw" - "github.com/opencloud-eu/opencloud/services/collaboration/pkg/config" - "github.com/urfave/cli/v2" + "github.com/spf13/cobra" ) // Version prints the service versions of all running instances. -func Version(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: "version", - Usage: "print the version of this binary and the running service instances", - Category: "info", - Action: func(c *cli.Context) error { +func Version(cfg *config.Config) *cobra.Command { + return &cobra.Command{ + Use: "version", + Short: "print the version of this binary and the running service instances", + RunE: func(cmd *cobra.Command, args []string) error { fmt.Println("Version: " + version.GetString()) fmt.Printf("Compiled: %s\n", version.Compiled()) fmt.Println("") From e2964e284d8addc1e34cf978247804491a6201e5 Mon Sep 17 00:00:00 2001 From: Christian Richter Date: Tue, 2 Dec 2025 14:28:33 +0100 Subject: [PATCH 13/82] migrate eventhistory from urfave/cli to spf13/cobra Signed-off-by: Christian Richter --- services/eventhistory/pkg/command/health.go | 13 +++++----- services/eventhistory/pkg/command/root.go | 18 +++++++------- services/eventhistory/pkg/command/server.go | 25 ++++++++++---------- services/eventhistory/pkg/command/version.go | 14 +++++------ 4 files changed, 36 insertions(+), 34 deletions(-) diff --git a/services/eventhistory/pkg/command/health.go b/services/eventhistory/pkg/command/health.go index 43b91f2ce2..df7326af54 100644 --- a/services/eventhistory/pkg/command/health.go +++ b/services/eventhistory/pkg/command/health.go @@ -2,15 +2,16 @@ package command import ( "github.com/opencloud-eu/opencloud/services/eventhistory/pkg/config" - "github.com/urfave/cli/v2" + + "github.com/spf13/cobra" ) // Health is the entrypoint for the health command. -func Health(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: "health", - Usage: "Check health status", - Action: func(c *cli.Context) error { +func Health(cfg *config.Config) *cobra.Command { + return &cobra.Command{ + Use: "health", + Short: "Check health status", + RunE: func(cmd *cobra.Command, args []string) error { // Not implemented return nil }, diff --git a/services/eventhistory/pkg/command/root.go b/services/eventhistory/pkg/command/root.go index 61e35da61b..11d1f8dde7 100644 --- a/services/eventhistory/pkg/command/root.go +++ b/services/eventhistory/pkg/command/root.go @@ -5,12 +5,13 @@ import ( "github.com/opencloud-eu/opencloud/pkg/clihelper" "github.com/opencloud-eu/opencloud/services/eventhistory/pkg/config" - "github.com/urfave/cli/v2" + + "github.com/spf13/cobra" ) // GetCommands provides all commands for this service -func GetCommands(cfg *config.Config) cli.Commands { - return []*cli.Command{ +func GetCommands(cfg *config.Config) []*cobra.Command { + return []*cobra.Command{ // start this service Server(cfg), @@ -24,11 +25,12 @@ func GetCommands(cfg *config.Config) cli.Commands { // Execute is the entry point for the eventhistory command. func Execute(cfg *config.Config) error { - app := clihelper.DefaultApp(&cli.App{ - Name: "eventhistory", - Usage: "starts eventhistory service", - Commands: GetCommands(cfg), + app := clihelper.DefaultAppCobra(&cobra.Command{ + Use: "eventhistory", + Short: "starts eventhistory service", }) + app.AddCommand(GetCommands(cfg)...) + app.SetArgs(os.Args[1:]) - return app.RunContext(cfg.Context, os.Args) + return app.ExecuteContext(cfg.Context) } diff --git a/services/eventhistory/pkg/command/server.go b/services/eventhistory/pkg/command/server.go index 12200b4072..94cdfc927d 100644 --- a/services/eventhistory/pkg/command/server.go +++ b/services/eventhistory/pkg/command/server.go @@ -5,11 +5,6 @@ import ( "fmt" "os/signal" - "github.com/opencloud-eu/reva/v2/pkg/events/stream" - "github.com/opencloud-eu/reva/v2/pkg/store" - "github.com/urfave/cli/v2" - microstore "go-micro.dev/v4/store" - "github.com/opencloud-eu/opencloud/pkg/config/configlog" "github.com/opencloud-eu/opencloud/pkg/generators" "github.com/opencloud-eu/opencloud/pkg/runner" @@ -22,20 +17,24 @@ import ( "github.com/opencloud-eu/opencloud/services/eventhistory/pkg/metrics" "github.com/opencloud-eu/opencloud/services/eventhistory/pkg/server/debug" "github.com/opencloud-eu/opencloud/services/eventhistory/pkg/server/grpc" + "github.com/opencloud-eu/reva/v2/pkg/events/stream" + "github.com/opencloud-eu/reva/v2/pkg/store" + + "github.com/spf13/cobra" + microstore "go-micro.dev/v4/store" ) // Server is the entrypoint for the server command. -func Server(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: "server", - Usage: fmt.Sprintf("start the %s service without runtime (unsupervised mode)", cfg.Service.Name), - Category: "server", - Before: func(c *cli.Context) error { +func Server(cfg *config.Config) *cobra.Command { + return &cobra.Command{ + Use: "server", + Short: fmt.Sprintf("start the %s service without runtime (unsupervised mode)", cfg.Service.Name), + PreRunE: func(cmd *cobra.Command, args []string) error { return configlog.ReturnFatal(parser.ParseConfig(cfg)) }, - Action: func(c *cli.Context) error { + RunE: func(cmd *cobra.Command, args []string) error { logger := logging.Configure(cfg.Service.Name, cfg.Log) - traceProvider, err := tracing.GetTraceProvider(c.Context, cfg.Commons.TracesExporter, cfg.Service.Name) + traceProvider, err := tracing.GetTraceProvider(cmd.Context(), cfg.Commons.TracesExporter, cfg.Service.Name) if err != nil { return err } diff --git a/services/eventhistory/pkg/command/version.go b/services/eventhistory/pkg/command/version.go index ba492b66cf..9b1c3e0267 100644 --- a/services/eventhistory/pkg/command/version.go +++ b/services/eventhistory/pkg/command/version.go @@ -2,16 +2,16 @@ package command import ( "github.com/opencloud-eu/opencloud/services/eventhistory/pkg/config" - "github.com/urfave/cli/v2" + + "github.com/spf13/cobra" ) // Version prints the service versions of all running instances. -func Version(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: "version", - Usage: "print the version of this binary and the running service instances", - Category: "info", - Action: func(c *cli.Context) error { +func Version(cfg *config.Config) *cobra.Command { + return &cobra.Command{ + Use: "version", + Short: "print the version of this binary and the running service instances", + RunE: func(cmd *cobra.Command, args []string) error { // not implemented return nil }, From 947a76803cff2be83a2112f4d5655d1957c37ab3 Mon Sep 17 00:00:00 2001 From: Christian Richter Date: Tue, 2 Dec 2025 14:32:47 +0100 Subject: [PATCH 14/82] migrate frontend from urfave/cli to spf13/cobra Signed-off-by: Christian Richter --- services/frontend/pkg/command/health.go | 17 ++++++++--------- services/frontend/pkg/command/root.go | 18 ++++++++++-------- services/frontend/pkg/command/server.go | 21 ++++++++++----------- services/frontend/pkg/command/version.go | 13 ++++++------- 4 files changed, 34 insertions(+), 35 deletions(-) diff --git a/services/frontend/pkg/command/health.go b/services/frontend/pkg/command/health.go index 537c977a76..43ddd2a93c 100644 --- a/services/frontend/pkg/command/health.go +++ b/services/frontend/pkg/command/health.go @@ -5,24 +5,23 @@ import ( "fmt" "net/http" - "github.com/urfave/cli/v2" - "github.com/opencloud-eu/opencloud/pkg/config/configlog" "github.com/opencloud-eu/opencloud/services/frontend/pkg/config" "github.com/opencloud-eu/opencloud/services/frontend/pkg/config/parser" "github.com/opencloud-eu/opencloud/services/frontend/pkg/logging" + + "github.com/spf13/cobra" ) // Health is the entrypoint for the health command. -func Health(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: "health", - Usage: "check health status", - Category: "info", - Before: func(c *cli.Context) error { +func Health(cfg *config.Config) *cobra.Command { + return &cobra.Command{ + Use: "health", + Short: "check health status", + PreRunE: func(cmd *cobra.Command, args []string) error { return configlog.ReturnError(parser.ParseConfig(cfg)) }, - Action: func(c *cli.Context) error { + RunE: func(cmd *cobra.Command, args []string) error { logger := logging.Configure(cfg.Service.Name, cfg.Log) resp, err := http.Get( diff --git a/services/frontend/pkg/command/root.go b/services/frontend/pkg/command/root.go index 87c8783d8d..52d5cf9c38 100644 --- a/services/frontend/pkg/command/root.go +++ b/services/frontend/pkg/command/root.go @@ -5,12 +5,13 @@ import ( "github.com/opencloud-eu/opencloud/pkg/clihelper" "github.com/opencloud-eu/opencloud/services/frontend/pkg/config" - "github.com/urfave/cli/v2" + + "github.com/spf13/cobra" ) // GetCommands provides all commands for this service -func GetCommands(cfg *config.Config) cli.Commands { - return []*cli.Command{ +func GetCommands(cfg *config.Config) []*cobra.Command { + return []*cobra.Command{ // start this service Server(cfg), @@ -24,11 +25,12 @@ func GetCommands(cfg *config.Config) cli.Commands { // Execute is the entry point for the opencloud-frontend command. func Execute(cfg *config.Config) error { - app := clihelper.DefaultApp(&cli.App{ - Name: "frontend", - Usage: "Provide various HTTP apis for OpenCloud", - Commands: GetCommands(cfg), + app := clihelper.DefaultAppCobra(&cobra.Command{ + Use: "frontend", + Short: "Provide various HTTP apis for OpenCloud", }) + app.AddCommand(GetCommands(cfg)...) + app.SetArgs(os.Args[1:]) - return app.RunContext(cfg.Context, os.Args) + return app.ExecuteContext(cfg.Context) } diff --git a/services/frontend/pkg/command/server.go b/services/frontend/pkg/command/server.go index 355f040a01..547c7c8b22 100644 --- a/services/frontend/pkg/command/server.go +++ b/services/frontend/pkg/command/server.go @@ -5,9 +5,6 @@ import ( "fmt" "os/signal" - "github.com/opencloud-eu/reva/v2/cmd/revad/runtime" - "github.com/urfave/cli/v2" - "github.com/opencloud-eu/opencloud/pkg/config/configlog" "github.com/opencloud-eu/opencloud/pkg/registry" "github.com/opencloud-eu/opencloud/pkg/runner" @@ -18,20 +15,22 @@ import ( "github.com/opencloud-eu/opencloud/services/frontend/pkg/logging" "github.com/opencloud-eu/opencloud/services/frontend/pkg/revaconfig" "github.com/opencloud-eu/opencloud/services/frontend/pkg/server/debug" + "github.com/opencloud-eu/reva/v2/cmd/revad/runtime" + + "github.com/spf13/cobra" ) // Server is the entry point for the server command. -func Server(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: "server", - Usage: fmt.Sprintf("start the %s service without runtime (unsupervised mode)", cfg.Service.Name), - Category: "server", - Before: func(c *cli.Context) error { +func Server(cfg *config.Config) *cobra.Command { + return &cobra.Command{ + Use: "server", + Short: fmt.Sprintf("start the %s service without runtime (unsupervised mode)", cfg.Service.Name), + PreRunE: func(cmd *cobra.Command, args []string) error { return configlog.ReturnFatal(parser.ParseConfig(cfg)) }, - Action: func(c *cli.Context) error { + RunE: func(cmd *cobra.Command, args []string) error { logger := logging.Configure(cfg.Service.Name, cfg.Log) - traceProvider, err := tracing.GetTraceProvider(c.Context, cfg.Commons.TracesExporter, cfg.Service.Name) + traceProvider, err := tracing.GetTraceProvider(cmd.Context(), cfg.Commons.TracesExporter, cfg.Service.Name) if err != nil { return err } diff --git a/services/frontend/pkg/command/version.go b/services/frontend/pkg/command/version.go index 71d7c7260f..02c8bf2141 100644 --- a/services/frontend/pkg/command/version.go +++ b/services/frontend/pkg/command/version.go @@ -10,16 +10,15 @@ import ( "github.com/olekukonko/tablewriter" "github.com/olekukonko/tablewriter/tw" "github.com/opencloud-eu/opencloud/services/frontend/pkg/config" - "github.com/urfave/cli/v2" + "github.com/spf13/cobra" ) // Version prints the service versions of all running instances. -func Version(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: "version", - Usage: "print the version of this binary and the running service instances", - Category: "info", - Action: func(c *cli.Context) error { +func Version(cfg *config.Config) *cobra.Command { + return &cobra.Command{ + Use: "version", + Short: "print the version of this binary and the running service instances", + RunE: func(cmd *cobra.Command, args []string) error { fmt.Println("Version: " + version.GetString()) fmt.Printf("Compiled: %s\n", version.Compiled()) fmt.Println("") From 57013e848b6bd3c256d1cba7fafb1ddea5bbd405 Mon Sep 17 00:00:00 2001 From: Christian Richter Date: Tue, 2 Dec 2025 14:36:26 +0100 Subject: [PATCH 15/82] migrate gateway from urfave/cli to spf13/cobra Signed-off-by: Christian Richter --- services/gateway/pkg/command/health.go | 16 ++++++++-------- services/gateway/pkg/command/root.go | 18 ++++++++++-------- services/gateway/pkg/command/server.go | 18 +++++++++--------- services/gateway/pkg/command/version.go | 15 +++++++-------- 4 files changed, 34 insertions(+), 33 deletions(-) diff --git a/services/gateway/pkg/command/health.go b/services/gateway/pkg/command/health.go index 1249bc18a5..83b35d2ee1 100644 --- a/services/gateway/pkg/command/health.go +++ b/services/gateway/pkg/command/health.go @@ -8,19 +8,19 @@ import ( "github.com/opencloud-eu/opencloud/services/gateway/pkg/config" "github.com/opencloud-eu/opencloud/services/gateway/pkg/config/parser" "github.com/opencloud-eu/opencloud/services/gateway/pkg/logging" - "github.com/urfave/cli/v2" + + "github.com/spf13/cobra" ) // Health is the entrypoint for the health command. -func Health(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: "health", - Usage: "check health status", - Category: "info", - Before: func(c *cli.Context) error { +func Health(cfg *config.Config) *cobra.Command { + return &cobra.Command{ + Use: "health", + Short: "check health status", + PreRunE: func(cmd *cobra.Command, args []string) error { return configlog.ReturnError(parser.ParseConfig(cfg)) }, - Action: func(c *cli.Context) error { + RunE: func(cmd *cobra.Command, args []string) error { logger := logging.Configure(cfg.Service.Name, cfg.Log) resp, err := http.Get( diff --git a/services/gateway/pkg/command/root.go b/services/gateway/pkg/command/root.go index 40dc50f5f0..0d91084137 100644 --- a/services/gateway/pkg/command/root.go +++ b/services/gateway/pkg/command/root.go @@ -5,12 +5,13 @@ import ( "github.com/opencloud-eu/opencloud/pkg/clihelper" "github.com/opencloud-eu/opencloud/services/gateway/pkg/config" - "github.com/urfave/cli/v2" + + "github.com/spf13/cobra" ) // GetCommands provides all commands for this service -func GetCommands(cfg *config.Config) cli.Commands { - return []*cli.Command{ +func GetCommands(cfg *config.Config) []*cobra.Command { + return []*cobra.Command{ // start this service Server(cfg), @@ -24,11 +25,12 @@ func GetCommands(cfg *config.Config) cli.Commands { // Execute is the entry point for the opencloud-gateway command. func Execute(cfg *config.Config) error { - app := clihelper.DefaultApp(&cli.App{ - Name: "gateway", - Usage: "Provide a CS3api gateway for OpenCloud", - Commands: GetCommands(cfg), + app := clihelper.DefaultAppCobra(&cobra.Command{ + Use: "gateway", + Short: "Provide a CS3api gateway for OpenCloud", }) + app.AddCommand(GetCommands(cfg)...) + app.SetArgs(os.Args[1:]) - return app.RunContext(cfg.Context, os.Args) + return app.ExecuteContext(cfg.Context) } diff --git a/services/gateway/pkg/command/server.go b/services/gateway/pkg/command/server.go index f9f2a846a1..038f881294 100644 --- a/services/gateway/pkg/command/server.go +++ b/services/gateway/pkg/command/server.go @@ -16,21 +16,21 @@ import ( "github.com/opencloud-eu/opencloud/services/gateway/pkg/revaconfig" "github.com/opencloud-eu/opencloud/services/gateway/pkg/server/debug" "github.com/opencloud-eu/reva/v2/cmd/revad/runtime" - "github.com/urfave/cli/v2" + + "github.com/spf13/cobra" ) // Server is the entry point for the server command. -func Server(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: "server", - Usage: fmt.Sprintf("start the %s service without runtime (unsupervised mode)", cfg.Service.Name), - Category: "server", - Before: func(c *cli.Context) error { +func Server(cfg *config.Config) *cobra.Command { + return &cobra.Command{ + Use: "server", + Short: fmt.Sprintf("start the %s service without runtime (unsupervised mode)", cfg.Service.Name), + PreRunE: func(cmd *cobra.Command, args []string) error { return configlog.ReturnFatal(parser.ParseConfig(cfg)) }, - Action: func(c *cli.Context) error { + RunE: func(cmd *cobra.Command, args []string) error { logger := logging.Configure(cfg.Service.Name, cfg.Log) - traceProvider, err := tracing.GetTraceProvider(c.Context, cfg.Commons.TracesExporter, cfg.Service.Name) + traceProvider, err := tracing.GetTraceProvider(cmd.Context(), cfg.Commons.TracesExporter, cfg.Service.Name) if err != nil { return err } diff --git a/services/gateway/pkg/command/version.go b/services/gateway/pkg/command/version.go index 903269ee10..e2b4e13380 100644 --- a/services/gateway/pkg/command/version.go +++ b/services/gateway/pkg/command/version.go @@ -6,20 +6,19 @@ import ( "github.com/opencloud-eu/opencloud/pkg/registry" "github.com/opencloud-eu/opencloud/pkg/version" + "github.com/opencloud-eu/opencloud/services/gateway/pkg/config" "github.com/olekukonko/tablewriter" "github.com/olekukonko/tablewriter/tw" - "github.com/opencloud-eu/opencloud/services/gateway/pkg/config" - "github.com/urfave/cli/v2" + "github.com/spf13/cobra" ) // Version prints the service versions of all running instances. -func Version(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: "version", - Usage: "print the version of this binary and the running service instances", - Category: "info", - Action: func(c *cli.Context) error { +func Version(cfg *config.Config) *cobra.Command { + return &cobra.Command{ + Use: "version", + Short: "print the version of this binary and the running service instances", + RunE: func(cmd *cobra.Command, args []string) error { fmt.Println("Version: " + version.GetString()) fmt.Printf("Compiled: %s\n", version.Compiled()) fmt.Println("") From 8d6f7a8942a194f969aff214eef6072bb1e44e99 Mon Sep 17 00:00:00 2001 From: Christian Richter Date: Tue, 2 Dec 2025 14:42:38 +0100 Subject: [PATCH 16/82] migrate graph from urfave/cli to spf13/cobra Signed-off-by: Christian Richter --- services/graph/pkg/command/health.go | 16 ++++++------ services/graph/pkg/command/root.go | 21 ++++++++------- services/graph/pkg/command/server.go | 25 +++++++++--------- services/graph/pkg/command/unified_roles.go | 29 ++++++++++----------- services/graph/pkg/command/version.go | 16 ++++++------ 5 files changed, 53 insertions(+), 54 deletions(-) diff --git a/services/graph/pkg/command/health.go b/services/graph/pkg/command/health.go index 06c3dd58da..cac63221b8 100644 --- a/services/graph/pkg/command/health.go +++ b/services/graph/pkg/command/health.go @@ -8,19 +8,19 @@ import ( "github.com/opencloud-eu/opencloud/services/graph/pkg/config" "github.com/opencloud-eu/opencloud/services/graph/pkg/config/parser" "github.com/opencloud-eu/opencloud/services/graph/pkg/logging" - "github.com/urfave/cli/v2" + + "github.com/spf13/cobra" ) // Health is the entrypoint for the health command. -func Health(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: "health", - Usage: "check health status", - Category: "info", - Before: func(c *cli.Context) error { +func Health(cfg *config.Config) *cobra.Command { + return &cobra.Command{ + Use: "health", + Short: "check health status", + PreRunE: func(cmd *cobra.Command, args []string) error { return configlog.ReturnError(parser.ParseConfig(cfg)) }, - Action: func(c *cli.Context) error { + RunE: func(cmd *cobra.Command, args []string) error { logger := logging.Configure(cfg.Service.Name, cfg.Log) resp, err := http.Get( diff --git a/services/graph/pkg/command/root.go b/services/graph/pkg/command/root.go index 6cd1a0b66b..e9f8c3f30e 100644 --- a/services/graph/pkg/command/root.go +++ b/services/graph/pkg/command/root.go @@ -4,15 +4,14 @@ import ( "os" "github.com/opencloud-eu/opencloud/pkg/clihelper" - - "github.com/urfave/cli/v2" - "github.com/opencloud-eu/opencloud/services/graph/pkg/config" + + "github.com/spf13/cobra" ) // GetCommands provides all commands for this service -func GetCommands(cfg *config.Config) cli.Commands { - return append([]*cli.Command{ +func GetCommands(cfg *config.Config) []*cobra.Command { + return append([]*cobra.Command{ // start this service Server(cfg), @@ -26,10 +25,12 @@ func GetCommands(cfg *config.Config) cli.Commands { // Execute is the entry point for the opencloud graph command. func Execute(cfg *config.Config) error { - app := clihelper.DefaultApp(&cli.App{ - Name: "graph", - Usage: "Serve Graph API for OpenCloud", - Commands: GetCommands(cfg), + app := clihelper.DefaultAppCobra(&cobra.Command{ + Use: "graph", + Short: "Serve Graph API for OpenCloud", }) - return app.RunContext(cfg.Context, os.Args) + app.AddCommand(GetCommands(cfg)...) + app.SetArgs(os.Args[1:]) + + return app.ExecuteContext(cfg.Context) } diff --git a/services/graph/pkg/command/server.go b/services/graph/pkg/command/server.go index 4642f88023..fb61fadd70 100644 --- a/services/graph/pkg/command/server.go +++ b/services/graph/pkg/command/server.go @@ -5,11 +5,6 @@ import ( "fmt" "os/signal" - "github.com/nats-io/nats.go" - "github.com/nats-io/nats.go/jetstream" - "github.com/pkg/errors" - "github.com/urfave/cli/v2" - "github.com/opencloud-eu/opencloud/pkg/config/configlog" "github.com/opencloud-eu/opencloud/pkg/runner" "github.com/opencloud-eu/opencloud/pkg/tracing" @@ -20,20 +15,24 @@ import ( "github.com/opencloud-eu/opencloud/services/graph/pkg/metrics" "github.com/opencloud-eu/opencloud/services/graph/pkg/server/debug" "github.com/opencloud-eu/opencloud/services/graph/pkg/server/http" + + "github.com/nats-io/nats.go" + "github.com/nats-io/nats.go/jetstream" + "github.com/pkg/errors" + "github.com/spf13/cobra" ) // Server is the entrypoint for the server command. -func Server(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: "server", - Usage: fmt.Sprintf("start the %s service without runtime (unsupervised mode)", cfg.Service.Name), - Category: "server", - Before: func(c *cli.Context) error { +func Server(cfg *config.Config) *cobra.Command { + return &cobra.Command{ + Use: "server", + Short: fmt.Sprintf("start the %s service without runtime (unsupervised mode)", cfg.Service.Name), + PreRunE: func(cmd *cobra.Command, args []string) error { return configlog.ReturnFatal(parser.ParseConfig(cfg)) }, - Action: func(c *cli.Context) error { + RunE: func(cmd *cobra.Command, args []string) error { logger := logging.Configure(cfg.Service.Name, cfg.Log) - traceProvider, err := tracing.GetTraceProvider(c.Context, cfg.Commons.TracesExporter, cfg.Service.Name) + traceProvider, err := tracing.GetTraceProvider(cmd.Context(), cfg.Commons.TracesExporter, cfg.Service.Name) if err != nil { return err } diff --git a/services/graph/pkg/command/unified_roles.go b/services/graph/pkg/command/unified_roles.go index 6df9f9703a..531eacab12 100644 --- a/services/graph/pkg/command/unified_roles.go +++ b/services/graph/pkg/command/unified_roles.go @@ -5,15 +5,15 @@ import ( "slices" "strings" - "github.com/olekukonko/tablewriter" - "github.com/olekukonko/tablewriter/renderer" - "github.com/olekukonko/tablewriter/tw" - "github.com/urfave/cli/v2" - "github.com/opencloud-eu/opencloud/pkg/config/configlog" "github.com/opencloud-eu/opencloud/services/graph/pkg/config" "github.com/opencloud-eu/opencloud/services/graph/pkg/config/parser" "github.com/opencloud-eu/opencloud/services/graph/pkg/unifiedrole" + + "github.com/olekukonko/tablewriter" + "github.com/olekukonko/tablewriter/renderer" + "github.com/olekukonko/tablewriter/tw" + "github.com/spf13/cobra" ) var ( @@ -34,15 +34,14 @@ var ( ) // UnifiedRoles bundles available commands for unified roles -func UnifiedRoles(cfg *config.Config) cli.Commands { - cmds := cli.Commands{ +func UnifiedRoles(cfg *config.Config) []*cobra.Command { + cmds := []*cobra.Command{ listUnifiedRoles(cfg), } for _, cmd := range cmds { - cmd.Category = "unified-roles" - cmd.Name = strings.Join([]string{cmd.Name, "unified-roles"}, "-") - cmd.Before = func(c *cli.Context) error { + cmd.Use = strings.Join([]string{cmd.Use, "unified-roles"}, "-") + cmd.PreRunE = func(cmd *cobra.Command, args []string) error { return configlog.ReturnError(parser.ParseConfig(cfg)) } } @@ -51,11 +50,11 @@ func UnifiedRoles(cfg *config.Config) cli.Commands { } // unifiedRolesStatus lists available unified roles, it contains an indicator to show if the role is enabled or not -func listUnifiedRoles(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: "list", - Usage: "list available unified roles", - Action: func(c *cli.Context) error { +func listUnifiedRoles(cfg *config.Config) *cobra.Command { + return &cobra.Command{ + Use: "list", + Short: "list available unified roles", + RunE: func(cmd *cobra.Command, args []string) error { r := tw.Rendition{ Settings: tw.Settings{ Separators: tw.Separators{ diff --git a/services/graph/pkg/command/version.go b/services/graph/pkg/command/version.go index ac418dc338..6836f10277 100644 --- a/services/graph/pkg/command/version.go +++ b/services/graph/pkg/command/version.go @@ -6,20 +6,20 @@ import ( "github.com/opencloud-eu/opencloud/pkg/registry" "github.com/opencloud-eu/opencloud/pkg/version" + "github.com/opencloud-eu/opencloud/services/graph/pkg/config" "github.com/olekukonko/tablewriter" "github.com/olekukonko/tablewriter/tw" - "github.com/opencloud-eu/opencloud/services/graph/pkg/config" - "github.com/urfave/cli/v2" + + "github.com/spf13/cobra" ) // Version prints the service versions of all running instances. -func Version(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: "version", - Usage: "print the version of this binary and the running service instances", - Category: "info", - Action: func(c *cli.Context) error { +func Version(cfg *config.Config) *cobra.Command { + return &cobra.Command{ + Use: "version", + Short: "print the version of this binary and the running service instances", + RunE: func(cmd *cobra.Command, args []string) error { fmt.Println("Version: " + version.GetString()) fmt.Printf("Compiled: %s\n", version.Compiled()) fmt.Println("") From 07956c97f814813ccc18507a500cd68d2e13f10c Mon Sep 17 00:00:00 2001 From: Christian Richter Date: Tue, 2 Dec 2025 14:46:03 +0100 Subject: [PATCH 17/82] migrate groups from urfave/cli to spf13/cobra Signed-off-by: Christian Richter --- services/groups/pkg/command/health.go | 16 ++++++++-------- services/groups/pkg/command/root.go | 18 ++++++++++-------- services/groups/pkg/command/server.go | 18 +++++++++--------- services/groups/pkg/command/version.go | 15 +++++++-------- 4 files changed, 34 insertions(+), 33 deletions(-) diff --git a/services/groups/pkg/command/health.go b/services/groups/pkg/command/health.go index 917dbbfb46..250309c204 100644 --- a/services/groups/pkg/command/health.go +++ b/services/groups/pkg/command/health.go @@ -8,19 +8,19 @@ import ( "github.com/opencloud-eu/opencloud/services/groups/pkg/config" "github.com/opencloud-eu/opencloud/services/groups/pkg/config/parser" "github.com/opencloud-eu/opencloud/services/groups/pkg/logging" - "github.com/urfave/cli/v2" + + "github.com/spf13/cobra" ) // Health is the entrypoint for the health command. -func Health(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: "health", - Usage: "check health status", - Category: "info", - Before: func(c *cli.Context) error { +func Health(cfg *config.Config) *cobra.Command { + return &cobra.Command{ + Use: "health", + Short: "check health status", + PreRunE: func(cmd *cobra.Command, args []string) error { return configlog.ReturnError(parser.ParseConfig(cfg)) }, - Action: func(c *cli.Context) error { + RunE: func(cmd *cobra.Command, args []string) error { logger := logging.Configure(cfg.Service.Name, cfg.Log) resp, err := http.Get( diff --git a/services/groups/pkg/command/root.go b/services/groups/pkg/command/root.go index c8172e866d..3931be653a 100644 --- a/services/groups/pkg/command/root.go +++ b/services/groups/pkg/command/root.go @@ -5,12 +5,13 @@ import ( "github.com/opencloud-eu/opencloud/pkg/clihelper" "github.com/opencloud-eu/opencloud/services/groups/pkg/config" - "github.com/urfave/cli/v2" + + "github.com/spf13/cobra" ) // GetCommands provides all commands for this service -func GetCommands(cfg *config.Config) cli.Commands { - return []*cli.Command{ +func GetCommands(cfg *config.Config) []*cobra.Command { + return []*cobra.Command{ // start this service Server(cfg), @@ -24,11 +25,12 @@ func GetCommands(cfg *config.Config) cli.Commands { // Execute is the entry point for the opencloud group command. func Execute(cfg *config.Config) error { - app := clihelper.DefaultApp(&cli.App{ - Name: "group", - Usage: "Provide groups for OpenCloud", - Commands: GetCommands(cfg), + app := clihelper.DefaultAppCobra(&cobra.Command{ + Use: "group", + Short: "Provide groups for OpenCloud", }) + app.AddCommand(GetCommands(cfg)...) + app.SetArgs(os.Args[1:]) - return app.RunContext(cfg.Context, os.Args) + return app.ExecuteContext(cfg.Context) } diff --git a/services/groups/pkg/command/server.go b/services/groups/pkg/command/server.go index 2c570d7ae5..053f3d5a9b 100644 --- a/services/groups/pkg/command/server.go +++ b/services/groups/pkg/command/server.go @@ -17,21 +17,21 @@ import ( "github.com/opencloud-eu/opencloud/services/groups/pkg/revaconfig" "github.com/opencloud-eu/opencloud/services/groups/pkg/server/debug" "github.com/opencloud-eu/reva/v2/cmd/revad/runtime" - "github.com/urfave/cli/v2" + + "github.com/spf13/cobra" ) // Server is the entry point for the server command. -func Server(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: "server", - Usage: fmt.Sprintf("start the %s service without runtime (unsupervised mode)", cfg.Service.Name), - Category: "server", - Before: func(c *cli.Context) error { +func Server(cfg *config.Config) *cobra.Command { + return &cobra.Command{ + Use: "server", + Short: fmt.Sprintf("start the %s service without runtime (unsupervised mode)", cfg.Service.Name), + PreRunE: func(cmd *cobra.Command, args []string) error { return configlog.ReturnFatal(parser.ParseConfig(cfg)) }, - Action: func(c *cli.Context) error { + RunE: func(cmd *cobra.Command, args []string) error { logger := logging.Configure(cfg.Service.Name, cfg.Log) - traceProvider, err := tracing.GetTraceProvider(c.Context, cfg.Commons.TracesExporter, cfg.Service.Name) + traceProvider, err := tracing.GetTraceProvider(cmd.Context(), cfg.Commons.TracesExporter, cfg.Service.Name) if err != nil { return err } diff --git a/services/groups/pkg/command/version.go b/services/groups/pkg/command/version.go index 3c6493b3c8..c2d4bc3abc 100644 --- a/services/groups/pkg/command/version.go +++ b/services/groups/pkg/command/version.go @@ -6,20 +6,19 @@ import ( "github.com/opencloud-eu/opencloud/pkg/registry" "github.com/opencloud-eu/opencloud/pkg/version" + "github.com/opencloud-eu/opencloud/services/groups/pkg/config" "github.com/olekukonko/tablewriter" "github.com/olekukonko/tablewriter/tw" - "github.com/opencloud-eu/opencloud/services/groups/pkg/config" - "github.com/urfave/cli/v2" + "github.com/spf13/cobra" ) // Version prints the service versions of all running instances. -func Version(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: "version", - Usage: "print the version of this binary and the running service instances", - Category: "info", - Action: func(c *cli.Context) error { +func Version(cfg *config.Config) *cobra.Command { + return &cobra.Command{ + Use: "version", + Short: "print the version of this binary and the running service instances", + RunE: func(cmd *cobra.Command, args []string) error { fmt.Println("Version: " + version.GetString()) fmt.Printf("Compiled: %s\n", version.Compiled()) fmt.Println("") From c8d021729b238a024f9078010822a52962c12b19 Mon Sep 17 00:00:00 2001 From: Christian Richter Date: Tue, 2 Dec 2025 14:56:20 +0100 Subject: [PATCH 18/82] migrate idm from urfave/cli to spf13/cobra Signed-off-by: Christian Richter --- services/idm/pkg/command/health.go | 16 +++++------ services/idm/pkg/command/resetpw.go | 42 ++++++++++++++--------------- services/idm/pkg/command/root.go | 18 +++++++------ services/idm/pkg/command/server.go | 25 +++++++++-------- services/idm/pkg/command/version.go | 14 +++++----- 5 files changed, 58 insertions(+), 57 deletions(-) diff --git a/services/idm/pkg/command/health.go b/services/idm/pkg/command/health.go index 0283dc5f97..050dd35e24 100644 --- a/services/idm/pkg/command/health.go +++ b/services/idm/pkg/command/health.go @@ -8,19 +8,19 @@ import ( "github.com/opencloud-eu/opencloud/services/idm/pkg/config" "github.com/opencloud-eu/opencloud/services/idm/pkg/config/parser" "github.com/opencloud-eu/opencloud/services/idm/pkg/logging" - "github.com/urfave/cli/v2" + + "github.com/spf13/cobra" ) // Health is the entrypoint for the health command. -func Health(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: "health", - Usage: "check health status", - Category: "info", - Before: func(c *cli.Context) error { +func Health(cfg *config.Config) *cobra.Command { + return &cobra.Command{ + Use: "health", + Short: "check health status", + PreRunE: func(cmd *cobra.Command, args []string) error { return configlog.ReturnError(parser.ParseConfig(cfg)) }, - Action: func(c *cli.Context) error { + RunE: func(cmd *cobra.Command, args []string) error { logger := logging.Configure(cfg.Service.Name, cfg.Log) resp, err := http.Get( diff --git a/services/idm/pkg/command/resetpw.go b/services/idm/pkg/command/resetpw.go index 8674fbb664..ab9d5f0c9c 100644 --- a/services/idm/pkg/command/resetpw.go +++ b/services/idm/pkg/command/resetpw.go @@ -8,44 +8,44 @@ import ( "syscall" "time" - "github.com/go-ldap/ldap/v3" - "github.com/libregraph/idm/pkg/ldbbolt" - "github.com/libregraph/idm/server" "github.com/opencloud-eu/opencloud/pkg/config/configlog" "github.com/opencloud-eu/opencloud/pkg/log" "github.com/opencloud-eu/opencloud/services/idm/pkg/config" "github.com/opencloud-eu/opencloud/services/idm/pkg/config/parser" "github.com/opencloud-eu/opencloud/services/idm/pkg/logging" - "github.com/urfave/cli/v2" + + "github.com/go-ldap/ldap/v3" + "github.com/libregraph/idm/pkg/ldbbolt" + "github.com/libregraph/idm/server" + "github.com/spf13/cobra" bolt "go.etcd.io/bbolt" "golang.org/x/term" ) // ResetPassword is the entrypoint for the resetpassword command -func ResetPassword(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: "resetpassword", - Usage: "Reset user password", - Category: "password reset", - Flags: []cli.Flag{ - &cli.StringFlag{ - Name: "user-name", - Aliases: []string{"u"}, - Usage: "User name", - Value: "admin", - }, - }, - Before: func(_ *cli.Context) error { +func ResetPassword(cfg *config.Config) *cobra.Command { + resetPasswordCmd := &cobra.Command{ + Use: "resetpassword", + Short: "Reset user password", + PreRunE: func(cmd *cobra.Command, args []string) error { return configlog.ReturnFatal(parser.ParseConfig(cfg)) }, - Action: func(c *cli.Context) error { + RunE: func(cmd *cobra.Command, args []string) error { logger := logging.Configure(cfg.Service.Name, cfg.Log) - ctx, cancel := context.WithCancel(c.Context) + ctx, cancel := context.WithCancel(cmd.Context()) defer cancel() - return resetPassword(ctx, logger, cfg, c.String("user-name")) + return resetPassword(ctx, logger, cfg, cmd.Flag("user-name").Value.String()) }, } + resetPasswordCmd.Flags().StringP( + "user-name", + "u", + "admin", + "User name", + ) + + return resetPasswordCmd } func resetPassword(_ context.Context, logger log.Logger, cfg *config.Config, userName string) error { diff --git a/services/idm/pkg/command/root.go b/services/idm/pkg/command/root.go index 8a84bf814a..c0fb60ba96 100644 --- a/services/idm/pkg/command/root.go +++ b/services/idm/pkg/command/root.go @@ -5,12 +5,13 @@ import ( "github.com/opencloud-eu/opencloud/pkg/clihelper" "github.com/opencloud-eu/opencloud/services/idm/pkg/config" - "github.com/urfave/cli/v2" + + "github.com/spf13/cobra" ) // GetCommands provides all commands for this service -func GetCommands(cfg *config.Config) cli.Commands { - return []*cli.Command{ +func GetCommands(cfg *config.Config) []*cobra.Command { + return []*cobra.Command{ // start this service Server(cfg), @@ -25,11 +26,12 @@ func GetCommands(cfg *config.Config) cli.Commands { // Execute is the entry point for the opencloud idm command. func Execute(cfg *config.Config) error { - app := clihelper.DefaultApp(&cli.App{ - Name: "idm", - Usage: "Embedded LDAP service for OpenCloud", - Commands: GetCommands(cfg), + app := clihelper.DefaultAppCobra(&cobra.Command{ + Use: "idm", + Short: "Embedded LDAP service for OpenCloud", }) + app.AddCommand(GetCommands(cfg)...) + app.SetArgs(os.Args[1:]) - return app.RunContext(cfg.Context, os.Args) + return app.ExecuteContext(cfg.Context) } diff --git a/services/idm/pkg/command/server.go b/services/idm/pkg/command/server.go index 3d860381a6..a71991563e 100644 --- a/services/idm/pkg/command/server.go +++ b/services/idm/pkg/command/server.go @@ -10,12 +10,6 @@ import ( "os/signal" "strings" - "github.com/go-ldap/ldif" - "github.com/libregraph/idm/pkg/ldappassword" - "github.com/libregraph/idm/pkg/ldbbolt" - "github.com/libregraph/idm/server" - "github.com/urfave/cli/v2" - "github.com/opencloud-eu/opencloud/pkg/config/configlog" pkgcrypto "github.com/opencloud-eu/opencloud/pkg/crypto" "github.com/opencloud-eu/opencloud/pkg/log" @@ -25,18 +19,23 @@ import ( "github.com/opencloud-eu/opencloud/services/idm/pkg/config/parser" "github.com/opencloud-eu/opencloud/services/idm/pkg/logging" "github.com/opencloud-eu/opencloud/services/idm/pkg/server/debug" + + "github.com/go-ldap/ldif" + "github.com/libregraph/idm/pkg/ldappassword" + "github.com/libregraph/idm/pkg/ldbbolt" + "github.com/libregraph/idm/server" + "github.com/spf13/cobra" ) // Server is the entrypoint for the server command. -func Server(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: "server", - Usage: fmt.Sprintf("start the %s service without runtime (unsupervised mode)", cfg.Service.Name), - Category: "server", - Before: func(c *cli.Context) error { +func Server(cfg *config.Config) *cobra.Command { + return &cobra.Command{ + Use: "server", + Short: fmt.Sprintf("start the %s service without runtime (unsupervised mode)", cfg.Service.Name), + PreRunE: func(cmd *cobra.Command, args []string) error { return configlog.ReturnFatal(parser.ParseConfig(cfg)) }, - Action: func(c *cli.Context) error { + RunE: func(cmd *cobra.Command, args []string) error { var cancel context.CancelFunc if cfg.Context == nil { cfg.Context, cancel = signal.NotifyContext(context.Background(), runner.StopSignals...) diff --git a/services/idm/pkg/command/version.go b/services/idm/pkg/command/version.go index 859e5f20af..fab8d43d03 100644 --- a/services/idm/pkg/command/version.go +++ b/services/idm/pkg/command/version.go @@ -2,16 +2,16 @@ package command import ( "github.com/opencloud-eu/opencloud/services/idm/pkg/config" - "github.com/urfave/cli/v2" + + "github.com/spf13/cobra" ) // Version prints the service versions of all running instances. -func Version(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: "version", - Usage: "print the version of this binary and the running service instances", - Category: "info", - Action: func(c *cli.Context) error { +func Version(cfg *config.Config) *cobra.Command { + return &cobra.Command{ + Use: "version", + Short: "print the version of this binary and the running service instances", + RunE: func(cmd *cobra.Command, args []string) error { // not implemented return nil }, From d322c0d3903204a9ca18a93cb5330b80e6011832 Mon Sep 17 00:00:00 2001 From: Christian Richter Date: Tue, 2 Dec 2025 15:00:26 +0100 Subject: [PATCH 19/82] migrate idp from urfave/cli to spf13/cobra Signed-off-by: Christian Richter --- services/idp/pkg/command/health.go | 16 ++++++++-------- services/idp/pkg/command/root.go | 19 +++++++++++-------- services/idp/pkg/command/server.go | 18 +++++++++--------- services/idp/pkg/command/version.go | 15 +++++++-------- 4 files changed, 35 insertions(+), 33 deletions(-) diff --git a/services/idp/pkg/command/health.go b/services/idp/pkg/command/health.go index be007115f8..2cb1f6f49c 100644 --- a/services/idp/pkg/command/health.go +++ b/services/idp/pkg/command/health.go @@ -8,19 +8,19 @@ import ( "github.com/opencloud-eu/opencloud/services/idp/pkg/config" "github.com/opencloud-eu/opencloud/services/idp/pkg/config/parser" "github.com/opencloud-eu/opencloud/services/idp/pkg/logging" - "github.com/urfave/cli/v2" + + "github.com/spf13/cobra" ) // Health is the entrypoint for the health command. -func Health(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: "health", - Usage: "check health status", - Category: "info", - Before: func(c *cli.Context) error { +func Health(cfg *config.Config) *cobra.Command { + return &cobra.Command{ + Use: "health", + Short: "check health status", + PreRunE: func(cmd *cobra.Command, args []string) error { return configlog.ReturnError(parser.ParseConfig(cfg)) }, - Action: func(c *cli.Context) error { + RunE: func(cmd *cobra.Command, args []string) error { logger := logging.Configure(cfg.Service.Name, cfg.Log) resp, err := http.Get( diff --git a/services/idp/pkg/command/root.go b/services/idp/pkg/command/root.go index 4ec02457ce..d535ff89ba 100644 --- a/services/idp/pkg/command/root.go +++ b/services/idp/pkg/command/root.go @@ -5,12 +5,13 @@ import ( "github.com/opencloud-eu/opencloud/pkg/clihelper" "github.com/opencloud-eu/opencloud/services/idp/pkg/config" - "github.com/urfave/cli/v2" + + "github.com/spf13/cobra" ) // GetCommands provides all commands for this service -func GetCommands(cfg *config.Config) cli.Commands { - return []*cli.Command{ +func GetCommands(cfg *config.Config) []*cobra.Command { + return []*cobra.Command{ // start this service Server(cfg), @@ -24,11 +25,13 @@ func GetCommands(cfg *config.Config) cli.Commands { // Execute is the entry point for the opencloud-idp command. func Execute(cfg *config.Config) error { - app := clihelper.DefaultApp(&cli.App{ - Name: "idp", - Usage: "Serve IDP API for OpenCloud", - Commands: GetCommands(cfg), + app := clihelper.DefaultAppCobra(&cobra.Command{ + Use: "idp", + Short: "Serve IDP API for OpenCloud", }) - return app.RunContext(cfg.Context, os.Args) + app.AddCommand(GetCommands(cfg)...) + app.SetArgs(os.Args[1:]) + + return app.ExecuteContext(cfg.Context) } diff --git a/services/idp/pkg/command/server.go b/services/idp/pkg/command/server.go index 9112a481a6..6cc97b8e25 100644 --- a/services/idp/pkg/command/server.go +++ b/services/idp/pkg/command/server.go @@ -25,18 +25,18 @@ import ( "github.com/opencloud-eu/opencloud/services/idp/pkg/metrics" "github.com/opencloud-eu/opencloud/services/idp/pkg/server/debug" "github.com/opencloud-eu/opencloud/services/idp/pkg/server/http" - "github.com/urfave/cli/v2" + + "github.com/spf13/cobra" ) const _rsaKeySize = 4096 // Server is the entrypoint for the server command. -func Server(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: "server", - Usage: fmt.Sprintf("start the %s service without runtime (unsupervised mode)", cfg.Service.Name), - Category: "server", - Before: func(c *cli.Context) error { +func Server(cfg *config.Config) *cobra.Command { + return &cobra.Command{ + Use: "server", + Short: fmt.Sprintf("start the %s service without runtime (unsupervised mode)", cfg.Service.Name), + PreRunE: func(cmd *cobra.Command, args []string) error { err := configlog.ReturnFatal(parser.ParseConfig(cfg)) if err != nil { return err @@ -52,9 +52,9 @@ func Server(cfg *config.Config) *cli.Command { } return nil }, - Action: func(c *cli.Context) error { + RunE: func(cmd *cobra.Command, args []string) error { logger := logging.Configure(cfg.Service.Name, cfg.Log) - traceProvider, err := tracing.GetTraceProvider(c.Context, cfg.Commons.TracesExporter, cfg.Service.Name) + traceProvider, err := tracing.GetTraceProvider(cmd.Context(), cfg.Commons.TracesExporter, cfg.Service.Name) if err != nil { return err } diff --git a/services/idp/pkg/command/version.go b/services/idp/pkg/command/version.go index 6a5982bedf..e3f515e1ab 100644 --- a/services/idp/pkg/command/version.go +++ b/services/idp/pkg/command/version.go @@ -6,20 +6,19 @@ import ( "github.com/opencloud-eu/opencloud/pkg/registry" "github.com/opencloud-eu/opencloud/pkg/version" + "github.com/opencloud-eu/opencloud/services/idp/pkg/config" "github.com/olekukonko/tablewriter" "github.com/olekukonko/tablewriter/tw" - "github.com/opencloud-eu/opencloud/services/idp/pkg/config" - "github.com/urfave/cli/v2" + "github.com/spf13/cobra" ) // Version prints the service versions of all running instances. -func Version(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: "version", - Usage: "print the version of this binary and the running service instances", - Category: "info", - Action: func(c *cli.Context) error { +func Version(cfg *config.Config) *cobra.Command { + return &cobra.Command{ + Use: "version", + Short: "print the version of this binary and the running service instances", + RunE: func(cmd *cobra.Command, args []string) error { fmt.Println("Version: " + version.GetString()) fmt.Printf("Compiled: %s\n", version.Compiled()) fmt.Println("") From bd2f697cab14c1c802074e4ed08914cfaa8a141a Mon Sep 17 00:00:00 2001 From: Christian Richter Date: Tue, 2 Dec 2025 15:04:28 +0100 Subject: [PATCH 20/82] migrate invitations from urfave/cli to spf13/cobra Signed-off-by: Christian Richter --- services/invitations/pkg/command/health.go | 16 ++++++++-------- services/invitations/pkg/command/root.go | 18 ++++++++++-------- services/invitations/pkg/command/server.go | 18 +++++++++--------- services/invitations/pkg/command/version.go | 15 +++++++-------- 4 files changed, 34 insertions(+), 33 deletions(-) diff --git a/services/invitations/pkg/command/health.go b/services/invitations/pkg/command/health.go index 3a5c50cadd..05fddacdb9 100644 --- a/services/invitations/pkg/command/health.go +++ b/services/invitations/pkg/command/health.go @@ -8,19 +8,19 @@ import ( "github.com/opencloud-eu/opencloud/services/invitations/pkg/config" "github.com/opencloud-eu/opencloud/services/invitations/pkg/config/parser" "github.com/opencloud-eu/opencloud/services/invitations/pkg/logging" - "github.com/urfave/cli/v2" + + "github.com/spf13/cobra" ) // Health is the entrypoint for the health command. -func Health(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: "health", - Usage: "check health status", - Category: "info", - Before: func(c *cli.Context) error { +func Health(cfg *config.Config) *cobra.Command { + return &cobra.Command{ + Use: "health", + Short: "check health status", + PreRunE: func(cmd *cobra.Command, args []string) error { return configlog.ReturnError(parser.ParseConfig(cfg)) }, - Action: func(c *cli.Context) error { + RunE: func(cmd *cobra.Command, args []string) error { logger := logging.Configure(cfg.Service.Name, cfg.Log) resp, err := http.Get( diff --git a/services/invitations/pkg/command/root.go b/services/invitations/pkg/command/root.go index f9df7c9e56..3923756d2e 100644 --- a/services/invitations/pkg/command/root.go +++ b/services/invitations/pkg/command/root.go @@ -5,12 +5,13 @@ import ( "github.com/opencloud-eu/opencloud/pkg/clihelper" "github.com/opencloud-eu/opencloud/services/invitations/pkg/config" - "github.com/urfave/cli/v2" + + "github.com/spf13/cobra" ) // GetCommands provides all commands for this service -func GetCommands(cfg *config.Config) cli.Commands { - return []*cli.Command{ +func GetCommands(cfg *config.Config) []*cobra.Command { + return []*cobra.Command{ // start this service Server(cfg), @@ -24,11 +25,12 @@ func GetCommands(cfg *config.Config) cli.Commands { // Execute is the entry point for the opencloud invitations command. func Execute(cfg *config.Config) error { - app := clihelper.DefaultApp(&cli.App{ - Name: "invitations", - Usage: "Serve invitations API for OpenCloud", - Commands: GetCommands(cfg), + app := clihelper.DefaultAppCobra(&cobra.Command{ + Use: "invitations", + Short: "Serve invitations API for OpenCloud", }) + app.AddCommand(GetCommands(cfg)...) + app.SetArgs(os.Args[1:]) - return app.RunContext(cfg.Context, os.Args) + return app.ExecuteContext(cfg.Context) } diff --git a/services/invitations/pkg/command/server.go b/services/invitations/pkg/command/server.go index 5b0a6a739a..be6644c55d 100644 --- a/services/invitations/pkg/command/server.go +++ b/services/invitations/pkg/command/server.go @@ -16,21 +16,21 @@ import ( "github.com/opencloud-eu/opencloud/services/invitations/pkg/server/debug" "github.com/opencloud-eu/opencloud/services/invitations/pkg/server/http" "github.com/opencloud-eu/opencloud/services/invitations/pkg/service/v0" - "github.com/urfave/cli/v2" + + "github.com/spf13/cobra" ) // Server is the entrypoint for the server command. -func Server(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: "server", - Usage: fmt.Sprintf("start the %s service without runtime (unsupervised mode)", cfg.Service.Name), - Category: "server", - Before: func(c *cli.Context) error { +func Server(cfg *config.Config) *cobra.Command { + return &cobra.Command{ + Use: "server", + Short: fmt.Sprintf("start the %s service without runtime (unsupervised mode)", cfg.Service.Name), + PreRunE: func(cmd *cobra.Command, args []string) error { return configlog.ReturnFatal(parser.ParseConfig(cfg)) }, - Action: func(c *cli.Context) error { + RunE: func(cmd *cobra.Command, args []string) error { logger := logging.Configure(cfg.Service.Name, cfg.Log) - traceProvider, err := tracing.GetTraceProvider(c.Context, cfg.Commons.TracesExporter, cfg.Service.Name) + traceProvider, err := tracing.GetTraceProvider(cmd.Context(), cfg.Commons.TracesExporter, cfg.Service.Name) if err != nil { return err } diff --git a/services/invitations/pkg/command/version.go b/services/invitations/pkg/command/version.go index ffbfbce200..83361292ef 100644 --- a/services/invitations/pkg/command/version.go +++ b/services/invitations/pkg/command/version.go @@ -6,20 +6,19 @@ import ( "github.com/opencloud-eu/opencloud/pkg/registry" "github.com/opencloud-eu/opencloud/pkg/version" + "github.com/opencloud-eu/opencloud/services/invitations/pkg/config" "github.com/olekukonko/tablewriter" "github.com/olekukonko/tablewriter/tw" - "github.com/opencloud-eu/opencloud/services/invitations/pkg/config" - "github.com/urfave/cli/v2" + "github.com/spf13/cobra" ) // Version prints the service versions of all running instances. -func Version(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: "version", - Usage: "print the version of this binary and the running service instances", - Category: "info", - Action: func(c *cli.Context) error { +func Version(cfg *config.Config) *cobra.Command { + return &cobra.Command{ + Use: "version", + Short: "print the version of this binary and the running service instances", + RunE: func(cmd *cobra.Command, args []string) error { fmt.Println("Version: " + version.GetString()) fmt.Printf("Compiled: %s\n", version.Compiled()) fmt.Println("") From 2b411eb69c9b1273d13fde8f9206bd82f232e53d Mon Sep 17 00:00:00 2001 From: Christian Richter Date: Tue, 2 Dec 2025 15:08:29 +0100 Subject: [PATCH 21/82] migrate nats from urfave/cli to spf13/cobra Signed-off-by: Christian Richter --- services/nats/pkg/command/health.go | 13 +++++++------ services/nats/pkg/command/root.go | 18 ++++++++++-------- services/nats/pkg/command/server.go | 17 ++++++++--------- services/nats/pkg/command/version.go | 14 +++++++------- 4 files changed, 32 insertions(+), 30 deletions(-) diff --git a/services/nats/pkg/command/health.go b/services/nats/pkg/command/health.go index 28e5e1e3c4..42ec1ad842 100644 --- a/services/nats/pkg/command/health.go +++ b/services/nats/pkg/command/health.go @@ -2,15 +2,16 @@ package command import ( "github.com/opencloud-eu/opencloud/services/nats/pkg/config" - "github.com/urfave/cli/v2" + + "github.com/spf13/cobra" ) // Health is the entrypoint for the health command. -func Health(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: "health", - Usage: "Check health status", - Action: func(c *cli.Context) error { +func Health(cfg *config.Config) *cobra.Command { + return &cobra.Command{ + Use: "health", + Short: "Check health status", + RunE: func(cmd *cobra.Command, args []string) error { // Not implemented return nil }, diff --git a/services/nats/pkg/command/root.go b/services/nats/pkg/command/root.go index 2a03708e1e..10951422b5 100644 --- a/services/nats/pkg/command/root.go +++ b/services/nats/pkg/command/root.go @@ -5,12 +5,13 @@ import ( "github.com/opencloud-eu/opencloud/pkg/clihelper" "github.com/opencloud-eu/opencloud/services/nats/pkg/config" - "github.com/urfave/cli/v2" + + "github.com/spf13/cobra" ) // GetCommands provides all commands for this service -func GetCommands(cfg *config.Config) cli.Commands { - return []*cli.Command{ +func GetCommands(cfg *config.Config) []*cobra.Command { + return []*cobra.Command{ // start this service Server(cfg), @@ -24,11 +25,12 @@ func GetCommands(cfg *config.Config) cli.Commands { // Execute is the entry point for the nats command. func Execute(cfg *config.Config) error { - app := clihelper.DefaultApp(&cli.App{ - Name: "nats", - Usage: "starts nats server", - Commands: GetCommands(cfg), + app := clihelper.DefaultAppCobra(&cobra.Command{ + Use: "nats", + Short: "starts nats server", }) + app.AddCommand(GetCommands(cfg)...) + app.SetArgs(os.Args[1:]) - return app.RunContext(cfg.Context, os.Args) + return app.ExecuteContext(cfg.Context) } diff --git a/services/nats/pkg/command/server.go b/services/nats/pkg/command/server.go index b47a93e5b7..3bf9169389 100644 --- a/services/nats/pkg/command/server.go +++ b/services/nats/pkg/command/server.go @@ -6,8 +6,6 @@ import ( "fmt" "os/signal" - "github.com/urfave/cli/v2" - "github.com/opencloud-eu/opencloud/pkg/config/configlog" pkgcrypto "github.com/opencloud-eu/opencloud/pkg/crypto" "github.com/opencloud-eu/opencloud/pkg/runner" @@ -16,18 +14,19 @@ import ( "github.com/opencloud-eu/opencloud/services/nats/pkg/logging" "github.com/opencloud-eu/opencloud/services/nats/pkg/server/debug" "github.com/opencloud-eu/opencloud/services/nats/pkg/server/nats" + + "github.com/spf13/cobra" ) // Server is the entrypoint for the server command. -func Server(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: "server", - Usage: fmt.Sprintf("start the %s service without runtime (unsupervised mode)", cfg.Service.Name), - Category: "server", - Before: func(c *cli.Context) error { +func Server(cfg *config.Config) *cobra.Command { + return &cobra.Command{ + Use: "server", + Short: fmt.Sprintf("start the %s service without runtime (unsupervised mode)", cfg.Service.Name), + PreRunE: func(cmd *cobra.Command, args []string) error { return configlog.ReturnFatal(parser.ParseConfig(cfg)) }, - Action: func(c *cli.Context) error { + RunE: func(cmd *cobra.Command, args []string) error { logger := logging.Configure(cfg.Service.Name, cfg.Log) var cancel context.CancelFunc diff --git a/services/nats/pkg/command/version.go b/services/nats/pkg/command/version.go index b7db8b15a7..2146d1815b 100644 --- a/services/nats/pkg/command/version.go +++ b/services/nats/pkg/command/version.go @@ -2,16 +2,16 @@ package command import ( "github.com/opencloud-eu/opencloud/services/nats/pkg/config" - "github.com/urfave/cli/v2" + + "github.com/spf13/cobra" ) // Version prints the service versions of all running instances. -func Version(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: "version", - Usage: "print the version of this binary and the running service instances", - Category: "info", - Action: func(c *cli.Context) error { +func Version(cfg *config.Config) *cobra.Command { + return &cobra.Command{ + Use: "version", + Short: "print the version of this binary and the running service instances", + RunE: func(cmd *cobra.Command, args []string) error { // not implemented return nil }, From 5080aeaeb9fe08582b04d4b42a5d904a92e9e725 Mon Sep 17 00:00:00 2001 From: Christian Richter Date: Tue, 2 Dec 2025 15:15:04 +0100 Subject: [PATCH 22/82] migrate notifications from urfave/cli to spf13/cobra Signed-off-by: Christian Richter --- services/notifications/pkg/command/health.go | 13 ++--- services/notifications/pkg/command/root.go | 18 +++---- .../notifications/pkg/command/send_email.go | 48 ++++++++++--------- services/notifications/pkg/command/server.go | 33 ++++++------- services/notifications/pkg/command/version.go | 14 +++--- 5 files changed, 65 insertions(+), 61 deletions(-) diff --git a/services/notifications/pkg/command/health.go b/services/notifications/pkg/command/health.go index 42a326e840..b2999ca1d5 100644 --- a/services/notifications/pkg/command/health.go +++ b/services/notifications/pkg/command/health.go @@ -2,15 +2,16 @@ package command import ( "github.com/opencloud-eu/opencloud/services/notifications/pkg/config" - "github.com/urfave/cli/v2" + + "github.com/spf13/cobra" ) // Health is the entrypoint for the health command. -func Health(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: "health", - Usage: "Check health status", - Action: func(c *cli.Context) error { +func Health(cfg *config.Config) *cobra.Command { + return &cobra.Command{ + Use: "health", + Short: "Check health status", + RunE: func(cmd *cobra.Command, args []string) error { // Not implemented return nil }, diff --git a/services/notifications/pkg/command/root.go b/services/notifications/pkg/command/root.go index 977796e618..dc632c6b59 100644 --- a/services/notifications/pkg/command/root.go +++ b/services/notifications/pkg/command/root.go @@ -5,12 +5,13 @@ import ( "github.com/opencloud-eu/opencloud/pkg/clihelper" "github.com/opencloud-eu/opencloud/services/notifications/pkg/config" - "github.com/urfave/cli/v2" + + "github.com/spf13/cobra" ) // GetCommands provides all commands for this service -func GetCommands(cfg *config.Config) cli.Commands { - return []*cli.Command{ +func GetCommands(cfg *config.Config) []*cobra.Command { + return []*cobra.Command{ // start this service Server(cfg), @@ -25,11 +26,12 @@ func GetCommands(cfg *config.Config) cli.Commands { // Execute is the entry point for the notifications command. func Execute(cfg *config.Config) error { - app := clihelper.DefaultApp(&cli.App{ - Name: "notifications", - Usage: "starts notifications service", - Commands: GetCommands(cfg), + app := clihelper.DefaultAppCobra(&cobra.Command{ + Use: "notifications", + Short: "starts notifications service", }) + app.AddCommand(GetCommands(cfg)...) + app.SetArgs(os.Args[1:]) - return app.RunContext(cfg.Context, os.Args) + return app.ExecuteContext(cfg.Context) } diff --git a/services/notifications/pkg/command/send_email.go b/services/notifications/pkg/command/send_email.go index c2582c3a08..a496c5ad7a 100644 --- a/services/notifications/pkg/command/send_email.go +++ b/services/notifications/pkg/command/send_email.go @@ -5,30 +5,19 @@ import ( "github.com/opencloud-eu/opencloud/services/notifications/pkg/config" "github.com/opencloud-eu/reva/v2/pkg/events" "github.com/opencloud-eu/reva/v2/pkg/events/stream" + "github.com/pkg/errors" - "github.com/urfave/cli/v2" + "github.com/spf13/cobra" ) // SendEmail triggers the sending of grouped email notifications for daily or weekly emails. -func SendEmail(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: "send-email", - Usage: "Send grouped email notifications with daily or weekly interval. Specify at least one of the flags '--daily' or '--weekly'.", - Flags: []cli.Flag{ - &cli.BoolFlag{ - Name: "daily", - Aliases: []string{"d"}, - Usage: "Sends grouped daily email notifications.", - }, - &cli.BoolFlag{ - Name: "weekly", - Aliases: []string{"w"}, - Usage: "Sends grouped weekly email notifications.", - }, - }, - Action: func(c *cli.Context) error { - daily := c.Bool("daily") - weekly := c.Bool("weekly") +func SendEmail(cfg *config.Config) *cobra.Command { + sendEmailCmd := &cobra.Command{ + Use: "send-email", + Short: "Send grouped email notifications with daily or weekly interval. Specify at least one of the flags '--daily' or '--weekly'.", + RunE: func(cmd *cobra.Command, args []string) error { + daily := cmd.Flag("daily").Changed + weekly := cmd.Flag("weekly").Changed if !daily && !weekly { return errors.New("at least one of '--daily' or '--weekly' must be set") } @@ -38,7 +27,7 @@ func SendEmail(cfg *config.Config) *cli.Command { return err } if daily { - err = events.Publish(c.Context, s, events.SendEmailsEvent{ + err = events.Publish(cmd.Context(), s, events.SendEmailsEvent{ Interval: "daily", }) if err != nil { @@ -46,7 +35,7 @@ func SendEmail(cfg *config.Config) *cli.Command { } } if weekly { - err = events.Publish(c.Context, s, events.SendEmailsEvent{ + err = events.Publish(cmd.Context(), s, events.SendEmailsEvent{ Interval: "weekly", }) if err != nil { @@ -56,4 +45,19 @@ func SendEmail(cfg *config.Config) *cli.Command { return nil }, } + + sendEmailCmd.Flags().BoolP( + "daily", + "d", + false, + "Sends grouped daily email notifications.", + ) + + sendEmailCmd.Flags().BoolP( + "weekly", + "w", + false, + "Sends grouped weekly email notifications.", + ) + return sendEmailCmd } diff --git a/services/notifications/pkg/command/server.go b/services/notifications/pkg/command/server.go index 91ab0863ff..7c2896abed 100644 --- a/services/notifications/pkg/command/server.go +++ b/services/notifications/pkg/command/server.go @@ -6,16 +6,6 @@ import ( "os/signal" "reflect" - ehsvc "github.com/opencloud-eu/opencloud/protogen/gen/opencloud/services/eventhistory/v0" - "github.com/opencloud-eu/reva/v2/pkg/store" - microstore "go-micro.dev/v4/store" - - "github.com/urfave/cli/v2" - - "github.com/opencloud-eu/reva/v2/pkg/events" - "github.com/opencloud-eu/reva/v2/pkg/events/stream" - "github.com/opencloud-eu/reva/v2/pkg/rgrpc/todo/pool" - "github.com/opencloud-eu/opencloud/pkg/config/configlog" "github.com/opencloud-eu/opencloud/pkg/generators" "github.com/opencloud-eu/opencloud/pkg/registry" @@ -29,21 +19,28 @@ import ( "github.com/opencloud-eu/opencloud/services/notifications/pkg/logging" "github.com/opencloud-eu/opencloud/services/notifications/pkg/server/debug" "github.com/opencloud-eu/opencloud/services/notifications/pkg/service" + "github.com/opencloud-eu/reva/v2/pkg/events" + "github.com/opencloud-eu/reva/v2/pkg/events/stream" + "github.com/opencloud-eu/reva/v2/pkg/rgrpc/todo/pool" + + ehsvc "github.com/opencloud-eu/opencloud/protogen/gen/opencloud/services/eventhistory/v0" + "github.com/opencloud-eu/reva/v2/pkg/store" + "github.com/spf13/cobra" + microstore "go-micro.dev/v4/store" ) // Server is the entrypoint for the server command. -func Server(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: "server", - Usage: fmt.Sprintf("start the %s service without runtime (unsupervised mode)", cfg.Service.Name), - Category: "server", - Before: func(c *cli.Context) error { +func Server(cfg *config.Config) *cobra.Command { + return &cobra.Command{ + Use: "server", + Short: fmt.Sprintf("start the %s service without runtime (unsupervised mode)", cfg.Service.Name), + PreRunE: func(cmd *cobra.Command, args []string) error { return configlog.ReturnFatal(parser.ParseConfig(cfg)) }, - Action: func(c *cli.Context) error { + RunE: func(cmd *cobra.Command, args []string) error { logger := logging.Configure(cfg.Service.Name, cfg.Log) - traceProvider, err := tracing.GetTraceProvider(c.Context, cfg.Commons.TracesExporter, cfg.Service.Name) + traceProvider, err := tracing.GetTraceProvider(cmd.Context(), cfg.Commons.TracesExporter, cfg.Service.Name) if err != nil { return err } diff --git a/services/notifications/pkg/command/version.go b/services/notifications/pkg/command/version.go index e02e9136e6..e0595ea1b3 100644 --- a/services/notifications/pkg/command/version.go +++ b/services/notifications/pkg/command/version.go @@ -2,16 +2,16 @@ package command import ( "github.com/opencloud-eu/opencloud/services/notifications/pkg/config" - "github.com/urfave/cli/v2" + + "github.com/spf13/cobra" ) // Version prints the service versions of all running instances. -func Version(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: "version", - Usage: "print the version of this binary and the running service instances", - Category: "info", - Action: func(c *cli.Context) error { +func Version(cfg *config.Config) *cobra.Command { + return &cobra.Command{ + Use: "version", + Short: "print the version of this binary and the running service instances", + RunE: func(cmd *cobra.Command, args []string) error { // not implemented return nil }, From f43909de77d258d5815b121a4ac0adcab82a9e81 Mon Sep 17 00:00:00 2001 From: Christian Richter Date: Tue, 2 Dec 2025 15:18:33 +0100 Subject: [PATCH 23/82] migrate ocdav from urfave/cli to spf13/cobra Signed-off-by: Christian Richter --- services/ocdav/pkg/command/health.go | 16 ++++++++-------- services/ocdav/pkg/command/root.go | 18 ++++++++++-------- services/ocdav/pkg/command/server.go | 18 +++++++++--------- services/ocdav/pkg/command/version.go | 15 +++++++-------- 4 files changed, 34 insertions(+), 33 deletions(-) diff --git a/services/ocdav/pkg/command/health.go b/services/ocdav/pkg/command/health.go index f445109c71..cc26822086 100644 --- a/services/ocdav/pkg/command/health.go +++ b/services/ocdav/pkg/command/health.go @@ -8,19 +8,19 @@ import ( "github.com/opencloud-eu/opencloud/services/ocdav/pkg/config" "github.com/opencloud-eu/opencloud/services/ocdav/pkg/config/parser" "github.com/opencloud-eu/opencloud/services/ocdav/pkg/logging" - "github.com/urfave/cli/v2" + + "github.com/spf13/cobra" ) // Health is the entrypoint for the health command. -func Health(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: "health", - Usage: "check health status", - Category: "info", - Before: func(c *cli.Context) error { +func Health(cfg *config.Config) *cobra.Command { + return &cobra.Command{ + Use: "health", + Short: "check health status", + PreRunE: func(cmd *cobra.Command, args []string) error { return configlog.ReturnError(parser.ParseConfig(cfg)) }, - Action: func(c *cli.Context) error { + RunE: func(cmd *cobra.Command, args []string) error { logger := logging.Configure(cfg.Service.Name, cfg.Log) resp, err := http.Get( diff --git a/services/ocdav/pkg/command/root.go b/services/ocdav/pkg/command/root.go index 402a27d746..50dd6d1060 100644 --- a/services/ocdav/pkg/command/root.go +++ b/services/ocdav/pkg/command/root.go @@ -5,12 +5,13 @@ import ( "github.com/opencloud-eu/opencloud/pkg/clihelper" "github.com/opencloud-eu/opencloud/services/ocdav/pkg/config" - "github.com/urfave/cli/v2" + + "github.com/spf13/cobra" ) // GetCommands provides all commands for this service -func GetCommands(cfg *config.Config) cli.Commands { - return []*cli.Command{ +func GetCommands(cfg *config.Config) []*cobra.Command { + return []*cobra.Command{ // start this service Server(cfg), @@ -24,11 +25,12 @@ func GetCommands(cfg *config.Config) cli.Commands { // Execute is the entry point for the OpenCloud ocdav command. func Execute(cfg *config.Config) error { - app := clihelper.DefaultApp(&cli.App{ - Name: "ocdav", - Usage: "Provide a WebDav API for OpenCloud", - Commands: GetCommands(cfg), + app := clihelper.DefaultAppCobra(&cobra.Command{ + Use: "ocdav", + Short: "Provide a WebDav API for OpenCloud", }) + app.AddCommand(GetCommands(cfg)...) + app.SetArgs(os.Args[1:]) - return app.RunContext(cfg.Context, os.Args) + return app.ExecuteContext(cfg.Context) } diff --git a/services/ocdav/pkg/command/server.go b/services/ocdav/pkg/command/server.go index 300878cadf..5c5531fd0d 100644 --- a/services/ocdav/pkg/command/server.go +++ b/services/ocdav/pkg/command/server.go @@ -18,21 +18,21 @@ import ( "github.com/opencloud-eu/opencloud/services/ocdav/pkg/server/debug" "github.com/opencloud-eu/reva/v2/pkg/micro/ocdav" "github.com/opencloud-eu/reva/v2/pkg/sharedconf" - "github.com/urfave/cli/v2" + + "github.com/spf13/cobra" ) // Server is the entry point for the server command. -func Server(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: "server", - Usage: fmt.Sprintf("start the %s service without runtime (unsupervised mode)", cfg.Service.Name), - Category: "server", - Before: func(c *cli.Context) error { +func Server(cfg *config.Config) *cobra.Command { + return &cobra.Command{ + Use: "server", + Short: fmt.Sprintf("start the %s service without runtime (unsupervised mode)", cfg.Service.Name), + PreRunE: func(cmd *cobra.Command, args []string) error { return configlog.ReturnFatal(parser.ParseConfig(cfg)) }, - Action: func(c *cli.Context) error { + RunE: func(cmd *cobra.Command, args []string) error { logger := logging.Configure(cfg.Service.Name, cfg.Log) - traceProvider, err := tracing.GetTraceProvider(c.Context, cfg.Commons.TracesExporter, cfg.Service.Name) + traceProvider, err := tracing.GetTraceProvider(cmd.Context(), cfg.Commons.TracesExporter, cfg.Service.Name) if err != nil { return err } diff --git a/services/ocdav/pkg/command/version.go b/services/ocdav/pkg/command/version.go index ecc31c43e1..a3848599fd 100644 --- a/services/ocdav/pkg/command/version.go +++ b/services/ocdav/pkg/command/version.go @@ -6,20 +6,19 @@ import ( "github.com/opencloud-eu/opencloud/pkg/registry" "github.com/opencloud-eu/opencloud/pkg/version" + "github.com/opencloud-eu/opencloud/services/ocdav/pkg/config" "github.com/olekukonko/tablewriter" "github.com/olekukonko/tablewriter/tw" - "github.com/opencloud-eu/opencloud/services/ocdav/pkg/config" - "github.com/urfave/cli/v2" + "github.com/spf13/cobra" ) // Version prints the service versions of all running instances. -func Version(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: "version", - Usage: "print the version of this binary and the running service instances", - Category: "info", - Action: func(c *cli.Context) error { +func Version(cfg *config.Config) *cobra.Command { + return &cobra.Command{ + Use: "version", + Short: "print the version of this binary and the running service instances", + RunE: func(cmd *cobra.Command, args []string) error { fmt.Println("Version: " + version.GetString()) fmt.Printf("Compiled: %s\n", version.Compiled()) fmt.Println("") From 680423707cf886f0ead0cdf72c5aed7d993e1dc5 Mon Sep 17 00:00:00 2001 From: Christian Richter Date: Tue, 2 Dec 2025 15:27:48 +0100 Subject: [PATCH 24/82] migrate ocm from urfave/cli to spf13/cobra Signed-off-by: Christian Richter --- services/ocm/pkg/command/health.go | 13 +++++++------ services/ocm/pkg/command/root.go | 18 ++++++++++-------- services/ocm/pkg/command/server.go | 18 +++++++++--------- services/ocm/pkg/command/version.go | 14 +++++++------- 4 files changed, 33 insertions(+), 30 deletions(-) diff --git a/services/ocm/pkg/command/health.go b/services/ocm/pkg/command/health.go index 3e4cb6cd6c..d5f5c5bbb7 100644 --- a/services/ocm/pkg/command/health.go +++ b/services/ocm/pkg/command/health.go @@ -2,15 +2,16 @@ package command import ( "github.com/opencloud-eu/opencloud/services/ocm/pkg/config" - "github.com/urfave/cli/v2" + + "github.com/spf13/cobra" ) // Health is the entrypoint for the health command. -func Health(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: "health", - Usage: "Check health status", - Action: func(c *cli.Context) error { +func Health(cfg *config.Config) *cobra.Command { + return &cobra.Command{ + Use: "health", + Short: "Check health status", + RunE: func(cmd *cobra.Command, args []string) error { // Not implemented return nil }, diff --git a/services/ocm/pkg/command/root.go b/services/ocm/pkg/command/root.go index 9c07cb9550..5aa7b97fff 100644 --- a/services/ocm/pkg/command/root.go +++ b/services/ocm/pkg/command/root.go @@ -5,12 +5,13 @@ import ( "github.com/opencloud-eu/opencloud/pkg/clihelper" "github.com/opencloud-eu/opencloud/services/ocm/pkg/config" - "github.com/urfave/cli/v2" + + "github.com/spf13/cobra" ) // GetCommands provides all commands for this service -func GetCommands(cfg *config.Config) cli.Commands { - return []*cli.Command{ +func GetCommands(cfg *config.Config) []*cobra.Command { + return []*cobra.Command{ // start this service Server(cfg), @@ -24,11 +25,12 @@ func GetCommands(cfg *config.Config) cli.Commands { // Execute is the entry point for the ocm command. func Execute(cfg *config.Config) error { - app := clihelper.DefaultApp(&cli.App{ - Name: "ocm", - Usage: "starts ocm service", - Commands: GetCommands(cfg), + app := clihelper.DefaultAppCobra(&cobra.Command{ + Use: "ocm", + Short: "starts ocm service", }) + app.AddCommand(GetCommands(cfg)...) + app.SetArgs(os.Args[1:]) - return app.RunContext(cfg.Context, os.Args) + return app.ExecuteContext(cfg.Context) } diff --git a/services/ocm/pkg/command/server.go b/services/ocm/pkg/command/server.go index c58d31de00..dbb6d8ab13 100644 --- a/services/ocm/pkg/command/server.go +++ b/services/ocm/pkg/command/server.go @@ -16,21 +16,21 @@ import ( "github.com/opencloud-eu/opencloud/services/ocm/pkg/revaconfig" "github.com/opencloud-eu/opencloud/services/ocm/pkg/server/debug" "github.com/opencloud-eu/reva/v2/cmd/revad/runtime" - "github.com/urfave/cli/v2" + + "github.com/spf13/cobra" ) // Server is the entrypoint for the server command. -func Server(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: "server", - Usage: fmt.Sprintf("start the %s service without runtime (unsupervised mode)", cfg.Service.Name), - Category: "server", - Before: func(c *cli.Context) error { +func Server(cfg *config.Config) *cobra.Command { + return &cobra.Command{ + Use: "server", + Short: fmt.Sprintf("start the %s service without runtime (unsupervised mode)", cfg.Service.Name), + PreRunE: func(cmd *cobra.Command, args []string) error { return configlog.ReturnFatal(parser.ParseConfig(cfg)) }, - Action: func(c *cli.Context) error { + RunE: func(cmd *cobra.Command, args []string) error { logger := logging.Configure(cfg.Service.Name, cfg.Log) - traceProvider, err := tracing.GetTraceProvider(c.Context, cfg.Commons.TracesExporter, cfg.Service.Name) + traceProvider, err := tracing.GetTraceProvider(cmd.Context(), cfg.Commons.TracesExporter, cfg.Service.Name) if err != nil { return err } diff --git a/services/ocm/pkg/command/version.go b/services/ocm/pkg/command/version.go index 943460afc7..4b2d9e4579 100644 --- a/services/ocm/pkg/command/version.go +++ b/services/ocm/pkg/command/version.go @@ -2,16 +2,16 @@ package command import ( "github.com/opencloud-eu/opencloud/services/ocm/pkg/config" - "github.com/urfave/cli/v2" + + "github.com/spf13/cobra" ) // Version prints the service versions of all running instances. -func Version(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: "version", - Usage: "print the version of this binary and the running service instances", - Category: "info", - Action: func(c *cli.Context) error { +func Version(cfg *config.Config) *cobra.Command { + return &cobra.Command{ + Use: "version", + Short: "print the version of this binary and the running service instances", + RunE: func(cmd *cobra.Command, args []string) error { // not implemented return nil }, From 4488958347189530a4f1a2602fa4ff7f08983449 Mon Sep 17 00:00:00 2001 From: Christian Richter Date: Tue, 2 Dec 2025 15:31:14 +0100 Subject: [PATCH 25/82] migrate ocs from urfave/cli to spf13/cobra Signed-off-by: Christian Richter --- services/ocs/pkg/command/health.go | 16 ++++++++-------- services/ocs/pkg/command/root.go | 18 ++++++++++-------- services/ocs/pkg/command/server.go | 18 +++++++++--------- services/ocs/pkg/command/version.go | 15 +++++++-------- 4 files changed, 34 insertions(+), 33 deletions(-) diff --git a/services/ocs/pkg/command/health.go b/services/ocs/pkg/command/health.go index a0ef467e8d..297204d88e 100644 --- a/services/ocs/pkg/command/health.go +++ b/services/ocs/pkg/command/health.go @@ -8,19 +8,19 @@ import ( "github.com/opencloud-eu/opencloud/services/ocs/pkg/config" "github.com/opencloud-eu/opencloud/services/ocs/pkg/config/parser" "github.com/opencloud-eu/opencloud/services/ocs/pkg/logging" - "github.com/urfave/cli/v2" + + "github.com/spf13/cobra" ) // Health is the entrypoint for the health command. -func Health(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: "health", - Usage: "check health status", - Category: "info", - Before: func(c *cli.Context) error { +func Health(cfg *config.Config) *cobra.Command { + return &cobra.Command{ + Use: "health", + Short: "check health status", + PreRunE: func(cmd *cobra.Command, args []string) error { return configlog.ReturnError(parser.ParseConfig(cfg)) }, - Action: func(c *cli.Context) error { + RunE: func(cmd *cobra.Command, args []string) error { logger := logging.Configure(cfg.Service.Name, cfg.Log) resp, err := http.Get( diff --git a/services/ocs/pkg/command/root.go b/services/ocs/pkg/command/root.go index b0465488ae..cd0fc183e6 100644 --- a/services/ocs/pkg/command/root.go +++ b/services/ocs/pkg/command/root.go @@ -5,12 +5,13 @@ import ( "github.com/opencloud-eu/opencloud/pkg/clihelper" "github.com/opencloud-eu/opencloud/services/ocs/pkg/config" - "github.com/urfave/cli/v2" + + "github.com/spf13/cobra" ) // GetCommands provides all commands for this service -func GetCommands(cfg *config.Config) cli.Commands { - return []*cli.Command{ +func GetCommands(cfg *config.Config) []*cobra.Command { + return []*cobra.Command{ // start this service Server(cfg), @@ -24,11 +25,12 @@ func GetCommands(cfg *config.Config) cli.Commands { // Execute is the entry point for the opencloud-ocs command. func Execute(cfg *config.Config) error { - app := clihelper.DefaultApp(&cli.App{ - Name: "ocs", - Usage: "Serve OCS API for OpenCloud", - Commands: GetCommands(cfg), + app := clihelper.DefaultAppCobra(&cobra.Command{ + Use: "ocs", + Short: "Serve OCS API for OpenCloud", }) + app.AddCommand(GetCommands(cfg)...) + app.SetArgs(os.Args[1:]) - return app.RunContext(cfg.Context, os.Args) + return app.ExecuteContext(cfg.Context) } diff --git a/services/ocs/pkg/command/server.go b/services/ocs/pkg/command/server.go index 67927152b3..a28cef5b59 100644 --- a/services/ocs/pkg/command/server.go +++ b/services/ocs/pkg/command/server.go @@ -16,21 +16,21 @@ import ( "github.com/opencloud-eu/opencloud/services/ocs/pkg/metrics" "github.com/opencloud-eu/opencloud/services/ocs/pkg/server/debug" "github.com/opencloud-eu/opencloud/services/ocs/pkg/server/http" - "github.com/urfave/cli/v2" + + "github.com/spf13/cobra" ) // Server is the entrypoint for the server command. -func Server(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: "server", - Usage: fmt.Sprintf("start the %s service without runtime (unsupervised mode)", cfg.Service.Name), - Category: "server", - Before: func(c *cli.Context) error { +func Server(cfg *config.Config) *cobra.Command { + return &cobra.Command{ + Use: "server", + Short: fmt.Sprintf("start the %s service without runtime (unsupervised mode)", cfg.Service.Name), + PreRunE: func(cmd *cobra.Command, args []string) error { return configlog.ReturnFatal(parser.ParseConfig(cfg)) }, - Action: func(c *cli.Context) error { + RunE: func(cmd *cobra.Command, args []string) error { logger := logging.Configure(cfg.Service.Name, cfg.Log) - traceProvider, err := tracing.GetTraceProvider(c.Context, cfg.Commons.TracesExporter, cfg.Service.Name) + traceProvider, err := tracing.GetTraceProvider(cmd.Context(), cfg.Commons.TracesExporter, cfg.Service.Name) if err != nil { return err } diff --git a/services/ocs/pkg/command/version.go b/services/ocs/pkg/command/version.go index 5961aead42..fb81db193f 100644 --- a/services/ocs/pkg/command/version.go +++ b/services/ocs/pkg/command/version.go @@ -6,20 +6,19 @@ import ( "github.com/opencloud-eu/opencloud/pkg/registry" "github.com/opencloud-eu/opencloud/pkg/version" + "github.com/opencloud-eu/opencloud/services/ocs/pkg/config" "github.com/olekukonko/tablewriter" "github.com/olekukonko/tablewriter/tw" - "github.com/opencloud-eu/opencloud/services/ocs/pkg/config" - "github.com/urfave/cli/v2" + "github.com/spf13/cobra" ) // Version prints the service versions of all running instances. -func Version(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: "version", - Usage: "print the version of this binary and the running service instances", - Category: "info", - Action: func(c *cli.Context) error { +func Version(cfg *config.Config) *cobra.Command { + return &cobra.Command{ + Use: "version", + Short: "print the version of this binary and the running service instances", + RunE: func(cmd *cobra.Command, args []string) error { fmt.Println("Version: " + version.GetString()) fmt.Printf("Compiled: %s\n", version.Compiled()) fmt.Println("") From 131178e5d960292802cbc290f3772339505e7ab8 Mon Sep 17 00:00:00 2001 From: Christian Richter Date: Tue, 2 Dec 2025 15:34:58 +0100 Subject: [PATCH 26/82] migrate policies from urfave/cli to spf13/cobra Signed-off-by: Christian Richter --- services/policies/pkg/command/health.go | 19 +++++++++---------- services/policies/pkg/command/root.go | 18 ++++++++++-------- services/policies/pkg/command/server.go | 21 ++++++++++----------- services/policies/pkg/command/version.go | 15 +++++++-------- 4 files changed, 36 insertions(+), 37 deletions(-) diff --git a/services/policies/pkg/command/health.go b/services/policies/pkg/command/health.go index 77c53ce6de..15d18fa3e5 100644 --- a/services/policies/pkg/command/health.go +++ b/services/policies/pkg/command/health.go @@ -4,24 +4,23 @@ import ( "fmt" "net/http" - "github.com/opencloud-eu/opencloud/pkg/log" - "github.com/opencloud-eu/opencloud/pkg/config/configlog" + "github.com/opencloud-eu/opencloud/pkg/log" "github.com/opencloud-eu/opencloud/services/policies/pkg/config" "github.com/opencloud-eu/opencloud/services/policies/pkg/config/parser" - "github.com/urfave/cli/v2" + + "github.com/spf13/cobra" ) // Health is the entrypoint for the health command. -func Health(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: "health", - Usage: "check health status", - Category: "info", - Before: func(c *cli.Context) error { +func Health(cfg *config.Config) *cobra.Command { + return &cobra.Command{ + Use: "health", + Short: "check health status", + PreRunE: func(cmd *cobra.Command, args []string) error { return configlog.ReturnError(parser.ParseConfig(cfg)) }, - Action: func(c *cli.Context) error { + RunE: func(cmd *cobra.Command, args []string) error { logger := log.NewLogger( log.Name(cfg.Service.Name), log.Level(cfg.Log.Level), diff --git a/services/policies/pkg/command/root.go b/services/policies/pkg/command/root.go index 605d9b55a6..3229890546 100644 --- a/services/policies/pkg/command/root.go +++ b/services/policies/pkg/command/root.go @@ -5,12 +5,13 @@ import ( "github.com/opencloud-eu/opencloud/pkg/clihelper" "github.com/opencloud-eu/opencloud/services/policies/pkg/config" - "github.com/urfave/cli/v2" + + "github.com/spf13/cobra" ) // GetCommands provides all commands for this service -func GetCommands(cfg *config.Config) cli.Commands { - return []*cli.Command{ +func GetCommands(cfg *config.Config) []*cobra.Command { + return []*cobra.Command{ Server(cfg), Health(cfg), Version(cfg), @@ -19,11 +20,12 @@ func GetCommands(cfg *config.Config) cli.Commands { // Execute is the entry point for the policies command. func Execute(cfg *config.Config) error { - app := clihelper.DefaultApp(&cli.App{ - Name: "policies", - Usage: "Serve policies for OpenCloud", - Commands: GetCommands(cfg), + app := clihelper.DefaultAppCobra(&cobra.Command{ + Use: "policies", + Short: "Serve policies for OpenCloud", }) + app.AddCommand(GetCommands(cfg)...) + app.SetArgs(os.Args[1:]) - return app.RunContext(cfg.Context, os.Args) + return app.ExecuteContext(cfg.Context) } diff --git a/services/policies/pkg/command/server.go b/services/policies/pkg/command/server.go index 8726bf4297..1d807a2adb 100644 --- a/services/policies/pkg/command/server.go +++ b/services/policies/pkg/command/server.go @@ -5,9 +5,6 @@ import ( "fmt" "os/signal" - "github.com/opencloud-eu/reva/v2/pkg/events/stream" - "github.com/urfave/cli/v2" - "github.com/opencloud-eu/opencloud/pkg/config/configlog" "github.com/opencloud-eu/opencloud/pkg/generators" "github.com/opencloud-eu/opencloud/pkg/log" @@ -22,18 +19,20 @@ import ( "github.com/opencloud-eu/opencloud/services/policies/pkg/server/debug" svcEvent "github.com/opencloud-eu/opencloud/services/policies/pkg/service/event" svcGRPC "github.com/opencloud-eu/opencloud/services/policies/pkg/service/grpc" + "github.com/opencloud-eu/reva/v2/pkg/events/stream" + + "github.com/spf13/cobra" ) // Server is the entrypoint for the server command. -func Server(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: "server", - Usage: fmt.Sprintf("start the %s service without runtime (unsupervised mode)", "authz"), - Category: "server", - Before: func(c *cli.Context) error { +func Server(cfg *config.Config) *cobra.Command { + return &cobra.Command{ + Use: "server", + Short: fmt.Sprintf("start the %s service without runtime (unsupervised mode)", "authz"), + PreRunE: func(cmd *cobra.Command, args []string) error { return configlog.ReturnFatal(parser.ParseConfig(cfg)) }, - Action: func(c *cli.Context) error { + RunE: func(cmd *cobra.Command, args []string) error { var cancel context.CancelFunc if cfg.Context == nil { cfg.Context, cancel = signal.NotifyContext(context.Background(), runner.StopSignals...) @@ -49,7 +48,7 @@ func Server(cfg *config.Config) *cli.Command { log.File(cfg.Log.File), ).SubloggerWithRequestID(ctx) - traceProvider, err := tracing.GetTraceProvider(c.Context, cfg.Commons.TracesExporter, cfg.Service.Name) + traceProvider, err := tracing.GetTraceProvider(cmd.Context(), cfg.Commons.TracesExporter, cfg.Service.Name) if err != nil { return err } diff --git a/services/policies/pkg/command/version.go b/services/policies/pkg/command/version.go index 2886e9ee83..238c5cd1b3 100644 --- a/services/policies/pkg/command/version.go +++ b/services/policies/pkg/command/version.go @@ -6,20 +6,19 @@ import ( "github.com/opencloud-eu/opencloud/pkg/registry" "github.com/opencloud-eu/opencloud/pkg/version" + "github.com/opencloud-eu/opencloud/services/policies/pkg/config" "github.com/olekukonko/tablewriter" "github.com/olekukonko/tablewriter/tw" - "github.com/opencloud-eu/opencloud/services/policies/pkg/config" - "github.com/urfave/cli/v2" + "github.com/spf13/cobra" ) // Version prints the service versions of all running instances. -func Version(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: "version", - Usage: "print the version of this binary and the running service instances", - Category: "info", - Action: func(c *cli.Context) error { +func Version(cfg *config.Config) *cobra.Command { + return &cobra.Command{ + Use: "version", + Short: "print the version of this binary and the running service instances", + RunE: func(cmd *cobra.Command, args []string) error { fmt.Println("Version: " + version.GetString()) fmt.Printf("Compiled: %s\n", version.Compiled()) fmt.Println("") From b76d4fc661e0fa01ffa0cfc1944bfc313341bd8d Mon Sep 17 00:00:00 2001 From: Christian Richter Date: Tue, 2 Dec 2025 15:42:06 +0100 Subject: [PATCH 27/82] migrate postprocessing from urfave/cli to spf13/cobra Signed-off-by: Christian Richter --- services/postprocessing/pkg/command/health.go | 12 ++-- .../pkg/command/postprocessing.go | 61 ++++++++++--------- services/postprocessing/pkg/command/root.go | 18 +++--- services/postprocessing/pkg/command/server.go | 23 ++++--- .../postprocessing/pkg/command/version.go | 13 ++-- 5 files changed, 66 insertions(+), 61 deletions(-) diff --git a/services/postprocessing/pkg/command/health.go b/services/postprocessing/pkg/command/health.go index 674e26bc9e..f2404d7e18 100644 --- a/services/postprocessing/pkg/command/health.go +++ b/services/postprocessing/pkg/command/health.go @@ -2,15 +2,15 @@ package command import ( "github.com/opencloud-eu/opencloud/services/postprocessing/pkg/config" - "github.com/urfave/cli/v2" + "github.com/spf13/cobra" ) // Health is the entrypoint for the health command. -func Health(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: "health", - Usage: "Check health status", - Action: func(c *cli.Context) error { +func Health(cfg *config.Config) *cobra.Command { + return &cobra.Command{ + Use: "health", + Short: "Check health status", + RunE: func(cmd *cobra.Command, args []string) error { // Not implemented return nil }, diff --git a/services/postprocessing/pkg/command/postprocessing.go b/services/postprocessing/pkg/command/postprocessing.go index e8a63ddc4a..e82b14eea8 100644 --- a/services/postprocessing/pkg/command/postprocessing.go +++ b/services/postprocessing/pkg/command/postprocessing.go @@ -10,37 +10,21 @@ import ( "github.com/opencloud-eu/reva/v2/pkg/events" "github.com/opencloud-eu/reva/v2/pkg/events/stream" "github.com/opencloud-eu/reva/v2/pkg/utils" - "github.com/urfave/cli/v2" + + "github.com/spf13/cobra" ) // RestartPostprocessing cli command to restart postprocessing -func RestartPostprocessing(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: "resume", +func RestartPostprocessing(cfg *config.Config) *cobra.Command { + restartPostprocessingCmd := &cobra.Command{ + Use: "resume", Aliases: []string{"restart"}, - Usage: "resume postprocessing for an uploadID", - Flags: []cli.Flag{ - &cli.StringFlag{ - Name: "upload-id", - Aliases: []string{"u"}, - Usage: "the uploadid to resume. Ignored if unset.", - }, - &cli.StringFlag{ - Name: "step", - Aliases: []string{"s"}, - Usage: "resume all uploads in the given postprocessing step. Ignored if upload-id is set.", - Value: "finished", - }, - &cli.BoolFlag{ - Name: "restart", - Aliases: []string{"r"}, - Usage: "restart postprocessing for the given uploadID. Ignores the step flag.", - }, - }, - Before: func(c *cli.Context) error { + Short: "resume postprocessing for an uploadID", + + PreRunE: func(cmd *cobra.Command, args []string) error { return configlog.ReturnFatal(parser.ParseConfig(cfg)) }, - Action: func(c *cli.Context) error { + RunE: func(cmd *cobra.Command, args []string) error { connName := generators.GenerateConnectionName(cfg.Service.Name, generators.NTypeBus) stream, err := stream.NatsFromConfig(connName, false, stream.NatsConfig{ Endpoint: cfg.Postprocessing.Events.Endpoint, @@ -55,14 +39,14 @@ func RestartPostprocessing(cfg *config.Config) *cli.Command { return err } - uid, step := c.String("upload-id"), "" + uid, step := cmd.Flag("upload-id").Value.String(), "" if uid == "" { - step = c.String("step") + step = cmd.Flag("step").Value.String() } var ev events.Unmarshaller switch { - case c.Bool("retrigger"): + case cmd.Flag("restart").Changed: ev = events.RestartPostprocessing{ UploadID: uid, Timestamp: utils.TSNow(), @@ -78,4 +62,25 @@ func RestartPostprocessing(cfg *config.Config) *cli.Command { return events.Publish(context.Background(), stream, ev) }, } + + restartPostprocessingCmd.Flags().StringP( + "upload-id", + "u", + "", + "the uploadid to resume. Ignored if unset.", + ) + restartPostprocessingCmd.Flags().StringP( + "step", + "s", + "finished", + "resume all uploads in the given postprocessing step. Ignored if upload-id is set.", + ) + restartPostprocessingCmd.Flags().BoolP( + "restart", + "r", + false, + "restart postprocessing for the given uploadID. Ignores the step flag.", + ) + + return restartPostprocessingCmd } diff --git a/services/postprocessing/pkg/command/root.go b/services/postprocessing/pkg/command/root.go index 4172de3ff4..d80e21d4a6 100644 --- a/services/postprocessing/pkg/command/root.go +++ b/services/postprocessing/pkg/command/root.go @@ -5,12 +5,13 @@ import ( "github.com/opencloud-eu/opencloud/pkg/clihelper" "github.com/opencloud-eu/opencloud/services/postprocessing/pkg/config" - "github.com/urfave/cli/v2" + + "github.com/spf13/cobra" ) // GetCommands provides all commands for this service -func GetCommands(cfg *config.Config) cli.Commands { - return []*cli.Command{ +func GetCommands(cfg *config.Config) []*cobra.Command { + return []*cobra.Command{ // start this service Server(cfg), @@ -25,11 +26,12 @@ func GetCommands(cfg *config.Config) cli.Commands { // Execute is the entry point for the postprocessing command. func Execute(cfg *config.Config) error { - app := clihelper.DefaultApp(&cli.App{ - Name: "postprocessing", - Usage: "starts postprocessing service", - Commands: GetCommands(cfg), + app := clihelper.DefaultAppCobra(&cobra.Command{ + Use: "postprocessing", + Short: "starts postprocessing service", }) + app.AddCommand(GetCommands(cfg)...) + app.SetArgs(os.Args[1:]) - return app.RunContext(cfg.Context, os.Args) + return app.ExecuteContext(cfg.Context) } diff --git a/services/postprocessing/pkg/command/server.go b/services/postprocessing/pkg/command/server.go index 95df483841..575bc40cf4 100644 --- a/services/postprocessing/pkg/command/server.go +++ b/services/postprocessing/pkg/command/server.go @@ -6,10 +6,6 @@ import ( "os" "os/signal" - "github.com/opencloud-eu/reva/v2/pkg/store" - "github.com/urfave/cli/v2" - microstore "go-micro.dev/v4/store" - "github.com/opencloud-eu/opencloud/pkg/runner" "github.com/opencloud-eu/opencloud/pkg/tracing" "github.com/opencloud-eu/opencloud/services/postprocessing/pkg/config" @@ -17,15 +13,18 @@ import ( "github.com/opencloud-eu/opencloud/services/postprocessing/pkg/logging" "github.com/opencloud-eu/opencloud/services/postprocessing/pkg/server/debug" "github.com/opencloud-eu/opencloud/services/postprocessing/pkg/service" + "github.com/opencloud-eu/reva/v2/pkg/store" + + "github.com/spf13/cobra" + microstore "go-micro.dev/v4/store" ) // Server is the entrypoint for the server command. -func Server(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: "server", - Usage: fmt.Sprintf("start %s service without runtime (unsupervised mode)", cfg.Service.Name), - Category: "server", - Before: func(c *cli.Context) error { +func Server(cfg *config.Config) *cobra.Command { + return &cobra.Command{ + Use: "server", + Short: fmt.Sprintf("start %s service without runtime (unsupervised mode)", cfg.Service.Name), + PreRunE: func(cmd *cobra.Command, args []string) error { err := parser.ParseConfig(cfg) if err != nil { fmt.Printf("%v", err) @@ -33,7 +32,7 @@ func Server(cfg *config.Config) *cli.Command { } return err }, - Action: func(c *cli.Context) error { + RunE: func(cmd *cobra.Command, args []string) error { logger := logging.Configure(cfg.Service.Name, cfg.Log) var cancel context.CancelFunc @@ -43,7 +42,7 @@ func Server(cfg *config.Config) *cli.Command { } ctx := cfg.Context - traceProvider, err := tracing.GetTraceProvider(c.Context, cfg.Commons.TracesExporter, cfg.Service.Name) + traceProvider, err := tracing.GetTraceProvider(cmd.Context(), cfg.Commons.TracesExporter, cfg.Service.Name) if err != nil { return err } diff --git a/services/postprocessing/pkg/command/version.go b/services/postprocessing/pkg/command/version.go index e5a2fa6f54..38d32adeaa 100644 --- a/services/postprocessing/pkg/command/version.go +++ b/services/postprocessing/pkg/command/version.go @@ -2,16 +2,15 @@ package command import ( "github.com/opencloud-eu/opencloud/services/postprocessing/pkg/config" - "github.com/urfave/cli/v2" + "github.com/spf13/cobra" ) // Version prints the service versions of all running instances. -func Version(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: "version", - Usage: "print the version of this binary and the running extension instances", - Category: "info", - Action: func(c *cli.Context) error { +func Version(cfg *config.Config) *cobra.Command { + return &cobra.Command{ + Use: "version", + Short: "print the version of this binary and the running extension instances", + RunE: func(cmd *cobra.Command, args []string) error { // not implemented return nil }, From 882dede6d34d596b7e9078e9dd1d40a8debe65b1 Mon Sep 17 00:00:00 2001 From: Christian Richter Date: Tue, 2 Dec 2025 16:49:37 +0100 Subject: [PATCH 28/82] migrate proxy from urfave/cli to spf13/cobra Signed-off-by: Christian Richter --- services/proxy/pkg/command/health.go | 16 ++++++++-------- services/proxy/pkg/command/root.go | 18 ++++++++++-------- services/proxy/pkg/command/server.go | 20 ++++++++++---------- services/proxy/pkg/command/version.go | 15 +++++++-------- 4 files changed, 35 insertions(+), 34 deletions(-) diff --git a/services/proxy/pkg/command/health.go b/services/proxy/pkg/command/health.go index 28bbe2841c..d3952f653a 100644 --- a/services/proxy/pkg/command/health.go +++ b/services/proxy/pkg/command/health.go @@ -8,19 +8,19 @@ import ( "github.com/opencloud-eu/opencloud/services/proxy/pkg/config" "github.com/opencloud-eu/opencloud/services/proxy/pkg/config/parser" "github.com/opencloud-eu/opencloud/services/proxy/pkg/logging" - "github.com/urfave/cli/v2" + + "github.com/spf13/cobra" ) // Health is the entrypoint for the health command. -func Health(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: "health", - Usage: "check health status", - Category: "info", - Before: func(c *cli.Context) error { +func Health(cfg *config.Config) *cobra.Command { + return &cobra.Command{ + Use: "health", + Short: "check health status", + PreRunE: func(cmd *cobra.Command, args []string) error { return configlog.ReturnError(parser.ParseConfig(cfg)) }, - Action: func(c *cli.Context) error { + RunE: func(cmd *cobra.Command, args []string) error { logger := logging.Configure(cfg.Service.Name, cfg.Log) resp, err := http.Get( diff --git a/services/proxy/pkg/command/root.go b/services/proxy/pkg/command/root.go index 600e147875..9e32ede908 100644 --- a/services/proxy/pkg/command/root.go +++ b/services/proxy/pkg/command/root.go @@ -5,12 +5,13 @@ import ( "github.com/opencloud-eu/opencloud/pkg/clihelper" "github.com/opencloud-eu/opencloud/services/proxy/pkg/config" - "github.com/urfave/cli/v2" + + "github.com/spf13/cobra" ) // GetCommands provides all commands for this service -func GetCommands(cfg *config.Config) cli.Commands { - return []*cli.Command{ +func GetCommands(cfg *config.Config) []*cobra.Command { + return []*cobra.Command{ // start this service Server(cfg), @@ -24,11 +25,12 @@ func GetCommands(cfg *config.Config) cli.Commands { // Execute is the entry point for the opencloud-proxy command. func Execute(cfg *config.Config) error { - app := clihelper.DefaultApp(&cli.App{ - Name: "proxy", - Usage: "proxy for OpenCloud", - Commands: GetCommands(cfg), + app := clihelper.DefaultAppCobra(&cobra.Command{ + Use: "proxy", + Short: "proxy for OpenCloud", }) + app.AddCommand(GetCommands(cfg)...) + app.SetArgs(os.Args[1:]) - return app.RunContext(cfg.Context, os.Args) + return app.ExecuteContext(cfg.Context) } diff --git a/services/proxy/pkg/command/server.go b/services/proxy/pkg/command/server.go index a1f2f842dd..418dfc0e49 100644 --- a/services/proxy/pkg/command/server.go +++ b/services/proxy/pkg/command/server.go @@ -9,7 +9,6 @@ import ( "time" gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1" - chimiddleware "github.com/go-chi/chi/v5/middleware" "github.com/justinas/alice" "github.com/opencloud-eu/opencloud/pkg/config/configlog" "github.com/opencloud-eu/opencloud/pkg/generators" @@ -40,7 +39,9 @@ import ( "github.com/opencloud-eu/reva/v2/pkg/rgrpc/todo/pool" "github.com/opencloud-eu/reva/v2/pkg/signedurl" "github.com/opencloud-eu/reva/v2/pkg/store" - "github.com/urfave/cli/v2" + + chimiddleware "github.com/go-chi/chi/v5/middleware" + "github.com/spf13/cobra" "go-micro.dev/v4/selector" microstore "go-micro.dev/v4/store" "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" @@ -48,15 +49,14 @@ import ( ) // Server is the entrypoint for the server command. -func Server(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: "server", - Usage: fmt.Sprintf("start the %s service without runtime (unsupervised mode)", cfg.Service.Name), - Category: "server", - Before: func(c *cli.Context) error { +func Server(cfg *config.Config) *cobra.Command { + return &cobra.Command{ + Use: "server", + Short: fmt.Sprintf("start the %s service without runtime (unsupervised mode)", cfg.Service.Name), + PreRunE: func(cmd *cobra.Command, args []string) error { return configlog.ReturnFatal(parser.ParseConfig(cfg)) }, - Action: func(c *cli.Context) error { + RunE: func(cmd *cobra.Command, args []string) error { userInfoCache := store.Create( store.Store(cfg.OIDC.UserinfoCache.Store), store.TTL(cfg.OIDC.UserinfoCache.TTL), @@ -77,7 +77,7 @@ func Server(cfg *config.Config) *cli.Command { ) logger := logging.Configure(cfg.Service.Name, cfg.Log) - traceProvider, err := tracing.GetTraceProvider(c.Context, cfg.Commons.TracesExporter, cfg.Service.Name) + traceProvider, err := tracing.GetTraceProvider(cmd.Context(), cfg.Commons.TracesExporter, cfg.Service.Name) if err != nil { return err } diff --git a/services/proxy/pkg/command/version.go b/services/proxy/pkg/command/version.go index 951c96a1f5..39c30932bc 100644 --- a/services/proxy/pkg/command/version.go +++ b/services/proxy/pkg/command/version.go @@ -6,20 +6,19 @@ import ( "github.com/opencloud-eu/opencloud/pkg/registry" "github.com/opencloud-eu/opencloud/pkg/version" + "github.com/opencloud-eu/opencloud/services/proxy/pkg/config" "github.com/olekukonko/tablewriter" "github.com/olekukonko/tablewriter/tw" - "github.com/opencloud-eu/opencloud/services/proxy/pkg/config" - "github.com/urfave/cli/v2" + "github.com/spf13/cobra" ) // Version prints the service versions of all running instances. -func Version(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: "version", - Usage: "Print the version of this binary and the running service instances", - Category: "Version", - Action: func(c *cli.Context) error { +func Version(cfg *config.Config) *cobra.Command { + return &cobra.Command{ + Use: "version", + Short: "Print the version of this binary and the running service instances", + RunE: func(cmd *cobra.Command, args []string) error { fmt.Println("Version: " + version.GetString()) fmt.Printf("Compiled: %s\n", version.Compiled()) fmt.Println("") From e7a57886349cf328d1b6d77b2b3989d0eecfaa98 Mon Sep 17 00:00:00 2001 From: Christian Richter Date: Tue, 2 Dec 2025 16:58:00 +0100 Subject: [PATCH 29/82] migrate search from urfave/cli to spf13/cobra Signed-off-by: Christian Richter --- services/search/pkg/command/health.go | 16 ++++---- services/search/pkg/command/index.go | 51 +++++++++++++------------- services/search/pkg/command/root.go | 20 +++++----- services/search/pkg/command/server.go | 27 +++++++------- services/search/pkg/command/version.go | 15 ++++---- 5 files changed, 65 insertions(+), 64 deletions(-) diff --git a/services/search/pkg/command/health.go b/services/search/pkg/command/health.go index b4c33bff69..d3c6d40706 100644 --- a/services/search/pkg/command/health.go +++ b/services/search/pkg/command/health.go @@ -7,19 +7,19 @@ import ( "github.com/opencloud-eu/opencloud/services/search/pkg/config" "github.com/opencloud-eu/opencloud/services/search/pkg/config/parser" "github.com/opencloud-eu/opencloud/services/search/pkg/logging" - "github.com/urfave/cli/v2" + + "github.com/spf13/cobra" ) // Health is the entrypoint for the health command. -func Health(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: "health", - Usage: "check health status", - Category: "info", - Before: func(c *cli.Context) error { +func Health(cfg *config.Config) *cobra.Command { + return &cobra.Command{ + Use: "health", + Short: "check health status", + PreRunE: func(cmd *cobra.Command, args []string) error { return parser.ParseConfig(cfg) }, - Action: func(c *cli.Context) error { + RunE: func(cmd *cobra.Command, args []string) error { logger := logging.Configure(cfg.Service.Name, cfg.Log) resp, err := http.Get( diff --git a/services/search/pkg/command/index.go b/services/search/pkg/command/index.go index 9ea4a66161..1a9f979d85 100644 --- a/services/search/pkg/command/index.go +++ b/services/search/pkg/command/index.go @@ -6,44 +6,32 @@ import ( "fmt" "time" - "github.com/urfave/cli/v2" - "go-micro.dev/v4/client" - "github.com/opencloud-eu/opencloud/pkg/config/configlog" "github.com/opencloud-eu/opencloud/pkg/service/grpc" "github.com/opencloud-eu/opencloud/pkg/tracing" searchsvc "github.com/opencloud-eu/opencloud/protogen/gen/opencloud/services/search/v0" "github.com/opencloud-eu/opencloud/services/search/pkg/config" "github.com/opencloud-eu/opencloud/services/search/pkg/config/parser" + + "github.com/spf13/cobra" + "go-micro.dev/v4/client" ) // Index is the entrypoint for the server command. -func Index(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: "index", - Usage: "index the files for one one more users", - Category: "index management", - Aliases: []string{"i"}, - Flags: []cli.Flag{ - &cli.StringFlag{ - Name: "space", - Aliases: []string{"s"}, - Usage: "the id of the space to travers and index the files of. This or --all-spaces is required.", - }, - &cli.BoolFlag{ - Name: "all-spaces", - Usage: "index all spaces instead. This or --space is required.", - }, - }, - Before: func(_ *cli.Context) error { +func Index(cfg *config.Config) *cobra.Command { + indexCmd := &cobra.Command{ + Use: "index", + Short: "index the files for one one more users", + Aliases: []string{"i"}, + PreRunE: func(cmd *cobra.Command, args []string) error { return configlog.ReturnFatal(parser.ParseConfig(cfg)) }, - Action: func(ctx *cli.Context) error { - if ctx.String("space") == "" && !ctx.Bool("all-spaces") { + RunE: func(cmd *cobra.Command, args []string) error { + if cmd.Flag("space").Value.String() == "" && !cmd.Flag("all-spaces").Changed { return errors.New("either --space or --all-spaces is required") } - traceProvider, err := tracing.GetTraceProvider(ctx.Context, cfg.Commons.TracesExporter, cfg.Service.Name) + traceProvider, err := tracing.GetTraceProvider(cmd.Context(), cfg.Commons.TracesExporter, cfg.Service.Name) if err != nil { return err } @@ -58,7 +46,7 @@ func Index(cfg *config.Config) *cli.Command { c := searchsvc.NewSearchProviderService("eu.opencloud.api.search", grpcClient) _, err = c.IndexSpace(context.Background(), &searchsvc.IndexSpaceRequest{ - SpaceId: ctx.String("space"), + SpaceId: cmd.Flag("space").Value.String(), }, func(opts *client.CallOptions) { opts.RequestTimeout = 10 * time.Minute }) if err != nil { fmt.Println("failed to index space: " + err.Error()) @@ -67,4 +55,17 @@ func Index(cfg *config.Config) *cli.Command { return nil }, } + indexCmd.Flags().StringP( + "space", + "s", + "", + "the id of the space to travers and index the files of. This or --all-spaces is required.") + + indexCmd.Flags().Bool( + "all-spaces", + false, + "index all spaces instead. This or --space is required.", + ) + + return indexCmd } diff --git a/services/search/pkg/command/root.go b/services/search/pkg/command/root.go index 43b42c4e93..91badc9f93 100644 --- a/services/search/pkg/command/root.go +++ b/services/search/pkg/command/root.go @@ -4,14 +4,14 @@ import ( "os" "github.com/opencloud-eu/opencloud/pkg/clihelper" - "github.com/opencloud-eu/opencloud/services/search/pkg/config" - "github.com/urfave/cli/v2" + + "github.com/spf13/cobra" ) // GetCommands provides all commands for this service -func GetCommands(cfg *config.Config) cli.Commands { - return []*cli.Command{ +func GetCommands(cfg *config.Config) []*cobra.Command { + return []*cobra.Command{ // start this service Server(cfg), @@ -26,10 +26,12 @@ func GetCommands(cfg *config.Config) cli.Commands { // Execute is the entry point for the opencloud-search command. func Execute(cfg *config.Config) error { - app := clihelper.DefaultApp(&cli.App{ - Name: "search", - Usage: "Serve search API for OpenCloud", - Commands: GetCommands(cfg), + app := clihelper.DefaultAppCobra(&cobra.Command{ + Use: "search", + Short: "Serve search API for OpenCloud", }) - return app.RunContext(cfg.Context, os.Args) + app.AddCommand(GetCommands(cfg)...) + app.SetArgs(os.Args[1:]) + + return app.ExecuteContext(cfg.Context) } diff --git a/services/search/pkg/command/server.go b/services/search/pkg/command/server.go index 85c8755227..67f3987457 100644 --- a/services/search/pkg/command/server.go +++ b/services/search/pkg/command/server.go @@ -8,12 +8,6 @@ import ( "os" "os/signal" - "github.com/opencloud-eu/reva/v2/pkg/events/raw" - "github.com/opencloud-eu/reva/v2/pkg/rgrpc/todo/pool" - opensearchgo "github.com/opensearch-project/opensearch-go/v4" - opensearchgoAPI "github.com/opensearch-project/opensearch-go/v4/opensearchapi" - "github.com/urfave/cli/v2" - "github.com/opencloud-eu/opencloud/pkg/config/configlog" "github.com/opencloud-eu/opencloud/pkg/generators" "github.com/opencloud-eu/opencloud/pkg/registry" @@ -33,20 +27,25 @@ import ( "github.com/opencloud-eu/opencloud/services/search/pkg/server/debug" "github.com/opencloud-eu/opencloud/services/search/pkg/server/grpc" svcEvent "github.com/opencloud-eu/opencloud/services/search/pkg/service/event" + + "github.com/opencloud-eu/reva/v2/pkg/events/raw" + "github.com/opencloud-eu/reva/v2/pkg/rgrpc/todo/pool" + opensearchgo "github.com/opensearch-project/opensearch-go/v4" + opensearchgoAPI "github.com/opensearch-project/opensearch-go/v4/opensearchapi" + "github.com/spf13/cobra" ) // Server is the entrypoint for the server command. -func Server(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: "server", - Usage: fmt.Sprintf("start the %s service without runtime (unsupervised mode)", cfg.Service.Name), - Category: "server", - Before: func(c *cli.Context) error { +func Server(cfg *config.Config) *cobra.Command { + return &cobra.Command{ + Use: "server", + Short: fmt.Sprintf("start the %s service without runtime (unsupervised mode)", cfg.Service.Name), + PreRunE: func(cmd *cobra.Command, args []string) error { return configlog.ReturnFatal(parser.ParseConfig(cfg)) }, - Action: func(c *cli.Context) error { + RunE: func(cmd *cobra.Command, args []string) error { logger := logging.Configure(cfg.Service.Name, cfg.Log) - traceProvider, err := tracing.GetTraceProvider(c.Context, cfg.Commons.TracesExporter, cfg.Service.Name) + traceProvider, err := tracing.GetTraceProvider(cmd.Context(), cfg.Commons.TracesExporter, cfg.Service.Name) if err != nil { return err } diff --git a/services/search/pkg/command/version.go b/services/search/pkg/command/version.go index 4ee3da64e7..b409789bac 100644 --- a/services/search/pkg/command/version.go +++ b/services/search/pkg/command/version.go @@ -6,20 +6,19 @@ import ( "github.com/opencloud-eu/opencloud/pkg/registry" "github.com/opencloud-eu/opencloud/pkg/version" + "github.com/opencloud-eu/opencloud/services/search/pkg/config" "github.com/olekukonko/tablewriter" "github.com/olekukonko/tablewriter/tw" - "github.com/opencloud-eu/opencloud/services/search/pkg/config" - "github.com/urfave/cli/v2" + "github.com/spf13/cobra" ) // Version prints the service versions of all running instances. -func Version(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: "version", - Usage: "print the version of this binary and the running service instances", - Category: "info", - Action: func(c *cli.Context) error { +func Version(cfg *config.Config) *cobra.Command { + return &cobra.Command{ + Use: "version", + Short: "print the version of this binary and the running service instances", + RunE: func(cmd *cobra.Command, args []string) error { fmt.Println("Version: " + version.GetString()) fmt.Printf("Compiled: %s\n", version.Compiled()) fmt.Println("") From eebced529a2fd3e22f53e771f24b88980eeeaee9 Mon Sep 17 00:00:00 2001 From: Christian Richter Date: Tue, 2 Dec 2025 17:01:27 +0100 Subject: [PATCH 30/82] migrate settings from urfave/cli to spf13/cobra Signed-off-by: Christian Richter --- services/settings/pkg/command/health.go | 15 ++++++++------- services/settings/pkg/command/root.go | 18 ++++++++++-------- services/settings/pkg/command/server.go | 18 +++++++++--------- services/settings/pkg/command/version.go | 15 +++++++-------- 4 files changed, 34 insertions(+), 32 deletions(-) diff --git a/services/settings/pkg/command/health.go b/services/settings/pkg/command/health.go index 44be10ac54..be648868b2 100644 --- a/services/settings/pkg/command/health.go +++ b/services/settings/pkg/command/health.go @@ -8,18 +8,19 @@ import ( "github.com/opencloud-eu/opencloud/services/settings/pkg/config" "github.com/opencloud-eu/opencloud/services/settings/pkg/config/parser" "github.com/opencloud-eu/opencloud/services/settings/pkg/logging" - "github.com/urfave/cli/v2" + + "github.com/spf13/cobra" ) // Health is the entrypoint for the health command. -func Health(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: "health", - Usage: "Check health status", - Before: func(c *cli.Context) error { +func Health(cfg *config.Config) *cobra.Command { + return &cobra.Command{ + Use: "health", + Short: "Check health status", + PreRunE: func(cmd *cobra.Command, args []string) error { return configlog.ReturnError(parser.ParseConfig(cfg)) }, - Action: func(c *cli.Context) error { + RunE: func(cmd *cobra.Command, args []string) error { logger := logging.Configure(cfg.Service.Name, cfg.Log) resp, err := http.Get( diff --git a/services/settings/pkg/command/root.go b/services/settings/pkg/command/root.go index 80d57bc1a4..2d01abb43b 100644 --- a/services/settings/pkg/command/root.go +++ b/services/settings/pkg/command/root.go @@ -5,12 +5,13 @@ import ( "github.com/opencloud-eu/opencloud/pkg/clihelper" "github.com/opencloud-eu/opencloud/services/settings/pkg/config" - "github.com/urfave/cli/v2" + + "github.com/spf13/cobra" ) // GetCommands provides all commands for this service -func GetCommands(cfg *config.Config) cli.Commands { - return []*cli.Command{ +func GetCommands(cfg *config.Config) []*cobra.Command { + return []*cobra.Command{ // start this service Server(cfg), @@ -24,11 +25,12 @@ func GetCommands(cfg *config.Config) cli.Commands { // Execute is the entry point for the opencloud-settings command. func Execute(cfg *config.Config) error { - app := clihelper.DefaultApp(&cli.App{ - Name: "settings", - Usage: "Provide settings and permissions for OpenCloud", - Commands: GetCommands(cfg), + app := clihelper.DefaultAppCobra(&cobra.Command{ + Use: "settings", + Short: "Provide settings and permissions for OpenCloud", }) + app.AddCommand(GetCommands(cfg)...) + app.SetArgs(os.Args[1:]) - return app.RunContext(cfg.Context, os.Args) + return app.ExecuteContext(cfg.Context) } diff --git a/services/settings/pkg/command/server.go b/services/settings/pkg/command/server.go index f32903c222..dc889f053f 100644 --- a/services/settings/pkg/command/server.go +++ b/services/settings/pkg/command/server.go @@ -18,21 +18,21 @@ import ( "github.com/opencloud-eu/opencloud/services/settings/pkg/server/grpc" "github.com/opencloud-eu/opencloud/services/settings/pkg/server/http" svc "github.com/opencloud-eu/opencloud/services/settings/pkg/service/v0" - "github.com/urfave/cli/v2" + + "github.com/spf13/cobra" ) // Server is the entrypoint for the server command. -func Server(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: "server", - Usage: fmt.Sprintf("start the %s service without runtime (unsupervised mode)", cfg.Service.Name), - Category: "server", - Before: func(c *cli.Context) error { +func Server(cfg *config.Config) *cobra.Command { + return &cobra.Command{ + Use: "server", + Short: fmt.Sprintf("start the %s service without runtime (unsupervised mode)", cfg.Service.Name), + PreRunE: func(cmd *cobra.Command, args []string) error { return configlog.ReturnFatal(parser.ParseConfig(cfg)) }, - Action: func(c *cli.Context) error { + RunE: func(cmd *cobra.Command, args []string) error { logger := logging.Configure(cfg.Service.Name, cfg.Log) - traceProvider, err := tracing.GetTraceProvider(c.Context, cfg.Commons.TracesExporter, cfg.Service.Name) + traceProvider, err := tracing.GetTraceProvider(cmd.Context(), cfg.Commons.TracesExporter, cfg.Service.Name) if err != nil { return err } diff --git a/services/settings/pkg/command/version.go b/services/settings/pkg/command/version.go index 189ed639a4..b4e25dd52f 100644 --- a/services/settings/pkg/command/version.go +++ b/services/settings/pkg/command/version.go @@ -6,20 +6,19 @@ import ( "github.com/opencloud-eu/opencloud/pkg/registry" "github.com/opencloud-eu/opencloud/pkg/version" + "github.com/opencloud-eu/opencloud/services/settings/pkg/config" "github.com/olekukonko/tablewriter" "github.com/olekukonko/tablewriter/tw" - "github.com/opencloud-eu/opencloud/services/settings/pkg/config" - "github.com/urfave/cli/v2" + "github.com/spf13/cobra" ) // Version prints the service versions of all running instances. -func Version(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: "version", - Usage: "print the version of this binary and the running service instances", - Category: "info", - Action: func(c *cli.Context) error { +func Version(cfg *config.Config) *cobra.Command { + return &cobra.Command{ + Use: "version", + Short: "print the version of this binary and the running service instances", + RunE: func(cmd *cobra.Command, args []string) error { fmt.Println("Version: " + version.GetString()) fmt.Printf("Compiled: %s\n", version.Compiled()) fmt.Println("") From 378aba36b2890015e5e4a55592f36792d71c51e5 Mon Sep 17 00:00:00 2001 From: Christian Richter Date: Tue, 2 Dec 2025 17:04:53 +0100 Subject: [PATCH 31/82] migrate sharing from urfave/cli to spf13/cobra Signed-off-by: Christian Richter --- services/sharing/pkg/command/health.go | 16 ++++++++-------- services/sharing/pkg/command/root.go | 18 ++++++++++-------- services/sharing/pkg/command/server.go | 18 +++++++++--------- services/sharing/pkg/command/version.go | 15 +++++++-------- 4 files changed, 34 insertions(+), 33 deletions(-) diff --git a/services/sharing/pkg/command/health.go b/services/sharing/pkg/command/health.go index bd2cc2aa26..3bbcc0cdbd 100644 --- a/services/sharing/pkg/command/health.go +++ b/services/sharing/pkg/command/health.go @@ -8,19 +8,19 @@ import ( "github.com/opencloud-eu/opencloud/services/sharing/pkg/config" "github.com/opencloud-eu/opencloud/services/sharing/pkg/config/parser" "github.com/opencloud-eu/opencloud/services/sharing/pkg/logging" - "github.com/urfave/cli/v2" + + "github.com/spf13/cobra" ) // Health is the entrypoint for the health command. -func Health(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: "health", - Usage: "check health status", - Category: "info", - Before: func(c *cli.Context) error { +func Health(cfg *config.Config) *cobra.Command { + return &cobra.Command{ + Use: "health", + Short: "check health status", + PreRunE: func(cmd *cobra.Command, args []string) error { return configlog.ReturnError(parser.ParseConfig(cfg)) }, - Action: func(c *cli.Context) error { + RunE: func(cmd *cobra.Command, args []string) error { logger := logging.Configure(cfg.Service.Name, cfg.Log) resp, err := http.Get( diff --git a/services/sharing/pkg/command/root.go b/services/sharing/pkg/command/root.go index 0df9e865d3..bf7a143fdc 100644 --- a/services/sharing/pkg/command/root.go +++ b/services/sharing/pkg/command/root.go @@ -5,12 +5,13 @@ import ( "github.com/opencloud-eu/opencloud/pkg/clihelper" "github.com/opencloud-eu/opencloud/services/sharing/pkg/config" - "github.com/urfave/cli/v2" + + "github.com/spf13/cobra" ) // GetCommands provides all commands for this service -func GetCommands(cfg *config.Config) cli.Commands { - return []*cli.Command{ +func GetCommands(cfg *config.Config) []*cobra.Command { + return []*cobra.Command{ // start this service Server(cfg), @@ -24,11 +25,12 @@ func GetCommands(cfg *config.Config) cli.Commands { // Execute is the entry point for the sharing command. func Execute(cfg *config.Config) error { - app := clihelper.DefaultApp(&cli.App{ - Name: "sharing", - Usage: "Provide sharing for OpenCloud", - Commands: GetCommands(cfg), + app := clihelper.DefaultAppCobra(&cobra.Command{ + Use: "sharing", + Short: "Provide sharing for OpenCloud", }) + app.AddCommand(GetCommands(cfg)...) + app.SetArgs(os.Args[1:]) - return app.RunContext(cfg.Context, os.Args) + return app.ExecuteContext(cfg.Context) } diff --git a/services/sharing/pkg/command/server.go b/services/sharing/pkg/command/server.go index 5e5c74081c..14b2b17e56 100644 --- a/services/sharing/pkg/command/server.go +++ b/services/sharing/pkg/command/server.go @@ -18,21 +18,21 @@ import ( "github.com/opencloud-eu/opencloud/services/sharing/pkg/revaconfig" "github.com/opencloud-eu/opencloud/services/sharing/pkg/server/debug" "github.com/opencloud-eu/reva/v2/cmd/revad/runtime" - "github.com/urfave/cli/v2" + + "github.com/spf13/cobra" ) // Server is the entry point for the server command. -func Server(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: "server", - Usage: fmt.Sprintf("start the %s service without runtime (unsupervised mode)", cfg.Service.Name), - Category: "server", - Before: func(c *cli.Context) error { +func Server(cfg *config.Config) *cobra.Command { + return &cobra.Command{ + Use: "server", + Short: fmt.Sprintf("start the %s service without runtime (unsupervised mode)", cfg.Service.Name), + PreRunE: func(cmd *cobra.Command, args []string) error { return configlog.ReturnFatal(parser.ParseConfig(cfg)) }, - Action: func(c *cli.Context) error { + RunE: func(cmd *cobra.Command, args []string) error { logger := logging.Configure(cfg.Service.Name, cfg.Log) - traceProvider, err := tracing.GetTraceProvider(c.Context, cfg.Commons.TracesExporter, cfg.Service.Name) + traceProvider, err := tracing.GetTraceProvider(cmd.Context(), cfg.Commons.TracesExporter, cfg.Service.Name) if err != nil { return err } diff --git a/services/sharing/pkg/command/version.go b/services/sharing/pkg/command/version.go index d45bc5224c..b36c1d2620 100644 --- a/services/sharing/pkg/command/version.go +++ b/services/sharing/pkg/command/version.go @@ -6,20 +6,19 @@ import ( "github.com/opencloud-eu/opencloud/pkg/registry" "github.com/opencloud-eu/opencloud/pkg/version" + "github.com/opencloud-eu/opencloud/services/sharing/pkg/config" "github.com/olekukonko/tablewriter" "github.com/olekukonko/tablewriter/tw" - "github.com/opencloud-eu/opencloud/services/sharing/pkg/config" - "github.com/urfave/cli/v2" + "github.com/spf13/cobra" ) // Version prints the service versions of all running instances. -func Version(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: "version", - Usage: "print the version of this binary and the running service instances", - Category: "info", - Action: func(c *cli.Context) error { +func Version(cfg *config.Config) *cobra.Command { + return &cobra.Command{ + Use: "version", + Short: "print the version of this binary and the running service instances", + RunE: func(cmd *cobra.Command, args []string) error { fmt.Println("Version: " + version.GetString()) fmt.Printf("Compiled: %s\n", version.Compiled()) fmt.Println("") From 1024830d1383d9d0836fb148c91ae76a99a41448 Mon Sep 17 00:00:00 2001 From: Christian Richter Date: Tue, 2 Dec 2025 17:08:27 +0100 Subject: [PATCH 32/82] migrate sse from urfave/cli to spf13/cobra Signed-off-by: Christian Richter --- services/sse/pkg/command/health.go | 20 +++++++++----------- services/sse/pkg/command/root.go | 19 ++++++++++--------- services/sse/pkg/command/server.go | 23 +++++++++++------------ services/sse/pkg/command/version.go | 16 +++++++--------- 4 files changed, 37 insertions(+), 41 deletions(-) diff --git a/services/sse/pkg/command/health.go b/services/sse/pkg/command/health.go index d385f26dbd..0eafc3e406 100644 --- a/services/sse/pkg/command/health.go +++ b/services/sse/pkg/command/health.go @@ -4,25 +4,23 @@ import ( "fmt" "net/http" - "github.com/opencloud-eu/opencloud/pkg/log" - - "github.com/urfave/cli/v2" - "github.com/opencloud-eu/opencloud/pkg/config/configlog" + "github.com/opencloud-eu/opencloud/pkg/log" "github.com/opencloud-eu/opencloud/services/sse/pkg/config" "github.com/opencloud-eu/opencloud/services/sse/pkg/config/parser" + + "github.com/spf13/cobra" ) // Health is the entrypoint for the health command. -func Health(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: "health", - Usage: "check health status", - Category: "info", - Before: func(c *cli.Context) error { +func Health(cfg *config.Config) *cobra.Command { + return &cobra.Command{ + Use: "health", + Short: "check health status", + PreRunE: func(cmd *cobra.Command, args []string) error { return configlog.ReturnError(parser.ParseConfig(cfg)) }, - Action: func(c *cli.Context) error { + RunE: func(cmd *cobra.Command, args []string) error { logger := log.NewLogger( log.Name(cfg.Service.Name), log.Level(cfg.Log.Level), diff --git a/services/sse/pkg/command/root.go b/services/sse/pkg/command/root.go index 85082333e3..82b90791e9 100644 --- a/services/sse/pkg/command/root.go +++ b/services/sse/pkg/command/root.go @@ -3,15 +3,15 @@ package command import ( "os" - "github.com/urfave/cli/v2" - "github.com/opencloud-eu/opencloud/pkg/clihelper" "github.com/opencloud-eu/opencloud/services/sse/pkg/config" + + "github.com/spf13/cobra" ) // GetCommands provides all commands for this service -func GetCommands(cfg *config.Config) cli.Commands { - return []*cli.Command{ +func GetCommands(cfg *config.Config) []*cobra.Command { + return []*cobra.Command{ Server(cfg), Health(cfg), Version(cfg), @@ -20,11 +20,12 @@ func GetCommands(cfg *config.Config) cli.Commands { // Execute is the entry point for the sse command. func Execute(cfg *config.Config) error { - app := clihelper.DefaultApp(&cli.App{ - Name: "sse", - Usage: "Serve sse for OpenCloud", - Commands: GetCommands(cfg), + app := clihelper.DefaultAppCobra(&cobra.Command{ + Use: "sse", + Short: "Serve sse for OpenCloud", }) + app.AddCommand(GetCommands(cfg)...) + app.SetArgs(os.Args[1:]) - return app.RunContext(cfg.Context, os.Args) + return app.ExecuteContext(cfg.Context) } diff --git a/services/sse/pkg/command/server.go b/services/sse/pkg/command/server.go index 60708f29dc..feef04961c 100644 --- a/services/sse/pkg/command/server.go +++ b/services/sse/pkg/command/server.go @@ -5,10 +5,6 @@ import ( "fmt" "os/signal" - "github.com/opencloud-eu/reva/v2/pkg/events" - "github.com/opencloud-eu/reva/v2/pkg/events/stream" - "github.com/urfave/cli/v2" - "github.com/opencloud-eu/opencloud/pkg/config/configlog" "github.com/opencloud-eu/opencloud/pkg/generators" "github.com/opencloud-eu/opencloud/pkg/log" @@ -18,6 +14,10 @@ import ( "github.com/opencloud-eu/opencloud/services/sse/pkg/config/parser" "github.com/opencloud-eu/opencloud/services/sse/pkg/server/debug" "github.com/opencloud-eu/opencloud/services/sse/pkg/server/http" + "github.com/opencloud-eu/reva/v2/pkg/events" + "github.com/opencloud-eu/reva/v2/pkg/events/stream" + + "github.com/spf13/cobra" ) // all events we care about @@ -26,15 +26,14 @@ var _registeredEvents = []events.Unmarshaller{ } // Server is the entrypoint for the server command. -func Server(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: "server", - Usage: fmt.Sprintf("start the %s service without runtime (unsupervised mode)", cfg.Service.Name), - Category: "server", - Before: func(c *cli.Context) error { +func Server(cfg *config.Config) *cobra.Command { + return &cobra.Command{ + Use: "server", + Short: fmt.Sprintf("start the %s service without runtime (unsupervised mode)", cfg.Service.Name), + PreRunE: func(cmd *cobra.Command, args []string) error { return configlog.ReturnFatal(parser.ParseConfig(cfg)) }, - Action: func(c *cli.Context) error { + RunE: func(cmd *cobra.Command, args []string) error { var cancel context.CancelFunc if cfg.Context == nil { cfg.Context, cancel = signal.NotifyContext(context.Background(), runner.StopSignals...) @@ -50,7 +49,7 @@ func Server(cfg *config.Config) *cli.Command { log.File(cfg.Log.File), ) - tracerProvider, err := tracing.GetTraceProvider(c.Context, cfg.Commons.TracesExporter, cfg.Service.Name) + tracerProvider, err := tracing.GetTraceProvider(cmd.Context(), cfg.Commons.TracesExporter, cfg.Service.Name) if err != nil { return err } diff --git a/services/sse/pkg/command/version.go b/services/sse/pkg/command/version.go index c7e0a2d9c8..a144e5029a 100644 --- a/services/sse/pkg/command/version.go +++ b/services/sse/pkg/command/version.go @@ -4,19 +4,17 @@ import ( "fmt" "github.com/opencloud-eu/opencloud/pkg/version" - - "github.com/urfave/cli/v2" - "github.com/opencloud-eu/opencloud/services/sse/pkg/config" + + "github.com/spf13/cobra" ) // Version prints the service versions of all running instances. -func Version(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: "version", - Usage: "print the version of this binary and the running service instances", - Category: "info", - Action: func(c *cli.Context) error { +func Version(cfg *config.Config) *cobra.Command { + return &cobra.Command{ + Use: "version", + Short: "print the version of this binary and the running service instances", + RunE: func(cmd *cobra.Command, args []string) error { fmt.Println("Version: " + version.GetString()) fmt.Printf("Compiled: %s\n", version.Compiled()) fmt.Println("") From 3967e068153bb4934b855612fd364de088060ca8 Mon Sep 17 00:00:00 2001 From: Christian Richter Date: Tue, 2 Dec 2025 17:11:15 +0100 Subject: [PATCH 33/82] migrate storage-publiclink from urfave/cli to spf13/cobra Signed-off-by: Christian Richter --- .../storage-publiclink/pkg/command/health.go | 16 ++++++++-------- .../storage-publiclink/pkg/command/root.go | 18 ++++++++++-------- .../storage-publiclink/pkg/command/server.go | 18 +++++++++--------- .../storage-publiclink/pkg/command/version.go | 15 +++++++-------- 4 files changed, 34 insertions(+), 33 deletions(-) diff --git a/services/storage-publiclink/pkg/command/health.go b/services/storage-publiclink/pkg/command/health.go index f21f1f34a5..9ba9bacbdb 100644 --- a/services/storage-publiclink/pkg/command/health.go +++ b/services/storage-publiclink/pkg/command/health.go @@ -8,19 +8,19 @@ import ( "github.com/opencloud-eu/opencloud/services/storage-publiclink/pkg/config" "github.com/opencloud-eu/opencloud/services/storage-publiclink/pkg/config/parser" "github.com/opencloud-eu/opencloud/services/storage-publiclink/pkg/logging" - "github.com/urfave/cli/v2" + + "github.com/spf13/cobra" ) // Health is the entrypoint for the health command. -func Health(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: "health", - Usage: "check health status", - Category: "info", - Before: func(c *cli.Context) error { +func Health(cfg *config.Config) *cobra.Command { + return &cobra.Command{ + Use: "health", + Short: "check health status", + PreRunE: func(cmd *cobra.Command, args []string) error { return configlog.ReturnError(parser.ParseConfig(cfg)) }, - Action: func(c *cli.Context) error { + RunE: func(cmd *cobra.Command, args []string) error { logger := logging.Configure(cfg.Service.Name, cfg.Log) resp, err := http.Get( diff --git a/services/storage-publiclink/pkg/command/root.go b/services/storage-publiclink/pkg/command/root.go index ff0eca3383..977079f9cd 100644 --- a/services/storage-publiclink/pkg/command/root.go +++ b/services/storage-publiclink/pkg/command/root.go @@ -5,12 +5,13 @@ import ( "github.com/opencloud-eu/opencloud/pkg/clihelper" "github.com/opencloud-eu/opencloud/services/storage-publiclink/pkg/config" - "github.com/urfave/cli/v2" + + "github.com/spf13/cobra" ) // GetCommands provides all commands for this service -func GetCommands(cfg *config.Config) cli.Commands { - return []*cli.Command{ +func GetCommands(cfg *config.Config) []*cobra.Command { + return []*cobra.Command{ // start this service Server(cfg), @@ -24,11 +25,12 @@ func GetCommands(cfg *config.Config) cli.Commands { // Execute is the entry point for the storage-publiclink command. func Execute(cfg *config.Config) error { - app := clihelper.DefaultApp(&cli.App{ - Name: "storage-publiclink", - Usage: "Provide publiclink storage for OpenCloud", - Commands: GetCommands(cfg), + app := clihelper.DefaultAppCobra(&cobra.Command{ + Use: "storage-publiclink", + Short: "Provide publiclink storage for OpenCloud", }) + app.AddCommand(GetCommands(cfg)...) + app.SetArgs(os.Args[1:]) - return app.RunContext(cfg.Context, os.Args) + return app.ExecuteContext(cfg.Context) } diff --git a/services/storage-publiclink/pkg/command/server.go b/services/storage-publiclink/pkg/command/server.go index 45be6bc369..12d417c765 100644 --- a/services/storage-publiclink/pkg/command/server.go +++ b/services/storage-publiclink/pkg/command/server.go @@ -16,21 +16,21 @@ import ( "github.com/opencloud-eu/opencloud/services/storage-publiclink/pkg/revaconfig" "github.com/opencloud-eu/opencloud/services/storage-publiclink/pkg/server/debug" "github.com/opencloud-eu/reva/v2/cmd/revad/runtime" - "github.com/urfave/cli/v2" + + "github.com/spf13/cobra" ) // Server is the entry point for the server command. -func Server(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: "server", - Usage: fmt.Sprintf("start the %s service without runtime (unsupervised mode)", cfg.Service.Name), - Category: "server", - Before: func(c *cli.Context) error { +func Server(cfg *config.Config) *cobra.Command { + return &cobra.Command{ + Use: "server", + Short: fmt.Sprintf("start the %s service without runtime (unsupervised mode)", cfg.Service.Name), + PreRunE: func(cmd *cobra.Command, args []string) error { return configlog.ReturnFatal(parser.ParseConfig(cfg)) }, - Action: func(c *cli.Context) error { + RunE: func(cmd *cobra.Command, args []string) error { logger := logging.Configure(cfg.Service.Name, cfg.Log) - traceProvider, err := tracing.GetTraceProvider(c.Context, cfg.Commons.TracesExporter, cfg.Service.Name) + traceProvider, err := tracing.GetTraceProvider(cmd.Context(), cfg.Commons.TracesExporter, cfg.Service.Name) if err != nil { return err } diff --git a/services/storage-publiclink/pkg/command/version.go b/services/storage-publiclink/pkg/command/version.go index 6692fb0271..aa5fc90c66 100644 --- a/services/storage-publiclink/pkg/command/version.go +++ b/services/storage-publiclink/pkg/command/version.go @@ -6,20 +6,19 @@ import ( "github.com/opencloud-eu/opencloud/pkg/registry" "github.com/opencloud-eu/opencloud/pkg/version" + "github.com/opencloud-eu/opencloud/services/storage-publiclink/pkg/config" "github.com/olekukonko/tablewriter" "github.com/olekukonko/tablewriter/tw" - "github.com/opencloud-eu/opencloud/services/storage-publiclink/pkg/config" - "github.com/urfave/cli/v2" + "github.com/spf13/cobra" ) // Version prints the service versions of all running instances. -func Version(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: "version", - Usage: "print the version of this binary and the running service instances", - Category: "info", - Action: func(c *cli.Context) error { +func Version(cfg *config.Config) *cobra.Command { + return &cobra.Command{ + Use: "version", + Short: "print the version of this binary and the running service instances", + RunE: func(cmd *cobra.Command, args []string) error { fmt.Println("Version: " + version.GetString()) fmt.Printf("Compiled: %s\n", version.Compiled()) fmt.Println("") From 5c9f0ab07a2e6041e6bb0bc6a14b5731640a8d1e Mon Sep 17 00:00:00 2001 From: Christian Richter Date: Tue, 2 Dec 2025 17:14:56 +0100 Subject: [PATCH 34/82] migrate storage-shares from urfave/cli to spf13/cobra Signed-off-by: Christian Richter --- services/storage-shares/pkg/command/health.go | 16 ++++++++-------- services/storage-shares/pkg/command/root.go | 18 ++++++++++-------- services/storage-shares/pkg/command/server.go | 18 +++++++++--------- services/storage-shares/pkg/command/version.go | 15 +++++++-------- 4 files changed, 34 insertions(+), 33 deletions(-) diff --git a/services/storage-shares/pkg/command/health.go b/services/storage-shares/pkg/command/health.go index 7bb131f5f3..cec693d011 100644 --- a/services/storage-shares/pkg/command/health.go +++ b/services/storage-shares/pkg/command/health.go @@ -8,19 +8,19 @@ import ( "github.com/opencloud-eu/opencloud/services/storage-shares/pkg/config" "github.com/opencloud-eu/opencloud/services/storage-shares/pkg/config/parser" "github.com/opencloud-eu/opencloud/services/storage-shares/pkg/logging" - "github.com/urfave/cli/v2" + + "github.com/spf13/cobra" ) // Health is the entrypoint for the health command. -func Health(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: "health", - Usage: "check health status", - Category: "info", - Before: func(c *cli.Context) error { +func Health(cfg *config.Config) *cobra.Command { + return &cobra.Command{ + Use: "health", + Short: "check health status", + PreRunE: func(cmd *cobra.Command, args []string) error { return configlog.ReturnError(parser.ParseConfig(cfg)) }, - Action: func(c *cli.Context) error { + RunE: func(cmd *cobra.Command, args []string) error { logger := logging.Configure(cfg.Service.Name, cfg.Log) resp, err := http.Get( diff --git a/services/storage-shares/pkg/command/root.go b/services/storage-shares/pkg/command/root.go index fca2176a74..6c46190e71 100644 --- a/services/storage-shares/pkg/command/root.go +++ b/services/storage-shares/pkg/command/root.go @@ -5,12 +5,13 @@ import ( "github.com/opencloud-eu/opencloud/pkg/clihelper" "github.com/opencloud-eu/opencloud/services/storage-shares/pkg/config" - "github.com/urfave/cli/v2" + + "github.com/spf13/cobra" ) // GetCommands provides all commands for this service -func GetCommands(cfg *config.Config) cli.Commands { - return []*cli.Command{ +func GetCommands(cfg *config.Config) []*cobra.Command { + return []*cobra.Command{ // start this service Server(cfg), @@ -24,11 +25,12 @@ func GetCommands(cfg *config.Config) cli.Commands { // Execute is the entry point for the storage-shares command. func Execute(cfg *config.Config) error { - app := clihelper.DefaultApp(&cli.App{ - Name: "storage-shares", - Usage: "Provide a virtual storage for shares in OpenCloud", - Commands: GetCommands(cfg), + app := clihelper.DefaultAppCobra(&cobra.Command{ + Use: "storage-shares", + Short: "Provide a virtual storage for shares in OpenCloud", }) + app.AddCommand(GetCommands(cfg)...) + app.SetArgs(os.Args[1:]) - return app.RunContext(cfg.Context, os.Args) + return app.ExecuteContext(cfg.Context) } diff --git a/services/storage-shares/pkg/command/server.go b/services/storage-shares/pkg/command/server.go index 58c90d6711..b53c1f48e3 100644 --- a/services/storage-shares/pkg/command/server.go +++ b/services/storage-shares/pkg/command/server.go @@ -16,21 +16,21 @@ import ( "github.com/opencloud-eu/opencloud/services/storage-shares/pkg/revaconfig" "github.com/opencloud-eu/opencloud/services/storage-shares/pkg/server/debug" "github.com/opencloud-eu/reva/v2/cmd/revad/runtime" - "github.com/urfave/cli/v2" + + "github.com/spf13/cobra" ) // Server is the entry point for the server command. -func Server(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: "server", - Usage: fmt.Sprintf("start the %s service without runtime (unsupervised mode)", cfg.Service.Name), - Category: "server", - Before: func(c *cli.Context) error { +func Server(cfg *config.Config) *cobra.Command { + return &cobra.Command{ + Use: "server", + Short: fmt.Sprintf("start the %s service without runtime (unsupervised mode)", cfg.Service.Name), + PreRunE: func(cmd *cobra.Command, args []string) error { return configlog.ReturnFatal(parser.ParseConfig(cfg)) }, - Action: func(c *cli.Context) error { + RunE: func(cmd *cobra.Command, args []string) error { logger := logging.Configure(cfg.Service.Name, cfg.Log) - traceProvider, err := tracing.GetTraceProvider(c.Context, cfg.Commons.TracesExporter, cfg.Service.Name) + traceProvider, err := tracing.GetTraceProvider(cmd.Context(), cfg.Commons.TracesExporter, cfg.Service.Name) if err != nil { return err } diff --git a/services/storage-shares/pkg/command/version.go b/services/storage-shares/pkg/command/version.go index d6c825e7a2..332fe4d171 100644 --- a/services/storage-shares/pkg/command/version.go +++ b/services/storage-shares/pkg/command/version.go @@ -6,20 +6,19 @@ import ( "github.com/opencloud-eu/opencloud/pkg/registry" "github.com/opencloud-eu/opencloud/pkg/version" + "github.com/opencloud-eu/opencloud/services/storage-shares/pkg/config" "github.com/olekukonko/tablewriter" "github.com/olekukonko/tablewriter/tw" - "github.com/opencloud-eu/opencloud/services/storage-shares/pkg/config" - "github.com/urfave/cli/v2" + "github.com/spf13/cobra" ) // Version prints the service versions of all running instances. -func Version(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: "version", - Usage: "print the version of this binary and the running service instances", - Category: "info", - Action: func(c *cli.Context) error { +func Version(cfg *config.Config) *cobra.Command { + return &cobra.Command{ + Use: "version", + Short: "print the version of this binary and the running service instances", + RunE: func(cmd *cobra.Command, args []string) error { fmt.Println("Version: " + version.GetString()) fmt.Printf("Compiled: %s\n", version.Compiled()) fmt.Println("") From e398ce304eecfc4bf3b6f63335b72134550c930d Mon Sep 17 00:00:00 2001 From: Christian Richter Date: Tue, 2 Dec 2025 17:18:11 +0100 Subject: [PATCH 35/82] migrate storage-system from urfave/cli to spf13/cobra Signed-off-by: Christian Richter --- services/storage-system/pkg/command/health.go | 16 +++++++------- services/storage-system/pkg/command/root.go | 18 +++++++++------- services/storage-system/pkg/command/server.go | 21 +++++++++---------- .../storage-system/pkg/command/version.go | 15 +++++++------ 4 files changed, 35 insertions(+), 35 deletions(-) diff --git a/services/storage-system/pkg/command/health.go b/services/storage-system/pkg/command/health.go index f4b93fe5b9..79d9659984 100644 --- a/services/storage-system/pkg/command/health.go +++ b/services/storage-system/pkg/command/health.go @@ -8,19 +8,19 @@ import ( "github.com/opencloud-eu/opencloud/services/storage-system/pkg/config" "github.com/opencloud-eu/opencloud/services/storage-system/pkg/config/parser" "github.com/opencloud-eu/opencloud/services/storage-system/pkg/logging" - "github.com/urfave/cli/v2" + + "github.com/spf13/cobra" ) // Health is the entrypoint for the health command. -func Health(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: "health", - Usage: "check health status", - Category: "info", - Before: func(c *cli.Context) error { +func Health(cfg *config.Config) *cobra.Command { + return &cobra.Command{ + Use: "health", + Short: "check health status", + PreRunE: func(cmd *cobra.Command, args []string) error { return configlog.ReturnError(parser.ParseConfig(cfg)) }, - Action: func(c *cli.Context) error { + RunE: func(cmd *cobra.Command, args []string) error { logger := logging.Configure(cfg.Service.Name, cfg.Log) resp, err := http.Get( diff --git a/services/storage-system/pkg/command/root.go b/services/storage-system/pkg/command/root.go index 9e69d0caee..f81e980ef4 100644 --- a/services/storage-system/pkg/command/root.go +++ b/services/storage-system/pkg/command/root.go @@ -5,12 +5,13 @@ import ( "github.com/opencloud-eu/opencloud/pkg/clihelper" "github.com/opencloud-eu/opencloud/services/storage-system/pkg/config" - "github.com/urfave/cli/v2" + + "github.com/spf13/cobra" ) // GetCommands provides all commands for this service -func GetCommands(cfg *config.Config) cli.Commands { - return []*cli.Command{ +func GetCommands(cfg *config.Config) []*cobra.Command { + return []*cobra.Command{ // start this service Server(cfg), @@ -24,11 +25,12 @@ func GetCommands(cfg *config.Config) cli.Commands { // Execute is the entry point for the storage-system command. func Execute(cfg *config.Config) error { - app := clihelper.DefaultApp(&cli.App{ - Name: "storage-system", - Usage: "Provide system storage for OpenCloud", - Commands: GetCommands(cfg), + app := clihelper.DefaultAppCobra(&cobra.Command{ + Use: "storage-system", + Short: "Provide system storage for OpenCloud", }) + app.AddCommand(GetCommands(cfg)...) + app.SetArgs(os.Args[1:]) - return app.RunContext(cfg.Context, os.Args) + return app.ExecuteContext(cfg.Context) } diff --git a/services/storage-system/pkg/command/server.go b/services/storage-system/pkg/command/server.go index b3f0323c44..b69315e3cd 100644 --- a/services/storage-system/pkg/command/server.go +++ b/services/storage-system/pkg/command/server.go @@ -5,9 +5,6 @@ import ( "fmt" "os/signal" - "github.com/opencloud-eu/reva/v2/cmd/revad/runtime" - "github.com/urfave/cli/v2" - "github.com/opencloud-eu/opencloud/pkg/config/configlog" "github.com/opencloud-eu/opencloud/pkg/registry" "github.com/opencloud-eu/opencloud/pkg/runner" @@ -18,20 +15,22 @@ import ( "github.com/opencloud-eu/opencloud/services/storage-system/pkg/logging" "github.com/opencloud-eu/opencloud/services/storage-system/pkg/revaconfig" "github.com/opencloud-eu/opencloud/services/storage-system/pkg/server/debug" + "github.com/opencloud-eu/reva/v2/cmd/revad/runtime" + + "github.com/spf13/cobra" ) // Server is the entry point for the server command. -func Server(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: "server", - Usage: fmt.Sprintf("start the %s service without runtime (unsupervised mode)", cfg.Service.Name), - Category: "server", - Before: func(c *cli.Context) error { +func Server(cfg *config.Config) *cobra.Command { + return &cobra.Command{ + Use: "server", + Short: fmt.Sprintf("start the %s service without runtime (unsupervised mode)", cfg.Service.Name), + PreRunE: func(cmd *cobra.Command, args []string) error { return configlog.ReturnFatal(parser.ParseConfig(cfg)) }, - Action: func(c *cli.Context) error { + RunE: func(cmd *cobra.Command, args []string) error { logger := logging.Configure(cfg.Service.Name, cfg.Log) - traceProvider, err := tracing.GetTraceProvider(c.Context, cfg.Commons.TracesExporter, cfg.Service.Name) + traceProvider, err := tracing.GetTraceProvider(cmd.Context(), cfg.Commons.TracesExporter, cfg.Service.Name) if err != nil { return err } diff --git a/services/storage-system/pkg/command/version.go b/services/storage-system/pkg/command/version.go index 025818f1ef..e55eb0f54e 100644 --- a/services/storage-system/pkg/command/version.go +++ b/services/storage-system/pkg/command/version.go @@ -6,20 +6,19 @@ import ( "github.com/opencloud-eu/opencloud/pkg/registry" "github.com/opencloud-eu/opencloud/pkg/version" + "github.com/opencloud-eu/opencloud/services/storage-system/pkg/config" "github.com/olekukonko/tablewriter" "github.com/olekukonko/tablewriter/tw" - "github.com/opencloud-eu/opencloud/services/storage-system/pkg/config" - "github.com/urfave/cli/v2" + "github.com/spf13/cobra" ) // Version prints the service versions of all running instances. -func Version(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: "version", - Usage: "print the version of this binary and the running service instances", - Category: "info", - Action: func(c *cli.Context) error { +func Version(cfg *config.Config) *cobra.Command { + return &cobra.Command{ + Use: "version", + Short: "print the version of this binary and the running service instances", + RunE: func(cmd *cobra.Command, args []string) error { fmt.Println("Version: " + version.GetString()) fmt.Printf("Compiled: %s\n", version.Compiled()) fmt.Println("") From 5869f8c19c24ec6b41234f1fce7401d79feb16a1 Mon Sep 17 00:00:00 2001 From: Christian Richter Date: Tue, 2 Dec 2025 18:17:19 +0100 Subject: [PATCH 36/82] migrate storage-users from urfave/cli to spf13/cobra Signed-off-by: Christian Richter --- services/storage-users/pkg/command/health.go | 16 +- services/storage-users/pkg/command/root.go | 18 +- services/storage-users/pkg/command/server.go | 18 +- .../storage-users/pkg/command/trash_bin.go | 243 +++++++++--------- services/storage-users/pkg/command/uploads.go | 117 ++++----- services/storage-users/pkg/command/version.go | 15 +- 6 files changed, 200 insertions(+), 227 deletions(-) diff --git a/services/storage-users/pkg/command/health.go b/services/storage-users/pkg/command/health.go index 0cafe4ffe5..802cec278b 100644 --- a/services/storage-users/pkg/command/health.go +++ b/services/storage-users/pkg/command/health.go @@ -8,19 +8,19 @@ import ( "github.com/opencloud-eu/opencloud/services/storage-users/pkg/config" "github.com/opencloud-eu/opencloud/services/storage-users/pkg/config/parser" "github.com/opencloud-eu/opencloud/services/storage-users/pkg/logging" - "github.com/urfave/cli/v2" + + "github.com/spf13/cobra" ) // Health is the entrypoint for the health command. -func Health(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: "health", - Usage: "check health status", - Category: "info", - Before: func(c *cli.Context) error { +func Health(cfg *config.Config) *cobra.Command { + return &cobra.Command{ + Use: "health", + Short: "check health status", + PreRunE: func(cmd *cobra.Command, args []string) error { return configlog.ReturnError(parser.ParseConfig(cfg)) }, - Action: func(c *cli.Context) error { + RunE: func(cmd *cobra.Command, args []string) error { logger := logging.Configure(cfg.Service.Name, cfg.Log) resp, err := http.Get( diff --git a/services/storage-users/pkg/command/root.go b/services/storage-users/pkg/command/root.go index c49a510a76..1722ec679b 100644 --- a/services/storage-users/pkg/command/root.go +++ b/services/storage-users/pkg/command/root.go @@ -5,12 +5,13 @@ import ( "github.com/opencloud-eu/opencloud/pkg/clihelper" "github.com/opencloud-eu/opencloud/services/storage-users/pkg/config" - "github.com/urfave/cli/v2" + + "github.com/spf13/cobra" ) // GetCommands provides all commands for this service -func GetCommands(cfg *config.Config) cli.Commands { - return []*cli.Command{ +func GetCommands(cfg *config.Config) []*cobra.Command { + return []*cobra.Command{ // start this service Server(cfg), @@ -26,11 +27,12 @@ func GetCommands(cfg *config.Config) cli.Commands { // Execute is the entry point for the opencloud-storage-users command. func Execute(cfg *config.Config) error { - app := clihelper.DefaultApp(&cli.App{ - Name: "storage-users", - Usage: "Provide storage for users and projects in OpenCloud", - Commands: GetCommands(cfg), + app := clihelper.DefaultAppCobra(&cobra.Command{ + Use: "storage-users", + Short: "Provide storage for users and projects in OpenCloud", }) + app.AddCommand(GetCommands(cfg)...) + app.SetArgs(os.Args[1:]) - return app.RunContext(cfg.Context, os.Args) + return app.ExecuteContext(cfg.Context) } diff --git a/services/storage-users/pkg/command/server.go b/services/storage-users/pkg/command/server.go index 95d62944d2..1b7bb72bef 100644 --- a/services/storage-users/pkg/command/server.go +++ b/services/storage-users/pkg/command/server.go @@ -18,21 +18,21 @@ import ( "github.com/opencloud-eu/opencloud/services/storage-users/pkg/server/debug" "github.com/opencloud-eu/reva/v2/cmd/revad/runtime" "github.com/opencloud-eu/reva/v2/pkg/rgrpc/todo/pool" - "github.com/urfave/cli/v2" + + "github.com/spf13/cobra" ) // Server is the entry point for the server command. -func Server(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: "server", - Usage: fmt.Sprintf("start the %s service without runtime (unsupervised mode)", cfg.Service.Name), - Category: "server", - Before: func(c *cli.Context) error { +func Server(cfg *config.Config) *cobra.Command { + return &cobra.Command{ + Use: "server", + Short: fmt.Sprintf("start the %s service without runtime (unsupervised mode)", cfg.Service.Name), + PreRunE: func(cmd *cobra.Command, args []string) error { return configlog.ReturnFatal(parser.ParseConfig(cfg)) }, - Action: func(c *cli.Context) error { + RunE: func(cmd *cobra.Command, args []string) error { logger := logging.Configure(cfg.Service.Name, cfg.Log) - traceProvider, err := tracing.GetTraceProvider(c.Context, cfg.Commons.TracesExporter, cfg.Service.Name) + traceProvider, err := tracing.GetTraceProvider(cmd.Context(), cfg.Commons.TracesExporter, cfg.Service.Name) if err != nil { return err } diff --git a/services/storage-users/pkg/command/trash_bin.go b/services/storage-users/pkg/command/trash_bin.go index 12dac44471..c74591455a 100644 --- a/services/storage-users/pkg/command/trash_bin.go +++ b/services/storage-users/pkg/command/trash_bin.go @@ -10,12 +10,6 @@ import ( "strings" "time" - gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1" - rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" - provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" - "github.com/mohae/deepcopy" - "github.com/olekukonko/tablewriter" - "github.com/olekukonko/tablewriter/tw" "github.com/opencloud-eu/opencloud/pkg/config/configlog" zlog "github.com/opencloud-eu/opencloud/pkg/log" "github.com/opencloud-eu/opencloud/services/storage-users/pkg/config" @@ -25,8 +19,15 @@ import ( "github.com/opencloud-eu/reva/v2/pkg/rgrpc/todo/pool" "github.com/opencloud-eu/reva/v2/pkg/storagespace" "github.com/opencloud-eu/reva/v2/pkg/utils" + + gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1" + rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" + provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" + "github.com/mohae/deepcopy" + "github.com/olekukonko/tablewriter" + "github.com/olekukonko/tablewriter/tw" "github.com/rs/zerolog" - "github.com/urfave/cli/v2" + "github.com/spf13/cobra" ) const ( @@ -35,56 +36,37 @@ const ( KEEP_BOTH ) -var _optionFlagTmpl = cli.StringFlag{ - Name: "option", - Value: "skip", - Aliases: []string{"o"}, - Usage: "The restore option defines the behavior for a file to be restored, where the file name already already exists in the target space. Supported values are: 'skip', 'replace' and 'keep-both'.", - DefaultText: "The default value is 'skip' overwriting an existing file", -} - -var _verboseFlagTmpl = cli.BoolFlag{ - Name: "verbose", - Aliases: []string{"v"}, - Usage: "Get more verbose output", -} - -var _applyYesFlagTmpl = cli.BoolFlag{ - Name: "yes", - Aliases: []string{"y"}, - Usage: "Automatic yes to prompts. Assume 'yes' as answer to all prompts and run non-interactively.", -} - // TrashBin wraps trash-bin related sub-commands. -func TrashBin(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: "trash-bin", - Usage: "manage trash-bin's", - Subcommands: []*cli.Command{ - PurgeExpiredResources(cfg), - listTrashBinItems(cfg), - restoreAllTrashBinItems(cfg), - restoreTrashBindItem(cfg), - }, +func TrashBin(cfg *config.Config) *cobra.Command { + trashBinCmd := &cobra.Command{ + Use: "trash-bin", + Short: "manage trash-bin's", } + + trashBinCmd.AddCommand([]*cobra.Command{ + PurgeExpiredResources(cfg), + listTrashBinItems(cfg), + restoreAllTrashBinItems(cfg), + restoreTrashBinItem(cfg), + }...) + return trashBinCmd } // PurgeExpiredResources cli command removes old trash-bin items. -func PurgeExpiredResources(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: "purge-expired", - Usage: "Purge expired trash-bin items", - Flags: []cli.Flag{}, - Before: func(c *cli.Context) error { +func PurgeExpiredResources(cfg *config.Config) *cobra.Command { + return &cobra.Command{ + Use: "purge-expired", + Short: "Purge expired trash-bin items", + PreRunE: func(cmd *cobra.Command, args []string) error { return configlog.ReturnFatal(parser.ParseConfig(cfg)) }, - Action: func(c *cli.Context) error { + RunE: func(cmd *cobra.Command, args []string) error { stream, err := event.NewStream(cfg) if err != nil { return err } - if err := events.Publish(c.Context, stream, event.PurgeTrashBin{ExecutionTime: time.Now()}); err != nil { + if err := events.Publish(cmd.Context(), stream, event.PurgeTrashBin{ExecutionTime: time.Now()}); err != nil { return err } @@ -101,29 +83,24 @@ func PurgeExpiredResources(cfg *config.Config) *cli.Command { } } -func listTrashBinItems(cfg *config.Config) *cli.Command { - var verboseVal bool - verboseFlag := _verboseFlagTmpl - verboseFlag.Destination = &verboseVal - return &cli.Command{ - Name: "list", - Usage: "Print a list of all trash-bin items of a space.", - ArgsUsage: "['spaceID' required]", - Flags: []cli.Flag{ - &verboseFlag, - }, - Before: func(c *cli.Context) error { +func listTrashBinItems(cfg *config.Config) *cobra.Command { + listTrashBinItemsCmd := &cobra.Command{ + Use: "list", + Short: "Print a list of all trash-bin items of a space.", + // TODO: n might need to equal 2 not sure. + Args: cobra.ExactArgs(1), + PreRunE: func(cmd *cobra.Command, args []string) error { return configlog.ReturnFatal(parser.ParseConfig(cfg)) }, - Action: func(c *cli.Context) error { - log := cliLogger(verboseVal) + RunE: func(cmd *cobra.Command, args []string) error { + log := cliLogger(cmd.Flag("verbose").Changed) var spaceID string - if c.NArg() > 0 { - spaceID = c.Args().Get(0) + if len(args) > 0 { + spaceID = args[0] } if spaceID == "" { - _ = cli.ShowSubcommandHelp(c) - return fmt.Errorf("spaceID is requered") + _ = cmd.Help() + return fmt.Errorf("spaceID is requiered") } log.Info().Msgf("Getting trash-bin items for spaceID: '%s' ...", spaceID) @@ -153,42 +130,37 @@ func listTrashBinItems(cfg *config.Config) *cli.Command { return nil }, } + listTrashBinItemsCmd.Flags().BoolP( + "verbose", + "v", + false, + "Get more verbose output", + ) + return listTrashBinItemsCmd } -func restoreAllTrashBinItems(cfg *config.Config) *cli.Command { - var optionFlagVal string +func restoreAllTrashBinItems(cfg *config.Config) *cobra.Command { var overwriteOption int - optionFlag := _optionFlagTmpl - optionFlag.Destination = &optionFlagVal - var verboseVal bool - verboseFlag := _verboseFlagTmpl - verboseFlag.Destination = &verboseVal - var applyYesVal bool - applyYesFlag := _applyYesFlagTmpl - applyYesFlag.Destination = &applyYesVal - return &cli.Command{ - Name: "restore-all", - Usage: "Restore all trash-bin items for a space.", - ArgsUsage: "['spaceID' required]", - Flags: []cli.Flag{ - &optionFlag, - &verboseFlag, - &applyYesFlag, - }, - Before: func(c *cli.Context) error { + restoreAllTrashBinItemsCmd := &cobra.Command{ + Use: "restore-all", + Short: "Restore all trash-bin items for a space.", + // TODO: not sure this could also be 2 + Args: cobra.ExactArgs(1), + PreRunE: func(cmd *cobra.Command, args []string) error { return configlog.ReturnFatal(parser.ParseConfig(cfg)) }, - Action: func(c *cli.Context) error { - log := cliLogger(verboseVal) + RunE: func(cmd *cobra.Command, args []string) error { + log := cliLogger(cmd.Flag("verbose").Changed) var spaceID string - if c.NArg() > 0 { - spaceID = c.Args().Get(0) + if len(args) > 0 { + spaceID = args[0] } if spaceID == "" { - _ = cli.ShowSubcommandHelp(c) - return cli.Exit("The spaceID is required", 1) + _ = cmd.Help() + fmt.Errorf("spaceID is requiered") + os.Exit(1) } - switch optionFlagVal { + switch cmd.Flag("option").Value.String() { case "skip": overwriteOption = SKIP case "replace": @@ -196,8 +168,9 @@ func restoreAllTrashBinItems(cfg *config.Config) *cli.Command { case "keep-both": overwriteOption = KEEP_BOTH default: - _ = cli.ShowSubcommandHelp(c) - return cli.Exit("The option flag is invalid", 1) + _ = cmd.Help() + fmt.Errorf("The option flag is invalid") + os.Exit(1) } log.Info().Msgf("Restoring trash-bin items for spaceID: '%s' ...", spaceID) @@ -218,7 +191,7 @@ func restoreAllTrashBinItems(cfg *config.Config) *cli.Command { return err } - if !applyYesVal { + if !cmd.Flag("yes").Changed { for { fmt.Printf("Found %d items that could be restored, continue (Y/n), show the items list (s): ", len(res.GetRecycleItems())) var i string @@ -241,7 +214,7 @@ func restoreAllTrashBinItems(cfg *config.Config) *cli.Command { } } - log.Info().Msgf("Run restoring-all with option=%s", optionFlagVal) + log.Info().Msgf("Run restoring-all with option=%s", cmd.Flag("option").Value.String()) for _, item := range res.GetRecycleItems() { log.Info().Msgf("restoring itemID: '%s', path: '%s', type: '%s'", item.GetKey(), item.GetRef().GetPath(), itemType(item.GetType())) dstRes, err := restore(ctx, client, ref, item, overwriteOption, cfg.CliMaxAttemptsRenameFile, log) @@ -254,43 +227,54 @@ func restoreAllTrashBinItems(cfg *config.Config) *cli.Command { return nil }, } + restoreAllTrashBinItemsCmd.Flags().BoolP( + "verbose", + "v", + false, + "Get more verbose output", + ) + restoreAllTrashBinItemsCmd.Flags().StringP( + "option", + "o", + "skip", + "The restore option defines the behavior for a file to be restored, where the file name already already exists in the target space. Supported values are: 'skip', 'replace' and 'keep-both'. The default value is 'skip' overwriting an existing file.", + ) + restoreAllTrashBinItemsCmd.Flags().BoolP( + "yes", + "y", + false, + "Automatic yes to prompts. Assume 'yes' as answer to all prompts and run non-interactively.", + ) + + return restoreAllTrashBinItemsCmd } -func restoreTrashBindItem(cfg *config.Config) *cli.Command { - var optionFlagVal string +func restoreTrashBinItem(cfg *config.Config) *cobra.Command { var overwriteOption int - optionFlag := _optionFlagTmpl - optionFlag.Destination = &optionFlagVal - var verboseVal bool - verboseFlag := _verboseFlagTmpl - verboseFlag.Destination = &verboseVal - return &cli.Command{ - Name: "restore", - Usage: "Restore a trash-bin item by ID.", - ArgsUsage: "['spaceID' required] ['itemID' required]", - Flags: []cli.Flag{ - &optionFlag, - &verboseFlag, - }, - Before: func(c *cli.Context) error { + restoreTrashBinItemCmd := &cobra.Command{ + Use: "restore", + Short: "Restore a trash-bin item by ID.", + // TODO: not sure this could also be 2 + Args: cobra.ExactArgs(1), + PreRunE: func(cmd *cobra.Command, args []string) error { return configlog.ReturnFatal(parser.ParseConfig(cfg)) }, - Action: func(c *cli.Context) error { - log := cliLogger(verboseVal) + RunE: func(cmd *cobra.Command, args []string) error { + log := cliLogger(cmd.Flag("verbose").Changed) var spaceID, itemID string - if c.NArg() > 1 { - spaceID = c.Args().Get(0) - itemID = c.Args().Get(1) + if len(args) > 1 { + spaceID = args[0] + itemID = args[1] } if spaceID == "" { - _ = cli.ShowSubcommandHelp(c) + _ = cmd.Help() return fmt.Errorf("spaceID is requered") } if itemID == "" { - _ = cli.ShowSubcommandHelp(c) + _ = cmd.Help() return fmt.Errorf("itemID is requered") } - switch optionFlagVal { + switch cmd.Flag("option").Value.String() { case "skip": overwriteOption = SKIP case "replace": @@ -298,8 +282,9 @@ func restoreTrashBindItem(cfg *config.Config) *cli.Command { case "keep-both": overwriteOption = KEEP_BOTH default: - _ = cli.ShowSubcommandHelp(c) - return cli.Exit("The option flag is invalid", 1) + _ = cmd.Help() + fmt.Errorf("The option flag is invalid") + os.Exit(1) } log.Info().Msgf("Restoring trash-bin item for spaceID: '%s' itemID: '%s' ...", spaceID, itemID) @@ -332,7 +317,7 @@ func restoreTrashBindItem(cfg *config.Config) *cli.Command { if !found { return fmt.Errorf("itemID '%s' not found", itemID) } - log.Info().Msgf("Run restoring with option=%s", optionFlagVal) + log.Info().Msgf("Run restoring with option=%s", cmd.Flag("option").Value.String()) log.Info().Msgf("restoring itemID: '%s', path: '%s', type: '%s", itemRef.GetKey(), itemRef.GetRef().GetPath(), itemType(itemRef.GetType())) dstRes, err := restore(ctx, client, ref, itemRef, overwriteOption, cfg.CliMaxAttemptsRenameFile, log) if err != nil { @@ -342,6 +327,19 @@ func restoreTrashBindItem(cfg *config.Config) *cli.Command { return nil }, } + restoreTrashBinItemCmd.Flags().BoolP( + "verbose", + "v", + false, + "Get more verbose output", + ) + restoreTrashBinItemCmd.Flags().StringP( + "option", + "o", + "skip", + "The restore option defines the behavior for a file to be restored, where the file name already already exists in the target space. Supported values are: 'skip', 'replace' and 'keep-both'. The default value is 'skip' overwriting an existing file.", + ) + return restoreTrashBinItemCmd } func listRecycle(ctx context.Context, client gateway.GatewayAPIClient, ref provider.Reference) (*provider.ListRecycleResponse, error) { @@ -354,7 +352,8 @@ func listRecycle(ctx context.Context, client gateway.GatewayAPIClient, ref provi return nil, fmt.Errorf("%s %s", _retrievingErrorMsg, res.Status.Code) } if len(res.GetRecycleItems()) == 0 { - return res, cli.Exit("The trash-bin is empty. Nothing to restore", 0) + fmt.Errorf("The trash-bin is empty. Nothing to restore") + os.Exit(0) } return res, nil } diff --git a/services/storage-users/pkg/command/uploads.go b/services/storage-users/pkg/command/uploads.go index 785d64e93f..0b56a54ed1 100644 --- a/services/storage-users/pkg/command/uploads.go +++ b/services/storage-users/pkg/command/uploads.go @@ -11,7 +11,7 @@ import ( "github.com/olekukonko/tablewriter" "github.com/olekukonko/tablewriter/tw" - "github.com/urfave/cli/v2" + "github.com/spf13/cobra" userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" "github.com/opencloud-eu/opencloud/pkg/config/configlog" @@ -41,64 +41,28 @@ type Session struct { } // Uploads is the entry point for the uploads command -func Uploads(cfg *config.Config) *cli.Command { - return &cli.Command{ - - Name: "uploads", - Usage: "manage unfinished uploads", - Subcommands: []*cli.Command{ - ListUploadSessions(cfg), - }, +func Uploads(cfg *config.Config) *cobra.Command { + uploadsCmd := &cobra.Command{ + Use: "uploads", + Short: "manage unfinished uploads", } + uploadsCmd.AddCommand([]*cobra.Command{ + ListUploadSessions(cfg), + }...) + + return uploadsCmd + } // ListUploadSessions prints a list of upload sessiens -func ListUploadSessions(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: "sessions", - Usage: "Print a list of upload sessions", - Flags: []cli.Flag{ - &cli.StringFlag{ - Name: "id", - DefaultText: "unset", - Usage: "filter sessions by upload session id", - }, - &cli.BoolFlag{ - Name: "processing", - DefaultText: "unset", - Usage: "filter sessions by processing status", - }, - &cli.BoolFlag{ - Name: "expired", - DefaultText: "unset", - Usage: "filter sessions by expired status", - }, - &cli.BoolFlag{ - Name: "has-virus", - DefaultText: "unset", - Usage: "filter sessions by virus scan result", - }, - &cli.BoolFlag{ - Name: "json", - Usage: "output as json", - }, - &cli.BoolFlag{ - Name: "restart", - Usage: "send restart event for all listed sessions. Only one of resume/restart/clean can be set.", - }, - &cli.BoolFlag{ - Name: "resume", - Usage: "send resume event for all listed sessions. Only one of resume/restart/clean can be set.", - }, - &cli.BoolFlag{ - Name: "clean", - Usage: "remove uploads for all listed sessions. Only one of resume/restart/clean can be set.", - }, - }, - Before: func(c *cli.Context) error { +func ListUploadSessions(cfg *config.Config) *cobra.Command { + listUploadSessionsCmd := &cobra.Command{ + Use: "sessions", + Short: "Print a list of upload sessions", + PreRunE: func(cmd *cobra.Command, args []string) error { return configlog.ReturnFatal(parser.ParseConfig(cfg)) }, - Action: func(c *cli.Context) error { + RunE: func(cmd *cobra.Command, args []string) error { var err error f, ok := registry.NewFuncs[cfg.Driver] if !ok { @@ -131,7 +95,7 @@ func ListUploadSessions(cfg *config.Config) *cli.Command { } var stream events.Stream - if c.Bool("restart") || c.Bool("resume") { + if cmd.Flag("restart").Changed || cmd.Flag("resume").Changed { stream, err = event.NewStream(cfg) if err != nil { fmt.Fprintf(os.Stderr, "Failed to create event stream: %v\n", err) @@ -139,8 +103,8 @@ func ListUploadSessions(cfg *config.Config) *cli.Command { } } - filter := buildFilter(c) - uploads, err := managingFS.ListUploadSessions(c.Context, filter) + filter := buildFilter(cmd) + uploads, err := managingFS.ListUploadSessions(cmd.Context(), filter) if err != nil { return err } @@ -150,7 +114,7 @@ func ListUploadSessions(cfg *config.Config) *cli.Command { raw []Session ) - if !c.Bool("json") { + if !cmd.Flag("json").Changed { fmt.Println(buildInfo(filter)) table = tablewriter.NewTable(os.Stdout, tablewriter.WithHeaderAutoFormat(tw.Off)) @@ -175,7 +139,7 @@ func ListUploadSessions(cfg *config.Config) *cli.Command { ScanResult: sr, } - if c.Bool("json") { + if cmd.Flag("json").Changed { raw = append(raw, session) } else { table.Append([]string{ @@ -194,7 +158,7 @@ func ListUploadSessions(cfg *config.Config) *cli.Command { } switch { - case c.Bool("restart"): + case cmd.Flag("restart").Changed: if err := events.Publish(context.Background(), stream, events.RestartPostprocessing{ UploadID: u.ID(), Timestamp: utils.TSNow(), @@ -204,7 +168,7 @@ func ListUploadSessions(cfg *config.Config) *cli.Command { os.Exit(1) } - case c.Bool("resume"): + case cmd.Flag("resume").Changed: if err := events.Publish(context.Background(), stream, events.ResumePostprocessing{ UploadID: u.ID(), Timestamp: utils.TSNow(), @@ -214,15 +178,15 @@ func ListUploadSessions(cfg *config.Config) *cli.Command { os.Exit(1) } - case c.Bool("clean"): - if err := u.Purge(c.Context); err != nil { + case cmd.Flag("clean").Changed: + if err := u.Purge(cmd.Context()); err != nil { fmt.Fprintf(os.Stderr, "Failed to clean upload session '%s'\n", u.ID()) } } } - if !c.Bool("json") { + if !cmd.Flag("json").Changed { table.Render() return nil } @@ -236,24 +200,33 @@ func ListUploadSessions(cfg *config.Config) *cli.Command { return nil }, } + listUploadSessionsCmd.Flags().String("id", "unset", "filter sessions by upload session id") + listUploadSessionsCmd.Flags().Bool("processing", false, "filter sessions by processing status") + listUploadSessionsCmd.Flags().Bool("expired", false, "filter sessions by expired status") + listUploadSessionsCmd.Flags().Bool("has-virus", false, "filter sessions by virus scan result") + listUploadSessionsCmd.Flags().Bool("json", false, "output as json") + listUploadSessionsCmd.Flags().Bool("restart", false, "send restart event for all listed sessions. Only one of resume/restart/clean can be set.") + listUploadSessionsCmd.Flags().Bool("resume", false, "send resume event for all listed sessions. Only one of resume/restart/clean can be set.") + listUploadSessionsCmd.Flags().Bool("clean", false, "remove uploads for all listed sessions. Only one of resume/restart/clean can be set.") + return listUploadSessionsCmd } -func buildFilter(c *cli.Context) storage.UploadSessionFilter { +func buildFilter(cmd *cobra.Command) storage.UploadSessionFilter { filter := storage.UploadSessionFilter{} - if c.IsSet("processing") { - processingValue := c.Bool("processing") + if cmd.Flag("processing").Changed { + processingValue := cmd.Flag("processing").Changed filter.Processing = &processingValue } - if c.IsSet("expired") { - expiredValue := c.Bool("expired") + if cmd.Flag("expired").Changed { + expiredValue := cmd.Flag("expired").Changed filter.Expired = &expiredValue } - if c.IsSet("has-virus") { - infectedValue := c.Bool("has-virus") + if cmd.Flag("has-virus").Changed { + infectedValue := cmd.Flag("has-virus").Changed filter.HasVirus = &infectedValue } - if c.IsSet("id") { - idValue := c.String("id") + if cmd.Flag("id").Changed { + idValue := cmd.Flag("id").Value.String() filter.ID = &idValue } return filter diff --git a/services/storage-users/pkg/command/version.go b/services/storage-users/pkg/command/version.go index f227795d79..37da9051cf 100644 --- a/services/storage-users/pkg/command/version.go +++ b/services/storage-users/pkg/command/version.go @@ -6,20 +6,19 @@ import ( "github.com/opencloud-eu/opencloud/pkg/registry" "github.com/opencloud-eu/opencloud/pkg/version" + "github.com/opencloud-eu/opencloud/services/storage-users/pkg/config" "github.com/olekukonko/tablewriter" "github.com/olekukonko/tablewriter/tw" - "github.com/opencloud-eu/opencloud/services/storage-users/pkg/config" - "github.com/urfave/cli/v2" + "github.com/spf13/cobra" ) // Version prints the service versions of all running instances. -func Version(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: "version", - Usage: "print the version of this binary and the running service instances", - Category: "info", - Action: func(c *cli.Context) error { +func Version(cfg *config.Config) *cobra.Command { + return &cobra.Command{ + Use: "version", + Short: "print the version of this binary and the running service instances", + RunE: func(cmd *cobra.Command, args []string) error { fmt.Println("Version: " + version.GetString()) fmt.Printf("Compiled: %s\n", version.Compiled()) fmt.Println("") From 7e1ab4f34f4590bb31012f2b53a8d5be5cc37328 Mon Sep 17 00:00:00 2001 From: Christian Richter Date: Tue, 2 Dec 2025 18:20:14 +0100 Subject: [PATCH 37/82] migrate thumbnails from urfave/cli to spf13/cobra Signed-off-by: Christian Richter --- services/thumbnails/pkg/command/health.go | 16 ++++++++-------- services/thumbnails/pkg/command/root.go | 17 +++++++++-------- services/thumbnails/pkg/command/server.go | 18 +++++++++--------- services/thumbnails/pkg/command/version.go | 15 +++++++-------- 4 files changed, 33 insertions(+), 33 deletions(-) diff --git a/services/thumbnails/pkg/command/health.go b/services/thumbnails/pkg/command/health.go index 4240ac796c..8319313790 100644 --- a/services/thumbnails/pkg/command/health.go +++ b/services/thumbnails/pkg/command/health.go @@ -8,19 +8,19 @@ import ( "github.com/opencloud-eu/opencloud/services/thumbnails/pkg/config" "github.com/opencloud-eu/opencloud/services/thumbnails/pkg/config/parser" "github.com/opencloud-eu/opencloud/services/thumbnails/pkg/logging" - "github.com/urfave/cli/v2" + + "github.com/spf13/cobra" ) // Health is the entrypoint for the health command. -func Health(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: "health", - Usage: "check health status", - Category: "info", - Before: func(c *cli.Context) error { +func Health(cfg *config.Config) *cobra.Command { + return &cobra.Command{ + Use: "health", + Short: "check health status", + PreRunE: func(cmd *cobra.Command, args []string) error { return configlog.ReturnError(parser.ParseConfig(cfg)) }, - Action: func(c *cli.Context) error { + RunE: func(cmd *cobra.Command, args []string) error { logger := logging.Configure(cfg.Service.Name, cfg.Log) resp, err := http.Get( diff --git a/services/thumbnails/pkg/command/root.go b/services/thumbnails/pkg/command/root.go index 8e3d9e8308..7bbf9b7fd6 100644 --- a/services/thumbnails/pkg/command/root.go +++ b/services/thumbnails/pkg/command/root.go @@ -5,12 +5,12 @@ import ( "github.com/opencloud-eu/opencloud/pkg/clihelper" "github.com/opencloud-eu/opencloud/services/thumbnails/pkg/config" - "github.com/urfave/cli/v2" + "github.com/spf13/cobra" ) // GetCommands provides all commands for this service -func GetCommands(cfg *config.Config) cli.Commands { - return []*cli.Command{ +func GetCommands(cfg *config.Config) []*cobra.Command { + return []*cobra.Command{ // start this service Server(cfg), @@ -24,11 +24,12 @@ func GetCommands(cfg *config.Config) cli.Commands { // Execute is the entry point for the opencloud-thumbnails command. func Execute(cfg *config.Config) error { - app := clihelper.DefaultApp(&cli.App{ - Name: "thumbnails", - Usage: "Example usage", - Commands: GetCommands(cfg), + app := clihelper.DefaultAppCobra(&cobra.Command{ + Use: "thumbnails", + Short: "Example usage", }) + app.AddCommand(GetCommands(cfg)...) + app.SetArgs(os.Args[1:]) - return app.RunContext(cfg.Context, os.Args) + return app.ExecuteContext(cfg.Context) } diff --git a/services/thumbnails/pkg/command/server.go b/services/thumbnails/pkg/command/server.go index 195f70866b..1f99884288 100644 --- a/services/thumbnails/pkg/command/server.go +++ b/services/thumbnails/pkg/command/server.go @@ -17,22 +17,22 @@ import ( "github.com/opencloud-eu/opencloud/services/thumbnails/pkg/server/debug" "github.com/opencloud-eu/opencloud/services/thumbnails/pkg/server/grpc" "github.com/opencloud-eu/opencloud/services/thumbnails/pkg/server/http" - "github.com/urfave/cli/v2" + + "github.com/spf13/cobra" ) // Server is the entrypoint for the server command. -func Server(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: "server", - Usage: fmt.Sprintf("start the %s service without runtime (unsupervised mode)", cfg.Service.Name), - Category: "server", - Before: func(_ *cli.Context) error { +func Server(cfg *config.Config) *cobra.Command { + return &cobra.Command{ + Use: "server", + Short: fmt.Sprintf("start the %s service without runtime (unsupervised mode)", cfg.Service.Name), + PreRunE: func(cmd *cobra.Command, args []string) error { return configlog.ReturnFatal(parser.ParseConfig(cfg)) }, - Action: func(c *cli.Context) error { + RunE: func(cmd *cobra.Command, args []string) error { logger := logging.Configure(cfg.Service.Name, cfg.Log) - traceProvider, err := tracing.GetTraceProvider(c.Context, cfg.Commons.TracesExporter, cfg.Service.Name) + traceProvider, err := tracing.GetTraceProvider(cmd.Context(), cfg.Commons.TracesExporter, cfg.Service.Name) if err != nil { return err } diff --git a/services/thumbnails/pkg/command/version.go b/services/thumbnails/pkg/command/version.go index 003fc98d7e..2099bbee24 100644 --- a/services/thumbnails/pkg/command/version.go +++ b/services/thumbnails/pkg/command/version.go @@ -6,20 +6,19 @@ import ( "github.com/opencloud-eu/opencloud/pkg/registry" "github.com/opencloud-eu/opencloud/pkg/version" + "github.com/opencloud-eu/opencloud/services/thumbnails/pkg/config" "github.com/olekukonko/tablewriter" "github.com/olekukonko/tablewriter/tw" - "github.com/opencloud-eu/opencloud/services/thumbnails/pkg/config" - "github.com/urfave/cli/v2" + "github.com/spf13/cobra" ) // Version prints the service versions of all running instances. -func Version(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: "version", - Usage: "print the version of this binary and the running service instances", - Category: "info", - Action: func(_ *cli.Context) error { +func Version(cfg *config.Config) *cobra.Command { + return &cobra.Command{ + Use: "version", + Short: "print the version of this binary and the running service instances", + RunE: func(cmd *cobra.Command, args []string) error { fmt.Println("Version: " + version.GetString()) fmt.Printf("Compiled: %s\n", version.Compiled()) fmt.Println("") From 5f43accb26f1b2246c41fb3d49c4383c00bfb708 Mon Sep 17 00:00:00 2001 From: Christian Richter Date: Tue, 2 Dec 2025 18:23:05 +0100 Subject: [PATCH 38/82] migrate userlog from urfave/cli to spf13/cobra Signed-off-by: Christian Richter --- services/userlog/pkg/command/health.go | 13 ++++++----- services/userlog/pkg/command/root.go | 18 ++++++++------- services/userlog/pkg/command/server.go | 29 ++++++++++++------------- services/userlog/pkg/command/version.go | 14 ++++++------ 4 files changed, 38 insertions(+), 36 deletions(-) diff --git a/services/userlog/pkg/command/health.go b/services/userlog/pkg/command/health.go index ae9c5552fd..43955ed8bd 100644 --- a/services/userlog/pkg/command/health.go +++ b/services/userlog/pkg/command/health.go @@ -2,15 +2,16 @@ package command import ( "github.com/opencloud-eu/opencloud/services/userlog/pkg/config" - "github.com/urfave/cli/v2" + + "github.com/spf13/cobra" ) // Health is the entrypoint for the health command. -func Health(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: "health", - Usage: "Check health status", - Action: func(c *cli.Context) error { +func Health(cfg *config.Config) *cobra.Command { + return &cobra.Command{ + Use: "health", + Short: "Check health status", + RunE: func(cmd *cobra.Command, args []string) error { // Not implemented return nil }, diff --git a/services/userlog/pkg/command/root.go b/services/userlog/pkg/command/root.go index 4d4b95a3da..bf07f7356e 100644 --- a/services/userlog/pkg/command/root.go +++ b/services/userlog/pkg/command/root.go @@ -5,12 +5,13 @@ import ( "github.com/opencloud-eu/opencloud/pkg/clihelper" "github.com/opencloud-eu/opencloud/services/userlog/pkg/config" - "github.com/urfave/cli/v2" + + "github.com/spf13/cobra" ) // GetCommands provides all commands for this service -func GetCommands(cfg *config.Config) cli.Commands { - return []*cli.Command{ +func GetCommands(cfg *config.Config) []*cobra.Command { + return []*cobra.Command{ // start this service Server(cfg), @@ -24,11 +25,12 @@ func GetCommands(cfg *config.Config) cli.Commands { // Execute is the entry point for the userlog command. func Execute(cfg *config.Config) error { - app := clihelper.DefaultApp(&cli.App{ - Name: "userlog", - Usage: "starts userlog service", - Commands: GetCommands(cfg), + app := clihelper.DefaultAppCobra(&cobra.Command{ + Use: "userlog", + Short: "starts userlog service", }) + app.AddCommand(GetCommands(cfg)...) + app.SetArgs(os.Args[1:]) - return app.RunContext(cfg.Context, os.Args) + return app.ExecuteContext(cfg.Context) } diff --git a/services/userlog/pkg/command/server.go b/services/userlog/pkg/command/server.go index 25815dbcce..3021fddc3a 100644 --- a/services/userlog/pkg/command/server.go +++ b/services/userlog/pkg/command/server.go @@ -5,13 +5,6 @@ import ( "fmt" "os/signal" - "github.com/opencloud-eu/reva/v2/pkg/events" - "github.com/opencloud-eu/reva/v2/pkg/events/stream" - "github.com/opencloud-eu/reva/v2/pkg/rgrpc/todo/pool" - "github.com/opencloud-eu/reva/v2/pkg/store" - "github.com/urfave/cli/v2" - microstore "go-micro.dev/v4/store" - "github.com/opencloud-eu/opencloud/pkg/config/configlog" "github.com/opencloud-eu/opencloud/pkg/generators" "github.com/opencloud-eu/opencloud/pkg/registry" @@ -27,6 +20,13 @@ import ( "github.com/opencloud-eu/opencloud/services/userlog/pkg/metrics" "github.com/opencloud-eu/opencloud/services/userlog/pkg/server/debug" "github.com/opencloud-eu/opencloud/services/userlog/pkg/server/http" + + "github.com/opencloud-eu/reva/v2/pkg/events" + "github.com/opencloud-eu/reva/v2/pkg/events/stream" + "github.com/opencloud-eu/reva/v2/pkg/rgrpc/todo/pool" + "github.com/opencloud-eu/reva/v2/pkg/store" + "github.com/spf13/cobra" + microstore "go-micro.dev/v4/store" ) // all events we care about @@ -48,17 +48,16 @@ var _registeredEvents = []events.Unmarshaller{ } // Server is the entrypoint for the server command. -func Server(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: "server", - Usage: fmt.Sprintf("start the %s service without runtime (unsupervised mode)", cfg.Service.Name), - Category: "server", - Before: func(c *cli.Context) error { +func Server(cfg *config.Config) *cobra.Command { + return &cobra.Command{ + Use: "server", + Short: fmt.Sprintf("start the %s service without runtime (unsupervised mode)", cfg.Service.Name), + PreRunE: func(cmd *cobra.Command, args []string) error { return configlog.ReturnFatal(parser.ParseConfig(cfg)) }, - Action: func(c *cli.Context) error { + RunE: func(cmd *cobra.Command, args []string) error { logger := logging.Configure(cfg.Service.Name, cfg.Log) - tracerProvider, err := tracing.GetTraceProvider(c.Context, cfg.Commons.TracesExporter, cfg.Service.Name) + tracerProvider, err := tracing.GetTraceProvider(cmd.Context(), cfg.Commons.TracesExporter, cfg.Service.Name) if err != nil { return err } diff --git a/services/userlog/pkg/command/version.go b/services/userlog/pkg/command/version.go index ee5529e5a1..5186a2c96d 100644 --- a/services/userlog/pkg/command/version.go +++ b/services/userlog/pkg/command/version.go @@ -2,16 +2,16 @@ package command import ( "github.com/opencloud-eu/opencloud/services/userlog/pkg/config" - "github.com/urfave/cli/v2" + + "github.com/spf13/cobra" ) // Version prints the service versions of all running instances. -func Version(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: "version", - Usage: "print the version of this binary and the running service instances", - Category: "info", - Action: func(c *cli.Context) error { +func Version(cfg *config.Config) *cobra.Command { + return &cobra.Command{ + Use: "version", + Short: "print the version of this binary and the running service instances", + RunE: func(cmd *cobra.Command, args []string) error { // not implemented return nil }, From 63a0b10a0dd50f9b1b52b1de9f3b5ce023a85819 Mon Sep 17 00:00:00 2001 From: Christian Richter Date: Tue, 2 Dec 2025 18:26:15 +0100 Subject: [PATCH 39/82] migrate users from urfave/cli to spf13/cobra Signed-off-by: Christian Richter --- services/users/cmd/{user => users}/main.go | 0 services/users/pkg/command/health.go | 16 ++++++++-------- services/users/pkg/command/root.go | 18 ++++++++++-------- services/users/pkg/command/server.go | 18 +++++++++--------- services/users/pkg/command/version.go | 15 +++++++-------- 5 files changed, 34 insertions(+), 33 deletions(-) rename services/users/cmd/{user => users}/main.go (100%) diff --git a/services/users/cmd/user/main.go b/services/users/cmd/users/main.go similarity index 100% rename from services/users/cmd/user/main.go rename to services/users/cmd/users/main.go diff --git a/services/users/pkg/command/health.go b/services/users/pkg/command/health.go index 794a6a958f..6f4893b9f8 100644 --- a/services/users/pkg/command/health.go +++ b/services/users/pkg/command/health.go @@ -8,19 +8,19 @@ import ( "github.com/opencloud-eu/opencloud/services/users/pkg/config" "github.com/opencloud-eu/opencloud/services/users/pkg/config/parser" "github.com/opencloud-eu/opencloud/services/users/pkg/logging" - "github.com/urfave/cli/v2" + + "github.com/spf13/cobra" ) // Health is the entrypoint for the health command. -func Health(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: "health", - Usage: "check health status", - Category: "info", - Before: func(c *cli.Context) error { +func Health(cfg *config.Config) *cobra.Command { + return &cobra.Command{ + Use: "health", + Short: "check health status", + PreRunE: func(cmd *cobra.Command, args []string) error { return configlog.ReturnError(parser.ParseConfig(cfg)) }, - Action: func(c *cli.Context) error { + RunE: func(cmd *cobra.Command, args []string) error { logger := logging.Configure(cfg.Service.Name, cfg.Log) resp, err := http.Get( diff --git a/services/users/pkg/command/root.go b/services/users/pkg/command/root.go index ee8af27d58..0cb36cae53 100644 --- a/services/users/pkg/command/root.go +++ b/services/users/pkg/command/root.go @@ -5,12 +5,13 @@ import ( "github.com/opencloud-eu/opencloud/pkg/clihelper" "github.com/opencloud-eu/opencloud/services/users/pkg/config" - "github.com/urfave/cli/v2" + + "github.com/spf13/cobra" ) // GetCommands provides all commands for this service -func GetCommands(cfg *config.Config) cli.Commands { - return []*cli.Command{ +func GetCommands(cfg *config.Config) []*cobra.Command { + return []*cobra.Command{ // start this service Server(cfg), @@ -24,11 +25,12 @@ func GetCommands(cfg *config.Config) cli.Commands { // Execute is the entry point for the opencloud-user command. func Execute(cfg *config.Config) error { - app := clihelper.DefaultApp(&cli.App{ - Name: "user", - Usage: "Provide users for OpenCloud", - Commands: GetCommands(cfg), + app := clihelper.DefaultAppCobra(&cobra.Command{ + Use: "user", + Short: "Provide users for OpenCloud", }) + app.AddCommand(GetCommands(cfg)...) + app.SetArgs(os.Args[1:]) - return app.RunContext(cfg.Context, os.Args) + return app.ExecuteContext(cfg.Context) } diff --git a/services/users/pkg/command/server.go b/services/users/pkg/command/server.go index 50a549708d..3f6bc2ae36 100644 --- a/services/users/pkg/command/server.go +++ b/services/users/pkg/command/server.go @@ -17,21 +17,21 @@ import ( "github.com/opencloud-eu/opencloud/services/users/pkg/revaconfig" "github.com/opencloud-eu/opencloud/services/users/pkg/server/debug" "github.com/opencloud-eu/reva/v2/cmd/revad/runtime" - "github.com/urfave/cli/v2" + + "github.com/spf13/cobra" ) // Server is the entry point for the server command. -func Server(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: "server", - Usage: fmt.Sprintf("start the %s service without runtime (unsupervised mode)", cfg.Service.Name), - Category: "server", - Before: func(c *cli.Context) error { +func Server(cfg *config.Config) *cobra.Command { + return &cobra.Command{ + Use: "server", + Short: fmt.Sprintf("start the %s service without runtime (unsupervised mode)", cfg.Service.Name), + PreRunE: func(cmd *cobra.Command, args []string) error { return configlog.ReturnFatal(parser.ParseConfig(cfg)) }, - Action: func(c *cli.Context) error { + RunE: func(cmd *cobra.Command, args []string) error { logger := logging.Configure(cfg.Service.Name, cfg.Log) - traceProvider, err := tracing.GetTraceProvider(c.Context, cfg.Commons.TracesExporter, cfg.Service.Name) + traceProvider, err := tracing.GetTraceProvider(cmd.Context(), cfg.Commons.TracesExporter, cfg.Service.Name) if err != nil { return err } diff --git a/services/users/pkg/command/version.go b/services/users/pkg/command/version.go index a1c591f508..5b832e18a7 100644 --- a/services/users/pkg/command/version.go +++ b/services/users/pkg/command/version.go @@ -6,20 +6,19 @@ import ( "github.com/opencloud-eu/opencloud/pkg/registry" "github.com/opencloud-eu/opencloud/pkg/version" + "github.com/opencloud-eu/opencloud/services/users/pkg/config" "github.com/olekukonko/tablewriter" "github.com/olekukonko/tablewriter/tw" - "github.com/opencloud-eu/opencloud/services/users/pkg/config" - "github.com/urfave/cli/v2" + "github.com/spf13/cobra" ) // Version prints the service versions of all running instances. -func Version(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: "version", - Usage: "print the version of this binary and the running service instances", - Category: "info", - Action: func(c *cli.Context) error { +func Version(cfg *config.Config) *cobra.Command { + return &cobra.Command{ + Use: "version", + Short: "print the version of this binary and the running service instances", + RunE: func(cmd *cobra.Command, args []string) error { fmt.Println("Version: " + version.GetString()) fmt.Printf("Compiled: %s\n", version.Compiled()) fmt.Println("") From f1395313580c86e8b6eeba2bbc877af5cb71ffc3 Mon Sep 17 00:00:00 2001 From: Christian Richter Date: Tue, 2 Dec 2025 18:30:21 +0100 Subject: [PATCH 40/82] migrate web from urfave/cli to spf13/cobra Signed-off-by: Christian Richter --- services/web/pkg/command/health.go | 16 ++++++++-------- services/web/pkg/command/root.go | 18 ++++++++++-------- services/web/pkg/command/server.go | 18 +++++++++--------- services/web/pkg/command/version.go | 15 +++++++-------- 4 files changed, 34 insertions(+), 33 deletions(-) diff --git a/services/web/pkg/command/health.go b/services/web/pkg/command/health.go index a03913be95..c2b951e696 100644 --- a/services/web/pkg/command/health.go +++ b/services/web/pkg/command/health.go @@ -8,19 +8,19 @@ import ( "github.com/opencloud-eu/opencloud/services/web/pkg/config" "github.com/opencloud-eu/opencloud/services/web/pkg/config/parser" "github.com/opencloud-eu/opencloud/services/web/pkg/logging" - "github.com/urfave/cli/v2" + + "github.com/spf13/cobra" ) // Health is the entrypoint for the health command. -func Health(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: "health", - Usage: "check health status", - Category: "info", - Before: func(_ *cli.Context) error { +func Health(cfg *config.Config) *cobra.Command { + return &cobra.Command{ + Use: "health", + Short: "check health status", + PreRunE: func(cmd *cobra.Command, args []string) error { return configlog.ReturnError(parser.ParseConfig(cfg)) }, - Action: func(_ *cli.Context) error { + RunE: func(cmd *cobra.Command, args []string) error { logger := logging.Configure(cfg.Service.Name, cfg.Log) resp, err := http.Get( diff --git a/services/web/pkg/command/root.go b/services/web/pkg/command/root.go index 188224adcc..c754c40abb 100644 --- a/services/web/pkg/command/root.go +++ b/services/web/pkg/command/root.go @@ -5,12 +5,13 @@ import ( "github.com/opencloud-eu/opencloud/pkg/clihelper" "github.com/opencloud-eu/opencloud/services/web/pkg/config" - "github.com/urfave/cli/v2" + + "github.com/spf13/cobra" ) // GetCommands provides all commands for this service -func GetCommands(cfg *config.Config) cli.Commands { - return []*cli.Command{ +func GetCommands(cfg *config.Config) []*cobra.Command { + return []*cobra.Command{ // start this service Server(cfg), @@ -24,11 +25,12 @@ func GetCommands(cfg *config.Config) cli.Commands { // Execute is the entry point for the web command. func Execute(cfg *config.Config) error { - app := clihelper.DefaultApp(&cli.App{ - Name: "web", - Usage: "Serve Web for OpenCloud", - Commands: GetCommands(cfg), + app := clihelper.DefaultAppCobra(&cobra.Command{ + Use: "web", + Short: "Serve Web for OpenCloud", }) + app.AddCommand(GetCommands(cfg)...) + app.SetArgs(os.Args[1:]) - return app.RunContext(cfg.Context, os.Args) + return app.ExecuteContext(cfg.Context) } diff --git a/services/web/pkg/command/server.go b/services/web/pkg/command/server.go index 007d123d10..e8f0f2067e 100644 --- a/services/web/pkg/command/server.go +++ b/services/web/pkg/command/server.go @@ -16,21 +16,21 @@ import ( "github.com/opencloud-eu/opencloud/services/web/pkg/metrics" "github.com/opencloud-eu/opencloud/services/web/pkg/server/debug" "github.com/opencloud-eu/opencloud/services/web/pkg/server/http" - "github.com/urfave/cli/v2" + + "github.com/spf13/cobra" ) // Server is the entrypoint for the server command. -func Server(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: "server", - Usage: fmt.Sprintf("start the %s service without runtime (unsupervised mode)", cfg.Service.Name), - Category: "server", - Before: func(_ *cli.Context) error { +func Server(cfg *config.Config) *cobra.Command { + return &cobra.Command{ + Use: "server", + Short: fmt.Sprintf("start the %s service without runtime (unsupervised mode)", cfg.Service.Name), + PreRunE: func(cmd *cobra.Command, args []string) error { return configlog.ReturnFatal(parser.ParseConfig(cfg)) }, - Action: func(c *cli.Context) error { + RunE: func(cmd *cobra.Command, args []string) error { logger := logging.Configure(cfg.Service.Name, cfg.Log) - traceProvider, err := tracing.GetTraceProvider(c.Context, cfg.Commons.TracesExporter, cfg.Service.Name) + traceProvider, err := tracing.GetTraceProvider(cmd.Context(), cfg.Commons.TracesExporter, cfg.Service.Name) if err != nil { return err } diff --git a/services/web/pkg/command/version.go b/services/web/pkg/command/version.go index fa713ee5a6..c03bac8ef8 100644 --- a/services/web/pkg/command/version.go +++ b/services/web/pkg/command/version.go @@ -6,20 +6,19 @@ import ( "github.com/opencloud-eu/opencloud/pkg/registry" "github.com/opencloud-eu/opencloud/pkg/version" + "github.com/opencloud-eu/opencloud/services/web/pkg/config" "github.com/olekukonko/tablewriter" "github.com/olekukonko/tablewriter/tw" - "github.com/opencloud-eu/opencloud/services/web/pkg/config" - "github.com/urfave/cli/v2" + "github.com/spf13/cobra" ) // Version prints the service versions of all running instances. -func Version(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: "version", - Usage: "print the version of this binary and the running service instances", - Category: "info", - Action: func(_ *cli.Context) error { +func Version(cfg *config.Config) *cobra.Command { + return &cobra.Command{ + Use: "version", + Short: "print the version of this binary and the running service instances", + RunE: func(cmd *cobra.Command, args []string) error { fmt.Println("Version: " + version.GetString()) fmt.Printf("Compiled: %s\n", version.Compiled()) fmt.Println("") From ee5ff0820935ec3755e374ba85f7140233685e7f Mon Sep 17 00:00:00 2001 From: Christian Richter Date: Tue, 2 Dec 2025 18:33:30 +0100 Subject: [PATCH 41/82] migrate webdav from urfave/cli to spf13/cobra Signed-off-by: Christian Richter --- services/webdav/pkg/command/health.go | 16 ++++++++-------- services/webdav/pkg/command/root.go | 18 ++++++++++-------- services/webdav/pkg/command/server.go | 18 +++++++++--------- services/webdav/pkg/command/version.go | 15 +++++++-------- 4 files changed, 34 insertions(+), 33 deletions(-) diff --git a/services/webdav/pkg/command/health.go b/services/webdav/pkg/command/health.go index 593404c872..ae8cd68599 100644 --- a/services/webdav/pkg/command/health.go +++ b/services/webdav/pkg/command/health.go @@ -8,19 +8,19 @@ import ( "github.com/opencloud-eu/opencloud/services/webdav/pkg/config" "github.com/opencloud-eu/opencloud/services/webdav/pkg/config/parser" "github.com/opencloud-eu/opencloud/services/webdav/pkg/logging" - "github.com/urfave/cli/v2" + + "github.com/spf13/cobra" ) // Health is the entrypoint for the health command. -func Health(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: "health", - Usage: "check health status", - Category: "info", - Before: func(c *cli.Context) error { +func Health(cfg *config.Config) *cobra.Command { + return &cobra.Command{ + Use: "health", + Short: "check health status", + PreRunE: func(cmd *cobra.Command, args []string) error { return configlog.ReturnError(parser.ParseConfig(cfg)) }, - Action: func(c *cli.Context) error { + RunE: func(cmd *cobra.Command, args []string) error { logger := logging.Configure(cfg.Service.Name, cfg.Log) resp, err := http.Get( diff --git a/services/webdav/pkg/command/root.go b/services/webdav/pkg/command/root.go index 1273ccae67..cb781c75ff 100644 --- a/services/webdav/pkg/command/root.go +++ b/services/webdav/pkg/command/root.go @@ -5,12 +5,13 @@ import ( "github.com/opencloud-eu/opencloud/pkg/clihelper" "github.com/opencloud-eu/opencloud/services/webdav/pkg/config" - "github.com/urfave/cli/v2" + + "github.com/spf13/cobra" ) // GetCommands provides all commands for this service -func GetCommands(cfg *config.Config) cli.Commands { - return []*cli.Command{ +func GetCommands(cfg *config.Config) []*cobra.Command { + return []*cobra.Command{ // start this service Server(cfg), @@ -24,11 +25,12 @@ func GetCommands(cfg *config.Config) cli.Commands { // Execute is the entry point for the opencloud-webdav command. func Execute(cfg *config.Config) error { - app := clihelper.DefaultApp(&cli.App{ - Name: "webdav", - Usage: "Serve WebDAV API for OpenCloud", - Commands: GetCommands(cfg), + app := clihelper.DefaultAppCobra(&cobra.Command{ + Use: "webdav", + Short: "Serve WebDAV API for OpenCloud", }) + app.AddCommand(GetCommands(cfg)...) + app.SetArgs(os.Args[1:]) - return app.RunContext(cfg.Context, os.Args) + return app.ExecuteContext(cfg.Context) } diff --git a/services/webdav/pkg/command/server.go b/services/webdav/pkg/command/server.go index 750f933f82..b930695d48 100644 --- a/services/webdav/pkg/command/server.go +++ b/services/webdav/pkg/command/server.go @@ -16,21 +16,21 @@ import ( "github.com/opencloud-eu/opencloud/services/webdav/pkg/metrics" "github.com/opencloud-eu/opencloud/services/webdav/pkg/server/debug" "github.com/opencloud-eu/opencloud/services/webdav/pkg/server/http" - "github.com/urfave/cli/v2" + + "github.com/spf13/cobra" ) // Server is the entrypoint for the server command. -func Server(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: "server", - Usage: fmt.Sprintf("start the %s service without runtime (unsupervised mode)", cfg.Service.Name), - Category: "server", - Before: func(c *cli.Context) error { +func Server(cfg *config.Config) *cobra.Command { + return &cobra.Command{ + Use: "server", + Short: fmt.Sprintf("start the %s service without runtime (unsupervised mode)", cfg.Service.Name), + PreRunE: func(cmd *cobra.Command, args []string) error { return configlog.ReturnFatal(parser.ParseConfig(cfg)) }, - Action: func(c *cli.Context) error { + RunE: func(cmd *cobra.Command, args []string) error { logger := logging.Configure(cfg.Service.Name, cfg.Log) - traceProvider, err := tracing.GetTraceProvider(c.Context, cfg.Commons.TracesExporter, cfg.Service.Name) + traceProvider, err := tracing.GetTraceProvider(cmd.Context(), cfg.Commons.TracesExporter, cfg.Service.Name) if err != nil { return err } diff --git a/services/webdav/pkg/command/version.go b/services/webdav/pkg/command/version.go index d7b5e6d49d..b56ba3f7ca 100644 --- a/services/webdav/pkg/command/version.go +++ b/services/webdav/pkg/command/version.go @@ -6,20 +6,19 @@ import ( "github.com/opencloud-eu/opencloud/pkg/registry" "github.com/opencloud-eu/opencloud/pkg/version" + "github.com/opencloud-eu/opencloud/services/webdav/pkg/config" "github.com/olekukonko/tablewriter" "github.com/olekukonko/tablewriter/tw" - "github.com/opencloud-eu/opencloud/services/webdav/pkg/config" - "github.com/urfave/cli/v2" + "github.com/spf13/cobra" ) // Version prints the service versions of all running instances. -func Version(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: "version", - Usage: "print the version of this binary and the running service instances", - Category: "info", - Action: func(c *cli.Context) error { +func Version(cfg *config.Config) *cobra.Command { + return &cobra.Command{ + Use: "version", + Short: "print the version of this binary and the running service instances", + RunE: func(cmd *cobra.Command, args []string) error { fmt.Println("Version: " + version.GetString()) fmt.Printf("Compiled: %s\n", version.Compiled()) fmt.Println("") From 1e38489460a07bcfd29242ea7a0a4d53b147b769 Mon Sep 17 00:00:00 2001 From: Christian Richter Date: Tue, 2 Dec 2025 18:37:52 +0100 Subject: [PATCH 42/82] migrate webfinger from urfave/cli to spf13/cobra Signed-off-by: Christian Richter --- services/webfinger/pkg/command/health.go | 16 ++++++++-------- services/webfinger/pkg/command/root.go | 18 ++++++++++-------- services/webfinger/pkg/command/server.go | 18 +++++++++--------- services/webfinger/pkg/command/version.go | 13 ++++++------- 4 files changed, 33 insertions(+), 32 deletions(-) diff --git a/services/webfinger/pkg/command/health.go b/services/webfinger/pkg/command/health.go index cb1fdfe4b2..df5be2a710 100644 --- a/services/webfinger/pkg/command/health.go +++ b/services/webfinger/pkg/command/health.go @@ -8,19 +8,19 @@ import ( "github.com/opencloud-eu/opencloud/services/webfinger/pkg/config" "github.com/opencloud-eu/opencloud/services/webfinger/pkg/config/parser" "github.com/opencloud-eu/opencloud/services/webfinger/pkg/logging" - "github.com/urfave/cli/v2" + + "github.com/spf13/cobra" ) // Health is the entrypoint for the health command. -func Health(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: "health", - Usage: "check health status", - Category: "info", - Before: func(c *cli.Context) error { +func Health(cfg *config.Config) *cobra.Command { + return &cobra.Command{ + Use: "health", + Short: "check health status", + PreRunE: func(cmd *cobra.Command, args []string) error { return configlog.ReturnError(parser.ParseConfig(cfg)) }, - Action: func(c *cli.Context) error { + RunE: func(cmd *cobra.Command, args []string) error { logger := logging.Configure(cfg.Service.Name, cfg.Log) resp, err := http.Get( diff --git a/services/webfinger/pkg/command/root.go b/services/webfinger/pkg/command/root.go index e98acac137..6280793091 100644 --- a/services/webfinger/pkg/command/root.go +++ b/services/webfinger/pkg/command/root.go @@ -5,12 +5,13 @@ import ( "github.com/opencloud-eu/opencloud/pkg/clihelper" "github.com/opencloud-eu/opencloud/services/webfinger/pkg/config" - "github.com/urfave/cli/v2" + + "github.com/spf13/cobra" ) // GetCommands provides all commands for this service -func GetCommands(cfg *config.Config) cli.Commands { - return []*cli.Command{ +func GetCommands(cfg *config.Config) []*cobra.Command { + return []*cobra.Command{ // start this service Server(cfg), @@ -24,11 +25,12 @@ func GetCommands(cfg *config.Config) cli.Commands { // Execute is the entry point for the opencloud webfinger command. func Execute(cfg *config.Config) error { - app := clihelper.DefaultApp(&cli.App{ - Name: "webfinger", - Usage: "Serve webfinger API for OpenCloud", - Commands: GetCommands(cfg), + app := clihelper.DefaultAppCobra(&cobra.Command{ + Use: "webfinger", + Short: "Serve webfinger API for OpenCloud", }) + app.AddCommand(GetCommands(cfg)...) + app.SetArgs(os.Args[1:]) - return app.RunContext(cfg.Context, os.Args) + return app.ExecuteContext(cfg.Context) } diff --git a/services/webfinger/pkg/command/server.go b/services/webfinger/pkg/command/server.go index 2d9a0c723b..fb93d0868d 100644 --- a/services/webfinger/pkg/command/server.go +++ b/services/webfinger/pkg/command/server.go @@ -17,21 +17,21 @@ import ( "github.com/opencloud-eu/opencloud/services/webfinger/pkg/server/debug" "github.com/opencloud-eu/opencloud/services/webfinger/pkg/server/http" "github.com/opencloud-eu/opencloud/services/webfinger/pkg/service/v0" - "github.com/urfave/cli/v2" + + "github.com/spf13/cobra" ) // Server is the entrypoint for the server command. -func Server(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: "server", - Usage: fmt.Sprintf("start the %s service without runtime (unsupervised mode)", cfg.Service.Name), - Category: "server", - Before: func(c *cli.Context) error { +func Server(cfg *config.Config) *cobra.Command { + return &cobra.Command{ + Use: "server", + Short: fmt.Sprintf("start the %s service without runtime (unsupervised mode)", cfg.Service.Name), + PreRunE: func(cmd *cobra.Command, args []string) error { return configlog.ReturnFatal(parser.ParseConfig(cfg)) }, - Action: func(c *cli.Context) error { + RunE: func(cmd *cobra.Command, args []string) error { logger := logging.Configure(cfg.Service.Name, cfg.Log) - traceProvider, err := tracing.GetTraceProvider(c.Context, cfg.Commons.TracesExporter, cfg.Service.Name) + traceProvider, err := tracing.GetTraceProvider(cmd.Context(), cfg.Commons.TracesExporter, cfg.Service.Name) if err != nil { return err } diff --git a/services/webfinger/pkg/command/version.go b/services/webfinger/pkg/command/version.go index 646760f2bb..3f10a0e803 100644 --- a/services/webfinger/pkg/command/version.go +++ b/services/webfinger/pkg/command/version.go @@ -6,20 +6,19 @@ import ( "github.com/opencloud-eu/opencloud/pkg/registry" "github.com/opencloud-eu/opencloud/pkg/version" + "github.com/spf13/cobra" "github.com/olekukonko/tablewriter" "github.com/olekukonko/tablewriter/tw" "github.com/opencloud-eu/opencloud/services/webfinger/pkg/config" - "github.com/urfave/cli/v2" ) // Version prints the service versions of all running instances. -func Version(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: "version", - Usage: "print the version of this binary and the running service instances", - Category: "info", - Action: func(c *cli.Context) error { +func Version(cfg *config.Config) *cobra.Command { + return &cobra.Command{ + Use: "version", + Short: "print the version of this binary and the running service instances", + RunE: func(cmd *cobra.Command, args []string) error { fmt.Println("Version: " + version.GetString()) fmt.Printf("Compiled: %s\n", version.Compiled()) fmt.Println("") From 35900f887589055e1927ec229d76ca9961363ee8 Mon Sep 17 00:00:00 2001 From: Christian Richter Date: Wed, 3 Dec 2025 11:57:56 +0100 Subject: [PATCH 43/82] migrate posix cli commands from urfave/cli to spf13/cobra Signed-off-by: Christian Richter --- opencloud/pkg/command/posixfs.go | 47 +++++++++++++++---------------- opencloud/pkg/register/command.go | 4 +-- 2 files changed, 24 insertions(+), 27 deletions(-) diff --git a/opencloud/pkg/command/posixfs.go b/opencloud/pkg/command/posixfs.go index 0cc4321a82..88a173d742 100644 --- a/opencloud/pkg/command/posixfs.go +++ b/opencloud/pkg/command/posixfs.go @@ -10,9 +10,10 @@ import ( "github.com/opencloud-eu/opencloud/opencloud/pkg/register" "github.com/opencloud-eu/opencloud/pkg/config" + "github.com/pkg/xattr" + "github.com/spf13/cobra" "github.com/theckman/yacspin" - "github.com/urfave/cli/v2" "github.com/vmihailenco/msgpack/v5" ) @@ -37,15 +38,15 @@ type EntryInfo struct { } // PosixfsCommand is the entrypoint for the posixfs command. -func PosixfsCommand(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: "posixfs", - Usage: `cli tools to inspect and manipulate a posixfs storage.`, - Category: "maintenance", - Subcommands: []*cli.Command{ - consistencyCmd(cfg), - }, +func PosixfsCommand(cfg *config.Config) *cobra.Command { + posixCmd := &cobra.Command{ + Use: "posixfs", + Short: `cli tools to inspect and manipulate a posixfs storage.`, } + + posixCmd.AddCommand(consistencyCmd(cfg)) + + return posixCmd } func init() { @@ -53,27 +54,23 @@ func init() { } // consistencyCmd returns a command to check the consistency of the posixfs storage. -func consistencyCmd(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: "consistency", - Usage: "check the consistency of the posixfs storage", - Action: func(c *cli.Context) error { - return checkPosixfsConsistency(c, cfg) - }, - Flags: []cli.Flag{ - &cli.StringFlag{ - Name: "root", - Aliases: []string{"r"}, - Required: true, - Usage: "Path to the root directory of the posixfs storage", - }, +func consistencyCmd(cfg *config.Config) *cobra.Command { + consCmd := &cobra.Command{ + Use: "consistency", + Short: "check the consistency of the posixfs storage", + RunE: func(cmd *cobra.Command, args []string) error { + return checkPosixfsConsistency(cmd, cfg) }, } + consCmd.Flags().StringP("root", "r", "", "Path to the root directory of the posixfs storage") + _ = consCmd.MarkFlagRequired("root") + + return consCmd } // checkPosixfsConsistency checks the consistency of the posixfs storage. -func checkPosixfsConsistency(c *cli.Context, cfg *config.Config) error { - rootPath := c.String("root") +func checkPosixfsConsistency(cmd *cobra.Command, cfg *config.Config) error { + rootPath := cmd.Flag("root").Value.String() indexesPath := filepath.Join(rootPath, "indexes") _, err := os.Stat(indexesPath) diff --git a/opencloud/pkg/register/command.go b/opencloud/pkg/register/command.go index e65745aa4a..ac73c89a90 100644 --- a/opencloud/pkg/register/command.go +++ b/opencloud/pkg/register/command.go @@ -2,7 +2,7 @@ package register import ( "github.com/opencloud-eu/opencloud/pkg/config" - "github.com/urfave/cli/v2" + "github.com/spf13/cobra" ) var ( @@ -11,7 +11,7 @@ var ( ) // Command defines the register command. -type Command func(*config.Config) *cli.Command +type Command func(*config.Config) *cobra.Command // AddCommand appends a command to Commands. func AddCommand(cmd Command) { From 1e970499af9bfba037844f570791c8f8306c822b Mon Sep 17 00:00:00 2001 From: Christian Richter Date: Wed, 3 Dec 2025 12:16:10 +0100 Subject: [PATCH 44/82] migrate oc init command from urfave/cli to spf13/cobra Signed-off-by: Christian Richter --- go.mod | 6 + go.sum | 14 + opencloud/pkg/command/init.go | 97 +- .../pelletier/go-toml/v2/.dockerignore | 2 + .../pelletier/go-toml/v2/.gitattributes | 4 + .../pelletier/go-toml/v2/.gitignore | 7 + .../pelletier/go-toml/v2/.golangci.toml | 84 + .../pelletier/go-toml/v2/.goreleaser.yaml | 127 + .../pelletier/go-toml/v2/CONTRIBUTING.md | 193 ++ .../pelletier/go-toml/v2/Dockerfile | 5 + .../github.com/pelletier/go-toml/v2/LICENSE | 22 + .../github.com/pelletier/go-toml/v2/README.md | 576 +++++ .../pelletier/go-toml/v2/SECURITY.md | 16 + vendor/github.com/pelletier/go-toml/v2/ci.sh | 284 +++ .../github.com/pelletier/go-toml/v2/decode.go | 550 +++++ vendor/github.com/pelletier/go-toml/v2/doc.go | 2 + .../github.com/pelletier/go-toml/v2/errors.go | 252 ++ .../go-toml/v2/internal/characters/ascii.go | 42 + .../go-toml/v2/internal/characters/utf8.go | 199 ++ .../go-toml/v2/internal/danger/danger.go | 65 + .../go-toml/v2/internal/danger/typeid.go | 23 + .../go-toml/v2/internal/tracker/key.go | 48 + .../go-toml/v2/internal/tracker/seen.go | 358 +++ .../go-toml/v2/internal/tracker/tracker.go | 1 + .../pelletier/go-toml/v2/localtime.go | 122 + .../pelletier/go-toml/v2/marshaler.go | 1133 +++++++++ .../github.com/pelletier/go-toml/v2/strict.go | 107 + .../github.com/pelletier/go-toml/v2/toml.abnf | 243 ++ .../github.com/pelletier/go-toml/v2/types.go | 14 + .../pelletier/go-toml/v2/unmarshaler.go | 1334 +++++++++++ .../pelletier/go-toml/v2/unstable/ast.go | 136 ++ .../pelletier/go-toml/v2/unstable/builder.go | 71 + .../pelletier/go-toml/v2/unstable/doc.go | 3 + .../pelletier/go-toml/v2/unstable/kind.go | 71 + .../pelletier/go-toml/v2/unstable/parser.go | 1245 ++++++++++ .../pelletier/go-toml/v2/unstable/scanner.go | 270 +++ .../go-toml/v2/unstable/unmarshaler.go | 7 + .../sagikazarmark/locafero/.editorconfig | 21 + .../sagikazarmark/locafero/.gitignore | 8 + .../sagikazarmark/locafero/.golangci.yaml | 37 + .../github.com/sagikazarmark/locafero/LICENSE | 19 + .../sagikazarmark/locafero/README.md | 37 + .../sagikazarmark/locafero/file_type.go | 32 + .../sagikazarmark/locafero/finder.go | 171 ++ .../sagikazarmark/locafero/flake.lock | 255 ++ .../sagikazarmark/locafero/flake.nix | 42 + .../github.com/sagikazarmark/locafero/glob.go | 5 + .../sagikazarmark/locafero/glob_windows.go | 8 + .../sagikazarmark/locafero/helpers.go | 41 + .../sagikazarmark/locafero/justfile | 14 + .../github.com/sourcegraph/conc/.golangci.yml | 11 + vendor/github.com/sourcegraph/conc/LICENSE | 21 + vendor/github.com/sourcegraph/conc/Makefile | 24 + vendor/github.com/sourcegraph/conc/README.md | 464 ++++ .../sourcegraph/conc/panics/panics.go | 102 + .../github.com/sourcegraph/conc/panics/try.go | 11 + .../sourcegraph/conc/pool/context_pool.go | 104 + .../sourcegraph/conc/pool/error_pool.go | 100 + .../github.com/sourcegraph/conc/pool/pool.go | 174 ++ .../conc/pool/result_context_pool.go | 85 + .../conc/pool/result_error_pool.go | 80 + .../sourcegraph/conc/pool/result_pool.go | 142 ++ .../github.com/sourcegraph/conc/waitgroup.go | 52 + vendor/github.com/spf13/cast/.editorconfig | 15 + vendor/github.com/spf13/cast/.gitignore | 25 + vendor/github.com/spf13/cast/.golangci.yaml | 39 + vendor/github.com/spf13/cast/LICENSE | 21 + vendor/github.com/spf13/cast/Makefile | 40 + vendor/github.com/spf13/cast/README.md | 79 + vendor/github.com/spf13/cast/alias.go | 69 + vendor/github.com/spf13/cast/basic.go | 131 ++ vendor/github.com/spf13/cast/cast.go | 84 + vendor/github.com/spf13/cast/indirect.go | 37 + vendor/github.com/spf13/cast/internal/time.go | 79 + .../cast/internal/timeformattype_string.go | 27 + vendor/github.com/spf13/cast/map.go | 212 ++ vendor/github.com/spf13/cast/number.go | 549 +++++ vendor/github.com/spf13/cast/slice.go | 106 + vendor/github.com/spf13/cast/time.go | 116 + vendor/github.com/spf13/cast/zz_generated.go | 261 +++ vendor/github.com/spf13/viper/.editorconfig | 21 + vendor/github.com/spf13/viper/.gitignore | 8 + vendor/github.com/spf13/viper/.golangci.yaml | 118 + vendor/github.com/spf13/viper/.yamlignore | 2 + vendor/github.com/spf13/viper/.yamllint.yaml | 6 + vendor/github.com/spf13/viper/LICENSE | 21 + vendor/github.com/spf13/viper/Makefile | 87 + vendor/github.com/spf13/viper/README.md | 931 ++++++++ .../github.com/spf13/viper/TROUBLESHOOTING.md | 32 + vendor/github.com/spf13/viper/UPGRADE.md | 147 ++ vendor/github.com/spf13/viper/encoding.go | 181 ++ vendor/github.com/spf13/viper/experimental.go | 8 + vendor/github.com/spf13/viper/file.go | 104 + vendor/github.com/spf13/viper/finder.go | 55 + vendor/github.com/spf13/viper/flags.go | 57 + vendor/github.com/spf13/viper/flake.lock | 255 ++ vendor/github.com/spf13/viper/flake.nix | 61 + .../viper/internal/encoding/dotenv/codec.go | 61 + .../internal/encoding/dotenv/map_utils.go | 41 + .../viper/internal/encoding/json/codec.go | 17 + .../viper/internal/encoding/toml/codec.go | 16 + .../viper/internal/encoding/yaml/codec.go | 14 + .../viper/internal/features/bind_struct.go | 5 + .../internal/features/bind_struct_default.go | 5 + .../spf13/viper/internal/features/finder.go | 5 + .../viper/internal/features/finder_default.go | 5 + vendor/github.com/spf13/viper/logger.go | 31 + vendor/github.com/spf13/viper/remote.go | 259 ++ vendor/github.com/spf13/viper/util.go | 211 ++ vendor/github.com/spf13/viper/viper.go | 2077 +++++++++++++++++ .../github.com/subosito/gotenv/.env.invalid | 1 + vendor/github.com/subosito/gotenv/.gitignore | 4 + .../github.com/subosito/gotenv/.golangci.yaml | 7 + .../github.com/subosito/gotenv/CHANGELOG.md | 105 + vendor/github.com/subosito/gotenv/LICENSE | 21 + vendor/github.com/subosito/gotenv/README.md | 129 + vendor/github.com/subosito/gotenv/gotenv.go | 409 ++++ vendor/modules.txt | 30 + 118 files changed, 17189 insertions(+), 41 deletions(-) create mode 100644 vendor/github.com/pelletier/go-toml/v2/.dockerignore create mode 100644 vendor/github.com/pelletier/go-toml/v2/.gitattributes create mode 100644 vendor/github.com/pelletier/go-toml/v2/.gitignore create mode 100644 vendor/github.com/pelletier/go-toml/v2/.golangci.toml create mode 100644 vendor/github.com/pelletier/go-toml/v2/.goreleaser.yaml create mode 100644 vendor/github.com/pelletier/go-toml/v2/CONTRIBUTING.md create mode 100644 vendor/github.com/pelletier/go-toml/v2/Dockerfile create mode 100644 vendor/github.com/pelletier/go-toml/v2/LICENSE create mode 100644 vendor/github.com/pelletier/go-toml/v2/README.md create mode 100644 vendor/github.com/pelletier/go-toml/v2/SECURITY.md create mode 100644 vendor/github.com/pelletier/go-toml/v2/ci.sh create mode 100644 vendor/github.com/pelletier/go-toml/v2/decode.go create mode 100644 vendor/github.com/pelletier/go-toml/v2/doc.go create mode 100644 vendor/github.com/pelletier/go-toml/v2/errors.go create mode 100644 vendor/github.com/pelletier/go-toml/v2/internal/characters/ascii.go create mode 100644 vendor/github.com/pelletier/go-toml/v2/internal/characters/utf8.go create mode 100644 vendor/github.com/pelletier/go-toml/v2/internal/danger/danger.go create mode 100644 vendor/github.com/pelletier/go-toml/v2/internal/danger/typeid.go create mode 100644 vendor/github.com/pelletier/go-toml/v2/internal/tracker/key.go create mode 100644 vendor/github.com/pelletier/go-toml/v2/internal/tracker/seen.go create mode 100644 vendor/github.com/pelletier/go-toml/v2/internal/tracker/tracker.go create mode 100644 vendor/github.com/pelletier/go-toml/v2/localtime.go create mode 100644 vendor/github.com/pelletier/go-toml/v2/marshaler.go create mode 100644 vendor/github.com/pelletier/go-toml/v2/strict.go create mode 100644 vendor/github.com/pelletier/go-toml/v2/toml.abnf create mode 100644 vendor/github.com/pelletier/go-toml/v2/types.go create mode 100644 vendor/github.com/pelletier/go-toml/v2/unmarshaler.go create mode 100644 vendor/github.com/pelletier/go-toml/v2/unstable/ast.go create mode 100644 vendor/github.com/pelletier/go-toml/v2/unstable/builder.go create mode 100644 vendor/github.com/pelletier/go-toml/v2/unstable/doc.go create mode 100644 vendor/github.com/pelletier/go-toml/v2/unstable/kind.go create mode 100644 vendor/github.com/pelletier/go-toml/v2/unstable/parser.go create mode 100644 vendor/github.com/pelletier/go-toml/v2/unstable/scanner.go create mode 100644 vendor/github.com/pelletier/go-toml/v2/unstable/unmarshaler.go create mode 100644 vendor/github.com/sagikazarmark/locafero/.editorconfig create mode 100644 vendor/github.com/sagikazarmark/locafero/.gitignore create mode 100644 vendor/github.com/sagikazarmark/locafero/.golangci.yaml create mode 100644 vendor/github.com/sagikazarmark/locafero/LICENSE create mode 100644 vendor/github.com/sagikazarmark/locafero/README.md create mode 100644 vendor/github.com/sagikazarmark/locafero/file_type.go create mode 100644 vendor/github.com/sagikazarmark/locafero/finder.go create mode 100644 vendor/github.com/sagikazarmark/locafero/flake.lock create mode 100644 vendor/github.com/sagikazarmark/locafero/flake.nix create mode 100644 vendor/github.com/sagikazarmark/locafero/glob.go create mode 100644 vendor/github.com/sagikazarmark/locafero/glob_windows.go create mode 100644 vendor/github.com/sagikazarmark/locafero/helpers.go create mode 100644 vendor/github.com/sagikazarmark/locafero/justfile create mode 100644 vendor/github.com/sourcegraph/conc/.golangci.yml create mode 100644 vendor/github.com/sourcegraph/conc/LICENSE create mode 100644 vendor/github.com/sourcegraph/conc/Makefile create mode 100644 vendor/github.com/sourcegraph/conc/README.md create mode 100644 vendor/github.com/sourcegraph/conc/panics/panics.go create mode 100644 vendor/github.com/sourcegraph/conc/panics/try.go create mode 100644 vendor/github.com/sourcegraph/conc/pool/context_pool.go create mode 100644 vendor/github.com/sourcegraph/conc/pool/error_pool.go create mode 100644 vendor/github.com/sourcegraph/conc/pool/pool.go create mode 100644 vendor/github.com/sourcegraph/conc/pool/result_context_pool.go create mode 100644 vendor/github.com/sourcegraph/conc/pool/result_error_pool.go create mode 100644 vendor/github.com/sourcegraph/conc/pool/result_pool.go create mode 100644 vendor/github.com/sourcegraph/conc/waitgroup.go create mode 100644 vendor/github.com/spf13/cast/.editorconfig create mode 100644 vendor/github.com/spf13/cast/.gitignore create mode 100644 vendor/github.com/spf13/cast/.golangci.yaml create mode 100644 vendor/github.com/spf13/cast/LICENSE create mode 100644 vendor/github.com/spf13/cast/Makefile create mode 100644 vendor/github.com/spf13/cast/README.md create mode 100644 vendor/github.com/spf13/cast/alias.go create mode 100644 vendor/github.com/spf13/cast/basic.go create mode 100644 vendor/github.com/spf13/cast/cast.go create mode 100644 vendor/github.com/spf13/cast/indirect.go create mode 100644 vendor/github.com/spf13/cast/internal/time.go create mode 100644 vendor/github.com/spf13/cast/internal/timeformattype_string.go create mode 100644 vendor/github.com/spf13/cast/map.go create mode 100644 vendor/github.com/spf13/cast/number.go create mode 100644 vendor/github.com/spf13/cast/slice.go create mode 100644 vendor/github.com/spf13/cast/time.go create mode 100644 vendor/github.com/spf13/cast/zz_generated.go create mode 100644 vendor/github.com/spf13/viper/.editorconfig create mode 100644 vendor/github.com/spf13/viper/.gitignore create mode 100644 vendor/github.com/spf13/viper/.golangci.yaml create mode 100644 vendor/github.com/spf13/viper/.yamlignore create mode 100644 vendor/github.com/spf13/viper/.yamllint.yaml create mode 100644 vendor/github.com/spf13/viper/LICENSE create mode 100644 vendor/github.com/spf13/viper/Makefile create mode 100644 vendor/github.com/spf13/viper/README.md create mode 100644 vendor/github.com/spf13/viper/TROUBLESHOOTING.md create mode 100644 vendor/github.com/spf13/viper/UPGRADE.md create mode 100644 vendor/github.com/spf13/viper/encoding.go create mode 100644 vendor/github.com/spf13/viper/experimental.go create mode 100644 vendor/github.com/spf13/viper/file.go create mode 100644 vendor/github.com/spf13/viper/finder.go create mode 100644 vendor/github.com/spf13/viper/flags.go create mode 100644 vendor/github.com/spf13/viper/flake.lock create mode 100644 vendor/github.com/spf13/viper/flake.nix create mode 100644 vendor/github.com/spf13/viper/internal/encoding/dotenv/codec.go create mode 100644 vendor/github.com/spf13/viper/internal/encoding/dotenv/map_utils.go create mode 100644 vendor/github.com/spf13/viper/internal/encoding/json/codec.go create mode 100644 vendor/github.com/spf13/viper/internal/encoding/toml/codec.go create mode 100644 vendor/github.com/spf13/viper/internal/encoding/yaml/codec.go create mode 100644 vendor/github.com/spf13/viper/internal/features/bind_struct.go create mode 100644 vendor/github.com/spf13/viper/internal/features/bind_struct_default.go create mode 100644 vendor/github.com/spf13/viper/internal/features/finder.go create mode 100644 vendor/github.com/spf13/viper/internal/features/finder_default.go create mode 100644 vendor/github.com/spf13/viper/logger.go create mode 100644 vendor/github.com/spf13/viper/remote.go create mode 100644 vendor/github.com/spf13/viper/util.go create mode 100644 vendor/github.com/spf13/viper/viper.go create mode 100644 vendor/github.com/subosito/gotenv/.env.invalid create mode 100644 vendor/github.com/subosito/gotenv/.gitignore create mode 100644 vendor/github.com/subosito/gotenv/.golangci.yaml create mode 100644 vendor/github.com/subosito/gotenv/CHANGELOG.md create mode 100644 vendor/github.com/subosito/gotenv/LICENSE create mode 100644 vendor/github.com/subosito/gotenv/README.md create mode 100644 vendor/github.com/subosito/gotenv/gotenv.go diff --git a/go.mod b/go.mod index f4dd437f0a..7193be1acc 100644 --- a/go.mod +++ b/go.mod @@ -78,6 +78,7 @@ require ( github.com/sirupsen/logrus v1.9.4-0.20230606125235-dd1b4c2e81af github.com/spf13/afero v1.15.0 github.com/spf13/cobra v1.10.1 + github.com/spf13/viper v1.21.0 github.com/stretchr/testify v1.11.1 github.com/test-go/testify v1.1.4 github.com/testcontainers/testcontainers-go v0.40.0 @@ -319,6 +320,7 @@ require ( github.com/pablodz/inotifywaitgo v0.0.9 // indirect github.com/patrickmn/go-cache v2.1.0+incompatible // indirect github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 // indirect + github.com/pelletier/go-toml/v2 v2.2.4 // indirect github.com/philhofer/fwd v1.2.0 // indirect github.com/pierrec/lz4/v4 v4.1.15 // indirect github.com/pjbgf/sha1cd v0.3.2 // indirect @@ -335,6 +337,7 @@ require ( github.com/russellhaering/goxmldsig v1.5.0 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd // indirect + github.com/sagikazarmark/locafero v0.11.0 // indirect github.com/samber/lo v1.51.0 // indirect github.com/samber/slog-common v0.19.0 // indirect github.com/samber/slog-zerolog/v2 v2.9.0 // indirect @@ -350,10 +353,13 @@ require ( github.com/shurcooL/httpfs v0.0.0-20230704072500-f1e31cf0ba5c // indirect github.com/shurcooL/vfsgen v0.0.0-20230704071429-0000e147ea92 // indirect github.com/skeema/knownhosts v1.3.0 // indirect + github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 // indirect github.com/spacewander/go-suffix-tree v0.0.0-20191010040751-0865e368c784 // indirect + github.com/spf13/cast v1.10.0 // indirect github.com/spf13/pflag v1.0.10 // indirect github.com/stretchr/objx v0.5.2 // indirect github.com/studio-b12/gowebdav v0.9.0 // indirect + github.com/subosito/gotenv v1.6.0 // indirect github.com/tchap/go-patricia/v2 v2.3.3 // indirect github.com/tidwall/match v1.1.1 // indirect github.com/tidwall/pretty v1.2.1 // indirect diff --git a/go.sum b/go.sum index 75e411b265..f44cded042 100644 --- a/go.sum +++ b/go.sum @@ -352,6 +352,8 @@ github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8 github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= github.com/foxcpp/go-mockdns v1.1.0 h1:jI0rD8M0wuYAxL7r/ynTrCQQq0BVqfB99Vgk7DlmewI= github.com/foxcpp/go-mockdns v1.1.0/go.mod h1:IhLeSFGed3mJIAXPH2aiRQB+kqz7oqu8ld2qVbOu7Wk= +github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= +github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= @@ -988,6 +990,8 @@ github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 h1:onHthvaw9LFnH4t2D github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58/go.mod h1:DXv8WO4yhMYhSNPKjeNKa5WY9YCIEBRbNzFFPJbWO6Y= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc= +github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= +github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= github.com/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5 h1:Ii+DKncOVM8Cu1Hc+ETb5K+23HdAMvESYE3ZJ5b5cMI= github.com/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5/go.mod h1:iIss55rKnNBTvrwdmkUpLnDpZoAHvWaiq5+iMmen4AE= github.com/philhofer/fwd v1.2.0 h1:e6DnBTl7vGY+Gz322/ASL4Gyp1FspeMvx1RNDoToZuM= @@ -1093,6 +1097,8 @@ github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd h1:CmH9+J6ZSsIjUK github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd/go.mod h1:hPqNNc0+uJM6H+SuU8sEs5K5IQeKccPqeSjfgcKGgPk= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/sacloud/libsacloud v1.36.2/go.mod h1:P7YAOVmnIn3DKHqCZcUKYUXmSwGBm3yS7IBEjKVSrjg= +github.com/sagikazarmark/locafero v0.11.0 h1:1iurJgmM9G3PA/I+wWYIOw/5SyBtxapeHDcg+AAIFXc= +github.com/sagikazarmark/locafero v0.11.0/go.mod h1:nVIGvgyzw595SUSUE6tvCp3YYTeHs15MvlmU87WwIik= github.com/samber/lo v1.51.0 h1:kysRYLbHy/MB7kQZf5DSN50JHmMsNEdeY24VzJFu7wI= github.com/samber/lo v1.51.0/go.mod h1:4+MXEGsJzbKGaUEQFKBq2xtfuznW9oz/WrgyzMzRoM0= github.com/samber/slog-common v0.19.0 h1:fNcZb8B2uOLooeYwFpAlKjkQTUafdjfqKcwcC89G9YI= @@ -1142,6 +1148,8 @@ github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:s github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/smartystreets/gunit v1.0.4/go.mod h1:EH5qMBab2UclzXUcpR8b93eHsIlp9u+pDQIRp5DZNzQ= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= +github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 h1:+jumHNA0Wrelhe64i8F6HNlS8pkoyMv5sreGx2Ry5Rw= +github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8/go.mod h1:3n1Cwaq1E1/1lhQhtRK2ts/ZwZEhjcQeJQ1RuC6Q/8U= github.com/spacewander/go-suffix-tree v0.0.0-20191010040751-0865e368c784 h1:0jjO3HdJfOn6gYHD/ZNZh0LLMxEAqkYX7xoDPQReEgs= github.com/spacewander/go-suffix-tree v0.0.0-20191010040751-0865e368c784/go.mod h1:ff/5myEGgtsAwf26goQCO905GrEm5ugEZSd6OWTsrhM= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= @@ -1151,6 +1159,8 @@ github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I= github.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY= +github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo= github.com/spf13/cobra v1.1.1/go.mod h1:WnodtKOvamDL/PwE2M4iKs8aMDBZ5Q5klgD3qfVJQMI= github.com/spf13/cobra v1.10.1 h1:lJeBwCfmrnXthfAupyUTzJ/J4Nc1RsHC/mSRU2dll/s= github.com/spf13/cobra v1.10.1/go.mod h1:7SmJGaTHFVBY0jW4NXGluQoLvhqFQM+6XSKD+P4XaB0= @@ -1163,6 +1173,8 @@ github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= github.com/spf13/viper v1.7.1/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= +github.com/spf13/viper v1.21.0 h1:x5S+0EU27Lbphp4UKm1C+1oQO+rKx36vfCoaVebLFSU= +github.com/spf13/viper v1.21.0/go.mod h1:P0lhsswPGWD/1lZJ9ny3fYnVqxiegrlNrEmgLjbTCAY= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.3.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= @@ -1184,6 +1196,8 @@ github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/stvp/go-udp-testing v0.0.0-20201019212854-469649b16807/go.mod h1:7jxmlfBCDBXRzr0eAQJ48XC1hBu1np4CS5+cHEYfwpc= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= +github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= +github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= github.com/tchap/go-patricia/v2 v2.3.3 h1:xfNEsODumaEcCcY3gI0hYPZ/PcpVv5ju6RMAhgwZDDc= github.com/tchap/go-patricia/v2 v2.3.3/go.mod h1:VZRHKAb53DLaG+nA9EaYYiaEx6YztwDlLElMsnSHD4k= github.com/test-go/testify v1.1.4 h1:Tf9lntrKUMHiXQ07qBScBTSA0dhYQlu83hswqelv1iE= diff --git a/opencloud/pkg/command/init.go b/opencloud/pkg/command/init.go index 761c502cce..d381dd993b 100644 --- a/opencloud/pkg/command/init.go +++ b/opencloud/pkg/command/init.go @@ -11,49 +11,18 @@ import ( "github.com/opencloud-eu/opencloud/opencloud/pkg/register" "github.com/opencloud-eu/opencloud/pkg/config" "github.com/opencloud-eu/opencloud/pkg/config/defaults" - cli "github.com/urfave/cli/v2" + + "github.com/spf13/cobra" + "github.com/spf13/viper" ) // InitCommand is the entrypoint for the init command -func InitCommand(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: "init", - Usage: "initialise an OpenCloud config", - Flags: []cli.Flag{ - &cli.StringFlag{ - Name: "insecure", - EnvVars: []string{"OC_INSECURE"}, - Value: "ask", - Usage: "Allow insecure OpenCloud config", - }, - &cli.BoolFlag{ - Name: "diff", - Aliases: []string{"d"}, - Usage: "Show the difference between the current config and the new one", - Value: false, - }, - &cli.BoolFlag{ - Name: "force-overwrite", - Aliases: []string{"f"}, - EnvVars: []string{"OC_FORCE_CONFIG_OVERWRITE"}, - Value: false, - Usage: "Force overwrite existing config file", - }, - &cli.StringFlag{ - Name: "config-path", - Value: defaults.BaseConfigPath(), - Usage: "Config path for the OpenCloud runtime", - EnvVars: []string{"OC_CONFIG_DIR", "OC_BASE_DATA_PATH"}, - }, - &cli.StringFlag{ - Name: "admin-password", - Aliases: []string{"ap"}, - EnvVars: []string{"ADMIN_PASSWORD", "IDM_ADMIN_PASSWORD"}, - Usage: "Set admin password instead of using a random generated one", - }, - }, - Action: func(c *cli.Context) error { - insecureFlag := c.String("insecure") +func InitCommand(cfg *config.Config) *cobra.Command { + initCmd := &cobra.Command{ + Use: "init", + Short: "initialise an OpenCloud config", + RunE: func(cmd *cobra.Command, args []string) error { + insecureFlag := cmd.Flag("insecure").Value.String() insecure := false if insecureFlag == "ask" { answer := strings.ToLower(stringPrompt("Do you want to configure OpenCloud with certificate checking disabled?\n This is not recommended for public instances! [yes | no = default]")) @@ -63,13 +32,59 @@ func InitCommand(cfg *config.Config) *cli.Command { } else if insecureFlag == strings.ToLower("true") || insecureFlag == strings.ToLower("yes") || insecureFlag == strings.ToLower("y") { insecure = true } - err := ocinit.CreateConfig(insecure, c.Bool("force-overwrite"), c.Bool("diff"), c.String("config-path"), c.String("admin-password")) + err := ocinit.CreateConfig(insecure, cmd.Flag("force-overwrite").Changed, + cmd.Flag("diff").Changed, cmd.Flag("config-path").Value.String(), + cmd.Flag("admin-password").Value.String()) if err != nil { log.Fatalf("Could not create config: %s", err) } return nil }, } + initCmd.Flags().String("insecure", "ask", "Allow insecure OpenCloud config") + err := viper.BindEnv("insecure", "OC_INSECURE") + if err != nil { + log.Fatalf("Could not bind environment variable OC_INSECURE: %s", err) + } + err = viper.BindPFlag("insecure", initCmd.Flags().Lookup("insecure")) + if err != nil { + log.Fatalf("Could not bind flag OC_INSECURE: %s", err) + } + + initCmd.Flags().BoolP("diff", "d", false, "Show the difference between the current config and the new one") + + initCmd.Flags().BoolP("force-overwrite", "f", false, "Force overwrite existing config file") + err = viper.BindEnv("force-overwrite", "OC_FORCE_CONFIG_OVERWRITE") + if err != nil { + log.Fatalf("Could not bind environment variable OC_FORCE_CONFIG_OVERWRITE: %s", err) + } + err = viper.BindPFlag("force-overwrite", initCmd.Flags().Lookup("force-overwrite")) + if err != nil { + log.Fatalf("Could not bind flag OC_FORCE_CONFIG_OVERWRITE: %s", err) + } + + initCmd.Flags().String("config-path", defaults.BaseConfigPath(), "Config path for the OpenCloud runtime") + err = viper.BindEnv("config-path", "OC_CONFIG_DIR") + if err != nil { + log.Fatalf("Could not bind environment variable OC_CONFIG_DIR: %s", err) + } + err = viper.BindEnv("config-path", "OC_BASE_DATA_PATH") + if err != nil { + log.Fatalf("Could not bind environment variable OC_BASE_DATA_PATH: %s", err) + } + err = viper.BindPFlag("config-path", initCmd.Flags().Lookup("config-path")) + + initCmd.Flags().String("admin-password", "", "Set admin password instead of using a random generated one") + err = viper.BindEnv("admin-password", "ADMIN_PASSWORD") + if err != nil { + log.Fatalf("Could not bind environment variable ADMIN_PASSWORD: %s", err) + } + err = viper.BindEnv("admin-password", "IDM_ADMIN_PASSWORD") + if err != nil { + log.Fatalf("Could not bind environment variable IDM_ADMIN_PASSWORD: %s", err) + } + err = viper.BindPFlag("admin-password", initCmd.Flags().Lookup("admin-password")) + return initCmd } func init() { diff --git a/vendor/github.com/pelletier/go-toml/v2/.dockerignore b/vendor/github.com/pelletier/go-toml/v2/.dockerignore new file mode 100644 index 0000000000..7b5883475d --- /dev/null +++ b/vendor/github.com/pelletier/go-toml/v2/.dockerignore @@ -0,0 +1,2 @@ +cmd/tomll/tomll +cmd/tomljson/tomljson diff --git a/vendor/github.com/pelletier/go-toml/v2/.gitattributes b/vendor/github.com/pelletier/go-toml/v2/.gitattributes new file mode 100644 index 0000000000..34a0a21a36 --- /dev/null +++ b/vendor/github.com/pelletier/go-toml/v2/.gitattributes @@ -0,0 +1,4 @@ +* text=auto + +benchmark/benchmark.toml text eol=lf +testdata/** text eol=lf diff --git a/vendor/github.com/pelletier/go-toml/v2/.gitignore b/vendor/github.com/pelletier/go-toml/v2/.gitignore new file mode 100644 index 0000000000..4b7c4eda3a --- /dev/null +++ b/vendor/github.com/pelletier/go-toml/v2/.gitignore @@ -0,0 +1,7 @@ +test_program/test_program_bin +fuzz/ +cmd/tomll/tomll +cmd/tomljson/tomljson +cmd/tomltestgen/tomltestgen +dist +tests/ diff --git a/vendor/github.com/pelletier/go-toml/v2/.golangci.toml b/vendor/github.com/pelletier/go-toml/v2/.golangci.toml new file mode 100644 index 0000000000..067db55174 --- /dev/null +++ b/vendor/github.com/pelletier/go-toml/v2/.golangci.toml @@ -0,0 +1,84 @@ +[service] +golangci-lint-version = "1.39.0" + +[linters-settings.wsl] +allow-assign-and-anything = true + +[linters-settings.exhaustive] +default-signifies-exhaustive = true + +[linters] +disable-all = true +enable = [ + "asciicheck", + "bodyclose", + "cyclop", + "deadcode", + "depguard", + "dogsled", + "dupl", + "durationcheck", + "errcheck", + "errorlint", + "exhaustive", + # "exhaustivestruct", + "exportloopref", + "forbidigo", + # "forcetypeassert", + "funlen", + "gci", + # "gochecknoglobals", + "gochecknoinits", + "gocognit", + "goconst", + "gocritic", + "gocyclo", + "godot", + "godox", + # "goerr113", + "gofmt", + "gofumpt", + "goheader", + "goimports", + "golint", + "gomnd", + # "gomoddirectives", + "gomodguard", + "goprintffuncname", + "gosec", + "gosimple", + "govet", + # "ifshort", + "importas", + "ineffassign", + "lll", + "makezero", + "misspell", + "nakedret", + "nestif", + "nilerr", + # "nlreturn", + "noctx", + "nolintlint", + #"paralleltest", + "prealloc", + "predeclared", + "revive", + "rowserrcheck", + "sqlclosecheck", + "staticcheck", + "structcheck", + "stylecheck", + # "testpackage", + "thelper", + "tparallel", + "typecheck", + "unconvert", + "unparam", + "unused", + "varcheck", + "wastedassign", + "whitespace", + # "wrapcheck", + # "wsl" +] diff --git a/vendor/github.com/pelletier/go-toml/v2/.goreleaser.yaml b/vendor/github.com/pelletier/go-toml/v2/.goreleaser.yaml new file mode 100644 index 0000000000..47f0f59142 --- /dev/null +++ b/vendor/github.com/pelletier/go-toml/v2/.goreleaser.yaml @@ -0,0 +1,127 @@ +version: 2 +before: + hooks: + - go mod tidy + - go fmt ./... + - go test ./... +builds: + - id: tomll + main: ./cmd/tomll + binary: tomll + env: + - CGO_ENABLED=0 + flags: + - -trimpath + ldflags: + - -X main.version={{.Version}} -X main.commit={{.Commit}} -X main.date={{.CommitDate}} + mod_timestamp: '{{ .CommitTimestamp }}' + targets: + - linux_amd64 + - linux_arm64 + - linux_arm + - linux_riscv64 + - windows_amd64 + - windows_arm64 + - windows_arm + - darwin_amd64 + - darwin_arm64 + - id: tomljson + main: ./cmd/tomljson + binary: tomljson + env: + - CGO_ENABLED=0 + flags: + - -trimpath + ldflags: + - -X main.version={{.Version}} -X main.commit={{.Commit}} -X main.date={{.CommitDate}} + mod_timestamp: '{{ .CommitTimestamp }}' + targets: + - linux_amd64 + - linux_arm64 + - linux_arm + - linux_riscv64 + - windows_amd64 + - windows_arm64 + - windows_arm + - darwin_amd64 + - darwin_arm64 + - id: jsontoml + main: ./cmd/jsontoml + binary: jsontoml + env: + - CGO_ENABLED=0 + flags: + - -trimpath + ldflags: + - -X main.version={{.Version}} -X main.commit={{.Commit}} -X main.date={{.CommitDate}} + mod_timestamp: '{{ .CommitTimestamp }}' + targets: + - linux_amd64 + - linux_arm64 + - linux_riscv64 + - linux_arm + - windows_amd64 + - windows_arm64 + - windows_arm + - darwin_amd64 + - darwin_arm64 +universal_binaries: + - id: tomll + replace: true + name_template: tomll + - id: tomljson + replace: true + name_template: tomljson + - id: jsontoml + replace: true + name_template: jsontoml +archives: +- id: jsontoml + format: tar.xz + builds: + - jsontoml + files: + - none* + name_template: "{{ .Binary }}_{{.Version}}_{{ .Os }}_{{ .Arch }}" +- id: tomljson + format: tar.xz + builds: + - tomljson + files: + - none* + name_template: "{{ .Binary }}_{{.Version}}_{{ .Os }}_{{ .Arch }}" +- id: tomll + format: tar.xz + builds: + - tomll + files: + - none* + name_template: "{{ .Binary }}_{{.Version}}_{{ .Os }}_{{ .Arch }}" +dockers: + - id: tools + goos: linux + goarch: amd64 + ids: + - jsontoml + - tomljson + - tomll + image_templates: + - "ghcr.io/pelletier/go-toml:latest" + - "ghcr.io/pelletier/go-toml:{{ .Tag }}" + - "ghcr.io/pelletier/go-toml:v{{ .Major }}" + skip_push: false +checksum: + name_template: 'sha256sums.txt' +snapshot: + version_template: "{{ incpatch .Version }}-next" +release: + github: + owner: pelletier + name: go-toml + draft: true + prerelease: auto + mode: replace +changelog: + use: github-native +announce: + skip: true diff --git a/vendor/github.com/pelletier/go-toml/v2/CONTRIBUTING.md b/vendor/github.com/pelletier/go-toml/v2/CONTRIBUTING.md new file mode 100644 index 0000000000..96ecf9e2b3 --- /dev/null +++ b/vendor/github.com/pelletier/go-toml/v2/CONTRIBUTING.md @@ -0,0 +1,193 @@ +# Contributing + +Thank you for your interest in go-toml! We appreciate you considering +contributing to go-toml! + +The main goal is the project is to provide an easy-to-use and efficient TOML +implementation for Go that gets the job done and gets out of your way – dealing +with TOML is probably not the central piece of your project. + +As the single maintainer of go-toml, time is scarce. All help, big or small, is +more than welcomed! + +## Ask questions + +Any question you may have, somebody else might have it too. Always feel free to +ask them on the [discussion board][discussions]. We will try to answer them as +clearly and quickly as possible, time permitting. + +Asking questions also helps us identify areas where the documentation needs +improvement, or new features that weren't envisioned before. Sometimes, a +seemingly innocent question leads to the fix of a bug. Don't hesitate and ask +away! + +[discussions]: https://github.com/pelletier/go-toml/discussions + +## Improve the documentation + +The best way to share your knowledge and experience with go-toml is to improve +the documentation. Fix a typo, clarify an interface, add an example, anything +goes! + +The documentation is present in the [README][readme] and thorough the source +code. On release, it gets updated on [pkg.go.dev][pkg.go.dev]. To make a change +to the documentation, create a pull request with your proposed changes. For +simple changes like that, the easiest way to go is probably the "Fork this +project and edit the file" button on Github, displayed at the top right of the +file. Unless it's a trivial change (for example a typo), provide a little bit of +context in your pull request description or commit message. + +## Report a bug + +Found a bug! Sorry to hear that :(. Help us and other track them down and fix by +reporting it. [File a new bug report][bug-report] on the [issues +tracker][issues-tracker]. The template should provide enough guidance on what to +include. When in doubt: add more details! By reducing ambiguity and providing +more information, it decreases back and forth and saves everyone time. + +## Code changes + +Want to contribute a patch? Very happy to hear that! + +First, some high-level rules: + +- A short proposal with some POC code is better than a lengthy piece of text + with no code. Code speaks louder than words. That being said, bigger changes + should probably start with a [discussion][discussions]. +- No backward-incompatible patch will be accepted unless discussed. Sometimes + it's hard, but we try not to break people's programs unless we absolutely have + to. +- If you are writing a new feature or extending an existing one, make sure to + write some documentation. +- Bug fixes need to be accompanied with regression tests. +- New code needs to be tested. +- Your commit messages need to explain why the change is needed, even if already + included in the PR description. + +It does sound like a lot, but those best practices are here to save time overall +and continuously improve the quality of the project, which is something everyone +benefits from. + +### Get started + +The fairly standard code contribution process looks like that: + +1. [Fork the project][fork]. +2. Make your changes, commit on any branch you like. +3. [Open up a pull request][pull-request] +4. Review, potential ask for changes. +5. Merge. + +Feel free to ask for help! You can create draft pull requests to gather +some early feedback! + +### Run the tests + +You can run tests for go-toml using Go's test tool: `go test -race ./...`. + +During the pull request process, all tests will be ran on Linux, Windows, and +MacOS on the last two versions of Go. + +However, given GitHub's new policy to _not_ run Actions on pull requests until a +maintainer clicks on button, it is highly recommended that you run them locally +as you make changes. + +### Check coverage + +We use `go tool cover` to compute test coverage. Most code editors have a way to +run and display code coverage, but at the end of the day, we do this: + +``` +go test -covermode=atomic -coverprofile=coverage.out +go tool cover -func=coverage.out +``` + +and verify that the overall percentage of tested code does not go down. This is +a requirement. As a rule of thumb, all lines of code touched by your changes +should be covered. On Unix you can use `./ci.sh coverage -d v2` to check if your +code lowers the coverage. + +### Verify performance + +Go-toml aims to stay efficient. We rely on a set of scenarios executed with Go's +builtin benchmark systems. Because of their noisy nature, containers provided by +Github Actions cannot be reliably used for benchmarking. As a result, you are +responsible for checking that your changes do not incur a performance penalty. +You can run their following to execute benchmarks: + +``` +go test ./... -bench=. -count=10 +``` + +Benchmark results should be compared against each other with +[benchstat][benchstat]. Typical flow looks like this: + +1. On the `v2` branch, run `go test ./... -bench=. -count 10` and save output to + a file (for example `old.txt`). +2. Make some code changes. +3. Run `go test ....` again, and save the output to an other file (for example + `new.txt`). +4. Run `benchstat old.txt new.txt` to check that time/op does not go up in any + test. + +On Unix you can use `./ci.sh benchmark -d v2` to verify how your code impacts +performance. + +It is highly encouraged to add the benchstat results to your pull request +description. Pull requests that lower performance will receive more scrutiny. + +[benchstat]: https://pkg.go.dev/golang.org/x/perf/cmd/benchstat + +### Style + +Try to look around and follow the same format and structure as the rest of the +code. We enforce using `go fmt` on the whole code base. + +--- + +## Maintainers-only + +### Merge pull request + +Checklist: + +- Passing CI. +- Does not introduce backward-incompatible changes (unless discussed). +- Has relevant doc changes. +- Benchstat does not show performance regression. +- Pull request is [labeled appropriately][pr-labels]. +- Title will be understandable in the changelog. + +1. Merge using "squash and merge". +2. Make sure to edit the commit message to keep all the useful information + nice and clean. +3. Make sure the commit title is clear and contains the PR number (#123). + +### New release + +1. Decide on the next version number. Use semver. Review commits since last + version to assess. +2. Tag release. For example: +``` +git checkout v2 +git pull +git tag v2.2.0 +git push --tags +``` +3. CI automatically builds a draft Github release. Review it and edit as + necessary. Look for "Other changes". That would indicate a pull request not + labeled properly. Tweak labels and pull request titles until changelog looks + good for users. +4. Check "create discussion" box, in the "Releases" category. +5. If new version is an alpha or beta only, check pre-release box. + + +[issues-tracker]: https://github.com/pelletier/go-toml/issues +[bug-report]: https://github.com/pelletier/go-toml/issues/new?template=bug_report.md +[pkg.go.dev]: https://pkg.go.dev/github.com/pelletier/go-toml +[readme]: ./README.md +[fork]: https://help.github.com/articles/fork-a-repo +[pull-request]: https://help.github.com/en/articles/creating-a-pull-request +[new-release]: https://github.com/pelletier/go-toml/releases/new +[gh]: https://github.com/cli/cli +[pr-labels]: https://github.com/pelletier/go-toml/blob/v2/.github/release.yml diff --git a/vendor/github.com/pelletier/go-toml/v2/Dockerfile b/vendor/github.com/pelletier/go-toml/v2/Dockerfile new file mode 100644 index 0000000000..b9e9332379 --- /dev/null +++ b/vendor/github.com/pelletier/go-toml/v2/Dockerfile @@ -0,0 +1,5 @@ +FROM scratch +ENV PATH "$PATH:/bin" +COPY tomll /bin/tomll +COPY tomljson /bin/tomljson +COPY jsontoml /bin/jsontoml diff --git a/vendor/github.com/pelletier/go-toml/v2/LICENSE b/vendor/github.com/pelletier/go-toml/v2/LICENSE new file mode 100644 index 0000000000..991e2ae966 --- /dev/null +++ b/vendor/github.com/pelletier/go-toml/v2/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +go-toml v2 +Copyright (c) 2021 - 2023 Thomas Pelletier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/github.com/pelletier/go-toml/v2/README.md b/vendor/github.com/pelletier/go-toml/v2/README.md new file mode 100644 index 0000000000..0755e55642 --- /dev/null +++ b/vendor/github.com/pelletier/go-toml/v2/README.md @@ -0,0 +1,576 @@ +# go-toml v2 + +Go library for the [TOML](https://toml.io/en/) format. + +This library supports [TOML v1.0.0](https://toml.io/en/v1.0.0). + +[🐞 Bug Reports](https://github.com/pelletier/go-toml/issues) + +[💬 Anything else](https://github.com/pelletier/go-toml/discussions) + +## Documentation + +Full API, examples, and implementation notes are available in the Go +documentation. + +[![Go Reference](https://pkg.go.dev/badge/github.com/pelletier/go-toml/v2.svg)](https://pkg.go.dev/github.com/pelletier/go-toml/v2) + +## Import + +```go +import "github.com/pelletier/go-toml/v2" +``` + +See [Modules](#Modules). + +## Features + +### Stdlib behavior + +As much as possible, this library is designed to behave similarly as the +standard library's `encoding/json`. + +### Performance + +While go-toml favors usability, it is written with performance in mind. Most +operations should not be shockingly slow. See [benchmarks](#benchmarks). + +### Strict mode + +`Decoder` can be set to "strict mode", which makes it error when some parts of +the TOML document was not present in the target structure. This is a great way +to check for typos. [See example in the documentation][strict]. + +[strict]: https://pkg.go.dev/github.com/pelletier/go-toml/v2#example-Decoder.DisallowUnknownFields + +### Contextualized errors + +When most decoding errors occur, go-toml returns [`DecodeError`][decode-err], +which contains a human readable contextualized version of the error. For +example: + +``` +1| [server] +2| path = 100 + | ~~~ cannot decode TOML integer into struct field toml_test.Server.Path of type string +3| port = 50 +``` + +[decode-err]: https://pkg.go.dev/github.com/pelletier/go-toml/v2#DecodeError + +### Local date and time support + +TOML supports native [local date/times][ldt]. It allows to represent a given +date, time, or date-time without relation to a timezone or offset. To support +this use-case, go-toml provides [`LocalDate`][tld], [`LocalTime`][tlt], and +[`LocalDateTime`][tldt]. Those types can be transformed to and from `time.Time`, +making them convenient yet unambiguous structures for their respective TOML +representation. + +[ldt]: https://toml.io/en/v1.0.0#local-date-time +[tld]: https://pkg.go.dev/github.com/pelletier/go-toml/v2#LocalDate +[tlt]: https://pkg.go.dev/github.com/pelletier/go-toml/v2#LocalTime +[tldt]: https://pkg.go.dev/github.com/pelletier/go-toml/v2#LocalDateTime + +### Commented config + +Since TOML is often used for configuration files, go-toml can emit documents +annotated with [comments and commented-out values][comments-example]. For +example, it can generate the following file: + +```toml +# Host IP to connect to. +host = '127.0.0.1' +# Port of the remote server. +port = 4242 + +# Encryption parameters (optional) +# [TLS] +# cipher = 'AEAD-AES128-GCM-SHA256' +# version = 'TLS 1.3' +``` + +[comments-example]: https://pkg.go.dev/github.com/pelletier/go-toml/v2#example-Marshal-Commented + +## Getting started + +Given the following struct, let's see how to read it and write it as TOML: + +```go +type MyConfig struct { + Version int + Name string + Tags []string +} +``` + +### Unmarshaling + +[`Unmarshal`][unmarshal] reads a TOML document and fills a Go structure with its +content. For example: + +```go +doc := ` +version = 2 +name = "go-toml" +tags = ["go", "toml"] +` + +var cfg MyConfig +err := toml.Unmarshal([]byte(doc), &cfg) +if err != nil { + panic(err) +} +fmt.Println("version:", cfg.Version) +fmt.Println("name:", cfg.Name) +fmt.Println("tags:", cfg.Tags) + +// Output: +// version: 2 +// name: go-toml +// tags: [go toml] +``` + +[unmarshal]: https://pkg.go.dev/github.com/pelletier/go-toml/v2#Unmarshal + +### Marshaling + +[`Marshal`][marshal] is the opposite of Unmarshal: it represents a Go structure +as a TOML document: + +```go +cfg := MyConfig{ + Version: 2, + Name: "go-toml", + Tags: []string{"go", "toml"}, +} + +b, err := toml.Marshal(cfg) +if err != nil { + panic(err) +} +fmt.Println(string(b)) + +// Output: +// Version = 2 +// Name = 'go-toml' +// Tags = ['go', 'toml'] +``` + +[marshal]: https://pkg.go.dev/github.com/pelletier/go-toml/v2#Marshal + +## Unstable API + +This API does not yet follow the backward compatibility guarantees of this +library. They provide early access to features that may have rough edges or an +API subject to change. + +### Parser + +Parser is the unstable API that allows iterative parsing of a TOML document at +the AST level. See https://pkg.go.dev/github.com/pelletier/go-toml/v2/unstable. + +## Benchmarks + +Execution time speedup compared to other Go TOML libraries: + + + + + + + + + + + + + +
Benchmarkgo-toml v1BurntSushi/toml
Marshal/HugoFrontMatter-21.9x2.2x
Marshal/ReferenceFile/map-21.7x2.1x
Marshal/ReferenceFile/struct-22.2x3.0x
Unmarshal/HugoFrontMatter-22.9x2.7x
Unmarshal/ReferenceFile/map-22.6x2.7x
Unmarshal/ReferenceFile/struct-24.6x5.1x
+
See more +

The table above has the results of the most common use-cases. The table below +contains the results of all benchmarks, including unrealistic ones. It is +provided for completeness.

+ + + + + + + + + + + + + + + + + + +
Benchmarkgo-toml v1BurntSushi/toml
Marshal/SimpleDocument/map-21.8x2.7x
Marshal/SimpleDocument/struct-22.7x3.8x
Unmarshal/SimpleDocument/map-23.8x3.0x
Unmarshal/SimpleDocument/struct-25.6x4.1x
UnmarshalDataset/example-23.0x3.2x
UnmarshalDataset/code-22.3x2.9x
UnmarshalDataset/twitter-22.6x2.7x
UnmarshalDataset/citm_catalog-22.2x2.3x
UnmarshalDataset/canada-21.8x1.5x
UnmarshalDataset/config-24.1x2.9x
geomean2.7x2.8x
+

This table can be generated with ./ci.sh benchmark -a -html.

+
+ +## Modules + +go-toml uses Go's standard modules system. + +Installation instructions: + +- Go ≥ 1.16: Nothing to do. Use the import in your code. The `go` command deals + with it automatically. +- Go ≥ 1.13: `GO111MODULE=on go get github.com/pelletier/go-toml/v2`. + +In case of trouble: [Go Modules FAQ][mod-faq]. + +[mod-faq]: https://github.com/golang/go/wiki/Modules#why-does-installing-a-tool-via-go-get-fail-with-error-cannot-find-main-module + +## Tools + +Go-toml provides three handy command line tools: + + * `tomljson`: Reads a TOML file and outputs its JSON representation. + + ``` + $ go install github.com/pelletier/go-toml/v2/cmd/tomljson@latest + $ tomljson --help + ``` + + * `jsontoml`: Reads a JSON file and outputs a TOML representation. + + ``` + $ go install github.com/pelletier/go-toml/v2/cmd/jsontoml@latest + $ jsontoml --help + ``` + + * `tomll`: Lints and reformats a TOML file. + + ``` + $ go install github.com/pelletier/go-toml/v2/cmd/tomll@latest + $ tomll --help + ``` + +### Docker image + +Those tools are also available as a [Docker image][docker]. For example, to use +`tomljson`: + +``` +docker run -i ghcr.io/pelletier/go-toml:v2 tomljson < example.toml +``` + +Multiple versions are available on [ghcr.io][docker]. + +[docker]: https://github.com/pelletier/go-toml/pkgs/container/go-toml + +## Migrating from v1 + +This section describes the differences between v1 and v2, with some pointers on +how to get the original behavior when possible. + +### Decoding / Unmarshal + +#### Automatic field name guessing + +When unmarshaling to a struct, if a key in the TOML document does not exactly +match the name of a struct field or any of the `toml`-tagged field, v1 tries +multiple variations of the key ([code][v1-keys]). + +V2 instead does a case-insensitive matching, like `encoding/json`. + +This could impact you if you are relying on casing to differentiate two fields, +and one of them is a not using the `toml` struct tag. The recommended solution +is to be specific about tag names for those fields using the `toml` struct tag. + +[v1-keys]: https://github.com/pelletier/go-toml/blob/a2e52561804c6cd9392ebf0048ca64fe4af67a43/marshal.go#L775-L781 + +#### Ignore preexisting value in interface + +When decoding into a non-nil `interface{}`, go-toml v1 uses the type of the +element in the interface to decode the object. For example: + +```go +type inner struct { + B interface{} +} +type doc struct { + A interface{} +} + +d := doc{ + A: inner{ + B: "Before", + }, +} + +data := ` +[A] +B = "After" +` + +toml.Unmarshal([]byte(data), &d) +fmt.Printf("toml v1: %#v\n", d) + +// toml v1: main.doc{A:main.inner{B:"After"}} +``` + +In this case, field `A` is of type `interface{}`, containing a `inner` struct. +V1 sees that type and uses it when decoding the object. + +When decoding an object into an `interface{}`, V2 instead disregards whatever +value the `interface{}` may contain and replaces it with a +`map[string]interface{}`. With the same data structure as above, here is what +the result looks like: + +```go +toml.Unmarshal([]byte(data), &d) +fmt.Printf("toml v2: %#v\n", d) + +// toml v2: main.doc{A:map[string]interface {}{"B":"After"}} +``` + +This is to match `encoding/json`'s behavior. There is no way to make the v2 +decoder behave like v1. + +#### Values out of array bounds ignored + +When decoding into an array, v1 returns an error when the number of elements +contained in the doc is superior to the capacity of the array. For example: + +```go +type doc struct { + A [2]string +} +d := doc{} +err := toml.Unmarshal([]byte(`A = ["one", "two", "many"]`), &d) +fmt.Println(err) + +// (1, 1): unmarshal: TOML array length (3) exceeds destination array length (2) +``` + +In the same situation, v2 ignores the last value: + +```go +err := toml.Unmarshal([]byte(`A = ["one", "two", "many"]`), &d) +fmt.Println("err:", err, "d:", d) +// err: d: {[one two]} +``` + +This is to match `encoding/json`'s behavior. There is no way to make the v2 +decoder behave like v1. + +#### Support for `toml.Unmarshaler` has been dropped + +This method was not widely used, poorly defined, and added a lot of complexity. +A similar effect can be achieved by implementing the `encoding.TextUnmarshaler` +interface and use strings. + +#### Support for `default` struct tag has been dropped + +This feature adds complexity and a poorly defined API for an effect that can be +accomplished outside of the library. + +It does not seem like other format parsers in Go support that feature (the +project referenced in the original ticket #202 has not been updated since 2017). +Given that go-toml v2 should not touch values not in the document, the same +effect can be achieved by pre-filling the struct with defaults (libraries like +[go-defaults][go-defaults] can help). Also, string representation is not well +defined for all types: it creates issues like #278. + +The recommended replacement is pre-filling the struct before unmarshaling. + +[go-defaults]: https://github.com/mcuadros/go-defaults + +#### `toml.Tree` replacement + +This structure was the initial attempt at providing a document model for +go-toml. It allows manipulating the structure of any document, encoding and +decoding from their TOML representation. While a more robust feature was +initially planned in go-toml v2, this has been ultimately [removed from +scope][nodoc] of this library, with no plan to add it back at the moment. The +closest equivalent at the moment would be to unmarshal into an `interface{}` and +use type assertions and/or reflection to manipulate the arbitrary +structure. However this would fall short of providing all of the TOML features +such as adding comments and be specific about whitespace. + + +#### `toml.Position` are not retrievable anymore + +The API for retrieving the position (line, column) of a specific TOML element do +not exist anymore. This was done to minimize the amount of concepts introduced +by the library (query path), and avoid the performance hit related to storing +positions in the absence of a document model, for a feature that seemed to have +little use. Errors however have gained more detailed position +information. Position retrieval seems better fitted for a document model, which +has been [removed from the scope][nodoc] of go-toml v2 at the moment. + +### Encoding / Marshal + +#### Default struct fields order + +V1 emits struct fields order alphabetically by default. V2 struct fields are +emitted in order they are defined. For example: + +```go +type S struct { + B string + A string +} + +data := S{ + B: "B", + A: "A", +} + +b, _ := tomlv1.Marshal(data) +fmt.Println("v1:\n" + string(b)) + +b, _ = tomlv2.Marshal(data) +fmt.Println("v2:\n" + string(b)) + +// Output: +// v1: +// A = "A" +// B = "B" + +// v2: +// B = 'B' +// A = 'A' +``` + +There is no way to make v2 encoder behave like v1. A workaround could be to +manually sort the fields alphabetically in the struct definition, or generate +struct types using `reflect.StructOf`. + +#### No indentation by default + +V1 automatically indents content of tables by default. V2 does not. However the +same behavior can be obtained using [`Encoder.SetIndentTables`][sit]. For example: + +```go +data := map[string]interface{}{ + "table": map[string]string{ + "key": "value", + }, +} + +b, _ := tomlv1.Marshal(data) +fmt.Println("v1:\n" + string(b)) + +b, _ = tomlv2.Marshal(data) +fmt.Println("v2:\n" + string(b)) + +buf := bytes.Buffer{} +enc := tomlv2.NewEncoder(&buf) +enc.SetIndentTables(true) +enc.Encode(data) +fmt.Println("v2 Encoder:\n" + string(buf.Bytes())) + +// Output: +// v1: +// +// [table] +// key = "value" +// +// v2: +// [table] +// key = 'value' +// +// +// v2 Encoder: +// [table] +// key = 'value' +``` + +[sit]: https://pkg.go.dev/github.com/pelletier/go-toml/v2#Encoder.SetIndentTables + +#### Keys and strings are single quoted + +V1 always uses double quotes (`"`) around strings and keys that cannot be +represented bare (unquoted). V2 uses single quotes instead by default (`'`), +unless a character cannot be represented, then falls back to double quotes. As a +result of this change, `Encoder.QuoteMapKeys` has been removed, as it is not +useful anymore. + +There is no way to make v2 encoder behave like v1. + +#### `TextMarshaler` emits as a string, not TOML + +Types that implement [`encoding.TextMarshaler`][tm] can emit arbitrary TOML in +v1. The encoder would append the result to the output directly. In v2 the result +is wrapped in a string. As a result, this interface cannot be implemented by the +root object. + +There is no way to make v2 encoder behave like v1. + +[tm]: https://golang.org/pkg/encoding/#TextMarshaler + +#### `Encoder.CompactComments` has been removed + +Emitting compact comments is now the default behavior of go-toml. This option +is not necessary anymore. + +#### Struct tags have been merged + +V1 used to provide multiple struct tags: `comment`, `commented`, `multiline`, +`toml`, and `omitempty`. To behave more like the standard library, v2 has merged +`toml`, `multiline`, `commented`, and `omitempty`. For example: + +```go +type doc struct { + // v1 + F string `toml:"field" multiline:"true" omitempty:"true" commented:"true"` + // v2 + F string `toml:"field,multiline,omitempty,commented"` +} +``` + +Has a result, the `Encoder.SetTag*` methods have been removed, as there is just +one tag now. + +#### `Encoder.ArraysWithOneElementPerLine` has been renamed + +The new name is `Encoder.SetArraysMultiline`. The behavior should be the same. + +#### `Encoder.Indentation` has been renamed + +The new name is `Encoder.SetIndentSymbol`. The behavior should be the same. + + +#### Embedded structs behave like stdlib + +V1 defaults to merging embedded struct fields into the embedding struct. This +behavior was unexpected because it does not follow the standard library. To +avoid breaking backward compatibility, the `Encoder.PromoteAnonymous` method was +added to make the encoder behave correctly. Given backward compatibility is not +a problem anymore, v2 does the right thing by default: it follows the behavior +of `encoding/json`. `Encoder.PromoteAnonymous` has been removed. + +[nodoc]: https://github.com/pelletier/go-toml/discussions/506#discussioncomment-1526038 + +### `query` + +go-toml v1 provided the [`go-toml/query`][query] package. It allowed to run +JSONPath-style queries on TOML files. This feature is not available in v2. For a +replacement, check out [dasel][dasel]. + +This package has been removed because it was essentially not supported anymore +(last commit May 2020), increased the complexity of the code base, and more +complete solutions exist out there. + +[query]: https://github.com/pelletier/go-toml/tree/f99d6bbca119636aeafcf351ee52b3d202782627/query +[dasel]: https://github.com/TomWright/dasel + +## Versioning + +Expect for parts explicitly marked otherwise, go-toml follows [Semantic +Versioning](https://semver.org). The supported version of +[TOML](https://github.com/toml-lang/toml) is indicated at the beginning of this +document. The last two major versions of Go are supported (see [Go Release +Policy](https://golang.org/doc/devel/release.html#policy)). + +## License + +The MIT License (MIT). Read [LICENSE](LICENSE). diff --git a/vendor/github.com/pelletier/go-toml/v2/SECURITY.md b/vendor/github.com/pelletier/go-toml/v2/SECURITY.md new file mode 100644 index 0000000000..d4d554fda9 --- /dev/null +++ b/vendor/github.com/pelletier/go-toml/v2/SECURITY.md @@ -0,0 +1,16 @@ +# Security Policy + +## Supported Versions + +| Version | Supported | +| ---------- | ------------------ | +| Latest 2.x | :white_check_mark: | +| All 1.x | :x: | +| All 0.x | :x: | + +## Reporting a Vulnerability + +Email a vulnerability report to `security@pelletier.codes`. Make sure to include +as many details as possible to reproduce the vulnerability. This is a +side-project: I will try to get back to you as quickly as possible, time +permitting in my personal life. Providing a working patch helps very much! diff --git a/vendor/github.com/pelletier/go-toml/v2/ci.sh b/vendor/github.com/pelletier/go-toml/v2/ci.sh new file mode 100644 index 0000000000..86217a9b09 --- /dev/null +++ b/vendor/github.com/pelletier/go-toml/v2/ci.sh @@ -0,0 +1,284 @@ +#!/usr/bin/env bash + + +stderr() { + echo "$@" 1>&2 +} + +usage() { + b=$(basename "$0") + echo $b: ERROR: "$@" 1>&2 + + cat 1>&2 < coverage.out + go tool cover -func=coverage.out + echo "Coverage profile for ${branch}: ${dir}/coverage.out" >&2 + popd + + if [ "${branch}" != "HEAD" ]; then + git worktree remove --force "$dir" + fi +} + +coverage() { + case "$1" in + -d) + shift + target="${1?Need to provide a target branch argument}" + + output_dir="$(mktemp -d)" + target_out="${output_dir}/target.txt" + head_out="${output_dir}/head.txt" + + cover "${target}" > "${target_out}" + cover "HEAD" > "${head_out}" + + cat "${target_out}" + cat "${head_out}" + + echo "" + + target_pct="$(tail -n2 ${target_out} | head -n1 | sed -E 's/.*total.*\t([0-9.]+)%.*/\1/')" + head_pct="$(tail -n2 ${head_out} | head -n1 | sed -E 's/.*total.*\t([0-9.]+)%/\1/')" + echo "Results: ${target} ${target_pct}% HEAD ${head_pct}%" + + delta_pct=$(echo "$head_pct - $target_pct" | bc -l) + echo "Delta: ${delta_pct}" + + if [[ $delta_pct = \-* ]]; then + echo "Regression!"; + + target_diff="${output_dir}/target.diff.txt" + head_diff="${output_dir}/head.diff.txt" + cat "${target_out}" | grep -E '^github.com/pelletier/go-toml' | tr -s "\t " | cut -f 2,3 | sort > "${target_diff}" + cat "${head_out}" | grep -E '^github.com/pelletier/go-toml' | tr -s "\t " | cut -f 2,3 | sort > "${head_diff}" + + diff --side-by-side --suppress-common-lines "${target_diff}" "${head_diff}" + return 1 + fi + return 0 + ;; + esac + + cover "${1-HEAD}" +} + +bench() { + branch="${1}" + out="${2}" + replace="${3}" + dir="$(mktemp -d)" + + stderr "Executing benchmark for ${branch} at ${dir}" + + if [ "${branch}" = "HEAD" ]; then + cp -r . "${dir}/" + else + git worktree add "$dir" "$branch" + fi + + pushd "$dir" + + if [ "${replace}" != "" ]; then + find ./benchmark/ -iname '*.go' -exec sed -i -E "s|github.com/pelletier/go-toml/v2|${replace}|g" {} \; + go get "${replace}" + fi + + export GOMAXPROCS=2 + go test '-bench=^Benchmark(Un)?[mM]arshal' -count=10 -run=Nothing ./... | tee "${out}" + popd + + if [ "${branch}" != "HEAD" ]; then + git worktree remove --force "$dir" + fi +} + +fmktemp() { + if mktemp --version &> /dev/null; then + # GNU + mktemp --suffix=-$1 + else + # BSD + mktemp -t $1 + fi +} + +benchstathtml() { +python3 - $1 <<'EOF' +import sys + +lines = [] +stop = False + +with open(sys.argv[1]) as f: + for line in f.readlines(): + line = line.strip() + if line == "": + stop = True + if not stop: + lines.append(line.split(',')) + +results = [] +for line in reversed(lines[2:]): + if len(line) < 8 or line[0] == "": + continue + v2 = float(line[1]) + results.append([ + line[0].replace("-32", ""), + "%.1fx" % (float(line[3])/v2), # v1 + "%.1fx" % (float(line[7])/v2), # bs + ]) +# move geomean to the end +results.append(results[0]) +del results[0] + + +def printtable(data): + print(""" + + + + + """) + + for r in data: + print(" ".format(*r)) + + print(""" +
Benchmarkgo-toml v1BurntSushi/toml
{}{}{}
""") + + +def match(x): + return "ReferenceFile" in x[0] or "HugoFrontMatter" in x[0] + +above = [x for x in results if match(x)] +below = [x for x in results if not match(x)] + +printtable(above) +print("
See more") +print("""

The table above has the results of the most common use-cases. The table below +contains the results of all benchmarks, including unrealistic ones. It is +provided for completeness.

""") +printtable(below) +print('

This table can be generated with ./ci.sh benchmark -a -html.

') +print("
") + +EOF +} + +benchmark() { + case "$1" in + -d) + shift + target="${1?Need to provide a target branch argument}" + + old=`fmktemp ${target}` + bench "${target}" "${old}" + + new=`fmktemp HEAD` + bench HEAD "${new}" + + benchstat "${old}" "${new}" + return 0 + ;; + -a) + shift + + v2stats=`fmktemp go-toml-v2` + bench HEAD "${v2stats}" "github.com/pelletier/go-toml/v2" + v1stats=`fmktemp go-toml-v1` + bench HEAD "${v1stats}" "github.com/pelletier/go-toml" + bsstats=`fmktemp bs-toml` + bench HEAD "${bsstats}" "github.com/BurntSushi/toml" + + cp "${v2stats}" go-toml-v2.txt + cp "${v1stats}" go-toml-v1.txt + cp "${bsstats}" bs-toml.txt + + if [ "$1" = "-html" ]; then + tmpcsv=`fmktemp csv` + benchstat -format csv go-toml-v2.txt go-toml-v1.txt bs-toml.txt > $tmpcsv + benchstathtml $tmpcsv + else + benchstat go-toml-v2.txt go-toml-v1.txt bs-toml.txt + fi + + rm -f go-toml-v2.txt go-toml-v1.txt bs-toml.txt + return $? + esac + + bench "${1-HEAD}" `mktemp` +} + +case "$1" in + coverage) shift; coverage $@;; + benchmark) shift; benchmark $@;; + *) usage "bad argument $1";; +esac diff --git a/vendor/github.com/pelletier/go-toml/v2/decode.go b/vendor/github.com/pelletier/go-toml/v2/decode.go new file mode 100644 index 0000000000..f0ec3b1705 --- /dev/null +++ b/vendor/github.com/pelletier/go-toml/v2/decode.go @@ -0,0 +1,550 @@ +package toml + +import ( + "fmt" + "math" + "strconv" + "time" + + "github.com/pelletier/go-toml/v2/unstable" +) + +func parseInteger(b []byte) (int64, error) { + if len(b) > 2 && b[0] == '0' { + switch b[1] { + case 'x': + return parseIntHex(b) + case 'b': + return parseIntBin(b) + case 'o': + return parseIntOct(b) + default: + panic(fmt.Errorf("invalid base '%c', should have been checked by scanIntOrFloat", b[1])) + } + } + + return parseIntDec(b) +} + +func parseLocalDate(b []byte) (LocalDate, error) { + // full-date = date-fullyear "-" date-month "-" date-mday + // date-fullyear = 4DIGIT + // date-month = 2DIGIT ; 01-12 + // date-mday = 2DIGIT ; 01-28, 01-29, 01-30, 01-31 based on month/year + var date LocalDate + + if len(b) != 10 || b[4] != '-' || b[7] != '-' { + return date, unstable.NewParserError(b, "dates are expected to have the format YYYY-MM-DD") + } + + var err error + + date.Year, err = parseDecimalDigits(b[0:4]) + if err != nil { + return LocalDate{}, err + } + + date.Month, err = parseDecimalDigits(b[5:7]) + if err != nil { + return LocalDate{}, err + } + + date.Day, err = parseDecimalDigits(b[8:10]) + if err != nil { + return LocalDate{}, err + } + + if !isValidDate(date.Year, date.Month, date.Day) { + return LocalDate{}, unstable.NewParserError(b, "impossible date") + } + + return date, nil +} + +func parseDecimalDigits(b []byte) (int, error) { + v := 0 + + for i, c := range b { + if c < '0' || c > '9' { + return 0, unstable.NewParserError(b[i:i+1], "expected digit (0-9)") + } + v *= 10 + v += int(c - '0') + } + + return v, nil +} + +func parseDateTime(b []byte) (time.Time, error) { + // offset-date-time = full-date time-delim full-time + // full-time = partial-time time-offset + // time-offset = "Z" / time-numoffset + // time-numoffset = ( "+" / "-" ) time-hour ":" time-minute + + dt, b, err := parseLocalDateTime(b) + if err != nil { + return time.Time{}, err + } + + var zone *time.Location + + if len(b) == 0 { + // parser should have checked that when assigning the date time node + panic("date time should have a timezone") + } + + if b[0] == 'Z' || b[0] == 'z' { + b = b[1:] + zone = time.UTC + } else { + const dateTimeByteLen = 6 + if len(b) != dateTimeByteLen { + return time.Time{}, unstable.NewParserError(b, "invalid date-time timezone") + } + var direction int + switch b[0] { + case '-': + direction = -1 + case '+': + direction = +1 + default: + return time.Time{}, unstable.NewParserError(b[:1], "invalid timezone offset character") + } + + if b[3] != ':' { + return time.Time{}, unstable.NewParserError(b[3:4], "expected a : separator") + } + + hours, err := parseDecimalDigits(b[1:3]) + if err != nil { + return time.Time{}, err + } + if hours > 23 { + return time.Time{}, unstable.NewParserError(b[:1], "invalid timezone offset hours") + } + + minutes, err := parseDecimalDigits(b[4:6]) + if err != nil { + return time.Time{}, err + } + if minutes > 59 { + return time.Time{}, unstable.NewParserError(b[:1], "invalid timezone offset minutes") + } + + seconds := direction * (hours*3600 + minutes*60) + if seconds == 0 { + zone = time.UTC + } else { + zone = time.FixedZone("", seconds) + } + b = b[dateTimeByteLen:] + } + + if len(b) > 0 { + return time.Time{}, unstable.NewParserError(b, "extra bytes at the end of the timezone") + } + + t := time.Date( + dt.Year, + time.Month(dt.Month), + dt.Day, + dt.Hour, + dt.Minute, + dt.Second, + dt.Nanosecond, + zone) + + return t, nil +} + +func parseLocalDateTime(b []byte) (LocalDateTime, []byte, error) { + var dt LocalDateTime + + const localDateTimeByteMinLen = 11 + if len(b) < localDateTimeByteMinLen { + return dt, nil, unstable.NewParserError(b, "local datetimes are expected to have the format YYYY-MM-DDTHH:MM:SS[.NNNNNNNNN]") + } + + date, err := parseLocalDate(b[:10]) + if err != nil { + return dt, nil, err + } + dt.LocalDate = date + + sep := b[10] + if sep != 'T' && sep != ' ' && sep != 't' { + return dt, nil, unstable.NewParserError(b[10:11], "datetime separator is expected to be T or a space") + } + + t, rest, err := parseLocalTime(b[11:]) + if err != nil { + return dt, nil, err + } + dt.LocalTime = t + + return dt, rest, nil +} + +// parseLocalTime is a bit different because it also returns the remaining +// []byte that is didn't need. This is to allow parseDateTime to parse those +// remaining bytes as a timezone. +func parseLocalTime(b []byte) (LocalTime, []byte, error) { + var ( + nspow = [10]int{0, 1e8, 1e7, 1e6, 1e5, 1e4, 1e3, 1e2, 1e1, 1e0} + t LocalTime + ) + + // check if b matches to have expected format HH:MM:SS[.NNNNNN] + const localTimeByteLen = 8 + if len(b) < localTimeByteLen { + return t, nil, unstable.NewParserError(b, "times are expected to have the format HH:MM:SS[.NNNNNN]") + } + + var err error + + t.Hour, err = parseDecimalDigits(b[0:2]) + if err != nil { + return t, nil, err + } + + if t.Hour > 23 { + return t, nil, unstable.NewParserError(b[0:2], "hour cannot be greater 23") + } + if b[2] != ':' { + return t, nil, unstable.NewParserError(b[2:3], "expecting colon between hours and minutes") + } + + t.Minute, err = parseDecimalDigits(b[3:5]) + if err != nil { + return t, nil, err + } + if t.Minute > 59 { + return t, nil, unstable.NewParserError(b[3:5], "minutes cannot be greater 59") + } + if b[5] != ':' { + return t, nil, unstable.NewParserError(b[5:6], "expecting colon between minutes and seconds") + } + + t.Second, err = parseDecimalDigits(b[6:8]) + if err != nil { + return t, nil, err + } + + if t.Second > 60 { + return t, nil, unstable.NewParserError(b[6:8], "seconds cannot be greater 60") + } + + b = b[8:] + + if len(b) >= 1 && b[0] == '.' { + frac := 0 + precision := 0 + digits := 0 + + for i, c := range b[1:] { + if !isDigit(c) { + if i == 0 { + return t, nil, unstable.NewParserError(b[0:1], "need at least one digit after fraction point") + } + break + } + digits++ + + const maxFracPrecision = 9 + if i >= maxFracPrecision { + // go-toml allows decoding fractional seconds + // beyond the supported precision of 9 + // digits. It truncates the fractional component + // to the supported precision and ignores the + // remaining digits. + // + // https://github.com/pelletier/go-toml/discussions/707 + continue + } + + frac *= 10 + frac += int(c - '0') + precision++ + } + + if precision == 0 { + return t, nil, unstable.NewParserError(b[:1], "nanoseconds need at least one digit") + } + + t.Nanosecond = frac * nspow[precision] + t.Precision = precision + + return t, b[1+digits:], nil + } + return t, b, nil +} + +//nolint:cyclop +func parseFloat(b []byte) (float64, error) { + if len(b) == 4 && (b[0] == '+' || b[0] == '-') && b[1] == 'n' && b[2] == 'a' && b[3] == 'n' { + return math.NaN(), nil + } + + cleaned, err := checkAndRemoveUnderscoresFloats(b) + if err != nil { + return 0, err + } + + if cleaned[0] == '.' { + return 0, unstable.NewParserError(b, "float cannot start with a dot") + } + + if cleaned[len(cleaned)-1] == '.' { + return 0, unstable.NewParserError(b, "float cannot end with a dot") + } + + dotAlreadySeen := false + for i, c := range cleaned { + if c == '.' { + if dotAlreadySeen { + return 0, unstable.NewParserError(b[i:i+1], "float can have at most one decimal point") + } + if !isDigit(cleaned[i-1]) { + return 0, unstable.NewParserError(b[i-1:i+1], "float decimal point must be preceded by a digit") + } + if !isDigit(cleaned[i+1]) { + return 0, unstable.NewParserError(b[i:i+2], "float decimal point must be followed by a digit") + } + dotAlreadySeen = true + } + } + + start := 0 + if cleaned[0] == '+' || cleaned[0] == '-' { + start = 1 + } + if cleaned[start] == '0' && len(cleaned) > start+1 && isDigit(cleaned[start+1]) { + return 0, unstable.NewParserError(b, "float integer part cannot have leading zeroes") + } + + f, err := strconv.ParseFloat(string(cleaned), 64) + if err != nil { + return 0, unstable.NewParserError(b, "unable to parse float: %w", err) + } + + return f, nil +} + +func parseIntHex(b []byte) (int64, error) { + cleaned, err := checkAndRemoveUnderscoresIntegers(b[2:]) + if err != nil { + return 0, err + } + + i, err := strconv.ParseInt(string(cleaned), 16, 64) + if err != nil { + return 0, unstable.NewParserError(b, "couldn't parse hexadecimal number: %w", err) + } + + return i, nil +} + +func parseIntOct(b []byte) (int64, error) { + cleaned, err := checkAndRemoveUnderscoresIntegers(b[2:]) + if err != nil { + return 0, err + } + + i, err := strconv.ParseInt(string(cleaned), 8, 64) + if err != nil { + return 0, unstable.NewParserError(b, "couldn't parse octal number: %w", err) + } + + return i, nil +} + +func parseIntBin(b []byte) (int64, error) { + cleaned, err := checkAndRemoveUnderscoresIntegers(b[2:]) + if err != nil { + return 0, err + } + + i, err := strconv.ParseInt(string(cleaned), 2, 64) + if err != nil { + return 0, unstable.NewParserError(b, "couldn't parse binary number: %w", err) + } + + return i, nil +} + +func isSign(b byte) bool { + return b == '+' || b == '-' +} + +func parseIntDec(b []byte) (int64, error) { + cleaned, err := checkAndRemoveUnderscoresIntegers(b) + if err != nil { + return 0, err + } + + startIdx := 0 + + if isSign(cleaned[0]) { + startIdx++ + } + + if len(cleaned) > startIdx+1 && cleaned[startIdx] == '0' { + return 0, unstable.NewParserError(b, "leading zero not allowed on decimal number") + } + + i, err := strconv.ParseInt(string(cleaned), 10, 64) + if err != nil { + return 0, unstable.NewParserError(b, "couldn't parse decimal number: %w", err) + } + + return i, nil +} + +func checkAndRemoveUnderscoresIntegers(b []byte) ([]byte, error) { + start := 0 + if b[start] == '+' || b[start] == '-' { + start++ + } + + if len(b) == start { + return b, nil + } + + if b[start] == '_' { + return nil, unstable.NewParserError(b[start:start+1], "number cannot start with underscore") + } + + if b[len(b)-1] == '_' { + return nil, unstable.NewParserError(b[len(b)-1:], "number cannot end with underscore") + } + + // fast path + i := 0 + for ; i < len(b); i++ { + if b[i] == '_' { + break + } + } + if i == len(b) { + return b, nil + } + + before := false + cleaned := make([]byte, i, len(b)) + copy(cleaned, b) + + for i++; i < len(b); i++ { + c := b[i] + if c == '_' { + if !before { + return nil, unstable.NewParserError(b[i-1:i+1], "number must have at least one digit between underscores") + } + before = false + } else { + before = true + cleaned = append(cleaned, c) + } + } + + return cleaned, nil +} + +func checkAndRemoveUnderscoresFloats(b []byte) ([]byte, error) { + if b[0] == '_' { + return nil, unstable.NewParserError(b[0:1], "number cannot start with underscore") + } + + if b[len(b)-1] == '_' { + return nil, unstable.NewParserError(b[len(b)-1:], "number cannot end with underscore") + } + + // fast path + i := 0 + for ; i < len(b); i++ { + if b[i] == '_' { + break + } + } + if i == len(b) { + return b, nil + } + + before := false + cleaned := make([]byte, 0, len(b)) + + for i := 0; i < len(b); i++ { + c := b[i] + + switch c { + case '_': + if !before { + return nil, unstable.NewParserError(b[i-1:i+1], "number must have at least one digit between underscores") + } + if i < len(b)-1 && (b[i+1] == 'e' || b[i+1] == 'E') { + return nil, unstable.NewParserError(b[i+1:i+2], "cannot have underscore before exponent") + } + before = false + case '+', '-': + // signed exponents + cleaned = append(cleaned, c) + before = false + case 'e', 'E': + if i < len(b)-1 && b[i+1] == '_' { + return nil, unstable.NewParserError(b[i+1:i+2], "cannot have underscore after exponent") + } + cleaned = append(cleaned, c) + case '.': + if i < len(b)-1 && b[i+1] == '_' { + return nil, unstable.NewParserError(b[i+1:i+2], "cannot have underscore after decimal point") + } + if i > 0 && b[i-1] == '_' { + return nil, unstable.NewParserError(b[i-1:i], "cannot have underscore before decimal point") + } + cleaned = append(cleaned, c) + default: + before = true + cleaned = append(cleaned, c) + } + } + + return cleaned, nil +} + +// isValidDate checks if a provided date is a date that exists. +func isValidDate(year int, month int, day int) bool { + return month > 0 && month < 13 && day > 0 && day <= daysIn(month, year) +} + +// daysBefore[m] counts the number of days in a non-leap year +// before month m begins. There is an entry for m=12, counting +// the number of days before January of next year (365). +var daysBefore = [...]int32{ + 0, + 31, + 31 + 28, + 31 + 28 + 31, + 31 + 28 + 31 + 30, + 31 + 28 + 31 + 30 + 31, + 31 + 28 + 31 + 30 + 31 + 30, + 31 + 28 + 31 + 30 + 31 + 30 + 31, + 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31, + 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30, + 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31, + 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31 + 30, + 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31 + 30 + 31, +} + +func daysIn(m int, year int) int { + if m == 2 && isLeap(year) { + return 29 + } + return int(daysBefore[m] - daysBefore[m-1]) +} + +func isLeap(year int) bool { + return year%4 == 0 && (year%100 != 0 || year%400 == 0) +} + +func isDigit(r byte) bool { + return r >= '0' && r <= '9' +} diff --git a/vendor/github.com/pelletier/go-toml/v2/doc.go b/vendor/github.com/pelletier/go-toml/v2/doc.go new file mode 100644 index 0000000000..b7bc599bde --- /dev/null +++ b/vendor/github.com/pelletier/go-toml/v2/doc.go @@ -0,0 +1,2 @@ +// Package toml is a library to read and write TOML documents. +package toml diff --git a/vendor/github.com/pelletier/go-toml/v2/errors.go b/vendor/github.com/pelletier/go-toml/v2/errors.go new file mode 100644 index 0000000000..309733f1f9 --- /dev/null +++ b/vendor/github.com/pelletier/go-toml/v2/errors.go @@ -0,0 +1,252 @@ +package toml + +import ( + "fmt" + "strconv" + "strings" + + "github.com/pelletier/go-toml/v2/internal/danger" + "github.com/pelletier/go-toml/v2/unstable" +) + +// DecodeError represents an error encountered during the parsing or decoding +// of a TOML document. +// +// In addition to the error message, it contains the position in the document +// where it happened, as well as a human-readable representation that shows +// where the error occurred in the document. +type DecodeError struct { + message string + line int + column int + key Key + + human string +} + +// StrictMissingError occurs in a TOML document that does not have a +// corresponding field in the target value. It contains all the missing fields +// in Errors. +// +// Emitted by Decoder when DisallowUnknownFields() was called. +type StrictMissingError struct { + // One error per field that could not be found. + Errors []DecodeError +} + +// Error returns the canonical string for this error. +func (s *StrictMissingError) Error() string { + return "strict mode: fields in the document are missing in the target struct" +} + +// String returns a human readable description of all errors. +func (s *StrictMissingError) String() string { + var buf strings.Builder + + for i, e := range s.Errors { + if i > 0 { + buf.WriteString("\n---\n") + } + + buf.WriteString(e.String()) + } + + return buf.String() +} + +type Key []string + +// Error returns the error message contained in the DecodeError. +func (e *DecodeError) Error() string { + return "toml: " + e.message +} + +// String returns the human-readable contextualized error. This string is multi-line. +func (e *DecodeError) String() string { + return e.human +} + +// Position returns the (line, column) pair indicating where the error +// occurred in the document. Positions are 1-indexed. +func (e *DecodeError) Position() (row int, column int) { + return e.line, e.column +} + +// Key that was being processed when the error occurred. The key is present only +// if this DecodeError is part of a StrictMissingError. +func (e *DecodeError) Key() Key { + return e.key +} + +// decodeErrorFromHighlight creates a DecodeError referencing a highlighted +// range of bytes from document. +// +// highlight needs to be a sub-slice of document, or this function panics. +// +// The function copies all bytes used in DecodeError, so that document and +// highlight can be freely deallocated. +// +//nolint:funlen +func wrapDecodeError(document []byte, de *unstable.ParserError) *DecodeError { + offset := danger.SubsliceOffset(document, de.Highlight) + + errMessage := de.Error() + errLine, errColumn := positionAtEnd(document[:offset]) + before, after := linesOfContext(document, de.Highlight, offset, 3) + + var buf strings.Builder + + maxLine := errLine + len(after) - 1 + lineColumnWidth := len(strconv.Itoa(maxLine)) + + // Write the lines of context strictly before the error. + for i := len(before) - 1; i > 0; i-- { + line := errLine - i + buf.WriteString(formatLineNumber(line, lineColumnWidth)) + buf.WriteString("|") + + if len(before[i]) > 0 { + buf.WriteString(" ") + buf.Write(before[i]) + } + + buf.WriteRune('\n') + } + + // Write the document line that contains the error. + + buf.WriteString(formatLineNumber(errLine, lineColumnWidth)) + buf.WriteString("| ") + + if len(before) > 0 { + buf.Write(before[0]) + } + + buf.Write(de.Highlight) + + if len(after) > 0 { + buf.Write(after[0]) + } + + buf.WriteRune('\n') + + // Write the line with the error message itself (so it does not have a line + // number). + + buf.WriteString(strings.Repeat(" ", lineColumnWidth)) + buf.WriteString("| ") + + if len(before) > 0 { + buf.WriteString(strings.Repeat(" ", len(before[0]))) + } + + buf.WriteString(strings.Repeat("~", len(de.Highlight))) + + if len(errMessage) > 0 { + buf.WriteString(" ") + buf.WriteString(errMessage) + } + + // Write the lines of context strictly after the error. + + for i := 1; i < len(after); i++ { + buf.WriteRune('\n') + line := errLine + i + buf.WriteString(formatLineNumber(line, lineColumnWidth)) + buf.WriteString("|") + + if len(after[i]) > 0 { + buf.WriteString(" ") + buf.Write(after[i]) + } + } + + return &DecodeError{ + message: errMessage, + line: errLine, + column: errColumn, + key: de.Key, + human: buf.String(), + } +} + +func formatLineNumber(line int, width int) string { + format := "%" + strconv.Itoa(width) + "d" + + return fmt.Sprintf(format, line) +} + +func linesOfContext(document []byte, highlight []byte, offset int, linesAround int) ([][]byte, [][]byte) { + return beforeLines(document, offset, linesAround), afterLines(document, highlight, offset, linesAround) +} + +func beforeLines(document []byte, offset int, linesAround int) [][]byte { + var beforeLines [][]byte + + // Walk the document backward from the highlight to find previous lines + // of context. + rest := document[:offset] +backward: + for o := len(rest) - 1; o >= 0 && len(beforeLines) <= linesAround && len(rest) > 0; { + switch { + case rest[o] == '\n': + // handle individual lines + beforeLines = append(beforeLines, rest[o+1:]) + rest = rest[:o] + o = len(rest) - 1 + case o == 0: + // add the first line only if it's non-empty + beforeLines = append(beforeLines, rest) + + break backward + default: + o-- + } + } + + return beforeLines +} + +func afterLines(document []byte, highlight []byte, offset int, linesAround int) [][]byte { + var afterLines [][]byte + + // Walk the document forward from the highlight to find the following + // lines of context. + rest := document[offset+len(highlight):] +forward: + for o := 0; o < len(rest) && len(afterLines) <= linesAround; { + switch { + case rest[o] == '\n': + // handle individual lines + afterLines = append(afterLines, rest[:o]) + rest = rest[o+1:] + o = 0 + + case o == len(rest)-1: + // add last line only if it's non-empty + afterLines = append(afterLines, rest) + + break forward + default: + o++ + } + } + + return afterLines +} + +func positionAtEnd(b []byte) (row int, column int) { + row = 1 + column = 1 + + for _, c := range b { + if c == '\n' { + row++ + column = 1 + } else { + column++ + } + } + + return +} diff --git a/vendor/github.com/pelletier/go-toml/v2/internal/characters/ascii.go b/vendor/github.com/pelletier/go-toml/v2/internal/characters/ascii.go new file mode 100644 index 0000000000..80f698db4b --- /dev/null +++ b/vendor/github.com/pelletier/go-toml/v2/internal/characters/ascii.go @@ -0,0 +1,42 @@ +package characters + +var invalidAsciiTable = [256]bool{ + 0x00: true, + 0x01: true, + 0x02: true, + 0x03: true, + 0x04: true, + 0x05: true, + 0x06: true, + 0x07: true, + 0x08: true, + // 0x09 TAB + // 0x0A LF + 0x0B: true, + 0x0C: true, + // 0x0D CR + 0x0E: true, + 0x0F: true, + 0x10: true, + 0x11: true, + 0x12: true, + 0x13: true, + 0x14: true, + 0x15: true, + 0x16: true, + 0x17: true, + 0x18: true, + 0x19: true, + 0x1A: true, + 0x1B: true, + 0x1C: true, + 0x1D: true, + 0x1E: true, + 0x1F: true, + // 0x20 - 0x7E Printable ASCII characters + 0x7F: true, +} + +func InvalidAscii(b byte) bool { + return invalidAsciiTable[b] +} diff --git a/vendor/github.com/pelletier/go-toml/v2/internal/characters/utf8.go b/vendor/github.com/pelletier/go-toml/v2/internal/characters/utf8.go new file mode 100644 index 0000000000..db4f45acbf --- /dev/null +++ b/vendor/github.com/pelletier/go-toml/v2/internal/characters/utf8.go @@ -0,0 +1,199 @@ +package characters + +import ( + "unicode/utf8" +) + +type utf8Err struct { + Index int + Size int +} + +func (u utf8Err) Zero() bool { + return u.Size == 0 +} + +// Verified that a given string is only made of valid UTF-8 characters allowed +// by the TOML spec: +// +// Any Unicode character may be used except those that must be escaped: +// quotation mark, backslash, and the control characters other than tab (U+0000 +// to U+0008, U+000A to U+001F, U+007F). +// +// It is a copy of the Go 1.17 utf8.Valid implementation, tweaked to exit early +// when a character is not allowed. +// +// The returned utf8Err is Zero() if the string is valid, or contains the byte +// index and size of the invalid character. +// +// quotation mark => already checked +// backslash => already checked +// 0-0x8 => invalid +// 0x9 => tab, ok +// 0xA - 0x1F => invalid +// 0x7F => invalid +func Utf8TomlValidAlreadyEscaped(p []byte) (err utf8Err) { + // Fast path. Check for and skip 8 bytes of ASCII characters per iteration. + offset := 0 + for len(p) >= 8 { + // Combining two 32 bit loads allows the same code to be used + // for 32 and 64 bit platforms. + // The compiler can generate a 32bit load for first32 and second32 + // on many platforms. See test/codegen/memcombine.go. + first32 := uint32(p[0]) | uint32(p[1])<<8 | uint32(p[2])<<16 | uint32(p[3])<<24 + second32 := uint32(p[4]) | uint32(p[5])<<8 | uint32(p[6])<<16 | uint32(p[7])<<24 + if (first32|second32)&0x80808080 != 0 { + // Found a non ASCII byte (>= RuneSelf). + break + } + + for i, b := range p[:8] { + if InvalidAscii(b) { + err.Index = offset + i + err.Size = 1 + return + } + } + + p = p[8:] + offset += 8 + } + n := len(p) + for i := 0; i < n; { + pi := p[i] + if pi < utf8.RuneSelf { + if InvalidAscii(pi) { + err.Index = offset + i + err.Size = 1 + return + } + i++ + continue + } + x := first[pi] + if x == xx { + // Illegal starter byte. + err.Index = offset + i + err.Size = 1 + return + } + size := int(x & 7) + if i+size > n { + // Short or invalid. + err.Index = offset + i + err.Size = n - i + return + } + accept := acceptRanges[x>>4] + if c := p[i+1]; c < accept.lo || accept.hi < c { + err.Index = offset + i + err.Size = 2 + return + } else if size == 2 { + } else if c := p[i+2]; c < locb || hicb < c { + err.Index = offset + i + err.Size = 3 + return + } else if size == 3 { + } else if c := p[i+3]; c < locb || hicb < c { + err.Index = offset + i + err.Size = 4 + return + } + i += size + } + return +} + +// Return the size of the next rune if valid, 0 otherwise. +func Utf8ValidNext(p []byte) int { + c := p[0] + + if c < utf8.RuneSelf { + if InvalidAscii(c) { + return 0 + } + return 1 + } + + x := first[c] + if x == xx { + // Illegal starter byte. + return 0 + } + size := int(x & 7) + if size > len(p) { + // Short or invalid. + return 0 + } + accept := acceptRanges[x>>4] + if c := p[1]; c < accept.lo || accept.hi < c { + return 0 + } else if size == 2 { + } else if c := p[2]; c < locb || hicb < c { + return 0 + } else if size == 3 { + } else if c := p[3]; c < locb || hicb < c { + return 0 + } + + return size +} + +// acceptRange gives the range of valid values for the second byte in a UTF-8 +// sequence. +type acceptRange struct { + lo uint8 // lowest value for second byte. + hi uint8 // highest value for second byte. +} + +// acceptRanges has size 16 to avoid bounds checks in the code that uses it. +var acceptRanges = [16]acceptRange{ + 0: {locb, hicb}, + 1: {0xA0, hicb}, + 2: {locb, 0x9F}, + 3: {0x90, hicb}, + 4: {locb, 0x8F}, +} + +// first is information about the first byte in a UTF-8 sequence. +var first = [256]uint8{ + // 1 2 3 4 5 6 7 8 9 A B C D E F + as, as, as, as, as, as, as, as, as, as, as, as, as, as, as, as, // 0x00-0x0F + as, as, as, as, as, as, as, as, as, as, as, as, as, as, as, as, // 0x10-0x1F + as, as, as, as, as, as, as, as, as, as, as, as, as, as, as, as, // 0x20-0x2F + as, as, as, as, as, as, as, as, as, as, as, as, as, as, as, as, // 0x30-0x3F + as, as, as, as, as, as, as, as, as, as, as, as, as, as, as, as, // 0x40-0x4F + as, as, as, as, as, as, as, as, as, as, as, as, as, as, as, as, // 0x50-0x5F + as, as, as, as, as, as, as, as, as, as, as, as, as, as, as, as, // 0x60-0x6F + as, as, as, as, as, as, as, as, as, as, as, as, as, as, as, as, // 0x70-0x7F + // 1 2 3 4 5 6 7 8 9 A B C D E F + xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, // 0x80-0x8F + xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, // 0x90-0x9F + xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, // 0xA0-0xAF + xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, // 0xB0-0xBF + xx, xx, s1, s1, s1, s1, s1, s1, s1, s1, s1, s1, s1, s1, s1, s1, // 0xC0-0xCF + s1, s1, s1, s1, s1, s1, s1, s1, s1, s1, s1, s1, s1, s1, s1, s1, // 0xD0-0xDF + s2, s3, s3, s3, s3, s3, s3, s3, s3, s3, s3, s3, s3, s4, s3, s3, // 0xE0-0xEF + s5, s6, s6, s6, s7, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, xx, // 0xF0-0xFF +} + +const ( + // The default lowest and highest continuation byte. + locb = 0b10000000 + hicb = 0b10111111 + + // These names of these constants are chosen to give nice alignment in the + // table below. The first nibble is an index into acceptRanges or F for + // special one-byte cases. The second nibble is the Rune length or the + // Status for the special one-byte case. + xx = 0xF1 // invalid: size 1 + as = 0xF0 // ASCII: size 1 + s1 = 0x02 // accept 0, size 2 + s2 = 0x13 // accept 1, size 3 + s3 = 0x03 // accept 0, size 3 + s4 = 0x23 // accept 2, size 3 + s5 = 0x34 // accept 3, size 4 + s6 = 0x04 // accept 0, size 4 + s7 = 0x44 // accept 4, size 4 +) diff --git a/vendor/github.com/pelletier/go-toml/v2/internal/danger/danger.go b/vendor/github.com/pelletier/go-toml/v2/internal/danger/danger.go new file mode 100644 index 0000000000..e38e1131b8 --- /dev/null +++ b/vendor/github.com/pelletier/go-toml/v2/internal/danger/danger.go @@ -0,0 +1,65 @@ +package danger + +import ( + "fmt" + "reflect" + "unsafe" +) + +const maxInt = uintptr(int(^uint(0) >> 1)) + +func SubsliceOffset(data []byte, subslice []byte) int { + datap := (*reflect.SliceHeader)(unsafe.Pointer(&data)) + hlp := (*reflect.SliceHeader)(unsafe.Pointer(&subslice)) + + if hlp.Data < datap.Data { + panic(fmt.Errorf("subslice address (%d) is before data address (%d)", hlp.Data, datap.Data)) + } + offset := hlp.Data - datap.Data + + if offset > maxInt { + panic(fmt.Errorf("slice offset larger than int (%d)", offset)) + } + + intoffset := int(offset) + + if intoffset > datap.Len { + panic(fmt.Errorf("slice offset (%d) is farther than data length (%d)", intoffset, datap.Len)) + } + + if intoffset+hlp.Len > datap.Len { + panic(fmt.Errorf("slice ends (%d+%d) is farther than data length (%d)", intoffset, hlp.Len, datap.Len)) + } + + return intoffset +} + +func BytesRange(start []byte, end []byte) []byte { + if start == nil || end == nil { + panic("cannot call BytesRange with nil") + } + startp := (*reflect.SliceHeader)(unsafe.Pointer(&start)) + endp := (*reflect.SliceHeader)(unsafe.Pointer(&end)) + + if startp.Data > endp.Data { + panic(fmt.Errorf("start pointer address (%d) is after end pointer address (%d)", startp.Data, endp.Data)) + } + + l := startp.Len + endLen := int(endp.Data-startp.Data) + endp.Len + if endLen > l { + l = endLen + } + + if l > startp.Cap { + panic(fmt.Errorf("range length is larger than capacity")) + } + + return start[:l] +} + +func Stride(ptr unsafe.Pointer, size uintptr, offset int) unsafe.Pointer { + // TODO: replace with unsafe.Add when Go 1.17 is released + // https://github.com/golang/go/issues/40481 + return unsafe.Pointer(uintptr(ptr) + uintptr(int(size)*offset)) +} diff --git a/vendor/github.com/pelletier/go-toml/v2/internal/danger/typeid.go b/vendor/github.com/pelletier/go-toml/v2/internal/danger/typeid.go new file mode 100644 index 0000000000..9d41c28a2f --- /dev/null +++ b/vendor/github.com/pelletier/go-toml/v2/internal/danger/typeid.go @@ -0,0 +1,23 @@ +package danger + +import ( + "reflect" + "unsafe" +) + +// typeID is used as key in encoder and decoder caches to enable using +// the optimize runtime.mapaccess2_fast64 function instead of the more +// expensive lookup if we were to use reflect.Type as map key. +// +// typeID holds the pointer to the reflect.Type value, which is unique +// in the program. +// +// https://github.com/segmentio/encoding/blob/master/json/codec.go#L59-L61 +type TypeID unsafe.Pointer + +func MakeTypeID(t reflect.Type) TypeID { + // reflect.Type has the fields: + // typ unsafe.Pointer + // ptr unsafe.Pointer + return TypeID((*[2]unsafe.Pointer)(unsafe.Pointer(&t))[1]) +} diff --git a/vendor/github.com/pelletier/go-toml/v2/internal/tracker/key.go b/vendor/github.com/pelletier/go-toml/v2/internal/tracker/key.go new file mode 100644 index 0000000000..149b17f538 --- /dev/null +++ b/vendor/github.com/pelletier/go-toml/v2/internal/tracker/key.go @@ -0,0 +1,48 @@ +package tracker + +import "github.com/pelletier/go-toml/v2/unstable" + +// KeyTracker is a tracker that keeps track of the current Key as the AST is +// walked. +type KeyTracker struct { + k []string +} + +// UpdateTable sets the state of the tracker with the AST table node. +func (t *KeyTracker) UpdateTable(node *unstable.Node) { + t.reset() + t.Push(node) +} + +// UpdateArrayTable sets the state of the tracker with the AST array table node. +func (t *KeyTracker) UpdateArrayTable(node *unstable.Node) { + t.reset() + t.Push(node) +} + +// Push the given key on the stack. +func (t *KeyTracker) Push(node *unstable.Node) { + it := node.Key() + for it.Next() { + t.k = append(t.k, string(it.Node().Data)) + } +} + +// Pop key from stack. +func (t *KeyTracker) Pop(node *unstable.Node) { + it := node.Key() + for it.Next() { + t.k = t.k[:len(t.k)-1] + } +} + +// Key returns the current key +func (t *KeyTracker) Key() []string { + k := make([]string, len(t.k)) + copy(k, t.k) + return k +} + +func (t *KeyTracker) reset() { + t.k = t.k[:0] +} diff --git a/vendor/github.com/pelletier/go-toml/v2/internal/tracker/seen.go b/vendor/github.com/pelletier/go-toml/v2/internal/tracker/seen.go new file mode 100644 index 0000000000..76df2d5b6a --- /dev/null +++ b/vendor/github.com/pelletier/go-toml/v2/internal/tracker/seen.go @@ -0,0 +1,358 @@ +package tracker + +import ( + "bytes" + "fmt" + "sync" + + "github.com/pelletier/go-toml/v2/unstable" +) + +type keyKind uint8 + +const ( + invalidKind keyKind = iota + valueKind + tableKind + arrayTableKind +) + +func (k keyKind) String() string { + switch k { + case invalidKind: + return "invalid" + case valueKind: + return "value" + case tableKind: + return "table" + case arrayTableKind: + return "array table" + } + panic("missing keyKind string mapping") +} + +// SeenTracker tracks which keys have been seen with which TOML type to flag +// duplicates and mismatches according to the spec. +// +// Each node in the visited tree is represented by an entry. Each entry has an +// identifier, which is provided by a counter. Entries are stored in the array +// entries. As new nodes are discovered (referenced for the first time in the +// TOML document), entries are created and appended to the array. An entry +// points to its parent using its id. +// +// To find whether a given key (sequence of []byte) has already been visited, +// the entries are linearly searched, looking for one with the right name and +// parent id. +// +// Given that all keys appear in the document after their parent, it is +// guaranteed that all descendants of a node are stored after the node, this +// speeds up the search process. +// +// When encountering [[array tables]], the descendants of that node are removed +// to allow that branch of the tree to be "rediscovered". To maintain the +// invariant above, the deletion process needs to keep the order of entries. +// This results in more copies in that case. +type SeenTracker struct { + entries []entry + currentIdx int +} + +var pool = sync.Pool{ + New: func() interface{} { + return &SeenTracker{} + }, +} + +func (s *SeenTracker) reset() { + // Always contains a root element at index 0. + s.currentIdx = 0 + if len(s.entries) == 0 { + s.entries = make([]entry, 1, 2) + } else { + s.entries = s.entries[:1] + } + s.entries[0].child = -1 + s.entries[0].next = -1 +} + +type entry struct { + // Use -1 to indicate no child or no sibling. + child int + next int + + name []byte + kind keyKind + explicit bool + kv bool +} + +// Find the index of the child of parentIdx with key k. Returns -1 if +// it does not exist. +func (s *SeenTracker) find(parentIdx int, k []byte) int { + for i := s.entries[parentIdx].child; i >= 0; i = s.entries[i].next { + if bytes.Equal(s.entries[i].name, k) { + return i + } + } + return -1 +} + +// Remove all descendants of node at position idx. +func (s *SeenTracker) clear(idx int) { + if idx >= len(s.entries) { + return + } + + for i := s.entries[idx].child; i >= 0; { + next := s.entries[i].next + n := s.entries[0].next + s.entries[0].next = i + s.entries[i].next = n + s.entries[i].name = nil + s.clear(i) + i = next + } + + s.entries[idx].child = -1 +} + +func (s *SeenTracker) create(parentIdx int, name []byte, kind keyKind, explicit bool, kv bool) int { + e := entry{ + child: -1, + next: s.entries[parentIdx].child, + + name: name, + kind: kind, + explicit: explicit, + kv: kv, + } + var idx int + if s.entries[0].next >= 0 { + idx = s.entries[0].next + s.entries[0].next = s.entries[idx].next + s.entries[idx] = e + } else { + idx = len(s.entries) + s.entries = append(s.entries, e) + } + + s.entries[parentIdx].child = idx + + return idx +} + +func (s *SeenTracker) setExplicitFlag(parentIdx int) { + for i := s.entries[parentIdx].child; i >= 0; i = s.entries[i].next { + if s.entries[i].kv { + s.entries[i].explicit = true + s.entries[i].kv = false + } + s.setExplicitFlag(i) + } +} + +// CheckExpression takes a top-level node and checks that it does not contain +// keys that have been seen in previous calls, and validates that types are +// consistent. It returns true if it is the first time this node's key is seen. +// Useful to clear array tables on first use. +func (s *SeenTracker) CheckExpression(node *unstable.Node) (bool, error) { + if s.entries == nil { + s.reset() + } + switch node.Kind { + case unstable.KeyValue: + return s.checkKeyValue(node) + case unstable.Table: + return s.checkTable(node) + case unstable.ArrayTable: + return s.checkArrayTable(node) + default: + panic(fmt.Errorf("this should not be a top level node type: %s", node.Kind)) + } +} + +func (s *SeenTracker) checkTable(node *unstable.Node) (bool, error) { + if s.currentIdx >= 0 { + s.setExplicitFlag(s.currentIdx) + } + + it := node.Key() + + parentIdx := 0 + + // This code is duplicated in checkArrayTable. This is because factoring + // it in a function requires to copy the iterator, or allocate it to the + // heap, which is not cheap. + for it.Next() { + if it.IsLast() { + break + } + + k := it.Node().Data + + idx := s.find(parentIdx, k) + + if idx < 0 { + idx = s.create(parentIdx, k, tableKind, false, false) + } else { + entry := s.entries[idx] + if entry.kind == valueKind { + return false, fmt.Errorf("toml: expected %s to be a table, not a %s", string(k), entry.kind) + } + } + parentIdx = idx + } + + k := it.Node().Data + idx := s.find(parentIdx, k) + + first := false + if idx >= 0 { + kind := s.entries[idx].kind + if kind != tableKind { + return false, fmt.Errorf("toml: key %s should be a table, not a %s", string(k), kind) + } + if s.entries[idx].explicit { + return false, fmt.Errorf("toml: table %s already exists", string(k)) + } + s.entries[idx].explicit = true + } else { + idx = s.create(parentIdx, k, tableKind, true, false) + first = true + } + + s.currentIdx = idx + + return first, nil +} + +func (s *SeenTracker) checkArrayTable(node *unstable.Node) (bool, error) { + if s.currentIdx >= 0 { + s.setExplicitFlag(s.currentIdx) + } + + it := node.Key() + + parentIdx := 0 + + for it.Next() { + if it.IsLast() { + break + } + + k := it.Node().Data + + idx := s.find(parentIdx, k) + + if idx < 0 { + idx = s.create(parentIdx, k, tableKind, false, false) + } else { + entry := s.entries[idx] + if entry.kind == valueKind { + return false, fmt.Errorf("toml: expected %s to be a table, not a %s", string(k), entry.kind) + } + } + + parentIdx = idx + } + + k := it.Node().Data + idx := s.find(parentIdx, k) + + firstTime := idx < 0 + if firstTime { + idx = s.create(parentIdx, k, arrayTableKind, true, false) + } else { + kind := s.entries[idx].kind + if kind != arrayTableKind { + return false, fmt.Errorf("toml: key %s already exists as a %s, but should be an array table", kind, string(k)) + } + s.clear(idx) + } + + s.currentIdx = idx + + return firstTime, nil +} + +func (s *SeenTracker) checkKeyValue(node *unstable.Node) (bool, error) { + parentIdx := s.currentIdx + it := node.Key() + + for it.Next() { + k := it.Node().Data + + idx := s.find(parentIdx, k) + + if idx < 0 { + idx = s.create(parentIdx, k, tableKind, false, true) + } else { + entry := s.entries[idx] + if it.IsLast() { + return false, fmt.Errorf("toml: key %s is already defined", string(k)) + } else if entry.kind != tableKind { + return false, fmt.Errorf("toml: expected %s to be a table, not a %s", string(k), entry.kind) + } else if entry.explicit { + return false, fmt.Errorf("toml: cannot redefine table %s that has already been explicitly defined", string(k)) + } + } + + parentIdx = idx + } + + s.entries[parentIdx].kind = valueKind + + value := node.Value() + + switch value.Kind { + case unstable.InlineTable: + return s.checkInlineTable(value) + case unstable.Array: + return s.checkArray(value) + } + + return false, nil +} + +func (s *SeenTracker) checkArray(node *unstable.Node) (first bool, err error) { + it := node.Children() + for it.Next() { + n := it.Node() + switch n.Kind { + case unstable.InlineTable: + first, err = s.checkInlineTable(n) + if err != nil { + return false, err + } + case unstable.Array: + first, err = s.checkArray(n) + if err != nil { + return false, err + } + } + } + return first, nil +} + +func (s *SeenTracker) checkInlineTable(node *unstable.Node) (first bool, err error) { + s = pool.Get().(*SeenTracker) + s.reset() + + it := node.Children() + for it.Next() { + n := it.Node() + first, err = s.checkKeyValue(n) + if err != nil { + return false, err + } + } + + // As inline tables are self-contained, the tracker does not + // need to retain the details of what they contain. The + // keyValue element that creates the inline table is kept to + // mark the presence of the inline table and prevent + // redefinition of its keys: check* functions cannot walk into + // a value. + pool.Put(s) + return first, nil +} diff --git a/vendor/github.com/pelletier/go-toml/v2/internal/tracker/tracker.go b/vendor/github.com/pelletier/go-toml/v2/internal/tracker/tracker.go new file mode 100644 index 0000000000..bf0317392f --- /dev/null +++ b/vendor/github.com/pelletier/go-toml/v2/internal/tracker/tracker.go @@ -0,0 +1 @@ +package tracker diff --git a/vendor/github.com/pelletier/go-toml/v2/localtime.go b/vendor/github.com/pelletier/go-toml/v2/localtime.go new file mode 100644 index 0000000000..a856bfdb0d --- /dev/null +++ b/vendor/github.com/pelletier/go-toml/v2/localtime.go @@ -0,0 +1,122 @@ +package toml + +import ( + "fmt" + "strings" + "time" + + "github.com/pelletier/go-toml/v2/unstable" +) + +// LocalDate represents a calendar day in no specific timezone. +type LocalDate struct { + Year int + Month int + Day int +} + +// AsTime converts d into a specific time instance at midnight in zone. +func (d LocalDate) AsTime(zone *time.Location) time.Time { + return time.Date(d.Year, time.Month(d.Month), d.Day, 0, 0, 0, 0, zone) +} + +// String returns RFC 3339 representation of d. +func (d LocalDate) String() string { + return fmt.Sprintf("%04d-%02d-%02d", d.Year, d.Month, d.Day) +} + +// MarshalText returns RFC 3339 representation of d. +func (d LocalDate) MarshalText() ([]byte, error) { + return []byte(d.String()), nil +} + +// UnmarshalText parses b using RFC 3339 to fill d. +func (d *LocalDate) UnmarshalText(b []byte) error { + res, err := parseLocalDate(b) + if err != nil { + return err + } + *d = res + return nil +} + +// LocalTime represents a time of day of no specific day in no specific +// timezone. +type LocalTime struct { + Hour int // Hour of the day: [0; 24[ + Minute int // Minute of the hour: [0; 60[ + Second int // Second of the minute: [0; 60[ + Nanosecond int // Nanoseconds within the second: [0, 1000000000[ + Precision int // Number of digits to display for Nanosecond. +} + +// String returns RFC 3339 representation of d. +// If d.Nanosecond and d.Precision are zero, the time won't have a nanosecond +// component. If d.Nanosecond > 0 but d.Precision = 0, then the minimum number +// of digits for nanoseconds is provided. +func (d LocalTime) String() string { + s := fmt.Sprintf("%02d:%02d:%02d", d.Hour, d.Minute, d.Second) + + if d.Precision > 0 { + s += fmt.Sprintf(".%09d", d.Nanosecond)[:d.Precision+1] + } else if d.Nanosecond > 0 { + // Nanoseconds are specified, but precision is not provided. Use the + // minimum. + s += strings.Trim(fmt.Sprintf(".%09d", d.Nanosecond), "0") + } + + return s +} + +// MarshalText returns RFC 3339 representation of d. +func (d LocalTime) MarshalText() ([]byte, error) { + return []byte(d.String()), nil +} + +// UnmarshalText parses b using RFC 3339 to fill d. +func (d *LocalTime) UnmarshalText(b []byte) error { + res, left, err := parseLocalTime(b) + if err == nil && len(left) != 0 { + err = unstable.NewParserError(left, "extra characters") + } + if err != nil { + return err + } + *d = res + return nil +} + +// LocalDateTime represents a time of a specific day in no specific timezone. +type LocalDateTime struct { + LocalDate + LocalTime +} + +// AsTime converts d into a specific time instance in zone. +func (d LocalDateTime) AsTime(zone *time.Location) time.Time { + return time.Date(d.Year, time.Month(d.Month), d.Day, d.Hour, d.Minute, d.Second, d.Nanosecond, zone) +} + +// String returns RFC 3339 representation of d. +func (d LocalDateTime) String() string { + return d.LocalDate.String() + "T" + d.LocalTime.String() +} + +// MarshalText returns RFC 3339 representation of d. +func (d LocalDateTime) MarshalText() ([]byte, error) { + return []byte(d.String()), nil +} + +// UnmarshalText parses b using RFC 3339 to fill d. +func (d *LocalDateTime) UnmarshalText(data []byte) error { + res, left, err := parseLocalDateTime(data) + if err == nil && len(left) != 0 { + err = unstable.NewParserError(left, "extra characters") + } + if err != nil { + return err + } + + *d = res + return nil +} diff --git a/vendor/github.com/pelletier/go-toml/v2/marshaler.go b/vendor/github.com/pelletier/go-toml/v2/marshaler.go new file mode 100644 index 0000000000..161acd9343 --- /dev/null +++ b/vendor/github.com/pelletier/go-toml/v2/marshaler.go @@ -0,0 +1,1133 @@ +package toml + +import ( + "bytes" + "encoding" + "encoding/json" + "fmt" + "io" + "math" + "reflect" + "slices" + "strconv" + "strings" + "time" + "unicode" + + "github.com/pelletier/go-toml/v2/internal/characters" +) + +// Marshal serializes a Go value as a TOML document. +// +// It is a shortcut for Encoder.Encode() with the default options. +func Marshal(v interface{}) ([]byte, error) { + var buf bytes.Buffer + enc := NewEncoder(&buf) + + err := enc.Encode(v) + if err != nil { + return nil, err + } + + return buf.Bytes(), nil +} + +// Encoder writes a TOML document to an output stream. +type Encoder struct { + // output + w io.Writer + + // global settings + tablesInline bool + arraysMultiline bool + indentSymbol string + indentTables bool + marshalJsonNumbers bool +} + +// NewEncoder returns a new Encoder that writes to w. +func NewEncoder(w io.Writer) *Encoder { + return &Encoder{ + w: w, + indentSymbol: " ", + } +} + +// SetTablesInline forces the encoder to emit all tables inline. +// +// This behavior can be controlled on an individual struct field basis with the +// inline tag: +// +// MyField `toml:",inline"` +func (enc *Encoder) SetTablesInline(inline bool) *Encoder { + enc.tablesInline = inline + return enc +} + +// SetArraysMultiline forces the encoder to emit all arrays with one element per +// line. +// +// This behavior can be controlled on an individual struct field basis with the multiline tag: +// +// MyField `multiline:"true"` +func (enc *Encoder) SetArraysMultiline(multiline bool) *Encoder { + enc.arraysMultiline = multiline + return enc +} + +// SetIndentSymbol defines the string that should be used for indentation. The +// provided string is repeated for each indentation level. Defaults to two +// spaces. +func (enc *Encoder) SetIndentSymbol(s string) *Encoder { + enc.indentSymbol = s + return enc +} + +// SetIndentTables forces the encoder to intent tables and array tables. +func (enc *Encoder) SetIndentTables(indent bool) *Encoder { + enc.indentTables = indent + return enc +} + +// SetMarshalJsonNumbers forces the encoder to serialize `json.Number` as a +// float or integer instead of relying on TextMarshaler to emit a string. +// +// *Unstable:* This method does not follow the compatibility guarantees of +// semver. It can be changed or removed without a new major version being +// issued. +func (enc *Encoder) SetMarshalJsonNumbers(indent bool) *Encoder { + enc.marshalJsonNumbers = indent + return enc +} + +// Encode writes a TOML representation of v to the stream. +// +// If v cannot be represented to TOML it returns an error. +// +// # Encoding rules +// +// A top level slice containing only maps or structs is encoded as [[table +// array]]. +// +// All slices not matching rule 1 are encoded as [array]. As a result, any map +// or struct they contain is encoded as an {inline table}. +// +// Nil interfaces and nil pointers are not supported. +// +// Keys in key-values always have one part. +// +// Intermediate tables are always printed. +// +// By default, strings are encoded as literal string, unless they contain either +// a newline character or a single quote. In that case they are emitted as +// quoted strings. +// +// Unsigned integers larger than math.MaxInt64 cannot be encoded. Doing so +// results in an error. This rule exists because the TOML specification only +// requires parsers to support at least the 64 bits integer range. Allowing +// larger numbers would create non-standard TOML documents, which may not be +// readable (at best) by other implementations. To encode such numbers, a +// solution is a custom type that implements encoding.TextMarshaler. +// +// When encoding structs, fields are encoded in order of definition, with their +// exact name. +// +// Tables and array tables are separated by empty lines. However, consecutive +// subtables definitions are not. For example: +// +// [top1] +// +// [top2] +// [top2.child1] +// +// [[array]] +// +// [[array]] +// [array.child2] +// +// # Struct tags +// +// The encoding of each public struct field can be customized by the format +// string in the "toml" key of the struct field's tag. This follows +// encoding/json's convention. The format string starts with the name of the +// field, optionally followed by a comma-separated list of options. The name may +// be empty in order to provide options without overriding the default name. +// +// The "multiline" option emits strings as quoted multi-line TOML strings. It +// has no effect on fields that would not be encoded as strings. +// +// The "inline" option turns fields that would be emitted as tables into inline +// tables instead. It has no effect on other fields. +// +// The "omitempty" option prevents empty values or groups from being emitted. +// +// The "commented" option prefixes the value and all its children with a comment +// symbol. +// +// In addition to the "toml" tag struct tag, a "comment" tag can be used to emit +// a TOML comment before the value being annotated. Comments are ignored inside +// inline tables. For array tables, the comment is only present before the first +// element of the array. +func (enc *Encoder) Encode(v interface{}) error { + var ( + b []byte + ctx encoderCtx + ) + + ctx.inline = enc.tablesInline + + if v == nil { + return fmt.Errorf("toml: cannot encode a nil interface") + } + + b, err := enc.encode(b, ctx, reflect.ValueOf(v)) + if err != nil { + return err + } + + _, err = enc.w.Write(b) + if err != nil { + return fmt.Errorf("toml: cannot write: %w", err) + } + + return nil +} + +type valueOptions struct { + multiline bool + omitempty bool + commented bool + comment string +} + +type encoderCtx struct { + // Current top-level key. + parentKey []string + + // Key that should be used for a KV. + key string + // Extra flag to account for the empty string + hasKey bool + + // Set to true to indicate that the encoder is inside a KV, so that all + // tables need to be inlined. + insideKv bool + + // Set to true to skip the first table header in an array table. + skipTableHeader bool + + // Should the next table be encoded as inline + inline bool + + // Indentation level + indent int + + // Prefix the current value with a comment. + commented bool + + // Options coming from struct tags + options valueOptions +} + +func (ctx *encoderCtx) shiftKey() { + if ctx.hasKey { + ctx.parentKey = append(ctx.parentKey, ctx.key) + ctx.clearKey() + } +} + +func (ctx *encoderCtx) setKey(k string) { + ctx.key = k + ctx.hasKey = true +} + +func (ctx *encoderCtx) clearKey() { + ctx.key = "" + ctx.hasKey = false +} + +func (ctx *encoderCtx) isRoot() bool { + return len(ctx.parentKey) == 0 && !ctx.hasKey +} + +func (enc *Encoder) encode(b []byte, ctx encoderCtx, v reflect.Value) ([]byte, error) { + i := v.Interface() + + switch x := i.(type) { + case time.Time: + if x.Nanosecond() > 0 { + return x.AppendFormat(b, time.RFC3339Nano), nil + } + return x.AppendFormat(b, time.RFC3339), nil + case LocalTime: + return append(b, x.String()...), nil + case LocalDate: + return append(b, x.String()...), nil + case LocalDateTime: + return append(b, x.String()...), nil + case json.Number: + if enc.marshalJsonNumbers { + if x == "" { /// Useful zero value. + return append(b, "0"...), nil + } else if v, err := x.Int64(); err == nil { + return enc.encode(b, ctx, reflect.ValueOf(v)) + } else if f, err := x.Float64(); err == nil { + return enc.encode(b, ctx, reflect.ValueOf(f)) + } else { + return nil, fmt.Errorf("toml: unable to convert %q to int64 or float64", x) + } + } + } + + hasTextMarshaler := v.Type().Implements(textMarshalerType) + if hasTextMarshaler || (v.CanAddr() && reflect.PointerTo(v.Type()).Implements(textMarshalerType)) { + if !hasTextMarshaler { + v = v.Addr() + } + + if ctx.isRoot() { + return nil, fmt.Errorf("toml: type %s implementing the TextMarshaler interface cannot be a root element", v.Type()) + } + + text, err := v.Interface().(encoding.TextMarshaler).MarshalText() + if err != nil { + return nil, err + } + + b = enc.encodeString(b, string(text), ctx.options) + + return b, nil + } + + switch v.Kind() { + // containers + case reflect.Map: + return enc.encodeMap(b, ctx, v) + case reflect.Struct: + return enc.encodeStruct(b, ctx, v) + case reflect.Slice, reflect.Array: + return enc.encodeSlice(b, ctx, v) + case reflect.Interface: + if v.IsNil() { + return nil, fmt.Errorf("toml: encoding a nil interface is not supported") + } + + return enc.encode(b, ctx, v.Elem()) + case reflect.Ptr: + if v.IsNil() { + return enc.encode(b, ctx, reflect.Zero(v.Type().Elem())) + } + + return enc.encode(b, ctx, v.Elem()) + + // values + case reflect.String: + b = enc.encodeString(b, v.String(), ctx.options) + case reflect.Float32: + f := v.Float() + + if math.IsNaN(f) { + b = append(b, "nan"...) + } else if f > math.MaxFloat32 { + b = append(b, "inf"...) + } else if f < -math.MaxFloat32 { + b = append(b, "-inf"...) + } else if math.Trunc(f) == f { + b = strconv.AppendFloat(b, f, 'f', 1, 32) + } else { + b = strconv.AppendFloat(b, f, 'f', -1, 32) + } + case reflect.Float64: + f := v.Float() + if math.IsNaN(f) { + b = append(b, "nan"...) + } else if f > math.MaxFloat64 { + b = append(b, "inf"...) + } else if f < -math.MaxFloat64 { + b = append(b, "-inf"...) + } else if math.Trunc(f) == f { + b = strconv.AppendFloat(b, f, 'f', 1, 64) + } else { + b = strconv.AppendFloat(b, f, 'f', -1, 64) + } + case reflect.Bool: + if v.Bool() { + b = append(b, "true"...) + } else { + b = append(b, "false"...) + } + case reflect.Uint64, reflect.Uint32, reflect.Uint16, reflect.Uint8, reflect.Uint: + x := v.Uint() + if x > uint64(math.MaxInt64) { + return nil, fmt.Errorf("toml: not encoding uint (%d) greater than max int64 (%d)", x, int64(math.MaxInt64)) + } + b = strconv.AppendUint(b, x, 10) + case reflect.Int64, reflect.Int32, reflect.Int16, reflect.Int8, reflect.Int: + b = strconv.AppendInt(b, v.Int(), 10) + default: + return nil, fmt.Errorf("toml: cannot encode value of type %s", v.Kind()) + } + + return b, nil +} + +func isNil(v reflect.Value) bool { + switch v.Kind() { + case reflect.Ptr, reflect.Interface, reflect.Map: + return v.IsNil() + default: + return false + } +} + +func shouldOmitEmpty(options valueOptions, v reflect.Value) bool { + return options.omitempty && isEmptyValue(v) +} + +func (enc *Encoder) encodeKv(b []byte, ctx encoderCtx, options valueOptions, v reflect.Value) ([]byte, error) { + var err error + + if !ctx.inline { + b = enc.encodeComment(ctx.indent, options.comment, b) + b = enc.commented(ctx.commented, b) + b = enc.indent(ctx.indent, b) + } + + b = enc.encodeKey(b, ctx.key) + b = append(b, " = "...) + + // create a copy of the context because the value of a KV shouldn't + // modify the global context. + subctx := ctx + subctx.insideKv = true + subctx.shiftKey() + subctx.options = options + + b, err = enc.encode(b, subctx, v) + if err != nil { + return nil, err + } + + return b, nil +} + +func (enc *Encoder) commented(commented bool, b []byte) []byte { + if commented { + return append(b, "# "...) + } + return b +} + +func isEmptyValue(v reflect.Value) bool { + switch v.Kind() { + case reflect.Struct: + return isEmptyStruct(v) + case reflect.Array, reflect.Map, reflect.Slice, reflect.String: + return v.Len() == 0 + case reflect.Bool: + return !v.Bool() + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return v.Int() == 0 + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + return v.Uint() == 0 + case reflect.Float32, reflect.Float64: + return v.Float() == 0 + case reflect.Interface, reflect.Ptr: + return v.IsNil() + } + return false +} + +func isEmptyStruct(v reflect.Value) bool { + // TODO: merge with walkStruct and cache. + typ := v.Type() + for i := 0; i < typ.NumField(); i++ { + fieldType := typ.Field(i) + + // only consider exported fields + if fieldType.PkgPath != "" { + continue + } + + tag := fieldType.Tag.Get("toml") + + // special field name to skip field + if tag == "-" { + continue + } + + f := v.Field(i) + + if !isEmptyValue(f) { + return false + } + } + + return true +} + +const literalQuote = '\'' + +func (enc *Encoder) encodeString(b []byte, v string, options valueOptions) []byte { + if needsQuoting(v) { + return enc.encodeQuotedString(options.multiline, b, v) + } + + return enc.encodeLiteralString(b, v) +} + +func needsQuoting(v string) bool { + // TODO: vectorize + for _, b := range []byte(v) { + if b == '\'' || b == '\r' || b == '\n' || characters.InvalidAscii(b) { + return true + } + } + return false +} + +// caller should have checked that the string does not contain new lines or ' . +func (enc *Encoder) encodeLiteralString(b []byte, v string) []byte { + b = append(b, literalQuote) + b = append(b, v...) + b = append(b, literalQuote) + + return b +} + +func (enc *Encoder) encodeQuotedString(multiline bool, b []byte, v string) []byte { + stringQuote := `"` + + if multiline { + stringQuote = `"""` + } + + b = append(b, stringQuote...) + if multiline { + b = append(b, '\n') + } + + const ( + hextable = "0123456789ABCDEF" + // U+0000 to U+0008, U+000A to U+001F, U+007F + nul = 0x0 + bs = 0x8 + lf = 0xa + us = 0x1f + del = 0x7f + ) + + for _, r := range []byte(v) { + switch r { + case '\\': + b = append(b, `\\`...) + case '"': + b = append(b, `\"`...) + case '\b': + b = append(b, `\b`...) + case '\f': + b = append(b, `\f`...) + case '\n': + if multiline { + b = append(b, r) + } else { + b = append(b, `\n`...) + } + case '\r': + b = append(b, `\r`...) + case '\t': + b = append(b, `\t`...) + default: + switch { + case r >= nul && r <= bs, r >= lf && r <= us, r == del: + b = append(b, `\u00`...) + b = append(b, hextable[r>>4]) + b = append(b, hextable[r&0x0f]) + default: + b = append(b, r) + } + } + } + + b = append(b, stringQuote...) + + return b +} + +// caller should have checked that the string is in A-Z / a-z / 0-9 / - / _ . +func (enc *Encoder) encodeUnquotedKey(b []byte, v string) []byte { + return append(b, v...) +} + +func (enc *Encoder) encodeTableHeader(ctx encoderCtx, b []byte) ([]byte, error) { + if len(ctx.parentKey) == 0 { + return b, nil + } + + b = enc.encodeComment(ctx.indent, ctx.options.comment, b) + + b = enc.commented(ctx.commented, b) + + b = enc.indent(ctx.indent, b) + + b = append(b, '[') + + b = enc.encodeKey(b, ctx.parentKey[0]) + + for _, k := range ctx.parentKey[1:] { + b = append(b, '.') + b = enc.encodeKey(b, k) + } + + b = append(b, "]\n"...) + + return b, nil +} + +//nolint:cyclop +func (enc *Encoder) encodeKey(b []byte, k string) []byte { + needsQuotation := false + cannotUseLiteral := false + + if len(k) == 0 { + return append(b, "''"...) + } + + for _, c := range k { + if (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '-' || c == '_' { + continue + } + + if c == literalQuote { + cannotUseLiteral = true + } + + needsQuotation = true + } + + if needsQuotation && needsQuoting(k) { + cannotUseLiteral = true + } + + switch { + case cannotUseLiteral: + return enc.encodeQuotedString(false, b, k) + case needsQuotation: + return enc.encodeLiteralString(b, k) + default: + return enc.encodeUnquotedKey(b, k) + } +} + +func (enc *Encoder) keyToString(k reflect.Value) (string, error) { + keyType := k.Type() + switch { + case keyType.Kind() == reflect.String: + return k.String(), nil + + case keyType.Implements(textMarshalerType): + keyB, err := k.Interface().(encoding.TextMarshaler).MarshalText() + if err != nil { + return "", fmt.Errorf("toml: error marshalling key %v from text: %w", k, err) + } + return string(keyB), nil + + case keyType.Kind() == reflect.Int || keyType.Kind() == reflect.Int8 || keyType.Kind() == reflect.Int16 || keyType.Kind() == reflect.Int32 || keyType.Kind() == reflect.Int64: + return strconv.FormatInt(k.Int(), 10), nil + + case keyType.Kind() == reflect.Uint || keyType.Kind() == reflect.Uint8 || keyType.Kind() == reflect.Uint16 || keyType.Kind() == reflect.Uint32 || keyType.Kind() == reflect.Uint64: + return strconv.FormatUint(k.Uint(), 10), nil + + case keyType.Kind() == reflect.Float32: + return strconv.FormatFloat(k.Float(), 'f', -1, 32), nil + + case keyType.Kind() == reflect.Float64: + return strconv.FormatFloat(k.Float(), 'f', -1, 64), nil + } + return "", fmt.Errorf("toml: type %s is not supported as a map key", keyType.Kind()) +} + +func (enc *Encoder) encodeMap(b []byte, ctx encoderCtx, v reflect.Value) ([]byte, error) { + var ( + t table + emptyValueOptions valueOptions + ) + + iter := v.MapRange() + for iter.Next() { + v := iter.Value() + + if isNil(v) { + continue + } + + k, err := enc.keyToString(iter.Key()) + if err != nil { + return nil, err + } + + if willConvertToTableOrArrayTable(ctx, v) { + t.pushTable(k, v, emptyValueOptions) + } else { + t.pushKV(k, v, emptyValueOptions) + } + } + + sortEntriesByKey(t.kvs) + sortEntriesByKey(t.tables) + + return enc.encodeTable(b, ctx, t) +} + +func sortEntriesByKey(e []entry) { + slices.SortFunc(e, func(a, b entry) int { + return strings.Compare(a.Key, b.Key) + }) +} + +type entry struct { + Key string + Value reflect.Value + Options valueOptions +} + +type table struct { + kvs []entry + tables []entry +} + +func (t *table) pushKV(k string, v reflect.Value, options valueOptions) { + for _, e := range t.kvs { + if e.Key == k { + return + } + } + + t.kvs = append(t.kvs, entry{Key: k, Value: v, Options: options}) +} + +func (t *table) pushTable(k string, v reflect.Value, options valueOptions) { + for _, e := range t.tables { + if e.Key == k { + return + } + } + t.tables = append(t.tables, entry{Key: k, Value: v, Options: options}) +} + +func walkStruct(ctx encoderCtx, t *table, v reflect.Value) { + // TODO: cache this + typ := v.Type() + for i := 0; i < typ.NumField(); i++ { + fieldType := typ.Field(i) + + // only consider exported fields + if fieldType.PkgPath != "" { + continue + } + + tag := fieldType.Tag.Get("toml") + + // special field name to skip field + if tag == "-" { + continue + } + + k, opts := parseTag(tag) + if !isValidName(k) { + k = "" + } + + f := v.Field(i) + + if k == "" { + if fieldType.Anonymous { + if fieldType.Type.Kind() == reflect.Struct { + walkStruct(ctx, t, f) + } else if fieldType.Type.Kind() == reflect.Ptr && !f.IsNil() && f.Elem().Kind() == reflect.Struct { + walkStruct(ctx, t, f.Elem()) + } + continue + } else { + k = fieldType.Name + } + } + + if isNil(f) { + continue + } + + options := valueOptions{ + multiline: opts.multiline, + omitempty: opts.omitempty, + commented: opts.commented, + comment: fieldType.Tag.Get("comment"), + } + + if opts.inline || !willConvertToTableOrArrayTable(ctx, f) { + t.pushKV(k, f, options) + } else { + t.pushTable(k, f, options) + } + } +} + +func (enc *Encoder) encodeStruct(b []byte, ctx encoderCtx, v reflect.Value) ([]byte, error) { + var t table + + walkStruct(ctx, &t, v) + + return enc.encodeTable(b, ctx, t) +} + +func (enc *Encoder) encodeComment(indent int, comment string, b []byte) []byte { + for len(comment) > 0 { + var line string + idx := strings.IndexByte(comment, '\n') + if idx >= 0 { + line = comment[:idx] + comment = comment[idx+1:] + } else { + line = comment + comment = "" + } + b = enc.indent(indent, b) + b = append(b, "# "...) + b = append(b, line...) + b = append(b, '\n') + } + return b +} + +func isValidName(s string) bool { + if s == "" { + return false + } + for _, c := range s { + switch { + case strings.ContainsRune("!#$%&()*+-./:;<=>?@[]^_{|}~ ", c): + // Backslash and quote chars are reserved, but + // otherwise any punctuation chars are allowed + // in a tag name. + case !unicode.IsLetter(c) && !unicode.IsDigit(c): + return false + } + } + return true +} + +type tagOptions struct { + multiline bool + inline bool + omitempty bool + commented bool +} + +func parseTag(tag string) (string, tagOptions) { + opts := tagOptions{} + + idx := strings.Index(tag, ",") + if idx == -1 { + return tag, opts + } + + raw := tag[idx+1:] + tag = string(tag[:idx]) + for raw != "" { + var o string + i := strings.Index(raw, ",") + if i >= 0 { + o, raw = raw[:i], raw[i+1:] + } else { + o, raw = raw, "" + } + switch o { + case "multiline": + opts.multiline = true + case "inline": + opts.inline = true + case "omitempty": + opts.omitempty = true + case "commented": + opts.commented = true + } + } + + return tag, opts +} + +func (enc *Encoder) encodeTable(b []byte, ctx encoderCtx, t table) ([]byte, error) { + var err error + + ctx.shiftKey() + + if ctx.insideKv || (ctx.inline && !ctx.isRoot()) { + return enc.encodeTableInline(b, ctx, t) + } + + if !ctx.skipTableHeader { + b, err = enc.encodeTableHeader(ctx, b) + if err != nil { + return nil, err + } + + if enc.indentTables && len(ctx.parentKey) > 0 { + ctx.indent++ + } + } + ctx.skipTableHeader = false + + hasNonEmptyKV := false + for _, kv := range t.kvs { + if shouldOmitEmpty(kv.Options, kv.Value) { + continue + } + hasNonEmptyKV = true + + ctx.setKey(kv.Key) + ctx2 := ctx + ctx2.commented = kv.Options.commented || ctx2.commented + + b, err = enc.encodeKv(b, ctx2, kv.Options, kv.Value) + if err != nil { + return nil, err + } + + b = append(b, '\n') + } + + first := true + for _, table := range t.tables { + if shouldOmitEmpty(table.Options, table.Value) { + continue + } + if first { + first = false + if hasNonEmptyKV { + b = append(b, '\n') + } + } else { + b = append(b, "\n"...) + } + + ctx.setKey(table.Key) + + ctx.options = table.Options + ctx2 := ctx + ctx2.commented = ctx2.commented || ctx.options.commented + + b, err = enc.encode(b, ctx2, table.Value) + if err != nil { + return nil, err + } + } + + return b, nil +} + +func (enc *Encoder) encodeTableInline(b []byte, ctx encoderCtx, t table) ([]byte, error) { + var err error + + b = append(b, '{') + + first := true + for _, kv := range t.kvs { + if shouldOmitEmpty(kv.Options, kv.Value) { + continue + } + + if first { + first = false + } else { + b = append(b, `, `...) + } + + ctx.setKey(kv.Key) + + b, err = enc.encodeKv(b, ctx, kv.Options, kv.Value) + if err != nil { + return nil, err + } + } + + if len(t.tables) > 0 { + panic("inline table cannot contain nested tables, only key-values") + } + + b = append(b, "}"...) + + return b, nil +} + +func willConvertToTable(ctx encoderCtx, v reflect.Value) bool { + if !v.IsValid() { + return false + } + if v.Type() == timeType || v.Type().Implements(textMarshalerType) || (v.Kind() != reflect.Ptr && v.CanAddr() && reflect.PointerTo(v.Type()).Implements(textMarshalerType)) { + return false + } + + t := v.Type() + switch t.Kind() { + case reflect.Map, reflect.Struct: + return !ctx.inline + case reflect.Interface: + return willConvertToTable(ctx, v.Elem()) + case reflect.Ptr: + if v.IsNil() { + return false + } + + return willConvertToTable(ctx, v.Elem()) + default: + return false + } +} + +func willConvertToTableOrArrayTable(ctx encoderCtx, v reflect.Value) bool { + if ctx.insideKv { + return false + } + t := v.Type() + + if t.Kind() == reflect.Interface { + return willConvertToTableOrArrayTable(ctx, v.Elem()) + } + + if t.Kind() == reflect.Slice || t.Kind() == reflect.Array { + if v.Len() == 0 { + // An empty slice should be a kv = []. + return false + } + + for i := 0; i < v.Len(); i++ { + t := willConvertToTable(ctx, v.Index(i)) + + if !t { + return false + } + } + + return true + } + + return willConvertToTable(ctx, v) +} + +func (enc *Encoder) encodeSlice(b []byte, ctx encoderCtx, v reflect.Value) ([]byte, error) { + if v.Len() == 0 { + b = append(b, "[]"...) + + return b, nil + } + + if willConvertToTableOrArrayTable(ctx, v) { + return enc.encodeSliceAsArrayTable(b, ctx, v) + } + + return enc.encodeSliceAsArray(b, ctx, v) +} + +// caller should have checked that v is a slice that only contains values that +// encode into tables. +func (enc *Encoder) encodeSliceAsArrayTable(b []byte, ctx encoderCtx, v reflect.Value) ([]byte, error) { + ctx.shiftKey() + + scratch := make([]byte, 0, 64) + + scratch = enc.commented(ctx.commented, scratch) + + if enc.indentTables { + scratch = enc.indent(ctx.indent, scratch) + } + + scratch = append(scratch, "[["...) + + for i, k := range ctx.parentKey { + if i > 0 { + scratch = append(scratch, '.') + } + + scratch = enc.encodeKey(scratch, k) + } + + scratch = append(scratch, "]]\n"...) + ctx.skipTableHeader = true + + b = enc.encodeComment(ctx.indent, ctx.options.comment, b) + + if enc.indentTables { + ctx.indent++ + } + + for i := 0; i < v.Len(); i++ { + if i != 0 { + b = append(b, "\n"...) + } + + b = append(b, scratch...) + + var err error + b, err = enc.encode(b, ctx, v.Index(i)) + if err != nil { + return nil, err + } + } + + return b, nil +} + +func (enc *Encoder) encodeSliceAsArray(b []byte, ctx encoderCtx, v reflect.Value) ([]byte, error) { + multiline := ctx.options.multiline || enc.arraysMultiline + separator := ", " + + b = append(b, '[') + + subCtx := ctx + subCtx.options = valueOptions{} + + if multiline { + separator = ",\n" + + b = append(b, '\n') + + subCtx.indent++ + } + + var err error + first := true + + for i := 0; i < v.Len(); i++ { + if first { + first = false + } else { + b = append(b, separator...) + } + + if multiline { + b = enc.indent(subCtx.indent, b) + } + + b, err = enc.encode(b, subCtx, v.Index(i)) + if err != nil { + return nil, err + } + } + + if multiline { + b = append(b, '\n') + b = enc.indent(ctx.indent, b) + } + + b = append(b, ']') + + return b, nil +} + +func (enc *Encoder) indent(level int, b []byte) []byte { + for i := 0; i < level; i++ { + b = append(b, enc.indentSymbol...) + } + + return b +} diff --git a/vendor/github.com/pelletier/go-toml/v2/strict.go b/vendor/github.com/pelletier/go-toml/v2/strict.go new file mode 100644 index 0000000000..802e7e4d15 --- /dev/null +++ b/vendor/github.com/pelletier/go-toml/v2/strict.go @@ -0,0 +1,107 @@ +package toml + +import ( + "github.com/pelletier/go-toml/v2/internal/danger" + "github.com/pelletier/go-toml/v2/internal/tracker" + "github.com/pelletier/go-toml/v2/unstable" +) + +type strict struct { + Enabled bool + + // Tracks the current key being processed. + key tracker.KeyTracker + + missing []unstable.ParserError +} + +func (s *strict) EnterTable(node *unstable.Node) { + if !s.Enabled { + return + } + + s.key.UpdateTable(node) +} + +func (s *strict) EnterArrayTable(node *unstable.Node) { + if !s.Enabled { + return + } + + s.key.UpdateArrayTable(node) +} + +func (s *strict) EnterKeyValue(node *unstable.Node) { + if !s.Enabled { + return + } + + s.key.Push(node) +} + +func (s *strict) ExitKeyValue(node *unstable.Node) { + if !s.Enabled { + return + } + + s.key.Pop(node) +} + +func (s *strict) MissingTable(node *unstable.Node) { + if !s.Enabled { + return + } + + s.missing = append(s.missing, unstable.ParserError{ + Highlight: keyLocation(node), + Message: "missing table", + Key: s.key.Key(), + }) +} + +func (s *strict) MissingField(node *unstable.Node) { + if !s.Enabled { + return + } + + s.missing = append(s.missing, unstable.ParserError{ + Highlight: keyLocation(node), + Message: "missing field", + Key: s.key.Key(), + }) +} + +func (s *strict) Error(doc []byte) error { + if !s.Enabled || len(s.missing) == 0 { + return nil + } + + err := &StrictMissingError{ + Errors: make([]DecodeError, 0, len(s.missing)), + } + + for _, derr := range s.missing { + derr := derr + err.Errors = append(err.Errors, *wrapDecodeError(doc, &derr)) + } + + return err +} + +func keyLocation(node *unstable.Node) []byte { + k := node.Key() + + hasOne := k.Next() + if !hasOne { + panic("should not be called with empty key") + } + + start := k.Node().Data + end := k.Node().Data + + for k.Next() { + end = k.Node().Data + } + + return danger.BytesRange(start, end) +} diff --git a/vendor/github.com/pelletier/go-toml/v2/toml.abnf b/vendor/github.com/pelletier/go-toml/v2/toml.abnf new file mode 100644 index 0000000000..473f3749e8 --- /dev/null +++ b/vendor/github.com/pelletier/go-toml/v2/toml.abnf @@ -0,0 +1,243 @@ +;; This document describes TOML's syntax, using the ABNF format (defined in +;; RFC 5234 -- https://www.ietf.org/rfc/rfc5234.txt). +;; +;; All valid TOML documents will match this description, however certain +;; invalid documents would need to be rejected as per the semantics described +;; in the supporting text description. + +;; It is possible to try this grammar interactively, using instaparse. +;; http://instaparse.mojombo.com/ +;; +;; To do so, in the lower right, click on Options and change `:input-format` to +;; ':abnf'. Then paste this entire ABNF document into the grammar entry box +;; (above the options). Then you can type or paste a sample TOML document into +;; the beige box on the left. Tada! + +;; Overall Structure + +toml = expression *( newline expression ) + +expression = ws [ comment ] +expression =/ ws keyval ws [ comment ] +expression =/ ws table ws [ comment ] + +;; Whitespace + +ws = *wschar +wschar = %x20 ; Space +wschar =/ %x09 ; Horizontal tab + +;; Newline + +newline = %x0A ; LF +newline =/ %x0D.0A ; CRLF + +;; Comment + +comment-start-symbol = %x23 ; # +non-ascii = %x80-D7FF / %xE000-10FFFF +non-eol = %x09 / %x20-7F / non-ascii + +comment = comment-start-symbol *non-eol + +;; Key-Value pairs + +keyval = key keyval-sep val + +key = simple-key / dotted-key +simple-key = quoted-key / unquoted-key + +unquoted-key = 1*( ALPHA / DIGIT / %x2D / %x5F ) ; A-Z / a-z / 0-9 / - / _ +quoted-key = basic-string / literal-string +dotted-key = simple-key 1*( dot-sep simple-key ) + +dot-sep = ws %x2E ws ; . Period +keyval-sep = ws %x3D ws ; = + +val = string / boolean / array / inline-table / date-time / float / integer + +;; String + +string = ml-basic-string / basic-string / ml-literal-string / literal-string + +;; Basic String + +basic-string = quotation-mark *basic-char quotation-mark + +quotation-mark = %x22 ; " + +basic-char = basic-unescaped / escaped +basic-unescaped = wschar / %x21 / %x23-5B / %x5D-7E / non-ascii +escaped = escape escape-seq-char + +escape = %x5C ; \ +escape-seq-char = %x22 ; " quotation mark U+0022 +escape-seq-char =/ %x5C ; \ reverse solidus U+005C +escape-seq-char =/ %x62 ; b backspace U+0008 +escape-seq-char =/ %x66 ; f form feed U+000C +escape-seq-char =/ %x6E ; n line feed U+000A +escape-seq-char =/ %x72 ; r carriage return U+000D +escape-seq-char =/ %x74 ; t tab U+0009 +escape-seq-char =/ %x75 4HEXDIG ; uXXXX U+XXXX +escape-seq-char =/ %x55 8HEXDIG ; UXXXXXXXX U+XXXXXXXX + +;; Multiline Basic String + +ml-basic-string = ml-basic-string-delim [ newline ] ml-basic-body + ml-basic-string-delim +ml-basic-string-delim = 3quotation-mark +ml-basic-body = *mlb-content *( mlb-quotes 1*mlb-content ) [ mlb-quotes ] + +mlb-content = mlb-char / newline / mlb-escaped-nl +mlb-char = mlb-unescaped / escaped +mlb-quotes = 1*2quotation-mark +mlb-unescaped = wschar / %x21 / %x23-5B / %x5D-7E / non-ascii +mlb-escaped-nl = escape ws newline *( wschar / newline ) + +;; Literal String + +literal-string = apostrophe *literal-char apostrophe + +apostrophe = %x27 ; ' apostrophe + +literal-char = %x09 / %x20-26 / %x28-7E / non-ascii + +;; Multiline Literal String + +ml-literal-string = ml-literal-string-delim [ newline ] ml-literal-body + ml-literal-string-delim +ml-literal-string-delim = 3apostrophe +ml-literal-body = *mll-content *( mll-quotes 1*mll-content ) [ mll-quotes ] + +mll-content = mll-char / newline +mll-char = %x09 / %x20-26 / %x28-7E / non-ascii +mll-quotes = 1*2apostrophe + +;; Integer + +integer = dec-int / hex-int / oct-int / bin-int + +minus = %x2D ; - +plus = %x2B ; + +underscore = %x5F ; _ +digit1-9 = %x31-39 ; 1-9 +digit0-7 = %x30-37 ; 0-7 +digit0-1 = %x30-31 ; 0-1 + +hex-prefix = %x30.78 ; 0x +oct-prefix = %x30.6F ; 0o +bin-prefix = %x30.62 ; 0b + +dec-int = [ minus / plus ] unsigned-dec-int +unsigned-dec-int = DIGIT / digit1-9 1*( DIGIT / underscore DIGIT ) + +hex-int = hex-prefix HEXDIG *( HEXDIG / underscore HEXDIG ) +oct-int = oct-prefix digit0-7 *( digit0-7 / underscore digit0-7 ) +bin-int = bin-prefix digit0-1 *( digit0-1 / underscore digit0-1 ) + +;; Float + +float = float-int-part ( exp / frac [ exp ] ) +float =/ special-float + +float-int-part = dec-int +frac = decimal-point zero-prefixable-int +decimal-point = %x2E ; . +zero-prefixable-int = DIGIT *( DIGIT / underscore DIGIT ) + +exp = "e" float-exp-part +float-exp-part = [ minus / plus ] zero-prefixable-int + +special-float = [ minus / plus ] ( inf / nan ) +inf = %x69.6e.66 ; inf +nan = %x6e.61.6e ; nan + +;; Boolean + +boolean = true / false + +true = %x74.72.75.65 ; true +false = %x66.61.6C.73.65 ; false + +;; Date and Time (as defined in RFC 3339) + +date-time = offset-date-time / local-date-time / local-date / local-time + +date-fullyear = 4DIGIT +date-month = 2DIGIT ; 01-12 +date-mday = 2DIGIT ; 01-28, 01-29, 01-30, 01-31 based on month/year +time-delim = "T" / %x20 ; T, t, or space +time-hour = 2DIGIT ; 00-23 +time-minute = 2DIGIT ; 00-59 +time-second = 2DIGIT ; 00-58, 00-59, 00-60 based on leap second rules +time-secfrac = "." 1*DIGIT +time-numoffset = ( "+" / "-" ) time-hour ":" time-minute +time-offset = "Z" / time-numoffset + +partial-time = time-hour ":" time-minute ":" time-second [ time-secfrac ] +full-date = date-fullyear "-" date-month "-" date-mday +full-time = partial-time time-offset + +;; Offset Date-Time + +offset-date-time = full-date time-delim full-time + +;; Local Date-Time + +local-date-time = full-date time-delim partial-time + +;; Local Date + +local-date = full-date + +;; Local Time + +local-time = partial-time + +;; Array + +array = array-open [ array-values ] ws-comment-newline array-close + +array-open = %x5B ; [ +array-close = %x5D ; ] + +array-values = ws-comment-newline val ws-comment-newline array-sep array-values +array-values =/ ws-comment-newline val ws-comment-newline [ array-sep ] + +array-sep = %x2C ; , Comma + +ws-comment-newline = *( wschar / [ comment ] newline ) + +;; Table + +table = std-table / array-table + +;; Standard Table + +std-table = std-table-open key std-table-close + +std-table-open = %x5B ws ; [ Left square bracket +std-table-close = ws %x5D ; ] Right square bracket + +;; Inline Table + +inline-table = inline-table-open [ inline-table-keyvals ] inline-table-close + +inline-table-open = %x7B ws ; { +inline-table-close = ws %x7D ; } +inline-table-sep = ws %x2C ws ; , Comma + +inline-table-keyvals = keyval [ inline-table-sep inline-table-keyvals ] + +;; Array Table + +array-table = array-table-open key array-table-close + +array-table-open = %x5B.5B ws ; [[ Double left square bracket +array-table-close = ws %x5D.5D ; ]] Double right square bracket + +;; Built-in ABNF terms, reproduced here for clarity + +ALPHA = %x41-5A / %x61-7A ; A-Z / a-z +DIGIT = %x30-39 ; 0-9 +HEXDIG = DIGIT / "A" / "B" / "C" / "D" / "E" / "F" diff --git a/vendor/github.com/pelletier/go-toml/v2/types.go b/vendor/github.com/pelletier/go-toml/v2/types.go new file mode 100644 index 0000000000..3c6b8fe570 --- /dev/null +++ b/vendor/github.com/pelletier/go-toml/v2/types.go @@ -0,0 +1,14 @@ +package toml + +import ( + "encoding" + "reflect" + "time" +) + +var timeType = reflect.TypeOf((*time.Time)(nil)).Elem() +var textMarshalerType = reflect.TypeOf((*encoding.TextMarshaler)(nil)).Elem() +var textUnmarshalerType = reflect.TypeOf((*encoding.TextUnmarshaler)(nil)).Elem() +var mapStringInterfaceType = reflect.TypeOf(map[string]interface{}(nil)) +var sliceInterfaceType = reflect.TypeOf([]interface{}(nil)) +var stringType = reflect.TypeOf("") diff --git a/vendor/github.com/pelletier/go-toml/v2/unmarshaler.go b/vendor/github.com/pelletier/go-toml/v2/unmarshaler.go new file mode 100644 index 0000000000..189be525e1 --- /dev/null +++ b/vendor/github.com/pelletier/go-toml/v2/unmarshaler.go @@ -0,0 +1,1334 @@ +package toml + +import ( + "encoding" + "errors" + "fmt" + "io" + "math" + "reflect" + "strconv" + "strings" + "sync/atomic" + "time" + + "github.com/pelletier/go-toml/v2/internal/danger" + "github.com/pelletier/go-toml/v2/internal/tracker" + "github.com/pelletier/go-toml/v2/unstable" +) + +// Unmarshal deserializes a TOML document into a Go value. +// +// It is a shortcut for Decoder.Decode() with the default options. +func Unmarshal(data []byte, v interface{}) error { + d := decoder{} + d.p.Reset(data) + return d.FromParser(v) +} + +// Decoder reads and decode a TOML document from an input stream. +type Decoder struct { + // input + r io.Reader + + // global settings + strict bool + + // toggles unmarshaler interface + unmarshalerInterface bool +} + +// NewDecoder creates a new Decoder that will read from r. +func NewDecoder(r io.Reader) *Decoder { + return &Decoder{r: r} +} + +// DisallowUnknownFields causes the Decoder to return an error when the +// destination is a struct and the input contains a key that does not match a +// non-ignored field. +// +// In that case, the Decoder returns a StrictMissingError that can be used to +// retrieve the individual errors as well as generate a human readable +// description of the missing fields. +func (d *Decoder) DisallowUnknownFields() *Decoder { + d.strict = true + return d +} + +// EnableUnmarshalerInterface allows to enable unmarshaler interface. +// +// With this feature enabled, types implementing the unstable/Unmarshaler +// interface can be decoded from any structure of the document. It allows types +// that don't have a straightforward TOML representation to provide their own +// decoding logic. +// +// Currently, types can only decode from a single value. Tables and array tables +// are not supported. +// +// *Unstable:* This method does not follow the compatibility guarantees of +// semver. It can be changed or removed without a new major version being +// issued. +func (d *Decoder) EnableUnmarshalerInterface() *Decoder { + d.unmarshalerInterface = true + return d +} + +// Decode the whole content of r into v. +// +// By default, values in the document that don't exist in the target Go value +// are ignored. See Decoder.DisallowUnknownFields() to change this behavior. +// +// When a TOML local date, time, or date-time is decoded into a time.Time, its +// value is represented in time.Local timezone. Otherwise the appropriate Local* +// structure is used. For time values, precision up to the nanosecond is +// supported by truncating extra digits. +// +// Empty tables decoded in an interface{} create an empty initialized +// map[string]interface{}. +// +// Types implementing the encoding.TextUnmarshaler interface are decoded from a +// TOML string. +// +// When decoding a number, go-toml will return an error if the number is out of +// bounds for the target type (which includes negative numbers when decoding +// into an unsigned int). +// +// If an error occurs while decoding the content of the document, this function +// returns a toml.DecodeError, providing context about the issue. When using +// strict mode and a field is missing, a `toml.StrictMissingError` is +// returned. In any other case, this function returns a standard Go error. +// +// # Type mapping +// +// List of supported TOML types and their associated accepted Go types: +// +// String -> string +// Integer -> uint*, int*, depending on size +// Float -> float*, depending on size +// Boolean -> bool +// Offset Date-Time -> time.Time +// Local Date-time -> LocalDateTime, time.Time +// Local Date -> LocalDate, time.Time +// Local Time -> LocalTime, time.Time +// Array -> slice and array, depending on elements types +// Table -> map and struct +// Inline Table -> same as Table +// Array of Tables -> same as Array and Table +func (d *Decoder) Decode(v interface{}) error { + b, err := io.ReadAll(d.r) + if err != nil { + return fmt.Errorf("toml: %w", err) + } + + dec := decoder{ + strict: strict{ + Enabled: d.strict, + }, + unmarshalerInterface: d.unmarshalerInterface, + } + dec.p.Reset(b) + + return dec.FromParser(v) +} + +type decoder struct { + // Which parser instance in use for this decoding session. + p unstable.Parser + + // Flag indicating that the current expression is stashed. + // If set to true, calling nextExpr will not actually pull a new expression + // but turn off the flag instead. + stashedExpr bool + + // Skip expressions until a table is found. This is set to true when a + // table could not be created (missing field in map), so all KV expressions + // need to be skipped. + skipUntilTable bool + + // Flag indicating that the current array/slice table should be cleared because + // it is the first encounter of an array table. + clearArrayTable bool + + // Tracks position in Go arrays. + // This is used when decoding [[array tables]] into Go arrays. Given array + // tables are separate TOML expression, we need to keep track of where we + // are at in the Go array, as we can't just introspect its size. + arrayIndexes map[reflect.Value]int + + // Tracks keys that have been seen, with which type. + seen tracker.SeenTracker + + // Strict mode + strict strict + + // Flag that enables/disables unmarshaler interface. + unmarshalerInterface bool + + // Current context for the error. + errorContext *errorContext +} + +type errorContext struct { + Struct reflect.Type + Field []int +} + +func (d *decoder) typeMismatchError(toml string, target reflect.Type) error { + return fmt.Errorf("toml: %s", d.typeMismatchString(toml, target)) +} + +func (d *decoder) typeMismatchString(toml string, target reflect.Type) string { + if d.errorContext != nil && d.errorContext.Struct != nil { + ctx := d.errorContext + f := ctx.Struct.FieldByIndex(ctx.Field) + return fmt.Sprintf("cannot decode TOML %s into struct field %s.%s of type %s", toml, ctx.Struct, f.Name, f.Type) + } + return fmt.Sprintf("cannot decode TOML %s into a Go value of type %s", toml, target) +} + +func (d *decoder) expr() *unstable.Node { + return d.p.Expression() +} + +func (d *decoder) nextExpr() bool { + if d.stashedExpr { + d.stashedExpr = false + return true + } + return d.p.NextExpression() +} + +func (d *decoder) stashExpr() { + d.stashedExpr = true +} + +func (d *decoder) arrayIndex(shouldAppend bool, v reflect.Value) int { + if d.arrayIndexes == nil { + d.arrayIndexes = make(map[reflect.Value]int, 1) + } + + idx, ok := d.arrayIndexes[v] + + if !ok { + d.arrayIndexes[v] = 0 + } else if shouldAppend { + idx++ + d.arrayIndexes[v] = idx + } + + return idx +} + +func (d *decoder) FromParser(v interface{}) error { + r := reflect.ValueOf(v) + if r.Kind() != reflect.Ptr { + return fmt.Errorf("toml: decoding can only be performed into a pointer, not %s", r.Kind()) + } + + if r.IsNil() { + return fmt.Errorf("toml: decoding pointer target cannot be nil") + } + + r = r.Elem() + if r.Kind() == reflect.Interface && r.IsNil() { + newMap := map[string]interface{}{} + r.Set(reflect.ValueOf(newMap)) + } + + err := d.fromParser(r) + if err == nil { + return d.strict.Error(d.p.Data()) + } + + var e *unstable.ParserError + if errors.As(err, &e) { + return wrapDecodeError(d.p.Data(), e) + } + + return err +} + +func (d *decoder) fromParser(root reflect.Value) error { + for d.nextExpr() { + err := d.handleRootExpression(d.expr(), root) + if err != nil { + return err + } + } + + return d.p.Error() +} + +/* +Rules for the unmarshal code: + +- The stack is used to keep track of which values need to be set where. +- handle* functions <=> switch on a given unstable.Kind. +- unmarshalX* functions need to unmarshal a node of kind X. +- An "object" is either a struct or a map. +*/ + +func (d *decoder) handleRootExpression(expr *unstable.Node, v reflect.Value) error { + var x reflect.Value + var err error + var first bool // used for to clear array tables on first use + + if !(d.skipUntilTable && expr.Kind == unstable.KeyValue) { + first, err = d.seen.CheckExpression(expr) + if err != nil { + return err + } + } + + switch expr.Kind { + case unstable.KeyValue: + if d.skipUntilTable { + return nil + } + x, err = d.handleKeyValue(expr, v) + case unstable.Table: + d.skipUntilTable = false + d.strict.EnterTable(expr) + x, err = d.handleTable(expr.Key(), v) + case unstable.ArrayTable: + d.skipUntilTable = false + d.strict.EnterArrayTable(expr) + d.clearArrayTable = first + x, err = d.handleArrayTable(expr.Key(), v) + default: + panic(fmt.Errorf("parser should not permit expression of kind %s at document root", expr.Kind)) + } + + if d.skipUntilTable { + if expr.Kind == unstable.Table || expr.Kind == unstable.ArrayTable { + d.strict.MissingTable(expr) + } + } else if err == nil && x.IsValid() { + v.Set(x) + } + + return err +} + +func (d *decoder) handleArrayTable(key unstable.Iterator, v reflect.Value) (reflect.Value, error) { + if key.Next() { + return d.handleArrayTablePart(key, v) + } + return d.handleKeyValues(v) +} + +func (d *decoder) handleArrayTableCollectionLast(key unstable.Iterator, v reflect.Value) (reflect.Value, error) { + switch v.Kind() { + case reflect.Interface: + elem := v.Elem() + if !elem.IsValid() { + elem = reflect.New(sliceInterfaceType).Elem() + elem.Set(reflect.MakeSlice(sliceInterfaceType, 0, 16)) + } else if elem.Kind() == reflect.Slice { + if elem.Type() != sliceInterfaceType { + elem = reflect.New(sliceInterfaceType).Elem() + elem.Set(reflect.MakeSlice(sliceInterfaceType, 0, 16)) + } else if !elem.CanSet() { + nelem := reflect.New(sliceInterfaceType).Elem() + nelem.Set(reflect.MakeSlice(sliceInterfaceType, elem.Len(), elem.Cap())) + reflect.Copy(nelem, elem) + elem = nelem + } + if d.clearArrayTable && elem.Len() > 0 { + elem.SetLen(0) + d.clearArrayTable = false + } + } + return d.handleArrayTableCollectionLast(key, elem) + case reflect.Ptr: + elem := v.Elem() + if !elem.IsValid() { + ptr := reflect.New(v.Type().Elem()) + v.Set(ptr) + elem = ptr.Elem() + } + + elem, err := d.handleArrayTableCollectionLast(key, elem) + if err != nil { + return reflect.Value{}, err + } + v.Elem().Set(elem) + + return v, nil + case reflect.Slice: + if d.clearArrayTable && v.Len() > 0 { + v.SetLen(0) + d.clearArrayTable = false + } + elemType := v.Type().Elem() + var elem reflect.Value + if elemType.Kind() == reflect.Interface { + elem = makeMapStringInterface() + } else { + elem = reflect.New(elemType).Elem() + } + elem2, err := d.handleArrayTable(key, elem) + if err != nil { + return reflect.Value{}, err + } + if elem2.IsValid() { + elem = elem2 + } + return reflect.Append(v, elem), nil + case reflect.Array: + idx := d.arrayIndex(true, v) + if idx >= v.Len() { + return v, fmt.Errorf("%s at position %d", d.typeMismatchError("array table", v.Type()), idx) + } + elem := v.Index(idx) + _, err := d.handleArrayTable(key, elem) + return v, err + default: + return reflect.Value{}, d.typeMismatchError("array table", v.Type()) + } +} + +// When parsing an array table expression, each part of the key needs to be +// evaluated like a normal key, but if it returns a collection, it also needs to +// point to the last element of the collection. Unless it is the last part of +// the key, then it needs to create a new element at the end. +func (d *decoder) handleArrayTableCollection(key unstable.Iterator, v reflect.Value) (reflect.Value, error) { + if key.IsLast() { + return d.handleArrayTableCollectionLast(key, v) + } + + switch v.Kind() { + case reflect.Ptr: + elem := v.Elem() + if !elem.IsValid() { + ptr := reflect.New(v.Type().Elem()) + v.Set(ptr) + elem = ptr.Elem() + } + + elem, err := d.handleArrayTableCollection(key, elem) + if err != nil { + return reflect.Value{}, err + } + if elem.IsValid() { + v.Elem().Set(elem) + } + + return v, nil + case reflect.Slice: + elem := v.Index(v.Len() - 1) + x, err := d.handleArrayTable(key, elem) + if err != nil || d.skipUntilTable { + return reflect.Value{}, err + } + if x.IsValid() { + elem.Set(x) + } + + return v, err + case reflect.Array: + idx := d.arrayIndex(false, v) + if idx >= v.Len() { + return v, fmt.Errorf("%s at position %d", d.typeMismatchError("array table", v.Type()), idx) + } + elem := v.Index(idx) + _, err := d.handleArrayTable(key, elem) + return v, err + } + + return d.handleArrayTable(key, v) +} + +func (d *decoder) handleKeyPart(key unstable.Iterator, v reflect.Value, nextFn handlerFn, makeFn valueMakerFn) (reflect.Value, error) { + var rv reflect.Value + + // First, dispatch over v to make sure it is a valid object. + // There is no guarantee over what it could be. + switch v.Kind() { + case reflect.Ptr: + elem := v.Elem() + if !elem.IsValid() { + v.Set(reflect.New(v.Type().Elem())) + } + elem = v.Elem() + return d.handleKeyPart(key, elem, nextFn, makeFn) + case reflect.Map: + vt := v.Type() + + // Create the key for the map element. Convert to key type. + mk, err := d.keyFromData(vt.Key(), key.Node().Data) + if err != nil { + return reflect.Value{}, err + } + + // If the map does not exist, create it. + if v.IsNil() { + vt := v.Type() + v = reflect.MakeMap(vt) + rv = v + } + + mv := v.MapIndex(mk) + set := false + if !mv.IsValid() { + // If there is no value in the map, create a new one according to + // the map type. If the element type is interface, create either a + // map[string]interface{} or a []interface{} depending on whether + // this is the last part of the array table key. + + t := vt.Elem() + if t.Kind() == reflect.Interface { + mv = makeFn() + } else { + mv = reflect.New(t).Elem() + } + set = true + } else if mv.Kind() == reflect.Interface { + mv = mv.Elem() + if !mv.IsValid() { + mv = makeFn() + } + set = true + } else if !mv.CanAddr() { + vt := v.Type() + t := vt.Elem() + oldmv := mv + mv = reflect.New(t).Elem() + mv.Set(oldmv) + set = true + } + + x, err := nextFn(key, mv) + if err != nil { + return reflect.Value{}, err + } + + if x.IsValid() { + mv = x + set = true + } + + if set { + v.SetMapIndex(mk, mv) + } + case reflect.Struct: + path, found := structFieldPath(v, string(key.Node().Data)) + if !found { + d.skipUntilTable = true + return reflect.Value{}, nil + } + + if d.errorContext == nil { + d.errorContext = new(errorContext) + } + t := v.Type() + d.errorContext.Struct = t + d.errorContext.Field = path + + f := fieldByIndex(v, path) + x, err := nextFn(key, f) + if err != nil || d.skipUntilTable { + return reflect.Value{}, err + } + if x.IsValid() { + f.Set(x) + } + d.errorContext.Field = nil + d.errorContext.Struct = nil + case reflect.Interface: + if v.Elem().IsValid() { + v = v.Elem() + } else { + v = makeMapStringInterface() + } + + x, err := d.handleKeyPart(key, v, nextFn, makeFn) + if err != nil { + return reflect.Value{}, err + } + if x.IsValid() { + v = x + } + rv = v + default: + panic(fmt.Errorf("unhandled part: %s", v.Kind())) + } + + return rv, nil +} + +// HandleArrayTablePart navigates the Go structure v using the key v. It is +// only used for the prefix (non-last) parts of an array-table. When +// encountering a collection, it should go to the last element. +func (d *decoder) handleArrayTablePart(key unstable.Iterator, v reflect.Value) (reflect.Value, error) { + var makeFn valueMakerFn + if key.IsLast() { + makeFn = makeSliceInterface + } else { + makeFn = makeMapStringInterface + } + return d.handleKeyPart(key, v, d.handleArrayTableCollection, makeFn) +} + +// HandleTable returns a reference when it has checked the next expression but +// cannot handle it. +func (d *decoder) handleTable(key unstable.Iterator, v reflect.Value) (reflect.Value, error) { + if v.Kind() == reflect.Slice { + if v.Len() == 0 { + return reflect.Value{}, unstable.NewParserError(key.Node().Data, "cannot store a table in a slice") + } + elem := v.Index(v.Len() - 1) + x, err := d.handleTable(key, elem) + if err != nil { + return reflect.Value{}, err + } + if x.IsValid() { + elem.Set(x) + } + return reflect.Value{}, nil + } + if key.Next() { + // Still scoping the key + return d.handleTablePart(key, v) + } + // Done scoping the key. + // Now handle all the key-value expressions in this table. + return d.handleKeyValues(v) +} + +// Handle root expressions until the end of the document or the next +// non-key-value. +func (d *decoder) handleKeyValues(v reflect.Value) (reflect.Value, error) { + var rv reflect.Value + for d.nextExpr() { + expr := d.expr() + if expr.Kind != unstable.KeyValue { + // Stash the expression so that fromParser can just loop and use + // the right handler. + // We could just recurse ourselves here, but at least this gives a + // chance to pop the stack a bit. + d.stashExpr() + break + } + + _, err := d.seen.CheckExpression(expr) + if err != nil { + return reflect.Value{}, err + } + + x, err := d.handleKeyValue(expr, v) + if err != nil { + return reflect.Value{}, err + } + if x.IsValid() { + v = x + rv = x + } + } + return rv, nil +} + +type ( + handlerFn func(key unstable.Iterator, v reflect.Value) (reflect.Value, error) + valueMakerFn func() reflect.Value +) + +func makeMapStringInterface() reflect.Value { + return reflect.MakeMap(mapStringInterfaceType) +} + +func makeSliceInterface() reflect.Value { + return reflect.MakeSlice(sliceInterfaceType, 0, 16) +} + +func (d *decoder) handleTablePart(key unstable.Iterator, v reflect.Value) (reflect.Value, error) { + return d.handleKeyPart(key, v, d.handleTable, makeMapStringInterface) +} + +func (d *decoder) tryTextUnmarshaler(node *unstable.Node, v reflect.Value) (bool, error) { + // Special case for time, because we allow to unmarshal to it from + // different kind of AST nodes. + if v.Type() == timeType { + return false, nil + } + + if v.CanAddr() && v.Addr().Type().Implements(textUnmarshalerType) { + err := v.Addr().Interface().(encoding.TextUnmarshaler).UnmarshalText(node.Data) + if err != nil { + return false, unstable.NewParserError(d.p.Raw(node.Raw), "%w", err) + } + + return true, nil + } + + return false, nil +} + +func (d *decoder) handleValue(value *unstable.Node, v reflect.Value) error { + for v.Kind() == reflect.Ptr { + v = initAndDereferencePointer(v) + } + + if d.unmarshalerInterface { + if v.CanAddr() && v.Addr().CanInterface() { + if outi, ok := v.Addr().Interface().(unstable.Unmarshaler); ok { + return outi.UnmarshalTOML(value) + } + } + } + + ok, err := d.tryTextUnmarshaler(value, v) + if ok || err != nil { + return err + } + + switch value.Kind { + case unstable.String: + return d.unmarshalString(value, v) + case unstable.Integer: + return d.unmarshalInteger(value, v) + case unstable.Float: + return d.unmarshalFloat(value, v) + case unstable.Bool: + return d.unmarshalBool(value, v) + case unstable.DateTime: + return d.unmarshalDateTime(value, v) + case unstable.LocalDate: + return d.unmarshalLocalDate(value, v) + case unstable.LocalTime: + return d.unmarshalLocalTime(value, v) + case unstable.LocalDateTime: + return d.unmarshalLocalDateTime(value, v) + case unstable.InlineTable: + return d.unmarshalInlineTable(value, v) + case unstable.Array: + return d.unmarshalArray(value, v) + default: + panic(fmt.Errorf("handleValue not implemented for %s", value.Kind)) + } +} + +func (d *decoder) unmarshalArray(array *unstable.Node, v reflect.Value) error { + switch v.Kind() { + case reflect.Slice: + if v.IsNil() { + v.Set(reflect.MakeSlice(v.Type(), 0, 16)) + } else { + v.SetLen(0) + } + case reflect.Array: + // arrays are always initialized + case reflect.Interface: + elem := v.Elem() + if !elem.IsValid() { + elem = reflect.New(sliceInterfaceType).Elem() + elem.Set(reflect.MakeSlice(sliceInterfaceType, 0, 16)) + } else if elem.Kind() == reflect.Slice { + if elem.Type() != sliceInterfaceType { + elem = reflect.New(sliceInterfaceType).Elem() + elem.Set(reflect.MakeSlice(sliceInterfaceType, 0, 16)) + } else if !elem.CanSet() { + nelem := reflect.New(sliceInterfaceType).Elem() + nelem.Set(reflect.MakeSlice(sliceInterfaceType, elem.Len(), elem.Cap())) + reflect.Copy(nelem, elem) + elem = nelem + } + } + err := d.unmarshalArray(array, elem) + if err != nil { + return err + } + v.Set(elem) + return nil + default: + // TODO: use newDecodeError, but first the parser needs to fill + // array.Data. + return d.typeMismatchError("array", v.Type()) + } + + elemType := v.Type().Elem() + + it := array.Children() + idx := 0 + for it.Next() { + n := it.Node() + + // TODO: optimize + if v.Kind() == reflect.Slice { + elem := reflect.New(elemType).Elem() + + err := d.handleValue(n, elem) + if err != nil { + return err + } + + v.Set(reflect.Append(v, elem)) + } else { // array + if idx >= v.Len() { + return nil + } + elem := v.Index(idx) + err := d.handleValue(n, elem) + if err != nil { + return err + } + idx++ + } + } + + return nil +} + +func (d *decoder) unmarshalInlineTable(itable *unstable.Node, v reflect.Value) error { + // Make sure v is an initialized object. + switch v.Kind() { + case reflect.Map: + if v.IsNil() { + v.Set(reflect.MakeMap(v.Type())) + } + case reflect.Struct: + // structs are always initialized. + case reflect.Interface: + elem := v.Elem() + if !elem.IsValid() { + elem = makeMapStringInterface() + v.Set(elem) + } + return d.unmarshalInlineTable(itable, elem) + default: + return unstable.NewParserError(d.p.Raw(itable.Raw), "cannot store inline table in Go type %s", v.Kind()) + } + + it := itable.Children() + for it.Next() { + n := it.Node() + + x, err := d.handleKeyValue(n, v) + if err != nil { + return err + } + if x.IsValid() { + v = x + } + } + + return nil +} + +func (d *decoder) unmarshalDateTime(value *unstable.Node, v reflect.Value) error { + dt, err := parseDateTime(value.Data) + if err != nil { + return err + } + + v.Set(reflect.ValueOf(dt)) + return nil +} + +func (d *decoder) unmarshalLocalDate(value *unstable.Node, v reflect.Value) error { + ld, err := parseLocalDate(value.Data) + if err != nil { + return err + } + + if v.Type() == timeType { + cast := ld.AsTime(time.Local) + v.Set(reflect.ValueOf(cast)) + return nil + } + + v.Set(reflect.ValueOf(ld)) + + return nil +} + +func (d *decoder) unmarshalLocalTime(value *unstable.Node, v reflect.Value) error { + lt, rest, err := parseLocalTime(value.Data) + if err != nil { + return err + } + + if len(rest) > 0 { + return unstable.NewParserError(rest, "extra characters at the end of a local time") + } + + v.Set(reflect.ValueOf(lt)) + return nil +} + +func (d *decoder) unmarshalLocalDateTime(value *unstable.Node, v reflect.Value) error { + ldt, rest, err := parseLocalDateTime(value.Data) + if err != nil { + return err + } + + if len(rest) > 0 { + return unstable.NewParserError(rest, "extra characters at the end of a local date time") + } + + if v.Type() == timeType { + cast := ldt.AsTime(time.Local) + + v.Set(reflect.ValueOf(cast)) + return nil + } + + v.Set(reflect.ValueOf(ldt)) + + return nil +} + +func (d *decoder) unmarshalBool(value *unstable.Node, v reflect.Value) error { + b := value.Data[0] == 't' + + switch v.Kind() { + case reflect.Bool: + v.SetBool(b) + case reflect.Interface: + v.Set(reflect.ValueOf(b)) + default: + return unstable.NewParserError(value.Data, "cannot assign boolean to a %t", b) + } + + return nil +} + +func (d *decoder) unmarshalFloat(value *unstable.Node, v reflect.Value) error { + f, err := parseFloat(value.Data) + if err != nil { + return err + } + + switch v.Kind() { + case reflect.Float64: + v.SetFloat(f) + case reflect.Float32: + if f > math.MaxFloat32 { + return unstable.NewParserError(value.Data, "number %f does not fit in a float32", f) + } + v.SetFloat(f) + case reflect.Interface: + v.Set(reflect.ValueOf(f)) + default: + return unstable.NewParserError(value.Data, "float cannot be assigned to %s", v.Kind()) + } + + return nil +} + +const ( + maxInt = int64(^uint(0) >> 1) + minInt = -maxInt - 1 +) + +// Maximum value of uint for decoding. Currently the decoder parses the integer +// into an int64. As a result, on architectures where uint is 64 bits, the +// effective maximum uint we can decode is the maximum of int64. On +// architectures where uint is 32 bits, the maximum value we can decode is +// lower: the maximum of uint32. I didn't find a way to figure out this value at +// compile time, so it is computed during initialization. +var maxUint int64 = math.MaxInt64 + +func init() { + m := uint64(^uint(0)) + if m < uint64(maxUint) { + maxUint = int64(m) + } +} + +func (d *decoder) unmarshalInteger(value *unstable.Node, v reflect.Value) error { + kind := v.Kind() + if kind == reflect.Float32 || kind == reflect.Float64 { + return d.unmarshalFloat(value, v) + } + + i, err := parseInteger(value.Data) + if err != nil { + return err + } + + var r reflect.Value + + switch kind { + case reflect.Int64: + v.SetInt(i) + return nil + case reflect.Int32: + if i < math.MinInt32 || i > math.MaxInt32 { + return fmt.Errorf("toml: number %d does not fit in an int32", i) + } + + r = reflect.ValueOf(int32(i)) + case reflect.Int16: + if i < math.MinInt16 || i > math.MaxInt16 { + return fmt.Errorf("toml: number %d does not fit in an int16", i) + } + + r = reflect.ValueOf(int16(i)) + case reflect.Int8: + if i < math.MinInt8 || i > math.MaxInt8 { + return fmt.Errorf("toml: number %d does not fit in an int8", i) + } + + r = reflect.ValueOf(int8(i)) + case reflect.Int: + if i < minInt || i > maxInt { + return fmt.Errorf("toml: number %d does not fit in an int", i) + } + + r = reflect.ValueOf(int(i)) + case reflect.Uint64: + if i < 0 { + return fmt.Errorf("toml: negative number %d does not fit in an uint64", i) + } + + r = reflect.ValueOf(uint64(i)) + case reflect.Uint32: + if i < 0 || i > math.MaxUint32 { + return fmt.Errorf("toml: negative number %d does not fit in an uint32", i) + } + + r = reflect.ValueOf(uint32(i)) + case reflect.Uint16: + if i < 0 || i > math.MaxUint16 { + return fmt.Errorf("toml: negative number %d does not fit in an uint16", i) + } + + r = reflect.ValueOf(uint16(i)) + case reflect.Uint8: + if i < 0 || i > math.MaxUint8 { + return fmt.Errorf("toml: negative number %d does not fit in an uint8", i) + } + + r = reflect.ValueOf(uint8(i)) + case reflect.Uint: + if i < 0 || i > maxUint { + return fmt.Errorf("toml: negative number %d does not fit in an uint", i) + } + + r = reflect.ValueOf(uint(i)) + case reflect.Interface: + r = reflect.ValueOf(i) + default: + return unstable.NewParserError(d.p.Raw(value.Raw), d.typeMismatchString("integer", v.Type())) + } + + if !r.Type().AssignableTo(v.Type()) { + r = r.Convert(v.Type()) + } + + v.Set(r) + + return nil +} + +func (d *decoder) unmarshalString(value *unstable.Node, v reflect.Value) error { + switch v.Kind() { + case reflect.String: + v.SetString(string(value.Data)) + case reflect.Interface: + v.Set(reflect.ValueOf(string(value.Data))) + default: + return unstable.NewParserError(d.p.Raw(value.Raw), d.typeMismatchString("string", v.Type())) + } + + return nil +} + +func (d *decoder) handleKeyValue(expr *unstable.Node, v reflect.Value) (reflect.Value, error) { + d.strict.EnterKeyValue(expr) + + v, err := d.handleKeyValueInner(expr.Key(), expr.Value(), v) + if d.skipUntilTable { + d.strict.MissingField(expr) + d.skipUntilTable = false + } + + d.strict.ExitKeyValue(expr) + + return v, err +} + +func (d *decoder) handleKeyValueInner(key unstable.Iterator, value *unstable.Node, v reflect.Value) (reflect.Value, error) { + if key.Next() { + // Still scoping the key + return d.handleKeyValuePart(key, value, v) + } + // Done scoping the key. + // v is whatever Go value we need to fill. + return reflect.Value{}, d.handleValue(value, v) +} + +func (d *decoder) keyFromData(keyType reflect.Type, data []byte) (reflect.Value, error) { + switch { + case stringType.AssignableTo(keyType): + return reflect.ValueOf(string(data)), nil + + case stringType.ConvertibleTo(keyType): + return reflect.ValueOf(string(data)).Convert(keyType), nil + + case keyType.Implements(textUnmarshalerType): + mk := reflect.New(keyType.Elem()) + if err := mk.Interface().(encoding.TextUnmarshaler).UnmarshalText(data); err != nil { + return reflect.Value{}, fmt.Errorf("toml: error unmarshalling key type %s from text: %w", stringType, err) + } + return mk, nil + + case reflect.PointerTo(keyType).Implements(textUnmarshalerType): + mk := reflect.New(keyType) + if err := mk.Interface().(encoding.TextUnmarshaler).UnmarshalText(data); err != nil { + return reflect.Value{}, fmt.Errorf("toml: error unmarshalling key type %s from text: %w", stringType, err) + } + return mk.Elem(), nil + + case keyType.Kind() == reflect.Int || keyType.Kind() == reflect.Int8 || keyType.Kind() == reflect.Int16 || keyType.Kind() == reflect.Int32 || keyType.Kind() == reflect.Int64: + key, err := strconv.ParseInt(string(data), 10, 64) + if err != nil { + return reflect.Value{}, fmt.Errorf("toml: error parsing key of type %s from integer: %w", stringType, err) + } + return reflect.ValueOf(key).Convert(keyType), nil + case keyType.Kind() == reflect.Uint || keyType.Kind() == reflect.Uint8 || keyType.Kind() == reflect.Uint16 || keyType.Kind() == reflect.Uint32 || keyType.Kind() == reflect.Uint64: + key, err := strconv.ParseUint(string(data), 10, 64) + if err != nil { + return reflect.Value{}, fmt.Errorf("toml: error parsing key of type %s from unsigned integer: %w", stringType, err) + } + return reflect.ValueOf(key).Convert(keyType), nil + + case keyType.Kind() == reflect.Float32: + key, err := strconv.ParseFloat(string(data), 32) + if err != nil { + return reflect.Value{}, fmt.Errorf("toml: error parsing key of type %s from float: %w", stringType, err) + } + return reflect.ValueOf(float32(key)), nil + + case keyType.Kind() == reflect.Float64: + key, err := strconv.ParseFloat(string(data), 64) + if err != nil { + return reflect.Value{}, fmt.Errorf("toml: error parsing key of type %s from float: %w", stringType, err) + } + return reflect.ValueOf(float64(key)), nil + } + return reflect.Value{}, fmt.Errorf("toml: cannot convert map key of type %s to expected type %s", stringType, keyType) +} + +func (d *decoder) handleKeyValuePart(key unstable.Iterator, value *unstable.Node, v reflect.Value) (reflect.Value, error) { + // contains the replacement for v + var rv reflect.Value + + // First, dispatch over v to make sure it is a valid object. + // There is no guarantee over what it could be. + switch v.Kind() { + case reflect.Map: + vt := v.Type() + + mk, err := d.keyFromData(vt.Key(), key.Node().Data) + if err != nil { + return reflect.Value{}, err + } + + // If the map does not exist, create it. + if v.IsNil() { + v = reflect.MakeMap(vt) + rv = v + } + + mv := v.MapIndex(mk) + set := false + if !mv.IsValid() || key.IsLast() { + set = true + mv = reflect.New(v.Type().Elem()).Elem() + } + + nv, err := d.handleKeyValueInner(key, value, mv) + if err != nil { + return reflect.Value{}, err + } + if nv.IsValid() { + mv = nv + set = true + } + + if set { + v.SetMapIndex(mk, mv) + } + case reflect.Struct: + path, found := structFieldPath(v, string(key.Node().Data)) + if !found { + d.skipUntilTable = true + break + } + + if d.errorContext == nil { + d.errorContext = new(errorContext) + } + t := v.Type() + d.errorContext.Struct = t + d.errorContext.Field = path + + f := fieldByIndex(v, path) + + if !f.CanAddr() { + // If the field is not addressable, need to take a slower path and + // make a copy of the struct itself to a new location. + nvp := reflect.New(v.Type()) + nvp.Elem().Set(v) + v = nvp.Elem() + _, err := d.handleKeyValuePart(key, value, v) + if err != nil { + return reflect.Value{}, err + } + return nvp.Elem(), nil + } + x, err := d.handleKeyValueInner(key, value, f) + if err != nil { + return reflect.Value{}, err + } + + if x.IsValid() { + f.Set(x) + } + d.errorContext.Struct = nil + d.errorContext.Field = nil + case reflect.Interface: + v = v.Elem() + + // Following encoding/json: decoding an object into an + // interface{}, it needs to always hold a + // map[string]interface{}. This is for the types to be + // consistent whether a previous value was set or not. + if !v.IsValid() || v.Type() != mapStringInterfaceType { + v = makeMapStringInterface() + } + + x, err := d.handleKeyValuePart(key, value, v) + if err != nil { + return reflect.Value{}, err + } + if x.IsValid() { + v = x + } + rv = v + case reflect.Ptr: + elem := v.Elem() + if !elem.IsValid() { + ptr := reflect.New(v.Type().Elem()) + v.Set(ptr) + rv = v + elem = ptr.Elem() + } + + elem2, err := d.handleKeyValuePart(key, value, elem) + if err != nil { + return reflect.Value{}, err + } + if elem2.IsValid() { + elem = elem2 + } + v.Elem().Set(elem) + default: + return reflect.Value{}, fmt.Errorf("unhandled kv part: %s", v.Kind()) + } + + return rv, nil +} + +func initAndDereferencePointer(v reflect.Value) reflect.Value { + var elem reflect.Value + if v.IsNil() { + ptr := reflect.New(v.Type().Elem()) + v.Set(ptr) + } + elem = v.Elem() + return elem +} + +// Same as reflect.Value.FieldByIndex, but creates pointers if needed. +func fieldByIndex(v reflect.Value, path []int) reflect.Value { + for _, x := range path { + v = v.Field(x) + + if v.Kind() == reflect.Ptr { + if v.IsNil() { + v.Set(reflect.New(v.Type().Elem())) + } + v = v.Elem() + } + } + return v +} + +type fieldPathsMap = map[string][]int + +var globalFieldPathsCache atomic.Value // map[danger.TypeID]fieldPathsMap + +func structFieldPath(v reflect.Value, name string) ([]int, bool) { + t := v.Type() + + cache, _ := globalFieldPathsCache.Load().(map[danger.TypeID]fieldPathsMap) + fieldPaths, ok := cache[danger.MakeTypeID(t)] + + if !ok { + fieldPaths = map[string][]int{} + + forEachField(t, nil, func(name string, path []int) { + fieldPaths[name] = path + // extra copy for the case-insensitive match + fieldPaths[strings.ToLower(name)] = path + }) + + newCache := make(map[danger.TypeID]fieldPathsMap, len(cache)+1) + newCache[danger.MakeTypeID(t)] = fieldPaths + for k, v := range cache { + newCache[k] = v + } + globalFieldPathsCache.Store(newCache) + } + + path, ok := fieldPaths[name] + if !ok { + path, ok = fieldPaths[strings.ToLower(name)] + } + return path, ok +} + +func forEachField(t reflect.Type, path []int, do func(name string, path []int)) { + n := t.NumField() + for i := 0; i < n; i++ { + f := t.Field(i) + + if !f.Anonymous && f.PkgPath != "" { + // only consider exported fields. + continue + } + + fieldPath := append(path, i) + fieldPath = fieldPath[:len(fieldPath):len(fieldPath)] + + name := f.Tag.Get("toml") + if name == "-" { + continue + } + + if i := strings.IndexByte(name, ','); i >= 0 { + name = name[:i] + } + + if f.Anonymous && name == "" { + t2 := f.Type + if t2.Kind() == reflect.Ptr { + t2 = t2.Elem() + } + + if t2.Kind() == reflect.Struct { + forEachField(t2, fieldPath, do) + } + continue + } + + if name == "" { + name = f.Name + } + + do(name, fieldPath) + } +} diff --git a/vendor/github.com/pelletier/go-toml/v2/unstable/ast.go b/vendor/github.com/pelletier/go-toml/v2/unstable/ast.go new file mode 100644 index 0000000000..f526bf2c09 --- /dev/null +++ b/vendor/github.com/pelletier/go-toml/v2/unstable/ast.go @@ -0,0 +1,136 @@ +package unstable + +import ( + "fmt" + "unsafe" + + "github.com/pelletier/go-toml/v2/internal/danger" +) + +// Iterator over a sequence of nodes. +// +// Starts uninitialized, you need to call Next() first. +// +// For example: +// +// it := n.Children() +// for it.Next() { +// n := it.Node() +// // do something with n +// } +type Iterator struct { + started bool + node *Node +} + +// Next moves the iterator forward and returns true if points to a +// node, false otherwise. +func (c *Iterator) Next() bool { + if !c.started { + c.started = true + } else if c.node.Valid() { + c.node = c.node.Next() + } + return c.node.Valid() +} + +// IsLast returns true if the current node of the iterator is the last +// one. Subsequent calls to Next() will return false. +func (c *Iterator) IsLast() bool { + return c.node.next == 0 +} + +// Node returns a pointer to the node pointed at by the iterator. +func (c *Iterator) Node() *Node { + return c.node +} + +// Node in a TOML expression AST. +// +// Depending on Kind, its sequence of children should be interpreted +// differently. +// +// - Array have one child per element in the array. +// - InlineTable have one child per key-value in the table (each of kind +// InlineTable). +// - KeyValue have at least two children. The first one is the value. The rest +// make a potentially dotted key. +// - Table and ArrayTable's children represent a dotted key (same as +// KeyValue, but without the first node being the value). +// +// When relevant, Raw describes the range of bytes this node is referring to in +// the input document. Use Parser.Raw() to retrieve the actual bytes. +type Node struct { + Kind Kind + Raw Range // Raw bytes from the input. + Data []byte // Node value (either allocated or referencing the input). + + // References to other nodes, as offsets in the backing array + // from this node. References can go backward, so those can be + // negative. + next int // 0 if last element + child int // 0 if no child +} + +// Range of bytes in the document. +type Range struct { + Offset uint32 + Length uint32 +} + +// Next returns a pointer to the next node, or nil if there is no next node. +func (n *Node) Next() *Node { + if n.next == 0 { + return nil + } + ptr := unsafe.Pointer(n) + size := unsafe.Sizeof(Node{}) + return (*Node)(danger.Stride(ptr, size, n.next)) +} + +// Child returns a pointer to the first child node of this node. Other children +// can be accessed calling Next on the first child. Returns an nil if this Node +// has no child. +func (n *Node) Child() *Node { + if n.child == 0 { + return nil + } + ptr := unsafe.Pointer(n) + size := unsafe.Sizeof(Node{}) + return (*Node)(danger.Stride(ptr, size, n.child)) +} + +// Valid returns true if the node's kind is set (not to Invalid). +func (n *Node) Valid() bool { + return n != nil +} + +// Key returns the children nodes making the Key on a supported node. Panics +// otherwise. They are guaranteed to be all be of the Kind Key. A simple key +// would return just one element. +func (n *Node) Key() Iterator { + switch n.Kind { + case KeyValue: + value := n.Child() + if !value.Valid() { + panic(fmt.Errorf("KeyValue should have at least two children")) + } + return Iterator{node: value.Next()} + case Table, ArrayTable: + return Iterator{node: n.Child()} + default: + panic(fmt.Errorf("Key() is not supported on a %s", n.Kind)) + } +} + +// Value returns a pointer to the value node of a KeyValue. +// Guaranteed to be non-nil. Panics if not called on a KeyValue node, +// or if the Children are malformed. +func (n *Node) Value() *Node { + return n.Child() +} + +// Children returns an iterator over a node's children. +func (n *Node) Children() Iterator { + return Iterator{node: n.Child()} +} diff --git a/vendor/github.com/pelletier/go-toml/v2/unstable/builder.go b/vendor/github.com/pelletier/go-toml/v2/unstable/builder.go new file mode 100644 index 0000000000..9538e30df9 --- /dev/null +++ b/vendor/github.com/pelletier/go-toml/v2/unstable/builder.go @@ -0,0 +1,71 @@ +package unstable + +// root contains a full AST. +// +// It is immutable once constructed with Builder. +type root struct { + nodes []Node +} + +// Iterator over the top level nodes. +func (r *root) Iterator() Iterator { + it := Iterator{} + if len(r.nodes) > 0 { + it.node = &r.nodes[0] + } + return it +} + +func (r *root) at(idx reference) *Node { + return &r.nodes[idx] +} + +type reference int + +const invalidReference reference = -1 + +func (r reference) Valid() bool { + return r != invalidReference +} + +type builder struct { + tree root + lastIdx int +} + +func (b *builder) Tree() *root { + return &b.tree +} + +func (b *builder) NodeAt(ref reference) *Node { + return b.tree.at(ref) +} + +func (b *builder) Reset() { + b.tree.nodes = b.tree.nodes[:0] + b.lastIdx = 0 +} + +func (b *builder) Push(n Node) reference { + b.lastIdx = len(b.tree.nodes) + b.tree.nodes = append(b.tree.nodes, n) + return reference(b.lastIdx) +} + +func (b *builder) PushAndChain(n Node) reference { + newIdx := len(b.tree.nodes) + b.tree.nodes = append(b.tree.nodes, n) + if b.lastIdx >= 0 { + b.tree.nodes[b.lastIdx].next = newIdx - b.lastIdx + } + b.lastIdx = newIdx + return reference(b.lastIdx) +} + +func (b *builder) AttachChild(parent reference, child reference) { + b.tree.nodes[parent].child = int(child) - int(parent) +} + +func (b *builder) Chain(from reference, to reference) { + b.tree.nodes[from].next = int(to) - int(from) +} diff --git a/vendor/github.com/pelletier/go-toml/v2/unstable/doc.go b/vendor/github.com/pelletier/go-toml/v2/unstable/doc.go new file mode 100644 index 0000000000..7ff26c53c7 --- /dev/null +++ b/vendor/github.com/pelletier/go-toml/v2/unstable/doc.go @@ -0,0 +1,3 @@ +// Package unstable provides APIs that do not meet the backward compatibility +// guarantees yet. +package unstable diff --git a/vendor/github.com/pelletier/go-toml/v2/unstable/kind.go b/vendor/github.com/pelletier/go-toml/v2/unstable/kind.go new file mode 100644 index 0000000000..ff9df1bef8 --- /dev/null +++ b/vendor/github.com/pelletier/go-toml/v2/unstable/kind.go @@ -0,0 +1,71 @@ +package unstable + +import "fmt" + +// Kind represents the type of TOML structure contained in a given Node. +type Kind int + +const ( + // Meta + Invalid Kind = iota + Comment + Key + + // Top level structures + Table + ArrayTable + KeyValue + + // Containers values + Array + InlineTable + + // Values + String + Bool + Float + Integer + LocalDate + LocalTime + LocalDateTime + DateTime +) + +// String implementation of fmt.Stringer. +func (k Kind) String() string { + switch k { + case Invalid: + return "Invalid" + case Comment: + return "Comment" + case Key: + return "Key" + case Table: + return "Table" + case ArrayTable: + return "ArrayTable" + case KeyValue: + return "KeyValue" + case Array: + return "Array" + case InlineTable: + return "InlineTable" + case String: + return "String" + case Bool: + return "Bool" + case Float: + return "Float" + case Integer: + return "Integer" + case LocalDate: + return "LocalDate" + case LocalTime: + return "LocalTime" + case LocalDateTime: + return "LocalDateTime" + case DateTime: + return "DateTime" + } + panic(fmt.Errorf("Kind.String() not implemented for '%d'", k)) +} diff --git a/vendor/github.com/pelletier/go-toml/v2/unstable/parser.go b/vendor/github.com/pelletier/go-toml/v2/unstable/parser.go new file mode 100644 index 0000000000..50358a44ff --- /dev/null +++ b/vendor/github.com/pelletier/go-toml/v2/unstable/parser.go @@ -0,0 +1,1245 @@ +package unstable + +import ( + "bytes" + "fmt" + "unicode" + + "github.com/pelletier/go-toml/v2/internal/characters" + "github.com/pelletier/go-toml/v2/internal/danger" +) + +// ParserError describes an error relative to the content of the document. +// +// It cannot outlive the instance of Parser it refers to, and may cause panics +// if the parser is reset. +type ParserError struct { + Highlight []byte + Message string + Key []string // optional +} + +// Error is the implementation of the error interface. +func (e *ParserError) Error() string { + return e.Message +} + +// NewParserError is a convenience function to create a ParserError +// +// Warning: Highlight needs to be a subslice of Parser.data, so only slices +// returned by Parser.Raw are valid candidates. +func NewParserError(highlight []byte, format string, args ...interface{}) error { + return &ParserError{ + Highlight: highlight, + Message: fmt.Errorf(format, args...).Error(), + } +} + +// Parser scans over a TOML-encoded document and generates an iterative AST. +// +// To prime the Parser, first reset it with the contents of a TOML document. +// Then, process all top-level expressions sequentially. See Example. +// +// Don't forget to check Error() after you're done parsing. +// +// Each top-level expression needs to be fully processed before calling +// NextExpression() again. Otherwise, calls to various Node methods may panic if +// the parser has moved on the next expression. +// +// For performance reasons, go-toml doesn't make a copy of the input bytes to +// the parser. Make sure to copy all the bytes you need to outlive the slice +// given to the parser. +type Parser struct { + data []byte + builder builder + ref reference + left []byte + err error + first bool + + KeepComments bool +} + +// Data returns the slice provided to the last call to Reset. +func (p *Parser) Data() []byte { + return p.data +} + +// Range returns a range description that corresponds to a given slice of the +// input. If the argument is not a subslice of the parser input, this function +// panics. +func (p *Parser) Range(b []byte) Range { + return Range{ + Offset: uint32(danger.SubsliceOffset(p.data, b)), + Length: uint32(len(b)), + } +} + +// Raw returns the slice corresponding to the bytes in the given range. +func (p *Parser) Raw(raw Range) []byte { + return p.data[raw.Offset : raw.Offset+raw.Length] +} + +// Reset brings the parser to its initial state for a given input. It wipes an +// reuses internal storage to reduce allocation. +func (p *Parser) Reset(b []byte) { + p.builder.Reset() + p.ref = invalidReference + p.data = b + p.left = b + p.err = nil + p.first = true +} + +// NextExpression parses the next top-level expression. If an expression was +// successfully parsed, it returns true. If the parser is at the end of the +// document or an error occurred, it returns false. +// +// Retrieve the parsed expression with Expression(). +func (p *Parser) NextExpression() bool { + if len(p.left) == 0 || p.err != nil { + return false + } + + p.builder.Reset() + p.ref = invalidReference + + for { + if len(p.left) == 0 || p.err != nil { + return false + } + + if !p.first { + p.left, p.err = p.parseNewline(p.left) + } + + if len(p.left) == 0 || p.err != nil { + return false + } + + p.ref, p.left, p.err = p.parseExpression(p.left) + + if p.err != nil { + return false + } + + p.first = false + + if p.ref.Valid() { + return true + } + } +} + +// Expression returns a pointer to the node representing the last successfully +// parsed expression. +func (p *Parser) Expression() *Node { + return p.builder.NodeAt(p.ref) +} + +// Error returns any error that has occurred during parsing. +func (p *Parser) Error() error { + return p.err +} + +// Position describes a position in the input. +type Position struct { + // Number of bytes from the beginning of the input. + Offset int + // Line number, starting at 1. + Line int + // Column number, starting at 1. + Column int +} + +// Shape describes the position of a range in the input. +type Shape struct { + Start Position + End Position +} + +func (p *Parser) position(b []byte) Position { + offset := danger.SubsliceOffset(p.data, b) + + lead := p.data[:offset] + + return Position{ + Offset: offset, + Line: bytes.Count(lead, []byte{'\n'}) + 1, + Column: len(lead) - bytes.LastIndex(lead, []byte{'\n'}), + } +} + +// Shape returns the shape of the given range in the input. Will +// panic if the range is not a subslice of the input. +func (p *Parser) Shape(r Range) Shape { + raw := p.Raw(r) + return Shape{ + Start: p.position(raw), + End: p.position(raw[r.Length:]), + } +} + +func (p *Parser) parseNewline(b []byte) ([]byte, error) { + if b[0] == '\n' { + return b[1:], nil + } + + if b[0] == '\r' { + _, rest, err := scanWindowsNewline(b) + return rest, err + } + + return nil, NewParserError(b[0:1], "expected newline but got %#U", b[0]) +} + +func (p *Parser) parseComment(b []byte) (reference, []byte, error) { + ref := invalidReference + data, rest, err := scanComment(b) + if p.KeepComments && err == nil { + ref = p.builder.Push(Node{ + Kind: Comment, + Raw: p.Range(data), + Data: data, + }) + } + return ref, rest, err +} + +func (p *Parser) parseExpression(b []byte) (reference, []byte, error) { + // expression = ws [ comment ] + // expression =/ ws keyval ws [ comment ] + // expression =/ ws table ws [ comment ] + ref := invalidReference + + b = p.parseWhitespace(b) + + if len(b) == 0 { + return ref, b, nil + } + + if b[0] == '#' { + ref, rest, err := p.parseComment(b) + return ref, rest, err + } + + if b[0] == '\n' || b[0] == '\r' { + return ref, b, nil + } + + var err error + if b[0] == '[' { + ref, b, err = p.parseTable(b) + } else { + ref, b, err = p.parseKeyval(b) + } + + if err != nil { + return ref, nil, err + } + + b = p.parseWhitespace(b) + + if len(b) > 0 && b[0] == '#' { + cref, rest, err := p.parseComment(b) + if cref != invalidReference { + p.builder.Chain(ref, cref) + } + return ref, rest, err + } + + return ref, b, nil +} + +func (p *Parser) parseTable(b []byte) (reference, []byte, error) { + // table = std-table / array-table + if len(b) > 1 && b[1] == '[' { + return p.parseArrayTable(b) + } + + return p.parseStdTable(b) +} + +func (p *Parser) parseArrayTable(b []byte) (reference, []byte, error) { + // array-table = array-table-open key array-table-close + // array-table-open = %x5B.5B ws ; [[ Double left square bracket + // array-table-close = ws %x5D.5D ; ]] Double right square bracket + ref := p.builder.Push(Node{ + Kind: ArrayTable, + }) + + b = b[2:] + b = p.parseWhitespace(b) + + k, b, err := p.parseKey(b) + if err != nil { + return ref, nil, err + } + + p.builder.AttachChild(ref, k) + b = p.parseWhitespace(b) + + b, err = expect(']', b) + if err != nil { + return ref, nil, err + } + + b, err = expect(']', b) + + return ref, b, err +} + +func (p *Parser) parseStdTable(b []byte) (reference, []byte, error) { + // std-table = std-table-open key std-table-close + // std-table-open = %x5B ws ; [ Left square bracket + // std-table-close = ws %x5D ; ] Right square bracket + ref := p.builder.Push(Node{ + Kind: Table, + }) + + b = b[1:] + b = p.parseWhitespace(b) + + key, b, err := p.parseKey(b) + if err != nil { + return ref, nil, err + } + + p.builder.AttachChild(ref, key) + + b = p.parseWhitespace(b) + + b, err = expect(']', b) + + return ref, b, err +} + +func (p *Parser) parseKeyval(b []byte) (reference, []byte, error) { + // keyval = key keyval-sep val + ref := p.builder.Push(Node{ + Kind: KeyValue, + }) + + key, b, err := p.parseKey(b) + if err != nil { + return invalidReference, nil, err + } + + // keyval-sep = ws %x3D ws ; = + + b = p.parseWhitespace(b) + + if len(b) == 0 { + return invalidReference, nil, NewParserError(b, "expected = after a key, but the document ends there") + } + + b, err = expect('=', b) + if err != nil { + return invalidReference, nil, err + } + + b = p.parseWhitespace(b) + + valRef, b, err := p.parseVal(b) + if err != nil { + return ref, b, err + } + + p.builder.Chain(valRef, key) + p.builder.AttachChild(ref, valRef) + + return ref, b, err +} + +//nolint:cyclop,funlen +func (p *Parser) parseVal(b []byte) (reference, []byte, error) { + // val = string / boolean / array / inline-table / date-time / float / integer + ref := invalidReference + + if len(b) == 0 { + return ref, nil, NewParserError(b, "expected value, not eof") + } + + var err error + c := b[0] + + switch c { + case '"': + var raw []byte + var v []byte + if scanFollowsMultilineBasicStringDelimiter(b) { + raw, v, b, err = p.parseMultilineBasicString(b) + } else { + raw, v, b, err = p.parseBasicString(b) + } + + if err == nil { + ref = p.builder.Push(Node{ + Kind: String, + Raw: p.Range(raw), + Data: v, + }) + } + + return ref, b, err + case '\'': + var raw []byte + var v []byte + if scanFollowsMultilineLiteralStringDelimiter(b) { + raw, v, b, err = p.parseMultilineLiteralString(b) + } else { + raw, v, b, err = p.parseLiteralString(b) + } + + if err == nil { + ref = p.builder.Push(Node{ + Kind: String, + Raw: p.Range(raw), + Data: v, + }) + } + + return ref, b, err + case 't': + if !scanFollowsTrue(b) { + return ref, nil, NewParserError(atmost(b, 4), "expected 'true'") + } + + ref = p.builder.Push(Node{ + Kind: Bool, + Data: b[:4], + }) + + return ref, b[4:], nil + case 'f': + if !scanFollowsFalse(b) { + return ref, nil, NewParserError(atmost(b, 5), "expected 'false'") + } + + ref = p.builder.Push(Node{ + Kind: Bool, + Data: b[:5], + }) + + return ref, b[5:], nil + case '[': + return p.parseValArray(b) + case '{': + return p.parseInlineTable(b) + default: + return p.parseIntOrFloatOrDateTime(b) + } +} + +func atmost(b []byte, n int) []byte { + if n >= len(b) { + return b + } + + return b[:n] +} + +func (p *Parser) parseLiteralString(b []byte) ([]byte, []byte, []byte, error) { + v, rest, err := scanLiteralString(b) + if err != nil { + return nil, nil, nil, err + } + + return v, v[1 : len(v)-1], rest, nil +} + +func (p *Parser) parseInlineTable(b []byte) (reference, []byte, error) { + // inline-table = inline-table-open [ inline-table-keyvals ] inline-table-close + // inline-table-open = %x7B ws ; { + // inline-table-close = ws %x7D ; } + // inline-table-sep = ws %x2C ws ; , Comma + // inline-table-keyvals = keyval [ inline-table-sep inline-table-keyvals ] + parent := p.builder.Push(Node{ + Kind: InlineTable, + Raw: p.Range(b[:1]), + }) + + first := true + + var child reference + + b = b[1:] + + var err error + + for len(b) > 0 { + previousB := b + b = p.parseWhitespace(b) + + if len(b) == 0 { + return parent, nil, NewParserError(previousB[:1], "inline table is incomplete") + } + + if b[0] == '}' { + break + } + + if !first { + b, err = expect(',', b) + if err != nil { + return parent, nil, err + } + b = p.parseWhitespace(b) + } + + var kv reference + + kv, b, err = p.parseKeyval(b) + if err != nil { + return parent, nil, err + } + + if first { + p.builder.AttachChild(parent, kv) + } else { + p.builder.Chain(child, kv) + } + child = kv + + first = false + } + + rest, err := expect('}', b) + + return parent, rest, err +} + +//nolint:funlen,cyclop +func (p *Parser) parseValArray(b []byte) (reference, []byte, error) { + // array = array-open [ array-values ] ws-comment-newline array-close + // array-open = %x5B ; [ + // array-close = %x5D ; ] + // array-values = ws-comment-newline val ws-comment-newline array-sep array-values + // array-values =/ ws-comment-newline val ws-comment-newline [ array-sep ] + // array-sep = %x2C ; , Comma + // ws-comment-newline = *( wschar / [ comment ] newline ) + arrayStart := b + b = b[1:] + + parent := p.builder.Push(Node{ + Kind: Array, + }) + + // First indicates whether the parser is looking for the first element + // (non-comment) of the array. + first := true + + lastChild := invalidReference + + addChild := func(valueRef reference) { + if lastChild == invalidReference { + p.builder.AttachChild(parent, valueRef) + } else { + p.builder.Chain(lastChild, valueRef) + } + lastChild = valueRef + } + + var err error + for len(b) > 0 { + cref := invalidReference + cref, b, err = p.parseOptionalWhitespaceCommentNewline(b) + if err != nil { + return parent, nil, err + } + + if cref != invalidReference { + addChild(cref) + } + + if len(b) == 0 { + return parent, nil, NewParserError(arrayStart[:1], "array is incomplete") + } + + if b[0] == ']' { + break + } + + if b[0] == ',' { + if first { + return parent, nil, NewParserError(b[0:1], "array cannot start with comma") + } + b = b[1:] + + cref, b, err = p.parseOptionalWhitespaceCommentNewline(b) + if err != nil { + return parent, nil, err + } + if cref != invalidReference { + addChild(cref) + } + } else if !first { + return parent, nil, NewParserError(b[0:1], "array elements must be separated by commas") + } + + // TOML allows trailing commas in arrays. + if len(b) > 0 && b[0] == ']' { + break + } + + var valueRef reference + valueRef, b, err = p.parseVal(b) + if err != nil { + return parent, nil, err + } + + addChild(valueRef) + + cref, b, err = p.parseOptionalWhitespaceCommentNewline(b) + if err != nil { + return parent, nil, err + } + if cref != invalidReference { + addChild(cref) + } + + first = false + } + + rest, err := expect(']', b) + + return parent, rest, err +} + +func (p *Parser) parseOptionalWhitespaceCommentNewline(b []byte) (reference, []byte, error) { + rootCommentRef := invalidReference + latestCommentRef := invalidReference + + addComment := func(ref reference) { + if rootCommentRef == invalidReference { + rootCommentRef = ref + } else if latestCommentRef == invalidReference { + p.builder.AttachChild(rootCommentRef, ref) + latestCommentRef = ref + } else { + p.builder.Chain(latestCommentRef, ref) + latestCommentRef = ref + } + } + + for len(b) > 0 { + var err error + b = p.parseWhitespace(b) + + if len(b) > 0 && b[0] == '#' { + var ref reference + ref, b, err = p.parseComment(b) + if err != nil { + return invalidReference, nil, err + } + if ref != invalidReference { + addComment(ref) + } + } + + if len(b) == 0 { + break + } + + if b[0] == '\n' || b[0] == '\r' { + b, err = p.parseNewline(b) + if err != nil { + return invalidReference, nil, err + } + } else { + break + } + } + + return rootCommentRef, b, nil +} + +func (p *Parser) parseMultilineLiteralString(b []byte) ([]byte, []byte, []byte, error) { + token, rest, err := scanMultilineLiteralString(b) + if err != nil { + return nil, nil, nil, err + } + + i := 3 + + // skip the immediate new line + if token[i] == '\n' { + i++ + } else if token[i] == '\r' && token[i+1] == '\n' { + i += 2 + } + + return token, token[i : len(token)-3], rest, err +} + +//nolint:funlen,gocognit,cyclop +func (p *Parser) parseMultilineBasicString(b []byte) ([]byte, []byte, []byte, error) { + // ml-basic-string = ml-basic-string-delim [ newline ] ml-basic-body + // ml-basic-string-delim + // ml-basic-string-delim = 3quotation-mark + // ml-basic-body = *mlb-content *( mlb-quotes 1*mlb-content ) [ mlb-quotes ] + // + // mlb-content = mlb-char / newline / mlb-escaped-nl + // mlb-char = mlb-unescaped / escaped + // mlb-quotes = 1*2quotation-mark + // mlb-unescaped = wschar / %x21 / %x23-5B / %x5D-7E / non-ascii + // mlb-escaped-nl = escape ws newline *( wschar / newline ) + token, escaped, rest, err := scanMultilineBasicString(b) + if err != nil { + return nil, nil, nil, err + } + + i := 3 + + // skip the immediate new line + if token[i] == '\n' { + i++ + } else if token[i] == '\r' && token[i+1] == '\n' { + i += 2 + } + + // fast path + startIdx := i + endIdx := len(token) - len(`"""`) + + if !escaped { + str := token[startIdx:endIdx] + verr := characters.Utf8TomlValidAlreadyEscaped(str) + if verr.Zero() { + return token, str, rest, nil + } + return nil, nil, nil, NewParserError(str[verr.Index:verr.Index+verr.Size], "invalid UTF-8") + } + + var builder bytes.Buffer + + // The scanner ensures that the token starts and ends with quotes and that + // escapes are balanced. + for i < len(token)-3 { + c := token[i] + + //nolint:nestif + if c == '\\' { + // When the last non-whitespace character on a line is an unescaped \, + // it will be trimmed along with all whitespace (including newlines) up + // to the next non-whitespace character or closing delimiter. + + isLastNonWhitespaceOnLine := false + j := 1 + findEOLLoop: + for ; j < len(token)-3-i; j++ { + switch token[i+j] { + case ' ', '\t': + continue + case '\r': + if token[i+j+1] == '\n' { + continue + } + case '\n': + isLastNonWhitespaceOnLine = true + } + break findEOLLoop + } + if isLastNonWhitespaceOnLine { + i += j + for ; i < len(token)-3; i++ { + c := token[i] + if !(c == '\n' || c == '\r' || c == ' ' || c == '\t') { + i-- + break + } + } + i++ + continue + } + + // handle escaping + i++ + c = token[i] + + switch c { + case '"', '\\': + builder.WriteByte(c) + case 'b': + builder.WriteByte('\b') + case 'f': + builder.WriteByte('\f') + case 'n': + builder.WriteByte('\n') + case 'r': + builder.WriteByte('\r') + case 't': + builder.WriteByte('\t') + case 'e': + builder.WriteByte(0x1B) + case 'u': + x, err := hexToRune(atmost(token[i+1:], 4), 4) + if err != nil { + return nil, nil, nil, err + } + builder.WriteRune(x) + i += 4 + case 'U': + x, err := hexToRune(atmost(token[i+1:], 8), 8) + if err != nil { + return nil, nil, nil, err + } + + builder.WriteRune(x) + i += 8 + default: + return nil, nil, nil, NewParserError(token[i:i+1], "invalid escaped character %#U", c) + } + i++ + } else { + size := characters.Utf8ValidNext(token[i:]) + if size == 0 { + return nil, nil, nil, NewParserError(token[i:i+1], "invalid character %#U", c) + } + builder.Write(token[i : i+size]) + i += size + } + } + + return token, builder.Bytes(), rest, nil +} + +func (p *Parser) parseKey(b []byte) (reference, []byte, error) { + // key = simple-key / dotted-key + // simple-key = quoted-key / unquoted-key + // + // unquoted-key = 1*( ALPHA / DIGIT / %x2D / %x5F ) ; A-Z / a-z / 0-9 / - / _ + // quoted-key = basic-string / literal-string + // dotted-key = simple-key 1*( dot-sep simple-key ) + // + // dot-sep = ws %x2E ws ; . Period + raw, key, b, err := p.parseSimpleKey(b) + if err != nil { + return invalidReference, nil, err + } + + ref := p.builder.Push(Node{ + Kind: Key, + Raw: p.Range(raw), + Data: key, + }) + + for { + b = p.parseWhitespace(b) + if len(b) > 0 && b[0] == '.' { + b = p.parseWhitespace(b[1:]) + + raw, key, b, err = p.parseSimpleKey(b) + if err != nil { + return ref, nil, err + } + + p.builder.PushAndChain(Node{ + Kind: Key, + Raw: p.Range(raw), + Data: key, + }) + } else { + break + } + } + + return ref, b, nil +} + +func (p *Parser) parseSimpleKey(b []byte) (raw, key, rest []byte, err error) { + if len(b) == 0 { + return nil, nil, nil, NewParserError(b, "expected key but found none") + } + + // simple-key = quoted-key / unquoted-key + // unquoted-key = 1*( ALPHA / DIGIT / %x2D / %x5F ) ; A-Z / a-z / 0-9 / - / _ + // quoted-key = basic-string / literal-string + switch { + case b[0] == '\'': + return p.parseLiteralString(b) + case b[0] == '"': + return p.parseBasicString(b) + case isUnquotedKeyChar(b[0]): + key, rest = scanUnquotedKey(b) + return key, key, rest, nil + default: + return nil, nil, nil, NewParserError(b[0:1], "invalid character at start of key: %c", b[0]) + } +} + +//nolint:funlen,cyclop +func (p *Parser) parseBasicString(b []byte) ([]byte, []byte, []byte, error) { + // basic-string = quotation-mark *basic-char quotation-mark + // quotation-mark = %x22 ; " + // basic-char = basic-unescaped / escaped + // basic-unescaped = wschar / %x21 / %x23-5B / %x5D-7E / non-ascii + // escaped = escape escape-seq-char + // escape-seq-char = %x22 ; " quotation mark U+0022 + // escape-seq-char =/ %x5C ; \ reverse solidus U+005C + // escape-seq-char =/ %x62 ; b backspace U+0008 + // escape-seq-char =/ %x66 ; f form feed U+000C + // escape-seq-char =/ %x6E ; n line feed U+000A + // escape-seq-char =/ %x72 ; r carriage return U+000D + // escape-seq-char =/ %x74 ; t tab U+0009 + // escape-seq-char =/ %x75 4HEXDIG ; uXXXX U+XXXX + // escape-seq-char =/ %x55 8HEXDIG ; UXXXXXXXX U+XXXXXXXX + token, escaped, rest, err := scanBasicString(b) + if err != nil { + return nil, nil, nil, err + } + + startIdx := len(`"`) + endIdx := len(token) - len(`"`) + + // Fast path. If there is no escape sequence, the string should just be + // an UTF-8 encoded string, which is the same as Go. In that case, + // validate the string and return a direct reference to the buffer. + if !escaped { + str := token[startIdx:endIdx] + verr := characters.Utf8TomlValidAlreadyEscaped(str) + if verr.Zero() { + return token, str, rest, nil + } + return nil, nil, nil, NewParserError(str[verr.Index:verr.Index+verr.Size], "invalid UTF-8") + } + + i := startIdx + + var builder bytes.Buffer + + // The scanner ensures that the token starts and ends with quotes and that + // escapes are balanced. + for i < len(token)-1 { + c := token[i] + if c == '\\' { + i++ + c = token[i] + + switch c { + case '"', '\\': + builder.WriteByte(c) + case 'b': + builder.WriteByte('\b') + case 'f': + builder.WriteByte('\f') + case 'n': + builder.WriteByte('\n') + case 'r': + builder.WriteByte('\r') + case 't': + builder.WriteByte('\t') + case 'e': + builder.WriteByte(0x1B) + case 'u': + x, err := hexToRune(token[i+1:len(token)-1], 4) + if err != nil { + return nil, nil, nil, err + } + + builder.WriteRune(x) + i += 4 + case 'U': + x, err := hexToRune(token[i+1:len(token)-1], 8) + if err != nil { + return nil, nil, nil, err + } + + builder.WriteRune(x) + i += 8 + default: + return nil, nil, nil, NewParserError(token[i:i+1], "invalid escaped character %#U", c) + } + i++ + } else { + size := characters.Utf8ValidNext(token[i:]) + if size == 0 { + return nil, nil, nil, NewParserError(token[i:i+1], "invalid character %#U", c) + } + builder.Write(token[i : i+size]) + i += size + } + } + + return token, builder.Bytes(), rest, nil +} + +func hexToRune(b []byte, length int) (rune, error) { + if len(b) < length { + return -1, NewParserError(b, "unicode point needs %d character, not %d", length, len(b)) + } + b = b[:length] + + var r uint32 + for i, c := range b { + d := uint32(0) + switch { + case '0' <= c && c <= '9': + d = uint32(c - '0') + case 'a' <= c && c <= 'f': + d = uint32(c - 'a' + 10) + case 'A' <= c && c <= 'F': + d = uint32(c - 'A' + 10) + default: + return -1, NewParserError(b[i:i+1], "non-hex character") + } + r = r*16 + d + } + + if r > unicode.MaxRune || 0xD800 <= r && r < 0xE000 { + return -1, NewParserError(b, "escape sequence is invalid Unicode code point") + } + + return rune(r), nil +} + +func (p *Parser) parseWhitespace(b []byte) []byte { + // ws = *wschar + // wschar = %x20 ; Space + // wschar =/ %x09 ; Horizontal tab + _, rest := scanWhitespace(b) + + return rest +} + +//nolint:cyclop +func (p *Parser) parseIntOrFloatOrDateTime(b []byte) (reference, []byte, error) { + switch b[0] { + case 'i': + if !scanFollowsInf(b) { + return invalidReference, nil, NewParserError(atmost(b, 3), "expected 'inf'") + } + + return p.builder.Push(Node{ + Kind: Float, + Data: b[:3], + Raw: p.Range(b[:3]), + }), b[3:], nil + case 'n': + if !scanFollowsNan(b) { + return invalidReference, nil, NewParserError(atmost(b, 3), "expected 'nan'") + } + + return p.builder.Push(Node{ + Kind: Float, + Data: b[:3], + Raw: p.Range(b[:3]), + }), b[3:], nil + case '+', '-': + return p.scanIntOrFloat(b) + } + + if len(b) < 3 { + return p.scanIntOrFloat(b) + } + + s := 5 + if len(b) < s { + s = len(b) + } + + for idx, c := range b[:s] { + if isDigit(c) { + continue + } + + if idx == 2 && c == ':' || (idx == 4 && c == '-') { + return p.scanDateTime(b) + } + + break + } + + return p.scanIntOrFloat(b) +} + +func (p *Parser) scanDateTime(b []byte) (reference, []byte, error) { + // scans for contiguous characters in [0-9T:Z.+-], and up to one space if + // followed by a digit. + hasDate := false + hasTime := false + hasTz := false + seenSpace := false + + i := 0 +byteLoop: + for ; i < len(b); i++ { + c := b[i] + + switch { + case isDigit(c): + case c == '-': + hasDate = true + const minOffsetOfTz = 8 + if i >= minOffsetOfTz { + hasTz = true + } + case c == 'T' || c == 't' || c == ':' || c == '.': + hasTime = true + case c == '+' || c == '-' || c == 'Z' || c == 'z': + hasTz = true + case c == ' ': + if !seenSpace && i+1 < len(b) && isDigit(b[i+1]) { + i += 2 + // Avoid reaching past the end of the document in case the time + // is malformed. See TestIssue585. + if i >= len(b) { + i-- + } + seenSpace = true + hasTime = true + } else { + break byteLoop + } + default: + break byteLoop + } + } + + var kind Kind + + if hasTime { + if hasDate { + if hasTz { + kind = DateTime + } else { + kind = LocalDateTime + } + } else { + kind = LocalTime + } + } else { + kind = LocalDate + } + + return p.builder.Push(Node{ + Kind: kind, + Data: b[:i], + }), b[i:], nil +} + +//nolint:funlen,gocognit,cyclop +func (p *Parser) scanIntOrFloat(b []byte) (reference, []byte, error) { + i := 0 + + if len(b) > 2 && b[0] == '0' && b[1] != '.' && b[1] != 'e' && b[1] != 'E' { + var isValidRune validRuneFn + + switch b[1] { + case 'x': + isValidRune = isValidHexRune + case 'o': + isValidRune = isValidOctalRune + case 'b': + isValidRune = isValidBinaryRune + default: + i++ + } + + if isValidRune != nil { + i += 2 + for ; i < len(b); i++ { + if !isValidRune(b[i]) { + break + } + } + } + + return p.builder.Push(Node{ + Kind: Integer, + Data: b[:i], + Raw: p.Range(b[:i]), + }), b[i:], nil + } + + isFloat := false + + for ; i < len(b); i++ { + c := b[i] + + if c >= '0' && c <= '9' || c == '+' || c == '-' || c == '_' { + continue + } + + if c == '.' || c == 'e' || c == 'E' { + isFloat = true + + continue + } + + if c == 'i' { + if scanFollowsInf(b[i:]) { + return p.builder.Push(Node{ + Kind: Float, + Data: b[:i+3], + Raw: p.Range(b[:i+3]), + }), b[i+3:], nil + } + + return invalidReference, nil, NewParserError(b[i:i+1], "unexpected character 'i' while scanning for a number") + } + + if c == 'n' { + if scanFollowsNan(b[i:]) { + return p.builder.Push(Node{ + Kind: Float, + Data: b[:i+3], + Raw: p.Range(b[:i+3]), + }), b[i+3:], nil + } + + return invalidReference, nil, NewParserError(b[i:i+1], "unexpected character 'n' while scanning for a number") + } + + break + } + + if i == 0 { + return invalidReference, b, NewParserError(b, "incomplete number") + } + + kind := Integer + + if isFloat { + kind = Float + } + + return p.builder.Push(Node{ + Kind: kind, + Data: b[:i], + Raw: p.Range(b[:i]), + }), b[i:], nil +} + +func isDigit(r byte) bool { + return r >= '0' && r <= '9' +} + +type validRuneFn func(r byte) bool + +func isValidHexRune(r byte) bool { + return r >= 'a' && r <= 'f' || + r >= 'A' && r <= 'F' || + r >= '0' && r <= '9' || + r == '_' +} + +func isValidOctalRune(r byte) bool { + return r >= '0' && r <= '7' || r == '_' +} + +func isValidBinaryRune(r byte) bool { + return r == '0' || r == '1' || r == '_' +} + +func expect(x byte, b []byte) ([]byte, error) { + if len(b) == 0 { + return nil, NewParserError(b, "expected character %c but the document ended here", x) + } + + if b[0] != x { + return nil, NewParserError(b[0:1], "expected character %c", x) + } + + return b[1:], nil +} diff --git a/vendor/github.com/pelletier/go-toml/v2/unstable/scanner.go b/vendor/github.com/pelletier/go-toml/v2/unstable/scanner.go new file mode 100644 index 0000000000..0512181d28 --- /dev/null +++ b/vendor/github.com/pelletier/go-toml/v2/unstable/scanner.go @@ -0,0 +1,270 @@ +package unstable + +import "github.com/pelletier/go-toml/v2/internal/characters" + +func scanFollows(b []byte, pattern string) bool { + n := len(pattern) + + return len(b) >= n && string(b[:n]) == pattern +} + +func scanFollowsMultilineBasicStringDelimiter(b []byte) bool { + return scanFollows(b, `"""`) +} + +func scanFollowsMultilineLiteralStringDelimiter(b []byte) bool { + return scanFollows(b, `'''`) +} + +func scanFollowsTrue(b []byte) bool { + return scanFollows(b, `true`) +} + +func scanFollowsFalse(b []byte) bool { + return scanFollows(b, `false`) +} + +func scanFollowsInf(b []byte) bool { + return scanFollows(b, `inf`) +} + +func scanFollowsNan(b []byte) bool { + return scanFollows(b, `nan`) +} + +func scanUnquotedKey(b []byte) ([]byte, []byte) { + // unquoted-key = 1*( ALPHA / DIGIT / %x2D / %x5F ) ; A-Z / a-z / 0-9 / - / _ + for i := 0; i < len(b); i++ { + if !isUnquotedKeyChar(b[i]) { + return b[:i], b[i:] + } + } + + return b, b[len(b):] +} + +func isUnquotedKeyChar(r byte) bool { + return (r >= 'A' && r <= 'Z') || (r >= 'a' && r <= 'z') || (r >= '0' && r <= '9') || r == '-' || r == '_' +} + +func scanLiteralString(b []byte) ([]byte, []byte, error) { + // literal-string = apostrophe *literal-char apostrophe + // apostrophe = %x27 ; ' apostrophe + // literal-char = %x09 / %x20-26 / %x28-7E / non-ascii + for i := 1; i < len(b); { + switch b[i] { + case '\'': + return b[:i+1], b[i+1:], nil + case '\n', '\r': + return nil, nil, NewParserError(b[i:i+1], "literal strings cannot have new lines") + } + size := characters.Utf8ValidNext(b[i:]) + if size == 0 { + return nil, nil, NewParserError(b[i:i+1], "invalid character") + } + i += size + } + + return nil, nil, NewParserError(b[len(b):], "unterminated literal string") +} + +func scanMultilineLiteralString(b []byte) ([]byte, []byte, error) { + // ml-literal-string = ml-literal-string-delim [ newline ] ml-literal-body + // ml-literal-string-delim + // ml-literal-string-delim = 3apostrophe + // ml-literal-body = *mll-content *( mll-quotes 1*mll-content ) [ mll-quotes ] + // + // mll-content = mll-char / newline + // mll-char = %x09 / %x20-26 / %x28-7E / non-ascii + // mll-quotes = 1*2apostrophe + for i := 3; i < len(b); { + switch b[i] { + case '\'': + if scanFollowsMultilineLiteralStringDelimiter(b[i:]) { + i += 3 + + // At that point we found 3 apostrophe, and i is the + // index of the byte after the third one. The scanner + // needs to be eager, because there can be an extra 2 + // apostrophe that can be accepted at the end of the + // string. + + if i >= len(b) || b[i] != '\'' { + return b[:i], b[i:], nil + } + i++ + + if i >= len(b) || b[i] != '\'' { + return b[:i], b[i:], nil + } + i++ + + if i < len(b) && b[i] == '\'' { + return nil, nil, NewParserError(b[i-3:i+1], "''' not allowed in multiline literal string") + } + + return b[:i], b[i:], nil + } + case '\r': + if len(b) < i+2 { + return nil, nil, NewParserError(b[len(b):], `need a \n after \r`) + } + if b[i+1] != '\n' { + return nil, nil, NewParserError(b[i:i+2], `need a \n after \r`) + } + i += 2 // skip the \n + continue + } + size := characters.Utf8ValidNext(b[i:]) + if size == 0 { + return nil, nil, NewParserError(b[i:i+1], "invalid character") + } + i += size + } + + return nil, nil, NewParserError(b[len(b):], `multiline literal string not terminated by '''`) +} + +func scanWindowsNewline(b []byte) ([]byte, []byte, error) { + const lenCRLF = 2 + if len(b) < lenCRLF { + return nil, nil, NewParserError(b, "windows new line expected") + } + + if b[1] != '\n' { + return nil, nil, NewParserError(b, `windows new line should be \r\n`) + } + + return b[:lenCRLF], b[lenCRLF:], nil +} + +func scanWhitespace(b []byte) ([]byte, []byte) { + for i := 0; i < len(b); i++ { + switch b[i] { + case ' ', '\t': + continue + default: + return b[:i], b[i:] + } + } + + return b, b[len(b):] +} + +func scanComment(b []byte) ([]byte, []byte, error) { + // comment-start-symbol = %x23 ; # + // non-ascii = %x80-D7FF / %xE000-10FFFF + // non-eol = %x09 / %x20-7F / non-ascii + // + // comment = comment-start-symbol *non-eol + + for i := 1; i < len(b); { + if b[i] == '\n' { + return b[:i], b[i:], nil + } + if b[i] == '\r' { + if i+1 < len(b) && b[i+1] == '\n' { + return b[:i+1], b[i+1:], nil + } + return nil, nil, NewParserError(b[i:i+1], "invalid character in comment") + } + size := characters.Utf8ValidNext(b[i:]) + if size == 0 { + return nil, nil, NewParserError(b[i:i+1], "invalid character in comment") + } + + i += size + } + + return b, b[len(b):], nil +} + +func scanBasicString(b []byte) ([]byte, bool, []byte, error) { + // basic-string = quotation-mark *basic-char quotation-mark + // quotation-mark = %x22 ; " + // basic-char = basic-unescaped / escaped + // basic-unescaped = wschar / %x21 / %x23-5B / %x5D-7E / non-ascii + // escaped = escape escape-seq-char + escaped := false + i := 1 + + for ; i < len(b); i++ { + switch b[i] { + case '"': + return b[:i+1], escaped, b[i+1:], nil + case '\n', '\r': + return nil, escaped, nil, NewParserError(b[i:i+1], "basic strings cannot have new lines") + case '\\': + if len(b) < i+2 { + return nil, escaped, nil, NewParserError(b[i:i+1], "need a character after \\") + } + escaped = true + i++ // skip the next character + } + } + + return nil, escaped, nil, NewParserError(b[len(b):], `basic string not terminated by "`) +} + +func scanMultilineBasicString(b []byte) ([]byte, bool, []byte, error) { + // ml-basic-string = ml-basic-string-delim [ newline ] ml-basic-body + // ml-basic-string-delim + // ml-basic-string-delim = 3quotation-mark + // ml-basic-body = *mlb-content *( mlb-quotes 1*mlb-content ) [ mlb-quotes ] + // + // mlb-content = mlb-char / newline / mlb-escaped-nl + // mlb-char = mlb-unescaped / escaped + // mlb-quotes = 1*2quotation-mark + // mlb-unescaped = wschar / %x21 / %x23-5B / %x5D-7E / non-ascii + // mlb-escaped-nl = escape ws newline *( wschar / newline ) + + escaped := false + i := 3 + + for ; i < len(b); i++ { + switch b[i] { + case '"': + if scanFollowsMultilineBasicStringDelimiter(b[i:]) { + i += 3 + + // At that point we found 3 apostrophe, and i is the + // index of the byte after the third one. The scanner + // needs to be eager, because there can be an extra 2 + // apostrophe that can be accepted at the end of the + // string. + + if i >= len(b) || b[i] != '"' { + return b[:i], escaped, b[i:], nil + } + i++ + + if i >= len(b) || b[i] != '"' { + return b[:i], escaped, b[i:], nil + } + i++ + + if i < len(b) && b[i] == '"' { + return nil, escaped, nil, NewParserError(b[i-3:i+1], `""" not allowed in multiline basic string`) + } + + return b[:i], escaped, b[i:], nil + } + case '\\': + if len(b) < i+2 { + return nil, escaped, nil, NewParserError(b[len(b):], "need a character after \\") + } + escaped = true + i++ // skip the next character + case '\r': + if len(b) < i+2 { + return nil, escaped, nil, NewParserError(b[len(b):], `need a \n after \r`) + } + if b[i+1] != '\n' { + return nil, escaped, nil, NewParserError(b[i:i+2], `need a \n after \r`) + } + i++ // skip the \n + } + } + + return nil, escaped, nil, NewParserError(b[len(b):], `multiline basic string not terminated by """`) +} diff --git a/vendor/github.com/pelletier/go-toml/v2/unstable/unmarshaler.go b/vendor/github.com/pelletier/go-toml/v2/unstable/unmarshaler.go new file mode 100644 index 0000000000..00cfd6de45 --- /dev/null +++ b/vendor/github.com/pelletier/go-toml/v2/unstable/unmarshaler.go @@ -0,0 +1,7 @@ +package unstable + +// The Unmarshaler interface may be implemented by types to customize their +// behavior when being unmarshaled from a TOML document. +type Unmarshaler interface { + UnmarshalTOML(value *Node) error +} diff --git a/vendor/github.com/sagikazarmark/locafero/.editorconfig b/vendor/github.com/sagikazarmark/locafero/.editorconfig new file mode 100644 index 0000000000..6f944f5406 --- /dev/null +++ b/vendor/github.com/sagikazarmark/locafero/.editorconfig @@ -0,0 +1,21 @@ +root = true + +[*] +charset = utf-8 +end_of_line = lf +indent_size = 4 +indent_style = space +insert_final_newline = true +trim_trailing_whitespace = true + +[{Makefile,*.mk}] +indent_style = tab + +[*.nix] +indent_size = 2 + +[*.go] +indent_style = tab + +[{*.yml,*.yaml}] +indent_size = 2 diff --git a/vendor/github.com/sagikazarmark/locafero/.gitignore b/vendor/github.com/sagikazarmark/locafero/.gitignore new file mode 100644 index 0000000000..8f07e60163 --- /dev/null +++ b/vendor/github.com/sagikazarmark/locafero/.gitignore @@ -0,0 +1,8 @@ +/.devenv/ +/.direnv/ +/.task/ +/bin/ +/build/ +/tmp/ +/var/ +/vendor/ diff --git a/vendor/github.com/sagikazarmark/locafero/.golangci.yaml b/vendor/github.com/sagikazarmark/locafero/.golangci.yaml new file mode 100644 index 0000000000..a27a42959f --- /dev/null +++ b/vendor/github.com/sagikazarmark/locafero/.golangci.yaml @@ -0,0 +1,37 @@ +version: "2" + +run: + timeout: 10m + +linters: + enable: + - errcheck + - govet + - ineffassign + - misspell + - nolintlint + - revive + - staticcheck + - unused + + settings: + misspell: + locale: US + nolintlint: + allow-unused: false # report any unused nolint directives + require-specific: false # don't require nolint directives to be specific about which linter is being skipped + +formatters: + enable: + - gci + - gofmt + - gofumpt + - goimports + - golines + + settings: + gci: + sections: + - standard + - default + - localmodule diff --git a/vendor/github.com/sagikazarmark/locafero/LICENSE b/vendor/github.com/sagikazarmark/locafero/LICENSE new file mode 100644 index 0000000000..a70b0f2960 --- /dev/null +++ b/vendor/github.com/sagikazarmark/locafero/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2023 Márk Sági-Kazár + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/github.com/sagikazarmark/locafero/README.md b/vendor/github.com/sagikazarmark/locafero/README.md new file mode 100644 index 0000000000..d25fe80f3e --- /dev/null +++ b/vendor/github.com/sagikazarmark/locafero/README.md @@ -0,0 +1,37 @@ +# Finder library for [Afero](https://github.com/spf13/afero) + +[![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/sagikazarmark/locafero/ci.yaml?style=flat-square)](https://github.com/sagikazarmark/locafero/actions/workflows/ci.yaml) +[![go.dev reference](https://img.shields.io/badge/go.dev-reference-007d9c?logo=go&logoColor=white&style=flat-square)](https://pkg.go.dev/mod/github.com/sagikazarmark/locafero) +![GitHub go.mod Go version](https://img.shields.io/github/go-mod/go-version/sagikazarmark/locafero?style=flat-square&color=61CFDD) +[![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/sagikazarmark/locafero/badge?style=flat-square)](https://deps.dev/go/github.com%252Fsagikazarmark%252Flocafero) + +**Finder library for [Afero](https://github.com/spf13/afero) ported from [go-finder](https://github.com/sagikazarmark/go-finder).** + +> [!WARNING] +> This is an experimental library under development. +> +> **Backwards compatibility is not guaranteed, expect breaking changes.** + +## Installation + +```shell +go get github.com/sagikazarmark/locafero +``` + +## Usage + +Check out the [package example](https://pkg.go.dev/github.com/sagikazarmark/locafero#example-package) on go.dev. + +## Development + +**For an optimal developer experience, it is recommended to install [Nix](https://nixos.org/download.html) and [direnv](https://direnv.net/docs/installation.html).** + +Run the test suite: + +```shell +just test +``` + +## License + +The project is licensed under the [MIT License](LICENSE). diff --git a/vendor/github.com/sagikazarmark/locafero/file_type.go b/vendor/github.com/sagikazarmark/locafero/file_type.go new file mode 100644 index 0000000000..5ea57c93ee --- /dev/null +++ b/vendor/github.com/sagikazarmark/locafero/file_type.go @@ -0,0 +1,32 @@ +package locafero + +import "io/fs" + +// FileType represents the kind of entries [Finder] can return. +type FileType int + +// FileType represents the kind of entries [Finder] can return. +const ( + FileTypeAny FileType = iota + FileTypeFile + FileTypeDir + + // Deprecated: Use [FileTypeAny] instead. + FileTypeAll = FileTypeAny +) + +func (ft FileType) match(info fs.FileInfo) bool { + switch ft { + case FileTypeAny: + return true + + case FileTypeFile: + return info.Mode().IsRegular() + + case FileTypeDir: + return info.IsDir() + + default: + return false + } +} diff --git a/vendor/github.com/sagikazarmark/locafero/finder.go b/vendor/github.com/sagikazarmark/locafero/finder.go new file mode 100644 index 0000000000..ce43c78264 --- /dev/null +++ b/vendor/github.com/sagikazarmark/locafero/finder.go @@ -0,0 +1,171 @@ +// Package locafero looks for files and directories in an {fs.Fs} filesystem. +package locafero + +import ( + "errors" + "io/fs" + "path/filepath" + "strings" + + "github.com/sourcegraph/conc/pool" + "github.com/spf13/afero" +) + +// Finder looks for files and directories in an [afero.Fs] filesystem. +type Finder struct { + // Paths represents a list of locations that the [Finder] will search in. + // + // They are essentially the root directories or starting points for the search. + // + // Examples: + // - home/user + // - etc + Paths []string + + // Names are specific entries that the [Finder] will look for within the given Paths. + // + // It provides the capability to search for entries with depth, + // meaning it can target deeper locations within the directory structure. + // + // It also supports glob syntax (as defined by [filepath.Match]), offering greater flexibility in search patterns. + // + // Examples: + // - config.yaml + // - home/*/config.yaml + // - home/*/config.* + Names []string + + // Type restricts the kind of entries returned by the [Finder]. + // + // This parameter helps in differentiating and filtering out files from directories or vice versa. + Type FileType +} + +// Find looks for files and directories in an [afero.Fs] filesystem. +func (f Finder) Find(fsys afero.Fs) ([]string, error) { + // Arbitrary go routine limit (TODO: make this a parameter) + p := pool.NewWithResults[[]searchResult]().WithMaxGoroutines(5).WithErrors().WithFirstError() + + for _, searchPath := range f.Paths { + for _, searchName := range f.Names { + p.Go(func() ([]searchResult, error) { + // If the name contains any glob character, perform a glob match + if strings.ContainsAny(searchName, globMatch) { + return globWalkSearch(fsys, searchPath, searchName, f.Type) + } + + return statSearch(fsys, searchPath, searchName, f.Type) + }) + } + } + + searchResults, err := flatten(p.Wait()) + if err != nil { + return nil, err + } + + // Return early if no results were found + if len(searchResults) == 0 { + return nil, nil + } + + results := make([]string, 0, len(searchResults)) + + for _, searchResult := range searchResults { + results = append(results, searchResult.path) + } + + return results, nil +} + +type searchResult struct { + path string + info fs.FileInfo +} + +func flatten[T any](results [][]T, err error) ([]T, error) { + if err != nil { + return nil, err + } + + var flattened []T + + for _, r := range results { + flattened = append(flattened, r...) + } + + return flattened, nil +} + +func globWalkSearch( + fsys afero.Fs, + searchPath string, + searchName string, + searchType FileType, +) ([]searchResult, error) { + var results []searchResult + + err := afero.Walk(fsys, searchPath, func(p string, fileInfo fs.FileInfo, err error) error { + if err != nil { + return err + } + + // Skip the root path + if p == searchPath { + return nil + } + + var result error + + // Stop reading subdirectories + // TODO: add depth detection here + if fileInfo.IsDir() && filepath.Dir(p) == searchPath { + result = fs.SkipDir + } + + // Skip unmatching type + if !searchType.match(fileInfo) { + return result + } + + match, err := filepath.Match(searchName, fileInfo.Name()) + if err != nil { + return err + } + + if match { + results = append(results, searchResult{p, fileInfo}) + } + + return result + }) + if err != nil { + return results, err + } + + return results, nil +} + +func statSearch( + fsys afero.Fs, + searchPath string, + searchName string, + searchType FileType, +) ([]searchResult, error) { + filePath := filepath.Join(searchPath, searchName) + + fileInfo, err := fsys.Stat(filePath) + if errors.Is(err, fs.ErrNotExist) { + return nil, nil + } + if err != nil { + return nil, err + } + + // Skip unmatching type + if !searchType.match(fileInfo) { + return nil, nil + } + + return []searchResult{{filePath, fileInfo}}, nil +} diff --git a/vendor/github.com/sagikazarmark/locafero/flake.lock b/vendor/github.com/sagikazarmark/locafero/flake.lock new file mode 100644 index 0000000000..b14a842c2f --- /dev/null +++ b/vendor/github.com/sagikazarmark/locafero/flake.lock @@ -0,0 +1,255 @@ +{ + "nodes": { + "cachix": { + "inputs": { + "devenv": [ + "devenv" + ], + "flake-compat": [ + "devenv" + ], + "git-hooks": [ + "devenv", + "git-hooks" + ], + "nixpkgs": [ + "devenv", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1748883665, + "narHash": "sha256-R0W7uAg+BLoHjMRMQ8+oiSbTq8nkGz5RDpQ+ZfxxP3A=", + "owner": "cachix", + "repo": "cachix", + "rev": "f707778d902af4d62d8dd92c269f8e70de09acbe", + "type": "github" + }, + "original": { + "owner": "cachix", + "ref": "latest", + "repo": "cachix", + "type": "github" + } + }, + "devenv": { + "inputs": { + "cachix": "cachix", + "flake-compat": "flake-compat", + "git-hooks": "git-hooks", + "nix": "nix", + "nixpkgs": "nixpkgs" + }, + "locked": { + "lastModified": 1753981111, + "narHash": "sha256-uBJOyMxOkGRmxhD2M5rbN2aV6oP1T2AKq5oBaHHC4mw=", + "owner": "cachix", + "repo": "devenv", + "rev": "d4d70df706b153b601a87ab8e81c88a0b1a373b6", + "type": "github" + }, + "original": { + "owner": "cachix", + "repo": "devenv", + "type": "github" + } + }, + "flake-compat": { + "flake": false, + "locked": { + "lastModified": 1747046372, + "narHash": "sha256-CIVLLkVgvHYbgI2UpXvIIBJ12HWgX+fjA8Xf8PUmqCY=", + "owner": "edolstra", + "repo": "flake-compat", + "rev": "9100a0f413b0c601e0533d1d94ffd501ce2e7885", + "type": "github" + }, + "original": { + "owner": "edolstra", + "repo": "flake-compat", + "type": "github" + } + }, + "flake-parts": { + "inputs": { + "nixpkgs-lib": [ + "devenv", + "nix", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1733312601, + "narHash": "sha256-4pDvzqnegAfRkPwO3wmwBhVi/Sye1mzps0zHWYnP88c=", + "owner": "hercules-ci", + "repo": "flake-parts", + "rev": "205b12d8b7cd4802fbcb8e8ef6a0f1408781a4f9", + "type": "github" + }, + "original": { + "owner": "hercules-ci", + "repo": "flake-parts", + "type": "github" + } + }, + "flake-parts_2": { + "inputs": { + "nixpkgs-lib": "nixpkgs-lib" + }, + "locked": { + "lastModified": 1753121425, + "narHash": "sha256-TVcTNvOeWWk1DXljFxVRp+E0tzG1LhrVjOGGoMHuXio=", + "owner": "hercules-ci", + "repo": "flake-parts", + "rev": "644e0fc48951a860279da645ba77fe4a6e814c5e", + "type": "github" + }, + "original": { + "owner": "hercules-ci", + "repo": "flake-parts", + "type": "github" + } + }, + "git-hooks": { + "inputs": { + "flake-compat": [ + "devenv", + "flake-compat" + ], + "gitignore": "gitignore", + "nixpkgs": [ + "devenv", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1750779888, + "narHash": "sha256-wibppH3g/E2lxU43ZQHC5yA/7kIKLGxVEnsnVK1BtRg=", + "owner": "cachix", + "repo": "git-hooks.nix", + "rev": "16ec914f6fb6f599ce988427d9d94efddf25fe6d", + "type": "github" + }, + "original": { + "owner": "cachix", + "repo": "git-hooks.nix", + "type": "github" + } + }, + "gitignore": { + "inputs": { + "nixpkgs": [ + "devenv", + "git-hooks", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1709087332, + "narHash": "sha256-HG2cCnktfHsKV0s4XW83gU3F57gaTljL9KNSuG6bnQs=", + "owner": "hercules-ci", + "repo": "gitignore.nix", + "rev": "637db329424fd7e46cf4185293b9cc8c88c95394", + "type": "github" + }, + "original": { + "owner": "hercules-ci", + "repo": "gitignore.nix", + "type": "github" + } + }, + "nix": { + "inputs": { + "flake-compat": [ + "devenv", + "flake-compat" + ], + "flake-parts": "flake-parts", + "git-hooks-nix": [ + "devenv", + "git-hooks" + ], + "nixpkgs": [ + "devenv", + "nixpkgs" + ], + "nixpkgs-23-11": [ + "devenv" + ], + "nixpkgs-regression": [ + "devenv" + ] + }, + "locked": { + "lastModified": 1752773918, + "narHash": "sha256-dOi/M6yNeuJlj88exI+7k154z+hAhFcuB8tZktiW7rg=", + "owner": "cachix", + "repo": "nix", + "rev": "031c3cf42d2e9391eee373507d8c12e0f9606779", + "type": "github" + }, + "original": { + "owner": "cachix", + "ref": "devenv-2.30", + "repo": "nix", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1750441195, + "narHash": "sha256-yke+pm+MdgRb6c0dPt8MgDhv7fcBbdjmv1ZceNTyzKg=", + "owner": "cachix", + "repo": "devenv-nixpkgs", + "rev": "0ceffe312871b443929ff3006960d29b120dc627", + "type": "github" + }, + "original": { + "owner": "cachix", + "ref": "rolling", + "repo": "devenv-nixpkgs", + "type": "github" + } + }, + "nixpkgs-lib": { + "locked": { + "lastModified": 1751159883, + "narHash": "sha256-urW/Ylk9FIfvXfliA1ywh75yszAbiTEVgpPeinFyVZo=", + "owner": "nix-community", + "repo": "nixpkgs.lib", + "rev": "14a40a1d7fb9afa4739275ac642ed7301a9ba1ab", + "type": "github" + }, + "original": { + "owner": "nix-community", + "repo": "nixpkgs.lib", + "type": "github" + } + }, + "nixpkgs_2": { + "locked": { + "lastModified": 1753939845, + "narHash": "sha256-K2ViRJfdVGE8tpJejs8Qpvvejks1+A4GQej/lBk5y7I=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "94def634a20494ee057c76998843c015909d6311", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "devenv": "devenv", + "flake-parts": "flake-parts_2", + "nixpkgs": "nixpkgs_2" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/vendor/github.com/sagikazarmark/locafero/flake.nix b/vendor/github.com/sagikazarmark/locafero/flake.nix new file mode 100644 index 0000000000..bdb10dbe4f --- /dev/null +++ b/vendor/github.com/sagikazarmark/locafero/flake.nix @@ -0,0 +1,42 @@ +{ + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; + flake-parts.url = "github:hercules-ci/flake-parts"; + devenv.url = "github:cachix/devenv"; + }; + + outputs = + inputs@{ flake-parts, ... }: + flake-parts.lib.mkFlake { inherit inputs; } { + imports = [ + inputs.devenv.flakeModule + ]; + + systems = [ + "x86_64-linux" + "aarch64-darwin" + ]; + + perSystem = + { pkgs, ... }: + { + devenv.shells = { + default = { + languages = { + go.enable = true; + go.package = pkgs.lib.mkDefault pkgs.go_1_24; + }; + + packages = with pkgs; [ + just + + golangci-lint + ]; + + # https://github.com/cachix/devenv/issues/528#issuecomment-1556108767 + containers = pkgs.lib.mkForce { }; + }; + }; + }; + }; +} diff --git a/vendor/github.com/sagikazarmark/locafero/glob.go b/vendor/github.com/sagikazarmark/locafero/glob.go new file mode 100644 index 0000000000..00f833e99c --- /dev/null +++ b/vendor/github.com/sagikazarmark/locafero/glob.go @@ -0,0 +1,5 @@ +//go:build !windows + +package locafero + +const globMatch = "*?[]\\^" diff --git a/vendor/github.com/sagikazarmark/locafero/glob_windows.go b/vendor/github.com/sagikazarmark/locafero/glob_windows.go new file mode 100644 index 0000000000..7aec2b247d --- /dev/null +++ b/vendor/github.com/sagikazarmark/locafero/glob_windows.go @@ -0,0 +1,8 @@ +//go:build windows + +package locafero + +// See [filepath.Match]: +// +// On Windows, escaping is disabled. Instead, '\\' is treated as path separator. +const globMatch = "*?[]^" diff --git a/vendor/github.com/sagikazarmark/locafero/helpers.go b/vendor/github.com/sagikazarmark/locafero/helpers.go new file mode 100644 index 0000000000..05b434481f --- /dev/null +++ b/vendor/github.com/sagikazarmark/locafero/helpers.go @@ -0,0 +1,41 @@ +package locafero + +import "fmt" + +// NameWithExtensions creates a list of names from a base name and a list of extensions. +// +// TODO: find a better name for this function. +func NameWithExtensions(baseName string, extensions ...string) []string { + var names []string + + if baseName == "" { + return names + } + + for _, ext := range extensions { + if ext == "" { + continue + } + + names = append(names, fmt.Sprintf("%s.%s", baseName, ext)) + } + + return names +} + +// NameWithOptionalExtensions creates a list of names from a base name and a list of extensions, +// plus it adds the base name (without any extensions) to the end of the list. +// +// TODO: find a better name for this function. +func NameWithOptionalExtensions(baseName string, extensions ...string) []string { + var names []string + + if baseName == "" { + return names + } + + names = NameWithExtensions(baseName, extensions...) + names = append(names, baseName) + + return names +} diff --git a/vendor/github.com/sagikazarmark/locafero/justfile b/vendor/github.com/sagikazarmark/locafero/justfile new file mode 100644 index 0000000000..bac5e75db4 --- /dev/null +++ b/vendor/github.com/sagikazarmark/locafero/justfile @@ -0,0 +1,14 @@ +default: + just --list + +test: + go test -count 10 -shuffle on -race -v ./... + +fuzz: + go test -race -v -fuzz=Fuzz -fuzztime=60s ./... + +lint: + golangci-lint run + +fmt: + golangci-lint fmt diff --git a/vendor/github.com/sourcegraph/conc/.golangci.yml b/vendor/github.com/sourcegraph/conc/.golangci.yml new file mode 100644 index 0000000000..ae65a760a9 --- /dev/null +++ b/vendor/github.com/sourcegraph/conc/.golangci.yml @@ -0,0 +1,11 @@ +linters: + disable-all: true + enable: + - errcheck + - godot + - gosimple + - govet + - ineffassign + - staticcheck + - typecheck + - unused diff --git a/vendor/github.com/sourcegraph/conc/LICENSE b/vendor/github.com/sourcegraph/conc/LICENSE new file mode 100644 index 0000000000..1081f4ef4a --- /dev/null +++ b/vendor/github.com/sourcegraph/conc/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 Sourcegraph + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/github.com/sourcegraph/conc/Makefile b/vendor/github.com/sourcegraph/conc/Makefile new file mode 100644 index 0000000000..3e0720a123 --- /dev/null +++ b/vendor/github.com/sourcegraph/conc/Makefile @@ -0,0 +1,24 @@ +.DEFAULT_GOAL := help + +GO_BIN ?= $(shell go env GOPATH)/bin + +.PHONY: help +help: + @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' + +$(GO_BIN)/golangci-lint: + @echo "==> Installing golangci-lint within "${GO_BIN}"" + @go install -v github.com/golangci/golangci-lint/cmd/golangci-lint@latest + +.PHONY: lint +lint: $(GO_BIN)/golangci-lint ## Run linting on Go files + @echo "==> Linting Go source files" + @golangci-lint run -v --fix -c .golangci.yml ./... + +.PHONY: test +test: ## Run tests + go test -race -v ./... -coverprofile ./coverage.txt + +.PHONY: bench +bench: ## Run benchmarks. See https://pkg.go.dev/cmd/go#hdr-Testing_flags + go test ./... -bench . -benchtime 5s -timeout 0 -run=XXX -cpu 1 -benchmem diff --git a/vendor/github.com/sourcegraph/conc/README.md b/vendor/github.com/sourcegraph/conc/README.md new file mode 100644 index 0000000000..1c87c3c969 --- /dev/null +++ b/vendor/github.com/sourcegraph/conc/README.md @@ -0,0 +1,464 @@ +![conch](https://user-images.githubusercontent.com/12631702/210295964-785cc63d-d697-420c-99ff-f492eb81dec9.svg) + +# `conc`: better structured concurrency for go + +[![Go Reference](https://pkg.go.dev/badge/github.com/sourcegraph/conc.svg)](https://pkg.go.dev/github.com/sourcegraph/conc) +[![Sourcegraph](https://img.shields.io/badge/view%20on-sourcegraph-A112FE?logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAYAAAAeP4ixAAAEZklEQVRoQ+2aXWgUZxSG3292sxtNN43BhBakFPyhxSujRSxiU1pr7SaGXqgUxOIEW0IFkeYighYUxAuLUlq0lrq2iCDpjWtmFVtoG6QVNOCFVShVLyxIk0DVjZLMxt3xTGTccd2ZOd/8JBHci0CY9zvnPPN+/7sCIXwKavOwAcy2QgngQiIztDSE0OwQlDPYR1ebiaH6J5kZChyfW12gRG4QVgGTBfMchMbFP9Sn5nlZL2D0JjLD6710lc+z0NfqSGTXQRQ4bX07Mq423yoBL3OSyHSvUxirMuaEvgbJWrdcvkHMoJwxYuq4INUhyuWvQa1jvdMGxAvCxJlyEC9XOBCWL04wwRzpbDoDQ7wfZJzIQLi5Eggk6DiRhZgWIAbE3NrM4A3LPT8Q7UgqAqLqTmLSHLGPkyzG/qXEczhd0q6RH+zaSBfaUoc4iQx19pIClIscrTkNZzG6gd7qMY6eC2Hqyo705ZfTf+eqJmhMzcSbYtQpOXc92ZsZjLVAL4YNUQbJ5Ttg4CQrQdGYj44Xr9m1XJCzmZusFDJOWNpHjmh5x624a2ZFtOKDVL+uNo2TuXE3bZQQZUf8gtgqP31uI94Z/rMqix+IGiRfWw3xN9dCgVx+L3WrHm4Dju6PXz/EkjuXJ6R+IGgyOE1TbZqTq9y1eo0EZo7oMo1ktPu3xjHvuiLT5AFNszUyDULtWpzE2/fEsey8O5TbWuGWwxrs5rS7nFNMWJrNh2No74s9Ec4vRNmRRzPXMP19fBMSVsGcOJ98G8N3Wl2gXcbTjbX7vUBxLaeASDQCm5Cu/0E2tvtb0Ea+BowtskFD0wvlc6Rf2M+Jx7dTu7ubFr2dnKDRaMQe2v/tcIrNB7FH0O50AcrBaApmRDVwFO31ql3pD8QW4dP0feNwl/Q+kFEtRyIGyaWXnpy1OO0qNJWHo1y6iCmAGkBb/Ru+HenDWIF2mo4r8G+tRRzoniSn2uqFLxANhe9LKHVyTbz6egk9+x5w5fK6ulSNNMhZ/Feno+GebLZV6isTTa6k5qNl5RnZ5u56Ib6SBvFzaWBBVFZzvnERWlt/Cg4l27XChLCqFyLekjhy6xJyoytgjPf7opIB8QPx7sYFiMXHPGt76m741MhCKMZfng0nBOIjmoJPsLqWHwgFpe6V6qtfcopxveR2Oy+J0ntIN/zCWkf8QNAJ7y6d8Bq4lxLc2/qJl5K7t432XwcqX5CrI34gzATWuYILQtdQPyePDK3iuOekCR3Efjhig1B1Uq5UoXEEoZX7d1q535J5S9VOeFyYyEBku5XTMXXKQTToX5Rg7OI44nbW5oKYeYK4EniMeF0YFNSmb+grhc84LyRCEP1/OurOcipCQbKxDeK2V5FcVyIDMQvsgz5gwFhcWWwKyRlvQ3gv29RwWoDYAbIofNyBxI9eDlQ+n3YgsgCWnr4MStGXQXmv9pF2La/k3OccV54JEBM4yp9EsXa/3LfO0dGPcYq0Y7DfZB8nJzZw2rppHgKgVHs8L5wvRwAAAABJRU5ErkJggg==)](https://sourcegraph.com/github.com/sourcegraph/conc) +[![Go Report Card](https://goreportcard.com/badge/github.com/sourcegraph/conc)](https://goreportcard.com/report/github.com/sourcegraph/conc) +[![codecov](https://codecov.io/gh/sourcegraph/conc/branch/main/graph/badge.svg?token=MQZTEA1QWT)](https://codecov.io/gh/sourcegraph/conc) +[![Discord](https://img.shields.io/badge/discord-chat-%235765F2)](https://discord.gg/bvXQXmtRjN) + +`conc` is your toolbelt for structured concurrency in go, making common tasks +easier and safer. + +```sh +go get github.com/sourcegraph/conc +``` + +# At a glance + +- Use [`conc.WaitGroup`](https://pkg.go.dev/github.com/sourcegraph/conc#WaitGroup) if you just want a safer version of `sync.WaitGroup` +- Use [`pool.Pool`](https://pkg.go.dev/github.com/sourcegraph/conc/pool#Pool) if you want a concurrency-limited task runner +- Use [`pool.ResultPool`](https://pkg.go.dev/github.com/sourcegraph/conc/pool#ResultPool) if you want a concurrent task runner that collects task results +- Use [`pool.(Result)?ErrorPool`](https://pkg.go.dev/github.com/sourcegraph/conc/pool#ErrorPool) if your tasks are fallible +- Use [`pool.(Result)?ContextPool`](https://pkg.go.dev/github.com/sourcegraph/conc/pool#ContextPool) if your tasks should be canceled on failure +- Use [`stream.Stream`](https://pkg.go.dev/github.com/sourcegraph/conc/stream#Stream) if you want to process an ordered stream of tasks in parallel with serial callbacks +- Use [`iter.Map`](https://pkg.go.dev/github.com/sourcegraph/conc/iter#Map) if you want to concurrently map a slice +- Use [`iter.ForEach`](https://pkg.go.dev/github.com/sourcegraph/conc/iter#ForEach) if you want to concurrently iterate over a slice +- Use [`panics.Catcher`](https://pkg.go.dev/github.com/sourcegraph/conc/panics#Catcher) if you want to catch panics in your own goroutines + +All pools are created with +[`pool.New()`](https://pkg.go.dev/github.com/sourcegraph/conc/pool#New) +or +[`pool.NewWithResults[T]()`](https://pkg.go.dev/github.com/sourcegraph/conc/pool#NewWithResults), +then configured with methods: + +- [`p.WithMaxGoroutines()`](https://pkg.go.dev/github.com/sourcegraph/conc/pool#Pool.MaxGoroutines) configures the maximum number of goroutines in the pool +- [`p.WithErrors()`](https://pkg.go.dev/github.com/sourcegraph/conc/pool#Pool.WithErrors) configures the pool to run tasks that return errors +- [`p.WithContext(ctx)`](https://pkg.go.dev/github.com/sourcegraph/conc/pool#Pool.WithContext) configures the pool to run tasks that should be canceled on first error +- [`p.WithFirstError()`](https://pkg.go.dev/github.com/sourcegraph/conc/pool#ErrorPool.WithFirstError) configures error pools to only keep the first returned error rather than an aggregated error +- [`p.WithCollectErrored()`](https://pkg.go.dev/github.com/sourcegraph/conc/pool#ResultContextPool.WithCollectErrored) configures result pools to collect results even when the task errored + +# Goals + +The main goals of the package are: +1) Make it harder to leak goroutines +2) Handle panics gracefully +3) Make concurrent code easier to read + +## Goal #1: Make it harder to leak goroutines + +A common pain point when working with goroutines is cleaning them up. It's +really easy to fire off a `go` statement and fail to properly wait for it to +complete. + +`conc` takes the opinionated stance that all concurrency should be scoped. +That is, goroutines should have an owner and that owner should always +ensure that its owned goroutines exit properly. + +In `conc`, the owner of a goroutine is always a `conc.WaitGroup`. Goroutines +are spawned in a `WaitGroup` with `(*WaitGroup).Go()`, and +`(*WaitGroup).Wait()` should always be called before the `WaitGroup` goes out +of scope. + +In some cases, you might want a spawned goroutine to outlast the scope of the +caller. In that case, you could pass a `WaitGroup` into the spawning function. + +```go +func main() { + var wg conc.WaitGroup + defer wg.Wait() + + startTheThing(&wg) +} + +func startTheThing(wg *conc.WaitGroup) { + wg.Go(func() { ... }) +} +``` + +For some more discussion on why scoped concurrency is nice, check out [this +blog +post](https://vorpus.org/blog/notes-on-structured-concurrency-or-go-statement-considered-harmful/). + +## Goal #2: Handle panics gracefully + +A frequent problem with goroutines in long-running applications is handling +panics. A goroutine spawned without a panic handler will crash the whole process +on panic. This is usually undesirable. + +However, if you do add a panic handler to a goroutine, what do you do with the +panic once you catch it? Some options: +1) Ignore it +2) Log it +3) Turn it into an error and return that to the goroutine spawner +4) Propagate the panic to the goroutine spawner + +Ignoring panics is a bad idea since panics usually mean there is actually +something wrong and someone should fix it. + +Just logging panics isn't great either because then there is no indication to the spawner +that something bad happened, and it might just continue on as normal even though your +program is in a really bad state. + +Both (3) and (4) are reasonable options, but both require the goroutine to have +an owner that can actually receive the message that something went wrong. This +is generally not true with a goroutine spawned with `go`, but in the `conc` +package, all goroutines have an owner that must collect the spawned goroutine. +In the conc package, any call to `Wait()` will panic if any of the spawned goroutines +panicked. Additionally, it decorates the panic value with a stacktrace from the child +goroutine so that you don't lose information about what caused the panic. + +Doing this all correctly every time you spawn something with `go` is not +trivial and it requires a lot of boilerplate that makes the important parts of +the code more difficult to read, so `conc` does this for you. + + + + + + + + + + +
stdlibconc
+ +```go +type caughtPanicError struct { + val any + stack []byte +} + +func (e *caughtPanicError) Error() string { + return fmt.Sprintf( + "panic: %q\n%s", + e.val, + string(e.stack) + ) +} + +func main() { + done := make(chan error) + go func() { + defer func() { + if v := recover(); v != nil { + done <- &caughtPanicError{ + val: v, + stack: debug.Stack() + } + } else { + done <- nil + } + }() + doSomethingThatMightPanic() + }() + err := <-done + if err != nil { + panic(err) + } +} +``` + + +```go +func main() { + var wg conc.WaitGroup + wg.Go(doSomethingThatMightPanic) + // panics with a nice stacktrace + wg.Wait() +} +``` +
+ +## Goal #3: Make concurrent code easier to read + +Doing concurrency correctly is difficult. Doing it in a way that doesn't +obfuscate what the code is actually doing is more difficult. The `conc` package +attempts to make common operations easier by abstracting as much boilerplate +complexity as possible. + +Want to run a set of concurrent tasks with a bounded set of goroutines? Use +`pool.New()`. Want to process an ordered stream of results concurrently, but +still maintain order? Try `stream.New()`. What about a concurrent map over +a slice? Take a peek at `iter.Map()`. + +Browse some examples below for some comparisons with doing these by hand. + +# Examples + +Each of these examples forgoes propagating panics for simplicity. To see +what kind of complexity that would add, check out the "Goal #2" header above. + +Spawn a set of goroutines and waiting for them to finish: + + + + + + + + + + +
stdlibconc
+ +```go +func main() { + var wg sync.WaitGroup + for i := 0; i < 10; i++ { + wg.Add(1) + go func() { + defer wg.Done() + // crashes on panic! + doSomething() + }() + } + wg.Wait() +} +``` + + +```go +func main() { + var wg conc.WaitGroup + for i := 0; i < 10; i++ { + wg.Go(doSomething) + } + wg.Wait() +} +``` +
+ +Process each element of a stream in a static pool of goroutines: + + + + + + + + + + +
stdlibconc
+ +```go +func process(stream chan int) { + var wg sync.WaitGroup + for i := 0; i < 10; i++ { + wg.Add(1) + go func() { + defer wg.Done() + for elem := range stream { + handle(elem) + } + }() + } + wg.Wait() +} +``` + + +```go +func process(stream chan int) { + p := pool.New().WithMaxGoroutines(10) + for elem := range stream { + elem := elem + p.Go(func() { + handle(elem) + }) + } + p.Wait() +} +``` +
+ +Process each element of a slice in a static pool of goroutines: + + + + + + + + + + +
stdlibconc
+ +```go +func process(values []int) { + feeder := make(chan int, 8) + + var wg sync.WaitGroup + for i := 0; i < 10; i++ { + wg.Add(1) + go func() { + defer wg.Done() + for elem := range feeder { + handle(elem) + } + }() + } + + for _, value := range values { + feeder <- value + } + close(feeder) + wg.Wait() +} +``` + + +```go +func process(values []int) { + iter.ForEach(values, handle) +} +``` +
+ +Concurrently map a slice: + + + + + + + + + + +
stdlibconc
+ +```go +func concMap( + input []int, + f func(int) int, +) []int { + res := make([]int, len(input)) + var idx atomic.Int64 + + var wg sync.WaitGroup + for i := 0; i < 10; i++ { + wg.Add(1) + go func() { + defer wg.Done() + + for { + i := int(idx.Add(1) - 1) + if i >= len(input) { + return + } + + res[i] = f(input[i]) + } + }() + } + wg.Wait() + return res +} +``` + + +```go +func concMap( + input []int, + f func(*int) int, +) []int { + return iter.Map(input, f) +} +``` +
+ +Process an ordered stream concurrently: + + + + + + + + + + + +
stdlibconc
+ +```go +func mapStream( + in chan int, + out chan int, + f func(int) int, +) { + tasks := make(chan func()) + taskResults := make(chan chan int) + + // Worker goroutines + var workerWg sync.WaitGroup + for i := 0; i < 10; i++ { + workerWg.Add(1) + go func() { + defer workerWg.Done() + for task := range tasks { + task() + } + }() + } + + // Ordered reader goroutines + var readerWg sync.WaitGroup + readerWg.Add(1) + go func() { + defer readerWg.Done() + for result := range taskResults { + item := <-result + out <- item + } + }() + + // Feed the workers with tasks + for elem := range in { + resultCh := make(chan int, 1) + taskResults <- resultCh + tasks <- func() { + resultCh <- f(elem) + } + } + + // We've exhausted input. + // Wait for everything to finish + close(tasks) + workerWg.Wait() + close(taskResults) + readerWg.Wait() +} +``` + + +```go +func mapStream( + in chan int, + out chan int, + f func(int) int, +) { + s := stream.New().WithMaxGoroutines(10) + for elem := range in { + elem := elem + s.Go(func() stream.Callback { + res := f(elem) + return func() { out <- res } + }) + } + s.Wait() +} +``` +
+ +# Status + +This package is currently pre-1.0. There are likely to be minor breaking +changes before a 1.0 release as we stabilize the APIs and tweak defaults. +Please open an issue if you have questions, concerns, or requests that you'd +like addressed before the 1.0 release. Currently, a 1.0 is targeted for +March 2023. diff --git a/vendor/github.com/sourcegraph/conc/panics/panics.go b/vendor/github.com/sourcegraph/conc/panics/panics.go new file mode 100644 index 0000000000..abbed7fa05 --- /dev/null +++ b/vendor/github.com/sourcegraph/conc/panics/panics.go @@ -0,0 +1,102 @@ +package panics + +import ( + "fmt" + "runtime" + "runtime/debug" + "sync/atomic" +) + +// Catcher is used to catch panics. You can execute a function with Try, +// which will catch any spawned panic. Try can be called any number of times, +// from any number of goroutines. Once all calls to Try have completed, you can +// get the value of the first panic (if any) with Recovered(), or you can just +// propagate the panic (re-panic) with Repanic(). +type Catcher struct { + recovered atomic.Pointer[Recovered] +} + +// Try executes f, catching any panic it might spawn. It is safe +// to call from multiple goroutines simultaneously. +func (p *Catcher) Try(f func()) { + defer p.tryRecover() + f() +} + +func (p *Catcher) tryRecover() { + if val := recover(); val != nil { + rp := NewRecovered(1, val) + p.recovered.CompareAndSwap(nil, &rp) + } +} + +// Repanic panics if any calls to Try caught a panic. It will panic with the +// value of the first panic caught, wrapped in a panics.Recovered with caller +// information. +func (p *Catcher) Repanic() { + if val := p.Recovered(); val != nil { + panic(val) + } +} + +// Recovered returns the value of the first panic caught by Try, or nil if +// no calls to Try panicked. +func (p *Catcher) Recovered() *Recovered { + return p.recovered.Load() +} + +// NewRecovered creates a panics.Recovered from a panic value and a collected +// stacktrace. The skip parameter allows the caller to skip stack frames when +// collecting the stacktrace. Calling with a skip of 0 means include the call to +// NewRecovered in the stacktrace. +func NewRecovered(skip int, value any) Recovered { + // 64 frames should be plenty + var callers [64]uintptr + n := runtime.Callers(skip+1, callers[:]) + return Recovered{ + Value: value, + Callers: callers[:n], + Stack: debug.Stack(), + } +} + +// Recovered is a panic that was caught with recover(). +type Recovered struct { + // The original value of the panic. + Value any + // The caller list as returned by runtime.Callers when the panic was + // recovered. Can be used to produce a more detailed stack information with + // runtime.CallersFrames. + Callers []uintptr + // The formatted stacktrace from the goroutine where the panic was recovered. + // Easier to use than Callers. + Stack []byte +} + +// String renders a human-readable formatting of the panic. +func (p *Recovered) String() string { + return fmt.Sprintf("panic: %v\nstacktrace:\n%s\n", p.Value, p.Stack) +} + +// AsError casts the panic into an error implementation. The implementation +// is unwrappable with the cause of the panic, if the panic was provided one. +func (p *Recovered) AsError() error { + if p == nil { + return nil + } + return &ErrRecovered{*p} +} + +// ErrRecovered wraps a panics.Recovered in an error implementation. +type ErrRecovered struct{ Recovered } + +var _ error = (*ErrRecovered)(nil) + +func (p *ErrRecovered) Error() string { return p.String() } + +func (p *ErrRecovered) Unwrap() error { + if err, ok := p.Value.(error); ok { + return err + } + return nil +} diff --git a/vendor/github.com/sourcegraph/conc/panics/try.go b/vendor/github.com/sourcegraph/conc/panics/try.go new file mode 100644 index 0000000000..4ded92a1cb --- /dev/null +++ b/vendor/github.com/sourcegraph/conc/panics/try.go @@ -0,0 +1,11 @@ +package panics + +// Try executes f, catching and returning any panic it might spawn. +// +// The recovered panic can be propagated with panic(), or handled as a normal error with +// (*panics.Recovered).AsError(). +func Try(f func()) *Recovered { + var c Catcher + c.Try(f) + return c.Recovered() +} diff --git a/vendor/github.com/sourcegraph/conc/pool/context_pool.go b/vendor/github.com/sourcegraph/conc/pool/context_pool.go new file mode 100644 index 0000000000..85c34e5aef --- /dev/null +++ b/vendor/github.com/sourcegraph/conc/pool/context_pool.go @@ -0,0 +1,104 @@ +package pool + +import ( + "context" +) + +// ContextPool is a pool that runs tasks that take a context. +// A new ContextPool should be created with `New().WithContext(ctx)`. +// +// The configuration methods (With*) will panic if they are used after calling +// Go() for the first time. +type ContextPool struct { + errorPool ErrorPool + + ctx context.Context + cancel context.CancelFunc + + cancelOnError bool +} + +// Go submits a task. If it returns an error, the error will be +// collected and returned by Wait(). If all goroutines in the pool +// are busy, a call to Go() will block until the task can be started. +func (p *ContextPool) Go(f func(ctx context.Context) error) { + p.errorPool.Go(func() error { + if p.cancelOnError { + // If we are cancelling on error, then we also want to cancel if a + // panic is raised. To do this, we need to recover, cancel, and then + // re-throw the caught panic. + defer func() { + if r := recover(); r != nil { + p.cancel() + panic(r) + } + }() + } + + err := f(p.ctx) + if err != nil && p.cancelOnError { + // Leaky abstraction warning: We add the error directly because + // otherwise, canceling could cause another goroutine to exit and + // return an error before this error was added, which breaks the + // expectations of WithFirstError(). + p.errorPool.addErr(err) + p.cancel() + return nil + } + return err + }) +} + +// Wait cleans up all spawned goroutines, propagates any panics, and +// returns an error if any of the tasks errored. +func (p *ContextPool) Wait() error { + // Make sure we call cancel after pool is done to avoid memory leakage. + defer p.cancel() + return p.errorPool.Wait() +} + +// WithFirstError configures the pool to only return the first error +// returned by a task. By default, Wait() will return a combined error. +// This is particularly useful for (*ContextPool).WithCancelOnError(), +// where all errors after the first are likely to be context.Canceled. +func (p *ContextPool) WithFirstError() *ContextPool { + p.panicIfInitialized() + p.errorPool.WithFirstError() + return p +} + +// WithCancelOnError configures the pool to cancel its context as soon as +// any task returns an error or panics. By default, the pool's context is not +// canceled until the parent context is canceled. +// +// In this case, all errors returned from the pool after the first will +// likely be context.Canceled - you may want to also use +// (*ContextPool).WithFirstError() to configure the pool to only return +// the first error. +func (p *ContextPool) WithCancelOnError() *ContextPool { + p.panicIfInitialized() + p.cancelOnError = true + return p +} + +// WithFailFast is an alias for the combination of WithFirstError and +// WithCancelOnError. By default, the errors from all tasks are returned and +// the pool's context is not canceled until the parent context is canceled. +func (p *ContextPool) WithFailFast() *ContextPool { + p.panicIfInitialized() + p.WithFirstError() + p.WithCancelOnError() + return p +} + +// WithMaxGoroutines limits the number of goroutines in a pool. +// Defaults to unlimited. Panics if n < 1. +func (p *ContextPool) WithMaxGoroutines(n int) *ContextPool { + p.panicIfInitialized() + p.errorPool.WithMaxGoroutines(n) + return p +} + +func (p *ContextPool) panicIfInitialized() { + p.errorPool.panicIfInitialized() +} diff --git a/vendor/github.com/sourcegraph/conc/pool/error_pool.go b/vendor/github.com/sourcegraph/conc/pool/error_pool.go new file mode 100644 index 0000000000..e1789e61b6 --- /dev/null +++ b/vendor/github.com/sourcegraph/conc/pool/error_pool.go @@ -0,0 +1,100 @@ +package pool + +import ( + "context" + "errors" + "sync" +) + +// ErrorPool is a pool that runs tasks that may return an error. +// Errors are collected and returned by Wait(). +// +// The configuration methods (With*) will panic if they are used after calling +// Go() for the first time. +// +// A new ErrorPool should be created using `New().WithErrors()`. +type ErrorPool struct { + pool Pool + + onlyFirstError bool + + mu sync.Mutex + errs []error +} + +// Go submits a task to the pool. If all goroutines in the pool +// are busy, a call to Go() will block until the task can be started. +func (p *ErrorPool) Go(f func() error) { + p.pool.Go(func() { + p.addErr(f()) + }) +} + +// Wait cleans up any spawned goroutines, propagating any panics and +// returning any errors from tasks. +func (p *ErrorPool) Wait() error { + p.pool.Wait() + + errs := p.errs + p.errs = nil // reset errs + + if len(errs) == 0 { + return nil + } else if p.onlyFirstError { + return errs[0] + } else { + return errors.Join(errs...) + } +} + +// WithContext converts the pool to a ContextPool for tasks that should +// run under the same context, such that they each respect shared cancellation. +// For example, WithCancelOnError can be configured on the returned pool to +// signal that all goroutines should be cancelled upon the first error. +func (p *ErrorPool) WithContext(ctx context.Context) *ContextPool { + p.panicIfInitialized() + ctx, cancel := context.WithCancel(ctx) + return &ContextPool{ + errorPool: p.deref(), + ctx: ctx, + cancel: cancel, + } +} + +// WithFirstError configures the pool to only return the first error +// returned by a task. By default, Wait() will return a combined error. +func (p *ErrorPool) WithFirstError() *ErrorPool { + p.panicIfInitialized() + p.onlyFirstError = true + return p +} + +// WithMaxGoroutines limits the number of goroutines in a pool. +// Defaults to unlimited. Panics if n < 1. +func (p *ErrorPool) WithMaxGoroutines(n int) *ErrorPool { + p.panicIfInitialized() + p.pool.WithMaxGoroutines(n) + return p +} + +// deref is a helper that creates a shallow copy of the pool with the same +// settings. We don't want to just dereference the pointer because that makes +// the copylock lint angry. +func (p *ErrorPool) deref() ErrorPool { + return ErrorPool{ + pool: p.pool.deref(), + onlyFirstError: p.onlyFirstError, + } +} + +func (p *ErrorPool) panicIfInitialized() { + p.pool.panicIfInitialized() +} + +func (p *ErrorPool) addErr(err error) { + if err != nil { + p.mu.Lock() + p.errs = append(p.errs, err) + p.mu.Unlock() + } +} diff --git a/vendor/github.com/sourcegraph/conc/pool/pool.go b/vendor/github.com/sourcegraph/conc/pool/pool.go new file mode 100644 index 0000000000..8f4494efb1 --- /dev/null +++ b/vendor/github.com/sourcegraph/conc/pool/pool.go @@ -0,0 +1,174 @@ +package pool + +import ( + "context" + "sync" + + "github.com/sourcegraph/conc" +) + +// New creates a new Pool. +func New() *Pool { + return &Pool{} +} + +// Pool is a pool of goroutines used to execute tasks concurrently. +// +// Tasks are submitted with Go(). Once all your tasks have been submitted, you +// must call Wait() to clean up any spawned goroutines and propagate any +// panics. +// +// Goroutines are started lazily, so creating a new pool is cheap. There will +// never be more goroutines spawned than there are tasks submitted. +// +// The configuration methods (With*) will panic if they are used after calling +// Go() for the first time. +// +// Pool is efficient, but not zero cost. It should not be used for very short +// tasks. Startup and teardown come with an overhead of around 1µs, and each +// task has an overhead of around 300ns. +type Pool struct { + handle conc.WaitGroup + limiter limiter + tasks chan func() + initOnce sync.Once +} + +// Go submits a task to be run in the pool. If all goroutines in the pool +// are busy, a call to Go() will block until the task can be started. +func (p *Pool) Go(f func()) { + p.init() + + if p.limiter == nil { + // No limit on the number of goroutines. + select { + case p.tasks <- f: + // A goroutine was available to handle the task. + default: + // No goroutine was available to handle the task. + // Spawn a new one and send it the task. + p.handle.Go(func() { + p.worker(f) + }) + } + } else { + select { + case p.limiter <- struct{}{}: + // If we are below our limit, spawn a new worker rather + // than waiting for one to become available. + p.handle.Go(func() { + p.worker(f) + }) + case p.tasks <- f: + // A worker is available and has accepted the task. + return + } + } + +} + +// Wait cleans up spawned goroutines, propagating any panics that were +// raised by a tasks. +func (p *Pool) Wait() { + p.init() + + close(p.tasks) + + // After Wait() returns, reset the struct so tasks will be reinitialized on + // next use. This better matches the behavior of sync.WaitGroup + defer func() { p.initOnce = sync.Once{} }() + + p.handle.Wait() +} + +// MaxGoroutines returns the maximum size of the pool. +func (p *Pool) MaxGoroutines() int { + return p.limiter.limit() +} + +// WithMaxGoroutines limits the number of goroutines in a pool. +// Defaults to unlimited. Panics if n < 1. +func (p *Pool) WithMaxGoroutines(n int) *Pool { + p.panicIfInitialized() + if n < 1 { + panic("max goroutines in a pool must be greater than zero") + } + p.limiter = make(limiter, n) + return p +} + +// init ensures that the pool is initialized before use. This makes the +// zero value of the pool usable. +func (p *Pool) init() { + p.initOnce.Do(func() { + p.tasks = make(chan func()) + }) +} + +// panicIfInitialized will trigger a panic if a configuration method is called +// after the pool has started any goroutines for the first time. In the case that +// new settings are needed, a new pool should be created. +func (p *Pool) panicIfInitialized() { + if p.tasks != nil { + panic("pool can not be reconfigured after calling Go() for the first time") + } +} + +// WithErrors converts the pool to an ErrorPool so the submitted tasks can +// return errors. +func (p *Pool) WithErrors() *ErrorPool { + p.panicIfInitialized() + return &ErrorPool{ + pool: p.deref(), + } +} + +// deref is a helper that creates a shallow copy of the pool with the same +// settings. We don't want to just dereference the pointer because that makes +// the copylock lint angry. +func (p *Pool) deref() Pool { + p.panicIfInitialized() + return Pool{ + limiter: p.limiter, + } +} + +// WithContext converts the pool to a ContextPool for tasks that should +// run under the same context, such that they each respect shared cancellation. +// For example, WithCancelOnError can be configured on the returned pool to +// signal that all goroutines should be cancelled upon the first error. +func (p *Pool) WithContext(ctx context.Context) *ContextPool { + p.panicIfInitialized() + ctx, cancel := context.WithCancel(ctx) + return &ContextPool{ + errorPool: p.WithErrors().deref(), + ctx: ctx, + cancel: cancel, + } +} + +func (p *Pool) worker(initialFunc func()) { + // The only time this matters is if the task panics. + // This makes it possible to spin up new workers in that case. + defer p.limiter.release() + + if initialFunc != nil { + initialFunc() + } + + for f := range p.tasks { + f() + } +} + +type limiter chan struct{} + +func (l limiter) limit() int { + return cap(l) +} + +func (l limiter) release() { + if l != nil { + <-l + } +} diff --git a/vendor/github.com/sourcegraph/conc/pool/result_context_pool.go b/vendor/github.com/sourcegraph/conc/pool/result_context_pool.go new file mode 100644 index 0000000000..6bc30dd63c --- /dev/null +++ b/vendor/github.com/sourcegraph/conc/pool/result_context_pool.go @@ -0,0 +1,85 @@ +package pool + +import ( + "context" +) + +// ResultContextPool is a pool that runs tasks that take a context and return a +// result. The context passed to the task will be canceled if any of the tasks +// return an error, which makes its functionality different than just capturing +// a context with the task closure. +// +// The configuration methods (With*) will panic if they are used after calling +// Go() for the first time. +type ResultContextPool[T any] struct { + contextPool ContextPool + agg resultAggregator[T] + collectErrored bool +} + +// Go submits a task to the pool. If all goroutines in the pool +// are busy, a call to Go() will block until the task can be started. +func (p *ResultContextPool[T]) Go(f func(context.Context) (T, error)) { + idx := p.agg.nextIndex() + p.contextPool.Go(func(ctx context.Context) error { + res, err := f(ctx) + p.agg.save(idx, res, err != nil) + return err + }) +} + +// Wait cleans up all spawned goroutines, propagates any panics, and +// returns an error if any of the tasks errored. +func (p *ResultContextPool[T]) Wait() ([]T, error) { + err := p.contextPool.Wait() + results := p.agg.collect(p.collectErrored) + p.agg = resultAggregator[T]{} + return results, err +} + +// WithCollectErrored configures the pool to still collect the result of a task +// even if the task returned an error. By default, the result of tasks that errored +// are ignored and only the error is collected. +func (p *ResultContextPool[T]) WithCollectErrored() *ResultContextPool[T] { + p.panicIfInitialized() + p.collectErrored = true + return p +} + +// WithFirstError configures the pool to only return the first error +// returned by a task. By default, Wait() will return a combined error. +func (p *ResultContextPool[T]) WithFirstError() *ResultContextPool[T] { + p.panicIfInitialized() + p.contextPool.WithFirstError() + return p +} + +// WithCancelOnError configures the pool to cancel its context as soon as +// any task returns an error. By default, the pool's context is not +// canceled until the parent context is canceled. +func (p *ResultContextPool[T]) WithCancelOnError() *ResultContextPool[T] { + p.panicIfInitialized() + p.contextPool.WithCancelOnError() + return p +} + +// WithFailFast is an alias for the combination of WithFirstError and +// WithCancelOnError. By default, the errors from all tasks are returned and +// the pool's context is not canceled until the parent context is canceled. +func (p *ResultContextPool[T]) WithFailFast() *ResultContextPool[T] { + p.panicIfInitialized() + p.contextPool.WithFailFast() + return p +} + +// WithMaxGoroutines limits the number of goroutines in a pool. +// Defaults to unlimited. Panics if n < 1. +func (p *ResultContextPool[T]) WithMaxGoroutines(n int) *ResultContextPool[T] { + p.panicIfInitialized() + p.contextPool.WithMaxGoroutines(n) + return p +} + +func (p *ResultContextPool[T]) panicIfInitialized() { + p.contextPool.panicIfInitialized() +} diff --git a/vendor/github.com/sourcegraph/conc/pool/result_error_pool.go b/vendor/github.com/sourcegraph/conc/pool/result_error_pool.go new file mode 100644 index 0000000000..832cd9bb47 --- /dev/null +++ b/vendor/github.com/sourcegraph/conc/pool/result_error_pool.go @@ -0,0 +1,80 @@ +package pool + +import ( + "context" +) + +// ResultErrorPool is a pool that executes tasks that return a generic result +// type and an error. Tasks are executed in the pool with Go(), then the +// results of the tasks are returned by Wait(). +// +// The order of the results is guaranteed to be the same as the order the +// tasks were submitted. +// +// The configuration methods (With*) will panic if they are used after calling +// Go() for the first time. +type ResultErrorPool[T any] struct { + errorPool ErrorPool + agg resultAggregator[T] + collectErrored bool +} + +// Go submits a task to the pool. If all goroutines in the pool +// are busy, a call to Go() will block until the task can be started. +func (p *ResultErrorPool[T]) Go(f func() (T, error)) { + idx := p.agg.nextIndex() + p.errorPool.Go(func() error { + res, err := f() + p.agg.save(idx, res, err != nil) + return err + }) +} + +// Wait cleans up any spawned goroutines, propagating any panics and +// returning the results and any errors from tasks. +func (p *ResultErrorPool[T]) Wait() ([]T, error) { + err := p.errorPool.Wait() + results := p.agg.collect(p.collectErrored) + p.agg = resultAggregator[T]{} // reset for reuse + return results, err +} + +// WithCollectErrored configures the pool to still collect the result of a task +// even if the task returned an error. By default, the result of tasks that errored +// are ignored and only the error is collected. +func (p *ResultErrorPool[T]) WithCollectErrored() *ResultErrorPool[T] { + p.panicIfInitialized() + p.collectErrored = true + return p +} + +// WithContext converts the pool to a ResultContextPool for tasks that should +// run under the same context, such that they each respect shared cancellation. +// For example, WithCancelOnError can be configured on the returned pool to +// signal that all goroutines should be cancelled upon the first error. +func (p *ResultErrorPool[T]) WithContext(ctx context.Context) *ResultContextPool[T] { + p.panicIfInitialized() + return &ResultContextPool[T]{ + contextPool: *p.errorPool.WithContext(ctx), + } +} + +// WithFirstError configures the pool to only return the first error +// returned by a task. By default, Wait() will return a combined error. +func (p *ResultErrorPool[T]) WithFirstError() *ResultErrorPool[T] { + p.panicIfInitialized() + p.errorPool.WithFirstError() + return p +} + +// WithMaxGoroutines limits the number of goroutines in a pool. +// Defaults to unlimited. Panics if n < 1. +func (p *ResultErrorPool[T]) WithMaxGoroutines(n int) *ResultErrorPool[T] { + p.panicIfInitialized() + p.errorPool.WithMaxGoroutines(n) + return p +} + +func (p *ResultErrorPool[T]) panicIfInitialized() { + p.errorPool.panicIfInitialized() +} diff --git a/vendor/github.com/sourcegraph/conc/pool/result_pool.go b/vendor/github.com/sourcegraph/conc/pool/result_pool.go new file mode 100644 index 0000000000..f73a77261e --- /dev/null +++ b/vendor/github.com/sourcegraph/conc/pool/result_pool.go @@ -0,0 +1,142 @@ +package pool + +import ( + "context" + "sort" + "sync" +) + +// NewWithResults creates a new ResultPool for tasks with a result of type T. +// +// The configuration methods (With*) will panic if they are used after calling +// Go() for the first time. +func NewWithResults[T any]() *ResultPool[T] { + return &ResultPool[T]{ + pool: *New(), + } +} + +// ResultPool is a pool that executes tasks that return a generic result type. +// Tasks are executed in the pool with Go(), then the results of the tasks are +// returned by Wait(). +// +// The order of the results is guaranteed to be the same as the order the +// tasks were submitted. +type ResultPool[T any] struct { + pool Pool + agg resultAggregator[T] +} + +// Go submits a task to the pool. If all goroutines in the pool +// are busy, a call to Go() will block until the task can be started. +func (p *ResultPool[T]) Go(f func() T) { + idx := p.agg.nextIndex() + p.pool.Go(func() { + p.agg.save(idx, f(), false) + }) +} + +// Wait cleans up all spawned goroutines, propagating any panics, and returning +// a slice of results from tasks that did not panic. +func (p *ResultPool[T]) Wait() []T { + p.pool.Wait() + results := p.agg.collect(true) + p.agg = resultAggregator[T]{} // reset for reuse + return results +} + +// MaxGoroutines returns the maximum size of the pool. +func (p *ResultPool[T]) MaxGoroutines() int { + return p.pool.MaxGoroutines() +} + +// WithErrors converts the pool to an ResultErrorPool so the submitted tasks +// can return errors. +func (p *ResultPool[T]) WithErrors() *ResultErrorPool[T] { + p.panicIfInitialized() + return &ResultErrorPool[T]{ + errorPool: *p.pool.WithErrors(), + } +} + +// WithContext converts the pool to a ResultContextPool for tasks that should +// run under the same context, such that they each respect shared cancellation. +// For example, WithCancelOnError can be configured on the returned pool to +// signal that all goroutines should be cancelled upon the first error. +func (p *ResultPool[T]) WithContext(ctx context.Context) *ResultContextPool[T] { + p.panicIfInitialized() + return &ResultContextPool[T]{ + contextPool: *p.pool.WithContext(ctx), + } +} + +// WithMaxGoroutines limits the number of goroutines in a pool. +// Defaults to unlimited. Panics if n < 1. +func (p *ResultPool[T]) WithMaxGoroutines(n int) *ResultPool[T] { + p.panicIfInitialized() + p.pool.WithMaxGoroutines(n) + return p +} + +func (p *ResultPool[T]) panicIfInitialized() { + p.pool.panicIfInitialized() +} + +// resultAggregator is a utility type that lets us safely append from multiple +// goroutines. The zero value is valid and ready to use. +type resultAggregator[T any] struct { + mu sync.Mutex + len int + results []T + errored []int +} + +// nextIndex reserves a slot for a result. The returned value should be passed +// to save() when adding a result to the aggregator. +func (r *resultAggregator[T]) nextIndex() int { + r.mu.Lock() + defer r.mu.Unlock() + + nextIdx := r.len + r.len += 1 + return nextIdx +} + +func (r *resultAggregator[T]) save(i int, res T, errored bool) { + r.mu.Lock() + defer r.mu.Unlock() + + if i >= len(r.results) { + old := r.results + r.results = make([]T, r.len) + copy(r.results, old) + } + + r.results[i] = res + + if errored { + r.errored = append(r.errored, i) + } +} + +// collect returns the set of aggregated results. +func (r *resultAggregator[T]) collect(collectErrored bool) []T { + if !r.mu.TryLock() { + panic("collect should not be called until all goroutines have exited") + } + + if collectErrored || len(r.errored) == 0 { + return r.results + } + + filtered := r.results[:0] + sort.Ints(r.errored) + for i, e := range r.errored { + if i == 0 { + filtered = append(filtered, r.results[:e]...) + } else { + filtered = append(filtered, r.results[r.errored[i-1]+1:e]...) + } + } + return filtered +} diff --git a/vendor/github.com/sourcegraph/conc/waitgroup.go b/vendor/github.com/sourcegraph/conc/waitgroup.go new file mode 100644 index 0000000000..47b1bc1a5c --- /dev/null +++ b/vendor/github.com/sourcegraph/conc/waitgroup.go @@ -0,0 +1,52 @@ +package conc + +import ( + "sync" + + "github.com/sourcegraph/conc/panics" +) + +// NewWaitGroup creates a new WaitGroup. +func NewWaitGroup() *WaitGroup { + return &WaitGroup{} +} + +// WaitGroup is the primary building block for scoped concurrency. +// Goroutines can be spawned in the WaitGroup with the Go method, +// and calling Wait() will ensure that each of those goroutines exits +// before continuing. Any panics in a child goroutine will be caught +// and propagated to the caller of Wait(). +// +// The zero value of WaitGroup is usable, just like sync.WaitGroup. +// Also like sync.WaitGroup, it must not be copied after first use. +type WaitGroup struct { + wg sync.WaitGroup + pc panics.Catcher +} + +// Go spawns a new goroutine in the WaitGroup. +func (h *WaitGroup) Go(f func()) { + h.wg.Add(1) + go func() { + defer h.wg.Done() + h.pc.Try(f) + }() +} + +// Wait will block until all goroutines spawned with Go exit and will +// propagate any panics spawned in a child goroutine. +func (h *WaitGroup) Wait() { + h.wg.Wait() + + // Propagate a panic if we caught one from a child goroutine. + h.pc.Repanic() +} + +// WaitAndRecover will block until all goroutines spawned with Go exit and +// will return a *panics.Recovered if one of the child goroutines panics. +func (h *WaitGroup) WaitAndRecover() *panics.Recovered { + h.wg.Wait() + + // Return a recovered panic if we caught one from a child goroutine. + return h.pc.Recovered() +} diff --git a/vendor/github.com/spf13/cast/.editorconfig b/vendor/github.com/spf13/cast/.editorconfig new file mode 100644 index 0000000000..a85749f190 --- /dev/null +++ b/vendor/github.com/spf13/cast/.editorconfig @@ -0,0 +1,15 @@ +root = true + +[*] +charset = utf-8 +end_of_line = lf +indent_size = 4 +indent_style = space +insert_final_newline = true +trim_trailing_whitespace = true + +[*.go] +indent_style = tab + +[{*.yml,*.yaml}] +indent_size = 2 diff --git a/vendor/github.com/spf13/cast/.gitignore b/vendor/github.com/spf13/cast/.gitignore new file mode 100644 index 0000000000..53053a8ac5 --- /dev/null +++ b/vendor/github.com/spf13/cast/.gitignore @@ -0,0 +1,25 @@ +# Compiled Object files, Static and Dynamic libs (Shared Objects) +*.o +*.a +*.so + +# Folders +_obj +_test + +# Architecture specific extensions/prefixes +*.[568vq] +[568vq].out + +*.cgo1.go +*.cgo2.c +_cgo_defun.c +_cgo_gotypes.go +_cgo_export.* + +_testmain.go + +*.exe +*.test + +*.bench diff --git a/vendor/github.com/spf13/cast/.golangci.yaml b/vendor/github.com/spf13/cast/.golangci.yaml new file mode 100644 index 0000000000..e00fd47aa2 --- /dev/null +++ b/vendor/github.com/spf13/cast/.golangci.yaml @@ -0,0 +1,39 @@ +version: "2" + +run: + timeout: 10m + +linters: + enable: + - errcheck + - govet + - ineffassign + - misspell + - nolintlint + # - revive + - unused + + disable: + - staticcheck + + settings: + misspell: + locale: US + nolintlint: + allow-unused: false # report any unused nolint directives + require-specific: false # don't require nolint directives to be specific about which linter is being skipped + +formatters: + enable: + - gci + - gofmt + # - gofumpt + - goimports + # - golines + + settings: + gci: + sections: + - standard + - default + - localmodule diff --git a/vendor/github.com/spf13/cast/LICENSE b/vendor/github.com/spf13/cast/LICENSE new file mode 100644 index 0000000000..4527efb9c0 --- /dev/null +++ b/vendor/github.com/spf13/cast/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2014 Steve Francia + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/vendor/github.com/spf13/cast/Makefile b/vendor/github.com/spf13/cast/Makefile new file mode 100644 index 0000000000..f01a5dbb6e --- /dev/null +++ b/vendor/github.com/spf13/cast/Makefile @@ -0,0 +1,40 @@ +GOVERSION := $(shell go version | cut -d ' ' -f 3 | cut -d '.' -f 2) + +.PHONY: check fmt lint test test-race vet test-cover-html help +.DEFAULT_GOAL := help + +check: test-race fmt vet lint ## Run tests and linters + +test: ## Run tests + go test ./... + +test-race: ## Run tests with race detector + go test -race ./... + +fmt: ## Run gofmt linter +ifeq "$(GOVERSION)" "12" + @for d in `go list` ; do \ + if [ "`gofmt -l -s $$GOPATH/src/$$d | tee /dev/stderr`" ]; then \ + echo "^ improperly formatted go files" && echo && exit 1; \ + fi \ + done +endif + +lint: ## Run golint linter + @for d in `go list` ; do \ + if [ "`golint $$d | tee /dev/stderr`" ]; then \ + echo "^ golint errors!" && echo && exit 1; \ + fi \ + done + +vet: ## Run go vet linter + @if [ "`go vet | tee /dev/stderr`" ]; then \ + echo "^ go vet errors!" && echo && exit 1; \ + fi + +test-cover-html: ## Generate test coverage report + go test -coverprofile=coverage.out -covermode=count + go tool cover -func=coverage.out + +help: + @grep -E '^[a-zA-Z0-9_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' diff --git a/vendor/github.com/spf13/cast/README.md b/vendor/github.com/spf13/cast/README.md new file mode 100644 index 0000000000..c58eccb3fd --- /dev/null +++ b/vendor/github.com/spf13/cast/README.md @@ -0,0 +1,79 @@ +# cast + +[![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/spf13/cast/ci.yaml?style=flat-square)](https://github.com/spf13/cast/actions/workflows/ci.yaml) +[![go.dev reference](https://img.shields.io/badge/go.dev-reference-007d9c?logo=go&logoColor=white&style=flat-square)](https://pkg.go.dev/mod/github.com/spf13/cast) +![GitHub go.mod Go version](https://img.shields.io/github/go-mod/go-version/spf13/cast?style=flat-square&color=61CFDD) +[![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/spf13/cast/badge?style=flat-square)](https://deps.dev/go/github.com%252Fspf13%252Fcast) + +Easy and safe casting from one type to another in Go + +Don’t Panic! ... Cast + +## What is Cast? + +Cast is a library to convert between different go types in a consistent and easy way. + +Cast provides simple functions to easily convert a number to a string, an +interface into a bool, etc. Cast does this intelligently when an obvious +conversion is possible. It doesn’t make any attempts to guess what you meant, +for example you can only convert a string to an int when it is a string +representation of an int such as “8”. Cast was developed for use in +[Hugo](https://gohugo.io), a website engine which uses YAML, TOML or JSON +for meta data. + +## Why use Cast? + +When working with dynamic data in Go you often need to cast or convert the data +from one type into another. Cast goes beyond just using type assertion (though +it uses that when possible) to provide a very straightforward and convenient +library. + +If you are working with interfaces to handle things like dynamic content +you’ll need an easy way to convert an interface into a given type. This +is the library for you. + +If you are taking in data from YAML, TOML or JSON or other formats which lack +full types, then Cast is the library for you. + +## Usage + +Cast provides a handful of To_____ methods. These methods will always return +the desired type. **If input is provided that will not convert to that type, the +0 or nil value for that type will be returned**. + +Cast also provides identical methods To_____E. These return the same result as +the To_____ methods, plus an additional error which tells you if it successfully +converted. Using these methods you can tell the difference between when the +input matched the zero value or when the conversion failed and the zero value +was returned. + +The following examples are merely a sample of what is available. Please review +the code for a complete set. + +### Example ‘ToString’: + + cast.ToString("mayonegg") // "mayonegg" + cast.ToString(8) // "8" + cast.ToString(8.31) // "8.31" + cast.ToString([]byte("one time")) // "one time" + cast.ToString(nil) // "" + + var foo interface{} = "one more time" + cast.ToString(foo) // "one more time" + + +### Example ‘ToInt’: + + cast.ToInt(8) // 8 + cast.ToInt(8.31) // 8 + cast.ToInt("8") // 8 + cast.ToInt(true) // 1 + cast.ToInt(false) // 0 + + var eight interface{} = 8 + cast.ToInt(eight) // 8 + cast.ToInt(nil) // 0 + +## License + +The project is licensed under the [MIT License](LICENSE). diff --git a/vendor/github.com/spf13/cast/alias.go b/vendor/github.com/spf13/cast/alias.go new file mode 100644 index 0000000000..855d60005d --- /dev/null +++ b/vendor/github.com/spf13/cast/alias.go @@ -0,0 +1,69 @@ +// Copyright © 2014 Steve Francia . +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file. +package cast + +import ( + "reflect" + "slices" +) + +var kindNames = []string{ + reflect.String: "string", + reflect.Bool: "bool", + reflect.Int: "int", + reflect.Int8: "int8", + reflect.Int16: "int16", + reflect.Int32: "int32", + reflect.Int64: "int64", + reflect.Uint: "uint", + reflect.Uint8: "uint8", + reflect.Uint16: "uint16", + reflect.Uint32: "uint32", + reflect.Uint64: "uint64", + reflect.Float32: "float32", + reflect.Float64: "float64", +} + +var kinds = map[reflect.Kind]func(reflect.Value) any{ + reflect.String: func(v reflect.Value) any { return v.String() }, + reflect.Bool: func(v reflect.Value) any { return v.Bool() }, + reflect.Int: func(v reflect.Value) any { return int(v.Int()) }, + reflect.Int8: func(v reflect.Value) any { return int8(v.Int()) }, + reflect.Int16: func(v reflect.Value) any { return int16(v.Int()) }, + reflect.Int32: func(v reflect.Value) any { return int32(v.Int()) }, + reflect.Int64: func(v reflect.Value) any { return v.Int() }, + reflect.Uint: func(v reflect.Value) any { return uint(v.Uint()) }, + reflect.Uint8: func(v reflect.Value) any { return uint8(v.Uint()) }, + reflect.Uint16: func(v reflect.Value) any { return uint16(v.Uint()) }, + reflect.Uint32: func(v reflect.Value) any { return uint32(v.Uint()) }, + reflect.Uint64: func(v reflect.Value) any { return v.Uint() }, + reflect.Float32: func(v reflect.Value) any { return float32(v.Float()) }, + reflect.Float64: func(v reflect.Value) any { return v.Float() }, +} + +// resolveAlias attempts to resolve a named type to its underlying basic type (if possible). +// +// Pointers are expected to be indirected by this point. +func resolveAlias(i any) (any, bool) { + if i == nil { + return nil, false + } + + t := reflect.TypeOf(i) + + // Not a named type + if t.Name() == "" || slices.Contains(kindNames, t.Name()) { + return i, false + } + + resolve, ok := kinds[t.Kind()] + if !ok { // Not a supported kind + return i, false + } + + v := reflect.ValueOf(i) + + return resolve(v), true +} diff --git a/vendor/github.com/spf13/cast/basic.go b/vendor/github.com/spf13/cast/basic.go new file mode 100644 index 0000000000..fa330e207a --- /dev/null +++ b/vendor/github.com/spf13/cast/basic.go @@ -0,0 +1,131 @@ +// Copyright © 2014 Steve Francia . +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file. + +package cast + +import ( + "encoding/json" + "fmt" + "html/template" + "strconv" + "time" +) + +// ToBoolE casts any value to a bool type. +func ToBoolE(i any) (bool, error) { + i, _ = indirect(i) + + switch b := i.(type) { + case bool: + return b, nil + case nil: + return false, nil + case int: + return b != 0, nil + case int8: + return b != 0, nil + case int16: + return b != 0, nil + case int32: + return b != 0, nil + case int64: + return b != 0, nil + case uint: + return b != 0, nil + case uint8: + return b != 0, nil + case uint16: + return b != 0, nil + case uint32: + return b != 0, nil + case uint64: + return b != 0, nil + case float32: + return b != 0, nil + case float64: + return b != 0, nil + case time.Duration: + return b != 0, nil + case string: + return strconv.ParseBool(b) + case json.Number: + v, err := ToInt64E(b) + if err == nil { + return v != 0, nil + } + + return false, fmt.Errorf(errorMsg, i, i, false) + default: + if i, ok := resolveAlias(i); ok { + return ToBoolE(i) + } + + return false, fmt.Errorf(errorMsg, i, i, false) + } +} + +// ToStringE casts any value to a string type. +func ToStringE(i any) (string, error) { + switch s := i.(type) { + case string: + return s, nil + case bool: + return strconv.FormatBool(s), nil + case float64: + return strconv.FormatFloat(s, 'f', -1, 64), nil + case float32: + return strconv.FormatFloat(float64(s), 'f', -1, 32), nil + case int: + return strconv.Itoa(s), nil + case int8: + return strconv.FormatInt(int64(s), 10), nil + case int16: + return strconv.FormatInt(int64(s), 10), nil + case int32: + return strconv.FormatInt(int64(s), 10), nil + case int64: + return strconv.FormatInt(s, 10), nil + case uint: + return strconv.FormatUint(uint64(s), 10), nil + case uint8: + return strconv.FormatUint(uint64(s), 10), nil + case uint16: + return strconv.FormatUint(uint64(s), 10), nil + case uint32: + return strconv.FormatUint(uint64(s), 10), nil + case uint64: + return strconv.FormatUint(s, 10), nil + case json.Number: + return s.String(), nil + case []byte: + return string(s), nil + case template.HTML: + return string(s), nil + case template.URL: + return string(s), nil + case template.JS: + return string(s), nil + case template.CSS: + return string(s), nil + case template.HTMLAttr: + return string(s), nil + case nil: + return "", nil + case fmt.Stringer: + return s.String(), nil + case error: + return s.Error(), nil + default: + if i, ok := indirect(i); ok { + return ToStringE(i) + } + + if i, ok := resolveAlias(i); ok { + return ToStringE(i) + } + + return "", fmt.Errorf(errorMsg, i, i, "") + } +} diff --git a/vendor/github.com/spf13/cast/cast.go b/vendor/github.com/spf13/cast/cast.go new file mode 100644 index 0000000000..8d85539b35 --- /dev/null +++ b/vendor/github.com/spf13/cast/cast.go @@ -0,0 +1,84 @@ +// Copyright © 2014 Steve Francia . +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file. + +// Package cast provides easy and safe casting in Go. +package cast + +import "time" + +const errorMsg = "unable to cast %#v of type %T to %T" +const errorMsgWith = "unable to cast %#v of type %T to %T: %w" + +// Basic is a type parameter constraint for functions accepting basic types. +// +// It represents the supported basic types this package can cast to. +type Basic interface { + string | bool | Number | time.Time | time.Duration +} + +// ToE casts any value to a [Basic] type. +func ToE[T Basic](i any) (T, error) { + var t T + + var v any + var err error + + switch any(t).(type) { + case string: + v, err = ToStringE(i) + case bool: + v, err = ToBoolE(i) + case int: + v, err = toNumberE[int](i, parseInt[int]) + case int8: + v, err = toNumberE[int8](i, parseInt[int8]) + case int16: + v, err = toNumberE[int16](i, parseInt[int16]) + case int32: + v, err = toNumberE[int32](i, parseInt[int32]) + case int64: + v, err = toNumberE[int64](i, parseInt[int64]) + case uint: + v, err = toUnsignedNumberE[uint](i, parseUint[uint]) + case uint8: + v, err = toUnsignedNumberE[uint8](i, parseUint[uint8]) + case uint16: + v, err = toUnsignedNumberE[uint16](i, parseUint[uint16]) + case uint32: + v, err = toUnsignedNumberE[uint32](i, parseUint[uint32]) + case uint64: + v, err = toUnsignedNumberE[uint64](i, parseUint[uint64]) + case float32: + v, err = toNumberE[float32](i, parseFloat[float32]) + case float64: + v, err = toNumberE[float64](i, parseFloat[float64]) + case time.Time: + v, err = ToTimeE(i) + case time.Duration: + v, err = ToDurationE(i) + } + + if err != nil { + return t, err + } + + return v.(T), nil +} + +// Must is a helper that wraps a call to a cast function and panics if the error is non-nil. +func Must[T any](i any, err error) T { + if err != nil { + panic(err) + } + + return i.(T) +} + +// To casts any value to a [Basic] type. +func To[T Basic](i any) T { + v, _ := ToE[T](i) + + return v +} diff --git a/vendor/github.com/spf13/cast/indirect.go b/vendor/github.com/spf13/cast/indirect.go new file mode 100644 index 0000000000..093345f737 --- /dev/null +++ b/vendor/github.com/spf13/cast/indirect.go @@ -0,0 +1,37 @@ +// Copyright © 2014 Steve Francia . +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file. + +package cast + +import ( + "reflect" +) + +// From html/template/content.go +// Copyright 2011 The Go Authors. All rights reserved. +// indirect returns the value, after dereferencing as many times +// as necessary to reach the base type (or nil). +func indirect(i any) (any, bool) { + if i == nil { + return nil, false + } + + if t := reflect.TypeOf(i); t.Kind() != reflect.Ptr { + // Avoid creating a reflect.Value if it's not a pointer. + return i, false + } + + v := reflect.ValueOf(i) + + for v.Kind() == reflect.Ptr || (v.Kind() == reflect.Interface && v.Elem().Kind() == reflect.Ptr) { + if v.IsNil() { + return nil, true + } + + v = v.Elem() + } + + return v.Interface(), true +} diff --git a/vendor/github.com/spf13/cast/internal/time.go b/vendor/github.com/spf13/cast/internal/time.go new file mode 100644 index 0000000000..906e9aece3 --- /dev/null +++ b/vendor/github.com/spf13/cast/internal/time.go @@ -0,0 +1,79 @@ +package internal + +import ( + "fmt" + "time" +) + +//go:generate stringer -type=TimeFormatType + +type TimeFormatType int + +const ( + TimeFormatNoTimezone TimeFormatType = iota + TimeFormatNamedTimezone + TimeFormatNumericTimezone + TimeFormatNumericAndNamedTimezone + TimeFormatTimeOnly +) + +type TimeFormat struct { + Format string + Typ TimeFormatType +} + +func (f TimeFormat) HasTimezone() bool { + // We don't include the formats with only named timezones, see + // https://github.com/golang/go/issues/19694#issuecomment-289103522 + return f.Typ >= TimeFormatNumericTimezone && f.Typ <= TimeFormatNumericAndNamedTimezone +} + +var TimeFormats = []TimeFormat{ + // Keep common formats at the top. + {"2006-01-02", TimeFormatNoTimezone}, + {time.RFC3339, TimeFormatNumericTimezone}, + {"2006-01-02T15:04:05", TimeFormatNoTimezone}, // iso8601 without timezone + {time.RFC1123Z, TimeFormatNumericTimezone}, + {time.RFC1123, TimeFormatNamedTimezone}, + {time.RFC822Z, TimeFormatNumericTimezone}, + {time.RFC822, TimeFormatNamedTimezone}, + {time.RFC850, TimeFormatNamedTimezone}, + {"2006-01-02 15:04:05.999999999 -0700 MST", TimeFormatNumericAndNamedTimezone}, // Time.String() + {"2006-01-02T15:04:05-0700", TimeFormatNumericTimezone}, // RFC3339 without timezone hh:mm colon + {"2006-01-02 15:04:05Z0700", TimeFormatNumericTimezone}, // RFC3339 without T or timezone hh:mm colon + {"2006-01-02 15:04:05", TimeFormatNoTimezone}, + {time.ANSIC, TimeFormatNoTimezone}, + {time.UnixDate, TimeFormatNamedTimezone}, + {time.RubyDate, TimeFormatNumericTimezone}, + {"2006-01-02 15:04:05Z07:00", TimeFormatNumericTimezone}, + {"02 Jan 2006", TimeFormatNoTimezone}, + {"2006-01-02 15:04:05 -07:00", TimeFormatNumericTimezone}, + {"2006-01-02 15:04:05 -0700", TimeFormatNumericTimezone}, + {time.Kitchen, TimeFormatTimeOnly}, + {time.Stamp, TimeFormatTimeOnly}, + {time.StampMilli, TimeFormatTimeOnly}, + {time.StampMicro, TimeFormatTimeOnly}, + {time.StampNano, TimeFormatTimeOnly}, +} + +func ParseDateWith(s string, location *time.Location, formats []TimeFormat) (d time.Time, e error) { + for _, format := range formats { + if d, e = time.Parse(format.Format, s); e == nil { + + // Some time formats have a zone name, but no offset, so it gets + // put in that zone name (not the default one passed in to us), but + // without that zone's offset. So set the location manually. + if format.Typ <= TimeFormatNamedTimezone { + if location == nil { + location = time.Local + } + year, month, day := d.Date() + hour, min, sec := d.Clock() + d = time.Date(year, month, day, hour, min, sec, d.Nanosecond(), location) + } + + return + } + } + return d, fmt.Errorf("unable to parse date: %s", s) +} diff --git a/vendor/github.com/spf13/cast/internal/timeformattype_string.go b/vendor/github.com/spf13/cast/internal/timeformattype_string.go new file mode 100644 index 0000000000..60a29a862b --- /dev/null +++ b/vendor/github.com/spf13/cast/internal/timeformattype_string.go @@ -0,0 +1,27 @@ +// Code generated by "stringer -type=TimeFormatType"; DO NOT EDIT. + +package internal + +import "strconv" + +func _() { + // An "invalid array index" compiler error signifies that the constant values have changed. + // Re-run the stringer command to generate them again. + var x [1]struct{} + _ = x[TimeFormatNoTimezone-0] + _ = x[TimeFormatNamedTimezone-1] + _ = x[TimeFormatNumericTimezone-2] + _ = x[TimeFormatNumericAndNamedTimezone-3] + _ = x[TimeFormatTimeOnly-4] +} + +const _TimeFormatType_name = "TimeFormatNoTimezoneTimeFormatNamedTimezoneTimeFormatNumericTimezoneTimeFormatNumericAndNamedTimezoneTimeFormatTimeOnly" + +var _TimeFormatType_index = [...]uint8{0, 20, 43, 68, 101, 119} + +func (i TimeFormatType) String() string { + if i < 0 || i >= TimeFormatType(len(_TimeFormatType_index)-1) { + return "TimeFormatType(" + strconv.FormatInt(int64(i), 10) + ")" + } + return _TimeFormatType_name[_TimeFormatType_index[i]:_TimeFormatType_index[i+1]] +} diff --git a/vendor/github.com/spf13/cast/map.go b/vendor/github.com/spf13/cast/map.go new file mode 100644 index 0000000000..858d4ee43b --- /dev/null +++ b/vendor/github.com/spf13/cast/map.go @@ -0,0 +1,212 @@ +// Copyright © 2014 Steve Francia . +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file. + +package cast + +import ( + "encoding/json" + "fmt" + "reflect" +) + +func toMapE[K comparable, V any](i any, keyFn func(any) K, valFn func(any) V) (map[K]V, error) { + m := map[K]V{} + + if i == nil { + return m, fmt.Errorf(errorMsg, i, i, m) + } + + switch v := i.(type) { + case map[K]V: + return v, nil + + case map[K]any: + for k, val := range v { + m[k] = valFn(val) + } + + return m, nil + + case map[any]V: + for k, val := range v { + m[keyFn(k)] = val + } + + return m, nil + + case map[any]any: + for k, val := range v { + m[keyFn(k)] = valFn(val) + } + + return m, nil + + case string: + err := jsonStringToObject(v, &m) + return m, err + + default: + return m, fmt.Errorf(errorMsg, i, i, m) + } +} + +func toStringMapE[T any](i any, fn func(any) T) (map[string]T, error) { + return toMapE(i, ToString, fn) +} + +// ToStringMapStringE casts any value to a map[string]string type. +func ToStringMapStringE(i any) (map[string]string, error) { + return toStringMapE(i, ToString) +} + +// ToStringMapStringSliceE casts any value to a map[string][]string type. +func ToStringMapStringSliceE(i any) (map[string][]string, error) { + m := map[string][]string{} + + switch v := i.(type) { + case map[string][]string: + return v, nil + case map[string][]any: + for k, val := range v { + m[ToString(k)] = ToStringSlice(val) + } + return m, nil + case map[string]string: + for k, val := range v { + m[ToString(k)] = []string{val} + } + case map[string]any: + for k, val := range v { + switch vt := val.(type) { + case []any: + m[ToString(k)] = ToStringSlice(vt) + case []string: + m[ToString(k)] = vt + default: + m[ToString(k)] = []string{ToString(val)} + } + } + return m, nil + case map[any][]string: + for k, val := range v { + m[ToString(k)] = ToStringSlice(val) + } + return m, nil + case map[any]string: + for k, val := range v { + m[ToString(k)] = ToStringSlice(val) + } + return m, nil + case map[any][]any: + for k, val := range v { + m[ToString(k)] = ToStringSlice(val) + } + return m, nil + case map[any]any: + for k, val := range v { + key, err := ToStringE(k) + if err != nil { + return m, fmt.Errorf(errorMsg, i, i, m) + } + value, err := ToStringSliceE(val) + if err != nil { + return m, fmt.Errorf(errorMsg, i, i, m) + } + m[key] = value + } + case string: + err := jsonStringToObject(v, &m) + return m, err + default: + return m, fmt.Errorf(errorMsg, i, i, m) + } + + return m, nil +} + +// ToStringMapBoolE casts any value to a map[string]bool type. +func ToStringMapBoolE(i any) (map[string]bool, error) { + return toStringMapE(i, ToBool) +} + +// ToStringMapE casts any value to a map[string]any type. +func ToStringMapE(i any) (map[string]any, error) { + fn := func(i any) any { return i } + + return toStringMapE(i, fn) +} + +func toStringMapIntE[T int | int64](i any, fn func(any) T, fnE func(any) (T, error)) (map[string]T, error) { + m := map[string]T{} + + if i == nil { + return nil, fmt.Errorf(errorMsg, i, i, m) + } + + switch v := i.(type) { + case map[string]T: + return v, nil + + case map[string]any: + for k, val := range v { + m[k] = fn(val) + } + + return m, nil + + case map[any]T: + for k, val := range v { + m[ToString(k)] = val + } + + return m, nil + + case map[any]any: + for k, val := range v { + m[ToString(k)] = fn(val) + } + + return m, nil + + case string: + err := jsonStringToObject(v, &m) + return m, err + } + + if reflect.TypeOf(i).Kind() != reflect.Map { + return m, fmt.Errorf(errorMsg, i, i, m) + } + + mVal := reflect.ValueOf(m) + v := reflect.ValueOf(i) + + for _, keyVal := range v.MapKeys() { + val, err := fnE(v.MapIndex(keyVal).Interface()) + if err != nil { + return m, fmt.Errorf(errorMsg, i, i, m) + } + + mVal.SetMapIndex(keyVal, reflect.ValueOf(val)) + } + + return m, nil +} + +// ToStringMapIntE casts any value to a map[string]int type. +func ToStringMapIntE(i any) (map[string]int, error) { + return toStringMapIntE(i, ToInt, ToIntE) +} + +// ToStringMapInt64E casts any value to a map[string]int64 type. +func ToStringMapInt64E(i any) (map[string]int64, error) { + return toStringMapIntE(i, ToInt64, ToInt64E) +} + +// jsonStringToObject attempts to unmarshall a string as JSON into +// the object passed as pointer. +func jsonStringToObject(s string, v any) error { + data := []byte(s) + return json.Unmarshal(data, v) +} diff --git a/vendor/github.com/spf13/cast/number.go b/vendor/github.com/spf13/cast/number.go new file mode 100644 index 0000000000..a58dc4d1ed --- /dev/null +++ b/vendor/github.com/spf13/cast/number.go @@ -0,0 +1,549 @@ +// Copyright © 2014 Steve Francia . +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file. + +package cast + +import ( + "encoding/json" + "errors" + "fmt" + "regexp" + "strconv" + "strings" + "time" +) + +var errNegativeNotAllowed = errors.New("unable to cast negative value") + +type float64EProvider interface { + Float64() (float64, error) +} + +type float64Provider interface { + Float64() float64 +} + +// Number is a type parameter constraint for functions accepting number types. +// +// It represents the supported number types this package can cast to. +type Number interface { + int | int8 | int16 | int32 | int64 | uint | uint8 | uint16 | uint32 | uint64 | float32 | float64 +} + +type integer interface { + int | int8 | int16 | int32 | int64 +} + +type unsigned interface { + uint | uint8 | uint16 | uint32 | uint64 +} + +type float interface { + float32 | float64 +} + +// ToNumberE casts any value to a [Number] type. +func ToNumberE[T Number](i any) (T, error) { + var t T + + switch any(t).(type) { + case int: + return toNumberE[T](i, parseNumber[T]) + case int8: + return toNumberE[T](i, parseNumber[T]) + case int16: + return toNumberE[T](i, parseNumber[T]) + case int32: + return toNumberE[T](i, parseNumber[T]) + case int64: + return toNumberE[T](i, parseNumber[T]) + case uint: + return toUnsignedNumberE[T](i, parseNumber[T]) + case uint8: + return toUnsignedNumberE[T](i, parseNumber[T]) + case uint16: + return toUnsignedNumberE[T](i, parseNumber[T]) + case uint32: + return toUnsignedNumberE[T](i, parseNumber[T]) + case uint64: + return toUnsignedNumberE[T](i, parseNumber[T]) + case float32: + return toNumberE[T](i, parseNumber[T]) + case float64: + return toNumberE[T](i, parseNumber[T]) + default: + return 0, fmt.Errorf("unknown number type: %T", t) + } +} + +// ToNumber casts any value to a [Number] type. +func ToNumber[T Number](i any) T { + v, _ := ToNumberE[T](i) + + return v +} + +// toNumber's semantics differ from other "to" functions. +// It returns false as the second parameter if the conversion fails. +// This is to signal other callers that they should proceed with their own conversions. +func toNumber[T Number](i any) (T, bool) { + i, _ = indirect(i) + + switch s := i.(type) { + case T: + return s, true + case int: + return T(s), true + case int8: + return T(s), true + case int16: + return T(s), true + case int32: + return T(s), true + case int64: + return T(s), true + case uint: + return T(s), true + case uint8: + return T(s), true + case uint16: + return T(s), true + case uint32: + return T(s), true + case uint64: + return T(s), true + case float32: + return T(s), true + case float64: + return T(s), true + case bool: + if s { + return 1, true + } + + return 0, true + case nil: + return 0, true + case time.Weekday: + return T(s), true + case time.Month: + return T(s), true + } + + return 0, false +} + +func toNumberE[T Number](i any, parseFn func(string) (T, error)) (T, error) { + n, ok := toNumber[T](i) + if ok { + return n, nil + } + + i, _ = indirect(i) + + switch s := i.(type) { + case string: + if s == "" { + return 0, nil + } + + v, err := parseFn(s) + if err != nil { + return 0, fmt.Errorf(errorMsgWith, i, i, n, err) + } + + return v, nil + case json.Number: + if s == "" { + return 0, nil + } + + v, err := parseFn(string(s)) + if err != nil { + return 0, fmt.Errorf(errorMsgWith, i, i, n, err) + } + + return v, nil + case float64EProvider: + if _, ok := any(n).(float64); !ok { + return 0, fmt.Errorf(errorMsg, i, i, n) + } + + v, err := s.Float64() + if err != nil { + return 0, fmt.Errorf(errorMsg, i, i, n) + } + + return T(v), nil + case float64Provider: + if _, ok := any(n).(float64); !ok { + return 0, fmt.Errorf(errorMsg, i, i, n) + } + + return T(s.Float64()), nil + default: + if i, ok := resolveAlias(i); ok { + return toNumberE(i, parseFn) + } + + return 0, fmt.Errorf(errorMsg, i, i, n) + } +} + +func toUnsignedNumber[T Number](i any) (T, bool, bool) { + i, _ = indirect(i) + + switch s := i.(type) { + case T: + return s, true, true + case int: + if s < 0 { + return 0, false, false + } + + return T(s), true, true + case int8: + if s < 0 { + return 0, false, false + } + + return T(s), true, true + case int16: + if s < 0 { + return 0, false, false + } + + return T(s), true, true + case int32: + if s < 0 { + return 0, false, false + } + + return T(s), true, true + case int64: + if s < 0 { + return 0, false, false + } + + return T(s), true, true + case uint: + return T(s), true, true + case uint8: + return T(s), true, true + case uint16: + return T(s), true, true + case uint32: + return T(s), true, true + case uint64: + return T(s), true, true + case float32: + if s < 0 { + return 0, false, false + } + + return T(s), true, true + case float64: + if s < 0 { + return 0, false, false + } + + return T(s), true, true + case bool: + if s { + return 1, true, true + } + + return 0, true, true + case nil: + return 0, true, true + case time.Weekday: + if s < 0 { + return 0, false, false + } + + return T(s), true, true + case time.Month: + if s < 0 { + return 0, false, false + } + + return T(s), true, true + } + + return 0, true, false +} + +func toUnsignedNumberE[T Number](i any, parseFn func(string) (T, error)) (T, error) { + n, valid, ok := toUnsignedNumber[T](i) + if ok { + return n, nil + } + + i, _ = indirect(i) + + if !valid { + return 0, errNegativeNotAllowed + } + + switch s := i.(type) { + case string: + if s == "" { + return 0, nil + } + + v, err := parseFn(s) + if err != nil { + return 0, fmt.Errorf(errorMsgWith, i, i, n, err) + } + + return v, nil + case json.Number: + if s == "" { + return 0, nil + } + + v, err := parseFn(string(s)) + if err != nil { + return 0, fmt.Errorf(errorMsgWith, i, i, n, err) + } + + return v, nil + case float64EProvider: + if _, ok := any(n).(float64); !ok { + return 0, fmt.Errorf(errorMsg, i, i, n) + } + + v, err := s.Float64() + if err != nil { + return 0, fmt.Errorf(errorMsg, i, i, n) + } + + if v < 0 { + return 0, errNegativeNotAllowed + } + + return T(v), nil + case float64Provider: + if _, ok := any(n).(float64); !ok { + return 0, fmt.Errorf(errorMsg, i, i, n) + } + + v := s.Float64() + + if v < 0 { + return 0, errNegativeNotAllowed + } + + return T(v), nil + default: + if i, ok := resolveAlias(i); ok { + return toUnsignedNumberE(i, parseFn) + } + + return 0, fmt.Errorf(errorMsg, i, i, n) + } +} + +func parseNumber[T Number](s string) (T, error) { + var t T + + switch any(t).(type) { + case int: + v, err := parseInt[int](s) + + return T(v), err + case int8: + v, err := parseInt[int8](s) + + return T(v), err + case int16: + v, err := parseInt[int16](s) + + return T(v), err + case int32: + v, err := parseInt[int32](s) + + return T(v), err + case int64: + v, err := parseInt[int64](s) + + return T(v), err + case uint: + v, err := parseUint[uint](s) + + return T(v), err + case uint8: + v, err := parseUint[uint8](s) + + return T(v), err + case uint16: + v, err := parseUint[uint16](s) + + return T(v), err + case uint32: + v, err := parseUint[uint32](s) + + return T(v), err + case uint64: + v, err := parseUint[uint64](s) + + return T(v), err + case float32: + v, err := strconv.ParseFloat(s, 32) + + return T(v), err + case float64: + v, err := strconv.ParseFloat(s, 64) + + return T(v), err + + default: + return 0, fmt.Errorf("unknown number type: %T", t) + } +} + +func parseInt[T integer](s string) (T, error) { + v, err := strconv.ParseInt(trimDecimal(s), 0, 0) + if err != nil { + return 0, err + } + + return T(v), nil +} + +func parseUint[T unsigned](s string) (T, error) { + v, err := strconv.ParseUint(strings.TrimLeft(trimDecimal(s), "+"), 0, 0) + if err != nil { + return 0, err + } + + return T(v), nil +} + +func parseFloat[T float](s string) (T, error) { + var t T + + var v any + var err error + + switch any(t).(type) { + case float32: + n, e := strconv.ParseFloat(s, 32) + + v = float32(n) + err = e + case float64: + n, e := strconv.ParseFloat(s, 64) + + v = float64(n) + err = e + } + + return v.(T), err +} + +// ToFloat64E casts an interface to a float64 type. +func ToFloat64E(i any) (float64, error) { + return toNumberE[float64](i, parseFloat[float64]) +} + +// ToFloat32E casts an interface to a float32 type. +func ToFloat32E(i any) (float32, error) { + return toNumberE[float32](i, parseFloat[float32]) +} + +// ToInt64E casts an interface to an int64 type. +func ToInt64E(i any) (int64, error) { + return toNumberE[int64](i, parseInt[int64]) +} + +// ToInt32E casts an interface to an int32 type. +func ToInt32E(i any) (int32, error) { + return toNumberE[int32](i, parseInt[int32]) +} + +// ToInt16E casts an interface to an int16 type. +func ToInt16E(i any) (int16, error) { + return toNumberE[int16](i, parseInt[int16]) +} + +// ToInt8E casts an interface to an int8 type. +func ToInt8E(i any) (int8, error) { + return toNumberE[int8](i, parseInt[int8]) +} + +// ToIntE casts an interface to an int type. +func ToIntE(i any) (int, error) { + return toNumberE[int](i, parseInt[int]) +} + +// ToUintE casts an interface to a uint type. +func ToUintE(i any) (uint, error) { + return toUnsignedNumberE[uint](i, parseUint[uint]) +} + +// ToUint64E casts an interface to a uint64 type. +func ToUint64E(i any) (uint64, error) { + return toUnsignedNumberE[uint64](i, parseUint[uint64]) +} + +// ToUint32E casts an interface to a uint32 type. +func ToUint32E(i any) (uint32, error) { + return toUnsignedNumberE[uint32](i, parseUint[uint32]) +} + +// ToUint16E casts an interface to a uint16 type. +func ToUint16E(i any) (uint16, error) { + return toUnsignedNumberE[uint16](i, parseUint[uint16]) +} + +// ToUint8E casts an interface to a uint type. +func ToUint8E(i any) (uint8, error) { + return toUnsignedNumberE[uint8](i, parseUint[uint8]) +} + +func trimZeroDecimal(s string) string { + var foundZero bool + for i := len(s); i > 0; i-- { + switch s[i-1] { + case '.': + if foundZero { + return s[:i-1] + } + case '0': + foundZero = true + default: + return s + } + } + return s +} + +var stringNumberRe = regexp.MustCompile(`^([-+]?\d*)(\.\d*)?$`) + +// see [BenchmarkDecimal] for details about the implementation +func trimDecimal(s string) string { + if !strings.Contains(s, ".") { + return s + } + + matches := stringNumberRe.FindStringSubmatch(s) + if matches != nil { + // matches[1] is the captured integer part with sign + s = matches[1] + + // handle special cases + switch s { + case "-", "+": + s += "0" + case "": + s = "0" + } + + return s + } + + return s +} diff --git a/vendor/github.com/spf13/cast/slice.go b/vendor/github.com/spf13/cast/slice.go new file mode 100644 index 0000000000..e6a8328c60 --- /dev/null +++ b/vendor/github.com/spf13/cast/slice.go @@ -0,0 +1,106 @@ +// Copyright © 2014 Steve Francia . +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file. + +package cast + +import ( + "fmt" + "reflect" + "strings" +) + +// ToSliceE casts any value to a []any type. +func ToSliceE(i any) ([]any, error) { + i, _ = indirect(i) + + var s []any + + switch v := i.(type) { + case []any: + // TODO: use slices.Clone + return append(s, v...), nil + case []map[string]any: + for _, u := range v { + s = append(s, u) + } + + return s, nil + default: + return s, fmt.Errorf(errorMsg, i, i, s) + } +} + +func toSliceE[T Basic](i any) ([]T, error) { + v, ok, err := toSliceEOk[T](i) + if err != nil { + return nil, err + } + + if !ok { + return nil, fmt.Errorf(errorMsg, i, i, []T{}) + } + + return v, nil +} + +func toSliceEOk[T Basic](i any) ([]T, bool, error) { + i, _ = indirect(i) + if i == nil { + return nil, true, fmt.Errorf(errorMsg, i, i, []T{}) + } + + switch v := i.(type) { + case []T: + // TODO: clone slice + return v, true, nil + } + + kind := reflect.TypeOf(i).Kind() + switch kind { + case reflect.Slice, reflect.Array: + s := reflect.ValueOf(i) + a := make([]T, s.Len()) + + for j := 0; j < s.Len(); j++ { + val, err := ToE[T](s.Index(j).Interface()) + if err != nil { + return nil, true, fmt.Errorf(errorMsg, i, i, []T{}) + } + + a[j] = val + } + + return a, true, nil + default: + return nil, false, nil + } +} + +// ToStringSliceE casts any value to a []string type. +func ToStringSliceE(i any) ([]string, error) { + if a, ok, err := toSliceEOk[string](i); ok { + if err != nil { + return nil, err + } + + return a, nil + } + + var a []string + + switch v := i.(type) { + case string: + return strings.Fields(v), nil + case any: + str, err := ToStringE(v) + if err != nil { + return nil, fmt.Errorf(errorMsg, i, i, a) + } + + return []string{str}, nil + default: + return nil, fmt.Errorf(errorMsg, i, i, a) + } +} diff --git a/vendor/github.com/spf13/cast/time.go b/vendor/github.com/spf13/cast/time.go new file mode 100644 index 0000000000..744cd5accd --- /dev/null +++ b/vendor/github.com/spf13/cast/time.go @@ -0,0 +1,116 @@ +// Copyright © 2014 Steve Francia . +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file. + +package cast + +import ( + "encoding/json" + "errors" + "fmt" + "strings" + "time" + + "github.com/spf13/cast/internal" +) + +// ToTimeE any value to a [time.Time] type. +func ToTimeE(i any) (time.Time, error) { + return ToTimeInDefaultLocationE(i, time.UTC) +} + +// ToTimeInDefaultLocationE casts an empty interface to [time.Time], +// interpreting inputs without a timezone to be in the given location, +// or the local timezone if nil. +func ToTimeInDefaultLocationE(i any, location *time.Location) (tim time.Time, err error) { + i, _ = indirect(i) + + switch v := i.(type) { + case time.Time: + return v, nil + case string: + return StringToDateInDefaultLocation(v, location) + case json.Number: + // Originally this used ToInt64E, but adding string float conversion broke ToTime. + // the behavior of ToTime would have changed if we continued using it. + // For now, using json.Number's own Int64 method should be good enough to preserve backwards compatibility. + v = json.Number(trimZeroDecimal(string(v))) + s, err1 := v.Int64() + if err1 != nil { + return time.Time{}, fmt.Errorf(errorMsg, i, i, time.Time{}) + } + return time.Unix(s, 0), nil + case int: + return time.Unix(int64(v), 0), nil + case int32: + return time.Unix(int64(v), 0), nil + case int64: + return time.Unix(v, 0), nil + case uint: + return time.Unix(int64(v), 0), nil + case uint32: + return time.Unix(int64(v), 0), nil + case uint64: + return time.Unix(int64(v), 0), nil + case nil: + return time.Time{}, nil + default: + return time.Time{}, fmt.Errorf(errorMsg, i, i, time.Time{}) + } +} + +// ToDurationE casts any value to a [time.Duration] type. +func ToDurationE(i any) (time.Duration, error) { + i, _ = indirect(i) + + switch s := i.(type) { + case time.Duration: + return s, nil + case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64: + v, err := ToInt64E(s) + if err != nil { + // TODO: once there is better error handling, this should be easier + return 0, errors.New(strings.ReplaceAll(err.Error(), " int64", "time.Duration")) + } + + return time.Duration(v), nil + case float32, float64, float64EProvider, float64Provider: + v, err := ToFloat64E(s) + if err != nil { + // TODO: once there is better error handling, this should be easier + return 0, errors.New(strings.ReplaceAll(err.Error(), " float64", "time.Duration")) + } + + return time.Duration(v), nil + case string: + if !strings.ContainsAny(s, "nsuµmh") { + return time.ParseDuration(s + "ns") + } + + return time.ParseDuration(s) + case nil: + return time.Duration(0), nil + default: + if i, ok := resolveAlias(i); ok { + return ToDurationE(i) + } + + return 0, fmt.Errorf(errorMsg, i, i, time.Duration(0)) + } +} + +// StringToDate attempts to parse a string into a [time.Time] type using a +// predefined list of formats. +// +// If no suitable format is found, an error is returned. +func StringToDate(s string) (time.Time, error) { + return internal.ParseDateWith(s, time.UTC, internal.TimeFormats) +} + +// StringToDateInDefaultLocation casts an empty interface to a [time.Time], +// interpreting inputs without a timezone to be in the given location, +// or the local timezone if nil. +func StringToDateInDefaultLocation(s string, location *time.Location) (time.Time, error) { + return internal.ParseDateWith(s, location, internal.TimeFormats) +} diff --git a/vendor/github.com/spf13/cast/zz_generated.go b/vendor/github.com/spf13/cast/zz_generated.go new file mode 100644 index 0000000000..ce3ec0f78f --- /dev/null +++ b/vendor/github.com/spf13/cast/zz_generated.go @@ -0,0 +1,261 @@ +// Code generated by cast generator. DO NOT EDIT. + +package cast + +import "time" + +// ToBool casts any value to a(n) bool type. +func ToBool(i any) bool { + v, _ := ToBoolE(i) + return v +} + +// ToString casts any value to a(n) string type. +func ToString(i any) string { + v, _ := ToStringE(i) + return v +} + +// ToTime casts any value to a(n) time.Time type. +func ToTime(i any) time.Time { + v, _ := ToTimeE(i) + return v +} + +// ToTimeInDefaultLocation casts any value to a(n) time.Time type. +func ToTimeInDefaultLocation(i any, location *time.Location) time.Time { + v, _ := ToTimeInDefaultLocationE(i, location) + return v +} + +// ToDuration casts any value to a(n) time.Duration type. +func ToDuration(i any) time.Duration { + v, _ := ToDurationE(i) + return v +} + +// ToInt casts any value to a(n) int type. +func ToInt(i any) int { + v, _ := ToIntE(i) + return v +} + +// ToInt8 casts any value to a(n) int8 type. +func ToInt8(i any) int8 { + v, _ := ToInt8E(i) + return v +} + +// ToInt16 casts any value to a(n) int16 type. +func ToInt16(i any) int16 { + v, _ := ToInt16E(i) + return v +} + +// ToInt32 casts any value to a(n) int32 type. +func ToInt32(i any) int32 { + v, _ := ToInt32E(i) + return v +} + +// ToInt64 casts any value to a(n) int64 type. +func ToInt64(i any) int64 { + v, _ := ToInt64E(i) + return v +} + +// ToUint casts any value to a(n) uint type. +func ToUint(i any) uint { + v, _ := ToUintE(i) + return v +} + +// ToUint8 casts any value to a(n) uint8 type. +func ToUint8(i any) uint8 { + v, _ := ToUint8E(i) + return v +} + +// ToUint16 casts any value to a(n) uint16 type. +func ToUint16(i any) uint16 { + v, _ := ToUint16E(i) + return v +} + +// ToUint32 casts any value to a(n) uint32 type. +func ToUint32(i any) uint32 { + v, _ := ToUint32E(i) + return v +} + +// ToUint64 casts any value to a(n) uint64 type. +func ToUint64(i any) uint64 { + v, _ := ToUint64E(i) + return v +} + +// ToFloat32 casts any value to a(n) float32 type. +func ToFloat32(i any) float32 { + v, _ := ToFloat32E(i) + return v +} + +// ToFloat64 casts any value to a(n) float64 type. +func ToFloat64(i any) float64 { + v, _ := ToFloat64E(i) + return v +} + +// ToStringMapString casts any value to a(n) map[string]string type. +func ToStringMapString(i any) map[string]string { + v, _ := ToStringMapStringE(i) + return v +} + +// ToStringMapStringSlice casts any value to a(n) map[string][]string type. +func ToStringMapStringSlice(i any) map[string][]string { + v, _ := ToStringMapStringSliceE(i) + return v +} + +// ToStringMapBool casts any value to a(n) map[string]bool type. +func ToStringMapBool(i any) map[string]bool { + v, _ := ToStringMapBoolE(i) + return v +} + +// ToStringMapInt casts any value to a(n) map[string]int type. +func ToStringMapInt(i any) map[string]int { + v, _ := ToStringMapIntE(i) + return v +} + +// ToStringMapInt64 casts any value to a(n) map[string]int64 type. +func ToStringMapInt64(i any) map[string]int64 { + v, _ := ToStringMapInt64E(i) + return v +} + +// ToStringMap casts any value to a(n) map[string]any type. +func ToStringMap(i any) map[string]any { + v, _ := ToStringMapE(i) + return v +} + +// ToSlice casts any value to a(n) []any type. +func ToSlice(i any) []any { + v, _ := ToSliceE(i) + return v +} + +// ToBoolSlice casts any value to a(n) []bool type. +func ToBoolSlice(i any) []bool { + v, _ := ToBoolSliceE(i) + return v +} + +// ToStringSlice casts any value to a(n) []string type. +func ToStringSlice(i any) []string { + v, _ := ToStringSliceE(i) + return v +} + +// ToIntSlice casts any value to a(n) []int type. +func ToIntSlice(i any) []int { + v, _ := ToIntSliceE(i) + return v +} + +// ToInt64Slice casts any value to a(n) []int64 type. +func ToInt64Slice(i any) []int64 { + v, _ := ToInt64SliceE(i) + return v +} + +// ToUintSlice casts any value to a(n) []uint type. +func ToUintSlice(i any) []uint { + v, _ := ToUintSliceE(i) + return v +} + +// ToFloat64Slice casts any value to a(n) []float64 type. +func ToFloat64Slice(i any) []float64 { + v, _ := ToFloat64SliceE(i) + return v +} + +// ToDurationSlice casts any value to a(n) []time.Duration type. +func ToDurationSlice(i any) []time.Duration { + v, _ := ToDurationSliceE(i) + return v +} + +// ToBoolSliceE casts any value to a(n) []bool type. +func ToBoolSliceE(i any) ([]bool, error) { + return toSliceE[bool](i) +} + +// ToDurationSliceE casts any value to a(n) []time.Duration type. +func ToDurationSliceE(i any) ([]time.Duration, error) { + return toSliceE[time.Duration](i) +} + +// ToIntSliceE casts any value to a(n) []int type. +func ToIntSliceE(i any) ([]int, error) { + return toSliceE[int](i) +} + +// ToInt8SliceE casts any value to a(n) []int8 type. +func ToInt8SliceE(i any) ([]int8, error) { + return toSliceE[int8](i) +} + +// ToInt16SliceE casts any value to a(n) []int16 type. +func ToInt16SliceE(i any) ([]int16, error) { + return toSliceE[int16](i) +} + +// ToInt32SliceE casts any value to a(n) []int32 type. +func ToInt32SliceE(i any) ([]int32, error) { + return toSliceE[int32](i) +} + +// ToInt64SliceE casts any value to a(n) []int64 type. +func ToInt64SliceE(i any) ([]int64, error) { + return toSliceE[int64](i) +} + +// ToUintSliceE casts any value to a(n) []uint type. +func ToUintSliceE(i any) ([]uint, error) { + return toSliceE[uint](i) +} + +// ToUint8SliceE casts any value to a(n) []uint8 type. +func ToUint8SliceE(i any) ([]uint8, error) { + return toSliceE[uint8](i) +} + +// ToUint16SliceE casts any value to a(n) []uint16 type. +func ToUint16SliceE(i any) ([]uint16, error) { + return toSliceE[uint16](i) +} + +// ToUint32SliceE casts any value to a(n) []uint32 type. +func ToUint32SliceE(i any) ([]uint32, error) { + return toSliceE[uint32](i) +} + +// ToUint64SliceE casts any value to a(n) []uint64 type. +func ToUint64SliceE(i any) ([]uint64, error) { + return toSliceE[uint64](i) +} + +// ToFloat32SliceE casts any value to a(n) []float32 type. +func ToFloat32SliceE(i any) ([]float32, error) { + return toSliceE[float32](i) +} + +// ToFloat64SliceE casts any value to a(n) []float64 type. +func ToFloat64SliceE(i any) ([]float64, error) { + return toSliceE[float64](i) +} diff --git a/vendor/github.com/spf13/viper/.editorconfig b/vendor/github.com/spf13/viper/.editorconfig new file mode 100644 index 0000000000..faef0c91e7 --- /dev/null +++ b/vendor/github.com/spf13/viper/.editorconfig @@ -0,0 +1,21 @@ +root = true + +[*] +charset = utf-8 +end_of_line = lf +indent_size = 4 +indent_style = space +insert_final_newline = true +trim_trailing_whitespace = true + +[*.go] +indent_style = tab + +[{Makefile,*.mk}] +indent_style = tab + +[*.nix] +indent_size = 2 + +[.golangci.yaml] +indent_size = 2 diff --git a/vendor/github.com/spf13/viper/.gitignore b/vendor/github.com/spf13/viper/.gitignore new file mode 100644 index 0000000000..f1bbd42803 --- /dev/null +++ b/vendor/github.com/spf13/viper/.gitignore @@ -0,0 +1,8 @@ +/.devenv/ +/.direnv/ +/.idea/ +/.pre-commit-config.yaml +/bin/ +/build/ +/var/ +/vendor/ diff --git a/vendor/github.com/spf13/viper/.golangci.yaml b/vendor/github.com/spf13/viper/.golangci.yaml new file mode 100644 index 0000000000..bed0b83ecc --- /dev/null +++ b/vendor/github.com/spf13/viper/.golangci.yaml @@ -0,0 +1,118 @@ +version: "2" + +run: + timeout: 5m + +linters: + enable: + - bodyclose + - dogsled + - dupl + - durationcheck + - exhaustive + - gocritic + - godot + - gomoddirectives + - goprintffuncname + - govet + - importas + - ineffassign + - makezero + - misspell + - nakedret + - nilerr + - noctx + - nolintlint + - prealloc + - predeclared + - revive + - rowserrcheck + - sqlclosecheck + - staticcheck + - tparallel + - unconvert + - unparam + - unused + - wastedassign + - whitespace + + # fixme + # - cyclop + # - errcheck + # - errorlint + # - exhaustivestruct + # - forbidigo + # - forcetypeassert + # - gochecknoglobals + # - gochecknoinits + # - gocognit + # - goconst + # - gocyclo + # - gosec + # - gosimple + # - ifshort + # - lll + # - nlreturn + # - paralleltest + # - scopelint + # - thelper + # - wrapcheck + + # unused + # - depguard + # - goheader + # - gomodguard + + # don't enable: + # - asciicheck + # - funlen + # - godox + # - goerr113 + # - gomnd + # - interfacer + # - maligned + # - nestif + # - testpackage + # - wsl + + exclusions: + rules: + - linters: + - errcheck + - noctx + path: _test.go + presets: + - comments + - std-error-handling + + settings: + misspell: + locale: US + nolintlint: + allow-unused: false # report any unused nolint directives + require-specific: false # don't require nolint directives to be specific about which linter is being skipped + gocritic: + # Enable multiple checks by tags. See "Tags" section in https://github.com/go-critic/go-critic#usage. + enabled-tags: + - diagnostic + - experimental + - opinionated + - style + disabled-checks: + - importShadow + - unnamedResult + +formatters: + enable: + - gci + - gofmt + - gofumpt + - goimports + # - golines + + settings: + gci: + sections: + - standard + - default + - localmodule diff --git a/vendor/github.com/spf13/viper/.yamlignore b/vendor/github.com/spf13/viper/.yamlignore new file mode 100644 index 0000000000..c04c4dead1 --- /dev/null +++ b/vendor/github.com/spf13/viper/.yamlignore @@ -0,0 +1,2 @@ +# TODO: FIXME +/.github/ diff --git a/vendor/github.com/spf13/viper/.yamllint.yaml b/vendor/github.com/spf13/viper/.yamllint.yaml new file mode 100644 index 0000000000..bac19ce18b --- /dev/null +++ b/vendor/github.com/spf13/viper/.yamllint.yaml @@ -0,0 +1,6 @@ +ignore-from-file: [.gitignore, .yamlignore] + +extends: default + +rules: + line-length: disable diff --git a/vendor/github.com/spf13/viper/LICENSE b/vendor/github.com/spf13/viper/LICENSE new file mode 100644 index 0000000000..4527efb9c0 --- /dev/null +++ b/vendor/github.com/spf13/viper/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2014 Steve Francia + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/vendor/github.com/spf13/viper/Makefile b/vendor/github.com/spf13/viper/Makefile new file mode 100644 index 0000000000..a77b9c81c1 --- /dev/null +++ b/vendor/github.com/spf13/viper/Makefile @@ -0,0 +1,87 @@ +# A Self-Documenting Makefile: http://marmelab.com/blog/2016/02/29/auto-documented-makefile.html + +OS = $(shell uname | tr A-Z a-z) +export PATH := $(abspath bin/):${PATH} + +# Build variables +BUILD_DIR ?= build +export CGO_ENABLED ?= 0 +export GOOS = $(shell go env GOOS) +ifeq (${VERBOSE}, 1) +ifeq ($(filter -v,${GOARGS}),) + GOARGS += -v +endif +TEST_FORMAT = short-verbose +endif + +# Dependency versions +GOTESTSUM_VERSION = 1.9.0 +GOLANGCI_VERSION = 1.53.3 + +# Add the ability to override some variables +# Use with care +-include override.mk + +.PHONY: clear +clear: ## Clear the working area and the project + rm -rf bin/ + +.PHONY: check +check: test lint ## Run tests and linters + + +TEST_PKGS ?= ./... +.PHONY: test +test: TEST_FORMAT ?= short +test: SHELL = /bin/bash +test: export CGO_ENABLED=1 +test: bin/gotestsum ## Run tests + @mkdir -p ${BUILD_DIR} + bin/gotestsum --no-summary=skipped --junitfile ${BUILD_DIR}/coverage.xml --format ${TEST_FORMAT} -- -race -coverprofile=${BUILD_DIR}/coverage.txt -covermode=atomic $(filter-out -v,${GOARGS}) $(if ${TEST_PKGS},${TEST_PKGS},./...) + +.PHONY: lint +lint: lint-go lint-yaml +lint: ## Run linters + +.PHONY: lint-go +lint-go: + golangci-lint run $(if ${CI},--out-format github-actions,) + +.PHONY: lint-yaml +lint-yaml: + yamllint $(if ${CI},-f github,) --no-warnings . + +.PHONY: fmt +fmt: ## Format code + golangci-lint run --fix + +deps: bin/golangci-lint bin/gotestsum yamllint +deps: ## Install dependencies + +bin/gotestsum: + @mkdir -p bin + curl -L https://github.com/gotestyourself/gotestsum/releases/download/v${GOTESTSUM_VERSION}/gotestsum_${GOTESTSUM_VERSION}_${OS}_amd64.tar.gz | tar -zOxf - gotestsum > ./bin/gotestsum && chmod +x ./bin/gotestsum + +bin/golangci-lint: + @mkdir -p bin + curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | bash -s -- v${GOLANGCI_VERSION} + +.PHONY: yamllint +yamllint: + pip3 install --user yamllint + +# Add custom targets here +-include custom.mk + +.PHONY: list +list: ## List all make targets + @${MAKE} -pRrn : -f $(MAKEFILE_LIST) 2>/dev/null | awk -v RS= -F: '/^# File/,/^# Finished Make data base/ {if ($$1 !~ "^[#.]") {print $$1}}' | egrep -v -e '^[^[:alnum:]]' -e '^$@$$' | sort + +.PHONY: help +.DEFAULT_GOAL := help +help: + @grep -h -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' + +# Variable outputting/exporting rules +var-%: ; @echo $($*) +varexport-%: ; @echo $*=$($*) diff --git a/vendor/github.com/spf13/viper/README.md b/vendor/github.com/spf13/viper/README.md new file mode 100644 index 0000000000..7a4c0fc305 --- /dev/null +++ b/vendor/github.com/spf13/viper/README.md @@ -0,0 +1,931 @@ +> ## Viper v2 feedback +> Viper is heading towards v2 and we would love to hear what _**you**_ would like to see in it. Share your thoughts here: https://forms.gle/R6faU74qPRPAzchZ9 +> +> **Thank you!** + +![viper logo](https://github.com/user-attachments/assets/acae9193-2974-41f3-808d-2d433f5ada5e) + + +[![Mentioned in Awesome Go](https://awesome.re/mentioned-badge-flat.svg)](https://github.com/avelino/awesome-go#configuration) +[![run on repl.it](https://repl.it/badge/github/sagikazarmark/Viper-example)](https://repl.it/@sagikazarmark/Viper-example#main.go) + +[![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/spf13/viper/ci.yaml?branch=master&style=flat-square)](https://github.com/spf13/viper/actions?query=workflow%3ACI) +[![Join the chat at https://gitter.im/spf13/viper](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/spf13/viper?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) +[![Go Report Card](https://goreportcard.com/badge/github.com/spf13/viper?style=flat-square)](https://goreportcard.com/report/github.com/spf13/viper) +![Go Version](https://img.shields.io/badge/go%20version-%3E=1.23-61CFDD.svg?style=flat-square) +[![PkgGoDev](https://pkg.go.dev/badge/mod/github.com/spf13/viper)](https://pkg.go.dev/mod/github.com/spf13/viper) + +**Go configuration with fangs!** + +Many Go projects are built using Viper including: + +* [Hugo](http://gohugo.io) +* [EMC RexRay](http://rexray.readthedocs.org/en/stable/) +* [Imgur’s Incus](https://github.com/Imgur/incus) +* [Nanobox](https://github.com/nanobox-io/nanobox)/[Nanopack](https://github.com/nanopack) +* [Docker Notary](https://github.com/docker/Notary) +* [BloomApi](https://www.bloomapi.com/) +* [doctl](https://github.com/digitalocean/doctl) +* [Clairctl](https://github.com/jgsqware/clairctl) +* [Mercure](https://mercure.rocks) +* [Meshery](https://github.com/meshery/meshery) +* [Bearer](https://github.com/bearer/bearer) +* [Coder](https://github.com/coder/coder) +* [Vitess](https://vitess.io/) + + +## Install + +```shell +go get github.com/spf13/viper +``` + +**Note:** Viper uses [Go Modules](https://go.dev/wiki/Modules) to manage dependencies. + + +## What is Viper? + +Viper is a complete configuration solution for Go applications including [12-Factor apps](https://12factor.net/#the_twelve_factors). +It is designed to work within an application, and can handle all types of configuration needs +and formats. It supports: + +* setting defaults +* reading from JSON, TOML, YAML, HCL, envfile and Java properties config files +* live watching and re-reading of config files (optional) +* reading from environment variables +* reading from remote config systems (etcd or Consul), and watching changes +* reading from command line flags +* reading from buffer +* setting explicit values + +Viper can be thought of as a registry for all of your applications configuration needs. + + +## Why Viper? + +When building a modern application, you don’t want to worry about +configuration file formats; you want to focus on building awesome software. +Viper is here to help with that. + +Viper does the following for you: + +1. Find, load, and unmarshal a configuration file in JSON, TOML, YAML, HCL, INI, envfile or Java properties formats. +2. Provide a mechanism to set default values for your different configuration options. +3. Provide a mechanism to set override values for options specified through command line flags. +4. Provide an alias system to easily rename parameters without breaking existing code. +5. Make it easy to tell the difference between when a user has provided a command line or config file which is the same as the default. + +Viper uses the following precedence order. Each item takes precedence over the item below it: + + * explicit call to `Set` + * flag + * env + * config + * key/value store + * default + +**Important:** Viper configuration keys are case insensitive. +There are ongoing discussions about making that optional. + + +## Putting Values into Viper + +### Establishing Defaults + +A good configuration system will support default values. A default value is not +required for a key, but it’s useful in the event that a key hasn't been set via +config file, environment variable, remote configuration or flag. + +Examples: + +```go +viper.SetDefault("ContentDir", "content") +viper.SetDefault("LayoutDir", "layouts") +viper.SetDefault("Taxonomies", map[string]string{"tag": "tags", "category": "categories"}) +``` + +### Reading Config Files + +Viper requires minimal configuration so it knows where to look for config files. +Viper supports JSON, TOML, YAML, HCL, INI, envfile and Java Properties files. Viper can search multiple paths, but +currently a single Viper instance only supports a single configuration file. +Viper does not default to any configuration search paths leaving defaults decision +to an application. + +Here is an example of how to use Viper to search for and read a configuration file. +None of the specific paths are required, but at least one path should be provided +where a configuration file is expected. + +```go +viper.SetConfigName("config") // name of config file (without extension) +viper.SetConfigType("yaml") // REQUIRED if the config file does not have the extension in the name +viper.AddConfigPath("/etc/appname/") // path to look for the config file in +viper.AddConfigPath("$HOME/.appname") // call multiple times to add many search paths +viper.AddConfigPath(".") // optionally look for config in the working directory +err := viper.ReadInConfig() // Find and read the config file +if err != nil { // Handle errors reading the config file + panic(fmt.Errorf("fatal error config file: %w", err)) +} +``` + +You can handle the specific case where no config file is found like this: + +```go +if err := viper.ReadInConfig(); err != nil { + if _, ok := err.(viper.ConfigFileNotFoundError); ok { + // Config file not found; ignore error if desired + } else { + // Config file was found but another error was produced + } +} + +// Config file found and successfully parsed +``` + +*NOTE [since 1.6]:* You can also have a file without an extension and specify the format programmatically. For those configuration files that lie in the home of the user without any extension like `.bashrc` + +### Writing Config Files + +Reading from config files is useful, but at times you want to store all modifications made at run time. +For that, a bunch of commands are available, each with its own purpose: + +* WriteConfig - writes the current viper configuration to the predefined path, if exists. Errors if no predefined path. Will overwrite the current config file, if it exists. +* SafeWriteConfig - writes the current viper configuration to the predefined path. Errors if no predefined path. Will not overwrite the current config file, if it exists. +* WriteConfigAs - writes the current viper configuration to the given filepath. Will overwrite the given file, if it exists. +* SafeWriteConfigAs - writes the current viper configuration to the given filepath. Will not overwrite the given file, if it exists. + +As a rule of the thumb, everything marked with safe won't overwrite any file, but just create if not existent, whilst the default behavior is to create or truncate. + +A small examples section: + +```go +viper.WriteConfig() // writes current config to predefined path set by 'viper.AddConfigPath()' and 'viper.SetConfigName' +viper.SafeWriteConfig() +viper.WriteConfigAs("/path/to/my/.config") +viper.SafeWriteConfigAs("/path/to/my/.config") // will error since it has already been written +viper.SafeWriteConfigAs("/path/to/my/.other_config") +``` + +### Watching and re-reading config files + +Viper supports the ability to have your application live read a config file while running. + +Gone are the days of needing to restart a server to have a config take effect, +viper powered applications can read an update to a config file while running and +not miss a beat. + +Simply tell the viper instance to watchConfig. +Optionally you can provide a function for Viper to run each time a change occurs. + +**Make sure you add all of the configPaths prior to calling `WatchConfig()`** + +```go +viper.OnConfigChange(func(e fsnotify.Event) { + fmt.Println("Config file changed:", e.Name) +}) +viper.WatchConfig() +``` + +### Reading Config from io.Reader + +Viper predefines many configuration sources such as files, environment +variables, flags, and remote K/V store, but you are not bound to them. You can +also implement your own required configuration source and feed it to viper. + +```go +viper.SetConfigType("yaml") // or viper.SetConfigType("YAML") + +// any approach to require this configuration into your program. +var yamlExample = []byte(` +Hacker: true +name: steve +hobbies: +- skateboarding +- snowboarding +- go +clothing: + jacket: leather + trousers: denim +age: 35 +eyes : brown +beard: true +`) + +viper.ReadConfig(bytes.NewBuffer(yamlExample)) + +viper.Get("name") // this would be "steve" +``` + +### Setting Overrides + +These could be from a command line flag, or from your own application logic. + +```go +viper.Set("Verbose", true) +viper.Set("LogFile", LogFile) +viper.Set("host.port", 5899) // set subset +``` + +### Registering and Using Aliases + +Aliases permit a single value to be referenced by multiple keys + +```go +viper.RegisterAlias("loud", "Verbose") + +viper.Set("verbose", true) // same result as next line +viper.Set("loud", true) // same result as prior line + +viper.GetBool("loud") // true +viper.GetBool("verbose") // true +``` + +### Working with Environment Variables + +Viper has full support for environment variables. This enables 12 factor +applications out of the box. There are five methods that exist to aid working +with ENV: + + * `AutomaticEnv()` + * `BindEnv(string...) : error` + * `SetEnvPrefix(string)` + * `SetEnvKeyReplacer(string...) *strings.Replacer` + * `AllowEmptyEnv(bool)` + +_When working with ENV variables, it’s important to recognize that Viper +treats ENV variables as case sensitive._ + +Viper provides a mechanism to try to ensure that ENV variables are unique. By +using `SetEnvPrefix`, you can tell Viper to use a prefix while reading from +the environment variables. Both `BindEnv` and `AutomaticEnv` will use this +prefix. + +`BindEnv` takes one or more parameters. The first parameter is the key name, the +rest are the name of the environment variables to bind to this key. If more than +one are provided, they will take precedence in the specified order. The name of +the environment variable is case sensitive. If the ENV variable name is not provided, then +Viper will automatically assume that the ENV variable matches the following format: prefix + "_" + the key name in ALL CAPS. When you explicitly provide the ENV variable name (the second parameter), +it **does not** automatically add the prefix. For example if the second parameter is "id", +Viper will look for the ENV variable "ID". + +One important thing to recognize when working with ENV variables is that the +value will be read each time it is accessed. Viper does not fix the value when +the `BindEnv` is called. + +`AutomaticEnv` is a powerful helper especially when combined with +`SetEnvPrefix`. When called, Viper will check for an environment variable any +time a `viper.Get` request is made. It will apply the following rules. It will +check for an environment variable with a name matching the key uppercased and +prefixed with the `EnvPrefix` if set. + +`SetEnvKeyReplacer` allows you to use a `strings.Replacer` object to rewrite Env +keys to an extent. This is useful if you want to use `-` or something in your +`Get()` calls, but want your environmental variables to use `_` delimiters. An +example of using it can be found in `viper_test.go`. + +Alternatively, you can use `EnvKeyReplacer` with `NewWithOptions` factory function. +Unlike `SetEnvKeyReplacer`, it accepts a `StringReplacer` interface allowing you to write custom string replacing logic. + +By default empty environment variables are considered unset and will fall back to +the next configuration source. To treat empty environment variables as set, use +the `AllowEmptyEnv` method. + +#### Env example + +```go +SetEnvPrefix("spf") // will be uppercased automatically +BindEnv("id") + +os.Setenv("SPF_ID", "13") // typically done outside of the app + +id := Get("id") // 13 +``` + +### Working with Flags + +Viper has the ability to bind to flags. Specifically, Viper supports `Pflags` +as used in the [Cobra](https://github.com/spf13/cobra) library. + +Like `BindEnv`, the value is not set when the binding method is called, but when +it is accessed. This means you can bind as early as you want, even in an +`init()` function. + +For individual flags, the `BindPFlag()` method provides this functionality. + +Example: + +```go +serverCmd.Flags().Int("port", 1138, "Port to run Application server on") +viper.BindPFlag("port", serverCmd.Flags().Lookup("port")) +``` + +You can also bind an existing set of pflags (pflag.FlagSet): + +Example: + +```go +pflag.Int("flagname", 1234, "help message for flagname") + +pflag.Parse() +viper.BindPFlags(pflag.CommandLine) + +i := viper.GetInt("flagname") // retrieve values from viper instead of pflag +``` + +The use of [pflag](https://github.com/spf13/pflag/) in Viper does not preclude +the use of other packages that use the [flag](https://golang.org/pkg/flag/) +package from the standard library. The pflag package can handle the flags +defined for the flag package by importing these flags. This is accomplished +by a calling a convenience function provided by the pflag package called +AddGoFlagSet(). + +Example: + +```go +package main + +import ( + "flag" + "github.com/spf13/pflag" +) + +func main() { + + // using standard library "flag" package + flag.Int("flagname", 1234, "help message for flagname") + + pflag.CommandLine.AddGoFlagSet(flag.CommandLine) + pflag.Parse() + viper.BindPFlags(pflag.CommandLine) + + i := viper.GetInt("flagname") // retrieve value from viper + + // ... +} +``` + +#### Flag interfaces + +Viper provides two Go interfaces to bind other flag systems if you don’t use `Pflags`. + +`FlagValue` represents a single flag. This is a very simple example on how to implement this interface: + +```go +type myFlag struct {} +func (f myFlag) HasChanged() bool { return false } +func (f myFlag) Name() string { return "my-flag-name" } +func (f myFlag) ValueString() string { return "my-flag-value" } +func (f myFlag) ValueType() string { return "string" } +``` + +Once your flag implements this interface, you can simply tell Viper to bind it: + +```go +viper.BindFlagValue("my-flag-name", myFlag{}) +``` + +`FlagValueSet` represents a group of flags. This is a very simple example on how to implement this interface: + +```go +type myFlagSet struct { + flags []myFlag +} + +func (f myFlagSet) VisitAll(fn func(FlagValue)) { + for _, flag := range flags { + fn(flag) + } +} +``` + +Once your flag set implements this interface, you can simply tell Viper to bind it: + +```go +fSet := myFlagSet{ + flags: []myFlag{myFlag{}, myFlag{}}, +} +viper.BindFlagValues("my-flags", fSet) +``` + +### Remote Key/Value Store Support + +To enable remote support in Viper, do a blank import of the `viper/remote` +package: + +`import _ "github.com/spf13/viper/remote"` + +Viper will read a config string (as JSON, TOML, YAML, HCL or envfile) retrieved from a path +in a Key/Value store such as etcd or Consul. These values take precedence over +default values, but are overridden by configuration values retrieved from disk, +flags, or environment variables. + +Viper supports multiple hosts. To use, pass a list of endpoints separated by `;`. For example `http://127.0.0.1:4001;http://127.0.0.1:4002`. + +Viper uses [crypt](https://github.com/sagikazarmark/crypt) to retrieve +configuration from the K/V store, which means that you can store your +configuration values encrypted and have them automatically decrypted if you have +the correct gpg keyring. Encryption is optional. + +You can use remote configuration in conjunction with local configuration, or +independently of it. + +`crypt` has a command-line helper that you can use to put configurations in your +K/V store. `crypt` defaults to etcd on http://127.0.0.1:4001. + +```bash +$ go get github.com/sagikazarmark/crypt/bin/crypt +$ crypt set -plaintext /config/hugo.json /Users/hugo/settings/config.json +``` + +Confirm that your value was set: + +```bash +$ crypt get -plaintext /config/hugo.json +``` + +See the `crypt` documentation for examples of how to set encrypted values, or +how to use Consul. + +### Remote Key/Value Store Example - Unencrypted + +#### etcd +```go +viper.AddRemoteProvider("etcd", "http://127.0.0.1:4001","/config/hugo.json") +viper.SetConfigType("json") // because there is no file extension in a stream of bytes, supported extensions are "json", "toml", "yaml", "yml", "properties", "props", "prop", "env", "dotenv" +err := viper.ReadRemoteConfig() +``` + +#### etcd3 +```go +viper.AddRemoteProvider("etcd3", "http://127.0.0.1:4001","/config/hugo.json") +viper.SetConfigType("json") // because there is no file extension in a stream of bytes, supported extensions are "json", "toml", "yaml", "yml", "properties", "props", "prop", "env", "dotenv" +err := viper.ReadRemoteConfig() +``` + +#### Consul +You need to set a key to Consul key/value storage with JSON value containing your desired config. +For example, create a Consul key/value store key `MY_CONSUL_KEY` with value: + +```json +{ + "port": 8080, + "hostname": "myhostname.com" +} +``` + +```go +viper.AddRemoteProvider("consul", "localhost:8500", "MY_CONSUL_KEY") +viper.SetConfigType("json") // Need to explicitly set this to json +err := viper.ReadRemoteConfig() + +fmt.Println(viper.Get("port")) // 8080 +fmt.Println(viper.Get("hostname")) // myhostname.com +``` + +#### Firestore + +```go +viper.AddRemoteProvider("firestore", "google-cloud-project-id", "collection/document") +viper.SetConfigType("json") // Config's format: "json", "toml", "yaml", "yml" +err := viper.ReadRemoteConfig() +``` + +Of course, you're allowed to use `SecureRemoteProvider` also + + +#### NATS + +```go +viper.AddRemoteProvider("nats", "nats://127.0.0.1:4222", "myapp.config") +viper.SetConfigType("json") +err := viper.ReadRemoteConfig() +``` + +### Remote Key/Value Store Example - Encrypted + +```go +viper.AddSecureRemoteProvider("etcd","http://127.0.0.1:4001","/config/hugo.json","/etc/secrets/mykeyring.gpg") +viper.SetConfigType("json") // because there is no file extension in a stream of bytes, supported extensions are "json", "toml", "yaml", "yml", "properties", "props", "prop", "env", "dotenv" +err := viper.ReadRemoteConfig() +``` + +### Watching Changes in etcd - Unencrypted + +```go +// alternatively, you can create a new viper instance. +var runtime_viper = viper.New() + +runtime_viper.AddRemoteProvider("etcd", "http://127.0.0.1:4001", "/config/hugo.yml") +runtime_viper.SetConfigType("yaml") // because there is no file extension in a stream of bytes, supported extensions are "json", "toml", "yaml", "yml", "properties", "props", "prop", "env", "dotenv" + +// read from remote config the first time. +err := runtime_viper.ReadRemoteConfig() + +// unmarshal config +runtime_viper.Unmarshal(&runtime_conf) + +// open a goroutine to watch remote changes forever +go func(){ + for { + time.Sleep(time.Second * 5) // delay after each request + + // currently, only tested with etcd support + err := runtime_viper.WatchRemoteConfig() + if err != nil { + log.Errorf("unable to read remote config: %v", err) + continue + } + + // unmarshal new config into our runtime config struct. you can also use channel + // to implement a signal to notify the system of the changes + runtime_viper.Unmarshal(&runtime_conf) + } +}() +``` + +## Getting Values From Viper + +In Viper, there are a few ways to get a value depending on the value’s type. +The following functions and methods exist: + + * `Get(key string) : any` + * `GetBool(key string) : bool` + * `GetFloat64(key string) : float64` + * `GetInt(key string) : int` + * `GetIntSlice(key string) : []int` + * `GetString(key string) : string` + * `GetStringMap(key string) : map[string]any` + * `GetStringMapString(key string) : map[string]string` + * `GetStringSlice(key string) : []string` + * `GetTime(key string) : time.Time` + * `GetDuration(key string) : time.Duration` + * `IsSet(key string) : bool` + * `AllSettings() : map[string]any` + +One important thing to recognize is that each Get function will return a zero +value if it’s not found. To check if a given key exists, the `IsSet()` method +has been provided. + +The zero value will also be returned if the value is set, but fails to parse +as the requested type. + +Example: +```go +viper.GetString("logfile") // case-insensitive Setting & Getting +if viper.GetBool("verbose") { + fmt.Println("verbose enabled") +} +``` +### Accessing nested keys + +The accessor methods also accept formatted paths to deeply nested keys. For +example, if the following JSON file is loaded: + +```json +{ + "host": { + "address": "localhost", + "port": 5799 + }, + "datastore": { + "metric": { + "host": "127.0.0.1", + "port": 3099 + }, + "warehouse": { + "host": "198.0.0.1", + "port": 2112 + } + } +} + +``` + +Viper can access a nested field by passing a `.` delimited path of keys: + +```go +GetString("datastore.metric.host") // (returns "127.0.0.1") +``` + +This obeys the precedence rules established above; the search for the path +will cascade through the remaining configuration registries until found. + +For example, given this configuration file, both `datastore.metric.host` and +`datastore.metric.port` are already defined (and may be overridden). If in addition +`datastore.metric.protocol` was defined in the defaults, Viper would also find it. + +However, if `datastore.metric` was overridden (by a flag, an environment variable, +the `Set()` method, …) with an immediate value, then all sub-keys of +`datastore.metric` become undefined, they are “shadowed” by the higher-priority +configuration level. + +Viper can access array indices by using numbers in the path. For example: + +```jsonc +{ + "host": { + "address": "localhost", + "ports": [ + 5799, + 6029 + ] + }, + "datastore": { + "metric": { + "host": "127.0.0.1", + "port": 3099 + }, + "warehouse": { + "host": "198.0.0.1", + "port": 2112 + } + } +} + +GetInt("host.ports.1") // returns 6029 + +``` + +Lastly, if there exists a key that matches the delimited key path, its value +will be returned instead. E.g. + +```jsonc +{ + "datastore.metric.host": "0.0.0.0", + "host": { + "address": "localhost", + "port": 5799 + }, + "datastore": { + "metric": { + "host": "127.0.0.1", + "port": 3099 + }, + "warehouse": { + "host": "198.0.0.1", + "port": 2112 + } + } +} + +GetString("datastore.metric.host") // returns "0.0.0.0" +``` + +### Extracting a sub-tree + +When developing reusable modules, it's often useful to extract a subset of the configuration +and pass it to a module. This way the module can be instantiated more than once, with different configurations. + +For example, an application might use multiple different cache stores for different purposes: + +```yaml +cache: + cache1: + max-items: 100 + item-size: 64 + cache2: + max-items: 200 + item-size: 80 +``` + +We could pass the cache name to a module (eg. `NewCache("cache1")`), +but it would require weird concatenation for accessing config keys and would be less separated from the global config. + +So instead of doing that let's pass a Viper instance to the constructor that represents a subset of the configuration: + +```go +cache1Config := viper.Sub("cache.cache1") +if cache1Config == nil { // Sub returns nil if the key cannot be found + panic("cache configuration not found") +} + +cache1 := NewCache(cache1Config) +``` + +**Note:** Always check the return value of `Sub`. It returns `nil` if a key cannot be found. + +Internally, the `NewCache` function can address `max-items` and `item-size` keys directly: + +```go +func NewCache(v *Viper) *Cache { + return &Cache{ + MaxItems: v.GetInt("max-items"), + ItemSize: v.GetInt("item-size"), + } +} +``` + +The resulting code is easy to test, since it's decoupled from the main config structure, +and easier to reuse (for the same reason). + + +### Unmarshaling + +You also have the option of Unmarshaling all or a specific value to a struct, map, +etc. + +There are two methods to do this: + + * `Unmarshal(rawVal any) : error` + * `UnmarshalKey(key string, rawVal any) : error` + +Example: + +```go +type config struct { + Port int + Name string + PathMap string `mapstructure:"path_map"` +} + +var C config + +err := viper.Unmarshal(&C) +if err != nil { + t.Fatalf("unable to decode into struct, %v", err) +} +``` + +If you want to unmarshal configuration where the keys themselves contain dot (the default key delimiter), +you have to change the delimiter: + +```go +v := viper.NewWithOptions(viper.KeyDelimiter("::")) + +v.SetDefault("chart::values", map[string]any{ + "ingress": map[string]any{ + "annotations": map[string]any{ + "traefik.frontend.rule.type": "PathPrefix", + "traefik.ingress.kubernetes.io/ssl-redirect": "true", + }, + }, +}) + +type config struct { + Chart struct{ + Values map[string]any + } +} + +var C config + +v.Unmarshal(&C) +``` + +Viper also supports unmarshaling into embedded structs: + +```go +/* +Example config: + +module: + enabled: true + token: 89h3f98hbwf987h3f98wenf89ehf +*/ +type config struct { + Module struct { + Enabled bool + + moduleConfig `mapstructure:",squash"` + } +} + +// moduleConfig could be in a module specific package +type moduleConfig struct { + Token string +} + +var C config + +err := viper.Unmarshal(&C) +if err != nil { + t.Fatalf("unable to decode into struct, %v", err) +} +``` + +Viper uses [github.com/go-viper/mapstructure](https://github.com/go-viper/mapstructure) under the hood for unmarshaling values which uses `mapstructure` tags by default. + +### Decoding custom formats + +A frequently requested feature for Viper is adding more value formats and decoders. +For example, parsing character (dot, comma, semicolon, etc) separated strings into slices. + +This is already available in Viper using mapstructure decode hooks. + +Read more about the details in [this blog post](https://sagikazarmark.hu/blog/decoding-custom-formats-with-viper/). + +### Marshalling to string + +You may need to marshal all the settings held in viper into a string rather than write them to a file. +You can use your favorite format's marshaller with the config returned by `AllSettings()`. + +```go +import ( + yaml "go.yaml.in/yaml/v3" + // ... +) + +func yamlStringSettings() string { + c := viper.AllSettings() + bs, err := yaml.Marshal(c) + if err != nil { + log.Fatalf("unable to marshal config to YAML: %v", err) + } + return string(bs) +} +``` + +## Viper or Vipers? + +Viper comes with a global instance (singleton) out of the box. + +Although it makes setting up configuration easy, +using it is generally discouraged as it makes testing harder and can lead to unexpected behavior. + +The best practice is to initialize a Viper instance and pass that around when necessary. + +The global instance _MAY_ be deprecated in the future. +See [#1855](https://github.com/spf13/viper/issues/1855) for more details. + +### Working with multiple vipers + +You can also create many different vipers for use in your application. Each will +have its own unique set of configurations and values. Each can read from a +different config file, key value store, etc. All of the functions that viper +package supports are mirrored as methods on a viper. + +Example: + +```go +x := viper.New() +y := viper.New() + +x.SetDefault("ContentDir", "content") +y.SetDefault("ContentDir", "foobar") + +//... +``` + +When working with multiple vipers, it is up to the user to keep track of the +different vipers. + + +## Q & A + +### Why is it called “Viper”? + +A: Viper is designed to be a [companion](http://en.wikipedia.org/wiki/Viper_(G.I._Joe)) +to [Cobra](https://github.com/spf13/cobra). While both can operate completely +independently, together they make a powerful pair to handle much of your +application foundation needs. + +### Why is it called “Cobra”? + +Is there a better name for a [commander](http://en.wikipedia.org/wiki/Cobra_Commander)? + +### Does Viper support case sensitive keys? + +**tl;dr:** No. + +Viper merges configuration from various sources, many of which are either case insensitive or uses different casing than the rest of the sources (eg. env vars). +In order to provide the best experience when using multiple sources, the decision has been made to make all keys case insensitive. + +There has been several attempts to implement case sensitivity, but unfortunately it's not that trivial. We might take a stab at implementing it in [Viper v2](https://github.com/spf13/viper/issues/772), but despite the initial noise, it does not seem to be requested that much. + +You can vote for case sensitivity by filling out this feedback form: https://forms.gle/R6faU74qPRPAzchZ9 + +### Is it safe to concurrently read and write to a viper? + +No, you will need to synchronize access to the viper yourself (for example by using the `sync` package). Concurrent reads and writes can cause a panic. + +## Troubleshooting + +See [TROUBLESHOOTING.md](TROUBLESHOOTING.md). + +## Development + +**For an optimal developer experience, it is recommended to install [Nix](https://nixos.org/download.html) and [direnv](https://direnv.net/docs/installation.html).** + +_Alternatively, install [Go](https://go.dev/dl/) on your computer then run `make deps` to install the rest of the dependencies._ + +Run the test suite: + +```shell +make test +``` + +Run linters: + +```shell +make lint # pass -j option to run them in parallel +``` + +Some linter violations can automatically be fixed: + +```shell +make fmt +``` + +## License + +The project is licensed under the [MIT License](LICENSE). diff --git a/vendor/github.com/spf13/viper/TROUBLESHOOTING.md b/vendor/github.com/spf13/viper/TROUBLESHOOTING.md new file mode 100644 index 0000000000..b68993d412 --- /dev/null +++ b/vendor/github.com/spf13/viper/TROUBLESHOOTING.md @@ -0,0 +1,32 @@ +# Troubleshooting + +## Unmarshaling doesn't work + +The most common reason for this issue is improper use of struct tags (eg. `yaml` or `json`). Viper uses [github.com/mitchellh/mapstructure](https://github.com/mitchellh/mapstructure) under the hood for unmarshaling values which uses `mapstructure` tags by default. Please refer to the library's documentation for using other struct tags. + +## Cannot find package + +Viper installation seems to fail a lot lately with the following (or a similar) error: + +``` +cannot find package "github.com/hashicorp/hcl/tree/hcl1" in any of: +/usr/local/Cellar/go/1.15.7_1/libexec/src/github.com/hashicorp/hcl/tree/hcl1 (from $GOROOT) +/Users/user/go/src/github.com/hashicorp/hcl/tree/hcl1 (from $GOPATH) +``` + +As the error message suggests, Go tries to look up dependencies in `GOPATH` mode (as it's commonly called) from the `GOPATH`. +Viper opted to use [Go Modules](https://go.dev/wiki/Modules) to manage its dependencies. While in many cases the two methods are interchangeable, once a dependency releases new (major) versions, `GOPATH` mode is no longer able to decide which version to use, so it'll either use one that's already present or pick a version (usually the `master` branch). + +The solution is easy: switch to using Go Modules. +Please refer to the [wiki](https://go.dev/wiki/Modules) on how to do that. + +**tl;dr* `export GO111MODULE=on` + +## Unquoted 'y' and 'n' characters get replaced with _true_ and _false_ when reading a YAML file + +This is a YAML 1.1 feature according to [go-yaml/yaml#740](https://github.com/go-yaml/yaml/issues/740). + +Potential solutions are: + +1. Quoting values resolved as boolean +1. Upgrading to YAML v3 (for the time being this is possible by passing the `viper_yaml3` tag to your build) diff --git a/vendor/github.com/spf13/viper/UPGRADE.md b/vendor/github.com/spf13/viper/UPGRADE.md new file mode 100644 index 0000000000..a33c965a41 --- /dev/null +++ b/vendor/github.com/spf13/viper/UPGRADE.md @@ -0,0 +1,147 @@ +# Update Log + +**This document details any major updates required to use new features or improvements in Viper.** + +## v1.20.x + +### New file searching API + +Viper now includes a new file searching API that allows users to customize how Viper looks for config files. + +Viper accepts a custom [`Finder`](https://pkg.go.dev/github.com/spf13/viper#Finder) interface implementation: + +```go +// Finder looks for files and directories in an [afero.Fs] filesystem. +type Finder interface { + Find(fsys afero.Fs) ([]string, error) +} +``` + +It is supposed to return a list of paths to config files. + +The default implementation uses [github.com/sagikazarmark/locafero](https://github.com/sagikazarmark/locafero) under the hood. + +You can supply your own implementation using `WithFinder`: + +```go +v := viper.NewWithOptions( + viper.WithFinder(&MyFinder{}), +) +``` + +For more information, check out the [Finder examples](https://pkg.go.dev/github.com/spf13/viper#Finder) +and the [documentation](https://pkg.go.dev/github.com/sagikazarmark/locafero) for the locafero package. + +### New encoding API + +Viper now allows customizing the encoding layer by providing an API for encoding and decoding configuration data: + +```go +// Encoder encodes Viper's internal data structures into a byte representation. +// It's primarily used for encoding a map[string]any into a file format. +type Encoder interface { + Encode(v map[string]any) ([]byte, error) +} + +// Decoder decodes the contents of a byte slice into Viper's internal data structures. +// It's primarily used for decoding contents of a file into a map[string]any. +type Decoder interface { + Decode(b []byte, v map[string]any) error +} + +// Codec combines [Encoder] and [Decoder] interfaces. +type Codec interface { + Encoder + Decoder +} +``` + +By default, Viper includes the following codecs: + +- JSON +- TOML +- YAML +- Dotenv + +The rest of the codecs are moved to [github.com/go-viper/encoding](https://github.com/go-viper/encoding) + +Customizing the encoding layer is possible by providing a custom registry of codecs: + +- [Encoder](https://pkg.go.dev/github.com/spf13/viper#Encoder) -> [EncoderRegistry](https://pkg.go.dev/github.com/spf13/viper#EncoderRegistry) +- [Decoder](https://pkg.go.dev/github.com/spf13/viper#Decoder) -> [DecoderRegistry](https://pkg.go.dev/github.com/spf13/viper#DecoderRegistry) +- [Codec](https://pkg.go.dev/github.com/spf13/viper#Codec) -> [CodecRegistry](https://pkg.go.dev/github.com/spf13/viper#CodecRegistry) + +You can supply the registry of codecs to Viper using the appropriate `With*Registry` function: + +```go +codecRegistry := viper.NewCodecRegistry() + +codecRegistry.RegisterCodec("myformat", &MyCodec{}) + +v := viper.NewWithOptions( + viper.WithCodecRegistry(codecRegistry), +) +``` + +### BREAKING: "github.com/mitchellh/mapstructure" depedency replaced + +The original [mapstructure](https://github.com/mitchellh/mapstructure) has been [archived](https://github.com/mitchellh/mapstructure/issues/349) and was replaced with a [fork](https://github.com/go-viper/mapstructure) maintained by Viper ([#1723](https://github.com/spf13/viper/pull/1723)). + +As a result, the package import path needs to be changed in cases where `mapstructure` is directly referenced in your code. + +For example, when providing a custom decoder config: + +```go +err := viper.Unmarshal(&appConfig, func(config *mapstructure.DecoderConfig) { + config.TagName = "yaml" +}) +``` + +The change is fairly straightforward, just replace all occurrences of the import path `github.com/mitchellh/mapstructure` with `github.com/go-viper/mapstructure/v2`: + +```diff +- import "github.com/mitchellh/mapstructure" ++ import "github.com/go-viper/mapstructure/v2" +``` + +### BREAKING: HCL, Java properties, INI removed from core + +In order to reduce third-party dependencies, Viper dropped support for the following formats from the core: + +- HCL +- Java properties +- INI + +You can still use these formats though by importing them from [github.com/go-viper/encoding](https://github.com/go-viper/encoding): + +```go +import ( + "github.com/go-viper/encoding/hcl" + "github.com/go-viper/encoding/javaproperties" + "github.com/go-viper/encoding/ini" +) + +codecRegistry := viper.NewCodecRegistry() + +{ + codec := hcl.Codec{} + + codecRegistry.RegisterCodec("hcl", codec) + codecRegistry.RegisterCodec("tfvars", codec) + +} + +{ + codec := &javaproperties.Codec{} + + codecRegistry.RegisterCodec("properties", codec) + codecRegistry.RegisterCodec("props", codec) + codecRegistry.RegisterCodec("prop", codec) +} + +codecRegistry.RegisterCodec("ini", ini.Codec{}) + +v := viper.NewWithOptions( + viper.WithCodecRegistry(codecRegistry), +) +``` diff --git a/vendor/github.com/spf13/viper/encoding.go b/vendor/github.com/spf13/viper/encoding.go new file mode 100644 index 0000000000..a7da55860e --- /dev/null +++ b/vendor/github.com/spf13/viper/encoding.go @@ -0,0 +1,181 @@ +package viper + +import ( + "errors" + "strings" + "sync" + + "github.com/spf13/viper/internal/encoding/dotenv" + "github.com/spf13/viper/internal/encoding/json" + "github.com/spf13/viper/internal/encoding/toml" + "github.com/spf13/viper/internal/encoding/yaml" +) + +// Encoder encodes Viper's internal data structures into a byte representation. +// It's primarily used for encoding a map[string]any into a file format. +type Encoder interface { + Encode(v map[string]any) ([]byte, error) +} + +// Decoder decodes the contents of a byte slice into Viper's internal data structures. +// It's primarily used for decoding contents of a file into a map[string]any. +type Decoder interface { + Decode(b []byte, v map[string]any) error +} + +// Codec combines [Encoder] and [Decoder] interfaces. +type Codec interface { + Encoder + Decoder +} + +// TODO: consider adding specific errors for not found scenarios + +// EncoderRegistry returns an [Encoder] for a given format. +// +// Format is case-insensitive. +// +// [EncoderRegistry] returns an error if no [Encoder] is registered for the format. +type EncoderRegistry interface { + Encoder(format string) (Encoder, error) +} + +// DecoderRegistry returns an [Decoder] for a given format. +// +// Format is case-insensitive. +// +// [DecoderRegistry] returns an error if no [Decoder] is registered for the format. +type DecoderRegistry interface { + Decoder(format string) (Decoder, error) +} + +// [CodecRegistry] combines [EncoderRegistry] and [DecoderRegistry] interfaces. +type CodecRegistry interface { + EncoderRegistry + DecoderRegistry +} + +// WithEncoderRegistry sets a custom [EncoderRegistry]. +func WithEncoderRegistry(r EncoderRegistry) Option { + return optionFunc(func(v *Viper) { + if r == nil { + return + } + + v.encoderRegistry = r + }) +} + +// WithDecoderRegistry sets a custom [DecoderRegistry]. +func WithDecoderRegistry(r DecoderRegistry) Option { + return optionFunc(func(v *Viper) { + if r == nil { + return + } + + v.decoderRegistry = r + }) +} + +// WithCodecRegistry sets a custom [EncoderRegistry] and [DecoderRegistry]. +func WithCodecRegistry(r CodecRegistry) Option { + return optionFunc(func(v *Viper) { + if r == nil { + return + } + + v.encoderRegistry = r + v.decoderRegistry = r + }) +} + +// DefaultCodecRegistry is a simple implementation of [CodecRegistry] that allows registering custom [Codec]s. +type DefaultCodecRegistry struct { + codecs map[string]Codec + + mu sync.RWMutex + once sync.Once +} + +// NewCodecRegistry returns a new [CodecRegistry], ready to accept custom [Codec]s. +func NewCodecRegistry() *DefaultCodecRegistry { + r := &DefaultCodecRegistry{} + + r.init() + + return r +} + +func (r *DefaultCodecRegistry) init() { + r.once.Do(func() { + r.codecs = map[string]Codec{} + }) +} + +// RegisterCodec registers a custom [Codec]. +// +// Format is case-insensitive. +func (r *DefaultCodecRegistry) RegisterCodec(format string, codec Codec) error { + r.init() + + r.mu.Lock() + defer r.mu.Unlock() + + r.codecs[strings.ToLower(format)] = codec + + return nil +} + +// Encoder implements the [EncoderRegistry] interface. +// +// Format is case-insensitive. +func (r *DefaultCodecRegistry) Encoder(format string) (Encoder, error) { + encoder, ok := r.codec(format) + if !ok { + return nil, errors.New("encoder not found for this format") + } + + return encoder, nil +} + +// Decoder implements the [DecoderRegistry] interface. +// +// Format is case-insensitive. +func (r *DefaultCodecRegistry) Decoder(format string) (Decoder, error) { + decoder, ok := r.codec(format) + if !ok { + return nil, errors.New("decoder not found for this format") + } + + return decoder, nil +} + +func (r *DefaultCodecRegistry) codec(format string) (Codec, bool) { + r.mu.Lock() + defer r.mu.Unlock() + + format = strings.ToLower(format) + + if r.codecs != nil { + codec, ok := r.codecs[format] + if ok { + return codec, true + } + } + + switch format { + case "yaml", "yml": + return yaml.Codec{}, true + + case "json": + return json.Codec{}, true + + case "toml": + return toml.Codec{}, true + + case "dotenv", "env": + return &dotenv.Codec{}, true + } + + return nil, false +} diff --git a/vendor/github.com/spf13/viper/experimental.go b/vendor/github.com/spf13/viper/experimental.go new file mode 100644 index 0000000000..6e19e8a100 --- /dev/null +++ b/vendor/github.com/spf13/viper/experimental.go @@ -0,0 +1,8 @@ +package viper + +// ExperimentalBindStruct tells Viper to use the new bind struct feature. +func ExperimentalBindStruct() Option { + return optionFunc(func(v *Viper) { + v.experimentalBindStruct = true + }) +} diff --git a/vendor/github.com/spf13/viper/file.go b/vendor/github.com/spf13/viper/file.go new file mode 100644 index 0000000000..50a40581d0 --- /dev/null +++ b/vendor/github.com/spf13/viper/file.go @@ -0,0 +1,104 @@ +package viper + +import ( + "fmt" + "os" + "path/filepath" + + "github.com/sagikazarmark/locafero" + "github.com/spf13/afero" +) + +// ExperimentalFinder tells Viper to use the new Finder interface for finding configuration files. +func ExperimentalFinder() Option { + return optionFunc(func(v *Viper) { + v.experimentalFinder = true + }) +} + +// Search for a config file. +func (v *Viper) findConfigFile() (string, error) { + finder := v.finder + + if finder == nil && v.experimentalFinder { + var names []string + + if v.configType != "" { + names = locafero.NameWithOptionalExtensions(v.configName, SupportedExts...) + } else { + names = locafero.NameWithExtensions(v.configName, SupportedExts...) + } + + finder = locafero.Finder{ + Paths: v.configPaths, + Names: names, + Type: locafero.FileTypeFile, + } + } + + if finder != nil { + return v.findConfigFileWithFinder(finder) + } + + return v.findConfigFileOld() +} + +func (v *Viper) findConfigFileWithFinder(finder Finder) (string, error) { + results, err := finder.Find(v.fs) + if err != nil { + return "", err + } + + if len(results) == 0 { + return "", ConfigFileNotFoundError{v.configName, fmt.Sprintf("%s", v.configPaths)} + } + + // We call clean on the final result to ensure that the path is in its canonical form. + // This is mostly for consistent path handling and to make sure tests pass. + return results[0], nil +} + +// Search all configPaths for any config file. +// Returns the first path that exists (and is a config file). +func (v *Viper) findConfigFileOld() (string, error) { + v.logger.Info("searching for config in paths", "paths", v.configPaths) + + for _, cp := range v.configPaths { + file := v.searchInPath(cp) + if file != "" { + return file, nil + } + } + return "", ConfigFileNotFoundError{v.configName, fmt.Sprintf("%s", v.configPaths)} +} + +func (v *Viper) searchInPath(in string) (filename string) { + v.logger.Debug("searching for config in path", "path", in) + for _, ext := range SupportedExts { + v.logger.Debug("checking if file exists", "file", filepath.Join(in, v.configName+"."+ext)) + if b, _ := exists(v.fs, filepath.Join(in, v.configName+"."+ext)); b { + v.logger.Debug("found file", "file", filepath.Join(in, v.configName+"."+ext)) + return filepath.Join(in, v.configName+"."+ext) + } + } + + if v.configType != "" { + if b, _ := exists(v.fs, filepath.Join(in, v.configName)); b { + return filepath.Join(in, v.configName) + } + } + + return "" +} + +// exists checks if file exists. +func exists(fs afero.Fs, path string) (bool, error) { + stat, err := fs.Stat(path) + if err == nil { + return !stat.IsDir(), nil + } + if os.IsNotExist(err) { + return false, nil + } + return false, err +} diff --git a/vendor/github.com/spf13/viper/finder.go b/vendor/github.com/spf13/viper/finder.go new file mode 100644 index 0000000000..9b203ea69a --- /dev/null +++ b/vendor/github.com/spf13/viper/finder.go @@ -0,0 +1,55 @@ +package viper + +import ( + "errors" + + "github.com/spf13/afero" +) + +// WithFinder sets a custom [Finder]. +func WithFinder(f Finder) Option { + return optionFunc(func(v *Viper) { + if f == nil { + return + } + + v.finder = f + }) +} + +// Finder looks for files and directories in an [afero.Fs] filesystem. +type Finder interface { + Find(fsys afero.Fs) ([]string, error) +} + +// Finders combines multiple finders into one. +func Finders(finders ...Finder) Finder { + return &combinedFinder{finders: finders} +} + +// combinedFinder is a Finder that combines multiple finders. +type combinedFinder struct { + finders []Finder +} + +// Find implements the [Finder] interface. +func (c *combinedFinder) Find(fsys afero.Fs) ([]string, error) { + var results []string + var errs []error + + for _, finder := range c.finders { + if finder == nil { + continue + } + + r, err := finder.Find(fsys) + if err != nil { + errs = append(errs, err) + continue + } + + results = append(results, r...) + } + + return results, errors.Join(errs...) +} diff --git a/vendor/github.com/spf13/viper/flags.go b/vendor/github.com/spf13/viper/flags.go new file mode 100644 index 0000000000..de033ed58f --- /dev/null +++ b/vendor/github.com/spf13/viper/flags.go @@ -0,0 +1,57 @@ +package viper + +import "github.com/spf13/pflag" + +// FlagValueSet is an interface that users can implement +// to bind a set of flags to viper. +type FlagValueSet interface { + VisitAll(fn func(FlagValue)) +} + +// FlagValue is an interface that users can implement +// to bind different flags to viper. +type FlagValue interface { + HasChanged() bool + Name() string + ValueString() string + ValueType() string +} + +// pflagValueSet is a wrapper around *pflag.ValueSet +// that implements FlagValueSet. +type pflagValueSet struct { + flags *pflag.FlagSet +} + +// VisitAll iterates over all *pflag.Flag inside the *pflag.FlagSet. +func (p pflagValueSet) VisitAll(fn func(flag FlagValue)) { + p.flags.VisitAll(func(flag *pflag.Flag) { + fn(pflagValue{flag}) + }) +} + +// pflagValue is a wrapper around *pflag.flag +// that implements FlagValue. +type pflagValue struct { + flag *pflag.Flag +} + +// HasChanged returns whether the flag has changes or not. +func (p pflagValue) HasChanged() bool { + return p.flag.Changed +} + +// Name returns the name of the flag. +func (p pflagValue) Name() string { + return p.flag.Name +} + +// ValueString returns the value of the flag as a string. +func (p pflagValue) ValueString() string { + return p.flag.Value.String() +} + +// ValueType returns the type of the flag as a string. +func (p pflagValue) ValueType() string { + return p.flag.Value.Type() +} diff --git a/vendor/github.com/spf13/viper/flake.lock b/vendor/github.com/spf13/viper/flake.lock new file mode 100644 index 0000000000..0b8cfb5a83 --- /dev/null +++ b/vendor/github.com/spf13/viper/flake.lock @@ -0,0 +1,255 @@ +{ + "nodes": { + "cachix": { + "inputs": { + "devenv": [ + "devenv" + ], + "flake-compat": [ + "devenv" + ], + "git-hooks": [ + "devenv", + "git-hooks" + ], + "nixpkgs": [ + "devenv", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1748883665, + "narHash": "sha256-R0W7uAg+BLoHjMRMQ8+oiSbTq8nkGz5RDpQ+ZfxxP3A=", + "owner": "cachix", + "repo": "cachix", + "rev": "f707778d902af4d62d8dd92c269f8e70de09acbe", + "type": "github" + }, + "original": { + "owner": "cachix", + "ref": "latest", + "repo": "cachix", + "type": "github" + } + }, + "devenv": { + "inputs": { + "cachix": "cachix", + "flake-compat": "flake-compat", + "git-hooks": "git-hooks", + "nix": "nix", + "nixpkgs": "nixpkgs" + }, + "locked": { + "lastModified": 1755257397, + "narHash": "sha256-VU+OHexL2y6y7yrpEc6bZvYYwoQg6aZK1b4YxT0yZCk=", + "owner": "cachix", + "repo": "devenv", + "rev": "6f9c3d4722aa253631644329f7bda60b1d3d1b97", + "type": "github" + }, + "original": { + "owner": "cachix", + "repo": "devenv", + "type": "github" + } + }, + "flake-compat": { + "flake": false, + "locked": { + "lastModified": 1747046372, + "narHash": "sha256-CIVLLkVgvHYbgI2UpXvIIBJ12HWgX+fjA8Xf8PUmqCY=", + "owner": "edolstra", + "repo": "flake-compat", + "rev": "9100a0f413b0c601e0533d1d94ffd501ce2e7885", + "type": "github" + }, + "original": { + "owner": "edolstra", + "repo": "flake-compat", + "type": "github" + } + }, + "flake-parts": { + "inputs": { + "nixpkgs-lib": [ + "devenv", + "nix", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1733312601, + "narHash": "sha256-4pDvzqnegAfRkPwO3wmwBhVi/Sye1mzps0zHWYnP88c=", + "owner": "hercules-ci", + "repo": "flake-parts", + "rev": "205b12d8b7cd4802fbcb8e8ef6a0f1408781a4f9", + "type": "github" + }, + "original": { + "owner": "hercules-ci", + "repo": "flake-parts", + "type": "github" + } + }, + "flake-parts_2": { + "inputs": { + "nixpkgs-lib": "nixpkgs-lib" + }, + "locked": { + "lastModified": 1754487366, + "narHash": "sha256-pHYj8gUBapuUzKV/kN/tR3Zvqc7o6gdFB9XKXIp1SQ8=", + "owner": "hercules-ci", + "repo": "flake-parts", + "rev": "af66ad14b28a127c5c0f3bbb298218fc63528a18", + "type": "github" + }, + "original": { + "owner": "hercules-ci", + "repo": "flake-parts", + "type": "github" + } + }, + "git-hooks": { + "inputs": { + "flake-compat": [ + "devenv", + "flake-compat" + ], + "gitignore": "gitignore", + "nixpkgs": [ + "devenv", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1750779888, + "narHash": "sha256-wibppH3g/E2lxU43ZQHC5yA/7kIKLGxVEnsnVK1BtRg=", + "owner": "cachix", + "repo": "git-hooks.nix", + "rev": "16ec914f6fb6f599ce988427d9d94efddf25fe6d", + "type": "github" + }, + "original": { + "owner": "cachix", + "repo": "git-hooks.nix", + "type": "github" + } + }, + "gitignore": { + "inputs": { + "nixpkgs": [ + "devenv", + "git-hooks", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1709087332, + "narHash": "sha256-HG2cCnktfHsKV0s4XW83gU3F57gaTljL9KNSuG6bnQs=", + "owner": "hercules-ci", + "repo": "gitignore.nix", + "rev": "637db329424fd7e46cf4185293b9cc8c88c95394", + "type": "github" + }, + "original": { + "owner": "hercules-ci", + "repo": "gitignore.nix", + "type": "github" + } + }, + "nix": { + "inputs": { + "flake-compat": [ + "devenv", + "flake-compat" + ], + "flake-parts": "flake-parts", + "git-hooks-nix": [ + "devenv", + "git-hooks" + ], + "nixpkgs": [ + "devenv", + "nixpkgs" + ], + "nixpkgs-23-11": [ + "devenv" + ], + "nixpkgs-regression": [ + "devenv" + ] + }, + "locked": { + "lastModified": 1755029779, + "narHash": "sha256-3+GHIYGg4U9XKUN4rg473frIVNn8YD06bjwxKS1IPrU=", + "owner": "cachix", + "repo": "nix", + "rev": "b0972b0eee6726081d10b1199f54de6d2917f861", + "type": "github" + }, + "original": { + "owner": "cachix", + "ref": "devenv-2.30", + "repo": "nix", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1750441195, + "narHash": "sha256-yke+pm+MdgRb6c0dPt8MgDhv7fcBbdjmv1ZceNTyzKg=", + "owner": "cachix", + "repo": "devenv-nixpkgs", + "rev": "0ceffe312871b443929ff3006960d29b120dc627", + "type": "github" + }, + "original": { + "owner": "cachix", + "ref": "rolling", + "repo": "devenv-nixpkgs", + "type": "github" + } + }, + "nixpkgs-lib": { + "locked": { + "lastModified": 1753579242, + "narHash": "sha256-zvaMGVn14/Zz8hnp4VWT9xVnhc8vuL3TStRqwk22biA=", + "owner": "nix-community", + "repo": "nixpkgs.lib", + "rev": "0f36c44e01a6129be94e3ade315a5883f0228a6e", + "type": "github" + }, + "original": { + "owner": "nix-community", + "repo": "nixpkgs.lib", + "type": "github" + } + }, + "nixpkgs_2": { + "locked": { + "lastModified": 1755268003, + "narHash": "sha256-nNaeJjo861wFR0tjHDyCnHs1rbRtrMgxAKMoig9Sj/w=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "32f313e49e42f715491e1ea7b306a87c16fe0388", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixpkgs-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "devenv": "devenv", + "flake-parts": "flake-parts_2", + "nixpkgs": "nixpkgs_2" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/vendor/github.com/spf13/viper/flake.nix b/vendor/github.com/spf13/viper/flake.nix new file mode 100644 index 0000000000..a16b2e3a71 --- /dev/null +++ b/vendor/github.com/spf13/viper/flake.nix @@ -0,0 +1,61 @@ +{ + description = "Viper"; + + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable"; + flake-parts.url = "github:hercules-ci/flake-parts"; + devenv.url = "github:cachix/devenv"; + }; + + outputs = + inputs@{ flake-parts, ... }: + flake-parts.lib.mkFlake { inherit inputs; } { + imports = [ + inputs.devenv.flakeModule + ]; + + systems = [ + "x86_64-linux" + "x86_64-darwin" + "aarch64-darwin" + ]; + + perSystem = + { pkgs, ... }: + { + devenv.shells = { + default = { + languages = { + go.enable = true; + }; + + git-hooks.hooks = { + nixpkgs-fmt.enable = true; + yamllint.enable = true; + }; + + packages = with pkgs; [ + gnumake + + golangci-lint + yamllint + ]; + + scripts = { + versions.exec = '' + go version + golangci-lint version + ''; + }; + + enterShell = '' + versions + ''; + + # https://github.com/cachix/devenv/issues/528#issuecomment-1556108767 + containers = pkgs.lib.mkForce { }; + }; + }; + }; + }; +} diff --git a/vendor/github.com/spf13/viper/internal/encoding/dotenv/codec.go b/vendor/github.com/spf13/viper/internal/encoding/dotenv/codec.go new file mode 100644 index 0000000000..3ebc76f029 --- /dev/null +++ b/vendor/github.com/spf13/viper/internal/encoding/dotenv/codec.go @@ -0,0 +1,61 @@ +package dotenv + +import ( + "bytes" + "fmt" + "sort" + "strings" + + "github.com/subosito/gotenv" +) + +const keyDelimiter = "_" + +// Codec implements the encoding.Encoder and encoding.Decoder interfaces for encoding data containing environment variables +// (commonly called as dotenv format). +type Codec struct{} + +func (Codec) Encode(v map[string]any) ([]byte, error) { + flattened := map[string]any{} + + flattened = flattenAndMergeMap(flattened, v, "", keyDelimiter) + + keys := make([]string, 0, len(flattened)) + + for key := range flattened { + keys = append(keys, key) + } + + sort.Strings(keys) + + var buf bytes.Buffer + + for _, key := range keys { + _, err := buf.WriteString(fmt.Sprintf("%v=%v\n", strings.ToUpper(key), flattened[key])) + if err != nil { + return nil, err + } + } + + return buf.Bytes(), nil +} + +func (Codec) Decode(b []byte, v map[string]any) error { + var buf bytes.Buffer + + _, err := buf.Write(b) + if err != nil { + return err + } + + env, err := gotenv.StrictParse(&buf) + if err != nil { + return err + } + + for key, value := range env { + v[key] = value + } + + return nil +} diff --git a/vendor/github.com/spf13/viper/internal/encoding/dotenv/map_utils.go b/vendor/github.com/spf13/viper/internal/encoding/dotenv/map_utils.go new file mode 100644 index 0000000000..8bfe0a9de2 --- /dev/null +++ b/vendor/github.com/spf13/viper/internal/encoding/dotenv/map_utils.go @@ -0,0 +1,41 @@ +package dotenv + +import ( + "strings" + + "github.com/spf13/cast" +) + +// flattenAndMergeMap recursively flattens the given map into a new map +// Code is based on the function with the same name in the main package. +// TODO: move it to a common place. +func flattenAndMergeMap(shadow, m map[string]any, prefix, delimiter string) map[string]any { + if shadow != nil && prefix != "" && shadow[prefix] != nil { + // prefix is shadowed => nothing more to flatten + return shadow + } + if shadow == nil { + shadow = make(map[string]any) + } + + var m2 map[string]any + if prefix != "" { + prefix += delimiter + } + for k, val := range m { + fullKey := prefix + k + switch val := val.(type) { + case map[string]any: + m2 = val + case map[any]any: + m2 = cast.ToStringMap(val) + default: + // immediate value + shadow[strings.ToLower(fullKey)] = val + continue + } + // recursively merge to shadow map + shadow = flattenAndMergeMap(shadow, m2, fullKey, delimiter) + } + return shadow +} diff --git a/vendor/github.com/spf13/viper/internal/encoding/json/codec.go b/vendor/github.com/spf13/viper/internal/encoding/json/codec.go new file mode 100644 index 0000000000..da7546b5a1 --- /dev/null +++ b/vendor/github.com/spf13/viper/internal/encoding/json/codec.go @@ -0,0 +1,17 @@ +package json + +import ( + "encoding/json" +) + +// Codec implements the encoding.Encoder and encoding.Decoder interfaces for JSON encoding. +type Codec struct{} + +func (Codec) Encode(v map[string]any) ([]byte, error) { + // TODO: expose prefix and indent in the Codec as setting? + return json.MarshalIndent(v, "", " ") +} + +func (Codec) Decode(b []byte, v map[string]any) error { + return json.Unmarshal(b, &v) +} diff --git a/vendor/github.com/spf13/viper/internal/encoding/toml/codec.go b/vendor/github.com/spf13/viper/internal/encoding/toml/codec.go new file mode 100644 index 0000000000..c70aa8d280 --- /dev/null +++ b/vendor/github.com/spf13/viper/internal/encoding/toml/codec.go @@ -0,0 +1,16 @@ +package toml + +import ( + "github.com/pelletier/go-toml/v2" +) + +// Codec implements the encoding.Encoder and encoding.Decoder interfaces for TOML encoding. +type Codec struct{} + +func (Codec) Encode(v map[string]any) ([]byte, error) { + return toml.Marshal(v) +} + +func (Codec) Decode(b []byte, v map[string]any) error { + return toml.Unmarshal(b, &v) +} diff --git a/vendor/github.com/spf13/viper/internal/encoding/yaml/codec.go b/vendor/github.com/spf13/viper/internal/encoding/yaml/codec.go new file mode 100644 index 0000000000..a7a839fd93 --- /dev/null +++ b/vendor/github.com/spf13/viper/internal/encoding/yaml/codec.go @@ -0,0 +1,14 @@ +package yaml + +import "go.yaml.in/yaml/v3" + +// Codec implements the encoding.Encoder and encoding.Decoder interfaces for YAML encoding. +type Codec struct{} + +func (Codec) Encode(v map[string]any) ([]byte, error) { + return yaml.Marshal(v) +} + +func (Codec) Decode(b []byte, v map[string]any) error { + return yaml.Unmarshal(b, &v) +} diff --git a/vendor/github.com/spf13/viper/internal/features/bind_struct.go b/vendor/github.com/spf13/viper/internal/features/bind_struct.go new file mode 100644 index 0000000000..89302c2164 --- /dev/null +++ b/vendor/github.com/spf13/viper/internal/features/bind_struct.go @@ -0,0 +1,5 @@ +//go:build viper_bind_struct + +package features + +const BindStruct = true diff --git a/vendor/github.com/spf13/viper/internal/features/bind_struct_default.go b/vendor/github.com/spf13/viper/internal/features/bind_struct_default.go new file mode 100644 index 0000000000..edfaf73b64 --- /dev/null +++ b/vendor/github.com/spf13/viper/internal/features/bind_struct_default.go @@ -0,0 +1,5 @@ +//go:build !viper_bind_struct + +package features + +const BindStruct = false diff --git a/vendor/github.com/spf13/viper/internal/features/finder.go b/vendor/github.com/spf13/viper/internal/features/finder.go new file mode 100644 index 0000000000..983ea3a9d7 --- /dev/null +++ b/vendor/github.com/spf13/viper/internal/features/finder.go @@ -0,0 +1,5 @@ +//go:build viper_finder + +package features + +const Finder = true diff --git a/vendor/github.com/spf13/viper/internal/features/finder_default.go b/vendor/github.com/spf13/viper/internal/features/finder_default.go new file mode 100644 index 0000000000..89bcb06ee1 --- /dev/null +++ b/vendor/github.com/spf13/viper/internal/features/finder_default.go @@ -0,0 +1,5 @@ +//go:build !viper_finder + +package features + +const Finder = false diff --git a/vendor/github.com/spf13/viper/logger.go b/vendor/github.com/spf13/viper/logger.go new file mode 100644 index 0000000000..828042f29a --- /dev/null +++ b/vendor/github.com/spf13/viper/logger.go @@ -0,0 +1,31 @@ +package viper + +import ( + "context" + "log/slog" +) + +// WithLogger sets a custom logger. +func WithLogger(l *slog.Logger) Option { + return optionFunc(func(v *Viper) { + v.logger = l + }) +} + +type discardHandler struct{} + +func (n *discardHandler) Enabled(_ context.Context, _ slog.Level) bool { + return false +} + +func (n *discardHandler) Handle(_ context.Context, _ slog.Record) error { + return nil +} + +func (n *discardHandler) WithAttrs(_ []slog.Attr) slog.Handler { + return n +} + +func (n *discardHandler) WithGroup(_ string) slog.Handler { + return n +} diff --git a/vendor/github.com/spf13/viper/remote.go b/vendor/github.com/spf13/viper/remote.go new file mode 100644 index 0000000000..46f26721d4 --- /dev/null +++ b/vendor/github.com/spf13/viper/remote.go @@ -0,0 +1,259 @@ +package viper + +import ( + "bytes" + "fmt" + "io" + "reflect" + "slices" +) + +// SupportedRemoteProviders are universally supported remote providers. +var SupportedRemoteProviders = []string{"etcd", "etcd3", "consul", "firestore", "nats"} + +func resetRemote() { + SupportedRemoteProviders = []string{"etcd", "etcd3", "consul", "firestore", "nats"} +} + +type remoteConfigFactory interface { + Get(rp RemoteProvider) (io.Reader, error) + Watch(rp RemoteProvider) (io.Reader, error) + WatchChannel(rp RemoteProvider) (<-chan *RemoteResponse, chan bool) +} + +type RemoteResponse struct { + Value []byte + Error error +} + +// RemoteConfig is optional, see the remote package. +var RemoteConfig remoteConfigFactory + +// UnsupportedRemoteProviderError denotes encountering an unsupported remote +// provider. Currently only etcd and Consul are supported. +type UnsupportedRemoteProviderError string + +// Error returns the formatted remote provider error. +func (str UnsupportedRemoteProviderError) Error() string { + return fmt.Sprintf("Unsupported Remote Provider Type %q", string(str)) +} + +// RemoteConfigError denotes encountering an error while trying to +// pull the configuration from the remote provider. +type RemoteConfigError string + +// Error returns the formatted remote provider error. +func (rce RemoteConfigError) Error() string { + return fmt.Sprintf("Remote Configurations Error: %s", string(rce)) +} + +type defaultRemoteProvider struct { + provider string + endpoint string + path string + secretKeyring string +} + +func (rp defaultRemoteProvider) Provider() string { + return rp.provider +} + +func (rp defaultRemoteProvider) Endpoint() string { + return rp.endpoint +} + +func (rp defaultRemoteProvider) Path() string { + return rp.path +} + +func (rp defaultRemoteProvider) SecretKeyring() string { + return rp.secretKeyring +} + +// RemoteProvider stores the configuration necessary +// to connect to a remote key/value store. +// Optional secretKeyring to unencrypt encrypted values +// can be provided. +type RemoteProvider interface { + Provider() string + Endpoint() string + Path() string + SecretKeyring() string +} + +// AddRemoteProvider adds a remote configuration source. +// Remote Providers are searched in the order they are added. +// provider is a string value: "etcd", "etcd3", "consul", "firestore" or "nats" are currently supported. +// endpoint is the url. etcd requires http://ip:port, consul requires ip:port, nats requires nats://ip:port +// path is the path in the k/v store to retrieve configuration +// To retrieve a config file called myapp.json from /configs/myapp.json +// you should set path to /configs and set config name (SetConfigName()) to +// "myapp". +func AddRemoteProvider(provider, endpoint, path string) error { + return v.AddRemoteProvider(provider, endpoint, path) +} + +func (v *Viper) AddRemoteProvider(provider, endpoint, path string) error { + if !slices.Contains(SupportedRemoteProviders, provider) { + return UnsupportedRemoteProviderError(provider) + } + if provider != "" && endpoint != "" { + v.logger.Info("adding remote provider", "provider", provider, "endpoint", endpoint) + + rp := &defaultRemoteProvider{ + endpoint: endpoint, + provider: provider, + path: path, + } + if !v.providerPathExists(rp) { + v.remoteProviders = append(v.remoteProviders, rp) + } + } + return nil +} + +// AddSecureRemoteProvider adds a remote configuration source. +// Secure Remote Providers are searched in the order they are added. +// provider is a string value: "etcd", "etcd3", "consul", "firestore" or "nats" are currently supported. +// endpoint is the url. etcd requires http://ip:port consul requires ip:port +// secretkeyring is the filepath to your openpgp secret keyring. e.g. /etc/secrets/myring.gpg +// path is the path in the k/v store to retrieve configuration +// To retrieve a config file called myapp.json from /configs/myapp.json +// you should set path to /configs and set config name (SetConfigName()) to +// "myapp". +// Secure Remote Providers are implemented with github.com/sagikazarmark/crypt. +func AddSecureRemoteProvider(provider, endpoint, path, secretkeyring string) error { + return v.AddSecureRemoteProvider(provider, endpoint, path, secretkeyring) +} + +func (v *Viper) AddSecureRemoteProvider(provider, endpoint, path, secretkeyring string) error { + if !slices.Contains(SupportedRemoteProviders, provider) { + return UnsupportedRemoteProviderError(provider) + } + if provider != "" && endpoint != "" { + v.logger.Info("adding remote provider", "provider", provider, "endpoint", endpoint) + + rp := &defaultRemoteProvider{ + endpoint: endpoint, + provider: provider, + path: path, + secretKeyring: secretkeyring, + } + if !v.providerPathExists(rp) { + v.remoteProviders = append(v.remoteProviders, rp) + } + } + return nil +} + +func (v *Viper) providerPathExists(p *defaultRemoteProvider) bool { + for _, y := range v.remoteProviders { + if reflect.DeepEqual(y, p) { + return true + } + } + return false +} + +// ReadRemoteConfig attempts to get configuration from a remote source +// and read it in the remote configuration registry. +func ReadRemoteConfig() error { return v.ReadRemoteConfig() } + +func (v *Viper) ReadRemoteConfig() error { + return v.getKeyValueConfig() +} + +func WatchRemoteConfig() error { return v.WatchRemoteConfig() } +func (v *Viper) WatchRemoteConfig() error { + return v.watchKeyValueConfig() +} + +func (v *Viper) WatchRemoteConfigOnChannel() error { + return v.watchKeyValueConfigOnChannel() +} + +// Retrieve the first found remote configuration. +func (v *Viper) getKeyValueConfig() error { + if RemoteConfig == nil { + return RemoteConfigError("Enable the remote features by doing a blank import of the viper/remote package: '_ github.com/spf13/viper/remote'") + } + + if len(v.remoteProviders) == 0 { + return RemoteConfigError("No Remote Providers") + } + + for _, rp := range v.remoteProviders { + val, err := v.getRemoteConfig(rp) + if err != nil { + v.logger.Error(fmt.Errorf("get remote config: %w", err).Error()) + + continue + } + + v.kvstore = val + + return nil + } + return RemoteConfigError("No Files Found") +} + +func (v *Viper) getRemoteConfig(provider RemoteProvider) (map[string]any, error) { + reader, err := RemoteConfig.Get(provider) + if err != nil { + return nil, err + } + err = v.unmarshalReader(reader, v.kvstore) + return v.kvstore, err +} + +// Retrieve the first found remote configuration. +func (v *Viper) watchKeyValueConfigOnChannel() error { + if len(v.remoteProviders) == 0 { + return RemoteConfigError("No Remote Providers") + } + + for _, rp := range v.remoteProviders { + respc, _ := RemoteConfig.WatchChannel(rp) + // Todo: Add quit channel + go func(rc <-chan *RemoteResponse) { + for { + b := <-rc + reader := bytes.NewReader(b.Value) + err := v.unmarshalReader(reader, v.kvstore) + if err != nil { + v.logger.Error(fmt.Errorf("failed to unmarshal remote config: %w", err).Error()) + } + } + }(respc) + return nil + } + return RemoteConfigError("No Files Found") +} + +// Retrieve the first found remote configuration. +func (v *Viper) watchKeyValueConfig() error { + if len(v.remoteProviders) == 0 { + return RemoteConfigError("No Remote Providers") + } + + for _, rp := range v.remoteProviders { + val, err := v.watchRemoteConfig(rp) + if err != nil { + v.logger.Error(fmt.Errorf("watch remote config: %w", err).Error()) + + continue + } + v.kvstore = val + return nil + } + return RemoteConfigError("No Files Found") +} + +func (v *Viper) watchRemoteConfig(provider RemoteProvider) (map[string]any, error) { + reader, err := RemoteConfig.Watch(provider) + if err != nil { + return nil, err + } + err = v.unmarshalReader(reader, v.kvstore) + return v.kvstore, err +} diff --git a/vendor/github.com/spf13/viper/util.go b/vendor/github.com/spf13/viper/util.go new file mode 100644 index 0000000000..d08ed46211 --- /dev/null +++ b/vendor/github.com/spf13/viper/util.go @@ -0,0 +1,211 @@ +// Copyright © 2014 Steve Francia . +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file. + +// Viper is a application configuration system. +// It believes that applications can be configured a variety of ways +// via flags, ENVIRONMENT variables, configuration files retrieved +// from the file system, or a remote key/value store. + +package viper + +import ( + "fmt" + "log/slog" + "os" + "path/filepath" + "runtime" + "strings" + "unicode" + + "github.com/spf13/cast" +) + +// ConfigParseError denotes failing to parse configuration file. +type ConfigParseError struct { + err error +} + +// Error returns the formatted configuration error. +func (pe ConfigParseError) Error() string { + return fmt.Sprintf("While parsing config: %s", pe.err.Error()) +} + +// Unwrap returns the wrapped error. +func (pe ConfigParseError) Unwrap() error { + return pe.err +} + +// toCaseInsensitiveValue checks if the value is a map; +// if so, create a copy and lower-case the keys recursively. +func toCaseInsensitiveValue(value any) any { + switch v := value.(type) { + case map[any]any: + value = copyAndInsensitiviseMap(cast.ToStringMap(v)) + case map[string]any: + value = copyAndInsensitiviseMap(v) + } + + return value +} + +// copyAndInsensitiviseMap behaves like insensitiviseMap, but creates a copy of +// any map it makes case insensitive. +func copyAndInsensitiviseMap(m map[string]any) map[string]any { + nm := make(map[string]any) + + for key, val := range m { + lkey := strings.ToLower(key) + switch v := val.(type) { + case map[any]any: + nm[lkey] = copyAndInsensitiviseMap(cast.ToStringMap(v)) + case map[string]any: + nm[lkey] = copyAndInsensitiviseMap(v) + default: + nm[lkey] = v + } + } + + return nm +} + +func insensitiviseVal(val any) any { + switch v := val.(type) { + case map[any]any: + // nested map: cast and recursively insensitivise + val = cast.ToStringMap(val) + insensitiviseMap(val.(map[string]any)) + case map[string]any: + // nested map: recursively insensitivise + insensitiviseMap(v) + case []any: + // nested array: recursively insensitivise + insensitiveArray(v) + } + return val +} + +func insensitiviseMap(m map[string]any) { + for key, val := range m { + val = insensitiviseVal(val) + lower := strings.ToLower(key) + if key != lower { + // remove old key (not lower-cased) + delete(m, key) + } + // update map + m[lower] = val + } +} + +func insensitiveArray(a []any) { + for i, val := range a { + a[i] = insensitiviseVal(val) + } +} + +func absPathify(logger *slog.Logger, inPath string) string { + logger.Info("trying to resolve absolute path", "path", inPath) + + if inPath == "$HOME" || strings.HasPrefix(inPath, "$HOME"+string(os.PathSeparator)) { + inPath = userHomeDir() + inPath[5:] + } + + inPath = os.ExpandEnv(inPath) + + if filepath.IsAbs(inPath) { + return filepath.Clean(inPath) + } + + p, err := filepath.Abs(inPath) + if err == nil { + return filepath.Clean(p) + } + + logger.Error(fmt.Errorf("could not discover absolute path: %w", err).Error()) + + return "" +} + +func userHomeDir() string { + if runtime.GOOS == "windows" { + home := os.Getenv("HOMEDRIVE") + os.Getenv("HOMEPATH") + if home == "" { + home = os.Getenv("USERPROFILE") + } + return home + } + return os.Getenv("HOME") +} + +func safeMul(a, b uint) uint { + c := a * b + if a > 1 && b > 1 && c/b != a { + return 0 + } + return c +} + +// parseSizeInBytes converts strings like 1GB or 12 mb into an unsigned integer number of bytes. +func parseSizeInBytes(sizeStr string) uint { + sizeStr = strings.TrimSpace(sizeStr) + lastChar := len(sizeStr) - 1 + multiplier := uint(1) + + if lastChar > 0 { + if sizeStr[lastChar] == 'b' || sizeStr[lastChar] == 'B' { + if lastChar > 1 { + switch unicode.ToLower(rune(sizeStr[lastChar-1])) { + case 'k': + multiplier = 1 << 10 + sizeStr = strings.TrimSpace(sizeStr[:lastChar-1]) + case 'm': + multiplier = 1 << 20 + sizeStr = strings.TrimSpace(sizeStr[:lastChar-1]) + case 'g': + multiplier = 1 << 30 + sizeStr = strings.TrimSpace(sizeStr[:lastChar-1]) + default: + multiplier = 1 + sizeStr = strings.TrimSpace(sizeStr[:lastChar]) + } + } + } + } + + size := max(cast.ToInt(sizeStr), 0) + + return safeMul(uint(size), multiplier) +} + +// deepSearch scans deep maps, following the key indexes listed in the +// sequence "path". +// The last value is expected to be another map, and is returned. +// +// In case intermediate keys do not exist, or map to a non-map value, +// a new map is created and inserted, and the search continues from there: +// the initial map "m" may be modified! +func deepSearch(m map[string]any, path []string) map[string]any { + for _, k := range path { + m2, ok := m[k] + if !ok { + // intermediate key does not exist + // => create it and continue from there + m3 := make(map[string]any) + m[k] = m3 + m = m3 + continue + } + m3, ok := m2.(map[string]any) + if !ok { + // intermediate key is a value + // => replace with a new map + m3 = make(map[string]any) + m[k] = m3 + } + // continue search from here + m = m3 + } + return m +} diff --git a/vendor/github.com/spf13/viper/viper.go b/vendor/github.com/spf13/viper/viper.go new file mode 100644 index 0000000000..34a94798b8 --- /dev/null +++ b/vendor/github.com/spf13/viper/viper.go @@ -0,0 +1,2077 @@ +// Copyright © 2014 Steve Francia . +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file. + +// Viper is an application configuration system. +// It believes that applications can be configured a variety of ways +// via flags, ENVIRONMENT variables, configuration files retrieved +// from the file system, or a remote key/value store. + +// Each item takes precedence over the item below it: + +// overrides +// flag +// env +// config +// key/value store +// default + +package viper + +import ( + "bytes" + "encoding/csv" + "errors" + "fmt" + "io" + "log/slog" + "os" + "path/filepath" + "reflect" + "slices" + "strconv" + "strings" + "sync" + "time" + + "github.com/fsnotify/fsnotify" + "github.com/go-viper/mapstructure/v2" + "github.com/spf13/afero" + "github.com/spf13/cast" + "github.com/spf13/pflag" + + "github.com/spf13/viper/internal/features" +) + +// ConfigMarshalError happens when failing to marshal the configuration. +type ConfigMarshalError struct { + err error +} + +// Error returns the formatted configuration error. +func (e ConfigMarshalError) Error() string { + return fmt.Sprintf("While marshaling config: %s", e.err.Error()) +} + +var v *Viper + +func init() { + v = New() +} + +// UnsupportedConfigError denotes encountering an unsupported +// configuration filetype. +type UnsupportedConfigError string + +// Error returns the formatted configuration error. +func (str UnsupportedConfigError) Error() string { + return fmt.Sprintf("Unsupported Config Type %q", string(str)) +} + +// ConfigFileNotFoundError denotes failing to find configuration file. +type ConfigFileNotFoundError struct { + name, locations string +} + +// Error returns the formatted configuration error. +func (fnfe ConfigFileNotFoundError) Error() string { + return fmt.Sprintf("Config File %q Not Found in %q", fnfe.name, fnfe.locations) +} + +// ConfigFileAlreadyExistsError denotes failure to write new configuration file. +type ConfigFileAlreadyExistsError string + +// Error returns the formatted error when configuration already exists. +func (faee ConfigFileAlreadyExistsError) Error() string { + return fmt.Sprintf("Config File %q Already Exists", string(faee)) +} + +// A DecoderConfigOption can be passed to viper.Unmarshal to configure +// mapstructure.DecoderConfig options. +type DecoderConfigOption func(*mapstructure.DecoderConfig) + +// DecodeHook returns a DecoderConfigOption which overrides the default +// DecoderConfig.DecodeHook value, the default is: +// +// mapstructure.ComposeDecodeHookFunc( +// mapstructure.StringToTimeDurationHookFunc(), +// mapstructure.StringToSliceHookFunc(","), +// ) +func DecodeHook(hook mapstructure.DecodeHookFunc) DecoderConfigOption { + return func(c *mapstructure.DecoderConfig) { + c.DecodeHook = hook + } +} + +// Viper is a prioritized configuration registry. It +// maintains a set of configuration sources, fetches +// values to populate those, and provides them according +// to the source's priority. +// The priority of the sources is the following: +// 1. overrides +// 2. flags +// 3. env. variables +// 4. config file +// 5. key/value store +// 6. defaults +// +// For example, if values from the following sources were loaded: +// +// Defaults : { +// "secret": "", +// "user": "default", +// "endpoint": "https://localhost" +// } +// Config : { +// "user": "root" +// "secret": "defaultsecret" +// } +// Env : { +// "secret": "somesecretkey" +// } +// +// The resulting config will have the following values: +// +// { +// "secret": "somesecretkey", +// "user": "root", +// "endpoint": "https://localhost" +// } +// +// Note: Vipers are not safe for concurrent Get() and Set() operations. +type Viper struct { + // Delimiter that separates a list of keys + // used to access a nested value in one go + keyDelim string + + // A set of paths to look for the config file in + configPaths []string + + // The filesystem to read config from. + fs afero.Fs + + finder Finder + + // A set of remote providers to search for the configuration + remoteProviders []*defaultRemoteProvider + + // Name of file to look for inside the path + configName string + configFile string + configType string + configPermissions os.FileMode + envPrefix string + + automaticEnvApplied bool + envKeyReplacer StringReplacer + allowEmptyEnv bool + + parents []string + config map[string]any + override map[string]any + defaults map[string]any + kvstore map[string]any + pflags map[string]FlagValue + env map[string][]string + aliases map[string]string + typeByDefValue bool + + onConfigChange func(fsnotify.Event) + + logger *slog.Logger + + encoderRegistry EncoderRegistry + decoderRegistry DecoderRegistry + + decodeHook mapstructure.DecodeHookFunc + + experimentalFinder bool + experimentalBindStruct bool +} + +// New returns an initialized Viper instance. +func New() *Viper { + v := new(Viper) + v.keyDelim = "." + v.configName = "config" + v.configPermissions = os.FileMode(0o644) + v.fs = afero.NewOsFs() + v.config = make(map[string]any) + v.parents = []string{} + v.override = make(map[string]any) + v.defaults = make(map[string]any) + v.kvstore = make(map[string]any) + v.pflags = make(map[string]FlagValue) + v.env = make(map[string][]string) + v.aliases = make(map[string]string) + v.typeByDefValue = false + v.logger = slog.New(&discardHandler{}) + + codecRegistry := NewCodecRegistry() + + v.encoderRegistry = codecRegistry + v.decoderRegistry = codecRegistry + + v.experimentalFinder = features.Finder + v.experimentalBindStruct = features.BindStruct + + return v +} + +// Option configures Viper using the functional options paradigm popularized by Rob Pike and Dave Cheney. +// If you're unfamiliar with this style, +// see https://commandcenter.blogspot.com/2014/01/self-referential-functions-and-design.html and +// https://dave.cheney.net/2014/10/17/functional-options-for-friendly-apis. +type Option interface { + apply(v *Viper) +} + +type optionFunc func(v *Viper) + +func (fn optionFunc) apply(v *Viper) { + fn(v) +} + +// KeyDelimiter sets the delimiter used for determining key parts. +// By default it's value is ".". +func KeyDelimiter(d string) Option { + return optionFunc(func(v *Viper) { + v.keyDelim = d + }) +} + +// StringReplacer applies a set of replacements to a string. +type StringReplacer interface { + // Replace returns a copy of s with all replacements performed. + Replace(s string) string +} + +// EnvKeyReplacer sets a replacer used for mapping environment variables to internal keys. +func EnvKeyReplacer(r StringReplacer) Option { + return optionFunc(func(v *Viper) { + if r == nil { + return + } + + v.envKeyReplacer = r + }) +} + +// WithDecodeHook sets a default decode hook for mapstructure. +func WithDecodeHook(h mapstructure.DecodeHookFunc) Option { + return optionFunc(func(v *Viper) { + if h == nil { + return + } + + v.decodeHook = h + }) +} + +// NewWithOptions creates a new Viper instance. +func NewWithOptions(opts ...Option) *Viper { + v := New() + + for _, opt := range opts { + opt.apply(v) + } + + return v +} + +// SetOptions sets the options on the global Viper instance. +// +// Be careful when using this function: subsequent calls may override options you set. +// It's always better to use a local Viper instance. +func SetOptions(opts ...Option) { + for _, opt := range opts { + opt.apply(v) + } +} + +// Reset is intended for testing, will reset all to default settings. +// In the public interface for the viper package so applications +// can use it in their testing as well. +func Reset() { + v = New() + SupportedExts = []string{"json", "toml", "yaml", "yml", "properties", "props", "prop", "hcl", "tfvars", "dotenv", "env", "ini"} + + resetRemote() +} + +// SupportedExts are universally supported extensions. +var SupportedExts = []string{"json", "toml", "yaml", "yml", "properties", "props", "prop", "hcl", "tfvars", "dotenv", "env", "ini"} + +// OnConfigChange sets the event handler that is called when a config file changes. +func OnConfigChange(run func(in fsnotify.Event)) { v.OnConfigChange(run) } + +// OnConfigChange sets the event handler that is called when a config file changes. +func (v *Viper) OnConfigChange(run func(in fsnotify.Event)) { + v.onConfigChange = run +} + +// WatchConfig starts watching a config file for changes. +func WatchConfig() { v.WatchConfig() } + +// WatchConfig starts watching a config file for changes. +func (v *Viper) WatchConfig() { + initWG := sync.WaitGroup{} + initWG.Add(1) + go func() { + watcher, err := fsnotify.NewWatcher() + if err != nil { + v.logger.Error(fmt.Sprintf("failed to create watcher: %s", err)) + os.Exit(1) + } + defer watcher.Close() + // we have to watch the entire directory to pick up renames/atomic saves in a cross-platform way + filename, err := v.getConfigFile() + if err != nil { + v.logger.Error(fmt.Sprintf("get config file: %s", err)) + initWG.Done() + return + } + + configFile := filepath.Clean(filename) + configDir, _ := filepath.Split(configFile) + realConfigFile, _ := filepath.EvalSymlinks(filename) + + eventsWG := sync.WaitGroup{} + eventsWG.Add(1) + go func() { + for { + select { + case event, ok := <-watcher.Events: + if !ok { // 'Events' channel is closed + eventsWG.Done() + return + } + currentConfigFile, _ := filepath.EvalSymlinks(filename) + // we only care about the config file with the following cases: + // 1 - if the config file was modified or created + // 2 - if the real path to the config file changed (eg: k8s ConfigMap replacement) + if (filepath.Clean(event.Name) == configFile && + (event.Has(fsnotify.Write) || event.Has(fsnotify.Create))) || + (currentConfigFile != "" && currentConfigFile != realConfigFile) { + realConfigFile = currentConfigFile + err := v.ReadInConfig() + if err != nil { + v.logger.Error(fmt.Sprintf("read config file: %s", err)) + } + if v.onConfigChange != nil { + v.onConfigChange(event) + } + } else if filepath.Clean(event.Name) == configFile && event.Has(fsnotify.Remove) { + eventsWG.Done() + return + } + + case err, ok := <-watcher.Errors: + if ok { // 'Errors' channel is not closed + v.logger.Error(fmt.Sprintf("watcher error: %s", err)) + } + eventsWG.Done() + return + } + } + }() + err = watcher.Add(configDir) + if err != nil { + v.logger.Error(fmt.Sprintf("failed to add watcher: %s", err)) + initWG.Done() + return + } + initWG.Done() // done initializing the watch in this go routine, so the parent routine can move on... + eventsWG.Wait() // now, wait for event loop to end in this go-routine... + }() + initWG.Wait() // make sure that the go routine above fully ended before returning +} + +// SetConfigFile explicitly defines the path, name and extension of the config file. +// Viper will use this and not check any of the config paths. +func SetConfigFile(in string) { v.SetConfigFile(in) } + +func (v *Viper) SetConfigFile(in string) { + if in != "" { + v.configFile = in + } +} + +// SetEnvPrefix defines a prefix that ENVIRONMENT variables will use. +// E.g. if your prefix is "spf", the env registry will look for env +// variables that start with "SPF_". +func SetEnvPrefix(in string) { v.SetEnvPrefix(in) } + +func (v *Viper) SetEnvPrefix(in string) { + if in != "" { + v.envPrefix = in + } +} + +func GetEnvPrefix() string { return v.GetEnvPrefix() } + +func (v *Viper) GetEnvPrefix() string { + return v.envPrefix +} + +func (v *Viper) mergeWithEnvPrefix(in string) string { + if v.envPrefix != "" { + return strings.ToUpper(v.envPrefix + "_" + in) + } + + return strings.ToUpper(in) +} + +// AllowEmptyEnv tells Viper to consider set, +// but empty environment variables as valid values instead of falling back. +// For backward compatibility reasons this is false by default. +func AllowEmptyEnv(allowEmptyEnv bool) { v.AllowEmptyEnv(allowEmptyEnv) } + +func (v *Viper) AllowEmptyEnv(allowEmptyEnv bool) { + v.allowEmptyEnv = allowEmptyEnv +} + +// TODO: should getEnv logic be moved into find(). Can generalize the use of +// rewriting keys many things, Ex: Get('someKey') -> some_key +// (camel case to snake case for JSON keys perhaps) + +// getEnv is a wrapper around os.Getenv which replaces characters in the original +// key. This allows env vars which have different keys than the config object +// keys. +func (v *Viper) getEnv(key string) (string, bool) { + if v.envKeyReplacer != nil { + key = v.envKeyReplacer.Replace(key) + } + + val, ok := os.LookupEnv(key) + + return val, ok && (v.allowEmptyEnv || val != "") +} + +// ConfigFileUsed returns the file used to populate the config registry. +func ConfigFileUsed() string { return v.ConfigFileUsed() } +func (v *Viper) ConfigFileUsed() string { return v.configFile } + +// AddConfigPath adds a path for Viper to search for the config file in. +// Can be called multiple times to define multiple search paths. +func AddConfigPath(in string) { v.AddConfigPath(in) } + +func (v *Viper) AddConfigPath(in string) { + if v.finder != nil { + v.logger.Warn("ineffective call to function: custom finder takes precedence", slog.String("function", "AddConfigPath")) + } + + if in != "" { + absin := absPathify(v.logger, in) + + v.logger.Info("adding path to search paths", "path", absin) + if !slices.Contains(v.configPaths, absin) { + v.configPaths = append(v.configPaths, absin) + } + } +} + +// searchMap recursively searches for a value for path in source map. +// Returns nil if not found. +// Note: This assumes that the path entries and map keys are lower cased. +func (v *Viper) searchMap(source map[string]any, path []string) any { + if len(path) == 0 { + return source + } + + next, ok := source[path[0]] + if ok { + // Fast path + if len(path) == 1 { + return next + } + + // Nested case + switch next := next.(type) { + case map[any]any: + return v.searchMap(cast.ToStringMap(next), path[1:]) + case map[string]any: + // Type assertion is safe here since it is only reached + // if the type of `next` is the same as the type being asserted + return v.searchMap(next, path[1:]) + default: + // got a value but nested key expected, return "nil" for not found + return nil + } + } + return nil +} + +// searchIndexableWithPathPrefixes recursively searches for a value for path in source map/slice. +// +// While searchMap() considers each path element as a single map key or slice index, this +// function searches for, and prioritizes, merged path elements. +// e.g., if in the source, "foo" is defined with a sub-key "bar", and "foo.bar" +// is also defined, this latter value is returned for path ["foo", "bar"]. +// +// This should be useful only at config level (other maps may not contain dots +// in their keys). +// +// Note: This assumes that the path entries and map keys are lower cased. +func (v *Viper) searchIndexableWithPathPrefixes(source any, path []string) any { + if len(path) == 0 { + return source + } + + // search for path prefixes, starting from the longest one + for i := len(path); i > 0; i-- { + prefixKey := strings.ToLower(strings.Join(path[0:i], v.keyDelim)) + + var val any + switch sourceIndexable := source.(type) { + case []any: + val = v.searchSliceWithPathPrefixes(sourceIndexable, prefixKey, i, path) + case map[string]any: + val = v.searchMapWithPathPrefixes(sourceIndexable, prefixKey, i, path) + } + if val != nil { + return val + } + } + + // not found + return nil +} + +// searchSliceWithPathPrefixes searches for a value for path in sourceSlice +// +// This function is part of the searchIndexableWithPathPrefixes recurring search and +// should not be called directly from functions other than searchIndexableWithPathPrefixes. +func (v *Viper) searchSliceWithPathPrefixes( + sourceSlice []any, + prefixKey string, + pathIndex int, + path []string, +) any { + // if the prefixKey is not a number or it is out of bounds of the slice + index, err := strconv.Atoi(prefixKey) + if err != nil || len(sourceSlice) <= index { + return nil + } + + next := sourceSlice[index] + + // Fast path + if pathIndex == len(path) { + return next + } + + switch n := next.(type) { + case map[any]any: + return v.searchIndexableWithPathPrefixes(cast.ToStringMap(n), path[pathIndex:]) + case map[string]any, []any: + return v.searchIndexableWithPathPrefixes(n, path[pathIndex:]) + default: + // got a value but nested key expected, do nothing and look for next prefix + } + + // not found + return nil +} + +// searchMapWithPathPrefixes searches for a value for path in sourceMap +// +// This function is part of the searchIndexableWithPathPrefixes recurring search and +// should not be called directly from functions other than searchIndexableWithPathPrefixes. +func (v *Viper) searchMapWithPathPrefixes( + sourceMap map[string]any, + prefixKey string, + pathIndex int, + path []string, +) any { + next, ok := sourceMap[prefixKey] + if !ok { + return nil + } + + // Fast path + if pathIndex == len(path) { + return next + } + + // Nested case + switch n := next.(type) { + case map[any]any: + return v.searchIndexableWithPathPrefixes(cast.ToStringMap(n), path[pathIndex:]) + case map[string]any, []any: + return v.searchIndexableWithPathPrefixes(n, path[pathIndex:]) + default: + // got a value but nested key expected, do nothing and look for next prefix + } + + // not found + return nil +} + +// isPathShadowedInDeepMap makes sure the given path is not shadowed somewhere +// on its path in the map. +// e.g., if "foo.bar" has a value in the given map, it “shadows” +// +// "foo.bar.baz" in a lower-priority map +func (v *Viper) isPathShadowedInDeepMap(path []string, m map[string]any) string { + var parentVal any + for i := 1; i < len(path); i++ { + parentVal = v.searchMap(m, path[0:i]) + if parentVal == nil { + // not found, no need to add more path elements + return "" + } + switch parentVal.(type) { + case map[any]any: + continue + case map[string]any: + continue + default: + // parentVal is a regular value which shadows "path" + return strings.Join(path[0:i], v.keyDelim) + } + } + return "" +} + +// isPathShadowedInFlatMap makes sure the given path is not shadowed somewhere +// in a sub-path of the map. +// e.g., if "foo.bar" has a value in the given map, it “shadows” +// +// "foo.bar.baz" in a lower-priority map +func (v *Viper) isPathShadowedInFlatMap(path []string, mi any) string { + // unify input map + var m map[string]interface{} + switch miv := mi.(type) { + case map[string]string: + m = castMapStringToMapInterface(miv) + case map[string]FlagValue: + m = castMapFlagToMapInterface(miv) + default: + return "" + } + + // scan paths + var parentKey string + for i := 1; i < len(path); i++ { + parentKey = strings.Join(path[0:i], v.keyDelim) + if _, ok := m[parentKey]; ok { + return parentKey + } + } + return "" +} + +// isPathShadowedInAutoEnv makes sure the given path is not shadowed somewhere +// in the environment, when automatic env is on. +// e.g., if "foo.bar" has a value in the environment, it “shadows” +// +// "foo.bar.baz" in a lower-priority map +func (v *Viper) isPathShadowedInAutoEnv(path []string) string { + var parentKey string + for i := 1; i < len(path); i++ { + parentKey = strings.Join(path[0:i], v.keyDelim) + if _, ok := v.getEnv(v.mergeWithEnvPrefix(parentKey)); ok { + return parentKey + } + } + return "" +} + +// SetTypeByDefaultValue enables or disables the inference of a key value's +// type when the Get function is used based upon a key's default value as +// opposed to the value returned based on the normal fetch logic. +// +// For example, if a key has a default value of []string{} and the same key +// is set via an environment variable to "a b c", a call to the Get function +// would return a string slice for the key if the key's type is inferred by +// the default value and the Get function would return: +// +// []string {"a", "b", "c"} +// +// Otherwise the Get function would return: +// +// "a b c" +func SetTypeByDefaultValue(enable bool) { v.SetTypeByDefaultValue(enable) } + +func (v *Viper) SetTypeByDefaultValue(enable bool) { + v.typeByDefValue = enable +} + +// GetViper gets the global Viper instance. +func GetViper() *Viper { + return v +} + +// Get can retrieve any value given the key to use. +// Get is case-insensitive for a key. +// Get has the behavior of returning the value associated with the first +// place from where it is set. Viper will check in the following order: +// override, flag, env, config file, key/value store, default +// +// Get returns an interface. For a specific value use one of the Get____ methods. +func Get(key string) any { return v.Get(key) } + +func (v *Viper) Get(key string) any { + lcaseKey := strings.ToLower(key) + val := v.find(lcaseKey, true) + if val == nil { + return nil + } + + if v.typeByDefValue { + // TODO(bep) this branch isn't covered by a single test. + valType := val + path := strings.Split(lcaseKey, v.keyDelim) + defVal := v.searchMap(v.defaults, path) + if defVal != nil { + valType = defVal + } + + switch valType.(type) { + case bool: + return cast.ToBool(val) + case string: + return cast.ToString(val) + case int32, int16, int8, int: + return cast.ToInt(val) + case uint: + return cast.ToUint(val) + case uint32: + return cast.ToUint32(val) + case uint64: + return cast.ToUint64(val) + case int64: + return cast.ToInt64(val) + case float64, float32: + return cast.ToFloat64(val) + case time.Time: + return cast.ToTime(val) + case time.Duration: + return cast.ToDuration(val) + case []string: + return cast.ToStringSlice(val) + case []int: + return cast.ToIntSlice(val) + case []time.Duration: + return cast.ToDurationSlice(val) + } + } + + return val +} + +// Sub returns new Viper instance representing a sub tree of this instance. +// Sub is case-insensitive for a key. +func Sub(key string) *Viper { return v.Sub(key) } + +func (v *Viper) Sub(key string) *Viper { + subv := New() + data := v.Get(key) + if data == nil { + return nil + } + + if reflect.TypeOf(data).Kind() == reflect.Map { + subv.parents = append([]string(nil), v.parents...) + subv.parents = append(subv.parents, strings.ToLower(key)) + subv.automaticEnvApplied = v.automaticEnvApplied + subv.envPrefix = v.envPrefix + subv.envKeyReplacer = v.envKeyReplacer + subv.keyDelim = v.keyDelim + subv.config = cast.ToStringMap(data) + return subv + } + return nil +} + +// GetString returns the value associated with the key as a string. +func GetString(key string) string { return v.GetString(key) } + +func (v *Viper) GetString(key string) string { + return cast.ToString(v.Get(key)) +} + +// GetBool returns the value associated with the key as a boolean. +func GetBool(key string) bool { return v.GetBool(key) } + +func (v *Viper) GetBool(key string) bool { + return cast.ToBool(v.Get(key)) +} + +// GetInt returns the value associated with the key as an integer. +func GetInt(key string) int { return v.GetInt(key) } + +func (v *Viper) GetInt(key string) int { + return cast.ToInt(v.Get(key)) +} + +// GetInt32 returns the value associated with the key as an integer. +func GetInt32(key string) int32 { return v.GetInt32(key) } + +func (v *Viper) GetInt32(key string) int32 { + return cast.ToInt32(v.Get(key)) +} + +// GetInt64 returns the value associated with the key as an integer. +func GetInt64(key string) int64 { return v.GetInt64(key) } + +func (v *Viper) GetInt64(key string) int64 { + return cast.ToInt64(v.Get(key)) +} + +// GetUint8 returns the value associated with the key as an unsigned integer. +func GetUint8(key string) uint8 { return v.GetUint8(key) } + +func (v *Viper) GetUint8(key string) uint8 { + return cast.ToUint8(v.Get(key)) +} + +// GetUint returns the value associated with the key as an unsigned integer. +func GetUint(key string) uint { return v.GetUint(key) } + +func (v *Viper) GetUint(key string) uint { + return cast.ToUint(v.Get(key)) +} + +// GetUint16 returns the value associated with the key as an unsigned integer. +func GetUint16(key string) uint16 { return v.GetUint16(key) } + +func (v *Viper) GetUint16(key string) uint16 { + return cast.ToUint16(v.Get(key)) +} + +// GetUint32 returns the value associated with the key as an unsigned integer. +func GetUint32(key string) uint32 { return v.GetUint32(key) } + +func (v *Viper) GetUint32(key string) uint32 { + return cast.ToUint32(v.Get(key)) +} + +// GetUint64 returns the value associated with the key as an unsigned integer. +func GetUint64(key string) uint64 { return v.GetUint64(key) } + +func (v *Viper) GetUint64(key string) uint64 { + return cast.ToUint64(v.Get(key)) +} + +// GetFloat64 returns the value associated with the key as a float64. +func GetFloat64(key string) float64 { return v.GetFloat64(key) } + +func (v *Viper) GetFloat64(key string) float64 { + return cast.ToFloat64(v.Get(key)) +} + +// GetTime returns the value associated with the key as time. +func GetTime(key string) time.Time { return v.GetTime(key) } + +func (v *Viper) GetTime(key string) time.Time { + return cast.ToTime(v.Get(key)) +} + +// GetDuration returns the value associated with the key as a duration. +func GetDuration(key string) time.Duration { return v.GetDuration(key) } + +func (v *Viper) GetDuration(key string) time.Duration { + return cast.ToDuration(v.Get(key)) +} + +// GetIntSlice returns the value associated with the key as a slice of int values. +func GetIntSlice(key string) []int { return v.GetIntSlice(key) } + +func (v *Viper) GetIntSlice(key string) []int { + return cast.ToIntSlice(v.Get(key)) +} + +// GetStringSlice returns the value associated with the key as a slice of strings. +func GetStringSlice(key string) []string { return v.GetStringSlice(key) } + +func (v *Viper) GetStringSlice(key string) []string { + return cast.ToStringSlice(v.Get(key)) +} + +// GetStringMap returns the value associated with the key as a map of interfaces. +func GetStringMap(key string) map[string]any { return v.GetStringMap(key) } + +func (v *Viper) GetStringMap(key string) map[string]any { + return cast.ToStringMap(v.Get(key)) +} + +// GetStringMapString returns the value associated with the key as a map of strings. +func GetStringMapString(key string) map[string]string { return v.GetStringMapString(key) } + +func (v *Viper) GetStringMapString(key string) map[string]string { + return cast.ToStringMapString(v.Get(key)) +} + +// GetStringMapStringSlice returns the value associated with the key as a map to a slice of strings. +func GetStringMapStringSlice(key string) map[string][]string { return v.GetStringMapStringSlice(key) } + +func (v *Viper) GetStringMapStringSlice(key string) map[string][]string { + return cast.ToStringMapStringSlice(v.Get(key)) +} + +// GetSizeInBytes returns the size of the value associated with the given key +// in bytes. +func GetSizeInBytes(key string) uint { return v.GetSizeInBytes(key) } + +func (v *Viper) GetSizeInBytes(key string) uint { + sizeStr := cast.ToString(v.Get(key)) + return parseSizeInBytes(sizeStr) +} + +// UnmarshalKey takes a single key and unmarshals it into a Struct. +func UnmarshalKey(key string, rawVal any, opts ...DecoderConfigOption) error { + return v.UnmarshalKey(key, rawVal, opts...) +} + +func (v *Viper) UnmarshalKey(key string, rawVal any, opts ...DecoderConfigOption) error { + return decode(v.Get(key), v.defaultDecoderConfig(rawVal, opts...)) +} + +// Unmarshal unmarshals the config into a Struct. Make sure that the tags +// on the fields of the structure are properly set. +func Unmarshal(rawVal any, opts ...DecoderConfigOption) error { + return v.Unmarshal(rawVal, opts...) +} + +func (v *Viper) Unmarshal(rawVal any, opts ...DecoderConfigOption) error { + keys := v.AllKeys() + + if v.experimentalBindStruct { + // TODO: make this optional? + structKeys, err := v.decodeStructKeys(rawVal, opts...) + if err != nil { + return err + } + + keys = append(keys, structKeys...) + } + + // TODO: struct keys should be enough? + return decode(v.getSettings(keys), v.defaultDecoderConfig(rawVal, opts...)) +} + +func (v *Viper) decodeStructKeys(input any, opts ...DecoderConfigOption) ([]string, error) { + var structKeyMap map[string]any + + err := decode(input, v.defaultDecoderConfig(&structKeyMap, opts...)) + if err != nil { + return nil, err + } + + flattenedStructKeyMap := v.flattenAndMergeMap(map[string]bool{}, structKeyMap, "") + + r := make([]string, 0, len(flattenedStructKeyMap)) + for v := range flattenedStructKeyMap { + r = append(r, v) + } + + return r, nil +} + +// defaultDecoderConfig returns default mapstructure.DecoderConfig with support +// of time.Duration values & string slices. +func (v *Viper) defaultDecoderConfig(output any, opts ...DecoderConfigOption) *mapstructure.DecoderConfig { + decodeHook := v.decodeHook + if decodeHook == nil { + decodeHook = mapstructure.ComposeDecodeHookFunc( + mapstructure.StringToTimeDurationHookFunc(), + // mapstructure.StringToSliceHookFunc(","), + stringToWeakSliceHookFunc(","), + ) + } + + c := &mapstructure.DecoderConfig{ + Metadata: nil, + WeaklyTypedInput: true, + DecodeHook: decodeHook, + } + + for _, opt := range opts { + opt(c) + } + + // Do not allow overwriting the output + c.Result = output + + return c +} + +// As of mapstructure v2.0.0 StringToSliceHookFunc checks if the return type is a string slice. +// This function removes that check. +// TODO: implement a function that checks if the value can be converted to the return type and use it instead. +func stringToWeakSliceHookFunc(sep string) mapstructure.DecodeHookFunc { + return func( + f reflect.Type, + t reflect.Type, + data interface{}, + ) (interface{}, error) { + if f.Kind() != reflect.String || t.Kind() != reflect.Slice { + return data, nil + } + + raw := data.(string) + if raw == "" { + return []string{}, nil + } + + return strings.Split(raw, sep), nil + } +} + +// decode is a wrapper around mapstructure.Decode that mimics the WeakDecode functionality. +func decode(input any, config *mapstructure.DecoderConfig) error { + decoder, err := mapstructure.NewDecoder(config) + if err != nil { + return err + } + return decoder.Decode(input) +} + +// UnmarshalExact unmarshals the config into a Struct, erroring if a field is nonexistent +// in the destination struct. +func UnmarshalExact(rawVal any, opts ...DecoderConfigOption) error { + return v.UnmarshalExact(rawVal, opts...) +} + +func (v *Viper) UnmarshalExact(rawVal any, opts ...DecoderConfigOption) error { + config := v.defaultDecoderConfig(rawVal, opts...) + config.ErrorUnused = true + + keys := v.AllKeys() + + if v.experimentalBindStruct { + // TODO: make this optional? + structKeys, err := v.decodeStructKeys(rawVal, opts...) + if err != nil { + return err + } + + keys = append(keys, structKeys...) + } + + // TODO: struct keys should be enough? + return decode(v.getSettings(keys), config) +} + +// BindPFlags binds a full flag set to the configuration, using each flag's long +// name as the config key. +func BindPFlags(flags *pflag.FlagSet) error { return v.BindPFlags(flags) } + +func (v *Viper) BindPFlags(flags *pflag.FlagSet) error { + return v.BindFlagValues(pflagValueSet{flags}) +} + +// BindPFlag binds a specific key to a pflag (as used by cobra). +// Example (where serverCmd is a Cobra instance): +// +// serverCmd.Flags().Int("port", 1138, "Port to run Application server on") +// Viper.BindPFlag("port", serverCmd.Flags().Lookup("port")) +func BindPFlag(key string, flag *pflag.Flag) error { return v.BindPFlag(key, flag) } + +func (v *Viper) BindPFlag(key string, flag *pflag.Flag) error { + if flag == nil { + return fmt.Errorf("flag for %q is nil", key) + } + return v.BindFlagValue(key, pflagValue{flag}) +} + +// BindFlagValues binds a full FlagValue set to the configuration, using each flag's long +// name as the config key. +func BindFlagValues(flags FlagValueSet) error { return v.BindFlagValues(flags) } + +func (v *Viper) BindFlagValues(flags FlagValueSet) (err error) { + flags.VisitAll(func(flag FlagValue) { + if err = v.BindFlagValue(flag.Name(), flag); err != nil { + return + } + }) + return nil +} + +// BindFlagValue binds a specific key to a FlagValue. +func BindFlagValue(key string, flag FlagValue) error { return v.BindFlagValue(key, flag) } + +func (v *Viper) BindFlagValue(key string, flag FlagValue) error { + if flag == nil { + return fmt.Errorf("flag for %q is nil", key) + } + v.pflags[strings.ToLower(key)] = flag + return nil +} + +// BindEnv binds a Viper key to a ENV variable. +// ENV variables are case sensitive. +// If only a key is provided, it will use the env key matching the key, uppercased. +// If more arguments are provided, they will represent the env variable names that +// should bind to this key and will be taken in the specified order. +// EnvPrefix will be used when set when env name is not provided. +func BindEnv(input ...string) error { return v.BindEnv(input...) } + +func (v *Viper) BindEnv(input ...string) error { + if len(input) == 0 { + return fmt.Errorf("missing key to bind to") + } + + key := strings.ToLower(input[0]) + + if len(input) == 1 { + v.env[key] = append(v.env[key], v.mergeWithEnvPrefix(key)) + } else { + v.env[key] = append(v.env[key], input[1:]...) + } + + return nil +} + +// MustBindEnv wraps BindEnv in a panic. +// If there is an error binding an environment variable, MustBindEnv will +// panic. +func MustBindEnv(input ...string) { v.MustBindEnv(input...) } + +func (v *Viper) MustBindEnv(input ...string) { + if err := v.BindEnv(input...); err != nil { + panic(fmt.Sprintf("error while binding environment variable: %v", err)) + } +} + +// Given a key, find the value. +// +// Viper will check to see if an alias exists first. +// Viper will then check in the following order: +// flag, env, config file, key/value store. +// Lastly, if no value was found and flagDefault is true, and if the key +// corresponds to a flag, the flag's default value is returned. +// +// Note: this assumes a lower-cased key given. +func (v *Viper) find(lcaseKey string, flagDefault bool) any { + var ( + val any + exists bool + path = strings.Split(lcaseKey, v.keyDelim) + nested = len(path) > 1 + ) + + // compute the path through the nested maps to the nested value + if nested && v.isPathShadowedInDeepMap(path, castMapStringToMapInterface(v.aliases)) != "" { + return nil + } + + // if the requested key is an alias, then return the proper key + lcaseKey = v.realKey(lcaseKey) + path = strings.Split(lcaseKey, v.keyDelim) + nested = len(path) > 1 + + // Set() override first + val = v.searchMap(v.override, path) + if val != nil { + return val + } + if nested && v.isPathShadowedInDeepMap(path, v.override) != "" { + return nil + } + + // PFlag override next + flag, exists := v.pflags[lcaseKey] + if exists && flag.HasChanged() { + switch flag.ValueType() { + case "int", "int8", "int16", "int32", "int64": + return cast.ToInt(flag.ValueString()) + case "bool": + return cast.ToBool(flag.ValueString()) + case "stringSlice", "stringArray": + s := strings.TrimPrefix(flag.ValueString(), "[") + s = strings.TrimSuffix(s, "]") + res, _ := readAsCSV(s) + return res + case "boolSlice": + s := strings.TrimPrefix(flag.ValueString(), "[") + s = strings.TrimSuffix(s, "]") + res, _ := readAsCSV(s) + return cast.ToBoolSlice(res) + case "intSlice": + s := strings.TrimPrefix(flag.ValueString(), "[") + s = strings.TrimSuffix(s, "]") + res, _ := readAsCSV(s) + return cast.ToIntSlice(res) + case "uintSlice": + s := strings.TrimPrefix(flag.ValueString(), "[") + s = strings.TrimSuffix(s, "]") + res, _ := readAsCSV(s) + return cast.ToUintSlice(res) + case "float64Slice": + s := strings.TrimPrefix(flag.ValueString(), "[") + s = strings.TrimSuffix(s, "]") + res, _ := readAsCSV(s) + return cast.ToFloat64Slice(res) + case "durationSlice": + s := strings.TrimPrefix(flag.ValueString(), "[") + s = strings.TrimSuffix(s, "]") + slice := strings.Split(s, ",") + return cast.ToDurationSlice(slice) + case "stringToString": + return stringToStringConv(flag.ValueString()) + case "stringToInt": + return stringToIntConv(flag.ValueString()) + default: + return flag.ValueString() + } + } + if nested && v.isPathShadowedInFlatMap(path, v.pflags) != "" { + return nil + } + + // Env override next + if v.automaticEnvApplied { + envKey := strings.Join(append(v.parents, lcaseKey), ".") + // even if it hasn't been registered, if automaticEnv is used, + // check any Get request + if val, ok := v.getEnv(v.mergeWithEnvPrefix(envKey)); ok { + return val + } + if nested && v.isPathShadowedInAutoEnv(path) != "" { + return nil + } + } + envkeys, exists := v.env[lcaseKey] + if exists { + for _, envkey := range envkeys { + if val, ok := v.getEnv(envkey); ok { + return val + } + } + } + if nested && v.isPathShadowedInFlatMap(path, v.env) != "" { + return nil + } + + // Config file next + val = v.searchIndexableWithPathPrefixes(v.config, path) + if val != nil { + return val + } + if nested && v.isPathShadowedInDeepMap(path, v.config) != "" { + return nil + } + + // K/V store next + val = v.searchMap(v.kvstore, path) + if val != nil { + return val + } + if nested && v.isPathShadowedInDeepMap(path, v.kvstore) != "" { + return nil + } + + // Default next + val = v.searchMap(v.defaults, path) + if val != nil { + return val + } + if nested && v.isPathShadowedInDeepMap(path, v.defaults) != "" { + return nil + } + + if flagDefault { + // last chance: if no value is found and a flag does exist for the key, + // get the flag's default value even if the flag's value has not been set. + if flag, exists := v.pflags[lcaseKey]; exists { + switch flag.ValueType() { + case "int", "int8", "int16", "int32", "int64": + return cast.ToInt(flag.ValueString()) + case "bool": + return cast.ToBool(flag.ValueString()) + case "stringSlice", "stringArray": + s := strings.TrimPrefix(flag.ValueString(), "[") + s = strings.TrimSuffix(s, "]") + res, _ := readAsCSV(s) + return res + case "boolSlice": + s := strings.TrimPrefix(flag.ValueString(), "[") + s = strings.TrimSuffix(s, "]") + res, _ := readAsCSV(s) + return cast.ToBoolSlice(res) + case "intSlice": + s := strings.TrimPrefix(flag.ValueString(), "[") + s = strings.TrimSuffix(s, "]") + res, _ := readAsCSV(s) + return cast.ToIntSlice(res) + case "uintSlice": + s := strings.TrimPrefix(flag.ValueString(), "[") + s = strings.TrimSuffix(s, "]") + res, _ := readAsCSV(s) + return cast.ToUintSlice(res) + case "float64Slice": + s := strings.TrimPrefix(flag.ValueString(), "[") + s = strings.TrimSuffix(s, "]") + res, _ := readAsCSV(s) + return cast.ToFloat64Slice(res) + case "stringToString": + return stringToStringConv(flag.ValueString()) + case "stringToInt": + return stringToIntConv(flag.ValueString()) + case "durationSlice": + s := strings.TrimPrefix(flag.ValueString(), "[") + s = strings.TrimSuffix(s, "]") + slice := strings.Split(s, ",") + return cast.ToDurationSlice(slice) + default: + return flag.ValueString() + } + } + // last item, no need to check shadowing + } + + return nil +} + +func readAsCSV(val string) ([]string, error) { + if val == "" { + return []string{}, nil + } + stringReader := strings.NewReader(val) + csvReader := csv.NewReader(stringReader) + return csvReader.Read() +} + +// mostly copied from pflag's implementation of this operation here https://github.com/spf13/pflag/blob/master/string_to_string.go#L79 +// alterations are: errors are swallowed, map[string]any is returned in order to enable cast.ToStringMap. +func stringToStringConv(val string) any { + val = strings.Trim(val, "[]") + // An empty string would cause an empty map + if val == "" { + return map[string]any{} + } + r := csv.NewReader(strings.NewReader(val)) + ss, err := r.Read() + if err != nil { + return nil + } + out := make(map[string]any, len(ss)) + for _, pair := range ss { + k, vv, found := strings.Cut(pair, "=") + if !found { + return nil + } + out[k] = vv + } + return out +} + +// mostly copied from pflag's implementation of this operation here https://github.com/spf13/pflag/blob/d5e0c0615acee7028e1e2740a11102313be88de1/string_to_int.go#L68 +// alterations are: errors are swallowed, map[string]any is returned in order to enable cast.ToStringMap. +func stringToIntConv(val string) any { + val = strings.Trim(val, "[]") + // An empty string would cause an empty map + if val == "" { + return map[string]any{} + } + ss := strings.Split(val, ",") + out := make(map[string]any, len(ss)) + for _, pair := range ss { + k, vv, found := strings.Cut(pair, "=") + if !found { + return nil + } + var err error + out[k], err = strconv.Atoi(vv) + if err != nil { + return nil + } + } + return out +} + +// IsSet checks to see if the key has been set in any of the data locations. +// IsSet is case-insensitive for a key. +func IsSet(key string) bool { return v.IsSet(key) } + +func (v *Viper) IsSet(key string) bool { + lcaseKey := strings.ToLower(key) + val := v.find(lcaseKey, false) + return val != nil +} + +// AutomaticEnv makes Viper check if environment variables match any of the existing keys +// (config, default or flags). If matching env vars are found, they are loaded into Viper. +func AutomaticEnv() { v.AutomaticEnv() } + +func (v *Viper) AutomaticEnv() { + v.automaticEnvApplied = true +} + +// SetEnvKeyReplacer sets the strings.Replacer on the viper object +// Useful for mapping an environmental variable to a key that does +// not match it. +func SetEnvKeyReplacer(r *strings.Replacer) { v.SetEnvKeyReplacer(r) } + +func (v *Viper) SetEnvKeyReplacer(r *strings.Replacer) { + v.envKeyReplacer = r +} + +// RegisterAlias creates an alias that provides another accessor for the same key. +// This enables one to change a name without breaking the application. +func RegisterAlias(alias, key string) { v.RegisterAlias(alias, key) } + +func (v *Viper) RegisterAlias(alias, key string) { + v.registerAlias(alias, strings.ToLower(key)) +} + +func (v *Viper) registerAlias(alias, key string) { + alias = strings.ToLower(alias) + if alias != key && alias != v.realKey(key) { + _, exists := v.aliases[alias] + + if !exists { + // if we alias something that exists in one of the maps to another + // name, we'll never be able to get that value using the original + // name, so move the config value to the new realkey. + if val, ok := v.config[alias]; ok { + delete(v.config, alias) + v.config[key] = val + } + if val, ok := v.kvstore[alias]; ok { + delete(v.kvstore, alias) + v.kvstore[key] = val + } + if val, ok := v.defaults[alias]; ok { + delete(v.defaults, alias) + v.defaults[key] = val + } + if val, ok := v.override[alias]; ok { + delete(v.override, alias) + v.override[key] = val + } + v.aliases[alias] = key + } + } else { + v.logger.Warn("creating circular reference alias", "alias", alias, "key", key, "real_key", v.realKey(key)) + } +} + +func (v *Viper) realKey(key string) string { + newkey, exists := v.aliases[key] + if exists { + v.logger.Debug("key is an alias", "alias", key, "to", newkey) + + return v.realKey(newkey) + } + return key +} + +// InConfig checks to see if the given key (or an alias) is in the config file. +func InConfig(key string) bool { return v.InConfig(key) } + +func (v *Viper) InConfig(key string) bool { + lcaseKey := strings.ToLower(key) + + // if the requested key is an alias, then return the proper key + lcaseKey = v.realKey(lcaseKey) + path := strings.Split(lcaseKey, v.keyDelim) + + return v.searchIndexableWithPathPrefixes(v.config, path) != nil +} + +// SetDefault sets the default value for this key. +// SetDefault is case-insensitive for a key. +// Default only used when no value is provided by the user via flag, config or ENV. +func SetDefault(key string, value any) { v.SetDefault(key, value) } + +func (v *Viper) SetDefault(key string, value any) { + // If alias passed in, then set the proper default + key = v.realKey(strings.ToLower(key)) + value = toCaseInsensitiveValue(value) + + path := strings.Split(key, v.keyDelim) + lastKey := strings.ToLower(path[len(path)-1]) + deepestMap := deepSearch(v.defaults, path[0:len(path)-1]) + + // set innermost value + deepestMap[lastKey] = value +} + +// Set sets the value for the key in the override register. +// Set is case-insensitive for a key. +// Will be used instead of values obtained via +// flags, config file, ENV, default, or key/value store. +func Set(key string, value any) { v.Set(key, value) } + +func (v *Viper) Set(key string, value any) { + // If alias passed in, then set the proper override + key = v.realKey(strings.ToLower(key)) + value = toCaseInsensitiveValue(value) + + path := strings.Split(key, v.keyDelim) + lastKey := strings.ToLower(path[len(path)-1]) + deepestMap := deepSearch(v.override, path[0:len(path)-1]) + + // set innermost value + deepestMap[lastKey] = value +} + +// ReadInConfig will discover and load the configuration file from disk +// and key/value stores, searching in one of the defined paths. +func ReadInConfig() error { return v.ReadInConfig() } + +func (v *Viper) ReadInConfig() error { + v.logger.Info("attempting to read in config file") + filename, err := v.getConfigFile() + if err != nil { + return err + } + + if !slices.Contains(SupportedExts, v.getConfigType()) { + return UnsupportedConfigError(v.getConfigType()) + } + + v.logger.Debug("reading file", "file", filename) + file, err := afero.ReadFile(v.fs, filename) + if err != nil { + return err + } + + config := make(map[string]any) + + err = v.unmarshalReader(bytes.NewReader(file), config) + if err != nil { + return err + } + + v.config = config + return nil +} + +// MergeInConfig merges a new configuration with an existing config. +func MergeInConfig() error { return v.MergeInConfig() } + +func (v *Viper) MergeInConfig() error { + v.logger.Info("attempting to merge in config file") + filename, err := v.getConfigFile() + if err != nil { + return err + } + + if !slices.Contains(SupportedExts, v.getConfigType()) { + return UnsupportedConfigError(v.getConfigType()) + } + + file, err := afero.ReadFile(v.fs, filename) + if err != nil { + return err + } + + return v.MergeConfig(bytes.NewReader(file)) +} + +// ReadConfig will read a configuration file, setting existing keys to nil if the +// key does not exist in the file. +func ReadConfig(in io.Reader) error { return v.ReadConfig(in) } + +func (v *Viper) ReadConfig(in io.Reader) error { + config := make(map[string]any) + + err := v.unmarshalReader(in, config) + if err != nil { + return err + } + + v.config = config + + return nil +} + +// MergeConfig merges a new configuration with an existing config. +func MergeConfig(in io.Reader) error { return v.MergeConfig(in) } + +func (v *Viper) MergeConfig(in io.Reader) error { + config := make(map[string]any) + + if err := v.unmarshalReader(in, config); err != nil { + return err + } + + return v.MergeConfigMap(config) +} + +// MergeConfigMap merges the configuration from the map given with an existing config. +// Note that the map given may be modified. +func MergeConfigMap(cfg map[string]any) error { return v.MergeConfigMap(cfg) } + +func (v *Viper) MergeConfigMap(cfg map[string]any) error { + if v.config == nil { + v.config = make(map[string]any) + } + insensitiviseMap(cfg) + mergeMaps(cfg, v.config, nil) + return nil +} + +// WriteConfig writes the current configuration to a file. +func WriteConfig() error { return v.WriteConfig() } + +func (v *Viper) WriteConfig() error { + filename, err := v.getConfigFile() + if err != nil { + return err + } + return v.writeConfig(filename, true) +} + +// SafeWriteConfig writes current configuration to file only if the file does not exist. +func SafeWriteConfig() error { return v.SafeWriteConfig() } + +func (v *Viper) SafeWriteConfig() error { + if len(v.configPaths) < 1 { + return errors.New("missing configuration for 'configPath'") + } + return v.SafeWriteConfigAs(filepath.Join(v.configPaths[0], v.configName+"."+v.configType)) +} + +// WriteConfigAs writes current configuration to a given filename. +func WriteConfigAs(filename string) error { return v.WriteConfigAs(filename) } + +func (v *Viper) WriteConfigAs(filename string) error { + return v.writeConfig(filename, true) +} + +// WriteConfigTo writes current configuration to an [io.Writer]. +func WriteConfigTo(w io.Writer) error { return v.WriteConfigTo(w) } + +func (v *Viper) WriteConfigTo(w io.Writer) error { + format := strings.ToLower(v.getConfigType()) + + if !slices.Contains(SupportedExts, format) { + return UnsupportedConfigError(format) + } + + return v.marshalWriter(w, format) +} + +// SafeWriteConfigAs writes current configuration to a given filename if it does not exist. +func SafeWriteConfigAs(filename string) error { return v.SafeWriteConfigAs(filename) } + +func (v *Viper) SafeWriteConfigAs(filename string) error { + alreadyExists, err := afero.Exists(v.fs, filename) + if alreadyExists && err == nil { + return ConfigFileAlreadyExistsError(filename) + } + return v.writeConfig(filename, false) +} + +func (v *Viper) writeConfig(filename string, force bool) error { + v.logger.Info("attempting to write configuration to file") + + var configType string + + ext := filepath.Ext(filename) + if ext != "" && ext != filepath.Base(filename) { + configType = ext[1:] + } else { + configType = v.configType + } + if configType == "" { + return fmt.Errorf("config type could not be determined for %s", filename) + } + + if !slices.Contains(SupportedExts, configType) { + return UnsupportedConfigError(configType) + } + if v.config == nil { + v.config = make(map[string]any) + } + flags := os.O_CREATE | os.O_TRUNC | os.O_WRONLY + if !force { + flags |= os.O_EXCL + } + f, err := v.fs.OpenFile(filename, flags, v.configPermissions) + if err != nil { + return err + } + defer f.Close() + + if err := v.marshalWriter(f, configType); err != nil { + return err + } + + return f.Sync() +} + +func (v *Viper) unmarshalReader(in io.Reader, c map[string]any) error { + format := strings.ToLower(v.getConfigType()) + if format == "" { + return errors.New("cannot decode configuration: unable to determine config type") + } + + buf := new(bytes.Buffer) + _, err := buf.ReadFrom(in) + if err != nil { + return fmt.Errorf("failed to read configuration from input: %w", err) + } + + // TODO: remove this once SupportedExts is deprecated/removed + if !slices.Contains(SupportedExts, format) { + return UnsupportedConfigError(format) + } + + // TODO: return [UnsupportedConfigError] if the registry does not contain the format + // TODO: consider deprecating this error type + decoder, err := v.decoderRegistry.Decoder(format) + if err != nil { + return ConfigParseError{err} + } + + err = decoder.Decode(buf.Bytes(), c) + if err != nil { + return ConfigParseError{err} + } + + insensitiviseMap(c) + return nil +} + +// Marshal a map into Writer. +func (v *Viper) marshalWriter(w io.Writer, configType string) error { + c := v.AllSettings() + + encoder, err := v.encoderRegistry.Encoder(configType) + if err != nil { + return ConfigMarshalError{err} + } + + b, err := encoder.Encode(c) + if err != nil { + return ConfigMarshalError{err} + } + + _, err = w.Write(b) + if err != nil { + return ConfigMarshalError{err} + } + + return nil +} + +func keyExists(k string, m map[string]any) string { + lk := strings.ToLower(k) + for mk := range m { + lmk := strings.ToLower(mk) + if lmk == lk { + return mk + } + } + return "" +} + +func castToMapStringInterface( + src map[any]any, +) map[string]any { + tgt := map[string]any{} + for k, v := range src { + tgt[fmt.Sprintf("%v", k)] = v + } + return tgt +} + +func castMapStringSliceToMapInterface(src map[string][]string) map[string]any { + tgt := map[string]any{} + for k, v := range src { + tgt[k] = v + } + return tgt +} + +func castMapStringToMapInterface(src map[string]string) map[string]any { + tgt := map[string]any{} + for k, v := range src { + tgt[k] = v + } + return tgt +} + +func castMapFlagToMapInterface(src map[string]FlagValue) map[string]any { + tgt := map[string]any{} + for k, v := range src { + tgt[k] = v + } + return tgt +} + +// mergeMaps merges two maps. The `itgt` parameter is for handling go-yaml's +// insistence on parsing nested structures as `map[any]any` +// instead of using a `string` as the key for nest structures beyond one level +// deep. Both map types are supported as there is a go-yaml fork that uses +// `map[string]any` instead. +func mergeMaps(src, tgt map[string]any, itgt map[any]any) { + for sk, sv := range src { + tk := keyExists(sk, tgt) + if tk == "" { + v.logger.Debug("", "tk", "\"\"", fmt.Sprintf("tgt[%s]", sk), sv) + tgt[sk] = sv + if itgt != nil { + itgt[sk] = sv + } + continue + } + + tv, ok := tgt[tk] + if !ok { + v.logger.Debug("", fmt.Sprintf("ok[%s]", tk), false, fmt.Sprintf("tgt[%s]", sk), sv) + tgt[sk] = sv + if itgt != nil { + itgt[sk] = sv + } + continue + } + + svType := reflect.TypeOf(sv) + tvType := reflect.TypeOf(tv) + + v.logger.Debug( + "processing", + "key", sk, + "st", svType, + "tt", tvType, + "sv", sv, + "tv", tv, + ) + + switch ttv := tv.(type) { + case map[any]any: + v.logger.Debug("merging maps (must convert)") + tsv, ok := sv.(map[any]any) + if !ok { + v.logger.Error( + "Could not cast sv to map[any]any", + "key", sk, + "st", svType, + "tt", tvType, + "sv", sv, + "tv", tv, + ) + continue + } + + ssv := castToMapStringInterface(tsv) + stv := castToMapStringInterface(ttv) + mergeMaps(ssv, stv, ttv) + case map[string]any: + v.logger.Debug("merging maps") + tsv, ok := sv.(map[string]any) + if !ok { + v.logger.Error( + "Could not cast sv to map[string]any", + "key", sk, + "st", svType, + "tt", tvType, + "sv", sv, + "tv", tv, + ) + continue + } + mergeMaps(tsv, ttv, nil) + default: + v.logger.Debug("setting value") + tgt[tk] = sv + if itgt != nil { + itgt[tk] = sv + } + } + } +} + +// AllKeys returns all keys holding a value, regardless of where they are set. +// Nested keys are returned with a v.keyDelim separator. +func AllKeys() []string { return v.AllKeys() } + +func (v *Viper) AllKeys() []string { + m := map[string]bool{} + // add all paths, by order of descending priority to ensure correct shadowing + m = v.flattenAndMergeMap(m, castMapStringToMapInterface(v.aliases), "") + m = v.flattenAndMergeMap(m, v.override, "") + m = v.mergeFlatMap(m, castMapFlagToMapInterface(v.pflags)) + m = v.mergeFlatMap(m, castMapStringSliceToMapInterface(v.env)) + m = v.flattenAndMergeMap(m, v.config, "") + m = v.flattenAndMergeMap(m, v.kvstore, "") + m = v.flattenAndMergeMap(m, v.defaults, "") + + // convert set of paths to list + a := make([]string, 0, len(m)) + for x := range m { + a = append(a, x) + } + return a +} + +// flattenAndMergeMap recursively flattens the given map into a map[string]bool +// of key paths (used as a set, easier to manipulate than a []string): +// - each path is merged into a single key string, delimited with v.keyDelim +// - if a path is shadowed by an earlier value in the initial shadow map, +// it is skipped. +// +// The resulting set of paths is merged to the given shadow set at the same time. +func (v *Viper) flattenAndMergeMap(shadow map[string]bool, m map[string]any, prefix string) map[string]bool { + if shadow != nil && prefix != "" && shadow[prefix] { + // prefix is shadowed => nothing more to flatten + return shadow + } + if shadow == nil { + shadow = make(map[string]bool) + } + + var m2 map[string]any + if prefix != "" { + prefix += v.keyDelim + } + for k, val := range m { + fullKey := prefix + k + switch val := val.(type) { + case map[string]any: + m2 = val + case map[any]any: + m2 = cast.ToStringMap(val) + default: + // immediate value + shadow[strings.ToLower(fullKey)] = true + continue + } + // recursively merge to shadow map + shadow = v.flattenAndMergeMap(shadow, m2, fullKey) + } + return shadow +} + +// mergeFlatMap merges the given maps, excluding values of the second map +// shadowed by values from the first map. +func (v *Viper) mergeFlatMap(shadow map[string]bool, m map[string]any) map[string]bool { + // scan keys +outer: + for k := range m { + path := strings.Split(k, v.keyDelim) + // scan intermediate paths + var parentKey string + for i := 1; i < len(path); i++ { + parentKey = strings.Join(path[0:i], v.keyDelim) + if shadow[parentKey] { + // path is shadowed, continue + continue outer + } + } + // add key + shadow[strings.ToLower(k)] = true + } + return shadow +} + +// AllSettings merges all settings and returns them as a map[string]any. +func AllSettings() map[string]any { return v.AllSettings() } + +func (v *Viper) AllSettings() map[string]any { + return v.getSettings(v.AllKeys()) +} + +func (v *Viper) getSettings(keys []string) map[string]any { + m := map[string]any{} + // start from the list of keys, and construct the map one value at a time + for _, k := range keys { + value := v.Get(k) + if value == nil { + // should not happen, since AllKeys() returns only keys holding a value, + // check just in case anything changes + continue + } + path := strings.Split(k, v.keyDelim) + lastKey := strings.ToLower(path[len(path)-1]) + deepestMap := deepSearch(m, path[0:len(path)-1]) + // set innermost value + deepestMap[lastKey] = value + } + return m +} + +// SetFs sets the filesystem to use to read configuration. +func SetFs(fs afero.Fs) { v.SetFs(fs) } + +func (v *Viper) SetFs(fs afero.Fs) { + v.fs = fs +} + +// SetConfigName sets name for the config file. +// Does not include extension. +func SetConfigName(in string) { v.SetConfigName(in) } + +func (v *Viper) SetConfigName(in string) { + if v.finder != nil { + v.logger.Warn("ineffective call to function: custom finder takes precedence", slog.String("function", "SetConfigName")) + } + + if in != "" { + v.configName = in + v.configFile = "" + } +} + +// SetConfigType sets the type of the configuration returned by the +// remote source, e.g. "json". +func SetConfigType(in string) { v.SetConfigType(in) } + +func (v *Viper) SetConfigType(in string) { + if in != "" { + v.configType = in + } +} + +// SetConfigPermissions sets the permissions for the config file. +func SetConfigPermissions(perm os.FileMode) { v.SetConfigPermissions(perm) } + +func (v *Viper) SetConfigPermissions(perm os.FileMode) { + v.configPermissions = perm.Perm() +} + +func (v *Viper) getConfigType() string { + if v.configType != "" { + return v.configType + } + + cf, err := v.getConfigFile() + if err != nil { + return "" + } + + ext := filepath.Ext(cf) + + if len(ext) > 1 { + return ext[1:] + } + + return "" +} + +func (v *Viper) getConfigFile() (string, error) { + if v.configFile == "" { + cf, err := v.findConfigFile() + if err != nil { + return "", err + } + v.configFile = cf + } + return v.configFile, nil +} + +// Debug prints all configuration registries for debugging +// purposes. +func Debug() { v.Debug() } +func DebugTo(w io.Writer) { v.DebugTo(w) } + +func (v *Viper) Debug() { v.DebugTo(os.Stdout) } + +func (v *Viper) DebugTo(w io.Writer) { + fmt.Fprintf(w, "Aliases:\n%#v\n", v.aliases) + fmt.Fprintf(w, "Override:\n%#v\n", v.override) + fmt.Fprintf(w, "PFlags:\n%#v\n", v.pflags) + fmt.Fprintf(w, "Env:\n%#v\n", v.env) + fmt.Fprintf(w, "Key/Value Store:\n%#v\n", v.kvstore) + fmt.Fprintf(w, "Config:\n%#v\n", v.config) + fmt.Fprintf(w, "Defaults:\n%#v\n", v.defaults) +} diff --git a/vendor/github.com/subosito/gotenv/.env.invalid b/vendor/github.com/subosito/gotenv/.env.invalid new file mode 100644 index 0000000000..016d5e0cea --- /dev/null +++ b/vendor/github.com/subosito/gotenv/.env.invalid @@ -0,0 +1 @@ +lol$wut diff --git a/vendor/github.com/subosito/gotenv/.gitignore b/vendor/github.com/subosito/gotenv/.gitignore new file mode 100644 index 0000000000..7db37c1db4 --- /dev/null +++ b/vendor/github.com/subosito/gotenv/.gitignore @@ -0,0 +1,4 @@ +*.test +*.out +annotate.json +profile.cov diff --git a/vendor/github.com/subosito/gotenv/.golangci.yaml b/vendor/github.com/subosito/gotenv/.golangci.yaml new file mode 100644 index 0000000000..8c82a762e2 --- /dev/null +++ b/vendor/github.com/subosito/gotenv/.golangci.yaml @@ -0,0 +1,7 @@ +# Options for analysis running. +run: + timeout: 1m + +linters-settings: + gofmt: + simplify: true diff --git a/vendor/github.com/subosito/gotenv/CHANGELOG.md b/vendor/github.com/subosito/gotenv/CHANGELOG.md new file mode 100644 index 0000000000..c4fe7d3268 --- /dev/null +++ b/vendor/github.com/subosito/gotenv/CHANGELOG.md @@ -0,0 +1,105 @@ +# Changelog + +## [1.5.0] - 2023-08-15 + +### Fixed + +- Use io.Reader instead of custom Reader + +## [1.5.0] - 2023-08-15 + +### Added + +- Support for reading UTF16 files + +### Fixed + +- Scanner error handling +- Reader error handling + +## [1.4.2] - 2023-01-11 + +### Fixed + +- Env var initialization + +### Changed + +- More consitent line splitting + +## [1.4.1] - 2022-08-23 + +### Fixed + +- Missing file close + +### Changed + +- Updated dependencies + +## [1.4.0] - 2022-06-02 + +### Added + +- Add `Marshal` and `Unmarshal` helpers + +### Changed + +- The CI will now run a linter and the tests on PRs. + +## [1.3.0] - 2022-05-23 + +### Added + +- Support = within double-quoted strings +- Add support for multiline values + +### Changed + +- `OverLoad` prefer environment variables over local variables + +## [1.2.0] - 2019-08-03 + +### Added + +- Add `Must` helper to raise an error as panic. It can be used with `Load` and `OverLoad`. +- Add more tests to be 100% coverage. +- Add CHANGELOG +- Add more OS for the test: OSX and Windows + +### Changed + +- Reduce complexity and improve source code for having `A+` score in [goreportcard](https://goreportcard.com/report/github.com/subosito/gotenv). +- Updated README with mentions to all available functions + +### Removed + +- Remove `ErrFormat` +- Remove `MustLoad` and `MustOverload`, replaced with `Must` helper. + +## [1.1.1] - 2018-06-05 + +### Changed + +- Replace `os.Getenv` with `os.LookupEnv` to ensure that the environment variable is not set, by [radding](https://github.com/radding) + +## [1.1.0] - 2017-03-20 + +### Added + +- Supports carriage return in env +- Handle files with UTF-8 BOM + +### Changed + +- Whitespace handling + +### Fixed + +- Incorrect variable expansion +- Handling escaped '$' characters + +## [1.0.0] - 2014-10-05 + +First stable release. + diff --git a/vendor/github.com/subosito/gotenv/LICENSE b/vendor/github.com/subosito/gotenv/LICENSE new file mode 100644 index 0000000000..f64ccaedc3 --- /dev/null +++ b/vendor/github.com/subosito/gotenv/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2013 Alif Rachmawadi + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/github.com/subosito/gotenv/README.md b/vendor/github.com/subosito/gotenv/README.md new file mode 100644 index 0000000000..fc9616e3b0 --- /dev/null +++ b/vendor/github.com/subosito/gotenv/README.md @@ -0,0 +1,129 @@ +# gotenv + +[![Build Status](https://github.com/subosito/gotenv/workflows/Go%20workflow/badge.svg)](https://github.com/subosito/gotenv/actions) +[![Coverage Status](https://badgen.net/codecov/c/github/subosito/gotenv)](https://codecov.io/gh/subosito/gotenv) +[![Go Report Card](https://goreportcard.com/badge/github.com/subosito/gotenv)](https://goreportcard.com/report/github.com/subosito/gotenv) +[![GoDoc](https://godoc.org/github.com/subosito/gotenv?status.svg)](https://godoc.org/github.com/subosito/gotenv) + +Load environment variables from `.env` or `io.Reader` in Go. + +## Usage + +Put the gotenv package on your `import` statement: + +```go +import "github.com/subosito/gotenv" +``` + +To modify your app environment variables, `gotenv` expose 2 main functions: + +- `gotenv.Load` +- `gotenv.Apply` + +By default, `gotenv.Load` will look for a file called `.env` in the current working directory. + +Behind the scene, it will then load `.env` file and export the valid variables to the environment variables. Make sure you call the method as soon as possible to ensure it loads all variables, say, put it on `init()` function. + +Once loaded you can use `os.Getenv()` to get the value of the variable. + +Let's say you have `.env` file: + +```sh +APP_ID=1234567 +APP_SECRET=abcdef +``` + +Here's the example of your app: + +```go +package main + +import ( + "github.com/subosito/gotenv" + "log" + "os" +) + +func init() { + gotenv.Load() +} + +func main() { + log.Println(os.Getenv("APP_ID")) // "1234567" + log.Println(os.Getenv("APP_SECRET")) // "abcdef" +} +``` + +You can also load other than `.env` file if you wish. Just supply filenames when calling `Load()`. It will load them in order and the first value set for a variable will win.: + +```go +gotenv.Load(".env.production", "credentials") +``` + +While `gotenv.Load` loads entries from `.env` file, `gotenv.Apply` allows you to use any `io.Reader`: + +```go +gotenv.Apply(strings.NewReader("APP_ID=1234567")) + +log.Println(os.Getenv("APP_ID")) +// Output: "1234567" +``` + +Both `gotenv.Load` and `gotenv.Apply` **DO NOT** overrides existing environment variables. If you want to override existing ones, you can see section below. + +### Environment Overrides + +Besides above functions, `gotenv` also provides another functions that overrides existing: + +- `gotenv.OverLoad` +- `gotenv.OverApply` + +Here's the example of this overrides behavior: + +```go +os.Setenv("HELLO", "world") + +// NOTE: using Apply existing value will be reserved +gotenv.Apply(strings.NewReader("HELLO=universe")) +fmt.Println(os.Getenv("HELLO")) +// Output: "world" + +// NOTE: using OverApply existing value will be overridden +gotenv.OverApply(strings.NewReader("HELLO=universe")) +fmt.Println(os.Getenv("HELLO")) +// Output: "universe" +``` + +### Throw a Panic + +Both `gotenv.Load` and `gotenv.OverLoad` returns an error on something wrong occurred, like your env file is not exist, and so on. To make it easier to use, `gotenv` also provides `gotenv.Must` helper, to let it panic when an error returned. + +```go +err := gotenv.Load(".env-is-not-exist") +fmt.Println("error", err) +// error: open .env-is-not-exist: no such file or directory + +gotenv.Must(gotenv.Load, ".env-is-not-exist") +// it will throw a panic +// panic: open .env-is-not-exist: no such file or directory +``` + +### Another Scenario + +Just in case you want to parse environment variables from any `io.Reader`, gotenv keeps its `Parse` and `StrictParse` function as public API so you can use that. + +```go +// import "strings" + +pairs := gotenv.Parse(strings.NewReader("FOO=test\nBAR=$FOO")) +// gotenv.Env{"FOO": "test", "BAR": "test"} + +pairs, err := gotenv.StrictParse(strings.NewReader(`FOO="bar"`)) +// gotenv.Env{"FOO": "bar"} +``` + +`Parse` ignores invalid lines and returns `Env` of valid environment variables, while `StrictParse` returns an error for invalid lines. + +## Notes + +The gotenv package is a Go port of [`dotenv`](https://github.com/bkeepers/dotenv) project with some additions made for Go. For general features, it aims to be compatible as close as possible. diff --git a/vendor/github.com/subosito/gotenv/gotenv.go b/vendor/github.com/subosito/gotenv/gotenv.go new file mode 100644 index 0000000000..1191d35874 --- /dev/null +++ b/vendor/github.com/subosito/gotenv/gotenv.go @@ -0,0 +1,409 @@ +// Package gotenv provides functionality to dynamically load the environment variables +package gotenv + +import ( + "bufio" + "bytes" + "fmt" + "io" + "os" + "path/filepath" + "regexp" + "sort" + "strconv" + "strings" + + "golang.org/x/text/encoding/unicode" + "golang.org/x/text/transform" +) + +const ( + // Pattern for detecting valid line format + linePattern = `\A\s*(?:export\s+)?([\w\.]+)(?:\s*=\s*|:\s+?)('(?:\'|[^'])*'|"(?:\"|[^"])*"|[^#\n]+)?\s*(?:\s*\#.*)?\z` + + // Pattern for detecting valid variable within a value + variablePattern = `(\\)?(\$)(\{?([A-Z0-9_]+)?\}?)` +) + +// Byte order mark character +var ( + bomUTF8 = []byte("\xEF\xBB\xBF") + bomUTF16LE = []byte("\xFF\xFE") + bomUTF16BE = []byte("\xFE\xFF") +) + +// Env holds key/value pair of valid environment variable +type Env map[string]string + +// Load is a function to load a file or multiple files and then export the valid variables into environment variables if they do not exist. +// When it's called with no argument, it will load `.env` file on the current path and set the environment variables. +// Otherwise, it will loop over the filenames parameter and set the proper environment variables. +func Load(filenames ...string) error { + return loadenv(false, filenames...) +} + +// OverLoad is a function to load a file or multiple files and then export and override the valid variables into environment variables. +func OverLoad(filenames ...string) error { + return loadenv(true, filenames...) +} + +// Must is wrapper function that will panic when supplied function returns an error. +func Must(fn func(filenames ...string) error, filenames ...string) { + if err := fn(filenames...); err != nil { + panic(err.Error()) + } +} + +// Apply is a function to load an io Reader then export the valid variables into environment variables if they do not exist. +func Apply(r io.Reader) error { + return parset(r, false) +} + +// OverApply is a function to load an io Reader then export and override the valid variables into environment variables. +func OverApply(r io.Reader) error { + return parset(r, true) +} + +func loadenv(override bool, filenames ...string) error { + if len(filenames) == 0 { + filenames = []string{".env"} + } + + for _, filename := range filenames { + f, err := os.Open(filename) + if err != nil { + return err + } + + err = parset(f, override) + f.Close() + if err != nil { + return err + } + } + + return nil +} + +// parse and set :) +func parset(r io.Reader, override bool) error { + env, err := strictParse(r, override) + if err != nil { + return err + } + + for key, val := range env { + setenv(key, val, override) + } + + return nil +} + +func setenv(key, val string, override bool) { + if override { + os.Setenv(key, val) + } else { + if _, present := os.LookupEnv(key); !present { + os.Setenv(key, val) + } + } +} + +// Parse is a function to parse line by line any io.Reader supplied and returns the valid Env key/value pair of valid variables. +// It expands the value of a variable from the environment variable but does not set the value to the environment itself. +// This function is skipping any invalid lines and only processing the valid one. +func Parse(r io.Reader) Env { + env, _ := strictParse(r, false) + return env +} + +// StrictParse is a function to parse line by line any io.Reader supplied and returns the valid Env key/value pair of valid variables. +// It expands the value of a variable from the environment variable but does not set the value to the environment itself. +// This function is returning an error if there are any invalid lines. +func StrictParse(r io.Reader) (Env, error) { + return strictParse(r, false) +} + +// Read is a function to parse a file line by line and returns the valid Env key/value pair of valid variables. +// It expands the value of a variable from the environment variable but does not set the value to the environment itself. +// This function is skipping any invalid lines and only processing the valid one. +func Read(filename string) (Env, error) { + f, err := os.Open(filename) + if err != nil { + return nil, err + } + defer f.Close() + return strictParse(f, false) +} + +// Unmarshal reads a string line by line and returns the valid Env key/value pair of valid variables. +// It expands the value of a variable from the environment variable but does not set the value to the environment itself. +// This function is returning an error if there are any invalid lines. +func Unmarshal(str string) (Env, error) { + return strictParse(strings.NewReader(str), false) +} + +// Marshal outputs the given environment as a env file. +// Variables will be sorted by name. +func Marshal(env Env) (string, error) { + lines := make([]string, 0, len(env)) + for k, v := range env { + if d, err := strconv.Atoi(v); err == nil { + lines = append(lines, fmt.Sprintf(`%s=%d`, k, d)) + } else { + lines = append(lines, fmt.Sprintf(`%s=%q`, k, v)) + } + } + sort.Strings(lines) + return strings.Join(lines, "\n"), nil +} + +// Write serializes the given environment and writes it to a file +func Write(env Env, filename string) error { + content, err := Marshal(env) + if err != nil { + return err + } + // ensure the path exists + if err := os.MkdirAll(filepath.Dir(filename), 0o775); err != nil { + return err + } + // create or truncate the file + file, err := os.Create(filename) + if err != nil { + return err + } + defer file.Close() + _, err = file.WriteString(content + "\n") + if err != nil { + return err + } + + return file.Sync() +} + +// splitLines is a valid SplitFunc for a bufio.Scanner. It will split lines on CR ('\r'), LF ('\n') or CRLF (any of the three sequences). +// If a CR is immediately followed by a LF, it is treated as a CRLF (one single line break). +func splitLines(data []byte, atEOF bool) (advance int, token []byte, err error) { + if atEOF && len(data) == 0 { + return 0, nil, bufio.ErrFinalToken + } + + idx := bytes.IndexAny(data, "\r\n") + switch { + case atEOF && idx < 0: + return len(data), data, bufio.ErrFinalToken + + case idx < 0: + return 0, nil, nil + } + + // consume CR or LF + eol := idx + 1 + // detect CRLF + if len(data) > eol && data[eol-1] == '\r' && data[eol] == '\n' { + eol++ + } + + return eol, data[:idx], nil +} + +func strictParse(r io.Reader, override bool) (Env, error) { + env := make(Env) + + buf := new(bytes.Buffer) + tee := io.TeeReader(r, buf) + + // There can be a maximum of 3 BOM bytes. + bomByteBuffer := make([]byte, 3) + _, err := tee.Read(bomByteBuffer) + if err != nil && err != io.EOF { + return env, err + } + + z := io.MultiReader(buf, r) + + // We chooes a different scanner depending on file encoding. + var scanner *bufio.Scanner + + if bytes.HasPrefix(bomByteBuffer, bomUTF8) { + scanner = bufio.NewScanner(transform.NewReader(z, unicode.UTF8BOM.NewDecoder())) + } else if bytes.HasPrefix(bomByteBuffer, bomUTF16LE) { + scanner = bufio.NewScanner(transform.NewReader(z, unicode.UTF16(unicode.LittleEndian, unicode.ExpectBOM).NewDecoder())) + } else if bytes.HasPrefix(bomByteBuffer, bomUTF16BE) { + scanner = bufio.NewScanner(transform.NewReader(z, unicode.UTF16(unicode.BigEndian, unicode.ExpectBOM).NewDecoder())) + } else { + scanner = bufio.NewScanner(z) + } + + scanner.Split(splitLines) + + for scanner.Scan() { + if err := scanner.Err(); err != nil { + return env, err + } + + line := strings.TrimSpace(scanner.Text()) + if line == "" || line[0] == '#' { + continue + } + + quote := "" + // look for the delimiter character + idx := strings.Index(line, "=") + if idx == -1 { + idx = strings.Index(line, ":") + } + // look for a quote character + if idx > 0 && idx < len(line)-1 { + val := strings.TrimSpace(line[idx+1:]) + if val[0] == '"' || val[0] == '\'' { + quote = val[:1] + // look for the closing quote character within the same line + idx = strings.LastIndex(strings.TrimSpace(val[1:]), quote) + if idx >= 0 && val[idx] != '\\' { + quote = "" + } + } + } + // look for the closing quote character + for quote != "" && scanner.Scan() { + l := scanner.Text() + line += "\n" + l + idx := strings.LastIndex(l, quote) + if idx > 0 && l[idx-1] == '\\' { + // foud a matching quote character but it's escaped + continue + } + if idx >= 0 { + // foud a matching quote + quote = "" + } + } + + if quote != "" { + return env, fmt.Errorf("missing quotes") + } + + err := parseLine(line, env, override) + if err != nil { + return env, err + } + } + + return env, scanner.Err() +} + +var ( + lineRgx = regexp.MustCompile(linePattern) + unescapeRgx = regexp.MustCompile(`\\([^$])`) + varRgx = regexp.MustCompile(variablePattern) +) + +func parseLine(s string, env Env, override bool) error { + rm := lineRgx.FindStringSubmatch(s) + + if len(rm) == 0 { + return checkFormat(s, env) + } + + key := strings.TrimSpace(rm[1]) + val := strings.TrimSpace(rm[2]) + + var hsq, hdq bool + + // check if the value is quoted + if l := len(val); l >= 2 { + l -= 1 + // has double quotes + hdq = val[0] == '"' && val[l] == '"' + // has single quotes + hsq = val[0] == '\'' && val[l] == '\'' + + // remove quotes '' or "" + if hsq || hdq { + val = val[1:l] + } + } + + if hdq { + val = strings.ReplaceAll(val, `\n`, "\n") + val = strings.ReplaceAll(val, `\r`, "\r") + + // Unescape all characters except $ so variables can be escaped properly + val = unescapeRgx.ReplaceAllString(val, "$1") + } + + if !hsq { + fv := func(s string) string { + return varReplacement(s, hsq, env, override) + } + val = varRgx.ReplaceAllStringFunc(val, fv) + } + + env[key] = val + return nil +} + +func parseExport(st string, env Env) error { + if strings.HasPrefix(st, "export") { + vs := strings.SplitN(st, " ", 2) + + if len(vs) > 1 { + if _, ok := env[vs[1]]; !ok { + return fmt.Errorf("line `%s` has an unset variable", st) + } + } + } + + return nil +} + +var varNameRgx = regexp.MustCompile(`(\$)(\{?([A-Z0-9_]+)\}?)`) + +func varReplacement(s string, hsq bool, env Env, override bool) string { + if s == "" { + return s + } + + if s[0] == '\\' { + // the dollar sign is escaped + return s[1:] + } + + if hsq { + return s + } + + mn := varNameRgx.FindStringSubmatch(s) + + if len(mn) == 0 { + return s + } + + v := mn[3] + + if replace, ok := os.LookupEnv(v); ok && !override { + return replace + } + + if replace, ok := env[v]; ok { + return replace + } + + return os.Getenv(v) +} + +func checkFormat(s string, env Env) error { + st := strings.TrimSpace(s) + + if st == "" || st[0] == '#' { + return nil + } + + if err := parseExport(st, env); err != nil { + return err + } + + return fmt.Errorf("line `%s` doesn't match format", s) +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 9c4068de15..cf85317a62 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -1794,6 +1794,13 @@ github.com/patrickmn/go-cache # github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 ## explicit; go 1.16 github.com/pbnjay/memory +# github.com/pelletier/go-toml/v2 v2.2.4 +## explicit; go 1.21.0 +github.com/pelletier/go-toml/v2 +github.com/pelletier/go-toml/v2/internal/characters +github.com/pelletier/go-toml/v2/internal/danger +github.com/pelletier/go-toml/v2/internal/tracker +github.com/pelletier/go-toml/v2/unstable # github.com/philhofer/fwd v1.2.0 ## explicit; go 1.20 github.com/philhofer/fwd @@ -1904,6 +1911,9 @@ github.com/russross/blackfriday/v2 ## explicit github.com/rwcarlsen/goexif/exif github.com/rwcarlsen/goexif/tiff +# github.com/sagikazarmark/locafero v0.11.0 +## explicit; go 1.23.0 +github.com/sagikazarmark/locafero # github.com/samber/lo v1.51.0 ## explicit; go 1.18 github.com/samber/lo @@ -2024,6 +2034,11 @@ github.com/sirupsen/logrus # github.com/skeema/knownhosts v1.3.0 ## explicit; go 1.17 github.com/skeema/knownhosts +# github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 +## explicit; go 1.20 +github.com/sourcegraph/conc +github.com/sourcegraph/conc/panics +github.com/sourcegraph/conc/pool # github.com/spacewander/go-suffix-tree v0.0.0-20191010040751-0865e368c784 ## explicit github.com/spacewander/go-suffix-tree @@ -2032,12 +2047,24 @@ github.com/spacewander/go-suffix-tree github.com/spf13/afero github.com/spf13/afero/internal/common github.com/spf13/afero/mem +# github.com/spf13/cast v1.10.0 +## explicit; go 1.21.0 +github.com/spf13/cast +github.com/spf13/cast/internal # github.com/spf13/cobra v1.10.1 ## explicit; go 1.15 github.com/spf13/cobra # github.com/spf13/pflag v1.0.10 ## explicit; go 1.12 github.com/spf13/pflag +# github.com/spf13/viper v1.21.0 +## explicit; go 1.23.0 +github.com/spf13/viper +github.com/spf13/viper/internal/encoding/dotenv +github.com/spf13/viper/internal/encoding/json +github.com/spf13/viper/internal/encoding/toml +github.com/spf13/viper/internal/encoding/yaml +github.com/spf13/viper/internal/features # github.com/stretchr/objx v0.5.2 ## explicit; go 1.20 github.com/stretchr/objx @@ -2050,6 +2077,9 @@ github.com/stretchr/testify/require # github.com/studio-b12/gowebdav v0.9.0 => github.com/kobergj/gowebdav v0.0.0-20250102091030-aa65266db202 ## explicit; go 1.17 github.com/studio-b12/gowebdav +# github.com/subosito/gotenv v1.6.0 +## explicit; go 1.18 +github.com/subosito/gotenv # github.com/tchap/go-patricia/v2 v2.3.3 ## explicit; go 1.16 github.com/tchap/go-patricia/v2/patricia From 56324caba1a5b8a5c9b00732e5a0b3df6d84d8f0 Mon Sep 17 00:00:00 2001 From: Christian Richter Date: Wed, 3 Dec 2025 12:40:50 +0100 Subject: [PATCH 45/82] migrate decomposedfs cli command from urfave/cli to spf13/cobra Signed-off-by: Christian Richter --- opencloud/pkg/command/decomposedfs.go | 217 ++++++++++++-------------- 1 file changed, 99 insertions(+), 118 deletions(-) diff --git a/opencloud/pkg/command/decomposedfs.go b/opencloud/pkg/command/decomposedfs.go index a088bb3286..2f96b3ec58 100644 --- a/opencloud/pkg/command/decomposedfs.go +++ b/opencloud/pkg/command/decomposedfs.go @@ -9,8 +9,6 @@ import ( "sort" "strings" - userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" - provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" "github.com/opencloud-eu/opencloud/opencloud/pkg/register" "github.com/opencloud-eu/opencloud/pkg/config" revactx "github.com/opencloud-eu/reva/v2/pkg/ctx" @@ -25,69 +23,61 @@ import ( "github.com/opencloud-eu/reva/v2/pkg/storage/pkg/decomposedfs/tree" "github.com/opencloud-eu/reva/v2/pkg/storagespace" "github.com/opencloud-eu/reva/v2/pkg/store" + + userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" + provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" "github.com/rs/zerolog" - "github.com/urfave/cli/v2" + "github.com/spf13/cobra" ) // DecomposedfsCommand is the entrypoint for the groups command. -func DecomposedfsCommand(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: "decomposedfs", - Usage: `cli tools to inspect and manipulate a decomposedfs storage.`, - Category: "maintenance", - Subcommands: []*cli.Command{ - metadataCmd(cfg), - checkCmd(cfg), - }, +func DecomposedfsCommand(cfg *config.Config) *cobra.Command { + decomposedCmd := &cobra.Command{ + Use: "decomposedfs", + Short: `cli tools to inspect and manipulate a decomposedfs storage.`, } + decomposedCmd.AddCommand(metadataCmd(cfg), checkCmd(cfg)) + return decomposedCmd } func init() { register.AddCommand(DecomposedfsCommand) } -func checkCmd(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: "check-treesize", - Usage: `cli tool to check the treesize metadata of a Space`, - Flags: []cli.Flag{ - &cli.StringFlag{ - Name: "root", - Aliases: []string{"r"}, - Required: true, - Usage: "Path to the root directory of the decomposedfs", - }, - &cli.StringFlag{ - Name: "node", - Required: true, - Aliases: []string{"n"}, - Usage: "Space ID of the Space to inspect", - }, - &cli.BoolFlag{ - Name: "repair", - Usage: "Try to repair nodes with incorrect treesize metadata. IMPORTANT: Only use this while OpenCloud is not running.", - }, - &cli.BoolFlag{ - Name: "force", - Usage: "Do not prompt for confirmation when running in repair mode.", - }, - }, - Action: check, +func checkCmd(cfg *config.Config) *cobra.Command { + cCmd := &cobra.Command{ + Use: "check-treesize", + Short: `cli tool to check the treesize metadata of a Space`, + RunE: check, } + cCmd.Flags().StringP("root", "r", "", "Path to the root directory of the decomposedfs") + err := cCmd.MarkFlagRequired("root") + if err != nil { + fmt.Println(err) + } + cCmd.Flags().StringP("node", "n", "", "Space ID of the Space to inspect") + err = cCmd.MarkFlagRequired("node") + if err != nil { + fmt.Println(err) + } + cCmd.Flags().Bool("repair", false, "Try to repair nodes with incorrect treesize metadata. IMPORTANT: Only use this while OpenCloud is not running.") + cCmd.Flags().Bool("force", false, "Do not prompt for confirmation when running in repair mode.") + + return cCmd } -func check(c *cli.Context) error { - rootFlag := c.String("root") - repairFlag := c.Bool("repair") +func check(cmd *cobra.Command, args []string) error { + rootFlag := cmd.Flag("root").Value.String() + repairFlag := cmd.Flag("repair").Changed - if repairFlag && !c.Bool("force") { + if repairFlag && !cmd.Flag("force").Changed { answer := strings.ToLower(stringPrompt("IMPORTANT: Only use '--repair' when OpenCloud is not running. Do you want to continue? [yes | no = default]")) if answer != "yes" && answer != "y" { return nil } } - lu, backend := getBackend(c) + lu, backend := getBackend(cmd) o := &options.Options{ MetadataBackend: backend.Name(), MaxConcurrency: 100, @@ -100,7 +90,7 @@ func check(c *cli.Context) error { tree := tree.New(lu, bs, o, permissions.Permissions{}, store.Create(), &zerolog.Logger{}) - nId := c.String("node") + nId := cmd.Flag("node").Value.String() n, err := lu.NodeFromSpaceID(context.Background(), nId) if err != nil || !n.Exists { fmt.Println("Can not find node '" + nId + "'") @@ -116,7 +106,7 @@ func check(c *cli.Context) error { }) treeSize, err := walkTree(ctx, tree, lu, n, repairFlag) - treesizeFromMetadata, err := n.GetTreeSize(c.Context) + treesizeFromMetadata, err := n.GetTreeSize(cmd.Context()) if err != nil { fmt.Printf("failed to read treesize of node: %s: %s\n", n.ID, err) } @@ -126,7 +116,7 @@ func check(c *cli.Context) error { if repairFlag { fmt.Printf("Fixing tree size for node: %s. Calculated treesize: %d\n", n.ID, treeSize) - n.SetTreeSize(c.Context, treeSize) + n.SetTreeSize(cmd.Context(), treeSize) } } return nil @@ -185,105 +175,83 @@ func walkTree(ctx context.Context, tree *tree.Tree, lu *lookup.Lookup, root *nod return treesize, nil } -func metadataCmd(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: "metadata", - Usage: `cli tools to inspect and manipulate node metadata`, - Flags: []cli.Flag{ - &cli.StringFlag{ - Name: "root", - Aliases: []string{"r"}, - Required: true, - Usage: "Path to the decomposedfs", - }, - &cli.StringFlag{ - Name: "node", - Required: true, - Aliases: []string{"n"}, - Usage: "Path to or ID of the node to inspect", - }, - }, - Subcommands: []*cli.Command{dumpCmd(cfg), getCmd(cfg), setCmd(cfg)}, +func metadataCmd(cfg *config.Config) *cobra.Command { + metaCmd := &cobra.Command{ + Use: "metadata", + Short: `cli tools to inspect and manipulate node metadata`, } + metaCmd.AddCommand(dumpCmd(cfg), getCmd(cfg), setCmd(cfg)) + metaCmd.Flags().StringP("root", "r", "", "Path to the root directory of the decomposedfs") + err := metaCmd.MarkFlagRequired("root") + if err != nil { + fmt.Println(err) + } + metaCmd.Flags().StringP("node", "n", "", "Path to or ID of the node to inspect") + err = metaCmd.MarkFlagRequired("node") + if err != nil { + fmt.Println(err) + } + return metaCmd } -func dumpCmd(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: "dump", - Usage: `print the metadata of the given node. String attributes will be enclosed in quotes. Binary attributes will be returned encoded as base64 with their value being prefixed with '0s'.`, - Action: func(c *cli.Context) error { - lu, backend := getBackend(c) - path, err := getNode(c, lu) +func dumpCmd(cfg *config.Config) *cobra.Command { + return &cobra.Command{ + Use: "dump", + Short: `print the metadata of the given node. String attributes will be enclosed in quotes. Binary attributes will be returned encoded as base64 with their value being prefixed with '0s'.`, + RunE: func(cmd *cobra.Command, args []string) error { + lu, backend := getBackend(cmd) + path, err := getNode(cmd, lu) if err != nil { return err } - attribs, err := backend.All(c.Context, path) + attribs, err := backend.All(cmd.Context(), path) if err != nil { fmt.Println("Error reading attributes") return err } - printAttribs(attribs, c.String("attribute")) + printAttribs(attribs, cmd.Flag("attribute").Value.String()) return nil }, } } -func getCmd(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: "get", - Usage: `print a specific attribute of the given node. String attributes will be enclosed in quotes. Binary attributes will be returned encoded as base64 with their value being prefixed with '0s'.`, - Flags: []cli.Flag{ - &cli.StringFlag{ - Name: "attribute", - Aliases: []string{"a"}, - Usage: "attribute to inspect", - }, - }, - Action: func(c *cli.Context) error { - lu, backend := getBackend(c) - path, err := getNode(c, lu) +func getCmd(cfg *config.Config) *cobra.Command { + gCmd := &cobra.Command{ + Use: "get", + Short: `print a specific attribute of the given node. String attributes will be enclosed in quotes. Binary attributes will be returned encoded as base64 with their value being prefixed with '0s'.`, + RunE: func(cmd *cobra.Command, args []string) error { + lu, backend := getBackend(cmd) + path, err := getNode(cmd, lu) if err != nil { return err } - attribs, err := backend.All(c.Context, path) + attribs, err := backend.All(cmd.Context(), path) if err != nil { fmt.Println("Error reading attributes") return err } - printAttribs(attribs, c.String("attribute")) + printAttribs(attribs, cmd.Flag("attribute").Value.String()) return nil }, } + gCmd.Flags().StringP("attribute", "a", "", "attribute to inspect, can be a glob pattern (e.g. 'user.*' will match all attributes starting with 'user.').") + return gCmd } -func setCmd(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: "set", - Usage: `manipulate metadata of the given node. Binary attributes can be given hex encoded (prefix by '0x') or base64 encoded (prefix by '0s').`, - Flags: []cli.Flag{ - &cli.StringFlag{ - Name: "attribute", - Required: true, - Aliases: []string{"a"}, - Usage: "attribute to inspect", - }, - &cli.StringFlag{ - Name: "value", - Required: true, - Aliases: []string{"v"}, - Usage: "value to set", - }, - }, - Action: func(c *cli.Context) error { - lu, backend := getBackend(c) - n, err := getNode(c, lu) +func setCmd(cfg *config.Config) *cobra.Command { + sCmd := &cobra.Command{ + Use: "set", + Short: `manipulate metadata of the given node. Binary attributes can be given hex encoded (prefix by '0x') or base64 encoded (prefix by '0s').`, + RunE: func(cmd *cobra.Command, args []string) error { + lu, backend := getBackend(cmd) + n, err := getNode(cmd, lu) if err != nil { return err } - v := c.String("value") + v := cmd.Flag("value").Value.String() if strings.HasPrefix(v, "0s") { b64, err := base64.StdEncoding.DecodeString(v[2:]) if err == nil { @@ -300,7 +268,7 @@ func setCmd(cfg *config.Config) *cli.Command { } } - err = backend.Set(c.Context, n, c.String("attribute"), []byte(v)) + err = backend.Set(cmd.Context(), n, cmd.Flag("attribute").Value.String(), []byte(v)) if err != nil { fmt.Println("Error setting attribute") return err @@ -308,6 +276,19 @@ func setCmd(cfg *config.Config) *cli.Command { return nil }, } + sCmd.Flags().StringP("attribute", "a", "", "attribute to inspect, can be a glob pattern (e.g. 'user.*' will match all attributes starting with 'user.').") + err := sCmd.MarkFlagRequired("attribute") + if err != nil { + fmt.Println(err) + } + + sCmd.Flags().StringP("value", "v", "", "value to set") + err = sCmd.MarkFlagRequired("value") + if err != nil { + fmt.Println(err) + } + + return sCmd } func backend(root, backend string) metadata.Backend { @@ -320,8 +301,8 @@ func backend(root, backend string) metadata.Backend { return metadata.NullBackend{} } -func getBackend(c *cli.Context) (*lookup.Lookup, metadata.Backend) { - rootFlag := c.String("root") +func getBackend(cmd *cobra.Command) (*lookup.Lookup, metadata.Backend) { + rootFlag := cmd.Flag("root").Value.String() bod := lookup.DetectBackendOnDisk(rootFlag) backend := backend(rootFlag, bod) @@ -332,8 +313,8 @@ func getBackend(c *cli.Context) (*lookup.Lookup, metadata.Backend) { return lu, backend } -func getNode(c *cli.Context, lu *lookup.Lookup) (*node.Node, error) { - nodeFlag := c.String("node") +func getNode(cmd *cobra.Command, lu *lookup.Lookup) (*node.Node, error) { + nodeFlag := cmd.Flag("node").Value.String() id, err := storagespace.ParseID(nodeFlag) if err != nil { From 3cb002977fb8328419c4f57ea4cae25cc99c565c Mon Sep 17 00:00:00 2001 From: Christian Richter Date: Wed, 3 Dec 2025 12:48:01 +0100 Subject: [PATCH 46/82] migrate backup cli command from urfave/cli to spf13/cobra Signed-off-by: Christian Richter --- opencloud/pkg/command/backup.go | 64 ++++++++++++++------------------- 1 file changed, 27 insertions(+), 37 deletions(-) diff --git a/opencloud/pkg/command/backup.go b/opencloud/pkg/command/backup.go index d5b6ababe1..469130a3e5 100644 --- a/opencloud/pkg/command/backup.go +++ b/opencloud/pkg/command/backup.go @@ -11,62 +11,44 @@ import ( "github.com/opencloud-eu/opencloud/pkg/config/parser" decomposedbs "github.com/opencloud-eu/reva/v2/pkg/storage/fs/decomposed/blobstore" decomposeds3bs "github.com/opencloud-eu/reva/v2/pkg/storage/fs/decomposeds3/blobstore" - "github.com/urfave/cli/v2" + "github.com/spf13/cobra" ) // BackupCommand is the entrypoint for the backup command -func BackupCommand(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: "backup", - Usage: "OpenCloud backup functionality", - Subcommands: []*cli.Command{ - ConsistencyCommand(cfg), - }, - Before: func(c *cli.Context) error { +func BackupCommand(cfg *config.Config) *cobra.Command { + bckCmd := &cobra.Command{ + Use: "backup", + Short: "OpenCloud backup functionality", + PreRunE: func(cmd *cobra.Command, args []string) error { return configlog.ReturnError(parser.ParseConfig(cfg, true)) }, - Action: func(_ *cli.Context) error { + RunE: func(cmd *cobra.Command, args []string) error { fmt.Println("Read the docs") return nil }, } + bckCmd.AddCommand(ConsistencyCommand(cfg)) + return bckCmd } // ConsistencyCommand is the entrypoint for the consistency Command -func ConsistencyCommand(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: "consistency", - Usage: "check backup consistency", - Flags: []cli.Flag{ - &cli.StringFlag{ - Name: "basepath", - Aliases: []string{"p"}, - Usage: "the basepath of the decomposedfs (e.g. /var/tmp/opencloud/storage/users)", - Required: true, - }, - &cli.StringFlag{ - Name: "blobstore", - Aliases: []string{"b"}, - Usage: "the blobstore type. Can be (none, decomposed, decomposeds3). Default decomposed", - Value: "decomposed", - }, - &cli.BoolFlag{ - Name: "fail", - Usage: "exit with non-zero status if consistency check fails", - }, - }, - Action: func(c *cli.Context) error { - basePath := c.String("basepath") +func ConsistencyCommand(cfg *config.Config) *cobra.Command { + consCmd := &cobra.Command{ + Use: "consistency", + Short: "check backup consistency", + RunE: func(cmd *cobra.Command, args []string) error { + basePath := cmd.Flag("basepath").Value.String() if basePath == "" { fmt.Println("basepath is required") - return cli.ShowCommandHelp(c, "consistency") + _ = cmd.Help() + return nil } var ( bs backup.ListBlobstore err error ) - switch c.String("blobstore") { + switch cmd.Flag("blobstore").Value.String() { case "decomposeds3": bs, err = decomposeds3bs.New( cfg.StorageUsers.Drivers.DecomposedS3.Endpoint, @@ -87,7 +69,7 @@ func ConsistencyCommand(cfg *config.Config) *cli.Command { fmt.Println(err) return err } - if err := backup.CheckProviderConsistency(basePath, bs, c.Bool("fail")); err != nil { + if err := backup.CheckProviderConsistency(basePath, bs, cmd.Flag("fail").Changed); err != nil { fmt.Println(err) return err } @@ -95,6 +77,14 @@ func ConsistencyCommand(cfg *config.Config) *cli.Command { return nil }, } + consCmd.Flags().StringP("basepath", "p", "", "the basepath of the decomposedfs (e.g. /var/tmp/opencloud/storage/users)") + err := consCmd.MarkFlagRequired("basepath") + if err != nil { + fmt.Println(err) + } + consCmd.Flags().StringP("blobstore", "b", "", "the blobstore type. Can be (none, decomposed, decomposeds3). Default decomposed") + consCmd.Flags().Bool("fail", false, "exit with non-zero status if consistency check fails") + return consCmd } func init() { From 83bbcfb4ed86fa839ab92f1c7836b66562a41407 Mon Sep 17 00:00:00 2001 From: Christian Richter Date: Wed, 3 Dec 2025 14:13:45 +0100 Subject: [PATCH 47/82] migrate benchmark cli command from urfave/cli to spf13/cobra Signed-off-by: Christian Richter --- opencloud/pkg/command/benchmark.go | 171 +++++++++++------------------ 1 file changed, 62 insertions(+), 109 deletions(-) diff --git a/opencloud/pkg/command/benchmark.go b/opencloud/pkg/command/benchmark.go index be9589e179..7d7104f982 100644 --- a/opencloud/pkg/command/benchmark.go +++ b/opencloud/pkg/command/benchmark.go @@ -21,105 +21,46 @@ import ( "github.com/opencloud-eu/opencloud/pkg/version" "github.com/pkg/xattr" "github.com/rogpeppe/go-internal/lockedfile" - "github.com/urfave/cli/v2" + "github.com/spf13/cobra" ) // BenchmarkCommand is the entrypoint for the benchmark commands. -func BenchmarkCommand(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: "benchmark", - Usage: "cli tools to test low and high level performance", - Category: "benchmark", - Subcommands: []*cli.Command{BenchmarkClientCommand(cfg), BenchmarkSyscallsCommand(cfg)}, +func BenchmarkCommand(cfg *config.Config) *cobra.Command { + benchCmd := &cobra.Command{ + Use: "benchmark", + Short: "cli tools to test low and high level performance", } + benchCmd.AddCommand(BenchmarkClientCommand(cfg), BenchmarkSyscallsCommand(cfg)) + return benchCmd } // BenchmarkClientCommand is the entrypoint for the benchmark client command. -func BenchmarkClientCommand(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: "client", - - Usage: "Start a client that continuously makes web requests and prints stats. The options mimic curl, but URL must be at the end.", - Flags: []cli.Flag{ - - // TODO with v3 'flag.Persistent: true' can be set to make the order of flags no longer relevant \o/ - // flags mimicing curl - &cli.StringFlag{ - Name: "request", - Aliases: []string{"X"}, - Value: "PROPFIND", - Usage: "Specifies a custom request method to use when communicating with the HTTP server.", - }, - &cli.StringFlag{ - Name: "user", - Aliases: []string{"u"}, - Value: "admin:admin", - Usage: "Specify the user name and password to use for server authentication.", - }, - &cli.BoolFlag{ - Name: "insecure", - Aliases: []string{"k"}, - Usage: "Skip the TLS verification step and proceed without checking.", - }, - &cli.StringFlag{ - Name: "data", - Aliases: []string{"d"}, - Usage: "Sends the specified data in a request to the HTTP server.", - // TODE support multiple data flags, support data-binary, data-raw - }, - &cli.StringSliceFlag{ - Name: "header", - Aliases: []string{"H"}, - Usage: "Extra header to include in information sent.", - }, - &cli.StringFlag{ - Name: "rate", - Usage: `Specify the maximum transfer frequency you allow a client to use - in number of transfer starts per time unit (sometimes called request rate). - The request rate is provided as "N/U" where N is an integer number and U is a time unit. Supported units are 's' (second), 'm' (minute), 'h' (hour) and 'd' /(day, as in a 24 hour unit). The default time unit, if no "/U" is provided, is number of transfers per hour.`, - }, - /* - &cli.StringFlag{ - Name: "oauth2-bearer", - Usage: "Specify the Bearer Token for OAUTH 2.0 server authentication.", - }, - &cli.StringFlag{ - Name: "user-agent", - Aliases: []string{"A"}, - Value: "admin:admin", - Usage: "Specify the User-Agent string to send to the HTTP server.", - }, - */ - // other flags - &cli.StringFlag{ - Name: "bearer-token-command", - Usage: "Command to execute for a bearer token, e.g. 'oidc-token opencloud'. When set, disables basic auth.", - }, - &cli.IntFlag{ - Name: "every", - Usage: "Aggregate stats every time this amount of seconds has passed.", - }, - &cli.IntFlag{ - Name: "jobs", - Aliases: []string{"j"}, - Value: 1, - Usage: "Number of parallel clients to start.", - }, - }, - Category: "benchmark", - Action: func(c *cli.Context) error { +func BenchmarkClientCommand(cfg *config.Config) *cobra.Command { + benchClientCmd := &cobra.Command{ + Use: "client", + Short: "Start a client that continuously makes web requests and prints stats. The options mimic curl, but URL must be at the end.", + RunE: func(cmd *cobra.Command, args []string) error { + jobs, err := cmd.Flags().GetInt("jobs") + if err != nil { + return err + } opt := clientOptions{ - request: c.String("request"), - url: c.Args().First(), - insecure: c.Bool("insecure"), - jobs: c.Int("jobs"), + request: cmd.Flag("request").Value.String(), + url: args[0], + insecure: cmd.Flag("insecure").Changed, + jobs: jobs, headers: make(map[string]string), - data: []byte(c.String("data")), + data: []byte(cmd.Flag("data").Value.String()), } if opt.url == "" { log.Fatal(errors.New("no URL specified")) } - for _, h := range c.StringSlice("headers") { + headersSlice, err := cmd.Flags().GetStringSlice("headers") + if err != nil { + return err + } + for _, h := range headersSlice { parts := strings.SplitN(h, ":", 2) if len(parts) != 2 { log.Fatal(errors.New("invalid header '" + h + "'")) @@ -127,7 +68,7 @@ func BenchmarkClientCommand(cfg *config.Config) *cli.Command { opt.headers[parts[0]] = strings.TrimSpace(parts[1]) } - rate := c.String("rate") + rate := cmd.Flag("rate").Value.String() if rate != "" { parts := strings.SplitN(rate, "/", 2) num, err := strconv.Atoi(parts[0]) @@ -150,12 +91,12 @@ func BenchmarkClientCommand(cfg *config.Config) *cli.Command { opt.rateDelay = unit / time.Duration(num) } - user := c.String("user") + user := cmd.Flag("user").Value.String() opt.auth = func() string { return "Basic " + base64.StdEncoding.EncodeToString([]byte(user)) } - btc := c.String("bearer-token-command") + btc := cmd.Flag("bearer-token-command").Value.String() if btc != "" { parts := strings.SplitN(btc, " ", 2) var cmd *exec.Cmd @@ -173,7 +114,10 @@ func BenchmarkClientCommand(cfg *config.Config) *cli.Command { } } - every := c.Int("every") + every, err := cmd.Flags().GetInt("every") + if err != nil { + return err + } if every != 0 { opt.ticker = time.NewTicker(time.Second * time.Duration(every)) defer opt.ticker.Stop() @@ -183,6 +127,22 @@ func BenchmarkClientCommand(cfg *config.Config) *cli.Command { }, } + + // TODO with v3 'flag.Persistent: true' can be set to make the order of flags no longer relevant \o/ + // flags mimicing curl + benchClientCmd.Flags().StringP("request", "X", "PROPFIND", "Specifies a custom request method to use when communicating with the HTTP server.") + benchClientCmd.Flags().StringP("user", "u", "admin:admin", "Specify the user name and password to use for server authentication.") + benchClientCmd.Flags().BoolP("insecure", "k", false, "Skip the TLS verification step and proceed without checking.") + benchClientCmd.Flags().StringP("data", "d", "", "Sends the specified data in a request to the HTTP server.") + benchClientCmd.Flags().StringSliceP("headers", "H", []string{}, "Extra header to include in information sent.") + benchClientCmd.Flags().String("rate", "", "Specify the maximum transfer frequency you allow a client to use - in number of transfer starts per time unit (sometimes called request rate). The request rate is provided as \"N/U\" where N is an integer number and U is a time unit. Supported units are 's' (second), 'm' (minute), 'h' (hour) and 'd' /(day, as in a 24 hour unit). The default time unit, if no \"/U\" is provided, is number of transfers per hour.") + + // other flags + benchClientCmd.Flags().IntP("jobs", "j", 1, "Number of parallel clients to start. Defaults to 1.") + benchClientCmd.Flags().Int("every", 0, "Aggregate stats every time this amount of seconds has passed.") + benchClientCmd.Flags().String("bearer-token-command", "", "Command to execute for a bearer token, e.g. 'oidc-token opencloud'. When set, disables basic auth.") + + return benchClientCmd } type clientOptions struct { @@ -280,25 +240,13 @@ func client(o clientOptions) error { } // BenchmarkSyscallsCommand is the entrypoint for the benchmark syscalls command. -func BenchmarkSyscallsCommand(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: "syscalls", - Usage: "test the performance of syscalls", - Flags: []cli.Flag{ - &cli.StringFlag{ - Name: "path", - Usage: "Path to test", - }, - &cli.StringFlag{ - Name: "iterations", - Value: "100", - Usage: "Number of iterations to execute", - }, - }, - Category: "benchmark", - Action: func(c *cli.Context) error { +func BenchmarkSyscallsCommand(cfg *config.Config) *cobra.Command { + benchSysCallCmd := &cobra.Command{ + Use: "syscalls", + Short: "test the performance of syscalls", + RunE: func(cmd *cobra.Command, args []string) error { - path := c.String("path") + path := cmd.Flag("path").Value.String() if path == "" { f, err := os.CreateTemp("", "opencloud-bench-temp-") if err != nil { @@ -309,11 +257,16 @@ func BenchmarkSyscallsCommand(cfg *config.Config) *cli.Command { defer os.Remove(path) } - iterations := c.Int("iterations") - + iterations, err := cmd.Flags().GetInt("iterations") + if err != nil { + return err + } return benchmark(iterations, path) }, } + benchSysCallCmd.Flags().String("path", "", "Path to test") + benchSysCallCmd.Flags().Int("iterations", 100, "Number of iterations to execute") + return benchSysCallCmd } func benchmark(iterations int, path string) error { From dfa2577cf01b897ad2f16ad8ce06a61e7056e50d Mon Sep 17 00:00:00 2001 From: Christian Richter Date: Wed, 3 Dec 2025 14:17:02 +0100 Subject: [PATCH 48/82] migrate list cli command from urfave/cli to spf13/cobra Signed-off-by: Christian Richter --- opencloud/pkg/command/list.go | 37 +++++++++++++++-------------------- 1 file changed, 16 insertions(+), 21 deletions(-) diff --git a/opencloud/pkg/command/list.go b/opencloud/pkg/command/list.go index 91eac8d31a..405aabbce7 100644 --- a/opencloud/pkg/command/list.go +++ b/opencloud/pkg/command/list.go @@ -8,30 +8,17 @@ import ( "github.com/opencloud-eu/opencloud/opencloud/pkg/register" "github.com/opencloud-eu/opencloud/pkg/config" - "github.com/urfave/cli/v2" + + "github.com/spf13/cobra" + "github.com/spf13/viper" ) // ListCommand is the entrypoint for the list command. -func ListCommand(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: "list", - Usage: "list OpenCloud services running in the runtime (supervised mode)", - Category: "runtime", - Flags: []cli.Flag{ - &cli.StringFlag{ - Name: "hostname", - Value: "localhost", - EnvVars: []string{"OC_RUNTIME_HOST"}, - Destination: &cfg.Runtime.Host, - }, - &cli.StringFlag{ - Name: "port", - Value: "9250", - EnvVars: []string{"OC_RUNTIME_PORT"}, - Destination: &cfg.Runtime.Port, - }, - }, - Action: func(c *cli.Context) error { +func ListCommand(cfg *config.Config) *cobra.Command { + listCmd := &cobra.Command{ + Use: "list", + Short: "list OpenCloud services running in the runtime (supervised mode)", + RunE: func(cmd *cobra.Command, args []string) error { client, err := rpc.DialHTTP("tcp", net.JoinHostPort(cfg.Runtime.Host, cfg.Runtime.Port)) if err != nil { log.Fatalf("Failed to connect to the runtime. Has the runtime been started and did you configure the right runtime address (\"%s\")", cfg.Runtime.Host+":"+cfg.Runtime.Port) @@ -48,6 +35,14 @@ func ListCommand(cfg *config.Config) *cli.Command { return nil }, } + listCmd.Flags().String("hostname", "localhost", "hostname of the runtime") + viper.BindEnv("hostname", "OC_RUNTIME_HOST") + viper.BindPFlag("hostname", listCmd.Flags().Lookup("hostname")) + + listCmd.Flags().String("port", "9250", "port of the runtime") + viper.BindEnv("port", "OC_RUNTIME_PORT") + viper.BindPFlag("port", listCmd.Flags().Lookup("port")) + return listCmd } func init() { From 4794deb0ae85ae27befbd480b78d099aaad104a1 Mon Sep 17 00:00:00 2001 From: Christian Richter Date: Wed, 3 Dec 2025 14:23:48 +0100 Subject: [PATCH 49/82] migrate revisions cli command from urfave/cli to spf13/cobra Signed-off-by: Christian Richter --- opencloud/pkg/command/list.go | 2 +- opencloud/pkg/command/revisions.go | 89 +++++++++++------------------- 2 files changed, 33 insertions(+), 58 deletions(-) diff --git a/opencloud/pkg/command/list.go b/opencloud/pkg/command/list.go index 405aabbce7..e0d94d2ca6 100644 --- a/opencloud/pkg/command/list.go +++ b/opencloud/pkg/command/list.go @@ -8,7 +8,7 @@ import ( "github.com/opencloud-eu/opencloud/opencloud/pkg/register" "github.com/opencloud-eu/opencloud/pkg/config" - + "github.com/spf13/cobra" "github.com/spf13/viper" ) diff --git a/opencloud/pkg/command/revisions.go b/opencloud/pkg/command/revisions.go index 76d09aff7a..2ae0a0b6ab 100644 --- a/opencloud/pkg/command/revisions.go +++ b/opencloud/pkg/command/revisions.go @@ -15,7 +15,8 @@ import ( decomposeds3bs "github.com/opencloud-eu/reva/v2/pkg/storage/fs/decomposeds3/blobstore" "github.com/opencloud-eu/reva/v2/pkg/storage/fs/posix/lookup" "github.com/opencloud-eu/reva/v2/pkg/storagespace" - "github.com/urfave/cli/v2" + + "github.com/spf13/cobra" ) var ( @@ -24,75 +25,41 @@ var ( ) // RevisionsCommand is the entrypoint for the revisions command. -func RevisionsCommand(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: "revisions", - Usage: "OpenCloud revisions functionality", - Subcommands: []*cli.Command{ - PurgeRevisionsCommand(cfg), - }, - Before: func(_ *cli.Context) error { +func RevisionsCommand(cfg *config.Config) *cobra.Command { + revCmd := &cobra.Command{ + Use: "revisions", + Short: "OpenCloud revisions functionality", + PreRunE: func(cmd *cobra.Command, args []string) error { return configlog.ReturnError(parser.ParseConfig(cfg, true)) }, - Action: func(_ *cli.Context) error { + RunE: func(cmd *cobra.Command, args []string) error { fmt.Println("Read the docs") return nil }, } + revCmd.AddCommand(PurgeRevisionsCommand(cfg)) + + return revCmd } // PurgeRevisionsCommand allows removing all revisions from a storage provider. -func PurgeRevisionsCommand(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: "purge", - Usage: "purge revisions", - Flags: []cli.Flag{ - &cli.StringFlag{ - Name: "basepath", - Aliases: []string{"p"}, - Usage: "the basepath of the decomposedfs (e.g. /var/tmp/opencloud/storage/metadata)", - Required: true, - }, - &cli.StringFlag{ - Name: "blobstore", - Aliases: []string{"b"}, - Usage: "the blobstore type. Can be (none, decomposed, decomposeds3). Default decomposed. Note: When using decomposeds3 this needs same configuration as the storage-users service", - Value: "decomposed", - }, - &cli.BoolFlag{ - Name: "dry-run", - Usage: "do not delete anything, just print what would be deleted", - Value: true, - }, - &cli.BoolFlag{ - Name: "verbose", - Aliases: []string{"v"}, - Usage: "print verbose output", - Value: false, - }, - &cli.StringFlag{ - Name: "resource-id", - Aliases: []string{"r"}, - Usage: "purge all revisions of this file/space. If not set, all revisions will be purged", - }, - &cli.StringFlag{ - Name: "glob-mechanism", - Usage: "the glob mechanism to find all nodes. Can be 'glob', 'list' or 'workers'. 'glob' uses globbing with a single worker. 'workers' spawns multiple go routines, accelatering the command drastically but causing high cpu and ram usage. 'list' looks for references by listing directories with multiple workers. Default is 'glob'", - Value: "glob", - }, - }, - Action: func(c *cli.Context) error { - basePath := c.String("basepath") +func PurgeRevisionsCommand(cfg *config.Config) *cobra.Command { + revCmd := &cobra.Command{ + Use: "purge", + Short: "purge revisions", + RunE: func(cmd *cobra.Command, args []string) error { + basePath := cmd.Flag("basepath").Value.String() if basePath == "" { fmt.Println("basepath is required") - return cli.ShowCommandHelp(c, "revisions") + _ = cmd.Help() + return nil } var ( bs revisions.DelBlobstore err error ) - switch c.String("blobstore") { + switch cmd.Flag("blobstore").Value.String() { case "decomposeds3": bs, err = decomposeds3bs.New( cfg.StorageUsers.Drivers.DecomposedS3.Endpoint, @@ -115,12 +82,12 @@ func PurgeRevisionsCommand(cfg *config.Config) *cli.Command { } var rid *provider.ResourceId - resid, err := storagespace.ParseID(c.String("resource-id")) + resid, err := storagespace.ParseID(cmd.Flag("resource-id").Value.String()) if err == nil { rid = &resid } - mechanism := c.String("glob-mechanism") + mechanism := cmd.Flag("glob-mechanism").Value.String() if rid.GetOpaqueId() != "" { mechanism = "glob" } @@ -146,11 +113,19 @@ func PurgeRevisionsCommand(cfg *config.Config) *cli.Command { ch = revisions.List(p, 10) } - files, blobs, revisions := revisions.PurgeRevisions(ch, bs, c.Bool("dry-run"), c.Bool("verbose")) - printResults(files, blobs, revisions, c.Bool("dry-run")) + files, blobs, revisions := revisions.PurgeRevisions(ch, bs, cmd.Flag("dry-run").Changed, cmd.Flag("verbose").Changed) + printResults(files, blobs, revisions, cmd.Flag("dry-run").Changed) return nil }, } + revCmd.Flags().StringP("basepath", "p", "", "the basepath of the decomposedfs (e.g. /var/tmp/opencloud/storage/metadata)") + revCmd.Flags().StringP("blobstore", "b", "decomposed", "the blobstore type. Can be (none, decomposed, decomposeds3). Default decomposed") + revCmd.Flags().Bool("dry-run", true, "do not delete anything, just print what would be deleted") + revCmd.Flags().BoolP("verbose", "v", false, "print verbose output") + revCmd.Flags().StringP("resource-id", "r", "", "purge all revisions of this file/space. If not set, all revisions will be purged") + revCmd.Flags().String("glob-mechanism", "glob", "the glob mechanism to find all nodes. Can be 'glob', 'list' or 'workers'. 'glob' uses globbing with a single worker. 'workers' spawns multiple go routines, accelatering the command drastically but causing high cpu and ram usage. 'list' looks for references by listing directories with multiple workers. Default is 'glob'") + + return revCmd } func printResults(countFiles, countBlobs, countRevisions int, dryRun bool) { From 3f73faa3fe0dd8890835796c81661ac1098ff4ce Mon Sep 17 00:00:00 2001 From: Christian Richter Date: Wed, 3 Dec 2025 14:51:31 +0100 Subject: [PATCH 50/82] migrate shares cli command from urfave/cli to spf13/cobra Signed-off-by: Christian Richter --- opencloud/pkg/command/shares.go | 90 ++++++++++++++++++--------------- 1 file changed, 48 insertions(+), 42 deletions(-) diff --git a/opencloud/pkg/command/shares.go b/opencloud/pkg/command/shares.go index e2bb37a0e4..ccecacf85f 100644 --- a/opencloud/pkg/command/shares.go +++ b/opencloud/pkg/command/shares.go @@ -2,14 +2,7 @@ package command import ( "errors" - - "github.com/rs/zerolog" - "github.com/urfave/cli/v2" - - "github.com/opencloud-eu/reva/v2/pkg/rgrpc/todo/pool" - "github.com/opencloud-eu/reva/v2/pkg/share/manager/jsoncs3" - "github.com/opencloud-eu/reva/v2/pkg/share/manager/registry" - "github.com/opencloud-eu/reva/v2/pkg/utils" + "fmt" "github.com/opencloud-eu/opencloud/opencloud/pkg/register" "github.com/opencloud-eu/opencloud/pkg/config" @@ -19,15 +12,22 @@ import ( mregistry "github.com/opencloud-eu/opencloud/pkg/registry" sharing "github.com/opencloud-eu/opencloud/services/sharing/pkg/config" sharingparser "github.com/opencloud-eu/opencloud/services/sharing/pkg/config/parser" + "github.com/opencloud-eu/reva/v2/pkg/rgrpc/todo/pool" + "github.com/opencloud-eu/reva/v2/pkg/share/manager/jsoncs3" + "github.com/opencloud-eu/reva/v2/pkg/share/manager/registry" + "github.com/opencloud-eu/reva/v2/pkg/utils" + "github.com/spf13/viper" + + "github.com/rs/zerolog" + "github.com/spf13/cobra" ) // SharesCommand is the entrypoint for the groups command. -func SharesCommand(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: "shares", - Usage: `cli tools to manage entries in the share manager.`, - Category: "maintenance", - Before: func(c *cli.Context) error { +func SharesCommand(cfg *config.Config) *cobra.Command { + sharesCmd := &cobra.Command{ + Use: "shares", + Short: `cli tools to manage entries in the share manager.`, + PreRunE: func(cmd *cobra.Command, args []string) error { // Parse base config if err := parser.ParseConfig(cfg, true); err != nil { return configlog.ReturnError(err) @@ -37,37 +37,21 @@ func SharesCommand(cfg *config.Config) *cli.Command { cfg.Sharing.Commons = cfg.Commons return configlog.ReturnError(sharingparser.ParseConfig(cfg.Sharing)) }, - Subcommands: []*cli.Command{ - cleanupCmd(cfg), - }, } + sharesCmd.AddCommand(cleanupCmd(cfg)) + + return sharesCmd } func init() { register.AddCommand(SharesCommand) } -func cleanupCmd(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: "cleanup", - Usage: `clean up stale entries in the share manager.`, - Flags: []cli.Flag{ - &cli.StringFlag{ - Name: "service-account-id", - Value: "", - Usage: "Name of the service account to use for the cleanup", - EnvVars: []string{"OC_SERVICE_ACCOUNT_ID"}, - Required: true, - }, - &cli.StringFlag{ - Name: "service-account-secret", - Value: "", - Usage: "Secret for the service account", - EnvVars: []string{"OC_SERVICE_ACCOUNT_SECRET"}, - Required: true, - }, - }, - Before: func(c *cli.Context) error { +func cleanupCmd(cfg *config.Config) *cobra.Command { + cleanCmd := &cobra.Command{ + Use: "cleanup", + Short: `clean up stale entries in the share manager.`, + PreRunE: func(cmd *cobra.Command, args []string) error { // Parse base config if err := parser.ParseConfig(cfg, true); err != nil { return configlog.ReturnError(err) @@ -77,13 +61,34 @@ func cleanupCmd(cfg *config.Config) *cli.Command { cfg.Sharing.Commons = cfg.Commons return configlog.ReturnError(sharingparser.ParseConfig(cfg.Sharing)) }, - Action: func(c *cli.Context) error { - return cleanup(c, cfg) + RunE: func(cmd *cobra.Command, args []string) error { + return cleanup(cmd, cfg) }, } + cleanCmd.Flags().String("service-account-id", "", "Name of the service account to use for the cleanup") + err := viper.BindEnv("service-account-id", "OC_SERVICE_ACCOUNT_ID") + if err != nil { + fmt.Printf("Could not bind environment variable OC_SERVICE_ACCOUNT_ID: %s", err) + } + err = viper.BindPFlag("service-account-id", cleanCmd.Flags().Lookup("service-account-id")) + if err != nil { + fmt.Printf("Could not bind flag OC_SERVICE_ACCOUNT_ID: %s", err) + } + + cleanCmd.Flags().String("service-account-secret", "", "Secret for the service account") + err = viper.BindEnv("service-account-secret", "OC_SERVICE_ACCOUNT_SECRET") + if err != nil { + fmt.Printf("Could not bind environment variable OC_SERVICE_ACCOUNT_SECRET: %s", err) + } + err = viper.BindPFlag("service-account-secret", cleanCmd.Flags().Lookup("service-account-secret")) + if err != nil { + fmt.Printf("Could not bind flag OC_SERVICE_ACCOUNT_SECRET: %s", err) + } + + return cleanCmd } -func cleanup(c *cli.Context, cfg *config.Config) error { +func cleanup(cmd *cobra.Command, cfg *config.Config) error { driver := cfg.Sharing.UserSharingDriver // cleanup is only implemented for the jsoncs3 share manager if driver != "jsoncs3" { @@ -114,7 +119,8 @@ func cleanup(c *cli.Context, cfg *config.Config) error { return configlog.ReturnError(err) } - serviceUserCtx, err := utils.GetServiceUserContext(c.String("service-account-id"), client, c.String("service-account-secret")) + serviceUserCtx, err := utils.GetServiceUserContext(cmd.Flag("service-account-id").Value.String(), + client, cmd.Flag("service-account-secret").Value.String()) if err != nil { return configlog.ReturnError(err) } From 7c2eb190500fb3b06904e5fd62bc91dbd8bc20c0 Mon Sep 17 00:00:00 2001 From: Christian Richter Date: Wed, 3 Dec 2025 14:56:59 +0100 Subject: [PATCH 51/82] migrate services.go from urfave/cli to spf13/cobra Signed-off-by: Christian Richter --- opencloud/pkg/command/services.go | 102 +++++++++++++++--------------- 1 file changed, 51 insertions(+), 51 deletions(-) diff --git a/opencloud/pkg/command/services.go b/opencloud/pkg/command/services.go index e87548979c..f6b05e843b 100644 --- a/opencloud/pkg/command/services.go +++ b/opencloud/pkg/command/services.go @@ -1,8 +1,6 @@ package command import ( - "github.com/urfave/cli/v2" - "github.com/opencloud-eu/opencloud/opencloud/pkg/command/helper" "github.com/opencloud-eu/opencloud/opencloud/pkg/register" "github.com/opencloud-eu/opencloud/pkg/config" @@ -50,215 +48,217 @@ import ( web "github.com/opencloud-eu/opencloud/services/web/pkg/command" webdav "github.com/opencloud-eu/opencloud/services/webdav/pkg/command" webfinger "github.com/opencloud-eu/opencloud/services/webfinger/pkg/command" + + "github.com/spf13/cobra" ) var svccmds = []register.Command{ - func(cfg *config.Config) *cli.Command { + func(cfg *config.Config) *cobra.Command { return ServiceCommand(cfg, cfg.Activitylog.Service.Name, activitylog.GetCommands(cfg.Activitylog), func(c *config.Config) { cfg.Activitylog.Commons = cfg.Commons }) }, - func(cfg *config.Config) *cli.Command { + func(cfg *config.Config) *cobra.Command { return ServiceCommand(cfg, cfg.Antivirus.Service.Name, antivirus.GetCommands(cfg.Antivirus), func(c *config.Config) { // cfg.Antivirus.Commons = cfg.Commons // antivirus needs no commons atm }) }, - func(cfg *config.Config) *cli.Command { + func(cfg *config.Config) *cobra.Command { return ServiceCommand(cfg, cfg.AppProvider.Service.Name, appprovider.GetCommands(cfg.AppProvider), func(c *config.Config) { cfg.AppProvider.Commons = cfg.Commons }) }, - func(cfg *config.Config) *cli.Command { + func(cfg *config.Config) *cobra.Command { return ServiceCommand(cfg, cfg.AppRegistry.Service.Name, appregistry.GetCommands(cfg.AppRegistry), func(c *config.Config) { cfg.AppRegistry.Commons = cfg.Commons }) }, - func(cfg *config.Config) *cli.Command { + func(cfg *config.Config) *cobra.Command { return ServiceCommand(cfg, cfg.Audit.Service.Name, audit.GetCommands(cfg.Audit), func(c *config.Config) { cfg.Audit.Commons = cfg.Commons }) }, - func(cfg *config.Config) *cli.Command { + func(cfg *config.Config) *cobra.Command { return ServiceCommand(cfg, cfg.AuthApp.Service.Name, authapp.GetCommands(cfg.AuthApp), func(_ *config.Config) { cfg.AuthApp.Commons = cfg.Commons }) }, - func(cfg *config.Config) *cli.Command { + func(cfg *config.Config) *cobra.Command { return ServiceCommand(cfg, cfg.AuthBasic.Service.Name, authbasic.GetCommands(cfg.AuthBasic), func(c *config.Config) { cfg.AuthBasic.Commons = cfg.Commons }) }, - func(cfg *config.Config) *cli.Command { + func(cfg *config.Config) *cobra.Command { return ServiceCommand(cfg, cfg.AuthBearer.Service.Name, authbearer.GetCommands(cfg.AuthBearer), func(c *config.Config) { cfg.AuthBearer.Commons = cfg.Commons }) }, - func(cfg *config.Config) *cli.Command { + func(cfg *config.Config) *cobra.Command { return ServiceCommand(cfg, cfg.AuthMachine.Service.Name, authmachine.GetCommands(cfg.AuthMachine), func(c *config.Config) { cfg.AuthMachine.Commons = cfg.Commons }) }, - func(cfg *config.Config) *cli.Command { + func(cfg *config.Config) *cobra.Command { return ServiceCommand(cfg, cfg.AuthService.Service.Name, authservice.GetCommands(cfg.AuthService), func(c *config.Config) { cfg.AuthService.Commons = cfg.Commons }) }, - func(cfg *config.Config) *cli.Command { + func(cfg *config.Config) *cobra.Command { return ServiceCommand(cfg, cfg.Clientlog.Service.Name, clientlog.GetCommands(cfg.Clientlog), func(c *config.Config) { cfg.Clientlog.Commons = cfg.Commons }) }, - func(cfg *config.Config) *cli.Command { + func(cfg *config.Config) *cobra.Command { return ServiceCommand(cfg, cfg.Collaboration.Service.Name, collaboration.GetCommands(cfg.Collaboration), func(c *config.Config) { cfg.Collaboration.Commons = cfg.Commons }) }, - func(cfg *config.Config) *cli.Command { + func(cfg *config.Config) *cobra.Command { return ServiceCommand(cfg, cfg.EventHistory.Service.Name, eventhistory.GetCommands(cfg.EventHistory), func(c *config.Config) { cfg.EventHistory.Commons = cfg.Commons }) }, - func(cfg *config.Config) *cli.Command { + func(cfg *config.Config) *cobra.Command { return ServiceCommand(cfg, cfg.Frontend.Service.Name, frontend.GetCommands(cfg.Frontend), func(c *config.Config) { cfg.Frontend.Commons = cfg.Commons }) }, - func(cfg *config.Config) *cli.Command { + func(cfg *config.Config) *cobra.Command { return ServiceCommand(cfg, cfg.Gateway.Service.Name, gateway.GetCommands(cfg.Gateway), func(c *config.Config) { cfg.Gateway.Commons = cfg.Commons }) }, - func(cfg *config.Config) *cli.Command { + func(cfg *config.Config) *cobra.Command { return ServiceCommand(cfg, cfg.Graph.Service.Name, graph.GetCommands(cfg.Graph), func(c *config.Config) { cfg.Graph.Commons = cfg.Commons }) }, - func(cfg *config.Config) *cli.Command { + func(cfg *config.Config) *cobra.Command { return ServiceCommand(cfg, cfg.Groups.Service.Name, groups.GetCommands(cfg.Groups), func(c *config.Config) { cfg.Groups.Commons = cfg.Commons }) }, - func(cfg *config.Config) *cli.Command { + func(cfg *config.Config) *cobra.Command { return ServiceCommand(cfg, cfg.IDM.Service.Name, idm.GetCommands(cfg.IDM), func(c *config.Config) { cfg.IDM.Commons = cfg.Commons }) }, - func(cfg *config.Config) *cli.Command { + func(cfg *config.Config) *cobra.Command { return ServiceCommand(cfg, cfg.IDP.Service.Name, idp.GetCommands(cfg.IDP), func(c *config.Config) { cfg.IDP.Commons = cfg.Commons }) }, - func(cfg *config.Config) *cli.Command { + func(cfg *config.Config) *cobra.Command { return ServiceCommand(cfg, cfg.Invitations.Service.Name, invitations.GetCommands(cfg.Invitations), func(c *config.Config) { cfg.Invitations.Commons = cfg.Commons }) }, - func(cfg *config.Config) *cli.Command { + func(cfg *config.Config) *cobra.Command { return ServiceCommand(cfg, cfg.Nats.Service.Name, nats.GetCommands(cfg.Nats), func(c *config.Config) { cfg.Nats.Commons = cfg.Commons }) }, - func(cfg *config.Config) *cli.Command { + func(cfg *config.Config) *cobra.Command { return ServiceCommand(cfg, cfg.Notifications.Service.Name, notifications.GetCommands(cfg.Notifications), func(c *config.Config) { cfg.Notifications.Commons = cfg.Commons }) }, - func(cfg *config.Config) *cli.Command { + func(cfg *config.Config) *cobra.Command { return ServiceCommand(cfg, cfg.OCDav.Service.Name, ocdav.GetCommands(cfg.OCDav), func(c *config.Config) { cfg.OCDav.Commons = cfg.Commons }) }, - func(cfg *config.Config) *cli.Command { + func(cfg *config.Config) *cobra.Command { return ServiceCommand(cfg, cfg.OCM.Service.Name, ocm.GetCommands(cfg.OCM), func(c *config.Config) { cfg.OCM.Commons = cfg.Commons }) }, - func(cfg *config.Config) *cli.Command { + func(cfg *config.Config) *cobra.Command { return ServiceCommand(cfg, cfg.OCS.Service.Name, ocs.GetCommands(cfg.OCS), func(c *config.Config) { cfg.OCS.Commons = cfg.Commons }) }, - func(cfg *config.Config) *cli.Command { + func(cfg *config.Config) *cobra.Command { return ServiceCommand(cfg, cfg.Policies.Service.Name, policies.GetCommands(cfg.Policies), func(c *config.Config) { cfg.Policies.Commons = cfg.Commons }) }, - func(cfg *config.Config) *cli.Command { + func(cfg *config.Config) *cobra.Command { return ServiceCommand(cfg, cfg.Postprocessing.Service.Name, postprocessing.GetCommands(cfg.Postprocessing), func(c *config.Config) { cfg.Postprocessing.Commons = cfg.Commons }) }, - func(cfg *config.Config) *cli.Command { + func(cfg *config.Config) *cobra.Command { return ServiceCommand(cfg, cfg.Proxy.Service.Name, proxy.GetCommands(cfg.Proxy), func(c *config.Config) { cfg.Proxy.Commons = cfg.Commons }) }, - func(cfg *config.Config) *cli.Command { + func(cfg *config.Config) *cobra.Command { return ServiceCommand(cfg, cfg.Search.Service.Name, search.GetCommands(cfg.Search), func(c *config.Config) { cfg.Search.Commons = cfg.Commons }) }, - func(cfg *config.Config) *cli.Command { + func(cfg *config.Config) *cobra.Command { return ServiceCommand(cfg, cfg.Settings.Service.Name, settings.GetCommands(cfg.Settings), func(c *config.Config) { cfg.Settings.Commons = cfg.Commons }) }, - func(cfg *config.Config) *cli.Command { + func(cfg *config.Config) *cobra.Command { return ServiceCommand(cfg, cfg.Sharing.Service.Name, sharing.GetCommands(cfg.Sharing), func(c *config.Config) { cfg.Sharing.Commons = cfg.Commons }) }, - func(cfg *config.Config) *cli.Command { + func(cfg *config.Config) *cobra.Command { return ServiceCommand(cfg, cfg.SSE.Service.Name, sse.GetCommands(cfg.SSE), func(c *config.Config) { cfg.SSE.Commons = cfg.Commons }) }, - func(cfg *config.Config) *cli.Command { + func(cfg *config.Config) *cobra.Command { return ServiceCommand(cfg, cfg.StoragePublicLink.Service.Name, storagepubliclink.GetCommands(cfg.StoragePublicLink), func(c *config.Config) { cfg.StoragePublicLink.Commons = cfg.Commons }) }, - func(cfg *config.Config) *cli.Command { + func(cfg *config.Config) *cobra.Command { return ServiceCommand(cfg, cfg.StorageShares.Service.Name, storageshares.GetCommands(cfg.StorageShares), func(c *config.Config) { cfg.StorageShares.Commons = cfg.Commons }) }, - func(cfg *config.Config) *cli.Command { + func(cfg *config.Config) *cobra.Command { return ServiceCommand(cfg, cfg.StorageSystem.Service.Name, storagesystem.GetCommands(cfg.StorageSystem), func(c *config.Config) { cfg.StorageSystem.Commons = cfg.Commons }) }, - func(cfg *config.Config) *cli.Command { + func(cfg *config.Config) *cobra.Command { return ServiceCommand(cfg, cfg.StorageUsers.Service.Name, storageusers.GetCommands(cfg.StorageUsers), func(c *config.Config) { cfg.StorageUsers.Commons = cfg.Commons }) }, - func(cfg *config.Config) *cli.Command { + func(cfg *config.Config) *cobra.Command { return ServiceCommand(cfg, cfg.Thumbnails.Service.Name, thumbnails.GetCommands(cfg.Thumbnails), func(c *config.Config) { cfg.Thumbnails.Commons = cfg.Commons }) }, - func(cfg *config.Config) *cli.Command { + func(cfg *config.Config) *cobra.Command { return ServiceCommand(cfg, cfg.Userlog.Service.Name, userlog.GetCommands(cfg.Userlog), func(c *config.Config) { cfg.Userlog.Commons = cfg.Commons }) }, - func(cfg *config.Config) *cli.Command { + func(cfg *config.Config) *cobra.Command { return ServiceCommand(cfg, cfg.Users.Service.Name, users.GetCommands(cfg.Users), func(c *config.Config) { cfg.Users.Commons = cfg.Commons }) }, - func(cfg *config.Config) *cli.Command { + func(cfg *config.Config) *cobra.Command { return ServiceCommand(cfg, cfg.Web.Service.Name, web.GetCommands(cfg.Web), func(c *config.Config) { cfg.Web.Commons = cfg.Commons }) }, - func(cfg *config.Config) *cli.Command { + func(cfg *config.Config) *cobra.Command { return ServiceCommand(cfg, cfg.WebDAV.Service.Name, webdav.GetCommands(cfg.WebDAV), func(c *config.Config) { cfg.WebDAV.Commons = cfg.Commons }) }, - func(cfg *config.Config) *cli.Command { + func(cfg *config.Config) *cobra.Command { return ServiceCommand(cfg, cfg.Webfinger.Service.Name, webfinger.GetCommands(cfg.Webfinger), func(c *config.Config) { cfg.Webfinger.Commons = cfg.Commons }) @@ -266,18 +266,18 @@ var svccmds = []register.Command{ } // ServiceCommand is the entry point for the all service commands. -func ServiceCommand(cfg *config.Config, serviceName string, subcommands []*cli.Command, f func(*config.Config)) *cli.Command { - return &cli.Command{ - Name: serviceName, - Usage: helper.SubcommandDescription(serviceName), - Category: "services", - Before: func(c *cli.Context) error { +func ServiceCommand(cfg *config.Config, serviceName string, subcommands []*cobra.Command, f func(*config.Config)) *cobra.Command { + svcCommand := &cobra.Command{ + Use: serviceName, + Short: helper.SubcommandDescription(serviceName), + RunE: func(cmd *cobra.Command, args []string) error { configlog.Error(parser.ParseConfig(cfg, true)) f(cfg) return nil }, - Subcommands: subcommands, } + svcCommand.AddCommand(subcommands...) + return svcCommand } func init() { From d89054484de50de97b53df48e087a291bc562c95 Mon Sep 17 00:00:00 2001 From: Christian Richter Date: Wed, 3 Dec 2025 15:01:38 +0100 Subject: [PATCH 52/82] migrate oc root cli command from urfave/cli to spf13/cobra Signed-off-by: Christian Richter --- opencloud/pkg/command/root.go | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/opencloud/pkg/command/root.go b/opencloud/pkg/command/root.go index 82623359fd..77be4594a2 100644 --- a/opencloud/pkg/command/root.go +++ b/opencloud/pkg/command/root.go @@ -9,25 +9,23 @@ import ( "github.com/opencloud-eu/opencloud/opencloud/pkg/register" "github.com/opencloud-eu/opencloud/pkg/clihelper" "github.com/opencloud-eu/opencloud/pkg/config" - "github.com/urfave/cli/v2" + + "github.com/spf13/cobra" ) // Execute is the entry point for the opencloud command. func Execute() error { cfg := config.DefaultConfig() - app := clihelper.DefaultApp(&cli.App{ - Name: "opencloud", - Usage: "opencloud", + app := clihelper.DefaultAppCobra(&cobra.Command{ + Use: "opencloud", + Short: "opencloud", }) for _, fn := range register.Commands { - app.Commands = append( - app.Commands, - fn(cfg), - ) + app.AddCommand(fn(cfg)) } - + app.SetArgs(os.Args[1:]) ctx, _ := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT, syscall.SIGHUP) - return app.RunContext(ctx, os.Args) + return app.ExecuteContext(ctx) } From 6d054c527c967bf2d0ee977a97d3928b1350e204 Mon Sep 17 00:00:00 2001 From: Christian Richter Date: Wed, 3 Dec 2025 15:06:00 +0100 Subject: [PATCH 53/82] migrate trash cli command from urfave/cli to spf13/cobra Signed-off-by: Christian Richter --- opencloud/pkg/command/trash.go | 63 ++++++++++++++++------------------ 1 file changed, 30 insertions(+), 33 deletions(-) diff --git a/opencloud/pkg/command/trash.go b/opencloud/pkg/command/trash.go index 9b26ca9068..0f28666bd1 100644 --- a/opencloud/pkg/command/trash.go +++ b/opencloud/pkg/command/trash.go @@ -3,57 +3,45 @@ package command import ( "fmt" - "github.com/opencloud-eu/opencloud/opencloud/pkg/trash" - "github.com/opencloud-eu/opencloud/opencloud/pkg/register" + "github.com/opencloud-eu/opencloud/opencloud/pkg/trash" "github.com/opencloud-eu/opencloud/pkg/config" "github.com/opencloud-eu/opencloud/pkg/config/configlog" "github.com/opencloud-eu/opencloud/pkg/config/parser" - "github.com/urfave/cli/v2" + + "github.com/spf13/cobra" ) -func TrashCommand(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: "trash", - Usage: "OpenCloud trash functionality", - Subcommands: []*cli.Command{ - TrashPurgeEmptyDirsCommand(cfg), - }, - Before: func(c *cli.Context) error { +func TrashCommand(cfg *config.Config) *cobra.Command { + trashCmd := &cobra.Command{ + Use: "trash", + Short: "OpenCloud trash functionality", + PreRunE: func(cmd *cobra.Command, args []string) error { return configlog.ReturnError(parser.ParseConfig(cfg, true)) }, - Action: func(_ *cli.Context) error { + RunE: func(cmd *cobra.Command, args []string) error { fmt.Println("Read the docs") return nil }, } + trashCmd.AddCommand(TrashPurgeEmptyDirsCommand(cfg)) + + return trashCmd } -func TrashPurgeEmptyDirsCommand(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: "purge-empty-dirs", - Usage: "purge empty directories", - Flags: []cli.Flag{ - &cli.StringFlag{ - Name: "basepath", - Aliases: []string{"p"}, - Usage: "the basepath of the decomposedfs (e.g. /var/tmp/opencloud/storage/users)", - Required: true, - }, - &cli.BoolFlag{ - Name: "dry-run", - Usage: "do not delete anything, just print what would be deleted", - Value: true, - }, - }, - Action: func(c *cli.Context) error { - basePath := c.String("basepath") +func TrashPurgeEmptyDirsCommand(cfg *config.Config) *cobra.Command { + trashPurgeCmd := &cobra.Command{ + Use: "purge-empty-dirs", + Short: "purge empty directories", + RunE: func(cmd *cobra.Command, args []string) error { + basePath := cmd.Flag("basepath").Value.String() if basePath == "" { fmt.Println("basepath is required") - return cli.ShowCommandHelp(c, "trash") + _ = cmd.Help() + return nil } - if err := trash.PurgeTrashEmptyPaths(basePath, c.Bool("dry-run")); err != nil { + if err := trash.PurgeTrashEmptyPaths(basePath, cmd.Flag("dry-run").Changed); err != nil { fmt.Println(err) return err } @@ -61,6 +49,15 @@ func TrashPurgeEmptyDirsCommand(cfg *config.Config) *cli.Command { return nil }, } + trashPurgeCmd.Flags().StringP("basepath", "p", "", "the basepath of the decomposedfs (e.g. /var/tmp/opencloud/storage/users)") + err := trashPurgeCmd.MarkFlagRequired("basepath") + if err != nil { + fmt.Println(err) + } + + trashPurgeCmd.Flags().Bool("dry-run", true, "do not delete anything, just print what would be deleted") + + return trashPurgeCmd } func init() { From 7be33b0607132254cce1bdddac174e3362b3ecba Mon Sep 17 00:00:00 2001 From: Christian Richter Date: Wed, 3 Dec 2025 15:08:01 +0100 Subject: [PATCH 54/82] refactor interims DefaultAppCobra to DefaultApp Signed-off-by: Christian Richter --- opencloud/pkg/command/root.go | 2 +- pkg/clihelper/app.go | 27 +++---------------- services/activitylog/pkg/command/root.go | 2 +- services/antivirus/pkg/command/root.go | 2 +- services/app-provider/pkg/command/root.go | 2 +- services/app-registry/pkg/command/root.go | 2 +- services/audit/pkg/command/root.go | 2 +- services/auth-app/pkg/command/root.go | 2 +- services/auth-basic/pkg/command/root.go | 2 +- services/auth-bearer/pkg/command/root.go | 2 +- services/auth-machine/pkg/command/root.go | 2 +- services/auth-service/pkg/command/root.go | 2 +- services/clientlog/pkg/command/root.go | 2 +- services/collaboration/pkg/command/root.go | 2 +- services/eventhistory/pkg/command/root.go | 2 +- services/frontend/pkg/command/root.go | 2 +- services/gateway/pkg/command/root.go | 2 +- services/graph/pkg/command/root.go | 2 +- services/groups/pkg/command/root.go | 2 +- services/idm/pkg/command/root.go | 2 +- services/idp/pkg/command/root.go | 2 +- services/invitations/pkg/command/root.go | 2 +- services/nats/pkg/command/root.go | 2 +- services/notifications/pkg/command/root.go | 2 +- services/ocdav/pkg/command/root.go | 2 +- services/ocm/pkg/command/root.go | 2 +- services/ocs/pkg/command/root.go | 2 +- services/policies/pkg/command/root.go | 2 +- services/postprocessing/pkg/command/root.go | 2 +- services/proxy/pkg/command/root.go | 2 +- services/search/pkg/command/root.go | 2 +- services/settings/pkg/command/root.go | 2 +- services/sharing/pkg/command/root.go | 2 +- services/sse/pkg/command/root.go | 2 +- .../storage-publiclink/pkg/command/root.go | 2 +- services/storage-shares/pkg/command/root.go | 2 +- services/storage-system/pkg/command/root.go | 2 +- services/storage-users/pkg/command/root.go | 2 +- services/thumbnails/pkg/command/root.go | 2 +- services/userlog/pkg/command/root.go | 2 +- services/users/pkg/command/root.go | 2 +- services/web/pkg/command/root.go | 2 +- services/webdav/pkg/command/root.go | 2 +- services/webfinger/pkg/command/root.go | 2 +- 44 files changed, 46 insertions(+), 67 deletions(-) diff --git a/opencloud/pkg/command/root.go b/opencloud/pkg/command/root.go index 77be4594a2..ac1dc4bace 100644 --- a/opencloud/pkg/command/root.go +++ b/opencloud/pkg/command/root.go @@ -17,7 +17,7 @@ import ( func Execute() error { cfg := config.DefaultConfig() - app := clihelper.DefaultAppCobra(&cobra.Command{ + app := clihelper.DefaultApp(&cobra.Command{ Use: "opencloud", Short: "opencloud", }) diff --git a/pkg/clihelper/app.go b/pkg/clihelper/app.go index 814b32326a..e2543faff0 100644 --- a/pkg/clihelper/app.go +++ b/pkg/clihelper/app.go @@ -4,33 +4,12 @@ import ( "fmt" "github.com/opencloud-eu/opencloud/pkg/version" + "github.com/spf13/cobra" - "github.com/urfave/cli/v2" ) -// DefaultApp provides some default settings for the cli app -func DefaultApp(app *cli.App) *cli.App { - // version info - app.Version = version.String - app.Compiled = version.Compiled() - - // author info - app.Authors = []*cli.Author{ - { - Name: "OpenCloud GmbH", - Email: "support@opencloud.eu", - }, - } - - // disable global version flag - // instead we provide the version command - app.HideVersion = true - - return app -} - -// DefaultAppCobra is a wrapper for DefaultApp that adds Cobra specific settings -func DefaultAppCobra(app *cobra.Command) *cobra.Command { +// DefaultApp is a wrapper for DefaultApp that adds Cobra specific settings +func DefaultApp(app *cobra.Command) *cobra.Command { // TODO: when migration is done this has to become DefaultApp // version info app.Version = fmt.Sprintf("%s (%s <%s>) (%s)", version.String, "OpenCloud GmbH", "support@opencloud.eu", version.Compiled()) diff --git a/services/activitylog/pkg/command/root.go b/services/activitylog/pkg/command/root.go index c61aec8809..e922bd7810 100644 --- a/services/activitylog/pkg/command/root.go +++ b/services/activitylog/pkg/command/root.go @@ -24,7 +24,7 @@ func GetCommands(cfg *config.Config) []*cobra.Command { // Execute is the entry point for the activitylog command. func Execute(cfg *config.Config) error { - app := clihelper.DefaultAppCobra(&cobra.Command{ + app := clihelper.DefaultApp(&cobra.Command{ Use: "activitylog", Short: "starts activitylog service", }) diff --git a/services/antivirus/pkg/command/root.go b/services/antivirus/pkg/command/root.go index ecea2c6043..5d9f248b40 100644 --- a/services/antivirus/pkg/command/root.go +++ b/services/antivirus/pkg/command/root.go @@ -19,7 +19,7 @@ func GetCommands(cfg *config.Config) []*cobra.Command { // Execute is the entry point for the antivirus command. func Execute(cfg *config.Config) error { - app := clihelper.DefaultAppCobra(&cobra.Command{ + app := clihelper.DefaultApp(&cobra.Command{ Use: "antivirus", Short: "Antivirus service for OpenCloud", }) diff --git a/services/app-provider/pkg/command/root.go b/services/app-provider/pkg/command/root.go index 052e17d76a..2b16a9e32a 100644 --- a/services/app-provider/pkg/command/root.go +++ b/services/app-provider/pkg/command/root.go @@ -24,7 +24,7 @@ func GetCommands(cfg *config.Config) []*cobra.Command { // Execute is the entry point for the opencloud app-provider command. func Execute(cfg *config.Config) error { - app := clihelper.DefaultAppCobra(&cobra.Command{ + app := clihelper.DefaultApp(&cobra.Command{ Use: "app-provider", Short: "Provide apps for OpenCloud", }) diff --git a/services/app-registry/pkg/command/root.go b/services/app-registry/pkg/command/root.go index d30cdff5f2..3bb6da0158 100644 --- a/services/app-registry/pkg/command/root.go +++ b/services/app-registry/pkg/command/root.go @@ -24,7 +24,7 @@ func GetCommands(cfg *config.Config) []*cobra.Command { // Execute is the entry point for the opencloud app-registry command. func Execute(cfg *config.Config) error { - app := clihelper.DefaultAppCobra(&cobra.Command{ + app := clihelper.DefaultApp(&cobra.Command{ Use: "app-registry", Short: "Provide an app registry for OpenCloud", }) diff --git a/services/audit/pkg/command/root.go b/services/audit/pkg/command/root.go index 447ab92706..0adf9bf877 100644 --- a/services/audit/pkg/command/root.go +++ b/services/audit/pkg/command/root.go @@ -24,7 +24,7 @@ func GetCommands(cfg *config.Config) []*cobra.Command { // Execute is the entry point for the audit command. func Execute(cfg *config.Config) error { - app := clihelper.DefaultAppCobra(&cobra.Command{ + app := clihelper.DefaultApp(&cobra.Command{ Use: "audit", Short: "starts audit service", }) diff --git a/services/auth-app/pkg/command/root.go b/services/auth-app/pkg/command/root.go index f05588e095..51fdf46be3 100644 --- a/services/auth-app/pkg/command/root.go +++ b/services/auth-app/pkg/command/root.go @@ -26,7 +26,7 @@ func GetCommands(cfg *config.Config) []*cobra.Command { // Execute is the entry point for the opencloud auth-app command. func Execute(cfg *config.Config) error { - app := clihelper.DefaultAppCobra(&cobra.Command{ + app := clihelper.DefaultApp(&cobra.Command{ Use: "auth-app", Short: "Provide app authentication for OpenCloud", }) diff --git a/services/auth-basic/pkg/command/root.go b/services/auth-basic/pkg/command/root.go index 3c1299ed2a..e7b5482ce1 100644 --- a/services/auth-basic/pkg/command/root.go +++ b/services/auth-basic/pkg/command/root.go @@ -24,7 +24,7 @@ func GetCommands(cfg *config.Config) []*cobra.Command { // Execute is the entry point for the opencloud auth-basic command. func Execute(cfg *config.Config) error { - app := clihelper.DefaultAppCobra(&cobra.Command{ + app := clihelper.DefaultApp(&cobra.Command{ Use: "auth-basic", Short: "Provide basic authentication for OpenCloud", }) diff --git a/services/auth-bearer/pkg/command/root.go b/services/auth-bearer/pkg/command/root.go index f79741f599..599f733b76 100644 --- a/services/auth-bearer/pkg/command/root.go +++ b/services/auth-bearer/pkg/command/root.go @@ -28,7 +28,7 @@ func GetCommands(cfg *config.Config) []*cobra.Command { // Execute is the entry point for the opencloud auth-bearer command. func Execute(cfg *config.Config) error { - app := clihelper.DefaultAppCobra(&cobra.Command{ + app := clihelper.DefaultApp(&cobra.Command{ Use: "auth-bearer", Short: "Provide bearer authentication for OpenCloud", }) diff --git a/services/auth-machine/pkg/command/root.go b/services/auth-machine/pkg/command/root.go index 918cb998e2..080b1c6539 100644 --- a/services/auth-machine/pkg/command/root.go +++ b/services/auth-machine/pkg/command/root.go @@ -25,7 +25,7 @@ func GetCommands(cfg *config.Config) []*cobra.Command { // Execute is the entry point for the opencloud auth-machine command. func Execute(cfg *config.Config) error { - app := clihelper.DefaultAppCobra(&cobra.Command{ + app := clihelper.DefaultApp(&cobra.Command{ Use: "auth-machine", Short: "Provide machine authentication for OpenCloud", }) diff --git a/services/auth-service/pkg/command/root.go b/services/auth-service/pkg/command/root.go index 0d36a806a3..1217ef44b9 100644 --- a/services/auth-service/pkg/command/root.go +++ b/services/auth-service/pkg/command/root.go @@ -25,7 +25,7 @@ func GetCommands(cfg *config.Config) []*cobra.Command { // Execute is the entry point for the opencloud auth-service command. func Execute(cfg *config.Config) error { - app := clihelper.DefaultAppCobra(&cobra.Command{ + app := clihelper.DefaultApp(&cobra.Command{ Use: "auth-service", Short: "Provide service authentication for OpenCloud", }) diff --git a/services/clientlog/pkg/command/root.go b/services/clientlog/pkg/command/root.go index 38d8ea9e10..75b2696e59 100644 --- a/services/clientlog/pkg/command/root.go +++ b/services/clientlog/pkg/command/root.go @@ -25,7 +25,7 @@ func GetCommands(cfg *config.Config) []*cobra.Command { // Execute is the entry point for the clientlog command. func Execute(cfg *config.Config) error { - app := clihelper.DefaultAppCobra(&cobra.Command{ + app := clihelper.DefaultApp(&cobra.Command{ Use: "clientlog", Short: "starts clientlog service", }) diff --git a/services/collaboration/pkg/command/root.go b/services/collaboration/pkg/command/root.go index 779287dba4..60f872e9c0 100644 --- a/services/collaboration/pkg/command/root.go +++ b/services/collaboration/pkg/command/root.go @@ -20,7 +20,7 @@ func GetCommands(cfg *config.Config) []*cobra.Command { // Execute is the entry point for the antivirus command. func Execute(cfg *config.Config) error { - app := clihelper.DefaultAppCobra(&cobra.Command{ + app := clihelper.DefaultApp(&cobra.Command{ Use: "collaboration", Short: "Serve WOPI for OpenCloud", }) diff --git a/services/eventhistory/pkg/command/root.go b/services/eventhistory/pkg/command/root.go index 11d1f8dde7..64e0c9e3e7 100644 --- a/services/eventhistory/pkg/command/root.go +++ b/services/eventhistory/pkg/command/root.go @@ -25,7 +25,7 @@ func GetCommands(cfg *config.Config) []*cobra.Command { // Execute is the entry point for the eventhistory command. func Execute(cfg *config.Config) error { - app := clihelper.DefaultAppCobra(&cobra.Command{ + app := clihelper.DefaultApp(&cobra.Command{ Use: "eventhistory", Short: "starts eventhistory service", }) diff --git a/services/frontend/pkg/command/root.go b/services/frontend/pkg/command/root.go index 52d5cf9c38..57f417678d 100644 --- a/services/frontend/pkg/command/root.go +++ b/services/frontend/pkg/command/root.go @@ -25,7 +25,7 @@ func GetCommands(cfg *config.Config) []*cobra.Command { // Execute is the entry point for the opencloud-frontend command. func Execute(cfg *config.Config) error { - app := clihelper.DefaultAppCobra(&cobra.Command{ + app := clihelper.DefaultApp(&cobra.Command{ Use: "frontend", Short: "Provide various HTTP apis for OpenCloud", }) diff --git a/services/gateway/pkg/command/root.go b/services/gateway/pkg/command/root.go index 0d91084137..8a83c2ab43 100644 --- a/services/gateway/pkg/command/root.go +++ b/services/gateway/pkg/command/root.go @@ -25,7 +25,7 @@ func GetCommands(cfg *config.Config) []*cobra.Command { // Execute is the entry point for the opencloud-gateway command. func Execute(cfg *config.Config) error { - app := clihelper.DefaultAppCobra(&cobra.Command{ + app := clihelper.DefaultApp(&cobra.Command{ Use: "gateway", Short: "Provide a CS3api gateway for OpenCloud", }) diff --git a/services/graph/pkg/command/root.go b/services/graph/pkg/command/root.go index e9f8c3f30e..69c5a478ed 100644 --- a/services/graph/pkg/command/root.go +++ b/services/graph/pkg/command/root.go @@ -25,7 +25,7 @@ func GetCommands(cfg *config.Config) []*cobra.Command { // Execute is the entry point for the opencloud graph command. func Execute(cfg *config.Config) error { - app := clihelper.DefaultAppCobra(&cobra.Command{ + app := clihelper.DefaultApp(&cobra.Command{ Use: "graph", Short: "Serve Graph API for OpenCloud", }) diff --git a/services/groups/pkg/command/root.go b/services/groups/pkg/command/root.go index 3931be653a..655a52c6d4 100644 --- a/services/groups/pkg/command/root.go +++ b/services/groups/pkg/command/root.go @@ -25,7 +25,7 @@ func GetCommands(cfg *config.Config) []*cobra.Command { // Execute is the entry point for the opencloud group command. func Execute(cfg *config.Config) error { - app := clihelper.DefaultAppCobra(&cobra.Command{ + app := clihelper.DefaultApp(&cobra.Command{ Use: "group", Short: "Provide groups for OpenCloud", }) diff --git a/services/idm/pkg/command/root.go b/services/idm/pkg/command/root.go index c0fb60ba96..9ee4271e8c 100644 --- a/services/idm/pkg/command/root.go +++ b/services/idm/pkg/command/root.go @@ -26,7 +26,7 @@ func GetCommands(cfg *config.Config) []*cobra.Command { // Execute is the entry point for the opencloud idm command. func Execute(cfg *config.Config) error { - app := clihelper.DefaultAppCobra(&cobra.Command{ + app := clihelper.DefaultApp(&cobra.Command{ Use: "idm", Short: "Embedded LDAP service for OpenCloud", }) diff --git a/services/idp/pkg/command/root.go b/services/idp/pkg/command/root.go index d535ff89ba..c0ec288431 100644 --- a/services/idp/pkg/command/root.go +++ b/services/idp/pkg/command/root.go @@ -25,7 +25,7 @@ func GetCommands(cfg *config.Config) []*cobra.Command { // Execute is the entry point for the opencloud-idp command. func Execute(cfg *config.Config) error { - app := clihelper.DefaultAppCobra(&cobra.Command{ + app := clihelper.DefaultApp(&cobra.Command{ Use: "idp", Short: "Serve IDP API for OpenCloud", }) diff --git a/services/invitations/pkg/command/root.go b/services/invitations/pkg/command/root.go index 3923756d2e..eab0c97352 100644 --- a/services/invitations/pkg/command/root.go +++ b/services/invitations/pkg/command/root.go @@ -25,7 +25,7 @@ func GetCommands(cfg *config.Config) []*cobra.Command { // Execute is the entry point for the opencloud invitations command. func Execute(cfg *config.Config) error { - app := clihelper.DefaultAppCobra(&cobra.Command{ + app := clihelper.DefaultApp(&cobra.Command{ Use: "invitations", Short: "Serve invitations API for OpenCloud", }) diff --git a/services/nats/pkg/command/root.go b/services/nats/pkg/command/root.go index 10951422b5..ce63c2f9c6 100644 --- a/services/nats/pkg/command/root.go +++ b/services/nats/pkg/command/root.go @@ -25,7 +25,7 @@ func GetCommands(cfg *config.Config) []*cobra.Command { // Execute is the entry point for the nats command. func Execute(cfg *config.Config) error { - app := clihelper.DefaultAppCobra(&cobra.Command{ + app := clihelper.DefaultApp(&cobra.Command{ Use: "nats", Short: "starts nats server", }) diff --git a/services/notifications/pkg/command/root.go b/services/notifications/pkg/command/root.go index dc632c6b59..d363ad3d9a 100644 --- a/services/notifications/pkg/command/root.go +++ b/services/notifications/pkg/command/root.go @@ -26,7 +26,7 @@ func GetCommands(cfg *config.Config) []*cobra.Command { // Execute is the entry point for the notifications command. func Execute(cfg *config.Config) error { - app := clihelper.DefaultAppCobra(&cobra.Command{ + app := clihelper.DefaultApp(&cobra.Command{ Use: "notifications", Short: "starts notifications service", }) diff --git a/services/ocdav/pkg/command/root.go b/services/ocdav/pkg/command/root.go index 50dd6d1060..b147a8c580 100644 --- a/services/ocdav/pkg/command/root.go +++ b/services/ocdav/pkg/command/root.go @@ -25,7 +25,7 @@ func GetCommands(cfg *config.Config) []*cobra.Command { // Execute is the entry point for the OpenCloud ocdav command. func Execute(cfg *config.Config) error { - app := clihelper.DefaultAppCobra(&cobra.Command{ + app := clihelper.DefaultApp(&cobra.Command{ Use: "ocdav", Short: "Provide a WebDav API for OpenCloud", }) diff --git a/services/ocm/pkg/command/root.go b/services/ocm/pkg/command/root.go index 5aa7b97fff..73c519c7ff 100644 --- a/services/ocm/pkg/command/root.go +++ b/services/ocm/pkg/command/root.go @@ -25,7 +25,7 @@ func GetCommands(cfg *config.Config) []*cobra.Command { // Execute is the entry point for the ocm command. func Execute(cfg *config.Config) error { - app := clihelper.DefaultAppCobra(&cobra.Command{ + app := clihelper.DefaultApp(&cobra.Command{ Use: "ocm", Short: "starts ocm service", }) diff --git a/services/ocs/pkg/command/root.go b/services/ocs/pkg/command/root.go index cd0fc183e6..e25ae60eb9 100644 --- a/services/ocs/pkg/command/root.go +++ b/services/ocs/pkg/command/root.go @@ -25,7 +25,7 @@ func GetCommands(cfg *config.Config) []*cobra.Command { // Execute is the entry point for the opencloud-ocs command. func Execute(cfg *config.Config) error { - app := clihelper.DefaultAppCobra(&cobra.Command{ + app := clihelper.DefaultApp(&cobra.Command{ Use: "ocs", Short: "Serve OCS API for OpenCloud", }) diff --git a/services/policies/pkg/command/root.go b/services/policies/pkg/command/root.go index 3229890546..884c814064 100644 --- a/services/policies/pkg/command/root.go +++ b/services/policies/pkg/command/root.go @@ -20,7 +20,7 @@ func GetCommands(cfg *config.Config) []*cobra.Command { // Execute is the entry point for the policies command. func Execute(cfg *config.Config) error { - app := clihelper.DefaultAppCobra(&cobra.Command{ + app := clihelper.DefaultApp(&cobra.Command{ Use: "policies", Short: "Serve policies for OpenCloud", }) diff --git a/services/postprocessing/pkg/command/root.go b/services/postprocessing/pkg/command/root.go index d80e21d4a6..73171029e5 100644 --- a/services/postprocessing/pkg/command/root.go +++ b/services/postprocessing/pkg/command/root.go @@ -26,7 +26,7 @@ func GetCommands(cfg *config.Config) []*cobra.Command { // Execute is the entry point for the postprocessing command. func Execute(cfg *config.Config) error { - app := clihelper.DefaultAppCobra(&cobra.Command{ + app := clihelper.DefaultApp(&cobra.Command{ Use: "postprocessing", Short: "starts postprocessing service", }) diff --git a/services/proxy/pkg/command/root.go b/services/proxy/pkg/command/root.go index 9e32ede908..10ee5398f3 100644 --- a/services/proxy/pkg/command/root.go +++ b/services/proxy/pkg/command/root.go @@ -25,7 +25,7 @@ func GetCommands(cfg *config.Config) []*cobra.Command { // Execute is the entry point for the opencloud-proxy command. func Execute(cfg *config.Config) error { - app := clihelper.DefaultAppCobra(&cobra.Command{ + app := clihelper.DefaultApp(&cobra.Command{ Use: "proxy", Short: "proxy for OpenCloud", }) diff --git a/services/search/pkg/command/root.go b/services/search/pkg/command/root.go index 91badc9f93..ff158883f2 100644 --- a/services/search/pkg/command/root.go +++ b/services/search/pkg/command/root.go @@ -26,7 +26,7 @@ func GetCommands(cfg *config.Config) []*cobra.Command { // Execute is the entry point for the opencloud-search command. func Execute(cfg *config.Config) error { - app := clihelper.DefaultAppCobra(&cobra.Command{ + app := clihelper.DefaultApp(&cobra.Command{ Use: "search", Short: "Serve search API for OpenCloud", }) diff --git a/services/settings/pkg/command/root.go b/services/settings/pkg/command/root.go index 2d01abb43b..bf2b58b127 100644 --- a/services/settings/pkg/command/root.go +++ b/services/settings/pkg/command/root.go @@ -25,7 +25,7 @@ func GetCommands(cfg *config.Config) []*cobra.Command { // Execute is the entry point for the opencloud-settings command. func Execute(cfg *config.Config) error { - app := clihelper.DefaultAppCobra(&cobra.Command{ + app := clihelper.DefaultApp(&cobra.Command{ Use: "settings", Short: "Provide settings and permissions for OpenCloud", }) diff --git a/services/sharing/pkg/command/root.go b/services/sharing/pkg/command/root.go index bf7a143fdc..e9c6bcf4a8 100644 --- a/services/sharing/pkg/command/root.go +++ b/services/sharing/pkg/command/root.go @@ -25,7 +25,7 @@ func GetCommands(cfg *config.Config) []*cobra.Command { // Execute is the entry point for the sharing command. func Execute(cfg *config.Config) error { - app := clihelper.DefaultAppCobra(&cobra.Command{ + app := clihelper.DefaultApp(&cobra.Command{ Use: "sharing", Short: "Provide sharing for OpenCloud", }) diff --git a/services/sse/pkg/command/root.go b/services/sse/pkg/command/root.go index 82b90791e9..aacf53d753 100644 --- a/services/sse/pkg/command/root.go +++ b/services/sse/pkg/command/root.go @@ -20,7 +20,7 @@ func GetCommands(cfg *config.Config) []*cobra.Command { // Execute is the entry point for the sse command. func Execute(cfg *config.Config) error { - app := clihelper.DefaultAppCobra(&cobra.Command{ + app := clihelper.DefaultApp(&cobra.Command{ Use: "sse", Short: "Serve sse for OpenCloud", }) diff --git a/services/storage-publiclink/pkg/command/root.go b/services/storage-publiclink/pkg/command/root.go index 977079f9cd..9008c52ece 100644 --- a/services/storage-publiclink/pkg/command/root.go +++ b/services/storage-publiclink/pkg/command/root.go @@ -25,7 +25,7 @@ func GetCommands(cfg *config.Config) []*cobra.Command { // Execute is the entry point for the storage-publiclink command. func Execute(cfg *config.Config) error { - app := clihelper.DefaultAppCobra(&cobra.Command{ + app := clihelper.DefaultApp(&cobra.Command{ Use: "storage-publiclink", Short: "Provide publiclink storage for OpenCloud", }) diff --git a/services/storage-shares/pkg/command/root.go b/services/storage-shares/pkg/command/root.go index 6c46190e71..f3d00086fc 100644 --- a/services/storage-shares/pkg/command/root.go +++ b/services/storage-shares/pkg/command/root.go @@ -25,7 +25,7 @@ func GetCommands(cfg *config.Config) []*cobra.Command { // Execute is the entry point for the storage-shares command. func Execute(cfg *config.Config) error { - app := clihelper.DefaultAppCobra(&cobra.Command{ + app := clihelper.DefaultApp(&cobra.Command{ Use: "storage-shares", Short: "Provide a virtual storage for shares in OpenCloud", }) diff --git a/services/storage-system/pkg/command/root.go b/services/storage-system/pkg/command/root.go index f81e980ef4..f36a7186ec 100644 --- a/services/storage-system/pkg/command/root.go +++ b/services/storage-system/pkg/command/root.go @@ -25,7 +25,7 @@ func GetCommands(cfg *config.Config) []*cobra.Command { // Execute is the entry point for the storage-system command. func Execute(cfg *config.Config) error { - app := clihelper.DefaultAppCobra(&cobra.Command{ + app := clihelper.DefaultApp(&cobra.Command{ Use: "storage-system", Short: "Provide system storage for OpenCloud", }) diff --git a/services/storage-users/pkg/command/root.go b/services/storage-users/pkg/command/root.go index 1722ec679b..a0c2022927 100644 --- a/services/storage-users/pkg/command/root.go +++ b/services/storage-users/pkg/command/root.go @@ -27,7 +27,7 @@ func GetCommands(cfg *config.Config) []*cobra.Command { // Execute is the entry point for the opencloud-storage-users command. func Execute(cfg *config.Config) error { - app := clihelper.DefaultAppCobra(&cobra.Command{ + app := clihelper.DefaultApp(&cobra.Command{ Use: "storage-users", Short: "Provide storage for users and projects in OpenCloud", }) diff --git a/services/thumbnails/pkg/command/root.go b/services/thumbnails/pkg/command/root.go index 7bbf9b7fd6..14b0b83432 100644 --- a/services/thumbnails/pkg/command/root.go +++ b/services/thumbnails/pkg/command/root.go @@ -24,7 +24,7 @@ func GetCommands(cfg *config.Config) []*cobra.Command { // Execute is the entry point for the opencloud-thumbnails command. func Execute(cfg *config.Config) error { - app := clihelper.DefaultAppCobra(&cobra.Command{ + app := clihelper.DefaultApp(&cobra.Command{ Use: "thumbnails", Short: "Example usage", }) diff --git a/services/userlog/pkg/command/root.go b/services/userlog/pkg/command/root.go index bf07f7356e..0bee5dcd78 100644 --- a/services/userlog/pkg/command/root.go +++ b/services/userlog/pkg/command/root.go @@ -25,7 +25,7 @@ func GetCommands(cfg *config.Config) []*cobra.Command { // Execute is the entry point for the userlog command. func Execute(cfg *config.Config) error { - app := clihelper.DefaultAppCobra(&cobra.Command{ + app := clihelper.DefaultApp(&cobra.Command{ Use: "userlog", Short: "starts userlog service", }) diff --git a/services/users/pkg/command/root.go b/services/users/pkg/command/root.go index 0cb36cae53..9bf352f980 100644 --- a/services/users/pkg/command/root.go +++ b/services/users/pkg/command/root.go @@ -25,7 +25,7 @@ func GetCommands(cfg *config.Config) []*cobra.Command { // Execute is the entry point for the opencloud-user command. func Execute(cfg *config.Config) error { - app := clihelper.DefaultAppCobra(&cobra.Command{ + app := clihelper.DefaultApp(&cobra.Command{ Use: "user", Short: "Provide users for OpenCloud", }) diff --git a/services/web/pkg/command/root.go b/services/web/pkg/command/root.go index c754c40abb..504cd5e9ca 100644 --- a/services/web/pkg/command/root.go +++ b/services/web/pkg/command/root.go @@ -25,7 +25,7 @@ func GetCommands(cfg *config.Config) []*cobra.Command { // Execute is the entry point for the web command. func Execute(cfg *config.Config) error { - app := clihelper.DefaultAppCobra(&cobra.Command{ + app := clihelper.DefaultApp(&cobra.Command{ Use: "web", Short: "Serve Web for OpenCloud", }) diff --git a/services/webdav/pkg/command/root.go b/services/webdav/pkg/command/root.go index cb781c75ff..104f7e7d79 100644 --- a/services/webdav/pkg/command/root.go +++ b/services/webdav/pkg/command/root.go @@ -25,7 +25,7 @@ func GetCommands(cfg *config.Config) []*cobra.Command { // Execute is the entry point for the opencloud-webdav command. func Execute(cfg *config.Config) error { - app := clihelper.DefaultAppCobra(&cobra.Command{ + app := clihelper.DefaultApp(&cobra.Command{ Use: "webdav", Short: "Serve WebDAV API for OpenCloud", }) diff --git a/services/webfinger/pkg/command/root.go b/services/webfinger/pkg/command/root.go index 6280793091..a5f9867d42 100644 --- a/services/webfinger/pkg/command/root.go +++ b/services/webfinger/pkg/command/root.go @@ -25,7 +25,7 @@ func GetCommands(cfg *config.Config) []*cobra.Command { // Execute is the entry point for the opencloud webfinger command. func Execute(cfg *config.Config) error { - app := clihelper.DefaultAppCobra(&cobra.Command{ + app := clihelper.DefaultApp(&cobra.Command{ Use: "webfinger", Short: "Serve webfinger API for OpenCloud", }) From 67981d8f9a5241ddb710c51b0cf603799c46df4a Mon Sep 17 00:00:00 2001 From: Christian Richter Date: Wed, 3 Dec 2025 15:10:53 +0100 Subject: [PATCH 55/82] refactor version cli command from urfave/cli to spf13/cobra Signed-off-by: Christian Richter --- opencloud/pkg/command/version.go | 31 +++++++++++++------------------ 1 file changed, 13 insertions(+), 18 deletions(-) diff --git a/opencloud/pkg/command/version.go b/opencloud/pkg/command/version.go index beb39d686d..39fd6cc748 100644 --- a/opencloud/pkg/command/version.go +++ b/opencloud/pkg/command/version.go @@ -4,15 +4,15 @@ import ( "fmt" "os" - "github.com/olekukonko/tablewriter" - "github.com/olekukonko/tablewriter/tw" - "github.com/urfave/cli/v2" - mreg "go-micro.dev/v4/registry" - "github.com/opencloud-eu/opencloud/opencloud/pkg/register" "github.com/opencloud-eu/opencloud/pkg/config" "github.com/opencloud-eu/opencloud/pkg/registry" "github.com/opencloud-eu/opencloud/pkg/version" + "github.com/spf13/cobra" + + "github.com/olekukonko/tablewriter" + "github.com/olekukonko/tablewriter/tw" + mreg "go-micro.dev/v4/registry" ) const ( @@ -20,23 +20,16 @@ const ( ) // VersionCommand is the entrypoint for the version command. -func VersionCommand(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: "version", - Usage: "print the version of this binary and all running service instances", - Flags: []cli.Flag{ - &cli.BoolFlag{ - Name: _skipServiceListingFlagName, - Usage: "skip service listing", - }, - }, - Category: "info", - Action: func(c *cli.Context) error { +func VersionCommand(cfg *config.Config) *cobra.Command { + versionCmd := &cobra.Command{ + Use: "version", + Short: "print the version of this binary and all running service instances", + RunE: func(cmd *cobra.Command, args []string) error { fmt.Println("Version: " + version.GetString()) fmt.Printf("Edition: %s\n", version.Edition) fmt.Printf("Compiled: %s\n", version.Compiled()) - if c.Bool(_skipServiceListingFlagName) { + if cmd.Flag(_skipServiceListingFlagName).Changed { return nil } @@ -75,6 +68,8 @@ func VersionCommand(cfg *config.Config) *cli.Command { return nil }, } + versionCmd.Flags().Bool(_skipServiceListingFlagName, false, "skip service listing") + return versionCmd } func init() { From 07c160f67c783c9e4f5e952e327de5930f785a41 Mon Sep 17 00:00:00 2001 From: Christian Richter Date: Wed, 3 Dec 2025 15:12:32 +0100 Subject: [PATCH 56/82] refactor oc runtime server from urfave/cli to spf13/cobra Signed-off-by: Christian Richter --- opencloud/pkg/command/server.go | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/opencloud/pkg/command/server.go b/opencloud/pkg/command/server.go index 8ed88f065f..68f6aa018c 100644 --- a/opencloud/pkg/command/server.go +++ b/opencloud/pkg/command/server.go @@ -6,22 +6,22 @@ import ( "github.com/opencloud-eu/opencloud/pkg/config" "github.com/opencloud-eu/opencloud/pkg/config/configlog" "github.com/opencloud-eu/opencloud/pkg/config/parser" - "github.com/urfave/cli/v2" + + "github.com/spf13/cobra" ) // Server is the entrypoint for the server command. -func Server(cfg *config.Config) *cli.Command { - return &cli.Command{ - Name: "server", - Usage: "start a fullstack server (runtime and all services in supervised mode)", - Category: "fullstack", - Before: func(c *cli.Context) error { +func Server(cfg *config.Config) *cobra.Command { + return &cobra.Command{ + Use: "server", + Short: "start a fullstack server (runtime and all services in supervised mode)", + PreRunE: func(cmd *cobra.Command, args []string) error { return configlog.ReturnError(parser.ParseConfig(cfg, false)) }, - Action: func(c *cli.Context) error { + RunE: func(cmd *cobra.Command, args []string) error { // Prefer the in-memory registry as the default when running in single-binary mode r := runtime.New(cfg) - return r.Start(c.Context) + return r.Start(cmd.Context()) }, } } From a8545bfa39742370708ce60db4da4a94362a03ed Mon Sep 17 00:00:00 2001 From: Christian Richter Date: Wed, 3 Dec 2025 15:16:53 +0100 Subject: [PATCH 57/82] add go test with race Signed-off-by: Christian Richter --- Makefile | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Makefile b/Makefile index 22c8924990..44d4c8c361 100644 --- a/Makefile +++ b/Makefile @@ -198,6 +198,10 @@ go-mod-tidy: test: @go test -v -tags '$(TAGS)' -coverprofile coverage.out ./... +.PHONY: test-with-race +test-with-race: + @go test -race -v -tags '$(TAGS)' -coverprofile coverage.out ./... + .PHONY: go-coverage go-coverage: @if [ ! -f coverage.out ]; then $(MAKE) test &>/dev/null; fi; From 0372869b8b6e9507feb7844b21710e2901ec5780 Mon Sep 17 00:00:00 2001 From: Christian Richter Date: Thu, 4 Dec 2025 10:11:06 +0100 Subject: [PATCH 58/82] refactor remaining code from urfave/cli Signed-off-by: Christian Richter --- opencloud/pkg/runtime/options.go | 9 --------- pkg/service/http/option.go | 7 ++++--- pkg/service/http/service.go | 3 ++- services/activitylog/pkg/server/http/option.go | 9 +++++---- services/auth-app/pkg/server/http/option.go | 9 +++++---- services/graph/pkg/server/http/option.go | 14 +++++++------- services/idp/pkg/server/http/option.go | 9 +++++---- services/invitations/pkg/server/http/option.go | 9 +++++---- services/ocs/pkg/server/http/option.go | 9 +++++---- services/proxy/pkg/server/http/option.go | 11 ++++++----- services/settings/pkg/server/http/option.go | 9 +++++---- services/thumbnails/pkg/server/http/option.go | 12 ++++++++++-- services/userlog/pkg/server/http/option.go | 9 +++++---- services/web/pkg/server/http/option.go | 12 ++++++++++-- services/webdav/pkg/server/http/option.go | 9 +++++---- services/webfinger/pkg/server/http/option.go | 15 +++++++++++---- 16 files changed, 90 insertions(+), 65 deletions(-) diff --git a/opencloud/pkg/runtime/options.go b/opencloud/pkg/runtime/options.go index cfebd47f94..aa354743ab 100644 --- a/opencloud/pkg/runtime/options.go +++ b/opencloud/pkg/runtime/options.go @@ -2,14 +2,12 @@ package runtime import ( "github.com/opencloud-eu/opencloud/pkg/log" - "github.com/urfave/cli/v2" ) // Options is a runtime option type Options struct { Services []string Logger log.Logger - Context *cli.Context } // Option undocumented @@ -21,10 +19,3 @@ func Services(s []string) Option { o.Services = append(o.Services, s...) } } - -// Context option -func Context(c *cli.Context) Option { - return func(o *Options) { - o.Context = c - } -} diff --git a/pkg/service/http/option.go b/pkg/service/http/option.go index c495966e3c..604d23cfec 100644 --- a/pkg/service/http/option.go +++ b/pkg/service/http/option.go @@ -6,7 +6,8 @@ import ( "github.com/opencloud-eu/opencloud/pkg/log" "github.com/opencloud-eu/opencloud/pkg/shared" - "github.com/urfave/cli/v2" + + "github.com/spf13/pflag" "go.opentelemetry.io/otel/trace" ) @@ -23,7 +24,7 @@ type Options struct { Address string Handler http.Handler Context context.Context - Flags []cli.Flag + Flags []pflag.Flag TraceProvider trace.TracerProvider } @@ -83,7 +84,7 @@ func Context(ctx context.Context) Option { } // Flags provides a function to set the flags option. -func Flags(flags ...cli.Flag) Option { +func Flags(flags ...pflag.Flag) Option { return func(o *Options) { o.Flags = append(o.Flags, flags...) } diff --git a/pkg/service/http/service.go b/pkg/service/http/service.go index cf3b9d94e0..ae8d67082f 100644 --- a/pkg/service/http/service.go +++ b/pkg/service/http/service.go @@ -62,7 +62,8 @@ func NewService(opts ...Option) (Service, error) { micro.Name(strings.Join([]string{sopts.Namespace, sopts.Name}, ".")), micro.Version(sopts.Version), micro.Context(sopts.Context), - micro.Flags(sopts.Flags...), + // TODO: clarify if this is actually used on the go-micro side + //micro.Flags(sopts.Flags...), micro.Registry(registry.GetRegistry()), micro.RegisterTTL(registry.GetRegisterTTL()), micro.RegisterInterval(registry.GetRegisterInterval()), diff --git a/services/activitylog/pkg/server/http/option.go b/services/activitylog/pkg/server/http/option.go index 652053145d..6fb3d5cf5c 100644 --- a/services/activitylog/pkg/server/http/option.go +++ b/services/activitylog/pkg/server/http/option.go @@ -11,7 +11,8 @@ import ( "github.com/opencloud-eu/opencloud/services/activitylog/pkg/metrics" "github.com/opencloud-eu/reva/v2/pkg/events" "github.com/opencloud-eu/reva/v2/pkg/rgrpc/todo/pool" - "github.com/urfave/cli/v2" + + "github.com/spf13/pflag" "go-micro.dev/v4/store" "go.opentelemetry.io/otel/trace" ) @@ -25,7 +26,7 @@ type Options struct { Context context.Context Config *config.Config Metrics *metrics.Metrics - Flags []cli.Flag + Flags []pflag.Flag Namespace string Store store.Store Stream events.Stream @@ -76,9 +77,9 @@ func Metrics(val *metrics.Metrics) Option { } // Flags provides a function to set the flags option. -func Flags(val []cli.Flag) Option { +func Flags(flags ...pflag.Flag) Option { return func(o *Options) { - o.Flags = append(o.Flags, val...) + o.Flags = append(o.Flags, flags...) } } diff --git a/services/auth-app/pkg/server/http/option.go b/services/auth-app/pkg/server/http/option.go index b0e3b88fb1..7fb3778a59 100644 --- a/services/auth-app/pkg/server/http/option.go +++ b/services/auth-app/pkg/server/http/option.go @@ -8,7 +8,8 @@ import ( settingssvc "github.com/opencloud-eu/opencloud/protogen/gen/opencloud/services/settings/v0" "github.com/opencloud-eu/opencloud/services/auth-app/pkg/config" "github.com/opencloud-eu/reva/v2/pkg/rgrpc/todo/pool" - "github.com/urfave/cli/v2" + + "github.com/spf13/pflag" "go.opentelemetry.io/otel/trace" ) @@ -20,7 +21,7 @@ type Options struct { Logger log.Logger Context context.Context Config *config.Config - Flags []cli.Flag + Flags []pflag.Flag Namespace string GatewaySelector pool.Selectable[gateway.GatewayAPIClient] RoleClient settingssvc.RoleService @@ -60,9 +61,9 @@ func Config(val *config.Config) Option { } // Flags provides a function to set the flags option. -func Flags(val []cli.Flag) Option { +func Flags(flags ...pflag.Flag) Option { return func(o *Options) { - o.Flags = append(o.Flags, val...) + o.Flags = append(o.Flags, flags...) } } diff --git a/services/graph/pkg/server/http/option.go b/services/graph/pkg/server/http/option.go index a8df4fdecc..350edbad29 100644 --- a/services/graph/pkg/server/http/option.go +++ b/services/graph/pkg/server/http/option.go @@ -3,13 +3,13 @@ package http import ( "context" - "github.com/nats-io/nats.go/jetstream" - "github.com/urfave/cli/v2" - "go.opentelemetry.io/otel/trace" - "github.com/opencloud-eu/opencloud/pkg/log" "github.com/opencloud-eu/opencloud/services/graph/pkg/config" "github.com/opencloud-eu/opencloud/services/graph/pkg/metrics" + + "github.com/nats-io/nats.go/jetstream" + "github.com/spf13/pflag" + "go.opentelemetry.io/otel/trace" ) // Option defines a single option function. @@ -21,7 +21,7 @@ type Options struct { Context context.Context Config *config.Config Metrics *metrics.Metrics - Flags []cli.Flag + Flags []pflag.Flag Namespace string TraceProvider trace.TracerProvider NatsKeyValue jetstream.KeyValue @@ -67,9 +67,9 @@ func Metrics(val *metrics.Metrics) Option { } // Flags provides a function to set the flags option. -func Flags(val []cli.Flag) Option { +func Flags(flags ...pflag.Flag) Option { return func(o *Options) { - o.Flags = append(o.Flags, val...) + o.Flags = append(o.Flags, flags...) } } diff --git a/services/idp/pkg/server/http/option.go b/services/idp/pkg/server/http/option.go index 1c1b60cdaa..7e897ee2fa 100644 --- a/services/idp/pkg/server/http/option.go +++ b/services/idp/pkg/server/http/option.go @@ -6,7 +6,8 @@ import ( "github.com/opencloud-eu/opencloud/pkg/log" "github.com/opencloud-eu/opencloud/services/idp/pkg/config" "github.com/opencloud-eu/opencloud/services/idp/pkg/metrics" - "github.com/urfave/cli/v2" + + "github.com/spf13/pflag" "go.opentelemetry.io/otel/trace" ) @@ -20,7 +21,7 @@ type Options struct { Context context.Context Config *config.Config Metrics *metrics.Metrics - Flags []cli.Flag + Flags []pflag.Flag TraceProvider trace.TracerProvider } @@ -64,9 +65,9 @@ func Metrics(val *metrics.Metrics) Option { } // Flags provides a function to set the flags option. -func Flags(val []cli.Flag) Option { +func Flags(flags ...pflag.Flag) Option { return func(o *Options) { - o.Flags = append(o.Flags, val...) + o.Flags = append(o.Flags, flags...) } } diff --git a/services/invitations/pkg/server/http/option.go b/services/invitations/pkg/server/http/option.go index 3c3204f7f5..30a73f8e17 100644 --- a/services/invitations/pkg/server/http/option.go +++ b/services/invitations/pkg/server/http/option.go @@ -6,7 +6,8 @@ import ( "github.com/opencloud-eu/opencloud/pkg/log" "github.com/opencloud-eu/opencloud/services/invitations/pkg/config" svc "github.com/opencloud-eu/opencloud/services/invitations/pkg/service/v0" - "github.com/urfave/cli/v2" + + "github.com/spf13/pflag" ) // Option defines a single option function. @@ -19,7 +20,7 @@ type Options struct { Logger log.Logger Context context.Context Config *config.Config - Flags []cli.Flag + Flags []pflag.Flag Service svc.Service } @@ -63,9 +64,9 @@ func Config(val *config.Config) Option { } // Flags provides a function to set the flags option. -func Flags(val []cli.Flag) Option { +func Flags(flags ...pflag.Flag) Option { return func(o *Options) { - o.Flags = append(o.Flags, val...) + o.Flags = append(o.Flags, flags...) } } diff --git a/services/ocs/pkg/server/http/option.go b/services/ocs/pkg/server/http/option.go index 3ec05cd75c..00a52efb84 100644 --- a/services/ocs/pkg/server/http/option.go +++ b/services/ocs/pkg/server/http/option.go @@ -6,7 +6,8 @@ import ( "github.com/opencloud-eu/opencloud/pkg/log" "github.com/opencloud-eu/opencloud/services/ocs/pkg/config" "github.com/opencloud-eu/opencloud/services/ocs/pkg/metrics" - "github.com/urfave/cli/v2" + + "github.com/spf13/pflag" "go.opentelemetry.io/otel/trace" ) @@ -20,7 +21,7 @@ type Options struct { Context context.Context Config *config.Config Metrics *metrics.Metrics - Flags []cli.Flag + Flags []pflag.Flag TraceProvider trace.TracerProvider } @@ -64,9 +65,9 @@ func Metrics(val *metrics.Metrics) Option { } // Flags provides a function to set the flags option. -func Flags(val []cli.Flag) Option { +func Flags(flags ...pflag.Flag) Option { return func(o *Options) { - o.Flags = append(o.Flags, val...) + o.Flags = append(o.Flags, flags...) } } diff --git a/services/proxy/pkg/server/http/option.go b/services/proxy/pkg/server/http/option.go index 28e3d06267..c7ba8bad76 100644 --- a/services/proxy/pkg/server/http/option.go +++ b/services/proxy/pkg/server/http/option.go @@ -4,11 +4,12 @@ import ( "context" "net/http" - "github.com/justinas/alice" "github.com/opencloud-eu/opencloud/pkg/log" "github.com/opencloud-eu/opencloud/services/proxy/pkg/config" "github.com/opencloud-eu/opencloud/services/proxy/pkg/metrics" - "github.com/urfave/cli/v2" + + "github.com/justinas/alice" + "github.com/spf13/pflag" ) // Option defines a single option function. @@ -21,7 +22,7 @@ type Options struct { Config *config.Config Handler http.Handler Metrics *metrics.Metrics - Flags []cli.Flag + Flags []pflag.Flag Middlewares alice.Chain } @@ -65,9 +66,9 @@ func Metrics(val *metrics.Metrics) Option { } // Flags provides a function to set the flags option. -func Flags(val []cli.Flag) Option { +func Flags(flags ...pflag.Flag) Option { return func(o *Options) { - o.Flags = append(o.Flags, val...) + o.Flags = append(o.Flags, flags...) } } diff --git a/services/settings/pkg/server/http/option.go b/services/settings/pkg/server/http/option.go index 1293259eb5..f92ac3b582 100644 --- a/services/settings/pkg/server/http/option.go +++ b/services/settings/pkg/server/http/option.go @@ -7,7 +7,8 @@ import ( "github.com/opencloud-eu/opencloud/services/settings/pkg/config" "github.com/opencloud-eu/opencloud/services/settings/pkg/metrics" "github.com/opencloud-eu/opencloud/services/settings/pkg/settings" - "github.com/urfave/cli/v2" + + "github.com/spf13/pflag" "go.opentelemetry.io/otel/trace" ) @@ -22,7 +23,7 @@ type Options struct { Config *config.Config Metrics *metrics.Metrics ServiceHandler settings.ServiceHandler - Flags []cli.Flag + Flags []pflag.Flag TraceProvider trace.TracerProvider } @@ -73,9 +74,9 @@ func Metrics(val *metrics.Metrics) Option { } // Flags provides a function to set the flags option. -func Flags(val []cli.Flag) Option { +func Flags(flags ...pflag.Flag) Option { return func(o *Options) { - o.Flags = append(o.Flags, val...) + o.Flags = append(o.Flags, flags...) } } diff --git a/services/thumbnails/pkg/server/http/option.go b/services/thumbnails/pkg/server/http/option.go index b840bfb9fd..480510a7b6 100644 --- a/services/thumbnails/pkg/server/http/option.go +++ b/services/thumbnails/pkg/server/http/option.go @@ -6,7 +6,8 @@ import ( "github.com/opencloud-eu/opencloud/pkg/log" "github.com/opencloud-eu/opencloud/services/thumbnails/pkg/config" "github.com/opencloud-eu/opencloud/services/thumbnails/pkg/metrics" - "github.com/urfave/cli/v2" + + "github.com/spf13/pflag" "go.opentelemetry.io/otel/trace" "go.opentelemetry.io/otel/trace/noop" ) @@ -21,7 +22,7 @@ type Options struct { Context context.Context Config *config.Config Metrics *metrics.Metrics - Flags []cli.Flag + Flags []pflag.Flag TraceProvider trace.TracerProvider } @@ -81,3 +82,10 @@ func TraceProvider(traceProvider trace.TracerProvider) Option { } } } + +// Flags provides a function to set the flags option. +func Flags(flags ...pflag.Flag) Option { + return func(o *Options) { + o.Flags = append(o.Flags, flags...) + } +} diff --git a/services/userlog/pkg/server/http/option.go b/services/userlog/pkg/server/http/option.go index b6ce605c97..aee27b1258 100644 --- a/services/userlog/pkg/server/http/option.go +++ b/services/userlog/pkg/server/http/option.go @@ -11,7 +11,8 @@ import ( "github.com/opencloud-eu/opencloud/services/userlog/pkg/metrics" "github.com/opencloud-eu/reva/v2/pkg/events" "github.com/opencloud-eu/reva/v2/pkg/rgrpc/todo/pool" - "github.com/urfave/cli/v2" + + "github.com/spf13/pflag" "go-micro.dev/v4/store" "go.opentelemetry.io/otel/trace" ) @@ -25,7 +26,7 @@ type Options struct { Context context.Context Config *config.Config Metrics *metrics.Metrics - Flags []cli.Flag + Flags []pflag.Flag Namespace string Store store.Store Stream events.Stream @@ -77,9 +78,9 @@ func Metrics(val *metrics.Metrics) Option { } // Flags provides a function to set the flags option. -func Flags(val []cli.Flag) Option { +func Flags(flags ...pflag.Flag) Option { return func(o *Options) { - o.Flags = append(o.Flags, val...) + o.Flags = append(o.Flags, flags...) } } diff --git a/services/web/pkg/server/http/option.go b/services/web/pkg/server/http/option.go index 1187cb4b9e..16e4c5f7db 100644 --- a/services/web/pkg/server/http/option.go +++ b/services/web/pkg/server/http/option.go @@ -6,7 +6,8 @@ import ( "github.com/opencloud-eu/opencloud/pkg/log" "github.com/opencloud-eu/opencloud/services/web/pkg/config" "github.com/opencloud-eu/opencloud/services/web/pkg/metrics" - "github.com/urfave/cli/v2" + + "github.com/spf13/pflag" "go.opentelemetry.io/otel/trace" ) @@ -19,7 +20,7 @@ type Options struct { Context context.Context Config *config.Config Metrics *metrics.Metrics - Flags []cli.Flag + Flags []pflag.Flag Namespace string TraceProvider trace.TracerProvider } @@ -76,3 +77,10 @@ func TraceProvider(val trace.TracerProvider) Option { o.TraceProvider = val } } + +// Flags provides a function to set the flags option. +func Flags(flags ...pflag.Flag) Option { + return func(o *Options) { + o.Flags = append(o.Flags, flags...) + } +} diff --git a/services/webdav/pkg/server/http/option.go b/services/webdav/pkg/server/http/option.go index 56859aef7c..0128c54f08 100644 --- a/services/webdav/pkg/server/http/option.go +++ b/services/webdav/pkg/server/http/option.go @@ -6,7 +6,8 @@ import ( "github.com/opencloud-eu/opencloud/pkg/log" "github.com/opencloud-eu/opencloud/services/webdav/pkg/config" "github.com/opencloud-eu/opencloud/services/webdav/pkg/metrics" - "github.com/urfave/cli/v2" + + "github.com/spf13/pflag" "go.opentelemetry.io/otel/trace" ) @@ -20,7 +21,7 @@ type Options struct { Context context.Context Config *config.Config Metrics *metrics.Metrics - Flags []cli.Flag + Flags []pflag.Flag TraceProvider trace.TracerProvider } @@ -64,9 +65,9 @@ func Metrics(val *metrics.Metrics) Option { } // Flags provides a function to set the flags option. -func Flags(val []cli.Flag) Option { +func Flags(flags ...pflag.Flag) Option { return func(o *Options) { - o.Flags = append(o.Flags, val...) + o.Flags = append(o.Flags, flags...) } } diff --git a/services/webfinger/pkg/server/http/option.go b/services/webfinger/pkg/server/http/option.go index 5f9b9153e1..8dee2722b7 100644 --- a/services/webfinger/pkg/server/http/option.go +++ b/services/webfinger/pkg/server/http/option.go @@ -3,13 +3,13 @@ package http import ( "context" - "go.opentelemetry.io/otel/trace/noop" - "github.com/opencloud-eu/opencloud/pkg/log" "github.com/opencloud-eu/opencloud/services/webfinger/pkg/config" svc "github.com/opencloud-eu/opencloud/services/webfinger/pkg/service/v0" - "github.com/urfave/cli/v2" + + "github.com/spf13/pflag" "go.opentelemetry.io/otel/trace" + "go.opentelemetry.io/otel/trace/noop" ) // Option defines a single option function. @@ -22,7 +22,7 @@ type Options struct { Logger log.Logger Context context.Context Config *config.Config - Flags []cli.Flag + Flags []pflag.Flag Service svc.Service TraceProvider trace.TracerProvider } @@ -66,6 +66,13 @@ func Service(val svc.Service) Option { } } +// Flags provides a function to set the flags option. +func Flags(flags ...pflag.Flag) Option { + return func(o *Options) { + o.Flags = append(o.Flags, flags...) + } +} + // TraceProvider provides a function to configure the trace provider func TraceProvider(traceProvider trace.TracerProvider) Option { return func(o *Options) { From 75bcc548d7ca9fe6ebbf158799cdaa62e9c2ae82 Mon Sep 17 00:00:00 2001 From: Christian Richter Date: Thu, 4 Dec 2025 10:25:54 +0100 Subject: [PATCH 59/82] vendor Signed-off-by: Christian Richter --- go.mod | 4 ++-- go.sum | 4 ++-- vendor/github.com/urfave/cli/v2/flag_string_slice.go | 2 +- vendor/github.com/urfave/cli/v2/godoc-current.txt | 5 +---- vendor/github.com/urfave/cli/v2/help.go | 2 +- vendor/github.com/urfave/cli/v2/template.go | 5 +---- vendor/modules.txt | 2 +- 7 files changed, 9 insertions(+), 15 deletions(-) diff --git a/go.mod b/go.mod index 7193be1acc..4b49ca2bec 100644 --- a/go.mod +++ b/go.mod @@ -78,6 +78,7 @@ require ( github.com/sirupsen/logrus v1.9.4-0.20230606125235-dd1b4c2e81af github.com/spf13/afero v1.15.0 github.com/spf13/cobra v1.10.1 + github.com/spf13/pflag v1.0.10 github.com/spf13/viper v1.21.0 github.com/stretchr/testify v1.11.1 github.com/test-go/testify v1.1.4 @@ -89,7 +90,6 @@ require ( github.com/tidwall/sjson v1.2.5 github.com/tus/tusd/v2 v2.8.0 github.com/unrolled/secure v1.16.0 - github.com/urfave/cli/v2 v2.27.7 github.com/vmihailenco/msgpack/v5 v5.4.1 github.com/xhit/go-simple-mail/v2 v2.16.0 go-micro.dev/v4 v4.11.0 @@ -356,7 +356,6 @@ require ( github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 // indirect github.com/spacewander/go-suffix-tree v0.0.0-20191010040751-0865e368c784 // indirect github.com/spf13/cast v1.10.0 // indirect - github.com/spf13/pflag v1.0.10 // indirect github.com/stretchr/objx v0.5.2 // indirect github.com/studio-b12/gowebdav v0.9.0 // indirect github.com/subosito/gotenv v1.6.0 // indirect @@ -368,6 +367,7 @@ require ( github.com/tklauser/numcpus v0.8.0 // indirect github.com/toorop/go-dkim v0.0.0-20201103131630-e1cd1a0a5208 // indirect github.com/trustelem/zxcvbn v1.0.1 // indirect + github.com/urfave/cli/v2 v2.27.5 // indirect github.com/valyala/fastjson v1.6.4 // indirect github.com/vektah/gqlparser/v2 v2.5.30 // indirect github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect diff --git a/go.sum b/go.sum index f44cded042..e82bcc7bb4 100644 --- a/go.sum +++ b/go.sum @@ -1239,8 +1239,8 @@ github.com/tus/tusd/v2 v2.8.0/go.mod h1:3/zEOVQQIwmJhvNam8phV4x/UQt68ZmZiTzeuJUN github.com/uber-go/atomic v1.3.2/go.mod h1:/Ct5t2lcmbJ4OSe/waGBoaVvVqtO0bmtfVNex1PFV8g= github.com/urfave/cli v1.22.4/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI= -github.com/urfave/cli/v2 v2.27.7 h1:bH59vdhbjLv3LAvIu6gd0usJHgoTTPhCFib8qqOwXYU= -github.com/urfave/cli/v2 v2.27.7/go.mod h1:CyNAG/xg+iAOg0N4MPGZqVmv2rCoP267496AOXUZjA4= +github.com/urfave/cli/v2 v2.27.5 h1:WoHEJLdsXr6dDWoJgMq/CboDmyY/8HMMH1fTECbih+w= +github.com/urfave/cli/v2 v2.27.5/go.mod h1:3Sevf16NykTbInEnD0yKkjDAeZDS0A6bzhBH5hrMvTQ= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/fastjson v1.6.4 h1:uAUNq9Z6ymTgGhcm0UynUAB6tlbakBrz6CQFax3BXVQ= github.com/valyala/fastjson v1.6.4/go.mod h1:CLCAqky6SMuOcxStkYQvblddUtoRxhYMGLrsQns1aXY= diff --git a/vendor/github.com/urfave/cli/v2/flag_string_slice.go b/vendor/github.com/urfave/cli/v2/flag_string_slice.go index 66bdf1afcd..28f4798f55 100644 --- a/vendor/github.com/urfave/cli/v2/flag_string_slice.go +++ b/vendor/github.com/urfave/cli/v2/flag_string_slice.go @@ -150,8 +150,8 @@ func (f *StringSliceFlag) Apply(set *flag.FlagSet) error { setValue = f.Value.clone() default: setValue = new(StringSlice) + setValue.WithSeparatorSpec(f.separator) } - setValue.WithSeparatorSpec(f.separator) setValue.keepSpace = f.KeepSpace diff --git a/vendor/github.com/urfave/cli/v2/godoc-current.txt b/vendor/github.com/urfave/cli/v2/godoc-current.txt index 2f3d76e319..3e29faabac 100644 --- a/vendor/github.com/urfave/cli/v2/godoc-current.txt +++ b/vendor/github.com/urfave/cli/v2/godoc-current.txt @@ -136,10 +136,7 @@ var SubcommandHelpTemplate = `NAME: {{template "helpNameTemplate" .}} USAGE: - {{template "usageTemplate" .}}{{if .Category}} - -CATEGORY: - {{.Category}}{{end}}{{if .Description}} + {{if .UsageText}}{{wrap .UsageText 3}}{{else}}{{.HelpName}} {{if .VisibleFlags}}command [command options]{{end}}{{if .ArgsUsage}} {{.ArgsUsage}}{{else}}{{if .Args}} [arguments...]{{end}}{{end}}{{end}}{{if .Description}} DESCRIPTION: {{template "descriptionTemplate" .}}{{end}}{{if .VisibleCommands}} diff --git a/vendor/github.com/urfave/cli/v2/help.go b/vendor/github.com/urfave/cli/v2/help.go index d27e8ce385..f1b9e7f18f 100644 --- a/vendor/github.com/urfave/cli/v2/help.go +++ b/vendor/github.com/urfave/cli/v2/help.go @@ -54,7 +54,7 @@ var helpCommand = &Command{ cCtx = cCtx.parentContext } - // Case 4. $ app help foo + // Case 4. $ app hello foo // foo is the command for which help needs to be shown if argsPresent { return ShowCommandHelp(cCtx, firstArg) diff --git a/vendor/github.com/urfave/cli/v2/template.go b/vendor/github.com/urfave/cli/v2/template.go index ccb22f1d53..8abc5ba421 100644 --- a/vendor/github.com/urfave/cli/v2/template.go +++ b/vendor/github.com/urfave/cli/v2/template.go @@ -83,10 +83,7 @@ var SubcommandHelpTemplate = `NAME: {{template "helpNameTemplate" .}} USAGE: - {{template "usageTemplate" .}}{{if .Category}} - -CATEGORY: - {{.Category}}{{end}}{{if .Description}} + {{if .UsageText}}{{wrap .UsageText 3}}{{else}}{{.HelpName}} {{if .VisibleFlags}}command [command options]{{end}}{{if .ArgsUsage}} {{.ArgsUsage}}{{else}}{{if .Args}} [arguments...]{{end}}{{end}}{{end}}{{if .Description}} DESCRIPTION: {{template "descriptionTemplate" .}}{{end}}{{if .VisibleCommands}} diff --git a/vendor/modules.txt b/vendor/modules.txt index cf85317a62..d10fcfcfe7 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -2146,7 +2146,7 @@ github.com/tus/tusd/v2/pkg/handler ## explicit; go 1.13 github.com/unrolled/secure github.com/unrolled/secure/cspbuilder -# github.com/urfave/cli/v2 v2.27.7 +# github.com/urfave/cli/v2 v2.27.5 ## explicit; go 1.18 github.com/urfave/cli/v2 # github.com/valyala/fastjson v1.6.4 From a9b1bea4f862b1e7fd46f139112771f984c0d0d4 Mon Sep 17 00:00:00 2001 From: Christian Richter Date: Mon, 8 Dec 2025 09:40:33 +0100 Subject: [PATCH 60/82] override RunE function of root subcommands to ensure that help is shown when no arguments are given Signed-off-by: Christian Richter --- opencloud/pkg/command/root.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/opencloud/pkg/command/root.go b/opencloud/pkg/command/root.go index ac1dc4bace..91d7d1edf8 100644 --- a/opencloud/pkg/command/root.go +++ b/opencloud/pkg/command/root.go @@ -23,7 +23,12 @@ func Execute() error { }) for _, fn := range register.Commands { - app.AddCommand(fn(cfg)) + cmd := fn(cfg) + cmd.RunE = func(cmd *cobra.Command, args []string) error { + cmd.Help() + return nil + } + app.AddCommand(cmd) } app.SetArgs(os.Args[1:]) ctx, _ := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT, syscall.SIGHUP) From 0fa3a2a7f81ee4b74dc0eb2efdd7e0af99d27f1e Mon Sep 17 00:00:00 2001 From: Christian Richter Date: Mon, 8 Dec 2025 14:01:37 +0100 Subject: [PATCH 61/82] prevent overwrite of RunE Signed-off-by: Christian Richter --- opencloud/pkg/command/root.go | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/opencloud/pkg/command/root.go b/opencloud/pkg/command/root.go index 91d7d1edf8..a0f42f4b54 100644 --- a/opencloud/pkg/command/root.go +++ b/opencloud/pkg/command/root.go @@ -24,9 +24,14 @@ func Execute() error { for _, fn := range register.Commands { cmd := fn(cfg) - cmd.RunE = func(cmd *cobra.Command, args []string) error { - cmd.Help() - return nil + // if the command has a RunE function, it is a subcommand of root + // if not it is a consumed root command from the services + // wee need to overwrite the RunE function to get the help output there + if cmd.RunE == nil { + cmd.RunE = func(cmd *cobra.Command, args []string) error { + cmd.Help() + return nil + } } app.AddCommand(cmd) } From 879de391298103154a2e30cabcca9ca9dd05117b Mon Sep 17 00:00:00 2001 From: Florian Schade Date: Thu, 11 Dec 2025 14:03:51 +0100 Subject: [PATCH 62/82] fix: list service child commands the service is called without arguments --- opencloud/pkg/command/root.go | 15 +++------------ opencloud/pkg/command/services.go | 23 ++++++++++++++++------- opencloud/pkg/register/command.go | 7 ++++--- 3 files changed, 23 insertions(+), 22 deletions(-) diff --git a/opencloud/pkg/command/root.go b/opencloud/pkg/command/root.go index a0f42f4b54..1a80aa60fd 100644 --- a/opencloud/pkg/command/root.go +++ b/opencloud/pkg/command/root.go @@ -22,18 +22,9 @@ func Execute() error { Short: "opencloud", }) - for _, fn := range register.Commands { - cmd := fn(cfg) - // if the command has a RunE function, it is a subcommand of root - // if not it is a consumed root command from the services - // wee need to overwrite the RunE function to get the help output there - if cmd.RunE == nil { - cmd.RunE = func(cmd *cobra.Command, args []string) error { - cmd.Help() - return nil - } - } - app.AddCommand(cmd) + for _, commandFactory := range register.Commands { + command := commandFactory(cfg) + app.AddCommand(command) } app.SetArgs(os.Args[1:]) ctx, _ := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT, syscall.SIGHUP) diff --git a/opencloud/pkg/command/services.go b/opencloud/pkg/command/services.go index f6b05e843b..de84b049f4 100644 --- a/opencloud/pkg/command/services.go +++ b/opencloud/pkg/command/services.go @@ -52,7 +52,7 @@ import ( "github.com/spf13/cobra" ) -var svccmds = []register.Command{ +var serviceCommands = []register.Command{ func(cfg *config.Config) *cobra.Command { return ServiceCommand(cfg, cfg.Activitylog.Service.Name, activitylog.GetCommands(cfg.Activitylog), func(c *config.Config) { cfg.Activitylog.Commons = cfg.Commons @@ -265,9 +265,9 @@ var svccmds = []register.Command{ }, } -// ServiceCommand is the entry point for the all service commands. -func ServiceCommand(cfg *config.Config, serviceName string, subcommands []*cobra.Command, f func(*config.Config)) *cobra.Command { - svcCommand := &cobra.Command{ +// ServiceCommand composes a cobra command from the given inputs. +func ServiceCommand(cfg *config.Config, serviceName string, subCommands []*cobra.Command, f func(*config.Config)) *cobra.Command { + command := &cobra.Command{ Use: serviceName, Short: helper.SubcommandDescription(serviceName), RunE: func(cmd *cobra.Command, args []string) error { @@ -276,12 +276,21 @@ func ServiceCommand(cfg *config.Config, serviceName string, subcommands []*cobra return nil }, } - svcCommand.AddCommand(subcommands...) - return svcCommand + + // if a service should have multiple child commands, + // we expect that at least one argument is needed; + // this helps cobra to force display all available child commands. + if len(subCommands) > 0 { + command.Args = cobra.MinimumNArgs(1) + } + + command.AddCommand(subCommands...) + + return command } func init() { - for _, c := range svccmds { + for _, c := range serviceCommands { register.AddCommand(c) } } diff --git a/opencloud/pkg/register/command.go b/opencloud/pkg/register/command.go index ac73c89a90..6a81f5047a 100644 --- a/opencloud/pkg/register/command.go +++ b/opencloud/pkg/register/command.go @@ -1,13 +1,14 @@ package register import ( - "github.com/opencloud-eu/opencloud/pkg/config" "github.com/spf13/cobra" + + "github.com/opencloud-eu/opencloud/pkg/config" ) var ( - // Commands defines the slice of commands. - Commands = []Command{} + // Commands define the slice of commands. + Commands []Command ) // Command defines the register command. From 9904e5b932a673d2f6d39602e7a9b7dfe41e5579 Mon Sep 17 00:00:00 2001 From: Florian Schade Date: Thu, 11 Dec 2025 14:40:46 +0100 Subject: [PATCH 63/82] enhancement(cli): group commands by topic --- opencloud/pkg/command/command.go | 7 +++++++ opencloud/pkg/command/decomposedfs.go | 5 +++-- opencloud/pkg/command/helper/common.go | 9 --------- opencloud/pkg/command/init.go | 7 ++++--- opencloud/pkg/command/posixfs.go | 5 +++-- opencloud/pkg/command/root.go | 8 ++++++++ opencloud/pkg/command/server.go | 1 + opencloud/pkg/command/services.go | 8 +++++--- opencloud/pkg/command/version.go | 8 +++++--- 9 files changed, 36 insertions(+), 22 deletions(-) create mode 100644 opencloud/pkg/command/command.go delete mode 100644 opencloud/pkg/command/helper/common.go diff --git a/opencloud/pkg/command/command.go b/opencloud/pkg/command/command.go new file mode 100644 index 0000000000..964ec377a7 --- /dev/null +++ b/opencloud/pkg/command/command.go @@ -0,0 +1,7 @@ +package command + +const ( + CommandGroupServer = "Server" + CommandGroupServices = "Service" + CommandGroupStorage = "Storage" +) diff --git a/opencloud/pkg/command/decomposedfs.go b/opencloud/pkg/command/decomposedfs.go index 2f96b3ec58..f66ab47f58 100644 --- a/opencloud/pkg/command/decomposedfs.go +++ b/opencloud/pkg/command/decomposedfs.go @@ -33,8 +33,9 @@ import ( // DecomposedfsCommand is the entrypoint for the groups command. func DecomposedfsCommand(cfg *config.Config) *cobra.Command { decomposedCmd := &cobra.Command{ - Use: "decomposedfs", - Short: `cli tools to inspect and manipulate a decomposedfs storage.`, + Use: "decomposedfs", + Short: `cli tools to inspect and manipulate a decomposedfs storage.`, + GroupID: CommandGroupStorage, } decomposedCmd.AddCommand(metadataCmd(cfg), checkCmd(cfg)) return decomposedCmd diff --git a/opencloud/pkg/command/helper/common.go b/opencloud/pkg/command/helper/common.go deleted file mode 100644 index 1c2fdd990b..0000000000 --- a/opencloud/pkg/command/helper/common.go +++ /dev/null @@ -1,9 +0,0 @@ -package helper - -import ( - "fmt" -) - -func SubcommandDescription(serviceName string) string { - return fmt.Sprintf("%s service commands", serviceName) -} diff --git a/opencloud/pkg/command/init.go b/opencloud/pkg/command/init.go index d381dd993b..84f37c674c 100644 --- a/opencloud/pkg/command/init.go +++ b/opencloud/pkg/command/init.go @@ -17,10 +17,11 @@ import ( ) // InitCommand is the entrypoint for the init command -func InitCommand(cfg *config.Config) *cobra.Command { +func InitCommand(_ *config.Config) *cobra.Command { initCmd := &cobra.Command{ - Use: "init", - Short: "initialise an OpenCloud config", + Use: "init", + Short: "initialise an OpenCloud config", + GroupID: CommandGroupServer, RunE: func(cmd *cobra.Command, args []string) error { insecureFlag := cmd.Flag("insecure").Value.String() insecure := false diff --git a/opencloud/pkg/command/posixfs.go b/opencloud/pkg/command/posixfs.go index 88a173d742..070eb2b5d7 100644 --- a/opencloud/pkg/command/posixfs.go +++ b/opencloud/pkg/command/posixfs.go @@ -40,8 +40,9 @@ type EntryInfo struct { // PosixfsCommand is the entrypoint for the posixfs command. func PosixfsCommand(cfg *config.Config) *cobra.Command { posixCmd := &cobra.Command{ - Use: "posixfs", - Short: `cli tools to inspect and manipulate a posixfs storage.`, + Use: "posixfs", + Short: `cli tools to inspect and manipulate a posixfs storage.`, + GroupID: CommandGroupStorage, } posixCmd.AddCommand(consistencyCmd(cfg)) diff --git a/opencloud/pkg/command/root.go b/opencloud/pkg/command/root.go index 1a80aa60fd..834bc68e02 100644 --- a/opencloud/pkg/command/root.go +++ b/opencloud/pkg/command/root.go @@ -24,6 +24,14 @@ func Execute() error { for _, commandFactory := range register.Commands { command := commandFactory(cfg) + + if command.GroupID != "" && !app.ContainsGroup(command.GroupID) { + app.AddGroup(&cobra.Group{ + ID: command.GroupID, + Title: command.GroupID, + }) + } + app.AddCommand(command) } app.SetArgs(os.Args[1:]) diff --git a/opencloud/pkg/command/server.go b/opencloud/pkg/command/server.go index 68f6aa018c..776f24d271 100644 --- a/opencloud/pkg/command/server.go +++ b/opencloud/pkg/command/server.go @@ -18,6 +18,7 @@ func Server(cfg *config.Config) *cobra.Command { PreRunE: func(cmd *cobra.Command, args []string) error { return configlog.ReturnError(parser.ParseConfig(cfg, false)) }, + GroupID: CommandGroupServer, RunE: func(cmd *cobra.Command, args []string) error { // Prefer the in-memory registry as the default when running in single-binary mode r := runtime.New(cfg) diff --git a/opencloud/pkg/command/services.go b/opencloud/pkg/command/services.go index de84b049f4..a3cfd02beb 100644 --- a/opencloud/pkg/command/services.go +++ b/opencloud/pkg/command/services.go @@ -1,7 +1,8 @@ package command import ( - "github.com/opencloud-eu/opencloud/opencloud/pkg/command/helper" + "fmt" + "github.com/opencloud-eu/opencloud/opencloud/pkg/register" "github.com/opencloud-eu/opencloud/pkg/config" "github.com/opencloud-eu/opencloud/pkg/config/configlog" @@ -268,8 +269,9 @@ var serviceCommands = []register.Command{ // ServiceCommand composes a cobra command from the given inputs. func ServiceCommand(cfg *config.Config, serviceName string, subCommands []*cobra.Command, f func(*config.Config)) *cobra.Command { command := &cobra.Command{ - Use: serviceName, - Short: helper.SubcommandDescription(serviceName), + Use: serviceName, + Short: fmt.Sprintf("%s service commands", serviceName), + GroupID: CommandGroupServices, RunE: func(cmd *cobra.Command, args []string) error { configlog.Error(parser.ParseConfig(cfg, true)) f(cfg) diff --git a/opencloud/pkg/command/version.go b/opencloud/pkg/command/version.go index 39fd6cc748..5f77911561 100644 --- a/opencloud/pkg/command/version.go +++ b/opencloud/pkg/command/version.go @@ -4,11 +4,12 @@ import ( "fmt" "os" + "github.com/spf13/cobra" + "github.com/opencloud-eu/opencloud/opencloud/pkg/register" "github.com/opencloud-eu/opencloud/pkg/config" "github.com/opencloud-eu/opencloud/pkg/registry" "github.com/opencloud-eu/opencloud/pkg/version" - "github.com/spf13/cobra" "github.com/olekukonko/tablewriter" "github.com/olekukonko/tablewriter/tw" @@ -22,8 +23,9 @@ const ( // VersionCommand is the entrypoint for the version command. func VersionCommand(cfg *config.Config) *cobra.Command { versionCmd := &cobra.Command{ - Use: "version", - Short: "print the version of this binary and all running service instances", + Use: "version", + Short: "print the version of this binary and all running service instances", + GroupID: CommandGroupServer, RunE: func(cmd *cobra.Command, args []string) error { fmt.Println("Version: " + version.GetString()) fmt.Printf("Edition: %s\n", version.Edition) From 5824d8df340a6a94dc7272c84209abf0261e77df Mon Sep 17 00:00:00 2001 From: Florian Schade Date: Fri, 12 Dec 2025 12:43:47 +0100 Subject: [PATCH 64/82] fix(cli): hydrate shared configurations such as commons before running any service command --- opencloud/pkg/command/services.go | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/opencloud/pkg/command/services.go b/opencloud/pkg/command/services.go index a3cfd02beb..b25823476c 100644 --- a/opencloud/pkg/command/services.go +++ b/opencloud/pkg/command/services.go @@ -272,20 +272,13 @@ func ServiceCommand(cfg *config.Config, serviceName string, subCommands []*cobra Use: serviceName, Short: fmt.Sprintf("%s service commands", serviceName), GroupID: CommandGroupServices, - RunE: func(cmd *cobra.Command, args []string) error { + PersistentPreRunE: func(cmd *cobra.Command, args []string) error { configlog.Error(parser.ParseConfig(cfg, true)) f(cfg) return nil }, } - // if a service should have multiple child commands, - // we expect that at least one argument is needed; - // this helps cobra to force display all available child commands. - if len(subCommands) > 0 { - command.Args = cobra.MinimumNArgs(1) - } - command.AddCommand(subCommands...) return command From efb7e73195169c8a8bffb6172a60edf36ec2cf3c Mon Sep 17 00:00:00 2001 From: Florian Schade Date: Fri, 12 Dec 2025 13:32:55 +0100 Subject: [PATCH 65/82] fix(cli): fix varius cmds - cmd backup consistency, fix flags - revisions purge, fix flags --- opencloud/pkg/command/backup.go | 11 ++++------- opencloud/pkg/command/revisions.go | 21 ++++++++++++++------- 2 files changed, 18 insertions(+), 14 deletions(-) diff --git a/opencloud/pkg/command/backup.go b/opencloud/pkg/command/backup.go index 469130a3e5..3573c3df40 100644 --- a/opencloud/pkg/command/backup.go +++ b/opencloud/pkg/command/backup.go @@ -4,6 +4,8 @@ import ( "errors" "fmt" + "github.com/spf13/cobra" + "github.com/opencloud-eu/opencloud/opencloud/pkg/backup" "github.com/opencloud-eu/opencloud/opencloud/pkg/register" "github.com/opencloud-eu/opencloud/pkg/config" @@ -11,7 +13,6 @@ import ( "github.com/opencloud-eu/opencloud/pkg/config/parser" decomposedbs "github.com/opencloud-eu/reva/v2/pkg/storage/fs/decomposed/blobstore" decomposeds3bs "github.com/opencloud-eu/reva/v2/pkg/storage/fs/decomposeds3/blobstore" - "github.com/spf13/cobra" ) // BackupCommand is the entrypoint for the backup command @@ -19,13 +20,9 @@ func BackupCommand(cfg *config.Config) *cobra.Command { bckCmd := &cobra.Command{ Use: "backup", Short: "OpenCloud backup functionality", - PreRunE: func(cmd *cobra.Command, args []string) error { + PersistentPreRunE: func(cmd *cobra.Command, args []string) error { return configlog.ReturnError(parser.ParseConfig(cfg, true)) }, - RunE: func(cmd *cobra.Command, args []string) error { - fmt.Println("Read the docs") - return nil - }, } bckCmd.AddCommand(ConsistencyCommand(cfg)) return bckCmd @@ -82,7 +79,7 @@ func ConsistencyCommand(cfg *config.Config) *cobra.Command { if err != nil { fmt.Println(err) } - consCmd.Flags().StringP("blobstore", "b", "", "the blobstore type. Can be (none, decomposed, decomposeds3). Default decomposed") + consCmd.Flags().StringP("blobstore", "b", "decomposed", "the blobstore type. Can be (none, decomposed, decomposeds3). Default decomposed") consCmd.Flags().Bool("fail", false, "exit with non-zero status if consistency check fails") return consCmd } diff --git a/opencloud/pkg/command/revisions.go b/opencloud/pkg/command/revisions.go index 2ae0a0b6ab..14ecdc53a5 100644 --- a/opencloud/pkg/command/revisions.go +++ b/opencloud/pkg/command/revisions.go @@ -6,6 +6,7 @@ import ( "path/filepath" provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" + "github.com/opencloud-eu/opencloud/opencloud/pkg/register" "github.com/opencloud-eu/opencloud/opencloud/pkg/revisions" "github.com/opencloud-eu/opencloud/pkg/config" @@ -29,13 +30,9 @@ func RevisionsCommand(cfg *config.Config) *cobra.Command { revCmd := &cobra.Command{ Use: "revisions", Short: "OpenCloud revisions functionality", - PreRunE: func(cmd *cobra.Command, args []string) error { + PersistentPreRunE: func(cmd *cobra.Command, args []string) error { return configlog.ReturnError(parser.ParseConfig(cfg, true)) }, - RunE: func(cmd *cobra.Command, args []string) error { - fmt.Println("Read the docs") - return nil - }, } revCmd.AddCommand(PurgeRevisionsCommand(cfg)) @@ -113,8 +110,18 @@ func PurgeRevisionsCommand(cfg *config.Config) *cobra.Command { ch = revisions.List(p, 10) } - files, blobs, revisions := revisions.PurgeRevisions(ch, bs, cmd.Flag("dry-run").Changed, cmd.Flag("verbose").Changed) - printResults(files, blobs, revisions, cmd.Flag("dry-run").Changed) + flagDryRun, err := cmd.Flags().GetBool("dry-run") + if err != nil { + return err + } + + flagVerbose, err := cmd.Flags().GetBool("verbose") + if err != nil { + return err + } + + files, blobs, revisionResults := revisions.PurgeRevisions(ch, bs, flagDryRun, flagVerbose) + printResults(files, blobs, revisionResults, flagDryRun) return nil }, } From 93601752a9a2c1c6c2484689f2467896c2cde618 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rn=20Friedrich=20Dreyer?= Date: Fri, 12 Dec 2025 14:32:56 +0100 Subject: [PATCH 66/82] fix storage-users uploads sessions flags MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Jörn Friedrich Dreyer --- services/storage-users/pkg/command/uploads.go | 31 ++++++++++++------- 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/services/storage-users/pkg/command/uploads.go b/services/storage-users/pkg/command/uploads.go index 0b56a54ed1..3d846ccfee 100644 --- a/services/storage-users/pkg/command/uploads.go +++ b/services/storage-users/pkg/command/uploads.go @@ -94,8 +94,13 @@ func ListUploadSessions(cfg *config.Config) *cobra.Command { os.Exit(1) } + restart, _ := cmd.Flags().GetBool("restart") + resume, _ := cmd.Flags().GetBool("resume") + clean, _ := cmd.Flags().GetBool("clean") + renderJson, _ := cmd.Flags().GetBool("json") + var stream events.Stream - if cmd.Flag("restart").Changed || cmd.Flag("resume").Changed { + if restart || resume { stream, err = event.NewStream(cfg) if err != nil { fmt.Fprintf(os.Stderr, "Failed to create event stream: %v\n", err) @@ -114,7 +119,7 @@ func ListUploadSessions(cfg *config.Config) *cobra.Command { raw []Session ) - if !cmd.Flag("json").Changed { + if !renderJson { fmt.Println(buildInfo(filter)) table = tablewriter.NewTable(os.Stdout, tablewriter.WithHeaderAutoFormat(tw.Off)) @@ -139,7 +144,7 @@ func ListUploadSessions(cfg *config.Config) *cobra.Command { ScanResult: sr, } - if cmd.Flag("json").Changed { + if renderJson { raw = append(raw, session) } else { table.Append([]string{ @@ -158,7 +163,7 @@ func ListUploadSessions(cfg *config.Config) *cobra.Command { } switch { - case cmd.Flag("restart").Changed: + case restart: if err := events.Publish(context.Background(), stream, events.RestartPostprocessing{ UploadID: u.ID(), Timestamp: utils.TSNow(), @@ -168,7 +173,7 @@ func ListUploadSessions(cfg *config.Config) *cobra.Command { os.Exit(1) } - case cmd.Flag("resume").Changed: + case resume: if err := events.Publish(context.Background(), stream, events.ResumePostprocessing{ UploadID: u.ID(), Timestamp: utils.TSNow(), @@ -178,7 +183,7 @@ func ListUploadSessions(cfg *config.Config) *cobra.Command { os.Exit(1) } - case cmd.Flag("clean").Changed: + case clean: if err := u.Purge(cmd.Context()); err != nil { fmt.Fprintf(os.Stderr, "Failed to clean upload session '%s'\n", u.ID()) } @@ -186,7 +191,7 @@ func ListUploadSessions(cfg *config.Config) *cobra.Command { } - if !cmd.Flag("json").Changed { + if !renderJson { table.Render() return nil } @@ -200,7 +205,7 @@ func ListUploadSessions(cfg *config.Config) *cobra.Command { return nil }, } - listUploadSessionsCmd.Flags().String("id", "unset", "filter sessions by upload session id") + listUploadSessionsCmd.Flags().String("id", "", "filter sessions by upload session id") listUploadSessionsCmd.Flags().Bool("processing", false, "filter sessions by processing status") listUploadSessionsCmd.Flags().Bool("expired", false, "filter sessions by expired status") listUploadSessionsCmd.Flags().Bool("has-virus", false, "filter sessions by virus scan result") @@ -214,20 +219,22 @@ func ListUploadSessions(cfg *config.Config) *cobra.Command { func buildFilter(cmd *cobra.Command) storage.UploadSessionFilter { filter := storage.UploadSessionFilter{} if cmd.Flag("processing").Changed { - processingValue := cmd.Flag("processing").Changed + processingValue, _ := cmd.Flags().GetBool("processing") filter.Processing = &processingValue } if cmd.Flag("expired").Changed { - expiredValue := cmd.Flag("expired").Changed + expiredValue, _ := cmd.Flags().GetBool("expired") filter.Expired = &expiredValue } if cmd.Flag("has-virus").Changed { - infectedValue := cmd.Flag("has-virus").Changed + infectedValue, _ := cmd.Flags().GetBool("has-virus") filter.HasVirus = &infectedValue } if cmd.Flag("id").Changed { idValue := cmd.Flag("id").Value.String() - filter.ID = &idValue + if idValue != "" { + filter.ID = &idValue + } } return filter } From 9a597922f24025b41a620934ec2f0675608ff8f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rn=20Friedrich=20Dreyer?= Date: Fri, 12 Dec 2025 14:35:29 +0100 Subject: [PATCH 67/82] fix storage-users trash-bin flags MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Jörn Friedrich Dreyer --- .../storage-users/pkg/command/trash_bin.go | 41 +++++++++---------- 1 file changed, 20 insertions(+), 21 deletions(-) diff --git a/services/storage-users/pkg/command/trash_bin.go b/services/storage-users/pkg/command/trash_bin.go index c74591455a..af7f9f2d34 100644 --- a/services/storage-users/pkg/command/trash_bin.go +++ b/services/storage-users/pkg/command/trash_bin.go @@ -93,7 +93,8 @@ func listTrashBinItems(cfg *config.Config) *cobra.Command { return configlog.ReturnFatal(parser.ParseConfig(cfg)) }, RunE: func(cmd *cobra.Command, args []string) error { - log := cliLogger(cmd.Flag("verbose").Changed) + verbose, _ := cmd.Flags().GetBool("verbose") + log := cliLogger(verbose) var spaceID string if len(args) > 0 { spaceID = args[0] @@ -150,17 +151,18 @@ func restoreAllTrashBinItems(cfg *config.Config) *cobra.Command { return configlog.ReturnFatal(parser.ParseConfig(cfg)) }, RunE: func(cmd *cobra.Command, args []string) error { - log := cliLogger(cmd.Flag("verbose").Changed) + verbose, _ := cmd.Flags().GetBool("verbose") + log := cliLogger(verbose) var spaceID string if len(args) > 0 { spaceID = args[0] } if spaceID == "" { _ = cmd.Help() - fmt.Errorf("spaceID is requiered") - os.Exit(1) + return fmt.Errorf("spaceID is requiered") } - switch cmd.Flag("option").Value.String() { + option := cmd.Flag("option").Value.String() + switch option { case "skip": overwriteOption = SKIP case "replace": @@ -169,8 +171,7 @@ func restoreAllTrashBinItems(cfg *config.Config) *cobra.Command { overwriteOption = KEEP_BOTH default: _ = cmd.Help() - fmt.Errorf("The option flag is invalid") - os.Exit(1) + return fmt.Errorf("option flag '%s' is invalid", option) } log.Info().Msgf("Restoring trash-bin items for spaceID: '%s' ...", spaceID) @@ -190,8 +191,8 @@ func restoreAllTrashBinItems(cfg *config.Config) *cobra.Command { if err != nil { return err } - - if !cmd.Flag("yes").Changed { + assumeYes, _ := cmd.Flags().GetBool("yes") + if !assumeYes { for { fmt.Printf("Found %d items that could be restored, continue (Y/n), show the items list (s): ", len(res.GetRecycleItems())) var i string @@ -214,7 +215,7 @@ func restoreAllTrashBinItems(cfg *config.Config) *cobra.Command { } } - log.Info().Msgf("Run restoring-all with option=%s", cmd.Flag("option").Value.String()) + log.Info().Msgf("Run restoring-all with option=%s", option) for _, item := range res.GetRecycleItems() { log.Info().Msgf("restoring itemID: '%s', path: '%s', type: '%s'", item.GetKey(), item.GetRef().GetPath(), itemType(item.GetType())) dstRes, err := restore(ctx, client, ref, item, overwriteOption, cfg.CliMaxAttemptsRenameFile, log) @@ -254,18 +255,16 @@ func restoreTrashBinItem(cfg *config.Config) *cobra.Command { restoreTrashBinItemCmd := &cobra.Command{ Use: "restore", Short: "Restore a trash-bin item by ID.", - // TODO: not sure this could also be 2 - Args: cobra.ExactArgs(1), + Args: cobra.ExactArgs(2), PreRunE: func(cmd *cobra.Command, args []string) error { return configlog.ReturnFatal(parser.ParseConfig(cfg)) }, RunE: func(cmd *cobra.Command, args []string) error { - log := cliLogger(cmd.Flag("verbose").Changed) + verbose, _ := cmd.Flags().GetBool("verbose") + log := cliLogger(verbose) var spaceID, itemID string - if len(args) > 1 { - spaceID = args[0] - itemID = args[1] - } + spaceID = args[0] + itemID = args[1] if spaceID == "" { _ = cmd.Help() return fmt.Errorf("spaceID is requered") @@ -274,7 +273,8 @@ func restoreTrashBinItem(cfg *config.Config) *cobra.Command { _ = cmd.Help() return fmt.Errorf("itemID is requered") } - switch cmd.Flag("option").Value.String() { + option := cmd.Flag("option").Value.String() + switch option { case "skip": overwriteOption = SKIP case "replace": @@ -283,8 +283,7 @@ func restoreTrashBinItem(cfg *config.Config) *cobra.Command { overwriteOption = KEEP_BOTH default: _ = cmd.Help() - fmt.Errorf("The option flag is invalid") - os.Exit(1) + return fmt.Errorf("option flag '%s' is invalid", option) } log.Info().Msgf("Restoring trash-bin item for spaceID: '%s' itemID: '%s' ...", spaceID, itemID) @@ -317,7 +316,7 @@ func restoreTrashBinItem(cfg *config.Config) *cobra.Command { if !found { return fmt.Errorf("itemID '%s' not found", itemID) } - log.Info().Msgf("Run restoring with option=%s", cmd.Flag("option").Value.String()) + log.Info().Msgf("Run restoring with option=%s", option) log.Info().Msgf("restoring itemID: '%s', path: '%s', type: '%s", itemRef.GetKey(), itemRef.GetRef().GetPath(), itemType(itemRef.GetType())) dstRes, err := restore(ctx, client, ref, itemRef, overwriteOption, cfg.CliMaxAttemptsRenameFile, log) if err != nil { From f66600d7db4b35f3738aac5e5bb190b33efca0c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rn=20Friedrich=20Dreyer?= Date: Fri, 12 Dec 2025 14:38:06 +0100 Subject: [PATCH 68/82] fix notifications send-email flags MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Jörn Friedrich Dreyer --- services/notifications/pkg/command/send_email.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/services/notifications/pkg/command/send_email.go b/services/notifications/pkg/command/send_email.go index a496c5ad7a..39f8dca095 100644 --- a/services/notifications/pkg/command/send_email.go +++ b/services/notifications/pkg/command/send_email.go @@ -16,8 +16,8 @@ func SendEmail(cfg *config.Config) *cobra.Command { Use: "send-email", Short: "Send grouped email notifications with daily or weekly interval. Specify at least one of the flags '--daily' or '--weekly'.", RunE: func(cmd *cobra.Command, args []string) error { - daily := cmd.Flag("daily").Changed - weekly := cmd.Flag("weekly").Changed + daily, _ := cmd.Flags().GetBool("daily") + weekly, _ := cmd.Flags().GetBool("weekly") if !daily && !weekly { return errors.New("at least one of '--daily' or '--weekly' must be set") } From 2c8c334e8870b6eacc24f06df3a7f5011aee07ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rn=20Friedrich=20Dreyer?= Date: Fri, 12 Dec 2025 14:43:25 +0100 Subject: [PATCH 69/82] fix postprocessing restart flags MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Jörn Friedrich Dreyer --- services/postprocessing/pkg/command/postprocessing.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/services/postprocessing/pkg/command/postprocessing.go b/services/postprocessing/pkg/command/postprocessing.go index e82b14eea8..69e8b5f2d2 100644 --- a/services/postprocessing/pkg/command/postprocessing.go +++ b/services/postprocessing/pkg/command/postprocessing.go @@ -44,9 +44,10 @@ func RestartPostprocessing(cfg *config.Config) *cobra.Command { step = cmd.Flag("step").Value.String() } + restart, _ := cmd.Flags().GetBool("restart") var ev events.Unmarshaller switch { - case cmd.Flag("restart").Changed: + case restart: ev = events.RestartPostprocessing{ UploadID: uid, Timestamp: utils.TSNow(), From 6e75e4102311dbe1f9185756065c814df22ab105 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rn=20Friedrich=20Dreyer?= Date: Fri, 12 Dec 2025 14:44:49 +0100 Subject: [PATCH 70/82] fix search index flags MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Jörn Friedrich Dreyer --- services/search/pkg/command/index.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/services/search/pkg/command/index.go b/services/search/pkg/command/index.go index 1a9f979d85..383b178f1a 100644 --- a/services/search/pkg/command/index.go +++ b/services/search/pkg/command/index.go @@ -27,7 +27,8 @@ func Index(cfg *config.Config) *cobra.Command { return configlog.ReturnFatal(parser.ParseConfig(cfg)) }, RunE: func(cmd *cobra.Command, args []string) error { - if cmd.Flag("space").Value.String() == "" && !cmd.Flag("all-spaces").Changed { + allSpaces, _ := cmd.Flags().GetBool("all-spaces") + if cmd.Flag("space").Value.String() == "" && !allSpaces { return errors.New("either --space or --all-spaces is required") } From f7681b36dd8b708000c934350c930b49c45c86fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rn=20Friedrich=20Dreyer?= Date: Fri, 12 Dec 2025 14:46:35 +0100 Subject: [PATCH 71/82] fix opencloud backup flags MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Jörn Friedrich Dreyer --- opencloud/pkg/command/backup.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/opencloud/pkg/command/backup.go b/opencloud/pkg/command/backup.go index 3573c3df40..30acc2d829 100644 --- a/opencloud/pkg/command/backup.go +++ b/opencloud/pkg/command/backup.go @@ -66,7 +66,8 @@ func ConsistencyCommand(cfg *config.Config) *cobra.Command { fmt.Println(err) return err } - if err := backup.CheckProviderConsistency(basePath, bs, cmd.Flag("fail").Changed); err != nil { + fail, _ := cmd.Flags().GetBool("fail") + if err := backup.CheckProviderConsistency(basePath, bs, fail); err != nil { fmt.Println(err) return err } From 94a49aa404fbc5ba1d3f9bffd6577736d97ccd26 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rn=20Friedrich=20Dreyer?= Date: Fri, 12 Dec 2025 14:47:25 +0100 Subject: [PATCH 72/82] fix opencloud benchmark flags MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Jörn Friedrich Dreyer --- opencloud/pkg/command/benchmark.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/opencloud/pkg/command/benchmark.go b/opencloud/pkg/command/benchmark.go index 7d7104f982..76fd956bbc 100644 --- a/opencloud/pkg/command/benchmark.go +++ b/opencloud/pkg/command/benchmark.go @@ -44,10 +44,11 @@ func BenchmarkClientCommand(cfg *config.Config) *cobra.Command { if err != nil { return err } + insecure, _ := cmd.Flags().GetBool("insecure") opt := clientOptions{ request: cmd.Flag("request").Value.String(), url: args[0], - insecure: cmd.Flag("insecure").Changed, + insecure: insecure, jobs: jobs, headers: make(map[string]string), data: []byte(cmd.Flag("data").Value.String()), From 35b3e5105256e5922944b704a394901d1a373c8f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rn=20Friedrich=20Dreyer?= Date: Fri, 12 Dec 2025 14:49:14 +0100 Subject: [PATCH 73/82] fix opencloud decomposedfs flags MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Jörn Friedrich Dreyer --- opencloud/pkg/command/decomposedfs.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/opencloud/pkg/command/decomposedfs.go b/opencloud/pkg/command/decomposedfs.go index f66ab47f58..5297123f42 100644 --- a/opencloud/pkg/command/decomposedfs.go +++ b/opencloud/pkg/command/decomposedfs.go @@ -69,9 +69,10 @@ func checkCmd(cfg *config.Config) *cobra.Command { func check(cmd *cobra.Command, args []string) error { rootFlag := cmd.Flag("root").Value.String() - repairFlag := cmd.Flag("repair").Changed + repairFlag, _ := cmd.Flags().GetBool("repair") + forceFlag, _ := cmd.Flags().GetBool("force") - if repairFlag && !cmd.Flag("force").Changed { + if repairFlag && !forceFlag { answer := strings.ToLower(stringPrompt("IMPORTANT: Only use '--repair' when OpenCloud is not running. Do you want to continue? [yes | no = default]")) if answer != "yes" && answer != "y" { return nil From 6204c6b3cb48561ec6e725d8c275c314d50d54d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rn=20Friedrich=20Dreyer?= Date: Fri, 12 Dec 2025 14:53:35 +0100 Subject: [PATCH 74/82] fix opencloud init flags MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Jörn Friedrich Dreyer --- opencloud/pkg/command/init.go | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/opencloud/pkg/command/init.go b/opencloud/pkg/command/init.go index 84f37c674c..f4b5e3f9e3 100644 --- a/opencloud/pkg/command/init.go +++ b/opencloud/pkg/command/init.go @@ -33,8 +33,10 @@ func InitCommand(_ *config.Config) *cobra.Command { } else if insecureFlag == strings.ToLower("true") || insecureFlag == strings.ToLower("yes") || insecureFlag == strings.ToLower("y") { insecure = true } - err := ocinit.CreateConfig(insecure, cmd.Flag("force-overwrite").Changed, - cmd.Flag("diff").Changed, cmd.Flag("config-path").Value.String(), + forceOverwrite, _ := cmd.Flags().GetBool("force-overwrite") + diff, _ := cmd.Flags().GetBool("force-overwrite") + err := ocinit.CreateConfig(insecure, forceOverwrite, + diff, cmd.Flag("config-path").Value.String(), cmd.Flag("admin-password").Value.String()) if err != nil { log.Fatalf("Could not create config: %s", err) @@ -74,6 +76,9 @@ func InitCommand(_ *config.Config) *cobra.Command { log.Fatalf("Could not bind environment variable OC_BASE_DATA_PATH: %s", err) } err = viper.BindPFlag("config-path", initCmd.Flags().Lookup("config-path")) + if err != nil { + log.Fatalf("Could not bind flag OC_BASE_DATA_PATH: %s", err) + } initCmd.Flags().String("admin-password", "", "Set admin password instead of using a random generated one") err = viper.BindEnv("admin-password", "ADMIN_PASSWORD") @@ -85,6 +90,9 @@ func InitCommand(_ *config.Config) *cobra.Command { log.Fatalf("Could not bind environment variable IDM_ADMIN_PASSWORD: %s", err) } err = viper.BindPFlag("admin-password", initCmd.Flags().Lookup("admin-password")) + if err != nil { + log.Fatalf("Could not bind flag IDM_ADMIN_PASSWORD: %s", err) + } return initCmd } From eebccf7c782824db9d8b5e1d67f41fb039342c70 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rn=20Friedrich=20Dreyer?= Date: Fri, 12 Dec 2025 14:55:40 +0100 Subject: [PATCH 75/82] fix opencloud trash flags MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Jörn Friedrich Dreyer --- opencloud/pkg/command/trash.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/opencloud/pkg/command/trash.go b/opencloud/pkg/command/trash.go index 0f28666bd1..5e3ca0b19c 100644 --- a/opencloud/pkg/command/trash.go +++ b/opencloud/pkg/command/trash.go @@ -41,7 +41,8 @@ func TrashPurgeEmptyDirsCommand(cfg *config.Config) *cobra.Command { return nil } - if err := trash.PurgeTrashEmptyPaths(basePath, cmd.Flag("dry-run").Changed); err != nil { + dryRun, _ := cmd.Flags().GetBool("dry-run") + if err := trash.PurgeTrashEmptyPaths(basePath, dryRun); err != nil { fmt.Println(err) return err } From 8564fa45363487979e3bae6e0f2db900f41b1207 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rn=20Friedrich=20Dreyer?= Date: Fri, 12 Dec 2025 14:55:51 +0100 Subject: [PATCH 76/82] fix opencloud version flags MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Jörn Friedrich Dreyer --- opencloud/pkg/command/version.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/opencloud/pkg/command/version.go b/opencloud/pkg/command/version.go index 5f77911561..39a1ab9454 100644 --- a/opencloud/pkg/command/version.go +++ b/opencloud/pkg/command/version.go @@ -31,7 +31,8 @@ func VersionCommand(cfg *config.Config) *cobra.Command { fmt.Printf("Edition: %s\n", version.Edition) fmt.Printf("Compiled: %s\n", version.Compiled()) - if cmd.Flag(_skipServiceListingFlagName).Changed { + skipServiceListing, _ := cmd.Flags().GetBool(_skipServiceListingFlagName) + if skipServiceListing { return nil } From 032c218789ccecdfab89ead799754063d05a2960 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rn=20Friedrich=20Dreyer?= Date: Fri, 12 Dec 2025 15:06:16 +0100 Subject: [PATCH 77/82] replace .Value.String() with .GetString(...) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Jörn Friedrich Dreyer --- opencloud/pkg/command/backup.go | 2 +- opencloud/pkg/command/benchmark.go | 8 ++++---- opencloud/pkg/command/decomposedfs.go | 10 +++++----- opencloud/pkg/command/init.go | 2 +- opencloud/pkg/command/posixfs.go | 2 +- opencloud/pkg/command/revisions.go | 4 ++-- opencloud/pkg/command/trash.go | 2 +- services/auth-app/pkg/command/create.go | 2 +- services/postprocessing/pkg/command/postprocessing.go | 2 +- services/storage-users/pkg/command/trash_bin.go | 4 ++-- services/storage-users/pkg/command/uploads.go | 2 +- 11 files changed, 20 insertions(+), 20 deletions(-) diff --git a/opencloud/pkg/command/backup.go b/opencloud/pkg/command/backup.go index 30acc2d829..359da06f20 100644 --- a/opencloud/pkg/command/backup.go +++ b/opencloud/pkg/command/backup.go @@ -34,7 +34,7 @@ func ConsistencyCommand(cfg *config.Config) *cobra.Command { Use: "consistency", Short: "check backup consistency", RunE: func(cmd *cobra.Command, args []string) error { - basePath := cmd.Flag("basepath").Value.String() + basePath, _ := cmd.Flags().GetString("basepath") if basePath == "" { fmt.Println("basepath is required") _ = cmd.Help() diff --git a/opencloud/pkg/command/benchmark.go b/opencloud/pkg/command/benchmark.go index 76fd956bbc..f74460ef62 100644 --- a/opencloud/pkg/command/benchmark.go +++ b/opencloud/pkg/command/benchmark.go @@ -69,7 +69,7 @@ func BenchmarkClientCommand(cfg *config.Config) *cobra.Command { opt.headers[parts[0]] = strings.TrimSpace(parts[1]) } - rate := cmd.Flag("rate").Value.String() + rate, _ := cmd.Flags().GetString("rate") if rate != "" { parts := strings.SplitN(rate, "/", 2) num, err := strconv.Atoi(parts[0]) @@ -92,12 +92,12 @@ func BenchmarkClientCommand(cfg *config.Config) *cobra.Command { opt.rateDelay = unit / time.Duration(num) } - user := cmd.Flag("user").Value.String() + user, _ := cmd.Flags().GetString("user") opt.auth = func() string { return "Basic " + base64.StdEncoding.EncodeToString([]byte(user)) } - btc := cmd.Flag("bearer-token-command").Value.String() + btc, _ := cmd.Flags().GetString("bearer-token-command") if btc != "" { parts := strings.SplitN(btc, " ", 2) var cmd *exec.Cmd @@ -247,7 +247,7 @@ func BenchmarkSyscallsCommand(cfg *config.Config) *cobra.Command { Short: "test the performance of syscalls", RunE: func(cmd *cobra.Command, args []string) error { - path := cmd.Flag("path").Value.String() + path, _ := cmd.Flags().GetString("path") if path == "" { f, err := os.CreateTemp("", "opencloud-bench-temp-") if err != nil { diff --git a/opencloud/pkg/command/decomposedfs.go b/opencloud/pkg/command/decomposedfs.go index 5297123f42..4344e4aa63 100644 --- a/opencloud/pkg/command/decomposedfs.go +++ b/opencloud/pkg/command/decomposedfs.go @@ -68,7 +68,7 @@ func checkCmd(cfg *config.Config) *cobra.Command { } func check(cmd *cobra.Command, args []string) error { - rootFlag := cmd.Flag("root").Value.String() + rootFlag, _ := cmd.Flags().GetString("root") repairFlag, _ := cmd.Flags().GetBool("repair") forceFlag, _ := cmd.Flags().GetBool("force") @@ -92,7 +92,7 @@ func check(cmd *cobra.Command, args []string) error { tree := tree.New(lu, bs, o, permissions.Permissions{}, store.Create(), &zerolog.Logger{}) - nId := cmd.Flag("node").Value.String() + nId, _ := cmd.Flags().GetString("node") n, err := lu.NodeFromSpaceID(context.Background(), nId) if err != nil || !n.Exists { fmt.Println("Can not find node '" + nId + "'") @@ -253,7 +253,7 @@ func setCmd(cfg *config.Config) *cobra.Command { return err } - v := cmd.Flag("value").Value.String() + v, _ := cmd.Flags().GetString("value") if strings.HasPrefix(v, "0s") { b64, err := base64.StdEncoding.DecodeString(v[2:]) if err == nil { @@ -304,7 +304,7 @@ func backend(root, backend string) metadata.Backend { } func getBackend(cmd *cobra.Command) (*lookup.Lookup, metadata.Backend) { - rootFlag := cmd.Flag("root").Value.String() + rootFlag, _ := cmd.Flags().GetString("root") bod := lookup.DetectBackendOnDisk(rootFlag) backend := backend(rootFlag, bod) @@ -316,7 +316,7 @@ func getBackend(cmd *cobra.Command) (*lookup.Lookup, metadata.Backend) { } func getNode(cmd *cobra.Command, lu *lookup.Lookup) (*node.Node, error) { - nodeFlag := cmd.Flag("node").Value.String() + nodeFlag, _ := cmd.Flags().GetString("node") id, err := storagespace.ParseID(nodeFlag) if err != nil { diff --git a/opencloud/pkg/command/init.go b/opencloud/pkg/command/init.go index f4b5e3f9e3..470a9aa6fa 100644 --- a/opencloud/pkg/command/init.go +++ b/opencloud/pkg/command/init.go @@ -23,7 +23,7 @@ func InitCommand(_ *config.Config) *cobra.Command { Short: "initialise an OpenCloud config", GroupID: CommandGroupServer, RunE: func(cmd *cobra.Command, args []string) error { - insecureFlag := cmd.Flag("insecure").Value.String() + insecureFlag, _ := cmd.Flags().GetString("insecure") insecure := false if insecureFlag == "ask" { answer := strings.ToLower(stringPrompt("Do you want to configure OpenCloud with certificate checking disabled?\n This is not recommended for public instances! [yes | no = default]")) diff --git a/opencloud/pkg/command/posixfs.go b/opencloud/pkg/command/posixfs.go index 070eb2b5d7..b02e6bc1f6 100644 --- a/opencloud/pkg/command/posixfs.go +++ b/opencloud/pkg/command/posixfs.go @@ -71,7 +71,7 @@ func consistencyCmd(cfg *config.Config) *cobra.Command { // checkPosixfsConsistency checks the consistency of the posixfs storage. func checkPosixfsConsistency(cmd *cobra.Command, cfg *config.Config) error { - rootPath := cmd.Flag("root").Value.String() + rootPath, _ := cmd.Flags().GetString("root") indexesPath := filepath.Join(rootPath, "indexes") _, err := os.Stat(indexesPath) diff --git a/opencloud/pkg/command/revisions.go b/opencloud/pkg/command/revisions.go index 14ecdc53a5..d7ce2c1b3c 100644 --- a/opencloud/pkg/command/revisions.go +++ b/opencloud/pkg/command/revisions.go @@ -45,7 +45,7 @@ func PurgeRevisionsCommand(cfg *config.Config) *cobra.Command { Use: "purge", Short: "purge revisions", RunE: func(cmd *cobra.Command, args []string) error { - basePath := cmd.Flag("basepath").Value.String() + basePath, _ := cmd.Flags().GetString("basepath") if basePath == "" { fmt.Println("basepath is required") _ = cmd.Help() @@ -84,7 +84,7 @@ func PurgeRevisionsCommand(cfg *config.Config) *cobra.Command { rid = &resid } - mechanism := cmd.Flag("glob-mechanism").Value.String() + mechanism, _ := cmd.Flags().GetString("glob-mechanism") if rid.GetOpaqueId() != "" { mechanism = "glob" } diff --git a/opencloud/pkg/command/trash.go b/opencloud/pkg/command/trash.go index 5e3ca0b19c..06210c012f 100644 --- a/opencloud/pkg/command/trash.go +++ b/opencloud/pkg/command/trash.go @@ -34,7 +34,7 @@ func TrashPurgeEmptyDirsCommand(cfg *config.Config) *cobra.Command { Use: "purge-empty-dirs", Short: "purge empty directories", RunE: func(cmd *cobra.Command, args []string) error { - basePath := cmd.Flag("basepath").Value.String() + basePath, _ := cmd.Flags().GetString("basepath") if basePath == "" { fmt.Println("basepath is required") _ = cmd.Help() diff --git a/services/auth-app/pkg/command/create.go b/services/auth-app/pkg/command/create.go index 0d81535ab0..f80ccc17d7 100644 --- a/services/auth-app/pkg/command/create.go +++ b/services/auth-app/pkg/command/create.go @@ -56,7 +56,7 @@ func Create(cfg *config.Config) *cobra.Command { return err } - userName := cmd.Flag("user-name").Value.String() + userName, _ := cmd.Flags().GetString("user-name") if userName == "" { fmt.Printf("Username to create app token for: ") if _, err := fmt.Scanln(&userName); err != nil { diff --git a/services/postprocessing/pkg/command/postprocessing.go b/services/postprocessing/pkg/command/postprocessing.go index 69e8b5f2d2..b11627f6e3 100644 --- a/services/postprocessing/pkg/command/postprocessing.go +++ b/services/postprocessing/pkg/command/postprocessing.go @@ -39,7 +39,7 @@ func RestartPostprocessing(cfg *config.Config) *cobra.Command { return err } - uid, step := cmd.Flag("upload-id").Value.String(), "" + uid, step, _ := cmd.Flags().GetString("upload-id"), "" if uid == "" { step = cmd.Flag("step").Value.String() } diff --git a/services/storage-users/pkg/command/trash_bin.go b/services/storage-users/pkg/command/trash_bin.go index af7f9f2d34..50def1ab52 100644 --- a/services/storage-users/pkg/command/trash_bin.go +++ b/services/storage-users/pkg/command/trash_bin.go @@ -161,7 +161,7 @@ func restoreAllTrashBinItems(cfg *config.Config) *cobra.Command { _ = cmd.Help() return fmt.Errorf("spaceID is requiered") } - option := cmd.Flag("option").Value.String() + option, _ := cmd.Flags().GetString("option") switch option { case "skip": overwriteOption = SKIP @@ -273,7 +273,7 @@ func restoreTrashBinItem(cfg *config.Config) *cobra.Command { _ = cmd.Help() return fmt.Errorf("itemID is requered") } - option := cmd.Flag("option").Value.String() + option, _ := cmd.Flags().GetString("option") switch option { case "skip": overwriteOption = SKIP diff --git a/services/storage-users/pkg/command/uploads.go b/services/storage-users/pkg/command/uploads.go index 3d846ccfee..fc169fabe6 100644 --- a/services/storage-users/pkg/command/uploads.go +++ b/services/storage-users/pkg/command/uploads.go @@ -231,7 +231,7 @@ func buildFilter(cmd *cobra.Command) storage.UploadSessionFilter { filter.HasVirus = &infectedValue } if cmd.Flag("id").Changed { - idValue := cmd.Flag("id").Value.String() + idValue, _ := cmd.Flags().GetString("id") if idValue != "" { filter.ID = &idValue } From d0e51010bff5743e02a8baca96e6746e51d74310 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rn=20Friedrich=20Dreyer?= Date: Fri, 12 Dec 2025 15:26:41 +0100 Subject: [PATCH 78/82] replace more .Value.String() occurences MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Jörn Friedrich Dreyer --- opencloud/pkg/command/backup.go | 3 ++- opencloud/pkg/command/decomposedfs.go | 24 ++++++++++++------- opencloud/pkg/command/init.go | 10 ++++---- opencloud/pkg/command/revisions.go | 6 +++-- opencloud/pkg/command/shares.go | 5 ++-- services/auth-app/pkg/command/create.go | 2 +- services/idm/pkg/command/resetpw.go | 3 ++- .../pkg/command/postprocessing.go | 5 ++-- services/search/pkg/command/index.go | 7 +++--- tests/ocwrapper/cmd/cmd.go | 21 ++++++++++------ 10 files changed, 53 insertions(+), 33 deletions(-) diff --git a/opencloud/pkg/command/backup.go b/opencloud/pkg/command/backup.go index 359da06f20..05b922474e 100644 --- a/opencloud/pkg/command/backup.go +++ b/opencloud/pkg/command/backup.go @@ -45,7 +45,8 @@ func ConsistencyCommand(cfg *config.Config) *cobra.Command { bs backup.ListBlobstore err error ) - switch cmd.Flag("blobstore").Value.String() { + blobstoreFlag, _ := cmd.Flags().GetString("blobstore") + switch blobstoreFlag { case "decomposeds3": bs, err = decomposeds3bs.New( cfg.StorageUsers.Drivers.DecomposedS3.Endpoint, diff --git a/opencloud/pkg/command/decomposedfs.go b/opencloud/pkg/command/decomposedfs.go index 4344e4aa63..d2f64ec939 100644 --- a/opencloud/pkg/command/decomposedfs.go +++ b/opencloud/pkg/command/decomposedfs.go @@ -45,7 +45,7 @@ func init() { register.AddCommand(DecomposedfsCommand) } -func checkCmd(cfg *config.Config) *cobra.Command { +func checkCmd(_ *config.Config) *cobra.Command { cCmd := &cobra.Command{ Use: "check-treesize", Short: `cli tool to check the treesize metadata of a Space`, @@ -108,6 +108,9 @@ func check(cmd *cobra.Command, args []string) error { }) treeSize, err := walkTree(ctx, tree, lu, n, repairFlag) + if err != nil { + fmt.Printf("failed to walk tree of node %s: %s\n", n.ID, err) + } treesizeFromMetadata, err := n.GetTreeSize(cmd.Context()) if err != nil { fmt.Printf("failed to read treesize of node: %s: %s\n", n.ID, err) @@ -196,7 +199,7 @@ func metadataCmd(cfg *config.Config) *cobra.Command { return metaCmd } -func dumpCmd(cfg *config.Config) *cobra.Command { +func dumpCmd(_ *config.Config) *cobra.Command { return &cobra.Command{ Use: "dump", Short: `print the metadata of the given node. String attributes will be enclosed in quotes. Binary attributes will be returned encoded as base64 with their value being prefixed with '0s'.`, @@ -212,13 +215,14 @@ func dumpCmd(cfg *config.Config) *cobra.Command { fmt.Println("Error reading attributes") return err } - printAttribs(attribs, cmd.Flag("attribute").Value.String()) + attributeFlag, _ := cmd.Flags().GetString("attribute") + printAttribs(attribs, attributeFlag) return nil }, } } -func getCmd(cfg *config.Config) *cobra.Command { +func getCmd(_ *config.Config) *cobra.Command { gCmd := &cobra.Command{ Use: "get", Short: `print a specific attribute of the given node. String attributes will be enclosed in quotes. Binary attributes will be returned encoded as base64 with their value being prefixed with '0s'.`, @@ -234,7 +238,8 @@ func getCmd(cfg *config.Config) *cobra.Command { fmt.Println("Error reading attributes") return err } - printAttribs(attribs, cmd.Flag("attribute").Value.String()) + attributeFlag, _ := cmd.Flags().GetString("attribute") + printAttribs(attribs, attributeFlag) return nil }, } @@ -242,7 +247,7 @@ func getCmd(cfg *config.Config) *cobra.Command { return gCmd } -func setCmd(cfg *config.Config) *cobra.Command { +func setCmd(_ *config.Config) *cobra.Command { sCmd := &cobra.Command{ Use: "set", Short: `manipulate metadata of the given node. Binary attributes can be given hex encoded (prefix by '0x') or base64 encoded (prefix by '0s').`, @@ -270,7 +275,8 @@ func setCmd(cfg *config.Config) *cobra.Command { } } - err = backend.Set(cmd.Context(), n, cmd.Flag("attribute").Value.String(), []byte(v)) + attributeFlag, _ := cmd.Flags().GetString("attribute") + err = backend.Set(cmd.Context(), n, attributeFlag, []byte(v)) if err != nil { fmt.Println("Error setting attribute") return err @@ -293,7 +299,7 @@ func setCmd(cfg *config.Config) *cobra.Command { return sCmd } -func backend(root, backend string) metadata.Backend { +func backend(backend string) metadata.Backend { switch backend { case "xattrs": return metadata.NewXattrsBackend(cache.Config{}) @@ -307,7 +313,7 @@ func getBackend(cmd *cobra.Command) (*lookup.Lookup, metadata.Backend) { rootFlag, _ := cmd.Flags().GetString("root") bod := lookup.DetectBackendOnDisk(rootFlag) - backend := backend(rootFlag, bod) + backend := backend(bod) lu := lookup.New(backend, &options.Options{ Root: rootFlag, MetadataBackend: bod, diff --git a/opencloud/pkg/command/init.go b/opencloud/pkg/command/init.go index 470a9aa6fa..57dea6b5ae 100644 --- a/opencloud/pkg/command/init.go +++ b/opencloud/pkg/command/init.go @@ -33,11 +33,11 @@ func InitCommand(_ *config.Config) *cobra.Command { } else if insecureFlag == strings.ToLower("true") || insecureFlag == strings.ToLower("yes") || insecureFlag == strings.ToLower("y") { insecure = true } - forceOverwrite, _ := cmd.Flags().GetBool("force-overwrite") - diff, _ := cmd.Flags().GetBool("force-overwrite") - err := ocinit.CreateConfig(insecure, forceOverwrite, - diff, cmd.Flag("config-path").Value.String(), - cmd.Flag("admin-password").Value.String()) + forceOverwriteFlag, _ := cmd.Flags().GetBool("force-overwrite") + diffFlag, _ := cmd.Flags().GetBool("force-overwrite") + configPathFlag, _ := cmd.Flags().GetString("config-path") + adminPasswordFlag, _ := cmd.Flags().GetString("admin-password") + err := ocinit.CreateConfig(insecure, forceOverwriteFlag, diffFlag, configPathFlag, adminPasswordFlag) if err != nil { log.Fatalf("Could not create config: %s", err) } diff --git a/opencloud/pkg/command/revisions.go b/opencloud/pkg/command/revisions.go index d7ce2c1b3c..f8c928c499 100644 --- a/opencloud/pkg/command/revisions.go +++ b/opencloud/pkg/command/revisions.go @@ -56,7 +56,8 @@ func PurgeRevisionsCommand(cfg *config.Config) *cobra.Command { bs revisions.DelBlobstore err error ) - switch cmd.Flag("blobstore").Value.String() { + blobstoreFlag, _ := cmd.Flags().GetString("blobstore") + switch blobstoreFlag { case "decomposeds3": bs, err = decomposeds3bs.New( cfg.StorageUsers.Drivers.DecomposedS3.Endpoint, @@ -79,7 +80,8 @@ func PurgeRevisionsCommand(cfg *config.Config) *cobra.Command { } var rid *provider.ResourceId - resid, err := storagespace.ParseID(cmd.Flag("resource-id").Value.String()) + resourceIDFlag, _ := cmd.Flags().GetString("resource-id") + resid, err := storagespace.ParseID(resourceIDFlag) if err == nil { rid = &resid } diff --git a/opencloud/pkg/command/shares.go b/opencloud/pkg/command/shares.go index ccecacf85f..0bb3c9b2a6 100644 --- a/opencloud/pkg/command/shares.go +++ b/opencloud/pkg/command/shares.go @@ -119,8 +119,9 @@ func cleanup(cmd *cobra.Command, cfg *config.Config) error { return configlog.ReturnError(err) } - serviceUserCtx, err := utils.GetServiceUserContext(cmd.Flag("service-account-id").Value.String(), - client, cmd.Flag("service-account-secret").Value.String()) + serviceAccountIDFlag, _ := cmd.Flags().GetString("service-account-id") + serviceAccountSecretFlag, _ := cmd.Flags().GetString("service-account-secret") + serviceUserCtx, err := utils.GetServiceUserContext(serviceAccountIDFlag, client, serviceAccountSecretFlag) if err != nil { return configlog.ReturnError(err) } diff --git a/services/auth-app/pkg/command/create.go b/services/auth-app/pkg/command/create.go index f80ccc17d7..fc889cfd74 100644 --- a/services/auth-app/pkg/command/create.go +++ b/services/auth-app/pkg/command/create.go @@ -85,7 +85,7 @@ func Create(cfg *config.Config) *cobra.Command { return err } - expiry, err := time.ParseDuration(cmd.Flag("expiration").Value.String()) + expiry, err := cmd.Flags().GetDuration("expiration") if err != nil { return err } diff --git a/services/idm/pkg/command/resetpw.go b/services/idm/pkg/command/resetpw.go index ab9d5f0c9c..1f31065cdb 100644 --- a/services/idm/pkg/command/resetpw.go +++ b/services/idm/pkg/command/resetpw.go @@ -35,7 +35,8 @@ func ResetPassword(cfg *config.Config) *cobra.Command { ctx, cancel := context.WithCancel(cmd.Context()) defer cancel() - return resetPassword(ctx, logger, cfg, cmd.Flag("user-name").Value.String()) + userNameFlag, _ := cmd.Flags().GetString("user-name") + return resetPassword(ctx, logger, cfg, userNameFlag) }, } resetPasswordCmd.Flags().StringP( diff --git a/services/postprocessing/pkg/command/postprocessing.go b/services/postprocessing/pkg/command/postprocessing.go index b11627f6e3..be575d2587 100644 --- a/services/postprocessing/pkg/command/postprocessing.go +++ b/services/postprocessing/pkg/command/postprocessing.go @@ -39,9 +39,10 @@ func RestartPostprocessing(cfg *config.Config) *cobra.Command { return err } - uid, step, _ := cmd.Flags().GetString("upload-id"), "" + uid, _ := cmd.Flags().GetString("upload-id") + step := "" if uid == "" { - step = cmd.Flag("step").Value.String() + step, _ = cmd.Flags().GetString("step") } restart, _ := cmd.Flags().GetBool("restart") diff --git a/services/search/pkg/command/index.go b/services/search/pkg/command/index.go index 383b178f1a..efb35444e1 100644 --- a/services/search/pkg/command/index.go +++ b/services/search/pkg/command/index.go @@ -27,8 +27,9 @@ func Index(cfg *config.Config) *cobra.Command { return configlog.ReturnFatal(parser.ParseConfig(cfg)) }, RunE: func(cmd *cobra.Command, args []string) error { - allSpaces, _ := cmd.Flags().GetBool("all-spaces") - if cmd.Flag("space").Value.String() == "" && !allSpaces { + allSpacesFlag, _ := cmd.Flags().GetBool("all-spaces") + spaceFlag, _ := cmd.Flags().GetString("space") + if spaceFlag == "" && !allSpacesFlag { return errors.New("either --space or --all-spaces is required") } @@ -47,7 +48,7 @@ func Index(cfg *config.Config) *cobra.Command { c := searchsvc.NewSearchProviderService("eu.opencloud.api.search", grpcClient) _, err = c.IndexSpace(context.Background(), &searchsvc.IndexSpaceRequest{ - SpaceId: cmd.Flag("space").Value.String(), + SpaceId: spaceFlag, }, func(opts *client.CallOptions) { opts.RequestTimeout = 10 * time.Minute }) if err != nil { fmt.Println("failed to index space: " + err.Error()) diff --git a/tests/ocwrapper/cmd/cmd.go b/tests/ocwrapper/cmd/cmd.go index 1f5e3edfec..4b67e12954 100644 --- a/tests/ocwrapper/cmd/cmd.go +++ b/tests/ocwrapper/cmd/cmd.go @@ -30,16 +30,23 @@ func serveCmd() *cobra.Command { common.Wg.Add(2) // set configs - opencloudConfig.Set("bin", cmd.Flag("bin").Value.String()) - opencloudConfig.Set("url", cmd.Flag("url").Value.String()) - opencloudConfig.Set("retry", cmd.Flag("retry").Value.String()) - opencloudConfig.Set("adminUsername", cmd.Flag("admin-username").Value.String()) - opencloudConfig.Set("adminPassword", cmd.Flag("admin-password").Value.String()) + binFlag, _ := cmd.Flags().GetString("bin") + opencloudConfig.Set("bin", binFlag) + urlFlag, _ := cmd.Flags().GetString("url") + opencloudConfig.Set("url", urlFlag) + retryFlag, _ := cmd.Flags().GetString("retry") + opencloudConfig.Set("retry", retryFlag) + adminUsernameFlag, _ := cmd.Flags().GetString("admin-username") + opencloudConfig.Set("adminUsername", adminUsernameFlag) + adminPasswordFlag, _ := cmd.Flags().GetString("admin-password") + opencloudConfig.Set("adminPassword", adminPasswordFlag) - if cmd.Flag("skip-OpenCloud-run").Value.String() == "false" { + skipOpenCloudRunFlag, _ := cmd.Flags().GetBool("skip-OpenCloud-run") + if !skipOpenCloudRunFlag { go opencloud.Start(nil) } - go wrapper.Start(cmd.Flag("port").Value.String()) + portFlag, _ := cmd.Flags().GetString("port") + go wrapper.Start(portFlag) }, } From 61707ab6d026ae872dbcee38c102c986da9ed405 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rn=20Friedrich=20Dreyer?= Date: Fri, 12 Dec 2025 16:07:23 +0100 Subject: [PATCH 79/82] double check required args MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Jörn Friedrich Dreyer --- opencloud/pkg/command/backup.go | 13 ++---------- opencloud/pkg/command/decomposedfs.go | 30 ++++++--------------------- opencloud/pkg/command/revisions.go | 6 +----- opencloud/pkg/command/shares.go | 23 ++++++-------------- opencloud/pkg/command/trash.go | 11 +--------- 5 files changed, 16 insertions(+), 67 deletions(-) diff --git a/opencloud/pkg/command/backup.go b/opencloud/pkg/command/backup.go index 05b922474e..19a8622964 100644 --- a/opencloud/pkg/command/backup.go +++ b/opencloud/pkg/command/backup.go @@ -34,17 +34,11 @@ func ConsistencyCommand(cfg *config.Config) *cobra.Command { Use: "consistency", Short: "check backup consistency", RunE: func(cmd *cobra.Command, args []string) error { - basePath, _ := cmd.Flags().GetString("basepath") - if basePath == "" { - fmt.Println("basepath is required") - _ = cmd.Help() - return nil - } - var ( bs backup.ListBlobstore err error ) + basePath, _ := cmd.Flags().GetString("basepath") blobstoreFlag, _ := cmd.Flags().GetString("blobstore") switch blobstoreFlag { case "decomposeds3": @@ -77,10 +71,7 @@ func ConsistencyCommand(cfg *config.Config) *cobra.Command { }, } consCmd.Flags().StringP("basepath", "p", "", "the basepath of the decomposedfs (e.g. /var/tmp/opencloud/storage/users)") - err := consCmd.MarkFlagRequired("basepath") - if err != nil { - fmt.Println(err) - } + _ = consCmd.MarkFlagRequired("basepath") consCmd.Flags().StringP("blobstore", "b", "decomposed", "the blobstore type. Can be (none, decomposed, decomposeds3). Default decomposed") consCmd.Flags().Bool("fail", false, "exit with non-zero status if consistency check fails") return consCmd diff --git a/opencloud/pkg/command/decomposedfs.go b/opencloud/pkg/command/decomposedfs.go index d2f64ec939..32516e3363 100644 --- a/opencloud/pkg/command/decomposedfs.go +++ b/opencloud/pkg/command/decomposedfs.go @@ -52,15 +52,9 @@ func checkCmd(_ *config.Config) *cobra.Command { RunE: check, } cCmd.Flags().StringP("root", "r", "", "Path to the root directory of the decomposedfs") - err := cCmd.MarkFlagRequired("root") - if err != nil { - fmt.Println(err) - } + _ = cCmd.MarkFlagRequired("root") cCmd.Flags().StringP("node", "n", "", "Space ID of the Space to inspect") - err = cCmd.MarkFlagRequired("node") - if err != nil { - fmt.Println(err) - } + _ = cCmd.MarkFlagRequired("node") cCmd.Flags().Bool("repair", false, "Try to repair nodes with incorrect treesize metadata. IMPORTANT: Only use this while OpenCloud is not running.") cCmd.Flags().Bool("force", false, "Do not prompt for confirmation when running in repair mode.") @@ -187,15 +181,9 @@ func metadataCmd(cfg *config.Config) *cobra.Command { } metaCmd.AddCommand(dumpCmd(cfg), getCmd(cfg), setCmd(cfg)) metaCmd.Flags().StringP("root", "r", "", "Path to the root directory of the decomposedfs") - err := metaCmd.MarkFlagRequired("root") - if err != nil { - fmt.Println(err) - } + _ = metaCmd.MarkFlagRequired("root") metaCmd.Flags().StringP("node", "n", "", "Path to or ID of the node to inspect") - err = metaCmd.MarkFlagRequired("node") - if err != nil { - fmt.Println(err) - } + _ = metaCmd.MarkFlagRequired("node") return metaCmd } @@ -285,16 +273,10 @@ func setCmd(_ *config.Config) *cobra.Command { }, } sCmd.Flags().StringP("attribute", "a", "", "attribute to inspect, can be a glob pattern (e.g. 'user.*' will match all attributes starting with 'user.').") - err := sCmd.MarkFlagRequired("attribute") - if err != nil { - fmt.Println(err) - } + _ = sCmd.MarkFlagRequired("attribute") sCmd.Flags().StringP("value", "v", "", "value to set") - err = sCmd.MarkFlagRequired("value") - if err != nil { - fmt.Println(err) - } + _ = sCmd.MarkFlagRequired("value") return sCmd } diff --git a/opencloud/pkg/command/revisions.go b/opencloud/pkg/command/revisions.go index f8c928c499..212388759a 100644 --- a/opencloud/pkg/command/revisions.go +++ b/opencloud/pkg/command/revisions.go @@ -46,11 +46,6 @@ func PurgeRevisionsCommand(cfg *config.Config) *cobra.Command { Short: "purge revisions", RunE: func(cmd *cobra.Command, args []string) error { basePath, _ := cmd.Flags().GetString("basepath") - if basePath == "" { - fmt.Println("basepath is required") - _ = cmd.Help() - return nil - } var ( bs revisions.DelBlobstore @@ -128,6 +123,7 @@ func PurgeRevisionsCommand(cfg *config.Config) *cobra.Command { }, } revCmd.Flags().StringP("basepath", "p", "", "the basepath of the decomposedfs (e.g. /var/tmp/opencloud/storage/metadata)") + _ = revCmd.MarkFlagRequired("basepath") revCmd.Flags().StringP("blobstore", "b", "decomposed", "the blobstore type. Can be (none, decomposed, decomposeds3). Default decomposed") revCmd.Flags().Bool("dry-run", true, "do not delete anything, just print what would be deleted") revCmd.Flags().BoolP("verbose", "v", false, "print verbose output") diff --git a/opencloud/pkg/command/shares.go b/opencloud/pkg/command/shares.go index 0bb3c9b2a6..f9cd84180d 100644 --- a/opencloud/pkg/command/shares.go +++ b/opencloud/pkg/command/shares.go @@ -2,7 +2,6 @@ package command import ( "errors" - "fmt" "github.com/opencloud-eu/opencloud/opencloud/pkg/register" "github.com/opencloud-eu/opencloud/pkg/config" @@ -66,24 +65,14 @@ func cleanupCmd(cfg *config.Config) *cobra.Command { }, } cleanCmd.Flags().String("service-account-id", "", "Name of the service account to use for the cleanup") - err := viper.BindEnv("service-account-id", "OC_SERVICE_ACCOUNT_ID") - if err != nil { - fmt.Printf("Could not bind environment variable OC_SERVICE_ACCOUNT_ID: %s", err) - } - err = viper.BindPFlag("service-account-id", cleanCmd.Flags().Lookup("service-account-id")) - if err != nil { - fmt.Printf("Could not bind flag OC_SERVICE_ACCOUNT_ID: %s", err) - } + _ = cleanCmd.MarkFlagRequired("service-account-id") + _ = viper.BindEnv("service-account-id", "OC_SERVICE_ACCOUNT_ID") + _ = viper.BindPFlag("service-account-id", cleanCmd.Flags().Lookup("service-account-id")) cleanCmd.Flags().String("service-account-secret", "", "Secret for the service account") - err = viper.BindEnv("service-account-secret", "OC_SERVICE_ACCOUNT_SECRET") - if err != nil { - fmt.Printf("Could not bind environment variable OC_SERVICE_ACCOUNT_SECRET: %s", err) - } - err = viper.BindPFlag("service-account-secret", cleanCmd.Flags().Lookup("service-account-secret")) - if err != nil { - fmt.Printf("Could not bind flag OC_SERVICE_ACCOUNT_SECRET: %s", err) - } + _ = cleanCmd.MarkFlagRequired("service-account-secret") + _ = viper.BindEnv("service-account-secret", "OC_SERVICE_ACCOUNT_SECRET") + _ = viper.BindPFlag("service-account-secret", cleanCmd.Flags().Lookup("service-account-secret")) return cleanCmd } diff --git a/opencloud/pkg/command/trash.go b/opencloud/pkg/command/trash.go index 06210c012f..18b0979bd5 100644 --- a/opencloud/pkg/command/trash.go +++ b/opencloud/pkg/command/trash.go @@ -35,12 +35,6 @@ func TrashPurgeEmptyDirsCommand(cfg *config.Config) *cobra.Command { Short: "purge empty directories", RunE: func(cmd *cobra.Command, args []string) error { basePath, _ := cmd.Flags().GetString("basepath") - if basePath == "" { - fmt.Println("basepath is required") - _ = cmd.Help() - return nil - } - dryRun, _ := cmd.Flags().GetBool("dry-run") if err := trash.PurgeTrashEmptyPaths(basePath, dryRun); err != nil { fmt.Println(err) @@ -51,10 +45,7 @@ func TrashPurgeEmptyDirsCommand(cfg *config.Config) *cobra.Command { }, } trashPurgeCmd.Flags().StringP("basepath", "p", "", "the basepath of the decomposedfs (e.g. /var/tmp/opencloud/storage/users)") - err := trashPurgeCmd.MarkFlagRequired("basepath") - if err != nil { - fmt.Println(err) - } + _ = trashPurgeCmd.MarkFlagRequired("basepath") trashPurgeCmd.Flags().Bool("dry-run", true, "do not delete anything, just print what would be deleted") From 3a2a38870cf7d7088e3b08bc9ed75b97db4e8144 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rn=20Friedrich=20Dreyer?= Date: Fri, 12 Dec 2025 16:09:58 +0100 Subject: [PATCH 80/82] ignore some viper errors MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Jörn Friedrich Dreyer --- opencloud/pkg/command/init.go | 50 +++++++---------------------------- opencloud/pkg/command/list.go | 8 +++--- 2 files changed, 14 insertions(+), 44 deletions(-) diff --git a/opencloud/pkg/command/init.go b/opencloud/pkg/command/init.go index 57dea6b5ae..42b72f736e 100644 --- a/opencloud/pkg/command/init.go +++ b/opencloud/pkg/command/init.go @@ -45,54 +45,24 @@ func InitCommand(_ *config.Config) *cobra.Command { }, } initCmd.Flags().String("insecure", "ask", "Allow insecure OpenCloud config") - err := viper.BindEnv("insecure", "OC_INSECURE") - if err != nil { - log.Fatalf("Could not bind environment variable OC_INSECURE: %s", err) - } - err = viper.BindPFlag("insecure", initCmd.Flags().Lookup("insecure")) - if err != nil { - log.Fatalf("Could not bind flag OC_INSECURE: %s", err) - } + _ = viper.BindEnv("insecure", "OC_INSECURE") + _ = viper.BindPFlag("insecure", initCmd.Flags().Lookup("insecure")) initCmd.Flags().BoolP("diff", "d", false, "Show the difference between the current config and the new one") initCmd.Flags().BoolP("force-overwrite", "f", false, "Force overwrite existing config file") - err = viper.BindEnv("force-overwrite", "OC_FORCE_CONFIG_OVERWRITE") - if err != nil { - log.Fatalf("Could not bind environment variable OC_FORCE_CONFIG_OVERWRITE: %s", err) - } - err = viper.BindPFlag("force-overwrite", initCmd.Flags().Lookup("force-overwrite")) - if err != nil { - log.Fatalf("Could not bind flag OC_FORCE_CONFIG_OVERWRITE: %s", err) - } + _ = viper.BindEnv("force-overwrite", "OC_FORCE_CONFIG_OVERWRITE") + _ = viper.BindPFlag("force-overwrite", initCmd.Flags().Lookup("force-overwrite")) initCmd.Flags().String("config-path", defaults.BaseConfigPath(), "Config path for the OpenCloud runtime") - err = viper.BindEnv("config-path", "OC_CONFIG_DIR") - if err != nil { - log.Fatalf("Could not bind environment variable OC_CONFIG_DIR: %s", err) - } - err = viper.BindEnv("config-path", "OC_BASE_DATA_PATH") - if err != nil { - log.Fatalf("Could not bind environment variable OC_BASE_DATA_PATH: %s", err) - } - err = viper.BindPFlag("config-path", initCmd.Flags().Lookup("config-path")) - if err != nil { - log.Fatalf("Could not bind flag OC_BASE_DATA_PATH: %s", err) - } + _ = viper.BindEnv("config-path", "OC_CONFIG_DIR") + _ = viper.BindEnv("config-path", "OC_BASE_DATA_PATH") + _ = viper.BindPFlag("config-path", initCmd.Flags().Lookup("config-path")) initCmd.Flags().String("admin-password", "", "Set admin password instead of using a random generated one") - err = viper.BindEnv("admin-password", "ADMIN_PASSWORD") - if err != nil { - log.Fatalf("Could not bind environment variable ADMIN_PASSWORD: %s", err) - } - err = viper.BindEnv("admin-password", "IDM_ADMIN_PASSWORD") - if err != nil { - log.Fatalf("Could not bind environment variable IDM_ADMIN_PASSWORD: %s", err) - } - err = viper.BindPFlag("admin-password", initCmd.Flags().Lookup("admin-password")) - if err != nil { - log.Fatalf("Could not bind flag IDM_ADMIN_PASSWORD: %s", err) - } + _ = viper.BindEnv("admin-password", "ADMIN_PASSWORD") + _ = viper.BindEnv("admin-password", "IDM_ADMIN_PASSWORD") + _ = viper.BindPFlag("admin-password", initCmd.Flags().Lookup("admin-password")) return initCmd } diff --git a/opencloud/pkg/command/list.go b/opencloud/pkg/command/list.go index e0d94d2ca6..94557be565 100644 --- a/opencloud/pkg/command/list.go +++ b/opencloud/pkg/command/list.go @@ -36,12 +36,12 @@ func ListCommand(cfg *config.Config) *cobra.Command { }, } listCmd.Flags().String("hostname", "localhost", "hostname of the runtime") - viper.BindEnv("hostname", "OC_RUNTIME_HOST") - viper.BindPFlag("hostname", listCmd.Flags().Lookup("hostname")) + _ = viper.BindEnv("hostname", "OC_RUNTIME_HOST") + _ = viper.BindPFlag("hostname", listCmd.Flags().Lookup("hostname")) listCmd.Flags().String("port", "9250", "port of the runtime") - viper.BindEnv("port", "OC_RUNTIME_PORT") - viper.BindPFlag("port", listCmd.Flags().Lookup("port")) + _ = viper.BindEnv("port", "OC_RUNTIME_PORT") + _ = viper.BindPFlag("port", listCmd.Flags().Lookup("port")) return listCmd } From 9dd03b6c904cfa32cf732ad7dafcc00c0bc52cce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rn=20Friedrich=20Dreyer?= Date: Fri, 12 Dec 2025 16:22:18 +0100 Subject: [PATCH 81/82] small fixes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Jörn Friedrich Dreyer --- services/auth-app/pkg/command/create.go | 4 ++-- services/storage-users/pkg/command/trash_bin.go | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/services/auth-app/pkg/command/create.go b/services/auth-app/pkg/command/create.go index fc889cfd74..115f1600d2 100644 --- a/services/auth-app/pkg/command/create.go +++ b/services/auth-app/pkg/command/create.go @@ -114,9 +114,9 @@ func Create(cfg *config.Config) *cobra.Command { "", "user to create the app-token for", ) - createCmd.Flags().String( + createCmd.Flags().Duration( "expiration", - "72h", + time.Hour*72, "expiration of the app password, e.g. 72h, 1h, 1m, 1s. Default is 72h.", ) diff --git a/services/storage-users/pkg/command/trash_bin.go b/services/storage-users/pkg/command/trash_bin.go index 50def1ab52..8c3b59f7cc 100644 --- a/services/storage-users/pkg/command/trash_bin.go +++ b/services/storage-users/pkg/command/trash_bin.go @@ -191,8 +191,8 @@ func restoreAllTrashBinItems(cfg *config.Config) *cobra.Command { if err != nil { return err } - assumeYes, _ := cmd.Flags().GetBool("yes") - if !assumeYes { + applyYesFlag, _ := cmd.Flags().GetBool("yes") + if !applyYesFlag { for { fmt.Printf("Found %d items that could be restored, continue (Y/n), show the items list (s): ", len(res.GetRecycleItems())) var i string From f465a2bfa978cf01f64b7bfe16db7424a76949e1 Mon Sep 17 00:00:00 2001 From: Florian Schade Date: Mon, 15 Dec 2025 16:44:18 +0100 Subject: [PATCH 82/82] fix: flaky integration tests --- tests/acceptance/features/apiAntivirus/antivirus.feature | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/acceptance/features/apiAntivirus/antivirus.feature b/tests/acceptance/features/apiAntivirus/antivirus.feature index c2113899fb..133b8621c7 100644 --- a/tests/acceptance/features/apiAntivirus/antivirus.feature +++ b/tests/acceptance/features/apiAntivirus/antivirus.feature @@ -191,6 +191,7 @@ Feature: antivirus And user "Brian" should get a notification with subject "Virus found" and message: | message | | | + When the user waits for "10" seconds for postprocessing to finish And as "Brian" file "/Shares/uploadFolder/" should not exist And as "Alice" file "/uploadFolder/" should not exist Examples: