Skip to content

Commit b4b6965

Browse files
author
mirkobrombin
committed
feat: add DockerBasePlugin and DockerStandardPlugin for Docker integration
1 parent 1a46852 commit b4b6965

File tree

3 files changed

+431
-0
lines changed

3 files changed

+431
-0
lines changed

cmd/goup/main.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ func main() {
1616
pluginManager.Register(&plugins.AuthPlugin{})
1717
pluginManager.Register(&plugins.NodeJSPlugin{})
1818
pluginManager.Register(&plugins.PythonPlugin{})
19+
pluginManager.Register(&plugins.DockerBasePlugin{}) // currently here for testig purposes
20+
pluginManager.Register(&plugins.DockerStandardPlugin{})
1921

2022
cli.Execute()
2123
}

plugins/docker_base.go

Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
package plugins
2+
3+
import (
4+
"bytes"
5+
"context"
6+
"fmt"
7+
"io/ioutil"
8+
"net"
9+
"net/http"
10+
"os"
11+
"os/exec"
12+
"path/filepath"
13+
"strings"
14+
"sync"
15+
"time"
16+
17+
"github.com/mirkobrombin/goup/internal/config"
18+
"github.com/mirkobrombin/goup/internal/plugin"
19+
log "github.com/sirupsen/logrus"
20+
)
21+
22+
// DockerBaseConfig holds configuration for Docker/Podman integration.
23+
type DockerBaseConfig struct {
24+
Enable bool `json:"enable"`
25+
Mode string `json:"mode"` // e.g. "compose" or "standard"
26+
ComposeFile string `json:"compose_file"` // for compose mode
27+
DockerfilePath string `json:"dockerfile_path"`
28+
SocketPath string `json:"socket_path"`
29+
CLICommand string `json:"cli_command"`
30+
}
31+
32+
// DockerBasePlugin provides common Docker functionality.
33+
type DockerBasePlugin struct {
34+
plugin.BasePlugin
35+
mu sync.Mutex
36+
Config DockerBaseConfig
37+
}
38+
39+
func (d *DockerBasePlugin) Name() string {
40+
return "DockerBasePlugin"
41+
}
42+
43+
func (d *DockerBasePlugin) OnInit() error {
44+
return nil
45+
}
46+
47+
func (d *DockerBasePlugin) OnInitForSite(conf config.SiteConfig, domainLogger *log.Logger) error {
48+
if err := d.SetupLoggers(conf, d.Name(), domainLogger); err != nil {
49+
return err
50+
}
51+
var cfg DockerBaseConfig
52+
raw, ok := conf.PluginConfigs[d.Name()]
53+
if ok {
54+
if rawMap, ok := raw.(map[string]interface{}); ok {
55+
if v, ok := rawMap["enable"].(bool); ok {
56+
cfg.Enable = v
57+
}
58+
if v, ok := rawMap["mode"].(string); ok {
59+
cfg.Mode = v
60+
}
61+
if v, ok := rawMap["compose_file"].(string); ok {
62+
cfg.ComposeFile = v
63+
}
64+
if v, ok := rawMap["dockerfile_path"].(string); ok {
65+
cfg.DockerfilePath = v
66+
}
67+
if v, ok := rawMap["socket_path"].(string); ok {
68+
cfg.SocketPath = v
69+
}
70+
if v, ok := rawMap["cli_command"].(string); ok {
71+
cfg.CLICommand = v
72+
}
73+
}
74+
}
75+
d.Config = cfg
76+
77+
// Determine CLICommand if not set.
78+
if d.Config.CLICommand == "" {
79+
if _, err := exec.LookPath("docker"); err == nil {
80+
d.Config.CLICommand = "docker"
81+
} else if _, err := exec.LookPath("podman"); err == nil {
82+
d.Config.CLICommand = "podman"
83+
} else {
84+
return fmt.Errorf("neither 'docker' nor 'podman' found in PATH")
85+
}
86+
}
87+
88+
// Set default SocketPath.
89+
if strings.ToLower(d.Config.CLICommand) == "podman" && d.Config.SocketPath == "" {
90+
userSocket := fmt.Sprintf("/run/user/%d/podman/podman.sock", os.Getuid())
91+
if _, err := os.Stat(userSocket); err == nil {
92+
d.Config.SocketPath = userSocket
93+
} else {
94+
d.Config.SocketPath = "/run/podman/podman.sock"
95+
}
96+
}
97+
if d.Config.SocketPath == "" {
98+
d.Config.SocketPath = "/var/run/docker.sock"
99+
}
100+
101+
d.DomainLogger.Infof("[DockerBasePlugin] Initialized for domain=%s, mode=%s, CLICommand=%s, SocketPath=%s",
102+
conf.Domain, d.Config.Mode, d.Config.CLICommand, d.Config.SocketPath)
103+
return nil
104+
}
105+
106+
func (d *DockerBasePlugin) BeforeRequest(r *http.Request) {
107+
}
108+
109+
func (d *DockerBasePlugin) HandleRequest(w http.ResponseWriter, r *http.Request) bool {
110+
// Intercepts requests starting with "/docker/" to list containers.
111+
if !strings.HasPrefix(r.URL.Path, "/docker/") {
112+
return false
113+
}
114+
output, err := d.ListContainers()
115+
if err != nil {
116+
http.Error(w, fmt.Sprintf("Error listing containers: %v", err), http.StatusInternalServerError)
117+
return true
118+
}
119+
w.Header().Set("Content-Type", "application/json")
120+
w.Write([]byte(output))
121+
return true
122+
}
123+
124+
func (d *DockerBasePlugin) AfterRequest(w http.ResponseWriter, r *http.Request) {
125+
}
126+
127+
func (d *DockerBasePlugin) OnExit() error {
128+
return nil
129+
}
130+
131+
// ListContainers lists containers via Docker API; falls back to CLI if needed.
132+
func (d *DockerBasePlugin) ListContainers() (string, error) {
133+
res, err := d.callDockerAPI("GET", "/containers/json", nil)
134+
if err == nil {
135+
return res, nil
136+
}
137+
// CLI fallback.
138+
if strings.ToLower(d.Config.CLICommand) == "podman" {
139+
return RunDockerCLI(d.Config.CLICommand, d.Config.DockerfilePath, "ps", "--format", "json")
140+
}
141+
return RunDockerCLI(d.Config.CLICommand, d.Config.DockerfilePath, "ps", "--format", "{{json .}}")
142+
}
143+
144+
func (d *DockerBasePlugin) callDockerAPI(method, path string, body []byte) (string, error) {
145+
d.DomainLogger.Infof("[DockerBasePlugin] Calling Docker API: %s %s", method, path)
146+
socket := d.Config.SocketPath
147+
if socket == "" {
148+
socket = "/var/run/docker.sock"
149+
}
150+
transport := &http.Transport{
151+
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
152+
return net.Dial("unix", socket)
153+
},
154+
}
155+
client := &http.Client{Transport: transport, Timeout: 5 * time.Second}
156+
urlStr := "http://unix" + path
157+
req, err := http.NewRequest(method, urlStr, bytes.NewReader(body))
158+
if err != nil {
159+
return "", err
160+
}
161+
resp, err := client.Do(req)
162+
if err != nil {
163+
return "", err
164+
}
165+
defer resp.Body.Close()
166+
data, err := ioutil.ReadAll(resp.Body)
167+
return string(data), err
168+
}
169+
170+
// RunDockerCLI executes a Docker/Podman CLI command.
171+
func RunDockerCLI(cliCommand, dockerfilePath string, args ...string) (string, error) {
172+
cmd := exec.Command(cliCommand, args...)
173+
workDir := filepath.Dir(dockerfilePath)
174+
if workDir == "" {
175+
workDir = "."
176+
}
177+
cmd.Dir = workDir
178+
var stdout bytes.Buffer
179+
cmd.Stdout = &stdout
180+
cmd.Stderr = &stdout
181+
err := cmd.Run()
182+
return stdout.String(), err
183+
}
184+
185+
// GetRunningContainer returns the running container ID for the given image.
186+
func GetRunningContainer(cliCommand, dockerfilePath, imageName string) (string, error) {
187+
output, err := RunDockerCLI(cliCommand, dockerfilePath, "ps", "--filter", fmt.Sprintf("ancestor=%s", imageName), "--format", "{{.ID}}")
188+
if err != nil {
189+
return "", err
190+
}
191+
return strings.TrimSpace(output), nil
192+
}

0 commit comments

Comments
 (0)