Skip to content

Commit 8577c5c

Browse files
CSE-364: Extend cb logdest with TLS and connection-log flags and add update (#193)
Add optional --tls-verify-disabled and --forward-connection-logs to cb logdest add, and introduce cb logdest update to change an existing destination. Update merges omitted fields and unset booleans from the current cluster listing so PUT payloads stay complete. Extend LogDestination model and list output (TTY labels and extra pipe columns) for the new fields. Wire Client#update_log_destination and teach completion to suggest the new flags and true or false after them. Add specs for add (including invalid boolean strings), update (merge, overrides, and not-found), list, completion, captured add payloads without mock_client, and model JSON parsing with and without the new keys. Adjust the spec factory and trim spec_helper accordingly. Note by Erik: This was written under my direction by the robot in Cursor with the model set to Auto. While it wrote some tests as we went, once the new functionality was working I asked it to do another pass for spec/test coverage on the new functionality and it found some more issues that it fixed. * crystal tool format fixes * Changes for ameba's Lint/NotNil rule * Limit codegen threads to 1 for crystal spec runs on CI * Changes from Adam's review.
1 parent 0e0f362 commit 8577c5c

16 files changed

Lines changed: 564 additions & 19 deletions

flake.nix

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,9 @@
8282
name = "specs";
8383
src = specSrc;
8484
buildInputs = [ pkgs.libssh2 ];
85-
installPhase = "mkdir $out && HOME=$TMP crystal spec --progress";
85+
# Cap compiler threads: very high CPU counts on CI can trigger
86+
# "BUG: a codegen process failed" during crystal spec (codegen OOM / LLVM flake).
87+
installPhase = "mkdir $out && HOME=$TMP crystal spec --progress --threads 1";
8688
shardsFile = specSrc + "/shards.nix";
8789
doCheck = false;
8890
dontPatch = true;

spec/cb/completion_spec.cr

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -401,10 +401,14 @@ Spectator.describe CB::Completion do
401401
expect(result).to have_option "list"
402402
expect(result).to have_option "destroy"
403403
expect(result).to have_option "add"
404+
expect(result).to have_option "update"
404405

405406
result = parse("cb logdest l")
406407
expect(result).to have_option "list"
407408

409+
result = parse("cb logdest u")
410+
expect(result).to have_option "update"
411+
408412
result = parse("cb logdest list ")
409413
expect(result).to have_option "--cluster"
410414

@@ -451,6 +455,8 @@ Spectator.describe CB::Completion do
451455
expect(result).to have_option "--desc"
452456
expect(result).to have_option "--template"
453457
expect(result).to have_option "--host"
458+
expect(result).to have_option "--tls-verify-disabled"
459+
expect(result).to have_option "--forward-connection-logs"
454460

455461
result = parse("cb logdest add --port 3 ")
456462
expect(result).to_not have_option "--port"
@@ -467,6 +473,39 @@ Spectator.describe CB::Completion do
467473
result = parse("cb logdest add --host something.com ")
468474
expect(result).to_not have_option "--host"
469475
expect(result).to have_option "--cluster"
476+
477+
result = parse("cb logdest update ")
478+
expect(result).to have_option "--cluster"
479+
expect(result).to have_option "--logdest"
480+
expect(result).to have_option "--port"
481+
expect(result).to have_option "--desc"
482+
expect(result).to have_option "--template"
483+
expect(result).to have_option "--host"
484+
485+
result = parse("cb logdest update --cluster ")
486+
expect(result).to eq expected_cluster_suggestion
487+
488+
result = parse("cb logdest update --cluster abc ")
489+
expect(result).to_not have_option "--cluster"
490+
expect(result).to have_option "--logdest"
491+
492+
result = parse("cb logdest update --cluster abc --logdest ")
493+
expect(result).to eq ["logid\tlogdest descr"]
494+
495+
result = parse("cb logdest update --cluster abc --logdest logid ")
496+
expect(result).to have_option "--port"
497+
expect(result).to have_option "--host"
498+
expect(result).to have_option "--tls-verify-disabled"
499+
expect(result).to have_option "--forward-connection-logs"
500+
501+
result = parse("cb logdest add --cluster abc --tls-verify-disabled ")
502+
expect(result).to eq ["false", "true"]
503+
504+
result = parse("cb logdest update --cluster abc --logdest logid --tls-verify-disabled ")
505+
expect(result).to eq ["false", "true"]
506+
507+
result = parse("cb logdest update --cluster abc --logdest logid --forward-connection-logs ")
508+
expect(result).to eq ["false", "true"]
470509
end
471510

472511
it "completes psql" do
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
require "../spec_helper"
2+
3+
# This file intentionally does not use `mock_client`: Spectator's Client mock
4+
# is injected into CB::Client and prevents subclasses from overriding API methods.
5+
6+
private class LogDestinationAddCaptureClient < CB::Client
7+
getter last_cluster_id : String?
8+
getter last_payload_json : String = ""
9+
10+
def initialize(token : String = TEST_TOKEN)
11+
super(host: CB::HOST, bearer_token: token)
12+
end
13+
14+
def add_log_destination(cluster_id, ld)
15+
@last_cluster_id = cluster_id.to_s
16+
@last_payload_json = ld.to_json
17+
"{}"
18+
end
19+
end
20+
21+
Spectator.describe "CB::LogDestinationAdd API payload" do
22+
let(cluster) { Factory.cluster }
23+
24+
it "sends tls and forward-connection flags in the create payload" do
25+
cap = LogDestinationAddCaptureClient.new
26+
act = CB::LogDestinationAdd.new(client: cap, output: IO::Memory.new)
27+
act.cluster_id = cluster.id
28+
act.port = 514
29+
act.description = "d"
30+
act.host = "logs.example.com"
31+
act.template = "jsonline"
32+
act.tls_verify_disabled = "true"
33+
act.forward_connection_logs = "false"
34+
act.call
35+
cap.last_cluster_id.should eq cluster.id
36+
j = JSON.parse(cap.last_payload_json).as_h
37+
j["host"].as_s.should eq "logs.example.com"
38+
j["port"].as_i.should eq 514
39+
j["template"].as_s.should eq "jsonline"
40+
j["description"].as_s.should eq "d"
41+
j["tls_verify_disabled"].as_bool.should be_true
42+
j["forward_connection_logs"].as_bool.should be_false
43+
end
44+
45+
it "defaults tls and forward-connection to false in the payload when unset" do
46+
cap = LogDestinationAddCaptureClient.new
47+
act = CB::LogDestinationAdd.new(client: cap, output: IO::Memory.new)
48+
act.cluster_id = cluster.id
49+
act.port = 514
50+
act.description = "d"
51+
act.host = "logs.example.com"
52+
act.template = "jsonline"
53+
act.call
54+
j = JSON.parse(cap.last_payload_json).as_h
55+
j["tls_verify_disabled"].as_bool.should be_false
56+
j["forward_connection_logs"].as_bool.should be_false
57+
end
58+
end

spec/cb/logdest_add_spec.cr

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,4 +53,26 @@ Spectator.describe CB::LogDestinationAdd do
5353
lda.host = "logs.zam.com"
5454
lda.description.should eq "already set dont change"
5555
end
56+
57+
it "accepts tls and connection log flags as true or false strings" do
58+
lda = make_lda
59+
lda.tls_verify_disabled = "true"
60+
lda.tls_verify_disabled.should be_true
61+
lda.tls_verify_disabled = "false"
62+
lda.tls_verify_disabled.should be_false
63+
lda.forward_connection_logs = "TRUE"
64+
lda.forward_connection_logs.should be_true
65+
lda.forward_connection_logs = "false"
66+
lda.forward_connection_logs.should be_false
67+
end
68+
69+
it "rejects invalid boolean strings for tls_verify_disabled" do
70+
lda = make_lda
71+
expect { lda.tls_verify_disabled = "yes" }.to raise_error(CB::Program::Error, /Invalid/)
72+
end
73+
74+
it "rejects invalid boolean strings for forward_connection_logs" do
75+
lda = make_lda
76+
expect { lda.forward_connection_logs = "1" }.to raise_error(CB::Program::Error, /Invalid/)
77+
end
5678
end

spec/cb/logdest_list_spec.cr

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
require "../spec_helper"
2+
3+
private class TtyMemory < IO::Memory
4+
def tty?
5+
true
6+
end
7+
end
8+
9+
private class LogDestinationListTestClient < CB::Client
10+
getter last_cluster : String?
11+
12+
def get_log_destinations(cluster_id)
13+
@last_cluster = cluster_id
14+
[
15+
Factory.log_destination(
16+
tls_verify_disabled: true,
17+
forward_connection_logs: false,
18+
),
19+
Factory.log_destination(
20+
id: "pkdpq6yynjgjbps4otxd7il2u4",
21+
description: "second",
22+
tls_verify_disabled: false,
23+
forward_connection_logs: true,
24+
),
25+
]
26+
end
27+
end
28+
29+
private class LogDestinationListEmptyClient < CB::Client
30+
def initialize(token : String = TEST_TOKEN)
31+
super(host: CB::HOST, bearer_token: token)
32+
end
33+
34+
def get_log_destinations(cluster_id)
35+
[] of CB::Model::LogDestination
36+
end
37+
end
38+
39+
Spectator.describe CB::LogDestinationList do
40+
let(client) { LogDestinationListTestClient.new(TEST_TOKEN) }
41+
let(cluster) { Factory.cluster }
42+
subject(action) { described_class.new client: client, output: IO::Memory.new }
43+
44+
it "lists flags on tty" do
45+
io = TtyMemory.new
46+
act = CB::LogDestinationList.new(client: client, output: io)
47+
act.cluster_id = cluster.id
48+
act.call
49+
s = io.to_s
50+
s.should contain("TLS verify disabled: yes")
51+
s.should contain("Forward connection logs: no")
52+
s.should contain("TLS verify disabled: no")
53+
s.should contain("Forward connection logs: yes")
54+
client.last_cluster.should eq cluster.id
55+
end
56+
57+
it "appends flag columns when piped" do
58+
act = CB::LogDestinationList.new(client: client, output: IO::Memory.new)
59+
act.cluster_id = cluster.id
60+
act.call
61+
lines = act.output.to_s.lines.map(&.rstrip("\n"))
62+
lines.size.should eq 2
63+
lines[0].split('\t').size.should eq 7
64+
lines[0].ends_with?("\ttrue\tfalse").should be_true
65+
lines[1].ends_with?("\tfalse\ttrue").should be_true
66+
end
67+
68+
it "prints a message when there are no log destinations" do
69+
io = TtyMemory.new
70+
act = CB::LogDestinationList.new(client: LogDestinationListEmptyClient.new(TEST_TOKEN), output: io)
71+
act.cluster_id = Factory.cluster.id
72+
act.call
73+
io.to_s.should contain("no log destinations")
74+
end
75+
end

0 commit comments

Comments
 (0)