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
17 changes: 6 additions & 11 deletions internal/drivers/app_platform_buildspec.go
Original file line number Diff line number Diff line change
Expand Up @@ -711,12 +711,9 @@ func buildJobSpec(m map[string]any) *godo.AppJobSpec {
InstanceSizeSlug: strFromConfig(m, "instance_size_slug", ""),
Envs: envVarsFromJobConfig(m),
}
// Image from "image" field.
if imgStr := strFromConfig(m, "image", ""); imgStr != "" {
img, err := ParseImageRef(imgStr)
if err == nil {
job.Image = img
}
// Image from "image" field (accepts string or structured map).
if img, err := imageSpecFromConfig(m); err == nil {
job.Image = img
}
// Scheduled jobs have a cron expression.
if cron := strFromConfig(m, "cron", ""); cron != "" {
Expand Down Expand Up @@ -785,11 +782,9 @@ func buildWorkerSpec(m map[string]any) *godo.AppWorkerSpec {
Envs: envVarsFromJobConfig(m),
Autoscaling: autoscalingFromConfig(m),
}
if imgStr := strFromConfig(m, "image", ""); imgStr != "" {
img, err := ParseImageRef(imgStr)
if err == nil {
w.Image = img
}
// Image from "image" field (accepts string or structured map).
if img, err := imageSpecFromConfig(m); err == nil {
w.Image = img
}
// size tier override.
if size := strFromConfig(m, "size", ""); size != "" {
Expand Down
169 changes: 169 additions & 0 deletions internal/drivers/app_platform_buildspec_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1043,6 +1043,92 @@ func TestBuildAppSpec_Jobs_Termination(t *testing.T) {
}
}

func TestBuildAppSpec_Jobs_ImageMapShape(t *testing.T) {
cfg := map[string]any{
"image": "registry.digitalocean.com/myrepo/myapp:v1",
"jobs": []any{
map[string]any{
"name": "db-reset",
"kind": "pre_deploy",
"image": map[string]any{
"registry_type": "DOCKER_HUB",
"repository": "library/postgres",
"tag": "18",
},
"run_command": "/usr/local/bin/reset-db",
},
},
}
spec := buildSpecViaCreate(t, cfg)
if len(spec.Jobs) != 1 {
t.Fatalf("expected 1 job, got %d", len(spec.Jobs))
}
job := spec.Jobs[0]
if job.Image == nil {
t.Fatal("Job.Image is nil; structured map image was silently dropped")
}
if job.Image.RegistryType != godo.ImageSourceSpecRegistryType_DockerHub {
t.Errorf("RegistryType = %q, want DOCKER_HUB", job.Image.RegistryType)
}
if job.Image.Repository != "library/postgres" {
t.Errorf("Repository = %q, want library/postgres", job.Image.Repository)
}
if job.Image.Tag != "18" {
t.Errorf("Tag = %q, want 18", job.Image.Tag)
}
}

func TestBuildAppSpec_Jobs_ImageStringShape(t *testing.T) {
cfg := map[string]any{
"image": "registry.digitalocean.com/myrepo/myapp:v1",
"jobs": []any{
map[string]any{
"name": "migrate",
"kind": "pre_deploy",
"image": "docker.io/library/postgres:16",
"run_command": "/migrate up",
},
},
}
spec := buildSpecViaCreate(t, cfg)
if len(spec.Jobs) != 1 {
t.Fatalf("expected 1 job, got %d", len(spec.Jobs))
}
job := spec.Jobs[0]
if job.Image == nil {
t.Fatal("Job.Image is nil; string image was not parsed")
}
if job.Image.RegistryType != godo.ImageSourceSpecRegistryType_DockerHub {
t.Errorf("RegistryType = %q, want DOCKER_HUB", job.Image.RegistryType)
}
if job.Image.Repository != "postgres" {
t.Errorf("Repository = %q, want postgres", job.Image.Repository)
}
if job.Image.Tag != "16" {
t.Errorf("Tag = %q, want 16", job.Image.Tag)
}
}

func TestBuildAppSpec_Jobs_ImageEmpty(t *testing.T) {
cfg := map[string]any{
"image": "registry.digitalocean.com/myrepo/myapp:v1",
"jobs": []any{
map[string]any{
"name": "no-image-job",
"kind": "post_deploy",
"run_command": "/app/smoke",
},
},
}
spec := buildSpecViaCreate(t, cfg)
if len(spec.Jobs) != 1 {
t.Fatalf("expected 1 job, got %d", len(spec.Jobs))
}
if spec.Jobs[0].Image != nil {
t.Errorf("expected Job.Image to be nil when no image is set, got %+v", spec.Jobs[0].Image)
}
}

// ── Workers ──────────────────────────────────────────────────────────────────

func TestBuildAppSpec_Workers(t *testing.T) {
Expand Down Expand Up @@ -1134,6 +1220,89 @@ func TestBuildAppSpec_Workers_Termination(t *testing.T) {
}
}

func TestBuildAppSpec_Workers_ImageMapShape(t *testing.T) {
cfg := map[string]any{
"image": "registry.digitalocean.com/myrepo/myapp:v1",
"workers": []any{
map[string]any{
"name": "queue-processor",
"image": map[string]any{
"registry_type": "DOCKER_HUB",
"repository": "library/redis",
"tag": "7",
},
"run_command": "/app/worker",
},
},
}
spec := buildSpecViaCreate(t, cfg)
if len(spec.Workers) != 1 {
t.Fatalf("expected 1 worker, got %d", len(spec.Workers))
}
w := spec.Workers[0]
if w.Image == nil {
t.Fatal("Worker.Image is nil; structured map image was silently dropped")
}
if w.Image.RegistryType != godo.ImageSourceSpecRegistryType_DockerHub {
t.Errorf("RegistryType = %q, want DOCKER_HUB", w.Image.RegistryType)
}
if w.Image.Repository != "library/redis" {
t.Errorf("Repository = %q, want library/redis", w.Image.Repository)
}
if w.Image.Tag != "7" {
t.Errorf("Tag = %q, want 7", w.Image.Tag)
}
}

func TestBuildAppSpec_Workers_ImageStringShape(t *testing.T) {
cfg := map[string]any{
"image": "registry.digitalocean.com/myrepo/myapp:v1",
"workers": []any{
map[string]any{
"name": "queue-processor",
"image": "docker.io/library/redis:7",
"run_command": "/app/worker",
},
},
}
spec := buildSpecViaCreate(t, cfg)
if len(spec.Workers) != 1 {
t.Fatalf("expected 1 worker, got %d", len(spec.Workers))
}
w := spec.Workers[0]
if w.Image == nil {
t.Fatal("Worker.Image is nil; string image was not parsed")
}
if w.Image.RegistryType != godo.ImageSourceSpecRegistryType_DockerHub {
t.Errorf("RegistryType = %q, want DOCKER_HUB", w.Image.RegistryType)
}
if w.Image.Repository != "redis" {
t.Errorf("Repository = %q, want redis", w.Image.Repository)
}
if w.Image.Tag != "7" {
t.Errorf("Tag = %q, want 7", w.Image.Tag)
}
}

func TestBuildAppSpec_Workers_ImageEmpty(t *testing.T) {
cfg := map[string]any{
"image": "registry.digitalocean.com/myrepo/myapp:v1",
"workers": []any{
map[string]any{
"name": "no-image-worker",
"run_command": "/app/worker",
},
},
}
spec := buildSpecViaCreate(t, cfg)
if len(spec.Workers) != 1 {
t.Fatalf("expected 1 worker, got %d", len(spec.Workers))
}
if spec.Workers[0].Image != nil {
t.Errorf("expected Worker.Image to be nil when no image is set, got %+v", spec.Workers[0].Image)
}
}

// ── StaticSites ──────────────────────────────────────────────────────────────

func TestBuildAppSpec_StaticSites(t *testing.T) {
Expand Down
Loading