docker-gen/internal/context/context.go
2021-09-16 07:51:24 +02:00

243 lines
5.4 KiB
Go

package context
import (
"bufio"
"fmt"
"os"
"regexp"
"sync"
docker "github.com/fsouza/go-dockerclient"
"github.com/nginx-proxy/docker-gen/internal/utils"
)
var (
mu sync.RWMutex
dockerInfo Docker
dockerEnv *docker.Env
)
type Context []*RuntimeContainer
func (c *Context) Env() map[string]string {
return utils.SplitKeyValueSlice(os.Environ())
}
func (c *Context) Docker() Docker {
mu.RLock()
defer mu.RUnlock()
return dockerInfo
}
func SetServerInfo(d *docker.DockerInfo) {
mu.Lock()
defer mu.Unlock()
dockerInfo = Docker{
Name: d.Name,
NumContainers: d.Containers,
NumImages: d.Images,
Version: dockerEnv.Get("Version"),
ApiVersion: dockerEnv.Get("ApiVersion"),
GoVersion: dockerEnv.Get("GoVersion"),
OperatingSystem: dockerEnv.Get("Os"),
Architecture: dockerEnv.Get("Arch"),
CurrentContainerID: GetCurrentContainerID(),
}
}
func SetDockerEnv(d *docker.Env) {
mu.Lock()
defer mu.Unlock()
dockerEnv = d
}
type Address struct {
IP string
IP6LinkLocal string
IP6Global string
Port string
HostPort string
Proto string
HostIP string
}
type Network struct {
IP string
Name string
Gateway string
EndpointID string
IPv6Gateway string
GlobalIPv6Address string
MacAddress string
GlobalIPv6PrefixLen int
IPPrefixLen int
}
type Volume struct {
Path string
HostPath string
ReadWrite bool
}
type State struct {
Running bool
}
type RuntimeContainer struct {
ID string
Addresses []Address
Networks []Network
Gateway string
Name string
Hostname string
Image DockerImage
Env map[string]string
Volumes map[string]Volume
Node SwarmNode
Labels map[string]string
IP string
IP6LinkLocal string
IP6Global string
Mounts []Mount
State State
}
func (r *RuntimeContainer) Equals(o RuntimeContainer) bool {
return r.ID == o.ID && r.Image == o.Image
}
func (r *RuntimeContainer) PublishedAddresses() []Address {
mapped := []Address{}
for _, address := range r.Addresses {
if address.HostPort != "" {
mapped = append(mapped, address)
}
}
return mapped
}
type DockerImage struct {
Registry string
Repository string
Tag string
}
func (i *DockerImage) String() string {
ret := i.Repository
if i.Registry != "" {
ret = i.Registry + "/" + i.Repository
}
if i.Tag != "" {
ret = ret + ":" + i.Tag
}
return ret
}
type SwarmNode struct {
ID string
Name string
Address Address
}
type Mount struct {
Name string
Source string
Destination string
Driver string
Mode string
RW bool
}
type Docker struct {
Name string
NumContainers int
NumImages int
Version string
ApiVersion string
GoVersion string
OperatingSystem string
Architecture string
CurrentContainerID 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.
func GetCurrentContainerID(filepaths ...string) (id string) {
if len(filepaths) == 0 {
filepaths = []string{"/proc/1/cpuset", "/proc/self/cgroup", "/proc/self/mountinfo"}
}
// We try to match a 64 character hex string starting with the hostname first
for _, filepath := range filepaths {
file, err := os.Open(filepath)
if err != nil {
continue
}
defer file.Close()
scanner := bufio.NewScanner(file)
scanner.Split(bufio.ScanLines)
for scanner.Scan() {
_, lines, err := bufio.ScanLines([]byte(scanner.Text()), true)
if err == nil {
strLines := string(lines)
if id = matchContainerIDWithHostname(strLines); len(id) == 64 {
return
}
}
}
}
// If we didn't get any ID that matches the hostname, fall back to matching the first 64 character hex string
for _, filepath := range filepaths {
file, err := os.Open(filepath)
if err != nil {
continue
}
defer file.Close()
scanner := bufio.NewScanner(file)
scanner.Split(bufio.ScanLines)
for scanner.Scan() {
_, lines, err := bufio.ScanLines([]byte(scanner.Text()), true)
if err == nil {
strLines := string(lines)
if id = matchContainerID("([[:alnum:]]{64})", strLines); len(id) == 64 {
return
}
}
}
}
return
}
func matchContainerIDWithHostname(lines string) string {
hostname := os.Getenv("HOSTNAME")
re := regexp.MustCompilePOSIX("^[[:alnum:]]{12}$")
if re.MatchString(hostname) {
regex := fmt.Sprintf("(%s[[:alnum:]]{52})", hostname)
return matchContainerID(regex, lines)
}
return ""
}
func matchContainerID(regex, lines string) string {
// Attempt to detect if we're on a line from a /proc/<pid>/mountinfo file and modify the regexp accordingly
// https://www.kernel.org/doc/Documentation/filesystems/proc.txt section 3.5
re := regexp.MustCompilePOSIX("^[0-9]+ [0-9]+ [0-9]+:[0-9]+ /")
if re.MatchString(lines) {
regex = fmt.Sprintf("containers/%v", regex)
}
re = regexp.MustCompilePOSIX(regex)
if re.MatchString(lines) {
submatches := re.FindStringSubmatch(string(lines))
containerID := submatches[1]
return containerID
}
return ""
}