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: 15 additions & 0 deletions apps/api/internal/services/service/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -321,6 +321,12 @@ func (self *ServiceService) CreateService(ctx context.Context, requesterUserID u
}
}

sourcePorts := ports
skipDetectedPorts := skipsDetectedPorts(addDetectedPorts, input.CronSchedule)
if skipDetectedPorts {
ports = nil
}

// Validate health check
if input.HealthCheck != nil {
if input.HealthCheck.Port == nil && len(ports) > 0 {
Expand Down Expand Up @@ -415,6 +421,9 @@ func (self *ServiceService) CreateService(ctx context.Context, requesterUserID u
if addDetectedPorts {
detectedPorts = ports
}
if skipDetectedPorts {
detectedPorts = sourcePorts
}

createService, err := self.repo.Service().Create(ctx, tx,
&service_repo.CreateServiceInput{
Expand Down Expand Up @@ -598,6 +607,12 @@ func resolveAutoDeploy(requested *bool, serviceType schema.ServiceType) *bool {
return nil
}

// A scheduled service answers no requests, so the ports detected from its source
// stay metadata and never get it a port or a generated domain
func skipsDetectedPorts(detected bool, cronSchedule *string) bool {
return detected && cronSchedule != nil && *cronSchedule != ""
}

// A service that listens on a port is public unless the caller says otherwise.
// A database is private unless the caller asks for a public one.
func resolveIsPublic(requested *bool, serviceType schema.ServiceType, ports []schema.PortSpec) *bool {
Expand Down
14 changes: 14 additions & 0 deletions apps/api/internal/services/service/create_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,3 +92,17 @@ func TestCleanCreateWatchPaths(t *testing.T) {
})
}
}

// A cron service built from a repo that EXPOSEs a port must not get that port, and
// with no port it gets no generated domain
func TestSkipsDetectedPortsForCron(t *testing.T) {
hourly := "0 * * * *"
empty := ""

assert.True(t, skipsDetectedPorts(true, &hourly))
assert.False(t, skipsDetectedPorts(false, &hourly), "ports the caller sent are kept")
assert.False(t, skipsDetectedPorts(true, &empty), "an empty schedule runs continuously")
assert.False(t, skipsDetectedPorts(true, nil))

assert.Nil(t, resolveIsPublic(nil, schema.ServiceTypeGithub, nil))
}
2 changes: 1 addition & 1 deletion apps/docs/content/(platform)/services/cron-jobs.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ A failed run can send a [webhook](/monitoring/webhooks). Pick **Cron Run Failed*

## What changes with a schedule

A service on a schedule has no replicas, domains or ports, and no health checks. Those settings show why they are unavailable. Its domains stop answering while it has a schedule and come back when the schedule is removed.
A service on a schedule has no replicas, domains or ports, and no health checks. Those settings show why they are unavailable. Its domains stop answering while it has a schedule and come back when the schedule is removed. A service created with a schedule starts without a port or a domain, even when its Dockerfile exposes one.

**Stop** suspends the schedule: no runs start until you **Start** it again, and the run history stays.

Expand Down