Merge pull request #11180 from baude/buildplaykube

Add ability to build images in play kube
This commit is contained in:
OpenShift Merge Robot 2021-08-18 15:06:19 -04:00 committed by GitHub
commit fa206e11ca
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 400 additions and 41 deletions

View file

@ -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")
}

View file

@ -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.

View file

@ -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.

View file

@ -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
}

View file

@ -845,3 +845,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
View 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(""))
})
})

View file

@ -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