Repository: replicatedhq/kots
Pull request #1117 (2020-09-21 23:23:42)

Making createVersion atomic


Patch:
diff --git a/go.mod b/go.mod
index ff9d102fe..00a602906 100644
--- a/go.mod
+++ b/go.mod
@@ -30,6 +30,7 @@ require (
 	github.com/gotestyourself/gotestyourself v2.2.0+incompatible // indirect
 	github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 // indirect
 	github.com/manifoldco/promptui v0.3.2
+	github.com/marccampbell/yaml-toolbox v0.0.0-20200805160637-950ceb36c770
 	github.com/mattn/go-isatty v0.0.12
 	github.com/mholt/archiver v3.1.1+incompatible
 	github.com/otiai10/copy v1.0.2
@@ -38,8 +39,9 @@ require (
 	github.com/replicatedhq/kurl/kurlkinds v0.0.0-20200617224429-55f493a72baf
 	github.com/replicatedhq/troubleshoot v0.9.42
 	github.com/replicatedhq/yaml/v3 v3.0.0-beta5-replicatedhq
+	github.com/sergi/go-diff v1.0.0
 	github.com/spf13/cobra v1.0.0
-	github.com/spf13/viper v1.4.0
+	github.com/spf13/viper v1.6.2
 	github.com/stevvooe/resumable v0.0.0-20180830230917-22b14a53ba50 // indirect
 	github.com/stretchr/testify v1.6.1
 	github.com/tj/go-spin v1.1.0
@@ -48,6 +50,7 @@ require (
 	github.com/yvasiyarov/gorelic v0.0.7 // indirect
 	github.com/yvasiyarov/newrelic_platform_go v0.0.0-20160601141957-9c099fbc30e9 // indirect
 	golang.org/x/crypto v0.0.0-20200423211502-4bdfaf469ed5
+	gopkg.in/go-playground/assert.v1 v1.2.1
 	gopkg.in/yaml.v2 v2.3.0
 	helm.sh/helm/v3 v3.2.4
 	k8s.io/api v0.18.4
diff --git a/go.sum b/go.sum
index c213232df..0b59be3a5 100644
--- a/go.sum
+++ b/go.sum
@@ -68,6 +68,7 @@ github.com/Masterminds/goutils v1.1.0 h1:zukEsf/1JZwCMgHiK3GZftabmxiCw4apj3a28RP
 github.com/Masterminds/goutils v1.1.0/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
 github.com/Masterminds/semver v1.4.2 h1:WBLTQ37jOCzSLtXNdoo8bNM8876KhNqOKvrlGITgsTc=
 github.com/Masterminds/semver v1.4.2/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=
+github.com/Masterminds/semver/v3 v3.0.3/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
 github.com/Masterminds/semver/v3 v3.1.0 h1:Y2lUDsFKVRSYGojLJ1yLxSXdMmMYTYls0rCvoqmMUQk=
 github.com/Masterminds/semver/v3 v3.1.0/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
 github.com/Masterminds/sprig v2.22.0+incompatible h1:z4yfnGrZ7netVz+0EDJ0Wi+5VZCSYp4Z0m2dk6cEM60=
@@ -507,6 +508,7 @@ github.com/googleapis/gnostic v0.3.1 h1:WeAefnSUHlBb0iJKwxFDZdbfGwkd7xRNuV+IpXMJ
 github.com/googleapis/gnostic v0.3.1/go.mod h1:on+2t9HRStVgn95RSsFWFz+6Q0Snyqv1awfrALZdbtU=
 github.com/gophercloud/gophercloud v0.1.0 h1:P/nh25+rzXouhytV2pUHBb65fnds26Ghl8/391+sT5o=
 github.com/gophercloud/gophercloud v0.1.0/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8=
+github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
 github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
 github.com/gordonklaus/ineffassign v0.0.0-20180909121442-1003c8bd00dc h1:cJlkeAx1QYgO5N80aF5xRGstVsRQwgLR7uA2FnP1ZjY=
 github.com/gordonklaus/ineffassign v0.0.0-20180909121442-1003c8bd00dc/go.mod h1:cuNKsD1zp2v6XfE/orVX2QE1LC+i254ceGcVeDT3pTU=
@@ -587,6 +589,7 @@ github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/u
 github.com/json-iterator/go v1.1.8 h1:QiWkFLKq0T7mpzwOTu6BzNDbfTE8OLrYhVKYMLF46Ok=
 github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
 github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
+github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
 github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
 github.com/juju/ansiterm v0.0.0-20180109212912-720a0952cc2a h1:FaWFmfWdAUKbSCtOU2QjDaorUexogfaMgbipgYATUMU=
 github.com/juju/ansiterm v0.0.0-20180109212912-720a0952cc2a/go.mod h1:UJSiEoRfvx3hP73CvoARgeLjaIOjybY9vj8PUPPFGeU=
@@ -617,6 +620,8 @@ github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxv
 github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
 github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
 github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
+github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs=
+github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
 github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
 github.com/kr/pty v1.1.3/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
 github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA=
@@ -663,6 +668,9 @@ github.com/mailru/easyjson v0.7.0 h1:aizVhC/NAAcKWb+5QsU1iNOZb4Yws5UO2I+aIprQITM
 github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs=
 github.com/manifoldco/promptui v0.3.2 h1:rir7oByTERac6jhpHUPErHuopoRDvO3jxS+FdadEns8=
 github.com/manifoldco/promptui v0.3.2/go.mod h1:8JU+igZ+eeiiRku4T5BjtKh2ms8sziGpSYl1gN8Bazw=
+github.com/marccampbell/yaml-remarshaler v0.0.0-20200328184140-6002a2f30c7f/go.mod h1:oaJcbU9OP14sTuYsu9LvSBlyCJIlQTIvWrSfk+k3blE=
+github.com/marccampbell/yaml-toolbox v0.0.0-20200805160637-950ceb36c770 h1:s2WLaBLLvtqZaADwaeQCjOoisJtYnTmtkTPg4mL7mnc=
+github.com/marccampbell/yaml-toolbox v0.0.0-20200805160637-950ceb36c770/go.mod h1:jMJ6Zc40Nygk0M3+3xE6dsfcYqf7yTtD7Nk9oAU1zas=
 github.com/marten-seemann/qtls v0.2.3/go.mod h1:xzjG7avBwGGbdZ8dTGxlBnLArsVKLvwmjgmPuiQEcYk=
 github.com/matoous/godox v0.0.0-20190911065817-5d6d842e92eb/go.mod h1:1BELzlh859Sh1c6+90blK8lbYy0kwQf1bYlBhBysy1s=
 github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
@@ -890,6 +898,7 @@ github.com/ryanuber/go-glob v0.0.0-20170128012129-256dc444b735/go.mod h1:807d1WS
 github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
 github.com/seccomp/libseccomp-golang v0.9.1/go.mod h1:GbW5+tmTXfcxTToHLXlScSlAvWlF4P2Ca7zGrPiEpWo=
 github.com/securego/gosec v0.0.0-20191002120514-e680875ea14d/go.mod h1:w5+eXa0mYznDkHaMCXA4XYffjlH+cy1oyKbfzJXa2Do=
+github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ=
 github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
 github.com/shirou/gopsutil v0.0.0-20180427012116-c95755e4bcd7/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
 github.com/shirou/gopsutil v0.0.0-20190901111213-e4ec7b275ada/go.mod h1:WWnYX4lzhCH5h/3YBfyVA3VbLYjlMZZAQcW9ojMexNc=
@@ -926,7 +935,9 @@ github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4
 github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
 github.com/sirupsen/logrus v1.6.0 h1:UBcNElsrwanuuMsnGSlYmtmgbb23qDR5dG+6X6Oo89I=
 github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
+github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
 github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
+github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
 github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
 github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
 github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d/go.mod h1:UdhH50NIW0fCiwBSr0co2m7BnFLdv4fQTgdqdJTHFeE=
@@ -946,6 +957,7 @@ github.com/spf13/cobra v0.0.2/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3
 github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
 github.com/spf13/cobra v0.0.5 h1:f0B+LkLX6DtmRH1isoNA9VTtNUK9K8xYd28JNNfOv/s=
 github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
+github.com/spf13/cobra v0.0.7/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE=
 github.com/spf13/cobra v1.0.0 h1:6m/oheQuQ13N9ks4hubMG6BnvwOeaJrqSPLahSnczz8=
 github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE=
 github.com/spf13/jwalterweatherman v0.0.0-20180109140146-7c0cea34c8ec/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
@@ -963,6 +975,8 @@ github.com/spf13/viper v1.0.2/go.mod h1:A8kyI5cUJhb8N+3pkfONlcEcZbueH6nhAm0Fq7Sr
 github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
 github.com/spf13/viper v1.4.0 h1:yXHLWeravcrgGyFSyCgdYpXQ9dR9c/WED3pg1RhxqEU=
 github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE=
+github.com/spf13/viper v1.6.2 h1:7aKfF+e8/k68gda3LOjo5RxiUqddoFxVq4BKBPrxk5E=
+github.com/spf13/viper v1.6.2/go.mod h1:t3iDnF5Jlj76alVNuyFBk5oUMCvsrkbvZK0WQdfDi5k=
 github.com/stevvooe/resumable v0.0.0-20180830230917-22b14a53ba50 h1:4bT0pPowCpQImewr+BjzfUKcuFW+KVyB8d1OF3b6oTI=
 github.com/stevvooe/resumable v0.0.0-20180830230917-22b14a53ba50/go.mod h1:1pdIZTAHUz+HDKDVZ++5xg/duPlhKAIzw9qy42CWYp4=
 github.com/storageos/go-api v0.0.0-20180912212459-343b3eff91fc/go.mod h1:ZrLn+e0ZuF3Y65PNF6dIwbJPZqfmtCXxFm9ckv0agOY=
@@ -978,6 +992,8 @@ github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5
 github.com/stretchr/testify v1.6.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
 github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s=
+github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
 github.com/syndtr/gocapability v0.0.0-20170704070218-db04d3cc01c8/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww=
 github.com/syndtr/gocapability v0.0.0-20180916011248-d98352740cb2 h1:b6uOv7YOFK0TYG7HtkIgExQo+2RdLuwRft63jn2HWj8=
 github.com/syndtr/gocapability v0.0.0-20180916011248-d98352740cb2/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww=
@@ -1006,6 +1022,8 @@ github.com/ultraware/funlen v0.0.2/go.mod h1:Dp4UiAus7Wdb9KUZsYWZEWiRzGuM2kXM1lP
 github.com/ultraware/whitespace v0.0.4/go.mod h1:aVMh/gQve5Maj9hQ/hg+F75lr/X5A89uZnzAmWSineA=
 github.com/undefinedlabs/go-mpatch v0.0.0-20200122175732-0044123dbb98 h1:Z/megzdoMbuZ0H/oLmfTw92PAEfyTi1DkvAr8AUzFgw=
 github.com/undefinedlabs/go-mpatch v0.0.0-20200122175732-0044123dbb98/go.mod h1:TyJZDQ/5AgyN7FSLiBJ8RO9u2c6wbtRvK827b6AVqY4=
+github.com/undefinedlabs/go-mpatch v0.0.0-20200326085307-1a86426f42a6 h1:VO1oVFjnL0fwOlwLpDqY1xhY/cfr0Ycz4aLwWM76D6k=
+github.com/undefinedlabs/go-mpatch v0.0.0-20200326085307-1a86426f42a6/go.mod h1:TyJZDQ/5AgyN7FSLiBJ8RO9u2c6wbtRvK827b6AVqY4=
 github.com/urfave/cli v0.0.0-20171014202726-7bc6a0acffa5/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
 github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
 github.com/urfave/negroni v1.0.0/go.mod h1:Meg73S6kFm/4PpbYdq35yYWoCZ9mS/YSx+lKnmiohz4=
@@ -1072,6 +1090,8 @@ go.uber.org/zap v1.10.0 h1:ORx85nbTijNz8ljznvCMR1ZBIPKFn3jQrag10X2AsuM=
 go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
 go.undefinedlabs.com/scopeagent v0.1.7 h1:TPALQQNkPah8NgVjPKKhaBNZL2hg+hY2GRPfRMSt48c=
 go.undefinedlabs.com/scopeagent v0.1.7/go.mod h1:XCD8wnuBZ+eskhynYNP+uvBiFan9cFrWLwHnWOETXDU=
+go.undefinedlabs.com/scopeagent v0.1.15 h1:H3n8Vb2ndEoMnt8EKeq4uqnV58EMsNQsTRvg314KXfg=
+go.undefinedlabs.com/scopeagent v0.1.15/go.mod h1:ebAQoyMi0CPxEnrI09lQ19vnpu01KMlLv7M++V9bYdo=
 go4.org v0.0.0-20180809161055-417644f6feb5/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE=
 golang.org/x/build v0.0.0-20190111050920-041ab4dc3f9d/go.mod h1:OWs+y06UdEOHN4y+MfF/py+xQ/tYqIWW03b70/CG9Rw=
 golang.org/x/build v0.0.0-20190927031335-2835ba2e683f/go.mod h1:fYw7AShPAhGMdXqA9gRadk/CcMsvLlClpE5oBwnS3dM=
@@ -1163,6 +1183,7 @@ golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLL
 golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20200202094626-16171245cfb2 h1:CCH4IOTTfewWjGOlSp+zGcjutRKlBEZQ6wTn8ozI/nI=
 golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e h1:3G+cUijn7XD+S4eJFddp53Pv7+slrESplyjG25HgL+k=
 golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
 golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
@@ -1229,6 +1250,7 @@ golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7w
 golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5 h1:LfCXLvNmTYH9kEmVgqbnsWfruoXZIrh4YBgqVHtDvw0=
 golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200519105757-fe76b779f299 h1:DYfZAGf2WMFjMxbgTjaC+2HC7NkNAQs+6Q8b9WEB/F4=
 golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -1345,6 +1367,7 @@ google.golang.org/grpc v1.24.0/go.mod h1:XDChyiUovWa60DnaeDeZmSW86xtLtjtZbwvSiRn
 google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
 google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
 google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
+google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
 google.golang.org/grpc v1.28.0 h1:bO/TA4OxCOummhSf10siHuG7vJOiwh7SpRpFZDkOgl4=
 google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
 gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U=
@@ -1362,10 +1385,14 @@ gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
 gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
 gopkg.in/gcfg.v1 v1.2.0/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o=
 gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2/go.mod h1:Xk6kEKp8OKb+X14hQBKWaSkCsqBpgog8nAV2xsGOxlo=
+gopkg.in/go-playground/assert.v1 v1.2.1 h1:xoYuJVE7KT85PYWrN730RguIQO0ePzVRfFMXadIrXTM=
+gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE=
 gopkg.in/gorp.v1 v1.7.2 h1:j3DWlAyGVv8whO7AcIWznQ2Yj7yJkn34B8s63GViAAw=
 gopkg.in/gorp.v1 v1.7.2/go.mod h1:Wo3h+DBQZIxATwftsglhdD/62zRFPhGhTiu5jUJmCaw=
 gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
 gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
+gopkg.in/ini.v1 v1.51.0 h1:AQvPpx3LzTDM0AjnIRlVFwFFGC+npRopjZxLJj6gdno=
+gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
 gopkg.in/mcuadros/go-syslog.v2 v2.2.1/go.mod h1:l5LPIyOOyIdQquNg+oU6Z3524YwrcqEm0aKH+5zpt2U=
 gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k=
 gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
diff --git a/kotsadm/go.mod b/kotsadm/go.mod
index d34848b8e..4a958d92f 100644
--- a/kotsadm/go.mod
+++ b/kotsadm/go.mod
@@ -21,7 +21,7 @@ require (
 	github.com/gorilla/websocket v1.4.0
 	github.com/gosimple/slug v1.9.0
 	github.com/lib/pq v1.3.0
-	github.com/marccampbell/yaml-toolbox v0.0.0-20200328202846-85b6f7184a20
+	github.com/marccampbell/yaml-toolbox v0.0.0-20200805160637-950ceb36c770
 	github.com/mholt/archiver v3.1.1+incompatible
 	github.com/opencontainers/image-spec v1.0.2-0.20190823105129-775207bd45b6
 	github.com/pkg/errors v0.9.1
@@ -31,7 +31,6 @@ require (
 	github.com/robfig/cron v1.1.0
 	github.com/robfig/cron/v3 v3.0.0
 	github.com/segmentio/ksuid v1.0.2
-	github.com/sergi/go-diff v1.0.0
 	github.com/spf13/cobra v1.0.0
 	github.com/spf13/viper v1.6.2
 	github.com/stretchr/testify v1.6.1
@@ -39,7 +38,6 @@ require (
 	go.uber.org/zap v1.13.0
 	golang.org/x/crypto v0.0.0-20200423211502-4bdfaf469ed5
 	google.golang.org/api v0.9.0
-	gopkg.in/go-playground/assert.v1 v1.2.1
 	gopkg.in/ini.v1 v1.51.0
 	gopkg.in/src-d/go-git.v4 v4.13.1
 	gopkg.in/yaml.v2 v2.3.0
diff --git a/kotsadm/go.sum b/kotsadm/go.sum
index 588852149..f20189424 100644
--- a/kotsadm/go.sum
+++ b/kotsadm/go.sum
@@ -728,8 +728,8 @@ github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7
 github.com/manifoldco/promptui v0.3.2 h1:rir7oByTERac6jhpHUPErHuopoRDvO3jxS+FdadEns8=
 github.com/manifoldco/promptui v0.3.2/go.mod h1:8JU+igZ+eeiiRku4T5BjtKh2ms8sziGpSYl1gN8Bazw=
 github.com/marccampbell/yaml-remarshaler v0.0.0-20200328184140-6002a2f30c7f/go.mod h1:oaJcbU9OP14sTuYsu9LvSBlyCJIlQTIvWrSfk+k3blE=
-github.com/marccampbell/yaml-toolbox v0.0.0-20200328202846-85b6f7184a20 h1:/y1it/UfmqfQEe6wg91Jy3iAUJ8bT4lh5ivdhPYqP+M=
-github.com/marccampbell/yaml-toolbox v0.0.0-20200328202846-85b6f7184a20/go.mod h1:jMJ6Zc40Nygk0M3+3xE6dsfcYqf7yTtD7Nk9oAU1zas=
+github.com/marccampbell/yaml-toolbox v0.0.0-20200805160637-950ceb36c770 h1:s2WLaBLLvtqZaADwaeQCjOoisJtYnTmtkTPg4mL7mnc=
+github.com/marccampbell/yaml-toolbox v0.0.0-20200805160637-950ceb36c770/go.mod h1:jMJ6Zc40Nygk0M3+3xE6dsfcYqf7yTtD7Nk9oAU1zas=
 github.com/marten-seemann/qtls v0.2.3/go.mod h1:xzjG7avBwGGbdZ8dTGxlBnLArsVKLvwmjgmPuiQEcYk=
 github.com/matoous/godox v0.0.0-20190911065817-5d6d842e92eb/go.mod h1:1BELzlh859Sh1c6+90blK8lbYy0kwQf1bYlBhBysy1s=
 github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
diff --git a/kotsadm/pkg/gitops/types/gitops_interface.go b/kotsadm/pkg/gitops/types/gitops_interface.go
new file mode 100644
index 000000000..65ad808f4
--- /dev/null
+++ b/kotsadm/pkg/gitops/types/gitops_interface.go
@@ -0,0 +1,5 @@
+package types
+
+type DownstreamGitOps interface {
+	CreateGitOpsDownstreamCommit(appID string, clusterID string, newSequence int, archiveDir string, downstreamName string) (string, error)
+}
diff --git a/kotsadm/pkg/store/ocistore/version_store.go b/kotsadm/pkg/store/ocistore/version_store.go
index 0290411be..2c1c2a081 100644
--- a/kotsadm/pkg/store/ocistore/version_store.go
+++ b/kotsadm/pkg/store/ocistore/version_store.go
@@ -19,9 +19,12 @@ import (
 	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
 	"github.com/pkg/errors"
 	apptypes "github.com/replicatedhq/kots/kotsadm/pkg/app/types"
+	kotsconfig "github.com/replicatedhq/kots/kotsadm/pkg/config"
+	gitopstypes "github.com/replicatedhq/kots/kotsadm/pkg/gitops/types"
 	"github.com/replicatedhq/kots/kotsadm/pkg/logger"
 	versiontypes "github.com/replicatedhq/kots/kotsadm/pkg/version/types"
 	"github.com/replicatedhq/kots/pkg/kotsutil"
+	"github.com/replicatedhq/kots/pkg/kustomize"
 	"go.uber.org/zap"
 )
 
@@ -295,7 +298,98 @@ func (s OCIStore) GetAppVersionArchive(appID string, sequence int64) (string, er
 	return tmpDir, nil
 }
 
-func (s OCIStore) CreateAppVersion(appID string, currentSequence *int64, appName string, appIcon string, kotsKinds *kotsutil.KotsKinds) (int64, error) {
+func (s OCIStore) CreateAppVersion(appID string, currentSequence *int64, appName string, appIcon string, kotsKinds *kotsutil.KotsKinds, filesInDir string, gitops gitopstypes.DownstreamGitOps, source string) (int64, error) {
+	newSequence, err := s.createAppVersion(appID, currentSequence, appName, appIcon, kotsKinds)
+	if err != nil {
+		return int64(0), errors.Wrap(err, "failed to create app version")
+	}
+
+	if err := s.CreateAppVersionArchive(appID, int64(newSequence), filesInDir); err != nil {
+		return int64(0), errors.Wrap(err, "failed to create app version archive")
+	}
+
+	previousArchiveDir := ""
+	if currentSequence != nil {
+		// Get the previous archive, we need this to calculate the diff
+		previousDir, err := s.GetAppVersionArchive(appID, *currentSequence)
+		if err != nil {
+			return int64(0), errors.Wrap(err, "failed to get previous archive")
+		}
+
+		previousArchiveDir = previousDir
+	}
+
+	downstreams, err := s.ListDownstreamsForApp(appID)
+	if err != nil {
+		return int64(0), errors.Wrap(err, "failed to list downstreams")
+	}
+
+	for _, d := range downstreams {
+		downstreamStatus := "pending"
+		if currentSequence == nil && kotsKinds.Config != nil {
+			downstreamStatus = "pending_config"
+		} else if kotsKinds.Preflight != nil {
+			downstreamStatus = "pending_preflight"
+		}
+
+		if currentSequence != nil {
+			// there's a small chance this is not optimal, but no current code path
+			// will support multiple downstreams, so this is cleaner here for now
+			licenseSpec, err := kotsKinds.Marshal("kots.io", "v1beta1", "License")
+			if err != nil {
+				return int64(0), errors.Wrap(err, "failed to marshal license spec")
+			}
+			configSpec, err := kotsKinds.Marshal("kots.io", "v1beta1", "Config")
+			if err != nil {
+				return int64(0), errors.Wrap(err, "failed to marshal config spec")
+			}
+			configValuesSpec, err := kotsKinds.Marshal("kots.io", "v1beta1", "ConfigValues")
+			if err != nil {
+				return int64(0), errors.Wrap(err, "failed to marshal configvalues spec")
+			}
+
+			// check if version needs additional configuration
+			t, err := kotsconfig.NeedsConfiguration(configSpec, configValuesSpec, licenseSpec)
+			if err != nil {
+				return int64(0), errors.Wrap(err, "failed to check if version needs configuration")
+			}
+			if t {
+				downstreamStatus = "pending_config"
+			}
+		}
+
+		diffSummary, diffSummaryError := "", ""
+		if currentSequence != nil {
+			// diff this release from the last release
+			diff, err := kustomize.DiffAppVersionsForDownstream(d.Name, filesInDir, previousArchiveDir, kotsKinds.KustomizeVersion())
+			if err != nil {
+				diffSummaryError = errors.Wrap(err, "failed to diff").Error()
+			} else {
+				b, err := json.Marshal(diff)
+				if err != nil {
+					diffSummaryError = errors.Wrap(err, "failed to marshal diff").Error()
+				}
+				diffSummary = string(b)
+			}
+		}
+
+		commitURL, err := gitops.CreateGitOpsDownstreamCommit(appID, d.ClusterID, int(newSequence), filesInDir, d.Name)
+		if err != nil {
+			return int64(0), errors.Wrap(err, "failed to create gitops commit")
+		}
+
+		err = s.addAppVersionToDownstream(appID, d.ClusterID, newSequence,
+			kotsKinds.Installation.Spec.VersionLabel, downstreamStatus, source,
+			diffSummary, diffSummaryError, commitURL, commitURL != "")
+		if err != nil {
+			return int64(0), errors.Wrap(err, "failed to create downstream version")
+		}
+	}
+
+	return newSequence, nil
+}
+
+func (s OCIStore) createAppVersion(appID string, currentSequence *int64, appName string, appIcon string, kotsKinds *kotsutil.KotsKinds) (int64, error) {
 	// NOTE that this experimental store doesn't have a tx and it's possible that this
 	// could overwrite if there are multiple updates happening concurrently
 	latestAppVersion, err := s.getLatestAppVersion(appID)
@@ -342,7 +436,7 @@ func (s OCIStore) CreateAppVersion(appID string, currentSequence *int64, appName
 	return newSequence, nil
 }
 
-func (s OCIStore) AddAppVersionToDownstream(appID string, clusterID string, sequence int64, versionLabel string, status string, source string, diffSummary string, diffSummaryError string, commitURL string, gitDeployable bool) error {
+func (s OCIStore) addAppVersionToDownstream(appID string, clusterID string, sequence int64, versionLabel string, status string, source string, diffSummary string, diffSummaryError string, commitURL string, gitDeployable bool) error {
 	return ErrNotImplemented
 }
 
diff --git a/kotsadm/pkg/store/s3pg/version_store.go b/kotsadm/pkg/store/s3pg/version_store.go
index f6e0ed936..00422b6a8 100644
--- a/kotsadm/pkg/store/s3pg/version_store.go
+++ b/kotsadm/pkg/store/s3pg/version_store.go
@@ -3,6 +3,7 @@ package s3pg
 import (
 	"context"
 	"database/sql"
+	"encoding/json"
 	"fmt"
 	"io/ioutil"
 	"os"
@@ -16,12 +17,15 @@ import (
 	"github.com/mholt/archiver"
 	"github.com/pkg/errors"
 	apptypes "github.com/replicatedhq/kots/kotsadm/pkg/app/types"
+	kotsconfig "github.com/replicatedhq/kots/kotsadm/pkg/config"
+	gitopstypes "github.com/replicatedhq/kots/kotsadm/pkg/gitops/types"
 	"github.com/replicatedhq/kots/kotsadm/pkg/persistence"
 	"github.com/replicatedhq/kots/kotsadm/pkg/render"
 	kotss3 "github.com/replicatedhq/kots/kotsadm/pkg/s3"
 	versiontypes "github.com/replicatedhq/kots/kotsadm/pkg/version/types"
 	kotsv1beta1 "github.com/replicatedhq/kots/kotskinds/apis/kots/v1beta1"
 	"github.com/replicatedhq/kots/pkg/kotsutil"
+	"github.com/replicatedhq/kots/pkg/kustomize"
 	velerov1 "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/client-go/kubernetes"
@@ -267,7 +271,110 @@ func (s S3PGStore) GetAppVersionArchive(appID string, sequence int64) (string, e
 	return tmpDir, nil
 }
 
-func (s S3PGStore) CreateAppVersion(appID string, currentSequence *int64, appName string, appIcon string, kotsKinds *kotsutil.KotsKinds) (int64, error) {
+func (s S3PGStore) CreateAppVersion(appID string, currentSequence *int64, appName string, appIcon string, kotsKinds *kotsutil.KotsKinds, filesInDir string, gitops gitopstypes.DownstreamGitOps, source string) (int64, error) {
+	db := persistence.MustGetPGSession()
+
+	tx, err := db.Begin()
+	if err != nil {
+		return int64(0), errors.Wrap(err, "failed to begin")
+	}
+	defer tx.Rollback()
+
+	newSequence, err := s.createAppVersion(tx, appID, currentSequence, appName, appIcon, kotsKinds)
+	if err != nil {
+		return int64(0), errors.Wrap(err, "failed to create app version")
+	}
+
+	if err := s.CreateAppVersionArchive(appID, int64(newSequence), filesInDir); err != nil {
+		return int64(0), errors.Wrap(err, "failed to create app version archive")
+	}
+
+	previousArchiveDir := ""
+	if currentSequence != nil {
+		// Get the previous archive, we need this to calculate the diff
+		previousDir, err := s.GetAppVersionArchive(appID, *currentSequence)
+		if err != nil {
+			return int64(0), errors.Wrap(err, "failed to get previous archive")
+		}
+
+		previousArchiveDir = previousDir
+	}
+
+	downstreams, err := s.ListDownstreamsForApp(appID)
+	if err != nil {
+		return int64(0), errors.Wrap(err, "failed to list downstreams")
+	}
+
+	for _, d := range downstreams {
+		downstreamStatus := "pending"
+		if currentSequence == nil && kotsKinds.Config != nil {
+			downstreamStatus = "pending_config"
+		} else if kotsKinds.Preflight != nil {
+			downstreamStatus = "pending_preflight"
+		}
+
+		if currentSequence != nil {
+			// there's a small chance this is not optimal, but no current code path
+			// will support multiple downstreams, so this is cleaner here for now
+			licenseSpec, err := kotsKinds.Marshal("kots.io", "v1beta1", "License")
+			if err != nil {
+				return int64(0), errors.Wrap(err, "failed to marshal license spec")
+			}
+			configSpec, err := kotsKinds.Marshal("kots.io", "v1beta1", "Config")
+			if err != nil {
+				return int64(0), errors.Wrap(err, "failed to marshal config spec")
+			}
+			configValuesSpec, err := kotsKinds.Marshal("kots.io", "v1beta1", "ConfigValues")
+			if err != nil {
+				return int64(0), errors.Wrap(err, "failed to marshal configvalues spec")
+			}
+
+			// check if version needs additional configuration
+			t, err := kotsconfig.NeedsConfiguration(configSpec, configValuesSpec, licenseSpec)
+			if err != nil {
+				return int64(0), errors.Wrap(err, "failed to check if version needs configuration")
+			}
+			if t {
+				downstreamStatus = "pending_config"
+			}
+		}
+
+		diffSummary, diffSummaryError := "", ""
+		if currentSequence != nil {
+			// diff this release from the last release
+			diff, err := kustomize.DiffAppVersionsForDownstream(d.Name, filesInDir, previousArchiveDir, kotsKinds.KustomizeVersion())
+			if err != nil {
+				diffSummaryError = errors.Wrap(err, "failed to diff").Error()
+			} else {
+				b, err := json.Marshal(diff)
+				if err != nil {
+					diffSummaryError = errors.Wrap(err, "failed to marshal diff").Error()
+				}
+				diffSummary = string(b)
+			}
+		}
+
+		commitURL, err := gitops.CreateGitOpsDownstreamCommit(appID, d.ClusterID, int(newSequence), filesInDir, d.Name)
+		if err != nil {
+			return int64(0), errors.Wrap(err, "failed to create gitops commit")
+		}
+
+		err = s.addAppVersionToDownstream(tx, appID, d.ClusterID, newSequence,
+			kotsKinds.Installation.Spec.VersionLabel, downstreamStatus, source,
+			diffSummary, diffSummaryError, commitURL, commitURL != "")
+		if err != nil {
+			return int64(0), errors.Wrap(err, "failed to create downstream version")
+		}
+	}
+
+	if err := tx.Commit(); err != nil {
+		return int64(0), errors.Wrap(err, "failed to commit transaction")
+	}
+
+	return newSequence, nil
+}
+
+func (s S3PGStore) createAppVersion(tx *sql.Tx, appID string, currentSequence *int64, appName string, appIcon string, kotsKinds *kotsutil.KotsKinds) (int64, error) {
 	// we marshal these here because it's a decision of the store to cache them in the app version table
 	// not all stores will do this
 	supportBundleSpec, err := kotsKinds.Marshal("troubleshoot.replicated.com", "v1beta1", "Collector")
@@ -313,17 +420,9 @@ func (s S3PGStore) CreateAppVersion(appID string, currentSequence *int64, appNam
 		return int64(0), errors.Wrap(err, "failed to marshal configvalues spec")
 	}
 
-	db := persistence.MustGetPGSession()
-
-	tx, err := db.Begin()
-	if err != nil {
-		return int64(0), errors.Wrap(err, "failed to begin")
-	}
-	defer tx.Rollback()
-
 	newSequence := int64(0)
 	if currentSequence != nil {
-		row := db.QueryRow(`select max(sequence) from app_version where app_id = $1`, appID)
+		row := tx.QueryRow(`select max(sequence) from app_version where app_id = $1`, appID)
 		if err := row.Scan(&newSequence); err != nil {
 			return 0, errors.Wrap(err, "failed to find current max sequence in row")
 		}
@@ -376,18 +475,12 @@ func (s S3PGStore) CreateAppVersion(appID string, currentSequence *int64, appNam
 		return int64(0), errors.Wrap(err, "failed to update app")
 	}
 
-	if err := tx.Commit(); err != nil {
-		return int64(0), errors.Wrap(err, "failed to commit tx")
-	}
-
 	return int64(newSequence), nil
 }
 
-func (s S3PGStore) AddAppVersionToDownstream(appID string, clusterID string, sequence int64, versionLabel string, status string, source string, diffSummary string, diffSummaryError string, commitURL string, gitDeployable bool) error {
-	db := persistence.MustGetPGSession()
-
+func (s S3PGStore) addAppVersionToDownstream(tx *sql.Tx, appID string, clusterID string, sequence int64, versionLabel string, status string, source string, diffSummary string, diffSummaryError string, commitURL string, gitDeployable bool) error {
 	query := `insert into app_downstream_version (app_id, cluster_id, sequence, parent_sequence, created_at, version_label, status, source, diff_summary, diff_summary_error, git_commit_url, git_deployable) values ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12)`
-	_, err := db.Exec(
+	_, err := tx.Exec(
 		query,
 		appID,
 		clusterID,
diff --git a/kotsadm/pkg/store/store_interface.go b/kotsadm/pkg/store/store_interface.go
index 79f7e56e7..afb430e9e 100644
--- a/kotsadm/pkg/store/store_interface.go
+++ b/kotsadm/pkg/store/store_interface.go
@@ -8,6 +8,7 @@ import (
 	apptypes "github.com/replicatedhq/kots/kotsadm/pkg/app/types"
 	appstatustypes "github.com/replicatedhq/kots/kotsadm/pkg/appstatus/types"
 	downstreamtypes "github.com/replicatedhq/kots/kotsadm/pkg/downstream/types"
+	gitopstypes "github.com/replicatedhq/kots/kotsadm/pkg/gitops/types"
 	installationtypes "github.com/replicatedhq/kots/kotsadm/pkg/online/types"
 	preflighttypes "github.com/replicatedhq/kots/kotsadm/pkg/preflight/types"
 	registrytypes "github.com/replicatedhq/kots/kotsadm/pkg/registry/types"
@@ -102,7 +103,6 @@ type AppStatusStore interface {
 type AppStore interface {
 	AddAppToAllDownstreams(appID string) error
 	SetAppInstallState(appID string, state string) error
-	AddAppVersionToDownstream(string, string, int64, string, string, string, string, string, string, bool) error
 	ListInstalledApps() ([]*apptypes.App, error)
 	GetAppIDFromSlug(slug string) (appID string, err error)
 	GetApp(appID string) (*apptypes.App, error)
@@ -130,7 +130,7 @@ type VersionStore interface {
 	IsSnapshotsSupportedForVersion(a *apptypes.App, sequence int64) (bool, error)
 	GetAppVersionArchive(appID string, sequence int64) (archivePath string, err error)
 	CreateAppVersionArchive(appID string, sequence int64, archivePath string) error
-	CreateAppVersion(string, *int64, string, string, *kotsutil.KotsKinds) (int64, error)
+	CreateAppVersion(appID string, currentSequence *int64, appName string, appIcon string, kotsKinds *kotsutil.KotsKinds, filesInDir string, gitops gitopstypes.DownstreamGitOps, source string) (int64, error)
 	GetAppVersion(string, int64) (*versiontypes.AppVersion, error)
 	GetAppVersionsAfter(string, int64) ([]*versiontypes.AppVersion, error)
 }
diff --git a/kotsadm/pkg/version/version.go b/kotsadm/pkg/version/version.go
index 440753610..1b1f6912b 100644
--- a/kotsadm/pkg/version/version.go
+++ b/kotsadm/pkg/version/version.go
@@ -2,13 +2,10 @@ package version
 
 import (
 	"database/sql"
-	"encoding/json"
 	"fmt"
 	"time"
 
 	"github.com/pkg/errors"
-	"github.com/replicatedhq/kots/kotsadm/pkg/config"
-	"github.com/replicatedhq/kots/kotsadm/pkg/downstream"
 	"github.com/replicatedhq/kots/kotsadm/pkg/gitops"
 	"github.com/replicatedhq/kots/kotsadm/pkg/persistence"
 	"github.com/replicatedhq/kots/kotsadm/pkg/secrets"
@@ -47,6 +44,30 @@ func CreateVersion(appID string, filesInDir string, source string, currentSequen
 	return createVersion(appID, filesInDir, source, &currentSequence)
 }
 
+type downstreamGitOps struct {
+}
+
+func (d *downstreamGitOps) CreateGitOpsDownstreamCommit(appID string, clusterID string, newSequence int, filesInDir string, downstreamName string) (string, error) {
+	downstreamGitOps, err := gitops.GetDownstreamGitOps(appID, clusterID)
+	if err != nil {
+		return "", errors.Wrap(err, "failed to get downstream gitops")
+	}
+	if downstreamGitOps == nil {
+		return "", nil
+	}
+
+	a, err := store.GetStore().GetApp(appID)
+	if err != nil {
+		return "", errors.Wrap(err, "failed to get app")
+	}
+	createdCommitURL, err := gitops.CreateGitOpsCommit(downstreamGitOps, a.Slug, a.Name, int(newSequence), filesInDir, downstreamName)
+	if err != nil {
+		return "", errors.Wrap(err, "failed to create gitops commit")
+	}
+
+	return createdCommitURL, nil
+}
+
 // this is the common, internal function to create an app version, used in both
 // new and updates to apps
 func createVersion(appID string, filesInDir string, source string, currentSequence *int64) (int64, error) {
@@ -67,107 +88,13 @@ func createVersion(appID string, filesInDir string, source string, currentSequen
 
 	appIcon := kotsKinds.KotsApplication.Spec.Icon
 
-	newSequence, err := store.GetStore().CreateAppVersion(appID, currentSequence, appName, appIcon, kotsKinds)
-	if err != nil {
-		return int64(0), errors.Wrap(err, "failed to create app version")
-	}
-
-	if err := store.GetStore().CreateAppVersionArchive(appID, int64(newSequence), filesInDir); err != nil {
-		return int64(0), errors.Wrap(err, "failed to create app version archive")
-	}
-
-	previousArchiveDir := ""
-	if currentSequence != nil {
-		// Get the previous archive, we need this to calculate the diff
-		previousDir, err := store.GetStore().GetAppVersionArchive(appID, *currentSequence)
-		if err != nil {
-			return int64(0), errors.Wrap(err, "failed to get previous archive")
-		}
-
-		previousArchiveDir = previousDir
-	}
-
-	downstreams, err := store.GetStore().ListDownstreamsForApp(appID)
-	if err != nil {
-		return int64(0), errors.Wrap(err, "failed to list downstreams")
-	}
-
 	if err := secrets.ReplaceSecretsInPath(filesInDir); err != nil {
 		return int64(0), errors.Wrap(err, "failed to replace secrets")
 	}
 
-	for _, d := range downstreams {
-		downstreamStatus := "pending"
-		if currentSequence == nil && kotsKinds.Config != nil {
-			downstreamStatus = "pending_config"
-		} else if kotsKinds.Preflight != nil {
-			downstreamStatus = "pending_preflight"
-		}
-
-		if currentSequence != nil {
-			// there's a small chance this is not optimal, but no current code path
-			// will support multiple downstreams, so this is cleaner here for now
-			licenseSpec, err := kotsKinds.Marshal("kots.io", "v1beta1", "License")
-			if err != nil {
-				return int64(0), errors.Wrap(err, "failed to marshal license spec")
-			}
-			configSpec, err := kotsKinds.Marshal("kots.io", "v1beta1", "Config")
-			if err != nil {
-				return int64(0), errors.Wrap(err, "failed to marshal config spec")
-			}
-			configValuesSpec, err := kotsKinds.Marshal("kots.io", "v1beta1", "ConfigValues")
-			if err != nil {
-				return int64(0), errors.Wrap(err, "failed to marshal configvalues spec")
-			}
-
-			// check if version needs additional configuration
-			t, err := config.NeedsConfiguration(configSpec, configValuesSpec, licenseSpec)
-			if err != nil {
-				return int64(0), errors.Wrap(err, "failed to check if version needs configuration")
-			}
-			if t {
-				downstreamStatus = "pending_config"
-			}
-		}
-
-		diffSummary, diffSummaryError := "", ""
-		if currentSequence != nil {
-			// diff this release from the last release
-			diff, err := downstream.DiffAppVersionsForDownstream(d.Name, filesInDir, previousArchiveDir, kotsKinds.KustomizeVersion())
-			if err != nil {
-				diffSummaryError = errors.Wrap(err, "failed to diff").Error()
-			} else {
-				b, err := json.Marshal(diff)
-				if err != nil {
-					diffSummaryError = errors.Wrap(err, "failed to marshal diff").Error()
-				}
-				diffSummary = string(b)
-			}
-		}
-
-		commitURL := ""
-		downstreamGitOps, err := gitops.GetDownstreamGitOps(appID, d.ClusterID)
-		if err != nil {
-			return int64(0), errors.Wrap(err, "failed to get downstream gitops")
-		}
-		if downstreamGitOps != nil {
-			a, err := store.GetStore().GetApp(appID)
-			if err != nil {
-				return int64(0), errors.Wrap(err, "failed to get app")
-			}
-			createdCommitURL, err := gitops.CreateGitOpsCommit(downstreamGitOps, a.Slug, a.Name, int(newSequence), filesInDir, d.Name)
-			if err != nil {
-				return int64(0), errors.Wrap(err, "failed to create gitops commit")
-			}
-			commitURL = createdCommitURL
-		}
-
-		err = store.GetStore().AddAppVersionToDownstream(appID, d.ClusterID, newSequence,
-			kotsKinds.Installation.Spec.VersionLabel, downstreamStatus, source,
-			diffSummary, diffSummaryError, commitURL, commitURL != "")
-		if err != nil {
-			return int64(0), errors.Wrap(err, "failed to create downstream version")
-		}
+	newSequence, err := store.GetStore().CreateAppVersion(appID, currentSequence, appName, appIcon, kotsKinds, filesInDir, &downstreamGitOps{}, source)
+	if err != nil {
+		return int64(0), errors.Wrap(err, "failed to create app version")
 	}
 
 	return int64(newSequence), nil
diff --git a/kotsadm/pkg/downstream/diff.go b/pkg/kustomize/diff.go
similarity index 99%
rename from kotsadm/pkg/downstream/diff.go
rename to pkg/kustomize/diff.go
index c9bab4bf7..c803131f6 100644
--- a/kotsadm/pkg/downstream/diff.go
+++ b/pkg/kustomize/diff.go
@@ -1,4 +1,4 @@
-package downstream
+package kustomize
 
 import (
 	"bufio"


Test patch:
diff --git a/kotsadm/pkg/downstream/diff_test.go b/pkg/kustomize/diff_test.go
similarity index 98%
rename from kotsadm/pkg/downstream/diff_test.go
rename to pkg/kustomize/diff_test.go
index 29676e922..9cc1853d3 100644
--- a/kotsadm/pkg/downstream/diff_test.go
+++ b/pkg/kustomize/diff_test.go
@@ -1,4 +1,4 @@
-package downstream
+package kustomize
 
 import (
 	"testing"