153 lines
5.1 KiB
Go
153 lines
5.1 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"flag"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"time"
|
|
|
|
"git.yuntuo.hk/k2safe/orbit/internal/config"
|
|
"git.yuntuo.hk/k2safe/orbit/internal/storage/mysql"
|
|
core "git.yuntuo.hk/k2safe/orbit/model"
|
|
)
|
|
|
|
func main() {
|
|
if err := run(); err != nil {
|
|
fmt.Fprintln(os.Stderr, err)
|
|
os.Exit(1)
|
|
}
|
|
}
|
|
func run() error {
|
|
if len(os.Args) < 2 {
|
|
return errors.New("usage: orbit-admin bootstrap|verify|credential status|rotate|revoke|apply")
|
|
}
|
|
switch os.Args[1] {
|
|
case "bootstrap":
|
|
return runBootstrap(os.Args[2:])
|
|
case "verify":
|
|
return runVerify(os.Args[2:])
|
|
case "credential":
|
|
return runCredential(os.Args[2:])
|
|
}
|
|
return errors.New("usage: orbit-admin bootstrap|verify|credential status|rotate|revoke|apply")
|
|
}
|
|
|
|
func runVerify(args []string) error {
|
|
flags := flag.NewFlagSet("verify", flag.ContinueOnError)
|
|
if err := flags.Parse(args); err != nil {
|
|
return err
|
|
}
|
|
if flags.NArg() != 0 {
|
|
return errors.New("verify does not accept positional arguments")
|
|
}
|
|
cfg, err := config.Load()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
|
|
defer cancel()
|
|
db, err := mysql.Open(ctx, cfg.MySQLDSN, 2, 1)
|
|
if err != nil {
|
|
return errors.New("database unavailable")
|
|
}
|
|
defer db.Close()
|
|
verification, err := mysql.VerifyOperationalSchema(ctx, db)
|
|
encoded, encodeErr := json.Marshal(verification)
|
|
if encodeErr != nil {
|
|
return encodeErr
|
|
}
|
|
fmt.Println(string(encoded))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func runBootstrap(args []string) error {
|
|
flags := flag.NewFlagSet("bootstrap", flag.ContinueOnError)
|
|
var in mysql.BootstrapInput
|
|
var output string
|
|
flags.StringVar(&in.Application, "application", "", "new application name")
|
|
flags.Func("application-id", "existing application id; mutually exclusive with --application", func(v string) error {
|
|
in.ExistingApplicationID = core.ID(v)
|
|
return in.ExistingApplicationID.Validate()
|
|
})
|
|
flags.BoolVar(&in.ResourceAccess, "resource-access", false, "grant application-wide resource read and manage actions")
|
|
flags.StringVar(&in.Namespace, "namespace", "", "new namespace name")
|
|
flags.StringVar(&in.Environment, "environment", "", "environment name")
|
|
flags.StringVar(&in.Queue, "queue", "", "initial queue name")
|
|
flags.StringVar(&output, "output", "", "new private credentials JSON file")
|
|
flags.BoolVar(&in.WorkerAccess, "worker-access", false, "also grant Worker connect and consume actions for development")
|
|
flags.BoolVar(&in.ScheduleAccess, "schedule-access", false, "also grant schedule read and manage actions for development")
|
|
flags.BoolVar(&in.EventAccess, "event-access", false, "also grant result subscription read, manage and consume actions")
|
|
flags.BoolVar(&in.RecoveryAccess, "recovery-access", false, "explicitly grant task review, manual retry and operation audit read actions")
|
|
flags.BoolVar(&in.WorkflowAccess, "workflow-access", false, "explicitly grant workflow definition read and publish actions")
|
|
flags.BoolVar(&in.WorkflowRunAccess, "workflow-run-access", false, "explicitly grant workflow start and read actions")
|
|
flags.BoolVar(&in.WorkflowControlAccess, "workflow-control-access", false, "explicitly grant workflow control and read actions")
|
|
flags.BoolVar(&in.WorkflowSignalAccess, "workflow-signal-access", false, "explicitly grant workflow signal and read actions")
|
|
flags.BoolVar(&in.WorkflowRecoveryAccess, "workflow-recovery-access", false, "explicitly grant workflow recovery and read actions")
|
|
flags.BoolVar(&in.WorkflowAuditAccess, "workflow-audit-access", false, "explicitly grant workflow operation audit read action")
|
|
if err := flags.Parse(args); err != nil {
|
|
return err
|
|
}
|
|
if flags.NArg() != 0 || output == "" {
|
|
return errors.New("--output and named resource arguments are required")
|
|
}
|
|
cfg, err := config.Load()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
file, err := os.OpenFile(output, os.O_CREATE|os.O_EXCL|os.O_WRONLY, 0600)
|
|
if err != nil {
|
|
return errors.New("cannot create new private output file (existing files are never overwritten)")
|
|
}
|
|
defer file.Close()
|
|
delivered := false
|
|
defer func() {
|
|
if !delivered {
|
|
_ = os.Remove(output)
|
|
}
|
|
}()
|
|
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
|
|
defer cancel()
|
|
db, err := mysql.Open(ctx, cfg.MySQLDSN, 2, 1)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer db.Close()
|
|
err = mysql.Bootstrap(ctx, db, in, func(result mysql.BootstrapResult) error {
|
|
if err := json.NewEncoder(file).Encode(result); err != nil {
|
|
return err
|
|
}
|
|
if err := file.Sync(); err != nil {
|
|
return err
|
|
}
|
|
directory, err := os.Open(filepath.Dir(output))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
err = directory.Sync()
|
|
closeErr := directory.Close()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if closeErr != nil {
|
|
return closeErr
|
|
}
|
|
delivered = true
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
if delivered {
|
|
return errors.New("bootstrap commit could not be confirmed; private output was retained; verify its credential before retrying")
|
|
}
|
|
return errors.New("bootstrap failed before credential delivery; check schema, names and database access")
|
|
}
|
|
fmt.Println("Bootstrap committed. Credentials saved in the requested private file; valid for 30 days.")
|
|
return nil
|
|
}
|