diff options
Diffstat (limited to 'pkg')
54 files changed, 769 insertions, 321 deletions
diff --git a/pkg/api/handlers/compat/containers_archive.go b/pkg/api/handlers/compat/containers_archive.go index a9d74e5f4..541f702e7 100644 --- a/pkg/api/handlers/compat/containers_archive.go +++ b/pkg/api/handlers/compat/containers_archive.go @@ -1,6 +1,7 @@ package compat import ( + "encoding/json" "fmt" "net/http" "os" @@ -93,8 +94,9 @@ func handleHeadAndGet(w http.ResponseWriter, r *http.Request, decoder *schema.De func handlePut(w http.ResponseWriter, r *http.Request, decoder *schema.Decoder, runtime *libpod.Runtime) { query := struct { - Path string `schema:"path"` - Chown bool `schema:"copyUIDGID"` + Path string `schema:"path"` + Chown bool `schema:"copyUIDGID"` + Rename string `schema:"rename"` // TODO handle params below NoOverwriteDirNonDir bool `schema:"noOverwriteDirNonDir"` }{ @@ -107,10 +109,19 @@ func handlePut(w http.ResponseWriter, r *http.Request, decoder *schema.Decoder, return } + var rename map[string]string + if query.Rename != "" { + if err := json.Unmarshal([]byte(query.Rename), &rename); err != nil { + utils.Error(w, "Bad Request.", http.StatusBadRequest, errors.Wrap(err, "couldn't decode the query")) + return + } + } + containerName := utils.GetName(r) containerEngine := abi.ContainerEngine{Libpod: runtime} - copyFunc, err := containerEngine.ContainerCopyFromArchive(r.Context(), containerName, query.Path, r.Body, entities.CopyOptions{Chown: query.Chown}) + copyOptions := entities.CopyOptions{Chown: query.Chown, Rename: rename} + copyFunc, err := containerEngine.ContainerCopyFromArchive(r.Context(), containerName, query.Path, r.Body, copyOptions) if errors.Cause(err) == define.ErrNoSuchCtr || os.IsNotExist(err) { // 404 is returned for an absent container and path. The // clients must deal with it accordingly. diff --git a/pkg/api/handlers/compat/images.go b/pkg/api/handlers/compat/images.go index 7baa1145a..6f8fb21f0 100644 --- a/pkg/api/handlers/compat/images.go +++ b/pkg/api/handlers/compat/images.go @@ -266,41 +266,26 @@ func CreateImageFromImage(w http.ResponseWriter, r *http.Request) { } defer auth.RemoveAuthfile(authfile) - platformSpecs := strings.Split(query.Platform, "/") // split query into its parts - - addOS := true // default assume true due to structure of if/else below - addArch := false - addVariant := false - - if len(platformSpecs) > 1 { // if we have two arguments then we have os and arch - addArch = true - if len(platformSpecs) > 2 { // if we have 3 arguments then we have os arch and variant - addVariant = true - } - } else if len(platformSpecs) == 0 { - addOS = false - } - pullOptions := &libimage.PullOptions{} pullOptions.AuthFilePath = authfile if authConf != nil { pullOptions.Username = authConf.Username pullOptions.Password = authConf.Password pullOptions.IdentityToken = authConf.IdentityToken - if addOS { // if the len is not 0 - pullOptions.OS = platformSpecs[0] - if addArch { - pullOptions.Architecture = platformSpecs[1] - } - if addVariant { - pullOptions.Variant = platformSpecs[2] - } - } } pullOptions.Writer = os.Stderr // allows for debugging on the server - progress := make(chan types.ProgressProperties) + // Handle the platform. + platformSpecs := strings.Split(query.Platform, "/") + pullOptions.OS = platformSpecs[0] // may be empty + if len(platformSpecs) > 1 { + pullOptions.Architecture = platformSpecs[1] + if len(platformSpecs) > 2 { + pullOptions.Variant = platformSpecs[2] + } + } + progress := make(chan types.ProgressProperties) pullOptions.Progress = progress pullResChan := make(chan pullResult) diff --git a/pkg/api/handlers/compat/images_build.go b/pkg/api/handlers/compat/images_build.go index 64805b7fa..2c98a5361 100644 --- a/pkg/api/handlers/compat/images_build.go +++ b/pkg/api/handlers/compat/images_build.go @@ -393,16 +393,16 @@ func BuildImage(w http.ResponseWriter, r *http.Request) { defer auth.RemoveAuthfile(authfile) // Channels all mux'ed in select{} below to follow API build protocol - stdout := channel.NewWriter(make(chan []byte, 1)) + stdout := channel.NewWriter(make(chan []byte)) defer stdout.Close() - auxout := channel.NewWriter(make(chan []byte, 1)) + auxout := channel.NewWriter(make(chan []byte)) defer auxout.Close() - stderr := channel.NewWriter(make(chan []byte, 1)) + stderr := channel.NewWriter(make(chan []byte)) defer stderr.Close() - reporter := channel.NewWriter(make(chan []byte, 1)) + reporter := channel.NewWriter(make(chan []byte)) defer reporter.Close() runtime := r.Context().Value("runtime").(*libpod.Runtime) @@ -529,7 +529,7 @@ func BuildImage(w http.ResponseWriter, r *http.Request) { enc := json.NewEncoder(body) enc.SetEscapeHTML(true) -loop: + for { m := struct { Stream string `json:"stream,omitempty"` @@ -543,13 +543,13 @@ loop: stderr.Write([]byte(err.Error())) } flush() - case e := <-auxout.Chan(): + case e := <-reporter.Chan(): m.Stream = string(e) if err := enc.Encode(m); err != nil { stderr.Write([]byte(err.Error())) } flush() - case e := <-reporter.Chan(): + case e := <-auxout.Chan(): m.Stream = string(e) if err := enc.Encode(m); err != nil { stderr.Write([]byte(err.Error())) @@ -561,8 +561,8 @@ loop: logrus.Warnf("Failed to json encode error %v", err) } flush() + return case <-runCtx.Done(): - flush() if success { if !utils.IsLibpodRequest(r) { m.Stream = fmt.Sprintf("Successfully built %12.12s\n", imageID) @@ -579,7 +579,8 @@ loop: } } } - break loop + flush() + return case <-r.Context().Done(): cancel() logrus.Infof("Client disconnect reported for build %q / %q.", registry, query.Dockerfile) diff --git a/pkg/api/handlers/compat/images_history.go b/pkg/api/handlers/compat/images_history.go index ea596890f..54c893f47 100644 --- a/pkg/api/handlers/compat/images_history.go +++ b/pkg/api/handlers/compat/images_history.go @@ -3,7 +3,6 @@ package compat import ( "net/http" - "github.com/containers/common/libimage" "github.com/containers/podman/v3/libpod" "github.com/containers/podman/v3/pkg/api/handlers" "github.com/containers/podman/v3/pkg/api/handlers/utils" @@ -14,8 +13,7 @@ func HistoryImage(w http.ResponseWriter, r *http.Request) { runtime := r.Context().Value("runtime").(*libpod.Runtime) name := utils.GetName(r) - lookupOptions := &libimage.LookupImageOptions{IgnorePlatform: true} - newImage, _, err := runtime.LibimageRuntime().LookupImage(name, lookupOptions) + newImage, _, err := runtime.LibimageRuntime().LookupImage(name, nil) if err != nil { utils.Error(w, "Something went wrong.", http.StatusNotFound, errors.Wrapf(err, "failed to find image %s", name)) return diff --git a/pkg/api/handlers/compat/images_tag.go b/pkg/api/handlers/compat/images_tag.go index 8d256f4fa..199ad0488 100644 --- a/pkg/api/handlers/compat/images_tag.go +++ b/pkg/api/handlers/compat/images_tag.go @@ -4,7 +4,6 @@ import ( "fmt" "net/http" - "github.com/containers/common/libimage" "github.com/containers/podman/v3/libpod" "github.com/containers/podman/v3/pkg/api/handlers/utils" "github.com/pkg/errors" @@ -16,8 +15,7 @@ func TagImage(w http.ResponseWriter, r *http.Request) { // /v1.xx/images/(name)/tag name := utils.GetName(r) - lookupOptions := &libimage.LookupImageOptions{IgnorePlatform: true} - newImage, _, err := runtime.LibimageRuntime().LookupImage(name, lookupOptions) + newImage, _, err := runtime.LibimageRuntime().LookupImage(name, nil) if err != nil { utils.ImageNotFound(w, name, errors.Wrapf(err, "failed to find image %s", name)) return diff --git a/pkg/api/handlers/compat/networks.go b/pkg/api/handlers/compat/networks.go index 4e1f31404..b990a916b 100644 --- a/pkg/api/handlers/compat/networks.go +++ b/pkg/api/handlers/compat/networks.go @@ -25,6 +25,12 @@ import ( "github.com/sirupsen/logrus" ) +type pluginInterface struct { + PluginType string `json:"type"` + IPAM network.IPAMConfig `json:"ipam"` + IsGW bool `json:"isGateway"` +} + func InspectNetwork(w http.ResponseWriter, r *http.Request) { runtime := r.Context().Value("runtime").(*libpod.Runtime) @@ -103,12 +109,12 @@ func getNetworkResourceByNameOrID(nameOrID string, runtime *libpod.Runtime, filt } } - // No Bridge plugin means we bail - bridge, err := genericPluginsToBridge(conf.Plugins, network.DefaultNetworkDriver) + plugin, err := getPlugin(conf.Plugins) if err != nil { return nil, err } - for _, outer := range bridge.IPAM.Ranges { + + for _, outer := range plugin.IPAM.Ranges { for _, n := range outer { ipamConfig := dockerNetwork.IPAMConfig{ Subnet: n.Subnet, @@ -140,19 +146,26 @@ func getNetworkResourceByNameOrID(nameOrID string, runtime *libpod.Runtime, filt labels = map[string]string{} } + isInternal := false + dockerDriver := plugin.PluginType + if plugin.PluginType == network.DefaultNetworkDriver { + isInternal = !plugin.IsGW + dockerDriver = "default" + } + report := types.NetworkResource{ Name: conf.Name, ID: networkid.GetNetworkID(conf.Name), Created: time.Unix(int64(stat.Ctim.Sec), int64(stat.Ctim.Nsec)), // nolint: unconvert Scope: "local", - Driver: network.DefaultNetworkDriver, + Driver: plugin.PluginType, EnableIPv6: false, IPAM: dockerNetwork.IPAM{ - Driver: "default", + Driver: dockerDriver, Options: map[string]string{}, Config: ipamConfigs, }, - Internal: !bridge.IsGW, + Internal: isInternal, Attachable: false, Ingress: false, ConfigFrom: dockerNetwork.ConfigReference{}, @@ -166,23 +179,19 @@ func getNetworkResourceByNameOrID(nameOrID string, runtime *libpod.Runtime, filt return &report, nil } -func genericPluginsToBridge(plugins []*libcni.NetworkConfig, pluginType string) (network.HostLocalBridge, error) { - var bridge network.HostLocalBridge - generic, err := findPluginByName(plugins, pluginType) - if err != nil { - return bridge, err - } - err = json.Unmarshal(generic, &bridge) - return bridge, err -} +func getPlugin(plugins []*libcni.NetworkConfig) (pluginInterface, error) { + var plugin pluginInterface -func findPluginByName(plugins []*libcni.NetworkConfig, pluginType string) ([]byte, error) { for _, p := range plugins { - if pluginType == p.Network.Type { - return p.Bytes, nil + for _, pluginType := range network.SupportedNetworkDrivers { + if pluginType == p.Network.Type { + err := json.Unmarshal(p.Bytes, &plugin) + return plugin, err + } } } - return nil, errors.New("unable to find bridge plugin") + + return plugin, errors.New("unable to find supported plugin") } func ListNetworks(w http.ResponseWriter, r *http.Request) { diff --git a/pkg/api/handlers/libpod/containers_create.go b/pkg/api/handlers/libpod/containers_create.go index b92588346..65951861b 100644 --- a/pkg/api/handlers/libpod/containers_create.go +++ b/pkg/api/handlers/libpod/containers_create.go @@ -22,6 +22,7 @@ func CreateContainer(w http.ResponseWriter, r *http.Request) { utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "Decode()")) return } + warn, err := generate.CompleteSpec(r.Context(), runtime, &sg) if err != nil { utils.InternalServerError(w, err) diff --git a/pkg/api/handlers/libpod/images.go b/pkg/api/handlers/libpod/images.go index fc6ab4b4c..d759f4824 100644 --- a/pkg/api/handlers/libpod/images.go +++ b/pkg/api/handlers/libpod/images.go @@ -11,7 +11,6 @@ import ( "strings" "github.com/containers/buildah" - "github.com/containers/common/libimage" "github.com/containers/common/pkg/filters" "github.com/containers/image/v5/manifest" "github.com/containers/image/v5/types" @@ -215,8 +214,7 @@ func ExportImage(w http.ResponseWriter, r *http.Request) { } name := utils.GetName(r) - lookupOptions := &libimage.LookupImageOptions{IgnorePlatform: true} - if _, _, err := runtime.LibimageRuntime().LookupImage(name, lookupOptions); err != nil { + if _, _, err := runtime.LibimageRuntime().LookupImage(name, nil); err != nil { utils.ImageNotFound(w, name, err) return } diff --git a/pkg/api/handlers/types.go b/pkg/api/handlers/types.go index 59f948567..af5878798 100644 --- a/pkg/api/handlers/types.go +++ b/pkg/api/handlers/types.go @@ -133,6 +133,7 @@ type PodCreateConfig struct { Infra bool `json:"infra"` InfraCommand string `json:"infra-command"` InfraImage string `json:"infra-image"` + InfraName string `json:"infra-name"` Labels []string `json:"labels"` Publish []string `json:"publish"` Share string `json:"share"` @@ -175,6 +176,11 @@ func ImageToImageSummary(l *libimage.Image) (*entities.ImageSummary, error) { } containerCount := len(containers) + isDangling, err := l.IsDangling(context.TODO()) + if err != nil { + return nil, errors.Wrapf(err, "failed to check if image %s is dangling", l.ID()) + } + is := entities.ImageSummary{ ID: l.ID(), ParentId: imageData.Parent, @@ -187,7 +193,7 @@ func ImageToImageSummary(l *libimage.Image) (*entities.ImageSummary, error) { Labels: imageData.Labels, Containers: containerCount, ReadOnly: l.IsReadOnly(), - Dangling: l.IsDangling(), + Dangling: isDangling, Names: l.Names(), Digest: string(imageData.Digest), ConfigDigest: "", // TODO: libpod/image didn't set it but libimage should diff --git a/pkg/api/handlers/utils/images.go b/pkg/api/handlers/utils/images.go index 2a1908d63..1e8edb6dd 100644 --- a/pkg/api/handlers/utils/images.go +++ b/pkg/api/handlers/utils/images.go @@ -88,8 +88,7 @@ func GetImages(w http.ResponseWriter, r *http.Request) ([]*libimage.Image, error func GetImage(r *http.Request, name string) (*libimage.Image, error) { runtime := r.Context().Value("runtime").(*libpod.Runtime) - lookupOptions := &libimage.LookupImageOptions{IgnorePlatform: true} - image, _, err := runtime.LibimageRuntime().LookupImage(name, lookupOptions) + image, _, err := runtime.LibimageRuntime().LookupImage(name, nil) if err != nil { return nil, err } diff --git a/pkg/api/server/docs.go b/pkg/api/server/docs.go index e72b78221..bf15afbf9 100644 --- a/pkg/api/server/docs.go +++ b/pkg/api/server/docs.go @@ -42,7 +42,7 @@ // // InfoExtensions: // x-logo: -// - url: https://raw.githubusercontent.com/containers/libpod/master/logo/podman-logo.png +// - url: https://raw.githubusercontent.com/containers/libpod/main/logo/podman-logo.png // - altText: "Podman logo" // // Produces: diff --git a/pkg/api/server/register_archive.go b/pkg/api/server/register_archive.go index ee7449fbb..82d72ee6a 100644 --- a/pkg/api/server/register_archive.go +++ b/pkg/api/server/register_archive.go @@ -151,6 +151,10 @@ func (s *APIServer) registerArchiveHandlers(r *mux.Router) error { // type: string // description: Path to a directory in the container to extract // required: true + // - in: query + // name: rename + // type: string + // description: JSON encoded map[string]string to translate paths // responses: // 200: // description: no error diff --git a/pkg/api/server/register_volumes.go b/pkg/api/server/register_volumes.go index d58bf0662..fb02cffcf 100644 --- a/pkg/api/server/register_volumes.go +++ b/pkg/api/server/register_volumes.go @@ -68,6 +68,7 @@ func (s *APIServer) registerVolumeHandlers(r *mux.Router) error { // - label=<key> or label=<key>:<value> Matches volumes based on the presence of a label alone or a label and a value. // - name=<volume-name> Matches all of volume name. // - opt=<driver-option> Matches a storage driver options + // - `until=<timestamp>` List volumes created before this timestamp. The `<timestamp>` can be Unix timestamps, date formatted timestamps, or Go duration strings (e.g. `10m`, `1h30m`) computed relative to the daemon machine’s time. // responses: // '200': // "$ref": "#/responses/VolumeList" @@ -166,6 +167,7 @@ func (s *APIServer) registerVolumeHandlers(r *mux.Router) error { // - driver=<volume-driver-name> Matches volumes based on their driver. // - label=<key> or label=<key>:<value> Matches volumes based on the presence of a label alone or a label and a value. // - name=<volume-name> Matches all of volume name. + // - `until=<timestamp>` List volumes created before this timestamp. The `<timestamp>` can be Unix timestamps, date formatted timestamps, or Go duration strings (e.g. `10m`, `1h30m`) computed relative to the daemon machine’s time. // // Note: // The boolean `dangling` filter is not yet implemented for this endpoint. diff --git a/pkg/autoupdate/autoupdate.go b/pkg/autoupdate/autoupdate.go index 0a13e7e74..c51e2cd03 100644 --- a/pkg/autoupdate/autoupdate.go +++ b/pkg/autoupdate/autoupdate.go @@ -9,12 +9,13 @@ import ( "github.com/containers/common/pkg/config" "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/podman/v3/libpod" "github.com/containers/podman/v3/libpod/define" + "github.com/containers/podman/v3/pkg/domain/entities" "github.com/containers/podman/v3/pkg/systemd" systemdDefine "github.com/containers/podman/v3/pkg/systemd/define" + "github.com/coreos/go-systemd/v22/dbus" "github.com/pkg/errors" "github.com/sirupsen/logrus" ) @@ -74,12 +75,6 @@ func LookupPolicy(s string) (Policy, error) { return "", errors.Errorf("invalid auto-update policy %q: valid policies are %+q", s, keys) } -// Options include parameters for auto updates. -type Options struct { - // Authfile to use when contacting registries. - Authfile string -} - // ValidateImageReference checks if the specified imageName is a fully-qualified // image reference to the docker transport (without digest). Such a reference // includes a domain, name and tag (e.g., quay.io/podman/stable:latest). The @@ -119,7 +114,7 @@ func ValidateImageReference(imageName string) error { // // It returns a slice of successfully restarted systemd units and a slice of // errors encountered during auto update. -func AutoUpdate(runtime *libpod.Runtime, options Options) ([]string, []error) { +func AutoUpdate(ctx context.Context, runtime *libpod.Runtime, options entities.AutoUpdateOptions) ([]*entities.AutoUpdateReport, []error) { // Create a map from `image ID -> []*Container`. containerMap, errs := imageContainersMap(runtime) if len(containerMap) == 0 { @@ -130,7 +125,7 @@ func AutoUpdate(runtime *libpod.Runtime, options Options) ([]string, []error) { listOptions := &libimage.ListImagesOptions{ Filters: []string{"readonly=false"}, } - imagesSlice, err := runtime.LibimageRuntime().ListImages(context.Background(), nil, listOptions) + imagesSlice, err := runtime.LibimageRuntime().ListImages(ctx, nil, listOptions) if err != nil { return nil, []error{err} } @@ -147,8 +142,8 @@ func AutoUpdate(runtime *libpod.Runtime, options Options) ([]string, []error) { } defer conn.Close() - // Update images. - containersToRestart := []*libpod.Container{} + // Update all images/container according to their auto-update policy. + var allReports []*entities.AutoUpdateReport updatedRawImages := make(map[string]bool) for imageID, policyMapper := range containerMap { image, exists := imageMap[imageID] @@ -156,76 +151,149 @@ func AutoUpdate(runtime *libpod.Runtime, options Options) ([]string, []error) { errs = append(errs, errors.Errorf("container image ID %q not found in local storage", imageID)) return nil, errs } - // Now we have to check if the image of any containers must be updated. - // Note that the image ID is NOT enough for this check as a given image - // may have multiple tags. - for _, registryCtr := range policyMapper[PolicyRegistryImage] { - cid := registryCtr.ID() - rawImageName := registryCtr.RawImageName() - if rawImageName == "" { - errs = append(errs, errors.Errorf("error registry auto-updating container %q: raw-image name is empty", cid)) - } - readAuthenticationPath(registryCtr, options) - needsUpdate, err := newerRemoteImageAvailable(runtime, image, rawImageName, options) + + for _, ctr := range policyMapper[PolicyRegistryImage] { + report, err := autoUpdateRegistry(ctx, image, ctr, updatedRawImages, &options, conn, runtime) if err != nil { - errs = append(errs, errors.Wrapf(err, "error registry auto-updating container %q: image check for %q failed", cid, rawImageName)) - continue + errs = append(errs, err) } - - if needsUpdate { - logrus.Infof("Auto-updating container %q using registry image %q", cid, rawImageName) - if _, updated := updatedRawImages[rawImageName]; !updated { - _, err = updateImage(runtime, rawImageName, options) - if err != nil { - errs = append(errs, errors.Wrapf(err, "error registry auto-updating container %q: image update for %q failed", cid, rawImageName)) - continue - } - updatedRawImages[rawImageName] = true - } - containersToRestart = append(containersToRestart, registryCtr) + if report != nil { + allReports = append(allReports, report) } } - for _, localCtr := range policyMapper[PolicyLocalImage] { - cid := localCtr.ID() - rawImageName := localCtr.RawImageName() - if rawImageName == "" { - errs = append(errs, errors.Errorf("error locally auto-updating container %q: raw-image name is empty", cid)) - } - // This avoids restarting containers unnecessarily. - needsUpdate, err := newerLocalImageAvailable(runtime, image, rawImageName) + for _, ctr := range policyMapper[PolicyLocalImage] { + report, err := autoUpdateLocally(ctx, image, ctr, &options, conn, runtime) if err != nil { - errs = append(errs, errors.Wrapf(err, "error locally auto-updating container %q: image check for %q failed", cid, rawImageName)) - continue + errs = append(errs, err) } - - if needsUpdate { - logrus.Infof("Auto-updating container %q using local image %q", cid, rawImageName) - containersToRestart = append(containersToRestart, localCtr) + if report != nil { + allReports = append(allReports, report) } } } - // Restart containers. - updatedUnits := []string{} - for _, ctr := range containersToRestart { - labels := ctr.Labels() - unit, exists := labels[systemdDefine.EnvVariable] - if !exists { - // Shouldn't happen but let's be sure of it. - errs = append(errs, errors.Errorf("error auto-updating container %q: no %s label found", ctr.ID(), systemdDefine.EnvVariable)) - continue - } - _, err := conn.RestartUnit(unit, "replace", nil) - if err != nil { - errs = append(errs, errors.Wrapf(err, "error auto-updating container %q: restarting systemd unit %q failed", ctr.ID(), unit)) - continue + return allReports, errs +} + +// autoUpdateRegistry updates the image/container according to the "registry" policy. +func autoUpdateRegistry(ctx context.Context, image *libimage.Image, ctr *libpod.Container, updatedRawImages map[string]bool, options *entities.AutoUpdateOptions, conn *dbus.Conn, runtime *libpod.Runtime) (*entities.AutoUpdateReport, error) { + cid := ctr.ID() + rawImageName := ctr.RawImageName() + if rawImageName == "" { + return nil, errors.Errorf("error registry auto-updating container %q: raw-image name is empty", cid) + } + + labels := ctr.Labels() + unit, exists := labels[systemdDefine.EnvVariable] + if !exists { + return nil, errors.Errorf("error auto-updating container %q: no %s label found", ctr.ID(), systemdDefine.EnvVariable) + } + + report := &entities.AutoUpdateReport{ + ContainerID: cid, + ContainerName: ctr.Name(), + ImageName: rawImageName, + Policy: PolicyRegistryImage, + SystemdUnit: unit, + Updated: "failed", + } + + if _, updated := updatedRawImages[rawImageName]; updated { + logrus.Infof("Auto-updating container %q using registry image %q", cid, rawImageName) + if err := restartSystemdUnit(ctr, unit, conn); err != nil { + return report, err } - logrus.Infof("Successfully restarted systemd unit %q", unit) - updatedUnits = append(updatedUnits, unit) + report.Updated = "true" + return report, nil + } + + authfile := getAuthfilePath(ctr, options) + needsUpdate, err := newerRemoteImageAvailable(ctx, runtime, image, rawImageName, authfile) + if err != nil { + return report, errors.Wrapf(err, "error registry auto-updating container %q: image check for %q failed", cid, rawImageName) + } + + if !needsUpdate { + report.Updated = "false" + return report, nil + } + + if options.DryRun { + report.Updated = "pending" + return report, nil + } + + if _, err := updateImage(ctx, runtime, rawImageName, options); err != nil { + return report, errors.Wrapf(err, "error registry auto-updating container %q: image update for %q failed", cid, rawImageName) + } + updatedRawImages[rawImageName] = true + + logrus.Infof("Auto-updating container %q using registry image %q", cid, rawImageName) + if err := restartSystemdUnit(ctr, unit, conn); err != nil { + return report, err } - return updatedUnits, errs + report.Updated = "true" + return report, nil +} + +// autoUpdateRegistry updates the image/container according to the "local" policy. +func autoUpdateLocally(ctx context.Context, image *libimage.Image, ctr *libpod.Container, options *entities.AutoUpdateOptions, conn *dbus.Conn, runtime *libpod.Runtime) (*entities.AutoUpdateReport, error) { + cid := ctr.ID() + rawImageName := ctr.RawImageName() + if rawImageName == "" { + return nil, errors.Errorf("error locally auto-updating container %q: raw-image name is empty", cid) + } + + labels := ctr.Labels() + unit, exists := labels[systemdDefine.EnvVariable] + if !exists { + return nil, errors.Errorf("error auto-updating container %q: no %s label found", ctr.ID(), systemdDefine.EnvVariable) + } + + report := &entities.AutoUpdateReport{ + ContainerID: cid, + ContainerName: ctr.Name(), + ImageName: rawImageName, + Policy: PolicyLocalImage, + SystemdUnit: unit, + Updated: "failed", + } + + needsUpdate, err := newerLocalImageAvailable(runtime, image, rawImageName) + if err != nil { + return report, errors.Wrapf(err, "error locally auto-updating container %q: image check for %q failed", cid, rawImageName) + } + + if !needsUpdate { + report.Updated = "false" + return report, nil + } + + if options.DryRun { + report.Updated = "pending" + return report, nil + } + + logrus.Infof("Auto-updating container %q using local image %q", cid, rawImageName) + if err := restartSystemdUnit(ctr, unit, conn); err != nil { + return report, err + } + + report.Updated = "true" + return report, nil +} + +// restartSystemdUnit restarts the systemd unit the container is running in. +func restartSystemdUnit(ctr *libpod.Container, unit string, conn *dbus.Conn) error { + _, err := conn.RestartUnit(unit, "replace", nil) + if err != nil { + return errors.Wrapf(err, "error auto-updating container %q: restarting systemd unit %q failed", ctr.ID(), unit) + } + + logrus.Infof("Successfully restarted systemd unit %q of container %q", unit, ctr.ID()) + return nil } // imageContainersMap generates a map[image ID] -> [containers using the image] @@ -280,52 +348,25 @@ func imageContainersMap(runtime *libpod.Runtime) (map[string]policyMapper, []err return containerMap, errors } -// readAuthenticationPath reads a container's labels and reads authentication path into options -func readAuthenticationPath(ctr *libpod.Container, options Options) { +// getAuthfilePath returns an authfile path, if set. The authfile label in the +// container, if set, as precedence over the one set in the options. +func getAuthfilePath(ctr *libpod.Container, options *entities.AutoUpdateOptions) string { labels := ctr.Labels() authFilePath, exists := labels[AuthfileLabel] if exists { - options.Authfile = authFilePath + return authFilePath } + return options.Authfile } // newerRemoteImageAvailable returns true if there corresponding image on the remote // registry is newer. -func newerRemoteImageAvailable(runtime *libpod.Runtime, img *libimage.Image, origName string, options Options) (bool, error) { +func newerRemoteImageAvailable(ctx context.Context, runtime *libpod.Runtime, img *libimage.Image, origName string, authfile string) (bool, error) { remoteRef, err := docker.ParseReference("//" + origName) if err != nil { return false, err } - - data, err := img.Inspect(context.Background(), false) - if err != nil { - return false, err - } - - sys := runtime.SystemContext() - sys.AuthFilePath = options.Authfile - - // We need to account for the arch that the image uses. It seems - // common on ARM to tweak this option to pull the correct image. See - // github.com/containers/podman/issues/6613. - sys.ArchitectureChoice = data.Architecture - - remoteImg, err := remoteRef.NewImage(context.Background(), sys) - if err != nil { - return false, err - } - - rawManifest, _, err := remoteImg.Manifest(context.Background()) - if err != nil { - return false, err - } - - remoteDigest, err := manifest.Digest(rawManifest) - if err != nil { - return false, err - } - - return img.Digest().String() != remoteDigest.String(), nil + return img.HasDifferentDigest(ctx, remoteRef) } // newerLocalImageAvailable returns true if the container and local image have different digests @@ -334,21 +375,16 @@ func newerLocalImageAvailable(runtime *libpod.Runtime, img *libimage.Image, rawI if err != nil { return false, err } - - localDigest := localImg.Digest().String() - - ctrDigest := img.Digest().String() - - return localDigest != ctrDigest, nil + return localImg.Digest().String() != img.Digest().String(), nil } // updateImage pulls the specified image. -func updateImage(runtime *libpod.Runtime, name string, options Options) (*libimage.Image, error) { +func updateImage(ctx context.Context, runtime *libpod.Runtime, name string, options *entities.AutoUpdateOptions) (*libimage.Image, error) { pullOptions := &libimage.PullOptions{} pullOptions.AuthFilePath = options.Authfile pullOptions.Writer = os.Stderr - pulledImages, err := runtime.LibimageRuntime().Pull(context.Background(), name, config.PullPolicyAlways, pullOptions) + pulledImages, err := runtime.LibimageRuntime().Pull(ctx, name, config.PullPolicyAlways, pullOptions) if err != nil { return nil, err } diff --git a/pkg/bindings/connection.go b/pkg/bindings/connection.go index fd93c5ac7..62b1655ac 100644 --- a/pkg/bindings/connection.go +++ b/pkg/bindings/connection.go @@ -327,7 +327,7 @@ func (c *Connection) DoRequest(httpBody io.Reader, httpMethod, endpoint string, uri := fmt.Sprintf("http://d/v%d.%d.%d/libpod"+endpoint, params...) logrus.Debugf("DoRequest Method: %s URI: %v", httpMethod, uri) - req, err := http.NewRequest(httpMethod, uri, httpBody) + req, err := http.NewRequestWithContext(context.WithValue(context.Background(), clientKey, c), httpMethod, uri, httpBody) if err != nil { return nil, err } @@ -337,7 +337,6 @@ func (c *Connection) DoRequest(httpBody io.Reader, httpMethod, endpoint string, for key, val := range header { req.Header.Set(key, val) } - req = req.WithContext(context.WithValue(context.Background(), clientKey, c)) // Give the Do three chances in the case of a comm/service hiccup for i := 0; i < 3; i++ { response, err = c.Client.Do(req) // nolint diff --git a/pkg/bindings/containers/attach.go b/pkg/bindings/containers/attach.go index cc12c8ab7..01c14d350 100644 --- a/pkg/bindings/containers/attach.go +++ b/pkg/bindings/containers/attach.go @@ -25,6 +25,12 @@ import ( "golang.org/x/crypto/ssh/terminal" ) +// The CloseWriter interface is used to determine whether we can do a one-sided +// close of a hijacked connection. +type CloseWriter interface { + CloseWrite() error +} + // Attach attaches to a running container func Attach(ctx context.Context, nameOrID string, stdin io.Reader, stdout io.Writer, stderr io.Writer, attachReady chan bool, options *AttachOptions) error { if options == nil { @@ -161,6 +167,12 @@ func Attach(ctx context.Context, nameOrID string, stdin io.Reader, stdout io.Wri logrus.Error("failed to write input to service: " + err.Error()) } stdinChan <- err + + if closeWrite, ok := socket.(CloseWriter); ok { + if err := closeWrite.CloseWrite(); err != nil { + logrus.Warnf("Failed to close STDIN for writing: %v", err) + } + } }() } @@ -485,6 +497,13 @@ func ExecStartAndAttach(ctx context.Context, sessionID string, options *ExecStar if err != nil { logrus.Error("failed to write input to service: " + err.Error()) } + + if closeWrite, ok := socket.(CloseWriter); ok { + logrus.Debugf("Closing STDIN") + if err := closeWrite.CloseWrite(); err != nil { + logrus.Warnf("Failed to close STDIN for writing: %v", err) + } + } }() } diff --git a/pkg/bindings/containers/types.go b/pkg/bindings/containers/types.go index 1058c7a48..cf088441f 100644 --- a/pkg/bindings/containers/types.go +++ b/pkg/bindings/containers/types.go @@ -62,6 +62,7 @@ type RestoreOptions struct { Keep *bool Name *string TCPEstablished *bool + Pod *string } //go:generate go run ../generator/generator.go CreateOptions @@ -263,4 +264,6 @@ type CopyOptions struct { // If used with CopyFromArchive and set to true it will change ownership of files from the source tar archive // to the primary uid/gid of the target container. Chown *bool `schema:"copyUIDGID"` + // Map to translate path names. + Rename map[string]string } diff --git a/pkg/bindings/containers/types_copy_options.go b/pkg/bindings/containers/types_copy_options.go index 12ad085fd..0624b450e 100644 --- a/pkg/bindings/containers/types_copy_options.go +++ b/pkg/bindings/containers/types_copy_options.go @@ -35,3 +35,19 @@ func (o *CopyOptions) GetChown() bool { } return *o.Chown } + +// WithRename +func (o *CopyOptions) WithRename(value map[string]string) *CopyOptions { + v := value + o.Rename = v + return o +} + +// GetRename +func (o *CopyOptions) GetRename() map[string]string { + var rename map[string]string + if o.Rename == nil { + return rename + } + return o.Rename +} diff --git a/pkg/bindings/containers/types_restore_options.go b/pkg/bindings/containers/types_restore_options.go index ea6c810a2..820a7696f 100644 --- a/pkg/bindings/containers/types_restore_options.go +++ b/pkg/bindings/containers/types_restore_options.go @@ -131,3 +131,19 @@ func (o *RestoreOptions) GetTCPEstablished() bool { } return *o.TCPEstablished } + +// WithPod +func (o *RestoreOptions) WithPod(value string) *RestoreOptions { + v := &value + o.Pod = v + return o +} + +// GetPod +func (o *RestoreOptions) GetPod() string { + var pod string + if o.Pod == nil { + return pod + } + return *o.Pod +} diff --git a/pkg/bindings/images/build.go b/pkg/bindings/images/build.go index 95d9d4df7..a35f461a7 100644 --- a/pkg/bindings/images/build.go +++ b/pkg/bindings/images/build.go @@ -301,6 +301,8 @@ func Build(ctx context.Context, containerFiles []string, options entities.BuildO tarContent := []string{options.ContextDirectory} newContainerFiles := []string{} + + dontexcludes := []string{"!Dockerfile", "!Containerfile", "!.dockerignore", "!.containerignore"} for _, c := range containerFiles { if c == "/dev/stdin" { content, err := ioutil.ReadAll(os.Stdin) @@ -328,6 +330,7 @@ func Build(ctx context.Context, containerFiles []string, options entities.BuildO // Do NOT add to tarfile if strings.HasPrefix(containerfile, contextDir+string(filepath.Separator)) { containerfile = strings.TrimPrefix(containerfile, contextDir+string(filepath.Separator)) + dontexcludes = append(dontexcludes, "!"+containerfile) } else { // If Containerfile does not exists assume it is in context directory, do Not add to tarfile if _, err := os.Lstat(containerfile); err != nil { @@ -349,8 +352,7 @@ func Build(ctx context.Context, containerFiles []string, options entities.BuildO } params.Set("dockerfile", string(cFileJSON)) } - - tarfile, err := nTar(excludes, tarContent...) + tarfile, err := nTar(append(excludes, dontexcludes...), tarContent...) if err != nil { logrus.Errorf("cannot tar container entries %v error: %v", tarContent, err) return nil, err @@ -389,42 +391,50 @@ func Build(ctx context.Context, containerFiles []string, options entities.BuildO dec := json.NewDecoder(body) var id string - var mErr error for { var s struct { Stream string `json:"stream,omitempty"` Error string `json:"error,omitempty"` } - if err := dec.Decode(&s); err != nil { - if errors.Is(err, io.EOF) { - if mErr == nil && id == "" { - mErr = errors.New("stream dropped, unexpected failure") - } - break - } - s.Error = err.Error() + "\n" - } select { + // FIXME(vrothberg): it seems we always hit the EOF case below, + // even when the server quit but it seems desirable to + // distinguish a proper build from a transient EOF. case <-response.Request.Context().Done(): - return &entities.BuildReport{ID: id}, mErr + return &entities.BuildReport{ID: id}, nil default: // non-blocking select } + if err := dec.Decode(&s); err != nil { + if errors.Is(err, io.ErrUnexpectedEOF) { + return nil, errors.Wrap(err, "server probably quit") + } + // EOF means the stream is over in which case we need + // to have read the id. + if errors.Is(err, io.EOF) && id != "" { + break + } + return &entities.BuildReport{ID: id}, errors.Wrap(err, "decoding stream") + } + switch { case s.Stream != "": - stdout.Write([]byte(s.Stream)) - if iidRegex.Match([]byte(s.Stream)) { + raw := []byte(s.Stream) + stdout.Write(raw) + if iidRegex.Match(raw) { id = strings.TrimSuffix(s.Stream, "\n") } case s.Error != "": - mErr = errors.New(s.Error) + // If there's an error, return directly. The stream + // will be closed on return. + return &entities.BuildReport{ID: id}, errors.New(s.Error) default: return &entities.BuildReport{ID: id}, errors.New("failed to parse build results stream, unexpected input") } } - return &entities.BuildReport{ID: id}, mErr + return &entities.BuildReport{ID: id}, nil } func nTar(excludes []string, sources ...string) (io.ReadCloser, error) { @@ -548,9 +558,13 @@ func nTar(excludes []string, sources ...string) (io.ReadCloser, error) { } func parseDockerignore(root string) ([]string, error) { - ignore, err := ioutil.ReadFile(filepath.Join(root, ".dockerignore")) - if err != nil && !os.IsNotExist(err) { - return nil, errors.Wrapf(err, "error reading .dockerignore: '%s'", root) + ignore, err := ioutil.ReadFile(filepath.Join(root, ".containerignore")) + if err != nil { + var dockerIgnoreErr error + ignore, dockerIgnoreErr = ioutil.ReadFile(filepath.Join(root, ".dockerignore")) + if dockerIgnoreErr != nil && !os.IsNotExist(dockerIgnoreErr) { + return nil, errors.Wrapf(err, "error reading .containerignore: '%s'", root) + } } rawexcludes := strings.Split(string(ignore), "\n") excludes := make([]string, 0, len(rawexcludes)) diff --git a/pkg/checkpoint/checkpoint_restore.go b/pkg/checkpoint/checkpoint_restore.go index 0d45cab5f..9fdf04933 100644 --- a/pkg/checkpoint/checkpoint_restore.go +++ b/pkg/checkpoint/checkpoint_restore.go @@ -9,6 +9,9 @@ import ( "github.com/containers/common/libimage" "github.com/containers/common/pkg/config" "github.com/containers/podman/v3/libpod" + ann "github.com/containers/podman/v3/pkg/annotations" + "github.com/containers/podman/v3/pkg/checkpoint/crutils" + "github.com/containers/podman/v3/pkg/criu" "github.com/containers/podman/v3/pkg/domain/entities" "github.com/containers/podman/v3/pkg/errorhandling" "github.com/containers/podman/v3/pkg/specgen/generate" @@ -68,6 +71,14 @@ func CRImportCheckpoint(ctx context.Context, runtime *libpod.Runtime, restoreOpt return nil, err } + if ctrConfig.Pod != "" && restoreOptions.Pod == "" { + return nil, errors.New("cannot restore pod container without --pod") + } + + if ctrConfig.Pod == "" && restoreOptions.Pod != "" { + return nil, errors.New("cannot restore non pod container into pod") + } + // This should not happen as checkpoints with these options are not exported. if len(ctrConfig.Dependencies) > 0 { return nil, errors.Errorf("Cannot import checkpoints of containers with dependencies") @@ -96,6 +107,91 @@ func CRImportCheckpoint(ctx context.Context, runtime *libpod.Runtime, restoreOpt newName = true } + if restoreOptions.Pod != "" { + // Restoring into a Pod requires much newer versions of CRIU + if !criu.CheckForCriu(criu.PodCriuVersion) { + return nil, errors.Errorf("restoring containers into pods requires at least CRIU %d", criu.PodCriuVersion) + } + // The runtime also has to support it + if !crutils.CRRuntimeSupportsPodCheckpointRestore(runtime.GetOCIRuntimePath()) { + return nil, errors.Errorf("runtime %s does not support pod restore", runtime.GetOCIRuntimePath()) + } + // Restoring into an existing Pod + ctrConfig.Pod = restoreOptions.Pod + + // According to podman pod create a pod can share the following namespaces: + // cgroup, ipc, net, pid, uts + // Let's make sure we a restoring into a pod with the same shared namespaces. + pod, err := runtime.LookupPod(ctrConfig.Pod) + if err != nil { + return nil, errors.Wrapf(err, "pod %q cannot be retrieved", ctrConfig.Pod) + } + + infraContainer, err := pod.InfraContainer() + if err != nil { + return nil, errors.Wrapf(err, "cannot retrieve infra container from pod %q", ctrConfig.Pod) + } + + // If a namespaces was shared (!= "") it needs to be set to the new infrastructure container + // If the infrastructure container does not share the same namespaces as the to be restored + // container we abort. + if ctrConfig.IPCNsCtr != "" { + if !pod.SharesIPC() { + return nil, errors.Errorf("pod %s does not share the IPC namespace", ctrConfig.Pod) + } + ctrConfig.IPCNsCtr = infraContainer.ID() + } + + if ctrConfig.NetNsCtr != "" { + if !pod.SharesNet() { + return nil, errors.Errorf("pod %s does not share the network namespace", ctrConfig.Pod) + } + ctrConfig.NetNsCtr = infraContainer.ID() + } + + if ctrConfig.PIDNsCtr != "" { + if !pod.SharesPID() { + return nil, errors.Errorf("pod %s does not share the PID namespace", ctrConfig.Pod) + } + ctrConfig.PIDNsCtr = infraContainer.ID() + } + + if ctrConfig.UTSNsCtr != "" { + if !pod.SharesUTS() { + return nil, errors.Errorf("pod %s does not share the UTS namespace", ctrConfig.Pod) + } + ctrConfig.UTSNsCtr = infraContainer.ID() + } + + if ctrConfig.CgroupNsCtr != "" { + if !pod.SharesCgroup() { + return nil, errors.Errorf("pod %s does not share the cgroup namespace", ctrConfig.Pod) + } + ctrConfig.CgroupNsCtr = infraContainer.ID() + } + + // Change SELinux labels to infrastructure container labels + ctrConfig.MountLabel = infraContainer.MountLabel() + ctrConfig.ProcessLabel = infraContainer.ProcessLabel() + + // Fix parent cgroup + cgroupPath, err := pod.CgroupPath() + if err != nil { + return nil, errors.Wrapf(err, "cannot retrieve cgroup path from pod %q", ctrConfig.Pod) + } + ctrConfig.CgroupParent = cgroupPath + + oldPodID := dumpSpec.Annotations[ann.SandboxID] + // Fix up SandboxID in the annotations + dumpSpec.Annotations[ann.SandboxID] = ctrConfig.Pod + // Fix up CreateCommand + for i, c := range ctrConfig.CreateCommand { + if c == oldPodID { + ctrConfig.CreateCommand[i] = ctrConfig.Pod + } + } + } + if len(restoreOptions.PublishPorts) > 0 { ports, _, _, err := generate.ParsePortMapping(restoreOptions.PublishPorts) if err != nil { diff --git a/pkg/checkpoint/crutils/checkpoint_restore_utils.go b/pkg/checkpoint/crutils/checkpoint_restore_utils.go index 53ff55865..3b77368bb 100644 --- a/pkg/checkpoint/crutils/checkpoint_restore_utils.go +++ b/pkg/checkpoint/crutils/checkpoint_restore_utils.go @@ -1,6 +1,7 @@ package crutils import ( + "bytes" "io" "os" "os/exec" @@ -189,3 +190,13 @@ func CRRuntimeSupportsCheckpointRestore(runtimePath string) bool { } return false } + +// CRRuntimeSupportsCheckpointRestore tests if the runtime at 'runtimePath' +// supports restoring into existing Pods. The runtime needs to support +// the CRIU option --lsm-mount-context and the existence of this is checked +// by this function. In addition it is necessary to at least have CRIU 3.16. +func CRRuntimeSupportsPodCheckpointRestore(runtimePath string) bool { + cmd := exec.Command(runtimePath, "restore", "--lsm-mount-context") + out, _ := cmd.CombinedOutput() + return bytes.Contains(out, []byte("flag needs an argument")) +} diff --git a/pkg/copy/parse.go b/pkg/copy/parse.go index 39e0e1547..93edec5fa 100644 --- a/pkg/copy/parse.go +++ b/pkg/copy/parse.go @@ -18,18 +18,6 @@ func ParseSourceAndDestination(source, destination string) (string, string, stri sourceContainer, sourcePath := parseUserInput(source) destContainer, destPath := parseUserInput(destination) - numContainers := 0 - if len(sourceContainer) > 0 { - numContainers++ - } - if len(destContainer) > 0 { - numContainers++ - } - - if numContainers != 1 { - return "", "", "", "", errors.Errorf("invalid arguments %q, %q: exactly 1 container expected but %d specified", source, destination, numContainers) - } - if len(sourcePath) == 0 || len(destPath) == 0 { return "", "", "", "", errors.Errorf("invalid arguments %q, %q: you must specify paths", source, destination) } diff --git a/pkg/criu/criu.go b/pkg/criu/criu.go index f4cce238a..2a6805979 100644 --- a/pkg/criu/criu.go +++ b/pkg/criu/criu.go @@ -1,17 +1,21 @@ package criu import ( - "github.com/checkpoint-restore/go-criu" + "github.com/checkpoint-restore/go-criu/v5" ) // MinCriuVersion for Podman at least CRIU 3.11 is required const MinCriuVersion = 31100 +// PodCriuVersion is the version of CRIU needed for +// checkpointing and restoring containers out of and into Pods. +const PodCriuVersion = 31600 + // CheckForCriu uses CRIU's go bindings to check if the CRIU // binary exists and if it at least the version Podman needs. -func CheckForCriu() bool { +func CheckForCriu(version int) bool { c := criu.MakeCriu() - result, err := c.IsCriuAtLeast(MinCriuVersion) + result, err := c.IsCriuAtLeast(version) if err != nil { return false } diff --git a/pkg/domain/entities/auto-update.go b/pkg/domain/entities/auto-update.go index c51158816..eed617bf8 100644 --- a/pkg/domain/entities/auto-update.go +++ b/pkg/domain/entities/auto-update.go @@ -4,10 +4,25 @@ package entities type AutoUpdateOptions struct { // Authfile to use when contacting registries. Authfile string + // Only check for but do not perform any update. If an update is + // pending, it will be indicated in the Updated field of + // AutoUpdateReport. + DryRun bool } // AutoUpdateReport contains the results from running auto-update. type AutoUpdateReport struct { - // Units - the restarted systemd units during auto-update. - Units []string + // ID of the container *before* an update. + ContainerID string + // Name of the container *before* an update. + ContainerName string + // Name of the image. + ImageName string + // The configured auto-update policy. + Policy string + // SystemdUnit running a container configured for auto updates. + SystemdUnit string + // Indicates the update status: true, false, failed, pending (see + // DryRun). + Updated string } diff --git a/pkg/domain/entities/containers.go b/pkg/domain/entities/containers.go index 302b35a47..564921c52 100644 --- a/pkg/domain/entities/containers.go +++ b/pkg/domain/entities/containers.go @@ -165,6 +165,8 @@ type CopyOptions struct { // it will change ownership of files from the source tar archive // to the primary uid/gid of the destination container. Chown bool + // Map to translate path names. + Rename map[string]string } type CommitReport struct { @@ -207,6 +209,7 @@ type RestoreOptions struct { TCPEstablished bool ImportPrevious string PublishPorts []specgen.PortMapping + Pod string } type RestoreReport struct { @@ -242,6 +245,8 @@ type ContainerLogsOptions struct { Names bool // Show logs since this timestamp. Since time.Time + // Show logs until this timestamp. + Until time.Time // Number of lines to display at the end of the output. Tail int64 // Show timestamps in the logs. diff --git a/pkg/domain/entities/engine_container.go b/pkg/domain/entities/engine_container.go index 28e5160db..d573e4704 100644 --- a/pkg/domain/entities/engine_container.go +++ b/pkg/domain/entities/engine_container.go @@ -8,13 +8,12 @@ import ( "github.com/containers/podman/v3/libpod/define" "github.com/containers/podman/v3/pkg/domain/entities/reports" "github.com/containers/podman/v3/pkg/specgen" - "github.com/spf13/cobra" ) type ContainerCopyFunc func() error type ContainerEngine interface { - AutoUpdate(ctx context.Context, options AutoUpdateOptions) (*AutoUpdateReport, []error) + AutoUpdate(ctx context.Context, options AutoUpdateOptions) ([]*AutoUpdateReport, []error) Config(ctx context.Context) (*config.Config, error) ContainerAttach(ctx context.Context, nameOrID string, options AttachOptions) error ContainerCheckpoint(ctx context.Context, namesOrIds []string, options CheckpointOptions) ([]*CheckpointReport, error) @@ -82,7 +81,7 @@ type ContainerEngine interface { 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) - SetupRootless(ctx context.Context, cmd *cobra.Command) error + SetupRootless(ctx context.Context, noMoveProcess bool) error SecretCreate(ctx context.Context, name string, reader io.Reader, options SecretCreateOptions) (*SecretCreateReport, error) SecretInspect(ctx context.Context, nameOrIDs []string) ([]*SecretInfoReport, []error, error) SecretList(ctx context.Context) ([]*SecretInfoReport, error) diff --git a/pkg/domain/entities/pods.go b/pkg/domain/entities/pods.go index 35f940bca..68e335f8d 100644 --- a/pkg/domain/entities/pods.go +++ b/pkg/domain/entities/pods.go @@ -112,12 +112,14 @@ type PodCreateOptions struct { Hostname string Infra bool InfraImage string + InfraName string InfraCommand string InfraConmonPidFile string Labels map[string]string Name string Net *NetOptions Share []string + Pid string Cpus float64 CpusetCpus string } @@ -146,6 +148,18 @@ func (p *PodCreateOptions) CPULimits() *specs.LinuxCPU { return cpu } +func setNamespaces(p *PodCreateOptions) ([4]specgen.Namespace, error) { + allNS := [4]specgen.Namespace{} + if p.Pid != "" { + pid, err := specgen.ParseNamespace(p.Pid) + if err != nil { + return [4]specgen.Namespace{}, err + } + allNS[0] = pid + } + return allNS, nil +} + func (p *PodCreateOptions) ToPodSpecGen(s *specgen.PodSpecGenerator) error { // Basic Config s.Name = p.Name @@ -159,6 +173,7 @@ func (p *PodCreateOptions) ToPodSpecGen(s *specgen.PodSpecGenerator) error { s.InfraConmonPidFile = p.InfraConmonPidFile } s.InfraImage = p.InfraImage + s.InfraName = p.InfraName s.SharedNamespaces = p.Share s.PodCreateCommand = p.CreateCommand @@ -178,6 +193,14 @@ func (p *PodCreateOptions) ToPodSpecGen(s *specgen.PodSpecGenerator) error { s.NoManageHosts = p.Net.NoHosts s.HostAdd = p.Net.AddHosts + namespaces, err := setNamespaces(p) + if err != nil { + return err + } + if !namespaces[0].IsDefault() { + s.Pid = namespaces[0] + } + // Cgroup s.CgroupParent = p.CGroupParent diff --git a/pkg/domain/filters/volumes.go b/pkg/domain/filters/volumes.go index df23c31c0..d55c44ef5 100644 --- a/pkg/domain/filters/volumes.go +++ b/pkg/domain/filters/volumes.go @@ -51,6 +51,12 @@ func GenerateVolumeFilters(filters url.Values) ([]libpod.VolumeFilter, error) { } return false }) + case "until": + f, err := createUntilFilterVolumeFunction(val) + if err != nil { + return nil, err + } + vf = append(vf, f) case "dangling": danglingVal := val invert := false @@ -93,16 +99,11 @@ func GeneratePruneVolumeFilters(filters url.Values) ([]libpod.VolumeFilter, erro return util.MatchLabelFilters([]string{filterVal}, v.Labels()) }) case "until": - until, err := util.ComputeUntilTimestamp([]string{filterVal}) + f, err := createUntilFilterVolumeFunction(filterVal) if err != nil { return nil, err } - vf = append(vf, func(v *libpod.Volume) bool { - if !until.IsZero() && v.CreatedTime().Before(until) { - return true - } - return false - }) + vf = append(vf, f) default: return nil, errors.Errorf("%q is an invalid volume filter", filter) } @@ -110,3 +111,16 @@ func GeneratePruneVolumeFilters(filters url.Values) ([]libpod.VolumeFilter, erro } return vf, nil } + +func createUntilFilterVolumeFunction(filter string) (libpod.VolumeFilter, error) { + until, err := util.ComputeUntilTimestamp([]string{filter}) + if err != nil { + return nil, err + } + return func(v *libpod.Volume) bool { + if !until.IsZero() && v.CreatedTime().Before(until) { + return true + } + return false + }, nil +} diff --git a/pkg/domain/infra/abi/archive.go b/pkg/domain/infra/abi/archive.go index 1a5bb6dc4..b60baa935 100644 --- a/pkg/domain/infra/abi/archive.go +++ b/pkg/domain/infra/abi/archive.go @@ -12,7 +12,7 @@ func (ic *ContainerEngine) ContainerCopyFromArchive(ctx context.Context, nameOrI if err != nil { return nil, err } - return container.CopyFromArchive(ctx, containerPath, options.Chown, reader) + return container.CopyFromArchive(ctx, containerPath, options.Chown, options.Rename, reader) } func (ic *ContainerEngine) ContainerCopyToArchive(ctx context.Context, nameOrID string, containerPath string, writer io.Writer) (entities.ContainerCopyFunc, error) { diff --git a/pkg/domain/infra/abi/auto-update.go b/pkg/domain/infra/abi/auto-update.go index c9d7f2130..b98ee1cb2 100644 --- a/pkg/domain/infra/abi/auto-update.go +++ b/pkg/domain/infra/abi/auto-update.go @@ -7,11 +7,6 @@ import ( "github.com/containers/podman/v3/pkg/domain/entities" ) -func (ic *ContainerEngine) AutoUpdate(ctx context.Context, options entities.AutoUpdateOptions) (*entities.AutoUpdateReport, []error) { - // Convert the entities options to the autoupdate ones. We can't use - // them in the entities package as low-level packages must not leak - // into the remote client. - autoOpts := autoupdate.Options{Authfile: options.Authfile} - units, failures := autoupdate.AutoUpdate(ic.Libpod, autoOpts) - return &entities.AutoUpdateReport{Units: units}, failures +func (ic *ContainerEngine) AutoUpdate(ctx context.Context, options entities.AutoUpdateOptions) ([]*entities.AutoUpdateReport, []error) { + return autoupdate.AutoUpdate(ctx, ic.Libpod, options) } diff --git a/pkg/domain/infra/abi/containers.go b/pkg/domain/infra/abi/containers.go index 2c5300ccb..2003879b8 100644 --- a/pkg/domain/infra/abi/containers.go +++ b/pkg/domain/infra/abi/containers.go @@ -261,6 +261,24 @@ func (ic *ContainerEngine) ContainerRestart(ctx context.Context, namesOrIds []st return reports, nil } +func (ic *ContainerEngine) removeContainer(ctx context.Context, ctr *libpod.Container, options entities.RmOptions) error { + err := ic.Libpod.RemoveContainer(ctx, ctr, options.Force, options.Volumes) + if err == nil { + return nil + } + logrus.Debugf("Failed to remove container %s: %s", ctr.ID(), err.Error()) + switch errors.Cause(err) { + case define.ErrNoSuchCtr: + if options.Ignore { + logrus.Debugf("Ignoring error (--allow-missing): %v", err) + return nil + } + case define.ErrCtrRemoved: + return nil + } + return err +} + func (ic *ContainerEngine) ContainerRm(ctx context.Context, namesOrIds []string, options entities.RmOptions) ([]*entities.RmReport, error) { reports := []*entities.RmReport{} @@ -318,21 +336,7 @@ func (ic *ContainerEngine) ContainerRm(ctx context.Context, namesOrIds []string, } errMap, err := parallelctr.ContainerOp(ctx, ctrs, func(c *libpod.Container) error { - err := ic.Libpod.RemoveContainer(ctx, c, options.Force, options.Volumes) - if err == nil { - return nil - } - logrus.Debugf("Failed to remove container %s: %s", c.ID(), err.Error()) - switch errors.Cause(err) { - case define.ErrNoSuchCtr: - if options.Ignore { - logrus.Debugf("Ignoring error (--allow-missing): %v", err) - return nil - } - case define.ErrCtrRemoved: - return nil - } - return err + return ic.removeContainer(ctx, c, options) }) if err != nil { return nil, err @@ -525,6 +529,7 @@ func (ic *ContainerEngine) ContainerRestore(ctx context.Context, namesOrIds []st IgnoreStaticIP: options.IgnoreStaticIP, IgnoreStaticMAC: options.IgnoreStaticMAC, ImportPrevious: options.ImportPrevious, + Pod: options.Pod, } filterFuncs := []libpod.ContainerFilter{ @@ -614,7 +619,7 @@ func makeExecConfig(options entities.ExecOptions, rt *libpod.Runtime) (*libpod.E return nil, errors.Wrapf(err, "error retrieving Libpod configuration to build exec exit command") } // TODO: Add some ability to toggle syslog - exitCommandArgs, err := generate.CreateExitCommandArgs(storageConfig, runtimeConfig, false, true, true) + exitCommandArgs, err := generate.CreateExitCommandArgs(storageConfig, runtimeConfig, false, false, true) if err != nil { return nil, errors.Wrapf(err, "error constructing exit command for exec session") } @@ -791,6 +796,11 @@ func (ic *ContainerEngine) ContainerStart(ctx context.Context, namesOrIds []stri Err: err, ExitCode: exitCode, }) + if ctr.AutoRemove() { + if err := ic.removeContainer(ctx, ctr, entities.RmOptions{}); err != nil { + logrus.Errorf("Error removing container %s: %v", ctr.ID(), err) + } + } return reports, errors.Wrapf(err, "unable to start container %s", ctr.ID()) } @@ -827,9 +837,6 @@ func (ic *ContainerEngine) ContainerStart(ctx context.Context, namesOrIds []stri ExitCode: 125, } if err := ctr.Start(ctx, true); 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") @@ -838,6 +845,11 @@ func (ic *ContainerEngine) ContainerStart(ctx context.Context, namesOrIds []stri } report.Err = errors.Wrapf(err, "unable to start container %q", ctr.ID()) reports = append(reports, report) + if ctr.AutoRemove() { + if err := ic.removeContainer(ctx, ctr, entities.RmOptions{}); err != nil { + logrus.Errorf("Error removing container %s: %v", ctr.ID(), err) + } + } continue } report.ExitCode = 0 @@ -987,6 +999,7 @@ func (ic *ContainerEngine) ContainerLogs(ctx context.Context, containers []strin Details: options.Details, Follow: options.Follow, Since: options.Since, + Until: options.Until, Tail: options.Tail, Timestamps: options.Timestamps, UseName: options.Names, diff --git a/pkg/domain/infra/abi/images.go b/pkg/domain/infra/abi/images.go index 6d1acb590..e8739615d 100644 --- a/pkg/domain/infra/abi/images.go +++ b/pkg/domain/infra/abi/images.go @@ -89,7 +89,7 @@ func toDomainHistoryLayer(layer *libimage.ImageHistory) entities.ImageHistoryLay } func (ir *ImageEngine) History(ctx context.Context, nameOrID string, opts entities.ImageHistoryOptions) (*entities.ImageHistoryReport, error) { - image, _, err := ir.Libpod.LibimageRuntime().LookupImage(nameOrID, &libimage.LookupImageOptions{IgnorePlatform: true}) + image, _, err := ir.Libpod.LibimageRuntime().LookupImage(nameOrID, nil) if err != nil { return nil, err } @@ -245,7 +245,7 @@ func (ir *ImageEngine) Inspect(ctx context.Context, namesOrIDs []string, opts en reports := []*entities.ImageInspectReport{} errs := []error{} for _, i := range namesOrIDs { - img, _, err := ir.Libpod.LibimageRuntime().LookupImage(i, &libimage.LookupImageOptions{IgnorePlatform: true}) + img, _, err := ir.Libpod.LibimageRuntime().LookupImage(i, nil) if err != nil { // This is probably a no such image, treat as nonfatal. errs = append(errs, err) @@ -321,7 +321,7 @@ func (ir *ImageEngine) Push(ctx context.Context, source string, destination stri } func (ir *ImageEngine) Tag(ctx context.Context, nameOrID string, tags []string, options entities.ImageTagOptions) error { - image, _, err := ir.Libpod.LibimageRuntime().LookupImage(nameOrID, &libimage.LookupImageOptions{IgnorePlatform: true}) + image, _, err := ir.Libpod.LibimageRuntime().LookupImage(nameOrID, nil) if err != nil { return err } @@ -334,7 +334,7 @@ func (ir *ImageEngine) Tag(ctx context.Context, nameOrID string, tags []string, } func (ir *ImageEngine) Untag(ctx context.Context, nameOrID string, tags []string, options entities.ImageUntagOptions) error { - image, _, err := ir.Libpod.LibimageRuntime().LookupImage(nameOrID, &libimage.LookupImageOptions{IgnorePlatform: true}) + image, _, err := ir.Libpod.LibimageRuntime().LookupImage(nameOrID, nil) if err != nil { return err } @@ -454,7 +454,7 @@ func (ir *ImageEngine) Build(ctx context.Context, containerFiles []string, opts } func (ir *ImageEngine) Tree(ctx context.Context, nameOrID string, opts entities.ImageTreeOptions) (*entities.ImageTreeReport, error) { - image, _, err := ir.Libpod.LibimageRuntime().LookupImage(nameOrID, &libimage.LookupImageOptions{IgnorePlatform: true}) + image, _, err := ir.Libpod.LibimageRuntime().LookupImage(nameOrID, nil) if err != nil { return nil, err } diff --git a/pkg/domain/infra/abi/images_list.go b/pkg/domain/infra/abi/images_list.go index b0e947991..2ec4ad244 100644 --- a/pkg/domain/infra/abi/images_list.go +++ b/pkg/domain/infra/abi/images_list.go @@ -30,12 +30,16 @@ func (ir *ImageEngine) List(ctx context.Context, opts entities.ImageListOptions) for j, d := range img.Digests() { digests[j] = string(d) } + isDangling, err := img.IsDangling(ctx) + if err != nil { + return nil, errors.Wrapf(err, "error checking if image %q is dangling", img.ID()) + } e := entities.ImageSummary{ ID: img.ID(), // ConfigDigest: string(img.ConfigDigest), Created: img.Created().Unix(), - Dangling: img.IsDangling(), + Dangling: isDangling, Digest: string(img.Digest()), RepoDigests: digests, History: img.NamesHistory(), diff --git a/pkg/domain/infra/abi/manifest.go b/pkg/domain/infra/abi/manifest.go index e905036be..666bc997d 100644 --- a/pkg/domain/infra/abi/manifest.go +++ b/pkg/domain/infra/abi/manifest.go @@ -47,7 +47,7 @@ func (ir *ImageEngine) ManifestCreate(ctx context.Context, names []string, image // ManifestExists checks if a manifest list with the given name exists in local storage func (ir *ImageEngine) ManifestExists(ctx context.Context, name string) (*entities.BoolReport, error) { - image, _, err := ir.Libpod.LibimageRuntime().LookupImage(name, &libimage.LookupImageOptions{IgnorePlatform: true}) + _, err := ir.Libpod.LibimageRuntime().LookupManifestList(name) if err != nil { if errors.Cause(err) == storage.ErrImageUnknown { return &entities.BoolReport{Value: false}, nil @@ -55,11 +55,7 @@ func (ir *ImageEngine) ManifestExists(ctx context.Context, name string) (*entiti return nil, err } - isManifestList, err := image.IsManifestList(ctx) - if err != nil { - return nil, err - } - return &entities.BoolReport{Value: isManifestList}, nil + return &entities.BoolReport{Value: true}, nil } // ManifestInspect returns the content of a manifest list or image @@ -341,6 +337,7 @@ func (ir *ImageEngine) ManifestPush(ctx context.Context, name, destination strin pushOptions.ManifestMIMEType = manifestType pushOptions.RemoveSignatures = opts.RemoveSignatures pushOptions.SignBy = opts.SignBy + pushOptions.InsecureSkipTLSVerify = opts.SkipTLSVerify if opts.All { pushOptions.ImageListSelection = cp.CopyAllImages diff --git a/pkg/domain/infra/abi/play.go b/pkg/domain/infra/abi/play.go index 4782f0d01..d257bad18 100644 --- a/pkg/domain/infra/abi/play.go +++ b/pkg/domain/infra/abi/play.go @@ -277,7 +277,10 @@ func (ic *ContainerEngine) playKubePod(ctx context.Context, podName string, podY // registry on localhost. pullPolicy := config.PullPolicyNewer if len(container.ImagePullPolicy) > 0 { - pullPolicy, err = config.ParsePullPolicy(string(container.ImagePullPolicy)) + // Make sure to lower the strings since K8s pull policy + // may be capitalized (see bugzilla.redhat.com/show_bug.cgi?id=1985905). + rawPolicy := string(container.ImagePullPolicy) + pullPolicy, err = config.ParsePullPolicy(strings.ToLower(rawPolicy)) if err != nil { return nil, err } diff --git a/pkg/domain/infra/abi/system.go b/pkg/domain/infra/abi/system.go index ebe59e871..bc98edd06 100644 --- a/pkg/domain/infra/abi/system.go +++ b/pkg/domain/infra/abi/system.go @@ -24,7 +24,6 @@ import ( "github.com/containers/storage/pkg/unshare" "github.com/pkg/errors" "github.com/sirupsen/logrus" - "github.com/spf13/cobra" "github.com/spf13/pflag" ) @@ -57,7 +56,7 @@ func (ic *ContainerEngine) Info(ctx context.Context) (*define.Info, error) { return info, err } -func (ic *ContainerEngine) SetupRootless(_ context.Context, cmd *cobra.Command) error { +func (ic *ContainerEngine) SetupRootless(_ context.Context, noMoveProcess bool) error { // do it only after podman has already re-execed and running with uid==0. hasCapSysAdmin, err := unshare.HasCapSysAdmin() if err != nil { @@ -104,6 +103,9 @@ func (ic *ContainerEngine) SetupRootless(_ context.Context, cmd *cobra.Command) if became { os.Exit(ret) } + if noMoveProcess { + return nil + } // if there is no pid file, try to join existing containers, and create a pause process. ctrs, err := ic.Libpod.GetRunningContainers() @@ -118,9 +120,10 @@ func (ic *ContainerEngine) SetupRootless(_ context.Context, cmd *cobra.Command) } became, ret, err = rootless.TryJoinFromFilePaths(pausePidPath, true, paths) + if err := movePauseProcessToScope(ic.Libpod); err != nil { - conf, err := ic.Config(context.Background()) - if err != nil { + conf, err2 := ic.Config(context.Background()) + if err2 != nil { return err } if conf.Engine.CgroupManager == config.SystemdCgroupsManager { @@ -148,7 +151,6 @@ func movePauseProcessToScope(r *libpod.Runtime) error { if err != nil { return errors.Wrapf(err, "could not get pause process pid file path") } - data, err := ioutil.ReadFile(pausePidPath) if err != nil { return errors.Wrapf(err, "cannot read pause pid file") @@ -403,6 +405,8 @@ func (ic *ContainerEngine) Unshare(ctx context.Context, args []string, options e if err != nil { return err } + // make sure to unlock, unshare can run for a long time + rootlesscni.Lock.Unlock() defer rootlesscni.Cleanup(ic.Libpod) return rootlesscni.Do(unshare) } diff --git a/pkg/domain/infra/runtime_abi.go b/pkg/domain/infra/runtime_abi.go index ca201b5ae..177e9cff4 100644 --- a/pkg/domain/infra/runtime_abi.go +++ b/pkg/domain/infra/runtime_abi.go @@ -33,6 +33,7 @@ func NewImageEngine(facts *entities.PodmanConfig) (entities.ImageEngine, error) r, err := NewLibpodImageRuntime(facts.FlagSet, facts) return r, err case entities.TunnelMode: + // TODO: look at me! ctx, err := bindings.NewConnectionWithIdentity(context.Background(), facts.URI, facts.Identity) return &tunnel.ImageEngine{ClientCtx: ctx}, err } diff --git a/pkg/domain/infra/tunnel/auto-update.go b/pkg/domain/infra/tunnel/auto-update.go index 41165cc74..038c60537 100644 --- a/pkg/domain/infra/tunnel/auto-update.go +++ b/pkg/domain/infra/tunnel/auto-update.go @@ -7,6 +7,6 @@ import ( "github.com/pkg/errors" ) -func (ic *ContainerEngine) AutoUpdate(ctx context.Context, options entities.AutoUpdateOptions) (*entities.AutoUpdateReport, []error) { +func (ic *ContainerEngine) AutoUpdate(ctx context.Context, options entities.AutoUpdateOptions) ([]*entities.AutoUpdateReport, []error) { return nil, []error{errors.New("not implemented")} } diff --git a/pkg/domain/infra/tunnel/containers.go b/pkg/domain/infra/tunnel/containers.go index 56315f46f..58f9c5fb0 100644 --- a/pkg/domain/infra/tunnel/containers.go +++ b/pkg/domain/infra/tunnel/containers.go @@ -369,10 +369,11 @@ func (ic *ContainerEngine) ContainerCreate(ctx context.Context, s *specgen.SpecG func (ic *ContainerEngine) ContainerLogs(_ context.Context, nameOrIDs []string, opts entities.ContainerLogsOptions) error { since := opts.Since.Format(time.RFC3339) + until := opts.Until.Format(time.RFC3339) tail := strconv.FormatInt(opts.Tail, 10) stdout := opts.StdoutWriter != nil stderr := opts.StderrWriter != nil - options := new(containers.LogOptions).WithFollow(opts.Follow).WithSince(since).WithStderr(stderr) + options := new(containers.LogOptions).WithFollow(opts.Follow).WithSince(since).WithUntil(until).WithStderr(stderr) options.WithStdout(stdout).WithTail(tail) var err error @@ -541,6 +542,17 @@ func (ic *ContainerEngine) ContainerStart(ctx context.Context, namesOrIds []stri return nil, err } removeOptions := new(containers.RemoveOptions).WithVolumes(true).WithForce(false) + removeContainer := func(id string) { + if err := containers.Remove(ic.ClientCtx, id, removeOptions); err != nil { + if errorhandling.Contains(err, define.ErrNoSuchCtr) || + errorhandling.Contains(err, define.ErrCtrRemoved) { + logrus.Debugf("Container %s does not exist: %v", id, err) + } else { + logrus.Errorf("Error removing container %s: %v", id, err) + } + } + } + // There can only be one container if attach was used for i, ctr := range ctrs { name := ctr.ID @@ -568,6 +580,9 @@ func (ic *ContainerEngine) ContainerStart(ctx context.Context, namesOrIds []stri } if err != nil { + if ctr.AutoRemove { + removeContainer(ctr.ID) + } report.ExitCode = define.ExitCode(report.Err) report.Err = err reports = append(reports, &report) @@ -582,16 +597,10 @@ func (ic *ContainerEngine) ContainerStart(ctx context.Context, namesOrIds []stri logrus.Errorf("Failed to check if %s should restart: %v", ctr.ID, err) return } + logrus.Errorf("Should restart: %v", shouldRestart) - if !shouldRestart { - if err := containers.Remove(ic.ClientCtx, ctr.ID, removeOptions); err != nil { - if errorhandling.Contains(err, define.ErrNoSuchCtr) || - errorhandling.Contains(err, define.ErrCtrRemoved) { - logrus.Debugf("Container %s does not exist: %v", ctr.ID, err) - } else { - logrus.Errorf("Error removing container %s: %v", ctr.ID, err) - } - } + if !shouldRestart && ctr.AutoRemove { + removeContainer(ctr.ID) } }() } @@ -844,7 +853,8 @@ func (ic *ContainerEngine) ContainerPort(ctx context.Context, nameOrID string, o } func (ic *ContainerEngine) ContainerCopyFromArchive(ctx context.Context, nameOrID, path string, reader io.Reader, options entities.CopyOptions) (entities.ContainerCopyFunc, error) { - return containers.CopyFromArchiveWithOptions(ic.ClientCtx, nameOrID, path, reader, new(containers.CopyOptions).WithChown(options.Chown)) + copyOptions := new(containers.CopyOptions).WithChown(options.Chown).WithRename(options.Rename) + return containers.CopyFromArchiveWithOptions(ic.ClientCtx, nameOrID, path, reader, copyOptions) } func (ic *ContainerEngine) ContainerCopyToArchive(ctx context.Context, nameOrID string, path string, writer io.Writer) (entities.ContainerCopyFunc, error) { diff --git a/pkg/domain/infra/tunnel/system.go b/pkg/domain/infra/tunnel/system.go index 7400d3771..6b43cf038 100644 --- a/pkg/domain/infra/tunnel/system.go +++ b/pkg/domain/infra/tunnel/system.go @@ -7,14 +7,13 @@ import ( "github.com/containers/podman/v3/libpod/define" "github.com/containers/podman/v3/pkg/bindings/system" "github.com/containers/podman/v3/pkg/domain/entities" - "github.com/spf13/cobra" ) func (ic *ContainerEngine) Info(ctx context.Context) (*define.Info, error) { return system.Info(ic.ClientCtx, nil) } -func (ic *ContainerEngine) SetupRootless(_ context.Context, cmd *cobra.Command) error { +func (ic *ContainerEngine) SetupRootless(_ context.Context, noMoveProcess bool) error { panic(errors.New("rootless engine mode is not supported when tunneling")) } diff --git a/pkg/ps/ps.go b/pkg/ps/ps.go index ef79973d6..54079baa1 100644 --- a/pkg/ps/ps.go +++ b/pkg/ps/ps.go @@ -9,7 +9,6 @@ import ( "strings" "time" - "github.com/containers/common/libimage" "github.com/containers/podman/v3/libpod" "github.com/containers/podman/v3/libpod/define" "github.com/containers/podman/v3/pkg/domain/entities" @@ -272,8 +271,7 @@ func ListStorageContainer(rt *libpod.Runtime, ctr storage.Container) (entities.L imageName := "" if ctr.ImageID != "" { - lookupOptions := &libimage.LookupImageOptions{IgnorePlatform: true} - image, _, err := rt.LibimageRuntime().LookupImage(ctr.ImageID, lookupOptions) + image, _, err := rt.LibimageRuntime().LookupImage(ctr.ImageID, nil) if err != nil { return ps, err } diff --git a/pkg/rootless/rootless_linux.go b/pkg/rootless/rootless_linux.go index f76eab0e3..9ef56acb4 100644 --- a/pkg/rootless/rootless_linux.go +++ b/pkg/rootless/rootless_linux.go @@ -14,11 +14,13 @@ import ( "os/user" "runtime" "strconv" + "strings" "sync" "unsafe" "github.com/containers/podman/v3/pkg/errorhandling" "github.com/containers/storage/pkg/idtools" + pmount "github.com/containers/storage/pkg/mount" "github.com/containers/storage/pkg/unshare" "github.com/pkg/errors" "github.com/sirupsen/logrus" @@ -235,6 +237,24 @@ func becomeRootInUserNS(pausePid, fileToRead string, fileOutput *os.File) (_ boo return false, 0, nil } + if mounts, err := pmount.GetMounts(); err == nil { + for _, m := range mounts { + if m.Mountpoint == "/" { + isShared := false + for _, o := range strings.Split(m.Optional, ",") { + if strings.HasPrefix(o, "shared:") { + isShared = true + break + } + } + if !isShared { + logrus.Warningf("%q is not a shared mount, this could cause issues or missing mounts with rootless containers", m.Mountpoint) + } + break + } + } + } + cPausePid := C.CString(pausePid) defer C.free(unsafe.Pointer(cPausePid)) diff --git a/pkg/specgen/generate/container.go b/pkg/specgen/generate/container.go index ca92f558d..1f6d00eb7 100644 --- a/pkg/specgen/generate/container.go +++ b/pkg/specgen/generate/container.go @@ -26,8 +26,7 @@ func CompleteSpec(ctx context.Context, r *libpod.Runtime, s *specgen.SpecGenerat var inspectData *libimage.ImageData var err error if s.Image != "" { - lookupOptions := &libimage.LookupImageOptions{IgnorePlatform: true} - newImage, _, err = r.LibimageRuntime().LookupImage(s.Image, lookupOptions) + newImage, _, err = r.LibimageRuntime().LookupImage(s.Image, nil) if err != nil { return nil, err } diff --git a/pkg/specgen/generate/container_create.go b/pkg/specgen/generate/container_create.go index e2901f0b6..4e3a86ae4 100644 --- a/pkg/specgen/generate/container_create.go +++ b/pkg/specgen/generate/container_create.go @@ -92,8 +92,7 @@ func MakeContainer(ctx context.Context, rt *libpod.Runtime, s *specgen.SpecGener options = append(options, libpod.WithRootFS(s.Rootfs)) } else { var resolvedImageName string - lookupOptions := &libimage.LookupImageOptions{IgnorePlatform: true} - newImage, resolvedImageName, err = rt.LibimageRuntime().LookupImage(s.Image, lookupOptions) + newImage, resolvedImageName, err = rt.LibimageRuntime().LookupImage(s.Image, nil) if err != nil { return nil, err } @@ -154,7 +153,15 @@ func MakeContainer(ctx context.Context, rt *libpod.Runtime, s *specgen.SpecGener if err != nil { return nil, err } - return rt.NewContainer(ctx, runtimeSpec, options...) + + ctr, err := rt.NewContainer(ctx, runtimeSpec, options...) + if err != nil { + return ctr, err + } + + // Copy the content from the underlying image into the newly created + // volume if configured to do so. + return ctr, rt.PrepareVolumeOnCreateContainer(ctx, ctr) } func extractCDIDevices(s *specgen.SpecGenerator) []libpod.CtrCreateOption { diff --git a/pkg/specgen/generate/kube/kube.go b/pkg/specgen/generate/kube/kube.go index fb563f935..fb7eb99a2 100644 --- a/pkg/specgen/generate/kube/kube.go +++ b/pkg/specgen/generate/kube/kube.go @@ -6,10 +6,12 @@ import ( "fmt" "net" "strings" + "time" "github.com/containers/common/libimage" "github.com/containers/common/pkg/parse" "github.com/containers/common/pkg/secrets" + "github.com/containers/image/v5/manifest" ann "github.com/containers/podman/v3/pkg/annotations" "github.com/containers/podman/v3/pkg/specgen" "github.com/containers/podman/v3/pkg/specgen/generate" @@ -129,6 +131,10 @@ func ToSpecGen(ctx context.Context, opts *CtrSpecGenOptions) (*specgen.SpecGener } setupSecurityContext(s, opts.Container) + err := setupLivenessProbe(s, opts.Container, opts.RestartPolicy) + if err != nil { + return nil, errors.Wrap(err, "Failed to configure livenessProbe") + } // Since we prefix the container name with pod name to work-around the uniqueness requirement, // the seccomp profile should reference the actual container name from the YAML @@ -270,10 +276,11 @@ func ToSpecGen(ctx context.Context, opts *CtrSpecGenOptions) (*specgen.SpecGener return nil, err } + volume.MountPath = dest switch volumeSource.Type { case KubeVolumeTypeBindMount: mount := spec.Mount{ - Destination: dest, + Destination: volume.MountPath, Source: volumeSource.Source, Type: "bind", Options: options, @@ -281,7 +288,7 @@ func ToSpecGen(ctx context.Context, opts *CtrSpecGenOptions) (*specgen.SpecGener s.Mounts = append(s.Mounts, mount) case KubeVolumeTypeNamed: namedVolume := specgen.NamedVolume{ - Dest: dest, + Dest: volume.MountPath, Name: volumeSource.Source, Options: options, } @@ -324,12 +331,105 @@ func parseMountPath(mountPath string, readOnly bool) (string, []string, error) { options = strings.Split(splitVol[1], ",") } if err := parse.ValidateVolumeCtrDir(dest); err != nil { - return "", options, errors.Wrapf(err, "error in parsing MountPath") + return "", options, errors.Wrapf(err, "parsing MountPath") } if readOnly { options = append(options, "ro") } - return dest, options, nil + opts, err := parse.ValidateVolumeOpts(options) + if err != nil { + return "", opts, errors.Wrapf(err, "parsing MountOptions") + } + return dest, opts, nil +} + +func setupLivenessProbe(s *specgen.SpecGenerator, containerYAML v1.Container, restartPolicy string) error { + var err error + if containerYAML.LivenessProbe == nil { + return nil + } + emptyHandler := v1.Handler{} + if containerYAML.LivenessProbe.Handler != emptyHandler { + var commandString string + failureCmd := "exit 1" + probe := containerYAML.LivenessProbe + probeHandler := probe.Handler + + // append `exit 1` to `cmd` so healthcheck can be marked as `unhealthy`. + // append `kill 1` to `cmd` if appropriate restart policy is configured. + if restartPolicy == "always" || restartPolicy == "onfailure" { + // container will be restarted so we can kill init. + failureCmd = "kill 1" + } + + // configure healthcheck on the basis of Handler Actions. + if probeHandler.Exec != nil { + execString := strings.Join(probeHandler.Exec.Command, " ") + commandString = fmt.Sprintf("%s || %s", execString, failureCmd) + } else if probeHandler.HTTPGet != nil { + commandString = fmt.Sprintf("curl %s://%s:%d/%s || %s", probeHandler.HTTPGet.Scheme, probeHandler.HTTPGet.Host, probeHandler.HTTPGet.Port.IntValue(), probeHandler.HTTPGet.Path, failureCmd) + } else if probeHandler.TCPSocket != nil { + commandString = fmt.Sprintf("nc -z -v %s %d || %s", probeHandler.TCPSocket.Host, probeHandler.TCPSocket.Port.IntValue(), failureCmd) + } + s.HealthConfig, err = makeHealthCheck(commandString, probe.PeriodSeconds, probe.FailureThreshold, probe.TimeoutSeconds, probe.InitialDelaySeconds) + if err != nil { + return err + } + return nil + } + return nil +} + +func makeHealthCheck(inCmd string, interval int32, retries int32, timeout int32, startPeriod int32) (*manifest.Schema2HealthConfig, error) { + // Every healthcheck requires a command + if len(inCmd) == 0 { + return nil, errors.New("Must define a healthcheck command for all healthchecks") + } + + // first try to parse option value as JSON array of strings... + cmd := []string{} + + if inCmd == "none" { + cmd = []string{"NONE"} + } else { + err := json.Unmarshal([]byte(inCmd), &cmd) + if err != nil { + // ...otherwise pass it to "/bin/sh -c" inside the container + cmd = []string{"CMD-SHELL"} + cmd = append(cmd, strings.Split(inCmd, " ")...) + } + } + hc := manifest.Schema2HealthConfig{ + Test: cmd, + } + + if interval < 1 { + //kubernetes interval defaults to 10 sec and cannot be less than 1 + interval = 10 + } + hc.Interval = (time.Duration(interval) * time.Second) + if retries < 1 { + //kubernetes retries defaults to 3 + retries = 3 + } + hc.Retries = int(retries) + if timeout < 1 { + //kubernetes timeout defaults to 1 + timeout = 1 + } + timeoutDuration := (time.Duration(timeout) * time.Second) + if timeoutDuration < time.Duration(1) { + return nil, errors.New("healthcheck-timeout must be at least 1 second") + } + hc.Timeout = timeoutDuration + + startPeriodDuration := (time.Duration(startPeriod) * time.Second) + if startPeriodDuration < time.Duration(0) { + return nil, errors.New("healthcheck-start-period must be 0 seconds or greater") + } + hc.StartPeriod = startPeriodDuration + + return &hc, nil } func setupSecurityContext(s *specgen.SpecGenerator, containerYAML v1.Container) { diff --git a/pkg/specgen/generate/oci.go b/pkg/specgen/generate/oci.go index bf8d44ed6..6e310d8a6 100644 --- a/pkg/specgen/generate/oci.go +++ b/pkg/specgen/generate/oci.go @@ -321,6 +321,10 @@ func SpecGenToOCI(ctx context.Context, s *specgen.SpecGenerator, rt *libpod.Runt } } + for _, dev := range s.DeviceCGroupRule { + g.AddLinuxResourcesDevice(true, dev.Type, dev.Major, dev.Minor, dev.Access) + } + BlockAccessToKernelFilesystems(s.Privileged, s.PidNS.IsHost(), s.Mask, s.Unmask, &g) for name, val := range s.Env { diff --git a/pkg/specgen/generate/pod_create.go b/pkg/specgen/generate/pod_create.go index 023ebb41e..aab29499e 100644 --- a/pkg/specgen/generate/pod_create.go +++ b/pkg/specgen/generate/pod_create.go @@ -98,10 +98,18 @@ func createPodOptions(p *specgen.PodSpecGenerator, rt *libpod.Runtime) ([]libpod options = append(options, libpod.WithInfraImage(p.InfraImage)) } + if len(p.InfraName) > 0 { + options = append(options, libpod.WithInfraName(p.InfraName)) + } + if len(p.InfraCommand) > 0 { options = append(options, libpod.WithInfraCommand(p.InfraCommand)) } + if !p.Pid.IsDefault() { + options = append(options, libpod.WithPodPidNS(p.Pid)) + } + switch p.NetNS.NSMode { case specgen.Default, "": if p.NoInfra { diff --git a/pkg/specgen/pod_validate.go b/pkg/specgen/pod_validate.go index c746bcd1a..bca7b6dbe 100644 --- a/pkg/specgen/pod_validate.go +++ b/pkg/specgen/pod_validate.go @@ -36,6 +36,9 @@ func (p *PodSpecGenerator) Validate() error { if len(p.InfraImage) > 0 { return exclusivePodOptions("NoInfra", "InfraImage") } + if len(p.InfraName) > 0 { + return exclusivePodOptions("NoInfra", "InfraName") + } if len(p.SharedNamespaces) > 0 { return exclusivePodOptions("NoInfra", "SharedNamespaces") } diff --git a/pkg/specgen/podspecgen.go b/pkg/specgen/podspecgen.go index 000a787ea..02237afe9 100644 --- a/pkg/specgen/podspecgen.go +++ b/pkg/specgen/podspecgen.go @@ -43,6 +43,12 @@ type PodBasicConfig struct { // Conflicts with NoInfra=true. // Optional. InfraImage string `json:"infra_image,omitempty"` + // InfraName is the name that will be used for the infra container. + // If not set, the default set in the Libpod configuration file will be + // used. + // Conflicts with NoInfra=true. + // Optional. + InfraName string `json:"infra_name,omitempty"` // SharedNamespaces instructs the pod to share a set of namespaces. // Shared namespaces will be joined (by default) by every container // which joins the pod. @@ -57,6 +63,10 @@ type PodBasicConfig struct { // (e.g. `podman generate systemd --new`). // Optional. PodCreateCommand []string `json:"pod_create_command,omitempty"` + // Pid sets the process id namespace of the pod + // Optional (defaults to private if unset). This sets the PID namespace of the infra container + // This configuration will then be shared with the entire pod if PID namespace sharing is enabled via --share + Pid Namespace `json:"pid,omitempty:"` } // PodNetworkConfig contains networking configuration for a pod. diff --git a/pkg/specgen/specgen.go b/pkg/specgen/specgen.go index c5cc726d7..7eec48a55 100644 --- a/pkg/specgen/specgen.go +++ b/pkg/specgen/specgen.go @@ -239,6 +239,9 @@ type ContainerStorageConfig struct { // Devices are devices that will be added to the container. // Optional. Devices []spec.LinuxDevice `json:"devices,omitempty"` + // DeviceCGroupRule are device cgroup rules that allow containers + // to use additional types of devices. + DeviceCGroupRule []spec.LinuxDeviceCgroup `json:"device_cgroup_rule,omitempty"` // IpcNS is the container's IPC namespace. // Default is private. // Conflicts with ShmSize if not set to private. diff --git a/pkg/systemd/generate/common.go b/pkg/systemd/generate/common.go index e183125a7..349805980 100644 --- a/pkg/systemd/generate/common.go +++ b/pkg/systemd/generate/common.go @@ -34,7 +34,7 @@ const headerTemplate = `# {{{{.ServiceName}}}}.service [Unit] Description=Podman {{{{.ServiceName}}}}.service Documentation=man:podman-generate-systemd(1) -Wants=network.target +Wants=network-online.target After=network-online.target RequiresMountsFor={{{{.RunRoot}}}} ` diff --git a/pkg/systemd/generate/containers_test.go b/pkg/systemd/generate/containers_test.go index 12a8f3004..1d24cc4a9 100644 --- a/pkg/systemd/generate/containers_test.go +++ b/pkg/systemd/generate/containers_test.go @@ -46,7 +46,7 @@ func TestCreateContainerSystemdUnit(t *testing.T) { [Unit] Description=Podman container-639c53578af4d84b8800b4635fa4e680ee80fd67e0e6a2d4eea48d1e3230f401.service Documentation=man:podman-generate-systemd(1) -Wants=network.target +Wants=network-online.target After=network-online.target RequiresMountsFor=/var/run/containers/storage @@ -72,7 +72,7 @@ WantedBy=multi-user.target default.target [Unit] Description=Podman container-foobar.service Documentation=man:podman-generate-systemd(1) -Wants=network.target +Wants=network-online.target After=network-online.target RequiresMountsFor=/var/run/containers/storage @@ -96,7 +96,7 @@ WantedBy=multi-user.target default.target [Unit] Description=Podman container-foobar.service Documentation=man:podman-generate-systemd(1) -Wants=network.target +Wants=network-online.target After=network-online.target RequiresMountsFor=/var/run/containers/storage BindsTo=a.service b.service c.service pod.service @@ -122,7 +122,7 @@ WantedBy=multi-user.target default.target [Unit] Description=Podman jadda-jadda.service Documentation=man:podman-generate-systemd(1) -Wants=network.target +Wants=network-online.target After=network-online.target RequiresMountsFor=/var/run/containers/storage @@ -144,7 +144,7 @@ WantedBy=multi-user.target default.target [Unit] Description=Podman jadda-jadda.service Documentation=man:podman-generate-systemd(1) -Wants=network.target +Wants=network-online.target After=network-online.target RequiresMountsFor=/var/run/containers/storage @@ -166,7 +166,7 @@ WantedBy=multi-user.target default.target [Unit] Description=Podman jadda-jadda.service Documentation=man:podman-generate-systemd(1) -Wants=network.target +Wants=network-online.target After=network-online.target RequiresMountsFor=/var/run/containers/storage @@ -188,7 +188,7 @@ WantedBy=multi-user.target default.target [Unit] Description=Podman jadda-jadda.service Documentation=man:podman-generate-systemd(1) -Wants=network.target +Wants=network-online.target After=network-online.target RequiresMountsFor=/var/run/containers/storage @@ -210,7 +210,7 @@ WantedBy=multi-user.target default.target [Unit] Description=Podman container-639c53578af4d84b8800b4635fa4e680ee80fd67e0e6a2d4eea48d1e3230f401.service Documentation=man:podman-generate-systemd(1) -Wants=network.target +Wants=network-online.target After=network-online.target RequiresMountsFor=/var/run/containers/storage @@ -233,7 +233,7 @@ WantedBy=multi-user.target default.target [Unit] Description=Podman jadda-jadda.service Documentation=man:podman-generate-systemd(1) -Wants=network.target +Wants=network-online.target After=network-online.target RequiresMountsFor=/var/run/containers/storage @@ -259,7 +259,7 @@ WantedBy=multi-user.target default.target [Unit] Description=Podman jadda-jadda.service Documentation=man:podman-generate-systemd(1) -Wants=network.target +Wants=network-online.target After=network-online.target RequiresMountsFor=/var/run/containers/storage @@ -281,7 +281,7 @@ WantedBy=multi-user.target default.target [Unit] Description=Podman jadda-jadda.service Documentation=man:podman-generate-systemd(1) -Wants=network.target +Wants=network-online.target After=network-online.target RequiresMountsFor=/var/run/containers/storage @@ -303,7 +303,7 @@ WantedBy=multi-user.target default.target [Unit] Description=Podman jadda-jadda.service Documentation=man:podman-generate-systemd(1) -Wants=network.target +Wants=network-online.target After=network-online.target RequiresMountsFor=/var/run/containers/storage @@ -325,7 +325,7 @@ WantedBy=multi-user.target default.target [Unit] Description=Podman jadda-jadda.service Documentation=man:podman-generate-systemd(1) -Wants=network.target +Wants=network-online.target After=network-online.target RequiresMountsFor=/var/run/containers/storage @@ -347,7 +347,7 @@ WantedBy=multi-user.target default.target [Unit] Description=Podman jadda-jadda.service Documentation=man:podman-generate-systemd(1) -Wants=network.target +Wants=network-online.target After=network-online.target RequiresMountsFor=/var/run/containers/storage @@ -369,7 +369,7 @@ WantedBy=multi-user.target default.target [Unit] Description=Podman jadda-jadda.service Documentation=man:podman-generate-systemd(1) -Wants=network.target +Wants=network-online.target After=network-online.target RequiresMountsFor=/var/run/containers/storage @@ -391,7 +391,7 @@ WantedBy=multi-user.target default.target [Unit] Description=Podman jadda-jadda.service Documentation=man:podman-generate-systemd(1) -Wants=network.target +Wants=network-online.target After=network-online.target RequiresMountsFor=/var/run/containers/storage @@ -413,7 +413,7 @@ WantedBy=multi-user.target default.target [Unit] Description=Podman jadda-jadda.service Documentation=man:podman-generate-systemd(1) -Wants=network.target +Wants=network-online.target After=network-online.target RequiresMountsFor=/var/run/containers/storage diff --git a/pkg/systemd/generate/pods_test.go b/pkg/systemd/generate/pods_test.go index a11e1e11e..4b8a9ffd5 100644 --- a/pkg/systemd/generate/pods_test.go +++ b/pkg/systemd/generate/pods_test.go @@ -45,7 +45,7 @@ func TestCreatePodSystemdUnit(t *testing.T) { [Unit] Description=Podman pod-123abc.service Documentation=man:podman-generate-systemd(1) -Wants=network.target +Wants=network-online.target After=network-online.target RequiresMountsFor=/var/run/containers/storage Requires=container-1.service container-2.service @@ -73,7 +73,7 @@ WantedBy=multi-user.target default.target [Unit] Description=Podman pod-123abc.service Documentation=man:podman-generate-systemd(1) -Wants=network.target +Wants=network-online.target After=network-online.target RequiresMountsFor=/var/run/containers/storage Requires=container-1.service container-2.service @@ -101,7 +101,7 @@ WantedBy=multi-user.target default.target [Unit] Description=Podman pod-123abc.service Documentation=man:podman-generate-systemd(1) -Wants=network.target +Wants=network-online.target After=network-online.target RequiresMountsFor=/var/run/containers/storage Requires=container-1.service container-2.service @@ -129,7 +129,7 @@ WantedBy=multi-user.target default.target [Unit] Description=Podman pod-123abc.service Documentation=man:podman-generate-systemd(1) -Wants=network.target +Wants=network-online.target After=network-online.target RequiresMountsFor=/var/run/containers/storage Requires=container-1.service container-2.service @@ -157,7 +157,7 @@ WantedBy=multi-user.target default.target [Unit] Description=Podman pod-123abc.service Documentation=man:podman-generate-systemd(1) -Wants=network.target +Wants=network-online.target After=network-online.target RequiresMountsFor=/var/run/containers/storage Requires=container-1.service container-2.service |