@@ -242,6 +242,87 @@ static List<UserScramCredentialAlteration> ParseUserScramCredentialAlterations(
242242 return alterations ;
243243 }
244244
245+ static Tuple < IsolationLevel , List < TopicPartitionOffsetSpec > > ParseListOffsetsArgs ( string [ ] args )
246+ {
247+ if ( args . Length == 0 )
248+ {
249+ Console . WriteLine ( "usage: .. <bootstrapServers> list-offsets <isolation_level> " +
250+ "<topic1> <partition1> <EARLIEST/LATEST/MAXTIMESTAMP/TIMESTAMP t1> .." ) ;
251+ Environment . ExitCode = 1 ;
252+ return null ;
253+ }
254+
255+ var isolationLevel = Enum . Parse < IsolationLevel > ( args [ 0 ] ) ;
256+ var topicPartitionOffsetSpecs = new List < TopicPartitionOffsetSpec > ( ) ;
257+ for ( int i = 1 ; i < args . Length ; )
258+ {
259+ if ( args . Length < i + 3 )
260+ {
261+ throw new ArgumentException ( $ "Invalid number of arguments for topicPartitionOffsetSpec[{ topicPartitionOffsetSpecs . Count } ]: { args . Length - i } ") ;
262+ }
263+
264+ string topic = args [ i ] ;
265+ var partition = Int32 . Parse ( args [ i + 1 ] ) ;
266+ var offsetSpec = args [ i + 2 ] ;
267+ if ( offsetSpec == "TIMESTAMP" )
268+ {
269+ if ( args . Length < i + 4 )
270+ {
271+ throw new ArgumentException ( $ "Invalid number of arguments for topicPartitionOffsetSpec[{ topicPartitionOffsetSpecs . Count } ]: { args . Length - i } ") ;
272+ }
273+
274+ var timestamp = Int64 . Parse ( args [ i + 3 ] ) ;
275+ i = i + 1 ;
276+ topicPartitionOffsetSpecs . Add ( new TopicPartitionOffsetSpec
277+ {
278+ TopicPartition = new TopicPartition ( topic , new Partition ( partition ) ) ,
279+ OffsetSpec = OffsetSpec . ForTimestamp ( timestamp )
280+ } ) ;
281+ }
282+ else if ( offsetSpec == "MAX_TIMESTAMP" )
283+ {
284+ topicPartitionOffsetSpecs . Add ( new TopicPartitionOffsetSpec
285+ {
286+ TopicPartition = new TopicPartition ( topic , new Partition ( partition ) ) ,
287+ OffsetSpec = OffsetSpec . MaxTimestamp ( )
288+ } ) ;
289+ }
290+ else if ( offsetSpec == "EARLIEST" )
291+ {
292+ topicPartitionOffsetSpecs . Add ( new TopicPartitionOffsetSpec
293+ {
294+ TopicPartition = new TopicPartition ( topic , new Partition ( partition ) ) ,
295+ OffsetSpec = OffsetSpec . Earliest ( )
296+ } ) ;
297+ }
298+ else if ( offsetSpec == "LATEST" )
299+ {
300+ topicPartitionOffsetSpecs . Add ( new TopicPartitionOffsetSpec
301+ {
302+ TopicPartition = new TopicPartition ( topic , new Partition ( partition ) ) ,
303+ OffsetSpec = OffsetSpec . Latest ( )
304+ } ) ;
305+ }
306+ else
307+ {
308+ throw new ArgumentException (
309+ "offsetSpec can be EARLIEST, LATEST, MAX_TIMESTAMP or TIMESTAMP T1." ) ;
310+ }
311+ i = i + 3 ;
312+ }
313+ return Tuple . Create ( isolationLevel , topicPartitionOffsetSpecs ) ;
314+ }
315+
316+ static void PrintListOffsetsResultInfos ( List < ListOffsetsResultInfo > ListOffsetsResultInfos )
317+ {
318+ foreach ( var listOffsetsResultInfo in ListOffsetsResultInfos )
319+ {
320+ Console . WriteLine ( " ListOffsetsResultInfo:" ) ;
321+ Console . WriteLine ( $ " TopicPartitionOffsetError: { listOffsetsResultInfo . TopicPartitionOffsetError } ") ;
322+ Console . WriteLine ( $ " Timestamp: { listOffsetsResultInfo . Timestamp } ") ;
323+ }
324+ }
325+
245326 static async Task CreateAclsAsync ( string bootstrapServers , string [ ] commandArgs )
246327 {
247328 List < AclBinding > aclBindings ;
@@ -793,6 +874,38 @@ await adminClient.AlterUserScramCredentialsAsync(alterations,
793874 }
794875 }
795876
877+ static async Task ListOffsetsAsync ( string bootstrapServers , string [ ] commandArgs ) {
878+
879+ var listOffsetsArgs = ParseListOffsetsArgs ( commandArgs ) ;
880+ if ( listOffsetsArgs == null ) { return ; }
881+
882+ var isolationLevel = listOffsetsArgs . Item1 ;
883+ var topicPartitionOffsets = listOffsetsArgs . Item2 ;
884+
885+ var timeout = TimeSpan . FromSeconds ( 30 ) ;
886+ ListOffsetsOptions options = new ListOffsetsOptions ( ) { RequestTimeout = timeout , IsolationLevel = isolationLevel } ;
887+
888+ using ( var adminClient = new AdminClientBuilder ( new AdminClientConfig { BootstrapServers = bootstrapServers } ) . Build ( ) )
889+ {
890+ try
891+ {
892+ var listOffsetsResult = await adminClient . ListOffsetsAsync ( topicPartitionOffsets , options ) ;
893+ Console . WriteLine ( "ListOffsetsResult:" ) ;
894+ PrintListOffsetsResultInfos ( listOffsetsResult . ListOffsetsResultInfos ) ;
895+ }
896+ catch ( ListOffsetsException e )
897+ {
898+ Console . WriteLine ( "ListOffsetsReport:" ) ;
899+ Console . WriteLine ( $ " Error: { e . Error } ") ;
900+ PrintListOffsetsResultInfos ( e . Result . ListOffsetsResultInfos ) ;
901+ }
902+ catch ( KafkaException e )
903+ {
904+ Console . WriteLine ( $ "An error occurred listing offsets: { e } ") ;
905+ Environment . ExitCode = 1 ;
906+ }
907+ }
908+ }
796909 static void PrintTopicDescriptions ( List < TopicDescription > topicDescriptions , bool includeAuthorizedOperations )
797910 {
798911 foreach ( var topic in topicDescriptions )
@@ -956,12 +1069,11 @@ public static async Task Main(string[] args)
9561069 Console . WriteLine (
9571070 "usage: .. <bootstrapServers> " + String . Join ( "|" , new string [ ] {
9581071 "list-groups" , "metadata" , "library-version" , "create-topic" , "create-acls" ,
959- "describe-acls" , "delete-acls" ,
9601072 "list-consumer-groups" , "describe-consumer-groups" ,
9611073 "list-consumer-group-offsets" , "alter-consumer-group-offsets" ,
9621074 "incremental-alter-configs" , "describe-user-scram-credentials" ,
9631075 "alter-user-scram-credentials" , "describe-topics" ,
964- "describe-cluster"
1076+ "describe-cluster" , "list-offsets"
9651077 } ) +
9661078 " .." ) ;
9671079 Environment . ExitCode = 1 ;
@@ -1022,6 +1134,9 @@ public static async Task Main(string[] args)
10221134 case "describe-cluster" :
10231135 await DescribeClusterAsync ( bootstrapServers , commandArgs ) ;
10241136 break ;
1137+ case "list-offsets" :
1138+ await ListOffsetsAsync ( bootstrapServers , commandArgs ) ;
1139+ break ;
10251140 default :
10261141 Console . WriteLine ( $ "unknown command: { command } ") ;
10271142 break ;
0 commit comments