Skip to content

Commit fd7a3f2

Browse files
authored
feat: add DOCKER_HOST environment (#875)
* start of work to add DOCKER_HOST environment this seems to be the basic to add support for a custom docker host as an envar or command line option. Some questions I have: 1. how/where to test 2. should the default value be set to something instead of empty string? Signed-off-by: vsoch <[email protected]> * adding support for bare DOCKER_ envars, and DOCKER_HOST tests Signed-off-by: vsoch <[email protected]> * trying to fix linting Signed-off-by: vsoch <[email protected]> * more linting interesting I did not see this output locally - I must have a different development environment than what is run in the tests! Signed-off-by: vsoch <[email protected]> * fixes from Dave review! Signed-off-by: vsoch <[email protected]> * fix parsing of docker host flag value Signed-off-by: vsoch <[email protected]> * typo in docker test and we do not need custom lookup for docker host flag Signed-off-by: vsoch <[email protected]> * undefined out Signed-off-by: vsoch <[email protected]> * simplify build command Signed-off-by: vsoch <[email protected]> * simplify build command Signed-off-by: vsoch <[email protected]> * try to fix docker build - scratch adding an empty file Signed-off-by: vsoch <[email protected]> * attempting to fix tests Signed-off-by: vsoch <[email protected]> * try triggering ci again? Signed-off-by: vsoch <[email protected]> Co-authored-by: vsoch <[email protected]>
1 parent cd1aa5c commit fd7a3f2

File tree

12 files changed

+196
-16
lines changed

12 files changed

+196
-16
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121
specified instance, running within a cgroup.
2222
- Add `--sparse` flag to `overlay create` command to allow generation of a
2323
sparse ext3 overlay image.
24+
- Support for `DOCKER_HOST` parsing when using `docker-daemon://`
25+
- `DOCKER_USERNAME` and `DOCKER_PASSWORD` supported without `SINGULARITY_` prefix.
2426
- The `--no-mount` flag now accepts the value `bind-paths` to disable mounting of
2527
all `bind path` entries in `singularity.conf.
2628

cmd/internal/cli/action_flags.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -861,6 +861,7 @@ func init() {
861861
cmdManager.RegisterFlagForCmd(&commonNoHTTPSFlag, actionsInstanceCmd...)
862862
cmdManager.RegisterFlagForCmd(&commonOldNoHTTPSFlag, actionsInstanceCmd...)
863863
cmdManager.RegisterFlagForCmd(&dockerLoginFlag, actionsInstanceCmd...)
864+
cmdManager.RegisterFlagForCmd(&dockerHostFlag, actionsInstanceCmd...)
864865
cmdManager.RegisterFlagForCmd(&dockerPasswordFlag, actionsInstanceCmd...)
865866
cmdManager.RegisterFlagForCmd(&dockerUsernameFlag, actionsInstanceCmd...)
866867
cmdManager.RegisterFlagForCmd(&actionEnvFlag, actionsInstanceCmd...)

cmd/internal/cli/build.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -300,6 +300,7 @@ func init() {
300300
cmdManager.RegisterFlagForCmd(&commonNoHTTPSFlag, buildCmd)
301301
cmdManager.RegisterFlagForCmd(&commonTmpDirFlag, buildCmd)
302302

303+
cmdManager.RegisterFlagForCmd(&dockerHostFlag, buildCmd)
303304
cmdManager.RegisterFlagForCmd(&dockerUsernameFlag, buildCmd)
304305
cmdManager.RegisterFlagForCmd(&dockerPasswordFlag, buildCmd)
305306
cmdManager.RegisterFlagForCmd(&dockerLoginFlag, buildCmd)

cmd/internal/cli/build_linux.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -393,6 +393,7 @@ func runBuildLocal(ctx context.Context, cmd *cobra.Command, dst, spec string) {
393393
LibraryAuthToken: authToken,
394394
KeyServerOpts: ko,
395395
DockerAuthConfig: authConf,
396+
DockerDaemonHost: dockerHost,
396397
EncryptionKeyInfo: keyInfo,
397398
FixPerms: buildArgs.fixPerms,
398399
SandboxTarget: sandboxTarget,

cmd/internal/cli/pull.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,7 @@ func init() {
141141
cmdManager.RegisterFlagForCmd(&pullDisableCacheFlag, PullCmd)
142142
cmdManager.RegisterFlagForCmd(&pullDirFlag, PullCmd)
143143

144+
cmdManager.RegisterFlagForCmd(&dockerHostFlag, PullCmd)
144145
cmdManager.RegisterFlagForCmd(&dockerUsernameFlag, PullCmd)
145146
cmdManager.RegisterFlagForCmd(&dockerPasswordFlag, PullCmd)
146147
cmdManager.RegisterFlagForCmd(&dockerLoginFlag, PullCmd)

cmd/internal/cli/push.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ func init() {
6969
cmdManager.RegisterFlagForCmd(&pushAllowUnsignedFlag, PushCmd)
7070
cmdManager.RegisterFlagForCmd(&pushDescriptionFlag, PushCmd)
7171

72+
cmdManager.RegisterFlagForCmd(&dockerHostFlag, PushCmd)
7273
cmdManager.RegisterFlagForCmd(&dockerUsernameFlag, PushCmd)
7374
cmdManager.RegisterFlagForCmd(&dockerPasswordFlag, PushCmd)
7475
})

cmd/internal/cli/singularity.go

Lines changed: 28 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ var currentRemoteEndpoint *endpoint.Config
5050
var (
5151
dockerAuthConfig ocitypes.DockerAuthConfig
5252
dockerLogin bool
53+
dockerHost string
5354

5455
encryptionPEMPath string
5556
promptForPassphrase bool
@@ -124,24 +125,26 @@ var singVerboseFlag = cmdline.Flag{
124125

125126
// --docker-username
126127
var dockerUsernameFlag = cmdline.Flag{
127-
ID: "dockerUsernameFlag",
128-
Value: &dockerAuthConfig.Username,
129-
DefaultValue: "",
130-
Name: "docker-username",
131-
Usage: "specify a username for docker authentication",
132-
Hidden: true,
133-
EnvKeys: []string{"DOCKER_USERNAME"},
128+
ID: "dockerUsernameFlag",
129+
Value: &dockerAuthConfig.Username,
130+
DefaultValue: "",
131+
Name: "docker-username",
132+
Usage: "specify a username for docker authentication",
133+
Hidden: true,
134+
EnvKeys: []string{"DOCKER_USERNAME"},
135+
WithoutPrefix: true,
134136
}
135137

136138
// --docker-password
137139
var dockerPasswordFlag = cmdline.Flag{
138-
ID: "dockerPasswordFlag",
139-
Value: &dockerAuthConfig.Password,
140-
DefaultValue: "",
141-
Name: "docker-password",
142-
Usage: "specify a password for docker authentication",
143-
Hidden: true,
144-
EnvKeys: []string{"DOCKER_PASSWORD"},
140+
ID: "dockerPasswordFlag",
141+
Value: &dockerAuthConfig.Password,
142+
DefaultValue: "",
143+
Name: "docker-password",
144+
Usage: "specify a password for docker authentication",
145+
Hidden: true,
146+
EnvKeys: []string{"DOCKER_PASSWORD"},
147+
WithoutPrefix: true,
145148
}
146149

147150
// --docker-login
@@ -154,6 +157,17 @@ var dockerLoginFlag = cmdline.Flag{
154157
EnvKeys: []string{"DOCKER_LOGIN"},
155158
}
156159

160+
// --docker-host
161+
var dockerHostFlag = cmdline.Flag{
162+
ID: "dockerHostFlag",
163+
Value: &dockerHost,
164+
DefaultValue: "",
165+
Name: "docker-host",
166+
Usage: "specify a custom Docker daemon host",
167+
EnvKeys: []string{"DOCKER_HOST"},
168+
WithoutPrefix: true,
169+
}
170+
157171
// --passphrase
158172
var commonPromptForPassphraseFlag = cmdline.Flag{
159173
ID: "commonPromptForPassphraseFlag",

e2e/docker/docker.go

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,12 @@ package docker
88
import (
99
"fmt"
1010
"os"
11+
"os/exec"
1112
"path/filepath"
1213
"strings"
1314
"testing"
1415

16+
dockerclient "github.com/docker/docker/client"
1517
"github.com/pkg/errors"
1618
"github.com/sylabs/singularity/e2e/internal/e2e"
1719
"github.com/sylabs/singularity/e2e/internal/testhelper"
@@ -118,6 +120,138 @@ func (c ctx) testDockerPulls(t *testing.T) {
118120
}
119121
}
120122

123+
// Testing DOCKER_ host support (only if docker available)
124+
func (c ctx) testDockerHost(t *testing.T) {
125+
require.Command(t, "docker")
126+
127+
// Write a temporary "empty" Dockerfile (from scratch)
128+
tmpPath, err := fs.MakeTmpDir(c.env.TestDir, "docker-", 0o755)
129+
err = errors.Wrapf(err, "creating temporary directory in %q for docker host test", c.env.TestDir)
130+
if err != nil {
131+
t.Fatalf("failed to create temporary directory: %+v", err)
132+
}
133+
defer os.RemoveAll(tmpPath)
134+
135+
// Here is the Dockerfile, and a temporary image path
136+
dockerfile := filepath.Join(tmpPath, "Dockerfile")
137+
emptyfile := filepath.Join(tmpPath, "file")
138+
tmpImage := filepath.Join(tmpPath, "scratch-tmp.sif")
139+
140+
// Write Dockerfile and empty file to file
141+
dockerfileContent := []byte("FROM scratch\nCOPY file /mrbigglesworth")
142+
err = os.WriteFile(dockerfile, dockerfileContent, 0o644)
143+
if err != nil {
144+
t.Fatalf("failed to create temporary Dockerfile: %+v", err)
145+
}
146+
147+
fileContent := []byte("")
148+
err = os.WriteFile(emptyfile, fileContent, 0o644)
149+
if err != nil {
150+
t.Fatalf("failed to create empty file: %+v", err)
151+
}
152+
153+
dockerURI := "dinosaur/test-image:latest"
154+
pullURI := "docker-daemon:" + dockerURI
155+
156+
// Invoke docker build to build an empty scratch image in the docker daemon.
157+
// Use os/exec because easier to generate a command with a working directory
158+
e2e.Privileged(func(t *testing.T) {
159+
cmd := exec.Command("docker", "build", "-t", dockerURI, ".")
160+
cmd.Dir = tmpPath
161+
out, err := cmd.CombinedOutput()
162+
if err != nil {
163+
t.Fatalf("Unexpected error while running command.\n%s: %s", err, string(out))
164+
}
165+
})
166+
167+
tests := []struct {
168+
name string
169+
envarName string
170+
envarValue string
171+
exit int
172+
}{
173+
// Unset docker host should use default and succeed
174+
{
175+
name: "singularityDockerHostEmpty",
176+
envarName: "SINGULARITY_DOCKER_HOST",
177+
envarValue: "",
178+
exit: 0,
179+
},
180+
{
181+
name: "dockerHostEmpty",
182+
envarName: "DOCKER_HOST",
183+
envarValue: "",
184+
exit: 0,
185+
},
186+
187+
// bad Docker host should fail
188+
{
189+
name: "singularityDockerHostInvalid",
190+
envarName: "SINGULARITY_DOCKER_HOST",
191+
envarValue: "tcp://192.168.59.103:oops",
192+
exit: 255,
193+
},
194+
{
195+
name: "dockerHostInvalid",
196+
envarName: "DOCKER_HOST",
197+
envarValue: "tcp://192.168.59.103:oops",
198+
exit: 255,
199+
},
200+
201+
// Set to default should succeed
202+
// The default host varies based on OS, so we use dockerclient default
203+
{
204+
name: "singularityDockerHostValid",
205+
envarName: "SINGULARITY_DOCKER_HOST",
206+
envarValue: dockerclient.DefaultDockerHost,
207+
exit: 0,
208+
},
209+
{
210+
name: "dockerHostValid",
211+
envarName: "DOCKER_HOST",
212+
envarValue: dockerclient.DefaultDockerHost,
213+
exit: 0,
214+
},
215+
}
216+
217+
for _, tt := range tests {
218+
// Export variable to environment if it's defined
219+
if tt.envarValue != "" {
220+
e2e.Privileged(func(t *testing.T) {
221+
c.env.RunSingularity(
222+
t,
223+
e2e.WithEnv(append(os.Environ(), tt.envarValue)),
224+
e2e.AsSubtest(tt.name),
225+
e2e.WithProfile(e2e.UserProfile),
226+
e2e.WithCommand("pull"),
227+
e2e.WithArgs(tmpImage, pullURI),
228+
e2e.ExpectExit(tt.exit),
229+
)
230+
})
231+
} else {
232+
e2e.Privileged(func(t *testing.T) {
233+
c.env.RunSingularity(
234+
t,
235+
e2e.AsSubtest(tt.name),
236+
e2e.WithProfile(e2e.UserProfile),
237+
e2e.WithCommand("pull"),
238+
e2e.WithArgs(tmpImage, pullURI),
239+
e2e.ExpectExit(tt.exit),
240+
)
241+
})
242+
}
243+
}
244+
245+
// Clean up docker image
246+
e2e.Privileged(func(t *testing.T) {
247+
cmd := exec.Command("docker", "rmi", dockerURI)
248+
_, err = cmd.Output()
249+
if err != nil {
250+
t.Fatalf("Unexpected error while cleaning up docker image.\n%s", err)
251+
}
252+
})
253+
}
254+
121255
// AUFS sanity tests
122256
func (c ctx) testDockerAUFS(t *testing.T) {
123257
imageDir, cleanup := e2e.MakeTempDir(t, c.env.TestDir, "aufs-", "")
@@ -731,6 +865,7 @@ func E2ETests(env e2e.TestEnv) testhelper.Tests {
731865
return testhelper.Tests{
732866
"AUFS": c.testDockerAUFS,
733867
"def file": c.testDockerDefFile,
868+
"docker host": c.testDockerHost,
734869
"permissions": c.testDockerPermissions,
735870
"pulls": c.testDockerPulls,
736871
"registry": c.testDockerRegistry,

internal/pkg/build/sources/conveyorPacker_oci.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,11 +144,13 @@ func (cp *OCIConveyorPacker) Get(ctx context.Context, b *sytypes.Bundle) (err er
144144
cp.sysCtx = &types.SystemContext{
145145
OCIInsecureSkipTLSVerify: cp.b.Opts.NoHTTPS,
146146
DockerAuthConfig: cp.b.Opts.DockerAuthConfig,
147+
DockerDaemonHost: cp.b.Opts.DockerDaemonHost,
147148
OSChoice: "linux",
148149
AuthFilePath: syfs.DockerConf(),
149150
DockerRegistryUserAgent: useragent.Value(),
150151
BigFilesTemporaryDir: b.TmpDir,
151152
}
153+
152154
if cp.b.Opts.NoHTTPS {
153155
cp.sysCtx.DockerInsecureSkipTLSVerify = types.NewOptionalBool(true)
154156
}

pkg/build/types/bundle.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,8 @@ type Options struct {
4949
KeyServerOpts []scskeyclient.Option
5050
// contains docker credentials if specified.
5151
DockerAuthConfig *ocitypes.DockerAuthConfig
52+
// Custom docker Daemon host
53+
DockerDaemonHost string
5254
// EncryptionKeyInfo specifies the key used for filesystem
5355
// encryption if applicable.
5456
// A nil value indicates encryption should not occur.

pkg/cmdline/flag.go

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ type Flag struct {
2727
Required bool
2828
EnvKeys []string
2929
EnvHandler EnvHandler
30+
// Export envar also without prefix
31+
WithoutPrefix bool
3032
// When Value is a []String:
3133
// If true, will use pFlag StringArrayVar(P) type, where values are not split on comma.
3234
// If false, will use pFlag StringSliceVar(P) type, where a single value is split on commas.
@@ -52,6 +54,11 @@ func (m *flagManager) setFlagOptions(flag *Flag, cmd *cobra.Command) {
5254

5355
if len(flag.EnvKeys) > 0 {
5456
cmd.Flags().SetAnnotation(flag.Name, "envkey", flag.EnvKeys)
57+
58+
// Environment flags can also be exported without a prefix (e.g. DOCKER_*)
59+
if flag.WithoutPrefix {
60+
cmd.Flags().SetAnnotation(flag.Name, "withoutPrefix", []string{"true"})
61+
}
5562
}
5663
if flag.Deprecated != "" {
5764
cmd.Flags().MarkDeprecated(flag.Name, flag.Deprecated)
@@ -202,9 +209,22 @@ func (m *flagManager) updateCmdFlagFromEnv(cmd *cobra.Command, prefix string) er
202209
return
203210
}
204211
for _, key := range envKeys {
212+
213+
// First priority goes to prefixed variable
205214
val, set := os.LookupEnv(prefix + key)
206215
if !set {
207-
continue
216+
217+
// Determine if environment keys should be looked for without prefix
218+
// This annotation just needs to be present, period
219+
_, withoutPrefix := flag.Annotations["withoutPrefix"]
220+
if !withoutPrefix {
221+
continue
222+
}
223+
// Second try - looking for the same without prefix!
224+
val, set = os.LookupEnv(key)
225+
if !set {
226+
continue
227+
}
208228
}
209229
if mflag.EnvHandler != nil {
210230
if err := mflag.EnvHandler(flag, val); err != nil {

pkg/sylog/sylog.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ var messageColors = map[messageLevel]string{
2828

2929
var (
3030
noColorLevel messageLevel = 90
31-
loggerLevel messageLevel = InfoLevel
31+
loggerLevel = InfoLevel
3232
)
3333

3434
var logWriter = (io.Writer)(os.Stderr)

0 commit comments

Comments
 (0)