Skip to content

Commit

Permalink
Add unit tests
Browse files Browse the repository at this point in the history
  • Loading branch information
vstarostin committed Nov 23, 2023
1 parent 3fce0e2 commit 88d872a
Show file tree
Hide file tree
Showing 5 changed files with 170 additions and 101 deletions.
31 changes: 7 additions & 24 deletions cmd/imagePushToRegistry.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,31 +18,15 @@ const (
targetDockerConfigPath = "/root/.docker/config.json"
)

type dockerConfigUtils interface {
CreateDockerConfigJSON(registry, username, password, targetPath, configPath string, utils piperutils.FileUtils) (string, error)
MergeDockerConfigJSON(sourcePath, targetPath string, utils piperutils.FileUtils) error
}

type dockerImageUtils interface {
LoadImage(src string) (v1.Image, error)
PushImage(im v1.Image, dest string) error
CopyImage(src, dest string) error
}

type dockerConfigUtilsBundle struct{}

func (d *dockerConfigUtilsBundle) CreateDockerConfigJSON(registry, username, password, targetPath, configPath string, utils piperutils.FileUtils) (string, error) {
return docker.CreateDockerConfigJSON(registry, username, password, targetPath, configPath, utils)
}

func (d *dockerConfigUtilsBundle) MergeDockerConfigJSON(sourcePath, targetPath string, utils piperutils.FileUtils) error {
return docker.MergeDockerConfigJSON(sourcePath, targetPath, utils)
}

type imagePushToRegistryUtils interface {
command.ExecRunner
piperutils.FileUtils
dockerConfigUtils
dockerImageUtils

// Add more methods here, or embed additional interfaces, or remove/replace as required.
Expand All @@ -54,7 +38,6 @@ type imagePushToRegistryUtils interface {
type imagePushToRegistryUtilsBundle struct {
*command.Command
*piperutils.Files
*dockerConfigUtilsBundle
dockerImageUtils

// Embed more structs as necessary to implement methods or interfaces you add to imagePushToRegistryUtils.
Expand All @@ -65,10 +48,9 @@ type imagePushToRegistryUtilsBundle struct {

func newImagePushToRegistryUtils() imagePushToRegistryUtils {
utils := imagePushToRegistryUtilsBundle{
Command: &command.Command{},
Files: &piperutils.Files{},
dockerConfigUtilsBundle: &dockerConfigUtilsBundle{},
dockerImageUtils: &docker.CraneUtilsBundle{},
Command: &command.Command{},
Files: &piperutils.Files{},
dockerImageUtils: &docker.CraneUtilsBundle{},
}
// Reroute command output to logging framework
utils.Stdout(log.Writer())
Expand Down Expand Up @@ -134,19 +116,19 @@ func handleCredentialsForPrivateRegistry(dockerConfigJsonPath, registry, usernam
}

if len(dockerConfigJsonPath) == 0 {
_, err := utils.CreateDockerConfigJSON(registry, username, password, "", targetDockerConfigPath, utils)
_, err := docker.CreateDockerConfigJSON(registry, username, password, "", targetDockerConfigPath, utils)
if err != nil {
return errors.Wrap(err, "failed to create new docker config")
}
return nil
}

_, err := utils.CreateDockerConfigJSON(registry, username, password, targetDockerConfigPath, dockerConfigJsonPath, utils)
_, err := docker.CreateDockerConfigJSON(registry, username, password, targetDockerConfigPath, dockerConfigJsonPath, utils)
if err != nil {
return errors.Wrapf(err, "failed to update docker config %q", dockerConfigJsonPath)
}

err = utils.MergeDockerConfigJSON(targetDockerConfigPath, dockerConfigJsonPath, utils)
err = docker.MergeDockerConfigJSON(targetDockerConfigPath, dockerConfigJsonPath, utils)
if err != nil {
return errors.Wrapf(err, "failed to merge docker config files")
}
Expand All @@ -159,6 +141,7 @@ func pushLocalImageToTargetRegistry(localDockerImagePath, targetRegistry string,
if err != nil {
return err
}

return utils.PushImage(img, targetRegistry)
}

Expand Down
166 changes: 147 additions & 19 deletions cmd/imagePushToRegistry_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,34 @@ import (
"github.com/SAP/jenkins-library/pkg/mock"
)

const (
customDockerConfig = `{"auths":{"source.registry":{"auth":"c291cmNldXNlcjpzb3VyY2VwYXNzd29yZA=="},"target.registry":{"auth":"dGFyZ2V0dXNlcjp0YXJnZXRwYXNzd29yZA=="}}}`
dockerConfig = `{
"auths": {
"source.registry": {
"auth": "c291cmNldXNlcjpzb3VyY2VwYXNzd29yZA=="
},
"target.registry": {
"auth": "dGFyZ2V0dXNlcjp0YXJnZXRwYXNzd29yZA=="
},
"test.registry": {
"auth": "dGVzdHVzZXI6dGVzdHBhc3N3b3Jk"
}
}
}`
)

type imagePushToRegistryMockUtils struct {
*mock.ExecMockRunner
*mock.FilesMock
*dockermock.CraneMockUtils
*dockerConfigUtilsBundle
}

func newImagePushToRegistryMockUtils(craneUtils *dockermock.CraneMockUtils) imagePushToRegistryUtils {
func newImagePushToRegistryMockUtils(craneUtils *dockermock.CraneMockUtils) *imagePushToRegistryMockUtils {
utils := &imagePushToRegistryMockUtils{
ExecMockRunner: &mock.ExecMockRunner{},
FilesMock: &mock.FilesMock{},
CraneMockUtils: &dockermock.CraneMockUtils{},
CraneMockUtils: craneUtils,
}

return utils
Expand All @@ -29,38 +45,150 @@ func newImagePushToRegistryMockUtils(craneUtils *dockermock.CraneMockUtils) imag
func TestRunImagePushToRegistry(t *testing.T) {
t.Parallel()

t.Run("happy path", func(t *testing.T) {
t.Run("good case 1", func(t *testing.T) {
t.Parallel()

config := imagePushToRegistryOptions{
SourceRegistryURL: "https://source.registry",
SourceImage: "source-image:latest",
SourceRegistryUser: "sourceuser",
SourceRegistryPassword: "sourcepassword",
TargetRegistryURL: "https://target.registry",
TargetImage: "target-image:latest",
TargetRegistryUser: "targetuser",
TargetRegistryPassword: "targetpassword",
}
craneMockUtils := &dockermock.CraneMockUtils{}
utils := newImagePushToRegistryMockUtils(craneMockUtils)
err := runImagePushToRegistry(&config, nil, utils)
assert.NoError(t, err)
createdConfig, err := utils.FileRead(targetDockerConfigPath)
assert.NoError(t, err)
assert.Equal(t, customDockerConfig, string(createdConfig))
})

t.Run("good case 2", func(t *testing.T) {
t.Parallel()
// init

config := imagePushToRegistryOptions{
SourceRegistryURL: "https://source.registry",
SourceImage: "source-image:latest",
TargetImage: "target-image",
TargetRegistryURL: "https://registry.test",
TargetRegistryUser: "user",
TargetRegistryPassword: "password",
TargetRegistryURL: "https://target.registry",
TargetRegistryUser: "targetuser",
TargetRegistryPassword: "targetpassword",
LocalDockerImagePath: "/local/path",
}
craneMockUtils := &dockermock.CraneMockUtils{}
utils := newImagePushToRegistryMockUtils(craneMockUtils)
// utils.AddFile("file.txt", []byte("dummy content"))
err := runImagePushToRegistry(&config, nil, utils)
assert.NoError(t, err)
})

// test
t.Run("failed to copy image", func(t *testing.T) {
t.Parallel()

config := imagePushToRegistryOptions{
SourceRegistryURL: "https://source.registry",
SourceImage: "source-image:latest",
TargetRegistryURL: "https://target.registry",
TargetRegistryUser: "targetuser",
TargetRegistryPassword: "targetpassword",
}
craneMockUtils := &dockermock.CraneMockUtils{
ErrCopyImage: dockermock.ErrCopyImage,
}
utils := newImagePushToRegistryMockUtils(craneMockUtils)
err := runImagePushToRegistry(&config, nil, utils)
assert.EqualError(t, err, "failed to copy image from \"source.registry\" to \"target.registry\": copy image err")
})

t.Run("failed to push local image", func(t *testing.T) {
t.Parallel()

// assert
config := imagePushToRegistryOptions{
SourceImage: "source-image:latest",
TargetRegistryURL: "https://target.registry",
TargetRegistryUser: "targetuser",
TargetRegistryPassword: "targetpassword",
LocalDockerImagePath: "/local/path",
}
craneMockUtils := &dockermock.CraneMockUtils{
ErrLoadImage: dockermock.ErrLoadImage,
}
utils := newImagePushToRegistryMockUtils(craneMockUtils)
err := runImagePushToRegistry(&config, nil, utils)
assert.EqualError(t, err, "failed to push local image to \"target.registry\": load image err")
})
}

func TestHandleCredentialsForPrivateRegistry(t *testing.T) {
t.Parallel()

craneMockUtils := &dockermock.CraneMockUtils{}
t.Run("no custom docker config provided", func(t *testing.T) {
t.Parallel()

utils := newImagePushToRegistryMockUtils(craneMockUtils)
utils.AddFile("targetDockerConfigPath", []byte("abc"))
err := handleCredentialsForPrivateRegistry("", "target.registry", "targetuser", "targetpassword", utils)
assert.NoError(t, err)
createdConfigFile, err := utils.FileRead(targetDockerConfigPath)
assert.NoError(t, err)
assert.Equal(t, `{"auths":{"target.registry":{"auth":"dGFyZ2V0dXNlcjp0YXJnZXRwYXNzd29yZA=="}}}`, string(createdConfigFile))
})

t.Run("error path", func(t *testing.T) {
t.Run("custom docker config provided", func(t *testing.T) {
t.Parallel()
// init
config := imagePushToRegistryOptions{}

utils := newImagePushToRegistryMockUtils(craneMockUtils)
utils.AddFile(targetDockerConfigPath, []byte(customDockerConfig))
err := handleCredentialsForPrivateRegistry(targetDockerConfigPath, "test.registry", "testuser", "testpassword", utils)
assert.NoError(t, err)
createdConfigFile, err := utils.FileRead(targetDockerConfigPath)
assert.NoError(t, err)
assert.Equal(t, dockerConfig, string(createdConfigFile))
})

t.Run("wrong format of docker config", func(t *testing.T) {
t.Parallel()

utils := newImagePushToRegistryMockUtils(craneMockUtils)
utils.AddFile(targetDockerConfigPath, []byte(`{auths:}`))
err := handleCredentialsForPrivateRegistry("", "test.registry", "testuser", "testpassword", utils)
assert.EqualError(t, err, "failed to create new docker config: failed to unmarshal json file '/root/.docker/config.json': invalid character 'a' looking for beginning of object key string")
})
}

func TestPushLocalImageToTargetRegistry(t *testing.T) {
t.Parallel()
t.Run("good case", func(t *testing.T) {
t.Parallel()

craneMockUtils := &dockermock.CraneMockUtils{}
utils := newImagePushToRegistryMockUtils(craneMockUtils)
err := pushLocalImageToTargetRegistry("/image/path", "target.registry", utils)
assert.NoError(t, err)
})

// test
_ = runImagePushToRegistry(&config, nil, utils)
t.Run("bad case - failed to load image", func(t *testing.T) {
t.Parallel()

// assert
// assert.EqualError(t, err, "cannot run without important file")
craneMockUtils := &dockermock.CraneMockUtils{
ErrLoadImage: dockermock.ErrLoadImage,
}
utils := newImagePushToRegistryMockUtils(craneMockUtils)
err := pushLocalImageToTargetRegistry("/image/path", "target.registry", utils)
assert.EqualError(t, err, "load image err")
})

t.Run("bad case - failed to push image", func(t *testing.T) {
t.Parallel()

craneMockUtils := &dockermock.CraneMockUtils{
ErrPushImage: dockermock.ErrPushImage,
}
utils := newImagePushToRegistryMockUtils(craneMockUtils)
err := pushLocalImageToTargetRegistry("/image/path", "target.registry", utils)
assert.EqualError(t, err, "push image err")
})
}
2 changes: 1 addition & 1 deletion pkg/docker/crane.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import (

type CraneUtilsBundle struct{}

func (c *CraneUtilsBundle) CopyImage(src string, dest string) error {
func (c *CraneUtilsBundle) CopyImage(src, dest string) error {
return crane.Copy(src, dest)
}

Expand Down
20 changes: 15 additions & 5 deletions pkg/docker/mock/crane.go
Original file line number Diff line number Diff line change
@@ -1,19 +1,29 @@
package mock

import v1 "github.com/google/go-containerregistry/pkg/v1"
import (
"errors"

v1 "github.com/google/go-containerregistry/pkg/v1"
)

var (
ErrCopyImage = errors.New("copy image err")
ErrPushImage = errors.New("push image err")
ErrLoadImage = errors.New("load image err")
)

type CraneMockUtils struct {
errCopyImage, errPushImage, errLoadImage error
ErrCopyImage, ErrPushImage, ErrLoadImage error
}

func (c *CraneMockUtils) CopyImage(src string, dest string) error {
return c.errCopyImage
return c.ErrCopyImage
}

func (c *CraneMockUtils) PushImage(im v1.Image, dest string) error {
return c.errPushImage
return c.ErrPushImage
}

func (c *CraneMockUtils) LoadImage(src string) (v1.Image, error) {
return nil, c.errLoadImage
return nil, c.ErrLoadImage
}
52 changes: 0 additions & 52 deletions pkg/docker/mock/crane_test.go

This file was deleted.

0 comments on commit 88d872a

Please sign in to comment.