Docker template generator
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

monitor.go 4.0KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134
  1. package docker
  2. import (
  3. "context"
  4. "github.com/csmith/dotege/model"
  5. "github.com/docker/docker/api/types"
  6. "github.com/docker/docker/api/types/filters"
  7. "github.com/docker/docker/client"
  8. "go.uber.org/zap"
  9. "time"
  10. )
  11. // ContainerMonitor watches for newly created and destroyed containers, and emits their information on a channel.
  12. // Destroyed containers are given a grace period before being announced, to allow for restarts etc to be less
  13. // disruptive.
  14. type ContainerMonitor struct {
  15. logger *zap.SugaredLogger
  16. newContainers chan<- model.Container
  17. goneContainerNames chan<- string
  18. client *client.Client
  19. expiryTimes map[string]time.Time
  20. deletionTime time.Duration
  21. nextExpiry time.Time
  22. expiryTimer *time.Timer
  23. }
  24. // NewContainerMonitor creates a new container monitor
  25. func NewContainerMonitor(logger *zap.SugaredLogger, client *client.Client, newContainerChannel chan<- model.Container, goneContainerChannel chan<- string) *ContainerMonitor {
  26. timer := time.NewTimer(time.Hour)
  27. timer.Stop()
  28. return &ContainerMonitor{
  29. logger: logger,
  30. newContainers: newContainerChannel,
  31. goneContainerNames: goneContainerChannel,
  32. client: client,
  33. expiryTimes: make(map[string]time.Time),
  34. deletionTime: 10 * time.Second,
  35. expiryTimer: timer,
  36. nextExpiry: time.Now(),
  37. }
  38. }
  39. // Monitor starts monitoring for changes, and publishes info on any pre-existing containers. It blocks indefinitely,
  40. // and should be run from a goroutine.
  41. func (c *ContainerMonitor) Monitor() {
  42. args := filters.NewArgs()
  43. args.Add("type", "container")
  44. args.Add("event", "create")
  45. args.Add("event", "destroy")
  46. eventsChan, errChan := c.client.Events(context.Background(), types.EventsOptions{Filters: args})
  47. c.publishExistingContainers()
  48. for {
  49. select {
  50. case event := <-eventsChan:
  51. if event.Action == "create" {
  52. c.publishNewContainer(event.Actor.ID)
  53. } else {
  54. c.scheduleExpiry(event.Actor.Attributes["name"])
  55. }
  56. case <-c.expiryTimer.C:
  57. c.publishExpiredContainers()
  58. case err := <-errChan:
  59. c.logger.Fatal("Error received from docker events API", err)
  60. }
  61. }
  62. }
  63. func (c *ContainerMonitor) publishExistingContainers() {
  64. containers, err := c.client.ContainerList(context.Background(), types.ContainerListOptions{})
  65. if err != nil {
  66. c.logger.Fatal("Error received trying to list containers", err)
  67. }
  68. for _, container := range containers {
  69. c.logger.Infof("Found existing container %s", container.Names[0][1:])
  70. c.newContainers <- model.Container{
  71. Id: container.ID,
  72. Name: container.Names[0][1:],
  73. Labels: container.Labels,
  74. }
  75. }
  76. }
  77. func (c *ContainerMonitor) publishNewContainer(id string) {
  78. container, err := c.client.ContainerInspect(context.Background(), id)
  79. if err != nil {
  80. c.logger.Fatal("Error received trying to inspect container", err)
  81. }
  82. c.newContainers <- model.Container{
  83. Id: container.ID,
  84. Name: container.Name[1:],
  85. Labels: container.Config.Labels,
  86. }
  87. c.logger.Info("Found new container %s", container.Name[1:])
  88. delete(c.expiryTimes, container.Name[1:])
  89. }
  90. func (c *ContainerMonitor) scheduleExpiry(name string) {
  91. now := time.Now()
  92. expiryTime := now.Add(c.deletionTime)
  93. c.expiryTimes[name] = expiryTime
  94. c.logger.Infof("Scheduling expiry timer for %s", name)
  95. if c.nextExpiry.Before(now) || c.nextExpiry.After(expiryTime) {
  96. c.logger.Debugf("Starting expiry timer with default duration")
  97. c.expiryTimer.Reset(c.deletionTime + 1*time.Second)
  98. c.nextExpiry = expiryTime
  99. }
  100. }
  101. func (c *ContainerMonitor) publishExpiredContainers() {
  102. now := time.Now()
  103. next := 0 * time.Second
  104. for name, expiryTime := range c.expiryTimes {
  105. if expiryTime.Before(now) {
  106. c.logger.Infof("Expiring %s", name)
  107. delete(c.expiryTimes, name)
  108. c.goneContainerNames <- name
  109. } else if next == 0 || expiryTime.Sub(now) < next {
  110. next = expiryTime.Sub(now)
  111. }
  112. }
  113. if next > 0 {
  114. c.logger.Debugf("Starting expiry timer with duration %s\n", next)
  115. c.expiryTimer.Reset(next + 1*time.Second)
  116. c.nextExpiry = now.Add(next)
  117. }
  118. }