feat #43: add CLI commands to manage backends
All checks were successful
ci/woodpecker/push/test Pipeline was successful
ci/woodpecker/push/deploy Pipeline was successful

This commit is contained in:
Melora Hugues 2023-10-28 16:51:00 +02:00
parent fadef50bc2
commit 74cb316d78
8 changed files with 357 additions and 1 deletions

View file

@ -0,0 +1,40 @@
/*
Copyright © 2023 NAME HERE <EMAIL ADDRESS>
*/
package cmd
import (
"fmt"
"github.com/spf13/cobra"
)
// backendCmd represents the backend command
var backendCmd = &cobra.Command{
Use: "backend",
Short: "A brief description of your command",
Long: `A longer description that spans multiple lines and likely contains examples
and usage of using your command. For example:
Cobra is a CLI library for Go that empowers applications.
This application is a tool to generate the needed files
to quickly create a Cobra application.`,
Run: func(cmd *cobra.Command, args []string) {
fmt.Println("backend called")
},
}
func init() {
rootCmd.AddCommand(backendCmd)
// Here you will define your flags and configuration settings.
// Cobra supports Persistent Flags which will work for this command
// and all subcommands, e.g.:
// backendCmd.PersistentFlags().String("foo", "", "A help for foo")
// Cobra supports local flags which will only run when this command
// is called directly, e.g.:
// backendCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
}

View file

@ -0,0 +1,50 @@
/*
Copyright © 2023 NAME HERE <EMAIL ADDRESS>
*/
package cmd
import (
"fmt"
"github.com/spf13/cobra"
)
var (
backendID string
backendName string
backendIssuer string
)
// backendAddCmd represents the add command
var backendAddCmd = &cobra.Command{
Use: "add",
Short: "Add a new backend to the storage",
Long: `Add a new backend to the storage.
Parameters to provide:
- id: Unique ID to represent the backend in the storage
- name: Human readable name to represent the backend. It will be used by
the user in the authentication page to select a backend during
authentication
- issuer: Full hostname of the OIDC provider, e.g. 'https://github.com'`,
Run: func(cmd *cobra.Command, args []string) {
fmt.Println("add called")
},
}
func init() {
backendCmd.AddCommand(backendAddCmd)
// Here you will define your flags and configuration settings.
// Cobra supports Persistent Flags which will work for this command
// and all subcommands, e.g.:
// backendAddCmd.PersistentFlags().String("foo", "", "A help for foo")
// Cobra supports local flags which will only run when this command
// is called directly, e.g.:
// backendAddCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
backendAddCmd.Flags().StringVarP(&backendID, "id", "i", "", "ID to identify the backend in the storage")
backendAddCmd.Flags().StringVarP(&backendName, "name", "n", "", "Name to represent the backend")
backendAddCmd.Flags().StringVarP(&backendIssuer, "issuer", "d", "", "Full hostname of the backend")
}

View file

@ -0,0 +1,39 @@
/*
Copyright © 2023 NAME HERE <EMAIL ADDRESS>
*/
package cmd
import (
"fmt"
"github.com/spf13/cobra"
)
// backendRemoveCmd represents the remove command
var backendRemoveCmd = &cobra.Command{
Use: "remove",
Short: "A brief description of your command",
Long: `A longer description that spans multiple lines and likely contains examples
and usage of using your command. For example:
Cobra is a CLI library for Go that empowers applications.
This application is a tool to generate the needed files
to quickly create a Cobra application.`,
Run: func(cmd *cobra.Command, args []string) {
fmt.Println("remove called")
},
}
func init() {
backendCmd.AddCommand(backendRemoveCmd)
// Here you will define your flags and configuration settings.
// Cobra supports Persistent Flags which will work for this command
// and all subcommands, e.g.:
// backendRemoveCmd.PersistentFlags().String("foo", "", "A help for foo")
// Cobra supports local flags which will only run when this command
// is called directly, e.g.:
// backendRemoveCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
}

View file

@ -0,0 +1,97 @@
/*
Copyright © 2023 NAME HERE <EMAIL ADDRESS>
*/
package cmd
import (
"errors"
"fmt"
"os"
"git.faercol.me/faercol/polyculeconnect/polyculeconnect/config"
"git.faercol.me/faercol/polyculeconnect/polyculeconnect/services"
"git.faercol.me/faercol/polyculeconnect/polyculeconnect/services/backend"
"github.com/dexidp/dex/storage"
"github.com/spf13/cobra"
)
// backendShowCmd represents the show command for backends
var backendShowCmd = &cobra.Command{
Use: "show [backend_id]",
Short: "Display installed backends",
Long: `Display the configuration for the backends.
Pass the commands without arguments to display the list of currently installed backends
Pass the optional 'id' argument to display the configuration for this specific backend`,
Args: cobra.MaximumNArgs(1),
Run: func(cmd *cobra.Command, args []string) {
conf, err := config.New(configPath)
if err != nil {
panic(err)
}
s, err := services.InitStorage(conf)
if err != nil {
panic(err)
}
if len(args) > 0 {
showBackend(args[0], backend.New(s))
} else {
listBackends(backend.New(s))
}
},
}
func printProperty(key, value string) {
fmt.Printf("\t- %s: %s\n", key, value)
}
func showBackend(backendId string, backendService backend.Service) {
backendConfig, err := backendService.GetBackend(backendId)
if err != nil {
if errors.Is(err, storage.ErrNotFound) {
fmt.Fprintf(os.Stderr, "Backend with ID %s does not exist\n", backendId)
} else {
fmt.Fprintf(os.Stderr, "Failed to get config for backend %s: %q\n", backendId, err.Error())
}
return
}
fmt.Println("Backend config:")
printProperty("ID", backendConfig.Storage.ID)
printProperty("Name", backendConfig.Storage.Name)
printProperty("Issuer", backendConfig.OIDC.Issuer)
printProperty("Client ID", backendConfig.OIDC.ClientID)
printProperty("Client secret", backendConfig.OIDC.ClientSecret)
}
func listBackends(backendService backend.Service) {
backends, err := backendService.ListBackends()
if err != nil {
fmt.Fprintf(os.Stderr, "Failed to list backends: %q\n", err.Error())
return
}
if len(backends) == 0 {
fmt.Println("No backend configured")
} else {
for _, b := range backends {
fmt.Printf("\t - backend %s: id %s issuer %s\n", b.Storage.Name, b.Storage.ID, b.OIDC.Issuer)
}
}
}
func init() {
backendCmd.AddCommand(backendShowCmd)
// Here you will define your flags and configuration settings.
// Cobra supports Persistent Flags which will work for this command
// and all subcommands, e.g.:
// backendShowCmd.PersistentFlags().String("foo", "", "A help for foo")
// Cobra supports local flags which will only run when this command
// is called directly, e.g.:
// backendShowCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
}

View file

@ -64,7 +64,7 @@ func serve() {
logger.L.Info("Initializing authentication backends") logger.L.Info("Initializing authentication backends")
dex_server.ConnectorsConfig["refuseAll"] = func() dex_server.ConnectorConfig { return new(connector.RefuseAllConfig) } dex_server.ConnectorsConfig[connector.TypeRefuseAll] = func() dex_server.ConnectorConfig { return new(connector.RefuseAllConfig) }
connectors, err := dexConf.Storage.ListConnectors() connectors, err := dexConf.Storage.ListConnectors()
if err != nil { if err != nil {
logger.L.Fatalf("Failed to get existing connectors: %s", err.Error()) logger.L.Fatalf("Failed to get existing connectors: %s", err.Error())

View file

@ -8,6 +8,8 @@ import (
"github.com/dexidp/dex/pkg/log" "github.com/dexidp/dex/pkg/log"
) )
const TypeRefuseAll = "refuseAll"
type RefuseAllConfig struct{} type RefuseAllConfig struct{}
func (c *RefuseAllConfig) Open(id string, logger log.Logger) (connector.Connector, error) { func (c *RefuseAllConfig) Open(id string, logger log.Logger) (connector.Connector, error) {

View file

@ -0,0 +1,86 @@
package backend
import (
"encoding/json"
"errors"
"fmt"
"git.faercol.me/faercol/polyculeconnect/polyculeconnect/connector"
"github.com/dexidp/dex/connector/oidc"
"github.com/dexidp/dex/storage"
)
var ErrUnsupportedType = errors.New("unsupported connector type")
type BackendConfig struct {
OIDC oidc.Config
Storage storage.Connector
}
func (bc *BackendConfig) FromConnector(connector storage.Connector) error {
if connector.Type != "oidc" {
return ErrUnsupportedType
}
if err := json.Unmarshal(connector.Config, &bc.OIDC); err != nil {
return fmt.Errorf("invalid OIDC config: %w", err)
}
bc.Storage = connector
return nil
}
type Service interface {
ListBackends() ([]BackendConfig, error)
GetBackend(id string) (BackendConfig, error)
AddBackend(config BackendConfig) error
RemoveBackend(id string) error
}
type concreteBackendService struct {
s storage.Storage
}
func (cbs *concreteBackendService) ListBackends() ([]BackendConfig, error) {
connectors, err := cbs.s.ListConnectors()
if err != nil {
return nil, fmt.Errorf("failed to get connectors from storage: %w", err)
}
var res []BackendConfig
for _, c := range connectors {
// We know that this type is special, we don't want to use it at all here
if c.Type == connector.TypeRefuseAll {
continue
}
var b BackendConfig
if err := b.FromConnector(c); err != nil {
return res, err
}
res = append(res, b)
}
return res, nil
}
func (cbs *concreteBackendService) GetBackend(connectorID string) (BackendConfig, error) {
c, err := cbs.s.GetConnector(connectorID)
if err != nil {
return BackendConfig{}, fmt.Errorf("failed to get connector from storage: %w", err)
}
var res BackendConfig
if err := res.FromConnector(c); err != nil {
return BackendConfig{}, err
}
return res, nil
}
func (cbs *concreteBackendService) AddBackend(config BackendConfig) error {
return cbs.s.CreateConnector(config.Storage)
}
func (cbs *concreteBackendService) RemoveBackend(connectorID string) error {
return cbs.s.DeleteConnector(connectorID)
}
func New(s storage.Storage) Service {
return &concreteBackendService{s}
}

View file

@ -0,0 +1,42 @@
package test
import "github.com/dexidp/dex/storage"
type MockBackendService struct {
Connectors map[string]storage.Connector
ListError error
GetError error
AddError error
RemoveError error
}
func (mbs *MockBackendService) ListConnectors() ([]storage.Connector, error) {
var res []storage.Connector
for _, c := range mbs.Connectors {
res = append(res, c)
}
return res, mbs.ListError
}
func (mbs *MockBackendService) GetConnector(connectorID string) (storage.Connector, error) {
if res, ok := mbs.Connectors[connectorID]; ok {
return res, mbs.GetError
}
return storage.Connector{}, storage.ErrNotFound
}
func (mbs *MockBackendService) AddConnector(config storage.Connector) error {
if _, ok := mbs.Connectors[config.ID]; ok {
return storage.ErrAlreadyExists
}
if mbs.AddError != nil {
return mbs.AddError
}
mbs.Connectors[config.ID] = config
return nil
}
func (mbs *MockBackendService) RemoveConnector(connectorID string) error {
delete(mbs.Connectors, connectorID)
return mbs.RemoveError
}