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/mini/provider.go b/cloud/shepherd/mini/provider.go
new file mode 100644
index 0000000..05b628f
--- /dev/null
+++ b/cloud/shepherd/mini/provider.go
@@ -0,0 +1,126 @@
+package main
+
+import (
+	"context"
+	"database/sql"
+	"fmt"
+	"net/netip"
+
+	"k8s.io/klog/v2"
+
+	"source.monogon.dev/cloud/bmaas/bmdb"
+	"source.monogon.dev/cloud/bmaas/bmdb/model"
+	"source.monogon.dev/cloud/shepherd"
+)
+
+// provider represents a shepherd.Provider that works entirely on a
+// static device list. It requires a provider type and a device list.
+type provider struct {
+	providerType model.Provider
+	machines     map[shepherd.ProviderID]machine
+}
+
+type machine struct {
+	ProviderID shepherd.ProviderID `json:"ID"`
+	Address    netip.Addr          `json:"Addr"`
+	Location   string              `json:"Location"`
+}
+
+func (d machine) ID() shepherd.ProviderID {
+	return d.ProviderID
+}
+
+func (d machine) Addr() netip.Addr {
+	return d.Address
+}
+
+func (d machine) State() shepherd.State {
+	return shepherd.StatePossiblyUsed
+}
+
+func (p *provider) ListMachines(ctx context.Context) ([]shepherd.Machine, error) {
+	machines := make([]shepherd.Machine, 0, len(p.machines))
+	for _, m := range p.machines {
+		machines = append(machines, m)
+	}
+
+	return machines, nil
+}
+
+func (p *provider) GetMachine(ctx context.Context, id shepherd.ProviderID) (shepherd.Machine, error) {
+	// If the provided machine is not inside our known machines,
+	// bail-out early as this is unsupported.
+	if _, ok := p.machines[id]; !ok {
+		return nil, fmt.Errorf("unknown provided machine requested")
+	}
+
+	return p.machines[id], nil
+}
+
+func (p *provider) CreateMachine(ctx context.Context, session *bmdb.Session, request shepherd.CreateMachineRequest) (shepherd.Machine, error) {
+	if request.UnusedMachine == nil {
+		return nil, fmt.Errorf("parameter UnusedMachine is missing")
+	}
+
+	//TODO: Do we just trust the implementation to be correct?
+	m, ok := request.UnusedMachine.(machine)
+	if !ok {
+		return nil, fmt.Errorf("invalid type for parameter UnusedMachine")
+	}
+
+	if err := p.assimilate(ctx, session, m); err != nil {
+		klog.Errorf("Failed to provision machine %s: %v", m.ProviderID, err)
+		return nil, err
+	}
+
+	return m, nil
+}
+
+func (p *provider) assimilate(ctx context.Context, sess *bmdb.Session, machine machine) error {
+	return sess.Transact(ctx, func(q *model.Queries) error {
+		// Create a new machine record within BMDB.
+		m, err := q.NewMachine(ctx)
+		if err != nil {
+			return fmt.Errorf("while creating a new machine record in BMDB: %w", err)
+		}
+
+		// Link the new machine with the device, and tag it "provided".
+		addParams := model.MachineAddProvidedParams{
+			MachineID:  m.MachineID,
+			ProviderID: string(machine.ProviderID),
+			Provider:   p.providerType,
+		}
+		klog.Infof("Setting \"provided\" tag (ID: %s, PID: %s, Provider: %s).", addParams.MachineID, addParams.ProviderID, addParams.Provider)
+		if err := q.MachineAddProvided(ctx, addParams); err != nil {
+			return fmt.Errorf("while tagging machine active: %w", err)
+		}
+
+		upParams := model.MachineUpdateProviderStatusParams{
+			ProviderID: string(machine.ProviderID),
+			Provider:   p.providerType,
+			ProviderIpAddress: sql.NullString{
+				String: machine.Address.String(),
+				Valid:  true,
+			},
+			ProviderLocation: sql.NullString{
+				String: machine.Location,
+				Valid:  machine.Location != "",
+			},
+			ProviderStatus: model.NullProviderStatus{
+				ProviderStatus: model.ProviderStatusUnknown,
+				Valid:          true,
+			},
+		}
+
+		klog.Infof("Setting \"provided\" tag status parameter (ID: %s, PID: %s, Provider: %s).", addParams.MachineID, upParams.ProviderID, upParams.Provider)
+		if err := q.MachineUpdateProviderStatus(ctx, upParams); err != nil {
+			return fmt.Errorf("while setting machine params: %w", err)
+		}
+
+		return nil
+	})
+}
+
+func (p *provider) Type() model.Provider {
+	return p.providerType
+}