diff --git a/actions/component_rm.go b/actions/component_rm.go
new file mode 100644
index 0000000000000000000000000000000000000000..985ad96fd934dad400eb3d1409a3af2e0e556954
--- /dev/null
+++ b/actions/component_rm.go
@@ -0,0 +1,62 @@
+// Copyright 2018 The ksonnet authors
+//
+//
+//    Licensed under the Apache License, Version 2.0 (the "License");
+//    you may not use this file except in compliance with the License.
+//    You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+//    Unless required by applicable law or agreed to in writing, software
+//    distributed under the License is distributed on an "AS IS" BASIS,
+//    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+//    See the License for the specific language governing permissions and
+//    limitations under the License.
+
+package actions
+
+import (
+	"github.com/ksonnet/ksonnet/component"
+	"github.com/ksonnet/ksonnet/metadata/app"
+)
+
+// RunComponentRm runs `component list`
+func RunComponentRm(m map[string]interface{}) error {
+	cr, err := NewComponentRm(m)
+	if err != nil {
+		return err
+	}
+
+	return cr.Run()
+}
+
+// ComponentRm create a list of components in a namespace.
+type ComponentRm struct {
+	app  app.App
+	name string
+
+	componentDeleteFn func(app.App, string) error
+}
+
+// NewComponentRm creates an instance of ComponentRm.
+func NewComponentRm(m map[string]interface{}) (*ComponentRm, error) {
+	ol := newOptionLoader(m)
+
+	cr := &ComponentRm{
+		app:  ol.loadApp(),
+		name: ol.loadString(OptionComponentName),
+
+		componentDeleteFn: component.Delete,
+	}
+
+	if ol.err != nil {
+		return nil, ol.err
+	}
+
+	return cr, nil
+}
+
+// Run runs the ComponentRm action.
+func (cr *ComponentRm) Run() error {
+	return cr.componentDeleteFn(cr.app, cr.name)
+}
diff --git a/actions/component_rm_test.go b/actions/component_rm_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..7a5b1379c5001f484ef16deaf01b5c87d0a98ef4
--- /dev/null
+++ b/actions/component_rm_test.go
@@ -0,0 +1,54 @@
+// Copyright 2018 The ksonnet authors
+//
+//
+//    Licensed under the Apache License, Version 2.0 (the "License");
+//    you may not use this file except in compliance with the License.
+//    You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+//    Unless required by applicable law or agreed to in writing, software
+//    distributed under the License is distributed on an "AS IS" BASIS,
+//    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+//    See the License for the specific language governing permissions and
+//    limitations under the License.
+
+package actions
+
+import (
+	"testing"
+
+	"github.com/ksonnet/ksonnet/metadata/app"
+	amocks "github.com/ksonnet/ksonnet/metadata/app/mocks"
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
+)
+
+func TestComponentRm(t *testing.T) {
+	withApp(t, func(appMock *amocks.App) {
+		name := "component-name"
+
+		var didDelete bool
+
+		deleteFn := func(a app.App, componentName string) error {
+			assert.Equal(t, componentName, name)
+			didDelete = true
+			return nil
+		}
+
+		in := map[string]interface{}{
+			OptionApp:           appMock,
+			OptionComponentName: name,
+		}
+
+		a, err := NewComponentRm(in)
+		require.NoError(t, err)
+
+		a.componentDeleteFn = deleteFn
+
+		err = a.Run()
+		require.NoError(t, err)
+
+		assert.True(t, didDelete)
+	})
+}
diff --git a/actions/env_add.go b/actions/env_add.go
index 0e6ee3146a91e7a8dbea19dd74e1ba1ca1b7cc03..d5269c9f75929547b0e8a50f1316b9fd1b302ad9 100644
--- a/actions/env_add.go
+++ b/actions/env_add.go
@@ -16,14 +16,8 @@
 package actions
 
 import (
-	"github.com/ksonnet/ksonnet/env"
 	"github.com/ksonnet/ksonnet/metadata/app"
-)
-
-const (
-	baseLibsonnetFile = "base.libsonnet"
-	componentsDir     = "components"
-	paramsFileName    = "params.libsonnet"
+	"github.com/ksonnet/ksonnet/pkg/env"
 )
 
 // RunEnvAdd runs `env add`
@@ -79,8 +73,8 @@ func (ea *EnvAdd) Run() error {
 		destination,
 		ea.envName,
 		ea.k8sSpecFlag,
-		env.DefaultOverrideData(),
-		env.DefaultParamsData(),
+		env.DefaultOverrideData,
+		env.DefaultParamsData,
 		ea.isOverride,
 	)
 }
diff --git a/actions/env_add_test.go b/actions/env_add_test.go
index f5e27512db6a62a743975a4e7f0190d7bbae9679..48fbb1e85efcfe5e121106f8c53f75bf557265e2 100644
--- a/actions/env_add_test.go
+++ b/actions/env_add_test.go
@@ -18,9 +18,9 @@ package actions
 import (
 	"testing"
 
-	"github.com/ksonnet/ksonnet/env"
 	"github.com/ksonnet/ksonnet/metadata/app"
 	amocks "github.com/ksonnet/ksonnet/metadata/app/mocks"
+	"github.com/ksonnet/ksonnet/pkg/env"
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
 )
diff --git a/actions/env_rm.go b/actions/env_rm.go
index d606aff430afb972db3fda14163b2a6957cd0890..95e1e1573aace6fe871c980a8d7fda2abb3ec872 100644
--- a/actions/env_rm.go
+++ b/actions/env_rm.go
@@ -16,8 +16,8 @@
 package actions
 
 import (
-	"github.com/ksonnet/ksonnet/env"
 	"github.com/ksonnet/ksonnet/metadata/app"
+	"github.com/ksonnet/ksonnet/pkg/env"
 )
 
 // RunEnvRm runs `env rm`
diff --git a/actions/env_set.go b/actions/env_set.go
index 1eb968cacd571e0bbfb2f0e165a9ccfd8917cb53..e18e1737b07a75ae938cd2acd3eee84dd6ad2d31 100644
--- a/actions/env_set.go
+++ b/actions/env_set.go
@@ -16,8 +16,8 @@
 package actions
 
 import (
-	"github.com/ksonnet/ksonnet/env"
 	"github.com/ksonnet/ksonnet/metadata/app"
+	"github.com/ksonnet/ksonnet/pkg/env"
 )
 
 // EnvSetNamespace is an option for setting a new namespace name.
diff --git a/actions/param_set.go b/actions/param_set.go
index 8415c3463ddeade3fbfa056b326a87b5b43641fb..2db3d16c830c041647fa8df3d7a673ece2fe7707 100644
--- a/actions/param_set.go
+++ b/actions/param_set.go
@@ -20,9 +20,9 @@ import (
 	"strings"
 
 	"github.com/ksonnet/ksonnet/component"
-	"github.com/ksonnet/ksonnet/env"
 	"github.com/ksonnet/ksonnet/metadata/app"
 	mp "github.com/ksonnet/ksonnet/metadata/params"
+	"github.com/ksonnet/ksonnet/pkg/env"
 	"github.com/ksonnet/ksonnet/pkg/params"
 	"github.com/pkg/errors"
 )
diff --git a/cmd/actions.go b/cmd/actions.go
index 699469b8f6ce17098ceae88e9a2e376711418680..9b185e76f8ea1a97fc5f858c9380926d0ed17e72 100644
--- a/cmd/actions.go
+++ b/cmd/actions.go
@@ -25,6 +25,7 @@ type initName int
 const (
 	actionApply initName = iota
 	actionComponentList
+	actionComponentRm
 	actionDelete
 	actionDiff
 	actionEnvAdd
@@ -62,6 +63,7 @@ var (
 	actionFns = map[initName]actionFn{
 		actionApply:         actions.RunApply,
 		actionComponentList: actions.RunComponentList,
+		actionComponentRm:   actions.RunComponentRm,
 		// actionDelete
 		// actionDiff
 		actionEnvAdd:      actions.RunEnvAdd,
@@ -102,10 +104,3 @@ func runAction(name initName, args map[string]interface{}) error {
 
 	return fn(args)
 }
-
-var (
-	actionMap = map[initName]interface{}{
-		actionInit:     actions.RunInit,
-		actionValidate: actions.RunValidate,
-	}
-)
diff --git a/cmd/apply.go b/cmd/apply.go
index 5bf8b6f054e9f8cc7922a1657f9a5270b55a4c61..cb38bffceb5b37b9be8553a86e8fda0044e4926a 100644
--- a/cmd/apply.go
+++ b/cmd/apply.go
@@ -32,22 +32,6 @@ var (
 )
 
 const (
-
-	// AnnotationGcTag annotation that triggers
-	// garbage collection. Objects with value equal to
-	// command-line flag that are *not* in config will be deleted.
-	AnnotationGcTag = "kubecfg.ksonnet.io/garbage-collect-tag"
-
-	// AnnotationGcStrategy controls gc logic.  Current values:
-	// `auto` (default if absent) - do garbage collection
-	// `ignore` - never garbage collect this object
-	AnnotationGcStrategy = "kubecfg.ksonnet.io/garbage-collect-strategy"
-
-	// GcStrategyAuto is the default automatic gc logic
-	GcStrategyAuto = "auto"
-	// GcStrategyIgnore means this object should be ignored by garbage collection
-	GcStrategyIgnore = "ignore"
-
 	applyShortDesc = "Apply local Kubernetes manifests (components) to remote clusters"
 )
 
diff --git a/cmd/component_rm.go b/cmd/component_rm.go
index c9ea023d852727e4da0e2c7790450eb8c4b3471c..6fd52cea043a641adcfccaa14436916bb43bc3c4 100644
--- a/cmd/component_rm.go
+++ b/cmd/component_rm.go
@@ -18,7 +18,7 @@ package cmd
 import (
 	"fmt"
 
-	"github.com/ksonnet/ksonnet/pkg/kubecfg"
+	"github.com/ksonnet/ksonnet/actions"
 	"github.com/spf13/cobra"
 )
 
@@ -30,11 +30,12 @@ var componentRmCmd = &cobra.Command{
 			return fmt.Errorf("'component rm' takes a single argument, that is the name of the component")
 		}
 
-		component := args[0]
+		m := map[string]interface{}{
+			actions.OptionApp:           ka,
+			actions.OptionComponentName: args[0],
+		}
 
-		// TODO: move this to actions
-		c := kubecfg.NewComponentRmCmd(component)
-		return c.Run()
+		return runAction(actionComponentRm, m)
 	},
 	Long: `Delete a component from the ksonnet application. This is equivalent to deleting the
 component file in the components directory and cleaning up all component
diff --git a/cmd/component_rm_test.go b/cmd/component_rm_test.go
index cc5bd9f5d22c2c9bc945f142d42997bc861444ec..ac2a5a550d9631e22ee9cc84961e7ac8483cb37e 100644
--- a/cmd/component_rm_test.go
+++ b/cmd/component_rm_test.go
@@ -17,22 +17,22 @@ package cmd
 
 import (
 	"testing"
+
+	"github.com/ksonnet/ksonnet/actions"
 )
 
 func Test_componentRmCmd(t *testing.T) {
+	cases := []cmdTestCase{
+		{
+			name:   "in general",
+			args:   []string{"component", "rm", "name"},
+			action: actionComponentRm,
+			expected: map[string]interface{}{
+				actions.OptionApp:           ka,
+				actions.OptionComponentName: "name",
+			},
+		},
+	}
 
-	// TODO: re-enable when component rm is an action
-	// cases := []cmdTestCase{
-	// 	{
-	// 		name:   "in general",
-	// 		args:   []string{"component", "rm", "name"},
-	// 		action: actionComponentList,
-	// 		expected: map[string]interface{}{
-	// 			actions.OptionApp:           ka,
-	// 			actions.OptionComponentName: "name",
-	// 		},
-	// 	},
-	// }
-
-	// runTestCmd(t, cases)
+	runTestCmd(t, cases)
 }
diff --git a/cmd/helpers_test.go b/cmd/helpers_test.go
index ecef0ff8f3a349b6d563fce083da45472a0905c4..bc624a9d8108f39ffcf105145a53e18027caa6a0 100644
--- a/cmd/helpers_test.go
+++ b/cmd/helpers_test.go
@@ -48,20 +48,11 @@ type cmdTestCase struct {
 	expected map[string]interface{}
 }
 
-func stubCmdOverride() (*map[string]interface{}, func(map[string]interface{}) error) {
-	var got *map[string]interface{}
-
-	return got, func(m map[string]interface{}) error {
-		got = &m
-		return nil
-	}
-}
-
-type stubCmdOverride2 struct {
+type stubCmdOverride struct {
 	got map[string]interface{}
 }
 
-func (s *stubCmdOverride2) override(m map[string]interface{}) error {
+func (s *stubCmdOverride) override(m map[string]interface{}) error {
 	s.got = m
 	return nil
 }
@@ -69,7 +60,7 @@ func (s *stubCmdOverride2) override(m map[string]interface{}) error {
 func runTestCmd(t *testing.T, cases []cmdTestCase) {
 	for _, tc := range cases {
 		t.Run(tc.name, func(t *testing.T) {
-			s := stubCmdOverride2{}
+			s := stubCmdOverride{}
 
 			withCmd(tc.action, s.override, func() {
 
diff --git a/cmd/root.go b/cmd/root.go
index 65bfc192b4a2846c090e97c2b4f12c8d12d87485..b38a0a00f6059b7173b5353dfa38afb79d03c1da 100644
--- a/cmd/root.go
+++ b/cmd/root.go
@@ -28,9 +28,9 @@ import (
 
 	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
 
-	"github.com/ksonnet/ksonnet/env"
 	"github.com/ksonnet/ksonnet/metadata"
 	"github.com/ksonnet/ksonnet/metadata/app"
+	"github.com/ksonnet/ksonnet/pkg/env"
 	"github.com/ksonnet/ksonnet/pkg/pipeline"
 	"github.com/ksonnet/ksonnet/plugin"
 	str "github.com/ksonnet/ksonnet/strings"
@@ -175,15 +175,6 @@ func runPlugin(p plugin.Plugin, args []string) error {
 	return cmd.Run()
 }
 
-func ksApp() (app.App, error) {
-	cwd, err := os.Getwd()
-	if err != nil {
-		return nil, err
-	}
-
-	return app.Load(appFs, cwd)
-}
-
 func logLevel(verbosity int) log.Level {
 	switch verbosity {
 	case 0:
diff --git a/component/component.go b/component/component.go
index 6135c7118d9c8bfbda2a72cbab418f6ef65f9e14..1dd3796fe87c29d22a2648e62287f4d5ebafa2ee 100644
--- a/component/component.go
+++ b/component/component.go
@@ -149,32 +149,6 @@ func isComponentDir(fs afero.Fs, path string) (bool, error) {
 	return false, nil
 }
 
-// MakePathsByNamespace creates a map of component paths categorized by namespace.
-func MakePathsByNamespace(a app.App, env string) (map[Namespace][]string, error) {
-	paths, err := MakePaths(a, env)
-	if err != nil {
-		return nil, err
-	}
-
-	m := make(map[Namespace][]string)
-
-	for i := range paths {
-		prefix := a.Root() + "/components/"
-		if strings.HasSuffix(a.Root(), "/") {
-			prefix = a.Root() + "components/"
-		}
-		path := strings.TrimPrefix(paths[i], prefix)
-		ns, _ := ExtractNamespacedComponent(a, path)
-		if _, ok := m[ns]; !ok {
-			m[ns] = make([]string, 0)
-		}
-
-		m[ns] = append(m[ns], paths[i])
-	}
-
-	return m, nil
-}
-
 // MakePaths creates a slice of component paths
 func MakePaths(a app.App, env string) ([]string, error) {
 	cpl, err := newComponentPathLocator(a, env)
diff --git a/component/delete.go b/component/delete.go
new file mode 100644
index 0000000000000000000000000000000000000000..d858a715a962bc1c46afab8a2eeb94dfa097a301
--- /dev/null
+++ b/component/delete.go
@@ -0,0 +1,122 @@
+// Copyright 2018 The ksonnet authors
+//
+//
+//    Licensed under the Apache License, Version 2.0 (the "License");
+//    you may not use this file except in compliance with the License.
+//    You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+//    Unless required by applicable law or agreed to in writing, software
+//    distributed under the License is distributed on an "AS IS" BASIS,
+//    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+//    See the License for the specific language governing permissions and
+//    limitations under the License.
+
+package component
+
+import (
+	"path/filepath"
+
+	"github.com/ksonnet/ksonnet/metadata/app"
+	param "github.com/ksonnet/ksonnet/metadata/params"
+	"github.com/pkg/errors"
+	log "github.com/sirupsen/logrus"
+	"github.com/spf13/afero"
+)
+
+// Delete deletes the component file and all references.
+// Write operations will happen at the end to minimal-ize failures that leave
+// the directory structure in a half-finished state.
+func Delete(a app.App, name string) error {
+	log.Debugf("deleting component %s", name)
+	componentPath, err := Path(a, name)
+	if err != nil {
+		return err
+	}
+
+	ns, _ := ExtractNamespacedComponent(a, name)
+
+	// Build the new component/params.libsonnet file.
+	componentParamsFile, err := afero.ReadFile(a.Fs(), ns.ParamsPath())
+	if err != nil {
+		return err
+	}
+	componentJsonnet, err := param.DeleteComponent(name, string(componentParamsFile))
+	if err != nil {
+		return err
+	}
+
+	// Build the new environment/<env>/params.libsonnet files.
+	// environment name -> jsonnet
+	envParams := make(map[string]string)
+	envs, err := a.Environments()
+	if err != nil {
+		return err
+	}
+	for envName, env := range envs {
+		var updated string
+		updated, err = collectEnvParams(a, env, name, envName)
+		if err != nil {
+			return err
+		}
+
+		envParams[envName] = updated
+	}
+
+	//
+	// Delete the component references.
+	//
+	log.Infof("Removing component parameter references ...")
+
+	// Remove the references in component/params.libsonnet.
+	log.Debugf("... deleting references in %s", ns.ParamsPath())
+	err = afero.WriteFile(a.Fs(), ns.ParamsPath(), []byte(componentJsonnet), defaultFilePermissions)
+	if err != nil {
+		return err
+	}
+
+	if err = updateEnvParam(a, envs, envParams); err != nil {
+		return errors.Wrap(err, "writing environment params")
+	}
+
+	//
+	// Delete the component file in components/.
+	//
+	log.Infof("Deleting component '%s' at path '%s'", name, componentPath)
+	if err := a.Fs().Remove(componentPath); err != nil {
+		return err
+	}
+
+	// TODO: Remove,
+	// references in main.jsonnet.
+	// component references in other component files (feature does not yet exist).
+	log.Infof("Successfully deleted component '%s'", name)
+	return nil
+}
+
+// collectEnvParams collects environment params in
+func collectEnvParams(a app.App, env *app.EnvironmentSpec, componentName, envName string) (string, error) {
+	log.Debugf("collecting params for environment %s", envName)
+	path := filepath.Join(a.Root(), "environments", envName, "params.libsonnet")
+	var envParamsFile []byte
+	envParamsFile, err := afero.ReadFile(a.Fs(), path)
+	if err != nil {
+		return "", err
+	}
+	return param.DeleteEnvironmentComponent(componentName, string(envParamsFile))
+}
+
+/// updateEnvParam removes the component references in each environment's
+// paramss.libsonnet.
+func updateEnvParam(a app.App, envs app.EnvironmentSpecs, envParams map[string]string) error {
+	for envName := range envs {
+		path := filepath.Join(a.Root(), "environments", envName, "params.libsonnet")
+		log.Debugf("... deleting references in %s", path)
+		if err := afero.WriteFile(a.Fs(), path, []byte(envParams[envName]), app.DefaultFilePermissions); err != nil {
+			return errors.Wrapf(err, "writing params for environment %q", envName)
+		}
+	}
+
+	return nil
+}
diff --git a/env/env_test.go b/component/delete_test.go
similarity index 51%
rename from env/env_test.go
rename to component/delete_test.go
index d0b165881d72400726e3d0684b0c0f2194d8a43f..a16f70d38d4fbda9d94043d5b3b035a90cf1ab04 100644
--- a/env/env_test.go
+++ b/component/delete_test.go
@@ -1,4 +1,4 @@
-// Copyright 2018 The kubecfg authors
+// Copyright 2018 The ksonnet authors
 //
 //
 //    Licensed under the Apache License, Version 2.0 (the "License");
@@ -13,42 +13,43 @@
 //    See the License for the specific language governing permissions and
 //    limitations under the License.
 
-package env
+package component
 
 import (
+	"path/filepath"
 	"testing"
 
 	"github.com/ksonnet/ksonnet/metadata/app"
 	"github.com/ksonnet/ksonnet/metadata/app/mocks"
+	"github.com/ksonnet/ksonnet/pkg/util/test"
 	"github.com/spf13/afero"
 	"github.com/stretchr/testify/require"
 )
 
-func TestList(t *testing.T) {
-	withEnv(t, func(appMock *mocks.App, fs afero.Fs) {
-		specEnvs := app.EnvironmentSpecs{
-			"default": &app.EnvironmentSpec{
-				Path: "default",
-				Destination: &app.EnvironmentDestinationSpec{
-					Namespace: "default",
-					Server:    "http://example.com",
-				},
-				KubernetesVersion: "v1.8.7",
-			},
+func TestDelete(t *testing.T) {
+	test.WithApp(t, "/app", func(a *mocks.App, fs afero.Fs) {
+		test.StageDir(t, fs, "delete", "/app")
+
+		envs := app.EnvironmentSpecs{
+			"default": &app.EnvironmentSpec{},
 		}
-		appMock.On("Environments").Return(specEnvs, nil)
+		a.On("Environments").Return(envs, nil)
 
-		envs, err := List(appMock)
+		err := Delete(a, "guestbook-ui")
 		require.NoError(t, err)
 
-		expected := map[string]Env{
-			"default": Env{
-				KubernetesVersion: "v1.8.7",
-				Name:              "default",
-				Destination:       NewDestination("http://example.com", "default"),
-			},
-		}
-
-		require.Equal(t, expected, envs)
+		test.AssertNotExists(t, fs, filepath.Join("/app", "components", "guestbook-ui.jsonnet"))
+		test.AssertContents(
+			t,
+			fs,
+			"delete-params.libsonnet",
+			filepath.Join("/app", "components", "params.libsonnet"),
+		)
+		test.AssertContents(
+			t,
+			fs,
+			"delete-env-params.libsonnet",
+			filepath.Join("/app", "environments", "default", "params.libsonnet"),
+		)
 	})
 }
diff --git a/component/testdata/delete-env-params.libsonnet b/component/testdata/delete-env-params.libsonnet
new file mode 100644
index 0000000000000000000000000000000000000000..796f6ac9a45714184b3a8d76202e6b85a4d2f5fa
--- /dev/null
+++ b/component/testdata/delete-env-params.libsonnet
@@ -0,0 +1,5 @@
+local params = import "../../components/params.libsonnet";
+params {
+  components +: {
+  },
+}
diff --git a/component/testdata/delete-params.libsonnet b/component/testdata/delete-params.libsonnet
new file mode 100644
index 0000000000000000000000000000000000000000..4fe2a83791f1bcad9e4fb92922d9725b60eaa0f7
--- /dev/null
+++ b/component/testdata/delete-params.libsonnet
@@ -0,0 +1,10 @@
+{
+  global: {
+    // User-defined global parameters; accessible to all component and environments, Ex:
+    // replicas: 4,
+  },
+  components: {
+    // Component-level parameters, defined initially from 'ks prototype use ...'
+    // Each object below should correspond to a component in the components/ directory
+  },
+}
diff --git a/component/testdata/delete/components/guestbook-ui.jsonnet b/component/testdata/delete/components/guestbook-ui.jsonnet
new file mode 100644
index 0000000000000000000000000000000000000000..5e1c81978e3970a3fdbfcfb48719bc5940d10e34
--- /dev/null
+++ b/component/testdata/delete/components/guestbook-ui.jsonnet
@@ -0,0 +1,29 @@
+local env = std.extVar("__ksonnet/environments");
+local params = std.extVar("__ksonnet/params").components["guestbook-ui"];
+local k = import "k.libsonnet";
+local deployment = k.apps.v1beta1.deployment;
+local container = k.apps.v1beta1.deployment.mixin.spec.template.spec.containersType;
+local containerPort = container.portsType;
+local service = k.core.v1.service;
+local servicePort = k.core.v1.service.mixin.spec.portsType;
+
+local targetPort = params.containerPort;
+local labels = {app: params.name};
+
+local appService = service
+  .new(
+    params.name,
+    labels,
+    servicePort.new(params.servicePort, targetPort))
+  .withType(params.type);
+
+local appDeployment = deployment
+  .new(
+    params.name,
+    params.replicas,
+    container
+      .new(params.name, params.image)
+      .withPorts(containerPort.new(targetPort)),
+    labels);
+
+k.core.v1.list.new([appService, appDeployment])
\ No newline at end of file
diff --git a/component/testdata/delete/components/params.libsonnet b/component/testdata/delete/components/params.libsonnet
new file mode 100644
index 0000000000000000000000000000000000000000..4fe74232ef21fb682fa334856eba5ae4116aed91
--- /dev/null
+++ b/component/testdata/delete/components/params.libsonnet
@@ -0,0 +1,19 @@
+{
+  global: {
+    // User-defined global parameters; accessible to all component and environments, Ex:
+    // replicas: 4,
+  },
+  components: {
+    // Component-level parameters, defined initially from 'ks prototype use ...'
+    // Each object below should correspond to a component in the components/ directory
+    "guestbook-ui": {
+      containerPort: 80,
+      image: "gcr.io/heptio-images/ks-guestbook-demo:0.1",
+      name: "guiroot",
+      replicas: 1,
+      servicePort: 80,
+      type: "ClusterIP",
+      obj: {a: "b"},
+    },
+  },
+}
diff --git a/component/testdata/delete/environments/default/params.libsonnet b/component/testdata/delete/environments/default/params.libsonnet
new file mode 100644
index 0000000000000000000000000000000000000000..bc6b4dc093afc1d41c9006f1170cfa0fcce8c9b4
--- /dev/null
+++ b/component/testdata/delete/environments/default/params.libsonnet
@@ -0,0 +1,8 @@
+local params = import "../../components/params.libsonnet";
+params {
+  components +: {
+    "guestbook-ui" +: {
+       name: "guestbook-dev",
+    },
+  },
+}
diff --git a/e2e/component_test.go b/e2e/component_test.go
index c183cc76e1ee37e4ae91f1e89c5a6ae77ba63553..0281b33e1c80584d9039d9a8b980d78a052fcef7 100644
--- a/e2e/component_test.go
+++ b/e2e/component_test.go
@@ -55,7 +55,7 @@ var _ = Describe("ks component", func() {
 
 	Describe("rm", func() {
 		It("removes a component", func() {
-			o := a.runKs("component", "rm", "guestbook-ui")
+			o := a.runKs("component", "rm", "guestbook-ui", "-v")
 			assertExitStatus(o, 0)
 
 			o = a.componentList()
diff --git a/env/env.go b/env/env.go
deleted file mode 100644
index 6a168e45de7f295cd5bd5ae3961a1838ef03eaac..0000000000000000000000000000000000000000
--- a/env/env.go
+++ /dev/null
@@ -1,77 +0,0 @@
-// Copyright 2018 The kubecfg authors
-//
-//
-//    Licensed under the Apache License, Version 2.0 (the "License");
-//    you may not use this file except in compliance with the License.
-//    You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-//    Unless required by applicable law or agreed to in writing, software
-//    distributed under the License is distributed on an "AS IS" BASIS,
-//    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-//    See the License for the specific language governing permissions and
-//    limitations under the License.
-
-package env
-
-import (
-	"github.com/ksonnet/ksonnet/metadata/app"
-)
-
-const (
-	// primary environment files.
-	envFileName    = "main.jsonnet"
-	paramsFileName = "params.libsonnet"
-
-	// envRoot is the name for the environment root.
-	envRoot = "environments"
-)
-
-// Env represents a ksonnet environment.
-type Env struct {
-	// Name is the environment name.
-	Name string
-	// KubernetesVersion is the version of Kubernetes for this environment.
-	KubernetesVersion string
-	// Destination is the cluster destination for this environment.
-	Destination Destination
-	// Targets are the component namespaces that will be installed.
-	Targets []string
-}
-
-func envFromSpec(name string, envSpec *app.EnvironmentSpec) *Env {
-	return &Env{
-		Name:              name,
-		KubernetesVersion: envSpec.KubernetesVersion,
-		Destination:       NewDestination(envSpec.Destination.Server, envSpec.Destination.Namespace),
-		Targets:           envSpec.Targets,
-	}
-}
-
-// List lists all environments for the current ksonnet application.
-func List(ksApp app.App) (map[string]Env, error) {
-	envs := make(map[string]Env)
-
-	specs, err := ksApp.Environments()
-	if err != nil {
-		return nil, err
-	}
-
-	for name, spec := range specs {
-		env := envFromSpec(name, spec)
-		envs[name] = *env
-	}
-
-	return envs, nil
-}
-
-// Retrieve retrieves an environment by name.
-func Retrieve(ksApp app.App, name string) (*Env, error) {
-	envSpec, err := ksApp.Environment(name)
-	if err != nil {
-		return nil, err
-	}
-
-	return envFromSpec(name, envSpec), nil
-}
diff --git a/metadata/component.go b/metadata/component.go
index 962e7c6265e43c5eb6d2ee8b5441de13257bb57a..de41c5b89e115ee08a99072bb0985bf37343636b 100644
--- a/metadata/component.go
+++ b/metadata/component.go
@@ -20,6 +20,7 @@ import (
 	"path"
 
 	"github.com/ksonnet/ksonnet/component"
+	"github.com/ksonnet/ksonnet/metadata/app"
 	param "github.com/ksonnet/ksonnet/metadata/params"
 	"github.com/ksonnet/ksonnet/prototype"
 	str "github.com/ksonnet/ksonnet/strings"
@@ -115,7 +116,7 @@ func (m *manager) DeleteComponent(name string) error {
 	// Build the new environment/<env>/params.libsonnet files.
 	// environment name -> jsonnet
 	envJsonnets := make(map[string]string)
-	envs, err := m.GetEnvironments()
+	envs, err := ksApp.Environments()
 	if err != nil {
 		return err
 	}
@@ -139,7 +140,7 @@ func (m *manager) DeleteComponent(name string) error {
 
 	// Remove the references in component/params.libsonnet.
 	log.Debugf("... deleting references in %s", m.componentParamsPath)
-	err = afero.WriteFile(m.appFS, ns.ParamsPath(), []byte(componentJsonnet), defaultFilePermissions)
+	err = afero.WriteFile(m.appFS, ns.ParamsPath(), []byte(componentJsonnet), app.DefaultFilePermissions)
 	if err != nil {
 		return err
 	}
@@ -148,7 +149,7 @@ func (m *manager) DeleteComponent(name string) error {
 	for _, env := range envs {
 		path := str.AppendToPath(m.environmentsPath, env.Name, paramsFileName)
 		log.Debugf("... deleting references in %s", path)
-		err = afero.WriteFile(m.appFS, path, []byte(envJsonnets[env.Name]), defaultFilePermissions)
+		err = afero.WriteFile(m.appFS, path, []byte(envJsonnets[env.Name]), app.DefaultFilePermissions)
 		if err != nil {
 			return err
 		}
@@ -234,5 +235,5 @@ func (m *manager) SetComponentParams(path string, params param.Params) error {
 		return err
 	}
 
-	return afero.WriteFile(m.appFS, paramsPath, []byte(jsonnet), defaultFilePermissions)
+	return afero.WriteFile(m.appFS, paramsPath, []byte(jsonnet), app.DefaultFilePermissions)
 }
diff --git a/metadata/environment.go b/metadata/environment.go
index 22b9bc872b08dabe707ec0c5c710164f74ca7f4c..12b39b06755fedcbcc6e94d37a56d790b0377c22 100644
--- a/metadata/environment.go
+++ b/metadata/environment.go
@@ -16,23 +16,13 @@
 package metadata
 
 import (
-	"bytes"
-	"fmt"
-	"runtime/debug"
-
-	"github.com/ksonnet/ksonnet/env"
-	"github.com/ksonnet/ksonnet/metadata/lib"
+	"github.com/ksonnet/ksonnet/pkg/env"
 	str "github.com/ksonnet/ksonnet/strings"
-	"github.com/pkg/errors"
-
-	log "github.com/sirupsen/logrus"
 
 	param "github.com/ksonnet/ksonnet/metadata/params"
 )
 
 const (
-	defaultEnvName = "default"
-
 	// primary environment files
 	envFileName    = "main.jsonnet"
 	paramsFileName = "params.libsonnet"
@@ -43,66 +33,15 @@ var (
 	envCreate = env.Create
 )
 
-func (m *manager) CreateEnvironment(name, server, namespace, k8sSpecFlag string) error {
-	debug.PrintStack()
-	return errors.Errorf("deprecated")
-	// a, err := m.App()
-	// if err != nil {
-	// 	return err
-	// }
-
-	// config := env.CreateConfig{
-	// 	App:         a,
-	// 	Destination: env.NewDestination(server, namespace),
-	// 	K8sSpecFlag: k8sSpecFlag,
-	// 	Name:        name,
-
-	// 	OverrideData: m.generateOverrideData(),
-	// 	ParamsData:   m.generateParamsData(),
-	// }
-
-	// return envCreate(config)
-
-}
-
-func (m *manager) DeleteEnvironment(name string) error {
-	a, err := m.App()
-	if err != nil {
-		return err
-	}
-
-	// TODO: move this to actions
-	return env.Delete(a, name, false)
-}
-
-func (m *manager) GetEnvironments() (map[string]env.Env, error) {
-	a, err := m.App()
-	if err != nil {
-		return nil, err
-	}
-
-	log.Debug("Retrieving all environments")
-	return env.List(a)
-}
-
-func (m *manager) GetEnvironment(name string) (*env.Env, error) {
-	a, err := m.App()
-	if err != nil {
-		return nil, err
-	}
-
-	return env.Retrieve(a, name)
-}
-
-func (m *manager) SetEnvironment(from, to string) error {
-	a, err := m.App()
-	if err != nil {
-		return err
-	}
+// func (m *manager) DeleteEnvironment(name string) error {
+// 	a, err := m.App()
+// 	if err != nil {
+// 		return err
+// 	}
 
-	// TODO: move this to an action
-	return env.Rename(a, from, to, false)
-}
+// 	// TODO: move this to actions
+// 	return env.Delete(a, name, false)
+// }
 
 func (m *manager) GetEnvironmentParams(name, nsName string) (map[string]param.Params, error) {
 	a, err := m.App()
@@ -155,32 +94,3 @@ func (m *manager) getLibPath(envName string) (string, error) {
 
 	return a.LibPath(envName)
 }
-
-func (m *manager) generateOverrideData() []byte {
-	var buf bytes.Buffer
-	buf.WriteString(fmt.Sprintf("local base = import \"%s\";\n", baseLibsonnetFile))
-	buf.WriteString(fmt.Sprintf("local k = import \"%s\";\n\n", lib.ExtensionsLibFilename))
-	buf.WriteString("base + {\n")
-	buf.WriteString("  // Insert user-specified overrides here. For example if a component is named \"nginx-deployment\", you might have something like:\n")
-	buf.WriteString("  //   \"nginx-deployment\"+: k.deployment.mixin.metadata.labels({foo: \"bar\"})\n")
-	buf.WriteString("}\n")
-	return buf.Bytes()
-}
-
-func (m *manager) generateParamsData() []byte {
-	const (
-		relComponentParamsPath = "../../" + componentsDir + "/" + paramsFileName
-	)
-
-	return []byte(`local params = import "` + relComponentParamsPath + `";
-params + {
-  components +: {
-    // Insert component parameter overrides here. Ex:
-    // guestbook +: {
-    //   name: "guestbook-dev",
-    //   replicas: params.global.replicas,
-    // },
-  },
-}
-`)
-}
diff --git a/metadata/interface.go b/metadata/interface.go
index 34ddf47089477d75c090df179dd9cd5aed7ae704..53d24c08fc525ec884569447dbab4e51020aca12 100644
--- a/metadata/interface.go
+++ b/metadata/interface.go
@@ -16,20 +16,14 @@
 package metadata
 
 import (
-	"os"
-
 	"github.com/ksonnet/ksonnet/component"
-	"github.com/ksonnet/ksonnet/env"
 	"github.com/ksonnet/ksonnet/metadata/app"
 	param "github.com/ksonnet/ksonnet/metadata/params"
-	"github.com/ksonnet/ksonnet/pkg/registry"
 	"github.com/ksonnet/ksonnet/prototype"
 	"github.com/spf13/afero"
 )
 
 var appFS afero.Fs
-var defaultFolderPermissions = os.FileMode(0755)
-var defaultFilePermissions = os.FileMode(0644)
 
 // Manager abstracts over a ksonnet application's metadata, allowing users to do
 // things like: create and delete environments; search for prototypes; vendor
@@ -59,12 +53,7 @@ type Manager interface {
 	SetEnvironmentParams(env, component string, params param.Params) error
 
 	// Environment API.
-	// CreateEnvironment(name, uri, namespace, spec string) error
-	DeleteEnvironment(name string) error
-	GetEnvironments() (map[string]env.Env, error)
-	GetEnvironment(name string) (*env.Env, error)
-	SetEnvironment(name, desiredName string) error
-	GetDestination(envName string) (env.Destination, error)
+	// DeleteEnvironment(name string) error
 }
 
 // Find will recursively search the current directory and its parents for a
@@ -74,27 +63,6 @@ func Find(path string) (Manager, error) {
 	return findManager(path, afero.NewOsFs())
 }
 
-// Init will generate the directory tree for a ksonnet project.
-func Init(name, rootPath string, k8sSpecFlag, serverURI, namespace *string) (Manager, error) {
-	// Generate `incubator` registry. We do this before before creating
-	// directory tree, in case the network call fails.
-	const (
-		defaultIncubatorRegName = "incubator"
-		defaultIncubatorURI     = "github.com/ksonnet/parts/tree/master/" + defaultIncubatorRegName
-	)
-
-	gh, err := registry.NewGitHub(&app.RegistryRefSpec{
-		Name:     "incubator",
-		Protocol: registry.ProtocolGitHub,
-		URI:      defaultIncubatorURI,
-	})
-	if err != nil {
-		return nil, err
-	}
-
-	return initManager(name, rootPath, k8sSpecFlag, serverURI, namespace, gh, appFS)
-}
-
 func init() {
 	appFS = afero.NewOsFs()
 }
diff --git a/metadata/manager.go b/metadata/manager.go
index bf5b888020123e2058e32491b9ab63fb0e58beca..84af2d3332e96f4593c4fea8b609214df1249067 100644
--- a/metadata/manager.go
+++ b/metadata/manager.go
@@ -21,13 +21,9 @@ import (
 	"path"
 	"path/filepath"
 
-	"github.com/ksonnet/ksonnet/component"
-	"github.com/ksonnet/ksonnet/env"
 	"github.com/ksonnet/ksonnet/metadata/app"
-	"github.com/ksonnet/ksonnet/pkg/registry"
 	str "github.com/ksonnet/ksonnet/strings"
 	"github.com/pkg/errors"
-	log "github.com/sirupsen/logrus"
 	"github.com/spf13/afero"
 )
 
@@ -115,55 +111,6 @@ func findManager(p string, appFS afero.Fs) (*manager, error) {
 	}
 }
 
-func initManager(name, rootPath string, k8sSpecFlag, serverURI, namespace *string, incubatorReg registry.Registry, appFS afero.Fs) (*manager, error) {
-	m, err := newManager(rootPath, appFS)
-	if err != nil {
-		return nil, errors.Wrap(err, "create manager")
-	}
-
-	// Retrieve `registry.yaml`.
-	registryYAMLData, err := generateRegistryYAMLData(incubatorReg)
-	if err != nil {
-		return nil, err
-	}
-
-	// Generate data for `app.yaml`.
-	appYAMLData, err := generateAppYAMLData(name, incubatorReg.MakeRegistryRefSpec())
-	if err != nil {
-		return nil, err
-	}
-
-	// Generate data for `base.libsonnet`.
-	baseLibData := genBaseLibsonnetContent()
-
-	// Initialize directory structure.
-	if err := m.createAppDirTree(name, appYAMLData, baseLibData, incubatorReg); err != nil {
-		return nil, err
-	}
-
-	// Initialize user dir structure.
-	if err := m.createUserDirTree(); err != nil {
-		return nil, errorOnCreateFailure(name, err)
-	}
-
-	// Initialize environment, and cache specification data.
-	if serverURI != nil {
-		err := m.CreateEnvironment(defaultEnvName, *serverURI, *namespace, *k8sSpecFlag)
-		if err != nil {
-			return nil, errorOnCreateFailure(name, err)
-		}
-	}
-
-	// Write out `incubator` registry spec.
-	registryPath := m.registryPath(incubatorReg)
-	err = afero.WriteFile(m.appFS, registryPath, registryYAMLData, defaultFilePermissions)
-	if err != nil {
-		return nil, errorOnCreateFailure(name, err)
-	}
-
-	return m, nil
-}
-
 func newManager(rootPath string, appFS afero.Fs) (*manager, error) {
 	usr, err := user.Current()
 	if err != nil {
@@ -206,126 +153,3 @@ func (m *manager) App() (app.App, error) {
 func (m *manager) LibPaths() (envPath, vendorPath string) {
 	return m.environmentsPath, m.vendorPath
 }
-
-func (m *manager) GetDestination(envName string) (env.Destination, error) {
-	appEnv, err := m.GetEnvironment(envName)
-	if err != nil {
-		return env.Destination{}, err
-	}
-
-	return appEnv.Destination, nil
-}
-
-func (m *manager) createUserDirTree() error {
-	dirPaths := []string{
-		m.userKsonnetRootPath,
-		m.pkgSrcCachePath,
-	}
-
-	for _, p := range dirPaths {
-		if err := m.appFS.MkdirAll(p, defaultFolderPermissions); err != nil {
-			return err
-		}
-	}
-
-	return nil
-}
-
-func (m *manager) createAppDirTree(name string, appYAMLData, baseLibData []byte, gh registry.Registry) error {
-	exists, err := afero.DirExists(m.appFS, m.rootPath)
-	if err != nil {
-		return fmt.Errorf("Could not check existence of directory '%s':\n%v", m.rootPath, err)
-	} else if exists {
-		return fmt.Errorf("Could not create app; directory '%s' already exists", m.rootPath)
-	}
-
-	dirPaths := []string{
-		m.rootPath,
-		m.ksonnetPath,
-		m.registriesPath,
-		m.libPath,
-		m.componentsPath,
-		m.environmentsPath,
-		m.vendorPath,
-		m.registryDir(gh),
-	}
-
-	for _, p := range dirPaths {
-		log.Debugf("Creating directory '%s'", p)
-		if err := m.appFS.MkdirAll(p, defaultFolderPermissions); err != nil {
-			return errorOnCreateFailure(name, err)
-		}
-	}
-
-	filePaths := []struct {
-		path    string
-		content []byte
-	}{
-		{
-			m.componentParamsPath,
-			component.GenParamsContent(),
-		},
-		{
-			m.baseLibsonnetPath,
-			genBaseLibsonnetContent(),
-		},
-		{
-			m.appYAMLPath,
-			appYAMLData,
-		},
-		{
-			m.baseLibsonnetPath,
-			baseLibData,
-		},
-	}
-
-	for _, f := range filePaths {
-		log.Debugf("Creating file '%s'", f.path)
-		if err := afero.WriteFile(m.appFS, f.path, f.content, defaultFilePermissions); err != nil {
-			return err
-		}
-	}
-
-	return nil
-}
-
-func generateRegistryYAMLData(incubatorReg registry.Registry) ([]byte, error) {
-	regSpec, err := incubatorReg.FetchRegistrySpec()
-	if err != nil {
-		return nil, err
-	}
-
-	return regSpec.Marshal()
-}
-
-func generateAppYAMLData(name string, refs ...*app.RegistryRefSpec) ([]byte, error) {
-	content := app.Spec{
-		APIVersion:   app.DefaultAPIVersion,
-		Kind:         app.Kind,
-		Name:         name,
-		Version:      app.DefaultVersion,
-		Registries:   app.RegistryRefSpecs{},
-		Environments: app.EnvironmentSpecs{},
-	}
-
-	for _, ref := range refs {
-		err := content.AddRegistryRef(ref)
-		if err != nil {
-			return nil, err
-		}
-	}
-
-	return content.Marshal()
-}
-
-func genBaseLibsonnetContent() []byte {
-	return []byte(`local components = std.extVar("` + ComponentsExtCodeKey + `");
-components + {
-  // Insert user-specified overrides here.
-}
-`)
-}
-
-func errorOnCreateFailure(appName string, err error) error {
-	return fmt.Errorf("%s\nTo undo this simply delete directory '%s' and re-run `ks init`.\nIf the error persists, try using flag '--context' to set a different context or run `ks init --help` for more options", err, appName)
-}
diff --git a/pkg/appinit/init.go b/pkg/appinit/init.go
index 69345354b84e9423a67795893136ad92b3964169..c2006a1844114d50931bcd12c6239d9d86f8fcdd 100644
--- a/pkg/appinit/init.go
+++ b/pkg/appinit/init.go
@@ -19,8 +19,8 @@ import (
 	"path/filepath"
 
 	"github.com/ksonnet/ksonnet/component"
-	"github.com/ksonnet/ksonnet/env"
 	"github.com/ksonnet/ksonnet/metadata/app"
+	"github.com/ksonnet/ksonnet/pkg/env"
 	"github.com/ksonnet/ksonnet/pkg/registry"
 	"github.com/pkg/errors"
 	log "github.com/sirupsen/logrus"
@@ -78,8 +78,8 @@ func (i *initApp) Run() error {
 			d,
 			env.DefaultEnvName,
 			i.k8sSpecFlag,
-			env.DefaultOverrideData(),
-			env.DefaultParamsData(),
+			env.DefaultOverrideData,
+			env.DefaultParamsData,
 			false,
 		)
 
@@ -193,7 +193,7 @@ func (i *initApp) createAppDirTree() error {
 		},
 		{
 			filepath.Join(i.rootPath, "environments", "base.libsonnet"),
-			env.DefaultBaseData(),
+			env.DefaultBaseData,
 		},
 		{
 			filepath.Join(i.rootPath, "app.yaml"),
diff --git a/env/create.go b/pkg/env/create.go
similarity index 94%
rename from env/create.go
rename to pkg/env/create.go
index fe891eb3cf6dc930ac76e6ef1bcf92271aad0bf5..29555e563cc3c5cf14375d5c873b45d54a7da1a3 100644
--- a/env/create.go
+++ b/pkg/env/create.go
@@ -33,17 +33,6 @@ const (
 	DefaultEnvName = "default"
 )
 
-// CreateConfig is configuration for creating an environment.
-type CreateConfig struct {
-	App         app.App
-	Destination Destination
-	K8sSpecFlag string
-	Name        string
-
-	OverrideData []byte
-	ParamsData   []byte
-}
-
 // Create creates a new environment for the project.
 func Create(a app.App, d Destination, name, k8sSpecFlag string, overrideData, paramsData []byte, isOverride bool) error {
 	c, err := newCreator(a, d, name, k8sSpecFlag, overrideData, paramsData, isOverride)
diff --git a/env/create_test.go b/pkg/env/create_test.go
similarity index 100%
rename from env/create_test.go
rename to pkg/env/create_test.go
diff --git a/env/data.go b/pkg/env/data.go
similarity index 59%
rename from env/data.go
rename to pkg/env/data.go
index afa4fb82050efefaa498cf2ce96cd7cba96acc45..447c0cad1b722238b8ea222d438299852218a615 100644
--- a/env/data.go
+++ b/pkg/env/data.go
@@ -15,35 +15,25 @@
 
 package env
 
-import (
-	"bytes"
-	"fmt"
-)
-
 const (
 	// ComponentsExtCodeKey is the ExtCode key for component imports
 	ComponentsExtCodeKey = "__ksonnet/components"
+
+	relComponentParamsPath = "../../components/params.libsonnet"
 )
 
 // DefaultOverrideData generates the contents for an environment's `main.jsonnet`.
-func DefaultOverrideData() []byte {
-	var buf bytes.Buffer
-	buf.WriteString(fmt.Sprintf("local base = import \"%s\";\n", "base.libsonnet"))
-	buf.WriteString(fmt.Sprintf("local k = import \"%s\";\n\n", "k.libsonnet"))
-	buf.WriteString("base + {\n")
-	buf.WriteString("  // Insert user-specified overrides here. For example if a component is named \"nginx-deployment\", you might have something like:\n")
-	buf.WriteString("  //   \"nginx-deployment\"+: k.deployment.mixin.metadata.labels({foo: \"bar\"})\n")
-	buf.WriteString("}\n")
-	return buf.Bytes()
+var DefaultOverrideData = []byte(`local base = import "base.libsonnet";
+local k = import "k.libsonnet"
+
+base + {
+  // Insert user-specified overrides here. For example if a component is named \"nginx-deployment\", you might have something like:\n")
+  // "nginx-deployment"+: k.deployment.mixin.metadata.labels({foo: "bar"})
 }
+`)
 
 // DefaultParamsData generates the contents for an environment's `params.libsonnet`
-func DefaultParamsData() []byte {
-	const (
-		relComponentParamsPath = "../../components/params.libsonnet"
-	)
-
-	return []byte(`local params = import "` + relComponentParamsPath + `";
+var DefaultParamsData = []byte(`local params = import "` + relComponentParamsPath + `";
 params + {
   components +: {
     // Insert component parameter overrides here. Ex:
@@ -54,13 +44,10 @@ params + {
   },
 }
 `)
-}
 
 // DefaultBaseData generates environment `base.libsonnet`.
-func DefaultBaseData() []byte {
-	return []byte(`local components = std.extVar("` + ComponentsExtCodeKey + `");
+var DefaultBaseData = []byte(`local components = std.extVar("` + ComponentsExtCodeKey + `");
 components + {
   // Insert user-specified overrides here.
 }
 `)
-}
diff --git a/env/delete.go b/pkg/env/delete.go
similarity index 100%
rename from env/delete.go
rename to pkg/env/delete.go
diff --git a/env/delete_test.go b/pkg/env/delete_test.go
similarity index 100%
rename from env/delete_test.go
rename to pkg/env/delete_test.go
diff --git a/env/destination.go b/pkg/env/destination.go
similarity index 100%
rename from env/destination.go
rename to pkg/env/destination.go
diff --git a/pkg/env/env.go b/pkg/env/env.go
new file mode 100644
index 0000000000000000000000000000000000000000..419eb6d48c34f7a86803fdb8d9f739ecf35eb724
--- /dev/null
+++ b/pkg/env/env.go
@@ -0,0 +1,25 @@
+// Copyright 2018 The kubecfg authors
+//
+//
+//    Licensed under the Apache License, Version 2.0 (the "License");
+//    you may not use this file except in compliance with the License.
+//    You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+//    Unless required by applicable law or agreed to in writing, software
+//    distributed under the License is distributed on an "AS IS" BASIS,
+//    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+//    See the License for the specific language governing permissions and
+//    limitations under the License.
+
+package env
+
+const (
+	// primary environment files.
+	envFileName    = "main.jsonnet"
+	paramsFileName = "params.libsonnet"
+
+	// envRoot is the name for the environment root.
+	envRoot = "environments"
+)
diff --git a/pkg/env/env_test.go b/pkg/env/env_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..7d2a70aa4f7b8feb3f35dd34d8f66d47d786ab72
--- /dev/null
+++ b/pkg/env/env_test.go
@@ -0,0 +1,16 @@
+// Copyright 2018 The kubecfg authors
+//
+//
+//    Licensed under the Apache License, Version 2.0 (the "License");
+//    you may not use this file except in compliance with the License.
+//    You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+//    Unless required by applicable law or agreed to in writing, software
+//    distributed under the License is distributed on an "AS IS" BASIS,
+//    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+//    See the License for the specific language governing permissions and
+//    limitations under the License.
+
+package env
diff --git a/env/params.go b/pkg/env/params.go
similarity index 100%
rename from env/params.go
rename to pkg/env/params.go
diff --git a/env/params_test.go b/pkg/env/params_test.go
similarity index 100%
rename from env/params_test.go
rename to pkg/env/params_test.go
diff --git a/env/rename.go b/pkg/env/rename.go
similarity index 82%
rename from env/rename.go
rename to pkg/env/rename.go
index 9f95e6bd40a161d278f295e3b7ee084f508e1853..e480238876a8ef708253bce8a233fbfa7f52d447 100644
--- a/env/rename.go
+++ b/pkg/env/rename.go
@@ -98,39 +98,6 @@ func envExists(ksApp app.App, name string) (bool, error) {
 	return afero.Exists(ksApp.Fs(), path)
 }
 
-func moveDir(fs afero.Fs, src, dest string) error {
-	exists, err := afero.DirExists(fs, dest)
-	if err != nil {
-		return err
-	}
-
-	if !exists {
-		if err = fs.MkdirAll(dest, app.DefaultFolderPermissions); err != nil {
-			return errors.Wrapf(err, "unable to create destination %q", dest)
-		}
-	}
-
-	fis, err := afero.ReadDir(fs, src)
-	if err != nil {
-		return err
-	}
-
-	for _, fi := range fis {
-		if fi.IsDir() && fi.Name() != ".metadata" {
-			continue
-		}
-
-		srcPath := filepath.Join(src, fi.Name())
-		destPath := filepath.Join(dest, fi.Name())
-
-		if err = fs.Rename(srcPath, destPath); err != nil {
-			return err
-		}
-	}
-
-	return nil
-}
-
 func envPath(ksApp app.App, name string, subPath ...string) string {
 	return filepath.Join(append([]string{ksApp.Root(), envRoot, name}, subPath...)...)
 }
diff --git a/env/rename_test.go b/pkg/env/rename_test.go
similarity index 100%
rename from env/rename_test.go
rename to pkg/env/rename_test.go
diff --git a/env/testdata/app.yaml b/pkg/env/testdata/app.yaml
similarity index 100%
rename from env/testdata/app.yaml
rename to pkg/env/testdata/app.yaml
diff --git a/env/testdata/component-params.libsonnet b/pkg/env/testdata/component-params.libsonnet
similarity index 100%
rename from env/testdata/component-params.libsonnet
rename to pkg/env/testdata/component-params.libsonnet
diff --git a/env/testdata/main.jsonnet b/pkg/env/testdata/main.jsonnet
similarity index 100%
rename from env/testdata/main.jsonnet
rename to pkg/env/testdata/main.jsonnet
diff --git a/env/testdata/params.libsonnet b/pkg/env/testdata/params.libsonnet
similarity index 100%
rename from env/testdata/params.libsonnet
rename to pkg/env/testdata/params.libsonnet
diff --git a/env/testdata/updated-params.libsonnet b/pkg/env/testdata/updated-params.libsonnet
similarity index 100%
rename from env/testdata/updated-params.libsonnet
rename to pkg/env/testdata/updated-params.libsonnet
diff --git a/env/util_test.go b/pkg/env/util_test.go
similarity index 100%
rename from env/util_test.go
rename to pkg/env/util_test.go
diff --git a/pkg/kubecfg/apply.go b/pkg/kubecfg/apply.go
deleted file mode 100644
index 75abd2bc73ee83c4ebc93565c7d1d0f15a99440d..0000000000000000000000000000000000000000
--- a/pkg/kubecfg/apply.go
+++ /dev/null
@@ -1,275 +0,0 @@
-// Copyright 2018 The kubecfg authors
-//
-//
-//    Licensed under the Apache License, Version 2.0 (the "License");
-//    you may not use this file except in compliance with the License.
-//    You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-//    Unless required by applicable law or agreed to in writing, software
-//    distributed under the License is distributed on an "AS IS" BASIS,
-//    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-//    See the License for the specific language governing permissions and
-//    limitations under the License.
-
-package kubecfg
-
-import (
-	"encoding/json"
-	"fmt"
-	"sort"
-
-	"github.com/ksonnet/ksonnet/client"
-	"github.com/ksonnet/ksonnet/metadata/app"
-	"github.com/ksonnet/ksonnet/utils"
-	log "github.com/sirupsen/logrus"
-	"k8s.io/apimachinery/pkg/api/errors"
-	"k8s.io/apimachinery/pkg/api/meta"
-	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
-	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
-	"k8s.io/apimachinery/pkg/runtime"
-	"k8s.io/apimachinery/pkg/runtime/schema"
-	"k8s.io/apimachinery/pkg/types"
-	kdiff "k8s.io/apimachinery/pkg/util/diff"
-	"k8s.io/apimachinery/pkg/util/sets"
-	"k8s.io/client-go/discovery"
-	"k8s.io/client-go/dynamic"
-)
-
-const (
-	// AnnotationGcTag annotation that triggers
-	// garbage collection. Objects with value equal to
-	// command-line flag that are *not* in config will be deleted.
-	AnnotationGcTag = "kubecfg.ksonnet.io/garbage-collect-tag"
-
-	// AnnotationGcStrategy controls gc logic.  Current values:
-	// `auto` (default if absent) - do garbage collection
-	// `ignore` - never garbage collect this object
-	AnnotationGcStrategy = "kubecfg.ksonnet.io/garbage-collect-strategy"
-
-	// GcStrategyAuto is the default automatic gc logic
-	GcStrategyAuto = "auto"
-	// GcStrategyIgnore means this object should be ignored by garbage collection
-	GcStrategyIgnore = "ignore"
-)
-
-// ApplyCmd represents the apply subcommand
-type ApplyCmd struct {
-	App          app.App
-	ClientConfig *client.Config
-	Env          string
-	Create       bool
-	GcTag        string
-	SkipGc       bool
-	DryRun       bool
-}
-
-// Run applies the components to the designated environment cluster.
-func (c *ApplyCmd) Run(apiObjects []*unstructured.Unstructured, wd string) error {
-	clientPool, discovery, namespace, err := c.ClientConfig.RestClient(c.App, &c.Env)
-	if err != nil {
-		return err
-	}
-
-	dryRunText := ""
-	if c.DryRun {
-		dryRunText = " (dry-run)"
-	}
-
-	sort.Sort(utils.DependencyOrder(apiObjects))
-
-	seenUids := sets.NewString()
-
-	for _, obj := range apiObjects {
-		if c.GcTag != "" {
-			utils.SetMetaDataAnnotation(obj, AnnotationGcTag, c.GcTag)
-		}
-
-		desc := fmt.Sprintf("%s %s", utils.ResourceNameFor(discovery, obj), utils.FqName(obj))
-		log.Info("Updating ", desc, dryRunText)
-
-		rc, err := utils.ClientForResource(clientPool, discovery, obj, namespace)
-		if err != nil {
-			return err
-		}
-
-		asPatch, err := json.Marshal(obj)
-		if err != nil {
-			return err
-		}
-		var newobj metav1.Object
-		if !c.DryRun {
-			newobj, err = rc.Patch(obj.GetName(), types.MergePatchType, asPatch)
-			log.Debugf("Patch(%s) returned (%v, %v)", obj.GetName(), newobj, err)
-		} else {
-			newobj, err = rc.Get(obj.GetName(), metav1.GetOptions{})
-		}
-		if c.Create && errors.IsNotFound(err) {
-			log.Info(" Creating non-existent ", desc, dryRunText)
-			if !c.DryRun {
-				newobj, err = rc.Create(obj)
-				log.Debugf("Create(%s) returned (%v, %v)", obj.GetName(), newobj, err)
-			} else {
-				newobj = obj
-				err = nil
-			}
-		}
-		if err != nil {
-			// TODO: retry
-			return fmt.Errorf("Error updating %s: %s", desc, err)
-		}
-
-		log.Debug("Updated object: ", kdiff.ObjectDiff(obj, newobj))
-
-		// Some objects appear under multiple kinds
-		// (eg: Deployment is both extensions/v1beta1
-		// and apps/v1beta1).  UID is the only stable
-		// identifier that links these two views of
-		// the same object.
-		seenUids.Insert(string(newobj.GetUID()))
-	}
-
-	if c.GcTag != "" && !c.SkipGc {
-		version, err := utils.FetchVersion(discovery)
-		if err != nil {
-			return err
-		}
-
-		err = walkObjects(clientPool, discovery, metav1.ListOptions{}, func(o runtime.Object) error {
-			meta, err := meta.Accessor(o)
-			if err != nil {
-				return err
-			}
-			gvk := o.GetObjectKind().GroupVersionKind()
-			desc := fmt.Sprintf("%s %s (%s)", utils.ResourceNameFor(discovery, o), utils.FqName(meta), gvk.GroupVersion())
-			log.Debugf("Considering %v for gc", desc)
-			if eligibleForGc(meta, c.GcTag) && !seenUids.Has(string(meta.GetUID())) {
-				log.Info("Garbage collecting ", desc, dryRunText)
-				if !c.DryRun {
-					err := gcDelete(clientPool, discovery, &version, o)
-					if err != nil {
-						return err
-					}
-				}
-			}
-			return nil
-		})
-		if err != nil {
-			return err
-		}
-	}
-
-	return nil
-}
-
-func stringListContains(list []string, value string) bool {
-	for _, item := range list {
-		if item == value {
-			return true
-		}
-	}
-	return false
-}
-
-func gcDelete(clientpool dynamic.ClientPool, disco discovery.DiscoveryInterface, version *utils.ServerVersion, o runtime.Object) error {
-	obj, err := meta.Accessor(o)
-	if err != nil {
-		return fmt.Errorf("Unexpected object type: %s", err)
-	}
-
-	uid := obj.GetUID()
-	desc := fmt.Sprintf("%s %s", utils.ResourceNameFor(disco, o), utils.FqName(obj))
-
-	deleteOpts := metav1.DeleteOptions{
-		Preconditions: &metav1.Preconditions{UID: &uid},
-	}
-	if version.Compare(1, 6) < 0 {
-		// 1.5.x option
-		boolFalse := false
-		deleteOpts.OrphanDependents = &boolFalse
-	} else {
-		// 1.6.x option (NB: Background is broken)
-		fg := metav1.DeletePropagationForeground
-		deleteOpts.PropagationPolicy = &fg
-	}
-
-	c, err := utils.ClientForResource(clientpool, disco, o, metav1.NamespaceNone)
-	if err != nil {
-		return err
-	}
-
-	err = c.Delete(obj.GetName(), &deleteOpts)
-	if err != nil && (errors.IsNotFound(err) || errors.IsConflict(err)) {
-		// We lost a race with something else changing the object
-		log.Debugf("Ignoring error while deleting %s: %s", desc, err)
-		err = nil
-	}
-	if err != nil {
-		return fmt.Errorf("Error deleting %s: %s", desc, err)
-	}
-
-	return nil
-}
-
-func walkObjects(pool dynamic.ClientPool, disco discovery.DiscoveryInterface, listopts metav1.ListOptions, callback func(runtime.Object) error) error {
-	rsrclists, err := disco.ServerResources()
-	if err != nil {
-		return err
-	}
-	for _, rsrclist := range rsrclists {
-		gv, err := schema.ParseGroupVersion(rsrclist.GroupVersion)
-		if err != nil {
-			return err
-		}
-		for _, rsrc := range rsrclist.APIResources {
-			gvk := gv.WithKind(rsrc.Kind)
-
-			if !stringListContains(rsrc.Verbs, "list") {
-				log.Debugf("Don't know how to list %v, skipping", rsrc)
-				continue
-			}
-			client, err := pool.ClientForGroupVersionKind(gvk)
-			if err != nil {
-				return err
-			}
-
-			var ns string
-			if rsrc.Namespaced {
-				ns = metav1.NamespaceAll
-			} else {
-				ns = metav1.NamespaceNone
-			}
-
-			rc := client.Resource(&rsrc, ns)
-			log.Debugf("Listing %s", gvk)
-			obj, err := rc.List(listopts)
-			if err != nil {
-				return err
-			}
-			if err = meta.EachListItem(obj, callback); err != nil {
-				return err
-			}
-		}
-	}
-	return nil
-}
-
-func eligibleForGc(obj metav1.Object, gcTag string) bool {
-	for _, ref := range obj.GetOwnerReferences() {
-		if ref.Controller != nil && *ref.Controller {
-			// Has a controller ref
-			return false
-		}
-	}
-
-	a := obj.GetAnnotations()
-
-	strategy, ok := a[AnnotationGcStrategy]
-	if !ok {
-		strategy = GcStrategyAuto
-	}
-
-	return a[AnnotationGcTag] == gcTag &&
-		strategy == GcStrategyAuto
-}
diff --git a/pkg/kubecfg/init.go b/pkg/kubecfg/init.go
deleted file mode 100644
index efe08c56216d5be3632c2ad8aa66a83e6c0f5a03..0000000000000000000000000000000000000000
--- a/pkg/kubecfg/init.go
+++ /dev/null
@@ -1,41 +0,0 @@
-// Copyright 2018 The kubecfg authors
-//
-//
-//    Licensed under the Apache License, Version 2.0 (the "License");
-//    you may not use this file except in compliance with the License.
-//    You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-//    Unless required by applicable law or agreed to in writing, software
-//    distributed under the License is distributed on an "AS IS" BASIS,
-//    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-//    See the License for the specific language governing permissions and
-//    limitations under the License.
-
-package kubecfg
-
-import (
-	"github.com/ksonnet/ksonnet/metadata"
-	log "github.com/sirupsen/logrus"
-)
-
-type InitCmd struct {
-	name        string
-	rootPath    string
-	k8sSpecFlag *string
-	serverURI   *string
-	namespace   *string
-}
-
-func NewInitCmd(name, rootPath string, k8sSpecFlag, serverURI, namespace *string) (*InitCmd, error) {
-	return &InitCmd{name: name, rootPath: rootPath, k8sSpecFlag: k8sSpecFlag, serverURI: serverURI, namespace: namespace}, nil
-}
-
-func (c *InitCmd) Run() error {
-	_, err := metadata.Init(c.name, c.rootPath, c.k8sSpecFlag, c.serverURI, c.namespace)
-	if err == nil {
-		log.Info("ksonnet app successfully created! Next, try creating a component with `ks generate`.")
-	}
-	return err
-}
diff --git a/pkg/kubecfg/param.go b/pkg/kubecfg/param.go
deleted file mode 100644
index bccd0bbdb97987603edc441b44f72a3502884d72..0000000000000000000000000000000000000000
--- a/pkg/kubecfg/param.go
+++ /dev/null
@@ -1,378 +0,0 @@
-// Copyright 2017 The kubecfg authors
-//
-//
-//    Licensed under the Apache License, Version 2.0 (the "License");
-//    you may not use this file except in compliance with the License.
-//    You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-//    Unless required by applicable law or agreed to in writing, software
-//    distributed under the License is distributed on an "AS IS" BASIS,
-//    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-//    See the License for the specific language governing permissions and
-//    limitations under the License.
-
-package kubecfg
-
-import (
-	"fmt"
-	"io"
-	"os"
-	"reflect"
-	"sort"
-	"strconv"
-
-	"github.com/fatih/color"
-	"github.com/ksonnet/ksonnet/component"
-	param "github.com/ksonnet/ksonnet/metadata/params"
-	"github.com/ksonnet/ksonnet/pkg/util/table"
-	str "github.com/ksonnet/ksonnet/strings"
-	"github.com/pkg/errors"
-	"github.com/spf13/afero"
-
-	log "github.com/sirupsen/logrus"
-)
-
-func sortedKeys(params map[string]param.Params) []string {
-	// keys maintains an alphabetically sorted list of the components
-	keys := make([]string, 0, len(params))
-	for key := range params {
-		keys = append(keys, key)
-	}
-	sort.Strings(keys)
-	return keys
-}
-
-func sortedParams(params param.Params) []string {
-	// keys maintains an alphabetically sorted list of the params
-	keys := make([]string, 0, len(params))
-	for key := range params {
-		keys = append(keys, key)
-	}
-	sort.Strings(keys)
-	return keys
-}
-
-// ----------------------------------------------------------------------------
-
-// ParamSetCmd stores the information necessary to set component and
-// environment params.
-type ParamSetCmd struct {
-	component string
-	env       string
-
-	param string
-	value string
-}
-
-// NewParamSetCmd acts as a constructor for ParamSetCmd. It will also sanitize
-// or "quote" the param value first if necessary.
-func NewParamSetCmd(component, env, param, value string) *ParamSetCmd {
-	return &ParamSetCmd{component: component, env: env, param: param, value: sanitizeParamValue(value)}
-}
-
-// Run executes the setting of params.
-func (c *ParamSetCmd) Run() error {
-	manager, err := manager()
-	if err != nil {
-		return err
-	}
-
-	if len(c.env) == 0 {
-		if err = manager.SetComponentParams(c.component, param.Params{c.param: c.value}); err == nil {
-			log.Infof("Parameter '%s' successfully set to '%s' for component '%s'", c.param, c.value, c.component)
-		}
-	} else {
-		if err = manager.SetEnvironmentParams(c.env, c.component, param.Params{c.param: c.value}); err == nil {
-			log.Infof("Parameter '%s' successfully set to '%s' for component '%s' in environment '%s'",
-				c.param, c.value, c.component, c.env)
-		}
-	}
-
-	return err
-}
-
-// sanitizeParamValue does a best effort to identify value types. It will put
-// quotes around values which it categorizes as strings.
-func sanitizeParamValue(value string) string {
-	// numeric
-	if _, err := strconv.ParseFloat(value, 64); err == nil {
-		return value
-	}
-	// string
-	return fmt.Sprintf(`"%s"`, value)
-}
-
-// ----------------------------------------------------------------------------
-
-const (
-	paramComponentHeader = "COMPONENT"
-	paramNameHeader      = "PARAM"
-	paramValueHeader     = "VALUE"
-)
-
-// ParamListCmd stores the information necessary display component or
-// environment parameters
-type ParamListCmd struct {
-	fs        afero.Fs
-	root      string
-	component string
-	env       string
-	nsName    string
-}
-
-// NewParamListCmd acts as a constructor for ParamListCmd.
-func NewParamListCmd(component, env, nsName string) *ParamListCmd {
-	return &ParamListCmd{
-		component: component,
-		env:       env,
-		nsName:    nsName,
-	}
-}
-
-// Run executes the displaying of params.
-func (c *ParamListCmd) Run(out io.Writer) error {
-	cwd, err := os.Getwd()
-	if err != nil {
-		return errors.Wrap(err, "get current working directory")
-	}
-
-	manager, err := manager()
-	if err != nil {
-		return err
-	}
-
-	var params map[string]param.Params
-	if len(c.env) != 0 {
-		params, err = manager.GetEnvironmentParams(c.env, c.nsName)
-		if err != nil {
-			return err
-		}
-	} else {
-		params, err = manager.GetAllComponentParams(cwd)
-		if err != nil {
-			return err
-		}
-	}
-
-	if len(c.component) != 0 {
-		if _, ok := params[c.component]; !ok {
-			return fmt.Errorf("No such component '%s' found", c.component)
-		}
-
-		p := params[c.component]
-		outputParamsFor(c.component, p, out)
-		return nil
-	}
-
-	outputParams(params, out)
-	return nil
-}
-
-func outputParamsFor(component string, params param.Params, out io.Writer) {
-	keys := sortedParams(params)
-
-	t := table.New(out)
-	t.SetHeader([]string{paramNameHeader, paramValueHeader})
-	for _, k := range keys {
-		t.Append([]string{k, params[k]})
-	}
-
-	t.Render()
-}
-
-func outputParams(params map[string]param.Params, out io.Writer) {
-	keys := sortedKeys(params)
-
-	t := table.New(out)
-	t.SetHeader([]string{paramComponentHeader, paramNameHeader, paramValueHeader})
-
-	for _, k := range keys {
-		// sort params to display alphabetically
-		ps := sortedParams(params[k])
-
-		for _, p := range ps {
-			t.Append([]string{k, p, params[k][p]})
-		}
-	}
-
-	t.Render()
-}
-
-// ----------------------------------------------------------------------------
-
-// ParamDiffCmd stores the information necessary to diff between environment
-// parameters.
-type ParamDiffCmd struct {
-	fs   afero.Fs
-	root string
-	env1 string
-	env2 string
-
-	component string
-}
-
-// NewParamDiffCmd acts as a constructor for ParamDiffCmd.
-func NewParamDiffCmd(fs afero.Fs, root, env1, env2, componentName string) *ParamDiffCmd {
-	return &ParamDiffCmd{
-		fs:        fs,
-		root:      root,
-		env1:      env1,
-		env2:      env2,
-		component: componentName,
-	}
-}
-
-type paramDiffRecord struct {
-	component string
-	param     string
-	value1    string
-	value2    string
-}
-
-// Run executes the diffing of environment params.
-func (c *ParamDiffCmd) Run(out io.Writer) error {
-	const (
-		componentHeader = "COMPONENT"
-		paramHeader     = "PARAM"
-	)
-
-	manager, err := manager()
-	if err != nil {
-		return err
-	}
-
-	ksApp, err := manager.App()
-	if err != nil {
-		return err
-	}
-
-	ns, componentName := component.ExtractNamespacedComponent(ksApp, c.component)
-
-	params1, err := manager.GetEnvironmentParams(c.env1, ns.Name())
-	if err != nil {
-		return err
-	}
-
-	params2, err := manager.GetEnvironmentParams(c.env2, ns.Name())
-	if err != nil {
-		return err
-	}
-
-	if len(c.component) != 0 {
-		params1 = map[string]param.Params{componentName: params1[componentName]}
-		params2 = map[string]param.Params{componentName: params2[componentName]}
-	}
-
-	if reflect.DeepEqual(params1, params2) {
-		log.Info("No differences found.")
-		return nil
-	}
-
-	componentNames := collectComponents(params1, params2)
-
-	headers := str.Row{
-		Content: []string{componentHeader, paramHeader, c.env1, c.env2},
-	}
-
-	var body []str.Row
-	for _, componentName := range componentNames {
-		paramNames := collectParams(params1[componentName], params2[componentName])
-
-		for _, paramName := range paramNames {
-			var v1, v2 string
-
-			if p, ok := params1[componentName]; ok {
-				v1 = p[paramName]
-			}
-
-			if p, ok := params2[componentName]; ok {
-				v2 = p[paramName]
-			}
-
-			var bgColor *color.Color
-			if v1 == "" {
-				bgColor = color.New(color.BgGreen)
-			} else if v2 == "" {
-				bgColor = color.New(color.BgRed)
-			} else if v1 != v2 {
-				bgColor = color.New(color.BgYellow)
-			}
-
-			body = append(body, str.Row{
-				Content: []string{
-					componentName,
-					paramName,
-					v1,
-					v2,
-				},
-				Color: bgColor,
-			})
-		}
-	}
-
-	formatted, err := str.Table(headers, body)
-	if err != nil {
-		return err
-	}
-
-	for _, row := range formatted {
-		if row.Color != nil {
-			_, err = row.Color.Fprint(out, row.Content)
-			if err != nil {
-				return err
-			}
-			// Must print new line separately otherwise color alignment will be
-			// incorrect.
-			fmt.Println()
-		} else {
-			_, err = fmt.Fprintln(out, row.Content)
-			if err != nil {
-				return err
-			}
-		}
-	}
-
-	return nil
-}
-
-func collectComponents(param1, param2 map[string]param.Params) []string {
-	m := make(map[string]bool)
-	for k := range param1 {
-		m[k] = true
-	}
-	for k := range param2 {
-		m[k] = true
-	}
-
-	var names []string
-
-	for k := range m {
-		names = append(names, k)
-	}
-
-	sort.Strings(names)
-
-	return names
-}
-
-func collectParams(param1, param2 param.Params) []string {
-	m := make(map[string]bool)
-	for k := range param1 {
-		m[k] = true
-	}
-	for k := range param2 {
-		m[k] = true
-	}
-
-	var names []string
-
-	for k := range m {
-		names = append(names, k)
-	}
-
-	sort.Strings(names)
-
-	return names
-}
diff --git a/pkg/kubecfg/param_diff.go b/pkg/kubecfg/param_diff.go
new file mode 100644
index 0000000000000000000000000000000000000000..f302467188879ce5247a630e6de56afe229d4987
--- /dev/null
+++ b/pkg/kubecfg/param_diff.go
@@ -0,0 +1,205 @@
+// Copyright 2018 The ksonnet authors
+//
+//
+//    Licensed under the Apache License, Version 2.0 (the "License");
+//    you may not use this file except in compliance with the License.
+//    You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+//    Unless required by applicable law or agreed to in writing, software
+//    distributed under the License is distributed on an "AS IS" BASIS,
+//    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+//    See the License for the specific language governing permissions and
+//    limitations under the License.
+
+package kubecfg
+
+import (
+	"fmt"
+	"io"
+	"reflect"
+	"sort"
+
+	"github.com/fatih/color"
+	"github.com/ksonnet/ksonnet/component"
+	param "github.com/ksonnet/ksonnet/metadata/params"
+	str "github.com/ksonnet/ksonnet/strings"
+	log "github.com/sirupsen/logrus"
+	"github.com/spf13/afero"
+)
+
+// ParamDiffCmd stores the information necessary to diff between environment
+// parameters.
+type ParamDiffCmd struct {
+	fs   afero.Fs
+	root string
+	env1 string
+	env2 string
+
+	component string
+}
+
+// NewParamDiffCmd acts as a constructor for ParamDiffCmd.
+func NewParamDiffCmd(fs afero.Fs, root, env1, env2, componentName string) *ParamDiffCmd {
+	return &ParamDiffCmd{
+		fs:        fs,
+		root:      root,
+		env1:      env1,
+		env2:      env2,
+		component: componentName,
+	}
+}
+
+type paramDiffRecord struct {
+	component string
+	param     string
+	value1    string
+	value2    string
+}
+
+// Run executes the diffing of environment params.
+func (c *ParamDiffCmd) Run(out io.Writer) error {
+	const (
+		componentHeader = "COMPONENT"
+		paramHeader     = "PARAM"
+	)
+
+	manager, err := manager()
+	if err != nil {
+		return err
+	}
+
+	ksApp, err := manager.App()
+	if err != nil {
+		return err
+	}
+
+	ns, componentName := component.ExtractNamespacedComponent(ksApp, c.component)
+
+	params1, err := manager.GetEnvironmentParams(c.env1, ns.Name())
+	if err != nil {
+		return err
+	}
+
+	params2, err := manager.GetEnvironmentParams(c.env2, ns.Name())
+	if err != nil {
+		return err
+	}
+
+	if len(c.component) != 0 {
+		params1 = map[string]param.Params{componentName: params1[componentName]}
+		params2 = map[string]param.Params{componentName: params2[componentName]}
+	}
+
+	if reflect.DeepEqual(params1, params2) {
+		log.Info("No differences found.")
+		return nil
+	}
+
+	componentNames := collectComponents(params1, params2)
+
+	headers := str.Row{
+		Content: []string{componentHeader, paramHeader, c.env1, c.env2},
+	}
+
+	var body []str.Row
+	for _, componentName := range componentNames {
+		paramNames := collectParams(params1[componentName], params2[componentName])
+
+		for _, paramName := range paramNames {
+			var v1, v2 string
+
+			if p, ok := params1[componentName]; ok {
+				v1 = p[paramName]
+			}
+
+			if p, ok := params2[componentName]; ok {
+				v2 = p[paramName]
+			}
+
+			var bgColor *color.Color
+			if v1 == "" {
+				bgColor = color.New(color.BgGreen)
+			} else if v2 == "" {
+				bgColor = color.New(color.BgRed)
+			} else if v1 != v2 {
+				bgColor = color.New(color.BgYellow)
+			}
+
+			body = append(body, str.Row{
+				Content: []string{
+					componentName,
+					paramName,
+					v1,
+					v2,
+				},
+				Color: bgColor,
+			})
+		}
+	}
+
+	formatted, err := str.Table(headers, body)
+	if err != nil {
+		return err
+	}
+
+	for _, row := range formatted {
+		if row.Color != nil {
+			_, err = row.Color.Fprint(out, row.Content)
+			if err != nil {
+				return err
+			}
+			// Must print new line separately otherwise color alignment will be
+			// incorrect.
+			fmt.Println()
+		} else {
+			_, err = fmt.Fprintln(out, row.Content)
+			if err != nil {
+				return err
+			}
+		}
+	}
+
+	return nil
+}
+
+func collectComponents(param1, param2 map[string]param.Params) []string {
+	m := make(map[string]bool)
+	for k := range param1 {
+		m[k] = true
+	}
+	for k := range param2 {
+		m[k] = true
+	}
+
+	var names []string
+
+	for k := range m {
+		names = append(names, k)
+	}
+
+	sort.Strings(names)
+
+	return names
+}
+
+func collectParams(param1, param2 param.Params) []string {
+	m := make(map[string]bool)
+	for k := range param1 {
+		m[k] = true
+	}
+	for k := range param2 {
+		m[k] = true
+	}
+
+	var names []string
+
+	for k := range m {
+		names = append(names, k)
+	}
+
+	sort.Strings(names)
+
+	return names
+}
diff --git a/pkg/kubecfg/param_test.go b/pkg/kubecfg/param_test.go
deleted file mode 100644
index 797cbabab3a8ef0dac207fbdc93f8a6629483ec4..0000000000000000000000000000000000000000
--- a/pkg/kubecfg/param_test.go
+++ /dev/null
@@ -1,54 +0,0 @@
-// Copyright 2017 The kubecfg authors
-//
-//
-//    Licensed under the Apache License, Version 2.0 (the "License");
-//    you may not use this file except in compliance with the License.
-//    You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-//    Unless required by applicable law or agreed to in writing, software
-//    distributed under the License is distributed on an "AS IS" BASIS,
-//    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-//    See the License for the specific language governing permissions and
-//    limitations under the License.
-
-package kubecfg
-
-import (
-	"testing"
-
-	"github.com/stretchr/testify/require"
-)
-
-func TestSanitizeParamValue(t *testing.T) {
-	tests := []struct {
-		value    string
-		expected string
-	}{
-		// numbers
-		{
-			value:    "64.5",
-			expected: "64.5",
-		},
-		{
-			value:    "64",
-			expected: "64",
-		},
-		// boolean
-		{
-			value:    "false",
-			expected: `"false"`,
-		},
-		// string
-		{
-			value:    "my string",
-			expected: `"my string"`,
-		},
-	}
-
-	for _, te := range tests {
-		got := sanitizeParamValue(te.value)
-		require.Equal(t, got, te.expected, "Unexpected sanitized param value")
-	}
-}
diff --git a/pkg/registry/github.go b/pkg/registry/github.go
index d8d7a1dc65ae18da85acbfeed73ee750a708d03d..c9bf37fc5e1416c7c31d190929e74a2dccb547b8 100644
--- a/pkg/registry/github.go
+++ b/pkg/registry/github.go
@@ -31,10 +31,6 @@ import (
 const (
 	rawGitHubRoot       = "https://raw.githubusercontent.com"
 	defaultGitHubBranch = "master"
-
-	uriField         = "uri"
-	refSpecField     = "refSpec"
-	resolvedSHAField = "resolvedSHA"
 )
 
 var (
diff --git a/pkg/util/github/github.go b/pkg/util/github/github.go
index 3724d5f78a8d4c555540809b329fe5fa69920584..9f11ae682de74f0df54661bc45b9c1d5e0ddf8d0 100644
--- a/pkg/util/github/github.go
+++ b/pkg/util/github/github.go
@@ -17,7 +17,6 @@ package github
 
 import (
 	"context"
-	"fmt"
 	"net/http"
 	"os"
 
@@ -26,16 +25,9 @@ import (
 	"golang.org/x/oauth2"
 )
 
-const (
-	registryYAMLFile    = "registry.yaml"
-	defaultGitHubBranch = "master"
-)
-
 var (
 	// DefaultClient is the default GitHub client.
 	DefaultClient = &defaultGitHub{}
-
-	errInvalidURI = fmt.Errorf("Invalid GitHub URI: try navigating in GitHub to the URI of the folder containing the 'yaml', and using that URI instead. Generally, this URI should be of the form 'github.com/{organization}/{repository}/tree/{branch}/[path-to-directory]'")
 )
 
 // Repo is a GitHub repo
diff --git a/pkg/util/test/assert.go b/pkg/util/test/assert.go
new file mode 100644
index 0000000000000000000000000000000000000000..079752fbe9c3f3d7a2092db4f3a5983c3ad27440
--- /dev/null
+++ b/pkg/util/test/assert.go
@@ -0,0 +1,53 @@
+// Copyright 2018 The ksonnet authors
+//
+//
+//    Licensed under the Apache License, Version 2.0 (the "License");
+//    you may not use this file except in compliance with the License.
+//    You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+//    Unless required by applicable law or agreed to in writing, software
+//    distributed under the License is distributed on an "AS IS" BASIS,
+//    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+//    See the License for the specific language governing permissions and
+//    limitations under the License.
+
+package test
+
+import (
+	"io/ioutil"
+	"path/filepath"
+	"testing"
+
+	"github.com/spf13/afero"
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
+)
+
+// AssertExists asserts if a path exists.
+func AssertExists(t *testing.T, fs afero.Fs, path string) {
+	exists, err := afero.Exists(fs, path)
+	require.NoError(t, err)
+
+	assert.True(t, exists, "%q does not exist", path)
+}
+
+// AssertNotExists asserts a path does exist.
+func AssertNotExists(t *testing.T, fs afero.Fs, path string) {
+	exists, err := afero.Exists(fs, path)
+	require.NoError(t, err)
+
+	assert.False(t, exists, "%q exists", path)
+}
+
+// AssertContents asserts the contents of a file.
+func AssertContents(t *testing.T, fs afero.Fs, expectedPath, contentPath string) {
+	expected, err := ioutil.ReadFile(filepath.Join("testdata", expectedPath))
+	require.NoError(t, err)
+
+	got, err := afero.ReadFile(fs, contentPath)
+	require.NoError(t, err)
+
+	assert.Equal(t, string(expected), string(got), "unexpected %q contents", contentPath)
+}