Skip to content

Commit

Permalink
Add image squash ut & Update docs/command-reference.md
Browse files Browse the repository at this point in the history
Signed-off-by: weipeng <[email protected]>
  • Loading branch information
weipeng committed Jan 15, 2025
1 parent baf13d2 commit 70fc46c
Show file tree
Hide file tree
Showing 6 changed files with 177 additions and 60 deletions.
19 changes: 7 additions & 12 deletions cmd/nerdctl/image/image_squash.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,15 +28,15 @@ import (
)

func addSquashFlags(cmd *cobra.Command) {
cmd.Flags().IntP("layer-count", "c", 0, "The number of layers that can be compressed")
cmd.Flags().StringP("layer-digest", "d", "", "The digest of the layer to be compressed")
cmd.Flags().StringP("author", "a", "", `Author (e.g., "nerdctl contributor <[email protected]>")`)
cmd.Flags().StringP("message", "m", "", "Commit message")
cmd.Flags().IntP("last-n-layer", "n", 0, "The number of specify squashing the last N (N=layer-count) layers")
cmd.Flags().StringP("author", "a", "nerdctl", `Author (e.g., "nerdctl contributor <[email protected]>")`)
cmd.Flags().StringP("message", "m", "generated by nerdctl squash", "Commit message")
}

// NewSquashCommand returns a new `squash` command to compress the number of layers of the image
func NewSquashCommand() *cobra.Command {
var squashCommand = &cobra.Command{
Use: "squash [flags] SOURCE_IMAGE TAG_IMAGE",
Use: "squash [flags] SOURCE_IMAGE TARGET_IMAGE",
Short: "Compress the number of layers of the image",
Args: helpers.IsExactArgs(2),
RunE: squashAction,
Expand All @@ -52,11 +52,7 @@ func processSquashCommandFlags(cmd *cobra.Command, args []string) (options types
if err != nil {
return options, err
}
layerCount, err := cmd.Flags().GetInt("layer-count")
if err != nil {
return options, err
}
layerDigest, err := cmd.Flags().GetString("layer-digest")
layerN, err := cmd.Flags().GetInt("last-n-layer")
if err != nil {
return options, err
}
Expand All @@ -78,8 +74,7 @@ func processSquashCommandFlags(cmd *cobra.Command, args []string) (options types
SourceImageRef: args[0],
TargetImageName: args[1],

SquashLayerCount: layerCount,
SquashLayerDigest: layerDigest,
SquashLayerLastN: layerN,
}
return options, nil
}
Expand Down
100 changes: 100 additions & 0 deletions cmd/nerdctl/image/image_squash_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package image

import (
"fmt"
"testing"

"gotest.tools/v3/assert"

"github.com/containerd/nerdctl/v2/pkg/testutil"
"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest"
"github.com/containerd/nerdctl/v2/pkg/testutil/test"
)

func squashIdentifierName(identifier string) string {
return fmt.Sprintf("%s-squash", identifier)
}

func secondCommitedIdentifierName(identifier string) string {
return fmt.Sprintf("%s-second", identifier)
}

func TestSquash(t *testing.T) {
testCase := nerdtest.Setup()

require := test.Require(
test.Not(nerdtest.Docker),
nerdtest.CGroup,
)

testCase.SubTests = []*test.Case{
{
Description: "by last-n-layer",
Require: require,
NoParallel: true,
Cleanup: func(data test.Data, helpers test.Helpers) {
identifier := data.Identifier()
secondIdentifier := secondCommitedIdentifierName(identifier)
squashIdentifier := squashIdentifierName(identifier)
helpers.Anyhow("rm", "-f", identifier)
helpers.Anyhow("rm", "-f", secondIdentifier)
helpers.Anyhow("rm", "-f", squashIdentifier)

helpers.Anyhow("rmi", "-f", secondIdentifier)
helpers.Anyhow("rmi", "-f", identifier)
helpers.Anyhow("rmi", "-f", squashIdentifier)
helpers.Anyhow("image", "prune", "-f")
},
Setup: func(data test.Data, helpers test.Helpers) {
identifier := data.Identifier()
helpers.Ensure("run", "-d", "--name", identifier, testutil.CommonImage, "sleep", nerdtest.Infinity)
helpers.Ensure("exec", identifier, "sh", "-euxc", `echo hello-first-commit > /foo`)
helpers.Ensure("commit", "-c", `CMD ["cat", "/foo"]`, "-m", `first commit`, "--pause=true", identifier, identifier)
out := helpers.Capture("run", "--rm", identifier)
assert.Equal(t, out, "hello-first-commit\n")

secondIdentifier := secondCommitedIdentifierName(identifier)
helpers.Ensure("run", "-d", "--name", secondIdentifier, identifier, "sleep", nerdtest.Infinity)
helpers.Ensure("exec", secondIdentifier, "sh", "-euxc", `echo hello-second-commit > /bar && echo hello-squash-commit > /foo`)
helpers.Ensure("commit", "-c", `CMD ["cat", "/foo", "/bar"]`, "-m", `second commit`, "--pause=true", secondIdentifier, secondIdentifier)
out = helpers.Capture("run", "--rm", secondIdentifier)
assert.Equal(t, out, "hello-squash-commit\nhello-second-commit\n")

squashIdentifier := squashIdentifierName(identifier)
helpers.Ensure("image", "squash", "-n", "2", "-m", "squash commit", secondIdentifier, squashIdentifier)
out = helpers.Capture("run", "--rm", squashIdentifier)
assert.Equal(t, out, "hello-squash-commit\nhello-second-commit\n")
},
Command: func(data test.Data, helpers test.Helpers) test.TestableCommand {
identifier := data.Identifier()

squashIdentifier := squashIdentifierName(identifier)
return helpers.Command("image", "history", "--human=true", "--format=json", squashIdentifier)
},
Expected: test.Expects(0, nil, func(stdout string, info string, t *testing.T) {
history, err := decode(stdout)
assert.NilError(t, err, info)
assert.Equal(t, len(history), 3, info)
assert.Equal(t, history[0].Comment, "squash commit", info)
}),
},
}

testCase.Run(t)
}
1 change: 0 additions & 1 deletion cmd/nerdctl/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -295,7 +295,6 @@ Config file ($NERDCTL_TOML): %s
image.NewTagCommand(),
image.NewRmiCommand(),
image.NewHistoryCommand(),
image.NewSquashCommand(),
// #endregion

// #region System
Expand Down
18 changes: 18 additions & 0 deletions docs/command-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -994,6 +994,24 @@ Flags:
- `--platform=<PLATFORM>` : Convert content for a specific platform
- `--all-platforms` : Convert content for all platforms (default: false)

### :nerd_face: nerdctl image squash

Squash last-n-layer into a single layer.

Usage: `nerdctl image squash [OPTIONS] SOURCE_IMAGE[:TAG] TARGET_IMAGE[:TAG]`

Example:

```bash
nerdctl image pull example.com/foo:latest
nerdctl image squash ----last-n-layer=2 --message="generated by nerdctl squash" example.com/foo:latest example.com/foo:squashed
```

Flags:
- `-n --last-n-layer=<NUMBER>`: The number of specify squashing the last N (N=layer-count) layers
- `-m --message=<MESSAGE>`: Commit message for the squashed image
- `-a --author=<AUTHOR>`: Author of the squashed image

## Registry

### :whale: nerdctl login
Expand Down
6 changes: 2 additions & 4 deletions pkg/api/types/image_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -305,8 +305,6 @@ type ImageSquashOptions struct {
// TargetImageName is the name of the squashed image
TargetImageName string

// SquashLayerCount is the number of layers to squash
SquashLayerCount int
// SquashLayerDigest is the digest of the layer to squash
SquashLayerDigest string
// SquashLayerLastN is the number of layers to squash
SquashLayerLastN int
}
Loading

0 comments on commit 70fc46c

Please sign in to comment.