Skip to content

Commit 2f785a9

Browse files
committed
add integration test
1 parent eda7b52 commit 2f785a9

File tree

2 files changed

+115
-19
lines changed

2 files changed

+115
-19
lines changed

integration/docker_test.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
package integration_test
55

66
import (
7+
"encoding/json"
78
"fmt"
89
"net"
910
"os"
@@ -17,6 +18,7 @@ import (
1718
"github.com/stretchr/testify/require"
1819

1920
"github.com/coder/envbox/cli"
21+
"github.com/coder/envbox/dockerutil"
2022
"github.com/coder/envbox/integration/integrationtest"
2123
)
2224

@@ -318,19 +320,39 @@ func TestDocker(t *testing.T) {
318320
regKeyPath := filepath.Join(certDir, "registry_key.pem")
319321
integrationtest.WriteCertificate(t, dockerCert, regCertPath, regKeyPath)
320322

323+
username := "coder"
324+
password := "helloworld"
325+
321326
// Start up the docker registry and push an image
322327
// to it that we can reference.
323328
image := integrationtest.RunLocalDockerRegistry(t, pool, integrationtest.RegistryConfig{
324329
HostCertPath: regCertPath,
325330
HostKeyPath: regKeyPath,
326331
Image: integrationtest.UbuntuImage,
327332
TLSPort: strconv.Itoa(registryAddr.Port),
333+
PasswordDir: dir,
334+
Username: username,
335+
Password: password,
328336
})
329337

338+
type authConfigs struct {
339+
Auths map[string]dockerutil.AuthConfig `json:"auths"`
340+
}
341+
342+
auths := authConfigs{
343+
Auths: map[string]dockerutil.AuthConfig{
344+
image.Registry(): {Username: username, Password: password},
345+
},
346+
}
347+
348+
authStr, err := json.Marshal(auths)
349+
require.NoError(t, err)
350+
330351
envs := []string{
331352
integrationtest.EnvVar(cli.EnvAgentToken, "faketoken"),
332353
integrationtest.EnvVar(cli.EnvAgentURL, fmt.Sprintf("https://%s:%d", "host.docker.internal", coderAddr.Port)),
333354
integrationtest.EnvVar(cli.EnvExtraCertsPath, "/tmp/certs"),
355+
integrationtest.EnvVar(cli.EnvBoxPullImageSecretEnvVar, string(authStr)),
334356
}
335357

336358
// Run the envbox container.

integration/integrationtest/docker.go

Lines changed: 93 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"bytes"
66
"context"
77
"crypto/tls"
8+
"encoding/base64"
89
"encoding/json"
910
"fmt"
1011
"io"
@@ -20,6 +21,7 @@ import (
2021
"github.com/ory/dockertest/v3"
2122
"github.com/ory/dockertest/v3/docker"
2223
"github.com/stretchr/testify/require"
24+
"golang.org/x/crypto/bcrypt"
2325
"golang.org/x/xerrors"
2426

2527
"github.com/coder/envbox/buildlog"
@@ -361,6 +363,10 @@ type RegistryConfig struct {
361363
HostKeyPath string
362364
TLSPort string
363365
Image string
366+
Username string
367+
Password string
368+
// PasswordDir is the directory under which the htpasswd file is written.
369+
PasswordDir string
364370
}
365371

366372
type RegistryImage string
@@ -373,28 +379,50 @@ func (r RegistryImage) String() string {
373379
return string(r)
374380
}
375381

376-
func RunLocalDockerRegistry(t testing.TB, pool *dockertest.Pool, conf RegistryConfig) RegistryImage {
382+
func RunLocalDockerRegistry(t *testing.T, pool *dockertest.Pool, conf RegistryConfig) RegistryImage {
377383
t.Helper()
378384

379385
const (
380386
certPath = "/certs/cert.pem"
381387
keyPath = "/certs/key.pem"
388+
authPath = "/auth/htpasswd"
382389
)
383390

384-
resource, err := pool.RunWithOptions(&dockertest.RunOptions{
385-
Repository: registryImage,
386-
Tag: registryTag,
387-
Env: []string{
391+
var (
392+
envs = []string{
393+
EnvVar("REGISTRY_HTTP_ADDR", "0.0.0.0:443"),
394+
}
395+
binds []string
396+
)
397+
398+
if conf.HostCertPath != "" && conf.HostKeyPath != "" {
399+
envs = append(envs,
388400
EnvVar("REGISTRY_HTTP_TLS_CERTIFICATE", certPath),
389401
EnvVar("REGISTRY_HTTP_TLS_KEY", keyPath),
390-
EnvVar("REGISTRY_HTTP_ADDR", "0.0.0.0:443"),
391-
},
392-
ExposedPorts: []string{"443/tcp"},
393-
}, func(host *docker.HostConfig) {
394-
host.Binds = []string{
402+
)
403+
binds = append(binds,
395404
mountBinding(conf.HostCertPath, certPath),
396405
mountBinding(conf.HostKeyPath, keyPath),
397-
}
406+
)
407+
}
408+
409+
if conf.PasswordDir != "" {
410+
authFile := GenerateRegistryAuth(t, conf.PasswordDir, conf.Username, conf.Password)
411+
envs = append(envs,
412+
EnvVar("REGISTRY_AUTH", "htpasswd"),
413+
EnvVar("REGISTRY_AUTH_HTPASSWD_REALM", "Test Registry"),
414+
EnvVar("REGISTRY_AUTH_HTPASSWD_PATH", authPath),
415+
)
416+
binds = append(binds, mountBinding(authFile, authPath))
417+
}
418+
419+
resource, err := pool.RunWithOptions(&dockertest.RunOptions{
420+
Repository: registryImage,
421+
Tag: registryTag,
422+
Env: envs,
423+
ExposedPorts: []string{"443/tcp"},
424+
}, func(host *docker.HostConfig) {
425+
host.Binds = binds
398426
host.ExtraHosts = []string{"host.docker.internal:host-gateway"}
399427
host.PortBindings = map[docker.Port][]docker.PortBinding{
400428
"443/tcp": {{
@@ -415,7 +443,13 @@ func RunLocalDockerRegistry(t testing.TB, pool *dockertest.Pool, conf RegistryCo
415443
url := fmt.Sprintf("https://%s/v2/_catalog", host)
416444

417445
waitForRegistry(t, pool, resource, url)
418-
return pushLocalImage(t, pool, host, conf.Image)
446+
return pushLocalImage(t, pool, pushOptions{
447+
Host: host,
448+
RemoteImage: conf.Image,
449+
Username: conf.Username,
450+
Password: conf.Password,
451+
ConfigDir: conf.PasswordDir,
452+
})
419453
}
420454

421455
func waitForRegistry(t testing.TB, pool *dockertest.Pool, resource *dockertest.Resource, url string) {
@@ -447,18 +481,26 @@ func waitForRegistry(t testing.TB, pool *dockertest.Pool, resource *dockertest.R
447481
continue
448482
}
449483
_ = res.Body.Close()
450-
if res.StatusCode == http.StatusOK {
484+
if res.StatusCode == http.StatusOK || res.StatusCode == http.StatusUnauthorized {
451485
return
452486
}
453487
}
454488
require.NoError(t, ctx.Err())
455489
}
456490

457-
func pushLocalImage(t testing.TB, pool *dockertest.Pool, host, remoteImage string) RegistryImage {
491+
type pushOptions struct {
492+
Host string
493+
RemoteImage string
494+
Username string
495+
Password string
496+
ConfigDir string
497+
}
498+
499+
func pushLocalImage(t *testing.T, pool *dockertest.Pool, opts pushOptions) RegistryImage {
458500
t.Helper()
459501

460502
const registryHost = "127.0.0.1"
461-
name := filepath.Base(remoteImage)
503+
name := filepath.Base(opts.RemoteImage)
462504
repoTag := strings.Split(name, ":")
463505
tag := "latest"
464506
if len(repoTag) == 2 {
@@ -469,25 +511,45 @@ func pushLocalImage(t testing.TB, pool *dockertest.Pool, host, remoteImage strin
469511
t: t,
470512
}
471513
err := pool.Client.PullImage(docker.PullImageOptions{
472-
Repository: strings.Split(remoteImage, ":")[0],
514+
Repository: strings.Split(opts.RemoteImage, ":")[0],
473515
Tag: tag,
474516
OutputStream: tw,
475517
}, docker.AuthConfiguration{})
476518
require.NoError(t, err)
477519

478-
_, port, err := net.SplitHostPort(host)
520+
_, port, err := net.SplitHostPort(opts.Host)
479521
require.NoError(t, err)
480522

481-
err = pool.Client.TagImage(remoteImage, docker.TagImageOptions{
523+
err = pool.Client.TagImage(opts.RemoteImage, docker.TagImageOptions{
482524
Repo: fmt.Sprintf("%s:%s/%s", registryHost, port, name),
483525
Tag: tag,
484526
})
485527
require.NoError(t, err)
486528

529+
type config struct {
530+
Auths map[string]dockerutil.AuthConfig `json:"auths"`
531+
}
532+
533+
auth := base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("%s:%s", opts.Username, opts.Password)))
534+
535+
cfg := config{
536+
Auths: map[string]dockerutil.AuthConfig{
537+
net.JoinHostPort(registryHost, port): {
538+
Username: opts.Username,
539+
Password: opts.Password,
540+
Auth: auth,
541+
},
542+
},
543+
}
544+
b, err := json.Marshal(cfg)
545+
require.NoError(t, err)
546+
configPath := filepath.Join(opts.ConfigDir, "config.json")
547+
WriteFile(t, configPath, string(b))
548+
487549
// Idk what to tell you but the pool.Client.PushImage
488550
// function is bugged or I'm just dumb...
489551
image := fmt.Sprintf("%s:%s/%s:%s", registryHost, port, name, tag)
490-
cmd := exec.Command("docker", "push", image)
552+
cmd := exec.Command("docker", "--config", opts.ConfigDir, "push", image)
491553
cmd.Stderr = tw
492554
cmd.Stdout = tw
493555
err = cmd.Run()
@@ -516,3 +578,15 @@ func BindMount(src, dst string, ro bool) docker.HostMount {
516578
Type: "bind",
517579
}
518580
}
581+
582+
func GenerateRegistryAuth(t *testing.T, directory, username, password string) string {
583+
t.Helper()
584+
585+
p, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
586+
require.NoError(t, err)
587+
588+
authFile := filepath.Join(directory, "credentials")
589+
WriteFile(t, authFile, fmt.Sprintf("%s:%s", username, string(p)))
590+
591+
return authFile
592+
}

0 commit comments

Comments
 (0)