Add agent no-schedule flag (#1567)

This flag allows an agent owner / admin to stop the agent from taking
new workflows / pipelines.
This commit is contained in:
Anbraten 2023-01-30 20:18:48 +01:00 committed by GitHub
parent 5f818e933b
commit 71d6c03ca7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 28 additions and 19 deletions

View file

@ -73,6 +73,7 @@ func PatchAgent(c *gin.Context) {
return return
} }
agent.Name = in.Name agent.Name = in.Name
agent.NoSchedule = in.NoSchedule
err = _store.AgentUpdate(agent) err = _store.AgentUpdate(agent)
if err != nil { if err != nil {
@ -95,8 +96,9 @@ func PostAgent(c *gin.Context) {
user := session.User(c) user := session.User(c)
agent := &model.Agent{ agent := &model.Agent{
Name: in.Name, Name: in.Name,
OwnerID: user.ID, NoSchedule: in.NoSchedule,
OwnerID: user.ID,
Token: base32.StdEncoding.EncodeToString( Token: base32.StdEncoding.EncodeToString(
securecookie.GenerateRandomKey(32), securecookie.GenerateRandomKey(32),
), ),

View file

@ -70,6 +70,13 @@ func (s *RPC) Next(c context.Context, agentFilter rpc.Filter) (*rpc.Pipeline, er
return nil, err return nil, err
} }
for { for {
agent, err := s.getAgentFromContext(c)
if err != nil {
return nil, err
} else if agent.NoSchedule {
return nil, nil
}
task, err := s.queue.Poll(c, fn) task, err := s.queue.Poll(c, fn)
if err != nil { if err != nil {
return nil, err return nil, err
@ -82,6 +89,7 @@ func (s *RPC) Next(c context.Context, agentFilter rpc.Filter) (*rpc.Pipeline, er
err = json.Unmarshal(task.Data, pipeline) err = json.Unmarshal(task.Data, pipeline)
return pipeline, err return pipeline, err
} }
if err := s.Done(c, task.ID, rpc.State{}); err != nil { if err := s.Done(c, task.ID, rpc.State{}); err != nil {
log.Error().Err(err).Msgf("mark task '%s' done failed", task.ID) log.Error().Err(err).Msgf("mark task '%s' done failed", task.ID)
} }

View file

@ -26,6 +26,7 @@ type Agent struct {
Backend string `json:"backend" xorm:"VARCHAR(100)"` Backend string `json:"backend" xorm:"VARCHAR(100)"`
Capacity int32 `json:"capacity"` Capacity int32 `json:"capacity"`
Version string `json:"version"` Version string `json:"version"`
NoSchedule bool `json:"no_schedule"`
} }
// TableName return database table name for xorm // TableName return database table name for xorm

18
web/components.d.ts vendored
View file

@ -21,10 +21,8 @@ declare module '@vue/runtime-core' {
FluidContainer: typeof import('./src/components/layout/FluidContainer.vue')['default'] FluidContainer: typeof import('./src/components/layout/FluidContainer.vue')['default']
GeneralTab: typeof import('./src/components/repo/settings/GeneralTab.vue')['default'] GeneralTab: typeof import('./src/components/repo/settings/GeneralTab.vue')['default']
Header: typeof import('./src/components/layout/scaffold/Header.vue')['default'] Header: typeof import('./src/components/layout/scaffold/Header.vue')['default']
IBiCheckCircle: typeof import('~icons/bi/check-circle')['default']
IBiCheckCircleFill: typeof import('~icons/bi/check-circle-fill')['default'] IBiCheckCircleFill: typeof import('~icons/bi/check-circle-fill')['default']
IBiCircle: typeof import('~icons/bi/circle')['default'] IBiCircle: typeof import('~icons/bi/circle')['default']
IBiPauseCircleFill: typeof import('~icons/bi/pause-circle-fill')['default']
IBiPlayCircleFill: typeof import('~icons/bi/play-circle-fill')['default'] IBiPlayCircleFill: typeof import('~icons/bi/play-circle-fill')['default']
IBiSlashCircleFill: typeof import('~icons/bi/slash-circle-fill')['default'] IBiSlashCircleFill: typeof import('~icons/bi/slash-circle-fill')['default']
IBiStopCircleFill: typeof import('~icons/bi/stop-circle-fill')['default'] IBiStopCircleFill: typeof import('~icons/bi/stop-circle-fill')['default']
@ -33,13 +31,9 @@ declare module '@vue/runtime-core' {
ICarbonCloseOutline: typeof import('~icons/carbon/close-outline')['default'] ICarbonCloseOutline: typeof import('~icons/carbon/close-outline')['default']
ICarbonInProgress: typeof import('~icons/carbon/in-progress')['default'] ICarbonInProgress: typeof import('~icons/carbon/in-progress')['default']
IClarityDeployLine: typeof import('~icons/clarity/deploy-line')['default'] IClarityDeployLine: typeof import('~icons/clarity/deploy-line')['default']
IClarityNoAccessSolid: typeof import('~icons/clarity/no-access-solid')['default']
IClaritySettingsSolid: typeof import('~icons/clarity/settings-solid')['default'] IClaritySettingsSolid: typeof import('~icons/clarity/settings-solid')['default']
IClaritySuccessStandardSolid: typeof import('~icons/clarity/success-standard-solid')['default']
IClarityTimesCircleSolid: typeof import('~icons/clarity/times-circle-solid')['default']
Icon: typeof import('./src/components/atomic/Icon.vue')['default'] Icon: typeof import('./src/components/atomic/Icon.vue')['default']
IconButton: typeof import('./src/components/atomic/IconButton.vue')['default'] IconButton: typeof import('./src/components/atomic/IconButton.vue')['default']
IEntypoDotsTwoVertical: typeof import('~icons/entypo/dots-two-vertical')['default']
IGgTrash: typeof import('~icons/gg/trash')['default'] IGgTrash: typeof import('~icons/gg/trash')['default']
IIcBaselineDarkMode: typeof import('~icons/ic/baseline-dark-mode')['default'] IIcBaselineDarkMode: typeof import('~icons/ic/baseline-dark-mode')['default']
IIcBaselineDownloadForOffline: typeof import('~icons/ic/baseline-download-for-offline')['default'] IIcBaselineDownloadForOffline: typeof import('~icons/ic/baseline-download-for-offline')['default']
@ -67,24 +61,14 @@ declare module '@vue/runtime-core' {
IMdiSync: typeof import('~icons/mdi/sync')['default'] IMdiSync: typeof import('~icons/mdi/sync')['default']
IMdiTagOutline: typeof import('~icons/mdi/tag-outline')['default'] IMdiTagOutline: typeof import('~icons/mdi/tag-outline')['default']
InputField: typeof import('./src/components/form/InputField.vue')['default'] InputField: typeof import('./src/components/form/InputField.vue')['default']
IOcticonSkip24: typeof import('~icons/octicon/skip24')['default']
IPhCheckCircle: typeof import('~icons/ph/check-circle')['default']
IPhGitlabLogoSimpleFill: typeof import('~icons/ph/gitlab-logo-simple-fill')['default'] IPhGitlabLogoSimpleFill: typeof import('~icons/ph/gitlab-logo-simple-fill')['default']
IPhHand: typeof import('~icons/ph/hand')['default']
IPhHourglass: typeof import('~icons/ph/hourglass')['default']
IPhProhibit: typeof import('~icons/ph/prohibit')['default']
IPhWarning: typeof import('~icons/ph/warning')['default']
IPhXCircle: typeof import('~icons/ph/x-circle')['default']
ISimpleIconsGitea: typeof import('~icons/simple-icons/gitea')['default'] ISimpleIconsGitea: typeof import('~icons/simple-icons/gitea')['default']
ITeenyiconsGitSolid: typeof import('~icons/teenyicons/git-solid')['default'] ITeenyiconsGitSolid: typeof import('~icons/teenyicons/git-solid')['default']
IUiwCircleCheck: typeof import('~icons/uiw/circle-check')['default'] ITeenyiconsRefreshOutline: typeof import('~icons/teenyicons/refresh-outline')['default']
IUiwCircleClose: typeof import('~icons/uiw/circle-close')['default']
IUiwStop: typeof import('~icons/uiw/stop')['default']
IVaadinQuestionCircleO: typeof import('~icons/vaadin/question-circle-o')['default'] IVaadinQuestionCircleO: typeof import('~icons/vaadin/question-circle-o')['default']
ListItem: typeof import('./src/components/atomic/ListItem.vue')['default'] ListItem: typeof import('./src/components/atomic/ListItem.vue')['default']
ManualPipelinePopup: typeof import('./src/components/layout/popups/ManualPipelinePopup.vue')['default'] ManualPipelinePopup: typeof import('./src/components/layout/popups/ManualPipelinePopup.vue')['default']
Navbar: typeof import('./src/components/layout/header/Navbar.vue')['default'] Navbar: typeof import('./src/components/layout/header/Navbar.vue')['default']
NavbarIcon: typeof import('./src/components/layout/header/NavbarIcon.vue')['default']
NumberField: typeof import('./src/components/form/NumberField.vue')['default'] NumberField: typeof import('./src/components/form/NumberField.vue')['default']
OrgSecretsTab: typeof import('./src/components/org/settings/OrgSecretsTab.vue')['default'] OrgSecretsTab: typeof import('./src/components/org/settings/OrgSecretsTab.vue')['default']
Panel: typeof import('./src/components/layout/Panel.vue')['default'] Panel: typeof import('./src/components/layout/Panel.vue')['default']

View file

@ -336,6 +336,10 @@
"name": "Name", "name": "Name",
"placeholder": "Name of the agent" "placeholder": "Name of the agent"
}, },
"no_schedule": {
"name": "Disable agent",
"placeholder": "Stop agent from taking new tasks"
},
"token": "Token", "token": "Token",
"platform": "Platform", "platform": "Platform",
"backend": "Backend", "backend": "Backend",

View file

@ -43,6 +43,14 @@
/> />
</InputField> </InputField>
<InputField :label="$t('admin.settings.agents.no_schedule.name')">
<Checkbox
:model-value="selectedAgent.no_schedule || false"
:label="$t('admin.settings.agents.no_schedule.placeholder')"
@update:model-value="selectedAgent!.no_schedule = $event"
/>
</InputField>
<template v-if="isEditingAgent"> <template v-if="isEditingAgent">
<InputField :label="$t('admin.settings.agents.token')"> <InputField :label="$t('admin.settings.agents.token')">
<TextField v-model="selectedAgent.token" :placeholder="$t('admin.settings.agents.token')" disabled /> <TextField v-model="selectedAgent.token" :placeholder="$t('admin.settings.agents.token')" disabled />
@ -97,6 +105,7 @@ import { useI18n } from 'vue-i18n';
import Button from '~/components/atomic/Button.vue'; import Button from '~/components/atomic/Button.vue';
import ListItem from '~/components/atomic/ListItem.vue'; import ListItem from '~/components/atomic/ListItem.vue';
import Checkbox from '~/components/form/Checkbox.vue';
import InputField from '~/components/form/InputField.vue'; import InputField from '~/components/form/InputField.vue';
import TextField from '~/components/form/TextField.vue'; import TextField from '~/components/form/TextField.vue';
import Panel from '~/components/layout/Panel.vue'; import Panel from '~/components/layout/Panel.vue';

View file

@ -9,4 +9,5 @@ export type Agent = {
backend: string; backend: string;
capacity: number; capacity: number;
version: string; version: string;
no_schedule: boolean;
}; };