diff options
Diffstat (limited to 'pkg')
46 files changed, 517 insertions, 116 deletions
diff --git a/pkg/api/handlers/compat/containers_archive.go b/pkg/api/handlers/compat/containers_archive.go index cda23a399..54cbe01e9 100644 --- a/pkg/api/handlers/compat/containers_archive.go +++ b/pkg/api/handlers/compat/containers_archive.go @@ -133,8 +133,10 @@ func handlePut(w http.ResponseWriter, r *http.Request, decoder *schema.Decoder, return } - w.WriteHeader(http.StatusOK) if err := copyFunc(); err != nil { logrus.Error(err.Error()) + utils.Error(w, "Something went wrong.", http.StatusInternalServerError, err) + return } + w.WriteHeader(http.StatusOK) } diff --git a/pkg/api/handlers/compat/containers_create.go b/pkg/api/handlers/compat/containers_create.go index d5abb6e44..8837e08ca 100644 --- a/pkg/api/handlers/compat/containers_create.go +++ b/pkg/api/handlers/compat/containers_create.go @@ -52,6 +52,13 @@ func CreateContainer(w http.ResponseWriter, r *http.Request) { return } + imageName, err := utils.NormalizeToDockerHub(r, body.Config.Image) + if err != nil { + utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "error normalizing image")) + return + } + body.Config.Image = imageName + newImage, resolvedName, err := runtime.LibimageRuntime().LookupImage(body.Config.Image, nil) if err != nil { if errors.Cause(err) == storage.ErrImageUnknown { diff --git a/pkg/api/handlers/compat/images.go b/pkg/api/handlers/compat/images.go index 0b7ba8bee..4533fddeb 100644 --- a/pkg/api/handlers/compat/images.go +++ b/pkg/api/handlers/compat/images.go @@ -12,7 +12,6 @@ import ( "github.com/containers/common/libimage" "github.com/containers/common/pkg/config" "github.com/containers/image/v5/manifest" - "github.com/containers/image/v5/pkg/shortnames" "github.com/containers/image/v5/types" "github.com/containers/podman/v3/libpod" "github.com/containers/podman/v3/pkg/api/handlers" @@ -56,6 +55,12 @@ func ExportImage(w http.ResponseWriter, r *http.Request) { defer os.Remove(tmpfile.Name()) name := utils.GetName(r) + possiblyNormalizedName, err := utils.NormalizeToDockerHub(r, name) + if err != nil { + utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "error normalizing image")) + return + } + imageEngine := abi.ImageEngine{Libpod: runtime} saveOptions := entities.ImageSaveOptions{ @@ -63,7 +68,7 @@ func ExportImage(w http.ResponseWriter, r *http.Request) { Output: tmpfile.Name(), } - if err := imageEngine.Save(r.Context(), name, nil, saveOptions); err != nil { + if err := imageEngine.Save(r.Context(), possiblyNormalizedName, nil, saveOptions); err != nil { if errors.Cause(err) == storage.ErrImageUnknown { utils.ImageNotFound(w, name, errors.Wrapf(err, "failed to find image %s", name)) return @@ -87,9 +92,6 @@ func ExportImage(w http.ResponseWriter, r *http.Request) { } func CommitContainer(w http.ResponseWriter, r *http.Request) { - var ( - destImage string - ) decoder := r.Context().Value(api.DecoderKey).(*schema.Decoder) runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime) @@ -98,12 +100,12 @@ func CommitContainer(w http.ResponseWriter, r *http.Request) { Changes string `schema:"changes"` Comment string `schema:"comment"` Container string `schema:"container"` + Pause bool `schema:"pause"` + Repo string `schema:"repo"` + Tag string `schema:"tag"` // fromSrc string # fromSrc is currently unused - Pause bool `schema:"pause"` - Repo string `schema:"repo"` - Tag string `schema:"tag"` }{ - // This is where you can override the golang default value for one of fields + Tag: "latest", } if err := decoder.Decode(&query, r.URL.Query()); err != nil { @@ -116,7 +118,6 @@ func CommitContainer(w http.ResponseWriter, r *http.Request) { return } sc := runtime.SystemContext() - tag := "latest" options := libpod.ContainerCommitOptions{ Pause: true, } @@ -133,9 +134,6 @@ func CommitContainer(w http.ResponseWriter, r *http.Request) { return } - if len(query.Tag) > 0 { - tag = query.Tag - } options.Message = query.Comment options.Author = query.Author options.Pause = query.Pause @@ -146,9 +144,15 @@ func CommitContainer(w http.ResponseWriter, r *http.Request) { return } - // I know mitr hates this ... but doing for now + var destImage string if len(query.Repo) > 1 { - destImage = fmt.Sprintf("%s:%s", query.Repo, tag) + destImage = fmt.Sprintf("%s:%s", query.Repo, query.Tag) + possiblyNormalizedName, err := utils.NormalizeToDockerHub(r, destImage) + if err != nil { + utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "error normalizing image")) + return + } + destImage = possiblyNormalizedName } commitImage, err := ctr.Commit(r.Context(), destImage, options) @@ -156,7 +160,7 @@ func CommitContainer(w http.ResponseWriter, r *http.Request) { utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrapf(err, "CommitFailure")) return } - utils.WriteResponse(w, http.StatusOK, handlers.IDResponse{ID: commitImage.ID()}) // nolint + utils.WriteResponse(w, http.StatusCreated, handlers.IDResponse{ID: commitImage.ID()}) // nolint } func CreateImageFromSrc(w http.ResponseWriter, r *http.Request) { @@ -195,12 +199,22 @@ func CreateImageFromSrc(w http.ResponseWriter, r *http.Request) { } } + reference := query.Repo + if query.Repo != "" { + possiblyNormalizedName, err := utils.NormalizeToDockerHub(r, reference) + if err != nil { + utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "error normalizing image")) + return + } + reference = possiblyNormalizedName + } + platformSpecs := strings.Split(query.Platform, "/") opts := entities.ImageImportOptions{ Source: source, Changes: query.Changes, Message: query.Message, - Reference: query.Repo, + Reference: reference, OS: platformSpecs[0], } if len(platformSpecs) > 1 { @@ -250,13 +264,9 @@ func CreateImageFromImage(w http.ResponseWriter, r *http.Request) { return } - fromImage := mergeNameAndTagOrDigest(query.FromImage, query.Tag) - - // without this early check this function would return 200 but reported error via body stream soon after - // it's better to let caller know early via HTTP status code that request cannot be processed - _, err := shortnames.Resolve(runtime.SystemContext(), fromImage) + possiblyNormalizedName, err := utils.NormalizeToDockerHub(r, mergeNameAndTagOrDigest(query.FromImage, query.Tag)) if err != nil { - utils.Error(w, "Something went wrong.", http.StatusBadRequest, errors.Wrap(err, "failed to resolve image name")) + utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "error normalizing image")) return } @@ -291,7 +301,7 @@ func CreateImageFromImage(w http.ResponseWriter, r *http.Request) { pullResChan := make(chan pullResult) go func() { - pulledImages, err := runtime.LibimageRuntime().Pull(r.Context(), fromImage, config.PullPolicyAlways, pullOptions) + pulledImages, err := runtime.LibimageRuntime().Pull(r.Context(), possiblyNormalizedName, config.PullPolicyAlways, pullOptions) pullResChan <- pullResult{images: pulledImages, err: err} }() @@ -371,7 +381,13 @@ func GetImage(w http.ResponseWriter, r *http.Request) { // 404 no such // 500 internal name := utils.GetName(r) - newImage, err := utils.GetImage(r, name) + possiblyNormalizedName, err := utils.NormalizeToDockerHub(r, name) + if err != nil { + utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "error normalizing image")) + return + } + + newImage, err := utils.GetImage(r, possiblyNormalizedName) if err != nil { // Here we need to fiddle with the error message because docker-py is looking for "No // such image" to determine on how to raise the correct exception. @@ -393,14 +409,20 @@ func GetImages(w http.ResponseWriter, r *http.Request) { utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "Failed get images")) return } - var summaries = make([]*entities.ImageSummary, len(images)) - for j, img := range images { + + summaries := make([]*entities.ImageSummary, 0, len(images)) + for _, img := range images { + // If the image is a manifest list, extract as much as we can. + if isML, _ := img.IsManifestList(r.Context()); isML { + continue + } + is, err := handlers.ImageToImageSummary(img) if err != nil { utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "Failed transform image summaries")) return } - summaries[j] = is + summaries = append(summaries, is) } utils.WriteResponse(w, http.StatusOK, summaries) } @@ -483,7 +505,16 @@ func ExportImages(w http.ResponseWriter, r *http.Request) { return } - images := query.Names + images := make([]string, len(query.Names)) + for i, img := range query.Names { + possiblyNormalizedName, err := utils.NormalizeToDockerHub(r, img) + if err != nil { + utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "error normalizing image")) + return + } + images[i] = possiblyNormalizedName + } + tmpfile, err := ioutil.TempFile("", "api.tar") if err != nil { utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "unable to create tempfile")) diff --git a/pkg/api/handlers/compat/images_build.go b/pkg/api/handlers/compat/images_build.go index 7bbc4b99c..f85df02e1 100644 --- a/pkg/api/handlers/compat/images_build.go +++ b/pkg/api/handlers/compat/images_build.go @@ -24,6 +24,7 @@ import ( "github.com/containers/podman/v3/pkg/auth" "github.com/containers/podman/v3/pkg/channel" "github.com/containers/storage/pkg/archive" + "github.com/docker/docker/pkg/jsonmessage" "github.com/gorilla/schema" "github.com/opencontainers/runtime-spec/specs-go" "github.com/pkg/errors" @@ -117,10 +118,11 @@ func BuildImage(w http.ResponseWriter, r *http.Request) { SecurityOpt string `schema:"securityopt"` ShmSize int `schema:"shmsize"` Squash bool `schema:"squash"` - Tag []string `schema:"t"` + Tags []string `schema:"t"` Target string `schema:"target"` Timestamp int64 `schema:"timestamp"` Ulimits string `schema:"ulimits"` + Secrets string `schema:"secrets"` }{ Dockerfile: "Dockerfile", Registry: "docker.io", @@ -143,6 +145,9 @@ func BuildImage(w http.ResponseWriter, r *http.Request) { } } + // convert tag formats + tags := query.Tags + // convert addcaps formats var addCaps = []string{} if _, found := r.URL.Query()["addcaps"]; found { @@ -238,9 +243,57 @@ func BuildImage(w http.ResponseWriter, r *http.Request) { dnssearch = m } + var secrets = []string{} + if _, found := r.URL.Query()["secrets"]; found { + var m = []string{} + if err := json.Unmarshal([]byte(query.Secrets), &m); err != nil { + utils.BadRequest(w, "secrets", query.Secrets, err) + return + } + + // for podman-remote all secrets must be picked from context director + // hence modify src so contextdir is added as prefix + + for _, secret := range m { + secretOpt := strings.Split(secret, ",") + if len(secretOpt) > 0 { + modifiedOpt := []string{} + for _, token := range secretOpt { + arr := strings.SplitN(token, "=", 2) + if len(arr) > 1 { + if arr[0] == "src" { + /* move secret away from contextDir */ + /* to make sure we dont accidentally commit temporary secrets to image*/ + builderDirectory, _ := filepath.Split(contextDirectory) + // following path is outside build context + newSecretPath := filepath.Join(builderDirectory, arr[1]) + oldSecretPath := filepath.Join(contextDirectory, arr[1]) + err := os.Rename(oldSecretPath, newSecretPath) + if err != nil { + utils.BadRequest(w, "secrets", query.Secrets, err) + return + } + + modifiedSrc := fmt.Sprintf("src=%s", newSecretPath) + modifiedOpt = append(modifiedOpt, modifiedSrc) + } else { + modifiedOpt = append(modifiedOpt, token) + } + } + } + secrets = append(secrets, strings.Join(modifiedOpt[:], ",")) + } + } + } + var output string - if len(query.Tag) > 0 { - output = query.Tag[0] + if len(tags) > 0 { + possiblyNormalizedName, err := utils.NormalizeToDockerHub(r, tags[0]) + if err != nil { + utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "error normalizing image")) + return + } + output = possiblyNormalizedName } format := buildah.Dockerv2ImageManifest registry := query.Registry @@ -256,9 +309,14 @@ func BuildImage(w http.ResponseWriter, r *http.Request) { } } } - var additionalTags []string - if len(query.Tag) > 1 { - additionalTags = query.Tag[1:] + var additionalTags []string // nolint + for i := 1; i < len(tags); i++ { + possiblyNormalizedTag, err := utils.NormalizeToDockerHub(r, tags[i]) + if err != nil { + utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "error normalizing image")) + return + } + additionalTags = append(additionalTags, possiblyNormalizedTag) } var buildArgs = map[string]string{} @@ -403,6 +461,22 @@ func BuildImage(w http.ResponseWriter, r *http.Request) { } defer auth.RemoveAuthfile(authfile) + fromImage := query.From + if fromImage != "" { + possiblyNormalizedName, err := utils.NormalizeToDockerHub(r, fromImage) + if err != nil { + utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "error normalizing image")) + return + } + fromImage = possiblyNormalizedName + } + + systemContext := &types.SystemContext{ + AuthFilePath: authfile, + DockerAuthConfig: creds, + } + utils.PossiblyEnforceDockerHub(r, systemContext) + // Channels all mux'ed in select{} below to follow API build protocol stdout := channel.NewWriter(make(chan []byte)) defer stdout.Close() @@ -446,6 +520,7 @@ func BuildImage(w http.ResponseWriter, r *http.Request) { SeccompProfilePath: seccomp, ShmSize: strconv.Itoa(query.ShmSize), Ulimit: ulimits, + Secrets: secrets, }, CNIConfigDir: rtc.Network.CNIPluginDirs[0], CNIPluginPath: util.DefaultCNIPluginPath, @@ -457,7 +532,7 @@ func BuildImage(w http.ResponseWriter, r *http.Request) { Err: auxout, Excludes: excludes, ForceRmIntermediateCtrs: query.ForceRm, - From: query.From, + From: fromImage, IgnoreUnrecognizedInstructions: query.Ignore, Isolation: isolation, Jobs: &jobs, @@ -480,10 +555,7 @@ func BuildImage(w http.ResponseWriter, r *http.Request) { RusageLogFile: query.RusageLogFile, Squash: query.Squash, Target: query.Target, - SystemContext: &types.SystemContext{ - AuthFilePath: authfile, - DockerAuthConfig: creds, - }, + SystemContext: systemContext, } for _, platformSpec := range query.Platform { @@ -546,8 +618,10 @@ func BuildImage(w http.ResponseWriter, r *http.Request) { for { m := struct { - Stream string `json:"stream,omitempty"` - Error string `json:"error,omitempty"` + Stream string `json:"stream,omitempty"` + Error *jsonmessage.JSONError `json:"errorDetail,omitempty"` + // NOTE: `error` is being deprecated check https://github.com/moby/moby/blob/master/pkg/jsonmessage/jsonmessage.go#L148 + ErrorMessage string `json:"error,omitempty"` // deprecate this slowly }{} select { @@ -570,7 +644,10 @@ func BuildImage(w http.ResponseWriter, r *http.Request) { } flush() case e := <-stderr.Chan(): - m.Error = string(e) + m.ErrorMessage = string(e) + m.Error = &jsonmessage.JSONError{ + Message: m.ErrorMessage, + } if err := enc.Encode(m); err != nil { logrus.Warnf("Failed to json encode error %v", err) } @@ -584,7 +661,7 @@ func BuildImage(w http.ResponseWriter, r *http.Request) { logrus.Warnf("Failed to json encode error %v", err) } flush() - for _, tag := range query.Tag { + for _, tag := range tags { m.Stream = fmt.Sprintf("Successfully tagged %s\n", tag) if err := enc.Encode(m); err != nil { logrus.Warnf("Failed to json encode error %v", err) diff --git a/pkg/api/handlers/compat/images_history.go b/pkg/api/handlers/compat/images_history.go index 0c6b9fa88..fb3c2ebd2 100644 --- a/pkg/api/handlers/compat/images_history.go +++ b/pkg/api/handlers/compat/images_history.go @@ -14,9 +14,15 @@ func HistoryImage(w http.ResponseWriter, r *http.Request) { runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime) name := utils.GetName(r) - newImage, _, err := runtime.LibimageRuntime().LookupImage(name, nil) + possiblyNormalizedName, err := utils.NormalizeToDockerHub(r, name) if err != nil { - utils.Error(w, "Something went wrong.", http.StatusNotFound, errors.Wrapf(err, "failed to find image %s", name)) + utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "error normalizing image")) + return + } + + newImage, _, err := runtime.LibimageRuntime().LookupImage(possiblyNormalizedName, nil) + if err != nil { + utils.ImageNotFound(w, possiblyNormalizedName, errors.Wrapf(err, "failed to find image %s", possiblyNormalizedName)) return } history, err := newImage.History(r.Context()) diff --git a/pkg/api/handlers/compat/images_push.go b/pkg/api/handlers/compat/images_push.go index 8b6d3d56a..3a84b5799 100644 --- a/pkg/api/handlers/compat/images_push.go +++ b/pkg/api/handlers/compat/images_push.go @@ -61,12 +61,30 @@ func PushImage(w http.ResponseWriter, r *http.Request) { if query.Tag != "" { imageName += ":" + query.Tag } + if _, err := utils.ParseStorageReference(imageName); err != nil { utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest, errors.Wrapf(err, "image source %q is not a containers-storage-transport reference", imageName)) return } + possiblyNormalizedName, err := utils.NormalizeToDockerHub(r, imageName) + if err != nil { + utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "error normalizing image")) + return + } + imageName = possiblyNormalizedName + localImage, _, err := runtime.LibimageRuntime().LookupImage(possiblyNormalizedName, nil) + if err != nil { + utils.ImageNotFound(w, imageName, errors.Wrapf(err, "failed to find image %s", imageName)) + return + } + rawManifest, _, err := localImage.Manifest(r.Context()) + if err != nil { + utils.Error(w, "Something went wrong.", http.StatusBadRequest, err) + return + } + authconf, authfile, key, err := auth.GetCredentials(r) if err != nil { utils.Error(w, "Something went wrong.", http.StatusBadRequest, errors.Wrapf(err, "failed to parse %q header for %s", key, r.URL.String())) @@ -184,7 +202,7 @@ loop: // break out of for/select infinite loop if tag == "" { tag = "latest" } - report.Status = fmt.Sprintf("%s: digest: %s", tag, string(digestBytes)) + report.Status = fmt.Sprintf("%s: digest: %s size: %d", tag, string(digestBytes), len(rawManifest)) if err := enc.Encode(report); err != nil { logrus.Warnf("Failed to json encode error %q", err.Error()) } diff --git a/pkg/api/handlers/compat/images_remove.go b/pkg/api/handlers/compat/images_remove.go index 2dc247c1f..5c06d8de0 100644 --- a/pkg/api/handlers/compat/images_remove.go +++ b/pkg/api/handlers/compat/images_remove.go @@ -34,12 +34,18 @@ func RemoveImage(w http.ResponseWriter, r *http.Request) { } } name := utils.GetName(r) + possiblyNormalizedName, err := utils.NormalizeToDockerHub(r, name) + if err != nil { + utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "error normalizing image")) + return + } + imageEngine := abi.ImageEngine{Libpod: runtime} options := entities.ImageRemoveOptions{ Force: query.Force, } - report, rmerrors := imageEngine.Remove(r.Context(), []string{name}, options) + report, rmerrors := imageEngine.Remove(r.Context(), []string{possiblyNormalizedName}, options) if len(rmerrors) > 0 && rmerrors[0] != nil { err := rmerrors[0] if errors.Cause(err) == storage.ErrImageUnknown { diff --git a/pkg/api/handlers/compat/images_tag.go b/pkg/api/handlers/compat/images_tag.go index 5d413a821..3fe13e2f5 100644 --- a/pkg/api/handlers/compat/images_tag.go +++ b/pkg/api/handlers/compat/images_tag.go @@ -14,12 +14,16 @@ import ( func TagImage(w http.ResponseWriter, r *http.Request) { runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime) - // /v1.xx/images/(name)/tag name := utils.GetName(r) + possiblyNormalizedName, err := utils.NormalizeToDockerHub(r, name) + if err != nil { + utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "error normalizing image")) + return + } // Allow tagging manifest list instead of resolving instances from manifest lookupOptions := &libimage.LookupImageOptions{ManifestList: true} - newImage, _, err := runtime.LibimageRuntime().LookupImage(name, lookupOptions) + newImage, _, err := runtime.LibimageRuntime().LookupImage(possiblyNormalizedName, lookupOptions) if err != nil { utils.ImageNotFound(w, name, errors.Wrapf(err, "failed to find image %s", name)) return @@ -35,7 +39,14 @@ func TagImage(w http.ResponseWriter, r *http.Request) { } repo := r.Form.Get("repo") tagName := fmt.Sprintf("%s:%s", repo, tag) - if err := newImage.Tag(tagName); err != nil { + + possiblyNormalizedTag, err := utils.NormalizeToDockerHub(r, tagName) + if err != nil { + utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "error normalizing image")) + return + } + + if err := newImage.Tag(possiblyNormalizedTag); err != nil { utils.Error(w, "Something went wrong.", http.StatusInternalServerError, err) return } diff --git a/pkg/api/handlers/libpod/generate.go b/pkg/api/handlers/libpod/generate.go index 1411c680e..88fd69d45 100644 --- a/pkg/api/handlers/libpod/generate.go +++ b/pkg/api/handlers/libpod/generate.go @@ -22,6 +22,7 @@ func GenerateSystemd(w http.ResponseWriter, r *http.Request) { NoHeader bool `schema:"noHeader"` TemplateUnitFile bool `schema:"templateUnitFile"` RestartPolicy *string `schema:"restartPolicy"` + RestartSec uint `schema:"restartSec"` StopTimeout uint `schema:"stopTimeout"` StartTimeout uint `schema:"startTimeout"` ContainerPrefix string `schema:"containerPrefix"` @@ -53,6 +54,7 @@ func GenerateSystemd(w http.ResponseWriter, r *http.Request) { ContainerPrefix: query.ContainerPrefix, PodPrefix: query.PodPrefix, Separator: query.Separator, + RestartSec: &query.RestartSec, } report, err := containerEngine.GenerateSystemd(r.Context(), utils.GetName(r), options) diff --git a/pkg/api/handlers/utils/images.go b/pkg/api/handlers/utils/images.go index d5eb71aa1..d874165e3 100644 --- a/pkg/api/handlers/utils/images.go +++ b/pkg/api/handlers/utils/images.go @@ -3,19 +3,61 @@ package utils import ( "fmt" "net/http" + "strings" "github.com/containers/common/libimage" "github.com/containers/common/pkg/filters" "github.com/containers/image/v5/docker" - "github.com/containers/image/v5/storage" + storageTransport "github.com/containers/image/v5/storage" "github.com/containers/image/v5/transports/alltransports" "github.com/containers/image/v5/types" "github.com/containers/podman/v3/libpod" api "github.com/containers/podman/v3/pkg/api/types" + "github.com/containers/podman/v3/pkg/util" + "github.com/containers/storage" + "github.com/docker/distribution/reference" "github.com/gorilla/schema" "github.com/pkg/errors" ) +// NormalizeToDockerHub normalizes the specified nameOrID to Docker Hub if the +// request is for the compat API and if containers.conf set the specific mode. +// If nameOrID is a (short) ID for a local image, the full ID will be returned. +func NormalizeToDockerHub(r *http.Request, nameOrID string) (string, error) { + if IsLibpodRequest(r) || !util.DefaultContainerConfig().Engine.CompatAPIEnforceDockerHub { + return nameOrID, nil + } + + // Try to lookup the input to figure out if it was an ID or not. + runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime) + img, _, err := runtime.LibimageRuntime().LookupImage(nameOrID, nil) + if err != nil { + if errors.Cause(err) != storage.ErrImageUnknown { + return "", fmt.Errorf("normalizing name for compat API: %v", err) + } + } else if strings.HasPrefix(img.ID(), nameOrID) { + return img.ID(), nil + } + + // No ID, so we can normalize. + named, err := reference.ParseNormalizedNamed(nameOrID) + if err != nil { + return "", fmt.Errorf("normalizing name for compat API: %v", err) + } + + return named.String(), nil +} + +// PossiblyEnforceDockerHub sets fields in the system context to enforce +// resolving short names to Docker Hub if the request is for the compat API and +// if containers.conf set the specific mode. +func PossiblyEnforceDockerHub(r *http.Request, sys *types.SystemContext) { + if IsLibpodRequest(r) || !util.DefaultContainerConfig().Engine.CompatAPIEnforceDockerHub { + return + } + sys.PodmanOnlyShortNamesIgnoreRegistriesConfAndForceDockerHub = true +} + // IsRegistryReference checks if the specified name points to the "docker://" // transport. If it points to no supported transport, we'll assume a // non-transport reference pointing to an image (e.g., "fedora:latest"). @@ -35,13 +77,13 @@ func IsRegistryReference(name string) error { // `types.ImageReference` and enforces it to refer to a // containers-storage-transport reference. func ParseStorageReference(name string) (types.ImageReference, error) { - storagePrefix := fmt.Sprintf("%s:", storage.Transport.Name()) + storagePrefix := storageTransport.Transport.Name() imageRef, err := alltransports.ParseImageName(name) if err == nil && imageRef.Transport().Name() != docker.Transport.Name() { return nil, errors.Errorf("reference %q must be a storage reference", name) } else if err != nil { origErr := err - imageRef, err = alltransports.ParseImageName(fmt.Sprintf("%s%s", storagePrefix, name)) + imageRef, err = alltransports.ParseImageName(fmt.Sprintf("%s:%s", storagePrefix, name)) if err != nil { return nil, errors.Wrapf(origErr, "reference %q must be a storage reference", name) } diff --git a/pkg/api/server/register_generate.go b/pkg/api/server/register_generate.go index 0e36394cf..65340bf56 100644 --- a/pkg/api/server/register_generate.go +++ b/pkg/api/server/register_generate.go @@ -67,6 +67,11 @@ func (s *APIServer) registerGenerateHandlers(r *mux.Router) error { // type: string // default: "-" // description: Systemd unit name separator between name/id and prefix. + // - in: query + // name: restartSec + // type: integer + // default: 0 + // description: Configures the time to sleep before restarting a service. // produces: // - application/json // responses: diff --git a/pkg/bindings/generate/types.go b/pkg/bindings/generate/types.go index 092474e4a..ce560c547 100644 --- a/pkg/bindings/generate/types.go +++ b/pkg/bindings/generate/types.go @@ -20,6 +20,8 @@ type SystemdOptions struct { TemplateUnitFile *bool // RestartPolicy - systemd restart policy. RestartPolicy *string + // RestartSec - systemd service restartsec. Configures the time to sleep before restarting a service. + RestartSec *uint // StartTimeout - time when starting the container. StartTimeout *uint // StopTimeout - time when stopping the container. diff --git a/pkg/bindings/generate/types_systemd_options.go b/pkg/bindings/generate/types_systemd_options.go index d60f1d70e..504d4da7f 100644 --- a/pkg/bindings/generate/types_systemd_options.go +++ b/pkg/bindings/generate/types_systemd_options.go @@ -92,6 +92,21 @@ func (o *SystemdOptions) GetRestartPolicy() string { return *o.RestartPolicy } +// WithRestartSec set field RestartSec to given value +func (o *SystemdOptions) WithRestartSec(value uint) *SystemdOptions { + o.RestartSec = &value + return o +} + +// GetRestartSec returns value of field RestartSec +func (o *SystemdOptions) GetRestartSec() uint { + if o.RestartSec == nil { + var z uint + return z + } + return *o.RestartSec +} + // WithStartTimeout set field StartTimeout to given value func (o *SystemdOptions) WithStartTimeout(value uint) *SystemdOptions { o.StartTimeout = &value diff --git a/pkg/bindings/images/build.go b/pkg/bindings/images/build.go index 3b0bebe9f..be6e5ab55 100644 --- a/pkg/bindings/images/build.go +++ b/pkg/bindings/images/build.go @@ -5,6 +5,7 @@ import ( "compress/gzip" "context" "encoding/json" + "fmt" "io" "io/ioutil" "net/http" @@ -345,6 +346,11 @@ func Build(ctx context.Context, containerFiles []string, options entities.BuildO } c = tmpFile.Name() } + cfDir := filepath.Dir(c) + if absDir, err := filepath.EvalSymlinks(cfDir); err == nil { + name := filepath.ToSlash(strings.TrimPrefix(c, cfDir+string(filepath.Separator))) + c = filepath.Join(absDir, name) + } containerfile, err := filepath.Abs(c) if err != nil { logrus.Errorf("Cannot find absolute path of %v: %v", c, err) @@ -377,6 +383,59 @@ func Build(ctx context.Context, containerFiles []string, options entities.BuildO } params.Set("dockerfile", string(cFileJSON)) } + + // build secrets are usually absolute host path or relative to context dir on host + // in any case move secret to current context and ship the tar. + if secrets := options.CommonBuildOpts.Secrets; len(secrets) > 0 { + secretsForRemote := []string{} + + for _, secret := range secrets { + secretOpt := strings.Split(secret, ",") + if len(secretOpt) > 0 { + modifiedOpt := []string{} + for _, token := range secretOpt { + arr := strings.SplitN(token, "=", 2) + if len(arr) > 1 { + if arr[0] == "src" { + // read specified secret into a tmp file + // move tmp file to tar and change secret source to relative tmp file + tmpSecretFile, err := ioutil.TempFile(options.ContextDirectory, "podman-build-secret") + if err != nil { + return nil, err + } + defer os.Remove(tmpSecretFile.Name()) // clean up + defer tmpSecretFile.Close() + srcSecretFile, err := os.Open(arr[1]) + if err != nil { + return nil, err + } + defer srcSecretFile.Close() + _, err = io.Copy(tmpSecretFile, srcSecretFile) + if err != nil { + return nil, err + } + + //add tmp file to context dir + tarContent = append(tarContent, tmpSecretFile.Name()) + + modifiedSrc := fmt.Sprintf("src=%s", filepath.Base(tmpSecretFile.Name())) + modifiedOpt = append(modifiedOpt, modifiedSrc) + } else { + modifiedOpt = append(modifiedOpt, token) + } + } + } + secretsForRemote = append(secretsForRemote, strings.Join(modifiedOpt[:], ",")) + } + } + + c, err := jsoniter.MarshalToString(secretsForRemote) + if err != nil { + return nil, err + } + params.Add("secrets", c) + } + tarfile, err := nTar(append(excludes, dontexcludes...), tarContent...) if err != nil { logrus.Errorf("Cannot tar container entries %v error: %v", tarContent, err) diff --git a/pkg/bindings/test/attach_test.go b/pkg/bindings/test/attach_test.go index 5c3ec48e4..c78836cb3 100644 --- a/pkg/bindings/test/attach_test.go +++ b/pkg/bindings/test/attach_test.go @@ -1,4 +1,4 @@ -package test_bindings +package bindings_test import ( "bytes" diff --git a/pkg/bindings/test/auth_test.go b/pkg/bindings/test/auth_test.go index d48a3eff7..26690c46a 100644 --- a/pkg/bindings/test/auth_test.go +++ b/pkg/bindings/test/auth_test.go @@ -1,4 +1,4 @@ -package test_bindings +package bindings_test import ( "io/ioutil" diff --git a/pkg/bindings/test/common_test.go b/pkg/bindings/test/common_test.go index 233666a48..76649f628 100644 --- a/pkg/bindings/test/common_test.go +++ b/pkg/bindings/test/common_test.go @@ -1,4 +1,4 @@ -package test_bindings +package bindings_test import ( "context" @@ -51,7 +51,7 @@ var ( shortName: "busybox", tarballName: "busybox.tar", } - CACHE_IMAGES = []testImage{alpine, busybox} + CACHE_IMAGES = []testImage{alpine, busybox} //nolint:golint,stylecheck ) type bindingTest struct { diff --git a/pkg/bindings/test/connection_test.go b/pkg/bindings/test/connection_test.go index 561cf32b5..84a047bc9 100644 --- a/pkg/bindings/test/connection_test.go +++ b/pkg/bindings/test/connection_test.go @@ -1,4 +1,4 @@ -package test_bindings +package bindings_test import ( "context" diff --git a/pkg/bindings/test/containers_test.go b/pkg/bindings/test/containers_test.go index 0f535bc31..b6c06756b 100644 --- a/pkg/bindings/test/containers_test.go +++ b/pkg/bindings/test/containers_test.go @@ -1,4 +1,4 @@ -package test_bindings +package bindings_test import ( "net/http" @@ -103,6 +103,7 @@ var _ = Describe("Podman containers ", func() { Expect(err).To(BeNil()) // Pause by name err = containers.Pause(bt.conn, name, nil) + Expect(err).To(BeNil(), "error from containers.Pause()") //paused := "paused" //_, err = containers.Wait(bt.conn, cid, &paused) //Expect(err).To(BeNil()) diff --git a/pkg/bindings/test/create_test.go b/pkg/bindings/test/create_test.go index 3c83aac02..f70ba7248 100644 --- a/pkg/bindings/test/create_test.go +++ b/pkg/bindings/test/create_test.go @@ -1,4 +1,4 @@ -package test_bindings +package bindings_test import ( "time" diff --git a/pkg/bindings/test/exec_test.go b/pkg/bindings/test/exec_test.go index c10452eaf..d2f9b121a 100644 --- a/pkg/bindings/test/exec_test.go +++ b/pkg/bindings/test/exec_test.go @@ -1,4 +1,4 @@ -package test_bindings +package bindings_test import ( "time" diff --git a/pkg/bindings/test/generator_test.go b/pkg/bindings/test/generator_test.go index d04cc10f9..ab0c49713 100644 --- a/pkg/bindings/test/generator_test.go +++ b/pkg/bindings/test/generator_test.go @@ -1,4 +1,4 @@ -package test_bindings +package bindings_test import ( "github.com/containers/podman/v3/pkg/bindings/containers" diff --git a/pkg/bindings/test/images_test.go b/pkg/bindings/test/images_test.go index aa8ff0537..8489e6ff1 100644 --- a/pkg/bindings/test/images_test.go +++ b/pkg/bindings/test/images_test.go @@ -1,4 +1,4 @@ -package test_bindings +package bindings_test import ( "net/http" @@ -89,6 +89,10 @@ var _ = Describe("Podman images", func() { response, errs := images.Remove(bt.conn, []string{"foobar5000"}, nil) Expect(len(errs)).To(BeNumerically(">", 0)) code, _ := bindings.CheckResponseCode(errs[0]) + // FIXME FIXME FIXME: #12441: THIS IS BROKEN + // FIXME FIXME FIXME: we get msg: "foobar5000: image not known" + // FIXME FIXME FIXME: ...with no ResponseCode + Expect(code).To(BeNumerically("==", -1)) // Remove an image by name, validate image is removed and error is nil inspectData, err := images.GetImage(bt.conn, busybox.shortName, nil) @@ -99,6 +103,7 @@ var _ = Describe("Podman images", func() { Expect(inspectData.ID).To(Equal(response.Deleted[0])) inspectData, err = images.GetImage(bt.conn, busybox.shortName, nil) code, _ = bindings.CheckResponseCode(err) + Expect(code).To(BeNumerically("==", http.StatusNotFound)) // Start a container with alpine image var top string = "top" @@ -113,6 +118,9 @@ var _ = Describe("Podman images", func() { // deleting hence image cannot be deleted until the container is deleted. response, errs = images.Remove(bt.conn, []string{alpine.shortName}, nil) code, _ = bindings.CheckResponseCode(errs[0]) + // FIXME FIXME FIXME: #12441: another invalid error + // FIXME FIXME FIXME: this time msg="Image used by SHA: ..." + Expect(code).To(BeNumerically("==", -1)) // Removing the image "alpine" where force = true options := new(images.RemoveOptions).WithForce(true) @@ -122,10 +130,12 @@ var _ = Describe("Podman images", func() { // is gone as well. _, err = containers.Inspect(bt.conn, "top", nil) code, _ = bindings.CheckResponseCode(err) + Expect(code).To(BeNumerically("==", http.StatusNotFound)) // Now make sure both images are gone. inspectData, err = images.GetImage(bt.conn, busybox.shortName, nil) code, _ = bindings.CheckResponseCode(err) + Expect(code).To(BeNumerically("==", http.StatusNotFound)) inspectData, err = images.GetImage(bt.conn, alpine.shortName, nil) code, _ = bindings.CheckResponseCode(err) @@ -339,6 +349,7 @@ var _ = Describe("Podman images", func() { // Search with a fqdn reports, err = images.Search(bt.conn, "quay.io/libpod/alpine_nginx", nil) + Expect(err).To(BeNil(), "Error in images.Search()") Expect(len(reports)).To(BeNumerically(">=", 1)) }) diff --git a/pkg/bindings/test/info_test.go b/pkg/bindings/test/info_test.go index f61e8c370..f643a2c28 100644 --- a/pkg/bindings/test/info_test.go +++ b/pkg/bindings/test/info_test.go @@ -1,4 +1,4 @@ -package test_bindings +package bindings_test import ( "runtime" diff --git a/pkg/bindings/test/manifests_test.go b/pkg/bindings/test/manifests_test.go index 4d8ca74bf..e65632057 100644 --- a/pkg/bindings/test/manifests_test.go +++ b/pkg/bindings/test/manifests_test.go @@ -1,4 +1,4 @@ -package test_bindings +package bindings_test import ( "net/http" diff --git a/pkg/bindings/test/networks_test.go b/pkg/bindings/test/networks_test.go index 85ceeb998..d95862f6f 100644 --- a/pkg/bindings/test/networks_test.go +++ b/pkg/bindings/test/networks_test.go @@ -1,4 +1,4 @@ -package test_bindings +package bindings_test import ( "context" diff --git a/pkg/bindings/test/pods_test.go b/pkg/bindings/test/pods_test.go index 879d4d00d..0a4261ea2 100644 --- a/pkg/bindings/test/pods_test.go +++ b/pkg/bindings/test/pods_test.go @@ -1,4 +1,4 @@ -package test_bindings +package bindings_test import ( "fmt" @@ -79,6 +79,7 @@ var _ = Describe("Podman pods", func() { var newpod2 string = "newpod2" bt.Podcreate(&newpod2) podSummary, err = pods.List(bt.conn, nil) + Expect(err).To(BeNil(), "Error from pods.List") Expect(len(podSummary)).To(Equal(2)) var names []string for _, i := range podSummary { @@ -106,6 +107,7 @@ var _ = Describe("Podman pods", func() { options := new(pods.ListOptions).WithFilters(filters) filteredPods, err := pods.List(bt.conn, options) Expect(err).ToNot(BeNil()) + Expect(len(filteredPods)).To(Equal(0), "len(filteredPods)") code, _ := bindings.CheckResponseCode(err) Expect(code).To(BeNumerically("==", http.StatusInternalServerError)) @@ -301,6 +303,7 @@ var _ = Describe("Podman pods", func() { // No pods pruned since no pod in exited state pruneResponse, err := pods.Prune(bt.conn, nil) Expect(err).To(BeNil()) + Expect(len(pruneResponse)).To(Equal(0), "len(pruneResponse)") podSummary, err := pods.List(bt.conn, nil) Expect(err).To(BeNil()) Expect(len(podSummary)).To(Equal(2)) @@ -317,6 +320,7 @@ var _ = Describe("Podman pods", func() { Expect(response.State).To(Equal(define.PodStateExited)) pruneResponse, err = pods.Prune(bt.conn, nil) Expect(err).To(BeNil()) + Expect(len(pruneResponse)).To(Equal(1), "len(pruneResponse)") // Validate status and record pod id of pod to be pruned Expect(response.State).To(Equal(define.PodStateExited)) podID := response.ID diff --git a/pkg/bindings/test/resource_test.go b/pkg/bindings/test/resource_test.go index b12d1ccd6..19ac33eb2 100644 --- a/pkg/bindings/test/resource_test.go +++ b/pkg/bindings/test/resource_test.go @@ -1,4 +1,4 @@ -package test_bindings +package bindings_test import ( "context" diff --git a/pkg/bindings/test/secrets_test.go b/pkg/bindings/test/secrets_test.go index 5edb37f18..52c964556 100644 --- a/pkg/bindings/test/secrets_test.go +++ b/pkg/bindings/test/secrets_test.go @@ -1,4 +1,4 @@ -package test_bindings +package bindings_test import ( "context" diff --git a/pkg/bindings/test/system_test.go b/pkg/bindings/test/system_test.go index aecc5db81..4549a14c9 100644 --- a/pkg/bindings/test/system_test.go +++ b/pkg/bindings/test/system_test.go @@ -1,4 +1,4 @@ -package test_bindings +package bindings_test import ( "sync" diff --git a/pkg/bindings/test/test_suite_test.go b/pkg/bindings/test/test_suite_test.go index d2c2c7838..8fb46c205 100644 --- a/pkg/bindings/test/test_suite_test.go +++ b/pkg/bindings/test/test_suite_test.go @@ -1,4 +1,4 @@ -package test_bindings_test +package bindings_test import ( "testing" diff --git a/pkg/bindings/test/volumes_test.go b/pkg/bindings/test/volumes_test.go index 14bda114e..43ef54889 100644 --- a/pkg/bindings/test/volumes_test.go +++ b/pkg/bindings/test/volumes_test.go @@ -1,4 +1,4 @@ -package test_bindings +package bindings_test import ( "context" diff --git a/pkg/domain/entities/generate.go b/pkg/domain/entities/generate.go index 7e80e5d2d..e431a70af 100644 --- a/pkg/domain/entities/generate.go +++ b/pkg/domain/entities/generate.go @@ -10,6 +10,8 @@ type GenerateSystemdOptions struct { New bool // RestartPolicy - systemd restart policy. RestartPolicy *string + // RestartSec - systemd service restartsec. Configures the time to sleep before restarting a service. + RestartSec *uint // StartTimeout - time when starting the container. StartTimeout *uint // StopTimeout - time when stopping the container. diff --git a/pkg/domain/entities/images.go b/pkg/domain/entities/images.go index 54f7b5d45..8b0fd2b85 100644 --- a/pkg/domain/entities/images.go +++ b/pkg/domain/entities/images.go @@ -208,6 +208,8 @@ type ImagePushOptions struct { SkipTLSVerify types.OptionalBool // Progress to get progress notifications Progress chan types.ProgressProperties + // CompressionFormat is the format to use for the compression of the blobs + CompressionFormat string } // ImageSearchOptions are the arguments for searching images. diff --git a/pkg/domain/infra/abi/generate.go b/pkg/domain/infra/abi/generate.go index 0defa1923..68bb351bf 100644 --- a/pkg/domain/infra/abi/generate.go +++ b/pkg/domain/infra/abi/generate.go @@ -139,7 +139,11 @@ func (ic *ContainerEngine) GenerateKube(ctx context.Context, nameOrIDs []string, podContent = append(podContent, b) if options.Service { - b, err := generateKubeYAML(libpod.GenerateKubeServiceFromV1Pod(po, []k8sAPI.ServicePort{})) + svc, err := libpod.GenerateKubeServiceFromV1Pod(po, []k8sAPI.ServicePort{}) + if err != nil { + return nil, err + } + b, err := generateKubeYAML(svc) if err != nil { return nil, err } @@ -177,7 +181,11 @@ func getKubePods(ctx context.Context, pods []*libpod.Pod, getService bool) ([][] pos = append(pos, b) if getService { - b, err := generateKubeYAML(libpod.GenerateKubeServiceFromV1Pod(po, sp)) + svc, err := libpod.GenerateKubeServiceFromV1Pod(po, sp) + if err != nil { + return nil, nil, err + } + b, err := generateKubeYAML(svc) if err != nil { return nil, nil, err } diff --git a/pkg/domain/infra/abi/images.go b/pkg/domain/infra/abi/images.go index 8b44b869a..4346182d6 100644 --- a/pkg/domain/infra/abi/images.go +++ b/pkg/domain/infra/abi/images.go @@ -18,6 +18,7 @@ import ( "github.com/containers/image/v5/docker" "github.com/containers/image/v5/docker/reference" "github.com/containers/image/v5/manifest" + "github.com/containers/image/v5/pkg/compression" "github.com/containers/image/v5/signature" "github.com/containers/image/v5/transports" "github.com/containers/image/v5/transports/alltransports" @@ -305,6 +306,22 @@ func (ir *ImageEngine) Push(ctx context.Context, source string, destination stri pushOptions.SignBy = options.SignBy pushOptions.InsecureSkipTLSVerify = options.SkipTLSVerify + compressionFormat := options.CompressionFormat + if compressionFormat == "" { + config, err := ir.Libpod.GetConfigNoCopy() + if err != nil { + return err + } + compressionFormat = config.Engine.CompressionFormat + } + if compressionFormat != "" { + algo, err := compression.AlgorithmByName(compressionFormat) + if err != nil { + return err + } + pushOptions.CompressionFormat = &algo + } + if !options.Quiet { pushOptions.Writer = os.Stderr } diff --git a/pkg/domain/infra/abi/system.go b/pkg/domain/infra/abi/system.go index 7da7754f2..e6c9d850b 100644 --- a/pkg/domain/infra/abi/system.go +++ b/pkg/domain/infra/abi/system.go @@ -365,9 +365,12 @@ 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 + // Make sure to unlock, unshare can run for a long time. rootlessNetNS.Lock.Unlock() - defer rootlessNetNS.Cleanup(ic.Libpod) + // We do not want to cleanup the netns after unshare. + // The problem is that we cannot know if we need to cleanup and + // secondly unshare should allow user to setup the namespace with + // special things, e.g. potentially macvlan or something like that. return rootlessNetNS.Do(unshare) } return unshare() diff --git a/pkg/domain/infra/tunnel/generate.go b/pkg/domain/infra/tunnel/generate.go index d62a318d6..dd895b61f 100644 --- a/pkg/domain/infra/tunnel/generate.go +++ b/pkg/domain/infra/tunnel/generate.go @@ -19,6 +19,9 @@ func (ic *ContainerEngine) GenerateSystemd(ctx context.Context, nameOrID string, if opts.RestartPolicy != nil { options.WithRestartPolicy(*opts.RestartPolicy) } + if opts.RestartSec != nil { + options.WithRestartSec(*opts.RestartSec) + } return generate.Systemd(ic.ClientCtx, nameOrID, options) } diff --git a/pkg/domain/infra/tunnel/images.go b/pkg/domain/infra/tunnel/images.go index fde57972f..2feb9d7ad 100644 --- a/pkg/domain/infra/tunnel/images.go +++ b/pkg/domain/infra/tunnel/images.go @@ -270,7 +270,10 @@ func (ir *ImageEngine) Save(ctx context.Context, nameOrID string, tags []string, defer func() { _ = os.Remove(f.Name()) }() } default: - f, err = os.Create(opts.Output) + // This code was added to allow for opening stdout replacing + // os.Create(opts.Output) which was attempting to open the file + // for read/write which fails on Darwin platforms + f, err = os.OpenFile(opts.Output, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644) } if err != nil { return err diff --git a/pkg/machine/ignition.go b/pkg/machine/ignition.go index e19940b22..5c465d37d 100644 --- a/pkg/machine/ignition.go +++ b/pkg/machine/ignition.go @@ -89,7 +89,7 @@ Type=oneshot RemainAfterExit=yes ExecStart=/bin/sh -c '/usr/bin/echo Ready >/dev/%s' [Install] -RequiredBy=multi-user.target +RequiredBy=default.target ` deMoby := `[Unit] Description=Remove moby-engine @@ -106,7 +106,7 @@ ExecStart=/usr/bin/rpm-ostree ex apply-live --allow-replacement ExecStartPost=/bin/touch /var/lib/%N.stamp [Install] -WantedBy=multi-user.target +WantedBy=default.target ` _ = ready ignSystemd := Systemd{ diff --git a/pkg/specgen/generate/oci.go b/pkg/specgen/generate/oci.go index 1b022b912..df5788099 100644 --- a/pkg/specgen/generate/oci.go +++ b/pkg/specgen/generate/oci.go @@ -329,6 +329,14 @@ func SpecGenToOCI(ctx context.Context, s *specgen.SpecGenerator, rt *libpod.Runt g.AddLinuxResourcesDevice(true, dev.Type, dev.Major, dev.Minor, dev.Access) } + for k, v := range s.WeightDevice { + statT := unix.Stat_t{} + if err := unix.Stat(k, &statT); err != nil { + return nil, errors.Wrapf(err, "failed to inspect '%s' in --blkio-weight-device", k) + } + g.AddLinuxResourcesBlockIOWeightDevice((int64(unix.Major(uint64(statT.Rdev)))), (int64(unix.Minor(uint64(statT.Rdev)))), *v.Weight) + } + BlockAccessToKernelFilesystems(s.Privileged, s.PidNS.IsHost(), s.Mask, s.Unmask, &g) g.ClearProcessEnv() diff --git a/pkg/specgenutil/specgen.go b/pkg/specgenutil/specgen.go index 7a572e730..637a6a8dd 100644 --- a/pkg/specgenutil/specgen.go +++ b/pkg/specgenutil/specgen.go @@ -85,7 +85,7 @@ func getIOLimits(s *specgen.SpecGenerator, c *entities.ContainerCreateOptions) ( } if len(c.BlkIOWeightDevice) > 0 { - if err := parseWeightDevices(s, c.BlkIOWeightDevice); err != nil { + if s.WeightDevice, err = parseWeightDevices(c.BlkIOWeightDevice); err != nil { return nil, err } hasLimits = true @@ -791,29 +791,30 @@ func makeHealthCheckFromCli(inCmd, interval string, retries uint, timeout, start return &hc, nil } -func parseWeightDevices(s *specgen.SpecGenerator, weightDevs []string) error { +func parseWeightDevices(weightDevs []string) (map[string]specs.LinuxWeightDevice, error) { + wd := make(map[string]specs.LinuxWeightDevice) for _, val := range weightDevs { split := strings.SplitN(val, ":", 2) if len(split) != 2 { - return fmt.Errorf("bad format: %s", val) + return nil, fmt.Errorf("bad format: %s", val) } if !strings.HasPrefix(split[0], "/dev/") { - return fmt.Errorf("bad format for device path: %s", val) + return nil, fmt.Errorf("bad format for device path: %s", val) } weight, err := strconv.ParseUint(split[1], 10, 0) if err != nil { - return fmt.Errorf("invalid weight for device: %s", val) + return nil, fmt.Errorf("invalid weight for device: %s", val) } if weight > 0 && (weight < 10 || weight > 1000) { - return fmt.Errorf("invalid weight for device: %s", val) + return nil, fmt.Errorf("invalid weight for device: %s", val) } w := uint16(weight) - s.WeightDevice[split[0]] = specs.LinuxWeightDevice{ + wd[split[0]] = specs.LinuxWeightDevice{ Weight: &w, LeafWeight: nil, } } - return nil + return wd, nil } func parseThrottleBPSDevices(bpsDevices []string) (map[string]specs.LinuxThrottleDevice, error) { diff --git a/pkg/systemd/generate/containers.go b/pkg/systemd/generate/containers.go index 2fdec5fb1..d0c94123d 100644 --- a/pkg/systemd/generate/containers.go +++ b/pkg/systemd/generate/containers.go @@ -134,7 +134,7 @@ NotifyAccess={{{{.NotifyAccess}}}} {{{{- end}}}} [Install] -WantedBy=multi-user.target default.target +WantedBy=default.target ` // ContainerUnit generates a systemd unit for the specified container. Based diff --git a/pkg/systemd/generate/containers_test.go b/pkg/systemd/generate/containers_test.go index eab2c2e67..33b09005c 100644 --- a/pkg/systemd/generate/containers_test.go +++ b/pkg/systemd/generate/containers_test.go @@ -62,7 +62,7 @@ PIDFile=/run/containers/storage/overlay-containers/639c53578af4d84b8800b4635fa4e Type=forking [Install] -WantedBy=multi-user.target default.target +WantedBy=default.target ` goodID := serviceInfo + headerInfo + goodIDContent goodIDNoHeaderInfo := serviceInfo + goodIDContent @@ -88,7 +88,7 @@ PIDFile=/run/containers/storage/overlay-containers/639c53578af4d84b8800b4635fa4e Type=forking [Install] -WantedBy=multi-user.target default.target +WantedBy=default.target ` goodNameBoundTo := `# container-foobar.service @@ -114,7 +114,7 @@ PIDFile=/run/containers/storage/overlay-containers/639c53578af4d84b8800b4635fa4e Type=forking [Install] -WantedBy=multi-user.target default.target +WantedBy=default.target ` goodWithNameAndGeneric := `# jadda-jadda.service @@ -139,7 +139,7 @@ Type=notify NotifyAccess=all [Install] -WantedBy=multi-user.target default.target +WantedBy=default.target ` goodWithNameAndSdnotify := `# jadda-jadda.service @@ -164,7 +164,7 @@ Type=notify NotifyAccess=all [Install] -WantedBy=multi-user.target default.target +WantedBy=default.target ` goodWithExplicitShortDetachParam := `# jadda-jadda.service @@ -189,7 +189,7 @@ Type=notify NotifyAccess=all [Install] -WantedBy=multi-user.target default.target +WantedBy=default.target ` goodNameNewWithPodFile := `# jadda-jadda.service @@ -214,7 +214,7 @@ Type=notify NotifyAccess=all [Install] -WantedBy=multi-user.target default.target +WantedBy=default.target ` goodNameNewDetach := `# jadda-jadda.service @@ -239,7 +239,7 @@ Type=notify NotifyAccess=all [Install] -WantedBy=multi-user.target default.target +WantedBy=default.target ` goodIDNew := `# container-639c53578af4d84b8800b4635fa4e680ee80fd67e0e6a2d4eea48d1e3230f401.service @@ -264,7 +264,7 @@ Type=notify NotifyAccess=all [Install] -WantedBy=multi-user.target default.target +WantedBy=default.target ` genGoodNewDetach := func(detachparam string) string { @@ -292,7 +292,7 @@ Type=notify NotifyAccess=all [Install] -WantedBy=multi-user.target default.target +WantedBy=default.target ` return goodNewDetach } @@ -319,7 +319,7 @@ Type=notify NotifyAccess=all [Install] -WantedBy=multi-user.target default.target +WantedBy=default.target ` goodNewRootFlags := `# jadda-jadda.service @@ -344,7 +344,7 @@ Type=notify NotifyAccess=all [Install] -WantedBy=multi-user.target default.target +WantedBy=default.target ` goodContainerCreate := `# jadda-jadda.service @@ -369,7 +369,7 @@ Type=notify NotifyAccess=all [Install] -WantedBy=multi-user.target default.target +WantedBy=default.target ` goodNewWithJournaldTag := `# jadda-jadda.service @@ -394,7 +394,7 @@ Type=notify NotifyAccess=all [Install] -WantedBy=multi-user.target default.target +WantedBy=default.target ` goodNewWithSpecialChars := `# jadda-jadda.service @@ -419,7 +419,7 @@ Type=notify NotifyAccess=all [Install] -WantedBy=multi-user.target default.target +WantedBy=default.target ` goodNewWithIDFiles := `# jadda-jadda.service @@ -444,7 +444,7 @@ Type=notify NotifyAccess=all [Install] -WantedBy=multi-user.target default.target +WantedBy=default.target ` goodNewWithPodIDFiles := `# jadda-jadda.service @@ -469,7 +469,7 @@ Type=notify NotifyAccess=all [Install] -WantedBy=multi-user.target default.target +WantedBy=default.target ` goodNewWithEnvar := `# jadda-jadda.service @@ -495,7 +495,7 @@ Type=notify NotifyAccess=all [Install] -WantedBy=multi-user.target default.target +WantedBy=default.target ` goodNewWithRestartPolicy := `# jadda-jadda.service @@ -521,7 +521,7 @@ Type=notify NotifyAccess=all [Install] -WantedBy=multi-user.target default.target +WantedBy=default.target ` templateGood := `# container-foo@.service @@ -547,7 +547,7 @@ Type=notify NotifyAccess=all [Install] -WantedBy=multi-user.target default.target +WantedBy=default.target ` tests := []struct { name string diff --git a/pkg/systemd/generate/pods.go b/pkg/systemd/generate/pods.go index f4cc31c8e..f2d04dadc 100644 --- a/pkg/systemd/generate/pods.go +++ b/pkg/systemd/generate/pods.go @@ -30,6 +30,8 @@ type podInfo struct { StopTimeout uint // RestartPolicy of the systemd unit (e.g., no, on-failure, always). RestartPolicy string + // RestartSec of the systemd unit. Configures the time to sleep before restarting a service. + RestartSec uint // PIDFile of the service. Required for forking services. Must point to the // PID of the associated conmon process. PIDFile string @@ -89,6 +91,9 @@ Before={{{{- range $index, $value := .RequiredServices -}}}}{{{{if $index}}}} {{ [Service] Environment={{{{.EnvVariable}}}}=%n Restart={{{{.RestartPolicy}}}} +{{{{- if .RestartSec}}}} +RestartSec={{{{.RestartSec}}}} +{{{{- end}}}} TimeoutStopSec={{{{.TimeoutStopSec}}}} {{{{- if .ExecStartPre1}}}} ExecStartPre={{{{.ExecStartPre1}}}} @@ -103,7 +108,7 @@ PIDFile={{{{.PIDFile}}}} Type=forking [Install] -WantedBy=multi-user.target default.target +WantedBy=default.target ` // PodUnits generates systemd units for the specified pod and its containers. @@ -242,6 +247,10 @@ func executePodTemplate(info *podInfo, options entities.GenerateSystemdOptions) info.RestartPolicy = *options.RestartPolicy } + if options.RestartSec != nil { + info.RestartSec = *options.RestartSec + } + // Make sure the executable is set. if info.Executable == "" { executable, err := os.Executable() diff --git a/pkg/systemd/generate/pods_test.go b/pkg/systemd/generate/pods_test.go index c565a30ed..0889507a5 100644 --- a/pkg/systemd/generate/pods_test.go +++ b/pkg/systemd/generate/pods_test.go @@ -62,11 +62,38 @@ PIDFile=/run/containers/storage/overlay-containers/639c53578af4d84b8800b4635fa4e Type=forking [Install] -WantedBy=multi-user.target default.target +WantedBy=default.target ` podGood := serviceInfo + headerInfo + podContent podGoodNoHeaderInfo := serviceInfo + podContent + podGoodRestartSec := `# pod-123abc.service +# autogenerated by Podman CI + +[Unit] +Description=Podman pod-123abc.service +Documentation=man:podman-generate-systemd(1) +Wants=network-online.target +After=network-online.target +RequiresMountsFor=/var/run/containers/storage +Requires=container-1.service container-2.service +Before=container-1.service container-2.service + +[Service] +Environment=PODMAN_SYSTEMD_UNIT=%n +Restart=on-failure +RestartSec=15 +TimeoutStopSec=102 +ExecStart=/usr/bin/podman start jadda-jadda-infra +ExecStop=/usr/bin/podman stop -t 42 jadda-jadda-infra +ExecStopPost=/usr/bin/podman stop -t 42 jadda-jadda-infra +PIDFile=/run/containers/storage/overlay-containers/639c53578af4d84b8800b4635fa4e680ee80fd67e0e6a2d4eea48d1e3230f401/userdata/conmon.pid +Type=forking + +[Install] +WantedBy=default.target +` + podGoodNamedNew := `# pod-123abc.service # autogenerated by Podman CI @@ -92,7 +119,7 @@ PIDFile=%t/pod-123abc.pid Type=forking [Install] -WantedBy=multi-user.target default.target +WantedBy=default.target ` podGoodNamedNewWithRootArgs := `# pod-123abc.service @@ -120,7 +147,7 @@ PIDFile=%t/pod-123abc.pid Type=forking [Install] -WantedBy=multi-user.target default.target +WantedBy=default.target ` podGoodNamedNewWithReplaceFalse := `# pod-123abc.service @@ -148,7 +175,7 @@ PIDFile=%t/pod-123abc.pid Type=forking [Install] -WantedBy=multi-user.target default.target +WantedBy=default.target ` podNewLabelWithCurlyBraces := `# pod-123abc.service @@ -176,7 +203,7 @@ PIDFile=%t/pod-123abc.pid Type=forking [Install] -WantedBy=multi-user.target default.target +WantedBy=default.target ` tests := []struct { @@ -205,6 +232,25 @@ WantedBy=multi-user.target default.target false, false, }, + {"pod restartSec", + podInfo{ + Executable: "/usr/bin/podman", + ServiceName: "pod-123abc", + InfraNameOrID: "jadda-jadda-infra", + PIDFile: "/run/containers/storage/overlay-containers/639c53578af4d84b8800b4635fa4e680ee80fd67e0e6a2d4eea48d1e3230f401/userdata/conmon.pid", + StopTimeout: 42, + PodmanVersion: "CI", + GraphRoot: "/var/lib/containers/storage", + RunRoot: "/var/run/containers/storage", + RequiredServices: []string{"container-1", "container-2"}, + CreateCommand: []string{"podman", "pod", "create", "--name", "foo", "bar=arg with space"}, + RestartSec: 15, + }, + podGoodRestartSec, + false, + false, + false, + }, {"pod noHeader", podInfo{ Executable: "/usr/bin/podman", |