mirror of
https://github.com/zix99/traefik-lazyload.git
synced 2025-12-21 13:23:04 +01:00
Code refactor, separate discovery from manager
This commit is contained in:
@@ -5,6 +5,7 @@ import (
|
|||||||
"path"
|
"path"
|
||||||
"text/template"
|
"text/template"
|
||||||
"traefik-lazyload/pkg/config"
|
"traefik-lazyload/pkg/config"
|
||||||
|
"traefik-lazyload/pkg/containers"
|
||||||
"traefik-lazyload/pkg/service"
|
"traefik-lazyload/pkg/service"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -22,8 +23,8 @@ var splashTemplate = template.Must(template.ParseFS(httpAssets, path.Join("asset
|
|||||||
|
|
||||||
type StatusPageModel struct {
|
type StatusPageModel struct {
|
||||||
Active []*service.ContainerState
|
Active []*service.ContainerState
|
||||||
Qualifying []service.ContainerWrapper
|
Qualifying []containers.Wrapper
|
||||||
Providers []service.ContainerWrapper
|
Providers []containers.Wrapper
|
||||||
RuntimeMetrics string
|
RuntimeMetrics string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
17
main.go
17
main.go
@@ -11,6 +11,7 @@ import (
|
|||||||
"os/signal"
|
"os/signal"
|
||||||
"runtime"
|
"runtime"
|
||||||
"traefik-lazyload/pkg/config"
|
"traefik-lazyload/pkg/config"
|
||||||
|
"traefik-lazyload/pkg/containers"
|
||||||
"traefik-lazyload/pkg/service"
|
"traefik-lazyload/pkg/service"
|
||||||
|
|
||||||
"github.com/docker/docker/client"
|
"github.com/docker/docker/client"
|
||||||
@@ -18,6 +19,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var core *service.Core
|
var core *service.Core
|
||||||
|
var discovery *containers.Discovery
|
||||||
|
|
||||||
func mustCreateDockerClient() *client.Client {
|
func mustCreateDockerClient() *client.Client {
|
||||||
cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
|
cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
|
||||||
@@ -34,8 +36,11 @@ func main() {
|
|||||||
logrus.Debug("Verbose is on")
|
logrus.Debug("Verbose is on")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
dockerClient := mustCreateDockerClient()
|
||||||
|
discovery = containers.NewDiscovery(dockerClient)
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
core, err = service.New(mustCreateDockerClient(), config.Model.PollFreq)
|
core, err = service.New(dockerClient, discovery, config.Model.PollFreq)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logrus.Fatal(err)
|
logrus.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -87,7 +92,7 @@ func ContainerHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if sOpts, err := core.StartHost(host); err != nil {
|
if sOpts, err := core.StartHost(host); err != nil {
|
||||||
if errors.Is(err, service.ErrNotFound) {
|
if errors.Is(err, containers.ErrNotFound) {
|
||||||
w.WriteHeader(http.StatusNotFound)
|
w.WriteHeader(http.StatusNotFound)
|
||||||
io.WriteString(w, "not found")
|
io.WriteString(w, "not found")
|
||||||
} else {
|
} else {
|
||||||
@@ -111,10 +116,14 @@ func StatusHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
case "/":
|
case "/":
|
||||||
var stats runtime.MemStats
|
var stats runtime.MemStats
|
||||||
runtime.ReadMemStats(&stats)
|
runtime.ReadMemStats(&stats)
|
||||||
|
|
||||||
|
qualifying, _ := discovery.QualifyingContainers(r.Context())
|
||||||
|
providers, _ := discovery.ProviderContainers(r.Context())
|
||||||
|
|
||||||
statusPageTemplate.Execute(w, StatusPageModel{
|
statusPageTemplate.Execute(w, StatusPageModel{
|
||||||
Active: core.ActiveContainers(),
|
Active: core.ActiveContainers(),
|
||||||
Qualifying: core.QualifyingContainers(r.Context()),
|
Qualifying: qualifying,
|
||||||
Providers: core.ProviderContainers(r.Context()),
|
Providers: providers,
|
||||||
RuntimeMetrics: fmt.Sprintf("Heap=%d, InUse=%d, Total=%d, Sys=%d, NumGC=%d", stats.HeapAlloc, stats.HeapInuse, stats.TotalAlloc, stats.Sys, stats.NumGC),
|
RuntimeMetrics: fmt.Sprintf("Heap=%d, InUse=%d, Total=%d, Sys=%d, NumGC=%d", stats.HeapAlloc, stats.HeapInuse, stats.TotalAlloc, stats.Sys, stats.NumGC),
|
||||||
})
|
})
|
||||||
default:
|
default:
|
||||||
|
|||||||
78
pkg/containers/discovery.go
Normal file
78
pkg/containers/discovery.go
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
package containers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"strings"
|
||||||
|
"traefik-lazyload/pkg/config"
|
||||||
|
|
||||||
|
"github.com/docker/docker/api/types"
|
||||||
|
"github.com/docker/docker/api/types/filters"
|
||||||
|
"github.com/docker/docker/client"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Discovery struct {
|
||||||
|
client *client.Client
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewDiscovery(client *client.Client) *Discovery {
|
||||||
|
return &Discovery{client}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return all containers that qualify to be load-managed (eg. have the tag)
|
||||||
|
func (s *Discovery) QualifyingContainers(ctx context.Context) ([]Wrapper, error) {
|
||||||
|
return s.FindAllLazyload(ctx, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Discovery) ProviderContainers(ctx context.Context) ([]Wrapper, error) {
|
||||||
|
filters := filters.NewArgs()
|
||||||
|
filters.Add("label", config.SubLabel("provides"))
|
||||||
|
|
||||||
|
return wrapListResult(s.client.ContainerList(ctx, types.ContainerListOptions{
|
||||||
|
Filters: filters,
|
||||||
|
All: true,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Discovery) FindAllLazyload(ctx context.Context, includeStopped bool) ([]Wrapper, error) {
|
||||||
|
filters := filters.NewArgs()
|
||||||
|
filters.Add("label", config.Model.LabelPrefix)
|
||||||
|
|
||||||
|
return wrapListResult(s.client.ContainerList(ctx, types.ContainerListOptions{
|
||||||
|
All: includeStopped,
|
||||||
|
Filters: filters,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Discovery) FindContainerByHostname(ctx context.Context, hostname string) (*Wrapper, error) {
|
||||||
|
containers, err := s.FindAllLazyload(ctx, true)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, c := range containers {
|
||||||
|
if hostStr, ok := c.Config("hosts"); ok {
|
||||||
|
hosts := strings.Split(hostStr, ",")
|
||||||
|
if strSliceContains(hosts, hostname) {
|
||||||
|
return &c, nil
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// If not defined explicitely, infer from traefik route
|
||||||
|
for k, v := range c.Labels {
|
||||||
|
if strings.Contains(k, "traefik.http.routers.") && strings.Contains(v, hostname) { // TODO: More complex
|
||||||
|
return &c, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, ErrNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Discovery) FindDepProvider(ctx context.Context, name string) ([]Wrapper, error) {
|
||||||
|
filters := filters.NewArgs()
|
||||||
|
filters.Add("label", config.SubLabel("provides")+"="+name)
|
||||||
|
return wrapListResult(s.client.ContainerList(ctx, types.ContainerListOptions{
|
||||||
|
Filters: filters,
|
||||||
|
All: true,
|
||||||
|
}))
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package service
|
package containers
|
||||||
|
|
||||||
import "errors"
|
import "errors"
|
||||||
|
|
||||||
10
pkg/containers/util.go
Normal file
10
pkg/containers/util.go
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
package containers
|
||||||
|
|
||||||
|
func strSliceContains(slice []string, s string) bool {
|
||||||
|
for _, item := range slice {
|
||||||
|
if item == s {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
120
pkg/containers/wrapper.go
Normal file
120
pkg/containers/wrapper.go
Normal file
@@ -0,0 +1,120 @@
|
|||||||
|
package containers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"sort"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
"traefik-lazyload/pkg/config"
|
||||||
|
|
||||||
|
"github.com/docker/docker/api/types"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Wrapper for container results that opaques and adds some methods to that data
|
||||||
|
type Wrapper struct {
|
||||||
|
types.Container
|
||||||
|
}
|
||||||
|
|
||||||
|
// Human-consumable name + ID
|
||||||
|
func (s *Wrapper) NameID() string {
|
||||||
|
var name string
|
||||||
|
if len(s.Names) > 0 {
|
||||||
|
name = strings.TrimPrefix(s.Names[0], "/")
|
||||||
|
} else {
|
||||||
|
name = s.Image
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%s (%s)", name, s.ShortId())
|
||||||
|
}
|
||||||
|
|
||||||
|
// char-len capped ID
|
||||||
|
func (s *Wrapper) ShortId() string {
|
||||||
|
const SLEN = 8
|
||||||
|
if len(s.ID) <= SLEN {
|
||||||
|
return s.ID
|
||||||
|
}
|
||||||
|
return s.ID[:SLEN]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns config labels with the prefix trimmed
|
||||||
|
func (s *Wrapper) ConfigLabels() map[string]string {
|
||||||
|
var matchString = config.Model.LabelPrefix + "."
|
||||||
|
|
||||||
|
ret := make(map[string]string)
|
||||||
|
for k, v := range s.Labels {
|
||||||
|
if strings.HasPrefix(k, matchString) {
|
||||||
|
ret[k[len(matchString):]] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Wrapper) Config(sublabel string) (string, bool) {
|
||||||
|
ret, ok := s.Labels[config.SubLabel(sublabel)]
|
||||||
|
return ret, ok
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Wrapper) ConfigOrDefault(sublabel, dflt string) (string, bool) {
|
||||||
|
if val, ok := s.Config(sublabel); ok {
|
||||||
|
return val, true
|
||||||
|
}
|
||||||
|
return dflt, false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Wrapper) ConfigCSV(sublabel string, dflt []string) ([]string, bool) {
|
||||||
|
if val, ok := s.Config(sublabel); ok {
|
||||||
|
return strings.Split(val, ","), true
|
||||||
|
}
|
||||||
|
return dflt, false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Wrapper) ConfigInt(sublabel string, dflt int) (int, bool) {
|
||||||
|
val, ok := s.Config(sublabel)
|
||||||
|
if !ok {
|
||||||
|
return dflt, false
|
||||||
|
}
|
||||||
|
|
||||||
|
if ival, err := strconv.Atoi(val); err != nil {
|
||||||
|
logrus.Warnf("Unable to parse %s on %s: %v. Using default of %d", sublabel, s.NameID(), err, dflt)
|
||||||
|
return dflt, false
|
||||||
|
} else {
|
||||||
|
return ival, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Wrapper) ConfigDuration(sublabel string, dflt time.Duration) (time.Duration, bool) {
|
||||||
|
val, ok := s.Config(sublabel)
|
||||||
|
if !ok {
|
||||||
|
return dflt, false
|
||||||
|
}
|
||||||
|
|
||||||
|
if dur, err := time.ParseDuration(val); err != nil {
|
||||||
|
logrus.Warnf("Unable to parse %s on %s: %v. Using default of %s", sublabel, s.NameID(), err, dflt.String())
|
||||||
|
return dflt, false
|
||||||
|
} else {
|
||||||
|
return dur, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// true if state is running
|
||||||
|
func (s *Wrapper) IsRunning() bool {
|
||||||
|
return s.State == "running"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wrap a container set
|
||||||
|
func wrapContainers(cts ...types.Container) []Wrapper {
|
||||||
|
ret := make([]Wrapper, len(cts))
|
||||||
|
for i, c := range cts {
|
||||||
|
ret[i] = Wrapper{c}
|
||||||
|
}
|
||||||
|
sort.Slice(ret, func(i, j int) bool {
|
||||||
|
return ret[i].NameID() < ret[j].NameID()
|
||||||
|
})
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
func wrapListResult(cts []types.Container, err error) ([]Wrapper, error) {
|
||||||
|
return wrapContainers(cts...), err
|
||||||
|
}
|
||||||
@@ -1,12 +1,9 @@
|
|||||||
package service
|
package service
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"sort"
|
|
||||||
"strings"
|
|
||||||
"time"
|
"time"
|
||||||
"traefik-lazyload/pkg/config"
|
"traefik-lazyload/pkg/config"
|
||||||
|
"traefik-lazyload/pkg/containers"
|
||||||
"github.com/docker/docker/api/types"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type containerSettings struct {
|
type containerSettings struct {
|
||||||
@@ -26,21 +23,21 @@ type ContainerState struct {
|
|||||||
pinned bool // Don't remove, even if not started
|
pinned bool // Don't remove, even if not started
|
||||||
}
|
}
|
||||||
|
|
||||||
func newStateFromContainer(ct *types.Container) *ContainerState {
|
func newStateFromContainer(ct *containers.Wrapper) *ContainerState {
|
||||||
return &ContainerState{
|
return &ContainerState{
|
||||||
name: containerShort(ct),
|
name: ct.NameID(),
|
||||||
containerSettings: extractContainerLabels(ct),
|
containerSettings: extractContainerLabels(ct),
|
||||||
lastActivity: time.Now(),
|
lastActivity: time.Now(),
|
||||||
started: time.Now(),
|
started: time.Now(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func extractContainerLabels(ct *types.Container) (target containerSettings) {
|
func extractContainerLabels(ct *containers.Wrapper) (target containerSettings) {
|
||||||
target.stopDelay, _ = labelOrDefaultDuration(ct, "stopdelay", config.Model.StopDelay)
|
target.stopDelay, _ = ct.ConfigDuration("stopdelay", config.Model.StopDelay)
|
||||||
target.waitForCode, _ = labelOrDefaultInt(ct, "waitforcode", 200)
|
target.waitForCode, _ = ct.ConfigInt("waitforcode", 200)
|
||||||
target.waitForPath, _ = labelOrDefault(ct, "waitforpath", "/")
|
target.waitForPath, _ = ct.ConfigOrDefault("waitforpath", "/")
|
||||||
target.waitForMethod, _ = labelOrDefault(ct, "waitformethod", "HEAD")
|
target.waitForMethod, _ = ct.ConfigOrDefault("waitformethod", "HEAD")
|
||||||
target.needs, _ = labelOrDefaultArr(ct, "needs")
|
target.needs, _ = ct.ConfigCSV("needs", nil)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -52,7 +49,7 @@ func (s *ContainerState) LastActive() time.Time {
|
|||||||
return s.lastActivity
|
return s.lastActivity
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ContainerState) LastActiveAge() string {
|
func (s *ContainerState) LastActiveAge() string { // FIXME: Return duration (update UI)
|
||||||
return time.Since(s.lastActivity).Round(time.Second).String()
|
return time.Since(s.lastActivity).Round(time.Second).String()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -68,7 +65,7 @@ func (s *ContainerState) Started() time.Time {
|
|||||||
return s.started
|
return s.started
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *containerSettings) StopDelay() string {
|
func (s *containerSettings) StopDelay() string { // FIXME: Return duration (update UI)
|
||||||
return s.stopDelay.String()
|
return s.stopDelay.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -83,35 +80,3 @@ func (s *ContainerState) WaitForPath() string {
|
|||||||
func (s *ContainerState) WaitForMethod() string {
|
func (s *ContainerState) WaitForMethod() string {
|
||||||
return s.waitForMethod
|
return s.waitForMethod
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wrapper for container results that opaques and adds some methods to that data
|
|
||||||
type ContainerWrapper struct {
|
|
||||||
types.Container
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *ContainerWrapper) NameID() string {
|
|
||||||
return containerShort(&s.Container)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *ContainerWrapper) ConfigLabels() map[string]string {
|
|
||||||
var matchString = config.Model.LabelPrefix + "."
|
|
||||||
|
|
||||||
ret := make(map[string]string)
|
|
||||||
for k, v := range s.Labels {
|
|
||||||
if strings.HasPrefix(k, matchString) {
|
|
||||||
ret[k[len(matchString):]] = v
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ret
|
|
||||||
}
|
|
||||||
|
|
||||||
func wrapContainers(cts ...types.Container) []ContainerWrapper {
|
|
||||||
ret := make([]ContainerWrapper, len(cts))
|
|
||||||
for i, c := range cts {
|
|
||||||
ret[i] = ContainerWrapper{c}
|
|
||||||
}
|
|
||||||
sort.Slice(ret, func(i, j int) bool {
|
|
||||||
return ret[i].NameID() < ret[j].NameID()
|
|
||||||
})
|
|
||||||
return ret
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,53 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -5,14 +5,12 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
"traefik-lazyload/pkg/config"
|
"traefik-lazyload/pkg/containers"
|
||||||
|
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
"github.com/docker/docker/api/types/container"
|
"github.com/docker/docker/api/types/container"
|
||||||
"github.com/docker/docker/api/types/filters"
|
|
||||||
"github.com/docker/docker/client"
|
"github.com/docker/docker/client"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
@@ -21,12 +19,13 @@ type Core struct {
|
|||||||
mux sync.Mutex
|
mux sync.Mutex
|
||||||
term chan bool
|
term chan bool
|
||||||
|
|
||||||
client *client.Client
|
client *client.Client
|
||||||
|
discovery *containers.Discovery
|
||||||
|
|
||||||
active map[string]*ContainerState // cid -> state
|
active map[string]*ContainerState // cid -> state
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(client *client.Client, pollRate time.Duration) (*Core, error) {
|
func New(client *client.Client, discovery *containers.Discovery, pollRate time.Duration) (*Core, error) {
|
||||||
// Test client and report
|
// Test client and report
|
||||||
if info, err := client.Info(context.Background()); err != nil {
|
if info, err := client.Info(context.Background()); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -36,9 +35,10 @@ func New(client *client.Client, pollRate time.Duration) (*Core, error) {
|
|||||||
|
|
||||||
// Make core
|
// Make core
|
||||||
ret := &Core{
|
ret := &Core{
|
||||||
client: client,
|
client: client,
|
||||||
active: make(map[string]*ContainerState),
|
discovery: discovery,
|
||||||
term: make(chan bool),
|
active: make(map[string]*ContainerState),
|
||||||
|
term: make(chan bool),
|
||||||
}
|
}
|
||||||
|
|
||||||
ret.Poll() // initial force-poll to update
|
ret.Poll() // initial force-poll to update
|
||||||
@@ -61,7 +61,7 @@ func (s *Core) StartHost(hostname string) (*ContainerState, error) {
|
|||||||
|
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
ct, err := s.findContainerByHostname(ctx, hostname)
|
ct, err := s.discovery.FindContainerByHostname(ctx, hostname)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logrus.Warnf("Unable to find container for host %s: %s", hostname, err)
|
logrus.Warnf("Unable to find container for host %s: %s", hostname, err)
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -85,7 +85,7 @@ func (s *Core) StartHost(hostname string) (*ContainerState, error) {
|
|||||||
ets.lastActivity = time.Now()
|
ets.lastActivity = time.Now()
|
||||||
s.mux.Unlock()
|
s.mux.Unlock()
|
||||||
}()
|
}()
|
||||||
s.startDependencyFor(ctx, ets.needs, containerShort(ct))
|
s.startDependencyFor(ctx, ets.needs, ct.NameID())
|
||||||
s.startContainerSync(ctx, ct)
|
s.startContainerSync(ctx, ct)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
@@ -107,26 +107,38 @@ func (s *Core) StopAll() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Core) startContainerSync(ctx context.Context, ct *types.Container) error {
|
// Returns all actively managed containers
|
||||||
if isRunning(ct) {
|
func (s *Core) ActiveContainers() []*ContainerState {
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
s.mux.Lock()
|
s.mux.Lock()
|
||||||
defer s.mux.Unlock()
|
defer s.mux.Unlock()
|
||||||
|
|
||||||
|
ret := make([]*ContainerState, 0, len(s.active))
|
||||||
|
for _, item := range s.active {
|
||||||
|
ret = append(ret, item)
|
||||||
|
}
|
||||||
|
sort.Slice(ret, func(i, j int) bool {
|
||||||
|
return ret[i].name < ret[j].name
|
||||||
|
})
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Core) startContainerSync(ctx context.Context, ct *containers.Wrapper) error {
|
||||||
|
if ct.IsRunning() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
if err := s.client.ContainerStart(ctx, ct.ID, types.ContainerStartOptions{}); err != nil {
|
if err := s.client.ContainerStart(ctx, ct.ID, types.ContainerStartOptions{}); err != nil {
|
||||||
logrus.Warnf("Error starting container %s: %s", containerShort(ct), err)
|
logrus.Warnf("Error starting container %s: %s", ct.NameID(), err)
|
||||||
return err
|
return err
|
||||||
} else {
|
} else {
|
||||||
logrus.Infof("Started container %s", containerShort(ct))
|
logrus.Infof("Started container %s", ct.NameID())
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Core) startDependencyFor(ctx context.Context, needs []string, forContainer string) {
|
func (s *Core) startDependencyFor(ctx context.Context, needs []string, forContainer string) {
|
||||||
for _, dep := range needs {
|
for _, dep := range needs {
|
||||||
providers, err := s.findContainersByDepProvider(ctx, dep)
|
providers, err := s.discovery.FindDepProvider(ctx, dep)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logrus.Errorf("Error finding dependency provider for %s: %v", dep, err)
|
logrus.Errorf("Error finding dependency provider for %s: %v", dep, err)
|
||||||
@@ -134,12 +146,12 @@ func (s *Core) startDependencyFor(ctx context.Context, needs []string, forContai
|
|||||||
logrus.Warnf("Unable to find any container that provides %s for %s", dep, forContainer)
|
logrus.Warnf("Unable to find any container that provides %s for %s", dep, forContainer)
|
||||||
} else {
|
} else {
|
||||||
for _, provider := range providers {
|
for _, provider := range providers {
|
||||||
if !isRunning(&provider) {
|
if !provider.IsRunning() {
|
||||||
logrus.Infof("Starting dependency for %s: %s", forContainer, containerShort(&provider))
|
logrus.Infof("Starting dependency for %s: %s", forContainer, provider.NameID())
|
||||||
|
|
||||||
s.startContainerSync(ctx, &provider)
|
s.startContainerSync(ctx, &provider)
|
||||||
|
|
||||||
delay, _ := labelOrDefaultDuration(&provider, "provides.delay", 2*time.Second)
|
delay, _ := provider.ConfigDuration("provides.delay", 2*time.Second)
|
||||||
logrus.Debugf("Delaying %s to start %s", delay.String(), dep)
|
logrus.Debugf("Delaying %s to start %s", delay.String(), dep)
|
||||||
time.Sleep(delay)
|
time.Sleep(delay)
|
||||||
}
|
}
|
||||||
@@ -167,15 +179,15 @@ func (s *Core) stopDependenciesFor(ctx context.Context, cid string, cts *Contain
|
|||||||
for dep, needed := range deps {
|
for dep, needed := range deps {
|
||||||
if !needed {
|
if !needed {
|
||||||
logrus.Infof("Stopping dependency %s...", dep)
|
logrus.Infof("Stopping dependency %s...", dep)
|
||||||
containers, err := s.findContainersByDepProvider(ctx, dep)
|
containers, err := s.discovery.FindDepProvider(ctx, dep)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logrus.Errorf("Unable to find dependency provider containers for %s: %v", dep, err)
|
logrus.Errorf("Unable to find dependency provider containers for %s: %v", dep, err)
|
||||||
} else if len(containers) == 0 {
|
} else if len(containers) == 0 {
|
||||||
logrus.Warnf("Unable to find any containers for dependency %s", dep)
|
logrus.Warnf("Unable to find any containers for dependency %s", dep)
|
||||||
} else {
|
} else {
|
||||||
for _, ct := range containers {
|
for _, ct := range containers {
|
||||||
if isRunning(&ct) {
|
if ct.IsRunning() {
|
||||||
logrus.Infof("Stopping %s...", containerShort(&ct))
|
logrus.Infof("Stopping %s...", ct.NameID())
|
||||||
go s.client.ContainerStop(ctx, ct.ID, container.StopOptions{})
|
go s.client.ContainerStop(ctx, ct.ID, container.StopOptions{})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -215,16 +227,16 @@ func (s *Core) Poll() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *Core) checkForNewContainersSync(ctx context.Context) {
|
func (s *Core) checkForNewContainersSync(ctx context.Context) {
|
||||||
containers, err := s.findAllLazyloadContainers(ctx, false)
|
cts, err := s.discovery.FindAllLazyload(ctx, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logrus.Warnf("Error checking for new containers: %v", err)
|
logrus.Warnf("Error checking for new containers: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
runningContainers := make(map[string]*types.Container)
|
runningContainers := make(map[string]*containers.Wrapper)
|
||||||
for i, ct := range containers {
|
for i, ct := range cts {
|
||||||
if isRunning(&ct) {
|
if ct.IsRunning() {
|
||||||
runningContainers[ct.ID] = &containers[i]
|
runningContainers[ct.ID] = &cts[i]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -240,7 +252,7 @@ func (s *Core) checkForNewContainersSync(ctx context.Context) {
|
|||||||
// now, look for containers that are running, but aren't in our active inventory
|
// now, look for containers that are running, but aren't in our active inventory
|
||||||
for _, ct := range runningContainers {
|
for _, ct := range runningContainers {
|
||||||
if _, ok := s.active[ct.ID]; !ok {
|
if _, ok := s.active[ct.ID]; !ok {
|
||||||
logrus.Infof("Discovered running container %s", containerShort(ct))
|
logrus.Infof("Discovered running container %s", ct.NameID())
|
||||||
s.active[ct.ID] = newStateFromContainer(ct)
|
s.active[ct.ID] = newStateFromContainer(ct)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -306,88 +318,3 @@ 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("provides")+"="+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 {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, c := range containers {
|
|
||||||
if hostStr, ok := labelOrDefault(&c, "hosts", ""); ok {
|
|
||||||
hosts := strings.Split(hostStr, ",")
|
|
||||||
if strSliceContains(hosts, hostname) {
|
|
||||||
return &c, nil
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// If not defined explicitely, infer from traefik route
|
|
||||||
for k, v := range c.Labels {
|
|
||||||
if strings.Contains(k, "traefik.http.routers.") && strings.Contains(v, hostname) { // TODO: More complex
|
|
||||||
return &c, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, ErrNotFound
|
|
||||||
}
|
|
||||||
|
|
||||||
// Finds all containers on node that are labeled with lazyloader config
|
|
||||||
func (s *Core) findAllLazyloadContainers(ctx context.Context, includeStopped bool) ([]types.Container, error) {
|
|
||||||
filters := filters.NewArgs()
|
|
||||||
filters.Add("label", config.Model.LabelPrefix)
|
|
||||||
|
|
||||||
return s.client.ContainerList(ctx, types.ContainerListOptions{
|
|
||||||
All: includeStopped,
|
|
||||||
Filters: filters,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Returns all actively managed containers
|
|
||||||
func (s *Core) ActiveContainers() []*ContainerState {
|
|
||||||
s.mux.Lock()
|
|
||||||
defer s.mux.Unlock()
|
|
||||||
|
|
||||||
ret := make([]*ContainerState, 0, len(s.active))
|
|
||||||
for _, item := range s.active {
|
|
||||||
ret = append(ret, item)
|
|
||||||
}
|
|
||||||
sort.Slice(ret, func(i, j int) bool {
|
|
||||||
return ret[i].name < ret[j].name
|
|
||||||
})
|
|
||||||
return ret
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return all containers that qualify to be load-managed (eg. have the tag)
|
|
||||||
func (s *Core) QualifyingContainers(ctx context.Context) []ContainerWrapper {
|
|
||||||
ct, err := s.findAllLazyloadContainers(ctx, true)
|
|
||||||
if err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return wrapContainers(ct...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Core) ProviderContainers(ctx context.Context) []ContainerWrapper {
|
|
||||||
filters := filters.NewArgs()
|
|
||||||
filters.Add("label", config.SubLabel("provides"))
|
|
||||||
|
|
||||||
ct, err := s.client.ContainerList(ctx, types.ContainerListOptions{
|
|
||||||
Filters: filters,
|
|
||||||
All: true,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return wrapContainers(ct...)
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,9 +1,6 @@
|
|||||||
package service
|
package service
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -14,38 +11,3 @@ func sumNetworkBytes(networks map[string]types.NetworkStats) (recv int64, send i
|
|||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func shortId(id string) string {
|
|
||||||
const SLEN = 8
|
|
||||||
if len(id) <= SLEN {
|
|
||||||
return id
|
|
||||||
}
|
|
||||||
return id[:SLEN]
|
|
||||||
}
|
|
||||||
|
|
||||||
func containerShort(c *types.Container) string {
|
|
||||||
var name string
|
|
||||||
if len(c.Names) > 0 {
|
|
||||||
name = trimRootPath(c.Names[0])
|
|
||||||
} else {
|
|
||||||
name = c.Image
|
|
||||||
}
|
|
||||||
return fmt.Sprintf("%s(%s)", name, shortId(c.ID))
|
|
||||||
}
|
|
||||||
|
|
||||||
func trimRootPath(s string) string {
|
|
||||||
return strings.TrimPrefix(s, "/")
|
|
||||||
}
|
|
||||||
|
|
||||||
func isRunning(c *types.Container) bool {
|
|
||||||
return c.State == "running"
|
|
||||||
}
|
|
||||||
|
|
||||||
func strSliceContains(slice []string, s string) bool {
|
|
||||||
for _, item := range slice {
|
|
||||||
if item == s {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|||||||
Reference in New Issue
Block a user