Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add the alpha <resource> delete command #2321

Merged
merged 1 commit into from
Jan 22, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading