Skip to content

Commit

Permalink
feat: support idling messages from core
Browse files Browse the repository at this point in the history
  • Loading branch information
shreddedbacon committed Feb 23, 2024
1 parent 0e99812 commit 862b4f7
Show file tree
Hide file tree
Showing 10 changed files with 354 additions and 6 deletions.
1 change: 1 addition & 0 deletions apis/lagoon/v1beta1/lagoonmessaging_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ type LagoonMessage struct {
Type string `json:"type,omitempty"`
Namespace string `json:"namespace,omitempty"`
Meta *LagoonLogMeta `json:"meta,omitempty"`
Idled bool `json:"idled,omitempty"`
// BuildInfo *LagoonBuildInfo `json:"buildInfo,omitempty"`
}

Expand Down
34 changes: 28 additions & 6 deletions apis/lagoon/v1beta1/lagoontask_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,12 +75,34 @@ func (b TaskType) String() string {
type LagoonTaskSpec struct {
// INSERT ADDITIONAL SPEC FIELDS - desired state of cluster
// Important: Run "make" to regenerate code after modifying this file
Key string `json:"key,omitempty"`
Task LagoonTaskInfo `json:"task,omitempty"`
Project LagoonTaskProject `json:"project,omitempty"`
Environment LagoonTaskEnvironment `json:"environment,omitempty"`
Misc *LagoonMiscInfo `json:"misc,omitempty"`
AdvancedTask *LagoonAdvancedTaskInfo `json:"advancedTask,omitempty"`
Key string `json:"key,omitempty"`
Task LagoonTaskInfo `json:"task,omitempty"`
Project LagoonTaskProject `json:"project,omitempty"`
Environment LagoonTaskEnvironment `json:"environment,omitempty"`
Misc *LagoonMiscInfo `json:"misc,omitempty"`
AdvancedTask *LagoonAdvancedTaskInfo `json:"advancedTask,omitempty"`
LagoonService LagoonServiceInfo `json:"lagoonService,omitempty"`
Idling LagoonIdling `json:"lagoonIdling,omitempty"`
}

// TaskType const for the status type
type ServiceState string

// These are valid conditions of a job.
const (
StateStop ServiceState = "stop"
StateStart ServiceState = "start"
StateRestart ServiceState = "restart"
)

type LagoonServiceInfo struct {
Name string `json:"name,omitempty"`
State ServiceState `json:"state,omitempty"`
}

type LagoonIdling struct {
Idle bool `json:"idle,omitempty"`
ForceScale bool `json:"forceScale,omitempty"`
}

// LagoonTaskInfo defines what a task can use to communicate with Lagoon via SSH/API.
Expand Down
32 changes: 32 additions & 0 deletions apis/lagoon/v1beta1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions config/crd/bases/crd.lagoon.sh_lagoonbuilds.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -302,6 +302,8 @@ spec:
description: LagoonMessage is used for sending build info back to
Lagoon messaging queue to update the environment or deployment
properties:
idled:
type: boolean
meta:
description: LagoonLogMeta is the metadata that is used by logging
in Lagoon.
Expand Down
17 changes: 17 additions & 0 deletions config/crd/bases/crd.lagoon.sh_lagoontasks.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,21 @@ spec:
description: 'INSERT ADDITIONAL SPEC FIELDS - desired state of cluster
Important: Run "make" to regenerate code after modifying this file'
type: string
lagoonIdling:
properties:
forceScale:
type: boolean
idle:
type: boolean
type: object
lagoonService:
properties:
name:
type: string
state:
description: TaskType const for the status type
type: string
type: object
misc:
description: LagoonMiscInfo defines the resource or backup information
for a misc task.
Expand Down Expand Up @@ -284,6 +299,8 @@ spec:
description: LagoonMessage is used for sending build info back to
Lagoon messaging queue to update the environment or deployment
properties:
idled:
type: boolean
meta:
description: LagoonLogMeta is the metadata that is used by logging
in Lagoon.
Expand Down
104 changes: 104 additions & 0 deletions controllers/namespace/namespace.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
/*
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 namespace

import (
"context"
"encoding/json"
"fmt"
"strconv"

"github.com/go-logr/logr"
lagoonv1beta1 "github.com/uselagoon/remote-controller/apis/lagoon/v1beta1"
"github.com/uselagoon/remote-controller/internal/messenger"
"k8s.io/apimachinery/pkg/runtime"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"

corev1 "k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
)

// NamespaceReconciler reconciles idling
type NamespaceReconciler struct {
client.Client
Log logr.Logger
Scheme *runtime.Scheme
EnableMQ bool
Messaging *messenger.Messenger
LagoonTargetName string
}

func (r *NamespaceReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
opLog := r.Log.WithValues("namespace", req.NamespacedName)

var namespace corev1.Namespace
if err := r.Get(ctx, req.NamespacedName, &namespace); err != nil {
return ctrl.Result{}, ignoreNotFound(err)
}

// this would be nice to be a lagoon label :)
if val, ok := namespace.ObjectMeta.Labels["idling.amazee.io/idled"]; ok {
idled, _ := strconv.ParseBool(val)
opLog.Info(fmt.Sprintf("environment %s idle state %t", namespace.Name, idled))
if r.EnableMQ {
var projectName, environmentName string
if p, ok := namespace.ObjectMeta.Labels["lagoon.sh/project"]; ok {
projectName = p
}
if e, ok := namespace.ObjectMeta.Labels["lagoon.sh/environment"]; ok {
environmentName = e
}
msg := lagoonv1beta1.LagoonMessage{
Type: "idling",
Namespace: namespace.Name,
Meta: &lagoonv1beta1.LagoonLogMeta{
Environment: environmentName,
Project: projectName,
Cluster: r.LagoonTargetName,
},
Idled: idled,
}
msgBytes, err := json.Marshal(msg)
if err != nil {
opLog.Error(err, "Unable to encode message as JSON")
}
// @TODO: if we can't publish the message because for some reason, log the error and move on
// this may result in the state being out of sync in lagoon but eventually will be consistent
if err := r.Messaging.Publish("lagoon-tasks:controller", msgBytes); err != nil {
return ctrl.Result{}, nil
}
}
return ctrl.Result{}, nil
}
return ctrl.Result{}, nil
}

// SetupWithManager sets up the watch on the namespace resource with an event filter (see predicates.go)
func (r *NamespaceReconciler) SetupWithManager(mgr ctrl.Manager) error {
return ctrl.NewControllerManagedBy(mgr).
For(&corev1.Namespace{}).
WithEventFilter(NamespacePredicates{}).
Complete(r)
}

// will ignore not found errors
func ignoreNotFound(err error) error {
if apierrors.IsNotFound(err) {
return nil
}
return err
}
38 changes: 38 additions & 0 deletions controllers/namespace/predicates.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package namespace

import (
"sigs.k8s.io/controller-runtime/pkg/event"
"sigs.k8s.io/controller-runtime/pkg/predicate"
)

// NamespacePredicates defines the funcs for predicates
type NamespacePredicates struct {
predicate.Funcs
}

// Create is used when a creation event is received by the controller.
func (n NamespacePredicates) Create(e event.CreateEvent) bool {
return false
}

// Delete is used when a deletion event is received by the controller.
func (n NamespacePredicates) Delete(e event.DeleteEvent) bool {
return false
}

// Update is used when an update event is received by the controller.
func (n NamespacePredicates) Update(e event.UpdateEvent) bool {
if oldIdled, ok := e.ObjectOld.GetLabels()["idling.amazee.io/idled"]; ok {
if newIdled, ok := e.ObjectNew.GetLabels()["idling.amazee.io/idled"]; ok {
if oldIdled != newIdled {
return true
}
}
}
return false
}

// Generic is used when any other event is received by the controller.
func (n NamespacePredicates) Generic(e event.GenericEvent) bool {
return false
}
33 changes: 33 additions & 0 deletions internal/messenger/consumer.go
Original file line number Diff line number Diff line change
Expand Up @@ -416,6 +416,39 @@ func (m *Messenger) Consumer(targetName string) { //error {
message.Ack(false) // ack to remove from queue
return
}
case "deploytarget:environment:idling":
opLog.Info(
fmt.Sprintf(
"Received environment idling request for project %s, environment %s - %s",
jobSpec.Project.Name,
jobSpec.Environment.Name,
namespace,
),
)
// idle or unidle an environment, optionally forcible scale it so it can't be unidled by the ingress
err := m.ScaleOrIdleEnvironment(ctx, opLog, namespace, jobSpec.Idling.Idle, jobSpec.Idling.ForceScale)
if err != nil {
//@TODO: send msg back to lagoon and update task to failed?
message.Ack(false) // ack to remove from queue
return
}
case "deploytarget:environment:service":
opLog.Info(
fmt.Sprintf(
"Received environment service request for project %s, environment %s service %s - %s",
jobSpec.Project.Name,
jobSpec.Environment.Name,
jobSpec.LagoonService.Name,
namespace,
),
)
// idle an environment, optionally forcible scale it so it can't be unidled by the ingress
err := m.EnvironmentServiceState(ctx, opLog, namespace, jobSpec.LagoonService.Name, jobSpec.LagoonService.State)
if err != nil {
//@TODO: send msg back to lagoon and update task to failed?
message.Ack(false) // ack to remove from queue
return
}
default:
// if we get something that we don't know about, spit out the entire message
opLog.Info(
Expand Down
Loading

0 comments on commit 862b4f7

Please sign in to comment.