Unverified Commit bb346e6f authored by Bryan Liles's avatar Bryan Liles Committed by GitHub
Browse files

Merge pull request #403 from bryanl/upgrade-env

moved env to pkg/env
parents 3043f720 0c1422e4
// Copyright 2018 The kubecfg authors
// Copyright 2018 The ksonnet authors
//
//
// Licensed under the Apache License, Version 2.0 (the "License");
......@@ -13,65 +13,50 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package env
package actions
import (
"github.com/ksonnet/ksonnet/component"
"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"
)
// RunComponentRm runs `component list`
func RunComponentRm(m map[string]interface{}) error {
cr, err := NewComponentRm(m)
if err != nil {
return err
}
// 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
return cr.Run()
}
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,
}
// ComponentRm create a list of components in a namespace.
type ComponentRm struct {
app app.App
name string
componentDeleteFn func(app.App, string) error
}
// List lists all environments for the current ksonnet application.
func List(ksApp app.App) (map[string]Env, error) {
envs := make(map[string]Env)
// NewComponentRm creates an instance of ComponentRm.
func NewComponentRm(m map[string]interface{}) (*ComponentRm, error) {
ol := newOptionLoader(m)
specs, err := ksApp.Environments()
if err != nil {
return nil, err
cr := &ComponentRm{
app: ol.loadApp(),
name: ol.loadString(OptionComponentName),
componentDeleteFn: component.Delete,
}
for name, spec := range specs {
env := envFromSpec(name, spec)
envs[name] = *env
if ol.err != nil {
return nil, ol.err
}
return envs, nil
return cr, 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
// Run runs the ComponentRm action.
func (cr *ComponentRm) Run() error {
return cr.componentDeleteFn(cr.app, cr.name)
}
// 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)
})
}
......@@ -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,
)
}
......@@ -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"
)
......
......@@ -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`
......
......@@ -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.
......
......@@ -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"
)
......
......@@ -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,
}
)
......@@ -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"
)
......
......@@ -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
......
......@@ -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)
}
......@@ -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() {
......
......@@ -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:
......
......@@ -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)
......
// 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
}
// 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(