Migrate unit tests for image pulling credentials and error handling
Also remove the dockertools package completely.
This commit is contained in:
parent
48caf95a6c
commit
4b72d229f7
@ -18,6 +18,9 @@ package testing
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"sync"
|
"sync"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
runtimeapi "k8s.io/kubernetes/pkg/kubelet/api/v1alpha1/runtime"
|
runtimeapi "k8s.io/kubernetes/pkg/kubelet/api/v1alpha1/runtime"
|
||||||
"k8s.io/kubernetes/pkg/kubelet/util/sliceutils"
|
"k8s.io/kubernetes/pkg/kubelet/util/sliceutils"
|
||||||
@ -29,6 +32,8 @@ type FakeImageService struct {
|
|||||||
FakeImageSize uint64
|
FakeImageSize uint64
|
||||||
Called []string
|
Called []string
|
||||||
Images map[string]*runtimeapi.Image
|
Images map[string]*runtimeapi.Image
|
||||||
|
|
||||||
|
pulledImages []*pulledImage
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *FakeImageService) SetFakeImages(images []string) {
|
func (r *FakeImageService) SetFakeImages(images []string) {
|
||||||
@ -97,6 +102,7 @@ func (r *FakeImageService) PullImage(image *runtimeapi.ImageSpec, auth *runtimea
|
|||||||
|
|
||||||
r.Called = append(r.Called, "PullImage")
|
r.Called = append(r.Called, "PullImage")
|
||||||
|
|
||||||
|
r.pulledImages = append(r.pulledImages, &pulledImage{imageSpec: image, authConfig: auth})
|
||||||
// ImageID should be randomized for real container runtime, but here just use
|
// ImageID should be randomized for real container runtime, but here just use
|
||||||
// image's name for easily making fake images.
|
// image's name for easily making fake images.
|
||||||
imageID := image.Image
|
imageID := image.Image
|
||||||
@ -128,3 +134,15 @@ func (r *FakeImageService) ImageFsInfo() (*runtimeapi.FsInfo, error) {
|
|||||||
|
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *FakeImageService) AssertImagePulledWithAuth(t *testing.T, image *runtimeapi.ImageSpec, auth *runtimeapi.AuthConfig, failMsg string) {
|
||||||
|
r.Lock()
|
||||||
|
defer r.Unlock()
|
||||||
|
expected := &pulledImage{imageSpec: image, authConfig: auth}
|
||||||
|
assert.Contains(t, r.pulledImages, expected, failMsg)
|
||||||
|
}
|
||||||
|
|
||||||
|
type pulledImage struct {
|
||||||
|
imageSpec *runtimeapi.ImageSpec
|
||||||
|
authConfig *runtimeapi.AuthConfig
|
||||||
|
}
|
||||||
|
@ -18,10 +18,12 @@ package dockershim
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/docker/docker/pkg/jsonmessage"
|
||||||
dockertypes "github.com/docker/engine-api/types"
|
dockertypes "github.com/docker/engine-api/types"
|
||||||
runtimeapi "k8s.io/kubernetes/pkg/kubelet/api/v1alpha1/runtime"
|
|
||||||
|
|
||||||
|
runtimeapi "k8s.io/kubernetes/pkg/kubelet/api/v1alpha1/runtime"
|
||||||
"k8s.io/kubernetes/pkg/kubelet/dockershim/libdocker"
|
"k8s.io/kubernetes/pkg/kubelet/dockershim/libdocker"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -80,7 +82,7 @@ func (ds *dockerService) PullImage(image *runtimeapi.ImageSpec, auth *runtimeapi
|
|||||||
dockertypes.ImagePullOptions{},
|
dockertypes.ImagePullOptions{},
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", filterHTTPError(err, image.Image)
|
||||||
}
|
}
|
||||||
|
|
||||||
return getImageRef(ds.client, image.Image)
|
return getImageRef(ds.client, image.Image)
|
||||||
@ -127,3 +129,18 @@ func getImageRef(client libdocker.Interface, image string) (string, error) {
|
|||||||
func (ds *dockerService) ImageFsInfo() (*runtimeapi.FsInfo, error) {
|
func (ds *dockerService) ImageFsInfo() (*runtimeapi.FsInfo, error) {
|
||||||
return nil, fmt.Errorf("not implemented")
|
return nil, fmt.Errorf("not implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func filterHTTPError(err error, image string) error {
|
||||||
|
// docker/docker/pull/11314 prints detailed error info for docker pull.
|
||||||
|
// When it hits 502, it returns a verbose html output including an inline svg,
|
||||||
|
// which makes the output of kubectl get pods much harder to parse.
|
||||||
|
// Here converts such verbose output to a concise one.
|
||||||
|
jerr, ok := err.(*jsonmessage.JSONError)
|
||||||
|
if ok && (jerr.Code == http.StatusBadGateway ||
|
||||||
|
jerr.Code == http.StatusServiceUnavailable ||
|
||||||
|
jerr.Code == http.StatusGatewayTimeout) {
|
||||||
|
return fmt.Errorf("RegistryUnavailable: %v", err)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
|
||||||
|
}
|
||||||
|
@ -17,12 +17,14 @@ limitations under the License.
|
|||||||
package dockershim
|
package dockershim
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/docker/docker/pkg/jsonmessage"
|
||||||
dockertypes "github.com/docker/engine-api/types"
|
dockertypes "github.com/docker/engine-api/types"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
runtimeapi "k8s.io/kubernetes/pkg/kubelet/api/v1alpha1/runtime"
|
runtimeapi "k8s.io/kubernetes/pkg/kubelet/api/v1alpha1/runtime"
|
||||||
|
|
||||||
"k8s.io/kubernetes/pkg/kubelet/dockershim/libdocker"
|
"k8s.io/kubernetes/pkg/kubelet/dockershim/libdocker"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -44,3 +46,29 @@ func TestRemoveImageWithMultipleTags(t *testing.T) {
|
|||||||
libdocker.NewCalledDetail("remove_image", []interface{}{"foo", dockertypes.ImageRemoveOptions{PruneChildren: true}}),
|
libdocker.NewCalledDetail("remove_image", []interface{}{"foo", dockertypes.ImageRemoveOptions{PruneChildren: true}}),
|
||||||
libdocker.NewCalledDetail("remove_image", []interface{}{"bar", dockertypes.ImageRemoveOptions{PruneChildren: true}}))
|
libdocker.NewCalledDetail("remove_image", []interface{}{"bar", dockertypes.ImageRemoveOptions{PruneChildren: true}}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestPullWithJSONError(t *testing.T) {
|
||||||
|
ds, fakeDocker, _ := newTestDockerService()
|
||||||
|
tests := map[string]struct {
|
||||||
|
image *runtimeapi.ImageSpec
|
||||||
|
err error
|
||||||
|
expectedError string
|
||||||
|
}{
|
||||||
|
"Json error": {
|
||||||
|
&runtimeapi.ImageSpec{Image: "ubuntu"},
|
||||||
|
&jsonmessage.JSONError{Code: 50, Message: "Json error"},
|
||||||
|
"Json error",
|
||||||
|
},
|
||||||
|
"Bad gateway": {
|
||||||
|
&runtimeapi.ImageSpec{Image: "ubuntu"},
|
||||||
|
&jsonmessage.JSONError{Code: 502, Message: "<!doctype html>\n<html class=\"no-js\" lang=\"\">\n <head>\n </head>\n <body>\n <h1>Oops, there was an error!</h1>\n <p>We have been contacted of this error, feel free to check out <a href=\"http://status.docker.com/\">status.docker.com</a>\n to see if there is a bigger issue.</p>\n\n </body>\n</html>"},
|
||||||
|
"RegistryUnavailable",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for key, test := range tests {
|
||||||
|
fakeDocker.InjectError("pull", test.err)
|
||||||
|
_, err := ds.PullImage(test.image, &runtimeapi.AuthConfig{})
|
||||||
|
assert.Error(t, err, fmt.Sprintf("TestCase [%s]", key))
|
||||||
|
assert.Contains(t, err.Error(), test.expectedError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,54 +0,0 @@
|
|||||||
package(default_visibility = ["//visibility:public"])
|
|
||||||
|
|
||||||
licenses(["notice"])
|
|
||||||
|
|
||||||
load(
|
|
||||||
"@io_bazel_rules_go//go:def.bzl",
|
|
||||||
"go_library",
|
|
||||||
"go_test",
|
|
||||||
)
|
|
||||||
|
|
||||||
go_library(
|
|
||||||
name = "go_default_library",
|
|
||||||
srcs = ["docker.go"],
|
|
||||||
tags = ["automanaged"],
|
|
||||||
deps = [
|
|
||||||
"//pkg/api/v1:go_default_library",
|
|
||||||
"//pkg/credentialprovider:go_default_library",
|
|
||||||
"//pkg/kubelet/dockershim/libdocker:go_default_library",
|
|
||||||
"//pkg/kubelet/images:go_default_library",
|
|
||||||
"//vendor/github.com/docker/docker/pkg/jsonmessage:go_default_library",
|
|
||||||
"//vendor/github.com/docker/engine-api/types:go_default_library",
|
|
||||||
"//vendor/github.com/golang/glog:go_default_library",
|
|
||||||
"//vendor/k8s.io/apimachinery/pkg/util/errors:go_default_library",
|
|
||||||
],
|
|
||||||
)
|
|
||||||
|
|
||||||
go_test(
|
|
||||||
name = "go_default_test",
|
|
||||||
srcs = ["docker_test.go"],
|
|
||||||
library = ":go_default_library",
|
|
||||||
tags = [
|
|
||||||
"automanaged",
|
|
||||||
],
|
|
||||||
deps = [
|
|
||||||
"//pkg/api/v1:go_default_library",
|
|
||||||
"//pkg/credentialprovider:go_default_library",
|
|
||||||
"//pkg/kubelet/dockershim/libdocker:go_default_library",
|
|
||||||
"//pkg/kubelet/images:go_default_library",
|
|
||||||
"//vendor/github.com/docker/docker/pkg/jsonmessage:go_default_library",
|
|
||||||
],
|
|
||||||
)
|
|
||||||
|
|
||||||
filegroup(
|
|
||||||
name = "package-srcs",
|
|
||||||
srcs = glob(["**"]),
|
|
||||||
tags = ["automanaged"],
|
|
||||||
visibility = ["//visibility:private"],
|
|
||||||
)
|
|
||||||
|
|
||||||
filegroup(
|
|
||||||
name = "all-srcs",
|
|
||||||
srcs = [":package-srcs"],
|
|
||||||
tags = ["automanaged"],
|
|
||||||
)
|
|
@ -1,3 +0,0 @@
|
|||||||
approvers:
|
|
||||||
- Random-Liu
|
|
||||||
- yujuhong
|
|
@ -1,141 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2014 The Kubernetes Authors.
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License 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 dockertools
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/docker/docker/pkg/jsonmessage"
|
|
||||||
dockertypes "github.com/docker/engine-api/types"
|
|
||||||
"github.com/golang/glog"
|
|
||||||
utilerrors "k8s.io/apimachinery/pkg/util/errors"
|
|
||||||
"k8s.io/kubernetes/pkg/api/v1"
|
|
||||||
"k8s.io/kubernetes/pkg/credentialprovider"
|
|
||||||
"k8s.io/kubernetes/pkg/kubelet/dockershim/libdocker"
|
|
||||||
"k8s.io/kubernetes/pkg/kubelet/images"
|
|
||||||
)
|
|
||||||
|
|
||||||
// DockerPuller is an abstract interface for testability. It abstracts image pull operations.
|
|
||||||
// DockerPuller is *not* in use anywhere in the codebase.
|
|
||||||
// TODO: Examine whether we can migrate the unit tests and remove the code.
|
|
||||||
type DockerPuller interface {
|
|
||||||
Pull(image string, secrets []v1.Secret) error
|
|
||||||
GetImageRef(image string) (string, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
// dockerPuller is the default implementation of DockerPuller.
|
|
||||||
type dockerPuller struct {
|
|
||||||
client libdocker.Interface
|
|
||||||
keyring credentialprovider.DockerKeyring
|
|
||||||
}
|
|
||||||
|
|
||||||
// newDockerPuller creates a new instance of the default implementation of DockerPuller.
|
|
||||||
func newDockerPuller(client libdocker.Interface) DockerPuller {
|
|
||||||
return &dockerPuller{
|
|
||||||
client: client,
|
|
||||||
keyring: credentialprovider.NewDockerKeyring(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func filterHTTPError(err error, image string) error {
|
|
||||||
// docker/docker/pull/11314 prints detailed error info for docker pull.
|
|
||||||
// When it hits 502, it returns a verbose html output including an inline svg,
|
|
||||||
// which makes the output of kubectl get pods much harder to parse.
|
|
||||||
// Here converts such verbose output to a concise one.
|
|
||||||
jerr, ok := err.(*jsonmessage.JSONError)
|
|
||||||
if ok && (jerr.Code == http.StatusBadGateway ||
|
|
||||||
jerr.Code == http.StatusServiceUnavailable ||
|
|
||||||
jerr.Code == http.StatusGatewayTimeout) {
|
|
||||||
glog.V(2).Infof("Pulling image %q failed: %v", image, err)
|
|
||||||
return images.RegistryUnavailable
|
|
||||||
} else {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p dockerPuller) Pull(image string, secrets []v1.Secret) error {
|
|
||||||
keyring, err := credentialprovider.MakeDockerKeyring(secrets, p.keyring)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// The only used image pull option RegistryAuth will be set in kube_docker_client
|
|
||||||
opts := dockertypes.ImagePullOptions{}
|
|
||||||
|
|
||||||
creds, haveCredentials := keyring.Lookup(image)
|
|
||||||
if !haveCredentials {
|
|
||||||
glog.V(1).Infof("Pulling image %s without credentials", image)
|
|
||||||
|
|
||||||
err := p.client.PullImage(image, dockertypes.AuthConfig{}, opts)
|
|
||||||
if err == nil {
|
|
||||||
// Sometimes PullImage failed with no error returned.
|
|
||||||
imageRef, ierr := p.GetImageRef(image)
|
|
||||||
if ierr != nil {
|
|
||||||
glog.Warningf("Failed to inspect image %s: %v", image, ierr)
|
|
||||||
}
|
|
||||||
if imageRef == "" {
|
|
||||||
return fmt.Errorf("image pull failed for unknown error")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Image spec: [<registry>/]<repository>/<image>[:<version] so we count '/'
|
|
||||||
explicitRegistry := (strings.Count(image, "/") == 2)
|
|
||||||
// Hack, look for a private registry, and decorate the error with the lack of
|
|
||||||
// credentials. This is heuristic, and really probably could be done better
|
|
||||||
// by talking to the registry API directly from the kubelet here.
|
|
||||||
if explicitRegistry {
|
|
||||||
return fmt.Errorf("image pull failed for %s, this may be because there are no credentials on this request. details: (%v)", image, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return filterHTTPError(err, image)
|
|
||||||
}
|
|
||||||
|
|
||||||
var pullErrs []error
|
|
||||||
for _, currentCreds := range creds {
|
|
||||||
err = p.client.PullImage(image, credentialprovider.LazyProvide(currentCreds), opts)
|
|
||||||
// If there was no error, return success
|
|
||||||
if err == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
pullErrs = append(pullErrs, filterHTTPError(err, image))
|
|
||||||
}
|
|
||||||
|
|
||||||
return utilerrors.NewAggregate(pullErrs)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p dockerPuller) GetImageRef(image string) (string, error) {
|
|
||||||
resp, err := p.client.InspectImageByRef(image)
|
|
||||||
if err == nil {
|
|
||||||
if resp == nil {
|
|
||||||
return "", nil
|
|
||||||
}
|
|
||||||
|
|
||||||
imageRef := resp.ID
|
|
||||||
if len(resp.RepoDigests) > 0 {
|
|
||||||
imageRef = resp.RepoDigests[0]
|
|
||||||
}
|
|
||||||
return imageRef, nil
|
|
||||||
}
|
|
||||||
if libdocker.IsImageNotFoundError(err) {
|
|
||||||
return "", nil
|
|
||||||
}
|
|
||||||
return "", err
|
|
||||||
}
|
|
@ -1,179 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2014 The Kubernetes Authors.
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License 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 dockertools
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/docker/docker/pkg/jsonmessage"
|
|
||||||
"k8s.io/kubernetes/pkg/api/v1"
|
|
||||||
"k8s.io/kubernetes/pkg/credentialprovider"
|
|
||||||
"k8s.io/kubernetes/pkg/kubelet/dockershim/libdocker"
|
|
||||||
"k8s.io/kubernetes/pkg/kubelet/images"
|
|
||||||
)
|
|
||||||
|
|
||||||
// TODO: Examine the tests and see if they can be migrated to kuberuntime.
|
|
||||||
func TestPullWithNoSecrets(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
imageName string
|
|
||||||
expectedImage string
|
|
||||||
}{
|
|
||||||
{"ubuntu", "ubuntu using {}"},
|
|
||||||
{"ubuntu:2342", "ubuntu:2342 using {}"},
|
|
||||||
{"ubuntu:latest", "ubuntu:latest using {}"},
|
|
||||||
{"foo/bar:445566", "foo/bar:445566 using {}"},
|
|
||||||
{"registry.example.com:5000/foobar", "registry.example.com:5000/foobar using {}"},
|
|
||||||
{"registry.example.com:5000/foobar:5342", "registry.example.com:5000/foobar:5342 using {}"},
|
|
||||||
{"registry.example.com:5000/foobar:latest", "registry.example.com:5000/foobar:latest using {}"},
|
|
||||||
}
|
|
||||||
for _, test := range tests {
|
|
||||||
fakeKeyring := &credentialprovider.FakeKeyring{}
|
|
||||||
fakeClient := libdocker.NewFakeDockerClient()
|
|
||||||
|
|
||||||
dp := dockerPuller{
|
|
||||||
client: fakeClient,
|
|
||||||
keyring: fakeKeyring,
|
|
||||||
}
|
|
||||||
|
|
||||||
err := dp.Pull(test.imageName, []v1.Secret{})
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("unexpected non-nil err: %s", err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := fakeClient.AssertImagesPulled([]string{test.imageName}); err != nil {
|
|
||||||
t.Errorf("images pulled do not match the expected: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPullWithJSONError(t *testing.T) {
|
|
||||||
tests := map[string]struct {
|
|
||||||
imageName string
|
|
||||||
err error
|
|
||||||
expectedError string
|
|
||||||
}{
|
|
||||||
"Json error": {
|
|
||||||
"ubuntu",
|
|
||||||
&jsonmessage.JSONError{Code: 50, Message: "Json error"},
|
|
||||||
"Json error",
|
|
||||||
},
|
|
||||||
"Bad gateway": {
|
|
||||||
"ubuntu",
|
|
||||||
&jsonmessage.JSONError{Code: 502, Message: "<!doctype html>\n<html class=\"no-js\" lang=\"\">\n <head>\n </head>\n <body>\n <h1>Oops, there was an error!</h1>\n <p>We have been contacted of this error, feel free to check out <a href=\"http://status.docker.com/\">status.docker.com</a>\n to see if there is a bigger issue.</p>\n\n </body>\n</html>"},
|
|
||||||
images.RegistryUnavailable.Error(),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for i, test := range tests {
|
|
||||||
fakeKeyring := &credentialprovider.FakeKeyring{}
|
|
||||||
fakeClient := libdocker.NewFakeDockerClient()
|
|
||||||
fakeClient.InjectError("pull", test.err)
|
|
||||||
|
|
||||||
puller := &dockerPuller{
|
|
||||||
client: fakeClient,
|
|
||||||
keyring: fakeKeyring,
|
|
||||||
}
|
|
||||||
err := puller.Pull(test.imageName, []v1.Secret{})
|
|
||||||
if err == nil || !strings.Contains(err.Error(), test.expectedError) {
|
|
||||||
t.Errorf("%s: expect error %s, got : %s", i, test.expectedError, err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPullWithSecrets(t *testing.T) {
|
|
||||||
// auth value is equivalent to: "username":"passed-user","password":"passed-password"
|
|
||||||
dockerCfg := map[string]map[string]string{"index.docker.io/v1/": {"email": "passed-email", "auth": "cGFzc2VkLXVzZXI6cGFzc2VkLXBhc3N3b3Jk"}}
|
|
||||||
dockercfgContent, err := json.Marshal(dockerCfg)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("unexpected error: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
dockerConfigJson := map[string]map[string]map[string]string{"auths": dockerCfg}
|
|
||||||
dockerConfigJsonContent, err := json.Marshal(dockerConfigJson)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("unexpected error: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
tests := map[string]struct {
|
|
||||||
imageName string
|
|
||||||
passedSecrets []v1.Secret
|
|
||||||
builtInDockerConfig credentialprovider.DockerConfig
|
|
||||||
expectedPulls []string
|
|
||||||
}{
|
|
||||||
"no matching secrets": {
|
|
||||||
"ubuntu",
|
|
||||||
[]v1.Secret{},
|
|
||||||
credentialprovider.DockerConfig(map[string]credentialprovider.DockerConfigEntry{}),
|
|
||||||
[]string{"ubuntu using {}"},
|
|
||||||
},
|
|
||||||
"default keyring secrets": {
|
|
||||||
"ubuntu",
|
|
||||||
[]v1.Secret{},
|
|
||||||
credentialprovider.DockerConfig(map[string]credentialprovider.DockerConfigEntry{
|
|
||||||
"index.docker.io/v1/": {Username: "built-in", Password: "password", Email: "email", Provider: nil},
|
|
||||||
}),
|
|
||||||
[]string{`ubuntu using {"username":"built-in","password":"password","email":"email"}`},
|
|
||||||
},
|
|
||||||
"default keyring secrets unused": {
|
|
||||||
"ubuntu",
|
|
||||||
[]v1.Secret{},
|
|
||||||
credentialprovider.DockerConfig(map[string]credentialprovider.DockerConfigEntry{
|
|
||||||
"extraneous": {Username: "built-in", Password: "password", Email: "email", Provider: nil},
|
|
||||||
}),
|
|
||||||
[]string{`ubuntu using {}`},
|
|
||||||
},
|
|
||||||
"builtin keyring secrets, but use passed": {
|
|
||||||
"ubuntu",
|
|
||||||
[]v1.Secret{{Type: v1.SecretTypeDockercfg, Data: map[string][]byte{v1.DockerConfigKey: dockercfgContent}}},
|
|
||||||
credentialprovider.DockerConfig(map[string]credentialprovider.DockerConfigEntry{
|
|
||||||
"index.docker.io/v1/": {Username: "built-in", Password: "password", Email: "email", Provider: nil},
|
|
||||||
}),
|
|
||||||
[]string{`ubuntu using {"username":"passed-user","password":"passed-password","email":"passed-email"}`},
|
|
||||||
},
|
|
||||||
"builtin keyring secrets, but use passed with new docker config": {
|
|
||||||
"ubuntu",
|
|
||||||
[]v1.Secret{{Type: v1.SecretTypeDockerConfigJson, Data: map[string][]byte{v1.DockerConfigJsonKey: dockerConfigJsonContent}}},
|
|
||||||
credentialprovider.DockerConfig(map[string]credentialprovider.DockerConfigEntry{
|
|
||||||
"index.docker.io/v1/": {Username: "built-in", Password: "password", Email: "email", Provider: nil},
|
|
||||||
}),
|
|
||||||
[]string{`ubuntu using {"username":"passed-user","password":"passed-password","email":"passed-email"}`},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for i, test := range tests {
|
|
||||||
builtInKeyRing := &credentialprovider.BasicDockerKeyring{}
|
|
||||||
builtInKeyRing.Add(test.builtInDockerConfig)
|
|
||||||
|
|
||||||
fakeClient := libdocker.NewFakeDockerClient()
|
|
||||||
|
|
||||||
dp := dockerPuller{
|
|
||||||
client: fakeClient,
|
|
||||||
keyring: builtInKeyRing,
|
|
||||||
}
|
|
||||||
|
|
||||||
err := dp.Pull(test.imageName, test.passedSecrets)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("%s: unexpected non-nil err: %s", i, err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if err := fakeClient.AssertImagesPulledMsgs(test.expectedPulls); err != nil {
|
|
||||||
t.Errorf("images pulled do not match the expected: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -56,7 +56,7 @@ func (f *fakePodGetter) GetPodByUID(uid types.UID) (*v1.Pod, bool) {
|
|||||||
return pod, found
|
return pod, found
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewFakeKubeRuntimeManager(runtimeService internalapi.RuntimeService, imageService internalapi.ImageManagerService, machineInfo *cadvisorapi.MachineInfo, osInterface kubecontainer.OSInterface, runtimeHelper kubecontainer.RuntimeHelper) (*kubeGenericRuntimeManager, error) {
|
func NewFakeKubeRuntimeManager(runtimeService internalapi.RuntimeService, imageService internalapi.ImageManagerService, machineInfo *cadvisorapi.MachineInfo, osInterface kubecontainer.OSInterface, runtimeHelper kubecontainer.RuntimeHelper, keyring credentialprovider.DockerKeyring) (*kubeGenericRuntimeManager, error) {
|
||||||
recorder := &record.FakeRecorder{}
|
recorder := &record.FakeRecorder{}
|
||||||
kubeRuntimeManager := &kubeGenericRuntimeManager{
|
kubeRuntimeManager := &kubeGenericRuntimeManager{
|
||||||
recorder: recorder,
|
recorder: recorder,
|
||||||
@ -68,7 +68,7 @@ func NewFakeKubeRuntimeManager(runtimeService internalapi.RuntimeService, imageS
|
|||||||
runtimeHelper: runtimeHelper,
|
runtimeHelper: runtimeHelper,
|
||||||
runtimeService: runtimeService,
|
runtimeService: runtimeService,
|
||||||
imageService: imageService,
|
imageService: imageService,
|
||||||
keyring: credentialprovider.NewDockerKeyring(),
|
keyring: keyring,
|
||||||
}
|
}
|
||||||
|
|
||||||
typedVersion, err := runtimeService.Version(kubeRuntimeAPIVersion)
|
typedVersion, err := runtimeService.Version(kubeRuntimeAPIVersion)
|
||||||
|
@ -17,10 +17,16 @@ limitations under the License.
|
|||||||
package kuberuntime
|
package kuberuntime
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
"k8s.io/apimachinery/pkg/util/sets"
|
"k8s.io/apimachinery/pkg/util/sets"
|
||||||
|
"k8s.io/kubernetes/pkg/api/v1"
|
||||||
|
"k8s.io/kubernetes/pkg/credentialprovider"
|
||||||
|
runtimeapi "k8s.io/kubernetes/pkg/kubelet/api/v1alpha1/runtime"
|
||||||
kubecontainer "k8s.io/kubernetes/pkg/kubelet/container"
|
kubecontainer "k8s.io/kubernetes/pkg/kubelet/container"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -94,3 +100,74 @@ func TestImageStats(t *testing.T) {
|
|||||||
expectedStats := &kubecontainer.ImageStats{TotalStorageBytes: imageSize * uint64(len(images))}
|
expectedStats := &kubecontainer.ImageStats{TotalStorageBytes: imageSize * uint64(len(images))}
|
||||||
assert.Equal(t, expectedStats, actualStats)
|
assert.Equal(t, expectedStats, actualStats)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestPullWithSecrets(t *testing.T) {
|
||||||
|
// auth value is equivalent to: "username":"passed-user","password":"passed-password"
|
||||||
|
dockerCfg := map[string]map[string]string{"index.docker.io/v1/": {"email": "passed-email", "auth": "cGFzc2VkLXVzZXI6cGFzc2VkLXBhc3N3b3Jk"}}
|
||||||
|
dockercfgContent, err := json.Marshal(dockerCfg)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
dockerConfigJson := map[string]map[string]map[string]string{"auths": dockerCfg}
|
||||||
|
dockerConfigJsonContent, err := json.Marshal(dockerConfigJson)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
tests := map[string]struct {
|
||||||
|
imageName string
|
||||||
|
passedSecrets []v1.Secret
|
||||||
|
builtInDockerConfig credentialprovider.DockerConfig
|
||||||
|
expectedAuth *runtimeapi.AuthConfig
|
||||||
|
}{
|
||||||
|
"no matching secrets": {
|
||||||
|
"ubuntu",
|
||||||
|
[]v1.Secret{},
|
||||||
|
credentialprovider.DockerConfig(map[string]credentialprovider.DockerConfigEntry{}),
|
||||||
|
nil,
|
||||||
|
},
|
||||||
|
"default keyring secrets": {
|
||||||
|
"ubuntu",
|
||||||
|
[]v1.Secret{},
|
||||||
|
credentialprovider.DockerConfig(map[string]credentialprovider.DockerConfigEntry{
|
||||||
|
"index.docker.io/v1/": {Username: "built-in", Password: "password", Provider: nil},
|
||||||
|
}),
|
||||||
|
&runtimeapi.AuthConfig{Username: "built-in", Password: "password"},
|
||||||
|
},
|
||||||
|
"default keyring secrets unused": {
|
||||||
|
"ubuntu",
|
||||||
|
[]v1.Secret{},
|
||||||
|
credentialprovider.DockerConfig(map[string]credentialprovider.DockerConfigEntry{
|
||||||
|
"extraneous": {Username: "built-in", Password: "password", Provider: nil},
|
||||||
|
}),
|
||||||
|
nil,
|
||||||
|
},
|
||||||
|
"builtin keyring secrets, but use passed": {
|
||||||
|
"ubuntu",
|
||||||
|
[]v1.Secret{{Type: v1.SecretTypeDockercfg, Data: map[string][]byte{v1.DockerConfigKey: dockercfgContent}}},
|
||||||
|
credentialprovider.DockerConfig(map[string]credentialprovider.DockerConfigEntry{
|
||||||
|
"index.docker.io/v1/": {Username: "built-in", Password: "password", Provider: nil},
|
||||||
|
}),
|
||||||
|
&runtimeapi.AuthConfig{Username: "passed-user", Password: "passed-password"},
|
||||||
|
},
|
||||||
|
"builtin keyring secrets, but use passed with new docker config": {
|
||||||
|
"ubuntu",
|
||||||
|
[]v1.Secret{{Type: v1.SecretTypeDockerConfigJson, Data: map[string][]byte{v1.DockerConfigJsonKey: dockerConfigJsonContent}}},
|
||||||
|
credentialprovider.DockerConfig(map[string]credentialprovider.DockerConfigEntry{
|
||||||
|
"index.docker.io/v1/": {Username: "built-in", Password: "password", Provider: nil},
|
||||||
|
}),
|
||||||
|
&runtimeapi.AuthConfig{Username: "passed-user", Password: "passed-password"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for description, test := range tests {
|
||||||
|
builtInKeyRing := &credentialprovider.BasicDockerKeyring{}
|
||||||
|
builtInKeyRing.Add(test.builtInDockerConfig)
|
||||||
|
_, fakeImageService, fakeManager, err := customTestRuntimeManager(builtInKeyRing)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
_, err = fakeManager.PullImage(kubecontainer.ImageSpec{Image: test.imageName}, test.passedSecrets)
|
||||||
|
require.NoError(t, err)
|
||||||
|
fakeImageService.AssertImagePulledWithAuth(t, &runtimeapi.ImageSpec{Image: test.imageName}, test.expectedAuth, description)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -24,11 +24,13 @@ import (
|
|||||||
|
|
||||||
cadvisorapi "github.com/google/cadvisor/info/v1"
|
cadvisorapi "github.com/google/cadvisor/info/v1"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
"k8s.io/apimachinery/pkg/types"
|
"k8s.io/apimachinery/pkg/types"
|
||||||
kubetypes "k8s.io/apimachinery/pkg/types"
|
kubetypes "k8s.io/apimachinery/pkg/types"
|
||||||
"k8s.io/client-go/util/flowcontrol"
|
"k8s.io/client-go/util/flowcontrol"
|
||||||
"k8s.io/kubernetes/pkg/api/v1"
|
"k8s.io/kubernetes/pkg/api/v1"
|
||||||
|
"k8s.io/kubernetes/pkg/credentialprovider"
|
||||||
apitest "k8s.io/kubernetes/pkg/kubelet/api/testing"
|
apitest "k8s.io/kubernetes/pkg/kubelet/api/testing"
|
||||||
runtimeapi "k8s.io/kubernetes/pkg/kubelet/api/v1alpha1/runtime"
|
runtimeapi "k8s.io/kubernetes/pkg/kubelet/api/v1alpha1/runtime"
|
||||||
kubecontainer "k8s.io/kubernetes/pkg/kubelet/container"
|
kubecontainer "k8s.io/kubernetes/pkg/kubelet/container"
|
||||||
@ -40,6 +42,10 @@ var (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func createTestRuntimeManager() (*apitest.FakeRuntimeService, *apitest.FakeImageService, *kubeGenericRuntimeManager, error) {
|
func createTestRuntimeManager() (*apitest.FakeRuntimeService, *apitest.FakeImageService, *kubeGenericRuntimeManager, error) {
|
||||||
|
return customTestRuntimeManager(&credentialprovider.BasicDockerKeyring{})
|
||||||
|
}
|
||||||
|
|
||||||
|
func customTestRuntimeManager(keyring *credentialprovider.BasicDockerKeyring) (*apitest.FakeRuntimeService, *apitest.FakeImageService, *kubeGenericRuntimeManager, error) {
|
||||||
fakeRuntimeService := apitest.NewFakeRuntimeService()
|
fakeRuntimeService := apitest.NewFakeRuntimeService()
|
||||||
fakeImageService := apitest.NewFakeImageService()
|
fakeImageService := apitest.NewFakeImageService()
|
||||||
// Only an empty machineInfo is needed here, because in unit test all containers are besteffort,
|
// Only an empty machineInfo is needed here, because in unit test all containers are besteffort,
|
||||||
@ -47,7 +53,7 @@ func createTestRuntimeManager() (*apitest.FakeRuntimeService, *apitest.FakeImage
|
|||||||
// we may want to set memory capacity.
|
// we may want to set memory capacity.
|
||||||
machineInfo := &cadvisorapi.MachineInfo{}
|
machineInfo := &cadvisorapi.MachineInfo{}
|
||||||
osInterface := &containertest.FakeOS{}
|
osInterface := &containertest.FakeOS{}
|
||||||
manager, err := NewFakeKubeRuntimeManager(fakeRuntimeService, fakeImageService, machineInfo, osInterface, &containertest.FakeRuntimeHelper{})
|
manager, err := NewFakeKubeRuntimeManager(fakeRuntimeService, fakeImageService, machineInfo, osInterface, &containertest.FakeRuntimeHelper{}, keyring)
|
||||||
return fakeRuntimeService, fakeImageService, manager, err
|
return fakeRuntimeService, fakeImageService, manager, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user