go-compose-exporter/main.go

190 lines
5.6 KiB
Go

package main
import (
"bufio"
"bytes"
"flag"
"fmt"
"log"
"os"
"os/exec"
"strings"
"gopkg.in/yaml.v2"
)
func main() {
// Command-line flags
stackName := flag.String("s", "", "Specify the stack or project name to export.")
outputFile := flag.String("o", "docker-compose.yml", "Specify the output file for the YAML.")
flag.Parse()
// Fetch list of all containers with their stack or project labels
containers, err := listContainers()
if err != nil {
log.Fatalf("Error fetching container list: %v", err)
}
// Map to group containers by stack or project
stacks := make(map[string][]Container)
// Group containers by Docker Compose project or Docker Swarm stack
for _, container := range containers {
project := container.Labels["com.docker.compose.project"]
stackNamespace := container.Labels["com.docker.stack.namespace"]
stackKey := project
if stackNamespace != "" {
stackKey = stackNamespace
}
if stackKey != "" {
stacks[stackKey] = append(stacks[stackKey], container)
}
}
if *stackName == "" {
// No stack specified, list available stacks
if len(stacks) == 0 {
fmt.Println("No Docker Compose projects or Docker Swarm stacks found.")
} else {
fmt.Println("Available stacks or Docker Compose projects to export:")
for stack := range stacks {
fmt.Println("- " + stack)
}
}
return
}
// Stack specified, check if it exists
containers, exists := stacks[*stackName]
if !exists {
log.Fatalf("Stack or project '%s' not found.", *stackName)
}
// Export the specified stack/project to a docker-compose.yml file
err = exportStackToComposeFile(*stackName, containers, *outputFile)
if err != nil {
log.Fatalf("Error exporting stack %s: %v", *stackName, err)
}
fmt.Printf("docker-compose.yml generated successfully for stack '%s' in %s\n", *stackName, *outputFile)
}
// Container represents basic Docker container information.
type Container struct {
ID string
Name string
Image string
Labels map[string]string
Env []string
Networks []string
}
// listContainers runs 'docker ps' and 'docker inspect' to get a list of all containers.
func listContainers() ([]Container, error) {
cmd := exec.Command("docker", "ps", "--format", "{{.ID}} {{.Names}} {{.Image}} {{.Label \"com.docker.compose.project\"}} {{.Label \"com.docker.stack.namespace\"}}")
var out bytes.Buffer
cmd.Stdout = &out
err := cmd.Run()
if err != nil {
return nil, err
}
scanner := bufio.NewScanner(&out)
var containers []Container
for scanner.Scan() {
fields := strings.Fields(scanner.Text())
if len(fields) < 3 {
continue
}
id := fields[0]
name := fields[1]
image := fields[2]
labels := map[string]string{
"com.docker.compose.project": fields[3],
"com.docker.stack.namespace": fields[4],
}
// Run 'docker inspect' to get detailed container information.
env, networks, err := inspectContainer(id)
if err != nil {
return nil, err
}
containers = append(containers, Container{
ID: id,
Name: name,
Image: image,
Labels: labels,
Env: env,
Networks: networks,
})
}
return containers, scanner.Err()
}
// inspectContainer runs 'docker inspect' and parses environment variables and networks.
func inspectContainer(containerID string) ([]string, []string, error) {
cmd := exec.Command("docker", "inspect", "--format", "{{range .Config.Env}}{{printf \"%s \" .}}{{end}} {{range $key, $value := .NetworkSettings.Networks}}{{$key}} {{end}}", containerID)
var out bytes.Buffer
cmd.Stdout = &out
err := cmd.Run()
if err != nil {
return nil, nil, err
}
parts := strings.Split(out.String(), " ")
envVars := strings.Fields(parts[0])
networks := strings.Fields(parts[1])
return envVars, networks, nil
}
// exportStackToComposeFile generates a docker-compose.yml for a given stack or project.
func exportStackToComposeFile(stack string, containers []Container, outputFile string) error {
// Map to hold docker-compose configuration
composeConfig := make(map[string]interface{})
composeConfig["services"] = make(map[string]interface{})
// Process each container in the stack
for _, container := range containers {
serviceName := container.Name
image := container.Image
// Initialize service configuration
serviceConfig := map[string]interface{}{
"image": image,
}
// Handle environment variables
if len(container.Env) > 0 {
serviceConfig["environment"] = container.Env
}
// Handle network settings
if len(container.Networks) > 0 {
serviceConfig["networks"] = container.Networks
}
// Add service configuration to compose config
composeConfig["services"].(map[string]interface{})[serviceName] = serviceConfig
}
// Convert to YAML
yamlData, err := yaml.Marshal(&composeConfig)
if err != nil {
return fmt.Errorf("error generating YAML for stack %s: %v", stack, err)
}
// Write YAML to a file
err = os.WriteFile(outputFile, yamlData, 0644)
if err != nil {
return fmt.Errorf("error writing to file %s: %v", outputFile, err)
}
return nil
}