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
7 changed files with 400 additions and 41 deletions

View File

@ -100,6 +100,9 @@ func init() {
configmapFlagName := "configmap" configmapFlagName := "configmap"
flags.StringSliceVar(&kubeOptions.ConfigMaps, configmapFlagName, []string{}, "`Pathname` of a YAML file containing a kubernetes configmap") flags.StringSliceVar(&kubeOptions.ConfigMaps, configmapFlagName, []string{}, "`Pathname` of a YAML file containing a kubernetes configmap")
_ = kubeCmd.RegisterFlagCompletionFunc(configmapFlagName, completion.AutocompleteDefault) _ = 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") _ = 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/gid
- volume.podman.io/mount-options - 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 ## OPTIONS
#### **--authfile**=*path* #### **--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 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` environment variable. `export REGISTRY_AUTH_FILE=path`
#### **--build**
Build images even if they are found in the local storage.
#### **--cert-dir**=*path* #### **--cert-dir**=*path*
Use certificates at *path* (\*.crt, \*.cert, \*.key) to connect to the registry. Use certificates at *path* (\*.crt, \*.cert, \*.key) to connect to the registry.

View File

@ -10,6 +10,8 @@ import (
type PlayKubeOptions struct { type PlayKubeOptions struct {
// Authfile - path to an authentication file. // Authfile - path to an authentication file.
Authfile string Authfile string
// Indicator to build all images with Containerfile or Dockerfile
Build bool
// CertDir - to a directory containing TLS certifications and keys. // CertDir - to a directory containing TLS certifications and keys.
CertDir string CertDir string
// Username for authenticating against the registry. // Username for authenticating against the registry.

View File

@ -7,9 +7,11 @@ import (
"io" "io"
"io/ioutil" "io/ioutil"
"os" "os"
"path/filepath"
"strconv" "strconv"
"strings" "strings"
buildahDefine "github.com/containers/buildah/define"
"github.com/containers/common/libimage" "github.com/containers/common/libimage"
"github.com/containers/common/pkg/config" "github.com/containers/common/pkg/config"
"github.com/containers/image/v5/types" "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)) 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 { for _, container := range podYAML.Spec.Containers {
// Contains all labels obtained from kube // Contains all labels obtained from kube
labels := make(map[string]string) labels := make(map[string]string)
var pulledImage *libimage.Image
// NOTE: set the pull policy to "newer". This will cover cases buildFile, err := getBuildFile(container.Image, cwd)
// where the "latest" tag requires a pull and will also if err != nil {
// transparently handle "localhost/" prefixed files which *may* return nil, err
// refer to a locally built image OR an image running a }
// registry on localhost. existsLocally, err := ic.Libpod.LibimageRuntime().Exists(container.Image)
pullPolicy := config.PullPolicyNewer if err != nil {
if len(container.ImagePullPolicy) > 0 { return nil, err
// Make sure to lower the strings since K8s pull policy }
// may be capitalized (see bugzilla.redhat.com/show_bug.cgi?id=1985905). if (len(buildFile) > 0 && !existsLocally) || (len(buildFile) > 0 && options.Build) {
rawPolicy := string(container.ImagePullPolicy) buildOpts := new(buildahDefine.BuildOptions)
pullPolicy, err = config.ParsePullPolicy(strings.ToLower(rawPolicy)) 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 { if err != nil {
return nil, err return nil, err
} }
} pulledImage = i
// This ensures the image is the image store } else {
pullOptions := &libimage.PullOptions{} // NOTE: set the pull policy to "newer". This will cover cases
pullOptions.AuthFilePath = options.Authfile // where the "latest" tag requires a pull and will also
pullOptions.CertDirPath = options.CertDir // transparently handle "localhost/" prefixed files which *may*
pullOptions.SignaturePolicyPath = options.SignaturePolicy // refer to a locally built image OR an image running a
pullOptions.Writer = writer // registry on localhost.
pullOptions.Username = options.Username pullPolicy := config.PullPolicyNewer
pullOptions.Password = options.Password if len(container.ImagePullPolicy) > 0 {
pullOptions.InsecureSkipTLSVerify = options.SkipTLSVerify // 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) pulledImages, err := ic.Libpod.LibimageRuntime().Pull(ctx, container.Image, pullPolicy, pullOptions)
if err != nil { if err != nil {
return nil, err return nil, err
}
pulledImage = pulledImages[0]
} }
// Handle kube annotations // Handle kube annotations
@ -318,7 +350,7 @@ func (ic *ContainerEngine) playKubePod(ctx context.Context, podName string, podY
specgenOpts := kube.CtrSpecGenOptions{ specgenOpts := kube.CtrSpecGenOptions{
Container: container, Container: container,
Image: pulledImages[0], Image: pulledImage,
Volumes: volumes, Volumes: volumes,
PodID: pod.ID(), PodID: pod.ID(),
PodName: podName, PodName: podName,
@ -509,3 +541,48 @@ func sortKubeKinds(documentList [][]byte) ([][]byte, error) {
return sortedDocumentList, nil 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() output := session.OutputToStringArray()
return output[len(output)-1] 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"}`) 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. // getKubeYaml returns a kubernetes YAML document.
func getKubeYaml(kind string, object interface{}) (string, error) { func getKubeYaml(kind string, object interface{}) (string, error) {
var yamlTemplate string var yamlTemplate string