diff options
Diffstat (limited to 'pkg')
112 files changed, 7608 insertions, 661 deletions
diff --git a/pkg/adapter/containers.go b/pkg/adapter/containers.go index 0d2ca1a64..ecadbd2f9 100644 --- a/pkg/adapter/containers.go +++ b/pkg/adapter/containers.go @@ -16,6 +16,7 @@ import ( "time" "github.com/containers/buildah" + cfg "github.com/containers/common/pkg/config" "github.com/containers/image/v5/manifest" "github.com/containers/libpod/cmd/podman/cliconfig" "github.com/containers/libpod/cmd/podman/shared" @@ -25,6 +26,7 @@ import ( "github.com/containers/libpod/libpod/image" "github.com/containers/libpod/libpod/logs" "github.com/containers/libpod/pkg/adapter/shortcuts" + "github.com/containers/libpod/pkg/checkpoint" envLib "github.com/containers/libpod/pkg/env" "github.com/containers/libpod/pkg/systemd/generate" "github.com/containers/storage" @@ -380,11 +382,11 @@ func (r *LocalRuntime) selectDetachKeys(flagValue string) (string, error) { if err != nil { return "", errors.Wrapf(err, "unable to retrieve runtime config") } - if config.DetachKeys != "" { - return config.DetachKeys, nil + if config.Engine.DetachKeys != "" { + return config.Engine.DetachKeys, nil } - return define.DefaultDetachKeys, nil + return cfg.DefaultDetachKeys, nil } // Run a libpod container @@ -624,7 +626,7 @@ func (r *LocalRuntime) Restore(ctx context.Context, c *cliconfig.RestoreValues) switch { case c.Import != "": - containers, err = crImportCheckpoint(ctx, r.Runtime, c.Import, c.Name) + containers, err = checkpoint.CRImportCheckpoint(ctx, r.Runtime, c.Import, c.Name) case c.All: containers, err = r.GetContainers(filterFuncs...) default: @@ -1002,7 +1004,7 @@ func (r *LocalRuntime) ExecContainer(ctx context.Context, cli *cliconfig.ExecVal } env = envLib.Join(env, cliEnv) - streams := new(libpod.AttachStreams) + streams := new(define.AttachStreams) streams.OutputStream = os.Stdout streams.ErrorStream = os.Stderr if cli.Interactive { @@ -1212,8 +1214,8 @@ func (r *LocalRuntime) generateSystemdgenContainerInfo(c *cliconfig.GenerateSyst return nil, false, err } - timeout := int(ctr.StopTimeout()) - if c.StopTimeout >= 0 { + timeout := ctr.StopTimeout() + if c.Flags().Changed("timeout") || c.Flags().Changed("time") { timeout = c.StopTimeout } @@ -1369,9 +1371,9 @@ func (r *LocalRuntime) Commit(ctx context.Context, c *cliconfig.CommitValues, co return "", err } - sc := image.GetSystemContext(rtc.SignaturePolicyPath, "", false) + sc := image.GetSystemContext(rtc.Engine.SignaturePolicyPath, "", false) coptions := buildah.CommitOptions{ - SignaturePolicyPath: rtc.SignaturePolicyPath, + SignaturePolicyPath: rtc.Engine.SignaturePolicyPath, ReportWriter: writer, SystemContext: sc, PreferredManifestType: mimeType, diff --git a/pkg/adapter/containers_remote.go b/pkg/adapter/containers_remote.go index 32a84b60d..fc8b524d6 100644 --- a/pkg/adapter/containers_remote.go +++ b/pkg/adapter/containers_remote.go @@ -15,11 +15,11 @@ import ( "github.com/containers/libpod/cmd/podman/cliconfig" "github.com/containers/libpod/cmd/podman/shared" - iopodman "github.com/containers/libpod/cmd/podman/varlink" "github.com/containers/libpod/libpod" "github.com/containers/libpod/libpod/define" "github.com/containers/libpod/libpod/logs" envLib "github.com/containers/libpod/pkg/env" + iopodman "github.com/containers/libpod/pkg/varlink" "github.com/containers/libpod/pkg/varlinkapi/virtwriter" "github.com/cri-o/ocicni/pkg/ocicni" "github.com/docker/docker/pkg/term" @@ -32,12 +32,12 @@ import ( ) // Inspect returns an inspect struct from varlink -func (c *Container) Inspect(size bool) (*libpod.InspectContainerData, error) { +func (c *Container) Inspect(size bool) (*define.InspectContainerData, error) { reply, err := iopodman.ContainerInspectData().Call(c.Runtime.Conn, c.ID(), size) if err != nil { return nil, err } - data := libpod.InspectContainerData{} + data := define.InspectContainerData{} if err := json.Unmarshal([]byte(reply), &data); err != nil { return nil, err } diff --git a/pkg/adapter/errors.go b/pkg/adapter/errors.go index ede3d4b1a..012d01d39 100644 --- a/pkg/adapter/errors.go +++ b/pkg/adapter/errors.go @@ -3,8 +3,8 @@ package adapter import ( - iopodman "github.com/containers/libpod/cmd/podman/varlink" "github.com/containers/libpod/libpod/define" + iopodman "github.com/containers/libpod/pkg/varlink" "github.com/pkg/errors" ) diff --git a/pkg/adapter/images_remote.go b/pkg/adapter/images_remote.go index e7b38dccc..2df0ffcde 100644 --- a/pkg/adapter/images_remote.go +++ b/pkg/adapter/images_remote.go @@ -6,8 +6,8 @@ import ( "context" "encoding/json" - iopodman "github.com/containers/libpod/cmd/podman/varlink" "github.com/containers/libpod/pkg/inspect" + iopodman "github.com/containers/libpod/pkg/varlink" ) // Inspect returns returns an ImageData struct from over a varlink connection diff --git a/pkg/adapter/info_remote.go b/pkg/adapter/info_remote.go index c55d1f6ef..0e8fb06d1 100644 --- a/pkg/adapter/info_remote.go +++ b/pkg/adapter/info_remote.go @@ -4,9 +4,9 @@ package adapter import ( "encoding/json" - "github.com/containers/libpod/libpod/define" - "github.com/containers/libpod/cmd/podman/varlink" + "github.com/containers/libpod/libpod/define" + iopodman "github.com/containers/libpod/pkg/varlink" ) // Info returns information for the host system and its components diff --git a/pkg/adapter/network.go b/pkg/adapter/network.go index b25f54a13..577ffe19f 100644 --- a/pkg/adapter/network.go +++ b/pkg/adapter/network.go @@ -23,9 +23,9 @@ func getCNIConfDir(r *LocalRuntime) (string, error) { if err != nil { return "", err } - configPath := config.CNIConfigDir + configPath := config.Network.NetworkConfigDir - if len(config.CNIConfigDir) < 1 { + if len(config.Network.NetworkConfigDir) < 1 { configPath = network.CNIConfigDir } return configPath, nil @@ -211,7 +211,7 @@ func (r *LocalRuntime) NetworkCreateBridge(cli *cliconfig.NetworkCreateValues) ( plugins = append(plugins, network.NewPortMapPlugin()) plugins = append(plugins, network.NewFirewallPlugin()) // if we find the dnsname plugin, we add configuration for it - if network.HasDNSNamePlugin(runtimeConfig.CNIPluginDir) && !cli.DisableDNS { + if network.HasDNSNamePlugin(runtimeConfig.Network.CNIPluginDirs) && !cli.DisableDNS { // Note: in the future we might like to allow for dynamic domain names plugins = append(plugins, network.NewDNSNamePlugin(network.DefaultPodmanDomainName)) } diff --git a/pkg/adapter/pods.go b/pkg/adapter/pods.go index 64ebd3954..7c2a84cc7 100644 --- a/pkg/adapter/pods.go +++ b/pkg/adapter/pods.go @@ -784,6 +784,12 @@ 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, @@ -792,7 +798,12 @@ func getPodPorts(containers []v1.Container) []ocicni.PortMapping { if p.HostIP != "" { logrus.Debug("HostIP on port bindings is not supported") } - infraPorts = append(infraPorts, portBinding) + // 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 diff --git a/pkg/adapter/pods_remote.go b/pkg/adapter/pods_remote.go index 6b8f22f15..ebd10a92a 100644 --- a/pkg/adapter/pods_remote.go +++ b/pkg/adapter/pods_remote.go @@ -10,9 +10,9 @@ import ( "github.com/containers/libpod/cmd/podman/cliconfig" "github.com/containers/libpod/cmd/podman/shared" - iopodman "github.com/containers/libpod/cmd/podman/varlink" "github.com/containers/libpod/libpod" "github.com/containers/libpod/libpod/define" + iopodman "github.com/containers/libpod/pkg/varlink" "github.com/containers/libpod/pkg/varlinkapi" "github.com/pkg/errors" "github.com/sirupsen/logrus" diff --git a/pkg/adapter/reset_remote.go b/pkg/adapter/reset_remote.go index 663fab639..284b54a17 100644 --- a/pkg/adapter/reset_remote.go +++ b/pkg/adapter/reset_remote.go @@ -3,7 +3,7 @@ package adapter import ( - "github.com/containers/libpod/cmd/podman/varlink" + iopodman "github.com/containers/libpod/pkg/varlink" ) // Info returns information for the host system and its components diff --git a/pkg/adapter/runtime.go b/pkg/adapter/runtime.go index 7817a1f98..7a181e7e5 100644 --- a/pkg/adapter/runtime.go +++ b/pkg/adapter/runtime.go @@ -13,7 +13,6 @@ import ( "github.com/containers/buildah" "github.com/containers/buildah/imagebuildah" "github.com/containers/buildah/pkg/formats" - "github.com/containers/buildah/pkg/parse" "github.com/containers/image/v5/docker/reference" "github.com/containers/image/v5/types" "github.com/containers/libpod/cmd/podman/cliconfig" @@ -296,37 +295,13 @@ func libpodVolumeToVolume(volumes []*libpod.Volume) []*Volume { // Build is the wrapper to build images func (r *LocalRuntime) Build(ctx context.Context, c *cliconfig.BuildValues, options imagebuildah.BuildOptions, dockerfiles []string) (string, reference.Canonical, error) { - namespaceOptions, networkPolicy, err := parse.NamespaceOptions(c.PodmanCommand.Command) - if err != nil { - return "", nil, errors.Wrapf(err, "error parsing namespace-related options") - } - usernsOption, idmappingOptions, err := parse.IDMappingOptions(c.PodmanCommand.Command, options.Isolation) - if err != nil { - return "", nil, errors.Wrapf(err, "error parsing ID mapping options") - } - namespaceOptions.AddOrReplace(usernsOption...) - - systemContext, err := parse.SystemContextFromOptions(c.PodmanCommand.Command) - if err != nil { - return "", nil, errors.Wrapf(err, "error building system context") - } authfile := c.Authfile if len(c.Authfile) == 0 { authfile = os.Getenv("REGISTRY_AUTH_FILE") } - systemContext.AuthFilePath = authfile - commonOpts, err := parse.CommonBuildOptions(c.PodmanCommand.Command) - if err != nil { - return "", nil, err - } - - options.NamespaceOptions = namespaceOptions - options.ConfigureNetwork = networkPolicy - options.IDMappingOptions = idmappingOptions - options.CommonBuildOpts = commonOpts - options.SystemContext = systemContext + options.SystemContext.AuthFilePath = authfile if c.GlobalFlags.Runtime != "" { options.Runtime = c.GlobalFlags.Runtime @@ -356,11 +331,11 @@ func (r *LocalRuntime) PruneVolumes(ctx context.Context) ([]string, []error) { errs = append(errs, err) return vids, errs } - for _, r := range reports { - if r.Err == nil { - vids = append(vids, r.Id) + for k, v := range reports { + if v == nil { + vids = append(vids, k) } else { - errs = append(errs, r.Err) + errs = append(errs, v) } } return vids, errs diff --git a/pkg/adapter/runtime_remote.go b/pkg/adapter/runtime_remote.go index fc396eddb..a4ac660ea 100644 --- a/pkg/adapter/runtime_remote.go +++ b/pkg/adapter/runtime_remote.go @@ -17,16 +17,17 @@ import ( "github.com/containers/buildah/imagebuildah" "github.com/containers/buildah/pkg/formats" + "github.com/containers/common/pkg/config" "github.com/containers/image/v5/docker/reference" "github.com/containers/image/v5/types" "github.com/containers/libpod/cmd/podman/cliconfig" "github.com/containers/libpod/cmd/podman/remoteclientconfig" - iopodman "github.com/containers/libpod/cmd/podman/varlink" "github.com/containers/libpod/libpod" "github.com/containers/libpod/libpod/define" "github.com/containers/libpod/libpod/events" "github.com/containers/libpod/libpod/image" "github.com/containers/libpod/pkg/util" + iopodman "github.com/containers/libpod/pkg/varlink" "github.com/containers/libpod/utils" "github.com/containers/storage/pkg/archive" "github.com/opencontainers/go-digest" @@ -113,15 +114,20 @@ func (r RemoteRuntime) DeferredShutdown(force bool) { } } -// RuntimeConfig is a bogus wrapper for compat with the libpod runtime -type RuntimeConfig struct { +// Containers is a bogus wrapper for compat with the libpod runtime +type ContainersConfig struct { // CGroupManager is the CGroup Manager to use // Valid values are "cgroupfs" and "systemd" CgroupManager string } +// RuntimeConfig is a bogus wrapper for compat with the libpod runtime +type RuntimeConfig struct { + Containers ContainersConfig +} + // Shutdown is a bogus wrapper for compat with the libpod runtime -func (r *RemoteRuntime) GetConfig() (*RuntimeConfig, error) { +func (r *RemoteRuntime) GetConfig() (*config.Config, error) { return nil, nil } @@ -535,32 +541,40 @@ func (r *LocalRuntime) Build(ctx context.Context, c *cliconfig.BuildValues, opti Ulimit: options.CommonBuildOpts.Ulimit, Volume: options.CommonBuildOpts.Volumes, } - buildinfo := iopodman.BuildInfo{ - AdditionalTags: options.AdditionalTags, - Annotations: options.Annotations, - BuildArgs: options.Args, - BuildOptions: buildOptions, - CniConfigDir: options.CNIConfigDir, - CniPluginDir: options.CNIPluginPath, - Compression: string(options.Compression), - DefaultsMountFilePath: options.DefaultMountsFilePath, - Dockerfiles: dockerfiles, // Err: string(options.Err), + // Out: + // ReportWriter: + Architecture: options.Architecture, + AddCapabilities: options.AddCapabilities, + AdditionalTags: options.AdditionalTags, + Annotations: options.Annotations, + BuildArgs: options.Args, + BuildOptions: buildOptions, + CniConfigDir: options.CNIConfigDir, + CniPluginDir: options.CNIPluginPath, + Compression: string(options.Compression), + Devices: options.Devices, + DefaultsMountFilePath: options.DefaultMountsFilePath, + Dockerfiles: dockerfiles, + DropCapabilities: options.DropCapabilities, ForceRmIntermediateCtrs: options.ForceRmIntermediateCtrs, Iidfile: options.IIDFile, Label: options.Labels, Layers: options.Layers, - Nocache: options.NoCache, - // Out: + // NamespaceOptions: options.NamespaceOptions, + Nocache: options.NoCache, + Os: options.OS, Output: options.Output, OutputFormat: options.OutputFormat, PullPolicy: options.PullPolicy.String(), Quiet: options.Quiet, RemoteIntermediateCtrs: options.RemoveIntermediateCtrs, - // ReportWriter: - RuntimeArgs: options.RuntimeArgs, - Squash: options.Squash, + RuntimeArgs: options.RuntimeArgs, + SignBy: options.SignBy, + Squash: options.Squash, + Target: options.Target, + TransientMounts: options.TransientMounts, } // tar the file outputFile, err := ioutil.TempFile("", "varlink_tar_send") diff --git a/pkg/adapter/terminal_linux.go b/pkg/adapter/terminal_linux.go index ef5a6f926..a56704be6 100644 --- a/pkg/adapter/terminal_linux.go +++ b/pkg/adapter/terminal_linux.go @@ -7,6 +7,7 @@ import ( "os" "github.com/containers/libpod/libpod" + "github.com/containers/libpod/libpod/define" "github.com/pkg/errors" "github.com/sirupsen/logrus" "golang.org/x/crypto/ssh/terminal" @@ -14,7 +15,7 @@ import ( ) // ExecAttachCtr execs and attaches to a container -func ExecAttachCtr(ctx context.Context, ctr *libpod.Container, tty, privileged bool, env map[string]string, cmd []string, user, workDir string, streams *libpod.AttachStreams, preserveFDs uint, detachKeys string) (int, error) { +func ExecAttachCtr(ctx context.Context, ctr *libpod.Container, tty, privileged bool, env map[string]string, cmd []string, user, workDir string, streams *define.AttachStreams, preserveFDs uint, detachKeys string) (int, error) { resize := make(chan remotecommand.TerminalSize) haveTerminal := terminal.IsTerminal(int(os.Stdin.Fd())) @@ -69,7 +70,7 @@ func StartAttachCtr(ctx context.Context, ctr *libpod.Container, stdout, stderr, defer cancel() } - streams := new(libpod.AttachStreams) + streams := new(define.AttachStreams) streams.OutputStream = stdout streams.ErrorStream = stderr streams.InputStream = bufio.NewReader(stdin) diff --git a/pkg/adapter/terminal_unsupported.go b/pkg/adapter/terminal_unsupported.go index 3009f0a38..9067757a1 100644 --- a/pkg/adapter/terminal_unsupported.go +++ b/pkg/adapter/terminal_unsupported.go @@ -11,7 +11,7 @@ import ( ) // ExecAttachCtr execs and attaches to a container -func ExecAttachCtr(ctx context.Context, ctr *libpod.Container, tty, privileged bool, env map[string]string, cmd []string, user, workDir string, streams *libpod.AttachStreams, preserveFDs uint, detachKeys string) (int, error) { +func ExecAttachCtr(ctx context.Context, ctr *libpod.Container, tty, privileged bool, env map[string]string, cmd []string, user, workDir string, streams *define.AttachStreams, preserveFDs uint, detachKeys string) (int, error) { return -1, define.ErrNotImplemented } diff --git a/pkg/api/handlers/compat/containers_create.go b/pkg/api/handlers/compat/containers_create.go index 6b8440fc2..12af40876 100644 --- a/pkg/api/handlers/compat/containers_create.go +++ b/pkg/api/handlers/compat/containers_create.go @@ -6,8 +6,8 @@ import ( "net/http" "strings" + "github.com/containers/common/pkg/config" "github.com/containers/libpod/libpod" - "github.com/containers/libpod/libpod/define" image2 "github.com/containers/libpod/libpod/image" "github.com/containers/libpod/pkg/api/handlers" "github.com/containers/libpod/pkg/api/handlers/utils" @@ -46,7 +46,12 @@ func CreateContainer(w http.ResponseWriter, r *http.Request) { utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "NewFromLocal()")) return } - cc, err := makeCreateConfig(input, newImage) + defaultContainerConfig, err := runtime.GetConfig() + if err != nil { + utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "GetConfig()")) + return + } + cc, err := makeCreateConfig(defaultContainerConfig, input, newImage) if err != nil { utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "makeCreatConfig()")) return @@ -55,7 +60,7 @@ func CreateContainer(w http.ResponseWriter, r *http.Request) { utils.CreateContainer(r.Context(), w, runtime, &cc) } -func makeCreateConfig(input handlers.CreateContainerConfig, newImage *image2.Image) (createconfig.CreateConfig, error) { +func makeCreateConfig(defaultContainerConfig *config.Config, input handlers.CreateContainerConfig, newImage *image2.Image) (createconfig.CreateConfig, error) { var ( err error init bool @@ -76,7 +81,7 @@ func makeCreateConfig(input handlers.CreateContainerConfig, newImage *image2.Ima workDir = input.WorkingDir } - stopTimeout := uint(define.CtrRemoveTimeout) + stopTimeout := defaultContainerConfig.Engine.StopTimeout if input.StopTimeout != nil { stopTimeout = uint(*input.StopTimeout) } diff --git a/pkg/api/handlers/compat/containers_export.go b/pkg/api/handlers/compat/containers_export.go new file mode 100644 index 000000000..37b9fbf2b --- /dev/null +++ b/pkg/api/handlers/compat/containers_export.go @@ -0,0 +1,42 @@ +package compat + +import ( + "io/ioutil" + "net/http" + "os" + + "github.com/containers/libpod/libpod" + "github.com/containers/libpod/pkg/api/handlers/utils" + "github.com/pkg/errors" +) + +func ExportContainer(w http.ResponseWriter, r *http.Request) { + runtime := r.Context().Value("runtime").(*libpod.Runtime) + name := utils.GetName(r) + con, err := runtime.LookupContainer(name) + if err != nil { + utils.ContainerNotFound(w, name, err) + return + } + tmpfile, err := ioutil.TempFile("", "api.tar") + if err != nil { + utils.Error(w, "unable to create tarball tempfile", http.StatusInternalServerError, errors.Wrap(err, "unable to create tempfile")) + return + } + defer os.Remove(tmpfile.Name()) + if err := tmpfile.Close(); err != nil { + utils.Error(w, "unable to close tempfile", http.StatusInternalServerError, errors.Wrap(err, "unable to close tempfile")) + return + } + if err := con.Export(tmpfile.Name()); err != nil { + utils.Error(w, "failed to save the image", http.StatusInternalServerError, errors.Wrap(err, "failed to save image")) + return + } + rdr, err := os.Open(tmpfile.Name()) + if err != nil { + utils.Error(w, "failed to read temp tarball", http.StatusInternalServerError, errors.Wrap(err, "failed to read the exported tarfile")) + return + } + defer rdr.Close() + utils.WriteResponse(w, http.StatusOK, rdr) +} diff --git a/pkg/api/handlers/compat/exec.go b/pkg/api/handlers/compat/exec.go new file mode 100644 index 000000000..ec1a8ac96 --- /dev/null +++ b/pkg/api/handlers/compat/exec.go @@ -0,0 +1,107 @@ +package compat + +import ( + "encoding/json" + "fmt" + "net/http" + "strings" + + "github.com/containers/libpod/libpod" + "github.com/containers/libpod/libpod/define" + "github.com/containers/libpod/pkg/api/handlers" + "github.com/containers/libpod/pkg/api/handlers/utils" + "github.com/gorilla/mux" + "github.com/pkg/errors" + "github.com/sirupsen/logrus" +) + +// ExecCreateHandler creates an exec session for a given container. +func ExecCreateHandler(w http.ResponseWriter, r *http.Request) { + runtime := r.Context().Value("runtime").(*libpod.Runtime) + + input := new(handlers.ExecCreateConfig) + if err := json.NewDecoder(r.Body).Decode(&input); err != nil { + utils.InternalServerError(w, errors.Wrapf(err, "error decoding request body as JSON")) + return + } + + ctrName := utils.GetName(r) + ctr, err := runtime.LookupContainer(ctrName) + if err != nil { + utils.ContainerNotFound(w, ctrName, err) + return + } + + libpodConfig := new(libpod.ExecConfig) + libpodConfig.Command = input.Cmd + libpodConfig.Terminal = input.Tty + libpodConfig.AttachStdin = input.AttachStdin + libpodConfig.AttachStderr = input.AttachStderr + libpodConfig.AttachStdout = input.AttachStdout + if input.DetachKeys != "" { + libpodConfig.DetachKeys = &input.DetachKeys + } + libpodConfig.Environment = make(map[string]string) + for _, envStr := range input.Env { + split := strings.SplitN(envStr, "=", 2) + if len(split) != 2 { + utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest, errors.Errorf("environment variable %q badly formed, must be key=value", envStr)) + return + } + libpodConfig.Environment[split[0]] = split[1] + } + libpodConfig.WorkDir = input.WorkingDir + libpodConfig.Privileged = input.Privileged + libpodConfig.User = input.User + + sessID, err := ctr.ExecCreate(libpodConfig) + if err != nil { + if errors.Cause(err) == define.ErrCtrStateInvalid { + // Check if the container is paused. If so, return a 409 + state, err := ctr.State() + if err == nil { + // Ignore the error != nil case. We're already + // throwing an InternalServerError below. + if state == define.ContainerStatePaused { + utils.Error(w, "Container is paused", http.StatusConflict, errors.Errorf("cannot create exec session as container %s is paused", ctr.ID())) + return + } + } + } + utils.InternalServerError(w, err) + return + } + + resp := new(handlers.ExecCreateResponse) + resp.ID = sessID + + utils.WriteResponse(w, http.StatusCreated, resp) +} + +// ExecInspectHandler inspects a given exec session. +func ExecInspectHandler(w http.ResponseWriter, r *http.Request) { + runtime := r.Context().Value("runtime").(*libpod.Runtime) + + sessionID := mux.Vars(r)["id"] + sessionCtr, err := runtime.GetExecSessionContainer(sessionID) + if err != nil { + utils.Error(w, fmt.Sprintf("No such exec session: %s", sessionID), http.StatusNotFound, err) + return + } + + logrus.Debugf("Inspecting exec session %s of container %s", sessionID, sessionCtr.ID()) + + session, err := sessionCtr.ExecSession(sessionID) + if err != nil { + utils.InternalServerError(w, errors.Wrapf(err, "error retrieving exec session %s from container %s", sessionID, sessionCtr.ID())) + return + } + + inspectOut, err := session.Inspect() + if err != nil { + utils.InternalServerError(w, err) + return + } + + utils.WriteResponse(w, http.StatusOK, inspectOut) +} diff --git a/pkg/api/handlers/compat/images.go b/pkg/api/handlers/compat/images.go index cce718f54..ea9cbd691 100644 --- a/pkg/api/handlers/compat/images.go +++ b/pkg/api/handlers/compat/images.go @@ -64,6 +64,7 @@ func PruneImages(w http.ResponseWriter, r *http.Request) { runtime := r.Context().Value("runtime").(*libpod.Runtime) query := struct { + All bool Filters map[string][]string `schema:"filters"` }{ // This is where you can override the golang default value for one of fields @@ -80,7 +81,7 @@ func PruneImages(w http.ResponseWriter, r *http.Request) { filters = append(filters, fmt.Sprintf("%s=%s", k, val)) } } - pruneCids, err := runtime.ImageRuntime().PruneImages(r.Context(), false, filters) + pruneCids, err := runtime.ImageRuntime().PruneImages(r.Context(), query.All, filters) if err != nil { utils.InternalServerError(w, err) return @@ -128,13 +129,13 @@ func CommitContainer(w http.ResponseWriter, r *http.Request) { utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "Decode()")) return } - sc := image2.GetSystemContext(rtc.SignaturePolicyPath, "", false) + sc := image2.GetSystemContext(rtc.Engine.SignaturePolicyPath, "", false) tag := "latest" options := libpod.ContainerCommitOptions{ Pause: true, } options.CommitOptions = buildah.CommitOptions{ - SignaturePolicyPath: rtc.SignaturePolicyPath, + SignaturePolicyPath: rtc.Engine.SignaturePolicyPath, ReportWriter: os.Stderr, SystemContext: sc, PreferredManifestType: manifest.DockerV2Schema2MediaType, diff --git a/pkg/api/handlers/compat/images_push.go b/pkg/api/handlers/compat/images_push.go new file mode 100644 index 000000000..2260d5557 --- /dev/null +++ b/pkg/api/handlers/compat/images_push.go @@ -0,0 +1,80 @@ +package compat + +import ( + "context" + "net/http" + "os" + "strings" + + "github.com/containers/libpod/libpod" + "github.com/containers/libpod/libpod/image" + "github.com/containers/libpod/pkg/api/handlers/utils" + "github.com/gorilla/schema" + "github.com/pkg/errors" +) + +// PushImage is the handler for the compat http endpoint for pushing images. +func PushImage(w http.ResponseWriter, r *http.Request) { + decoder := r.Context().Value("decoder").(*schema.Decoder) + runtime := r.Context().Value("runtime").(*libpod.Runtime) + + query := struct { + Tag string `schema:"tag"` + }{ + // This is where you can override the golang default value for one of fields + } + + if err := decoder.Decode(&query, r.URL.Query()); err != nil { + utils.Error(w, "Something went wrong.", http.StatusBadRequest, errors.Wrapf(err, "Failed to parse parameters for %s", r.URL.String())) + return + } + + // Note that Docker's docs state "Image name or ID" to be in the path + // parameter but it really must be a name as Docker does not allow for + // pushing an image by ID. + imageName := strings.TrimSuffix(utils.GetName(r), "/push") // GetName returns the entire path + if query.Tag != "" { + imageName += ":" + query.Tag + } + if _, err := utils.ParseStorageReference(imageName); err != nil { + utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest, + errors.Wrapf(err, "image source %q is not a containers-storage-transport reference", imageName)) + return + } + + newImage, err := runtime.ImageRuntime().NewFromLocal(imageName) + if err != nil { + utils.ImageNotFound(w, imageName, errors.Wrapf(err, "Failed to find image %s", imageName)) + return + } + + // TODO: the X-Registry-Auth header is not checked yet here nor in any other + // endpoint. Pushing does NOT work with authentication at the moment. + dockerRegistryOptions := &image.DockerRegistryOptions{} + authfile := "" + if sys := runtime.SystemContext(); sys != nil { + dockerRegistryOptions.DockerCertPath = sys.DockerCertPath + authfile = sys.AuthFilePath + } + + err = newImage.PushImageToHeuristicDestination( + context.Background(), + imageName, + "", // manifest type + authfile, + "", // digest file + "", // signature policy + os.Stderr, + false, // force compression + image.SigningOptions{}, + dockerRegistryOptions, + nil, // additional tags + ) + if err != nil { + utils.Error(w, "Something went wrong.", http.StatusBadRequest, errors.Wrapf(err, "Error pushing image %q", imageName)) + return + } + + utils.WriteResponse(w, http.StatusOK, "") + +} diff --git a/pkg/api/handlers/compat/images_remove.go b/pkg/api/handlers/compat/images_remove.go index 3d346543e..ed0153529 100644 --- a/pkg/api/handlers/compat/images_remove.go +++ b/pkg/api/handlers/compat/images_remove.go @@ -36,17 +36,23 @@ func RemoveImage(w http.ResponseWriter, r *http.Request) { return } - _, err = runtime.RemoveImage(r.Context(), newImage, query.Force) + results, err := runtime.RemoveImage(r.Context(), newImage, query.Force) if err != nil { utils.Error(w, "Something went wrong.", http.StatusInternalServerError, err) return } - // TODO - // This will need to be fixed for proper response, like Deleted: and Untagged: - m := make(map[string]string) - m["Deleted"] = newImage.ID() - foo := []map[string]string{} - foo = append(foo, m) - utils.WriteResponse(w, http.StatusOK, foo) + + response := make([]map[string]string, 0, len(results.Untagged)+1) + deleted := make(map[string]string, 1) + deleted["Deleted"] = results.Deleted + response = append(response, deleted) + + for _, u := range results.Untagged { + untagged := make(map[string]string, 1) + untagged["Untagged"] = u + response = append(response, untagged) + } + + utils.WriteResponse(w, http.StatusOK, response) } diff --git a/pkg/api/handlers/compat/info.go b/pkg/api/handlers/compat/info.go index 30b49948d..104d0793b 100644 --- a/pkg/api/handlers/compat/info.go +++ b/pkg/api/handlers/compat/info.go @@ -9,8 +9,8 @@ import ( "strings" "time" + "github.com/containers/common/pkg/config" "github.com/containers/libpod/libpod" - "github.com/containers/libpod/libpod/config" "github.com/containers/libpod/libpod/define" "github.com/containers/libpod/pkg/api/handlers" "github.com/containers/libpod/pkg/api/handlers/utils" @@ -60,7 +60,7 @@ func GetInfo(w http.ResponseWriter, r *http.Request) { CPUCfsQuota: sysInfo.CPUCfsQuota, CPUSet: sysInfo.Cpuset, CPUShares: sysInfo.CPUShares, - CgroupDriver: configInfo.CgroupManager, + CgroupDriver: configInfo.Engine.CgroupManager, ClusterAdvertise: "", ClusterStore: "", ContainerdCommit: docker.Commit{}, @@ -69,7 +69,7 @@ func GetInfo(w http.ResponseWriter, r *http.Request) { ContainersRunning: stateInfo[define.ContainerStateRunning], ContainersStopped: stateInfo[define.ContainerStateStopped] + stateInfo[define.ContainerStateExited], Debug: log.IsLevelEnabled(log.DebugLevel), - DefaultRuntime: configInfo.OCIRuntime, + DefaultRuntime: configInfo.Engine.OCIRuntime, DockerRootDir: storeInfo["GraphRoot"].(string), Driver: storeInfo["GraphDriverName"].(string), DriverStatus: getGraphStatus(storeInfo), @@ -152,7 +152,7 @@ func getSecOpts(sysInfo *sysinfo.SysInfo) []string { func getRuntimes(configInfo *config.Config) map[string]docker.Runtime { var runtimes = map[string]docker.Runtime{} - for name, paths := range configInfo.OCIRuntimes { + for name, paths := range configInfo.Engine.OCIRuntimes { runtimes[name] = docker.Runtime{ Path: paths[0], Args: nil, diff --git a/pkg/api/handlers/libpod/containers.go b/pkg/api/handlers/libpod/containers.go index cdc34004f..fde72552b 100644 --- a/pkg/api/handlers/libpod/containers.go +++ b/pkg/api/handlers/libpod/containers.go @@ -1,16 +1,21 @@ package libpod import ( + "io/ioutil" "net/http" + "os" "path/filepath" "sort" "strconv" "time" + "github.com/containers/libpod/pkg/api/handlers/compat" + "github.com/containers/libpod/cmd/podman/shared" "github.com/containers/libpod/libpod" "github.com/containers/libpod/libpod/define" "github.com/containers/libpod/pkg/api/handlers/utils" + "github.com/containers/libpod/pkg/domain/entities" "github.com/gorilla/schema" "github.com/pkg/errors" "github.com/sirupsen/logrus" @@ -325,3 +330,129 @@ func ListContainerBatch(rt *libpod.Runtime, ctr *libpod.Container, opts shared.P } return ps, nil } + +func Checkpoint(w http.ResponseWriter, r *http.Request) { + var targetFile string + decoder := r.Context().Value("decoder").(*schema.Decoder) + query := struct { + Keep bool `schema:"keep"` + LeaveRunning bool `schema:"leaveRunning"` + TCPEstablished bool `schema:"tcpEstablished"` + Export bool `schema:"export"` + IgnoreRootFS bool `schema:"ignoreRootFS"` + }{ + // override any golang type defaults + } + + 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 + } + name := utils.GetName(r) + runtime := r.Context().Value("runtime").(*libpod.Runtime) + ctr, err := runtime.LookupContainer(name) + if err != nil { + utils.ContainerNotFound(w, name, err) + return + } + if query.Export { + tmpFile, err := ioutil.TempFile("", "checkpoint") + if err != nil { + utils.InternalServerError(w, err) + return + } + defer os.Remove(tmpFile.Name()) + if err := tmpFile.Close(); err != nil { + utils.InternalServerError(w, err) + return + } + targetFile = tmpFile.Name() + } + options := libpod.ContainerCheckpointOptions{ + Keep: query.Keep, + KeepRunning: query.LeaveRunning, + TCPEstablished: query.TCPEstablished, + IgnoreRootfs: query.IgnoreRootFS, + } + if query.Export { + options.TargetFile = targetFile + } + err = ctr.Checkpoint(r.Context(), options) + if err != nil { + utils.InternalServerError(w, err) + return + } + if query.Export { + f, err := os.Open(targetFile) + if err != nil { + utils.InternalServerError(w, err) + return + } + defer f.Close() + utils.WriteResponse(w, http.StatusOK, f) + return + } + utils.WriteResponse(w, http.StatusOK, entities.CheckpointReport{Id: ctr.ID()}) +} + +func Restore(w http.ResponseWriter, r *http.Request) { + var ( + targetFile string + ) + decoder := r.Context().Value("decoder").(*schema.Decoder) + query := struct { + Keep bool `schema:"keep"` + TCPEstablished bool `schema:"tcpEstablished"` + Import bool `schema:"import"` + Name string `schema:"name"` + IgnoreRootFS bool `schema:"ignoreRootFS"` + IgnoreStaticIP bool `schema:"ignoreStaticIP"` + IgnoreStaticMAC bool `schema:"ignoreStaticMAC"` + }{ + // override any golang type defaults + } + 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 + } + name := utils.GetName(r) + runtime := r.Context().Value("runtime").(*libpod.Runtime) + ctr, err := runtime.LookupContainer(name) + if err != nil { + utils.ContainerNotFound(w, name, err) + return + } + if query.Import { + t, err := ioutil.TempFile("", "restore") + if err != nil { + utils.InternalServerError(w, err) + return + } + defer t.Close() + if err := compat.SaveFromBody(t, r); err != nil { + utils.InternalServerError(w, err) + return + } + targetFile = t.Name() + } + + options := libpod.ContainerCheckpointOptions{ + Keep: query.Keep, + TCPEstablished: query.TCPEstablished, + IgnoreRootfs: query.IgnoreRootFS, + IgnoreStaticIP: query.IgnoreStaticIP, + IgnoreStaticMAC: query.IgnoreStaticMAC, + } + if query.Import { + options.TargetFile = targetFile + options.Name = query.Name + } + err = ctr.Restore(r.Context(), options) + if err != nil { + utils.InternalServerError(w, err) + return + } + utils.WriteResponse(w, http.StatusOK, entities.RestoreReport{Id: ctr.ID()}) +} diff --git a/pkg/api/handlers/libpod/containers_create.go b/pkg/api/handlers/libpod/containers_create.go index ebca41151..38a341a89 100644 --- a/pkg/api/handlers/libpod/containers_create.go +++ b/pkg/api/handlers/libpod/containers_create.go @@ -7,6 +7,7 @@ import ( "github.com/containers/libpod/libpod" "github.com/containers/libpod/pkg/api/handlers/utils" "github.com/containers/libpod/pkg/specgen" + "github.com/containers/libpod/pkg/specgen/generate" "github.com/pkg/errors" ) @@ -19,7 +20,11 @@ func CreateContainer(w http.ResponseWriter, r *http.Request) { utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "Decode()")) return } - ctr, err := sg.MakeContainer(runtime) + if err := generate.CompleteSpec(r.Context(), runtime, &sg); err != nil { + utils.InternalServerError(w, err) + return + } + ctr, err := generate.MakeContainer(runtime, &sg) if err != nil { utils.InternalServerError(w, err) return diff --git a/pkg/api/handlers/libpod/images.go b/pkg/api/handlers/libpod/images.go index f8e666451..850de4598 100644 --- a/pkg/api/handlers/libpod/images.go +++ b/pkg/api/handlers/libpod/images.go @@ -14,15 +14,16 @@ import ( "github.com/containers/image/v5/docker" "github.com/containers/image/v5/docker/reference" "github.com/containers/image/v5/manifest" - "github.com/containers/image/v5/transports/alltransports" "github.com/containers/image/v5/types" "github.com/containers/libpod/libpod" + "github.com/containers/libpod/libpod/define" "github.com/containers/libpod/libpod/image" image2 "github.com/containers/libpod/libpod/image" "github.com/containers/libpod/pkg/api/handlers" "github.com/containers/libpod/pkg/api/handlers/utils" "github.com/containers/libpod/pkg/domain/entities" "github.com/containers/libpod/pkg/util" + utils2 "github.com/containers/libpod/utils" "github.com/gorilla/schema" "github.com/pkg/errors" ) @@ -119,12 +120,12 @@ func GetImages(w http.ResponseWriter, r *http.Request) { func PruneImages(w http.ResponseWriter, r *http.Request) { var ( - all bool err error ) runtime := r.Context().Value("runtime").(*libpod.Runtime) decoder := r.Context().Value("decoder").(*schema.Decoder) query := struct { + All bool `schema:"all"` Filters map[string][]string `schema:"filters"` }{ // override any golang type defaults @@ -140,7 +141,7 @@ func PruneImages(w http.ResponseWriter, r *http.Request) { if _, found := r.URL.Query()["filters"]; found { dangling := query.Filters["all"] if len(dangling) > 0 { - all, err = strconv.ParseBool(query.Filters["all"][0]) + query.All, err = strconv.ParseBool(query.Filters["all"][0]) if err != nil { utils.InternalServerError(w, err) return @@ -152,7 +153,8 @@ func PruneImages(w http.ResponseWriter, r *http.Request) { libpodFilters = append(libpodFilters, fmt.Sprintf("%s=%s", k, v[0])) } } - cids, err := runtime.ImageRuntime().PruneImages(r.Context(), all, libpodFilters) + + cids, err := runtime.ImageRuntime().PruneImages(r.Context(), query.All, libpodFilters) if err != nil { utils.Error(w, "Something went wrong.", http.StatusInternalServerError, err) return @@ -161,13 +163,16 @@ func PruneImages(w http.ResponseWriter, r *http.Request) { } func ExportImage(w http.ResponseWriter, r *http.Request) { + var ( + output string + ) runtime := r.Context().Value("runtime").(*libpod.Runtime) decoder := r.Context().Value("decoder").(*schema.Decoder) query := struct { Compress bool `schema:"compress"` Format string `schema:"format"` }{ - Format: "docker-archive", + Format: define.OCIArchive, } if err := decoder.Decode(&query, r.URL.Query()); err != nil { @@ -175,14 +180,27 @@ func ExportImage(w http.ResponseWriter, r *http.Request) { errors.Wrapf(err, "Failed to parse parameters for %s", r.URL.String())) return } - - tmpfile, err := ioutil.TempFile("", "api.tar") - if err != nil { - utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "unable to create tempfile")) - return - } - if err := tmpfile.Close(); err != nil { - utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "unable to close tempfile")) + switch query.Format { + case define.OCIArchive, define.V2s2Archive: + tmpfile, err := ioutil.TempFile("", "api.tar") + if err != nil { + utils.Error(w, "unable to create tmpfile", http.StatusInternalServerError, errors.Wrap(err, "unable to create tempfile")) + return + } + output = tmpfile.Name() + if err := tmpfile.Close(); err != nil { + utils.Error(w, "unable to close tmpfile", http.StatusInternalServerError, errors.Wrap(err, "unable to close tempfile")) + return + } + case define.OCIManifestDir, define.V2s2ManifestDir: + tmpdir, err := ioutil.TempDir("", "save") + if err != nil { + utils.Error(w, "unable to create tmpdir", http.StatusInternalServerError, errors.Wrap(err, "unable to create tempdir")) + return + } + output = tmpdir + default: + utils.Error(w, "unknown format", http.StatusInternalServerError, errors.Errorf("unknown format %q", query.Format)) return } name := utils.GetName(r) @@ -192,17 +210,28 @@ func ExportImage(w http.ResponseWriter, r *http.Request) { return } - if err := newImage.Save(r.Context(), name, query.Format, tmpfile.Name(), []string{}, false, query.Compress); err != nil { + if err := newImage.Save(r.Context(), name, query.Format, output, []string{}, false, query.Compress); err != nil { utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest, err) return } - rdr, err := os.Open(tmpfile.Name()) + defer os.RemoveAll(output) + // if dir format, we need to tar it + if query.Format == "oci-dir" || query.Format == "docker-dir" { + rdr, err := utils2.Tar(output) + if err != nil { + utils.InternalServerError(w, err) + return + } + defer rdr.Close() + utils.WriteResponse(w, http.StatusOK, rdr) + return + } + rdr, err := os.Open(output) if err != nil { utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "failed to read the exported tarfile")) return } defer rdr.Close() - defer os.Remove(tmpfile.Name()) utils.WriteResponse(w, http.StatusOK, rdr) } @@ -253,7 +282,7 @@ func ImagesLoad(w http.ResponseWriter, r *http.Request) { return } } - utils.WriteResponse(w, http.StatusOK, handlers.LibpodImagesLoadReport{ID: loadedImage}) + utils.WriteResponse(w, http.StatusOK, entities.ImageLoadReport{Name: loadedImage}) } func ImagesImport(w http.ResponseWriter, r *http.Request) { @@ -299,9 +328,13 @@ func ImagesImport(w http.ResponseWriter, r *http.Request) { return } - utils.WriteResponse(w, http.StatusOK, handlers.LibpodImagesImportReport{ID: importedImage}) + utils.WriteResponse(w, http.StatusOK, entities.ImageImportReport{Id: importedImage}) } +// ImagesPull is the v2 libpod endpoint for pulling images. Note that the +// mandatory `reference` must be a reference to a registry (i.e., of docker +// transport or be normalized to one). Other transports are rejected as they +// do not make sense in a remote context. func ImagesPull(w http.ResponseWriter, r *http.Request) { runtime := r.Context().Value("runtime").(*libpod.Runtime) decoder := r.Context().Value("decoder").(*schema.Decoder) @@ -326,36 +359,27 @@ func ImagesPull(w http.ResponseWriter, r *http.Request) { utils.InternalServerError(w, errors.New("reference parameter cannot be empty")) return } - // Enforce the docker transport. This is just a precaution as some callers - // might accustomed to using the "transport:reference" notation. Using - // another than the "docker://" transport does not really make sense for a - // remote case. For loading tarballs, the load and import endpoints should - // be used. - imageRef, err := alltransports.ParseImageName(query.Reference) - if err == nil && imageRef.Transport().Name() != docker.Transport.Name() { + + imageRef, err := utils.ParseDockerReference(query.Reference) + if err != nil { utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest, - errors.Errorf("reference %q must be a docker reference", query.Reference)) + errors.Wrapf(err, "image destination %q is not a docker-transport reference", query.Reference)) return - } else if err != nil { - origErr := err - imageRef, err = alltransports.ParseImageName(fmt.Sprintf("%s://%s", docker.Transport.Name(), query.Reference)) - if err != nil { - utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest, - errors.Wrapf(origErr, "reference %q must be a docker reference", query.Reference)) - return - } } + // Trim the docker-transport prefix. + rawImage := strings.TrimPrefix(query.Reference, fmt.Sprintf("%s://", docker.Transport.Name())) + // all-tags doesn't work with a tagged reference, so let's check early - namedRef, err := reference.Parse(query.Reference) + namedRef, err := reference.Parse(rawImage) if err != nil { utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest, - errors.Wrapf(err, "error parsing reference %q", query.Reference)) + errors.Wrapf(err, "error parsing reference %q", rawImage)) return } if _, isTagged := namedRef.(reference.Tagged); isTagged && query.AllTags { utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest, - errors.Errorf("reference %q must not have a tag for all-tags", query.Reference)) + errors.Errorf("reference %q must not have a tag for all-tags", rawImage)) return } @@ -376,7 +400,7 @@ func ImagesPull(w http.ResponseWriter, r *http.Request) { OSChoice: query.OverrideOS, ArchitectureChoice: query.OverrideArch, } - if query.TLSVerify { + if _, found := r.URL.Query()["tlsVerify"]; found { dockerRegistryOptions.DockerInsecureSkipTLSVerify = types.NewOptionalBool(!query.TLSVerify) } @@ -399,13 +423,19 @@ func ImagesPull(w http.ResponseWriter, r *http.Request) { } } + authfile := "" + if sys := runtime.SystemContext(); sys != nil { + dockerRegistryOptions.DockerCertPath = sys.DockerCertPath + authfile = sys.AuthFilePath + } + // Finally pull the images for _, img := range imagesToPull { newImage, err := runtime.ImageRuntime().New( context.Background(), img, "", - "", + authfile, os.Stderr, &dockerRegistryOptions, image.SigningOptions{}, @@ -421,6 +451,94 @@ func ImagesPull(w http.ResponseWriter, r *http.Request) { utils.WriteResponse(w, http.StatusOK, res) } +// PushImage is the handler for the compat http endpoint for pushing images. +func PushImage(w http.ResponseWriter, r *http.Request) { + decoder := r.Context().Value("decoder").(*schema.Decoder) + runtime := r.Context().Value("runtime").(*libpod.Runtime) + + query := struct { + Credentials string `schema:"credentials"` + Destination string `schema:"destination"` + TLSVerify bool `schema:"tlsVerify"` + }{ + // This is where you can override the golang default value for one of fields + } + + if err := decoder.Decode(&query, r.URL.Query()); err != nil { + utils.Error(w, "Something went wrong.", http.StatusBadRequest, errors.Wrapf(err, "Failed to parse parameters for %s", r.URL.String())) + return + } + + source := strings.TrimSuffix(utils.GetName(r), "/push") // GetName returns the entire path + if _, err := utils.ParseStorageReference(source); err != nil { + utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest, + errors.Wrapf(err, "image source %q is not a containers-storage-transport reference", source)) + return + } + + destination := query.Destination + if destination == "" { + destination = source + } + + if _, err := utils.ParseDockerReference(destination); err != nil { + utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest, + errors.Wrapf(err, "image destination %q is not a docker-transport reference", destination)) + return + } + + newImage, err := runtime.ImageRuntime().NewFromLocal(source) + if err != nil { + utils.ImageNotFound(w, source, errors.Wrapf(err, "Failed to find image %s", source)) + return + } + + var registryCreds *types.DockerAuthConfig + if len(query.Credentials) != 0 { + creds, err := util.ParseRegistryCreds(query.Credentials) + if err != nil { + utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest, + errors.Wrapf(err, "error parsing credentials %q", query.Credentials)) + return + } + registryCreds = creds + } + + // TODO: the X-Registry-Auth header is not checked yet here nor in any other + // endpoint. Pushing does NOT work with authentication at the moment. + dockerRegistryOptions := &image.DockerRegistryOptions{ + DockerRegistryCreds: registryCreds, + } + authfile := "" + if sys := runtime.SystemContext(); sys != nil { + dockerRegistryOptions.DockerCertPath = sys.DockerCertPath + authfile = sys.AuthFilePath + } + if _, found := r.URL.Query()["tlsVerify"]; found { + dockerRegistryOptions.DockerInsecureSkipTLSVerify = types.NewOptionalBool(!query.TLSVerify) + } + + err = newImage.PushImageToHeuristicDestination( + context.Background(), + destination, + "", // manifest type + authfile, + "", // digest file + "", // signature policy + os.Stderr, + false, // force compression + image.SigningOptions{}, + dockerRegistryOptions, + nil, // additional tags + ) + if err != nil { + utils.Error(w, "Something went wrong.", http.StatusBadRequest, errors.Wrapf(err, "Error pushing image %q", destination)) + return + } + + utils.WriteResponse(w, http.StatusOK, "") +} + func CommitContainer(w http.ResponseWriter, r *http.Request) { var ( destImage string @@ -451,7 +569,7 @@ func CommitContainer(w http.ResponseWriter, r *http.Request) { utils.Error(w, "failed to get runtime config", http.StatusInternalServerError, errors.Wrap(err, "failed to get runtime config")) return } - sc := image2.GetSystemContext(rtc.SignaturePolicyPath, "", false) + sc := image2.GetSystemContext(rtc.Engine.SignaturePolicyPath, "", false) tag := "latest" options := libpod.ContainerCommitOptions{ Pause: true, @@ -470,7 +588,7 @@ func CommitContainer(w http.ResponseWriter, r *http.Request) { return } options.CommitOptions = buildah.CommitOptions{ - SignaturePolicyPath: rtc.SignaturePolicyPath, + SignaturePolicyPath: rtc.Engine.SignaturePolicyPath, ReportWriter: os.Stderr, SystemContext: sc, PreferredManifestType: mimeType, @@ -501,3 +619,29 @@ func CommitContainer(w http.ResponseWriter, r *http.Request) { } utils.WriteResponse(w, http.StatusOK, handlers.IDResponse{ID: commitImage.ID()}) // nolint } + +func UntagImage(w http.ResponseWriter, r *http.Request) { + runtime := r.Context().Value("runtime").(*libpod.Runtime) + + name := utils.GetName(r) + newImage, err := runtime.ImageRuntime().NewFromLocal(name) + if err != nil { + utils.ImageNotFound(w, name, errors.Wrapf(err, "Failed to find image %s", name)) + return + } + tag := "latest" + if len(r.Form.Get("tag")) > 0 { + tag = r.Form.Get("tag") + } + if len(r.Form.Get("repo")) < 1 { + utils.Error(w, "repo tag is required", http.StatusBadRequest, errors.New("repo parameter is required to tag an image")) + return + } + repo := r.Form.Get("repo") + tagName := fmt.Sprintf("%s:%s", repo, tag) + if err := newImage.UntagImage(tagName); err != nil { + utils.Error(w, "failed to untag", http.StatusInternalServerError, err) + return + } + utils.WriteResponse(w, http.StatusCreated, "") +} diff --git a/pkg/api/handlers/libpod/manifests.go b/pkg/api/handlers/libpod/manifests.go index a3d2caba6..d87ed7eba 100644 --- a/pkg/api/handlers/libpod/manifests.go +++ b/pkg/api/handlers/libpod/manifests.go @@ -36,7 +36,7 @@ func ManifestCreate(w http.ResponseWriter, r *http.Request) { utils.InternalServerError(w, err) return } - sc := image.GetSystemContext(rtc.SignaturePolicyPath, "", false) + sc := image.GetSystemContext(rtc.Engine.SignaturePolicyPath, "", false) manID, err := image.CreateManifestList(runtime.ImageRuntime(), *sc, query.Name, query.Image, query.All) if err != nil { utils.InternalServerError(w, err) @@ -79,7 +79,7 @@ func ManifestAdd(w http.ResponseWriter, r *http.Request) { utils.InternalServerError(w, err) return } - sc := image.GetSystemContext(rtc.SignaturePolicyPath, "", false) + sc := image.GetSystemContext(rtc.Engine.SignaturePolicyPath, "", false) newID, err := newImage.AddManifest(*sc, manifestInput) if err != nil { utils.InternalServerError(w, err) @@ -149,7 +149,7 @@ func ManifestPush(w http.ResponseWriter, r *http.Request) { utils.InternalServerError(w, err) return } - sc := image.GetSystemContext(rtc.SignaturePolicyPath, "", false) + sc := image.GetSystemContext(rtc.Engine.SignaturePolicyPath, "", false) opts := manifests.PushOptions{ ImageListSelection: copy2.CopySpecificImages, SystemContext: sc, diff --git a/pkg/api/handlers/libpod/networks.go b/pkg/api/handlers/libpod/networks.go index e3dbe3b35..e8a92e93e 100644 --- a/pkg/api/handlers/libpod/networks.go +++ b/pkg/api/handlers/libpod/networks.go @@ -18,7 +18,7 @@ func ListNetworks(w http.ResponseWriter, r *http.Request) { utils.InternalServerError(w, err) return } - configDir := config.CNIConfigDir + configDir := config.Network.NetworkConfigDir if len(configDir) < 1 { configDir = network.CNIConfigDir } diff --git a/pkg/api/handlers/libpod/pods.go b/pkg/api/handlers/libpod/pods.go index 27ec64d89..a890169a1 100644 --- a/pkg/api/handlers/libpod/pods.go +++ b/pkg/api/handlers/libpod/pods.go @@ -6,12 +6,12 @@ import ( "net/http" "strings" - "github.com/containers/libpod/cmd/podman/shared" - "github.com/containers/libpod/cmd/podman/shared/parse" "github.com/containers/libpod/libpod" "github.com/containers/libpod/libpod/define" "github.com/containers/libpod/pkg/api/handlers" "github.com/containers/libpod/pkg/api/handlers/utils" + "github.com/containers/libpod/pkg/domain/entities" + "github.com/containers/libpod/pkg/specgen" "github.com/containers/libpod/pkg/util" "github.com/gorilla/schema" "github.com/pkg/errors" @@ -20,76 +20,14 @@ import ( func PodCreate(w http.ResponseWriter, r *http.Request) { var ( runtime = r.Context().Value("runtime").(*libpod.Runtime) - options []libpod.PodCreateOption err error ) - labels := make(map[string]string) - input := handlers.PodCreateConfig{} - if err := json.NewDecoder(r.Body).Decode(&input); err != nil { - utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "Decode()")) + var psg specgen.PodSpecGenerator + if err := json.NewDecoder(r.Body).Decode(&psg); err != nil { + utils.Error(w, "Failed to decode specgen", http.StatusInternalServerError, errors.Wrap(err, "failed to decode specgen")) return } - if len(input.InfraCommand) > 0 || len(input.InfraImage) > 0 { - utils.Error(w, "Something went wrong.", http.StatusInternalServerError, - errors.New("infra-command and infra-image are not implemented yet")) - return - } - // TODO long term we should break the following out of adapter and into libpod proper - // so that the cli and api can share the creation of a pod with the same options - if len(input.CGroupParent) > 0 { - options = append(options, libpod.WithPodCgroupParent(input.CGroupParent)) - } - - if len(input.Labels) > 0 { - labels, err = parse.GetAllLabels([]string{}, input.Labels) - if err != nil { - utils.Error(w, "Something went wrong.", http.StatusInternalServerError, err) - return - } - } - - if len(labels) != 0 { - options = append(options, libpod.WithPodLabels(labels)) - } - - if len(input.Name) > 0 { - options = append(options, libpod.WithPodName(input.Name)) - } - - if len(input.Hostname) > 0 { - options = append(options, libpod.WithPodHostname(input.Hostname)) - } - - if input.Infra { - // TODO infra-image and infra-command are not supported in the libpod API yet. Will fix - // when implemented in libpod - options = append(options, libpod.WithInfraContainer()) - sharedNamespaces := shared.DefaultKernelNamespaces - if len(input.Share) > 0 { - sharedNamespaces = input.Share - } - nsOptions, err := shared.GetNamespaceOptions(strings.Split(sharedNamespaces, ",")) - if err != nil { - utils.Error(w, "Something went wrong.", http.StatusInternalServerError, err) - return - } - options = append(options, nsOptions...) - } - - if len(input.Publish) > 0 { - portBindings, err := shared.CreatePortBindings(input.Publish) - if err != nil { - utils.Error(w, "Something went wrong.", http.StatusInternalServerError, err) - return - } - options = append(options, libpod.WithInfraContainerPorts(portBindings)) - - } - // always have containers use pod cgroups - // User Opt out is not yet supported - options = append(options, libpod.WithPodCgroups()) - - pod, err := runtime.NewPod(r.Context(), options...) + pod, err := psg.MakePod(runtime) if err != nil { http_code := http.StatusInternalServerError if errors.Cause(err) == define.ErrPodExists { @@ -102,9 +40,6 @@ func PodCreate(w http.ResponseWriter, r *http.Request) { } func Pods(w http.ResponseWriter, r *http.Request) { - var ( - podInspectData []*libpod.PodInspect - ) decoder := r.Context().Value("decoder").(*schema.Decoder) query := struct { Filters map[string][]string `schema:"filters"` @@ -118,20 +53,11 @@ func Pods(w http.ResponseWriter, r *http.Request) { } pods, err := utils.GetPods(w, r) - if err != nil { utils.Error(w, "Something went wrong", http.StatusInternalServerError, err) return } - for _, pod := range pods { - data, err := pod.Inspect() - if err != nil { - utils.Error(w, "Something went wrong", http.StatusInternalServerError, err) - return - } - podInspectData = append(podInspectData, data) - } - utils.WriteResponse(w, http.StatusOK, podInspectData) + utils.WriteResponse(w, http.StatusOK, pods) } func PodInspect(w http.ResponseWriter, r *http.Request) { @@ -147,7 +73,10 @@ func PodInspect(w http.ResponseWriter, r *http.Request) { utils.Error(w, "Something went wrong", http.StatusInternalServerError, err) return } - utils.WriteResponse(w, http.StatusOK, podData) + report := entities.PodInspectReport{ + PodInspect: podData, + } + utils.WriteResponse(w, http.StatusOK, report) } func PodStop(w http.ResponseWriter, r *http.Request) { @@ -155,6 +84,8 @@ func PodStop(w http.ResponseWriter, r *http.Request) { stopError error runtime = r.Context().Value("runtime").(*libpod.Runtime) decoder = r.Context().Value("decoder").(*schema.Decoder) + responses map[string]error + errs []error ) query := struct { Timeout int `schema:"t"` @@ -185,18 +116,28 @@ func PodStop(w http.ResponseWriter, r *http.Request) { } if query.Timeout > 0 { - _, stopError = pod.StopWithTimeout(r.Context(), false, query.Timeout) + responses, stopError = pod.StopWithTimeout(r.Context(), false, query.Timeout) } else { - _, stopError = pod.Stop(r.Context(), false) + responses, stopError = pod.Stop(r.Context(), false) } if stopError != nil { utils.Error(w, "Something went wrong", http.StatusInternalServerError, err) return } - utils.WriteResponse(w, http.StatusOK, "") + for _, err := range responses { + errs = append(errs, err) + } + report := entities.PodStopReport{ + Errs: errs, + Id: pod.ID(), + } + utils.WriteResponse(w, http.StatusOK, report) } func PodStart(w http.ResponseWriter, r *http.Request) { + var ( + errs []error + ) runtime := r.Context().Value("runtime").(*libpod.Runtime) name := utils.GetName(r) pod, err := runtime.LookupPod(name) @@ -213,11 +154,19 @@ func PodStart(w http.ResponseWriter, r *http.Request) { utils.WriteResponse(w, http.StatusNotModified, "") return } - if _, err := pod.Start(r.Context()); err != nil { + responses, err := pod.Start(r.Context()) + if err != nil { utils.Error(w, "Something went wrong", http.StatusInternalServerError, err) return } - utils.WriteResponse(w, http.StatusOK, "") + for _, err := range responses { + errs = append(errs, err) + } + report := entities.PodStartReport{ + Errs: errs, + Id: pod.ID(), + } + utils.WriteResponse(w, http.StatusOK, report) } func PodDelete(w http.ResponseWriter, r *http.Request) { @@ -246,10 +195,16 @@ func PodDelete(w http.ResponseWriter, r *http.Request) { utils.Error(w, "Something went wrong", http.StatusInternalServerError, err) return } - utils.WriteResponse(w, http.StatusNoContent, "") + report := entities.PodRmReport{ + Id: pod.ID(), + } + utils.WriteResponse(w, http.StatusOK, report) } func PodRestart(w http.ResponseWriter, r *http.Request) { + var ( + errs []error + ) runtime := r.Context().Value("runtime").(*libpod.Runtime) name := utils.GetName(r) pod, err := runtime.LookupPod(name) @@ -257,12 +212,19 @@ func PodRestart(w http.ResponseWriter, r *http.Request) { utils.PodNotFound(w, name, err) return } - _, err = pod.Restart(r.Context()) + responses, err := pod.Restart(r.Context()) if err != nil { utils.Error(w, "Something went wrong", http.StatusInternalServerError, err) return } - utils.WriteResponse(w, http.StatusOK, "") + for _, err := range responses { + errs = append(errs, err) + } + report := entities.PodRestartReport{ + Errs: errs, + Id: pod.ID(), + } + utils.WriteResponse(w, http.StatusOK, report) } func PodPrune(w http.ResponseWriter, r *http.Request) { @@ -278,6 +240,9 @@ func PodPrune(w http.ResponseWriter, r *http.Request) { } func PodPause(w http.ResponseWriter, r *http.Request) { + var ( + errs []error + ) runtime := r.Context().Value("runtime").(*libpod.Runtime) name := utils.GetName(r) pod, err := runtime.LookupPod(name) @@ -285,15 +250,25 @@ func PodPause(w http.ResponseWriter, r *http.Request) { utils.PodNotFound(w, name, err) return } - _, err = pod.Pause() + responses, err := pod.Pause() if err != nil { utils.Error(w, "Something went wrong", http.StatusInternalServerError, err) return } - utils.WriteResponse(w, http.StatusNoContent, "") + for _, v := range responses { + errs = append(errs, v) + } + report := entities.PodPauseReport{ + Errs: errs, + Id: pod.ID(), + } + utils.WriteResponse(w, http.StatusOK, report) } func PodUnpause(w http.ResponseWriter, r *http.Request) { + var ( + errs []error + ) runtime := r.Context().Value("runtime").(*libpod.Runtime) name := utils.GetName(r) pod, err := runtime.LookupPod(name) @@ -301,12 +276,61 @@ func PodUnpause(w http.ResponseWriter, r *http.Request) { utils.PodNotFound(w, name, err) return } - _, err = pod.Unpause() + responses, err := pod.Unpause() if err != nil { - utils.Error(w, "Something went wrong", http.StatusInternalServerError, err) + utils.Error(w, "failed to pause pod", http.StatusInternalServerError, err) + return + } + for _, v := range responses { + errs = append(errs, v) + } + report := entities.PodUnpauseReport{ + Errs: errs, + Id: pod.ID(), + } + utils.WriteResponse(w, http.StatusOK, &report) +} + +func PodTop(w http.ResponseWriter, r *http.Request) { + runtime := r.Context().Value("runtime").(*libpod.Runtime) + decoder := r.Context().Value("decoder").(*schema.Decoder) + + query := struct { + PsArgs string `schema:"ps_args"` + }{ + PsArgs: "", + } + 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 } - utils.WriteResponse(w, http.StatusOK, "") + + name := utils.GetName(r) + pod, err := runtime.LookupPod(name) + if err != nil { + utils.ContainerNotFound(w, name, err) + return + } + + args := []string{} + if query.PsArgs != "" { + args = append(args, query.PsArgs) + } + output, err := pod.GetPodPidInformation(args) + if err != nil { + utils.InternalServerError(w, err) + return + } + + var body = handlers.PodTopOKBody{} + if len(output) > 0 { + body.Titles = strings.Split(output[0], "\t") + for _, line := range output[1:] { + body.Processes = append(body.Processes, strings.Split(line, "\t")) + } + } + utils.WriteJSON(w, http.StatusOK, body) } func PodKill(w http.ResponseWriter, r *http.Request) { @@ -314,6 +338,7 @@ func PodKill(w http.ResponseWriter, r *http.Request) { runtime = r.Context().Value("runtime").(*libpod.Runtime) decoder = r.Context().Value("decoder").(*schema.Decoder) signal = "SIGKILL" + errs []error ) query := struct { Signal string `schema:"signal"` @@ -356,12 +381,23 @@ func PodKill(w http.ResponseWriter, r *http.Request) { utils.Error(w, msg, http.StatusConflict, errors.Errorf("cannot kill a pod with no running containers: %s", pod.ID())) return } - _, err = pod.Kill(uint(sig)) + + responses, err := pod.Kill(uint(sig)) if err != nil { - utils.Error(w, "Something went wrong", http.StatusInternalServerError, err) + utils.Error(w, "failed to kill pod", http.StatusInternalServerError, err) return } - utils.WriteResponse(w, http.StatusOK, "") + + for _, v := range responses { + if v != nil { + errs = append(errs, v) + } + } + report := &entities.PodKillReport{ + Errs: errs, + Id: pod.ID(), + } + utils.WriteResponse(w, http.StatusOK, report) } func PodExists(w http.ResponseWriter, r *http.Request) { diff --git a/pkg/api/handlers/libpod/swagger.go b/pkg/api/handlers/libpod/swagger.go index 149fa10dc..1fad2dd1a 100644 --- a/pkg/api/handlers/libpod/swagger.go +++ b/pkg/api/handlers/libpod/swagger.go @@ -6,6 +6,7 @@ import ( "github.com/containers/image/v5/manifest" "github.com/containers/libpod/pkg/api/handlers/utils" + "github.com/containers/libpod/pkg/domain/entities" "github.com/pkg/errors" ) @@ -26,6 +27,55 @@ type swagInspectManifestResponse struct { Body manifest.List } +// Kill Pod +// swagger:response PodKillReport +type swagKillPodResponse struct { + // in:body + Body entities.PodKillReport +} + +// Pause pod +// swagger:response PodPauseReport +type swagPausePodResponse struct { + // in:body + Body entities.PodPauseReport +} + +// Unpause pod +// swagger:response PodUnpauseReport +type swagUnpausePodResponse struct { + // in:body + Body entities.PodUnpauseReport +} + +// Stop pod +// swagger:response PodStopReport +type swagStopPodResponse struct { + // in:body + Body entities.PodStopReport +} + +// Restart pod +// swagger:response PodRestartReport +type swagRestartPodResponse struct { + // in:body + Body entities.PodRestartReport +} + +// Start pod +// swagger:response PodStartReport +type swagStartPodResponse struct { + // in:body + Body entities.PodStartReport +} + +// Rm pod +// swagger:response PodRmReport +type swagRmPodResponse struct { + // in:body + Body entities.PodRmReport +} + func ServeSwagger(w http.ResponseWriter, r *http.Request) { path := DefaultPodmanSwaggerSpec if p, found := os.LookupEnv("PODMAN_SWAGGER_SPEC"); found { diff --git a/pkg/api/handlers/libpod/volumes.go b/pkg/api/handlers/libpod/volumes.go index e61d272f4..5a6fc021e 100644 --- a/pkg/api/handlers/libpod/volumes.go +++ b/pkg/api/handlers/libpod/volumes.go @@ -149,13 +149,20 @@ func ListVolumes(w http.ResponseWriter, r *http.Request) { func PruneVolumes(w http.ResponseWriter, r *http.Request) { var ( runtime = r.Context().Value("runtime").(*libpod.Runtime) + reports []*entities.VolumePruneReport ) pruned, err := runtime.PruneVolumes(r.Context()) if err != nil { utils.InternalServerError(w, err) return } - utils.WriteResponse(w, http.StatusOK, pruned) + for k, v := range pruned { + reports = append(reports, &entities.VolumePruneReport{ + Err: v, + Id: k, + }) + } + utils.WriteResponse(w, http.StatusOK, reports) } func RemoveVolume(w http.ResponseWriter, r *http.Request) { diff --git a/pkg/api/handlers/swagger.go b/pkg/api/handlers/swagger.go index 4ba123ba9..33a9fdd58 100644 --- a/pkg/api/handlers/swagger.go +++ b/pkg/api/handlers/swagger.go @@ -2,7 +2,9 @@ package handlers import ( "github.com/containers/libpod/libpod" + "github.com/containers/libpod/libpod/define" "github.com/containers/libpod/libpod/image" + "github.com/containers/libpod/pkg/domain/entities" "github.com/containers/libpod/pkg/inspect" "github.com/docker/docker/api/types" ) @@ -29,14 +31,14 @@ type swagImageInspect struct { // swagger:response DocsLibpodImagesLoadResponse type swagLibpodImagesLoadResponse struct { // in:body - Body []LibpodImagesLoadReport + Body entities.ImageLoadReport } // Import response // swagger:response DocsLibpodImagesImportResponse type swagLibpodImagesImportResponse struct { // in:body - Body LibpodImagesImportReport + Body entities.ImageImportReport } // Pull response @@ -95,20 +97,29 @@ type swagContainerInspectResponse struct { } // List processes in container -// swagger:response DockerTopResponse -type swagDockerTopResponse struct { +// swagger:response DocsContainerTopResponse +type swagContainerTopResponse struct { // in:body Body struct { ContainerTopOKBody } } +// List processes in pod +// swagger:response DocsPodTopResponse +type swagPodTopResponse struct { + // in:body + Body struct { + PodTopOKBody + } +} + // Inspect container // swagger:response LibpodInspectContainerResponse type swagLibpodInspectContainerResponse struct { // in:body Body struct { - libpod.InspectContainerData + define.InspectContainerData } } @@ -116,7 +127,7 @@ type swagLibpodInspectContainerResponse struct { // swagger:response ListPodsResponse type swagListPodsResponse struct { // in:body - Body []libpod.PodInspect + Body []entities.ListPodsReport } // Inspect pod diff --git a/pkg/api/handlers/types.go b/pkg/api/handlers/types.go index 84ca0fbed..496512f2e 100644 --- a/pkg/api/handlers/types.go +++ b/pkg/api/handlers/types.go @@ -38,10 +38,6 @@ type LibpodImagesLoadReport struct { ID string `json:"id"` } -type LibpodImagesImportReport struct { - ID string `json:"id"` -} - type LibpodImagesPullReport struct { ID string `json:"id"` } @@ -133,6 +129,10 @@ type ContainerTopOKBody struct { dockerContainer.ContainerTopOKBody } +type PodTopOKBody struct { + dockerContainer.ContainerTopOKBody +} + // swagger:model PodCreateConfig type PodCreateConfig struct { Name string `json:"name"` @@ -172,6 +172,14 @@ type ImageTreeResponse struct { Layers []ImageLayer `json:"layers"` } +type ExecCreateConfig struct { + docker.ExecConfig +} + +type ExecCreateResponse struct { + docker.IDResponse +} + func EventToApiEvent(e *events.Event) *Event { return &Event{dockerEvents.Message{ Type: e.Type.String(), diff --git a/pkg/api/handlers/utils/handler.go b/pkg/api/handlers/utils/handler.go index 32b8c5b0a..b5bd488fb 100644 --- a/pkg/api/handlers/utils/handler.go +++ b/pkg/api/handlers/utils/handler.go @@ -46,6 +46,13 @@ func WriteResponse(w http.ResponseWriter, code int, value interface{}) { if _, err := io.Copy(w, v); err != nil { logrus.Errorf("unable to copy to response: %q", err) } + case io.Reader: + w.Header().Set("Content-Type", "application/x-tar") + w.WriteHeader(code) + + if _, err := io.Copy(w, v); err != nil { + logrus.Errorf("unable to copy to response: %q", err) + } default: WriteJSON(w, code, value) } diff --git a/pkg/api/handlers/utils/images.go b/pkg/api/handlers/utils/images.go index 696d5f745..1c67de9db 100644 --- a/pkg/api/handlers/utils/images.go +++ b/pkg/api/handlers/utils/images.go @@ -4,11 +4,52 @@ import ( "fmt" "net/http" + "github.com/containers/image/v5/docker" + "github.com/containers/image/v5/storage" + "github.com/containers/image/v5/transports/alltransports" + "github.com/containers/image/v5/types" "github.com/containers/libpod/libpod" "github.com/containers/libpod/libpod/image" "github.com/gorilla/schema" + "github.com/pkg/errors" ) +// ParseDockerReference parses the specified image name to a +// `types.ImageReference` and enforces it to refer to a docker-transport +// reference. +func ParseDockerReference(name string) (types.ImageReference, error) { + dockerPrefix := fmt.Sprintf("%s://", docker.Transport.Name()) + imageRef, err := alltransports.ParseImageName(name) + if err == nil && imageRef.Transport().Name() != docker.Transport.Name() { + return nil, errors.Errorf("reference %q must be a docker reference", name) + } else if err != nil { + origErr := err + imageRef, err = alltransports.ParseImageName(fmt.Sprintf("%s%s", dockerPrefix, name)) + if err != nil { + return nil, errors.Wrapf(origErr, "reference %q must be a docker reference", name) + } + } + return imageRef, nil +} + +// ParseStorageReference parses the specified image name to a +// `types.ImageReference` and enforces it to refer to a +// containers-storage-transport reference. +func ParseStorageReference(name string) (types.ImageReference, error) { + storagePrefix := fmt.Sprintf("%s:", storage.Transport.Name()) + imageRef, err := alltransports.ParseImageName(name) + if err == nil && imageRef.Transport().Name() != docker.Transport.Name() { + return nil, errors.Errorf("reference %q must be a storage reference", name) + } else if err != nil { + origErr := err + imageRef, err = alltransports.ParseImageName(fmt.Sprintf("%s%s", storagePrefix, name)) + if err != nil { + return nil, errors.Wrapf(origErr, "reference %q must be a storage reference", name) + } + } + return imageRef, nil +} + // GetImages is a common function used to get images for libpod and other compatibility // mechanisms func GetImages(w http.ResponseWriter, r *http.Request) ([]*image.Image, error) { diff --git a/pkg/api/handlers/utils/pods.go b/pkg/api/handlers/utils/pods.go index 266ad9a4b..d47053eda 100644 --- a/pkg/api/handlers/utils/pods.go +++ b/pkg/api/handlers/utils/pods.go @@ -6,10 +6,16 @@ import ( "github.com/containers/libpod/cmd/podman/shared" "github.com/containers/libpod/libpod" + "github.com/containers/libpod/pkg/domain/entities" "github.com/gorilla/schema" ) -func GetPods(w http.ResponseWriter, r *http.Request) ([]*libpod.Pod, error) { +func GetPods(w http.ResponseWriter, r *http.Request) ([]*entities.ListPodsReport, error) { + var ( + lps []*entities.ListPodsReport + pods []*libpod.Pod + podErr error + ) runtime := r.Context().Value("runtime").(*libpod.Runtime) decoder := r.Context().Value("decoder").(*schema.Decoder) @@ -37,9 +43,47 @@ func GetPods(w http.ResponseWriter, r *http.Request) ([]*libpod.Pod, error) { if err != nil { return nil, err } - return shared.FilterAllPodsWithFilterFunc(runtime, filterFuncs...) + pods, podErr = shared.FilterAllPodsWithFilterFunc(runtime, filterFuncs...) + } else { + pods, podErr = runtime.GetAllPods() } - - return runtime.GetAllPods() - + if podErr != nil { + return nil, podErr + } + for _, pod := range pods { + status, err := pod.GetPodStatus() + if err != nil { + return nil, err + } + ctrs, err := pod.AllContainers() + if err != nil { + return nil, err + } + infraId, err := pod.InfraContainerID() + if err != nil { + return nil, err + } + lp := entities.ListPodsReport{ + Cgroup: pod.CgroupParent(), + Created: pod.CreatedTime(), + Id: pod.ID(), + Name: pod.Name(), + Namespace: pod.Namespace(), + Status: status, + InfraId: infraId, + } + for _, ctr := range ctrs { + state, err := ctr.State() + if err != nil { + return nil, err + } + lp.Containers = append(lp.Containers, &entities.ListPodContainer{ + Id: ctr.ID(), + Names: ctr.Name(), + Status: state.String(), + }) + } + lps = append(lps, &lp) + } + return lps, nil } diff --git a/pkg/api/server/register_containers.go b/pkg/api/server/register_containers.go index 2656d1d89..f126112d0 100644 --- a/pkg/api/server/register_containers.go +++ b/pkg/api/server/register_containers.go @@ -429,7 +429,7 @@ func (s *APIServer) registerContainersHandlers(r *mux.Router) error { // - application/json // responses: // 200: - // $ref: "#/responses/DockerTopResponse" + // $ref: "#/responses/DocsContainerTopResponse" // 404: // $ref: "#/responses/NoSuchContainer" // 500: @@ -587,6 +587,29 @@ func (s *APIServer) registerContainersHandlers(r *mux.Router) error { r.HandleFunc(VersionedPath("/containers/{name}/resize"), s.APIHandler(compat.ResizeContainer)).Methods(http.MethodPost) // Added non version path to URI to support docker non versioned paths r.HandleFunc("/containers/{name}/resize", s.APIHandler(compat.ResizeContainer)).Methods(http.MethodPost) + // swagger:operation GET /containers/{name}/export compat exportContainer + // --- + // tags: + // - containers (compat) + // summary: Export a container + // description: Export the contents of a container as a tarball. + // parameters: + // - in: path + // name: name + // type: string + // required: true + // description: the name or ID of the container + // produces: + // - application/json + // responses: + // 200: + // description: tarball is returned in body + // 404: + // $ref: "#/responses/NoSuchContainer" + // 500: + // $ref: "#/responses/InternalError" + r.HandleFunc(VersionedPath("/containers/{name}/export"), s.APIHandler(compat.ExportContainer)).Methods(http.MethodGet) + r.HandleFunc("/containers/{name}/export", s.APIHandler(compat.ExportContainer)).Methods(http.MethodGet) /* libpod endpoints @@ -1041,7 +1064,7 @@ func (s *APIServer) registerContainersHandlers(r *mux.Router) error { // - application/json // responses: // 200: - // $ref: "#/responses/DockerTopResponse" + // $ref: "#/responses/DocsContainerTopResponse" // 404: // $ref: "#/responses/NoSuchContainer" // 500: @@ -1237,5 +1260,122 @@ func (s *APIServer) registerContainersHandlers(r *mux.Router) error { // 500: // $ref: "#/responses/InternalError" r.HandleFunc(VersionedPath("/libpod/containers/{name}/resize"), s.APIHandler(compat.ResizeContainer)).Methods(http.MethodPost) + // swagger:operation GET /libpod/containers/{name}/export libpod libpodExportContainer + // --- + // tags: + // - containers + // summary: Export a container + // description: Export the contents of a container as a tarball. + // parameters: + // - in: path + // name: name + // type: string + // required: true + // description: the name or ID of the container + // produces: + // - application/json + // responses: + // 200: + // description: tarball is returned in body + // 404: + // $ref: "#/responses/NoSuchContainer" + // 500: + // $ref: "#/responses/InternalError" + r.HandleFunc(VersionedPath("/libpod/containers/{name}/export"), s.APIHandler(compat.ExportContainer)).Methods(http.MethodGet) + // swagger:operation GET /libpod/containers/{name}/checkout libpod libpodCheckpointContainer + // --- + // tags: + // - containers + // summary: Checkpoint a container + // parameters: + // - in: path + // name: name + // type: string + // required: true + // description: the name or ID of the container + // - in: query + // name: keep + // type: boolean + // description: keep all temporary checkpoint files + // - in: query + // name: leaveRunning + // type: boolean + // description: leave the container running after writing checkpoint to disk + // - in: query + // name: tcpEstablished + // type: boolean + // description: checkpoint a container with established TCP connections + // - in: query + // name: export + // type: boolean + // description: export the checkpoint image to a tar.gz + // - in: query + // name: ignoreRootFS + // type: boolean + // description: do not include root file-system changes when exporting + // produces: + // - application/json + // responses: + // 200: + // description: tarball is returned in body if exported + // 404: + // $ref: "#/responses/NoSuchContainer" + // 500: + // $ref: "#/responses/InternalError" + r.HandleFunc(VersionedPath("/libpod/containers/{name}/checkpoint"), s.APIHandler(libpod.Checkpoint)).Methods(http.MethodPost) + // swagger:operation GET /libpod/containers/{name} restore libpod libpodRestoreContainer + // --- + // tags: + // - containers + // summary: Restore a container + // description: Restore a container from a checkpoint. + // parameters: + // - in: path + // name: name + // type: string + // required: true + // description: the name or id of the container + // - in: query + // name: name + // type: string + // description: the name of the container when restored from a tar. can only be used with import + // - in: query + // name: keep + // type: boolean + // description: keep all temporary checkpoint files + // - in: query + // name: leaveRunning + // type: boolean + // description: leave the container running after writing checkpoint to disk + // - in: query + // name: tcpEstablished + // type: boolean + // description: checkpoint a container with established TCP connections + // - in: query + // name: import + // type: boolean + // description: import the restore from a checkpoint tar.gz + // - in: query + // name: ignoreRootFS + // type: boolean + // description: do not include root file-system changes when exporting + // - in: query + // name: ignoreStaticIP + // type: boolean + // description: ignore IP address if set statically + // - in: query + // name: ignoreStaticMAC + // type: boolean + // description: ignore MAC address if set statically + // produces: + // - application/json + // responses: + // 200: + // description: tarball is returned in body if exported + // 404: + // $ref: "#/responses/NoSuchContainer" + // 500: + // $ref: "#/responses/InternalError" + r.HandleFunc(VersionedPath("/libpod/containers/{name}/restore"), s.APIHandler(libpod.Restore)).Methods(http.MethodPost) return nil } diff --git a/pkg/api/server/register_exec.go b/pkg/api/server/register_exec.go index d27d21a04..71fb50307 100644 --- a/pkg/api/server/register_exec.go +++ b/pkg/api/server/register_exec.go @@ -8,7 +8,7 @@ import ( ) func (s *APIServer) registerExecHandlers(r *mux.Router) error { - // swagger:operation POST /containers/{name}/create compat createExec + // swagger:operation POST /containers/{name}/exec compat createExec // --- // tags: // - exec (compat) @@ -74,9 +74,9 @@ func (s *APIServer) registerExecHandlers(r *mux.Router) error { // description: container is paused // 500: // $ref: "#/responses/InternalError" - r.Handle(VersionedPath("/containers/{name}/create"), s.APIHandler(compat.UnsupportedHandler)).Methods(http.MethodPost) + r.Handle(VersionedPath("/containers/{name}/exec"), s.APIHandler(compat.ExecCreateHandler)).Methods(http.MethodPost) // Added non version path to URI to support docker non versioned paths - r.Handle("/containers/{name}/create", s.APIHandler(compat.UnsupportedHandler)).Methods(http.MethodPost) + r.Handle("/containers/{name}/exec", s.APIHandler(compat.ExecCreateHandler)).Methods(http.MethodPost) // swagger:operation POST /exec/{id}/start compat startExec // --- // tags: @@ -169,15 +169,15 @@ func (s *APIServer) registerExecHandlers(r *mux.Router) error { // $ref: "#/responses/NoSuchExecInstance" // 500: // $ref: "#/responses/InternalError" - r.Handle(VersionedPath("/exec/{id}/json"), s.APIHandler(compat.UnsupportedHandler)).Methods(http.MethodGet) + r.Handle(VersionedPath("/exec/{id}/json"), s.APIHandler(compat.ExecInspectHandler)).Methods(http.MethodGet) // Added non version path to URI to support docker non versioned paths - r.Handle("/exec/{id}/json", s.APIHandler(compat.UnsupportedHandler)).Methods(http.MethodGet) + r.Handle("/exec/{id}/json", s.APIHandler(compat.ExecInspectHandler)).Methods(http.MethodGet) /* libpod api follows */ - // swagger:operation POST /libpod/containers/{name}/create libpod libpodCreateExec + // swagger:operation POST /libpod/containers/{name}/exec libpod libpodCreateExec // --- // tags: // - exec @@ -243,7 +243,7 @@ func (s *APIServer) registerExecHandlers(r *mux.Router) error { // description: container is paused // 500: // $ref: "#/responses/InternalError" - r.Handle(VersionedPath("/libpod/containers/{name}/create"), s.APIHandler(compat.UnsupportedHandler)).Methods(http.MethodPost) + r.Handle(VersionedPath("/libpod/containers/{name}/exec"), s.APIHandler(compat.ExecCreateHandler)).Methods(http.MethodPost) // swagger:operation POST /libpod/exec/{id}/start libpod libpodStartExec // --- // tags: @@ -332,6 +332,6 @@ func (s *APIServer) registerExecHandlers(r *mux.Router) error { // $ref: "#/responses/NoSuchExecInstance" // 500: // $ref: "#/responses/InternalError" - r.Handle(VersionedPath("/libpod/exec/{id}/json"), s.APIHandler(compat.UnsupportedHandler)).Methods(http.MethodGet) + r.Handle(VersionedPath("/libpod/exec/{id}/json"), s.APIHandler(compat.ExecInspectHandler)).Methods(http.MethodGet) return nil } diff --git a/pkg/api/server/register_images.go b/pkg/api/server/register_images.go index e8dfe2fa8..d45423096 100644 --- a/pkg/api/server/register_images.go +++ b/pkg/api/server/register_images.go @@ -211,6 +211,41 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error { r.Handle(VersionedPath("/images/{name:.*}"), s.APIHandler(compat.RemoveImage)).Methods(http.MethodDelete) // Added non version path to URI to support docker non versioned paths r.Handle("/images/{name:.*}", s.APIHandler(compat.RemoveImage)).Methods(http.MethodDelete) + // swagger:operation POST /images/{name:.*}/push compat pushImage + // --- + // tags: + // - images (compat) + // summary: Push Image + // description: Push an image to a container registry + // parameters: + // - in: path + // name: name:.* + // type: string + // required: true + // description: Name of image to push. + // - in: query + // name: tag + // type: string + // description: The tag to associate with the image on the registry. + // - in: header + // name: X-Registry-Auth + // type: string + // description: A base64-encoded auth configuration. + // produces: + // - application/json + // responses: + // 200: + // description: no error + // schema: + // type: string + // format: binary + // 404: + // $ref: '#/responses/NoSuchImage' + // 500: + // $ref: '#/responses/InternalError' + r.Handle(VersionedPath("/images/{name:.*}/push"), s.APIHandler(compat.PushImage)).Methods(http.MethodPost) + // Added non version path to URI to support docker non versioned paths + r.Handle("/images/{name:.*}/push", s.APIHandler(compat.PushImage)).Methods(http.MethodPost) // swagger:operation GET /images/{name:.*}/get compat exportImage // --- // tags: @@ -583,6 +618,43 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error { libpod endpoints */ + // swagger:operation POST /libpod/images/{name:.*}/push libpod libpodPushImage + // --- + // tags: + // - images (libpod) + // summary: Push Image + // description: Push an image to a container registry + // parameters: + // - in: path + // name: name:.* + // type: string + // required: true + // description: Name of image to push. + // - in: query + // name: tag + // type: string + // description: The tag to associate with the image on the registry. + // - in: query + // name: credentials + // description: username:password for the registry. + // type: string + // - in: header + // name: X-Registry-Auth + // type: string + // description: A base64-encoded auth configuration. + // produces: + // - application/json + // responses: + // 200: + // description: no error + // schema: + // type: string + // format: binary + // 404: + // $ref: '#/responses/NoSuchImage' + // 500: + // $ref: '#/responses/InternalError' + r.Handle(VersionedPath("/libpod/images/{name:.*}/push"), s.APIHandler(libpod.PushImage)).Methods(http.MethodPost) // swagger:operation GET /libpod/images/{name:.*}/exists libpod libpodImageExists // --- // tags: @@ -883,7 +955,7 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error { // tags: // - images // summary: Export an image - // description: Export an image as a tarball + // description: Export an image // parameters: // - in: path // name: name:.* @@ -1019,5 +1091,39 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error { // 500: // $ref: '#/responses/InternalError' r.Handle(VersionedPath("/libpod/commit"), s.APIHandler(libpod.CommitContainer)).Methods(http.MethodPost) + // swagger:operation POST /libpod/images/{name:.*}/untag libpod libpodUntagImage + // --- + // tags: + // - images + // summary: Untag an image + // description: Untag an image + // parameters: + // - in: path + // name: name:.* + // type: string + // required: true + // description: the name or ID of the container + // - in: query + // name: repo + // type: string + // description: the repository to untag + // - in: query + // name: tag + // type: string + // description: the name of the tag to untag + // produces: + // - application/json + // responses: + // 201: + // description: no error + // 400: + // $ref: '#/responses/BadParamError' + // 404: + // $ref: '#/responses/NoSuchImage' + // 409: + // $ref: '#/responses/ConflictError' + // 500: + // $ref: '#/responses/InternalError' + r.Handle(VersionedPath("/libpod/images/{name:.*}/untag"), s.APIHandler(libpod.UntagImage)).Methods(http.MethodPost) return nil } diff --git a/pkg/api/server/register_pods.go b/pkg/api/server/register_pods.go index af2330665..a49bf1f63 100644 --- a/pkg/api/server/register_pods.go +++ b/pkg/api/server/register_pods.go @@ -37,7 +37,7 @@ func (s *APIServer) registerPodsHandlers(r *mux.Router) error { // description: attributes for creating a pod // schema: // type: object - // $ref: "#/definitions/PodCreateConfig" + // $ref: "#/definitions/PodSpecGenerator" // responses: // 200: // $ref: "#/definitions/IdResponse" @@ -81,8 +81,8 @@ func (s *APIServer) registerPodsHandlers(r *mux.Router) error { // type: boolean // description : force removal of a running pod by first stopping all containers, then removing all containers in the pod // responses: - // 204: - // description: no error + // 200: + // $ref: '#/responses/PodRmReport' // 400: // $ref: "#/responses/BadParamError" // 404: @@ -146,8 +146,8 @@ func (s *APIServer) registerPodsHandlers(r *mux.Router) error { // description: signal to be sent to pod // default: SIGKILL // responses: - // 204: - // description: no error + // 200: + // $ref: "#/responses/PodKillReport" // 400: // $ref: "#/responses/BadParamError" // 404: @@ -170,8 +170,8 @@ func (s *APIServer) registerPodsHandlers(r *mux.Router) error { // required: true // description: the name or ID of the pod // responses: - // 204: - // description: no error + // 200: + // $ref: '#/responses/PodPauseReport' // 404: // $ref: "#/responses/NoSuchPod" // 500: @@ -189,8 +189,8 @@ func (s *APIServer) registerPodsHandlers(r *mux.Router) error { // required: true // description: the name or ID of the pod // responses: - // 204: - // description: no error + // 200: + // $ref: '#/responses/PodRestartReport' // 404: // $ref: "#/responses/NoSuchPod" // 500: @@ -208,8 +208,8 @@ func (s *APIServer) registerPodsHandlers(r *mux.Router) error { // required: true // description: the name or ID of the pod // responses: - // 204: - // description: no error + // 200: + // $ref: '#/responses/PodStartReport' // 304: // $ref: "#/responses/PodAlreadyStartedError" // 404: @@ -233,8 +233,8 @@ func (s *APIServer) registerPodsHandlers(r *mux.Router) error { // type: integer // description: timeout // responses: - // 204: - // description: no error + // 200: + // $ref: '#/responses/PodStopReport' // 304: // $ref: "#/responses/PodAlreadyStoppedError" // 400: @@ -256,12 +256,43 @@ func (s *APIServer) registerPodsHandlers(r *mux.Router) error { // required: true // description: the name or ID of the pod // responses: - // 204: - // description: no error + // 200: + // $ref: '#/responses/PodUnpauseReport' // 404: // $ref: "#/responses/NoSuchPod" // 500: // $ref: "#/responses/InternalError" r.Handle(VersionedPath("/libpod/pods/{name}/unpause"), s.APIHandler(libpod.PodUnpause)).Methods(http.MethodPost) + // swagger:operation GET /libpod/pods/{name}/top pods topPod + // --- + // summary: List processes + // description: List processes running inside a pod + // produces: + // - application/json + // parameters: + // - in: path + // name: name + // type: string + // required: true + // description: | + // Name of pod to query for processes + // - in: query + // name: stream + // type: boolean + // default: true + // description: Stream the output + // - in: query + // name: ps_args + // type: string + // default: -ef + // description: arguments to pass to ps such as aux. Requires ps(1) to be installed in the container if no ps(1) compatible AIX descriptors are used. + // responses: + // 200: + // $ref: "#/responses/DocsPodTopResponse" + // 404: + // $ref: "#/responses/NoSuchContainer" + // 500: + // $ref: "#/responses/InternalError" + r.Handle(VersionedPath("/libpod/pods/{name}/top"), s.APIHandler(libpod.PodTop)).Methods(http.MethodGet) return nil } diff --git a/pkg/api/server/swagger.go b/pkg/api/server/swagger.go index 9156f3f8a..2433a6a05 100644 --- a/pkg/api/server/swagger.go +++ b/pkg/api/server/swagger.go @@ -2,6 +2,7 @@ package server import ( "github.com/containers/libpod/libpod" + "github.com/containers/libpod/libpod/define" "github.com/containers/libpod/pkg/api/handlers/utils" "github.com/containers/libpod/pkg/domain/entities" ) @@ -178,6 +179,6 @@ type swagVolumeListResponse struct { type swagHealthCheckRunResponse struct { // in:body Body struct { - libpod.HealthCheckResults + define.HealthCheckResults } } diff --git a/pkg/apparmor/apparmor.go b/pkg/apparmor/apparmor.go index 1e824550d..8e17361cb 100644 --- a/pkg/apparmor/apparmor.go +++ b/pkg/apparmor/apparmor.go @@ -3,14 +3,15 @@ package apparmor import ( "errors" + "github.com/containers/common/pkg/config" libpodVersion "github.com/containers/libpod/version" ) var ( // DefaultLipodProfilePrefix is used for version-independent presence checks. - DefaultLipodProfilePrefix = "libpod-default" + "-" + DefaultLipodProfilePrefix = config.DefaultApparmorProfile // DefaultLibpodProfile is the name of default libpod AppArmor profile. - DefaultLibpodProfile = DefaultLipodProfilePrefix + libpodVersion.Version + DefaultLibpodProfile = DefaultLipodProfilePrefix + "-" + libpodVersion.Version // ErrApparmorUnsupported indicates that AppArmor support is not supported. ErrApparmorUnsupported = errors.New("AppArmor is not supported") // ErrApparmorRootless indicates that AppArmor support is not supported in rootless mode. diff --git a/pkg/bindings/containers/checkpoint.go b/pkg/bindings/containers/checkpoint.go new file mode 100644 index 000000000..84924587b --- /dev/null +++ b/pkg/bindings/containers/checkpoint.go @@ -0,0 +1,79 @@ +package containers + +import ( + "context" + "net/http" + "net/url" + "strconv" + + "github.com/containers/libpod/pkg/bindings" + "github.com/containers/libpod/pkg/domain/entities" +) + +// Checkpoint checkpoints the given container (identified by nameOrId). All additional +// options are options and allow for more fine grained control of the checkpoint process. +func Checkpoint(ctx context.Context, nameOrId string, keep, leaveRunning, tcpEstablished, ignoreRootFS *bool, export *string) (*entities.CheckpointReport, error) { + var report entities.CheckpointReport + conn, err := bindings.GetClient(ctx) + if err != nil { + return nil, err + } + params := url.Values{} + if keep != nil { + params.Set("keep", strconv.FormatBool(*keep)) + } + if leaveRunning != nil { + params.Set("leaveRunning", strconv.FormatBool(*leaveRunning)) + } + if tcpEstablished != nil { + params.Set("TCPestablished", strconv.FormatBool(*tcpEstablished)) + } + if ignoreRootFS != nil { + params.Set("ignoreRootFS", strconv.FormatBool(*ignoreRootFS)) + } + if export != nil { + params.Set("export", *export) + } + response, err := conn.DoRequest(nil, http.MethodPost, "/containers/%s/checkpoint", params, nameOrId) + if err != nil { + return nil, err + } + return &report, response.Process(&report) +} + +// Restore restores a checkpointed container to running. The container is identified by the nameOrId option. All +// additional options are optional and allow finer control of the restore processs. +func Restore(ctx context.Context, nameOrId string, keep, tcpEstablished, ignoreRootFS, ignoreStaticIP, ignoreStaticMAC *bool, name, importArchive *string) (*entities.RestoreReport, error) { + var report entities.RestoreReport + conn, err := bindings.GetClient(ctx) + if err != nil { + return nil, err + } + params := url.Values{} + if keep != nil { + params.Set("keep", strconv.FormatBool(*keep)) + } + if tcpEstablished != nil { + params.Set("TCPestablished", strconv.FormatBool(*tcpEstablished)) + } + if ignoreRootFS != nil { + params.Set("ignoreRootFS", strconv.FormatBool(*ignoreRootFS)) + } + if ignoreStaticIP != nil { + params.Set("ignoreStaticIP", strconv.FormatBool(*ignoreStaticIP)) + } + if ignoreStaticMAC != nil { + params.Set("ignoreStaticMAC", strconv.FormatBool(*ignoreStaticMAC)) + } + if name != nil { + params.Set("name", *name) + } + if importArchive != nil { + params.Set("import", *importArchive) + } + response, err := conn.DoRequest(nil, http.MethodPost, "/containers/%s/restore", params, nameOrId) + if err != nil { + return nil, err + } + return &report, response.Process(&report) +} diff --git a/pkg/bindings/containers/containers.go b/pkg/bindings/containers/containers.go index 231b6f232..49a2dfd58 100644 --- a/pkg/bindings/containers/containers.go +++ b/pkg/bindings/containers/containers.go @@ -2,12 +2,14 @@ package containers import ( "context" + "io" "net/http" "net/url" "strconv" + "strings" - "github.com/containers/libpod/libpod" "github.com/containers/libpod/libpod/define" + "github.com/containers/libpod/pkg/api/handlers" lpapiv2 "github.com/containers/libpod/pkg/api/handlers/libpod" "github.com/containers/libpod/pkg/bindings" ) @@ -106,7 +108,7 @@ func Remove(ctx context.Context, nameOrID string, force, volumes *bool) error { // or a partial/full ID. The size bool determines whether the size of the container's root filesystem // should be calculated. Calculating the size of a container requires extra work from the filesystem and // is therefore slower. -func Inspect(ctx context.Context, nameOrID string, size *bool) (*libpod.InspectContainerData, error) { +func Inspect(ctx context.Context, nameOrID string, size *bool) (*define.InspectContainerData, error) { conn, err := bindings.GetClient(ctx) if err != nil { return nil, err @@ -119,7 +121,7 @@ func Inspect(ctx context.Context, nameOrID string, size *bool) (*libpod.InspectC if err != nil { return nil, err } - inspect := libpod.InspectContainerData{} + inspect := define.InspectContainerData{} return &inspect, response.Process(&inspect) } @@ -194,7 +196,40 @@ func Start(ctx context.Context, nameOrID string, detachKeys *string) error { } func Stats() {} -func Top() {} + +// Top gathers statistics about the running processes in a container. The nameOrID can be a container name +// or a partial/full ID. The descriptors allow for specifying which data to collect from the process. +func Top(ctx context.Context, nameOrID string, descriptors []string) ([]string, error) { + conn, err := bindings.GetClient(ctx) + if err != nil { + return nil, err + } + params := url.Values{} + + if len(descriptors) > 0 { + // flatten the slice into one string + params.Set("ps_args", strings.Join(descriptors, ",")) + } + response, err := conn.DoRequest(nil, http.MethodGet, "/containers/%s/top", params, nameOrID) + if err != nil { + return nil, err + } + + body := handlers.ContainerTopOKBody{} + if err = response.Process(&body); err != nil { + return nil, err + } + + // handlers.ContainerTopOKBody{} returns a slice of slices where each cell in the top table is an item. + // In libpod land, we're just using a slice with cells being split by tabs, which allows for an idiomatic + // usage of the tabwriter. + topOutput := []string{strings.Join(body.Titles, "\t")} + for _, out := range body.Processes { + topOutput = append(topOutput, strings.Join(out, "\t")) + } + + return topOutput, err +} // Unpause resumes the given paused container. The nameOrID can be a container name // or a partial/full ID. @@ -262,3 +297,22 @@ func Stop(ctx context.Context, nameOrID string, timeout *uint) error { } return response.Process(nil) } + +// Export creates a tarball of the given name or ID of a container. It +// requires an io.Writer be provided to write the tarball. +func Export(ctx context.Context, nameOrID string, w io.Writer) error { + params := url.Values{} + conn, err := bindings.GetClient(ctx) + if err != nil { + return err + } + response, err := conn.DoRequest(nil, http.MethodGet, "/containers/%s/export", params, nameOrID) + if err != nil { + return err + } + if response.StatusCode/100 == 2 { + _, err = io.Copy(w, response.Body) + return err + } + return response.Process(nil) +} diff --git a/pkg/bindings/containers/exec.go b/pkg/bindings/containers/exec.go new file mode 100644 index 000000000..48f9ed697 --- /dev/null +++ b/pkg/bindings/containers/exec.go @@ -0,0 +1,71 @@ +package containers + +import ( + "context" + "net/http" + "strings" + + "github.com/containers/libpod/libpod/define" + "github.com/containers/libpod/pkg/api/handlers" + "github.com/containers/libpod/pkg/bindings" + jsoniter "github.com/json-iterator/go" + "github.com/pkg/errors" + "github.com/sirupsen/logrus" +) + +var json = jsoniter.ConfigCompatibleWithStandardLibrary + +// ExecCreate creates a new exec session in an existing container. +// The exec session will not be started; that is done with ExecStart. +// Returns ID of new exec session, or an error if one occurred. +func ExecCreate(ctx context.Context, nameOrID string, config *handlers.ExecCreateConfig) (string, error) { + conn, err := bindings.GetClient(ctx) + if err != nil { + return "", err + } + + if config == nil { + return "", errors.Errorf("must provide a configuration for exec session") + } + + requestJSON, err := json.Marshal(config) + if err != nil { + return "", errors.Wrapf(err, "error marshalling exec config to JSON") + } + jsonReader := strings.NewReader(string(requestJSON)) + + resp, err := conn.DoRequest(jsonReader, http.MethodPost, "/containers/%s/exec", nil, nameOrID) + if err != nil { + return "", err + } + + respStruct := new(handlers.ExecCreateResponse) + if err := resp.Process(respStruct); err != nil { + return "", err + } + + return respStruct.ID, nil +} + +// ExecInspect inspects an existing exec session, returning detailed information +// about it. +func ExecInspect(ctx context.Context, sessionID string) (*define.InspectExecSession, error) { + conn, err := bindings.GetClient(ctx) + if err != nil { + return nil, err + } + + logrus.Debugf("Inspecting session ID %q", sessionID) + + resp, err := conn.DoRequest(nil, http.MethodGet, "/exec/%s/json", nil, sessionID) + if err != nil { + return nil, err + } + + respStruct := new(define.InspectExecSession) + if err := resp.Process(respStruct); err != nil { + return nil, err + } + + return respStruct, nil +} diff --git a/pkg/bindings/containers/healthcheck.go b/pkg/bindings/containers/healthcheck.go index 85cc2814c..2b783ac73 100644 --- a/pkg/bindings/containers/healthcheck.go +++ b/pkg/bindings/containers/healthcheck.go @@ -4,19 +4,19 @@ import ( "context" "net/http" - "github.com/containers/libpod/libpod" + "github.com/containers/libpod/libpod/define" "github.com/containers/libpod/pkg/bindings" ) // RunHealthCheck executes the container's healthcheck and returns the health status of the // container. -func RunHealthCheck(ctx context.Context, nameOrID string) (*libpod.HealthCheckResults, error) { +func RunHealthCheck(ctx context.Context, nameOrID string) (*define.HealthCheckResults, error) { conn, err := bindings.GetClient(ctx) if err != nil { return nil, err } var ( - status libpod.HealthCheckResults + status define.HealthCheckResults ) response, err := conn.DoRequest(nil, http.MethodGet, "/containers/%s/healthcheck", nil, nameOrID) if err != nil { diff --git a/pkg/bindings/images/images.go b/pkg/bindings/images/images.go index e67965042..1b3df609b 100644 --- a/pkg/bindings/images/images.go +++ b/pkg/bindings/images/images.go @@ -3,15 +3,16 @@ package images import ( "context" "errors" + "fmt" "io" "net/http" "net/url" "strconv" + "github.com/containers/image/v5/types" "github.com/containers/libpod/pkg/api/handlers" "github.com/containers/libpod/pkg/bindings" "github.com/containers/libpod/pkg/domain/entities" - "github.com/containers/libpod/pkg/inspect" ) // Exists a lightweight way to determine if an image exists in local storage. It returns a @@ -56,7 +57,7 @@ func List(ctx context.Context, all *bool, filters map[string][]string) ([]*entit // Get performs an image inspect. To have the on-disk size of the image calculated, you can // use the optional size parameter. -func GetImage(ctx context.Context, nameOrID string, size *bool) (*inspect.ImageData, error) { +func GetImage(ctx context.Context, nameOrID string, size *bool) (*entities.ImageData, error) { conn, err := bindings.GetClient(ctx) if err != nil { return nil, err @@ -65,7 +66,7 @@ func GetImage(ctx context.Context, nameOrID string, size *bool) (*inspect.ImageD if size != nil { params.Set("size", strconv.FormatBool(*size)) } - inspectedData := inspect.ImageData{} + inspectedData := entities.ImageData{} response, err := conn.DoRequest(nil, http.MethodGet, "/images/%s/json", params, nameOrID) if err != nil { return &inspectedData, err @@ -91,11 +92,11 @@ func History(ctx context.Context, nameOrID string) ([]*handlers.HistoryResponse, return history, response.Process(&history) } -func Load(ctx context.Context, r io.Reader, name *string) (string, error) { - var id handlers.IDResponse +func Load(ctx context.Context, r io.Reader, name *string) (*entities.ImageLoadReport, error) { + var report entities.ImageLoadReport conn, err := bindings.GetClient(ctx) if err != nil { - return "", err + return nil, err } params := url.Values{} if name != nil { @@ -103,9 +104,9 @@ func Load(ctx context.Context, r io.Reader, name *string) (string, error) { } response, err := conn.DoRequest(r, http.MethodPost, "/images/load", params) if err != nil { - return "", err + return nil, err } - return id.ID, response.Process(&id) + return &report, response.Process(&report) } // Remove deletes an image from local storage. The optional force parameter will forcibly remove @@ -145,16 +146,17 @@ func Export(ctx context.Context, nameOrID string, w io.Writer, format *string, c if err != nil { return err } - if err := response.Process(nil); err != nil { + + if response.StatusCode/100 == 2 || response.StatusCode/100 == 3 { + _, err = io.Copy(w, response.Body) return err } - _, err = io.Copy(w, response.Body) - return err + return nil } // Prune removes unused images from local storage. The optional filters can be used to further // define which images should be pruned. -func Prune(ctx context.Context, filters map[string][]string) ([]string, error) { +func Prune(ctx context.Context, all *bool, filters map[string][]string) ([]string, error) { var ( deleted []string ) @@ -163,6 +165,9 @@ func Prune(ctx context.Context, filters map[string][]string) ([]string, error) { return nil, err } params := url.Values{} + if all != nil { + params.Set("all", strconv.FormatBool(*all)) + } if filters != nil { stringFilter, err := bindings.FiltersToString(filters) if err != nil { @@ -174,7 +179,7 @@ func Prune(ctx context.Context, filters map[string][]string) ([]string, error) { if err != nil { return deleted, err } - return deleted, response.Process(nil) + return deleted, response.Process(&deleted) } // Tag adds an additional name to locally-stored image. Both the tag and repo parameters are required. @@ -193,19 +198,35 @@ func Tag(ctx context.Context, nameOrID, tag, repo string) error { return response.Process(nil) } +// Untag removes a name from locally-stored image. Both the tag and repo parameters are required. +func Untag(ctx context.Context, nameOrID, tag, repo string) error { + conn, err := bindings.GetClient(ctx) + if err != nil { + return err + } + params := url.Values{} + params.Set("tag", tag) + params.Set("repo", repo) + response, err := conn.DoRequest(nil, http.MethodPost, "/images/%s/untag", params, nameOrID) + if err != nil { + return err + } + return response.Process(nil) +} + func Build(nameOrId string) {} // Imports adds the given image to the local image store. This can be done by file and the given reader // or via the url parameter. Additional metadata can be associated with the image by using the changes and // message parameters. The image can also be tagged given a reference. One of url OR r must be provided. -func Import(ctx context.Context, changes []string, message, reference, u *string, r io.Reader) (string, error) { - var id handlers.IDResponse +func Import(ctx context.Context, changes []string, message, reference, u *string, r io.Reader) (*entities.ImageImportReport, error) { + var report entities.ImageImportReport if r != nil && u != nil { - return "", errors.New("url and r parameters cannot be used together") + return nil, errors.New("url and r parameters cannot be used together") } conn, err := bindings.GetClient(ctx) if err != nil { - return "", err + return nil, err } params := url.Values{} for _, change := range changes { @@ -222,7 +243,68 @@ func Import(ctx context.Context, changes []string, message, reference, u *string } response, err := conn.DoRequest(r, http.MethodPost, "/images/import", params) if err != nil { - return "", err + return nil, err } - return id.ID, response.Process(&id) + return &report, response.Process(&report) +} + +// Pull is the binding for libpod's v2 endpoints for pulling images. Note that +// `rawImage` must be a reference to a registry (i.e., of docker transport or be +// normalized to one). Other transports are rejected as they do not make sense +// in a remote context. +func Pull(ctx context.Context, rawImage string, options entities.ImagePullOptions) ([]string, error) { + conn, err := bindings.GetClient(ctx) + if err != nil { + return nil, err + } + params := url.Values{} + params.Set("reference", rawImage) + params.Set("credentials", options.Credentials) + params.Set("overrideArch", options.OverrideArch) + params.Set("overrideOS", options.OverrideOS) + if options.TLSVerify != types.OptionalBoolUndefined { + val := bool(options.TLSVerify == types.OptionalBoolTrue) + params.Set("tlsVerify", strconv.FormatBool(val)) + } + params.Set("allTags", strconv.FormatBool(options.AllTags)) + + response, err := conn.DoRequest(nil, http.MethodPost, "/images/pull", params) + if err != nil { + return nil, err + } + + reports := []handlers.LibpodImagesPullReport{} + if err := response.Process(&reports); err != nil { + return nil, err + } + + pulledImages := []string{} + for _, r := range reports { + pulledImages = append(pulledImages, r.ID) + } + + return pulledImages, nil +} + +// Push is the binding for libpod's v2 endpoints for push images. Note that +// `source` must be a refering to an image in the remote's container storage. +// The destination must be a reference to a registry (i.e., of docker transport +// or be normalized to one). Other transports are rejected as they do not make +// sense in a remote context. +func Push(ctx context.Context, source string, destination string, options entities.ImagePushOptions) error { + conn, err := bindings.GetClient(ctx) + if err != nil { + return err + } + params := url.Values{} + params.Set("credentials", options.Credentials) + params.Set("destination", destination) + if options.TLSVerify != types.OptionalBoolUndefined { + val := bool(options.TLSVerify == types.OptionalBoolTrue) + params.Set("tlsVerify", strconv.FormatBool(val)) + } + + path := fmt.Sprintf("/images/%s/push", source) + _, err = conn.DoRequest(nil, http.MethodPost, path, params) + return err } diff --git a/pkg/bindings/pods/pods.go b/pkg/bindings/pods/pods.go index 1a8c31be1..83847614a 100644 --- a/pkg/bindings/pods/pods.go +++ b/pkg/bindings/pods/pods.go @@ -5,14 +5,33 @@ import ( "net/http" "net/url" "strconv" + "strings" - "github.com/containers/libpod/libpod" + "github.com/containers/libpod/pkg/api/handlers" "github.com/containers/libpod/pkg/bindings" + "github.com/containers/libpod/pkg/domain/entities" + "github.com/containers/libpod/pkg/specgen" + jsoniter "github.com/json-iterator/go" ) -func CreatePod() error { - // TODO - return bindings.ErrNotImplemented +func CreatePodFromSpec(ctx context.Context, s *specgen.PodSpecGenerator) (*entities.PodCreateReport, error) { + var ( + pcr entities.PodCreateReport + ) + conn, err := bindings.GetClient(ctx) + if err != nil { + return nil, err + } + specgenString, err := jsoniter.MarshalToString(s) + if err != nil { + return nil, err + } + stringReader := strings.NewReader(specgenString) + response, err := conn.DoRequest(stringReader, http.MethodPost, "/pods/create", nil) + if err != nil { + return nil, err + } + return &pcr, response.Process(&pcr) } // Exists is a lightweight method to determine if a pod exists in local storage @@ -29,25 +48,30 @@ func Exists(ctx context.Context, nameOrID string) (bool, error) { } // Inspect returns low-level information about the given pod. -func Inspect(ctx context.Context, nameOrID string) (*libpod.PodInspect, error) { +func Inspect(ctx context.Context, nameOrID string) (*entities.PodInspectReport, error) { + var ( + report entities.PodInspectReport + ) conn, err := bindings.GetClient(ctx) if err != nil { return nil, err } - inspect := libpod.PodInspect{} response, err := conn.DoRequest(nil, http.MethodGet, "/pods/%s/json", nil, nameOrID) if err != nil { - return &inspect, err + return nil, err } - return &inspect, response.Process(&inspect) + return &report, response.Process(&report) } // Kill sends a SIGTERM to all the containers in a pod. The optional signal parameter // can be used to override SIGTERM. -func Kill(ctx context.Context, nameOrID string, signal *string) error { +func Kill(ctx context.Context, nameOrID string, signal *string) (*entities.PodKillReport, error) { + var ( + report entities.PodKillReport + ) conn, err := bindings.GetClient(ctx) if err != nil { - return err + return nil, err } params := url.Values{} if signal != nil { @@ -55,22 +79,23 @@ func Kill(ctx context.Context, nameOrID string, signal *string) error { } response, err := conn.DoRequest(nil, http.MethodPost, "/pods/%s/kill", params, nameOrID) if err != nil { - return err + return nil, err } - return response.Process(nil) + return &report, response.Process(&report) } // Pause pauses all running containers in a given pod. -func Pause(ctx context.Context, nameOrID string) error { +func Pause(ctx context.Context, nameOrID string) (*entities.PodPauseReport, error) { + var report entities.PodPauseReport conn, err := bindings.GetClient(ctx) if err != nil { - return err + return nil, err } response, err := conn.DoRequest(nil, http.MethodPost, "/pods/%s/pause", nil, nameOrID) if err != nil { - return err + return nil, err } - return response.Process(nil) + return &report, response.Process(&report) } // Prune removes all non-running pods in local storage. @@ -88,9 +113,9 @@ func Prune(ctx context.Context) error { // List returns all pods in local storage. The optional filters parameter can // be used to refine which pods should be listed. -func List(ctx context.Context, filters map[string][]string) ([]*libpod.PodInspect, error) { +func List(ctx context.Context, filters map[string][]string) ([]*entities.ListPodsReport, error) { var ( - inspect []*libpod.PodInspect + podsReports []*entities.ListPodsReport ) conn, err := bindings.GetClient(ctx) if err != nil { @@ -106,30 +131,32 @@ func List(ctx context.Context, filters map[string][]string) ([]*libpod.PodInspec } response, err := conn.DoRequest(nil, http.MethodGet, "/pods/json", params) if err != nil { - return inspect, err + return podsReports, err } - return inspect, response.Process(&inspect) + return podsReports, response.Process(&podsReports) } // Restart restarts all containers in a pod. -func Restart(ctx context.Context, nameOrID string) error { +func Restart(ctx context.Context, nameOrID string) (*entities.PodRestartReport, error) { + var report entities.PodRestartReport conn, err := bindings.GetClient(ctx) if err != nil { - return err + return nil, err } response, err := conn.DoRequest(nil, http.MethodPost, "/pods/%s/restart", nil, nameOrID) if err != nil { - return err + return nil, err } - return response.Process(nil) + return &report, response.Process(&report) } // Remove deletes a Pod from from local storage. The optional force parameter denotes // that the Pod can be removed even if in a running state. -func Remove(ctx context.Context, nameOrID string, force *bool) error { +func Remove(ctx context.Context, nameOrID string, force *bool) (*entities.PodRmReport, error) { + var report entities.PodRmReport conn, err := bindings.GetClient(ctx) if err != nil { - return err + return nil, err } params := url.Values{} if force != nil { @@ -137,22 +164,27 @@ func Remove(ctx context.Context, nameOrID string, force *bool) error { } response, err := conn.DoRequest(nil, http.MethodDelete, "/pods/%s", params, nameOrID) if err != nil { - return err + return nil, err } - return response.Process(nil) + return &report, response.Process(&report) } // Start starts all containers in a pod. -func Start(ctx context.Context, nameOrID string) error { +func Start(ctx context.Context, nameOrID string) (*entities.PodStartReport, error) { + var report entities.PodStartReport conn, err := bindings.GetClient(ctx) if err != nil { - return err + return nil, err } response, err := conn.DoRequest(nil, http.MethodPost, "/pods/%s/start", nil, nameOrID) if err != nil { - return err + return nil, err } - return response.Process(nil) + if response.StatusCode == http.StatusNotModified { + report.Id = nameOrID + return &report, nil + } + return &report, response.Process(&report) } func Stats() error { @@ -162,10 +194,11 @@ func Stats() error { // Stop stops all containers in a Pod. The optional timeout parameter can be // used to override the timeout before the container is killed. -func Stop(ctx context.Context, nameOrID string, timeout *int) error { +func Stop(ctx context.Context, nameOrID string, timeout *int) (*entities.PodStopReport, error) { + var report entities.PodStopReport conn, err := bindings.GetClient(ctx) if err != nil { - return err + return nil, err } params := url.Values{} if timeout != nil { @@ -173,25 +206,59 @@ func Stop(ctx context.Context, nameOrID string, timeout *int) error { } response, err := conn.DoRequest(nil, http.MethodPost, "/pods/%s/stop", params, nameOrID) if err != nil { - return err + return nil, err } - return response.Process(nil) + if response.StatusCode == http.StatusNotModified { + report.Id = nameOrID + return &report, nil + } + return &report, response.Process(&report) } -func Top() error { - // TODO - return bindings.ErrNotImplemented // nolint:typecheck +// Top gathers statistics about the running processes in a pod. The nameOrID can be a pod name +// or a partial/full ID. The descriptors allow for specifying which data to collect from each process. +func Top(ctx context.Context, nameOrID string, descriptors []string) ([]string, error) { + conn, err := bindings.GetClient(ctx) + if err != nil { + return nil, err + } + params := url.Values{} + + if len(descriptors) > 0 { + // flatten the slice into one string + params.Set("ps_args", strings.Join(descriptors, ",")) + } + response, err := conn.DoRequest(nil, http.MethodGet, "/pods/%s/top", params, nameOrID) + if err != nil { + return nil, err + } + + body := handlers.PodTopOKBody{} + if err = response.Process(&body); err != nil { + return nil, err + } + + // handlers.PodTopOKBody{} returns a slice of slices where each cell in the top table is an item. + // In libpod land, we're just using a slice with cells being split by tabs, which allows for an idiomatic + // usage of the tabwriter. + topOutput := []string{strings.Join(body.Titles, "\t")} + for _, out := range body.Processes { + topOutput = append(topOutput, strings.Join(out, "\t")) + } + + return topOutput, err } // Unpause unpauses all paused containers in a Pod. -func Unpause(ctx context.Context, nameOrID string) error { +func Unpause(ctx context.Context, nameOrID string) (*entities.PodUnpauseReport, error) { + var report entities.PodUnpauseReport conn, err := bindings.GetClient(ctx) if err != nil { - return err + return nil, err } response, err := conn.DoRequest(nil, http.MethodPost, "/pods/%s/unpause", nil, nameOrID) if err != nil { - return err + return nil, err } - return response.Process(nil) + return &report, response.Process(&report) } diff --git a/pkg/bindings/test/containers_test.go b/pkg/bindings/test/containers_test.go index f5465c803..a31181958 100644 --- a/pkg/bindings/test/containers_test.go +++ b/pkg/bindings/test/containers_test.go @@ -1,12 +1,12 @@ package test_bindings import ( - "github.com/containers/libpod/libpod/define" "net/http" "strconv" "strings" "time" + "github.com/containers/libpod/libpod/define" "github.com/containers/libpod/pkg/bindings" "github.com/containers/libpod/pkg/bindings/containers" "github.com/containers/libpod/pkg/specgen" @@ -34,7 +34,7 @@ var _ = Describe("Podman containers ", func() { AfterEach(func() { s.Kill() - //bt.cleanup() + bt.cleanup() }) It("podman pause a bogus container", func() { @@ -380,4 +380,34 @@ var _ = Describe("Podman containers ", func() { _, err = time.Parse(time.RFC1123Z, o) Expect(err).To(BeNil()) }) + + It("podman top", func() { + var name = "top" + cid, err := bt.RunTopContainer(&name, &bindings.PFalse, nil) + Expect(err).To(BeNil()) + + // By name + _, err = containers.Top(bt.conn, name, nil) + Expect(err).To(BeNil()) + + // By id + _, err = containers.Top(bt.conn, cid, nil) + Expect(err).To(BeNil()) + + // With descriptors + output, err := containers.Top(bt.conn, cid, []string{"user,pid,hpid"}) + Expect(err).To(BeNil()) + header := strings.Split(output[0], "\t") + for _, d := range []string{"USER", "PID", "HPID"} { + Expect(d).To(BeElementOf(header)) + } + + // With bogus ID + _, err = containers.Top(bt.conn, "IdoNotExist", nil) + Expect(err).ToNot(BeNil()) + + // With bogus descriptors + _, err = containers.Top(bt.conn, cid, []string{"Me,Neither"}) + Expect(err).To(BeNil()) + }) }) diff --git a/pkg/bindings/test/exec_test.go b/pkg/bindings/test/exec_test.go new file mode 100644 index 000000000..1ef2197b6 --- /dev/null +++ b/pkg/bindings/test/exec_test.go @@ -0,0 +1,77 @@ +package test_bindings + +import ( + "time" + + "github.com/containers/libpod/pkg/api/handlers" + "github.com/containers/libpod/pkg/bindings" + "github.com/containers/libpod/pkg/bindings/containers" + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + "github.com/onsi/gomega/gexec" +) + +var _ = Describe("Podman containers exec", func() { + var ( + bt *bindingTest + s *gexec.Session + ) + + BeforeEach(func() { + bt = newBindingTest() + bt.RestoreImagesFromCache() + s = bt.startAPIService() + time.Sleep(1 * time.Second) + err := bt.NewConnection() + Expect(err).To(BeNil()) + }) + + AfterEach(func() { + s.Kill() + bt.cleanup() + }) + + It("Podman exec create makes an exec session", func() { + name := "testCtr" + cid, err := bt.RunTopContainer(&name, &bindings.PFalse, nil) + Expect(err).To(BeNil()) + + execConfig := new(handlers.ExecCreateConfig) + execConfig.Cmd = []string{"echo", "hello world"} + + sessionID, err := containers.ExecCreate(bt.conn, name, execConfig) + Expect(err).To(BeNil()) + Expect(sessionID).To(Not(Equal(""))) + + inspectOut, err := containers.ExecInspect(bt.conn, sessionID) + Expect(err).To(BeNil()) + Expect(inspectOut.ContainerID).To(Equal(cid)) + Expect(inspectOut.ProcessConfig.Entrypoint).To(Equal("echo")) + Expect(len(inspectOut.ProcessConfig.Arguments)).To(Equal(1)) + Expect(inspectOut.ProcessConfig.Arguments[0]).To(Equal("hello world")) + }) + + It("Podman exec create with bad command fails", func() { + name := "testCtr" + _, err := bt.RunTopContainer(&name, &bindings.PFalse, nil) + Expect(err).To(BeNil()) + + execConfig := new(handlers.ExecCreateConfig) + + _, err = containers.ExecCreate(bt.conn, name, execConfig) + Expect(err).To(Not(BeNil())) + }) + + It("Podman exec create with invalid container fails", func() { + execConfig := new(handlers.ExecCreateConfig) + execConfig.Cmd = []string{"echo", "hello world"} + + _, err := containers.ExecCreate(bt.conn, "doesnotexist", execConfig) + Expect(err).To(Not(BeNil())) + }) + + It("Podman exec inspect on invalid session fails", func() { + _, err := containers.ExecInspect(bt.conn, "0000000000000000000000000000000000000000000000000000000000000000") + Expect(err).To(Not(BeNil())) + }) +}) diff --git a/pkg/bindings/test/images_test.go b/pkg/bindings/test/images_test.go index 5e4cfe7be..992720196 100644 --- a/pkg/bindings/test/images_test.go +++ b/pkg/bindings/test/images_test.go @@ -9,6 +9,7 @@ import ( "github.com/containers/libpod/pkg/bindings" "github.com/containers/libpod/pkg/bindings/containers" "github.com/containers/libpod/pkg/bindings/images" + "github.com/containers/libpod/pkg/domain/entities" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" "github.com/onsi/gomega/gexec" @@ -16,22 +17,22 @@ import ( var _ = Describe("Podman images", func() { var ( - //tempdir string - //err error - //podmanTest *PodmanTestIntegration + // tempdir string + // err error + // podmanTest *PodmanTestIntegration bt *bindingTest s *gexec.Session err error ) BeforeEach(func() { - //tempdir, err = CreateTempDirInTempDir() - //if err != nil { + // tempdir, err = CreateTempDirInTempDir() + // if err != nil { // os.Exit(1) - //} - //podmanTest = PodmanTestCreate(tempdir) - //podmanTest.Setup() - //podmanTest.SeedImages() + // } + // podmanTest = PodmanTestCreate(tempdir) + // podmanTest.Setup() + // podmanTest.SeedImages() bt = newBindingTest() bt.RestoreImagesFromCache() s = bt.startAPIService() @@ -41,12 +42,13 @@ var _ = Describe("Podman images", func() { }) AfterEach(func() { - //podmanTest.Cleanup() - //f := CurrentGinkgoTestDescription() - //processTestResult(f) + // podmanTest.Cleanup() + // f := CurrentGinkgoTestDescription() + // processTestResult(f) s.Kill() bt.cleanup() }) + It("inspect image", func() { // Inspect invalid image be 404 _, err = images.GetImage(bt.conn, "foobar5000", nil) @@ -71,7 +73,7 @@ var _ = Describe("Podman images", func() { Expect(err).To(BeNil()) // TODO it looks like the images API alwaays returns size regardless // of bool or not. What should we do ? - //Expect(data.Size).To(BeZero()) + // Expect(data.Size).To(BeZero()) // Enabling the size parameter should result in size being populated data, err = images.GetImage(bt.conn, alpine.name, &bindings.PTrue) @@ -142,7 +144,7 @@ var _ = Describe("Podman images", func() { err = images.Tag(bt.conn, alpine.shortName, "demo", alpine.shortName) Expect(err).To(BeNil()) - //Validates if name updates when the image is retagged. + // Validates if name updates when the image is retagged. _, err := images.GetImage(bt.conn, "alpine:demo", nil) Expect(err).To(BeNil()) @@ -165,7 +167,7 @@ var _ = Describe("Podman images", func() { Expect(err).To(BeNil()) Expect(len(imageSummary)).To(Equal(3)) - //Validate the image names. + // Validate the image names. var names []string for _, i := range imageSummary { names = append(names, i.RepoTags...) @@ -217,7 +219,7 @@ var _ = Describe("Podman images", func() { Expect(err).To(BeNil()) names, err := images.Load(bt.conn, f, nil) Expect(err).To(BeNil()) - Expect(names).To(Equal(alpine.name)) + Expect(names.Name).To(Equal(alpine.name)) exists, err = images.Exists(bt.conn, alpine.name) Expect(err).To(BeNil()) Expect(exists).To(BeTrue()) @@ -233,7 +235,7 @@ var _ = Describe("Podman images", func() { newName := "quay.io/newname:fizzle" names, err = images.Load(bt.conn, f, &newName) Expect(err).To(BeNil()) - Expect(names).To(Equal(alpine.name)) + Expect(names.Name).To(Equal(alpine.name)) exists, err = images.Exists(bt.conn, newName) Expect(err).To(BeNil()) Expect(exists).To(BeTrue()) @@ -289,6 +291,7 @@ var _ = Describe("Podman images", func() { Expect(data.Comment).To(Equal(testMessage)) }) + It("History Image", func() { // a bogus name should return a 404 _, err := images.History(bt.conn, "foobar") @@ -343,4 +346,32 @@ var _ = Describe("Podman images", func() { Expect(len(imgs)).To(BeNumerically(">=", 1)) }) + It("Prune images", func() { + trueBoxed := true + results, err := images.Prune(bt.conn, &trueBoxed, nil) + Expect(err).NotTo(HaveOccurred()) + Expect(len(results)).To(BeNumerically(">", 0)) + Expect(results).To(ContainElement("docker.io/library/alpine:latest")) + }) + + // TODO: we really need to extent to pull tests once we have a more sophisticated CI. + It("Image Pull", func() { + rawImage := "docker.io/library/busybox:latest" + + pulledImages, err := images.Pull(bt.conn, rawImage, entities.ImagePullOptions{}) + Expect(err).To(BeNil()) + Expect(len(pulledImages)).To(Equal(1)) + + exists, err := images.Exists(bt.conn, rawImage) + Expect(err).To(BeNil()) + Expect(exists).To(BeTrue()) + + // Make sure the normalization AND the full-transport reference works. + _, err = images.Pull(bt.conn, "docker://"+rawImage, entities.ImagePullOptions{}) + Expect(err).To(BeNil()) + + // The v2 endpoint only supports the docker transport. Let's see if that's really true. + _, err = images.Pull(bt.conn, "bogus-transport:bogus.com/image:reference", entities.ImagePullOptions{}) + Expect(err).To(Not(BeNil())) + }) }) diff --git a/pkg/bindings/test/pods_test.go b/pkg/bindings/test/pods_test.go index e94048a9c..2599ec7ef 100644 --- a/pkg/bindings/test/pods_test.go +++ b/pkg/bindings/test/pods_test.go @@ -2,11 +2,13 @@ package test_bindings import ( "net/http" + "strings" "time" "github.com/containers/libpod/libpod/define" "github.com/containers/libpod/pkg/bindings" "github.com/containers/libpod/pkg/bindings/pods" + "github.com/containers/libpod/pkg/specgen" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" "github.com/onsi/gomega/gexec" @@ -71,7 +73,7 @@ var _ = Describe("Podman pods", func() { Expect(len(podSummary)).To(Equal(2)) var names []string for _, i := range podSummary { - names = append(names, i.Config.Name) + names = append(names, i.Name) } Expect(StringInSlice(newpod, names)).To(BeTrue()) Expect(StringInSlice("newpod2", names)).To(BeTrue()) @@ -79,9 +81,7 @@ var _ = Describe("Podman pods", func() { // The test validates the list pod endpoint with passing filters as the params. It("List pods with filters", func() { - var ( - newpod2 string = "newpod2" - ) + newpod2 := "newpod2" bt.Podcreate(&newpod2) _, err = bt.RunTopContainer(nil, &bindings.PTrue, &newpod) Expect(err).To(BeNil()) @@ -109,13 +109,14 @@ var _ = Describe("Podman pods", func() { Expect(len(filteredPods)).To(BeNumerically("==", 1)) var names []string for _, i := range filteredPods { - names = append(names, i.Config.Name) + names = append(names, i.Name) } Expect(StringInSlice("newpod2", names)).To(BeTrue()) // Validate list pod with id filter filters = make(map[string][]string) response, err := pods.Inspect(bt.conn, newpod) + Expect(err).To(BeNil()) id := response.Config.ID filters["id"] = []string{id} filteredPods, err = pods.List(bt.conn, filters) @@ -123,7 +124,7 @@ var _ = Describe("Podman pods", func() { Expect(len(filteredPods)).To(BeNumerically("==", 1)) names = names[:0] for _, i := range filteredPods { - names = append(names, i.Config.Name) + names = append(names, i.Name) } Expect(StringInSlice("newpod", names)).To(BeTrue()) @@ -134,7 +135,7 @@ var _ = Describe("Podman pods", func() { Expect(len(filteredPods)).To(BeNumerically("==", 1)) names = names[:0] for _, i := range filteredPods { - names = append(names, i.Config.Name) + names = append(names, i.Name) } Expect(StringInSlice("newpod", names)).To(BeTrue()) }) @@ -157,7 +158,7 @@ var _ = Describe("Podman pods", func() { // TODO fix this Skip("Pod behavior is jacked right now.") // Pause invalid container - err := pods.Pause(bt.conn, "dummyName") + _, err := pods.Pause(bt.conn, "dummyName") Expect(err).ToNot(BeNil()) code, _ := bindings.CheckResponseCode(err) Expect(code).To(BeNumerically("==", http.StatusNotFound)) @@ -169,9 +170,10 @@ var _ = Describe("Podman pods", func() { // Binding needs to be modified to inspect the pod state. // Since we don't have a pod state we inspect the states of the containers within the pod. // Pause a valid container - err = pods.Pause(bt.conn, newpod) + _, err = pods.Pause(bt.conn, newpod) Expect(err).To(BeNil()) response, err := pods.Inspect(bt.conn, newpod) + Expect(err).To(BeNil()) Expect(response.State.Status).To(Equal(define.PodStatePaused)) for _, i := range response.Containers { Expect(define.StringToContainerStatus(i.State)). @@ -179,9 +181,10 @@ var _ = Describe("Podman pods", func() { } // Unpause a valid container - err = pods.Unpause(bt.conn, newpod) + _, err = pods.Unpause(bt.conn, newpod) Expect(err).To(BeNil()) response, err = pods.Inspect(bt.conn, newpod) + Expect(err).To(BeNil()) Expect(response.State.Status).To(Equal(define.PodStateRunning)) for _, i := range response.Containers { Expect(define.StringToContainerStatus(i.State)). @@ -191,28 +194,29 @@ var _ = Describe("Podman pods", func() { It("start stop restart pod", func() { // Start an invalid pod - err = pods.Start(bt.conn, "dummyName") + _, err = pods.Start(bt.conn, "dummyName") Expect(err).ToNot(BeNil()) code, _ := bindings.CheckResponseCode(err) Expect(code).To(BeNumerically("==", http.StatusNotFound)) // Stop an invalid pod - err = pods.Stop(bt.conn, "dummyName", nil) + _, err = pods.Stop(bt.conn, "dummyName", nil) Expect(err).ToNot(BeNil()) code, _ = bindings.CheckResponseCode(err) Expect(code).To(BeNumerically("==", http.StatusNotFound)) // Restart an invalid pod - err = pods.Restart(bt.conn, "dummyName") + _, err = pods.Restart(bt.conn, "dummyName") Expect(err).ToNot(BeNil()) code, _ = bindings.CheckResponseCode(err) Expect(code).To(BeNumerically("==", http.StatusNotFound)) // Start a valid pod and inspect status of each container - err = pods.Start(bt.conn, newpod) + _, err = pods.Start(bt.conn, newpod) Expect(err).To(BeNil()) response, err := pods.Inspect(bt.conn, newpod) + Expect(err).To(BeNil()) Expect(response.State.Status).To(Equal(define.PodStateRunning)) for _, i := range response.Containers { Expect(define.StringToContainerStatus(i.State)). @@ -220,11 +224,11 @@ var _ = Describe("Podman pods", func() { } // Start an already running pod - err = pods.Start(bt.conn, newpod) + _, err = pods.Start(bt.conn, newpod) Expect(err).To(BeNil()) // Stop the running pods - err = pods.Stop(bt.conn, newpod, nil) + _, err = pods.Stop(bt.conn, newpod, nil) Expect(err).To(BeNil()) response, _ = pods.Inspect(bt.conn, newpod) Expect(response.State.Status).To(Equal(define.PodStateExited)) @@ -234,10 +238,10 @@ var _ = Describe("Podman pods", func() { } // Stop an already stopped pod - err = pods.Stop(bt.conn, newpod, nil) + _, err = pods.Stop(bt.conn, newpod, nil) Expect(err).To(BeNil()) - err = pods.Restart(bt.conn, newpod) + _, err = pods.Restart(bt.conn, newpod) Expect(err).To(BeNil()) response, _ = pods.Inspect(bt.conn, newpod) Expect(response.State.Status).To(Equal(define.PodStateRunning)) @@ -262,11 +266,12 @@ var _ = Describe("Podman pods", func() { // Prune only one pod which is in exited state. // Start then stop a pod. // pod moves to exited state one pod should be pruned now. - err = pods.Start(bt.conn, newpod) + _, err = pods.Start(bt.conn, newpod) Expect(err).To(BeNil()) - err = pods.Stop(bt.conn, newpod, nil) + _, err = pods.Stop(bt.conn, newpod, nil) Expect(err).To(BeNil()) response, err := pods.Inspect(bt.conn, newpod) + Expect(err).To(BeNil()) Expect(response.State.Status).To(Equal(define.PodStateExited)) err = pods.Prune(bt.conn) Expect(err).To(BeNil()) @@ -276,21 +281,23 @@ var _ = Describe("Podman pods", func() { // Test prune all pods in exited state. bt.Podcreate(&newpod) - err = pods.Start(bt.conn, newpod) + _, err = pods.Start(bt.conn, newpod) Expect(err).To(BeNil()) - err = pods.Start(bt.conn, newpod2) + _, err = pods.Start(bt.conn, newpod2) Expect(err).To(BeNil()) - err = pods.Stop(bt.conn, newpod, nil) + _, err = pods.Stop(bt.conn, newpod, nil) Expect(err).To(BeNil()) response, err = pods.Inspect(bt.conn, newpod) + Expect(err).To(BeNil()) Expect(response.State.Status).To(Equal(define.PodStateExited)) for _, i := range response.Containers { Expect(define.StringToContainerStatus(i.State)). To(Equal(define.ContainerStateStopped)) } - err = pods.Stop(bt.conn, newpod2, nil) + _, err = pods.Stop(bt.conn, newpod2, nil) Expect(err).To(BeNil()) response, err = pods.Inspect(bt.conn, newpod2) + Expect(err).To(BeNil()) Expect(response.State.Status).To(Equal(define.PodStateExited)) for _, i := range response.Containers { Expect(define.StringToContainerStatus(i.State)). @@ -302,4 +309,44 @@ var _ = Describe("Podman pods", func() { Expect(err).To(BeNil()) Expect(len(podSummary)).To(Equal(0)) }) + + It("simple create pod", func() { + ps := specgen.PodSpecGenerator{} + ps.Name = "foobar" + _, err := pods.CreatePodFromSpec(bt.conn, &ps) + Expect(err).To(BeNil()) + + exists, err := pods.Exists(bt.conn, "foobar") + Expect(err).To(BeNil()) + Expect(exists).To(BeTrue()) + }) + + // Test validates the pod top bindings + It("pod top", func() { + var name string = "podA" + + bt.Podcreate(&name) + _, err := pods.Start(bt.conn, name) + Expect(err).To(BeNil()) + + // By name + _, err = pods.Top(bt.conn, name, nil) + Expect(err).To(BeNil()) + + // With descriptors + output, err := pods.Top(bt.conn, name, []string{"user,pid,hpid"}) + Expect(err).To(BeNil()) + header := strings.Split(output[0], "\t") + for _, d := range []string{"USER", "PID", "HPID"} { + Expect(d).To(BeElementOf(header)) + } + + // With bogus ID + _, err = pods.Top(bt.conn, "IdoNotExist", nil) + Expect(err).ToNot(BeNil()) + + // With bogus descriptors + _, err = pods.Top(bt.conn, name, []string{"Me,Neither"}) + Expect(err).ToNot(BeNil()) + }) }) diff --git a/pkg/adapter/checkpoint_restore.go b/pkg/checkpoint/checkpoint_restore.go index 7f80b782a..78f592d32 100644 --- a/pkg/adapter/checkpoint_restore.go +++ b/pkg/checkpoint/checkpoint_restore.go @@ -1,6 +1,4 @@ -// +build !remoteclient - -package adapter +package checkpoint import ( "context" @@ -42,9 +40,9 @@ func crImportFromJSON(filePath string, v interface{}) error { return nil } -// crImportCheckpoint it the function which imports the information +// CRImportCheckpoint it the function which imports the information // from checkpoint tarball and re-creates the container from that information -func crImportCheckpoint(ctx context.Context, runtime *libpod.Runtime, input string, name string) ([]*libpod.Container, error) { +func CRImportCheckpoint(ctx context.Context, runtime *libpod.Runtime, input string, name string) ([]*libpod.Container, error) { // First get the container definition from the // tarball to a temporary directory archiveFile, err := os.Open(input) @@ -114,7 +112,7 @@ func crImportCheckpoint(ctx context.Context, runtime *libpod.Runtime, input stri return nil, err } - _, err = runtime.ImageRuntime().New(ctx, config.RootfsImageName, rtc.SignaturePolicyPath, "", writer, nil, image.SigningOptions{}, nil, util.PullImageMissing) + _, err = runtime.ImageRuntime().New(ctx, config.RootfsImageName, rtc.Engine.SignaturePolicyPath, "", writer, nil, image.SigningOptions{}, nil, util.PullImageMissing) if err != nil { return nil, err } diff --git a/pkg/domain/entities/containers.go b/pkg/domain/entities/containers.go index 8b7406ae8..6b4bb9ba2 100644 --- a/pkg/domain/entities/containers.go +++ b/pkg/domain/entities/containers.go @@ -1,6 +1,8 @@ package entities import ( + "io" + "os" "time" "github.com/containers/libpod/libpod/define" @@ -22,6 +24,11 @@ type BoolReport struct { Value bool } +// StringSliceReport wraps a string slice. +type StringSliceReport struct { + Value []string +} + type PauseUnPauseOptions struct { All bool } @@ -44,6 +51,16 @@ type StopReport struct { Id string } +type TopOptions struct { + // CLI flags. + ListDescriptors bool + Latest bool + + // Options for the API. + Descriptors []string + NameOrID string +} + type KillOptions struct { All bool Latest bool @@ -81,3 +98,112 @@ type RmReport struct { Err error Id string } + +type ContainerInspectReport struct { + *define.InspectContainerData +} + +type CommitOptions struct { + Author string + Changes []string + Format string + ImageName string + IncludeVolumes bool + Message string + Pause bool + Quiet bool + Writer io.Writer +} + +type CommitReport struct { + Id string +} + +type ContainerExportOptions struct { + Output string +} + +type CheckpointOptions struct { + All bool + Export string + IgnoreRootFS bool + Keep bool + Latest bool + LeaveRuninng bool + TCPEstablished bool +} + +type CheckpointReport struct { + Err error + Id string +} + +type RestoreOptions struct { + All bool + IgnoreRootFS bool + IgnoreStaticIP bool + IgnoreStaticMAC bool + Import string + Keep bool + Latest bool + Name string + TCPEstablished bool +} + +type RestoreReport struct { + Err error + Id string +} + +type ContainerCreateReport struct { + Id string +} + +// AttachOptions describes the cli and other values +// needed to perform an attach +type AttachOptions struct { + DetachKeys string + Latest bool + NoStdin bool + SigProxy bool + Stdin *os.File + Stdout *os.File + Stderr *os.File +} + +// ExecOptions describes the cli values to exec into +// a container +type ExecOptions struct { + Cmd []string + DetachKeys string + Envs map[string]string + Interactive bool + Latest bool + PreserveFDs uint + Privileged bool + Streams define.AttachStreams + Tty bool + User string + WorkDir string +} + +// ContainerStartOptions describes the val from the +// CLI needed to start a container +type ContainerStartOptions struct { + Attach bool + DetachKeys string + Interactive bool + Latest bool + SigProxy bool + Stdout *os.File + Stderr *os.File + Stdin *os.File +} + +// ContainerStartReport describes the response from starting +// containers from the cli +type ContainerStartReport struct { + Id string + Err error + ExitCode int +} diff --git a/pkg/domain/entities/engine.go b/pkg/domain/entities/engine.go index 8553f5326..c14348529 100644 --- a/pkg/domain/entities/engine.go +++ b/pkg/domain/entities/engine.go @@ -4,7 +4,7 @@ import ( "os/user" "path/filepath" - "github.com/containers/libpod/libpod/define" + "github.com/containers/common/pkg/config" "github.com/spf13/pflag" ) @@ -60,7 +60,7 @@ type EngineOptions struct { func NewEngineOptions() (EngineOptions, error) { u, _ := user.Current() return EngineOptions{ - CGroupManager: define.SystemdCgroupsManager, + CGroupManager: config.SystemdCgroupsManager, CniConfigDir: "", Config: "", ConmonPath: filepath.Join("usr", "bin", "conmon"), diff --git a/pkg/domain/entities/engine_container.go b/pkg/domain/entities/engine_container.go index 2efdbd602..264780771 100644 --- a/pkg/domain/entities/engine_container.go +++ b/pkg/domain/entities/engine_container.go @@ -2,21 +2,48 @@ package entities import ( "context" + + "github.com/containers/libpod/libpod/define" + "github.com/containers/libpod/pkg/specgen" ) type ContainerEngine interface { + ContainerAttach(ctx context.Context, nameOrId string, options AttachOptions) error + ContainerCommit(ctx context.Context, nameOrId string, options CommitOptions) (*CommitReport, error) + ContainerCheckpoint(ctx context.Context, namesOrIds []string, options CheckpointOptions) ([]*CheckpointReport, error) + ContainerRestore(ctx context.Context, namesOrIds []string, options RestoreOptions) ([]*RestoreReport, error) + ContainerCreate(ctx context.Context, s *specgen.SpecGenerator) (*ContainerCreateReport, error) + ContainerExec(ctx context.Context, nameOrId string, options ExecOptions) (int, error) ContainerExists(ctx context.Context, nameOrId string) (*BoolReport, error) + ContainerInspect(ctx context.Context, namesOrIds []string, options InspectOptions) ([]*ContainerInspectReport, error) + ContainerExport(ctx context.Context, nameOrId string, options ContainerExportOptions) error ContainerKill(ctx context.Context, namesOrIds []string, options KillOptions) ([]*KillReport, error) ContainerPause(ctx context.Context, namesOrIds []string, options PauseUnPauseOptions) ([]*PauseUnpauseReport, error) ContainerRestart(ctx context.Context, namesOrIds []string, options RestartOptions) ([]*RestartReport, error) ContainerRm(ctx context.Context, namesOrIds []string, options RmOptions) ([]*RmReport, error) - ContainerUnpause(ctx context.Context, namesOrIds []string, options PauseUnPauseOptions) ([]*PauseUnpauseReport, error) + ContainerStart(ctx context.Context, namesOrIds []string, options ContainerStartOptions) ([]*ContainerStartReport, error) ContainerStop(ctx context.Context, namesOrIds []string, options StopOptions) ([]*StopReport, error) + ContainerTop(ctx context.Context, options TopOptions) (*StringSliceReport, error) + ContainerUnpause(ctx context.Context, namesOrIds []string, options PauseUnPauseOptions) ([]*PauseUnpauseReport, error) ContainerWait(ctx context.Context, namesOrIds []string, options WaitOptions) ([]WaitReport, error) + HealthCheckRun(ctx context.Context, nameOrId string, options HealthCheckOptions) (*define.HealthCheckResults, error) + + PodCreate(ctx context.Context, opts PodCreateOptions) (*PodCreateReport, error) PodExists(ctx context.Context, nameOrId string) (*BoolReport, error) + PodKill(ctx context.Context, namesOrIds []string, options PodKillOptions) ([]*PodKillReport, error) + PodPause(ctx context.Context, namesOrIds []string, options PodPauseOptions) ([]*PodPauseReport, error) + PodPs(ctx context.Context, options PodPSOptions) ([]*ListPodsReport, error) + PodRestart(ctx context.Context, namesOrIds []string, options PodRestartOptions) ([]*PodRestartReport, error) + PodRm(ctx context.Context, namesOrIds []string, options PodRmOptions) ([]*PodRmReport, error) + PodStart(ctx context.Context, namesOrIds []string, options PodStartOptions) ([]*PodStartReport, error) + PodStop(ctx context.Context, namesOrIds []string, options PodStopOptions) ([]*PodStopReport, error) + PodTop(ctx context.Context, options PodTopOptions) (*StringSliceReport, error) + PodUnpause(ctx context.Context, namesOrIds []string, options PodunpauseOptions) ([]*PodUnpauseReport, error) + PodInspect(ctx context.Context, options PodInspectOptions) (*PodInspectReport, error) + VolumeCreate(ctx context.Context, opts VolumeCreateOptions) (*IdOrNameResponse, error) VolumeInspect(ctx context.Context, namesOrIds []string, opts VolumeInspectOptions) ([]*VolumeInspectReport, error) - VolumeRm(ctx context.Context, namesOrIds []string, opts VolumeRmOptions) ([]*VolumeRmReport, error) - VolumePrune(ctx context.Context, opts VolumePruneOptions) ([]*VolumePruneReport, error) VolumeList(ctx context.Context, opts VolumeListOptions) ([]*VolumeListReport, error) + VolumePrune(ctx context.Context, opts VolumePruneOptions) ([]*VolumePruneReport, error) + VolumeRm(ctx context.Context, namesOrIds []string, opts VolumeRmOptions) ([]*VolumeRmReport, error) } diff --git a/pkg/domain/entities/engine_image.go b/pkg/domain/entities/engine_image.go index d44fdaf53..a28bfc548 100644 --- a/pkg/domain/entities/engine_image.go +++ b/pkg/domain/entities/engine_image.go @@ -5,8 +5,17 @@ import ( ) type ImageEngine interface { - Delete(ctx context.Context, nameOrId string, opts ImageDeleteOptions) (*ImageDeleteReport, error) + Delete(ctx context.Context, nameOrId []string, opts ImageDeleteOptions) (*ImageDeleteReport, error) + Exists(ctx context.Context, nameOrId string) (*BoolReport, error) History(ctx context.Context, nameOrId string, opts ImageHistoryOptions) (*ImageHistoryReport, error) + Inspect(ctx context.Context, names []string, opts InspectOptions) (*ImageInspectReport, error) List(ctx context.Context, opts ImageListOptions) ([]*ImageSummary, error) Prune(ctx context.Context, opts ImagePruneOptions) (*ImagePruneReport, error) + Pull(ctx context.Context, rawImage string, opts ImagePullOptions) (*ImagePullReport, error) + Tag(ctx context.Context, nameOrId string, tags []string, options ImageTagOptions) error + Untag(ctx context.Context, nameOrId string, tags []string, options ImageUntagOptions) error + Load(ctx context.Context, opts ImageLoadOptions) (*ImageLoadReport, error) + Import(ctx context.Context, opts ImageImportOptions) (*ImageImportReport, error) + Push(ctx context.Context, source string, destination string, opts ImagePushOptions) error + Save(ctx context.Context, nameOrId string, tags []string, options ImageSaveOptions) error } diff --git a/pkg/domain/entities/healthcheck.go b/pkg/domain/entities/healthcheck.go new file mode 100644 index 000000000..a880805f9 --- /dev/null +++ b/pkg/domain/entities/healthcheck.go @@ -0,0 +1,3 @@ +package entities + +type HealthCheckOptions struct{} diff --git a/pkg/domain/entities/images.go b/pkg/domain/entities/images.go index f04317e37..bc8a34c13 100644 --- a/pkg/domain/entities/images.go +++ b/pkg/domain/entities/images.go @@ -4,6 +4,8 @@ import ( "net/url" "github.com/containers/image/v5/manifest" + "github.com/containers/image/v5/types" + "github.com/containers/libpod/pkg/inspect" docker "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/container" "github.com/opencontainers/go-digest" @@ -11,7 +13,6 @@ import ( ) type Image struct { - IdOrNamed ID string `json:"Id"` RepoTags []string `json:",omitempty"` RepoDigests []string `json:",omitempty"` @@ -81,14 +82,18 @@ func (i *ImageSummary) IsDangling() bool { } type ImageDeleteOptions struct { + All bool Force bool } -// ImageDeleteResponse is the response for removing an image from storage and containers -// what was untagged vs actually removed +// ImageDeleteResponse is the response for removing one or more image(s) from storage +// and containers what was untagged vs actually removed type ImageDeleteReport struct { - Untagged []string `json:"untagged"` - Deleted string `json:"deleted"` + Untagged []string `json:",omitempty"` + Deleted []string `json:",omitempty"` + Errors []error + ImageNotFound error + ImageInUse error } type ImageHistoryOptions struct{} @@ -106,29 +111,133 @@ type ImageHistoryReport struct { Layers []ImageHistoryLayer } -type ImageInspectOptions struct { - TypeObject string `json:",omitempty"` - Format string `json:",omitempty"` - Size bool `json:",omitempty"` - Latest bool `json:",omitempty"` +// ImagePullOptions are the arguments for pulling images. +type ImagePullOptions struct { + // AllTags can be specified to pull all tags of the spiecifed image. Note + // that this only works if the specified image does not include a tag. + AllTags bool + // Authfile is the path to the authentication file. Ignored for remote + // calls. + Authfile string + // CertDir is the path to certificate directories. Ignored for remote + // calls. + CertDir string + // Credentials for authenticating against the registry in the format + // USERNAME:PASSWORD. + Credentials string + // OverrideArch will overwrite the local architecture for image pulls. + OverrideArch string + // OverrideOS will overwrite the local operating system (OS) for image + // pulls. + OverrideOS string + // Quiet can be specified to suppress pull progress when pulling. Ignored + // for remote calls. + Quiet bool + // SignaturePolicy to use when pulling. Ignored for remote calls. + SignaturePolicy string + // TLSVerify to enable/disable HTTPS and certificate verification. + TLSVerify types.OptionalBool +} + +// ImagePullReport is the response from pulling one or more images. +type ImagePullReport struct { + Images []string +} + +// ImagePushOptions are the arguments for pushing images. +type ImagePushOptions struct { + // Authfile is the path to the authentication file. Ignored for remote + // calls. + Authfile string + // CertDir is the path to certificate directories. Ignored for remote + // calls. + CertDir string + // Compress tarball image layers when pushing to a directory using the 'dir' + // transport. Default is same compression type as source. Ignored for remote + // calls. + Compress bool + // Credentials for authenticating against the registry in the format + // USERNAME:PASSWORD. + Credentials string + // DigestFile, after copying the image, write the digest of the resulting + // image to the file. Ignored for remote calls. + DigestFile string + // Format is the Manifest type (oci, v2s1, or v2s2) to use when pushing an + // image using the 'dir' transport. Default is manifest type of source. + // Ignored for remote calls. + Format string + // Quiet can be specified to suppress pull progress when pulling. Ignored + // for remote calls. + Quiet bool + // RemoveSignatures, discard any pre-existing signatures in the image. + // Ignored for remote calls. + RemoveSignatures bool + // SignaturePolicy to use when pulling. Ignored for remote calls. + SignaturePolicy string + // SignBy adds a signature at the destination using the specified key. + // Ignored for remote calls. + SignBy string + // TLSVerify to enable/disable HTTPS and certificate verification. + TLSVerify types.OptionalBool } type ImageListOptions struct { All bool `json:"all" schema:"all"` - Filter []string `json:",omitempty"` + Filter []string `json:"Filter,omitempty"` Filters url.Values `json:"filters" schema:"filters"` } -// type ImageListReport struct { -// Images []ImageSummary -// } - type ImagePruneOptions struct { - All bool - Filter ImageFilter + All bool `json:"all" schema:"all"` + Filter []string `json:"filter" schema:"filter"` + Filters url.Values `json:"filters" schema:"filters"` } type ImagePruneReport struct { Report Report Size int64 } + +type ImageTagOptions struct{} +type ImageUntagOptions struct{} + +type ImageData struct { + *inspect.ImageData +} + +type ImageInspectReport struct { + Images []*ImageData + Errors map[string]error +} + +type ImageLoadOptions struct { + Name string + Tag string + Input string + Quiet bool + SignaturePolicy string +} + +type ImageLoadReport struct { + Name string +} + +type ImageImportOptions struct { + Changes []string + Message string + Quiet bool + Reference string + Source string + SourceIsURL bool +} + +type ImageImportReport struct { + Id string +} + +type ImageSaveOptions struct { + Compress bool + Format string + Output string + Quiet bool +} diff --git a/pkg/domain/entities/pods.go b/pkg/domain/entities/pods.go new file mode 100644 index 000000000..cd2e79961 --- /dev/null +++ b/pkg/domain/entities/pods.go @@ -0,0 +1,178 @@ +package entities + +import ( + "time" + + "github.com/containers/libpod/libpod" + "github.com/containers/libpod/pkg/specgen" +) + +type PodKillOptions struct { + All bool + Latest bool + Signal string +} + +type PodKillReport struct { + Errs []error + Id string +} + +type ListPodsReport struct { + Cgroup string + Containers []*ListPodContainer + Created time.Time + Id string + InfraId string + Name string + Namespace string + Status string +} + +type ListPodContainer struct { + Id string + Names string + Status string +} + +type PodPauseOptions struct { + All bool + Latest bool +} + +type PodPauseReport struct { + Errs []error + Id string +} + +type PodunpauseOptions struct { + All bool + Latest bool +} + +type PodUnpauseReport struct { + Errs []error + Id string +} + +type PodStopOptions struct { + All bool + Ignore bool + Latest bool + Timeout int +} + +type PodStopReport struct { + Errs []error + Id string +} + +type PodRestartOptions struct { + All bool + Latest bool +} + +type PodRestartReport struct { + Errs []error + Id string +} + +type PodStartOptions struct { + All bool + Latest bool +} + +type PodStartReport struct { + Errs []error + Id string +} + +type PodRmOptions struct { + All bool + Force bool + Ignore bool + Latest bool +} + +type PodRmReport struct { + Err error + Id string +} + +type PodCreateOptions struct { + CGroupParent string + Hostname string + Infra bool + InfraImage string + InfraCommand string + Labels map[string]string + Name string + Net *NetOptions + Share []string +} + +type PodCreateReport struct { + Id string +} + +func (p PodCreateOptions) ToPodSpecGen(s *specgen.PodSpecGenerator) { + // Basic Config + s.Name = p.Name + s.Hostname = p.Hostname + s.Labels = p.Labels + s.NoInfra = !p.Infra + s.InfraCommand = []string{p.InfraCommand} + s.InfraImage = p.InfraImage + s.SharedNamespaces = p.Share + + // Networking config + s.NetNS = p.Net.Network + s.StaticIP = p.Net.StaticIP + s.StaticMAC = p.Net.StaticMAC + s.PortMappings = p.Net.PublishPorts + s.CNINetworks = p.Net.CNINetworks + if p.Net.DNSHost { + s.NoManageResolvConf = true + } + s.DNSServer = p.Net.DNSServers + s.DNSSearch = p.Net.DNSSearch + s.DNSOption = p.Net.DNSOptions + s.NoManageHosts = p.Net.NoHosts + s.HostAdd = p.Net.AddHosts + + // Cgroup + s.CgroupParent = p.CGroupParent +} + +type PodTopOptions struct { + // CLI flags. + ListDescriptors bool + Latest bool + + // Options for the API. + Descriptors []string + NameOrID string +} + +type PodPSOptions struct { + CtrNames bool + CtrIds bool + CtrStatus bool + Filters map[string][]string + Format string + Latest bool + Namespace bool + Quiet bool + Sort string +} + +type PodInspectOptions struct { + Latest bool + + // Options for the API. + NameOrID string +} + +type PodInspectReport struct { + *libpod.PodInspect +} diff --git a/pkg/domain/entities/types.go b/pkg/domain/entities/types.go index e7757a74b..dd7aaa07f 100644 --- a/pkg/domain/entities/types.go +++ b/pkg/domain/entities/types.go @@ -1,5 +1,12 @@ package entities +import ( + "net" + + "github.com/containers/libpod/pkg/specgen" + "github.com/cri-o/ocicni/pkg/ocicni" +) + type Container struct { IdOrNamed } @@ -15,3 +22,30 @@ type Report struct { type PodDeleteReport struct{ Report } type PodPruneOptions struct{} + +type PodPruneReport struct{ Report } +type VolumeDeleteOptions struct{} +type VolumeDeleteReport struct{ Report } + +// NetOptions reflect the shared network options between +// pods and containers +type NetOptions struct { + AddHosts []string + CNINetworks []string + DNSHost bool + DNSOptions []string + DNSSearch []string + DNSServers []net.IP + Network specgen.Namespace + NoHosts bool + PublishPorts []ocicni.PortMapping + StaticIP *net.IP + StaticMAC *net.HardwareAddr +} + +// All CLI inspect commands and inspect sub-commands use the same options +type InspectOptions struct { + Format string `json:",omitempty"` + Latest bool `json:",omitempty"` + Size bool `json:",omitempty"` +} diff --git a/pkg/domain/infra/abi/containers.go b/pkg/domain/infra/abi/containers.go index a3da310c2..929c3f335 100644 --- a/pkg/domain/infra/abi/containers.go +++ b/pkg/domain/infra/abi/containers.go @@ -5,17 +5,54 @@ package abi import ( "context" "io/ioutil" + "strconv" "strings" + "github.com/containers/buildah" + "github.com/containers/image/v5/manifest" "github.com/containers/libpod/libpod" "github.com/containers/libpod/libpod/define" - "github.com/containers/libpod/pkg/adapter/shortcuts" + "github.com/containers/libpod/libpod/events" + "github.com/containers/libpod/libpod/image" + "github.com/containers/libpod/pkg/checkpoint" "github.com/containers/libpod/pkg/domain/entities" + "github.com/containers/libpod/pkg/domain/infra/abi/terminal" "github.com/containers/libpod/pkg/signal" + "github.com/containers/libpod/pkg/specgen" + "github.com/containers/libpod/pkg/specgen/generate" "github.com/pkg/errors" "github.com/sirupsen/logrus" ) +// getContainersByContext gets pods whether all, latest, or a slice of names/ids +// is specified. +func getContainersByContext(all, latest bool, names []string, runtime *libpod.Runtime) (ctrs []*libpod.Container, err error) { + var ctr *libpod.Container + ctrs = []*libpod.Container{} + + switch { + case all: + ctrs, err = runtime.GetAllContainers() + case latest: + ctr, err = runtime.GetLatestContainer() + ctrs = append(ctrs, ctr) + default: + for _, n := range names { + ctr, e := runtime.LookupContainer(n) + if e != nil { + // Log all errors here, so callers don't need to. + logrus.Debugf("Error looking up container %q: %v", n, e) + if err == nil { + err = e + } + } else { + ctrs = append(ctrs, ctr) + } + } + } + return +} + // TODO: Should return *entities.ContainerExistsReport, error func (ic *ContainerEngine) ContainerExists(ctx context.Context, nameOrId string) (*entities.BoolReport, error) { _, err := ic.Libpod.LookupContainer(nameOrId) @@ -29,7 +66,7 @@ func (ic *ContainerEngine) ContainerWait(ctx context.Context, namesOrIds []strin var ( responses []entities.WaitReport ) - ctrs, err := shortcuts.GetContainersByContext(false, options.Latest, namesOrIds, ic.Libpod) + ctrs, err := getContainersByContext(false, options.Latest, namesOrIds, ic.Libpod) if err != nil { return nil, err } @@ -55,7 +92,7 @@ func (ic *ContainerEngine) ContainerPause(ctx context.Context, namesOrIds []stri if options.All { ctrs, err = ic.Libpod.GetAllContainers() } else { - ctrs, err = shortcuts.GetContainersByContext(false, false, namesOrIds, ic.Libpod) + ctrs, err = getContainersByContext(false, false, namesOrIds, ic.Libpod) } if err != nil { return nil, err @@ -76,7 +113,7 @@ func (ic *ContainerEngine) ContainerUnpause(ctx context.Context, namesOrIds []st if options.All { ctrs, err = ic.Libpod.GetAllContainers() } else { - ctrs, err = shortcuts.GetContainersByContext(false, false, namesOrIds, ic.Libpod) + ctrs, err = getContainersByContext(false, false, namesOrIds, ic.Libpod) } if err != nil { return nil, err @@ -100,7 +137,7 @@ func (ic *ContainerEngine) ContainerStop(ctx context.Context, namesOrIds []strin id := strings.Split(string(content), "\n")[0] names = append(names, id) } - ctrs, err := shortcuts.GetContainersByContext(options.All, options.Latest, names, ic.Libpod) + ctrs, err := getContainersByContext(options.All, options.Latest, names, ic.Libpod) if err != nil && !(options.Ignore && errors.Cause(err) == define.ErrNoSuchCtr) { return nil, err } @@ -136,7 +173,7 @@ func (ic *ContainerEngine) ContainerKill(ctx context.Context, namesOrIds []strin if err != nil { return nil, err } - ctrs, err := shortcuts.GetContainersByContext(options.All, options.Latest, namesOrIds, ic.Libpod) + ctrs, err := getContainersByContext(options.All, options.Latest, namesOrIds, ic.Libpod) if err != nil { return nil, err } @@ -152,7 +189,7 @@ func (ic *ContainerEngine) ContainerRestart(ctx context.Context, namesOrIds []st var ( reports []*entities.RestartReport ) - ctrs, err := shortcuts.GetContainersByContext(options.All, options.Latest, namesOrIds, ic.Libpod) + ctrs, err := getContainersByContext(options.All, options.Latest, namesOrIds, ic.Libpod) if err != nil { return nil, err } @@ -194,7 +231,7 @@ func (ic *ContainerEngine) ContainerRm(ctx context.Context, namesOrIds []string, names = append(names, id) } - ctrs, err := shortcuts.GetContainersByContext(options.All, options.Latest, names, ic.Libpod) + ctrs, err := getContainersByContext(options.All, options.Latest, names, ic.Libpod) if err != nil && !(options.Ignore && errors.Cause(err) == define.ErrNoSuchCtr) { // Failed to get containers. If force is specified, get the containers ID // and evict them @@ -239,3 +276,344 @@ func (ic *ContainerEngine) ContainerRm(ctx context.Context, namesOrIds []string, } return reports, nil } + +func (ic *ContainerEngine) ContainerInspect(ctx context.Context, namesOrIds []string, options entities.InspectOptions) ([]*entities.ContainerInspectReport, error) { + var reports []*entities.ContainerInspectReport + ctrs, err := getContainersByContext(false, options.Latest, namesOrIds, ic.Libpod) + if err != nil { + return nil, err + } + for _, c := range ctrs { + data, err := c.Inspect(options.Size) + if err != nil { + return nil, err + } + reports = append(reports, &entities.ContainerInspectReport{InspectContainerData: data}) + } + return reports, nil +} + +func (ic *ContainerEngine) ContainerTop(ctx context.Context, options entities.TopOptions) (*entities.StringSliceReport, error) { + var ( + container *libpod.Container + err error + ) + + // Look up the container. + if options.Latest { + container, err = ic.Libpod.GetLatestContainer() + } else { + container, err = ic.Libpod.LookupContainer(options.NameOrID) + } + if err != nil { + return nil, errors.Wrap(err, "unable to lookup requested container") + } + + // Run Top. + report := &entities.StringSliceReport{} + report.Value, err = container.Top(options.Descriptors) + return report, err +} + +func (ic *ContainerEngine) ContainerCommit(ctx context.Context, nameOrId string, options entities.CommitOptions) (*entities.CommitReport, error) { + var ( + mimeType string + ) + ctr, err := ic.Libpod.LookupContainer(nameOrId) + if err != nil { + return nil, err + } + rtc, err := ic.Libpod.GetConfig() + if err != nil { + return nil, err + } + switch options.Format { + case "oci": + mimeType = buildah.OCIv1ImageManifest + if len(options.Message) > 0 { + return nil, errors.Errorf("messages are only compatible with the docker image format (-f docker)") + } + case "docker": + mimeType = manifest.DockerV2Schema2MediaType + default: + return nil, errors.Errorf("unrecognized image format %q", options.Format) + } + sc := image.GetSystemContext(rtc.Engine.SignaturePolicyPath, "", false) + coptions := buildah.CommitOptions{ + SignaturePolicyPath: rtc.Engine.SignaturePolicyPath, + ReportWriter: options.Writer, + SystemContext: sc, + PreferredManifestType: mimeType, + } + opts := libpod.ContainerCommitOptions{ + CommitOptions: coptions, + Pause: options.Pause, + IncludeVolumes: options.IncludeVolumes, + Message: options.Message, + Changes: options.Changes, + Author: options.Author, + } + newImage, err := ctr.Commit(ctx, options.ImageName, opts) + if err != nil { + return nil, err + } + return &entities.CommitReport{Id: newImage.ID()}, nil +} + +func (ic *ContainerEngine) ContainerExport(ctx context.Context, nameOrId string, options entities.ContainerExportOptions) error { + ctr, err := ic.Libpod.LookupContainer(nameOrId) + if err != nil { + return err + } + return ctr.Export(options.Output) +} + +func (ic *ContainerEngine) ContainerCheckpoint(ctx context.Context, namesOrIds []string, options entities.CheckpointOptions) ([]*entities.CheckpointReport, error) { + var ( + err error + cons []*libpod.Container + reports []*entities.CheckpointReport + ) + checkOpts := libpod.ContainerCheckpointOptions{ + Keep: options.Keep, + TCPEstablished: options.TCPEstablished, + TargetFile: options.Export, + IgnoreRootfs: options.IgnoreRootFS, + } + + if options.All { + running := func(c *libpod.Container) bool { + state, _ := c.State() + return state == define.ContainerStateRunning + } + cons, err = ic.Libpod.GetContainers(running) + } else { + cons, err = getContainersByContext(false, options.Latest, namesOrIds, ic.Libpod) + } + if err != nil { + return nil, err + } + for _, con := range cons { + err = con.Checkpoint(ctx, checkOpts) + reports = append(reports, &entities.CheckpointReport{ + Err: err, + Id: con.ID(), + }) + } + return reports, nil +} + +func (ic *ContainerEngine) ContainerRestore(ctx context.Context, namesOrIds []string, options entities.RestoreOptions) ([]*entities.RestoreReport, error) { + var ( + cons []*libpod.Container + err error + filterFuncs []libpod.ContainerFilter + reports []*entities.RestoreReport + ) + + restoreOptions := libpod.ContainerCheckpointOptions{ + Keep: options.Keep, + TCPEstablished: options.TCPEstablished, + TargetFile: options.Import, + Name: options.Name, + IgnoreRootfs: options.IgnoreRootFS, + IgnoreStaticIP: options.IgnoreStaticIP, + IgnoreStaticMAC: options.IgnoreStaticMAC, + } + + filterFuncs = append(filterFuncs, func(c *libpod.Container) bool { + state, _ := c.State() + return state == define.ContainerStateExited + }) + + switch { + case options.Import != "": + cons, err = checkpoint.CRImportCheckpoint(ctx, ic.Libpod, options.Import, options.Name) + case options.All: + cons, err = ic.Libpod.GetContainers(filterFuncs...) + default: + cons, err = getContainersByContext(false, options.Latest, namesOrIds, ic.Libpod) + } + if err != nil { + return nil, err + } + for _, con := range cons { + err := con.Restore(ctx, restoreOptions) + reports = append(reports, &entities.RestoreReport{ + Err: err, + Id: con.ID(), + }) + } + return reports, nil +} + +func (ic *ContainerEngine) ContainerCreate(ctx context.Context, s *specgen.SpecGenerator) (*entities.ContainerCreateReport, error) { + if err := generate.CompleteSpec(ctx, ic.Libpod, s); err != nil { + return nil, err + } + ctr, err := generate.MakeContainer(ic.Libpod, s) + if err != nil { + return nil, err + } + return &entities.ContainerCreateReport{Id: ctr.ID()}, nil +} + +func (ic *ContainerEngine) ContainerAttach(ctx context.Context, nameOrId string, options entities.AttachOptions) error { + ctrs, err := getContainersByContext(false, options.Latest, []string{nameOrId}, ic.Libpod) + if err != nil { + return err + } + ctr := ctrs[0] + conState, err := ctr.State() + if err != nil { + return errors.Wrapf(err, "unable to determine state of %s", ctr.ID()) + } + if conState != define.ContainerStateRunning { + return errors.Errorf("you can only attach to running containers") + } + + // If the container is in a pod, also set to recursively start dependencies + if err := terminal.StartAttachCtr(ctx, ctr, options.Stdin, options.Stderr, options.Stdin, options.DetachKeys, options.SigProxy, false, ctr.PodID() != ""); err != nil && errors.Cause(err) != define.ErrDetach { + return errors.Wrapf(err, "error attaching to container %s", ctr.ID()) + } + return nil +} + +func (ic *ContainerEngine) ContainerExec(ctx context.Context, nameOrId string, options entities.ExecOptions) (int, error) { + ec := define.ExecErrorCodeGeneric + if options.PreserveFDs > 0 { + entries, err := ioutil.ReadDir("/proc/self/fd") + if err != nil { + return ec, errors.Wrapf(err, "unable to read /proc/self/fd") + } + + m := make(map[int]bool) + for _, e := range entries { + i, err := strconv.Atoi(e.Name()) + if err != nil { + return ec, errors.Wrapf(err, "cannot parse %s in /proc/self/fd", e.Name()) + } + m[i] = true + } + + for i := 3; i < 3+int(options.PreserveFDs); i++ { + if _, found := m[i]; !found { + return ec, errors.New("invalid --preserve-fds=N specified. Not enough FDs available") + } + } + } + ctrs, err := getContainersByContext(false, options.Latest, []string{nameOrId}, ic.Libpod) + if err != nil { + return ec, err + } + ctr := ctrs[0] + ec, err = terminal.ExecAttachCtr(ctx, ctr, options.Tty, options.Privileged, options.Envs, options.Cmd, options.User, options.WorkDir, &options.Streams, options.PreserveFDs, options.DetachKeys) + return define.TranslateExecErrorToExitCode(ec, err), err +} + +func (ic *ContainerEngine) ContainerStart(ctx context.Context, namesOrIds []string, options entities.ContainerStartOptions) ([]*entities.ContainerStartReport, error) { + var reports []*entities.ContainerStartReport + var exitCode = define.ExecErrorCodeGeneric + ctrs, err := getContainersByContext(false, options.Latest, namesOrIds, ic.Libpod) + if err != nil { + return nil, err + } + // There can only be one container if attach was used + for _, ctr := range ctrs { + ctrState, err := ctr.State() + if err != nil { + return nil, err + } + ctrRunning := ctrState == define.ContainerStateRunning + + if options.Attach { + err = terminal.StartAttachCtr(ctx, ctr, options.Stdout, options.Stderr, options.Stdin, options.DetachKeys, options.SigProxy, !ctrRunning, ctr.PodID() != "") + if errors.Cause(err) == define.ErrDetach { + // User manually detached + // Exit cleanly immediately + reports = append(reports, &entities.ContainerStartReport{ + Id: ctr.ID(), + Err: nil, + ExitCode: 0, + }) + return reports, nil + } + + if errors.Cause(err) == define.ErrWillDeadlock { + logrus.Debugf("Deadlock error: %v", err) + reports = append(reports, &entities.ContainerStartReport{ + Id: ctr.ID(), + Err: err, + ExitCode: define.ExitCode(err), + }) + return reports, errors.Errorf("attempting to start container %s would cause a deadlock; please run 'podman system renumber' to resolve", ctr.ID()) + } + + if ctrRunning { + reports = append(reports, &entities.ContainerStartReport{ + Id: ctr.ID(), + Err: nil, + ExitCode: 0, + }) + return reports, err + } + + if err != nil { + reports = append(reports, &entities.ContainerStartReport{ + Id: ctr.ID(), + Err: err, + ExitCode: exitCode, + }) + return reports, errors.Wrapf(err, "unable to start container %s", ctr.ID()) + } + + if ecode, err := ctr.Wait(); err != nil { + if errors.Cause(err) == define.ErrNoSuchCtr { + // Check events + event, err := ic.Libpod.GetLastContainerEvent(ctr.ID(), events.Exited) + if err != nil { + logrus.Errorf("Cannot get exit code: %v", err) + exitCode = define.ExecErrorCodeNotFound + } else { + exitCode = event.ContainerExitCode + } + } + } else { + exitCode = int(ecode) + } + reports = append(reports, &entities.ContainerStartReport{ + Id: ctr.ID(), + Err: err, + ExitCode: exitCode, + }) + return reports, nil + } // end attach + + // Start the container if it's not running already. + if !ctrRunning { + // Handle non-attach start + // If the container is in a pod, also set to recursively start dependencies + report := &entities.ContainerStartReport{ + Id: ctr.ID(), + ExitCode: 125, + } + if err := ctr.Start(ctx, ctr.PodID() != ""); err != nil { + //if lastError != nil { + // fmt.Fprintln(os.Stderr, lastError) + //} + report.Err = err + if errors.Cause(err) == define.ErrWillDeadlock { + report.Err = errors.Wrapf(err, "please run 'podman system renumber' to resolve deadlocks") + reports = append(reports, report) + continue + } + report.Err = errors.Wrapf(err, "unable to start container %q", ctr.ID()) + reports = append(reports, report) + continue + } + report.ExitCode = 0 + reports = append(reports, report) + } + } + return reports, nil +} diff --git a/pkg/domain/infra/abi/healthcheck.go b/pkg/domain/infra/abi/healthcheck.go new file mode 100644 index 000000000..699483243 --- /dev/null +++ b/pkg/domain/infra/abi/healthcheck.go @@ -0,0 +1,26 @@ +// +build ABISupport + +package abi + +import ( + "context" + + "github.com/containers/libpod/libpod" + "github.com/containers/libpod/libpod/define" + "github.com/containers/libpod/pkg/domain/entities" +) + +func (ic *ContainerEngine) HealthCheckRun(ctx context.Context, nameOrId string, options entities.HealthCheckOptions) (*define.HealthCheckResults, error) { + status, err := ic.Libpod.HealthCheck(nameOrId) + if err != nil { + return nil, err + } + hcStatus := "unhealthy" + if status == libpod.HealthCheckSuccess { + hcStatus = "healthy" + } + report := define.HealthCheckResults{ + Status: hcStatus, + } + return &report, nil +} diff --git a/pkg/domain/infra/abi/images.go b/pkg/domain/infra/abi/images.go index 6e9d7f566..9d706a112 100644 --- a/pkg/domain/infra/abi/images.go +++ b/pkg/domain/infra/abi/images.go @@ -4,38 +4,116 @@ package abi import ( "context" + "fmt" + "io" + "os" + "strings" + "github.com/containers/image/v5/docker" + dockerarchive "github.com/containers/image/v5/docker/archive" + "github.com/containers/image/v5/docker/reference" + "github.com/containers/image/v5/manifest" + "github.com/containers/image/v5/transports/alltransports" + "github.com/containers/image/v5/types" + "github.com/containers/libpod/libpod/image" libpodImage "github.com/containers/libpod/libpod/image" "github.com/containers/libpod/pkg/domain/entities" - "github.com/containers/libpod/pkg/domain/utils" + domainUtils "github.com/containers/libpod/pkg/domain/utils" + "github.com/containers/libpod/pkg/util" + "github.com/containers/storage" + imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1" + "github.com/pkg/errors" + "github.com/sirupsen/logrus" ) -func (ir *ImageEngine) Delete(ctx context.Context, nameOrId string, opts entities.ImageDeleteOptions) (*entities.ImageDeleteReport, error) { - image, err := ir.Libpod.ImageRuntime().NewFromLocal(nameOrId) - if err != nil { - return nil, err +func (ir *ImageEngine) Exists(_ context.Context, nameOrId string) (*entities.BoolReport, error) { + if _, err := ir.Libpod.ImageRuntime().NewFromLocal(nameOrId); err != nil { + return &entities.BoolReport{}, nil } + return &entities.BoolReport{Value: true}, nil +} - results, err := ir.Libpod.RemoveImage(ctx, image, opts.Force) - if err != nil { - return nil, err +func (ir *ImageEngine) Delete(ctx context.Context, nameOrId []string, opts entities.ImageDeleteOptions) (*entities.ImageDeleteReport, error) { + report := entities.ImageDeleteReport{} + + if opts.All { + var previousTargets []*libpodImage.Image + repeatRun: + targets, err := ir.Libpod.ImageRuntime().GetRWImages() + if err != nil { + return &report, errors.Wrapf(err, "unable to query local images") + } + + if len(targets) > 0 && len(targets) == len(previousTargets) { + return &report, errors.New("unable to delete all images; re-run the rmi command again.") + } + previousTargets = targets + + for _, img := range targets { + isParent, err := img.IsParent(ctx) + if err != nil { + return &report, err + } + if isParent { + continue + } + err = ir.deleteImage(ctx, img, opts, report) + report.Errors = append(report.Errors, err) + } + if len(previousTargets) != 1 { + goto repeatRun + } + return &report, nil } - report := entities.ImageDeleteReport{} - if err := utils.DeepCopy(&report, results); err != nil { - return nil, err + for _, id := range nameOrId { + image, err := ir.Libpod.ImageRuntime().NewFromLocal(id) + if err != nil { + return nil, err + } + + err = ir.deleteImage(ctx, image, opts, report) + if err != nil { + return &report, err + } } return &report, nil } +func (ir *ImageEngine) deleteImage(ctx context.Context, img *libpodImage.Image, opts entities.ImageDeleteOptions, report entities.ImageDeleteReport) error { + results, err := ir.Libpod.RemoveImage(ctx, img, opts.Force) + switch errors.Cause(err) { + case nil: + break + case storage.ErrImageUsedByContainer: + report.ImageInUse = errors.New( + fmt.Sprintf("A container associated with containers/storage, i.e. via Buildah, CRI-O, etc., may be associated with this image: %-12.12s\n", img.ID())) + return nil + case libpodImage.ErrNoSuchImage: + report.ImageNotFound = err + return nil + default: + return err + } + + report.Deleted = append(report.Deleted, results.Deleted) + report.Untagged = append(report.Untagged, results.Untagged...) + return nil +} + func (ir *ImageEngine) Prune(ctx context.Context, opts entities.ImagePruneOptions) (*entities.ImagePruneReport, error) { - results, err := ir.Libpod.ImageRuntime().PruneImages(ctx, opts.All, []string{}) + results, err := ir.Libpod.ImageRuntime().PruneImages(ctx, opts.All, opts.Filter) if err != nil { return nil, err } - report := entities.ImagePruneReport{} - copy(report.Report.Id, results) + report := entities.ImagePruneReport{ + Report: entities.Report{ + Id: results, + Err: nil, + }, + Size: 0, + } return &report, nil } @@ -70,6 +148,178 @@ func ToDomainHistoryLayer(layer *libpodImage.History) entities.ImageHistoryLayer return l } +func (ir *ImageEngine) Pull(ctx context.Context, rawImage string, options entities.ImagePullOptions) (*entities.ImagePullReport, error) { + var writer io.Writer + if !options.Quiet { + writer = os.Stderr + } + + dockerPrefix := fmt.Sprintf("%s://", docker.Transport.Name()) + imageRef, err := alltransports.ParseImageName(rawImage) + if err != nil { + imageRef, err = alltransports.ParseImageName(fmt.Sprintf("%s%s", dockerPrefix, rawImage)) + if err != nil { + return nil, errors.Errorf("invalid image reference %q", rawImage) + } + } + + // Special-case for docker-archive which allows multiple tags. + if imageRef.Transport().Name() == dockerarchive.Transport.Name() { + newImage, err := ir.Libpod.ImageRuntime().LoadFromArchiveReference(ctx, imageRef, options.SignaturePolicy, writer) + if err != nil { + return nil, errors.Wrapf(err, "error pulling image %q", rawImage) + } + return &entities.ImagePullReport{Images: []string{newImage[0].ID()}}, nil + } + + var registryCreds *types.DockerAuthConfig + if options.Credentials != "" { + creds, err := util.ParseRegistryCreds(options.Credentials) + if err != nil { + return nil, err + } + registryCreds = creds + } + dockerRegistryOptions := image.DockerRegistryOptions{ + DockerRegistryCreds: registryCreds, + DockerCertPath: options.CertDir, + OSChoice: options.OverrideOS, + ArchitectureChoice: options.OverrideArch, + DockerInsecureSkipTLSVerify: options.TLSVerify, + } + + if !options.AllTags { + newImage, err := ir.Libpod.ImageRuntime().New(ctx, rawImage, options.SignaturePolicy, options.Authfile, writer, &dockerRegistryOptions, image.SigningOptions{}, nil, util.PullImageAlways) + if err != nil { + return nil, errors.Wrapf(err, "error pulling image %q", rawImage) + } + return &entities.ImagePullReport{Images: []string{newImage.ID()}}, nil + } + + // --all-tags requires the docker transport + if imageRef.Transport().Name() != docker.Transport.Name() { + return nil, errors.New("--all-tags requires docker transport") + } + + // Trim the docker-transport prefix. + rawImage = strings.TrimPrefix(rawImage, docker.Transport.Name()) + + // all-tags doesn't work with a tagged reference, so let's check early + namedRef, err := reference.Parse(rawImage) + if err != nil { + return nil, errors.Wrapf(err, "error parsing %q", rawImage) + } + if _, isTagged := namedRef.(reference.Tagged); isTagged { + return nil, errors.New("--all-tags requires a reference without a tag") + + } + + systemContext := image.GetSystemContext("", options.Authfile, false) + tags, err := docker.GetRepositoryTags(ctx, systemContext, imageRef) + if err != nil { + return nil, errors.Wrapf(err, "error getting repository tags") + } + + var foundIDs []string + for _, tag := range tags { + name := rawImage + ":" + tag + newImage, err := ir.Libpod.ImageRuntime().New(ctx, name, options.SignaturePolicy, options.Authfile, writer, &dockerRegistryOptions, image.SigningOptions{}, nil, util.PullImageAlways) + if err != nil { + logrus.Errorf("error pulling image %q", name) + continue + } + foundIDs = append(foundIDs, newImage.ID()) + } + + if len(tags) != len(foundIDs) { + return nil, errors.Errorf("error pulling image %q", rawImage) + } + return &entities.ImagePullReport{Images: foundIDs}, nil +} + +func (ir *ImageEngine) Inspect(ctx context.Context, names []string, opts entities.InspectOptions) (*entities.ImageInspectReport, error) { + report := entities.ImageInspectReport{ + Errors: make(map[string]error), + } + + for _, id := range names { + img, err := ir.Libpod.ImageRuntime().NewFromLocal(id) + if err != nil { + report.Errors[id] = err + continue + } + + results, err := img.Inspect(ctx) + if err != nil { + report.Errors[id] = err + continue + } + + cookedResults := entities.ImageData{} + _ = domainUtils.DeepCopy(&cookedResults, results) + report.Images = append(report.Images, &cookedResults) + } + return &report, nil +} + +func (ir *ImageEngine) Push(ctx context.Context, source string, destination string, options entities.ImagePushOptions) error { + var writer io.Writer + if !options.Quiet { + writer = os.Stderr + } + + var manifestType string + switch options.Format { + case "": + // Default + case "oci": + manifestType = imgspecv1.MediaTypeImageManifest + case "v2s1": + manifestType = manifest.DockerV2Schema1SignedMediaType + case "v2s2", "docker": + manifestType = manifest.DockerV2Schema2MediaType + default: + return fmt.Errorf("unknown format %q. Choose on of the supported formats: 'oci', 'v2s1', or 'v2s2'", options.Format) + } + + var registryCreds *types.DockerAuthConfig + if options.Credentials != "" { + creds, err := util.ParseRegistryCreds(options.Credentials) + if err != nil { + return err + } + registryCreds = creds + } + dockerRegistryOptions := image.DockerRegistryOptions{ + DockerRegistryCreds: registryCreds, + DockerCertPath: options.CertDir, + DockerInsecureSkipTLSVerify: options.TLSVerify, + } + + signOptions := image.SigningOptions{ + RemoveSignatures: options.RemoveSignatures, + SignBy: options.SignBy, + } + + newImage, err := ir.Libpod.ImageRuntime().NewFromLocal(source) + if err != nil { + return err + } + + return newImage.PushImageToHeuristicDestination( + ctx, + destination, + manifestType, + options.Authfile, + options.DigestFile, + options.SignaturePolicy, + writer, + options.Compress, + signOptions, + &dockerRegistryOptions, + nil) +} + // func (r *imageRuntime) Delete(ctx context.Context, nameOrId string, opts entities.ImageDeleteOptions) (*entities.ImageDeleteReport, error) { // image, err := r.libpod.ImageEngine().NewFromLocal(nameOrId) // if err != nil { @@ -82,7 +332,7 @@ func ToDomainHistoryLayer(layer *libpodImage.History) entities.ImageHistoryLayer // } // // report := entities.ImageDeleteReport{} -// if err := utils.DeepCopy(&report, results); err != nil { +// if err := domainUtils.DeepCopy(&report, results); err != nil { // return nil, err // } // return &report, nil @@ -100,3 +350,66 @@ func ToDomainHistoryLayer(layer *libpodImage.History) entities.ImageHistoryLayer // copy(report.Report.Id, id) // return &report, nil // } + +func (ir *ImageEngine) Tag(ctx context.Context, nameOrId string, tags []string, options entities.ImageTagOptions) error { + newImage, err := ir.Libpod.ImageRuntime().NewFromLocal(nameOrId) + if err != nil { + return err + } + for _, tag := range tags { + if err := newImage.TagImage(tag); err != nil { + return err + } + } + return nil +} + +func (ir *ImageEngine) Untag(ctx context.Context, nameOrId string, tags []string, options entities.ImageUntagOptions) error { + newImage, err := ir.Libpod.ImageRuntime().NewFromLocal(nameOrId) + if err != nil { + return err + } + for _, tag := range tags { + if err := newImage.UntagImage(tag); err != nil { + return err + } + } + return nil +} + +func (ir *ImageEngine) Load(ctx context.Context, opts entities.ImageLoadOptions) (*entities.ImageLoadReport, error) { + var ( + writer io.Writer + ) + if !opts.Quiet { + writer = os.Stderr + } + name, err := ir.Libpod.LoadImage(ctx, opts.Name, opts.Input, writer, opts.SignaturePolicy) + if err != nil { + return nil, err + } + newImage, err := ir.Libpod.ImageRuntime().NewFromLocal(name) + if err != nil { + return nil, errors.Wrap(err, "image loaded but no additional tags were created") + } + if err := newImage.TagImage(opts.Name); err != nil { + return nil, errors.Wrapf(err, "error adding %q to image %q", opts.Name, newImage.InputName) + } + return &entities.ImageLoadReport{Name: name}, nil +} + +func (ir *ImageEngine) Import(ctx context.Context, opts entities.ImageImportOptions) (*entities.ImageImportReport, error) { + id, err := ir.Libpod.Import(ctx, opts.Source, opts.Reference, opts.Changes, opts.Message, opts.Quiet) + if err != nil { + return nil, err + } + return &entities.ImageImportReport{Id: id}, nil +} + +func (ir *ImageEngine) Save(ctx context.Context, nameOrId string, tags []string, options entities.ImageSaveOptions) error { + newImage, err := ir.Libpod.ImageRuntime().NewFromLocal(nameOrId) + if err != nil { + return err + } + return newImage.Save(ctx, nameOrId, options.Format, options.Output, tags, options.Quiet, options.Compress) +} diff --git a/pkg/domain/infra/abi/pods.go b/pkg/domain/infra/abi/pods.go index de22de68e..073cd8d5c 100644 --- a/pkg/domain/infra/abi/pods.go +++ b/pkg/domain/infra/abi/pods.go @@ -4,12 +4,48 @@ package abi import ( "context" - "github.com/pkg/errors" + "github.com/containers/libpod/libpod" "github.com/containers/libpod/libpod/define" + "github.com/containers/libpod/libpod/podfilters" "github.com/containers/libpod/pkg/domain/entities" + "github.com/containers/libpod/pkg/signal" + "github.com/containers/libpod/pkg/specgen" + "github.com/pkg/errors" + "github.com/sirupsen/logrus" ) +// getPodsByContext returns a slice of pods. Note that all, latest and pods are +// mutually exclusive arguments. +func getPodsByContext(all, latest bool, pods []string, runtime *libpod.Runtime) ([]*libpod.Pod, error) { + var outpods []*libpod.Pod + if all { + return runtime.GetAllPods() + } + if latest { + p, err := runtime.GetLatestPod() + if err != nil { + return nil, err + } + outpods = append(outpods, p) + return outpods, nil + } + var err error + for _, p := range pods { + pod, e := runtime.LookupPod(p) + if e != nil { + // Log all errors here, so callers don't need to. + logrus.Debugf("Error looking up pod %q: %v", p, e) + if err == nil { + err = e + } + } else { + outpods = append(outpods, pod) + } + } + return outpods, err +} + func (ic *ContainerEngine) PodExists(ctx context.Context, nameOrId string) (*entities.BoolReport, error) { _, err := ic.Libpod.LookupPod(nameOrId) if err != nil && errors.Cause(err) != define.ErrNoSuchPod { @@ -17,3 +53,302 @@ func (ic *ContainerEngine) PodExists(ctx context.Context, nameOrId string) (*ent } return &entities.BoolReport{Value: err == nil}, nil } + +func (ic *ContainerEngine) PodKill(ctx context.Context, namesOrIds []string, options entities.PodKillOptions) ([]*entities.PodKillReport, error) { + var ( + reports []*entities.PodKillReport + ) + sig, err := signal.ParseSignalNameOrNumber(options.Signal) + if err != nil { + return nil, err + } + pods, err := getPodsByContext(options.All, options.Latest, namesOrIds, ic.Libpod) + if err != nil { + return nil, err + } + + for _, p := range pods { + report := entities.PodKillReport{Id: p.ID()} + conErrs, err := p.Kill(uint(sig)) + if err != nil { + report.Errs = []error{err} + reports = append(reports, &report) + continue + } + if len(conErrs) > 0 { + for _, err := range conErrs { + report.Errs = append(report.Errs, err) + } + reports = append(reports, &report) + continue + } + reports = append(reports, &report) + } + return reports, nil +} + +func (ic *ContainerEngine) PodPause(ctx context.Context, namesOrIds []string, options entities.PodPauseOptions) ([]*entities.PodPauseReport, error) { + var ( + reports []*entities.PodPauseReport + ) + pods, err := getPodsByContext(options.All, options.Latest, namesOrIds, ic.Libpod) + if err != nil { + return nil, err + } + for _, p := range pods { + report := entities.PodPauseReport{Id: p.ID()} + errs, err := p.Pause() + if err != nil { + report.Errs = []error{err} + continue + } + if len(errs) > 0 { + for _, v := range errs { + report.Errs = append(report.Errs, v) + } + reports = append(reports, &report) + continue + } + reports = append(reports, &report) + } + return reports, nil +} + +func (ic *ContainerEngine) PodUnpause(ctx context.Context, namesOrIds []string, options entities.PodunpauseOptions) ([]*entities.PodUnpauseReport, error) { + var ( + reports []*entities.PodUnpauseReport + ) + pods, err := getPodsByContext(options.All, options.Latest, namesOrIds, ic.Libpod) + if err != nil { + return nil, err + } + for _, p := range pods { + report := entities.PodUnpauseReport{Id: p.ID()} + errs, err := p.Unpause() + if err != nil { + report.Errs = []error{err} + continue + } + if len(errs) > 0 { + for _, v := range errs { + report.Errs = append(report.Errs, v) + } + reports = append(reports, &report) + continue + } + reports = append(reports, &report) + } + return reports, nil +} + +func (ic *ContainerEngine) PodStop(ctx context.Context, namesOrIds []string, options entities.PodStopOptions) ([]*entities.PodStopReport, error) { + var ( + reports []*entities.PodStopReport + ) + pods, err := getPodsByContext(options.All, options.Latest, namesOrIds, ic.Libpod) + if err != nil { + return nil, err + } + for _, p := range pods { + report := entities.PodStopReport{Id: p.ID()} + errs, err := p.StopWithTimeout(ctx, false, options.Timeout) + if err != nil { + report.Errs = []error{err} + continue + } + if len(errs) > 0 { + for _, v := range errs { + report.Errs = append(report.Errs, v) + } + reports = append(reports, &report) + continue + } + reports = append(reports, &report) + } + return reports, nil +} + +func (ic *ContainerEngine) PodRestart(ctx context.Context, namesOrIds []string, options entities.PodRestartOptions) ([]*entities.PodRestartReport, error) { + var ( + reports []*entities.PodRestartReport + ) + pods, err := getPodsByContext(options.All, options.Latest, namesOrIds, ic.Libpod) + if err != nil { + return nil, err + } + for _, p := range pods { + report := entities.PodRestartReport{Id: p.ID()} + errs, err := p.Restart(ctx) + if err != nil { + report.Errs = []error{err} + continue + } + if len(errs) > 0 { + for _, v := range errs { + report.Errs = append(report.Errs, v) + } + reports = append(reports, &report) + continue + } + reports = append(reports, &report) + } + return reports, nil +} + +func (ic *ContainerEngine) PodStart(ctx context.Context, namesOrIds []string, options entities.PodStartOptions) ([]*entities.PodStartReport, error) { + var ( + reports []*entities.PodStartReport + ) + pods, err := getPodsByContext(options.All, options.Latest, namesOrIds, ic.Libpod) + if err != nil { + return nil, err + } + for _, p := range pods { + report := entities.PodStartReport{Id: p.ID()} + errs, err := p.Start(ctx) + if err != nil { + report.Errs = []error{err} + continue + } + if len(errs) > 0 { + for _, v := range errs { + report.Errs = append(report.Errs, v) + } + reports = append(reports, &report) + continue + } + reports = append(reports, &report) + } + return reports, nil +} + +func (ic *ContainerEngine) PodRm(ctx context.Context, namesOrIds []string, options entities.PodRmOptions) ([]*entities.PodRmReport, error) { + var ( + reports []*entities.PodRmReport + ) + pods, err := getPodsByContext(options.All, options.Latest, namesOrIds, ic.Libpod) + if err != nil { + return nil, err + } + for _, p := range pods { + report := entities.PodRmReport{Id: p.ID()} + err := ic.Libpod.RemovePod(ctx, p, true, options.Force) + if err != nil { + report.Err = err + continue + } + reports = append(reports, &report) + } + return reports, nil +} + +func (ic *ContainerEngine) PodCreate(ctx context.Context, opts entities.PodCreateOptions) (*entities.PodCreateReport, error) { + podSpec := specgen.NewPodSpecGenerator() + opts.ToPodSpecGen(podSpec) + pod, err := podSpec.MakePod(ic.Libpod) + if err != nil { + return nil, err + } + return &entities.PodCreateReport{Id: pod.ID()}, nil +} + +func (ic *ContainerEngine) PodTop(ctx context.Context, options entities.PodTopOptions) (*entities.StringSliceReport, error) { + var ( + pod *libpod.Pod + err error + ) + + // Look up the pod. + if options.Latest { + pod, err = ic.Libpod.GetLatestPod() + } else { + pod, err = ic.Libpod.LookupPod(options.NameOrID) + } + if err != nil { + return nil, errors.Wrap(err, "unable to lookup requested container") + } + + // Run Top. + report := &entities.StringSliceReport{} + report.Value, err = pod.GetPodPidInformation(options.Descriptors) + return report, err +} + +func (ic *ContainerEngine) PodPs(ctx context.Context, options entities.PodPSOptions) ([]*entities.ListPodsReport, error) { + var ( + filters []libpod.PodFilter + reports []*entities.ListPodsReport + ) + for k, v := range options.Filters { + for _, filter := range v { + f, err := podfilters.GeneratePodFilterFunc(k, filter) + if err != nil { + return nil, err + } + filters = append(filters, f) + + } + } + pds, err := ic.Libpod.Pods(filters...) + if err != nil { + return nil, err + } + for _, p := range pds { + var lpcs []*entities.ListPodContainer + status, err := p.GetPodStatus() + if err != nil { + return nil, err + } + cons, err := p.AllContainers() + if err != nil { + return nil, err + } + for _, c := range cons { + state, err := c.State() + if err != nil { + return nil, err + } + lpcs = append(lpcs, &entities.ListPodContainer{ + Id: c.ID(), + Names: c.Name(), + Status: state.String(), + }) + } + infraId, err := p.InfraContainerID() + if err != nil { + return nil, err + } + reports = append(reports, &entities.ListPodsReport{ + Cgroup: p.CgroupParent(), + Containers: lpcs, + Created: p.CreatedTime(), + Id: p.ID(), + InfraId: infraId, + Name: p.Name(), + Namespace: p.Namespace(), + Status: status, + }) + } + return reports, nil +} + +func (ic *ContainerEngine) PodInspect(ctx context.Context, options entities.PodInspectOptions) (*entities.PodInspectReport, error) { + var ( + pod *libpod.Pod + err error + ) + // Look up the pod. + if options.Latest { + pod, err = ic.Libpod.GetLatestPod() + } else { + pod, err = ic.Libpod.LookupPod(options.NameOrID) + } + if err != nil { + return nil, errors.Wrap(err, "unable to lookup requested container") + } + inspect, err := pod.Inspect() + if err != nil { + return nil, err + } + return &entities.PodInspectReport{PodInspect: inspect}, nil +} diff --git a/pkg/domain/infra/abi/terminal/sigproxy_linux.go b/pkg/domain/infra/abi/terminal/sigproxy_linux.go new file mode 100644 index 000000000..d7f5853d8 --- /dev/null +++ b/pkg/domain/infra/abi/terminal/sigproxy_linux.go @@ -0,0 +1,47 @@ +// +build ABISupport + +package terminal + +import ( + "os" + "syscall" + + "github.com/containers/libpod/libpod" + "github.com/containers/libpod/pkg/signal" + "github.com/sirupsen/logrus" +) + +// ProxySignals ... +func ProxySignals(ctr *libpod.Container) { + sigBuffer := make(chan os.Signal, 128) + signal.CatchAll(sigBuffer) + + logrus.Debugf("Enabling signal proxying") + + go func() { + for s := range sigBuffer { + // Ignore SIGCHLD and SIGPIPE - these are mostly likely + // intended for the podman command itself. + // SIGURG was added because of golang 1.14 and its preemptive changes + // causing more signals to "show up". + // https://github.com/containers/libpod/issues/5483 + if s == syscall.SIGCHLD || s == syscall.SIGPIPE || s == syscall.SIGURG { + continue + } + + if err := ctr.Kill(uint(s.(syscall.Signal))); err != nil { + // If the container dies, and we find out here, + // we need to forward that one signal to + // ourselves so that it is not lost, and then + // we terminate the proxy and let the defaults + // play out. + logrus.Errorf("Error forwarding signal %d to container %s: %v", s, ctr.ID(), err) + signal.StopCatch(sigBuffer) + if err := syscall.Kill(syscall.Getpid(), s.(syscall.Signal)); err != nil { + logrus.Errorf("failed to kill pid %d", syscall.Getpid()) + } + return + } + } + }() +} diff --git a/pkg/domain/infra/abi/terminal/terminal.go b/pkg/domain/infra/abi/terminal/terminal.go new file mode 100644 index 000000000..f187bdd6b --- /dev/null +++ b/pkg/domain/infra/abi/terminal/terminal.go @@ -0,0 +1,103 @@ +// +build ABISupport + +package terminal + +import ( + "context" + "os" + "os/signal" + + lsignal "github.com/containers/libpod/pkg/signal" + "github.com/docker/docker/pkg/term" + "github.com/pkg/errors" + "github.com/sirupsen/logrus" + "k8s.io/client-go/tools/remotecommand" +) + +// RawTtyFormatter ... +type RawTtyFormatter struct { +} + +// getResize returns a TerminalSize command matching stdin's current +// size on success, and nil on errors. +func getResize() *remotecommand.TerminalSize { + winsize, err := term.GetWinsize(os.Stdin.Fd()) + if err != nil { + logrus.Warnf("Could not get terminal size %v", err) + return nil + } + return &remotecommand.TerminalSize{ + Width: winsize.Width, + Height: winsize.Height, + } +} + +// Helper for prepareAttach - set up a goroutine to generate terminal resize events +func resizeTty(ctx context.Context, resize chan remotecommand.TerminalSize) { + sigchan := make(chan os.Signal, 1) + signal.Notify(sigchan, lsignal.SIGWINCH) + go func() { + defer close(resize) + // Update the terminal size immediately without waiting + // for a SIGWINCH to get the correct initial size. + resizeEvent := getResize() + for { + if resizeEvent == nil { + select { + case <-ctx.Done(): + return + case <-sigchan: + resizeEvent = getResize() + } + } else { + select { + case <-ctx.Done(): + return + case <-sigchan: + resizeEvent = getResize() + case resize <- *resizeEvent: + resizeEvent = nil + } + } + } + }() +} + +func restoreTerminal(state *term.State) error { + logrus.SetFormatter(&logrus.TextFormatter{}) + return term.RestoreTerminal(os.Stdin.Fd(), state) +} + +// Format ... +func (f *RawTtyFormatter) Format(entry *logrus.Entry) ([]byte, error) { + textFormatter := logrus.TextFormatter{} + bytes, err := textFormatter.Format(entry) + + if err == nil { + bytes = append(bytes, '\r') + } + + return bytes, err +} + +func handleTerminalAttach(ctx context.Context, resize chan remotecommand.TerminalSize) (context.CancelFunc, *term.State, error) { + logrus.Debugf("Handling terminal attach") + + subCtx, cancel := context.WithCancel(ctx) + + resizeTty(subCtx, resize) + + oldTermState, err := term.SaveState(os.Stdin.Fd()) + if err != nil { + // allow caller to not have to do any cleaning up if we error here + cancel() + return nil, nil, errors.Wrapf(err, "unable to save terminal state") + } + + logrus.SetFormatter(&RawTtyFormatter{}) + if _, err := term.SetRawTerminal(os.Stdin.Fd()); err != nil { + return cancel, nil, err + } + + return cancel, oldTermState, nil +} diff --git a/pkg/domain/infra/abi/terminal/terminal_linux.go b/pkg/domain/infra/abi/terminal/terminal_linux.go new file mode 100644 index 000000000..664205df1 --- /dev/null +++ b/pkg/domain/infra/abi/terminal/terminal_linux.go @@ -0,0 +1,123 @@ +// +build ABISupport + +package terminal + +import ( + "bufio" + "context" + "fmt" + "os" + + "github.com/containers/libpod/libpod" + "github.com/containers/libpod/libpod/define" + "github.com/pkg/errors" + "github.com/sirupsen/logrus" + "golang.org/x/crypto/ssh/terminal" + "k8s.io/client-go/tools/remotecommand" +) + +// ExecAttachCtr execs and attaches to a container +func ExecAttachCtr(ctx context.Context, ctr *libpod.Container, tty, privileged bool, env map[string]string, cmd []string, user, workDir string, streams *define.AttachStreams, preserveFDs uint, detachKeys string) (int, error) { + resize := make(chan remotecommand.TerminalSize) + haveTerminal := terminal.IsTerminal(int(os.Stdin.Fd())) + + // Check if we are attached to a terminal. If we are, generate resize + // events, and set the terminal to raw mode + if haveTerminal && tty { + cancel, oldTermState, err := handleTerminalAttach(ctx, resize) + if err != nil { + return -1, err + } + defer cancel() + defer func() { + if err := restoreTerminal(oldTermState); err != nil { + logrus.Errorf("unable to restore terminal: %q", err) + } + }() + } + + execConfig := new(libpod.ExecConfig) + execConfig.Command = cmd + execConfig.Terminal = tty + execConfig.Privileged = privileged + execConfig.Environment = env + execConfig.User = user + execConfig.WorkDir = workDir + execConfig.DetachKeys = &detachKeys + execConfig.PreserveFDs = preserveFDs + + return ctr.Exec(execConfig, streams, resize) +} + +// StartAttachCtr starts and (if required) attaches to a container +// if you change the signature of this function from os.File to io.Writer, it will trigger a downstream +// error. we may need to just lint disable this one. +func StartAttachCtr(ctx context.Context, ctr *libpod.Container, stdout, stderr, stdin *os.File, detachKeys string, sigProxy bool, startContainer bool, recursive bool) error { //nolint-interfacer + resize := make(chan remotecommand.TerminalSize) + + haveTerminal := terminal.IsTerminal(int(os.Stdin.Fd())) + + // Check if we are attached to a terminal. If we are, generate resize + // events, and set the terminal to raw mode + if haveTerminal && ctr.Spec().Process.Terminal { + cancel, oldTermState, err := handleTerminalAttach(ctx, resize) + if err != nil { + return err + } + defer func() { + if err := restoreTerminal(oldTermState); err != nil { + logrus.Errorf("unable to restore terminal: %q", err) + } + }() + defer cancel() + } + + streams := new(define.AttachStreams) + streams.OutputStream = stdout + streams.ErrorStream = stderr + streams.InputStream = bufio.NewReader(stdin) + streams.AttachOutput = true + streams.AttachError = true + streams.AttachInput = true + + if stdout == nil { + logrus.Debugf("Not attaching to stdout") + streams.AttachOutput = false + } + if stderr == nil { + logrus.Debugf("Not attaching to stderr") + streams.AttachError = false + } + if stdin == nil { + logrus.Debugf("Not attaching to stdin") + streams.AttachInput = false + } + + if !startContainer { + if sigProxy { + ProxySignals(ctr) + } + + return ctr.Attach(streams, detachKeys, resize) + } + + attachChan, err := ctr.StartAndAttach(ctx, streams, detachKeys, resize, recursive) + if err != nil { + return err + } + + if sigProxy { + ProxySignals(ctr) + } + + if stdout == nil && stderr == nil { + fmt.Printf("%s\n", ctr.ID()) + } + + err = <-attachChan + if err != nil { + return errors.Wrapf(err, "error attaching to container %s", ctr.ID()) + } + + return nil +} diff --git a/pkg/domain/infra/abi/volumes.go b/pkg/domain/infra/abi/volumes.go index 0cc20474e..bdae4359d 100644 --- a/pkg/domain/infra/abi/volumes.go +++ b/pkg/domain/infra/abi/volumes.go @@ -107,13 +107,26 @@ func (ic *ContainerEngine) VolumeInspect(ctx context.Context, namesOrIds []strin UID: v.UID(), GID: v.GID(), } - reports = append(reports, &entities.VolumeInspectReport{&config}) + reports = append(reports, &entities.VolumeInspectReport{VolumeConfigResponse: &config}) } return reports, nil } func (ic *ContainerEngine) VolumePrune(ctx context.Context, opts entities.VolumePruneOptions) ([]*entities.VolumePruneReport, error) { - return ic.Libpod.PruneVolumes(ctx) + var ( + reports []*entities.VolumePruneReport + ) + pruned, err := ic.Libpod.PruneVolumes(ctx) + if err != nil { + return nil, err + } + for k, v := range pruned { + reports = append(reports, &entities.VolumePruneReport{ + Err: v, + Id: k, + }) + } + return reports, nil } func (ic *ContainerEngine) VolumeList(ctx context.Context, opts entities.VolumeListOptions) ([]*entities.VolumeListReport, error) { @@ -140,7 +153,7 @@ func (ic *ContainerEngine) VolumeList(ctx context.Context, opts entities.VolumeL UID: v.UID(), GID: v.GID(), } - reports = append(reports, &entities.VolumeListReport{config}) + reports = append(reports, &entities.VolumeListReport{VolumeConfigResponse: config}) } return reports, nil } diff --git a/pkg/domain/infra/runtime_libpod.go b/pkg/domain/infra/runtime_libpod.go index 730ded2e0..d59759707 100644 --- a/pkg/domain/infra/runtime_libpod.go +++ b/pkg/domain/infra/runtime_libpod.go @@ -220,9 +220,6 @@ func getRuntime(ctx context.Context, fs *flag.FlagSet, opts *engineOpts) (*libpo if !opts.withFDS { options = append(options, libpod.WithEnableSDNotify()) } - if fs.Changed("config") { - return libpod.NewRuntimeFromConfig(ctx, opts.flags.Config, options...) - } return libpod.NewRuntime(ctx, options...) } diff --git a/pkg/domain/infra/tunnel/containers.go b/pkg/domain/infra/tunnel/containers.go index a8ecff41b..4101068ba 100644 --- a/pkg/domain/infra/tunnel/containers.go +++ b/pkg/domain/infra/tunnel/containers.go @@ -2,9 +2,16 @@ package tunnel import ( "context" + "io" + "os" + "github.com/containers/image/v5/docker/reference" + "github.com/containers/libpod/libpod/define" + "github.com/containers/libpod/pkg/api/handlers/libpod" "github.com/containers/libpod/pkg/bindings/containers" "github.com/containers/libpod/pkg/domain/entities" + "github.com/containers/libpod/pkg/specgen" + "github.com/pkg/errors" ) func (ic *ContainerEngine) ContainerExists(ctx context.Context, nameOrId string) (*entities.BoolReport, error) { @@ -138,3 +145,175 @@ func (ic *ContainerEngine) ContainerRm(ctx context.Context, namesOrIds []string, } return reports, nil } + +func (ic *ContainerEngine) ContainerInspect(ctx context.Context, namesOrIds []string, options entities.InspectOptions) ([]*entities.ContainerInspectReport, error) { + var ( + reports []*entities.ContainerInspectReport + ) + ctrs, err := getContainersByContext(ic.ClientCxt, false, namesOrIds) + if err != nil { + return nil, err + } + for _, con := range ctrs { + data, err := containers.Inspect(ic.ClientCxt, con.ID, &options.Size) + if err != nil { + return nil, err + } + reports = append(reports, &entities.ContainerInspectReport{InspectContainerData: data}) + } + return reports, nil +} + +func (ic *ContainerEngine) ContainerTop(ctx context.Context, options entities.TopOptions) (*entities.StringSliceReport, error) { + switch { + case options.Latest: + return nil, errors.New("latest is not supported") + case options.NameOrID == "": + return nil, errors.New("NameOrID must be specified") + } + + topOutput, err := containers.Top(ic.ClientCxt, options.NameOrID, options.Descriptors) + if err != nil { + return nil, err + } + return &entities.StringSliceReport{Value: topOutput}, nil +} + +func (ic *ContainerEngine) ContainerCommit(ctx context.Context, nameOrId string, options entities.CommitOptions) (*entities.CommitReport, error) { + var ( + repo string + tag string = "latest" + ) + if len(options.ImageName) > 0 { + ref, err := reference.Parse(options.ImageName) + if err != nil { + return nil, err + } + if t, ok := ref.(reference.Tagged); ok { + tag = t.Tag() + } + if r, ok := ref.(reference.Named); ok { + repo = r.Name() + } + if len(repo) < 1 { + return nil, errors.Errorf("invalid image name %q", options.ImageName) + } + } + commitOpts := containers.CommitOptions{ + Author: &options.Author, + Changes: options.Changes, + Comment: &options.Message, + Format: &options.Format, + Pause: &options.Pause, + Repo: &repo, + Tag: &tag, + } + response, err := containers.Commit(ic.ClientCxt, nameOrId, commitOpts) + if err != nil { + return nil, err + } + return &entities.CommitReport{Id: response.ID}, nil +} + +func (ic *ContainerEngine) ContainerExport(ctx context.Context, nameOrId string, options entities.ContainerExportOptions) error { + var ( + err error + w io.Writer + ) + if len(options.Output) > 0 { + w, err = os.Create(options.Output) + if err != nil { + return err + } + } + return containers.Export(ic.ClientCxt, nameOrId, w) +} + +func (ic *ContainerEngine) ContainerCheckpoint(ctx context.Context, namesOrIds []string, options entities.CheckpointOptions) ([]*entities.CheckpointReport, error) { + var ( + reports []*entities.CheckpointReport + err error + ctrs []libpod.ListContainer + ) + + if options.All { + allCtrs, err := getContainersByContext(ic.ClientCxt, true, []string{}) + if err != nil { + return nil, err + } + // narrow the list to running only + for _, c := range allCtrs { + if c.State == define.ContainerStateRunning.String() { + ctrs = append(ctrs, c) + } + } + + } else { + ctrs, err = getContainersByContext(ic.ClientCxt, false, namesOrIds) + if err != nil { + return nil, err + } + } + for _, c := range ctrs { + report, err := containers.Checkpoint(ic.ClientCxt, c.ID, &options.Keep, &options.LeaveRuninng, &options.TCPEstablished, &options.IgnoreRootFS, &options.Export) + if err != nil { + reports = append(reports, &entities.CheckpointReport{Id: c.ID, Err: err}) + } + reports = append(reports, report) + } + return reports, nil +} + +func (ic *ContainerEngine) ContainerRestore(ctx context.Context, namesOrIds []string, options entities.RestoreOptions) ([]*entities.RestoreReport, error) { + var ( + reports []*entities.RestoreReport + err error + ctrs []libpod.ListContainer + ) + if options.All { + allCtrs, err := getContainersByContext(ic.ClientCxt, true, []string{}) + if err != nil { + return nil, err + } + // narrow the list to exited only + for _, c := range allCtrs { + if c.State == define.ContainerStateExited.String() { + ctrs = append(ctrs, c) + } + } + + } else { + ctrs, err = getContainersByContext(ic.ClientCxt, false, namesOrIds) + if err != nil { + return nil, err + } + } + for _, c := range ctrs { + report, err := containers.Restore(ic.ClientCxt, c.ID, &options.Keep, &options.TCPEstablished, &options.IgnoreRootFS, &options.IgnoreStaticIP, &options.IgnoreStaticMAC, &options.Name, &options.Import) + if err != nil { + reports = append(reports, &entities.RestoreReport{Id: c.ID, Err: err}) + } + reports = append(reports, report) + } + return reports, nil +} + +func (ic *ContainerEngine) ContainerCreate(ctx context.Context, s *specgen.SpecGenerator) (*entities.ContainerCreateReport, error) { + response, err := containers.CreateWithSpec(ic.ClientCxt, s) + if err != nil { + return nil, err + } + return &entities.ContainerCreateReport{Id: response.ID}, nil +} + +func (ic *ContainerEngine) ContainerAttach(ctx context.Context, nameOrId string, options entities.AttachOptions) error { + return errors.New("not implemented") +} + +func (ic *ContainerEngine) ContainerExec(ctx context.Context, nameOrId string, options entities.ExecOptions) (int, error) { + return 125, errors.New("not implemented") +} + +func (ic *ContainerEngine) ContainerStart(ctx context.Context, namesOrIds []string, options entities.ContainerStartOptions) ([]*entities.ContainerStartReport, error) { + return nil, errors.New("not implemented") +} diff --git a/pkg/domain/infra/tunnel/healthcheck.go b/pkg/domain/infra/tunnel/healthcheck.go new file mode 100644 index 000000000..e589489b3 --- /dev/null +++ b/pkg/domain/infra/tunnel/healthcheck.go @@ -0,0 +1,13 @@ +package tunnel + +import ( + "context" + + "github.com/containers/libpod/libpod/define" + "github.com/containers/libpod/pkg/bindings/containers" + "github.com/containers/libpod/pkg/domain/entities" +) + +func (ic *ContainerEngine) HealthCheckRun(ctx context.Context, nameOrId string, options entities.HealthCheckOptions) (*define.HealthCheckResults, error) { + return containers.RunHealthCheck(ic.ClientCxt, nameOrId) +} diff --git a/pkg/domain/infra/tunnel/helpers.go b/pkg/domain/infra/tunnel/helpers.go index 11fca5278..f9183c955 100644 --- a/pkg/domain/infra/tunnel/helpers.go +++ b/pkg/domain/infra/tunnel/helpers.go @@ -4,9 +4,12 @@ import ( "context" "strings" + "github.com/containers/libpod/libpod/define" "github.com/containers/libpod/pkg/api/handlers/libpod" "github.com/containers/libpod/pkg/bindings" "github.com/containers/libpod/pkg/bindings/containers" + "github.com/containers/libpod/pkg/bindings/pods" + "github.com/containers/libpod/pkg/domain/entities" "github.com/containers/libpod/pkg/util" "github.com/pkg/errors" ) @@ -40,3 +43,34 @@ func getContainersByContext(contextWithConnection context.Context, all bool, nam } return cons, nil } + +func getPodsByContext(contextWithConnection context.Context, all bool, namesOrIds []string) ([]*entities.ListPodsReport, error) { + var ( + sPods []*entities.ListPodsReport + ) + if all && len(namesOrIds) > 0 { + return nil, errors.New("cannot lookup specific pods and all") + } + + fPods, err := pods.List(contextWithConnection, nil) + if err != nil { + return nil, err + } + if all { + return fPods, nil + } + for _, nameOrId := range namesOrIds { + var found bool + for _, f := range fPods { + if f.Name == nameOrId || strings.HasPrefix(f.Id, nameOrId) { + sPods = append(sPods, f) + found = true + break + } + } + if !found { + return nil, errors.Wrapf(define.ErrNoSuchPod, "unable to find pod %q", nameOrId) + } + } + return sPods, nil +} diff --git a/pkg/domain/infra/tunnel/images.go b/pkg/domain/infra/tunnel/images.go index 60df40498..516914a68 100644 --- a/pkg/domain/infra/tunnel/images.go +++ b/pkg/domain/infra/tunnel/images.go @@ -2,34 +2,41 @@ package tunnel import ( "context" - "net/url" + "io/ioutil" + "os" + "github.com/containers/image/v5/docker/reference" images "github.com/containers/libpod/pkg/bindings/images" "github.com/containers/libpod/pkg/domain/entities" "github.com/containers/libpod/pkg/domain/utils" + utils2 "github.com/containers/libpod/utils" + "github.com/pkg/errors" ) -func (ir *ImageEngine) Delete(ctx context.Context, nameOrId string, opts entities.ImageDeleteOptions) (*entities.ImageDeleteReport, error) { - results, err := images.Remove(ir.ClientCxt, nameOrId, &opts.Force) - if err != nil { - return nil, err - } +func (ir *ImageEngine) Exists(_ context.Context, nameOrId string) (*entities.BoolReport, error) { + found, err := images.Exists(ir.ClientCxt, nameOrId) + return &entities.BoolReport{Value: found}, err +} - report := entities.ImageDeleteReport{ - Untagged: nil, - Deleted: "", - } +func (ir *ImageEngine) Delete(ctx context.Context, nameOrId []string, opts entities.ImageDeleteOptions) (*entities.ImageDeleteReport, error) { + report := entities.ImageDeleteReport{} - for _, e := range results { - if a, ok := e["Deleted"]; ok { - report.Deleted = a + for _, id := range nameOrId { + results, err := images.Remove(ir.ClientCxt, id, &opts.Force) + if err != nil { + return nil, err } + for _, e := range results { + if a, ok := e["Deleted"]; ok { + report.Deleted = append(report.Deleted, a) + } - if a, ok := e["Untagged"]; ok { - report.Untagged = append(report.Untagged, a) + if a, ok := e["Untagged"]; ok { + report.Untagged = append(report.Untagged, a) + } } } - return &report, err + return &report, nil } func (ir *ImageEngine) List(ctx context.Context, opts entities.ImageListOptions) ([]*entities.ImageSummary, error) { @@ -69,12 +76,168 @@ func (ir *ImageEngine) History(ctx context.Context, nameOrId string, opts entiti } func (ir *ImageEngine) Prune(ctx context.Context, opts entities.ImagePruneOptions) (*entities.ImagePruneReport, error) { - results, err := images.Prune(ir.ClientCxt, url.Values{}) + results, err := images.Prune(ir.ClientCxt, &opts.All, opts.Filters) + if err != nil { + return nil, err + } + + report := entities.ImagePruneReport{ + Report: entities.Report{ + Id: results, + Err: nil, + }, + Size: 0, + } + return &report, nil +} + +func (ir *ImageEngine) Pull(ctx context.Context, rawImage string, options entities.ImagePullOptions) (*entities.ImagePullReport, error) { + pulledImages, err := images.Pull(ir.ClientCxt, rawImage, options) if err != nil { return nil, err } + return &entities.ImagePullReport{Images: pulledImages}, nil +} + +func (ir *ImageEngine) Tag(ctx context.Context, nameOrId string, tags []string, options entities.ImageTagOptions) error { + for _, newTag := range tags { + var ( + tag, repo string + ) + ref, err := reference.Parse(newTag) + if err != nil { + return err + } + if t, ok := ref.(reference.Tagged); ok { + tag = t.Tag() + } + if r, ok := ref.(reference.Named); ok { + repo = r.Name() + } + if len(repo) < 1 { + return errors.Errorf("invalid image name %q", nameOrId) + } + if err := images.Tag(ir.ClientCxt, nameOrId, tag, repo); err != nil { + return err + } + } + return nil +} + +func (ir *ImageEngine) Untag(ctx context.Context, nameOrId string, tags []string, options entities.ImageUntagOptions) error { + for _, newTag := range tags { + var ( + tag, repo string + ) + ref, err := reference.Parse(newTag) + if err != nil { + return err + } + if t, ok := ref.(reference.Tagged); ok { + tag = t.Tag() + } + if r, ok := ref.(reference.Named); ok { + repo = r.Name() + } + if len(repo) < 1 { + return errors.Errorf("invalid image name %q", nameOrId) + } + if err := images.Untag(ir.ClientCxt, nameOrId, tag, repo); err != nil { + return err + } + } + return nil +} - report := entities.ImagePruneReport{} - copy(report.Report.Id, results) +func (ir *ImageEngine) Inspect(_ context.Context, names []string, opts entities.InspectOptions) (*entities.ImageInspectReport, error) { + report := entities.ImageInspectReport{} + for _, id := range names { + r, err := images.GetImage(ir.ClientCxt, id, &opts.Size) + if err != nil { + report.Errors[id] = err + } + report.Images = append(report.Images, r) + } return &report, nil } + +func (ir *ImageEngine) Load(ctx context.Context, opts entities.ImageLoadOptions) (*entities.ImageLoadReport, error) { + f, err := os.Open(opts.Input) + if err != nil { + return nil, err + } + defer f.Close() + return images.Load(ir.ClientCxt, f, &opts.Name) +} + +func (ir *ImageEngine) Import(ctx context.Context, opts entities.ImageImportOptions) (*entities.ImageImportReport, error) { + var ( + err error + sourceURL *string + f *os.File + ) + if opts.SourceIsURL { + sourceURL = &opts.Source + } else { + f, err = os.Open(opts.Source) + if err != nil { + return nil, err + } + } + return images.Import(ir.ClientCxt, opts.Changes, &opts.Message, &opts.Reference, sourceURL, f) +} + +func (ir *ImageEngine) Push(ctx context.Context, source string, destination string, options entities.ImagePushOptions) error { + return images.Push(ir.ClientCxt, source, destination, options) +} + +func (ir *ImageEngine) Save(ctx context.Context, nameOrId string, tags []string, options entities.ImageSaveOptions) error { + var ( + f *os.File + err error + ) + + switch options.Format { + case "oci-dir", "docker-dir": + f, err = ioutil.TempFile("", "podman_save") + if err == nil { + defer func() { _ = os.Remove(f.Name()) }() + } + default: + f, err = os.Create(options.Output) + } + if err != nil { + return err + } + + exErr := images.Export(ir.ClientCxt, nameOrId, f, &options.Format, &options.Compress) + if err := f.Close(); err != nil { + return err + } + if exErr != nil { + return exErr + } + + if options.Format != "oci-dir" && options.Format != "docker-dir" { + return nil + } + + f, err = os.Open(f.Name()) + if err != nil { + return err + } + info, err := os.Stat(options.Output) + switch { + case err == nil: + if info.Mode().IsRegular() { + return errors.Errorf("%q already exists as a regular file", options.Output) + } + case os.IsNotExist(err): + if err := os.Mkdir(options.Output, 0755); err != nil { + return err + } + default: + return err + } + return utils2.UntarToFileSystem(options.Output, f, nil) +} diff --git a/pkg/domain/infra/tunnel/pods.go b/pkg/domain/infra/tunnel/pods.go index 500069d51..dad77284f 100644 --- a/pkg/domain/infra/tunnel/pods.go +++ b/pkg/domain/infra/tunnel/pods.go @@ -5,9 +5,205 @@ import ( "github.com/containers/libpod/pkg/bindings/pods" "github.com/containers/libpod/pkg/domain/entities" + "github.com/containers/libpod/pkg/specgen" + "github.com/pkg/errors" ) func (ic *ContainerEngine) PodExists(ctx context.Context, nameOrId string) (*entities.BoolReport, error) { exists, err := pods.Exists(ic.ClientCxt, nameOrId) return &entities.BoolReport{Value: exists}, err } + +func (ic *ContainerEngine) PodKill(ctx context.Context, namesOrIds []string, options entities.PodKillOptions) ([]*entities.PodKillReport, error) { + var ( + reports []*entities.PodKillReport + ) + foundPods, err := getPodsByContext(ic.ClientCxt, options.All, namesOrIds) + if err != nil { + return nil, err + } + for _, p := range foundPods { + response, err := pods.Kill(ic.ClientCxt, p.Id, &options.Signal) + if err != nil { + report := entities.PodKillReport{ + Errs: []error{err}, + Id: p.Id, + } + reports = append(reports, &report) + continue + } + reports = append(reports, response) + } + return reports, nil +} + +func (ic *ContainerEngine) PodPause(ctx context.Context, namesOrIds []string, options entities.PodPauseOptions) ([]*entities.PodPauseReport, error) { + var ( + reports []*entities.PodPauseReport + ) + foundPods, err := getPodsByContext(ic.ClientCxt, options.All, namesOrIds) + if err != nil { + return nil, err + } + for _, p := range foundPods { + response, err := pods.Pause(ic.ClientCxt, p.Id) + if err != nil { + report := entities.PodPauseReport{ + Errs: []error{err}, + Id: p.Id, + } + reports = append(reports, &report) + continue + } + reports = append(reports, response) + } + return reports, nil +} + +func (ic *ContainerEngine) PodUnpause(ctx context.Context, namesOrIds []string, options entities.PodunpauseOptions) ([]*entities.PodUnpauseReport, error) { + var ( + reports []*entities.PodUnpauseReport + ) + foundPods, err := getPodsByContext(ic.ClientCxt, options.All, namesOrIds) + if err != nil { + return nil, err + } + for _, p := range foundPods { + response, err := pods.Unpause(ic.ClientCxt, p.Id) + if err != nil { + report := entities.PodUnpauseReport{ + Errs: []error{err}, + Id: p.Id, + } + reports = append(reports, &report) + continue + } + reports = append(reports, response) + } + return reports, nil +} + +func (ic *ContainerEngine) PodStop(ctx context.Context, namesOrIds []string, options entities.PodStopOptions) ([]*entities.PodStopReport, error) { + var ( + reports []*entities.PodStopReport + timeout int = -1 + ) + foundPods, err := getPodsByContext(ic.ClientCxt, options.All, namesOrIds) + if err != nil { + return nil, err + } + if options.Timeout != -1 { + timeout = options.Timeout + } + for _, p := range foundPods { + response, err := pods.Stop(ic.ClientCxt, p.Id, &timeout) + if err != nil { + report := entities.PodStopReport{ + Errs: []error{err}, + Id: p.Id, + } + reports = append(reports, &report) + continue + } + reports = append(reports, response) + } + return reports, nil +} + +func (ic *ContainerEngine) PodRestart(ctx context.Context, namesOrIds []string, options entities.PodRestartOptions) ([]*entities.PodRestartReport, error) { + var reports []*entities.PodRestartReport + foundPods, err := getPodsByContext(ic.ClientCxt, options.All, namesOrIds) + if err != nil { + return nil, err + } + for _, p := range foundPods { + response, err := pods.Restart(ic.ClientCxt, p.Id) + if err != nil { + report := entities.PodRestartReport{ + Errs: []error{err}, + Id: p.Id, + } + reports = append(reports, &report) + continue + } + reports = append(reports, response) + } + return reports, nil +} + +func (ic *ContainerEngine) PodStart(ctx context.Context, namesOrIds []string, options entities.PodStartOptions) ([]*entities.PodStartReport, error) { + var reports []*entities.PodStartReport + foundPods, err := getPodsByContext(ic.ClientCxt, options.All, namesOrIds) + if err != nil { + return nil, err + } + for _, p := range foundPods { + response, err := pods.Start(ic.ClientCxt, p.Id) + if err != nil { + report := entities.PodStartReport{ + Errs: []error{err}, + Id: p.Id, + } + reports = append(reports, &report) + continue + } + reports = append(reports, response) + } + return reports, nil +} + +func (ic *ContainerEngine) PodRm(ctx context.Context, namesOrIds []string, options entities.PodRmOptions) ([]*entities.PodRmReport, error) { + var reports []*entities.PodRmReport + foundPods, err := getPodsByContext(ic.ClientCxt, options.All, namesOrIds) + if err != nil { + return nil, err + } + for _, p := range foundPods { + response, err := pods.Remove(ic.ClientCxt, p.Id, &options.Force) + if err != nil { + report := entities.PodRmReport{ + Err: err, + Id: p.Id, + } + reports = append(reports, &report) + continue + } + reports = append(reports, response) + } + return reports, nil +} + +func (ic *ContainerEngine) PodCreate(ctx context.Context, opts entities.PodCreateOptions) (*entities.PodCreateReport, error) { + podSpec := specgen.NewPodSpecGenerator() + opts.ToPodSpecGen(podSpec) + return pods.CreatePodFromSpec(ic.ClientCxt, podSpec) +} + +func (ic *ContainerEngine) PodTop(ctx context.Context, options entities.PodTopOptions) (*entities.StringSliceReport, error) { + switch { + case options.Latest: + return nil, errors.New("latest is not supported") + case options.NameOrID == "": + return nil, errors.New("NameOrID must be specified") + } + + topOutput, err := pods.Top(ic.ClientCxt, options.NameOrID, options.Descriptors) + if err != nil { + return nil, err + } + return &entities.StringSliceReport{Value: topOutput}, nil +} + +func (ic *ContainerEngine) PodPs(ctx context.Context, options entities.PodPSOptions) ([]*entities.ListPodsReport, error) { + return pods.List(ic.ClientCxt, options.Filters) +} + +func (ic *ContainerEngine) PodInspect(ctx context.Context, options entities.PodInspectOptions) (*entities.PodInspectReport, error) { + switch { + case options.Latest: + return nil, errors.New("latest is not supported") + case options.NameOrID == "": + return nil, errors.New("NameOrID must be specified") + } + return pods.Inspect(ic.ClientCxt, options.NameOrID) +} diff --git a/pkg/domain/infra/tunnel/system.go b/pkg/domain/infra/tunnel/system.go new file mode 100644 index 000000000..5bafef1fe --- /dev/null +++ b/pkg/domain/infra/tunnel/system.go @@ -0,0 +1 @@ +package tunnel diff --git a/pkg/namespaces/namespaces.go b/pkg/namespaces/namespaces.go index 78b55bb2a..14453e7f4 100644 --- a/pkg/namespaces/namespaces.go +++ b/pkg/namespaces/namespaces.go @@ -101,7 +101,7 @@ func (n UsernsMode) IsPrivate() bool { func (n UsernsMode) Valid() bool { parts := strings.Split(string(n), ":") switch mode := parts[0]; mode { - case "", hostType, "keep-id", nsType: + case "", privateType, hostType, "keep-id", nsType: case containerType: if len(parts) != 2 || parts[1] == "" { return false @@ -173,7 +173,7 @@ func (n UTSMode) Container() string { func (n UTSMode) Valid() bool { parts := strings.Split(string(n), ":") switch mode := parts[0]; mode { - case "", hostType: + case "", privateType, hostType: case containerType: if len(parts) != 2 || parts[1] == "" { return false @@ -255,7 +255,7 @@ func (n PidMode) IsContainer() bool { func (n PidMode) Valid() bool { parts := strings.Split(string(n), ":") switch mode := parts[0]; mode { - case "", hostType: + case "", privateType, hostType: case containerType: if len(parts) != 2 || parts[1] == "" { return false diff --git a/pkg/spec/createconfig.go b/pkg/spec/createconfig.go index 12dfed8c3..daa997104 100644 --- a/pkg/spec/createconfig.go +++ b/pkg/spec/createconfig.go @@ -196,6 +196,7 @@ func (c *CreateConfig) createExitCommand(runtime *libpod.Runtime) ([]string, err if err != nil { return nil, err } + storageConfig := runtime.StorageConfig() // We need a cleanup process for containers in the current model. // But we can't assume that the caller is Podman - it could be another @@ -208,23 +209,23 @@ func (c *CreateConfig) createExitCommand(runtime *libpod.Runtime) ([]string, err } command := []string{cmd, - "--root", config.StorageConfig.GraphRoot, - "--runroot", config.StorageConfig.RunRoot, + "--root", storageConfig.GraphRoot, + "--runroot", storageConfig.RunRoot, "--log-level", logrus.GetLevel().String(), - "--cgroup-manager", config.CgroupManager, - "--tmpdir", config.TmpDir, + "--cgroup-manager", config.Engine.CgroupManager, + "--tmpdir", config.Engine.TmpDir, } - if config.OCIRuntime != "" { - command = append(command, []string{"--runtime", config.OCIRuntime}...) + if config.Engine.OCIRuntime != "" { + command = append(command, []string{"--runtime", config.Engine.OCIRuntime}...) } - if config.StorageConfig.GraphDriverName != "" { - command = append(command, []string{"--storage-driver", config.StorageConfig.GraphDriverName}...) + if storageConfig.GraphDriverName != "" { + command = append(command, []string{"--storage-driver", storageConfig.GraphDriverName}...) } - for _, opt := range config.StorageConfig.GraphDriverOptions { + for _, opt := range storageConfig.GraphDriverOptions { command = append(command, []string{"--storage-opt", opt}...) } - if config.EventsLogger != "" { - command = append(command, []string{"--events-backend", config.EventsLogger}...) + if config.Engine.EventsLogger != "" { + command = append(command, []string{"--events-backend", config.Engine.EventsLogger}...) } if c.Syslog { diff --git a/pkg/spec/spec.go b/pkg/spec/spec.go index 8f0630b85..5de07fc28 100644 --- a/pkg/spec/spec.go +++ b/pkg/spec/spec.go @@ -4,9 +4,8 @@ import ( "strings" "github.com/containers/common/pkg/capabilities" + cconfig "github.com/containers/common/pkg/config" "github.com/containers/libpod/libpod" - libpodconfig "github.com/containers/libpod/libpod/config" - "github.com/containers/libpod/libpod/define" "github.com/containers/libpod/pkg/cgroups" "github.com/containers/libpod/pkg/env" "github.com/containers/libpod/pkg/rootless" @@ -81,6 +80,37 @@ func (config *CreateConfig) createConfigToOCISpec(runtime *libpod.Runtime, userM g.AddLinuxMaskedPaths("/sys/kernel") } } + var runtimeConfig *cconfig.Config + + if runtime != nil { + runtimeConfig, err = runtime.GetConfig() + if err != nil { + return nil, err + } + g.Config.Process.Capabilities.Bounding = runtimeConfig.Containers.DefaultCapabilities + sysctls, err := util.ValidateSysctls(runtimeConfig.Containers.DefaultSysctls) + if err != nil { + return nil, err + } + + for name, val := range config.Security.Sysctl { + sysctls[name] = val + } + config.Security.Sysctl = sysctls + if !util.StringInSlice("host", config.Resources.Ulimit) { + config.Resources.Ulimit = append(runtimeConfig.Containers.DefaultUlimits, config.Resources.Ulimit...) + } + if config.Resources.PidsLimit < 0 && !config.cgroupDisabled() { + config.Resources.PidsLimit = runtimeConfig.Containers.PidsLimit + } + + } else { + g.Config.Process.Capabilities.Bounding = cconfig.DefaultCapabilities + if config.Resources.PidsLimit < 0 && !config.cgroupDisabled() { + config.Resources.PidsLimit = cconfig.DefaultPidsLimit + } + } + gid5Available := true if isRootless { nGids, err := GetAvailableGids() @@ -242,16 +272,6 @@ func (config *CreateConfig) createConfigToOCISpec(runtime *libpod.Runtime, userM } } - // SECURITY OPTS - var runtimeConfig *libpodconfig.Config - - if runtime != nil { - runtimeConfig, err = runtime.GetConfig() - if err != nil { - return nil, err - } - } - g.SetProcessNoNewPrivileges(config.Security.NoNewPrivs) if !config.Security.Privileged { @@ -261,7 +281,7 @@ func (config *CreateConfig) createConfigToOCISpec(runtime *libpod.Runtime, userM // Unless already set via the CLI, check if we need to disable process // labels or set the defaults. if len(config.Security.LabelOpts) == 0 && runtimeConfig != nil { - if !runtimeConfig.EnableLabeling { + if !runtimeConfig.Containers.EnableLabeling { // Disabled in the config. config.Security.LabelOpts = append(config.Security.LabelOpts, "disable") } else if err := config.Security.SetLabelOpts(runtime, &config.Pid, &config.Ipc); err != nil { @@ -284,7 +304,7 @@ func (config *CreateConfig) createConfigToOCISpec(runtime *libpod.Runtime, userM if err != nil { return nil, err } - if (!cgroup2 || (runtimeConfig != nil && runtimeConfig.CgroupManager != define.SystemdCgroupsManager)) && config.Resources.PidsLimit == sysinfo.GetDefaultPidsLimit() { + if (!cgroup2 || (runtimeConfig != nil && runtimeConfig.Engine.CgroupManager != cconfig.SystemdCgroupsManager)) && config.Resources.PidsLimit == sysinfo.GetDefaultPidsLimit() { setPidLimit = false } } @@ -296,7 +316,17 @@ func (config *CreateConfig) createConfigToOCISpec(runtime *libpod.Runtime, userM // Make sure to always set the default variables unless overridden in the // config. - config.Env = env.Join(env.DefaultEnvVariables, config.Env) + var defaultEnv map[string]string + if runtimeConfig == nil { + defaultEnv = env.DefaultEnvVariables + } else { + defaultEnv, err = env.ParseSlice(runtimeConfig.Containers.Env) + if err != nil { + return nil, errors.Wrap(err, "Env fields in containers.conf failed ot parse") + } + defaultEnv = env.Join(env.DefaultEnvVariables, defaultEnv) + } + config.Env = env.Join(defaultEnv, config.Env) for name, val := range config.Env { g.AddProcessEnv(name, val) } @@ -351,11 +381,9 @@ func (config *CreateConfig) createConfigToOCISpec(runtime *libpod.Runtime, userM // BIND MOUNTS configSpec.Mounts = SupercedeUserMounts(userMounts, configSpec.Mounts) // Process mounts to ensure correct options - finalMounts, err := InitFSMounts(configSpec.Mounts) - if err != nil { + if err := InitFSMounts(configSpec.Mounts); err != nil { return nil, err } - configSpec.Mounts = finalMounts // BLOCK IO blkio, err := config.CreateBlockIO() @@ -376,7 +404,7 @@ func (config *CreateConfig) createConfigToOCISpec(runtime *libpod.Runtime, userM configSpec.Linux.Resources = &spec.LinuxResources{} } - canUseResources := cgroup2 && runtimeConfig != nil && (runtimeConfig.CgroupManager == define.SystemdCgroupsManager) + canUseResources := cgroup2 && runtimeConfig != nil && (runtimeConfig.Engine.CgroupManager == cconfig.SystemdCgroupsManager) if addedResources && !canUseResources { return nil, errors.New("invalid configuration, cannot specify resource limits without cgroups v2 and --cgroup-manager=systemd") @@ -433,6 +461,10 @@ func (config *CreateConfig) createConfigToOCISpec(runtime *libpod.Runtime, userM return configSpec, nil } +func (config *CreateConfig) cgroupDisabled() bool { + return config.Cgroup.Cgroups == "disabled" +} + func BlockAccessToKernelFilesystems(privileged, pidModeIsHost bool, g *generate.Generator) { if !privileged { for _, mp := range []string{ diff --git a/pkg/spec/storage.go b/pkg/spec/storage.go index c365701de..68a84d638 100644 --- a/pkg/spec/storage.go +++ b/pkg/spec/storage.go @@ -10,7 +10,6 @@ import ( "github.com/containers/buildah/pkg/parse" "github.com/containers/libpod/libpod" "github.com/containers/libpod/pkg/util" - pmount "github.com/containers/storage/pkg/mount" spec "github.com/opencontainers/runtime-spec/specs-go" "github.com/pkg/errors" "github.com/sirupsen/logrus" @@ -124,7 +123,7 @@ func (config *CreateConfig) parseVolumes(runtime *libpod.Runtime) ([]spec.Mount, if err != nil { return nil, nil, err } - initPath = rtc.InitPath + initPath = rtc.Engine.InitPath } initMount, err := config.addContainerInitBinary(initPath) if err != nil { @@ -855,75 +854,22 @@ func SupercedeUserMounts(mounts []spec.Mount, configMount []spec.Mount) []spec.M } // Ensure mount options on all mounts are correct -func InitFSMounts(inputMounts []spec.Mount) ([]spec.Mount, error) { - // We need to look up mounts so we can figure out the proper mount flags - // to apply. - systemMounts, err := pmount.GetMounts() - if err != nil { - return nil, errors.Wrapf(err, "error retrieving system mounts to look up mount options") - } - - // TODO: We probably don't need to re-build the mounts array - var mounts []spec.Mount - for _, m := range inputMounts { - if m.Type == TypeBind { - baseMnt, err := findMount(m.Destination, systemMounts) +func InitFSMounts(mounts []spec.Mount) error { + for i, m := range mounts { + switch { + case m.Type == TypeBind: + opts, err := util.ProcessOptions(m.Options, false, m.Source) if err != nil { - return nil, errors.Wrapf(err, "error looking up mountpoint for mount %s", m.Destination) - } - var noexec, nosuid, nodev bool - for _, baseOpt := range strings.Split(baseMnt.Opts, ",") { - switch baseOpt { - case "noexec": - noexec = true - case "nosuid": - nosuid = true - case "nodev": - nodev = true - } + return err } - - defaultMountOpts := new(util.DefaultMountOptions) - defaultMountOpts.Noexec = noexec - defaultMountOpts.Nosuid = nosuid - defaultMountOpts.Nodev = nodev - - opts, err := util.ProcessOptions(m.Options, false, defaultMountOpts) + mounts[i].Options = opts + case m.Type == TypeTmpfs && filepath.Clean(m.Destination) != "/dev": + opts, err := util.ProcessOptions(m.Options, true, "") if err != nil { - return nil, err + return err } - m.Options = opts - } - if m.Type == TypeTmpfs && filepath.Clean(m.Destination) != "/dev" { - opts, err := util.ProcessOptions(m.Options, true, nil) - if err != nil { - return nil, err - } - m.Options = opts - } - - mounts = append(mounts, m) - } - return mounts, nil -} - -// TODO: We could make this a bit faster by building a tree of the mountpoints -// and traversing it to identify the correct mount. -func findMount(target string, mounts []*pmount.Info) (*pmount.Info, error) { - var err error - target, err = filepath.Abs(target) - if err != nil { - return nil, errors.Wrapf(err, "cannot resolve %s", target) - } - var bestSoFar *pmount.Info - for _, i := range mounts { - if bestSoFar != nil && len(bestSoFar.Mountpoint) > len(i.Mountpoint) { - // Won't be better than what we have already found - continue - } - if strings.HasPrefix(target, i.Mountpoint) { - bestSoFar = i + mounts[i].Options = opts } } - return bestSoFar, nil + return nil } diff --git a/pkg/specgen/config_linux.go b/pkg/specgen/config_linux.go new file mode 100644 index 000000000..82a371492 --- /dev/null +++ b/pkg/specgen/config_linux.go @@ -0,0 +1,93 @@ +package specgen + +//func createBlockIO() (*spec.LinuxBlockIO, error) { +// var ret *spec.LinuxBlockIO +// bio := &spec.LinuxBlockIO{} +// if c.Resources.BlkioWeight > 0 { +// ret = bio +// bio.Weight = &c.Resources.BlkioWeight +// } +// if len(c.Resources.BlkioWeightDevice) > 0 { +// var lwds []spec.LinuxWeightDevice +// ret = bio +// for _, i := range c.Resources.BlkioWeightDevice { +// wd, err := ValidateweightDevice(i) +// if err != nil { +// return ret, errors.Wrapf(err, "invalid values for blkio-weight-device") +// } +// wdStat, err := GetStatFromPath(wd.Path) +// if err != nil { +// return ret, errors.Wrapf(err, "error getting stat from path %q", wd.Path) +// } +// lwd := spec.LinuxWeightDevice{ +// Weight: &wd.Weight, +// } +// lwd.Major = int64(unix.Major(wdStat.Rdev)) +// lwd.Minor = int64(unix.Minor(wdStat.Rdev)) +// lwds = append(lwds, lwd) +// } +// bio.WeightDevice = lwds +// } +// if len(c.Resources.DeviceReadBps) > 0 { +// ret = bio +// readBps, err := makeThrottleArray(c.Resources.DeviceReadBps, bps) +// if err != nil { +// return ret, err +// } +// bio.ThrottleReadBpsDevice = readBps +// } +// if len(c.Resources.DeviceWriteBps) > 0 { +// ret = bio +// writeBpds, err := makeThrottleArray(c.Resources.DeviceWriteBps, bps) +// if err != nil { +// return ret, err +// } +// bio.ThrottleWriteBpsDevice = writeBpds +// } +// if len(c.Resources.DeviceReadIOps) > 0 { +// ret = bio +// readIOps, err := makeThrottleArray(c.Resources.DeviceReadIOps, iops) +// if err != nil { +// return ret, err +// } +// bio.ThrottleReadIOPSDevice = readIOps +// } +// if len(c.Resources.DeviceWriteIOps) > 0 { +// ret = bio +// writeIOps, err := makeThrottleArray(c.Resources.DeviceWriteIOps, iops) +// if err != nil { +// return ret, err +// } +// bio.ThrottleWriteIOPSDevice = writeIOps +// } +// return ret, nil +//} + +//func makeThrottleArray(throttleInput []string, rateType int) ([]spec.LinuxThrottleDevice, error) { +// var ( +// ltds []spec.LinuxThrottleDevice +// t *throttleDevice +// err error +// ) +// for _, i := range throttleInput { +// if rateType == bps { +// t, err = validateBpsDevice(i) +// } else { +// t, err = validateIOpsDevice(i) +// } +// if err != nil { +// return []spec.LinuxThrottleDevice{}, err +// } +// ltdStat, err := GetStatFromPath(t.path) +// if err != nil { +// return ltds, errors.Wrapf(err, "error getting stat from path %q", t.path) +// } +// ltd := spec.LinuxThrottleDevice{ +// Rate: t.rate, +// } +// ltd.Major = int64(unix.Major(ltdStat.Rdev)) +// ltd.Minor = int64(unix.Minor(ltdStat.Rdev)) +// ltds = append(ltds, ltd) +// } +// return ltds, nil +//} diff --git a/pkg/specgen/config_linux_cgo.go b/pkg/specgen/config_linux_cgo.go index 6f547a40d..ef6c6e951 100644 --- a/pkg/specgen/config_linux_cgo.go +++ b/pkg/specgen/config_linux_cgo.go @@ -17,7 +17,6 @@ import ( func (s *SpecGenerator) getSeccompConfig(configSpec *spec.Spec, img *image.Image) (*spec.LinuxSeccomp, error) { var seccompConfig *spec.LinuxSeccomp var err error - scp, err := seccomp.LookupPolicy(s.SeccompPolicy) if err != nil { return nil, err diff --git a/pkg/specgen/validate.go b/pkg/specgen/container_validate.go index dd5ca3a55..aad14ddcb 100644 --- a/pkg/specgen/validate.go +++ b/pkg/specgen/container_validate.go @@ -3,7 +3,7 @@ package specgen import ( "strings" - "github.com/containers/libpod/libpod" + "github.com/containers/libpod/pkg/rootless" "github.com/containers/libpod/pkg/util" "github.com/pkg/errors" ) @@ -14,7 +14,7 @@ var ( // SystemDValues describes the only values that SystemD can be SystemDValues = []string{"true", "false", "always"} // ImageVolumeModeValues describes the only values that ImageVolumeMode can be - ImageVolumeModeValues = []string{"ignore", "tmpfs", "anonymous"} + ImageVolumeModeValues = []string{"ignore", "tmpfs", "bind"} ) func exclusiveOptions(opt1, opt2 string) error { @@ -23,7 +23,7 @@ func exclusiveOptions(opt1, opt2 string) error { // Validate verifies that the given SpecGenerator is valid and satisfies required // input for creating a container. -func (s *SpecGenerator) validate(rt *libpod.Runtime) error { +func (s *SpecGenerator) Validate() error { // // ContainerBasicConfig @@ -138,9 +138,6 @@ func (s *SpecGenerator) validate(rt *libpod.Runtime) error { if err := s.IpcNS.validate(); err != nil { return err } - if err := validateNetNS(&s.NetNS); err != nil { - return err - } if err := s.PidNS.validate(); err != nil { return err } @@ -155,5 +152,16 @@ func (s *SpecGenerator) validate(rt *libpod.Runtime) error { if len(s.WorkDir) < 1 { s.WorkDir = "/" } + + // Set defaults if network info is not provided + if s.NetNS.NSMode == "" { + s.NetNS.NSMode = Bridge + if rootless.IsRootless() { + s.NetNS.NSMode = Slirp + } + } + if err := validateNetNS(&s.NetNS); err != nil { + return err + } return nil } diff --git a/pkg/specgen/generate/container.go b/pkg/specgen/generate/container.go new file mode 100644 index 000000000..78c77fec1 --- /dev/null +++ b/pkg/specgen/generate/container.go @@ -0,0 +1,168 @@ +package generate + +import ( + "context" + + "github.com/containers/libpod/libpod" + ann "github.com/containers/libpod/pkg/annotations" + envLib "github.com/containers/libpod/pkg/env" + "github.com/containers/libpod/pkg/signal" + "github.com/containers/libpod/pkg/specgen" + "github.com/pkg/errors" + "golang.org/x/sys/unix" +) + +func CompleteSpec(ctx context.Context, r *libpod.Runtime, s *specgen.SpecGenerator) error { + + newImage, err := r.ImageRuntime().NewFromLocal(s.Image) + if err != nil { + return err + } + + // Image stop signal + if s.StopSignal == nil && newImage.Config != nil { + sig, err := signal.ParseSignalNameOrNumber(newImage.Config.StopSignal) + if err != nil { + return err + } + s.StopSignal = &sig + } + // Image envs from the image if they don't exist + // already + if newImage.Config != nil && len(newImage.Config.Env) > 0 { + envs, err := envLib.ParseSlice(newImage.Config.Env) + if err != nil { + return err + } + for k, v := range envs { + if _, exists := s.Env[k]; !exists { + s.Env[v] = k + } + } + } + + // labels from the image that dont exist already + if config := newImage.Config; config != nil { + for k, v := range config.Labels { + if _, exists := s.Labels[k]; !exists { + s.Labels[k] = v + } + } + } + + // annotations + // in the event this container is in a pod, and the pod has an infra container + // we will want to configure it as a type "container" instead defaulting to + // the behavior of a "sandbox" container + // In Kata containers: + // - "sandbox" is the annotation that denotes the container should use its own + // VM, which is the default behavior + // - "container" denotes the container should join the VM of the SandboxID + // (the infra container) + s.Annotations = make(map[string]string) + if len(s.Pod) > 0 { + s.Annotations[ann.SandboxID] = s.Pod + s.Annotations[ann.ContainerType] = ann.ContainerTypeContainer + } + // + // Next, add annotations from the image + annotations, err := newImage.Annotations(ctx) + if err != nil { + return err + } + for k, v := range annotations { + annotations[k] = v + } + + // entrypoint + if config := newImage.Config; config != nil { + if len(s.Entrypoint) < 1 && len(config.Entrypoint) > 0 { + s.Entrypoint = config.Entrypoint + } + if len(s.Command) < 1 && len(config.Cmd) > 0 { + s.Command = config.Cmd + } + if len(s.Command) < 1 && len(s.Entrypoint) < 1 { + return errors.Errorf("No command provided or as CMD or ENTRYPOINT in this image") + } + // workdir + if len(s.WorkDir) < 1 && len(config.WorkingDir) > 1 { + s.WorkDir = config.WorkingDir + } + } + + if len(s.SeccompProfilePath) < 1 { + p, err := libpod.DefaultSeccompPath() + if err != nil { + return err + } + s.SeccompProfilePath = p + } + + if user := s.User; len(user) == 0 { + switch { + // TODO This should be enabled when namespaces actually work + //case usernsMode.IsKeepID(): + // user = fmt.Sprintf("%d:%d", rootless.GetRootlessUID(), rootless.GetRootlessGID()) + case newImage.Config == nil || (newImage.Config != nil && len(newImage.Config.User) == 0): + s.User = "0" + default: + s.User = newImage.Config.User + } + } + if err := finishThrottleDevices(s); err != nil { + return err + } + return nil +} + +// finishThrottleDevices takes the temporary representation of the throttle +// devices in the specgen and looks up the major and major minors. it then +// sets the throttle devices proper in the specgen +func finishThrottleDevices(s *specgen.SpecGenerator) error { + if bps := s.ThrottleReadBpsDevice; len(bps) > 0 { + for k, v := range bps { + statT := unix.Stat_t{} + if err := unix.Stat(k, &statT); err != nil { + return err + } + v.Major = (int64(unix.Major(statT.Rdev))) + v.Minor = (int64(unix.Minor(statT.Rdev))) + s.ResourceLimits.BlockIO.ThrottleReadBpsDevice = append(s.ResourceLimits.BlockIO.ThrottleReadBpsDevice, v) + } + } + if bps := s.ThrottleWriteBpsDevice; len(bps) > 0 { + for k, v := range bps { + statT := unix.Stat_t{} + if err := unix.Stat(k, &statT); err != nil { + return err + } + v.Major = (int64(unix.Major(statT.Rdev))) + v.Minor = (int64(unix.Minor(statT.Rdev))) + s.ResourceLimits.BlockIO.ThrottleWriteBpsDevice = append(s.ResourceLimits.BlockIO.ThrottleWriteBpsDevice, v) + } + } + if iops := s.ThrottleReadIOPSDevice; len(iops) > 0 { + for k, v := range iops { + statT := unix.Stat_t{} + if err := unix.Stat(k, &statT); err != nil { + return err + } + v.Major = (int64(unix.Major(statT.Rdev))) + v.Minor = (int64(unix.Minor(statT.Rdev))) + s.ResourceLimits.BlockIO.ThrottleReadIOPSDevice = append(s.ResourceLimits.BlockIO.ThrottleReadIOPSDevice, v) + } + } + if iops := s.ThrottleWriteBpsDevice; len(iops) > 0 { + for k, v := range iops { + statT := unix.Stat_t{} + if err := unix.Stat(k, &statT); err != nil { + return err + } + v.Major = (int64(unix.Major(statT.Rdev))) + v.Minor = (int64(unix.Minor(statT.Rdev))) + s.ResourceLimits.BlockIO.ThrottleWriteIOPSDevice = append(s.ResourceLimits.BlockIO.ThrottleWriteIOPSDevice, v) + } + } + return nil +} diff --git a/pkg/specgen/create.go b/pkg/specgen/generate/container_create.go index aefbe7405..aad59a861 100644 --- a/pkg/specgen/create.go +++ b/pkg/specgen/generate/container_create.go @@ -1,19 +1,21 @@ -package specgen +package generate import ( "context" "os" + "github.com/containers/common/pkg/config" "github.com/containers/libpod/libpod" - "github.com/containers/libpod/libpod/config" "github.com/containers/libpod/libpod/define" + "github.com/containers/libpod/pkg/specgen" + "github.com/containers/storage" "github.com/pkg/errors" "github.com/sirupsen/logrus" ) // MakeContainer creates a container based on the SpecGenerator -func (s *SpecGenerator) MakeContainer(rt *libpod.Runtime) (*libpod.Container, error) { - if err := s.validate(rt); err != nil { +func MakeContainer(rt *libpod.Runtime, s *specgen.SpecGenerator) (*libpod.Container, error) { + if err := s.Validate(); err != nil { return nil, errors.Wrap(err, "invalid config provided") } rtc, err := rt.GetConfig() @@ -21,7 +23,7 @@ func (s *SpecGenerator) MakeContainer(rt *libpod.Runtime) (*libpod.Container, er return nil, err } - options, err := s.createContainerOptions(rt) + options, err := createContainerOptions(rt, s) if err != nil { return nil, err } @@ -30,7 +32,7 @@ func (s *SpecGenerator) MakeContainer(rt *libpod.Runtime) (*libpod.Container, er if err != nil { return nil, err } - options = append(options, s.createExitCommandOption(rtc, podmanPath)) + options = append(options, createExitCommandOption(s, rt.StorageConfig(), rtc, podmanPath)) newImage, err := rt.ImageRuntime().NewFromLocal(s.Image) if err != nil { return nil, err @@ -38,14 +40,14 @@ func (s *SpecGenerator) MakeContainer(rt *libpod.Runtime) (*libpod.Container, er options = append(options, libpod.WithRootFSFromImage(newImage.ID(), s.Image, s.RawImageName)) - runtimeSpec, err := s.toOCISpec(rt, newImage) + runtimeSpec, err := s.ToOCISpec(rt, newImage) if err != nil { return nil, err } return rt.NewContainer(context.Background(), runtimeSpec, options...) } -func (s *SpecGenerator) createContainerOptions(rt *libpod.Runtime) ([]libpod.CtrCreateOption, error) { +func createContainerOptions(rt *libpod.Runtime, s *specgen.SpecGenerator) ([]libpod.CtrCreateOption, error) { var options []libpod.CtrCreateOption var err error @@ -113,7 +115,7 @@ func (s *SpecGenerator) createContainerOptions(rt *libpod.Runtime) ([]libpod.Ctr options = append(options, libpod.WithPrivileged(s.Privileged)) // Get namespace related options - namespaceOptions, err := s.generateNamespaceContainerOpts(rt) + namespaceOptions, err := s.GenerateNamespaceContainerOpts(rt) if err != nil { return nil, err } @@ -148,7 +150,7 @@ func (s *SpecGenerator) createContainerOptions(rt *libpod.Runtime) ([]libpod.Ctr return options, nil } -func (s *SpecGenerator) createExitCommandOption(config *config.Config, podmanPath string) libpod.CtrCreateOption { +func createExitCommandOption(s *specgen.SpecGenerator, storageConfig storage.StoreOptions, config *config.Config, podmanPath string) libpod.CtrCreateOption { // We need a cleanup process for containers in the current model. // But we can't assume that the caller is Podman - it could be another // user of the API. @@ -156,23 +158,23 @@ func (s *SpecGenerator) createExitCommandOption(config *config.Config, podmanPat // still invoke a cleanup process. command := []string{podmanPath, - "--root", config.StorageConfig.GraphRoot, - "--runroot", config.StorageConfig.RunRoot, + "--root", storageConfig.GraphRoot, + "--runroot", storageConfig.RunRoot, "--log-level", logrus.GetLevel().String(), - "--cgroup-manager", config.CgroupManager, - "--tmpdir", config.TmpDir, + "--cgroup-manager", config.Engine.CgroupManager, + "--tmpdir", config.Engine.TmpDir, } - if config.OCIRuntime != "" { - command = append(command, []string{"--runtime", config.OCIRuntime}...) + if config.Engine.OCIRuntime != "" { + command = append(command, []string{"--runtime", config.Engine.OCIRuntime}...) } - if config.StorageConfig.GraphDriverName != "" { - command = append(command, []string{"--storage-driver", config.StorageConfig.GraphDriverName}...) + if storageConfig.GraphDriverName != "" { + command = append(command, []string{"--storage-driver", storageConfig.GraphDriverName}...) } - for _, opt := range config.StorageConfig.GraphDriverOptions { + for _, opt := range storageConfig.GraphDriverOptions { command = append(command, []string{"--storage-opt", opt}...) } - if config.EventsLogger != "" { - command = append(command, []string{"--events-backend", config.EventsLogger}...) + if config.Engine.EventsLogger != "" { + command = append(command, []string{"--events-backend", config.Engine.EventsLogger}...) } // TODO Mheon wants to leave this for now diff --git a/pkg/specgen/namespaces.go b/pkg/specgen/namespaces.go index fa2dee77d..2a7bb3495 100644 --- a/pkg/specgen/namespaces.go +++ b/pkg/specgen/namespaces.go @@ -16,6 +16,9 @@ import ( type NamespaceMode string const ( + // Default indicates the spec generator should determine + // a sane default + Default NamespaceMode = "default" // Host means the the namespace is derived from // the host Host NamespaceMode = "host" @@ -83,7 +86,7 @@ func validateNetNS(n *Namespace) error { return nil } -// validate perform simple validation on the namespace to make sure it is not +// Validate perform simple validation on the namespace to make sure it is not // invalid from the get-go func (n *Namespace) validate() error { if n == nil { @@ -103,7 +106,7 @@ func (n *Namespace) validate() error { return nil } -func (s *SpecGenerator) generateNamespaceContainerOpts(rt *libpod.Runtime) ([]libpod.CtrCreateOption, error) { +func (s *SpecGenerator) GenerateNamespaceContainerOpts(rt *libpod.Runtime) ([]libpod.CtrCreateOption, error) { var portBindings []ocicni.PortMapping options := make([]libpod.CtrCreateOption, 0) diff --git a/pkg/specgen/oci.go b/pkg/specgen/oci.go index 2523f21b3..0756782b4 100644 --- a/pkg/specgen/oci.go +++ b/pkg/specgen/oci.go @@ -11,7 +11,7 @@ import ( "github.com/opencontainers/runtime-tools/generate" ) -func (s *SpecGenerator) toOCISpec(rt *libpod.Runtime, newImage *image.Image) (*spec.Spec, error) { +func (s *SpecGenerator) ToOCISpec(rt *libpod.Runtime, newImage *image.Image) (*spec.Spec, error) { var ( inUserNS bool ) @@ -215,11 +215,9 @@ func (s *SpecGenerator) toOCISpec(rt *libpod.Runtime, newImage *image.Image) (*s // BIND MOUNTS configSpec.Mounts = createconfig.SupercedeUserMounts(s.Mounts, configSpec.Mounts) // Process mounts to ensure correct options - finalMounts, err := createconfig.InitFSMounts(configSpec.Mounts) - if err != nil { + if err := createconfig.InitFSMounts(configSpec.Mounts); err != nil { return nil, err } - configSpec.Mounts = finalMounts // Add annotations if configSpec.Annotations == nil { diff --git a/pkg/specgen/pod_create.go b/pkg/specgen/pod_create.go new file mode 100644 index 000000000..06aa24e22 --- /dev/null +++ b/pkg/specgen/pod_create.go @@ -0,0 +1,83 @@ +package specgen + +import ( + "context" + + "github.com/containers/libpod/cmd/podman/shared" + "github.com/containers/libpod/libpod" + "github.com/sirupsen/logrus" +) + +func (p *PodSpecGenerator) MakePod(rt *libpod.Runtime) (*libpod.Pod, error) { + if err := p.validate(); err != nil { + return nil, err + } + options, err := p.createPodOptions() + if err != nil { + return nil, err + } + return rt.NewPod(context.Background(), options...) +} + +func (p *PodSpecGenerator) createPodOptions() ([]libpod.PodCreateOption, error) { + var ( + options []libpod.PodCreateOption + ) + if !p.NoInfra { + options = append(options, libpod.WithInfraContainer()) + nsOptions, err := shared.GetNamespaceOptions(p.SharedNamespaces) + if err != nil { + return nil, err + } + options = append(options, nsOptions...) + } + if len(p.CgroupParent) > 0 { + options = append(options, libpod.WithPodCgroupParent(p.CgroupParent)) + } + if len(p.Labels) > 0 { + options = append(options, libpod.WithPodLabels(p.Labels)) + } + if len(p.Name) > 0 { + options = append(options, libpod.WithPodName(p.Name)) + } + if len(p.Hostname) > 0 { + options = append(options, libpod.WithPodHostname(p.Hostname)) + } + if len(p.HostAdd) > 0 { + options = append(options, libpod.WithPodHosts(p.HostAdd)) + } + if len(p.DNSOption) > 0 { + options = append(options, libpod.WithPodDNSOption(p.DNSOption)) + } + if len(p.DNSSearch) > 0 { + options = append(options, libpod.WithPodDNSSearch(p.DNSSearch)) + } + if p.StaticIP != nil { + options = append(options, libpod.WithPodStaticIP(*p.StaticIP)) + } + if p.StaticMAC != nil { + options = append(options, libpod.WithPodStaticMAC(*p.StaticMAC)) + } + if p.NoManageResolvConf { + options = append(options, libpod.WithPodUseImageResolvConf()) + } + switch p.NetNS.NSMode { + case Bridge: + logrus.Debugf("Pod using default network mode") + case Host: + logrus.Debugf("Pod will use host networking") + options = append(options, libpod.WithPodHostNetwork()) + default: + logrus.Debugf("Pod joining CNI networks: %v", p.CNINetworks) + options = append(options, libpod.WithPodNetworks(p.CNINetworks)) + } + + if p.NoManageHosts { + options = append(options, libpod.WithPodUseImageHosts()) + } + if len(p.PortMappings) > 0 { + options = append(options, libpod.WithInfraContainerPorts(p.PortMappings)) + } + options = append(options, libpod.WithPodCgroups()) + return options, nil +} diff --git a/pkg/specgen/pod_validate.go b/pkg/specgen/pod_validate.go new file mode 100644 index 000000000..50309f096 --- /dev/null +++ b/pkg/specgen/pod_validate.go @@ -0,0 +1,104 @@ +package specgen + +import ( + "github.com/containers/libpod/libpod/define" + "github.com/containers/libpod/pkg/rootless" + "github.com/pkg/errors" +) + +var ( + // ErrInvalidPodSpecConfig describes an error given when the podspecgenerator is invalid + ErrInvalidPodSpecConfig error = errors.New("invalid pod spec") +) + +func exclusivePodOptions(opt1, opt2 string) error { + return errors.Wrapf(ErrInvalidPodSpecConfig, "%s and %s are mutually exclusive pod options", opt1, opt2) +} + +func (p *PodSpecGenerator) validate() error { + // PodBasicConfig + if p.NoInfra { + if len(p.InfraCommand) > 0 { + return exclusivePodOptions("NoInfra", "InfraCommand") + } + if len(p.InfraImage) > 0 { + return exclusivePodOptions("NoInfra", "InfraImage") + } + if len(p.SharedNamespaces) > 0 { + return exclusivePodOptions("NoInfo", "SharedNamespaces") + } + } + + // PodNetworkConfig + if err := p.NetNS.validate(); err != nil { + return err + } + if p.NoInfra { + if p.NetNS.NSMode == NoNetwork { + return errors.New("NoInfra and a none network cannot be used toegther") + } + if p.StaticIP != nil { + return exclusivePodOptions("NoInfra", "StaticIP") + } + if p.StaticMAC != nil { + return exclusivePodOptions("NoInfra", "StaticMAC") + } + if len(p.DNSOption) > 0 { + return exclusivePodOptions("NoInfra", "DNSOption") + } + if len(p.DNSSearch) > 0 { + return exclusivePodOptions("NoInfo", "DNSSearch") + } + if len(p.DNSServer) > 0 { + return exclusivePodOptions("NoInfra", "DNSServer") + } + if len(p.HostAdd) > 0 { + return exclusivePodOptions("NoInfra", "HostAdd") + } + if p.NoManageResolvConf { + return exclusivePodOptions("NoInfra", "NoManageResolvConf") + } + } + if p.NetNS.NSMode != Bridge { + if len(p.PortMappings) > 0 { + return errors.New("PortMappings can only be used with Bridge mode networking") + } + if len(p.CNINetworks) > 0 { + return errors.New("CNINetworks can only be used with Bridge mode networking") + } + } + if p.NoManageResolvConf { + if len(p.DNSServer) > 0 { + return exclusivePodOptions("NoManageResolvConf", "DNSServer") + } + if len(p.DNSSearch) > 0 { + return exclusivePodOptions("NoManageResolvConf", "DNSSearch") + } + if len(p.DNSOption) > 0 { + return exclusivePodOptions("NoManageResolvConf", "DNSOption") + } + } + if p.NoManageHosts && len(p.HostAdd) > 0 { + return exclusivePodOptions("NoManageHosts", "HostAdd") + } + + if err := p.NetNS.validate(); err != nil { + return err + } + + // Set Defaults + if p.NetNS.Value == "" { + if rootless.IsRootless() { + p.NetNS.NSMode = Slirp + } else { + p.NetNS.NSMode = Bridge + } + } + if len(p.InfraImage) < 1 { + p.InfraImage = define.DefaultInfraImage + } + if len(p.InfraCommand) < 1 { + p.InfraCommand = []string{define.DefaultInfraCommand} + } + return nil +} diff --git a/pkg/specgen/pod.go b/pkg/specgen/podspecgen.go index 1aada83c4..3f830014d 100644 --- a/pkg/specgen/pod.go +++ b/pkg/specgen/podspecgen.go @@ -138,3 +138,16 @@ type PodCgroupConfig struct { // Optional. CgroupParent string `json:"cgroup_parent,omitempty"` } + +// PodSpecGenerator describes options to create a pod +// swagger:model PodSpecGenerator +type PodSpecGenerator struct { + PodBasicConfig + PodNetworkConfig + PodCgroupConfig +} + +// NewPodSpecGenerator creates a new pod spec +func NewPodSpecGenerator() *PodSpecGenerator { + return &PodSpecGenerator{} +} diff --git a/pkg/specgen/security.go b/pkg/specgen/security.go new file mode 100644 index 000000000..158e4a7b3 --- /dev/null +++ b/pkg/specgen/security.go @@ -0,0 +1,165 @@ +package specgen + +// ToCreateOptions convert the SecurityConfig to a slice of container create +// options. +/* +func (c *SecurityConfig) ToCreateOptions() ([]libpod.CtrCreateOption, error) { + options := make([]libpod.CtrCreateOption, 0) + options = append(options, libpod.WithSecLabels(c.LabelOpts)) + options = append(options, libpod.WithPrivileged(c.Privileged)) + return options, nil +} +*/ + +// SetLabelOpts sets the label options of the SecurityConfig according to the +// input. +/* +func (c *SecurityConfig) SetLabelOpts(runtime *libpod.Runtime, pidConfig *PidConfig, ipcConfig *IpcConfig) error { + if c.Privileged { + c.LabelOpts = label.DisableSecOpt() + return nil + } + + var labelOpts []string + if pidConfig.PidMode.IsHost() { + labelOpts = append(labelOpts, label.DisableSecOpt()...) + } else if pidConfig.PidMode.IsContainer() { + ctr, err := runtime.LookupContainer(pidConfig.PidMode.Container()) + if err != nil { + return errors.Wrapf(err, "container %q not found", pidConfig.PidMode.Container()) + } + secopts, err := label.DupSecOpt(ctr.ProcessLabel()) + if err != nil { + return errors.Wrapf(err, "failed to duplicate label %q ", ctr.ProcessLabel()) + } + labelOpts = append(labelOpts, secopts...) + } + + if ipcConfig.IpcMode.IsHost() { + labelOpts = append(labelOpts, label.DisableSecOpt()...) + } else if ipcConfig.IpcMode.IsContainer() { + ctr, err := runtime.LookupContainer(ipcConfig.IpcMode.Container()) + if err != nil { + return errors.Wrapf(err, "container %q not found", ipcConfig.IpcMode.Container()) + } + secopts, err := label.DupSecOpt(ctr.ProcessLabel()) + if err != nil { + return errors.Wrapf(err, "failed to duplicate label %q ", ctr.ProcessLabel()) + } + labelOpts = append(labelOpts, secopts...) + } + + c.LabelOpts = append(c.LabelOpts, labelOpts...) + return nil +} +*/ + +// SetSecurityOpts the the security options (labels, apparmor, seccomp, etc.). +func SetSecurityOpts(securityOpts []string) error { + return nil +} + +// ConfigureGenerator configures the generator according to the input. +/* +func (c *SecurityConfig) ConfigureGenerator(g *generate.Generator, user *UserConfig) error { + // HANDLE CAPABILITIES + // NOTE: Must happen before SECCOMP + if c.Privileged { + g.SetupPrivileged(true) + } + + useNotRoot := func(user string) bool { + if user == "" || user == "root" || user == "0" { + return false + } + return true + } + + configSpec := g.Config + var err error + var defaultCaplist []string + bounding := configSpec.Process.Capabilities.Bounding + if useNotRoot(user.User) { + configSpec.Process.Capabilities.Bounding = defaultCaplist + } + defaultCaplist, err = capabilities.MergeCapabilities(configSpec.Process.Capabilities.Bounding, c.CapAdd, c.CapDrop) + if err != nil { + return err + } + + privCapRequired := []string{} + + if !c.Privileged && len(c.CapRequired) > 0 { + // Pass CapRequired in CapAdd field to normalize capabilities names + capRequired, err := capabilities.MergeCapabilities(nil, c.CapRequired, nil) + if err != nil { + logrus.Errorf("capabilities requested by user or image are not valid: %q", strings.Join(c.CapRequired, ",")) + } else { + // Verify all capRequiered are in the defaultCapList + for _, cap := range capRequired { + if !util.StringInSlice(cap, defaultCaplist) { + privCapRequired = append(privCapRequired, cap) + } + } + } + if len(privCapRequired) == 0 { + defaultCaplist = capRequired + } else { + logrus.Errorf("capabilities requested by user or image are not allowed by default: %q", strings.Join(privCapRequired, ",")) + } + } + configSpec.Process.Capabilities.Bounding = defaultCaplist + configSpec.Process.Capabilities.Permitted = defaultCaplist + configSpec.Process.Capabilities.Inheritable = defaultCaplist + configSpec.Process.Capabilities.Effective = defaultCaplist + configSpec.Process.Capabilities.Ambient = defaultCaplist + if useNotRoot(user.User) { + defaultCaplist, err = capabilities.MergeCapabilities(bounding, c.CapAdd, c.CapDrop) + if err != nil { + return err + } + } + configSpec.Process.Capabilities.Bounding = defaultCaplist + + // HANDLE SECCOMP + if c.SeccompProfilePath != "unconfined" { + seccompConfig, err := getSeccompConfig(c, configSpec) + if err != nil { + return err + } + configSpec.Linux.Seccomp = seccompConfig + } + + // Clear default Seccomp profile from Generator for privileged containers + if c.SeccompProfilePath == "unconfined" || c.Privileged { + configSpec.Linux.Seccomp = nil + } + + for _, opt := range c.SecurityOpts { + // Split on both : and = + splitOpt := strings.Split(opt, "=") + if len(splitOpt) == 1 { + splitOpt = strings.Split(opt, ":") + } + if len(splitOpt) < 2 { + continue + } + switch splitOpt[0] { + case "label": + configSpec.Annotations[libpod.InspectAnnotationLabel] = splitOpt[1] + case "seccomp": + configSpec.Annotations[libpod.InspectAnnotationSeccomp] = splitOpt[1] + case "apparmor": + configSpec.Annotations[libpod.InspectAnnotationApparmor] = splitOpt[1] + } + } + + g.SetRootReadonly(c.ReadOnlyRootfs) + for sysctlKey, sysctlVal := range c.Sysctl { + g.AddLinuxSysctl(sysctlKey, sysctlVal) + } + + return nil +} + +*/ diff --git a/pkg/specgen/specgen.go b/pkg/specgen/specgen.go index b123c1da5..2e6dd9c1d 100644 --- a/pkg/specgen/specgen.go +++ b/pkg/specgen/specgen.go @@ -4,8 +4,9 @@ import ( "net" "syscall" - "github.com/containers/image/v5/manifest" "github.com/containers/libpod/libpod" + + "github.com/containers/image/v5/manifest" "github.com/containers/libpod/pkg/rootless" "github.com/containers/storage" "github.com/cri-o/ocicni/pkg/ocicni" @@ -371,6 +372,16 @@ type ContainerResourceConfig struct { // processes to kill for the container's process. // Optional. OOMScoreAdj *int `json:"oom_score_adj,omitempty"` + // Weight per cgroup per device, can override BlkioWeight + WeightDevice map[string]spec.LinuxWeightDevice `json:"weightDevice,omitempty"` + // IO read rate limit per cgroup per device, bytes per second + ThrottleReadBpsDevice map[string]spec.LinuxThrottleDevice `json:"throttleReadBpsDevice,omitempty"` + // IO write rate limit per cgroup per device, bytes per second + ThrottleWriteBpsDevice map[string]spec.LinuxThrottleDevice `json:"throttleWriteBpsDevice,omitempty"` + // IO read rate limit per cgroup per device, IO per second + ThrottleReadIOPSDevice map[string]spec.LinuxThrottleDevice `json:"throttleReadIOPSDevice,omitempty"` + // IO write rate limit per cgroup per device, IO per second + ThrottleWriteIOPSDevice map[string]spec.LinuxThrottleDevice `json:"throttleWriteIOPSDevice,omitempty"` } // ContainerHealthCheckConfig describes a container healthcheck with attributes @@ -394,18 +405,18 @@ type SpecGenerator struct { // NewSpecGenerator returns a SpecGenerator struct given one of two mandatory inputs func NewSpecGenerator(image string) *SpecGenerator { - net := ContainerNetworkConfig{ + networkConfig := ContainerNetworkConfig{ NetNS: Namespace{ NSMode: Bridge, }, } csc := ContainerStorageConfig{Image: image} if rootless.IsRootless() { - net.NetNS.NSMode = Slirp + networkConfig.NetNS.NSMode = Slirp } return &SpecGenerator{ ContainerStorageConfig: csc, - ContainerNetworkConfig: net, + ContainerNetworkConfig: networkConfig, } } diff --git a/pkg/specgen/storage.go b/pkg/specgen/storage.go new file mode 100644 index 000000000..1b903f608 --- /dev/null +++ b/pkg/specgen/storage.go @@ -0,0 +1,885 @@ +package specgen + +//nolint + +import ( + "fmt" + "path" + "path/filepath" + "strings" + + "github.com/containers/libpod/libpod" + + "github.com/containers/buildah/pkg/parse" + "github.com/containers/libpod/pkg/util" + spec "github.com/opencontainers/runtime-spec/specs-go" + "github.com/pkg/errors" + "github.com/sirupsen/logrus" +) + +const ( + // TypeBind is the type for mounting host dir + TypeBind = "bind" + // TypeVolume is the type for named volumes + TypeVolume = "volume" + // TypeTmpfs is the type for mounting tmpfs + TypeTmpfs = "tmpfs" +) + +var ( + errDuplicateDest = errors.Errorf("duplicate mount destination") //nolint + optionArgError = errors.Errorf("must provide an argument for option") //nolint + noDestError = errors.Errorf("must set volume destination") //nolint +) + +// Parse all volume-related options in the create config into a set of mounts +// and named volumes to add to the container. +// Handles --volumes-from, --volumes, --tmpfs, --init, and --init-path flags. +// TODO: Named volume options - should we default to rprivate? It bakes into a +// bind mount under the hood... +// TODO: handle options parsing/processing via containers/storage/pkg/mount +func (s *SpecGenerator) parseVolumes(mounts, volMounts, tmpMounts []string) error { //nolint + + // TODO this needs to come from the image and erquires a runtime + + // Add image volumes. + //baseMounts, baseVolumes, err := config.getImageVolumes() + //if err != nil { + // return nil, nil, err + //} + + // Add --volumes-from. + // Overrides image volumes unconditionally. + //vFromMounts, vFromVolumes, err := config.getVolumesFrom(runtime) + //if err != nil { + // return nil, nil, err + //} + //for dest, mount := range vFromMounts { + // baseMounts[dest] = mount + //} + //for dest, volume := range vFromVolumes { + // baseVolumes[dest] = volume + //} + + // Next mounts from the --mounts flag. + // Do not override yet. + //unifiedMounts, _, err := getMounts(mounts) + //if err != nil { + // return err + //} + // + //// Next --volumes flag. + //// Do not override yet. + //volumeMounts, _ , err := getVolumeMounts(volMounts) + //if err != nil { + // return err + //} + // + //// Next --tmpfs flag. + //// Do not override yet. + //tmpfsMounts, err := getTmpfsMounts(tmpMounts) + //if err != nil { + // return err + //} + + //// Unify mounts from --mount, --volume, --tmpfs. + //// Also add mounts + volumes directly from createconfig. + //// Start with --volume. + //for dest, mount := range volumeMounts { + // if _, ok := unifiedMounts[dest]; ok { + // return nil, nil, errors.Wrapf(errDuplicateDest, dest) + // } + // unifiedMounts[dest] = mount + //} + //for dest, volume := range volumeVolumes { + // if _, ok := unifiedVolumes[dest]; ok { + // return nil, nil, errors.Wrapf(errDuplicateDest, dest) + // } + // unifiedVolumes[dest] = volume + //} + //// Now --tmpfs + //for dest, tmpfs := range tmpfsMounts { + // if _, ok := unifiedMounts[dest]; ok { + // return nil, nil, errors.Wrapf(errDuplicateDest, dest) + // } + // unifiedMounts[dest] = tmpfs + //} + //// Now spec mounts and volumes + //for _, mount := range config.Mounts { + // dest := mount.Destination + // if _, ok := unifiedMounts[dest]; ok { + // return nil, nil, errors.Wrapf(errDuplicateDest, dest) + // } + // unifiedMounts[dest] = mount + //} + //for _, volume := range config.NamedVolumes { + // dest := volume.Dest + // if _, ok := unifiedVolumes[dest]; ok { + // return nil, nil, errors.Wrapf(errDuplicateDest, dest) + // } + // unifiedVolumes[dest] = volume + //} + // + //// If requested, add container init binary + //if config.Init { + // initPath := config.InitPath + // if initPath == "" { + // rtc, err := runtime.GetConfig() + // if err != nil { + // return nil, nil, err + // } + // initPath = rtc.Engine.InitPath + // } + // initMount, err := config.addContainerInitBinary(initPath) + // if err != nil { + // return nil, nil, err + // } + // if _, ok := unifiedMounts[initMount.Destination]; ok { + // return nil, nil, errors.Wrapf(errDuplicateDest, "conflict with mount added by --init to %q", initMount.Destination) + // } + // unifiedMounts[initMount.Destination] = initMount + //} + // + //// Before superseding, we need to find volume mounts which conflict with + //// named volumes, and vice versa. + //// We'll delete the conflicts here as we supersede. + //for dest := range unifiedMounts { + // if _, ok := baseVolumes[dest]; ok { + // delete(baseVolumes, dest) + // } + //} + //for dest := range unifiedVolumes { + // if _, ok := baseMounts[dest]; ok { + // delete(baseMounts, dest) + // } + //} + // + //// Supersede volumes-from/image volumes with unified volumes from above. + //// This is an unconditional replacement. + //for dest, mount := range unifiedMounts { + // baseMounts[dest] = mount + //} + //for dest, volume := range unifiedVolumes { + // baseVolumes[dest] = volume + //} + // + //// If requested, add tmpfs filesystems for read-only containers. + //if config.Security.ReadOnlyRootfs && config.Security.ReadOnlyTmpfs { + // readonlyTmpfs := []string{"/tmp", "/var/tmp", "/run"} + // options := []string{"rw", "rprivate", "nosuid", "nodev", "tmpcopyup"} + // for _, dest := range readonlyTmpfs { + // if _, ok := baseMounts[dest]; ok { + // continue + // } + // if _, ok := baseVolumes[dest]; ok { + // continue + // } + // localOpts := options + // if dest == "/run" { + // localOpts = append(localOpts, "noexec", "size=65536k") + // } else { + // localOpts = append(localOpts, "exec") + // } + // baseMounts[dest] = spec.Mount{ + // Destination: dest, + // Type: "tmpfs", + // Source: "tmpfs", + // Options: localOpts, + // } + // } + //} + // + //// Check for conflicts between named volumes and mounts + //for dest := range baseMounts { + // if _, ok := baseVolumes[dest]; ok { + // return nil, nil, errors.Wrapf(errDuplicateDest, "conflict at mount destination %v", dest) + // } + //} + //for dest := range baseVolumes { + // if _, ok := baseMounts[dest]; ok { + // return nil, nil, errors.Wrapf(errDuplicateDest, "conflict at mount destination %v", dest) + // } + //} + // + //// Final step: maps to arrays + //finalMounts := make([]spec.Mount, 0, len(baseMounts)) + //for _, mount := range baseMounts { + // if mount.Type == TypeBind { + // absSrc, err := filepath.Abs(mount.Source) + // if err != nil { + // return nil, nil, errors.Wrapf(err, "error getting absolute path of %s", mount.Source) + // } + // mount.Source = absSrc + // } + // finalMounts = append(finalMounts, mount) + //} + //finalVolumes := make([]*define.ContainerNamedVolume, 0, len(baseVolumes)) + //for _, volume := range baseVolumes { + // finalVolumes = append(finalVolumes, volume) + //} + + //return finalMounts, finalVolumes, nil + return nil +} + +// Parse volumes from - a set of containers whose volumes we will mount in. +// Grab the containers, retrieve any user-created spec mounts and all named +// volumes, and return a list of them. +// Conflicts are resolved simply - the last container specified wins. +// Container names may be suffixed by mount options after a colon. +// TODO: We should clean these paths if possible +// TODO deferred baude +func getVolumesFrom() (map[string]spec.Mount, map[string]*libpod.ContainerNamedVolume, error) { //nolint + // Both of these are maps of mount destination to mount type. + // We ensure that each destination is only mounted to once in this way. + //finalMounts := make(map[string]spec.Mount) + //finalNamedVolumes := make(map[string]*define.ContainerNamedVolume) + // + //for _, vol := range config.VolumesFrom { + // var ( + // options = []string{} + // err error + // splitVol = strings.SplitN(vol, ":", 2) + // ) + // if len(splitVol) == 2 { + // splitOpts := strings.Split(splitVol[1], ",") + // for _, checkOpt := range splitOpts { + // switch checkOpt { + // case "z", "ro", "rw": + // // Do nothing, these are valid options + // default: + // return nil, nil, errors.Errorf("invalid options %q, can only specify 'ro', 'rw', and 'z'", splitVol[1]) + // } + // } + // + // if options, err = parse.ValidateVolumeOpts(splitOpts); err != nil { + // return nil, nil, err + // } + // } + // ctr, err := runtime.LookupContainer(splitVol[0]) + // if err != nil { + // return nil, nil, errors.Wrapf(err, "error looking up container %q for volumes-from", splitVol[0]) + // } + // + // logrus.Debugf("Adding volumes from container %s", ctr.ID()) + // + // // Look up the container's user volumes. This gets us the + // // destinations of all mounts the user added to the container. + // userVolumesArr := ctr.UserVolumes() + // + // // We're going to need to access them a lot, so convert to a map + // // to reduce looping. + // // We'll also use the map to indicate if we missed any volumes along the way. + // userVolumes := make(map[string]bool) + // for _, dest := range userVolumesArr { + // userVolumes[dest] = false + // } + // + // // Now we get the container's spec and loop through its volumes + // // and append them in if we can find them. + // spec := ctr.Spec() + // if spec == nil { + // return nil, nil, errors.Errorf("error retrieving container %s spec for volumes-from", ctr.ID()) + // } + // for _, mnt := range spec.Mounts { + // if mnt.Type != TypeBind { + // continue + // } + // if _, exists := userVolumes[mnt.Destination]; exists { + // userVolumes[mnt.Destination] = true + // + // if len(options) != 0 { + // mnt.Options = options + // } + // + // if _, ok := finalMounts[mnt.Destination]; ok { + // logrus.Debugf("Overriding mount to %s with new mount from container %s", mnt.Destination, ctr.ID()) + // } + // finalMounts[mnt.Destination] = mnt + // } + // } + // + // // We're done with the spec mounts. Add named volumes. + // // Add these unconditionally - none of them are automatically + // // part of the container, as some spec mounts are. + // namedVolumes := ctr.NamedVolumes() + // for _, namedVol := range namedVolumes { + // if _, exists := userVolumes[namedVol.Dest]; exists { + // userVolumes[namedVol.Dest] = true + // } + // + // if len(options) != 0 { + // namedVol.Options = options + // } + // + // if _, ok := finalMounts[namedVol.Dest]; ok { + // logrus.Debugf("Overriding named volume mount to %s with new named volume from container %s", namedVol.Dest, ctr.ID()) + // } + // finalNamedVolumes[namedVol.Dest] = namedVol + // } + // + // // Check if we missed any volumes + // for volDest, found := range userVolumes { + // if !found { + // logrus.Warnf("Unable to match volume %s from container %s for volumes-from", volDest, ctr.ID()) + // } + // } + //} + // + //return finalMounts, finalNamedVolumes, nil + return nil, nil, nil +} + +// getMounts takes user-provided input from the --mount flag and creates OCI +// spec mounts and Libpod named volumes. +// podman run --mount type=bind,src=/etc/resolv.conf,target=/etc/resolv.conf ... +// podman run --mount type=tmpfs,target=/dev/shm ... +// podman run --mount type=volume,source=test-volume, ... +func getMounts(mounts []string) (map[string]spec.Mount, map[string]*libpod.ContainerNamedVolume, error) { //nolint + finalMounts := make(map[string]spec.Mount) + finalNamedVolumes := make(map[string]*libpod.ContainerNamedVolume) + + errInvalidSyntax := errors.Errorf("incorrect mount format: should be --mount type=<bind|tmpfs|volume>,[src=<host-dir|volume-name>,]target=<ctr-dir>[,options]") + + // TODO(vrothberg): the manual parsing can be replaced with a regular expression + // to allow a more robust parsing of the mount format and to give + // precise errors regarding supported format versus supported options. + for _, mount := range mounts { + arr := strings.SplitN(mount, ",", 2) + if len(arr) < 2 { + return nil, nil, errors.Wrapf(errInvalidSyntax, "%q", mount) + } + kv := strings.Split(arr[0], "=") + // TODO: type is not explicitly required in Docker. + // If not specified, it defaults to "volume". + if len(kv) != 2 || kv[0] != "type" { + return nil, nil, errors.Wrapf(errInvalidSyntax, "%q", mount) + } + + tokens := strings.Split(arr[1], ",") + switch kv[1] { + case TypeBind: + mount, err := getBindMount(tokens) + if err != nil { + return nil, nil, err + } + if _, ok := finalMounts[mount.Destination]; ok { + return nil, nil, errors.Wrapf(errDuplicateDest, mount.Destination) + } + finalMounts[mount.Destination] = mount + case TypeTmpfs: + mount, err := getTmpfsMount(tokens) + if err != nil { + return nil, nil, err + } + if _, ok := finalMounts[mount.Destination]; ok { + return nil, nil, errors.Wrapf(errDuplicateDest, mount.Destination) + } + finalMounts[mount.Destination] = mount + case "volume": + volume, err := getNamedVolume(tokens) + if err != nil { + return nil, nil, err + } + if _, ok := finalNamedVolumes[volume.Dest]; ok { + return nil, nil, errors.Wrapf(errDuplicateDest, volume.Dest) + } + finalNamedVolumes[volume.Dest] = volume + default: + return nil, nil, errors.Errorf("invalid filesystem type %q", kv[1]) + } + } + + return finalMounts, finalNamedVolumes, nil +} + +// Parse a single bind mount entry from the --mount flag. +func getBindMount(args []string) (spec.Mount, error) { //nolint + newMount := spec.Mount{ + Type: TypeBind, + } + + var setSource, setDest, setRORW, setSuid, setDev, setExec, setRelabel bool + + for _, val := range args { + kv := strings.Split(val, "=") + switch kv[0] { + case "bind-nonrecursive": + newMount.Options = append(newMount.Options, "bind") + case "ro", "rw": + if setRORW { + return newMount, errors.Wrapf(optionArgError, "cannot pass 'ro' or 'rw' options more than once") + } + setRORW = true + // Can be formatted as one of: + // ro + // ro=[true|false] + // rw + // rw=[true|false] + switch len(kv) { + case 1: + newMount.Options = append(newMount.Options, kv[0]) + case 2: + switch strings.ToLower(kv[1]) { + case "true": + newMount.Options = append(newMount.Options, kv[0]) + case "false": + // Set the opposite only for rw + // ro's opposite is the default + if kv[0] == "rw" { + newMount.Options = append(newMount.Options, "ro") + } + default: + return newMount, errors.Wrapf(optionArgError, "%s must be set to true or false, instead received %q", kv[0], kv[1]) + } + default: + return newMount, errors.Wrapf(optionArgError, "badly formatted option %q", val) + } + case "nosuid", "suid": + if setSuid { + return newMount, errors.Wrapf(optionArgError, "cannot pass 'nosuid' and 'suid' options more than once") + } + setSuid = true + newMount.Options = append(newMount.Options, kv[0]) + case "nodev", "dev": + if setDev { + return newMount, errors.Wrapf(optionArgError, "cannot pass 'nodev' and 'dev' options more than once") + } + setDev = true + newMount.Options = append(newMount.Options, kv[0]) + case "noexec", "exec": + if setExec { + return newMount, errors.Wrapf(optionArgError, "cannot pass 'noexec' and 'exec' options more than once") + } + setExec = true + newMount.Options = append(newMount.Options, kv[0]) + case "shared", "rshared", "private", "rprivate", "slave", "rslave", "Z", "z": + newMount.Options = append(newMount.Options, kv[0]) + case "bind-propagation": + if len(kv) == 1 { + return newMount, errors.Wrapf(optionArgError, kv[0]) + } + newMount.Options = append(newMount.Options, kv[1]) + case "src", "source": + if len(kv) == 1 { + return newMount, errors.Wrapf(optionArgError, kv[0]) + } + if err := parse.ValidateVolumeHostDir(kv[1]); err != nil { + return newMount, err + } + newMount.Source = kv[1] + setSource = true + case "target", "dst", "destination": + if len(kv) == 1 { + return newMount, errors.Wrapf(optionArgError, kv[0]) + } + if err := parse.ValidateVolumeCtrDir(kv[1]); err != nil { + return newMount, err + } + newMount.Destination = filepath.Clean(kv[1]) + setDest = true + case "relabel": + if setRelabel { + return newMount, errors.Wrapf(optionArgError, "cannot pass 'relabel' option more than once") + } + setRelabel = true + if len(kv) != 2 { + return newMount, errors.Wrapf(util.ErrBadMntOption, "%s mount option must be 'private' or 'shared'", kv[0]) + } + switch kv[1] { + case "private": + newMount.Options = append(newMount.Options, "z") + case "shared": + newMount.Options = append(newMount.Options, "Z") + default: + return newMount, errors.Wrapf(util.ErrBadMntOption, "%s mount option must be 'private' or 'shared'", kv[0]) + } + default: + return newMount, errors.Wrapf(util.ErrBadMntOption, kv[0]) + } + } + + if !setDest { + return newMount, noDestError + } + + if !setSource { + newMount.Source = newMount.Destination + } + + options, err := parse.ValidateVolumeOpts(newMount.Options) + if err != nil { + return newMount, err + } + newMount.Options = options + return newMount, nil +} + +// Parse a single tmpfs mount entry from the --mount flag +func getTmpfsMount(args []string) (spec.Mount, error) { //nolint + newMount := spec.Mount{ + Type: TypeTmpfs, + Source: TypeTmpfs, + } + + var setDest, setRORW, setSuid, setDev, setExec, setTmpcopyup bool + + for _, val := range args { + kv := strings.Split(val, "=") + switch kv[0] { + case "tmpcopyup", "notmpcopyup": + if setTmpcopyup { + return newMount, errors.Wrapf(optionArgError, "cannot pass 'tmpcopyup' and 'notmpcopyup' options more than once") + } + setTmpcopyup = true + newMount.Options = append(newMount.Options, kv[0]) + case "ro", "rw": + if setRORW { + return newMount, errors.Wrapf(optionArgError, "cannot pass 'ro' and 'rw' options more than once") + } + setRORW = true + newMount.Options = append(newMount.Options, kv[0]) + case "nosuid", "suid": + if setSuid { + return newMount, errors.Wrapf(optionArgError, "cannot pass 'nosuid' and 'suid' options more than once") + } + setSuid = true + newMount.Options = append(newMount.Options, kv[0]) + case "nodev", "dev": + if setDev { + return newMount, errors.Wrapf(optionArgError, "cannot pass 'nodev' and 'dev' options more than once") + } + setDev = true + newMount.Options = append(newMount.Options, kv[0]) + case "noexec", "exec": + if setExec { + return newMount, errors.Wrapf(optionArgError, "cannot pass 'noexec' and 'exec' options more than once") + } + setExec = true + newMount.Options = append(newMount.Options, kv[0]) + case "tmpfs-mode": + if len(kv) == 1 { + return newMount, errors.Wrapf(optionArgError, kv[0]) + } + newMount.Options = append(newMount.Options, fmt.Sprintf("mode=%s", kv[1])) + case "tmpfs-size": + if len(kv) == 1 { + return newMount, errors.Wrapf(optionArgError, kv[0]) + } + newMount.Options = append(newMount.Options, fmt.Sprintf("size=%s", kv[1])) + case "src", "source": + return newMount, errors.Errorf("source is not supported with tmpfs mounts") + case "target", "dst", "destination": + if len(kv) == 1 { + return newMount, errors.Wrapf(optionArgError, kv[0]) + } + if err := parse.ValidateVolumeCtrDir(kv[1]); err != nil { + return newMount, err + } + newMount.Destination = filepath.Clean(kv[1]) + setDest = true + default: + return newMount, errors.Wrapf(util.ErrBadMntOption, kv[0]) + } + } + + if !setDest { + return newMount, noDestError + } + + return newMount, nil +} + +// Parse a single volume mount entry from the --mount flag. +// Note that the volume-label option for named volumes is currently NOT supported. +// TODO: add support for --volume-label +func getNamedVolume(args []string) (*libpod.ContainerNamedVolume, error) { //nolint + newVolume := new(libpod.ContainerNamedVolume) + + var setSource, setDest, setRORW, setSuid, setDev, setExec bool + + for _, val := range args { + kv := strings.Split(val, "=") + switch kv[0] { + case "ro", "rw": + if setRORW { + return nil, errors.Wrapf(optionArgError, "cannot pass 'ro' and 'rw' options more than once") + } + setRORW = true + newVolume.Options = append(newVolume.Options, kv[0]) + case "nosuid", "suid": + if setSuid { + return nil, errors.Wrapf(optionArgError, "cannot pass 'nosuid' and 'suid' options more than once") + } + setSuid = true + newVolume.Options = append(newVolume.Options, kv[0]) + case "nodev", "dev": + if setDev { + return nil, errors.Wrapf(optionArgError, "cannot pass 'nodev' and 'dev' options more than once") + } + setDev = true + newVolume.Options = append(newVolume.Options, kv[0]) + case "noexec", "exec": + if setExec { + return nil, errors.Wrapf(optionArgError, "cannot pass 'noexec' and 'exec' options more than once") + } + setExec = true + newVolume.Options = append(newVolume.Options, kv[0]) + case "volume-label": + return nil, errors.Errorf("the --volume-label option is not presently implemented") + case "src", "source": + if len(kv) == 1 { + return nil, errors.Wrapf(optionArgError, kv[0]) + } + newVolume.Name = kv[1] + setSource = true + case "target", "dst", "destination": + if len(kv) == 1 { + return nil, errors.Wrapf(optionArgError, kv[0]) + } + if err := parse.ValidateVolumeCtrDir(kv[1]); err != nil { + return nil, err + } + newVolume.Dest = filepath.Clean(kv[1]) + setDest = true + default: + return nil, errors.Wrapf(util.ErrBadMntOption, kv[0]) + } + } + + if !setSource { + return nil, errors.Errorf("must set source volume") + } + if !setDest { + return nil, noDestError + } + + return newVolume, nil +} + +func getVolumeMounts(vols []string) (map[string]spec.Mount, map[string]*libpod.ContainerNamedVolume, error) { //nolint + mounts := make(map[string]spec.Mount) + volumes := make(map[string]*libpod.ContainerNamedVolume) + + volumeFormatErr := errors.Errorf("incorrect volume format, should be [host-dir:]ctr-dir[:option]") + + for _, vol := range vols { + var ( + options []string + src string + dest string + err error + ) + + splitVol := strings.Split(vol, ":") + if len(splitVol) > 3 { + return nil, nil, errors.Wrapf(volumeFormatErr, vol) + } + + src = splitVol[0] + if len(splitVol) == 1 { + // This is an anonymous named volume. Only thing given + // is destination. + // Name/source will be blank, and populated by libpod. + src = "" + dest = splitVol[0] + } else if len(splitVol) > 1 { + dest = splitVol[1] + } + if len(splitVol) > 2 { + if options, err = parse.ValidateVolumeOpts(strings.Split(splitVol[2], ",")); err != nil { + return nil, nil, err + } + } + + // Do not check source dir for anonymous volumes + if len(splitVol) > 1 { + if err := parse.ValidateVolumeHostDir(src); err != nil { + return nil, nil, err + } + } + if err := parse.ValidateVolumeCtrDir(dest); err != nil { + return nil, nil, err + } + + cleanDest := filepath.Clean(dest) + + if strings.HasPrefix(src, "/") || strings.HasPrefix(src, ".") { + // This is not a named volume + newMount := spec.Mount{ + Destination: cleanDest, + Type: string(TypeBind), + Source: src, + Options: options, + } + if _, ok := mounts[newMount.Destination]; ok { + return nil, nil, errors.Wrapf(errDuplicateDest, newMount.Destination) + } + mounts[newMount.Destination] = newMount + } else { + // This is a named volume + newNamedVol := new(libpod.ContainerNamedVolume) + newNamedVol.Name = src + newNamedVol.Dest = cleanDest + newNamedVol.Options = options + + if _, ok := volumes[newNamedVol.Dest]; ok { + return nil, nil, errors.Wrapf(errDuplicateDest, newNamedVol.Dest) + } + volumes[newNamedVol.Dest] = newNamedVol + } + + logrus.Debugf("User mount %s:%s options %v", src, dest, options) + } + + return mounts, volumes, nil +} + +// Get mounts for container's image volumes +// TODO deferred baude +func getImageVolumes() (map[string]spec.Mount, map[string]*libpod.ContainerNamedVolume, error) { //nolint + //mounts := make(map[string]spec.Mount) + //volumes := make(map[string]*define.ContainerNamedVolume) + // + //if config.ImageVolumeType == "ignore" { + // return mounts, volumes, nil + //} + // + //for vol := range config.BuiltinImgVolumes { + // cleanDest := filepath.Clean(vol) + // logrus.Debugf("Adding image volume at %s", cleanDest) + // if config.ImageVolumeType == "tmpfs" { + // // Tmpfs image volumes are handled as mounts + // mount := spec.Mount{ + // Destination: cleanDest, + // Source: TypeTmpfs, + // Type: TypeTmpfs, + // Options: []string{"rprivate", "rw", "nodev", "exec"}, + // } + // mounts[cleanDest] = mount + // } else { + // // Anonymous volumes have no name. + // namedVolume := new(define.ContainerNamedVolume) + // namedVolume.Options = []string{"rprivate", "rw", "nodev", "exec"} + // namedVolume.Dest = cleanDest + // volumes[cleanDest] = namedVolume + // } + //} + // + //return mounts, volumes, nil + return nil, nil, nil +} + +// GetTmpfsMounts creates spec.Mount structs for user-requested tmpfs mounts +func getTmpfsMounts(mounts []string) (map[string]spec.Mount, error) { //nolint + m := make(map[string]spec.Mount) + for _, i := range mounts { + // Default options if nothing passed + var options []string + spliti := strings.Split(i, ":") + destPath := spliti[0] + if err := parse.ValidateVolumeCtrDir(spliti[0]); err != nil { + return nil, err + } + if len(spliti) > 1 { + options = strings.Split(spliti[1], ",") + } + + if _, ok := m[destPath]; ok { + return nil, errors.Wrapf(errDuplicateDest, destPath) + } + + mount := spec.Mount{ + Destination: filepath.Clean(destPath), + Type: string(TypeTmpfs), + Options: options, + Source: string(TypeTmpfs), + } + m[destPath] = mount + } + return m, nil +} + +// AddContainerInitBinary adds the init binary specified by path iff the +// container will run in a private PID namespace that is not shared with the +// host or another pre-existing container, where an init-like process is +// already running. +// +// Note that AddContainerInitBinary prepends "/dev/init" "--" to the command +// to execute the bind-mounted binary as PID 1. +// TODO this needs to be worked on to work in new env +func addContainerInitBinary(path string) (spec.Mount, error) { //nolint + mount := spec.Mount{ + Destination: "/dev/init", + Type: TypeBind, + Source: path, + Options: []string{TypeBind, "ro"}, + } + + //if path == "" { + // return mount, fmt.Errorf("please specify a path to the container-init binary") + //} + //if !config.Pid.PidMode.IsPrivate() { + // return mount, fmt.Errorf("cannot add init binary as PID 1 (PID namespace isn't private)") + //} + //if config.Systemd { + // return mount, fmt.Errorf("cannot use container-init binary with systemd") + //} + //if _, err := os.Stat(path); os.IsNotExist(err) { + // return mount, errors.Wrap(err, "container-init binary not found on the host") + //} + //config.Command = append([]string{"/dev/init", "--"}, config.Command...) + return mount, nil +} + +// Supersede existing mounts in the spec with new, user-specified mounts. +// TODO: Should we unmount subtree mounts? E.g., if /tmp/ is mounted by +// one mount, and we already have /tmp/a and /tmp/b, should we remove +// the /tmp/a and /tmp/b mounts in favor of the more general /tmp? +func SupercedeUserMounts(mounts []spec.Mount, configMount []spec.Mount) []spec.Mount { + if len(mounts) > 0 { + // If we have overlappings mounts, remove them from the spec in favor of + // the user-added volume mounts + destinations := make(map[string]bool) + for _, mount := range mounts { + destinations[path.Clean(mount.Destination)] = true + } + // Copy all mounts from spec to defaultMounts, except for + // - mounts overridden by a user supplied mount; + // - all mounts under /dev if a user supplied /dev is present; + mountDev := destinations["/dev"] + for _, mount := range configMount { + if _, ok := destinations[path.Clean(mount.Destination)]; !ok { + if mountDev && strings.HasPrefix(mount.Destination, "/dev/") { + // filter out everything under /dev if /dev is user-mounted + continue + } + + logrus.Debugf("Adding mount %s", mount.Destination) + mounts = append(mounts, mount) + } + } + return mounts + } + return configMount +} + +func InitFSMounts(mounts []spec.Mount) error { + for i, m := range mounts { + switch { + case m.Type == TypeBind: + opts, err := util.ProcessOptions(m.Options, false, m.Source) + if err != nil { + return err + } + mounts[i].Options = opts + case m.Type == TypeTmpfs && filepath.Clean(m.Destination) != "/dev": + opts, err := util.ProcessOptions(m.Options, true, "") + if err != nil { + return err + } + mounts[i].Options = opts + } + } + return nil +} diff --git a/pkg/systemd/generate/systemdgen.go b/pkg/systemd/generate/systemdgen.go index eb15d4927..73fe52c0e 100644 --- a/pkg/systemd/generate/systemdgen.go +++ b/pkg/systemd/generate/systemdgen.go @@ -31,7 +31,7 @@ type ContainerInfo struct { InfraContainer string // StopTimeout sets the timeout Podman waits before killing the container // during service stop. - StopTimeout int + StopTimeout uint // RestartPolicy of the systemd unit (e.g., no, on-failure, always). RestartPolicy string // PIDFile of the service. Required for forking services. Must point to the diff --git a/pkg/util/mountOpts.go b/pkg/util/mountOpts.go index d21800bc3..329a7c913 100644 --- a/pkg/util/mountOpts.go +++ b/pkg/util/mountOpts.go @@ -13,19 +13,17 @@ var ( ErrDupeMntOption = errors.Errorf("duplicate mount option passed") ) -// DefaultMountOptions sets default mount options for ProcessOptions. -type DefaultMountOptions struct { - Noexec bool - Nosuid bool - Nodev bool +type defaultMountOptions struct { + noexec bool + nosuid bool + nodev bool } // ProcessOptions parses the options for a bind or tmpfs mount and ensures that // they are sensible and follow convention. The isTmpfs variable controls // whether extra, tmpfs-specific options will be allowed. -// The defaults variable controls default mount options that will be set. If it -// is not included, they will be set unconditionally. -func ProcessOptions(options []string, isTmpfs bool, defaults *DefaultMountOptions) ([]string, error) { +// The sourcePath variable, if not empty, contains a bind mount source. +func ProcessOptions(options []string, isTmpfs bool, sourcePath string) ([]string, error) { var ( foundWrite, foundSize, foundProp, foundMode, foundExec, foundSuid, foundDev, foundCopyUp, foundBind, foundZ bool ) @@ -122,13 +120,17 @@ func ProcessOptions(options []string, isTmpfs bool, defaults *DefaultMountOption if !foundProp { newOptions = append(newOptions, "rprivate") } - if !foundExec && (defaults == nil || defaults.Noexec) { + defaults, err := getDefaultMountOptions(sourcePath) + if err != nil { + return nil, err + } + if !foundExec && defaults.noexec { newOptions = append(newOptions, "noexec") } - if !foundSuid && (defaults == nil || defaults.Nosuid) { + if !foundSuid && defaults.nosuid { newOptions = append(newOptions, "nosuid") } - if !foundDev && (defaults == nil || defaults.Nodev) { + if !foundDev && defaults.nodev { newOptions = append(newOptions, "nodev") } if isTmpfs && !foundCopyUp { diff --git a/pkg/util/mountOpts_linux.go b/pkg/util/mountOpts_linux.go new file mode 100644 index 000000000..3eac4dd25 --- /dev/null +++ b/pkg/util/mountOpts_linux.go @@ -0,0 +1,23 @@ +package util + +import ( + "os" + + "golang.org/x/sys/unix" +) + +func getDefaultMountOptions(path string) (defaultMountOptions, error) { + opts := defaultMountOptions{true, true, true} + if path == "" { + return opts, nil + } + var statfs unix.Statfs_t + if e := unix.Statfs(path, &statfs); e != nil { + return opts, &os.PathError{Op: "statfs", Path: path, Err: e} + } + opts.nodev = (statfs.Flags&unix.MS_NODEV == unix.MS_NODEV) + opts.noexec = (statfs.Flags&unix.MS_NOEXEC == unix.MS_NOEXEC) + opts.nosuid = (statfs.Flags&unix.MS_NOSUID == unix.MS_NOSUID) + + return opts, nil +} diff --git a/pkg/util/mountOpts_other.go b/pkg/util/mountOpts_other.go new file mode 100644 index 000000000..6a34942e5 --- /dev/null +++ b/pkg/util/mountOpts_other.go @@ -0,0 +1,7 @@ +// +build !linux + +package util + +func getDefaultMountOptions(path string) (opts defaultMountOptions, err error) { + return +} diff --git a/pkg/util/utils.go b/pkg/util/utils.go index 3e11c010a..0c055745d 100644 --- a/pkg/util/utils.go +++ b/pkg/util/utils.go @@ -609,3 +609,46 @@ func Tmpdir() string { return tmpdir } + +// ValidateSysctls validates a list of sysctl and returns it. +func ValidateSysctls(strSlice []string) (map[string]string, error) { + sysctl := make(map[string]string) + validSysctlMap := map[string]bool{ + "kernel.msgmax": true, + "kernel.msgmnb": true, + "kernel.msgmni": true, + "kernel.sem": true, + "kernel.shmall": true, + "kernel.shmmax": true, + "kernel.shmmni": true, + "kernel.shm_rmid_forced": true, + } + validSysctlPrefixes := []string{ + "net.", + "fs.mqueue.", + } + + for _, val := range strSlice { + foundMatch := false + arr := strings.Split(val, "=") + if len(arr) < 2 { + return nil, errors.Errorf("%s is invalid, sysctl values must be in the form of KEY=VALUE", val) + } + if validSysctlMap[arr[0]] { + sysctl[arr[0]] = arr[1] + continue + } + + for _, prefix := range validSysctlPrefixes { + if strings.HasPrefix(arr[0], prefix) { + sysctl[arr[0]] = arr[1] + foundMatch = true + break + } + } + if !foundMatch { + return nil, errors.Errorf("sysctl '%s' is not whitelisted", arr[0]) + } + } + return sysctl, nil +} diff --git a/pkg/util/utils_test.go b/pkg/util/utils_test.go index 0995d1e20..a9b37844e 100644 --- a/pkg/util/utils_test.go +++ b/pkg/util/utils_test.go @@ -245,3 +245,15 @@ func TestGetImageConfigMisc(t *testing.T) { _, err = GetImageConfig([]string{"BADINST testvalue"}) assert.NotNil(t, err) } + +func TestValidateSysctls(t *testing.T) { + strSlice := []string{"net.core.test1=4", "kernel.msgmax=2"} + result, _ := ValidateSysctls(strSlice) + assert.Equal(t, result["net.core.test1"], "4") +} + +func TestValidateSysctlBadSysctl(t *testing.T) { + strSlice := []string{"BLAU=BLUE", "GELB^YELLOW"} + _, err := ValidateSysctls(strSlice) + assert.Error(t, err) +} diff --git a/pkg/varlink/generate.go b/pkg/varlink/generate.go new file mode 100644 index 000000000..b3f58d4a5 --- /dev/null +++ b/pkg/varlink/generate.go @@ -0,0 +1,3 @@ +package iopodman + +//go:generate go run ../../vendor/github.com/varlink/go/cmd/varlink-go-interface-generator/main.go io.podman.varlink diff --git a/pkg/varlink/io.podman.varlink b/pkg/varlink/io.podman.varlink new file mode 100644 index 000000000..0cb95ef97 --- /dev/null +++ b/pkg/varlink/io.podman.varlink @@ -0,0 +1,1398 @@ +# Podman Service Interface and API description. The master version of this document can be found +# in the [API.md](https://github.com/containers/libpod/blob/master/API.md) file in the upstream libpod repository. +interface io.podman + +type Volume ( + name: string, + labels: [string]string, + mountPoint: string, + driver: string, + options: [string]string +) + +type NotImplemented ( + comment: string +) + +type StringResponse ( + message: string +) + +type RemoveImageResponse ( + untagged: []string, + deleted: string +) + +type LogLine ( + device: string, + parseLogType : string, + time: string, + msg: string, + cid: string +) + +# ContainerChanges describes the return struct for ListContainerChanges +type ContainerChanges ( + changed: []string, + added: []string, + deleted: []string +) + +type ImageSaveOptions ( + name: string, + format: string, + output: string, + outputType: string, + moreTags: []string, + quiet: bool, + compress: bool +) + +type VolumeCreateOpts ( + volumeName: string, + driver: string, + labels: [string]string, + options: [string]string +) + +type VolumeRemoveOpts ( + volumes: []string, + all: bool, + force: bool +) + +type Image ( + id: string, + digest: string, + digests: []string, + parentId: string, + repoTags: []string, + repoDigests: []string, + created: string, # as RFC3339 + size: int, + virtualSize: int, + containers: int, + labels: [string]string, + isParent: bool, + topLayer: string, + readOnly: bool, + history: []string +) + +# ImageHistory describes the returned structure from ImageHistory. +type ImageHistory ( + id: string, + created: string, # as RFC3339 + createdBy: string, + tags: []string, + size: int, + comment: string +) + +# Represents a single search result from SearchImages +type ImageSearchResult ( + description: string, + is_official: bool, + is_automated: bool, + registry: string, + name: string, + star_count: int +) + +type ImageSearchFilter ( + is_official: ?bool, + is_automated: ?bool, + star_count: int +) + +type AuthConfig ( + username: string, + password: string +) + +type KubePodService ( + pod: string, + service: string +) + +type Container ( + id: string, + image: string, + imageid: string, + command: []string, + createdat: string, # as RFC3339 + runningfor: string, + status: string, + ports: []ContainerPortMappings, + rootfssize: int, + rwsize: int, + names: string, + labels: [string]string, + mounts: []ContainerMount, + containerrunning: bool, + namespaces: ContainerNameSpace +) + +# ContainerStats is the return struct for the stats of a container +type ContainerStats ( + id: string, + name: string, + cpu: float, + cpu_nano: int, + system_nano: int, + mem_usage: int, + mem_limit: int, + mem_perc: float, + net_input: int, + net_output: int, + block_output: int, + block_input: int, + pids: int +) + +type PsOpts ( + all: bool, + filters: ?[]string, + last: ?int, + latest: ?bool, + noTrunc: ?bool, + pod: ?bool, + quiet: ?bool, + size: ?bool, + sort: ?string, + sync: ?bool +) + +type PsContainer ( + id: string, + image: string, + command: string, + created: string, + ports: string, + names: string, + isInfra: bool, + status: string, + state: string, + pidNum: int, + rootFsSize: int, + rwSize: int, + pod: string, + createdAt: string, + exitedAt: string, + startedAt: string, + labels: [string]string, + nsPid: string, + cgroup: string, + ipc: string, + mnt: string, + net: string, + pidNs: string, + user: string, + uts: string, + mounts: string +) + +# ContainerMount describes the struct for mounts in a container +type ContainerMount ( + destination: string, + type: string, + source: string, + options: []string +) + +# ContainerPortMappings describes the struct for portmappings in an existing container +type ContainerPortMappings ( + host_port: string, + host_ip: string, + protocol: string, + container_port: string +) + +# ContainerNamespace describes the namespace structure for an existing container +type ContainerNameSpace ( + user: string, + uts: string, + pidns: string, + pid: string, + cgroup: string, + net: string, + mnt: string, + ipc: string +) + +# InfoDistribution describes the host's distribution +type InfoDistribution ( + distribution: string, + version: string +) + +# InfoHost describes the host stats portion of PodmanInfo +type InfoHost ( + buildah_version: string, + distribution: InfoDistribution, + mem_free: int, + mem_total: int, + swap_free: int, + swap_total: int, + arch: string, + cpus: int, + hostname: string, + kernel: string, + os: string, + uptime: string, + eventlogger: string +) + +# InfoGraphStatus describes the detailed status of the storage driver +type InfoGraphStatus ( + backing_filesystem: string, + native_overlay_diff: string, + supports_d_type: string +) + +# InfoRegistry describes the host's registry information +type InfoRegistry ( + search: []string, + insecure: []string, + blocked: []string +) + +# InfoStore describes the host's storage informatoin +type InfoStore ( + containers: int, + images: int, + graph_driver_name: string, + graph_driver_options: string, + graph_root: string, + graph_status: InfoGraphStatus, + run_root: string +) + +# InfoPodman provides details on the Podman binary +type InfoPodmanBinary ( + compiler: string, + go_version: string, + podman_version: string, + git_commit: string +) + +# PodmanInfo describes the Podman host and build +type PodmanInfo ( + host: InfoHost, + registries: InfoRegistry, + store: InfoStore, + podman: InfoPodmanBinary +) + +# Sockets describes sockets location for a container +type Sockets( + container_id: string, + io_socket: string, + control_socket: string +) + +# Create is an input structure for creating containers. +# args[0] is the image name or id +# args[1-] are the new commands if changed +type Create ( + args: []string, + addHost: ?[]string, + annotation: ?[]string, + attach: ?[]string, + blkioWeight: ?string, + blkioWeightDevice: ?[]string, + capAdd: ?[]string, + capDrop: ?[]string, + cgroupParent: ?string, + cidFile: ?string, + conmonPidfile: ?string, + command: ?[]string, + cpuPeriod: ?int, + cpuQuota: ?int, + cpuRtPeriod: ?int, + cpuRtRuntime: ?int, + cpuShares: ?int, + cpus: ?float, + cpuSetCpus: ?string, + cpuSetMems: ?string, + detach: ?bool, + detachKeys: ?string, + device: ?[]string, + deviceReadBps: ?[]string, + deviceReadIops: ?[]string, + deviceWriteBps: ?[]string, + deviceWriteIops: ?[]string, + dns: ?[]string, + dnsOpt: ?[]string, + dnsSearch: ?[]string, + dnsServers: ?[]string, + entrypoint: ?string, + env: ?[]string, + envFile: ?[]string, + expose: ?[]string, + gidmap: ?[]string, + groupadd: ?[]string, + healthcheckCommand: ?string, + healthcheckInterval: ?string, + healthcheckRetries: ?int, + healthcheckStartPeriod: ?string, + healthcheckTimeout:?string, + hostname: ?string, + imageVolume: ?string, + init: ?bool, + initPath: ?string, + interactive: ?bool, + ip: ?string, + ipc: ?string, + kernelMemory: ?string, + label: ?[]string, + labelFile: ?[]string, + logDriver: ?string, + logOpt: ?[]string, + macAddress: ?string, + memory: ?string, + memoryReservation: ?string, + memorySwap: ?string, + memorySwappiness: ?int, + name: ?string, + network: ?string, + noHosts: ?bool, + oomKillDisable: ?bool, + oomScoreAdj: ?int, + overrideArch: ?string, + overrideOS: ?string, + pid: ?string, + pidsLimit: ?int, + pod: ?string, + privileged: ?bool, + publish: ?[]string, + publishAll: ?bool, + pull: ?string, + quiet: ?bool, + readonly: ?bool, + readonlytmpfs: ?bool, + restart: ?string, + rm: ?bool, + rootfs: ?bool, + securityOpt: ?[]string, + shmSize: ?string, + stopSignal: ?string, + stopTimeout: ?int, + storageOpt: ?[]string, + subuidname: ?string, + subgidname: ?string, + sysctl: ?[]string, + systemd: ?string, + tmpfs: ?[]string, + tty: ?bool, + uidmap: ?[]string, + ulimit: ?[]string, + user: ?string, + userns: ?string, + uts: ?string, + mount: ?[]string, + volume: ?[]string, + volumesFrom: ?[]string, + workDir: ?string +) + +# BuildOptions are are used to describe describe physical attributes of the build +type BuildOptions ( + addHosts: []string, + cgroupParent: string, + cpuPeriod: int, + cpuQuota: int, + cpuShares: int, + cpusetCpus: string, + cpusetMems: string, + memory: int, + memorySwap: int, + shmSize: string, + ulimit: []string, + volume: []string +) + +# BuildInfo is used to describe user input for building images +type BuildInfo ( + architecture: string, + addCapabilities: []string, + additionalTags: []string, + annotations: []string, + buildArgs: [string]string, + buildOptions: BuildOptions, + cniConfigDir: string, + cniPluginDir: string, + compression: string, + contextDir: string, + defaultsMountFilePath: string, + devices: []string, + dockerfiles: []string, + dropCapabilities: []string, + err: string, + forceRmIntermediateCtrs: bool, + iidfile: string, + label: []string, + layers: bool, + nocache: bool, + os: string, + out: string, + output: string, + outputFormat: string, + pullPolicy: string, + quiet: bool, + remoteIntermediateCtrs: bool, + reportWriter: string, + runtimeArgs: []string, + signBy: string, + squash: bool, + target: string, + transientMounts: []string +) + +# MoreResponse is a struct for when responses from varlink requires longer output +type MoreResponse ( + logs: []string, + id: string +) + +# ListPodContainerInfo is a returned struct for describing containers +# in a pod. +type ListPodContainerInfo ( + name: string, + id: string, + status: string +) + +# PodCreate is an input structure for creating pods. +# It emulates options to podman pod create. The infraCommand and +# infraImage options are currently NotSupported. +type PodCreate ( + name: string, + cgroupParent: string, + labels: [string]string, + share: []string, + infra: bool, + infraCommand: string, + infraImage: string, + publish: []string +) + +# ListPodData is the returned struct for an individual pod +type ListPodData ( + id: string, + name: string, + createdat: string, + cgroup: string, + status: string, + labels: [string]string, + numberofcontainers: string, + containersinfo: []ListPodContainerInfo +) + +type PodContainerErrorData ( + containerid: string, + reason: string +) + +# Runlabel describes the required input for container runlabel +type Runlabel( + image: string, + authfile: string, + display: bool, + name: string, + pull: bool, + label: string, + extraArgs: []string, + opts: [string]string +) + +# Event describes a libpod struct +type Event( + # TODO: make status and type a enum at some point? + # id is the container, volume, pod, image ID + id: string, + # image is the image name where applicable + image: string, + # name is the name of the pod, container, image + name: string, + # status describes the event that happened (i.e. create, remove, ...) + status: string, + # time the event happened + time: string, + # type describes object the event happened with (image, container...) + type: string +) + +type DiffInfo( + # path that is different + path: string, + # Add, Delete, Modify + changeType: string +) + +type ExecOpts( + # container name or id + name: string, + # Create pseudo tty + tty: bool, + # privileged access in container + privileged: bool, + # command to execute in container + cmd: []string, + # user to use in container + user: ?string, + # workdir to run command in container + workdir: ?string, + # slice of keyword=value environment variables + env: ?[]string, + # string of detach keys + detachKeys: ?string +) + +# GetVersion returns version and build information of the podman service +method GetVersion() -> ( + version: string, + go_version: string, + git_commit: string, + built: string, # as RFC3339 + os_arch: string, + remote_api_version: int +) + +# Reset resets Podman back to its initial state. +# Removes all Pods, Containers, Images and Volumes +method Reset() -> () + +# GetInfo returns a [PodmanInfo](#PodmanInfo) struct that describes podman and its host such as storage stats, +# build information of Podman, and system-wide registries. +method GetInfo() -> (info: PodmanInfo) + +# ListContainers returns information about all containers. +# See also [GetContainer](#GetContainer). +method ListContainers() -> (containers: []Container) + +method Ps(opts: PsOpts) -> (containers: []PsContainer) + +method GetContainersByStatus(status: []string) -> (containerS: []Container) + +method Top (nameOrID: string, descriptors: []string) -> (top: []string) + +# HealthCheckRun executes defined container's healthcheck command +# and returns the container's health status. +method HealthCheckRun (nameOrID: string) -> (healthCheckStatus: string) + +# GetContainer returns information about a single container. If a container +# with the given id doesn't exist, a [ContainerNotFound](#ContainerNotFound) +# error will be returned. See also [ListContainers](ListContainers) and +# [InspectContainer](#InspectContainer). +method GetContainer(id: string) -> (container: Container) + +# GetContainersByContext allows you to get a list of container ids depending on all, latest, or a list of +# container names. The definition of latest container means the latest by creation date. In a multi- +# user environment, results might differ from what you expect. +method GetContainersByContext(all: bool, latest: bool, args: []string) -> (containers: []string) + +# CreateContainer creates a new container from an image. It uses a [Create](#Create) type for input. +method CreateContainer(create: Create) -> (container: string) + +# InspectContainer data takes a name or ID of a container returns the inspection +# data in string format. You can then serialize the string into JSON. A [ContainerNotFound](#ContainerNotFound) +# error will be returned if the container cannot be found. See also [InspectImage](#InspectImage). +method InspectContainer(name: string) -> (container: string) + +# ListContainerProcesses takes a name or ID of a container and returns the processes +# running inside the container as array of strings. It will accept an array of string +# arguments that represent ps options. If the container cannot be found, a [ContainerNotFound](#ContainerNotFound) +# error will be returned. +# #### Example +# ~~~ +# $ varlink call -m unix:/run/podman/io.podman/io.podman.ListContainerProcesses '{"name": "135d71b9495f", "opts": []}' +# { +# "container": [ +# " UID PID PPID C STIME TTY TIME CMD", +# " 0 21220 21210 0 09:05 pts/0 00:00:00 /bin/sh", +# " 0 21232 21220 0 09:05 pts/0 00:00:00 top", +# " 0 21284 21220 0 09:05 pts/0 00:00:00 vi /etc/hosts" +# ] +# } +# ~~~ +method ListContainerProcesses(name: string, opts: []string) -> (container: []string) + +# GetContainerLogs takes a name or ID of a container and returns the logs of that container. +# If the container cannot be found, a [ContainerNotFound](#ContainerNotFound) error will be returned. +# The container logs are returned as an array of strings. GetContainerLogs will honor the streaming +# capability of varlink if the client invokes it. +method GetContainerLogs(name: string) -> (container: []string) + +method GetContainersLogs(names: []string, follow: bool, latest: bool, since: string, tail: int, timestamps: bool) -> (log: LogLine) + +# ListContainerChanges takes a name or ID of a container and returns changes between the container and +# its base image. It returns a struct of changed, deleted, and added path names. +method ListContainerChanges(name: string) -> (container: ContainerChanges) + +# ExportContainer creates an image from a container. It takes the name or ID of a container and a +# path representing the target tarfile. If the container cannot be found, a [ContainerNotFound](#ContainerNotFound) +# error will be returned. +# The return value is the written tarfile. +# #### Example +# ~~~ +# $ varlink call -m unix:/run/podman/io.podman/io.podman.ExportContainer '{"name": "flamboyant_payne", "path": "/tmp/payne.tar" }' +# { +# "tarfile": "/tmp/payne.tar" +# } +# ~~~ +method ExportContainer(name: string, path: string) -> (tarfile: string) + +# GetContainerStats takes the name or ID of a container and returns a single ContainerStats structure which +# contains attributes like memory and cpu usage. If the container cannot be found, a +# [ContainerNotFound](#ContainerNotFound) error will be returned. If the container is not running, a [NoContainerRunning](#NoContainerRunning) +# error will be returned +# #### Example +# ~~~ +# $ varlink call -m unix:/run/podman/io.podman/io.podman.GetContainerStats '{"name": "c33e4164f384"}' +# { +# "container": { +# "block_input": 0, +# "block_output": 0, +# "cpu": 2.571123918839990154678e-08, +# "cpu_nano": 49037378, +# "id": "c33e4164f384aa9d979072a63319d66b74fd7a128be71fa68ede24f33ec6cfee", +# "mem_limit": 33080606720, +# "mem_perc": 2.166828456524753747370e-03, +# "mem_usage": 716800, +# "name": "competent_wozniak", +# "net_input": 768, +# "net_output": 5910, +# "pids": 1, +# "system_nano": 10000000 +# } +# } +# ~~~ +method GetContainerStats(name: string) -> (container: ContainerStats) + +# GetContainerStatsWithHistory takes a previous set of container statistics and uses libpod functions +# to calculate the containers statistics based on current and previous measurements. +method GetContainerStatsWithHistory(previousStats: ContainerStats) -> (container: ContainerStats) + +# This method has not be implemented yet. +# method ResizeContainerTty() -> (notimplemented: NotImplemented) + +# StartContainer starts a created or stopped container. It takes the name or ID of container. It returns +# the container ID once started. If the container cannot be found, a [ContainerNotFound](#ContainerNotFound) +# error will be returned. See also [CreateContainer](#CreateContainer). +method StartContainer(name: string) -> (container: string) + +# StopContainer stops a container given a timeout. It takes the name or ID of a container as well as a +# timeout value. The timeout value the time before a forcible stop to the container is applied. It +# returns the container ID once stopped. If the container cannot be found, a [ContainerNotFound](#ContainerNotFound) +# error will be returned instead. See also [KillContainer](KillContainer). +# #### Error +# ~~~ +# $ varlink call -m unix:/run/podman/io.podman/io.podman.StopContainer '{"name": "135d71b9495f", "timeout": 5}' +# { +# "container": "135d71b9495f7c3967f536edad57750bfdb569336cd107d8aabab45565ffcfb6" +# } +# ~~~ +method StopContainer(name: string, timeout: int) -> (container: string) + +# InitContainer initializes the given container. It accepts a container name or +# ID, and will initialize the container matching that ID if possible, and error +# if not. Containers can only be initialized when they are in the Created or +# Exited states. Initialization prepares a container to be started, but does not +# start the container. It is intended to be used to debug a container's state +# prior to starting it. +method InitContainer(name: string) -> (container: string) + +# RestartContainer will restart a running container given a container name or ID and timeout value. The timeout +# value is the time before a forcible stop is used to stop the container. If the container cannot be found by +# name or ID, a [ContainerNotFound](#ContainerNotFound) error will be returned; otherwise, the ID of the +# container will be returned. +method RestartContainer(name: string, timeout: int) -> (container: string) + +# KillContainer takes the name or ID of a container as well as a signal to be applied to the container. Once the +# container has been killed, the container's ID is returned. If the container cannot be found, a +# [ContainerNotFound](#ContainerNotFound) error is returned. See also [StopContainer](StopContainer). +method KillContainer(name: string, signal: int) -> (container: string) + +# This method has not be implemented yet. +# method UpdateContainer() -> (notimplemented: NotImplemented) + +# This method has not be implemented yet. +# method RenameContainer() -> (notimplemented: NotImplemented) + +# PauseContainer takes the name or ID of container and pauses it. If the container cannot be found, +# a [ContainerNotFound](#ContainerNotFound) error will be returned; otherwise the ID of the container is returned. +# See also [UnpauseContainer](#UnpauseContainer). +method PauseContainer(name: string) -> (container: string) + +# UnpauseContainer takes the name or ID of container and unpauses a paused container. If the container cannot be +# found, a [ContainerNotFound](#ContainerNotFound) error will be returned; otherwise the ID of the container is returned. +# See also [PauseContainer](#PauseContainer). +method UnpauseContainer(name: string) -> (container: string) + +# Attach takes the name or ID of a container and sets up the ability to remotely attach to its console. The start +# bool is whether you wish to start the container in question first. +method Attach(name: string, detachKeys: string, start: bool) -> () + +method AttachControl(name: string) -> () + +# GetAttachSockets takes the name or ID of an existing container. It returns file paths for two sockets needed +# to properly communicate with a container. The first is the actual I/O socket that the container uses. The +# second is a "control" socket where things like resizing the TTY events are sent. If the container cannot be +# found, a [ContainerNotFound](#ContainerNotFound) error will be returned. +# #### Example +# ~~~ +# $ varlink call -m unix:/run/io.podman/io.podman.GetAttachSockets '{"name": "b7624e775431219161"}' +# { +# "sockets": { +# "container_id": "b7624e7754312191613245ce1a46844abee60025818fe3c3f3203435623a1eca", +# "control_socket": "/var/lib/containers/storage/overlay-containers/b7624e7754312191613245ce1a46844abee60025818fe3c3f3203435623a1eca/userdata/ctl", +# "io_socket": "/var/run/libpod/socket/b7624e7754312191613245ce1a46844abee60025818fe3c3f3203435623a1eca/attach" +# } +# } +# ~~~ +method GetAttachSockets(name: string) -> (sockets: Sockets) + +# WaitContainer takes the name or ID of a container and waits the given interval in milliseconds until the container +# stops. Upon stopping, the return code of the container is returned. If the container container cannot be found by ID +# or name, a [ContainerNotFound](#ContainerNotFound) error is returned. +method WaitContainer(name: string, interval: int) -> (exitcode: int) + +# RemoveContainer requires the name or ID of a container as well as a boolean that +# indicates whether a container should be forcefully removed (e.g., by stopping it), and a boolean +# indicating whether to remove builtin volumes. Upon successful removal of the +# container, its ID is returned. If the +# container cannot be found by name or ID, a [ContainerNotFound](#ContainerNotFound) error will be returned. +# See also [EvictContainer](EvictContainer). +# #### Example +# ~~~ +# $ varlink call -m unix:/run/podman/io.podman/io.podman.RemoveContainer '{"name": "62f4fd98cb57"}' +# { +# "container": "62f4fd98cb57f529831e8f90610e54bba74bd6f02920ffb485e15376ed365c20" +# } +# ~~~ +method RemoveContainer(name: string, force: bool, removeVolumes: bool) -> (container: string) + +# EvictContainer requires the name or ID of a container as well as a boolean that +# indicates to remove builtin volumes. Upon successful eviction of the container, +# its ID is returned. If the container cannot be found by name or ID, +# a [ContainerNotFound](#ContainerNotFound) error will be returned. +# See also [RemoveContainer](RemoveContainer). +# #### Example +# ~~~ +# $ varlink call -m unix:/run/podman/io.podman/io.podman.EvictContainer '{"name": "62f4fd98cb57"}' +# { +# "container": "62f4fd98cb57f529831e8f90610e54bba74bd6f02920ffb485e15376ed365c20" +# } +# ~~~ +method EvictContainer(name: string, removeVolumes: bool) -> (container: string) + +# DeleteStoppedContainers will delete all containers that are not running. It will return a list the deleted +# container IDs. See also [RemoveContainer](RemoveContainer). +# #### Example +# ~~~ +# $ varlink call -m unix:/run/podman/io.podman/io.podman.DeleteStoppedContainers +# { +# "containers": [ +# "451410b931d00def8aa9b4f8084e4d4a39e5e04ea61f358cf53a5cf95afcdcee", +# "8b60f754a3e01389494a9581ade97d35c2765b6e2f19acd2d3040c82a32d1bc0", +# "cf2e99d4d3cad6073df199ed32bbe64b124f3e1aba6d78821aa8460e70d30084", +# "db901a329587312366e5ecff583d08f0875b4b79294322df67d90fc6eed08fc1" +# ] +# } +# ~~~ +method DeleteStoppedContainers() -> (containers: []string) + +# ListImages returns information about the images that are currently in storage. +# See also [InspectImage](#InspectImage). +method ListImages() -> (images: []Image) + +# ListImagesWithFilters returns information about the images that are currently in storage +# after one or more filters has been applied. +# See also [InspectImage](#InspectImage). +method ListImagesWithFilters(filters: []string) -> (images: []Image) + +# GetImage returns information about a single image in storage. +# If the image caGetImage returns be found, [ImageNotFound](#ImageNotFound) will be returned. +method GetImage(id: string) -> (image: Image) + +# BuildImage takes a [BuildInfo](#BuildInfo) structure and builds an image. At a minimum, you must provide the +# contextDir tarball path, the 'dockerfiles' path, and 'output' option in the BuildInfo structure. The 'output' +# options is the name of the of the resulting build. It will return a [MoreResponse](#MoreResponse) structure +# that contains the build logs and resulting image ID. +# #### Example +# ~~~ +# $ sudo varlink call -m unix:///run/podman/io.podman/io.podman.BuildImage '{"build":{"contextDir":"/tmp/t/context.tar","dockerfiles":["Dockerfile"], "output":"foobar"}}' +# { +# "image": { +# "id": "", +# "logs": [ +# "STEP 1: FROM alpine\n" +# ] +# } +# } +# { +# "image": { +# "id": "", +# "logs": [ +# "STEP 2: COMMIT foobar\n" +# ] +# } +# } +# { +# "image": { +# "id": "", +# "logs": [ +# "b7b28af77ffec6054d13378df4fdf02725830086c7444d9c278af25312aa39b9\n" +# ] +# } +# } +# { +# "image": { +# "id": "b7b28af77ffec6054d13378df4fdf02725830086c7444d9c278af25312aa39b9", +# "logs": [] +# } +# } +# ~~~ +method BuildImage(build: BuildInfo) -> (image: MoreResponse) + +# This function is not implemented yet. +# method CreateImage() -> (notimplemented: NotImplemented) + +# InspectImage takes the name or ID of an image and returns a string representation of data associated with the +#image. You must serialize the string into JSON to use it further. An [ImageNotFound](#ImageNotFound) error will +# be returned if the image cannot be found. +method InspectImage(name: string) -> (image: string) + +# HistoryImage takes the name or ID of an image and returns information about its history and layers. The returned +# history is in the form of an array of ImageHistory structures. If the image cannot be found, an +# [ImageNotFound](#ImageNotFound) error is returned. +method HistoryImage(name: string) -> (history: []ImageHistory) + +# PushImage takes two input arguments: the name or ID of an image, the fully-qualified destination name of the image, +# It will return an [ImageNotFound](#ImageNotFound) error if +# the image cannot be found in local storage; otherwise it will return a [MoreResponse](#MoreResponse) +method PushImage(name: string, tag: string, compress: bool, format: string, removeSignatures: bool, signBy: string) -> (reply: MoreResponse) + +# TagImage takes the name or ID of an image in local storage as well as the desired tag name. If the image cannot +# be found, an [ImageNotFound](#ImageNotFound) error will be returned; otherwise, the ID of the image is returned on success. +method TagImage(name: string, tagged: string) -> (image: string) + +# UntagImage takes the name or ID of an image in local storage as well as the +# tag name to be removed. If the image cannot be found, an +# [ImageNotFound](#ImageNotFound) error will be returned; otherwise, the ID of +# the image is returned on success. +method UntagImage(name: string, tag: string) -> (image: string) + +# RemoveImage takes the name or ID of an image as well as a boolean that determines if containers using that image +# should be deleted. If the image cannot be found, an [ImageNotFound](#ImageNotFound) error will be returned. The +# ID of the removed image is returned when complete. See also [DeleteUnusedImages](DeleteUnusedImages). +# #### Example +# ~~~ +# varlink call -m unix:/run/podman/io.podman/io.podman.RemoveImage '{"name": "registry.fedoraproject.org/fedora", "force": true}' +# { +# "image": "426866d6fa419873f97e5cbd320eeb22778244c1dfffa01c944db3114f55772e" +# } +# ~~~ +method RemoveImage(name: string, force: bool) -> (image: string) + +# RemoveImageWithResponse takes the name or ID of an image as well as a boolean that determines if containers using that image +# should be deleted. If the image cannot be found, an [ImageNotFound](#ImageNotFound) error will be returned. The response is +# in the form of a RemoveImageResponse . +method RemoveImageWithResponse(name: string, force: bool) -> (response: RemoveImageResponse) + +# SearchImages searches available registries for images that contain the +# contents of "query" in their name. If "limit" is given, limits the amount of +# search results per registry. +method SearchImages(query: string, limit: ?int, filter: ImageSearchFilter) -> (results: []ImageSearchResult) + +# DeleteUnusedImages deletes any images not associated with a container. The IDs of the deleted images are returned +# in a string array. +# #### Example +# ~~~ +# $ varlink call -m unix:/run/podman/io.podman/io.podman.DeleteUnusedImages +# { +# "images": [ +# "166ea6588079559c724c15223f52927f514f73dd5c5cf2ae2d143e3b2e6e9b52", +# "da86e6ba6ca197bf6bc5e9d900febd906b133eaa4750e6bed647b0fbe50ed43e", +# "3ef70f7291f47dfe2b82931a993e16f5a44a0e7a68034c3e0e086d77f5829adc", +# "59788edf1f3e78cd0ebe6ce1446e9d10788225db3dedcfd1a59f764bad2b2690" +# ] +# } +# ~~~ +method DeleteUnusedImages() -> (images: []string) + +# Commit, creates an image from an existing container. It requires the name or +# ID of the container as well as the resulting image name. Optionally, you can define an author and message +# to be added to the resulting image. You can also define changes to the resulting image for the following +# attributes: _CMD, ENTRYPOINT, ENV, EXPOSE, LABEL, ONBUILD, STOPSIGNAL, USER, VOLUME, and WORKDIR_. To pause the +# container while it is being committed, pass a _true_ bool for the pause argument. If the container cannot +# be found by the ID or name provided, a (ContainerNotFound)[#ContainerNotFound] error will be returned; otherwise, +# the resulting image's ID will be returned as a string inside a MoreResponse. +method Commit(name: string, image_name: string, changes: []string, author: string, message: string, pause: bool, manifestType: string) -> (reply: MoreResponse) + +# ImportImage imports an image from a source (like tarball) into local storage. The image can have additional +# descriptions added to it using the message and changes options. See also [ExportImage](ExportImage). +method ImportImage(source: string, reference: string, message: string, changes: []string, delete: bool) -> (image: string) + +# ExportImage takes the name or ID of an image and exports it to a destination like a tarball. There is also +# a boolean option to force compression. It also takes in a string array of tags to be able to save multiple +# tags of the same image to a tarball (each tag should be of the form <image>:<tag>). Upon completion, the ID +# of the image is returned. If the image cannot be found in local storage, an [ImageNotFound](#ImageNotFound) +# error will be returned. See also [ImportImage](ImportImage). +method ExportImage(name: string, destination: string, compress: bool, tags: []string) -> (image: string) + +# PullImage pulls an image from a repository to local storage. After a successful pull, the image id and logs +# are returned as a [MoreResponse](#MoreResponse). This connection also will handle a WantsMores request to send +# status as it occurs. +method PullImage(name: string, creds: AuthConfig) -> (reply: MoreResponse) + +# CreatePod creates a new empty pod. It uses a [PodCreate](#PodCreate) type for input. +# On success, the ID of the newly created pod will be returned. +# #### Example +# ~~~ +# $ varlink call unix:/run/podman/io.podman/io.podman.CreatePod '{"create": {"name": "test"}}' +# { +# "pod": "b05dee7bd4ccfee688099fe1588a7a898d6ddd6897de9251d4671c9b0feacb2a" +# } +# +# $ varlink call unix:/run/podman/io.podman/io.podman.CreatePod '{"create": {"infra": true, "share": ["ipc", "net", "uts"]}}' +# { +# "pod": "d7697449a8035f613c1a8891286502aca68fff7d5d49a85279b3bda229af3b28" +# } +# ~~~ +method CreatePod(create: PodCreate) -> (pod: string) + +# ListPods returns a list of pods in no particular order. They are +# returned as an array of ListPodData structs. See also [GetPod](#GetPod). +# #### Example +# ~~~ +# $ varlink call -m unix:/run/podman/io.podman/io.podman.ListPods +# { +# "pods": [ +# { +# "cgroup": "machine.slice", +# "containersinfo": [ +# { +# "id": "00c130a45de0411f109f1a0cfea2e298df71db20fa939de5cab8b2160a36be45", +# "name": "1840835294cf-infra", +# "status": "running" +# }, +# { +# "id": "49a5cce72093a5ca47c6de86f10ad7bb36391e2d89cef765f807e460865a0ec6", +# "name": "upbeat_murdock", +# "status": "running" +# } +# ], +# "createdat": "2018-12-07 13:10:15.014139258 -0600 CST", +# "id": "1840835294cf076a822e4e12ba4152411f131bd869e7f6a4e8b16df9b0ea5c7f", +# "name": "foobar", +# "numberofcontainers": "2", +# "status": "Running" +# }, +# { +# "cgroup": "machine.slice", +# "containersinfo": [ +# { +# "id": "1ca4b7bbba14a75ba00072d4b705c77f3df87db0109afaa44d50cb37c04a477e", +# "name": "784306f655c6-infra", +# "status": "running" +# } +# ], +# "createdat": "2018-12-07 13:09:57.105112457 -0600 CST", +# "id": "784306f655c6200aea321dd430ba685e9b2cc1f7d7528a72f3ff74ffb29485a2", +# "name": "nostalgic_pike", +# "numberofcontainers": "1", +# "status": "Running" +# } +# ] +# } +# ~~~ +method ListPods() -> (pods: []ListPodData) + +# GetPod takes a name or ID of a pod and returns single [ListPodData](#ListPodData) +# structure. A [PodNotFound](#PodNotFound) error will be returned if the pod cannot be found. +# See also [ListPods](ListPods). +# #### Example +# ~~~ +# $ varlink call -m unix:/run/podman/io.podman/io.podman.GetPod '{"name": "foobar"}' +# { +# "pod": { +# "cgroup": "machine.slice", +# "containersinfo": [ +# { +# "id": "00c130a45de0411f109f1a0cfea2e298df71db20fa939de5cab8b2160a36be45", +# "name": "1840835294cf-infra", +# "status": "running" +# }, +# { +# "id": "49a5cce72093a5ca47c6de86f10ad7bb36391e2d89cef765f807e460865a0ec6", +# "name": "upbeat_murdock", +# "status": "running" +# } +# ], +# "createdat": "2018-12-07 13:10:15.014139258 -0600 CST", +# "id": "1840835294cf076a822e4e12ba4152411f131bd869e7f6a4e8b16df9b0ea5c7f", +# "name": "foobar", +# "numberofcontainers": "2", +# "status": "Running" +# } +# } +# ~~~ +method GetPod(name: string) -> (pod: ListPodData) + +# InspectPod takes the name or ID of an image and returns a string representation of data associated with the +# pod. You must serialize the string into JSON to use it further. A [PodNotFound](#PodNotFound) error will +# be returned if the pod cannot be found. +method InspectPod(name: string) -> (pod: string) + +# StartPod starts containers in a pod. It takes the name or ID of pod. If the pod cannot be found, a [PodNotFound](#PodNotFound) +# error will be returned. Containers in a pod are started independently. If there is an error starting one container, the ID of those containers +# will be returned in a list, along with the ID of the pod in a [PodContainerError](#PodContainerError). +# If the pod was started with no errors, the pod ID is returned. +# See also [CreatePod](#CreatePod). +# #### Example +# ~~~ +# $ varlink call -m unix:/run/podman/io.podman/io.podman.StartPod '{"name": "135d71b9495f"}' +# { +# "pod": "135d71b9495f7c3967f536edad57750bfdb569336cd107d8aabab45565ffcfb6", +# } +# ~~~ +method StartPod(name: string) -> (pod: string) + +# StopPod stops containers in a pod. It takes the name or ID of a pod and a timeout. +# If the pod cannot be found, a [PodNotFound](#PodNotFound) error will be returned instead. +# Containers in a pod are stopped independently. If there is an error stopping one container, the ID of those containers +# will be returned in a list, along with the ID of the pod in a [PodContainerError](#PodContainerError). +# If the pod was stopped with no errors, the pod ID is returned. +# See also [KillPod](KillPod). +# #### Example +# ~~~ +# $ varlink call -m unix:/run/podman/io.podman/io.podman.StopPod '{"name": "135d71b9495f"}' +# { +# "pod": "135d71b9495f7c3967f536edad57750bfdb569336cd107d8aabab45565ffcfb6" +# } +# ~~~ +method StopPod(name: string, timeout: int) -> (pod: string) + +# RestartPod will restart containers in a pod given a pod name or ID. Containers in +# the pod that are running will be stopped, then all stopped containers will be run. +# If the pod cannot be found by name or ID, a [PodNotFound](#PodNotFound) error will be returned. +# Containers in a pod are restarted independently. If there is an error restarting one container, the ID of those containers +# will be returned in a list, along with the ID of the pod in a [PodContainerError](#PodContainerError). +# If the pod was restarted with no errors, the pod ID is returned. +# #### Example +# ~~~ +# $ varlink call -m unix:/run/podman/io.podman/io.podman.RestartPod '{"name": "135d71b9495f"}' +# { +# "pod": "135d71b9495f7c3967f536edad57750bfdb569336cd107d8aabab45565ffcfb6" +# } +# ~~~ +method RestartPod(name: string) -> (pod: string) + +# KillPod takes the name or ID of a pod as well as a signal to be applied to the pod. If the pod cannot be found, a +# [PodNotFound](#PodNotFound) error is returned. +# Containers in a pod are killed independently. If there is an error killing one container, the ID of those containers +# will be returned in a list, along with the ID of the pod in a [PodContainerError](#PodContainerError). +# If the pod was killed with no errors, the pod ID is returned. +# See also [StopPod](StopPod). +# #### Example +# ~~~ +# $ varlink call -m unix:/run/podman/io.podman/io.podman.KillPod '{"name": "foobar", "signal": 15}' +# { +# "pod": "1840835294cf076a822e4e12ba4152411f131bd869e7f6a4e8b16df9b0ea5c7f" +# } +# ~~~ +method KillPod(name: string, signal: int) -> (pod: string) + +# PausePod takes the name or ID of a pod and pauses the running containers associated with it. If the pod cannot be found, +# a [PodNotFound](#PodNotFound) error will be returned. +# Containers in a pod are paused independently. If there is an error pausing one container, the ID of those containers +# will be returned in a list, along with the ID of the pod in a [PodContainerError](#PodContainerError). +# If the pod was paused with no errors, the pod ID is returned. +# See also [UnpausePod](#UnpausePod). +# #### Example +# ~~~ +# $ varlink call -m unix:/run/podman/io.podman/io.podman.PausePod '{"name": "foobar"}' +# { +# "pod": "1840835294cf076a822e4e12ba4152411f131bd869e7f6a4e8b16df9b0ea5c7f" +# } +# ~~~ +method PausePod(name: string) -> (pod: string) + +# UnpausePod takes the name or ID of a pod and unpauses the paused containers associated with it. If the pod cannot be +# found, a [PodNotFound](#PodNotFound) error will be returned. +# Containers in a pod are unpaused independently. If there is an error unpausing one container, the ID of those containers +# will be returned in a list, along with the ID of the pod in a [PodContainerError](#PodContainerError). +# If the pod was unpaused with no errors, the pod ID is returned. +# See also [PausePod](#PausePod). +# #### Example +# ~~~ +# $ varlink call -m unix:/run/podman/io.podman/io.podman.UnpausePod '{"name": "foobar"}' +# { +# "pod": "1840835294cf076a822e4e12ba4152411f131bd869e7f6a4e8b16df9b0ea5c7f" +# } +# ~~~ +method UnpausePod(name: string) -> (pod: string) + +# RemovePod takes the name or ID of a pod as well a boolean representing whether a running +# container in the pod can be stopped and removed. If a pod has containers associated with it, and force is not true, +# an error will occur. +# If the pod cannot be found by name or ID, a [PodNotFound](#PodNotFound) error will be returned. +# Containers in a pod are removed independently. If there is an error removing any container, the ID of those containers +# will be returned in a list, along with the ID of the pod in a [PodContainerError](#PodContainerError). +# If the pod was removed with no errors, the pod ID is returned. +# #### Example +# ~~~ +# $ varlink call -m unix:/run/podman/io.podman/io.podman.RemovePod '{"name": "62f4fd98cb57", "force": "true"}' +# { +# "pod": "62f4fd98cb57f529831e8f90610e54bba74bd6f02920ffb485e15376ed365c20" +# } +# ~~~ +method RemovePod(name: string, force: bool) -> (pod: string) + +# This method has not be implemented yet. +# method WaitPod() -> (notimplemented: NotImplemented) + +method TopPod(pod: string, latest: bool, descriptors: []string) -> (stats: []string) + +# GetPodStats takes the name or ID of a pod and returns a pod name and slice of ContainerStats structure which +# contains attributes like memory and cpu usage. If the pod cannot be found, a [PodNotFound](#PodNotFound) +# error will be returned. If the pod has no running containers associated with it, a [NoContainerRunning](#NoContainerRunning) +# error will be returned. +# #### Example +# ~~~ +# $ varlink call unix:/run/podman/io.podman/io.podman.GetPodStats '{"name": "7f62b508b6f12b11d8fe02e"}' +# { +# "containers": [ +# { +# "block_input": 0, +# "block_output": 0, +# "cpu": 2.833470544016107524276e-08, +# "cpu_nano": 54363072, +# "id": "a64b51f805121fe2c5a3dc5112eb61d6ed139e3d1c99110360d08b58d48e4a93", +# "mem_limit": 12276146176, +# "mem_perc": 7.974359265237864966003e-03, +# "mem_usage": 978944, +# "name": "quirky_heisenberg", +# "net_input": 866, +# "net_output": 7388, +# "pids": 1, +# "system_nano": 20000000 +# } +# ], +# "pod": "7f62b508b6f12b11d8fe02e0db4de6b9e43a7d7699b33a4fc0d574f6e82b4ebd" +# } +# ~~~ +method GetPodStats(name: string) -> (pod: string, containers: []ContainerStats) + +# GetPodsByStatus searches for pods whose status is included in statuses +method GetPodsByStatus(statuses: []string) -> (pods: []string) + +# ImageExists talks a full or partial image ID or name and returns an int as to whether +# the image exists in local storage. An int result of 0 means the image does exist in +# local storage; whereas 1 indicates the image does not exists in local storage. +# #### Example +# ~~~ +# $ varlink call -m unix:/run/podman/io.podman/io.podman.ImageExists '{"name": "imageddoesntexist"}' +# { +# "exists": 1 +# } +# ~~~ +method ImageExists(name: string) -> (exists: int) + +# ImageTree returns the image tree for the provided image name or ID +# #### Example +# ~~~ +# $ varlink call -m unix:/run/podman/io.podman/io.podman.ImageTree '{"name": "alpine"}' +# { +# "tree": "Image ID: e7d92cdc71fe\nTags: [docker.io/library/alpine:latest]\nSize: 5.861MB\nImage Layers\nāāā ID: 5216338b40a7 Size: 5.857MB Top Layer of: [docker.io/library/alpine:latest]\n" +# } +# ~~~ +method ImageTree(name: string, whatRequires: bool) -> (tree: string) + +# ContainerExists takes a full or partial container ID or name and returns an int as to +# whether the container exists in local storage. A result of 0 means the container does +# exists; whereas a result of 1 means it could not be found. +# #### Example +# ~~~ +# $ varlink call -m unix:/run/podman/io.podman/io.podman.ContainerExists '{"name": "flamboyant_payne"}'{ +# "exists": 0 +# } +# ~~~ +method ContainerExists(name: string) -> (exists: int) + +# ContainerCheckPoint performs a checkpopint on a container by its name or full/partial container +# ID. On successful checkpoint, the id of the checkpointed container is returned. +method ContainerCheckpoint(name: string, keep: bool, leaveRunning: bool, tcpEstablished: bool) -> (id: string) + +# ContainerRestore restores a container that has been checkpointed. The container to be restored can +# be identified by its name or full/partial container ID. A successful restore will result in the return +# of the container's ID. +method ContainerRestore(name: string, keep: bool, tcpEstablished: bool) -> (id: string) + +# ContainerRunlabel runs executes a command as described by a given container image label. +method ContainerRunlabel(runlabel: Runlabel) -> () + +# ExecContainer executes a command in the given container. +method ExecContainer(opts: ExecOpts) -> () + +# ListContainerMounts gathers all the mounted container mount points and returns them as an array +# of strings +# #### Example +# ~~~ +# $ varlink call unix:/run/podman/io.podman/io.podman.ListContainerMounts +# { +# "mounts": { +# "04e4c255269ed2545e7f8bd1395a75f7949c50c223415c00c1d54bfa20f3b3d9": "/var/lib/containers/storage/overlay/a078925828f57e20467ca31cfca8a849210d21ec7e5757332b72b6924f441c17/merged", +# "1d58c319f9e881a644a5122ff84419dccf6d138f744469281446ab243ef38924": "/var/lib/containers/storage/overlay/948fcf93f8cb932f0f03fd52e3180a58627d547192ffe3b88e0013b98ddcd0d2/merged" +# } +# } +# ~~~ +method ListContainerMounts() -> (mounts: [string]string) + +# MountContainer mounts a container by name or full/partial ID. Upon a successful mount, the destination +# mount is returned as a string. +# #### Example +# ~~~ +# $ varlink call -m unix:/run/podman/io.podman/io.podman.MountContainer '{"name": "jolly_shannon"}'{ +# "path": "/var/lib/containers/storage/overlay/419eeb04e783ea159149ced67d9fcfc15211084d65e894792a96bedfae0470ca/merged" +# } +# ~~~ +method MountContainer(name: string) -> (path: string) + +# UnmountContainer umounts a container by its name or full/partial container ID. +# #### Example +# ~~~ +# $ varlink call -m unix:/run/podman/io.podman/io.podman.UnmountContainer '{"name": "jolly_shannon", "force": false}' +# {} +# ~~~ +method UnmountContainer(name: string, force: bool) -> () + +# ImagesPrune removes all unused images from the local store. Upon successful pruning, +# the IDs of the removed images are returned. +method ImagesPrune(all: bool, filter: []string) -> (pruned: []string) + +# 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(name: string, service: bool) -> (pod: KubePodService) + +# 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) + +# ContainerConfig returns a container's config in string form. This call is for +# development of Podman only and generally should not be used. +method ContainerConfig(name: string) -> (config: string) + +# ContainerArtifacts returns a container's artifacts in string form. This call is for +# development of Podman only and generally should not be used. +method ContainerArtifacts(name: string, artifactName: string) -> (config: string) + +# ContainerInspectData returns a container's inspect data in string form. This call is for +# development of Podman only and generally should not be used. +method ContainerInspectData(name: string, size: bool) -> (config: string) + +# ContainerStateData returns a container's state config in string form. This call is for +# development of Podman only and generally should not be used. +method ContainerStateData(name: string) -> (config: string) + +# PodStateData returns inspectr level information of a given pod in string form. This call is for +# development of Podman only and generally should not be used. +method PodStateData(name: string) -> (config: string) + +# This call is for the development of Podman only and should not be used. +method CreateFromCC(in: []string) -> (id: string) + +# Spec returns the oci spec for a container. This call is for development of Podman only and generally should not be used. +method Spec(name: string) -> (config: string) + +# Sendfile allows a remote client to send a file to the host +method SendFile(type: string, length: int) -> (file_handle: string) + +# ReceiveFile allows the host to send a remote client a file +method ReceiveFile(path: string, delete: bool) -> (len: int) + +# VolumeCreate creates a volume on a remote host +method VolumeCreate(options: VolumeCreateOpts) -> (volumeName: string) + +# VolumeRemove removes a volume on a remote host +method VolumeRemove(options: VolumeRemoveOpts) -> (successes: []string, failures: [string]string) + +# GetVolumes gets slice of the volumes on a remote host +method GetVolumes(args: []string, all: bool) -> (volumes: []Volume) + +# InspectVolume inspects a single volume. Returns inspect JSON in the form of a +# string. +method InspectVolume(name: string) -> (volume: string) + +# VolumesPrune removes unused volumes on the host +method VolumesPrune() -> (prunedNames: []string, prunedErrors: []string) + +# ImageSave allows you to save an image from the local image storage to a tarball +method ImageSave(options: ImageSaveOptions) -> (reply: MoreResponse) + +# GetPodsByContext allows you to get a list pod ids depending on all, latest, or a list of +# pod names. The definition of latest pod means the latest by creation date. In a multi- +# user environment, results might differ from what you expect. +method GetPodsByContext(all: bool, latest: bool, args: []string) -> (pods: []string) + +# LoadImage allows you to load an image into local storage from a tarball. +method LoadImage(name: string, inputFile: string, quiet: bool, deleteFile: bool) -> (reply: MoreResponse) + +# GetEvents returns known libpod events filtered by the options provided. +method GetEvents(filter: []string, since: string, until: string) -> (events: Event) + +# Diff returns a diff between libpod objects +method Diff(name: string) -> (diffs: []DiffInfo) + +# GetLayersMapWithImageInfo is for the development of Podman and should not be used. +method GetLayersMapWithImageInfo() -> (layerMap: string) + +# BuildImageHierarchyMap is for the development of Podman and should not be used. +method BuildImageHierarchyMap(name: string) -> (imageInfo: string) + +# ImageNotFound means the image could not be found by the provided name or ID in local storage. +error ImageNotFound (id: string, reason: string) + +# ContainerNotFound means the container could not be found by the provided name or ID in local storage. +error ContainerNotFound (id: string, reason: string) + +# NoContainerRunning means none of the containers requested are running in a command that requires a running container. +error NoContainerRunning () + +# PodNotFound means the pod could not be found by the provided name or ID in local storage. +error PodNotFound (name: string, reason: string) + +# VolumeNotFound means the volume could not be found by the name or ID in local storage. +error VolumeNotFound (id: string, reason: string) + +# PodContainerError means a container associated with a pod failed to perform an operation. It contains +# a container ID of the container that failed. +error PodContainerError (podname: string, errors: []PodContainerErrorData) + +# NoContainersInPod means a pod has no containers on which to perform the operation. It contains +# the pod ID. +error NoContainersInPod (name: string) + +# InvalidState indicates that a container or pod was in an improper state for the requested operation +error InvalidState (id: string, reason: string) + +# ErrorOccurred is a generic error for an error that occurs during the execution. The actual error message +# is includes as part of the error's text. +error ErrorOccurred (reason: string) + +# RuntimeErrors generally means a runtime could not be found or gotten. +error RuntimeError (reason: string) + +# The Podman endpoint requires that you use a streaming connection. +error WantsMoreRequired (reason: string) + +# Container is already stopped +error ErrCtrStopped (id: string) + +# This function requires CGroupsV2 to run in rootless mode. +error ErrRequiresCgroupsV2ForRootless(reason: string) diff --git a/pkg/varlinkapi/attach.go b/pkg/varlinkapi/attach.go index 5beca3c6f..34f351669 100644 --- a/pkg/varlinkapi/attach.go +++ b/pkg/varlinkapi/attach.go @@ -6,17 +6,17 @@ import ( "bufio" "io" - "github.com/containers/libpod/cmd/podman/varlink" "github.com/containers/libpod/libpod" "github.com/containers/libpod/libpod/define" "github.com/containers/libpod/libpod/events" + iopodman "github.com/containers/libpod/pkg/varlink" "github.com/containers/libpod/pkg/varlinkapi/virtwriter" "github.com/pkg/errors" "github.com/sirupsen/logrus" "k8s.io/client-go/tools/remotecommand" ) -func setupStreams(call iopodman.VarlinkCall) (*bufio.Reader, *bufio.Writer, *io.PipeReader, *io.PipeWriter, *libpod.AttachStreams) { +func setupStreams(call iopodman.VarlinkCall) (*bufio.Reader, *bufio.Writer, *io.PipeReader, *io.PipeWriter, *define.AttachStreams) { // These are the varlink sockets reader := call.Call.Reader @@ -28,9 +28,9 @@ func setupStreams(call iopodman.VarlinkCall) (*bufio.Reader, *bufio.Writer, *io. stdoutWriter := virtwriter.NewVirtWriteCloser(writer, virtwriter.ToStdout) // TODO if runc ever starts passing stderr, we can too - //stderrWriter := NewVirtWriteCloser(writer, ToStderr) + // stderrWriter := NewVirtWriteCloser(writer, ToStderr) - streams := libpod.AttachStreams{ + streams := define.AttachStreams{ OutputStream: stdoutWriter, InputStream: bufio.NewReader(pr), // Runc eats the error stream @@ -117,7 +117,7 @@ func (i *LibpodAPI) Attach(call iopodman.VarlinkCall, name string, detachKeys st return call.Writer.Flush() } -func attach(ctr *libpod.Container, streams *libpod.AttachStreams, detachKeys string, resize chan remotecommand.TerminalSize, errChan chan error) error { +func attach(ctr *libpod.Container, streams *define.AttachStreams, detachKeys string, resize chan remotecommand.TerminalSize, errChan chan error) error { go func() { if err := ctr.Attach(streams, detachKeys, resize); err != nil { errChan <- err @@ -127,7 +127,7 @@ func attach(ctr *libpod.Container, streams *libpod.AttachStreams, detachKeys str return attachError } -func startAndAttach(ctr *libpod.Container, streams *libpod.AttachStreams, detachKeys string, resize chan remotecommand.TerminalSize, errChan chan error) error { +func startAndAttach(ctr *libpod.Container, streams *define.AttachStreams, detachKeys string, resize chan remotecommand.TerminalSize, errChan chan error) error { var finalErr error attachChan, err := ctr.StartAndAttach(getContext(), streams, detachKeys, resize, false) if err != nil { diff --git a/pkg/varlinkapi/config.go b/pkg/varlinkapi/config.go index e75170547..c69dc794a 100644 --- a/pkg/varlinkapi/config.go +++ b/pkg/varlinkapi/config.go @@ -4,8 +4,8 @@ package varlinkapi import ( "github.com/containers/libpod/cmd/podman/cliconfig" - iopodman "github.com/containers/libpod/cmd/podman/varlink" "github.com/containers/libpod/libpod" + iopodman "github.com/containers/libpod/pkg/varlink" "github.com/spf13/cobra" ) diff --git a/pkg/varlinkapi/containers.go b/pkg/varlinkapi/containers.go index 55427771c..2d051470f 100644 --- a/pkg/varlinkapi/containers.go +++ b/pkg/varlinkapi/containers.go @@ -15,13 +15,13 @@ import ( "time" "github.com/containers/libpod/cmd/podman/shared" - iopodman "github.com/containers/libpod/cmd/podman/varlink" "github.com/containers/libpod/libpod" "github.com/containers/libpod/libpod/define" "github.com/containers/libpod/libpod/logs" "github.com/containers/libpod/pkg/adapter/shortcuts" "github.com/containers/libpod/pkg/cgroups" "github.com/containers/libpod/pkg/rootless" + iopodman "github.com/containers/libpod/pkg/varlink" "github.com/containers/libpod/pkg/varlinkapi/virtwriter" "github.com/containers/storage/pkg/archive" "github.com/pkg/errors" @@ -900,7 +900,7 @@ func (i *LibpodAPI) ExecContainer(call iopodman.VarlinkCall, opts iopodman.ExecO return ecErr.Error } -//HealthCheckRun executes defined container's healthcheck command and returns the container's health status. +// HealthCheckRun executes defined container's healthcheck command and returns the container's health status. func (i *LibpodAPI) HealthCheckRun(call iopodman.VarlinkCall, nameOrID string) error { hcStatus, err := i.Runtime.HealthCheck(nameOrID) if err != nil && hcStatus != libpod.HealthCheckFailure { diff --git a/pkg/varlinkapi/containers_create.go b/pkg/varlinkapi/containers_create.go index 6b23dce5e..bbd4d59f1 100644 --- a/pkg/varlinkapi/containers_create.go +++ b/pkg/varlinkapi/containers_create.go @@ -4,7 +4,7 @@ package varlinkapi import ( "github.com/containers/libpod/cmd/podman/shared" - "github.com/containers/libpod/cmd/podman/varlink" + iopodman "github.com/containers/libpod/pkg/varlink" ) // CreateContainer ... diff --git a/pkg/varlinkapi/events.go b/pkg/varlinkapi/events.go index f9a9d9321..4ae2d1cb2 100644 --- a/pkg/varlinkapi/events.go +++ b/pkg/varlinkapi/events.go @@ -6,8 +6,8 @@ import ( "fmt" "time" - "github.com/containers/libpod/cmd/podman/varlink" "github.com/containers/libpod/libpod/events" + iopodman "github.com/containers/libpod/pkg/varlink" ) // GetEvents is a remote endpoint to get events from the event log diff --git a/pkg/varlinkapi/generate.go b/pkg/varlinkapi/generate.go index 19010097d..c19c8dede 100644 --- a/pkg/varlinkapi/generate.go +++ b/pkg/varlinkapi/generate.go @@ -6,7 +6,7 @@ import ( "encoding/json" "github.com/containers/libpod/cmd/podman/shared" - iopodman "github.com/containers/libpod/cmd/podman/varlink" + iopodman "github.com/containers/libpod/pkg/varlink" ) // GenerateKube ... diff --git a/pkg/varlinkapi/images.go b/pkg/varlinkapi/images.go index 2dfb84e58..c3b4bd9ae 100644 --- a/pkg/varlinkapi/images.go +++ b/pkg/varlinkapi/images.go @@ -21,16 +21,15 @@ import ( "github.com/containers/image/v5/transports/alltransports" "github.com/containers/image/v5/types" "github.com/containers/libpod/cmd/podman/shared" - iopodman "github.com/containers/libpod/cmd/podman/varlink" "github.com/containers/libpod/libpod" "github.com/containers/libpod/libpod/define" "github.com/containers/libpod/libpod/image" "github.com/containers/libpod/pkg/channelwriter" "github.com/containers/libpod/pkg/util" + iopodman "github.com/containers/libpod/pkg/varlink" "github.com/containers/libpod/utils" "github.com/containers/storage/pkg/archive" v1 "github.com/opencontainers/image-spec/specs-go/v1" - "github.com/opencontainers/runtime-spec/specs-go" "github.com/pkg/errors" "github.com/sirupsen/logrus" ) @@ -147,7 +146,6 @@ func (i *LibpodAPI) BuildImage(call iopodman.VarlinkCall, config iopodman.BuildI err error ) - systemContext := types.SystemContext{} contextDir := config.ContextDir newContextDir, err := ioutil.TempDir("", "buildTarball") @@ -175,6 +173,8 @@ func (i *LibpodAPI) BuildImage(call iopodman.VarlinkCall, config iopodman.BuildI logrus.Errorf("unable to delete directory '%s': %q", newContextDir, err) } }() + + systemContext := types.SystemContext{} // All output (stdout, stderr) is captured in output as well var output bytes.Buffer @@ -192,40 +192,40 @@ func (i *LibpodAPI) BuildImage(call iopodman.VarlinkCall, config iopodman.BuildI Volumes: config.BuildOptions.Volume, } - hostNetwork := buildah.NamespaceOption{ - Name: string(specs.NetworkNamespace), - Host: true, - } - - namespace = append(namespace, hostNetwork) - options := imagebuildah.BuildOptions{ - CommonBuildOpts: commonOpts, + AddCapabilities: config.AddCapabilities, AdditionalTags: config.AdditionalTags, Annotations: config.Annotations, + Architecture: config.Architecture, Args: config.BuildArgs, CNIConfigDir: config.CniConfigDir, CNIPluginPath: config.CniPluginDir, + CommonBuildOpts: commonOpts, Compression: stringCompressionToArchiveType(config.Compression), ContextDirectory: newContextDir, DefaultMountsFilePath: config.DefaultsMountFilePath, + Devices: config.Devices, Err: &output, ForceRmIntermediateCtrs: config.ForceRmIntermediateCtrs, IIDFile: config.Iidfile, Labels: config.Label, Layers: config.Layers, + NamespaceOptions: namespace, NoCache: config.Nocache, + OS: config.Os, Out: &output, Output: config.Output, - NamespaceOptions: namespace, OutputFormat: config.OutputFormat, PullPolicy: stringPullPolicyToType(config.PullPolicy), Quiet: config.Quiet, RemoveIntermediateCtrs: config.RemoteIntermediateCtrs, ReportWriter: &output, RuntimeArgs: config.RuntimeArgs, + SignBy: config.SignBy, Squash: config.Squash, SystemContext: &systemContext, + Target: config.Target, + TransientMounts: config.TransientMounts, } if call.WantsMore() { @@ -587,7 +587,7 @@ func (i *LibpodAPI) Commit(call iopodman.VarlinkCall, name, imageName string, ch if err != nil { return call.ReplyErrorOccurred(err.Error()) } - sc := image.GetSystemContext(rtc.SignaturePolicyPath, "", false) + sc := image.GetSystemContext(rtc.Engine.SignaturePolicyPath, "", false) switch manifestType { case "oci", "": // nolint mimeType = buildah.OCIv1ImageManifest @@ -597,7 +597,7 @@ func (i *LibpodAPI) Commit(call iopodman.VarlinkCall, name, imageName string, ch return call.ReplyErrorOccurred(fmt.Sprintf("unrecognized image format %q", manifestType)) } coptions := buildah.CommitOptions{ - SignaturePolicyPath: rtc.SignaturePolicyPath, + SignaturePolicyPath: rtc.Engine.SignaturePolicyPath, ReportWriter: output, SystemContext: sc, PreferredManifestType: mimeType, diff --git a/pkg/varlinkapi/mount.go b/pkg/varlinkapi/mount.go index 63ce44291..2450f6fd9 100644 --- a/pkg/varlinkapi/mount.go +++ b/pkg/varlinkapi/mount.go @@ -2,9 +2,7 @@ package varlinkapi -import ( - "github.com/containers/libpod/cmd/podman/varlink" -) +import iopodman "github.com/containers/libpod/pkg/varlink" // ListContainerMounts ... func (i *LibpodAPI) ListContainerMounts(call iopodman.VarlinkCall) error { diff --git a/pkg/varlinkapi/pods.go b/pkg/varlinkapi/pods.go index 2ec45f7a1..79ffb6677 100644 --- a/pkg/varlinkapi/pods.go +++ b/pkg/varlinkapi/pods.go @@ -8,9 +8,9 @@ import ( "syscall" "github.com/containers/libpod/cmd/podman/shared" - "github.com/containers/libpod/cmd/podman/varlink" "github.com/containers/libpod/libpod" "github.com/containers/libpod/pkg/adapter/shortcuts" + iopodman "github.com/containers/libpod/pkg/varlink" ) // CreatePod ... diff --git a/pkg/varlinkapi/remote_client.go b/pkg/varlinkapi/remote_client.go index dd0613494..a16d11dec 100644 --- a/pkg/varlinkapi/remote_client.go +++ b/pkg/varlinkapi/remote_client.go @@ -3,8 +3,8 @@ package varlinkapi import ( - "github.com/containers/libpod/cmd/podman/varlink" "github.com/containers/libpod/libpod" + iopodman "github.com/containers/libpod/pkg/varlink" ) // ContainerStatsToLibpodContainerStats converts the varlink containerstats to a libpod diff --git a/pkg/varlinkapi/system.go b/pkg/varlinkapi/system.go index e88d010c5..04fb9f648 100644 --- a/pkg/varlinkapi/system.go +++ b/pkg/varlinkapi/system.go @@ -10,8 +10,8 @@ import ( "time" "github.com/containers/image/v5/pkg/sysregistriesv2" - iopodman "github.com/containers/libpod/cmd/podman/varlink" "github.com/containers/libpod/libpod/define" + iopodman "github.com/containers/libpod/pkg/varlink" "github.com/sirupsen/logrus" ) diff --git a/pkg/varlinkapi/transfers.go b/pkg/varlinkapi/transfers.go index 31d26c3aa..654da276e 100644 --- a/pkg/varlinkapi/transfers.go +++ b/pkg/varlinkapi/transfers.go @@ -9,7 +9,7 @@ import ( "io/ioutil" "os" - "github.com/containers/libpod/cmd/podman/varlink" + iopodman "github.com/containers/libpod/pkg/varlink" "github.com/sirupsen/logrus" ) diff --git a/pkg/varlinkapi/util.go b/pkg/varlinkapi/util.go index d3a41f7ab..6b196f384 100644 --- a/pkg/varlinkapi/util.go +++ b/pkg/varlinkapi/util.go @@ -10,10 +10,10 @@ import ( "github.com/containers/buildah" "github.com/containers/libpod/cmd/podman/shared" - "github.com/containers/libpod/cmd/podman/varlink" "github.com/containers/libpod/libpod" "github.com/containers/libpod/libpod/define" "github.com/containers/libpod/pkg/channelwriter" + iopodman "github.com/containers/libpod/pkg/varlink" "github.com/containers/storage/pkg/archive" ) diff --git a/pkg/varlinkapi/volumes.go b/pkg/varlinkapi/volumes.go index cbb4a70cc..b0c3608c4 100644 --- a/pkg/varlinkapi/volumes.go +++ b/pkg/varlinkapi/volumes.go @@ -6,8 +6,8 @@ import ( "encoding/json" "github.com/containers/libpod/cmd/podman/shared" - "github.com/containers/libpod/cmd/podman/varlink" "github.com/containers/libpod/libpod" + iopodman "github.com/containers/libpod/pkg/varlink" ) // VolumeCreate creates a libpod volume based on input from a varlink connection @@ -113,11 +113,11 @@ func (i *LibpodAPI) VolumesPrune(call iopodman.VarlinkCall) error { if err != nil { return call.ReplyVolumesPrune([]string{}, []string{err.Error()}) } - for _, i := range responses { - if i.Err == nil { - prunedNames = append(prunedNames, i.Id) + for k, v := range responses { + if v == nil { + prunedNames = append(prunedNames, k) } else { - prunedErrors = append(prunedErrors, i.Err.Error()) + prunedErrors = append(prunedErrors, v.Error()) } } return call.ReplyVolumesPrune(prunedNames, prunedErrors) |