From 92ed2174e74e9cfb21ec02d9c31991de07f3d782 Mon Sep 17 00:00:00 2001 From: Stephen Paul Weber Date: Mon, 23 May 2022 14:10:35 -0500 Subject: [PATCH] Initial bare-bones admin command for launching a snikket instance --- config-schema.dhall | 1 + config.dhall.sample | 1 + forms/snikket_launch.rb | 9 ++++ forms/snikket_launched.rb | 19 +++++++ lib/snikket.rb | 104 ++++++++++++++++++++++++++++++++++++++ sgx_jmp.rb | 32 ++++++++++++ test/test_snikket.rb | 73 ++++++++++++++++++++++++++ 7 files changed, 239 insertions(+) create mode 100644 forms/snikket_launch.rb create mode 100644 forms/snikket_launched.rb create mode 100644 lib/snikket.rb create mode 100644 test/test_snikket.rb diff --git a/config-schema.dhall b/config-schema.dhall index e34ee2e19c4df5c62ae988e3095a697662a6cdd5..36f07d4118cc7261be71c281c3b1d800a9489386 100644 --- a/config-schema.dhall +++ b/config-schema.dhall @@ -41,6 +41,7 @@ , sgx : Text , sip : { app : Text, realm : Text } , sip_host : Text +, snikket_hosting_api : Text , unbilled_targets : List Text , upstream_domain : Text , web : < Inet : { interface : Text, port : Natural } | Unix : Text > diff --git a/config.dhall.sample b/config.dhall.sample index b16a3c4430d02f008c1ecd0402fafe08d4a79dad..59d7f081955a309ec4c1c4645e240ed5cc9b5f42 100644 --- a/config.dhall.sample +++ b/config.dhall.sample @@ -79,6 +79,7 @@ in unbilled_targets = ["+14169938000"], keep_area_codes = ["555"], keep_area_codes_in = { account = "", site_id = "", sip_peer_id = "" }, + snikket_hosting_api = "", upstream_domain = "example.net", approved_domains = toMap { `example.com` = Some "customer_id" } } diff --git a/forms/snikket_launch.rb b/forms/snikket_launch.rb new file mode 100644 index 0000000000000000000000000000000000000000..6989cccb5957e555339522946614d308f6b9f79a --- /dev/null +++ b/forms/snikket_launch.rb @@ -0,0 +1,9 @@ +form! + +title "Launch Snikket Instance" + +field( + var: "domain", + label: "Domain for Instance", + type: "text-single" +) diff --git a/forms/snikket_launched.rb b/forms/snikket_launched.rb new file mode 100644 index 0000000000000000000000000000000000000000..749ed11b8bfb1c87ee550d6e68dbdc8d45d4589f --- /dev/null +++ b/forms/snikket_launched.rb @@ -0,0 +1,19 @@ +result! + +title "Snikket Instance Lauching" + +instructions "Snikket instance is launching now. Please wait a few minutes." + +field( + type: "text-single", + var: "instance-id", + label: "Instance ID", + value: @launched.instance_id +) + +field( + type: "text-single", + var: "bootstrap-uri", + label: "Admin Invite", + value: @launched.bootstrap_uri(@domain) +) diff --git a/lib/snikket.rb b/lib/snikket.rb new file mode 100644 index 0000000000000000000000000000000000000000..3403d1c6335b5b2075da209cc5cef09036bee366 --- /dev/null +++ b/lib/snikket.rb @@ -0,0 +1,104 @@ +# frozen_string_literal: true + +require "blather" + +module Snikket + class Launch < Blather::Stanza::Iq + register nil, "launch", "xmpp:snikket.org/hosting/v1" + + def self.new(type=nil, to=nil, id=nil, domain: nil) + stanza = super(type || :set, to, id) + node = Nokogiri::XML::Node.new("launch", stanza.document) + node.default_namespace = registered_ns + stanza << node + stanza.domain = domain if domain + stanza + end + + def domain=(domain) + query.at_xpath("./ns:domain", ns: self.class.registered_ns)&.remove + node = Nokogiri::XML::Node.new("domain", document) + node.default_namespace = self.class.registered_ns + node.content = domain + query << node + end + + def query + at_xpath("./ns:launch", ns: self.class.registered_ns) + end + end + + class Launched < Blather::Stanza::Iq + register :snikket_launched, "launched", "xmpp:snikket.org/hosting/v1" + + def instance_id + query + .at_xpath("./ns:instance-id", ns: self.class.registered_ns) + &.content + end + + def bootstrap_token + query + .at_xpath("./ns:bootstrap/ns:token", ns: self.class.registered_ns) + &.content + end + + def bootstrap_uri(instance_domain) + "https://#{instance_domain}/invites_bootstrap?token=#{bootstrap_token}" + end + + def query + at_xpath("./ns:launched", ns: self.class.registered_ns) + end + end + + class Instance < Blather::Stanza::Iq + register :snikket_instance, "instance", "xmpp:snikket.org/hosting/v1" + + def self.new(type=nil, to=nil, id=nil, instance_id: nil) + stanza = super(type || :get, to, id) + node = Nokogiri::XML::Node.new("instance", stanza.document) + node.default_namespace = registered_ns + stanza << node + stanza.instance_id = instance_id if instance_id + stanza + end + + def inherit(*) + query.remove + super + end + + def instance_id=(instance_id) + query.at_xpath("./ns:instance-id", ns: self.class.registered_ns)&.remove + node = Nokogiri::XML::Node.new("instance-id", document) + node.default_namespace = self.class.registered_ns + node.content = instance_id + query << node + end + + def instance_id + query + .at_xpath("./ns:instance-id", ns: self.class.registered_ns) + &.content + end + + def update_needed? + !!query.at_xpath("./ns:update-needed", ns: self.class.registered_ns) + end + + def operation + query.at_xpath("./ns:operation", ns: self.class.registered_ns) + end + + def status + query + .at_xpath("./ns:status", ns: self.class.registered_ns) + &.content&.to_sym + end + + def query + at_xpath("./ns:instance", ns: self.class.registered_ns) + end + end +end diff --git a/sgx_jmp.rb b/sgx_jmp.rb index 80a9c92cabbd66e21e060e7325cc1a23ff44bdbd..d347337f5e420c056b61901fec0a0101735a2b0d 100644 --- a/sgx_jmp.rb +++ b/sgx_jmp.rb @@ -97,6 +97,7 @@ require_relative "lib/registration" require_relative "lib/transaction" require_relative "lib/tel_selections" require_relative "lib/session_manager" +require_relative "lib/snikket" require_relative "lib/statsd" require_relative "web" @@ -772,6 +773,37 @@ Command.new( end }.register(self).then(&CommandList.method(:register)) +Command.new( + "snikket", + "Launch Snikket Instance", + list_for: ->(customer: nil, **) { customer&.admin? } +) { + Command.customer.then do |customer| + raise AuthError, "You are not an admin" unless customer&.admin? + + Command.reply { |reply| + reply.allowed_actions = [:next] + reply.command << FormTemplate.render("snikket_launch") + }.then { |response| + domain = response.form.field("domain").value.to_s + IQ_MANAGER.write(Snikket::Launch.new( + nil, CONFIG[:snikket_hosting_api], + domain: domain + )).then do |launched| + [domain, launched] + end + }.then { |(domain, launched)| + Command.finish do |reply| + reply.command << FormTemplate.render( + "snikket_launched", + launched: launched, + domain: domain + ) + end + } + end +}.register(self).then(&CommandList.method(:register)) + def reply_with_note(iq, text, type: :info) reply = iq.reply reply.status = :completed diff --git a/test/test_snikket.rb b/test/test_snikket.rb new file mode 100644 index 0000000000000000000000000000000000000000..26e89f258a7bcf51bf379146c7b947a7e6d2bf5a --- /dev/null +++ b/test/test_snikket.rb @@ -0,0 +1,73 @@ +# frozen_string_literal: true + +require "snikket" + +class TestSnikket < Minitest::Test + NS = "xmpp:snikket.org/hosting/v1" + + def test_launch + launch = Snikket::Launch.new(domain: "example.com") + assert_equal :set, launch.type + assert_equal NS, launch.query.namespace.href + assert_equal "launch", launch.query.node_name + assert_equal( + "example.com", + launch.query.at_xpath("./ns:domain", ns: NS).content + ) + end + + def test_launched + launched = Blather::XMPPNode.parse(<<~XML) + + + + fZLy6iTh + + si-12345 + + + XML + assert_equal :result, launched.type + assert_equal NS, launched.query.namespace.href + assert_equal "launched", launched.query.node_name + assert_equal( + "https://example.com/invites_bootstrap?token=fZLy6iTh", + launched.bootstrap_uri("example.com") + ) + assert_equal "si-12345", launched.instance_id + end + + def test_instance_get + instance = Snikket::Instance.new(instance_id: "si-1234") + assert_equal :get, instance.type + assert_equal NS, instance.query.namespace.href + assert_equal "instance", instance.query.node_name + assert_equal( + "si-1234", + instance.query.at_xpath("./ns:instance-id", ns: NS).content + ) + end + + def test_instance_result + instance = Blather::XMPPNode.parse(<<~XML) + + + + si-1234 + + 50 + running + + up + + + XML + assert_equal :result, instance.type + assert_equal NS, instance.query.namespace.href + assert_equal "instance", instance.query.node_name + assert_equal "si-1234", instance.instance_id + assert instance.update_needed? + assert_kind_of Nokogiri::XML::Element, instance.operation + assert_equal :up, instance.status + end +end