cloud: split shepherd up

Change-Id: I8e386d9eaaf17543743e1e8a37a8d71426910d59
Reviewed-on: https://review.monogon.dev/c/monogon/+/2213
Reviewed-by: Serge Bazanski <serge@monogon.tech>
Tested-by: Jenkins CI
diff --git a/cloud/shepherd/provider/equinix/main.go b/cloud/shepherd/provider/equinix/main.go
new file mode 100644
index 0000000..3a402e8
--- /dev/null
+++ b/cloud/shepherd/provider/equinix/main.go
@@ -0,0 +1,154 @@
+package main
+
+import (
+	"context"
+	"flag"
+	"fmt"
+	"os"
+
+	"golang.org/x/crypto/ssh"
+	"k8s.io/klog/v2"
+
+	"source.monogon.dev/cloud/bmaas/bmdb"
+	"source.monogon.dev/cloud/bmaas/bmdb/webug"
+	"source.monogon.dev/cloud/equinix/wrapngo"
+	"source.monogon.dev/cloud/lib/component"
+	"source.monogon.dev/cloud/shepherd/manager"
+	clicontext "source.monogon.dev/metropolis/cli/pkg/context"
+)
+
+type Config struct {
+	Component   component.ComponentConfig
+	BMDB        bmdb.BMDB
+	WebugConfig webug.Config
+
+	SSHKey            manager.SSHKey
+	InitializerConfig manager.InitializerConfig
+	ProvisionerConfig manager.ProvisionerConfig
+	RecovererConfig   manager.RecovererConfig
+
+	API           wrapngo.Opts
+	Provider      providerConfig
+	UpdaterConfig UpdaterConfig
+}
+
+// TODO(q3k): factor this out to BMDB library?
+func runtimeInfo() string {
+	hostname, _ := os.Hostname()
+	if hostname == "" {
+		hostname = "UNKNOWN"
+	}
+	return fmt.Sprintf("host %s", hostname)
+}
+
+func (c *Config) RegisterFlags() {
+	c.Component.RegisterFlags("shepherd")
+	c.BMDB.ComponentName = "shepherd-equinix"
+	c.BMDB.RuntimeInfo = runtimeInfo()
+	c.BMDB.Database.RegisterFlags("bmdb")
+	c.WebugConfig.RegisterFlags()
+
+	c.SSHKey.RegisterFlags()
+	c.InitializerConfig.RegisterFlags()
+	c.ProvisionerConfig.RegisterFlags()
+	c.RecovererConfig.RegisterFlags()
+
+	c.API.RegisterFlags()
+	c.Provider.RegisterFlags()
+	c.UpdaterConfig.RegisterFlags()
+}
+
+func main() {
+	var c Config
+	c.RegisterFlags()
+
+	flag.Parse()
+	if flag.NArg() > 0 {
+		klog.Exitf("unexpected positional arguments: %v", flag.Args())
+	}
+
+	registry := c.Component.PrometheusRegistry()
+	c.BMDB.EnableMetrics(registry)
+
+	ctx := clicontext.WithInterrupt(context.Background())
+	c.Component.StartPrometheus(ctx)
+
+	if c.API.APIKey == "" || c.API.User == "" {
+		klog.Exitf("-equinix_api_username and -equinix_api_key must be set")
+	}
+	c.API.MetricsRegistry = registry
+	api := wrapngo.New(&c.API)
+
+	provider, err := c.Provider.New(&c.SSHKey, api)
+	if err != nil {
+		klog.Exitf("%v", err)
+	}
+
+	sshSigner, err := c.SSHKey.Signer()
+	if err != nil {
+		klog.Exitf("%v", err)
+	}
+
+	sshClient := &manager.PlainSSHClient{
+		AuthMethod: ssh.PublicKeys(sshSigner),
+		// Equinix OS installations always use root.
+		Username: "root",
+	}
+
+	provisioner, err := manager.NewProvisioner(provider, c.ProvisionerConfig)
+	if err != nil {
+		klog.Exitf("%v", err)
+	}
+
+	initializer, err := manager.NewInitializer(provider, sshClient, c.InitializerConfig)
+	if err != nil {
+		klog.Exitf("%v", err)
+	}
+
+	recoverer, err := manager.NewRecoverer(provider, c.RecovererConfig)
+	if err != nil {
+		klog.Exitf("%v", err)
+	}
+
+	updater, err := c.UpdaterConfig.New(api)
+	if err != nil {
+		klog.Exitf("%v", err)
+	}
+
+	conn, err := c.BMDB.Open(true)
+	if err != nil {
+		klog.Exitf("Failed to open BMDB connection: %v", err)
+	}
+
+	go func() {
+		err = provisioner.Run(ctx, conn)
+		if err != nil {
+			klog.Exit(err)
+		}
+	}()
+	go func() {
+		err = manager.RunControlLoop(ctx, conn, initializer)
+		if err != nil {
+			klog.Exit(err)
+		}
+	}()
+	go func() {
+		err = manager.RunControlLoop(ctx, conn, recoverer)
+		if err != nil {
+			klog.Exit(err)
+		}
+	}()
+	go func() {
+		err = updater.Run(ctx, conn)
+		if err != nil {
+			klog.Exit(err)
+		}
+	}()
+	go func() {
+		if err := c.WebugConfig.Start(ctx, conn); err != nil && err != ctx.Err() {
+			klog.Exitf("Failed to start webug: %v", err)
+		}
+	}()
+
+	<-ctx.Done()
+}