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),
|
execCommand(&opts, dockerCli, backend),
|
||||||
attachCommand(&opts, dockerCli, backend),
|
attachCommand(&opts, dockerCli, backend),
|
||||||
exportCommand(&opts, dockerCli, backend),
|
exportCommand(&opts, dockerCli, backend),
|
||||||
|
commitCommand(&opts, dockerCli, backend),
|
||||||
pauseCommand(&opts, dockerCli, backend),
|
pauseCommand(&opts, dockerCli, backend),
|
||||||
unpauseCommand(&opts, dockerCli, backend),
|
unpauseCommand(&opts, dockerCli, backend),
|
||||||
topCommand(&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 |
|
| [`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 |
|
| [`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 |
|
| [`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 |
|
| [`cp`](compose_cp.md) | Copy files/folders between a service container and the local filesystem |
|
||||||
| [`create`](compose_create.md) | Creates containers for a service |
|
| [`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:
|
cname:
|
||||||
- docker compose attach
|
- docker compose attach
|
||||||
- docker compose build
|
- docker compose build
|
||||||
|
- docker compose commit
|
||||||
- docker compose config
|
- docker compose config
|
||||||
- docker compose cp
|
- docker compose cp
|
||||||
- docker compose create
|
- docker compose create
|
||||||
|
@ -39,6 +40,7 @@ cname:
|
||||||
clink:
|
clink:
|
||||||
- docker_compose_attach.yaml
|
- docker_compose_attach.yaml
|
||||||
- docker_compose_build.yaml
|
- docker_compose_build.yaml
|
||||||
|
- docker_compose_commit.yaml
|
||||||
- docker_compose_config.yaml
|
- docker_compose_config.yaml
|
||||||
- docker_compose_cp.yaml
|
- docker_compose_cp.yaml
|
||||||
- docker_compose_create.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"
|
"time"
|
||||||
|
|
||||||
"github.com/compose-spec/compose-go/v2/types"
|
"github.com/compose-spec/compose-go/v2/types"
|
||||||
|
"github.com/docker/cli/opts"
|
||||||
"github.com/docker/compose/v2/pkg/utils"
|
"github.com/docker/compose/v2/pkg/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -92,6 +93,8 @@ type Service interface {
|
||||||
Scale(ctx context.Context, project *types.Project, options ScaleOptions) error
|
Scale(ctx context.Context, project *types.Project, options ScaleOptions) error
|
||||||
// Export a service container's filesystem as a tar archive
|
// Export a service container's filesystem as a tar archive
|
||||||
Export(ctx context.Context, projectName string, options ExportOptions) error
|
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 generates a Compose Project from existing containers
|
||||||
Generate(ctx context.Context, options GenerateOptions) (*types.Project, error)
|
Generate(ctx context.Context, options GenerateOptions) (*types.Project, error)
|
||||||
}
|
}
|
||||||
|
@ -565,6 +568,19 @@ type ExportOptions struct {
|
||||||
Output string
|
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 {
|
type GenerateOptions struct {
|
||||||
// ProjectName to set in the Compose file
|
// ProjectName to set in the Compose file
|
||||||
ProjectName string
|
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)
|
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.
|
// Copy mocks base method.
|
||||||
func (m *MockService) Copy(ctx context.Context, projectName string, options api.CopyOptions) error {
|
func (m *MockService) Copy(ctx context.Context, projectName string, options api.CopyOptions) error {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
|
|
Loading…
Reference in New Issue