Files
teleport/integrations/operator/controllers/resources/role_controller.go
T
Hugo Shaka 9e1f53d33a Move operator into integrations/ (#22618)
* Move operator into `integrations/`

* fixup! Move operator into `integrations/`

* fixup! fixup! Move operator into `integrations/`

* fixup! fixup! fixup! Move operator into `integrations/`

* fixup! fixup! fixup! fixup! Move operator into `integrations/`
2023-03-06 17:59:02 +00:00

171 lines
6.6 KiB
Go

/*
Copyright 2022 Gravitational, Inc.
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
http://www.apache.org/licenses/LICENSE-2.0
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.
*/
package resources
import (
"context"
"fmt"
"github.com/gravitational/trace"
"k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
ctrl "sigs.k8s.io/controller-runtime"
kclient "sigs.k8s.io/controller-runtime/pkg/client"
"github.com/gravitational/teleport/api/types"
v5 "github.com/gravitational/teleport/integrations/operator/apis/resources/v5"
"github.com/gravitational/teleport/integrations/operator/sidecar"
)
const teleportRoleKind = "TeleportRole"
// TODO(for v12): Have the Role controller to use the generic Teleport reconciler
// This means we'll have to move back to a statically typed client.
// This will require removing the crdgen hack, fixing TeleportRole JSON serialization
var teleportRoleGVKV5 = schema.GroupVersionKind{
Group: v5.GroupVersion.Group,
Version: v5.GroupVersion.Version,
Kind: teleportRoleKind,
}
// RoleReconciler reconciles a TeleportRole object
type RoleReconciler struct {
kclient.Client
Scheme *runtime.Scheme
TeleportClientAccessor sidecar.ClientAccessor
}
//+kubebuilder:rbac:groups=resources.teleport.dev,resources=roles,verbs=get;list;watch;create;update;patch;delete
//+kubebuilder:rbac:groups=resources.teleport.dev,resources=roles/status,verbs=get;update;patch
//+kubebuilder:rbac:groups=resources.teleport.dev,resources=roles/finalizers,verbs=update
// Reconcile is part of the main kubernetes reconciliation loop which aims to
// move the current state of the cluster closer to the desired state.
//
// For more details, check Reconcile and its Result here:
// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.11.0/pkg/reconcile
func (r *RoleReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
// The TeleportRole OpenAPI spec does not validate typing of Label fields like `node_labels`.
// This means we can receive invalid data, by default it won't be unmarshalled properly and will crash the operator.
// To handle this more gracefully we unmarshall first in an unstructured object.
// The unstructured object will be converted later to a typed one, in r.UpsertExternal.
// See `/operator/crdgen/schemagen.go` and https://github.com/gravitational/teleport/issues/15204 for context.
// TODO: (Check how to handle multiple versions)
obj := getUnstructuredObjectFromGVK(teleportRoleGVKV5)
return ResourceBaseReconciler{
Client: r.Client,
DeleteExternal: r.Delete,
UpsertExternal: r.Upsert,
}.Do(ctx, req, obj)
}
// SetupWithManager sets up the controller with the Manager.
func (r *RoleReconciler) SetupWithManager(mgr ctrl.Manager) error {
// The TeleportRole OpenAPI spec does not validate typing of Label fields like `node_labels`.
// This means we can receive invalid data, by default it won't be unmarshalled properly and will crash the operator
// To handle this more gracefully we unmarshall first in an unstructured object.
// The unstructured object will be converted later to a typed one, in r.UpsertExternal.
// See `/operator/crdgen/schemagen.go` and https://github.com/gravitational/teleport/issues/15204 for context
// TODO: (Check how to handle multiple versions)
obj := getUnstructuredObjectFromGVK(teleportRoleGVKV5)
return ctrl.NewControllerManagedBy(mgr).
For(obj).
Complete(r)
}
func (r *RoleReconciler) Delete(ctx context.Context, obj kclient.Object) error {
teleportClient, err := r.TeleportClientAccessor(ctx)
if err != nil {
return trace.Wrap(err)
}
return teleportClient.DeleteRole(ctx, obj.GetName())
}
func (r *RoleReconciler) Upsert(ctx context.Context, obj kclient.Object) error {
// We receive an unstructured object. We convert it to a typed TeleportRole object and gracefully handle errors.
u, ok := obj.(*unstructured.Unstructured)
if !ok {
return fmt.Errorf("failed to convert Object into resource object: %T", obj)
}
k8sResource := &v5.TeleportRole{}
// If an error happens we want to put it in status.conditions before returning.
err := runtime.DefaultUnstructuredConverter.FromUnstructuredWithValidation(
u.Object,
k8sResource, true, /* returnUnknownFields */
)
newStructureCondition := getStructureConditionFromError(err)
meta.SetStatusCondition(k8sResource.StatusConditions(), newStructureCondition)
if err != nil {
silentUpdateStatus(ctx, r.Client, k8sResource)
return trace.Wrap(err)
}
// Converting the Kubernetes resource into a Teleport one, checking potential ownership issues.
teleportResource := k8sResource.ToTeleport()
teleportClient, err := r.TeleportClientAccessor(ctx)
if err != nil {
silentUpdateStatus(ctx, r.Client, k8sResource)
return trace.Wrap(err)
}
existingResource, err := teleportClient.GetRole(ctx, teleportResource.GetName())
if err != nil && !trace.IsNotFound(err) {
silentUpdateStatus(ctx, r.Client, k8sResource)
return trace.Wrap(err)
}
newOwnershipCondition, isOwned := checkOwnership(existingResource)
meta.SetStatusCondition(k8sResource.StatusConditions(), newOwnershipCondition)
if !isOwned {
silentUpdateStatus(ctx, r.Client, k8sResource)
return trace.AlreadyExists("unowned resource '%s' already exists", existingResource.GetName())
}
r.addTeleportResourceOrigin(teleportResource)
// If an error happens we want to put it in status.conditions before returning.
err = teleportClient.UpsertRole(ctx, teleportResource)
newReconciliationCondition := getReconciliationConditionFromError(err)
meta.SetStatusCondition(k8sResource.StatusConditions(), newReconciliationCondition)
if err != nil {
silentUpdateStatus(ctx, r.Client, k8sResource)
return trace.Wrap(err)
}
// We update the status conditions on exit
return trace.Wrap(r.Status().Update(ctx, k8sResource))
}
func (r *RoleReconciler) addTeleportResourceOrigin(resource types.Role) {
metadata := resource.GetMetadata()
if metadata.Labels == nil {
metadata.Labels = make(map[string]string)
}
metadata.Labels[types.OriginLabel] = types.OriginKubernetes
resource.SetMetadata(metadata)
}
func getUnstructuredObjectFromGVK(gvk schema.GroupVersionKind) *unstructured.Unstructured {
obj := unstructured.Unstructured{}
obj.SetGroupVersionKind(gvk)
return &obj
}