Skip to content
Snippets Groups Projects
Commit 530f5777 authored by Jessica Yuen's avatar Jessica Yuen
Browse files

Add subcommand 'env rm'

'env rm <env-name>' deletes an environment within a ksonnet project.
This is the same as removing the <env-name> environment directory and
all files contained.  Empty parent directories will also be removed.
parent a169d973
No related branches found
No related tags found
No related merge requests found
......@@ -28,6 +28,7 @@ import (
func init() {
RootCmd.AddCommand(envCmd)
envCmd.AddCommand(envAddCmd)
envCmd.AddCommand(envRmCmd)
envCmd.AddCommand(envListCmd)
// TODO: We need to make this default to checking the `kubeconfig` file.
envAddCmd.PersistentFlags().String(flagAPISpec, "version:v1.7.0",
......@@ -107,6 +108,39 @@ Below is an example directory structure:
ksonnet env add default localhost:8000`,
}
var envRmCmd = &cobra.Command{
Use: "rm <env-name>",
Short: "Delete an environment within a ksonnet project",
RunE: func(cmd *cobra.Command, args []string) error {
if len(args) != 1 {
return fmt.Errorf("'env rm' takes a single argument, that is the name of the environment")
}
envName := args[0]
appDir, err := os.Getwd()
if err != nil {
return err
}
appRoot := metadata.AbsPath(appDir)
c, err := kubecfg.NewEnvRmCmd(envName, appRoot)
if err != nil {
return err
}
return c.Run()
},
Long: `Delete an environment within a ksonnet project. This is the same
as removing the <env-name> environment directory and all files contained.
Empty parent directories will also be removed.
`,
Example: ` # Remove the directory 'us-west/staging' and all contents
# in the 'environments' directory. This will also remove the parent directory
# 'us-west' if it is empty.
ksonnet env rm us-west/staging`,
}
var envListCmd = &cobra.Command{
Use: "list",
Short: "List all environments within a ksonnet project",
......
......@@ -17,6 +17,7 @@ package metadata
import (
"encoding/json"
"errors"
"os"
"path/filepath"
"strings"
......@@ -96,6 +97,54 @@ func (m *manager) CreateEnvironment(name, uri string, spec ClusterSpec, extensio
return afero.WriteFile(m.appFS, string(envSpecPath), envSpecData, os.ModePerm)
}
func (m *manager) DeleteEnvironment(name string) error {
envPath := string(appendToAbsPath(m.environmentsDir, name))
envs, err := m.GetEnvironments()
if err != nil {
return err
}
// Check whether this environment exists
envExists := false
for _, env := range envs {
if env.Path == envPath {
envExists = true
break
}
}
if !envExists {
return errors.New("Environment \"" + name + "\" does not exist.")
}
// Remove the directory and all files within the environment path.
err = m.appFS.RemoveAll(envPath)
if err != nil {
return err
}
// Need to ensure empty parent directories are also removed.
dirs := strings.Split(name, "/")
parentDir := name
for i := len(dirs) - 2; i >= 0; i-- {
parentDir = strings.TrimSuffix(parentDir, "/"+dirs[i+1])
parentPath := string(appendToAbsPath(m.environmentsDir, parentDir))
isEmpty, err := afero.IsEmpty(m.appFS, parentPath)
if err != nil {
return err
}
if isEmpty {
err := m.appFS.RemoveAll(parentPath)
if err != nil {
return err
}
}
}
return nil
}
func (m *manager) GetEnvironments() ([]Environment, error) {
envs := []Environment{}
......
......@@ -18,6 +18,7 @@ package metadata
import (
"fmt"
"os"
"strings"
"testing"
"github.com/spf13/afero"
......@@ -26,58 +27,102 @@ import (
const (
mockSpecJSON = "spec.json"
mockSpecJSONURI = "localhost:8080"
mockEnvName = "us-west/test"
mockEnvName2 = "us-west/prod"
mockEnvName3 = "us-east/test"
)
func TestGetEnvironments(t *testing.T) {
func mockEnvironments(t *testing.T, appName string) *manager {
spec, err := parseClusterSpec(fmt.Sprintf("file:%s", blankSwagger), testFS)
if err != nil {
t.Fatalf("Failed to parse cluster spec: %v", err)
}
appPath := AbsPath("/test-app")
appPath := AbsPath(appName)
m, err := initManager(appPath, spec, testFS)
if err != nil {
t.Fatalf("Failed to init cluster spec: %v", err)
}
defaultEnvDir := appendToAbsPath(environmentsDir, defaultEnvName)
anotherEnvDir := appendToAbsPath(environmentsDir, mockEnvName)
envDir := appendToAbsPath(appPath, environmentsDir)
testDirExists(t, string(envDir))
path := appendToAbsPath(appPath, environmentsDir)
exists, err := afero.DirExists(testFS, string(path))
if err != nil {
t.Fatalf("Expected to create directory '%s', but failed:\n%v", environmentsDir, err)
} else if !exists {
t.Fatalf("Expected to create directory '%s', but failed", path)
envNames := []string{defaultEnvName, mockEnvName, mockEnvName2, mockEnvName3}
for _, env := range envNames {
envPath := appendToAbsPath(envDir, env)
specPath := appendToAbsPath(envPath, mockSpecJSON)
specData, err := generateSpecData(mockSpecJSONURI)
if err != nil {
t.Fatalf("Expected to marshal:\n%s\n, but failed", mockSpecJSONURI)
}
err = afero.WriteFile(testFS, string(specPath), specData, os.ModePerm)
if err != nil {
t.Fatalf("Could not write file at path: %s", specPath)
}
testDirExists(t, string(envPath))
}
defaultEnvPath := appendToAbsPath(appPath, string(defaultEnvDir))
anotherEnvPath := appendToAbsPath(appPath, string(anotherEnvDir))
specDefaultEnvPath := appendToAbsPath(defaultEnvPath, mockSpecJSON)
specAnotherEnvPath := appendToAbsPath(anotherEnvPath, mockSpecJSON)
return m
}
specData, err := generateSpecData(mockSpecJSONURI)
func testDirExists(t *testing.T, path string) {
exists, err := afero.DirExists(testFS, path)
if err != nil {
t.Fatalf("Expected to marshal:\n%s\n, but failed", mockSpecJSONURI)
t.Fatalf("Expected directory at '%s' to exist, but failed:\n%v", path, err)
} else if !exists {
t.Fatalf("Expected directory at '%s' to exist, but it does not", path)
}
}
func testDirNotExists(t *testing.T, path string) {
exists, err := afero.DirExists(testFS, path)
if err != nil {
t.Fatalf("Expected directory at '%s' to be removed, but failed:\n%v", path, err)
} else if exists {
t.Fatalf("Expected directory at '%s' to be removed, but it exists", path)
}
}
func TestDeleteEnvironment(t *testing.T) {
appName := "test-delete-envs"
m := mockEnvironments(t, appName)
err = afero.WriteFile(testFS, string(specDefaultEnvPath), specData, os.ModePerm)
// Test that both directory and empty parent directory is deleted.
expectedPath := appendToAbsPath(m.environmentsDir, mockEnvName3)
parentDir := strings.Split(mockEnvName3, "/")[0]
expectedParentPath := appendToAbsPath(m.environmentsDir, parentDir)
err := m.DeleteEnvironment(mockEnvName3)
if err != nil {
t.Fatalf("Could not write file at path: %s", specDefaultEnvPath)
t.Fatalf("Expected %s to be deleted but got err:\n %s", mockEnvName3, err)
}
err = afero.WriteFile(testFS, string(specAnotherEnvPath), specData, os.ModePerm)
testDirNotExists(t, string(expectedPath))
testDirNotExists(t, string(expectedParentPath))
// Test that only leaf directory is deleted if parent directory is shared
expectedPath = appendToAbsPath(m.environmentsDir, mockEnvName2)
parentDir = strings.Split(mockEnvName2, "/")[0]
expectedParentPath = appendToAbsPath(m.environmentsDir, parentDir)
err = m.DeleteEnvironment(mockEnvName2)
if err != nil {
t.Fatalf("Could not write file at path: %s", specAnotherEnvPath)
t.Fatalf("Expected %s to be deleted but got err:\n %s", mockEnvName3, err)
}
testDirNotExists(t, string(expectedPath))
testDirExists(t, string(expectedParentPath))
}
func TestGetEnvironments(t *testing.T) {
m := mockEnvironments(t, "test-get-envs")
envs, err := m.GetEnvironments()
if err != nil {
t.Fatalf("Expected to successfully get environments but failed:\n %s", err)
}
if len(envs) != 2 {
t.Fatalf("Expected to get %d environments, got %d", 2, len(envs))
if len(envs) != 4 {
t.Fatalf("Expected to get %d environments, got %d", 4, len(envs))
}
if envs[0].URI != mockSpecJSONURI {
......
......@@ -38,6 +38,7 @@ type Manager interface {
LibPaths(envName string) (libPath, envLibPath AbsPath)
GenerateKsonnetLibData(spec ClusterSpec) ([]byte, []byte, error)
CreateEnvironment(name, uri string, spec ClusterSpec, extensionsLibData, k8sLibData []byte) error
DeleteEnvironment(name string) error
GetEnvironments() ([]Environment, error)
//
// TODO: Fill in methods as we need them.
......@@ -45,7 +46,6 @@ type Manager interface {
// GetPrototype(id string) Protoype
// SearchPrototypes(query string) []Protoype
// VendorLibrary(uri, version string) error
// DeleteEnv(name string) error
//
}
......
......@@ -55,6 +55,27 @@ func (c *EnvAddCmd) Run() error {
// ==================================================================
type EnvRmCmd struct {
name string
rootPath metadata.AbsPath
}
func NewEnvRmCmd(name string, rootPath metadata.AbsPath) (*EnvRmCmd, error) {
return &EnvRmCmd{name: name, rootPath: rootPath}, nil
}
func (c *EnvRmCmd) Run() error {
manager, err := metadata.Find(c.rootPath)
if err != nil {
return err
}
return manager.DeleteEnvironment(c.name)
}
// ==================================================================
type EnvListCmd struct {
rootPath metadata.AbsPath
}
......
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment