Initial QSfera import
This commit is contained in:
+561
@@ -0,0 +1,561 @@
|
||||
package testcontainers
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"maps"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/cpuguy83/dockercfg"
|
||||
"github.com/google/uuid"
|
||||
"github.com/moby/go-archive"
|
||||
"github.com/moby/moby/api/types/container"
|
||||
"github.com/moby/moby/api/types/network"
|
||||
"github.com/moby/moby/api/types/registry"
|
||||
"github.com/moby/moby/client"
|
||||
"github.com/moby/patternmatcher/ignorefile"
|
||||
|
||||
tcexec "github.com/testcontainers/testcontainers-go/exec"
|
||||
"github.com/testcontainers/testcontainers-go/internal/core"
|
||||
"github.com/testcontainers/testcontainers-go/log"
|
||||
"github.com/testcontainers/testcontainers-go/wait"
|
||||
)
|
||||
|
||||
// Deprecated: Use [Container]
|
||||
//
|
||||
// DeprecatedContainer shows methods that were supported before, but are now deprecated
|
||||
type DeprecatedContainer interface {
|
||||
GetHostEndpoint(ctx context.Context, port string) (string, string, error)
|
||||
GetIPAddress(ctx context.Context) (string, error)
|
||||
LivenessCheckPorts(ctx context.Context) (network.PortSet, error)
|
||||
Terminate(ctx context.Context) error
|
||||
}
|
||||
|
||||
// Container allows getting info about and controlling a single container instance
|
||||
type Container interface {
|
||||
GetContainerID() string // get the container id from the provider
|
||||
Endpoint(context.Context, string) (string, error) // get proto://ip:port string for the lowest exposed port
|
||||
PortEndpoint(ctx context.Context, port string, proto string) (string, error) // get proto://ip:port string for the given exposed port
|
||||
Host(context.Context) (string, error) // get host where the container port is exposed
|
||||
Inspect(context.Context) (*container.InspectResponse, error) // get container info
|
||||
MappedPort(context.Context, string) (network.Port, error) // get externally mapped port for a container port
|
||||
Ports(context.Context) (network.PortMap, error) // Deprecated: Use c.Inspect(ctx).NetworkSettings.Ports instead
|
||||
SessionID() string // get session id
|
||||
IsRunning() bool // IsRunning returns true if the container is running, false otherwise.
|
||||
Start(context.Context) error // start the container
|
||||
Stop(context.Context, *time.Duration) error // stop the container
|
||||
|
||||
// Terminate stops and removes the container and its image if it was built and not flagged as kept.
|
||||
Terminate(ctx context.Context, opts ...TerminateOption) error
|
||||
|
||||
Logs(context.Context) (io.ReadCloser, error) // Get logs of the container
|
||||
FollowOutput(LogConsumer) // Deprecated: it will be removed in the next major release
|
||||
StartLogProducer(context.Context, ...LogProductionOption) error // Deprecated: Use the ContainerRequest instead
|
||||
StopLogProducer() error // Deprecated: it will be removed in the next major release
|
||||
Name(context.Context) (string, error) // Deprecated: Use c.Inspect(ctx).Name instead
|
||||
State(context.Context) (*container.State, error) // returns container's running state
|
||||
Networks(context.Context) ([]string, error) // get container networks
|
||||
NetworkAliases(context.Context) (map[string][]string, error) // get container network aliases for a network
|
||||
Exec(ctx context.Context, cmd []string, options ...tcexec.ProcessOption) (int, io.Reader, error)
|
||||
ContainerIP(context.Context) (string, error) // get container ip
|
||||
ContainerIPs(context.Context) ([]string, error) // get all container IPs
|
||||
CopyToContainer(ctx context.Context, fileContent []byte, containerFilePath string, fileMode int64) error
|
||||
CopyDirToContainer(ctx context.Context, hostDirPath string, containerParentPath string, fileMode int64) error
|
||||
CopyFileToContainer(ctx context.Context, hostFilePath string, containerFilePath string, fileMode int64) error
|
||||
CopyFileFromContainer(ctx context.Context, filePath string) (io.ReadCloser, error)
|
||||
GetLogProductionErrorChannel() <-chan error
|
||||
}
|
||||
|
||||
// ImageBuildInfo defines what is needed to build an image
|
||||
type ImageBuildInfo interface {
|
||||
BuildOptions() (client.ImageBuildOptions, error) // converts the ImageBuildInfo to a build.ImageBuildOptions
|
||||
GetContext() (io.Reader, error) // the path to the build context
|
||||
GetDockerfile() string // the relative path to the Dockerfile, including the file itself
|
||||
GetRepo() string // get repo label for image
|
||||
GetTag() string // get tag label for image
|
||||
BuildLogWriter() io.Writer // for output of build log, use io.Discard to disable the output
|
||||
ShouldBuildImage() bool // return true if the image needs to be built
|
||||
GetBuildArgs() map[string]*string // return the environment args used to build the Dockerfile
|
||||
GetAuthConfigs() map[string]registry.AuthConfig // Deprecated. Testcontainers will detect registry credentials automatically. Return the auth configs to be able to pull from an authenticated docker registry
|
||||
}
|
||||
|
||||
// FromDockerfile represents the parameters needed to build an image from a Dockerfile
|
||||
// rather than using a pre-built one
|
||||
type FromDockerfile struct {
|
||||
Context string // the path to the context of the docker build
|
||||
ContextArchive io.ReadSeeker // the tar archive file to send to docker that contains the build context
|
||||
Dockerfile string // the path from the context to the Dockerfile for the image, defaults to "Dockerfile"
|
||||
Repo string // the repo label for image, defaults to UUID
|
||||
Tag string // the tag label for image, defaults to UUID
|
||||
BuildArgs map[string]*string // enable user to pass build args to docker daemon
|
||||
PrintBuildLog bool // Deprecated: Use BuildLogWriter instead
|
||||
BuildLogWriter io.Writer // for output of build log, defaults to io.Discard
|
||||
AuthConfigs map[string]registry.AuthConfig // Deprecated. Testcontainers will detect registry credentials automatically. Enable auth configs to be able to pull from an authenticated docker registry
|
||||
// KeepImage describes whether DockerContainer.Terminate should not delete the
|
||||
// container image. Useful for images that are built from a Dockerfile and take a
|
||||
// long time to build. Keeping the image also Docker to reuse it.
|
||||
KeepImage bool
|
||||
// BuildOptionsModifier Modifier for the build options before image build. Use it for
|
||||
// advanced configurations while building the image. Please consider that the modifier
|
||||
// is called after the default build options are set.
|
||||
BuildOptionsModifier func(*client.ImageBuildOptions)
|
||||
}
|
||||
|
||||
type ContainerFile struct {
|
||||
HostFilePath string // If Reader is present, HostFilePath is ignored
|
||||
Reader io.Reader // If Reader is present, HostFilePath is ignored
|
||||
ContainerFilePath string
|
||||
FileMode int64
|
||||
}
|
||||
|
||||
// validate validates the ContainerFile
|
||||
func (c *ContainerFile) validate() error {
|
||||
if c.HostFilePath == "" && c.Reader == nil {
|
||||
return errors.New("either HostFilePath or Reader must be specified")
|
||||
}
|
||||
|
||||
if c.ContainerFilePath == "" {
|
||||
return errors.New("ContainerFilePath must be specified")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ContainerRequest represents the parameters used to get a running container
|
||||
type ContainerRequest struct {
|
||||
FromDockerfile
|
||||
HostAccessPorts []int
|
||||
Image string
|
||||
ImageSubstitutors []ImageSubstitutor
|
||||
Entrypoint []string
|
||||
Env map[string]string
|
||||
ExposedPorts []string // allow specifying protocol info
|
||||
Cmd []string
|
||||
Labels map[string]string
|
||||
Mounts ContainerMounts
|
||||
Tmpfs map[string]string
|
||||
RegistryCred string // Deprecated: Testcontainers will detect registry credentials automatically
|
||||
WaitingFor wait.Strategy
|
||||
Name string // for specifying container name
|
||||
Hostname string // Deprecated: Use [ConfigModifier] instead. S
|
||||
WorkingDir string // Deprecated: Use [ConfigModifier] instead. Specify the working directory of the container
|
||||
ExtraHosts []string // Deprecated: Use HostConfigModifier instead
|
||||
Privileged bool // Deprecated: Use [HostConfigModifier] instead. For starting privileged container
|
||||
Networks []string // for specifying network names
|
||||
NetworkAliases map[string][]string // for specifying network aliases
|
||||
NetworkMode container.NetworkMode // Deprecated: Use HostConfigModifier instead
|
||||
Resources container.Resources // Deprecated: Use HostConfigModifier instead
|
||||
Files []ContainerFile // files which will be copied when container starts
|
||||
User string // Deprecated: Use [ConfigModifier] instead. For specifying uid:gid
|
||||
SkipReaper bool // Deprecated: The reaper is globally controlled by the .testcontainers.properties file or the TESTCONTAINERS_RYUK_DISABLED environment variable
|
||||
ReaperImage string // Deprecated: use WithImageName ContainerOption instead. Alternative reaper image
|
||||
ReaperOptions []ContainerOption // Deprecated: the reaper is configured at the properties level, for an entire test session
|
||||
AutoRemove bool // Deprecated: Use HostConfigModifier instead. 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 // Deprecated: Use HostConfigModifier instead
|
||||
ShmSize int64 // Deprecated: Use [HostConfigModifier] instead. Amount of memory shared with the host (in bytes)
|
||||
CapAdd []string // Deprecated: Use HostConfigModifier instead. Add Linux capabilities
|
||||
CapDrop []string // Deprecated: Use HostConfigModifier instead. Drop Linux capabilities
|
||||
ConfigModifier func(*container.Config) // Modifier for the config before container creation
|
||||
HostConfigModifier func(*container.HostConfig) // Modifier for the host config before container creation
|
||||
EndpointSettingsModifier func(map[string]*network.EndpointSettings) // Modifier for the network settings before container creation
|
||||
LifecycleHooks []ContainerLifecycleHooks // define hooks to be executed during container lifecycle
|
||||
LogConsumerCfg *LogConsumerConfig // define the configuration for the log producer and its log consumers to follow the logs
|
||||
}
|
||||
|
||||
// sessionID returns the session ID for the container request.
|
||||
func (c *ContainerRequest) sessionID() string {
|
||||
if sessionID := c.Labels[core.LabelSessionID]; sessionID != "" {
|
||||
return sessionID
|
||||
}
|
||||
|
||||
return core.SessionID()
|
||||
}
|
||||
|
||||
// containerOptions functional options for a container
|
||||
type containerOptions struct {
|
||||
ImageName string
|
||||
RegistryCredentials string // Deprecated: Testcontainers will detect registry credentials automatically
|
||||
}
|
||||
|
||||
// Deprecated: it will be removed in the next major release
|
||||
// functional option for setting the reaper image
|
||||
type ContainerOption func(*containerOptions)
|
||||
|
||||
// Deprecated: it will be removed in the next major release
|
||||
// WithImageName sets the reaper image name
|
||||
func WithImageName(imageName string) ContainerOption {
|
||||
return func(o *containerOptions) {
|
||||
o.ImageName = imageName
|
||||
}
|
||||
}
|
||||
|
||||
// Deprecated: Testcontainers will detect registry credentials automatically, and it will be removed in the next major release
|
||||
// WithRegistryCredentials sets the reaper registry credentials
|
||||
func WithRegistryCredentials(registryCredentials string) ContainerOption {
|
||||
return func(o *containerOptions) {
|
||||
o.RegistryCredentials = registryCredentials
|
||||
}
|
||||
}
|
||||
|
||||
// Validate ensures that the ContainerRequest does not have invalid parameters configured to it
|
||||
// ex. make sure you are not specifying both an image as well as a context
|
||||
func (c *ContainerRequest) Validate() error {
|
||||
validationMethods := []func() error{
|
||||
c.validateContextAndImage,
|
||||
c.validateContextOrImageIsSpecified,
|
||||
c.validateMounts,
|
||||
}
|
||||
|
||||
var err error
|
||||
for _, validationMethod := range validationMethods {
|
||||
err = validationMethod()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetContext retrieve the build context for the request
|
||||
// Must be closed when no longer needed.
|
||||
func (c *ContainerRequest) GetContext() (io.Reader, error) {
|
||||
includes := []string{"."}
|
||||
|
||||
if c.ContextArchive != nil {
|
||||
return c.ContextArchive, nil
|
||||
}
|
||||
|
||||
// always pass context as absolute path
|
||||
abs, err := filepath.Abs(c.Context)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error getting absolute path: %w", err)
|
||||
}
|
||||
c.Context = abs
|
||||
|
||||
dockerIgnoreExists, excluded, err := parseDockerIgnore(abs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if dockerIgnoreExists {
|
||||
// only add .dockerignore if it exists
|
||||
includes = append(includes, ".dockerignore")
|
||||
}
|
||||
|
||||
includes = append(includes, c.GetDockerfile())
|
||||
|
||||
buildContext, err := archive.TarWithOptions(
|
||||
c.Context,
|
||||
&archive.TarOptions{ExcludePatterns: excluded, IncludeFiles: includes},
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return buildContext, nil
|
||||
}
|
||||
|
||||
// parseDockerIgnore returns if the file exists, the excluded files and an error if any
|
||||
func parseDockerIgnore(targetDir string) (bool, []string, error) {
|
||||
// based on https://github.com/docker/cli/blob/master/cli/command/image/build/dockerignore.go#L14
|
||||
fileLocation := filepath.Join(targetDir, ".dockerignore")
|
||||
var excluded []string
|
||||
exists := false
|
||||
if f, openErr := os.Open(fileLocation); openErr == nil {
|
||||
defer f.Close()
|
||||
|
||||
exists = true
|
||||
|
||||
var err error
|
||||
excluded, err = ignorefile.ReadAll(f)
|
||||
if err != nil {
|
||||
return true, excluded, fmt.Errorf("error reading .dockerignore: %w", err)
|
||||
}
|
||||
}
|
||||
return exists, excluded, nil
|
||||
}
|
||||
|
||||
// GetBuildArgs returns the env args to be used when creating from Dockerfile
|
||||
func (c *ContainerRequest) GetBuildArgs() map[string]*string {
|
||||
return c.BuildArgs
|
||||
}
|
||||
|
||||
// GetDockerfile returns the Dockerfile from the ContainerRequest, defaults to "Dockerfile".
|
||||
// Sets FromDockerfile.Dockerfile to the default if blank.
|
||||
func (c *ContainerRequest) GetDockerfile() string {
|
||||
if c.Dockerfile == "" {
|
||||
c.Dockerfile = "Dockerfile"
|
||||
}
|
||||
|
||||
return c.Dockerfile
|
||||
}
|
||||
|
||||
// GetRepo returns the Repo label for image from the ContainerRequest, defaults to UUID.
|
||||
// Sets FromDockerfile.Repo to the default value if blank.
|
||||
func (c *ContainerRequest) GetRepo() string {
|
||||
if c.Repo == "" {
|
||||
c.Repo = uuid.NewString()
|
||||
}
|
||||
|
||||
return strings.ToLower(c.Repo)
|
||||
}
|
||||
|
||||
// GetTag returns the Tag label for image from the ContainerRequest, defaults to UUID.
|
||||
// Sets FromDockerfile.Tag to the default value if blank.
|
||||
func (c *ContainerRequest) GetTag() string {
|
||||
if c.Tag == "" {
|
||||
c.Tag = uuid.NewString()
|
||||
}
|
||||
|
||||
return strings.ToLower(c.Tag)
|
||||
}
|
||||
|
||||
// Deprecated: Testcontainers will detect registry credentials automatically, and it will be removed in the next major release.
|
||||
// GetAuthConfigs returns the auth configs to be able to pull from an authenticated docker registry.
|
||||
// Panics if an error occurs.
|
||||
func (c *ContainerRequest) GetAuthConfigs() map[string]registry.AuthConfig {
|
||||
auth, err := getAuthConfigsFromDockerfile(c)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("failed to get auth configs from Dockerfile: %v", err))
|
||||
}
|
||||
return auth
|
||||
}
|
||||
|
||||
// dockerFileImages returns the images from the request Dockerfile.
|
||||
func (c *ContainerRequest) dockerFileImages() ([]string, error) {
|
||||
if c.ContextArchive == nil {
|
||||
// Source is a directory, we can read the Dockerfile directly.
|
||||
images, err := core.ExtractImagesFromDockerfile(filepath.Join(c.Context, c.GetDockerfile()), c.GetBuildArgs())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("extract images from Dockerfile: %w", err)
|
||||
}
|
||||
|
||||
return images, nil
|
||||
}
|
||||
|
||||
// Source is an archive, we need to read it to get the Dockerfile.
|
||||
dockerFile := c.GetDockerfile()
|
||||
tr := tar.NewReader(c.ContextArchive)
|
||||
|
||||
for {
|
||||
hdr, err := tr.Next()
|
||||
if err != nil {
|
||||
if errors.Is(err, io.EOF) {
|
||||
return nil, fmt.Errorf("dockerfile %q not found in context archive", dockerFile)
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("reading tar archive: %w", err)
|
||||
}
|
||||
|
||||
if hdr.Name != dockerFile {
|
||||
continue
|
||||
}
|
||||
|
||||
images, err := core.ExtractImagesFromReader(tr, c.GetBuildArgs())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("extract images from Dockerfile: %w", err)
|
||||
}
|
||||
|
||||
// Reset the archive to the beginning.
|
||||
if _, err := c.ContextArchive.Seek(0, io.SeekStart); err != nil {
|
||||
return nil, fmt.Errorf("seek context archive to start: %w", err)
|
||||
}
|
||||
|
||||
return images, nil
|
||||
}
|
||||
}
|
||||
|
||||
// getAuthConfigsFromDockerfile returns the auth configs to be able to pull from an authenticated docker registry
|
||||
func getAuthConfigsFromDockerfile(c *ContainerRequest) (map[string]registry.AuthConfig, error) {
|
||||
images, err := c.dockerFileImages()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("docker file images: %w", err)
|
||||
}
|
||||
|
||||
// Get the auth configs once for all images as it can be a time-consuming operation.
|
||||
configs, err := getDockerAuthConfigs()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
authConfigs := map[string]registry.AuthConfig{}
|
||||
for _, image := range images {
|
||||
registry, authConfig, err := dockerImageAuth(context.Background(), image, configs)
|
||||
if err != nil {
|
||||
if !errors.Is(err, dockercfg.ErrCredentialsNotFound) {
|
||||
return nil, fmt.Errorf("docker image auth %q: %w", image, err)
|
||||
}
|
||||
|
||||
// Credentials not found no config to add.
|
||||
continue
|
||||
}
|
||||
|
||||
authConfigs[registry] = authConfig
|
||||
}
|
||||
|
||||
return authConfigs, nil
|
||||
}
|
||||
|
||||
func (c *ContainerRequest) ShouldBuildImage() bool {
|
||||
return c.Context != "" || c.ContextArchive != nil
|
||||
}
|
||||
|
||||
func (c *ContainerRequest) ShouldKeepBuiltImage() bool {
|
||||
return c.KeepImage
|
||||
}
|
||||
|
||||
// BuildLogWriter returns the io.Writer for output of log when building a Docker image from
|
||||
// a Dockerfile. It returns the BuildLogWriter from the ContainerRequest, defaults to io.Discard.
|
||||
// For backward compatibility, if BuildLogWriter is default and PrintBuildLog is true,
|
||||
// the function returns os.Stderr.
|
||||
//
|
||||
//nolint:staticcheck //FIXME
|
||||
func (c *ContainerRequest) BuildLogWriter() io.Writer {
|
||||
if c.FromDockerfile.BuildLogWriter != nil {
|
||||
return c.FromDockerfile.BuildLogWriter
|
||||
}
|
||||
if c.PrintBuildLog {
|
||||
c.FromDockerfile.BuildLogWriter = os.Stderr
|
||||
} else {
|
||||
c.FromDockerfile.BuildLogWriter = io.Discard
|
||||
}
|
||||
return c.FromDockerfile.BuildLogWriter
|
||||
}
|
||||
|
||||
// BuildOptions returns the image build options when building a Docker image from a Dockerfile.
|
||||
// It will apply some defaults and finally call the BuildOptionsModifier from the FromDockerfile struct,
|
||||
// if set.
|
||||
func (c *ContainerRequest) BuildOptions() (client.ImageBuildOptions, error) {
|
||||
buildOptions := client.ImageBuildOptions{
|
||||
Remove: true,
|
||||
ForceRemove: true,
|
||||
}
|
||||
|
||||
if c.BuildOptionsModifier != nil {
|
||||
c.BuildOptionsModifier(&buildOptions)
|
||||
}
|
||||
|
||||
// apply mandatory values after the modifier
|
||||
buildOptions.BuildArgs = c.GetBuildArgs()
|
||||
buildOptions.Dockerfile = c.GetDockerfile()
|
||||
|
||||
// Make sure the auth configs from the Dockerfile are set right after the user-defined build options.
|
||||
authsFromDockerfile, err := getAuthConfigsFromDockerfile(c)
|
||||
if err != nil {
|
||||
return client.ImageBuildOptions{}, fmt.Errorf("auth configs from Dockerfile: %w", err)
|
||||
}
|
||||
|
||||
if buildOptions.AuthConfigs == nil {
|
||||
buildOptions.AuthConfigs = map[string]registry.AuthConfig{}
|
||||
}
|
||||
|
||||
maps.Copy(buildOptions.AuthConfigs, authsFromDockerfile)
|
||||
|
||||
// make sure the first tag is the one defined in the ContainerRequest
|
||||
tag := fmt.Sprintf("%s:%s", c.GetRepo(), c.GetTag())
|
||||
|
||||
// apply substitutors to the built image
|
||||
for _, is := range c.ImageSubstitutors {
|
||||
modifiedTag, err := is.Substitute(tag)
|
||||
if err != nil {
|
||||
return client.ImageBuildOptions{}, fmt.Errorf("failed to substitute image %s with %s: %w", tag, is.Description(), err)
|
||||
}
|
||||
|
||||
if modifiedTag != tag {
|
||||
log.Printf("✍🏼 Replacing image with %s. From: %s to %s\n", is.Description(), tag, modifiedTag)
|
||||
tag = modifiedTag
|
||||
}
|
||||
}
|
||||
|
||||
if len(buildOptions.Tags) > 0 {
|
||||
// prepend the tag
|
||||
buildOptions.Tags = append([]string{tag}, buildOptions.Tags...)
|
||||
} else {
|
||||
buildOptions.Tags = []string{tag}
|
||||
}
|
||||
|
||||
if !c.ShouldKeepBuiltImage() {
|
||||
dst := GenericLabels()
|
||||
if err = core.MergeCustomLabels(dst, c.Labels); err != nil {
|
||||
return client.ImageBuildOptions{}, err
|
||||
}
|
||||
if err = core.MergeCustomLabels(dst, buildOptions.Labels); err != nil {
|
||||
return client.ImageBuildOptions{}, err
|
||||
}
|
||||
buildOptions.Labels = dst
|
||||
}
|
||||
|
||||
// Do this as late as possible to ensure we don't leak the context on error/panic.
|
||||
buildContext, err := c.GetContext()
|
||||
if err != nil {
|
||||
return client.ImageBuildOptions{}, err
|
||||
}
|
||||
|
||||
buildOptions.Context = buildContext
|
||||
|
||||
return buildOptions, nil
|
||||
}
|
||||
|
||||
func (c *ContainerRequest) validateContextAndImage() error {
|
||||
if c.Context != "" && c.Image != "" {
|
||||
return errors.New("you cannot specify both an Image and Context in a ContainerRequest")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *ContainerRequest) validateContextOrImageIsSpecified() error {
|
||||
if c.Context == "" && c.ContextArchive == nil && c.Image == "" {
|
||||
return errors.New("you must specify either a build context or an image")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// validateMounts ensures that the mounts do not have duplicate targets.
|
||||
// It will check the Mounts and HostConfigModifier.Binds fields.
|
||||
func (c *ContainerRequest) validateMounts() error {
|
||||
targets := make(map[string]bool, len(c.Mounts))
|
||||
|
||||
for idx := range c.Mounts {
|
||||
m := c.Mounts[idx]
|
||||
targetPath := m.Target.Target()
|
||||
if targets[targetPath] {
|
||||
return fmt.Errorf("%w: %s", ErrDuplicateMountTarget, targetPath)
|
||||
}
|
||||
targets[targetPath] = true
|
||||
}
|
||||
|
||||
if c.HostConfigModifier == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
hostConfig := container.HostConfig{}
|
||||
|
||||
c.HostConfigModifier(&hostConfig)
|
||||
|
||||
if len(hostConfig.Binds) > 0 {
|
||||
for _, bind := range hostConfig.Binds {
|
||||
parts := strings.Split(bind, ":")
|
||||
if len(parts) != 2 && len(parts) != 3 {
|
||||
return fmt.Errorf("%w: %s", ErrInvalidBindMount, bind)
|
||||
}
|
||||
targetPath := parts[1]
|
||||
if targets[targetPath] {
|
||||
return fmt.Errorf("%w: %s", ErrDuplicateMountTarget, targetPath)
|
||||
}
|
||||
targets[targetPath] = true
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user