[core] Split out cli commands

Signed-off-by: Knut Ahlers <knut@ahlers.me>
This commit is contained in:
Knut Ahlers 2023-03-24 21:41:38 +01:00
parent 120f88ecbd
commit d8c6f8d221
Signed by: luzifer
GPG key ID: D91C3E91E4CAD6F5
7 changed files with 232 additions and 88 deletions

85
cli.go Normal file
View file

@ -0,0 +1,85 @@
package main
import (
"fmt"
"os"
"sort"
"strings"
"sync"
"github.com/pkg/errors"
)
type (
cliRegistry struct {
cmds map[string]cliRegistryEntry
sync.Mutex
}
cliRegistryEntry struct {
Description string
Name string
Params []string
Run func([]string) error
}
)
var (
cli = newCLIRegistry()
errHelpCalled = errors.New("help called")
)
func newCLIRegistry() *cliRegistry {
return &cliRegistry{
cmds: make(map[string]cliRegistryEntry),
}
}
func (c *cliRegistry) Add(e cliRegistryEntry) {
c.Lock()
defer c.Unlock()
c.cmds[e.Name] = e
}
func (c *cliRegistry) Call(args []string) error {
c.Lock()
defer c.Unlock()
cmdEntry := c.cmds[args[0]]
if cmdEntry.Name != args[0] {
c.help()
return errHelpCalled
}
return cmdEntry.Run(args)
}
func (c *cliRegistry) help() {
// Called from Call, does not need lock
var (
maxCmdLen int
cmds []cliRegistryEntry
)
for name := range c.cmds {
entry := c.cmds[name]
if l := len(entry.CommandDisplay()); l > maxCmdLen {
maxCmdLen = l
}
cmds = append(cmds, entry)
}
sort.Slice(cmds, func(i, j int) bool { return cmds[i].Name < cmds[j].Name })
tpl := fmt.Sprintf(" %%-%ds %%s\n", maxCmdLen)
fmt.Fprintln(os.Stdout, "Supported sub-commands are:")
for _, cmd := range cmds {
fmt.Fprintf(os.Stdout, tpl, cmd.CommandDisplay(), cmd.Description)
}
}
func (c cliRegistryEntry) CommandDisplay() string {
return strings.Join(append([]string{c.Name}, c.Params...), " ")
}

26
cli_actorDocs.go Normal file
View file

@ -0,0 +1,26 @@
package main
import (
"bytes"
"os"
"github.com/pkg/errors"
)
func init() {
cli.Add(cliRegistryEntry{
Name: "actor-docs",
Description: "Generate markdown documentation for available actors",
Run: func(args []string) error {
doc, err := generateActorDocs()
if err != nil {
return errors.Wrap(err, "generating actor docs")
}
if _, err = os.Stdout.Write(append(bytes.TrimSpace(doc), '\n')); err != nil {
return errors.Wrap(err, "writing actor docs to stdout")
}
return nil
},
})
}

43
cli_apiToken.go Normal file
View file

@ -0,0 +1,43 @@
package main
import (
"os"
"github.com/gofrs/uuid/v3"
"github.com/pkg/errors"
log "github.com/sirupsen/logrus"
"gopkg.in/yaml.v2"
)
func init() {
cli.Add(cliRegistryEntry{
Name: "api-token",
Description: "Generate an api-token to be entered into the config",
Params: []string{"<token-name>", "<scope>", "[...scope]"},
Run: func(args []string) error {
if len(args) < 3 { //nolint:gomnd // Just a count of parameters
return errors.New("Usage: twitch-bot api-token <token name> <scope> [...scope]")
}
t := configAuthToken{
Name: args[1],
Modules: args[2:],
}
if err := fillAuthToken(&t); err != nil {
return errors.Wrap(err, "generating token")
}
log.WithField("token", t.Token).Info("Token generated, add this to your config:")
if err := yaml.NewEncoder(os.Stdout).Encode(map[string]map[string]configAuthToken{
"auth_tokens": {
uuid.Must(uuid.NewV4()).String(): t,
},
}); err != nil {
return errors.Wrap(err, "printing token info")
}
return nil
},
})
}

33
cli_migrateV2.go Normal file
View file

@ -0,0 +1,33 @@
package main
import (
"github.com/pkg/errors"
log "github.com/sirupsen/logrus"
"github.com/Luzifer/twitch-bot/v3/internal/v2migrator"
)
func init() {
cli.Add(cliRegistryEntry{
Name: "migrate-v2",
Description: "Migrate old (*.json.gz) storage file into new database",
Params: []string{"<old-file>"},
Run: func(args []string) error {
if len(args) < 2 { //nolint:gomnd // Just a count of parameters
return errors.New("Usage: twitch-bot migrate-v2 <old storage file>")
}
v2s := v2migrator.NewStorageFile()
if err := v2s.Load(args[1], cfg.StorageEncryptionPass); err != nil {
return errors.Wrap(err, "loading v2 storage file")
}
if err := v2s.Migrate(db); err != nil {
return errors.Wrap(err, "migrating v2 storage file")
}
log.Info("v2 storage file was migrated")
return nil
},
})
}

26
cli_resetSecrets.go Normal file
View file

@ -0,0 +1,26 @@
package main
import (
"github.com/pkg/errors"
log "github.com/sirupsen/logrus"
)
func init() {
cli.Add(cliRegistryEntry{
Name: "reset-secrets",
Description: "Remove encrypted data to reset encryption passphrase",
Run: func(args []string) error {
if err := accessService.RemoveAllExtendedTwitchCredentials(); err != nil {
return errors.Wrap(err, "resetting Twitch credentials")
}
log.Info("removed stored Twitch credentials")
if err := db.ResetEncryptedCoreMeta(); err != nil {
return errors.Wrap(err, "resetting encrypted meta entries")
}
log.Info("removed encrypted meta entries")
return nil
},
})
}

16
cli_validateConfig.go Normal file
View file

@ -0,0 +1,16 @@
package main
import "github.com/pkg/errors"
func init() {
cli.Add(cliRegistryEntry{
Name: "validate-config",
Description: "Try to load configuration file and report errors if any",
Run: func(args []string) error {
return errors.Wrap(
loadConfig(cfg.Config),
"loading config",
)
},
})
}

91
main.go
View file

@ -1,7 +1,6 @@
package main
import (
"bytes"
"context"
"crypto/rand"
"encoding/hex"
@ -23,14 +22,12 @@ import (
"github.com/pkg/errors"
"github.com/robfig/cron/v3"
log "github.com/sirupsen/logrus"
"gopkg.in/yaml.v2"
"github.com/Luzifer/go_helpers/v2/backoff"
"github.com/Luzifer/go_helpers/v2/str"
"github.com/Luzifer/rconfig/v2"
"github.com/Luzifer/twitch-bot/v3/internal/service/access"
"github.com/Luzifer/twitch-bot/v3/internal/service/timer"
"github.com/Luzifer/twitch-bot/v3/internal/v2migrator"
"github.com/Luzifer/twitch-bot/v3/pkg/database"
"github.com/Luzifer/twitch-bot/v3/pkg/twitch"
)
@ -158,90 +155,6 @@ func getEventSubSecret() (secret, handle string, err error) {
return eventSubSecret, eventSubSecret[:5], errors.Wrap(db.StoreEncryptedCoreMeta(coreMetaKeyEventSubSecret, eventSubSecret), "storing secret to database")
}
func handleSubCommand(args []string) {
switch args[0] {
case "actor-docs":
doc, err := generateActorDocs()
if err != nil {
log.WithError(err).Fatal("Unable to generate actor docs")
}
if _, err = os.Stdout.Write(append(bytes.TrimSpace(doc), '\n')); err != nil {
log.WithError(err).Fatal("Unable to write actor docs to stdout")
}
case "api-token":
if len(args) < 3 { //nolint:gomnd // Just a count of parameters
log.Fatalf("Usage: twitch-bot api-token <token name> <scope> [...scope]")
}
t := configAuthToken{
Name: args[1],
Modules: args[2:],
}
if err := fillAuthToken(&t); err != nil {
log.WithError(err).Fatal("Unable to generate token")
}
log.WithField("token", t.Token).Info("Token generated, add this to your config:")
if err := yaml.NewEncoder(os.Stdout).Encode(map[string]map[string]configAuthToken{
"auth_tokens": {
uuid.Must(uuid.NewV4()).String(): t,
},
}); err != nil {
log.WithError(err).Fatal("Unable to output token info")
}
case "help":
fmt.Println("Supported sub-commands are:")
fmt.Println(" actor-docs Generate markdown documentation for available actors")
fmt.Println(" api-token <name> <scope...> Generate an api-token to be entered into the config")
fmt.Println(" migrate-v2 <old file> Migrate old (*.json.gz) storage file into new database")
fmt.Println(" reset-secrets Remove encrypted data to reset encryption passphrase")
fmt.Println(" validate-config Try to load configuration file and report errors if any")
fmt.Println(" help Prints this help message")
case "migrate-v2":
if len(args) < 2 { //nolint:gomnd // Just a count of parameters
log.Fatalf("Usage: twitch-bot migrate-v2 <old storage file>")
}
v2s := v2migrator.NewStorageFile()
if err := v2s.Load(args[1], cfg.StorageEncryptionPass); err != nil {
log.WithError(err).Fatal("loading v2 storage file")
}
if err := v2s.Migrate(db); err != nil {
log.WithError(err).Fatal("migrating v2 storage file")
}
log.Info("v2 storage file was migrated")
case "reset-secrets":
// Nuke permission table entries
if err := accessService.RemoveAllExtendedTwitchCredentials(); err != nil {
log.WithError(err).Fatal("resetting Twitch credentials")
}
log.Info("removed stored Twitch credentials")
if err := db.ResetEncryptedCoreMeta(); err != nil {
log.WithError(err).Fatal("resetting encrypted meta entries")
}
log.Info("removed encrypted meta entries")
case "validate-config":
if err := loadConfig(cfg.Config); err != nil {
log.WithError(err).Fatal("loading config")
}
default:
handleSubCommand([]string{"help"})
log.Fatalf("Unknown sub-command %q", args[0])
}
}
//nolint:funlen,gocognit,gocyclo // Complexity is a little too high but makes no sense to split
func main() {
var err error
@ -315,7 +228,9 @@ func main() {
}
if len(rconfig.Args()) > 1 {
handleSubCommand(rconfig.Args()[1:])
if err = cli.Call(rconfig.Args()[1:]); err != nil {
log.Fatalf("error in command: %s", err)
}
return
}