~singpolyma/sgx-jmp

cd3111be9878c7733c3657cb58bc1392d2792b59 — Stephen Paul Weber 8 months ago b31d693 + 92ed217
Merge branch 'snikket'

* snikket:
  Initial bare-bones admin command for launching a snikket instance
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