forked from elliot-gustafsson/kbootcd
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmain.go
More file actions
175 lines (150 loc) · 5.25 KB
/
Copy pathmain.go
File metadata and controls
175 lines (150 loc) · 5.25 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
package main
//go:generate go run github.com/vektra/mockery/v3
import (
"context"
"errors"
"fmt"
"log/slog"
"os"
"os/signal"
"strings"
"syscall"
"time"
"github.com/elliot-gustafsson/kbootcd/internal/command"
"github.com/elliot-gustafsson/kbootcd/internal/controller"
"github.com/spf13/cobra"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
"k8s.io/utils/clock"
)
func setupDefaultLogger(level, format string) {
l := slog.LevelInfo
if level != "" {
err := l.UnmarshalText([]byte(level))
if err != nil {
slog.Error("failed to parse log level, defaulting to INFO",
"error", err,
"provided_level", level,
)
l = slog.LevelInfo
}
}
opts := &slog.HandlerOptions{
Level: l,
}
var handler slog.Handler
switch strings.ToLower(format) {
case "json":
handler = slog.NewJSONHandler(os.Stdout, opts)
case "text", "":
handler = slog.NewTextHandler(os.Stdout, opts)
default:
slog.Error("unknown log format option, defaulting to text",
"provided_format", format,
)
handler = slog.NewTextHandler(os.Stdout, opts)
}
slog.SetDefault(slog.New(handler))
}
var (
logLevel string
logFormat string
configPath string
leaseName string
leaseNamespace string
leaseDuration time.Duration
tickerInterval time.Duration
rebootDelay time.Duration
cooldownDelay time.Duration
rebootDays []string
rebootStart string
rebootEnd string
timezone string
)
var logLevels = []string{
slog.LevelDebug.String(),
slog.LevelInfo.String(),
slog.LevelWarn.String(),
slog.LevelError.String(),
}
func main() {
var rootCmd = &cobra.Command{
Use: "kbootcd",
Short: "Kubernetes Bootc Daemon",
RunE: func(cmd *cobra.Command, args []string) error {
return run(cmd.Context())
},
}
rootCmd.Flags().StringVar(&logLevel, "log-level", slog.LevelInfo.String(), fmt.Sprintf("Log level. Valid values: %s", strings.Join(logLevels, ", ")))
rootCmd.Flags().StringVar(&logFormat, "log-format", "text", "Log format. Valid values: text, json")
rootCmd.Flags().StringVar(&configPath, "config-path", "/etc/bootc-config/target_image", "Path to the ConfigMap mount containing the desired digest")
rootCmd.Flags().StringVar(&leaseName, "lease-name", "bootc-upgrade-lock", "Name of the cluster-wide coordination lease")
rootCmd.Flags().StringVar(&leaseNamespace, "lease-namespace", "kube-system", "Namespace of the coordination lease")
rootCmd.Flags().DurationVar(&leaseDuration, "lease-duration", 60*time.Minute, "Lease duration")
rootCmd.Flags().DurationVar(&tickerInterval, "interval", 5*time.Minute, "How often to run the reconciliation loop")
rootCmd.Flags().DurationVar(&rebootDelay, "reboot-delay", 0, "Delay reboot for this duration")
rootCmd.Flags().DurationVar(&cooldownDelay, "cooldown-delay", 0, "Delay the release of the lease lock for this duration after a successful update")
rootCmd.PersistentFlags().StringSliceVar(&rebootDays, "reboot-days", controller.EveryDay, "schedule reboot on these days")
rootCmd.PersistentFlags().StringVar(&rebootStart, "start-time", "0:00", "schedule reboot only after this time of day")
rootCmd.PersistentFlags().StringVar(&rebootEnd, "end-time", "23:59:59", "schedule reboot only before this time of day")
rootCmd.PersistentFlags().StringVar(&timezone, "time-zone", "UTC", "use this timezone for schedule inputs")
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGQUIT, syscall.SIGTERM)
defer stop()
err := rootCmd.ExecuteContext(ctx)
if err != nil {
slog.Error(err.Error())
os.Exit(1)
}
}
func run(ctx context.Context) error {
setupDefaultLogger(logLevel, logFormat)
nodeName := os.Getenv("NODE_NAME")
if nodeName == "" {
return fmt.Errorf("error NODE_NAME environment variable must be set")
}
kubeConfig, err := rest.InClusterConfig()
if err != nil {
return fmt.Errorf("failed to get in-cluster kubernetes config, err: %w", err)
}
clientset, err := kubernetes.NewForConfig(kubeConfig)
if err != nil {
return fmt.Errorf("failed to create kubernetes clientset, err: %w", err)
}
config := controller.Config{
Clock: clock.RealClock{},
NodeName: nodeName,
TargetImageConfigPath: configPath,
LeaseName: leaseName,
LeaseNamespace: leaseNamespace,
LeaseDuration: leaseDuration,
RebootDelay: rebootDelay,
CooldownDelay: cooldownDelay,
}
config.Window, err = controller.BuildTimeWindow(rebootStart, rebootEnd, rebootDays, timezone)
if err != nil {
return fmt.Errorf("error building time window, err: %w", err)
}
slog.Info("Starting kbootcd", "node", nodeName, "interval", tickerInterval.String())
cmder := command.NewCommander()
// Run immediate baseline check on startup
err = controller.Reconcile(ctx, cmder, clientset, config)
if err != nil {
slog.Error("error during initial reconciliation", "error", err.Error())
}
ticker := time.NewTicker(tickerInterval)
for {
select {
case <-ctx.Done():
return nil
case <-ticker.C:
err := controller.Reconcile(ctx, cmder, clientset, config)
if err != nil {
if errors.Is(err, controller.ErrRebootInitiated) {
slog.Info("reboot issued successfully, waiting for node shutdown...", "node", nodeName)
return nil
}
slog.Error("error during reconciliation", "error", err.Error())
}
}
}
}