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.

dotege.go 4.9KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180
  1. package main
  2. import (
  3. "fmt"
  4. "github.com/csmith/dotege/model"
  5. "github.com/docker/docker/client"
  6. "github.com/xenolf/lego/certcrypto"
  7. "github.com/xenolf/lego/lego"
  8. "github.com/xenolf/lego/platform/config/env"
  9. "go.uber.org/zap"
  10. "go.uber.org/zap/zapcore"
  11. "os"
  12. "os/signal"
  13. "strings"
  14. "syscall"
  15. "time"
  16. )
  17. func monitorSignals() <-chan bool {
  18. signals := make(chan os.Signal, 1)
  19. done := make(chan bool, 1)
  20. signal.Notify(signals, syscall.SIGINT, syscall.SIGTERM)
  21. go func() {
  22. sig := <-signals
  23. fmt.Printf("Received %s signal\n", sig)
  24. done <- true
  25. }()
  26. return done
  27. }
  28. func createLogger() *zap.SugaredLogger {
  29. zapConfig := zap.NewDevelopmentConfig()
  30. zapConfig.DisableCaller = true
  31. zapConfig.DisableStacktrace = true
  32. zapConfig.EncoderConfig.EncodeLevel = zapcore.CapitalColorLevelEncoder
  33. zapConfig.OutputPaths = []string{"stdout"}
  34. zapConfig.ErrorOutputPaths = []string{"stdout"}
  35. logger, _ := zapConfig.Build()
  36. return logger.Sugar()
  37. }
  38. func createConfig() *model.Config {
  39. return &model.Config{
  40. Templates: []model.TemplateConfig{
  41. {
  42. Source: "./templates/haproxy.cfg.tpl",
  43. Destination: "haproxy.cfg",
  44. },
  45. },
  46. Labels: model.LabelConfig{
  47. Hostnames: "com.chameth.vhost",
  48. RequireAuth: "com.chameth.auth",
  49. },
  50. DefaultCertActions: model.COMBINE | model.FLATTEN,
  51. DefaultCertDestination: "/data/certs/",
  52. }
  53. }
  54. func createTemplateGenerator(logger *zap.SugaredLogger, config *model.Config) *TemplateGenerator {
  55. templateGenerator := NewTemplateGenerator(logger)
  56. for _, template := range config.Templates {
  57. templateGenerator.AddTemplate(template)
  58. }
  59. return templateGenerator
  60. }
  61. func createCertificateManager(logger *zap.SugaredLogger) *CertificateManager {
  62. certificateManager := NewCertificateManager(logger, lego.LEDirectoryStaging, certcrypto.EC256, env.GetOrDefaultString("DOTEGE_DNS_PROVIDER", ""), "/config/certs.json")
  63. err := certificateManager.Init(env.GetOrDefaultString("DOTEGE_ACME_EMAIL", ""))
  64. if err != nil {
  65. panic(err)
  66. }
  67. return certificateManager
  68. }
  69. func main() {
  70. logger := createLogger()
  71. logger.Info("Dotege is starting")
  72. doneChan := monitorSignals()
  73. config := createConfig()
  74. dockerStopChan := make(chan struct{})
  75. dockerClient, err := client.NewEnvClient()
  76. if err != nil {
  77. panic(err)
  78. }
  79. templateGenerator := createTemplateGenerator(logger, config)
  80. certificateManager := createCertificateManager(logger)
  81. jitterTimer := time.NewTimer(time.Minute)
  82. redeployTimer := time.NewTicker(time.Hour * 24)
  83. containers := make(map[string]model.Container)
  84. go func() {
  85. err := monitorContainers(dockerClient, dockerStopChan, func(container model.Container) {
  86. containers[container.Name] = container
  87. jitterTimer.Reset(100 * time.Millisecond)
  88. err, _ = certificateManager.GetCertificate(getHostnamesForContainer(container, *config))
  89. }, func(name string) {
  90. delete(containers, name)
  91. jitterTimer.Reset(100 * time.Millisecond)
  92. })
  93. if err != nil {
  94. logger.Fatal("Error monitoring containers: ", err.Error())
  95. }
  96. }()
  97. go func() {
  98. for {
  99. select {
  100. case <-jitterTimer.C:
  101. hostnames := getHostnames(containers, *config)
  102. templateGenerator.Generate(Context{
  103. Containers: containers,
  104. Hostnames: hostnames,
  105. })
  106. case <-redeployTimer.C:
  107. logger.Info("Performing periodic certificate refresh")
  108. for _, container := range containers {
  109. err, _ = certificateManager.GetCertificate(getHostnamesForContainer(container, *config))
  110. }
  111. }
  112. }
  113. }()
  114. <-doneChan
  115. dockerStopChan <- struct{}{}
  116. err = dockerClient.Close()
  117. if err != nil {
  118. panic(err)
  119. }
  120. }
  121. func getHostnamesForContainer(container model.Container, config model.Config) []string {
  122. if label, ok := container.Labels[config.Labels.Hostnames]; ok {
  123. return strings.Split(strings.Replace(label, ",", " ", -1), " ")
  124. } else {
  125. return []string{}
  126. }
  127. }
  128. func getHostnames(containers map[string]model.Container, config model.Config) (hostnames map[string]*model.Hostname) {
  129. hostnames = make(map[string]*model.Hostname)
  130. for _, container := range containers {
  131. if label, ok := container.Labels[config.Labels.Hostnames]; ok {
  132. names := strings.Split(strings.Replace(label, ",", " ", -1), " ")
  133. if hostname, ok := hostnames[names[0]]; ok {
  134. hostname.Containers = append(hostname.Containers, container)
  135. } else {
  136. hostnames[names[0]] = &model.Hostname{
  137. Name: names[0],
  138. Alternatives: make(map[string]bool),
  139. Containers: []model.Container{container},
  140. CertActions: config.DefaultCertActions,
  141. CertDestination: config.DefaultCertDestination,
  142. }
  143. }
  144. addAlternatives(hostnames[names[0]], names[1:])
  145. if label, ok = container.Labels[config.Labels.RequireAuth]; ok {
  146. hostnames[names[0]].RequiresAuth = true
  147. hostnames[names[0]].AuthGroup = label
  148. }
  149. }
  150. }
  151. return
  152. }
  153. func addAlternatives(hostname *model.Hostname, alternatives []string) {
  154. for _, alternative := range alternatives {
  155. hostname.Alternatives[alternative] = true
  156. }
  157. }