Skip to content

Commit b8a887c

Browse files
Merge pull request #21549 from openshift-cherrypick-robot/cherry-pick-20657-to-v4.9
[v4.9] RHEL-14922: accept a config blob alongside the "changes" slice when committing
2 parents 4c14019 + 0ac114f commit b8a887c

File tree

19 files changed

+330
-74
lines changed

19 files changed

+330
-74
lines changed

cmd/podman/containers/commit.go

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"github.com/containers/common/pkg/completion"
1010
"github.com/containers/podman/v4/cmd/podman/common"
1111
"github.com/containers/podman/v4/cmd/podman/registry"
12+
"github.com/containers/podman/v4/pkg/api/handlers"
1213
"github.com/containers/podman/v4/pkg/domain/entities"
1314
"github.com/spf13/cobra"
1415
)
@@ -47,7 +48,7 @@ var (
4748
commitOptions = entities.CommitOptions{
4849
ImageName: "",
4950
}
50-
iidFile string
51+
configFile, iidFile string
5152
)
5253

5354
func commitFlags(cmd *cobra.Command) {
@@ -57,6 +58,10 @@ func commitFlags(cmd *cobra.Command) {
5758
flags.StringArrayVarP(&commitOptions.Changes, changeFlagName, "c", []string{}, "Apply the following possible instructions to the created image (default []): "+strings.Join(common.ChangeCmds, " | "))
5859
_ = cmd.RegisterFlagCompletionFunc(changeFlagName, common.AutocompleteChangeInstructions)
5960

61+
configFileFlagName := "config"
62+
flags.StringVar(&configFile, configFileFlagName, "", "`file` containing a container configuration to merge into the image")
63+
_ = cmd.RegisterFlagCompletionFunc(configFileFlagName, completion.AutocompleteDefault)
64+
6065
formatFlagName := "format"
6166
flags.StringVarP(&commitOptions.Format, formatFlagName, "f", "oci", "`Format` of the image manifest and metadata")
6267
_ = cmd.RegisterFlagCompletionFunc(formatFlagName, common.AutocompleteImageFormat)
@@ -100,7 +105,16 @@ func commit(cmd *cobra.Command, args []string) error {
100105
if !commitOptions.Quiet {
101106
commitOptions.Writer = os.Stderr
102107
}
103-
108+
if len(commitOptions.Changes) > 0 {
109+
commitOptions.Changes = handlers.DecodeChanges(commitOptions.Changes)
110+
}
111+
if len(configFile) > 0 {
112+
cfg, err := os.ReadFile(configFile)
113+
if err != nil {
114+
return fmt.Errorf("--config: %w", err)
115+
}
116+
commitOptions.Config = cfg
117+
}
104118
response, err := registry.ContainerEngine().ContainerCommit(context.Background(), container, commitOptions)
105119
if err != nil {
106120
return err

cmd/podman/containers/create.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -400,7 +400,7 @@ func createPodIfNecessary(cmd *cobra.Command, s *specgen.SpecGenerator, netOpts
400400
var err error
401401
uns := specgen.Namespace{NSMode: specgen.Default}
402402
if cliVals.UserNS != "" {
403-
uns, err = specgen.ParseNamespace(cliVals.UserNS)
403+
uns, err = specgen.ParseUserNamespace(cliVals.UserNS)
404404
if err != nil {
405405
return err
406406
}

docs/source/markdown/options/volume.image.md

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,8 @@
44
####> are applicable to all of those.
55
#### **--volume**, **-v**=*[HOST-DIR:CONTAINER-DIR[:OPTIONS]]*
66

7-
Create a bind mount. Specifying the `-v /HOST-DIR:/CONTAINER-DIR` option, Podman
8-
bind mounts `/HOST-DIR` from the host to `/CONTAINER-DIR` in the Podman
9-
container.
7+
Mount a host directory into containers when executing RUN instructions during
8+
the build.
109

1110
The `OPTIONS` are a comma-separated list and can be: <sup>[[1]](#Footnote1)</sup>
1211

@@ -17,12 +16,9 @@ The `OPTIONS` are a comma-separated list and can be: <sup>[[1]](#Footnote1)</sup
1716

1817
The `CONTAINER-DIR` must be an absolute path such as `/src/docs`. The `HOST-DIR`
1918
must be an absolute path as well. Podman bind-mounts the `HOST-DIR` to the
20-
specified path. For example, when specifying the host path `/foo`,
21-
Podman copies the contents of `/foo` to the container filesystem on the host
22-
and bind mounts that into the container.
19+
specified path when processing RUN instructions.
2320

24-
You can specify multiple **-v** options to mount one or more mounts to a
25-
container.
21+
You can specify multiple **-v** options to mount one or more mounts.
2622

2723
You can add the `:ro` or `:rw` suffix to a volume to mount it read-only or
2824
read-write mode, respectively. By default, the volumes are mounted read-write.

docs/source/markdown/podman-commit.1.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,13 @@ Apply the following possible instructions to the created image:
3636

3737
Can be set multiple times.
3838

39+
#### **--config**=*ConfigBlobFile*
40+
41+
Merge the container configuration from the specified file into the configuration for the image
42+
as it is being committed. The file contents should be a JSON-encoded version of
43+
a Schema2Config structure, which is defined at
44+
https://github.com/containers/image/blob/v5.29.0/manifest/docker_schema2.go#L67.
45+
3946
#### **--format**, **-f**=**oci** | *docker*
4047

4148
Set the format of the image manifest and metadata. The currently supported formats are **oci** and *docker*.\

libpod/container_commit.go

Lines changed: 6 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -20,16 +20,16 @@ import (
2020

2121
// ContainerCommitOptions is a struct used to commit a container to an image
2222
// It uses buildah's CommitOptions as a base. Long-term we might wish to
23-
// add these to the buildah struct once buildah is more integrated with
24-
// libpod
23+
// decouple these because it includes duplicates of fields that are in, or
24+
// could later be added, to buildah's CommitOptions, which gets confusing
2525
type ContainerCommitOptions struct {
2626
buildah.CommitOptions
2727
Pause bool
2828
IncludeVolumes bool
2929
Author string
3030
Message string
31-
Changes []string
32-
Squash bool
31+
Changes []string // gets merged with CommitOptions.OverrideChanges
32+
Squash bool // always used instead of CommitOptions.Squash
3333
}
3434

3535
// Commit commits the changes between a container and its image, creating a new
@@ -69,6 +69,8 @@ func (c *Container) Commit(ctx context.Context, destImage string, options Contai
6969
Squash: options.Squash,
7070
SystemContext: c.runtime.imageContext,
7171
PreferredManifestType: options.PreferredManifestType,
72+
OverrideChanges: append(append([]string{}, options.Changes...), options.CommitOptions.OverrideChanges...),
73+
OverrideConfig: options.CommitOptions.OverrideConfig,
7274
}
7375
importBuilder, err := buildah.ImportBuilder(ctx, c.runtime.store, builderOptions)
7476
importBuilder.Format = options.PreferredManifestType
@@ -150,51 +152,6 @@ func (c *Container) Commit(ctx context.Context, destImage string, options Contai
150152
// Workdir
151153
importBuilder.SetWorkDir(c.config.Spec.Process.Cwd)
152154

153-
// Process user changes
154-
newImageConfig, err := libimage.ImageConfigFromChanges(options.Changes)
155-
if err != nil {
156-
return nil, err
157-
}
158-
if newImageConfig.User != "" {
159-
importBuilder.SetUser(newImageConfig.User)
160-
}
161-
// EXPOSE only appends
162-
for port := range newImageConfig.ExposedPorts {
163-
importBuilder.SetPort(port)
164-
}
165-
// ENV only appends
166-
for _, env := range newImageConfig.Env {
167-
splitEnv := strings.SplitN(env, "=", 2)
168-
key := splitEnv[0]
169-
value := ""
170-
if len(splitEnv) == 2 {
171-
value = splitEnv[1]
172-
}
173-
importBuilder.SetEnv(key, value)
174-
}
175-
if newImageConfig.Entrypoint != nil {
176-
importBuilder.SetEntrypoint(newImageConfig.Entrypoint)
177-
}
178-
if newImageConfig.Cmd != nil {
179-
importBuilder.SetCmd(newImageConfig.Cmd)
180-
}
181-
// VOLUME only appends
182-
for vol := range newImageConfig.Volumes {
183-
importBuilder.AddVolume(vol)
184-
}
185-
if newImageConfig.WorkingDir != "" {
186-
importBuilder.SetWorkDir(newImageConfig.WorkingDir)
187-
}
188-
for k, v := range newImageConfig.Labels {
189-
importBuilder.SetLabel(k, v)
190-
}
191-
if newImageConfig.StopSignal != "" {
192-
importBuilder.SetStopSignal(newImageConfig.StopSignal)
193-
}
194-
for _, onbuild := range newImageConfig.OnBuild {
195-
importBuilder.SetOnBuild(onbuild)
196-
}
197-
198155
var commitRef types.ImageReference
199156
if destImage != "" {
200157
// Now resolve the name.

pkg/api/handlers/changes.go

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package handlers
2+
3+
import (
4+
"strings"
5+
"unicode"
6+
)
7+
8+
// DecodeChanges reads one or more changes from a slice and cleans them up,
9+
// since what we've advertised as being acceptable in the past isn't really.
10+
func DecodeChanges(changes []string) []string {
11+
result := make([]string, 0, len(changes))
12+
for _, possiblyMultilineChange := range changes {
13+
for _, change := range strings.Split(possiblyMultilineChange, "\n") {
14+
// In particular, we document that we accept values
15+
// like "CMD=/bin/sh", which is not valid Dockerfile
16+
// syntax, so we can't just pass such a value directly
17+
// to a parser that's going to rightfully reject it.
18+
// If we trim the string of whitespace at both ends,
19+
// and the first occurrence of "=" is before the first
20+
// whitespace, replace that "=" with whitespace.
21+
change = strings.TrimSpace(change)
22+
if change == "" {
23+
continue
24+
}
25+
firstEqualIndex := strings.Index(change, "=")
26+
firstSpaceIndex := strings.IndexFunc(change, unicode.IsSpace)
27+
if firstEqualIndex != -1 && (firstSpaceIndex == -1 || firstEqualIndex < firstSpaceIndex) {
28+
change = change[:firstEqualIndex] + " " + change[firstEqualIndex+1:]
29+
}
30+
result = append(result, change)
31+
}
32+
}
33+
return result
34+
}

pkg/api/handlers/changes_test.go

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
package handlers
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stretchr/testify/assert"
7+
)
8+
9+
func TestDecodeChanges(t *testing.T) {
10+
testCases := []struct {
11+
description string
12+
input string
13+
output []string
14+
}{
15+
{
16+
description: "nothing",
17+
input: "",
18+
output: []string{},
19+
},
20+
{
21+
description: "space",
22+
input: `CMD=/bin/bash`,
23+
output: []string{"CMD /bin/bash"},
24+
},
25+
{
26+
description: "equal",
27+
input: `CMD=/bin/bash`,
28+
output: []string{"CMD /bin/bash"},
29+
},
30+
{
31+
description: "both-but-right-first",
32+
input: `LABEL A=B`,
33+
output: []string{"LABEL A=B"},
34+
},
35+
{
36+
description: "both-but-right-second",
37+
input: `LABEL A=B C=D`,
38+
output: []string{"LABEL A=B C=D"},
39+
},
40+
{
41+
description: "both-but-wrong",
42+
input: `LABEL=A=B C=D`,
43+
output: []string{"LABEL A=B C=D"},
44+
},
45+
}
46+
for _, testCase := range testCases {
47+
t.Run(testCase.description, func(t *testing.T) {
48+
output := DecodeChanges([]string{testCase.input})
49+
assert.Equalf(t, testCase.output, output, "decoded value was not what we expected")
50+
})
51+
}
52+
}

pkg/api/handlers/compat/images.go

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ package compat
22

33
import (
44
"context"
5-
"encoding/json"
65
"errors"
76
"fmt"
87
"net/http"
@@ -133,18 +132,17 @@ func CommitContainer(w http.ResponseWriter, r *http.Request) {
133132
PreferredManifestType: manifest.DockerV2Schema2MediaType,
134133
}
135134

136-
input := handlers.CreateContainerConfig{}
137-
if err := json.NewDecoder(r.Body).Decode(&input); err != nil {
138-
utils.Error(w, http.StatusInternalServerError, fmt.Errorf("Decode(): %w", err))
139-
return
140-
}
141-
142135
options.Message = query.Comment
143136
options.Author = query.Author
144137
options.Pause = query.Pause
145138
options.Squash = query.Squash
146-
for _, change := range query.Changes {
147-
options.Changes = append(options.Changes, strings.Split(change, "\n")...)
139+
options.Changes = handlers.DecodeChanges(query.Changes)
140+
if r.Body != nil {
141+
defer r.Body.Close()
142+
if options.CommitOptions.OverrideConfig, err = abi.DecodeOverrideConfig(r.Body); err != nil {
143+
utils.Error(w, http.StatusBadRequest, err)
144+
return
145+
}
148146
}
149147
ctr, err := runtime.LookupContainer(query.Container)
150148
if err != nil {

pkg/api/handlers/libpod/images.go

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -487,14 +487,21 @@ func CommitContainer(w http.ResponseWriter, r *http.Request) {
487487
SystemContext: sc,
488488
PreferredManifestType: mimeType,
489489
}
490+
if r.Body != nil {
491+
defer r.Body.Close()
492+
if options.CommitOptions.OverrideConfig, err = abi.DecodeOverrideConfig(r.Body); err != nil {
493+
utils.Error(w, http.StatusBadRequest, err)
494+
return
495+
}
496+
}
490497
if len(query.Tag) > 0 {
491498
tag = query.Tag
492499
}
493500
options.Message = query.Comment
494501
options.Author = query.Author
495502
options.Pause = query.Pause
496503
options.Squash = query.Squash
497-
options.Changes = query.Changes
504+
options.Changes = handlers.DecodeChanges(query.Changes)
498505
ctr, err := runtime.LookupContainer(query.Container)
499506
if err != nil {
500507
utils.Error(w, http.StatusNotFound, err)

pkg/bindings/containers/commit.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,11 @@ func Commit(ctx context.Context, nameOrID string, options *CommitOptions) (entit
3333
return entities.IDResponse{}, err
3434
}
3535
params.Set("container", nameOrID)
36-
response, err := conn.DoRequest(ctx, nil, http.MethodPost, "/commit", params, nil)
36+
var requestBody io.Reader
37+
if options.Config != nil {
38+
requestBody = *options.Config
39+
}
40+
response, err := conn.DoRequest(ctx, requestBody, http.MethodPost, "/commit", params, nil)
3741
if err != nil {
3842
return id, err
3943
}

0 commit comments

Comments
 (0)