Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

-envFile option to read variables from within container at runtime #678

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
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
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ Usage of /bin/registrator:
-tags="": Append tags for all registered services
-ttl=0: TTL for services (default is no expiry)
-ttl-refresh=0: Frequency with which service TTLs are refreshed
-envFile="": Read envs from within container from specified file. Updates on each refresh
```

## Contributing
Expand Down
63 changes: 62 additions & 1 deletion bridge/bridge.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
package bridge

import (
"archive/tar"
"bufio"
"errors"
"io"
"log"
"net"
"net/url"
Expand Down Expand Up @@ -75,9 +78,25 @@ func (b *Bridge) Refresh() {
}
}

if b.config.DynamicEnvFile != "" {
// Refresh service name, tags and attrs from withib container...
for _, services := range b.services {
isGroup := len(services) > 1

for _, service := range services {
newService := b.newService(service.Origin, isGroup)
// We can't change ID of already registered service
service.Name = newService.Name
service.Tags = newService.Tags
service.Attrs = newService.Attrs
}
}
}

for containerId, services := range b.services {
for _, service := range services {
err := b.registry.Refresh(service)

if err != nil {
log.Println("refresh failed:", service.ID, err)
continue
Expand Down Expand Up @@ -246,6 +265,40 @@ func (b *Bridge) add(containerId string, quiet bool) {
}
}

func (b *Bridge) readEnvFileFromContainer(containerID, path string) []string {
r, w := io.Pipe()
defer r.Close()

go func() {
opts := dockerapi.DownloadFromContainerOptions{
Path: path,
OutputStream: w,
}
w.CloseWithError(b.docker.DownloadFromContainer(containerID, opts))
}()

tarreader := tar.NewReader(r)
_, err := tarreader.Next()
if err != nil {
log.Printf("unable to read file %s from container %s: %s", path, containerID, err)
return nil
}

result := []string{}

scanner := bufio.NewScanner(tarreader)

for scanner.Scan() {
line := strings.TrimSpace(scanner.Text())
parts := strings.Split(line, "=")
if len(parts) == 2 {
result = append(result, line)
}
}

return result
}

func (b *Bridge) newService(port ServicePort, isgroup bool) *Service {
container := port.container
defaultName := strings.Split(path.Base(container.Config.Image), ":")[0]
Expand All @@ -266,7 +319,15 @@ func (b *Bridge) newService(port ServicePort, isgroup bool) *Service {
port.HostIP = b.config.HostIp
}

metadata, metadataFromPort := serviceMetaData(container.Config, port.ExposedPort)
// metadata that takes priority when parsing in serviceMetaData
meta := []string{}

// Try to read env-file from within container if its path specified
if b.config.DynamicEnvFile != "" {
meta = b.readEnvFileFromContainer(container.ID, b.config.DynamicEnvFile)
}

metadata, metadataFromPort := serviceMetaData(meta, container.Config, port.ExposedPort)

ignore := mapDefault(metadata, "ignore", "")
if ignore != "" {
Expand Down
1 change: 1 addition & 0 deletions bridge/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ type Config struct {
RefreshInterval int
DeregisterCheck string
Cleanup bool
DynamicEnvFile string
}

type Service struct {
Expand Down
13 changes: 11 additions & 2 deletions bridge/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,11 +54,20 @@ func combineTags(tagParts ...string) []string {
return tags
}

func serviceMetaData(config *dockerapi.Config, port string) (map[string]string, map[string]bool) {
meta := config.Env
// serviceMetaData extracts metadata from Docker-container config.
// meta - is highest priority metadata from other source
func serviceMetaData(meta []string, config *dockerapi.Config, port string) (map[string]string, map[string]bool) {
if meta == nil {
meta = make([]string, 0)
}

for _, line := range config.Env {
meta = append(meta, line)
}
for k, v := range config.Labels {
meta = append(meta, k+"="+v)
}

metadata := make(map[string]string)
metadataFromPort := make(map[string]bool)
for _, kv := range meta {
Expand Down
5 changes: 4 additions & 1 deletion consul/consul.go
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,10 @@ func (r *ConsulAdapter) Deregister(service *bridge.Service) error {
}

func (r *ConsulAdapter) Refresh(service *bridge.Service) error {
return nil
// It is safe for consul to update(re-register) service with same ID. ID is an unique identifier across whole Consul cluster.
// So if our service description changes we can update it in Consul as long as ID stay same....
// We never change service ID anyway.
return r.Register(service)
}

func (r *ConsulAdapter) Services() ([]*bridge.Service, error) {
Expand Down
12 changes: 12 additions & 0 deletions docs/user/run.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ Option | Since | Description
`-ttl <seconds>` | | TTL for services. Default: 0, no expiry (supported backends only)
`-ttl-refresh <seconds>` | | Frequency service TTLs are refreshed (supported backends only)
`-useIpFromLabel <label>` | | Uses the IP address stored in the given label, which is assigned to a container, for registration with Consul
`-envFile <filepath>` | | Read envs from within container from specified file. Updates on each refresh

If the `-internal` option is used, Registrator will register the docker0
internal IP and port instead of the host mapped ones.
Expand All @@ -64,6 +65,17 @@ registry to get back in sync if they fall out of sync. Use this option with caut
as it will notify all the watches you may have registered on your services, and
may rapidly flood your system (e.g. consul-template makes extensive use of watches).

If the `-envFile` option is used, Registrator will try to extract specified file from **each** container. This file must be in simple env-file form:
```
SERVICE_DYNAMIC_META=metavalue
SERVICE_NAME=dynamicname
```

It's contents will be used on first service initialization like any Label or ENV variable. Variables in file take highest priority over Label or ENV.
If refreshInterval specified file will be rereaded on each refresh operation, so it is posiible to change Tags or Meta of registered service from inside container by application logic. It is even possible to change service name by specify diffrent `SERVICE_NAME` in file.

`-envFile` may not be supported by all backends, but Consul-catalog backend works well.

## Consul ACL token

If consul is configured to require an ACL token, Registrator needs to know about it,
Expand Down
2 changes: 2 additions & 0 deletions registrator.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ var deregister = flag.String("deregister", "always", "Deregister exited services
var retryAttempts = flag.Int("retry-attempts", 0, "Max retry attempts to establish a connection with the backend. Use -1 for infinite retries")
var retryInterval = flag.Int("retry-interval", 2000, "Interval (in millisecond) between retry-attempts.")
var cleanup = flag.Bool("cleanup", false, "Remove dangling services")
var dynamicEnvFile = flag.String("envFile", "", "Read envs from within container from specified file. Updates on each refresh")

func getopt(name, def string) string {
if env := os.Getenv(name); env != "" {
Expand Down Expand Up @@ -111,6 +112,7 @@ func main() {
RefreshTtl: *refreshTtl,
RefreshInterval: *refreshInterval,
DeregisterCheck: *deregister,
DynamicEnvFile: *dynamicEnvFile,
Cleanup: *cleanup,
})

Expand Down