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 Dec 17, 2023
1 parent 38ca596 commit 94e80d3
Show file tree
Hide file tree
Showing 10 changed files with 614 additions and 429 deletions.
769 changes: 351 additions & 418 deletions node-packages/commons/src/tasks.ts

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions services/actions-handler/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,14 @@ go 1.21

require (
github.com/cheshir/go-mq/v2 v2.0.1
github.com/uselagoon/machinery v0.0.9
github.com/uselagoon/machinery v0.0.15-0.20231215023103-5cdfc5838e87
gopkg.in/matryer/try.v1 v1.0.0-20150601225556-312d2599e12e
)

require (
github.com/NeowayLabs/wabbit v0.0.0-20210927194032-73ad61d1620e // indirect
github.com/cheekybits/is v0.0.0-20150225183255-68e9c0620927 // indirect
github.com/dgrijalva/jwt-go v3.2.0+incompatible // indirect
github.com/golang-jwt/jwt v3.2.2+incompatible // indirect
github.com/google/go-cmp v0.5.9 // indirect
github.com/google/uuid v1.3.0 // indirect
github.com/guregu/null v4.0.0+incompatible // indirect
Expand Down
7 changes: 4 additions & 3 deletions services/actions-handler/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -303,7 +303,6 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/denverdino/aliyungo v0.0.0-20190125010748-a747050bb1ba/go.mod h1:dV8lFg6daOBZbT6/BDGIz6Y3WFGn8juu6G+CQ6LHtl0=
github.com/dgrijalva/jwt-go v0.0.0-20170104182250-a601269ab70c/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
github.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E=
Expand Down Expand Up @@ -407,6 +406,8 @@ github.com/gogo/protobuf v1.3.0/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXP
github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
Expand Down Expand Up @@ -838,8 +839,8 @@ 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.9 h1:wj9CVSUtneh/ynt2by5UsaUoveFxkI7MY1c/EbR90p8=
github.com/uselagoon/machinery v0.0.9/go.mod h1:IXLxlkahEAEgpCmu9Xa/Wmjo6ja4Aoq7tf8G7VrileE=
github.com/uselagoon/machinery v0.0.15-0.20231215023103-5cdfc5838e87 h1:l6iDDMEy9pa0215arObnKpEXbaClAw7gtE8ACJRjfQ4=
github.com/uselagoon/machinery v0.0.15-0.20231215023103-5cdfc5838e87/go.mod h1:h/qeMWQR4Qqu33x+8AulNDeolEwvb/G+aIsn/jyUtwk=
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
4 changes: 2 additions & 2 deletions services/actions-handler/handler/controller_builds.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ func (m *Messenger) handleBuild(ctx context.Context, messageQueue *mq.MessageQue

// set up a lagoon client for use in the following process
l := lclient.New(m.LagoonAPI.Endpoint, "actions-handler", &token, false)
deployment, err := lagoon.GetDeploymentByName(ctx, message.Namespace, message.Meta.BuildName, l)
deployment, err := lagoon.GetDeploymentByName(ctx, message.Meta.Project, message.Meta.Environment, message.Meta.BuildName, false, l)
if err != nil {
m.toLagoonLogs(messageQueue, map[string]interface{}{
"severity": "error",
Expand Down Expand Up @@ -87,7 +87,7 @@ func (m *Messenger) handleBuild(ctx context.Context, messageQueue *mq.MessageQue
"message": err.Error(),
})
if m.EnableDebug {
log.Println(fmt.Sprintf("%sERROR: unable to get project - %v", prefix, err))
log.Println(fmt.Sprintf("%sERROR: unable to get environment - %v", prefix, err))
}
return err
}
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');
})
};
6 changes: 5 additions & 1 deletion services/api/src/resolvers.js
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,8 @@ const {
userCanSshToEnvironment,
getEnvironmentUrl,
getEnvironmentsByKubernetes,
idleEnvironment,
unidleEnvironment,
} = require('./resources/environment/resolvers');

const {
Expand Down Expand Up @@ -714,7 +716,9 @@ const resolvers = {
removeDeployTargetFromOrganization,
updateEnvironmentDeployTarget,
removeUserFromOrganizationGroups,
bulkImportProjectsAndGroupsToOrganization
bulkImportProjectsAndGroupsToOrganization,
idleEnvironment,
unidleEnvironment
},
Subscription: {
backupChanged: backupSubscriber,
Expand Down
139 changes: 136 additions & 3 deletions services/api/src/resources/environment/resolvers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@ import * as R from 'ramda';
// @ts-ignore
import { sendToLagoonLogs } from '@lagoon/commons/dist/logs/lagoon-logger';
// @ts-ignore
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 @@ -668,7 +671,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 @@ -693,7 +697,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 @@ -808,3 +813,131 @@ export const userCanSshToEnvironment: ResolverFn = async (
return null;
}
};

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

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

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

if (!environment.idled) {
const project = await projectHelpers(sqlClientPool).getProjectById(
environment.project
);

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

const data = {
environment,
project,
disableAutomaticUnidling: args.disableAutomaticUnidling,
};

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

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

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

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

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

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

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

const data = {
environment,
project,
};

userActivityLogger(`User requested environment unidle for '${environment.name}'`, {
project: '',
event: 'api:unidleEnvironment',
payload: {
project: project.name,
environment: environment.name
}
});

try {
await createMiscTask({ key: 'unidle:environment', data });
return 'success';
} catch (error) {
sendToLagoonLogs(
'error',
'',
'',
'api:unidleEnvironment',
{ environment: environment.id },
`Environment unidle attempt possibly failed, reason: ${error}`
);
throw new Error(
error.message
);
}
} else {
throw new Error(
`environment is already unidled`
);
}
};
6 changes: 6 additions & 0 deletions services/api/src/typeDefs.js
Original file line number Diff line number Diff line change
Expand Up @@ -928,6 +928,10 @@ const typeDefs = gql`
kubernetes: Kubernetes
kubernetesNamespacePattern: String
workflows: [Workflow]
"""
Is the environment currently idled
"""
idled: Boolean
}
type EnvironmentHitsMonth {
Expand Down Expand Up @@ -2471,6 +2475,8 @@ const typeDefs = gql`
This mutation performs a lot of actions, on big project and group imports, if it times out, subsequent runs will perform only the changes necessary
"""
bulkImportProjectsAndGroupsToOrganization(input: AddProjectToOrganizationInput, detachNotification: Boolean): ProjectGroupsToOrganization
idleEnvironment(id: Int!, disableAutomaticUnidling: Boolean): String
unidleEnvironment(id: Int!): String
}
type Subscription {
Expand Down

0 comments on commit 94e80d3

Please sign in to comment.