Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 11 additions & 4 deletions cmd/schedctl/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,18 @@ func run(cmd *cobra.Command, _ []string, attach bool) error {
driver := cmd.Flags().Lookup("driver").Value.String()
schedulerID := cmd.Flags().Args()[0]

image, err := schedulers.GetScheduler(schedulerID)
result, err := schedulers.GetScheduler(schedulerID)
if err != nil {
return err
}

switch result.Source {
case schedulers.SourceManifest:
_, _ = output.Out("Running scheduler '%s' from manifest: %s\n", schedulerID, result.ImageURI)
case schedulers.SourceDirect:
_, _ = output.Out("Running container image: %s\n", result.ImageURI)
}

if driver == constants.CONTAINERD {
// connect to rootful containerd
client, err := containerd.NewClient()
Expand All @@ -44,19 +51,19 @@ func run(cmd *cobra.Command, _ []string, attach bool) error {
}
defer client.Close()

err = containerd.Run(client, image, schedulerID, attach, true)
err = containerd.Run(client, result.ImageURI, schedulerID, attach, true)
if err != nil {
return err
}
}

if driver == constants.PODMAN {
err := podman.Run(image, schedulerID)
err := podman.Run(result.ImageURI, schedulerID)
if err != nil {
panic(err)
}

_, _ = output.Out("Container %s started successfully\n", image)
_, _ = output.Out("Container %s started successfully\n", result.ImageURI)
}

return nil
Expand Down
75 changes: 67 additions & 8 deletions internal/schedulers/schedulers.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"errors"
"io"
"net/http"
"strings"

"schedctl/internal/output"
)
Expand All @@ -17,6 +18,18 @@ type ManifestEntry struct {

type Manifest map[string]ManifestEntry

type ImageSource string

const (
SourceManifest ImageSource = "manifest"
SourceDirect ImageSource = "direct"
)

type SchedulerImage struct {
ImageURI string
Source ImageSource
}

func List() Manifest {
var manifest Manifest

Expand All @@ -41,19 +54,65 @@ func List() Manifest {
return manifest
}

func GetScheduler(id string) (string, error) {
var image string
// isContainerImage checks if a string looks like a container image URI.
// Examples:
// - ghcr.io/user/repo
// - docker.io/nginx:latest
// - quay.io/org/image:v1.0
// - localhost:5000/myimage
// - nginx/alpine
func isContainerImage(input string) bool {
if strings.Contains(input, "/") {
parts := strings.Split(input, "/")
firstPart := parts[0]

if strings.Contains(firstPart, ".") || strings.Contains(firstPart, ":") {
return true
}

if len(parts) >= 2 {
return true
}
}

return false
}

func ensureImageTag(image string) string {
if strings.Contains(image, "@") {
return image
}

parts := strings.Split(image, "/")
lastPart := parts[len(parts)-1]

// Check if the last part (repo name) has a tag
if !strings.Contains(lastPart, ":") {
return image + ":latest"
}

return image
}

func GetScheduler(id string) (SchedulerImage, error) {
var result SchedulerImage

for key, entry := range List() {
manifest := List()
for key, entry := range manifest {
if key == id {
// For the moment we just append the :latest tag to the image
image = entry.ImageURI + ":latest"
result.ImageURI = ensureImageTag(entry.ImageURI)
result.Source = SourceManifest
return result, nil
}
}

if len(image) == 0 {
return "", errors.New("scheduler not found")
if isContainerImage(id) {
result.ImageURI = ensureImageTag(id)
result.Source = SourceDirect
return result, nil
}

return image, nil
return result, errors.New(
"scheduler not found in manifest and input does not appear to be a valid container image URI",
)
}
47 changes: 45 additions & 2 deletions internal/schedulers/schedulers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,55 @@ import (
)

func TestSchedulerFound(t *testing.T) {
schedulerImage, err := schedulers.GetScheduler("scx_rusty")
result, err := schedulers.GetScheduler("scx_rusty")
assert.Nil(t, err)
assert.Equal(t, "ghcr.io/schedkit/scx_rusty:latest", schedulerImage)
assert.Equal(t, "ghcr.io/schedkit/scx_rusty:latest", result.ImageURI)
assert.Equal(t, schedulers.SourceManifest, result.Source)
}

func TestSchedulerNotFound(t *testing.T) {
_, err := schedulers.GetScheduler("unknown")
assert.NotNil(t, err)
assert.Contains(t, err.Error(), "scheduler not found")
}

func TestDirectImageWithRegistry(t *testing.T) {
result, err := schedulers.GetScheduler("ghcr.io/myorg/custom-scheduler")
assert.Nil(t, err)
assert.Equal(t, "ghcr.io/myorg/custom-scheduler:latest", result.ImageURI)
assert.Equal(t, schedulers.SourceDirect, result.Source)
}

func TestDirectImageWithTag(t *testing.T) {
result, err := schedulers.GetScheduler("docker.io/nginx:alpine")
assert.Nil(t, err)
assert.Equal(t, "docker.io/nginx:alpine", result.ImageURI)
assert.Equal(t, schedulers.SourceDirect, result.Source)
}

func TestDirectImageWithDigest(t *testing.T) {
result, err := schedulers.GetScheduler("quay.io/org/image@sha256:abc123")
assert.Nil(t, err)
assert.Equal(t, "quay.io/org/image@sha256:abc123", result.ImageURI)
assert.Equal(t, schedulers.SourceDirect, result.Source)
}

func TestDirectImageLocalhost(t *testing.T) {
result, err := schedulers.GetScheduler("localhost:5000/myimage")
assert.Nil(t, err)
assert.Equal(t, "localhost:5000/myimage:latest", result.ImageURI)
assert.Equal(t, schedulers.SourceDirect, result.Source)
}

func TestDirectImageDockerHub(t *testing.T) {
result, err := schedulers.GetScheduler("nginx/alpine")
assert.Nil(t, err)
assert.Equal(t, "nginx/alpine:latest", result.ImageURI)
assert.Equal(t, schedulers.SourceDirect, result.Source)
}

func TestInvalidInput(t *testing.T) {
_, err := schedulers.GetScheduler("just-a-name")
assert.NotNil(t, err)
assert.Contains(t, err.Error(), "scheduler not found")
}