mirror of
https://github.com/zix99/traefik-lazyload.git
synced 2025-12-21 13:23:04 +01:00
Handle signals, code cleanup, easier labels
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
README.md
|
||||
docker-compose.yml
|
||||
*compose.yml
|
||||
Dockerfile
|
||||
.gitignore
|
||||
traefik-lazyload
|
||||
4
.gitignore
vendored
4
.gitignore
vendored
@@ -1 +1,5 @@
|
||||
# Binary
|
||||
traefik-lazyload
|
||||
|
||||
# Various testing/compose files
|
||||
*compose.yml
|
||||
@@ -14,10 +14,8 @@ var httpAssets embed.FS
|
||||
const httpAssetPrefix = "/__llassets/"
|
||||
|
||||
type SplashModel struct {
|
||||
*service.ContainerState
|
||||
Name string
|
||||
CID string
|
||||
WaitForCode int
|
||||
WaitForPath string
|
||||
}
|
||||
|
||||
var splashTemplate = template.Must(template.ParseFS(httpAssets, path.Join("assets", config.Model.Splash)))
|
||||
|
||||
@@ -23,13 +23,13 @@
|
||||
</div>
|
||||
<div class="message">
|
||||
<h2>Starting {{.Name}}</h2>
|
||||
<h3>{{.CID}}</h3>
|
||||
<h3>{{.Name}}</h3>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
async function testForOk(url) {
|
||||
const response = await fetch(url, {
|
||||
method: "HEAD",
|
||||
method: "{{.WaitForMethod}}",
|
||||
});
|
||||
console.log(`Got ${response.status}`);
|
||||
return response.status === {{.WaitForCode}};
|
||||
|
||||
@@ -1,57 +0,0 @@
|
||||
version: '3.5'
|
||||
|
||||
services:
|
||||
# reverse-proxy:
|
||||
# image: traefik:v2.4
|
||||
# command:
|
||||
# - --api.insecure
|
||||
# - --providers.docker
|
||||
# - --providers.docker.defaultRule=Host(`{{.Name}}.d0.lan`)
|
||||
# - --entryPoints.web.address=:85
|
||||
# - --entryPoints.web.forwardedHeaders.insecure
|
||||
# - --providers.docker.exposedByDefault=false
|
||||
# - --providers.docker.constraints=Label(`my.zone`,`zone1`)
|
||||
# restart: always
|
||||
# ports:
|
||||
# - "85:85" # The HTTP port
|
||||
# - "8086:8080" # The Web UI (enabled by --api)
|
||||
# volumes:
|
||||
# - /var/run/docker.sock:/var/run/docker.sock # So that Traefik can listen to the Docker events
|
||||
|
||||
monitor:
|
||||
build: .
|
||||
labels:
|
||||
- traefik.enable=true
|
||||
- "traefik.http.routers.lazyload.priority=-100"
|
||||
- "traefik.http.routers.lazyload.rule=Host(`whoami.d.lan`, `whoami2.d.lan`, `pdf.d.lan`, `noexist.d.lan`, `lazyloader.d.lan`)"
|
||||
environment:
|
||||
TLL_STOPATBOOT: true
|
||||
TLL_STATUSHOST: lazyloader.d.lan
|
||||
networks:
|
||||
- traefik-bridge
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
|
||||
whoami:
|
||||
image: containous/whoami
|
||||
networks:
|
||||
- traefik-bridge
|
||||
labels:
|
||||
- traefik.enable=true
|
||||
- "traefik.http.routers.lazywhoami.rule=Host(`whoami.d.lan`)"
|
||||
- lazyloader=true
|
||||
|
||||
whoami2:
|
||||
image: containous/whoami
|
||||
networks:
|
||||
- traefik-bridge
|
||||
labels:
|
||||
- traefik.enable=true
|
||||
- "traefik.http.routers.lazywhoami2.rule=Host(`whoami2.d.lan`)"
|
||||
- lazyloader=true
|
||||
- lazyloader.stopdelay=1m
|
||||
|
||||
networks:
|
||||
traefik-bridge:
|
||||
external: true
|
||||
name: traefik-bridge
|
||||
30
main.go
30
main.go
@@ -1,11 +1,14 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/fs"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
"runtime"
|
||||
"traefik-lazyload/pkg/config"
|
||||
"traefik-lazyload/pkg/service"
|
||||
@@ -44,14 +47,31 @@ func main() {
|
||||
|
||||
// Set up http server
|
||||
subFs, _ := fs.Sub(httpAssets, "assets")
|
||||
http.Handle(httpAssetPrefix, http.StripPrefix(httpAssetPrefix, http.FileServer(http.FS(subFs))))
|
||||
http.HandleFunc("/", ContainerHandler)
|
||||
router := http.NewServeMux()
|
||||
router.Handle(httpAssetPrefix, http.StripPrefix(httpAssetPrefix, http.FileServer(http.FS(subFs))))
|
||||
router.HandleFunc("/", ContainerHandler)
|
||||
|
||||
srv := &http.Server{
|
||||
Addr: config.Model.Listen,
|
||||
Handler: router,
|
||||
}
|
||||
|
||||
sigChan := make(chan os.Signal, 1)
|
||||
signal.Notify(sigChan, os.Interrupt)
|
||||
|
||||
go func() {
|
||||
<-sigChan
|
||||
logrus.Info("Shutting down...")
|
||||
srv.Shutdown(context.Background())
|
||||
}()
|
||||
|
||||
logrus.Infof("Listening on %s...", config.Model.Listen)
|
||||
if config.Model.StatusHost != "" {
|
||||
logrus.Infof("Status host set to %s", config.Model.StatusHost)
|
||||
}
|
||||
http.ListenAndServe(config.Model.Listen, nil)
|
||||
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func ContainerHandler(w http.ResponseWriter, r *http.Request) {
|
||||
@@ -78,9 +98,7 @@ func ContainerHandler(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusAccepted)
|
||||
renderErr := splashTemplate.Execute(w, SplashModel{
|
||||
Name: host,
|
||||
CID: sOpts.ContainerName,
|
||||
WaitForCode: sOpts.WaitForCode,
|
||||
WaitForPath: sOpts.WaitForPath,
|
||||
ContainerState: sOpts,
|
||||
})
|
||||
if renderErr != nil {
|
||||
logrus.Error(renderErr)
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
"traefik-lazyload/pkg/config"
|
||||
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type containerSettings struct {
|
||||
stopDelay time.Duration
|
||||
waitForCode int
|
||||
waitForPath string
|
||||
waitForMethod string
|
||||
needs []string
|
||||
}
|
||||
|
||||
type ContainerState struct {
|
||||
@@ -34,26 +34,11 @@ func newStateFromContainer(ct *types.Container) *ContainerState {
|
||||
}
|
||||
|
||||
func extractContainerLabels(ct *types.Container) (target containerSettings) {
|
||||
{ // Parse stop delay
|
||||
stopDelay, _ := labelOrDefault(ct, "stopdelay", config.Model.StopDelay.String())
|
||||
if dur, stopErr := time.ParseDuration(stopDelay); stopErr != nil {
|
||||
target.stopDelay = config.Model.StopDelay
|
||||
logrus.Warnf("Unable to parse stopdelay for %s of %s, defaulting to %s", containerShort(ct), stopDelay, target.stopDelay.String())
|
||||
} else {
|
||||
target.stopDelay = dur
|
||||
}
|
||||
}
|
||||
{ // WaitForCode
|
||||
codeStr, _ := labelOrDefault(ct, "waitforcode", "200")
|
||||
if code, err := strconv.Atoi(codeStr); err != nil {
|
||||
target.waitForCode = 200
|
||||
logrus.Warnf("Unable to parse WaitForCode of %s, defaulting to %d", containerShort(ct), target.waitForCode)
|
||||
} else {
|
||||
target.waitForCode = code
|
||||
}
|
||||
}
|
||||
|
||||
target.stopDelay, _ = labelOrDefaultDuration(ct, "stopdelay", config.Model.StopDelay)
|
||||
target.waitForCode, _ = labelOrDefaultInt(ct, "waitforcode", 200)
|
||||
target.waitForPath, _ = labelOrDefault(ct, "waitforpath", "/")
|
||||
target.waitForMethod, _ = labelOrDefault(ct, "waitformethod", "HEAD")
|
||||
target.needs, _ = labelOrDefaultArr(ct, "needs")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -85,6 +70,19 @@ func (s *containerSettings) StopDelay() string {
|
||||
return s.stopDelay.String()
|
||||
}
|
||||
|
||||
func (s *ContainerState) WaitForCode() int {
|
||||
return s.waitForCode
|
||||
}
|
||||
|
||||
func (s *ContainerState) WaitForPath() string {
|
||||
return s.waitForPath
|
||||
}
|
||||
|
||||
func (s *ContainerState) WaitForMethod() string {
|
||||
return s.waitForMethod
|
||||
}
|
||||
|
||||
// Wrapper for container results that opaques and adds some methods to that data
|
||||
type ContainerWrapper struct {
|
||||
types.Container
|
||||
}
|
||||
|
||||
53
pkg/service/labels.go
Normal file
53
pkg/service/labels.go
Normal file
@@ -0,0 +1,53 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
"traefik-lazyload/pkg/config"
|
||||
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func labelOrDefault(ct *types.Container, sublabel, dflt string) (string, bool) {
|
||||
if val, ok := ct.Labels[config.SubLabel(sublabel)]; ok {
|
||||
return val, true
|
||||
}
|
||||
return dflt, false
|
||||
}
|
||||
|
||||
func labelOrDefaultArr(ct *types.Container, sublabel string) ([]string, bool) {
|
||||
if val, ok := ct.Labels[config.SubLabel(sublabel)]; ok {
|
||||
return strings.Split(val, ","), true
|
||||
}
|
||||
return []string{}, false
|
||||
}
|
||||
|
||||
func labelOrDefaultInt(ct *types.Container, sublabel string, dflt int) (int, bool) {
|
||||
s, ok := labelOrDefault(ct, sublabel, "")
|
||||
if !ok {
|
||||
return dflt, false
|
||||
}
|
||||
|
||||
if val, err := strconv.Atoi(s); err != nil {
|
||||
logrus.Warnf("Unable to parse %s on %s: %v. Using default of %d", sublabel, containerShort(ct), err, dflt)
|
||||
return dflt, false
|
||||
} else {
|
||||
return val, true
|
||||
}
|
||||
}
|
||||
|
||||
func labelOrDefaultDuration(ct *types.Container, sublabel string, dflt time.Duration) (time.Duration, bool) {
|
||||
s, ok := labelOrDefault(ct, sublabel, "")
|
||||
if !ok {
|
||||
return dflt, false
|
||||
}
|
||||
|
||||
if val, err := time.ParseDuration(s); err != nil {
|
||||
logrus.Warnf("Unable to parse %s on %s: %v. Using default of %s", sublabel, containerShort(ct), err, dflt.String())
|
||||
return dflt, false
|
||||
} else {
|
||||
return val, true
|
||||
}
|
||||
}
|
||||
@@ -55,13 +55,7 @@ func (s *Core) Close() error {
|
||||
return s.client.Close()
|
||||
}
|
||||
|
||||
type StartResult struct {
|
||||
WaitForCode int
|
||||
WaitForPath string
|
||||
ContainerName string
|
||||
}
|
||||
|
||||
func (s *Core) StartHost(hostname string) (*StartResult, error) {
|
||||
func (s *Core) StartHost(hostname string) (*ContainerState, error) {
|
||||
s.mux.Lock()
|
||||
defer s.mux.Unlock()
|
||||
|
||||
@@ -75,11 +69,7 @@ func (s *Core) StartHost(hostname string) (*StartResult, error) {
|
||||
|
||||
if ets, exists := s.active[ct.ID]; exists {
|
||||
logrus.Debugf("Asked to start host, but we already think it's started: %s", ets.name)
|
||||
return &StartResult{
|
||||
WaitForCode: ets.waitForCode,
|
||||
WaitForPath: ets.waitForPath,
|
||||
ContainerName: containerShort(ct),
|
||||
}, nil
|
||||
return ets, nil
|
||||
}
|
||||
|
||||
go s.startContainer(ctx, ct)
|
||||
@@ -88,10 +78,7 @@ func (s *Core) StartHost(hostname string) (*StartResult, error) {
|
||||
ets := newStateFromContainer(ct)
|
||||
s.active[ct.ID] = ets
|
||||
|
||||
return &StartResult{
|
||||
WaitForCode: ets.waitForCode,
|
||||
WaitForPath: ets.waitForPath,
|
||||
}, nil
|
||||
return ets, nil
|
||||
}
|
||||
|
||||
func (s *Core) StopAll() {
|
||||
@@ -229,6 +216,15 @@ func (s *Core) checkContainerForInactivity(ctx context.Context, cid string, ct *
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func (s *Core) findContainersByDepProvider(ctx context.Context, name string) ([]types.Container, error) {
|
||||
filters := filters.NewArgs()
|
||||
filters.Add("label", config.SubLabel("providers")+"="+name)
|
||||
return s.client.ContainerList(ctx, types.ContainerListOptions{
|
||||
Filters: filters,
|
||||
All: true,
|
||||
})
|
||||
}
|
||||
|
||||
func (s *Core) findContainerByHostname(ctx context.Context, hostname string) (*types.Container, error) {
|
||||
containers, err := s.findAllLazyloadContainers(ctx, true)
|
||||
if err != nil {
|
||||
|
||||
@@ -3,7 +3,6 @@ package service
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"traefik-lazyload/pkg/config"
|
||||
|
||||
"github.com/docker/docker/api/types"
|
||||
)
|
||||
@@ -16,13 +15,6 @@ func sumNetworkBytes(networks map[string]types.NetworkStats) (recv int64, send i
|
||||
return
|
||||
}
|
||||
|
||||
func labelOrDefault(ct *types.Container, sublabel, dflt string) (string, bool) {
|
||||
if val, ok := ct.Labels[config.SubLabel(sublabel)]; ok {
|
||||
return val, true
|
||||
}
|
||||
return dflt, false
|
||||
}
|
||||
|
||||
func shortId(id string) string {
|
||||
const SLEN = 8
|
||||
if len(id) <= SLEN {
|
||||
@@ -42,10 +34,7 @@ func containerShort(c *types.Container) string {
|
||||
}
|
||||
|
||||
func trimRootPath(s string) string {
|
||||
if strings.HasPrefix(s, "/") {
|
||||
return s[1:]
|
||||
}
|
||||
return s
|
||||
return strings.TrimPrefix(s, "/")
|
||||
}
|
||||
|
||||
func isRunning(c *types.Container) bool {
|
||||
|
||||
Reference in New Issue
Block a user