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-06-27 17:53:47 +02:00
"net"
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/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"
2023-06-12 12:25:05 +02:00
pkgkubernetes "github.com/xenitab/pkg/kubernetes"
2023-01-24 15:47:27 +01:00
"go.uber.org/zap"
"golang.org/x/sync/errgroup"
2023-10-27 16:50:48 +02:00
"k8s.io/klog/v2"
2023-01-24 15:47:27 +01:00
2024-01-18 11:33:11 +01:00
"github.com/xenitab/spegel/pkg/metrics"
2023-12-11 22:05:41 +01:00
"github.com/xenitab/spegel/pkg/oci"
2024-01-23 11:56:22 +01:00
"github.com/xenitab/spegel/pkg/registry"
"github.com/xenitab/spegel/pkg/routing"
"github.com/xenitab/spegel/pkg/state"
2023-01-24 15:47:27 +01:00
)
2023-03-15 23:10:18 +01:00
type ConfigurationCmd struct {
ContainerdRegistryConfigPath string ` arg:"--containerd-registry-config-path" default:"/etc/containerd/certs.d" help:"Directory where mirror configuration is written." `
2023-02-10 11:59:05 +01:00
Registries [ ] url . URL ` arg:"--registries,required" help:"registries that are configured to be mirrored." `
MirrorRegistries [ ] url . URL ` arg:"--mirror-registries,required" help:"registries that are configured to act as mirrors." `
2023-08-17 13:52:40 +02:00
ResolveTags bool ` arg:"--resolve-tags" default:"true" help:"When true Spegel will resolve tags to digests." `
2023-03-15 23:10:18 +01:00
}
type RegistryCmd struct {
2023-12-29 11:45:16 +00:00
KubeconfigPath string ` arg:"--kubeconfig-path" help:"Path to the kubeconfig file." `
ContainerdRegistryConfigPath string ` arg:"--containerd-registry-config-path" default:"/etc/containerd/certs.d" help:"Directory where mirror configuration is written." `
2023-07-31 22:33:56 +02:00
MetricsAddr string ` arg:"--metrics-addr,required" help:"address to serve metrics." `
2023-12-29 11:45:16 +00:00
LocalAddr string ` arg:"--local-addr,required" help:"Address that the local Spegel instance will be reached at." `
2023-07-31 22:33:56 +02:00
ContainerdSock string ` arg:"--containerd-sock" default:"/run/containerd/containerd.sock" help:"Endpoint of containerd service." `
ContainerdNamespace string ` arg:"--containerd-namespace" default:"k8s.io" help:"Containerd namespace to fetch images from." `
2023-12-29 11:45:16 +00:00
RouterAddr string ` arg:"--router-addr,required" help:"address to serve router." `
2023-07-31 22:33:56 +02:00
LeaderElectionName string ` arg:"--leader-election-name" default:"spegel-leader-election" help:"Name of leader election." `
2023-12-29 11:45:16 +00:00
LeaderElectionNamespace string ` arg:"--leader-election-namespace" default:"spegel" help:"Kubernetes namespace to write leader election data." `
RegistryAddr string ` arg:"--registry-addr,required" help:"address to server image registry." `
Registries [ ] url . URL ` arg:"--registries,required" help:"registries that are configured to be mirrored." `
MirrorResolveTimeout time . Duration ` arg:"--mirror-resolve-timeout" default:"5s" help:"Max duration spent finding a mirror." `
MirrorResolveRetries int ` arg:"--mirror-resolve-retries" default:"3" help:"Max amount of mirrors to attempt." `
2023-07-31 22:33:56 +02:00
ResolveLatestTag bool ` arg:"--resolve-latest-tag" default:"true" help:"When true latest tags will be resolved to digests." `
2023-03-15 23:10:18 +01:00
}
2023-03-17 13:41:43 +01:00
type Arguments struct {
2023-03-15 23:10:18 +01:00
Configuration * ConfigurationCmd ` arg:"subcommand:configuration" `
Registry * RegistryCmd ` arg:"subcommand:registry" `
2023-01-24 15:47:27 +01:00
}
func main ( ) {
2023-03-17 13:41:43 +01:00
args := & Arguments { }
2023-01-24 15:47:27 +01:00
arg . MustParse ( args )
zapLog , err := zap . NewProduction ( )
if err != nil {
panic ( fmt . Sprintf ( "who watches the watchmen (%v)?" , err ) )
}
log := zapr . NewLogger ( zapLog )
2023-10-27 16:50:48 +02:00
klog . SetLogger ( log )
2023-03-15 23:10:18 +01:00
ctx := logr . NewContext ( context . Background ( ) , log )
2023-01-24 15:47:27 +01:00
2023-03-15 23:10:18 +01:00
err = run ( ctx , args )
2023-01-24 15:47:27 +01:00
if err != nil {
2023-01-31 23:17:06 +01:00
log . Error ( err , "" )
2023-01-24 15:47:27 +01:00
os . Exit ( 1 )
}
2023-01-31 23:17:06 +01:00
log . Info ( "gracefully shutdown" )
}
2023-01-24 15:47:27 +01:00
2023-03-17 13:41:43 +01:00
func run ( ctx context . Context , args * Arguments ) error {
2023-03-15 23:10:18 +01:00
ctx , cancel := signal . NotifyContext ( ctx , syscall . SIGTERM )
defer cancel ( )
switch {
case args . Configuration != nil :
return configurationCommand ( ctx , args . Configuration )
case args . Registry != nil :
return registryCommand ( ctx , args . Registry )
default :
return fmt . Errorf ( "unknown subcommand" )
}
}
2023-03-17 13:41:43 +01:00
func configurationCommand ( ctx context . Context , args * ConfigurationCmd ) error {
2023-03-15 23:10:18 +01:00
fs := afero . NewOsFs ( )
2023-08-17 13:52:40 +02:00
err := oci . AddMirrorConfiguration ( ctx , fs , args . ContainerdRegistryConfigPath , args . Registries , args . MirrorRegistries , args . ResolveTags )
2023-03-15 23:10:18 +01:00
if err != nil {
return err
}
return nil
}
2023-03-17 13:41:43 +01:00
func registryCommand ( ctx context . Context , args * RegistryCmd ) ( err error ) {
2023-03-15 23:10:18 +01:00
log := logr . FromContextOrDiscard ( ctx )
g , ctx := errgroup . WithContext ( ctx )
2023-03-17 13:41:43 +01:00
cs , err := pkgkubernetes . GetKubernetesClientset ( args . KubeconfigPath )
2023-01-26 18:48:02 +01:00
if err != nil {
2023-01-31 23:17:06 +01:00
return err
}
2023-07-31 22:33:56 +02:00
ociClient , err := oci . NewContainerd ( args . ContainerdSock , args . ContainerdNamespace , args . ContainerdRegistryConfigPath , args . Registries )
if err != nil {
return err
}
err = ociClient . Verify ( ctx )
2023-01-31 23:17:06 +01:00
if err != nil {
2023-05-16 09:10:41 +02:00
return err
2023-01-26 18:48:02 +01:00
}
2023-01-31 23:17:06 +01:00
2024-01-19 18:25:48 +01:00
// Metrics
2024-01-18 11:33:11 +01:00
metrics . Register ( )
2023-01-24 16:40:11 +01:00
mux := http . NewServeMux ( )
2024-01-18 11:33:11 +01:00
mux . Handle ( "/metrics" , promhttp . HandlerFor ( metrics . DefaultGatherer , promhttp . HandlerOpts { } ) )
2023-06-28 21:52:39 +02:00
metricsSrv := & http . Server {
2023-03-17 13:41:43 +01:00
Addr : args . MetricsAddr ,
2023-01-24 16:40:11 +01:00
Handler : mux ,
}
g . Go ( func ( ) error {
2023-06-28 21:52:39 +02:00
if err := metricsSrv . ListenAndServe ( ) ; err != nil && ! errors . Is ( err , http . ErrServerClosed ) {
2023-01-24 16:40:11 +01:00
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 ( )
2023-06-28 21:52:39 +02:00
return metricsSrv . Shutdown ( shutdownCtx )
2023-01-24 16:40:11 +01:00
} )
2024-01-19 18:25:48 +01:00
// Router
2023-06-27 17:53:47 +02:00
_ , registryPort , err := net . SplitHostPort ( args . RegistryAddr )
if err != nil {
return err
}
2023-03-17 13:41:43 +01:00
bootstrapper := routing . NewKubernetesBootstrapper ( cs , args . LeaderElectionNamespace , args . LeaderElectionName )
2023-06-27 17:53:47 +02:00
router , err := routing . NewP2PRouter ( ctx , args . RouterAddr , bootstrapper , registryPort )
2023-01-24 15:47:27 +01:00
if err != nil {
2023-01-31 23:17:06 +01:00
return err
2023-01-24 15:47:27 +01:00
}
2024-01-19 18:25:48 +01:00
g . Go ( func ( ) error {
return router . Run ( ctx )
} )
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
} )
2024-01-19 18:25:48 +01:00
// State tracking
2023-01-24 15:47:27 +01:00
g . Go ( func ( ) error {
2023-08-17 21:51:35 +02:00
state . Track ( ctx , ociClient , router , args . ResolveLatestTag )
return nil
2023-01-24 15:47:27 +01:00
} )
2024-01-19 18:25:48 +01:00
// Registry
2023-08-03 13:46:33 +02:00
reg := registry . NewRegistry ( ociClient , router , args . LocalAddr , args . MirrorResolveRetries , args . MirrorResolveTimeout , args . ResolveLatestTag )
2023-06-28 21:52:39 +02:00
regSrv := reg . Server ( args . RegistryAddr , log )
2023-01-24 15:47:27 +01:00
g . Go ( func ( ) error {
2023-06-28 21:52:39 +02:00
if err := regSrv . ListenAndServe ( ) ; err != nil && ! errors . Is ( err , http . ErrServerClosed ) {
return err
}
return nil
2023-01-24 15:47:27 +01:00
} )
g . Go ( func ( ) error {
<- ctx . Done ( )
2023-06-28 21:52:39 +02:00
shutdownCtx , cancel := context . WithTimeout ( context . Background ( ) , 30 * time . Second )
defer cancel ( )
return regSrv . Shutdown ( shutdownCtx )
2023-01-24 15:47:27 +01:00
} )
2024-01-19 18:25:48 +01:00
log . Info ( "running Spegel" , "registry" , args . RegistryAddr , "router" , args . RouterAddr )
2023-01-24 15:47:27 +01:00
err = g . Wait ( )
if err != nil {
2023-01-31 23:17:06 +01:00
return err
2023-01-24 15:47:27 +01:00
}
2023-01-31 23:17:06 +01:00
return nil
2023-01-24 15:47:27 +01:00
}