diff --git a/pkg/box/docker/docker.go b/pkg/box/docker/docker.go index 8c5a3b34..85822018 100644 --- a/pkg/box/docker/docker.go +++ b/pkg/box/docker/docker.go @@ -35,6 +35,14 @@ func (box *DockerBoxClient) close() error { // TODO limit resources by size? func (box *DockerBoxClient) createBox(opts *boxModel.CreateOptions) (*boxModel.BoxInfo, error) { + // pull image + imageName := opts.Template.Image.Name() + if err := box.docker.PullImageOffline(imageName, func() { + box.eventBus.Publish(newImagePullDockerLoaderEvent(imageName)) + }); err != nil { + return nil, err + } + // boxName containerName := opts.Template.GenerateName() @@ -70,9 +78,6 @@ func (box *DockerBoxClient) createBox(opts *boxModel.CreateOptions) (*boxModel.B // use vpn network networkMode = docker.ContainerNetworkMode(sidecarContainerId) - - // TODO remove sidecar on exit - // defer box.docker.Client.ContainerRemove(sidecarContainerId) } } else { // defaults @@ -80,13 +85,6 @@ func (box *DockerBoxClient) createBox(opts *boxModel.CreateOptions) (*boxModel.B networkMode = docker.DefaultNetworkMode() } - imageName := opts.Template.Image.Name() - if err := box.docker.PullImageOffline(imageName, func() { - box.eventBus.Publish(newImagePullDockerLoaderEvent(imageName)) - }); err != nil { - return nil, err - } - var containerEnv []docker.ContainerEnv for _, e := range opts.Template.EnvironmentVariables() { containerEnv = append(containerEnv, docker.ContainerEnv{Key: e.Key, Value: e.Value}) @@ -184,6 +182,23 @@ func (box *DockerBoxClient) execBox(template *boxModel.BoxV1, info *boxModel.Box command := template.Shell box.eventBus.Publish(newContainerExecDockerEvent(info.Id, info.Name, command)) + // attempt to restart all associated sidecars + sidecars, err := box.docker.GetSidecars(info.Name) + if err != nil { + return err + } + for _, sidecar := range sidecars { + restartsOpts := &docker.ContainerRestartOpts{ + ContainerId: sidecar.Id, + OnRestartCallback: func(status string) { + box.eventBus.Publish(newContainerRestartDockerEvent(sidecar.Id, status)) + }, + } + if err := box.docker.Client.ContainerRestart(restartsOpts); err != nil { + return err + } + } + restartsOpts := &docker.ContainerRestartOpts{ ContainerId: info.Id, OnRestartCallback: func(status string) { @@ -221,6 +236,16 @@ func (box *DockerBoxClient) execBox(template *boxModel.BoxV1, info *boxModel.Box for _, port := range containerDetails.Ports { box.publishPortInfo(template.NetworkPorts(false), containerDetails.Info.ContainerName, port) } + // print sidecar ports + for _, sidecar := range sidecars { + sidecarDetails, err := box.docker.Client.ContainerInspect(sidecar.Id) + if err != nil { + return err + } + for _, port := range sidecarDetails.Ports { + box.publishPortInfo(template.NetworkPorts(false), containerDetails.Info.ContainerName, port) + } + } } execOpts := &docker.ContainerExecOpts{ @@ -238,6 +263,12 @@ func (box *DockerBoxClient) execBox(template *boxModel.BoxV1, info *boxModel.Box box.eventBus.Publish(newContainerExecExitDockerEvent(info.Id)) if deleteOnExit { + for _, sidecar := range sidecars { + if err := box.docker.Client.ContainerRemove(sidecar.Id); err != nil { + box.eventBus.Publish(newContainerExecErrorDockerEvent(sidecar.Id, errors.Wrap(err, "error sidecar exec remove"))) + } + } + if err := box.docker.Client.ContainerRemove(info.Id); err != nil { box.eventBus.Publish(newContainerExecErrorDockerEvent(info.Id, errors.Wrap(err, "error container exec remove"))) } @@ -358,12 +389,12 @@ func (box *DockerBoxClient) listBoxes() ([]boxModel.BoxInfo, error) { return nil, err } - var result []boxModel.BoxInfo + var boxes []boxModel.BoxInfo for index, c := range containers { - result = append(result, newBoxInfo(c)) + boxes = append(boxes, newBoxInfo(c)) box.eventBus.Publish(newContainerListDockerEvent(index, c.ContainerName, c.ContainerId, c.Healthy)) } - return result, nil + return boxes, nil } func (box *DockerBoxClient) deleteBoxes(names []string) ([]string, error) { @@ -386,6 +417,17 @@ func (box *DockerBoxClient) deleteBoxes(names []string) ([]string, error) { // silently ignore box.eventBus.Publish(newContainerRemoveIgnoreDockerEvent(boxInfo.Id)) } + + // delete all sidecars + sidecars, _ := box.docker.GetSidecars(boxInfo.Name) + for _, sidecar := range sidecars { + if err := box.docker.Client.ContainerRemove(sidecar.Id); err != nil { + box.eventBus.Publish(newContainerRemoveDockerEvent(sidecar.Id)) + } else { + // silently ignore + box.eventBus.Publish(newContainerRemoveIgnoreDockerEvent(sidecar.Id)) + } + } } } return deleted, nil diff --git a/pkg/client/docker/client.go b/pkg/client/docker/client.go index da65c375..e80c899d 100644 --- a/pkg/client/docker/client.go +++ b/pkg/client/docker/client.go @@ -273,11 +273,19 @@ func newContainerDetails(container types.ContainerJSON) (ContainerDetails, error return ContainerDetails{}, errors.Wrapf(err, "error parsing container created time %s", container.Created) } - if len(container.NetworkSettings.Networks) != 1 { - return ContainerDetails{}, errors.Wrapf(err, "found %d container networks, expected only 1", len(container.NetworkSettings.Networks)) + var networkInfo NetworkInfo + if len(container.NetworkSettings.Networks) > 1 { + return ContainerDetails{}, errors.Wrapf(err, "found %d container networks, expected at most 1", len(container.NetworkSettings.Networks)) + } else if len(container.NetworkSettings.Networks) == 1 { + networkName := maps.Keys(container.NetworkSettings.Networks)[0] + network := container.NetworkSettings.Networks[networkName] + networkInfo = NetworkInfo{ + Id: network.NetworkID, + Name: networkName, + IpAddress: network.IPAddress, + MacAddress: network.MacAddress, + } } - networkName := maps.Keys(container.NetworkSettings.Networks)[0] - network := container.NetworkSettings.Networks[networkName] return ContainerDetails{ Info: newContainerInfo(container.ID, container.Name, container.State.Status), @@ -285,12 +293,7 @@ func newContainerDetails(container types.ContainerJSON) (ContainerDetails, error Labels: container.Config.Labels, Env: envs, Ports: ports, - Network: NetworkInfo{ - Id: network.NetworkID, - Name: networkName, - IpAddress: network.IPAddress, - MacAddress: network.MacAddress, - }, + Network: networkInfo, }, nil } diff --git a/pkg/common/docker/client.go b/pkg/common/docker/client.go index 87734951..3d36981a 100644 --- a/pkg/common/docker/client.go +++ b/pkg/common/docker/client.go @@ -2,6 +2,7 @@ package docker import ( "fmt" + "github.com/hckops/hckctl/pkg/schema" "strings" "github.com/pkg/errors" @@ -75,20 +76,38 @@ func (common *DockerCommonClient) PullImageOffline(imageName string, onImagePull func buildVpnSidecarName(containerName string) string { // expect valid name always tokens := strings.Split(containerName, "-") - return fmt.Sprintf("sidecar-vpn-%s", tokens[len(tokens)-1]) + return fmt.Sprintf("%svpn-%s", commonModel.SidecarPrefixName, tokens[len(tokens)-1]) } -type StartVpnSidecarOptions struct { - MainContainerName string - VpnInfo *commonModel.VpnNetworkInfo - ContainerPorts []docker.ContainerPort - OnPortBindCallback func(port docker.ContainerPort) +func sidecarLabel() string { + return fmt.Sprintf("%s=%s", commonModel.LabelSchemaKind, schema.KindSidecarV1.String()) } -func (common *DockerCommonClient) StartVpnSidecar(baseContainerName string, vpnInfo *commonModel.VpnNetworkInfo, portConfig *docker.ContainerPortConfigOpts) (string, error) { +func (common *DockerCommonClient) GetSidecars(containerName string) ([]commonModel.SidecarInfo, error) { + + // filter by prefix and label + containers, err := common.Client.ContainerList(commonModel.SidecarPrefixName, sidecarLabel()) + if err != nil { + return nil, err + } + + var sidecars []commonModel.SidecarInfo + for _, c := range containers { + // expect valid name always + tokens := strings.Split(c.ContainerName, "-") + // include only associated containers + if strings.HasSuffix(containerName, tokens[len(tokens)-1]) { + sidecars = append(sidecars, commonModel.SidecarInfo{Id: c.ContainerId, Name: c.ContainerName}) + // TODO common.eventBus.Publish + } + } + return sidecars, nil +} + +func (common *DockerCommonClient) StartVpnSidecar(mainContainerName string, vpnInfo *commonModel.VpnNetworkInfo, portConfig *docker.ContainerPortConfigOpts) (string, error) { // sidecarName - containerName := buildVpnSidecarName(baseContainerName) + containerName := buildVpnSidecarName(mainContainerName) // constants imageName := commonModel.SidecarVpnImageName @@ -104,12 +123,12 @@ func (common *DockerCommonClient) StartVpnSidecar(baseContainerName string, vpnI containerConfig, err := docker.BuildContainerConfig(&docker.ContainerConfigOpts{ ImageName: imageName, - Hostname: baseContainerName, + Hostname: mainContainerName, Env: []docker.ContainerEnv{{Key: "OPENVPN_CONFIG", Value: vpnConfigPath}}, Ports: portConfig.Ports, Tty: false, Cmd: []string{}, - Labels: commonModel.Labels{}, + Labels: commonModel.NewSidecarLabels().AddSidecarMain(mainContainerName), }) if err != nil { return "", err diff --git a/pkg/common/model/constant.go b/pkg/common/model/constant.go index cda8f46d..1c8e164a 100644 --- a/pkg/common/model/constant.go +++ b/pkg/common/model/constant.go @@ -5,6 +5,7 @@ const ( KubernetesProvider = "kube" CloudProvider = "cloud" + SidecarPrefixName = "sidecar-" SidecarVpnImageName = "hckops/alpine-openvpn:latest" MountShareDir = "/hck/share" ) diff --git a/pkg/common/model/labels.go b/pkg/common/model/labels.go index 1163a5be..36d7b653 100644 --- a/pkg/common/model/labels.go +++ b/pkg/common/model/labels.go @@ -2,6 +2,7 @@ package model import ( "fmt" + "github.com/hckops/hckctl/pkg/schema" "path/filepath" "strings" @@ -21,6 +22,7 @@ const ( LabelTemplateGitDir = "com.hckops.template.git.dir" LabelTemplateGitName = "com.hckops.template.git.name" LabelTemplateCachePath = "com.hckops.template.cache.path" + LabelSidecarMain = "com.hckops.sidecar.main" ) func (l Labels) AddLabel(key string, value string) Labels { @@ -96,3 +98,13 @@ func (l Labels) ToGitTemplateInfo() *GitTemplateInfo { Name: l[LabelTemplateGitName], } } + +func NewSidecarLabels() Labels { + return map[string]string{ + LabelSchemaKind: schema.KindSidecarV1.String(), + } +} + +func (l Labels) AddSidecarMain(containerName string) Labels { + return l.AddLabel(LabelSidecarMain, containerName) +} diff --git a/pkg/common/model/types.go b/pkg/common/model/types.go index 95daa4d5..ccd84719 100644 --- a/pkg/common/model/types.go +++ b/pkg/common/model/types.go @@ -45,6 +45,11 @@ func (image *Image) ResolveVersion() string { type Parameters map[string]string +type SidecarInfo struct { + Id string + Name string +} + type NetworkInfo struct { Vpn *VpnNetworkInfo } diff --git a/pkg/schema/schema.go b/pkg/schema/schema.go index 948b7bd2..cb016444 100644 --- a/pkg/schema/schema.go +++ b/pkg/schema/schema.go @@ -23,6 +23,7 @@ type SchemaKind int const ( KindConfigV1 SchemaKind = iota KindApiV1 + KindSidecarV1 KindBoxV1 KindLabV1 KindTaskV1 @@ -31,13 +32,14 @@ const ( ) var kinds = map[SchemaKind]string{ - KindConfigV1: "config/v1", - KindApiV1: "api/v1", - KindBoxV1: "box/v1", - KindLabV1: "lab/v1", - KindTaskV1: "task/v1", - KindFlowV1: "flow/v1", - KindDumpV1: "dump/v1", + KindConfigV1: "config/v1", + KindApiV1: "api/v1", + KindSidecarV1: "sidecar/v1", + KindBoxV1: "box/v1", + KindLabV1: "lab/v1", + KindTaskV1: "task/v1", + KindFlowV1: "flow/v1", + KindDumpV1: "dump/v1", } func (s SchemaKind) String() string { diff --git a/pkg/schema/schema_test.go b/pkg/schema/schema_test.go index 43e781ab..ae8afe44 100644 --- a/pkg/schema/schema_test.go +++ b/pkg/schema/schema_test.go @@ -7,9 +7,10 @@ import ( ) func TestProviderFlag(t *testing.T) { - assert.Equal(t, 7, len(kinds)) + assert.Equal(t, 8, len(kinds)) assert.Equal(t, "config/v1", KindConfigV1.String()) assert.Equal(t, "api/v1", KindApiV1.String()) + assert.Equal(t, "sidecar/v1", KindSidecarV1.String()) assert.Equal(t, "box/v1", KindBoxV1.String()) assert.Equal(t, "lab/v1", KindLabV1.String()) assert.Equal(t, "task/v1", KindTaskV1.String())