Repository: replicatedhq/kots
Pull request #62 (2019-09-20 18:45:24)

Support for airgap installs from the command line

marccampbell: This PR adds support for deploying an airgap install from the command line.

Given: a logged in registry at registry.somebigbank.com:

```
kubectl kots pull replicated://sentry-enterprise \
    --license-file ~/sentry-prod-license.yaml \
    --rewrite-images \
    --image-namespace sentry \
    --registry-endpoint registry.somebigbank.com 
    --shared-password password
```

This will result in all images from the deployment (application and kotsadm) retagged as registry.somebigbank.com/sentry/* and pushed. Also the midstream kustomization yaml will now contain:

```
apiVersion: kustomize.config.k8s.io/v1beta1
bases:
- ../../base
images:
- name: docker.io/bitnami/redis:4.0.11-debian-9
  newName: registry.somebigbank.com/sentry/redis
  newTag: 4.0.11-debian-9
- name: docker.io/kotsadm/kotsadm-api:alpha
  newName: registry.somebigbank.com/sentry/kotsadm-api
  newTag: alpha
- name: docker.io/kotsadm/kotsadm-operator:alpha
  newName: registry.somebigbank.com/sentry/kotsadm-operator
  newTag: alpha
- name: docker.io/kotsadm/kotsadm-web:alpha
  newName: registry.somebigbank.com/sentry/kotsadm-web
  newTag: alpha
- name: docker.io/library/postgres:10.7
  newName: registry.somebigbank.com/sentry/postgres
  newTag: "10.7"
- name: docker.io/library/postgres:9.6
  newName: registry.somebigbank.com/sentry/postgres
  newTag: "9.6"
- name: docker.io/library/sentry:9.1.1
  newName: registry.somebigbank.com/sentry/sentry
  newTag: 9.1.1
- name: docker.io/minio/mc:RELEASE.2019-07-17T22-13-42Z
  newName: registry.somebigbank.com/sentry/mc
  newTag: RELEASE.2019-07-17T22-13-42Z
- name: docker.io/minio/minio:RELEASE.2019-05-14T23-57-45Z
  newName: registry.somebigbank.com/sentry/minio
  newTag: RELEASE.2019-05-14T23-57-45Z
kind: Kustomization
```

Patch:
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 000000000..d57da7bc1
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,6 @@
+# Contributing to Kots
+
+This is intended te be the guide describing how to contribute to Kots and how to set up a dev environment.
+
+
+
diff --git a/Makefile b/Makefile
index b832abb83..91eac1846 100644
--- a/Makefile
+++ b/Makefile
@@ -32,26 +32,27 @@ define LDFLAGS
 "
 endef
 
+BUILDTAGS = containers_image_ostree_stub exclude_graphdriver_devicemapper exclude_graphdriver_btrfs containers_image_openpgp
+
 .PHONY: test
 test:
-	go test ./pkg/... ./cmd/... ./ffi/... -coverprofile cover.out
+	go test -tags "$(BUILDTAGS)" ./pkg/... ./cmd/... ./ffi/... -coverprofile cover.out
 
 .PHONY: integration-cli
 integration-cli:
 	go build -o bin/kots-integration ./integration
-	
+
 .PHONY: ci-test
 ci-test:
-	go test ./pkg/... ./cmd/... ./ffi/... ./integration/... -coverprofile cover.out
+	go test -tags "$(BUILDTAGS)" ./pkg/... ./cmd/... ./ffi/... ./integration/... -coverprofile cover.out
 
-	
 .PHONY: kots
 kots: fmt vet
-	go build ${LDFLAGS} -o bin/kots github.com/replicatedhq/kots/cmd/kots
+	go build ${LDFLAGS} -o bin/kots -tags "$(BUILDTAGS)" github.com/replicatedhq/kots/cmd/kots
 
 .PHONY: ffi
 ffi: fmt vet
-	go build ${LDFLAGS} -o bin/kots.so -buildmode=c-shared ./ffi/...
+	go build ${LDFLAGS} -o bin/kots.so -tags "$(BUILDTAGS)" -buildmode=c-shared ./ffi/...
 
 .PHONY: fmt
 fmt:
@@ -59,7 +60,7 @@ fmt:
 
 .PHONY: vet
 vet:
-	go vet ./pkg/... ./cmd/... ./ffi/...
+	go vet -tags "$(BUILDTAGS)" ./pkg/... ./cmd/... ./ffi/...
 
 .PHONY: gosec
 gosec:
diff --git a/cmd/kots/cli/pull.go b/cmd/kots/cli/pull.go
index b4f071bfc..36402a7b9 100644
--- a/cmd/kots/cli/pull.go
+++ b/cmd/kots/cli/pull.go
@@ -28,6 +28,9 @@ func PullCmd() *cobra.Command {
 				os.Exit(1)
 			}
 
+			// registry host should not have the scheme (https).  need to
+			// strip it if included or else the rewrite images will fail
+
 			pullOptions := pull.PullOptions{
 				HelmRepoURI:         v.GetString("repo"),
 				RootDir:             ExpandDir(v.GetString("rootdir")),
@@ -40,6 +43,11 @@ func PullCmd() *cobra.Command {
 				SharedPassword:      v.GetString("shared-password"),
 				CreateAppDir:        true,
 				HelmOptions:         v.GetStringSlice("set"),
+				RewriteImages:       v.GetBool("rewrite-images"),
+				RewriteImageOptions: pull.RewriteImageOptions{
+					Host:      v.GetString("registry-endpoint"),
+					Namespace: v.GetString("image-namespace"),
+				},
 			}
 
 			renderDir, err := pull.Pull(args[0], pullOptions)
@@ -67,11 +75,14 @@ func PullCmd() *cobra.Command {
 	cmd.Flags().String("rootdir", homeDir(), "root directory that will be used to write the yaml to")
 	cmd.Flags().String("namespace", "default", "namespace to render the upstream to in the base")
 	cmd.Flags().StringSlice("downstream", []string{}, "the list of any downstreams to create/update")
-	cmd.Flags().String("local-path", "", "specify a local-path to test the behavior of rendering a replicated app locally (only supported on replicated app types currently)")
+	cmd.Flags().String("local-path", "", "specify a local-path to pull a locally available replicated app (only supported on replicated app types currently)")
 	cmd.Flags().String("license-file", "", "path to a license file to use when download a replicated app")
 	cmd.Flags().Bool("exclude-kots-kinds", true, "set to true to exclude rendering kots custom objects to the base directory")
 	cmd.Flags().Bool("exclude-admin-console", false, "set to true to exclude the admin console (replicated apps only)")
 	cmd.Flags().String("shared-password", "", "shared password to use when deploying the admin console")
+	cmd.Flags().Bool("rewrite-images", false, "set to true to force all container images to be rewritten and pushed to a local registry")
+	cmd.Flags().String("image-namespace", "", "the namespace/org in the docker registry to push images to (required when --rewrite-images is set)")
+	cmd.Flags().String("registry-endpoint", "", "the endpoint of the local docker registry to use when pushing images (required when --rewrite-images is set)")
 
 	return cmd
 }
diff --git a/deploy/.goreleaser.snapshot.yml b/deploy/.goreleaser.snapshot.yml
index 955f8a0e8..24db94aa8 100644
--- a/deploy/.goreleaser.snapshot.yml
+++ b/deploy/.goreleaser.snapshot.yml
@@ -21,7 +21,7 @@ builds:
       -X github.com/replicatedhq/kots/pkg/version.gitSHA={{.FullCommit}}
       -X github.com/replicatedhq/kots/pkg/version.buildTime={{.Date}}
       -extldflags "-static"
-    flags: -tags netgo -installsuffix netgo
+    flags: -tags netgo containers_image_ostree_stub exclude_graphdriver_devicemapper exclude_graphdriver_btrfs containers_image_openpgp -installsuffix netgo
     binary: kots
     hooks: {}
   - id: so
diff --git a/deploy/.goreleaser.yml b/deploy/.goreleaser.yml
index ab71b3032..ff4cfb1df 100644
--- a/deploy/.goreleaser.yml
+++ b/deploy/.goreleaser.yml
@@ -21,7 +21,7 @@ builds:
       -X github.com/replicatedhq/kots/pkg/version.gitSHA={{.FullCommit}}
       -X github.com/replicatedhq/kots/pkg/version.buildTime={{.Date}}
       -extldflags "-static"
-    flags: -tags netgo -installsuffix netgo
+    flags: -tags netgo containers_image_ostree_stub exclude_graphdriver_devicemapper exclude_graphdriver_btrfs containers_image_openpgp -installsuffix netgo
     binary: kots
     hooks: {}
   - id: so
diff --git a/ffi/airgap.go b/ffi/airgap.go
index ed025781d..09eabdf93 100644
--- a/ffi/airgap.go
+++ b/ffi/airgap.go
@@ -31,7 +31,7 @@ type ImageRef struct {
 }
 
 //export RewriteAndPushImageName
-func RewriteAndPushImageName(socket, imageFile, image, format, registryHost, registryOrg, username, password string) {
+func RewriteAndPushImageName(socket, imageFile, image, registryHost, registryOrg, username, password string) {
 	go func() {
 		var ffiResult *FFIResult
 
@@ -60,7 +60,7 @@ func RewriteAndPushImageName(socket, imageFile, image, format, registryHost, reg
 			cmdArgs = append(cmdArgs, fmt.Sprintf("--dest-creds=%s:%s", username, password))
 		}
 		cmdArgs = append(cmdArgs,
-			fmt.Sprintf("%s:%s", format, imageFile),
+			fmt.Sprintf("oci-archive:%s", imageFile),
 			fmt.Sprintf("docker://%s", localImage),
 		)
 
diff --git a/go.mod b/go.mod
index 9571eeac1..50356cabe 100644
--- a/go.mod
+++ b/go.mod
@@ -4,45 +4,85 @@ go 1.12
 
 require (
 	cloud.google.com/go v0.36.0 // indirect
+	github.com/14rcole/gopopulate v0.0.0-20180821133914-b175b219e774 // indirect
+	github.com/DataDog/zstd v1.4.0 // indirect
 	github.com/Masterminds/goutils v1.1.0 // indirect
 	github.com/Masterminds/semver v1.4.2
 	github.com/Masterminds/semver/v3 v3.0.1
 	github.com/Masterminds/sprig v2.20.0+incompatible
+	github.com/Microsoft/go-winio v0.4.12 // indirect
+	github.com/Microsoft/hcsshim v0.8.6 // indirect
+	github.com/VividCortex/ewma v1.1.1 // indirect
 	github.com/ahmetalpbalkan/go-cursor v0.0.0-20131010032410-8136607ea412
+	github.com/alecthomas/units v0.0.0-20190910110746-680d30ca3117 // indirect
+	github.com/containerd/continuity v0.0.0-20190827140505-75bee3e2ccb6 // indirect
+	github.com/containers/image v3.0.2+incompatible
+	github.com/containers/storage v1.12.13 // indirect
 	github.com/cyphar/filepath-securejoin v0.2.2 // indirect
-	github.com/docker/distribution v2.7.1+incompatible
+	github.com/docker/distribution v0.0.0-20170817175659-5f6282db7d65
+	github.com/docker/docker v0.0.0-20180522102801-da99009bbb11 // indirect
+	github.com/docker/docker-credential-helpers v0.6.3 // indirect
+	github.com/docker/go-connections v0.4.0 // indirect
 	github.com/docker/go-units v0.4.0
+	github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7 // indirect
 	github.com/docker/spdystream v0.0.0-20181023171402-6480d4af844c // indirect
 	github.com/dsnet/compress v0.0.1 // indirect
 	github.com/elazarl/goproxy v0.0.0-20190711103511-473e67f1d7d2 // indirect
 	github.com/elazarl/goproxy/ext v0.0.0-20190711103511-473e67f1d7d2 // indirect
+	github.com/etcd-io/bbolt v1.3.3 // indirect
 	github.com/fatih/color v1.7.0
 	github.com/frankban/quicktest v1.4.1 // indirect
 	github.com/ghodss/yaml v1.0.0
 	github.com/gobwas/glob v0.2.3 // indirect
+	github.com/golang/protobuf v1.3.2 // indirect
 	github.com/golang/snappy v0.0.1 // indirect
 	github.com/google/go-github/v28 v28.1.1
 	github.com/google/uuid v1.1.1
+	github.com/gorilla/mux v1.7.3 // indirect
+	github.com/gotestyourself/gotestyourself v2.2.0+incompatible // indirect
 	github.com/huandu/xstrings v1.2.0 // indirect
 	github.com/imdario/mergo v0.3.7 // indirect
+	github.com/json-iterator/go v1.1.7 // indirect
+	github.com/klauspost/compress v1.7.2 // indirect
+	github.com/klauspost/cpuid v1.2.1 // indirect
+	github.com/klauspost/pgzip v1.2.1 // indirect
 	github.com/manifoldco/promptui v0.3.2
 	github.com/mattn/go-colorable v0.1.2 // indirect
+	github.com/mattn/go-shellwords v1.0.5 // indirect
 	github.com/mholt/archiver v3.1.1+incompatible
+	github.com/mistifyio/go-zfs v2.1.1+incompatible // indirect
+	github.com/mtrmac/gpgme v0.0.0-20170102180018-b2432428689c // indirect
 	github.com/nwaples/rardecode v1.0.0 // indirect
 	github.com/opencontainers/go-digest v1.0.0-rc1 // indirect
+	github.com/opencontainers/image-spec v1.0.1 // indirect
+	github.com/opencontainers/runc v1.0.0-rc8 // indirect
+	github.com/opencontainers/selinux v1.2.2 // indirect
+	github.com/ostreedev/ostree-go v0.0.0-20190702140239-759a8c1ac913 // indirect
 	github.com/otiai10/copy v1.0.2
 	github.com/pierrec/lz4 v2.2.6+incompatible // indirect
 	github.com/pkg/errors v0.8.1
+	github.com/pquerna/ffjson v0.0.0-20181028064349-e517b90714f7 // indirect
+	github.com/sirupsen/logrus v1.4.2 // indirect
 	github.com/spf13/cobra v0.0.5
 	github.com/spf13/viper v1.4.0
-	github.com/stretchr/testify v1.3.0
+	github.com/stretchr/testify v1.4.0
+	github.com/syndtr/gocapability v0.0.0-20180916011248-d98352740cb2 // indirect
+	github.com/tchap/go-patricia v2.3.0+incompatible // indirect
 	github.com/tj/go-spin v1.1.0
+	github.com/vbatts/tar-split v0.11.1 // indirect
+	github.com/vbauerster/mpb v3.4.0+incompatible // indirect
+	github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
+	github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
+	github.com/xeipuuv/gojsonschema v1.1.0 // indirect
 	github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect
 	golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4
+	golang.org/x/net v0.0.0-20190628185345-da137c7871d7 // indirect
 	golang.org/x/oauth2 v0.0.0-20190402181905-9f3314589c9a // indirect
+	golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3 // indirect
 	google.golang.org/appengine v1.5.0 // indirect
 	gopkg.in/alecthomas/kingpin.v3-unstable v3.0.0-20180810215634-df19058c872c // indirect
 	gopkg.in/yaml.v2 v2.2.2
+	gotest.tools v0.0.0-20190624233834-05ebafbffc79 // indirect
 	k8s.io/api v0.0.0-20190409021203-6e4e0e4f393b
 	k8s.io/apimachinery v0.0.0-20190404173353-6a84e37a896d
 	k8s.io/client-go v11.0.1-0.20190409021438-1a26190bd76a+incompatible
diff --git a/go.sum b/go.sum
index 32836ad9f..804cff588 100644
--- a/go.sum
+++ b/go.sum
@@ -10,8 +10,12 @@ dmitri.shuralyov.com/html/belt v0.0.0-20180602232347-f7d459c86be0/go.mod h1:JLBr
 dmitri.shuralyov.com/service/change v0.0.0-20181023043359-a85b471d5412/go.mod h1:a1inKt/atXimZ4Mv927x+r7UpyzRUf4emIoiiSC2TN4=
 dmitri.shuralyov.com/state v0.0.0-20180228185332-28bcc343414c/go.mod h1:0PRwlb0D6DFvNNtx+9ybjezNCa8XF0xaYcETyp6rHWU=
 git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg=
+github.com/14rcole/gopopulate v0.0.0-20180821133914-b175b219e774 h1:SCbEWT58NSt7d2mcFdvxC9uyrdcTfvBbPLThhkDmXzg=
+github.com/14rcole/gopopulate v0.0.0-20180821133914-b175b219e774/go.mod h1:6/0dYRLLXyJjbkIPeeGyoJ/eKOSI0eU6eTlCBYibgd0=
 github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
 github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
+github.com/DataDog/zstd v1.4.0 h1:vhoV+DUHnRZdKW1i5UMjAk2G4JY8wN4ayRfYDNdEhwo=
+github.com/DataDog/zstd v1.4.0/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo=
 github.com/Masterminds/goutils v1.1.0 h1:zukEsf/1JZwCMgHiK3GZftabmxiCw4apj3a28RPBiVg=
 github.com/Masterminds/goutils v1.1.0/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
 github.com/Masterminds/semver v1.4.2 h1:WBLTQ37jOCzSLtXNdoo8bNM8876KhNqOKvrlGITgsTc=
@@ -20,6 +24,10 @@ github.com/Masterminds/semver/v3 v3.0.1 h1:2kKm5lb7dKVrt5TYUiAavE6oFc1cFT0057UVG
 github.com/Masterminds/semver/v3 v3.0.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
 github.com/Masterminds/sprig v2.20.0+incompatible h1:dJTKKuUkYW3RMFdQFXPU/s6hg10RgctmTjRcbZ98Ap8=
 github.com/Masterminds/sprig v2.20.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o=
+github.com/Microsoft/go-winio v0.4.12 h1:xAfWHN1IrQ0NJ9TBC0KBZoqLjzDTr1ML+4MywiUOryc=
+github.com/Microsoft/go-winio v0.4.12/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA=
+github.com/Microsoft/hcsshim v0.8.6 h1:ZfF0+zZeYdzMIVMZHKtDKJvLHj76XCuVae/jNkjj0IA=
+github.com/Microsoft/hcsshim v0.8.6/go.mod h1:Op3hHsoHPAvb6lceZHDtd9OkTew38wNoXnJs8iY7rUg=
 github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ=
 github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
 github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
@@ -28,6 +36,8 @@ github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbt
 github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
 github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M=
 github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
+github.com/VividCortex/ewma v1.1.1 h1:MnEK4VOv6n0RSY4vtRe3h11qjxL3+t0B8yOL8iMXdcM=
+github.com/VividCortex/ewma v1.1.1/go.mod h1:2Tkkvm3sRDVXaiyucHiACn4cqf7DpdyLvmxzcbUokwA=
 github.com/ahmetalpbalkan/go-cursor v0.0.0-20131010032410-8136607ea412 h1:vOVO0ypMfTt6tZacyI0kp+iCZb1XSNiYDqnzBWYgfe4=
 github.com/ahmetalpbalkan/go-cursor v0.0.0-20131010032410-8136607ea412/go.mod h1:AI9hp1tkp10pAlK5TCwL+7yWbRgtDm9jhToq6qij2xs=
 github.com/alecthomas/gometalinter v2.0.11+incompatible h1:ENdXMllZNSVDTJUUVIzBW9CSEpntTrQa76iRsEFLX/M=
@@ -35,6 +45,8 @@ github.com/alecthomas/gometalinter v2.0.11+incompatible/go.mod h1:qfIpQGGz3d+Nmg
 github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
 github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf h1:qet1QNfXsQxTZqLG4oE62mJzwPIB8+Tee4RNCL9ulrY=
 github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
+github.com/alecthomas/units v0.0.0-20190910110746-680d30ca3117 h1:aUo+WrWZtRRfc6WITdEKzEczFRlEpfW15NhNeLRc17U=
+github.com/alecthomas/units v0.0.0-20190910110746-680d30ca3117/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
 github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
 github.com/appscode/jsonpatch v0.0.0-20190108182946-7c0e3b262f30 h1:Kn3rqvbUFqSepE2OqVu0Pn1CbDw9IuMlONapol0zuwk=
 github.com/appscode/jsonpatch v0.0.0-20190108182946-7c0e3b262f30/go.mod h1:4AJxUpXUhv4N+ziTvIcWWXgeorXpxPZOfk9HdEVr96M=
@@ -52,6 +64,12 @@ github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 h1:q763qf9huN11kDQavWs
 github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
 github.com/client9/misspell v0.3.4 h1:ta993UF76GwbvJcIo3Y68y/M3WxlpEHPWIGDkJYwzJI=
 github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
+github.com/containerd/continuity v0.0.0-20190827140505-75bee3e2ccb6 h1:NmTXa/uVnDyp0TY5MKi197+3HWcnYWfnHGyaFthlnGw=
+github.com/containerd/continuity v0.0.0-20190827140505-75bee3e2ccb6/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y=
+github.com/containers/image v3.0.2+incompatible h1:B1lqAE8MUPCrsBLE86J0gnXleeRq8zJnQryhiiGQNyE=
+github.com/containers/image v3.0.2+incompatible/go.mod h1:8Vtij257IWSanUQKe1tAeNOm2sRVkSqQTVQ1IlwI3+M=
+github.com/containers/storage v1.12.13 h1:GtaLCY8p1Drlk1Oew581jGvB137UaO+kpz0HII67T0A=
+github.com/containers/storage v1.12.13/go.mod h1:+RirK6VQAqskQlaTBrOG6ulDvn4si2QjFE1NZCn06MM=
 github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
 github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
 github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
@@ -68,10 +86,18 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
 github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
-github.com/docker/distribution v2.7.1+incompatible h1:a5mlkVzth6W5A4fOsS3D2EO5BUmsJpcB+cRlLU7cSug=
-github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
+github.com/docker/distribution v0.0.0-20170817175659-5f6282db7d65 h1:4zlOyrJUbYnrvlzChJ+jP2J3i77Jbhm336NEuCv7kZo=
+github.com/docker/distribution v0.0.0-20170817175659-5f6282db7d65/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
+github.com/docker/docker v0.0.0-20180522102801-da99009bbb11 h1:p8hSDXZgVhyh/C9bPlG8QMY64VeXtVfjmjIlzaQok5Q=
+github.com/docker/docker v0.0.0-20180522102801-da99009bbb11/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
+github.com/docker/docker-credential-helpers v0.6.3 h1:zI2p9+1NQYdnG6sMU26EX4aVGlqbInSQxQXLvzJ4RPQ=
+github.com/docker/docker-credential-helpers v0.6.3/go.mod h1:WRaJzqw3CTB9bk10avuGsjVBZsD05qeibJ1/TYlvc0Y=
+github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ=
+github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
 github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw=
 github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
+github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7 h1:UhxFibDNY/bfvqU5CAUmr9zpesgbU6SWc8/B4mflAE4=
+github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7/go.mod h1:cyGadeNEkKy96OOhEzfZl+yxihPEzKnqJwvfuSUqbZE=
 github.com/docker/spdystream v0.0.0-20181023171402-6480d4af844c h1:ZfSZ3P3BedhKGUhzj7BQlPSU4OvT6tfOKe3DVHzOA7s=
 github.com/docker/spdystream v0.0.0-20181023171402-6480d4af844c/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM=
 github.com/dsnet/compress v0.0.1 h1:PlZu0n3Tuv04TzpfPbrnI0HW/YwodEXDS+oPKahKF0Q=
@@ -85,6 +111,8 @@ github.com/elazarl/goproxy/ext v0.0.0-20190711103511-473e67f1d7d2/go.mod h1:gNh8
 github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
 github.com/emicklei/go-restful v2.9.6+incompatible h1:tfrHha8zJ01ywiOEC1miGY8st1/igzWB8OmvPgoYX7w=
 github.com/emicklei/go-restful v2.9.6+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
+github.com/etcd-io/bbolt v1.3.3 h1:gSJmxrs37LgTqR/oyJBWok6k6SvXEUerFTbltIhXkBM=
+github.com/etcd-io/bbolt v1.3.3/go.mod h1:ZF2nL25h33cCyBtcyWeZ2/I3HQOfTP+0PIEvHjkjCrw=
 github.com/evanphx/json-patch v4.0.0+incompatible h1:xregGRMLBeuRcwiOTHRCsPPuzCQlqhxUPbqdw+zNkLc=
 github.com/evanphx/json-patch v4.0.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
 github.com/evanphx/json-patch v4.1.0+incompatible h1:K1MDoo4AZ4wU0GIU/fPmtZg7VpzLjCxu+UwBD1FvwOc=
@@ -144,6 +172,8 @@ github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM
 github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
 github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg=
 github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs=
+github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
 github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4=
 github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
 github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
@@ -181,7 +211,11 @@ github.com/googleapis/gnostic v0.3.0/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTV
 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=
+github.com/gorilla/mux v1.7.3 h1:gnP5JzjVOuiZD07fKKToCAOjS0yOpj/qPETTXCCS6hw=
+github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
 github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
+github.com/gotestyourself/gotestyourself v2.2.0+incompatible h1:AQwinXlbQR2HvPjQZOmDhRqsv5mZf+Jb1RnSLxcqZcI=
+github.com/gotestyourself/gotestyourself v2.2.0+incompatible/go.mod h1:zZKM6oeNM8k+FRljX1mnzVYeS8wiGgQyvST1/GafPbY=
 github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
 github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
 github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
@@ -207,13 +241,24 @@ github.com/json-iterator/go v0.0.0-20180612202835-f2b4162afba3/go.mod h1:+SdeFBv
 github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
 github.com/json-iterator/go v1.1.6 h1:MrUvLMLTMxbqFJ9kzlvat/rYZqZnW3u4wkLzWTaFwKs=
 github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
+github.com/json-iterator/go v1.1.7 h1:KfgG9LzI+pYjr4xvmz/5H4FXjokeP+rlHLhv3iH62Fo=
+github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
 github.com/juju/ansiterm v0.0.0-20180109212912-720a0952cc2a h1:FaWFmfWdAUKbSCtOU2QjDaorUexogfaMgbipgYATUMU=
 github.com/juju/ansiterm v0.0.0-20180109212912-720a0952cc2a/go.mod h1:UJSiEoRfvx3hP73CvoARgeLjaIOjybY9vj8PUPPFGeU=
 github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
 github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
 github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
+github.com/klauspost/compress v1.4.1 h1:8VMb5+0wMgdBykOV96DwNwKFQ+WTI4pzYURP99CcB9E=
 github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
+github.com/klauspost/compress v1.7.2 h1:liMOoeIvFpr9kEvalrZ7VVBA4wGf7zfOgwBjzz/5g2Y=
+github.com/klauspost/compress v1.7.2/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
+github.com/klauspost/cpuid v1.2.0 h1:NMpwD2G9JSFOE1/TJjGSo5zG7Yb2bTe7eq1jH+irmeE=
 github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
+github.com/klauspost/cpuid v1.2.1 h1:vJi+O/nMdFt0vqm8NZBI6wzALWdA2X+egi0ogNyrC/w=
+github.com/klauspost/cpuid v1.2.1/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
+github.com/klauspost/pgzip v1.2.1 h1:oIPZROsWuPHpOdMVWLuJZXwgjhrW8r1yEX8UqMyeNHM=
+github.com/klauspost/pgzip v1.2.1/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
+github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk=
 github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
 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=
@@ -241,21 +286,28 @@ github.com/mattn/go-isatty v0.0.4 h1:bnP0vzxcAdeI1zdubAl5PjU6zsERjGZb7raWodagDYs
 github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
 github.com/mattn/go-isatty v0.0.8 h1:HLtExJ+uU2HOZ+wI0Tt5DtUDrx8yhUqDcp7fYERX4CE=
 github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
+github.com/mattn/go-shellwords v1.0.5 h1:JhhFTIOslh5ZsPrpa3Wdg8bF0WI3b44EMblmU9wIsXc=
+github.com/mattn/go-shellwords v1.0.5/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o=
 github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
 github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
 github.com/mholt/archiver v3.1.1+incompatible h1:1dCVxuqs0dJseYEhi5pl7MYPH9zDa1wBi7mF09cbNkU=
 github.com/mholt/archiver v3.1.1+incompatible/go.mod h1:Dh2dOXnSdiLxRiPoVfIr/fI1TwETms9B8CTWfeh7ROU=
 github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4=
+github.com/mistifyio/go-zfs v2.1.1+incompatible h1:gAMO1HM9xBRONLHHYnu5iFsOJUiJdNZo6oqSENd4eW8=
+github.com/mistifyio/go-zfs v2.1.1+incompatible/go.mod h1:8AuVvqP/mXw1px98n46wfvcGfQ4ci2FwoAjKYxuo3Z4=
 github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
 github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
 github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE=
 github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
+github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
 github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
 github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
 github.com/modern-go/reflect2 v0.0.0-20180320133207-05fbef0ca5da/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
 github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
 github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI=
 github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
+github.com/mtrmac/gpgme v0.0.0-20170102180018-b2432428689c h1:xa+eQWKuJ9MbB9FBL/eoNvDFvveAkz2LQoz8PzX7Q/4=
+github.com/mtrmac/gpgme v0.0.0-20170102180018-b2432428689c/go.mod h1:GhAqVMEWnTcW2dxoD/SO3n2enrgWl3y6Dnx4m59GvcA=
 github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
 github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
 github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo=
@@ -273,7 +325,15 @@ github.com/onsi/gomega v1.5.0 h1:izbySO9zDPmjJ8rDjLvkA2zJHIo+HkYXHnf7eN7SSyo=
 github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
 github.com/opencontainers/go-digest v1.0.0-rc1 h1:WzifXhOVOEOuFYOJAW6aQqW0TooG2iki3E3Ii+WN7gQ=
 github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s=
+github.com/opencontainers/image-spec v1.0.1 h1:JMemWkRwHx4Zj+fVxWoMCFm/8sYGGrUVojFA6h/TRcI=
+github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
+github.com/opencontainers/runc v1.0.0-rc8 h1:dDCFes8Hj1r/i5qnypONo5jdOme/8HWZC/aNDyhECt0=
+github.com/opencontainers/runc v1.0.0-rc8/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U=
+github.com/opencontainers/selinux v1.2.2 h1:Kx9J6eDG5/24A6DtUquGSpJQ+m2MUTahn4FtGEe8bFg=
+github.com/opencontainers/selinux v1.2.2/go.mod h1:+BLncwf63G4dgOzykXAxcmnFlUaOlkDdmw/CqsW6pjs=
 github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8=
+github.com/ostreedev/ostree-go v0.0.0-20190702140239-759a8c1ac913 h1:TnbXhKzrTOyuvWrjI8W6pcoI9XPbLHFXCdN2dtUw7Rw=
+github.com/ostreedev/ostree-go v0.0.0-20190702140239-759a8c1ac913/go.mod h1:J6OG6YJVEWopen4avK3VNQSnALmmjvniMmni/YFYAwc=
 github.com/otiai10/copy v1.0.2 h1:DDNipYy6RkIkjMwy+AWzgKiNTyj2RUI9yEMeETEpVyc=
 github.com/otiai10/copy v1.0.2/go.mod h1:c7RpqBkwMom4bYTSkLSym4VSJz/XtncWRAj/J4PEIMY=
 github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95 h1:+OLn68pqasWca0z5ryit9KGfp3sUsW4Lqg32iRMJyzs=
@@ -293,6 +353,8 @@ github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE
 github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/pquerna/ffjson v0.0.0-20181028064349-e517b90714f7 h1:gGBSHPOU7g8YjTbhwn+lvFm2VDEhhA+PwDIlstkgSxE=
+github.com/pquerna/ffjson v0.0.0-20181028064349-e517b90714f7/go.mod h1:YARuvh7BUWHNhzDq2OM5tzR2RiCcN2D7sapiKyCel/M=
 github.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
 github.com/prometheus/client_golang v0.9.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
 github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
@@ -336,7 +398,10 @@ github.com/shurcooL/reactions v0.0.0-20181006231557-f2e0b4ca5b82/go.mod h1:TCR1l
 github.com/shurcooL/sanitized_anchor_name v0.0.0-20170918181015-86672fcb3f95/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
 github.com/shurcooL/users v0.0.0-20180125191416-49c67e49c537/go.mod h1:QJTqeLYEDaXHZDBsXlPCDqdhQuJkuw4NOtaxYe3xii4=
 github.com/shurcooL/webdavfs v0.0.0-20170829043945-18c3829fa133/go.mod h1:hKmq5kWdCj2z2KEozexVbfEZIWiTjhE0+UjmZgPqehw=
+github.com/sirupsen/logrus v1.2.0 h1:juTguoYk5qI21pwyTXY3B3Y5cOTH3ZUyZCg1v/mihuo=
 github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
+github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4=
+github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
 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=
 github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e/go.mod h1:HuIsMU8RRBOtsCgI77wP899iHVBQpCmg4ErYMZB+2IA=
@@ -368,7 +433,13 @@ github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1
 github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
 github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
 github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
+github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
+github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
+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=
 github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA=
+github.com/tchap/go-patricia v2.3.0+incompatible h1:GkY4dP3cEfEASBPPkWd+AmjYxhmDkqO9/zg7R0lSQRs=
+github.com/tchap/go-patricia v2.3.0+incompatible/go.mod h1:bmLyhP68RS6kStMGxByiQ23RP/odRBOTVjwp2cDyi6I=
 github.com/tj/go-spin v1.1.0 h1:lhdWZsvImxvZ3q1C5OIB7d72DuOwP4O2NdBg9PyzNds=
 github.com/tj/go-spin v1.1.0/go.mod h1:Mg1mzmePZm4dva8Qz60H2lHwmJ2loum4VIrLgVnKwh4=
 github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
@@ -379,10 +450,21 @@ github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGr
 github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
 github.com/ulikunitz/xz v0.5.6 h1:jGHAfXawEGZQ3blwU5wnWKQJvAraT7Ftq9EXjnXYgt8=
 github.com/ulikunitz/xz v0.5.6/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8=
+github.com/vbatts/tar-split v0.11.1 h1:0Odu65rhcZ3JZaPHxl7tCI3V/C/Q9Zf82UFravl02dE=
+github.com/vbatts/tar-split v0.11.1/go.mod h1:LEuURwDEiWjRjwu46yU3KVGuUdVv/dcnpcEPSzR8z6g=
+github.com/vbauerster/mpb v3.4.0+incompatible h1:mfiiYw87ARaeRW6x5gWwYRUawxaW1tLAD8IceomUCNw=
+github.com/vbauerster/mpb v3.4.0+incompatible/go.mod h1:zAHG26FUhVKETRu+MWqYXcI70POlC6N8up9p1dID7SU=
+github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo=
+github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
+github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0=
+github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
+github.com/xeipuuv/gojsonschema v1.1.0 h1:ngVtJC9TY/lg0AA/1k48FYhBrhRoFlEmWzsehpNAaZg=
+github.com/xeipuuv/gojsonschema v1.1.0/go.mod h1:5yf86TLmAcydyeJq5YvxkGPE2fm/u4myDekKRoLuqhs=
 github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 h1:nIPpBwaJSVYIxUFsDv3M8ofmx9yWTog9BfvIu0q41lo=
 github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMxjDjgmT5uz5wzYJKVo23qUhYTos=
 github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
 github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
+go.etcd.io/bbolt v1.3.2 h1:Z/90sZLPOeCy2PwprqkFa25PdkusRzaj9P8zm/KNyvk=
 go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
 go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA=
 go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
@@ -427,6 +509,8 @@ golang.org/x/net v0.0.0-20190613194153-d28f0bde5980 h1:dfGZHvZk057jK2MCeWus/TowK
 golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20190620200207-3b0461eec859 h1:R/3boaszxrf1GEUWTVDzSKVwLmSJpwZ1yqXm8j0v2QI=
 golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20190628185345-da137c7871d7 h1:rTIdg5QFRR7XCaK4LCjBiPbx8j4DQRpdYMnGn/bJUEU=
+golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be h1:vEDujvNQGv4jgYKudGeI/+DAX4Jffq6hpD55MmoEvKs=
 golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
 golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
@@ -438,6 +522,7 @@ golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJ
 golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU=
 golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sys v0.0.0-20170830134202-bb24a47a89ea/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -452,10 +537,13 @@ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv
 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f h1:25KHgbfyiSm6vwQLbM3zZIe1v9p/3ea4Rz+nnM5K/i4=
 golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190621203818-d432491b9138 h1:t8BZD9RDjkm9/h7yYN6kE8oaeov5r9aztkB7zKA5Tkg=
 golang.org/x/sys v0.0.0-20190621203818-d432491b9138/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3 h1:4y9KwBHBgBNwDbtu44R5o1fdOCQUEXhbk/P4A9WmJq0=
+golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -467,6 +555,7 @@ golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxb
 golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 h1:SvFZT6jyqRaOeXpc5h/JSfZenJ2O330aBsf7JfSUXmQ=
 golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20180810170437-e96c4e24768d/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20181011042414-1f849cf54d09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
@@ -515,6 +604,8 @@ gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bl
 gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
 gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gotest.tools v0.0.0-20190624233834-05ebafbffc79 h1:C+K4iPg1rIvmCf4JjelkbWv2jeWevEwp05Lz8XfTYgE=
+gotest.tools v0.0.0-20190624233834-05ebafbffc79/go.mod h1:R//lfYlUuTOTfblYI3lGoAAAebUdzjvbmQsuB7Ykd90=
 grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJdjuHRquDANNeA4x7B8WQ9o=
 honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
 honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
diff --git a/kotskinds/README.md b/kotskinds/README.md
index d5f552007..f94a67131 100644
--- a/kotskinds/README.md
+++ b/kotskinds/README.md
@@ -5,3 +5,6 @@ This directory contains the definitions and clientsets for the kots.io kinds. Th
 ## Building
 
 To build these, simply run `make` in this directory. This will execute the kubernetes controller-gen and client-gen code to generate a typed client and the necessary deepcopy methods for these types.
+
+
+
diff --git a/pkg/image/builder.go b/pkg/image/builder.go
new file mode 100644
index 000000000..f0ee557db
--- /dev/null
+++ b/pkg/image/builder.go
@@ -0,0 +1,238 @@
+package image
+
+import (
+	"bytes"
+	"context"
+	"fmt"
+	"io/ioutil"
+	"os"
+	"path/filepath"
+	"strings"
+
+	"github.com/containers/image/copy"
+	"github.com/containers/image/signature"
+	"github.com/containers/image/transports/alltransports"
+	"github.com/docker/distribution/reference"
+	"github.com/pkg/errors"
+	"github.com/replicatedhq/kots/pkg/logger"
+	"gopkg.in/yaml.v2"
+)
+
+var imagePolicy = []byte(`{
+  "default": [{"type": "insecureAcceptAnything"}]
+}`)
+
+type k8sYAML struct {
+	Spec k8sSpec `yaml:"spec"`
+}
+
+type k8sSpec struct {
+	Template k8sTemplate `yaml:"template"`
+}
+
+type k8sTemplate struct {
+	Spec k8sPodSpec `yaml:"spec"`
+}
+
+type k8sPodSpec struct {
+	Containers []k8sContainer `yaml:"containers"`
+}
+
+type k8sContainer struct {
+	Image string `yaml:"image"`
+}
+
+type ImageRef struct {
+	Domain string
+	Name   string
+	Tag    string
+	Digest string
+}
+
+type RegistryAuth struct {
+	Username string
+	Password string
+}
+
+func SaveImages(log *logger.Logger, imagesDir string, upstreamDir string) error {
+	savedImages := make(map[string]bool)
+
+	err := filepath.Walk(upstreamDir,
+		func(path string, info os.FileInfo, err error) error {
+			if err != nil {
+				return err
+			}
+
+			if info.IsDir() {
+				return nil
+			}
+
+			contents, err := ioutil.ReadFile(path)
+			if err != nil {
+				return err
+			}
+
+			err = extractImagesFromFile(log, imagesDir, contents, savedImages)
+			if err != nil {
+				return errors.Wrap(err, "failed to extract images")
+			}
+
+			return nil
+		})
+
+	if err != nil {
+		return errors.Wrap(err, "failed to walk upstream dir")
+	}
+
+	return nil
+}
+
+func extractImagesFromFile(log *logger.Logger, imagesDir string, fileData []byte, savedImages map[string]bool) error {
+	yamlDocs := bytes.Split(fileData, []byte("\n---\n"))
+	for _, yamlDoc := range yamlDocs {
+		parsed := &k8sYAML{}
+		if err := yaml.Unmarshal(yamlDoc, parsed); err != nil {
+			continue
+		}
+
+		for _, container := range parsed.Spec.Template.Spec.Containers {
+			if _, saved := savedImages[container.Image]; saved {
+				continue
+			}
+
+			log.ChildActionWithSpinner("Pulling image %s", container.Image)
+			err := saveOneImage(imagesDir, container.Image)
+			if err != nil {
+				log.FinishChildSpinner()
+				return errors.Wrap(err, "failed to save image")
+			}
+
+			log.FinishChildSpinner()
+			savedImages[container.Image] = true
+		}
+	}
+
+	return nil
+}
+
+func saveOneImage(imagesDir string, image string) error {
+	imageRef, err := imageRefImage(image)
+	if err != nil {
+		return errors.Wrap(err, "failed to parse image ref")
+	}
+
+	pathInBundle := imageRef.pathInBundle()
+	archiveName := filepath.Join(imagesDir, pathInBundle)
+	destDir := filepath.Dir(archiveName)
+
+	if err := os.MkdirAll(destDir, 0755); err != nil {
+		return errors.Wrap(err, "failed to create destination dir")
+	}
+
+	policy, err := signature.NewPolicyFromBytes(imagePolicy)
+	if err != nil {
+		return errors.Wrap(err, "failed to read default policy")
+	}
+	policyContext, err := signature.NewPolicyContext(policy)
+	if err != nil {
+		return errors.Wrap(err, "failed to create policy")
+	}
+
+	srcRef, err := alltransports.ParseImageName(fmt.Sprintf("docker://%s", image))
+	if err != nil {
+		return errors.Wrap(err, "failed to parse source image name")
+	}
+
+	destRef, err := alltransports.ParseImageName(fmt.Sprintf("docker-archive:%s", archiveName))
+	if err != nil {
+		return errors.Wrap(err, "failed to parse dest image name")
+	}
+
+	_, err = copy.Image(context.Background(), policyContext, destRef, srcRef, &copy.Options{
+		RemoveSignatures:      true,
+		SignBy:                "",
+		ReportWriter:          nil,
+		SourceCtx:             nil,
+		DestinationCtx:        nil,
+		ForceManifestMIMEType: "",
+	})
+	if err != nil {
+		return errors.Wrap(err, "failed to copy image")
+	}
+
+	return nil
+}
+
+func imageRefImage(image string) (*ImageRef, error) {
+	ref := &ImageRef{}
+
+	// named, err := reference.ParseNormalizedNamed(image)
+	parsed, err := reference.ParseAnyReference(image)
+	if err != nil {
+		return nil, errors.Wrapf(err, "failed to parse image name %q", image)
+	}
+
+	if named, ok := parsed.(reference.Named); ok {
+		ref.Domain = reference.Domain(named)
+		ref.Name = named.Name()
+	} else {
+		return nil, errors.New(fmt.Sprintf("unsupported ref type: %T", parsed))
+	}
+
+	if tagged, ok := parsed.(reference.Tagged); ok {
+		ref.Tag = tagged.Tag()
+	} else if can, ok := parsed.(reference.Canonical); ok {
+		ref.Digest = can.Digest().String()
+	} else {
+		ref.Tag = "latest"
+	}
+
+	return ref, nil
+}
+
+func (ref *ImageRef) pathInBundle() string {
+	path := []string{ref.Name}
+	if ref.Tag != "" {
+		path = append(path, ref.Tag)
+	}
+	if ref.Digest != "" {
+		digestParts := strings.Split(ref.Digest, ":")
+		path = append(path, digestParts...)
+	}
+	return filepath.Join(path...)
+}
+
+func CopyFromFileToRegistry(path string, name string, tag string, digest string) error {
+	policy, err := signature.NewPolicyFromBytes(imagePolicy)
+	if err != nil {
+		return errors.Wrap(err, "failed to read default policy")
+	}
+	policyContext, err := signature.NewPolicyContext(policy)
+	if err != nil {
+		return errors.Wrap(err, "failed to create policy")
+	}
+
+	srcRef, err := alltransports.ParseImageName(fmt.Sprintf("docker-archive:%s", path))
+	if err != nil {
+		return errors.Wrap(err, "failed to parse src image name")
+	}
+
+	destRef, err := alltransports.ParseImageName(fmt.Sprintf("docker://%s:%s", name, tag))
+	if err != nil {
+		return errors.Wrap(err, "failed to parse dest image name")
+	}
+
+	_, err = copy.Image(context.Background(), policyContext, destRef, srcRef, &copy.Options{
+		RemoveSignatures:      true,
+		SignBy:                "",
+		ReportWriter:          nil,
+		SourceCtx:             nil,
+		DestinationCtx:        nil,
+		ForceManifestMIMEType: "",
+	})
+	if err != nil {
+		return errors.Wrap(err, "failed to copy image")
+	}
+
+	return nil
+}
diff --git a/pkg/image/image.go b/pkg/image/image.go
index b489ef9c1..165c35a6d 100644
--- a/pkg/image/image.go
+++ b/pkg/image/image.go
@@ -7,21 +7,25 @@ import (
 	"path/filepath"
 
 	"github.com/pkg/errors"
-	"sigs.k8s.io/kustomize/v3/pkg/image"
+	kustomizeimage "sigs.k8s.io/kustomize/v3/pkg/image"
 )
 
-func BuildRewriteList(rootDir string, host string, namespace string) ([]image.Image, error) {
+func BuildRewriteList(rootDir string, host string, namespace string) ([]kustomizeimage.Image, error) {
 	images, err := findImages(rootDir, host, namespace, []string{})
-	return images, errors.Wrap(err, "failed to find images")
+	if err != nil {
+		return nil, errors.Wrap(err, "failed to find images")
+	}
+
+	return images, nil
 }
 
-func findImages(srcDir string, host string, namespace string, imageNameParts []string) ([]image.Image, error) {
+func findImages(srcDir string, host string, namespace string, imageNameParts []string) ([]kustomizeimage.Image, error) {
 	files, err := ioutil.ReadDir(srcDir)
 	if err != nil {
 		return nil, errors.Wrapf(err, "failed to list image files")
 	}
 
-	images := make([]image.Image, 0)
+	images := make([]kustomizeimage.Image, 0)
 	for _, file := range files {
 		if file.IsDir() {
 			moreImages, err := findImages(filepath.Join(srcDir, file.Name()), host, namespace, append(imageNameParts, file.Name()))
@@ -30,7 +34,7 @@ func findImages(srcDir string, host string, namespace string, imageNameParts []s
 			}
 			images = append(images, moreImages...)
 		} else {
-			image, err := imageInfoFromFile(host, namespace, append(imageNameParts, file.Name()))
+			image, err := ImageInfoFromFile(host, namespace, append(imageNameParts, file.Name()))
 			if err != nil {
 				return nil, errors.Wrapf(err, "failed to create local image name")
 			}
@@ -41,14 +45,14 @@ func findImages(srcDir string, host string, namespace string, imageNameParts []s
 	return images, nil
 }
 
-func imageInfoFromFile(registryHost string, namespace string, nameParts []string) (image.Image, error) {
+func ImageInfoFromFile(registryHost string, namespace string, nameParts []string) (kustomizeimage.Image, error) {
 	// imageNameParts looks like this:
 	// ["quay.io", "someorg", "imagename", "imagetag"]
 	// or
 	// ["quay.io", "someorg", "imagename", "sha256", "<sha>"]
 	// we want to discard everything upto "imagename" and replace that with local host and namespace
 
-	image := image.Image{}
+	image := kustomizeimage.Image{}
 
 	if len(nameParts) < 4 {
 		return image, fmt.Errorf("not enough parts in image name: %v", nameParts)
diff --git a/pkg/pull/pull.go b/pkg/pull/pull.go
index b6cd939e2..a0f4cc150 100644
--- a/pkg/pull/pull.go
+++ b/pkg/pull/pull.go
@@ -10,7 +10,6 @@ import (
 	kotsscheme "github.com/replicatedhq/kots/kotskinds/client/kotsclientset/scheme"
 	"github.com/replicatedhq/kots/pkg/base"
 	"github.com/replicatedhq/kots/pkg/downstream"
-	kotsimage "github.com/replicatedhq/kots/pkg/image"
 	"github.com/replicatedhq/kots/pkg/logger"
 	"github.com/replicatedhq/kots/pkg/midstream"
 	"github.com/replicatedhq/kots/pkg/upstream"
@@ -129,6 +128,38 @@ func Pull(upstreamURI string, pullOptions PullOptions) (string, error) {
 	}
 	log.FinishSpinner()
 
+	var images []image.Image
+	if pullOptions.RewriteImages {
+		if pullOptions.RewriteImageOptions.ImageFiles == "" {
+			writeUpstreamImageOptions := upstream.WriteUpstreamImageOptions{
+				RootDir:      pullOptions.RootDir,
+				CreateAppDir: pullOptions.CreateAppDir,
+				Log:          log,
+			}
+			if err := u.WriteUpstreamImages(writeUpstreamImageOptions); err != nil {
+				return "", errors.Wrap(err, "failed to write upstream images")
+			}
+		}
+
+		// If the request includes a rewrite image options host name, then also
+		// push the images
+		if pullOptions.RewriteImageOptions.Host != "" {
+			pushUpstreamImageOptions := upstream.PushUpstreamImageOptions{
+				RootDir:           pullOptions.RootDir,
+				CreateAppDir:      pullOptions.CreateAppDir,
+				Log:               log,
+				RegistryHost:      pullOptions.RewriteImageOptions.Host,
+				RegistryNamespace: pullOptions.RewriteImageOptions.Namespace,
+			}
+			rewrittenImages, err := u.TagAndPushUpstreamImages(pushUpstreamImageOptions)
+			if err != nil {
+				return "", errors.Wrap(err, "failed to push upstream images")
+			}
+
+			images = rewrittenImages
+		}
+	}
+
 	renderOptions := base.RenderOptions{
 		SplitMultiDocYAML: true,
 		Namespace:         pullOptions.Namespace,
@@ -152,17 +183,6 @@ func Pull(upstreamURI string, pullOptions PullOptions) (string, error) {
 
 	log.ActionWithSpinner("Creating midstream")
 
-	var images []image.Image
-	if pullOptions.RewriteImages {
-		if pullOptions.LocalPath != "" {
-			i, err := kotsimage.BuildRewriteList(pullOptions.RewriteImageOptions.ImageFiles, pullOptions.RewriteImageOptions.Host, pullOptions.RewriteImageOptions.Namespace)
-			if err != nil {
-				return "", errors.Wrap(err, "failed to rewrite images")
-			}
-			images = i
-		}
-	}
-
 	m, err := midstream.CreateMidstream(b, images)
 	if err != nil {
 		return "", errors.Wrap(err, "failed to create midstream")
diff --git a/pkg/upstream/push_images.go b/pkg/upstream/push_images.go
new file mode 100644
index 000000000..24dde45b8
--- /dev/null
+++ b/pkg/upstream/push_images.go
@@ -0,0 +1,67 @@
+package upstream
+
+import (
+	"os"
+	"path"
+	"path/filepath"
+	"strings"
+
+	"github.com/pkg/errors"
+	"github.com/replicatedhq/kots/pkg/image"
+	"github.com/replicatedhq/kots/pkg/logger"
+	kustomizeimage "sigs.k8s.io/kustomize/v3/pkg/image"
+)
+
+type PushUpstreamImageOptions struct {
+	RootDir           string
+	CreateAppDir      bool
+	Log               *logger.Logger
+	RegistryHost      string
+	RegistryNamespace string
+}
+
+func (u *Upstream) TagAndPushUpstreamImages(options PushUpstreamImageOptions) ([]kustomizeimage.Image, error) {
+	rootDir := options.RootDir
+	if options.CreateAppDir {
+		rootDir = path.Join(rootDir, u.Name)
+	}
+	imagesDir := path.Join(rootDir, "images")
+
+	images := []kustomizeimage.Image{}
+
+	err := filepath.Walk(imagesDir,
+		func(path string, info os.FileInfo, err error) error {
+			if err != nil {
+				return err
+			}
+
+			if info.IsDir() {
+				return nil
+			}
+
+			pathWithoutRoot := path[len(imagesDir)+1:]
+
+			rewrittenImage, err := image.ImageInfoFromFile(options.RegistryHost, options.RegistryNamespace, strings.Split(pathWithoutRoot, string(os.PathSeparator)))
+			if err != nil {
+				return errors.Wrap(err, "failed to decode image from path")
+			}
+
+			// copy to the registry
+			options.Log.ChildActionWithSpinner("Pushing image %s:%s", rewrittenImage.NewName, rewrittenImage.NewTag)
+			err = image.CopyFromFileToRegistry(path, rewrittenImage.NewName, rewrittenImage.NewTag, rewrittenImage.Digest)
+			if err != nil {
+				options.Log.FinishChildSpinner()
+				return errors.Wrap(err, "failed to push image")
+			}
+			options.Log.FinishChildSpinner()
+
+			images = append(images, rewrittenImage)
+			return nil
+		})
+
+	if err != nil {
+		return nil, errors.Wrap(err, "failed to walk images dir")
+	}
+
+	return images, nil
+}
diff --git a/pkg/upstream/write_images.go b/pkg/upstream/write_images.go
new file mode 100644
index 000000000..bcc065aa3
--- /dev/null
+++ b/pkg/upstream/write_images.go
@@ -0,0 +1,38 @@
+package upstream
+
+import (
+	"os"
+	"path"
+
+	"github.com/pkg/errors"
+	"github.com/replicatedhq/kots/pkg/image"
+	"github.com/replicatedhq/kots/pkg/logger"
+)
+
+type WriteUpstreamImageOptions struct {
+	RootDir      string
+	CreateAppDir bool
+	Log          *logger.Logger
+}
+
+func (u *Upstream) WriteUpstreamImages(options WriteUpstreamImageOptions) error {
+	rootDir := options.RootDir
+	if options.CreateAppDir {
+		rootDir = path.Join(rootDir, u.Name)
+	}
+	upstreamDir := path.Join(rootDir, "upstream")
+	imagesDir := path.Join(rootDir, "images")
+
+	_, err := os.Stat(imagesDir)
+	if err == nil {
+		if err := os.RemoveAll(imagesDir); err != nil {
+			return errors.Wrap(err, "failed to remove existing images")
+		}
+	}
+
+	if err := image.SaveImages(options.Log, imagesDir, upstreamDir); err != nil {
+		return errors.Wrap(err, "failed to save images")
+	}
+
+	return nil
+}


Test patch:
diff --git a/pkg/image/builder_test.go b/pkg/image/builder_test.go
new file mode 100644
index 000000000..d112e2997
--- /dev/null
+++ b/pkg/image/builder_test.go
@@ -0,0 +1,67 @@
+package image
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/require"
+)
+
+func TestReleaseStartNum(t *testing.T) {
+	var err error
+	var ref *ImageRef
+
+	ref, err = imageRefImage("debian:2.5")
+	require.NoError(t, err)
+	require.Equal(t, &ImageRef{
+		Domain: "docker.io",
+		Name:   "docker.io/library/debian",
+		Tag:    "2.5",
+	}, ref)
+	require.Equal(t,
+		"docker.io/library/debian/2.5",
+		ref.pathInBundle())
+
+	ref, err = imageRefImage("debian")
+	require.NoError(t, err)
+	require.Equal(t, &ImageRef{
+		Domain: "docker.io",
+		Name:   "docker.io/library/debian",
+		Tag:    "latest",
+	}, ref)
+	require.Equal(t,
+		"docker.io/library/debian/latest",
+		ref.pathInBundle())
+
+	ref, err = imageRefImage("quay.io/replicated/debian:2.5")
+	require.NoError(t, err)
+	require.Equal(t, &ImageRef{
+		Domain: "quay.io",
+		Name:   "quay.io/replicated/debian",
+		Tag:    "2.5",
+	}, ref)
+	require.Equal(t,
+		"quay.io/replicated/debian/2.5",
+		ref.pathInBundle())
+
+	ref, err = imageRefImage("myorg/ubuntu:14")
+	require.NoError(t, err)
+	require.Equal(t, &ImageRef{
+		Domain: "docker.io",
+		Name:   "docker.io/myorg/ubuntu",
+		Tag:    "14",
+	}, ref)
+	require.Equal(t,
+		"docker.io/myorg/ubuntu/14",
+		ref.pathInBundle())
+
+	ref, err = imageRefImage("myorg/ubuntu@sha256:45b23dee08af5e43a7fea6c4cf9c25ccf269ee113168c19722f87876677c5cb2")
+	require.NoError(t, err)
+	require.Equal(t, &ImageRef{
+		Domain: "docker.io",
+		Name:   "docker.io/myorg/ubuntu",
+		Digest: "sha256:45b23dee08af5e43a7fea6c4cf9c25ccf269ee113168c19722f87876677c5cb2",
+	}, ref)
+	require.Equal(t,
+		"docker.io/myorg/ubuntu/sha256/45b23dee08af5e43a7fea6c4cf9c25ccf269ee113168c19722f87876677c5cb2",
+		ref.pathInBundle())
+}
diff --git a/pkg/image/image_test.go b/pkg/image/image_test.go
index 315fb76f1..da7091ec5 100644
--- a/pkg/image/image_test.go
+++ b/pkg/image/image_test.go
@@ -50,7 +50,7 @@ func Test_ImageNameFromNameParts(t *testing.T) {
 
 	for _, test := range tests {
 		t.Run(test.name, func(t *testing.T) {
-			image, err := imageInfoFromFile(registry, namespace, test.parts)
+			image, err := ImageInfoFromFile(registry, namespace, test.parts)
 			if test.isError {
 				assert.Error(t, err)
 			} else {