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
|
README.md
|
||||||
docker-compose.yml
|
*compose.yml
|
||||||
Dockerfile
|
Dockerfile
|
||||||
.gitignore
|
.gitignore
|
||||||
traefik-lazyload
|
traefik-lazyload
|
||||||
4
.gitignore
vendored
4
.gitignore
vendored
@@ -1 +1,5 @@
|
|||||||
|
# Binary
|
||||||
traefik-lazyload
|
traefik-lazyload
|
||||||
|
|
||||||
|
# Various testing/compose files
|
||||||
|
*compose.yml
|
||||||
@@ -14,10 +14,8 @@ var httpAssets embed.FS
|
|||||||
const httpAssetPrefix = "/__llassets/"
|
const httpAssetPrefix = "/__llassets/"
|
||||||
|
|
||||||
type SplashModel struct {
|
type SplashModel struct {
|
||||||
Name string
|
*service.ContainerState
|
||||||
CID string
|
Name string
|
||||||
WaitForCode int
|
|
||||||
WaitForPath string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var splashTemplate = template.Must(template.ParseFS(httpAssets, path.Join("assets", config.Model.Splash)))
|
var splashTemplate = template.Must(template.ParseFS(httpAssets, path.Join("assets", config.Model.Splash)))
|
||||||
|
|||||||
@@ -23,13 +23,13 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="message">
|
<div class="message">
|
||||||
<h2>Starting {{.Name}}</h2>
|
<h2>Starting {{.Name}}</h2>
|
||||||
<h3>{{.CID}}</h3>
|
<h3>{{.Name}}</h3>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<script>
|
<script>
|
||||||
async function testForOk(url) {
|
async function testForOk(url) {
|
||||||
const response = await fetch(url, {
|
const response = await fetch(url, {
|
||||||
method: "HEAD",
|
method: "{{.WaitForMethod}}",
|
||||||
});
|
});
|
||||||
console.log(`Got ${response.status}`);
|
console.log(`Got ${response.status}`);
|
||||||
return response.status === {{.WaitForCode}};
|
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
|
|
||||||
32
main.go
32
main.go
@@ -1,11 +1,14 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"os/signal"
|
||||||
"runtime"
|
"runtime"
|
||||||
"traefik-lazyload/pkg/config"
|
"traefik-lazyload/pkg/config"
|
||||||
"traefik-lazyload/pkg/service"
|
"traefik-lazyload/pkg/service"
|
||||||
@@ -44,14 +47,31 @@ func main() {
|
|||||||
|
|
||||||
// Set up http server
|
// Set up http server
|
||||||
subFs, _ := fs.Sub(httpAssets, "assets")
|
subFs, _ := fs.Sub(httpAssets, "assets")
|
||||||
http.Handle(httpAssetPrefix, http.StripPrefix(httpAssetPrefix, http.FileServer(http.FS(subFs))))
|
router := http.NewServeMux()
|
||||||
http.HandleFunc("/", ContainerHandler)
|
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)
|
logrus.Infof("Listening on %s...", config.Model.Listen)
|
||||||
if config.Model.StatusHost != "" {
|
if config.Model.StatusHost != "" {
|
||||||
logrus.Infof("Status host set to %s", 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) {
|
func ContainerHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
@@ -77,10 +97,8 @@ func ContainerHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
} else {
|
} else {
|
||||||
w.WriteHeader(http.StatusAccepted)
|
w.WriteHeader(http.StatusAccepted)
|
||||||
renderErr := splashTemplate.Execute(w, SplashModel{
|
renderErr := splashTemplate.Execute(w, SplashModel{
|
||||||
Name: host,
|
Name: host,
|
||||||
CID: sOpts.ContainerName,
|
ContainerState: sOpts,
|
||||||
WaitForCode: sOpts.WaitForCode,
|
|
||||||
WaitForPath: sOpts.WaitForPath,
|
|
||||||
})
|
})
|
||||||
if renderErr != nil {
|
if renderErr != nil {
|
||||||
logrus.Error(renderErr)
|
logrus.Error(renderErr)
|
||||||
|
|||||||
@@ -1,19 +1,19 @@
|
|||||||
package service
|
package service
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"strconv"
|
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
"traefik-lazyload/pkg/config"
|
"traefik-lazyload/pkg/config"
|
||||||
|
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
"github.com/sirupsen/logrus"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type containerSettings struct {
|
type containerSettings struct {
|
||||||
stopDelay time.Duration
|
stopDelay time.Duration
|
||||||
waitForCode int
|
waitForCode int
|
||||||
waitForPath string
|
waitForPath string
|
||||||
|
waitForMethod string
|
||||||
|
needs []string
|
||||||
}
|
}
|
||||||
|
|
||||||
type ContainerState struct {
|
type ContainerState struct {
|
||||||
@@ -34,26 +34,11 @@ func newStateFromContainer(ct *types.Container) *ContainerState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func extractContainerLabels(ct *types.Container) (target containerSettings) {
|
func extractContainerLabels(ct *types.Container) (target containerSettings) {
|
||||||
{ // Parse stop delay
|
target.stopDelay, _ = labelOrDefaultDuration(ct, "stopdelay", config.Model.StopDelay)
|
||||||
stopDelay, _ := labelOrDefault(ct, "stopdelay", config.Model.StopDelay.String())
|
target.waitForCode, _ = labelOrDefaultInt(ct, "waitforcode", 200)
|
||||||
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.waitForPath, _ = labelOrDefault(ct, "waitforpath", "/")
|
target.waitForPath, _ = labelOrDefault(ct, "waitforpath", "/")
|
||||||
|
target.waitForMethod, _ = labelOrDefault(ct, "waitformethod", "HEAD")
|
||||||
|
target.needs, _ = labelOrDefaultArr(ct, "needs")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -85,6 +70,19 @@ func (s *containerSettings) StopDelay() string {
|
|||||||
return s.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 {
|
type ContainerWrapper struct {
|
||||||
types.Container
|
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()
|
return s.client.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
type StartResult struct {
|
func (s *Core) StartHost(hostname string) (*ContainerState, error) {
|
||||||
WaitForCode int
|
|
||||||
WaitForPath string
|
|
||||||
ContainerName string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Core) StartHost(hostname string) (*StartResult, error) {
|
|
||||||
s.mux.Lock()
|
s.mux.Lock()
|
||||||
defer s.mux.Unlock()
|
defer s.mux.Unlock()
|
||||||
|
|
||||||
@@ -75,11 +69,7 @@ func (s *Core) StartHost(hostname string) (*StartResult, error) {
|
|||||||
|
|
||||||
if ets, exists := s.active[ct.ID]; exists {
|
if ets, exists := s.active[ct.ID]; exists {
|
||||||
logrus.Debugf("Asked to start host, but we already think it's started: %s", ets.name)
|
logrus.Debugf("Asked to start host, but we already think it's started: %s", ets.name)
|
||||||
return &StartResult{
|
return ets, nil
|
||||||
WaitForCode: ets.waitForCode,
|
|
||||||
WaitForPath: ets.waitForPath,
|
|
||||||
ContainerName: containerShort(ct),
|
|
||||||
}, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
go s.startContainer(ctx, ct)
|
go s.startContainer(ctx, ct)
|
||||||
@@ -88,10 +78,7 @@ func (s *Core) StartHost(hostname string) (*StartResult, error) {
|
|||||||
ets := newStateFromContainer(ct)
|
ets := newStateFromContainer(ct)
|
||||||
s.active[ct.ID] = ets
|
s.active[ct.ID] = ets
|
||||||
|
|
||||||
return &StartResult{
|
return ets, nil
|
||||||
WaitForCode: ets.waitForCode,
|
|
||||||
WaitForPath: ets.waitForPath,
|
|
||||||
}, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Core) StopAll() {
|
func (s *Core) StopAll() {
|
||||||
@@ -229,6 +216,15 @@ func (s *Core) checkContainerForInactivity(ctx context.Context, cid string, ct *
|
|||||||
return false, nil
|
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) {
|
func (s *Core) findContainerByHostname(ctx context.Context, hostname string) (*types.Container, error) {
|
||||||
containers, err := s.findAllLazyloadContainers(ctx, true)
|
containers, err := s.findAllLazyloadContainers(ctx, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ package service
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
"traefik-lazyload/pkg/config"
|
|
||||||
|
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
)
|
)
|
||||||
@@ -16,13 +15,6 @@ func sumNetworkBytes(networks map[string]types.NetworkStats) (recv int64, send i
|
|||||||
return
|
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 {
|
func shortId(id string) string {
|
||||||
const SLEN = 8
|
const SLEN = 8
|
||||||
if len(id) <= SLEN {
|
if len(id) <= SLEN {
|
||||||
@@ -42,10 +34,7 @@ func containerShort(c *types.Container) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func trimRootPath(s string) string {
|
func trimRootPath(s string) string {
|
||||||
if strings.HasPrefix(s, "/") {
|
return strings.TrimPrefix(s, "/")
|
||||||
return s[1:]
|
|
||||||
}
|
|
||||||
return s
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func isRunning(c *types.Container) bool {
|
func isRunning(c *types.Container) bool {
|
||||||
|
|||||||
Reference in New Issue
Block a user