From 2d2086cbd22d89de6aab9c5a679846f1444dc994 Mon Sep 17 00:00:00 2001 From: niqdev Date: Tue, 19 Sep 2023 22:08:14 +0100 Subject: [PATCH] update docker task --- internal/command/task/task.go | 6 ++-- pkg/box/docker/docker.go | 20 ++++++++----- pkg/box/docker/events.go | 8 ++--- pkg/box/model/box_test.go | 7 ++--- pkg/client/docker/builder.go | 19 ++++++++---- pkg/client/docker/builder_test.go | 28 +++++++++++++----- pkg/client/docker/options.go | 20 ++++++++----- pkg/task/docker/docker.go | 49 +++++++++++++++---------------- pkg/task/docker/events.go | 38 ++++++++++++++++++++++++ pkg/task/model/task.go | 5 ++++ pkg/task/model/task_test.go | 36 +++++++++++++++++++++++ 11 files changed, 171 insertions(+), 65 deletions(-) diff --git a/internal/command/task/task.go b/internal/command/task/task.go index 103b419b..a91381ad 100644 --- a/internal/command/task/task.go +++ b/internal/command/task/task.go @@ -113,19 +113,17 @@ func (opts *taskCmdOptions) runTask(sourceLoader template.SourceLoader[taskModel loader.Start("loading template %s", info.Value.Data.Name) // TODO review template name e.g task/name (lowercase) defer loader.Stop() + log.Info().Msgf("loading template: provider=%s name=%s\n%s", opts.provider, info.Value.Data.Name, info.Value.Data.Pretty()) + taskClient, err := newDefaultTaskClient(opts.provider, opts.configRef, loader) if err != nil { return err } - // TODO --command and --input - // TODO opts.networkVpnFlag - var arguments []string if opts.commandFlag.Inline { arguments = inlineArguments } else { - // TODO expand/merge values arguments = info.Value.Data.DefaultCommandArguments() } diff --git a/pkg/box/docker/docker.go b/pkg/box/docker/docker.go index 31a171b7..608b1169 100644 --- a/pkg/box/docker/docker.go +++ b/pkg/box/docker/docker.go @@ -83,13 +83,13 @@ func (box *DockerBoxClient) createBox(opts *boxModel.CreateOptions) (*boxModel.B } containerConfig, err := docker.BuildContainerConfig(&docker.ContainerConfigOpts{ - ImageName: imageName, - ContainerName: containerName, - Env: containerEnv, - Ports: containerPorts, - Labels: opts.Labels, - Tty: true, // always - Cmd: []string{}, + ImageName: imageName, + Hostname: containerName, + Env: containerEnv, + Ports: containerPorts, + Labels: opts.Labels, + Tty: true, // always + Cmd: []string{}, }) if err != nil { return nil, err @@ -98,7 +98,11 @@ func (box *DockerBoxClient) createBox(opts *boxModel.CreateOptions) (*boxModel.B onPortBindCallback := func(port docker.ContainerPort) { box.publishPortInfo(networkMap, containerName, port) } - hostConfig, err := docker.BuildHostConfig(containerPorts, onPortBindCallback) + hostConfig, err := docker.BuildHostConfig(&docker.ContainerHostConfigOpts{ + NetworkMode: docker.DefaultNetworkMode(), + Ports: containerPorts, + OnPortBindCallback: onPortBindCallback, + }) if err != nil { return nil, err } diff --git a/pkg/box/docker/events.go b/pkg/box/docker/events.go index 2c31385f..e8e033a1 100644 --- a/pkg/box/docker/events.go +++ b/pkg/box/docker/events.go @@ -76,14 +76,14 @@ func newContainerCreateEnvDockerConsoleEvent(containerName string, env model.Box return &dockerBoxEvent{kind: event.PrintConsole, value: fmt.Sprintf("[%s] %s=%s", containerName, env.Key, env.Value)} } -func newContainerCreateDockerEvent(templateName string, containerName string, containerId string) *dockerBoxEvent { - return &dockerBoxEvent{kind: event.LogInfo, value: fmt.Sprintf("container create: templateName=%s containerName=%s containerId=%s", templateName, containerName, containerId)} -} - func newContainerCreateStatusDockerEvent(status string) *dockerBoxEvent { return &dockerBoxEvent{kind: event.LogDebug, value: status} } +func newContainerCreateDockerEvent(templateName string, containerName string, containerId string) *dockerBoxEvent { + return &dockerBoxEvent{kind: event.LogInfo, value: fmt.Sprintf("container create: templateName=%s containerName=%s containerId=%s", templateName, containerName, containerId)} +} + func newContainerRestartDockerEvent(containerId string, status string) *dockerBoxEvent { return &dockerBoxEvent{kind: event.LogInfo, value: fmt.Sprintf("container restart: containerId=%s status=%s", containerId, status)} } diff --git a/pkg/box/model/box_test.go b/pkg/box/model/box_test.go index a55edba7..c99ae6c3 100644 --- a/pkg/box/model/box_test.go +++ b/pkg/box/model/box_test.go @@ -5,16 +5,15 @@ import ( "testing" "github.com/stretchr/testify/assert" + + commonModel "github.com/hckops/hckctl/pkg/common/model" ) var testBox = &BoxV1{ Kind: "box/v1", Name: "my-name", Tags: []string{"my-tag"}, - Image: struct { - Repository string - Version string - }{ + Image: commonModel.Image{ Repository: "hckops/my-image", }, Shell: "/bin/bash", diff --git a/pkg/client/docker/builder.go b/pkg/client/docker/builder.go index 3381e9ff..f901661f 100644 --- a/pkg/client/docker/builder.go +++ b/pkg/client/docker/builder.go @@ -2,6 +2,7 @@ package docker import ( "fmt" + "strings" "github.com/pkg/errors" @@ -28,7 +29,7 @@ func BuildContainerConfig(opts *ContainerConfigOpts) (*container.Config, error) } return &container.Config{ - Hostname: opts.ContainerName, // TODO vpn conflict with NetworkMode + Hostname: opts.Hostname, Image: opts.ImageName, AttachStdin: true, AttachStdout: true, @@ -43,10 +44,10 @@ func BuildContainerConfig(opts *ContainerConfigOpts) (*container.Config, error) }, nil } -func BuildHostConfig(ports []ContainerPort, onPortBindCallback func(port ContainerPort)) (*container.HostConfig, error) { +func BuildHostConfig(opts *ContainerHostConfigOpts) (*container.HostConfig, error) { portBindings := make(nat.PortMap) - for _, port := range ports { + for _, port := range opts.Ports { localPort, err := util.FindOpenPort(port.Local) if err != nil { @@ -59,7 +60,7 @@ func BuildHostConfig(ports []ContainerPort, onPortBindCallback func(port Contain } // actual bound port - onPortBindCallback(ContainerPort{ + opts.OnPortBindCallback(ContainerPort{ Local: localPort, Remote: port.Remote, }) @@ -71,11 +72,19 @@ func BuildHostConfig(ports []ContainerPort, onPortBindCallback func(port Contain } return &container.HostConfig{ + NetworkMode: container.NetworkMode(opts.NetworkMode), PortBindings: portBindings, - //NetworkMode: "container:alpine-openvpn-???", // TODO vpn }, nil } +func DefaultNetworkMode() string { + return string(container.IsolationDefault) +} + +func ContainerNetworkMode(idOrName string) string { + return strings.Join([]string{"container", idOrName}, ":") +} + func BuildNetworkingConfig(networkName, networkId string) *network.NetworkingConfig { return &network.NetworkingConfig{EndpointsConfig: map[string]*network.EndpointSettings{networkName: {NetworkID: networkId}}} } diff --git a/pkg/client/docker/builder_test.go b/pkg/client/docker/builder_test.go index eba52e5f..43fbd7af 100644 --- a/pkg/client/docker/builder_test.go +++ b/pkg/client/docker/builder_test.go @@ -43,12 +43,12 @@ func TestBuildContainerConfig(t *testing.T) { }, } opts := &ContainerConfigOpts{ - ImageName: "myImageName", - ContainerName: "myContainerName", - Env: envs, - Ports: ports, - Tty: true, - Cmd: []string{}, + ImageName: "myImageName", + Hostname: "myContainerName", + Env: envs, + Ports: ports, + Tty: true, + Cmd: []string{}, Labels: map[string]string{ "a.b.c": "hello", "x.y.z": "world", @@ -66,16 +66,30 @@ func TestBuildHostConfig(t *testing.T) { {Local: "1024", Remote: "1024"}, } expected := &container.HostConfig{ + NetworkMode: "myNetworkMode", PortBindings: nat.PortMap{ "1024/tcp": []nat.PortBinding{{HostIP: "0.0.0.0", HostPort: "1024"}}, }, } + opts := &ContainerHostConfigOpts{ + NetworkMode: "myNetworkMode", + Ports: ports, + OnPortBindCallback: func(port ContainerPort) {}, + } - result, err := BuildHostConfig(ports, func(port ContainerPort) {}) + result, err := BuildHostConfig(opts) assert.NoError(t, err) assert.Equal(t, expected, result) } +func TestDefaultNetworkMode(t *testing.T) { + assert.Equal(t, "default", DefaultNetworkMode()) +} + +func TestContainerNetworkMode(t *testing.T) { + assert.Equal(t, "container:myIdOrName", ContainerNetworkMode("myIdOrName")) +} + func TestBuildNetworkingConfig(t *testing.T) { expected := &network.NetworkingConfig{EndpointsConfig: map[string]*network.EndpointSettings{"myNetwork": {NetworkID: "123"}}} diff --git a/pkg/client/docker/options.go b/pkg/client/docker/options.go index be918a95..da5bfac8 100644 --- a/pkg/client/docker/options.go +++ b/pkg/client/docker/options.go @@ -8,13 +8,19 @@ import ( ) type ContainerConfigOpts struct { - ImageName string - ContainerName string - Env []ContainerEnv - Ports []ContainerPort - Tty bool - Cmd []string - Labels map[string]string + ImageName string + Hostname string + Env []ContainerEnv + Ports []ContainerPort + Tty bool + Cmd []string + Labels map[string]string +} + +type ContainerHostConfigOpts struct { + NetworkMode string + Ports []ContainerPort + OnPortBindCallback func(port ContainerPort) } type ImagePullOpts struct { diff --git a/pkg/task/docker/docker.go b/pkg/task/docker/docker.go index f5b2ea79..8a586245 100644 --- a/pkg/task/docker/docker.go +++ b/pkg/task/docker/docker.go @@ -37,14 +37,14 @@ func (task *DockerTaskClient) runTask(opts *taskModel.RunOptions) error { imagePullOpts := &docker.ImagePullOpts{ ImageName: imageName, OnImagePullCallback: func() { - // TODO box.eventBus.Publish(newImagePullDockerLoaderEvent(imageName)) + task.eventBus.Publish(newImagePullDockerLoaderEvent(imageName)) }, } - // TODO box.eventBus.Publish(newImagePullDockerEvent(imageName)) + task.eventBus.Publish(newImagePullDockerEvent(imageName)) if err := task.client.ImagePull(imagePullOpts); err != nil { // try to use an existing image if exists if task.clientOpts.IgnoreImagePullError { - // TODO box.eventBus.Publish(newImagePullIgnoreDockerEvent(imageName)) + task.eventBus.Publish(newImagePullIgnoreDockerEvent(imageName)) } else { // do not allow offline return err @@ -54,11 +54,11 @@ func (task *DockerTaskClient) runTask(opts *taskModel.RunOptions) error { // cleanup obsolete nightly images imageRemoveOpts := &docker.ImageRemoveOpts{ OnImageRemoveCallback: func(imageId string) { - // TODO box.eventBus.Publish(newImageRemoveDockerEvent(imageId)) + task.eventBus.Publish(newImageRemoveDockerEvent(imageId)) }, OnImageRemoveErrorCallback: func(imageId string, err error) { // ignore error: keep images used by existing containers - // TODO box.eventBus.Publish(newImageRemoveIgnoreDockerEvent(imageId, err)) + task.eventBus.Publish(newImageRemoveIgnoreDockerEvent(imageId, err)) }, } if err := task.client.ImageRemoveDangling(imageRemoveOpts); err != nil { @@ -69,20 +69,23 @@ func (task *DockerTaskClient) runTask(opts *taskModel.RunOptions) error { containerName := opts.Template.GenerateName() containerConfig, err := docker.BuildContainerConfig(&docker.ContainerConfigOpts{ - ImageName: imageName, - ContainerName: containerName, - Env: []docker.ContainerEnv{}, - Ports: []docker.ContainerPort{}, - Labels: opts.Labels, - Tty: opts.StreamOpts.IsTty, - Cmd: opts.Arguments, + ImageName: imageName, + Hostname: "", // TODO vpn NetworkMode conflicts with Hostname containerName + Env: []docker.ContainerEnv{}, + Ports: []docker.ContainerPort{}, + Labels: opts.Labels, + Tty: opts.StreamOpts.IsTty, + Cmd: opts.Arguments, }) if err != nil { return err } - onPortBindCallback := func(port docker.ContainerPort) {} - hostConfig, err := docker.BuildHostConfig([]docker.ContainerPort{}, onPortBindCallback) + hostConfig, err := docker.BuildHostConfig(&docker.ContainerHostConfigOpts{ + NetworkMode: docker.DefaultNetworkMode(), // TODO vpn + Ports: []docker.ContainerPort{}, + OnPortBindCallback: func(docker.ContainerPort) {}, + }) if err != nil { return err } @@ -92,7 +95,7 @@ func (task *DockerTaskClient) runTask(opts *taskModel.RunOptions) error { if err != nil { return err } - // TODO box.eventBus.Publish(newNetworkUpsertDockerEvent(networkName, networkId)) + task.eventBus.Publish(newNetworkUpsertDockerEvent(networkName, networkId)) containerOpts := &docker.ContainerCreateOpts{ ContainerName: containerName, @@ -100,24 +103,18 @@ func (task *DockerTaskClient) runTask(opts *taskModel.RunOptions) error { HostConfig: hostConfig, NetworkingConfig: docker.BuildNetworkingConfig(networkName, networkId), // all on the same network WaitStatus: true, - OnContainerStatusCallback: func(s string) { - //box.eventBus.Publish(newContainerCreateStatusDockerEvent(status)) - }, - OnContainerStartCallback: func() { - //for _, e := range opts.Template.EnvironmentVariables() { - // box.eventBus.Publish(newContainerCreateEnvDockerEvent(containerName, e)) - // box.eventBus.Publish(newContainerCreateEnvDockerConsoleEvent(containerName, e)) - //} + OnContainerStatusCallback: func(status string) { + task.eventBus.Publish(newContainerCreateStatusDockerEvent(status)) }, + OnContainerStartCallback: func() {}, } // taskId containerId, err := task.client.ContainerCreate(containerOpts) if err != nil { return err } - // TODO box.eventBus.Publish(newContainerCreateDockerEvent(opts.Template.Name, containerName, containerId)) - - // TODO stop loader + task.eventBus.Publish(newContainerCreateDockerEvent(opts.Template.Name, containerName, containerId)) + task.eventBus.Publish(newContainerCreateDockerLoaderEvent()) if err := task.client.ContainerLogsStd(containerId); err != nil { return err diff --git a/pkg/task/docker/events.go b/pkg/task/docker/events.go index 1e4c1037..d1b662c9 100644 --- a/pkg/task/docker/events.go +++ b/pkg/task/docker/events.go @@ -1,6 +1,8 @@ package docker import ( + "fmt" + "github.com/hckops/hckctl/pkg/event" "github.com/hckops/hckctl/pkg/task/model" ) @@ -29,3 +31,39 @@ func newInitDockerClientEvent() *dockerTaskEvent { func newCloseDockerClientEvent() *dockerTaskEvent { return &dockerTaskEvent{kind: event.LogDebug, value: "close docker client"} } + +func newImagePullDockerEvent(imageName string) *dockerTaskEvent { + return &dockerTaskEvent{kind: event.LogInfo, value: fmt.Sprintf("image pull: imageName=%s", imageName)} +} + +func newImagePullDockerLoaderEvent(imageName string) *dockerTaskEvent { + return &dockerTaskEvent{kind: event.LoaderUpdate, value: fmt.Sprintf("pulling image %s", imageName)} +} + +func newImagePullIgnoreDockerEvent(imageName string) *dockerTaskEvent { + return &dockerTaskEvent{kind: event.LogWarning, value: fmt.Sprintf("image pull ignored: imageName=%s", imageName)} +} + +func newImageRemoveDockerEvent(imageId string) *dockerTaskEvent { + return &dockerTaskEvent{kind: event.LogInfo, value: fmt.Sprintf("image remove: imageId=%s", imageId)} +} + +func newImageRemoveIgnoreDockerEvent(imageId string, err error) *dockerTaskEvent { + return &dockerTaskEvent{kind: event.LogWarning, value: fmt.Sprintf("image remove ignored: imageId=%s error=%v", imageId, err)} +} + +func newNetworkUpsertDockerEvent(networkName string, networkId string) *dockerTaskEvent { + return &dockerTaskEvent{kind: event.LogInfo, value: fmt.Sprintf("network upsert: networkName=%s networkId=%s", networkName, networkId)} +} + +func newContainerCreateStatusDockerEvent(status string) *dockerTaskEvent { + return &dockerTaskEvent{kind: event.LogDebug, value: status} +} + +func newContainerCreateDockerEvent(templateName string, containerName string, containerId string) *dockerTaskEvent { + return &dockerTaskEvent{kind: event.LogInfo, value: fmt.Sprintf("container create: templateName=%s containerName=%s containerId=%s", templateName, containerName, containerId)} +} + +func newContainerCreateDockerLoaderEvent() *dockerTaskEvent { + return &dockerTaskEvent{kind: event.LoaderStop, value: "waiting"} +} diff --git a/pkg/task/model/task.go b/pkg/task/model/task.go index 3a8a45d7..63d16a55 100644 --- a/pkg/task/model/task.go +++ b/pkg/task/model/task.go @@ -45,3 +45,8 @@ func (task *TaskV1) DefaultCommandArguments() []string { } return []string{} } + +func (task *TaskV1) Pretty() string { + value, _ := util.EncodeJsonIndent(task) + return value +} diff --git a/pkg/task/model/task_test.go b/pkg/task/model/task_test.go index abef5009..078c2728 100644 --- a/pkg/task/model/task_test.go +++ b/pkg/task/model/task_test.go @@ -1,6 +1,7 @@ package model import ( + commonModel "github.com/hckops/hckctl/pkg/common/model" "strings" "testing" @@ -44,3 +45,38 @@ func TestDefaultCommandArgs(t *testing.T) { emptyArgs := (&TaskV1{}).DefaultCommandArguments() assert.Equal(t, []string{}, emptyArgs) } + +func TestPretty(t *testing.T) { + task := &TaskV1{ + Kind: "task/v1", + Name: "whalesay", + Tags: []string{"test"}, + Image: commonModel.Image{ + Repository: "docker/whalesay", + }, + Commands: []TaskCommand{ + {Name: "default", Arguments: []string{"cowsay", "${hello:hckops}"}}, + }, + } + json := `{ + "Kind": "task/v1", + "Name": "whalesay", + "Tags": [ + "test" + ], + "Image": { + "Repository": "docker/whalesay", + "Version": "" + }, + "Commands": [ + { + "Name": "default", + "Arguments": [ + "cowsay", + "${hello:hckops}" + ] + } + ] +}` + assert.Equal(t, json, task.Pretty()) +}