mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-28 05:57:25 +00:00
Prepulls images by default
kubeadm now pulls container images before the init step if it cannot find them on the system * This commit also cleans up a dependency cycle Closes #825
This commit is contained in:
parent
c0f91a8a1e
commit
2f2de31d3d
@ -301,7 +301,7 @@ func NewCmdConfigImagesPull() *cobra.Command {
|
|||||||
kubeadmutil.CheckErr(err)
|
kubeadmutil.CheckErr(err)
|
||||||
internalcfg, err := configutil.ConfigFileAndDefaultsToInternalConfig(cfgPath, cfg)
|
internalcfg, err := configutil.ConfigFileAndDefaultsToInternalConfig(cfgPath, cfg)
|
||||||
kubeadmutil.CheckErr(err)
|
kubeadmutil.CheckErr(err)
|
||||||
puller, err := images.NewImagePuller(utilsexec.New(), internalcfg.GetCRISocket())
|
puller, err := images.NewCRInterfacer(utilsexec.New(), internalcfg.GetCRISocket())
|
||||||
kubeadmutil.CheckErr(err)
|
kubeadmutil.CheckErr(err)
|
||||||
imagesPull := NewImagesPull(puller, images.GetAllImages(internalcfg))
|
imagesPull := NewImagesPull(puller, images.GetAllImages(internalcfg))
|
||||||
kubeadmutil.CheckErr(imagesPull.PullAll())
|
kubeadmutil.CheckErr(imagesPull.PullAll())
|
||||||
|
@ -258,6 +258,9 @@ func NewInit(cfgPath string, externalcfg *kubeadmapiv1alpha2.MasterConfiguration
|
|||||||
if err := preflight.RunInitMasterChecks(utilsexec.New(), cfg, ignorePreflightErrors); err != nil {
|
if err := preflight.RunInitMasterChecks(utilsexec.New(), cfg, ignorePreflightErrors); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
if err := preflight.RunPullImagesCheck(utilsexec.New(), cfg, ignorePreflightErrors); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
return &Init{cfg: cfg, skipTokenPrint: skipTokenPrint, dryRun: dryRun, ignorePreflightErrors: ignorePreflightErrors}, nil
|
return &Init{cfg: cfg, skipTokenPrint: skipTokenPrint, dryRun: dryRun, ignorePreflightErrors: ignorePreflightErrors}, nil
|
||||||
}
|
}
|
||||||
|
@ -10,12 +10,12 @@ go_library(
|
|||||||
name = "go_default_library",
|
name = "go_default_library",
|
||||||
srcs = [
|
srcs = [
|
||||||
"images.go",
|
"images.go",
|
||||||
"puller.go",
|
"interface.go",
|
||||||
],
|
],
|
||||||
importpath = "k8s.io/kubernetes/cmd/kubeadm/app/images",
|
importpath = "k8s.io/kubernetes/cmd/kubeadm/app/images",
|
||||||
deps = [
|
deps = [
|
||||||
"//cmd/kubeadm/app/apis/kubeadm:go_default_library",
|
"//cmd/kubeadm/app/apis/kubeadm:go_default_library",
|
||||||
"//cmd/kubeadm/app/apis/kubeadm/v1alpha1:go_default_library",
|
"//cmd/kubeadm/app/apis/kubeadm/v1alpha2:go_default_library",
|
||||||
"//cmd/kubeadm/app/constants:go_default_library",
|
"//cmd/kubeadm/app/constants:go_default_library",
|
||||||
"//cmd/kubeadm/app/features:go_default_library",
|
"//cmd/kubeadm/app/features:go_default_library",
|
||||||
"//cmd/kubeadm/app/phases/addons/dns:go_default_library",
|
"//cmd/kubeadm/app/phases/addons/dns:go_default_library",
|
||||||
@ -46,10 +46,10 @@ filegroup(
|
|||||||
|
|
||||||
go_test(
|
go_test(
|
||||||
name = "go_default_xtest",
|
name = "go_default_xtest",
|
||||||
srcs = ["puller_test.go"],
|
srcs = ["interface_test.go"],
|
||||||
deps = [
|
deps = [
|
||||||
":go_default_library",
|
":go_default_library",
|
||||||
"//cmd/kubeadm/app/apis/kubeadm/v1alpha1:go_default_library",
|
"//cmd/kubeadm/app/apis/kubeadm/v1alpha2:go_default_library",
|
||||||
"//vendor/k8s.io/utils/exec:go_default_library",
|
"//vendor/k8s.io/utils/exec:go_default_library",
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
89
cmd/kubeadm/app/images/interface.go
Normal file
89
cmd/kubeadm/app/images/interface.go
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2018 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 images
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
kubeadmapiv1alpha2 "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1alpha2"
|
||||||
|
utilsexec "k8s.io/utils/exec"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Puller is an interface for pulling images
|
||||||
|
type Puller interface {
|
||||||
|
Pull(string) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Existence is an interface to determine if an image exists on the system
|
||||||
|
// A nil error means the image was found
|
||||||
|
type Existence interface {
|
||||||
|
Exists(string) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Images defines the set of behaviors needed for images relating to the CRI
|
||||||
|
type Images interface {
|
||||||
|
Puller
|
||||||
|
Existence
|
||||||
|
}
|
||||||
|
|
||||||
|
// CRInterfacer is a struct that interfaces with the container runtime
|
||||||
|
type CRInterfacer struct {
|
||||||
|
criSocket string
|
||||||
|
exec utilsexec.Interface
|
||||||
|
crictlPath string
|
||||||
|
dockerPath string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewCRInterfacer sets up and returns a CRInterfacer
|
||||||
|
func NewCRInterfacer(execer utilsexec.Interface, criSocket string) (*CRInterfacer, error) {
|
||||||
|
var crictlPath, dockerPath string
|
||||||
|
var err error
|
||||||
|
if criSocket != kubeadmapiv1alpha2.DefaultCRISocket {
|
||||||
|
if crictlPath, err = execer.LookPath("crictl"); err != nil {
|
||||||
|
return nil, fmt.Errorf("crictl is required for non docker container runtimes: %v", err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// use the dockershim
|
||||||
|
if dockerPath, err = execer.LookPath("docker"); err != nil {
|
||||||
|
return nil, fmt.Errorf("`docker` is required when docker is the container runtime and the kubelet is not running: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &CRInterfacer{
|
||||||
|
exec: execer,
|
||||||
|
criSocket: criSocket,
|
||||||
|
crictlPath: crictlPath,
|
||||||
|
dockerPath: dockerPath,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pull pulls the actual image using either crictl or docker
|
||||||
|
func (cri *CRInterfacer) Pull(image string) error {
|
||||||
|
if cri.criSocket != kubeadmapiv1alpha2.DefaultCRISocket {
|
||||||
|
return cri.exec.Command(cri.crictlPath, "-r", cri.criSocket, "pull", image).Run()
|
||||||
|
}
|
||||||
|
return cri.exec.Command(cri.dockerPath, "pull", image).Run()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exists checks to see if the image exists on the system already
|
||||||
|
// Returns an error if the image is not found.
|
||||||
|
func (cri *CRInterfacer) Exists(image string) error {
|
||||||
|
if cri.criSocket != kubeadmapiv1alpha2.DefaultCRISocket {
|
||||||
|
return cri.exec.Command(cri.crictlPath, "-r", cri.criSocket, "inspecti", image).Run()
|
||||||
|
}
|
||||||
|
return cri.exec.Command(cri.dockerPath, "inspect", image).Run()
|
||||||
|
}
|
266
cmd/kubeadm/app/images/interface_test.go
Normal file
266
cmd/kubeadm/app/images/interface_test.go
Normal file
@ -0,0 +1,266 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2018 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 images_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
kubeadmapiv1alpha2 "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1alpha2"
|
||||||
|
"k8s.io/kubernetes/cmd/kubeadm/app/images"
|
||||||
|
"k8s.io/utils/exec"
|
||||||
|
)
|
||||||
|
|
||||||
|
type fakeCmd struct {
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *fakeCmd) Run() error {
|
||||||
|
return f.err
|
||||||
|
}
|
||||||
|
func (f *fakeCmd) CombinedOutput() ([]byte, error) { return nil, nil }
|
||||||
|
func (f *fakeCmd) Output() ([]byte, error) { return nil, nil }
|
||||||
|
func (f *fakeCmd) SetDir(dir string) {}
|
||||||
|
func (f *fakeCmd) SetStdin(in io.Reader) {}
|
||||||
|
func (f *fakeCmd) SetStdout(out io.Writer) {}
|
||||||
|
func (f *fakeCmd) SetStderr(out io.Writer) {}
|
||||||
|
func (f *fakeCmd) Stop() {}
|
||||||
|
|
||||||
|
type fakeExecer struct {
|
||||||
|
cmd exec.Cmd
|
||||||
|
findCrictl bool
|
||||||
|
findDocker bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *fakeExecer) Command(cmd string, args ...string) exec.Cmd { return f.cmd }
|
||||||
|
func (f *fakeExecer) CommandContext(ctx context.Context, cmd string, args ...string) exec.Cmd {
|
||||||
|
return f.cmd
|
||||||
|
}
|
||||||
|
func (f *fakeExecer) LookPath(file string) (string, error) {
|
||||||
|
if file == "crictl" {
|
||||||
|
if f.findCrictl {
|
||||||
|
return "/path", nil
|
||||||
|
}
|
||||||
|
return "", errors.New("no crictl for you")
|
||||||
|
}
|
||||||
|
if file == "docker" {
|
||||||
|
if f.findDocker {
|
||||||
|
return "/path", nil
|
||||||
|
}
|
||||||
|
return "", errors.New("no docker for you")
|
||||||
|
}
|
||||||
|
return "", errors.New("unknown binary")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewCRInterfacer(t *testing.T) {
|
||||||
|
testcases := []struct {
|
||||||
|
name string
|
||||||
|
criSocket string
|
||||||
|
findCrictl bool
|
||||||
|
findDocker bool
|
||||||
|
expectError bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "need crictl but can only find docker should return an error",
|
||||||
|
criSocket: "/not/docker",
|
||||||
|
findCrictl: false,
|
||||||
|
findDocker: true,
|
||||||
|
expectError: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "need crictl and cannot find either should return an error",
|
||||||
|
criSocket: "/not/docker",
|
||||||
|
findCrictl: false,
|
||||||
|
findDocker: false,
|
||||||
|
expectError: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "need crictl and cannot find docker should return no error",
|
||||||
|
criSocket: "/not/docker",
|
||||||
|
findCrictl: true,
|
||||||
|
findDocker: false,
|
||||||
|
expectError: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "need crictl and can find both should return no error",
|
||||||
|
criSocket: "/not/docker",
|
||||||
|
findCrictl: true,
|
||||||
|
findDocker: true,
|
||||||
|
expectError: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "need docker and cannot find crictl should return no error",
|
||||||
|
criSocket: kubeadmapiv1alpha2.DefaultCRISocket,
|
||||||
|
findCrictl: false,
|
||||||
|
findDocker: true,
|
||||||
|
expectError: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "need docker and cannot find docker should return an error",
|
||||||
|
criSocket: kubeadmapiv1alpha2.DefaultCRISocket,
|
||||||
|
findCrictl: false,
|
||||||
|
findDocker: false,
|
||||||
|
expectError: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "need docker and can find both should return no error",
|
||||||
|
criSocket: kubeadmapiv1alpha2.DefaultCRISocket,
|
||||||
|
findCrictl: true,
|
||||||
|
findDocker: true,
|
||||||
|
expectError: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "need docker and can only find crictl should return an error",
|
||||||
|
criSocket: kubeadmapiv1alpha2.DefaultCRISocket,
|
||||||
|
findCrictl: true,
|
||||||
|
findDocker: false,
|
||||||
|
expectError: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testcases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
fe := &fakeExecer{
|
||||||
|
findCrictl: tc.findCrictl,
|
||||||
|
findDocker: tc.findDocker,
|
||||||
|
}
|
||||||
|
_, err := images.NewCRInterfacer(fe, tc.criSocket)
|
||||||
|
if tc.expectError && err == nil {
|
||||||
|
t.Fatal("expected an error but did not get one")
|
||||||
|
}
|
||||||
|
if !tc.expectError && err != nil {
|
||||||
|
t.Fatalf("did not expedt an error but got an error: %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestImagePuller(t *testing.T) {
|
||||||
|
testcases := []struct {
|
||||||
|
name string
|
||||||
|
criSocket string
|
||||||
|
pullFails bool
|
||||||
|
errorExpected bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "using docker and pull fails",
|
||||||
|
criSocket: kubeadmapiv1alpha2.DefaultCRISocket,
|
||||||
|
pullFails: true,
|
||||||
|
errorExpected: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "using docker and pull succeeds",
|
||||||
|
criSocket: kubeadmapiv1alpha2.DefaultCRISocket,
|
||||||
|
pullFails: false,
|
||||||
|
errorExpected: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "using crictl pull fails",
|
||||||
|
criSocket: "/not/default",
|
||||||
|
pullFails: true,
|
||||||
|
errorExpected: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "using crictl and pull succeeds",
|
||||||
|
criSocket: "/not/default",
|
||||||
|
pullFails: false,
|
||||||
|
errorExpected: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tc := range testcases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
var err error
|
||||||
|
if tc.pullFails {
|
||||||
|
err = errors.New("error")
|
||||||
|
}
|
||||||
|
|
||||||
|
fe := &fakeExecer{
|
||||||
|
cmd: &fakeCmd{err},
|
||||||
|
findCrictl: true,
|
||||||
|
findDocker: true,
|
||||||
|
}
|
||||||
|
ip, _ := images.NewCRInterfacer(fe, tc.criSocket)
|
||||||
|
|
||||||
|
err = ip.Pull("imageName")
|
||||||
|
if tc.errorExpected && err == nil {
|
||||||
|
t.Fatal("expected an error and did not get one")
|
||||||
|
}
|
||||||
|
if !tc.errorExpected && err != nil {
|
||||||
|
t.Fatalf("expected no error but got one: %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestImageExists(t *testing.T) {
|
||||||
|
testcases := []struct {
|
||||||
|
name string
|
||||||
|
criSocket string
|
||||||
|
existFails bool
|
||||||
|
errorExpected bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "using docker and exist fails",
|
||||||
|
criSocket: kubeadmapiv1alpha2.DefaultCRISocket,
|
||||||
|
existFails: true,
|
||||||
|
errorExpected: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "using docker and exist succeeds",
|
||||||
|
criSocket: kubeadmapiv1alpha2.DefaultCRISocket,
|
||||||
|
existFails: false,
|
||||||
|
errorExpected: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "using crictl exist fails",
|
||||||
|
criSocket: "/not/default",
|
||||||
|
existFails: true,
|
||||||
|
errorExpected: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "using crictl and exist succeeds",
|
||||||
|
criSocket: "/not/default",
|
||||||
|
existFails: false,
|
||||||
|
errorExpected: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tc := range testcases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
var err error
|
||||||
|
if tc.existFails {
|
||||||
|
err = errors.New("error")
|
||||||
|
}
|
||||||
|
|
||||||
|
fe := &fakeExecer{
|
||||||
|
cmd: &fakeCmd{err},
|
||||||
|
findCrictl: true,
|
||||||
|
findDocker: true,
|
||||||
|
}
|
||||||
|
ip, _ := images.NewCRInterfacer(fe, tc.criSocket)
|
||||||
|
|
||||||
|
err = ip.Exists("imageName")
|
||||||
|
if tc.errorExpected && err == nil {
|
||||||
|
t.Fatal("expected an error and did not get one")
|
||||||
|
}
|
||||||
|
if !tc.errorExpected && err != nil {
|
||||||
|
t.Fatalf("expected no error but got one: %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
@ -1,57 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2018 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 images
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
kubeadmapiv1alpha1 "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1alpha1"
|
|
||||||
utilsexec "k8s.io/utils/exec"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Puller is an interface for pulling images
|
|
||||||
type Puller interface {
|
|
||||||
Pull(string) error
|
|
||||||
}
|
|
||||||
|
|
||||||
// ImagePuller is a struct that can pull images and hides the implementation (crictl vs docker)
|
|
||||||
type ImagePuller struct {
|
|
||||||
criSocket string
|
|
||||||
exec utilsexec.Interface
|
|
||||||
crictlPath string
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewImagePuller returns a ready to go ImagePuller
|
|
||||||
func NewImagePuller(execer utilsexec.Interface, criSocket string) (*ImagePuller, error) {
|
|
||||||
crictlPath, err := execer.LookPath("crictl")
|
|
||||||
if err != nil && criSocket != kubeadmapiv1alpha1.DefaultCRISocket {
|
|
||||||
return nil, fmt.Errorf("crictl is required for non docker container runtimes: %v", err)
|
|
||||||
}
|
|
||||||
return &ImagePuller{
|
|
||||||
exec: execer,
|
|
||||||
criSocket: criSocket,
|
|
||||||
crictlPath: crictlPath,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Pull pulls the actual image using either crictl or docker
|
|
||||||
func (ip *ImagePuller) Pull(image string) error {
|
|
||||||
if ip.criSocket != kubeadmapiv1alpha1.DefaultCRISocket {
|
|
||||||
return ip.exec.Command(ip.crictlPath, "-r", ip.criSocket, "pull", image).Run()
|
|
||||||
}
|
|
||||||
return ip.exec.Command("sh", "-c", fmt.Sprintf("docker pull %v", image)).Run()
|
|
||||||
}
|
|
@ -1,138 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2018 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 images_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
kubeadmdefaults "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1alpha1"
|
|
||||||
"k8s.io/kubernetes/cmd/kubeadm/app/images"
|
|
||||||
"k8s.io/utils/exec"
|
|
||||||
)
|
|
||||||
|
|
||||||
type fakeCmd struct {
|
|
||||||
cmd string
|
|
||||||
args []string
|
|
||||||
out io.Writer
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *fakeCmd) Run() error {
|
|
||||||
fmt.Fprintf(f.out, "%v %v", f.cmd, strings.Join(f.args, " "))
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
func (f *fakeCmd) CombinedOutput() ([]byte, error) { return nil, nil }
|
|
||||||
func (f *fakeCmd) Output() ([]byte, error) { return nil, nil }
|
|
||||||
func (f *fakeCmd) SetDir(dir string) {}
|
|
||||||
func (f *fakeCmd) SetStdin(in io.Reader) {}
|
|
||||||
func (f *fakeCmd) SetStdout(out io.Writer) {
|
|
||||||
f.out = out
|
|
||||||
}
|
|
||||||
func (f *fakeCmd) SetStderr(out io.Writer) {}
|
|
||||||
func (f *fakeCmd) Stop() {}
|
|
||||||
|
|
||||||
type fakeExecer struct {
|
|
||||||
cmd exec.Cmd
|
|
||||||
lookPathSucceeds bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *fakeExecer) Command(cmd string, args ...string) exec.Cmd { return f.cmd }
|
|
||||||
func (f *fakeExecer) CommandContext(ctx context.Context, cmd string, args ...string) exec.Cmd {
|
|
||||||
return f.cmd
|
|
||||||
}
|
|
||||||
func (f *fakeExecer) LookPath(file string) (string, error) {
|
|
||||||
if f.lookPathSucceeds {
|
|
||||||
return file, nil
|
|
||||||
}
|
|
||||||
return "", &os.PathError{Err: errors.New("does not exist")}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestImagePuller(t *testing.T) {
|
|
||||||
testcases := []struct {
|
|
||||||
name string
|
|
||||||
criSocket string
|
|
||||||
cmd exec.Cmd
|
|
||||||
findCrictl bool
|
|
||||||
expected string
|
|
||||||
errorExpected bool
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "New succeeds even if crictl is not in path",
|
|
||||||
criSocket: kubeadmdefaults.DefaultCRISocket,
|
|
||||||
cmd: &fakeCmd{
|
|
||||||
cmd: "hello",
|
|
||||||
args: []string{"world", "and", "friends"},
|
|
||||||
},
|
|
||||||
findCrictl: false,
|
|
||||||
expected: "hello world and friends",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "New succeeds with crictl in path",
|
|
||||||
criSocket: "/not/default",
|
|
||||||
cmd: &fakeCmd{
|
|
||||||
cmd: "crictl",
|
|
||||||
args: []string{"-r", "/some/socket", "imagename"},
|
|
||||||
},
|
|
||||||
findCrictl: true,
|
|
||||||
expected: "crictl -r /some/socket imagename",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "New fails with crictl not in path but is required",
|
|
||||||
criSocket: "/not/docker",
|
|
||||||
cmd: &fakeCmd{
|
|
||||||
cmd: "crictl",
|
|
||||||
args: []string{"-r", "/not/docker", "an image"},
|
|
||||||
},
|
|
||||||
findCrictl: false,
|
|
||||||
errorExpected: true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, tc := range testcases {
|
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
|
||||||
var b bytes.Buffer
|
|
||||||
tc.cmd.SetStdout(&b)
|
|
||||||
fe := &fakeExecer{
|
|
||||||
cmd: tc.cmd,
|
|
||||||
lookPathSucceeds: tc.findCrictl,
|
|
||||||
}
|
|
||||||
ip, err := images.NewImagePuller(fe, tc.criSocket)
|
|
||||||
|
|
||||||
if tc.errorExpected {
|
|
||||||
if err == nil {
|
|
||||||
t.Fatalf("expected an error but found nil: %v", fe)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("expected nil but found an error: %v", err)
|
|
||||||
}
|
|
||||||
if err = ip.Pull("imageName"); err != nil {
|
|
||||||
t.Fatalf("expected nil pulling an image but found: %v", err)
|
|
||||||
}
|
|
||||||
if b.String() != tc.expected {
|
|
||||||
t.Fatalf("expected %v but got: %v", tc.expected, b.String())
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
@ -52,6 +52,7 @@ go_library(
|
|||||||
"//cmd/kubeadm/app/apis/kubeadm:go_default_library",
|
"//cmd/kubeadm/app/apis/kubeadm:go_default_library",
|
||||||
"//cmd/kubeadm/app/apis/kubeadm/v1alpha1:go_default_library",
|
"//cmd/kubeadm/app/apis/kubeadm/v1alpha1:go_default_library",
|
||||||
"//cmd/kubeadm/app/constants:go_default_library",
|
"//cmd/kubeadm/app/constants:go_default_library",
|
||||||
|
"//cmd/kubeadm/app/images:go_default_library",
|
||||||
"//pkg/apis/core/validation:go_default_library",
|
"//pkg/apis/core/validation:go_default_library",
|
||||||
"//pkg/registry/core/service/ipallocator:go_default_library",
|
"//pkg/registry/core/service/ipallocator:go_default_library",
|
||||||
"//pkg/util/initsystem:go_default_library",
|
"//pkg/util/initsystem:go_default_library",
|
||||||
|
@ -46,6 +46,7 @@ import (
|
|||||||
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
|
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
|
||||||
kubeadmdefaults "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1alpha1"
|
kubeadmdefaults "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1alpha1"
|
||||||
kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
|
kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
|
||||||
|
"k8s.io/kubernetes/cmd/kubeadm/app/images"
|
||||||
"k8s.io/kubernetes/pkg/apis/core/validation"
|
"k8s.io/kubernetes/pkg/apis/core/validation"
|
||||||
"k8s.io/kubernetes/pkg/registry/core/service/ipallocator"
|
"k8s.io/kubernetes/pkg/registry/core/service/ipallocator"
|
||||||
"k8s.io/kubernetes/pkg/util/initsystem"
|
"k8s.io/kubernetes/pkg/util/initsystem"
|
||||||
@ -76,10 +77,16 @@ type Error struct {
|
|||||||
Msg string
|
Msg string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Error implements the standard error interface
|
||||||
func (e *Error) Error() string {
|
func (e *Error) Error() string {
|
||||||
return fmt.Sprintf("[preflight] Some fatal errors occurred:\n%s%s", e.Msg, "[preflight] If you know what you are doing, you can make a check non-fatal with `--ignore-preflight-errors=...`")
|
return fmt.Sprintf("[preflight] Some fatal errors occurred:\n%s%s", e.Msg, "[preflight] If you know what you are doing, you can make a check non-fatal with `--ignore-preflight-errors=...`")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Preflight identifies this error as a preflight error
|
||||||
|
func (e *Error) Preflight() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
// Checker validates the state of the system to ensure kubeadm will be
|
// Checker validates the state of the system to ensure kubeadm will be
|
||||||
// successful as often as possible.
|
// successful as often as possible.
|
||||||
type Checker interface {
|
type Checker interface {
|
||||||
@ -850,6 +857,30 @@ func (ResolveCheck) Check() (warnings, errors []error) {
|
|||||||
return warnings, errors
|
return warnings, errors
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ImagePullCheck will pull container images used by kubeadm
|
||||||
|
type ImagePullCheck struct {
|
||||||
|
Images images.Images
|
||||||
|
ImageList []string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Name returns the label for ImagePullCheck
|
||||||
|
func (ImagePullCheck) Name() string {
|
||||||
|
return "ImagePull"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check pulls images required by kubeadm. This is a mutating check
|
||||||
|
func (i ImagePullCheck) Check() (warnings, errors []error) {
|
||||||
|
for _, image := range i.ImageList {
|
||||||
|
if err := i.Images.Exists(image); err == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err := i.Images.Pull(image); err != nil {
|
||||||
|
errors = append(errors, fmt.Errorf("failed to pull image [%s]: %v", image, err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return warnings, errors
|
||||||
|
}
|
||||||
|
|
||||||
// RunInitMasterChecks executes all individual, applicable to Master node checks.
|
// RunInitMasterChecks executes all individual, applicable to Master node checks.
|
||||||
func RunInitMasterChecks(execer utilsexec.Interface, cfg *kubeadmapi.MasterConfiguration, ignorePreflightErrors sets.String) error {
|
func RunInitMasterChecks(execer utilsexec.Interface, cfg *kubeadmapi.MasterConfiguration, ignorePreflightErrors sets.String) error {
|
||||||
// First, check if we're root separately from the other preflight checks and fail fast
|
// First, check if we're root separately from the other preflight checks and fail fast
|
||||||
@ -1012,6 +1043,19 @@ func RunRootCheckOnly(ignorePreflightErrors sets.String) error {
|
|||||||
return RunChecks(checks, os.Stderr, ignorePreflightErrors)
|
return RunChecks(checks, os.Stderr, ignorePreflightErrors)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RunPullImagesCheck will pull images kubeadm needs if the are not found on the system
|
||||||
|
func RunPullImagesCheck(execer utilsexec.Interface, cfg *kubeadmapi.MasterConfiguration, ignorePreflightErrors sets.String) error {
|
||||||
|
criInterfacer, err := images.NewCRInterfacer(execer, cfg.GetCRISocket())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
checks := []Checker{
|
||||||
|
ImagePullCheck{Images: criInterfacer, ImageList: images.GetAllImages(cfg)},
|
||||||
|
}
|
||||||
|
return RunChecks(checks, os.Stderr, ignorePreflightErrors)
|
||||||
|
}
|
||||||
|
|
||||||
// RunChecks runs each check, displays it's warnings/errors, and once all
|
// RunChecks runs each check, displays it's warnings/errors, and once all
|
||||||
// are processed will exit if any errors occurred.
|
// are processed will exit if any errors occurred.
|
||||||
func RunChecks(checks []Checker, ww io.Writer, ignorePreflightErrors sets.String) error {
|
func RunChecks(checks []Checker, ww io.Writer, ignorePreflightErrors sets.String) error {
|
||||||
|
@ -18,6 +18,7 @@ package preflight
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"strings"
|
"strings"
|
||||||
@ -696,3 +697,32 @@ func TestSetHasItemOrAll(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type imgs struct{}
|
||||||
|
|
||||||
|
func (i *imgs) Pull(image string) error {
|
||||||
|
if image == "bad pull" {
|
||||||
|
return errors.New("pull error")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
func (i *imgs) Exists(image string) error {
|
||||||
|
if image == "found" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return errors.New("error")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestImagePullCheck(t *testing.T) {
|
||||||
|
i := ImagePullCheck{
|
||||||
|
Images: &imgs{},
|
||||||
|
ImageList: []string{"found", "not found", "bad pull"},
|
||||||
|
}
|
||||||
|
warnings, errors := i.Check()
|
||||||
|
if len(warnings) != 0 {
|
||||||
|
t.Fatalf("did not expect any warnings but got %q", warnings)
|
||||||
|
}
|
||||||
|
if len(errors) != 1 {
|
||||||
|
t.Fatalf("expected 1 errors but got %d: %q", len(errors), errors)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -20,7 +20,6 @@ go_library(
|
|||||||
importpath = "k8s.io/kubernetes/cmd/kubeadm/app/util",
|
importpath = "k8s.io/kubernetes/cmd/kubeadm/app/util",
|
||||||
deps = [
|
deps = [
|
||||||
"//cmd/kubeadm/app/apis/kubeadm:go_default_library",
|
"//cmd/kubeadm/app/apis/kubeadm:go_default_library",
|
||||||
"//cmd/kubeadm/app/preflight:go_default_library",
|
|
||||||
"//vendor/gopkg.in/yaml.v2:go_default_library",
|
"//vendor/gopkg.in/yaml.v2:go_default_library",
|
||||||
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
|
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
|
||||||
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
|
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
|
||||||
@ -47,7 +46,6 @@ go_test(
|
|||||||
"//cmd/kubeadm/app/apis/kubeadm:go_default_library",
|
"//cmd/kubeadm/app/apis/kubeadm:go_default_library",
|
||||||
"//cmd/kubeadm/app/apis/kubeadm/scheme:go_default_library",
|
"//cmd/kubeadm/app/apis/kubeadm/scheme:go_default_library",
|
||||||
"//cmd/kubeadm/app/apis/kubeadm/v1alpha1:go_default_library",
|
"//cmd/kubeadm/app/apis/kubeadm/v1alpha1:go_default_library",
|
||||||
"//cmd/kubeadm/app/preflight:go_default_library",
|
|
||||||
"//vendor/k8s.io/api/core/v1:go_default_library",
|
"//vendor/k8s.io/api/core/v1:go_default_library",
|
||||||
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
||||||
],
|
],
|
||||||
|
@ -22,7 +22,6 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
utilerrors "k8s.io/apimachinery/pkg/util/errors"
|
utilerrors "k8s.io/apimachinery/pkg/util/errors"
|
||||||
"k8s.io/kubernetes/cmd/kubeadm/app/preflight"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -60,13 +59,19 @@ func CheckErr(err error) {
|
|||||||
checkErr("", err, fatal)
|
checkErr("", err, fatal)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// preflightError allows us to know if the error is a preflight error or not
|
||||||
|
// defining the interface here avoids an import cycle of pulling in preflight into the util package
|
||||||
|
type preflightError interface {
|
||||||
|
Preflight() bool
|
||||||
|
}
|
||||||
|
|
||||||
// checkErr formats a given error as a string and calls the passed handleErr
|
// checkErr formats a given error as a string and calls the passed handleErr
|
||||||
// func with that string and an kubectl exit code.
|
// func with that string and an kubectl exit code.
|
||||||
func checkErr(prefix string, err error, handleErr func(string, int)) {
|
func checkErr(prefix string, err error, handleErr func(string, int)) {
|
||||||
switch err.(type) {
|
switch err.(type) {
|
||||||
case nil:
|
case nil:
|
||||||
return
|
return
|
||||||
case *preflight.Error:
|
case preflightError:
|
||||||
handleErr(err.Error(), PreFlightExitCode)
|
handleErr(err.Error(), PreFlightExitCode)
|
||||||
case utilerrors.Aggregate:
|
case utilerrors.Aggregate:
|
||||||
handleErr(err.Error(), ValidationExitCode)
|
handleErr(err.Error(), ValidationExitCode)
|
||||||
|
@ -19,10 +19,12 @@ package util
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"k8s.io/kubernetes/cmd/kubeadm/app/preflight"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type pferror struct{}
|
||||||
|
|
||||||
|
func (p *pferror) Preflight() bool { return true }
|
||||||
|
func (p *pferror) Error() string { return "" }
|
||||||
func TestCheckErr(t *testing.T) {
|
func TestCheckErr(t *testing.T) {
|
||||||
var codeReturned int
|
var codeReturned int
|
||||||
errHandle := func(err string, code int) {
|
errHandle := func(err string, code int) {
|
||||||
@ -35,7 +37,7 @@ func TestCheckErr(t *testing.T) {
|
|||||||
}{
|
}{
|
||||||
{nil, 0},
|
{nil, 0},
|
||||||
{fmt.Errorf(""), DefaultErrorExitCode},
|
{fmt.Errorf(""), DefaultErrorExitCode},
|
||||||
{&preflight.Error{}, PreFlightExitCode},
|
{&pferror{}, PreFlightExitCode},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, rt := range tokenTest {
|
for _, rt := range tokenTest {
|
||||||
|
Loading…
Reference in New Issue
Block a user