Skip to content

Commit

Permalink
feat: support idling and unidling from api with idled status on envir…
Browse files Browse the repository at this point in the history
…onment
  • Loading branch information
shreddedbacon committed Mar 27, 2024
1 parent 1ef8e57 commit 4962200
Show file tree
Hide file tree
Showing 9 changed files with 299 additions and 9 deletions.
10 changes: 8 additions & 2 deletions node-packages/commons/src/tasks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1196,9 +1196,7 @@ export const createMiscTask = async function(taskData: any) {
data: { project }
} = taskData;

// handle any controller based misc tasks
let updatedKey = `deploytarget:${key}`;
let taskId = 'misc-kubernetes';
// determine the deploy target (openshift/kubernetes) for the task to go to
// we get this from the environment
const result = await getOpenShiftInfoForEnvironment(taskData.data.environment.id);
Expand Down Expand Up @@ -1373,6 +1371,14 @@ export const createMiscTask = async function(taskData: any) {
// build cancellation is just a standard unmodified message
miscTaskData.misc = taskData.data.build
break;
case 'deploytarget:environment:idling':
// environment idling is used to handle idling or unidling of an an environment
miscTaskData.idling = taskData.data.idling
break;
case 'deploytarget:environment:service':
// environment service is used to handle stop, start, or restarting of a service in an environment
miscTaskData.lagoonService = taskData.data.lagoonService
break;
default:
miscTaskData.misc = taskData.data.build
break;
Expand Down
2 changes: 1 addition & 1 deletion services/actions-handler/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ go 1.21

require (
github.com/cheshir/go-mq/v2 v2.0.1
github.com/uselagoon/machinery v0.0.17
github.com/uselagoon/machinery v0.0.17-0.20240226005245-c8fa2fc9f9ab
gopkg.in/matryer/try.v1 v1.0.0-20150601225556-312d2599e12e
)

Expand Down
18 changes: 16 additions & 2 deletions services/actions-handler/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -839,8 +839,22 @@ github.com/urfave/cli v0.0.0-20171014202726-7bc6a0acffa5/go.mod h1:70zkFmudgCuE/
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
github.com/uselagoon/machinery v0.0.17 h1:rykgkboGvSX+aMa6MPXtR22DJ67eTPC4WITwpVDuj+Y=
github.com/uselagoon/machinery v0.0.17/go.mod h1:Duljjz/3d/7m0jbmF1nVRDTNaMxMr6m+5LkgjiRrQaU=
github.com/uselagoon/machinery v0.0.17-0.20240108020517-e6622621374c h1:xEUsDgNpM3ZaD0S6dsxQ+j/8hrnMM1HLEhbDUxrqFHE=
github.com/uselagoon/machinery v0.0.17-0.20240108020517-e6622621374c/go.mod h1:Duljjz/3d/7m0jbmF1nVRDTNaMxMr6m+5LkgjiRrQaU=
github.com/uselagoon/machinery v0.0.17-0.20240108024302-9784c2db7f1b h1:eF3O0RMETo+Bk3/I1SLjwkTvqbn/SkwCyTPWOFAl6ek=
github.com/uselagoon/machinery v0.0.17-0.20240108024302-9784c2db7f1b/go.mod h1:Duljjz/3d/7m0jbmF1nVRDTNaMxMr6m+5LkgjiRrQaU=
github.com/uselagoon/machinery v0.0.17-0.20240108025245-bf15fc15ab08 h1:SRCSGYkw6G77d20f5aA0Q/TJYhczybgEU4g7SodVsNg=
github.com/uselagoon/machinery v0.0.17-0.20240108025245-bf15fc15ab08/go.mod h1:Duljjz/3d/7m0jbmF1nVRDTNaMxMr6m+5LkgjiRrQaU=
github.com/uselagoon/machinery v0.0.17-0.20240108043534-c066518389be h1:cOBkrQa7DYDnIVZebJViIc9XEggFKYjfSKzqHC9VckE=
github.com/uselagoon/machinery v0.0.17-0.20240108043534-c066518389be/go.mod h1:Duljjz/3d/7m0jbmF1nVRDTNaMxMr6m+5LkgjiRrQaU=
github.com/uselagoon/machinery v0.0.17-0.20240108043655-6cf0c0b42884 h1:33GmXqKEkpPzsspIse3xx/aN/Leh+Jdl9QcRrbnEktg=
github.com/uselagoon/machinery v0.0.17-0.20240108043655-6cf0c0b42884/go.mod h1:Duljjz/3d/7m0jbmF1nVRDTNaMxMr6m+5LkgjiRrQaU=
github.com/uselagoon/machinery v0.0.17-0.20240108050446-30ff0a7df794 h1:2LP/ytk7sY6BrVY67PizVVYF6EnZKPzwKEOJg8JFY1I=
github.com/uselagoon/machinery v0.0.17-0.20240108050446-30ff0a7df794/go.mod h1:Duljjz/3d/7m0jbmF1nVRDTNaMxMr6m+5LkgjiRrQaU=
github.com/uselagoon/machinery v0.0.17-0.20240108054822-78639cc0a1f3 h1:DYklzy44C1s1a1O6LqAi8RUpuqDzTzJTnW9IRQ8J91k=
github.com/uselagoon/machinery v0.0.17-0.20240108054822-78639cc0a1f3/go.mod h1:Duljjz/3d/7m0jbmF1nVRDTNaMxMr6m+5LkgjiRrQaU=
github.com/uselagoon/machinery v0.0.17-0.20240226005245-c8fa2fc9f9ab h1:lBAsDSwVaj7l7Bt53yiWlll0mq4ZsEGtj/queCK+2OY=
github.com/uselagoon/machinery v0.0.17-0.20240226005245-c8fa2fc9f9ab/go.mod h1:Duljjz/3d/7m0jbmF1nVRDTNaMxMr6m+5LkgjiRrQaU=
github.com/vishvananda/netlink v0.0.0-20181108222139-023a6dafdcdf/go.mod h1:+SR5DhBJrl6ZM7CoCKvpw5BKroDKQ+PJqOg65H/2ktk=
github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE=
github.com/vishvananda/netlink v1.1.1-0.20201029203352-d40f9887b852/go.mod h1:twkDnbuQxJYemMlGd4JFIcuhgX83tXhKS2B/PRMpOho=
Expand Down
85 changes: 85 additions & 0 deletions services/actions-handler/handler/controller_idling.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
package handler

import (
"context"
"fmt"
"log"
"time"

mq "github.com/cheshir/go-mq/v2"
"github.com/uselagoon/machinery/api/lagoon"
lclient "github.com/uselagoon/machinery/api/lagoon/client"
"github.com/uselagoon/machinery/api/schema"
"github.com/uselagoon/machinery/utils/jwt"
)

func (m *Messenger) handleIdling(ctx context.Context, messageQueue *mq.MessageQueue, message *schema.LagoonMessage, messageID string) error {
prefix := fmt.Sprintf("(messageid:%s) %s: ", messageID, message.Namespace)
log.Println(fmt.Sprintf("%sreceived idling environment status update", prefix))
// generate a lagoon token with a expiry of 60 seconds from now
token, err := jwt.GenerateAdminToken(m.LagoonAPI.TokenSigningKey, m.LagoonAPI.JWTAudience, m.LagoonAPI.JWTSubject, m.LagoonAPI.JWTIssuer, time.Now().Unix(), 60)
if err != nil {
// the token wasn't generated
if m.EnableDebug {
log.Println(fmt.Sprintf("ERROR: unable to generate token: %v", err))
}
return nil
}
// set up a lagoon client for use in the following process
l := lclient.New(m.LagoonAPI.Endpoint, "actions-handler", &token, false)
var environmentID uint
// determine the environment id from the message
if message.Meta.ProjectID == nil && message.Meta.EnvironmentID == nil {
project, err := lagoon.GetMinimalProjectByName(ctx, message.Meta.Project, l)
if err != nil {
// send the log to the lagoon-logs exchange to be processed
m.toLagoonLogs(messageQueue, map[string]interface{}{
"severity": "error",
"event": fmt.Sprintf("actions-handler:%s:failed", "updateEnvironment"),
"meta": project,
"message": err.Error(),
})
if m.EnableDebug {
log.Println(fmt.Sprintf("%sERROR: unable to get project - %v", prefix, err))
}
return err
}
environment, err := lagoon.GetEnvironmentByName(ctx, message.Meta.Environment, project.ID, l)
if err != nil {
// send the log to the lagoon-logs exchange to be processed
m.toLagoonLogs(messageQueue, map[string]interface{}{
"severity": "error",
"event": fmt.Sprintf("actions-handler:%s:failed", "updateEnvironment"),
"meta": project,
"message": err.Error(),
})
if m.EnableDebug {
log.Println(fmt.Sprintf("%sERROR: unable to get environment - %v", prefix, err))
}
return err
}
environmentID = environment.ID
} else {
// pull the id from the message
environmentID = *message.Meta.EnvironmentID
}
updateEnvironmentPatch := schema.UpdateEnvironmentPatchInput{
Idled: &message.Idled,
}
updateEnvironment, err := lagoon.UpdateEnvironment(ctx, environmentID, updateEnvironmentPatch, l)
if err != nil {
// send the log to the lagoon-logs exchange to be processed
m.toLagoonLogs(messageQueue, map[string]interface{}{
"severity": "error",
"event": fmt.Sprintf("actions-handler:%s:failed", "updateDeployment"),
"meta": updateEnvironment,
"message": err.Error(),
})
if m.EnableDebug {
log.Println(fmt.Sprintf("%sERROR: unable to update environment - %v", prefix, err))
}
return err
}
log.Println(fmt.Sprintf("%supdated environment", prefix))
return nil
}
2 changes: 2 additions & 0 deletions services/actions-handler/handler/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,8 @@ func (m *Messenger) Consumer() {
err = m.handleRemoval(ctx, messageQueue, logMsg, messageID)
case "task":
err = m.handleTask(ctx, messageQueue, logMsg, messageID)
case "idling":
err = m.handleIdling(ctx, messageQueue, logMsg, messageID)
}
// if there aren't any errors, then ack the message, an error indicates that there may have been an issue with the api handling the request
// skipping this means the message will remain in the queue
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/**
* @param { import("knex").Knex } knex
* @returns { Promise<void> }
*/
exports.up = async function(knex) {
return knex.schema
.alterTable('environment', (table) => {
table.boolean('idled').notNullable().defaultTo(0);
})
};

/**
* @param { import("knex").Knex } knex
* @returns { Promise<void> }
*/
exports.down = async function(knex) {
return knex.schema
.alterTable('environment', (table) => {
table.dropColumn('idled');
})
};
4 changes: 3 additions & 1 deletion services/api/src/resolvers.js
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ const {
getEnvironmentByServiceId,
getServiceContainersByServiceId,
deleteEnvironmentService,
environmentIdling,
} = require('./resources/environment/resolvers');

const {
Expand Down Expand Up @@ -730,7 +731,8 @@ const resolvers = {
removeUserFromOrganizationGroups,
bulkImportProjectsAndGroupsToOrganization,
addOrUpdateEnvironmentService,
deleteEnvironmentService
deleteEnvironmentService,
environmentIdling,
},
Subscription: {
backupChanged: backupSubscriber,
Expand Down
161 changes: 158 additions & 3 deletions services/api/src/resources/environment/resolvers.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import * as R from 'ramda';
import { sendToLagoonLogs } from '@lagoon/commons/dist/logs/lagoon-logger';
import { createRemoveTask } from '@lagoon/commons/dist/tasks';
import {
createRemoveTask,
createMiscTask
} from '@lagoon/commons/dist/tasks';
import { ResolverFn } from '../';
import { logger } from '../../loggers/logger';
import { isPatchEmpty, query, knex } from '../../util/db';
Expand Down Expand Up @@ -660,7 +663,8 @@ export const updateEnvironment: ResolverFn = async (
route: input.patch.route,
routes: input.patch.routes,
autoIdle: input.patch.autoIdle,
created: input.patch.created
created: input.patch.created,
idled: input.patch.idled
}
})
);
Expand All @@ -685,7 +689,8 @@ export const updateEnvironment: ResolverFn = async (
route: input.patch.route,
routes: input.patch.routes,
autoIdle: input.patch.autoIdle,
created: input.patch.created
created: input.patch.created,
idled: input.patch.idled
},
data: withK8s
}
Expand Down Expand Up @@ -941,3 +946,153 @@ export const getServiceContainersByServiceId: ResolverFn = async (
);
return await rows;
};

export const environmentIdling = async (
root,
input,
{ sqlClientPool, hasPermission, userActivityLogger }
) => {
const environment = await Helpers(sqlClientPool).getEnvironmentById(input.id);

if (!environment) {
throw new Error(
'Invalid environment ID'
);
}

await hasPermission('environment', 'view', {
project: environment.project
});

// don't try idle if the environment is already idled or unidled
if (environment.idled && input.idle) {
throw new Error(
`environment is already idled`
);
}
if (!environment.idled && !input.idle) {
throw new Error(
`environment is already unidled`
);
}

const project = await projectHelpers(sqlClientPool).getProjectById(
environment.project
);

await hasPermission('deployment', 'cancel', {
project: project.id
});

const data = {
environment,
project,
idling: {
idle: input.idle,
forceScale: input.disableAutomaticUnidling
}
};

userActivityLogger(`User requested environment idling for '${environment.name}'`, {
project: '',
event: 'api:idleEnvironment',
payload: {
project: project.name,
environment: environment.name,
idle: input.idle,
disableAutomaticUnidling: input.disableAutomaticUnidling,
}
});

try {
await createMiscTask({ key: 'environment:idling', data });
return 'success';
} catch (error) {
sendToLagoonLogs(
'error',
'',
'',
'api:idleEnvironment',
{ environment: environment.id },
`Environment idle attempt possibly failed, reason: ${error}`
);
throw new Error(
error.message
);
}
};

export const environmentService = async (
root,
input,
{ sqlClientPool, hasPermission, userActivityLogger }
) => {
const environment = await Helpers(sqlClientPool).getEnvironmentById(input.id);

if (!environment) {
throw new Error(
'Invalid environment ID'
);
}

await hasPermission('environment', 'view', {
project: environment.project
});

// don't try idle if the environment is already idled or unidled
if (environment.idled && input.idle) {
throw new Error(
`environment is already idled`
);
}
if (!environment.idled && !input.idle) {
throw new Error(
`environment is already unidled`
);
}

const project = await projectHelpers(sqlClientPool).getProjectById(
environment.project
);

await hasPermission('deployment', 'cancel', {
project: project.id
});

const data = {
environment,
project,
lagoonService: {
name: input.serviceName,
state: input.state
}
};

userActivityLogger(`User requested environment idling for '${environment.name}'`, {
project: '',
event: 'api:idleEnvironment',
payload: {
project: project.name,
environment: environment.name,
idle: input.idle,
disableAutomaticUnidling: input.disableAutomaticUnidling,
}
});

try {
await createMiscTask({ key: 'environment:idling', data });
return 'success';
} catch (error) {
sendToLagoonLogs(
'error',
'',
'',
'api:idleEnvironment',
{ environment: environment.id },
`Environment idle attempt possibly failed, reason: ${error}`
);
throw new Error(
error.message
);
}
};
5 changes: 5 additions & 0 deletions services/api/src/typeDefs.js
Original file line number Diff line number Diff line change
Expand Up @@ -920,6 +920,10 @@ const typeDefs = gql`
kubernetes: Kubernetes
kubernetesNamespacePattern: String
workflows: [Workflow]
"""
Is the environment currently idled
"""
idled: Boolean
}
type EnvironmentHitsMonth {
Expand Down Expand Up @@ -2530,6 +2534,7 @@ const typeDefs = gql`
bulkImportProjectsAndGroupsToOrganization(input: AddProjectToOrganizationInput, detachNotification: Boolean): ProjectGroupsToOrganization
addOrUpdateEnvironmentService(input: AddEnvironmentServiceInput!): EnvironmentService
deleteEnvironmentService(input: DeleteEnvironmentServiceInput!): String
environmentIdling(id: Int!, idle: Boolean!, disableAutomaticUnidling: Boolean): String
}
type Subscription {
Expand Down

0 comments on commit 4962200

Please sign in to comment.