fila-client 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +77 -1
- data/lib/fila/client.rb +82 -55
- data/lib/fila/proto/fila/v1/admin_pb.rb +13 -1
- data/lib/fila/proto/fila/v1/admin_services_pb.rb +7 -0
- data/lib/fila/version.rb +1 -1
- data/proto/fila/v1/admin.proto +83 -0
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 2d12705da00a1094120e5a6c8ecd88b6193b73a88f18d5c6786211bc8458fb96
|
|
4
|
+
data.tar.gz: 3f1adf19ca765745a2a14bc99bd6e1239dbcc1ef5940f7cb4203c80c3a3cacc9
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7e6db66c6b4a7dace398aff5659ccfa750e0d51e82ef2b34dff3175bdcd0ebb91768dc87e3515e1caa52c2aa24b6f94e81ac7078ef87886bd0701e0b8944c1d4
|
|
7
|
+
data.tar.gz: 7524edf3d910461afcf3844bb7ae7d7a780055b9700cecc2dec92ddab84e2aa5349d0b41116359fdc5063f2a23ee729973d14ba227bd62d544f9d2576f0fde24
|
data/README.md
CHANGED
|
@@ -44,12 +44,88 @@ end
|
|
|
44
44
|
client.close
|
|
45
45
|
```
|
|
46
46
|
|
|
47
|
+
### TLS (system trust store)
|
|
48
|
+
|
|
49
|
+
```ruby
|
|
50
|
+
require "fila"
|
|
51
|
+
|
|
52
|
+
# TLS using the OS system trust store (e.g., server uses a public CA).
|
|
53
|
+
client = Fila::Client.new("localhost:5555", tls: true)
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### TLS (custom CA)
|
|
57
|
+
|
|
58
|
+
```ruby
|
|
59
|
+
require "fila"
|
|
60
|
+
|
|
61
|
+
# TLS with an explicit CA certificate (e.g., private/self-signed CA).
|
|
62
|
+
client = Fila::Client.new("localhost:5555",
|
|
63
|
+
ca_cert: File.read("ca.pem")
|
|
64
|
+
)
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### mTLS (mutual TLS)
|
|
68
|
+
|
|
69
|
+
```ruby
|
|
70
|
+
require "fila"
|
|
71
|
+
|
|
72
|
+
# Mutual TLS with system trust store.
|
|
73
|
+
client = Fila::Client.new("localhost:5555",
|
|
74
|
+
tls: true,
|
|
75
|
+
client_cert: File.read("client.pem"),
|
|
76
|
+
client_key: File.read("client-key.pem")
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
# Mutual TLS with explicit CA certificate.
|
|
80
|
+
client = Fila::Client.new("localhost:5555",
|
|
81
|
+
ca_cert: File.read("ca.pem"),
|
|
82
|
+
client_cert: File.read("client.pem"),
|
|
83
|
+
client_key: File.read("client-key.pem")
|
|
84
|
+
)
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### API Key Authentication
|
|
88
|
+
|
|
89
|
+
```ruby
|
|
90
|
+
require "fila"
|
|
91
|
+
|
|
92
|
+
# API key sent as Bearer token on every request.
|
|
93
|
+
client = Fila::Client.new("localhost:5555",
|
|
94
|
+
api_key: "fila_your_api_key_here"
|
|
95
|
+
)
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### mTLS + API Key
|
|
99
|
+
|
|
100
|
+
```ruby
|
|
101
|
+
require "fila"
|
|
102
|
+
|
|
103
|
+
# Full security: mTLS transport + API key authentication.
|
|
104
|
+
client = Fila::Client.new("localhost:5555",
|
|
105
|
+
ca_cert: File.read("ca.pem"),
|
|
106
|
+
client_cert: File.read("client.pem"),
|
|
107
|
+
client_key: File.read("client-key.pem"),
|
|
108
|
+
api_key: "fila_your_api_key_here"
|
|
109
|
+
)
|
|
110
|
+
```
|
|
111
|
+
|
|
47
112
|
## API
|
|
48
113
|
|
|
49
|
-
### `Fila::Client.new(addr)`
|
|
114
|
+
### `Fila::Client.new(addr, tls: false, ca_cert: nil, client_cert: nil, client_key: nil, api_key: nil)`
|
|
50
115
|
|
|
51
116
|
Connect to a Fila broker at the given address (e.g., `"localhost:5555"`).
|
|
52
117
|
|
|
118
|
+
| Parameter | Type | Description |
|
|
119
|
+
|---|---|---|
|
|
120
|
+
| `addr` | `String` | Broker address in `"host:port"` format |
|
|
121
|
+
| `tls:` | `Boolean` | Enable TLS using the OS system trust store (default: `false`) |
|
|
122
|
+
| `ca_cert:` | `String` or `nil` | PEM-encoded CA certificate for TLS (implies `tls: true`) |
|
|
123
|
+
| `client_cert:` | `String` or `nil` | PEM-encoded client certificate for mTLS |
|
|
124
|
+
| `client_key:` | `String` or `nil` | PEM-encoded client private key for mTLS |
|
|
125
|
+
| `api_key:` | `String` or `nil` | API key for Bearer token authentication |
|
|
126
|
+
|
|
127
|
+
When no TLS/auth options are provided, the client connects over plaintext (backward compatible). When `tls: true` is set without `ca_cert:`, the OS system trust store is used for server certificate verification.
|
|
128
|
+
|
|
53
129
|
### `client.enqueue(queue:, headers:, payload:)`
|
|
54
130
|
|
|
55
131
|
Enqueue a message. Returns the broker-assigned message ID (UUIDv7).
|
data/lib/fila/client.rb
CHANGED
|
@@ -14,43 +14,37 @@ module Fila
|
|
|
14
14
|
#
|
|
15
15
|
# Wraps the hot-path gRPC operations: enqueue, consume, ack, nack.
|
|
16
16
|
#
|
|
17
|
-
# @example
|
|
17
|
+
# @example Plain-text (no auth)
|
|
18
18
|
# client = Fila::Client.new("localhost:5555")
|
|
19
|
-
#
|
|
20
|
-
#
|
|
21
|
-
#
|
|
22
|
-
#
|
|
23
|
-
#
|
|
24
|
-
# client.
|
|
19
|
+
#
|
|
20
|
+
# @example TLS with system trust store
|
|
21
|
+
# client = Fila::Client.new("localhost:5555", tls: true)
|
|
22
|
+
#
|
|
23
|
+
# @example TLS with custom CA
|
|
24
|
+
# client = Fila::Client.new("localhost:5555", ca_cert: File.read("ca.pem"))
|
|
25
|
+
#
|
|
26
|
+
# @example mTLS + API key
|
|
27
|
+
# client = Fila::Client.new("localhost:5555",
|
|
28
|
+
# ca_cert: File.read("ca.pem"),
|
|
29
|
+
# client_cert: File.read("client.pem"),
|
|
30
|
+
# client_key: File.read("client-key.pem"),
|
|
31
|
+
# api_key: "fila_abc123")
|
|
25
32
|
class Client
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
@stub = ::Fila::V1::FilaService::Stub.new(addr, :this_channel_is_insecure)
|
|
33
|
+
def initialize(addr, tls: false, ca_cert: nil, client_cert: nil, client_key: nil, api_key: nil)
|
|
34
|
+
@api_key = api_key
|
|
35
|
+
@credentials = build_credentials(tls: tls, ca_cert: ca_cert, client_cert: client_cert, client_key: client_key)
|
|
36
|
+
@stub = ::Fila::V1::FilaService::Stub.new(addr, @credentials)
|
|
31
37
|
end
|
|
32
38
|
|
|
33
|
-
|
|
34
|
-
def close
|
|
35
|
-
# grpc-ruby doesn't expose a direct channel close on stubs;
|
|
36
|
-
# the channel is garbage-collected. This is a no-op for API symmetry.
|
|
37
|
-
end
|
|
39
|
+
def close; end
|
|
38
40
|
|
|
39
|
-
# Enqueue a message to the specified queue.
|
|
40
|
-
#
|
|
41
|
-
# @param queue [String] target queue name
|
|
42
|
-
# @param headers [Hash<String, String>, nil] optional message headers
|
|
43
|
-
# @param payload [String] message payload bytes
|
|
44
|
-
# @return [String] broker-assigned message ID (UUIDv7)
|
|
45
|
-
# @raise [QueueNotFoundError] if the queue does not exist
|
|
46
|
-
# @raise [RPCError] for unexpected gRPC failures
|
|
47
41
|
def enqueue(queue:, payload:, headers: nil)
|
|
48
42
|
req = ::Fila::V1::EnqueueRequest.new(
|
|
49
43
|
queue: queue,
|
|
50
44
|
headers: headers || {},
|
|
51
45
|
payload: payload
|
|
52
46
|
)
|
|
53
|
-
resp = @stub.enqueue(req)
|
|
47
|
+
resp = @stub.enqueue(req, metadata: call_metadata)
|
|
54
48
|
resp.message_id
|
|
55
49
|
rescue GRPC::NotFound => e
|
|
56
50
|
raise QueueNotFoundError, "enqueue: #{e.details}"
|
|
@@ -58,36 +52,12 @@ module Fila
|
|
|
58
52
|
raise RPCError.new(e.code, e.details)
|
|
59
53
|
end
|
|
60
54
|
|
|
61
|
-
# Open a streaming consumer
|
|
62
|
-
#
|
|
63
|
-
# Yields messages as they become available. Nil message frames (keepalive
|
|
64
|
-
# signals) are skipped automatically. Nacked messages are redelivered on
|
|
65
|
-
# the same stream.
|
|
66
|
-
#
|
|
67
|
-
# If no block is given, returns an Enumerator.
|
|
68
|
-
#
|
|
69
|
-
# @param queue [String] queue to consume from
|
|
70
|
-
# @yield [ConsumeMessage] each message received from the broker
|
|
71
|
-
# @return [Enumerator<ConsumeMessage>] if no block given
|
|
72
|
-
# @raise [QueueNotFoundError] if the queue does not exist
|
|
73
|
-
# @raise [RPCError] for unexpected gRPC failures
|
|
55
|
+
# Open a streaming consumer. Yields messages as they arrive.
|
|
56
|
+
# Returns an Enumerator if no block given.
|
|
74
57
|
def consume(queue:, &block)
|
|
75
58
|
return enum_for(:consume, queue: queue) unless block
|
|
76
59
|
|
|
77
|
-
|
|
78
|
-
stream = @stub.consume(req)
|
|
79
|
-
stream.each do |resp|
|
|
80
|
-
msg = resp.message
|
|
81
|
-
next if msg.nil? || msg.id.empty?
|
|
82
|
-
|
|
83
|
-
block.call(build_consume_message(msg))
|
|
84
|
-
end
|
|
85
|
-
rescue GRPC::Cancelled
|
|
86
|
-
# Stream cancelled — normal when consumer breaks out of the loop.
|
|
87
|
-
rescue GRPC::NotFound => e
|
|
88
|
-
raise QueueNotFoundError, "consume: #{e.details}"
|
|
89
|
-
rescue GRPC::BadStatus => e
|
|
90
|
-
raise RPCError.new(e.code, e.details)
|
|
60
|
+
consume_with_redirect(queue: queue, redirected: false, &block)
|
|
91
61
|
end
|
|
92
62
|
|
|
93
63
|
# Acknowledge a successfully processed message.
|
|
@@ -98,7 +68,7 @@ module Fila
|
|
|
98
68
|
# @raise [RPCError] for unexpected gRPC failures
|
|
99
69
|
def ack(queue:, msg_id:)
|
|
100
70
|
req = ::Fila::V1::AckRequest.new(queue: queue, message_id: msg_id)
|
|
101
|
-
@stub.ack(req)
|
|
71
|
+
@stub.ack(req, metadata: call_metadata)
|
|
102
72
|
nil
|
|
103
73
|
rescue GRPC::NotFound => e
|
|
104
74
|
raise MessageNotFoundError, "ack: #{e.details}"
|
|
@@ -115,7 +85,7 @@ module Fila
|
|
|
115
85
|
# @raise [RPCError] for unexpected gRPC failures
|
|
116
86
|
def nack(queue:, msg_id:, error:)
|
|
117
87
|
req = ::Fila::V1::NackRequest.new(queue: queue, message_id: msg_id, error: error)
|
|
118
|
-
@stub.nack(req)
|
|
88
|
+
@stub.nack(req, metadata: call_metadata)
|
|
119
89
|
nil
|
|
120
90
|
rescue GRPC::NotFound => e
|
|
121
91
|
raise MessageNotFoundError, "nack: #{e.details}"
|
|
@@ -123,8 +93,65 @@ module Fila
|
|
|
123
93
|
raise RPCError.new(e.code, e.details)
|
|
124
94
|
end
|
|
125
95
|
|
|
96
|
+
LEADER_ADDR_KEY = 'x-fila-leader-addr'
|
|
97
|
+
|
|
98
|
+
private_constant :LEADER_ADDR_KEY
|
|
99
|
+
|
|
126
100
|
private
|
|
127
101
|
|
|
102
|
+
def consume_with_redirect(queue:, redirected:, &block) # rubocop:disable Metrics/AbcSize
|
|
103
|
+
stream = @stub.consume(::Fila::V1::ConsumeRequest.new(queue: queue), metadata: call_metadata)
|
|
104
|
+
stream.each do |resp|
|
|
105
|
+
msg = resp.message
|
|
106
|
+
next if msg.nil? || msg.id.empty?
|
|
107
|
+
|
|
108
|
+
block.call(build_consume_message(msg))
|
|
109
|
+
end
|
|
110
|
+
rescue GRPC::Cancelled then nil
|
|
111
|
+
rescue GRPC::NotFound => e
|
|
112
|
+
raise QueueNotFoundError, "consume: #{e.details}"
|
|
113
|
+
rescue GRPC::Unavailable => e
|
|
114
|
+
raise RPCError.new(e.code, e.details) if (leader_addr = extract_leader_addr(e)).nil? || redirected
|
|
115
|
+
|
|
116
|
+
@stub = ::Fila::V1::FilaService::Stub.new(leader_addr, @credentials)
|
|
117
|
+
consume_with_redirect(queue: queue, redirected: true, &block)
|
|
118
|
+
rescue GRPC::BadStatus => e
|
|
119
|
+
raise RPCError.new(e.code, e.details)
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def extract_leader_addr(err)
|
|
123
|
+
err.metadata[LEADER_ADDR_KEY]
|
|
124
|
+
rescue StandardError
|
|
125
|
+
nil
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def build_credentials(tls:, ca_cert:, client_cert:, client_key:)
|
|
129
|
+
tls_enabled = tls || ca_cert
|
|
130
|
+
validate_tls_options(tls_enabled, client_cert, client_key)
|
|
131
|
+
return :this_channel_is_insecure unless tls_enabled
|
|
132
|
+
|
|
133
|
+
build_channel_credentials(ca_cert, client_cert, client_key)
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def validate_tls_options(tls_enabled, client_cert, client_key)
|
|
137
|
+
return if tls_enabled || (!client_cert && !client_key)
|
|
138
|
+
|
|
139
|
+
raise ArgumentError, 'tls: true or ca_cert is required when client_cert or client_key is provided'
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def build_channel_credentials(ca_cert, client_cert, client_key)
|
|
143
|
+
if ca_cert then GRPC::Core::ChannelCredentials.new(ca_cert, client_key, client_cert)
|
|
144
|
+
elsif client_cert && client_key then GRPC::Core::ChannelCredentials.new(nil, client_key, client_cert)
|
|
145
|
+
else GRPC::Core::ChannelCredentials.new
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def call_metadata
|
|
150
|
+
return {} unless @api_key
|
|
151
|
+
|
|
152
|
+
{ 'authorization' => "Bearer #{@api_key}" }
|
|
153
|
+
end
|
|
154
|
+
|
|
128
155
|
def build_consume_message(msg)
|
|
129
156
|
metadata = msg.metadata
|
|
130
157
|
ConsumeMessage.new(
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
require 'google/protobuf'
|
|
6
6
|
|
|
7
7
|
|
|
8
|
-
descriptor_data = "\n\x13\x66ila/v1/admin.proto\x12\x07\x66ila.v1\"H\n\x12\x43reateQueueRequest\x12\x0c\n\x04name\x18\x01 \x01(\t\x12$\n\x06\x63onfig\x18\x02 \x01(\x0b\x32\x14.fila.v1.QueueConfig\"b\n\x0bQueueConfig\x12\x19\n\x11on_enqueue_script\x18\x01 \x01(\t\x12\x19\n\x11on_failure_script\x18\x02 \x01(\t\x12\x1d\n\x15visibility_timeout_ms\x18\x03 \x01(\x04\"\'\n\x13\x43reateQueueResponse\x12\x10\n\x08queue_id\x18\x01 \x01(\t\"#\n\x12\x44\x65leteQueueRequest\x12\r\n\x05queue\x18\x01 \x01(\t\"\x15\n\x13\x44\x65leteQueueResponse\".\n\x10SetConfigRequest\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t\"\x13\n\x11SetConfigResponse\"\x1f\n\x10GetConfigRequest\x12\x0b\n\x03key\x18\x01 \x01(\t\"\"\n\x11GetConfigResponse\x12\r\n\x05value\x18\x01 \x01(\t\")\n\x0b\x43onfigEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t\"#\n\x11ListConfigRequest\x12\x0e\n\x06prefix\x18\x01 \x01(\t\"P\n\x12ListConfigResponse\x12%\n\x07\x65ntries\x18\x01 \x03(\x0b\x32\x14.fila.v1.ConfigEntry\x12\x13\n\x0btotal_count\x18\x02 \x01(\r\" \n\x0fGetStatsRequest\x12\r\n\x05queue\x18\x01 \x01(\t\"b\n\x13PerFairnessKeyStats\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\x15\n\rpending_count\x18\x02 \x01(\x04\x12\x17\n\x0f\x63urrent_deficit\x18\x03 \x01(\x03\x12\x0e\n\x06weight\x18\x04 \x01(\r\"Z\n\x13PerThrottleKeyStats\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\x0e\n\x06tokens\x18\x02 \x01(\x01\x12\x17\n\x0frate_per_second\x18\x03 \x01(\x01\x12\r\n\x05\x62urst\x18\x04 \x01(\x01\"\
|
|
8
|
+
descriptor_data = "\n\x13\x66ila/v1/admin.proto\x12\x07\x66ila.v1\"H\n\x12\x43reateQueueRequest\x12\x0c\n\x04name\x18\x01 \x01(\t\x12$\n\x06\x63onfig\x18\x02 \x01(\x0b\x32\x14.fila.v1.QueueConfig\"b\n\x0bQueueConfig\x12\x19\n\x11on_enqueue_script\x18\x01 \x01(\t\x12\x19\n\x11on_failure_script\x18\x02 \x01(\t\x12\x1d\n\x15visibility_timeout_ms\x18\x03 \x01(\x04\"\'\n\x13\x43reateQueueResponse\x12\x10\n\x08queue_id\x18\x01 \x01(\t\"#\n\x12\x44\x65leteQueueRequest\x12\r\n\x05queue\x18\x01 \x01(\t\"\x15\n\x13\x44\x65leteQueueResponse\".\n\x10SetConfigRequest\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t\"\x13\n\x11SetConfigResponse\"\x1f\n\x10GetConfigRequest\x12\x0b\n\x03key\x18\x01 \x01(\t\"\"\n\x11GetConfigResponse\x12\r\n\x05value\x18\x01 \x01(\t\")\n\x0b\x43onfigEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t\"#\n\x11ListConfigRequest\x12\x0e\n\x06prefix\x18\x01 \x01(\t\"P\n\x12ListConfigResponse\x12%\n\x07\x65ntries\x18\x01 \x03(\x0b\x32\x14.fila.v1.ConfigEntry\x12\x13\n\x0btotal_count\x18\x02 \x01(\r\" \n\x0fGetStatsRequest\x12\r\n\x05queue\x18\x01 \x01(\t\"b\n\x13PerFairnessKeyStats\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\x15\n\rpending_count\x18\x02 \x01(\x04\x12\x17\n\x0f\x63urrent_deficit\x18\x03 \x01(\x03\x12\x0e\n\x06weight\x18\x04 \x01(\r\"Z\n\x13PerThrottleKeyStats\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\x0e\n\x06tokens\x18\x02 \x01(\x01\x12\x17\n\x0frate_per_second\x18\x03 \x01(\x01\x12\r\n\x05\x62urst\x18\x04 \x01(\x01\"\x9f\x02\n\x10GetStatsResponse\x12\r\n\x05\x64\x65pth\x18\x01 \x01(\x04\x12\x11\n\tin_flight\x18\x02 \x01(\x04\x12\x1c\n\x14\x61\x63tive_fairness_keys\x18\x03 \x01(\x04\x12\x18\n\x10\x61\x63tive_consumers\x18\x04 \x01(\r\x12\x0f\n\x07quantum\x18\x05 \x01(\r\x12\x33\n\rper_key_stats\x18\x06 \x03(\x0b\x32\x1c.fila.v1.PerFairnessKeyStats\x12\x38\n\x12per_throttle_stats\x18\x07 \x03(\x0b\x32\x1c.fila.v1.PerThrottleKeyStats\x12\x16\n\x0eleader_node_id\x18\x08 \x01(\x04\x12\x19\n\x11replication_count\x18\t \x01(\r\"2\n\x0eRedriveRequest\x12\x11\n\tdlq_queue\x18\x01 \x01(\t\x12\r\n\x05\x63ount\x18\x02 \x01(\x04\"#\n\x0fRedriveResponse\x12\x10\n\x08redriven\x18\x01 \x01(\x04\"\x13\n\x11ListQueuesRequest\"m\n\tQueueInfo\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\r\n\x05\x64\x65pth\x18\x02 \x01(\x04\x12\x11\n\tin_flight\x18\x03 \x01(\x04\x12\x18\n\x10\x61\x63tive_consumers\x18\x04 \x01(\r\x12\x16\n\x0eleader_node_id\x18\x05 \x01(\x04\"T\n\x12ListQueuesResponse\x12\"\n\x06queues\x18\x01 \x03(\x0b\x32\x12.fila.v1.QueueInfo\x12\x1a\n\x12\x63luster_node_count\x18\x02 \x01(\r\"Q\n\x13\x43reateApiKeyRequest\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x15\n\rexpires_at_ms\x18\x02 \x01(\x04\x12\x15\n\ris_superadmin\x18\x03 \x01(\x08\"J\n\x14\x43reateApiKeyResponse\x12\x0e\n\x06key_id\x18\x01 \x01(\t\x12\x0b\n\x03key\x18\x02 \x01(\t\x12\x15\n\ris_superadmin\x18\x03 \x01(\x08\"%\n\x13RevokeApiKeyRequest\x12\x0e\n\x06key_id\x18\x01 \x01(\t\"\x16\n\x14RevokeApiKeyResponse\"\x14\n\x12ListApiKeysRequest\"o\n\nApiKeyInfo\x12\x0e\n\x06key_id\x18\x01 \x01(\t\x12\x0c\n\x04name\x18\x02 \x01(\t\x12\x15\n\rcreated_at_ms\x18\x03 \x01(\x04\x12\x15\n\rexpires_at_ms\x18\x04 \x01(\x04\x12\x15\n\ris_superadmin\x18\x05 \x01(\x08\"8\n\x13ListApiKeysResponse\x12!\n\x04keys\x18\x01 \x03(\x0b\x32\x13.fila.v1.ApiKeyInfo\".\n\rAclPermission\x12\x0c\n\x04kind\x18\x01 \x01(\t\x12\x0f\n\x07pattern\x18\x02 \x01(\t\"L\n\rSetAclRequest\x12\x0e\n\x06key_id\x18\x01 \x01(\t\x12+\n\x0bpermissions\x18\x02 \x03(\x0b\x32\x16.fila.v1.AclPermission\"\x10\n\x0eSetAclResponse\"\x1f\n\rGetAclRequest\x12\x0e\n\x06key_id\x18\x01 \x01(\t\"d\n\x0eGetAclResponse\x12\x0e\n\x06key_id\x18\x01 \x01(\t\x12+\n\x0bpermissions\x18\x02 \x03(\x0b\x32\x16.fila.v1.AclPermission\x12\x15\n\ris_superadmin\x18\x03 \x01(\x08\x32\x8e\x07\n\tFilaAdmin\x12H\n\x0b\x43reateQueue\x12\x1b.fila.v1.CreateQueueRequest\x1a\x1c.fila.v1.CreateQueueResponse\x12H\n\x0b\x44\x65leteQueue\x12\x1b.fila.v1.DeleteQueueRequest\x1a\x1c.fila.v1.DeleteQueueResponse\x12\x42\n\tSetConfig\x12\x19.fila.v1.SetConfigRequest\x1a\x1a.fila.v1.SetConfigResponse\x12\x42\n\tGetConfig\x12\x19.fila.v1.GetConfigRequest\x1a\x1a.fila.v1.GetConfigResponse\x12\x45\n\nListConfig\x12\x1a.fila.v1.ListConfigRequest\x1a\x1b.fila.v1.ListConfigResponse\x12?\n\x08GetStats\x12\x18.fila.v1.GetStatsRequest\x1a\x19.fila.v1.GetStatsResponse\x12<\n\x07Redrive\x12\x17.fila.v1.RedriveRequest\x1a\x18.fila.v1.RedriveResponse\x12\x45\n\nListQueues\x12\x1a.fila.v1.ListQueuesRequest\x1a\x1b.fila.v1.ListQueuesResponse\x12K\n\x0c\x43reateApiKey\x12\x1c.fila.v1.CreateApiKeyRequest\x1a\x1d.fila.v1.CreateApiKeyResponse\x12K\n\x0cRevokeApiKey\x12\x1c.fila.v1.RevokeApiKeyRequest\x1a\x1d.fila.v1.RevokeApiKeyResponse\x12H\n\x0bListApiKeys\x12\x1b.fila.v1.ListApiKeysRequest\x1a\x1c.fila.v1.ListApiKeysResponse\x12\x39\n\x06SetAcl\x12\x16.fila.v1.SetAclRequest\x1a\x17.fila.v1.SetAclResponse\x12\x39\n\x06GetAcl\x12\x16.fila.v1.GetAclRequest\x1a\x17.fila.v1.GetAclResponseb\x06proto3"
|
|
9
9
|
|
|
10
10
|
pool = ::Google::Protobuf::DescriptorPool.generated_pool
|
|
11
11
|
pool.add_serialized_file(descriptor_data)
|
|
@@ -33,5 +33,17 @@ module Fila
|
|
|
33
33
|
ListQueuesRequest = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("fila.v1.ListQueuesRequest").msgclass
|
|
34
34
|
QueueInfo = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("fila.v1.QueueInfo").msgclass
|
|
35
35
|
ListQueuesResponse = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("fila.v1.ListQueuesResponse").msgclass
|
|
36
|
+
CreateApiKeyRequest = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("fila.v1.CreateApiKeyRequest").msgclass
|
|
37
|
+
CreateApiKeyResponse = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("fila.v1.CreateApiKeyResponse").msgclass
|
|
38
|
+
RevokeApiKeyRequest = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("fila.v1.RevokeApiKeyRequest").msgclass
|
|
39
|
+
RevokeApiKeyResponse = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("fila.v1.RevokeApiKeyResponse").msgclass
|
|
40
|
+
ListApiKeysRequest = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("fila.v1.ListApiKeysRequest").msgclass
|
|
41
|
+
ApiKeyInfo = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("fila.v1.ApiKeyInfo").msgclass
|
|
42
|
+
ListApiKeysResponse = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("fila.v1.ListApiKeysResponse").msgclass
|
|
43
|
+
AclPermission = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("fila.v1.AclPermission").msgclass
|
|
44
|
+
SetAclRequest = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("fila.v1.SetAclRequest").msgclass
|
|
45
|
+
SetAclResponse = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("fila.v1.SetAclResponse").msgclass
|
|
46
|
+
GetAclRequest = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("fila.v1.GetAclRequest").msgclass
|
|
47
|
+
GetAclResponse = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("fila.v1.GetAclResponse").msgclass
|
|
36
48
|
end
|
|
37
49
|
end
|
|
@@ -24,6 +24,13 @@ module Fila
|
|
|
24
24
|
rpc :GetStats, ::Fila::V1::GetStatsRequest, ::Fila::V1::GetStatsResponse
|
|
25
25
|
rpc :Redrive, ::Fila::V1::RedriveRequest, ::Fila::V1::RedriveResponse
|
|
26
26
|
rpc :ListQueues, ::Fila::V1::ListQueuesRequest, ::Fila::V1::ListQueuesResponse
|
|
27
|
+
# API key management. CreateApiKey bypasses auth (bootstrap); others require a valid key.
|
|
28
|
+
rpc :CreateApiKey, ::Fila::V1::CreateApiKeyRequest, ::Fila::V1::CreateApiKeyResponse
|
|
29
|
+
rpc :RevokeApiKey, ::Fila::V1::RevokeApiKeyRequest, ::Fila::V1::RevokeApiKeyResponse
|
|
30
|
+
rpc :ListApiKeys, ::Fila::V1::ListApiKeysRequest, ::Fila::V1::ListApiKeysResponse
|
|
31
|
+
# Per-key ACL management.
|
|
32
|
+
rpc :SetAcl, ::Fila::V1::SetAclRequest, ::Fila::V1::SetAclResponse
|
|
33
|
+
rpc :GetAcl, ::Fila::V1::GetAclRequest, ::Fila::V1::GetAclResponse
|
|
27
34
|
end
|
|
28
35
|
|
|
29
36
|
Stub = Service.rpc_stub_class
|
data/lib/fila/version.rb
CHANGED
data/proto/fila/v1/admin.proto
CHANGED
|
@@ -11,6 +11,15 @@ service FilaAdmin {
|
|
|
11
11
|
rpc GetStats(GetStatsRequest) returns (GetStatsResponse);
|
|
12
12
|
rpc Redrive(RedriveRequest) returns (RedriveResponse);
|
|
13
13
|
rpc ListQueues(ListQueuesRequest) returns (ListQueuesResponse);
|
|
14
|
+
|
|
15
|
+
// API key management. CreateApiKey bypasses auth (bootstrap); others require a valid key.
|
|
16
|
+
rpc CreateApiKey(CreateApiKeyRequest) returns (CreateApiKeyResponse);
|
|
17
|
+
rpc RevokeApiKey(RevokeApiKeyRequest) returns (RevokeApiKeyResponse);
|
|
18
|
+
rpc ListApiKeys(ListApiKeysRequest) returns (ListApiKeysResponse);
|
|
19
|
+
|
|
20
|
+
// Per-key ACL management.
|
|
21
|
+
rpc SetAcl(SetAclRequest) returns (SetAclResponse);
|
|
22
|
+
rpc GetAcl(GetAclRequest) returns (GetAclResponse);
|
|
14
23
|
}
|
|
15
24
|
|
|
16
25
|
message CreateQueueRequest {
|
|
@@ -89,6 +98,9 @@ message GetStatsResponse {
|
|
|
89
98
|
uint32 quantum = 5;
|
|
90
99
|
repeated PerFairnessKeyStats per_key_stats = 6;
|
|
91
100
|
repeated PerThrottleKeyStats per_throttle_stats = 7;
|
|
101
|
+
// Cluster fields (0 when not in cluster mode).
|
|
102
|
+
uint64 leader_node_id = 8;
|
|
103
|
+
uint32 replication_count = 9;
|
|
92
104
|
}
|
|
93
105
|
|
|
94
106
|
message RedriveRequest {
|
|
@@ -107,8 +119,79 @@ message QueueInfo {
|
|
|
107
119
|
uint64 depth = 2;
|
|
108
120
|
uint64 in_flight = 3;
|
|
109
121
|
uint32 active_consumers = 4;
|
|
122
|
+
uint64 leader_node_id = 5;
|
|
110
123
|
}
|
|
111
124
|
|
|
112
125
|
message ListQueuesResponse {
|
|
113
126
|
repeated QueueInfo queues = 1;
|
|
127
|
+
uint32 cluster_node_count = 2;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// --- API Key Management ---
|
|
131
|
+
|
|
132
|
+
message CreateApiKeyRequest {
|
|
133
|
+
/// Human-readable label for the key.
|
|
134
|
+
string name = 1;
|
|
135
|
+
/// Optional Unix timestamp (milliseconds) after which the key expires.
|
|
136
|
+
/// 0 means no expiration.
|
|
137
|
+
uint64 expires_at_ms = 2;
|
|
138
|
+
/// When true, the key bypasses all ACL checks (superadmin).
|
|
139
|
+
bool is_superadmin = 3;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
message CreateApiKeyResponse {
|
|
143
|
+
/// Opaque key ID for management operations (revoke, list, set-acl).
|
|
144
|
+
string key_id = 1;
|
|
145
|
+
/// Plaintext API key. Returned once — store it securely.
|
|
146
|
+
string key = 2;
|
|
147
|
+
/// Whether this key has superadmin privileges.
|
|
148
|
+
bool is_superadmin = 3;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
message RevokeApiKeyRequest {
|
|
152
|
+
string key_id = 1;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
message RevokeApiKeyResponse {}
|
|
156
|
+
|
|
157
|
+
message ListApiKeysRequest {}
|
|
158
|
+
|
|
159
|
+
message ApiKeyInfo {
|
|
160
|
+
string key_id = 1;
|
|
161
|
+
string name = 2;
|
|
162
|
+
uint64 created_at_ms = 3;
|
|
163
|
+
/// 0 means no expiration.
|
|
164
|
+
uint64 expires_at_ms = 4;
|
|
165
|
+
bool is_superadmin = 5;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
message ListApiKeysResponse {
|
|
169
|
+
repeated ApiKeyInfo keys = 1;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// --- ACL Management ---
|
|
173
|
+
|
|
174
|
+
/// A single permission grant: kind (produce/consume/admin) + queue pattern.
|
|
175
|
+
message AclPermission {
|
|
176
|
+
/// One of: "produce", "consume", "admin".
|
|
177
|
+
string kind = 1;
|
|
178
|
+
/// Queue name or wildcard ("*" or "orders.*").
|
|
179
|
+
string pattern = 2;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
message SetAclRequest {
|
|
183
|
+
string key_id = 1;
|
|
184
|
+
repeated AclPermission permissions = 2;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
message SetAclResponse {}
|
|
188
|
+
|
|
189
|
+
message GetAclRequest {
|
|
190
|
+
string key_id = 1;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
message GetAclResponse {
|
|
194
|
+
string key_id = 1;
|
|
195
|
+
repeated AclPermission permissions = 2;
|
|
196
|
+
bool is_superadmin = 3;
|
|
114
197
|
}
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: fila-client
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Faisca
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-03-
|
|
11
|
+
date: 2026-03-24 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: google-protobuf
|