From dd8d3ab741e212f150244734e594f00283647493 Mon Sep 17 00:00:00 2001 From: Antoine MOREL Date: Fri, 9 Jan 2026 16:36:22 +0000 Subject: [PATCH] Add swarm service --- internal/context/context.go | 33 ++++++++++++-- internal/generator/generator.go | 79 +++++++++++++++++++++++++++++++++ templates/services.tmpl | 11 +++++ 3 files changed, 120 insertions(+), 3 deletions(-) create mode 100644 templates/services.tmpl diff --git a/internal/context/context.go b/internal/context/context.go index de283f94..a83697e3 100644 --- a/internal/context/context.go +++ b/internal/context/context.go @@ -13,9 +13,10 @@ import ( ) var ( - mu sync.RWMutex - dockerInfo Docker - dockerEnv *docker.Env + mu sync.RWMutex + dockerInfo Docker + dockerEnv *docker.Env + dockerService []*RuntimeService ) type Context []*RuntimeContainer @@ -30,6 +31,18 @@ func (c *Context) Docker() Docker { return dockerInfo } +func (c *Context) Service() []*RuntimeService { + mu.RLock() + defer mu.RUnlock() + return dockerService +} + +func SetServices(services []*RuntimeService) { + mu.Lock() + defer mu.Unlock() + dockerService = services +} + func SetServerInfo(d *docker.DockerInfo) { mu.Lock() defer mu.Unlock() @@ -80,6 +93,16 @@ type Health struct { Status string } +type RuntimeService struct { + ID string + Name string + Labels map[string]string + Replicas uint64 + EndpointMode string + Mode string + State string +} + type RuntimeContainer struct { ID string Created time.Time @@ -149,6 +172,10 @@ type Docker struct { CurrentContainerID string } +type Service struct { + Name string +} + // GetCurrentContainerID attempts to extract the current container ID from the provided file paths. // If no files paths are provided, it will default to /proc/1/cpuset, /proc/self/cgroup and /proc/self/mountinfo. // It attempts to match the HOSTNAME first then use the fallback method, and returns with the first valid match. diff --git a/internal/generator/generator.go b/internal/generator/generator.go index 613d7924..20dbc12d 100644 --- a/internal/generator/generator.go +++ b/internal/generator/generator.go @@ -79,6 +79,7 @@ func NewGenerator(gc GeneratorConfig) (*generator, error) { func (g *generator) Generate() error { g.generateFromContainers() + // g.generateFromServices() g.generateAtInterval() g.generateFromEvents() g.generateFromSignals() @@ -123,6 +124,14 @@ func (g *generator) generateFromSignals() { func (g *generator) generateFromContainers() { for _, config := range g.Configs.Config { + services, err := g.getServices(config) + if err != nil { + log.Printf("Error listing services: %s\n", err) + return + } + + context.SetServices(services) + containers, err := g.getContainers(config) if err != nil { log.Printf("Error listing containers: %s\n", err) @@ -158,6 +167,14 @@ func (g *generator) generateAtInterval() { for { select { case <-ticker.C: + services, err := g.getServices(cfg) + if err != nil { + log.Printf("Error listing services: %s\n", err) + return + } + + context.SetServices(services) + containers, err := g.getContainers(cfg) if err != nil { log.Printf("Error listing containers: %s\n", err) @@ -204,6 +221,15 @@ func (g *generator) generateFromEvents() { defer g.wg.Done() debouncedChan := newDebounceChannel(watcher, cfg.Wait) for range debouncedChan { + + services, err := g.getServices(cfg) + if err != nil { + log.Printf("Error listing services: %s\n", err) + return + } + + context.SetServices(services) + containers, err := g.getContainers(cfg) if err != nil { log.Printf("Error listing containers: %s\n", err) @@ -388,6 +414,59 @@ func (g *generator) sendSignalToFilteredContainers(config config.Config) { } } +func (g *generator) getServices(config config.Config) ([]*context.RuntimeService, error) { + apiInfo, err := g.Client.Info() + if err != nil { + log.Printf("Error retrieving docker server info: %s\n", err) + } else { + context.SetServerInfo(apiInfo) + } + apiServices, err := g.Client.ListServices(docker.ListServicesOptions{ + Status: true, + Filters: nil, + }) + if err != nil { + return nil, err + } + + services := []*context.RuntimeService{} + for _, apiService := range apiServices { + + runtimeService := &context.RuntimeService{ + Name: apiService.Spec.Name, + ID: apiService.ID, + Labels: apiService.Spec.Labels, + } + + if apiService.UpdateStatus != nil { + runtimeService.State = string(apiService.UpdateStatus.State) + } else { + runtimeService.State = "" + } + + if apiService.Spec.EndpointSpec != nil { + runtimeService.EndpointMode = string(apiService.Spec.EndpointSpec.Mode) + } else { + runtimeService.EndpointMode = "" + } + + if apiService.Spec.Mode.Replicated != nil { + runtimeService.Mode = "replicated" + runtimeService.Replicas = *apiService.Spec.Mode.Replicated.Replicas + } else if apiService.Spec.Mode.Global != nil { + runtimeService.Mode = "global" + runtimeService.Replicas = 0 + } else { + runtimeService.Mode = "" + runtimeService.Replicas = 0 + } + + services = append(services, runtimeService) + } + + return services, nil +} + func (g *generator) getContainers(config config.Config) ([]*context.RuntimeContainer, error) { apiInfo, err := g.Client.Info() if err != nil { diff --git a/templates/services.tmpl b/templates/services.tmpl new file mode 100644 index 00000000..2981fb24 --- /dev/null +++ b/templates/services.tmpl @@ -0,0 +1,11 @@ +---{{ .Docker.Name }}--- +{{range $key, $value := .Service}} +******************** +{{ if eq $value.Labels.ingress "true"}} +ingress : {{ $value.Labels.ingress }} +{{ $value.Name }} / {{ $value.EndpointMode }} / {{ $value.Replicas }} / {{ $value.State }} + {{range $labelKey, $labelValue := $value.Labels}} + {{ $labelKey }} = {{ $labelValue }} + {{end}} +{{ end }} +{{end}}