Skip to content

Commit

Permalink
chore(repository): add repository package to support building and pus…
Browse files Browse the repository at this point in the history
…hing images (#1161)

This PR adds a repository package that builds and push images to a repository.

By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.
  • Loading branch information
Lou1415926 authored Jul 20, 2020
1 parent 0ce743e commit 5f61174
Show file tree
Hide file tree
Showing 3 changed files with 327 additions and 0 deletions.
139 changes: 139 additions & 0 deletions internal/pkg/repository/mocks/mock_repository.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

70 changes: 70 additions & 0 deletions internal/pkg/repository/repository.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

// Package repository provides support for building and pushing images to a repository.
package repository

import (
"fmt"
)

// ContainerLoginBuildPusher provides support for logging in to repositories, building images and pushing images to repositories.
type ContainerLoginBuildPusher interface {
Build(uri, path, imageTag string, additionalTags ...string) error
Login(uri, username, password string) error
Push(uri, imageTag string, additionalTags ...string) error
}

// Registry gets information of repositories.
type Registry interface {
RepositoryURI(name string) (string, error)
Auth() (string, string, error)
}

// Repository builds and pushes images to a repository.
type Repository struct {
name string
registry Registry

uri string
}

// New instantiates a new Repository.
func New(name string, registry Registry) (*Repository, error){
uri, err := registry.RepositoryURI(name)
if err != nil {
return nil, fmt.Errorf("get repository URI: %w", err)
}

return &Repository{
name: name,
uri: uri,
registry: registry,
}, nil
}

// BuildAndPush builds the image from Dockerfile and pushes it to the repository with tags.
func (r *Repository) BuildAndPush(docker ContainerLoginBuildPusher, dockerfilePath string, tag string, additionalTags ...string) error {
if err := docker.Build(r.uri, dockerfilePath, tag, additionalTags...); err != nil {
return fmt.Errorf("build Dockerfile at %s: %w", dockerfilePath, err)
}

username, password, err := r.registry.Auth()
if err != nil {
return fmt.Errorf("get auth: %w", err)
}

if err := docker.Login(r.uri, username, password); err != nil {
return fmt.Errorf("login to repo %s: %w", r.name, err)
}

if err := docker.Push(r.uri, tag, additionalTags...); err != nil {
return fmt.Errorf("push to repo %s: %w", r.name, err)
}
return nil
}

// URI returns the uri of the repository.
func (r *Repository) URI() string{
return r.uri
}
118 changes: 118 additions & 0 deletions internal/pkg/repository/repository_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

package repository

import (
"errors"
"fmt"
"github.com/aws/copilot-cli/internal/pkg/repository/mocks"
"github.com/golang/mock/gomock"
"github.com/stretchr/testify/require"
"testing"
)

func TestRepository_BuildAndPush(t *testing.T) {
inRepoName := "my-repo"
inDockerfilePath := "path/to/dockerfile"

mockTag1, mockTag2, mockTag3 := "tag1", "tag2", "tag3"
mockRepoURI := "mockURI"

testCases := map[string]struct {
inRepoName string
inDockerfilePath string
inMockDocker func(m *mocks.MockContainerLoginBuildPusher)

mockRegistry func(m *mocks.MockRegistry)

wantedError error
wantedURI string
}{
"failed to get auth": {
mockRegistry: func(m *mocks.MockRegistry) {
m.EXPECT().Auth().Return("", "", errors.New("error getting auth"))
},
inMockDocker: func(m *mocks.MockContainerLoginBuildPusher) {
m.EXPECT().Build(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes()
m.EXPECT().Login(gomock.Any(), gomock.Any(), gomock.Any()).Times(0)
m.EXPECT().Push(gomock.Any(), gomock.Any(), gomock.Any()).Times(0)
},
wantedError: errors.New("get auth: error getting auth"),
},
"failed to build image": {
mockRegistry: func(m *mocks.MockRegistry) {
m.EXPECT().Auth().Return("", "", nil).AnyTimes()
},
inMockDocker: func(m *mocks.MockContainerLoginBuildPusher) {
m.EXPECT().Build(mockRepoURI, inDockerfilePath, mockTag1, mockTag2, mockTag3).Return(errors.New("error building image"))
m.EXPECT().Login(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes()
m.EXPECT().Push(gomock.Any(), gomock.Any(), gomock.Any()).Times(0)
},
wantedError: fmt.Errorf("build Dockerfile at %s: error building image", inDockerfilePath),
},
"failed to login": {
mockRegistry: func(m *mocks.MockRegistry) {
m.EXPECT().Auth().Return("my-name", "my-pwd", nil)
},
inMockDocker: func(m *mocks.MockContainerLoginBuildPusher) {
m.EXPECT().Build(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes()
m.EXPECT().Login(mockRepoURI, "my-name", "my-pwd").Return(errors.New("error logging in"))
m.EXPECT().Push(gomock.Any(), gomock.Any(), gomock.Any()).Times(0)
},
wantedError: fmt.Errorf("login to repo %s: error logging in", inRepoName),
},
"failed to push": {
mockRegistry: func(m *mocks.MockRegistry) {
m.EXPECT().Auth().Times(1)
},
inMockDocker: func(m *mocks.MockContainerLoginBuildPusher) {
m.EXPECT().Build(mockRepoURI, inDockerfilePath, mockTag1, mockTag2, mockTag3).Times(1)
m.EXPECT().Login(gomock.Any(), gomock.Any(), gomock.Any()).Times(1)
m.EXPECT().Push(mockRepoURI, mockTag1, mockTag2, mockTag3).Return(errors.New("error pushing image"))
},
wantedError: errors.New("push to repo my-repo: error pushing image"),
},
"success": {
mockRegistry: func(m *mocks.MockRegistry) {
m.EXPECT().Auth().Return("my-name", "my-pwd", nil).Times(1)
},
inMockDocker: func(m *mocks.MockContainerLoginBuildPusher) {
m.EXPECT().Build(mockRepoURI, inDockerfilePath, mockTag1, mockTag2, mockTag3).Return(nil).Times(1)
m.EXPECT().Login(mockRepoURI, "my-name", "my-pwd").Return(nil).Times(1)
m.EXPECT().Push(mockRepoURI, mockTag1, mockTag2, mockTag3).Return(nil)
},
wantedURI: mockRepoURI,
},
}
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()

mockRepoGetter := mocks.NewMockRegistry(ctrl)
mockDocker := mocks.NewMockContainerLoginBuildPusher(ctrl)

if tc.mockRegistry != nil {
tc.mockRegistry(mockRepoGetter)
}
if tc.inMockDocker != nil {
tc.inMockDocker(mockDocker)
}

repo := &Repository{
name: inRepoName,
registry: mockRepoGetter,

uri: mockRepoURI,
}

err := repo.BuildAndPush(mockDocker, inDockerfilePath, mockTag1, []string{mockTag2, mockTag3}...)
if tc.wantedError != nil {
require.EqualError(t, tc.wantedError, err.Error())
} else {
require.Nil(t, err)
}
})
}
}

0 comments on commit 5f61174

Please sign in to comment.