Skip to content

Commit 7c2f52f

Browse files
feat: implement version picking and version listing for schedulers (#106)
* feat: implement version picking and version listing for schedulers * docs: regenerate manpages
1 parent e1d6be8 commit 7c2f52f

14 files changed

Lines changed: 242 additions & 15 deletions

File tree

cmd/schedctl/root.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ func NewRootCmd() *cli.Command {
4343
NewStopCmd(),
4444
NewStatusCmd(),
4545
NewDoctorCmd(),
46+
NewVersionsCmd(),
4647
},
4748
}
4849
}

cmd/schedctl/root_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ func TestNewRootCmd(t *testing.T) {
4242
func TestRootCmdHasSubcommands(t *testing.T) {
4343
rootCmd := cmd.NewRootCmd()
4444

45-
expected := []string{"run", "ps", "stop", "list", "doctor", "status"}
45+
expected := []string{"run", "ps", "stop", "list", "doctor", "status", "versions"}
4646
for _, name := range expected {
4747
assert.NotNil(t, findSubcommand(rootCmd, name), "Root command should have %s subcommand", name)
4848
}
@@ -51,7 +51,7 @@ func TestRootCmdHasSubcommands(t *testing.T) {
5151
func TestRootCmdSubcommandCount(t *testing.T) {
5252
rootCmd := cmd.NewRootCmd()
5353

54-
assert.Equal(t, 6, len(rootCmd.Commands), "Root command should declare exactly 5 subcommands")
54+
assert.Equal(t, 7, len(rootCmd.Commands), "Root command should declare exactly 7 subcommands")
5555
}
5656

5757
func TestRootCmdHasDriverPersistentFlag(t *testing.T) {

cmd/schedctl/run.go

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ Arguments after -- are passed to the scheduler container.
2424
2525
Examples:
2626
schedctl run scx_rusty
27+
schedctl run scx_rusty --version v1.0.0
2728
schedctl run scx_rusty -- --verbose
2829
schedctl run --attach scx_rusty -- --mode=performance --interval=100`,
2930
Flags: []cli.Flag{
@@ -34,6 +35,12 @@ Examples:
3435
Local: true,
3536
Category: categoryProcess,
3637
},
38+
&cli.StringFlag{
39+
Name: "version",
40+
Usage: "scheduler version (image tag) to run, e.g. v1.0.0",
41+
Local: true,
42+
Category: categoryProcess,
43+
},
3744
},
3845
Action: runAction,
3946
}
@@ -49,8 +56,9 @@ func runAction(_ context.Context, cmd *cli.Command) error {
4956
containerArgs := args[1:]
5057
driver := cmd.String("driver")
5158
attach := cmd.Bool("attach")
59+
version := cmd.String("version")
5260

53-
result, err := schedulers.GetScheduler(schedulerID)
61+
result, err := schedulers.GetScheduler(schedulerID, version)
5462
if err != nil {
5563
return err
5664
}

cmd/schedctl/run_test.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,18 @@ func TestRunCmdHasAttachFlag(t *testing.T) {
3232
assert.True(t, boolFlag.Local, "attach flag should be local to the run command")
3333
}
3434

35+
func TestRunCmdHasVersionFlag(t *testing.T) {
36+
runCmd := cmd.NewRunCmd()
37+
38+
versionFlag := lookupFlag(runCmd.Flags, "version")
39+
assert.NotNil(t, versionFlag, "run command should have 'version' flag")
40+
41+
stringFlag, ok := versionFlag.(*cli.StringFlag)
42+
assert.True(t, ok, "version flag should be a StringFlag")
43+
assert.Equal(t, "", stringFlag.Value, "version flag should default to empty string")
44+
assert.True(t, stringFlag.Local, "version flag should be local to the run command")
45+
}
46+
3547
func TestRunCmdAttachFlagIsCategorized(t *testing.T) {
3648
runCmd := cmd.NewRunCmd()
3749

cmd/schedctl/versions.go

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
package cmd
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"sort"
7+
8+
"github.com/urfave/cli/v3"
9+
10+
"schedctl/internal/output"
11+
"schedctl/internal/schedulers"
12+
)
13+
14+
func NewVersionsCmd() *cli.Command {
15+
return &cli.Command{
16+
Name: "versions",
17+
Usage: "list available versions for a scheduler",
18+
ArgsUsage: "SCHEDULER",
19+
Description: `Query the container registry for all available versions (tags)
20+
of the given scheduler.
21+
22+
Examples:
23+
schedctl versions scx_rusty
24+
schedctl versions ghcr.io/myorg/custom-scheduler`,
25+
Action: versionsAction,
26+
}
27+
}
28+
29+
func versionsAction(_ context.Context, cmd *cli.Command) error {
30+
args := cmd.Args().Slice()
31+
if len(args) == 0 {
32+
return fmt.Errorf("exactly one scheduler ID required")
33+
}
34+
35+
schedulerID := args[0]
36+
37+
versions, err := schedulers.ListVersions(schedulerID)
38+
if err != nil {
39+
return err
40+
}
41+
42+
sort.Strings(versions)
43+
for _, v := range versions {
44+
_, _ = output.Out("%s\n", v)
45+
}
46+
47+
return nil
48+
}

cmd/schedctl/versions_test.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package cmd_test
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stretchr/testify/assert"
7+
8+
cmd "schedctl/cmd/schedctl"
9+
)
10+
11+
func TestNewVersionsCmd(t *testing.T) {
12+
versionsCmd := cmd.NewVersionsCmd()
13+
14+
assert.NotNil(t, versionsCmd)
15+
assert.Equal(t, "versions", versionsCmd.Name)
16+
assert.Equal(t, "list available versions for a scheduler", versionsCmd.Usage)
17+
assert.Equal(t, "SCHEDULER", versionsCmd.ArgsUsage)
18+
assert.NotEmpty(t, versionsCmd.Description)
19+
}
20+
21+
func TestVersionsCmdHasNoLocalFlags(t *testing.T) {
22+
versionsCmd := cmd.NewVersionsCmd()
23+
24+
assert.Empty(t, versionsCmd.Flags, "versions command should have no local flags")
25+
}
26+
27+
func TestVersionsCmdRegisteredOnRoot(t *testing.T) {
28+
rootCmd := cmd.NewRootCmd()
29+
30+
assert.NotNil(t, findSubcommand(rootCmd, "versions"), "versions subcommand must be registered on the root command")
31+
}

dist/man/schedctl-run.1

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ schedctl-run
1111
.EX
1212
[--attach|-a]
1313
[--driver|-d]=[value]
14+
[--version]=[value]
1415
.EE
1516

1617

@@ -23,6 +24,7 @@ Arguments after -- are passed to the scheduler container.
2324
.PP
2425
Examples:
2526
schedctl run scx_rusty
27+
schedctl run scx_rusty --version v1.0.0
2628
schedctl run scx_rusty -- --verbose
2729
schedctl run --attach scx_rusty -- --mode=performance --interval=100
2830

@@ -39,3 +41,6 @@ schedctl run SCHEDULER [-- ARGS...]
3941

4042
.PP
4143
\fB--driver, -d\fP="": container runtime to use: containerd, podman (default: "podman")
44+
45+
.PP
46+
\fB--version\fP="": scheduler version (image tag) to run, e.g. v1.0.0

dist/man/schedctl-versions.1

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
.nh
2+
.TH schedctl-versions 1
3+
4+
.SH NAME
5+
schedctl-versions \- list available versions for a scheduler
6+
7+
8+
.SH SYNOPSIS
9+
schedctl-versions
10+
11+
.EX
12+
[--driver|-d]=[value]
13+
.EE
14+
15+
16+
.SH DESCRIPTION
17+
Query the container registry for all available versions (tags)
18+
of the given scheduler.
19+
20+
.PP
21+
Examples:
22+
schedctl versions scx_rusty
23+
schedctl versions ghcr.io/myorg/custom-scheduler
24+
25+
.PP
26+
\fBUsage\fP:
27+
28+
.EX
29+
schedctl versions SCHEDULER
30+
.EE
31+
32+
33+
.SH GLOBAL OPTIONS
34+
\fB--driver, -d\fP="": container runtime to use: containerd, podman (default: "podman")

dist/man/schedctl.1

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,9 @@ Run a specific scheduler
3838
.PP
3939
\fB--attach, -a\fP: attach to the current process instead of detaching
4040

41+
.PP
42+
\fB--version\fP="": scheduler version (image tag) to run, e.g. v1.0.0
43+
4144
.SH ps
4245
list running schedulers
4346

@@ -55,3 +58,6 @@ check host readiness for sched_ext schedulers
5558

5659
.PP
5760
\fB--output, -o\fP="": output format: text, json (default: "text")
61+
62+
.SH versions
63+
list available versions for a scheduler

go.mod

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ require (
66
github.com/anatol/vmtest v0.0.0-20230711210602-87511df0d4bc
77
github.com/containerd/containerd/v2 v2.2.0
88
github.com/containers/podman/v5 v5.8.2
9+
github.com/google/go-containerregistry v0.20.7
910
github.com/stretchr/testify v1.11.1
1011
github.com/tmc/scp v0.0.0-20170824174625-f7b48647feef
1112
github.com/urfave/cli-docs/v3 v3.1.0
@@ -86,6 +87,7 @@ require (
8687
github.com/cyberphone/json-canonicalization v0.0.0-20241213102144-19d51d7fe467 // indirect
8788
github.com/cyphar/filepath-securejoin v0.5.2 // indirect
8889
github.com/disiqueira/gotree/v3 v3.0.2 // indirect
90+
github.com/docker/cli v29.0.3+incompatible // indirect
8991
github.com/docker/distribution v2.8.3+incompatible // indirect
9092
github.com/docker/docker v28.5.2+incompatible // indirect
9193
github.com/docker/docker-credential-helpers v0.9.4 // indirect
@@ -95,7 +97,6 @@ require (
9597
github.com/go-jose/go-jose/v4 v4.1.4 // indirect
9698
github.com/godbus/dbus/v5 v5.1.1-0.20241109141217-c266b19b28e9 // indirect
9799
github.com/golang/protobuf v1.5.4 // indirect
98-
github.com/google/go-containerregistry v0.20.7 // indirect
99100
github.com/google/go-intervals v0.0.2 // indirect
100101
github.com/gorilla/mux v1.8.1 // indirect
101102
github.com/gorilla/schema v1.4.1 // indirect
@@ -111,6 +112,7 @@ require (
111112
github.com/mattn/go-sqlite3 v1.14.32 // indirect
112113
github.com/miekg/pkcs11 v1.1.1 // indirect
113114
github.com/mistifyio/go-zfs/v3 v3.1.0 // indirect
115+
github.com/mitchellh/go-homedir v1.1.0 // indirect
114116
github.com/moby/docker-image-spec v1.3.1 // indirect
115117
github.com/moby/sys/capability v0.4.0 // indirect
116118
github.com/moby/term v0.5.2 // indirect

0 commit comments

Comments
 (0)