-
Notifications
You must be signed in to change notification settings - Fork 428
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
chore(repository): add repository package to support building and pus…
…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
1 parent
0ce743e
commit 5f61174
Showing
3 changed files
with
327 additions
and
0 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} | ||
}) | ||
} | ||
} |