diff --git a/cmd/compose/build.go b/cmd/compose/build.go index 3dc9536775..2a83fbb904 100644 --- a/cmd/compose/build.go +++ b/cmd/compose/build.go @@ -183,5 +183,6 @@ func runBuild(ctx context.Context, dockerCli command.Cli, backendOptions *Backen } apiBuildOptions.Attestations = true - return backend.Build(ctx, project, apiBuildOptions) + err = backend.Build(ctx, project, apiBuildOptions) + return jobTargetErrOr(ctx, dockerCli, opts.ProjectOptions, services, err) } diff --git a/cmd/compose/compose.go b/cmd/compose/compose.go index 5b39a33593..cf91b42243 100644 --- a/cmd/compose/compose.go +++ b/cmd/compose/compose.go @@ -175,6 +175,14 @@ func (o *ProjectOptions) WithServices(dockerCli command.Cli, fn ProjectServicesF project, metrics, err := o.ToProject(ctx, dockerCli, backend, services, warnUnsupportedAttributes, cli.WithoutEnvironmentResolution) if err != nil { + // a service name among services can genuinely be a declared job: + // every WithServices caller has nothing to act on for it, so + // report that clearly instead of the raw "no such service" below + // — same translation projectOrName already centralizes for its + // own callers (start/stop/down/...). + if jobErr, replaced := jobTargetErr(ctx, dockerCli, o, services, err); replaced { + return jobErr + } return err } diff --git a/cmd/compose/create.go b/cmd/compose/create.go index 211ec2baec..d8dc01b817 100644 --- a/cmd/compose/create.go +++ b/cmd/compose/create.go @@ -31,7 +31,6 @@ import ( "github.com/spf13/cobra" "github.com/spf13/pflag" - "github.com/docker/compose/v5/cmd/display" "github.com/docker/compose/v5/pkg/api" "github.com/docker/compose/v5/pkg/compose" ) @@ -72,18 +71,9 @@ func createCommand(p *ProjectOptions, dockerCli command.Cli, backendOptions *Bac } return nil }), - RunE: func(cmd *cobra.Command, args []string) error { - err := p.WithServices(dockerCli, func(ctx context.Context, project *types.Project, services []string) error { - return runCreate(ctx, dockerCli, backendOptions, opts, buildOpts, project, services) - })(cmd, args) - if jobErr, replaced := jobTargetErr(cmd.Context(), dockerCli, p, args, err); replaced { - if display.Mode == display.ModeJSON { - return makeJSONError(jobErr) - } - return jobErr - } - return err - }, + RunE: p.WithServices(dockerCli, func(ctx context.Context, project *types.Project, services []string) error { + return runCreate(ctx, dockerCli, backendOptions, opts, buildOpts, project, services) + }), ValidArgsFunction: completeServiceNames(dockerCli, p), } flags := cmd.Flags() @@ -108,11 +98,6 @@ 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 } diff --git a/cmd/compose/pull.go b/cmd/compose/pull.go index 4710d94f1c..a279912a9b 100644 --- a/cmd/compose/pull.go +++ b/cmd/compose/pull.go @@ -106,7 +106,7 @@ func runPull(ctx context.Context, dockerCli command.Cli, backendOptions *Backend project, _, err := opts.ToProject(ctx, dockerCli, backend, services, warnUnsupportedAttributes, cli.WithoutEnvironmentResolution) if err != nil { - return err + return jobTargetErrOr(ctx, dockerCli, opts.ProjectOptions, services, err) } project, err = opts.apply(project, services) diff --git a/cmd/compose/push.go b/cmd/compose/push.go index 178dbab584..7ddb755ea3 100644 --- a/cmd/compose/push.go +++ b/cmd/compose/push.go @@ -62,7 +62,7 @@ func runPush(ctx context.Context, dockerCli command.Cli, backendOptions *Backend project, _, err := opts.ToProject(ctx, dockerCli, backend, services, warnUnsupportedAttributes) if err != nil { - return err + return jobTargetErrOr(ctx, dockerCli, opts.ProjectOptions, services, err) } if !opts.IncludeDeps { diff --git a/cmd/compose/run.go b/cmd/compose/run.go index 852b48658b..98351ff657 100644 --- a/cmd/compose/run.go +++ b/cmd/compose/run.go @@ -364,6 +364,17 @@ func jobTargetErr(ctx context.Context, dockerCli command.Cli, p *ProjectOptions, return err, false } +// jobTargetErrOr applies jobTargetErr's translation to err when it matches, +// and returns err unchanged otherwise — the wrap every caller loading a +// project outside WithServices/projectOrName's own centralized handling +// (build, pull, push) needs around its own selection error. +func jobTargetErrOr(ctx context.Context, dockerCli command.Cli, p *ProjectOptions, names []string, err error) error { + if jobErr, replaced := jobTargetErr(ctx, dockerCli, p, names, err); replaced { + return jobErr + } + return err +} + func runRun(ctx context.Context, backend api.Compose, project *types.Project, options runOptions, createOpts createOptions, buildOpts buildOptions, dockerCli command.Cli) error { project, err := options.apply(project) if err != nil { @@ -429,6 +440,21 @@ func runRun(ctx context.Context, backend api.Compose, project *types.Project, op Index: 0, } + if _, ok := project.AllJobs()[options.Service]; ok { + if options.name != "" { + logrus.Warnf("--name has no effect on a job: the engine names its run containers itself") + } + exitCode, err := backend.RunJob(ctx, project, options.Service, runOpts) + if exitCode != 0 { + errMsg := "" + if err != nil { + errMsg = err.Error() + } + return cli.StatusError{StatusCode: exitCode, Status: errMsg, Cause: err} + } + return err + } + for name, service := range project.Services { if name == options.Service { service.StdinOpen = options.interactive @@ -466,8 +492,8 @@ func materializeManualJob(project *types.Project, name string) (*types.Project, if !ok { return project, nil } - if job.Triggers != nil && job.Triggers.Manual != nil && !*job.Triggers.Manual { - return nil, fmt.Errorf("job %q is declared with manual: false, it cannot be run manually", name) + if compose.ManualTriggerDisabled(job) { + return nil, compose.ManualTriggerDisabledErr(name) } project, err := project.WithSelectedJob(name) if err != nil { @@ -482,7 +508,7 @@ func materializeManualJob(project *types.Project, name string) (*types.Project, if err := materializeJobClosure(project, jobs, job, map[string]bool{name: true}); err != nil { return nil, err } - project.Services[name] = jobAsService(project, name, job) + project.Services[name] = compose.JobAsService(project, name, job) return project, nil } @@ -503,39 +529,13 @@ func materializeJobClosure(project *types.Project, jobs types.Jobs, job types.Jo if !isJob { continue } - if depJob.Triggers != nil && depJob.Triggers.Manual != nil && !*depJob.Triggers.Manual { + if compose.ManualTriggerDisabled(depJob) { return fmt.Errorf("job %q is declared with manual: false, it cannot be triggered even as a dependency of another job", dep) } if err := materializeJobClosure(project, jobs, depJob, seen); err != nil { return err } - project.Services[dep] = jobAsService(project, dep, depJob) + project.Services[dep] = compose.JobAsService(project, dep, depJob) } return nil } - -// jobAsService materializes a job as a service for the one-off machinery: a -// job is a ContainerSpec+WorkloadSpec, the same layers a service is made of. -// It carries the standard custom labels the loader stamps on every service — -// materialization happens after loading, so without them the containers -// created for a dependency job would be invisible to every label-driven -// path: start would silently skip them, ps/down would not see them, and the -// dependency wait would report the job as a missing dependency. -func jobAsService(project *types.Project, name string, job types.JobConfig) types.ServiceConfig { - svc := types.ServiceConfig{ - Name: name, - Profiles: job.Profiles, - Extensions: job.Extensions, - ContainerSpec: job.ContainerSpec, - WorkloadSpec: job.WorkloadSpec, - } - svc.CustomLabels = types.Labels{ - api.ProjectLabel: project.Name, - api.ServiceLabel: name, - api.VersionLabel: api.ComposeVersion, - api.WorkingDirLabel: project.WorkingDir, - api.ConfigFilesLabel: strings.Join(project.ComposeFiles, ","), - api.OneoffLabel: "False", - } - return svc -} diff --git a/cmd/compose/start.go b/cmd/compose/start.go index f3f69cec34..062efb680d 100644 --- a/cmd/compose/start.go +++ b/cmd/compose/start.go @@ -56,12 +56,6 @@ func runStart(ctx context.Context, dockerCli command.Cli, backendOptions *Backen if err != nil { return err } - if project != nil { - // with a file, refuse active scheduled jobs like up and create do - if err := rejectScheduledJobs(project); err != nil { - return err - } - } var timeout time.Duration if opts.waitTimeout > 0 { diff --git a/cmd/compose/up.go b/cmd/compose/up.go index 3716b5c8be..7bc0bd0370 100644 --- a/cmd/compose/up.go +++ b/cmd/compose/up.go @@ -242,9 +242,6 @@ func runUp( return err } - if err := rejectScheduledJobs(project); err != nil { - return err - } warnIgnoredJobs(project) err := createOptions.Apply(project) @@ -297,7 +294,10 @@ func runUp( } if upOptions.noStart { - return backend.Create(ctx, project, create) + return backend.Up(ctx, project, api.UpOptions{ + Create: create, + Start: api.StartOptions{Project: project, NoStart: true}, + }) } var consumer api.LogConsumer @@ -356,34 +356,32 @@ func runUp( }) } -// warnIgnoredJobs names the declared jobs up will not act on: manual jobs -// wait for an explicit `compose run ` trigger. +// warnIgnoredJobs names the declared manual jobs up will not act on: they +// wait for an explicit `compose run ` trigger. Scheduled jobs are not +// ignored — they are registered with the engine (see pkg/compose Up). func warnIgnoredJobs(project *types.Project) { - jobs := project.Jobs - if len(jobs) == 0 { + names := manualJobNames(project) + if len(names) == 0 { return } - names := make([]string, 0, len(jobs)) - for name := range jobs { - names = append(names, name) - } - sort.Strings(names) logrus.Warnf("jobs are not started by up; trigger them with `docker compose run`: %s", strings.Join(names, ", ")) } -// 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 { +// manualJobNames returns the sorted names of the project's jobs that up +// leaves untouched: a job with a schedule is registered with the engine +// (see pkg/compose Up), so only jobs without one wait for an explicit +// `compose run ` trigger — unless they opt out with `manual: false`. +func manualJobNames(project *types.Project) []string { 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 compose.HasSchedule(job) { + continue } - } - if len(names) == 0 { - return nil + if compose.ManualTriggerDisabled(job) { + continue + } + names = append(names, name) } sort.Strings(names) - return fmt.Errorf("scheduled jobs are not supported in this version: %s", strings.Join(names, ", ")) + return names } diff --git a/cmd/compose/up_test.go b/cmd/compose/up_test.go index 98ac8949db..f869a85f4d 100644 --- a/cmd/compose/up_test.go +++ b/cmd/compose/up_test.go @@ -180,20 +180,19 @@ services: 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}} +func TestManualJobNames(t *testing.T) { + manualTrigger := true + manual := types.JobConfig{Triggers: &types.TriggerConfig{Manual: &manualTrigger}} 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") + assert.DeepEqual(t, manualJobNames(&types.Project{}), []string{}) + assert.DeepEqual(t, manualJobNames(&types.Project{Jobs: types.Jobs{"backup": scheduled}}), []string{}) + assert.DeepEqual(t, + manualJobNames(&types.Project{Jobs: types.Jobs{"backup": scheduled, "sync": manual, "migrate": manual}}), + []string{"migrate", "sync"}, + ) } // warnIgnoredJobs must only name profile-enabled jobs: a job disabled by diff --git a/go.mod b/go.mod index 96fb967f96..a48097e5f4 100644 --- a/go.mod +++ b/go.mod @@ -27,6 +27,7 @@ require ( github.com/mattn/go-shellwords v1.0.15 github.com/mitchellh/go-ps v1.0.0 github.com/moby/buildkit v0.33.0 + github.com/moby/extensions v0.0.0-20260826001921-d37867cb107f github.com/moby/go-archive v0.3.3 github.com/moby/moby/api v1.56.0 github.com/moby/moby/client v0.6.0 @@ -54,6 +55,7 @@ require ( golang.org/x/sync v0.23.0 golang.org/x/sys v0.48.0 google.golang.org/grpc v1.83.2 + google.golang.org/protobuf v1.36.12 gotest.tools/v3 v3.5.2 tags.cncf.io/container-device-interface v1.1.1 ) @@ -123,7 +125,6 @@ require ( golang.org/x/time v0.15.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20260825221802-da73d73af1c5 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20260825221802-da73d73af1c5 // indirect - google.golang.org/protobuf v1.36.12 // indirect gopkg.in/ini.v1 v1.67.3 // indirect ) diff --git a/go.sum b/go.sum index 1d9c2d48b7..644869896a 100644 --- a/go.sum +++ b/go.sum @@ -139,6 +139,8 @@ github.com/moby/buildkit v0.33.0 h1:zBbt1FiMcTB/oFg1iCNcKa83k5Rn8MGcVjXFIcfYhuQ= github.com/moby/buildkit v0.33.0/go.mod h1:uNKSZnfMk1aSa18JiCR5BT68M/3jIDGo6XuAByUK3ek= github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= +github.com/moby/extensions v0.0.0-20260826001921-d37867cb107f h1:KVamkmaHmI/Mopjhye+cPWTL4Td7d053aAp2hU53MXs= +github.com/moby/extensions v0.0.0-20260826001921-d37867cb107f/go.mod h1:Zq+KLMU0GgWgGzFw9ouysZj1+lARdDl7Gy4nFQD/QNY= github.com/moby/go-archive v0.3.3 h1:OxxR9paxsluYi+zDUEXTTaIxtkK3viymW+Ka7vRhhME= github.com/moby/go-archive v0.3.3/go.mod h1:Npdv43fFqlhZW7Xo8fbm3ZMYFvAGNviUPqX21VERbcE= github.com/moby/locker v1.0.1 h1:fOXqR41zeveg4fFODix+1Ch4mj/gT0NE1XJbp/epuBg= diff --git a/internal/jobsapi/errors.go b/internal/jobsapi/errors.go new file mode 100644 index 0000000000..4069abeee7 --- /dev/null +++ b/internal/jobsapi/errors.go @@ -0,0 +1,53 @@ +/* + Copyright 2020 Docker Compose CLI authors + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package jobsv0 + +import ( + "github.com/containerd/errdefs" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" +) + +// MapError translates the Jobs API's gRPC status codes into containerd/errdefs +// errors, so callers can reuse the same errdefs.IsXxx checks Compose already +// applies to the container/network/volume APIs. jobsv0.Jobs methods return +// raw gRPC errors (extensionclient.Resolve is lazy: unavailability only +// surfaces as codes.Unimplemented on the first real call), so callers must +// map each returned error explicitly. +func MapError(err error) error { + if err == nil { + return nil + } + st, ok := status.FromError(err) + if !ok { + return err + } + switch st.Code() { + case codes.Unimplemented: + return errdefs.ErrNotImplemented.WithMessage("the engine does not support jobs; start dockerd with --feature jobs") + case codes.AlreadyExists: + return errdefs.ErrAlreadyExists.WithMessage(st.Message()) + case codes.FailedPrecondition: + return errdefs.ErrFailedPrecondition.WithMessage(st.Message()) + case codes.InvalidArgument: + return errdefs.ErrInvalidArgument.WithMessage(st.Message()) + case codes.NotFound: + return errdefs.ErrNotFound.WithMessage(st.Message()) + default: + return err + } +} diff --git a/internal/jobsapi/jobs.go b/internal/jobsapi/jobs.go new file mode 100644 index 0000000000..eb23fc1790 --- /dev/null +++ b/internal/jobsapi/jobs.go @@ -0,0 +1,552 @@ +/* + Copyright 2020 Docker Compose CLI authors + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +//go:generate go run github.com/moby/extensions/cmd/mobyextgen + +// Package jobsv0 defines the extension point through which the jobs extension +// exposes its client-facing API on the daemon socket. +// +// A Job is a daemon-managed resource holding a container spec plus a trigger +// declaration; each execution is a Run with a stable identity that survives +// container removal. Jobs fire manually or on a cron schedule; the daemon +// evaluates schedule triggers on its own clock, so scheduled work keeps firing +// while no client is connected. +// +// Wire-format conventions, constrained by the mobyextgen generator (unary +// methods only; no enums, oneof, or well-known types): +// - enumerations are documented strings (see the *State and policy constants); +// - timestamps are int64 Unix nanoseconds, zero meaning "not set"; +// - the container definition travels as a JSON payload (see JobSpec.ContainerSpec); +// - trigger kinds are mutually exclusive fields on a single message, and a +// trigger declaring no known kind is rejected rather than interpreted +// (see Trigger). +package jobsv0 + +import ( + "context" + + "github.com/moby/extensions" +) + +// Jobs is the jobs service. All methods are unary; blocking semantics +// (Wait) hold the request open until the condition is met. +// +// Errors are returned as gRPC status codes: InvalidArgument for a spec that +// fails validation, NotFound for an unknown job or run, AlreadyExists for a +// Create whose name is taken by a different spec (the message mentions both +// spec hashes for diagnostics only; the code is the contract), and +// FailedPrecondition for an execution refused by the concurrency policy. +type Jobs interface { + // Create registers a job without ever starting a run. It is idempotent on + // the spec hash: re-submitting the same name+spec is a no-op (Created is + // false), a new name registers the job (Created is true), and an existing + // name with a different spec fails with AlreadyExists. Registering a + // schedule job arms its trigger. + Create(ctx context.Context, req *CreateRequest) (*CreateReply, error) + + // Run executes an existing job, creating the next run. It fails with + // FailedPrecondition if the job is already running and its concurrency + // policy forbids overlap. + Run(ctx context.Context, req *RunRequest) (*RunReply, error) + + // CreateAndRun atomically registers a job if needed and starts a run. + // At least one of Name and Spec must be set; a missing name is generated + // by the daemon. Only manual jobs are served: a manual-trigger spec (or + // the nil-Trigger shorthand) is required, and a Name resolving to an + // existing schedule job is rejected with InvalidArgument. The name/spec + // resolution matrix mirrors Create: + // same spec runs the existing job, a different spec for an existing name + // fails with AlreadyExists. + CreateAndRun(ctx context.Context, req *CreateAndRunRequest) (*CreateAndRunReply, error) + + // Inspect returns a job with its latest run summary. + Inspect(ctx context.Context, req *InspectRequest) (*InspectReply, error) + + // List returns jobs matching all the given filters. + List(ctx context.Context, req *ListRequest) (*ListReply, error) + + // Pause suppresses a job's schedule trigger. An in-flight run is not + // affected, and explicit Run calls still work. Idempotent; a no-op for + // manual jobs. + Pause(ctx context.Context, req *PauseRequest) error + + // Resume re-arms a paused job's schedule trigger from the next cron tick. + // Fires missed while paused are not backfilled. Idempotent. + Resume(ctx context.Context, req *ResumeRequest) error + + // Cancel stops the job's in-flight run. Triggers keep firing; use Pause + // to suppress them. + Cancel(ctx context.Context, req *CancelRequest) (*CancelReply, error) + + // Remove deletes a job, cancelling its in-flight run and disarming its + // trigger. Run history retention is controlled by RunsRemoval; for + // retention purposes the run cancelled by Remove itself counts as + // in-flight, so remove-finished keeps its record. + Remove(ctx context.Context, req *RemoveRequest) error + + // Prune removes idle manual jobs. Schedule jobs are never pruned: an + // idle schedule job is armed, not abandoned. + Prune(ctx context.Context, req *PruneRequest) (*PruneReply, error) + + // ListRuns returns a job's runs, most recent first. + ListRuns(ctx context.Context, req *ListRunsRequest) (*ListRunsReply, error) + + // InspectRun returns a single run. + InspectRun(ctx context.Context, req *InspectRunRequest) (*InspectRunReply, error) + + // Wait blocks until the run satisfies the requested condition and + // returns the run as it is then. A run that jumps past the condition + // still satisfies it: waiting for running on a run that went straight + // from pending to a terminal state returns that terminal run. It returns + // immediately when the condition is already met. Cancelling the request + // context detaches the caller without affecting the run. + Wait(ctx context.Context, req *WaitRequest) (*WaitReply, error) +} + +// Point is single-cardinality: the jobs API has one authoritative provider, +// and the host rejects a second one at startup. +// +//mobyextgen:service=Jobs +var Point = extensions.DefineSinglePoint[Jobs]("org.mobyproject.extension.jobs.api.v0") + +// Job states. A job is a trigger declaration: it is idle between fires, even +// when it has never run. Whether the last execution worked lives on the run. +const ( + // JobStateIdle means no run is currently executing. + JobStateIdle = "idle" + // JobStateRunning means a run is pending or running. + JobStateRunning = "running" +) + +// Run states. Terminal states (succeeded, failed, timed_out, cancelled) are +// sticky: a terminal run never changes again, re-execution creates a new run. +const ( + // RunStatePending covers the window between run-record creation and + // container start. A run that fails to create its container goes from + // pending straight to failed, with Error set and no exit code. + RunStatePending = "pending" + // RunStateRunning means the container is executing. + RunStateRunning = "running" + // RunStateSucceeded means the container exited zero. + RunStateSucceeded = "succeeded" + // RunStateFailed means the container exited nonzero after its restart + // policy was exhausted, or its creation failed. + RunStateFailed = "failed" + // RunStateTimedOut means the run exceeded JobSpec.TimeoutSeconds and was + // stopped by the daemon. + RunStateTimedOut = "timed_out" + // RunStateCancelled means the run was stopped by an explicit Cancel. + RunStateCancelled = "cancelled" +) + +// Concurrency policies, applied when a trigger fires while a run is already +// in flight. An empty value defaults to forbid. +const ( + // ConcurrencyForbid drops the new fire. + ConcurrencyForbid = "forbid" + // ConcurrencyQueue defers a single fire until the current run ends; + // further fires while one is queued are dropped. + ConcurrencyQueue = "queue" +) + +// Missed-fire policies, applied when the daemon starts up after schedule +// fires were missed. An empty value defaults to one. +const ( + // MissedFiresOne fires a single catch-up run, then re-arms from the next + // cron tick. + MissedFiresOne = "one" + // MissedFiresSkip drops missed fires and re-arms from the next cron tick. + MissedFiresSkip = "skip" +) + +// Trigger kinds, as recorded on run evidence and used in list filters. +const ( + // TriggerKindManual identifies jobs fired only by explicit Run calls. + TriggerKindManual = "manual" + // TriggerKindSchedule identifies jobs fired by the cron scheduler. + TriggerKindSchedule = "schedule" +) + +// Run-history removal modes for Remove. An empty value defaults to keep. +const ( + // RunsKeep retains all run records of the removed job. + RunsKeep = "keep" + // RunsRemove drops all run records, including terminal ones. + RunsRemove = "remove" + // RunsRemoveFinished drops terminal run records and keeps in-flight ones. + RunsRemoveFinished = "remove-finished" +) + +// Wait conditions. An empty value defaults to terminal. +const ( + // WaitConditionTerminal waits until the run reaches a terminal state. + WaitConditionTerminal = "terminal" + // WaitConditionRunning waits until the run leaves pending. + WaitConditionRunning = "running" +) + +// JobSpec is the immutable definition of a job. The daemon canonicalizes the +// spec (applying defaults) before hashing it; the resulting hash is the job's +// identity for idempotent re-registration. +type JobSpec struct { + // ContainerSpec is the JSON-encoded container definition, in the exact + // format of the container-create API request body (Config, HostConfig, + // NetworkingConfig). It is decoded and validated by the same path as the + // container API; unknown fields are rejected. HostConfig.AutoRemove and + // the always/unless-stopped restart policies are rejected: run outcome + // capture requires the container to outlive its exit, and a job is + // expected to terminate. + ContainerSpec []byte `pb:"1"` + // Trigger declares what fires the job. A nil Trigger is the manual + // shorthand; a non-nil Trigger must declare exactly one kind (see + // Trigger). + Trigger *Trigger `pb:"2"` + // Labels are applied to the job itself, not to run containers. Run + // containers carry the reserved com.docker.job.id and + // com.docker.job.run-id labels instead. + Labels map[string]string `pb:"3"` + // TimeoutSeconds bounds a run's execution; past the deadline the daemon + // stops the container and the run ends timed_out. Zero means no timeout. + TimeoutSeconds int64 `pb:"4"` + // RemoveOnSuccess removes the run container after a successful exit, + // once the terminal run record is written. Logs are lost with the + // container; exit code and error are preserved on the run record. + RemoveOnSuccess bool `pb:"5"` + // RemoveOnFailure removes the run container after a failed exit. False + // by default so failed containers are kept for postmortem. + RemoveOnFailure bool `pb:"6"` + // RunHistoryLimit caps retained run records, evicting the oldest + // terminal runs first. The in-flight run is never evicted. Zero means + // the daemon default of 10000; retaining no history is deliberately + // not supported. + RunHistoryLimit uint32 `pb:"7"` +} + +// Trigger declares what fires a job. Exactly one field must be set. +// +// A nil Trigger on the JobSpec is the manual shorthand; a non-nil Trigger +// that declares no known kind is rejected with InvalidArgument rather than +// interpreted. This is deliberate: protobuf silently drops fields it does not +// know, so a spec using a trigger kind from a newer contract version must +// fail loudly on an older daemon instead of silently registering as a manual +// job that never fires. Future trigger kinds (events) are added as new +// fields under this rule. +type Trigger struct { + // Manual declares that the job fires only on explicit Run calls. + // Mutually exclusive with Schedule. + Manual bool `pb:"1"` + // Schedule fires the job on a cron schedule. Mutually exclusive with + // Manual. + Schedule *ScheduleTrigger `pb:"2"` +} + +// ScheduleTrigger fires a job on the daemon's clock. +type ScheduleTrigger struct { + // Cron is a strict five-field POSIX crontab expression. Shortcuts such + // as @daily are not accepted on the wire; clients expand them. + Cron string `pb:"1"` + // Timezone is an IANA timezone name for evaluating the expression. + // Empty means UTC, never the daemon host's local timezone, so a spec + // evaluates identically on every host. + Timezone string `pb:"2"` + // Concurrency is the policy applied when the schedule fires while a run + // is in flight. See the Concurrency constants; empty means forbid. + Concurrency string `pb:"3"` + // MissedFires is the policy applied to fires missed while the daemon was + // down. See the MissedFires constants; empty means one. + MissedFires string `pb:"4"` +} + +// Job is a registered job and its current state. +type Job struct { + // ID is the daemon-generated stable identifier. + ID string `pb:"1"` + // Name is unique across all jobs on the daemon. + Name string `pb:"2"` + // Spec is the job definition as submitted, before canonicalization. + Spec *JobSpec `pb:"3"` + // SpecHash is the canonical hash of the spec after daemon-side + // canonicalization. Callers can compare hashes to check spec equality + // before a Create. + SpecHash string `pb:"4"` + // State is the job's execution state. See the JobState constants. + State string `pb:"5"` + // Paused reports whether the schedule trigger is suppressed. + Paused bool `pb:"6"` + // NextFireAtNano is the next scheduled fire in Unix nanoseconds. Zero + // when the job is manual, paused, or currently running. + NextFireAtNano int64 `pb:"7"` + // CreatedAtNano is the registration time in Unix nanoseconds. + CreatedAtNano int64 `pb:"8"` + // UpdatedAtNano is the last state-change time in Unix nanoseconds. + UpdatedAtNano int64 `pb:"9"` + // LatestRun is the most recent run, nil if the job never ran. + LatestRun *Run `pb:"10"` +} + +// Run is a single execution attempt of a job. +type Run struct { + // ID is the daemon-generated stable identifier, valid across container + // removal and daemon restarts. + ID string `pb:"1"` + // JobID identifies the owning job. + JobID string `pb:"2"` + // Iteration is the 1-indexed position of this run in the job's history. + Iteration uint64 `pb:"3"` + // ContainerID is the container backing this run. Consumers read run logs + // from the standard container logs API using this ID; the jobs service + // does not proxy logs. + ContainerID string `pb:"4"` + // ContainerGone reports that the container was removed while the run + // record was kept; its logs are no longer available. + ContainerGone bool `pb:"5"` + // State is the run's execution state. See the RunState constants. + State string `pb:"6"` + // CreatedAtNano is the run-record creation time in Unix nanoseconds. The + // record is written before the container is created, so a run exists + // even when container creation fails. + CreatedAtNano int64 `pb:"7"` + // StartedAtNano is the container start time in Unix nanoseconds, zero if + // the container never started. + StartedAtNano int64 `pb:"8"` + // FinishedAtNano is the terminal-transition time in Unix nanoseconds, + // zero while the run is in flight. + FinishedAtNano int64 `pb:"9"` + // ExitCode is the container's exit code, nil while in flight or when the + // container never ran. + ExitCode *ExitCode `pb:"10"` + // Error describes why a run failed outside the container's own exit, + // such as a container-create failure or a container lost across a daemon + // restart. + Error string `pb:"11"` + // Trigger records what fired this run. + Trigger *TriggerEvidence `pb:"12"` +} + +// ExitCode wraps an exit code so that absence (run never exited) is +// distinguishable from zero (run exited successfully). +type ExitCode struct { + // Value is the container's exit code. + Value int64 `pb:"1"` +} + +// TriggerEvidence records what fired a run. +type TriggerEvidence struct { + // Kind is the trigger kind. See the TriggerKind constants. + Kind string `pb:"1"` + // ScheduledAtNano is the cron time the fire was due, in Unix + // nanoseconds. Zero for manual fires. + ScheduledAtNano int64 `pb:"2"` + // FiredAtNano is when the daemon actually fired the run, in Unix + // nanoseconds. + FiredAtNano int64 `pb:"3"` +} + +// CreateRequest registers a job. +type CreateRequest struct { + // Name is the job name, unique across the daemon. Required. + Name string `pb:"1"` + // Spec is the job definition. Required. + Spec *JobSpec `pb:"2"` +} + +// CreateReply reports the registered job. +type CreateReply struct { + // Job is the registered (or pre-existing identical) job. + Job *Job `pb:"1"` + // Created is false when an identical job already existed and the call + // was a no-op. + Created bool `pb:"2"` +} + +// RunRequest executes an existing job. +type RunRequest struct { + // JobRef is the job's ID or name. + JobRef string `pb:"1"` + // Reschedule makes this fire stand in for the job's next scheduled + // occurrence, which is skipped; later occurrences keep the cron + // alignment. Rejected for jobs without a schedule trigger. + Reschedule bool `pb:"2"` +} + +// RunReply reports the created run. +type RunReply struct { + // Run is the newly created run. ContainerID is set once the container + // is created. + Run *Run `pb:"1"` +} + +// CreateAndRunRequest atomically registers a job if needed and runs it. +type CreateAndRunRequest struct { + // Name is the job name. Optional when Spec is set; the daemon then + // generates one. + Name string `pb:"1"` + // Spec is the job definition. Optional when Name refers to an existing + // job. + Spec *JobSpec `pb:"2"` +} + +// CreateAndRunReply reports the resolved job and its new run. +type CreateAndRunReply struct { + // Job is the resolved job, carrying the daemon-generated name when the + // request had none. + Job *Job `pb:"1"` + // Run is the newly created run. + Run *Run `pb:"2"` + // Created is false when the job already existed. + Created bool `pb:"3"` +} + +// InspectRequest fetches one job. +type InspectRequest struct { + // JobRef is the job's ID or name. + JobRef string `pb:"1"` +} + +// InspectReply carries the inspected job. +type InspectReply struct { + // Job is the inspected job. + Job *Job `pb:"1"` +} + +// ListRequest filters jobs. All filters are conjunctive; within one filter, +// values are disjunctive, matching the filter semantics of the container API. +type ListRequest struct { + // Names filters on exact job names. + Names []string `pb:"1"` + // Labels filters on job labels, each entry either "key" or "key=value". + Labels []string `pb:"2"` + // States filters on job states. See the JobState constants. + States []string `pb:"3"` + // TriggerKinds filters on trigger kinds. See the TriggerKind constants. + TriggerKinds []string `pb:"4"` + // Paused filters on the paused flag: "true", "false", or empty for both. + Paused string `pb:"5"` + // LatestRunStates filters on the state of each job's latest run. See the + // RunState constants. + LatestRunStates []string `pb:"6"` +} + +// ListReply carries the matching jobs. +type ListReply struct { + // Jobs are the matching jobs. LatestRun is not trimmed: each entry + // carries the same fields as Inspect. + Jobs []Job `pb:"1"` +} + +// PauseRequest suppresses a job's schedule trigger. +type PauseRequest struct { + // JobRef is the job's ID or name. + JobRef string `pb:"1"` +} + +// ResumeRequest re-arms a paused job's schedule trigger. +type ResumeRequest struct { + // JobRef is the job's ID or name. + JobRef string `pb:"1"` +} + +// CancelRequest stops a job's in-flight run. +type CancelRequest struct { + // JobRef is the job's ID or name. + JobRef string `pb:"1"` +} + +// CancelReply reports the cancelled run. +type CancelReply struct { + // RunID is the ID of the run that was cancelled, empty if no run was in + // flight. + RunID string `pb:"1"` +} + +// RemoveRequest deletes a job. +type RemoveRequest struct { + // JobRef is the job's ID or name. + JobRef string `pb:"1"` + // RunsRemoval controls run-history retention. See the Runs constants; + // empty means keep. + RunsRemoval string `pb:"2"` +} + +// PruneRequest removes idle manual jobs. +type PruneRequest struct { + // Labels restricts pruning to jobs matching all entries, each either + // "key" or "key=value". + Labels []string `pb:"1"` +} + +// PruneReply reports what was pruned. +type PruneReply struct { + // RemovedJobIDs are the IDs of the removed jobs. + RemovedJobIDs []string `pb:"1"` +} + +// ListRunsRequest pages through a job's runs, most recent first. +type ListRunsRequest struct { + // JobRef is the job's ID or name. + JobRef string `pb:"1"` + // Limit caps the page size. Zero means the daemon default of 20; a + // negative value is rejected with InvalidArgument. + Limit int32 `pb:"2"` + // Before restricts the page to runs older than the given cursor; pass + // the previous reply's NextCursor to fetch the next page. + Before string `pb:"3"` +} + +// ListRunsReply carries one page of runs. +type ListRunsReply struct { + // Runs are the page's runs, most recent first. + Runs []Run `pb:"1"` + // NextCursor resumes the listing on the next call, empty on the last + // page. + NextCursor string `pb:"2"` + // CursorStale reports that the requested Before cursor was evicted from + // history; the listing restarted from the most recent run. + CursorStale bool `pb:"3"` +} + +// InspectRunRequest fetches one run. +type InspectRunRequest struct { + // JobRef is the job's ID or name. + JobRef string `pb:"1"` + // RunRef is the run's ID, or "latest" for the most recent run. + RunRef string `pb:"2"` +} + +// InspectRunReply carries the inspected run. +type InspectRunReply struct { + // Run is the inspected run. + Run *Run `pb:"1"` +} + +// WaitRequest blocks until a run reaches a condition. +type WaitRequest struct { + // JobRef is the job's ID or name. + JobRef string `pb:"1"` + // RunRef is the run's ID, or "latest" (also the default when empty). + // For an idle schedule job, "latest" resolves to the next run to fire, + // so the call may block until the next cron tick. + RunRef string `pb:"2"` + // Condition is the state to wait for. See the WaitCondition constants; + // empty means terminal. + Condition string `pb:"3"` +} + +// WaitReply carries the run in its awaited state. +type WaitReply struct { + // Run is the run that reached the condition. + Run *Run `pb:"1"` +} diff --git a/internal/jobsapi/protogen/jobs.pb.go b/internal/jobsapi/protogen/jobs.pb.go new file mode 100644 index 0000000000..46b4129a3c --- /dev/null +++ b/internal/jobsapi/protogen/jobs.pb.go @@ -0,0 +1,2129 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.36.12 +// protoc (unknown) +// source: extpoints/jobs/api/v0/jobs.proto + +package protogen + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" + unsafe "unsafe" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type CancelReply struct { + state protoimpl.MessageState `protogen:"open.v1"` + RunId string `protobuf:"bytes,1,opt,name=run_id,json=runId,proto3" json:"run_id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *CancelReply) Reset() { + *x = CancelReply{} + mi := &file_extpoints_jobs_api_v0_jobs_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *CancelReply) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CancelReply) ProtoMessage() {} + +func (x *CancelReply) ProtoReflect() protoreflect.Message { + mi := &file_extpoints_jobs_api_v0_jobs_proto_msgTypes[0] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CancelReply.ProtoReflect.Descriptor instead. +func (*CancelReply) Descriptor() ([]byte, []int) { + return file_extpoints_jobs_api_v0_jobs_proto_rawDescGZIP(), []int{0} +} + +func (x *CancelReply) GetRunId() string { + if x != nil { + return x.RunId + } + return "" +} + +type CancelRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + JobRef string `protobuf:"bytes,1,opt,name=job_ref,json=jobRef,proto3" json:"job_ref,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *CancelRequest) Reset() { + *x = CancelRequest{} + mi := &file_extpoints_jobs_api_v0_jobs_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *CancelRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CancelRequest) ProtoMessage() {} + +func (x *CancelRequest) ProtoReflect() protoreflect.Message { + mi := &file_extpoints_jobs_api_v0_jobs_proto_msgTypes[1] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CancelRequest.ProtoReflect.Descriptor instead. +func (*CancelRequest) Descriptor() ([]byte, []int) { + return file_extpoints_jobs_api_v0_jobs_proto_rawDescGZIP(), []int{1} +} + +func (x *CancelRequest) GetJobRef() string { + if x != nil { + return x.JobRef + } + return "" +} + +type CreateAndRunReply struct { + state protoimpl.MessageState `protogen:"open.v1"` + Job *Job `protobuf:"bytes,1,opt,name=job,proto3" json:"job,omitempty"` + Run *Run `protobuf:"bytes,2,opt,name=run,proto3" json:"run,omitempty"` + Created bool `protobuf:"varint,3,opt,name=created,proto3" json:"created,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *CreateAndRunReply) Reset() { + *x = CreateAndRunReply{} + mi := &file_extpoints_jobs_api_v0_jobs_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *CreateAndRunReply) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CreateAndRunReply) ProtoMessage() {} + +func (x *CreateAndRunReply) ProtoReflect() protoreflect.Message { + mi := &file_extpoints_jobs_api_v0_jobs_proto_msgTypes[2] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CreateAndRunReply.ProtoReflect.Descriptor instead. +func (*CreateAndRunReply) Descriptor() ([]byte, []int) { + return file_extpoints_jobs_api_v0_jobs_proto_rawDescGZIP(), []int{2} +} + +func (x *CreateAndRunReply) GetJob() *Job { + if x != nil { + return x.Job + } + return nil +} + +func (x *CreateAndRunReply) GetRun() *Run { + if x != nil { + return x.Run + } + return nil +} + +func (x *CreateAndRunReply) GetCreated() bool { + if x != nil { + return x.Created + } + return false +} + +type CreateAndRunRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + Spec *JobSpec `protobuf:"bytes,2,opt,name=spec,proto3" json:"spec,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *CreateAndRunRequest) Reset() { + *x = CreateAndRunRequest{} + mi := &file_extpoints_jobs_api_v0_jobs_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *CreateAndRunRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CreateAndRunRequest) ProtoMessage() {} + +func (x *CreateAndRunRequest) ProtoReflect() protoreflect.Message { + mi := &file_extpoints_jobs_api_v0_jobs_proto_msgTypes[3] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CreateAndRunRequest.ProtoReflect.Descriptor instead. +func (*CreateAndRunRequest) Descriptor() ([]byte, []int) { + return file_extpoints_jobs_api_v0_jobs_proto_rawDescGZIP(), []int{3} +} + +func (x *CreateAndRunRequest) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *CreateAndRunRequest) GetSpec() *JobSpec { + if x != nil { + return x.Spec + } + return nil +} + +type CreateReply struct { + state protoimpl.MessageState `protogen:"open.v1"` + Job *Job `protobuf:"bytes,1,opt,name=job,proto3" json:"job,omitempty"` + Created bool `protobuf:"varint,2,opt,name=created,proto3" json:"created,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *CreateReply) Reset() { + *x = CreateReply{} + mi := &file_extpoints_jobs_api_v0_jobs_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *CreateReply) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CreateReply) ProtoMessage() {} + +func (x *CreateReply) ProtoReflect() protoreflect.Message { + mi := &file_extpoints_jobs_api_v0_jobs_proto_msgTypes[4] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CreateReply.ProtoReflect.Descriptor instead. +func (*CreateReply) Descriptor() ([]byte, []int) { + return file_extpoints_jobs_api_v0_jobs_proto_rawDescGZIP(), []int{4} +} + +func (x *CreateReply) GetJob() *Job { + if x != nil { + return x.Job + } + return nil +} + +func (x *CreateReply) GetCreated() bool { + if x != nil { + return x.Created + } + return false +} + +type CreateRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + Spec *JobSpec `protobuf:"bytes,2,opt,name=spec,proto3" json:"spec,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *CreateRequest) Reset() { + *x = CreateRequest{} + mi := &file_extpoints_jobs_api_v0_jobs_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *CreateRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CreateRequest) ProtoMessage() {} + +func (x *CreateRequest) ProtoReflect() protoreflect.Message { + mi := &file_extpoints_jobs_api_v0_jobs_proto_msgTypes[5] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CreateRequest.ProtoReflect.Descriptor instead. +func (*CreateRequest) Descriptor() ([]byte, []int) { + return file_extpoints_jobs_api_v0_jobs_proto_rawDescGZIP(), []int{5} +} + +func (x *CreateRequest) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *CreateRequest) GetSpec() *JobSpec { + if x != nil { + return x.Spec + } + return nil +} + +type ExitCode struct { + state protoimpl.MessageState `protogen:"open.v1"` + Value int64 `protobuf:"varint,1,opt,name=value,proto3" json:"value,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ExitCode) Reset() { + *x = ExitCode{} + mi := &file_extpoints_jobs_api_v0_jobs_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ExitCode) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ExitCode) ProtoMessage() {} + +func (x *ExitCode) ProtoReflect() protoreflect.Message { + mi := &file_extpoints_jobs_api_v0_jobs_proto_msgTypes[6] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ExitCode.ProtoReflect.Descriptor instead. +func (*ExitCode) Descriptor() ([]byte, []int) { + return file_extpoints_jobs_api_v0_jobs_proto_rawDescGZIP(), []int{6} +} + +func (x *ExitCode) GetValue() int64 { + if x != nil { + return x.Value + } + return 0 +} + +type InspectReply struct { + state protoimpl.MessageState `protogen:"open.v1"` + Job *Job `protobuf:"bytes,1,opt,name=job,proto3" json:"job,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *InspectReply) Reset() { + *x = InspectReply{} + mi := &file_extpoints_jobs_api_v0_jobs_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *InspectReply) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*InspectReply) ProtoMessage() {} + +func (x *InspectReply) ProtoReflect() protoreflect.Message { + mi := &file_extpoints_jobs_api_v0_jobs_proto_msgTypes[7] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use InspectReply.ProtoReflect.Descriptor instead. +func (*InspectReply) Descriptor() ([]byte, []int) { + return file_extpoints_jobs_api_v0_jobs_proto_rawDescGZIP(), []int{7} +} + +func (x *InspectReply) GetJob() *Job { + if x != nil { + return x.Job + } + return nil +} + +type InspectRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + JobRef string `protobuf:"bytes,1,opt,name=job_ref,json=jobRef,proto3" json:"job_ref,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *InspectRequest) Reset() { + *x = InspectRequest{} + mi := &file_extpoints_jobs_api_v0_jobs_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *InspectRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*InspectRequest) ProtoMessage() {} + +func (x *InspectRequest) ProtoReflect() protoreflect.Message { + mi := &file_extpoints_jobs_api_v0_jobs_proto_msgTypes[8] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use InspectRequest.ProtoReflect.Descriptor instead. +func (*InspectRequest) Descriptor() ([]byte, []int) { + return file_extpoints_jobs_api_v0_jobs_proto_rawDescGZIP(), []int{8} +} + +func (x *InspectRequest) GetJobRef() string { + if x != nil { + return x.JobRef + } + return "" +} + +type InspectRunReply struct { + state protoimpl.MessageState `protogen:"open.v1"` + Run *Run `protobuf:"bytes,1,opt,name=run,proto3" json:"run,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *InspectRunReply) Reset() { + *x = InspectRunReply{} + mi := &file_extpoints_jobs_api_v0_jobs_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *InspectRunReply) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*InspectRunReply) ProtoMessage() {} + +func (x *InspectRunReply) ProtoReflect() protoreflect.Message { + mi := &file_extpoints_jobs_api_v0_jobs_proto_msgTypes[9] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use InspectRunReply.ProtoReflect.Descriptor instead. +func (*InspectRunReply) Descriptor() ([]byte, []int) { + return file_extpoints_jobs_api_v0_jobs_proto_rawDescGZIP(), []int{9} +} + +func (x *InspectRunReply) GetRun() *Run { + if x != nil { + return x.Run + } + return nil +} + +type InspectRunRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + JobRef string `protobuf:"bytes,1,opt,name=job_ref,json=jobRef,proto3" json:"job_ref,omitempty"` + RunRef string `protobuf:"bytes,2,opt,name=run_ref,json=runRef,proto3" json:"run_ref,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *InspectRunRequest) Reset() { + *x = InspectRunRequest{} + mi := &file_extpoints_jobs_api_v0_jobs_proto_msgTypes[10] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *InspectRunRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*InspectRunRequest) ProtoMessage() {} + +func (x *InspectRunRequest) ProtoReflect() protoreflect.Message { + mi := &file_extpoints_jobs_api_v0_jobs_proto_msgTypes[10] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use InspectRunRequest.ProtoReflect.Descriptor instead. +func (*InspectRunRequest) Descriptor() ([]byte, []int) { + return file_extpoints_jobs_api_v0_jobs_proto_rawDescGZIP(), []int{10} +} + +func (x *InspectRunRequest) GetJobRef() string { + if x != nil { + return x.JobRef + } + return "" +} + +func (x *InspectRunRequest) GetRunRef() string { + if x != nil { + return x.RunRef + } + return "" +} + +type Job struct { + state protoimpl.MessageState `protogen:"open.v1"` + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` + Spec *JobSpec `protobuf:"bytes,3,opt,name=spec,proto3" json:"spec,omitempty"` + SpecHash string `protobuf:"bytes,4,opt,name=spec_hash,json=specHash,proto3" json:"spec_hash,omitempty"` + State string `protobuf:"bytes,5,opt,name=state,proto3" json:"state,omitempty"` + Paused bool `protobuf:"varint,6,opt,name=paused,proto3" json:"paused,omitempty"` + NextFireAtNano int64 `protobuf:"varint,7,opt,name=next_fire_at_nano,json=nextFireAtNano,proto3" json:"next_fire_at_nano,omitempty"` + CreatedAtNano int64 `protobuf:"varint,8,opt,name=created_at_nano,json=createdAtNano,proto3" json:"created_at_nano,omitempty"` + UpdatedAtNano int64 `protobuf:"varint,9,opt,name=updated_at_nano,json=updatedAtNano,proto3" json:"updated_at_nano,omitempty"` + LatestRun *Run `protobuf:"bytes,10,opt,name=latest_run,json=latestRun,proto3" json:"latest_run,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Job) Reset() { + *x = Job{} + mi := &file_extpoints_jobs_api_v0_jobs_proto_msgTypes[11] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Job) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Job) ProtoMessage() {} + +func (x *Job) ProtoReflect() protoreflect.Message { + mi := &file_extpoints_jobs_api_v0_jobs_proto_msgTypes[11] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Job.ProtoReflect.Descriptor instead. +func (*Job) Descriptor() ([]byte, []int) { + return file_extpoints_jobs_api_v0_jobs_proto_rawDescGZIP(), []int{11} +} + +func (x *Job) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +func (x *Job) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *Job) GetSpec() *JobSpec { + if x != nil { + return x.Spec + } + return nil +} + +func (x *Job) GetSpecHash() string { + if x != nil { + return x.SpecHash + } + return "" +} + +func (x *Job) GetState() string { + if x != nil { + return x.State + } + return "" +} + +func (x *Job) GetPaused() bool { + if x != nil { + return x.Paused + } + return false +} + +func (x *Job) GetNextFireAtNano() int64 { + if x != nil { + return x.NextFireAtNano + } + return 0 +} + +func (x *Job) GetCreatedAtNano() int64 { + if x != nil { + return x.CreatedAtNano + } + return 0 +} + +func (x *Job) GetUpdatedAtNano() int64 { + if x != nil { + return x.UpdatedAtNano + } + return 0 +} + +func (x *Job) GetLatestRun() *Run { + if x != nil { + return x.LatestRun + } + return nil +} + +type JobSpec struct { + state protoimpl.MessageState `protogen:"open.v1"` + ContainerSpec []byte `protobuf:"bytes,1,opt,name=container_spec,json=containerSpec,proto3" json:"container_spec,omitempty"` + Trigger *Trigger `protobuf:"bytes,2,opt,name=trigger,proto3" json:"trigger,omitempty"` + Labels map[string]string `protobuf:"bytes,3,rep,name=labels,proto3" json:"labels,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` + TimeoutSeconds int64 `protobuf:"varint,4,opt,name=timeout_seconds,json=timeoutSeconds,proto3" json:"timeout_seconds,omitempty"` + RemoveOnSuccess bool `protobuf:"varint,5,opt,name=remove_on_success,json=removeOnSuccess,proto3" json:"remove_on_success,omitempty"` + RemoveOnFailure bool `protobuf:"varint,6,opt,name=remove_on_failure,json=removeOnFailure,proto3" json:"remove_on_failure,omitempty"` + RunHistoryLimit uint32 `protobuf:"varint,7,opt,name=run_history_limit,json=runHistoryLimit,proto3" json:"run_history_limit,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *JobSpec) Reset() { + *x = JobSpec{} + mi := &file_extpoints_jobs_api_v0_jobs_proto_msgTypes[12] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *JobSpec) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*JobSpec) ProtoMessage() {} + +func (x *JobSpec) ProtoReflect() protoreflect.Message { + mi := &file_extpoints_jobs_api_v0_jobs_proto_msgTypes[12] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use JobSpec.ProtoReflect.Descriptor instead. +func (*JobSpec) Descriptor() ([]byte, []int) { + return file_extpoints_jobs_api_v0_jobs_proto_rawDescGZIP(), []int{12} +} + +func (x *JobSpec) GetContainerSpec() []byte { + if x != nil { + return x.ContainerSpec + } + return nil +} + +func (x *JobSpec) GetTrigger() *Trigger { + if x != nil { + return x.Trigger + } + return nil +} + +func (x *JobSpec) GetLabels() map[string]string { + if x != nil { + return x.Labels + } + return nil +} + +func (x *JobSpec) GetTimeoutSeconds() int64 { + if x != nil { + return x.TimeoutSeconds + } + return 0 +} + +func (x *JobSpec) GetRemoveOnSuccess() bool { + if x != nil { + return x.RemoveOnSuccess + } + return false +} + +func (x *JobSpec) GetRemoveOnFailure() bool { + if x != nil { + return x.RemoveOnFailure + } + return false +} + +func (x *JobSpec) GetRunHistoryLimit() uint32 { + if x != nil { + return x.RunHistoryLimit + } + return 0 +} + +type ListReply struct { + state protoimpl.MessageState `protogen:"open.v1"` + Jobs []*Job `protobuf:"bytes,1,rep,name=jobs,proto3" json:"jobs,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ListReply) Reset() { + *x = ListReply{} + mi := &file_extpoints_jobs_api_v0_jobs_proto_msgTypes[13] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ListReply) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListReply) ProtoMessage() {} + +func (x *ListReply) ProtoReflect() protoreflect.Message { + mi := &file_extpoints_jobs_api_v0_jobs_proto_msgTypes[13] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListReply.ProtoReflect.Descriptor instead. +func (*ListReply) Descriptor() ([]byte, []int) { + return file_extpoints_jobs_api_v0_jobs_proto_rawDescGZIP(), []int{13} +} + +func (x *ListReply) GetJobs() []*Job { + if x != nil { + return x.Jobs + } + return nil +} + +type ListRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Names []string `protobuf:"bytes,1,rep,name=names,proto3" json:"names,omitempty"` + Labels []string `protobuf:"bytes,2,rep,name=labels,proto3" json:"labels,omitempty"` + States []string `protobuf:"bytes,3,rep,name=states,proto3" json:"states,omitempty"` + TriggerKinds []string `protobuf:"bytes,4,rep,name=trigger_kinds,json=triggerKinds,proto3" json:"trigger_kinds,omitempty"` + Paused string `protobuf:"bytes,5,opt,name=paused,proto3" json:"paused,omitempty"` + LatestRunStates []string `protobuf:"bytes,6,rep,name=latest_run_states,json=latestRunStates,proto3" json:"latest_run_states,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ListRequest) Reset() { + *x = ListRequest{} + mi := &file_extpoints_jobs_api_v0_jobs_proto_msgTypes[14] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ListRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListRequest) ProtoMessage() {} + +func (x *ListRequest) ProtoReflect() protoreflect.Message { + mi := &file_extpoints_jobs_api_v0_jobs_proto_msgTypes[14] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListRequest.ProtoReflect.Descriptor instead. +func (*ListRequest) Descriptor() ([]byte, []int) { + return file_extpoints_jobs_api_v0_jobs_proto_rawDescGZIP(), []int{14} +} + +func (x *ListRequest) GetNames() []string { + if x != nil { + return x.Names + } + return nil +} + +func (x *ListRequest) GetLabels() []string { + if x != nil { + return x.Labels + } + return nil +} + +func (x *ListRequest) GetStates() []string { + if x != nil { + return x.States + } + return nil +} + +func (x *ListRequest) GetTriggerKinds() []string { + if x != nil { + return x.TriggerKinds + } + return nil +} + +func (x *ListRequest) GetPaused() string { + if x != nil { + return x.Paused + } + return "" +} + +func (x *ListRequest) GetLatestRunStates() []string { + if x != nil { + return x.LatestRunStates + } + return nil +} + +type ListRunsReply struct { + state protoimpl.MessageState `protogen:"open.v1"` + Runs []*Run `protobuf:"bytes,1,rep,name=runs,proto3" json:"runs,omitempty"` + NextCursor string `protobuf:"bytes,2,opt,name=next_cursor,json=nextCursor,proto3" json:"next_cursor,omitempty"` + CursorStale bool `protobuf:"varint,3,opt,name=cursor_stale,json=cursorStale,proto3" json:"cursor_stale,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ListRunsReply) Reset() { + *x = ListRunsReply{} + mi := &file_extpoints_jobs_api_v0_jobs_proto_msgTypes[15] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ListRunsReply) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListRunsReply) ProtoMessage() {} + +func (x *ListRunsReply) ProtoReflect() protoreflect.Message { + mi := &file_extpoints_jobs_api_v0_jobs_proto_msgTypes[15] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListRunsReply.ProtoReflect.Descriptor instead. +func (*ListRunsReply) Descriptor() ([]byte, []int) { + return file_extpoints_jobs_api_v0_jobs_proto_rawDescGZIP(), []int{15} +} + +func (x *ListRunsReply) GetRuns() []*Run { + if x != nil { + return x.Runs + } + return nil +} + +func (x *ListRunsReply) GetNextCursor() string { + if x != nil { + return x.NextCursor + } + return "" +} + +func (x *ListRunsReply) GetCursorStale() bool { + if x != nil { + return x.CursorStale + } + return false +} + +type ListRunsRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + JobRef string `protobuf:"bytes,1,opt,name=job_ref,json=jobRef,proto3" json:"job_ref,omitempty"` + Limit int32 `protobuf:"varint,2,opt,name=limit,proto3" json:"limit,omitempty"` + Before string `protobuf:"bytes,3,opt,name=before,proto3" json:"before,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ListRunsRequest) Reset() { + *x = ListRunsRequest{} + mi := &file_extpoints_jobs_api_v0_jobs_proto_msgTypes[16] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ListRunsRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListRunsRequest) ProtoMessage() {} + +func (x *ListRunsRequest) ProtoReflect() protoreflect.Message { + mi := &file_extpoints_jobs_api_v0_jobs_proto_msgTypes[16] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListRunsRequest.ProtoReflect.Descriptor instead. +func (*ListRunsRequest) Descriptor() ([]byte, []int) { + return file_extpoints_jobs_api_v0_jobs_proto_rawDescGZIP(), []int{16} +} + +func (x *ListRunsRequest) GetJobRef() string { + if x != nil { + return x.JobRef + } + return "" +} + +func (x *ListRunsRequest) GetLimit() int32 { + if x != nil { + return x.Limit + } + return 0 +} + +func (x *ListRunsRequest) GetBefore() string { + if x != nil { + return x.Before + } + return "" +} + +type PauseRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + JobRef string `protobuf:"bytes,1,opt,name=job_ref,json=jobRef,proto3" json:"job_ref,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *PauseRequest) Reset() { + *x = PauseRequest{} + mi := &file_extpoints_jobs_api_v0_jobs_proto_msgTypes[17] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *PauseRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PauseRequest) ProtoMessage() {} + +func (x *PauseRequest) ProtoReflect() protoreflect.Message { + mi := &file_extpoints_jobs_api_v0_jobs_proto_msgTypes[17] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PauseRequest.ProtoReflect.Descriptor instead. +func (*PauseRequest) Descriptor() ([]byte, []int) { + return file_extpoints_jobs_api_v0_jobs_proto_rawDescGZIP(), []int{17} +} + +func (x *PauseRequest) GetJobRef() string { + if x != nil { + return x.JobRef + } + return "" +} + +type PruneReply struct { + state protoimpl.MessageState `protogen:"open.v1"` + RemovedJobIds []string `protobuf:"bytes,1,rep,name=removed_job_ids,json=removedJobIds,proto3" json:"removed_job_ids,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *PruneReply) Reset() { + *x = PruneReply{} + mi := &file_extpoints_jobs_api_v0_jobs_proto_msgTypes[18] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *PruneReply) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PruneReply) ProtoMessage() {} + +func (x *PruneReply) ProtoReflect() protoreflect.Message { + mi := &file_extpoints_jobs_api_v0_jobs_proto_msgTypes[18] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PruneReply.ProtoReflect.Descriptor instead. +func (*PruneReply) Descriptor() ([]byte, []int) { + return file_extpoints_jobs_api_v0_jobs_proto_rawDescGZIP(), []int{18} +} + +func (x *PruneReply) GetRemovedJobIds() []string { + if x != nil { + return x.RemovedJobIds + } + return nil +} + +type PruneRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Labels []string `protobuf:"bytes,1,rep,name=labels,proto3" json:"labels,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *PruneRequest) Reset() { + *x = PruneRequest{} + mi := &file_extpoints_jobs_api_v0_jobs_proto_msgTypes[19] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *PruneRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PruneRequest) ProtoMessage() {} + +func (x *PruneRequest) ProtoReflect() protoreflect.Message { + mi := &file_extpoints_jobs_api_v0_jobs_proto_msgTypes[19] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PruneRequest.ProtoReflect.Descriptor instead. +func (*PruneRequest) Descriptor() ([]byte, []int) { + return file_extpoints_jobs_api_v0_jobs_proto_rawDescGZIP(), []int{19} +} + +func (x *PruneRequest) GetLabels() []string { + if x != nil { + return x.Labels + } + return nil +} + +type RemoveRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + JobRef string `protobuf:"bytes,1,opt,name=job_ref,json=jobRef,proto3" json:"job_ref,omitempty"` + RunsRemoval string `protobuf:"bytes,2,opt,name=runs_removal,json=runsRemoval,proto3" json:"runs_removal,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *RemoveRequest) Reset() { + *x = RemoveRequest{} + mi := &file_extpoints_jobs_api_v0_jobs_proto_msgTypes[20] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *RemoveRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RemoveRequest) ProtoMessage() {} + +func (x *RemoveRequest) ProtoReflect() protoreflect.Message { + mi := &file_extpoints_jobs_api_v0_jobs_proto_msgTypes[20] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RemoveRequest.ProtoReflect.Descriptor instead. +func (*RemoveRequest) Descriptor() ([]byte, []int) { + return file_extpoints_jobs_api_v0_jobs_proto_rawDescGZIP(), []int{20} +} + +func (x *RemoveRequest) GetJobRef() string { + if x != nil { + return x.JobRef + } + return "" +} + +func (x *RemoveRequest) GetRunsRemoval() string { + if x != nil { + return x.RunsRemoval + } + return "" +} + +type ResumeRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + JobRef string `protobuf:"bytes,1,opt,name=job_ref,json=jobRef,proto3" json:"job_ref,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ResumeRequest) Reset() { + *x = ResumeRequest{} + mi := &file_extpoints_jobs_api_v0_jobs_proto_msgTypes[21] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ResumeRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ResumeRequest) ProtoMessage() {} + +func (x *ResumeRequest) ProtoReflect() protoreflect.Message { + mi := &file_extpoints_jobs_api_v0_jobs_proto_msgTypes[21] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ResumeRequest.ProtoReflect.Descriptor instead. +func (*ResumeRequest) Descriptor() ([]byte, []int) { + return file_extpoints_jobs_api_v0_jobs_proto_rawDescGZIP(), []int{21} +} + +func (x *ResumeRequest) GetJobRef() string { + if x != nil { + return x.JobRef + } + return "" +} + +type Run struct { + state protoimpl.MessageState `protogen:"open.v1"` + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + JobId string `protobuf:"bytes,2,opt,name=job_id,json=jobId,proto3" json:"job_id,omitempty"` + Iteration uint64 `protobuf:"varint,3,opt,name=iteration,proto3" json:"iteration,omitempty"` + ContainerId string `protobuf:"bytes,4,opt,name=container_id,json=containerId,proto3" json:"container_id,omitempty"` + ContainerGone bool `protobuf:"varint,5,opt,name=container_gone,json=containerGone,proto3" json:"container_gone,omitempty"` + State string `protobuf:"bytes,6,opt,name=state,proto3" json:"state,omitempty"` + CreatedAtNano int64 `protobuf:"varint,7,opt,name=created_at_nano,json=createdAtNano,proto3" json:"created_at_nano,omitempty"` + StartedAtNano int64 `protobuf:"varint,8,opt,name=started_at_nano,json=startedAtNano,proto3" json:"started_at_nano,omitempty"` + FinishedAtNano int64 `protobuf:"varint,9,opt,name=finished_at_nano,json=finishedAtNano,proto3" json:"finished_at_nano,omitempty"` + ExitCode *ExitCode `protobuf:"bytes,10,opt,name=exit_code,json=exitCode,proto3" json:"exit_code,omitempty"` + Error string `protobuf:"bytes,11,opt,name=error,proto3" json:"error,omitempty"` + Trigger *TriggerEvidence `protobuf:"bytes,12,opt,name=trigger,proto3" json:"trigger,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Run) Reset() { + *x = Run{} + mi := &file_extpoints_jobs_api_v0_jobs_proto_msgTypes[22] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Run) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Run) ProtoMessage() {} + +func (x *Run) ProtoReflect() protoreflect.Message { + mi := &file_extpoints_jobs_api_v0_jobs_proto_msgTypes[22] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Run.ProtoReflect.Descriptor instead. +func (*Run) Descriptor() ([]byte, []int) { + return file_extpoints_jobs_api_v0_jobs_proto_rawDescGZIP(), []int{22} +} + +func (x *Run) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +func (x *Run) GetJobId() string { + if x != nil { + return x.JobId + } + return "" +} + +func (x *Run) GetIteration() uint64 { + if x != nil { + return x.Iteration + } + return 0 +} + +func (x *Run) GetContainerId() string { + if x != nil { + return x.ContainerId + } + return "" +} + +func (x *Run) GetContainerGone() bool { + if x != nil { + return x.ContainerGone + } + return false +} + +func (x *Run) GetState() string { + if x != nil { + return x.State + } + return "" +} + +func (x *Run) GetCreatedAtNano() int64 { + if x != nil { + return x.CreatedAtNano + } + return 0 +} + +func (x *Run) GetStartedAtNano() int64 { + if x != nil { + return x.StartedAtNano + } + return 0 +} + +func (x *Run) GetFinishedAtNano() int64 { + if x != nil { + return x.FinishedAtNano + } + return 0 +} + +func (x *Run) GetExitCode() *ExitCode { + if x != nil { + return x.ExitCode + } + return nil +} + +func (x *Run) GetError() string { + if x != nil { + return x.Error + } + return "" +} + +func (x *Run) GetTrigger() *TriggerEvidence { + if x != nil { + return x.Trigger + } + return nil +} + +type RunReply struct { + state protoimpl.MessageState `protogen:"open.v1"` + Run *Run `protobuf:"bytes,1,opt,name=run,proto3" json:"run,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *RunReply) Reset() { + *x = RunReply{} + mi := &file_extpoints_jobs_api_v0_jobs_proto_msgTypes[23] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *RunReply) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RunReply) ProtoMessage() {} + +func (x *RunReply) ProtoReflect() protoreflect.Message { + mi := &file_extpoints_jobs_api_v0_jobs_proto_msgTypes[23] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RunReply.ProtoReflect.Descriptor instead. +func (*RunReply) Descriptor() ([]byte, []int) { + return file_extpoints_jobs_api_v0_jobs_proto_rawDescGZIP(), []int{23} +} + +func (x *RunReply) GetRun() *Run { + if x != nil { + return x.Run + } + return nil +} + +type RunRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + JobRef string `protobuf:"bytes,1,opt,name=job_ref,json=jobRef,proto3" json:"job_ref,omitempty"` + Reschedule bool `protobuf:"varint,2,opt,name=reschedule,proto3" json:"reschedule,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *RunRequest) Reset() { + *x = RunRequest{} + mi := &file_extpoints_jobs_api_v0_jobs_proto_msgTypes[24] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *RunRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RunRequest) ProtoMessage() {} + +func (x *RunRequest) ProtoReflect() protoreflect.Message { + mi := &file_extpoints_jobs_api_v0_jobs_proto_msgTypes[24] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RunRequest.ProtoReflect.Descriptor instead. +func (*RunRequest) Descriptor() ([]byte, []int) { + return file_extpoints_jobs_api_v0_jobs_proto_rawDescGZIP(), []int{24} +} + +func (x *RunRequest) GetJobRef() string { + if x != nil { + return x.JobRef + } + return "" +} + +func (x *RunRequest) GetReschedule() bool { + if x != nil { + return x.Reschedule + } + return false +} + +type ScheduleTrigger struct { + state protoimpl.MessageState `protogen:"open.v1"` + Cron string `protobuf:"bytes,1,opt,name=cron,proto3" json:"cron,omitempty"` + Timezone string `protobuf:"bytes,2,opt,name=timezone,proto3" json:"timezone,omitempty"` + Concurrency string `protobuf:"bytes,3,opt,name=concurrency,proto3" json:"concurrency,omitempty"` + MissedFires string `protobuf:"bytes,4,opt,name=missed_fires,json=missedFires,proto3" json:"missed_fires,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ScheduleTrigger) Reset() { + *x = ScheduleTrigger{} + mi := &file_extpoints_jobs_api_v0_jobs_proto_msgTypes[25] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ScheduleTrigger) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ScheduleTrigger) ProtoMessage() {} + +func (x *ScheduleTrigger) ProtoReflect() protoreflect.Message { + mi := &file_extpoints_jobs_api_v0_jobs_proto_msgTypes[25] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ScheduleTrigger.ProtoReflect.Descriptor instead. +func (*ScheduleTrigger) Descriptor() ([]byte, []int) { + return file_extpoints_jobs_api_v0_jobs_proto_rawDescGZIP(), []int{25} +} + +func (x *ScheduleTrigger) GetCron() string { + if x != nil { + return x.Cron + } + return "" +} + +func (x *ScheduleTrigger) GetTimezone() string { + if x != nil { + return x.Timezone + } + return "" +} + +func (x *ScheduleTrigger) GetConcurrency() string { + if x != nil { + return x.Concurrency + } + return "" +} + +func (x *ScheduleTrigger) GetMissedFires() string { + if x != nil { + return x.MissedFires + } + return "" +} + +type Trigger struct { + state protoimpl.MessageState `protogen:"open.v1"` + Manual bool `protobuf:"varint,1,opt,name=manual,proto3" json:"manual,omitempty"` + Schedule *ScheduleTrigger `protobuf:"bytes,2,opt,name=schedule,proto3" json:"schedule,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Trigger) Reset() { + *x = Trigger{} + mi := &file_extpoints_jobs_api_v0_jobs_proto_msgTypes[26] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Trigger) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Trigger) ProtoMessage() {} + +func (x *Trigger) ProtoReflect() protoreflect.Message { + mi := &file_extpoints_jobs_api_v0_jobs_proto_msgTypes[26] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Trigger.ProtoReflect.Descriptor instead. +func (*Trigger) Descriptor() ([]byte, []int) { + return file_extpoints_jobs_api_v0_jobs_proto_rawDescGZIP(), []int{26} +} + +func (x *Trigger) GetManual() bool { + if x != nil { + return x.Manual + } + return false +} + +func (x *Trigger) GetSchedule() *ScheduleTrigger { + if x != nil { + return x.Schedule + } + return nil +} + +type TriggerEvidence struct { + state protoimpl.MessageState `protogen:"open.v1"` + Kind string `protobuf:"bytes,1,opt,name=kind,proto3" json:"kind,omitempty"` + ScheduledAtNano int64 `protobuf:"varint,2,opt,name=scheduled_at_nano,json=scheduledAtNano,proto3" json:"scheduled_at_nano,omitempty"` + FiredAtNano int64 `protobuf:"varint,3,opt,name=fired_at_nano,json=firedAtNano,proto3" json:"fired_at_nano,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *TriggerEvidence) Reset() { + *x = TriggerEvidence{} + mi := &file_extpoints_jobs_api_v0_jobs_proto_msgTypes[27] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *TriggerEvidence) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TriggerEvidence) ProtoMessage() {} + +func (x *TriggerEvidence) ProtoReflect() protoreflect.Message { + mi := &file_extpoints_jobs_api_v0_jobs_proto_msgTypes[27] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use TriggerEvidence.ProtoReflect.Descriptor instead. +func (*TriggerEvidence) Descriptor() ([]byte, []int) { + return file_extpoints_jobs_api_v0_jobs_proto_rawDescGZIP(), []int{27} +} + +func (x *TriggerEvidence) GetKind() string { + if x != nil { + return x.Kind + } + return "" +} + +func (x *TriggerEvidence) GetScheduledAtNano() int64 { + if x != nil { + return x.ScheduledAtNano + } + return 0 +} + +func (x *TriggerEvidence) GetFiredAtNano() int64 { + if x != nil { + return x.FiredAtNano + } + return 0 +} + +type WaitReply struct { + state protoimpl.MessageState `protogen:"open.v1"` + Run *Run `protobuf:"bytes,1,opt,name=run,proto3" json:"run,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *WaitReply) Reset() { + *x = WaitReply{} + mi := &file_extpoints_jobs_api_v0_jobs_proto_msgTypes[28] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *WaitReply) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*WaitReply) ProtoMessage() {} + +func (x *WaitReply) ProtoReflect() protoreflect.Message { + mi := &file_extpoints_jobs_api_v0_jobs_proto_msgTypes[28] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use WaitReply.ProtoReflect.Descriptor instead. +func (*WaitReply) Descriptor() ([]byte, []int) { + return file_extpoints_jobs_api_v0_jobs_proto_rawDescGZIP(), []int{28} +} + +func (x *WaitReply) GetRun() *Run { + if x != nil { + return x.Run + } + return nil +} + +type WaitRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + JobRef string `protobuf:"bytes,1,opt,name=job_ref,json=jobRef,proto3" json:"job_ref,omitempty"` + RunRef string `protobuf:"bytes,2,opt,name=run_ref,json=runRef,proto3" json:"run_ref,omitempty"` + Condition string `protobuf:"bytes,3,opt,name=condition,proto3" json:"condition,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *WaitRequest) Reset() { + *x = WaitRequest{} + mi := &file_extpoints_jobs_api_v0_jobs_proto_msgTypes[29] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *WaitRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*WaitRequest) ProtoMessage() {} + +func (x *WaitRequest) ProtoReflect() protoreflect.Message { + mi := &file_extpoints_jobs_api_v0_jobs_proto_msgTypes[29] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use WaitRequest.ProtoReflect.Descriptor instead. +func (*WaitRequest) Descriptor() ([]byte, []int) { + return file_extpoints_jobs_api_v0_jobs_proto_rawDescGZIP(), []int{29} +} + +func (x *WaitRequest) GetJobRef() string { + if x != nil { + return x.JobRef + } + return "" +} + +func (x *WaitRequest) GetRunRef() string { + if x != nil { + return x.RunRef + } + return "" +} + +func (x *WaitRequest) GetCondition() string { + if x != nil { + return x.Condition + } + return "" +} + +type PauseResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *PauseResponse) Reset() { + *x = PauseResponse{} + mi := &file_extpoints_jobs_api_v0_jobs_proto_msgTypes[30] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *PauseResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PauseResponse) ProtoMessage() {} + +func (x *PauseResponse) ProtoReflect() protoreflect.Message { + mi := &file_extpoints_jobs_api_v0_jobs_proto_msgTypes[30] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PauseResponse.ProtoReflect.Descriptor instead. +func (*PauseResponse) Descriptor() ([]byte, []int) { + return file_extpoints_jobs_api_v0_jobs_proto_rawDescGZIP(), []int{30} +} + +type ResumeResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ResumeResponse) Reset() { + *x = ResumeResponse{} + mi := &file_extpoints_jobs_api_v0_jobs_proto_msgTypes[31] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ResumeResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ResumeResponse) ProtoMessage() {} + +func (x *ResumeResponse) ProtoReflect() protoreflect.Message { + mi := &file_extpoints_jobs_api_v0_jobs_proto_msgTypes[31] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ResumeResponse.ProtoReflect.Descriptor instead. +func (*ResumeResponse) Descriptor() ([]byte, []int) { + return file_extpoints_jobs_api_v0_jobs_proto_rawDescGZIP(), []int{31} +} + +type RemoveResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *RemoveResponse) Reset() { + *x = RemoveResponse{} + mi := &file_extpoints_jobs_api_v0_jobs_proto_msgTypes[32] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *RemoveResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RemoveResponse) ProtoMessage() {} + +func (x *RemoveResponse) ProtoReflect() protoreflect.Message { + mi := &file_extpoints_jobs_api_v0_jobs_proto_msgTypes[32] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RemoveResponse.ProtoReflect.Descriptor instead. +func (*RemoveResponse) Descriptor() ([]byte, []int) { + return file_extpoints_jobs_api_v0_jobs_proto_rawDescGZIP(), []int{32} +} + +var File_extpoints_jobs_api_v0_jobs_proto protoreflect.FileDescriptor + +const file_extpoints_jobs_api_v0_jobs_proto_rawDesc = "" + + "\n" + + " extpoints/jobs/api/v0/jobs.proto\x12%org.mobyproject.extension.jobs.api.v0\"$\n" + + "\vCancelReply\x12\x15\n" + + "\x06run_id\x18\x01 \x01(\tR\x05runId\"(\n" + + "\rCancelRequest\x12\x17\n" + + "\ajob_ref\x18\x01 \x01(\tR\x06jobRef\"\xa9\x01\n" + + "\x11CreateAndRunReply\x12<\n" + + "\x03job\x18\x01 \x01(\v2*.org.mobyproject.extension.jobs.api.v0.JobR\x03job\x12<\n" + + "\x03run\x18\x02 \x01(\v2*.org.mobyproject.extension.jobs.api.v0.RunR\x03run\x12\x18\n" + + "\acreated\x18\x03 \x01(\bR\acreated\"m\n" + + "\x13CreateAndRunRequest\x12\x12\n" + + "\x04name\x18\x01 \x01(\tR\x04name\x12B\n" + + "\x04spec\x18\x02 \x01(\v2..org.mobyproject.extension.jobs.api.v0.JobSpecR\x04spec\"e\n" + + "\vCreateReply\x12<\n" + + "\x03job\x18\x01 \x01(\v2*.org.mobyproject.extension.jobs.api.v0.JobR\x03job\x12\x18\n" + + "\acreated\x18\x02 \x01(\bR\acreated\"g\n" + + "\rCreateRequest\x12\x12\n" + + "\x04name\x18\x01 \x01(\tR\x04name\x12B\n" + + "\x04spec\x18\x02 \x01(\v2..org.mobyproject.extension.jobs.api.v0.JobSpecR\x04spec\" \n" + + "\bExitCode\x12\x14\n" + + "\x05value\x18\x01 \x01(\x03R\x05value\"L\n" + + "\fInspectReply\x12<\n" + + "\x03job\x18\x01 \x01(\v2*.org.mobyproject.extension.jobs.api.v0.JobR\x03job\")\n" + + "\x0eInspectRequest\x12\x17\n" + + "\ajob_ref\x18\x01 \x01(\tR\x06jobRef\"O\n" + + "\x0fInspectRunReply\x12<\n" + + "\x03run\x18\x01 \x01(\v2*.org.mobyproject.extension.jobs.api.v0.RunR\x03run\"E\n" + + "\x11InspectRunRequest\x12\x17\n" + + "\ajob_ref\x18\x01 \x01(\tR\x06jobRef\x12\x17\n" + + "\arun_ref\x18\x02 \x01(\tR\x06runRef\"\xfe\x02\n" + + "\x03Job\x12\x0e\n" + + "\x02id\x18\x01 \x01(\tR\x02id\x12\x12\n" + + "\x04name\x18\x02 \x01(\tR\x04name\x12B\n" + + "\x04spec\x18\x03 \x01(\v2..org.mobyproject.extension.jobs.api.v0.JobSpecR\x04spec\x12\x1b\n" + + "\tspec_hash\x18\x04 \x01(\tR\bspecHash\x12\x14\n" + + "\x05state\x18\x05 \x01(\tR\x05state\x12\x16\n" + + "\x06paused\x18\x06 \x01(\bR\x06paused\x12)\n" + + "\x11next_fire_at_nano\x18\a \x01(\x03R\x0enextFireAtNano\x12&\n" + + "\x0fcreated_at_nano\x18\b \x01(\x03R\rcreatedAtNano\x12&\n" + + "\x0fupdated_at_nano\x18\t \x01(\x03R\rupdatedAtNano\x12I\n" + + "\n" + + "latest_run\x18\n" + + " \x01(\v2*.org.mobyproject.extension.jobs.api.v0.RunR\tlatestRun\"\xb6\x03\n" + + "\aJobSpec\x12%\n" + + "\x0econtainer_spec\x18\x01 \x01(\fR\rcontainerSpec\x12H\n" + + "\atrigger\x18\x02 \x01(\v2..org.mobyproject.extension.jobs.api.v0.TriggerR\atrigger\x12R\n" + + "\x06labels\x18\x03 \x03(\v2:.org.mobyproject.extension.jobs.api.v0.JobSpec.LabelsEntryR\x06labels\x12'\n" + + "\x0ftimeout_seconds\x18\x04 \x01(\x03R\x0etimeoutSeconds\x12*\n" + + "\x11remove_on_success\x18\x05 \x01(\bR\x0fremoveOnSuccess\x12*\n" + + "\x11remove_on_failure\x18\x06 \x01(\bR\x0fremoveOnFailure\x12*\n" + + "\x11run_history_limit\x18\a \x01(\rR\x0frunHistoryLimit\x1a9\n" + + "\vLabelsEntry\x12\x10\n" + + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + + "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"K\n" + + "\tListReply\x12>\n" + + "\x04jobs\x18\x01 \x03(\v2*.org.mobyproject.extension.jobs.api.v0.JobR\x04jobs\"\xbc\x01\n" + + "\vListRequest\x12\x14\n" + + "\x05names\x18\x01 \x03(\tR\x05names\x12\x16\n" + + "\x06labels\x18\x02 \x03(\tR\x06labels\x12\x16\n" + + "\x06states\x18\x03 \x03(\tR\x06states\x12#\n" + + "\rtrigger_kinds\x18\x04 \x03(\tR\ftriggerKinds\x12\x16\n" + + "\x06paused\x18\x05 \x01(\tR\x06paused\x12*\n" + + "\x11latest_run_states\x18\x06 \x03(\tR\x0flatestRunStates\"\x93\x01\n" + + "\rListRunsReply\x12>\n" + + "\x04runs\x18\x01 \x03(\v2*.org.mobyproject.extension.jobs.api.v0.RunR\x04runs\x12\x1f\n" + + "\vnext_cursor\x18\x02 \x01(\tR\n" + + "nextCursor\x12!\n" + + "\fcursor_stale\x18\x03 \x01(\bR\vcursorStale\"X\n" + + "\x0fListRunsRequest\x12\x17\n" + + "\ajob_ref\x18\x01 \x01(\tR\x06jobRef\x12\x14\n" + + "\x05limit\x18\x02 \x01(\x05R\x05limit\x12\x16\n" + + "\x06before\x18\x03 \x01(\tR\x06before\"'\n" + + "\fPauseRequest\x12\x17\n" + + "\ajob_ref\x18\x01 \x01(\tR\x06jobRef\"4\n" + + "\n" + + "PruneReply\x12&\n" + + "\x0fremoved_job_ids\x18\x01 \x03(\tR\rremovedJobIds\"&\n" + + "\fPruneRequest\x12\x16\n" + + "\x06labels\x18\x01 \x03(\tR\x06labels\"K\n" + + "\rRemoveRequest\x12\x17\n" + + "\ajob_ref\x18\x01 \x01(\tR\x06jobRef\x12!\n" + + "\fruns_removal\x18\x02 \x01(\tR\vrunsRemoval\"(\n" + + "\rResumeRequest\x12\x17\n" + + "\ajob_ref\x18\x01 \x01(\tR\x06jobRef\"\xda\x03\n" + + "\x03Run\x12\x0e\n" + + "\x02id\x18\x01 \x01(\tR\x02id\x12\x15\n" + + "\x06job_id\x18\x02 \x01(\tR\x05jobId\x12\x1c\n" + + "\titeration\x18\x03 \x01(\x04R\titeration\x12!\n" + + "\fcontainer_id\x18\x04 \x01(\tR\vcontainerId\x12%\n" + + "\x0econtainer_gone\x18\x05 \x01(\bR\rcontainerGone\x12\x14\n" + + "\x05state\x18\x06 \x01(\tR\x05state\x12&\n" + + "\x0fcreated_at_nano\x18\a \x01(\x03R\rcreatedAtNano\x12&\n" + + "\x0fstarted_at_nano\x18\b \x01(\x03R\rstartedAtNano\x12(\n" + + "\x10finished_at_nano\x18\t \x01(\x03R\x0efinishedAtNano\x12L\n" + + "\texit_code\x18\n" + + " \x01(\v2/.org.mobyproject.extension.jobs.api.v0.ExitCodeR\bexitCode\x12\x14\n" + + "\x05error\x18\v \x01(\tR\x05error\x12P\n" + + "\atrigger\x18\f \x01(\v26.org.mobyproject.extension.jobs.api.v0.TriggerEvidenceR\atrigger\"H\n" + + "\bRunReply\x12<\n" + + "\x03run\x18\x01 \x01(\v2*.org.mobyproject.extension.jobs.api.v0.RunR\x03run\"E\n" + + "\n" + + "RunRequest\x12\x17\n" + + "\ajob_ref\x18\x01 \x01(\tR\x06jobRef\x12\x1e\n" + + "\n" + + "reschedule\x18\x02 \x01(\bR\n" + + "reschedule\"\x86\x01\n" + + "\x0fScheduleTrigger\x12\x12\n" + + "\x04cron\x18\x01 \x01(\tR\x04cron\x12\x1a\n" + + "\btimezone\x18\x02 \x01(\tR\btimezone\x12 \n" + + "\vconcurrency\x18\x03 \x01(\tR\vconcurrency\x12!\n" + + "\fmissed_fires\x18\x04 \x01(\tR\vmissedFires\"u\n" + + "\aTrigger\x12\x16\n" + + "\x06manual\x18\x01 \x01(\bR\x06manual\x12R\n" + + "\bschedule\x18\x02 \x01(\v26.org.mobyproject.extension.jobs.api.v0.ScheduleTriggerR\bschedule\"u\n" + + "\x0fTriggerEvidence\x12\x12\n" + + "\x04kind\x18\x01 \x01(\tR\x04kind\x12*\n" + + "\x11scheduled_at_nano\x18\x02 \x01(\x03R\x0fscheduledAtNano\x12\"\n" + + "\rfired_at_nano\x18\x03 \x01(\x03R\vfiredAtNano\"I\n" + + "\tWaitReply\x12<\n" + + "\x03run\x18\x01 \x01(\v2*.org.mobyproject.extension.jobs.api.v0.RunR\x03run\"]\n" + + "\vWaitRequest\x12\x17\n" + + "\ajob_ref\x18\x01 \x01(\tR\x06jobRef\x12\x17\n" + + "\arun_ref\x18\x02 \x01(\tR\x06runRef\x12\x1c\n" + + "\tcondition\x18\x03 \x01(\tR\tcondition\"\x0f\n" + + "\rPauseResponse\"\x10\n" + + "\x0eResumeResponse\"\x10\n" + + "\x0eRemoveResponse2\x80\f\n" + + "\x04Jobs\x12r\n" + + "\x06Create\x124.org.mobyproject.extension.jobs.api.v0.CreateRequest\x1a2.org.mobyproject.extension.jobs.api.v0.CreateReply\x12i\n" + + "\x03Run\x121.org.mobyproject.extension.jobs.api.v0.RunRequest\x1a/.org.mobyproject.extension.jobs.api.v0.RunReply\x12\x84\x01\n" + + "\fCreateAndRun\x12:.org.mobyproject.extension.jobs.api.v0.CreateAndRunRequest\x1a8.org.mobyproject.extension.jobs.api.v0.CreateAndRunReply\x12u\n" + + "\aInspect\x125.org.mobyproject.extension.jobs.api.v0.InspectRequest\x1a3.org.mobyproject.extension.jobs.api.v0.InspectReply\x12l\n" + + "\x04List\x122.org.mobyproject.extension.jobs.api.v0.ListRequest\x1a0.org.mobyproject.extension.jobs.api.v0.ListReply\x12r\n" + + "\x05Pause\x123.org.mobyproject.extension.jobs.api.v0.PauseRequest\x1a4.org.mobyproject.extension.jobs.api.v0.PauseResponse\x12u\n" + + "\x06Resume\x124.org.mobyproject.extension.jobs.api.v0.ResumeRequest\x1a5.org.mobyproject.extension.jobs.api.v0.ResumeResponse\x12r\n" + + "\x06Cancel\x124.org.mobyproject.extension.jobs.api.v0.CancelRequest\x1a2.org.mobyproject.extension.jobs.api.v0.CancelReply\x12u\n" + + "\x06Remove\x124.org.mobyproject.extension.jobs.api.v0.RemoveRequest\x1a5.org.mobyproject.extension.jobs.api.v0.RemoveResponse\x12o\n" + + "\x05Prune\x123.org.mobyproject.extension.jobs.api.v0.PruneRequest\x1a1.org.mobyproject.extension.jobs.api.v0.PruneReply\x12x\n" + + "\bListRuns\x126.org.mobyproject.extension.jobs.api.v0.ListRunsRequest\x1a4.org.mobyproject.extension.jobs.api.v0.ListRunsReply\x12~\n" + + "\n" + + "InspectRun\x128.org.mobyproject.extension.jobs.api.v0.InspectRunRequest\x1a6.org.mobyproject.extension.jobs.api.v0.InspectRunReply\x12l\n" + + "\x04Wait\x122.org.mobyproject.extension.jobs.api.v0.WaitRequest\x1a0.org.mobyproject.extension.jobs.api.v0.WaitReplyB8Z6github.com/moby/moby/v2/extpoints/jobs/api/v0/protogenb\x06proto3" + +var ( + file_extpoints_jobs_api_v0_jobs_proto_rawDescOnce sync.Once + file_extpoints_jobs_api_v0_jobs_proto_rawDescData []byte +) + +func file_extpoints_jobs_api_v0_jobs_proto_rawDescGZIP() []byte { + file_extpoints_jobs_api_v0_jobs_proto_rawDescOnce.Do(func() { + file_extpoints_jobs_api_v0_jobs_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_extpoints_jobs_api_v0_jobs_proto_rawDesc), len(file_extpoints_jobs_api_v0_jobs_proto_rawDesc))) + }) + return file_extpoints_jobs_api_v0_jobs_proto_rawDescData +} + +var file_extpoints_jobs_api_v0_jobs_proto_msgTypes = make([]protoimpl.MessageInfo, 34) +var file_extpoints_jobs_api_v0_jobs_proto_goTypes = []any{ + (*CancelReply)(nil), // 0: org.mobyproject.extension.jobs.api.v0.CancelReply + (*CancelRequest)(nil), // 1: org.mobyproject.extension.jobs.api.v0.CancelRequest + (*CreateAndRunReply)(nil), // 2: org.mobyproject.extension.jobs.api.v0.CreateAndRunReply + (*CreateAndRunRequest)(nil), // 3: org.mobyproject.extension.jobs.api.v0.CreateAndRunRequest + (*CreateReply)(nil), // 4: org.mobyproject.extension.jobs.api.v0.CreateReply + (*CreateRequest)(nil), // 5: org.mobyproject.extension.jobs.api.v0.CreateRequest + (*ExitCode)(nil), // 6: org.mobyproject.extension.jobs.api.v0.ExitCode + (*InspectReply)(nil), // 7: org.mobyproject.extension.jobs.api.v0.InspectReply + (*InspectRequest)(nil), // 8: org.mobyproject.extension.jobs.api.v0.InspectRequest + (*InspectRunReply)(nil), // 9: org.mobyproject.extension.jobs.api.v0.InspectRunReply + (*InspectRunRequest)(nil), // 10: org.mobyproject.extension.jobs.api.v0.InspectRunRequest + (*Job)(nil), // 11: org.mobyproject.extension.jobs.api.v0.Job + (*JobSpec)(nil), // 12: org.mobyproject.extension.jobs.api.v0.JobSpec + (*ListReply)(nil), // 13: org.mobyproject.extension.jobs.api.v0.ListReply + (*ListRequest)(nil), // 14: org.mobyproject.extension.jobs.api.v0.ListRequest + (*ListRunsReply)(nil), // 15: org.mobyproject.extension.jobs.api.v0.ListRunsReply + (*ListRunsRequest)(nil), // 16: org.mobyproject.extension.jobs.api.v0.ListRunsRequest + (*PauseRequest)(nil), // 17: org.mobyproject.extension.jobs.api.v0.PauseRequest + (*PruneReply)(nil), // 18: org.mobyproject.extension.jobs.api.v0.PruneReply + (*PruneRequest)(nil), // 19: org.mobyproject.extension.jobs.api.v0.PruneRequest + (*RemoveRequest)(nil), // 20: org.mobyproject.extension.jobs.api.v0.RemoveRequest + (*ResumeRequest)(nil), // 21: org.mobyproject.extension.jobs.api.v0.ResumeRequest + (*Run)(nil), // 22: org.mobyproject.extension.jobs.api.v0.Run + (*RunReply)(nil), // 23: org.mobyproject.extension.jobs.api.v0.RunReply + (*RunRequest)(nil), // 24: org.mobyproject.extension.jobs.api.v0.RunRequest + (*ScheduleTrigger)(nil), // 25: org.mobyproject.extension.jobs.api.v0.ScheduleTrigger + (*Trigger)(nil), // 26: org.mobyproject.extension.jobs.api.v0.Trigger + (*TriggerEvidence)(nil), // 27: org.mobyproject.extension.jobs.api.v0.TriggerEvidence + (*WaitReply)(nil), // 28: org.mobyproject.extension.jobs.api.v0.WaitReply + (*WaitRequest)(nil), // 29: org.mobyproject.extension.jobs.api.v0.WaitRequest + (*PauseResponse)(nil), // 30: org.mobyproject.extension.jobs.api.v0.PauseResponse + (*ResumeResponse)(nil), // 31: org.mobyproject.extension.jobs.api.v0.ResumeResponse + (*RemoveResponse)(nil), // 32: org.mobyproject.extension.jobs.api.v0.RemoveResponse + nil, // 33: org.mobyproject.extension.jobs.api.v0.JobSpec.LabelsEntry +} +var file_extpoints_jobs_api_v0_jobs_proto_depIdxs = []int32{ + 11, // 0: org.mobyproject.extension.jobs.api.v0.CreateAndRunReply.job:type_name -> org.mobyproject.extension.jobs.api.v0.Job + 22, // 1: org.mobyproject.extension.jobs.api.v0.CreateAndRunReply.run:type_name -> org.mobyproject.extension.jobs.api.v0.Run + 12, // 2: org.mobyproject.extension.jobs.api.v0.CreateAndRunRequest.spec:type_name -> org.mobyproject.extension.jobs.api.v0.JobSpec + 11, // 3: org.mobyproject.extension.jobs.api.v0.CreateReply.job:type_name -> org.mobyproject.extension.jobs.api.v0.Job + 12, // 4: org.mobyproject.extension.jobs.api.v0.CreateRequest.spec:type_name -> org.mobyproject.extension.jobs.api.v0.JobSpec + 11, // 5: org.mobyproject.extension.jobs.api.v0.InspectReply.job:type_name -> org.mobyproject.extension.jobs.api.v0.Job + 22, // 6: org.mobyproject.extension.jobs.api.v0.InspectRunReply.run:type_name -> org.mobyproject.extension.jobs.api.v0.Run + 12, // 7: org.mobyproject.extension.jobs.api.v0.Job.spec:type_name -> org.mobyproject.extension.jobs.api.v0.JobSpec + 22, // 8: org.mobyproject.extension.jobs.api.v0.Job.latest_run:type_name -> org.mobyproject.extension.jobs.api.v0.Run + 26, // 9: org.mobyproject.extension.jobs.api.v0.JobSpec.trigger:type_name -> org.mobyproject.extension.jobs.api.v0.Trigger + 33, // 10: org.mobyproject.extension.jobs.api.v0.JobSpec.labels:type_name -> org.mobyproject.extension.jobs.api.v0.JobSpec.LabelsEntry + 11, // 11: org.mobyproject.extension.jobs.api.v0.ListReply.jobs:type_name -> org.mobyproject.extension.jobs.api.v0.Job + 22, // 12: org.mobyproject.extension.jobs.api.v0.ListRunsReply.runs:type_name -> org.mobyproject.extension.jobs.api.v0.Run + 6, // 13: org.mobyproject.extension.jobs.api.v0.Run.exit_code:type_name -> org.mobyproject.extension.jobs.api.v0.ExitCode + 27, // 14: org.mobyproject.extension.jobs.api.v0.Run.trigger:type_name -> org.mobyproject.extension.jobs.api.v0.TriggerEvidence + 22, // 15: org.mobyproject.extension.jobs.api.v0.RunReply.run:type_name -> org.mobyproject.extension.jobs.api.v0.Run + 25, // 16: org.mobyproject.extension.jobs.api.v0.Trigger.schedule:type_name -> org.mobyproject.extension.jobs.api.v0.ScheduleTrigger + 22, // 17: org.mobyproject.extension.jobs.api.v0.WaitReply.run:type_name -> org.mobyproject.extension.jobs.api.v0.Run + 5, // 18: org.mobyproject.extension.jobs.api.v0.Jobs.Create:input_type -> org.mobyproject.extension.jobs.api.v0.CreateRequest + 24, // 19: org.mobyproject.extension.jobs.api.v0.Jobs.Run:input_type -> org.mobyproject.extension.jobs.api.v0.RunRequest + 3, // 20: org.mobyproject.extension.jobs.api.v0.Jobs.CreateAndRun:input_type -> org.mobyproject.extension.jobs.api.v0.CreateAndRunRequest + 8, // 21: org.mobyproject.extension.jobs.api.v0.Jobs.Inspect:input_type -> org.mobyproject.extension.jobs.api.v0.InspectRequest + 14, // 22: org.mobyproject.extension.jobs.api.v0.Jobs.List:input_type -> org.mobyproject.extension.jobs.api.v0.ListRequest + 17, // 23: org.mobyproject.extension.jobs.api.v0.Jobs.Pause:input_type -> org.mobyproject.extension.jobs.api.v0.PauseRequest + 21, // 24: org.mobyproject.extension.jobs.api.v0.Jobs.Resume:input_type -> org.mobyproject.extension.jobs.api.v0.ResumeRequest + 1, // 25: org.mobyproject.extension.jobs.api.v0.Jobs.Cancel:input_type -> org.mobyproject.extension.jobs.api.v0.CancelRequest + 20, // 26: org.mobyproject.extension.jobs.api.v0.Jobs.Remove:input_type -> org.mobyproject.extension.jobs.api.v0.RemoveRequest + 19, // 27: org.mobyproject.extension.jobs.api.v0.Jobs.Prune:input_type -> org.mobyproject.extension.jobs.api.v0.PruneRequest + 16, // 28: org.mobyproject.extension.jobs.api.v0.Jobs.ListRuns:input_type -> org.mobyproject.extension.jobs.api.v0.ListRunsRequest + 10, // 29: org.mobyproject.extension.jobs.api.v0.Jobs.InspectRun:input_type -> org.mobyproject.extension.jobs.api.v0.InspectRunRequest + 29, // 30: org.mobyproject.extension.jobs.api.v0.Jobs.Wait:input_type -> org.mobyproject.extension.jobs.api.v0.WaitRequest + 4, // 31: org.mobyproject.extension.jobs.api.v0.Jobs.Create:output_type -> org.mobyproject.extension.jobs.api.v0.CreateReply + 23, // 32: org.mobyproject.extension.jobs.api.v0.Jobs.Run:output_type -> org.mobyproject.extension.jobs.api.v0.RunReply + 2, // 33: org.mobyproject.extension.jobs.api.v0.Jobs.CreateAndRun:output_type -> org.mobyproject.extension.jobs.api.v0.CreateAndRunReply + 7, // 34: org.mobyproject.extension.jobs.api.v0.Jobs.Inspect:output_type -> org.mobyproject.extension.jobs.api.v0.InspectReply + 13, // 35: org.mobyproject.extension.jobs.api.v0.Jobs.List:output_type -> org.mobyproject.extension.jobs.api.v0.ListReply + 30, // 36: org.mobyproject.extension.jobs.api.v0.Jobs.Pause:output_type -> org.mobyproject.extension.jobs.api.v0.PauseResponse + 31, // 37: org.mobyproject.extension.jobs.api.v0.Jobs.Resume:output_type -> org.mobyproject.extension.jobs.api.v0.ResumeResponse + 0, // 38: org.mobyproject.extension.jobs.api.v0.Jobs.Cancel:output_type -> org.mobyproject.extension.jobs.api.v0.CancelReply + 32, // 39: org.mobyproject.extension.jobs.api.v0.Jobs.Remove:output_type -> org.mobyproject.extension.jobs.api.v0.RemoveResponse + 18, // 40: org.mobyproject.extension.jobs.api.v0.Jobs.Prune:output_type -> org.mobyproject.extension.jobs.api.v0.PruneReply + 15, // 41: org.mobyproject.extension.jobs.api.v0.Jobs.ListRuns:output_type -> org.mobyproject.extension.jobs.api.v0.ListRunsReply + 9, // 42: org.mobyproject.extension.jobs.api.v0.Jobs.InspectRun:output_type -> org.mobyproject.extension.jobs.api.v0.InspectRunReply + 28, // 43: org.mobyproject.extension.jobs.api.v0.Jobs.Wait:output_type -> org.mobyproject.extension.jobs.api.v0.WaitReply + 31, // [31:44] is the sub-list for method output_type + 18, // [18:31] is the sub-list for method input_type + 18, // [18:18] is the sub-list for extension type_name + 18, // [18:18] is the sub-list for extension extendee + 0, // [0:18] is the sub-list for field type_name +} + +func init() { file_extpoints_jobs_api_v0_jobs_proto_init() } +func file_extpoints_jobs_api_v0_jobs_proto_init() { + if File_extpoints_jobs_api_v0_jobs_proto != nil { + return + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeFor[x]().PkgPath(), + RawDescriptor: unsafe.Slice(unsafe.StringData(file_extpoints_jobs_api_v0_jobs_proto_rawDesc), len(file_extpoints_jobs_api_v0_jobs_proto_rawDesc)), + NumEnums: 0, + NumMessages: 34, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_extpoints_jobs_api_v0_jobs_proto_goTypes, + DependencyIndexes: file_extpoints_jobs_api_v0_jobs_proto_depIdxs, + MessageInfos: file_extpoints_jobs_api_v0_jobs_proto_msgTypes, + }.Build() + File_extpoints_jobs_api_v0_jobs_proto = out.File + file_extpoints_jobs_api_v0_jobs_proto_goTypes = nil + file_extpoints_jobs_api_v0_jobs_proto_depIdxs = nil +} diff --git a/internal/jobsapi/protogen/wire.gen.go b/internal/jobsapi/protogen/wire.gen.go new file mode 100644 index 0000000000..1336439855 --- /dev/null +++ b/internal/jobsapi/protogen/wire.gen.go @@ -0,0 +1,1262 @@ +// Code generated by mobyextgen. DO NOT EDIT. +package protogen + +import ( + context "context" + extensions "github.com/moby/extensions" + clientpoint "github.com/moby/extensions/clientpoint" + serverpoint "github.com/moby/extensions/serverpoint" + jobsv0 "github.com/docker/compose/v5/internal/jobsapi" + grpc "google.golang.org/grpc" +) + +// serviceName is the point's fully-qualified gRPC service name. +const serviceName = "org.mobyproject.extension.jobs.api.v0.Jobs" + +const ( + methodCreate = "/" + serviceName + "/Create" + methodRun = "/" + serviceName + "/Run" + methodCreateAndRun = "/" + serviceName + "/CreateAndRun" + methodInspect = "/" + serviceName + "/Inspect" + methodList = "/" + serviceName + "/List" + methodPause = "/" + serviceName + "/Pause" + methodResume = "/" + serviceName + "/Resume" + methodCancel = "/" + serviceName + "/Cancel" + methodRemove = "/" + serviceName + "/Remove" + methodPrune = "/" + serviceName + "/Prune" + methodListRuns = "/" + serviceName + "/ListRuns" + methodInspectRun = "/" + serviceName + "/InspectRun" + methodWait = "/" + serviceName + "/Wait" +) + +// JobsServer is the server side of the point's gRPC service. It is the +// proto-level shape of the point, not the point's Go interface: a contract +// method returning a bare error returns an empty response message here. +type JobsServer interface { + Create(context.Context, *CreateRequest) (*CreateReply, error) + Run(context.Context, *RunRequest) (*RunReply, error) + CreateAndRun(context.Context, *CreateAndRunRequest) (*CreateAndRunReply, error) + Inspect(context.Context, *InspectRequest) (*InspectReply, error) + List(context.Context, *ListRequest) (*ListReply, error) + Pause(context.Context, *PauseRequest) (*PauseResponse, error) + Resume(context.Context, *ResumeRequest) (*ResumeResponse, error) + Cancel(context.Context, *CancelRequest) (*CancelReply, error) + Remove(context.Context, *RemoveRequest) (*RemoveResponse, error) + Prune(context.Context, *PruneRequest) (*PruneReply, error) + ListRuns(context.Context, *ListRunsRequest) (*ListRunsReply, error) + InspectRun(context.Context, *InspectRunRequest) (*InspectRunReply, error) + Wait(context.Context, *WaitRequest) (*WaitReply, error) +} + +// serviceDesc describes the point's gRPC service to a server. HandlerType is +// what a registrar type-checks an implementation against, so registering the +// wrong provider for this point is caught at registration. +var serviceDesc = grpc.ServiceDesc{ + ServiceName: serviceName, + HandlerType: (*JobsServer)(nil), + Metadata: "extpoints/jobs/api/v0/jobs.proto", + Methods: []grpc.MethodDesc{ + {MethodName: "Create", Handler: handleCreate}, + {MethodName: "Run", Handler: handleRun}, + {MethodName: "CreateAndRun", Handler: handleCreateAndRun}, + {MethodName: "Inspect", Handler: handleInspect}, + {MethodName: "List", Handler: handleList}, + {MethodName: "Pause", Handler: handlePause}, + {MethodName: "Resume", Handler: handleResume}, + {MethodName: "Cancel", Handler: handleCancel}, + {MethodName: "Remove", Handler: handleRemove}, + {MethodName: "Prune", Handler: handlePrune}, + {MethodName: "ListRuns", Handler: handleListRuns}, + {MethodName: "InspectRun", Handler: handleInspectRun}, + {MethodName: "Wait", Handler: handleWait}, + }, +} + +func handleCreate(srv any, ctx context.Context, dec func(any) error, interceptor grpc.UnaryServerInterceptor) (any, error) { + in := new(CreateRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(JobsServer).Create(ctx, in) + } + info := &grpc.UnaryServerInfo{Server: srv, FullMethod: methodCreate} + return interceptor(ctx, in, info, func(ctx context.Context, req any) (any, error) { + return srv.(JobsServer).Create(ctx, req.(*CreateRequest)) + }) +} + +func handleRun(srv any, ctx context.Context, dec func(any) error, interceptor grpc.UnaryServerInterceptor) (any, error) { + in := new(RunRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(JobsServer).Run(ctx, in) + } + info := &grpc.UnaryServerInfo{Server: srv, FullMethod: methodRun} + return interceptor(ctx, in, info, func(ctx context.Context, req any) (any, error) { + return srv.(JobsServer).Run(ctx, req.(*RunRequest)) + }) +} + +func handleCreateAndRun(srv any, ctx context.Context, dec func(any) error, interceptor grpc.UnaryServerInterceptor) (any, error) { + in := new(CreateAndRunRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(JobsServer).CreateAndRun(ctx, in) + } + info := &grpc.UnaryServerInfo{Server: srv, FullMethod: methodCreateAndRun} + return interceptor(ctx, in, info, func(ctx context.Context, req any) (any, error) { + return srv.(JobsServer).CreateAndRun(ctx, req.(*CreateAndRunRequest)) + }) +} + +func handleInspect(srv any, ctx context.Context, dec func(any) error, interceptor grpc.UnaryServerInterceptor) (any, error) { + in := new(InspectRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(JobsServer).Inspect(ctx, in) + } + info := &grpc.UnaryServerInfo{Server: srv, FullMethod: methodInspect} + return interceptor(ctx, in, info, func(ctx context.Context, req any) (any, error) { + return srv.(JobsServer).Inspect(ctx, req.(*InspectRequest)) + }) +} + +func handleList(srv any, ctx context.Context, dec func(any) error, interceptor grpc.UnaryServerInterceptor) (any, error) { + in := new(ListRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(JobsServer).List(ctx, in) + } + info := &grpc.UnaryServerInfo{Server: srv, FullMethod: methodList} + return interceptor(ctx, in, info, func(ctx context.Context, req any) (any, error) { + return srv.(JobsServer).List(ctx, req.(*ListRequest)) + }) +} + +func handlePause(srv any, ctx context.Context, dec func(any) error, interceptor grpc.UnaryServerInterceptor) (any, error) { + in := new(PauseRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(JobsServer).Pause(ctx, in) + } + info := &grpc.UnaryServerInfo{Server: srv, FullMethod: methodPause} + return interceptor(ctx, in, info, func(ctx context.Context, req any) (any, error) { + return srv.(JobsServer).Pause(ctx, req.(*PauseRequest)) + }) +} + +func handleResume(srv any, ctx context.Context, dec func(any) error, interceptor grpc.UnaryServerInterceptor) (any, error) { + in := new(ResumeRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(JobsServer).Resume(ctx, in) + } + info := &grpc.UnaryServerInfo{Server: srv, FullMethod: methodResume} + return interceptor(ctx, in, info, func(ctx context.Context, req any) (any, error) { + return srv.(JobsServer).Resume(ctx, req.(*ResumeRequest)) + }) +} + +func handleCancel(srv any, ctx context.Context, dec func(any) error, interceptor grpc.UnaryServerInterceptor) (any, error) { + in := new(CancelRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(JobsServer).Cancel(ctx, in) + } + info := &grpc.UnaryServerInfo{Server: srv, FullMethod: methodCancel} + return interceptor(ctx, in, info, func(ctx context.Context, req any) (any, error) { + return srv.(JobsServer).Cancel(ctx, req.(*CancelRequest)) + }) +} + +func handleRemove(srv any, ctx context.Context, dec func(any) error, interceptor grpc.UnaryServerInterceptor) (any, error) { + in := new(RemoveRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(JobsServer).Remove(ctx, in) + } + info := &grpc.UnaryServerInfo{Server: srv, FullMethod: methodRemove} + return interceptor(ctx, in, info, func(ctx context.Context, req any) (any, error) { + return srv.(JobsServer).Remove(ctx, req.(*RemoveRequest)) + }) +} + +func handlePrune(srv any, ctx context.Context, dec func(any) error, interceptor grpc.UnaryServerInterceptor) (any, error) { + in := new(PruneRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(JobsServer).Prune(ctx, in) + } + info := &grpc.UnaryServerInfo{Server: srv, FullMethod: methodPrune} + return interceptor(ctx, in, info, func(ctx context.Context, req any) (any, error) { + return srv.(JobsServer).Prune(ctx, req.(*PruneRequest)) + }) +} + +func handleListRuns(srv any, ctx context.Context, dec func(any) error, interceptor grpc.UnaryServerInterceptor) (any, error) { + in := new(ListRunsRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(JobsServer).ListRuns(ctx, in) + } + info := &grpc.UnaryServerInfo{Server: srv, FullMethod: methodListRuns} + return interceptor(ctx, in, info, func(ctx context.Context, req any) (any, error) { + return srv.(JobsServer).ListRuns(ctx, req.(*ListRunsRequest)) + }) +} + +func handleInspectRun(srv any, ctx context.Context, dec func(any) error, interceptor grpc.UnaryServerInterceptor) (any, error) { + in := new(InspectRunRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(JobsServer).InspectRun(ctx, in) + } + info := &grpc.UnaryServerInfo{Server: srv, FullMethod: methodInspectRun} + return interceptor(ctx, in, info, func(ctx context.Context, req any) (any, error) { + return srv.(JobsServer).InspectRun(ctx, req.(*InspectRunRequest)) + }) +} + +func handleWait(srv any, ctx context.Context, dec func(any) error, interceptor grpc.UnaryServerInterceptor) (any, error) { + in := new(WaitRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(JobsServer).Wait(ctx, in) + } + info := &grpc.UnaryServerInfo{Server: srv, FullMethod: methodWait} + return interceptor(ctx, in, info, func(ctx context.Context, req any) (any, error) { + return srv.(JobsServer).Wait(ctx, req.(*WaitRequest)) + }) +} + +// JobsClient calls the point's gRPC service. It is exported so a client +// outside the framework - one calling a service an extension publishes on the +// API socket - can reach it with a plain gRPC client. +type JobsClient interface { + Create(ctx context.Context, in *CreateRequest, opts ...grpc.CallOption) (*CreateReply, error) + Run(ctx context.Context, in *RunRequest, opts ...grpc.CallOption) (*RunReply, error) + CreateAndRun(ctx context.Context, in *CreateAndRunRequest, opts ...grpc.CallOption) (*CreateAndRunReply, error) + Inspect(ctx context.Context, in *InspectRequest, opts ...grpc.CallOption) (*InspectReply, error) + List(ctx context.Context, in *ListRequest, opts ...grpc.CallOption) (*ListReply, error) + Pause(ctx context.Context, in *PauseRequest, opts ...grpc.CallOption) (*PauseResponse, error) + Resume(ctx context.Context, in *ResumeRequest, opts ...grpc.CallOption) (*ResumeResponse, error) + Cancel(ctx context.Context, in *CancelRequest, opts ...grpc.CallOption) (*CancelReply, error) + Remove(ctx context.Context, in *RemoveRequest, opts ...grpc.CallOption) (*RemoveResponse, error) + Prune(ctx context.Context, in *PruneRequest, opts ...grpc.CallOption) (*PruneReply, error) + ListRuns(ctx context.Context, in *ListRunsRequest, opts ...grpc.CallOption) (*ListRunsReply, error) + InspectRun(ctx context.Context, in *InspectRunRequest, opts ...grpc.CallOption) (*InspectRunReply, error) + Wait(ctx context.Context, in *WaitRequest, opts ...grpc.CallOption) (*WaitReply, error) +} + +// NewJobsClient returns a client for the point's gRPC service on cc. +func NewJobsClient(cc grpc.ClientConnInterface) JobsClient { return &serviceClient{cc: cc} } + +type serviceClient struct{ cc grpc.ClientConnInterface } + +func (c *serviceClient) Create(ctx context.Context, in *CreateRequest, opts ...grpc.CallOption) (*CreateReply, error) { + out := new(CreateReply) + if err := c.cc.Invoke(ctx, methodCreate, in, out, append([]grpc.CallOption{grpc.StaticMethod()}, opts...)...); err != nil { + return nil, err + } + return out, nil +} + +func (c *serviceClient) Run(ctx context.Context, in *RunRequest, opts ...grpc.CallOption) (*RunReply, error) { + out := new(RunReply) + if err := c.cc.Invoke(ctx, methodRun, in, out, append([]grpc.CallOption{grpc.StaticMethod()}, opts...)...); err != nil { + return nil, err + } + return out, nil +} + +func (c *serviceClient) CreateAndRun(ctx context.Context, in *CreateAndRunRequest, opts ...grpc.CallOption) (*CreateAndRunReply, error) { + out := new(CreateAndRunReply) + if err := c.cc.Invoke(ctx, methodCreateAndRun, in, out, append([]grpc.CallOption{grpc.StaticMethod()}, opts...)...); err != nil { + return nil, err + } + return out, nil +} + +func (c *serviceClient) Inspect(ctx context.Context, in *InspectRequest, opts ...grpc.CallOption) (*InspectReply, error) { + out := new(InspectReply) + if err := c.cc.Invoke(ctx, methodInspect, in, out, append([]grpc.CallOption{grpc.StaticMethod()}, opts...)...); err != nil { + return nil, err + } + return out, nil +} + +func (c *serviceClient) List(ctx context.Context, in *ListRequest, opts ...grpc.CallOption) (*ListReply, error) { + out := new(ListReply) + if err := c.cc.Invoke(ctx, methodList, in, out, append([]grpc.CallOption{grpc.StaticMethod()}, opts...)...); err != nil { + return nil, err + } + return out, nil +} + +func (c *serviceClient) Pause(ctx context.Context, in *PauseRequest, opts ...grpc.CallOption) (*PauseResponse, error) { + out := new(PauseResponse) + if err := c.cc.Invoke(ctx, methodPause, in, out, append([]grpc.CallOption{grpc.StaticMethod()}, opts...)...); err != nil { + return nil, err + } + return out, nil +} + +func (c *serviceClient) Resume(ctx context.Context, in *ResumeRequest, opts ...grpc.CallOption) (*ResumeResponse, error) { + out := new(ResumeResponse) + if err := c.cc.Invoke(ctx, methodResume, in, out, append([]grpc.CallOption{grpc.StaticMethod()}, opts...)...); err != nil { + return nil, err + } + return out, nil +} + +func (c *serviceClient) Cancel(ctx context.Context, in *CancelRequest, opts ...grpc.CallOption) (*CancelReply, error) { + out := new(CancelReply) + if err := c.cc.Invoke(ctx, methodCancel, in, out, append([]grpc.CallOption{grpc.StaticMethod()}, opts...)...); err != nil { + return nil, err + } + return out, nil +} + +func (c *serviceClient) Remove(ctx context.Context, in *RemoveRequest, opts ...grpc.CallOption) (*RemoveResponse, error) { + out := new(RemoveResponse) + if err := c.cc.Invoke(ctx, methodRemove, in, out, append([]grpc.CallOption{grpc.StaticMethod()}, opts...)...); err != nil { + return nil, err + } + return out, nil +} + +func (c *serviceClient) Prune(ctx context.Context, in *PruneRequest, opts ...grpc.CallOption) (*PruneReply, error) { + out := new(PruneReply) + if err := c.cc.Invoke(ctx, methodPrune, in, out, append([]grpc.CallOption{grpc.StaticMethod()}, opts...)...); err != nil { + return nil, err + } + return out, nil +} + +func (c *serviceClient) ListRuns(ctx context.Context, in *ListRunsRequest, opts ...grpc.CallOption) (*ListRunsReply, error) { + out := new(ListRunsReply) + if err := c.cc.Invoke(ctx, methodListRuns, in, out, append([]grpc.CallOption{grpc.StaticMethod()}, opts...)...); err != nil { + return nil, err + } + return out, nil +} + +func (c *serviceClient) InspectRun(ctx context.Context, in *InspectRunRequest, opts ...grpc.CallOption) (*InspectRunReply, error) { + out := new(InspectRunReply) + if err := c.cc.Invoke(ctx, methodInspectRun, in, out, append([]grpc.CallOption{grpc.StaticMethod()}, opts...)...); err != nil { + return nil, err + } + return out, nil +} + +func (c *serviceClient) Wait(ctx context.Context, in *WaitRequest, opts ...grpc.CallOption) (*WaitReply, error) { + out := new(WaitReply) + if err := c.cc.Invoke(ctx, methodWait, in, out, append([]grpc.CallOption{grpc.StaticMethod()}, opts...)...); err != nil { + return nil, err + } + return out, nil +} + +// ServerPoint serves the Jobs point: it registers the point's gRPC service for +// a provider with an SDK server. A binary passes it to (*sdk.Server).Register. +var ServerPoint = serverpoint.Registration{ + Point: jobsv0.Point.ID(), + Register: func(r grpc.ServiceRegistrar, impl any) { + r.RegisterService(&serviceDesc, &grpcServer{impl: impl.(jobsv0.Jobs)}) + }, +} + +// ClientProvider builds a broker provider for the Jobs point from an +// out-of-process gRPC connection. +func ClientProvider(conn grpc.ClientConnInterface) extensions.Provider { + return jobsv0.Point.Provide(NewClient(conn)) +} + +// ClientPoint registers ClientProvider for the Jobs point with a host. +// Single carries the contract's cardinality: the point admits one provider. +var ClientPoint = clientpoint.Registration{Point: jobsv0.Point.ID(), Provider: ClientProvider, Single: true} + +// NewClient returns a jobsv0.Jobs that calls the Jobs point over conn. +func NewClient(conn grpc.ClientConnInterface) jobsv0.Jobs { + return &grpcClient{client: NewJobsClient(conn)} +} + +// grpcServer serves an implementation of the contract's Go interface. +type grpcServer struct { + impl jobsv0.Jobs +} + +func (s *grpcServer) Create(ctx context.Context, req *CreateRequest) (*CreateReply, error) { + resp, err := s.impl.Create(ctx, createRequestFromProto(req)) + if err != nil { + return nil, err + } + return createReplyToProto(resp), nil +} + +func (s *grpcServer) Run(ctx context.Context, req *RunRequest) (*RunReply, error) { + resp, err := s.impl.Run(ctx, runRequestFromProto(req)) + if err != nil { + return nil, err + } + return runReplyToProto(resp), nil +} + +func (s *grpcServer) CreateAndRun(ctx context.Context, req *CreateAndRunRequest) (*CreateAndRunReply, error) { + resp, err := s.impl.CreateAndRun(ctx, createAndRunRequestFromProto(req)) + if err != nil { + return nil, err + } + return createAndRunReplyToProto(resp), nil +} + +func (s *grpcServer) Inspect(ctx context.Context, req *InspectRequest) (*InspectReply, error) { + resp, err := s.impl.Inspect(ctx, inspectRequestFromProto(req)) + if err != nil { + return nil, err + } + return inspectReplyToProto(resp), nil +} + +func (s *grpcServer) List(ctx context.Context, req *ListRequest) (*ListReply, error) { + resp, err := s.impl.List(ctx, listRequestFromProto(req)) + if err != nil { + return nil, err + } + return listReplyToProto(resp), nil +} + +func (s *grpcServer) Pause(ctx context.Context, req *PauseRequest) (*PauseResponse, error) { + if err := s.impl.Pause(ctx, pauseRequestFromProto(req)); err != nil { + return nil, err + } + return &PauseResponse{}, nil +} + +func (s *grpcServer) Resume(ctx context.Context, req *ResumeRequest) (*ResumeResponse, error) { + if err := s.impl.Resume(ctx, resumeRequestFromProto(req)); err != nil { + return nil, err + } + return &ResumeResponse{}, nil +} + +func (s *grpcServer) Cancel(ctx context.Context, req *CancelRequest) (*CancelReply, error) { + resp, err := s.impl.Cancel(ctx, cancelRequestFromProto(req)) + if err != nil { + return nil, err + } + return cancelReplyToProto(resp), nil +} + +func (s *grpcServer) Remove(ctx context.Context, req *RemoveRequest) (*RemoveResponse, error) { + if err := s.impl.Remove(ctx, removeRequestFromProto(req)); err != nil { + return nil, err + } + return &RemoveResponse{}, nil +} + +func (s *grpcServer) Prune(ctx context.Context, req *PruneRequest) (*PruneReply, error) { + resp, err := s.impl.Prune(ctx, pruneRequestFromProto(req)) + if err != nil { + return nil, err + } + return pruneReplyToProto(resp), nil +} + +func (s *grpcServer) ListRuns(ctx context.Context, req *ListRunsRequest) (*ListRunsReply, error) { + resp, err := s.impl.ListRuns(ctx, listRunsRequestFromProto(req)) + if err != nil { + return nil, err + } + return listRunsReplyToProto(resp), nil +} + +func (s *grpcServer) InspectRun(ctx context.Context, req *InspectRunRequest) (*InspectRunReply, error) { + resp, err := s.impl.InspectRun(ctx, inspectRunRequestFromProto(req)) + if err != nil { + return nil, err + } + return inspectRunReplyToProto(resp), nil +} + +func (s *grpcServer) Wait(ctx context.Context, req *WaitRequest) (*WaitReply, error) { + resp, err := s.impl.Wait(ctx, waitRequestFromProto(req)) + if err != nil { + return nil, err + } + return waitReplyToProto(resp), nil +} + +type grpcClient struct { + client JobsClient +} + +func (c *grpcClient) Create(ctx context.Context, req *jobsv0.CreateRequest) (*jobsv0.CreateReply, error) { + resp, err := c.client.Create(ctx, createRequestToProto(req)) + if err != nil { + return nil, err + } + return createReplyFromProto(resp), nil +} + +func (c *grpcClient) Run(ctx context.Context, req *jobsv0.RunRequest) (*jobsv0.RunReply, error) { + resp, err := c.client.Run(ctx, runRequestToProto(req)) + if err != nil { + return nil, err + } + return runReplyFromProto(resp), nil +} + +func (c *grpcClient) CreateAndRun(ctx context.Context, req *jobsv0.CreateAndRunRequest) (*jobsv0.CreateAndRunReply, error) { + resp, err := c.client.CreateAndRun(ctx, createAndRunRequestToProto(req)) + if err != nil { + return nil, err + } + return createAndRunReplyFromProto(resp), nil +} + +func (c *grpcClient) Inspect(ctx context.Context, req *jobsv0.InspectRequest) (*jobsv0.InspectReply, error) { + resp, err := c.client.Inspect(ctx, inspectRequestToProto(req)) + if err != nil { + return nil, err + } + return inspectReplyFromProto(resp), nil +} + +func (c *grpcClient) List(ctx context.Context, req *jobsv0.ListRequest) (*jobsv0.ListReply, error) { + resp, err := c.client.List(ctx, listRequestToProto(req)) + if err != nil { + return nil, err + } + return listReplyFromProto(resp), nil +} + +func (c *grpcClient) Pause(ctx context.Context, req *jobsv0.PauseRequest) error { + _, err := c.client.Pause(ctx, pauseRequestToProto(req)) + return err +} + +func (c *grpcClient) Resume(ctx context.Context, req *jobsv0.ResumeRequest) error { + _, err := c.client.Resume(ctx, resumeRequestToProto(req)) + return err +} + +func (c *grpcClient) Cancel(ctx context.Context, req *jobsv0.CancelRequest) (*jobsv0.CancelReply, error) { + resp, err := c.client.Cancel(ctx, cancelRequestToProto(req)) + if err != nil { + return nil, err + } + return cancelReplyFromProto(resp), nil +} + +func (c *grpcClient) Remove(ctx context.Context, req *jobsv0.RemoveRequest) error { + _, err := c.client.Remove(ctx, removeRequestToProto(req)) + return err +} + +func (c *grpcClient) Prune(ctx context.Context, req *jobsv0.PruneRequest) (*jobsv0.PruneReply, error) { + resp, err := c.client.Prune(ctx, pruneRequestToProto(req)) + if err != nil { + return nil, err + } + return pruneReplyFromProto(resp), nil +} + +func (c *grpcClient) ListRuns(ctx context.Context, req *jobsv0.ListRunsRequest) (*jobsv0.ListRunsReply, error) { + resp, err := c.client.ListRuns(ctx, listRunsRequestToProto(req)) + if err != nil { + return nil, err + } + return listRunsReplyFromProto(resp), nil +} + +func (c *grpcClient) InspectRun(ctx context.Context, req *jobsv0.InspectRunRequest) (*jobsv0.InspectRunReply, error) { + resp, err := c.client.InspectRun(ctx, inspectRunRequestToProto(req)) + if err != nil { + return nil, err + } + return inspectRunReplyFromProto(resp), nil +} + +func (c *grpcClient) Wait(ctx context.Context, req *jobsv0.WaitRequest) (*jobsv0.WaitReply, error) { + resp, err := c.client.Wait(ctx, waitRequestToProto(req)) + if err != nil { + return nil, err + } + return waitReplyFromProto(resp), nil +} + +func cancelReplyToProto(in *jobsv0.CancelReply) *CancelReply { + if in == nil { + return nil + } + out := &CancelReply{} + out.RunId = in.RunID + return out +} + +func cancelReplyFromProto(in *CancelReply) *jobsv0.CancelReply { + if in == nil { + return nil + } + out := &jobsv0.CancelReply{} + out.RunID = in.GetRunId() + return out +} + +func cancelRequestToProto(in *jobsv0.CancelRequest) *CancelRequest { + if in == nil { + return nil + } + out := &CancelRequest{} + out.JobRef = in.JobRef + return out +} + +func cancelRequestFromProto(in *CancelRequest) *jobsv0.CancelRequest { + if in == nil { + return nil + } + out := &jobsv0.CancelRequest{} + out.JobRef = in.GetJobRef() + return out +} + +func createAndRunReplyToProto(in *jobsv0.CreateAndRunReply) *CreateAndRunReply { + if in == nil { + return nil + } + out := &CreateAndRunReply{} + out.Job = jobToProto(in.Job) + out.Run = runToProto(in.Run) + out.Created = in.Created + return out +} + +func createAndRunReplyFromProto(in *CreateAndRunReply) *jobsv0.CreateAndRunReply { + if in == nil { + return nil + } + out := &jobsv0.CreateAndRunReply{} + out.Job = jobFromProto(in.GetJob()) + out.Run = runFromProto(in.GetRun()) + out.Created = in.GetCreated() + return out +} + +func createAndRunRequestToProto(in *jobsv0.CreateAndRunRequest) *CreateAndRunRequest { + if in == nil { + return nil + } + out := &CreateAndRunRequest{} + out.Name = in.Name + out.Spec = jobSpecToProto(in.Spec) + return out +} + +func createAndRunRequestFromProto(in *CreateAndRunRequest) *jobsv0.CreateAndRunRequest { + if in == nil { + return nil + } + out := &jobsv0.CreateAndRunRequest{} + out.Name = in.GetName() + out.Spec = jobSpecFromProto(in.GetSpec()) + return out +} + +func createReplyToProto(in *jobsv0.CreateReply) *CreateReply { + if in == nil { + return nil + } + out := &CreateReply{} + out.Job = jobToProto(in.Job) + out.Created = in.Created + return out +} + +func createReplyFromProto(in *CreateReply) *jobsv0.CreateReply { + if in == nil { + return nil + } + out := &jobsv0.CreateReply{} + out.Job = jobFromProto(in.GetJob()) + out.Created = in.GetCreated() + return out +} + +func createRequestToProto(in *jobsv0.CreateRequest) *CreateRequest { + if in == nil { + return nil + } + out := &CreateRequest{} + out.Name = in.Name + out.Spec = jobSpecToProto(in.Spec) + return out +} + +func createRequestFromProto(in *CreateRequest) *jobsv0.CreateRequest { + if in == nil { + return nil + } + out := &jobsv0.CreateRequest{} + out.Name = in.GetName() + out.Spec = jobSpecFromProto(in.GetSpec()) + return out +} + +func exitCodeToProto(in *jobsv0.ExitCode) *ExitCode { + if in == nil { + return nil + } + out := &ExitCode{} + out.Value = in.Value + return out +} + +func exitCodeFromProto(in *ExitCode) *jobsv0.ExitCode { + if in == nil { + return nil + } + out := &jobsv0.ExitCode{} + out.Value = in.GetValue() + return out +} + +func inspectReplyToProto(in *jobsv0.InspectReply) *InspectReply { + if in == nil { + return nil + } + out := &InspectReply{} + out.Job = jobToProto(in.Job) + return out +} + +func inspectReplyFromProto(in *InspectReply) *jobsv0.InspectReply { + if in == nil { + return nil + } + out := &jobsv0.InspectReply{} + out.Job = jobFromProto(in.GetJob()) + return out +} + +func inspectRequestToProto(in *jobsv0.InspectRequest) *InspectRequest { + if in == nil { + return nil + } + out := &InspectRequest{} + out.JobRef = in.JobRef + return out +} + +func inspectRequestFromProto(in *InspectRequest) *jobsv0.InspectRequest { + if in == nil { + return nil + } + out := &jobsv0.InspectRequest{} + out.JobRef = in.GetJobRef() + return out +} + +func inspectRunReplyToProto(in *jobsv0.InspectRunReply) *InspectRunReply { + if in == nil { + return nil + } + out := &InspectRunReply{} + out.Run = runToProto(in.Run) + return out +} + +func inspectRunReplyFromProto(in *InspectRunReply) *jobsv0.InspectRunReply { + if in == nil { + return nil + } + out := &jobsv0.InspectRunReply{} + out.Run = runFromProto(in.GetRun()) + return out +} + +func inspectRunRequestToProto(in *jobsv0.InspectRunRequest) *InspectRunRequest { + if in == nil { + return nil + } + out := &InspectRunRequest{} + out.JobRef = in.JobRef + out.RunRef = in.RunRef + return out +} + +func inspectRunRequestFromProto(in *InspectRunRequest) *jobsv0.InspectRunRequest { + if in == nil { + return nil + } + out := &jobsv0.InspectRunRequest{} + out.JobRef = in.GetJobRef() + out.RunRef = in.GetRunRef() + return out +} + +func jobToProto(in *jobsv0.Job) *Job { + if in == nil { + return nil + } + out := &Job{} + out.Id = in.ID + out.Name = in.Name + out.Spec = jobSpecToProto(in.Spec) + out.SpecHash = in.SpecHash + out.State = in.State + out.Paused = in.Paused + out.NextFireAtNano = in.NextFireAtNano + out.CreatedAtNano = in.CreatedAtNano + out.UpdatedAtNano = in.UpdatedAtNano + out.LatestRun = runToProto(in.LatestRun) + return out +} + +func jobFromProto(in *Job) *jobsv0.Job { + if in == nil { + return nil + } + out := &jobsv0.Job{} + out.ID = in.GetId() + out.Name = in.GetName() + out.Spec = jobSpecFromProto(in.GetSpec()) + out.SpecHash = in.GetSpecHash() + out.State = in.GetState() + out.Paused = in.GetPaused() + out.NextFireAtNano = in.GetNextFireAtNano() + out.CreatedAtNano = in.GetCreatedAtNano() + out.UpdatedAtNano = in.GetUpdatedAtNano() + out.LatestRun = runFromProto(in.GetLatestRun()) + return out +} + +func jobSpecToProto(in *jobsv0.JobSpec) *JobSpec { + if in == nil { + return nil + } + out := &JobSpec{} + out.ContainerSpec = in.ContainerSpec + out.Trigger = triggerToProto(in.Trigger) + out.Labels = in.Labels + out.TimeoutSeconds = in.TimeoutSeconds + out.RemoveOnSuccess = in.RemoveOnSuccess + out.RemoveOnFailure = in.RemoveOnFailure + out.RunHistoryLimit = in.RunHistoryLimit + return out +} + +func jobSpecFromProto(in *JobSpec) *jobsv0.JobSpec { + if in == nil { + return nil + } + out := &jobsv0.JobSpec{} + out.ContainerSpec = in.GetContainerSpec() + out.Trigger = triggerFromProto(in.GetTrigger()) + out.Labels = in.GetLabels() + out.TimeoutSeconds = in.GetTimeoutSeconds() + out.RemoveOnSuccess = in.GetRemoveOnSuccess() + out.RemoveOnFailure = in.GetRemoveOnFailure() + out.RunHistoryLimit = in.GetRunHistoryLimit() + return out +} + +func listReplyToProto(in *jobsv0.ListReply) *ListReply { + if in == nil { + return nil + } + out := &ListReply{} + for i := range in.Jobs { + out.Jobs = append(out.Jobs, jobToProto(&in.Jobs[i])) + } + return out +} + +func listReplyFromProto(in *ListReply) *jobsv0.ListReply { + if in == nil { + return nil + } + out := &jobsv0.ListReply{} + for _, e := range in.GetJobs() { + out.Jobs = append(out.Jobs, *jobFromProto(e)) + } + return out +} + +func listRequestToProto(in *jobsv0.ListRequest) *ListRequest { + if in == nil { + return nil + } + out := &ListRequest{} + out.Names = in.Names + out.Labels = in.Labels + out.States = in.States + out.TriggerKinds = in.TriggerKinds + out.Paused = in.Paused + out.LatestRunStates = in.LatestRunStates + return out +} + +func listRequestFromProto(in *ListRequest) *jobsv0.ListRequest { + if in == nil { + return nil + } + out := &jobsv0.ListRequest{} + out.Names = in.GetNames() + out.Labels = in.GetLabels() + out.States = in.GetStates() + out.TriggerKinds = in.GetTriggerKinds() + out.Paused = in.GetPaused() + out.LatestRunStates = in.GetLatestRunStates() + return out +} + +func listRunsReplyToProto(in *jobsv0.ListRunsReply) *ListRunsReply { + if in == nil { + return nil + } + out := &ListRunsReply{} + for i := range in.Runs { + out.Runs = append(out.Runs, runToProto(&in.Runs[i])) + } + out.NextCursor = in.NextCursor + out.CursorStale = in.CursorStale + return out +} + +func listRunsReplyFromProto(in *ListRunsReply) *jobsv0.ListRunsReply { + if in == nil { + return nil + } + out := &jobsv0.ListRunsReply{} + for _, e := range in.GetRuns() { + out.Runs = append(out.Runs, *runFromProto(e)) + } + out.NextCursor = in.GetNextCursor() + out.CursorStale = in.GetCursorStale() + return out +} + +func listRunsRequestToProto(in *jobsv0.ListRunsRequest) *ListRunsRequest { + if in == nil { + return nil + } + out := &ListRunsRequest{} + out.JobRef = in.JobRef + out.Limit = in.Limit + out.Before = in.Before + return out +} + +func listRunsRequestFromProto(in *ListRunsRequest) *jobsv0.ListRunsRequest { + if in == nil { + return nil + } + out := &jobsv0.ListRunsRequest{} + out.JobRef = in.GetJobRef() + out.Limit = in.GetLimit() + out.Before = in.GetBefore() + return out +} + +func pauseRequestToProto(in *jobsv0.PauseRequest) *PauseRequest { + if in == nil { + return nil + } + out := &PauseRequest{} + out.JobRef = in.JobRef + return out +} + +func pauseRequestFromProto(in *PauseRequest) *jobsv0.PauseRequest { + if in == nil { + return nil + } + out := &jobsv0.PauseRequest{} + out.JobRef = in.GetJobRef() + return out +} + +func pruneReplyToProto(in *jobsv0.PruneReply) *PruneReply { + if in == nil { + return nil + } + out := &PruneReply{} + out.RemovedJobIds = in.RemovedJobIDs + return out +} + +func pruneReplyFromProto(in *PruneReply) *jobsv0.PruneReply { + if in == nil { + return nil + } + out := &jobsv0.PruneReply{} + out.RemovedJobIDs = in.GetRemovedJobIds() + return out +} + +func pruneRequestToProto(in *jobsv0.PruneRequest) *PruneRequest { + if in == nil { + return nil + } + out := &PruneRequest{} + out.Labels = in.Labels + return out +} + +func pruneRequestFromProto(in *PruneRequest) *jobsv0.PruneRequest { + if in == nil { + return nil + } + out := &jobsv0.PruneRequest{} + out.Labels = in.GetLabels() + return out +} + +func removeRequestToProto(in *jobsv0.RemoveRequest) *RemoveRequest { + if in == nil { + return nil + } + out := &RemoveRequest{} + out.JobRef = in.JobRef + out.RunsRemoval = in.RunsRemoval + return out +} + +func removeRequestFromProto(in *RemoveRequest) *jobsv0.RemoveRequest { + if in == nil { + return nil + } + out := &jobsv0.RemoveRequest{} + out.JobRef = in.GetJobRef() + out.RunsRemoval = in.GetRunsRemoval() + return out +} + +func resumeRequestToProto(in *jobsv0.ResumeRequest) *ResumeRequest { + if in == nil { + return nil + } + out := &ResumeRequest{} + out.JobRef = in.JobRef + return out +} + +func resumeRequestFromProto(in *ResumeRequest) *jobsv0.ResumeRequest { + if in == nil { + return nil + } + out := &jobsv0.ResumeRequest{} + out.JobRef = in.GetJobRef() + return out +} + +func runToProto(in *jobsv0.Run) *Run { + if in == nil { + return nil + } + out := &Run{} + out.Id = in.ID + out.JobId = in.JobID + out.Iteration = in.Iteration + out.ContainerId = in.ContainerID + out.ContainerGone = in.ContainerGone + out.State = in.State + out.CreatedAtNano = in.CreatedAtNano + out.StartedAtNano = in.StartedAtNano + out.FinishedAtNano = in.FinishedAtNano + out.ExitCode = exitCodeToProto(in.ExitCode) + out.Error = in.Error + out.Trigger = triggerEvidenceToProto(in.Trigger) + return out +} + +func runFromProto(in *Run) *jobsv0.Run { + if in == nil { + return nil + } + out := &jobsv0.Run{} + out.ID = in.GetId() + out.JobID = in.GetJobId() + out.Iteration = in.GetIteration() + out.ContainerID = in.GetContainerId() + out.ContainerGone = in.GetContainerGone() + out.State = in.GetState() + out.CreatedAtNano = in.GetCreatedAtNano() + out.StartedAtNano = in.GetStartedAtNano() + out.FinishedAtNano = in.GetFinishedAtNano() + out.ExitCode = exitCodeFromProto(in.GetExitCode()) + out.Error = in.GetError() + out.Trigger = triggerEvidenceFromProto(in.GetTrigger()) + return out +} + +func runReplyToProto(in *jobsv0.RunReply) *RunReply { + if in == nil { + return nil + } + out := &RunReply{} + out.Run = runToProto(in.Run) + return out +} + +func runReplyFromProto(in *RunReply) *jobsv0.RunReply { + if in == nil { + return nil + } + out := &jobsv0.RunReply{} + out.Run = runFromProto(in.GetRun()) + return out +} + +func runRequestToProto(in *jobsv0.RunRequest) *RunRequest { + if in == nil { + return nil + } + out := &RunRequest{} + out.JobRef = in.JobRef + out.Reschedule = in.Reschedule + return out +} + +func runRequestFromProto(in *RunRequest) *jobsv0.RunRequest { + if in == nil { + return nil + } + out := &jobsv0.RunRequest{} + out.JobRef = in.GetJobRef() + out.Reschedule = in.GetReschedule() + return out +} + +func scheduleTriggerToProto(in *jobsv0.ScheduleTrigger) *ScheduleTrigger { + if in == nil { + return nil + } + out := &ScheduleTrigger{} + out.Cron = in.Cron + out.Timezone = in.Timezone + out.Concurrency = in.Concurrency + out.MissedFires = in.MissedFires + return out +} + +func scheduleTriggerFromProto(in *ScheduleTrigger) *jobsv0.ScheduleTrigger { + if in == nil { + return nil + } + out := &jobsv0.ScheduleTrigger{} + out.Cron = in.GetCron() + out.Timezone = in.GetTimezone() + out.Concurrency = in.GetConcurrency() + out.MissedFires = in.GetMissedFires() + return out +} + +func triggerToProto(in *jobsv0.Trigger) *Trigger { + if in == nil { + return nil + } + out := &Trigger{} + out.Manual = in.Manual + out.Schedule = scheduleTriggerToProto(in.Schedule) + return out +} + +func triggerFromProto(in *Trigger) *jobsv0.Trigger { + if in == nil { + return nil + } + out := &jobsv0.Trigger{} + out.Manual = in.GetManual() + out.Schedule = scheduleTriggerFromProto(in.GetSchedule()) + return out +} + +func triggerEvidenceToProto(in *jobsv0.TriggerEvidence) *TriggerEvidence { + if in == nil { + return nil + } + out := &TriggerEvidence{} + out.Kind = in.Kind + out.ScheduledAtNano = in.ScheduledAtNano + out.FiredAtNano = in.FiredAtNano + return out +} + +func triggerEvidenceFromProto(in *TriggerEvidence) *jobsv0.TriggerEvidence { + if in == nil { + return nil + } + out := &jobsv0.TriggerEvidence{} + out.Kind = in.GetKind() + out.ScheduledAtNano = in.GetScheduledAtNano() + out.FiredAtNano = in.GetFiredAtNano() + return out +} + +func waitReplyToProto(in *jobsv0.WaitReply) *WaitReply { + if in == nil { + return nil + } + out := &WaitReply{} + out.Run = runToProto(in.Run) + return out +} + +func waitReplyFromProto(in *WaitReply) *jobsv0.WaitReply { + if in == nil { + return nil + } + out := &jobsv0.WaitReply{} + out.Run = runFromProto(in.GetRun()) + return out +} + +func waitRequestToProto(in *jobsv0.WaitRequest) *WaitRequest { + if in == nil { + return nil + } + out := &WaitRequest{} + out.JobRef = in.JobRef + out.RunRef = in.RunRef + out.Condition = in.Condition + return out +} + +func waitRequestFromProto(in *WaitRequest) *jobsv0.WaitRequest { + if in == nil { + return nil + } + out := &jobsv0.WaitRequest{} + out.JobRef = in.GetJobRef() + out.RunRef = in.GetRunRef() + out.Condition = in.GetCondition() + return out +} diff --git a/pkg/api/api.go b/pkg/api/api.go index 9966736b3a..a70574d403 100644 --- a/pkg/api/api.go +++ b/pkg/api/api.go @@ -128,6 +128,14 @@ type Compose interface { Kill(ctx context.Context, projectName string, options KillOptions) error // RunOneOffContainer creates a service oneoff container and starts its dependencies RunOneOffContainer(ctx context.Context, project *types.Project, opts RunOptions) (int, error) + // RunJob triggers a manual-trigger job's Run on the engine's jobs API, + // starts its dependencies first, and returns the Run's exit code. options + // carries the same CLI overrides RunOneOffContainer accepts (User, + // Command, Entrypoint, WorkingDir, CapAdd/CapDrop, Environment, Labels, + // NoDeps); Service is ignored, name is authoritative. Name has no effect: + // the jobs API assigns the run container's identity itself, there is no + // per-run naming hook to plug --name into. + RunJob(ctx context.Context, project *types.Project, name string, options RunOptions) (int, error) // Remove executes the equivalent to a `compose rm` Remove(ctx context.Context, projectName string, options RemoveOptions) error // Exec executes a command in a running service container @@ -365,6 +373,10 @@ type StartOptions struct { // NavigationMenu enables the keyboard menu of Up's foreground session; // ignored by Start. NavigationMenu bool + // NoStart makes Up create and register the project (containers, scheduled + // jobs) without starting anything — `up --no-start`'s mode switch. + // Ignored by Start. + NoStart bool } type Cascade int diff --git a/pkg/api/labels.go b/pkg/api/labels.go index 19b4e1bd3c..201a181721 100644 --- a/pkg/api/labels.go +++ b/pkg/api/labels.go @@ -73,6 +73,8 @@ const ( // runPreStartHook so orphan hook containers from a previous failed run can // be found and removed by project+service+hook label filters. HookLabel = "com.docker.compose.hook" + // JobLabel allow to track resource related to a compose job + JobLabel = "com.docker.compose.job" ) // ComposeVersion is the compose tool version as declared by label VersionLabel diff --git a/pkg/compose/compose.go b/pkg/compose/compose.go index 7f39461ed2..2c1d718b6a 100644 --- a/pkg/compose/compose.go +++ b/pkg/compose/compose.go @@ -30,11 +30,13 @@ import ( "github.com/docker/cli/cli/config/configfile" "github.com/docker/cli/cli/flags" "github.com/docker/cli/cli/streams" + extensionclient "github.com/moby/extensions/client" "github.com/moby/moby/api/types/container" "github.com/moby/moby/api/types/swarm" "github.com/moby/moby/client" "github.com/sirupsen/logrus" + jobsv0 "github.com/docker/compose/v5/internal/jobsapi" "github.com/docker/compose/v5/pkg/api" "github.com/docker/compose/v5/pkg/dryrun" ) @@ -213,6 +215,11 @@ type composeService struct { dryRun bool runtimeAPIVersion runtimeVersionCache + + jobsGRPCOnce sync.Once + jobsExtClient *extensionclient.Client + jobsAPI jobsv0.Jobs + jobsAPIErr error } // Close releases any connections/resources held by the underlying clients. @@ -224,6 +231,9 @@ func (s *composeService) Close() error { if s.dockerCli != nil { errs = append(errs, s.apiClient().Close()) } + if s.jobsExtClient != nil { + errs = append(errs, s.jobsExtClient.Close()) + } return errors.Join(errs...) } diff --git a/pkg/compose/create.go b/pkg/compose/create.go index b5496a68e9..8520202eb2 100644 --- a/pkg/compose/create.go +++ b/pkg/compose/create.go @@ -117,11 +117,22 @@ func (s *composeService) create(ctx context.Context, project *types.Project, opt warnUnmanagedNetworks(project, observed) warnUnmanagedVolumes(project, observed) - if len(observed.Orphans) > 0 && !options.IgnoreOrphans && !options.RemoveOrphans { + // A kept job run container (RemoveOnFailure: false) has no matching + // service by design — it's not something the user forgot to clean up, so + // it shouldn't trigger this warning. It's still a real orphan for + // reconcileOrphans (down/up --remove-orphans can sweep it), so the + // exclusion is scoped to this warning's own name list, not observed.Orphans. + var nonJobOrphans []string + for _, o := range observed.Orphans { + if _, isJobRun := o.Summary.Labels[jobContainerIDLabel]; !isJobRun { + nonJobOrphans = append(nonJobOrphans, o.Name) + } + } + if len(nonJobOrphans) > 0 && !options.IgnoreOrphans && !options.RemoveOrphans { logrus.Warnf("Found orphan containers (%s) for this project. If "+ "you removed or renamed this service in your compose "+ "file, you can run this command with the "+ - "--remove-orphans flag to clean it up.", observed.orphanNames()) + "--remove-orphans flag to clean it up.", strings.Join(nonJobOrphans, ", ")) } plan, err := reconcile(ctx, project, observed, toReconcileOptions(options), s.prompt) @@ -275,6 +286,7 @@ func (s *composeService) getCreateConfigs(ctx context.Context, for dep, d := range service.DependsOn { dependencies = append(dependencies, fmt.Sprintf("%s:%s:%t", dep, d.Condition, d.Restart)) } + slices.Sort(dependencies) labels[api.DependenciesLabel] = strings.Join(dependencies, ",") var runCmd, entrypoint []string diff --git a/pkg/compose/down.go b/pkg/compose/down.go index b4bbc6e3cf..1d6903e0e0 100644 --- a/pkg/compose/down.go +++ b/pkg/compose/down.go @@ -68,6 +68,10 @@ func (s *composeService) down(ctx context.Context, projectName string, options a if err != nil { return err } + // No compose file to read Jobs from: reconstruct them from the + // engine's own registry, otherwise ensureJobsDown silently sees none + // and a project's scheduled jobs outlive `down` forever. + project.Jobs = s.actualJobs(ctx, projectName) } // keep only the requested services that exist in the model @@ -115,6 +119,7 @@ func (s *composeService) down(ctx context.Context, projectName string, options a } ops := s.ensureNetworksDown(ctx, project) + ops = append(ops, s.ensureJobsDown(ctx, project)...) if options.Images != "" { imgOps, err := s.ensureImagesDown(ctx, project, options) diff --git a/pkg/compose/down_test.go b/pkg/compose/down_test.go index 06817b8da1..14204dd14a 100644 --- a/pkg/compose/down_test.go +++ b/pkg/compose/down_test.go @@ -17,8 +17,10 @@ package compose import ( + "context" "errors" "fmt" + "net" "os" "strings" "testing" @@ -38,6 +40,14 @@ import ( "github.com/docker/compose/v5/pkg/mocks" ) +// noJobsDialer stands in for the jobs extension's gRPC dialer in tests that +// reconstruct a project without a compose file (down's actualJobs lookup): +// it fails to connect, which actualJobs treats the same as an engine with no +// jobs feature — no jobs, no further mock expectations needed. +func noJobsDialer(context.Context) (net.Conn, error) { + return nil, errors.New("no jobs dialer in tests") +} + func TestDown(t *testing.T) { mockCtrl := gomock.NewController(t) defer mockCtrl.Finish() @@ -465,6 +475,10 @@ func prepareMocks(mockCtrl *gomock.Controller) (*mocks.MockAPIClient, *mocks.Moc cli.EXPECT().Client().Return(api).AnyTimes() cli.EXPECT().Err().Return(streams.NewOut(os.Stderr)).AnyTimes() cli.EXPECT().Out().Return(streams.NewOut(os.Stdout)).AnyTimes() + // down's actualJobs lookup calls Dialer() when it reconstructs a project + // without a compose file; AnyTimes() covers both that path and callers + // that pass an explicit Project and never reach it. + api.EXPECT().Dialer().Return(noJobsDialer).AnyTimes() return api, cli } diff --git a/pkg/compose/jobs.go b/pkg/compose/jobs.go new file mode 100644 index 0000000000..51f96b8a34 --- /dev/null +++ b/pkg/compose/jobs.go @@ -0,0 +1,606 @@ +/* + Copyright 2020 Docker Compose CLI authors + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package compose + +import ( + "context" + "encoding/json" + "fmt" + "io" + "strings" + + "github.com/compose-spec/compose-go/v2/types" + "github.com/containerd/errdefs" + extensionclient "github.com/moby/extensions/client" + "github.com/moby/moby/api/pkg/stdcopy" + containerType "github.com/moby/moby/api/types/container" + "github.com/moby/moby/client" + "github.com/sirupsen/logrus" + "golang.org/x/sync/errgroup" + + jobsv0 "github.com/docker/compose/v5/internal/jobsapi" + jobspb "github.com/docker/compose/v5/internal/jobsapi/protogen" + "github.com/docker/compose/v5/pkg/api" +) + +// jobRunHistoryLimit caps retained terminal run records (see +// jobsv0.JobSpec.RunHistoryLimit): low enough that repeated failures of a +// frequently-firing schedule don't accumulate kept containers indefinitely. +const jobRunHistoryLimit = 5 + +// jobsClient lazily resolves the engine's jobs extension point over the same +// dialer the Docker API client already uses. Resolve never dials by itself — +// an engine without the jobs feature only surfaces codes.Unimplemented on +// the first real call — so projects with no jobs never touch the network. +func (s *composeService) jobsClient() (jobsv0.Jobs, error) { + s.jobsGRPCOnce.Do(func() { + exts, err := extensionclient.New(s.apiClient(), extensionclient.WithGRPCPoint(jobspb.ClientPoint)) + if err != nil { + s.jobsAPIErr = err + return + } + s.jobsExtClient = exts + s.jobsAPI, s.jobsAPIErr = extensionclient.Resolve(exts, jobsv0.Point) + }) + return s.jobsAPI, s.jobsAPIErr +} + +// engineJobNameSeparator joins a project and job name into the engine's +// daemon-wide job name. The engine only accepts [a-zA-Z0-9][a-zA-Z0-9_.-]* for +// job names, which rules out "/" as a separator (confirmed against a live +// engine: it rejects it with InvalidArgument). "." still works: project names +// are restricted to [a-z0-9_-] (see compose-go's NormalizeProjectName, no +// dot), while job names may contain one, so a "." can only originate from the +// job-name side — the join stays unambiguous to reverse. Unlike api.Separator +// ("-"), which both project and job names can contain, this avoids project +// "app-sub" + job "service" colliding with project "app" + job "sub-service". +const engineJobNameSeparator = "." + +// engineJobName is the daemon-wide unique name Compose registers a job +// under: the engine has no notion of Compose projects, so the project name +// is folded into the job name. +func engineJobName(project *types.Project, name string) string { + return project.Name + engineJobNameSeparator + name +} + +// jobTrigger translates compose-go's TriggerConfig into the engine's +// Trigger type: 1:1 field mapping, already verified against the engine +// contract. +func jobTrigger(job types.JobConfig) (*jobsv0.Trigger, error) { + switch { + case job.Triggers == nil: + return nil, fmt.Errorf("job %q has no trigger", job.Name) + case job.Triggers.Manual != nil && *job.Triggers.Manual && len(job.Triggers.Schedule) > 0: + return nil, fmt.Errorf("job %q declares both manual:true and a schedule, exactly one is supported", job.Name) + case job.Triggers.Manual != nil && *job.Triggers.Manual: + return &jobsv0.Trigger{Manual: true}, nil + case len(job.Triggers.Schedule) == 1: + sc := job.Triggers.Schedule[0] + return &jobsv0.Trigger{Schedule: &jobsv0.ScheduleTrigger{ + Cron: sc.Cron, + Timezone: sc.Timezone, + Concurrency: sc.Concurrency, + MissedFires: sc.MissedFires, + }}, nil + case len(job.Triggers.Schedule) > 1: + return nil, fmt.Errorf("job %q declares %d schedules, exactly one is supported", job.Name, len(job.Triggers.Schedule)) + default: + return nil, fmt.Errorf("job %q has no trigger", job.Name) + } +} + +// buildJobSpec builds the engine JobSpec for a job: svc is resolved exactly +// like a service (materializeManualJob or the synthetic ServiceConfig built +// from the JobConfig for scheduled jobs), and getCreateConfigs is the same +// service->container-create-body conversion used to create real containers. +func (s *composeService) buildJobSpec(ctx context.Context, project *types.Project, svc types.ServiceConfig, job types.JobConfig, useNetworkAliases bool) (*jobsv0.JobSpec, error) { + trigger, err := jobTrigger(job) + if err != nil { + return nil, err + } + + // The engine only carries JobSpec.Labels on the job object, not on the run + // container (see jobsv0.JobSpec.Labels godoc): project/service labels must + // be set here on the container spec itself so run containers stay visible + // to the rest of Compose's tooling (ps, label-scoped listings) exactly + // like any other service container. svc.CustomLabels is trustworthy here + // because JobAsService is the single place both materialization paths + // (RunJob's cmd-layer materializeManualJob and registerScheduledJobs' + // scopedProjectForJob) build it — the same job's spec no longer differs + // depending on how it was triggered. + cfgs, err := s.getCreateConfigs(ctx, project, svc, 1, nil, createOptions{ + UseNetworkAliases: useNetworkAliases, + Labels: mergeLabels(svc.Labels, svc.CustomLabels), + }) + if err != nil { + return nil, err + } + spec, err := json.Marshal(containerType.CreateRequest{ + Config: cfgs.Container, + HostConfig: cfgs.Host, + NetworkingConfig: cfgs.Network, + }) + if err != nil { + return nil, err + } + + return &jobsv0.JobSpec{ + ContainerSpec: spec, + Trigger: trigger, + Labels: map[string]string{ + api.ProjectLabel: project.Name, + api.JobLabel: job.Name, + }, + // Successful runs are disposable: drop the container once its + // terminal record is written. Failed ones are kept for postmortem, + // bounded by RunHistoryLimit so repeated failures don't accumulate. + RemoveOnSuccess: true, + RemoveOnFailure: false, + RunHistoryLimit: jobRunHistoryLimit, + }, nil +} + +// sortedJobNames returns the sorted names of a project's jobs, optionally +// restricted to those matching keep. +func sortedJobNames(jobs types.Jobs, keep func(types.JobConfig) bool) []string { + if keep == nil { + return sortedMapKeys(jobs) + } + filtered := make(types.Jobs, len(jobs)) + for name, job := range jobs { + if keep(job) { + filtered[name] = job + } + } + return sortedMapKeys(filtered) +} + +// jobChangedErr reports that the engine already has a job of this name with +// a different spec: re-running the same verb won't reconcile it, only +// `down` removing it first will. +func jobChangedErr(name, verb string) error { + return fmt.Errorf("job %q has changed: run `docker compose down` to remove it, then `%s` again", name, verb) +} + +// mapAlreadyExists maps err through jobsv0.MapError, translating an +// AlreadyExists (the engine already has name registered with a different +// spec) into jobChangedErr instead of the raw engine error. +func mapAlreadyExists(err error, name, verb string) error { + err = jobsv0.MapError(err) + if errdefs.IsAlreadyExists(err) { + return jobChangedErr(name, verb) + } + return err +} + +// HasSchedule reports whether a job declares a schedule trigger — the +// predicate `up` uses to register it with the engine instead of warning +// that it waits for `docker compose run`. +func HasSchedule(job types.JobConfig) bool { + return job.Triggers != nil && len(job.Triggers.Schedule) > 0 +} + +// ManualTriggerDisabled reports whether a job explicitly opts out of manual +// triggering with `triggers.manual: false`. Per the spec, every job accepts +// `docker compose run` regardless of its automated triggers unless it opts +// out this way — this is the single source of truth for that rule, shared +// by every layer that materializes or runs a job manually. +func ManualTriggerDisabled(job types.JobConfig) bool { + return job.Triggers != nil && job.Triggers.Manual != nil && !*job.Triggers.Manual +} + +// ManualTriggerDisabledErr reports that name was declared with +// `manual: false` and so cannot be run manually — the error every caller +// of ManualTriggerDisabled raises on that condition. +func ManualTriggerDisabledErr(name string) error { + return fmt.Errorf("job %q is declared with manual: false, it cannot be run manually", name) +} + +// JobAsService materializes a job as a service for the one-off machinery: a +// job is a ContainerSpec+WorkloadSpec, the same layers a service is made of. +// It carries the standard custom labels the loader stamps on every service — +// materialization happens after loading, so without them the containers +// created for the job would be invisible to every label-driven path: start +// would silently skip them, ps/down would not see them, and the dependency +// wait would report the job as a missing dependency. Exported as the single +// source of truth both materialization paths (cmd/compose/run.go's +// materializeManualJob for a manual run, scopedProjectForJob below for a +// scheduled registration) share, so the same job's spec doesn't differ +// depending on how it was triggered. +func JobAsService(project *types.Project, name string, job types.JobConfig) types.ServiceConfig { + svc := types.ServiceConfig{ + Name: name, + Profiles: job.Profiles, + Extensions: job.Extensions, + ContainerSpec: job.ContainerSpec, + WorkloadSpec: job.WorkloadSpec, + } + svc.CustomLabels = types.Labels{ + api.ProjectLabel: project.Name, + api.ServiceLabel: name, + api.VersionLabel: api.ComposeVersion, + api.WorkingDirLabel: project.WorkingDir, + api.ConfigFilesLabel: strings.Join(project.ComposeFiles, ","), + api.OneoffLabel: "False", + } + return svc +} + +// scopedProjectForJob returns a shallow copy of project with name's job +// materialized into a fresh Services map, so a scheduled job can run +// through the same project-wide normalization passes (useAPISocket, +// ensureImagesExists, ensureModels) that a manually-run job gets for free +// once materializeManualJob puts it into the real project.Services. The +// copy is throwaway: callers must discard it once they've read back the +// resolved ServiceConfig, so the job never joins the project the real +// service-reconciliation loop (create/start) iterates over. +// +// Configs, and each service's Environment/CustomLabels/Volumes/Configs, +// also get their own copy: registerScheduledJobs calls this concurrently +// for every scheduled job, and the normalization passes above mutate those +// fields in place across every service in the project, not just the job +// being registered — useAPISocket writes project.Configs["#apisocket"], +// service.Environment["DOCKER_CONFIG"], and appends to service.Volumes and +// service.Configs; ensureModels' SetModelVariables writes +// service.Environment for any service with a models: reference; and +// ensureImagesExists writes service.CustomLabels (via Labels.Add, which +// mutates its receiver) and, through resolveImageVolumes, service.Volumes +// elements in place for any `type: image` volume. A plain struct copy of +// ServiceConfig leaves all of these aliased to the real project's, so two +// jobs registering concurrently would both write into the same map or +// backing array — a data race. +func scopedProjectForJob(project *types.Project, name string, job types.JobConfig) *types.Project { + scoped := *project + services := make(types.Services, len(project.Services)+1) + for n, svc := range project.Services { + env := make(types.MappingWithEquals, len(svc.Environment)) + for k, v := range svc.Environment { + env[k] = v + } + svc.Environment = env + + labels := make(types.Labels, len(svc.CustomLabels)) + for k, v := range svc.CustomLabels { + labels[k] = v + } + svc.CustomLabels = labels + + svc.Volumes = append([]types.ServiceVolumeConfig{}, svc.Volumes...) + svc.Configs = append([]types.ServiceConfigObjConfig{}, svc.Configs...) + + services[n] = svc + } + services[name] = JobAsService(project, name, job) + scoped.Services = services + + configs := make(types.Configs, len(project.Configs)) + for n, cfg := range project.Configs { + configs[n] = cfg + } + scoped.Configs = configs + return &scoped +} + +// registerScheduledJobs registers the project's scheduled jobs with the +// engine. Create is idempotent on SpecHash: re-applying the same spec on a +// later `up` is a no-op, which is what makes `up` safely re-runnable. +func (s *composeService) registerScheduledJobs(ctx context.Context, project *types.Project) error { + names := sortedJobNames(project.Jobs, HasSchedule) + if len(names) == 0 { + return nil + } + + jc, err := s.jobsClient() + if err != nil { + return err + } + eg, ctx := errgroup.WithContext(ctx) + eg.SetLimit(s.maxConcurrency) + for _, name := range names { + eg.Go(func() error { + job := project.Jobs[name] + scoped, err := s.useAPISocket(scopedProjectForJob(project, name, job)) + if err != nil { + return err + } + // Without this, a scheduled job declaring only `build:` (no + // `image:`) registers successfully here and then fails, every + // time its schedule fires, because the image was never built — + // silently, since up's own auto-build only ever scoped to + // project.ServiceNames() (see pkg/compose/build.go). + if err := s.ensureImagesExists(ctx, scoped, &api.BuildOptions{Services: []string{name}}, false); err != nil { + return err + } + if err := s.ensureModels(ctx, scoped, false); err != nil { + return err + } + svc, err := scoped.GetService(name) + if err != nil { + return err + } + spec, err := s.buildJobSpec(ctx, scoped, svc, job, false) + if err != nil { + return err + } + _, err = jc.Create(ctx, &jobsv0.CreateRequest{ + Name: engineJobName(project, name), + Spec: spec, + }) + return mapAlreadyExists(err, name, "up") + }) + } + return eg.Wait() +} + +// RunJob triggers a manual-trigger job's Run on the engine, following the +// same dependency-startup path as a one-off service run, then streams the +// Run's container logs and waits for its terminal state. See the RunJob +// doc comment on api.Compose for which options fields have an effect. +func (s *composeService) RunJob(ctx context.Context, project *types.Project, name string, options api.RunOptions) (int, error) { + job, ok := project.AllJobs()[name] + if !ok { + return 0, fmt.Errorf("job %q not found", name) + } + if ManualTriggerDisabled(job) { + return 0, ManualTriggerDisabledErr(name) + } + + // materializeManualJob already put the job into project.Services, so it + // is seen by these project-wide normalization passes exactly like a + // service would be (use_api_socket / models: support). + project, err := s.useAPISocket(project) + if err != nil { + return 0, err + } + + if err := s.startDependencies(ctx, project, api.RunOptions{ + Service: name, + NoDeps: options.NoDeps, + CreateOptions: options.CreateOptions, + }); err != nil { + return 0, err + } + // The job's own image build (if any) is scoped to just this job, unlike + // startDependencies' unscoped Build above which may also build other + // services the job depends on. RunJob is always called with name == + // options.Service, so this is the same scoping prepareRun uses. + buildOpts := prepareBuildOptions(options) + if err := s.ensureImagesExists(ctx, project, buildOpts, options.QuietPull); err != nil { + return 0, err + } + if err := s.ensureModels(ctx, project, false); err != nil { + return 0, err + } + + svc, err := project.GetService(name) + if err != nil { + return 0, err + } + applyRunOptions(project, &svc, options) + // A job has no run-assigned container name: the jobs API owns the run + // container's identity itself. But two independent layers inject + // CLI/terminal-context defaults into the service before RunJob ever + // sees it — prepareRun's own Tty/StdinOpen overrides don't apply here, + // yet cmd/compose/run.go's runOptions.apply still sets + // target.Tty/StdinOpen from the terminal before materializing the job. + // Left in place, that leaks into the spec sent to the engine and + // spuriously conflicts (SpecHash mismatch) with the identical spec `up` + // already registered for a scheduled job. Restore the job's own + // declared values (set correctly by JobAsService for both + // materialization paths) rather than the terminal's. + svc.Tty = job.Tty + svc.StdinOpen = job.StdinOpen + svc.ContainerName = "" + + observed, err := s.getContainers(ctx, project.Name, oneOffInclude, true) + if err != nil { + return 0, err + } + if err := s.waitDependencies(ctx, project, name, svc.DependsOn, observed, 0); err != nil { + return 0, err + } + // A job may reference a sibling service or job — volumes_from, or + // service:-scoped network_mode/ipc/pid — exactly like a service run + // would; the daemon knows nothing about compose service names, so these + // must resolve to live container IDs before the spec reaches it. + if err := s.resolveRunServiceReferences(ctx, project.Name, &svc); err != nil { + return 0, err + } + spec, err := s.buildJobSpec(ctx, project, svc, job, options.UseNetworkAliases) + if err != nil { + return 0, err + } + + jc, err := s.jobsClient() + if err != nil { + return 0, err + } + created, err := s.createJobRun(ctx, jc, project, name, job, spec) + if err != nil { + return 0, err + } + + running, err := jc.Wait(ctx, &jobsv0.WaitRequest{ + JobRef: created.JobID, + RunRef: created.ID, + Condition: jobsv0.WaitConditionRunning, + }) + if err := jobsv0.MapError(err); err != nil { + return 0, err + } + containerID := created.ContainerID + if running.Run != nil { + containerID = running.Run.ContainerID + } + + logsDone := make(chan struct{}) + go func() { + defer close(logsDone) + if containerID == "" { + return + } + if err := s.streamJobLogs(ctx, containerID, svc.Tty); err != nil && ctx.Err() == nil { + logrus.Debugf("job %q: log stream ended: %v", name, err) + } + }() + + waited, err := jc.Wait(ctx, &jobsv0.WaitRequest{ + JobRef: created.JobID, + RunRef: created.ID, + }) + <-logsDone + if err := jobsv0.MapError(err); err != nil { + return 0, err + } + + run := waited.Run + if run == nil { + return 1, fmt.Errorf("job %q: Wait returned no run", name) + } + switch run.State { + case jobsv0.RunStateSucceeded: + return 0, nil + case jobsv0.RunStateFailed: + if run.ExitCode != nil { + return int(run.ExitCode.Value), nil + } + return 1, fmt.Errorf("job %q run %s failed: %s", name, run.ID, run.Error) + case jobsv0.RunStateTimedOut: + return 124, nil + case jobsv0.RunStateCancelled: + return 130, nil + default: + return 1, fmt.Errorf("job %q run %s ended in unexpected state %q", name, run.ID, run.State) + } +} + +// createJobRun starts name's Run on the engine, routed on the job's +// declared trigger rather than on how compose happens to invoke it: the +// engine's CreateAndRun refuses a schedule-trigger spec outright +// ("create-and-run serves manual jobs only"), because registering a cron +// must never imply an immediate run. A manual-trigger job (the opt-out +// default included) is still created and run atomically via CreateAndRun. +// A scheduled job is instead Created — idempotent on SpecHash, a no-op if +// `up` already registered the identical spec, arming the schedule if not — +// then explicitly Run with Reschedule: false, so the manual fire adds to +// the cron cadence instead of replacing its next occurrence. +func (s *composeService) createJobRun(ctx context.Context, jc jobsv0.Jobs, project *types.Project, name string, job types.JobConfig, spec *jobsv0.JobSpec) (*jobsv0.Run, error) { + engineName := engineJobName(project, name) + if !HasSchedule(job) { + reply, err := jc.CreateAndRun(ctx, &jobsv0.CreateAndRunRequest{ + Name: engineName, + Spec: spec, + }) + if err := mapAlreadyExists(err, name, "run"); err != nil { + return nil, err + } + if reply.Run == nil { + return nil, fmt.Errorf("job %q: engine returned no run", name) + } + return reply.Run, nil + } + + _, err := jc.Create(ctx, &jobsv0.CreateRequest{Name: engineName, Spec: spec}) + if err := mapAlreadyExists(err, name, "run"); err != nil { + return nil, err + } + reply, err := jc.Run(ctx, &jobsv0.RunRequest{JobRef: engineName, Reschedule: false}) + if err := jobsv0.MapError(err); err != nil { + return nil, err + } + if reply.Run == nil { + return nil, fmt.Errorf("job %q: engine returned no run", name) + } + return reply.Run, nil +} + +// streamJobLogs follows a job Run's container logs from the start, exactly +// like `docker logs -f`, until the container stops producing output. tty +// must match the container's own Tty setting: with a tty allocated, the +// daemon returns a single raw stream instead of the stdout/stderr-framed +// stream stdcopy expects (see doLogContainer in logs.go for the same split). +func (s *composeService) streamJobLogs(ctx context.Context, containerID string, tty bool) error { + r, err := s.apiClient().ContainerLogs(ctx, containerID, client.ContainerLogsOptions{ + ShowStdout: true, + ShowStderr: true, + Follow: true, + }) + if err != nil { + return err + } + defer r.Close() //nolint:errcheck + if tty { + _, err = io.Copy(s.stdout(), r) + } else { + _, err = stdcopy.StdCopy(s.stdout(), s.stderr(), r) + } + return err +} + +// ensureJobsDown removes the project's jobs from the engine. Run history is +// kept by default (RunsRemoval left empty); each removal tolerates the job +// already being gone, matching removeResource's NotFound handling. +func (s *composeService) ensureJobsDown(ctx context.Context, project *types.Project) []downOp { + names := sortedJobNames(project.Jobs, nil) + + var ops []downOp + for _, name := range names { + ops = append(ops, func() error { + return s.removeResource("Job "+name, func() error { + jc, err := s.jobsClient() + if err != nil { + return err + } + err = jc.Remove(ctx, &jobsv0.RemoveRequest{JobRef: engineJobName(project, name)}) + return jobsv0.MapError(err) + }) + }) + } + return ops +} + +// actualJobs reconstructs a project's jobs from the engine's own registry, +// keyed by their local (un-prefixed) name — used when down has no compose +// file to read Jobs from directly (e.g. `compose --project-name X down`). +// Best-effort: any error, including an engine with no jobs feature, is +// treated as "no jobs" so it never blocks an otherwise-successful down. +func (s *composeService) actualJobs(ctx context.Context, projectName string) types.Jobs { + jc, err := s.jobsClient() + if err != nil { + return nil + } + reply, err := jc.List(ctx, &jobsv0.ListRequest{ + Labels: []string{api.ProjectLabel + "=" + projectName}, + }) + if err := jobsv0.MapError(err); err != nil { + // Best-effort: down must not fail or get noisy just because this + // project has no jobs (or the engine has no jobs feature at all — + // which surfaces as anything from a clean Unimplemented to a raw + // transport error, depending on the daemon). Still traceable with + // -v for the case where jobs really were left behind. + logrus.Debugf("failed to list jobs for project %q: %v", projectName, err) + return nil + } + + prefix := projectName + engineJobNameSeparator + jobs := types.Jobs{} + for _, j := range reply.Jobs { + name := strings.TrimPrefix(j.Name, prefix) + jobs[name] = types.JobConfig{Name: name} + } + return jobs +} diff --git a/pkg/compose/jobs_test.go b/pkg/compose/jobs_test.go new file mode 100644 index 0000000000..e2b716937d --- /dev/null +++ b/pkg/compose/jobs_test.go @@ -0,0 +1,303 @@ +/* + Copyright 2020 Docker Compose CLI authors + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package compose + +import ( + "context" + "testing" + + "github.com/compose-spec/compose-go/v2/types" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + "gotest.tools/v3/assert" + + jobsv0 "github.com/docker/compose/v5/internal/jobsapi" +) + +func TestJobTrigger(t *testing.T) { + yes, no := true, false + + t.Run("no triggers declared is refused", func(t *testing.T) { + _, err := jobTrigger(types.JobConfig{Name: "migrate"}) + assert.Error(t, err, `job "migrate" has no trigger`) + }) + + t.Run("manual:true translates to a Manual trigger", func(t *testing.T) { + trigger, err := jobTrigger(types.JobConfig{Name: "migrate", Triggers: &types.TriggerConfig{Manual: &yes}}) + assert.NilError(t, err) + assert.DeepEqual(t, trigger, &jobsv0.Trigger{Manual: true}) + }) + + t.Run("manual:false alone (no schedule) is refused: it has no trigger left", func(t *testing.T) { + _, err := jobTrigger(types.JobConfig{Name: "migrate", Triggers: &types.TriggerConfig{Manual: &no}}) + assert.Error(t, err, `job "migrate" has no trigger`) + }) + + t.Run("a single schedule translates to a Schedule trigger", func(t *testing.T) { + trigger, err := jobTrigger(types.JobConfig{Name: "backup", Triggers: &types.TriggerConfig{ + Schedule: []types.ScheduleConfig{{Cron: "0 3 * * *", Timezone: "UTC"}}, + }}) + assert.NilError(t, err) + assert.DeepEqual(t, trigger, &jobsv0.Trigger{Schedule: &jobsv0.ScheduleTrigger{Cron: "0 3 * * *", Timezone: "UTC"}}) + }) + + t.Run("more than one schedule is refused", func(t *testing.T) { + _, err := jobTrigger(types.JobConfig{Name: "backup", Triggers: &types.TriggerConfig{ + Schedule: []types.ScheduleConfig{{Cron: "0 3 * * *"}, {Cron: "0 4 * * *"}}, + }}) + assert.Error(t, err, `job "backup" declares 2 schedules, exactly one is supported`) + }) + + // A job declaring both manual:true and a schedule used to silently lose + // the schedule: the switch checked Manual before Schedule, so + // registerScheduledJobs would still select the job (HasSchedule doesn't + // look at Manual) but jobTrigger built a Manual-only Trigger, dropping + // the cron with no error and no warning anywhere. + t.Run("manual:true together with a schedule is refused, not silently resolved to Manual", func(t *testing.T) { + _, err := jobTrigger(types.JobConfig{Name: "backup", Triggers: &types.TriggerConfig{ + Manual: &yes, + Schedule: []types.ScheduleConfig{{Cron: "0 3 * * *"}}, + }}) + assert.Error(t, err, `job "backup" declares both manual:true and a schedule, exactly one is supported`) + }) + + t.Run("manual:false together with a schedule keeps the schedule", func(t *testing.T) { + trigger, err := jobTrigger(types.JobConfig{Name: "backup", Triggers: &types.TriggerConfig{ + Manual: &no, + Schedule: []types.ScheduleConfig{{Cron: "0 3 * * *"}}, + }}) + assert.NilError(t, err) + assert.DeepEqual(t, trigger, &jobsv0.Trigger{Schedule: &jobsv0.ScheduleTrigger{Cron: "0 3 * * *"}}) + }) +} + +func TestHasSchedule(t *testing.T) { + assert.Assert(t, !HasSchedule(types.JobConfig{})) + assert.Assert(t, !HasSchedule(types.JobConfig{Triggers: &types.TriggerConfig{}})) + assert.Assert(t, HasSchedule(types.JobConfig{Triggers: &types.TriggerConfig{ + Schedule: []types.ScheduleConfig{{Cron: "0 3 * * *"}}, + }})) +} + +func TestManualTriggerDisabled(t *testing.T) { + yes, no := true, false + + assert.Assert(t, !ManualTriggerDisabled(types.JobConfig{}), "no triggers at all is not an opt-out") + assert.Assert(t, !ManualTriggerDisabled(types.JobConfig{Triggers: &types.TriggerConfig{}}), "an unset Manual is not an opt-out") + assert.Assert(t, !ManualTriggerDisabled(types.JobConfig{Triggers: &types.TriggerConfig{Manual: &yes}})) + assert.Assert(t, ManualTriggerDisabled(types.JobConfig{Triggers: &types.TriggerConfig{Manual: &no}})) +} + +func TestSortedJobNames(t *testing.T) { + jobs := types.Jobs{ + "backup": {Triggers: &types.TriggerConfig{Schedule: []types.ScheduleConfig{{Cron: "0 3 * * *"}}}}, + "migrate": {}, + "report": {}, + } + + assert.DeepEqual(t, sortedJobNames(jobs, nil), []string{"backup", "migrate", "report"}) + assert.DeepEqual(t, sortedJobNames(jobs, HasSchedule), []string{"backup"}) + assert.DeepEqual(t, sortedJobNames(types.Jobs{}, HasSchedule), []string{}) +} + +func TestJobChangedErr(t *testing.T) { + assert.Error(t, jobChangedErr("backup", "up"), + `job "backup" has changed: run `+"`docker compose down`"+` to remove it, then `+"`up`"+` again`) + assert.Error(t, jobChangedErr("migrate", "run"), + `job "migrate" has changed: run `+"`docker compose down`"+` to remove it, then `+"`run`"+` again`) +} + +func TestManualTriggerDisabledErr(t *testing.T) { + assert.Error(t, ManualTriggerDisabledErr("rotation"), + `job "rotation" is declared with manual: false, it cannot be run manually`) +} + +// scopedProjectForJob is what lets registerScheduledJobs run a scheduled +// job through the same useAPISocket/ensureImagesExists/ensureModels passes +// a manually-run job gets for free from materializeManualJob — without the +// job ever joining the real project.Services the reconciliation loop +// iterates over. +func TestScopedProjectForJob(t *testing.T) { + project := &types.Project{ + Name: "myproject", + Services: types.Services{ + "db": { + Name: "db", + ContainerSpec: types.ContainerSpec{ + Image: "postgres", + Environment: types.MappingWithEquals{"FOO": strPtr("original")}, + CustomLabels: types.Labels{"original": "label"}, + }, + }, + }, + Jobs: types.Jobs{ + "backup": { + Name: "backup", + Extensions: types.Extensions{"x-team": "platform"}, + ContainerSpec: types.ContainerSpec{Image: "backup-tool"}, + }, + }, + Configs: types.Configs{ + "cfg": {Content: "original"}, + }, + } + job := project.Jobs["backup"] + + scoped := scopedProjectForJob(project, "backup", job) + + t.Run("the job is materialized into the scoped copy's Services", func(t *testing.T) { + svc, err := scoped.GetService("backup") + assert.NilError(t, err) + assert.Equal(t, svc.Image, "backup-tool") + assert.Equal(t, svc.Extensions["x-team"], "platform") + }) + + t.Run("existing services are carried over", func(t *testing.T) { + svc, err := scoped.GetService("db") + assert.NilError(t, err) + assert.Equal(t, svc.Image, "postgres") + }) + + t.Run("the real project.Services is never mutated", func(t *testing.T) { + _, ok := project.Services["backup"] + assert.Assert(t, !ok, "the job must not leak into the shared project's Services") + assert.Equal(t, len(project.Services), 1) + }) + + t.Run("Configs is its own map, not shared with the real project", func(t *testing.T) { + scoped.Configs["new"] = types.ConfigObjConfig{Content: "added"} + _, ok := project.Configs["new"] + assert.Assert(t, !ok, "writing to the scoped copy's Configs must not be visible on the shared project — registerScheduledJobs runs this concurrently per job") + assert.Equal(t, project.Configs["cfg"].Content, "original") + }) + + t.Run("a carried-over service's Environment is its own map, not shared with the real project", func(t *testing.T) { + svc, err := scoped.GetService("db") + assert.NilError(t, err) + svc.Environment["FOO"] = strPtr("mutated") + // useAPISocket/ensureModels write into a job's scoped Environment map + // concurrently with other jobs' registration — it must not be the + // shared project's map. + assert.Equal(t, *project.Services["db"].Environment["FOO"], "original") + }) + + t.Run("a carried-over service's CustomLabels is its own map, not shared with the real project", func(t *testing.T) { + svc, err := scoped.GetService("db") + assert.NilError(t, err) + svc.CustomLabels["new"] = "added" + _, ok := project.Services["db"].CustomLabels["new"] + assert.Assert(t, !ok, "ensureImagesExists writes into a job's scoped CustomLabels map (via Labels.Add) concurrently with other jobs' registration — it must not be the shared project's map") + }) +} + +// fakeJobsClient is a minimal jobsv0.Jobs double for createJobRun's routing: +// only Create/Run/CreateAndRun are ever exercised by RunJob, so every other +// method is left to the embedded nil interface — calling one would panic, +// which is exactly the loud failure a test relying on it deserves. +type fakeJobsClient struct { + jobsv0.Jobs + + createCalls []*jobsv0.CreateRequest + createErr error + createAndRunCalls []*jobsv0.CreateAndRunRequest + createAndRunReply *jobsv0.CreateAndRunReply + createAndRunErr error + runCalls []*jobsv0.RunRequest + runReply *jobsv0.RunReply + runErr error +} + +func (f *fakeJobsClient) Create(_ context.Context, req *jobsv0.CreateRequest) (*jobsv0.CreateReply, error) { + f.createCalls = append(f.createCalls, req) + if f.createErr != nil { + return nil, f.createErr + } + return &jobsv0.CreateReply{Job: &jobsv0.Job{Name: req.Name}}, nil +} + +func (f *fakeJobsClient) CreateAndRun(_ context.Context, req *jobsv0.CreateAndRunRequest) (*jobsv0.CreateAndRunReply, error) { + f.createAndRunCalls = append(f.createAndRunCalls, req) + return f.createAndRunReply, f.createAndRunErr +} + +func (f *fakeJobsClient) Run(_ context.Context, req *jobsv0.RunRequest) (*jobsv0.RunReply, error) { + f.runCalls = append(f.runCalls, req) + return f.runReply, f.runErr +} + +// createJobRun routes on the job's declared trigger, not on how it happens +// to be invoked: the engine's CreateAndRun refuses a schedule-trigger spec +// outright ("create-and-run serves manual jobs only"), because registering +// a cron must never imply an immediate run. A manual-trigger job (the +// opt-out default included) still goes through CreateAndRun unchanged. +func TestCreateJobRun(t *testing.T) { + s := &composeService{} + project := &types.Project{Name: "myproject"} + spec := &jobsv0.JobSpec{} + + t.Run("a manual-trigger job uses CreateAndRun", func(t *testing.T) { + yes := true + job := types.JobConfig{Name: "migrate", Triggers: &types.TriggerConfig{Manual: &yes}} + fake := &fakeJobsClient{createAndRunReply: &jobsv0.CreateAndRunReply{Run: &jobsv0.Run{ID: "run-1", JobID: "job-1"}}} + + run, err := s.createJobRun(t.Context(), fake, project, "migrate", job, spec) + assert.NilError(t, err) + assert.Equal(t, run.ID, "run-1") + assert.Equal(t, len(fake.createAndRunCalls), 1) + assert.Equal(t, fake.createAndRunCalls[0].Name, "myproject.migrate") + assert.Equal(t, len(fake.createCalls), 0, "a manual job must never call Create") + assert.Equal(t, len(fake.runCalls), 0, "a manual job must never call the schedule-job Run path") + }) + + t.Run("a scheduled job uses Create then Run, not CreateAndRun", func(t *testing.T) { + job := types.JobConfig{Name: "backup", Triggers: &types.TriggerConfig{ + Schedule: []types.ScheduleConfig{{Cron: "0 3 * * *"}}, + }} + fake := &fakeJobsClient{runReply: &jobsv0.RunReply{Run: &jobsv0.Run{ID: "run-2", JobID: "job-2"}}} + + run, err := s.createJobRun(t.Context(), fake, project, "backup", job, spec) + assert.NilError(t, err) + assert.Equal(t, run.ID, "run-2") + assert.Equal(t, len(fake.createCalls), 1) + assert.Equal(t, fake.createCalls[0].Name, "myproject.backup") + assert.Equal(t, len(fake.runCalls), 1) + assert.Equal(t, fake.runCalls[0].JobRef, "myproject.backup") + assert.Assert(t, !fake.runCalls[0].Reschedule, "a manual fire must add to the cron cadence, not replace its next occurrence") + assert.Equal(t, len(fake.createAndRunCalls), 0, "a scheduled job must never call CreateAndRun: the engine rejects it") + }) + + t.Run("a scheduled job already registered by up is a no-op Create, not a conflict", func(t *testing.T) { + job := types.JobConfig{Name: "backup", Triggers: &types.TriggerConfig{ + Schedule: []types.ScheduleConfig{{Cron: "0 3 * * *"}}, + }} + fake := &fakeJobsClient{runReply: &jobsv0.RunReply{Run: &jobsv0.Run{ID: "run-3"}}} + + _, err := s.createJobRun(t.Context(), fake, project, "backup", job, spec) + assert.NilError(t, err, "Create must succeed as a no-op when up already registered the identical spec") + }) + + t.Run("a scheduled job with a changed spec reports the same conflict a manual job would", func(t *testing.T) { + job := types.JobConfig{Name: "backup", Triggers: &types.TriggerConfig{ + Schedule: []types.ScheduleConfig{{Cron: "0 3 * * *"}}, + }} + fake := &fakeJobsClient{createErr: status.Error(codes.AlreadyExists, "spec differs")} + + _, err := s.createJobRun(t.Context(), fake, project, "backup", job, spec) + assert.Error(t, err, `job "backup" has changed: run `+"`docker compose down`"+` to remove it, then `+"`run`"+` again`) + assert.Equal(t, len(fake.runCalls), 0, "Run must not be attempted after a Create conflict") + }) +} diff --git a/pkg/compose/observed_state.go b/pkg/compose/observed_state.go index 25d0e3da6c..a4df46a72e 100644 --- a/pkg/compose/observed_state.go +++ b/pkg/compose/observed_state.go @@ -21,7 +21,6 @@ import ( "slices" "sort" "strconv" - "strings" "github.com/compose-spec/compose-go/v2/types" "github.com/containerd/errdefs" @@ -31,6 +30,10 @@ import ( "github.com/docker/compose/v5/pkg/api" ) +// jobContainerIDLabel is the engine's reserved label identifying a job run +// container (see the jobsv0 contract); Compose never sets it itself. +const jobContainerIDLabel = "com.docker.job.id" + // ObservedState captures the current state of all Docker resources belonging to // a Compose project. It is a snapshot taken before reconciliation so that the // reconciler can compare desired state (types.Project) with reality without @@ -394,15 +397,6 @@ func emitRunningEvents(project *types.Project, observed *ObservedState, plan *Pl } } -// orphanNames returns the names of orphaned containers as a comma-separated string. -func (s *ObservedState) orphanNames() string { - names := make([]string, len(s.Orphans)) - for i, o := range s.Orphans { - names[i] = o.Name - } - return strings.Join(names, ", ") -} - // containersByService flattens the observed containers into the shape // resolveServiceReferences expects: project service name → raw Summaries. func (s *ObservedState) containersByService() map[string]Containers { diff --git a/pkg/compose/run.go b/pkg/compose/run.go index 1f233fe32f..d780ba30df 100644 --- a/pkg/compose/run.go +++ b/pkg/compose/run.go @@ -138,6 +138,9 @@ func (s *composeService) prepareRun(ctx context.Context, project *types.Project, return prepareRunResult{}, err } + service.Tty = options.Tty + service.StdinOpen = options.Interactive + service.ContainerName = options.Name applyRunOptions(project, &service, options) if err := s.stdin().CheckTty(options.Interactive, service.Tty); err != nil { @@ -223,11 +226,18 @@ func prepareBuildOptions(options api.RunOptions) *api.BuildOptions { return &buildOptionsCopy } +// applyRunOptions applies the CLI overrides shared by a one-off service run +// and a job run — Command, User, CapAdd/CapDrop, WorkingDir, Entrypoint, +// Environment, Labels, exactly the set RunJob's own doc comment on +// api.Compose documents as supported. Tty/StdinOpen/ContainerName are +// deliberately not here: prepareRun (RunOneOffContainer's caller) sets them +// itself for its own container-naming/attach needs, and a job has neither +// (no interactive attach, and the jobs API assigns the run container's +// identity itself) — RunJob resets them back to their zero value right +// after calling this, since CLI-layer code upstream of both (cmd/compose's +// runOptions.apply) already stamped the service with terminal-context +// defaults meant for a one-off container, not a job. func applyRunOptions(project *types.Project, service *types.ServiceConfig, options api.RunOptions) { - service.Tty = options.Tty - service.StdinOpen = options.Interactive - service.ContainerName = options.Name - if len(options.Command) > 0 { service.Command = options.Command } diff --git a/pkg/compose/up.go b/pkg/compose/up.go index 8aed083603..03b9a1c691 100644 --- a/pkg/compose/up.go +++ b/pkg/compose/up.go @@ -48,6 +48,12 @@ func (s *composeService) Up(ctx context.Context, project *types.Project, options if err != nil { return err } + if err := s.registerScheduledJobs(ctx, project); err != nil { + return err + } + if options.Start.NoStart { + return nil + } if options.Start.Attach == nil { return s.start(ctx, project.Name, options.Start, nil) } diff --git a/pkg/e2e/jobs_test.go b/pkg/e2e/jobs_test.go index b64f1700bd..52fe8d921c 100644 --- a/pkg/e2e/jobs_test.go +++ b/pkg/e2e/jobs_test.go @@ -20,16 +20,50 @@ package e2e import ( "testing" + "time" ) -// Scheduled jobs cannot run in this version: silently not scheduling them -// would break the user's expectations, so up must refuse the whole project. -func TestUpRejectsScheduledJobs(t *testing.T) { - NewScenario(t, "up must reject a project declaring active scheduled jobs, before creating anything"). - Step("up fails naming the scheduled job", - ComposeCmd("up", "-d").MayFail(), - StderrContains("scheduled jobs are not supported in this version: backup"), - ServiceNotCreated("web")) +// A scheduled job registers with the engine instead of being rejected: up is +// safely re-runnable on an unchanged spec, and the schedule fires on the +// engine's own clock, independent of the client. +func TestUpRegistersScheduledJobs(t *testing.T) { + NewScenario(t, "up must register a project's scheduled jobs with the engine and let them fire on their own"). + Step("up starts services and registers the scheduled job", + ComposeCmd("up", "-d"), + ServiceState("web", "running")). + Step("re-up is a no-op on the unchanged job spec, and the schedule fires on the engine's own clock", + ComposeCmd("up", "-d"), + ServiceState("web", "running"), + Eventually(ServiceState("backup", "exited"), 90*time.Second)) +} + +// run must build the exact same spec `up` already registered a scheduled +// job with: CreateAndRun refuses schedule-trigger jobs outright, so run +// routes through Create (idempotent on SpecHash) then Run instead. A run +// invocation carries CLI/terminal-context defaults (Tty, StdinOpen, +// ContainerName) that up's own registration never does — if those leaked +// into the spec sent to Create, this would spuriously conflict with the +// job up already registered, even though nothing in the compose file +// changed. +func TestRunAlreadyRegisteredScheduledJob(t *testing.T) { + NewScenario(t, "run must not conflict with a scheduled job up already registered with the identical spec"). + Step("up registers the scheduled job", + ComposeCmd("up", "-d"), + ServiceState("web", "running")). + Step("run fires it manually without a SpecHash conflict", + ComposeCmd("run", "--rm", "backup"), + OutputContains("backup-ran")) +} + +// --no-start's own path (Create, then registerScheduledJobs, then return +// before Start) must still register scheduled jobs: it used to bypass Up +// entirely by calling Create directly, silently skipping registration. +func TestUpNoStartRegistersScheduledJobs(t *testing.T) { + NewScenario(t, "up --no-start must still register a project's scheduled jobs with the engine"). + Step("up --no-start creates but never starts web, yet the schedule still fires on its own", + ComposeCmd("up", "--no-start"), + ServiceState("web", "created"), + Eventually(ServiceState("backup", "exited"), 90*time.Second)) } // A job runs through `compose run` exactly like a service would: its @@ -97,6 +131,15 @@ func TestCreateRefusesJob(t *testing.T) { ServiceNotCreated("migrate")) } +// up shares WithServices with create: the same translation must apply there too. +func TestUpRefusesJob(t *testing.T) { + NewScenario(t, "up must refuse a job by name, naming run as the right command"). + Step("up fails naming the job", + ComposeCmd("up", "-d", "migrate").MayFail(), + StderrContains(`job "migrate" can only be triggered with "docker compose run"`), + ServiceNotCreated("migrate")) +} + func TestStartRefusesJob(t *testing.T) { NewScenario(t, "start must refuse a job by name, naming run as the right command"). Step("start fails naming the job", diff --git a/pkg/e2e/testdata/TestUpRejectsScheduledJobs/compose.yaml b/pkg/e2e/testdata/TestRunAlreadyRegisteredScheduledJob/compose.yaml similarity index 75% rename from pkg/e2e/testdata/TestUpRejectsScheduledJobs/compose.yaml rename to pkg/e2e/testdata/TestRunAlreadyRegisteredScheduledJob/compose.yaml index 5efd658bb6..0b5c34e3b5 100644 --- a/pkg/e2e/testdata/TestUpRejectsScheduledJobs/compose.yaml +++ b/pkg/e2e/testdata/TestRunAlreadyRegisteredScheduledJob/compose.yaml @@ -1,12 +1,12 @@ services: web: image: alpine - init: true command: sleep infinity jobs: backup: image: alpine - command: echo backup + command: sh -c 'echo backup-ran' + tty: true triggers: schedule: - cron: "0 3 * * *" diff --git a/pkg/e2e/testdata/TestUpNoStartRegistersScheduledJobs/compose.yaml b/pkg/e2e/testdata/TestUpNoStartRegistersScheduledJobs/compose.yaml new file mode 100644 index 0000000000..48e5d07b69 --- /dev/null +++ b/pkg/e2e/testdata/TestUpNoStartRegistersScheduledJobs/compose.yaml @@ -0,0 +1,15 @@ +services: + web: + image: alpine + init: true + command: sleep infinity +jobs: + # Fails on purpose: a successful run is removed on exit (RemoveOnSuccess), + # leaving nothing to observe — failing keeps the container around + # (RemoveOnFailure: false) so the fire is verifiable via container state. + backup: + image: alpine + command: sh -c 'exit 1' + triggers: + schedule: + - cron: "* * * * *" diff --git a/pkg/e2e/testdata/TestUpRefusesJob/compose.yaml b/pkg/e2e/testdata/TestUpRefusesJob/compose.yaml new file mode 100644 index 0000000000..7cbebdadeb --- /dev/null +++ b/pkg/e2e/testdata/TestUpRefusesJob/compose.yaml @@ -0,0 +1,10 @@ +services: + web: + image: alpine + command: sleep infinity +jobs: + migrate: + image: alpine + command: sh -c 'echo "migration done"' + triggers: + manual: true diff --git a/pkg/e2e/testdata/TestUpRegistersScheduledJobs/compose.yaml b/pkg/e2e/testdata/TestUpRegistersScheduledJobs/compose.yaml new file mode 100644 index 0000000000..48e5d07b69 --- /dev/null +++ b/pkg/e2e/testdata/TestUpRegistersScheduledJobs/compose.yaml @@ -0,0 +1,15 @@ +services: + web: + image: alpine + init: true + command: sleep infinity +jobs: + # Fails on purpose: a successful run is removed on exit (RemoveOnSuccess), + # leaving nothing to observe — failing keeps the container around + # (RemoveOnFailure: false) so the fire is verifiable via container state. + backup: + image: alpine + command: sh -c 'exit 1' + triggers: + schedule: + - cron: "* * * * *" diff --git a/pkg/mocks/mock_docker_compose_api.go b/pkg/mocks/mock_docker_compose_api.go index 664de10609..799334a167 100644 --- a/pkg/mocks/mock_docker_compose_api.go +++ b/pkg/mocks/mock_docker_compose_api.go @@ -370,6 +370,21 @@ func (mr *MockComposeMockRecorder) Restart(ctx, projectName, options any) *gomoc return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Restart", reflect.TypeOf((*MockCompose)(nil).Restart), ctx, projectName, options) } +// RunJob mocks base method. +func (m *MockCompose) RunJob(ctx context.Context, project *types.Project, name string, options api.RunOptions) (int, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "RunJob", ctx, project, name, options) + ret0, _ := ret[0].(int) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// RunJob indicates an expected call of RunJob. +func (mr *MockComposeMockRecorder) RunJob(ctx, project, name, options any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RunJob", reflect.TypeOf((*MockCompose)(nil).RunJob), ctx, project, name, options) +} + // RunOneOffContainer mocks base method. func (m *MockCompose) RunOneOffContainer(ctx context.Context, project *types.Project, opts api.RunOptions) (int, error) { m.ctrl.T.Helper()