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

feat: support passing registry credentials to the reaper #647

Merged
merged 15 commits into from
Jan 2, 2023
Merged
Show file tree
Hide file tree
Changes from 12 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
38 changes: 31 additions & 7 deletions container.go
Original file line number Diff line number Diff line change
Expand Up @@ -112,13 +112,14 @@ type ContainerRequest struct {
NetworkAliases map[string][]string // for specifying network aliases
NetworkMode container.NetworkMode
Resources container.Resources
Files []ContainerFile // files which will be copied when container starts
User string // for specifying uid:gid
SkipReaper bool // indicates whether we skip setting up a reaper for this
ReaperImage string // alternative reaper image
AutoRemove bool // if set to true, the container will be removed from the host when stopped
AlwaysPullImage bool // Always pull image
ImagePlatform string // ImagePlatform describes the platform which the image runs on.
Files []ContainerFile // files which will be copied when container starts
User string // for specifying uid:gid
SkipReaper bool // indicates whether we skip setting up a reaper for this
ReaperImage string // Deprecated: use WithImageName ContainerOption instead. Alternative reaper image
ReaperOptions []ContainerOption // options for the reaper
AutoRemove bool // if set to true, the container will be removed from the host when stopped
AlwaysPullImage bool // Always pull image
ImagePlatform string // ImagePlatform describes the platform which the image runs on.
Binds []string
ShmSize int64 // Amount of memory shared with the host (in bytes)
CapAdd []string // Add Linux capabilities
Expand Down Expand Up @@ -149,6 +150,29 @@ func (f GenericProviderOptionFunc) ApplyGenericTo(opts *GenericProviderOptions)
f(opts)
}

// containerOptions functional options for a container
type containerOptions struct {
ImageName string
RegistryCredentials string
}

// functional option for setting the reaper image
type ContainerOption func(*containerOptions)

// WithImageName sets the reaper image name
func WithImageName(imageName string) ContainerOption {
return func(o *containerOptions) {
o.ImageName = imageName
}
}

// WithRegistryCredentials sets the reaper registry credentials
func WithRegistryCredentials(registryCredentials string) ContainerOption {
return func(o *containerOptions) {
o.RegistryCredentials = registryCredentials
}
}

// possible provider types
const (
ProviderDocker ProviderType = iota // Docker is default = 0
Expand Down
15 changes: 11 additions & 4 deletions docker.go
Original file line number Diff line number Diff line change
Expand Up @@ -961,11 +961,18 @@ func (p *DockerProvider) CreateContainer(ctx context.Context, req ContainerReque

sessionID := sessionID()

reaperOpts := containerOptions{
ImageName: req.ReaperImage
mdelapenya marked this conversation as resolved.
Show resolved Hide resolved
}
for _, opt := range req.ReaperOptions {
opt(&reaperOpts)
}

var termSignal chan bool
// the reaper does not need to start a reaper for itself
isReaperContainer := strings.EqualFold(req.Image, reaperImage(req.ReaperImage))
isReaperContainer := strings.EqualFold(req.Image, reaperImage(reaperOpts.ImageName))
mdelapenya marked this conversation as resolved.
Show resolved Hide resolved
if !req.SkipReaper && !isReaperContainer {
r, err := NewReaper(context.WithValue(ctx, dockerHostContextKey, p.host), sessionID.String(), p, req.ReaperImage)
r, err := newReaper(context.WithValue(ctx, dockerHostContextKey, p.host), sessionID.String(), p, req.ReaperOptions...)
if err != nil {
return nil, fmt.Errorf("%w: creating reaper failed", err)
}
Expand Down Expand Up @@ -1182,7 +1189,7 @@ func (p *DockerProvider) ReuseOrCreateContainer(ctx context.Context, req Contain
sessionID := sessionID()
var termSignal chan bool
if !req.SkipReaper {
r, err := NewReaper(context.WithValue(ctx, dockerHostContextKey, p.host), sessionID.String(), p, req.ReaperImage)
r, err := newReaper(context.WithValue(ctx, dockerHostContextKey, p.host), sessionID.String(), p, req.ReaperOptions...)
if err != nil {
return nil, fmt.Errorf("%w: creating reaper failed", err)
}
Expand Down Expand Up @@ -1337,7 +1344,7 @@ func (p *DockerProvider) CreateNetwork(ctx context.Context, req NetworkRequest)
var termSignal chan bool
if !req.SkipReaper {
sessionID := sessionID()
r, err := NewReaper(context.WithValue(ctx, dockerHostContextKey, p.host), sessionID.String(), p, req.ReaperImage)
r, err := newReaper(context.WithValue(ctx, dockerHostContextKey, p.host), sessionID.String(), p, req.ReaperOptions...)
if err != nil {
return nil, fmt.Errorf("%w: creating network reaper failed", err)
}
Expand Down
6 changes: 4 additions & 2 deletions network.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package testcontainers

import (
"context"

"github.com/docker/docker/api/types/network"

"github.com/docker/docker/api/types"
Expand Down Expand Up @@ -39,6 +40,7 @@ type NetworkRequest struct {
Attachable bool
IPAM *network.IPAM

SkipReaper bool // indicates whether we skip setting up a reaper for this
ReaperImage string //alternative reaper registry
SkipReaper bool // indicates whether we skip setting up a reaper for this
ReaperImage string // Deprecated: use WithImageName ContainerOption instead. Alternative reaper registry
ReaperOptions []ContainerOption // Reaper options to use for this network
}
27 changes: 22 additions & 5 deletions reaper.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,13 @@ type ReaperProvider interface {
}

// NewReaper creates a Reaper with a sessionID to identify containers and a provider to use
// Deprecated: it's not possible to create a reaper anymore.
func NewReaper(ctx context.Context, sessionID string, provider ReaperProvider, reaperImageName string) (*Reaper, error) {
return newReaper(ctx, sessionID, provider, WithImageName(reaperImageName))
}

// newReaper creates a Reaper with a sessionID to identify containers and a provider to use
func newReaper(ctx context.Context, sessionID string, provider ReaperProvider, opts ...ContainerOption) (*Reaper, error) {
mutex.Lock()
defer mutex.Unlock()
// If reaper already exists re-use it
Expand All @@ -58,19 +64,30 @@ func NewReaper(ctx context.Context, sessionID string, provider ReaperProvider, r

listeningPort := nat.Port("8080/tcp")

reaperOpts := containerOptions{}

for _, opt := range opts {
opt(&reaperOpts)
}

req := ContainerRequest{
Image: reaperImage(reaperImageName),
Image: reaperImage(reaperOpts.ImageName),
ExposedPorts: []string{string(listeningPort)},
NetworkMode: Bridge,
Labels: map[string]string{
TestcontainerLabelIsReaper: "true",
},
SkipReaper: true,
Mounts: Mounts(BindMount(dockerHost, "/var/run/docker.sock")),
AutoRemove: true,
WaitingFor: wait.ForListeningPort(listeningPort),
SkipReaper: true,
RegistryCred: reaperOpts.RegistryCredentials,
Mounts: Mounts(BindMount(dockerHost, "/var/run/docker.sock")),
AutoRemove: true,
WaitingFor: wait.ForListeningPort(listeningPort),
ReaperOptions: opts,
}

// keep backwards compatibility
req.ReaperImage = req.Image

// include reaper-specific labels to the reaper container
for k, v := range reaper.Labels() {
req.Labels[k] = v
Expand Down
49 changes: 48 additions & 1 deletion reaper_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ func (m *mockReaperProvider) Config() TestContainersConfig {
func createContainerRequest(customize func(ContainerRequest) ContainerRequest) ContainerRequest {
req := ContainerRequest{
Image: "reaperImage",
ReaperImage: "reaperImage",
ExposedPorts: []string{"8080/tcp"},
Labels: map[string]string{
TestcontainerLabel: "true",
Expand All @@ -44,6 +45,9 @@ func createContainerRequest(customize func(ContainerRequest) ContainerRequest) C
AutoRemove: true,
WaitingFor: wait.ForListeningPort(nat.Port("8080/tcp")),
NetworkMode: "bridge",
ReaperOptions: []ContainerOption{
WithImageName("reaperImage"),
},
}
if customize == nil {
return req
Expand All @@ -53,6 +57,7 @@ func createContainerRequest(customize func(ContainerRequest) ContainerRequest) C
}

func Test_NewReaper(t *testing.T) {
defer func() { reaper = nil }()

type cases struct {
name string
Expand Down Expand Up @@ -86,6 +91,16 @@ func Test_NewReaper(t *testing.T) {
config: TestContainersConfig{},
ctx: context.WithValue(context.TODO(), dockerHostContextKey, "unix:///value/in/context.sock"),
},
{
name: "with registry credentials",
req: createContainerRequest(func(req ContainerRequest) ContainerRequest {
creds := "registry-creds"
req.RegistryCred = creds
req.ReaperOptions = append(req.ReaperOptions, WithRegistryCredentials(creds))
return req
}),
config: TestContainersConfig{},
},
}

for _, test := range tests {
Expand All @@ -100,7 +115,7 @@ func Test_NewReaper(t *testing.T) {
test.ctx = context.TODO()
}

_, err := NewReaper(test.ctx, "sessionId", provider, "reaperImage")
_, err := newReaper(test.ctx, "sessionId", provider, test.req.ReaperOptions...)
// we should have errored out see mockReaperProvider.RunContainer
assert.EqualError(t, err, "expected")

Expand All @@ -110,6 +125,8 @@ func Test_NewReaper(t *testing.T) {
}

func Test_ExtractDockerHost(t *testing.T) {
defer func() { reaper = nil }()

t.Run("Docker Host as environment variable", func(t *testing.T) {
t.Setenv("TESTCONTAINERS_DOCKER_SOCKET_OVERRIDE", "/path/to/docker.sock")
host := extractDockerHost(context.Background())
Expand Down Expand Up @@ -147,3 +164,33 @@ func Test_ExtractDockerHost(t *testing.T) {
assert.Equal(t, "/this/is/a/sample.sock", host)
})
}

func Test_ReaperForNetwork(t *testing.T) {
defer func() { reaper = nil }()

ctx := context.Background()

networkName := "test-network-with-custom-reaper"

req := GenericNetworkRequest{
NetworkRequest: NetworkRequest{
Name: networkName,
CheckDuplicate: true,
ReaperOptions: []ContainerOption{
WithRegistryCredentials("credentials"),
WithImageName("reaperImage"),
},
},
}

provider := &mockReaperProvider{
config: TestContainersConfig{},
}

_, err := newReaper(ctx, "sessionId", provider, req.ReaperOptions...)
assert.EqualError(t, err, "expected")

assert.Equal(t, "credentials", provider.req.RegistryCred)
assert.Equal(t, "reaperImage", provider.req.Image)
assert.Equal(t, "reaperImage", provider.req.ReaperImage)
}