Skip to content

Commit

Permalink
Merge pull request #9 from uselagoon/feature/adds_webservice
Browse files Browse the repository at this point in the history
Feature - adds endpoint for gathering facts
  • Loading branch information
bomoko authored Jul 4, 2023
2 parents ae8cb85 + 9176d9d commit 85d418f
Show file tree
Hide file tree
Showing 13 changed files with 971 additions and 228 deletions.
3 changes: 2 additions & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Build the manager binary
FROM golang:1.16-alpine as builder
FROM golang:1.18-alpine as builder

WORKDIR /workspace
# Copy the Go Modules manifests
Expand All @@ -13,6 +13,7 @@ RUN go mod download
COPY main.go main.go
#COPY api/ api/
COPY controllers/ controllers/
COPY internal/ internal/
COPY cmlib/ cmlib/

# Build
Expand Down
6 changes: 5 additions & 1 deletion PROJECT
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
domain: lagoon.sh
layout:
- go.kubebuilder.io/v3
projectName: watchconfigmaps
ProjectName: watchconfigmaps
repo: lagoon.sh/insights-remote
resources:
- controller: true
group: core
kind: ConfigMap
path: k8s.io/api/core/v1
version: v1
- controller: true
domain: lagoon.sh
kind: Namespace
version: v1
version: "3"
26 changes: 26 additions & 0 deletions config/rbac/role.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,29 @@ rules:
- get
- patch
- update
- apiGroups:
- ""
resources:
- namespaces
verbs:
- create
- delete
- get
- list
- patch
- update
- watch
- apiGroups:
- ""
resources:
- namespaces/finalizers
verbs:
- update
- apiGroups:
- ""
resources:
- namespaces/status
verbs:
- get
- patch
- update
25 changes: 23 additions & 2 deletions controllers/configmap_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ package controllers
import (
"context"
"fmt"
"k8s.io/apimachinery/pkg/types"
"strconv"
"time"

corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/json"
Expand All @@ -28,8 +32,6 @@ import (
"sigs.k8s.io/controller-runtime/pkg/event"
"sigs.k8s.io/controller-runtime/pkg/log"
"sigs.k8s.io/controller-runtime/pkg/predicate"
"strconv"
"time"
)

const InsightsLabel = "lagoon.sh/insightsType"
Expand All @@ -41,6 +43,8 @@ type LagoonInsightsMessage struct {
BinaryPayload map[string][]byte `json:"binaryPayload"`
Annotations map[string]string `json:"annotations"`
Labels map[string]string `json:"labels"`
Environment string `json:"environment"`
Project string `json:"project"`
}

// ConfigMapReconciler reconciles a ConfigMap object
Expand Down Expand Up @@ -74,11 +78,28 @@ func (r *ConfigMapReconciler) Reconcile(ctx context.Context, req ctrl.Request) (
return ctrl.Result{}, client.IgnoreNotFound(err)
}

var nameSpace corev1.Namespace
if err := r.Get(ctx, types.NamespacedName{Name: req.Namespace}, &nameSpace); err != nil {
log.Error(err, "Unable to load Namespace")
return ctrl.Result{}, client.IgnoreNotFound(err)
}

var environmentName string
var projectName string
labels := nameSpace.Labels
if _, ok := labels["lagoon.sh/environment"]; ok {
environmentName = labels["lagoon.sh/environment"]
} else if _, ok := labels["lagoon.sh/project"]; ok {
projectName = labels["lagoon.sh/project"]
}

var sendData = LagoonInsightsMessage{
Payload: configMap.Data,
BinaryPayload: configMap.BinaryData,
Annotations: configMap.Annotations,
Labels: configMap.Labels,
Environment: environmentName,
Project: projectName,
}

marshalledData, err := json.Marshal(sendData)
Expand Down
239 changes: 239 additions & 0 deletions controllers/namespace_controller.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,239 @@
/*
Copyright 2022.
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 controllers

import (
"context"
"errors"
"fmt"
corev1 "k8s.io/api/core/v1"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/selection"
"lagoon.sh/insights-remote/internal/tokens"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/event"
"sigs.k8s.io/controller-runtime/pkg/log"
"sigs.k8s.io/controller-runtime/pkg/predicate"
)

// NamespaceReconciler reconciles a Namespace object
type NamespaceReconciler struct {
client.Client
Scheme *runtime.Scheme
InsightsJWTSecret string
}

const insightsTokenLabel = "lagoon.sh/insights-token"

//+kubebuilder:rbac:groups=core,resources=namespaces,verbs=get;list;watch;create;update;patch;delete
//+kubebuilder:rbac:groups=core,resources=namespaces/status,verbs=get;update;patch
//+kubebuilder:rbac:groups=core,resources=namespaces/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.
// TODO(user): Modify the Reconcile function to compare the state specified by
// the Namespace object against the actual cluster state, and then
// perform operations to make the cluster state reflect the state specified by
// the user.
//
// For more details, check Reconcile and its Result here:
// - https://pkg.go.dev/sigs.k8s.io/[email protected]/pkg/reconcile
func (r *NamespaceReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
log := log.FromContext(ctx)

var ns corev1.Namespace
if err := r.Get(ctx, req.NamespacedName, &ns); err != nil {
log.Error(err, "Unable to load Namespace")
return ctrl.Result{}, client.IgnoreNotFound(err)
}

secretList := &corev1.SecretList{}

labelSelectorParameters, err := labels.NewRequirement(insightsTokenLabel, selection.Exists, []string{})

if err != nil {
log.Error(err, fmt.Sprintf("bad requirement: %v\n\n", err))
}

labelSelector := labels.NewSelector()
labelSelector = labelSelector.Add(*labelSelectorParameters)

listOptions := client.ListOptions{
LabelSelector: labelSelector,
Namespace: ns.GetName(),
}

err = r.Client.List(ctx, secretList, &listOptions)
if err != nil {
return ctrl.Result{}, err
}

foundItem := false
deleteSecretMessage := ""
for _, v := range secretList.Items {
log.Info(fmt.Sprintf("Found secret with name '%v' and namespace '%v'", v.Name, v.Namespace))
// let's verify this to make sure it looks good
if val, ok := v.Data["INSIGHTS_TOKEN"]; ok {
namespaceDetails, err := tokens.ValidateAndExtractNamespaceDetailsFromToken(r.InsightsJWTSecret, string(val))

if err != nil {
log.Error(err, "Unable to decode token")
return ctrl.Result{}, err
}
if namespaceDetails.Namespace != ns.GetName() {
deleteSecretMessage = fmt.Sprintf("Token is invalid - namespaces '%v'!='%v'.", ns.GetName(), namespaceDetails.Namespace)
}
foundItem = true
} else {
//we delete this secret straight
deleteSecretMessage = "key INSIGHTS_TOKEN does not exist. Secret is invalid."
}
if deleteSecretMessage != "" {
log.Info(fmt.Sprintf("Removing secret '%v':'%v' - %v", ns.GetName(), v.Name, deleteSecretMessage))
err = r.Client.Delete(ctx, &v)
if err != nil {
log.Error(err, "Unable to delete secret")
return ctrl.Result{}, err
}
}
}

if !foundItem { //let's create the token and secret
labels := ns.GetLabels()

environmentId, err := getValueFromMap(labels, "lagoon.sh/environmentId")
if err != nil {
return ctrl.Result{}, errors.New("Unable to find lagoon.sh/environmentId label in namespace " + ns.GetName() + " failed creating insights token")
}
projectName, err := getValueFromMap(labels, "lagoon.sh/project")
if err != nil {
return ctrl.Result{}, errors.New("Unable to find lagoon.sh/project label in namespace" + ns.GetName() + " failed creating insights token")
}
environmentName, err := getValueFromMap(labels, "lagoon.sh/environment")
if err != nil {
return ctrl.Result{}, errors.New("Unable to find lagoon.sh/environment label in namespace " + ns.GetName() + " failed creating insights token")
}

jwt, err := tokens.GenerateTokenForNamespace(r.InsightsJWTSecret, tokens.NamespaceDetails{
Namespace: ns.GetName(),
EnvironmentId: environmentId,
ProjectName: projectName,
EnvironmentName: environmentName,
})
if err != nil {
log.Error(err, "Unable to generate jwt for namespace '%v'", ns.GetName())
}

newSecret := corev1.Secret{
ObjectMeta: v1.ObjectMeta{
Name: "insights-token",
Namespace: ns.GetName(),
Labels: map[string]string{
insightsTokenLabel: "true",
"lagoon.sh/dynamic-secret": "insights-token", //This will ensure that this is dynamically mounted in pods
},
Annotations: map[string]string{},
},
Immutable: nil,
Data: map[string][]byte{
"INSIGHTS_TOKEN": []byte(jwt),
},
}
err = r.Client.Create(ctx, &newSecret)
if err != nil {
return ctrl.Result{}, err
}
}

return ctrl.Result{}, nil
}

func getValueFromMap(labels map[string]string, key string) (string, error) {
if v, okay := labels[key]; !okay {
return "", errors.New("key not found")
} else {
return v, nil
}
}

func activeNamespacePredicate(tokenTargetLabel string) predicate.Predicate {
return predicate.Funcs{
CreateFunc: func(event event.CreateEvent) bool {
labels := event.Object.GetLabels()
_, err := getValueFromMap(labels, "lagoon.sh/environmentId")
if err != nil {
return false
}
_, err = getValueFromMap(labels, "lagoon.sh/project")
if err != nil {
return false
}
_, err = getValueFromMap(labels, "lagoon.sh/environment")
if err != nil {
return false
}

if tokenTargetLabel != "" {
_, err = getValueFromMap(labels, tokenTargetLabel)
if err != nil {
return false
}
}

return true
},
DeleteFunc: func(deleteEvent event.DeleteEvent) bool {
return false
},
UpdateFunc: func(updateEvent event.UpdateEvent) bool {
labels := updateEvent.ObjectNew.GetLabels()
_, err := getValueFromMap(labels, "lagoon.sh/environmentId")
if err != nil {
return false
}
_, err = getValueFromMap(labels, "lagoon.sh/project")
if err != nil {
return false
}
_, err = getValueFromMap(labels, "lagoon.sh/environment")
if err != nil {
return false
}
if tokenTargetLabel != "" {
_, err = getValueFromMap(labels, tokenTargetLabel)
if err != nil {
return false
}
}
return true
},
GenericFunc: func(genericEvent event.GenericEvent) bool {
return false
},
}
}

// SetupWithManager sets up the controller with the Manager.
func (r *NamespaceReconciler) SetupWithManager(mgr ctrl.Manager, tokenTargetLabel string) error {
return ctrl.NewControllerManagedBy(mgr).
For(&corev1.Namespace{}).
WithEventFilter(activeNamespacePredicate(tokenTargetLabel)).
Complete(r)
}
Loading

0 comments on commit 85d418f

Please sign in to comment.