Merge pull request #11164 from afbjorklund/build-rebase
Add re-implementation of the build commandpull/11185/head
commit
41bbb4f6c5
|
@ -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
3
go.mod
|
@ -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
8
go.sum
|
@ -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=
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
FROM busybox
|
||||
RUN true
|
||||
ADD content.txt /
|
|
@ -0,0 +1 @@
|
|||
Content for image build
|
|
@ -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.
|
|
@ -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
|
||||
}
|
Loading…
Reference in New Issue