diff options
Diffstat (limited to 'pkg')
23 files changed, 660 insertions, 328 deletions
diff --git a/pkg/api/handlers/compat/containers.go b/pkg/api/handlers/compat/containers.go index 8ce2180ab..b103e399d 100644 --- a/pkg/api/handlers/compat/containers.go +++ b/pkg/api/handlers/compat/containers.go @@ -1,29 +1,21 @@ package compat import ( - "encoding/binary" "encoding/json" "fmt" - "io" "net/http" - "strconv" "strings" - "sync" - "time" "github.com/containers/libpod/libpod" "github.com/containers/libpod/libpod/define" - "github.com/containers/libpod/libpod/logs" "github.com/containers/libpod/pkg/api/handlers" "github.com/containers/libpod/pkg/api/handlers/utils" "github.com/containers/libpod/pkg/signal" - "github.com/containers/libpod/pkg/util" "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/container" "github.com/docker/go-connections/nat" "github.com/gorilla/schema" "github.com/pkg/errors" - log "github.com/sirupsen/logrus" ) func RemoveContainer(w http.ResponseWriter, r *http.Request) { @@ -213,140 +205,6 @@ func WaitContainer(w http.ResponseWriter, r *http.Request) { }) } -func LogsFromContainer(w http.ResponseWriter, r *http.Request) { - decoder := r.Context().Value("decoder").(*schema.Decoder) - runtime := r.Context().Value("runtime").(*libpod.Runtime) - - query := struct { - Follow bool `schema:"follow"` - Stdout bool `schema:"stdout"` - Stderr bool `schema:"stderr"` - Since string `schema:"since"` - Until string `schema:"until"` - Timestamps bool `schema:"timestamps"` - Tail string `schema:"tail"` - }{ - Tail: "all", - } - if err := decoder.Decode(&query, r.URL.Query()); err != nil { - utils.Error(w, "Something went wrong.", http.StatusBadRequest, errors.Wrapf(err, "Failed to parse parameters for %s", r.URL.String())) - return - } - - if !(query.Stdout || query.Stderr) { - msg := fmt.Sprintf("%s: you must choose at least one stream", http.StatusText(http.StatusBadRequest)) - utils.Error(w, msg, http.StatusBadRequest, errors.Errorf("%s for %s", msg, r.URL.String())) - return - } - - name := utils.GetName(r) - ctnr, err := runtime.LookupContainer(name) - if err != nil { - utils.ContainerNotFound(w, name, err) - return - } - - var tail int64 = -1 - if query.Tail != "all" { - tail, err = strconv.ParseInt(query.Tail, 0, 64) - if err != nil { - utils.BadRequest(w, "tail", query.Tail, err) - return - } - } - - var since time.Time - if _, found := r.URL.Query()["since"]; found { - since, err = util.ParseInputTime(query.Since) - if err != nil { - utils.BadRequest(w, "since", query.Since, err) - return - } - } - - var until time.Time - if _, found := r.URL.Query()["until"]; found { - // FIXME: until != since but the logs backend does not yet support until. - since, err = util.ParseInputTime(query.Until) - if err != nil { - utils.BadRequest(w, "until", query.Until, err) - return - } - } - - options := &logs.LogOptions{ - Details: true, - Follow: query.Follow, - Since: since, - Tail: tail, - Timestamps: query.Timestamps, - } - - var wg sync.WaitGroup - options.WaitGroup = &wg - - logChannel := make(chan *logs.LogLine, tail+1) - if err := runtime.Log([]*libpod.Container{ctnr}, options, logChannel); err != nil { - utils.InternalServerError(w, errors.Wrapf(err, "Failed to obtain logs for Container '%s'", name)) - return - } - go func() { - wg.Wait() - close(logChannel) - }() - - w.WriteHeader(http.StatusOK) - - var frame strings.Builder - header := make([]byte, 8) - for ok := true; ok; ok = query.Follow { - for line := range logChannel { - if _, found := r.URL.Query()["until"]; found { - if line.Time.After(until) { - break - } - } - - // Reset buffer we're ready to loop again - frame.Reset() - switch line.Device { - case "stdout": - if !query.Stdout { - continue - } - header[0] = 1 - case "stderr": - if !query.Stderr { - continue - } - header[0] = 2 - default: - // Logging and moving on is the best we can do here. We may have already sent - // a Status and Content-Type to client therefore we can no longer report an error. - log.Infof("unknown Device type '%s' in log file from Container %s", line.Device, ctnr.ID()) - continue - } - - if query.Timestamps { - frame.WriteString(line.Time.Format(time.RFC3339)) - frame.WriteString(" ") - } - frame.WriteString(line.Msg) - - binary.BigEndian.PutUint32(header[4:], uint32(frame.Len())) - if _, err := w.Write(header[0:8]); err != nil { - log.Errorf("unable to write log output header: %q", err) - } - if _, err := io.WriteString(w, frame.String()); err != nil { - log.Errorf("unable to write frame string: %q", err) - } - if flusher, ok := w.(http.Flusher); ok { - flusher.Flush() - } - } - } -} - func LibpodToContainer(l *libpod.Container, sz bool) (*handlers.Container, error) { imageID, imageName := l.Image() diff --git a/pkg/api/handlers/compat/containers_logs.go b/pkg/api/handlers/compat/containers_logs.go new file mode 100644 index 000000000..3b25a3ecc --- /dev/null +++ b/pkg/api/handlers/compat/containers_logs.go @@ -0,0 +1,154 @@ +package compat + +import ( + "encoding/binary" + "fmt" + "io" + "net/http" + "strconv" + "strings" + "sync" + "time" + + "github.com/containers/libpod/libpod" + "github.com/containers/libpod/libpod/logs" + "github.com/containers/libpod/pkg/api/handlers/utils" + "github.com/containers/libpod/pkg/util" + "github.com/gorilla/schema" + "github.com/pkg/errors" + log "github.com/sirupsen/logrus" +) + +func LogsFromContainer(w http.ResponseWriter, r *http.Request) { + decoder := r.Context().Value("decoder").(*schema.Decoder) + runtime := r.Context().Value("runtime").(*libpod.Runtime) + + query := struct { + Follow bool `schema:"follow"` + Stdout bool `schema:"stdout"` + Stderr bool `schema:"stderr"` + Since string `schema:"since"` + Until string `schema:"until"` + Timestamps bool `schema:"timestamps"` + Tail string `schema:"tail"` + }{ + Tail: "all", + } + if err := decoder.Decode(&query, r.URL.Query()); err != nil { + utils.Error(w, "Something went wrong.", http.StatusBadRequest, errors.Wrapf(err, "Failed to parse parameters for %s", r.URL.String())) + return + } + + if !(query.Stdout || query.Stderr) { + msg := fmt.Sprintf("%s: you must choose at least one stream", http.StatusText(http.StatusBadRequest)) + utils.Error(w, msg, http.StatusBadRequest, errors.Errorf("%s for %s", msg, r.URL.String())) + return + } + + name := utils.GetName(r) + ctnr, err := runtime.LookupContainer(name) + if err != nil { + utils.ContainerNotFound(w, name, err) + return + } + + var tail int64 = -1 + if query.Tail != "all" { + tail, err = strconv.ParseInt(query.Tail, 0, 64) + if err != nil { + utils.BadRequest(w, "tail", query.Tail, err) + return + } + } + + var since time.Time + if _, found := r.URL.Query()["since"]; found { + since, err = util.ParseInputTime(query.Since) + if err != nil { + utils.BadRequest(w, "since", query.Since, err) + return + } + } + + var until time.Time + if _, found := r.URL.Query()["until"]; found { + // FIXME: until != since but the logs backend does not yet support until. + since, err = util.ParseInputTime(query.Until) + if err != nil { + utils.BadRequest(w, "until", query.Until, err) + return + } + } + + options := &logs.LogOptions{ + Details: true, + Follow: query.Follow, + Since: since, + Tail: tail, + Timestamps: query.Timestamps, + } + + var wg sync.WaitGroup + options.WaitGroup = &wg + + logChannel := make(chan *logs.LogLine, tail+1) + if err := runtime.Log([]*libpod.Container{ctnr}, options, logChannel); err != nil { + utils.InternalServerError(w, errors.Wrapf(err, "Failed to obtain logs for Container '%s'", name)) + return + } + go func() { + wg.Wait() + close(logChannel) + }() + + w.WriteHeader(http.StatusOK) + + var frame strings.Builder + header := make([]byte, 8) + for ok := true; ok; ok = query.Follow { + for line := range logChannel { + if _, found := r.URL.Query()["until"]; found { + if line.Time.After(until) { + break + } + } + + // Reset buffer we're ready to loop again + frame.Reset() + switch line.Device { + case "stdout": + if !query.Stdout { + continue + } + header[0] = 1 + case "stderr": + if !query.Stderr { + continue + } + header[0] = 2 + default: + // Logging and moving on is the best we can do here. We may have already sent + // a Status and Content-Type to client therefore we can no longer report an error. + log.Infof("unknown Device type '%s' in log file from Container %s", line.Device, ctnr.ID()) + continue + } + + if query.Timestamps { + frame.WriteString(line.Time.Format(time.RFC3339)) + frame.WriteString(" ") + } + frame.WriteString(line.Msg) + + binary.BigEndian.PutUint32(header[4:], uint32(frame.Len())) + if _, err := w.Write(header[0:8]); err != nil { + log.Errorf("unable to write log output header: %q", err) + } + if _, err := io.WriteString(w, frame.String()); err != nil { + log.Errorf("unable to write frame string: %q", err) + } + if flusher, ok := w.(http.Flusher); ok { + flusher.Flush() + } + } + } +} diff --git a/pkg/api/handlers/compat/images_build.go b/pkg/api/handlers/compat/images_build.go index 913994f46..f967acf32 100644 --- a/pkg/api/handlers/compat/images_build.go +++ b/pkg/api/handlers/compat/images_build.go @@ -48,34 +48,34 @@ func BuildImage(w http.ResponseWriter, r *http.Request) { defer os.RemoveAll(anchorDir) query := struct { - Dockerfile string `schema:"dockerfile"` - Tag string `schema:"t"` - ExtraHosts string `schema:"extrahosts"` - Remote string `schema:"remote"` - Quiet bool `schema:"q"` - NoCache bool `schema:"nocache"` - CacheFrom string `schema:"cachefrom"` - Pull bool `schema:"pull"` - Rm bool `schema:"rm"` - ForceRm bool `schema:"forcerm"` - Memory int64 `schema:"memory"` - MemSwap int64 `schema:"memswap"` - CpuShares uint64 `schema:"cpushares"` //nolint - CpuSetCpus string `schema:"cpusetcpus"` //nolint - CpuPeriod uint64 `schema:"cpuperiod"` //nolint - CpuQuota int64 `schema:"cpuquota"` //nolint - BuildArgs string `schema:"buildargs"` - ShmSize int `schema:"shmsize"` - Squash bool `schema:"squash"` - Labels string `schema:"labels"` - NetworkMode string `schema:"networkmode"` - Platform string `schema:"platform"` - Target string `schema:"target"` - Outputs string `schema:"outputs"` - Registry string `schema:"registry"` + Dockerfile string `schema:"dockerfile"` + Tag []string `schema:"t"` + ExtraHosts string `schema:"extrahosts"` + Remote string `schema:"remote"` + Quiet bool `schema:"q"` + NoCache bool `schema:"nocache"` + CacheFrom string `schema:"cachefrom"` + Pull bool `schema:"pull"` + Rm bool `schema:"rm"` + ForceRm bool `schema:"forcerm"` + Memory int64 `schema:"memory"` + MemSwap int64 `schema:"memswap"` + CpuShares uint64 `schema:"cpushares"` //nolint + CpuSetCpus string `schema:"cpusetcpus"` //nolint + CpuPeriod uint64 `schema:"cpuperiod"` //nolint + CpuQuota int64 `schema:"cpuquota"` //nolint + BuildArgs string `schema:"buildargs"` + ShmSize int `schema:"shmsize"` + Squash bool `schema:"squash"` + Labels string `schema:"labels"` + NetworkMode string `schema:"networkmode"` + Platform string `schema:"platform"` + Target string `schema:"target"` + Outputs string `schema:"outputs"` + Registry string `schema:"registry"` }{ Dockerfile: "Dockerfile", - Tag: "", + Tag: []string{}, ExtraHosts: "", Remote: "", Quiet: false, @@ -107,20 +107,19 @@ func BuildImage(w http.ResponseWriter, r *http.Request) { } var ( - // Tag is the name with optional tag... - name = query.Tag - tag = "latest" + output string + additionalNames []string ) - if strings.Contains(query.Tag, ":") { - tokens := strings.SplitN(query.Tag, ":", 2) - name = tokens[0] - tag = tokens[1] + if len(query.Tag) > 0 { + output = query.Tag[0] + } + if len(query.Tag) > 1 { + additionalNames = query.Tag[1:] } if _, found := r.URL.Query()["target"]; found { - name = query.Target + output = query.Target } - var buildArgs = map[string]string{} if _, found := r.URL.Query()["buildargs"]; found { if err := json.Unmarshal([]byte(query.BuildArgs), &buildArgs); err != nil { @@ -168,8 +167,8 @@ func BuildImage(w http.ResponseWriter, r *http.Request) { TransientMounts: nil, Compression: archive.Gzip, Args: buildArgs, - Output: name, - AdditionalTags: []string{tag}, + Output: output, + AdditionalTags: additionalNames, Log: func(format string, args ...interface{}) { buildEvents = append(buildEvents, fmt.Sprintf(format, args...)) }, diff --git a/pkg/api/handlers/libpod/containers_create.go b/pkg/api/handlers/libpod/containers_create.go index 71f440bce..8fbff9be7 100644 --- a/pkg/api/handlers/libpod/containers_create.go +++ b/pkg/api/handlers/libpod/containers_create.go @@ -22,7 +22,8 @@ func CreateContainer(w http.ResponseWriter, r *http.Request) { utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "Decode()")) return } - if err := generate.CompleteSpec(r.Context(), runtime, &sg); err != nil { + warn, err := generate.CompleteSpec(r.Context(), runtime, &sg) + if err != nil { utils.InternalServerError(w, err) return } @@ -31,6 +32,6 @@ func CreateContainer(w http.ResponseWriter, r *http.Request) { utils.InternalServerError(w, err) return } - response := entities.ContainerCreateResponse{ID: ctr.ID()} + response := entities.ContainerCreateResponse{ID: ctr.ID(), Warnings: warn} utils.WriteJSON(w, http.StatusCreated, response) } diff --git a/pkg/api/server/register_images.go b/pkg/api/server/register_images.go index 83584d0f3..754cb1b75 100644 --- a/pkg/api/server/register_images.go +++ b/pkg/api/server/register_images.go @@ -410,7 +410,7 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error { // swagger:operation POST /build compat buildImage // --- // tags: - // - images + // - images (compat) // summary: Create image // description: Build an image from the given Dockerfile(s) // parameters: @@ -425,7 +425,7 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error { // name: t // type: string // default: latest - // description: A name and optional tag to apply to the image in the `name:tag` format. + // description: A name and optional tag to apply to the image in the `name:tag` format. If you omit the tag the default latest value is assumed. You can provide several t parameters. // - in: query // name: extrahosts // type: string @@ -1211,7 +1211,7 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error { // name: t // type: string // default: latest - // description: A name and optional tag to apply to the image in the `name:tag` format. + // description: A name and optional tag to apply to the image in the `name:tag` format. If you omit the tag the default latest value is assumed. You can provide several t parameters. // - in: query // name: extrahosts // type: string diff --git a/pkg/bindings/images/images.go b/pkg/bindings/images/images.go index 9cb6a0ac5..bc2d116f3 100644 --- a/pkg/bindings/images/images.go +++ b/pkg/bindings/images/images.go @@ -229,6 +229,9 @@ func Build(ctx context.Context, containerFiles []string, options entities.BuildO if t := options.Output; len(t) > 0 { params.Set("t", t) } + for _, tag := range options.AdditionalTags { + params.Add("t", tag) + } // TODO Remote, Quiet if options.NoCache { params.Set("nocache", "1") diff --git a/pkg/domain/entities/engine_container.go b/pkg/domain/entities/engine_container.go index 979df7581..837550a2e 100644 --- a/pkg/domain/entities/engine_container.go +++ b/pkg/domain/entities/engine_container.go @@ -24,7 +24,7 @@ type ContainerEngine interface { ContainerExists(ctx context.Context, nameOrID string) (*BoolReport, error) ContainerExport(ctx context.Context, nameOrID string, options ContainerExportOptions) error ContainerInit(ctx context.Context, namesOrIds []string, options ContainerInitOptions) ([]*ContainerInitReport, error) - ContainerInspect(ctx context.Context, namesOrIds []string, options InspectOptions) ([]*ContainerInspectReport, error) + ContainerInspect(ctx context.Context, namesOrIds []string, options InspectOptions) ([]*ContainerInspectReport, []error, error) ContainerKill(ctx context.Context, namesOrIds []string, options KillOptions) ([]*KillReport, error) ContainerList(ctx context.Context, options ContainerListOptions) ([]ListContainer, error) ContainerLogs(ctx context.Context, containers []string, options ContainerLogsOptions) error diff --git a/pkg/domain/entities/engine_image.go b/pkg/domain/entities/engine_image.go index 60fb20b6e..7ece24c60 100644 --- a/pkg/domain/entities/engine_image.go +++ b/pkg/domain/entities/engine_image.go @@ -13,7 +13,7 @@ type ImageEngine interface { Exists(ctx context.Context, nameOrID string) (*BoolReport, error) History(ctx context.Context, nameOrID string, opts ImageHistoryOptions) (*ImageHistoryReport, error) Import(ctx context.Context, opts ImageImportOptions) (*ImageImportReport, error) - Inspect(ctx context.Context, namesOrIDs []string, opts InspectOptions) ([]*ImageInspectReport, error) + Inspect(ctx context.Context, namesOrIDs []string, opts InspectOptions) ([]*ImageInspectReport, []error, error) List(ctx context.Context, opts ImageListOptions) ([]*ImageSummary, error) Load(ctx context.Context, opts ImageLoadOptions) (*ImageLoadReport, error) Prune(ctx context.Context, opts ImagePruneOptions) (*ImagePruneReport, error) diff --git a/pkg/domain/entities/volumes.go b/pkg/domain/entities/volumes.go index 7cf7d82a2..c99b39f2d 100644 --- a/pkg/domain/entities/volumes.go +++ b/pkg/domain/entities/volumes.go @@ -47,13 +47,13 @@ type VolumeConfigResponse struct { // It is presently not used. Options map[string]string `json:"Options"` // UID is the UID that the volume was created with. - UID int `json:"UID,omitempty"` + UID int `json:"UID"` // GID is the GID that the volume was created with. - GID int `json:"GID,omitempty"` + GID int `json:"GID"` // Anonymous indicates that the volume was created as an anonymous // volume for a specific container, and will be be removed when any // container using it is removed. - Anonymous bool `json:"Anonymous,omitempty"` + Anonymous bool `json:"Anonymous"` } type VolumeRmOptions struct { diff --git a/pkg/domain/infra/abi/containers.go b/pkg/domain/infra/abi/containers.go index d2c8aefdc..8e0ffc075 100644 --- a/pkg/domain/infra/abi/containers.go +++ b/pkg/domain/infra/abi/containers.go @@ -338,20 +338,51 @@ func (ic *ContainerEngine) ContainerRm(ctx context.Context, namesOrIds []string, return reports, nil } -func (ic *ContainerEngine) ContainerInspect(ctx context.Context, namesOrIds []string, options entities.InspectOptions) ([]*entities.ContainerInspectReport, error) { - ctrs, err := getContainersByContext(false, options.Latest, namesOrIds, ic.Libpod) - if err != nil { - return nil, err +func (ic *ContainerEngine) ContainerInspect(ctx context.Context, namesOrIds []string, options entities.InspectOptions) ([]*entities.ContainerInspectReport, []error, error) { + if options.Latest { + ctr, err := ic.Libpod.GetLatestContainer() + if err != nil { + if errors.Cause(err) == define.ErrNoSuchCtr { + return nil, []error{errors.Wrapf(err, "no containers to inspect")}, nil + } + return nil, nil, err + } + + inspect, err := ctr.Inspect(options.Size) + if err != nil { + return nil, nil, err + } + + return []*entities.ContainerInspectReport{ + { + InspectContainerData: inspect, + }, + }, nil, nil } - reports := make([]*entities.ContainerInspectReport, 0, len(ctrs)) - for _, c := range ctrs { - data, err := c.Inspect(options.Size) + var ( + reports = make([]*entities.ContainerInspectReport, 0, len(namesOrIds)) + errs = []error{} + ) + for _, name := range namesOrIds { + ctr, err := ic.Libpod.LookupContainer(name) if err != nil { - return nil, err + // ErrNoSuchCtr is non-fatal, other errors will be + // treated as fatal. + if errors.Cause(err) == define.ErrNoSuchCtr { + errs = append(errs, errors.Errorf("no such container %s", name)) + continue + } + return nil, nil, err + } + + inspect, err := ctr.Inspect(options.Size) + if err != nil { + return nil, nil, err } - reports = append(reports, &entities.ContainerInspectReport{InspectContainerData: data}) + + reports = append(reports, &entities.ContainerInspectReport{InspectContainerData: inspect}) } - return reports, nil + return reports, errs, nil } func (ic *ContainerEngine) ContainerTop(ctx context.Context, options entities.TopOptions) (*entities.StringSliceReport, error) { @@ -511,9 +542,14 @@ func (ic *ContainerEngine) ContainerRestore(ctx context.Context, namesOrIds []st } func (ic *ContainerEngine) ContainerCreate(ctx context.Context, s *specgen.SpecGenerator) (*entities.ContainerCreateReport, error) { - if err := generate.CompleteSpec(ctx, ic.Libpod, s); err != nil { + warn, err := generate.CompleteSpec(ctx, ic.Libpod, s) + if err != nil { return nil, err } + // Print warnings + for _, w := range warn { + fmt.Fprintf(os.Stderr, "%s\n", w) + } ctr, err := generate.MakeContainer(ctx, ic.Libpod, s) if err != nil { return nil, err @@ -773,9 +809,14 @@ func (ic *ContainerEngine) ContainerDiff(ctx context.Context, nameOrID string, o } func (ic *ContainerEngine) ContainerRun(ctx context.Context, opts entities.ContainerRunOptions) (*entities.ContainerRunReport, error) { - if err := generate.CompleteSpec(ctx, ic.Libpod, opts.Spec); err != nil { + warn, err := generate.CompleteSpec(ctx, ic.Libpod, opts.Spec) + if err != nil { return nil, err } + // Print warnings + for _, w := range warn { + fmt.Fprintf(os.Stderr, "%s\n", w) + } ctr, err := generate.MakeContainer(ctx, ic.Libpod, opts.Spec) if err != nil { return nil, err diff --git a/pkg/domain/infra/abi/images.go b/pkg/domain/infra/abi/images.go index e630d9bc8..0f9ddfec4 100644 --- a/pkg/domain/infra/abi/images.go +++ b/pkg/domain/infra/abi/images.go @@ -184,24 +184,28 @@ func (ir *ImageEngine) Pull(ctx context.Context, rawImage string, options entiti return &entities.ImagePullReport{Images: foundIDs}, nil } -func (ir *ImageEngine) Inspect(ctx context.Context, namesOrIDs []string, opts entities.InspectOptions) ([]*entities.ImageInspectReport, error) { +func (ir *ImageEngine) Inspect(ctx context.Context, namesOrIDs []string, opts entities.InspectOptions) ([]*entities.ImageInspectReport, []error, error) { reports := []*entities.ImageInspectReport{} + errs := []error{} for _, i := range namesOrIDs { img, err := ir.Libpod.ImageRuntime().NewFromLocal(i) if err != nil { - return nil, err + // This is probably a no such image, treat as nonfatal. + errs = append(errs, err) + continue } result, err := img.Inspect(ctx) if err != nil { - return nil, err + // This is more likely to be fatal. + return nil, nil, err } report := entities.ImageInspectReport{} if err := domainUtils.DeepCopy(&report, result); err != nil { - return nil, err + return nil, nil, err } reports = append(reports, &report) } - return reports, nil + return reports, errs, nil } func (ir *ImageEngine) Push(ctx context.Context, source string, destination string, options entities.ImagePushOptions) error { diff --git a/pkg/domain/infra/tunnel/containers.go b/pkg/domain/infra/tunnel/containers.go index 8f6f5c8b7..45fbc64f8 100644 --- a/pkg/domain/infra/tunnel/containers.go +++ b/pkg/domain/infra/tunnel/containers.go @@ -185,20 +185,27 @@ func (ic *ContainerEngine) ContainerPrune(ctx context.Context, options entities. return containers.Prune(ic.ClientCxt, options.Filters) } -func (ic *ContainerEngine) ContainerInspect(ctx context.Context, namesOrIds []string, options entities.InspectOptions) ([]*entities.ContainerInspectReport, error) { - ctrs, err := getContainersByContext(ic.ClientCxt, false, namesOrIds) - if err != nil { - return nil, err - } - reports := make([]*entities.ContainerInspectReport, 0, len(ctrs)) - for _, con := range ctrs { - data, err := containers.Inspect(ic.ClientCxt, con.ID, &options.Size) +func (ic *ContainerEngine) ContainerInspect(ctx context.Context, namesOrIds []string, options entities.InspectOptions) ([]*entities.ContainerInspectReport, []error, error) { + var ( + reports = make([]*entities.ContainerInspectReport, 0, len(namesOrIds)) + errs = []error{} + ) + for _, name := range namesOrIds { + inspect, err := containers.Inspect(ic.ClientCxt, name, &options.Size) if err != nil { - return nil, err + errModel, ok := err.(entities.ErrorModel) + if !ok { + return nil, nil, err + } + if errModel.ResponseCode == 404 { + errs = append(errs, errors.Errorf("no such container %q", name)) + continue + } + return nil, nil, err } - reports = append(reports, &entities.ContainerInspectReport{InspectContainerData: data}) + reports = append(reports, &entities.ContainerInspectReport{InspectContainerData: inspect}) } - return reports, nil + return reports, errs, nil } func (ic *ContainerEngine) ContainerTop(ctx context.Context, options entities.TopOptions) (*entities.StringSliceReport, error) { @@ -340,6 +347,9 @@ func (ic *ContainerEngine) ContainerCreate(ctx context.Context, s *specgen.SpecG if err != nil { return nil, err } + for _, w := range response.Warnings { + fmt.Fprintf(os.Stderr, "%s\n", w) + } return &entities.ContainerCreateReport{Id: response.ID}, nil } @@ -497,6 +507,9 @@ func (ic *ContainerEngine) ContainerRun(ctx context.Context, opts entities.Conta if err != nil { return nil, err } + for _, w := range con.Warnings { + fmt.Fprintf(os.Stderr, "%s\n", w) + } report := entities.ContainerRunReport{Id: con.ID} // Attach if !opts.Detach { diff --git a/pkg/domain/infra/tunnel/images.go b/pkg/domain/infra/tunnel/images.go index fc7ac0aa8..35189cb0a 100644 --- a/pkg/domain/infra/tunnel/images.go +++ b/pkg/domain/infra/tunnel/images.go @@ -1,10 +1,13 @@ package tunnel import ( + "archive/tar" + "bytes" "context" + "io" "io/ioutil" "os" - "path" + "path/filepath" "strings" "github.com/containers/common/pkg/config" @@ -16,6 +19,7 @@ import ( utils2 "github.com/containers/libpod/utils" "github.com/containers/storage/pkg/archive" "github.com/pkg/errors" + "github.com/sirupsen/logrus" ) func (ir *ImageEngine) Exists(_ context.Context, nameOrID string) (*entities.BoolReport, error) { @@ -157,16 +161,25 @@ func (ir *ImageEngine) Untag(ctx context.Context, nameOrID string, tags []string return nil } -func (ir *ImageEngine) Inspect(ctx context.Context, namesOrIDs []string, opts entities.InspectOptions) ([]*entities.ImageInspectReport, error) { +func (ir *ImageEngine) Inspect(ctx context.Context, namesOrIDs []string, opts entities.InspectOptions) ([]*entities.ImageInspectReport, []error, error) { reports := []*entities.ImageInspectReport{} + errs := []error{} for _, i := range namesOrIDs { r, err := images.GetImage(ir.ClientCxt, i, &opts.Size) if err != nil { - return nil, err + errModel, ok := err.(entities.ErrorModel) + if !ok { + return nil, nil, err + } + if errModel.ResponseCode == 404 { + errs = append(errs, errors.Wrapf(err, "unable to inspect %q", i)) + continue + } + return nil, nil, err } reports = append(reports, r) } - return reports, nil + return reports, errs, nil } func (ir *ImageEngine) Load(ctx context.Context, opts entities.ImageLoadOptions) (*entities.ImageLoadReport, error) { @@ -267,14 +280,27 @@ func (ir *ImageEngine) Config(_ context.Context) (*config.Config, error) { } func (ir *ImageEngine) Build(ctx context.Context, containerFiles []string, opts entities.BuildOptions) (*entities.BuildReport, error) { - if len(containerFiles) > 1 { - return nil, errors.New("something") + var tarReader io.Reader + tarfile, err := archive.Tar(opts.ContextDirectory, 0) + if err != nil { + return nil, err } - tarfile, err := archive.Tar(path.Base(containerFiles[0]), 0) + tarReader = tarfile + cwd, err := os.Getwd() if err != nil { return nil, err } - return images.Build(ir.ClientCxt, containerFiles, opts, tarfile) + if cwd != opts.ContextDirectory { + fn := func(h *tar.Header, r io.Reader) (data []byte, update bool, skip bool, err error) { + h.Name = filepath.Join(filepath.Base(opts.ContextDirectory), h.Name) + return nil, false, false, nil + } + tarReader, err = transformArchive(tarfile, false, fn) + if err != nil { + return nil, err + } + } + return images.Build(ir.ClientCxt, containerFiles, opts, tarReader) } func (ir *ImageEngine) Tree(ctx context.Context, nameOrID string, opts entities.ImageTreeOptions) (*entities.ImageTreeReport, error) { @@ -288,3 +314,65 @@ func (ir *ImageEngine) Shutdown(_ context.Context) { func (ir *ImageEngine) Sign(ctx context.Context, names []string, options entities.SignOptions) (*entities.SignReport, error) { return nil, errors.New("not implemented yet") } + +// Sourced from openshift image builder + +// TransformFileFunc is given a chance to transform an arbitrary input file. +type TransformFileFunc func(h *tar.Header, r io.Reader) (data []byte, update bool, skip bool, err error) + +// filterArchive transforms the provided input archive to a new archive, +// giving the fn a chance to transform arbitrary files. +func filterArchive(r io.Reader, w io.Writer, fn TransformFileFunc) error { + tr := tar.NewReader(r) + tw := tar.NewWriter(w) + + var body io.Reader = tr + + for { + h, err := tr.Next() + if err == io.EOF { + return tw.Close() + } + if err != nil { + return err + } + + name := h.Name + data, ok, skip, err := fn(h, tr) + logrus.Debugf("Transform %q -> %q: data=%t ok=%t skip=%t err=%v", name, h.Name, data != nil, ok, skip, err) + if err != nil { + return err + } + if skip { + continue + } + if ok { + h.Size = int64(len(data)) + body = bytes.NewBuffer(data) + } + if err := tw.WriteHeader(h); err != nil { + return err + } + if _, err := io.Copy(tw, body); err != nil { + return err + } + } +} + +func transformArchive(r io.Reader, compressed bool, fn TransformFileFunc) (io.Reader, error) { + var cwe error + pr, pw := io.Pipe() + go func() { + if compressed { + in, err := archive.DecompressStream(r) + if err != nil { + cwe = pw.CloseWithError(err) + return + } + r = in + } + err := filterArchive(r, pw, fn) + cwe = pw.CloseWithError(err) + }() + return pr, cwe +} diff --git a/pkg/hooks/0.1.0/hook.go b/pkg/hooks/0.1.0/hook.go index 88a387647..185cc90d1 100644 --- a/pkg/hooks/0.1.0/hook.go +++ b/pkg/hooks/0.1.0/hook.go @@ -6,7 +6,6 @@ import ( "errors" "strings" - "github.com/containers/libpod/pkg/hooks" current "github.com/containers/libpod/pkg/hooks/1.0.0" rspec "github.com/opencontainers/runtime-spec/specs-go" ) @@ -32,8 +31,9 @@ type Hook struct { HasBindMounts *bool `json:"hasbindmounts,omitempty"` } -func read(content []byte) (hook *current.Hook, err error) { +func Read(content []byte) (hook *current.Hook, err error) { var raw Hook + if err = json.Unmarshal(content, &raw); err != nil { return nil, err } @@ -86,8 +86,3 @@ func read(content []byte) (hook *current.Hook, err error) { return hook, nil } - -func init() { - hooks.Readers[""] = read - hooks.Readers[Version] = read -} diff --git a/pkg/hooks/0.1.0/hook_test.go b/pkg/hooks/0.1.0/hook_test.go index 66774075e..11881ca0b 100644 --- a/pkg/hooks/0.1.0/hook_test.go +++ b/pkg/hooks/0.1.0/hook_test.go @@ -9,7 +9,7 @@ import ( ) func TestGood(t *testing.T) { - hook, err := read([]byte("{\"hook\": \"/a/b/c\", \"stages\": [\"prestart\"], \"cmds\": [\"sh\"]}")) + hook, err := Read([]byte("{\"hook\": \"/a/b/c\", \"stages\": [\"prestart\"], \"cmds\": [\"sh\"]}")) if err != nil { t.Fatal(err) } @@ -27,7 +27,7 @@ func TestGood(t *testing.T) { } func TestInvalidJSON(t *testing.T) { - _, err := read([]byte("{")) + _, err := Read([]byte("{")) if err == nil { t.Fatal("unexpected success") } @@ -35,7 +35,7 @@ func TestInvalidJSON(t *testing.T) { } func TestArguments(t *testing.T) { - hook, err := read([]byte("{\"hook\": \"/a/b/c\", \"arguments\": [\"d\", \"e\"], \"stages\": [\"prestart\"], \"cmds\": [\"sh\"]}")) + hook, err := Read([]byte("{\"hook\": \"/a/b/c\", \"arguments\": [\"d\", \"e\"], \"stages\": [\"prestart\"], \"cmds\": [\"sh\"]}")) if err != nil { t.Fatal(err) } @@ -54,7 +54,7 @@ func TestArguments(t *testing.T) { } func TestEmptyObject(t *testing.T) { - _, err := read([]byte("{}")) + _, err := Read([]byte("{}")) if err == nil { t.Fatal("unexpected success") } @@ -62,7 +62,7 @@ func TestEmptyObject(t *testing.T) { } func TestNoStages(t *testing.T) { - _, err := read([]byte("{\"hook\": \"/a/b/c\"}")) + _, err := Read([]byte("{\"hook\": \"/a/b/c\"}")) if err == nil { t.Fatal("unexpected success") } @@ -70,7 +70,7 @@ func TestNoStages(t *testing.T) { } func TestStage(t *testing.T) { - hook, err := read([]byte("{\"hook\": \"/a/b/c\", \"stage\": [\"prestart\"]}")) + hook, err := Read([]byte("{\"hook\": \"/a/b/c\", \"stage\": [\"prestart\"]}")) if err != nil { t.Fatal(err) } @@ -85,7 +85,7 @@ func TestStage(t *testing.T) { } func TestStagesAndStage(t *testing.T) { - _, err := read([]byte("{\"hook\": \"/a/b/c\", \"stages\": [\"prestart\"], \"stage\": [\"prestart\"]}")) + _, err := Read([]byte("{\"hook\": \"/a/b/c\", \"stages\": [\"prestart\"], \"stage\": [\"prestart\"]}")) if err == nil { t.Fatal("unexpected success") } @@ -93,7 +93,7 @@ func TestStagesAndStage(t *testing.T) { } func TestCmd(t *testing.T) { - hook, err := read([]byte("{\"hook\": \"/a/b/c\", \"stage\": [\"prestart\"], \"cmd\": [\"sh\"]}")) + hook, err := Read([]byte("{\"hook\": \"/a/b/c\", \"stage\": [\"prestart\"], \"cmd\": [\"sh\"]}")) if err != nil { t.Fatal(err) } @@ -111,7 +111,7 @@ func TestCmd(t *testing.T) { } func TestCmdsAndCmd(t *testing.T) { - _, err := read([]byte("{\"hook\": \"/a/b/c\", \"stages\": [\"prestart\"], \"cmds\": [\"sh\"], \"cmd\": [\"true\"]}")) + _, err := Read([]byte("{\"hook\": \"/a/b/c\", \"stages\": [\"prestart\"], \"cmds\": [\"sh\"], \"cmd\": [\"true\"]}")) if err == nil { t.Fatal("unexpected success") } @@ -119,7 +119,7 @@ func TestCmdsAndCmd(t *testing.T) { } func TestAnnotations(t *testing.T) { - hook, err := read([]byte("{\"hook\": \"/a/b/c\", \"stage\": [\"prestart\"], \"annotations\": [\"a\", \"b\"]}")) + hook, err := Read([]byte("{\"hook\": \"/a/b/c\", \"stage\": [\"prestart\"], \"annotations\": [\"a\", \"b\"]}")) if err != nil { t.Fatal(err) } @@ -137,7 +137,7 @@ func TestAnnotations(t *testing.T) { } func TestAnnotation(t *testing.T) { - hook, err := read([]byte("{\"hook\": \"/a/b/c\", \"stage\": [\"prestart\"], \"annotation\": [\"a\", \"b\"]}")) + hook, err := Read([]byte("{\"hook\": \"/a/b/c\", \"stage\": [\"prestart\"], \"annotation\": [\"a\", \"b\"]}")) if err != nil { t.Fatal(err) } @@ -155,7 +155,7 @@ func TestAnnotation(t *testing.T) { } func TestAnnotationsAndAnnotation(t *testing.T) { - _, err := read([]byte("{\"hook\": \"/a/b/c\", \"stages\": [\"prestart\"], \"annotations\": [\"a\"], \"annotation\": [\"b\"]}")) + _, err := Read([]byte("{\"hook\": \"/a/b/c\", \"stages\": [\"prestart\"], \"annotations\": [\"a\"], \"annotation\": [\"b\"]}")) if err == nil { t.Fatal("unexpected success") } @@ -163,7 +163,7 @@ func TestAnnotationsAndAnnotation(t *testing.T) { } func TestHasBindMounts(t *testing.T) { - hook, err := read([]byte("{\"hook\": \"/a/b/c\", \"stage\": [\"prestart\"], \"hasbindmounts\": true}")) + hook, err := Read([]byte("{\"hook\": \"/a/b/c\", \"stage\": [\"prestart\"], \"hasbindmounts\": true}")) if err != nil { t.Fatal(err) } diff --git a/pkg/hooks/read.go b/pkg/hooks/read.go index 560ff1899..e20ae9bee 100644 --- a/pkg/hooks/read.go +++ b/pkg/hooks/read.go @@ -3,12 +3,12 @@ package hooks import ( "encoding/json" - "fmt" "io/ioutil" "os" "path/filepath" "strings" + old "github.com/containers/libpod/pkg/hooks/0.1.0" current "github.com/containers/libpod/pkg/hooks/1.0.0" "github.com/pkg/errors" "github.com/sirupsen/logrus" @@ -49,7 +49,7 @@ func read(content []byte) (hook *current.Hook, err error) { } reader, ok := Readers[ver.Version] if !ok { - return nil, fmt.Errorf("unrecognized hook version: %q", ver.Version) + return nil, errors.Errorf("unrecognized hook version: %q", ver.Version) } hook, err = reader(content) @@ -95,4 +95,6 @@ func ReadDir(path string, extensionStages []string, hooks map[string]*current.Ho func init() { Readers[current.Version] = current.Read + Readers[old.Version] = old.Read + Readers[""] = old.Read } diff --git a/pkg/specgen/generate/container.go b/pkg/specgen/generate/container.go index 3d70571d5..df27f225b 100644 --- a/pkg/specgen/generate/container.go +++ b/pkg/specgen/generate/container.go @@ -5,6 +5,7 @@ import ( "github.com/containers/image/v5/manifest" "github.com/containers/libpod/libpod" + "github.com/containers/libpod/libpod/image" ann "github.com/containers/libpod/pkg/annotations" envLib "github.com/containers/libpod/pkg/env" "github.com/containers/libpod/pkg/signal" @@ -13,91 +14,103 @@ import ( "golang.org/x/sys/unix" ) -func CompleteSpec(ctx context.Context, r *libpod.Runtime, s *specgen.SpecGenerator) error { - // If a rootfs is used, then there is no image data - if s.ContainerStorageConfig.Rootfs != "" { - return nil - } - - newImage, err := r.ImageRuntime().NewFromLocal(s.Image) - if err != nil { - return err - } - - _, mediaType, err := newImage.Manifest(ctx) - if err != nil { - return err - } - - if s.HealthConfig == nil && mediaType == manifest.DockerV2Schema2MediaType { - s.HealthConfig, err = newImage.GetHealthCheck(ctx) +// Fill any missing parts of the spec generator (e.g. from the image). +// Returns a set of warnings or any fatal error that occurred. +func CompleteSpec(ctx context.Context, r *libpod.Runtime, s *specgen.SpecGenerator) ([]string, error) { + var ( + newImage *image.Image + err error + ) + + // Only add image configuration if we have an image + if s.Image != "" { + newImage, err = r.ImageRuntime().NewFromLocal(s.Image) if err != nil { - return err + return nil, err } - } - // Image stop signal - if s.StopSignal == nil { - stopSignal, err := newImage.StopSignal(ctx) + _, mediaType, err := newImage.Manifest(ctx) if err != nil { - return err + return nil, err } - if stopSignal != "" { - sig, err := signal.ParseSignalNameOrNumber(stopSignal) + + if s.HealthConfig == nil && mediaType == manifest.DockerV2Schema2MediaType { + s.HealthConfig, err = newImage.GetHealthCheck(ctx) if err != nil { - return err + return nil, err + } + } + + // Image stop signal + if s.StopSignal == nil { + stopSignal, err := newImage.StopSignal(ctx) + if err != nil { + return nil, err + } + if stopSignal != "" { + sig, err := signal.ParseSignalNameOrNumber(stopSignal) + if err != nil { + return nil, err + } + s.StopSignal = &sig } - s.StopSignal = &sig } } rtc, err := r.GetConfig() if err != nil { - return err + return nil, err } // Get Default Environment defaultEnvs, err := envLib.ParseSlice(rtc.Containers.Env) if err != nil { - return errors.Wrap(err, "Env fields in containers.conf failed to parse") + return nil, errors.Wrap(err, "Env fields in containers.conf failed to parse") } - // Image envs from the image if they don't exist - // already, overriding the default environments - imageEnvs, err := newImage.Env(ctx) - if err != nil { - return err - } + var envs map[string]string - envs, err := envLib.ParseSlice(imageEnvs) - if err != nil { - return errors.Wrap(err, "Env fields from image failed to parse") - } - s.Env = envLib.Join(envLib.Join(defaultEnvs, envs), s.Env) + if newImage != nil { + // Image envs from the image if they don't exist + // already, overriding the default environments + imageEnvs, err := newImage.Env(ctx) + if err != nil { + return nil, err + } - labels, err := newImage.Labels(ctx) - if err != nil { - return err + envs, err = envLib.ParseSlice(imageEnvs) + if err != nil { + return nil, errors.Wrap(err, "Env fields from image failed to parse") + } } - // labels from the image that dont exist already - if len(labels) > 0 && s.Labels == nil { - s.Labels = make(map[string]string) - } - for k, v := range labels { - if _, exists := s.Labels[k]; !exists { - s.Labels[k] = v + s.Env = envLib.Join(envLib.Join(defaultEnvs, envs), s.Env) + + // Labels and Annotations + annotations := make(map[string]string) + if newImage != nil { + labels, err := newImage.Labels(ctx) + if err != nil { + return nil, err } - } - // annotations + // labels from the image that dont exist already + if len(labels) > 0 && s.Labels == nil { + s.Labels = make(map[string]string) + } + for k, v := range labels { + if _, exists := s.Labels[k]; !exists { + s.Labels[k] = v + } + } - // Add annotations from the image - annotations, err := newImage.Annotations(ctx) - if err != nil { - return err - } - for k, v := range annotations { - annotations[k] = v + // Add annotations from the image + imgAnnotations, err := newImage.Annotations(ctx) + if err != nil { + return nil, err + } + for k, v := range imgAnnotations { + annotations[k] = v + } } // in the event this container is in a pod, and the pod has an infra container @@ -121,40 +134,42 @@ func CompleteSpec(ctx context.Context, r *libpod.Runtime, s *specgen.SpecGenerat s.Annotations = annotations // workdir - workingDir, err := newImage.WorkingDir(ctx) - if err != nil { - return err - } - if len(s.WorkDir) < 1 && len(workingDir) > 1 { - s.WorkDir = workingDir + if newImage != nil { + workingDir, err := newImage.WorkingDir(ctx) + if err != nil { + return nil, err + } + if len(s.WorkDir) < 1 && len(workingDir) > 1 { + s.WorkDir = workingDir + } } if len(s.SeccompProfilePath) < 1 { p, err := libpod.DefaultSeccompPath() if err != nil { - return err + return nil, err } s.SeccompProfilePath = p } - if len(s.User) == 0 { + if len(s.User) == 0 && newImage != nil { s.User, err = newImage.User(ctx) if err != nil { - return err + return nil, err } } if err := finishThrottleDevices(s); err != nil { - return err + return nil, err } // Unless already set via the CLI, check if we need to disable process // labels or set the defaults. if len(s.SelinuxOpts) == 0 { if err := setLabelOpts(s, r, s.PidNS, s.IpcNS); err != nil { - return err + return nil, err } } - return nil + return verifyContainerResources(s) } // finishThrottleDevices takes the temporary representation of the throttle diff --git a/pkg/specgen/generate/container_create.go b/pkg/specgen/generate/container_create.go index 33075b543..2f7100e7e 100644 --- a/pkg/specgen/generate/container_create.go +++ b/pkg/specgen/generate/container_create.go @@ -7,7 +7,6 @@ import ( "github.com/containers/common/pkg/config" "github.com/containers/libpod/libpod" - "github.com/containers/libpod/libpod/define" "github.com/containers/libpod/libpod/image" "github.com/containers/libpod/pkg/specgen" "github.com/containers/libpod/pkg/util" @@ -16,7 +15,9 @@ import ( "github.com/sirupsen/logrus" ) -// MakeContainer creates a container based on the SpecGenerator +// MakeContainer creates a container based on the SpecGenerator. +// Returns the created, container and any warnings resulting from creating the +// container, or an error. func MakeContainer(ctx context.Context, rt *libpod.Runtime, s *specgen.SpecGenerator) (*libpod.Container, error) { rtc, err := rt.GetConfig() if err != nil { @@ -249,9 +250,6 @@ func createContainerOptions(ctx context.Context, rt *libpod.Runtime, s *specgen. // Default used if not overridden on command line if s.RestartPolicy != "" { - if s.RestartPolicy == "unless-stopped" { - return nil, errors.Wrapf(define.ErrInvalidArg, "the unless-stopped restart policy is not supported") - } if s.RestartRetries != nil { options = append(options, libpod.WithRestartRetries(*s.RestartRetries)) } diff --git a/pkg/specgen/generate/validate.go b/pkg/specgen/generate/validate.go new file mode 100644 index 000000000..bb3ca9907 --- /dev/null +++ b/pkg/specgen/generate/validate.go @@ -0,0 +1,159 @@ +package generate + +import ( + "github.com/containers/common/pkg/sysinfo" + "github.com/containers/libpod/pkg/cgroups" + "github.com/containers/libpod/pkg/specgen" + "github.com/pkg/errors" +) + +// Verify resource limits are sanely set, removing any limits that are not +// possible with the current cgroups config. +func verifyContainerResources(s *specgen.SpecGenerator) ([]string, error) { + warnings := []string{} + + cgroup2, err := cgroups.IsCgroup2UnifiedMode() + if err != nil || cgroup2 { + return warnings, err + } + + sysInfo := sysinfo.New(true) + + if s.ResourceLimits == nil { + return warnings, nil + } + + // Memory checks + if s.ResourceLimits.Memory != nil { + memory := s.ResourceLimits.Memory + if memory.Limit != nil && !sysInfo.MemoryLimit { + warnings = append(warnings, "Your kernel does not support memory limit capabilities or the cgroup is not mounted. Limitation discarded.") + memory.Limit = nil + memory.Swap = nil + } + if memory.Limit != nil && memory.Swap != nil && !sysInfo.SwapLimit { + warnings = append(warnings, "Your kernel does not support swap limit capabilities,or the cgroup is not mounted. Memory limited without swap.") + memory.Swap = nil + } + if memory.Limit != nil && memory.Swap != nil && *memory.Swap < *memory.Limit { + return warnings, errors.New("minimum memoryswap limit should be larger than memory limit, see usage") + } + if memory.Limit == nil && memory.Swap != nil { + return warnings, errors.New("you should always set a memory limit when using a memoryswap limit, see usage") + } + if memory.Swappiness != nil { + if !sysInfo.MemorySwappiness { + warnings = append(warnings, "Your kernel does not support memory swappiness capabilities, or the cgroup is not mounted. Memory swappiness discarded.") + memory.Swappiness = nil + } else { + if *memory.Swappiness < 0 || *memory.Swappiness > 100 { + return warnings, errors.Errorf("invalid value: %v, valid memory swappiness range is 0-100", *memory.Swappiness) + } + } + } + if memory.Reservation != nil && !sysInfo.MemoryReservation { + warnings = append(warnings, "Your kernel does not support memory soft limit capabilities or the cgroup is not mounted. Limitation discarded.") + memory.Reservation = nil + } + if memory.Limit != nil && memory.Reservation != nil && *memory.Limit < *memory.Reservation { + return warnings, errors.New("minimum memory limit cannot be less than memory reservation limit, see usage") + } + if memory.Kernel != nil && !sysInfo.KernelMemory { + warnings = append(warnings, "Your kernel does not support kernel memory limit capabilities or the cgroup is not mounted. Limitation discarded.") + memory.Kernel = nil + } + if memory.DisableOOMKiller != nil && *memory.DisableOOMKiller && !sysInfo.OomKillDisable { + warnings = append(warnings, "Your kernel does not support OomKillDisable. OomKillDisable discarded.") + memory.DisableOOMKiller = nil + } + } + + // Pids checks + if s.ResourceLimits.Pids != nil { + pids := s.ResourceLimits.Pids + // TODO: Should this be 0, or checking that ResourceLimits.Pids + // is set at all? + if pids.Limit > 0 && !sysInfo.PidsLimit { + warnings = append(warnings, "Your kernel does not support pids limit capabilities or the cgroup is not mounted. PIDs limit discarded.") + s.ResourceLimits.Pids = nil + } + } + + // CPU Checks + if s.ResourceLimits.CPU != nil { + cpu := s.ResourceLimits.CPU + if cpu.Shares != nil && !sysInfo.CPUShares { + warnings = append(warnings, "Your kernel does not support CPU shares or the cgroup is not mounted. Shares discarded.") + cpu.Shares = nil + } + if cpu.Period != nil && !sysInfo.CPUCfsPeriod { + warnings = append(warnings, "Your kernel does not support CPU cfs period or the cgroup is not mounted. Period discarded.") + cpu.Period = nil + } + if cpu.Period != nil && (*cpu.Period < 1000 || *cpu.Period > 1000000) { + return warnings, errors.New("CPU cfs period cannot be less than 1ms (i.e. 1000) or larger than 1s (i.e. 1000000)") + } + if cpu.Quota != nil && !sysInfo.CPUCfsQuota { + warnings = append(warnings, "Your kernel does not support CPU cfs quota or the cgroup is not mounted. Quota discarded.") + cpu.Quota = nil + } + if cpu.Quota != nil && *cpu.Quota < 1000 { + return warnings, errors.New("CPU cfs quota cannot be less than 1ms (i.e. 1000)") + } + if (cpu.Cpus != "" || cpu.Mems != "") && !sysInfo.Cpuset { + warnings = append(warnings, "Your kernel does not support cpuset or the cgroup is not mounted. CPUset discarded.") + cpu.Cpus = "" + cpu.Mems = "" + } + + cpusAvailable, err := sysInfo.IsCpusetCpusAvailable(cpu.Cpus) + if err != nil { + return warnings, errors.Errorf("invalid value %s for cpuset cpus", cpu.Cpus) + } + if !cpusAvailable { + return warnings, errors.Errorf("requested CPUs are not available - requested %s, available: %s", cpu.Cpus, sysInfo.Cpus) + } + + memsAvailable, err := sysInfo.IsCpusetMemsAvailable(cpu.Mems) + if err != nil { + return warnings, errors.Errorf("invalid value %s for cpuset mems", cpu.Mems) + } + if !memsAvailable { + return warnings, errors.Errorf("requested memory nodes are not available - requested %s, available: %s", cpu.Mems, sysInfo.Mems) + } + } + + // Blkio checks + if s.ResourceLimits.BlockIO != nil { + blkio := s.ResourceLimits.BlockIO + if blkio.Weight != nil && !sysInfo.BlkioWeight { + warnings = append(warnings, "Your kernel does not support Block I/O weight or the cgroup is not mounted. Weight discarded.") + blkio.Weight = nil + } + if blkio.Weight != nil && (*blkio.Weight > 1000 || *blkio.Weight < 10) { + return warnings, errors.New("range of blkio weight is from 10 to 1000") + } + if len(blkio.WeightDevice) > 0 && !sysInfo.BlkioWeightDevice { + warnings = append(warnings, "Your kernel does not support Block I/O weight_device or the cgroup is not mounted. Weight-device discarded.") + blkio.WeightDevice = nil + } + if len(blkio.ThrottleReadBpsDevice) > 0 && !sysInfo.BlkioReadBpsDevice { + warnings = append(warnings, "Your kernel does not support BPS Block I/O read limit or the cgroup is not mounted. Block I/O BPS read limit discarded") + blkio.ThrottleReadBpsDevice = nil + } + if len(blkio.ThrottleWriteBpsDevice) > 0 && !sysInfo.BlkioWriteBpsDevice { + warnings = append(warnings, "Your kernel does not support BPS Block I/O write limit or the cgroup is not mounted. Block I/O BPS write limit discarded.") + blkio.ThrottleWriteBpsDevice = nil + } + if len(blkio.ThrottleReadIOPSDevice) > 0 && !sysInfo.BlkioReadIOpsDevice { + warnings = append(warnings, "Your kernel does not support IOPS Block read limit or the cgroup is not mounted. Block I/O IOPS read limit discarded.") + blkio.ThrottleReadIOPSDevice = nil + } + if len(blkio.ThrottleWriteIOPSDevice) > 0 && !sysInfo.BlkioWriteIOpsDevice { + warnings = append(warnings, "Your kernel does not support IOPS Block I/O write limit or the cgroup is not mounted. Block I/O IOPS write limit discarded.") + blkio.ThrottleWriteIOPSDevice = nil + } + } + + return warnings, nil +} diff --git a/pkg/systemd/generate/containers.go b/pkg/systemd/generate/containers.go index 4180022cb..16ff0b821 100644 --- a/pkg/systemd/generate/containers.go +++ b/pkg/systemd/generate/containers.go @@ -83,9 +83,7 @@ ExecStartPre={{.ExecStartPre}} {{- end}} ExecStart={{.ExecStart}} ExecStop={{.ExecStop}} -{{- if .ExecStopPost}} ExecStopPost={{.ExecStopPost}} -{{- end}} PIDFile={{.PIDFile}} KillMode=none Type=forking @@ -170,6 +168,7 @@ func executeContainerTemplate(info *containerInfo, options entities.GenerateSyst info.EnvVariable = EnvVariable info.ExecStart = "{{.Executable}} start {{.ContainerNameOrID}}" info.ExecStop = "{{.Executable}} stop {{if (ge .StopTimeout 0)}}-t {{.StopTimeout}}{{end}} {{.ContainerNameOrID}}" + info.ExecStopPost = "{{.Executable}} stop {{if (ge .StopTimeout 0)}}-t {{.StopTimeout}}{{end}} {{.ContainerNameOrID}}" // Assemble the ExecStart command when creating a new container. // diff --git a/pkg/systemd/generate/containers_test.go b/pkg/systemd/generate/containers_test.go index 8d3ea1ca0..5f35c31f5 100644 --- a/pkg/systemd/generate/containers_test.go +++ b/pkg/systemd/generate/containers_test.go @@ -50,6 +50,7 @@ Environment=PODMAN_SYSTEMD_UNIT=%n Restart=always ExecStart=/usr/bin/podman start 639c53578af4d84b8800b4635fa4e680ee80fd67e0e6a2d4eea48d1e3230f401 ExecStop=/usr/bin/podman stop -t 10 639c53578af4d84b8800b4635fa4e680ee80fd67e0e6a2d4eea48d1e3230f401 +ExecStopPost=/usr/bin/podman stop -t 10 639c53578af4d84b8800b4635fa4e680ee80fd67e0e6a2d4eea48d1e3230f401 PIDFile=/var/run/containers/storage/overlay-containers/639c53578af4d84b8800b4635fa4e680ee80fd67e0e6a2d4eea48d1e3230f401/userdata/conmon.pid KillMode=none Type=forking @@ -71,6 +72,7 @@ Environment=PODMAN_SYSTEMD_UNIT=%n Restart=always ExecStart=/usr/bin/podman start foobar ExecStop=/usr/bin/podman stop -t 10 foobar +ExecStopPost=/usr/bin/podman stop -t 10 foobar PIDFile=/var/run/containers/storage/overlay-containers/639c53578af4d84b8800b4635fa4e680ee80fd67e0e6a2d4eea48d1e3230f401/userdata/conmon.pid KillMode=none Type=forking @@ -96,6 +98,7 @@ Environment=PODMAN_SYSTEMD_UNIT=%n Restart=always ExecStart=/usr/bin/podman start foobar ExecStop=/usr/bin/podman stop -t 10 foobar +ExecStopPost=/usr/bin/podman stop -t 10 foobar PIDFile=/var/run/containers/storage/overlay-containers/639c53578af4d84b8800b4635fa4e680ee80fd67e0e6a2d4eea48d1e3230f401/userdata/conmon.pid KillMode=none Type=forking diff --git a/pkg/systemd/generate/pods.go b/pkg/systemd/generate/pods.go index 367b8381f..1bd0c7bce 100644 --- a/pkg/systemd/generate/pods.go +++ b/pkg/systemd/generate/pods.go @@ -82,9 +82,7 @@ ExecStartPre={{.ExecStartPre2}} {{- end}} ExecStart={{.ExecStart}} ExecStop={{.ExecStop}} -{{- if .ExecStopPost}} ExecStopPost={{.ExecStopPost}} -{{- end}} PIDFile={{.PIDFile}} KillMode=none Type=forking @@ -236,6 +234,7 @@ func executePodTemplate(info *podInfo, options entities.GenerateSystemdOptions) info.EnvVariable = EnvVariable info.ExecStart = "{{.Executable}} start {{.InfraNameOrID}}" info.ExecStop = "{{.Executable}} stop {{if (ge .StopTimeout 0)}}-t {{.StopTimeout}}{{end}} {{.InfraNameOrID}}" + info.ExecStopPost = "{{.Executable}} stop {{if (ge .StopTimeout 0)}}-t {{.StopTimeout}}{{end}} {{.InfraNameOrID}}" // Assemble the ExecStart command when creating a new pod. // diff --git a/pkg/systemd/generate/pods_test.go b/pkg/systemd/generate/pods_test.go index f7ce33a30..e12222317 100644 --- a/pkg/systemd/generate/pods_test.go +++ b/pkg/systemd/generate/pods_test.go @@ -52,6 +52,7 @@ 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 +ExecStopPost=/usr/bin/podman stop -t 10 jadda-jadda-infra PIDFile=/var/run/containers/storage/overlay-containers/639c53578af4d84b8800b4635fa4e680ee80fd67e0e6a2d4eea48d1e3230f401/userdata/conmon.pid KillMode=none Type=forking |