293 lines
9.1 KiB
Go
293 lines
9.1 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"]
|
|
|
|
// Use project name if available; otherwise, use 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)
|
|
}
|
|
|
|
// Determine if this is a Docker Swarm stack or a Docker Compose project
|
|
isSwarm := isDockerSwarmStack(*stackName, containers)
|
|
|
|
if isSwarm {
|
|
// Handle Docker Swarm stack export
|
|
err = exportSwarmStackToComposeFile(*stackName, *outputFile)
|
|
if err != nil {
|
|
log.Fatalf("Error exporting Docker Swarm stack %s: %v", *stackName, err)
|
|
}
|
|
fmt.Printf("docker-compose.yml generated successfully for Docker Swarm stack '%s' in %s\n", *stackName, *outputFile)
|
|
} else {
|
|
// Handle Docker Compose project export
|
|
err = exportComposeProjectToComposeFile(*stackName, containers, *outputFile)
|
|
if err != nil {
|
|
log.Fatalf("Error exporting Docker Compose project %s: %v", *stackName, err)
|
|
}
|
|
fmt.Printf("docker-compose.yml generated successfully for Docker Compose project '%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())
|
|
|
|
// Check that there are at least 3 fields (ID, Name, Image)
|
|
if len(fields) < 3 {
|
|
log.Printf("Warning: Skipping incomplete line: %s", scanner.Text())
|
|
continue
|
|
}
|
|
|
|
id := fields[0]
|
|
name := fields[1]
|
|
image := fields[2]
|
|
|
|
// Handle cases where project or stackNamespace might be missing
|
|
project := ""
|
|
stackNamespace := ""
|
|
if len(fields) > 3 {
|
|
project = fields[3]
|
|
}
|
|
if len(fields) > 4 {
|
|
stackNamespace = fields[4]
|
|
}
|
|
|
|
labels := map[string]string{
|
|
"com.docker.compose.project": project,
|
|
"com.docker.stack.namespace": stackNamespace,
|
|
}
|
|
|
|
// Run 'docker inspect' to get detailed container information.
|
|
env, networks, err := inspectContainer(id)
|
|
if err != nil {
|
|
log.Printf("Warning: Error inspecting container %s: %v", id, err)
|
|
continue
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
// Split output into environment variables and network names
|
|
parts := strings.Split(out.String(), " ")
|
|
if len(parts) < 2 {
|
|
// Handle cases where no networks are listed
|
|
parts = append(parts, "")
|
|
}
|
|
envVars := strings.Fields(parts[0])
|
|
networks := strings.Fields(parts[1])
|
|
|
|
return envVars, networks, nil
|
|
}
|
|
|
|
// isDockerSwarmStack checks if the stack is a Docker Swarm stack based on the container names.
|
|
func isDockerSwarmStack(stackName string, containers []Container) bool {
|
|
for _, container := range containers {
|
|
if strings.Contains(container.Name, ".") {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// exportSwarmStackToComposeFile exports a Docker Swarm stack to a docker-compose.yml file.
|
|
func exportSwarmStackToComposeFile(stackName string, outputFile string) error {
|
|
// Fetch stack details using `docker stack services` and construct the docker-compose.yml
|
|
cmd := exec.Command("docker", "stack", "services", stackName, "--format", "{{.Name}} {{.Image}} {{.Ports}} {{.Replicas}}")
|
|
var out bytes.Buffer
|
|
cmd.Stdout = &out
|
|
err := cmd.Run()
|
|
if err != nil {
|
|
return fmt.Errorf("error fetching Docker Swarm stack services: %v", err)
|
|
}
|
|
|
|
composeConfig := make(map[string]interface{})
|
|
composeConfig["services"] = make(map[string]interface{})
|
|
|
|
scanner := bufio.NewScanner(&out)
|
|
for scanner.Scan() {
|
|
fields := strings.Fields(scanner.Text())
|
|
if len(fields) < 2 {
|
|
continue
|
|
}
|
|
|
|
serviceName := fields[0]
|
|
image := fields[1]
|
|
|
|
serviceConfig := map[string]interface{}{
|
|
"image": image,
|
|
}
|
|
|
|
// Handle additional fields like ports and replicas
|
|
if len(fields) > 2 {
|
|
// Parse ports and replicas if available
|
|
ports := fields[2]
|
|
replicas := fields[3]
|
|
serviceConfig["deploy"] = map[string]interface{}{
|
|
"replicas": replicas,
|
|
}
|
|
if ports != "" {
|
|
serviceConfig["ports"] = []string{ports}
|
|
}
|
|
}
|
|
|
|
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 Docker Swarm stack %s: %v", stackName, 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
|
|
}
|
|
|
|
// exportComposeProjectToComposeFile exports a Docker Compose project to a docker-compose.yml file.
|
|
func exportComposeProjectToComposeFile(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 Docker Compose project %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
|
|
}
|
|
|