Skip to content

Commit

Permalink
Add the alpha <resource> delete command (#2321)
Browse files Browse the repository at this point in the history
  • Loading branch information
pPrecel authored Jan 22, 2025
1 parent 5ea93f7 commit e7fa138
Show file tree
Hide file tree
Showing 11 changed files with 303 additions and 59 deletions.
1 change: 1 addition & 0 deletions internal/cmd/alpha/alpha.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ func NewAlphaCMD() (*cobra.Command, clierror.Error) {
// list of template commands deffinitions
Explain: templates.BuildExplainCommand,
Create: templates.BuildCreateCommand,
Delete: templates.BuildDeleteCommand,
}, cmdcommon.CoreCommandsMap{
// map of available core commands
"registry_config": config.NewConfigCMD,
Expand Down
56 changes: 4 additions & 52 deletions internal/cmd/alpha/templates/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import (
"fmt"
"io"
"os"
"strings"

"github.com/kyma-project/cli.v3/internal/clierror"
"github.com/kyma-project/cli.v3/internal/cmd/alpha/templates/parameters"
Expand Down Expand Up @@ -46,7 +45,7 @@ func buildCreateCommand(out io.Writer, clientGetter KubeClientGetter, createOpti
},
}

flags := append(createOptions.CustomFlags, buildDefaultFlags(createOptions.ResourceInfo.Scope)...)
flags := append(createOptions.CustomFlags, commonResourceFlags(createOptions.ResourceInfo.Scope)...)
for _, flag := range flags {
value := parameters.NewTyped(flag.Type, flag.Path, flag.DefaultValue)
cmd.Flags().VarP(value, flag.Name, flag.Shorthand, flag.Description)
Expand Down Expand Up @@ -80,25 +79,9 @@ func createResource(args *createArgs) clierror.Error {
return clierr
}

for _, extraValue := range args.extraValues {
value := extraValue.GetValue()
if value == nil {
// value is not set and has no default value
continue
}

fields := strings.Split(
// remove optional dot at the beginning of the path
strings.TrimPrefix(extraValue.GetPath(), "."),
".",
)

err := unstructured.SetNestedField(u.Object, value, fields...)
if err != nil {
return clierror.Wrap(err, clierror.New(
fmt.Sprintf("failed to set value %v for path %s", value, extraValue.GetPath()),
))
}
clierr = setExtraValues(u, args.extraValues)
if clierr != nil {
return clierr
}

err := client.RootlessDynamic().Apply(args.ctx, u)
Expand All @@ -109,34 +92,3 @@ func createResource(args *createArgs) clierror.Error {
fmt.Fprintf(args.out, "resource %s applied\n", getResourceName(args.createOptions.ResourceInfo.Scope, u))
return nil
}

func buildDefaultFlags(resourceScope types.Scope) []types.CreateCustomFlag {
params := []types.CreateCustomFlag{
{
Name: "name",
Type: types.StringCustomFlagType,
Description: "name of the resource",
Path: ".metadata.name",
Required: true,
},
}
if resourceScope == types.NamespaceScope {
params = append(params, types.CreateCustomFlag{
Name: "namespace",
Type: types.StringCustomFlagType,
Description: "resource namespace",
Path: ".metadata.namespace",
DefaultValue: "default",
})
}

return params
}

func getResourceName(scope types.Scope, u *unstructured.Unstructured) string {
if scope == types.NamespaceScope {
return fmt.Sprintf("%s/%s", u.GetNamespace(), u.GetName())
}

return u.GetName()
}
2 changes: 1 addition & 1 deletion internal/cmd/alpha/templates/create_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ func fixCreateOptions() *CreateOptions {
CreateCommand: types.CreateCommand{
Description: "create test deploy",
DescriptionLong: "use this to create test deploy",
CustomFlags: []types.CreateCustomFlag{
CustomFlags: []types.CustomFlag{
{
Type: types.IntCustomFlagType,
Name: "replicas",
Expand Down
89 changes: 89 additions & 0 deletions internal/cmd/alpha/templates/delete.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
package templates

import (
"context"
"fmt"
"io"
"os"

"github.com/kyma-project/cli.v3/internal/clierror"
"github.com/kyma-project/cli.v3/internal/cmd/alpha/templates/parameters"
"github.com/kyma-project/cli.v3/internal/cmd/alpha/templates/types"
"github.com/spf13/cobra"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/schema"
)

type DeleteOptions struct {
types.DeleteCommand
ResourceInfo types.ResourceInfo
}

func BuildDeleteCommand(clientGetter KubeClientGetter, options *DeleteOptions) *cobra.Command {
return buildDeleteCommand(os.Stdout, clientGetter, options)
}

func buildDeleteCommand(out io.Writer, clientGetter KubeClientGetter, options *DeleteOptions) *cobra.Command {
extraValues := []parameters.Value{}
cmd := &cobra.Command{
Use: "delete",
Short: options.Description,
Long: options.DescriptionLong,
Run: func(cmd *cobra.Command, args []string) {
clierror.Check(deleteResource(&deleteArgs{
out: out,
ctx: cmd.Context(),
deleteOptions: options,
clientGetter: clientGetter,
extraValues: extraValues,
}))
},
}

for _, flag := range commonResourceFlags(options.ResourceInfo.Scope) {
value := parameters.NewTyped(flag.Type, flag.Path, flag.DefaultValue)
cmd.Flags().VarP(value, flag.Name, flag.Shorthand, flag.Description)
if flag.Required {
_ = cmd.MarkFlagRequired(flag.Name)
}
extraValues = append(extraValues, value)
}

return cmd
}

type deleteArgs struct {
out io.Writer
ctx context.Context
deleteOptions *DeleteOptions
clientGetter KubeClientGetter
extraValues []parameters.Value
}

func deleteResource(args *deleteArgs) clierror.Error {
u := &unstructured.Unstructured{}
u.SetGroupVersionKind(schema.GroupVersionKind{
Group: args.deleteOptions.ResourceInfo.Group,
Version: args.deleteOptions.ResourceInfo.Version,
Kind: args.deleteOptions.ResourceInfo.Kind,
})

client, clierr := args.clientGetter.GetKubeClientWithClierr()
if clierr != nil {
return clierr
}

clierr = setExtraValues(u, args.extraValues)
if clierr != nil {
return clierr
}

err := client.RootlessDynamic().Remove(args.ctx, u)
if err != nil {
return clierror.Wrap(err, clierror.New("failed to delete resource"))
}

fmt.Fprintf(args.out, "resource %s deleted\n", getResourceName(args.deleteOptions.ResourceInfo.Scope, u))

return nil
}
117 changes: 117 additions & 0 deletions internal/cmd/alpha/templates/delete_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
package templates

import (
"bytes"
"context"
"errors"
"io"
"testing"

"github.com/kyma-project/cli.v3/internal/clierror"
"github.com/kyma-project/cli.v3/internal/cmd/alpha/templates/types"
"github.com/kyma-project/cli.v3/internal/kube/fake"
"github.com/spf13/cobra"
"github.com/stretchr/testify/require"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
)

func Test_remove(t *testing.T) {
t.Run("build proper command", func(t *testing.T) {
cmd := fixDeleteCommand(bytes.NewBuffer([]byte{}), &mockGetter{})

require.Equal(t, "delete", cmd.Use)
require.Equal(t, "delete test deploy", cmd.Short)
require.Equal(t, "use this to delete test deploy", cmd.Long)

require.NotNil(t, cmd.Flag("name"))
require.NotNil(t, cmd.Flag("namespace"))
})

t.Run("delete resource", func(t *testing.T) {
buf := bytes.NewBuffer([]byte{})
fakeClient := &fake.RootlessDynamicClient{}
mock := mockGetter{
client: &fake.KubeClient{
TestRootlessDynamicInterface: fakeClient,
},
}

cmd := fixDeleteCommand(buf, &mock)

cmd.SetArgs([]string{"--name", "test-deploy", "--namespace", "test-namespace"})

err := cmd.Execute()
require.NoError(t, err)

require.Len(t, fakeClient.RemovedObjs, 1)
require.Equal(t, fixDeletedUnstructuredDeployment(), fakeClient.RemovedObjs[0])
})

t.Run("failed to get client", func(t *testing.T) {
buf := bytes.NewBuffer([]byte{})
mock := mockGetter{
clierror: clierror.New("test error"),
client: nil,
}

err := deleteResource(&deleteArgs{
out: buf,
ctx: context.Background(),
clientGetter: &mock,
deleteOptions: fixDeleteOptions(),
})
require.Equal(t, clierror.New("test error"), err)
})

t.Run("failed to delete object", func(t *testing.T) {
buf := bytes.NewBuffer([]byte{})
fakeClient := &fake.RootlessDynamicClient{
ReturnRemoveErr: errors.New("test error"),
}
mock := mockGetter{
client: &fake.KubeClient{
TestRootlessDynamicInterface: fakeClient,
},
}

err := deleteResource(&deleteArgs{
out: buf,
ctx: context.Background(),
clientGetter: &mock,
deleteOptions: fixDeleteOptions(),
})
require.Equal(t, clierror.Wrap(errors.New("test error"), clierror.New("failed to delete resource")), err)
})
}

func fixDeleteCommand(writer io.Writer, getter KubeClientGetter) *cobra.Command {
return buildDeleteCommand(writer, getter, fixDeleteOptions())
}

func fixDeleteOptions() *DeleteOptions {
return &DeleteOptions{
DeleteCommand: types.DeleteCommand{
Description: "delete test deploy",
DescriptionLong: "use this to delete test deploy",
},
ResourceInfo: types.ResourceInfo{
Scope: types.NamespaceScope,
Kind: "Deployment",
Group: "apps",
Version: "v1",
},
}
}

func fixDeletedUnstructuredDeployment() unstructured.Unstructured {
return unstructured.Unstructured{
Object: map[string]interface{}{
"apiVersion": "apps/v1",
"kind": "Deployment",
"metadata": map[string]interface{}{
"name": "test-deploy",
"namespace": "test-namespace",
},
},
}
}
67 changes: 67 additions & 0 deletions internal/cmd/alpha/templates/templates.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package templates

import (
"fmt"
"strings"

"github.com/kyma-project/cli.v3/internal/clierror"
"github.com/kyma-project/cli.v3/internal/cmd/alpha/templates/parameters"
"github.com/kyma-project/cli.v3/internal/cmd/alpha/templates/types"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
)

func setExtraValues(u *unstructured.Unstructured, extraValues []parameters.Value) clierror.Error {
for _, extraValue := range extraValues {
value := extraValue.GetValue()
if value == nil {
// value is not set and has no default value
continue
}

fields := strings.Split(
// remove optional dot at the beginning of the path
strings.TrimPrefix(extraValue.GetPath(), "."),
".",
)

err := unstructured.SetNestedField(u.Object, value, fields...)
if err != nil {
return clierror.Wrap(err, clierror.New(
fmt.Sprintf("failed to set value %v for path %s", value, extraValue.GetPath()),
))
}
}

return nil
}

func commonResourceFlags(resourceScope types.Scope) []types.CustomFlag {
params := []types.CustomFlag{
{
Name: "name",
Type: types.StringCustomFlagType,
Description: "name of the resource",
Path: ".metadata.name",
Required: true,
},
}
if resourceScope == types.NamespaceScope {
params = append(params, types.CustomFlag{
Name: "namespace",
Type: types.StringCustomFlagType,
Description: "resource namespace",
Path: ".metadata.namespace",
DefaultValue: "default",
})
}

return params
}

func getResourceName(scope types.Scope, u *unstructured.Unstructured) string {
if scope == types.NamespaceScope {
return fmt.Sprintf("%s/%s", u.GetNamespace(), u.GetName())
}

return u.GetName()
}
4 changes: 2 additions & 2 deletions internal/cmd/alpha/templates/types/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ type CreateCommand struct {
// long description of the command group
DescriptionLong string `yaml:"descriptionLong"`
// custom flags used to build command and set values for specific fields
CustomFlags []CreateCustomFlag `yaml:"customFlags"`
CustomFlags []CustomFlag `yaml:"customFlags"`
}

type CreateCustomFlagType string
Expand All @@ -17,7 +17,7 @@ var (
IntCustomFlagType CreateCustomFlagType = "int64"
)

type CreateCustomFlag struct {
type CustomFlag struct {
// type of the custom flag
Type CreateCustomFlagType `yaml:"type"`
// name of the custom flag
Expand Down
Loading

0 comments on commit e7fa138

Please sign in to comment.