mirror of
https://github.com/containers/podman.git
synced 2025-07-04 18:27:33 +08:00
Add ability to build images in play kube
When playing a kube YAML file, it can be desirable to be able to build an image on the fly. This is good for development of an image and YAML files and somewhat mocks what compose does. Signed-off-by: Brent Baude <bbaude@redhat.com>
This commit is contained in:
@ -100,6 +100,9 @@ func init() {
|
||||
configmapFlagName := "configmap"
|
||||
flags.StringSliceVar(&kubeOptions.ConfigMaps, configmapFlagName, []string{}, "`Pathname` of a YAML file containing a kubernetes configmap")
|
||||
_ = kubeCmd.RegisterFlagCompletionFunc(configmapFlagName, completion.AutocompleteDefault)
|
||||
|
||||
buildFlagName := "build"
|
||||
flags.BoolVar(&kubeOptions.Build, buildFlagName, false, "Build all images in a YAML (given Containerfiles exist)")
|
||||
}
|
||||
_ = flags.MarkHidden("signature-policy")
|
||||
}
|
||||
|
@ -35,6 +35,36 @@ A Kubernetes PersistentVolumeClaim represents a Podman named volume. Only the Pe
|
||||
- volume.podman.io/gid
|
||||
- volume.podman.io/mount-options
|
||||
|
||||
Play kube is capable of building images on the fly given the correct directory layout and Containerfiles. This
|
||||
option is not available for remote clients yet. Consider the following excerpt from a YAML file:
|
||||
```
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
...
|
||||
spec:
|
||||
containers:
|
||||
- command:
|
||||
- top
|
||||
- name: container
|
||||
value: podman
|
||||
image: foobar
|
||||
...
|
||||
```
|
||||
|
||||
If there is a directory named `foobar` in the current working directory with a file named `Containerfile` or `Dockerfile`,
|
||||
Podman play kube will build that image and name it `foobar`. An example directory structure for this example would look
|
||||
like:
|
||||
```
|
||||
|- mykubefiles
|
||||
|- myplayfile.yaml
|
||||
|- foobar
|
||||
|- Containerfile
|
||||
```
|
||||
|
||||
The build will consider `foobar` to be the context directory for the build. If there is an image in local storage
|
||||
called `foobar`, the image will not be built unless the `--build` flag is used.
|
||||
|
||||
## OPTIONS
|
||||
|
||||
#### **--authfile**=*path*
|
||||
@ -45,6 +75,10 @@ If the authorization state is not found there, $HOME/.docker/config.json is chec
|
||||
Note: You can also override the default path of the authentication file by setting the REGISTRY\_AUTH\_FILE
|
||||
environment variable. `export REGISTRY_AUTH_FILE=path`
|
||||
|
||||
#### **--build**
|
||||
|
||||
Build images even if they are found in the local storage.
|
||||
|
||||
#### **--cert-dir**=*path*
|
||||
|
||||
Use certificates at *path* (\*.crt, \*.cert, \*.key) to connect to the registry.
|
||||
|
@ -10,6 +10,8 @@ import (
|
||||
type PlayKubeOptions struct {
|
||||
// Authfile - path to an authentication file.
|
||||
Authfile string
|
||||
// Indicator to build all images with Containerfile or Dockerfile
|
||||
Build bool
|
||||
// CertDir - to a directory containing TLS certifications and keys.
|
||||
CertDir string
|
||||
// Username for authenticating against the registry.
|
||||
|
@ -7,9 +7,11 @@ import (
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
buildahDefine "github.com/containers/buildah/define"
|
||||
"github.com/containers/common/libimage"
|
||||
"github.com/containers/common/pkg/config"
|
||||
"github.com/containers/image/v5/types"
|
||||
@ -266,38 +268,68 @@ func (ic *ContainerEngine) playKubePod(ctx context.Context, podName string, podY
|
||||
}
|
||||
|
||||
containers := make([]*libpod.Container, 0, len(podYAML.Spec.Containers))
|
||||
cwd, err := os.Getwd()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, container := range podYAML.Spec.Containers {
|
||||
// Contains all labels obtained from kube
|
||||
labels := make(map[string]string)
|
||||
|
||||
// NOTE: set the pull policy to "newer". This will cover cases
|
||||
// where the "latest" tag requires a pull and will also
|
||||
// transparently handle "localhost/" prefixed files which *may*
|
||||
// refer to a locally built image OR an image running a
|
||||
// registry on localhost.
|
||||
pullPolicy := config.PullPolicyNewer
|
||||
if len(container.ImagePullPolicy) > 0 {
|
||||
// Make sure to lower the strings since K8s pull policy
|
||||
// may be capitalized (see bugzilla.redhat.com/show_bug.cgi?id=1985905).
|
||||
rawPolicy := string(container.ImagePullPolicy)
|
||||
pullPolicy, err = config.ParsePullPolicy(strings.ToLower(rawPolicy))
|
||||
var pulledImage *libimage.Image
|
||||
buildFile, err := getBuildFile(container.Image, cwd)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
existsLocally, err := ic.Libpod.LibimageRuntime().Exists(container.Image)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if (len(buildFile) > 0 && !existsLocally) || (len(buildFile) > 0 && options.Build) {
|
||||
buildOpts := new(buildahDefine.BuildOptions)
|
||||
commonOpts := new(buildahDefine.CommonBuildOptions)
|
||||
buildOpts.ConfigureNetwork = buildahDefine.NetworkDefault
|
||||
buildOpts.Isolation = buildahDefine.IsolationChroot
|
||||
buildOpts.CommonBuildOpts = commonOpts
|
||||
buildOpts.Output = container.Image
|
||||
if _, _, err := ic.Libpod.Build(ctx, *buildOpts, []string{buildFile}...); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
i, _, err := ic.Libpod.LibimageRuntime().LookupImage(container.Image, new(libimage.LookupImageOptions))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
// This ensures the image is the image store
|
||||
pullOptions := &libimage.PullOptions{}
|
||||
pullOptions.AuthFilePath = options.Authfile
|
||||
pullOptions.CertDirPath = options.CertDir
|
||||
pullOptions.SignaturePolicyPath = options.SignaturePolicy
|
||||
pullOptions.Writer = writer
|
||||
pullOptions.Username = options.Username
|
||||
pullOptions.Password = options.Password
|
||||
pullOptions.InsecureSkipTLSVerify = options.SkipTLSVerify
|
||||
pulledImage = i
|
||||
} else {
|
||||
// NOTE: set the pull policy to "newer". This will cover cases
|
||||
// where the "latest" tag requires a pull and will also
|
||||
// transparently handle "localhost/" prefixed files which *may*
|
||||
// refer to a locally built image OR an image running a
|
||||
// registry on localhost.
|
||||
pullPolicy := config.PullPolicyNewer
|
||||
if len(container.ImagePullPolicy) > 0 {
|
||||
// Make sure to lower the strings since K8s pull policy
|
||||
// may be capitalized (see bugzilla.redhat.com/show_bug.cgi?id=1985905).
|
||||
rawPolicy := string(container.ImagePullPolicy)
|
||||
pullPolicy, err = config.ParsePullPolicy(strings.ToLower(rawPolicy))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
// This ensures the image is the image store
|
||||
pullOptions := &libimage.PullOptions{}
|
||||
pullOptions.AuthFilePath = options.Authfile
|
||||
pullOptions.CertDirPath = options.CertDir
|
||||
pullOptions.SignaturePolicyPath = options.SignaturePolicy
|
||||
pullOptions.Writer = writer
|
||||
pullOptions.Username = options.Username
|
||||
pullOptions.Password = options.Password
|
||||
pullOptions.InsecureSkipTLSVerify = options.SkipTLSVerify
|
||||
|
||||
pulledImages, err := ic.Libpod.LibimageRuntime().Pull(ctx, container.Image, pullPolicy, pullOptions)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
pulledImages, err := ic.Libpod.LibimageRuntime().Pull(ctx, container.Image, pullPolicy, pullOptions)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pulledImage = pulledImages[0]
|
||||
}
|
||||
|
||||
// Handle kube annotations
|
||||
@ -318,7 +350,7 @@ func (ic *ContainerEngine) playKubePod(ctx context.Context, podName string, podY
|
||||
|
||||
specgenOpts := kube.CtrSpecGenOptions{
|
||||
Container: container,
|
||||
Image: pulledImages[0],
|
||||
Image: pulledImage,
|
||||
Volumes: volumes,
|
||||
PodID: pod.ID(),
|
||||
PodName: podName,
|
||||
@ -509,3 +541,48 @@ func sortKubeKinds(documentList [][]byte) ([][]byte, error) {
|
||||
|
||||
return sortedDocumentList, nil
|
||||
}
|
||||
func imageNamePrefix(imageName string) string {
|
||||
prefix := imageName
|
||||
s := strings.Split(prefix, ":")
|
||||
if len(s) > 0 {
|
||||
prefix = s[0]
|
||||
}
|
||||
s = strings.Split(prefix, "/")
|
||||
if len(s) > 0 {
|
||||
prefix = s[len(s)-1]
|
||||
}
|
||||
s = strings.Split(prefix, "@")
|
||||
if len(s) > 0 {
|
||||
prefix = s[0]
|
||||
}
|
||||
return prefix
|
||||
}
|
||||
|
||||
func getBuildFile(imageName string, cwd string) (string, error) {
|
||||
buildDirName := imageNamePrefix(imageName)
|
||||
containerfilePath := filepath.Join(cwd, buildDirName, "Containerfile")
|
||||
dockerfilePath := filepath.Join(cwd, buildDirName, "Dockerfile")
|
||||
|
||||
_, err := os.Stat(filepath.Join(containerfilePath))
|
||||
if err == nil {
|
||||
logrus.Debugf("building %s with %s", imageName, containerfilePath)
|
||||
return containerfilePath, nil
|
||||
}
|
||||
// If the error is not because the file does not exist, take
|
||||
// a mulligan and try Dockerfile. If that also fails, return that
|
||||
// error
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
logrus.Errorf("%v: unable to check for %s", err, containerfilePath)
|
||||
}
|
||||
|
||||
_, err = os.Stat(filepath.Join(dockerfilePath))
|
||||
if err == nil {
|
||||
logrus.Debugf("building %s with %s", imageName, dockerfilePath)
|
||||
return dockerfilePath, nil
|
||||
}
|
||||
// Strike two
|
||||
if os.IsNotExist(err) {
|
||||
return "", nil
|
||||
}
|
||||
return "", err
|
||||
}
|
||||
|
@ -841,3 +841,18 @@ func (p *PodmanTestIntegration) buildImage(dockerfile, imageName string, layers
|
||||
output := session.OutputToStringArray()
|
||||
return output[len(output)-1]
|
||||
}
|
||||
|
||||
func writeYaml(content string, fileName string) error {
|
||||
f, err := os.Create(fileName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
_, err = f.WriteString(content)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
243
test/e2e/play_build_test.go
Normal file
243
test/e2e/play_build_test.go
Normal file
@ -0,0 +1,243 @@
|
||||
// +build !remote
|
||||
|
||||
// build for play kube is not supported on remote yet.
|
||||
|
||||
package integration
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
. "github.com/containers/podman/v3/test/utils"
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
. "github.com/onsi/gomega/gexec"
|
||||
)
|
||||
|
||||
var _ = Describe("Podman play kube with build", func() {
|
||||
var (
|
||||
tempdir string
|
||||
err error
|
||||
podmanTest *PodmanTestIntegration
|
||||
)
|
||||
|
||||
BeforeEach(func() {
|
||||
tempdir, err = CreateTempDirInTempDir()
|
||||
if err != nil {
|
||||
os.Exit(1)
|
||||
}
|
||||
podmanTest = PodmanTestCreate(tempdir)
|
||||
podmanTest.Setup()
|
||||
podmanTest.SeedImages()
|
||||
})
|
||||
|
||||
AfterEach(func() {
|
||||
podmanTest.Cleanup()
|
||||
f := CurrentGinkgoTestDescription()
|
||||
processTestResult(f)
|
||||
|
||||
})
|
||||
|
||||
var testYAML = `
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
creationTimestamp: "2021-08-05T17:55:51Z"
|
||||
labels:
|
||||
app: foobar
|
||||
name: top_pod
|
||||
spec:
|
||||
containers:
|
||||
- command:
|
||||
- top
|
||||
env:
|
||||
- name: PATH
|
||||
value: /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
|
||||
- name: TERM
|
||||
value: xterm
|
||||
- name: container
|
||||
value: podman
|
||||
image: foobar
|
||||
name: foobar
|
||||
resources: {}
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: true
|
||||
capabilities:
|
||||
drop:
|
||||
- CAP_MKNOD
|
||||
- CAP_NET_RAW
|
||||
- CAP_AUDIT_WRITE
|
||||
privileged: false
|
||||
readOnlyRootFilesystem: false
|
||||
seLinuxOptions: {}
|
||||
tty: true
|
||||
workingDir: /
|
||||
dnsConfig: {}
|
||||
status: {}
|
||||
`
|
||||
|
||||
var playBuildFile = `
|
||||
FROM quay.io/libpod/alpine_nginx:latest
|
||||
RUN apk update && apk add strace
|
||||
LABEL homer=dad
|
||||
`
|
||||
var prebuiltImage = `
|
||||
FROM quay.io/libpod/alpine_nginx:latest
|
||||
RUN apk update && apk add strace
|
||||
LABEL marge=mom
|
||||
`
|
||||
It("Check that image is built using Dockerfile", func() {
|
||||
// Setup
|
||||
yamlDir := filepath.Join(tempdir, RandomString(12))
|
||||
err := os.Mkdir(yamlDir, 0755)
|
||||
err = writeYaml(testYAML, filepath.Join(yamlDir, "top.yaml"))
|
||||
Expect(err).To(BeNil())
|
||||
app1Dir := filepath.Join(yamlDir, "foobar")
|
||||
err = os.Mkdir(app1Dir, 0755)
|
||||
Expect(err).To(BeNil())
|
||||
err = writeYaml(playBuildFile, filepath.Join(app1Dir, "Dockerfile"))
|
||||
Expect(err).To(BeNil())
|
||||
|
||||
// Switch to temp dir and restore it afterwards
|
||||
cwd, err := os.Getwd()
|
||||
Expect(err).To(BeNil())
|
||||
Expect(os.Chdir(yamlDir)).To(BeNil())
|
||||
defer func() { (Expect(os.Chdir(cwd)).To(BeNil())) }()
|
||||
|
||||
session := podmanTest.Podman([]string{"play", "kube", "top.yaml"})
|
||||
session.WaitWithDefaultTimeout()
|
||||
Expect(session).Should(Exit(0))
|
||||
|
||||
exists := podmanTest.Podman([]string{"image", "exists", "foobar"})
|
||||
exists.WaitWithDefaultTimeout()
|
||||
Expect(exists).Should(Exit(0))
|
||||
|
||||
inspect := podmanTest.Podman([]string{"container", "inspect", "top_pod-foobar"})
|
||||
inspect.WaitWithDefaultTimeout()
|
||||
Expect(inspect).Should(Exit(0))
|
||||
inspectData := inspect.InspectContainerToJSON()
|
||||
Expect(len(inspectData)).To(BeNumerically(">", 0))
|
||||
Expect(inspectData[0].Config.Labels["homer"]).To(Equal("dad"))
|
||||
})
|
||||
|
||||
It("Check that image is built using Containerfile", func() {
|
||||
// Setup
|
||||
yamlDir := filepath.Join(tempdir, RandomString(12))
|
||||
err := os.Mkdir(yamlDir, 0755)
|
||||
err = writeYaml(testYAML, filepath.Join(yamlDir, "top.yaml"))
|
||||
Expect(err).To(BeNil())
|
||||
app1Dir := filepath.Join(yamlDir, "foobar")
|
||||
err = os.Mkdir(app1Dir, 0755)
|
||||
Expect(err).To(BeNil())
|
||||
err = writeYaml(playBuildFile, filepath.Join(app1Dir, "Containerfile"))
|
||||
Expect(err).To(BeNil())
|
||||
|
||||
// Switch to temp dir and restore it afterwards
|
||||
cwd, err := os.Getwd()
|
||||
Expect(err).To(BeNil())
|
||||
Expect(os.Chdir(yamlDir)).To(BeNil())
|
||||
defer func() { (Expect(os.Chdir(cwd)).To(BeNil())) }()
|
||||
|
||||
session := podmanTest.Podman([]string{"play", "kube", "top.yaml"})
|
||||
session.WaitWithDefaultTimeout()
|
||||
Expect(session).Should(Exit(0))
|
||||
|
||||
exists := podmanTest.Podman([]string{"image", "exists", "foobar"})
|
||||
exists.WaitWithDefaultTimeout()
|
||||
Expect(exists).Should(Exit(0))
|
||||
|
||||
inspect := podmanTest.Podman([]string{"container", "inspect", "top_pod-foobar"})
|
||||
inspect.WaitWithDefaultTimeout()
|
||||
Expect(inspect).Should(Exit(0))
|
||||
inspectData := inspect.InspectContainerToJSON()
|
||||
Expect(len(inspectData)).To(BeNumerically(">", 0))
|
||||
Expect(inspectData[0].Config.Labels["homer"]).To(Equal("dad"))
|
||||
})
|
||||
|
||||
It("Do not build image if already in the local store", func() {
|
||||
// Setup
|
||||
yamlDir := filepath.Join(tempdir, RandomString(12))
|
||||
err := os.Mkdir(yamlDir, 0755)
|
||||
err = writeYaml(testYAML, filepath.Join(yamlDir, "top.yaml"))
|
||||
Expect(err).To(BeNil())
|
||||
|
||||
// build an image called foobar but make sure it doesnt have
|
||||
// the same label as the yaml buildfile, so we can check that
|
||||
// the image is NOT rebuilt.
|
||||
err = writeYaml(prebuiltImage, filepath.Join(yamlDir, "Containerfile"))
|
||||
Expect(err).To(BeNil())
|
||||
|
||||
app1Dir := filepath.Join(yamlDir, "foobar")
|
||||
err = os.Mkdir(app1Dir, 0755)
|
||||
Expect(err).To(BeNil())
|
||||
err = writeYaml(playBuildFile, filepath.Join(app1Dir, "Containerfile"))
|
||||
Expect(err).To(BeNil())
|
||||
|
||||
// Switch to temp dir and restore it afterwards
|
||||
cwd, err := os.Getwd()
|
||||
Expect(err).To(BeNil())
|
||||
Expect(os.Chdir(yamlDir)).To(BeNil())
|
||||
defer func() { (Expect(os.Chdir(cwd)).To(BeNil())) }()
|
||||
|
||||
// Build the image into the local store
|
||||
build := podmanTest.Podman([]string{"build", "-t", "foobar", "-f", "Containerfile"})
|
||||
build.WaitWithDefaultTimeout()
|
||||
Expect(build).Should(Exit(0))
|
||||
|
||||
session := podmanTest.Podman([]string{"play", "kube", "top.yaml"})
|
||||
session.WaitWithDefaultTimeout()
|
||||
Expect(session).Should(Exit(0))
|
||||
|
||||
inspect := podmanTest.Podman([]string{"container", "inspect", "top_pod-foobar"})
|
||||
inspect.WaitWithDefaultTimeout()
|
||||
Expect(inspect).Should(Exit(0))
|
||||
inspectData := inspect.InspectContainerToJSON()
|
||||
Expect(len(inspectData)).To(BeNumerically(">", 0))
|
||||
Expect(inspectData[0].Config.Labels["homer"]).To(Equal(""))
|
||||
Expect(inspectData[0].Config.Labels["marge"]).To(Equal("mom"))
|
||||
})
|
||||
|
||||
It("--build should override image in store", func() {
|
||||
// Setup
|
||||
yamlDir := filepath.Join(tempdir, RandomString(12))
|
||||
err := os.Mkdir(yamlDir, 0755)
|
||||
err = writeYaml(testYAML, filepath.Join(yamlDir, "top.yaml"))
|
||||
Expect(err).To(BeNil())
|
||||
|
||||
// build an image called foobar but make sure it doesnt have
|
||||
// the same label as the yaml buildfile, so we can check that
|
||||
// the image is NOT rebuilt.
|
||||
err = writeYaml(prebuiltImage, filepath.Join(yamlDir, "Containerfile"))
|
||||
Expect(err).To(BeNil())
|
||||
|
||||
app1Dir := filepath.Join(yamlDir, "foobar")
|
||||
err = os.Mkdir(app1Dir, 0755)
|
||||
Expect(err).To(BeNil())
|
||||
err = writeYaml(playBuildFile, filepath.Join(app1Dir, "Containerfile"))
|
||||
Expect(err).To(BeNil())
|
||||
|
||||
// Switch to temp dir and restore it afterwards
|
||||
cwd, err := os.Getwd()
|
||||
Expect(err).To(BeNil())
|
||||
Expect(os.Chdir(yamlDir)).To(BeNil())
|
||||
defer func() { (Expect(os.Chdir(cwd)).To(BeNil())) }()
|
||||
|
||||
// Build the image into the local store
|
||||
build := podmanTest.Podman([]string{"build", "-t", "foobar", "-f", "Containerfile"})
|
||||
build.WaitWithDefaultTimeout()
|
||||
Expect(build).Should(Exit(0))
|
||||
|
||||
session := podmanTest.Podman([]string{"play", "kube", "--build", "top.yaml"})
|
||||
session.WaitWithDefaultTimeout()
|
||||
Expect(session).Should(Exit(0))
|
||||
|
||||
inspect := podmanTest.Podman([]string{"container", "inspect", "top_pod-foobar"})
|
||||
inspect.WaitWithDefaultTimeout()
|
||||
Expect(inspect).Should(Exit(0))
|
||||
inspectData := inspect.InspectContainerToJSON()
|
||||
Expect(len(inspectData)).To(BeNumerically(">", 0))
|
||||
Expect(inspectData[0].Config.Labels["homer"]).To(Equal("dad"))
|
||||
Expect(inspectData[0].Config.Labels["marge"]).To(Equal(""))
|
||||
})
|
||||
|
||||
})
|
@ -512,21 +512,6 @@ var (
|
||||
defaultSecret = []byte(`{"FOO":"Zm9v","BAR":"YmFy"}`)
|
||||
)
|
||||
|
||||
func writeYaml(content string, fileName string) error {
|
||||
f, err := os.Create(fileName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
_, err = f.WriteString(content)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// getKubeYaml returns a kubernetes YAML document.
|
||||
func getKubeYaml(kind string, object interface{}) (string, error) {
|
||||
var yamlTemplate string
|
||||
|
Reference in New Issue
Block a user