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 5.0KB

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