Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -370,6 +370,8 @@ type Docker struct {
// Host environment variables accessible from root in templates as .Env
```

The root also exposes `.CurrentContainer`, the `RuntimeContainer` of the docker-gen container itself (or `nil` if it cannot be determined). Like `.Docker`, it is resolved independently from the container list, so it remains available even when `-only-exposed`/`-only-published` would filter the docker-gen container out; depending on filters, it may also be present in the containers the templates iterate over.

For example, this is a JSON version of an emitted RuntimeContainer struct:

```json
Expand Down
13 changes: 13 additions & 0 deletions internal/context/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ var (
mu sync.RWMutex
dockerInfo Docker
dockerEnv *docker.Env
currentContainer *RuntimeContainer
hostnameRegex = regexp.MustCompilePOSIX("^[[:alnum:]]{12}$")
mountinfoPrefixRegex = regexp.MustCompilePOSIX("^[0-9]+ [0-9]+ [0-9]+:[0-9]+ /")
)
Expand All @@ -32,6 +33,12 @@ func (c *Context) Docker() Docker {
return dockerInfo
}

func (c *Context) CurrentContainer() *RuntimeContainer {
mu.RLock()
defer mu.RUnlock()
return currentContainer
}

func SetServerInfo(d *docker.DockerInfo) {
mu.Lock()
defer mu.Unlock()
Expand All @@ -54,6 +61,12 @@ func SetDockerEnv(d *docker.Env) {
dockerEnv = d
}

func SetCurrentContainer(c *RuntimeContainer) {
mu.Lock()
defer mu.Unlock()
currentContainer = c
}

type Network struct {
IP string
Name string
Expand Down
20 changes: 20 additions & 0 deletions internal/context/context_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,26 @@ func TestGetCurrentContainerEmpty(t *testing.T) {
assert.Equal(t, "", GetCurrentContainerID())
}

func TestCurrentContainer(t *testing.T) {
mu.Lock()
saved := currentContainer
mu.Unlock()
t.Cleanup(func() {
mu.Lock()
currentContainer = saved
mu.Unlock()
})

var c Context

rc := &RuntimeContainer{ID: "abc"}
SetCurrentContainer(rc)
assert.Same(t, rc, c.CurrentContainer())

SetCurrentContainer(nil)
assert.Nil(t, c.CurrentContainer())
}

func TestRuntimeContainerEquals(t *testing.T) {
rc1 := &RuntimeContainer{
ID: "baz",
Expand Down
225 changes: 127 additions & 98 deletions internal/generator/generator.go
Original file line number Diff line number Diff line change
Expand Up @@ -396,6 +396,8 @@ func sortNetworks(networks []context.Network) {
})
}

var getCurrentContainerID = context.GetCurrentContainerID

func (g *generator) getContainers(config config.Config) ([]*context.RuntimeContainer, error) {
apiInfo, err := g.Client.Info()
if err != nil {
Expand Down Expand Up @@ -424,122 +426,149 @@ func (g *generator) getContainers(config config.Config) ([]*context.RuntimeConta

containers := []*context.RuntimeContainer{}
for _, apiContainer := range apiContainers {
opts := docker.InspectContainerOptions{ID: apiContainer.ID}
container, err := g.Client.InspectContainerWithOptions(opts)
runtimeContainer, err := g.inspectContainer(apiContainer.ID, networks)
if err != nil {
log.Printf("Error inspecting container: %s: %s\n", apiContainer.ID, err)
continue
}
containers = append(containers, runtimeContainer)
}

// Inspect may return nil pointers for these structs; copy into zero values to avoid a panic.
var containerConfig docker.Config
if container.Config != nil {
containerConfig = *container.Config
}
var containerNetSettings docker.NetworkSettings
if container.NetworkSettings != nil {
containerNetSettings = *container.NetworkSettings
}
var containerHostConfig docker.HostConfig
if container.HostConfig != nil {
containerHostConfig = *container.HostConfig
}
context.SetCurrentContainer(g.currentContainer(containers, networks))
return containers, nil
}

registry, repository, tag := dockerclient.SplitDockerImage(containerConfig.Image)
runtimeContainer := &context.RuntimeContainer{
ID: container.ID,
Created: container.Created,
Image: context.DockerImage{
Registry: registry,
Repository: repository,
Tag: tag,
},
State: context.State{
Running: container.State.Running,
Health: context.Health{
Status: container.State.Health.Status,
},
},
Name: strings.TrimLeft(container.Name, "/"),
Hostname: containerConfig.Hostname,
Gateway: containerNetSettings.Gateway,
NetworkMode: containerHostConfig.NetworkMode,
Addresses: []context.Address{},
Networks: []context.Network{},
Devices: []context.Device{},
Env: make(map[string]string),
Volumes: make(map[string]context.Volume),
Node: context.SwarmNode{},
Labels: make(map[string]string),
IP: containerNetSettings.IPAddress,
IP6LinkLocal: containerNetSettings.LinkLocalIPv6Address,
IP6Global: containerNetSettings.GlobalIPv6Address,
func (g *generator) currentContainer(containers []*context.RuntimeContainer, networks map[string]docker.Network) *context.RuntimeContainer {
currentID := getCurrentContainerID()
if currentID == "" {
return nil
}
for _, c := range containers {
if c.ID == currentID {
return c
}
}
runtimeContainer, err := g.inspectContainer(currentID, networks)
if err != nil {
log.Printf("Error inspecting current container: %s: %s\n", currentID, err)
return nil
}
return runtimeContainer
}

addresses := context.GetContainerAddresses(container)
runtimeContainer.Addresses = append(runtimeContainer.Addresses, addresses...)

for k, v := range containerNetSettings.Networks {
network := context.Network{
IP: v.IPAddress,
Name: k,
Aliases: append([]string{}, v.Aliases...),
Gateway: v.Gateway,
EndpointID: v.EndpointID,
IPv6Gateway: v.IPv6Gateway,
GlobalIPv6Address: v.GlobalIPv6Address,
MacAddress: v.MacAddress,
GlobalIPv6PrefixLen: v.GlobalIPv6PrefixLen,
IPPrefixLen: v.IPPrefixLen,
Internal: networks[k].Internal,
}
func (g *generator) inspectContainer(id string, networks map[string]docker.Network) (*context.RuntimeContainer, error) {
opts := docker.InspectContainerOptions{ID: id}
container, err := g.Client.InspectContainerWithOptions(opts)
if err != nil {
return nil, err
}

runtimeContainer.Networks = append(runtimeContainer.Networks,
network)
}
// Inspect may return nil pointers for these structs; copy into zero values to avoid a panic.
var containerConfig docker.Config
if container.Config != nil {
containerConfig = *container.Config
}
var containerNetSettings docker.NetworkSettings
if container.NetworkSettings != nil {
containerNetSettings = *container.NetworkSettings
}
var containerHostConfig docker.HostConfig
if container.HostConfig != nil {
containerHostConfig = *container.HostConfig
}

sortNetworks(runtimeContainer.Networks)
registry, repository, tag := dockerclient.SplitDockerImage(containerConfig.Image)
runtimeContainer := &context.RuntimeContainer{
ID: container.ID,
Created: container.Created,
Image: context.DockerImage{
Registry: registry,
Repository: repository,
Tag: tag,
},
State: context.State{
Running: container.State.Running,
Health: context.Health{
Status: container.State.Health.Status,
},
},
Name: strings.TrimLeft(container.Name, "/"),
Hostname: containerConfig.Hostname,
Gateway: containerNetSettings.Gateway,
NetworkMode: containerHostConfig.NetworkMode,
Addresses: []context.Address{},
Networks: []context.Network{},
Devices: []context.Device{},
Env: make(map[string]string),
Volumes: make(map[string]context.Volume),
Node: context.SwarmNode{},
Labels: make(map[string]string),
IP: containerNetSettings.IPAddress,
IP6LinkLocal: containerNetSettings.LinkLocalIPv6Address,
IP6Global: containerNetSettings.GlobalIPv6Address,
}

for k, v := range container.Volumes {
runtimeContainer.Volumes[k] = context.Volume{
Path: k,
HostPath: v,
ReadWrite: container.VolumesRW[k],
}
}
if container.Node != nil {
runtimeContainer.Node.ID = container.Node.ID
runtimeContainer.Node.Name = container.Node.Name
runtimeContainer.Node.Address = context.Address{
IP: container.Node.IP,
}
addresses := context.GetContainerAddresses(container)
runtimeContainer.Addresses = append(runtimeContainer.Addresses, addresses...)

for k, v := range containerNetSettings.Networks {
network := context.Network{
IP: v.IPAddress,
Name: k,
Aliases: append([]string{}, v.Aliases...),
Gateway: v.Gateway,
EndpointID: v.EndpointID,
IPv6Gateway: v.IPv6Gateway,
GlobalIPv6Address: v.GlobalIPv6Address,
MacAddress: v.MacAddress,
GlobalIPv6PrefixLen: v.GlobalIPv6PrefixLen,
IPPrefixLen: v.IPPrefixLen,
Internal: networks[k].Internal,
}

for _, v := range container.Mounts {
runtimeContainer.Mounts = append(runtimeContainer.Mounts, context.Mount{
Name: v.Name,
Source: v.Source,
Destination: v.Destination,
Driver: v.Driver,
Mode: v.Mode,
RW: v.RW,
})
}
runtimeContainer.Networks = append(runtimeContainer.Networks,
network)
}

sortNetworks(runtimeContainer.Networks)

for _, v := range containerHostConfig.Devices {
runtimeContainer.Devices = append(runtimeContainer.Devices, context.Device{
PathOnHost: v.PathOnHost,
PathInContainer: v.PathInContainer,
Permissions: v.CgroupPermissions,
})
for k, v := range container.Volumes {
runtimeContainer.Volumes[k] = context.Volume{
Path: k,
HostPath: v,
ReadWrite: container.VolumesRW[k],
}
}
if container.Node != nil {
runtimeContainer.Node.ID = container.Node.ID
runtimeContainer.Node.Name = container.Node.Name
runtimeContainer.Node.Address = context.Address{
IP: container.Node.IP,
}
}

runtimeContainer.Env = utils.SplitKeyValueSlice(containerConfig.Env)
runtimeContainer.Labels = containerConfig.Labels
containers = append(containers, runtimeContainer)
for _, v := range container.Mounts {
runtimeContainer.Mounts = append(runtimeContainer.Mounts, context.Mount{
Name: v.Name,
Source: v.Source,
Destination: v.Destination,
Driver: v.Driver,
Mode: v.Mode,
RW: v.RW,
})
}

for _, v := range containerHostConfig.Devices {
runtimeContainer.Devices = append(runtimeContainer.Devices, context.Device{
PathOnHost: v.PathOnHost,
PathInContainer: v.PathInContainer,
Permissions: v.CgroupPermissions,
})
}
return containers, nil

runtimeContainer.Env = utils.SplitKeyValueSlice(containerConfig.Env)
runtimeContainer.Labels = containerConfig.Labels
return runtimeContainer, nil
}

func newSignalChannel() (<-chan os.Signal, func()) {
Expand Down
56 changes: 56 additions & 0 deletions internal/generator/generator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -248,3 +248,59 @@ func TestGetContainersDevices(t *testing.T) {
{PathOnHost: "/dev/ttyACM0", PathInContainer: "/dev/ttyUSB0", Permissions: "rwm"},
}, containers[0].Devices)
}

func TestGetContainersSetsCurrentContainer(t *testing.T) {
orig := log.Writer()
log.SetOutput(io.Discard)
t.Cleanup(func() { log.SetOutput(orig) })

currentID := "current123456789"
saved := getCurrentContainerID
getCurrentContainerID = func(...string) string { return currentID }
t.Cleanup(func() { getCurrentContainerID = saved })
t.Cleanup(func() { context.SetCurrentContainer(nil) })

server, err := dockertest.NewServer("127.0.0.1:0", nil, nil)
if err != nil {
t.Fatalf("failed to create test server: %s", err)
}
t.Cleanup(server.Stop)
server.CustomHandler("/info", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte(`{"Containers":0,"Images":0,"NFd":11,"NGoroutines":21}`))
}))
server.CustomHandler("/version", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte(`{"Version":"19.03.12","Os":"Linux","GoVersion":"go1.13.14","Arch":"amd64","ApiVersion":"1.40"}`))
}))
server.CustomHandler("/networks", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("[]"))
}))
server.CustomHandler("/containers/json", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("[]"))
}))
server.CustomHandler(fmt.Sprintf("/containers/%s/json", currentID), http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
json.NewEncoder(w).Encode(docker.Container{ID: currentID, Name: "/self"})
}))

serverURL := fmt.Sprintf("tcp://%s", strings.TrimRight(strings.TrimPrefix(server.URL(), "http://"), "/"))
client, err := dockerclient.NewDockerClient(serverURL, false, "", "", "")
if err != nil {
t.Fatalf("failed to create client: %s", err)
}
client.SkipServerVersionCheck = true

apiVersion, err := client.Version()
if err != nil {
t.Fatalf("failed to retrieve version: %s", err)
}
context.SetDockerEnv(apiVersion)

g := &generator{Client: client, Endpoint: serverURL}
containers, err := g.getContainers(config.Config{})
assert.NoError(t, err)
assert.Empty(t, containers)

var emptyCtx context.Context
current := emptyCtx.CurrentContainer()
assert.NotNil(t, current)
assert.Equal(t, currentID, current.ID)
}