jobs: warn and ignore, reject active scheduled jobs on up, create and start

The model may declare jobs this runtime cannot execute yet. Commands
that materialize the application warn about them, and refuse an active
(profile-enabled) scheduled job before any resource is created — up,
create and start alike: silently not scheduling would break
expectations, while manual jobs just wait for an explicit trigger.

Co-Authored-By: Claude Fable 5 <[email protected]>
Signed-off-by: Nicolas De Loof <[email protected]>
This commit is contained in:
Nicolas De Loof
2026-09-21 16:35:20 +02:00
committed by Nicolas De loof
co-authored by Claude Fable 5
parent e96ca020f2
commit 124cbb8eb9
5 changed files with 60 additions and 0 deletions
+10
View File
@@ -25,6 +25,7 @@ import (
"os"
"os/signal"
"path/filepath"
"sort"
"strconv"
"strings"
"syscall"
@@ -368,6 +369,15 @@ func (o *ProjectOptions) ToProject(
return nil, metrics, err
}
if jobs := project.AllJobs(); len(jobs) > 0 {
names := make([]string, 0, len(jobs))
for name := range jobs {
names = append(names, name)
}
sort.Strings(names)
logrus.Warnf("jobs are not supported yet and will be ignored: %s", strings.Join(names, ", "))
}
return project, metrics, nil
}
+5
View File
@@ -98,6 +98,11 @@ func createCommand(p *ProjectOptions, dockerCli command.Cli, backendOptions *Bac
}
func runCreate(ctx context.Context, dockerCli command.Cli, backendOptions *BackendOptions, createOpts createOptions, buildOpts buildOptions, project *types.Project, services []string) error {
// same contract as up: an active scheduled job is refused before any
// resource is created — silently not scheduling would break expectations
if err := rejectScheduledJobs(project); err != nil {
return err
}
if err := createOpts.Apply(project); err != nil {
return err
}
+7
View File
@@ -56,6 +56,13 @@ func runStart(ctx context.Context, dockerCli command.Cli, backendOptions *Backen
if err != nil {
return err
}
// a label-reconstructed project (no compose file) declares no jobs;
// with a file, refuse active scheduled jobs like up and create do
if project != nil {
if err := rejectScheduledJobs(project); err != nil {
return err
}
}
var timeout time.Duration
if opts.waitTimeout > 0 {
+22
View File
@@ -21,6 +21,7 @@ import (
"errors"
"fmt"
"os"
"sort"
"strings"
"time"
@@ -241,6 +242,10 @@ func runUp(
return err
}
if err := rejectScheduledJobs(project); err != nil {
return err
}
err := createOptions.Apply(project)
if err != nil {
return err
@@ -349,3 +354,20 @@ func runUp(
},
})
}
// rejectScheduledJobs refuses to bring a project up when it declares active
// scheduled jobs: silently not scheduling them would break the user's
// expectations, unlike manual jobs which simply wait for an explicit trigger.
func rejectScheduledJobs(project *types.Project) error {
names := make([]string, 0, len(project.Jobs))
for name, job := range project.Jobs {
if job.Triggers != nil && len(job.Triggers.Schedule) > 0 {
names = append(names, name)
}
}
if len(names) == 0 {
return nil
}
sort.Strings(names)
return fmt.Errorf("scheduled jobs are not supported in this version: %s", strings.Join(names, ", "))
}
+16
View File
@@ -178,3 +178,19 @@ services:
assert.Assert(t, strings.Contains(output, "LXKNS_PORT"), output)
assert.Assert(t, !strings.Contains(fmt.Sprint(err), "invalid ip address"), fmt.Sprint(err))
}
func TestRejectScheduledJobs(t *testing.T) {
yes := true
manual := types.JobConfig{Triggers: &types.TriggerConfig{Manual: &yes}}
scheduled := types.JobConfig{Triggers: &types.TriggerConfig{
Schedule: []types.ScheduleConfig{{Cron: "0 3 * * *"}},
}}
assert.NilError(t, rejectScheduledJobs(&types.Project{}))
assert.NilError(t, rejectScheduledJobs(&types.Project{Jobs: types.Jobs{"migrate": manual}}))
// profile-disabled scheduled jobs don't block up
assert.NilError(t, rejectScheduledJobs(&types.Project{DisabledJobs: types.Jobs{"backup": scheduled}}))
err := rejectScheduledJobs(&types.Project{Jobs: types.Jobs{"backup": scheduled, "sync": scheduled, "migrate": manual}})
assert.Error(t, err, "scheduled jobs are not supported in this version: backup, sync")
}