Repository: replicatedhq/kots
Pull request #129 (2019-11-06 23:09:10)

kotsadm should use user 1001 or 101, not 0


Patch:
diff --git a/ffi/config.go b/ffi/config.go
index b1fa46d7c..95d573ceb 100644
--- a/ffi/config.go
+++ b/ffi/config.go
@@ -12,7 +12,7 @@ import (
 func TemplateConfig(configPath string, configData string, configValuesData string) *C.char {
 	rendered, err := config.TemplateConfig(configPath, configData, configValuesData)
 	if err != nil {
-		fmt.Printf("failed to apply templates to config: %s\n", err.Error())
+		fmt.Printf("failed to decode config data: %s\n", err.Error())
 		return C.CString("")
 	}
 	return C.CString(rendered)
diff --git a/ffi/main.go b/ffi/main.go
index ef3b6c71f..f4c1d24ee 100644
--- a/ffi/main.go
+++ b/ffi/main.go
@@ -6,7 +6,7 @@ import (
 	"fmt"
 	"io/ioutil"
 	"os"
-	"path/filepath"
+	"path"
 
 	"github.com/mholt/archiver"
 	"github.com/pkg/errors"
@@ -57,7 +57,7 @@ func UpdateCheck(socket, fromArchivePath string) {
 			return
 		}
 
-		expectedLicenseFile := filepath.Join(tmpRoot, "upstream", "userdata", "license.yaml")
+		expectedLicenseFile := path.Join(tmpRoot, "upstream", "userdata", "license.yaml")
 		_, err = os.Stat(expectedLicenseFile)
 		if err != nil {
 			fmt.Printf("failed to find license file in archive\n")
@@ -83,7 +83,6 @@ func UpdateCheck(socket, fromArchivePath string) {
 
 		pullOptions := pull.PullOptions{
 			LicenseFile:         expectedLicenseFile,
-			ConfigFile:          filepath.Join(tmpRoot, "upstream", "userdata", "config.yaml"),
 			RootDir:             tmpRoot,
 			ExcludeKotsKinds:    true,
 			ExcludeAdminConsole: true,
@@ -112,9 +111,9 @@ func UpdateCheck(socket, fromArchivePath string) {
 		}
 
 		paths := []string{
-			filepath.Join(tmpRoot, "upstream"),
-			filepath.Join(tmpRoot, "base"),
-			filepath.Join(tmpRoot, "overlays"),
+			path.Join(tmpRoot, "upstream"),
+			path.Join(tmpRoot, "base"),
+			path.Join(tmpRoot, "overlays"),
 		}
 
 		err = os.Remove(fromArchivePath)
@@ -135,7 +134,7 @@ func UpdateCheck(socket, fromArchivePath string) {
 }
 
 func readCursorFromPath(rootPath string) (string, error) {
-	installationFilePath := filepath.Join(rootPath, "upstream", "userdata", "installation.yaml")
+	installationFilePath := path.Join(rootPath, "upstream", "userdata", "installation.yaml")
 	_, err := os.Stat(installationFilePath)
 	if os.IsNotExist(err) {
 		return "", nil
diff --git a/kotskinds/apis/kots/v1beta1/configvalues_types.go b/kotskinds/apis/kots/v1beta1/configvalues_types.go
index 90a54f6d5..35008ad27 100644
--- a/kotskinds/apis/kots/v1beta1/configvalues_types.go
+++ b/kotskinds/apis/kots/v1beta1/configvalues_types.go
@@ -20,14 +20,9 @@ import (
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 )
 
-type ConfigValue struct {
-	Value   string `json:"value,omitempty"`
-	Default string `json:"default,omitempty"`
-}
-
 // ConfigValuesSpec defines the desired state of ConfigValue
 type ConfigValuesSpec struct {
-	Values map[string]ConfigValue `json:"values"`
+	Values map[string]string `json:"values"`
 }
 
 // ConfigValuesStatus defines the observed state of ConfigValues
diff --git a/kotskinds/apis/kots/v1beta1/zz_generated.deepcopy.go b/kotskinds/apis/kots/v1beta1/zz_generated.deepcopy.go
index c5945e65a..2589c6f5a 100644
--- a/kotskinds/apis/kots/v1beta1/zz_generated.deepcopy.go
+++ b/kotskinds/apis/kots/v1beta1/zz_generated.deepcopy.go
@@ -106,18 +106,6 @@ func (in *ApplicationSpec) DeepCopyInto(out *ApplicationSpec) {
 		*out = make([]ApplicationPort, len(*in))
 		copy(*out, *in)
 	}
-	if in.StatusInformers != nil {
-		in, out := &in.StatusInformers, &out.StatusInformers
-		*out = make([]StatusInformer, len(*in))
-		copy(*out, *in)
-	}
-	if in.Graphs != nil {
-		in, out := &in.Graphs, &out.Graphs
-		*out = make([]MetricGraph, len(*in))
-		for i := range *in {
-			(*in)[i].DeepCopyInto(&(*out)[i])
-		}
-	}
 }
 
 // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ApplicationSpec.
@@ -303,21 +291,6 @@ func (in *ConfigStatus) DeepCopy() *ConfigStatus {
 	return out
 }
 
-// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
-func (in *ConfigValue) DeepCopyInto(out *ConfigValue) {
-	*out = *in
-}
-
-// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ConfigValue.
-func (in *ConfigValue) DeepCopy() *ConfigValue {
-	if in == nil {
-		return nil
-	}
-	out := new(ConfigValue)
-	in.DeepCopyInto(out)
-	return out
-}
-
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 func (in *ConfigValues) DeepCopyInto(out *ConfigValues) {
 	*out = *in
@@ -382,7 +355,7 @@ func (in *ConfigValuesSpec) DeepCopyInto(out *ConfigValuesSpec) {
 	*out = *in
 	if in.Values != nil {
 		in, out := &in.Values, &out.Values
-		*out = make(map[string]ConfigValue, len(*in))
+		*out = make(map[string]string, len(*in))
 		for key, val := range *in {
 			(*out)[key] = val
 		}
@@ -634,53 +607,3 @@ func (in *LicenseStatus) DeepCopy() *LicenseStatus {
 	in.DeepCopyInto(out)
 	return out
 }
-
-// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
-func (in *MetricGraph) DeepCopyInto(out *MetricGraph) {
-	*out = *in
-	if in.Queries != nil {
-		in, out := &in.Queries, &out.Queries
-		*out = make([]MetricQuery, len(*in))
-		copy(*out, *in)
-	}
-}
-
-// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MetricGraph.
-func (in *MetricGraph) DeepCopy() *MetricGraph {
-	if in == nil {
-		return nil
-	}
-	out := new(MetricGraph)
-	in.DeepCopyInto(out)
-	return out
-}
-
-// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
-func (in *MetricQuery) DeepCopyInto(out *MetricQuery) {
-	*out = *in
-}
-
-// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MetricQuery.
-func (in *MetricQuery) DeepCopy() *MetricQuery {
-	if in == nil {
-		return nil
-	}
-	out := new(MetricQuery)
-	in.DeepCopyInto(out)
-	return out
-}
-
-// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
-func (in *StatusInformer) DeepCopyInto(out *StatusInformer) {
-	*out = *in
-}
-
-// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new StatusInformer.
-func (in *StatusInformer) DeepCopy() *StatusInformer {
-	if in == nil {
-		return nil
-	}
-	out := new(StatusInformer)
-	in.DeepCopyInto(out)
-	return out
-}
diff --git a/pkg/base/replicated.go b/pkg/base/replicated.go
index 8df24aacb..ff3570f0c 100644
--- a/pkg/base/replicated.go
+++ b/pkg/base/replicated.go
@@ -20,7 +20,7 @@ func renderReplicated(u *upstream.Upstream, renderOptions *RenderOptions) (*Base
 	}
 
 	// Find the values from the context
-	var templateContext map[string]template.ItemValue
+	var templateContext map[string]interface{}
 	for _, c := range u.Files {
 		if c.Path == "userdata/config.yaml" {
 			ctx, err := UnmarshalConfigValuesContent(c.Content)
@@ -66,7 +66,7 @@ func renderReplicated(u *upstream.Upstream, renderOptions *RenderOptions) (*Base
 	return &base, nil
 }
 
-func UnmarshalConfigValuesContent(content []byte) (map[string]template.ItemValue, error) {
+func UnmarshalConfigValuesContent(content []byte) (map[string]interface{}, error) {
 	kotsscheme.AddToScheme(scheme.Scheme)
 	decode := scheme.Codecs.UniversalDeserializer().Decode
 	obj, gvk, err := decode(content, nil, nil)
@@ -80,12 +80,9 @@ func UnmarshalConfigValuesContent(content []byte) (map[string]template.ItemValue
 
 	values := obj.(*kotsv1beta1.ConfigValues)
 
-	ctx := map[string]template.ItemValue{}
+	ctx := map[string]interface{}{}
 	for k, v := range values.Spec.Values {
-		ctx[k] = template.ItemValue{
-			Value:   v.Value,
-			Default: v.Default,
-		}
+		ctx[k] = v
 	}
 
 	return ctx, nil
diff --git a/pkg/config/config.go b/pkg/config/config.go
index 66e0d6215..915a51193 100644
--- a/pkg/config/config.go
+++ b/pkg/config/config.go
@@ -2,6 +2,7 @@ package config
 
 import (
 	"bytes"
+	"fmt"
 
 	"github.com/pkg/errors"
 	kotsv1beta1 "github.com/replicatedhq/kots/kotskinds/apis/kots/v1beta1"
@@ -69,19 +70,17 @@ func marshalConfig(config *kotsv1beta1.Config) (string, error) {
 	return string(marshalled.Bytes()), nil
 }
 
-func applyValuesToConfig(config *kotsv1beta1.Config, values map[string]template.ItemValue) {
+func applyValuesToConfig(config *kotsv1beta1.Config, values map[string]interface{}) {
 	for idxG, g := range config.Spec.Groups {
 		for idxI, i := range g.Items {
 			value, ok := values[i.Name]
 			if ok {
-				config.Spec.Groups[idxG].Items[idxI].Value = value.ValueStr()
-				config.Spec.Groups[idxG].Items[idxI].Default = value.DefaultStr()
+				config.Spec.Groups[idxG].Items[idxI].Value = fmt.Sprintf("%s", value) // this assumes "value" implements String function
 			}
 			for idxC, c := range i.Items {
 				value, ok := values[c.Name]
 				if ok {
-					config.Spec.Groups[idxG].Items[idxI].Items[idxC].Value = value.ValueStr()
-					config.Spec.Groups[idxG].Items[idxI].Items[idxC].Default = value.DefaultStr()
+					config.Spec.Groups[idxG].Items[idxI].Items[idxC].Value = fmt.Sprintf("%s", value) // this assumes "value" implements String function
 				}
 			}
 		}
diff --git a/pkg/kotsadm/api_objects.go b/pkg/kotsadm/api_objects.go
index a45d2ccc7..3483b8896 100644
--- a/pkg/kotsadm/api_objects.go
+++ b/pkg/kotsadm/api_objects.go
@@ -8,6 +8,8 @@ import (
 	rbacv1 "k8s.io/api/rbac/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/apimachinery/pkg/util/intstr"
+
+	"github.com/replicatedhq/kots/pkg/util"
 )
 
 func apiRole(namespace string) *rbacv1.Role {
@@ -98,6 +100,9 @@ func apiDeployment(namespace, autoCreateClusterToken string) *appsv1.Deployment
 					},
 				},
 				Spec: corev1.PodSpec{
+					SecurityContext: &corev1.PodSecurityContext{
+						RunAsUser: util.IntPointer(1001),
+					},
 					ServiceAccountName: "kotsadm-api",
 					RestartPolicy:      corev1.RestartPolicyAlways,
 					Containers: []corev1.Container{
diff --git a/pkg/kotsadm/minio_objects.go b/pkg/kotsadm/minio_objects.go
index 19d07c361..73c5ce8c3 100644
--- a/pkg/kotsadm/minio_objects.go
+++ b/pkg/kotsadm/minio_objects.go
@@ -146,6 +146,9 @@ func minioStatefulset(namespace string) *appsv1.StatefulSet {
 					},
 				},
 				Spec: corev1.PodSpec{
+					SecurityContext: &corev1.PodSecurityContext{
+						// RunAsUser: util.IntPointer(1001), // TODO: make real user #
+					},
 					Volumes: []corev1.Volume{
 						{
 							Name: "kotsadm-minio",
@@ -300,6 +303,9 @@ func minioJob(namespace string) *batchv1.Job {
 					},
 				},
 				Spec: corev1.PodSpec{
+					SecurityContext: &corev1.PodSecurityContext{
+						// RunAsUser: util.IntPointer(1001), // TODO: make real user #
+					},
 					RestartPolicy: corev1.RestartPolicyOnFailure,
 					Volumes: []corev1.Volume{
 						{
diff --git a/pkg/kotsadm/operator_objects.go b/pkg/kotsadm/operator_objects.go
index 621d6737f..9058815b1 100644
--- a/pkg/kotsadm/operator_objects.go
+++ b/pkg/kotsadm/operator_objects.go
@@ -7,6 +7,8 @@ import (
 	corev1 "k8s.io/api/core/v1"
 	rbacv1 "k8s.io/api/rbac/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+
+	"github.com/replicatedhq/kots/pkg/util"
 )
 
 func operatorRole(namespace string) *rbacv1.Role {
@@ -96,6 +98,9 @@ func operatorDeployment(namespace, autoCreateClusterToken string) *appsv1.Deploy
 					},
 				},
 				Spec: corev1.PodSpec{
+					SecurityContext: &corev1.PodSecurityContext{
+						RunAsUser: util.IntPointer(1001),
+					},
 					ServiceAccountName: "kotsadm-operator",
 					RestartPolicy:      corev1.RestartPolicyAlways,
 					Containers: []corev1.Container{
@@ -112,14 +117,6 @@ func operatorDeployment(namespace, autoCreateClusterToken string) *appsv1.Deploy
 									Name:  "KOTSADM_TOKEN",
 									Value: autoCreateClusterToken,
 								},
-								{
-									Name: "KOTSADM_TARGET_NAMESPACE",
-									ValueFrom: &corev1.EnvVarSource{
-										FieldRef: &corev1.ObjectFieldSelector{
-											FieldPath: "metadata.namespace",
-										},
-									},
-								},
 							},
 						},
 					},
diff --git a/pkg/kotsadm/postgres_objects.go b/pkg/kotsadm/postgres_objects.go
index 114b28b5c..07dc6cd5f 100644
--- a/pkg/kotsadm/postgres_objects.go
+++ b/pkg/kotsadm/postgres_objects.go
@@ -48,6 +48,9 @@ func postgresStatefulset(namespace string) *appsv1.StatefulSet {
 					},
 				},
 				Spec: corev1.PodSpec{
+					SecurityContext: &corev1.PodSecurityContext{
+						// RunAsUser: util.IntPointer(1001), // TODO: make real user #
+					},
 					Volumes: []corev1.Volume{
 						{
 							Name: "kotsadm-postgres",
diff --git a/pkg/kotsadm/schemahero_objects.go b/pkg/kotsadm/schemahero_objects.go
index 5efa5e095..526373a18 100644
--- a/pkg/kotsadm/schemahero_objects.go
+++ b/pkg/kotsadm/schemahero_objects.go
@@ -6,6 +6,8 @@ import (
 
 	corev1 "k8s.io/api/core/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+
+	"github.com/replicatedhq/kots/pkg/util"
 )
 
 func migrationsPod(deployOptions DeployOptions) *corev1.Pod {
@@ -21,6 +23,10 @@ func migrationsPod(deployOptions DeployOptions) *corev1.Pod {
 			Namespace: deployOptions.Namespace,
 		},
 		Spec: corev1.PodSpec{
+			SecurityContext: &corev1.PodSecurityContext{
+				RunAsUser: util.IntPointer(1001),
+				FSGroup:   util.IntPointer(1001),
+			},
 			RestartPolicy: corev1.RestartPolicyOnFailure,
 			Containers: []corev1.Container{
 				{
diff --git a/pkg/kotsadm/web.go b/pkg/kotsadm/web.go
index 27a62abbf..591117a39 100644
--- a/pkg/kotsadm/web.go
+++ b/pkg/kotsadm/web.go
@@ -17,7 +17,7 @@ import (
 )
 
 var (
-	executableMode = int32(484)
+	executableMode = int32(511) // hex 777
 )
 
 func getWebYAML(deployOptions DeployOptions) (map[string][]byte, error) {
diff --git a/pkg/kotsadm/web_objects.go b/pkg/kotsadm/web_objects.go
index f94ae46ca..8e4393910 100644
--- a/pkg/kotsadm/web_objects.go
+++ b/pkg/kotsadm/web_objects.go
@@ -7,6 +7,8 @@ import (
 	corev1 "k8s.io/api/core/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/apimachinery/pkg/util/intstr"
+
+	"github.com/replicatedhq/kots/pkg/util"
 )
 
 func webConfig(deployOptions DeployOptions) *corev1.ConfigMap {
@@ -64,6 +66,10 @@ func webDeployment(namespace string) *appsv1.Deployment {
 					},
 				},
 				Spec: corev1.PodSpec{
+					SecurityContext: &corev1.PodSecurityContext{
+						RunAsUser: util.IntPointer(101),
+						FSGroup:   util.IntPointer(101),
+					},
 					Volumes: []corev1.Volume{
 						{
 							Name: "kotsadm-web-scripts",
diff --git a/pkg/pull/pull.go b/pkg/pull/pull.go
index df72e249b..74ca0df71 100644
--- a/pkg/pull/pull.go
+++ b/pkg/pull/pull.go
@@ -7,7 +7,6 @@ import (
 	"io"
 	"io/ioutil"
 	"net/url"
-	"os"
 	"path/filepath"
 
 	"github.com/pkg/errors"
@@ -33,7 +32,6 @@ type PullOptions struct {
 	Downstreams         []string
 	LocalPath           string
 	LicenseFile         string
-	ConfigFile          string
 	ExcludeKotsKinds    bool
 	ExcludeAdminConsole bool
 	SharedPassword      string
@@ -109,8 +107,6 @@ func Pull(upstreamURI string, pullOptions PullOptions) (string, error) {
 
 	fetchOptions := upstream.FetchOptions{}
 	fetchOptions.HelmRepoURI = pullOptions.HelmRepoURI
-	fetchOptions.RootDir = pullOptions.RootDir
-	fetchOptions.UseAppDir = pullOptions.CreateAppDir
 	fetchOptions.LocalPath = pullOptions.LocalPath
 
 	if pullOptions.LicenseFile != "" {
@@ -127,13 +123,6 @@ func Pull(upstreamURI string, pullOptions PullOptions) (string, error) {
 
 		fetchOptions.License = license
 	}
-	if pullOptions.ConfigFile != "" {
-		config, err := parseConfigValuesFromFile(pullOptions.ConfigFile)
-		if err != nil {
-			return "", errors.Wrap(err, "failed to parse license from file")
-		}
-		fetchOptions.ConfigValues = config
-	}
 
 	log.ActionWithSpinner("Pulling upstream")
 	u, err := upstream.FetchUpstream(upstreamURI, &fetchOptions)
@@ -369,31 +358,6 @@ func parseLicenseFromFile(filename string) (*kotsv1beta1.License, error) {
 	return license, nil
 }
 
-func parseConfigValuesFromFile(filename string) (*kotsv1beta1.ConfigValues, error) {
-	contents, err := ioutil.ReadFile(filename)
-	if err != nil {
-		if os.IsNotExist(err) {
-			return nil, nil
-		}
-		return nil, errors.Wrap(err, "failed to read config values file")
-	}
-
-	kotsscheme.AddToScheme(scheme.Scheme)
-	decode := scheme.Codecs.UniversalDeserializer().Decode
-	decoded, gvk, err := decode(contents, nil, nil)
-	if err != nil {
-		return nil, errors.Wrap(err, "unable to decode config values file")
-	}
-
-	if gvk.Group != "kots.io" || gvk.Version != "v1beta1" || gvk.Kind != "ConfigValues" {
-		return nil, errors.New("not config values")
-	}
-
-	config := decoded.(*kotsv1beta1.ConfigValues)
-
-	return config, nil
-}
-
 type registryInfo struct {
 	Registry string
 	Proxy    string
diff --git a/pkg/template/config_context.go b/pkg/template/config_context.go
index 3cdcfb995..f3bd5d3b4 100644
--- a/pkg/template/config_context.go
+++ b/pkg/template/config_context.go
@@ -9,7 +9,7 @@ import (
 	kotsv1beta1 "github.com/replicatedhq/kots/kotskinds/apis/kots/v1beta1"
 )
 
-func (b *Builder) NewConfigContext(configGroups []kotsv1beta1.ConfigGroup, templateContext map[string]ItemValue) (*ConfigCtx, error) {
+func (b *Builder) NewConfigContext(configGroups []kotsv1beta1.ConfigGroup, templateContext map[string]interface{}) (*ConfigCtx, error) {
 	configCtx := &ConfigCtx{
 		ItemValues: templateContext,
 	}
@@ -20,23 +20,19 @@ func (b *Builder) NewConfigContext(configGroups []kotsv1beta1.ConfigGroup, templ
 			// We have to ignore errors here because we only have the static context loaded
 			// for rendering. some items have templates that need the config context,
 			// so we can ignore these.
+			builtDefault, _ := b.String(configItem.Default)
+			builtValue, _ := b.String(configItem.Value)
 
-			var itemValue ItemValue
+			var built string
 			if v, ok := templateContext[configItem.Name]; ok {
-				itemValue = ItemValue{
-					Value:   v.Value,
-					Default: v.Default,
-				}
+				built = fmt.Sprintf("%s", v)
+			} else if builtValue != "" {
+				built = builtValue
 			} else {
-				builtDefault, _ := b.String(configItem.Default)
-				builtValue, _ := b.String(configItem.Value)
-				itemValue = ItemValue{
-					Value:   builtValue,
-					Default: builtDefault,
-				}
+				built = builtDefault
 			}
 
-			configCtx.ItemValues[configItem.Name] = itemValue
+			configCtx.ItemValues[configItem.Name] = built
 		}
 	}
 
@@ -44,35 +40,8 @@ func (b *Builder) NewConfigContext(configGroups []kotsv1beta1.ConfigGroup, templ
 }
 
 // ConfigCtx is the context for builder functions before the application has started.
-type ItemValue struct {
-	Value   interface{}
-	Default interface{}
-}
-
-func (i ItemValue) HasValue() bool {
-	return i.Value != nil
-}
-
-func (i ItemValue) ValueStr() string {
-	if i.HasValue() {
-		return fmt.Sprintf("%s", i.Value)
-	}
-	return ""
-}
-
-func (i ItemValue) HasDefault() bool {
-	return i.Default != nil
-}
-
-func (i ItemValue) DefaultStr() string {
-	if i.HasDefault() {
-		return fmt.Sprintf("%s", i.Default)
-	}
-	return ""
-}
-
 type ConfigCtx struct {
-	ItemValues map[string]ItemValue
+	ItemValues map[string]interface{}
 }
 
 // FuncMap represents the available functions in the ConfigCtx.
@@ -131,14 +100,9 @@ func (ctx ConfigCtx) configOptionNotEquals(name string, value string) bool {
 }
 
 func (ctx ConfigCtx) getConfigOptionValue(itemName string) (string, error) {
-	val, ok := ctx.ItemValues[itemName]
-	if !ok {
-		return "", errors.New("unable to find config item")
-	}
-
-	if val.HasValue() {
-		return val.ValueStr(), nil
+	if val, ok := ctx.ItemValues[itemName]; ok {
+		return fmt.Sprintf("%s", val), nil
 	}
 
-	return val.DefaultStr(), nil
+	return "", errors.New("unable to find config item")
 }
diff --git a/pkg/upstream/fetch.go b/pkg/upstream/fetch.go
index a7c8068c5..235f3f94c 100644
--- a/pkg/upstream/fetch.go
+++ b/pkg/upstream/fetch.go
@@ -9,14 +9,11 @@ import (
 )
 
 type FetchOptions struct {
-	RootDir      string
-	UseAppDir    bool
 	HelmRepoName string
 	HelmRepoURI  string
 	HelmOptions  []string
 	LocalPath    string
 	License      *kotsv1beta1.License
-	ConfigValues *kotsv1beta1.ConfigValues
 }
 
 func FetchUpstream(upstreamURI string, fetchOptions *FetchOptions) (*Upstream, error) {
@@ -41,7 +38,7 @@ func downloadUpstream(upstreamURI string, fetchOptions *FetchOptions) (*Upstream
 		return downloadHelm(u, fetchOptions.HelmRepoURI)
 	}
 	if u.Scheme == "replicated" {
-		return downloadReplicated(u, fetchOptions.LocalPath, fetchOptions.RootDir, fetchOptions.UseAppDir, fetchOptions.License, fetchOptions.ConfigValues)
+		return downloadReplicated(u, fetchOptions.LocalPath, fetchOptions.License)
 	}
 	if u.Scheme == "git" {
 		return downloadGit(upstreamURI)
diff --git a/pkg/upstream/replicated.go b/pkg/upstream/replicated.go
index 4d0a0c1b9..51a0aa16b 100644
--- a/pkg/upstream/replicated.go
+++ b/pkg/upstream/replicated.go
@@ -60,7 +60,7 @@ type Release struct {
 	Manifests    map[string][]byte
 }
 
-func downloadReplicated(u *url.URL, localPath string, rootDir string, useAppDir bool, license *kotsv1beta1.License, existingConfigValues *kotsv1beta1.ConfigValues) (*Upstream, error) {
+func downloadReplicated(u *url.URL, localPath string, license *kotsv1beta1.License) (*Upstream, error) {
 	var release *Release
 
 	if localPath != "" {
@@ -95,33 +95,17 @@ func downloadReplicated(u *url.URL, localPath string, rootDir string, useAppDir
 	}
 
 	// Find the config in the upstream and write out default values
-
-	application := findAppInRelease(release) // this function never returns nil
-
-	// NOTE: this currently comes from the application spec and not the channel release meta
-	if release.ReleaseNotes == "" {
-		release.ReleaseNotes = application.Spec.ReleaseNotes
-	}
-
-	if existingConfigValues == nil {
-		var prevConfigFile string
-		if useAppDir {
-			prevConfigFile = filepath.Join(rootDir, application.Name, "upstream", "userdata", "config.yaml")
-		} else {
-			prevConfigFile = filepath.Join(rootDir, "upstream", "userdata", "config.yaml")
-		}
-		var err error
-		existingConfigValues, err = findConfigValuesInFile(prevConfigFile)
-		if err != nil {
-			return nil, errors.Wrap(err, "failed to load existing config values")
+	application := findAppInRelease(release)
+	if application != nil {
+		// NOTE: this currently comes from the application spec and not the channel release meta
+		if release.ReleaseNotes == "" {
+			release.ReleaseNotes = application.Spec.ReleaseNotes
 		}
 	}
 
 	config := findConfigInRelease(release)
-	if config != nil || existingConfigValues != nil {
-		// If config existed and was removed from the app,
-		// values will be carried over to the new version anyway.
-		configValues, err := createConfigValues(application.Name, config, existingConfigValues)
+	if config != nil {
+		configValues, err := createEmptyConfigValues(application.Name, config)
 		if err != nil {
 			return nil, errors.Wrap(err, "failed to create empty config values")
 		}
@@ -346,29 +330,9 @@ func mustMarshalConfigValues(configValues *kotsv1beta1.ConfigValues) []byte {
 	return b.Bytes()
 }
 
-func createConfigValues(applicationName string, config *kotsv1beta1.Config, existingConfigValues *kotsv1beta1.ConfigValues) (*kotsv1beta1.ConfigValues, error) {
-	var newValues kotsv1beta1.ConfigValuesSpec
-	if existingConfigValues != nil {
-		newValues = kotsv1beta1.ConfigValuesSpec{
-			Values: existingConfigValues.Spec.Values,
-		}
-	} else {
-		newValues = kotsv1beta1.ConfigValuesSpec{
-			Values: map[string]kotsv1beta1.ConfigValue{},
-		}
-	}
-
-	if config == nil {
-		return &kotsv1beta1.ConfigValues{
-			TypeMeta: metav1.TypeMeta{
-				APIVersion: "kots.io/v1beta1",
-				Kind:       "ConfigValues",
-			},
-			ObjectMeta: metav1.ObjectMeta{
-				Name: applicationName,
-			},
-			Spec: newValues,
-		}, nil
+func createEmptyConfigValues(applicationName string, config *kotsv1beta1.Config) (*kotsv1beta1.ConfigValues, error) {
+	emptyValues := kotsv1beta1.ConfigValuesSpec{
+		Values: map[string]string{},
 	}
 
 	builder := template.Builder{}
@@ -376,36 +340,13 @@ func createConfigValues(applicationName string, config *kotsv1beta1.Config, exis
 
 	for _, group := range config.Spec.Groups {
 		for _, item := range group.Items {
-			var foundValue string
-			prevValue, ok := newValues.Values[item.Name]
-			if ok && prevValue.Value != "" {
-				foundValue = prevValue.Value
-			}
-
-			renderedValue, err := builder.RenderTemplate(item.Name, item.Value)
-			if err != nil {
-				return nil, errors.Wrap(err, "failed to render config item value")
-			}
-
-			renderedDefault, err := builder.RenderTemplate(item.Name, item.Default)
-			if err != nil {
-				return nil, errors.Wrap(err, "failed to render config item default")
-			}
-
-			if renderedValue == "" && renderedDefault == "" && foundValue == "" {
-				continue
-			}
-
-			if foundValue != "" {
-				newValues.Values[item.Name] = kotsv1beta1.ConfigValue{
-					Value:   foundValue,
-					Default: renderedDefault,
-				}
-			} else {
-				newValues.Values[item.Name] = kotsv1beta1.ConfigValue{
-					Value:   renderedValue,
-					Default: renderedDefault,
+			if item.Value != "" {
+				rendered, err := builder.RenderTemplate(item.Name, item.Value)
+				if err != nil {
+					return nil, errors.Wrap(err, "failed to render config item value")
 				}
+
+				emptyValues.Values[item.Name] = rendered
 			}
 		}
 	}
@@ -418,34 +359,12 @@ func createConfigValues(applicationName string, config *kotsv1beta1.Config, exis
 		ObjectMeta: metav1.ObjectMeta{
 			Name: applicationName,
 		},
-		Spec: newValues,
+		Spec: emptyValues,
 	}
 
 	return &configValues, nil
 }
 
-func findConfigValuesInFile(filename string) (*kotsv1beta1.ConfigValues, error) {
-	content, err := ioutil.ReadFile(filename)
-	if err != nil {
-		if os.IsNotExist(err) {
-			return nil, nil
-		}
-		return nil, errors.Wrap(err, "failed to open file")
-	}
-
-	decode := scheme.Codecs.UniversalDeserializer().Decode
-	obj, gvk, err := decode(content, nil, nil)
-	if err != nil {
-		return nil, nil
-	}
-
-	if gvk.Group == "kots.io" && gvk.Version == "v1beta1" && gvk.Kind == "ConfigValues" {
-		return obj.(*kotsv1beta1.ConfigValues), nil
-	}
-
-	return nil, nil
-}
-
 func findConfigInRelease(release *Release) *kotsv1beta1.Config {
 	kotsscheme.AddToScheme(scheme.Scheme)
 	for _, content := range release.Manifests {
diff --git a/pkg/util/util.go b/pkg/util/util.go
index 57d24c019..c926365b9 100644
--- a/pkg/util/util.go
+++ b/pkg/util/util.go
@@ -54,3 +54,9 @@ func SplitStringOnLen(str string, maxLength int) ([]string, error) {
 
 	return result, nil
 }
+
+func IntPointer(x int) *int64 {
+	var xout int64
+	xout = int64(x)
+	return &xout
+}


Test patch:
diff --git a/pkg/upstream/replicated_test.go b/pkg/upstream/replicated_test.go
index 32c166e8e..e1a806209 100644
--- a/pkg/upstream/replicated_test.go
+++ b/pkg/upstream/replicated_test.go
@@ -4,10 +4,8 @@ import (
 	"net/url"
 	"testing"
 
-	kotsv1beta1 "github.com/replicatedhq/kots/kotskinds/apis/kots/v1beta1"
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
-	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 )
 
 var (
@@ -149,117 +147,3 @@ func Test_releaseToFiles(t *testing.T) {
 		})
 	}
 }
-
-func Test_createConfigValues(t *testing.T) {
-	applicationName := "Test App"
-
-	config := &kotsv1beta1.Config{
-		TypeMeta: metav1.TypeMeta{
-			APIVersion: "kots.io/v1beta1",
-			Kind:       "Config",
-		},
-		ObjectMeta: metav1.ObjectMeta{
-			Name: applicationName,
-		},
-		Spec: kotsv1beta1.ConfigSpec{
-			Groups: []kotsv1beta1.ConfigGroup{
-				kotsv1beta1.ConfigGroup{
-					Name:  "group_name",
-					Title: "Group Title",
-					Items: []kotsv1beta1.ConfigItem{
-						// should replace default
-						kotsv1beta1.ConfigItem{
-							Name:    "1_with_default",
-							Type:    "string",
-							Default: "default_1_new",
-							Value:   "",
-						},
-						// should preserve value and add default
-						kotsv1beta1.ConfigItem{
-							Name:    "2_with_value",
-							Type:    "string",
-							Default: "default_2",
-							Value:   "value_2_new",
-						},
-						// should add a new item
-						kotsv1beta1.ConfigItem{
-							Name:    "4_with_default",
-							Type:    "string",
-							Default: "default_4",
-						},
-					},
-				},
-			},
-		},
-	}
-
-	configValues := &kotsv1beta1.ConfigValues{
-		TypeMeta: metav1.TypeMeta{
-			APIVersion: "kots.io/v1beta1",
-			Kind:       "ConfigValues",
-		},
-		ObjectMeta: metav1.ObjectMeta{
-			Name: applicationName,
-		},
-		Spec: kotsv1beta1.ConfigValuesSpec{
-			Values: map[string]kotsv1beta1.ConfigValue{
-				"1_with_default": kotsv1beta1.ConfigValue{
-					Default: "default_1",
-				},
-				"2_with_value": kotsv1beta1.ConfigValue{
-					Value: "value_2",
-				},
-				"3_with_both": kotsv1beta1.ConfigValue{
-					Value:   "value_3",
-					Default: "default_3",
-				},
-			},
-		},
-	}
-
-	req := require.New(t)
-
-	// like new install, should match config
-	expected1 := map[string]kotsv1beta1.ConfigValue{
-		"1_with_default": kotsv1beta1.ConfigValue{
-			Default: "default_1_new",
-		},
-		"2_with_value": kotsv1beta1.ConfigValue{
-			Value:   "value_2_new",
-			Default: "default_2",
-		},
-		"4_with_default": kotsv1beta1.ConfigValue{
-			Default: "default_4",
-		},
-	}
-	values1, err := createConfigValues(applicationName, config, nil)
-	req.NoError(err)
-	assert.Equal(t, expected1, values1.Spec.Values)
-
-	// Like an app without a config, should have exact same values
-	expected2 := configValues.Spec.Values
-	values2, err := createConfigValues(applicationName, nil, configValues)
-	req.NoError(err)
-	assert.Equal(t, expected2, values2.Spec.Values)
-
-	// updating existing values with new config, should do a merge
-	expected3 := map[string]kotsv1beta1.ConfigValue{
-		"1_with_default": kotsv1beta1.ConfigValue{
-			Default: "default_1_new",
-		},
-		"2_with_value": kotsv1beta1.ConfigValue{
-			Value:   "value_2",
-			Default: "default_2",
-		},
-		"3_with_both": kotsv1beta1.ConfigValue{
-			Value:   "value_3",
-			Default: "default_3",
-		},
-		"4_with_default": kotsv1beta1.ConfigValue{
-			Default: "default_4",
-		},
-	}
-	values3, err := createConfigValues(applicationName, config, configValues)
-	req.NoError(err)
-	assert.Equal(t, expected3, values3.Spec.Values)
-}
diff --git a/pkg/util/util_test.go b/pkg/util/util_test.go
index a781b3d2c..f271adb19 100644
--- a/pkg/util/util_test.go
+++ b/pkg/util/util_test.go
@@ -80,3 +80,39 @@ func Test_SplitStringOnLen(t *testing.T) {
 		})
 	}
 }
+
+func TestIntPointer(t *testing.T) {
+	tests := []struct {
+		name string
+		x    int
+		want int64
+	}{
+		{
+			name: "zero",
+			x:    0,
+			want: int64(0),
+		},
+		{
+			name: "positive",
+			x:    100,
+			want: int64(100),
+		},
+		{
+			name: "negative",
+			x:    -128,
+			want: int64(-128),
+		},
+		{
+			name: "int max",
+			x:    1<<31 - 1,
+			want: int64(1<<31 - 1),
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			req := require.New(t)
+			got := IntPointer(tt.x)
+			req.Equal(tt.want, *got)
+		})
+	}
+}