mirror of
https://github.com/yusing/godoxy.git
synced 2026-01-11 21:10:30 +01:00
feat: docker over tls (#178)
This commit is contained in:
@@ -29,13 +29,13 @@ func GetContainer(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
dockerHost, ok := docker.GetDockerHostByContainerID(id)
|
||||
dockerCfg, ok := docker.GetDockerCfgByContainerID(id)
|
||||
if !ok {
|
||||
c.JSON(http.StatusNotFound, apitypes.Error("container not found"))
|
||||
return
|
||||
}
|
||||
|
||||
dockerClient, err := docker.NewClient(dockerHost)
|
||||
dockerClient, err := docker.NewClient(dockerCfg)
|
||||
if err != nil {
|
||||
c.Error(apitypes.InternalServerError(err, "failed to create docker client"))
|
||||
return
|
||||
@@ -55,7 +55,7 @@ func GetContainer(c *gin.Context) {
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, &Container{
|
||||
Server: dockerHost,
|
||||
Server: dockerCfg.URL,
|
||||
Name: cont.Container.Name,
|
||||
ID: cont.Container.ID,
|
||||
Image: cont.Container.Image,
|
||||
|
||||
@@ -57,13 +57,13 @@ func Logs(c *gin.Context) {
|
||||
}
|
||||
|
||||
// TODO: implement levels
|
||||
dockerHost, ok := docker.GetDockerHostByContainerID(id)
|
||||
dockerCfg, ok := docker.GetDockerCfgByContainerID(id)
|
||||
if !ok {
|
||||
c.JSON(http.StatusNotFound, apitypes.Error(fmt.Sprintf("container %s not found", id)))
|
||||
return
|
||||
}
|
||||
|
||||
dockerClient, err := docker.NewClient(dockerHost)
|
||||
dockerClient, err := docker.NewClient(dockerCfg)
|
||||
if err != nil {
|
||||
c.Error(apitypes.InternalServerError(err, "failed to get docker client"))
|
||||
return
|
||||
@@ -105,7 +105,7 @@ func Logs(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
log.Err(err).
|
||||
Str("server", dockerHost).
|
||||
Str("server", dockerCfg.URL).
|
||||
Str("container", id).
|
||||
Msg("failed to de-multiplex logs")
|
||||
}
|
||||
|
||||
@@ -34,13 +34,13 @@ func Restart(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
dockerHost, ok := docker.GetDockerHostByContainerID(req.ID)
|
||||
dockerCfg, ok := docker.GetDockerCfgByContainerID(req.ID)
|
||||
if !ok {
|
||||
c.JSON(http.StatusNotFound, apitypes.Error("container not found"))
|
||||
return
|
||||
}
|
||||
|
||||
client, err := docker.NewClient(dockerHost)
|
||||
client, err := docker.NewClient(dockerCfg)
|
||||
if err != nil {
|
||||
c.Error(apitypes.InternalServerError(err, "failed to create docker client"))
|
||||
return
|
||||
|
||||
@@ -34,13 +34,13 @@ func Start(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
dockerHost, ok := docker.GetDockerHostByContainerID(req.ID)
|
||||
dockerCfg, ok := docker.GetDockerCfgByContainerID(req.ID)
|
||||
if !ok {
|
||||
c.JSON(http.StatusNotFound, apitypes.Error("container not found"))
|
||||
return
|
||||
}
|
||||
|
||||
client, err := docker.NewClient(dockerHost)
|
||||
client, err := docker.NewClient(dockerCfg)
|
||||
if err != nil {
|
||||
c.Error(apitypes.InternalServerError(err, "failed to create docker client"))
|
||||
return
|
||||
|
||||
@@ -34,13 +34,13 @@ func Stop(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
dockerHost, ok := docker.GetDockerHostByContainerID(req.ID)
|
||||
dockerCfg, ok := docker.GetDockerCfgByContainerID(req.ID)
|
||||
if !ok {
|
||||
c.JSON(http.StatusNotFound, apitypes.Error("container not found"))
|
||||
return
|
||||
}
|
||||
|
||||
client, err := docker.NewClient(dockerHost)
|
||||
client, err := docker.NewClient(dockerCfg)
|
||||
if err != nil {
|
||||
c.Error(apitypes.InternalServerError(err, "failed to create docker client"))
|
||||
return
|
||||
|
||||
@@ -318,9 +318,9 @@ func (state *state) loadRouteProviders() error {
|
||||
})
|
||||
}
|
||||
|
||||
for name, dockerHost := range providers.Docker {
|
||||
for name, dockerCfg := range providers.Docker {
|
||||
providersProducer.Go(func() {
|
||||
providersCh <- route.NewDockerProvider(name, dockerHost)
|
||||
providersCh <- route.NewDockerProvider(name, dockerCfg)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -32,12 +32,12 @@ type (
|
||||
HealthCheck types.HealthCheckConfig `json:"healthcheck"`
|
||||
}
|
||||
Providers struct {
|
||||
Files []string `json:"include" yaml:"include,omitempty" validate:"dive,filepath"`
|
||||
Docker map[string]string `json:"docker" yaml:"docker,omitempty" validate:"non_empty_docker_keys,dive,unix_addr|url"`
|
||||
Agents []*agent.AgentConfig `json:"agents" yaml:"agents,omitempty"`
|
||||
Notification []*notif.NotificationConfig `json:"notification" yaml:"notification,omitempty"`
|
||||
Proxmox []proxmox.Config `json:"proxmox" yaml:"proxmox,omitempty"`
|
||||
MaxMind *maxmind.Config `json:"maxmind" yaml:"maxmind,omitempty"`
|
||||
Files []string `json:"include" yaml:"include,omitempty" validate:"dive,filepath"`
|
||||
Docker map[string]types.DockerProviderConfig `json:"docker" yaml:"docker,omitempty" validate:"non_empty_docker_keys"`
|
||||
Agents []*agent.AgentConfig `json:"agents" yaml:"agents,omitempty"`
|
||||
Notification []*notif.NotificationConfig `json:"notification" yaml:"notification,omitempty"`
|
||||
Proxmox []proxmox.Config `json:"proxmox" yaml:"proxmox,omitempty"`
|
||||
MaxMind *maxmind.Config `json:"maxmind" yaml:"maxmind,omitempty"`
|
||||
}
|
||||
)
|
||||
|
||||
@@ -68,7 +68,7 @@ func init() {
|
||||
return true
|
||||
})
|
||||
serialization.MustRegisterValidation("non_empty_docker_keys", func(fl validator.FieldLevel) bool {
|
||||
m := fl.Field().Interface().(map[string]string)
|
||||
m := fl.Field().Interface().(map[string]types.DockerProviderConfig)
|
||||
for k := range m {
|
||||
if k == "" {
|
||||
return false
|
||||
|
||||
@@ -18,6 +18,7 @@ import (
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/yusing/godoxy/agent/pkg/agent"
|
||||
"github.com/yusing/godoxy/internal/common"
|
||||
"github.com/yusing/godoxy/internal/types"
|
||||
httputils "github.com/yusing/goutils/http"
|
||||
"github.com/yusing/goutils/task"
|
||||
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
|
||||
@@ -28,6 +29,8 @@ type (
|
||||
SharedClient struct {
|
||||
*client.Client
|
||||
|
||||
cfg types.DockerProviderConfig
|
||||
|
||||
refCount atomic.Int32
|
||||
closedOn atomic.Int64
|
||||
|
||||
@@ -120,7 +123,7 @@ func Clients() map[string]*SharedClient {
|
||||
// Returns:
|
||||
// - Client: the Docker client connection.
|
||||
// - error: an error if the connection failed.
|
||||
func NewClient(host string, unique ...bool) (*SharedClient, error) {
|
||||
func NewClient(cfg types.DockerProviderConfig, unique ...bool) (*SharedClient, error) {
|
||||
initClientCleanerOnce.Do(initClientCleaner)
|
||||
|
||||
u := false
|
||||
@@ -128,6 +131,8 @@ func NewClient(host string, unique ...bool) (*SharedClient, error) {
|
||||
u = unique[0]
|
||||
}
|
||||
|
||||
host := cfg.URL
|
||||
|
||||
if !u {
|
||||
clientMapMu.Lock()
|
||||
defer clientMapMu.Unlock()
|
||||
@@ -181,6 +186,10 @@ func NewClient(host string, unique ...bool) (*SharedClient, error) {
|
||||
}
|
||||
}
|
||||
|
||||
if cfg.TLS != nil {
|
||||
opt = append(opt, client.WithTLSClientConfig(cfg.TLS.CAFile, cfg.TLS.CertFile, cfg.TLS.KeyFile))
|
||||
}
|
||||
|
||||
client, err := client.New(opt...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -188,6 +197,7 @@ func NewClient(host string, unique ...bool) (*SharedClient, error) {
|
||||
|
||||
c := &SharedClient{
|
||||
Client: client,
|
||||
cfg: cfg,
|
||||
addr: addr,
|
||||
key: host,
|
||||
dial: dial,
|
||||
@@ -224,7 +234,7 @@ func (c *SharedClient) InterceptHTTPClient(intercept httputils.InterceptFunc) {
|
||||
func (c *SharedClient) CloneUnique() *SharedClient {
|
||||
// there will be no error here
|
||||
// since we are using the same host from a valid client.
|
||||
c, _ = NewClient(c.key, true)
|
||||
c, _ = NewClient(c.cfg, true)
|
||||
return c
|
||||
}
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ var (
|
||||
ErrNoNetwork = errors.New("no network found")
|
||||
)
|
||||
|
||||
func FromDocker(c *container.Summary, dockerHost string) (res *types.Container) {
|
||||
func FromDocker(c *container.Summary, dockerCfg types.DockerProviderConfig) (res *types.Container) {
|
||||
actualLabels := maps.Clone(c.Labels)
|
||||
|
||||
_, isExplicit := c.Labels[LabelAliases]
|
||||
@@ -47,7 +47,7 @@ func FromDocker(c *container.Summary, dockerHost string) (res *types.Container)
|
||||
|
||||
isExcluded, _ := strconv.ParseBool(helper.getDeleteLabel(LabelExclude))
|
||||
res = &types.Container{
|
||||
DockerHost: dockerHost,
|
||||
DockerCfg: dockerCfg,
|
||||
Image: helper.parseImage(),
|
||||
ContainerName: helper.getName(),
|
||||
ContainerID: c.ID,
|
||||
@@ -69,11 +69,11 @@ func FromDocker(c *container.Summary, dockerHost string) (res *types.Container)
|
||||
State: c.State,
|
||||
}
|
||||
|
||||
if agent.IsDockerHostAgent(dockerHost) {
|
||||
if agent.IsDockerHostAgent(dockerCfg.URL) {
|
||||
var ok bool
|
||||
res.Agent, ok = agent.GetAgent(dockerHost)
|
||||
res.Agent, ok = agent.GetAgent(dockerCfg.URL)
|
||||
if !ok {
|
||||
addError(res, fmt.Errorf("agent %q not found", dockerHost))
|
||||
addError(res, fmt.Errorf("agent %q not found", dockerCfg.URL))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -92,7 +92,7 @@ func IsBlacklisted(c *types.Container) bool {
|
||||
}
|
||||
|
||||
func UpdatePorts(c *types.Container) error {
|
||||
dockerClient, err := NewClient(c.DockerHost)
|
||||
dockerClient, err := NewClient(c.DockerCfg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -163,14 +163,14 @@ func isDatabase(c *types.Container) bool {
|
||||
}
|
||||
|
||||
func isLocal(c *types.Container) bool {
|
||||
if strings.HasPrefix(c.DockerHost, "unix://") {
|
||||
if strings.HasPrefix(c.DockerCfg.URL, "unix://") {
|
||||
return true
|
||||
}
|
||||
// treat it as local if the docker host is the same as the environment variable
|
||||
if c.DockerHost == EnvDockerHost {
|
||||
if c.DockerCfg.URL == EnvDockerHost {
|
||||
return true
|
||||
}
|
||||
url, err := url.Parse(c.DockerHost)
|
||||
url, err := url.Parse(c.DockerCfg.URL)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
@@ -190,7 +190,7 @@ func setPublicHostname(c *types.Container) {
|
||||
c.PublicHostname = "127.0.0.1"
|
||||
return
|
||||
}
|
||||
url, err := url.Parse(c.DockerHost)
|
||||
url, err := url.Parse(c.DockerCfg.URL)
|
||||
if err != nil {
|
||||
c.PublicHostname = "127.0.0.1"
|
||||
return
|
||||
@@ -258,7 +258,7 @@ func loadDeleteIdlewatcherLabels(c *types.Container, helper containerHelper) {
|
||||
if hasIdleTimeout {
|
||||
idwCfg := new(types.IdlewatcherConfig)
|
||||
idwCfg.Docker = &types.DockerConfig{
|
||||
DockerHost: c.DockerHost,
|
||||
DockerCfg: c.DockerCfg,
|
||||
ContainerID: c.ContainerID,
|
||||
ContainerName: c.ContainerName,
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/moby/moby/api/types/container"
|
||||
"github.com/yusing/godoxy/internal/types"
|
||||
expect "github.com/yusing/goutils/testing"
|
||||
)
|
||||
|
||||
@@ -36,7 +37,7 @@ func TestContainerExplicit(t *testing.T) {
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
c := FromDocker(&container.Summary{Names: []string{"test"}, State: "test", Labels: tt.labels}, "")
|
||||
c := FromDocker(&container.Summary{Names: []string{"test"}, State: "test", Labels: tt.labels}, types.DockerProviderConfig{})
|
||||
expect.Equal(t, c.IsExplicit, tt.isExplicit)
|
||||
})
|
||||
}
|
||||
@@ -73,7 +74,7 @@ func TestContainerHostNetworkMode(t *testing.T) {
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
c := FromDocker(tt.container, "")
|
||||
c := FromDocker(tt.container, types.DockerProviderConfig{})
|
||||
expect.Equal(t, c.IsHostNetworkMode, tt.isHostNetworkMode)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -2,18 +2,19 @@ package docker
|
||||
|
||||
import (
|
||||
"github.com/puzpuzpuz/xsync/v4"
|
||||
"github.com/yusing/godoxy/internal/types"
|
||||
)
|
||||
|
||||
var idDockerHostMap = xsync.NewMap[string, string](xsync.WithPresize(100))
|
||||
var idDockerCfgMap = xsync.NewMap[string, types.DockerProviderConfig](xsync.WithPresize(100))
|
||||
|
||||
func GetDockerHostByContainerID(id string) (string, bool) {
|
||||
return idDockerHostMap.Load(id)
|
||||
func GetDockerCfgByContainerID(id string) (types.DockerProviderConfig, bool) {
|
||||
return idDockerCfgMap.Load(id)
|
||||
}
|
||||
|
||||
func SetDockerHostByContainerID(id, host string) {
|
||||
idDockerHostMap.Store(id, host)
|
||||
func SetDockerCfgByContainerID(id string, cfg types.DockerProviderConfig) {
|
||||
idDockerCfgMap.Store(id, cfg)
|
||||
}
|
||||
|
||||
func DeleteDockerHostByContainerID(id string) {
|
||||
idDockerHostMap.Delete(id)
|
||||
func DeleteDockerCfgByContainerID(id string) {
|
||||
idDockerCfgMap.Delete(id)
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
|
||||
"github.com/moby/moby/api/types/container"
|
||||
"github.com/moby/moby/client"
|
||||
"github.com/yusing/godoxy/internal/types"
|
||||
)
|
||||
|
||||
var listOptions = client.ContainerListOptions{
|
||||
@@ -19,8 +20,8 @@ var listOptions = client.ContainerListOptions{
|
||||
All: true,
|
||||
}
|
||||
|
||||
func ListContainers(ctx context.Context, clientHost string) ([]container.Summary, error) {
|
||||
dockerClient, err := NewClient(clientHost)
|
||||
func ListContainers(ctx context.Context, dockerCfg types.DockerProviderConfig) ([]container.Summary, error) {
|
||||
dockerClient, err := NewClient(dockerCfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -20,14 +20,14 @@ type DockerProvider struct {
|
||||
|
||||
var startOptions = client.ContainerStartOptions{}
|
||||
|
||||
func NewDockerProvider(dockerHost, containerID string) (idlewatcher.Provider, error) {
|
||||
client, err := docker.NewClient(dockerHost)
|
||||
func NewDockerProvider(dockerCfg types.DockerProviderConfig, containerID string) (idlewatcher.Provider, error) {
|
||||
client, err := docker.NewClient(dockerCfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &DockerProvider{
|
||||
client: client,
|
||||
watcher: watcher.NewDockerWatcher(dockerHost),
|
||||
watcher: watcher.NewDockerWatcher(dockerCfg),
|
||||
containerID: containerID,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -209,7 +209,7 @@ func NewWatcher(parent task.Parent, r types.Route, cfg *types.IdlewatcherConfig)
|
||||
depCont := depRoute.ContainerInfo()
|
||||
if depCont != nil {
|
||||
depCfg.Docker = &types.DockerConfig{
|
||||
DockerHost: depCont.DockerHost,
|
||||
DockerCfg: depCont.DockerCfg,
|
||||
ContainerID: depCont.ContainerID,
|
||||
ContainerName: depCont.ContainerName,
|
||||
}
|
||||
@@ -256,7 +256,7 @@ func NewWatcher(parent task.Parent, r types.Route, cfg *types.IdlewatcherConfig)
|
||||
var kind string
|
||||
switch {
|
||||
case cfg.Docker != nil:
|
||||
p, err = provider.NewDockerProvider(cfg.Docker.DockerHost, cfg.Docker.ContainerID)
|
||||
p, err = provider.NewDockerProvider(cfg.Docker.DockerCfg, cfg.Docker.ContainerID)
|
||||
kind = "docker"
|
||||
default:
|
||||
p, err = provider.NewProxmoxProvider(cfg.Proxmox.Node, cfg.Proxmox.VMID)
|
||||
|
||||
@@ -18,8 +18,9 @@ import (
|
||||
)
|
||||
|
||||
type DockerProvider struct {
|
||||
name, dockerHost string
|
||||
l zerolog.Logger
|
||||
name string
|
||||
dockerCfg types.DockerProviderConfig
|
||||
l zerolog.Logger
|
||||
}
|
||||
|
||||
const (
|
||||
@@ -29,10 +30,10 @@ const (
|
||||
|
||||
var ErrAliasRefIndexOutOfRange = gperr.New("index out of range")
|
||||
|
||||
func DockerProviderImpl(name, dockerHost string) ProviderImpl {
|
||||
func DockerProviderImpl(name string, dockerCfg types.DockerProviderConfig) ProviderImpl {
|
||||
return &DockerProvider{
|
||||
name,
|
||||
dockerHost,
|
||||
dockerCfg,
|
||||
log.With().Str("type", "docker").Str("name", name).Logger(),
|
||||
}
|
||||
}
|
||||
@@ -54,14 +55,14 @@ func (p *DockerProvider) Logger() *zerolog.Logger {
|
||||
}
|
||||
|
||||
func (p *DockerProvider) NewWatcher() watcher.Watcher {
|
||||
return watcher.NewDockerWatcher(p.dockerHost)
|
||||
return watcher.NewDockerWatcher(p.dockerCfg)
|
||||
}
|
||||
|
||||
func (p *DockerProvider) loadRoutesImpl() (route.Routes, gperr.Error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
containers, err := docker.ListContainers(ctx, p.dockerHost)
|
||||
containers, err := docker.ListContainers(ctx, p.dockerCfg)
|
||||
if err != nil {
|
||||
return nil, gperr.Wrap(err)
|
||||
}
|
||||
@@ -70,7 +71,7 @@ func (p *DockerProvider) loadRoutesImpl() (route.Routes, gperr.Error) {
|
||||
routes := make(route.Routes)
|
||||
|
||||
for _, c := range containers {
|
||||
container := docker.FromDocker(&c, p.dockerHost)
|
||||
container := docker.FromDocker(&c, p.dockerCfg)
|
||||
|
||||
if container.Errors != nil {
|
||||
errs.Add(container.Errors)
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"github.com/goccy/go-yaml"
|
||||
"github.com/moby/moby/api/types/container"
|
||||
"github.com/yusing/godoxy/internal/docker"
|
||||
"github.com/yusing/godoxy/internal/types"
|
||||
expect "github.com/yusing/goutils/testing"
|
||||
|
||||
_ "embed"
|
||||
@@ -28,7 +29,7 @@ func TestParseDockerLabels(t *testing.T) {
|
||||
Ports: []container.PortSummary{
|
||||
{Type: "tcp", PrivatePort: 1234, PublicPort: 1234},
|
||||
},
|
||||
}, "/var/run/docker.sock"),
|
||||
}, types.DockerProviderConfig{URL: "unix:///var/run/docker.sock"}),
|
||||
)
|
||||
expect.NoError(t, err)
|
||||
expect.True(t, routes.Contains("app"))
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
D "github.com/yusing/godoxy/internal/docker"
|
||||
"github.com/yusing/godoxy/internal/route"
|
||||
routeTypes "github.com/yusing/godoxy/internal/route/types"
|
||||
"github.com/yusing/godoxy/internal/types"
|
||||
expect "github.com/yusing/goutils/testing"
|
||||
)
|
||||
|
||||
@@ -31,7 +32,7 @@ func makeRoutes(cont *container.Summary, dockerHostIP ...string) route.Routes {
|
||||
}
|
||||
cont.ID = "test"
|
||||
p.name = "test"
|
||||
routes := expect.Must(p.routesFromContainerLabels(D.FromDocker(cont, host)))
|
||||
routes := expect.Must(p.routesFromContainerLabels(D.FromDocker(cont, types.DockerProviderConfig{URL: host})))
|
||||
for _, r := range routes {
|
||||
r.Finalize()
|
||||
}
|
||||
@@ -39,7 +40,7 @@ func makeRoutes(cont *container.Summary, dockerHostIP ...string) route.Routes {
|
||||
}
|
||||
|
||||
func TestExplicitOnly(t *testing.T) {
|
||||
p := NewDockerProvider("a!", "")
|
||||
p := NewDockerProvider("a!", types.DockerProviderConfig{})
|
||||
expect.True(t, p.IsExplicitOnly())
|
||||
}
|
||||
|
||||
@@ -199,7 +200,7 @@ func TestApplyLabelWithRefIndexError(t *testing.T) {
|
||||
"proxy.*.port": "4444",
|
||||
"proxy.#4.scheme": "https",
|
||||
},
|
||||
}, "")
|
||||
}, types.DockerProviderConfig{})
|
||||
var p DockerProvider
|
||||
_, err := p.routesFromContainerLabels(c)
|
||||
expect.ErrorIs(t, ErrAliasRefIndexOutOfRange, err)
|
||||
@@ -211,7 +212,7 @@ func TestApplyLabelWithRefIndexError(t *testing.T) {
|
||||
D.LabelAliases: "a,b",
|
||||
"proxy.#0.host": "localhost",
|
||||
},
|
||||
}, "")
|
||||
}, types.DockerProviderConfig{})
|
||||
_, err = p.routesFromContainerLabels(c)
|
||||
expect.ErrorIs(t, ErrAliasRefIndexOutOfRange, err)
|
||||
}
|
||||
|
||||
@@ -69,13 +69,13 @@ func NewFileProvider(filename string) (p *Provider, err error) {
|
||||
return p, err
|
||||
}
|
||||
|
||||
func NewDockerProvider(name string, dockerHost string) *Provider {
|
||||
if dockerHost == common.DockerHostFromEnv {
|
||||
dockerHost = env.GetEnvString("DOCKER_HOST", client.DefaultDockerHost)
|
||||
func NewDockerProvider(name string, dockerCfg types.DockerProviderConfig) *Provider {
|
||||
if dockerCfg.URL == common.DockerHostFromEnv {
|
||||
dockerCfg.URL = env.GetEnvString("DOCKER_HOST", client.DefaultDockerHost)
|
||||
}
|
||||
|
||||
p := newProvider(provider.ProviderTypeDocker)
|
||||
p.ProviderImpl = DockerProviderImpl(name, dockerHost)
|
||||
p.ProviderImpl = DockerProviderImpl(name, dockerCfg)
|
||||
p.watcher = p.NewWatcher()
|
||||
return p
|
||||
}
|
||||
@@ -84,7 +84,9 @@ func NewAgentProvider(cfg *agent.AgentConfig) *Provider {
|
||||
p := newProvider(provider.ProviderTypeAgent)
|
||||
agent := &AgentProvider{
|
||||
AgentConfig: cfg,
|
||||
docker: DockerProviderImpl(cfg.Name, cfg.FakeDockerHost()),
|
||||
docker: DockerProviderImpl(cfg.Name, types.DockerProviderConfig{
|
||||
URL: cfg.FakeDockerHost(),
|
||||
}),
|
||||
}
|
||||
p.ProviderImpl = agent
|
||||
p.watcher = p.NewWatcher()
|
||||
|
||||
@@ -390,7 +390,7 @@ func (r *Route) start(parent task.Parent) gperr.Error {
|
||||
}
|
||||
|
||||
if cont := r.ContainerInfo(); cont != nil {
|
||||
docker.SetDockerHostByContainerID(cont.ContainerID, cont.DockerHost)
|
||||
docker.SetDockerCfgByContainerID(cont.ContainerID, cont.DockerCfg)
|
||||
}
|
||||
|
||||
if !excluded {
|
||||
@@ -405,7 +405,7 @@ func (r *Route) start(parent task.Parent) gperr.Error {
|
||||
|
||||
func (r *Route) Finish(reason any) {
|
||||
if cont := r.ContainerInfo(); cont != nil {
|
||||
docker.DeleteDockerHostByContainerID(cont.ContainerID)
|
||||
docker.DeleteDockerCfgByContainerID(cont.ContainerID)
|
||||
}
|
||||
r.FinishAndWait(reason)
|
||||
}
|
||||
|
||||
@@ -13,10 +13,10 @@ type (
|
||||
|
||||
PortMapping = map[int]container.PortSummary
|
||||
Container struct {
|
||||
DockerHost string `json:"docker_host"`
|
||||
Image *ContainerImage `json:"image"`
|
||||
ContainerName string `json:"container_name"`
|
||||
ContainerID string `json:"container_id"`
|
||||
DockerCfg DockerProviderConfig `json:"docker_cfg"`
|
||||
Image *ContainerImage `json:"image"`
|
||||
ContainerName string `json:"container_name"`
|
||||
ContainerID string `json:"container_id"`
|
||||
|
||||
State container.ContainerState `json:"state"`
|
||||
|
||||
|
||||
82
internal/types/docker_provider_config.go
Normal file
82
internal/types/docker_provider_config.go
Normal file
@@ -0,0 +1,82 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/url"
|
||||
"os"
|
||||
"strconv"
|
||||
|
||||
"github.com/yusing/godoxy/internal/common"
|
||||
"github.com/yusing/godoxy/internal/serialization"
|
||||
gperr "github.com/yusing/goutils/errs"
|
||||
)
|
||||
|
||||
type DockerProviderConfig struct {
|
||||
URL string `json:"url,omitempty"`
|
||||
TLS *DockerTLSConfig `json:"tls,omitempty"`
|
||||
} // @name DockerProviderConfig
|
||||
|
||||
type DockerProviderConfigDetailed struct {
|
||||
Scheme string `json:"scheme,omitempty" validate:"required,oneof=http https tls"`
|
||||
Host string `json:"host,omitempty" validate:"required,hostname|ip"`
|
||||
Port int `json:"port,omitempty" validate:"required,min=1,max=65535"`
|
||||
TLS *DockerTLSConfig `json:"tls" validate:"omitempty"`
|
||||
}
|
||||
|
||||
type DockerTLSConfig struct {
|
||||
CAFile string `json:"ca_file,omitempty" validate:"required"`
|
||||
CertFile string `json:"cert_file,omitempty" validate:"required"`
|
||||
KeyFile string `json:"key_file,omitempty" validate:"required"`
|
||||
} // @name DockerTLSConfig
|
||||
|
||||
func (cfg *DockerProviderConfig) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(cfg.URL)
|
||||
}
|
||||
|
||||
func (cfg *DockerProviderConfig) Parse(value string) error {
|
||||
u, err := url.Parse(value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch u.Scheme {
|
||||
case "http", "https", "tls":
|
||||
default:
|
||||
return fmt.Errorf("invalid scheme: %s", u.Scheme)
|
||||
}
|
||||
|
||||
cfg.URL = u.String()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cfg *DockerProviderConfig) UnmarshalMap(m map[string]any) gperr.Error {
|
||||
var tmp DockerProviderConfigDetailed
|
||||
var err = serialization.MapUnmarshalValidate(m, &tmp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cfg.URL = fmt.Sprintf("%s://%s", tmp.Scheme, net.JoinHostPort(tmp.Host, strconv.Itoa(tmp.Port)))
|
||||
cfg.TLS = tmp.TLS
|
||||
if cfg.TLS != nil {
|
||||
if err := checkFilesOk(cfg.TLS.CAFile, cfg.TLS.CertFile, cfg.TLS.KeyFile); err != nil {
|
||||
return gperr.Wrap(err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func checkFilesOk(files ...string) error {
|
||||
if common.IsTest {
|
||||
return nil
|
||||
}
|
||||
var errs gperr.Builder
|
||||
for _, file := range files {
|
||||
if _, err := os.Stat(file); err != nil {
|
||||
errs.Add(err)
|
||||
}
|
||||
}
|
||||
return errs.Error()
|
||||
}
|
||||
122
internal/types/docker_provider_config_test.go
Normal file
122
internal/types/docker_provider_config_test.go
Normal file
@@ -0,0 +1,122 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/yusing/godoxy/internal/serialization"
|
||||
)
|
||||
|
||||
func TestDockerProviderConfigUnmarshalMap(t *testing.T) {
|
||||
t.Run("string", func(t *testing.T) {
|
||||
var cfg map[string]*DockerProviderConfig
|
||||
err := serialization.UnmarshalValidateYAML([]byte("test: http://localhost:2375"), &cfg)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, &DockerProviderConfig{URL: "http://localhost:2375"}, cfg["test"])
|
||||
})
|
||||
|
||||
t.Run("detailed", func(t *testing.T) {
|
||||
var cfg map[string]*DockerProviderConfig
|
||||
err := serialization.UnmarshalValidateYAML([]byte(`
|
||||
test:
|
||||
scheme: http
|
||||
host: localhost
|
||||
port: 2375
|
||||
tls:
|
||||
ca_file: /etc/ssl/ca.crt
|
||||
cert_file: /etc/ssl/cert.crt
|
||||
key_file: /etc/ssl/key.crt`), &cfg)
|
||||
assert.Error(t, err, os.ErrNotExist)
|
||||
assert.Equal(t, &DockerProviderConfig{URL: "http://localhost:2375", TLS: &DockerTLSConfig{CAFile: "/etc/ssl/ca.crt", CertFile: "/etc/ssl/cert.crt", KeyFile: "/etc/ssl/key.crt"}}, cfg["test"])
|
||||
})
|
||||
}
|
||||
|
||||
func TestDockerProviderConfigValidation(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
yamlStr string
|
||||
wantErr bool
|
||||
}{
|
||||
{name: "valid url", yamlStr: "test: http://localhost:2375", wantErr: false},
|
||||
{name: "invalid url", yamlStr: "test: ftp://localhost/2375", wantErr: true},
|
||||
{name: "valid scheme", yamlStr: `
|
||||
test:
|
||||
scheme: http
|
||||
host: localhost
|
||||
port: 2375
|
||||
`, wantErr: false},
|
||||
{name: "invalid scheme", yamlStr: `
|
||||
test:
|
||||
scheme: invalid
|
||||
host: localhost
|
||||
port: 2375
|
||||
`, wantErr: true},
|
||||
{name: "valid host (ipv4)", yamlStr: `
|
||||
test:
|
||||
scheme: http
|
||||
host: 127.0.0.1
|
||||
port: 2375
|
||||
`, wantErr: false},
|
||||
{name: "valid host (ipv6)", yamlStr: `
|
||||
test:
|
||||
scheme: http
|
||||
host: ::1
|
||||
port: 2375
|
||||
`, wantErr: false},
|
||||
{name: "valid host (hostname)", yamlStr: `
|
||||
test:
|
||||
scheme: http
|
||||
host: example.com
|
||||
port: 2375
|
||||
`, wantErr: false},
|
||||
{name: "invalid host", yamlStr: `
|
||||
test:
|
||||
scheme: http
|
||||
host: invalid:1234
|
||||
port: 2375
|
||||
`, wantErr: true},
|
||||
{name: "valid port", yamlStr: `
|
||||
test:
|
||||
scheme: http
|
||||
host: localhost
|
||||
port: 2375
|
||||
`, wantErr: false},
|
||||
{name: "invalid port", yamlStr: `
|
||||
test:
|
||||
scheme: http
|
||||
host: localhost
|
||||
port: 65536
|
||||
`, wantErr: true},
|
||||
{name: "valid tls", yamlStr: `
|
||||
test:
|
||||
scheme: tls
|
||||
host: localhost
|
||||
port: 2375
|
||||
tls:
|
||||
ca_file: /etc/ssl/ca.crt
|
||||
cert_file: /etc/ssl/cert.crt
|
||||
key_file: /etc/ssl/key.crt
|
||||
`, wantErr: false},
|
||||
{name: "invalid tls (missing cert file and key file)", yamlStr: `
|
||||
test:
|
||||
scheme: tls
|
||||
host: localhost
|
||||
port: 2375
|
||||
tls:
|
||||
ca_file: /etc/ssl/ca.crt
|
||||
`, wantErr: true},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
var cfg map[string]*DockerProviderConfig
|
||||
err := serialization.UnmarshalValidateYAML([]byte(test.yamlStr), &cfg)
|
||||
if test.wantErr {
|
||||
assert.Error(t, err)
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -38,9 +38,9 @@ type (
|
||||
ContainerSignal string // @name ContainerSignal
|
||||
|
||||
DockerConfig struct {
|
||||
DockerHost string `json:"docker_host" validate:"required"`
|
||||
ContainerID string `json:"container_id" validate:"required"`
|
||||
ContainerName string `json:"container_name" validate:"required"`
|
||||
DockerCfg DockerProviderConfig `json:"docker_cfg" validate:"required"`
|
||||
ContainerID string `json:"container_id" validate:"required"`
|
||||
ContainerName string `json:"container_name" validate:"required"`
|
||||
} // @name DockerConfig
|
||||
ProxmoxConfig struct {
|
||||
Node string `json:"node" validate:"required"`
|
||||
|
||||
@@ -9,12 +9,15 @@ import (
|
||||
"github.com/moby/moby/client"
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/yusing/godoxy/internal/docker"
|
||||
"github.com/yusing/godoxy/internal/types"
|
||||
"github.com/yusing/godoxy/internal/watcher/events"
|
||||
gperr "github.com/yusing/goutils/errs"
|
||||
)
|
||||
|
||||
type (
|
||||
DockerWatcher string
|
||||
DockerWatcher struct {
|
||||
cfg types.DockerProviderConfig
|
||||
}
|
||||
DockerListOptions = client.EventsListOptions
|
||||
DockerFilters = client.Filters
|
||||
)
|
||||
@@ -73,8 +76,10 @@ func DockerFilterContainerNameID(nameOrID string) DockerFilter {
|
||||
return NewDockerFilter("container", nameOrID)
|
||||
}
|
||||
|
||||
func NewDockerWatcher(host string) DockerWatcher {
|
||||
return DockerWatcher(host)
|
||||
func NewDockerWatcher(dockerCfg types.DockerProviderConfig) DockerWatcher {
|
||||
return DockerWatcher{
|
||||
cfg: dockerCfg,
|
||||
}
|
||||
}
|
||||
|
||||
func (w DockerWatcher) Events(ctx context.Context) (<-chan Event, <-chan gperr.Error) {
|
||||
@@ -86,7 +91,7 @@ func (w DockerWatcher) EventsWithOptions(ctx context.Context, options DockerList
|
||||
errCh := make(chan gperr.Error)
|
||||
|
||||
go func() {
|
||||
client, err := docker.NewClient(string(w))
|
||||
client, err := docker.NewClient(w.cfg)
|
||||
if err != nil {
|
||||
errCh <- gperr.Wrap(err, "docker watcher: failed to initialize client")
|
||||
return
|
||||
|
||||
@@ -61,7 +61,7 @@ func NewMonitor(r types.Route) types.HealthMonCheck {
|
||||
}
|
||||
if r.IsDocker() {
|
||||
cont := r.ContainerInfo()
|
||||
client, err := docker.NewClient(cont.DockerHost)
|
||||
client, err := docker.NewClient(cont.DockerCfg)
|
||||
if err != nil {
|
||||
return mon
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user