add {generate,play} kube

Add the `podman generate kube` and `podman play kube` command.  The code
has largely been copied from Podman v1 but restructured to not leak the
K8s core API into the (remote) client.

Both commands are added in the same commit to allow for enabling the
tests at the same time.

Move some exports from `cmd/podman/common` to the appropriate places in
the backend to avoid circular dependencies.

Move definitions of label annotations to `libpod/define` and set the
security-opt labels in the frontend to make kube tests pass.

Implement rest endpoints, bindings and the tunnel interface.

Signed-off-by: Valentin Rothberg <rothberg@redhat.com>
This commit is contained in:
Valentin Rothberg
2020-05-05 11:35:32 +02:00
parent 0eb905ff2c
commit f269be3a31
35 changed files with 1269 additions and 110 deletions

View File

@ -534,10 +534,13 @@ func FillOutSpecGen(s *specgen.SpecGenerator, c *ContainerCLIOpts, args []string
case "label": case "label":
// TODO selinux opts and label opts are the same thing // TODO selinux opts and label opts are the same thing
s.ContainerSecurityConfig.SelinuxOpts = append(s.ContainerSecurityConfig.SelinuxOpts, con[1]) s.ContainerSecurityConfig.SelinuxOpts = append(s.ContainerSecurityConfig.SelinuxOpts, con[1])
s.Annotations[define.InspectAnnotationLabel] = con[1]
case "apparmor": case "apparmor":
s.ContainerSecurityConfig.ApparmorProfile = con[1] s.ContainerSecurityConfig.ApparmorProfile = con[1]
s.Annotations[define.InspectAnnotationApparmor] = con[1]
case "seccomp": case "seccomp":
s.SeccompProfilePath = con[1] s.SeccompProfilePath = con[1]
s.Annotations[define.InspectAnnotationSeccomp] = con[1]
default: default:
return fmt.Errorf("invalid --security-opt 2: %q", opt) return fmt.Errorf("invalid --security-opt 2: %q", opt)
} }

View File

@ -1,3 +0,0 @@
package common
var DefaultKernelNamespaces = "cgroup,ipc,net,uts"

View File

@ -22,7 +22,7 @@ var (
func init() { func init() {
registry.Commands = append(registry.Commands, registry.CliCommand{ registry.Commands = append(registry.Commands, registry.CliCommand{
Mode: []entities.EngineMode{entities.ABIMode}, Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode},
Command: generateCmd, Command: generateCmd,
}) })
} }

View File

@ -0,0 +1,68 @@
package pods
import (
"fmt"
"io/ioutil"
"os"
"github.com/containers/libpod/cmd/podman/registry"
"github.com/containers/libpod/cmd/podman/utils"
"github.com/containers/libpod/pkg/domain/entities"
"github.com/pkg/errors"
"github.com/spf13/cobra"
)
var (
kubeOptions = entities.GenerateKubeOptions{}
kubeFile = ""
kubeDescription = `Command generates Kubernetes pod and service YAML (v1 specification) from a Podman container or pod.
Whether the input is for a container or pod, Podman will always generate the specification as a pod.`
kubeCmd = &cobra.Command{
Use: "kube [flags] CONTAINER | POD",
Short: "Generate Kubernetes YAML from a container or pod.",
Long: kubeDescription,
RunE: kube,
Args: cobra.ExactArgs(1),
Example: `podman generate kube ctrID
podman generate kube podID
podman generate kube --service podID`,
}
)
func init() {
registry.Commands = append(registry.Commands, registry.CliCommand{
Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode},
Command: kubeCmd,
Parent: generateCmd,
})
flags := kubeCmd.Flags()
flags.BoolVarP(&kubeOptions.Service, "service", "s", false, "Generate YAML for a Kubernetes service object")
flags.StringVarP(&kubeFile, "filename", "f", "", "Write output to the specified path")
flags.SetNormalizeFunc(utils.AliasFlags)
}
func kube(cmd *cobra.Command, args []string) error {
report, err := registry.ContainerEngine().GenerateKube(registry.GetContext(), args[0], kubeOptions)
if err != nil {
return err
}
content, err := ioutil.ReadAll(report.Reader)
if err != nil {
return err
}
if cmd.Flags().Changed("filename") {
if _, err := os.Stat(kubeFile); err == nil {
return errors.Errorf("cannot write to %q", kubeFile)
}
if err := ioutil.WriteFile(kubeFile, content, 0644); err != nil {
return errors.Wrapf(err, "cannot write to %q", kubeFile)
}
return nil
}
fmt.Println(string(content))
return nil
}

View File

@ -29,7 +29,7 @@ var (
func init() { func init() {
registry.Commands = append(registry.Commands, registry.CliCommand{ registry.Commands = append(registry.Commands, registry.CliCommand{
Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, Mode: []entities.EngineMode{entities.ABIMode},
Command: systemdCmd, Command: systemdCmd,
Parent: generateCmd, Parent: generateCmd,
}) })

View File

@ -10,6 +10,7 @@ import (
_ "github.com/containers/libpod/cmd/podman/images" _ "github.com/containers/libpod/cmd/podman/images"
_ "github.com/containers/libpod/cmd/podman/manifest" _ "github.com/containers/libpod/cmd/podman/manifest"
_ "github.com/containers/libpod/cmd/podman/networks" _ "github.com/containers/libpod/cmd/podman/networks"
_ "github.com/containers/libpod/cmd/podman/play"
_ "github.com/containers/libpod/cmd/podman/pods" _ "github.com/containers/libpod/cmd/podman/pods"
"github.com/containers/libpod/cmd/podman/registry" "github.com/containers/libpod/cmd/podman/registry"
_ "github.com/containers/libpod/cmd/podman/system" _ "github.com/containers/libpod/cmd/podman/system"

101
cmd/podman/play/kube.go Normal file
View File

@ -0,0 +1,101 @@
package pods
import (
"fmt"
"os"
"github.com/containers/common/pkg/auth"
"github.com/containers/image/v5/types"
"github.com/containers/libpod/cmd/podman/registry"
"github.com/containers/libpod/cmd/podman/utils"
"github.com/containers/libpod/pkg/domain/entities"
"github.com/pkg/errors"
"github.com/spf13/cobra"
)
// playKubeOptionsWrapper allows for separating CLI-only fields from API-only
// fields.
type playKubeOptionsWrapper struct {
entities.PlayKubeOptions
TLSVerifyCLI bool
}
var (
// https://kubernetes.io/docs/reference/command-line-tools-reference/kubelet/
defaultSeccompRoot = "/var/lib/kubelet/seccomp"
kubeOptions = playKubeOptionsWrapper{}
kubeDescription = `Command reads in a structured file of Kubernetes YAML.
It creates the pod and containers described in the YAML. The containers within the pod are then started and the ID of the new Pod is output.`
kubeCmd = &cobra.Command{
Use: "kube [flags] KUBEFILE",
Short: "Play a pod based on Kubernetes YAML.",
Long: kubeDescription,
RunE: kube,
Args: cobra.ExactArgs(1),
Example: `podman play kube nginx.yml
podman play kube --creds user:password --seccomp-profile-root /custom/path apache.yml`,
}
)
func init() {
registry.Commands = append(registry.Commands, registry.CliCommand{
Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode},
Command: kubeCmd,
Parent: playCmd,
})
flags := kubeCmd.Flags()
flags.SetNormalizeFunc(utils.AliasFlags)
flags.StringVar(&kubeOptions.Credentials, "creds", "", "`Credentials` (USERNAME:PASSWORD) to use for authenticating to a registry")
flags.StringVar(&kubeOptions.Network, "network", "", "Connect pod to CNI network(s)")
flags.BoolVarP(&kubeOptions.Quiet, "quiet", "q", false, "Suppress output information when pulling images")
if !registry.IsRemote() {
flags.StringVar(&kubeOptions.Authfile, "authfile", auth.GetDefaultAuthFile(), "Path of the authentication file. Use REGISTRY_AUTH_FILE environment variable to override")
flags.StringVar(&kubeOptions.CertDir, "cert-dir", "", "`Pathname` of a directory containing TLS certificates and keys")
flags.BoolVar(&kubeOptions.TLSVerifyCLI, "tls-verify", true, "Require HTTPS and verify certificates when contacting registries")
flags.StringVar(&kubeOptions.SignaturePolicy, "signature-policy", "", "`Pathname` of signature policy file (not usually used)")
flags.StringVar(&kubeOptions.SeccompProfileRoot, "seccomp-profile-root", defaultSeccompRoot, "Directory path for seccomp profiles")
}
}
func kube(cmd *cobra.Command, args []string) error {
// TLS verification in c/image is controlled via a `types.OptionalBool`
// which allows for distinguishing among set-true, set-false, unspecified
// which is important to implement a sane way of dealing with defaults of
// boolean CLI flags.
if cmd.Flags().Changed("tls-verify") {
kubeOptions.SkipTLSVerify = types.NewOptionalBool(!kubeOptions.TLSVerifyCLI)
}
if kubeOptions.Authfile != "" {
if _, err := os.Stat(kubeOptions.Authfile); err != nil {
return errors.Wrapf(err, "error getting authfile %s", kubeOptions.Authfile)
}
}
report, err := registry.ContainerEngine().PlayKube(registry.GetContext(), args[0], kubeOptions.PlayKubeOptions)
if err != nil {
return err
}
for _, l := range report.Logs {
fmt.Fprintf(os.Stderr, l)
}
fmt.Printf("Pod:\n%s\n", report.Pod)
switch len(report.Containers) {
case 0:
return nil
case 1:
fmt.Printf("Container:\n")
default:
fmt.Printf("Containers:\n")
}
for _, ctr := range report.Containers {
fmt.Println(ctr)
}
return nil
}

26
cmd/podman/play/play.go Normal file
View File

@ -0,0 +1,26 @@
package pods
import (
"github.com/containers/libpod/cmd/podman/registry"
"github.com/containers/libpod/cmd/podman/validate"
"github.com/containers/libpod/pkg/domain/entities"
"github.com/spf13/cobra"
)
var (
// Command: podman _play_
playCmd = &cobra.Command{
Use: "play",
Short: "Play a pod and its containers from a structured file.",
Long: "Play structured data (e.g., Kubernetes pod or service yaml) based on containers and pods.",
TraverseChildren: true,
RunE: validate.SubCommandExists,
}
)
func init() {
registry.Commands = append(registry.Commands, registry.CliCommand{
Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode},
Command: playCmd,
})
}

View File

@ -12,6 +12,7 @@ import (
"github.com/containers/libpod/cmd/podman/validate" "github.com/containers/libpod/cmd/podman/validate"
"github.com/containers/libpod/pkg/domain/entities" "github.com/containers/libpod/pkg/domain/entities"
"github.com/containers/libpod/pkg/errorhandling" "github.com/containers/libpod/pkg/errorhandling"
createconfig "github.com/containers/libpod/pkg/spec"
"github.com/containers/libpod/pkg/specgen" "github.com/containers/libpod/pkg/specgen"
"github.com/containers/libpod/pkg/util" "github.com/containers/libpod/pkg/util"
"github.com/pkg/errors" "github.com/pkg/errors"
@ -57,7 +58,7 @@ func init() {
flags.StringVarP(&createOptions.Name, "name", "n", "", "Assign a name to the pod") flags.StringVarP(&createOptions.Name, "name", "n", "", "Assign a name to the pod")
flags.StringVarP(&createOptions.Hostname, "hostname", "", "", "Set a hostname to the pod") flags.StringVarP(&createOptions.Hostname, "hostname", "", "", "Set a hostname to the pod")
flags.StringVar(&podIDFile, "pod-id-file", "", "Write the pod ID to the file") flags.StringVar(&podIDFile, "pod-id-file", "", "Write the pod ID to the file")
flags.StringVar(&share, "share", common.DefaultKernelNamespaces, "A comma delimited list of kernel namespaces the pod will share") flags.StringVar(&share, "share", createconfig.DefaultKernelNamespaces, "A comma delimited list of kernel namespaces the pod will share")
} }
func create(cmd *cobra.Command, args []string) error { func create(cmd *cobra.Command, args []string) error {

View File

@ -1221,5 +1221,5 @@ func (c *Container) AutoRemove() bool {
if spec.Annotations == nil { if spec.Annotations == nil {
return false return false
} }
return c.Spec().Annotations[InspectAnnotationAutoremove] == InspectResponseTrue return c.Spec().Annotations[define.InspectAnnotationAutoremove] == define.InspectResponseTrue
} }

View File

@ -16,73 +16,6 @@ import (
"github.com/syndtr/gocapability/capability" "github.com/syndtr/gocapability/capability"
) )
const (
// InspectAnnotationCIDFile is used by Inspect to determine if a
// container ID file was created for the container.
// If an annotation with this key is found in the OCI spec, it will be
// used in the output of Inspect().
InspectAnnotationCIDFile = "io.podman.annotations.cid-file"
// InspectAnnotationAutoremove is used by Inspect to determine if a
// container will be automatically removed on exit.
// If an annotation with this key is found in the OCI spec and is one of
// the two supported boolean values (InspectResponseTrue and
// InspectResponseFalse) it will be used in the output of Inspect().
InspectAnnotationAutoremove = "io.podman.annotations.autoremove"
// InspectAnnotationVolumesFrom is used by Inspect to identify
// containers whose volumes are are being used by this container.
// It is expected to be a comma-separated list of container names and/or
// IDs.
// If an annotation with this key is found in the OCI spec, it will be
// used in the output of Inspect().
InspectAnnotationVolumesFrom = "io.podman.annotations.volumes-from"
// InspectAnnotationPrivileged is used by Inspect to identify containers
// which are privileged (IE, running with elevated privileges).
// It is expected to be a boolean, populated by one of
// InspectResponseTrue or InspectResponseFalse.
// If an annotation with this key is found in the OCI spec, it will be
// used in the output of Inspect().
InspectAnnotationPrivileged = "io.podman.annotations.privileged"
// InspectAnnotationPublishAll is used by Inspect to identify containers
// which have all the ports from their image published.
// It is expected to be a boolean, populated by one of
// InspectResponseTrue or InspectResponseFalse.
// If an annotation with this key is found in the OCI spec, it will be
// used in the output of Inspect().
InspectAnnotationPublishAll = "io.podman.annotations.publish-all"
// InspectAnnotationInit is used by Inspect to identify containers that
// mount an init binary in.
// It is expected to be a boolean, populated by one of
// InspectResponseTrue or InspectResponseFalse.
// If an annotation with this key is found in the OCI spec, it will be
// used in the output of Inspect().
InspectAnnotationInit = "io.podman.annotations.init"
// InspectAnnotationLabel is used by Inspect to identify containers with
// special SELinux-related settings. It is used to populate the output
// of the SecurityOpt setting.
// If an annotation with this key is found in the OCI spec, it will be
// used in the output of Inspect().
InspectAnnotationLabel = "io.podman.annotations.label"
// InspectAnnotationSeccomp is used by Inspect to identify containers
// with special Seccomp-related settings. It is used to populate the
// output of the SecurityOpt setting in Inspect.
// If an annotation with this key is found in the OCI spec, it will be
// used in the output of Inspect().
InspectAnnotationSeccomp = "io.podman.annotations.seccomp"
// InspectAnnotationApparmor is used by Inspect to identify containers
// with special Apparmor-related settings. It is used to populate the
// output of the SecurityOpt setting.
// If an annotation with this key is found in the OCI spec, it will be
// used in the output of Inspect().
InspectAnnotationApparmor = "io.podman.annotations.apparmor"
// InspectResponseTrue is a boolean True response for an inspect
// annotation.
InspectResponseTrue = "TRUE"
// InspectResponseFalse is a boolean False response for an inspect
// annotation.
InspectResponseFalse = "FALSE"
)
// inspectLocked inspects a container for low-level information. // inspectLocked inspects a container for low-level information.
// The caller must held c.lock. // The caller must held c.lock.
func (c *Container) inspectLocked(size bool) (*define.InspectContainerData, error) { func (c *Container) inspectLocked(size bool) (*define.InspectContainerData, error) {
@ -452,26 +385,26 @@ func (c *Container) generateInspectContainerHostConfig(ctrSpec *spec.Spec, named
// Annotations // Annotations
if ctrSpec.Annotations != nil { if ctrSpec.Annotations != nil {
hostConfig.ContainerIDFile = ctrSpec.Annotations[InspectAnnotationCIDFile] hostConfig.ContainerIDFile = ctrSpec.Annotations[define.InspectAnnotationCIDFile]
if ctrSpec.Annotations[InspectAnnotationAutoremove] == InspectResponseTrue { if ctrSpec.Annotations[define.InspectAnnotationAutoremove] == define.InspectResponseTrue {
hostConfig.AutoRemove = true hostConfig.AutoRemove = true
} }
if ctrs, ok := ctrSpec.Annotations[InspectAnnotationVolumesFrom]; ok { if ctrs, ok := ctrSpec.Annotations[define.InspectAnnotationVolumesFrom]; ok {
hostConfig.VolumesFrom = strings.Split(ctrs, ",") hostConfig.VolumesFrom = strings.Split(ctrs, ",")
} }
if ctrSpec.Annotations[InspectAnnotationPrivileged] == InspectResponseTrue { if ctrSpec.Annotations[define.InspectAnnotationPrivileged] == define.InspectResponseTrue {
hostConfig.Privileged = true hostConfig.Privileged = true
} }
if ctrSpec.Annotations[InspectAnnotationInit] == InspectResponseTrue { if ctrSpec.Annotations[define.InspectAnnotationInit] == define.InspectResponseTrue {
hostConfig.Init = true hostConfig.Init = true
} }
if label, ok := ctrSpec.Annotations[InspectAnnotationLabel]; ok { if label, ok := ctrSpec.Annotations[define.InspectAnnotationLabel]; ok {
hostConfig.SecurityOpt = append(hostConfig.SecurityOpt, fmt.Sprintf("label=%s", label)) hostConfig.SecurityOpt = append(hostConfig.SecurityOpt, fmt.Sprintf("label=%s", label))
} }
if seccomp, ok := ctrSpec.Annotations[InspectAnnotationSeccomp]; ok { if seccomp, ok := ctrSpec.Annotations[define.InspectAnnotationSeccomp]; ok {
hostConfig.SecurityOpt = append(hostConfig.SecurityOpt, fmt.Sprintf("seccomp=%s", seccomp)) hostConfig.SecurityOpt = append(hostConfig.SecurityOpt, fmt.Sprintf("seccomp=%s", seccomp))
} }
if apparmor, ok := ctrSpec.Annotations[InspectAnnotationApparmor]; ok { if apparmor, ok := ctrSpec.Annotations[define.InspectAnnotationApparmor]; ok {
hostConfig.SecurityOpt = append(hostConfig.SecurityOpt, fmt.Sprintf("apparmor=%s", apparmor)) hostConfig.SecurityOpt = append(hostConfig.SecurityOpt, fmt.Sprintf("apparmor=%s", apparmor))
} }
} }

View File

@ -0,0 +1,68 @@
package define
const (
// InspectAnnotationCIDFile is used by Inspect to determine if a
// container ID file was created for the container.
// If an annotation with this key is found in the OCI spec, it will be
// used in the output of Inspect().
InspectAnnotationCIDFile = "io.podman.annotations.cid-file"
// InspectAnnotationAutoremove is used by Inspect to determine if a
// container will be automatically removed on exit.
// If an annotation with this key is found in the OCI spec and is one of
// the two supported boolean values (InspectResponseTrue and
// InspectResponseFalse) it will be used in the output of Inspect().
InspectAnnotationAutoremove = "io.podman.annotations.autoremove"
// InspectAnnotationVolumesFrom is used by Inspect to identify
// containers whose volumes are are being used by this container.
// It is expected to be a comma-separated list of container names and/or
// IDs.
// If an annotation with this key is found in the OCI spec, it will be
// used in the output of Inspect().
InspectAnnotationVolumesFrom = "io.podman.annotations.volumes-from"
// InspectAnnotationPrivileged is used by Inspect to identify containers
// which are privileged (IE, running with elevated privileges).
// It is expected to be a boolean, populated by one of
// InspectResponseTrue or InspectResponseFalse.
// If an annotation with this key is found in the OCI spec, it will be
// used in the output of Inspect().
InspectAnnotationPrivileged = "io.podman.annotations.privileged"
// InspectAnnotationPublishAll is used by Inspect to identify containers
// which have all the ports from their image published.
// It is expected to be a boolean, populated by one of
// InspectResponseTrue or InspectResponseFalse.
// If an annotation with this key is found in the OCI spec, it will be
// used in the output of Inspect().
InspectAnnotationPublishAll = "io.podman.annotations.publish-all"
// InspectAnnotationInit is used by Inspect to identify containers that
// mount an init binary in.
// It is expected to be a boolean, populated by one of
// InspectResponseTrue or InspectResponseFalse.
// If an annotation with this key is found in the OCI spec, it will be
// used in the output of Inspect().
InspectAnnotationInit = "io.podman.annotations.init"
// InspectAnnotationLabel is used by Inspect to identify containers with
// special SELinux-related settings. It is used to populate the output
// of the SecurityOpt setting.
// If an annotation with this key is found in the OCI spec, it will be
// used in the output of Inspect().
InspectAnnotationLabel = "io.podman.annotations.label"
// InspectAnnotationSeccomp is used by Inspect to identify containers
// with special Seccomp-related settings. It is used to populate the
// output of the SecurityOpt setting in Inspect.
// If an annotation with this key is found in the OCI spec, it will be
// used in the output of Inspect().
InspectAnnotationSeccomp = "io.podman.annotations.seccomp"
// InspectAnnotationApparmor is used by Inspect to identify containers
// with special Apparmor-related settings. It is used to populate the
// output of the SecurityOpt setting.
// If an annotation with this key is found in the OCI spec, it will be
// used in the output of Inspect().
InspectAnnotationApparmor = "io.podman.annotations.apparmor"
// InspectResponseTrue is a boolean True response for an inspect
// annotation.
InspectResponseTrue = "TRUE"
// InspectResponseFalse is a boolean False response for an inspect
// annotation.
InspectResponseFalse = "FALSE"
)

View File

@ -469,7 +469,7 @@ func generateKubeSecurityContext(c *Container) (*v1.SecurityContext, error) {
} }
var selinuxOpts v1.SELinuxOptions var selinuxOpts v1.SELinuxOptions
opts := strings.SplitN(c.config.Spec.Annotations[InspectAnnotationLabel], ":", 2) opts := strings.SplitN(c.config.Spec.Annotations[define.InspectAnnotationLabel], ":", 2)
if len(opts) == 2 { if len(opts) == 2 {
switch opts[0] { switch opts[0] {
case "type": case "type":

View File

@ -0,0 +1,38 @@
package libpod
import (
"net/http"
"github.com/containers/libpod/libpod"
"github.com/containers/libpod/pkg/api/handlers/utils"
"github.com/containers/libpod/pkg/domain/entities"
"github.com/containers/libpod/pkg/domain/infra/abi"
"github.com/gorilla/schema"
"github.com/pkg/errors"
)
func GenerateKube(w http.ResponseWriter, r *http.Request) {
runtime := r.Context().Value("runtime").(*libpod.Runtime)
decoder := r.Context().Value("decoder").(*schema.Decoder)
query := struct {
Service bool `schema:"service"`
}{
// Defaults would go here.
}
if err := decoder.Decode(&query, r.URL.Query()); err != nil {
utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest,
errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String()))
return
}
containerEngine := abi.ContainerEngine{Libpod: runtime}
options := entities.GenerateKubeOptions{Service: query.Service}
report, err := containerEngine.GenerateKube(r.Context(), utils.GetName(r), options)
if err != nil {
utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "error generating YAML"))
return
}
utils.WriteResponse(w, http.StatusOK, report.Reader)
}

View File

@ -0,0 +1,64 @@
package libpod
import (
"io"
"io/ioutil"
"net/http"
"os"
"github.com/containers/image/v5/types"
"github.com/containers/libpod/libpod"
"github.com/containers/libpod/pkg/api/handlers/utils"
"github.com/containers/libpod/pkg/domain/entities"
"github.com/containers/libpod/pkg/domain/infra/abi"
"github.com/gorilla/schema"
"github.com/pkg/errors"
)
func PlayKube(w http.ResponseWriter, r *http.Request) {
runtime := r.Context().Value("runtime").(*libpod.Runtime)
decoder := r.Context().Value("decoder").(*schema.Decoder)
query := struct {
Network string `schema:"reference"`
TLSVerify bool `schema:"tlsVerify"`
}{
TLSVerify: true,
}
if err := decoder.Decode(&query, r.URL.Query()); err != nil {
utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest,
errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String()))
return
}
// Fetch the K8s YAML file from the body, and copy it to a temp file.
tmpfile, err := ioutil.TempFile("", "libpod-play-kube.yml")
if err != nil {
utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "unable to create tempfile"))
return
}
defer os.Remove(tmpfile.Name())
if _, err := io.Copy(tmpfile, r.Body); err != nil && err != io.EOF {
tmpfile.Close()
utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "unable to write archive to temporary file"))
return
}
if err := tmpfile.Close(); err != nil {
utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "error closing temporary file"))
return
}
containerEngine := abi.ContainerEngine{Libpod: runtime}
options := entities.PlayKubeOptions{Network: query.Network, Quiet: true}
if _, found := r.URL.Query()["tlsVerify"]; found {
options.SkipTLSVerify = types.NewOptionalBool(!query.TLSVerify)
}
report, err := containerEngine.PlayKube(r.Context(), tmpfile.Name(), options)
if err != nil {
utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "error playing YAML file"))
return
}
utils.WriteResponse(w, http.StatusOK, report)
}

View File

@ -56,6 +56,13 @@ type swagLibpodImagesRemoveResponse struct {
Body handlers.LibpodImagesRemoveReport Body handlers.LibpodImagesRemoveReport
} }
// PlayKube response
// swagger:response DocsLibpodPlayKubeResponse
type swagLibpodPlayKubeResponse struct {
// in:body
Body entities.PlayKubeReport
}
// Delete response // Delete response
// swagger:response DocsImageDeleteResponse // swagger:response DocsImageDeleteResponse
type swagImageDeleteResponse struct { type swagImageDeleteResponse struct {

View File

@ -0,0 +1,41 @@
package server
import (
"net/http"
"github.com/containers/libpod/pkg/api/handlers/libpod"
"github.com/gorilla/mux"
)
func (s *APIServer) registerGenerateHandlers(r *mux.Router) error {
// swagger:operation GET /libpod/generate/{name:.*}/kube libpod libpodGenerateKube
// ---
// tags:
// - containers
// - pods
// summary: Play a Kubernetes YAML file.
// description: Create and run pods based on a Kubernetes YAML file (pod or service kind).
// parameters:
// - in: path
// name: name:.*
// type: string
// required: true
// description: Name or ID of the container or pod.
// - in: query
// name: service
// type: boolean
// default: false
// description: Generate YAML for a Kubernetes service object.
// produces:
// - application/json
// responses:
// 200:
// description: no error
// schema:
// type: string
// format: binary
// 500:
// $ref: "#/responses/InternalError"
r.HandleFunc(VersionedPath("/libpod/generate/{name:.*}/kube"), s.APIHandler(libpod.GenerateKube)).Methods(http.MethodGet)
return nil
}

View File

@ -0,0 +1,42 @@
package server
import (
"net/http"
"github.com/containers/libpod/pkg/api/handlers/libpod"
"github.com/gorilla/mux"
)
func (s *APIServer) registerPlayHandlers(r *mux.Router) error {
// swagger:operation POST /libpod/play/kube libpod libpodPlayKube
// ---
// tags:
// - containers
// - pods
// summary: Play a Kubernetes YAML file.
// description: Create and run pods based on a Kubernetes YAML file (pod or service kind).
// parameters:
// - in: query
// name: network
// type: string
// description: Connect the pod to this network.
// - in: query
// name: tlsVerify
// type: boolean
// default: true
// description: Require HTTPS and verify signatures when contating registries.
// - in: body
// name: request
// description: Kubernetes YAML file.
// schema:
// type: string
// produces:
// - application/json
// responses:
// 200:
// $ref: "#/responses/DocsLibpodPlayKubeResponse"
// 500:
// $ref: "#/responses/InternalError"
r.HandleFunc(VersionedPath("/libpod/play/kube"), s.APIHandler(libpod.PlayKube)).Methods(http.MethodPost)
return nil
}

View File

@ -98,12 +98,14 @@ func newServer(runtime *libpod.Runtime, duration time.Duration, listener *net.Li
server.registerDistributionHandlers, server.registerDistributionHandlers,
server.registerEventsHandlers, server.registerEventsHandlers,
server.registerExecHandlers, server.registerExecHandlers,
server.registerGenerateHandlers,
server.registerHealthCheckHandlers, server.registerHealthCheckHandlers,
server.registerImagesHandlers, server.registerImagesHandlers,
server.registerInfoHandlers, server.registerInfoHandlers,
server.registerManifestHandlers, server.registerManifestHandlers,
server.registerMonitorHandlers, server.registerMonitorHandlers,
server.registerPingHandlers, server.registerPingHandlers,
server.registerPlayHandlers,
server.registerPluginsHandlers, server.registerPluginsHandlers,
server.registerPodsHandlers, server.registerPodsHandlers,
server.RegisterSwaggerHandlers, server.RegisterSwaggerHandlers,

View File

@ -1,4 +1,32 @@
package generate package generate
func GenerateKube() {} import (
func GenerateSystemd() {} "context"
"net/http"
"net/url"
"strconv"
"github.com/containers/libpod/pkg/bindings"
"github.com/containers/libpod/pkg/domain/entities"
)
func GenerateKube(ctx context.Context, nameOrID string, options entities.GenerateKubeOptions) (*entities.GenerateKubeReport, error) {
conn, err := bindings.GetClient(ctx)
if err != nil {
return nil, err
}
params := url.Values{}
params.Set("service", strconv.FormatBool(options.Service))
response, err := conn.DoRequest(nil, http.MethodGet, "/generate/%s/kube", params, nameOrID)
if err != nil {
return nil, err
}
if response.StatusCode == http.StatusOK {
return &entities.GenerateKubeReport{Reader: response.Body}, nil
}
// Unpack the error.
return nil, response.Process(nil)
}

View File

@ -1,7 +1,43 @@
package play package play
import "github.com/containers/libpod/pkg/bindings" import (
"context"
"net/http"
"net/url"
"os"
"strconv"
func PlayKube() error { "github.com/containers/image/v5/types"
return bindings.ErrNotImplemented "github.com/containers/libpod/pkg/bindings"
"github.com/containers/libpod/pkg/domain/entities"
)
func PlayKube(ctx context.Context, path string, options entities.PlayKubeOptions) (*entities.PlayKubeReport, error) {
var report entities.PlayKubeReport
conn, err := bindings.GetClient(ctx)
if err != nil {
return nil, err
}
f, err := os.Open(path)
if err != nil {
return nil, err
}
defer f.Close()
params := url.Values{}
params.Set("network", options.Network)
if options.SkipTLSVerify != types.OptionalBoolUndefined {
params.Set("tlsVerify", strconv.FormatBool(options.SkipTLSVerify == types.OptionalBoolTrue))
}
response, err := conn.DoRequest(f, http.MethodPost, "/play/kube", params)
if err != nil {
return nil, err
}
if err := response.Process(&report); err != nil {
return nil, err
}
return &report, nil
} }

View File

@ -43,6 +43,7 @@ type ContainerEngine interface {
ContainerWait(ctx context.Context, namesOrIds []string, options WaitOptions) ([]WaitReport, error) ContainerWait(ctx context.Context, namesOrIds []string, options WaitOptions) ([]WaitReport, error)
Events(ctx context.Context, opts EventsOptions) error Events(ctx context.Context, opts EventsOptions) error
GenerateSystemd(ctx context.Context, nameOrID string, opts GenerateSystemdOptions) (*GenerateSystemdReport, error) GenerateSystemd(ctx context.Context, nameOrID string, opts GenerateSystemdOptions) (*GenerateSystemdReport, error)
GenerateKube(ctx context.Context, nameOrID string, opts GenerateKubeOptions) (*GenerateKubeReport, error)
SystemPrune(ctx context.Context, options SystemPruneOptions) (*SystemPruneReport, error) SystemPrune(ctx context.Context, options SystemPruneOptions) (*SystemPruneReport, error)
HealthCheckRun(ctx context.Context, nameOrId string, options HealthCheckOptions) (*define.HealthCheckResults, error) HealthCheckRun(ctx context.Context, nameOrId string, options HealthCheckOptions) (*define.HealthCheckResults, error)
Info(ctx context.Context) (*define.Info, error) Info(ctx context.Context) (*define.Info, error)
@ -50,6 +51,7 @@ type ContainerEngine interface {
NetworkInspect(ctx context.Context, namesOrIds []string, options NetworkInspectOptions) ([]NetworkInspectReport, error) NetworkInspect(ctx context.Context, namesOrIds []string, options NetworkInspectOptions) ([]NetworkInspectReport, error)
NetworkList(ctx context.Context, options NetworkListOptions) ([]*NetworkListReport, error) NetworkList(ctx context.Context, options NetworkListOptions) ([]*NetworkListReport, error)
NetworkRm(ctx context.Context, namesOrIds []string, options NetworkRmOptions) ([]*NetworkRmReport, error) NetworkRm(ctx context.Context, namesOrIds []string, options NetworkRmOptions) ([]*NetworkRmReport, error)
PlayKube(ctx context.Context, path string, opts PlayKubeOptions) (*PlayKubeReport, error)
PodCreate(ctx context.Context, opts PodCreateOptions) (*PodCreateReport, error) PodCreate(ctx context.Context, opts PodCreateOptions) (*PodCreateReport, error)
PodExists(ctx context.Context, nameOrId string) (*BoolReport, error) PodExists(ctx context.Context, nameOrId string) (*BoolReport, error)
PodInspect(ctx context.Context, options PodInspectOptions) (*PodInspectReport, error) PodInspect(ctx context.Context, options PodInspectOptions) (*PodInspectReport, error)

View File

@ -1,5 +1,7 @@
package entities package entities
import "io"
// GenerateSystemdOptions control the generation of systemd unit files. // GenerateSystemdOptions control the generation of systemd unit files.
type GenerateSystemdOptions struct { type GenerateSystemdOptions struct {
// Files - generate files instead of printing to stdout. // Files - generate files instead of printing to stdout.
@ -20,3 +22,15 @@ type GenerateSystemdReport struct {
// entire content. // entire content.
Output string Output string
} }
// GenerateKubeOptions control the generation of Kubernetes YAML files.
type GenerateKubeOptions struct {
// Service - generate YAML for a Kubernetes _service_ object.
Service bool
}
// GenerateKubeReport
type GenerateKubeReport struct {
// Reader - the io.Reader to reader the generated YAML file.
Reader io.Reader
}

View File

@ -0,0 +1,36 @@
package entities
import "github.com/containers/image/v5/types"
// PlayKubeOptions controls playing kube YAML files.
type PlayKubeOptions struct {
// Authfile - path to an authentication file.
Authfile string
// CertDir - to a directory containing TLS certifications and keys.
CertDir string
// Credentials - `username:password` for authentication against a
// container registry.
Credentials string
// Network - name of the CNI network to connect to.
Network string
// Quiet - suppress output when pulling images.
Quiet bool
// SignaturePolicy - path to a signature-policy file.
SignaturePolicy string
// SkipTLSVerify - skip https and certificate validation when
// contacting container registries.
SkipTLSVerify types.OptionalBool
// SeccompProfileRoot - path to a directory containing seccomp
// profiles.
SeccompProfileRoot string
}
// PlayKubeReport contains the results of running play kube.
type PlayKubeReport struct {
// Pod - the ID of the created pod.
Pod string
// Containers - the IDs of the containers running in the created pod.
Containers []string
// Logs - non-fatal erros and log messages while processing.
Logs []string
}

View File

@ -1,14 +1,18 @@
package abi package abi
import ( import (
"bytes"
"context" "context"
"fmt" "fmt"
"strings" "strings"
"github.com/containers/libpod/libpod" "github.com/containers/libpod/libpod"
"github.com/containers/libpod/libpod/define"
"github.com/containers/libpod/pkg/domain/entities" "github.com/containers/libpod/pkg/domain/entities"
"github.com/containers/libpod/pkg/systemd/generate" "github.com/containers/libpod/pkg/systemd/generate"
"github.com/ghodss/yaml"
"github.com/pkg/errors" "github.com/pkg/errors"
k8sAPI "k8s.io/api/core/v1"
) )
func (ic *ContainerEngine) GenerateSystemd(ctx context.Context, nameOrID string, options entities.GenerateSystemdOptions) (*entities.GenerateSystemdReport, error) { func (ic *ContainerEngine) GenerateSystemd(ctx context.Context, nameOrID string, options entities.GenerateSystemdOptions) (*entities.GenerateSystemdReport, error) {
@ -172,3 +176,84 @@ func generateServiceName(ctr *libpod.Container, pod *libpod.Pod, options entitie
} }
return ctrName, fmt.Sprintf("%s-%s", kind, name) return ctrName, fmt.Sprintf("%s-%s", kind, name)
} }
func (ic *ContainerEngine) GenerateKube(ctx context.Context, nameOrID string, options entities.GenerateKubeOptions) (*entities.GenerateKubeReport, error) {
var (
pod *libpod.Pod
podYAML *k8sAPI.Pod
err error
ctr *libpod.Container
servicePorts []k8sAPI.ServicePort
serviceYAML k8sAPI.Service
)
// Get the container in question.
ctr, err = ic.Libpod.LookupContainer(nameOrID)
if err != nil {
pod, err = ic.Libpod.LookupPod(nameOrID)
if err != nil {
return nil, err
}
podYAML, servicePorts, err = pod.GenerateForKube()
} else {
if len(ctr.Dependencies()) > 0 {
return nil, errors.Wrapf(define.ErrNotImplemented, "containers with dependencies")
}
podYAML, err = ctr.GenerateForKube()
}
if err != nil {
return nil, err
}
if options.Service {
serviceYAML = libpod.GenerateKubeServiceFromV1Pod(podYAML, servicePorts)
}
content, err := generateKubeOutput(podYAML, &serviceYAML)
if err != nil {
return nil, err
}
return &entities.GenerateKubeReport{Reader: bytes.NewReader(content)}, nil
}
func generateKubeOutput(podYAML *k8sAPI.Pod, serviceYAML *k8sAPI.Service) ([]byte, error) {
var (
output []byte
marshalledPod []byte
marshalledService []byte
err error
)
marshalledPod, err = yaml.Marshal(podYAML)
if err != nil {
return nil, err
}
if serviceYAML != nil {
marshalledService, err = yaml.Marshal(serviceYAML)
if err != nil {
return nil, err
}
}
header := `# Generation of Kubernetes YAML is still under development!
#
# Save the output of this file and use kubectl create -f to import
# it into Kubernetes.
#
# Created with podman-%s
`
podmanVersion, err := define.GetVersion()
if err != nil {
return nil, err
}
output = append(output, []byte(fmt.Sprintf(header, podmanVersion.Version))...)
output = append(output, marshalledPod...)
if serviceYAML != nil {
output = append(output, []byte("---\n")...)
output = append(output, marshalledService...)
}
return output, nil
}

View File

@ -0,0 +1,544 @@
package abi
import (
"context"
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"strings"
"github.com/containers/buildah/pkg/parse"
"github.com/containers/image/v5/types"
"github.com/containers/libpod/libpod"
"github.com/containers/libpod/libpod/image"
ann "github.com/containers/libpod/pkg/annotations"
"github.com/containers/libpod/pkg/domain/entities"
envLib "github.com/containers/libpod/pkg/env"
ns "github.com/containers/libpod/pkg/namespaces"
createconfig "github.com/containers/libpod/pkg/spec"
"github.com/containers/libpod/pkg/specgen/generate"
"github.com/containers/libpod/pkg/util"
"github.com/containers/storage"
"github.com/cri-o/ocicni/pkg/ocicni"
"github.com/docker/distribution/reference"
"github.com/ghodss/yaml"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
v1 "k8s.io/api/core/v1"
)
const (
// https://kubernetes.io/docs/concepts/storage/volumes/#hostpath
kubeDirectoryPermission = 0755
// https://kubernetes.io/docs/concepts/storage/volumes/#hostpath
kubeFilePermission = 0644
)
func (ic *ContainerEngine) PlayKube(ctx context.Context, path string, options entities.PlayKubeOptions) (*entities.PlayKubeReport, error) {
var (
containers []*libpod.Container
pod *libpod.Pod
podOptions []libpod.PodCreateOption
podYAML v1.Pod
registryCreds *types.DockerAuthConfig
writer io.Writer
report entities.PlayKubeReport
)
content, err := ioutil.ReadFile(path)
if err != nil {
return nil, err
}
if err := yaml.Unmarshal(content, &podYAML); err != nil {
return nil, errors.Wrapf(err, "unable to read %q as YAML", path)
}
if podYAML.Kind != "Pod" {
return nil, errors.Errorf("invalid YAML kind: %q. Pod is the only supported Kubernetes YAML kind", podYAML.Kind)
}
// check for name collision between pod and container
podName := podYAML.ObjectMeta.Name
if podName == "" {
return nil, errors.Errorf("pod does not have a name")
}
for _, n := range podYAML.Spec.Containers {
if n.Name == podName {
report.Logs = append(report.Logs,
fmt.Sprintf("a container exists with the same name (%q) as the pod in your YAML file; changing pod name to %s_pod\n", podName, podName))
podName = fmt.Sprintf("%s_pod", podName)
}
}
podOptions = append(podOptions, libpod.WithInfraContainer())
podOptions = append(podOptions, libpod.WithPodName(podName))
// TODO for now we just used the default kernel namespaces; we need to add/subtract this from yaml
hostname := podYAML.Spec.Hostname
if hostname == "" {
hostname = podName
}
podOptions = append(podOptions, libpod.WithPodHostname(hostname))
if podYAML.Spec.HostNetwork {
podOptions = append(podOptions, libpod.WithPodHostNetwork())
}
nsOptions, err := generate.GetNamespaceOptions(strings.Split(createconfig.DefaultKernelNamespaces, ","))
if err != nil {
return nil, err
}
podOptions = append(podOptions, nsOptions...)
podPorts := getPodPorts(podYAML.Spec.Containers)
podOptions = append(podOptions, libpod.WithInfraContainerPorts(podPorts))
if options.Network != "" {
switch strings.ToLower(options.Network) {
case "bridge", "host":
return nil, errors.Errorf("invalid value passed to --network: bridge or host networking must be configured in YAML")
case "":
return nil, errors.Errorf("invalid value passed to --network: must provide a comma-separated list of CNI networks")
default:
// We'll assume this is a comma-separated list of CNI
// networks.
networks := strings.Split(options.Network, ",")
logrus.Debugf("Pod joining CNI networks: %v", networks)
podOptions = append(podOptions, libpod.WithPodNetworks(networks))
}
}
// Create the Pod
pod, err = ic.Libpod.NewPod(ctx, podOptions...)
if err != nil {
return nil, err
}
podInfraID, err := pod.InfraContainerID()
if err != nil {
return nil, err
}
hasUserns := false
if podInfraID != "" {
podCtr, err := ic.Libpod.GetContainer(podInfraID)
if err != nil {
return nil, err
}
mappings, err := podCtr.IDMappings()
if err != nil {
return nil, err
}
hasUserns = len(mappings.UIDMap) > 0
}
namespaces := map[string]string{
// Disabled during code review per mheon
//"pid": fmt.Sprintf("container:%s", podInfraID),
"net": fmt.Sprintf("container:%s", podInfraID),
"ipc": fmt.Sprintf("container:%s", podInfraID),
"uts": fmt.Sprintf("container:%s", podInfraID),
}
if hasUserns {
namespaces["user"] = fmt.Sprintf("container:%s", podInfraID)
}
if !options.Quiet {
writer = os.Stderr
}
dockerRegistryOptions := image.DockerRegistryOptions{
DockerRegistryCreds: registryCreds,
DockerCertPath: options.CertDir,
DockerInsecureSkipTLSVerify: options.SkipTLSVerify,
}
// map from name to mount point
volumes := make(map[string]string)
for _, volume := range podYAML.Spec.Volumes {
hostPath := volume.VolumeSource.HostPath
if hostPath == nil {
return nil, errors.Errorf("HostPath is currently the only supported VolumeSource")
}
if hostPath.Type != nil {
switch *hostPath.Type {
case v1.HostPathDirectoryOrCreate:
if _, err := os.Stat(hostPath.Path); os.IsNotExist(err) {
if err := os.Mkdir(hostPath.Path, kubeDirectoryPermission); err != nil {
return nil, errors.Errorf("Error creating HostPath %s at %s", volume.Name, hostPath.Path)
}
}
// Label a newly created volume
if err := libpod.LabelVolumePath(hostPath.Path); err != nil {
return nil, errors.Wrapf(err, "Error giving %s a label", hostPath.Path)
}
case v1.HostPathFileOrCreate:
if _, err := os.Stat(hostPath.Path); os.IsNotExist(err) {
f, err := os.OpenFile(hostPath.Path, os.O_RDONLY|os.O_CREATE, kubeFilePermission)
if err != nil {
return nil, errors.Errorf("Error creating HostPath %s at %s", volume.Name, hostPath.Path)
}
if err := f.Close(); err != nil {
logrus.Warnf("Error in closing newly created HostPath file: %v", err)
}
}
// unconditionally label a newly created volume
if err := libpod.LabelVolumePath(hostPath.Path); err != nil {
return nil, errors.Wrapf(err, "Error giving %s a label", hostPath.Path)
}
case v1.HostPathDirectory:
case v1.HostPathFile:
case v1.HostPathUnset:
// do nothing here because we will verify the path exists in validateVolumeHostDir
break
default:
return nil, errors.Errorf("Directories are the only supported HostPath type")
}
}
if err := parse.ValidateVolumeHostDir(hostPath.Path); err != nil {
return nil, errors.Wrapf(err, "Error in parsing HostPath in YAML")
}
volumes[volume.Name] = hostPath.Path
}
seccompPaths, err := initializeSeccompPaths(podYAML.ObjectMeta.Annotations, options.SeccompProfileRoot)
if err != nil {
return nil, err
}
for _, container := range podYAML.Spec.Containers {
pullPolicy := util.PullImageMissing
if len(container.ImagePullPolicy) > 0 {
pullPolicy, err = util.ValidatePullType(string(container.ImagePullPolicy))
if err != nil {
return nil, err
}
}
named, err := reference.ParseNormalizedNamed(container.Image)
if err != nil {
return nil, err
}
// In kube, if the image is tagged with latest, it should always pull
if tagged, isTagged := named.(reference.NamedTagged); isTagged {
if tagged.Tag() == image.LatestTag {
pullPolicy = util.PullImageAlways
}
}
newImage, err := ic.Libpod.ImageRuntime().New(ctx, container.Image, options.SignaturePolicy, options.Authfile, writer, &dockerRegistryOptions, image.SigningOptions{}, nil, pullPolicy)
if err != nil {
return nil, err
}
conf, err := kubeContainerToCreateConfig(ctx, container, ic.Libpod, newImage, namespaces, volumes, pod.ID(), podInfraID, seccompPaths)
if err != nil {
return nil, err
}
ctr, err := createconfig.CreateContainerFromCreateConfig(ic.Libpod, conf, ctx, pod)
if err != nil {
return nil, err
}
containers = append(containers, ctr)
}
// start the containers
for _, ctr := range containers {
if err := ctr.Start(ctx, true); err != nil {
// Making this a hard failure here to avoid a mess
// the other containers are in created status
return nil, err
}
}
report.Pod = pod.ID()
for _, ctr := range containers {
report.Containers = append(report.Containers, ctr.ID())
}
return &report, nil
}
// getPodPorts converts a slice of kube container descriptions to an
// array of ocicni portmapping descriptions usable in libpod
func getPodPorts(containers []v1.Container) []ocicni.PortMapping {
var infraPorts []ocicni.PortMapping
for _, container := range containers {
for _, p := range container.Ports {
if p.HostPort != 0 && p.ContainerPort == 0 {
p.ContainerPort = p.HostPort
}
if p.Protocol == "" {
p.Protocol = "tcp"
}
portBinding := ocicni.PortMapping{
HostPort: p.HostPort,
ContainerPort: p.ContainerPort,
Protocol: strings.ToLower(string(p.Protocol)),
}
if p.HostIP != "" {
logrus.Debug("HostIP on port bindings is not supported")
}
// only hostPort is utilized in podman context, all container ports
// are accessible inside the shared network namespace
if p.HostPort != 0 {
infraPorts = append(infraPorts, portBinding)
}
}
}
return infraPorts
}
func setupSecurityContext(securityConfig *createconfig.SecurityConfig, userConfig *createconfig.UserConfig, containerYAML v1.Container) {
if containerYAML.SecurityContext == nil {
return
}
if containerYAML.SecurityContext.ReadOnlyRootFilesystem != nil {
securityConfig.ReadOnlyRootfs = *containerYAML.SecurityContext.ReadOnlyRootFilesystem
}
if containerYAML.SecurityContext.Privileged != nil {
securityConfig.Privileged = *containerYAML.SecurityContext.Privileged
}
if containerYAML.SecurityContext.AllowPrivilegeEscalation != nil {
securityConfig.NoNewPrivs = !*containerYAML.SecurityContext.AllowPrivilegeEscalation
}
if seopt := containerYAML.SecurityContext.SELinuxOptions; seopt != nil {
if seopt.User != "" {
securityConfig.SecurityOpts = append(securityConfig.SecurityOpts, fmt.Sprintf("label=user:%s", seopt.User))
securityConfig.LabelOpts = append(securityConfig.LabelOpts, fmt.Sprintf("user:%s", seopt.User))
}
if seopt.Role != "" {
securityConfig.SecurityOpts = append(securityConfig.SecurityOpts, fmt.Sprintf("label=role:%s", seopt.Role))
securityConfig.LabelOpts = append(securityConfig.LabelOpts, fmt.Sprintf("role:%s", seopt.Role))
}
if seopt.Type != "" {
securityConfig.SecurityOpts = append(securityConfig.SecurityOpts, fmt.Sprintf("label=type:%s", seopt.Type))
securityConfig.LabelOpts = append(securityConfig.LabelOpts, fmt.Sprintf("type:%s", seopt.Type))
}
if seopt.Level != "" {
securityConfig.SecurityOpts = append(securityConfig.SecurityOpts, fmt.Sprintf("label=level:%s", seopt.Level))
securityConfig.LabelOpts = append(securityConfig.LabelOpts, fmt.Sprintf("level:%s", seopt.Level))
}
}
if caps := containerYAML.SecurityContext.Capabilities; caps != nil {
for _, capability := range caps.Add {
securityConfig.CapAdd = append(securityConfig.CapAdd, string(capability))
}
for _, capability := range caps.Drop {
securityConfig.CapDrop = append(securityConfig.CapDrop, string(capability))
}
}
if containerYAML.SecurityContext.RunAsUser != nil {
userConfig.User = fmt.Sprintf("%d", *containerYAML.SecurityContext.RunAsUser)
}
if containerYAML.SecurityContext.RunAsGroup != nil {
if userConfig.User == "" {
userConfig.User = "0"
}
userConfig.User = fmt.Sprintf("%s:%d", userConfig.User, *containerYAML.SecurityContext.RunAsGroup)
}
}
// kubeContainerToCreateConfig takes a v1.Container and returns a createconfig describing a container
func kubeContainerToCreateConfig(ctx context.Context, containerYAML v1.Container, runtime *libpod.Runtime, newImage *image.Image, namespaces map[string]string, volumes map[string]string, podID, infraID string, seccompPaths *kubeSeccompPaths) (*createconfig.CreateConfig, error) {
var (
containerConfig createconfig.CreateConfig
pidConfig createconfig.PidConfig
networkConfig createconfig.NetworkConfig
cgroupConfig createconfig.CgroupConfig
utsConfig createconfig.UtsConfig
ipcConfig createconfig.IpcConfig
userConfig createconfig.UserConfig
securityConfig createconfig.SecurityConfig
)
// The default for MemorySwappiness is -1, not 0
containerConfig.Resources.MemorySwappiness = -1
containerConfig.Image = containerYAML.Image
containerConfig.ImageID = newImage.ID()
containerConfig.Name = containerYAML.Name
containerConfig.Tty = containerYAML.TTY
containerConfig.Pod = podID
imageData, _ := newImage.Inspect(ctx)
userConfig.User = "0"
if imageData != nil {
userConfig.User = imageData.Config.User
}
setupSecurityContext(&securityConfig, &userConfig, containerYAML)
securityConfig.SeccompProfilePath = seccompPaths.findForContainer(containerConfig.Name)
containerConfig.Command = []string{}
if imageData != nil && imageData.Config != nil {
containerConfig.Command = append(containerConfig.Command, imageData.Config.Entrypoint...)
}
if len(containerYAML.Command) != 0 {
containerConfig.Command = append(containerConfig.Command, containerYAML.Command...)
} else if imageData != nil && imageData.Config != nil {
containerConfig.Command = append(containerConfig.Command, imageData.Config.Cmd...)
}
if imageData != nil && len(containerConfig.Command) == 0 {
return nil, errors.Errorf("No command specified in container YAML or as CMD or ENTRYPOINT in this image for %s", containerConfig.Name)
}
containerConfig.UserCommand = containerConfig.Command
containerConfig.StopSignal = 15
containerConfig.WorkDir = "/"
if imageData != nil {
// FIXME,
// we are currently ignoring imageData.Config.ExposedPorts
containerConfig.BuiltinImgVolumes = imageData.Config.Volumes
if imageData.Config.WorkingDir != "" {
containerConfig.WorkDir = imageData.Config.WorkingDir
}
containerConfig.Labels = imageData.Config.Labels
if imageData.Config.StopSignal != "" {
stopSignal, err := util.ParseSignal(imageData.Config.StopSignal)
if err != nil {
return nil, err
}
containerConfig.StopSignal = stopSignal
}
}
if containerYAML.WorkingDir != "" {
containerConfig.WorkDir = containerYAML.WorkingDir
}
// If the user does not pass in ID mappings, just set to basics
if userConfig.IDMappings == nil {
userConfig.IDMappings = &storage.IDMappingOptions{}
}
networkConfig.NetMode = ns.NetworkMode(namespaces["net"])
ipcConfig.IpcMode = ns.IpcMode(namespaces["ipc"])
utsConfig.UtsMode = ns.UTSMode(namespaces["uts"])
// disabled in code review per mheon
//containerConfig.PidMode = ns.PidMode(namespaces["pid"])
userConfig.UsernsMode = ns.UsernsMode(namespaces["user"])
if len(containerConfig.WorkDir) == 0 {
containerConfig.WorkDir = "/"
}
containerConfig.Pid = pidConfig
containerConfig.Network = networkConfig
containerConfig.Uts = utsConfig
containerConfig.Ipc = ipcConfig
containerConfig.Cgroup = cgroupConfig
containerConfig.User = userConfig
containerConfig.Security = securityConfig
annotations := make(map[string]string)
if infraID != "" {
annotations[ann.SandboxID] = infraID
annotations[ann.ContainerType] = ann.ContainerTypeContainer
}
containerConfig.Annotations = annotations
// Environment Variables
envs := map[string]string{}
if imageData != nil {
imageEnv, err := envLib.ParseSlice(imageData.Config.Env)
if err != nil {
return nil, errors.Wrap(err, "error parsing image environment variables")
}
envs = imageEnv
}
for _, e := range containerYAML.Env {
envs[e.Name] = e.Value
}
containerConfig.Env = envs
for _, volume := range containerYAML.VolumeMounts {
hostPath, exists := volumes[volume.Name]
if !exists {
return nil, errors.Errorf("Volume mount %s specified for container but not configured in volumes", volume.Name)
}
if err := parse.ValidateVolumeCtrDir(volume.MountPath); err != nil {
return nil, errors.Wrapf(err, "error in parsing MountPath")
}
containerConfig.Volumes = append(containerConfig.Volumes, fmt.Sprintf("%s:%s", hostPath, volume.MountPath))
}
return &containerConfig, nil
}
// kubeSeccompPaths holds information about a pod YAML's seccomp configuration
// it holds both container and pod seccomp paths
type kubeSeccompPaths struct {
containerPaths map[string]string
podPath string
}
// findForContainer checks whether a container has a seccomp path configured for it
// if not, it returns the podPath, which should always have a value
func (k *kubeSeccompPaths) findForContainer(ctrName string) string {
if path, ok := k.containerPaths[ctrName]; ok {
return path
}
return k.podPath
}
// initializeSeccompPaths takes annotations from the pod object metadata and finds annotations pertaining to seccomp
// it parses both pod and container level
// if the annotation is of the form "localhost/%s", the seccomp profile will be set to profileRoot/%s
func initializeSeccompPaths(annotations map[string]string, profileRoot string) (*kubeSeccompPaths, error) {
seccompPaths := &kubeSeccompPaths{containerPaths: make(map[string]string)}
var err error
if annotations != nil {
for annKeyValue, seccomp := range annotations {
// check if it is prefaced with container.seccomp.security.alpha.kubernetes.io/
prefixAndCtr := strings.Split(annKeyValue, "/")
if prefixAndCtr[0]+"/" != v1.SeccompContainerAnnotationKeyPrefix {
continue
} else if len(prefixAndCtr) != 2 {
// this could be caused by a user inputting either of
// container.seccomp.security.alpha.kubernetes.io{,/}
// both of which are invalid
return nil, errors.Errorf("Invalid seccomp path: %s", prefixAndCtr[0])
}
path, err := verifySeccompPath(seccomp, profileRoot)
if err != nil {
return nil, err
}
seccompPaths.containerPaths[prefixAndCtr[1]] = path
}
podSeccomp, ok := annotations[v1.SeccompPodAnnotationKey]
if ok {
seccompPaths.podPath, err = verifySeccompPath(podSeccomp, profileRoot)
} else {
seccompPaths.podPath, err = libpod.DefaultSeccompPath()
}
if err != nil {
return nil, err
}
}
return seccompPaths, nil
}
// verifySeccompPath takes a path and checks whether it is a default, unconfined, or a path
// the available options are parsed as defined in https://kubernetes.io/docs/concepts/policy/pod-security-policy/#seccomp
func verifySeccompPath(path string, profileRoot string) (string, error) {
switch path {
case v1.DeprecatedSeccompProfileDockerDefault:
fallthrough
case v1.SeccompProfileRuntimeDefault:
return libpod.DefaultSeccompPath()
case "unconfined":
return path, nil
default:
parts := strings.Split(path, "/")
if parts[0] == "localhost" {
return filepath.Join(profileRoot, parts[1]), nil
}
return "", errors.Errorf("invalid seccomp path: %s", path)
}
}

View File

@ -3,6 +3,7 @@ package tunnel
import ( import (
"context" "context"
"github.com/containers/libpod/pkg/bindings/generate"
"github.com/containers/libpod/pkg/domain/entities" "github.com/containers/libpod/pkg/domain/entities"
"github.com/pkg/errors" "github.com/pkg/errors"
) )
@ -10,3 +11,7 @@ import (
func (ic *ContainerEngine) GenerateSystemd(ctx context.Context, nameOrID string, options entities.GenerateSystemdOptions) (*entities.GenerateSystemdReport, error) { func (ic *ContainerEngine) GenerateSystemd(ctx context.Context, nameOrID string, options entities.GenerateSystemdOptions) (*entities.GenerateSystemdReport, error) {
return nil, errors.New("not implemented for tunnel") return nil, errors.New("not implemented for tunnel")
} }
func (ic *ContainerEngine) GenerateKube(ctx context.Context, nameOrID string, options entities.GenerateKubeOptions) (*entities.GenerateKubeReport, error) {
return generate.GenerateKube(ic.ClientCxt, nameOrID, options)
}

View File

@ -0,0 +1,12 @@
package tunnel
import (
"context"
"github.com/containers/libpod/pkg/bindings/play"
"github.com/containers/libpod/pkg/domain/entities"
)
func (ic *ContainerEngine) PlayKube(ctx context.Context, path string, options entities.PlayKubeOptions) (*entities.PlayKubeReport, error) {
return play.PlayKube(ic.ClientCxt, path, options)
}

View File

@ -17,6 +17,10 @@ import (
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
) )
// DefaultKernelNamespaces is a comma-separated list of default kernel
// namespaces.
const DefaultKernelNamespaces = "cgroup,ipc,net,uts"
// ToCreateOptions converts the input to a slice of container create options. // ToCreateOptions converts the input to a slice of container create options.
func (c *NetworkConfig) ToCreateOptions(runtime *libpod.Runtime, userns *UserConfig) ([]libpod.CtrCreateOption, error) { func (c *NetworkConfig) ToCreateOptions(runtime *libpod.Runtime, userns *UserConfig) ([]libpod.CtrCreateOption, error) {
var portBindings []ocicni.PortMapping var portBindings []ocicni.PortMapping
@ -154,9 +158,9 @@ func (c *NetworkConfig) ConfigureGenerator(g *generate.Generator) error {
} }
if c.PublishAll { if c.PublishAll {
g.Config.Annotations[libpod.InspectAnnotationPublishAll] = libpod.InspectResponseTrue g.Config.Annotations[define.InspectAnnotationPublishAll] = define.InspectResponseTrue
} else { } else {
g.Config.Annotations[libpod.InspectAnnotationPublishAll] = libpod.InspectResponseFalse g.Config.Annotations[define.InspectAnnotationPublishAll] = define.InspectResponseFalse
} }
return nil return nil

View File

@ -6,6 +6,7 @@ import (
"github.com/containers/common/pkg/capabilities" "github.com/containers/common/pkg/capabilities"
"github.com/containers/libpod/libpod" "github.com/containers/libpod/libpod"
"github.com/containers/libpod/libpod/define"
"github.com/containers/libpod/pkg/util" "github.com/containers/libpod/pkg/util"
"github.com/opencontainers/runtime-tools/generate" "github.com/opencontainers/runtime-tools/generate"
"github.com/opencontainers/selinux/go-selinux/label" "github.com/opencontainers/selinux/go-selinux/label"
@ -184,11 +185,11 @@ func (c *SecurityConfig) ConfigureGenerator(g *generate.Generator, user *UserCon
} }
switch splitOpt[0] { switch splitOpt[0] {
case "label": case "label":
configSpec.Annotations[libpod.InspectAnnotationLabel] = splitOpt[1] configSpec.Annotations[define.InspectAnnotationLabel] = splitOpt[1]
case "seccomp": case "seccomp":
configSpec.Annotations[libpod.InspectAnnotationSeccomp] = splitOpt[1] configSpec.Annotations[define.InspectAnnotationSeccomp] = splitOpt[1]
case "apparmor": case "apparmor":
configSpec.Annotations[libpod.InspectAnnotationApparmor] = splitOpt[1] configSpec.Annotations[define.InspectAnnotationApparmor] = splitOpt[1]
} }
} }

View File

@ -7,6 +7,7 @@ import (
cconfig "github.com/containers/common/pkg/config" cconfig "github.com/containers/common/pkg/config"
"github.com/containers/common/pkg/sysinfo" "github.com/containers/common/pkg/sysinfo"
"github.com/containers/libpod/libpod" "github.com/containers/libpod/libpod"
"github.com/containers/libpod/libpod/define"
"github.com/containers/libpod/pkg/cgroups" "github.com/containers/libpod/pkg/cgroups"
"github.com/containers/libpod/pkg/env" "github.com/containers/libpod/pkg/env"
"github.com/containers/libpod/pkg/rootless" "github.com/containers/libpod/pkg/rootless"
@ -436,29 +437,29 @@ func (config *CreateConfig) createConfigToOCISpec(runtime *libpod.Runtime, userM
} }
if config.CidFile != "" { if config.CidFile != "" {
configSpec.Annotations[libpod.InspectAnnotationCIDFile] = config.CidFile configSpec.Annotations[define.InspectAnnotationCIDFile] = config.CidFile
} }
if config.Rm { if config.Rm {
configSpec.Annotations[libpod.InspectAnnotationAutoremove] = libpod.InspectResponseTrue configSpec.Annotations[define.InspectAnnotationAutoremove] = define.InspectResponseTrue
} else { } else {
configSpec.Annotations[libpod.InspectAnnotationAutoremove] = libpod.InspectResponseFalse configSpec.Annotations[define.InspectAnnotationAutoremove] = define.InspectResponseFalse
} }
if len(config.VolumesFrom) > 0 { if len(config.VolumesFrom) > 0 {
configSpec.Annotations[libpod.InspectAnnotationVolumesFrom] = strings.Join(config.VolumesFrom, ",") configSpec.Annotations[define.InspectAnnotationVolumesFrom] = strings.Join(config.VolumesFrom, ",")
} }
if config.Security.Privileged { if config.Security.Privileged {
configSpec.Annotations[libpod.InspectAnnotationPrivileged] = libpod.InspectResponseTrue configSpec.Annotations[define.InspectAnnotationPrivileged] = define.InspectResponseTrue
} else { } else {
configSpec.Annotations[libpod.InspectAnnotationPrivileged] = libpod.InspectResponseFalse configSpec.Annotations[define.InspectAnnotationPrivileged] = define.InspectResponseFalse
} }
if config.Init { if config.Init {
configSpec.Annotations[libpod.InspectAnnotationInit] = libpod.InspectResponseTrue configSpec.Annotations[define.InspectAnnotationInit] = define.InspectResponseTrue
} else { } else {
configSpec.Annotations[libpod.InspectAnnotationInit] = libpod.InspectResponseFalse configSpec.Annotations[define.InspectAnnotationInit] = define.InspectResponseFalse
} }
return configSpec, nil return configSpec, nil

View File

@ -438,9 +438,9 @@ func specConfigureNamespaces(s *specgen.SpecGenerator, g *generate.Generator, rt
g.Config.Annotations = make(map[string]string) g.Config.Annotations = make(map[string]string)
} }
if s.PublishExposedPorts { if s.PublishExposedPorts {
g.Config.Annotations[libpod.InspectAnnotationPublishAll] = libpod.InspectResponseTrue g.Config.Annotations[define.InspectAnnotationPublishAll] = define.InspectResponseTrue
} else { } else {
g.Config.Annotations[libpod.InspectAnnotationPublishAll] = libpod.InspectResponseFalse g.Config.Annotations[define.InspectAnnotationPublishAll] = define.InspectResponseFalse
} }
return nil return nil

View File

@ -6,6 +6,7 @@ import (
"github.com/containers/common/pkg/config" "github.com/containers/common/pkg/config"
"github.com/containers/libpod/libpod" "github.com/containers/libpod/libpod"
"github.com/containers/libpod/libpod/define"
"github.com/containers/libpod/libpod/image" "github.com/containers/libpod/libpod/image"
"github.com/containers/libpod/pkg/rootless" "github.com/containers/libpod/pkg/rootless"
"github.com/containers/libpod/pkg/specgen" "github.com/containers/libpod/pkg/specgen"
@ -327,19 +328,19 @@ func SpecGenToOCI(ctx context.Context, s *specgen.SpecGenerator, rt *libpod.Runt
//} //}
if s.Remove { if s.Remove {
configSpec.Annotations[libpod.InspectAnnotationAutoremove] = libpod.InspectResponseTrue configSpec.Annotations[define.InspectAnnotationAutoremove] = define.InspectResponseTrue
} else { } else {
configSpec.Annotations[libpod.InspectAnnotationAutoremove] = libpod.InspectResponseFalse configSpec.Annotations[define.InspectAnnotationAutoremove] = define.InspectResponseFalse
} }
if len(s.VolumesFrom) > 0 { if len(s.VolumesFrom) > 0 {
configSpec.Annotations[libpod.InspectAnnotationVolumesFrom] = strings.Join(s.VolumesFrom, ",") configSpec.Annotations[define.InspectAnnotationVolumesFrom] = strings.Join(s.VolumesFrom, ",")
} }
if s.Privileged { if s.Privileged {
configSpec.Annotations[libpod.InspectAnnotationPrivileged] = libpod.InspectResponseTrue configSpec.Annotations[define.InspectAnnotationPrivileged] = define.InspectResponseTrue
} else { } else {
configSpec.Annotations[libpod.InspectAnnotationPrivileged] = libpod.InspectResponseFalse configSpec.Annotations[define.InspectAnnotationPrivileged] = define.InspectResponseFalse
} }
// TODO Init might not make it into the specgen and therefore is not available here. We should deal // TODO Init might not make it into the specgen and therefore is not available here. We should deal

View File

@ -21,7 +21,6 @@ var _ = Describe("Podman generate kube", func() {
) )
BeforeEach(func() { BeforeEach(func() {
Skip(v2fail)
tempdir, err = CreateTempDirInTempDir() tempdir, err = CreateTempDirInTempDir()
if err != nil { if err != nil {
os.Exit(1) os.Exit(1)

View File

@ -217,7 +217,6 @@ var _ = Describe("Podman generate kube", func() {
) )
BeforeEach(func() { BeforeEach(func() {
Skip(v2fail)
tempdir, err = CreateTempDirInTempDir() tempdir, err = CreateTempDirInTempDir()
if err != nil { if err != nil {
os.Exit(1) os.Exit(1)