mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-23 03:41:45 +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)
|
||||
internalcfg, err := configutil.ConfigFileAndDefaultsToInternalConfig(cfgPath, cfg)
|
||||
kubeadmutil.CheckErr(err)
|
||||
puller, err := images.NewImagePuller(utilsexec.New(), internalcfg.GetCRISocket())
|
||||
puller, err := images.NewCRInterfacer(utilsexec.New(), internalcfg.GetCRISocket())
|
||||
kubeadmutil.CheckErr(err)
|
||||
imagesPull := NewImagesPull(puller, images.GetAllImages(internalcfg))
|
||||
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 {
|
||||
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
|
||||
}
|
||||
|
@ -10,12 +10,12 @@ go_library(
|
||||
name = "go_default_library",
|
||||
srcs = [
|
||||
"images.go",
|
||||
"puller.go",
|
||||
"interface.go",
|
||||
],
|
||||
importpath = "k8s.io/kubernetes/cmd/kubeadm/app/images",
|
||||
deps = [
|
||||
"//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/features:go_default_library",
|
||||
"//cmd/kubeadm/app/phases/addons/dns:go_default_library",
|
||||
@ -46,10 +46,10 @@ filegroup(
|
||||
|
||||
go_test(
|
||||
name = "go_default_xtest",
|
||||
srcs = ["puller_test.go"],
|
||||
srcs = ["interface_test.go"],
|
||||
deps = [
|
||||
":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",
|
||||
],
|
||||
)
|
||||
|
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/v1alpha1: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/registry/core/service/ipallocator:go_default_library",
|
||||
"//pkg/util/initsystem:go_default_library",
|
||||
|
@ -46,6 +46,7 @@ import (
|
||||
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
|
||||
kubeadmdefaults "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1alpha1"
|
||||
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/registry/core/service/ipallocator"
|
||||
"k8s.io/kubernetes/pkg/util/initsystem"
|
||||
@ -76,10 +77,16 @@ type Error struct {
|
||||
Msg string
|
||||
}
|
||||
|
||||
// Error implements the standard error interface
|
||||
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=...`")
|
||||
}
|
||||
|
||||
// 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
|
||||
// successful as often as possible.
|
||||
type Checker interface {
|
||||
@ -850,6 +857,30 @@ func (ResolveCheck) Check() (warnings, errors []error) {
|
||||
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.
|
||||
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
|
||||
@ -1012,6 +1043,19 @@ func RunRootCheckOnly(ignorePreflightErrors sets.String) error {
|
||||
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
|
||||
// are processed will exit if any errors occurred.
|
||||
func RunChecks(checks []Checker, ww io.Writer, ignorePreflightErrors sets.String) error {
|
||||
|
@ -18,6 +18,7 @@ package preflight
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"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",
|
||||
deps = [
|
||||
"//cmd/kubeadm/app/apis/kubeadm:go_default_library",
|
||||
"//cmd/kubeadm/app/preflight: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/schema:go_default_library",
|
||||
@ -47,7 +46,6 @@ go_test(
|
||||
"//cmd/kubeadm/app/apis/kubeadm:go_default_library",
|
||||
"//cmd/kubeadm/app/apis/kubeadm/scheme: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/apimachinery/pkg/apis/meta/v1:go_default_library",
|
||||
],
|
||||
|
@ -22,7 +22,6 @@ import (
|
||||
"strings"
|
||||
|
||||
utilerrors "k8s.io/apimachinery/pkg/util/errors"
|
||||
"k8s.io/kubernetes/cmd/kubeadm/app/preflight"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -60,13 +59,19 @@ func CheckErr(err error) {
|
||||
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
|
||||
// func with that string and an kubectl exit code.
|
||||
func checkErr(prefix string, err error, handleErr func(string, int)) {
|
||||
switch err.(type) {
|
||||
case nil:
|
||||
return
|
||||
case *preflight.Error:
|
||||
case preflightError:
|
||||
handleErr(err.Error(), PreFlightExitCode)
|
||||
case utilerrors.Aggregate:
|
||||
handleErr(err.Error(), ValidationExitCode)
|
||||
|
@ -19,10 +19,12 @@ package util
|
||||
import (
|
||||
"fmt"
|
||||
"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) {
|
||||
var codeReturned int
|
||||
errHandle := func(err string, code int) {
|
||||
@ -35,7 +37,7 @@ func TestCheckErr(t *testing.T) {
|
||||
}{
|
||||
{nil, 0},
|
||||
{fmt.Errorf(""), DefaultErrorExitCode},
|
||||
{&preflight.Error{}, PreFlightExitCode},
|
||||
{&pferror{}, PreFlightExitCode},
|
||||
}
|
||||
|
||||
for _, rt := range tokenTest {
|
||||
|
Loading…
Reference in New Issue
Block a user