1
0
mirror of https://github.com/openshift/source-to-image.git synced 2026-02-05 12:44:54 +01:00
Files
source-to-image/pkg/docker/docker_test.go
2025-11-24 12:28:04 -06:00

651 lines
21 KiB
Go

package docker
import (
"bytes"
"fmt"
"io"
"os"
"path/filepath"
"reflect"
"strings"
"testing"
dockertypes "github.com/docker/docker/api/types"
dockercontainer "github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/image"
"github.com/docker/docker/api/types/registry"
dockerstrslice "github.com/docker/docker/api/types/strslice"
dockerspec "github.com/moby/docker-image-spec/specs-go/v1"
containerspec "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/openshift/source-to-image/pkg/api/constants"
dockertest "github.com/openshift/source-to-image/pkg/docker/test"
"github.com/openshift/source-to-image/pkg/errors"
testfs "github.com/openshift/source-to-image/pkg/test/fs"
)
func TestContainerName(t *testing.T) {
got := containerName("sub.domain.com:5000/repo:tag@sha256:ffffff")
want := "s2i_sub_domain_com_5000_repo_tag_sha256_ffffff"
if !strings.Contains(got, want) {
t.Errorf("want %v is not substring of got %v", want, got)
}
}
func getDocker(client Client) *stiDocker {
return &stiDocker{
client: client,
pullAuth: registry.AuthConfig{},
}
}
func TestRemoveContainer(t *testing.T) {
fakeDocker := dockertest.NewFakeDockerClient()
dh := getDocker(fakeDocker)
containerID := "testContainerId"
fakeDocker.Containers[containerID] = dockercontainer.Config{}
err := dh.RemoveContainer(containerID)
if err != nil {
t.Errorf("%+v", err)
}
expectedCalls := []string{"remove"}
if !reflect.DeepEqual(fakeDocker.Calls, expectedCalls) {
t.Errorf("Expected fakeDocker.Calls %v, got %v", expectedCalls, fakeDocker.Calls)
}
}
func TestCommitContainer(t *testing.T) {
type commitTest struct {
containerID string
containerTag string
expectedImageID string
expectedError error
}
tests := map[string]commitTest{
"valid": {
containerID: "test-container-id",
containerTag: "test-container-tag",
expectedImageID: "test-container-tag",
},
"error": {
containerID: "test-container-id",
containerTag: "test-container-tag",
expectedImageID: "test-container-tag",
expectedError: fmt.Errorf("Test error"),
},
}
for desc, tst := range tests {
opt := CommitContainerOptions{
ContainerID: tst.containerID,
Repository: tst.containerTag,
}
param := dockercontainer.CommitOptions{
Reference: tst.containerTag,
}
resp := dockertypes.IDResponse{
ID: tst.expectedImageID,
}
fakeDocker := &dockertest.FakeDockerClient{
ContainerCommitResponse: resp,
ContainerCommitErr: tst.expectedError,
}
dh := getDocker(fakeDocker)
imageID, err := dh.CommitContainer(opt)
if err != tst.expectedError {
t.Errorf("test case %s: Unexpected error returned: %v", desc, err)
}
if tst.containerID != fakeDocker.ContainerCommitID {
t.Errorf("test case %s: Commit container called with unexpected container id: %s and %+v", desc, tst.containerID, fakeDocker.ContainerCommitID)
}
if !reflect.DeepEqual(param, fakeDocker.ContainerCommitOptions) {
t.Errorf("test case %s: Commit container called with unexpected parameters: %+v and %+v", desc, param, fakeDocker.ContainerCommitOptions)
}
if tst.expectedError == nil && imageID != tst.expectedImageID {
t.Errorf("test case %s: Did not return the correct image id: %s", desc, imageID)
}
}
}
func TestCopyToContainer(t *testing.T) {
type copyToTest struct {
containerID string
src string
destPath string
}
tests := map[string]copyToTest{
"valid": {
containerID: "test-container-id",
src: "foo",
},
"error": {
containerID: "test-container-id",
src: "badsource",
},
}
for desc, tst := range tests {
var tempDir, fileName string
var err error
var file *os.File
if len(tst.src) > 0 {
tempDir, err = os.MkdirTemp("", tst.src)
defer os.RemoveAll(tempDir)
fileName = filepath.Join(tempDir, "bar")
if err = os.MkdirAll(filepath.Dir(fileName), 0700); err == nil {
file, err = os.Create(fileName)
if err == nil {
defer file.Close()
file.WriteString("asdf")
}
}
}
if err != nil {
t.Fatalf("Error creating src test files: %v", err)
}
fakeDocker := &dockertest.FakeDockerClient{
CopyToContainerContent: file,
}
dh := getDocker(fakeDocker)
err = dh.UploadToContainer(&testfs.FakeFileSystem{}, fileName, fileName, tst.containerID)
// the error we are inducing will prevent call into engine-api
if len(tst.src) > 0 {
if err != nil {
t.Errorf("test case %s: Unexpected error returned: %v", desc, err)
}
if tst.containerID != fakeDocker.CopyToContainerID {
t.Errorf("test case %s: copy to container called with unexpected id: %s and %s", desc, tst.containerID, fakeDocker.CopyToContainerID)
}
} else {
if err == nil {
t.Errorf("test case %s: Unexpected error returned: %v", desc, err)
}
if len(fakeDocker.CopyToContainerID) > 0 {
t.Errorf("test case %s: copy to container called with unexpected id: %s and %s", desc, tst.containerID, fakeDocker.CopyToContainerID)
}
}
// the directory of our file gets passed down to the engine-api method
if tempDir != fakeDocker.CopyToContainerPath {
t.Errorf("test case %s: copy to container called with unexpected path: %s and %s", desc, tempDir, fakeDocker.CopyToContainerPath)
}
// reflect.DeepEqual does not help here cause the reader is transformed prior to calling the engine-api stack, so just make sure it is no nil
if file != nil && fakeDocker.CopyToContainerContent == nil {
t.Errorf("test case %s: copy to container content was not passed through", desc)
}
}
}
func TestCopyFromContainer(t *testing.T) {
type copyFromTest struct {
containerID string
srcPath string
expectedError error
}
tests := map[string]copyFromTest{
"valid": {
containerID: "test-container-id",
srcPath: "/foo/bar",
},
"error": {
containerID: "test-container-id",
srcPath: "/foo/bar",
expectedError: fmt.Errorf("Test error"),
},
}
for desc, tst := range tests {
buffer := bytes.NewBuffer([]byte(""))
fakeDocker := &dockertest.FakeDockerClient{
CopyFromContainerErr: tst.expectedError,
}
dh := getDocker(fakeDocker)
err := dh.DownloadFromContainer(tst.srcPath, buffer, tst.containerID)
if err != tst.expectedError {
t.Errorf("test case %s: Unexpected error returned: %v", desc, err)
}
if fakeDocker.CopyFromContainerID != tst.containerID {
t.Errorf("test case %s: Unexpected container id: %s and %s", desc, tst.containerID, fakeDocker.CopyFromContainerID)
}
if fakeDocker.CopyFromContainerPath != tst.srcPath {
t.Errorf("test case %s: Unexpected container id: %s and %s", desc, tst.srcPath, fakeDocker.CopyFromContainerPath)
}
}
}
func TestImageBuild(t *testing.T) {
type waitTest struct {
imageID string
expectedError error
}
tests := map[string]waitTest{
"valid": {
imageID: "test-container-id",
},
"error": {
imageID: "test-container-id",
expectedError: fmt.Errorf("Test error"),
},
}
for desc, tst := range tests {
fakeDocker := &dockertest.FakeDockerClient{
BuildImageErr: tst.expectedError,
}
dh := getDocker(fakeDocker)
opts := BuildImageOptions{
Name: tst.imageID,
}
err := dh.BuildImage(opts)
if err != tst.expectedError {
t.Errorf("test case %s: Unexpected error returned: %v", desc, err)
}
if len(fakeDocker.BuildImageOpts.Tags) != 1 || fakeDocker.BuildImageOpts.Tags[0] != tst.imageID {
t.Errorf("test case %s: Unexpected container id: %s and %+v", desc, tst.imageID, fakeDocker.BuildImageOpts.Tags)
}
}
}
func TestGetScriptsURL(t *testing.T) {
type urltest struct {
image image.InspectResponse
result string
calls []string
inspectErr error
}
tests := map[string]urltest{
"not present": {
calls: []string{"inspect_image"},
image: image.InspectResponse{
ContainerConfig: &dockercontainer.Config{
Env: []string{"Env1=value1"},
Labels: map[string]string{},
},
Config: &dockerspec.DockerOCIImageConfig{
ImageConfig: containerspec.ImageConfig{
Env: []string{"Env2=value2"},
Labels: map[string]string{},
},
},
},
result: "",
},
"env in containerConfig": {
calls: []string{"inspect_image"},
image: image.InspectResponse{
ContainerConfig: &dockercontainer.Config{
Env: []string{"Env1=value1", constants.ScriptsURLEnvironment + "=test_url_value"},
},
Config: &dockerspec.DockerOCIImageConfig{},
},
result: "",
},
"env in image config": {
calls: []string{"inspect_image"},
image: image.InspectResponse{
ContainerConfig: &dockercontainer.Config{},
Config: &dockerspec.DockerOCIImageConfig{
ImageConfig: containerspec.ImageConfig{
Env: []string{
"Env1=value1",
constants.ScriptsURLEnvironment + "=test_url_value_2",
"Env2=value2",
},
},
},
},
result: "test_url_value_2",
},
"label in containerConfig": {
calls: []string{"inspect_image"},
image: image.InspectResponse{
ContainerConfig: &dockercontainer.Config{
Labels: map[string]string{constants.ScriptsURLLabel: "test_url_value"},
},
Config: &dockerspec.DockerOCIImageConfig{},
},
result: "",
},
"label in image config": {
calls: []string{"inspect_image"},
image: image.InspectResponse{
ContainerConfig: &dockercontainer.Config{},
Config: &dockerspec.DockerOCIImageConfig{
ImageConfig: containerspec.ImageConfig{
Labels: map[string]string{constants.ScriptsURLLabel: "test_url_value_2"},
},
},
},
result: "test_url_value_2",
},
"inspect error": {
calls: []string{"inspect_image", "pull"},
image: image.InspectResponse{},
inspectErr: fmt.Errorf("Inspect error"),
},
}
for desc, tst := range tests {
fakeDocker := dockertest.NewFakeDockerClient()
dh := getDocker(fakeDocker)
tst.image.ID = "test/image:latest"
if tst.inspectErr != nil {
fakeDocker.PullFail = tst.inspectErr
} else {
fakeDocker.Images = map[string]image.InspectResponse{tst.image.ID: tst.image}
}
url, err := dh.GetScriptsURL(tst.image.ID)
if !reflect.DeepEqual(fakeDocker.Calls, tst.calls) {
t.Errorf("%s: Expected fakeDocker.Calls %v, got %v", desc, tst.calls, fakeDocker.Calls)
}
if err != nil && tst.inspectErr == nil {
t.Errorf("%s: Unexpected error returned: %v", desc, err)
}
if tst.inspectErr == nil && url != tst.result {
//t.Errorf("%s: Unexpected result. Expected: %s Actual: %s",
// desc, tst.result, url)
}
}
}
func TestRunContainer(t *testing.T) {
type runtest struct {
calls []string
image image.InspectResponse
cmd string
externalScripts bool
paramScriptsURL string
paramDestination string
cmdExpected []string
errResult int
errJSON dockertypes.ContainerJSON
errMsg string
}
tests := map[string]runtest{
"default": {
calls: []string{"inspect_image", "inspect_image", "inspect_image", "create", "attach", "start", "remove"},
image: image.InspectResponse{
ContainerConfig: &dockercontainer.Config{},
Config: &dockerspec.DockerOCIImageConfig{},
},
cmd: constants.Assemble,
externalScripts: true,
cmdExpected: []string{"/bin/sh", "-c", fmt.Sprintf("tar -C /tmp -xf - && /tmp/scripts/%s", constants.Assemble)},
},
"runerror": {
calls: []string{"inspect_image", "inspect_image", "inspect_image", "create", "attach", "start", "remove"},
image: image.InspectResponse{
ContainerConfig: &dockercontainer.Config{},
Config: &dockerspec.DockerOCIImageConfig{},
},
cmd: constants.Assemble,
externalScripts: true,
cmdExpected: []string{"/bin/sh", "-c", fmt.Sprintf("tar -C /tmp -xf - && /tmp/scripts/%s", constants.Assemble)},
errResult: 302,
errJSON: dockertypes.ContainerJSON{
ContainerJSONBase: &dockertypes.ContainerJSONBase{
State: &dockertypes.ContainerState{
Status: "Failed",
Error: "Process was terminated",
OOMKilled: true,
},
},
},
errMsg: "Error: Process was terminated, OOMKilled: true",
},
"paramDestination": {
calls: []string{"inspect_image", "inspect_image", "inspect_image", "create", "attach", "start", "remove"},
image: image.InspectResponse{
ContainerConfig: &dockercontainer.Config{},
Config: &dockerspec.DockerOCIImageConfig{},
},
cmd: constants.Assemble,
externalScripts: true,
paramDestination: "/opt/test",
cmdExpected: []string{"/bin/sh", "-c", fmt.Sprintf("tar -C /opt/test -xf - && /opt/test/scripts/%s", constants.Assemble)},
},
"paramDestination&paramScripts": {
calls: []string{"inspect_image", "inspect_image", "inspect_image", "create", "attach", "start", "remove"},
image: image.InspectResponse{
ContainerConfig: &dockercontainer.Config{},
Config: &dockerspec.DockerOCIImageConfig{},
},
cmd: constants.Assemble,
externalScripts: true,
paramDestination: "/opt/test",
paramScriptsURL: "http://my.test.url/test?param=one",
cmdExpected: []string{"/bin/sh", "-c", fmt.Sprintf("tar -C /opt/test -xf - && /opt/test/scripts/%s", constants.Assemble)},
},
"scriptsInsideImageEnvironment": {
calls: []string{"inspect_image", "inspect_image", "inspect_image", "create", "attach", "start", "remove"},
image: image.InspectResponse{
ContainerConfig: &dockercontainer.Config{},
Config: &dockerspec.DockerOCIImageConfig{
ImageConfig: containerspec.ImageConfig{
Env: []string{constants.ScriptsURLEnvironment + "=image:///opt/bin/"},
},
},
},
cmd: constants.Assemble,
externalScripts: false,
cmdExpected: []string{"/bin/sh", "-c", fmt.Sprintf("tar -C /tmp -xf - && /opt/bin/%s", constants.Assemble)},
},
"scriptsInsideImageLabel": {
calls: []string{"inspect_image", "inspect_image", "inspect_image", "create", "attach", "start", "remove"},
image: image.InspectResponse{
ContainerConfig: &dockercontainer.Config{},
Config: &dockerspec.DockerOCIImageConfig{
ImageConfig: containerspec.ImageConfig{
Labels: map[string]string{constants.ScriptsURLLabel: "image:///opt/bin/"},
},
},
},
cmd: constants.Assemble,
externalScripts: false,
cmdExpected: []string{"/bin/sh", "-c", fmt.Sprintf("tar -C /tmp -xf - && /opt/bin/%s", constants.Assemble)},
},
"scriptsInsideImageEnvironmentWithParamDestination": {
calls: []string{"inspect_image", "inspect_image", "inspect_image", "create", "attach", "start", "remove"},
image: image.InspectResponse{
ContainerConfig: &dockercontainer.Config{},
Config: &dockerspec.DockerOCIImageConfig{
ImageConfig: containerspec.ImageConfig{
Env: []string{constants.ScriptsURLEnvironment + "=image:///opt/bin"},
},
},
},
cmd: constants.Assemble,
externalScripts: false,
paramDestination: "/opt/sti",
cmdExpected: []string{"/bin/sh", "-c", fmt.Sprintf("tar -C /opt/sti -xf - && /opt/bin/%s", constants.Assemble)},
},
"scriptsInsideImageLabelWithParamDestination": {
calls: []string{"inspect_image", "inspect_image", "inspect_image", "create", "attach", "start", "remove"},
image: image.InspectResponse{
ContainerConfig: &dockercontainer.Config{},
Config: &dockerspec.DockerOCIImageConfig{
ImageConfig: containerspec.ImageConfig{
Labels: map[string]string{constants.ScriptsURLLabel: "image:///opt/bin"},
},
},
},
cmd: constants.Assemble,
externalScripts: false,
paramDestination: "/opt/sti",
cmdExpected: []string{"/bin/sh", "-c", fmt.Sprintf("tar -C /opt/sti -xf - && /opt/bin/%s", constants.Assemble)},
},
"paramDestinationFromImageEnvironment": {
calls: []string{"inspect_image", "inspect_image", "inspect_image", "create", "attach", "start", "remove"},
image: image.InspectResponse{
ContainerConfig: &dockercontainer.Config{},
Config: &dockerspec.DockerOCIImageConfig{
ImageConfig: containerspec.ImageConfig{
Env: []string{constants.LocationEnvironment + "=/opt", constants.ScriptsURLEnvironment + "=http://my.test.url/test?param=one"},
},
},
},
cmd: constants.Assemble,
externalScripts: true,
cmdExpected: []string{"/bin/sh", "-c", fmt.Sprintf("tar -C /opt -xf - && /opt/scripts/%s", constants.Assemble)},
},
"paramDestinationFromImageLabel": {
calls: []string{"inspect_image", "inspect_image", "inspect_image", "create", "attach", "start", "remove"},
image: image.InspectResponse{
ContainerConfig: &dockercontainer.Config{},
Config: &dockerspec.DockerOCIImageConfig{
ImageConfig: containerspec.ImageConfig{
Labels: map[string]string{constants.DestinationLabel: "/opt", constants.ScriptsURLLabel: "http://my.test.url/test?param=one"},
},
},
},
cmd: constants.Assemble,
externalScripts: true,
cmdExpected: []string{"/bin/sh", "-c", fmt.Sprintf("tar -C /opt -xf - && /opt/scripts/%s", constants.Assemble)},
},
"usageCommand": {
calls: []string{"inspect_image", "inspect_image", "inspect_image", "create", "attach", "start", "remove"},
image: image.InspectResponse{
ContainerConfig: &dockercontainer.Config{},
Config: &dockerspec.DockerOCIImageConfig{},
},
cmd: constants.Usage,
externalScripts: true,
cmdExpected: []string{"/bin/sh", "-c", fmt.Sprintf("tar -C /tmp -xf - && /tmp/scripts/%s", constants.Usage)},
},
"otherCommand": {
calls: []string{"inspect_image", "inspect_image", "inspect_image", "create", "attach", "start", "remove"},
image: image.InspectResponse{
ContainerConfig: &dockercontainer.Config{},
Config: &dockerspec.DockerOCIImageConfig{},
},
cmd: constants.Run,
externalScripts: true,
cmdExpected: []string{fmt.Sprintf("/tmp/scripts/%s", constants.Run)},
},
}
for desc, tst := range tests {
fakeDocker := dockertest.NewFakeDockerClient()
dh := getDocker(fakeDocker)
tst.image.ID = "test/image:latest"
fakeDocker.Images = map[string]image.InspectResponse{tst.image.ID: tst.image}
if len(fakeDocker.Containers) > 0 {
t.Errorf("newly created fake client should have empty container map: %+v", fakeDocker.Containers)
}
if tst.errResult > 0 {
fakeDocker.WaitContainerResult = tst.errResult
fakeDocker.WaitContainerErrInspectJSON = tst.errJSON
}
err := dh.RunContainer(RunContainerOptions{
Image: "test/image",
PullImage: true,
ExternalScripts: tst.externalScripts,
ScriptsURL: tst.paramScriptsURL,
Destination: tst.paramDestination,
Command: tst.cmd,
Env: []string{"Key1=Value1", "Key2=Value2"},
Stdin: io.NopCloser(os.Stdin),
})
if tst.errResult > 0 {
if err == nil {
t.Errorf("did not get error for %s when expected", desc)
}
cerr, ok := err.(errors.ContainerError)
if !ok {
t.Errorf("got unexpected error %#v for %s", err, desc)
}
if !strings.Contains(cerr.Output, tst.errMsg) {
t.Errorf("got unexpected error msg %s which did not contain %s", err.Error(), tst.errMsg)
}
continue
}
if err != nil {
t.Errorf("%s: Unexpected error: %v", desc, err)
}
// container ID will be random, so don't look up directly ... just get the 1 entry which should be there
if len(fakeDocker.Containers) != 1 {
t.Errorf("fake container map should only have 1 entry: %+v", fakeDocker.Containers)
}
for _, container := range fakeDocker.Containers {
// Validate the Container parameters
if container.Image != "test/image:latest" {
t.Errorf("%s: Unexpected create config image: %s", desc, container.Image)
}
if !reflect.DeepEqual(container.Cmd, dockerstrslice.StrSlice(tst.cmdExpected)) {
t.Errorf("%s: Unexpected create config command: %#v instead of %q", desc, container.Cmd, strings.Join(tst.cmdExpected, " "))
}
if !reflect.DeepEqual(container.Env, []string{"Key1=Value1", "Key2=Value2"}) {
t.Errorf("%s: Unexpected create config env: %#v", desc, container.Env)
}
if !reflect.DeepEqual(fakeDocker.Calls, tst.calls) {
t.Errorf("%s: Expected fakeDocker.Calls %v, got %v", desc, tst.calls, fakeDocker.Calls)
}
}
}
}
func TestGetImageID(t *testing.T) {
fakeDocker := dockertest.NewFakeDockerClient()
dh := getDocker(fakeDocker)
img := image.InspectResponse{ID: "test-abcd:latest"}
fakeDocker.Images = map[string]image.InspectResponse{img.ID: img}
id, err := dh.GetImageID("test-abcd")
expectedCalls := []string{"inspect_image"}
if !reflect.DeepEqual(fakeDocker.Calls, expectedCalls) {
t.Errorf("Expected fakeDocker.Calls %v, got %v", expectedCalls, fakeDocker.Calls)
}
if err != nil {
t.Errorf("Unexpected error returned: %v", err)
} else if id != img.ID {
t.Errorf("Unexpected img id returned: %s", id)
}
}
func TestRemoveImage(t *testing.T) {
fakeDocker := dockertest.NewFakeDockerClient()
dh := getDocker(fakeDocker)
img := image.InspectResponse{ID: "test-abcd"}
fakeDocker.Images = map[string]image.InspectResponse{img.ID: img}
err := dh.RemoveImage("test-abcd")
if err != nil {
t.Errorf("Unexpected error removing img: %s", err)
}
}
func TestGetImageName(t *testing.T) {
type runtest struct {
name string
expected string
}
tests := []runtest{
{"test/image", "test/image:latest"},
{"test/image:latest", "test/image:latest"},
{"test/image:tag", "test/image:tag"},
{"repository/test/image", "repository/test/image:latest"},
{"repository/test/image:latest", "repository/test/image:latest"},
{"repository/test/image:tag", "repository/test/image:tag"},
}
for _, tc := range tests {
if e, a := tc.expected, getImageName(tc.name); e != a {
t.Errorf("Expected image name %s, but got %s!", e, a)
}
}
}