Skip to content

Commit 6719faa

Browse files
committed
refactor!: use topics subcommand for topic management
BREAKING CHANGE: the existing subcommands belong to the topics command now
1 parent c5d1716 commit 6719faa

File tree

9 files changed

+127
-58
lines changed

9 files changed

+127
-58
lines changed

CMakeLists.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,11 +33,11 @@ if (SIMPLEINI_INCLUDE_DIRS)
3333
else ()
3434
message(FATAL_ERROR "SimpleIni not found")
3535
endif ()
36-
include_directories(${SIMPLEINI_INCLUDE_DIRS})
36+
include_directories("${CMAKE_SOURCE_DIR}/include" ${SIMPLEINI_INCLUDE_DIRS})
3737

3838
find_package(RdKafka CONFIG REQUIRED)
3939
find_package(argparse CONFIG REQUIRED)
4040

41-
add_executable(snctl-cpp main.cc)
41+
add_executable(snctl-cpp src/main.cc)
4242
target_compile_definitions(snctl-cpp PUBLIC VERSION_STR="${VERSION_STR}")
4343
target_link_libraries(snctl-cpp PRIVATE argparse::argparse RdKafka::rdkafka)

README.md

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@ The CLI tool to manage clusters on StreamNative Cloud.
66

77
### (Recommended) Use pre-built binaries
88

9-
Currently the release only supports macOS 14 or later with arm64 architecture.
9+
Currently the release only supports
10+
- macOS 14 or later: arm64 architecture only
11+
- Alpine Linux 3.21 or later: amd64 or amd64 architecture
1012

1113
Take v0.1.0 for example:
1214

@@ -25,6 +27,10 @@ export PATH=$HOME/.snctl-cpp:$PATH
2527

2628
Now, you can run `snctl-cpp -h` to see the help message.
2729

30+
> **NOTE**:
31+
>
32+
> Actually running `./install.sh` is not necessary. You can run `./snctl-cpp` directly in the uncompressed directory because `sncloud.ini` is in the same directory.
33+
2834
### Build from source
2935

3036
You must have a C++ compiler that supports C++17.
@@ -38,7 +44,7 @@ cp build/snctl-cpp .
3844

3945
You can run `./install.sh` to override the existing installation in `~/.snctl-cpp` directory, but you can also just run `./snctl-cpp` directly without installing it. The `sncloud.ini` file in the current working directory has higher priority than the one in `~/.snctl-cpp` directory.
4046

41-
## How to manage topics in the cluster that enables Kafka protocol
47+
## Configuration
4248

4349
**Please make sure the `sncloud.ini` file is in the current working directory or `~/.snctl-cpp` directory if you don't specify the `--config` option.**
4450

@@ -50,21 +56,25 @@ Options:
5056
- Add the `--config <config-file>` option to specify a different path of the INI config file.
5157
- Add a `--client-id` option to specify the client id of the underlying Kafka client. In Ursa, the client id carries the zone information, see [here](https://docs.streamnative.io/docs/config-kafka-client#eliminate-cross-az-networking-traffic).
5258

59+
The built-in `sncloud.ini` file specifies `localhost:9092` as the default bootstrap server. You can also test `snctl-cpp` against a local Kafka cluster.
60+
61+
## Commands
62+
5363
### Create a topic
5464

5565
```bash
56-
$ snctl-cpp create tp0
66+
$ snctl-cpp topics create tp0
5767
Created topic "tp0" with 1 partition
58-
$ snctl-cpp create tp1 -p 5
68+
$ snctl-cpp topics create tp1 -p 5
5969
Created topic "tp1" with 5 partitions
6070
```
6171

6272
### Delete a topic
6373

6474
```bash
65-
$ snctl-cpp delete tp
75+
$ snctl-cpp topics delete tp
6676
Failed to delete topic "tp": Broker: Unknown topic or partition
67-
$ snctl-cpp delete tp0
77+
$ snctl-cpp topics delete tp0
6878
Deleted topic "tp0"
6979
```
7080

@@ -73,7 +83,7 @@ Deleted topic "tp0"
7383
Query the owner brokers for all partitions:
7484

7585
```bash
76-
$ snctl-cpp describe <topic>
86+
$ snctl-cpp topics describe <topic>
7787
Partition[0] leader: {"id": 816909419, url: "pb0-<xxx>:9093"}"
7888
Partition[1] leader: {"id": 101337027, url: "pb4-<xxx>:9093"}"
7989
...
@@ -83,7 +93,7 @@ Partition[15] leader: {"id": 644587507, url: "pb2-<xxx>:9093"}"
8393
Query the owner brokers for all partitions in a specific zone (`use1-az1` in this case):
8494
8595
```bash
86-
$ snctl-cpp --client-id zone_id=use1-az1 describe <topic>
96+
$ snctl-cpp --client-id zone_id=use1-az1 topics describe <topic>
8797
Partition[0] leader: {"id": 1868363245, url: "pb5-<xxx>:9093"}
8898
Partition[1] leader: {"id": 1868363245, url: "pb5-<xxx>:9093"}
8999
...
@@ -97,7 +107,7 @@ As you can see, when a client specifies `use1-az1` as its zone, only brokers in
97107
List all topics and print the number of partitions for each topic:
98108
99109
```bash
100-
$ snctl-cpp list
110+
$ snctl-cpp topics list
101111
topic count: 2
102112
[0] "my-topic-2" with 1 partition
103113
[1] "my-topic-1" with 10 partitions

include/snctl-cpp/topics.h

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
/**
2+
* Copyright 2025 Yunze Xu
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
#pragma once
17+
18+
#include "snctl-cpp/topics/create_topic.h"
19+
#include "snctl-cpp/topics/delete_topic.h"
20+
#include "snctl-cpp/topics/describe_topic.h"
21+
#include "snctl-cpp/topics/list_topics.h"
22+
#include <argparse/argparse.hpp>
23+
#include <iostream>
24+
#include <librdkafka/rdkafka.h>
25+
26+
class Topics {
27+
public:
28+
Topics(argparse::ArgumentParser &parent) {
29+
create_command_.add_description("Create a topic");
30+
create_command_.add_argument("topic").help("Topic to create").required();
31+
create_command_.add_argument("-p")
32+
.help("Number of partitions")
33+
.scan<'i', int>()
34+
.default_value(1);
35+
36+
delete_command_.add_description("Delete a topic");
37+
delete_command_.add_argument("topic").help("Topic to delete").required();
38+
39+
list_command_.add_description("List topics");
40+
41+
describe_command_.add_description("Describe a topic");
42+
describe_command_.add_argument("topic")
43+
.help("Topic to describe")
44+
.required();
45+
46+
current_.add_subparser(create_command_);
47+
current_.add_subparser(delete_command_);
48+
current_.add_subparser(list_command_);
49+
current_.add_subparser(describe_command_);
50+
51+
parent.add_subparser(current_);
52+
}
53+
54+
bool run(rd_kafka_t *rk, rd_kafka_queue_t *rkqu) {
55+
if (current_.is_subcommand_used(create_command_)) {
56+
auto topic = create_command_.get("topic");
57+
auto partitions = create_command_.get<int>("-p");
58+
if (partitions < 0) {
59+
throw std::invalid_argument(
60+
"Number of partitions must be greater than or equal to 0");
61+
}
62+
create_topic(rk, rkqu, topic, partitions);
63+
} else if (current_.is_subcommand_used(delete_command_)) {
64+
auto topic = delete_command_.get("topic");
65+
delete_topic(rk, rkqu, topic);
66+
} else if (current_.is_subcommand_used(list_command_)) {
67+
list_topics(rk);
68+
} else if (current_.is_subcommand_used(describe_command_)) {
69+
auto topic = describe_command_.get("topic");
70+
describe_topic(rk, rkqu, topic);
71+
} else {
72+
std::cerr << "Invalid subcommand for topics\n" << current_ << std::endl;
73+
return false;
74+
}
75+
return true;
76+
}
77+
78+
auto &handle() const noexcept { return current_; }
79+
80+
private:
81+
argparse::ArgumentParser current_{"topics"};
82+
83+
argparse::ArgumentParser create_command_{"create"};
84+
argparse::ArgumentParser delete_command_{"delete"};
85+
argparse::ArgumentParser list_command_{"list"};
86+
argparse::ArgumentParser describe_command_{"describe"};
87+
};
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
*/
1616
#pragma once
1717

18-
#include "rk_event_wrapper.h"
18+
#include "snctl-cpp/rk_event_wrapper.h"
1919
#include <array>
2020
#include <iostream>
2121
#include <librdkafka/rdkafka.h>
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
*/
1616
#pragma once
1717

18-
#include "rk_event_wrapper.h"
18+
#include "snctl-cpp/rk_event_wrapper.h"
1919
#include <iostream>
2020
#include <librdkafka/rdkafka.h>
2121
#include <memory>
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
*/
1616
#pragma once
1717

18-
#include "rk_event_wrapper.h"
18+
#include "snctl-cpp/rk_event_wrapper.h"
1919
#include <iostream>
2020
#include <librdkafka/rdkafka.h>
2121
#include <stdexcept>

main.cc renamed to src/main.cc

Lines changed: 16 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -14,23 +14,23 @@
1414
* limitations under the License.
1515
*/
1616
#include "SimpleIni.h"
17-
#include "create_topic.h"
18-
#include "delete_topic.h"
19-
#include "describe_topic.h"
20-
#include "list_topics.h"
2117
#include <argparse/argparse.hpp>
2218
#include <array>
2319
#include <cstdio>
2420
#include <cstdlib>
2521
#include <cstring>
22+
#include <exception>
2623
#include <filesystem>
24+
#include <iostream>
2725
#include <librdkafka/rdkafka.h>
2826
#include <memory>
2927
#include <stdexcept>
3028
#include <string>
3129
#include <type_traits>
3230
#include <vector>
3331

32+
#include "snctl-cpp/topics.h"
33+
3434
int main(int argc, char *argv[]) {
3535
std::vector<std::string> default_config_paths{
3636
std::filesystem::current_path() / "sncloud.ini",
@@ -49,30 +49,14 @@ int main(int argc, char *argv[]) {
4949
.help("Path to the config file");
5050
program.add_argument("--client-id").help("client id");
5151

52-
argparse::ArgumentParser describe_command("describe");
53-
describe_command.add_description("Describe a topic");
54-
describe_command.add_argument("topic").help("Topic to describe").required();
55-
program.add_subparser(describe_command);
56-
57-
argparse::ArgumentParser list_command("list");
58-
list_command.add_description("List topics");
59-
program.add_subparser(list_command);
60-
61-
argparse::ArgumentParser create_command("create");
62-
create_command.add_description("Create a topic");
63-
create_command.add_argument("topic").help("Topic to create").required();
64-
create_command.add_argument("-p")
65-
.help("Number of partitions")
66-
.scan<'i', int>()
67-
.default_value(1);
68-
program.add_subparser(create_command);
69-
70-
argparse::ArgumentParser delete_command("delete");
71-
delete_command.add_description("Delete a topic");
72-
delete_command.add_argument("topic").help("Topic to delete").required();
73-
program.add_subparser(delete_command);
74-
75-
program.parse_args(argc, argv);
52+
Topics topics{program};
53+
try {
54+
program.parse_args(argc, argv);
55+
} catch (const std::exception &err) {
56+
std::cerr << "Failed to parse args: " << err.what() << "\n"
57+
<< program << std::endl;
58+
return 1;
59+
}
7660

7761
auto rk_conf = rd_kafka_conf_new();
7862

@@ -162,23 +146,11 @@ int main(int argc, char *argv[]) {
162146
decltype(&rd_kafka_queue_destroy)>
163147
rkque_guard{rkqu, &rd_kafka_queue_destroy};
164148

165-
if (program.is_subcommand_used(describe_command)) {
166-
describe_topic(rk, rkqu, describe_command.get("topic"));
167-
} else if (program.is_subcommand_used(list_command)) {
168-
list_topics(rk);
169-
} else if (program.is_subcommand_used(create_command)) {
170-
auto topic = create_command.get("topic");
171-
auto partitions = create_command.get<int>("-p");
172-
if (partitions < 0) {
173-
throw std::invalid_argument(
174-
"Number of partitions must be greater than or equal to 0");
175-
}
176-
create_topic(rk, rkqu, topic, partitions);
177-
} else if (program.is_subcommand_used(delete_command)) {
178-
auto topic = delete_command.get("topic");
179-
delete_topic(rk, rkqu, topic);
149+
if (program.is_subcommand_used(topics.handle())) {
150+
topics.run(rk, rkqu);
180151
} else {
181-
throw std::runtime_error("Only describe command is supported");
152+
std::cerr << "Invalid subcommand\n" << program << std::endl;
153+
return 1;
182154
}
183155

184156
return 0;

0 commit comments

Comments
 (0)