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 {
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
@ -61,12 +63,24 @@ func main() {
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)
// 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)
}
fmt.Printf("docker-compose.yml generated successfully for stack '%s' in %s\n", *stackName, *outputFile)
}
// Container represents basic Docker container information.
@ -95,8 +109,8 @@ func listContainers() ([]Container, error) {
for scanner.Scan() {
fields := strings.Fields(scanner.Text())
// Check that there are at least 5 fields before accessing them
if len(fields) < 5 {
// 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
}
@ -104,9 +118,20 @@ func listContainers() ([]Container, error) {
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": fields[3],
"com.docker.stack.namespace": fields[4],
"com.docker.compose.project": project,
"com.docker.stack.namespace": stackNamespace,
}
// Run 'docker inspect' to get detailed container information.
@ -139,15 +164,89 @@ func inspectContainer(containerID string) ([]string, []string, error) {
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
}
// exportStackToComposeFile generates a docker-compose.yml for a given stack or project.
func exportStackToComposeFile(stack string, containers []Container, outputFile string) error {
// 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{})
@ -179,7 +278,7 @@ func exportStackToComposeFile(stack string, containers []Container, outputFile s
// Convert to YAML
yamlData, err := yaml.Marshal(&composeConfig)
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