mirror of
https://github.com/yusing/godoxy.git
synced 2026-02-23 02:44:52 +01:00
This is a large-scale refactoring across the codebase that replaces the custom `gperr.Error` type with Go's standard `error` interface. The changes include: - Replacing `gperr.Error` return types with `error` in function signatures - Using `errors.New()` and `fmt.Errorf()` instead of `gperr.New()` and `gperr.Errorf()` - Using `%w` format verb for error wrapping instead of `.With()` method - Replacing `gperr.Subject()` calls with `gperr.PrependSubject()` - Converting error logging from `gperr.Log*()` functions to zerolog's `.Err().Msg()` pattern - Update NewLogger to handle multiline error message - Updating `goutils` submodule to latest commit This refactoring aligns with Go idioms and removes the dependency on custom error handling abstractions in favor of standard library patterns.
196 lines
4.4 KiB
Go
Executable File
196 lines
4.4 KiB
Go
Executable File
package provider
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"strconv"
|
|
"time"
|
|
|
|
"github.com/goccy/go-yaml"
|
|
"github.com/rs/zerolog"
|
|
"github.com/rs/zerolog/log"
|
|
"github.com/yusing/godoxy/internal/docker"
|
|
"github.com/yusing/godoxy/internal/route"
|
|
"github.com/yusing/godoxy/internal/serialization"
|
|
"github.com/yusing/godoxy/internal/types"
|
|
"github.com/yusing/godoxy/internal/watcher"
|
|
gperr "github.com/yusing/goutils/errs"
|
|
)
|
|
|
|
type DockerProvider struct {
|
|
name string
|
|
dockerCfg types.DockerProviderConfig
|
|
l zerolog.Logger
|
|
}
|
|
|
|
const (
|
|
aliasRefPrefix = '#'
|
|
aliasRefPrefixAlt = '$'
|
|
)
|
|
|
|
var ErrAliasRefIndexOutOfRange = gperr.New("index out of range")
|
|
|
|
func DockerProviderImpl(name string, dockerCfg types.DockerProviderConfig) ProviderImpl {
|
|
return &DockerProvider{
|
|
name,
|
|
dockerCfg,
|
|
log.With().Str("type", "docker").Str("name", name).Logger(),
|
|
}
|
|
}
|
|
|
|
func (p *DockerProvider) String() string {
|
|
return "docker@" + p.name
|
|
}
|
|
|
|
func (p *DockerProvider) ShortName() string {
|
|
return p.name
|
|
}
|
|
|
|
func (p *DockerProvider) IsExplicitOnly() bool {
|
|
return p.name[len(p.name)-1] == '!'
|
|
}
|
|
|
|
func (p *DockerProvider) Logger() *zerolog.Logger {
|
|
return &p.l
|
|
}
|
|
|
|
func (p *DockerProvider) NewWatcher() watcher.Watcher {
|
|
return watcher.NewDockerWatcher(p.dockerCfg)
|
|
}
|
|
|
|
func (p *DockerProvider) loadRoutesImpl() (route.Routes, error) {
|
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
|
defer cancel()
|
|
|
|
containers, err := docker.ListContainers(ctx, p.dockerCfg)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
errs := gperr.NewBuilder("")
|
|
routes := make(route.Routes)
|
|
|
|
for _, c := range containers {
|
|
container := docker.FromDocker(&c, p.dockerCfg)
|
|
|
|
if container.Errors != nil {
|
|
errs.AddSubject(container.Errors, container.ContainerName)
|
|
continue
|
|
}
|
|
|
|
if container.IsHostNetworkMode {
|
|
err := docker.UpdatePorts(ctx, container)
|
|
if err != nil {
|
|
errs.AddSubject(err, container.ContainerName)
|
|
continue
|
|
}
|
|
}
|
|
|
|
newEntries, err := p.routesFromContainerLabels(container)
|
|
if err != nil {
|
|
errs.AddSubject(err, container.ContainerName)
|
|
}
|
|
for k, v := range newEntries {
|
|
if conflict, ok := routes[k]; ok {
|
|
err := gperr.Multiline().
|
|
Addf("route with alias %s already exists", k).
|
|
Addf("container %s", container.ContainerName).
|
|
Addf("conflicting container %s", conflict.Container.ContainerName)
|
|
if conflict.ShouldExclude() || v.ShouldExclude() {
|
|
log.Warn().Err(err).Msg("skipping conflicting route")
|
|
} else {
|
|
errs.Add(err)
|
|
}
|
|
} else {
|
|
routes[k] = v
|
|
}
|
|
}
|
|
}
|
|
|
|
return routes, errs.Error()
|
|
}
|
|
|
|
// Returns a list of proxy entries for a container.
|
|
// Always non-nil.
|
|
func (p *DockerProvider) routesFromContainerLabels(container *types.Container) (route.Routes, error) {
|
|
if !container.IsExplicit && p.IsExplicitOnly() {
|
|
return make(route.Routes, 0), nil
|
|
}
|
|
|
|
routes := make(route.Routes, len(container.Aliases))
|
|
|
|
// init entries map for all aliases
|
|
for _, a := range container.Aliases {
|
|
routes[a] = &route.Route{
|
|
Alias: a,
|
|
Metadata: route.Metadata{
|
|
Container: container,
|
|
},
|
|
}
|
|
}
|
|
|
|
errs := gperr.NewBuilder("label errors")
|
|
|
|
m, err := docker.ParseLabels(container.Labels, container.Aliases...)
|
|
errs.Add(err)
|
|
|
|
for alias, entryMapAny := range m {
|
|
if len(alias) == 0 {
|
|
errs.Adds("empty alias")
|
|
continue
|
|
}
|
|
|
|
entryMap, ok := entryMapAny.(types.LabelMap)
|
|
if !ok {
|
|
// try to deserialize to map
|
|
entryMap = make(types.LabelMap)
|
|
yamlStr, ok := entryMapAny.(string)
|
|
if !ok {
|
|
// should not happen
|
|
panic(fmt.Errorf("invalid entry map type %T", entryMapAny))
|
|
}
|
|
if err := yaml.Unmarshal([]byte(yamlStr), &entryMap); err != nil {
|
|
errs.AddSubject(err, alias)
|
|
continue
|
|
}
|
|
}
|
|
|
|
// check if it is an alias reference
|
|
switch alias[0] {
|
|
case aliasRefPrefix, aliasRefPrefixAlt:
|
|
index, err := strconv.Atoi(alias[1:])
|
|
if err != nil {
|
|
errs.Add(err)
|
|
break
|
|
}
|
|
if index < 1 || index > len(container.Aliases) {
|
|
errs.Add(ErrAliasRefIndexOutOfRange.Subject(strconv.Itoa(index)))
|
|
break
|
|
}
|
|
alias = container.Aliases[index-1]
|
|
}
|
|
|
|
// init entry if not exist
|
|
r, ok := routes[alias]
|
|
if !ok {
|
|
r = &route.Route{
|
|
Alias: alias,
|
|
Metadata: route.Metadata{
|
|
Container: container,
|
|
},
|
|
}
|
|
routes[alias] = r
|
|
}
|
|
|
|
// deserialize map into entry object
|
|
err := serialization.MapUnmarshalValidate(entryMap, r)
|
|
if err != nil {
|
|
errs.AddSubject(err, alias)
|
|
} else {
|
|
routes[alias] = r
|
|
}
|
|
}
|
|
|
|
return routes, errs.Error()
|
|
}
|