This commit is contained in:
Colin 2024-08-29 11:10:19 -04:00
parent aeea4e7f8c
commit 8fb4e0f3da
8 changed files with 111 additions and 12 deletions

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

123
main.go
View File

@ -32,6 +32,8 @@ func main() {
for _, container := range containers { for _, container := range containers {
project := container.Labels["com.docker.compose.project"] project := container.Labels["com.docker.compose.project"]
stackNamespace := container.Labels["com.docker.stack.namespace"] stackNamespace := container.Labels["com.docker.stack.namespace"]
// Use project name if available; otherwise, use stack namespace
stackKey := project stackKey := project
if stackNamespace != "" { if stackNamespace != "" {
stackKey = stackNamespace stackKey = stackNamespace
@ -61,12 +63,24 @@ func main() {
log.Fatalf("Stack or project '%s' not found.", *stackName) log.Fatalf("Stack or project '%s' not found.", *stackName)
} }
// Export the specified stack/project to a docker-compose.yml file // Determine if this is a Docker Swarm stack or a Docker Compose project
err = exportStackToComposeFile(*stackName, containers, *outputFile) isSwarm := isDockerSwarmStack(*stackName, containers)
if err != nil {
log.Fatalf("Error exporting stack %s: %v", *stackName, err) 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)
} }
fmt.Printf("docker-compose.yml generated successfully for stack '%s' in %s\n", *stackName, *outputFile)
} }
// Container represents basic Docker container information. // Container represents basic Docker container information.
@ -95,8 +109,8 @@ func listContainers() ([]Container, error) {
for scanner.Scan() { for scanner.Scan() {
fields := strings.Fields(scanner.Text()) fields := strings.Fields(scanner.Text())
// Check that there are at least 5 fields before accessing them // Check that there are at least 3 fields (ID, Name, Image)
if len(fields) < 5 { if len(fields) < 3 {
log.Printf("Warning: Skipping incomplete line: %s", scanner.Text()) log.Printf("Warning: Skipping incomplete line: %s", scanner.Text())
continue continue
} }
@ -104,9 +118,20 @@ func listContainers() ([]Container, error) {
id := fields[0] id := fields[0]
name := fields[1] name := fields[1]
image := fields[2] 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{ labels := map[string]string{
"com.docker.compose.project": fields[3], "com.docker.compose.project": project,
"com.docker.stack.namespace": fields[4], "com.docker.stack.namespace": stackNamespace,
} }
// Run 'docker inspect' to get detailed container information. // Run 'docker inspect' to get detailed container information.
@ -139,15 +164,89 @@ func inspectContainer(containerID string) ([]string, []string, error) {
return nil, nil, err return nil, nil, err
} }
// Split output into environment variables and network names
parts := strings.Split(out.String(), " ") parts := strings.Split(out.String(), " ")
if len(parts) < 2 {
// Handle cases where no networks are listed
parts = append(parts, "")
}
envVars := strings.Fields(parts[0]) envVars := strings.Fields(parts[0])
networks := strings.Fields(parts[1]) networks := strings.Fields(parts[1])
return envVars, networks, nil return envVars, networks, nil
} }
// exportStackToComposeFile generates a docker-compose.yml for a given stack or project. // isDockerSwarmStack checks if the stack is a Docker Swarm stack based on the container names.
func exportStackToComposeFile(stack string, containers []Container, outputFile string) error { 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 // Map to hold docker-compose configuration
composeConfig := make(map[string]interface{}) composeConfig := make(map[string]interface{})
composeConfig["services"] = make(map[string]interface{}) composeConfig["services"] = make(map[string]interface{})
@ -179,7 +278,7 @@ func exportStackToComposeFile(stack string, containers []Container, outputFile s
// Convert to YAML // Convert to YAML
yamlData, err := yaml.Marshal(&composeConfig) yamlData, err := yaml.Marshal(&composeConfig)
if err != nil { if err != nil {
return fmt.Errorf("error generating YAML for stack %s: %v", stack, err) return fmt.Errorf("error generating YAML for Docker Compose project %s: %v", stack, err)
} }
// Write YAML to a file // Write YAML to a file