Skip to content

Commit

Permalink
Increase code coverage (kyma-project#529)
Browse files Browse the repository at this point in the history
* Docker unit tests

* Add tests to docker

* Refactoring based on review

 * Moved docker to a separate package
  • Loading branch information
rakesh-garimella authored Jul 31, 2020
1 parent ddfd747 commit 8b5bafa
Show file tree
Hide file tree
Showing 5 changed files with 346 additions and 57 deletions.
102 changes: 72 additions & 30 deletions pkg/installation/docker.go → pkg/docker/docker.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package installation
package docker

import (
"bufio"
Expand All @@ -18,41 +18,90 @@ import (
docker "github.com/docker/docker/client"
"github.com/docker/docker/pkg/archive"
"github.com/kyma-project/cli/internal/minikube"
"github.com/kyma-project/cli/pkg/step"
)

const (
defaultRegistry = "index.docker.io"
)

// DockerErrorMessage is used to parse error messages coming from Docker
type DockerErrorMessage struct {
type dockerClient struct {
*docker.Client
}

type kymaDockerClient struct {
Docker Client
}

//go:generate mockery --name Client
type Client interface {
ArchiveDirectory(srcPath string, options *archive.TarOptions) (io.ReadCloser, error)
NegotiateAPIVersion(ctx context.Context)
ImagePush(ctx context.Context, image string, options types.ImagePushOptions) (io.ReadCloser, error)
ImageBuild(ctx context.Context, buildContext io.Reader, options types.ImageBuildOptions) (types.ImageBuildResponse, error)
}

type KymaClient interface {
PushKymaInstaller(image string, currentStep step.Step) error
BuildKymaInstaller(localSrcPath, imageName string) error
}

// ErrorMessage is used to parse error messages coming from Docker
type ErrorMessage struct {
Error string
}

func (i *Installation) buildKymaInstaller(imageName string) error {
var dc *docker.Client
var err error
if i.Options.IsLocal {
dc, err = minikube.DockerClient(i.Options.Verbose, i.Options.LocalCluster.Profile, i.Options.Timeout)
} else {
dc, err = docker.NewClientWithOpts(docker.FromEnv)
//NewDockerService creates docker client using docker environment of the OS
func NewClient() (Client, error) {
dClient, err := docker.NewClientWithOpts(docker.FromEnv)
if err != nil {
return nil, err
}

return &dockerClient{
dClient,
}, nil
}

//NewDockerMinikubeService creates docker client for minikube docker-env
func NewMinikubeClient(verbosity bool, profile string, timeout time.Duration) (Client, error) {
dClient, err := minikube.DockerClient(verbosity, profile, timeout)
if err != nil {
return err
return nil, err
}

reader, err := archive.TarWithOptions(i.Options.LocalSrcPath, &archive.TarOptions{})
return &dockerClient{
dClient,
}, nil
}

func NewKymaClient(isLocal bool, verbosity bool, profile string, timeout time.Duration) (KymaClient, error) {
var err error
var dc Client
if isLocal {
dc, err = NewMinikubeClient(verbosity, profile, timeout)
} else {
dc, err = NewClient()
}
return &kymaDockerClient{
Docker: dc,
}, err
}

func (d *dockerClient) ArchiveDirectory(srcPath string, options *archive.TarOptions) (io.ReadCloser, error) {
return archive.TarWithOptions(srcPath, &archive.TarOptions{})
}

func (k *kymaDockerClient) BuildKymaInstaller(localSrcPath, imageName string) error {
reader, err := k.Docker.ArchiveDirectory(localSrcPath, &archive.TarOptions{})
if err != nil {
return err
}

ctx, cancel := context.WithTimeout(context.Background(), time.Duration(300)*time.Second)
defer cancel()

dc.NegotiateAPIVersion(ctx)

k.Docker.NegotiateAPIVersion(ctx)
args := make(map[string]*string)
_, err = dc.ImageBuild(
_, err = k.Docker.ImageBuild(
ctx,
reader,
types.ImageBuildOptions{
Expand All @@ -70,18 +119,11 @@ func (i *Installation) buildKymaInstaller(imageName string) error {
return nil
}

func (i *Installation) pushKymaInstaller() error {
func (k *kymaDockerClient) PushKymaInstaller(image string, currentStep step.Step) error {
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(300)*time.Second)
defer cancel()

dc, err := docker.NewClientWithOpts(docker.FromEnv)
if err != nil {
return err
}

dc.NegotiateAPIVersion(ctx)

domain, _ := splitDockerDomain(i.Options.CustomImage)
k.Docker.NegotiateAPIVersion(ctx)
domain, _ := splitDockerDomain(image)
auth, err := resolve(domain)
if err != nil {
return err
Expand All @@ -93,16 +135,16 @@ func (i *Installation) pushKymaInstaller() error {
}
authStr := base64.URLEncoding.EncodeToString(encodedJSON)

i.currentStep.LogInfof("Pushing Docker image: '%s'", i.Options.CustomImage)
currentStep.LogInfof("Pushing Docker image: '%s'", image)

pusher, err := dc.ImagePush(ctx, i.Options.CustomImage, types.ImagePushOptions{RegistryAuth: authStr})
pusher, err := k.Docker.ImagePush(ctx, image, types.ImagePushOptions{RegistryAuth: authStr})
if err != nil {
return err
}

defer pusher.Close()

var errorMessage DockerErrorMessage
var errorMessage ErrorMessage
buffIOReader := bufio.NewReader(pusher)

for {
Expand Down
169 changes: 169 additions & 0 deletions pkg/docker/docker_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
package docker

import (
"encoding/base64"
"encoding/json"
"fmt"
"io/ioutil"
"os"
"path"
"strings"
"testing"

dockerConfigFile "github.com/docker/cli/cli/config/configfile"
"github.com/docker/cli/cli/config/types"
imageTypes "github.com/docker/docker/api/types"
"github.com/docker/docker/pkg/archive"
"github.com/kyma-project/cli/pkg/docker/mocks"
"github.com/kyma-project/cli/pkg/step"

"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
"gotest.tools/assert"
)

func Test_SplitDockerDomain(t *testing.T) {
test1 := "localhost:5000/test/testImage:1"
d1, r1 := splitDockerDomain(test1)
require.Equal(t, d1, "localhost:5000")
require.Equal(t, r1, "test/testImage:1")

test2 := "eu.gcr.io/test/testImage"
d2, r2 := splitDockerDomain(test2)
require.Equal(t, d2, "eu.gcr.io")
require.Equal(t, r2, "test/testImage")

test3 := "testImage"
d3, r3 := splitDockerDomain(test3)
require.Equal(t, d3, "index.docker.io")
require.Equal(t, r3, "testImage")
}

var exampleAuth = types.AuthConfig{
Username: "user",
Password: "pass",
Auth: "",
ServerAddress: "1.2.3.4",
Email: "[email protected]",
IdentityToken: "identityFoo",
RegistryToken: "registryFoo",
}

var expectedAuth = types.AuthConfig{
Username: "user",
Password: "pass",
IdentityToken: "identityFoo",
RegistryToken: "registryFoo",
}

func genConfigFile() *dockerConfigFile.ConfigFile {
configFile := dockerConfigFile.New("tmpConfig")

authStr := exampleAuth.Username + ":" + exampleAuth.Password

msg := []byte(authStr)
encoded := make([]byte, base64.StdEncoding.EncodedLen(len(msg)))
base64.StdEncoding.Encode(encoded, msg)
exampleAuth.Auth = string(encoded)
configFile.AuthConfigs["example.com"] = exampleAuth
return configFile
}

func Test_Resolve_happy_path(t *testing.T) {

tmpHome, err := ioutil.TempDir("/tmp", "config-test")
assert.NilError(t, err)
defer os.RemoveAll(tmpHome)

configFile := genConfigFile()
b, err := json.Marshal(configFile)
assert.NilError(t, err)
tmpFile := fmt.Sprintf("%s/config.json", tmpHome)
err = ioutil.WriteFile(tmpFile, b, 0600)
assert.NilError(t, err)

os.Setenv("DOCKER_CONFIG", tmpHome)

dockerCFG, err := resolve("example.com")
assert.NilError(t, err)
assert.Equal(t, dockerCFG.Username, "user")
assert.Equal(t, dockerCFG.Password, "pass")
}

func Test_BuildKymaInstaller(t *testing.T) {
imageName := "kyma-project-foo"
fooLocalSrcPath := "foo"

// mocks
mockDocker := &mocks.Client{}
// mockKymaDocker := mocks.KymaDockerService{}
stringReader := strings.NewReader("foo")
fooReadCloser := ioutil.NopCloser(stringReader)

fooArchiveTarOptions := &archive.TarOptions{}

k := kymaDockerClient{
Docker: mockDocker,
}

mockDocker.On("ArchiveDirectory", fooLocalSrcPath, fooArchiveTarOptions).Return(fooReadCloser, nil)
// as context.deadline can have different clocks assume mock.anything here
mockDocker.On("NegotiateAPIVersion", mock.Anything).Return(nil)
fooArgs := make(map[string]*string)
fooImageBuildOptions := imageTypes.ImageBuildOptions{
Tags: []string{strings.TrimSpace(string(imageName))},
SuppressOutput: true,
Remove: true,
Dockerfile: path.Join("tools", "kyma-installer", "kyma.Dockerfile"),
BuildArgs: fooArgs,
}
fooImageBuildRes := imageTypes.ImageBuildResponse{
Body: fooReadCloser,
OSType: "fooUnix",
}
mockDocker.On("ImageBuild", mock.Anything, fooReadCloser, fooImageBuildOptions).Return(fooImageBuildRes, nil)

// test the function
err := k.BuildKymaInstaller(fooLocalSrcPath, imageName)
assert.NilError(t, err)
}

func Test_PushKymaInstaller(t *testing.T) {
tmpHome, err := ioutil.TempDir("/tmp", "config-pus-kyma-test")
assert.NilError(t, err)
defer os.RemoveAll(tmpHome)

configFile := genConfigFile()
b, err := json.Marshal(configFile)
assert.NilError(t, err)
tmpFile := fmt.Sprintf("%s/config.json", tmpHome)
err = ioutil.WriteFile(tmpFile, b, 0600)
assert.NilError(t, err)

os.Setenv("DOCKER_CONFIG", tmpHome)
image := "example.com/foo"

// mocks
mockDocker := &mocks.Client{}
// as context.deadline can have different clocks assume mock.anything here

k := kymaDockerClient{
Docker: mockDocker,
}
mockDocker.On("NegotiateAPIVersion", mock.Anything).Return(nil)

encodedJSON, _ := json.Marshal(expectedAuth)
fooAuthStr := base64.URLEncoding.EncodeToString(encodedJSON)
imagePushOptions := imageTypes.ImagePushOptions{RegistryAuth: fooAuthStr}
stringReader := strings.NewReader("foo")
fooReadCloser := ioutil.NopCloser(stringReader)

var step step.Factory
currentStep := step.NewStep("push kyma installer test")

mockDocker.On("ImagePush", mock.Anything, image, imagePushOptions).Return(fooReadCloser, nil)

err = k.PushKymaInstaller(image, currentStep)
assert.NilError(t, err)

}
Loading

0 comments on commit 8b5bafa

Please sign in to comment.