├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── cattle └── cattle.go ├── docker ├── Dockerfile └── docker-compose.yml ├── health-check.go ├── main.go ├── metadata └── metadata.go ├── model └── schedule.go ├── scheduler └── scheduler.go └── vendor └── vendor.json /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor/*/ 2 | /docker/dist/* 3 | /docker/Dockerfile 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 SocialEngine 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | VERSION = 0.2.0 2 | OUTPUT_FILE = docker/dist/rancher-cron 3 | 4 | build: 5 | CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o $(OUTPUT_FILE) && chmod +x $(OUTPUT_FILE) 6 | package: build 7 | docker build -t socialengine/rancher-cron:$(VERSION) docker 8 | publish: 9 | docker tag socialengine/rancher-cron:$(VERSION) socialengine/rancher-cron:latest && \ 10 | docker push socialengine/rancher-cron:$(VERSION) && \ 11 | docker push socialengine/rancher-cron:latest 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Rancher Cron Service 2 | ======== 3 | [![Docker Pulls](https://img.shields.io/docker/pulls/socialengine/rancher-cron.svg)](https://hub.docker.com/r/socialengine/rancher-cron/) 4 | [![Go Report Card](https://goreportcard.com/badge/github.com/socialengine/rancher-cron)](https://goreportcard.com/report/github.com/socialengine/rancher-cron) 5 | 6 | This service is used to start containers on a specified schedule and 7 | uses [robfig/cron](https://github.com/robfig/cron) cron package. 8 | 9 | When this service is running on Rancher, it will poll [Rancher Metadata](http://docs.rancher.com/rancher/latest/en/rancher-services/metadata-service/) 10 | for all stacks in an environment to find services that 11 | have `com.socialengine.rancher-cron.schedule` label set to 12 | valid cron expression format (see below). 13 | 14 | It will automatically update itself with any new and removed services 15 | every 30 seconds. 16 | 17 | Once it finds a container with `com.socialengine.rancher-cron.schedule` 18 | label, it will start that container on schedule specified by the value 19 | of that label. 20 | 21 | You should not have _Auto Restart_ turned on and have scale of 1 for 22 | services you wish to run as a cron container. 23 | 24 | ## Running on Rancher 25 | 26 | Use following `docker-compose.yml` 27 | ```yml 28 | rancher-cron: 29 | labels: 30 | io.rancher.container.create_agent: 'true' 31 | io.rancher.container.agent.role: environment 32 | image: socialengine/rancher-cron:0.2.0 33 | ``` 34 | 35 | It is important to include both labels as Rancher will set `CATTLE_URL`, 36 | `CATTLE_ACCESS_KEY`, and `CATTLE_SECRET_KEY`. If you want a bit more control, 37 | feel free to set those manually. 38 | 39 | ### Debugging 40 | 41 | If something is not working as you expect, you can enable debug output by modifying 42 | the command to `rancher-cron -debug`. This is also helpful for submitting issues. 43 | 44 | ## CRON Expression Format 45 | 46 | A cron expression represents a set of times, using 6 space-separated fields. 47 | ``` 48 | Field name | Mandatory? | Allowed values | Allowed special characters 49 | ---------- | ---------- | -------------- | -------------------------- 50 | Seconds | Yes | 0-59 | * / , - 51 | Minutes | Yes | 0-59 | * / , - 52 | Hours | Yes | 0-23 | * / , - 53 | Day of month | Yes | 1-31 | * / , - ? 54 | Month | Yes | 1-12 or JAN-DEC | * / , - 55 | Day of week | Yes | 0-6 or SUN-SAT | * / , - ? 56 | ``` 57 | Note: Month and Day-of-week field values are not case sensitive. "SUN", "Sun", 58 | and "sun" are equally accepted. 59 | 60 | ### Special Characters 61 | 62 | #### Asterisk ( * ) 63 | 64 | The asterisk indicates that the cron expression will match for all values of the 65 | field; e.g., using an asterisk in the 5th field (month) would indicate every 66 | month. 67 | 68 | #### Slash ( / ) 69 | 70 | Slashes are used to describe increments of ranges. For example 3-59/15 in the 71 | 1st field (minutes) would indicate the 3rd minute of the hour and every 15 72 | minutes thereafter. The form "*\/..." is equivalent to the form "first-last/...", 73 | that is, an increment over the largest possible range of the field. The form 74 | "N/..." is accepted as meaning "N-MAX/...", that is, starting at N, use the 75 | increment until the end of that specific range. It does not wrap around. 76 | 77 | #### Comma ( , ) 78 | 79 | Commas are used to separate items of a list. For example, using "MON,WED,FRI" in 80 | the 5th field (day of week) would mean Mondays, Wednesdays and Fridays. 81 | 82 | #### Hyphen ( - ) 83 | 84 | Hyphens are used to define ranges. For example, 9-17 would indicate every 85 | hour between 9am and 5pm inclusive. 86 | 87 | #### Question mark ( ? ) 88 | 89 | Question mark may be used instead of '*' for leaving either day-of-month or 90 | day-of-week blank. 91 | 92 | #### Predefined schedules 93 | 94 | You may use one of several pre-defined schedules in place of a cron expression. 95 | ``` 96 | Entry | Description | Equivalent To 97 | ----- | ----------- | ------------- 98 | @yearly (or @annually) | Run once a year, midnight, Jan. 1st | 0 0 0 1 1 * 99 | @monthly | Run once a month, midnight, first of month | 0 0 0 1 * * 100 | @weekly | Run once a week, midnight on Sunday | 0 0 0 * * 0 101 | @daily (or @midnight) | Run once a day, midnight | 0 0 0 * * * 102 | @hourly | Run once an hour, beginning of hour | 0 0 * * * * 103 | ``` 104 | 105 | #### Intervals 106 | 107 | You may also schedule a job to execute at fixed intervals. This is supported by 108 | formatting the cron spec like this: 109 | ``` 110 | @every 111 | ``` 112 | where "duration" is a string accepted by [time.ParseDuration](http://golang.org/pkg/time/#ParseDuration). 113 | 114 | For example, "@every 1h30m10s" would indicate a schedule that activates every 115 | 1 hour, 30 minutes, 10 seconds. 116 | 117 | Note: The interval does not take the job runtime into account. For example, 118 | if a job takes 3 minutes to run, and it is scheduled to run every 5 minutes, 119 | it will have only 2 minutes of idle time between each run. 120 | 121 | ### Time zones 122 | 123 | All interpretation and scheduling is done in the machine's local time zone (as 124 | provided by the Go [time package](http://www.golang.org/pkg/time). 125 | 126 | **Be aware that jobs scheduled during daylight-savings leap-ahead transitions will 127 | not be run!** 128 | 129 | ## Building 130 | 131 | This project uses `govendor` for managing vendor dependancies, so first, please run `govendor sync` to 132 | install external packages. 133 | 134 | ### Makefile 135 | 136 | Run `make build` to create a new executable. 137 | 138 | Run `VERSION=dev make package` to create a new executable and package it into a docker image (which you can then 139 | test in rancher), it will be named `socialengine/rancher-cron:dev`. This assumes same docker daemon rancher has 140 | access to. -------------------------------------------------------------------------------- /cattle/cattle.go: -------------------------------------------------------------------------------- 1 | package cattle 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/rancher/go-rancher/client" 7 | ) 8 | 9 | // Client holds rancherClient and anything else that could be useful 10 | type Client struct { 11 | rancherClient *client.RancherClient 12 | } 13 | 14 | // NewClient grabs config necessary and sets an inited client or returns an error 15 | func NewClient(cattleURL string, cattleAccessKey string, cattleSecretKey string) (*Client, error) { 16 | apiClient, err := client.NewRancherClient(&client.ClientOpts{ 17 | Url: cattleURL, 18 | AccessKey: cattleAccessKey, 19 | SecretKey: cattleSecretKey, 20 | }) 21 | 22 | if err != nil { 23 | return nil, err 24 | } 25 | 26 | return &Client{ 27 | rancherClient: apiClient, 28 | }, nil 29 | } 30 | 31 | // GetServiceByUUID grabs a Service given a UUID 32 | func (c *Client) GetServiceByUUID(uuid string) (*client.Service, error) { 33 | opts := &client.ListOpts{ 34 | Filters: map[string]interface{}{ 35 | "uuid": uuid, 36 | }, 37 | } 38 | 39 | services, err := c.rancherClient.Service.List(opts) 40 | 41 | if err != nil { 42 | return nil, err 43 | } 44 | 45 | service := &services.Data[0] 46 | 47 | return service, err 48 | } 49 | 50 | // GetContainerByUUID grabs a Container given a UUID 51 | func (c *Client) GetContainerByUUID(uuid string) (*client.Container, error) { 52 | opts := &client.ListOpts{ 53 | Filters: map[string]interface{}{ 54 | "uuid": uuid, 55 | }, 56 | } 57 | 58 | containers, err := c.rancherClient.Container.List(opts) 59 | 60 | if err != nil { 61 | return nil, err 62 | } 63 | 64 | container := &containers.Data[0] 65 | 66 | return container, err 67 | } 68 | 69 | // StartContainerByID starts a container given ContainerId or fails in cases its already running 70 | func (c *Client) StartContainerByID(containerID string) (*client.Container, error) { 71 | container, err := c.rancherClient.Container.ById(containerID) 72 | 73 | if err != nil { 74 | return container, err 75 | } 76 | 77 | if container.State != "stopped" { 78 | return container, fmt.Errorf("container is not stopped. Currently in [%s] state", container.State) 79 | } 80 | 81 | _, err = c.rancherClient.Container.ActionStart(container) 82 | 83 | return container, err 84 | } 85 | 86 | // TestConnect ensures we can query the API 87 | func (c *Client) TestConnect() error { 88 | opts := &client.ListOpts{} 89 | _, err := c.rancherClient.Label.List(opts) 90 | return err 91 | } 92 | -------------------------------------------------------------------------------- /docker/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM alpine:3.2 2 | MAINTAINER SocialEngine 3 | RUN apk add --update ca-certificates 4 | 5 | ADD dist/rancher-cron /usr/bin/rancher-cron 6 | 7 | CMD ["rancher-cron"] 8 | -------------------------------------------------------------------------------- /docker/docker-compose.yml: -------------------------------------------------------------------------------- 1 | rancher-cron: 2 | labels: 3 | io.rancher.container.create_agent: 'true' 4 | io.rancher.container.agent.role: environment 5 | image: socialengine/rancher-cron:latest 6 | -------------------------------------------------------------------------------- /health-check.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/Sirupsen/logrus" 7 | "github.com/gorilla/mux" 8 | ) 9 | 10 | var ( 11 | router = mux.NewRouter() 12 | healthcheckPort = ":8080" 13 | ) 14 | 15 | func startHealthcheck() { 16 | router.HandleFunc("/", healthcheck).Methods("GET", "HEAD").Name("Healthcheck") 17 | logrus.Info("Healthcheck handler is listening on ", healthcheckPort) 18 | logrus.Fatal(http.ListenAndServe(healthcheckPort, router)) 19 | } 20 | 21 | func healthcheck(w http.ResponseWriter, req *http.Request) { 22 | _, err := m.MetadataClient.GetSelfStack() 23 | if err != nil { 24 | logrus.Error("Healthcheck failed: unable to reach metadata") 25 | http.Error(w, "Failed to reach metadata server", http.StatusInternalServerError) 26 | } else { 27 | err = c.TestConnect() 28 | if err != nil { 29 | logrus.Error("Healthcheck failed: unable to reach Cattle") 30 | http.Error(w, "Failed to connect to Cattle ", http.StatusInternalServerError) 31 | } else { 32 | w.Write([]byte("OK")) 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "os" 6 | 7 | "gopkg.in/robfig/cron.v2" 8 | 9 | "github.com/socialengine/rancher-cron/cattle" 10 | "github.com/socialengine/rancher-cron/metadata" 11 | "github.com/socialengine/rancher-cron/model" 12 | "github.com/socialengine/rancher-cron/scheduler" 13 | 14 | "github.com/Sirupsen/logrus" 15 | ) 16 | 17 | const ( 18 | poll = 1000 19 | // if metadata wasn't updated in 1 min, force update would be executed 20 | forceUpdateInterval = 10 21 | cronLabelName = "com.socialengine.rancher-cron.schedule" 22 | ) 23 | 24 | var ( 25 | debug = flag.Bool("debug", false, "Debug") 26 | 27 | c *cattle.Client 28 | m *metadata.Client 29 | s *scheduler.Scheduler 30 | cr *cron.Cron 31 | ) 32 | 33 | func setEnv() { 34 | flag.Parse() 35 | 36 | if *debug { 37 | logrus.SetLevel(logrus.DebugLevel) 38 | } 39 | 40 | cattleURL := os.Getenv("CATTLE_URL") 41 | if len(cattleURL) == 0 { 42 | logrus.Fatalf("CATTLE_URL is not set") 43 | } 44 | 45 | cattleAPIKey := os.Getenv("CATTLE_ACCESS_KEY") 46 | if len(cattleAPIKey) == 0 { 47 | logrus.Fatalf("CATTLE_ACCESS_KEY is not set") 48 | } 49 | 50 | cattleSecretKey := os.Getenv("CATTLE_SECRET_KEY") 51 | if len(cattleSecretKey) == 0 { 52 | logrus.Fatalf("CATTLE_SECRET_KEY is not set") 53 | } 54 | 55 | //configure cattle client 56 | cClient, err := cattle.NewClient(cattleURL, cattleAPIKey, cattleSecretKey) 57 | 58 | if err != nil { 59 | logrus.Fatalf("Failed to configure cattle client: %v", err) 60 | } 61 | c = cClient 62 | 63 | // configure metadata client 64 | mClient, err := metadata.NewClient(cronLabelName) 65 | if err != nil { 66 | logrus.Fatalf("Failed to configure rancher-metadata client: %v", err) 67 | } 68 | m = mClient 69 | 70 | s, err = scheduler.NewScheduler(cronLabelName, m, c) 71 | if err != nil { 72 | logrus.Fatalf("Failed to create the scheduler: %v", err) 73 | } 74 | } 75 | 76 | func main() { 77 | logrus.Infof("Starting up Rancher Cron service") 78 | 79 | setEnv() 80 | 81 | go startHealthcheck() 82 | 83 | cr = cron.New() 84 | cr.AddFunc("*/30 * * * * *", discoverCronContainers) 85 | discoverCronContainers() 86 | cr.Start() 87 | 88 | select {} 89 | } 90 | 91 | func discoverCronContainers() { 92 | sched, err := s.GetCronSchedules() 93 | schedules := *sched 94 | 95 | if err != nil { 96 | logrus.Error("Could not retrieve cron schedules from rancher metadata service") 97 | } 98 | 99 | if len(schedules) == 0 { 100 | logrus.Errorf("Could not find any active containers with label %s", cronLabelName) 101 | } 102 | 103 | // clean up old entities 104 | clearCron() 105 | 106 | logrus.Debugf("running discovery, found %d schedule containers", len(schedules)) 107 | 108 | for _, schedule := range schedules { 109 | // we already have a cron job, and it was not cleaned up 110 | if schedule.CronID > 0 { 111 | continue 112 | } 113 | scheduleFunc, err := getCronFunction(schedule) 114 | 115 | if err != nil { 116 | logrus.WithFields(logrus.Fields{ 117 | "container": schedule.ContainerUUID, 118 | }).Errorf("could not find container") 119 | } 120 | 121 | entryID, _ := cr.AddFunc(schedule.CronExpression, scheduleFunc) 122 | schedule.CronID = entryID 123 | } 124 | } 125 | 126 | func clearCron() { 127 | cleanedJobs := 0 128 | for key, schedule := range *s.Schedules { 129 | if !schedule.ToCleanup { 130 | continue 131 | } 132 | cr.Remove(schedule.CronID) 133 | delete(*s.Schedules, key) 134 | logrus.WithFields(logrus.Fields{ 135 | "schedule": schedule.CronExpression, 136 | "containerUUID": schedule.ContainerUUID, 137 | }).Info("deleted container from cron") 138 | 139 | cleanedJobs++ 140 | } 141 | if cleanedJobs > 0 { 142 | logrus.Debugf("removed %d cron entries, and their schedule", cleanedJobs) 143 | } 144 | } 145 | 146 | func getCronFunction(schedule *model.Schedule) (func(), error) { 147 | container, err := c.GetContainerByUUID(schedule.ContainerUUID) 148 | if err != nil { 149 | return nil, err 150 | } 151 | 152 | logrus.WithFields(logrus.Fields{ 153 | "schedule": schedule.CronExpression, 154 | "containerName": container.Name, 155 | "containerUUID": schedule.ContainerUUID, 156 | }).Info("adding container to cron") 157 | 158 | return func() { 159 | container, err := c.StartContainerByID(container.Id) 160 | if err != nil { 161 | logrus.WithFields(logrus.Fields{ 162 | "containerId": container.Id, 163 | "containerUuid": container.Uuid, 164 | }).Errorf("Failed to start container: %v", err) 165 | } else { 166 | logrus.WithFields(logrus.Fields{ 167 | "containerId": container.Id, 168 | "containerUuid": container.Uuid, 169 | }).Debugf("started container: %s", container.Name) 170 | } 171 | }, nil 172 | } 173 | -------------------------------------------------------------------------------- /metadata/metadata.go: -------------------------------------------------------------------------------- 1 | package metadata 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | 7 | "github.com/Sirupsen/logrus" 8 | "github.com/rancher/go-rancher-metadata/metadata" 9 | ) 10 | 11 | const ( 12 | metadataURL = "http://rancher-metadata/latest" 13 | ) 14 | 15 | // Client is a Struct that holds all metadata-specific data 16 | type Client struct { 17 | MetadataClient metadata.Client 18 | EnvironmentName string 19 | CronLabelName string 20 | } 21 | 22 | // NewClient creates a new metadata client 23 | func NewClient(cronLabelName string) (*Client, error) { 24 | m, err := metadata.NewClientAndWait(metadataURL) 25 | if err != nil { 26 | logrus.Fatalf("Failed to configure rancher-metadata: %v", err) 27 | } 28 | 29 | envName, err := getEnvironmentName(m) 30 | if err != nil { 31 | logrus.Fatalf("Error reading stack info: %v", err) 32 | } 33 | 34 | return &Client{ 35 | MetadataClient: *m, 36 | EnvironmentName: envName, 37 | CronLabelName: cronLabelName, 38 | }, nil 39 | } 40 | 41 | // GetVersion grabs the version of metadata client we're using 42 | func (m *Client) GetVersion() (string, error) { 43 | return m.MetadataClient.GetVersion() 44 | } 45 | 46 | // GetServices returns the services from the metadata client 47 | func (m *Client) GetServices() ([]metadata.Service, error) { 48 | return m.MetadataClient.GetServices() 49 | } 50 | 51 | // GetContainersFromService returns an array of UUIDs for the containers within a service 52 | func (m *Client) GetContainersFromService(service metadata.Service) ([]string, error) { 53 | containers := service.Containers 54 | var uuids []string 55 | 56 | for _, container := range containers { 57 | if len(container.ServiceName) == 0 { 58 | continue 59 | } 60 | 61 | if len(service.Name) != 0 { 62 | if service.Name != container.ServiceName { 63 | continue 64 | } 65 | if service.StackName != container.StackName { 66 | continue 67 | } 68 | } 69 | 70 | uuids = append(uuids, container.UUID) 71 | } 72 | 73 | if len(uuids) > 0 { 74 | return uuids, nil 75 | } 76 | 77 | return uuids, fmt.Errorf("could not find container UUID with %s", m.CronLabelName) 78 | } 79 | 80 | func getEnvironmentName(m *metadata.Client) (string, error) { 81 | timeout := 30 * time.Second 82 | var err error 83 | var stack metadata.Stack 84 | for i := 1 * time.Second; i < timeout; i *= time.Duration(2) { 85 | stack, err = m.GetSelfStack() 86 | if err != nil { 87 | logrus.Errorf("Error reading stack info: %v...will retry", err) 88 | time.Sleep(i) 89 | } else { 90 | return stack.EnvironmentName, nil 91 | } 92 | } 93 | return "", fmt.Errorf("Error reading stack info: %v", err) 94 | } 95 | -------------------------------------------------------------------------------- /model/schedule.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import "gopkg.in/robfig/cron.v2" 4 | 5 | // Schedule holds data related to an individual schedule such as cronId, schedule, ContainerUUID, etc 6 | type Schedule struct { 7 | ToCleanup bool 8 | CronExpression string 9 | ContainerUUID string 10 | CronID cron.EntryID 11 | ServiceUUID string 12 | } 13 | 14 | // Schedules is a simple collection of Schedule 15 | type Schedules map[string]*Schedule 16 | 17 | // NewSchedule is a constructor to help set default values 18 | func NewSchedule() *Schedule { 19 | return &Schedule{ 20 | ToCleanup: false, 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /scheduler/scheduler.go: -------------------------------------------------------------------------------- 1 | package scheduler 2 | 3 | import ( 4 | "github.com/Sirupsen/logrus" 5 | 6 | "github.com/socialengine/rancher-cron/cattle" 7 | "github.com/socialengine/rancher-cron/metadata" 8 | "github.com/socialengine/rancher-cron/model" 9 | ) 10 | 11 | // Scheduler is a Struct and contains all the schedule info 12 | type Scheduler struct { 13 | CattleClient *cattle.Client 14 | MetadataClient *metadata.Client 15 | CronLabelName string 16 | Schedules *model.Schedules 17 | } 18 | 19 | // NewScheduler creates a new Scheduler 20 | func NewScheduler(cronLabelName string, metadataClient *metadata.Client, cattleClient *cattle.Client) (*Scheduler, error) { 21 | schedules := make(model.Schedules) 22 | 23 | return &Scheduler{ 24 | CattleClient: cattleClient, 25 | MetadataClient: metadataClient, 26 | CronLabelName: cronLabelName, 27 | Schedules: &schedules, 28 | }, nil 29 | } 30 | 31 | // GetCronSchedules returns a map of schedules with ContainerUUID as a key 32 | func (m *Scheduler) GetCronSchedules() (*model.Schedules, error) { 33 | schedules := *m.Schedules 34 | services, err := m.MetadataClient.GetServices() 35 | 36 | if err != nil { 37 | logrus.Infof("Error reading services %v", err) 38 | return &schedules, err 39 | } 40 | 41 | markScheduleForCleanup(m.Schedules) 42 | 43 | for _, service := range services { 44 | cronExpression, ok := service.Labels[m.CronLabelName] 45 | if !ok { 46 | continue 47 | } 48 | 49 | cattleService, err := m.CattleClient.GetServiceByUUID(service.UUID) 50 | if err == nil && cattleService.State == "inactive" { 51 | logrus.Debugf("ignoring inactive service uuid %s", service.UUID) 52 | continue 53 | } 54 | 55 | containerUUIDs, err := m.MetadataClient.GetContainersFromService(service) 56 | if err != nil { 57 | continue 58 | } 59 | 60 | for _, containerUUID := range containerUUIDs { 61 | existingSchedule, ok := schedules[containerUUID] 62 | // we already have schedule for this container 63 | if ok { 64 | // do not cleanup 65 | existingSchedule.ToCleanup = false 66 | 67 | logrus.WithFields(logrus.Fields{ 68 | "uuid": containerUUID, 69 | "cronExpression": cronExpression, 70 | }).Debugf("already have container") 71 | 72 | continue 73 | } 74 | //label exists, configure schedule 75 | schedule := model.NewSchedule() 76 | 77 | schedule.CronExpression = cronExpression 78 | schedule.ContainerUUID = containerUUID 79 | schedule.ServiceUUID = service.UUID 80 | schedules[containerUUID] = schedule 81 | } 82 | } 83 | 84 | return &schedules, nil 85 | } 86 | 87 | func markScheduleForCleanup(schedules *model.Schedules) { 88 | for _, schedule := range *schedules { 89 | schedule.ToCleanup = true 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /vendor/vendor.json: -------------------------------------------------------------------------------- 1 | { 2 | "comment": "", 3 | "ignore": "test", 4 | "package": [ 5 | { 6 | "checksumSHA1": "htjvdG/znrHmFYRQBqA2vHrJsF4=", 7 | "path": "github.com/Sirupsen/logrus", 8 | "revision": "cd7d1bbe41066b6c1f19780f895901052150a575", 9 | "revisionTime": "2016-04-25T09:32:37Z" 10 | }, 11 | { 12 | "checksumSHA1": "iIUYZyoanCQQTUaWsu8b+iOSPt4=", 13 | "path": "github.com/gorilla/context", 14 | "revision": "a8d44e7d8e4d532b6a27a02dd82abb31cc1b01bd", 15 | "revisionTime": "2016-04-22T13:42:37Z" 16 | }, 17 | { 18 | "checksumSHA1": "/WoZGh9L9hFOcrU9d7rAeFPDy1U=", 19 | "path": "github.com/gorilla/mux", 20 | "revision": "9c19ed558d5df4da88e2ade9c8940d742aef0e7e", 21 | "revisionTime": "2016-05-02T17:56:24Z" 22 | }, 23 | { 24 | "checksumSHA1": "cQcdWtP2zDtOdlYEqU1pV4CyK1A=", 25 | "path": "github.com/rancher/go-rancher-metadata/metadata", 26 | "revision": "beb180ef575f061e28e9dca169d2c01a7e12d780", 27 | "revisionTime": "2016-04-28T16:33:32Z" 28 | }, 29 | { 30 | "checksumSHA1": "vIgimabmRTB7N7oCZmyvoCeXGxk=", 31 | "path": "github.com/rancher/go-rancher/client", 32 | "revision": "80f15a052d778601e047286669edda02e83383cc", 33 | "revisionTime": "2016-04-11T18:40:51Z" 34 | }, 35 | { 36 | "checksumSHA1": "CzLpoHWYgSP+mT3bcW/jt8epp5I=", 37 | "path": "gopkg.in/robfig/cron.v2", 38 | "revision": "be2e0b0deed5a68ffee390b4583a13aff8321535", 39 | "revisionTime": "2015-01-07T22:02:07Z" 40 | } 41 | ], 42 | "rootPath": "github.com/socialengine/rancher-cron" 43 | } 44 | --------------------------------------------------------------------------------