2023-01-24 15:47:27 +01:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
2023-01-24 16:40:11 +01:00
|
|
|
"errors"
|
2023-01-24 15:47:27 +01:00
|
|
|
"fmt"
|
2023-01-24 16:40:11 +01:00
|
|
|
"net/http"
|
2023-01-24 15:47:27 +01:00
|
|
|
"net/url"
|
|
|
|
"os"
|
|
|
|
"os/signal"
|
2023-01-24 16:56:17 +01:00
|
|
|
"syscall"
|
2023-01-24 16:40:11 +01:00
|
|
|
"time"
|
2023-01-24 15:47:27 +01:00
|
|
|
|
|
|
|
"github.com/alexflint/go-arg"
|
|
|
|
"github.com/containerd/containerd"
|
|
|
|
"github.com/go-logr/logr"
|
|
|
|
"github.com/go-logr/zapr"
|
2023-01-24 16:40:11 +01:00
|
|
|
"github.com/prometheus/client_golang/prometheus/promhttp"
|
2023-01-24 15:47:27 +01:00
|
|
|
"github.com/spf13/afero"
|
|
|
|
"go.uber.org/zap"
|
|
|
|
"golang.org/x/sync/errgroup"
|
|
|
|
|
2023-01-26 18:48:02 +01:00
|
|
|
pkgkubernetes "github.com/xenitab/pkg/kubernetes"
|
2023-01-24 15:47:27 +01:00
|
|
|
"github.com/xenitab/spegel/internal/mirror"
|
|
|
|
"github.com/xenitab/spegel/internal/registry"
|
2023-01-26 18:48:02 +01:00
|
|
|
"github.com/xenitab/spegel/internal/routing"
|
2023-01-24 15:47:27 +01:00
|
|
|
"github.com/xenitab/spegel/internal/state"
|
|
|
|
)
|
|
|
|
|
|
|
|
type arguments struct {
|
2023-01-25 13:14:41 +01:00
|
|
|
MirrorRegistries []url.URL `arg:"--mirror-registries,required"`
|
|
|
|
ImageFilter string `arg:"--image-filter"`
|
2023-01-26 18:48:02 +01:00
|
|
|
RegistryAddr string `arg:"--registry-addr" default:":5000"`
|
|
|
|
RouterAddr string `arg:"--router-addr" default:":5001"`
|
|
|
|
MetricsAddr string `arg:"--metrics-addr" default:":9090"`
|
2023-01-24 15:47:27 +01:00
|
|
|
ContainerdSock string `arg:"--containerd-sock" default:"/run/containerd/containerd.sock"`
|
|
|
|
ContainerdNamespace string `arg:"--containerd-namespace" default:"k8s.io"`
|
|
|
|
ContainerdRegistryConfigPath string `arg:"--containerd-registry-config-path" default:"/etc/containerd/certs.d"`
|
|
|
|
ContainerdMirrorAdd bool `arg:"--containerd-mirror-add" default:"true"`
|
|
|
|
ContainerdMirrorRemove bool `arg:"--containerd-mirror-remove" default:"true"`
|
2023-01-26 18:48:02 +01:00
|
|
|
KubeconfigPath string `arg:"--kubeconfig-path"`
|
|
|
|
LeaderElectionNamespace string `arg:"--leader-election-namespace" default:"spegel"`
|
|
|
|
LeaderElectionName string `arg:"--leader-election-name" default:"spegel-leader-election"`
|
2023-01-24 15:47:27 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
func main() {
|
|
|
|
args := &arguments{}
|
|
|
|
arg.MustParse(args)
|
|
|
|
|
|
|
|
zapLog, err := zap.NewProduction()
|
|
|
|
if err != nil {
|
|
|
|
panic(fmt.Sprintf("who watches the watchmen (%v)?", err))
|
|
|
|
}
|
|
|
|
log := zapr.NewLogger(zapLog)
|
|
|
|
ctx := logr.NewContext(context.Background(), log)
|
|
|
|
|
2023-01-24 16:56:17 +01:00
|
|
|
ctx, cancel := signal.NotifyContext(ctx, syscall.SIGTERM)
|
2023-01-24 15:47:27 +01:00
|
|
|
defer cancel()
|
|
|
|
g, ctx := errgroup.WithContext(ctx)
|
|
|
|
|
|
|
|
containerdClient, err := containerd.New(args.ContainerdSock, containerd.WithDefaultNamespace(args.ContainerdNamespace))
|
|
|
|
if err != nil {
|
|
|
|
log.Error(err, "could not create containerd client")
|
|
|
|
os.Exit(1)
|
|
|
|
}
|
|
|
|
defer containerdClient.Close()
|
|
|
|
|
2023-01-26 18:48:02 +01:00
|
|
|
// Run leader election
|
|
|
|
cs, err := pkgkubernetes.GetKubernetesClientset(args.KubeconfigPath)
|
|
|
|
if err != nil {
|
|
|
|
log.Error(err, "could not create Kubernetes client")
|
|
|
|
os.Exit(1)
|
|
|
|
}
|
|
|
|
|
2023-01-24 16:40:11 +01:00
|
|
|
// Start metrics server
|
|
|
|
mux := http.NewServeMux()
|
|
|
|
mux.Handle("/metrics", promhttp.Handler())
|
|
|
|
srv := &http.Server{
|
|
|
|
Addr: args.MetricsAddr,
|
|
|
|
Handler: mux,
|
|
|
|
}
|
|
|
|
g.Go(func() error {
|
|
|
|
if err := srv.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
g.Go(func() error {
|
|
|
|
<-ctx.Done()
|
2023-01-25 23:52:50 +01:00
|
|
|
shutdownCtx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
2023-01-24 16:40:11 +01:00
|
|
|
defer cancel()
|
|
|
|
return srv.Shutdown(shutdownCtx)
|
|
|
|
})
|
|
|
|
|
2023-01-24 15:47:27 +01:00
|
|
|
// Setup and run store
|
2023-01-26 18:48:02 +01:00
|
|
|
bootstrapper := routing.NewKubernetesBootstrapper(cs, args.LeaderElectionNamespace, args.LeaderElectionName)
|
|
|
|
router, err := routing.NewP2PRouter(ctx, args.RouterAddr, bootstrapper)
|
2023-01-24 15:47:27 +01:00
|
|
|
if err != nil {
|
2023-01-26 18:48:02 +01:00
|
|
|
log.Error(err, "could not create router")
|
2023-01-24 15:47:27 +01:00
|
|
|
os.Exit(1)
|
|
|
|
}
|
2023-01-25 19:08:33 +01:00
|
|
|
g.Go(func() error {
|
|
|
|
<-ctx.Done()
|
2023-01-26 18:48:02 +01:00
|
|
|
return router.Close()
|
2023-01-25 19:08:33 +01:00
|
|
|
})
|
|
|
|
|
|
|
|
// Track containerd state changes
|
2023-01-24 15:47:27 +01:00
|
|
|
g.Go(func() error {
|
2023-01-26 18:48:02 +01:00
|
|
|
return state.Track(ctx, containerdClient, router, args.ImageFilter)
|
2023-01-24 15:47:27 +01:00
|
|
|
})
|
|
|
|
|
|
|
|
// Configure mirrors
|
2023-01-25 11:23:30 +01:00
|
|
|
// TODO: Wait to write mirror configuration until registry is up and running.
|
2023-01-24 15:47:27 +01:00
|
|
|
if args.ContainerdMirrorAdd {
|
|
|
|
fs := afero.NewOsFs()
|
|
|
|
err := mirror.AddMirrorConfiguration(ctx, fs, args.ContainerdRegistryConfigPath, args.RegistryAddr, args.MirrorRegistries)
|
|
|
|
if err != nil {
|
|
|
|
log.Error(err, "could not configure containerd mirror")
|
|
|
|
os.Exit(1)
|
|
|
|
}
|
|
|
|
// TODO: Validate clean up is run if error occurs before start.
|
|
|
|
if args.ContainerdMirrorRemove {
|
|
|
|
g.Go(func() error {
|
|
|
|
<-ctx.Done()
|
|
|
|
return mirror.RemoveMirrorConfiguration(ctx, fs, args.ContainerdRegistryConfigPath, args.MirrorRegistries)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Setup and run registry
|
2023-01-26 18:48:02 +01:00
|
|
|
reg, err := registry.NewRegistry(ctx, args.RegistryAddr, containerdClient, router)
|
2023-01-24 15:47:27 +01:00
|
|
|
if err != nil {
|
|
|
|
log.Error(err, "could not create registry")
|
|
|
|
os.Exit(1)
|
|
|
|
}
|
|
|
|
g.Go(func() error {
|
|
|
|
return reg.ListenAndServe(ctx)
|
|
|
|
})
|
|
|
|
g.Go(func() error {
|
|
|
|
<-ctx.Done()
|
|
|
|
return reg.Shutdown()
|
|
|
|
})
|
|
|
|
|
|
|
|
log.Info("running registry", "addr", args.RegistryAddr)
|
|
|
|
err = g.Wait()
|
|
|
|
if err != nil {
|
|
|
|
log.Error(err, "exiting with error")
|
|
|
|
os.Exit(1)
|
|
|
|
}
|
|
|
|
log.Info("gracefully shutdown registry")
|
|
|
|
}
|