Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Docker-connection #13

Merged
merged 6 commits into from
Jun 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
File renamed without changes.
10 changes: 8 additions & 2 deletions config.local.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,15 @@
jobs:
- name: Test Job
tasks:
- command: ip a -pt
retries: 5
- command: php -v
retry-delay: 5s
connections:
- image: docker-mysql-database-phpmyadmin
volumes:
- "/home/motalleb/Downloads:/var/local/test"
env:
SHELL: /bin/bash

schedulers:
- on-init: true
- interval: 10m10s
7 changes: 6 additions & 1 deletion config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,11 @@ type (
OnFail []Task `mapstructure:"on-fail" json:"on_fail,omitempty"`
}
TaskConnection struct {
Local bool
Local bool `mapstructure:"local" json:"local,omitempty"`
DockerConnection string `mapstructure:"docker" json:"docker,omitempty"`
ContainerName string `mapstructure:"container" json:"container,omitempty"`
ImageName string `mapstructure:"image" json:"image,omitempty"`
Volumes []string `mapstructure:"volumes" json:"volumes,omitempty"`
Networks []string `mapstructure:"networks" json:"networks,omitempty"`
}
)
2 changes: 1 addition & 1 deletion config/validators.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@
return nil
}

func (t *Task) Validate(log *logrus.Entry) error {

Check failure on line 56 in config/validators.go

View workflow job for this annotation

GitHub Actions / analyze (go)

cyclomatic complexity 18 of func `(*Task).Validate` is high (> 15) (gocyclo)
actions := []bool{
t.Get != "",
t.Command != "",
Expand All @@ -74,7 +74,7 @@
)
}
if err := credential.Validate(log, t.UserName, t.GroupName); err != nil {
return err
log.WithError(err).Warn("Be careful when using credentials, in local mode you cant use credentials unless running as root")
}
if t.Command != "" && (t.Data != nil || t.Headers != nil) {
return fmt.Errorf("command cannot have data or headers field, violating command: `%s`", t.Command)
Expand Down
17 changes: 13 additions & 4 deletions core/cmd_connection/compiler.go
Original file line number Diff line number Diff line change
@@ -1,18 +1,27 @@
// Package connection
package connection

import (
"log"

"github.com/sirupsen/logrus"

"github.com/FMotalleb/crontab-go/abstraction"
"github.com/FMotalleb/crontab-go/config"
)

// CompileConnection compiles the task connection based on the provided configuration and logger.
// It returns an abstraction.CmdConnection interface based on the type of connection specified in the configuration.
// If the connection type is not recognized or invalid, it logs a fatal error and returns nil.
func CompileConnection(conn *config.TaskConnection, logger *logrus.Entry) abstraction.CmdConnection {
if conn.Local {
logger.Warn(conn)
switch {
case conn.Local:
return NewLocalCMDConn(logger)
case conn.ContainerName != "" && conn.ImageName == "":
return NewDockerAttachConnection(logger, conn)
case conn.ImageName != "":
return NewDockerCreateConnection(logger, conn)
}
log.Fatalln("cannot compile given taskConnection", conn)

logger.WithField("taskConnection", conn).Error("cannot compile given taskConnection")
Comment on lines +11 to +25
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tip

Codebase Verification

Tests for the different connection types handled by CompileConnection are missing. Please add test cases to ensure the robustness of this function.

  • Ensure that CompileConnection is tested for local connections.
  • Ensure that CompileConnection is tested for Docker attach connections.
  • Ensure that CompileConnection is tested for Docker create connections.
Analysis chain

The CompileConnection function now handles different connection types and logs errors appropriately. Ensure that all new connection types are thoroughly tested.

Scripts executed

The following scripts were executed for the analysis:


Script:

#!/bin/bash
# Description: Verify the handling of different connection types.

# Test: Search for the function usage. Expect: Correct handling of connection types.
rg --type go $'CompileConnection'

Length of output: 374



Script:

#!/bin/bash
# Description: Verify the handling of different connection types in tests.

# Test: Search for test cases that cover the different connection types handled by CompileConnection.
rg --type go 'CompileConnection' --glob '*_test.go'

Length of output: 200



Script:

#!/bin/bash
# Description: List all test files in the repository and search for CompileConnection usage.

# Step 1: List all test files in the repository.
echo "Listing all test files:"
fd --type f --glob '*_test.go'

# Step 2: Search for CompileConnection usage within test files.
echo "Searching for CompileConnection usage in test files:"
fd --type f --glob '*_test.go' --exec rg 'CompileConnection' {}

Length of output: 270

Tools
golangci-lint

23-23: field imageName is unused (unused)


23-23: field cancel is unused (unused)


18-18: ST1005: error strings should not be capitalized (stylecheck)


19-19: ST1020: comment on exported method Cancel should be of the form "Cancel ..." (stylecheck)

return nil
}
125 changes: 125 additions & 0 deletions core/cmd_connection/docker_attach.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
package connection

import (
"bytes"
"context"
"io"

"github.com/docker/docker/api/types"
"github.com/docker/docker/client"
"github.com/sirupsen/logrus"

"github.com/FMotalleb/crontab-go/abstraction"
"github.com/FMotalleb/crontab-go/config"
)

type DockerAttachConnection struct {
conn *config.TaskConnection
log *logrus.Entry
cli *client.Client
execCFG *types.ExecConfig
containerID string
ctx context.Context
}

// NewDockerAttachConnection creates a new DockerAttachConnection instance.
// It initializes the connection configuration and logging fields.
// Parameters:
// - log: A logrus.Entry instance for logging purposes.
// - conn: A TaskConnection instance containing the connection configuration.
// Returns:
// - A new instance of DockerAttachConnection implementing the CmdConnection interface.
func NewDockerAttachConnection(log *logrus.Entry, conn *config.TaskConnection) abstraction.CmdConnection {
return &DockerAttachConnection{
conn: conn,
log: log.WithFields(
logrus.Fields{
"connection": "docker",
"docker-mode": "attach",
},
),
}
}

// Prepare sets up the DockerAttachConnection for executing a task.
// It reshapes the environment variables, sets the context, and creates an exec configuration.
// Parameters:
// - ctx: A context.Context instance for managing the request lifetime.
// - task: A Task instance containing the task configuration.
// Returns:
// - An error if the preparation fails, otherwise nil.
func (d *DockerAttachConnection) Prepare(ctx context.Context, task *config.Task) error {
shell, shellArgs, env := reshapeEnviron(task, d.log)
d.ctx = ctx
// Specify the container ID or name
d.containerID = d.conn.ContainerName
if d.conn.DockerConnection == "" {
d.log.Debug("No explicit docker connection specified, using default: `unix:///var/run/docker.sock`")
d.conn.DockerConnection = "unix:///var/run/docker.sock"
}
cmd := append(
[]string{shell},
append(shellArgs, task.Command)...,
)
// Create an exec configuration
d.execCFG = &types.ExecConfig{
AttachStdout: true,
AttachStderr: true,
Privileged: true,
Env: env,
WorkingDir: task.WorkingDirectory,
User: task.UserName,
Cmd: cmd,
}
return nil
}
Comment on lines +44 to +75
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Prepare method sets up the Docker execution environment correctly. However, consider handling potential errors when setting the Docker connection string.

- d.conn.DockerConnection = "unix:///var/run/docker.sock"
+ if d.conn.DockerConnection == "" {
+   d.log.Debug("No explicit docker connection specified, using default: `unix:///var/run/docker.sock`")
+   d.conn.DockerConnection = "unix:///var/run/docker.sock"
+ }
Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Prepare sets up the DockerAttachConnection for executing a task.
// It reshapes the environment variables, sets the context, and creates an exec configuration.
// Parameters:
// - ctx: A context.Context instance for managing the request lifetime.
// - task: A Task instance containing the task configuration.
// Returns:
// - An error if the preparation fails, otherwise nil.
func (d *DockerAttachConnection) Prepare(ctx context.Context, task *config.Task) error {
shell, shellArgs, env := reshapeEnviron(task, d.log)
d.ctx = ctx
// Specify the container ID or name
d.containerID = d.conn.ContainerName
if d.conn.DockerConnection == "" {
d.log.Debug("No explicit docker connection specified, using default: `unix:///var/run/docker.sock`")
d.conn.DockerConnection = "unix:///var/run/docker.sock"
}
cmd := append(
[]string{shell},
append(shellArgs, task.Command)...,
)
// Create an exec configuration
d.execCFG = &types.ExecConfig{
AttachStdout: true,
AttachStderr: true,
Privileged: true,
Env: env,
WorkingDir: task.WorkingDirectory,
User: task.UserName,
Cmd: cmd,
}
return nil
}
// Prepare sets up the DockerAttachConnection for executing a task.
// It reshapes the environment variables, sets the context, and creates an exec configuration.
// Parameters:
// - ctx: A context.Context instance for managing the request lifetime.
// - task: A Task instance containing the task configuration.
// Returns:
// - An error if the preparation fails, otherwise nil.
func (d *DockerAttachConnection) Prepare(ctx context.Context, task *config.Task) error {
shell, shellArgs, env := reshapeEnviron(task, d.log)
d.ctx = ctx
// Specify the container ID or name
d.containerID = d.conn.ContainerName
if d.conn.DockerConnection == "" {
d.log.Debug("No explicit docker connection specified, using default: `unix:///var/run/docker.sock`")
d.conn.DockerConnection = "unix:///var/run/docker.sock"
}
cmd := append(
[]string{shell},
append(shellArgs, task.Command)...,
)
// Create an exec configuration
d.execCFG = &types.ExecConfig{
AttachStdout: true,
AttachStderr: true,
Privileged: true,
Env: env,
WorkingDir: task.WorkingDirectory,
User: task.UserName,
Cmd: cmd,
}
return nil
}
Tools
golangci-lint

70-70: Error return value of connection.Disconnect is not checked (errcheck)


56-56: cyclomatic complexity 18 of func (*Task).Validate is high (> 15) (gocyclo)


73-73: File is not gofumpt-ed (gofumpt)


62-62: response body must be closed (bodyclose)


45-45: ST1020: comment on exported method Cancel should be of the form "Cancel ..." (stylecheck)


// Connect establishes a connection to the Docker daemon.
// It initializes the Docker client with the specified connection settings.
// Returns:
// - An error if the connection fails, otherwise nil.
func (d *DockerAttachConnection) Connect() error {
cli, err := client.NewClientWithOpts(
client.WithHost(d.conn.DockerConnection),
)
if err != nil {
return err
}
d.cli = cli
return nil
}

// Execute runs the command in the Docker container and captures the output.
// It creates an exec instance, attaches to it, and reads the command output.
// Returns:
// - A byte slice containing the command output.
// - An error if the execution fails, otherwise nil.
func (d *DockerAttachConnection) Execute() ([]byte, error) {
// Create the exec instance
exec, err := d.cli.ContainerExecCreate(d.ctx, d.containerID, *d.execCFG)
if err != nil {
return nil, err
}

// Attach to the exec instance
resp, err := d.cli.ContainerExecAttach(d.ctx, exec.ID, types.ExecStartCheck{})
if err != nil {
return nil, err
}
defer resp.Close()

writer := bytes.NewBuffer([]byte{})
// Print the command output
_, err = io.Copy(writer, resp.Reader)
if err != nil {
return nil, err
}
return writer.Bytes(), nil
}
Comment on lines +92 to +118
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Execute method correctly handles command execution within Docker. Ensure that all resources are properly managed, especially with defer resp.Close().

- defer resp.Close()
+ defer func() {
+   if err := resp.Close(); err != nil {
+     d.log.Error("Failed to close response: ", err)
+   }
+ }()
Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Execute runs the command in the Docker container and captures the output.
// It creates an exec instance, attaches to it, and reads the command output.
// Returns:
// - A byte slice containing the command output.
// - An error if the execution fails, otherwise nil.
func (d *DockerAttachConnection) Execute() ([]byte, error) {
// Create the exec instance
exec, err := d.cli.ContainerExecCreate(d.ctx, d.containerID, *d.execCFG)
if err != nil {
return nil, err
}
// Attach to the exec instance
resp, err := d.cli.ContainerExecAttach(d.ctx, exec.ID, types.ExecStartCheck{})
if err != nil {
return nil, err
}
defer resp.Close()
writer := bytes.NewBuffer([]byte{})
// Print the command output
_, err = io.Copy(writer, resp.Reader)
if err != nil {
return nil, err
}
return writer.Bytes(), nil
}
// Attach to the exec instance
resp, err := d.cli.ContainerExecAttach(d.ctx, exec.ID, types.ExecStartCheck{})
if err != nil {
return nil, err
}
defer func() {
if err := resp.Close(); err != nil {
d.log.Error("Failed to close response: ", err)
}
}()
writer := bytes.NewBuffer([]byte{})
// Print the command output
_, err = io.Copy(writer, resp.Reader)
if err != nil {
return nil, err
}
return writer.Bytes(), nil


// Disconnect closes the connection to the Docker daemon.
// Returns:
// - An error if the disconnection fails, otherwise nil.
func (d *DockerAttachConnection) Disconnect() error {
return d.cli.Close()
}
180 changes: 180 additions & 0 deletions core/cmd_connection/docker_create.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
package connection

import (
"bytes"
"context"
"io"
"strings"

"github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/network"
"github.com/docker/docker/client"
"github.com/sirupsen/logrus"

"github.com/FMotalleb/crontab-go/abstraction"
"github.com/FMotalleb/crontab-go/config"
)

// DockerCreateConnection is a struct that manages the creation and execution of Docker containers.
type DockerCreateConnection struct {
conn *config.TaskConnection
log *logrus.Entry
cli *client.Client
imageName string

Check failure on line 23 in core/cmd_connection/docker_create.go

View workflow job for this annotation

GitHub Actions / analyze (go)

field `imageName` is unused (unused)
containerConfig *container.Config
hostConfig *container.HostConfig
networkConfig *network.NetworkingConfig
ctx context.Context
}

// NewDockerCreateConnection initializes a new DockerCreateConnection instance.
// Parameters:
// - log: A logrus.Entry instance for logging.
// - conn: A TaskConnection instance containing the connection configuration.
// Returns:
// - A new instance of DockerCreateConnection.
func NewDockerCreateConnection(log *logrus.Entry, conn *config.TaskConnection) abstraction.CmdConnection {
return &DockerCreateConnection{
conn: conn,
log: log.WithFields(
logrus.Fields{
"connection": "docker",
"docker-mode": "create",
},
),
}
}

// Prepare sets up the Docker container configuration based on the provided task.
// Parameters:
// - ctx: A context.Context instance for managing the lifecycle of the container.
// - task: A Task instance containing the task configuration.
// Returns:
// - An error if the preparation fails, otherwise nil.
func (d *DockerCreateConnection) Prepare(ctx context.Context, task *config.Task) error {
shell, shellArgs, env := reshapeEnviron(task, d.log)
d.ctx = ctx
if d.conn.DockerConnection == "" {
d.log.Debug("No explicit docker connection specified, using default: `unix:///var/run/docker.sock`")
d.conn.DockerConnection = "unix:///var/run/docker.sock"
}
cmd := append(
[]string{shell},
append(shellArgs, task.Command)...,
)
volumes := make(map[string]struct{})
for _, volume := range d.conn.Volumes {
inContainer := strings.Split(volume, ":")[1]
volumes[inContainer] = struct{}{}
}
// Create an exec configuration
d.containerConfig = &container.Config{
AttachStdout: true,
AttachStderr: true,
Env: env,
WorkingDir: task.WorkingDirectory,
User: task.UserName,
Cmd: cmd,
Image: d.conn.ImageName,
Volumes: volumes,
Entrypoint: []string{},
Shell: []string{},
}
d.hostConfig = &container.HostConfig{
Binds: d.conn.Volumes,
// AutoRemove: true,
}
endpointsConfig := make(map[string]*network.EndpointSettings)
for _, networkName := range d.conn.Networks {
endpointsConfig[networkName] = &network.EndpointSettings{}
}
d.networkConfig = &network.NetworkingConfig{
EndpointsConfig: endpointsConfig,
}
return nil
}
Comment on lines +48 to +95
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Prepare method sets up the Docker container configuration correctly. Consider adding error handling for potential issues during setup.

- return nil
+ if someConditionFails {
+   return fmt.Errorf("failed to prepare Docker configuration")
+ }
+ return nil

Committable suggestion was skipped due to low confidence.

Tools
golangci-lint

70-70: Error return value of connection.Disconnect is not checked (errcheck)


56-56: cyclomatic complexity 18 of func (*Task).Validate is high (> 15) (gocyclo)


73-73: File is not gofumpt-ed (gofumpt)


62-62: response body must be closed (bodyclose)


80-80: response body must be closed (bodyclose)


// Connect establishes a connection to the Docker daemon.
// Returns:
// - An error if the connection fails, otherwise nil.
func (d *DockerCreateConnection) Connect() error {
cli, err := client.NewClientWithOpts(
client.WithHost(d.conn.DockerConnection),
)
if err != nil {
return err
}
d.cli = cli
return nil
}

// Execute creates, starts, and logs the output of the Docker container.
// Returns:
// - A byte slice containing the command output.
// - An error if the execution fails, otherwise nil.
func (d *DockerCreateConnection) Execute() ([]byte, error) {
ctx := d.ctx
// Create the exec instance
exec, err := d.cli.ContainerCreate(
ctx,
d.containerConfig,
d.hostConfig,
d.networkConfig,
nil,
d.conn.ContainerName,
)
d.log.Debugf("container created: %v, warnings: %v", exec, exec.Warnings)
if err != nil {
return nil, err
}
defer d.cli.ContainerRemove(ctx, exec.ID,

Check failure on line 130 in core/cmd_connection/docker_create.go

View workflow job for this annotation

GitHub Actions / analyze (go)

Error return value of `d.cli.ContainerRemove` is not checked (errcheck)
container.RemoveOptions{
Force: true,
},
)
err = d.cli.ContainerStart(d.log.Context, exec.ID,
container.StartOptions{},
)
d.log.Debugf("container started: %v", exec)
if err != nil {
return nil, err
}
starting := true
for starting {
_, err := d.cli.ContainerStats(ctx, exec.ID, false)
if err == nil {
starting = false
}
}
d.log.Debugf("container ready to attach: %v", exec)
// Attach to the exec instance
resp, err := d.cli.ContainerLogs(
ctx,
exec.ID,
container.LogsOptions{
ShowStdout: true,
ShowStderr: true,
Follow: false,
Details: true,
},
)
if err != nil {
return nil, err
}
defer resp.Close()

Check failure on line 164 in core/cmd_connection/docker_create.go

View workflow job for this annotation

GitHub Actions / analyze (go)

Error return value of `resp.Close` is not checked (errcheck)

writer := bytes.NewBuffer([]byte{})
// Print the command output
_, err = io.Copy(writer, resp)
if err != nil {
return writer.Bytes(), err
}
return writer.Bytes(), nil
}
Comment on lines +111 to +173
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Execute method correctly handles the creation, start, and logging of the Docker container. Ensure that all resources are properly managed, especially with defer resp.Close().

- defer resp.Close()
+ defer func() {
+   if err := resp.Close(); err != nil {
+     d.log.Error("Failed to close response: ", err)
+   }
+ }()
Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Execute creates, starts, and logs the output of the Docker container.
// Returns:
// - A byte slice containing the command output.
// - An error if the execution fails, otherwise nil.
func (d *DockerCreateConnection) Execute() ([]byte, error) {
ctx := d.ctx
// Create the exec instance
exec, err := d.cli.ContainerCreate(
ctx,
d.containerConfig,
d.hostConfig,
d.networkConfig,
nil,
d.conn.ContainerName,
)
d.log.Debugf("container created: %v, warnings: %v", exec, exec.Warnings)
if err != nil {
return nil, err
}
defer d.cli.ContainerRemove(ctx, exec.ID,
container.RemoveOptions{
Force: true,
},
)
err = d.cli.ContainerStart(d.log.Context, exec.ID,
container.StartOptions{},
)
d.log.Debugf("container started: %v", exec)
if err != nil {
return nil, err
}
starting := true
for starting {
_, err := d.cli.ContainerStats(ctx, exec.ID, false)
if err == nil {
starting = false
}
}
d.log.Debugf("container ready to attach: %v", exec)
// Attach to the exec instance
resp, err := d.cli.ContainerLogs(
ctx,
exec.ID,
container.LogsOptions{
ShowStdout: true,
ShowStderr: true,
Follow: false,
Details: true,
},
)
if err != nil {
return nil, err
}
defer resp.Close()
writer := bytes.NewBuffer([]byte{})
// Print the command output
_, err = io.Copy(writer, resp)
if err != nil {
return writer.Bytes(), err
}
return writer.Bytes(), nil
}
// Execute creates, starts, and logs the output of the Docker container.
// Returns:
// - A byte slice containing the command output.
// - An error if the execution fails, otherwise nil.
func (d *DockerCreateConnection) Execute() ([]byte, error) {
ctx := d.ctx
// Create the exec instance
exec, err := d.cli.ContainerCreate(
ctx,
d.containerConfig,
d.hostConfig,
d.networkConfig,
nil,
d.conn.ContainerName,
)
d.log.Debugf("container created: %v, warnings: %v", exec, exec.Warnings)
if err != nil {
return nil, err
}
defer d.cli.ContainerRemove(ctx, exec.ID,
container.RemoveOptions{
Force: true,
},
)
err = d.cli.ContainerStart(d.log.Context, exec.ID,
container.StartOptions{},
)
d.log.Debugf("container started: %v", exec)
if err != nil {
return nil, err
}
starting := true
for starting {
_, err := d.cli.ContainerStats(ctx, exec.ID, false)
if err == nil {
starting = false
}
}
d.log.Debugf("container ready to attach: %v", exec)
// Attach to the exec instance
resp, err := d.cli.ContainerLogs(
ctx,
exec.ID,
container.LogsOptions{
ShowStdout: true,
ShowStderr: true,
Follow: false,
Details: true,
},
)
if err != nil {
return nil, err
}
defer func() {
if err := resp.Close(); err != nil {
d.log.Error("Failed to close response: ", err)
}
}()
writer := bytes.NewBuffer([]byte{})
// Print the command output
_, err = io.Copy(writer, resp)
if err != nil {
return writer.Bytes(), err
}
return writer.Bytes(), nil
}
Tools
golangci-lint

130-130: Error return value of d.cli.ContainerRemove is not checked (errcheck)


164-164: Error return value of resp.Close is not checked (errcheck)


128-128: S1002: should omit comparison to bool constant, can be simplified to s.OnInit (gosimple)

GitHub Check: analyze (go)

[failure] 130-130:
Error return value of d.cli.ContainerRemove is not checked (errcheck)


[failure] 164-164:
Error return value of resp.Close is not checked (errcheck)


// Disconnect closes the connection to the Docker daemon.
// Returns:
// - An error if the disconnection fails, otherwise nil.
func (d *DockerCreateConnection) Disconnect() error {
return d.cli.Close()
}
Loading
Loading