Repository: getporter/porter
Pull request #1368 (2020-11-17 15:19:27)

Generate/use canonical manifest for build and publish
# What does this change

Initially, I intended to restrict the change set to the creation of the canonical manifest.  However, in order to test the various interactions between the dynamic overrides (for name and version) on build that we wish to support (https://github.com/getporter/porter/issues/1334), I included these changes here as well.

Canonical Manifest changes:
- Store a canonical version of the Porter manifest in .cnab/app/porter.yaml (also included in the invocation image)
- Use yqlib to decode/encode manifest (and potentially apply updates)
  - We went this route as there are a handful of hidden fields (yaml tag of `"-"`) preventing a straight up Marshal
  - Also suits our need of single yaml field updates (to be used in https://github.com/getporter/porter/issues/1334)
- Delay invocation image build until after both the manifest and bundle.json is ready for inclusion

Build flag additions:
- Add name and version flags to the build command
- Use these when generating the canonical Porter manifest

Misc:
- Pin the manifest stamp generation to the user's original manifest (config.Name)

# What issue does it fix
Closes https://github.com/getporter/porter/issues/1352
Closes https://github.com/getporter/porter/issues/1334

# Notes for the reviewer

# Checklist
- [x] Unit Tests
- [x] Documentation
- [ ] Schema (porter.yaml)

If this is your first pull request, please add your name to the bottom of our [Contributors][contributors] list. Thank you for making Porter better! 🙇‍♀️

[contributors]: /CONTRIBUTORS.md


Patch:
diff --git a/cmd/porter/bundle.go b/cmd/porter/bundle.go
index aa90c288..f719f7af 100644
--- a/cmd/porter/bundle.go
+++ b/cmd/porter/bundle.go
@@ -58,6 +58,8 @@ func buildBundleBuildCommand(p *porter.Porter) *cobra.Command {
 	f := cmd.Flags()
 	f.BoolVar(&opts.NoLint, "no-lint", false, "Do not run the linter")
 	f.BoolVarP(&opts.Verbose, "verbose", "v", false, "Enable verbose logging")
+	f.StringVar(&opts.Name, "name", "", "Override the bundle name")
+	f.StringVar(&opts.Version, "version", "", "Override the bundle version")
 
 	return cmd
 }
diff --git a/docs/content/cli/build.md b/docs/content/cli/build.md
index 3829342c..4b5a646a 100644
--- a/docs/content/cli/build.md
+++ b/docs/content/cli/build.md
@@ -18,9 +18,11 @@ porter build [flags]
 ### Options
 
 ```
-  -h, --help      help for build
-      --no-lint   Do not run the linter
-  -v, --verbose   Enable verbose logging
+  -h, --help             help for build
+      --name string      Override the bundle name
+      --no-lint          Do not run the linter
+  -v, --verbose          Enable verbose logging
+      --version string   Override the bundle version
 ```
 
 ### Options inherited from parent commands
diff --git a/docs/content/cli/bundles_build.md b/docs/content/cli/bundles_build.md
index 151efe27..c6cc6977 100644
--- a/docs/content/cli/bundles_build.md
+++ b/docs/content/cli/bundles_build.md
@@ -18,9 +18,11 @@ porter bundles build [flags]
 ### Options
 
 ```
-  -h, --help      help for build
-      --no-lint   Do not run the linter
-  -v, --verbose   Enable verbose logging
+  -h, --help             help for build
+      --name string      Override the bundle name
+      --no-lint          Do not run the linter
+  -v, --verbose          Enable verbose logging
+      --version string   Override the bundle version
 ```
 
 ### Options inherited from parent commands
diff --git a/go.mod b/go.mod
index cc620534..f1e11d81 100644
--- a/go.mod
+++ b/go.mod
@@ -47,20 +47,21 @@ require (
 	github.com/hashicorp/go-plugin v0.0.0-00010101000000-000000000000
 	github.com/hashicorp/go-version v1.2.0 // indirect
 	github.com/magefile/mage v1.10.0
-	github.com/mattn/go-colorable v0.1.4 // indirect
-	github.com/mattn/go-isatty v0.0.9 // indirect
+	github.com/mikefarah/yq/v3 v3.0.0-20201020025845-ccb718cd0f59
 	github.com/mmcdole/gofeed v1.0.0-beta2
 	github.com/mmcdole/goxpp v0.0.0-20181012175147-0068e33feabf // indirect
 	github.com/olekukonko/tablewriter v0.0.4
 	github.com/pivotal/image-relocation v0.0.0-20191111101224-e94aff6df06c
 	github.com/pkg/errors v0.9.1
 	github.com/spf13/afero v1.2.2
-	github.com/spf13/cobra v0.0.6
+	github.com/spf13/cobra v1.0.0
 	github.com/spf13/pflag v1.0.5
 	github.com/spf13/viper v1.4.0
 	github.com/stretchr/testify v1.6.1
 	github.com/xeipuuv/gojsonschema v1.2.0
 	golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9
 	gopkg.in/AlecAivazis/survey.v1 v1.8.7
+	gopkg.in/op/go-logging.v1 v1.0.0-20160211212156-b2cb9fa56473
 	gopkg.in/yaml.v2 v2.2.4
+	gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776
 )
diff --git a/go.sum b/go.sum
index e3e3a4ca..6d1aad8d 100644
--- a/go.sum
+++ b/go.sum
@@ -94,7 +94,6 @@ github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEe
 github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=
 github.com/certifi/gocertifi v0.0.0-20180118203423-deb3ae2ef261/go.mod h1:GJKEexRPVJrBSOjoqN5VNOIKJ5Q3RViH6eu3puDRwx4=
 github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
-github.com/cespare/xxhash/v2 v2.1.0 h1:yTUvW7Vhb89inJ+8irsUqiWjh8iT6sQPZiQzI6ReGkA=
 github.com/cespare/xxhash/v2 v2.1.0/go.mod h1:dgIUBU3pDso/gPgZ1osOZ0iQf77oPR28Tjxl5dIMyVM=
 github.com/cilium/ebpf v0.0.0-20200702112145-1c8d4c9ef775/go.mod h1:7cR51M8ViRLIdUjrmSXlK9pkrsDlLHbO8jiB8X8JnOc=
 github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
@@ -113,19 +112,15 @@ github.com/containerd/cgroups v0.0.0-20200710171044-318312a37340/go.mod h1:s5q4S
 github.com/containerd/containerd v1.2.7/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA=
 github.com/containerd/containerd v1.3.0 h1:xjvXQWABwS2uiv3TWgQt5Uth60Gu86LTGZXMJkjc7rY=
 github.com/containerd/containerd v1.3.0/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA=
-github.com/containerd/continuity v0.0.0-20181203112020-004b46473808 h1:4BX8f882bXEDKfWIf0wa8HRvpnBoPszJJXL+TVbBw4M=
 github.com/containerd/continuity v0.0.0-20181203112020-004b46473808/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y=
 github.com/containerd/continuity v0.0.0-20200228182428-0f16d7a0959c h1:8ahmSVELW1wghbjerVAyuEYD5+Dio66RYvSS0iGfL1M=
 github.com/containerd/continuity v0.0.0-20200228182428-0f16d7a0959c/go.mod h1:Dq467ZllaHgAtVp4p1xUQWBrFXR9s/wyoTpG8zOJGkY=
-github.com/containerd/fifo v0.0.0-20190816180239-bda0ff6ed73c h1:KFbqHhDeaHM7IfFtXHfUHMDaUStpM2YwBR+iJCIOsKk=
 github.com/containerd/fifo v0.0.0-20190816180239-bda0ff6ed73c/go.mod h1:ODA38xgv3Kuk8dQz2ZQXpnv/UZZUHUCL7pnLehbXgQI=
 github.com/containerd/fifo v0.0.0-20191213151349-ff969a566b00 h1:lsjC5ENBl+Zgf38+B0ymougXFp0BaubeIVETltYZTQw=
 github.com/containerd/fifo v0.0.0-20191213151349-ff969a566b00/go.mod h1:jPQ2IAeZRCYxpS/Cm1495vGFww6ecHmMk1YJH2Q5ln0=
-github.com/containerd/ttrpc v0.0.0-20191028202541-4f1b8fe65a5c h1:+RqLdWzn0xFunb+sxXaEzHOg8NuEG/eaI+9C1xXX8Mw=
 github.com/containerd/ttrpc v0.0.0-20191028202541-4f1b8fe65a5c/go.mod h1:LPm1u0xBw8r8NOKoOdNMeVHSawSsltak+Ihv+etqsE8=
 github.com/containerd/ttrpc v1.0.0 h1:NY8Zk2i7TpkLxrkOASo+KTFq9iNCEmMH2/ZG9OuOw6k=
 github.com/containerd/ttrpc v1.0.0/go.mod h1:PvCDdDGpgqzQIzDW1TphrGLssLDZp2GuS+X5DkEJB8o=
-github.com/containerd/typeurl v0.0.0-20190911142611-5eb25027c9fd h1:bRLyitWw3PT/2YuVaCKTPg0cA5dOFKFwKtkfcP2dLsA=
 github.com/containerd/typeurl v0.0.0-20190911142611-5eb25027c9fd/go.mod h1:GeKYzf2pQcqv7tJ0AoCuuhtnqhva5LNU3U+OyKxxJpk=
 github.com/containerd/typeurl v1.0.0 h1:7LMH7LfEmpWeCkGcIputvd4P0Rnd0LrIv1Jk2s5oobs=
 github.com/containerd/typeurl v1.0.0/go.mod h1:Cm3kwCdlkCfMSHURc+r6fwoGH6/F1hH3S4sg0rLFWPc=
@@ -166,7 +161,6 @@ github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKoh
 github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
 github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c h1:+pKlWGMw7gf6bQ+oDZB4KHQFypsfjYlq/C4rfL7D3g8=
 github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c/go.mod h1:Uw6UezgYA44ePAFQYUehOuCzmy5zmg/+nl2ZfMWGkpA=
-github.com/docker/go-metrics v0.0.0-20181218153428-b84716841b82 h1:X0fj836zx99zFu83v/M79DuBn84IL/Syx1SY6Y5ZEMA=
 github.com/docker/go-metrics v0.0.0-20181218153428-b84716841b82/go.mod h1:/u0gXw0Gay3ceNrsHubL3BtdOL2fHf93USgMTe0W5dI=
 github.com/docker/go-metrics v0.0.1 h1:AgB/0SvBxihN0X8OR4SjsblXkbMvalQ8cjmtKQ2rQV8=
 github.com/docker/go-metrics v0.0.1/go.mod h1:cG1hvH2utMXtqgqqYE9plW6lDxS3/5ayHzueweSI3Vw=
@@ -188,6 +182,9 @@ github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5 h1:Yzb9+7DP
 github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a2zkGnVExMxdzMo3M0Hi/3sEU+cWnZpSni0O6/Yb/P0=
 github.com/evanphx/json-patch v4.2.0+incompatible h1:fUDGZCv/7iAN7u0puUVhvKCcsR6vRfwrJatElLBEf0I=
 github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
+github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
+github.com/fatih/color v1.9.0 h1:8xPHl4/q1VyqGIPif1F+1V3Y3lSmrq01EabUW3CoW5s=
+github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
 github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
 github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
 github.com/getsentry/raven-go v0.0.0-20180121060056-563b81fc02b7/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ=
@@ -205,6 +202,8 @@ github.com/go-openapi/jsonpointer v0.0.0-20160704185906-46af16f9f7b1/go.mod h1:+
 github.com/go-openapi/jsonreference v0.0.0-20160704190145-13c6e3589ad9/go.mod h1:W3Z9FmVs9qj+KR4zFKmDPGiLdk1D9Rlm7cyMvf57TTg=
 github.com/go-openapi/spec v0.0.0-20160808142527-6aced65f8501/go.mod h1:J8+jY1nAiCcj+friV/PDoE1/3eeccG9LYBs0tYvLOWc=
 github.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87/go.mod h1:DXUve3Dpr1UfpPtxFw+EFuQ41HhCWZfha5jSVRG7C7I=
+github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
+github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
 github.com/go-sql-driver/mysql v1.3.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
 github.com/go-sql-driver/mysql v1.4.1 h1:g24URVg0OFbNUTx9qqY1IRZ9D9z3iPyi5zKhQZpNwpA=
 github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
@@ -215,20 +214,20 @@ github.com/gobuffalo/packd v1.0.0 h1:6ERZvJHfe24rfFmA9OaoKBdC7+c9sydrytMg8SdFGBM
 github.com/gobuffalo/packd v1.0.0/go.mod h1:6VTc4htmJRFB7u1m/4LeMTWjFoYrUiBkU9Fdec9hrhI=
 github.com/gobuffalo/packr/v2 v2.8.0 h1:IULGd15bQL59ijXLxEvA5wlMxsmx/ZkQv9T282zNVIY=
 github.com/gobuffalo/packr/v2 v2.8.0/go.mod h1:PDk2k3vGevNE3SwVyVRgQCCXETC9SaONCNSXT1Q8M1g=
+github.com/goccy/go-yaml v1.8.1 h1:JuZRFlqLM5cWF6A+waL8AKVuCcqvKOuhJtUQI+L3ez0=
+github.com/goccy/go-yaml v1.8.1/go.mod h1:wS4gNoLalDSJxo/SpngzPQ2BN4uuZVLCmbM4S3vd4+Y=
 github.com/godbus/dbus v4.1.0+incompatible h1:WqqLRTsQic3apZUK9qC5sGNfXthmPXzUZ7nQPrNITa4=
 github.com/godbus/dbus v4.1.0+incompatible/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw=
 github.com/godbus/dbus/v5 v5.0.3 h1:ZqHaoEF7TBzh4jzPmqVhE/5A1z9of6orkAe5uHoAeME=
 github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
 github.com/gofrs/uuid v3.2.0+incompatible h1:y12jRkkFxsd7GpqdSZ+/KCs/fJbqpEXSGd4+jfEaewE=
 github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
-github.com/gogo/googleapis v1.3.0 h1:M695OaDJ5ipWvDPcoAg/YL9c3uORAegkEfBqTQF/fTQ=
 github.com/gogo/googleapis v1.3.0/go.mod h1:d+q1s/xVJxZGKWwC/6UfPIF33J+G1Tq4GYv9Y+Tg/EU=
 github.com/gogo/googleapis v1.3.2 h1:kX1es4djPJrsDhY7aZKJy7aZasdcB5oSOEphMjSB53c=
 github.com/gogo/googleapis v1.3.2/go.mod h1:5YRNX2z1oM5gXdAkurHa942MDgEJyk02w4OecKY87+c=
 github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
 github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
 github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
-github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d h1:3PaI8p3seN09VjbTYC/QWlUZdZ1qS1zGjy7LH2Wt07I=
 github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
 github.com/gogo/protobuf v1.3.0/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
 github.com/gogo/protobuf v1.3.1 h1:DqDEcV5aeaTmdFBePNpYsp3FlcVH/2ISVVM9Qf8PSls=
@@ -240,7 +239,6 @@ github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4er
 github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
 github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
 github.com/golang/protobuf v0.0.0-20161109072736-4bd1920723d7/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
-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/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
 github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs=
@@ -273,7 +271,6 @@ github.com/gophercloud/gophercloud v0.1.0 h1:P/nh25+rzXouhytV2pUHBb65fnds26Ghl8/
 github.com/gophercloud/gophercloud v0.1.0/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8=
 github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
 github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
-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/mux v1.7.4 h1:VuZ8uybHlWmqV03+zRzdwKL4tUnIp1MAQtp1mIFE1bc=
 github.com/gorilla/mux v1.7.4/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
@@ -293,7 +290,6 @@ github.com/hashicorp/go-hclog v0.9.2 h1:CG6TE5H9/JXsFWJCfoIVpKFIkFe6ysEuHirp4DxC
 github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ=
 github.com/hashicorp/go-multierror v1.1.0 h1:B9UzwGQJehnUY1yNrnwREHc3fGbC2xefo8g4TbElacI=
 github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA=
-github.com/hashicorp/go-version v1.1.0 h1:bPIoEKD27tNdebFGGxxYwcL4nepeY4j1QP23PFRGzg0=
 github.com/hashicorp/go-version v1.1.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
 github.com/hashicorp/go-version v1.2.0 h1:3vNe/fWF5CBgRIguda1meWhsZHy3m8gCJ5wx+dIzX/E=
 github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
@@ -341,7 +337,6 @@ github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+o
 github.com/kisielk/sqlstruct v0.0.0-20150923205031-648daed35d49/go.mod h1:yyMNCyc/Ib3bDTKd379tNMpB/7/H5TjM2Y9QJ5THLbE=
 github.com/kisom/goutils v1.1.0/go.mod h1:+UBTfd78habUYWFbNWTJNG+jNG/i/lGURakr4A/yNRw=
 github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
-github.com/konsorten/go-windows-terminal-sequences v1.0.2 h1:DB17ag19krx9CFsz4o3enTrPXyIXCl+2iCXH/aMAp9s=
 github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
 github.com/konsorten/go-windows-terminal-sequences v1.0.3 h1:CE8S1cTafDpPvMhIxNJKvHsGVBgn1xWYf1NbHQhywc8=
 github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
@@ -353,6 +348,8 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
 github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
 github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
 github.com/kylelemons/go-gypsy v0.0.0-20160905020020-08cad365cd28/go.mod h1:T/T7jsxVqf9k/zYOqbgNAsANsjxTd1Yq3htjDhQ1H0c=
+github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
+github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
 github.com/lib/pq v0.0.0-20180201184707-88edab080323/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
 github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
 github.com/lib/pq v1.2.0 h1:LXpIM/LZ5xGFhOpXAQUIMM1HdyqzVYM13zNdjCEEcA0=
@@ -367,12 +364,15 @@ github.com/markbates/oncer v1.0.0/go.mod h1:Z59JA581E9GP6w96jai+TGqafHPW+cPfRxz2
 github.com/markbates/safe v1.0.1 h1:yjZkbvRM6IzKj9tlu/zMJLS0n/V351OZWRnF3QfaUxI=
 github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0=
 github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
-github.com/mattn/go-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA=
 github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
+github.com/mattn/go-colorable v0.1.7 h1:bQGKb3vps/j0E9GfJQ03JyhRuxsvdAanXlT9BTw3mdw=
+github.com/mattn/go-colorable v0.1.7/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
 github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
 github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
-github.com/mattn/go-isatty v0.0.9 h1:d5US/mDsogSGW37IV293h//ZFaeajb69h+EHFsv2xGg=
-github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ=
+github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84=
+github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
+github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
+github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
 github.com/mattn/go-runewidth v0.0.7 h1:Ei8KR0497xHyKJPAv59M1dkC+rOZCMBJ+t3fZ+twI54=
 github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
 github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
@@ -384,6 +384,8 @@ github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b h1:j7+1HpAFS1zy5+Q4qx1f
 github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
 github.com/miekg/pkcs11 v1.0.3 h1:iMwmD7I5225wv84WxIG/bmxz9AXjWvTWIbM/TYHvWtw=
 github.com/miekg/pkcs11 v1.0.3/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs=
+github.com/mikefarah/yq/v3 v3.0.0-20201020025845-ccb718cd0f59 h1:6nvF+EEFIVD4KT64CgvAzWaMVC283Huno59khWs9r1A=
+github.com/mikefarah/yq/v3 v3.0.0-20201020025845-ccb718cd0f59/go.mod h1:7eVjFf5bgozMuHk+oKpyxR2zCN3iEN1tF0/bM5jvtKo=
 github.com/mitchellh/copystructure v1.0.0 h1:Laisrj+bAB6b/yJwB5Bt3ITZhGJdqmxquMKeZ+mmkFQ=
 github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw=
 github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
@@ -437,7 +439,6 @@ github.com/opencontainers/image-spec v1.0.1 h1:JMemWkRwHx4Zj+fVxWoMCFm/8sYGGrUVo
 github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
 github.com/opencontainers/runc v0.1.1 h1:GlxAyO6x8rfZYN9Tt0Kti5a/cP41iuiO2yYT0IJGY8Y=
 github.com/opencontainers/runc v0.1.1/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U=
-github.com/opencontainers/runtime-spec v1.0.1 h1:wY4pOY8fBdSIvs9+IDHC55thBuEulhzfSgKeC1yFvzQ=
 github.com/opencontainers/runtime-spec v1.0.1/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
 github.com/opencontainers/runtime-spec v1.0.2 h1:UfAcuLBJB9Coz72x1hgl8O5RVzTdNiaglX6v2DM6FI0=
 github.com/opencontainers/runtime-spec v1.0.2/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
@@ -450,7 +451,6 @@ github.com/pivotal/image-relocation v0.0.0-20191111101224-e94aff6df06c h1:aWFqZb
 github.com/pivotal/image-relocation v0.0.0-20191111101224-e94aff6df06c/go.mod h1:/JNbQwGylYm3AQh8q+MBF8e/h0W1Jy20JGTvozuXYTE=
 github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pkg/errors v0.8.1-0.20171018195549-f15c970de5b7/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
-github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
 github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
 github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
@@ -505,7 +505,6 @@ github.com/sirupsen/logrus v1.0.4-0.20170822132746-89742aefa4b2/go.mod h1:pMByvH
 github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
 github.com/sirupsen/logrus v1.3.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
 github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
-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=
@@ -517,10 +516,10 @@ github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTd
 github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8=
 github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
 github.com/spf13/cobra v0.0.2-0.20171109065643-2da4a54c5cee/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.6 h1:breEStsVwemnKh2/s6gMvSdMEkwW0sK8vGStnlVBMCs=
 github.com/spf13/cobra v0.0.6/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 v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk=
 github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
 github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
@@ -528,7 +527,6 @@ github.com/spf13/pflag v1.0.1-0.20171106142849-4c012f6dcd95/go.mod h1:DYY7MBk1bd
 github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
 github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
 github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
-github.com/spf13/viper v1.3.2 h1:VUFqw5KcqRf7i70GOzW7N+Q7+gxVBkSSqiXB12+JQ4M=
 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=
@@ -538,7 +536,6 @@ github.com/stretchr/testify v0.0.0-20151208002404-e3a8ff8ce365/go.mod h1:a8OnRci
 github.com/stretchr/testify v1.2.1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
 github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
 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/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
 github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
@@ -621,9 +618,7 @@ golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn
 golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
 golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
 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-20190812203447-cdfb69ac37fc h1:gkKoSkUmnU6bpS/VhkuO27bzQeSA51uaEfbOW5dNb68=
 golang.org/x/net v0.0.0-20190812203447-cdfb69ac37fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20200226121028-0de0cce0169b h1:0mm1VjtFUOIlE1SbDlwjYaDxZVDP2S5ou6y0gSgXHu8=
 golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
@@ -636,7 +631,6 @@ 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/sync v0.0.0-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY=
 golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -649,7 +643,6 @@ golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
 golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
-golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2 h1:+DCIGbF/swA92ohVg0//6X2IVY3KZs6p9mix0ziNYJM=
 golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 h1:SvFZT6jyqRaOeXpc5h/JSfZenJ2O330aBsf7JfSUXmQ=
@@ -672,6 +665,8 @@ golang.org/x/tools v0.0.0-20200521155704-91d71f6c2f04/go.mod h1:EkVYQZoAsY45+roY
 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
+golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk=
 google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
 google.golang.org/api v0.5.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
@@ -679,7 +674,6 @@ google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9Ywl
 google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
 google.golang.org/appengine v1.5.0 h1:KxkO13IPW4Lslp2bz+KHP2E3gtFlrIGNThxkZQ3g+4c=
 google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
-google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8 h1:Nw54tB0rB7hY/N0NQvRW8DG4Yk3Q6T9cu9RcFQDu1tc=
 google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
 google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
 google.golang.org/genproto v0.0.0-20190404172233-64821d5d2107/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
@@ -708,11 +702,15 @@ gopkg.in/fatih/pool.v2 v2.0.0/go.mod h1:8xVGeu1/2jr2wm5V9SPuMht2H5AEmf5aFMGSQixt
 gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
 gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
 gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2/go.mod h1:Xk6kEKp8OKb+X14hQBKWaSkCsqBpgog8nAV2xsGOxlo=
+gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE=
+gopkg.in/go-playground/validator.v9 v9.30.0/go.mod h1:+c9/zcJMFNgbLvly1L1V+PpxWdVbfP1avr/N00E2vyQ=
 gopkg.in/gorethink/gorethink.v3 v3.0.5 h1:e2Uc/Xe+hpcVQFsj6MuHlYog3r0JYpnTzwDj/y2O4MU=
 gopkg.in/gorethink/gorethink.v3 v3.0.5/go.mod h1:+3yIIHJUGMBK+wyPH+iN5TP+88ikFDfZdqTlK3Y9q8I=
 gopkg.in/inf.v0 v0.9.0/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
 gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
 gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
+gopkg.in/op/go-logging.v1 v1.0.0-20160211212156-b2cb9fa56473 h1:6D+BvnJ/j6e222UW8s2qTSe3wGBtvo0MbVQG/c5k8RE=
+gopkg.in/op/go-logging.v1 v1.0.0-20160211212156-b2cb9fa56473/go.mod h1:N1eN2tsCx0Ydtgjl4cqmbRCsY4/+z4cYDeqwZTk6zog=
 gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
 gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
 gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
@@ -721,8 +719,9 @@ gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I=
 gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
-gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 h1:tQIYjPdBoyREyB9XMu+nnTclpTYkz2zFM+lzLJFO4gQ=
+gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo=
 gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
 honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
diff --git a/pkg/build/build.go b/pkg/build/build.go
index 1af3f14e..a663f0fd 100644
--- a/pkg/build/build.go
+++ b/pkg/build/build.go
@@ -14,6 +14,10 @@ var (
 	// LOCAL_BUNDLE is the generated bundle.json file.
 	LOCAL_BUNDLE = filepath.Join(LOCAL_CNAB, "bundle.json")
 
+	// LOCAL_MANIFEST is the canonical Porter manifest generated from the
+	// user-provided manifest and any dynamic overrides
+	LOCAL_MANIFEST = filepath.Join(LOCAL_APP, "porter.yaml")
+
 	// LOCAL_RUN is the path to the generated CNAB entrypoint script, located at /cnab/app/run.
 	LOCAL_RUN = filepath.Join(LOCAL_APP, "run")
 
diff --git a/pkg/build/dockerfile-generater.go b/pkg/build/dockerfile-generater.go
index 6b53b378..4db435d4 100644
--- a/pkg/build/dockerfile-generater.go
+++ b/pkg/build/dockerfile-generater.go
@@ -72,8 +72,8 @@ func (g *DockerfileGenerator) buildDockerfile() ([]string, error) {
 		lines = append(pretoken, append(mixinLines, posttoken...)...)
 	}
 
-	lines = append(lines, g.buildCNABSection()...)
 	lines = append(lines, g.buildPorterSection()...)
+	lines = append(lines, g.buildCNABSection()...)
 	lines = append(lines, g.buildWORKDIRSection())
 	lines = append(lines, g.buildCMDSection())
 
@@ -144,7 +144,9 @@ func (g *DockerfileGenerator) getBaseDockerfile() ([]string, error) {
 
 func (g *DockerfileGenerator) buildPorterSection() []string {
 	return []string{
-		`COPY porter.yaml $BUNDLE_DIR/porter.yaml`,
+		// Remove the user-provided Porter manifest as the canonical version
+		// will migrate via its location in .cnab
+		`RUN rm $BUNDLE_DIR/porter.yaml`,
 	}
 }
 
@@ -183,7 +185,6 @@ func (g *DockerfileGenerator) buildMixinsSection() ([]string, error) {
 
 func (g *DockerfileGenerator) PrepareFilesystem() error {
 	// clean up previously generated files
-	g.FileSystem.RemoveAll(LOCAL_CNAB)
 	g.FileSystem.Remove("Dockerfile")
 
 	fmt.Fprintf(g.Out, "Copying porter runtime ===> \n")
diff --git a/pkg/cnab/config-adapter/stamp.go b/pkg/cnab/config-adapter/stamp.go
index 262585a9..f15b9c3b 100644
--- a/pkg/cnab/config-adapter/stamp.go
+++ b/pkg/cnab/config-adapter/stamp.go
@@ -64,7 +64,7 @@ func (c *ManifestConverter) GenerateStamp() (Stamp, error) {
 	stamp := Stamp{}
 
 	// Remember the original porter.yaml, base64 encoded to avoid canonical json shenanigans
-	rawManifest, err := manifest.ReadManifestData(c.Context, c.Manifest.ManifestPath)
+	rawManifest, err := manifest.ReadManifestData(c.Context, config.Name)
 	if err != nil {
 		return Stamp{}, err
 	}
@@ -90,13 +90,13 @@ func (c *ManifestConverter) GenerateStamp() (Stamp, error) {
 }
 
 func (c *ManifestConverter) DigestManifest() (string, error) {
-	if exists, _ := c.FileSystem.Exists(c.Manifest.ManifestPath); !exists {
-		return "", errors.Errorf("the specified porter configuration file %s does not exist", c.Manifest.ManifestPath)
+	if exists, _ := c.FileSystem.Exists(config.Name); !exists {
+		return "", errors.Errorf("the specified porter configuration file %s does not exist", config.Name)
 	}
 
-	data, err := c.FileSystem.ReadFile(c.Manifest.ManifestPath)
+	data, err := c.FileSystem.ReadFile(config.Name)
 	if err != nil {
-		return "", errors.Wrapf(err, "could not read manifest at %q", c.Manifest.ManifestPath)
+		return "", errors.Wrapf(err, "could not read manifest at %q", config.Name)
 	}
 
 	v := pkg.Version
diff --git a/pkg/porter/build.go b/pkg/porter/build.go
index 018e42a1..5e7a17b1 100644
--- a/pkg/porter/build.go
+++ b/pkg/porter/build.go
@@ -3,6 +3,7 @@ package porter
 import (
 	"fmt"
 	"os"
+	"regexp"
 
 	"get.porter.sh/porter/pkg/build"
 	configadapter "get.porter.sh/porter/pkg/cnab/config-adapter"
@@ -20,24 +21,67 @@ type BuildProvider interface {
 
 type BuildOptions struct {
 	contextOptions
+	metadataOpts
 	NoLint bool
 }
 
+// semVerRegex is a regex for ensuring bundle versions adhere to
+// semantic versioning per https://semver.org/#is-v123-a-semantic-version
+// Regex adapted from github.com/Masterminds/semver
+const semVerRegex string = `([0-9]+)(\.[0-9]+)?(\.[0-9]+)?` +
+	`(-([0-9A-Za-z\-]+(\.[0-9A-Za-z\-]+)*))?` +
+	`(\+([0-9A-Za-z\-]+(\.[0-9A-Za-z\-]+)*))?`
+
+func (o BuildOptions) Validate() error {
+	if o.Version != "" {
+		versionRegex := regexp.MustCompile("^" + semVerRegex + "$")
+		if m := versionRegex.FindStringSubmatch(o.Version); m == nil {
+			return fmt.Errorf("invalid bundle version: %q is not a valid semantic version", o.Version)
+		}
+	}
+
+	return nil
+}
+
 func (p *Porter) Build(opts BuildOptions) error {
 	opts.Apply(p.Context)
 
-	err := p.LoadManifest()
-	if err != nil {
+	if err := opts.Validate(); err != nil {
 		return err
 	}
 
+	if err := p.generateInternalManifest(opts.metadataOpts); err != nil {
+		return errors.Wrap(err, "unable to generate manifest")
+	}
+
+	// Publish may invoke this method and the manifest will already be
+	// populated.  Only load if still empty.
+	// For instance, Publish may be called with a full, new bundle reference
+	// via --tag, which will update the invocation image name and spark a new
+	// build here.  If we re-load from the local manifest, we will lose these
+	// values.
+	if p.Manifest == nil {
+		if err := p.LoadManifestFrom(build.LOCAL_MANIFEST); err != nil {
+			return err
+		}
+	}
+
 	if !opts.NoLint {
-		err = p.preLint()
-		if err != nil {
+		if err := p.preLint(); err != nil {
 			return err
 		}
 	}
 
+	// Build bundle so that resulting bundle.json is available for inclusion
+	// into the invocation image.
+	// Note: the content digest field on the invocation image section of the
+	// bundle.json will *not* be correct until the image is actually pushed
+	// to a registry.  The bundle.json will need to be updated after publishing
+	// and provided just-in-time during bundle execution.
+	if err := p.buildBundle(p.Manifest.Image, ""); err != nil {
+		return errors.Wrap(err, "unable to build bundle")
+	}
+
 	generator := build.NewDockerfileGenerator(p.Config, p.Manifest, p.Templates, p.Mixins)
 
 	if err := generator.PrepareFilesystem(); err != nil {
@@ -46,11 +90,8 @@ func (p *Porter) Build(opts BuildOptions) error {
 	if err := generator.GenerateDockerFile(); err != nil {
 		return fmt.Errorf("unable to generate Dockerfile: %s", err)
 	}
-	if err := p.Builder.BuildInvocationImage(p.Manifest); err != nil {
-		return errors.Wrap(err, "unable to build CNAB invocation image")
-	}
 
-	return p.buildBundle(p.Manifest.Image, "")
+	return errors.Wrap(p.Builder.BuildInvocationImage(p.Manifest), "unable to build CNAB invocation image")
 }
 
 func (p *Porter) preLint() error {
@@ -111,6 +152,7 @@ func (p *Porter) buildBundle(invocationImage string, digest string) error {
 	if err != nil {
 		return err
 	}
+
 	return p.writeBundle(bun)
 }
 
diff --git a/pkg/porter/generateManifest.go b/pkg/porter/generateManifest.go
new file mode 100644
index 00000000..361b12b3
--- /dev/null
+++ b/pkg/porter/generateManifest.go
@@ -0,0 +1,94 @@
+package porter
+
+import (
+	"os"
+
+	"get.porter.sh/porter/pkg/build"
+	"get.porter.sh/porter/pkg/config"
+	"github.com/mikefarah/yq/v3/pkg/yqlib"
+	"github.com/pkg/errors"
+	"gopkg.in/op/go-logging.v1"
+	"gopkg.in/yaml.v3"
+)
+
+// metadataOpts contain manifest fields eligible for dynamic
+// updating prior to saving Porter's internal version of the manifest
+type metadataOpts struct {
+	Name    string
+	Version string
+	Tag     string // This may be set via Publish
+}
+
+// generateInternalManifest decodes the manifest designated by filepath and applies
+// the provided generateInternalManifestOpts, saving the updated manifest to the path
+// designated by build.LOCAL_MANIFEST
+func (p *Porter) generateInternalManifest(opts metadataOpts) error {
+	p.initYqLogger()
+	var lib = yqlib.NewYqLib()
+
+	// Decode the user's manifest file into a yaml.Node
+	var node yaml.Node
+	input, err := p.FileSystem.Open(config.Name)
+	if err != nil {
+		return errors.Wrapf(err, "error opening %s", config.Name)
+	}
+	defer input.Close()
+
+	var decoder = yaml.NewDecoder(input)
+	err = decoder.Decode(&node)
+	if err != nil {
+		return errors.Wrap(err, "unable to decode manifest")
+	}
+
+	// Assemble update commands
+	var updateCommands []yqlib.UpdateCommand
+	if opts.Name != "" {
+		updateCommands = append(updateCommands, createUpdateCommand("name", opts.Name))
+	}
+	if opts.Version != "" {
+		updateCommands = append(updateCommands, createUpdateCommand("version", opts.Version))
+	}
+
+	// Make updates
+	for _, updateCommand := range updateCommands {
+		err = lib.Update(&node, updateCommand, true)
+		if err != nil {
+			return errors.Wrapf(err, "could not update manifest path %q with value %q", updateCommand.Path, updateCommand.Value.Value)
+		}
+	}
+
+	// Create the local app dir if it does not already exist
+	err = p.FileSystem.MkdirAll(build.LOCAL_APP, 0755)
+	if err != nil {
+		return errors.Wrapf(err, "unable to create directory %s", build.LOCAL_APP)
+	}
+
+	output, err := p.FileSystem.OpenFile(build.LOCAL_MANIFEST, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644)
+	if err != nil {
+		return errors.Wrapf(err, "error creating %s", build.LOCAL_MANIFEST)
+	}
+	defer output.Close()
+
+	// Encode the updated manifest to the proper location
+	// yqlib.NewYamlEncoder takes: dest (io.Writer), indent spaces (int), colorised output (bool)
+	var encoder = yqlib.NewYamlEncoder(output, 2, false)
+	return errors.Wrapf(encoder.Encode(&node), "unable to encode the manifest at %s", build.LOCAL_MANIFEST)
+}
+
+// createUpdateCommand creates a yqlib.UpdateCommand using the supplied YAML
+// path and replacement value
+func createUpdateCommand(path, value string) yqlib.UpdateCommand {
+	var valueParser = yqlib.NewValueParser()
+	// valueParser.Parse takes: argument (string), custom tag (string),
+	// custom style (string), anchor name (string), create alias (bool)
+	var parsedValue = valueParser.Parse(value, "", "", "", false)
+	return yqlib.UpdateCommand{Command: "update", Path: path, Value: parsedValue, Overwrite: true}
+}
+
+func (p *Porter) initYqLogger() {
+	// The yq lib that we use makes frequent calls to a logger
+	// Here we set up to log to Porter's Err stream at log level ERROR
+	var backend = logging.AddModuleLevel(logging.NewLogBackend(p.Err, "", 0))
+	backend.SetLevel(logging.ERROR, "yq")
+	logging.SetBackend(backend)
+}
diff --git a/pkg/porter/publish.go b/pkg/porter/publish.go
index d85de134..10206872 100644
--- a/pkg/porter/publish.go
+++ b/pkg/porter/publish.go
@@ -66,17 +66,15 @@ func (o *PublishOptions) Validate(cxt *portercontext.Context) error {
 // and then regenerates the bundle.json. Finally it publishes the manifest to an OCI registry.
 func (p *Porter) Publish(opts PublishOptions) error {
 	if opts.File != "" {
-		err := p.LoadManifestFrom(opts.File)
-		if err != nil {
+		if err := p.LoadManifestFrom(opts.File); err != nil {
 			return err
 		}
 	}
 
 	if opts.ArchiveFile == "" {
 		return p.publishFromFile(opts)
-	} else {
-		return p.publishFromArchive(opts)
 	}
+	return p.publishFromArchive(opts)
 }
 
 func (p *Porter) publishFromFile(opts PublishOptions) error {
@@ -88,17 +86,33 @@ func (p *Porter) publishFromFile(opts PublishOptions) error {
 			return errors.Wrapf(err, "unable to set invocation image name from tag %q", tag)
 		}
 	} else {
+		// If the manifest file is the default/user-supplied manifest,
+		// hot-swap in Porter's canonical translation (if exists) from
+		// the .cnab/app directory, as there may be dynamic overrides for
+		// the name and version fields to inform invocation image naming.
+		canonicalExists, err := p.FileSystem.Exists(build.LOCAL_MANIFEST)
+		if err != nil {
+			return err
+		}
+
+		if canonicalExists {
+			err := p.LoadManifestFrom(build.LOCAL_MANIFEST)
+			if err != nil {
+				return err
+			}
+		}
 		tag = p.Manifest.Reference
 	}
-	if p.Manifest.Reference == "" {
-		return errors.New("porter.yaml is missing registry or reference values needed for publishing")
-	}
 
 	err := p.ensureLocalBundleIsUpToDate(opts.bundleFileOptions)
 	if err != nil {
 		return err
 	}
 
+	if p.Manifest.Reference == "" {
+		return errors.New("porter.yaml is missing registry or reference values needed for publishing")
+	}
+
 	digest, err := p.Registry.PushInvocationImage(p.Manifest.Image)
 	if err != nil {
 		return errors.Wrapf(err, "unable to push CNAB invocation image %q", p.Manifest.Image)
diff --git a/pkg/templates/templates/build/Dockerfile b/pkg/templates/templates/build/Dockerfile
index c9da75d3..d23ffbfe 100644
--- a/pkg/templates/templates/build/Dockerfile
+++ b/pkg/templates/templates/build/Dockerfile
@@ -1,4 +1,4 @@
-FROM debian:stretch
+FROM debian:stretch-slim
 
 ARG BUNDLE_DIR
 
diff --git a/pkg/templates/templates/create/Dockerfile.tmpl b/pkg/templates/templates/create/Dockerfile.tmpl
index 58356b38..9a66e15f 100644
--- a/pkg/templates/templates/create/Dockerfile.tmpl
+++ b/pkg/templates/templates/create/Dockerfile.tmpl
@@ -1,4 +1,4 @@
-FROM debian:stretch
+FROM debian:stretch-slim
 
 ARG BUNDLE_DIR
 


Test patch:
diff --git a/pkg/build/dockerfile-generator_test.go b/pkg/build/dockerfile-generator_test.go
index a5546c1b..045f7060 100644
--- a/pkg/build/dockerfile-generator_test.go
+++ b/pkg/build/dockerfile-generator_test.go
@@ -31,7 +31,7 @@ func TestPorter_buildDockerfile(t *testing.T) {
 	require.NoError(t, err)
 
 	wantlines := []string{
-		"FROM debian:stretch",
+		"FROM debian:stretch-slim",
 		"",
 		"ARG BUNDLE_DIR",
 		"",
@@ -39,9 +39,9 @@ func TestPorter_buildDockerfile(t *testing.T) {
 		"",
 		"",
 		"COPY . $BUNDLE_DIR",
+		"RUN rm $BUNDLE_DIR/porter.yaml",
 		"RUN rm -fr $BUNDLE_DIR/.cnab",
 		"COPY .cnab /cnab",
-		"COPY porter.yaml $BUNDLE_DIR/porter.yaml",
 		"WORKDIR $BUNDLE_DIR",
 		"CMD [\"/cnab/app/run\"]",
 	}
@@ -107,9 +107,9 @@ COPY mybin /cnab/app/
 			"ARG BUNDLE_DIR",
 			"COPY mybin /cnab/app/",
 			"",
+			"RUN rm $BUNDLE_DIR/porter.yaml",
 			"RUN rm -fr $BUNDLE_DIR/.cnab",
 			"COPY .cnab /cnab",
-			"COPY porter.yaml $BUNDLE_DIR/porter.yaml",
 			"WORKDIR $BUNDLE_DIR",
 			"CMD [\"/cnab/app/run\"]",
 		}
@@ -137,7 +137,7 @@ func TestPorter_buildDockerfile_output(t *testing.T) {
 
 	wantlines := `
 Generating Dockerfile =======>
-FROM debian:stretch
+FROM debian:stretch-slim
 
 ARG BUNDLE_DIR
 
@@ -145,9 +145,9 @@ RUN apt-get update && apt-get install -y ca-certificates
 
 
 COPY . $BUNDLE_DIR
+RUN rm $BUNDLE_DIR/porter.yaml
 RUN rm -fr $BUNDLE_DIR/.cnab
 COPY .cnab /cnab
-COPY porter.yaml $BUNDLE_DIR/porter.yaml
 WORKDIR $BUNDLE_DIR
 CMD ["/cnab/app/run"]
 `
@@ -245,9 +245,9 @@ COPY mybin /cnab/app/
 		"",
 		"ARG BUNDLE_DIR",
 		"COPY mybin /cnab/app/",
+		"RUN rm $BUNDLE_DIR/porter.yaml",
 		"RUN rm -fr $BUNDLE_DIR/.cnab",
 		"COPY .cnab /cnab",
-		"COPY porter.yaml $BUNDLE_DIR/porter.yaml",
 		"WORKDIR $BUNDLE_DIR",
 		"CMD [\"/cnab/app/run\"]",
 	}
@@ -286,9 +286,9 @@ COPY mybin /cnab/app/
 		"COPY mybin /cnab/app/",
 		"# exec mixin has no buildtime dependencies",
 		"",
+		"RUN rm $BUNDLE_DIR/porter.yaml",
 		"RUN rm -fr $BUNDLE_DIR/.cnab",
 		"COPY .cnab /cnab",
-		"COPY porter.yaml $BUNDLE_DIR/porter.yaml",
 		"WORKDIR $BUNDLE_DIR",
 
 		"CMD [\"/cnab/app/run\"]",
diff --git a/pkg/porter/build_test.go b/pkg/porter/build_test.go
index 67f33137..1e3c913d 100644
--- a/pkg/porter/build_test.go
+++ b/pkg/porter/build_test.go
@@ -121,3 +121,38 @@ func TestPorter_paramRequired(t *testing.T) {
 	require.False(t, bundle.Parameters["command"].Required, "expected command param to not be required")
 	require.True(t, bundle.Parameters["command2"].Required, "expected command2 param to be required")
 }
+
+func TestValidateBuildOpts(t *testing.T) {
+	testcases := []struct {
+		name      string
+		opts      BuildOptions
+		wantError string
+	}{{
+		name:      "no opts",
+		opts:      BuildOptions{},
+		wantError: "",
+	}, {
+		name:      "invalid version set - latest",
+		opts:      BuildOptions{metadataOpts: metadataOpts{Version: "latest"}},
+		wantError: `invalid bundle version: "latest" is not a valid semantic version`,
+	}, {
+		name:      "invalid version set - v prefix",
+		opts:      BuildOptions{metadataOpts: metadataOpts{Version: "v1.0.0"}},
+		wantError: `invalid bundle version: "v1.0.0" is not a valid semantic version`,
+	}, {
+		name:      "valid name and value set",
+		opts:      BuildOptions{metadataOpts: metadataOpts{Name: "newname", Version: "1.0.0"}},
+		wantError: "",
+	}}
+
+	for _, tc := range testcases {
+		t.Run(tc.name, func(t *testing.T) {
+			err := tc.opts.Validate()
+			if tc.wantError != "" {
+				require.EqualError(t, err, tc.wantError)
+			} else {
+				require.NoError(t, err)
+			}
+		})
+	}
+}
diff --git a/pkg/porter/generateManifest_test.go b/pkg/porter/generateManifest_test.go
new file mode 100644
index 00000000..15258658
--- /dev/null
+++ b/pkg/porter/generateManifest_test.go
@@ -0,0 +1,52 @@
+package porter
+
+import (
+	"path/filepath"
+	"testing"
+
+	"get.porter.sh/porter/pkg/build"
+	"get.porter.sh/porter/pkg/config"
+	"github.com/stretchr/testify/require"
+)
+
+func TestgenerateInternalManifest(t *testing.T) {
+	testcases := []struct {
+		name         string
+		opts         metadataOpts
+		wantManifest string
+	}{{
+		name:         "no opts",
+		opts:         metadataOpts{},
+		wantManifest: "original.yaml",
+	}, {
+		name:         "name set",
+		opts:         metadataOpts{Name: "newname"},
+		wantManifest: "new-name.yaml",
+	}, {
+		name:         "version set",
+		opts:         metadataOpts{Version: "1.0.0"},
+		wantManifest: "new-version.yaml",
+	}, {
+		name:         "name and value set",
+		opts:         metadataOpts{Name: "newname", Version: "1.0.0"},
+		wantManifest: "all-fields.yaml",
+	}}
+
+	p := NewTestPorter(t)
+	p.TestConfig.SetupPorterHome()
+	p.TestConfig.TestContext.AddTestFile("testdata/generateManifest/original.yaml", config.Name)
+
+	for _, tc := range testcases {
+		t.Run(tc.name, func(t *testing.T) {
+			err := p.generateInternalManifest(tc.opts)
+			require.NoError(t, err)
+
+			want := p.TestConfig.TestContext.AddTestFile(
+				filepath.Join("testdata/generateManifest", tc.wantManifest), tc.wantManifest)
+
+			got, err := p.FileSystem.ReadFile(build.LOCAL_MANIFEST)
+			require.NoError(t, err)
+			require.Equal(t, want, got)
+		})
+	}
+}
diff --git a/pkg/porter/testdata/generateManifest/all-fields.yaml b/pkg/porter/testdata/generateManifest/all-fields.yaml
new file mode 100644
index 00000000..c006dc73
--- /dev/null
+++ b/pkg/porter/testdata/generateManifest/all-fields.yaml
@@ -0,0 +1,25 @@
+name: newname
+version: 1.0.0
+description: "An example Porter configuration"
+registry: getporter
+mixins:
+  - exec
+install:
+  - exec:
+      description: "Install Hello World"
+      command: ./helpers.sh
+      arguments:
+        - install
+status:
+  - exec:
+      description: "World Status"
+      command: ./helpers.sh
+      arguments:
+        - status
+uninstall:
+  - exec:
+      description: "Uninstall Hello World"
+      command: ./helpers.sh
+      arguments:
+        - uninstall
+# comments n stuff
diff --git a/pkg/porter/testdata/generateManifest/new-name.yaml b/pkg/porter/testdata/generateManifest/new-name.yaml
new file mode 100644
index 00000000..bdfab691
--- /dev/null
+++ b/pkg/porter/testdata/generateManifest/new-name.yaml
@@ -0,0 +1,25 @@
+name: newname
+version: 0.1.0
+description: "An example Porter configuration"
+registry: getporter
+mixins:
+  - exec
+install:
+  - exec:
+      description: "Install Hello World"
+      command: ./helpers.sh
+      arguments:
+        - install
+status:
+  - exec:
+      description: "World Status"
+      command: ./helpers.sh
+      arguments:
+        - status
+uninstall:
+  - exec:
+      description: "Uninstall Hello World"
+      command: ./helpers.sh
+      arguments:
+        - uninstall
+# comments n stuff
diff --git a/pkg/porter/testdata/generateManifest/new-version.yaml b/pkg/porter/testdata/generateManifest/new-version.yaml
new file mode 100644
index 00000000..279d497f
--- /dev/null
+++ b/pkg/porter/testdata/generateManifest/new-version.yaml
@@ -0,0 +1,25 @@
+name: porter-hello
+version: 1.0.0
+description: "An example Porter configuration"
+registry: getporter
+mixins:
+  - exec
+install:
+  - exec:
+      description: "Install Hello World"
+      command: ./helpers.sh
+      arguments:
+        - install
+status:
+  - exec:
+      description: "World Status"
+      command: ./helpers.sh
+      arguments:
+        - status
+uninstall:
+  - exec:
+      description: "Uninstall Hello World"
+      command: ./helpers.sh
+      arguments:
+        - uninstall
+# comments n stuff
diff --git a/pkg/porter/testdata/generateManifest/original.yaml b/pkg/porter/testdata/generateManifest/original.yaml
new file mode 100644
index 00000000..770f4985
--- /dev/null
+++ b/pkg/porter/testdata/generateManifest/original.yaml
@@ -0,0 +1,25 @@
+name: porter-hello
+version: 0.1.0
+description: "An example Porter configuration"
+registry: getporter
+mixins:
+  - exec
+install:
+  - exec:
+      description: "Install Hello World"
+      command: ./helpers.sh
+      arguments:
+        - install
+status:
+  - exec:
+      description: "World Status"
+      command: ./helpers.sh
+      arguments:
+        - status
+uninstall:
+  - exec:
+      description: "Uninstall Hello World"
+      command: ./helpers.sh
+      arguments:
+        - uninstall
+# comments n stuff