Files
tchivert 5e049f82c5
build / build (push) Successful in 1m48s
add options, fix input processing
2024-01-26 08:43:27 +01:00

316 lines
7.5 KiB
Go

package main
import (
"bytes"
"fmt"
"net/http"
"regexp"
"strings"
"text/template"
)
type Logging struct {
Driver string
Options map[string]string
}
type Service struct {
Name string
Container string
Image string
Ports []string
Volumes []string
Tmpfs []string
Environment []string
Restart string
Network string
Labels []string
CapAdd []string
CapDrop []string
GroupAdd []string
User string
Workdir string
Hostname string
Logging Logging
}
type PageData struct {
Input string
Result string
}
func extractImageName(image string) string {
parts := strings.Split(image, "/")
return strings.Split(parts[len(parts)-1], ":")[0]
}
func removeKeywords(parts []string, keywords ...string) []string {
var filtered []string
for _, part := range parts {
shouldRemove := false
for _, keyword := range keywords {
if part == keyword {
shouldRemove = true
break
}
}
if !shouldRemove {
filtered = append(filtered, part)
}
}
return filtered
}
func sanitizeInput(input string) string {
// Remove backslashes and newline characters
input = strings.ReplaceAll(input, "\\", "")
input = strings.ReplaceAll(input, "\n", " ")
input = strings.ReplaceAll(input, "--name=", "--name ")
input = strings.ReplaceAll(input, "--restart=", "--restart ")
input = strings.ReplaceAll(input, "--network=", "--network ")
input = strings.ReplaceAll(input, "--label=", "--label ")
input = strings.ReplaceAll(input, "--log-opt=", "--log-opt ")
input = strings.ReplaceAll(input, "--log-driver=", "--log-driver ")
input = strings.ReplaceAll(input, "--cap-add=", "--cap-add ")
input = strings.ReplaceAll(input, "--cap-drop=", "--cap-drop ")
input = strings.ReplaceAll(input, "--group-add=", "--group-add ")
input = strings.ReplaceAll(input, "--user=", "--user ")
input = strings.ReplaceAll(input, "--workdir=", "--workdir ")
input = strings.ReplaceAll(input, "--hostname=", "--hostname ")
// Replace quotes with double quotes
input = strings.ReplaceAll(input, "'", "\"")
// List of flags with no equivalent in Docker Compose that should be ignored
ignoredFlags := []string{
"-d", "--detach", "--detach-keys",
"--init",
"--ipc",
"--isolation",
"--pid",
"--privileged",
"--pull",
"--readonly",
"--rm",
"--shm-size",
"--stop-signal",
"--stop-timeout",
"--sysctl",
"--ulimit",
}
for _, flag := range ignoredFlags {
input = strings.ReplaceAll(input, " "+flag+" ", " ")
}
// Replace multiple consecutive spaces with a single space
re := regexp.MustCompile(`\s{2,}`)
input = re.ReplaceAllString(input, " ")
return input
}
func parseFlagValue(parts []string, index *int, flag string) string {
current := parts[*index]
if strings.HasPrefix(current, flag+"=") {
return strings.TrimPrefix(current, flag+"=")
} else if current == flag {
if *index+1 < len(parts) {
*index++
return parts[*index]
}
}
return ""
}
func splitArgs(input string) []string {
var args []string
var current string
var inQuote bool
for _, char := range input {
if char == '"' {
current += string(char)
inQuote = !inQuote
} else if char == ' ' && !inQuote {
args = append(args, current)
current = ""
} else {
current += string(char)
}
}
if current != "" {
args = append(args, current)
}
return args
}
func parseDockerRunCommand(input string) (Service, error) {
var name, container, image, restart, network string
var ports, volumes, tmpfs, environment, labels []string
var capAdd, capDrop, groupAdd []string
var user, workdir, hostname string
var loggingOptions map[string]string
var loggingDriver string
parts := splitArgs(input)
parts = removeKeywords(parts, "docker", "run")
for i := 0; i < len(parts); i++ {
arg := parts[i]
flagParsed := false
switch arg {
case "-p", "--publish":
value := parseFlagValue(parts, &i, arg)
ports = append(ports, value)
flagParsed = true
case "-v", "--volume":
value := parseFlagValue(parts, &i, arg)
volumes = append(volumes, value)
flagParsed = true
case "--tmpfs":
value := parseFlagValue(parts, &i, arg)
tmpfs = append(tmpfs, value)
flagParsed = true
case "-e", "--env":
value := parseFlagValue(parts, &i, arg)
environment = append(environment, value)
flagParsed = true
case "--name":
value := parseFlagValue(parts, &i, arg)
name = value
container = value
flagParsed = true
case "--restart":
value := parseFlagValue(parts, &i, arg)
restart = value
flagParsed = true
case "--network":
value := parseFlagValue(parts, &i, arg)
network = value
flagParsed = true
case "--label":
value := parseFlagValue(parts, &i, arg)
labels = append(labels, value)
flagParsed = true
case "--log-opt":
value := parseFlagValue(parts, &i, arg)
if loggingOptions == nil {
loggingOptions = make(map[string]string)
}
if strings.Contains(value, "=") {
loggingPair := strings.SplitN(value, "=", 2)
loggingOptions[loggingPair[0]] = loggingPair[1]
}
flagParsed = true
case "--log-driver":
value := parseFlagValue(parts, &i, arg)
loggingDriver = value
flagParsed = true
case "--cap-add":
value := parseFlagValue(parts, &i, arg)
capAdd = append(capAdd, value)
flagParsed = true
case "--cap-drop":
value := parseFlagValue(parts, &i, arg)
capDrop = append(capDrop, value)
flagParsed = true
case "--group-add":
value := parseFlagValue(parts, &i, arg)
groupAdd = append(groupAdd, value)
flagParsed = true
case "-u", "--user":
value := parseFlagValue(parts, &i, arg)
user = value
flagParsed = true
case "-w", "--workdir":
value := parseFlagValue(parts, &i, arg)
workdir = value
flagParsed = true
case "-h", "--hostname":
value := parseFlagValue(parts, &i, arg)
hostname = value
flagParsed = true
}
if !flagParsed && !strings.HasPrefix(arg, "-") && image == "" {
image = arg
}
}
if name == "" {
name = extractImageName(image)
}
service := Service{
Name: name,
Container: container,
Image: image,
Ports: ports,
Volumes: volumes,
Tmpfs: tmpfs,
Environment: environment,
Restart: restart,
Network: network,
Labels: labels,
CapAdd: capAdd,
CapDrop: capDrop,
GroupAdd: groupAdd,
User: user,
Workdir: workdir,
Hostname: hostname,
Logging: Logging{
Driver: loggingDriver,
Options: loggingOptions,
},
}
return service, nil
}
func htmlFormat(input string) string {
input = strings.ReplaceAll(input, "\n", "<br>")
input = strings.ReplaceAll(input, " ", "&nbsp;")
input = strings.ReplaceAll(input, "\"", "&quot;")
return input
}
func main() {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
tmpl := template.Must(template.ParseFiles("form.html"))
pageData := PageData{}
if r.Method == "POST" {
dockerRun := r.FormValue("docker_run")
sanitizedInput := sanitizeInput(dockerRun)
pageData.Input = htmlFormat(sanitizedInput)
service, err := parseDockerRunCommand(sanitizedInput)
if err == nil {
composeTmpl, err := template.ParseFiles("compose.tmpl")
if err == nil {
var result bytes.Buffer
err = composeTmpl.Execute(&result, service)
if err == nil {
pageData.Result = result.String()
}
}
}
}
if err := tmpl.Execute(w, pageData); err != nil {
fmt.Println("Error executing template:", err)
}
})
fmt.Println("Starting server at :8080")
if err := http.ListenAndServe(":8080", nil); err != nil {
fmt.Println("Error starting server:", err)
}
}