From 763836f397532e6e2090a3521aa55386c4a0ea2e Mon Sep 17 00:00:00 2001 From: Adam Brightwell Date: Mon, 20 May 2024 10:01:32 -0400 Subject: [PATCH] Add firewall rule management to `cb network` command. These changes move firewall rule management under `cb network` and deprecate the `cb firewall` rule command. This comes as part of a request to be able to update firewall rules more completely as described in #161. The following commands are now supported: * `cb network add-firewall-rule` * `cb network list-firewall-rules` * `cb network remove-firewall-rule` * `cb network update-firewall-rule` Each command requires the specification of the network to peform the operation. As well, the shell completion logic has been updated to suggest the list of available networks and firewall rule when applicable. --- .github/workflows/release.yml | 2 +- CHANGELOG.md | 7 + spec/cb/completion_spec.cr | 39 +++++ spec/cb/firewall_rule_spec.cr | 284 ++++++++++++++++++++++++++++++++++ spec/support/factory.cr | 5 +- src/cb/completion.cr | 84 +++++++++- src/cb/firewall_rule.cr | 156 +++++++++++++++++++ src/cb/manage_firewall.cr | 4 +- src/cli.cr | 91 ++++++++++- src/client/firewall_rule.cr | 38 +++-- src/client/network.cr | 7 +- src/models/firewall_rule.cr | 1 + 12 files changed, 698 insertions(+), 20 deletions(-) create mode 100644 spec/cb/firewall_rule_spec.cr create mode 100644 src/cb/firewall_rule.cr diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 152903f..276d5eb 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -198,7 +198,7 @@ jobs: asset_name: cb-v${{ steps.version.outputs.version }}_macos_amd64.zip asset_content_type: application/zip - - name: Update release zip from macos arm64 + - name: Upload release zip from macos arm64 uses: actions/upload-release-asset@v1 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/CHANGELOG.md b/CHANGELOG.md index b392cf8..171e4d8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,13 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] +### Added +- `cb network` now manages firewall rules and supports the following + subcommands: `add-firewall-rule`, `list-firewall-rules`, + `remove-firewall-rule` and `update-firewall-rule` + +### Deprecated +- `cb firewall` deprecated in favor of `cb network`. ### Fixed - `cb list` completion to include `--format`. diff --git a/spec/cb/completion_spec.cr b/spec/cb/completion_spec.cr index 5973669..6a7c1b7 100644 --- a/spec/cb/completion_spec.cr +++ b/spec/cb/completion_spec.cr @@ -9,6 +9,10 @@ private class CompletionTestClient < CB::Client [Factory.team(name: "my team", role: "manager")] end + def get_networks(team) + [Factory.network] + end + def get_firewall_rules(id) [Factory.firewall_rule(id: "f1", rule: "1.2.3.4/32"), Factory.firewall_rule(id: "f2", rule: "4.5.6.7/24")] end @@ -663,9 +667,44 @@ Spectator.describe CB::Completion do expect(result).to have_option "network" result = parse("cb network ") + expect(result).to have_option "add-firewall-rule" expect(result).to have_option "info" expect(result).to have_option "list" + expect(result).to have_option "list-firewall-rules" + expect(result).to have_option "remove-firewall-rule" + expect(result).to have_option "update-firewall-rule" + + # Network Firewall Rule Management + result = parse("cb network add-firewall-rule") + expect(result).to have_option "--format" + expect(result).to have_option "--network" + expect(result).to have_option "--rule" + + result = parse("cb network list-firewall-rules") + expect(result).to have_option "--format" + expect(result).to have_option "--network" + + result = parse("cb network remove-firewall-rule") + expect(result).to have_option "--format" + expect(result).to have_option "--network" + expect(result).to have_option "--firewall-rule" + + result = parse("cb network remove-firewall-rule --network abc ") + expect(result).to have_option "--firewall-rule" + + result = parse("cb network update-firewall-rule") + expect(result).to have_option "--description" + expect(result).to have_option "--firewall-rule" + expect(result).to have_option "--format" + expect(result).to have_option "--network" + expect(result).to have_option "--rule" + + result = parse("cb network update-firewall-rule --network abc ") + expect(result).to have_option "--description" + expect(result).to have_option "--firewall-rule" + expect(result).to have_option "--rule" + # Network Management result = parse("cb network info ") expect(result).to have_option "--network" expect(result).to have_option "--format" diff --git a/spec/cb/firewall_rule_spec.cr b/spec/cb/firewall_rule_spec.cr new file mode 100644 index 0000000..bb916ae --- /dev/null +++ b/spec/cb/firewall_rule_spec.cr @@ -0,0 +1,284 @@ +require "../spec_helper" + +Spectator.describe FirewallRuleAdd do + subject(action) { described_class.new client: client, output: IO::Memory.new } + + mock_client + + let(network) { Factory.network } + + describe "#validate" do + it "ensures required arguments are present" do + expect(&.validate).to raise_error Program::Error, /Missing required argument/ + + action.network_id = network.id + expect(&.validate).to raise_error Program::Error, /Missing required argument/ + + action.rule = "0.0.0.0/0" + expect(&.validate).to be_true + end + end + + describe "#call" do + before_each { + action.output = IO::Memory.new + action.network_id = network.id + action.rule = Factory.firewall_rule.rule + + expect(client).to receive(:create_firewall_rule).and_return Factory.firewall_rule + } + + it "outputs table with header" do + action.call + + expected = <<-EXPECTED + ID Rule Description + shofthj3fzaipie44lt6a5i3de 1.2.3.0/24 Example Description + EXPECTED + + expect(&.output.to_s).to look_like expected + end + + it "outputs table without header" do + action.no_header = true + action.call + + expected = <<-EXPECTED + shofthj3fzaipie44lt6a5i3de 1.2.3.0/24 Example Description + EXPECTED + + expect(&.output.to_s).to look_like expected + end + + it "outputs json" do + action.format = Format::JSON + action.call + + expected = <<-EXPECTED + { + "firewall_rules": [ + { + "id": "shofthj3fzaipie44lt6a5i3de", + "description": "Example Description", + "rule": "1.2.3.0/24" + } + ] + } + EXPECTED + + expect(&.output.to_s).to look_like expected + end + end +end + +Spectator.describe FirewallRuleList do + subject(action) { described_class.new client: client, output: IO::Memory.new } + + mock_client + + let(network) { Factory.network } + + describe "#validate" do + it "ensures required arguments are present" do + expect(&.validate).to raise_error Program::Error, /Missing required argument/ + + action.network_id = network.id + expect(&.validate).to be_true + end + end + + describe "#call" do + before_each { + action.output = IO::Memory.new + action.network_id = network.id + + expect(client).to receive(:get_firewall_rules).and_return [Factory.firewall_rule] + } + + it "outputs table with header" do + action.call + + expected = <<-EXPECTED + ID Rule Description + shofthj3fzaipie44lt6a5i3de 1.2.3.0/24 Example Description + EXPECTED + + expect(&.output.to_s).to look_like expected + end + + it "outputs table without header" do + action.no_header = true + action.call + + expected = <<-EXPECTED + shofthj3fzaipie44lt6a5i3de 1.2.3.0/24 Example Description + EXPECTED + + expect(&.output.to_s).to look_like expected + end + + it "outputs json" do + action.format = Format::JSON + action.call + + expected = <<-EXPECTED + { + "firewall_rules": [ + { + "id": "shofthj3fzaipie44lt6a5i3de", + "description": "Example Description", + "rule": "1.2.3.0/24" + } + ] + } + EXPECTED + + expect(&.output.to_s).to look_like expected + end + end +end + +Spectator.describe FirewallRuleRemove do + subject(action) { described_class.new client: client, output: IO::Memory.new } + + mock_client + + let(network) { Factory.network } + let(firewall_rule) { Factory.firewall_rule } + + describe "#validate" do + it "ensures required arguments are present" do + expect(&.validate).to raise_error Program::Error, /Missing required argument/ + + action.network_id = network.id + expect(&.validate).to raise_error Program::Error, /Missing required argument/ + + action.firewall_rule_id = firewall_rule.id + expect(&.validate).to be_true + end + end + + describe "#call" do + before_each { + action.output = IO::Memory.new + action.network_id = network.id + action.firewall_rule_id = firewall_rule.id + + expect(client).to receive(:destroy_firewall_rule).and_return Factory.firewall_rule + } + + it "outputs table with header" do + action.call + + expected = <<-EXPECTED + ID Rule Description + shofthj3fzaipie44lt6a5i3de 1.2.3.0/24 Example Description + EXPECTED + + expect(&.output.to_s).to look_like expected + end + + it "outputs table without header" do + action.no_header = true + action.call + + expected = <<-EXPECTED + shofthj3fzaipie44lt6a5i3de 1.2.3.0/24 Example Description + EXPECTED + + expect(&.output.to_s).to look_like expected + end + + it "outputs json" do + action.format = Format::JSON + action.call + + expected = <<-EXPECTED + { + "firewall_rules": [ + { + "id": "shofthj3fzaipie44lt6a5i3de", + "description": "Example Description", + "rule": "1.2.3.0/24" + } + ] + } + EXPECTED + + expect(&.output.to_s).to look_like expected + end + end +end + +Spectator.describe FirewallRuleUpdate do + subject(action) { described_class.new client: client, output: IO::Memory.new } + + mock_client + + let(network) { Factory.network } + let(firewall_rule) { Factory.firewall_rule } + + describe "#validate" do + it "ensures required arguments are present" do + expect(&.validate).to raise_error Program::Error, /Missing required argument/ + + action.network_id = network.id + expect(&.validate).to raise_error Program::Error, /Missing required argument/ + + action.firewall_rule_id = firewall_rule.id + expect(&.validate).to be_true + end + end + + describe "#call" do + before_each { + action.output = IO::Memory.new + action.network_id = network.id + action.firewall_rule_id = firewall_rule.id + action.rule = Factory.firewall_rule.rule + + expect(client).to receive(:update_firewall_rule).and_return Factory.firewall_rule + } + + it "outputs table with header" do + action.call + + expected = <<-EXPECTED + ID Rule Description + shofthj3fzaipie44lt6a5i3de 1.2.3.0/24 Example Description + EXPECTED + + expect(&.output.to_s).to look_like expected + end + + it "outputs table without header" do + action.no_header = true + action.call + + expected = <<-EXPECTED + shofthj3fzaipie44lt6a5i3de 1.2.3.0/24 Example Description + EXPECTED + + expect(&.output.to_s).to look_like expected + end + + it "outputs json" do + action.format = Format::JSON + action.call + + expected = <<-EXPECTED + { + "firewall_rules": [ + { + "id": "shofthj3fzaipie44lt6a5i3de", + "description": "Example Description", + "rule": "1.2.3.0/24" + } + ] + } + EXPECTED + + expect(&.output.to_s).to look_like expected + end + end +end diff --git a/spec/support/factory.cr b/spec/support/factory.cr index 8ca0e94..2b366c3 100644 --- a/spec/support/factory.cr +++ b/spec/support/factory.cr @@ -115,8 +115,9 @@ module Factory def firewall_rule(**params) params = { - id: "shofthj3fzaipie44lt6a5i3de", - rule: "1.2.3.0/24", + description: "Example Description", + id: "shofthj3fzaipie44lt6a5i3de", + rule: "1.2.3.0/24", }.merge(params) CB::Model::FirewallRule.new **params end diff --git a/src/cb/completion.cr b/src/cb/completion.cr index e80c0e5..42d9e00 100644 --- a/src/cb/completion.cr +++ b/src/cb/completion.cr @@ -155,6 +155,21 @@ class CB::Completion end end + def network_suggestions + teams = client.get_teams + networks = client.get_networks(teams) + + networks.map do |n| + team_name = teams.find { |t| t.id == n.team_id }.try(&.name) || "unknown_team" + "#{n.id}\t#{n.name}" + end + end + + def firewall_rule_suggestions(network_id : String?) + rules = client.get_firewall_rules(network_id) + rules.map { |r| "#{r.id}\t#{r.description}" } + end + def teams client.get_teams.map { |t| "#{t.id}\t#{t.name}" } end @@ -316,8 +331,8 @@ class CB::Completion end end - def firewall_rules(cluster_id) - rules = client.get_firewall_rules(cluster_id) + def firewall_rules(network_id) + rules = client.get_firewall_rules(network_id) rules.map(&.rule) - @args rescue Client::Error [] of String @@ -554,18 +569,83 @@ class CB::Completion def network case @args[1] + when "add-firewall-rule" + network_add_firewall_rule + when "list-firewall-rules" + network_list_firewall_rules + when "remove-firewall-rule" + network_remove_firewall_rule + when "update-firewall-rule" + network_update_firewall_rule when "info" network_info when "list" network_list else [ + "add-firewall-rule\tadd firewall rule", + "remove-firewall-rule\tremove firewall rule", + "list-firewall-rules\tlist firewall rules", + "update-firewall-rule\tupdate firewall rule", "info\tdetailed network information", "list\tlist available networks", ] end end + def network_add_firewall_rule + return ["table", "json"] if last_arg?("--format") + return network_suggestions if last_arg?("--network") + suggest = [] of String + suggest << "--description\tdescription of rule to add" unless has_full_flag? :description + suggest << "--format\tchoose output format" unless has_full_flag? :format + suggest << "--network\tnetwork id" unless has_full_flag? :network + suggest << "--rule\tcidr of rule to add" unless has_full_flag? :rule + suggest + end + + def network_remove_firewall_rule + if last_arg?("--firewall-rule") && has_full_flag?(:network) + network = find_arg_value "--network" + return firewall_rule_suggestions(network) + end + + return ["table", "json"] if last_arg?("--format") + return network_suggestions if last_arg?("--network") + suggest = [] of String + suggest << "--firewall-rule\tchoose firewall rule" unless has_full_flag?(:firewall_rule) + suggest << "--format\tchoose output format" unless has_full_flag? :format + suggest << "--network\tchoose network" unless has_full_flag? :network + suggest + end + + def network_list_firewall_rules + return ["table", "json"] if last_arg?("--format") + return network_suggestions if last_arg?("--network") + suggest = [] of String + suggest << "--format\tchoose output format" unless has_full_flag? :format + suggest << "--network\tchoose network" unless has_full_flag? :network + suggest + end + + def network_update_firewall_rule + if last_arg?("--firewall-rule") && has_full_flag?(:network) + network = find_arg_value "--network" + return firewall_rule_suggestions(network) + end + + return ["table", "json"] if last_arg?("--format") + return network_suggestions if last_arg?("--network") + + suggest = [] of String + suggest << "--description\tdescription of the rule" unless has_full_flag? :description + suggest << "--firewall-rule\tchoose firewall rule" unless has_full_flag?(:firewall_rule) + suggest << "--format\tchoose output format" unless has_full_flag? :format + suggest << "--network\tchoose network" unless has_full_flag? :network + suggest << "--rule\tcidr of the rule" unless has_full_flag? :rule + suggest + end + def network_info if last_arg?("--format") return ["table", "json"] diff --git a/src/cb/firewall_rule.cr b/src/cb/firewall_rule.cr new file mode 100644 index 0000000..11a0fbf --- /dev/null +++ b/src/cb/firewall_rule.cr @@ -0,0 +1,156 @@ +require "./action" + +module CB + # API Action for network firewall rules. + # + # All network firewall rule actions must inherit this action. + abstract class FirewallRuleAction < APIAction + # The output format. The default format is `table` format. + format_setter format + + # The ID of the target network. + eid_setter network_id + + # Flag to indicate whether the output should include a header. This only + # has an effect when the output format is a table. + property? no_header : Bool = false + + def validate + check_required_args do |missing| + missing << "network" unless @network_id + end + end + + abstract def run + + def display(firewall_rules : Array(Model::FirewallRule)) + case @format + when Format::Default, Format::Table + output_table(firewall_rules) + when Format::JSON + output_json(firewall_rules) + end + end + + def output_json(firewall_rules : Array(Model::FirewallRule)) + output << { + "firewall_rules": firewall_rules, + }.to_pretty_json << '\n' + end + + def output_table(firewall_rules : Array(Model::FirewallRule)) + table = Table::TableBuilder.new(border: :none) do + columns do + add "ID" + add "Rule" + add "Description" + end + + header unless @no_header + + rows firewall_rules.map { |fwr| [fwr.id, fwr.rule, fwr.description] } + end + + output << table.render << '\n' + end + end + + # Action for adding a firewall rule to a network. + class FirewallRuleAdd < FirewallRuleAction + # The rule (required). + property rule : String = "" + + # The description of the rule. + property description : String? + + def validate + super + + check_required_args do |missing| + missing << "rule" if @rule.empty? + end + end + + def run + validate + + firewall_rule = client.create_firewall_rule( + network_id: @network_id, + params: CB::Client::FirewallRuleCreateParams.new( + description: @description, + rule: @rule.to_s + ) + ) + + display([firewall_rule]) + end + end + + # Action for listing existing firewall rules for a network. + class FirewallRuleList < FirewallRuleAction + def run + validate + + firewall_rules = client.get_firewall_rules(@network_id) + + display(firewall_rules) + end + end + + # Action for removing a firewall rule from a network + class FirewallRuleRemove < FirewallRuleAction + # The ID of the firewall rule to remove. + eid_setter firewall_rule_id + + def validate + super + + check_required_args do |missing| + missing << "firewall-rule" unless @firewall_rule_id + end + end + + def run + validate + + firewall_rule = client.destroy_firewall_rule(network_id, firewall_rule_id) + + display([firewall_rule]) + end + end + + # Action for updating an existing firewall rule for a network. + class FirewallRuleUpdate < FirewallRuleAction + # The ID of the firewall rule to update. + eid_setter firewall_rule_id + + # The rule. + property rule : String? + + # The description of the rule. + property description : String? + + def validate + super + + check_required_args do |missing| + missing << "firewall-rule" unless @firewall_rule_id + end + end + + def run + validate + + firewall_rule = client.update_firewall_rule( + network_id: @network_id, + firewall_rule_id: @firewall_rule_id, + params: CB::Client::FirewallRuleUpdateParams.new( + description: @description, + rule: @rule, + ) + ) + + display([firewall_rule]) + end + end +end diff --git a/src/cb/manage_firewall.cr b/src/cb/manage_firewall.cr index 749421c..75a22b2 100644 --- a/src/cb/manage_firewall.cr +++ b/src/cb/manage_firewall.cr @@ -58,14 +58,14 @@ class CB::ManageFirewall < CB::APIAction end def remove_rule(rule : CB::Model::FirewallRule) - @client.delete_firewall_rule @network_id, rule.id + @client.destroy_firewall_rule @network_id, rule.id "done".colorize.t_success rescue e : Client::Error output.print e end def add_rule(cidr : String) - @client.add_firewall_rule @network_id, cidr + @client.create_firewall_rule @network_id, CB::Client::FirewallRuleCreateParams.new(rule: cidr) "done".colorize.t_success rescue e : Client::Error output.print e diff --git a/src/cli.cr b/src/cli.cr index a33515e..8394216 100755 --- a/src/cli.cr +++ b/src/cli.cr @@ -126,7 +126,8 @@ op = OptionParser.new do |parser| positional_args psql.cluster_id end - parser.on("firewall", "Manage firewall rules") do + parser.on("firewall") do + show_deprecated("Prefer use of #{"cb network".colorize.bold} instead") manage = set_action ManageFirewall parser.banner = "cb firewall <--cluster> [--add] [--remove]" @@ -493,13 +494,99 @@ op = OptionParser.new do |parser| parser.on("network", "Manage networks") do parser.banner = "cb network " + parser.on("add-firewall-rule", "Add a firewall rule to a network") do + add = set_action FirewallRuleAdd + + parser.banner = "cb network add-firewall-rule <--network> <--rule>" + + parser.on("--description DESC", "A description for the rule") { |arg| add.description = arg } + parser.on("--format FORMAT", "Output format (default: table)") { |arg| add.format = arg } + parser.on("--network ID", "The target network for the rule") { |arg| add.network_id = arg } + parser.on("--no-header", "Do not display table header") { add.no_header = true } + parser.on("--rule CIDR", "A firewall rule") { |arg| add.rule = arg } + + parser.examples = <<-EXAMPLES + Add a firewall rule. Output: table + $ cb network add-firewall-rule --network --rule + + Add a firewall rule. Output: table without header + $ cb network add-firewall-rule --network --rule --no-header + + Add a firewall rule with a description. Output: table + $ cb network add-firewall-rule --network --rule --description + + Add a firewall rule. Output: json + $ cb network add-firewall-rule --network --rule --format json + EXAMPLES + end + + parser.on("list-firewall-rules", "List all firewall rules for a network") do + list = set_action FirewallRuleList + + parser.on("--format FORMAT", "Output format (default: table)") { |arg| list.format = arg } + parser.on("--network ID", "The target network") { |arg| list.network_id = arg } + parser.on("--no-header", "Do not display table header") { list.no_header = true } + + parser.examples = <<-EXAMPLES + List all firewall rules. Output: table + $ cb network list-firewall-rules --network + + List all firewall rules. Output: table without header + $ cb network list-firewall-rules --network --no-header + + List all firewall rules. Output: json + $ cb network list-firewall-rules --network --format json + EXAMPLES + end + + parser.on("remove-firewall-rule", "Remove a firewall rule from a network") do + remove = set_action FirewallRuleRemove + + parser.banner = "cb network remove-firewall-rule <--network> <--firewall-rule>" + + parser.on("--firewall-rule ID", "The id of the rule to remove") { |arg| remove.firewall_rule_id = arg } + parser.on("--format FORMAT", "Output format (default: table)") { |arg| remove.format = arg } + parser.on("--network ID", "The target network") { |arg| remove.network_id = arg } + + parser.examples = <<-EXAMPLES + Remove firewall rule. Output: table + $ cb network remove-firewall-rule --network --firewall-rule + + Remove firewall rule. Output: table without header + $ cb network remove-firewall-rule --network --firewall-rule --no-header + + Remove firewall rule. Ouptut: json + $ cb network remove-firewall-rule --network --firewall-rule --format json + EXAMPLES + end + + parser.on("update-firewall-rule", "Update a network firewall rule") do + update = set_action FirewallRuleUpdate + + parser.banner = "cb network update-firewall-rule <--network> <--firewall-rule>" + + parser.on("--description DESC", "The description for the rule") { |arg| update.description = arg } + parser.on("--firewall-rule ID", "The id of the rule to remove") { |arg| update.firewall_rule_id = arg } + parser.on("--format FORMAT", "Output format (default: table)") { |arg| update.format = arg } + parser.on("--network ID", "The target network") { |arg| update.network_id = arg } + parser.on("--rule CIDR", "The firewall rule") { |arg| update.rule = arg } + + parser.examples = <<-EXAMPLES + Update rule. + $ cb network update-firewall-rule --network --firewall-rule --rule + + Update description. + $ cb network update-firewall-rule --network --firewall-rule --description + EXAMPLES + end + parser.on("info", "Detailed network information") do info = set_action NetworkInfo parser.banner = "cb network info <--network>" - parser.on("--network ID", "Choose network") { |arg| info.network_id = arg } parser.on("--format FORMAT", "Choose output format (default: table)") { |arg| info.format = arg } + parser.on("--network ID", "Choose network") { |arg| info.network_id = arg } parser.on("--no-header", "Do not display table header") { info.no_header = true } parser.examples = <<-EXAMPLES diff --git a/src/client/firewall_rule.cr b/src/client/firewall_rule.cr index 80ef53b..bfd766e 100644 --- a/src/client/firewall_rule.cr +++ b/src/client/firewall_rule.cr @@ -1,27 +1,45 @@ +require "json" + require "./client" module CB class Client - # Add a firewall rule to a cluster. + jrecord FirewallRuleCreateParams, + description : String? = nil, + rule : String = "" + + # Add a firewall rule to a network. # - # TODO (abrightwell): Add docs reference. - def add_firewall_rule(network_id, cidr) - post "networks/#{network_id}/firewall-rules", {rule: cidr} + # https://docs.crunchybridge.com/api/network-firewall-rule#create-firewall-rule + def create_firewall_rule(network_id, params : FirewallRuleCreateParams) + resp = post "networks/#{network_id}/firewall-rules", params + CB::Model::FirewallRule.from_json resp.body end - # Remove a firewall rule from a cluster. + # Remove a firewall rule from a network. # - # TODO (abrightwell): Add docs reference. - def delete_firewall_rule(network_id, firewall_rule_id) - delete "networks/#{network_id}/firewall-rules/#{firewall_rule_id}" + # https://docs.crunchybridge.com/api/network-firewall-rule#destroy-firewall-rule + def destroy_firewall_rule(network_id, firewall_rule_id) + resp = delete "networks/#{network_id}/firewall-rules/#{firewall_rule_id}" + CB::Model::FirewallRule.from_json resp.body end - # List current firewall rules for a cluster. + # List current firewall rules for a network. # - # TODO (abrightwell): Add docs reference. + # https://docs.crunchybridge.com/api/network-firewall-rule#list-firewall-rules def get_firewall_rules(network_id) resp = get "networks/#{network_id}/firewall-rules" Array(CB::Model::FirewallRule).from_json resp.body, root: "firewall_rules" end + + jrecord FirewallRuleUpdateParams, description : String?, rule : String? + + # Update a firewall rule for a network. + # + # https://docs.crunchybridge.com/api/network-firewall-rule#update-firewall-rule + def update_firewall_rule(network_id, firewall_rule_id, params : FirewallRuleUpdateParams) + resp = patch "networks/#{network_id}/firewall-rules/#{firewall_rule_id}", params + CB::Model::FirewallRule.from_json resp.body + end end end diff --git a/src/client/network.cr b/src/client/network.cr index 1e564cc..adda08f 100644 --- a/src/client/network.cr +++ b/src/client/network.cr @@ -25,10 +25,15 @@ module CB else get "networks" end - Array(CB::Model::Network).from_json resp.body, root: "networks" end + def get_networks(teams : Array(CB::Model::Team)) + networks = [] of CB::Model::Network + teams.each { |team| networks.concat get_networks(Identifier.new team.id.to_s) } + networks + end + private def get_network_by_name(id : Identifier) network = get_networks(nil).find { |n| id == n.name } raise Program::Error.new "network #{id.to_s.colorize.t_name} does not exist." unless network diff --git a/src/models/firewall_rule.cr b/src/models/firewall_rule.cr index e81f971..6a9dfdf 100644 --- a/src/models/firewall_rule.cr +++ b/src/models/firewall_rule.cr @@ -1,5 +1,6 @@ module CB::Model jrecord FirewallRule, id : String, + description : String, rule : String end