332 lines
5.4 KiB
Ruby
332 lines
5.4 KiB
Ruby
|
require "bundler/setup"
|
||
|
require "sinatra"
|
||
|
require "sinatra/json"
|
||
|
require "sinatra/cookies"
|
||
|
require "time"
|
||
|
require "fileutils"
|
||
|
require "json"
|
||
|
|
||
|
require "securerandom"
|
||
|
require "random/formatter"
|
||
|
require "ulid"
|
||
|
require "anyflake"
|
||
|
|
||
|
require "jwt"
|
||
|
|
||
|
SESSION_SECRET_HEX_LENGTH = 64
|
||
|
|
||
|
set :session_secret, ENV.fetch('SESSION_SECRET') { SecureRandom.hex(SESSION_SECRET_HEX_LENGTH) }
|
||
|
|
||
|
CLK_TCK = 100
|
||
|
PID_FILE_PATH = "/run/pid".freeze
|
||
|
PROC_UPTIME_PATH = "/proc/uptime".freeze
|
||
|
|
||
|
SECONDS_PER_YEAR = 31_556_952
|
||
|
SECONDS_PER_MINUTE = 60
|
||
|
SECONDS_PER_HOUR = 3_600
|
||
|
SECONDS_PER_DAY = 86_400
|
||
|
SECONDS_PER_WEEK = 604_800
|
||
|
SECONDS_PER_MONTH = SECONDS_PER_YEAR / 12
|
||
|
|
||
|
DURATION_PARTS = [
|
||
|
[SECONDS_PER_YEAR, "year", "y"],
|
||
|
[SECONDS_PER_MONTH, "month", "m"],
|
||
|
[SECONDS_PER_WEEK, "week", "w"],
|
||
|
[SECONDS_PER_DAY, "day", "d"],
|
||
|
[SECONDS_PER_HOUR, "hour", "h"],
|
||
|
[SECONDS_PER_MINUTE, "minute", "m"],
|
||
|
[1, "second", "s"],
|
||
|
].freeze
|
||
|
|
||
|
JWT_SECRET = SecureRandom.bytes(64).freeze
|
||
|
|
||
|
module Sinatra
|
||
|
module RequestHeadersHelper
|
||
|
def req_headers
|
||
|
hash = request.env.select { |k, _| k.start_with? "HTTP_" }
|
||
|
.collect { |k, v| [k.gsub(/^HTTP_/, "").gsub(/_/, "-").downcase, v] }
|
||
|
.sort
|
||
|
h = Rack::Headers.new
|
||
|
h.merge hash
|
||
|
end
|
||
|
end
|
||
|
|
||
|
helpers RequestHeadersHelper
|
||
|
end
|
||
|
|
||
|
module State
|
||
|
def enable
|
||
|
FileUtils.touch(@file)
|
||
|
end
|
||
|
|
||
|
def disable
|
||
|
File.delete @file if File.file? @file
|
||
|
end
|
||
|
|
||
|
def enabled?
|
||
|
File.file? @file
|
||
|
end
|
||
|
|
||
|
def toggle
|
||
|
return enable unless enabled?
|
||
|
|
||
|
disable
|
||
|
end
|
||
|
end
|
||
|
|
||
|
module UpDown
|
||
|
def up
|
||
|
enable
|
||
|
end
|
||
|
|
||
|
def down
|
||
|
disable
|
||
|
end
|
||
|
|
||
|
def to_s
|
||
|
enabled? ? "up" : "down"
|
||
|
end
|
||
|
|
||
|
def to_json(*_args)
|
||
|
return unless enabled?
|
||
|
|
||
|
JSON.generate({ "status" => "ok" })
|
||
|
end
|
||
|
end
|
||
|
|
||
|
class TickTock
|
||
|
def initialize
|
||
|
@pid = master_pid
|
||
|
@procfs_f = format "/proc/%s/stat", @pid
|
||
|
puts @pid
|
||
|
end
|
||
|
|
||
|
def uptime
|
||
|
x = ""
|
||
|
File.open @procfs_f do |f|
|
||
|
f.each_line do |l|
|
||
|
x = l.strip.split
|
||
|
end
|
||
|
end
|
||
|
system_uptime - (Integer(x[21]) / CLK_TCK)
|
||
|
end
|
||
|
|
||
|
def started_at
|
||
|
Time.now - uptime
|
||
|
end
|
||
|
end
|
||
|
|
||
|
class Health
|
||
|
include Singleton
|
||
|
include State
|
||
|
include UpDown
|
||
|
|
||
|
def initialize
|
||
|
@file = "./healthy"
|
||
|
end
|
||
|
|
||
|
def healthy?
|
||
|
enabled?
|
||
|
end
|
||
|
end
|
||
|
|
||
|
class Ready
|
||
|
include Singleton
|
||
|
include State
|
||
|
include UpDown
|
||
|
|
||
|
def initialize
|
||
|
@file = "./ready"
|
||
|
end
|
||
|
|
||
|
def ready?
|
||
|
enabled?
|
||
|
end
|
||
|
end
|
||
|
|
||
|
class Sleep
|
||
|
include Singleton
|
||
|
include State
|
||
|
|
||
|
def initialize
|
||
|
@file = "./sleep"
|
||
|
end
|
||
|
|
||
|
def asleep?
|
||
|
enabled?
|
||
|
end
|
||
|
|
||
|
def wake
|
||
|
disable
|
||
|
end
|
||
|
|
||
|
def sleep
|
||
|
enable
|
||
|
end
|
||
|
end
|
||
|
|
||
|
def master_pid
|
||
|
pid_s = File.read PID_FILE_PATH
|
||
|
Integer pid_s.strip
|
||
|
end
|
||
|
|
||
|
def system_uptime
|
||
|
uptime_s = File.read PROC_UPTIME_PATH
|
||
|
Float uptime_s.strip.split[0]
|
||
|
end
|
||
|
|
||
|
##
|
||
|
# Convert to human friendly time interval
|
||
|
#
|
||
|
# @param [Integer] time in seconds
|
||
|
def human_time(seconds, delim = " ")
|
||
|
s = []
|
||
|
DURATION_PARTS.each do |divisor, unit, abbrev|
|
||
|
q = seconds / divisor
|
||
|
r = seconds % divisor
|
||
|
s.push "#{q}#{abbrev}" if q.positive?
|
||
|
seconds = r
|
||
|
end
|
||
|
|
||
|
s.join delim
|
||
|
end
|
||
|
|
||
|
Health.instance.up
|
||
|
Ready.instance.up
|
||
|
Sleep.instance.wake
|
||
|
|
||
|
enable :sessions
|
||
|
|
||
|
configure do
|
||
|
mime_type :json, "application/json"
|
||
|
end
|
||
|
|
||
|
before do
|
||
|
# content_type 'text/plain'
|
||
|
sleep(1) while Sleep.instance.asleep? unless request.path_info == "/livez/sleep"
|
||
|
end
|
||
|
|
||
|
get "/" do
|
||
|
"hello there!\n"
|
||
|
end
|
||
|
|
||
|
get "/env" do
|
||
|
stream do |out|
|
||
|
ENV.sort.each do |k, v|
|
||
|
out << "#{k}=#{v}\n"
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
get "/headers", provides: "json" do
|
||
|
h = req_headers
|
||
|
return JSON.pretty_generate h if params.key? "pretty"
|
||
|
|
||
|
JSON.generate h
|
||
|
end
|
||
|
|
||
|
get "/headers" do
|
||
|
stream do |out|
|
||
|
req_headers.each do |k, v|
|
||
|
out << "#{k}: #{v.inspect}\n"
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
get "/livez" do
|
||
|
error 503 unless Health.instance.healthy?
|
||
|
|
||
|
return Health.instance.to_json if request.env["HTTP_ACCEPT"] == "application/json"
|
||
|
|
||
|
Health.instance.to_s
|
||
|
end
|
||
|
|
||
|
get "/livez/uptime" do
|
||
|
tt = TickTock.new
|
||
|
x = { started_at: tt.started_at, seconds: tt.uptime.to_i, human: human_time(tt.uptime.to_i) }
|
||
|
json x
|
||
|
end
|
||
|
|
||
|
post "/livez/toggle" do
|
||
|
Health.instance.toggle
|
||
|
"ok\n"
|
||
|
end
|
||
|
|
||
|
post "/livez/sleep" do
|
||
|
Sleep.instance.toggle
|
||
|
"ok\n"
|
||
|
end
|
||
|
|
||
|
get "/uuid" do
|
||
|
n = params.fetch(:n, 1).to_i
|
||
|
stream do |out|
|
||
|
n.times do |_|
|
||
|
out << format("%s\n", SecureRandom.uuid)
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
get "/ulid" do
|
||
|
n = params.fetch(:n, 1).to_i
|
||
|
stream do |out|
|
||
|
n.times do |_|
|
||
|
out << format("%s\n", ULID.generate)
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
get "/snowflake" do
|
||
|
n = params.fetch(:n, 1).to_i
|
||
|
epoch = Time.new(2016, 7, 1, 0, 0, 0).strftime("%s%L").to_i
|
||
|
node_id = 1
|
||
|
af = AnyFlake.new(epoch, node_id)
|
||
|
stream do |out|
|
||
|
n.times do |_|
|
||
|
out << format("%s\n", af.next_id)
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
post "/quit" do
|
||
|
Process.kill("TERM", master_pid)
|
||
|
nil
|
||
|
end
|
||
|
|
||
|
post "/halt" do
|
||
|
Process.kill("QUIT", master_pid)
|
||
|
nil
|
||
|
end
|
||
|
|
||
|
get "/pid" do
|
||
|
JSON.generate({ puma: master_pid, pid: Process.pid })
|
||
|
end
|
||
|
|
||
|
get "/token" do
|
||
|
exp = Time.now.to_i + SECONDS_PER_MINUTE * 2
|
||
|
payload = { name: "anonymous", exp: exp, jti: Random.uuid }
|
||
|
expires_at = Time.at(exp).to_datetime
|
||
|
token = JWT.encode payload, JWT_SECRET, "HS256"
|
||
|
token
|
||
|
x = { :token => token, :expires_at => expires_at }
|
||
|
json x
|
||
|
end
|
||
|
|
||
|
get "/token/validate" do
|
||
|
token = req_headers["authorization"].split[1]
|
||
|
payload = JWT.decode token, JWT_SECRET, true, algorithm: "HS256"
|
||
|
json payload
|
||
|
end
|
||
|
|
||
|
post "/session" do
|
||
|
session.merge! params
|
||
|
json session.to_hash
|
||
|
end
|
||
|
|
||
|
get "/session" do
|
||
|
json session.to_hash
|
||
|
end
|
||
|
|
||
|
get "/cookies" do
|
||
|
json response.headers
|
||
|
end
|