M config-schema.dhall => config-schema.dhall +1 -0
@@ 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 >
M config.dhall.sample => config.dhall.sample +1 -0
@@ 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" }
}
A forms/snikket_launch.rb => forms/snikket_launch.rb +9 -0
@@ 0,0 1,9 @@
+form!
+
+title "Launch Snikket Instance"
+
+field(
+ var: "domain",
+ label: "Domain for Instance",
+ type: "text-single"
+)
A forms/snikket_launched.rb => forms/snikket_launched.rb +19 -0
@@ 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)
+)
A lib/snikket.rb => lib/snikket.rb +104 -0
@@ 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
M sgx_jmp.rb => sgx_jmp.rb +32 -0
@@ 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
A test/test_snikket.rb => test/test_snikket.rb +73 -0
@@ 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)
+ <iq type="result" from="hosting-api.snikket.net" id="123">
+ <launched xmlns="xmpp:snikket.org/hosting/v1">
+ <bootstrap>
+ <token>fZLy6iTh</token>
+ </bootstrap>
+ <instance-id>si-12345</instance-id>
+ </launched>
+ </iq>
+ 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)
+ <iq type="result" from="hosting-api.snikket.net" id="000">
+ <instance xmlns="xmpp:snikket.org/hosting/v1">
+ <update-needed/>
+ <instance-id>si-1234</instance-id>
+ <operation>
+ <progress>50</progress>
+ <status>running</status>
+ </operation>
+ <status>up</status>
+ </instance>
+ </iq>
+ 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