blob: f2c3838cb14f882b56b16129abc4013e6b5a5386 [file] [log] [blame]
Serge Bazanskie6030f62020-06-03 17:52:59 +02001// Copyright 2020 The Monogon Project Authors.
2//
3// SPDX-License-Identifier: Apache-2.0
4//
5// Licensed under the Apache License, Version 2.0 (the "License");
6// you may not use this file except in compliance with the License.
7// You may obtain a copy of the License at
8//
9// http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing, software
12// distributed under the License is distributed on an "AS IS" BASIS,
13// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14// See the License for the specific language governing permissions and
15// limitations under the License.
16
Serge Bazanski662b5b32020-12-21 13:49:00 +010017// The reconciler ensures that a base set of K8s resources is always available
18// in the cluster. These are necessary to ensure correct out-of-the-box
19// functionality. All resources containing the
20// metropolis.monogon.dev/builtin=true label are assumed to be managed by the
21// reconciler.
22// It currently does not revert modifications made by admins, it is planned to
23// create an admission plugin prohibiting such modifications to resources with
24// the metropolis.monogon.dev/builtin label to deal with that problem. This
25// would also solve a potential issue where you could delete resources just by
26// adding the metropolis.monogon.dev/builtin=true label.
Serge Bazanskie6030f62020-06-03 17:52:59 +020027package reconciler
28
29import (
30 "context"
31 "fmt"
32 "time"
33
Serge Bazanskie6030f62020-06-03 17:52:59 +020034 meta "k8s.io/apimachinery/pkg/apis/meta/v1"
Serge Bazanskie6030f62020-06-03 17:52:59 +020035 "k8s.io/client-go/kubernetes"
Serge Bazanski77cb6c52020-12-19 00:09:22 +010036
Serge Bazanski31370b02021-01-07 16:31:14 +010037 "source.monogon.dev/metropolis/pkg/supervisor"
Serge Bazanskie6030f62020-06-03 17:52:59 +020038)
39
40// Sad workaround for all the pointer booleans in K8s specs
41func True() *bool {
42 val := true
43 return &val
44}
45func False() *bool {
46 val := false
47 return &val
48}
49
50const (
Serge Bazanski216fe7b2021-05-21 18:36:16 +020051 // BuiltinLabelKey is used as a k8s label to mark built-in objects (ie.,
52 // managed by the reconciler)
Serge Bazanski662b5b32020-12-21 13:49:00 +010053 BuiltinLabelKey = "metropolis.monogon.dev/builtin"
Serge Bazanski216fe7b2021-05-21 18:36:16 +020054 // BuiltinLabelValue is used as a k8s label value, under the
55 // BuiltinLabelKey key.
Serge Bazanskie6030f62020-06-03 17:52:59 +020056 BuiltinLabelValue = "true"
Serge Bazanski216fe7b2021-05-21 18:36:16 +020057 // BuiltinRBACPrefix is used to prefix all built-in objects that are part
58 // of the rbac/v1 API (eg. {Cluster,}Role{Binding,} objects). This
59 // corresponds to the colon-separated 'namespaces' notation used by
Serge Bazanskie6030f62020-06-03 17:52:59 +020060 // Kubernetes system (system:) objects.
Serge Bazanski662b5b32020-12-21 13:49:00 +010061 BuiltinRBACPrefix = "metropolis:"
Serge Bazanskie6030f62020-06-03 17:52:59 +020062)
63
Serge Bazanski216fe7b2021-05-21 18:36:16 +020064// builtinLabels makes a kubernetes-compatible label dictionary (key->value)
65// that is used to mark objects that are built-in into Metropolis (ie., managed
66// by the reconciler). These are then subsequently retrieved by listBuiltins.
67// The extra argument specifies what other labels are to be merged into the the
68// labels dictionary, for convenience. If nil or empty, no extra labels will be
69// applied.
Serge Bazanskie6030f62020-06-03 17:52:59 +020070func builtinLabels(extra map[string]string) map[string]string {
71 l := map[string]string{
72 BuiltinLabelKey: BuiltinLabelValue,
73 }
Tim Windelschmidt24ce66f2024-04-18 23:59:24 +020074 for k, v := range extra {
75 l[k] = v
Serge Bazanskie6030f62020-06-03 17:52:59 +020076 }
77 return l
78}
79
Serge Bazanski216fe7b2021-05-21 18:36:16 +020080// listBuiltins returns a k8s client ListOptions structure that allows to
81// retrieve all objects that are built-in into Metropolis currently present in
82// the API server (ie., ones that are to be managed by the reconciler). These
83// are created by applying builtinLabels to their metadata labels.
Serge Bazanskie6030f62020-06-03 17:52:59 +020084var listBuiltins = meta.ListOptions{
85 LabelSelector: fmt.Sprintf("%s=%s", BuiltinLabelKey, BuiltinLabelValue),
86}
87
Serge Bazanski216fe7b2021-05-21 18:36:16 +020088// builtinRBACName returns a name that is compatible with colon-delimited
89// 'namespaced' objects, a la system:*.
90// These names are to be used by all builtins created as part of the rbac/v1
91// Kubernetes API.
Serge Bazanskie6030f62020-06-03 17:52:59 +020092func builtinRBACName(name string) string {
93 return BuiltinRBACPrefix + name
94}
95
Serge Bazanski216fe7b2021-05-21 18:36:16 +020096// resource is a type of resource to be managed by the reconciler. All
Jan Schär7f727482024-03-25 13:03:51 +010097// built-ins/reconciled objects must implement this interface to be managed
Serge Bazanski216fe7b2021-05-21 18:36:16 +020098// correctly by the reconciler.
Serge Bazanskie6030f62020-06-03 17:52:59 +020099type resource interface {
Jan Schär7f727482024-03-25 13:03:51 +0100100 // List returns a list of objects currently present on the target
Serge Bazanski216fe7b2021-05-21 18:36:16 +0200101 // (ie. k8s API server).
Jan Schär7f727482024-03-25 13:03:51 +0100102 List(ctx context.Context) ([]meta.Object, error)
103 // Create creates an object on the target. The el argument is
104 // an object returned by the Expected() call.
105 Create(ctx context.Context, el meta.Object) error
106 // Delete deletes an object, by name, from the target.
Serge Bazanskie6030f62020-06-03 17:52:59 +0200107 Delete(ctx context.Context, name string) error
Jan Schär7f727482024-03-25 13:03:51 +0100108 // Expected returns a list of all objects expected to be present on the
109 // target. Objects are identified by their name, as returned by GetName.
110 Expected() []meta.Object
Serge Bazanskie6030f62020-06-03 17:52:59 +0200111}
112
113func allResources(clientSet kubernetes.Interface) map[string]resource {
114 return map[string]resource{
Serge Bazanskie6030f62020-06-03 17:52:59 +0200115 "clusterroles": resourceClusterRoles{clientSet},
116 "clusterrolebindings": resourceClusterRoleBindings{clientSet},
117 "storageclasses": resourceStorageClasses{clientSet},
118 "csidrivers": resourceCSIDrivers{clientSet},
Lorenz Brun5e4fc2d2020-09-22 18:35:15 +0200119 "runtimeclasses": resourceRuntimeClasses{clientSet},
Serge Bazanskie6030f62020-06-03 17:52:59 +0200120 }
121}
122
Serge Bazanski356cbf32023-03-16 17:52:20 +0100123func ReconcileAll(ctx context.Context, clientSet kubernetes.Interface) error {
124 resources := allResources(clientSet)
125 for name, resource := range resources {
126 err := reconcile(ctx, resource)
127 if err != nil {
128 return fmt.Errorf("resource %s: %w", name, err)
129 }
130 }
131 return nil
132}
133
134func Maintain(clientSet kubernetes.Interface) supervisor.Runnable {
Serge Bazanskie6030f62020-06-03 17:52:59 +0200135 return func(ctx context.Context) error {
136 log := supervisor.Logger(ctx)
Serge Bazanskie6030f62020-06-03 17:52:59 +0200137 supervisor.Signal(ctx, supervisor.SignalHealthy)
Serge Bazanski356cbf32023-03-16 17:52:20 +0100138 t := time.NewTicker(10 * time.Second)
139 defer t.Stop()
Serge Bazanskie6030f62020-06-03 17:52:59 +0200140 for {
141 select {
142 case <-t.C:
Serge Bazanski356cbf32023-03-16 17:52:20 +0100143 err := ReconcileAll(ctx, clientSet)
144 if err != nil {
145 log.Warning(err)
146 }
Serge Bazanskie6030f62020-06-03 17:52:59 +0200147 case <-ctx.Done():
148 return nil
149 }
150 }
151 }
152}
153
154func reconcile(ctx context.Context, r resource) error {
155 present, err := r.List(ctx)
156 if err != nil {
157 return err
158 }
Jan Schär7f727482024-03-25 13:03:51 +0100159 presentMap := make(map[string]meta.Object)
Serge Bazanskie6030f62020-06-03 17:52:59 +0200160 for _, el := range present {
Jan Schär7f727482024-03-25 13:03:51 +0100161 presentMap[el.GetName()] = el
Serge Bazanskie6030f62020-06-03 17:52:59 +0200162 }
Jan Schär7f727482024-03-25 13:03:51 +0100163 expected := r.Expected()
164 expectedMap := make(map[string]meta.Object)
165 for _, el := range expected {
166 expectedMap[el.GetName()] = el
167 }
168 for name, expectedEl := range expectedMap {
169 if _, ok := presentMap[name]; ok {
170 // TODO(#288): update the object if it is different than expected.
171 } else {
172 if err := r.Create(ctx, expectedEl); err != nil {
Serge Bazanskie6030f62020-06-03 17:52:59 +0200173 return err
174 }
175 }
176 }
Tim Windelschmidt6b6428d2024-04-11 01:35:41 +0200177 for name := range presentMap {
Serge Bazanskie6030f62020-06-03 17:52:59 +0200178 if _, ok := expectedMap[name]; !ok {
179 if err := r.Delete(ctx, name); err != nil {
180 return err
181 }
182 }
183 }
184 return nil
185}