2 Commits

Author SHA1 Message Date
784671b0cc Merge branch 'develop'
Some checks failed
Gitea Actions Demo / test (push) Blocked by required conditions
Gitea Actions Demo / release-image (push) Blocked by required conditions
Gitea Actions Demo / lint (push) Has been cancelled
2025-03-04 10:30:31 -06:00
7250aaa43e remove ingress
All checks were successful
Gitea Actions Demo / lint (push) Successful in 2m58s
Gitea Actions Demo / test (push) Successful in 2m13s
Gitea Actions Demo / release-image (push) Successful in 4m41s
2025-03-04 10:29:50 -06:00
32 changed files with 224 additions and 534 deletions

View File

@ -1,2 +0,0 @@
ARG VARIANT="3.4.2"
FROM ghcr.io/rails/devcontainer/images/ruby:${VARIANT}

View File

@ -1,4 +0,0 @@
#!/bin/bash
bundle update --bundler
bundler install

View File

@ -1,12 +0,0 @@
---
services:
kubernaut:
build:
context: .
dockerfile: Dockerfile
volumes:
- ..:/workspace:cached
- /var/run/docker.sock:/var/run/docker-host.sock
command: sleep infinity
memcached:
image: memcached:latest

View File

@ -1,20 +0,0 @@
{
"dockerComposeFile": "compose.yml",
"service": "kubernaut",
"workspaceFolder": "/workspace",
"customizations": {
"vscode": {
"extensions": [
"Shopify.ruby-lsp",
"ms-azuretools.vscode-docker"
]
}
},
"postCreateCommand": ".devcontainer/boot.sh",
"forwardPorts": [
4567
],
"features": {
"ghcr.io/devcontainers/features/docker-outside-of-docker:1": {}
}
}

View File

@ -1,16 +1,7 @@
--- ---
name: Gitea Actions Demo name: Gitea Actions Demo
run-name: ${{ gitea.actor }} is testing out Gitea Actions 🚀 run-name: ${{ gitea.actor }} is testing out Gitea Actions 🚀
on: [push]
on:
schedule:
- cron: "0 10 * * *"
push:
branches:
- "**"
tags:
- "v*.*.*"
pull_request:
jobs: jobs:
lint: lint:
@ -25,7 +16,7 @@ jobs:
- name: Ruby Setup - name: Ruby Setup
uses: ruby/setup-ruby@v1 uses: ruby/setup-ruby@v1
with: with:
ruby-version: '3.4' ruby-version: '3.3'
bundler-cache: true bundler-cache: true
- run: bundle install - run: bundle install
@ -44,12 +35,13 @@ jobs:
- name: Test - name: Test
uses: ruby/setup-ruby@v1 uses: ruby/setup-ruby@v1
with: with:
ruby-version: '3.4' ruby-version: '3.3'
bundler-cache: true bundler-cache: true
- run: bundle exec rake - run: bundle exec rspec
docker:
release-image:
needs: test needs: test
runs-on: ubuntu-latest runs-on: ubuntu-latest
container: container:
@ -63,21 +55,6 @@ jobs:
with: with:
fetch-depth: 0 # all history for all branches and tags fetch-depth: 0 # all history for all branches and tags
- name: Docker meta
id: meta
uses: docker/metadata-action@v5
with:
images: |
git.kill0.net/ryanc/kubernaut
tags: |
type=schedule
type=ref,event=branch
type=ref,event=pr
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}}
type=sha
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3 uses: docker/setup-buildx-action@v3
@ -91,6 +68,5 @@ jobs:
- name: Docker build and push - name: Docker build and push
uses: docker/build-push-action@v5 uses: docker/build-push-action@v5
with: with:
push: ${{ github.event_name != 'pull_request' }} push: true
tags: ${{ steps.meta.outputs.tags }} tags: git.kill0.net/ryanc/kipunji:latest
labels: ${{ steps.meta.outputs.labels }}

4
.gitignore vendored
View File

@ -1,3 +1,5 @@
.bundle .bundle
.cache
.local
.ruby-lsp .ruby-lsp
/vendor .ash_history

View File

@ -1,9 +0,0 @@
{
"[ruby]": {
"editor.defaultFormatter": "Shopify.ruby-lsp"
},
"rubyLsp.formatter": "standard",
"rubyLsp.linters": [
"standard"
],
}

View File

@ -9,6 +9,8 @@ RUN <<EOT
apk upgrade --no-cache apk upgrade --no-cache
EOT EOT
RUN mkdir -p /run/app
FROM base AS build FROM base AS build
@ -23,12 +25,25 @@ RUN <<EOT
bundle install bundle install
EOT EOT
FROM build AS dev
WORKDIR /app
RUN <<EOT
bundle install
EOT
CMD [ "sleep", "infinity" ]
FROM base FROM base
# RUN useradd ruby --home /app --shell /bin/sh # RUN useradd ruby --home /app --shell /bin/sh
RUN adduser ruby -h /app -D RUN adduser ruby -h /app -D
RUN mkdir -p /run/app
RUN chown ruby:ruby /run/app
USER ruby:ruby USER ruby:ruby
COPY --from=build /usr/local/bundle /usr/local/bundle COPY --from=build /usr/local/bundle /usr/local/bundle

View File

@ -12,13 +12,12 @@ gem "ulid"
gem "uuid7" gem "uuid7"
gem "jwt" gem "jwt"
gem "httparty"
group :development do group :development do
gem "ruby-lsp" gem "ruby-lsp"
gem "rubocop"
gem "rbs"
gem "rack-test" gem "rack-test"
gem "rspec" gem "rspec"
gem "standard" gem "standard"
gem "minitest"
gem "rake"
end end

View File

@ -2,127 +2,115 @@ GEM
remote: https://rubygems.org/ remote: https://rubygems.org/
specs: specs:
anyflake (0.0.1) anyflake (0.0.1)
ast (2.4.3) ast (2.4.2)
base64 (0.2.0) base64 (0.2.0)
bigdecimal (3.1.8) diff-lcs (1.5.1)
csv (3.3.0) json (2.7.2)
diff-lcs (1.6.0) jwt (2.8.2)
httparty (0.22.0)
csv
mini_mime (>= 1.0.0)
multi_xml (>= 0.5.2)
json (2.10.2)
jwt (2.10.1)
base64 base64
ksuid (1.0.0) ksuid (1.0.0)
language_server-protocol (3.17.0.4) language_server-protocol (3.17.0.3)
lint_roller (1.1.0) lint_roller (1.1.0)
logger (1.6.6) logger (1.6.0)
mini_mime (1.1.5)
minitest (5.25.4)
multi_json (1.15.0) multi_json (1.15.0)
multi_xml (0.7.1) mustermann (3.0.0)
bigdecimal (~> 3.1)
mustermann (3.0.3)
ruby2_keywords (~> 0.0.1) ruby2_keywords (~> 0.0.1)
nanoid (2.0.0) nanoid (2.0.0)
nio4r (2.7.4) nio4r (2.7.3)
parallel (1.26.3) parallel (1.25.1)
parser (3.3.7.2) parser (3.3.3.0)
ast (~> 2.4.1) ast (~> 2.4.1)
racc racc
prism (1.3.0) prism (0.30.0)
puma (6.6.0) puma (6.4.2)
nio4r (~> 2.0) nio4r (~> 2.0)
racc (1.8.1) racc (1.8.0)
rack (3.1.11) rack (3.1.3)
rack-protection (4.1.1) rack-protection (4.0.0)
base64 (>= 0.1.0) base64 (>= 0.1.0)
logger (>= 1.6.0)
rack (>= 3.0.0, < 4) rack (>= 3.0.0, < 4)
rack-session (2.1.0) rack-session (2.0.0)
base64 (>= 0.1.0)
rack (>= 3.0.0) rack (>= 3.0.0)
rack-test (2.2.0) rack-test (2.1.0)
rack (>= 1.3) rack (>= 1.3)
rackup (2.2.1) rackup (2.1.0)
rack (>= 3) rack (>= 3)
webrick (~> 1.8)
rainbow (3.1.1) rainbow (3.1.1)
rake (13.2.1) rbs (3.5.1)
rbs (3.8.1)
logger logger
regexp_parser (2.10.0) regexp_parser (2.9.2)
rexml (3.3.0)
strscan
rspec (3.13.0) rspec (3.13.0)
rspec-core (~> 3.13.0) rspec-core (~> 3.13.0)
rspec-expectations (~> 3.13.0) rspec-expectations (~> 3.13.0)
rspec-mocks (~> 3.13.0) rspec-mocks (~> 3.13.0)
rspec-core (3.13.3) rspec-core (3.13.0)
rspec-support (~> 3.13.0) rspec-support (~> 3.13.0)
rspec-expectations (3.13.3) rspec-expectations (3.13.1)
diff-lcs (>= 1.2.0, < 2.0) diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.13.0) rspec-support (~> 3.13.0)
rspec-mocks (3.13.2) rspec-mocks (3.13.1)
diff-lcs (>= 1.2.0, < 2.0) diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.13.0) rspec-support (~> 3.13.0)
rspec-support (3.13.2) rspec-support (3.13.1)
rubocop (1.73.2) rubocop (1.64.1)
json (~> 2.3) json (~> 2.3)
language_server-protocol (~> 3.17.0.2) language_server-protocol (>= 3.17.0)
lint_roller (~> 1.1.0)
parallel (~> 1.10) parallel (~> 1.10)
parser (>= 3.3.0.2) parser (>= 3.3.0.2)
rainbow (>= 2.2.2, < 4.0) rainbow (>= 2.2.2, < 4.0)
regexp_parser (>= 2.9.3, < 3.0) regexp_parser (>= 1.8, < 3.0)
rubocop-ast (>= 1.38.0, < 2.0) rexml (>= 3.2.5, < 4.0)
rubocop-ast (>= 1.31.1, < 2.0)
ruby-progressbar (~> 1.7) ruby-progressbar (~> 1.7)
unicode-display_width (>= 2.4.0, < 4.0) unicode-display_width (>= 2.4.0, < 3.0)
rubocop-ast (1.41.0) rubocop-ast (1.31.3)
parser (>= 3.3.7.2) parser (>= 3.3.1.0)
rubocop-performance (1.24.0) rubocop-performance (1.21.1)
lint_roller (~> 1.1) rubocop (>= 1.48.1, < 2.0)
rubocop (>= 1.72.1, < 2.0) rubocop-ast (>= 1.31.1, < 2.0)
rubocop-ast (>= 1.38.0, < 2.0) ruby-lsp (0.17.3)
ruby-lsp (0.23.11)
language_server-protocol (~> 3.17.0) language_server-protocol (~> 3.17.0)
prism (>= 1.2, < 2.0) prism (>= 0.29.0, < 0.31)
rbs (>= 3, < 4) rbs (>= 3, < 4)
sorbet-runtime (>= 0.5.10782) sorbet-runtime (>= 0.5.10782)
ruby-progressbar (1.13.0) ruby-progressbar (1.13.0)
ruby2_keywords (0.0.5) ruby2_keywords (0.0.5)
sinatra (4.1.1) sinatra (4.0.0)
logger (>= 1.6.0)
mustermann (~> 3.0) mustermann (~> 3.0)
rack (>= 3.0.0, < 4) rack (>= 3.0.0, < 4)
rack-protection (= 4.1.1) rack-protection (= 4.0.0)
rack-session (>= 2.0.0, < 3) rack-session (>= 2.0.0, < 3)
tilt (~> 2.0) tilt (~> 2.0)
sinatra-contrib (4.1.1) sinatra-contrib (4.0.0)
multi_json (>= 0.0.2) multi_json (>= 0.0.2)
mustermann (~> 3.0) mustermann (~> 3.0)
rack-protection (= 4.1.1) rack-protection (= 4.0.0)
sinatra (= 4.1.1) sinatra (= 4.0.0)
tilt (~> 2.0) tilt (~> 2.0)
sorbet-runtime (0.5.11911) sorbet-runtime (0.5.11435)
standard (1.47.0) standard (1.39.1)
language_server-protocol (~> 3.17.0.2) language_server-protocol (~> 3.17.0.2)
lint_roller (~> 1.0) lint_roller (~> 1.0)
rubocop (~> 1.73.0) rubocop (~> 1.64.0)
standard-custom (~> 1.0.0) standard-custom (~> 1.0.0)
standard-performance (~> 1.7) standard-performance (~> 1.4)
standard-custom (1.0.2) standard-custom (1.0.2)
lint_roller (~> 1.0) lint_roller (~> 1.0)
rubocop (~> 1.50) rubocop (~> 1.50)
standard-performance (1.7.0) standard-performance (1.4.0)
lint_roller (~> 1.1) lint_roller (~> 1.1)
rubocop-performance (~> 1.24.0) rubocop-performance (~> 1.21.0)
tilt (2.6.0) strscan (3.1.0)
tilt (2.3.0)
ulid (1.4.0) ulid (1.4.0)
unicode-display_width (3.1.4) unicode-display_width (2.5.0)
unicode-emoji (~> 4.0, >= 4.0.4)
unicode-emoji (4.0.4)
uuid7 (0.2.0) uuid7 (0.2.0)
zeitwerk (~> 2.4) zeitwerk (~> 2.4)
zeitwerk (2.7.2) webrick (1.8.1)
zeitwerk (2.6.15)
PLATFORMS PLATFORMS
ruby ruby
@ -130,16 +118,15 @@ PLATFORMS
DEPENDENCIES DEPENDENCIES
anyflake anyflake
httparty
jwt jwt
ksuid ksuid
minitest
nanoid nanoid
puma puma
rack-test rack-test
rackup rackup
rake rbs
rspec rspec
rubocop
ruby-lsp ruby-lsp
sinatra sinatra
sinatra-contrib sinatra-contrib
@ -148,4 +135,4 @@ DEPENDENCIES
uuid7 uuid7
BUNDLED WITH BUNDLED WITH
2.6.6 2.5.13

View File

@ -1,11 +0,0 @@
begin
require "rspec/core/rake_task"
RSpec::Core::RakeTask.new(:spec)
rescue LoadError
end
require "minitest/test_task"
Minitest::TestTask.create
task default: [:test, :spec]

234
app.rb
View File

@ -5,7 +5,6 @@ require "sinatra/multi_route"
require "time" require "time"
require "fileutils" require "fileutils"
require "json" require "json"
require "singleton"
require "securerandom" require "securerandom"
require "random/formatter" require "random/formatter"
@ -14,21 +13,12 @@ require "anyflake"
require "jwt" require "jwt"
require "httparty"
$LOAD_PATH.unshift File.dirname(__FILE__) + "/lib"
require "config"
CHUNK_SIZE = 1024**2
SESSION_SECRET_HEX_LENGTH = 64 SESSION_SECRET_HEX_LENGTH = 64
JWT_SECRET_HEX_LENGTH = 64
DEFAULT_FLAKEY = 50
NAME = "kubernaut".freeze set :session_secret, ENV.fetch("SESSION_SECRET") { SecureRandom.hex(SESSION_SECRET_HEX_LENGTH) }
ENV_PREFIX = NAME.upcase
CLK_TCK = 100 CLK_TCK = 100
PID_FILE_PATH = "/run/app/pid".freeze
PROC_UPTIME_PATH = "/proc/uptime".freeze PROC_UPTIME_PATH = "/proc/uptime".freeze
SECONDS_PER_YEAR = 31_556_952 SECONDS_PER_YEAR = 31_556_952
@ -48,10 +38,7 @@ DURATION_PARTS = [
[1, "second", "s"] [1, "second", "s"]
].freeze ].freeze
config = Config.new JWT_SECRET = SecureRandom.bytes(64).freeze
set :session_secret, config.session_secret.unwrap
set :public_folder, __dir__ + "/static"
module Sinatra module Sinatra
module RequestHeadersHelper module RequestHeadersHelper
@ -109,7 +96,7 @@ end
class TickTock class TickTock
def initialize def initialize
@pid = ppid @pid = master_pid
@procfs_f = format "/proc/%s/stat", @pid @procfs_f = format "/proc/%s/stat", @pid
puts @pid puts @pid
end end
@ -135,7 +122,7 @@ class Health
include UpDown include UpDown
def initialize def initialize
@file = "/dev/shm/healthy" @file = "./healthy"
end end
def healthy? def healthy?
@ -149,7 +136,7 @@ class Ready
include UpDown include UpDown
def initialize def initialize
@file = "/dev/shm/ready" @file = "./ready"
end end
def ready? def ready?
@ -162,7 +149,7 @@ class Sleep
include State include State
def initialize def initialize
@file = "/dev/shm/sleep" @file = "./sleep"
end end
def asleep? def asleep?
@ -178,22 +165,9 @@ class Sleep
end end
end end
def ppid def master_pid
pid = Process.pid pid_s = File.read PID_FILE_PATH
# self Integer pid_s.strip
ps = File.open "/proc/#{pid}/stat", &:readline
ps = ps.split(" ")
ppid = Integer(ps[3])
# ppid
ps = File.open "/proc/#{ppid}/stat", &:readline
ps = ps.split(" ")
if ps[1].include? "ruby"
ppid
else
pid
end
end end
def system_uptime def system_uptime
@ -221,13 +195,6 @@ Health.instance.up
Ready.instance.up Ready.instance.up
Sleep.instance.wake Sleep.instance.wake
def flaky(pct = DEFAULT_FLAKEY)
r = Random.rand(0..100)
unless r < (100 - pct)
halt 500, "so unreliable"
end
end
enable :sessions enable :sessions
configure do configure do
@ -239,27 +206,15 @@ before do
sleep(1) while Sleep.instance.asleep? && request.path_info != "/livez/sleep" sleep(1) while Sleep.instance.asleep? && request.path_info != "/livez/sleep"
content_type :text if request.path_info.start_with? "/_cat" content_type :text if request.path_info.start_with? "/_cat"
request.session_options[:skip] = !request.path_info.start_with?("/session") request.session_options[:skip] = !request.path_info.start_with?("/session")
if params.has_key? :flaky
begin
pct = Integer(params[:flaky])
pct = pct.clamp(0, 100)
rescue => e
logger.warn "#{e.message}: falling back to default flaky percentage of #{DEFAULT_FLAKEY}"
pct = DEFAULT_FLAKEY
end
flaky(pct)
end
end end
helpers do helpers do
def jsonify(obj, opts: nil, pretty: false) def json(obj, opts: nil, pretty: false)
buf = if pretty if pretty
JSON.pretty_generate obj, opts: JSON.pretty_generate obj, opts:
else else
JSON.generate(obj, opts:) JSON.generate(obj, opts:)
end end
"#{buf}\n"
end end
def protected! hidden = false def protected! hidden = false
@ -280,10 +235,6 @@ helpers do
@auth.credentials and @auth.credentials and
@auth.credentials == ["qwer", "asdf"] @auth.credentials == ["qwer", "asdf"]
end end
def hostname
ENV["HOSTNAME"]
end
end end
get "/" do get "/" do
@ -293,14 +244,14 @@ end
get "/env", provides: "json" do get "/env", provides: "json" do
pretty = params.key? :pretty pretty = params.key? :pretty
jsonify ENV.sort.to_h, pretty: json ENV.sort.to_h, pretty:
end end
get "/headers", provides: "json" do get "/headers", provides: "json" do
pretty = params.key? :pretty pretty = params.key? :pretty
h = req_headers h = req_headers
jsonify h, pretty: json h, pretty:
end end
get "/livez" do get "/livez" do
@ -315,7 +266,7 @@ get "/livez/uptime" do
tt = TickTock.new tt = TickTock.new
x = {started_at: tt.started_at, seconds: tt.uptime.to_i, human: human_time(tt.uptime.to_i)} x = {started_at: tt.started_at, seconds: tt.uptime.to_i, human: human_time(tt.uptime.to_i)}
jsonify x json x
end end
post "/livez/toggle" do post "/livez/toggle" do
@ -367,19 +318,19 @@ get "/snowflake" do
end end
post "/quit" do post "/quit" do
Process.kill("TERM", ppid) Process.kill("TERM", master_pid)
nil nil
end end
post "/halt" do post "/halt" do
Process.kill("QUIT", ppid) Process.kill("QUIT", master_pid)
nil nil
end end
get "/pid" do get "/pid" do
pretty = params.key? :pretty pretty = params.key? :pretty
jsonify({ppid: ppid, pid: Process.pid}, pretty:) json({puma: master_pid, pid: Process.pid}, pretty:)
end end
get "/token" do get "/token" do
@ -389,51 +340,31 @@ get "/token" do
token = JWT.encode payload, JWT_SECRET, "HS256" token = JWT.encode payload, JWT_SECRET, "HS256"
x = {token: token, expires_at: expires_at} x = {token: token, expires_at: expires_at}
jsonify x json x
end end
get "/token/validate" do get "/token/validate" do
token = req_headers["authorization"].split[1] token = req_headers["authorization"].split[1]
payload = JWT.decode token, JWT_SECRET, true, algorithm: "HS256" payload = JWT.decode token, JWT_SECRET, true, algorithm: "HS256"
jsonify payload json payload
end end
post "/session" do post "/session" do
session.merge! params session.merge! params
jsonify session.to_hash json session.to_hash
end end
get "/session" do get "/session" do
j = session.to_hash j = session.to_hash
j[:hostname] = ENV["HOSTNAME"] j[:hostname] = ENV["HOSTNAME"]
jsonify j json j
end end
get "/cookies" do get "/cookies" do
jsonify response.headers json response.headers
end
get "/config", provides: "json" do
pretty = params.key? :pretty
jsonify config.as_json, pretty:
end
get "/_cat" do
stream do |out|
out << "=^.^=\n"
x = Sinatra::Application.routes.map do |method, route|
route.map do |route|
route.first.to_s
end
end
x.flatten.sort.uniq.each do |route|
out << "#{route}\n" if route.start_with? "/_cat"
end
end
end end
get "/_cat/headers" do get "/_cat/headers" do
@ -460,29 +391,6 @@ get "/_cat/cookies" do
end end
end end
get "/_cat/config" do
stream do |out|
config.instance_variables.each do |k|
k = k.to_s.delete_prefix "@"
begin
v = config.send(k)
rescue NoMethodError
next
end
out << "#{k}(#{v.to_s.length})=#{v}\n"
end
end
end
get "/_cat/pid" do
stream do |out|
{ppid: ppid, pid: Process.pid}.sort.each do |k, v|
out << "#{k}=#{v}\n"
end
end
end
route :delete, :get, :patch, :post, :put, "/status/:code" do route :delete, :get, :patch, :post, :put, "/status/:code" do
# hello # hello
code = Integer(params[:code]) code = Integer(params[:code])
@ -490,14 +398,11 @@ route :delete, :get, :patch, :post, :put, "/status/:code" do
end end
get "/chunked/:delay" do get "/chunked/:delay" do
content_type "application/x-ndjson"
delay = Float(params[:delay]) delay = Float(params[:delay])
stream do |out| stream do |out|
30.times do |i| out << "Hello, world!\n"
out << jsonify({id: i, message: (i % 2).zero? ? "tick" : "tock"}) sleep delay
sleep delay out << "Hello, world!\n"
end
end end
end end
@ -510,90 +415,5 @@ route :delete, :get, :patch, :post, :put, "/auth/basic", provides: "json" do
protected! protected!
end end
jsonify({authenticated: true, user: @auth.username}, pretty:) json({authenticated: true, user: @auth.username}, pretty:)
end
def human_size_to_bytes(size)
units = %i[b kb mb gb tb pb eb zb yb rb qb]
number, unit = size.split(/(?<=\d)(?=[A-Za-z])/)
raise ArgumentError, "the unit is not recognized" if unit.nil?
number = Float(number)
unit = unit.downcase.to_sym
exponent = units.find_index(unit)
number *= (1024**exponent)
Integer(number.ceil)
end
MAX_DOWNLOAD_SIZE = "1GB"
get "/bytes/:size" do
size = params[:size]
n = [human_size_to_bytes(size), human_size_to_bytes(MAX_DOWNLOAD_SIZE)].min
headers["content-type"] = "application/octet-stream"
headers["content-length"] = n
headers["content-disposition"] = "attachment; filename=\"#{params[:f]}\"" if params.key? :f
def generate_bytes(number, byte = "\x00", block_size = 4096)
raise ArgumentError, "'byte' must be 1 byte" unless byte.b.length == 1
bytes_written = 0
block = byte * block_size
Enumerator.new do |g|
while bytes_written < number
remaining_bytes = number - bytes_written
bytes_to_write = [block_size, remaining_bytes].min
g.yield block[0, bytes_to_write]
bytes_written += bytes_to_write
end
end
end
generate_bytes(Integer(n))
end
get "/api/caas" do
send_file Dir[__dir__ + "/static/cat*.jpg"].sample
end
get "/meow" do
caas_host = ENV.fetch "CAAS_SERVICE_HOST", nil
caas_port = ENV.fetch "CAAS_SERVICE_PORT", nil
url = "http://#{caas_host}:#{caas_port}/"
unless caas_host && caas_port
url = url("/api/caas")
end
tmp_file = Tempfile.open binmode: true do |f|
# f.chmod 0o644
response = HTTParty.get(url, stream_body: true) do |fragment|
if [301, 302].include? fragment.code
print "skip writing for redirect"
elsif fragment.code == 200
f.write fragment
else
raise StandardError, "non-success status code while streaming #{fragment.code}"
end
end
content_type response.headers["content-type"]
f
end
tmp_file.open do |f|
stream do |out|
out << f.read
until f.eof?
out << f.read(CHUNK_SIZE)
end
end
ensure
f.close
f.unlink
end
end end

3
config/puma.rb Normal file
View File

@ -0,0 +1,3 @@
# workers 3
pidfile "/run/app/pid"
preload_app!

15
docker-compose.yml Normal file
View File

@ -0,0 +1,15 @@
services:
web:
build:
context: .
target: dev
ports:
- "4567:4567"
volumes:
- .:/app
environment:
{}
# WEB_CONCURRENCY: 3
command:
- sleep
- infinity

View File

@ -2,7 +2,7 @@
apiVersion: v1 apiVersion: v1
kind: ConfigMap kind: ConfigMap
metadata: metadata:
name: kubernaut-configmap name: kipunji-configmap
namespace: kubernaut namespace: kipunji
data: data:
KUBERNAUT_CAT: kilwin KIPUNJI_CAT: kilwin

View File

@ -2,21 +2,22 @@
apiVersion: apps/v1 apiVersion: apps/v1
kind: Deployment kind: Deployment
metadata: metadata:
name: kubernaut name: kipunji
annotations: annotations:
reloader.stakater.com/auto: "true" reloader.stakater.com/auto: "true"
spec: spec:
replicas: 5
selector: selector:
matchLabels: matchLabels:
app: kubernaut app: kipunji
template: template:
metadata: metadata:
labels: labels:
app: kubernaut app: kipunji
spec: spec:
containers: containers:
- name: kubernaut - name: kipunji
image: git.kill0.net/ryanc/kubernaut:latest image: git.kill0.net/ryanc/kipunji:latest
imagePullPolicy: Always imagePullPolicy: Always
ports: ports:
- name: sinatra-web - name: sinatra-web
@ -25,12 +26,12 @@ spec:
- name: SESSION_SECRET - name: SESSION_SECRET
valueFrom: valueFrom:
secretKeyRef: secretKeyRef:
name: kubernaut-session-secret name: kipunji-session-secret
key: session_secret key: session_secret
optional: true optional: true
envFrom: envFrom:
- configMapRef: - configMapRef:
name: kubernaut-configmap name: kipunji-configmap
livenessProbe: livenessProbe:
httpGet: httpGet:
path: /livez path: /livez

View File

@ -1,19 +0,0 @@
---
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: kubernaut
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: kubernaut
minReplicas: 2
maxReplicas: 5
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 50

View File

@ -1,10 +1,10 @@
--- ---
apiVersion: kustomize.config.k8s.io/v1beta1 apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization kind: Kustomization
namespace: kubernaut namespace: kipunji
resources: resources:
- secret.yaml - secret.yaml
- configmap.yaml - configmap.yaml
- deployment.yaml - deployment.yaml
- hpa.yaml
- services.yaml - services.yaml
- ingress.yaml

View File

@ -3,13 +3,13 @@ apiVersion: bitnami.com/v1alpha1
kind: SealedSecret kind: SealedSecret
metadata: metadata:
creationTimestamp: null creationTimestamp: null
name: kubernaut-session-secret name: kipunji-session-secret
namespace: kubernaut namespace: kipunji
spec: spec:
encryptedData: encryptedData:
session_secret: AgCY08t0AU418znEZt5d252J+lH+fwYki2g6jdJpfdRfVQjnA+b52P0KWrs/x5pB0PKab6Z3JY/Tz0SQCaoIsCR4IzUO3a095aulRqb6Qr1Lz8udBVta4JJMZLmo26tuUfVHlpD1d6J8rkBSm8vzckFLkOA1Wfl/9rS3K4qwiDogA5pI0ULghFkeEx1yKdRwPq0k8PuvOvLUJ6oNq3e5n+B/BrVWdQ+7XQxUq/AMANJrDbe+RD33f99LArHYA7bFMbY8YRazXSTAkeunpTlxTjuGZKYvJKupo29LHz2OVbZVX/hI0nZkdVpcgqvbxF6Vw9CuCeAmtKYl7A3qsAWqDLUdP3hRLsk2P9RDNhEzYWh4ml8APzziWzihdJbGEjwLy7HsHgKslM0XbBnRQDlxp/JtvcWdjQp33A+QOON32zOKHi+qJjDYyGebS1+xkPbnyb1MPSJVAtFpj7dlLbFekLFDZEbXuJYUl1wKdFOIjJHmNK/MTEV2kOhtiVj/aeKgSXwor9hR7Uxzs5ZSawp9uWw+hpr58EX6I+RtfO4yjFC6FjnagiU6SlI1Q2F7/nv82g1UWTYMpNN5bduS1YFWmsnXvK+W7YQHpSForr5ndtCSHmclbXb5Fc33sywC5u6Bi2Gu5/MW6d73BOog5BC3QtOuEQ044Q+cuU3RIlKADBqKLzZmHlmukyyGuZfXJnGjlWGKp3J1KecucTo6XC9QHpUkjXEKdlE63mOI1VuOGyBIHl60v4bnWiBg+aDZVHipz4JLKsVB0HOgBBK7+tOX6tr1GDG/F7Nz/i9ebzUV6i8Ec1jHf+2ZcTtBkNXBIkHc84+4Qd33/gOuP+lizLfIhfQ3DFWbwyfYumpVbeapyYhB0CE= session_secret: AgCY08t0AU418znEZt5d252J+lH+fwYki2g6jdJpfdRfVQjnA+b52P0KWrs/x5pB0PKab6Z3JY/Tz0SQCaoIsCR4IzUO3a095aulRqb6Qr1Lz8udBVta4JJMZLmo26tuUfVHlpD1d6J8rkBSm8vzckFLkOA1Wfl/9rS3K4qwiDogA5pI0ULghFkeEx1yKdRwPq0k8PuvOvLUJ6oNq3e5n+B/BrVWdQ+7XQxUq/AMANJrDbe+RD33f99LArHYA7bFMbY8YRazXSTAkeunpTlxTjuGZKYvJKupo29LHz2OVbZVX/hI0nZkdVpcgqvbxF6Vw9CuCeAmtKYl7A3qsAWqDLUdP3hRLsk2P9RDNhEzYWh4ml8APzziWzihdJbGEjwLy7HsHgKslM0XbBnRQDlxp/JtvcWdjQp33A+QOON32zOKHi+qJjDYyGebS1+xkPbnyb1MPSJVAtFpj7dlLbFekLFDZEbXuJYUl1wKdFOIjJHmNK/MTEV2kOhtiVj/aeKgSXwor9hR7Uxzs5ZSawp9uWw+hpr58EX6I+RtfO4yjFC6FjnagiU6SlI1Q2F7/nv82g1UWTYMpNN5bduS1YFWmsnXvK+W7YQHpSForr5ndtCSHmclbXb5Fc33sywC5u6Bi2Gu5/MW6d73BOog5BC3QtOuEQ044Q+cuU3RIlKADBqKLzZmHlmukyyGuZfXJnGjlWGKp3J1KecucTo6XC9QHpUkjXEKdlE63mOI1VuOGyBIHl60v4bnWiBg+aDZVHipz4JLKsVB0HOgBBK7+tOX6tr1GDG/F7Nz/i9ebzUV6i8Ec1jHf+2ZcTtBkNXBIkHc84+4Qd33/gOuP+lizLfIhfQ3DFWbwyfYumpVbeapyYhB0CE=
template: template:
metadata: metadata:
creationTimestamp: null creationTimestamp: null
name: kubernaut-session-secret name: kipunji-session-secret
namespace: kubernaut namespace: kipunji

View File

@ -2,11 +2,11 @@
apiVersion: v1 apiVersion: v1
kind: Service kind: Service
metadata: metadata:
name: kubernaut name: kipunji
spec: spec:
ports: ports:
- name: web - name: web
port: 80 port: 80
targetPort: sinatra-web targetPort: sinatra-web
selector: selector:
app: kubernaut app: kipunji

View File

@ -2,7 +2,8 @@
apiVersion: kustomize.config.k8s.io/v1beta1 apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization kind: Kustomization
metadata: metadata:
name: kubernaut name: kipunji
namespace: kipunji
resources: resources:
- namespace.yaml - namespace.yaml
- ./app - ./app

View File

@ -2,19 +2,19 @@
kind: Deployment kind: Deployment
apiVersion: apps/v1 apiVersion: apps/v1
metadata: metadata:
name: kubernaut-memcached name: kipunji-memcached
spec: spec:
selector: selector:
matchLabels: matchLabels:
app: kubernaut-memcached app: kipunji-memcached
template: template:
metadata: metadata:
labels: labels:
app: kubernaut-memcached app: kipunji-memcached
spec: spec:
containers: containers:
- name: kubernaut-memcached - name: kipunji-memcached
image: memcached:latest image: memcached:latest
ports: ports:
- name: memcached - name: memcached

View File

@ -1,7 +1,7 @@
--- ---
apiVersion: kustomize.config.k8s.io/v1beta1 apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization kind: Kustomization
namespace: kubernaut namespace: kipunji
resources: resources:
- deployment.yaml - deployment.yaml
- services.yaml - services.yaml

View File

@ -2,7 +2,7 @@
apiVersion: v1 apiVersion: v1
kind: Service kind: Service
metadata: metadata:
name: kubernaut-memcached name: kipunji-memcached
spec: spec:
ports: ports:
@ -10,4 +10,4 @@ spec:
port: 11211 port: 11211
targetPort: memcached targetPort: memcached
selector: selector:
app: kubernaut-memcached app: kipunji-memcached

View File

@ -2,6 +2,5 @@
apiVersion: v1 apiVersion: v1
kind: Namespace kind: Namespace
metadata: metadata:
name: kubernaut name: kipunji
labels: {}

View File

@ -1,49 +0,0 @@
require "sensitive"
class Config
attr_accessor :cat
attr_reader :jwt_secret, :session_secret
def initialize(prefix = ENV_PREFIX, jwt_secret = nil, session_secret = nil, cat = nil)
@prefix = prefix
@cat = cat
session_secret ||= ENV.fetch "SESSION_SECRET" do
SecureRandom.hex SESSION_SECRET_HEX_LENGTH
end
jwt_secret ||= fetch_env "JWT_SECRET" do
SecureRandom.hex JWT_SECRET_HEX_LENGTH
end
@session_secret = Sensitive.new session_secret
@jwt_secret = Sensitive.new jwt_secret
@cat ||= ENV.fetch "#{@prefix}_CAT", nil
end
def fetch_env(name, &)
ENV.fetch "#{@prefix}_#{name}", &
end
def as_json(options = nil)
{jwt_secret: jwt_secret, session_secret: @session_secret, cat: @cat}
end
def to_json(options = nil)
if options&.key?(:pretty) &&
options[:pretty] == true
JSON.pretty_generate as_json(options)
else
JSON.generate as_json(options)
end
end
def session_secret=(v)
@session_secret = Sensitive.new v
end
def jwt_secret=(v)
@jwt_secret = Sensitive.new v
end
end

View File

@ -1,39 +0,0 @@
class Sensitive
alias_method :eql?, :==
alias_method :equal?, :==
def initialize(v, ch: "*", head: 2, tail: 2)
@v = v
@ch = ch
@head = head
@tail = tail
end
def mask(v)
+"".concat(v[0, @head], @ch * (v.length - (@head + @tail)), v[-@tail, @tail])
end
def unwrap
@v
end
def length
@v.length
end
def to_s
mask @v
end
def inspect
"#<#{self.class.name} @v=#{wrap}>"
end
def hash
@v.hash
end
def ==(other)
other.is_a?(Sensitive) && other.hash == hash
end
end

63
sig/app.rbs Normal file
View File

@ -0,0 +1,63 @@
module State
def enable: () -> untyped
def disable: () -> (untyped | nil)
def enabled?: () -> bool
def toggle: () -> untyped
end
module UpDown
def up: () -> untyped
def down: () -> untyped
def to_s: () -> ("up" | "down")
def to_json: (*untyped _args) -> (nil | untyped)
end
class Health
@file: untyped
include Singleton
include State
include UpDown
def initialize: () -> void
def healthy?: () -> bool
end
class Ready
@file: untyped
include Singleton
include State
include UpDown
def initialize: () -> void
def ready?: () -> bool
end
class Sleep
@file: untyped
include Singleton
include State
def initialize: () -> void
def asleep?: () -> bool
def wake: () -> untyped
def sleep: () -> untyped
end

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 626 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 156 KiB

View File

@ -1,26 +0,0 @@
require "minitest/autorun"
$LOAD_PATH.unshift File.dirname(__FILE__) + "/../lib"
require "sensitive"
ALPHABET = ("a".."z").reduce(:concat)
class TestSensitive < Minitest::Test
def setup
@s = Sensitive.new ALPHABET
end
def test_initialize
assert_equal @s.to_s, "ab" + "*" * 22 + "yz"
end
def test_unwrap
assert_equal @s.unwrap, ALPHABET
end
def test_using_a_different_mask_character
s = Sensitive.new ALPHABET, ch: "x"
assert_equal s.to_s, "ab" + "x" * 22 + "yz"
end
end