From fd9198233349b1c0d077591c854b5087dfcafee3 Mon Sep 17 00:00:00 2001 From: Marco Dinis Date: Tue, 28 Jun 2022 15:31:50 +0100 Subject: [PATCH] improve docs and simplify const usage --- lib/kube/proxy/forwarder.go | 22 ++++++------ lib/kube/proxy/roundtrip.go | 11 +++--- operator/README.md | 13 ++++--- operator/apis/resources/constants.go | 22 ++++++++++++ .../apis/resources/v2/groupversion_info.go | 4 ++- operator/apis/resources/v2/user_types.go | 11 +++--- .../apis/resources/v5/groupversion_info.go | 4 ++- operator/apis/resources/v5/role_types.go | 11 +++--- operator/controllers/resources/reconciler.go | 35 ++++++++++++++----- .../controllers/resources/role_controller.go | 2 +- .../controllers/resources/user_controller.go | 2 +- 11 files changed, 88 insertions(+), 49 deletions(-) create mode 100644 operator/apis/resources/constants.go diff --git a/lib/kube/proxy/forwarder.go b/lib/kube/proxy/forwarder.go index 1aa3bb60d8f42..8e970b2dd4a75 100644 --- a/lib/kube/proxy/forwarder.go +++ b/lib/kube/proxy/forwarder.go @@ -1527,12 +1527,11 @@ func (f *Forwarder) catchAll(ctx *authContext, w http.ResponseWriter, req *http. func (f *Forwarder) getExecutor(ctx authContext, sess *clusterSession, req *http.Request) (remotecommand.Executor, error) { upgradeRoundTripper := NewSpdyRoundTripperWithDialer(roundTripperConfig{ - ctx: req.Context(), - authCtx: ctx, - dial: sess.DialWithContext, - tlsConfig: sess.tlsConfig, - followRedirects: true, - pingPeriod: f.cfg.ConnPingPeriod, + ctx: req.Context(), + authCtx: ctx, + dial: sess.DialWithContext, + tlsConfig: sess.tlsConfig, + pingPeriod: f.cfg.ConnPingPeriod, }) rt := http.RoundTripper(upgradeRoundTripper) if sess.creds != nil { @@ -1547,12 +1546,11 @@ func (f *Forwarder) getExecutor(ctx authContext, sess *clusterSession, req *http func (f *Forwarder) getDialer(ctx authContext, sess *clusterSession, req *http.Request) (httpstream.Dialer, error) { upgradeRoundTripper := NewSpdyRoundTripperWithDialer(roundTripperConfig{ - ctx: req.Context(), - authCtx: ctx, - dial: sess.DialWithContext, - tlsConfig: sess.tlsConfig, - followRedirects: true, - pingPeriod: f.cfg.ConnPingPeriod, + ctx: req.Context(), + authCtx: ctx, + dial: sess.DialWithContext, + tlsConfig: sess.tlsConfig, + pingPeriod: f.cfg.ConnPingPeriod, }) rt := http.RoundTripper(upgradeRoundTripper) if sess.creds != nil { diff --git a/lib/kube/proxy/roundtrip.go b/lib/kube/proxy/roundtrip.go index 8598b5df45905..3e912a1074e75 100644 --- a/lib/kube/proxy/roundtrip.go +++ b/lib/kube/proxy/roundtrip.go @@ -79,12 +79,11 @@ var _ utilnet.Dialer = &SpdyRoundTripper{} type DialWithContext func(context context.Context, network, address string) (net.Conn, error) type roundTripperConfig struct { - ctx context.Context - authCtx authContext - dial DialWithContext - tlsConfig *tls.Config - followRedirects bool - pingPeriod time.Duration + ctx context.Context + authCtx authContext + dial DialWithContext + tlsConfig *tls.Config + pingPeriod time.Duration } // NewSpdyRoundTripperWithDialer creates a new SpdyRoundTripper that will use diff --git a/operator/README.md b/operator/README.md index eec602df52bdb..0069c643acf1b 100644 --- a/operator/README.md +++ b/operator/README.md @@ -77,7 +77,7 @@ The first two steps above may change the object's state in K8S. If they do, we u ### Requirementes #### K8S cluster -You can use `minikube` if you don't have a cluster yet. +If you don't have a cluster yet, you can start one by using the [minikube](https://minikube.sigs.k8s.io/docs/start/) tool. #### Operator's docker image You can obtain the docker image by pulling from `quay.io/gravitational/teleport` @@ -93,8 +93,6 @@ We also need the following tools: `helm`, `kubectl` and `docker` ### Running the operator -Start `minikube` with `minikube start`. - Set the `TELEPORT_PROJECT` to the full path to your teleport's project checked out at `marco/plugins-teleport-operator-charts`. Install the helm chart: @@ -118,9 +116,9 @@ If it doesn't, check the errors. Now, we want access to two configuration tools using a Web UI: K8S UI and Teleport UI. -First, let's create a tunnel using minikube: `minikube tunnel` (this command runs is foreground, open another terminal for the remaining commands). +If you are using `minikube`, you have to create a tunnel with: `minikube tunnel` (this command runs is foreground, open another terminal for the remaining commands). -Now, let's create a new Teleport User and login in the web UI: +Create a new Teleport User and login in the web UI: ```bash PROXY_POD=$(kubectl get po -l app=teleport-cluster -o jsonpath='{.items[0].metadata.name}') kubectl exec $PROXY_POD teleport -- tctl users add --roles=access,editor teleoperator @@ -129,9 +127,10 @@ TP_CLUSTER_IP=$(kubectl get service teleport-cluster -o jsonpath='{ .status.load echo "https://${TP_CLUSTER_IP}/web/invite/" ``` -As for the K8S UI, you only need to run `minikube dashboard`. +Open the Kubernetes Dashboard (`minikube dashboard` if your cluster was created by `minikube`) and switch to `teleport-cluster` namespace. +Your resources will appear under the Custom Resources menu. -After this, you should be able to manage users and roles using, for example, `kubectl`. +You can manage users and roles using to usual kubernetes tools, for example, `kubectl`. As an example, create the following file (`roles.yaml`) and then apply it: ```yaml diff --git a/operator/apis/resources/constants.go b/operator/apis/resources/constants.go new file mode 100644 index 0000000000000..17fccea723a1e --- /dev/null +++ b/operator/apis/resources/constants.go @@ -0,0 +1,22 @@ +/* +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 + +const ( + GroupName = "resources.teleport.dev" + DescriptionKey = "description" +) diff --git a/operator/apis/resources/v2/groupversion_info.go b/operator/apis/resources/v2/groupversion_info.go index 3e2b4e0db4ce8..fb9bab2f8bee7 100644 --- a/operator/apis/resources/v2/groupversion_info.go +++ b/operator/apis/resources/v2/groupversion_info.go @@ -22,11 +22,13 @@ package v2 import ( "k8s.io/apimachinery/pkg/runtime/schema" "sigs.k8s.io/controller-runtime/pkg/scheme" + + "github.com/gravitational/teleport/operator/apis/resources" ) var ( // GroupVersion is group version used to register these objects - GroupVersion = schema.GroupVersion{Group: "resources.teleport.dev", Version: "v2"} + GroupVersion = schema.GroupVersion{Group: resources.GroupName, Version: "v2"} // SchemeBuilder is used to add go types to the GroupVersionKind scheme SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion} diff --git a/operator/apis/resources/v2/user_types.go b/operator/apis/resources/v2/user_types.go index 107b152db0215..e40440cc91176 100644 --- a/operator/apis/resources/v2/user_types.go +++ b/operator/apis/resources/v2/user_types.go @@ -20,9 +20,12 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "github.com/gravitational/teleport/api/types" + "github.com/gravitational/teleport/operator/apis/resources" ) -const DescriptionKey = "description" +func init() { + SchemeBuilder.Register(&User{}, &UserList{}) +} // EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN! // NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized. @@ -57,10 +60,6 @@ type UserList struct { Items []User `json:"items"` } -func init() { - SchemeBuilder.Register(&User{}, &UserList{}) -} - func (u User) ToTeleport() types.User { return &types.UserV2{ Kind: types.KindUser, @@ -68,7 +67,7 @@ func (u User) ToTeleport() types.User { Metadata: types.Metadata{ Name: u.Name, Labels: u.Labels, - Description: u.Annotations[DescriptionKey], + Description: u.Annotations[resources.DescriptionKey], }, Spec: types.UserSpecV2(u.Spec), } diff --git a/operator/apis/resources/v5/groupversion_info.go b/operator/apis/resources/v5/groupversion_info.go index d2ae4a3184c1f..a768aa83b92a4 100644 --- a/operator/apis/resources/v5/groupversion_info.go +++ b/operator/apis/resources/v5/groupversion_info.go @@ -22,11 +22,13 @@ package v5 import ( "k8s.io/apimachinery/pkg/runtime/schema" "sigs.k8s.io/controller-runtime/pkg/scheme" + + "github.com/gravitational/teleport/operator/apis/resources" ) var ( // GroupVersion is group version used to register these objects - GroupVersion = schema.GroupVersion{Group: "resources.teleport.dev", Version: "v5"} + GroupVersion = schema.GroupVersion{Group: resources.GroupName, Version: "v5"} // SchemeBuilder is used to add go types to the GroupVersionKind scheme SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion} diff --git a/operator/apis/resources/v5/role_types.go b/operator/apis/resources/v5/role_types.go index cb9a13e3ef290..2c8f94f56daec 100644 --- a/operator/apis/resources/v5/role_types.go +++ b/operator/apis/resources/v5/role_types.go @@ -20,9 +20,12 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "github.com/gravitational/teleport/api/types" + "github.com/gravitational/teleport/operator/apis/resources" ) -const DescriptionKey = "description" +func init() { + SchemeBuilder.Register(&Role{}, &RoleList{}) +} // EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN! // NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized. @@ -57,10 +60,6 @@ type RoleList struct { Items []Role `json:"items"` } -func init() { - SchemeBuilder.Register(&Role{}, &RoleList{}) -} - func (r Role) ToTeleport() types.Role { return &types.RoleV5{ Kind: types.KindRole, @@ -68,7 +67,7 @@ func (r Role) ToTeleport() types.Role { Metadata: types.Metadata{ Name: r.Name, Labels: r.Labels, - Description: r.Annotations[DescriptionKey], + Description: r.Annotations[resources.DescriptionKey], }, Spec: types.RoleSpecV5(r.Spec), } diff --git a/operator/controllers/resources/reconciler.go b/operator/controllers/resources/reconciler.go index 4648227081da8..6329fea7a5c1f 100644 --- a/operator/controllers/resources/reconciler.go +++ b/operator/controllers/resources/reconciler.go @@ -42,6 +42,30 @@ type ResourceBaseReconciler struct { UpsertExternal UpsertExternal } +/* +Do will receive an update request and reconcile the resource. + +When an event arrives we must propagate that change into the Teleport cluster. +We have two types of events: update/create and delete. + +For creating/updating we check if the resource exists in Teleport +- if it does, we update it +- otherwise we create it +Always using the state of the resource in the cluster as the source of truth. + +For deleting, the recommendation is to use finalizers. +Finalizers allow us to map an external resource to a kubernetes resource. +So, when we create or update a resource, we add our own finalizer to the kubernetes resource list of finalizers. + +For a delete event which has our finalizer: the resource is deleted in Teleport. +If it doesn't have the finalizer, we do nothing. + +---- + +Every time we update a resource in Kubernetes (adding finalizers or the OriginLabel), we end the reconciliation process. +Afterwards, we receive the request again and we progress to the next step. +This allow us to progress with smaller changes and avoid a long-running reconciliation. +*/ func (r ResourceBaseReconciler) Do(ctx context.Context, req ctrl.Request, obj kclient.Object) (ctrl.Result, error) { // https://sdk.operatorframework.io/docs/building-operators/golang/advanced-topics/#external-resources log := log.FromContext(ctx).WithValues("namespacedname", req.NamespacedName) @@ -101,16 +125,11 @@ func hasOriginLabel(obj kclient.Object) bool { return false } - for k := range obj.GetLabels() { - if k == types.OriginLabel { - return true - } - } - - return false + _, ok := obj.GetLabels()[types.OriginLabel] + return ok } -func addOriginLabel(ctx context.Context, k8sClient kclient.Client, obj kclient.Object) error { +func addOriginLabelToK8SObject(ctx context.Context, k8sClient kclient.Client, obj kclient.Object) error { k8sObjLabels := obj.GetLabels() if k8sObjLabels == nil { k8sObjLabels = make(map[string]string) diff --git a/operator/controllers/resources/role_controller.go b/operator/controllers/resources/role_controller.go index 89cd9fce7edf6..ac7996e1506e6 100644 --- a/operator/controllers/resources/role_controller.go +++ b/operator/controllers/resources/role_controller.go @@ -80,7 +80,7 @@ func (r *RoleReconciler) Upsert(ctx context.Context, obj kclient.Object) error { return trace.Wrap(err) } if trace.IsNotFound(err) && !hasOriginLabel(obj) { - return addOriginLabel(ctx, r.Client, obj) + return addOriginLabelToK8SObject(ctx, r.Client, obj) } return r.TeleportClient.UpsertRole(ctx, teleportResource) diff --git a/operator/controllers/resources/user_controller.go b/operator/controllers/resources/user_controller.go index 8e731a3c3e4ff..c9ed7a13bc419 100644 --- a/operator/controllers/resources/user_controller.go +++ b/operator/controllers/resources/user_controller.go @@ -81,7 +81,7 @@ func (r *UserReconciler) Upsert(ctx context.Context, obj kclient.Object) error { } if trace.IsNotFound(err) { if !hasOriginLabel(obj) { - return addOriginLabel(ctx, r.Client, obj) + return addOriginLabelToK8SObject(ctx, r.Client, obj) } return trace.Wrap(r.TeleportClient.CreateUser(ctx, teleportResource))