0.1.7 2fa9aef

containers.go RawHistoryFolder

1package docker
2
3import (
4	"bytes"
5	"context"
6	"fmt"
7	"strconv"
8	"strings"
9	"time"
10
11	"github.com/docker/docker/api/types"
12	"github.com/docker/docker/api/types/container"
13	"github.com/docker/docker/api/types/network"
14	"github.com/docker/go-connections/nat"
15)
16
17type ContainerCommandParams struct {
18	ContainerName     string
19	ContainerHost     string
20	Domain            string
21	ServiceName       string
22	IsSSL             bool
23	EnvVar            string
24	ImageName         string
25	ImageID           string
26	PortMappings      []PortMapping
27	Volumes           []string
28	Labels            []string
29	Network           string
30	Restart           string
31	Environment       []string
32	TraefikEntryPoint string
33}
34
35type PortMapping struct {
36	HostPort      string
37	ContainerPort string
38	Protocol      string
39}
40
41// ParsePortsSpecs receives a slice of strings in the format "hostPort:containerPort/Protocol" and returns a slice of PortMapping structs
42func ParsePortsSpecs(portsSpecs []string) ([]PortMapping, error) {
43	portMappings := make([]PortMapping, 0, len(portsSpecs))
44
45	for _, portSpec := range portsSpecs {
46		parts := strings.Split(portSpec, ":")
47		if len(parts) != 2 {
48			return nil, fmt.Errorf("invalid port specification: %s. Format should be hostPort:containerPort[/Protocol]", portSpec)
49		}
50
51		// Validate hostPort and containerPort
52		hostPort := parts[0]
53		if _, err := strconv.Atoi(hostPort); err != nil {
54			return nil, fmt.Errorf("invalid host port: %s. Must be a number", hostPort)
55		}
56
57		containerParts := strings.Split(parts[1], "/")
58		containerPort := containerParts[0]
59		if _, err := strconv.Atoi(containerPort); err != nil {
60			return nil, fmt.Errorf("invalid exposed port: %s. Must be a number", containerPort)
61		}
62
63		// Default protocol is tcp
64		protocol := "tcp"
65		if len(containerParts) > 1 {
66			protocol = containerParts[1]
67		}
68
69		portMappings = append(portMappings, PortMapping{
70			HostPort:      hostPort,
71			ContainerPort: containerPort,
72			Protocol:      protocol,
73		})
74	}
75
76	return portMappings, nil
77}
78
79func ContainerCommandParamsToConfig(cmdParams ContainerCommandParams) (*container.Config, error) {
80	return &container.Config{
81		Image:    cmdParams.ImageName,
82		Hostname: cmdParams.ContainerHost,
83		Env:      cmdParams.Environment,
84		Labels:   map[string]string{},
85		Volumes:  map[string]struct{}{},
86	}, nil
87}
88
89// ListRunningContainers lists all running containers
90func ListRunningContainers() ([]types.Container, error) {
91	// Check if the Docker client has been initialized
92	err := CheckIfInitialized()
93	if err != nil {
94		return nil, err
95	}
96	// List containers using the Docker client:
97	containers, err := dockerCli.ContainerList(context.Background(), container.ListOptions{All: true})
98	if err != nil {
99		return nil, err
100	}
101	// Check if the list is empty
102	if len(containers) == 0 {
103		return nil, fmt.Errorf("no containers found")
104	}
105	return containers, nil
106}
107
108// StopContainer try to stop a container gracefully, if it fails, it will stop it forcefully
109func StopContainer(containerID string) error {
110	StopContainerGracefully(containerID, 3*time.Second)
111	StopContainerRagefully(containerID)
112	return nil
113}
114
115// StopContainerGracefully stops a container by sending a SIGTERM and waiting for it to stop
116func StopContainerGracefully(containerID string, timeoutDuration time.Duration) (bool, error) {
117	// Start by sending a SIGTERM
118	err := dockerCli.ContainerKill(context.Background(), containerID, "SIGTERM")
119	if err != nil {
120		return false, err
121	}
122
123	// Initialize a ticker for timeout
124	ticker := time.NewTicker(1 * time.Second)
125	defer ticker.Stop()
126
127	for elapsed := 0; elapsed < int(timeoutDuration.Seconds()); elapsed++ {
128		select {
129		case <-ticker.C:
130			// Check if the container is still running
131			container, err := dockerCli.ContainerInspect(context.Background(), containerID)
132			if err != nil {
133				return false, err
134			}
135
136			// If the container is not running, return true indicating it was stopped
137			if !container.State.Running {
138				return true, nil
139			}
140		}
141	}
142
143	// Return false, signaling that the container needs to be force-stopped
144	return false, nil
145}
146
147// StopContainerRagefully stops a container by sending a SIGKILL
148func StopContainerRagefully(containerID string) error {
149	// Start by sending a SIGKILL
150	err := dockerCli.ContainerKill(context.Background(), containerID, "SIGKILL")
151	if err != nil {
152		return err
153	}
154
155	return nil
156}
157
158// RenameContainer renames a container with the given name
159func RenameContainer(containerID string, newName string) error {
160	// Rename container using the Docker client
161	err := dockerCli.ContainerRename(context.Background(), containerID, newName)
162	if err != nil {
163		return err
164	}
165
166	return nil
167}
168
169// RemoveContainer
170func RemoveContainer(containerID string) error {
171	err := dockerCli.ContainerRemove(context.Background(), containerID, container.RemoveOptions{Force: true})
172	if err != nil {
173		return err
174	}
175
176	return nil
177}
178
179// StartContainer starts a container
180func StartContainer(containerID string) error {
181	fmt.Println("Starting container", containerID)
182	// Check if the container is not already in a running state
183	containerInfo, err := GetContainerInfo(containerID)
184	if err != nil {
185		return fmt.Errorf("could not get container info: %v", err)
186	}
187
188	if containerInfo.State.Running {
189		return fmt.Errorf("container is already running")
190	}
191
192	// Start container using the Docker client
193	err = dockerCli.ContainerStart(context.Background(), containerID, container.StartOptions{})
194	if err != nil {
195		return fmt.Errorf("could not start container: %v", err)
196	}
197
198	return nil
199}
200
201// CreateContainer creates a container with the given parameters
202func CreateContainer(cmdParams ContainerCommandParams) (string, error) {
203	// Check if the Docker client has been initialized
204	CheckIfInitialized()
205
206	isNetworkCreated, err := CheckIfNetworkExists(cmdParams.Network)
207	if err != nil {
208		return "", err
209	}
210
211	if !isNetworkCreated {
212		_, err := dockerCli.NetworkCreate(context.Background(), cmdParams.Network, types.NetworkCreate{})
213		if err != nil {
214			return "", err
215		}
216	}
217
218	// if the network exist return its id
219	// networkInfo, err := GetNetworkInfo(cmdParams.Network)
220	// if err != nil {
221	// 	return "", err
222
223	// }
224
225	// fmt.Print(networkInfo.ID)
226
227	// Prepare port bindings
228	portBindings := nat.PortMap{}
229	for _, portMapping := range cmdParams.PortMappings {
230		// Prepare exposed port
231		exposedPort := nat.Port(portMapping.ContainerPort + "/" + portMapping.Protocol)
232		portBindings[exposedPort] = []nat.PortBinding{
233			{
234				HostIP:   "0.0.0.0",
235				HostPort: portMapping.HostPort,
236			},
237		}
238	}
239
240	// Prepare labels for Traefik
241	labels := map[string]string{}
242	for _, label := range cmdParams.Labels {
243		keyValue := strings.Split(label, "=")
244		if len(keyValue) == 2 {
245			labels[keyValue[0]] = keyValue[1]
246		}
247	}
248
249	// Prepare environment variables
250	envVars := append([]string{}, cmdParams.Environment...)
251
252	// Create container
253	resp, err := dockerCli.ContainerCreate(
254		context.Background(),
255		&container.Config{
256			Image:  cmdParams.ImageName,
257			Labels: labels,
258			Env:    envVars,
259		},
260		&container.HostConfig{
261			PortBindings: portBindings,
262			Binds:        cmdParams.Volumes,
263			RestartPolicy: container.RestartPolicy{
264				Name: "always",
265			},
266		},
267		&network.NetworkingConfig{
268			EndpointsConfig: map[string]*network.EndpointSettings{
269				cmdParams.Network: {},
270			},
271		},
272		nil,
273		cmdParams.ContainerName,
274	)
275	if err != nil {
276		return "", err
277	}
278
279	return resp.ID, nil
280}
281
282func CheckIfNetworkExists(networkName string) (bool, error) {
283	networks, err := dockerCli.NetworkList(context.Background(), types.NetworkListOptions{})
284	if err != nil {
285		return false, err
286	}
287
288	for _, network := range networks {
289		if network.Name == networkName {
290			return true, nil
291		}
292	}
293
294	return false, nil
295}
296
297// GetNetworkInfo returns information about a network
298func GetNetworkInfo(networkName string) (types.NetworkResource, error) {
299	// Get network info using the Docker client
300	networkInfo, err := dockerCli.NetworkInspect(context.Background(), networkName, types.NetworkInspectOptions{})
301	if err != nil {
302		return types.NetworkResource{}, err
303	}
304
305	return networkInfo, nil
306}
307
308// GetContainerInfo returns information about a container
309func GetContainerInfo(containerID string) (types.ContainerJSON, error) {
310	// Get container info using the Docker client
311	containerInfo, err := dockerCli.ContainerInspect(context.Background(), containerID)
312	if err != nil {
313		return types.ContainerJSON{}, err
314	}
315
316	return containerInfo, nil
317}
318
319// UpdateContainerConfig updates the configuration of an existing container.
320func UpdateContainerConfig(containerID string, newConfig *container.Config, newHostConfig *container.HostConfig, newNetworkingConfig *network.NetworkingConfig) error {
321	ctx := context.Background()
322	// 1. Gracefully stop the existing container
323	_, err := StopContainerGracefully(containerID, 3*time.Second)
324	if err != nil {
325		return err
326	}
327
328	// 2. Remove the old container
329	if err := dockerCli.ContainerRemove(ctx, containerID, container.RemoveOptions{}); err != nil {
330		return err
331	}
332
333	// 3. Create a new container with the new configuration
334	resp, err := dockerCli.ContainerCreate(
335		ctx,
336		newConfig,
337		&container.HostConfig{},
338		&network.NetworkingConfig{},
339		nil,
340		"",
341	)
342	if err != nil {
343		return err
344	}
345
346	// 4. Start the new container
347	if err := dockerCli.ContainerStart(ctx, resp.ID, container.StartOptions{}); err != nil {
348		return err
349	}
350
351	return nil
352}
353
354// GetContainerLogs returns the logs of a container
355func GetContainerLogs(containerID string) (string, error) {
356	// Get container logs using the Docker client
357	containerLogs, err := dockerCli.ContainerLogs(context.Background(), containerID, container.LogsOptions{ShowStdout: true, ShowStderr: true})
358	if err != nil {
359		return "", err
360	}
361
362	// Read the logs
363	buf := new(bytes.Buffer)
364	buf.ReadFrom(containerLogs)
365	containerLogsString := buf.String()
366
367	return containerLogsString, nil
368}
369
370// CheckContainerStatus checks if a container with the given name exists and is running
371func CheckContainerStatus(containerName string) (bool, bool, error) {
372	// List all containers
373	containers, err := ListRunningContainers()
374	if err != nil {
375		return false, false, err
376	}
377
378	// Check if a container with the given name exists and is running
379	for _, container := range containers {
380		for _, name := range container.Names {
381			if strings.TrimLeft(name, "/") == containerName {
382				return true, container.State == "running", nil
383			}
384		}
385	}
386
387	return false, false, nil
388}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 7

Tags 106

↑↓ moveenter openesc close