0.0.970 cdbcd03

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

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