Skip to content

Commit

Permalink
Adding features to force stopping a container & to provide network is…
Browse files Browse the repository at this point in the history
…olation into the container
  • Loading branch information
FlUxIuS committed Dec 17, 2024
1 parent f9b38ab commit 2a562c5
Show file tree
Hide file tree
Showing 4 changed files with 217 additions and 0 deletions.
22 changes: 22 additions & 0 deletions go/rfswift/cli/rfcli.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ var DockerName string
var DockerNewName string
var Bsource string
var Btarget string
var NetMode string
var NetExporsedPorts string
var NetBindedPorts string

var rootCmd = &cobra.Command{
Use: "rfswift",
Expand Down Expand Up @@ -59,6 +62,9 @@ var runCmd = &cobra.Command{
rfdock.DockerSetImage(DImage)
rfdock.DockerSetExtraHosts(ExtraHost)
rfdock.DockerSetPulse(PulseServer)
rfdock.DockerSetNetworkMode(NetMode)
rfdock.DockerSetExposedPorts(NetExporsedPorts)
rfdock.DockerSetBindexPorts(NetBindedPorts)
if os == "linux" { // use pactl to configure ACLs
rfutils.SetPulseCTL(PulseServer)
}
Expand Down Expand Up @@ -113,6 +119,15 @@ var commitCmd = &cobra.Command{
},
}

var stopCmd = &cobra.Command{
Use: "stop",
Short: "Stop a container",
Long: `Stop last or a particular container running`,
Run: func(cmd *cobra.Command, args []string) {
rfdock.DockerStop(ContID)
},
}

var pullCmd = &cobra.Command{
Use: "pull",
Short: "Pull a container",
Expand Down Expand Up @@ -311,6 +326,7 @@ func init() {
rootCmd.AddCommand(HostCmd)
rootCmd.AddCommand(UpdateCmd)
rootCmd.AddCommand(BindingsCmd)
rootCmd.AddCommand(stopCmd)

// Adding special commands for Windows
os := runtime.GOOS
Expand Down Expand Up @@ -360,9 +376,15 @@ func init() {
runCmd.Flags().StringVarP(&DImage, "image", "i", "", "image (default: 'myrfswift:latest')")
runCmd.Flags().StringVarP(&PulseServer, "pulseserver", "p", "tcp:127.0.0.1:34567", "PULSE SERVER TCP address (by default: tcp:127.0.0.1:34567)")
runCmd.Flags().StringVarP(&DockerName, "name", "n", "", "A docker name")
runCmd.Flags().StringVarP(&NetMode, "network", "t", "", "Network mode (default: 'host')")
runCmd.MarkFlagRequired("name")

runCmd.Flags().StringVarP(&NetExporsedPorts, "exposedports", "z", "", "Exposed ports")
runCmd.Flags().StringVarP(&NetBindedPorts, "bindedports", "w", "", "Exposed ports")
lastCmd.Flags().StringVarP(&FilterLast, "filter", "f", "", "filter by image name")

stopCmd.Flags().StringVarP(&ContID, "container", "c", "", "container to stop")

BindingsCmd.AddCommand(BindingsAddCmd)
BindingsCmd.AddCommand(BindingsRmCmd)
BindingsAddCmd.Flags().StringVarP(&ContID, "container", "c", "", "container to run")
Expand Down
18 changes: 18 additions & 0 deletions go/rfswift/dock/dockerutils.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,24 @@ func DockerSetExtraHosts(extrahosts string) {
}
}

func DockerSetNetworkMode(networkmode string) {
if networkmode != "" {
dockerObj.network_mode = networkmode
}
}

func DockerSetExposedPorts(exposedports string) {
if exposedports != "" {
dockerObj.exposed_ports = exposedports
}
}

func DockerSetBindexPorts(bindedports string) {
if bindedports != "" {
dockerObj.binded_ports = bindedports
}
}

// TODO: Optimize it and handle errors
func DockerInstallFromScript(contid string) {
/* Hot install inside a created Docker container
Expand Down
160 changes: 160 additions & 0 deletions go/rfswift/dock/rfdock.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"github.com/docker/docker/api/types/filters"
"github.com/docker/docker/api/types/image"
"github.com/docker/docker/api/types/network"
"github.com/docker/go-connections/nat"
"github.com/docker/docker/client"
"github.com/docker/docker/pkg/jsonmessage"
"github.com/moby/term"
Expand Down Expand Up @@ -154,6 +155,8 @@ type DockerInst struct {
extraenv string
pulse_server string
network_mode string
exposed_ports string
binded_ports string
}

var dockerObj = DockerInst{net: "host",
Expand All @@ -168,6 +171,8 @@ var dockerObj = DockerInst{net: "host",
extrahosts: "",
extraenv: "",
network_mode: "host",
exposed_ports: "",
binded_ports: "",
pulse_server: "tcp:localhost:34567",
shell: "/bin/bash"} // Instance with default values

Expand All @@ -187,6 +192,8 @@ func updateDockerObjFromConfig() {
dockerObj.repotag = config.General.RepoTag
dockerObj.shell = config.Container.Shell
dockerObj.network_mode = config.Container.Network
dockerObj.exposed_ports = config.Container.ExposedPorts
dockerObj.binded_ports = config.Container.PortBindings
dockerObj.x11forward = config.Container.X11Forward
dockerObj.xdisplay = config.Container.XDisplay
dockerObj.extrahosts = config.Container.ExtraHost
Expand Down Expand Up @@ -329,6 +336,8 @@ func printContainerProperties(ctx context.Context, cli *client.Client, container
{"Shell", props["Shell"]},
{"Privileged Mode", props["Privileged"]},
{"Network Mode", props["NetworkMode"]},
{"Exposed Ports", props["ExposedPorts"]},
{"Port Bindings", props["PortBindings"]},
{"Image Name", imageStatus},
{"Size on Disk", size},
{"Bindings", props["Bindings"]},
Expand Down Expand Up @@ -619,6 +628,31 @@ func latestDockerID(labelKey string, labelValue string) string {
return latestContainer.ID
}

func convertPortBindingsToString(portBindings nat.PortMap) string {
var result []string

for port, bindings := range portBindings {
for _, binding := range bindings {
// Format: HostIP:HostPort -> ContainerPort/Protocol
entry := fmt.Sprintf("%s:%s -> %s", binding.HostIP, binding.HostPort, port)
result = append(result, entry)
}
}

return strings.Join(result, ", ")
}

func convertExposedPortsToString(exposedPorts nat.PortSet) string {
var result []string

// Iterate through the PortSet (a map where keys are the exposed ports)
for port := range exposedPorts {
result = append(result, string(port)) // Convert the nat.Port to string
}

return strings.Join(result, ", ")
}

func getContainerProperties(ctx context.Context, cli *client.Client, containerID string) (map[string]string, error) {
containerJSON, err := cli.ContainerInspect(ctx, containerID)
if err != nil {
Expand Down Expand Up @@ -646,6 +680,8 @@ func getContainerProperties(ctx context.Context, cli *client.Client, containerID
"Shell": containerJSON.Path,
"Privileged": fmt.Sprintf("%v", containerJSON.HostConfig.Privileged),
"NetworkMode": string(containerJSON.HostConfig.NetworkMode),
"ExposedPorts": convertExposedPortsToString(containerJSON.Config.ExposedPorts),
"PortBindings": convertPortBindingsToString(containerJSON.HostConfig.PortBindings),
"ImageName": containerJSON.Config.Image,
"ImageHash": imageInfo.ID,
"Bindings": strings.Join(containerJSON.HostConfig.Binds, ","),
Expand Down Expand Up @@ -815,6 +851,81 @@ func DockerExec(containerIdentifier string, WorkingDir string) {
common.PrintSuccessMessage(fmt.Sprintf("Shell session in container '%s' ended", containerName))
}

func ParseExposedPorts(exposedPortsStr string) nat.PortSet {
exposedPorts := nat.PortSet{}

if exposedPortsStr == "" {
return exposedPorts
}

// Split by commas to get individual ports
portEntries := strings.Split(exposedPortsStr, ",")
for _, entry := range portEntries {
port := strings.TrimSpace(entry) // Remove extra spaces
if port == "" {
continue
}

// Add to nat.PortSet (e.g., "80/tcp")
exposedPorts[nat.Port(port)] = struct{}{}
}

return exposedPorts
}

func ParseBindedPorts(bindedPortsStr string) nat.PortMap {
portBindings := nat.PortMap{}

if (bindedPortsStr == "" || bindedPortsStr == "\"\"") {
return portBindings
}
common.PrintSuccessMessage(fmt.Sprintf("Binded: '%s'", bindedPortsStr))

// Split the input by ',' to get individual bindings
portEntries := strings.Split(bindedPortsStr, ",")
for _, entry := range portEntries {
// Expected format: containerPort[:hostAddress:]hostPort/protocol (e.g., 80:127.0.0.1:8080/tcp)
parts := strings.Split(entry, ":")
if len(parts) < 2 || len(parts) > 3 {
fmt.Printf("Invalid binded port format: %s (expected containerPort[:hostAddress:]hostPort/protocol)\n", entry)
continue
}

var containerPortProto, hostPort, hostAddress string

// Handle the optional hostAddress
if len(parts) == 3 {
containerPortProto = strings.TrimSpace(parts[0]) // e.g., 80
hostAddress = strings.TrimSpace(parts[1]) // e.g., 127.0.0.1
hostPort = strings.TrimSpace(parts[2]) // e.g., 8080/tcp
} else {
containerPortProto = strings.TrimSpace(parts[0]) // e.g., 80
hostPort = strings.TrimSpace(parts[1]) // e.g., 8080/tcp
}

// Split hostPort into hostPort and protocol
hostPortParts := strings.Split(hostPort, "/")
if len(hostPortParts) != 2 {
fmt.Printf("Invalid port format: %s (expected hostPort/protocol)\n", hostPort)
continue
}

hostPortValue := strings.TrimSpace(hostPortParts[0]) // e.g., 8080
protocol := strings.TrimSpace(hostPortParts[1]) // e.g., tcp

// Rearrange to containerPort/protocol (e.g., 80/tcp)
portKey := nat.Port(containerPortProto)

// Add the binding to the PortMap
portBindings[portKey] = append(portBindings[portKey], nat.PortBinding{
HostIP: hostAddress, // Optional host address
HostPort: fmt.Sprintf("%s/%s", hostPortValue, protocol),
})
}

return portBindings
}

func DockerRun(containerName string) {
/*
* Create a container with a specific name and run it
Expand All @@ -835,11 +946,14 @@ func DockerRun(containerName string) {
bindings := combineBindings(dockerObj.x11forward, dockerObj.usbforward, dockerObj.extrabinding)
extrahosts := splitAndCombine(dockerObj.extrahosts)
dockerenv := combineEnv(dockerObj.xdisplay, dockerObj.pulse_server, dockerObj.extraenv)
exposedPorts := ParseExposedPorts(dockerObj.exposed_ports)
bindedPorts := ParseBindedPorts(dockerObj.binded_ports)

resp, err := cli.ContainerCreate(ctx, &container.Config{
Image: dockerObj.imagename,
Cmd: []string{dockerObj.shell},
Env: dockerenv,
ExposedPorts: exposedPorts,
OpenStdin: true,
StdinOnce: false,
AttachStdin: true,
Expand All @@ -854,6 +968,7 @@ func DockerRun(containerName string) {
Binds: bindings,
Privileged: true,
ExtraHosts: extrahosts,
PortBindings: bindedPorts,
}, &network.NetworkingConfig{}, nil, containerName)
if err != nil {
common.PrintErrorMessage(err)
Expand Down Expand Up @@ -1798,3 +1913,48 @@ func getContainerIDByName(ctx context.Context, containerName string) string {
}
return ""
}

func DockerStop(containerIdentifier string) {
ctx := context.Background()

// Initialize Docker client
cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
if err != nil {
common.PrintErrorMessage(err)
return
}
defer cli.Close()

// Retrieve the latest container if no identifier is provided
if containerIdentifier == "" {
labelKey := "org.container.project"
labelValue := "rfswift"
containerIdentifier = latestDockerID(labelKey, labelValue)
if containerIdentifier == "" {
common.PrintErrorMessage(fmt.Errorf("no running containers found with label %s=%s", labelKey, labelValue))
return
}
}

// Inspect the container to get its current state
containerJSON, err := cli.ContainerInspect(ctx, containerIdentifier)
if err != nil {
common.PrintErrorMessage(fmt.Errorf("failed to inspect container: %v", err))
return
}

containerName := strings.TrimPrefix(containerJSON.Name, "/")
if !containerJSON.State.Running {
common.PrintSuccessMessage(fmt.Sprintf("Container '%s' is already stopped", containerName))
return
}

// Stop the container
timeout := 10 // Grace period in seconds before force stop
if err := cli.ContainerStop(ctx, containerIdentifier, container.StopOptions{Timeout: &timeout}); err != nil {
common.PrintErrorMessage(fmt.Errorf("failed to stop container: %v", err))
return
}

common.PrintSuccessMessage(fmt.Sprintf("Container '%s' stopped successfully", containerName))
}
17 changes: 17 additions & 0 deletions go/rfswift/rfutils/configs.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ type Config struct {
Shell string
Bindings []string
Network string
ExposedPorts string
PortBindings string
X11Forward string
XDisplay string
ExtraHost string
Expand Down Expand Up @@ -97,6 +99,10 @@ func ReadOrCreateConfig(filename string) (*Config, error) {
config.Container.Bindings = strings.Split(value, ",")
case "network":
config.Container.Network = value
case "exposedports":
config.Container.ExposedPorts = value
case "portbindings":
config.Container.PortBindings = value
case "x11forward":
config.Container.X11Forward = value
case "xdisplay":
Expand Down Expand Up @@ -139,6 +145,14 @@ func ReadOrCreateConfig(filename string) (*Config, error) {
printOrange("Network is missing in the config file.")
config.Container.Network = promptForValue("Network", "host")
}
if config.Container.ExposedPorts == "" {
printOrange("ExposedPorts is missing in the config file.")
config.Container.ExposedPorts = promptForValue("ExposedPorts", "")
}
if config.Container.PortBindings == "" {
printOrange("PortBindings is missing in the config file.")
config.Container.PortBindings = promptForValue("PortBindings", "")
}
if config.Container.X11Forward == "" {
printOrange("X11 forwarding is missing in the config file.")
config.Container.X11Forward = promptForValue("X11 forwarding", "/tmp/.X11-unix:/tmp/.X11-unix")
Expand All @@ -156,6 +170,7 @@ func ReadOrCreateConfig(filename string) (*Config, error) {
config.Audio.PulseServer = promptForValue("PulseAudio server", "tcp:localhost:34567")
}


return config, nil
}

Expand All @@ -168,6 +183,8 @@ repotag = penthertz/rfswift
shell = /bin/zsh
bindings = /dev/bus/usb:/dev/bus/usb,/run/dbus/system_bus_socket:/run/dbus/system_bus_socket,/dev/snd:/dev/snd,/dev/dri:/dev/dri,/dev/input:/dev/input,/dev/vhci:/dev/vhci
network = host
exposedports = ""
portbindings = ""
x11forward = /tmp/.X11-unix:/tmp/.X11-unix
xdisplay = "DISPLAY=:0"
extrahost = pluto.local:192.168.2.1
Expand Down

0 comments on commit 2a562c5

Please sign in to comment.