diff --git a/Makefile b/Makefile index 1fae6667a0b..289ad36ecce 100644 --- a/Makefile +++ b/Makefile @@ -484,6 +484,7 @@ clean: -rm -f .generic-rpm-integrated-done -rm -f amazon-ecs-volume-plugin -rm -rf $(EBS_CSI_DRIVER_DIR)/bin + -rm -rm /tmp/private-test-registry-htpasswd # private registry credentials cleanup clean-all: clean # for our dockerfree builds, we likely don't have docker diff --git a/agent/dockerclient/dockerapi/docker_client.go b/agent/dockerclient/dockerapi/docker_client.go index e7ca39c344f..39341d94ca9 100644 --- a/agent/dockerclient/dockerapi/docker_client.go +++ b/agent/dockerclient/dockerapi/docker_client.go @@ -52,6 +52,7 @@ import ( "github.com/docker/docker/api/types/events" "github.com/docker/docker/api/types/filters" "github.com/docker/docker/api/types/network" + "github.com/docker/docker/api/types/registry" "github.com/docker/docker/api/types/volume" ) @@ -121,6 +122,10 @@ type DockerClient interface { // be processed by the listener. ContainerEvents(context.Context) (<-chan DockerContainerChangeEvent, error) + // Given an image reference and registry auth credentials, pulls the image manifest + // of the image from the registry. + PullImageManifest(context.Context, string, *apicontainer.RegistryAuthenticationData) (registry.DistributionInspect, error) + // PullImage pulls an image. authData should contain authentication data provided by the ECS backend. PullImage(context.Context, string, *apicontainer.RegistryAuthenticationData, time.Duration) DockerContainerMetadata @@ -329,6 +334,34 @@ func (dg *dockerGoClient) time() ttime.Time { return dg._time } +// Pulls image manifest from the registry +func (dg *dockerGoClient) PullImageManifest( + ctx context.Context, imageRef string, authData *apicontainer.RegistryAuthenticationData, +) (registry.DistributionInspect, error) { + // Get auth creds + sdkAuthConfig, err := dg.getAuthdata(imageRef, authData) + if err != nil { + return registry.DistributionInspect{}, wrapPullErrorAsNamedError(imageRef, err) + } + encodedAuth, err := registry.EncodeAuthConfig(sdkAuthConfig) + if err != nil { + return registry.DistributionInspect{}, wrapPullErrorAsNamedError(imageRef, err) + } + + // Get an SDK Docker Client and call Distribution API + client, err := dg.sdkDockerClient() + if err != nil { + return registry.DistributionInspect{}, CannotGetDockerClientError{version: dg.version, err: err} + } + distInspect, err := client.DistributionInspect(ctx, imageRef, encodedAuth) + if err != nil { + err = redactEcrUrls(imageRef, err) + return registry.DistributionInspect{}, CannotPullContainerError{err} + } + + return distInspect, nil +} + func (dg *dockerGoClient) PullImage(ctx context.Context, image string, authData *apicontainer.RegistryAuthenticationData, timeout time.Duration) DockerContainerMetadata { ctx, cancel := context.WithTimeout(ctx, timeout) diff --git a/agent/dockerclient/dockerapi/docker_client_integ_test.go b/agent/dockerclient/dockerapi/docker_client_integ_test.go new file mode 100644 index 00000000000..00606f64bb1 --- /dev/null +++ b/agent/dockerclient/dockerapi/docker_client_integ_test.go @@ -0,0 +1,128 @@ +//go:build integration +// +build integration + +// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"). You may +// not use this file except in compliance with the License. A copy of the +// License is located at +// +// http://aws.amazon.com/apache2.0/ +// +// or in the "license" file accompanying this file. This file is distributed +// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either +// express or implied. See the License for the specific language governing +// permissions and limitations under the License. +package dockerapi + +import ( + "context" + "testing" + + "github.com/aws/amazon-ecs-agent/agent/api/container" + apicontainer "github.com/aws/amazon-ecs-agent/agent/api/container" + "github.com/aws/amazon-ecs-agent/agent/config" + "github.com/aws/amazon-ecs-agent/agent/dockerclient" + "github.com/aws/amazon-ecs-agent/agent/dockerclient/sdkclientfactory" + "github.com/docker/docker/api/types" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +const dockerEndpoint = "unix:///var/run/docker.sock" + +// This integration test checks that dockerGoClient can pull image manifest from registries. +// +// The test is skipped on environments with a Docker Engine version that does not support +// API version 1.35 as older engine versions do not have Distribution API needed for pulling +// image manifest. Technically, API version >1.30 have Distribution API but engine versions +// between API version 1.30 and 1.35 can be configured to allow image pulls from v1 registries +// but Distribution API does not work with v1 registries. v1 registry support was dropped +// with engine version 17.12 that was shipped with API version 1.35. +// +// The test depends on local test registries that are set up by `make test-registry` command. +func TestImageManifestPullInteg(t *testing.T) { + // Prepare a docker client that can pull image manifests + sdkClientFactory := sdkclientfactory.NewFactory(context.Background(), dockerEndpoint) + cfg := &config.Config{} + defaultClient, err := NewDockerGoClient(sdkClientFactory, cfg, context.Background()) + require.NoError(t, err) + version := dockerclient.GetSupportedDockerAPIVersion(dockerclient.Version_1_35) + supportedClient, err := defaultClient.WithVersion(version) + if err != nil { + t.Skipf("Skipping test due to unsupported Docker version: %v", err) + } + + tcs := []struct { + name string + dockerClient DockerClient + imageRef string + authData *container.RegistryAuthenticationData + expectedError string + }{ + { + name: "private registry success", + dockerClient: supportedClient, + imageRef: "127.0.0.1:51671/busybox:latest", + authData: func() *container.RegistryAuthenticationData { + asmAuthData := &apicontainer.ASMAuthData{} + asmAuthData.SetDockerAuthConfig(types.AuthConfig{ + Username: "username", + Password: "password", + }) + return &container.RegistryAuthenticationData{Type: apicontainer.AuthTypeASM, + ASMAuthData: asmAuthData, + } + }(), + }, + { + name: "private registry auth failure", + dockerClient: supportedClient, + imageRef: "127.0.0.1:51671/busybox:latest", + expectedError: "no basic auth credentials", + }, + { + name: "public registry success", + dockerClient: supportedClient, + imageRef: "127.0.0.1:51670/busybox:latest", + }, + { + name: "public registry success, no explicit tag", + dockerClient: supportedClient, + imageRef: "127.0.0.1:51670/busybox", + }, + { + name: "public ECR success", + dockerClient: supportedClient, + imageRef: "public.ecr.aws/amazonlinux/amazonlinux:2", + }, + { + name: "Docker client version too old", + dockerClient: func() DockerClient { + sdkClientFactory := sdkclientfactory.NewFactory(context.Background(), dockerEndpoint) + cfg := &config.Config{} + defaultClient, err := NewDockerGoClient(sdkClientFactory, cfg, context.Background()) + require.NoError(t, err) + version := dockerclient.GetSupportedDockerAPIVersion(dockerclient.Version_1_29) + unsupportedClient, err := defaultClient.WithVersion(version) + require.NoError(t, err) + return unsupportedClient + }(), + imageRef: "public.ecr.aws/amazonlinux/amazonlinux:2", + expectedError: `"distribution inspect" requires API version 1.30`, + }, + } + + for _, tc := range tcs { + t.Run(tc.name, func(t *testing.T) { + distInspect, err := tc.dockerClient.PullImageManifest( + context.Background(), tc.imageRef, tc.authData) + if tc.expectedError == "" { + require.NoError(t, err) + assert.NotEmpty(t, distInspect.Descriptor.Digest.Encoded()) + } else { + assert.ErrorContains(t, err, tc.expectedError) + } + }) + } +} diff --git a/agent/dockerclient/dockerapi/docker_client_test.go b/agent/dockerclient/dockerapi/docker_client_test.go index 88658995fa6..e279f885020 100644 --- a/agent/dockerclient/dockerapi/docker_client_test.go +++ b/agent/dockerclient/dockerapi/docker_client_test.go @@ -43,6 +43,8 @@ import ( "github.com/aws/amazon-ecs-agent/ecs-agent/ec2" "github.com/aws/amazon-ecs-agent/ecs-agent/utils/retry" mock_ttime "github.com/aws/amazon-ecs-agent/ecs-agent/utils/ttime/mocks" + "github.com/opencontainers/go-digest" + ocispec "github.com/opencontainers/image-spec/specs-go/v1" "github.com/aws/aws-sdk-go/aws" "github.com/docker/docker/api/types" @@ -50,6 +52,7 @@ import ( "github.com/docker/docker/api/types/events" "github.com/docker/docker/api/types/filters" "github.com/docker/docker/api/types/network" + "github.com/docker/docker/api/types/registry" "github.com/docker/docker/api/types/volume" "github.com/docker/go-connections/nat" "github.com/golang/mock/gomock" @@ -295,6 +298,143 @@ func TestPullImageECRSuccess(t *testing.T) { assert.NoError(t, metadata.Error, "Expected pull to succeed") } +func TestPullImageManifest(t *testing.T) { + someErr := errors.New("some error") + testDigest, err := digest.Parse("sha256:98ea6e4f216f2fb4b69fff9b3a44842c38686ca685f3f55dc48c5d3fb1107be4") + require.NoError(t, err) + testDistributionInspect := registry.DistributionInspect{ + Descriptor: ocispec.Descriptor{Digest: testDigest}, + } + type testCase struct { + name string + ctx context.Context + imageRef string + authData *apicontainer.RegistryAuthenticationData + setSDKFactoryExpectations func(f *mock_sdkclientfactory.MockFactory, ctrl *gomock.Controller) + setECRClientExpectations func(*mock_ecr.MockECRClient) + expectedError error + expectedDistributionInspect registry.DistributionInspect + } + tcs := []testCase{ + { + name: "failure in getting ECR auth data", + ctx: context.Background(), + imageRef: "image", + authData: &apicontainer.RegistryAuthenticationData{ + Type: apicontainer.AuthTypeECR, + ECRAuthData: &apicontainer.ECRAuthData{RegistryID: "registryId"}, + }, + setECRClientExpectations: func(me *mock_ecr.MockECRClient) { + me.EXPECT().GetAuthorizationToken("registryId").Return(nil, someErr) + }, + expectedError: CannotPullECRContainerError{someErr}, + }, + { + name: "Failure in getting SDK client", + ctx: context.Background(), + imageRef: "image", + setSDKFactoryExpectations: func(f *mock_sdkclientfactory.MockFactory, ctrl *gomock.Controller) { + f.EXPECT().GetDefaultClient().Return(nil, someErr) + }, + expectedError: CannotGetDockerClientError{version: "", err: someErr}, + }, + { + name: "Failure in DistributionInspect API - image URL is redacted", + ctx: context.Background(), + imageRef: "image", + setSDKFactoryExpectations: func(f *mock_sdkclientfactory.MockFactory, ctrl *gomock.Controller) { + client := mock_sdkclient.NewMockClient(ctrl) + client.EXPECT(). + DistributionInspect( + gomock.Any(), "image", base64.URLEncoding.EncodeToString([]byte("{}"))). + Return( + registry.DistributionInspect{}, + errors.New("Some error for https://prod-us-east-1-starport-layer-bucket.s3.us-east-1.amazonaws.com")) + f.EXPECT().GetDefaultClient().Return(client, nil) + }, + expectedError: CannotPullContainerError{errors.New("Some error for REDACTED ECR URL related to image")}, + }, + { + name: "Manifest is returned if there are no errors - no auth data", + ctx: context.Background(), + imageRef: "image", + setSDKFactoryExpectations: func(f *mock_sdkclientfactory.MockFactory, ctrl *gomock.Controller) { + client := mock_sdkclient.NewMockClient(ctrl) + client.EXPECT(). + DistributionInspect( + gomock.Any(), "image", base64.URLEncoding.EncodeToString([]byte("{}"))). + Return(testDistributionInspect, nil) + f.EXPECT().GetDefaultClient().Return(client, nil) + }, + expectedDistributionInspect: testDistributionInspect, + }, + func() testCase { + authData := &apicontainer.RegistryAuthenticationData{ + Type: apicontainer.AuthTypeASM, + ASMAuthData: &apicontainer.ASMAuthData{}, + } + authConfig := types.AuthConfig{Username: "username", Password: "password"} + authData.ASMAuthData.SetDockerAuthConfig(authConfig) + encodedAuthConfig, err := registry.EncodeAuthConfig(authConfig) + require.NoError(t, err) + return testCase{ + name: "Manifest is returned if there are no errors - auth data", + ctx: context.Background(), + imageRef: "image", + authData: authData, + setSDKFactoryExpectations: func(f *mock_sdkclientfactory.MockFactory, ctrl *gomock.Controller) { + client := mock_sdkclient.NewMockClient(ctrl) + client.EXPECT(). + DistributionInspect(gomock.Any(), "image", encodedAuthConfig). + Return(testDistributionInspect, nil) + f.EXPECT().GetDefaultClient().Return(client, nil) + }, + expectedDistributionInspect: testDistributionInspect, + } + }(), + } + + for _, tc := range tcs { + t.Run(tc.name, func(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + mockDockerSDK := mock_sdkclient.NewMockClient(ctrl) + mockDockerSDK.EXPECT().Ping(gomock.Any()).Return(types.Ping{}, nil) + sdkFactory := mock_sdkclientfactory.NewMockFactory(ctrl) + sdkFactory.EXPECT().GetDefaultClient().Return(mockDockerSDK, nil) + + client, err := NewDockerGoClient(sdkFactory, defaultTestConfig(), context.Background()) + require.NoError(t, err) + + if tc.setSDKFactoryExpectations != nil { + tc.setSDKFactoryExpectations(sdkFactory, ctrl) + } + + goClient, ok := client.(*dockerGoClient) + require.True(t, ok) + ecrClientFactory := mock_ecr.NewMockECRFactory(ctrl) + ecrClient := mock_ecr.NewMockECRClient(ctrl) + mockTime := mock_ttime.NewMockTime(ctrl) + goClient.ecrClientFactory = ecrClientFactory + goClient._time = mockTime + + if tc.setECRClientExpectations != nil { + ecrClientFactory.EXPECT().GetClient(tc.authData.ECRAuthData).Return(ecrClient, nil) + tc.setECRClientExpectations(ecrClient) + } + + res, err := goClient.PullImageManifest(tc.ctx, tc.imageRef, tc.authData) + if tc.expectedError != nil { + assert.Equal(t, tc.expectedError, err) + } else { + require.Nil(t, err) + assert.Equal(t, tc.expectedDistributionInspect, res) + } + }) + } +} + func TestPullImageECRAuthFail(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() @@ -1243,16 +1383,13 @@ func TestPingSdkFailError(t *testing.T) { func TestUsesVersionedClient(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() - // Docker SDK tests mockDockerSDK := mock_sdkclient.NewMockClient(ctrl) mockDockerSDK.EXPECT().Ping(gomock.Any()).Return(types.Ping{}, nil) sdkFactory := mock_sdkclientfactory.NewMockFactory(ctrl) sdkFactory.EXPECT().GetDefaultClient().AnyTimes().Return(mockDockerSDK, nil) - ctx, cancel := context.WithCancel(context.TODO()) defer cancel() - client, err := NewDockerGoClient(sdkFactory, defaultTestConfig(), ctx) assert.NoError(t, err) @@ -1272,16 +1409,13 @@ func TestUsesVersionedClient(t *testing.T) { func TestUnavailableVersionError(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() - // Docker SDK tests mockDockerSDK := mock_sdkclient.NewMockClient(ctrl) mockDockerSDK.EXPECT().Ping(gomock.Any()).Return(types.Ping{}, nil) sdkFactory := mock_sdkclientfactory.NewMockFactory(ctrl) sdkFactory.EXPECT().GetDefaultClient().AnyTimes().Return(mockDockerSDK, nil) - ctx, cancel := context.WithCancel(context.TODO()) defer cancel() - client, err := NewDockerGoClient(sdkFactory, defaultTestConfig(), ctx) assert.NoError(t, err) @@ -1294,7 +1428,6 @@ func TestUnavailableVersionError(t *testing.T) { sdkFactory.EXPECT().GetClient(dockerclient.DockerVersion("1.21")).Times(1).Return(nil, errors.New("Cannot get client")) metadata := vclient.StartContainer(ctx, "foo", defaultTestConfig().ContainerStartTimeout) - assert.NotNil(t, metadata.Error, "Expected error, didn't get one") if namederr, ok := metadata.Error.(apierrors.NamedError); ok { if namederr.ErrorName() != "CannotGetDockerclientError" { diff --git a/agent/dockerclient/dockerapi/mocks/dockerapi_mocks.go b/agent/dockerclient/dockerapi/mocks/dockerapi_mocks.go index 52bf3f769e4..a548f9b7cda 100644 --- a/agent/dockerclient/dockerapi/mocks/dockerapi_mocks.go +++ b/agent/dockerclient/dockerapi/mocks/dockerapi_mocks.go @@ -31,6 +31,7 @@ import ( types "github.com/docker/docker/api/types" container0 "github.com/docker/docker/api/types/container" filters "github.com/docker/docker/api/types/filters" + registry "github.com/docker/docker/api/types/registry" gomock "github.com/golang/mock/gomock" ) @@ -318,6 +319,21 @@ func (mr *MockDockerClientMockRecorder) PullImage(arg0, arg1, arg2, arg3 interfa return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PullImage", reflect.TypeOf((*MockDockerClient)(nil).PullImage), arg0, arg1, arg2, arg3) } +// PullImageManifest mocks base method. +func (m *MockDockerClient) PullImageManifest(arg0 context.Context, arg1 string, arg2 *container.RegistryAuthenticationData) (registry.DistributionInspect, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "PullImageManifest", arg0, arg1, arg2) + ret0, _ := ret[0].(registry.DistributionInspect) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// PullImageManifest indicates an expected call of PullImageManifest. +func (mr *MockDockerClientMockRecorder) PullImageManifest(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PullImageManifest", reflect.TypeOf((*MockDockerClient)(nil).PullImageManifest), arg0, arg1, arg2) +} + // RemoveContainer mocks base method. func (m *MockDockerClient) RemoveContainer(arg0 context.Context, arg1 string, arg2 time.Duration) error { m.ctrl.T.Helper() diff --git a/agent/dockerclient/sdkclient/interface.go b/agent/dockerclient/sdkclient/interface.go index 8bd1a3144cd..d7eaf9aa37c 100644 --- a/agent/dockerclient/sdkclient/interface.go +++ b/agent/dockerclient/sdkclient/interface.go @@ -24,6 +24,7 @@ import ( "github.com/docker/docker/api/types/events" "github.com/docker/docker/api/types/filters" "github.com/docker/docker/api/types/network" + "github.com/docker/docker/api/types/registry" "github.com/docker/docker/api/types/volume" v1 "github.com/opencontainers/image-spec/specs-go/v1" ) @@ -44,6 +45,7 @@ type Client interface { ContainerExecCreate(ctx context.Context, container string, config types.ExecConfig) (types.IDResponse, error) ContainerExecStart(ctx context.Context, execID string, config types.ExecStartCheck) error ContainerExecInspect(ctx context.Context, execID string) (types.ContainerExecInspect, error) + DistributionInspect(ctx context.Context, imageRef, encodedRegistryAuth string) (registry.DistributionInspect, error) Events(ctx context.Context, options types.EventsOptions) (<-chan events.Message, <-chan error) ImageImport(ctx context.Context, source types.ImageImportSource, ref string, options types.ImageImportOptions) (io.ReadCloser, error) diff --git a/agent/dockerclient/sdkclient/mocks/sdkclient_mocks.go b/agent/dockerclient/sdkclient/mocks/sdkclient_mocks.go index 531e5c84a7a..a863746a79c 100644 --- a/agent/dockerclient/sdkclient/mocks/sdkclient_mocks.go +++ b/agent/dockerclient/sdkclient/mocks/sdkclient_mocks.go @@ -28,6 +28,7 @@ import ( events "github.com/docker/docker/api/types/events" filters "github.com/docker/docker/api/types/filters" network "github.com/docker/docker/api/types/network" + registry "github.com/docker/docker/api/types/registry" volume "github.com/docker/docker/api/types/volume" gomock "github.com/golang/mock/gomock" v1 "github.com/opencontainers/image-spec/specs-go/v1" @@ -231,6 +232,21 @@ func (mr *MockClientMockRecorder) ContainerTop(arg0, arg1, arg2 interface{}) *go return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ContainerTop", reflect.TypeOf((*MockClient)(nil).ContainerTop), arg0, arg1, arg2) } +// DistributionInspect mocks base method. +func (m *MockClient) DistributionInspect(arg0 context.Context, arg1, arg2 string) (registry.DistributionInspect, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DistributionInspect", arg0, arg1, arg2) + ret0, _ := ret[0].(registry.DistributionInspect) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// DistributionInspect indicates an expected call of DistributionInspect. +func (mr *MockClientMockRecorder) DistributionInspect(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DistributionInspect", reflect.TypeOf((*MockClient)(nil).DistributionInspect), arg0, arg1, arg2) +} + // Events mocks base method. func (m *MockClient) Events(arg0 context.Context, arg1 types.EventsOptions) (<-chan events.Message, <-chan error) { m.ctrl.T.Helper() diff --git a/agent/go.mod b/agent/go.mod index 8a7de37c4dd..f45ae82168e 100644 --- a/agent/go.mod +++ b/agent/go.mod @@ -19,6 +19,7 @@ require ( github.com/golang/mock v1.6.0 github.com/gorilla/mux v1.8.0 github.com/hectane/go-acl v0.0.0-20190604041725-da78bae5fc95 + github.com/opencontainers/go-digest v1.0.0 github.com/opencontainers/image-spec v1.1.0-rc3 github.com/opencontainers/runtime-spec v1.1.0 github.com/pborman/uuid v1.2.1 @@ -56,7 +57,6 @@ require ( github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/morikuni/aec v1.0.0 // indirect - github.com/opencontainers/go-digest v1.0.0 // indirect github.com/patrickmn/go-cache v2.1.0+incompatible // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/prometheus/common v0.26.0 // indirect diff --git a/scripts/setup-test-registry b/scripts/setup-test-registry index 2c84990d433..5f1c7d43a8d 100755 --- a/scripts/setup-test-registry +++ b/scripts/setup-test-registry @@ -21,39 +21,86 @@ REGISTRY_IMAGE="public.ecr.aws/docker/library/registry:2.7.1" ROOT=$( cd "$( dirname "${BASH_SOURCE[0]}" )/.." && pwd ) cd "${ROOT}" -REGISTRY_CONTAINER_NAME="test-ecs-registry" +# Starts a registry container +start_registry() { + local REGISTRY_CONTAINER_NAME=$1 + local HOST_ADDRESS=$2 + local HOST_HTPASSWD_PATH=$3 # Optional + + echo "Removing $REGISTRY_CONTAINER_NAME" + # stop and remove the registry container. The script will not exit if the container is not running + docker stop "$REGISTRY_CONTAINER_NAME" || true && docker rm "$REGISTRY_CONTAINER_NAME" || true -echo "Removing $REGISTRY_CONTAINER_NAME" -# stop and remove the registry container. The script will not exit if the container is not running -docker stop "$REGISTRY_CONTAINER_NAME" || true && docker rm "$REGISTRY_CONTAINER_NAME" || true + echo "Running $REGISTRY_CONTAINER_NAME" + if [ -z "$HOST_HTPASSWD_PATH" ]; then + docker run -d --name="$REGISTRY_CONTAINER_NAME" \ + -e SETTINGS_FLAVOR=local \ + -p "${HOST_ADDRESS}:5000" "${REGISTRY_IMAGE}" + else + local HOST_HTPASSWD_DIR HTPASSWD_FILENAME + HOST_HTPASSWD_DIR=$(dirname "$HOST_HTPASSWD_PATH") + HTPASSWD_FILENAME=$(basename "$HOST_HTPASSWD_PATH") + docker run -d --name="$REGISTRY_CONTAINER_NAME" \ + -e SETTINGS_FLAVOR=local \ + -e REGISTRY_AUTH_HTPASSWD_REALM=basic-realm \ + -e REGISTRY_AUTH_HTPASSWD_PATH="/agent-integ-htpasswd/${HTPASSWD_FILENAME}" \ + -v "${HOST_HTPASSWD_DIR}:/agent-integ-htpasswd" \ + -p "${HOST_ADDRESS}:5000" "${REGISTRY_IMAGE}" + fi -echo "Running $REGISTRY_CONTAINER_NAME" -docker run -d --name="$REGISTRY_CONTAINER_NAME" -e SETTINGS_FLAVOR=local -p "127.0.0.1:51670:5000" "${REGISTRY_IMAGE}" -# give the registry some seconds to get ready for pushes -sleep 7 + # give the registry some seconds to get ready for pushes + sleep 7 + +} mirror_local_image() { echo "Mirroring $1" - docker tag $1 $2 - docker push $2 - docker rmi $2 + docker tag "$1" "$2" + docker push "$2" + docker rmi "$2" } +# Pull busybox image for later use +docker pull public.ecr.aws/docker/library/busybox:1.34.1 + +# --- Start public registry --- +REGISTRY_CONTAINER_NAME="test-ecs-registry" +HOST_ADDRESS="127.0.0.1:51670" +start_registry $REGISTRY_CONTAINER_NAME $HOST_ADDRESS + for image in "amazon/amazon-ecs-netkitten" "amazon/amazon-ecs-volumes-test" \ "amazon/image-cleanup-test-image1" "amazon/image-cleanup-test-image2" \ "amazon/image-cleanup-test-image3" "amazon/amazon-ecs-exec-command-agent-test"; do - mirror_local_image "${image}:make" "127.0.0.1:51670/${image}:latest" + mirror_local_image "${image}:make" "${HOST_ADDRESS}/${image}:latest" done if [[ "$(uname -m)" == "x86_64" ]]; then - mirror_local_image "amazon/fluentd:make" "127.0.0.1:51670/amazon/fluentd:latest" + mirror_local_image "amazon/fluentd:make" "${HOST_ADDRESS}/amazon/fluentd:latest" fi # Remove the tag so this image can be deleted successfully in the docker image cleanup integ tests docker rmi amazon/image-cleanup-test-image1:make amazon/image-cleanup-test-image2:make amazon/image-cleanup-test-image3:make -docker pull public.ecr.aws/docker/library/busybox:1.34.1 -mirror_local_image public.ecr.aws/docker/library/busybox:1.34.1 "127.0.0.1:51670/busybox:latest" +# Add a busybox image to the registry +mirror_local_image public.ecr.aws/docker/library/busybox:1.34.1 "${HOST_ADDRESS}/busybox:latest" + +# --- Start private registry --- + +# Set up an htpasswd file containing test auth credentials for the registry +# Username is 'username' and password is 'password' +# The password is hashed with bcrypt +HOST_HTPASSWD_PATH="/tmp/private-test-registry-htpasswd/htpasswd" +mkdir -p "$(dirname "$HOST_HTPASSWD_PATH")" +# shellcheck disable=SC2016 +echo 'username:$2y$10$agMyRiMiqDWu.W6RpS3LS.qowb3wwee5BSkonzVp.sx1phbAK.H1a' > "$HOST_HTPASSWD_PATH" + +REGISTRY_CONTAINER_NAME="test-ecs-registry-private" +HOST_ADDRESS="127.0.0.1:51671" +start_registry $REGISTRY_CONTAINER_NAME $HOST_ADDRESS $HOST_HTPASSWD_PATH + +# Upload images +echo "password" | docker login -u username --password-stdin "$HOST_ADDRESS" +mirror_local_image public.ecr.aws/docker/library/busybox:1.34.1 "${HOST_ADDRESS}/busybox:latest" # create a context folder used by docker build. It will only have a file # full of random bits so that the parallel pull images are different.