diff options
Diffstat (limited to 'pkg')
-rw-r--r-- | pkg/adapter/autoupdate.go | 11 | ||||
-rw-r--r-- | pkg/adapter/autoupdate_remote.go | 11 | ||||
-rw-r--r-- | pkg/adapter/pods.go | 26 | ||||
-rw-r--r-- | pkg/adapter/pods_remote.go | 7 | ||||
-rw-r--r-- | pkg/api/handlers/libpod/pods.go | 7 | ||||
-rw-r--r-- | pkg/api/handlers/libpod/swagger.go | 29 | ||||
-rw-r--r-- | pkg/api/handlers/utils/pods.go | 45 | ||||
-rw-r--r-- | pkg/api/server/register_manifest.go | 12 | ||||
-rw-r--r-- | pkg/api/server/register_swagger.go | 15 | ||||
-rw-r--r-- | pkg/api/server/server.go | 91 | ||||
-rw-r--r-- | pkg/autoupdate/autoupdate.go | 280 | ||||
-rw-r--r-- | pkg/autoupdate/autoupdate_test.go | 50 | ||||
-rw-r--r-- | pkg/bindings/test/common_test.go | 2 | ||||
-rw-r--r-- | pkg/bindings/test/pods_test.go | 67 | ||||
-rw-r--r-- | pkg/bindings/test/volumes_test.go | 5 | ||||
-rw-r--r-- | pkg/rootless/rootless_linux.c | 17 | ||||
-rw-r--r-- | pkg/spec/createconfig.go | 3 | ||||
-rw-r--r-- | pkg/specgen/create.go | 2 | ||||
-rw-r--r-- | pkg/specgen/specgen.go | 4 | ||||
-rw-r--r-- | pkg/systemd/dbus.go | 47 | ||||
-rw-r--r-- | pkg/systemd/generate/systemdgen.go | 9 | ||||
-rw-r--r-- | pkg/systemd/generate/systemdgen_test.go | 7 | ||||
-rw-r--r-- | pkg/varlinkapi/system.go | 2 |
23 files changed, 655 insertions, 94 deletions
diff --git a/pkg/adapter/autoupdate.go b/pkg/adapter/autoupdate.go new file mode 100644 index 000000000..01f7a29c5 --- /dev/null +++ b/pkg/adapter/autoupdate.go @@ -0,0 +1,11 @@ +// +build !remoteclient + +package adapter + +import ( + "github.com/containers/libpod/pkg/autoupdate" +) + +func (r *LocalRuntime) AutoUpdate() ([]string, []error) { + return autoupdate.AutoUpdate(r.Runtime) +} diff --git a/pkg/adapter/autoupdate_remote.go b/pkg/adapter/autoupdate_remote.go new file mode 100644 index 000000000..a2a82d0d4 --- /dev/null +++ b/pkg/adapter/autoupdate_remote.go @@ -0,0 +1,11 @@ +// +build remoteclient + +package adapter + +import ( + "github.com/containers/libpod/libpod/define" +) + +func (r *LocalRuntime) AutoUpdate() ([]string, []error) { + return nil, []error{define.ErrNotImplemented} +} diff --git a/pkg/adapter/pods.go b/pkg/adapter/pods.go index dc856cc8d..1417bd2b9 100644 --- a/pkg/adapter/pods.go +++ b/pkg/adapter/pods.go @@ -122,19 +122,31 @@ func (r *LocalRuntime) GetLatestPod() (*Pod, error) { return &pod, err } +// GetPodsWithFilters gets the filtered list of pods based on the filter parameters provided. +func (r *LocalRuntime) GetPodsWithFilters(filters string) ([]*Pod, error) { + pods, err := shared.GetPodsWithFilters(r.Runtime, filters) + if err != nil { + return nil, err + } + return r.podstoAdapterPods(pods) +} + +func (r *LocalRuntime) podstoAdapterPods(pod []*libpod.Pod) ([]*Pod, error) { + var pods []*Pod + for _, i := range pod { + + pods = append(pods, &Pod{i}) + } + return pods, nil +} + // GetAllPods gets all pods and wraps it in an adapter pod func (r *LocalRuntime) GetAllPods() ([]*Pod, error) { - var pods []*Pod allPods, err := r.Runtime.GetAllPods() if err != nil { return nil, err } - for _, p := range allPods { - pod := Pod{} - pod.Pod = p - pods = append(pods, &pod) - } - return pods, nil + return r.podstoAdapterPods(allPods) } // LookupPod gets a pod by name or id and wraps it in an adapter pod diff --git a/pkg/adapter/pods_remote.go b/pkg/adapter/pods_remote.go index 20f089628..6b8f22f15 100644 --- a/pkg/adapter/pods_remote.go +++ b/pkg/adapter/pods_remote.go @@ -10,7 +10,7 @@ import ( "github.com/containers/libpod/cmd/podman/cliconfig" "github.com/containers/libpod/cmd/podman/shared" - "github.com/containers/libpod/cmd/podman/varlink" + iopodman "github.com/containers/libpod/cmd/podman/varlink" "github.com/containers/libpod/libpod" "github.com/containers/libpod/libpod/define" "github.com/containers/libpod/pkg/varlinkapi" @@ -208,6 +208,11 @@ func (r *LocalRuntime) GetAllPods() ([]*Pod, error) { return pods, nil } +// This is a empty implementation stating remoteclient not yet implemented +func (r *LocalRuntime) GetPodsWithFilters(filters string) ([]*Pod, error) { + return nil, define.ErrNotImplemented +} + // GetPodsByStatus returns a slice of pods filtered by a libpod status func (r *LocalRuntime) GetPodsByStatus(statuses []string) ([]*Pod, error) { podIDs, err := iopodman.GetPodsByStatus().Call(r.Conn, statuses) diff --git a/pkg/api/handlers/libpod/pods.go b/pkg/api/handlers/libpod/pods.go index f93c8f8d5..27ec64d89 100644 --- a/pkg/api/handlers/libpod/pods.go +++ b/pkg/api/handlers/libpod/pods.go @@ -103,7 +103,6 @@ func PodCreate(w http.ResponseWriter, r *http.Request) { func Pods(w http.ResponseWriter, r *http.Request) { var ( - runtime = r.Context().Value("runtime").(*libpod.Runtime) podInspectData []*libpod.PodInspect ) decoder := r.Context().Value("decoder").(*schema.Decoder) @@ -118,12 +117,8 @@ func Pods(w http.ResponseWriter, r *http.Request) { return } - if len(query.Filters) > 0 { - utils.Error(w, "filters are not implemented yet", http.StatusInternalServerError, define.ErrNotImplemented) - return - } + pods, err := utils.GetPods(w, r) - pods, err := runtime.GetAllPods() if err != nil { utils.Error(w, "Something went wrong", http.StatusInternalServerError, err) return diff --git a/pkg/api/handlers/libpod/swagger.go b/pkg/api/handlers/libpod/swagger.go index f6a26134b..149fa10dc 100644 --- a/pkg/api/handlers/libpod/swagger.go +++ b/pkg/api/handlers/libpod/swagger.go @@ -1,6 +1,16 @@ package libpod -import "github.com/containers/image/v5/manifest" +import ( + "net/http" + "os" + + "github.com/containers/image/v5/manifest" + "github.com/containers/libpod/pkg/api/handlers/utils" + "github.com/pkg/errors" +) + +// DefaultPodmanSwaggerSpec provides the default path to the podman swagger spec file +const DefaultPodmanSwaggerSpec = "/usr/share/containers/podman/swagger.yaml" // List Containers // swagger:response ListContainers @@ -15,3 +25,20 @@ type swagInspectManifestResponse struct { // in:body Body manifest.List } + +func ServeSwagger(w http.ResponseWriter, r *http.Request) { + path := DefaultPodmanSwaggerSpec + if p, found := os.LookupEnv("PODMAN_SWAGGER_SPEC"); found { + path = p + } + if _, err := os.Stat(path); err != nil { + if os.IsNotExist(err) { + utils.InternalServerError(w, errors.Errorf("file %q does not exist", path)) + return + } + utils.InternalServerError(w, err) + return + } + w.Header().Set("Content-Type", "text/yaml") + http.ServeFile(w, r, path) +} diff --git a/pkg/api/handlers/utils/pods.go b/pkg/api/handlers/utils/pods.go new file mode 100644 index 000000000..266ad9a4b --- /dev/null +++ b/pkg/api/handlers/utils/pods.go @@ -0,0 +1,45 @@ +package utils + +import ( + "fmt" + "net/http" + + "github.com/containers/libpod/cmd/podman/shared" + "github.com/containers/libpod/libpod" + "github.com/gorilla/schema" +) + +func GetPods(w http.ResponseWriter, r *http.Request) ([]*libpod.Pod, error) { + runtime := r.Context().Value("runtime").(*libpod.Runtime) + decoder := r.Context().Value("decoder").(*schema.Decoder) + + query := struct { + All bool + Filters map[string][]string `schema:"filters"` + Digests bool + }{} + + if err := decoder.Decode(&query, r.URL.Query()); err != nil { + return nil, err + } + var filters = []string{} + if _, found := r.URL.Query()["digests"]; found && query.Digests { + UnSupportedParameter("digests") + } + + if len(query.Filters) > 0 { + for k, v := range query.Filters { + for _, val := range v { + filters = append(filters, fmt.Sprintf("%s=%s", k, val)) + } + } + filterFuncs, err := shared.GenerateFilterFunction(runtime, filters) + if err != nil { + return nil, err + } + return shared.FilterAllPodsWithFilterFunc(runtime, filterFuncs...) + } + + return runtime.GetAllPods() + +} diff --git a/pkg/api/server/register_manifest.go b/pkg/api/server/register_manifest.go index ccfc2192d..8fd84f205 100644 --- a/pkg/api/server/register_manifest.go +++ b/pkg/api/server/register_manifest.go @@ -38,7 +38,7 @@ func (s *APIServer) registerManifestHandlers(r *mux.Router) error { // 500: // $ref: "#/responses/InternalError" r.Handle(VersionedPath("/libpod/manifests/create"), s.APIHandler(libpod.ManifestCreate)).Methods(http.MethodPost) - // swagger:operation GET /libpod/manifests/{name}/json manifests Inspect + // swagger:operation GET /libpod/manifests/{name:.*}/json manifests Inspect // --- // summary: Inspect // description: Display a manifest list @@ -46,7 +46,7 @@ func (s *APIServer) registerManifestHandlers(r *mux.Router) error { // - application/json // parameters: // - in: path - // name: name + // name: name:.* // type: string // required: true // description: the name or ID of the manifest @@ -58,14 +58,14 @@ func (s *APIServer) registerManifestHandlers(r *mux.Router) error { // 500: // $ref: "#/responses/InternalError" r.Handle(VersionedPath("/libpod/manifests/{name:.*}/json"), s.APIHandler(libpod.ManifestInspect)).Methods(http.MethodGet) - // swagger:operation POST /libpod/manifests/{name}/add manifests AddManifest + // swagger:operation POST /libpod/manifests/{name:.*}/add manifests AddManifest // --- // description: Add an image to a manifest list // produces: // - application/json // parameters: // - in: path - // name: name + // name: name:.* // type: string // required: true // description: the name or ID of the manifest @@ -84,7 +84,7 @@ func (s *APIServer) registerManifestHandlers(r *mux.Router) error { // 500: // $ref: "#/responses/InternalError" r.Handle(VersionedPath("/libpod/manifests/{name:.*}/add"), s.APIHandler(libpod.ManifestAdd)).Methods(http.MethodPost) - // swagger:operation DELETE /libpod/manifests/{name} manifests RemoveManifest + // swagger:operation DELETE /libpod/manifests/{name:.*} manifests RemoveManifest // --- // summary: Remove // description: Remove an image from a manifest list @@ -92,7 +92,7 @@ func (s *APIServer) registerManifestHandlers(r *mux.Router) error { // - application/json // parameters: // - in: path - // name: name + // name: name:.* // type: string // required: true // description: the image associated with the manifest diff --git a/pkg/api/server/register_swagger.go b/pkg/api/server/register_swagger.go index 5564ec096..9048c1951 100644 --- a/pkg/api/server/register_swagger.go +++ b/pkg/api/server/register_swagger.go @@ -2,25 +2,14 @@ package server import ( "net/http" - "os" + "github.com/containers/libpod/pkg/api/handlers/libpod" "github.com/gorilla/mux" ) -// DefaultPodmanSwaggerSpec provides the default path to the podman swagger spec file -const DefaultPodmanSwaggerSpec = "/usr/share/containers/podman/swagger.yaml" - // RegisterSwaggerHandlers maps the swagger endpoint for the server func (s *APIServer) RegisterSwaggerHandlers(r *mux.Router) error { // This handler does _*NOT*_ provide an UI rather just a swagger spec that an UI could render - r.PathPrefix("/swagger/").HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - path := DefaultPodmanSwaggerSpec - if p, found := os.LookupEnv("PODMAN_SWAGGER_SPEC"); found { - path = p - } - w.Header().Set("Content-Type", "text/yaml") - - http.ServeFile(w, r, path) - }) + r.HandleFunc(VersionedPath("/libpod/swagger"), s.APIHandler(libpod.ServeSwagger)).Methods(http.MethodGet) return nil } diff --git a/pkg/api/server/server.go b/pkg/api/server/server.go index 8496cd11c..59f1f95cb 100644 --- a/pkg/api/server/server.go +++ b/pkg/api/server/server.go @@ -83,10 +83,6 @@ func newServer(runtime *libpod.Runtime, duration time.Duration, listener *net.Li ConnectionCh: make(chan int), } - server.Timer = time.AfterFunc(server.Duration, func() { - server.ConnectionCh <- NOOPHandler - }) - router.NotFoundHandler = http.HandlerFunc( func(w http.ResponseWriter, r *http.Request) { // We can track user errors... @@ -109,6 +105,7 @@ func newServer(runtime *libpod.Runtime, duration time.Duration, listener *net.Li server.registerPingHandlers, server.registerPluginsHandlers, server.registerPodsHandlers, + server.RegisterSwaggerHandlers, server.registerSwarmHandlers, server.registerSystemHandlers, server.registerVersionHandlers, @@ -139,36 +136,15 @@ func newServer(runtime *libpod.Runtime, duration time.Duration, listener *net.Li // Serve starts responding to HTTP requests func (s *APIServer) Serve() error { - // stalker to count the connections. Should the timer expire it will shutdown the service. - go func() { - for delta := range s.ConnectionCh { - switch delta { - case EnterHandler: - s.Timer.Stop() - s.ActiveConnections += 1 - s.TotalConnections += 1 - case ExitHandler: - s.Timer.Stop() - s.ActiveConnections -= 1 - if s.ActiveConnections == 0 { - // Server will be shutdown iff the timer expires before being reset or stopped - s.Timer = time.AfterFunc(s.Duration, func() { - if err := s.Shutdown(); err != nil { - logrus.Errorf("Failed to shutdown APIServer: %v", err) - os.Exit(1) - } - }) - } else { - s.Timer.Reset(s.Duration) - } - case NOOPHandler: - // push the check out another duration... - s.Timer.Reset(s.Duration) - default: - logrus.Errorf("ConnectionCh received unsupported input %d", delta) - } - } - }() + // This is initialized here as Timer is not needed until Serve'ing + if s.Duration > 0 { + s.Timer = time.AfterFunc(s.Duration, func() { + s.ConnectionCh <- NOOPHandler + }) + go s.ReadChannelWithTimeout() + } else { + go s.ReadChannelNoTimeout() + } sigChan := make(chan os.Signal, 1) signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM) @@ -193,6 +169,53 @@ func (s *APIServer) Serve() error { return nil } +func (s *APIServer) ReadChannelWithTimeout() { + // stalker to count the connections. Should the timer expire it will shutdown the service. + for delta := range s.ConnectionCh { + switch delta { + case EnterHandler: + s.Timer.Stop() + s.ActiveConnections += 1 + s.TotalConnections += 1 + case ExitHandler: + s.Timer.Stop() + s.ActiveConnections -= 1 + if s.ActiveConnections == 0 { + // Server will be shutdown iff the timer expires before being reset or stopped + s.Timer = time.AfterFunc(s.Duration, func() { + if err := s.Shutdown(); err != nil { + logrus.Errorf("Failed to shutdown APIServer: %v", err) + os.Exit(1) + } + }) + } else { + s.Timer.Reset(s.Duration) + } + case NOOPHandler: + // push the check out another duration... + s.Timer.Reset(s.Duration) + default: + logrus.Warnf("ConnectionCh received unsupported input %d", delta) + } + } +} + +func (s *APIServer) ReadChannelNoTimeout() { + // stalker to count the connections. + for delta := range s.ConnectionCh { + switch delta { + case EnterHandler: + s.ActiveConnections += 1 + s.TotalConnections += 1 + case ExitHandler: + s.ActiveConnections -= 1 + case NOOPHandler: + default: + logrus.Warnf("ConnectionCh received unsupported input %d", delta) + } + } +} + // Shutdown is a clean shutdown waiting on existing clients func (s *APIServer) Shutdown() error { // Duration == 0 flags no auto-shutdown of the server diff --git a/pkg/autoupdate/autoupdate.go b/pkg/autoupdate/autoupdate.go new file mode 100644 index 000000000..7c243eb00 --- /dev/null +++ b/pkg/autoupdate/autoupdate.go @@ -0,0 +1,280 @@ +package autoupdate + +import ( + "context" + "os" + "sort" + + "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/libpod/libpod" + "github.com/containers/libpod/libpod/define" + "github.com/containers/libpod/libpod/image" + "github.com/containers/libpod/pkg/systemd" + systemdGen "github.com/containers/libpod/pkg/systemd/generate" + "github.com/containers/libpod/pkg/util" + "github.com/pkg/errors" + "github.com/sirupsen/logrus" +) + +// Label denotes the container/pod label key to specify auto-update policies in +// container labels. +const Label = "io.containers.autoupdate" + +// Policy represents an auto-update policy. +type Policy string + +const ( + // PolicyDefault is the default policy denoting no auto updates. + PolicyDefault Policy = "disabled" + // PolicyNewImage is the policy to update as soon as there's a new image found. + PolicyNewImage = "image" +) + +// Map for easy lookups of supported policies. +var supportedPolicies = map[string]Policy{ + "": PolicyDefault, + "disabled": PolicyDefault, + "image": PolicyNewImage, +} + +// LookupPolicy looksup the corresponding Policy for the specified +// string. If none is found, an errors is returned including the list of +// supported policies. +// +// Note that an empty string resolved to PolicyDefault. +func LookupPolicy(s string) (Policy, error) { + policy, exists := supportedPolicies[s] + if exists { + return policy, nil + } + + // Sort the keys first as maps are non-deterministic. + keys := []string{} + for k := range supportedPolicies { + if k != "" { + keys = append(keys, k) + } + } + sort.Strings(keys) + + return "", errors.Errorf("invalid auto-update policy %q: valid policies are %+q", s, keys) +} + +// 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 +// reference may also be prefixed with "docker://" explicitly indicating that +// it's a reference to the docker transport. +func ValidateImageReference(imageName string) error { + // Make sure the input image is a docker. + imageRef, err := alltransports.ParseImageName(imageName) + if err == nil && imageRef.Transport().Name() != docker.Transport.Name() { + return errors.Errorf("auto updates require the docker image transport but image is of transport %q", imageRef.Transport().Name()) + } else if err != nil { + repo, err := reference.Parse(imageName) + if err != nil { + return errors.Wrap(err, "error enforcing fully-qualified docker transport reference for auto updates") + } + if _, ok := repo.(reference.NamedTagged); !ok { + return errors.Errorf("auto updates require fully-qualified image references (no tag): %q", imageName) + } + if _, ok := repo.(reference.Digested); ok { + return errors.Errorf("auto updates require fully-qualified image references without digest: %q", imageName) + } + } + return nil +} + +// AutoUpdate looks up containers with a specified auto-update policy and acts +// accordingly. If the policy is set to PolicyNewImage, it checks if the image +// on the remote registry is different than the local one. If the image digests +// differ, it pulls the remote image and restarts the systemd unit running the +// container. +// +// It returns a slice of successfully restarted systemd units and a slice of +// errors encountered during auto update. +func AutoUpdate(runtime *libpod.Runtime) ([]string, []error) { + // Create a map from `image ID -> []*Container`. + containerMap, errs := imageContainersMap(runtime) + if len(containerMap) == 0 { + return nil, errs + } + + // Create a map from `image ID -> *image.Image` for image lookups. + imagesSlice, err := runtime.ImageRuntime().GetImages() + if err != nil { + return nil, []error{err} + } + imageMap := make(map[string]*image.Image) + for i := range imagesSlice { + imageMap[imagesSlice[i].ID()] = imagesSlice[i] + } + + // Connect to DBUS. + conn, err := systemd.ConnectToDBUS() + if err != nil { + logrus.Errorf(err.Error()) + return nil, []error{err} + } + defer conn.Close() + + // Update images. + containersToRestart := []*libpod.Container{} + updatedRawImages := make(map[string]bool) + for imageID, containers := range containerMap { + image, exists := imageMap[imageID] + if !exists { + 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 i, ctr := range containers { + rawImageName := ctr.RawImageName() + if rawImageName == "" { + errs = append(errs, errors.Errorf("error auto-updating container %q: raw-image name is empty", ctr.ID())) + } + needsUpdate, err := newerImageAvailable(runtime, image, rawImageName) + if err != nil { + errs = append(errs, errors.Wrapf(err, "error auto-updating container %q: image check for %q failed", ctr.ID(), rawImageName)) + continue + } + if !needsUpdate { + continue + } + logrus.Infof("Auto-updating container %q using image %q", ctr.ID(), rawImageName) + if _, updated := updatedRawImages[rawImageName]; !updated { + _, err = updateImage(runtime, rawImageName) + if err != nil { + errs = append(errs, errors.Wrapf(err, "error auto-updating container %q: image update for %q failed", ctr.ID(), rawImageName)) + continue + } + updatedRawImages[rawImageName] = true + } + containersToRestart = append(containersToRestart, containers[i]) + } + } + + // Restart containers. + updatedUnits := []string{} + for _, ctr := range containersToRestart { + labels := ctr.Labels() + unit, exists := labels[systemdGen.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(), systemdGen.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 + } + logrus.Infof("Successfully restarted systemd unit %q", unit) + updatedUnits = append(updatedUnits, unit) + } + + return updatedUnits, errs +} + +// imageContainersMap generates a map[image ID] -> [containers using the image] +// of all containers with a valid auto-update policy. +func imageContainersMap(runtime *libpod.Runtime) (map[string][]*libpod.Container, []error) { + allContainers, err := runtime.GetAllContainers() + if err != nil { + return nil, []error{err} + } + + errors := []error{} + imageMap := make(map[string][]*libpod.Container) + for i, ctr := range allContainers { + state, err := ctr.State() + if err != nil { + errors = append(errors, err) + continue + } + // Only update running containers. + if state != define.ContainerStateRunning { + continue + } + // Only update containers with the specific label/policy set. + labels := ctr.Labels() + if value, exists := labels[Label]; exists { + policy, err := LookupPolicy(value) + if err != nil { + errors = append(errors, err) + continue + } + if policy != PolicyNewImage { + continue + } + } + // Now we know that `ctr` is configured for auto updates. + id, _ := ctr.Image() + imageMap[id] = append(imageMap[id], allContainers[i]) + } + + return imageMap, errors +} + +// newerImageAvailable returns true if there corresponding image on the remote +// registry is newer. +func newerImageAvailable(runtime *libpod.Runtime, img *image.Image, origName string) (bool, error) { + remoteRef, err := docker.ParseReference("//" + origName) + if err != nil { + return false, err + } + + remoteImg, err := remoteRef.NewImage(context.Background(), runtime.SystemContext()) + 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 +} + +// updateImage pulls the specified image. +func updateImage(runtime *libpod.Runtime, name string) (*image.Image, error) { + sys := runtime.SystemContext() + registryOpts := image.DockerRegistryOptions{} + signaturePolicyPath := "" + authFilePath := "" + + if sys != nil { + registryOpts.OSChoice = sys.OSChoice + registryOpts.ArchitectureChoice = sys.OSChoice + registryOpts.DockerCertPath = sys.DockerCertPath + + signaturePolicyPath = sys.SignaturePolicyPath + authFilePath = sys.AuthFilePath + } + + newImage, err := runtime.ImageRuntime().New(context.Background(), + docker.Transport.Name()+"://"+name, + signaturePolicyPath, + authFilePath, + os.Stderr, + ®istryOpts, + image.SigningOptions{}, + nil, + util.PullImageAlways, + ) + if err != nil { + return nil, err + } + return newImage, nil +} diff --git a/pkg/autoupdate/autoupdate_test.go b/pkg/autoupdate/autoupdate_test.go new file mode 100644 index 000000000..7a5da5bb0 --- /dev/null +++ b/pkg/autoupdate/autoupdate_test.go @@ -0,0 +1,50 @@ +package autoupdate + +import ( + "testing" +) + +func TestValidateImageReference(t *testing.T) { + tests := []struct { + input string + valid bool + }{ + { // Fully-qualified reference + input: "quay.io/foo/bar:tag", + valid: true, + }, + { // Fully-qualified reference in transport notation + input: "docker://quay.io/foo/bar:tag", + valid: true, + }, + { // Fully-qualified reference but with digest + input: "quay.io/foo/bar@sha256:c9b1b535fdd91a9855fb7f82348177e5f019329a58c53c47272962dd60f71fc9", + valid: false, + }, + { // Reference with missing tag + input: "quay.io/foo/bar", + valid: false, + }, + { // Short name + input: "alpine", + valid: false, + }, + { // Short name with repo + input: "library/alpine", + valid: false, + }, + { // Wrong transport + input: "docker-archive:/some/path.tar", + valid: false, + }, + } + + for _, test := range tests { + err := ValidateImageReference(test.input) + if test.valid && err != nil { + t.Fatalf("parsing %q should have succeeded: %v", test.input, err) + } else if !test.valid && err == nil { + t.Fatalf("parsing %q should have failed", test.input) + } + } +} diff --git a/pkg/bindings/test/common_test.go b/pkg/bindings/test/common_test.go index a4d065a14..5cd8f7e4f 100644 --- a/pkg/bindings/test/common_test.go +++ b/pkg/bindings/test/common_test.go @@ -152,7 +152,7 @@ func (b *bindingTest) startAPIService() *gexec.Session { var ( cmd []string ) - cmd = append(cmd, "--log-level=debug", "system", "service", "--timeout=999999", b.sock) + cmd = append(cmd, "--log-level=debug", "system", "service", "--timeout=0", b.sock) return b.runPodman(cmd) } diff --git a/pkg/bindings/test/pods_test.go b/pkg/bindings/test/pods_test.go index 7e29265b7..f6c4d465d 100644 --- a/pkg/bindings/test/pods_test.go +++ b/pkg/bindings/test/pods_test.go @@ -76,15 +76,66 @@ var _ = Describe("Podman pods", func() { } Expect(StringInSlice(newpod, names)).To(BeTrue()) Expect(StringInSlice("newpod2", names)).To(BeTrue()) + }) - // TODO not working Because: code to list based on filter - // "not yet implemented", - // Validate list pod with filters - //filters := make(map[string][]string) - //filters["name"] = []string{newpod} - //filteredPods, err := pods.List(bt.conn, filters) - //Expect(err).To(BeNil()) - //Expect(len(filteredPods)).To(BeNumerically("==", 1)) + // The test validates the list pod endpoint with passing filters as the params. + It("List pods with filters", func() { + var newpod2 string = "newpod2" + bt.Podcreate(&newpod2) + _, err = bt.RunTopContainer(nil, &trueFlag, &newpod) + Expect(err).To(BeNil()) + + // Expected err with invalid filter params + filters := make(map[string][]string) + filters["dummy"] = []string{"dummy"} + filteredPods, err := pods.List(bt.conn, filters) + Expect(err).ToNot(BeNil()) + code, _ := bindings.CheckResponseCode(err) + Expect(code).To(BeNumerically("==", http.StatusInternalServerError)) + + // Expected empty response with invalid filters + filters = make(map[string][]string) + filters["name"] = []string{"dummy"} + filteredPods, err = pods.List(bt.conn, filters) + Expect(err).To(BeNil()) + Expect(len(filteredPods)).To(BeNumerically("==", 0)) + + // Validate list pod with name filter + filters = make(map[string][]string) + filters["name"] = []string{newpod2} + filteredPods, err = pods.List(bt.conn, filters) + Expect(err).To(BeNil()) + Expect(len(filteredPods)).To(BeNumerically("==", 1)) + var names []string + for _, i := range filteredPods { + names = append(names, i.Config.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) + id := response.Config.ID + filters["id"] = []string{id} + filteredPods, err = pods.List(bt.conn, filters) + Expect(err).To(BeNil()) + Expect(len(filteredPods)).To(BeNumerically("==", 1)) + names = names[:0] + for _, i := range filteredPods { + names = append(names, i.Config.Name) + } + Expect(StringInSlice("newpod", names)).To(BeTrue()) + + // Using multiple filters + filters["name"] = []string{newpod} + filteredPods, err = pods.List(bt.conn, filters) + Expect(err).To(BeNil()) + Expect(len(filteredPods)).To(BeNumerically("==", 1)) + names = names[:0] + for _, i := range filteredPods { + names = append(names, i.Config.Name) + } + Expect(StringInSlice("newpod", names)).To(BeTrue()) }) // The test validates if the exists responds diff --git a/pkg/bindings/test/volumes_test.go b/pkg/bindings/test/volumes_test.go index c8940d46e..b1a742c43 100644 --- a/pkg/bindings/test/volumes_test.go +++ b/pkg/bindings/test/volumes_test.go @@ -3,11 +3,12 @@ package test_bindings import ( "context" "fmt" + "net/http" + "time" + "github.com/containers/libpod/pkg/api/handlers" "github.com/containers/libpod/pkg/bindings/containers" "github.com/containers/libpod/pkg/bindings/volumes" - "net/http" - "time" "github.com/containers/libpod/pkg/bindings" . "github.com/onsi/ginkgo" diff --git a/pkg/rootless/rootless_linux.c b/pkg/rootless/rootless_linux.c index db898e706..6643bfbbf 100644 --- a/pkg/rootless/rootless_linux.c +++ b/pkg/rootless/rootless_linux.c @@ -108,10 +108,9 @@ do_pause () } static char ** -get_cmd_line_args (pid_t pid) +get_cmd_line_args () { int fd; - char path[PATH_MAX]; char *buffer; size_t allocated; size_t used = 0; @@ -119,11 +118,7 @@ get_cmd_line_args (pid_t pid) int i, argc = 0; char **argv; - if (pid) - sprintf (path, "/proc/%d/cmdline", pid); - else - strcpy (path, "/proc/self/cmdline"); - fd = open (path, O_RDONLY); + fd = open ("/proc/self/cmdline", O_RDONLY); if (fd < 0) return NULL; @@ -196,7 +191,7 @@ can_use_shortcut () return false; #endif - argv = get_cmd_line_args (0); + argv = get_cmd_line_args (); if (argv == NULL) return false; @@ -542,7 +537,6 @@ create_pause_process (const char *pause_pid_file_path, char **argv) int reexec_userns_join (int userns, int mountns, char *pause_pid_file_path) { - pid_t ppid = getpid (); char uid[16]; char gid[16]; char **argv; @@ -559,7 +553,7 @@ reexec_userns_join (int userns, int mountns, char *pause_pid_file_path) sprintf (uid, "%d", geteuid ()); sprintf (gid, "%d", getegid ()); - argv = get_cmd_line_args (ppid); + argv = get_cmd_line_args (); if (argv == NULL) { fprintf (stderr, "cannot read argv: %s\n", strerror (errno)); @@ -724,7 +718,6 @@ reexec_in_user_namespace (int ready, char *pause_pid_file_path, char *file_to_re int ret; pid_t pid; char b; - pid_t ppid = getpid (); char **argv; char uid[16]; char gid[16]; @@ -801,7 +794,7 @@ reexec_in_user_namespace (int ready, char *pause_pid_file_path, char *file_to_re _exit (EXIT_FAILURE); } - argv = get_cmd_line_args (ppid); + argv = get_cmd_line_args (); if (argv == NULL) { fprintf (stderr, "cannot read argv: %s\n", strerror (errno)); diff --git a/pkg/spec/createconfig.go b/pkg/spec/createconfig.go index 9b2255d61..12dfed8c3 100644 --- a/pkg/spec/createconfig.go +++ b/pkg/spec/createconfig.go @@ -144,6 +144,7 @@ type CreateConfig struct { InitPath string //init-path Image string ImageID string + RawImageName string BuiltinImgVolumes map[string]struct{} // volumes defined in the image config ImageVolumeType string // how to handle the image volume, either bind, tmpfs, or ignore Interactive bool //interactive @@ -348,7 +349,7 @@ func (c *CreateConfig) getContainerCreateOptions(runtime *libpod.Runtime, pod *l options = append(options, nsOpts...) // Gather up the options for NewContainer which consist of With... funcs - options = append(options, libpod.WithRootFSFromImage(c.ImageID, c.Image)) + options = append(options, libpod.WithRootFSFromImage(c.ImageID, c.Image, c.RawImageName)) options = append(options, libpod.WithConmonPidFile(c.ConmonPidFile)) options = append(options, libpod.WithLabels(c.Labels)) options = append(options, libpod.WithShmSize(c.Resources.ShmSize)) diff --git a/pkg/specgen/create.go b/pkg/specgen/create.go index 99a99083b..aefbe7405 100644 --- a/pkg/specgen/create.go +++ b/pkg/specgen/create.go @@ -36,7 +36,7 @@ func (s *SpecGenerator) MakeContainer(rt *libpod.Runtime) (*libpod.Container, er return nil, err } - options = append(options, libpod.WithRootFSFromImage(newImage.ID(), s.Image)) + options = append(options, libpod.WithRootFSFromImage(newImage.ID(), s.Image, s.RawImageName)) runtimeSpec, err := s.toOCISpec(rt, newImage) if err != nil { diff --git a/pkg/specgen/specgen.go b/pkg/specgen/specgen.go index e1dfe4dc5..7a430652a 100644 --- a/pkg/specgen/specgen.go +++ b/pkg/specgen/specgen.go @@ -143,6 +143,10 @@ type ContainerStorageConfig struct { // Conflicts with Rootfs. // At least one of Image or Rootfs must be specified. Image string `json:"image"` + // RawImageName is the unprocessed and not-normalized user-specified image + // name. One use case for having this data at hand are auto-updates where + // the _exact_ user input is needed in order to look-up the correct image. + RawImageName string `json:"raw_image_name,omitempty"` // Rootfs is the path to a directory that will be used as the // container's root filesystem. No modification will be made to the // directory, it will be directly mounted into the container as root. diff --git a/pkg/systemd/dbus.go b/pkg/systemd/dbus.go new file mode 100644 index 000000000..df24667a1 --- /dev/null +++ b/pkg/systemd/dbus.go @@ -0,0 +1,47 @@ +package systemd + +import ( + "fmt" + "os" + "path/filepath" + "strconv" + + "github.com/containers/libpod/pkg/rootless" + "github.com/coreos/go-systemd/v22/dbus" + godbus "github.com/godbus/dbus/v5" +) + +func dbusAuthRootlessConnection(createBus func(opts ...godbus.ConnOption) (*godbus.Conn, error)) (*godbus.Conn, error) { + conn, err := createBus() + if err != nil { + return nil, err + } + + methods := []godbus.Auth{godbus.AuthExternal(strconv.Itoa(rootless.GetRootlessUID()))} + + err = conn.Auth(methods) + if err != nil { + conn.Close() + return nil, err + } + + return conn, nil +} + +func newRootlessConnection() (*dbus.Conn, error) { + return dbus.NewConnection(func() (*godbus.Conn, error) { + return dbusAuthRootlessConnection(func(opts ...godbus.ConnOption) (*godbus.Conn, error) { + path := filepath.Join(os.Getenv("XDG_RUNTIME_DIR"), "systemd/private") + return godbus.Dial(fmt.Sprintf("unix:path=%s", path)) + }) + }) +} + +// ConnectToDBUS returns a DBUS connection. It works both as root and non-root +// users. +func ConnectToDBUS() (*dbus.Conn, error) { + if rootless.IsRootless() { + return newRootlessConnection() + } + return dbus.NewSystemdConnection() +} diff --git a/pkg/systemd/generate/systemdgen.go b/pkg/systemd/generate/systemdgen.go index 4cd7745c0..eb15d4927 100644 --- a/pkg/systemd/generate/systemdgen.go +++ b/pkg/systemd/generate/systemdgen.go @@ -16,6 +16,10 @@ import ( "github.com/sirupsen/logrus" ) +// EnvVariable "PODMAN_SYSTEMD_UNIT" is set in all generated systemd units and +// is set to the unit's (unique) name. +const EnvVariable = "PODMAN_SYSTEMD_UNIT" + // ContainerInfo contains data required for generating a container's systemd // unit file. type ContainerInfo struct { @@ -57,6 +61,8 @@ type ContainerInfo struct { // RunCommand is a post-processed variant of CreateCommand and used for // the ExecStart field in generic unit files. RunCommand string + // EnvVariable is generate.EnvVariable and must not be set. + EnvVariable string } var restartPolicies = []string{"no", "on-success", "on-failure", "on-abnormal", "on-watchdog", "on-abort", "always"} @@ -94,6 +100,7 @@ Before={{- range $index, $value := .RequiredServices -}}{{if $index}} {{end}}{{ {{- end}} [Service] +Environment={{.EnvVariable}}=%n Restart={{.RestartPolicy}} {{- if .New}} ExecStartPre=/usr/bin/rm -f %t/%n-pid %t/%n-cid @@ -138,6 +145,8 @@ func CreateContainerSystemdUnit(info *ContainerInfo, opts Options) (string, erro info.Executable = executable } + info.EnvVariable = EnvVariable + // Assemble the ExecStart command when creating a new container. // // Note that we cannot catch all corner cases here such that users diff --git a/pkg/systemd/generate/systemdgen_test.go b/pkg/systemd/generate/systemdgen_test.go index bbdccdcf8..3269405a6 100644 --- a/pkg/systemd/generate/systemdgen_test.go +++ b/pkg/systemd/generate/systemdgen_test.go @@ -44,6 +44,7 @@ Wants=network.target After=network-online.target [Service] +Environment=PODMAN_SYSTEMD_UNIT=%n Restart=always ExecStart=/usr/bin/podman start 639c53578af4d84b8800b4635fa4e680ee80fd67e0e6a2d4eea48d1e3230f401 ExecStop=/usr/bin/podman stop -t 10 639c53578af4d84b8800b4635fa4e680ee80fd67e0e6a2d4eea48d1e3230f401 @@ -64,6 +65,7 @@ Wants=network.target After=network-online.target [Service] +Environment=PODMAN_SYSTEMD_UNIT=%n Restart=always ExecStart=/usr/bin/podman start foobar ExecStop=/usr/bin/podman stop -t 10 foobar @@ -88,6 +90,7 @@ BindsTo=a.service b.service c.service pod.service After=a.service b.service c.service pod.service [Service] +Environment=PODMAN_SYSTEMD_UNIT=%n Restart=always ExecStart=/usr/bin/podman start foobar ExecStop=/usr/bin/podman stop -t 10 foobar @@ -110,6 +113,7 @@ Requires=container-1.service container-2.service Before=container-1.service container-2.service [Service] +Environment=PODMAN_SYSTEMD_UNIT=%n Restart=always ExecStart=/usr/bin/podman start jadda-jadda-infra ExecStop=/usr/bin/podman stop -t 10 jadda-jadda-infra @@ -130,6 +134,7 @@ Wants=network.target After=network-online.target [Service] +Environment=PODMAN_SYSTEMD_UNIT=%n Restart=always ExecStartPre=/usr/bin/rm -f %t/%n-pid %t/%n-cid ExecStart=/usr/bin/podman run --conmon-pidfile %t/%n-pid --cidfile %t/%n-cid --cgroups=no-conmon -d --name jadda-jadda --hostname hello-world awesome-image:latest command arg1 ... argN @@ -152,6 +157,7 @@ Wants=network.target After=network-online.target [Service] +Environment=PODMAN_SYSTEMD_UNIT=%n Restart=always ExecStartPre=/usr/bin/rm -f %t/%n-pid %t/%n-cid ExecStart=/usr/bin/podman run --conmon-pidfile %t/%n-pid --cidfile %t/%n-cid --cgroups=no-conmon --detach --name jadda-jadda --hostname hello-world awesome-image:latest command arg1 ... argN @@ -174,6 +180,7 @@ Wants=network.target After=network-online.target [Service] +Environment=PODMAN_SYSTEMD_UNIT=%n Restart=always ExecStartPre=/usr/bin/rm -f %t/%n-pid %t/%n-cid ExecStart=/usr/bin/podman run --conmon-pidfile %t/%n-pid --cidfile %t/%n-cid --cgroups=no-conmon -d awesome-image:latest diff --git a/pkg/varlinkapi/system.go b/pkg/varlinkapi/system.go index 50aaaaa44..e88d010c5 100644 --- a/pkg/varlinkapi/system.go +++ b/pkg/varlinkapi/system.go @@ -10,7 +10,7 @@ import ( "time" "github.com/containers/image/v5/pkg/sysregistriesv2" - "github.com/containers/libpod/cmd/podman/varlink" + iopodman "github.com/containers/libpod/cmd/podman/varlink" "github.com/containers/libpod/libpod/define" "github.com/sirupsen/logrus" ) |