dockron/main.go

136 lines
3.8 KiB
Go
Raw Normal View History

2018-08-01 15:56:13 +00:00
package main
import (
2018-08-04 00:45:53 +00:00
"flag"
2018-08-01 15:56:13 +00:00
"fmt"
2020-04-06 23:53:28 +00:00
"log"
2019-06-28 17:24:41 +00:00
"os"
2018-08-01 15:56:13 +00:00
"strings"
"time"
2020-08-07 22:00:30 +00:00
dockerTypes "github.com/docker/docker/api/types"
dockerClient "github.com/docker/docker/client"
"github.com/robfig/cron/v3"
"golang.org/x/net/context"
2018-08-01 15:56:13 +00:00
)
2019-06-28 17:24:41 +00:00
var (
// defaultWatchInterval is the duration we should sleep until polling Docker
defaultWatchInterval = (1 * time.Minute)
2018-08-03 01:56:02 +00:00
2019-06-28 17:24:41 +00:00
// schedLabel is the string label to search for cron expressions
schedLabel = "dockron.schedule"
// version of dockron being run
version = "dev"
)
2018-08-04 00:30:17 +00:00
// ContainerClient provides an interface for interracting with Docker
type ContainerClient interface {
ContainerStart(context context.Context, containerID string, options dockerTypes.ContainerStartOptions) error
ContainerList(context context.Context, options dockerTypes.ContainerListOptions) ([]dockerTypes.Container, error)
}
2018-08-04 00:30:17 +00:00
// ContainerStartJob represents a scheduled container task
// It contains a reference to a client, the schedule to run on, and the
// ID of that container that should be started
2018-08-01 15:56:13 +00:00
type ContainerStartJob struct {
Client ContainerClient
2018-08-01 15:56:13 +00:00
ContainerID string
Context context.Context
Name string
Schedule string
}
2018-08-04 00:30:17 +00:00
// Run is executed based on the ContainerStartJob Schedule and starts the
// container
2018-08-01 15:56:13 +00:00
func (job ContainerStartJob) Run() {
2020-04-06 23:53:28 +00:00
log.Println("Starting:", job.Name)
err := job.Client.ContainerStart(job.Context, job.ContainerID, dockerTypes.ContainerStartOptions{})
2018-08-01 15:56:13 +00:00
if err != nil {
panic(err)
}
}
2018-08-04 00:30:17 +00:00
// QueryScheduledJobs queries Docker for all containers with a schedule and
// returns a list of ContainerStartJob records to be scheduled
func QueryScheduledJobs(client ContainerClient) (jobs []ContainerStartJob) {
2020-04-06 23:53:28 +00:00
log.Println("Scanning containers for new schedules...")
containers, err := client.ContainerList(context.Background(), dockerTypes.ContainerListOptions{All: true})
2018-08-01 15:56:13 +00:00
if err != nil {
panic(err)
}
for _, container := range containers {
2019-06-28 17:24:41 +00:00
if val, ok := container.Labels[schedLabel]; ok {
2018-08-01 15:56:13 +00:00
jobName := strings.Join(container.Names, "/")
2018-08-01 15:59:52 +00:00
jobs = append(jobs, ContainerStartJob{
2018-08-01 15:56:13 +00:00
Schedule: val,
Client: client,
2018-08-01 15:56:13 +00:00
ContainerID: container.ID,
Context: context.Background(),
Name: jobName,
})
}
}
2018-08-03 01:56:02 +00:00
return
}
2018-08-01 15:59:52 +00:00
2018-08-04 00:30:17 +00:00
// ScheduleJobs accepts a Cron instance and a list of jobs to schedule.
// It then schedules the provided jobs
2018-08-03 01:56:02 +00:00
func ScheduleJobs(c *cron.Cron, jobs []ContainerStartJob) {
2018-08-01 15:59:52 +00:00
for _, job := range jobs {
2020-04-06 23:53:28 +00:00
// TODO: Do something with the entryId returned here
_, err := c.AddJob(job.Schedule, job)
if err == nil {
log.Printf("Scheduled %s (%s) with schedule '%s'\n", job.Name, job.ContainerID[:10], job.Schedule)
} else {
// TODO: Track something for a healthcheck here
2020-08-07 22:00:30 +00:00
log.Printf("Error scheduling %s (%s) with schedule '%s'. %v\n", job.Name, job.ContainerID[:10], job.Schedule, err)
2020-04-06 23:53:28 +00:00
}
2018-08-01 15:59:52 +00:00
}
2018-08-03 01:56:02 +00:00
}
func main() {
2018-08-04 00:30:17 +00:00
// Get a Docker Client
client, err := dockerClient.NewEnvClient()
2018-08-03 01:56:02 +00:00
if err != nil {
panic(err)
}
2018-08-01 15:59:52 +00:00
2018-08-04 00:45:53 +00:00
// Read interval for polling Docker
var watchInterval time.Duration
2019-06-28 17:24:41 +00:00
flag.DurationVar(&watchInterval, "watch", defaultWatchInterval, "Interval used to poll Docker for changes")
var showVersion = flag.Bool("version", false, "Display the version of dockron and exit")
2018-08-04 00:45:53 +00:00
flag.Parse()
2019-06-28 17:24:41 +00:00
// Print version if asked
if *showVersion {
fmt.Println("Dockron version:", version)
os.Exit(0)
}
2018-08-04 00:30:17 +00:00
// Create a Cron
2018-08-03 01:56:02 +00:00
c := cron.New()
2018-08-01 15:56:13 +00:00
// Start the loop
for {
2018-08-03 01:56:02 +00:00
// HACK: This is risky as it could fall on the same interval as a task and that task would get skipped
// It would be best to manage a ContainerID to Job mapping and then remove entries that are missing
// in the new list and add new entries. However, cron does not support this yet.
// Stop and create a new cron
c.Stop()
c = cron.New()
jobs := QueryScheduledJobs(client)
2018-08-03 01:56:02 +00:00
ScheduleJobs(c, jobs)
2020-08-07 22:00:30 +00:00
2018-08-03 01:56:02 +00:00
c.Start()
// Sleep until the next query time
2018-08-04 00:45:53 +00:00
time.Sleep(watchInterval)
2018-08-01 15:56:13 +00:00
}
}