namespace_controller.go 11.8 KB
Newer Older
H
hongming 已提交
1
/*
H
hongming 已提交
2
Copyright 2019 The KubeSphere Authors.
H
hongming 已提交
3

H
hongming 已提交
4 5 6
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
H
hongming 已提交
7

H
hongming 已提交
8
    http://www.apache.org/licenses/LICENSE-2.0
H
hongming 已提交
9

H
hongming 已提交
10 11 12 13 14
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
H
hongming 已提交
15 16 17 18 19
*/

package namespace

import (
20
	"bytes"
H
hongming 已提交
21
	"context"
22
	"fmt"
R
Roland.Ma 已提交
23 24
	"reflect"

H
hongming 已提交
25
	"github.com/go-logr/logr"
J
Jeff 已提交
26
	appsv1 "k8s.io/api/apps/v1"
H
hongming 已提交
27
	corev1 "k8s.io/api/core/v1"
28
	rbacv1 "k8s.io/api/rbac/v1"
H
hongming 已提交
29 30
	"k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
31
	"k8s.io/apimachinery/pkg/labels"
H
hongming 已提交
32
	"k8s.io/apimachinery/pkg/types"
33
	"k8s.io/apimachinery/pkg/util/yaml"
H
hongming 已提交
34 35
	"k8s.io/client-go/kubernetes/scheme"
	"k8s.io/client-go/tools/record"
Z
zryfish 已提交
36 37 38 39 40
	ctrl "sigs.k8s.io/controller-runtime"
	"sigs.k8s.io/controller-runtime/pkg/client"
	"sigs.k8s.io/controller-runtime/pkg/controller"
	"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"

41 42
	iamv1alpha2 "kubesphere.io/kubesphere/pkg/apis/iam/v1alpha2"
	tenantv1alpha1 "kubesphere.io/kubesphere/pkg/apis/tenant/v1alpha1"
H
hongming 已提交
43
	"kubesphere.io/kubesphere/pkg/constants"
H
hongming 已提交
44
	controllerutils "kubesphere.io/kubesphere/pkg/controller/utils/controller"
H
hongming 已提交
45
	"kubesphere.io/kubesphere/pkg/utils/k8sutil"
H
hongming 已提交
46
	"kubesphere.io/kubesphere/pkg/utils/sliceutil"
H
hongming 已提交
47 48
)

H
hongming 已提交
49 50 51
const (
	controllerName = "namespace-controller"
)
H
hongming 已提交
52

H
hongming 已提交
53 54 55 56 57 58
// Reconciler reconciles a Namespace object
type Reconciler struct {
	client.Client
	Logger                  logr.Logger
	Recorder                record.EventRecorder
	MaxConcurrentReconciles int
H
hongming 已提交
59 60
}

H
hongming 已提交
61 62 63
func (r *Reconciler) SetupWithManager(mgr ctrl.Manager) error {
	if r.Client == nil {
		r.Client = mgr.GetClient()
H
hongming 已提交
64
	}
H
hongming 已提交
65 66
	if r.Logger == nil {
		r.Logger = ctrl.Log.WithName("controllers").WithName(controllerName)
H
hongming 已提交
67
	}
H
hongming 已提交
68 69 70 71 72 73 74 75 76 77 78 79 80
	if r.Recorder == nil {
		r.Recorder = mgr.GetEventRecorderFor(controllerName)
	}
	if r.MaxConcurrentReconciles <= 0 {
		r.MaxConcurrentReconciles = 1
	}
	return ctrl.NewControllerManagedBy(mgr).
		Named(controllerName).
		WithOptions(controller.Options{
			MaxConcurrentReconciles: r.MaxConcurrentReconciles,
		}).
		For(&corev1.Namespace{}).
		Complete(r)
H
hongming 已提交
81 82
}

H
hongming 已提交
83 84 85 86 87 88 89 90 91 92 93
// +kubebuilder:rbac:groups="",resources=namespaces,verbs=get;list;watch;create;update;patch;delete
// +kubebuilder:rbac:groups=tenant.kubesphere.io,resources=workspaces,verbs=get;list;watch
// +kubebuilder:rbac:groups=iam.kubesphere.io,resources=rolebases,verbs=get;list;watch
// +kubebuilder:rbac:groups=rbac.authorization.k8s.io,resources=roles,verbs=get;list;watch
// +kubebuilder:rbac:groups=rbac.authorization.k8s.io,resources=rolebindings,verbs=get;list;watch;create;update;patch;delete
func (r *Reconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {
	logger := r.Logger.WithValues("namespace", req.NamespacedName)
	rootCtx := context.Background()
	namespace := &corev1.Namespace{}
	if err := r.Get(rootCtx, req.NamespacedName, namespace); err != nil {
		return ctrl.Result{}, client.IgnoreNotFound(err)
H
hongming 已提交
94
	}
H
hongming 已提交
95 96
	// name of your custom finalizer
	finalizer := "finalizers.kubesphere.io/namespaces"
J
Jeff 已提交
97

H
hongming 已提交
98
	if namespace.ObjectMeta.DeletionTimestamp.IsZero() {
H
hongming 已提交
99 100
		// The object is not being deleted, so if it does not have our finalizer,
		// then lets add the finalizer and update the object.
H
hongming 已提交
101 102 103 104 105 106 107 108
		if !sliceutil.HasString(namespace.ObjectMeta.Finalizers, finalizer) {
			// create only once, ignore already exists error
			if err := r.initCreatorRoleBinding(rootCtx, logger, namespace); err != nil {
				return ctrl.Result{}, err
			}
			namespace.ObjectMeta.Finalizers = append(namespace.ObjectMeta.Finalizers, finalizer)
			if namespace.Labels == nil {
				namespace.Labels = make(map[string]string)
D
Duan Jiong 已提交
109
			}
H
hongming 已提交
110 111 112 113
			// used for NetworkPolicyPeer.NamespaceSelector
			namespace.Labels[constants.NamespaceLabelKey] = namespace.Name
			if err := r.Update(rootCtx, namespace); err != nil {
				return ctrl.Result{}, err
H
hongming 已提交
114
			}
J
Jeff 已提交
115
		}
H
hongming 已提交
116 117
	} else {
		// The object is being deleted
H
hongming 已提交
118 119 120
		if sliceutil.HasString(namespace.ObjectMeta.Finalizers, finalizer) {
			if err := r.deleteRouter(rootCtx, logger, namespace.Name); err != nil {
				return ctrl.Result{}, err
H
hongming 已提交
121 122
			}
			// remove our finalizer from the list and update it.
H
hongming 已提交
123
			namespace.ObjectMeta.Finalizers = sliceutil.RemoveString(namespace.ObjectMeta.Finalizers, func(item string) bool {
H
hongming 已提交
124 125
				return item == finalizer
			})
H
hongming 已提交
126 127
			if err := r.Update(rootCtx, namespace); err != nil {
				return ctrl.Result{}, err
H
hongming 已提交
128
			}
H
hongming 已提交
129
		}
H
hongming 已提交
130
		// Our finalizer has finished, so the reconciler can do nothing.
H
hongming 已提交
131
		return ctrl.Result{}, nil
H
hongming 已提交
132 133
	}

R
Roland.Ma 已提交
134 135
	// Bind to workspace if the namespace created by kubesphere
	_, hasWorkspaceLabel := namespace.Labels[tenantv1alpha1.WorkspaceLabel]
H
hongming 已提交
136 137 138 139 140 141 142 143 144 145 146 147 148
	// if the namespace doesn't have a label like kubefed.io/managed: "true" (single cluster environment)
	// or it has a label like kubefed.io/managed: "false"(multi-cluster environment), we set the owner reference filed.
	// Otherwise, kubefed controller will remove owner reference.
	kubefedManaged := namespace.Labels[constants.KubefedManagedLabel] == "true"
	if !kubefedManaged {
		if hasWorkspaceLabel {
			if err := r.bindWorkspace(rootCtx, logger, namespace); err != nil {
				return ctrl.Result{}, err
			}
		} else {
			if err := r.unbindWorkspace(rootCtx, logger, namespace); err != nil {
				return ctrl.Result{}, err
			}
H
hongming 已提交
149
		}
H
hongming 已提交
150
	}
R
Roland.Ma 已提交
151 152 153 154 155 156 157
	// Initialize roles for devops/project namespaces if created by kubesphere
	_, hasDevOpsProjectLabel := namespace.Labels[constants.DevOpsProjectLabelKey]
	if hasDevOpsProjectLabel || hasWorkspaceLabel {
		if err := r.initRoles(rootCtx, logger, namespace); err != nil {
			return ctrl.Result{}, err
		}
	}
H
hongming 已提交
158

H
hongming 已提交
159 160
	r.Recorder.Event(namespace, corev1.EventTypeNormal, controllerutils.SuccessSynced, controllerutils.MessageResourceSynced)
	return ctrl.Result{}, nil
H
hongming 已提交
161 162
}

H
hongming 已提交
163
func (r *Reconciler) bindWorkspace(ctx context.Context, logger logr.Logger, namespace *corev1.Namespace) error {
164
	workspace := &tenantv1alpha1.Workspace{}
H
hongming 已提交
165 166 167 168
	if err := r.Get(ctx, types.NamespacedName{Name: namespace.Labels[constants.WorkspaceLabelKey]}, workspace); err != nil {
		// remove existed owner reference if workspace not found
		if errors.IsNotFound(err) && k8sutil.IsControlledBy(namespace.OwnerReferences, tenantv1alpha1.ResourceKindWorkspace, "") {
			return r.unbindWorkspace(ctx, logger, namespace)
H
hongming 已提交
169
		}
H
hongming 已提交
170 171
		// skip if workspace not found
		return client.IgnoreNotFound(err)
H
hongming 已提交
172
	}
H
hongming 已提交
173
	// owner reference not match workspace label
H
hongming 已提交
174
	if !metav1.IsControlledBy(namespace, workspace) {
H
hongming 已提交
175 176 177 178
		namespace := namespace.DeepCopy()
		namespace.OwnerReferences = k8sutil.RemoveWorkspaceOwnerReference(namespace.OwnerReferences)
		if err := controllerutil.SetControllerReference(workspace, namespace, scheme.Scheme); err != nil {
			logger.Error(err, "set controller reference failed")
H
hongming 已提交
179 180
			return err
		}
H
hongming 已提交
181 182 183
		logger.V(4).Info("update namespace owner reference", "workspace", workspace.Name)
		if err := r.Update(ctx, namespace); err != nil {
			logger.Error(err, "update namespace failed")
H
hongming 已提交
184 185 186 187 188 189
			return err
		}
	}
	return nil
}

H
hongming 已提交
190
func (r *Reconciler) unbindWorkspace(ctx context.Context, logger logr.Logger, namespace *corev1.Namespace) error {
H
hongming 已提交
191 192
	if k8sutil.IsControlledBy(namespace.OwnerReferences, tenantv1alpha1.ResourceKindWorkspace, "") {
		namespace := namespace.DeepCopy()
H
hongming 已提交
193 194 195 196
		namespace.OwnerReferences = k8sutil.RemoveWorkspaceOwnerReference(namespace.OwnerReferences)
		logger.V(4).Info("remove owner reference", "workspace", namespace.Labels[constants.WorkspaceLabelKey])
		if err := r.Update(ctx, namespace); err != nil {
			logger.Error(err, "update owner reference failed")
H
hongming 已提交
197 198 199 200 201 202
			return err
		}
	}
	return nil
}

H
hongming 已提交
203
func (r *Reconciler) deleteRouter(ctx context.Context, logger logr.Logger, namespace string) error {
J
Jeff 已提交
204
	routerName := constants.IngressControllerPrefix + namespace
H
hongming 已提交
205

J
Jeff 已提交
206
	// delete service first
H
hongming 已提交
207 208
	service := corev1.Service{}
	err := r.Get(ctx, types.NamespacedName{Namespace: constants.IngressControllerNamespace, Name: routerName}, &service)
J
Jeff 已提交
209
	if err != nil {
H
hongming 已提交
210
		return client.IgnoreNotFound(err)
J
Jeff 已提交
211
	}
H
hongming 已提交
212 213
	logger.V(4).Info("delete router service", "namespace", service.Namespace, "service", service.Name)
	err = r.Delete(ctx, &service)
J
Jeff 已提交
214
	if err != nil {
H
hongming 已提交
215
		return client.IgnoreNotFound(err)
J
Jeff 已提交
216 217 218 219
	}

	// delete deployment
	deploy := appsv1.Deployment{}
H
hongming 已提交
220
	err = r.Get(ctx, types.NamespacedName{Namespace: constants.IngressControllerNamespace, Name: routerName}, &deploy)
J
Jeff 已提交
221
	if err != nil {
H
hongming 已提交
222
		logger.Error(err, "delete router deployment failed")
J
Jeff 已提交
223 224 225
		return err
	}

H
hongming 已提交
226 227
	logger.V(4).Info("delete router deployment", "namespace", deploy.Namespace, "deployment", deploy.Name)
	err = r.Delete(ctx, &deploy)
J
Jeff 已提交
228
	if err != nil {
H
hongming 已提交
229
		return client.IgnoreNotFound(err)
J
Jeff 已提交
230 231 232
	}

	return nil
233 234
}

H
hongming 已提交
235 236
func (r *Reconciler) initRoles(ctx context.Context, logger logr.Logger, namespace *corev1.Namespace) error {
	var templates iamv1alpha2.RoleBaseList
237 238 239 240 241 242 243 244 245
	var labelKey string
	// filtering initial roles by label
	if namespace.Labels[constants.DevOpsProjectLabelKey] != "" {
		// scope.kubesphere.io/devops: ""
		labelKey = fmt.Sprintf(iamv1alpha2.ScopeLabelFormat, iamv1alpha2.ScopeDevOps)
	} else {
		// scope.kubesphere.io/namespace: ""
		labelKey = fmt.Sprintf(iamv1alpha2.ScopeLabelFormat, iamv1alpha2.ScopeNamespace)
	}
H
hongming 已提交
246 247 248

	if err := r.List(ctx, &templates, client.MatchingLabelsSelector{Selector: labels.SelectorFromSet(labels.Set{labelKey: ""})}); err != nil {
		logger.Error(err, "list role bases failed")
249 250
		return err
	}
H
hongming 已提交
251
	for _, template := range templates.Items {
252
		var role rbacv1.Role
H
hongming 已提交
253
		if err := yaml.NewYAMLOrJSONDecoder(bytes.NewBuffer(template.Role.Raw), 1024).Decode(&role); err == nil && role.Kind == iamv1alpha2.ResourceKindRole {
254
			var old rbacv1.Role
H
hongming 已提交
255
			if err := r.Client.Get(ctx, types.NamespacedName{Namespace: namespace.Name, Name: role.Name}, &old); err != nil {
256 257
				if errors.IsNotFound(err) {
					role.Namespace = namespace.Name
H
hongming 已提交
258 259 260
					logger.V(4).Info("init builtin role", "role", role.Name)
					if err := r.Client.Create(ctx, &role); err != nil {
						logger.Error(err, "create role failed")
261 262 263 264 265 266 267 268 269 270 271 272 273
						return err
					}
					continue
				}
			}
			if !reflect.DeepEqual(role.Labels, old.Labels) ||
				!reflect.DeepEqual(role.Annotations, old.Annotations) ||
				!reflect.DeepEqual(role.Rules, old.Rules) {

				old.Labels = role.Labels
				old.Annotations = role.Annotations
				old.Rules = role.Rules

H
hongming 已提交
274 275 276
				logger.V(4).Info("update builtin role", "role", role.Name)
				if err := r.Update(ctx, &old); err != nil {
					logger.Error(err, "update role failed")
277 278
					return err
				}
279
			}
H
hongming 已提交
280 281
		} else if err != nil {
			logger.Error(fmt.Errorf("invalid role base found"), "init roles failed", "name", template.Name)
282 283 284 285 286
		}
	}
	return nil
}

H
hongming 已提交
287
func (r *Reconciler) initCreatorRoleBinding(ctx context.Context, logger logr.Logger, namespace *corev1.Namespace) error {
H
hongming 已提交
288 289 290 291 292
	creator := namespace.Annotations[constants.CreatorAnnotationKey]
	if creator == "" {
		return nil
	}
	var user iamv1alpha2.User
H
hongming 已提交
293 294 295 296 297 298 299
	if err := r.Get(ctx, types.NamespacedName{Name: creator}, &user); err != nil {
		return client.IgnoreNotFound(err)
	}
	creatorRoleBinding := newCreatorRoleBinding(creator, namespace.Name)
	logger.V(4).Info("init creator role binding", "creator", user.Name)
	if err := r.Client.Create(ctx, creatorRoleBinding); err != nil {
		if errors.IsAlreadyExists(err) {
300
			return nil
H
hongming 已提交
301
		}
H
hongming 已提交
302
		logger.Error(err, "create role binding failed")
H
hongming 已提交
303 304
		return err
	}
H
hongming 已提交
305 306
	return nil
}
H
hongming 已提交
307

H
hongming 已提交
308 309
func newCreatorRoleBinding(creator string, namespace string) *rbacv1.RoleBinding {
	return &rbacv1.RoleBinding{
H
hongming 已提交
310 311 312
		ObjectMeta: metav1.ObjectMeta{
			Name:      fmt.Sprintf("%s-%s", creator, iamv1alpha2.NamespaceAdmin),
			Labels:    map[string]string{iamv1alpha2.UserReferenceLabel: creator},
H
hongming 已提交
313
			Namespace: namespace,
H
hongming 已提交
314 315 316 317 318 319 320 321 322 323
		},
		RoleRef: rbacv1.RoleRef{
			APIGroup: rbacv1.GroupName,
			Kind:     iamv1alpha2.ResourceKindRole,
			Name:     iamv1alpha2.NamespaceAdmin,
		},
		Subjects: []rbacv1.Subject{
			{
				Name:     creator,
				Kind:     iamv1alpha2.ResourceKindUser,
324 325
				APIGroup: rbacv1.GroupName,
			},
H
hongming 已提交
326 327
		},
	}
J
Jeff 已提交
328
}