mirror of https://github.com/docker/compose.git
feat: add commit command
Signed-off-by: MohammadHasan Akbari <jarqvi.jarqvi@gmail.com>
This commit is contained in:
parent
a85f8a40a9
commit
9eaba55973
|
@ -0,0 +1,93 @@
|
|||
/*
|
||||
Copyright 2020 Docker Compose CLI 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 compose
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/cli/opts"
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
type commitOptions struct {
|
||||
*ProjectOptions
|
||||
|
||||
service string
|
||||
reference string
|
||||
|
||||
pause bool
|
||||
comment string
|
||||
author string
|
||||
changes opts.ListOpts
|
||||
|
||||
index int
|
||||
}
|
||||
|
||||
func commitCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *cobra.Command {
|
||||
options := commitOptions{
|
||||
ProjectOptions: p,
|
||||
}
|
||||
cmd := &cobra.Command{
|
||||
Use: "commit [OPTIONS] SERVICE [REPOSITORY[:TAG]]",
|
||||
Short: "Create a new image from a service container's changes",
|
||||
Args: cobra.RangeArgs(1, 2),
|
||||
PreRunE: Adapt(func(ctx context.Context, args []string) error {
|
||||
options.service = args[0]
|
||||
if len(args) > 1 {
|
||||
options.reference = args[1]
|
||||
}
|
||||
|
||||
return nil
|
||||
}),
|
||||
RunE: Adapt(func(ctx context.Context, args []string) error {
|
||||
return runCommit(ctx, dockerCli, backend, options)
|
||||
}),
|
||||
ValidArgsFunction: completeServiceNames(dockerCli, p),
|
||||
}
|
||||
|
||||
flags := cmd.Flags()
|
||||
flags.IntVar(&options.index, "index", 0, "index of the container if service has multiple replicas.")
|
||||
|
||||
flags.BoolVarP(&options.pause, "pause", "p", true, "Pause container during commit")
|
||||
flags.StringVarP(&options.comment, "message", "m", "", "Commit message")
|
||||
flags.StringVarP(&options.author, "author", "a", "", `Author (e.g., "John Hannibal Smith <hannibal@a-team.com>")`)
|
||||
options.changes = opts.NewListOpts(nil)
|
||||
flags.VarP(&options.changes, "change", "c", "Apply Dockerfile instruction to the created image")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func runCommit(ctx context.Context, dockerCli command.Cli, backend api.Service, options commitOptions) error {
|
||||
projectName, err := options.toProjectName(ctx, dockerCli)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
commitOptions := api.CommitOptions{
|
||||
Service: options.service,
|
||||
Reference: options.reference,
|
||||
Pause: options.pause,
|
||||
Comment: options.comment,
|
||||
Author: options.author,
|
||||
Changes: options.changes,
|
||||
Index: options.index,
|
||||
}
|
||||
|
||||
return backend.Commit(ctx, projectName, commitOptions)
|
||||
}
|
|
@ -617,6 +617,7 @@ func RootCommand(dockerCli command.Cli, backend Backend) *cobra.Command { //noli
|
|||
execCommand(&opts, dockerCli, backend),
|
||||
attachCommand(&opts, dockerCli, backend),
|
||||
exportCommand(&opts, dockerCli, backend),
|
||||
commitCommand(&opts, dockerCli, backend),
|
||||
pauseCommand(&opts, dockerCli, backend),
|
||||
unpauseCommand(&opts, dockerCli, backend),
|
||||
topCommand(&opts, dockerCli, backend),
|
||||
|
|
|
@ -13,6 +13,7 @@ Define and run multi-container applications with Docker
|
|||
|:--------------------------------|:----------------------------------------------------------------------------------------|
|
||||
| [`attach`](compose_attach.md) | Attach local standard input, output, and error streams to a service's running container |
|
||||
| [`build`](compose_build.md) | Build or rebuild services |
|
||||
| [`commit`](compose_commit.md) | Create a new image from a service container's changes |
|
||||
| [`config`](compose_config.md) | Parse, resolve and render compose file in canonical format |
|
||||
| [`cp`](compose_cp.md) | Copy files/folders between a service container and the local filesystem |
|
||||
| [`create`](compose_create.md) | Creates containers for a service |
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
# docker compose commit
|
||||
|
||||
<!---MARKER_GEN_START-->
|
||||
Create a new image from a service container's changes
|
||||
|
||||
### Options
|
||||
|
||||
| Name | Type | Default | Description |
|
||||
|:------------------|:---------|:--------|:-----------------------------------------------------------|
|
||||
| `-a`, `--author` | `string` | | Author (e.g., "John Hannibal Smith <hannibal@a-team.com>") |
|
||||
| `-c`, `--change` | `list` | | Apply Dockerfile instruction to the created image |
|
||||
| `--dry-run` | `bool` | | Execute command in dry run mode |
|
||||
| `--index` | `int` | `0` | index of the container if service has multiple replicas. |
|
||||
| `-m`, `--message` | `string` | | Commit message |
|
||||
| `-p`, `--pause` | `bool` | `true` | Pause container during commit |
|
||||
|
||||
|
||||
<!---MARKER_GEN_END-->
|
||||
|
|
@ -7,6 +7,7 @@ plink: docker.yaml
|
|||
cname:
|
||||
- docker compose attach
|
||||
- docker compose build
|
||||
- docker compose commit
|
||||
- docker compose config
|
||||
- docker compose cp
|
||||
- docker compose create
|
||||
|
@ -39,6 +40,7 @@ cname:
|
|||
clink:
|
||||
- docker_compose_attach.yaml
|
||||
- docker_compose_build.yaml
|
||||
- docker_compose_commit.yaml
|
||||
- docker_compose_config.yaml
|
||||
- docker_compose_cp.yaml
|
||||
- docker_compose_create.yaml
|
||||
|
|
|
@ -0,0 +1,76 @@
|
|||
command: docker compose commit
|
||||
short: Create a new image from a service container's changes
|
||||
long: Create a new image from a service container's changes
|
||||
usage: docker compose commit [OPTIONS] SERVICE [REPOSITORY[:TAG]]
|
||||
pname: docker compose
|
||||
plink: docker_compose.yaml
|
||||
options:
|
||||
- option: author
|
||||
shorthand: a
|
||||
value_type: string
|
||||
description: Author (e.g., "John Hannibal Smith <hannibal@a-team.com>")
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
- option: change
|
||||
shorthand: c
|
||||
value_type: list
|
||||
description: Apply Dockerfile instruction to the created image
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
- option: index
|
||||
value_type: int
|
||||
default_value: "0"
|
||||
description: index of the container if service has multiple replicas.
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
- option: message
|
||||
shorthand: m
|
||||
value_type: string
|
||||
description: Commit message
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
- option: pause
|
||||
shorthand: p
|
||||
value_type: bool
|
||||
default_value: "true"
|
||||
description: Pause container during commit
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
inherited_options:
|
||||
- option: dry-run
|
||||
value_type: bool
|
||||
default_value: "false"
|
||||
description: Execute command in dry run mode
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
|
|
@ -23,6 +23,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/compose-spec/compose-go/v2/types"
|
||||
"github.com/docker/cli/opts"
|
||||
"github.com/docker/compose/v2/pkg/utils"
|
||||
)
|
||||
|
||||
|
@ -92,6 +93,8 @@ type Service interface {
|
|||
Scale(ctx context.Context, project *types.Project, options ScaleOptions) error
|
||||
// Export a service container's filesystem as a tar archive
|
||||
Export(ctx context.Context, projectName string, options ExportOptions) error
|
||||
// Create a new image from a service container's changes
|
||||
Commit(ctx context.Context, projectName string, options CommitOptions) error
|
||||
// Generate generates a Compose Project from existing containers
|
||||
Generate(ctx context.Context, options GenerateOptions) (*types.Project, error)
|
||||
}
|
||||
|
@ -565,6 +568,19 @@ type ExportOptions struct {
|
|||
Output string
|
||||
}
|
||||
|
||||
// CommitOptions group options of the Commit API
|
||||
type CommitOptions struct {
|
||||
Service string
|
||||
Reference string
|
||||
|
||||
Pause bool
|
||||
Comment string
|
||||
Author string
|
||||
Changes opts.ListOpts
|
||||
|
||||
Index int
|
||||
}
|
||||
|
||||
type GenerateOptions struct {
|
||||
// ProjectName to set in the Compose file
|
||||
ProjectName string
|
||||
|
|
|
@ -0,0 +1,87 @@
|
|||
/*
|
||||
Copyright 2020 Docker Compose CLI 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 compose
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
"github.com/docker/compose/v2/pkg/progress"
|
||||
containerType "github.com/docker/docker/api/types/container"
|
||||
)
|
||||
|
||||
func (s *composeService) Commit(ctx context.Context, projectName string, options api.CommitOptions) error {
|
||||
return progress.RunWithTitle(ctx, func(ctx context.Context) error {
|
||||
return s.commit(ctx, projectName, options)
|
||||
}, s.stdinfo(), "Committing")
|
||||
}
|
||||
|
||||
func (s *composeService) commit(ctx context.Context, projectName string, options api.CommitOptions) error {
|
||||
projectName = strings.ToLower(projectName)
|
||||
|
||||
container, err := s.getSpecifiedContainer(ctx, projectName, oneOffInclude, false, options.Service, options.Index)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
clnt := s.dockerCli.Client()
|
||||
|
||||
w := progress.ContextWriter(ctx)
|
||||
|
||||
name := getCanonicalContainerName(container)
|
||||
msg := fmt.Sprintf("Commit %s", name)
|
||||
|
||||
w.Event(progress.Event{
|
||||
ID: name,
|
||||
Text: msg,
|
||||
Status: progress.Working,
|
||||
StatusText: "Committing",
|
||||
})
|
||||
|
||||
if s.dryRun {
|
||||
w.Event(progress.Event{
|
||||
ID: name,
|
||||
Text: msg,
|
||||
Status: progress.Done,
|
||||
StatusText: "Committed",
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
response, err := clnt.ContainerCommit(ctx, container.ID, containerType.CommitOptions{
|
||||
Reference: options.Reference,
|
||||
Comment: options.Comment,
|
||||
Author: options.Author,
|
||||
Changes: options.Changes.GetAll(),
|
||||
Pause: options.Pause,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
w.Event(progress.Event{
|
||||
ID: name,
|
||||
Text: msg,
|
||||
Status: progress.Done,
|
||||
StatusText: fmt.Sprintf("Committed as %s", response.ID),
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,93 @@
|
|||
/*
|
||||
Copyright 2023 Docker Compose CLI 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 e2e
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestCommit(t *testing.T) {
|
||||
const projectName = "e2e-commit-service"
|
||||
c := NewParallelCLI(t)
|
||||
|
||||
cleanup := func() {
|
||||
c.RunDockerComposeCmd(t, "--project-name", projectName, "down", "--timeout=0", "--remove-orphans")
|
||||
}
|
||||
t.Cleanup(cleanup)
|
||||
cleanup()
|
||||
|
||||
c.RunDockerComposeCmd(t, "-f", "./fixtures/commit/compose.yaml", "--project-name", projectName, "up", "-d", "service")
|
||||
|
||||
c.RunDockerComposeCmd(
|
||||
t,
|
||||
"--project-name",
|
||||
projectName,
|
||||
"commit",
|
||||
"-a",
|
||||
"\"John Hannibal Smith <hannibal@a-team.com>\"",
|
||||
"-c",
|
||||
"\"ENV DEBUG=true\"",
|
||||
"-m",
|
||||
"\"sample commit\"",
|
||||
"service",
|
||||
"service:latest",
|
||||
)
|
||||
}
|
||||
|
||||
func TestCommitWithReplicas(t *testing.T) {
|
||||
const projectName = "e2e-commit-service-with-replicas"
|
||||
c := NewParallelCLI(t)
|
||||
|
||||
cleanup := func() {
|
||||
c.RunDockerComposeCmd(t, "--project-name", projectName, "down", "--timeout=0", "--remove-orphans")
|
||||
}
|
||||
t.Cleanup(cleanup)
|
||||
cleanup()
|
||||
|
||||
c.RunDockerComposeCmd(t, "-f", "./fixtures/commit/compose.yaml", "--project-name", projectName, "up", "-d", "service-with-replicas")
|
||||
|
||||
c.RunDockerComposeCmd(
|
||||
t,
|
||||
"--project-name",
|
||||
projectName,
|
||||
"commit",
|
||||
"-a",
|
||||
"\"John Hannibal Smith <hannibal@a-team.com>\"",
|
||||
"-c",
|
||||
"\"ENV DEBUG=true\"",
|
||||
"-m",
|
||||
"\"sample commit\"",
|
||||
"--index=1",
|
||||
"service-with-replicas",
|
||||
"service-with-replicas:1",
|
||||
)
|
||||
c.RunDockerComposeCmd(
|
||||
t,
|
||||
"--project-name",
|
||||
projectName,
|
||||
"commit",
|
||||
"-a",
|
||||
"\"John Hannibal Smith <hannibal@a-team.com>\"",
|
||||
"-c",
|
||||
"\"ENV DEBUG=true\"",
|
||||
"-m",
|
||||
"\"sample commit\"",
|
||||
"--index=2",
|
||||
"service-with-replicas",
|
||||
"service-with-replicas:2",
|
||||
)
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
services:
|
||||
service:
|
||||
image: alpine
|
||||
command: sleep infinity
|
||||
service-with-replicas:
|
||||
image: alpine
|
||||
command: sleep infinity
|
||||
deploy:
|
||||
replicas: 3
|
|
@ -69,6 +69,20 @@ func (mr *MockServiceMockRecorder) Build(ctx, project, options any) *gomock.Call
|
|||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Build", reflect.TypeOf((*MockService)(nil).Build), ctx, project, options)
|
||||
}
|
||||
|
||||
// Commit mocks base method.
|
||||
func (m *MockService) Commit(ctx context.Context, projectName string, options api.CommitOptions) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Commit", ctx, projectName, options)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// Commit indicates an expected call of Commit.
|
||||
func (mr *MockServiceMockRecorder) Commit(ctx, projectName, options any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Commit", reflect.TypeOf((*MockService)(nil).Commit), ctx, projectName, options)
|
||||
}
|
||||
|
||||
// Copy mocks base method.
|
||||
func (m *MockService) Copy(ctx context.Context, projectName string, options api.CopyOptions) error {
|
||||
m.ctrl.T.Helper()
|
||||
|
|
Loading…
Reference in New Issue