diff options
Diffstat (limited to 'pkg')
-rw-r--r-- | pkg/api/handlers/libpod/containers_create.go | 5 | ||||
-rw-r--r-- | pkg/api/server/register_ping.go | 1 | ||||
-rw-r--r-- | pkg/domain/entities/engine_container.go | 2 | ||||
-rw-r--r-- | pkg/domain/entities/engine_image.go | 2 | ||||
-rw-r--r-- | pkg/domain/infra/abi/containers.go | 65 | ||||
-rw-r--r-- | pkg/domain/infra/abi/images.go | 14 | ||||
-rw-r--r-- | pkg/domain/infra/tunnel/containers.go | 37 | ||||
-rw-r--r-- | pkg/domain/infra/tunnel/images.go | 17 | ||||
-rw-r--r-- | pkg/hooks/0.1.0/hook.go | 9 | ||||
-rw-r--r-- | pkg/hooks/0.1.0/hook_test.go | 26 | ||||
-rw-r--r-- | pkg/hooks/read.go | 6 | ||||
-rw-r--r-- | pkg/specgen/generate/container.go | 159 | ||||
-rw-r--r-- | pkg/specgen/generate/container_create.go | 4 | ||||
-rw-r--r-- | pkg/specgen/generate/oci.go | 2 | ||||
-rw-r--r-- | pkg/specgen/generate/storage.go | 4 | ||||
-rw-r--r-- | pkg/specgen/generate/validate.go | 159 | ||||
-rw-r--r-- | pkg/systemd/generate/containers.go | 3 | ||||
-rw-r--r-- | pkg/systemd/generate/containers_test.go | 3 | ||||
-rw-r--r-- | pkg/systemd/generate/pods.go | 3 | ||||
-rw-r--r-- | pkg/systemd/generate/pods_test.go | 1 |
20 files changed, 383 insertions, 139 deletions
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_ping.go b/pkg/api/server/register_ping.go index 8a1cda3d4..bf7763029 100644 --- a/pkg/api/server/register_ping.go +++ b/pkg/api/server/register_ping.go @@ -19,6 +19,7 @@ func (s *APIServer) registerPingHandlers(r *mux.Router) error { // Return protocol information in response headers. // `HEAD /libpod/_ping` is also supported. // `/_ping` is available for compatibility with other engines. + // The '_ping' endpoints are not versioned. // tags: // - system (compat) // - system 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/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 955149fcf..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 } @@ -370,7 +380,7 @@ func (ic *ContainerEngine) ContainerLogs(_ context.Context, nameOrIDs []string, case <-ctx.Done(): return err case line := <-outCh: - _, _ = io.WriteString(options.Writer, line) + _, _ = io.WriteString(options.Writer, line+"\n") } } } @@ -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 ec2c53c4f..9ddc5f1a9 100644 --- a/pkg/domain/infra/tunnel/images.go +++ b/pkg/domain/infra/tunnel/images.go @@ -39,7 +39,7 @@ func (ir *ImageEngine) List(ctx context.Context, opts entities.ImageListOptions) return nil, err } - is := make([]*entities.ImageSummary, 0, len(images)) + is := make([]*entities.ImageSummary, len(images)) for i, img := range images { hold := entities.ImageSummary{} if err := utils.DeepCopy(&hold, img); err != nil { @@ -157,16 +157,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) { 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..869601e93 100644 --- a/pkg/specgen/generate/container_create.go +++ b/pkg/specgen/generate/container_create.go @@ -16,7 +16,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 { diff --git a/pkg/specgen/generate/oci.go b/pkg/specgen/generate/oci.go index 266abd28d..1c34f622b 100644 --- a/pkg/specgen/generate/oci.go +++ b/pkg/specgen/generate/oci.go @@ -112,7 +112,7 @@ func makeCommand(ctx context.Context, s *specgen.SpecGenerator, img *image.Image if initPath == "" { return nil, errors.Errorf("no path to init binary found but container requested an init") } - finalCommand = append([]string{initPath, "--"}, finalCommand...) + finalCommand = append([]string{"/dev/init", "--"}, finalCommand...) } return finalCommand, nil diff --git a/pkg/specgen/generate/storage.go b/pkg/specgen/generate/storage.go index 241c9adeb..0d78421a6 100644 --- a/pkg/specgen/generate/storage.go +++ b/pkg/specgen/generate/storage.go @@ -314,8 +314,8 @@ func addContainerInitBinary(s *specgen.SpecGenerator, path string) (spec.Mount, if !s.PidNS.IsPrivate() { return mount, fmt.Errorf("cannot add init binary as PID 1 (PID namespace isn't private)") } - if s.Systemd == "true" || s.Systemd == "always" { - return mount, fmt.Errorf("cannot use container-init binary with systemd") + if s.Systemd == "always" { + return mount, fmt.Errorf("cannot use container-init binary with systemd=always") } if _, err := os.Stat(path); os.IsNotExist(err) { return mount, errors.Wrap(err, "container-init binary not found on the host") 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 |