From 385da08e76ee60a3dbcaa2b1d52234539eb47785 Mon Sep 17 00:00:00 2001 From: Jessica Yuen <im.jessicayuen@gmail.com> Date: Thu, 16 Nov 2017 14:35:26 -0800 Subject: [PATCH] Support renaming of envs to parent & child directories Currently, there are limitations around the file system we are using that does not easily allow renaming of `us-west/prod` to `us-west`, or vice versa - `us-west` to `us-west/prod`. This commit will handle the logic to allow for that by moving the file contents. --- metadata/environment.go | 81 +++++++++++++++++++++++++++++------- metadata/environment_test.go | 36 +++++++++++++++- 2 files changed, 102 insertions(+), 15 deletions(-) diff --git a/metadata/environment.go b/metadata/environment.go index b180e37a..6463f4f9 100644 --- a/metadata/environment.go +++ b/metadata/environment.go @@ -47,6 +47,17 @@ const ( specFilename = "spec.json" ) +var envPaths = []string{ + // metadata Dir.wh + metadataDirName, + // environment base override file + envFileName, + // params file + paramsFileName, + // spec file + specFilename, +} + // Environment represents all fields of a ksonnet environment type Environment struct { Path string @@ -292,22 +303,36 @@ func (m *manager) SetEnvironment(name string, desired *Environment) error { if err != nil { return err } + if exists { - return fmt.Errorf("Failed to create environment, directory exists at path: %s", string(pathNew)) - } - // Need to first create subdirectories that don't exist - intermediatePath := path.Dir(string(pathNew)) - log.Debugf("Moving directory at path '%s' to '%s'", string(pathOld), string(pathNew)) - err = m.appFS.MkdirAll(intermediatePath, defaultFolderPermissions) - if err != nil { - return err - } - // finally, move the directory - err = m.appFS.Rename(string(pathOld), string(pathNew)) - if err != nil { - log.Debugf("Failed to move path '%s' to '%s", string(pathOld), string(pathNew)) - return err + // we know that the desired path is not an environment from + // the check earlier. This is an intermediate directory. + // We need to move the file contents. + m.tryMvEnvDir(pathOld, pathNew) + } else if filepath.HasPrefix(string(pathNew), string(pathOld)) { + // the new directory is a child of the old directory -- + // rename won't work. + err = m.appFS.MkdirAll(string(pathNew), defaultFolderPermissions) + if err != nil { + return err + } + m.tryMvEnvDir(pathOld, pathNew) + } else { + // Need to first create subdirectories that don't exist + intermediatePath := path.Dir(string(pathNew)) + log.Debugf("Moving directory at path '%s' to '%s'", string(pathOld), string(pathNew)) + err = m.appFS.MkdirAll(intermediatePath, defaultFolderPermissions) + if err != nil { + return err + } + // finally, move the directory + err = m.appFS.Rename(string(pathOld), string(pathNew)) + if err != nil { + log.Debugf("Failed to move path '%s' to '%s", string(pathOld), string(pathNew)) + return err + } } + // clean up any empty parent directory paths err = m.cleanEmptyParentDirs(name) if err != nil { @@ -414,6 +439,34 @@ func (m *manager) SetEnvironmentParams(env, component string, params param.Param return nil } +func (m *manager) tryMvEnvDir(dirPathOld, dirPathNew AbsPath) error { + // first ensure none of these paths exists in the new directory + for _, p := range envPaths { + path := string(appendToAbsPath(dirPathNew, p)) + if exists, err := afero.Exists(m.appFS, path); err != nil { + return err + } else if exists { + return fmt.Errorf("%s already exists", path) + } + } + + // note: afero and go does not provide simple ways to move the + // contents. We'll have to rename them individually. + for _, p := range envPaths { + err := m.appFS.Rename(string(appendToAbsPath(dirPathOld, p)), string(appendToAbsPath(dirPathNew, p))) + if err != nil { + return err + } + } + // clean up the old directory if it is empty + if empty, err := afero.IsEmpty(m.appFS, string(dirPathOld)); err != nil { + return err + } else if empty { + return m.appFS.RemoveAll(string(dirPathOld)) + } + return nil +} + func (m *manager) cleanEmptyParentDirs(name string) error { // clean up any empty parent directory paths log.Debug("Removing empty parent directories, if any") diff --git a/metadata/environment_test.go b/metadata/environment_test.go index 5d90a299..128a7526 100644 --- a/metadata/environment_test.go +++ b/metadata/environment_test.go @@ -36,8 +36,13 @@ const ( var mockAPIServer = "http://example.com" var mockNamespace = "some-namespace" +var mockEnvs = []string{defaultEnvName, mockEnvName, mockEnvName2, mockEnvName3} func mockEnvironments(t *testing.T, appName string) *manager { + return mockEnvironmentsWith(t, appName, mockEnvs) +} + +func mockEnvironmentsWith(t *testing.T, appName string, envNames []string) *manager { spec, err := parseClusterSpec(fmt.Sprintf("file:%s", blankSwagger), testFS) if err != nil { t.Fatalf("Failed to parse cluster spec: %v", err) @@ -50,7 +55,6 @@ func mockEnvironments(t *testing.T, appName string) *manager { t.Fatalf("Failed to init cluster spec: %v", err) } - envNames := []string{defaultEnvName, mockEnvName, mockEnvName2, mockEnvName3} for _, env := range envNames { envPath := appendToAbsPath(m.environmentsPath, env) testFS.Mkdir(string(envPath), defaultFolderPermissions) @@ -226,6 +230,36 @@ func TestSetEnvironment(t *testing.T) { if envSpec.Namespace != set.Namespace { t.Fatalf("Expected namespace to be set to '%s', got: '%s'", set.Namespace, envSpec.Namespace) } + + tests := []struct { + appName string + nameOld string + nameNew string + }{ + // Test changing the name of an env 'us-west' to 'us-west/dev' + { + "test-set-to-child", + "us-west", + "us-west/dev", + }, + // Test changing the name of an env 'us-west/dev' to 'us-west' + { + "test-set-to-parent", + "us-west/dev", + "us-west", + }, + } + + for _, v := range tests { + m = mockEnvironmentsWith(t, v.appName, []string{v.nameOld}) + err = m.SetEnvironment(v.nameOld, &Environment{Name: v.nameNew}) + if err != nil { + t.Fatalf("Could not set '%s', got:\n %s", v.nameOld, err) + } + // Ensure new env directory is created + expectedPath := appendToAbsPath(AbsPath(v.appName), environmentsDir, v.nameNew) + testDirExists(t, string(expectedPath)) + } } func TestGenerateOverrideData(t *testing.T) { -- GitLab