You've already forked runcompose
316 lines
7.5 KiB
Go
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, " ", " ")
|
|
input = strings.ReplaceAll(input, "\"", """)
|
|
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)
|
|
}
|
|
}
|