diff --git a/cmd/cmd/client.go b/cmd/cmd/client.go new file mode 100644 index 0000000..dd0ec35 --- /dev/null +++ b/cmd/cmd/client.go @@ -0,0 +1,48 @@ +package cmd + +import ( + "context" + "fmt" + + "gitea.libretechconsulting.com/rmcguire/ubiquiti-clients/cmd/internal/config" + "gitea.libretechconsulting.com/rmcguire/ubiquiti-clients/cmd/internal/util" +) + +// fetchDevice retrieves device data using the pre-configured clients from context. +// It handles both ToughSwitch and EdgeOS device types. +func fetchDevice(ctx context.Context, clientConf *config.ClientConfig) (any, error) { + switch clientConf.Type { + case config.TypeToughSwitch: + return fetchToughSwitchDevice(ctx, clientConf) + case config.TypeEdgeOS: + return fetchEdgeOSDevice(ctx, clientConf) + default: + return nil, fmt.Errorf("unknown device type: %s", clientConf.Type) + } +} + +func fetchToughSwitchDevice(ctx context.Context, clientConf *config.ClientConfig) (any, error) { + client := util.ToughSwitchClientFromContext(ctx) + if client == nil { + return nil, fmt.Errorf("toughswitch client not initialized") + } + + if err := client.Login(ctx, clientConf.Host); err != nil { + return nil, fmt.Errorf("login failed: %w", err) + } + + return client.GetDevice(ctx, clientConf.Host) +} + +func fetchEdgeOSDevice(ctx context.Context, clientConf *config.ClientConfig) (any, error) { + client := util.EdgeOSClientFromContext(ctx) + if client == nil { + return nil, fmt.Errorf("edgeos client not initialized") + } + + if err := client.Login(ctx, clientConf.Host); err != nil { + return nil, fmt.Errorf("login failed: %w", err) + } + + return client.GetConfig(ctx, clientConf.Host) +} diff --git a/cmd/cmd/get_device.go b/cmd/cmd/get_device.go index 930cd59..bebb29c 100644 --- a/cmd/cmd/get_device.go +++ b/cmd/cmd/get_device.go @@ -1,14 +1,10 @@ package cmd import ( - "context" "fmt" "os" - "gitea.libretechconsulting.com/rmcguire/ubiquiti-clients/cmd/internal/config" "gitea.libretechconsulting.com/rmcguire/ubiquiti-clients/cmd/internal/util" - "gitea.libretechconsulting.com/rmcguire/ubiquiti-clients/pkg/edgeos" - "gitea.libretechconsulting.com/rmcguire/ubiquiti-clients/pkg/toughswitch" "github.com/spf13/cobra" ) @@ -54,49 +50,10 @@ func runGetDevice(cmd *cobra.Command, args []string) error { logger.Debug().Str("device", deviceName).Str("type", string(clientConf.Type)).Msg("fetching device info") - var result any - var err error - - switch clientConf.Type { - case config.TypeToughSwitch: - result, err = getToughSwitchDevice(ctx, clientConf) - case config.TypeEdgeOS: - result, err = getEdgeOSDevice(ctx, clientConf) - default: - return fmt.Errorf("unknown device type: %s", clientConf.Type) - } - + result, err := fetchDevice(ctx, clientConf) if err != nil { return fmt.Errorf("failed to get device info: %w", err) } return util.YAMLOutput(os.Stdout, result, pretty, colorize) } - -func getToughSwitchDevice(ctx context.Context, clientConf *config.ClientConfig) (any, error) { - cfg := toughswitch.Config{ - Host: clientConf.Host, - Scheme: clientConf.Scheme, - Insecure: clientConf.Insecure, - Username: clientConf.User, - Password: clientConf.Pass, - Timeout: clientConf.Timeout, - } - - client := toughswitch.MustNew(ctx, []toughswitch.Config{cfg}) - return client.GetDevice(ctx, clientConf.Host) -} - -func getEdgeOSDevice(ctx context.Context, clientConf *config.ClientConfig) (any, error) { - cfg := edgeos.Config{ - Host: clientConf.Host, - Scheme: clientConf.Scheme, - Insecure: clientConf.Insecure, - Username: clientConf.User, - Password: clientConf.Pass, - Timeout: clientConf.Timeout, - } - - client := edgeos.MustNew(ctx, []edgeos.Config{cfg}) - return client.GetConfig(ctx, clientConf.Host) -} diff --git a/cmd/cmd/get_devices.go b/cmd/cmd/get_devices.go index eef935d..5aa5bed 100644 --- a/cmd/cmd/get_devices.go +++ b/cmd/cmd/get_devices.go @@ -1,15 +1,12 @@ package cmd import ( - "context" "fmt" "os" "sync" "gitea.libretechconsulting.com/rmcguire/ubiquiti-clients/cmd/internal/config" "gitea.libretechconsulting.com/rmcguire/ubiquiti-clients/cmd/internal/util" - "gitea.libretechconsulting.com/rmcguire/ubiquiti-clients/pkg/edgeos" - "gitea.libretechconsulting.com/rmcguire/ubiquiti-clients/pkg/toughswitch" "github.com/spf13/cobra" ) @@ -63,18 +60,7 @@ func runGetDevices(cmd *cobra.Command, args []string) error { Host: cc.Host, } - var data any - var err error - - switch cc.Type { - case config.TypeToughSwitch: - data, err = fetchToughSwitch(ctx, &cc) - case config.TypeEdgeOS: - data, err = fetchEdgeOS(ctx, &cc) - default: - err = fmt.Errorf("unknown device type: %s", cc.Type) - } - + data, err := fetchDevice(ctx, &cc) if err != nil { result.Error = err.Error() result.Failed = true @@ -93,31 +79,3 @@ func runGetDevices(cmd *cobra.Command, args []string) error { return util.YAMLOutput(os.Stdout, results, pretty, colorize) } - -func fetchToughSwitch(ctx context.Context, clientConf *config.ClientConfig) (any, error) { - cfg := toughswitch.Config{ - Host: clientConf.Host, - Scheme: clientConf.Scheme, - Insecure: clientConf.Insecure, - Username: clientConf.User, - Password: clientConf.Pass, - Timeout: clientConf.Timeout, - } - - client := toughswitch.MustNew(ctx, []toughswitch.Config{cfg}) - return client.GetDevice(ctx, clientConf.Host) -} - -func fetchEdgeOS(ctx context.Context, clientConf *config.ClientConfig) (any, error) { - cfg := edgeos.Config{ - Host: clientConf.Host, - Scheme: clientConf.Scheme, - Insecure: clientConf.Insecure, - Username: clientConf.User, - Password: clientConf.Pass, - Timeout: clientConf.Timeout, - } - - client := edgeos.MustNew(ctx, []edgeos.Config{cfg}) - return client.GetConfig(ctx, clientConf.Host) -} diff --git a/cmd/cmd/prerun.go b/cmd/cmd/prerun.go index 529496b..3cc81e1 100644 --- a/cmd/cmd/prerun.go +++ b/cmd/cmd/prerun.go @@ -8,6 +8,8 @@ import ( "gitea.libretechconsulting.com/rmcguire/ubiquiti-clients/cmd/internal/config" "gitea.libretechconsulting.com/rmcguire/ubiquiti-clients/cmd/internal/util" + "gitea.libretechconsulting.com/rmcguire/ubiquiti-clients/pkg/edgeos" + "gitea.libretechconsulting.com/rmcguire/ubiquiti-clients/pkg/toughswitch" "github.com/rs/zerolog" "github.com/spf13/cobra" ) @@ -20,6 +22,8 @@ var preRunFuncs = []preRunFunc{ validateConfigFile, prepareConfig, prepareLogger, + setEnvironment, + prepareClients, } // preRun executes all registered pre-run functions in order. @@ -32,6 +36,24 @@ func preRun(cmd *cobra.Command, args []string) error { return nil } +func setEnvironment(cmd *cobra.Command, _ []string) error { + conf := util.ConfigFromContext(cmd.Context()) + if conf == nil { + zerolog.Ctx(cmd.Context()).Fatal().Msg("unconfigured, nothing to do") + } + + for _, device := range conf.Clients { + if device.Type == config.TypeToughSwitch { + zerolog.Ctx(cmd.Context()).Debug(). + Msg("setting GODEBUG=x509negativeserial=1 for toughswitch devices") + os.Setenv("GODEBUG", "x509negativeserial=1") + return nil + } + } + + return nil +} + func prepareConfig(cmd *cobra.Command, _ []string) error { configFile, _ := cmd.Root().PersistentFlags().GetString(util.FLAG_CONFIG_FILE) conf, err := config.LoadConfig(&configFile) @@ -84,3 +106,52 @@ func validateConfigFile(cmd *cobra.Command, args []string) error { return nil } + +// prepareClients creates toughswitch and edgeos clients based on the configuration. +// Clients are only created if there are devices of that type in the config. +func prepareClients(cmd *cobra.Command, _ []string) error { + ctx := cmd.Context() + conf := util.ConfigFromContext(ctx) + logger := zerolog.Ctx(ctx) + + var tsConfigs []toughswitch.Config + var eosConfigs []edgeos.Config + + for _, clientConf := range conf.Clients { + switch clientConf.Type { + case config.TypeToughSwitch: + tsConfigs = append(tsConfigs, toughswitch.Config{ + Host: clientConf.Host, + Scheme: clientConf.Scheme, + Insecure: clientConf.Insecure, + Username: clientConf.User, + Password: clientConf.Pass, + Timeout: clientConf.Timeout, + }) + case config.TypeEdgeOS: + eosConfigs = append(eosConfigs, edgeos.Config{ + Host: clientConf.Host, + Scheme: clientConf.Scheme, + Insecure: clientConf.Insecure, + Username: clientConf.User, + Password: clientConf.Pass, + Timeout: clientConf.Timeout, + }) + } + } + + if len(tsConfigs) > 0 { + logger.Debug().Int("count", len(tsConfigs)).Msg("creating toughswitch client") + tsClient := toughswitch.MustNew(ctx, tsConfigs) + ctx = util.ContextWithToughSwitchClient(ctx, tsClient) + } + + if len(eosConfigs) > 0 { + logger.Debug().Int("count", len(eosConfigs)).Msg("creating edgeos client") + eosClient := edgeos.MustNew(ctx, eosConfigs) + ctx = util.ContextWithEdgeOSClient(ctx, eosClient) + } + + cmd.SetContext(ctx) + return nil +} diff --git a/cmd/internal/util/context.go b/cmd/internal/util/context.go index 111637e..365c67e 100644 --- a/cmd/internal/util/context.go +++ b/cmd/internal/util/context.go @@ -4,22 +4,25 @@ import ( "context" "gitea.libretechconsulting.com/rmcguire/ubiquiti-clients/cmd/internal/config" + "gitea.libretechconsulting.com/rmcguire/ubiquiti-clients/pkg/edgeos" + "gitea.libretechconsulting.com/rmcguire/ubiquiti-clients/pkg/toughswitch" "github.com/rs/zerolog" ) -type CmdContextVal uint8 +type ctxKey uint8 const ( - CTX_CONFIG CmdContextVal = iota - CTX_LOGGER + ctxConfig ctxKey = iota + ctxToughSwitchClient + ctxEdgeOSClient ) func ContextWithConfig(baseCtx context.Context, config *config.ClientsConfig) context.Context { - return context.WithValue(baseCtx, CTX_CONFIG, config) + return context.WithValue(baseCtx, ctxConfig, config) } func ConfigFromContext(ctx context.Context) *config.ClientsConfig { - val := ctx.Value(CTX_CONFIG) + val := ctx.Value(ctxConfig) conf, ok := val.(*config.ClientsConfig) if !ok { return nil @@ -28,16 +31,42 @@ func ConfigFromContext(ctx context.Context) *config.ClientsConfig { return conf } +// ContextWithLogger stores the logger in context using zerolog's built-in method. func ContextWithLogger(baseCtx context.Context, logger *zerolog.Logger) context.Context { - return context.WithValue(baseCtx, CTX_LOGGER, logger) + return logger.WithContext(baseCtx) } +// LoggerFromContext retrieves the logger from context using zerolog's built-in method. func LoggerFromContext(ctx context.Context) *zerolog.Logger { - val := ctx.Value(CTX_LOGGER) - logger, ok := val.(*zerolog.Logger) + return zerolog.Ctx(ctx) +} + +// ContextWithToughSwitchClient stores a toughswitch client in the context. +func ContextWithToughSwitchClient(baseCtx context.Context, client *toughswitch.Client) context.Context { + return context.WithValue(baseCtx, ctxToughSwitchClient, client) +} + +// ToughSwitchClientFromContext retrieves the toughswitch client from context. +func ToughSwitchClientFromContext(ctx context.Context) *toughswitch.Client { + val := ctx.Value(ctxToughSwitchClient) + client, ok := val.(*toughswitch.Client) if !ok { return nil } - - return logger + return client +} + +// ContextWithEdgeOSClient stores an edgeos client in the context. +func ContextWithEdgeOSClient(baseCtx context.Context, client *edgeos.Client) context.Context { + return context.WithValue(baseCtx, ctxEdgeOSClient, client) +} + +// EdgeOSClientFromContext retrieves the edgeos client from context. +func EdgeOSClientFromContext(ctx context.Context) *edgeos.Client { + val := ctx.Value(ctxEdgeOSClient) + client, ok := val.(*edgeos.Client) + if !ok { + return nil + } + return client } diff --git a/pkg/edgeos/client.go b/pkg/edgeos/client.go index 04e1daf..60dec99 100644 --- a/pkg/edgeos/client.go +++ b/pkg/edgeos/client.go @@ -119,6 +119,17 @@ func (c *Client) getDeviceByHost(host string) (*deviceClient, error) { return d, nil } +// Login authenticates with the device at the given host. +// This is called automatically on 401 responses, but can be called explicitly +// to pre-authenticate before making requests. +func (c *Client) Login(ctx context.Context, host string) error { + d, err := c.getDeviceByHost(host) + if err != nil { + return err + } + return d.login(ctx) +} + func (d *deviceClient) login(ctx context.Context) error { d.mu.Lock() defer d.mu.Unlock() diff --git a/pkg/toughswitch/client.go b/pkg/toughswitch/client.go index 9038dd1..e0f48e3 100644 --- a/pkg/toughswitch/client.go +++ b/pkg/toughswitch/client.go @@ -120,6 +120,17 @@ func (c *Client) getDeviceByHost(host string) (*deviceClient, error) { return d, nil } +// Login authenticates with the device at the given host. +// This is called automatically on 401 responses, but can be called explicitly +// to pre-authenticate before making requests. +func (c *Client) Login(ctx context.Context, host string) error { + d, err := c.getDeviceByHost(host) + if err != nil { + return err + } + return d.login(ctx) +} + func (d *deviceClient) login(ctx context.Context) error { d.mu.Lock() defer d.mu.Unlock()