diff --git a/docs/user-guide/how-to.md b/docs/user-guide/how-to.md index dbc335f3f..2deb42794 100644 --- a/docs/user-guide/how-to.md +++ b/docs/user-guide/how-to.md @@ -236,6 +236,12 @@ Instead, you must delete its anchor (note that `subns` is a short form of $ kubectl delete subns child -n parent ``` +You can also use the `kubectl-hns` plugin to delete subnamespaces. + +``` +$ kubectl hns delete child -n parent +``` + This _seems_ to imply that if you delete a _parent_ namespace, all its subnamespace children (and their descendants) will be deleted as well, since all objects in a namespace (such as anchors) are deleted along with the namespace. diff --git a/docs/user-guide/quickstart.md b/docs/user-guide/quickstart.md index 7302c33cb..d1212dace 100644 --- a/docs/user-guide/quickstart.md +++ b/docs/user-guide/quickstart.md @@ -615,6 +615,11 @@ kubectl delete subns service-3 -n team-a Note that `subns` is a short form for `subnamespaceanchor` or `subnamespaceanchor.hnc.x-k8s.io`. +You can also use the `kubectl-hns` plugin to delete subnamespaces. + +```bash +kubectl hns delete service-3 -n team-a +``` Now try to delete `service-1` in the same way, but you'll see it doesn't work: @@ -675,6 +680,12 @@ team-a [s] indicates subnamespaces ``` +You can also do the above steps in one command run with the `kubectl-hns` plugin: + +```bash +kubectl hns delete service-1 -n team-a --allowCascadingDeletion +``` + There's an important difference between subnamespaces and regular child namespace, also known as a full namespace. A subnamespace is created by HNC due to an anchor being created in the parent; when that anchor is deleted, the diff --git a/internal/kubectl/delete.go b/internal/kubectl/delete.go new file mode 100644 index 000000000..e92ba7f27 --- /dev/null +++ b/internal/kubectl/delete.go @@ -0,0 +1,63 @@ +/* + +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 kubectl + +import ( + "fmt" + "os" + + "github.com/spf13/cobra" +) + +var deleteCmd = &cobra.Command{ + Use: "delete -n PARENT CHILD", + Short: "Deletes a subnamespace under the given parent.", + Example: `# Delete the 'foo' anchor in the parent 'bar' namespace + kubectl hns delete foo -n bar + + # Allow 'foo' to be cascading deleted and delete it + kubectl hns delete foo -n bar --allowCascadingDeletion`, + Args: cobra.ExactArgs(1), + Run: func(cmd *cobra.Command, args []string) { + nnm := args[0] + + parent, _ := cmd.Flags().GetString("namespace") + if parent == "" { + fmt.Println("Error: parent must be set via --namespace or -n") + os.Exit(1) + } + + allowCD, _ := cmd.Flags().GetBool("allowCascadingDeletion") + if allowCD { + hc := client.getHierarchy(nnm) + if hc.Spec.AllowCascadingDeletion { + fmt.Printf("Cascading deletion for '%s' is already set to 'true'; unchanged\n", nnm) + } else { + fmt.Printf("Allowing cascading deletion on '%s'\n", nnm) + hc.Spec.AllowCascadingDeletion = true + client.updateHierarchy(hc, fmt.Sprintf("update the hierarchical configuration of %s", nnm)) + } + } + + client.deleteAnchor(parent, nnm) + }, +} + +func newDeleteCmd() *cobra.Command { + deleteCmd.Flags().StringP("namespace", "n", "", "The parent namespace for the new subnamespace") + deleteCmd.Flags().BoolP("allowCascadingDeletion", "a", false, "Allows cascading deletion of its subnamespaces.") + return deleteCmd +} diff --git a/internal/kubectl/root.go b/internal/kubectl/root.go index b43c57f43..8e35e1ece 100644 --- a/internal/kubectl/root.go +++ b/internal/kubectl/root.go @@ -49,6 +49,7 @@ type Client interface { getHierarchy(nnm string) *api.HierarchyConfiguration updateHierarchy(hier *api.HierarchyConfiguration, reason string) createAnchor(nnm string, hnnm string) + deleteAnchor(nnm string, hnnm string) getAnchorStatus(nnm string) anchorStatus getHNCConfig() *api.HNCConfiguration updateHNCConfig(*api.HNCConfiguration) @@ -102,6 +103,7 @@ func init() { rootCmd.AddCommand(newDescribeCmd()) rootCmd.AddCommand(newTreeCmd()) rootCmd.AddCommand(newCreateCmd()) + rootCmd.AddCommand(newDeleteCmd()) rootCmd.AddCommand(newConfigCmd()) rootCmd.AddCommand(newVersionCmd()) rootCmd.AddCommand(newHrqCmd()) @@ -186,6 +188,18 @@ func (cl *realClient) createAnchor(nnm string, hnnm string) { fmt.Printf("Successfully created %q subnamespace anchor in %q namespace\n", hnnm, nnm) } +func (cl *realClient) deleteAnchor(nnm string, hnnm string) { + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + + err := hncClient.Delete().Resource(api.Anchors).Namespace(nnm).Name(hnnm).Do(ctx).Error() + if err != nil { + fmt.Printf("\nCould not delete subnamespace anchor.\nReason: %s\n", err) + os.Exit(1) + } + fmt.Printf("Successfully deleted %q subnamespace anchor in %q namespace\n", hnnm, nnm) +} + func (cl *realClient) getHNCConfig() *api.HNCConfiguration { ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() diff --git a/test/e2e/quickstart_test.go b/test/e2e/quickstart_test.go index 78686a69b..288600a69 100644 --- a/test/e2e/quickstart_test.go +++ b/test/e2e/quickstart_test.go @@ -253,6 +253,12 @@ spec: // show how to delete a subns correctly MustNotRun("kubectl delete ns", nsService3) MustRun("kubectl delete subns", nsService3, "-n", nsTeamA) + + // show how to delete a subns correctly with the kubectl-hns plugin + CreateSubnamespace(nsService4, nsTeamA) + MustNotRun("kubectl hns delete", nsService4) + MustRun("kubectl hns delete", nsService4, "-n", nsTeamA) + // This should not run because service-1 contains its own subnamespace that would be deleted with it, MustNotRun("kubectl delete subns", nsService1, "-n", nsTeamA) @@ -263,6 +269,21 @@ spec: "└── [s] " + nsService2 RunShouldContain(expected, defTimeout, "kubectl hns tree", nsTeamA) + // cascading deletion with the kubectl-hns plugin + CreateSubnamespace(nsService1, nsTeamA) + CreateSubnamespace(nsService4, nsService1) + expected = "" + + nsService1 + "\n" + + "└── [s] " + nsService4 + RunShouldContain(expected, defTimeout, "kubectl hns tree", nsService1, "-n", nsTeamA) + MustNotRun("kubectl hns delete", nsService1, "-n", nsTeamA) + MustRun("kubectl hns delete", nsService1, "-n", nsTeamA, "--allowCascadingDeletion") + expected = "" + + nsTeamA + "\n" + + "└── [s] " + nsService2 + // cascading deletion of its subnamespaces takes time + RunShouldContain(expected, cleanupTimeout, "kubectl hns tree", nsTeamA) + // Show the difference of a subns and regular child ns CreateSubnamespace(nsService4, nsTeamA) expected = "" +