generated from golang-templates/seed
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #13 from FMotalleb/docker-connection
feat: Docker-connection
- Loading branch information
Showing
14 changed files
with
601 additions
and
69 deletions.
There are no files selected for viewing
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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") | ||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} | ||
|
||
// 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 | ||
} | ||
|
||
// 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() | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
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 | ||
} | ||
|
||
// 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, | ||
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 | ||
} | ||
|
||
// 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() | ||
} |
Oops, something went wrong.