Merge pull request #11164 from afbjorklund/build-rebase

Add re-implementation of the build command
pull/11185/head
Medya Ghazizadeh 2021-04-24 21:03:30 -07:00 committed by GitHub
commit 41bbb4f6c5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 732 additions and 27 deletions

View File

@ -19,7 +19,10 @@ package cmd
import (
"io"
"io/ioutil"
"net/url"
"os"
"path/filepath"
"runtime"
"strings"
"github.com/spf13/cobra"
@ -29,6 +32,7 @@ import (
"k8s.io/minikube/pkg/minikube/image"
"k8s.io/minikube/pkg/minikube/machine"
"k8s.io/minikube/pkg/minikube/reason"
docker "k8s.io/minikube/third_party/go-dockerclient"
)
// imageCmd represents the image command
@ -38,9 +42,14 @@ var imageCmd = &cobra.Command{
}
var (
pull bool
imgDaemon bool
imgRemote bool
pull bool
imgDaemon bool
imgRemote bool
tag string
push bool
dockerFile string
buildEnv []string
buildOpt []string
)
func saveFile(r io.Reader) (string, error) {
@ -69,7 +78,7 @@ var loadImageCmd = &cobra.Command{
if len(args) == 0 {
exit.Message(reason.Usage, "Please provide an image in your local daemon to load into minikube via <minikube image load IMAGE_NAME>")
}
// Cache and load images into docker daemon
// Cache and load images into container runtime
profile, err := config.LoadProfile(viper.GetString(config.ProfileName))
if err != nil {
exit.Error(reason.Usage, "loading profile", err)
@ -155,6 +164,67 @@ $ minikube image unload image busybox
},
}
func createTar(dir string) (string, error) {
tar, err := docker.CreateTarStream(dir, dockerFile)
if err != nil {
return "", err
}
return saveFile(tar)
}
// buildImageCmd represents the image build command
var buildImageCmd = &cobra.Command{
Use: "build PATH | URL | -",
Short: "Build a container image in minikube",
Long: "Build a container image, using the container runtime.",
Example: `minikube image build .`,
Run: func(cmd *cobra.Command, args []string) {
if len(args) < 1 {
exit.Message(reason.Usage, "Please provide a path or url to build")
}
// Build images into container runtime
profile, err := config.LoadProfile(viper.GetString(config.ProfileName))
if err != nil {
exit.Error(reason.Usage, "loading profile", err)
}
img := args[0]
var tmp string
if img == "-" {
tmp, err = saveFile(os.Stdin)
if err != nil {
exit.Error(reason.GuestImageBuild, "Failed to save stdin", err)
}
img = tmp
} else {
// If it is an URL, pass it as-is
u, err := url.Parse(img)
local := err == nil && u.Scheme == "" && u.Host == ""
if runtime.GOOS == "windows" && filepath.VolumeName(img) != "" {
local = true
}
if local {
// If it's a directory, tar it
info, err := os.Stat(img)
if err == nil && info.IsDir() {
tmp, err := createTar(img)
if err != nil {
exit.Error(reason.GuestImageBuild, "Failed to save dir", err)
}
img = tmp
}
// Otherwise, assume it's a tar
}
}
if err := machine.BuildImage(img, dockerFile, tag, push, buildEnv, buildOpt, []*config.Profile{profile}); err != nil {
exit.Error(reason.GuestImageBuild, "Failed to build image", err)
}
if tmp != "" {
os.Remove(tmp)
}
},
}
var listImageCmd = &cobra.Command{
Use: "list",
Short: "List images",
@ -167,6 +237,7 @@ $ minikube image list
if err != nil {
exit.Error(reason.Usage, "loading profile", err)
}
if err := machine.ListImages(profile); err != nil {
exit.Error(reason.GuestImageList, "Failed to list images", err)
}
@ -174,10 +245,16 @@ $ minikube image list
}
func init() {
imageCmd.AddCommand(loadImageCmd)
imageCmd.AddCommand(removeImageCmd)
loadImageCmd.Flags().BoolVarP(&pull, "pull", "", false, "Pull the remote image (no caching)")
loadImageCmd.Flags().BoolVar(&imgDaemon, "daemon", false, "Cache image from docker daemon")
loadImageCmd.Flags().BoolVar(&imgRemote, "remote", false, "Cache image from remote registry")
imageCmd.AddCommand(loadImageCmd)
imageCmd.AddCommand(removeImageCmd)
buildImageCmd.Flags().StringVarP(&tag, "tag", "t", "", "Tag to apply to the new image (optional)")
buildImageCmd.Flags().BoolVarP(&push, "push", "", false, "Push the new image (requires tag)")
buildImageCmd.Flags().StringVarP(&dockerFile, "file", "f", "", "Path to the Dockerfile to use (optional)")
buildImageCmd.Flags().StringArrayVar(&buildEnv, "build-env", nil, "Environment variables to pass to the build. (format: key=value)")
buildImageCmd.Flags().StringArrayVar(&buildOpt, "build-opt", nil, "Specify arbitrary flags to pass to the build. (format: key=value)")
imageCmd.AddCommand(buildImageCmd)
imageCmd.AddCommand(listImageCmd)
}

3
go.mod
View File

@ -21,7 +21,7 @@ require (
github.com/cloudfoundry-attic/jibber_jabber v0.0.0-20151120183258-bcc4c8345a21
github.com/cloudfoundry/jibber_jabber v0.0.0-20151120183258-bcc4c8345a21 // indirect
github.com/docker/cli v0.0.0-20200303162255-7d407207c304 // indirect
github.com/docker/docker v17.12.0-ce-rc1.0.20200916142827-bd33bbf0497b+incompatible
github.com/docker/docker v17.12.0-ce-rc1.0.20210128214336-420b1d36250f+incompatible
github.com/docker/go-units v0.4.0
github.com/docker/machine v0.16.2
github.com/elazarl/goproxy v0.0.0-20190421051319-9d40249d3c2f
@ -102,6 +102,7 @@ require (
replace (
git.apache.org/thrift.git => github.com/apache/thrift v0.0.0-20180902110319-2566ecd5d999
github.com/briandowns/spinner => github.com/alonyb/spinner v1.12.7
github.com/docker/docker => github.com/afbjorklund/moby v0.0.0-20210308214533-2fa72faf0e8b
github.com/docker/machine => github.com/machine-drivers/machine v0.7.1-0.20210306082426-fcb2ad5bcb17
github.com/google/go-containerregistry => github.com/afbjorklund/go-containerregistry v0.4.1-0.20210321165649-761f6f9626b1
github.com/samalba/dockerclient => github.com/sayboras/dockerclient v1.0.0

8
go.sum
View File

@ -109,6 +109,8 @@ github.com/VividCortex/godaemon v0.0.0-20201030160542-15e3f4925a21 h1:Pgxfz/g+Xy
github.com/VividCortex/godaemon v0.0.0-20201030160542-15e3f4925a21/go.mod h1:Y8CJ3IwPIAkMhv/rRUWIlczaeqd9ty9yrl+nc2AbaL4=
github.com/afbjorklund/go-containerregistry v0.4.1-0.20210321165649-761f6f9626b1 h1:AI8EIk8occ3pruhaTpkaQxQGlC1dHx3J9hAtg7t+FLI=
github.com/afbjorklund/go-containerregistry v0.4.1-0.20210321165649-761f6f9626b1/go.mod h1:Ct15B4yir3PLOP5jsy0GNeYVaIZs/MK/Jz5any1wFW0=
github.com/afbjorklund/moby v0.0.0-20210308214533-2fa72faf0e8b h1:wmyy8gOOzYzMD6SfMs44yCPoOWAAHcjxCio/zQjOlDU=
github.com/afbjorklund/moby v0.0.0-20210308214533-2fa72faf0e8b/go.mod h1:qXUBi22bjTfxOV8XyOI/W1PklPSinepyWoJ6eYSLwwo=
github.com/agnivade/levenshtein v1.0.1/go.mod h1:CURSv5d9Uaml+FovSIICkLbAUZ9S4RqaHDIsdSBg7lM=
github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
@ -292,10 +294,8 @@ github.com/docker/distribution v0.0.0-20190905152932-14b96e55d84c/go.mod h1:0+TT
github.com/docker/distribution v2.7.1-0.20190205005809-0d3efadf0154+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
github.com/docker/distribution v2.7.1+incompatible h1:a5mlkVzth6W5A4fOsS3D2EO5BUmsJpcB+cRlLU7cSug=
github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
github.com/docker/docker v1.4.2-0.20190924003213-a8608b5b67c7/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/docker v17.12.0-ce-rc1.0.20181225093023-5ddb1d410a8b+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/docker v17.12.0-ce-rc1.0.20200916142827-bd33bbf0497b+incompatible h1:SiUATuP//KecDjpOK2tvZJgeScYAklvyjfK8JZlU6fo=
github.com/docker/docker v17.12.0-ce-rc1.0.20200916142827-bd33bbf0497b+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/docker v17.12.0-ce-rc1.0.20210128214336-420b1d36250f+incompatible h1:nhVo1udYfMj0Jsw0lnqrTjjf33aLpdgW9Wve9fHVzhQ=
github.com/docker/docker v17.12.0-ce-rc1.0.20210128214336-420b1d36250f+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/docker-credential-helpers v0.6.3 h1:zI2p9+1NQYdnG6sMU26EX4aVGlqbInSQxQXLvzJ4RPQ=
github.com/docker/docker-credential-helpers v0.6.3/go.mod h1:WRaJzqw3CTB9bk10avuGsjVBZsD05qeibJ1/TYlvc0Y=
github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ=

View File

@ -21,6 +21,8 @@ import (
"encoding/base64"
"encoding/json"
"fmt"
"net/url"
"os"
"os/exec"
"path"
"strings"
@ -277,6 +279,109 @@ func (r *Containerd) RemoveImage(name string) error {
return removeCRIImage(r.Runner, name)
}
func gitClone(cr CommandRunner, src string) (string, error) {
// clone to a temporary directory
rr, err := cr.RunCmd(exec.Command("mktemp", "-d"))
if err != nil {
return "", err
}
tmp := strings.TrimSpace(rr.Stdout.String())
cmd := exec.Command("git", "clone", src, tmp)
if _, err := cr.RunCmd(cmd); err != nil {
return "", err
}
return tmp, nil
}
func downloadRemote(cr CommandRunner, src string) (string, error) {
u, err := url.Parse(src)
if err != nil {
return "", err
}
if u.Scheme == "" && u.Host == "" { // regular file, return
return src, nil
}
if u.Scheme == "git" {
return gitClone(cr, src)
}
// download to a temporary file
rr, err := cr.RunCmd(exec.Command("mktemp"))
if err != nil {
return "", err
}
dst := strings.TrimSpace(rr.Stdout.String())
cmd := exec.Command("curl", "-L", "-o", dst, src)
if _, err := cr.RunCmd(cmd); err != nil {
return "", err
}
// extract to a temporary directory
rr, err = cr.RunCmd(exec.Command("mktemp", "-d"))
if err != nil {
return "", err
}
tmp := strings.TrimSpace(rr.Stdout.String())
cmd = exec.Command("tar", "-C", tmp, "-xf", dst)
if _, err := cr.RunCmd(cmd); err != nil {
return "", err
}
return tmp, nil
}
// BuildImage builds an image into this runtime
func (r *Containerd) BuildImage(src string, file string, tag string, push bool, env []string, opts []string) error {
// download url if not already present
dir, err := downloadRemote(r.Runner, src)
if err != nil {
return err
}
if file != "" {
if dir != src {
file = path.Join(dir, file)
}
// copy to standard path for Dockerfile
df := path.Join(dir, "Dockerfile")
if file != df {
cmd := exec.Command("sudo", "cp", "-f", file, df)
if _, err := r.Runner.RunCmd(cmd); err != nil {
return err
}
}
}
klog.Infof("Building image: %s", dir)
extra := ""
if tag != "" {
// add default tag if missing
if !strings.Contains(tag, ":") {
tag += ":latest"
}
extra = fmt.Sprintf(",name=%s", tag)
if push {
extra += ",push=true"
}
}
args := []string{"buildctl", "build",
"--frontend", "dockerfile.v0",
"--local", fmt.Sprintf("context=%s", dir),
"--local", fmt.Sprintf("dockerfile=%s", dir),
"--output", fmt.Sprintf("type=image%s", extra)}
for _, opt := range opts {
args = append(args, "--"+opt)
}
c := exec.Command("sudo", args...)
e := os.Environ()
e = append(e, env...)
c.Env = e
c.Stdout = os.Stdout
c.Stderr = os.Stderr
if _, err := r.Runner.RunCmd(c); err != nil {
return errors.Wrap(err, "buildctl build.")
}
return nil
}
// CGroupDriver returns cgroup driver ("cgroupfs" or "systemd")
func (r *Containerd) CGroupDriver() (string, error) {
info, err := getCRIInfo(r.Runner)

View File

@ -20,6 +20,7 @@ import (
"encoding/json"
"fmt"
"net"
"os"
"os/exec"
"path"
"strings"
@ -197,6 +198,40 @@ func (r *CRIO) RemoveImage(name string) error {
return removeCRIImage(r.Runner, name)
}
// BuildImage builds an image into this runtime
func (r *CRIO) BuildImage(src string, file string, tag string, push bool, env []string, opts []string) error {
klog.Infof("Building image: %s", src)
args := []string{"podman", "build"}
if file != "" {
args = append(args, "-f", file)
}
if tag != "" {
args = append(args, "-t", tag)
}
args = append(args, src)
for _, opt := range opts {
args = append(args, "--"+opt)
}
c := exec.Command("sudo", args...)
e := os.Environ()
e = append(e, env...)
c.Env = e
c.Stdout = os.Stdout
c.Stderr = os.Stderr
if _, err := r.Runner.RunCmd(c); err != nil {
return errors.Wrap(err, "crio build image")
}
if tag != "" && push {
c := exec.Command("sudo", "podman", "push", tag)
c.Stdout = os.Stdout
c.Stderr = os.Stderr
if _, err := r.Runner.RunCmd(c); err != nil {
return errors.Wrap(err, "crio push image")
}
}
return nil
}
// CGroupDriver returns cgroup driver ("cgroupfs" or "systemd")
func (r *CRIO) CGroupDriver() (string, error) {
c := exec.Command("crio", "config")

View File

@ -97,6 +97,8 @@ type Manager interface {
LoadImage(string) error
// Pull an image to the runtime from the container registry
PullImage(string) error
// Build an image idempotently into the runtime on a host
BuildImage(string, string, string, bool, []string, []string) error
// ImageExists takes image name and image sha checks if an it exists
ImageExists(string, string) bool

View File

@ -18,6 +18,7 @@ package cruntime
import (
"fmt"
"os"
"os/exec"
"path"
"strings"
@ -217,6 +218,40 @@ func (r *Docker) RemoveImage(name string) error {
return nil
}
// BuildImage builds an image into this runtime
func (r *Docker) BuildImage(src string, file string, tag string, push bool, env []string, opts []string) error {
klog.Infof("Building image: %s", src)
args := []string{"build"}
if file != "" {
args = append(args, "-f", file)
}
if tag != "" {
args = append(args, "-t", tag)
}
args = append(args, src)
for _, opt := range opts {
args = append(args, "--"+opt)
}
c := exec.Command("docker", args...)
e := os.Environ()
e = append(e, env...)
c.Env = e
c.Stdout = os.Stdout
c.Stderr = os.Stderr
if _, err := r.Runner.RunCmd(c); err != nil {
return errors.Wrap(err, "buildimage docker.")
}
if tag != "" && push {
c := exec.Command("docker", "push", tag)
c.Stdout = os.Stdout
c.Stderr = os.Stderr
if _, err := r.Runner.RunCmd(c); err != nil {
return errors.Wrap(err, "pushimage docker.")
}
}
return nil
}
// CGroupDriver returns cgroup driver ("cgroupfs" or "systemd")
func (r *Docker) CGroupDriver() (string, error) {
// Note: the server daemon has to be running, for this call to return successfully

View File

@ -0,0 +1,189 @@
/*
Copyright 2021 The Kubernetes Authors All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
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 machine
import (
"net/url"
"os"
"os/exec"
"path"
"path/filepath"
"runtime"
"strings"
"github.com/docker/machine/libmachine/state"
"github.com/pkg/errors"
"k8s.io/klog/v2"
"k8s.io/minikube/pkg/minikube/assets"
"k8s.io/minikube/pkg/minikube/command"
"k8s.io/minikube/pkg/minikube/config"
"k8s.io/minikube/pkg/minikube/cruntime"
"k8s.io/minikube/pkg/minikube/localpath"
"k8s.io/minikube/pkg/minikube/vmpath"
)
// buildRoot is where images should be built from within the guest VM
var buildRoot = path.Join(vmpath.GuestPersistentDir, "build")
// BuildImage builds image to all profiles
func BuildImage(path string, file string, tag string, push bool, env []string, opt []string, profiles []*config.Profile) error {
api, err := NewAPIClient()
if err != nil {
return errors.Wrap(err, "api")
}
defer api.Close()
succeeded := []string{}
failed := []string{}
u, err := url.Parse(path)
if err == nil && u.Scheme == "file" {
path = u.Path
}
remote := err == nil && u.Scheme != ""
if runtime.GOOS == "windows" && filepath.VolumeName(path) != "" {
remote = false
}
for _, p := range profiles { // building images to all running profiles
pName := p.Name // capture the loop variable
c, err := config.Load(pName)
if err != nil {
// Non-fatal because it may race with profile deletion
klog.Errorf("Failed to load profile %q: %v", pName, err)
failed = append(failed, pName)
continue
}
for _, n := range c.Nodes {
m := config.MachineName(*c, n)
status, err := Status(api, m)
if err != nil {
klog.Warningf("error getting status for %s: %v", m, err)
failed = append(failed, m)
continue
}
if status == state.Running.String() {
h, err := api.Load(m)
if err != nil {
klog.Warningf("Failed to load machine %q: %v", m, err)
failed = append(failed, m)
continue
}
cr, err := CommandRunner(h)
if err != nil {
return err
}
if remote {
err = buildImage(cr, c.KubernetesConfig, path, file, tag, push, env, opt)
} else {
err = transferAndBuildImage(cr, c.KubernetesConfig, path, file, tag, push, env, opt)
}
if err != nil {
failed = append(failed, m)
klog.Warningf("Failed to build image for profile %s. make sure the profile is running. %v", pName, err)
continue
}
succeeded = append(succeeded, m)
}
}
}
klog.Infof("succeeded building to: %s", strings.Join(succeeded, " "))
klog.Infof("failed building to: %s", strings.Join(failed, " "))
return nil
}
// buildImage builds a single image
func buildImage(cr command.Runner, k8s config.KubernetesConfig, src string, file string, tag string, push bool, env []string, opt []string) error {
r, err := cruntime.New(cruntime.Config{Type: k8s.ContainerRuntime, Runner: cr})
if err != nil {
return errors.Wrap(err, "runtime")
}
klog.Infof("Building image from url: %s", src)
err = r.BuildImage(src, file, tag, push, env, opt)
if err != nil {
return errors.Wrapf(err, "%s build %s", r.Name(), src)
}
klog.Infof("Built %s from %s", tag, src)
return nil
}
// transferAndBuildImage transfers and builds a single image
func transferAndBuildImage(cr command.Runner, k8s config.KubernetesConfig, src string, file string, tag string, push bool, env []string, opt []string) error {
r, err := cruntime.New(cruntime.Config{Type: k8s.ContainerRuntime, Runner: cr})
if err != nil {
return errors.Wrap(err, "runtime")
}
klog.Infof("Building image from path: %s", src)
filename := filepath.Base(src)
filename = localpath.SanitizeCacheDir(filename)
if _, err := os.Stat(src); err != nil {
return err
}
args := append([]string{"mkdir", "-p"}, buildRoot)
if _, err := cr.RunCmd(exec.Command("sudo", args...)); err != nil {
return err
}
dst := path.Join(buildRoot, filename)
f, err := assets.NewFileAsset(src, buildRoot, filename, "0644")
if err != nil {
return errors.Wrapf(err, "creating copyable file asset: %s", filename)
}
if err := cr.Copy(f); err != nil {
return errors.Wrap(err, "transferring cached image")
}
context := path.Join(buildRoot, ".", strings.TrimSuffix(filename, filepath.Ext(filename)))
args = append([]string{"mkdir", "-p"}, context)
if _, err := cr.RunCmd(exec.Command("sudo", args...)); err != nil {
return err
}
args = append([]string{"tar", "-C", context, "-xf"}, dst)
if _, err := cr.RunCmd(exec.Command("sudo", args...)); err != nil {
return err
}
if file != "" && !path.IsAbs(file) {
file = path.Join(context, file)
}
err = r.BuildImage(context, file, tag, push, env, opt)
if err != nil {
return errors.Wrapf(err, "%s build %s", r.Name(), dst)
}
args = append([]string{"rm", "-rf"}, context)
if _, err := cr.RunCmd(exec.Command("sudo", args...)); err != nil {
return err
}
args = append([]string{"rm", "-f"}, dst)
if _, err := cr.RunCmd(exec.Command("sudo", args...)); err != nil {
return err
}
klog.Infof("Built %s from %s", tag, src)
return nil
}

View File

@ -250,6 +250,7 @@ var (
GuestImageList = Kind{ID: "GUEST_IMAGE_LIST", ExitCode: ExGuestError}
GuestImageLoad = Kind{ID: "GUEST_IMAGE_LOAD", ExitCode: ExGuestError}
GuestImageRemove = Kind{ID: "GUEST_IMAGE_REMOVE", ExitCode: ExGuestError}
GuestImageBuild = Kind{ID: "GUEST_IMAGE_BUILD", ExitCode: ExGuestError}
GuestLoadHost = Kind{ID: "GUEST_LOAD_HOST", ExitCode: ExGuestError}
GuestMount = Kind{ID: "GUEST_MOUNT", ExitCode: ExGuestError}
GuestMountConflict = Kind{ID: "GUEST_MOUNT_CONFLICT", ExitCode: ExGuestConflict}

View File

@ -35,6 +35,56 @@ Manage images
--vmodule moduleSpec comma-separated list of pattern=N settings for file-filtered logging
```
## minikube image build
Build a container image in minikube
### Synopsis
Build a container image, using the container runtime.
```shell
minikube image build PATH | URL | - [flags]
```
### Examples
```
minikube image build .
```
### Options
```
--build-env stringArray Environment variables to pass to the build. (format: key=value)
--build-opt stringArray Specify arbitrary flags to pass to the build. (format: key=value)
-f, --file string Path to the Dockerfile to use (optional)
--push Push the new image (requires tag)
-t, --tag string Tag to apply to the new image (optional)
```
### Options inherited from parent commands
```
--add_dir_header If true, adds the file directory to the header of the log messages
--alsologtostderr log to standard error as well as files
-b, --bootstrapper string The name of the cluster bootstrapper that will set up the Kubernetes cluster. (default "kubeadm")
-h, --help
--log_backtrace_at traceLocation when logging hits line file:N, emit a stack trace (default :0)
--log_dir string If non-empty, write log files in this directory
--log_file string If non-empty, use this log file
--log_file_max_size uint Defines the maximum size a log file can grow to. Unit is megabytes. If the value is 0, the maximum file size is unlimited. (default 1800)
--logtostderr log to standard error instead of files
--one_output If true, only write logs to their native severity level (vs also writing to each lower severity level)
-p, --profile string The name of the minikube VM being used. This can be set to allow having multiple instances of minikube independently. (default "minikube")
--skip_headers If true, avoid header prefixes in the log messages
--skip_log_headers If true, avoid headers when opening log files
--stderrthreshold severity logs at or above this threshold go to stderr (default 2)
--user string Specifies the user executing the operation. Useful for auditing operations executed by 3rd party tools. Defaults to the operating system username.
-v, --v Level number for the log level verbosity
--vmodule moduleSpec comma-separated list of pattern=N settings for file-filtered logging
```
## minikube image help
Help about any command

View File

@ -134,6 +134,7 @@ func TestFunctional(t *testing.T) {
{"NodeLabels", validateNodeLabels},
{"LoadImage", validateLoadImage},
{"RemoveImage", validateRemoveImage},
{"BuildImage", validateBuildImage},
}
for _, tc := range tests {
tc := tc
@ -156,12 +157,19 @@ func cleanupUnwantedImages(ctx context.Context, t *testing.T, profile string) {
t.Skipf("docker is not installed, cannot delete docker images")
} else {
t.Run("delete busybox image", func(t *testing.T) {
newImage := fmt.Sprintf("busybox:%s", profile)
newImage := fmt.Sprintf("docker.io/library/busybox:%s", profile)
rr, err := Run(t, exec.CommandContext(ctx, "docker", "rmi", "-f", newImage))
if err != nil {
t.Logf("failed to remove image busybox from docker images. args %q: %v", rr.Command(), err)
}
})
t.Run("delete my-image image", func(t *testing.T) {
newImage := fmt.Sprintf("localhost/my-image:%s", profile)
rr, err := Run(t, exec.CommandContext(ctx, "docker", "rmi", "-f", newImage))
if err != nil {
t.Logf("failed to remove image my-image from docker images. args %q: %v", rr.Command(), err)
}
})
t.Run("delete minikube cached images", func(t *testing.T) {
img := "minikube-local-cache-test:" + profile
@ -207,7 +215,7 @@ func validateLoadImage(ctx context.Context, t *testing.T, profile string) {
}
// tag busybox
newImage := fmt.Sprintf("busybox:%s", profile)
newImage := fmt.Sprintf("docker.io/library/busybox:%s", profile)
rr, err = Run(t, exec.CommandContext(ctx, "docker", "tag", busybox, newImage))
if err != nil {
t.Fatalf("failed to setup test (tag image) : %v\n%s", err, rr.Output())
@ -224,7 +232,7 @@ func validateLoadImage(ctx context.Context, t *testing.T, profile string) {
if err != nil {
t.Fatalf("listing images: %v\n%s", err, rr.Output())
}
if !strings.Contains(rr.Output(), newImage) {
if !strings.Contains(rr.Output(), fmt.Sprintf("busybox:%s", profile)) {
t.Fatalf("expected %s to be loaded into minikube but the image is not there", newImage)
}
@ -259,13 +267,7 @@ func validateRemoveImage(ctx context.Context, t *testing.T, profile string) {
t.Fatalf("removing image from minikube: %v\n%s", err, rr.Output())
}
// make sure the image was removed
var cmd *exec.Cmd
if ContainerRuntime() == "docker" {
cmd = exec.CommandContext(ctx, Target(), "ssh", "-p", profile, "--", "docker", "images")
} else {
cmd = exec.CommandContext(ctx, Target(), "ssh", "-p", profile, "--", "sudo", "crictl", "images")
}
rr, err = Run(t, cmd)
rr, err = listImages(ctx, t, profile)
if err != nil {
t.Fatalf("listing images: %v\n%s", err, rr.Output())
}
@ -279,13 +281,8 @@ func inspectImage(ctx context.Context, t *testing.T, profile string, image strin
var cmd *exec.Cmd
if ContainerRuntime() == "docker" {
cmd = exec.CommandContext(ctx, Target(), "ssh", "-p", profile, "--", "docker", "image", "inspect", image)
} else if ContainerRuntime() == "containerd" {
// crictl inspecti busybox:test-example
cmd = exec.CommandContext(ctx, Target(), "ssh", "-p", profile, "--", "sudo", "crictl", "inspecti", image)
} else {
// crio adds localhost prefix
// crictl inspecti localhost/busybox:test-example
cmd = exec.CommandContext(ctx, Target(), "ssh", "-p", profile, "--", "sudo", "crictl", "inspecti", "localhost/"+image)
cmd = exec.CommandContext(ctx, Target(), "ssh", "-p", profile, "--", "sudo", "crictl", "inspecti", image)
}
rr, err := Run(t, cmd)
if err != nil {
@ -294,6 +291,70 @@ func inspectImage(ctx context.Context, t *testing.T, profile string, image strin
return rr, nil
}
func listImages(ctx context.Context, t *testing.T, profile string) (*RunResult, error) {
var cmd *exec.Cmd
if ContainerRuntime() == "docker" {
cmd = exec.CommandContext(ctx, Target(), "ssh", "-p", profile, "--", "docker", "images")
} else {
cmd = exec.CommandContext(ctx, Target(), "ssh", "-p", profile, "--", "sudo", "crictl", "images")
}
rr, err := Run(t, cmd)
if err != nil {
return rr, err
}
return rr, nil
}
// validateBuildImage makes sures that `minikube image build` works as expected
func validateBuildImage(ctx context.Context, t *testing.T, profile string) {
if NoneDriver() {
t.Skip("load image not available on none driver")
}
if GithubActionRunner() && runtime.GOOS == "darwin" {
t.Skip("skipping on github actions and darwin, as this test requires a running docker daemon")
}
defer PostMortemLogs(t, profile)
newImage := fmt.Sprintf("localhost/my-image:%s", profile)
if ContainerRuntime() == "containerd" {
startBuildkit(ctx, t, profile)
// unix:///run/buildkit/buildkitd.sock
}
// try to build the new image with minikube
rr, err := Run(t, exec.CommandContext(ctx, Target(), "-p", profile, "image", "build", "-t", newImage, filepath.Join(*testdataDir, "build")))
if err != nil {
t.Fatalf("building image with minikube: %v\n%s", err, rr.Output())
}
if rr.Stdout.Len() > 0 {
t.Logf("(dbg) Stdout: %s:\n%s", rr.Command(), rr.Stdout)
}
if rr.Stderr.Len() > 0 {
t.Logf("(dbg) Stderr: %s:\n%s", rr.Command(), rr.Stderr)
}
// make sure the image was correctly built
rr, err = inspectImage(ctx, t, profile, newImage)
if err != nil {
ll, _ := listImages(ctx, t, profile)
t.Logf("(dbg) images: %s", ll.Output())
t.Fatalf("listing images: %v\n%s", err, rr.Output())
}
if !strings.Contains(rr.Output(), newImage) {
t.Fatalf("expected %s to be built with minikube but the image is not there", newImage)
}
}
func startBuildkit(ctx context.Context, t *testing.T, profile string) {
// sudo systemctl start buildkit.socket
cmd := exec.CommandContext(ctx, Target(), "ssh", "-p", profile, "--", "nohup",
"sudo", "-b", "buildkitd", "--oci-worker=false",
"--containerd-worker=true", "--containerd-worker-namespace=k8s.io")
if rr, err := Run(t, cmd); err != nil {
t.Fatalf("%s failed: %v", rr.Command(), err)
}
}
// check functionality of minikube after evaling docker-env
// TODO: Add validatePodmanEnv for crio runtime: #10231
func validateDockerEnv(ctx context.Context, t *testing.T, profile string) {

View File

@ -0,0 +1,3 @@
FROM busybox
RUN true
ADD content.txt /

View File

@ -0,0 +1 @@
Content for image build

23
third_party/go-dockerclient/LICENSE vendored Normal file
View File

@ -0,0 +1,23 @@
Copyright (c) 2013-2021, go-dockerclient authors
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

122
third_party/go-dockerclient/tar.go vendored Normal file
View File

@ -0,0 +1,122 @@
// Copyright 2014 go-dockerclient authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package docker
import (
"fmt"
"io"
"io/ioutil"
"os"
"path"
"path/filepath"
"strings"
"github.com/docker/docker/pkg/archive"
"github.com/docker/docker/pkg/fileutils"
)
func CreateTarStream(srcPath, dockerfilePath string) (io.ReadCloser, error) {
srcPath, err := filepath.Abs(srcPath)
if err != nil {
return nil, err
}
excludes, err := parseDockerignore(srcPath)
if err != nil {
return nil, err
}
includes := []string{"."}
// If .dockerignore mentions .dockerignore or the Dockerfile
// then make sure we send both files over to the daemon
// because Dockerfile is, obviously, needed no matter what, and
// .dockerignore is needed to know if either one needs to be
// removed. The deamon will remove them for us, if needed, after it
// parses the Dockerfile.
//
// https://github.com/docker/docker/issues/8330
//
forceIncludeFiles := []string{".dockerignore", dockerfilePath}
for _, includeFile := range forceIncludeFiles {
if includeFile == "" {
continue
}
keepThem, err := fileutils.Matches(includeFile, excludes)
if err != nil {
return nil, fmt.Errorf("cannot match .dockerfileignore: '%s', error: %w", includeFile, err)
}
if keepThem {
includes = append(includes, includeFile)
}
}
if err := validateContextDirectory(srcPath, excludes); err != nil {
return nil, err
}
tarOpts := &archive.TarOptions{
ExcludePatterns: excludes,
IncludeFiles: includes,
Compression: archive.Uncompressed,
NoLchown: true,
}
return archive.TarWithOptions(srcPath, tarOpts)
}
// validateContextDirectory checks if all the contents of the directory
// can be read and returns an error if some files can't be read.
// Symlinks which point to non-existing files don't trigger an error
func validateContextDirectory(srcPath string, excludes []string) error {
return filepath.Walk(filepath.Join(srcPath, "."), func(filePath string, f os.FileInfo, err error) error {
// skip this directory/file if it's not in the path, it won't get added to the context
if relFilePath, relErr := filepath.Rel(srcPath, filePath); relErr != nil {
return relErr
} else if skip, matchErr := fileutils.Matches(relFilePath, excludes); matchErr != nil {
return matchErr
} else if skip {
if f.IsDir() {
return filepath.SkipDir
}
return nil
}
if err != nil {
if os.IsPermission(err) {
return fmt.Errorf("cannot stat %q: %w", filePath, err)
}
if os.IsNotExist(err) {
return nil
}
return err
}
// skip checking if symlinks point to non-existing files, such symlinks can be useful
// also skip named pipes, because they hanging on open
if f.Mode()&(os.ModeSymlink|os.ModeNamedPipe) != 0 {
return nil
}
if !f.IsDir() {
currentFile, err := os.Open(filePath)
if err != nil {
return fmt.Errorf("cannot open %q for reading: %w", filePath, err)
}
currentFile.Close()
}
return nil
})
}
func parseDockerignore(root string) ([]string, error) {
var excludes []string
ignore, err := ioutil.ReadFile(path.Join(root, ".dockerignore"))
if err != nil && !os.IsNotExist(err) {
return excludes, fmt.Errorf("error reading .dockerignore: %w", err)
}
excludes = strings.Split(string(ignore), "\n")
return excludes, nil
}