-
Notifications
You must be signed in to change notification settings - Fork 82
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #186 from deviceinsight/feature/delete-records
add delete records command (fixes #183)
- Loading branch information
Showing
9 changed files
with
285 additions
and
30 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
package deletion | ||
|
||
import ( | ||
"github.com/deviceinsight/kafkactl/cmd/validation" | ||
"github.com/deviceinsight/kafkactl/internal/k8s" | ||
"github.com/deviceinsight/kafkactl/internal/topic" | ||
"github.com/deviceinsight/kafkactl/output" | ||
"github.com/spf13/cobra" | ||
) | ||
|
||
func newDeleteRecordsCmd() *cobra.Command { | ||
|
||
var flags topic.DeleteRecordsFlags | ||
|
||
var cmdDeleteRecords = &cobra.Command{ | ||
Use: "records TOPIC", | ||
Short: "delete a records from a topic", | ||
Args: cobra.ExactArgs(1), | ||
Run: func(cmd *cobra.Command, args []string) { | ||
if !k8s.NewOperation().TryRun(cmd, args) { | ||
if err := (&topic.Operation{}).DeleteRecords(args[0], flags); err != nil { | ||
output.Fail(err) | ||
} | ||
} | ||
}, | ||
ValidArgsFunction: topic.CompleteTopicNames, | ||
} | ||
|
||
cmdDeleteRecords.Flags().StringArrayVarP(&flags.Offsets, "offset", "", flags.Offsets, "offsets in format `partition=offset`. records with smaller offset will be deleted.") | ||
|
||
if err := validation.MarkFlagAtLeastOneRequired(cmdDeleteRecords.Flags(), "offset"); err != nil { | ||
panic(err) | ||
} | ||
|
||
return cmdDeleteRecords | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
package deletion_test | ||
|
||
import ( | ||
"strings" | ||
"testing" | ||
|
||
"github.com/deviceinsight/kafkactl/testutil" | ||
) | ||
|
||
func TestDeleteRecordsIntegration(t *testing.T) { | ||
|
||
testutil.StartIntegrationTest(t) | ||
|
||
topicName := testutil.CreateTopic(t, "delete-records-", "--partitions", "2") | ||
|
||
testutil.ProduceMessageOnPartition(t, topicName, "key-1", "a", 0, 0) | ||
testutil.ProduceMessageOnPartition(t, topicName, "key-1", "b", 0, 1) | ||
testutil.ProduceMessageOnPartition(t, topicName, "key-2", "c", 1, 0) | ||
testutil.ProduceMessageOnPartition(t, topicName, "key-2", "d", 1, 1) | ||
testutil.ProduceMessageOnPartition(t, topicName, "key-2", "e", 1, 2) | ||
|
||
kafkaCtl := testutil.CreateKafkaCtlCommand() | ||
|
||
// check initial messages | ||
if _, err := kafkaCtl.Execute("consume", topicName, "--from-beginning", "--print-keys", "--exit"); err != nil { | ||
t.Fatalf("failed to execute command: %v", err) | ||
} | ||
|
||
messages := strings.Split(strings.TrimSpace(kafkaCtl.GetStdOut()), "\n") | ||
|
||
if len(messages) != 5 { | ||
t.Fatalf("expected 5 messages, got %d", len(messages)) | ||
} | ||
|
||
// delete records | ||
if _, err := kafkaCtl.Execute("delete", "records", topicName, "--offset", "0=1", "--offset", "1=2"); err != nil { | ||
t.Fatalf("failed to execute command: %v", err) | ||
} | ||
|
||
// check messages | ||
if _, err := kafkaCtl.Execute("consume", topicName, "--from-beginning", "--print-keys", "--exit"); err != nil { | ||
t.Fatalf("failed to execute command: %v", err) | ||
} | ||
|
||
messages = strings.Split(strings.TrimSpace(kafkaCtl.GetStdOut()), "\n") | ||
|
||
if len(messages) != 2 { | ||
t.Fatalf("expected 2 messages, got %d", len(messages)) | ||
} | ||
|
||
testutil.AssertEquals(t, "key-1#b", messages[0]) | ||
testutil.AssertEquals(t, "key-2#e", messages[1]) | ||
} | ||
|
||
func TestDeleteRecordsAutoCompletionIntegration(t *testing.T) { | ||
|
||
testutil.StartIntegrationTest(t) | ||
|
||
prefix := "delete-complete-" | ||
|
||
topicName1 := testutil.CreateTopic(t, prefix+"a") | ||
topicName2 := testutil.CreateTopic(t, prefix+"b") | ||
topicName3 := testutil.CreateTopic(t, prefix+"c") | ||
|
||
kafkaCtl := testutil.CreateKafkaCtlCommand() | ||
kafkaCtl.Verbose = false | ||
|
||
if _, err := kafkaCtl.Execute("__complete", "delete", "records", ""); err != nil { | ||
t.Fatalf("failed to execute command: %v", err) | ||
} | ||
|
||
outputLines := strings.Split(strings.TrimSpace(kafkaCtl.GetStdOut()), "\n") | ||
|
||
testutil.AssertContains(t, topicName1, outputLines) | ||
testutil.AssertContains(t, topicName2, outputLines) | ||
testutil.AssertContains(t, topicName3, outputLines) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
package util | ||
|
||
import ( | ||
"math" | ||
"strconv" | ||
"strings" | ||
|
||
"github.com/pkg/errors" | ||
) | ||
|
||
const ErrOffset = math.MinInt64 | ||
const offsetSeparator = "=" | ||
|
||
func ParseOffsets(rawOffsets []string) (map[int32]int64, error) { | ||
|
||
offsets := make(map[int32]int64) | ||
|
||
for _, offsetFlag := range rawOffsets { | ||
offsetParts := strings.Split(offsetFlag, offsetSeparator) | ||
|
||
if len(offsetParts) != 2 { | ||
return nil, errors.Errorf("offset parameter has wrong format: %s %v", offsetFlag, rawOffsets) | ||
} | ||
|
||
partition, err := strconv.Atoi(offsetParts[0]) | ||
if err != nil { | ||
return nil, errors.Errorf("unable to parse offset parameter: %s (%v)", offsetFlag, err) | ||
} | ||
|
||
offset, err := strconv.ParseInt(offsetParts[1], 10, 64) | ||
if err != nil { | ||
return nil, errors.Errorf("unable to parse offset parameter: %s (%v)", offsetFlag, err) | ||
} | ||
|
||
offsets[int32(partition)] = offset | ||
} | ||
|
||
return offsets, nil | ||
} | ||
|
||
func ExtractOffsetForPartition(rawOffsets []string, currentPartition int32) (int64, error) { | ||
for _, offsetFlag := range rawOffsets { | ||
offsetParts := strings.Split(offsetFlag, offsetSeparator) | ||
|
||
if len(offsetParts) == 2 { | ||
|
||
partition, err := strconv.Atoi(offsetParts[0]) | ||
if err != nil { | ||
return ErrOffset, errors.Errorf("unable to parse offset parameter: %s (%v)", offsetFlag, err) | ||
} | ||
|
||
if int32(partition) != currentPartition { | ||
continue | ||
} | ||
|
||
offset, err := strconv.ParseInt(offsetParts[1], 10, 64) | ||
if err != nil { | ||
return ErrOffset, errors.Errorf("unable to parse offset parameter: %s (%v)", offsetFlag, err) | ||
} | ||
|
||
return offset, nil | ||
} | ||
} | ||
return ErrOffset, errors.Errorf("unable to find offset parameter for partition %d: %v", currentPartition, rawOffsets) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
package util_test | ||
|
||
import ( | ||
"reflect" | ||
"strings" | ||
"testing" | ||
|
||
"github.com/deviceinsight/kafkactl/util" | ||
) | ||
|
||
func TestParseOffsets(t *testing.T) { | ||
|
||
type testCases struct { | ||
description string | ||
input []string | ||
wantOffsets map[int32]int64 | ||
wantErr string | ||
} | ||
|
||
for _, test := range []testCases{ | ||
{ | ||
description: "successful_parsing", | ||
input: []string{"1=222", "2=333", "5=444"}, | ||
wantOffsets: map[int32]int64{1: 222, 2: 333, 5: 444}, | ||
}, | ||
{ | ||
description: "wrong_separator_fails", | ||
input: []string{"1:222"}, | ||
wantErr: "offset parameter has wrong format: 1:222 [1:222]", | ||
}, | ||
{ | ||
description: "partition_not_an_int_fails", | ||
input: []string{"abc=222"}, | ||
wantErr: "parsing \"abc\": invalid syntax", | ||
}, | ||
{ | ||
description: "offset_not_an_int_fails", | ||
input: []string{"1=nope"}, | ||
wantErr: "parsing \"nope\": invalid syntax", | ||
}, | ||
} { | ||
t.Run(test.description, func(t *testing.T) { | ||
|
||
offsets, err := util.ParseOffsets(test.input) | ||
|
||
if test.wantErr != "" { | ||
if err == nil { | ||
t.Errorf("want error %q but got nil", test.wantErr) | ||
} | ||
|
||
if !strings.Contains(err.Error(), test.wantErr) { | ||
t.Errorf("want error %q got %q", test.wantErr, err) | ||
} | ||
|
||
return | ||
} | ||
if err != nil { | ||
t.Errorf("doesn't want error but got %s", err) | ||
} | ||
|
||
if eq := reflect.DeepEqual(test.wantOffsets, offsets); !eq { | ||
t.Errorf("want %q got %q", test.wantOffsets, offsets) | ||
} | ||
}) | ||
} | ||
} |