blob: 0ce84d73e02b809a58357e5af41dc670fea0eea4 [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
37 "git.monogon.dev/source/nexantic.git/metropolis/node/common/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 (
51 // BuiltinLabelKey is used as a k8s label to mark built-in objects (ie., managed by the reconciler)
Serge Bazanski662b5b32020-12-21 13:49:00 +010052 BuiltinLabelKey = "metropolis.monogon.dev/builtin"
Serge Bazanskie6030f62020-06-03 17:52:59 +020053 // BuiltinLabelValue is used as a k8s label value, under the BuiltinLabelKey key.
54 BuiltinLabelValue = "true"
55 // BuiltinRBACPrefix is used to prefix all built-in objects that are part of the rbac/v1 API (eg.
56 // {Cluster,}Role{Binding,} objects). This corresponds to the colon-separated 'namespaces' notation used by
57 // Kubernetes system (system:) objects.
Serge Bazanski662b5b32020-12-21 13:49:00 +010058 BuiltinRBACPrefix = "metropolis:"
Serge Bazanskie6030f62020-06-03 17:52:59 +020059)
60
61// builtinLabels makes a kubernetes-compatible label dictionary (key->value) that is used to mark objects that are
Serge Bazanski662b5b32020-12-21 13:49:00 +010062// built-in into Metropolis (ie., managed by the reconciler). These are then subsequently retrieved by listBuiltins.
Serge Bazanskie6030f62020-06-03 17:52:59 +020063// The extra argument specifies what other labels are to be merged into the the labels dictionary, for convenience. If
64// nil or empty, no extra labels will be applied.
65func builtinLabels(extra map[string]string) map[string]string {
66 l := map[string]string{
67 BuiltinLabelKey: BuiltinLabelValue,
68 }
69 if extra != nil {
70 for k, v := range extra {
71 l[k] = v
72 }
73 }
74 return l
75}
76
77// listBuiltins returns a k8s client ListOptions structure that allows to retrieve all objects that are built-in into
Serge Bazanski662b5b32020-12-21 13:49:00 +010078// Metropolis currently present in the API server (ie., ones that are to be managed by the reconciler). These are
79// created by applying builtinLabels to their metadata labels.
Serge Bazanskie6030f62020-06-03 17:52:59 +020080var listBuiltins = meta.ListOptions{
81 LabelSelector: fmt.Sprintf("%s=%s", BuiltinLabelKey, BuiltinLabelValue),
82}
83
84// builtinRBACName returns a name that is compatible with colon-delimited 'namespaced' objects, a la system:*.
85// These names are to be used by all builtins created as part of the rbac/v1 Kubernetes API.
86func builtinRBACName(name string) string {
87 return BuiltinRBACPrefix + name
88}
89
90// resource is a type of resource to be managed by the reconciler. All builti-ins/reconciled objects must implement
91// this interface to be managed correctly by the reconciler.
92type resource interface {
93 // List returns a list of names of objects current present on the target (ie. k8s API server).
94 List(ctx context.Context) ([]string, error)
95 // Create creates an object on the target. The el interface{} argument is the black box object returned by the
96 // Expected() call.
97 Create(ctx context.Context, el interface{}) error
98 // Delete delete an object, by name, from the target.
99 Delete(ctx context.Context, name string) error
100 // Expected returns a map of all objects expected to be present on the target. The keys are names (which must
101 // correspond to the names returned by List() and used by Delete(), and the values are blackboxes that will then
102 // be passed to the Create() call if their corresponding key (name) does not exist on the target.
103 Expected() map[string]interface{}
104}
105
106func allResources(clientSet kubernetes.Interface) map[string]resource {
107 return map[string]resource{
108 "psps": resourcePodSecurityPolicies{clientSet},
109 "clusterroles": resourceClusterRoles{clientSet},
110 "clusterrolebindings": resourceClusterRoleBindings{clientSet},
111 "storageclasses": resourceStorageClasses{clientSet},
112 "csidrivers": resourceCSIDrivers{clientSet},
Lorenz Brun5e4fc2d2020-09-22 18:35:15 +0200113 "runtimeclasses": resourceRuntimeClasses{clientSet},
Serge Bazanskie6030f62020-06-03 17:52:59 +0200114 }
115}
116
117func Run(clientSet kubernetes.Interface) supervisor.Runnable {
118 return func(ctx context.Context) error {
119 log := supervisor.Logger(ctx)
120 resources := allResources(clientSet)
121 t := time.NewTicker(10 * time.Second)
122 reconcileAll := func() {
123 for name, resource := range resources {
124 if err := reconcile(ctx, resource); err != nil {
Serge Bazanskic7359672020-10-30 16:38:57 +0100125 log.Warningf("Failed to reconcile built-in resources %s: %v", name, err)
Serge Bazanskie6030f62020-06-03 17:52:59 +0200126 }
127 }
128 }
129 supervisor.Signal(ctx, supervisor.SignalHealthy)
130 reconcileAll()
131 for {
132 select {
133 case <-t.C:
134 reconcileAll()
135 case <-ctx.Done():
136 return nil
137 }
138 }
139 }
140}
141
142func reconcile(ctx context.Context, r resource) error {
143 present, err := r.List(ctx)
144 if err != nil {
145 return err
146 }
147 presentSet := make(map[string]bool)
148 for _, el := range present {
149 presentSet[el] = true
150 }
151 expectedMap := r.Expected()
152 for name, el := range expectedMap {
153 if !presentSet[name] {
154 if err := r.Create(ctx, el); err != nil {
155 return err
156 }
157 }
158 }
159 for name, _ := range presentSet {
160 if _, ok := expectedMap[name]; !ok {
161 if err := r.Delete(ctx, name); err != nil {
162 return err
163 }
164 }
165 }
166 return nil
167}