diff options
-rw-r--r-- | Makefile | 6 | ||||
-rw-r--r-- | cmd/podman/containers/commit.go | 1 | ||||
-rw-r--r-- | contrib/modules-load.d/podman-iptables.conf | 5 | ||||
-rw-r--r-- | docs/source/markdown/podman-commit.1.md | 5 | ||||
-rw-r--r-- | docs/source/markdown/podman-generate-systemd.1.md | 2 | ||||
-rw-r--r-- | libpod/container_commit.go | 2 | ||||
-rw-r--r-- | pkg/api/handlers/compat/images.go | 2 | ||||
-rw-r--r-- | pkg/api/handlers/libpod/images.go | 2 | ||||
-rw-r--r-- | pkg/api/server/register_images.go | 4 | ||||
-rw-r--r-- | pkg/bindings/containers/types.go | 1 | ||||
-rw-r--r-- | pkg/bindings/containers/types_commit_options.go | 15 | ||||
-rw-r--r-- | pkg/domain/entities/containers.go | 1 | ||||
-rw-r--r-- | pkg/domain/infra/abi/containers.go | 1 | ||||
-rw-r--r-- | pkg/domain/infra/tunnel/containers.go | 2 | ||||
-rw-r--r-- | pkg/specgen/generate/kube/kube.go | 48 | ||||
-rw-r--r-- | pkg/specgen/generate/kube/play_test.go | 79 | ||||
-rw-r--r-- | podman.spec.rpkg | 6 | ||||
-rw-r--r-- | test/e2e/commit_test.go | 18 | ||||
-rw-r--r-- | troubleshooting.md | 19 |
19 files changed, 215 insertions, 4 deletions
@@ -44,6 +44,7 @@ MANDIR ?= ${PREFIX}/share/man SHAREDIR_CONTAINERS ?= ${PREFIX}/share/containers ETCDIR ?= ${PREFIX}/etc TMPFILESDIR ?= ${PREFIX}/lib/tmpfiles.d +MODULESLOADDIR ?= ${PREFIX}/lib/modules-load.d SYSTEMDDIR ?= ${PREFIX}/lib/systemd/system USERSYSTEMDDIR ?= ${PREFIX}/lib/systemd/user REMOTETAGS ?= remote exclude_graphdriver_btrfs btrfs_noversion exclude_graphdriver_devicemapper containers_image_openpgp @@ -779,6 +780,11 @@ install.bin: install ${SELINUXOPT} -m 755 -d ${DESTDIR}${TMPFILESDIR} install ${SELINUXOPT} -m 644 contrib/tmpfile/podman.conf ${DESTDIR}${TMPFILESDIR}/podman.conf +.PHONY: install.modules-load +install.modules-load: # This should only be used by distros which might use iptables-legacy, this is not needed on RHEL + install ${SELINUXOPT} -m 755 -d ${DESTDIR}${MODULESLOADDIR} + install ${SELINUXOPT} -m 644 contrib/modules-load.d/podman-iptables.conf ${DESTDIR}${MODULESLOADDIR}/podman-iptables.conf + .PHONY: install.man install.man: install ${SELINUXOPT} -d -m 755 $(DESTDIR)$(MANDIR)/man1 diff --git a/cmd/podman/containers/commit.go b/cmd/podman/containers/commit.go index f74fd4ab1..e0cadd5b0 100644 --- a/cmd/podman/containers/commit.go +++ b/cmd/podman/containers/commit.go @@ -77,6 +77,7 @@ func commitFlags(cmd *cobra.Command) { flags.BoolVarP(&commitOptions.Pause, "pause", "p", false, "Pause container during commit") flags.BoolVarP(&commitOptions.Quiet, "quiet", "q", false, "Suppress output") + flags.BoolVarP(&commitOptions.Squash, "squash", "s", false, "squash newly built layers into a single new layer") flags.BoolVar(&commitOptions.IncludeVolumes, "include-volumes", false, "Include container volumes as image volumes") } diff --git a/contrib/modules-load.d/podman-iptables.conf b/contrib/modules-load.d/podman-iptables.conf new file mode 100644 index 000000000..001ef8af8 --- /dev/null +++ b/contrib/modules-load.d/podman-iptables.conf @@ -0,0 +1,5 @@ +# On fedora 36 ip_tables is no longer auto loaded and rootless user have no permsissions to load it. +# When we have actual nftables support in the future we might want to revisit this. +# If you use iptables-nft this is not needed. +ip_tables +ip6_tables diff --git a/docs/source/markdown/podman-commit.1.md b/docs/source/markdown/podman-commit.1.md index 87b7e8aea..df3c38711 100644 --- a/docs/source/markdown/podman-commit.1.md +++ b/docs/source/markdown/podman-commit.1.md @@ -60,6 +60,11 @@ Set commit message for committed image.\ Pause the container when creating an image.\ The default is **false**. +#### **--squash**, **-s** + +Squash newly built layers into a single new layer.\ +The default is **false**. + #### **--quiet**, **-q** Suppresses output.\ diff --git a/docs/source/markdown/podman-generate-systemd.1.md b/docs/source/markdown/podman-generate-systemd.1.md index fdc9c21a5..b2b5ee2ca 100644 --- a/docs/source/markdown/podman-generate-systemd.1.md +++ b/docs/source/markdown/podman-generate-systemd.1.md @@ -10,6 +10,8 @@ podman\-generate\-systemd - Generate systemd unit file(s) for a container or pod **podman generate systemd** will create a systemd unit file that can be used to control a container or pod. By default, the command will print the content of the unit files to stdout. +Generating unit files for a pod requires the pod to be created with an infra container (see `--infra=true`). An infra container runs across the entire lifespan of a pod and is hence required for systemd to manage the life cycle of the pod's main unit. + _Note: If you use this command with the remote client, including Mac and Windows (excluding WSL2) machines, you would still have to place the generated units on the remote system. Moreover, please make sure that the XDG_RUNTIME_DIR environment variable is set. If unset, you may set it via `export XDG_RUNTIME_DIR=/run/user/$(id -u)`._ ## OPTIONS diff --git a/libpod/container_commit.go b/libpod/container_commit.go index 99d08ccf1..7018ee7d8 100644 --- a/libpod/container_commit.go +++ b/libpod/container_commit.go @@ -27,6 +27,7 @@ type ContainerCommitOptions struct { Author string Message string Changes []string + Squash bool } // Commit commits the changes between a container and its image, creating a new @@ -63,6 +64,7 @@ func (c *Container) Commit(ctx context.Context, destImage string, options Contai commitOptions := buildah.CommitOptions{ SignaturePolicyPath: options.SignaturePolicyPath, ReportWriter: options.ReportWriter, + Squash: options.Squash, SystemContext: c.runtime.imageContext, PreferredManifestType: options.PreferredManifestType, } diff --git a/pkg/api/handlers/compat/images.go b/pkg/api/handlers/compat/images.go index 3546f88a0..edefce010 100644 --- a/pkg/api/handlers/compat/images.go +++ b/pkg/api/handlers/compat/images.go @@ -102,6 +102,7 @@ func CommitContainer(w http.ResponseWriter, r *http.Request) { Comment string `schema:"comment"` Container string `schema:"container"` Pause bool `schema:"pause"` + Squash bool `schema:"squash"` Repo string `schema:"repo"` Tag string `schema:"tag"` // fromSrc string # fromSrc is currently unused @@ -138,6 +139,7 @@ func CommitContainer(w http.ResponseWriter, r *http.Request) { options.Message = query.Comment options.Author = query.Author options.Pause = query.Pause + options.Squash = query.Squash for _, change := range query.Changes { options.Changes = append(options.Changes, strings.Split(change, "\n")...) } diff --git a/pkg/api/handlers/libpod/images.go b/pkg/api/handlers/libpod/images.go index f078c13cc..eb9fb12a6 100644 --- a/pkg/api/handlers/libpod/images.go +++ b/pkg/api/handlers/libpod/images.go @@ -497,6 +497,7 @@ func CommitContainer(w http.ResponseWriter, r *http.Request) { Container string `schema:"container"` Format string `schema:"format"` Pause bool `schema:"pause"` + Squash bool `schema:"squash"` Repo string `schema:"repo"` Tag string `schema:"tag"` }{ @@ -543,6 +544,7 @@ func CommitContainer(w http.ResponseWriter, r *http.Request) { options.Message = query.Comment options.Author = query.Author options.Pause = query.Pause + options.Squash = query.Squash options.Changes = query.Changes ctr, err := runtime.LookupContainer(query.Container) if err != nil { diff --git a/pkg/api/server/register_images.go b/pkg/api/server/register_images.go index d7bc17093..017310f12 100644 --- a/pkg/api/server/register_images.go +++ b/pkg/api/server/register_images.go @@ -460,6 +460,10 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error { // name: changes // type: string // description: instructions to apply while committing in Dockerfile format + // - in: query + // name: squash + // type: boolean + // description: squash newly built layers into a single new layer // produces: // - application/json // responses: diff --git a/pkg/bindings/containers/types.go b/pkg/bindings/containers/types.go index 4915e3e23..66b90af9b 100644 --- a/pkg/bindings/containers/types.go +++ b/pkg/bindings/containers/types.go @@ -30,6 +30,7 @@ type CommitOptions struct { Comment *string Format *string Pause *bool + Squash *bool Repo *string Tag *string } diff --git a/pkg/bindings/containers/types_commit_options.go b/pkg/bindings/containers/types_commit_options.go index 7eb04198f..7b4745eb8 100644 --- a/pkg/bindings/containers/types_commit_options.go +++ b/pkg/bindings/containers/types_commit_options.go @@ -92,6 +92,21 @@ func (o *CommitOptions) GetPause() bool { return *o.Pause } +// WithSquash set field Squash to given value +func (o *CommitOptions) WithSquash(value bool) *CommitOptions { + o.Squash = &value + return o +} + +// GetSquash returns value of field Squash +func (o *CommitOptions) GetSquash() bool { + if o.Squash == nil { + var z bool + return z + } + return *o.Squash +} + // WithRepo set field Repo to given value func (o *CommitOptions) WithRepo(value string) *CommitOptions { o.Repo = &value diff --git a/pkg/domain/entities/containers.go b/pkg/domain/entities/containers.go index e9bce0eb7..79795a221 100644 --- a/pkg/domain/entities/containers.go +++ b/pkg/domain/entities/containers.go @@ -154,6 +154,7 @@ type CommitOptions struct { Message string Pause bool Quiet bool + Squash bool Writer io.Writer } diff --git a/pkg/domain/infra/abi/containers.go b/pkg/domain/infra/abi/containers.go index 92f5b1a80..e6feb7c82 100644 --- a/pkg/domain/infra/abi/containers.go +++ b/pkg/domain/infra/abi/containers.go @@ -529,6 +529,7 @@ func (ic *ContainerEngine) ContainerCommit(ctx context.Context, nameOrID string, Message: options.Message, Changes: options.Changes, Author: options.Author, + Squash: options.Squash, } newImage, err := ctr.Commit(ctx, options.ImageName, opts) if err != nil { diff --git a/pkg/domain/infra/tunnel/containers.go b/pkg/domain/infra/tunnel/containers.go index aa4baf846..fe986361b 100644 --- a/pkg/domain/infra/tunnel/containers.go +++ b/pkg/domain/infra/tunnel/containers.go @@ -302,7 +302,7 @@ func (ic *ContainerEngine) ContainerCommit(ctx context.Context, nameOrID string, return nil, errors.Errorf("invalid image name %q", opts.ImageName) } } - options := new(containers.CommitOptions).WithAuthor(opts.Author).WithChanges(opts.Changes).WithComment(opts.Message) + options := new(containers.CommitOptions).WithAuthor(opts.Author).WithChanges(opts.Changes).WithComment(opts.Message).WithSquash(opts.Squash) options.WithFormat(opts.Format).WithPause(opts.Pause).WithRepo(repo).WithTag(tag) response, err := containers.Commit(ic.ClientCtx, nameOrID, options) if err != nil { diff --git a/pkg/specgen/generate/kube/kube.go b/pkg/specgen/generate/kube/kube.go index 767589ead..5e6671231 100644 --- a/pkg/specgen/generate/kube/kube.go +++ b/pkg/specgen/generate/kube/kube.go @@ -7,6 +7,7 @@ import ( "math" "net" "regexp" + "runtime" "strconv" "strings" "time" @@ -22,6 +23,7 @@ import ( "github.com/containers/podman/v4/pkg/specgen" "github.com/containers/podman/v4/pkg/specgen/generate" "github.com/containers/podman/v4/pkg/util" + "github.com/docker/docker/pkg/system" "github.com/docker/go-units" spec "github.com/opencontainers/runtime-spec/specs-go" "github.com/pkg/errors" @@ -712,8 +714,12 @@ func envVarValueResourceFieldRef(env v1.EnvVar, opts *CtrSpecGenOptions) (*strin divisor.Set(1) } + resources, err := getContainerResources(opts.Container) + if err != nil { + return nil, err + } + var value *resource.Quantity - resources := opts.Container.Resources resourceName := env.ValueFrom.ResourceFieldRef.Resource var isValidDivisor bool @@ -769,6 +775,46 @@ func isCPUDivisor(divisor resource.Quantity) bool { } } +func getContainerResources(container v1.Container) (v1.ResourceRequirements, error) { + result := v1.ResourceRequirements{ + Limits: v1.ResourceList{}, + Requests: v1.ResourceList{}, + } + + limits := container.Resources.Limits + requests := container.Resources.Requests + + if limits == nil || limits.Memory().IsZero() { + mi, err := system.ReadMemInfo() + if err != nil { + return result, err + } + result.Limits[v1.ResourceMemory] = *resource.NewQuantity(mi.MemTotal, resource.DecimalSI) + } else { + result.Limits[v1.ResourceMemory] = limits[v1.ResourceMemory] + } + + if limits == nil || limits.Cpu().IsZero() { + result.Limits[v1.ResourceCPU] = *resource.NewQuantity(int64(runtime.NumCPU()), resource.DecimalSI) + } else { + result.Limits[v1.ResourceCPU] = limits[v1.ResourceCPU] + } + + if requests == nil || requests.Memory().IsZero() { + result.Requests[v1.ResourceMemory] = result.Limits[v1.ResourceMemory] + } else { + result.Requests[v1.ResourceMemory] = requests[v1.ResourceMemory] + } + + if requests == nil || requests.Cpu().IsZero() { + result.Requests[v1.ResourceCPU] = result.Limits[v1.ResourceCPU] + } else { + result.Requests[v1.ResourceCPU] = requests[v1.ResourceCPU] + } + + return result, nil +} + // getPodPorts converts a slice of kube container descriptions to an // array of portmapping func getPodPorts(containers []v1.Container) []types.PortMapping { diff --git a/pkg/specgen/generate/kube/play_test.go b/pkg/specgen/generate/kube/play_test.go index 282324310..6798fdb1b 100644 --- a/pkg/specgen/generate/kube/play_test.go +++ b/pkg/specgen/generate/kube/play_test.go @@ -6,10 +6,12 @@ import ( "io/ioutil" "math" "os" + "runtime" "strconv" "testing" "github.com/containers/common/pkg/secrets" + "github.com/docker/docker/pkg/system" "github.com/stretchr/testify/assert" v1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/resource" @@ -193,6 +195,11 @@ func TestEnvVarValue(t *testing.T) { assert.NoError(t, err) defer os.RemoveAll(d) secretsManager := createSecrets(t, d) + stringNumCPUs := strconv.Itoa(runtime.NumCPU()) + + mi, err := system.ReadMemInfo() + assert.Nil(t, err) + stringMemTotal := strconv.FormatInt(mi.MemTotal, 10) tests := []struct { name string @@ -694,6 +701,78 @@ func TestEnvVarValue(t *testing.T) { true, strconv.Itoa(int(float64(cpuInt) / 0.001)), }, + { + "ResourceFieldRefNoLimitMemory", + v1.EnvVar{ + Name: "FOO", + ValueFrom: &v1.EnvVarSource{ + ResourceFieldRef: &v1.ResourceFieldSelector{ + Resource: "limits.memory", + }, + }, + }, + CtrSpecGenOptions{ + Container: v1.Container{ + Name: "test", + }, + }, + true, + stringMemTotal, + }, + { + "ResourceFieldRefNoRequestMemory", + v1.EnvVar{ + Name: "FOO", + ValueFrom: &v1.EnvVarSource{ + ResourceFieldRef: &v1.ResourceFieldSelector{ + Resource: "requests.memory", + }, + }, + }, + CtrSpecGenOptions{ + Container: v1.Container{ + Name: "test", + }, + }, + true, + stringMemTotal, + }, + { + "ResourceFieldRefNoLimitCPU", + v1.EnvVar{ + Name: "FOO", + ValueFrom: &v1.EnvVarSource{ + ResourceFieldRef: &v1.ResourceFieldSelector{ + Resource: "limits.cpu", + }, + }, + }, + CtrSpecGenOptions{ + Container: v1.Container{ + Name: "test", + }, + }, + true, + stringNumCPUs, + }, + { + "ResourceFieldRefNoRequestCPU", + v1.EnvVar{ + Name: "FOO", + ValueFrom: &v1.EnvVarSource{ + ResourceFieldRef: &v1.ResourceFieldSelector{ + Resource: "requests.cpu", + }, + }, + }, + CtrSpecGenOptions{ + Container: v1.Container{ + Name: "test", + }, + }, + true, + stringNumCPUs, + }, } for _, test := range tests { diff --git a/podman.spec.rpkg b/podman.spec.rpkg index d02b7ea99..f810d0307 100644 --- a/podman.spec.rpkg +++ b/podman.spec.rpkg @@ -206,6 +206,9 @@ PODMAN_VERSION=%{version} %{__make} DESTDIR=%{buildroot} PREFIX=%{_prefix} ETCDI install.docker \ install.docker-docs \ install.remote \ +%if 0%{?fedora} >= 36 + install.modules-load +%endif install -d -p %{buildroot}/%{_datadir}/%{name}/test/system cp -pav test/system %{buildroot}/%{_datadir}/%{name}/test/ @@ -242,6 +245,9 @@ done %{_userunitdir}/%{name}.socket %{_userunitdir}/%{name}-restart.service %{_usr}/lib/tmpfiles.d/%{name}.conf +%if 0%{?fedora} >= 36 + %{_usr}/lib/modules-load.d/%{name}-iptables.conf +%endif %files docker %{_bindir}/docker diff --git a/test/e2e/commit_test.go b/test/e2e/commit_test.go index 495e31520..6bcf17bfe 100644 --- a/test/e2e/commit_test.go +++ b/test/e2e/commit_test.go @@ -4,6 +4,7 @@ import ( "io/ioutil" "os" "path/filepath" + "strings" . "github.com/containers/podman/v4/test/utils" . "github.com/onsi/ginkgo" @@ -133,6 +134,23 @@ var _ = Describe("Podman commit", func() { Expect(foundBlue).To(Equal(true)) }) + It("podman commit container with --squash", func() { + test := podmanTest.Podman([]string{"run", "--name", "test1", "-d", ALPINE, "ls"}) + test.WaitWithDefaultTimeout() + Expect(test).Should(Exit(0)) + Expect(podmanTest.NumberOfContainers()).To(Equal(1)) + + session := podmanTest.Podman([]string{"commit", "--squash", "test1", "foobar.com/test1-image:latest"}) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + + session = podmanTest.Podman([]string{"inspect", "--format", "{{.RootFS.Layers}}", "foobar.com/test1-image:latest"}) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + // Check for one layers + Expect(strings.Fields(session.OutputToString())).To(HaveLen(1)) + }) + It("podman commit container with change flag and JSON entrypoint with =", func() { test := podmanTest.Podman([]string{"run", "--name", "test1", "-d", ALPINE, "ls"}) test.WaitWithDefaultTimeout() diff --git a/troubleshooting.md b/troubleshooting.md index dedcf6bb9..8bce8e50f 100644 --- a/troubleshooting.md +++ b/troubleshooting.md @@ -914,10 +914,25 @@ Error: error creating tmpdir: mkdir /run/user/1000: permission denied Podman expects a valid login session for the `rootless+cgroupv2` use-case. Podman execution is expected to fail if the login session is not present. In most cases, podman will figure out a solution on its own but if `XDG_RUNTIME_DIR` is pointing to a path that is not writable execution will most fail. Typical scenarious of such cases are seen when users are trying to use Podman with `su - <user> -c '<podman-command>`, or `sudo -l` and badly configured systemd session. -Resolution steps +Alternatives: + +* Execute Podman via __systemd-run__ that will first start a systemd login session: + + ``` + sudo systemd-run --machine=username@ --quiet --user --collect --pipe --wait podman run --rm docker.io/library/alpine echo hello + ``` +* Start an interactive shell in a systemd login session with the command `machinectl shell <username>@` + and then run Podman + + ``` + $ sudo -i + # machinectl shell username@ + Connected to the local host. Press ^] three times within 1s to exit session. + $ podman run --rm docker.io/library/alpine echo hello + ``` +* Start a new systemd login session by logging in with `ssh` i.e. `ssh <username>@localhost` and then run Podman. * Before invoking Podman command create a valid login session for your rootless user using `loginctl enable-linger <username>` -* If `loginctl` is unavailable you can also try logging in via `ssh` i.e `ssh <username>@localhost`. ### 31) 127.0.0.1:7777 port already bound |