diff options
author | baude <bbaude@redhat.com> | 2018-12-09 14:26:21 -0600 |
---|---|---|
committer | baude <bbaude@redhat.com> | 2018-12-19 14:20:55 -0600 |
commit | 9b03cacc87c4d59fc301c21ef73ddc301ec753fb (patch) | |
tree | ec7318272db56a8a3ba11b5ac1b01d262b21742c /cmd/podman | |
parent | eddfe6ba628d17435559ba32a8ef748c386105aa (diff) | |
download | podman-9b03cacc87c4d59fc301c21ef73ddc301ec753fb.tar.gz podman-9b03cacc87c4d59fc301c21ef73ddc301ec753fb.tar.bz2 podman-9b03cacc87c4d59fc301c21ef73ddc301ec753fb.zip |
Add Play
podman play kube adds the ability for the user to recreate pods and containers
from a Kubernetes YAML file in libpod.
Signed-off-by: baude <bbaude@redhat.com>
Diffstat (limited to 'cmd/podman')
-rw-r--r-- | cmd/podman/create.go | 63 | ||||
-rw-r--r-- | cmd/podman/generate_kube.go | 1 | ||||
-rw-r--r-- | cmd/podman/main.go | 1 | ||||
-rw-r--r-- | cmd/podman/play.go | 23 | ||||
-rw-r--r-- | cmd/podman/play_kube.go | 245 | ||||
-rw-r--r-- | cmd/podman/varlink/io.podman.varlink | 12 |
6 files changed, 316 insertions, 29 deletions
diff --git a/cmd/podman/create.go b/cmd/podman/create.go index 870eb28d6..2b31a6423 100644 --- a/cmd/podman/create.go +++ b/cmd/podman/create.go @@ -146,37 +146,10 @@ func createContainer(c *cli.Context, runtime *libpod.Runtime) (*libpod.Container return nil, nil, err } - runtimeSpec, err := cc.CreateConfigToOCISpec(createConfig) - if err != nil { - return nil, nil, err - } - - options, err := createConfig.GetContainerCreateOptions(runtime) - if err != nil { - return nil, nil, err - } - - became, ret, err := joinOrCreateRootlessUserNamespace(createConfig, runtime) + ctr, err := createContainerFromCreateConfig(runtime, createConfig, ctx) if err != nil { return nil, nil, err } - if became { - os.Exit(ret) - } - - ctr, err := runtime.NewContainer(ctx, runtimeSpec, options...) - if err != nil { - return nil, nil, err - } - - createConfigJSON, err := json.Marshal(createConfig) - if err != nil { - return nil, nil, err - } - if err := ctr.AddArtifact("create-config", createConfigJSON); err != nil { - return nil, nil, err - } - if cidFile != nil { _, err = cidFile.WriteString(ctr.ID()) if err != nil { @@ -913,3 +886,37 @@ func joinOrCreateRootlessUserNamespace(createConfig *cc.CreateConfig, runtime *l } return rootless.BecomeRootInUserNS() } + +func createContainerFromCreateConfig(r *libpod.Runtime, createConfig *cc.CreateConfig, ctx context.Context) (*libpod.Container, error) { + runtimeSpec, err := cc.CreateConfigToOCISpec(createConfig) + if err != nil { + return nil, err + } + + options, err := createConfig.GetContainerCreateOptions(r) + if err != nil { + return nil, err + } + + became, ret, err := joinOrCreateRootlessUserNamespace(createConfig, r) + if err != nil { + return nil, err + } + if became { + os.Exit(ret) + } + + ctr, err := r.NewContainer(ctx, runtimeSpec, options...) + if err != nil { + return nil, err + } + + createConfigJSON, err := json.Marshal(createConfig) + if err != nil { + return nil, err + } + if err := ctr.AddArtifact("create-config", createConfigJSON); err != nil { + return nil, err + } + return ctr, nil +} diff --git a/cmd/podman/generate_kube.go b/cmd/podman/generate_kube.go index 8f2f0de32..f63bd431b 100644 --- a/cmd/podman/generate_kube.go +++ b/cmd/podman/generate_kube.go @@ -33,7 +33,6 @@ var ( } ) -// generateKubeYAMLCmdgenerates or replays kube func generateKubeYAMLCmd(c *cli.Context) error { var ( podYAML *v1.Pod diff --git a/cmd/podman/main.go b/cmd/podman/main.go index 2db6c5dec..f47a75761 100644 --- a/cmd/podman/main.go +++ b/cmd/podman/main.go @@ -90,6 +90,7 @@ func main() { portCommand, pullCommand, pushCommand, + playCommand, restartCommand, rmCommand, rmiCommand, diff --git a/cmd/podman/play.go b/cmd/podman/play.go new file mode 100644 index 000000000..adbab3480 --- /dev/null +++ b/cmd/podman/play.go @@ -0,0 +1,23 @@ +package main + +import ( + "github.com/urfave/cli" +) + +var ( + playSubCommands = []cli.Command{ + playKubeCommand, + } + + playDescription = "Play a pod and its containers from a structured file." + playCommand = cli.Command{ + Name: "play", + Usage: "play a container or pod", + Description: playDescription, + ArgsUsage: "", + Subcommands: playSubCommands, + UseShortOptionHandling: true, + OnUsageError: usageErrorHandler, + Hidden: true, + } +) diff --git a/cmd/podman/play_kube.go b/cmd/podman/play_kube.go new file mode 100644 index 000000000..f165c5f0f --- /dev/null +++ b/cmd/podman/play_kube.go @@ -0,0 +1,245 @@ +package main + +import ( + "fmt" + "io" + "io/ioutil" + "os" + "strings" + + "github.com/containers/image/types" + "github.com/containers/libpod/cmd/podman/libpodruntime" + "github.com/containers/libpod/cmd/podman/shared" + "github.com/containers/libpod/libpod" + image2 "github.com/containers/libpod/libpod/image" + ns "github.com/containers/libpod/pkg/namespaces" + "github.com/containers/libpod/pkg/rootless" + "github.com/containers/libpod/pkg/spec" + "github.com/containers/storage" + "github.com/cri-o/ocicni/pkg/ocicni" + "github.com/ghodss/yaml" + "github.com/pkg/errors" + "github.com/sirupsen/logrus" + "github.com/urfave/cli" + "k8s.io/api/core/v1" +) + +var ( + playKubeFlags = []cli.Flag{ + cli.StringFlag{ + Name: "authfile", + Usage: "Path of the authentication file. Default is ${XDG_RUNTIME_DIR}/containers/auth.json. Use REGISTRY_AUTH_FILE environment variable to override. ", + }, + cli.StringFlag{ + Name: "cert-dir", + Usage: "`pathname` of a directory containing TLS certificates and keys", + }, + cli.StringFlag{ + Name: "creds", + Usage: "`credentials` (USERNAME:PASSWORD) to use for authenticating to a registry", + }, + cli.BoolFlag{ + Name: "quiet, q", + Usage: "Suppress output information when pulling images", + }, + cli.StringFlag{ + Name: "signature-policy", + Usage: "`pathname` of signature policy file (not usually used)", + }, + cli.BoolTFlag{ + Name: "tls-verify", + Usage: "require HTTPS and verify certificates when contacting registries (default: true)", + }, + } + playKubeDescription = "Play a Pod and its containers based on a Kubrernetes YAML" + playKubeCommand = cli.Command{ + Name: "kube", + Usage: "Play a pod based on Kubernetes YAML", + Description: playKubeDescription, + Action: playKubeYAMLCmd, + Flags: sortFlags(playKubeFlags), + ArgsUsage: "kubernetes YAML file", + UseShortOptionHandling: true, + OnUsageError: usageErrorHandler, + } +) + +func playKubeYAMLCmd(c *cli.Context) error { + var ( + podOptions []libpod.PodCreateOption + podYAML v1.Pod + registryCreds *types.DockerAuthConfig + containers []*libpod.Container + writer io.Writer + ) + + ctx := getContext() + if rootless.IsRootless() { + return errors.Wrapf(libpod.ErrNotImplemented, "rootless users") + } + args := c.Args() + if len(args) > 1 { + return errors.New("you can only play one kubernetes file at a time") + } + if len(args) < 1 { + return errors.New("you must supply at least one file") + } + + runtime, err := libpodruntime.GetRuntime(c) + if err != nil { + return errors.Wrapf(err, "could not get runtime") + } + defer runtime.Shutdown(false) + + content, err := ioutil.ReadFile(args[0]) + if err != nil { + return err + } + + if err := yaml.Unmarshal(content, &podYAML); err != nil { + return errors.Wrapf(err, "unable to read %s as YAML", args[0]) + } + + podOptions = append(podOptions, libpod.WithInfraContainer()) + podOptions = append(podOptions, libpod.WithPodName(podYAML.ObjectMeta.Name)) + // TODO for now we just used the default kernel namespaces; we need to add/subtract this from yaml + + nsOptions, err := shared.GetNamespaceOptions(strings.Split(DefaultKernelNamespaces, ",")) + if err != nil { + return err + } + podOptions = append(podOptions, nsOptions...) + podPorts := getPodPorts(podYAML.Spec.Containers) + podOptions = append(podOptions, libpod.WithInfraContainerPorts(podPorts)) + + // Create the Pod + pod, err := runtime.NewPod(ctx, podOptions...) + if err != nil { + return err + } + // Print the Pod's ID + fmt.Println(pod.ID()) + + podInfraID, err := pod.InfraContainerID() + if err != nil { + return err + } + + namespaces := map[string]string{ + // Disabled during code review per mheon + //"pid": fmt.Sprintf("container:%s", podInfraID), + "net": fmt.Sprintf("container:%s", podInfraID), + "user": fmt.Sprintf("container:%s", podInfraID), + "ipc": fmt.Sprintf("container:%s", podInfraID), + "uts": fmt.Sprintf("container:%s", podInfraID), + } + if !c.Bool("quiet") { + writer = os.Stderr + } + + dockerRegistryOptions := image2.DockerRegistryOptions{ + DockerRegistryCreds: registryCreds, + DockerCertPath: c.String("cert-dir"), + } + if c.IsSet("tls-verify") { + dockerRegistryOptions.DockerInsecureSkipTLSVerify = types.NewOptionalBool(!c.BoolT("tls-verify")) + } + + for _, container := range podYAML.Spec.Containers { + newImage, err := runtime.ImageRuntime().New(ctx, container.Image, c.String("signature-policy"), c.String("authfile"), writer, &dockerRegistryOptions, image2.SigningOptions{}, false) + if err != nil { + return err + } + createConfig := kubeContainerToCreateConfig(container, runtime, newImage, namespaces) + if err != nil { + return err + } + ctr, err := createContainerFromCreateConfig(runtime, createConfig, ctx) + if err != nil { + return err + } + containers = append(containers, ctr) + } + + // start the containers + for _, ctr := range containers { + if err := ctr.Start(ctx); err != nil { + // Making this a hard failure here to avoid a mess + // the other containers are in created status + return err + } + fmt.Println(ctr.ID()) + } + + return 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 { + 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") + } + infraPorts = append(infraPorts, portBinding) + } + } + return infraPorts +} + +// kubeContainerToCreateConfig takes a v1.Container and returns a createconfig describing a container +func kubeContainerToCreateConfig(containerYAML v1.Container, runtime *libpod.Runtime, newImage *image2.Image, namespaces map[string]string) *createconfig.CreateConfig { + var ( + containerConfig createconfig.CreateConfig + envs map[string]string + ) + + containerConfig.Runtime = runtime + containerConfig.Image = containerYAML.Image + containerConfig.ImageID = newImage.ID() + containerConfig.Name = containerYAML.Name + containerConfig.Tty = containerYAML.TTY + containerConfig.WorkDir = containerYAML.WorkingDir + if containerYAML.SecurityContext.ReadOnlyRootFilesystem != nil { + containerConfig.ReadOnlyRootfs = *containerYAML.SecurityContext.ReadOnlyRootFilesystem + } + if containerYAML.SecurityContext.Privileged != nil { + containerConfig.Privileged = *containerYAML.SecurityContext.Privileged + } + + if containerYAML.SecurityContext.AllowPrivilegeEscalation != nil { + containerConfig.NoNewPrivs = !*containerYAML.SecurityContext.AllowPrivilegeEscalation + } + + containerConfig.Command = containerYAML.Command + containerConfig.StopSignal = 15 + + // If the user does not pass in ID mappings, just set to basics + if containerConfig.IDMappings == nil { + containerConfig.IDMappings = &storage.IDMappingOptions{} + } + + containerConfig.NetMode = ns.NetworkMode(namespaces["net"]) + containerConfig.IpcMode = ns.IpcMode(namespaces["ipc"]) + containerConfig.UtsMode = ns.UTSMode(namespaces["uts"]) + // disabled in code review per mheon + //containerConfig.PidMode = ns.PidMode(namespaces["pid"]) + containerConfig.UsernsMode = ns.UsernsMode(namespaces["user"]) + + if len(containerYAML.Env) > 0 { + envs = make(map[string]string) + } + // Environment Variables + for _, e := range containerYAML.Env { + envs[e.Name] = e.Value + } + containerConfig.Env = envs + return &containerConfig +} diff --git a/cmd/podman/varlink/io.podman.varlink b/cmd/podman/varlink/io.podman.varlink index 376bbc950..c1b7c703a 100644 --- a/cmd/podman/varlink/io.podman.varlink +++ b/cmd/podman/varlink/io.podman.varlink @@ -1016,6 +1016,18 @@ method UnmountContainer(name: string, force: bool) -> () # This function is not implemented yet. method ListContainerPorts(name: string) -> (notimplemented: NotImplemented) +# GenerateKube generates a Kubernetes v1 Pod description of a Podman container or pod +# and its containers. The description is in YAML. See also [ReplayKube](ReplayKube). +method GenerateKube() -> (notimplemented: NotImplemented) + +# GenerateKubeService generates a Kubernetes v1 Service description of a Podman container or pod +# and its containers. The description is in YAML. See also [GenerateKube](GenerateKube). +method GenerateKubeService() -> (notimplemented: NotImplemented) + +# ReplayKube recreates a pod and its containers based on a Kubernetes v1 Pod description (in YAML) +# like that created by GenerateKube. See also [GenerateKube](GenerateKube). +method ReplayKube() -> (notimplemented: NotImplemented) + # ImageNotFound means the image could not be found by the provided name or ID in local storage. error ImageNotFound (name: string) |