mirror of
				https://github.com/traefik/traefik.git
				synced 2025-11-04 10:21:15 +01:00 
			
		
		
		
	
		
			
				
	
	
		
			212 lines
		
	
	
		
			7.2 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			212 lines
		
	
	
		
			7.2 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
package docker
 | 
						|
 | 
						|
import (
 | 
						|
	"context"
 | 
						|
	"fmt"
 | 
						|
	"net/http"
 | 
						|
	"text/template"
 | 
						|
	"time"
 | 
						|
 | 
						|
	"github.com/docker/cli/cli/connhelper"
 | 
						|
	dockertypes "github.com/docker/docker/api/types"
 | 
						|
	"github.com/docker/docker/client"
 | 
						|
	"github.com/docker/go-connections/nat"
 | 
						|
	"github.com/docker/go-connections/sockets"
 | 
						|
	"github.com/rs/zerolog/log"
 | 
						|
	ptypes "github.com/traefik/paerser/types"
 | 
						|
	"github.com/traefik/traefik/v3/pkg/provider"
 | 
						|
	"github.com/traefik/traefik/v3/pkg/types"
 | 
						|
	"github.com/traefik/traefik/v3/pkg/version"
 | 
						|
)
 | 
						|
 | 
						|
// DefaultTemplateRule The default template for the default rule.
 | 
						|
const DefaultTemplateRule = "Host(`{{ normalize .Name }}`)"
 | 
						|
 | 
						|
type Shared struct {
 | 
						|
	ExposedByDefault   bool   `description:"Expose containers by default." json:"exposedByDefault,omitempty" toml:"exposedByDefault,omitempty" yaml:"exposedByDefault,omitempty" export:"true"`
 | 
						|
	Constraints        string `description:"Constraints is an expression that Traefik matches against the container's labels to determine whether to create any route for that container." json:"constraints,omitempty" toml:"constraints,omitempty" yaml:"constraints,omitempty" export:"true"`
 | 
						|
	AllowEmptyServices bool   `description:"Disregards the Docker containers health checks with respect to the creation or removal of the corresponding services." json:"allowEmptyServices,omitempty" toml:"allowEmptyServices,omitempty" yaml:"allowEmptyServices,omitempty" export:"true"`
 | 
						|
	Network            string `description:"Default Docker network used." json:"network,omitempty" toml:"network,omitempty" yaml:"network,omitempty" export:"true"`
 | 
						|
	UseBindPortIP      bool   `description:"Use the ip address from the bound port, rather than from the inner network." json:"useBindPortIP,omitempty" toml:"useBindPortIP,omitempty" yaml:"useBindPortIP,omitempty" export:"true"`
 | 
						|
 | 
						|
	Watch       bool   `description:"Watch Docker events." json:"watch,omitempty" toml:"watch,omitempty" yaml:"watch,omitempty" export:"true"`
 | 
						|
	DefaultRule string `description:"Default rule." json:"defaultRule,omitempty" toml:"defaultRule,omitempty" yaml:"defaultRule,omitempty"`
 | 
						|
 | 
						|
	defaultRuleTpl *template.Template
 | 
						|
}
 | 
						|
 | 
						|
func inspectContainers(ctx context.Context, dockerClient client.ContainerAPIClient, containerID string) dockerData {
 | 
						|
	containerInspected, err := dockerClient.ContainerInspect(ctx, containerID)
 | 
						|
	if err != nil {
 | 
						|
		log.Ctx(ctx).Warn().Err(err).Msgf("Failed to inspect container %s", containerID)
 | 
						|
		return dockerData{}
 | 
						|
	}
 | 
						|
 | 
						|
	// This condition is here to avoid to have empty IP https://github.com/traefik/traefik/issues/2459
 | 
						|
	// We register only container which are running
 | 
						|
	if containerInspected.ContainerJSONBase != nil && containerInspected.ContainerJSONBase.State != nil && containerInspected.ContainerJSONBase.State.Running {
 | 
						|
		return parseContainer(containerInspected)
 | 
						|
	}
 | 
						|
 | 
						|
	return dockerData{}
 | 
						|
}
 | 
						|
 | 
						|
func parseContainer(container dockertypes.ContainerJSON) dockerData {
 | 
						|
	dData := dockerData{
 | 
						|
		NetworkSettings: networkSettings{},
 | 
						|
	}
 | 
						|
 | 
						|
	if container.ContainerJSONBase != nil {
 | 
						|
		dData.ID = container.ContainerJSONBase.ID
 | 
						|
		dData.Name = container.ContainerJSONBase.Name
 | 
						|
		dData.ServiceName = dData.Name // Default ServiceName to be the container's Name.
 | 
						|
		dData.Node = container.ContainerJSONBase.Node
 | 
						|
 | 
						|
		if container.ContainerJSONBase.HostConfig != nil {
 | 
						|
			dData.NetworkSettings.NetworkMode = container.ContainerJSONBase.HostConfig.NetworkMode
 | 
						|
		}
 | 
						|
 | 
						|
		if container.State != nil && container.State.Health != nil {
 | 
						|
			dData.Health = container.State.Health.Status
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	if container.Config != nil && container.Config.Labels != nil {
 | 
						|
		dData.Labels = container.Config.Labels
 | 
						|
	}
 | 
						|
 | 
						|
	if container.NetworkSettings != nil {
 | 
						|
		if container.NetworkSettings.Ports != nil {
 | 
						|
			dData.NetworkSettings.Ports = container.NetworkSettings.Ports
 | 
						|
		}
 | 
						|
		if container.NetworkSettings.Networks != nil {
 | 
						|
			dData.NetworkSettings.Networks = make(map[string]*networkData)
 | 
						|
			for name, containerNetwork := range container.NetworkSettings.Networks {
 | 
						|
				addr := containerNetwork.IPAddress
 | 
						|
				if addr == "" {
 | 
						|
					addr = containerNetwork.GlobalIPv6Address
 | 
						|
				}
 | 
						|
 | 
						|
				dData.NetworkSettings.Networks[name] = &networkData{
 | 
						|
					ID:   containerNetwork.NetworkID,
 | 
						|
					Name: name,
 | 
						|
					Addr: addr,
 | 
						|
				}
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return dData
 | 
						|
}
 | 
						|
 | 
						|
type ClientConfig struct {
 | 
						|
	apiVersion string
 | 
						|
 | 
						|
	Endpoint          string           `description:"Docker server endpoint. Can be a TCP or a Unix socket endpoint." json:"endpoint,omitempty" toml:"endpoint,omitempty" yaml:"endpoint,omitempty"`
 | 
						|
	TLS               *types.ClientTLS `description:"Enable Docker TLS support." json:"tls,omitempty" toml:"tls,omitempty" yaml:"tls,omitempty" export:"true"`
 | 
						|
	HTTPClientTimeout ptypes.Duration  `description:"Client timeout for HTTP connections." json:"httpClientTimeout,omitempty" toml:"httpClientTimeout,omitempty" yaml:"httpClientTimeout,omitempty" export:"true"`
 | 
						|
}
 | 
						|
 | 
						|
func createClient(ctx context.Context, cfg ClientConfig) (*client.Client, error) {
 | 
						|
	opts, err := getClientOpts(ctx, cfg)
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
 | 
						|
	httpHeaders := map[string]string{
 | 
						|
		"User-Agent": "Traefik " + version.Version,
 | 
						|
	}
 | 
						|
 | 
						|
	opts = append(opts,
 | 
						|
		client.WithHTTPHeaders(httpHeaders),
 | 
						|
		client.WithVersion(cfg.apiVersion))
 | 
						|
 | 
						|
	return client.NewClientWithOpts(opts...)
 | 
						|
}
 | 
						|
 | 
						|
func getClientOpts(ctx context.Context, cfg ClientConfig) ([]client.Opt, error) {
 | 
						|
	helper, err := connhelper.GetConnectionHelper(cfg.Endpoint)
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
 | 
						|
	// SSH
 | 
						|
	if helper != nil {
 | 
						|
		// https://github.com/docker/cli/blob/ebca1413117a3fcb81c89d6be226dcec74e5289f/cli/context/docker/load.go#L112-L123
 | 
						|
 | 
						|
		httpClient := &http.Client{
 | 
						|
			Transport: &http.Transport{
 | 
						|
				DialContext: helper.Dialer,
 | 
						|
			},
 | 
						|
		}
 | 
						|
 | 
						|
		return []client.Opt{
 | 
						|
			client.WithHTTPClient(httpClient),
 | 
						|
			client.WithTimeout(time.Duration(cfg.HTTPClientTimeout)),
 | 
						|
			client.WithHost(helper.Host), // To avoid 400 Bad Request: malformed Host header daemon error
 | 
						|
			client.WithDialContext(helper.Dialer),
 | 
						|
		}, nil
 | 
						|
	}
 | 
						|
 | 
						|
	opts := []client.Opt{
 | 
						|
		client.WithHost(cfg.Endpoint),
 | 
						|
		client.WithTimeout(time.Duration(cfg.HTTPClientTimeout)),
 | 
						|
	}
 | 
						|
 | 
						|
	if cfg.TLS != nil {
 | 
						|
		conf, err := cfg.TLS.CreateTLSConfig(ctx)
 | 
						|
		if err != nil {
 | 
						|
			return nil, fmt.Errorf("unable to create client TLS configuration: %w", err)
 | 
						|
		}
 | 
						|
 | 
						|
		hostURL, err := client.ParseHostURL(cfg.Endpoint)
 | 
						|
		if err != nil {
 | 
						|
			return nil, err
 | 
						|
		}
 | 
						|
 | 
						|
		tr := &http.Transport{
 | 
						|
			TLSClientConfig: conf,
 | 
						|
		}
 | 
						|
 | 
						|
		if err := sockets.ConfigureTransport(tr, hostURL.Scheme, hostURL.Host); err != nil {
 | 
						|
			return nil, err
 | 
						|
		}
 | 
						|
 | 
						|
		opts = append(opts, client.WithHTTPClient(&http.Client{Transport: tr, Timeout: time.Duration(cfg.HTTPClientTimeout)}))
 | 
						|
	}
 | 
						|
 | 
						|
	return opts, nil
 | 
						|
}
 | 
						|
 | 
						|
func getPort(container dockerData, serverPort string) string {
 | 
						|
	if len(serverPort) > 0 {
 | 
						|
		return serverPort
 | 
						|
	}
 | 
						|
 | 
						|
	var ports []nat.Port
 | 
						|
	for port := range container.NetworkSettings.Ports {
 | 
						|
		ports = append(ports, port)
 | 
						|
	}
 | 
						|
 | 
						|
	less := func(i, j nat.Port) bool {
 | 
						|
		return i.Int() < j.Int()
 | 
						|
	}
 | 
						|
	nat.Sort(ports, less)
 | 
						|
 | 
						|
	if len(ports) > 0 {
 | 
						|
		min := ports[0]
 | 
						|
		return min.Port()
 | 
						|
	}
 | 
						|
 | 
						|
	return ""
 | 
						|
}
 | 
						|
 | 
						|
func getServiceName(container dockerData) string {
 | 
						|
	serviceName := container.ServiceName
 | 
						|
 | 
						|
	if values, err := getStringMultipleStrict(container.Labels, labelDockerComposeProject, labelDockerComposeService); err == nil {
 | 
						|
		serviceName = values[labelDockerComposeService] + "_" + values[labelDockerComposeProject]
 | 
						|
	}
 | 
						|
 | 
						|
	return provider.Normalize(serviceName)
 | 
						|
}
 |