Skip to content

Commit 41b8242

Browse files
authored
Merge pull request #72 from raghavyuva/feat/container_management
Container Management
2 parents 78c2bab + f138672 commit 41b8242

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

45 files changed

+2779
-27
lines changed

api/doc/openapi.json

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
package controller
2+
3+
import (
4+
"net/http"
5+
"strconv"
6+
7+
"github.com/go-fuego/fuego"
8+
"github.com/raghavyuva/nixopus-api/internal/features/container/types"
9+
"github.com/raghavyuva/nixopus-api/internal/features/logger"
10+
shared_types "github.com/raghavyuva/nixopus-api/internal/types"
11+
)
12+
13+
func (c *ContainerController) GetContainer(f fuego.ContextNoBody) (*shared_types.Response, error) {
14+
containerID := f.PathParam("container_id")
15+
16+
containerInfo, err := c.dockerService.GetContainerById(containerID)
17+
if err != nil {
18+
c.logger.Log(logger.Error, err.Error(), "")
19+
return nil, fuego.HTTPError{
20+
Err: err,
21+
Status: http.StatusInternalServerError,
22+
}
23+
}
24+
25+
containerData := types.Container{
26+
ID: containerInfo.ID,
27+
Name: containerInfo.Name[1:],
28+
Image: containerInfo.Config.Image,
29+
Status: containerInfo.State.Status,
30+
State: containerInfo.State.Status,
31+
Created: containerInfo.Created,
32+
Labels: containerInfo.Config.Labels,
33+
Command: containerInfo.Config.Cmd[0],
34+
IPAddress: containerInfo.NetworkSettings.IPAddress,
35+
HostConfig: types.HostConfig{
36+
Memory: containerInfo.HostConfig.Memory,
37+
MemorySwap: containerInfo.HostConfig.MemorySwap,
38+
CPUShares: containerInfo.HostConfig.CPUShares,
39+
},
40+
}
41+
42+
for port, bindings := range containerInfo.NetworkSettings.Ports {
43+
for _, binding := range bindings {
44+
containerData.Ports = append(containerData.Ports, types.Port{
45+
PrivatePort: int(port.Int()),
46+
PublicPort: func() int { p, _ := strconv.Atoi(binding.HostPort); return p }(),
47+
Type: port.Proto(),
48+
})
49+
}
50+
}
51+
52+
for _, mount := range containerInfo.Mounts {
53+
containerData.Mounts = append(containerData.Mounts, types.Mount{
54+
Type: string(mount.Type),
55+
Source: mount.Source,
56+
Destination: mount.Destination,
57+
Mode: mount.Mode,
58+
})
59+
}
60+
61+
for name, network := range containerInfo.NetworkSettings.Networks {
62+
containerData.Networks = append(containerData.Networks, types.Network{
63+
Name: name,
64+
IPAddress: network.IPAddress,
65+
Gateway: network.Gateway,
66+
MacAddress: network.MacAddress,
67+
Aliases: network.Aliases,
68+
})
69+
}
70+
71+
return &shared_types.Response{
72+
Status: "success",
73+
Message: "Container fetched successfully",
74+
Data: containerData,
75+
}, nil
76+
}
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
package controller
2+
3+
import (
4+
"bytes"
5+
"encoding/binary"
6+
"io"
7+
"net/http"
8+
"strconv"
9+
10+
"github.com/docker/docker/api/types/container"
11+
"github.com/go-fuego/fuego"
12+
"github.com/raghavyuva/nixopus-api/internal/features/container/types"
13+
"github.com/raghavyuva/nixopus-api/internal/features/logger"
14+
shared_types "github.com/raghavyuva/nixopus-api/internal/types"
15+
)
16+
17+
func (c *ContainerController) GetContainerLogs(f fuego.ContextWithBody[types.ContainerLogsRequest]) (*shared_types.Response, error) {
18+
req, err := f.Body()
19+
if err != nil {
20+
return nil, fuego.HTTPError{
21+
Err: err,
22+
Status: http.StatusBadRequest,
23+
}
24+
}
25+
26+
logsReader, err := c.dockerService.GetContainerLogs(req.ID, container.LogsOptions{
27+
Follow: req.Follow,
28+
Tail: strconv.Itoa(req.Tail),
29+
Since: req.Since,
30+
Until: req.Until,
31+
ShowStdout: req.Stdout,
32+
ShowStderr: req.Stderr,
33+
})
34+
if err != nil {
35+
c.logger.Log(logger.Error, err.Error(), "")
36+
return nil, fuego.HTTPError{
37+
Err: err,
38+
Status: http.StatusInternalServerError,
39+
}
40+
}
41+
42+
buf := new(bytes.Buffer)
43+
_, err = io.Copy(buf, logsReader)
44+
if err != nil {
45+
c.logger.Log(logger.Error, err.Error(), "")
46+
return nil, fuego.HTTPError{
47+
Err: err,
48+
Status: http.StatusInternalServerError,
49+
}
50+
}
51+
52+
decodedLogs := decodeDockerLogs(buf.Bytes())
53+
54+
return &shared_types.Response{
55+
Status: "success",
56+
Message: "Container logs fetched successfully",
57+
Data: decodedLogs,
58+
}, nil
59+
}
60+
61+
func decodeDockerLogs(data []byte) string {
62+
var result bytes.Buffer
63+
offset := 0
64+
65+
for offset < len(data) {
66+
if offset+8 > len(data) {
67+
break
68+
}
69+
70+
streamType := data[offset]
71+
length := binary.BigEndian.Uint32(data[offset+4 : offset+8])
72+
offset += 8
73+
74+
if offset+int(length) > len(data) {
75+
break
76+
}
77+
78+
if streamType == 1 || streamType == 2 {
79+
result.Write(data[offset : offset+int(length)])
80+
}
81+
offset += int(length)
82+
}
83+
84+
return result.String()
85+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package controller
2+
3+
import (
4+
"context"
5+
"github.com/raghavyuva/nixopus-api/internal/features/deploy/docker"
6+
"github.com/raghavyuva/nixopus-api/internal/features/logger"
7+
"github.com/raghavyuva/nixopus-api/internal/features/notification"
8+
shared_storage "github.com/raghavyuva/nixopus-api/internal/storage"
9+
)
10+
11+
type ContainerController struct {
12+
store *shared_storage.Store
13+
dockerService *docker.DockerService
14+
ctx context.Context
15+
logger logger.Logger
16+
notification *notification.NotificationManager
17+
}
18+
19+
func NewContainerController(
20+
store *shared_storage.Store,
21+
ctx context.Context,
22+
l logger.Logger,
23+
notificationManager *notification.NotificationManager,
24+
) *ContainerController {
25+
return &ContainerController{
26+
store: store,
27+
dockerService: docker.NewDockerService(),
28+
ctx: ctx,
29+
logger: l,
30+
notification: notificationManager,
31+
}
32+
}
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
package controller
2+
3+
import (
4+
"net/http"
5+
6+
"github.com/go-fuego/fuego"
7+
"github.com/raghavyuva/nixopus-api/internal/features/container/types"
8+
"github.com/raghavyuva/nixopus-api/internal/features/logger"
9+
shared_types "github.com/raghavyuva/nixopus-api/internal/types"
10+
)
11+
12+
func (c *ContainerController) ListContainers(f fuego.ContextNoBody) (*shared_types.Response, error) {
13+
containers, err := c.dockerService.ListAllContainers()
14+
if err != nil {
15+
c.logger.Log(logger.Error, err.Error(), "")
16+
return nil, fuego.HTTPError{
17+
Err: err,
18+
Status: http.StatusInternalServerError,
19+
}
20+
}
21+
22+
var result []types.Container
23+
for _, container := range containers {
24+
containerInfo, err := c.dockerService.GetContainerById(container.ID)
25+
if err != nil {
26+
c.logger.Log(logger.Error, "Error inspecting container", container.ID)
27+
continue
28+
}
29+
30+
containerData := types.Container{
31+
ID: container.ID,
32+
Name: container.Names[0][1:],
33+
Image: container.Image,
34+
Status: container.Status,
35+
State: container.State,
36+
Created: containerInfo.Created,
37+
Labels: container.Labels,
38+
Command: containerInfo.Config.Cmd[0],
39+
IPAddress: containerInfo.NetworkSettings.IPAddress,
40+
HostConfig: types.HostConfig{
41+
Memory: containerInfo.HostConfig.Memory,
42+
MemorySwap: containerInfo.HostConfig.MemorySwap,
43+
CPUShares: containerInfo.HostConfig.CPUShares,
44+
},
45+
}
46+
47+
for _, port := range container.Ports {
48+
containerData.Ports = append(containerData.Ports, types.Port{
49+
PrivatePort: int(port.PrivatePort),
50+
PublicPort: int(port.PublicPort),
51+
Type: port.Type,
52+
})
53+
}
54+
55+
for _, mount := range containerInfo.Mounts {
56+
containerData.Mounts = append(containerData.Mounts, types.Mount{
57+
Type: string(mount.Type),
58+
Source: mount.Source,
59+
Destination: mount.Destination,
60+
Mode: mount.Mode,
61+
})
62+
}
63+
64+
for name, network := range containerInfo.NetworkSettings.Networks {
65+
containerData.Networks = append(containerData.Networks, types.Network{
66+
Name: name,
67+
IPAddress: network.IPAddress,
68+
Gateway: network.Gateway,
69+
MacAddress: network.MacAddress,
70+
Aliases: network.Aliases,
71+
})
72+
}
73+
74+
result = append(result, containerData)
75+
}
76+
77+
return &shared_types.Response{
78+
Status: "success",
79+
Message: "Containers fetched successfully",
80+
Data: result,
81+
}, nil
82+
}
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
package controller
2+
3+
import (
4+
"net/http"
5+
"strings"
6+
7+
"github.com/docker/docker/api/types/filters"
8+
"github.com/docker/docker/api/types/image"
9+
"github.com/go-fuego/fuego"
10+
container_types "github.com/raghavyuva/nixopus-api/internal/features/container/types"
11+
shared_types "github.com/raghavyuva/nixopus-api/internal/types"
12+
)
13+
14+
type ListImagesRequest struct {
15+
All bool `json:"all,omitempty"`
16+
ContainerID string `json:"container_id,omitempty"`
17+
ImagePrefix string `json:"image_prefix,omitempty"`
18+
}
19+
20+
func (c *ContainerController) ListImages(f fuego.ContextWithBody[ListImagesRequest]) (*shared_types.Response, error) {
21+
req, err := f.Body()
22+
if err != nil {
23+
return nil, fuego.HTTPError{
24+
Err: err,
25+
Status: http.StatusBadRequest,
26+
}
27+
}
28+
29+
filterArgs := filters.NewArgs()
30+
if req.ContainerID != "" {
31+
_, err := c.dockerService.GetContainerById(req.ContainerID)
32+
if err != nil {
33+
return nil, fuego.HTTPError{
34+
Err: err,
35+
Status: http.StatusNotFound,
36+
}
37+
}
38+
}
39+
40+
if req.ImagePrefix != "" {
41+
pattern := req.ImagePrefix
42+
if !strings.HasSuffix(pattern, "*") {
43+
pattern += "*"
44+
}
45+
filterArgs.Add("reference", pattern)
46+
}
47+
48+
images := c.dockerService.ListAllImages(image.ListOptions{
49+
All: req.All,
50+
Filters: filterArgs,
51+
})
52+
53+
if len(images) == 0 {
54+
return &shared_types.Response{
55+
Status: "success",
56+
Message: "No images found",
57+
Data: []container_types.Image{},
58+
}, nil
59+
}
60+
61+
var result []container_types.Image
62+
for _, img := range images {
63+
imageData := container_types.Image{
64+
ID: img.ID,
65+
RepoTags: img.RepoTags,
66+
RepoDigests: img.RepoDigests,
67+
Created: img.Created,
68+
Size: img.Size,
69+
SharedSize: img.SharedSize,
70+
VirtualSize: img.VirtualSize,
71+
Labels: img.Labels,
72+
}
73+
74+
result = append(result, imageData)
75+
}
76+
77+
return &shared_types.Response{
78+
Status: "success",
79+
Message: "Images listed successfully",
80+
Data: result,
81+
}, nil
82+
}

0 commit comments

Comments
 (0)