Skip to content

Commit

Permalink
fix: delete command supports kinds that do not match singular/plural …
Browse files Browse the repository at this point in the history
…names (#1236)

fix: delete command supports Kinds that do not match singular/plural names

If a resource has the kind "HiThere" but its singular is "hi-there" then
deletion was not possible since Tanka operated on the kind and not the
singular/plural defined in the CRD.

This changes the handling to following the `TYPE.VERSION.GROUP` format
when possible.
  • Loading branch information
zerok authored Nov 22, 2024
1 parent 5dcb6c5 commit bf702ef
Show file tree
Hide file tree
Showing 4 changed files with 69 additions and 15 deletions.
2 changes: 1 addition & 1 deletion pkg/kubernetes/client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ type Client interface {
DiffServerSide(data manifest.List) (*string, error)

// Delete the specified object(s) from the cluster
Delete(namespace, kind, name string, opts DeleteOpts) error
Delete(namespace, apiVersion, kind, name string, opts DeleteOpts) error

// Namespaces the cluster currently has
Namespaces() (map[string]bool, error)
Expand Down
42 changes: 38 additions & 4 deletions pkg/kubernetes/client/delete.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,33 @@ import (
"os"
"os/exec"
"strings"

"github.com/rs/zerolog/log"
)

func buildFullType(group, version, kind string) string {
output := strings.Builder{}
output.WriteString(kind)
// Unfortunately, kubectl does not support `Type.Version` for things like
// `Service` in v1. In this case, we cannot provide anything but the kind
// name:
if version != "" && group != "" {
output.WriteString(".")
output.WriteString(version)
output.WriteString(".")
output.WriteString(group)
}
return output.String()
}

// Test-ability: isolate deleteCtl to build and return exec.Cmd from DeleteOpts
func (k Kubectl) deleteCtl(namespace, kind, name string, opts DeleteOpts) *exec.Cmd {
func (k Kubectl) deleteCtl(namespace, group, version, kind, name string, opts DeleteOpts) *exec.Cmd {
fullType := buildFullType(group, version, kind)
argv := []string{
"-n", namespace,
kind, name,
fullType, name,
}
log.Debug().Str("name", name).Str("group", group).Str("version", version).Str("kind", kind).Str("namespace", namespace).Msg("Preparing to delete")
if opts.Force {
argv = append(argv, "--force")
}
Expand All @@ -27,8 +46,22 @@ func (k Kubectl) deleteCtl(namespace, kind, name string, opts DeleteOpts) *exec.
}

// Delete deletes the given Kubernetes resource from the cluster
func (k Kubectl) Delete(namespace, kind, name string, opts DeleteOpts) error {
cmd := k.deleteCtl(namespace, kind, name, opts)
func (k Kubectl) Delete(namespace, apiVersion, kind, name string, opts DeleteOpts) error {
apiVersionElements := strings.SplitN(apiVersion, "/", 2)
if len(apiVersionElements) < 1 {
return fmt.Errorf("apiVersion does not follow the group/version or version format: %s", apiVersion)
}
var group string
var version string
if len(apiVersionElements) == 1 {
group = ""
version = apiVersionElements[0]
} else {
group = apiVersionElements[0]
version = apiVersionElements[1]
}

cmd := k.deleteCtl(namespace, group, version, kind, name, opts)

var stdout bytes.Buffer
var stderr bytes.Buffer
Expand All @@ -42,6 +75,7 @@ func (k Kubectl) Delete(namespace, kind, name string, opts DeleteOpts) error {
print("Delete failed: " + stderr.String())
return nil
}
log.Trace().Msgf("Delete failed: %s", stderr.String())
return err
}
if opts.DryRun != "" {
Expand Down
38 changes: 29 additions & 9 deletions pkg/kubernetes/client/delete_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,12 @@ func TestKubectl_deleteCtl(t *testing.T) {
}

type args struct {
ns string
kind string
name string
opts DeleteOpts
ns string
group string
version string
kind string
name string
opts DeleteOpts
}

tests := []struct {
Expand All @@ -31,10 +33,28 @@ func TestKubectl_deleteCtl(t *testing.T) {
{
name: "test default",
args: args{
ns: "foo-ns",
kind: "deploy",
name: "foo-deploy",
opts: DeleteOpts{},
ns: "foo-ns",
group: "example.org",
version: "v1",
kind: "deploy",
name: "foo-deploy",
opts: DeleteOpts{},
},
expectedArgs: []string{"--context", info.Kubeconfig.Context.Name, "-n", "foo-ns", "deploy.v1.example.org", "foo-deploy"},
unExpectedArgs: []string{"--force", "--dry-run=server"},
},
{
name: "test no apiVersion group",
args: args{
ns: "foo-ns",
// Since there is no group, we should also not include the version since
// kubectl does not support something like `Service.v1` or
// `Service.v1.core`:
group: "",
version: "v1",
kind: "deploy",
name: "foo-deploy",
opts: DeleteOpts{},
},
expectedArgs: []string{"--context", info.Kubeconfig.Context.Name, "-n", "foo-ns", "deploy", "foo-deploy"},
unExpectedArgs: []string{"--force", "--dry-run=server"},
Expand All @@ -60,7 +80,7 @@ func TestKubectl_deleteCtl(t *testing.T) {
k := Kubectl{
info: info,
}
got := k.deleteCtl(tt.args.ns, tt.args.kind, tt.args.name, tt.args.opts)
got := k.deleteCtl(tt.args.ns, tt.args.group, tt.args.version, tt.args.kind, tt.args.name, tt.args.opts)
gotSet := sets.NewString(got.Args...)
if !gotSet.HasAll(tt.expectedArgs...) {
t.Errorf("Kubectl.applyCtl() = %v doesn't have (all) expectedArgs='%v'", got.Args, tt.expectedArgs)
Expand Down
2 changes: 1 addition & 1 deletion pkg/kubernetes/delete.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ func (k *Kubernetes) Delete(state manifest.List, opts DeleteOpts) error {
}

for _, m := range state {
if err := k.ctl.Delete(m.Metadata().Namespace(), m.Kind(), m.Metadata().Name(), client.DeleteOpts(opts)); err != nil {
if err := k.ctl.Delete(m.Metadata().Namespace(), m.APIVersion(), m.Kind(), m.Metadata().Name(), client.DeleteOpts(opts)); err != nil {
return err
}
}
Expand Down

0 comments on commit bf702ef

Please sign in to comment.