From bf43d2a58d706ee9f31a3e441c5f45cc8263b341 Mon Sep 17 00:00:00 2001
From: Vincent Demeester <vincent@sbr.pm>
Date: Tue, 27 Dec 2016 17:39:24 +0100
Subject: [PATCH] Make docker stack deploy a little bit more indempotent

Sort some slice fields before sending them to the swarm api so that it
won't trigger an update.

Signed-off-by: Vincent Demeester <vincent@sbr.pm>
---
 cli/compose/convert/service.go | 28 ++++++++++++++++++++++++++--
 1 file changed, 26 insertions(+), 2 deletions(-)

diff --git a/cli/compose/convert/service.go b/cli/compose/convert/service.go
index 7597c0f568..05e4fa4d14 100644
--- a/cli/compose/convert/service.go
+++ b/cli/compose/convert/service.go
@@ -2,6 +2,7 @@ package convert
 
 import (
 	"fmt"
+	"sort"
 	"time"
 
 	"github.com/docker/docker/api/types/container"
@@ -98,9 +99,9 @@ func convertService(
 				Command:         service.Entrypoint,
 				Args:            service.Command,
 				Hostname:        service.Hostname,
-				Hosts:           convertExtraHosts(service.ExtraHosts),
+				Hosts:           sortStrings(convertExtraHosts(service.ExtraHosts)),
 				Healthcheck:     healthcheck,
-				Env:             convertEnvironment(service.Environment),
+				Env:             sortStrings(convertEnvironment(service.Environment)),
 				Labels:          AddStackLabel(namespace, service.Labels),
 				Dir:             service.WorkingDir,
 				User:            service.User,
@@ -125,6 +126,17 @@ func convertService(
 	return serviceSpec, nil
 }
 
+func sortStrings(strs []string) []string {
+	sort.Strings(strs)
+	return strs
+}
+
+type byNetworkTarget []swarm.NetworkAttachmentConfig
+
+func (a byNetworkTarget) Len() int           { return len(a) }
+func (a byNetworkTarget) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
+func (a byNetworkTarget) Less(i, j int) bool { return a[i].Target < a[j].Target }
+
 func convertServiceNetworks(
 	networks map[string]*composetypes.ServiceNetworkConfig,
 	networkConfigs networkMap,
@@ -160,6 +172,9 @@ func convertServiceNetworks(
 			Aliases: append(aliases, name),
 		})
 	}
+
+	sort.Sort(byNetworkTarget(nets))
+
 	return nets, nil
 }
 
@@ -294,6 +309,12 @@ func convertResources(source composetypes.Resources) (*swarm.ResourceRequirement
 
 }
 
+type byPublishedPort []swarm.PortConfig
+
+func (a byPublishedPort) Len() int           { return len(a) }
+func (a byPublishedPort) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
+func (a byPublishedPort) Less(i, j int) bool { return a[i].PublishedPort < a[j].PublishedPort }
+
 func convertEndpointSpec(source []string) (*swarm.EndpointSpec, error) {
 	portConfigs := []swarm.PortConfig{}
 	ports, portBindings, err := nat.ParsePortSpecs(source)
@@ -307,6 +328,9 @@ func convertEndpointSpec(source []string) (*swarm.EndpointSpec, error) {
 			opts.ConvertPortToPortConfig(port, portBindings)...)
 	}
 
+	// Sorting to make sure these are always in the same order
+	sort.Sort(byPublishedPort(portConfigs))
+
 	return &swarm.EndpointSpec{Ports: portConfigs}, nil
 }