blob: 24a34ef2614405b3cc894ea8833ad95d67a3dc45 [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
17// The reconciler ensures that a base set of K8s resources is always available in the cluster. These are necessary to
18// ensure correct out-of-the-box functionality. All resources containing the smalltown.com/builtin=true label are assumed
19// to be managed by the reconciler.
20// It currently does not revert modifications made by admins, it is planned to create an admission plugin prohibiting
21// such modifications to resources with the smalltown.com/builtin label to deal with that problem. This would also solve a
22// potential issue where you could delete resources just by adding the smalltown.com/builtin=true label.
23package reconciler
24
25import (
26 "context"
27 "fmt"
28 "time"
29
30 "git.monogon.dev/source/nexantic.git/core/internal/common/supervisor"
31
32 meta "k8s.io/apimachinery/pkg/apis/meta/v1"
33
Serge Bazanskie6030f62020-06-03 17:52:59 +020034 "k8s.io/client-go/kubernetes"
35)
36
37// Sad workaround for all the pointer booleans in K8s specs
38func True() *bool {
39 val := true
40 return &val
41}
42func False() *bool {
43 val := false
44 return &val
45}
46
47const (
48 // BuiltinLabelKey is used as a k8s label to mark built-in objects (ie., managed by the reconciler)
49 BuiltinLabelKey = "smalltown.com/builtin"
50 // BuiltinLabelValue is used as a k8s label value, under the BuiltinLabelKey key.
51 BuiltinLabelValue = "true"
52 // BuiltinRBACPrefix is used to prefix all built-in objects that are part of the rbac/v1 API (eg.
53 // {Cluster,}Role{Binding,} objects). This corresponds to the colon-separated 'namespaces' notation used by
54 // Kubernetes system (system:) objects.
55 BuiltinRBACPrefix = "smalltown:"
56)
57
58// builtinLabels makes a kubernetes-compatible label dictionary (key->value) that is used to mark objects that are
59// built-in into Smalltown (ie., managed by the reconciler). These are then subsequently retrieved by listBuiltins.
60// The extra argument specifies what other labels are to be merged into the the labels dictionary, for convenience. If
61// nil or empty, no extra labels will be applied.
62func builtinLabels(extra map[string]string) map[string]string {
63 l := map[string]string{
64 BuiltinLabelKey: BuiltinLabelValue,
65 }
66 if extra != nil {
67 for k, v := range extra {
68 l[k] = v
69 }
70 }
71 return l
72}
73
74// listBuiltins returns a k8s client ListOptions structure that allows to retrieve all objects that are built-in into
75// Smalltown currently present in the API server (ie., ones that are to be managed by the reconciler). These are created
76// by applying builtinLabels to their metadata labels.
77var listBuiltins = meta.ListOptions{
78 LabelSelector: fmt.Sprintf("%s=%s", BuiltinLabelKey, BuiltinLabelValue),
79}
80
81// builtinRBACName returns a name that is compatible with colon-delimited 'namespaced' objects, a la system:*.
82// These names are to be used by all builtins created as part of the rbac/v1 Kubernetes API.
83func builtinRBACName(name string) string {
84 return BuiltinRBACPrefix + name
85}
86
87// resource is a type of resource to be managed by the reconciler. All builti-ins/reconciled objects must implement
88// this interface to be managed correctly by the reconciler.
89type resource interface {
90 // List returns a list of names of objects current present on the target (ie. k8s API server).
91 List(ctx context.Context) ([]string, error)
92 // Create creates an object on the target. The el interface{} argument is the black box object returned by the
93 // Expected() call.
94 Create(ctx context.Context, el interface{}) error
95 // Delete delete an object, by name, from the target.
96 Delete(ctx context.Context, name string) error
97 // Expected returns a map of all objects expected to be present on the target. The keys are names (which must
98 // correspond to the names returned by List() and used by Delete(), and the values are blackboxes that will then
99 // be passed to the Create() call if their corresponding key (name) does not exist on the target.
100 Expected() map[string]interface{}
101}
102
103func allResources(clientSet kubernetes.Interface) map[string]resource {
104 return map[string]resource{
105 "psps": resourcePodSecurityPolicies{clientSet},
106 "clusterroles": resourceClusterRoles{clientSet},
107 "clusterrolebindings": resourceClusterRoleBindings{clientSet},
108 "storageclasses": resourceStorageClasses{clientSet},
109 "csidrivers": resourceCSIDrivers{clientSet},
Lorenz Brun5e4fc2d2020-09-22 18:35:15 +0200110 "runtimeclasses": resourceRuntimeClasses{clientSet},
Serge Bazanskie6030f62020-06-03 17:52:59 +0200111 }
112}
113
114func Run(clientSet kubernetes.Interface) supervisor.Runnable {
115 return func(ctx context.Context) error {
116 log := supervisor.Logger(ctx)
117 resources := allResources(clientSet)
118 t := time.NewTicker(10 * time.Second)
119 reconcileAll := func() {
120 for name, resource := range resources {
121 if err := reconcile(ctx, resource); err != nil {
Serge Bazanskic7359672020-10-30 16:38:57 +0100122 log.Warningf("Failed to reconcile built-in resources %s: %v", name, err)
Serge Bazanskie6030f62020-06-03 17:52:59 +0200123 }
124 }
125 }
126 supervisor.Signal(ctx, supervisor.SignalHealthy)
127 reconcileAll()
128 for {
129 select {
130 case <-t.C:
131 reconcileAll()
132 case <-ctx.Done():
133 return nil
134 }
135 }
136 }
137}
138
139func reconcile(ctx context.Context, r resource) error {
140 present, err := r.List(ctx)
141 if err != nil {
142 return err
143 }
144 presentSet := make(map[string]bool)
145 for _, el := range present {
146 presentSet[el] = true
147 }
148 expectedMap := r.Expected()
149 for name, el := range expectedMap {
150 if !presentSet[name] {
151 if err := r.Create(ctx, el); err != nil {
152 return err
153 }
154 }
155 }
156 for name, _ := range presentSet {
157 if _, ok := expectedMap[name]; !ok {
158 if err := r.Delete(ctx, name); err != nil {
159 return err
160 }
161 }
162 }
163 return nil
164}