mirror of
https://github.com/woodpecker-ci/woodpecker.git
synced 2025-01-04 14:48:42 +00:00
parent
ed44c3b50f
commit
de4e62cfcf
11 changed files with 76 additions and 16 deletions
|
@ -23,6 +23,7 @@ import (
|
||||||
"github.com/woodpecker-ci/woodpecker/server"
|
"github.com/woodpecker-ci/woodpecker/server"
|
||||||
cronScheduler "github.com/woodpecker-ci/woodpecker/server/cron"
|
cronScheduler "github.com/woodpecker-ci/woodpecker/server/cron"
|
||||||
"github.com/woodpecker-ci/woodpecker/server/model"
|
"github.com/woodpecker-ci/woodpecker/server/model"
|
||||||
|
"github.com/woodpecker-ci/woodpecker/server/pipeline"
|
||||||
"github.com/woodpecker-ci/woodpecker/server/router/middleware/session"
|
"github.com/woodpecker-ci/woodpecker/server/router/middleware/session"
|
||||||
"github.com/woodpecker-ci/woodpecker/server/store"
|
"github.com/woodpecker-ci/woodpecker/server/store"
|
||||||
)
|
)
|
||||||
|
@ -45,6 +46,37 @@ func GetCron(c *gin.Context) {
|
||||||
c.JSON(200, cron)
|
c.JSON(200, cron)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RunCron starts a cron job now.
|
||||||
|
func RunCron(c *gin.Context) {
|
||||||
|
repo := session.Repo(c)
|
||||||
|
_store := store.FromContext(c)
|
||||||
|
id, err := strconv.ParseInt(c.Param("cron"), 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
c.String(400, "Error parsing cron id. %s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
cron, err := _store.CronFind(repo, id)
|
||||||
|
if err != nil {
|
||||||
|
c.String(http.StatusNotFound, "Error getting cron %q. %s", id, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
repo, newPipeline, err := cronScheduler.CreatePipeline(c, _store, server.Config.Services.Remote, cron)
|
||||||
|
if err != nil {
|
||||||
|
c.String(http.StatusInternalServerError, "Error creating pipeline for cron %q. %s", id, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
pl, err := pipeline.Create(c, _store, repo, newPipeline)
|
||||||
|
if err != nil {
|
||||||
|
handlePipelineErr(c, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(200, pl)
|
||||||
|
}
|
||||||
|
|
||||||
// PostCron persists the cron job to the database.
|
// PostCron persists the cron job to the database.
|
||||||
func PostCron(c *gin.Context) {
|
func PostCron(c *gin.Context) {
|
||||||
repo := session.Repo(c)
|
repo := session.Repo(c)
|
||||||
|
|
|
@ -21,6 +21,7 @@ package api
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
@ -28,6 +29,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/woodpecker-ci/woodpecker/server"
|
"github.com/woodpecker-ci/woodpecker/server"
|
||||||
|
"github.com/woodpecker-ci/woodpecker/server/store/types"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
|
@ -95,6 +97,10 @@ func GetPipelines(c *gin.Context) {
|
||||||
|
|
||||||
pipelines, err := store.FromContext(c).GetPipelineList(repo, page)
|
pipelines, err := store.FromContext(c).GetPipelineList(repo, page)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if errors.Is(err, types.RecordNotExist) {
|
||||||
|
c.AbortWithStatus(http.StatusNotFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
c.AbortWithStatus(http.StatusInternalServerError)
|
c.AbortWithStatus(http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -96,16 +96,16 @@ func runCron(store store.Store, remote remote.Remote, cron *model.Cron, now time
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
repo, newBuild, err := createBuild(ctx, store, remote, cron)
|
repo, newPipeline, err := CreatePipeline(ctx, store, remote, cron)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = pipeline.Create(ctx, store, repo, newBuild)
|
_, err = pipeline.Create(ctx, store, repo, newPipeline)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func createBuild(ctx context.Context, store store.Store, remote remote.Remote, cron *model.Cron) (*model.Repo, *model.Pipeline, error) {
|
func CreatePipeline(ctx context.Context, store store.Store, remote remote.Remote, cron *model.Cron) (*model.Repo, *model.Pipeline, error) {
|
||||||
repo, err := store.GetRepo(cron.RepoID)
|
repo, err := store.GetRepo(cron.RepoID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
|
|
|
@ -49,7 +49,7 @@ func TestCreateBuild(t *testing.T) {
|
||||||
store.On("GetUser", mock.Anything).Return(creator, nil)
|
store.On("GetUser", mock.Anything).Return(creator, nil)
|
||||||
remote.On("BranchHead", mock.Anything, creator, repo1, "default").Return("sha1", nil)
|
remote.On("BranchHead", mock.Anything, creator, repo1, "default").Return("sha1", nil)
|
||||||
|
|
||||||
_, pipeline, err := createBuild(ctx, store, remote, &model.Cron{
|
_, pipeline, err := CreatePipeline(ctx, store, remote, &model.Cron{
|
||||||
Name: "test",
|
Name: "test",
|
||||||
})
|
})
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
|
@ -114,6 +114,7 @@ func apiRoutes(e *gin.Engine) {
|
||||||
repo.GET("/cron", session.MustPush, api.GetCronList)
|
repo.GET("/cron", session.MustPush, api.GetCronList)
|
||||||
repo.POST("/cron", session.MustPush, api.PostCron)
|
repo.POST("/cron", session.MustPush, api.PostCron)
|
||||||
repo.GET("/cron/:cron", session.MustPush, api.GetCron)
|
repo.GET("/cron/:cron", session.MustPush, api.GetCron)
|
||||||
|
repo.POST("/cron/:cron", session.MustPush, api.RunCron)
|
||||||
repo.PATCH("/cron/:cron", session.MustPush, api.PatchCron)
|
repo.PATCH("/cron/:cron", session.MustPush, api.PatchCron)
|
||||||
repo.DELETE("/cron/:cron", session.MustPush, api.DeleteCron)
|
repo.DELETE("/cron/:cron", session.MustPush, api.DeleteCron)
|
||||||
|
|
||||||
|
|
1
web/components.d.ts
vendored
1
web/components.d.ts
vendored
|
@ -32,6 +32,7 @@ declare module '@vue/runtime-core' {
|
||||||
IIcBaselineFileDownload: typeof import('~icons/ic/baseline-file-download')['default']
|
IIcBaselineFileDownload: typeof import('~icons/ic/baseline-file-download')['default']
|
||||||
IIcBaselineFileDownloadOff: typeof import('~icons/ic/baseline-file-download-off')['default']
|
IIcBaselineFileDownloadOff: typeof import('~icons/ic/baseline-file-download-off')['default']
|
||||||
IIcBaselineHealing: typeof import('~icons/ic/baseline-healing')['default']
|
IIcBaselineHealing: typeof import('~icons/ic/baseline-healing')['default']
|
||||||
|
IIcBaselinePlayArrow: typeof import('~icons/ic/baseline-play-arrow')['default']
|
||||||
IIconoirArrowLeft: typeof import('~icons/iconoir/arrow-left')['default']
|
IIconoirArrowLeft: typeof import('~icons/iconoir/arrow-left')['default']
|
||||||
IIconParkOutlineAlarmClock: typeof import('~icons/icon-park-outline/alarm-clock')['default']
|
IIconParkOutlineAlarmClock: typeof import('~icons/icon-park-outline/alarm-clock')['default']
|
||||||
IIcRoundLightMode: typeof import('~icons/ic/round-light-mode')['default']
|
IIcRoundLightMode: typeof import('~icons/ic/round-light-mode')['default']
|
||||||
|
|
|
@ -154,6 +154,7 @@
|
||||||
"deleted": "Cron deleted",
|
"deleted": "Cron deleted",
|
||||||
"next_exec": "Next execution",
|
"next_exec": "Next execution",
|
||||||
"not_executed_yet": "Not executed yet",
|
"not_executed_yet": "Not executed yet",
|
||||||
|
"run": "Run now",
|
||||||
"branch": {
|
"branch": {
|
||||||
"title": "Branch",
|
"title": "Branch",
|
||||||
"placeholder": "Branch (uses default branch if empty)"
|
"placeholder": "Branch (uses default branch if empty)"
|
||||||
|
|
|
@ -41,6 +41,7 @@
|
||||||
<i-icon-park-outline-alarm-clock v-else-if="name === 'stopwatch'" class="h-6 w-6" />
|
<i-icon-park-outline-alarm-clock v-else-if="name === 'stopwatch'" class="h-6 w-6" />
|
||||||
<i-ic-baseline-file-download v-else-if="name === 'auto-scroll'" class="h-6 w-6" />
|
<i-ic-baseline-file-download v-else-if="name === 'auto-scroll'" class="h-6 w-6" />
|
||||||
<i-ic-baseline-file-download-off v-else-if="name === 'auto-scroll-off'" class="h-6 w-6" />
|
<i-ic-baseline-file-download-off v-else-if="name === 'auto-scroll-off'" class="h-6 w-6" />
|
||||||
|
<i-ic-baseline-play-arrow v-else-if="name === 'play'" class="h-6 w-6" />
|
||||||
<div v-else-if="name === 'blank'" class="h-6 w-6" />
|
<div v-else-if="name === 'blank'" class="h-6 w-6" />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -90,7 +91,8 @@ export type IconNames =
|
||||||
| 'stopwatch'
|
| 'stopwatch'
|
||||||
| 'download'
|
| 'download'
|
||||||
| 'auto-scroll'
|
| 'auto-scroll'
|
||||||
| 'auto-scroll-off';
|
| 'auto-scroll-off'
|
||||||
|
| 'play';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'Icon',
|
name: 'Icon',
|
||||||
|
|
|
@ -31,12 +31,8 @@
|
||||||
{{ $t('repo.settings.crons.next_exec') }}: {{ date.toLocaleString(new Date(cron.next_exec * 1000)) }}</span
|
{{ $t('repo.settings.crons.next_exec') }}: {{ date.toLocaleString(new Date(cron.next_exec * 1000)) }}</span
|
||||||
>
|
>
|
||||||
<span v-else class="ml-auto">{{ $t('repo.settings.crons.not_executed_yet') }}</span>
|
<span v-else class="ml-auto">{{ $t('repo.settings.crons.not_executed_yet') }}</span>
|
||||||
<IconButton
|
<IconButton icon="play" class="ml-auto w-8 h-8" :title="$t('repo.settings.crons.run')" @click="runCron(cron)" />
|
||||||
icon="edit"
|
<IconButton icon="edit" class="w-8 h-8" :title="$t('repo.settings.crons.edit')" @click="selectedCron = cron" />
|
||||||
class="ml-auto w-8 h-8"
|
|
||||||
:title="$t('repo.settings.crons.edit')"
|
|
||||||
@click="selectedCron = cron"
|
|
||||||
/>
|
|
||||||
<IconButton
|
<IconButton
|
||||||
icon="trash"
|
icon="trash"
|
||||||
class="w-8 h-8 hover:text-red-400 hover:dark:text-red-500"
|
class="w-8 h-8 hover:text-red-400 hover:dark:text-red-500"
|
||||||
|
@ -105,6 +101,7 @@ import { useAsyncAction } from '~/compositions/useAsyncAction';
|
||||||
import { useDate } from '~/compositions/useDate';
|
import { useDate } from '~/compositions/useDate';
|
||||||
import useNotifications from '~/compositions/useNotifications';
|
import useNotifications from '~/compositions/useNotifications';
|
||||||
import { Cron, Repo } from '~/lib/api/types';
|
import { Cron, Repo } from '~/lib/api/types';
|
||||||
|
import router from '~/router';
|
||||||
|
|
||||||
const apiClient = useApiClient();
|
const apiClient = useApiClient();
|
||||||
const notifications = useNotifications();
|
const notifications = useNotifications();
|
||||||
|
@ -156,6 +153,22 @@ const { doSubmit: deleteCron, isLoading: isDeleting } = useAsyncAction(async (_c
|
||||||
await loadCrons();
|
await loadCrons();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const { doSubmit: runCron } = useAsyncAction(async (_cron: Cron) => {
|
||||||
|
if (!repo?.value) {
|
||||||
|
throw new Error("Unexpected: Can't load repo");
|
||||||
|
}
|
||||||
|
|
||||||
|
const pipeline = await apiClient.runCron(repo.value.owner, repo.value.name, _cron.id);
|
||||||
|
await router.push({
|
||||||
|
name: 'repo-pipeline',
|
||||||
|
params: {
|
||||||
|
repoOwner: repo.value.owner,
|
||||||
|
repoName: repo.value.name,
|
||||||
|
pipelineId: pipeline.number,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
await loadCrons();
|
await loadCrons();
|
||||||
});
|
});
|
||||||
|
|
|
@ -166,16 +166,16 @@ export default defineComponent({
|
||||||
text: i18n.t('repo.settings.general.visibility.public.public'),
|
text: i18n.t('repo.settings.general.visibility.public.public'),
|
||||||
description: i18n.t('repo.settings.general.visibility.public.desc'),
|
description: i18n.t('repo.settings.general.visibility.public.desc'),
|
||||||
},
|
},
|
||||||
{
|
|
||||||
value: RepoVisibility.Private,
|
|
||||||
text: i18n.t('repo.settings.general.visibility.private.private'),
|
|
||||||
description: i18n.t('repo.settings.general.visibility.private.desc'),
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
value: RepoVisibility.Internal,
|
value: RepoVisibility.Internal,
|
||||||
text: i18n.t('repo.settings.general.visibility.internal.internal'),
|
text: i18n.t('repo.settings.general.visibility.internal.internal'),
|
||||||
description: i18n.t('repo.settings.general.visibility.internal.desc'),
|
description: i18n.t('repo.settings.general.visibility.internal.desc'),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
value: RepoVisibility.Private,
|
||||||
|
text: i18n.t('repo.settings.general.visibility.private.private'),
|
||||||
|
description: i18n.t('repo.settings.general.visibility.private.desc'),
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const cancelPreviousPipelineEventsOptions: CheckboxOption[] = [
|
const cancelPreviousPipelineEventsOptions: CheckboxOption[] = [
|
||||||
|
|
|
@ -162,6 +162,10 @@ export default class WoodpeckerClient extends ApiClient {
|
||||||
return this._delete(`/api/repos/${owner}/${repo}/cron/${cronId}`);
|
return this._delete(`/api/repos/${owner}/${repo}/cron/${cronId}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
runCron(owner: string, repo: string, cronId: number): Promise<Pipeline> {
|
||||||
|
return this._post(`/api/repos/${owner}/${repo}/cron/${cronId}`) as Promise<Pipeline>;
|
||||||
|
}
|
||||||
|
|
||||||
getOrgPermissions(owner: string): Promise<OrgPermissions> {
|
getOrgPermissions(owner: string): Promise<OrgPermissions> {
|
||||||
return this._get(`/api/orgs/${owner}/permissions`) as Promise<OrgPermissions>;
|
return this._get(`/api/orgs/${owner}/permissions`) as Promise<OrgPermissions>;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue