Skip to content

Commit 66a7783

Browse files
committed
added a hostname flag/var and Docker usage docs
Now Wadsworth should be a bit easier to use within a container. This change adds documentation for Docker usage as well as a solution to a problem introduced by using a container: the lack of access to the host's hostname. This change introduces a new flag/environment variable named `hostname`/`HOSTNAME` so the parent machine's hostname can be passed in to wadsworth's container and used during configuration execution.
1 parent 8a69a10 commit 66a7783

File tree

5 files changed

+54
-14
lines changed

5 files changed

+54
-14
lines changed

README.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,33 @@ Currently, Wadsworth has a single command: `run` and it takes a single parameter
3535
this way instead of just using the target repos to define what Wadsworth should do is 1. to consolidate Wadsworth config
3636
into one place, 2. separate the config of the tools from the applications and 3. keep your target repos clean.
3737

38+
## Usage as a Docker Container
39+
40+
See the `docker-compose.yml` file for an example and read below for details.
41+
42+
You can run Wadsworth as a Docker container. If you're using it to deploy Docker containers via compose, this makes the
43+
most sense. This is quite simple and is best done by writing a Docker Compose configuration for Wadsworth in order to
44+
bootstrap your deployment.
45+
46+
The Wadsworth image is built on the `docker/compose` image, since most use-cases will use Docker or Compose to deploy
47+
services. This means you must mount the Docker API socket into the container, just like Portainer or cAdvisor or any of
48+
the other Docker tools that also run inside a container.
49+
50+
The socket is located by default at `/var/run/docker.sock` and the `docker/compose` image expects this path too, so you
51+
just need to add a volume mount to your compose that specifies `/var/run/docker.sock:/var/run/docker.sock`.
52+
53+
Another minor detail you should know is that Wadsworth exposes a `HOSTNAME` variable for the configuration script.
54+
However, when in a container, this hostname is a randomised string such as `b50fa67783ad`. This means, if your
55+
configuration performs checks such as `if (HOSTNAME === 'server031')`, this won't work. To resolve this, Wadsworth will
56+
attempt to read the environment variable `HOSTNAME` and use that instead of using `/etc/hostname`.
57+
58+
This means, you can bootstrap a Wadsworth deployment with only two variables:
59+
60+
```env
61+
VAULT_TOKEN=abcxyz
62+
HOSTNAME=server012
63+
```
64+
3865
### Configuration
3966

4067
The precursor to Wadsworth used JSON for configuration, this was fine for simple tasks but the ability to provide a

main.go

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ this repository has new commits, Wadsworth will automatically reconfigure.`,
5959
Usage: "argument `target` specifies Git repository for configuration.",
6060
ArgsUsage: "target",
6161
Flags: []cli.Flag{
62+
cli.StringFlag{Name: "hostname", EnvVar: "HOSTNAME"},
6263
cli.StringFlag{Name: "directory", EnvVar: "DIRECTORY", Value: "./cache/"},
6364
cli.BoolFlag{Name: "no-ssh", EnvVar: "NO_SSH"},
6465
cli.DurationFlag{Name: "check-interval", EnvVar: "CHECK_INTERVAL", Value: time.Second * 10},
@@ -75,10 +76,20 @@ this repository has new commits, Wadsworth will automatically reconfigure.`,
7576
ctx, cancel := context.WithCancel(context.Background())
7677
defer cancel()
7778

79+
// If no hostname is provided, use the actual host's hostname
80+
hostname := c.String("hostname")
81+
if hostname == "" {
82+
hostname, err = os.Hostname()
83+
if err != nil {
84+
return errors.Wrap(err, "failed to get hostname")
85+
}
86+
}
87+
7888
svc, err := service.Initialise(ctx, service.Config{
7989
Target: c.Args().First(),
80-
NoSSH: c.Bool("no-ssh"),
90+
Hostname: hostname,
8191
Directory: c.String("directory"),
92+
NoSSH: c.Bool("no-ssh"),
8293
CheckInterval: c.Duration("check-interval"),
8394
VaultAddress: c.String("vault-addr"),
8495
VaultToken: c.String("vault-token"),

service/config/config.go

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ type State struct {
2121

2222
// ConfigFromDirectory searches a directory for configuration files and
2323
// constructs a desired state from the declarations.
24-
func ConfigFromDirectory(dir string) (state State, err error) {
24+
func ConfigFromDirectory(dir, hostname string) (state State, err error) {
2525
files, err := ioutil.ReadDir(dir)
2626
if err != nil {
2727
err = errors.Wrap(err, "failed to read config directory")
@@ -46,7 +46,7 @@ func ConfigFromDirectory(dir string) (state State, err error) {
4646
scripts: sources,
4747
}
4848

49-
err = cb.construct()
49+
err = cb.construct(hostname)
5050
if err != nil {
5151
return
5252
}
@@ -61,7 +61,7 @@ type configBuilder struct {
6161
scripts []string
6262
}
6363

64-
func (cb *configBuilder) construct() (err error) {
64+
func (cb *configBuilder) construct(hostname string) (err error) {
6565
//nolint:errcheck
6666
cb.vm.Run(`'use strict';
6767
var STATE = {
@@ -86,10 +86,6 @@ function E(k, v) {
8686
}
8787
`)
8888

89-
hostname, err := os.Hostname()
90-
if err != nil {
91-
return errors.Wrap(err, "failed to get hostname")
92-
}
9389
cb.vm.Set("HOSTNAME", hostname) //nolint:errcheck
9490

9591
env := make(map[string]string)

service/reconfigure.go

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import (
1818
// read the configuration file(s) from the repository, gather all the targets
1919
// and set up the target watcher. This should always happen in sync with the
2020
// rest of the service to prevent a reconfiguration during an event handler.
21-
func (app *App) reconfigure() (err error) {
21+
func (app *App) reconfigure(hostname string) (err error) {
2222
zap.L().Debug("reconfiguring")
2323

2424
err = app.watchConfig()
@@ -31,7 +31,12 @@ func (app *App) reconfigure() (err error) {
3131
if err != nil {
3232
return
3333
}
34-
state := getNewState(path, app.state)
34+
state := getNewState(path, hostname, app.state)
35+
36+
// Set the HOSTNAME config environment variable if necessary.
37+
if app.config.Hostname != "" {
38+
state.Env["HOSTNAME"] = app.config.Hostname
39+
}
3540

3641
// diff targets
3742
additions, removals := diffTargets(app.targets, state.Targets)
@@ -113,8 +118,8 @@ func (app *App) watchTargets() (err error) {
113118

114119
// getNewState attempts to obtain a new desired state from the given path, if
115120
// any failures occur, it simply returns a fallback state and logs an error
116-
func getNewState(path string, fallback config.State) (state config.State) {
117-
state, err := config.ConfigFromDirectory(path)
121+
func getNewState(path, hostname string, fallback config.State) (state config.State) {
122+
state, err := config.ConfigFromDirectory(path, hostname)
118123
if err != nil {
119124
zap.L().Error("failed to construct config from repo, falling back to original state",
120125
zap.Error(err))

service/service.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ type App struct {
3333

3434
type Config struct {
3535
Target string
36+
Hostname string
3637
NoSSH bool
3738
Directory string
3839
CheckInterval time.Duration
@@ -72,7 +73,7 @@ func Initialise(ctx context.Context, c Config) (app *App, err error) {
7273
}
7374
}
7475

75-
err = app.reconfigure()
76+
err = app.reconfigure(c.Hostname)
7677
if err != nil {
7778
return
7879
}
@@ -89,7 +90,7 @@ func (app *App) Start() (final error) {
8990
f := func() (err error) {
9091
select {
9192
case <-app.configWatcher.Events:
92-
err = app.reconfigure()
93+
err = app.reconfigure(app.config.Hostname)
9394

9495
case event := <-app.targetsWatcher.Events:
9596
e := app.handle(event)

0 commit comments

Comments
 (0)