Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New command: build to build images using minikube #11164

Merged
merged 28 commits into from
Apr 25, 2021
Merged
Show file tree
Hide file tree
Changes from 21 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
af5828d
Add initial re-implementation of the build command
afbjorklund Mar 7, 2021
a274482
Add some basic support for setting the image tag
afbjorklund Mar 8, 2021
9ef5d25
Add support for build directory and Dockerfile
afbjorklund Mar 8, 2021
6f26e4b
Generate documentation for the build command
afbjorklund Mar 8, 2021
6cfbf2c
Move minikube build cmd to minikube image build
afbjorklund Mar 10, 2021
844c696
Make sure to show output from build cmd
afbjorklund Mar 10, 2021
c94a6d7
Make sure to pass any file param to build
afbjorklund Mar 10, 2021
f670368
Move absolute file path handling to machine
afbjorklund Mar 10, 2021
22150fb
Don't add build context to the default file
afbjorklund Mar 10, 2021
a8a8788
Don't fail when the default file is passed
afbjorklund Mar 10, 2021
02b4267
Add option for pushing image to registry
afbjorklund Mar 10, 2021
40cbe65
Improve comments and remove docker daemon
afbjorklund Mar 12, 2021
9f9958f
Allow building image from an url as well
afbjorklund Mar 12, 2021
db203cd
Allow building tarball from stdin stream
afbjorklund Mar 12, 2021
c961190
Allow passing environ and options to build
afbjorklund Mar 12, 2021
947e31a
Add git url support to containerd runtime
afbjorklund Mar 12, 2021
e230f02
Address lint append suggestions from gocritic
afbjorklund Mar 12, 2021
9516122
Add example parameters to the usage help text
afbjorklund Mar 16, 2021
c715324
Regenerate documentation for the unit test
afbjorklund Mar 24, 2021
f7e0777
Clean up duplicated function etc after merge
afbjorklund Mar 25, 2021
943561b
Clean up the order of the image commands
afbjorklund Mar 25, 2021
a5f835b
Add skeleton for minikube image integration test
afbjorklund Apr 21, 2021
93c40d1
Make sure to start buildkit socket for containerd
afbjorklund Apr 22, 2021
c9a0a7a
Move the BuildImage test over to functional test
afbjorklund Apr 23, 2021
5d8d3d3
Use canonical names for the functional test images
afbjorklund Apr 23, 2021
d508459
url.Parse treats DOS volumes as URL schemes
afbjorklund Apr 23, 2021
96e199c
Support building a path local to the machine
afbjorklund Apr 23, 2021
4d0c0c6
The docker container runtime doesn't show name
afbjorklund Apr 23, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
83 changes: 77 additions & 6 deletions cmd/minikube/cmd/image.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ package cmd
import (
"io"
"io/ioutil"
"net/url"
"os"
"strings"

Expand All @@ -29,6 +30,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
Expand All @@ -38,9 +40,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) {
Expand Down Expand Up @@ -69,7 +76,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)
Expand Down Expand Up @@ -155,6 +162,63 @@ $ 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)
if err == nil && u.Scheme == "" && u.Host == "" {
// 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",
Expand All @@ -167,17 +231,24 @@ $ 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)
}
},
}

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 changes: 2 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is there a PR upstream that we could comment here to remove the replace after the PR gets merged ?

Copy link
Collaborator Author

@afbjorklund afbjorklund Apr 23, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had to backport a build fix, since the older Docker 19.03 doesn't build with the newer Windows versions.

See moby/moby@19.03...afbjorklund:v19.03.15-windows

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alternatively one could upgrade everything to the Docker 20.10 API, but that is much more intrusive and big

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The patch was: moby/moby@c3a0a37

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
Expand Down Expand Up @@ -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
Expand Down
8 changes: 4 additions & 4 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -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=
Expand Down Expand Up @@ -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=
Expand Down
105 changes: 105 additions & 0 deletions pkg/minikube/cruntime/containerd.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ import (
"encoding/base64"
"encoding/json"
"fmt"
"net/url"
"os"
"os/exec"
"path"
"strings"
Expand Down Expand Up @@ -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) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add unit test for this ? or maybe break down the logic of detecting remote and add unit tests, so we know exactly what user would input

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, but then we need to start a web server or something to host the files to build (the ones in testdata/build) ?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the detection part of the (the scheme detection) could be put in a separate func with unit test ?

Copy link
Collaborator Author

@afbjorklund afbjorklund Apr 23, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We only implement it for buildkit, both docker and podman have their own (internal) implementation...

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)
Expand Down
35 changes: 35 additions & 0 deletions pkg/minikube/cruntime/crio.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"encoding/json"
"fmt"
"net"
"os"
"os/exec"
"path"
"strings"
Expand Down Expand Up @@ -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")
Expand Down
2 changes: 2 additions & 0 deletions pkg/minikube/cruntime/cruntime.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
35 changes: 35 additions & 0 deletions pkg/minikube/cruntime/docker.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package cruntime

import (
"fmt"
"os"
"os/exec"
"path"
"strings"
Expand Down Expand Up @@ -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
Expand Down
Loading