5
5
"bytes"
6
6
"context"
7
7
"crypto/tls"
8
+ "encoding/base64"
8
9
"encoding/json"
9
10
"fmt"
10
11
"io"
@@ -20,6 +21,7 @@ import (
20
21
"github.com/ory/dockertest/v3"
21
22
"github.com/ory/dockertest/v3/docker"
22
23
"github.com/stretchr/testify/require"
24
+ "golang.org/x/crypto/bcrypt"
23
25
"golang.org/x/xerrors"
24
26
25
27
"github.com/coder/envbox/buildlog"
@@ -361,6 +363,10 @@ type RegistryConfig struct {
361
363
HostKeyPath string
362
364
TLSPort string
363
365
Image string
366
+ Username string
367
+ Password string
368
+ // PasswordDir is the directory under which the htpasswd file is written.
369
+ PasswordDir string
364
370
}
365
371
366
372
type RegistryImage string
@@ -373,28 +379,50 @@ func (r RegistryImage) String() string {
373
379
return string (r )
374
380
}
375
381
376
- func RunLocalDockerRegistry (t testing.TB , pool * dockertest.Pool , conf RegistryConfig ) RegistryImage {
382
+ func RunLocalDockerRegistry (t * testing.T , pool * dockertest.Pool , conf RegistryConfig ) RegistryImage {
377
383
t .Helper ()
378
384
379
385
const (
380
386
certPath = "/certs/cert.pem"
381
387
keyPath = "/certs/key.pem"
388
+ authPath = "/auth/htpasswd"
382
389
)
383
390
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 ,
388
400
EnvVar ("REGISTRY_HTTP_TLS_CERTIFICATE" , certPath ),
389
401
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 ,
395
404
mountBinding (conf .HostCertPath , certPath ),
396
405
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
398
426
host .ExtraHosts = []string {"host.docker.internal:host-gateway" }
399
427
host .PortBindings = map [docker.Port ][]docker.PortBinding {
400
428
"443/tcp" : {{
@@ -415,7 +443,13 @@ func RunLocalDockerRegistry(t testing.TB, pool *dockertest.Pool, conf RegistryCo
415
443
url := fmt .Sprintf ("https://%s/v2/_catalog" , host )
416
444
417
445
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
+ })
419
453
}
420
454
421
455
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
447
481
continue
448
482
}
449
483
_ = res .Body .Close ()
450
- if res .StatusCode == http .StatusOK {
484
+ if res .StatusCode == http .StatusOK || res . StatusCode == http . StatusUnauthorized {
451
485
return
452
486
}
453
487
}
454
488
require .NoError (t , ctx .Err ())
455
489
}
456
490
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 {
458
500
t .Helper ()
459
501
460
502
const registryHost = "127.0.0.1"
461
- name := filepath .Base (remoteImage )
503
+ name := filepath .Base (opts . RemoteImage )
462
504
repoTag := strings .Split (name , ":" )
463
505
tag := "latest"
464
506
if len (repoTag ) == 2 {
@@ -469,25 +511,45 @@ func pushLocalImage(t testing.TB, pool *dockertest.Pool, host, remoteImage strin
469
511
t : t ,
470
512
}
471
513
err := pool .Client .PullImage (docker.PullImageOptions {
472
- Repository : strings .Split (remoteImage , ":" )[0 ],
514
+ Repository : strings .Split (opts . RemoteImage , ":" )[0 ],
473
515
Tag : tag ,
474
516
OutputStream : tw ,
475
517
}, docker.AuthConfiguration {})
476
518
require .NoError (t , err )
477
519
478
- _ , port , err := net .SplitHostPort (host )
520
+ _ , port , err := net .SplitHostPort (opts . Host )
479
521
require .NoError (t , err )
480
522
481
- err = pool .Client .TagImage (remoteImage , docker.TagImageOptions {
523
+ err = pool .Client .TagImage (opts . RemoteImage , docker.TagImageOptions {
482
524
Repo : fmt .Sprintf ("%s:%s/%s" , registryHost , port , name ),
483
525
Tag : tag ,
484
526
})
485
527
require .NoError (t , err )
486
528
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
+
487
549
// Idk what to tell you but the pool.Client.PushImage
488
550
// function is bugged or I'm just dumb...
489
551
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 )
491
553
cmd .Stderr = tw
492
554
cmd .Stdout = tw
493
555
err = cmd .Run ()
@@ -516,3 +578,15 @@ func BindMount(src, dst string, ro bool) docker.HostMount {
516
578
Type : "bind" ,
517
579
}
518
580
}
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