Skip to content

Commit

Permalink
project based on spider-gazelle template
Browse files Browse the repository at this point in the history
  • Loading branch information
gabriel-aires committed Nov 22, 2021
0 parents commit 21dc707
Show file tree
Hide file tree
Showing 18 changed files with 774 additions and 0 deletions.
2 changes: 2 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Auto detect text files and perform LF normalization
* text=auto
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
lib
app
bin
.DS_Store
373 changes: 373 additions & 0 deletions LICENSE

Large diffs are not rendered by default.

21 changes: 21 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# os-probe

Dead simple dashboard for machine metrics and jobs

## Testing

`crystal spec`

* to run in development mode `crystal ./src/app.cr`

## Compiling

`crystal build ./src/app.cr`

### Deploying

Once compiled you are left with a binary `./app`

* for help `./app --help`
* viewing routes `./app --routes`
* run on a different port or host `./app -b 0.0.0.0 -p 80`
62 changes: 62 additions & 0 deletions shard.lock
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
version: 2.0
shards:
action-controller:
git: https://github.com/spider-gazelle/action-controller.git
version: 4.5.1

active-model:
git: https://github.com/spider-gazelle/active-model.git
version: 3.1.1

ameba:
git: https://github.com/veelenga/ameba.git
version: 0.14.3

cron_parser:
git: https://github.com/kostya/cron_parser.git
version: 0.4.0

future:
git: https://github.com/crystal-community/future.cr.git
version: 1.0.0

habitat:
git: https://github.com/luckyframework/habitat.git
version: 0.4.7

hardware:
git: https://github.com/crystal-community/hardware.git
version: 0.5.2

http-params-serializable:
git: https://github.com/place-labs/http-params-serializable.git
version: 0.5.0

json_mapping:
git: https://github.com/crystal-lang/json_mapping.cr.git
version: 0.1.1

kilt:
git: https://github.com/jeromegn/kilt.git
version: 0.6.1

lucky_router:
git: https://github.com/luckyframework/lucky_router.git
version: 0.5.1

popcorn:
git: https://github.com/icyleaf/popcorn.git
version: 0.3.0

tasker:
git: https://github.com/spider-gazelle/tasker.git
version: 2.0.5

totem:
git: https://github.com/icyleaf/totem.git
version: 0.7.0

yaml_mapping:
git: https://github.com/crystal-lang/yaml_mapping.cr.git
version: 0.1.1

41 changes: 41 additions & 0 deletions shard.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
name: os-probe
version: 0.1.0

authors:
- name [email protected]

description: |
Dead simple dashboard for machine metrics and jobs
dependencies:
action-controller:
github: spider-gazelle/action-controller
version: ~> 4.0
active-model:
github: spider-gazelle/active-model
version: ~> 3.0
tasker:
github: spider-gazelle/tasker
version: ~> 2.0
future:
github: crystal-community/future.cr
version: ~> 1.0
kilt:
github: jeromegn/kilt
version: ~> 0.6
totem:
github: icyleaf/totem
version: ~> 0.7
hardware:
github: crystal-community/hardware
version: ~> 0.5

development_dependencies:
ameba:
github: veelenga/ameba

targets:
app:
main: src/app.cr

license: MPL v2
8 changes: 8 additions & 0 deletions spec/spec_helper.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
require "spec"

# Your application config
# If you have a testing environment, replace this with a test config file
require "../src/config"

# Helper methods for testing controllers (curl, with_server, context)
require "../lib/action-controller/spec/curl_context"
25 changes: 25 additions & 0 deletions spec/welcome_spec.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
require "./spec_helper"

describe Welcome do
# ==============
# Unit Testing
# ==============
it "should generate a date string" do
# instantiate the controller you wish to unit test
welcome = Welcome.new(context("GET", "/"))

# Test the instance methods of the controller
welcome.set_date_header.should contain("GMT")
end

# ==============
# Test Responses
# ==============
with_server do
it "should welcome you" do
result = curl("GET", "/")
result.body.includes?("You're being trampled by Spider-Gazelle!").should eq(true)
result.headers["Date"]?.nil?.should eq(false)
end
end
end
90 changes: 90 additions & 0 deletions src/app.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
require "option_parser"
require "./constants"

# Server defaults
port = App::DEFAULT_PORT
host = App::DEFAULT_HOST
process_count = App::DEFAULT_PROCESS_COUNT

# Command line options
OptionParser.parse(ARGV.dup) do |parser|
parser.banner = "Usage: #{PROGRAM_NAME} [arguments]"

parser.on("-b HOST", "--bind=HOST", "Specifies the server host") { |h| host = h }
parser.on("-p PORT", "--port=PORT", "Specifies the server port") { |p| port = p.to_i }

parser.on("-w COUNT", "--workers=COUNT", "Specifies the number of processes to handle requests") do |w|
process_count = w.to_i
end

parser.on("-r", "--routes", "List the application routes") do
ActionController::Server.print_routes
exit 0
end

parser.on("-v", "--version", "Display the application version") do
puts "#{App::NAME} v#{App::VERSION}"
exit 0
end

parser.on("-c URL", "--curl=URL", "Perform a basic health check by requesting the URL") do |url|
begin
response = HTTP::Client.get url
exit 0 if (200..499).includes? response.status_code
puts "health check failed, received response code #{response.status_code}"
exit 1
rescue error
error.inspect_with_backtrace(STDOUT)
exit 2
end
end

parser.on("-h", "--help", "Show this help") do
puts parser
exit 0
end
end

# Load the routes
puts "Launching #{App::NAME} v#{App::VERSION}"

# Requiring config here ensures that the option parser runs before
# attempting to connect to databases etc.
require "./config"
server = ActionController::Server.new(port, host)

# (process_count < 1) == `System.cpu_count` but this is not always accurate
# Clustering using processes, there is no forking once crystal threads drop
server.cluster(process_count, "-w", "--workers") if process_count != 1

terminate = Proc(Signal, Nil).new do |signal|
puts " > terminating gracefully"
spawn { server.close }
signal.ignore
end

# Detect ctr-c to shutdown gracefully
# Docker containers use the term signal
Signal::INT.trap &terminate
Signal::TERM.trap &terminate

# Allow signals to change the log level at run-time
logging = Proc(Signal, Nil).new do |signal|
level = signal.usr1? ? Log::Severity::Debug : Log::Severity::Info
puts " > Log level changed to #{level}"
Log.builder.bind "#{App::NAME}.*", level, App::LOG_BACKEND
signal.ignore
end

# Turn on DEBUG level logging `kill -s USR1 %PID`
# Default production log levels (INFO and above) `kill -s USR2 %PID`
Signal::USR1.trap &logging
Signal::USR2.trap &logging

# Start the server
server.run do
puts "Listening on #{server.print_addresses}"
end

# Shutdown message
puts "#{App::NAME} leaps through the veldt\n"
55 changes: 55 additions & 0 deletions src/config.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# Application dependencies
require "action-controller"
require "active-model"
require "kilt"
require "./constants"

# Application code
require "./controllers/application"
require "./controllers/*"
require "./models/*"

# Server required after application controllers
require "action-controller/server"

# Configure logging (backend defined in constants.cr)
if App.running_in_production?
log_level = Log::Severity::Info
::Log.setup "*", :warn, App::LOG_BACKEND
else
log_level = Log::Severity::Debug
::Log.setup "*", :info, App::LOG_BACKEND
end
Log.builder.bind "action-controller.*", log_level, App::LOG_BACKEND
Log.builder.bind "#{App::NAME}.*", log_level, App::LOG_BACKEND

# Filter out sensitive params that shouldn't be logged
filter_params = ["password", "bearer_token"]
keeps_headers = ["X-Request-ID"]

# Add handlers that should run before your application
ActionController::Server.before(
ActionController::ErrorHandler.new(App.running_in_production?, keeps_headers),
ActionController::LogHandler.new(filter_params),
HTTP::CompressHandler.new
)

# Optional support for serving of static assests
if File.directory?(App::STATIC_FILE_PATH)
# Optionally add additional mime types
::MIME.register(".yaml", "text/yaml")

# Check for files if no paths matched in your application
ActionController::Server.before(
::HTTP::StaticFileHandler.new(App::STATIC_FILE_PATH, directory_listing: false)
)
end

# Configure session cookies
# NOTE:: Change these from defaults
ActionController::Session.configure do |settings|
settings.key = App::COOKIE_SESSION_KEY
settings.secret = App::COOKIE_SESSION_SECRET
# HTTPS only:
settings.secure = App.running_in_production?
end
25 changes: 25 additions & 0 deletions src/constants.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
require "action-controller/logger"
require "secrets-env"

module App
NAME = "Spider-Gazelle"
VERSION = {{ `shards version "#{__DIR__}"`.chomp.stringify.downcase }}

Log = ::Log.for(NAME)
LOG_BACKEND = ActionController.default_backend

ENVIRONMENT = ENV["SG_ENV"]? || "development"

DEFAULT_PORT = (ENV["SG_SERVER_PORT"]? || 3000).to_i
DEFAULT_HOST = ENV["SG_SERVER_HOST"]? || "127.0.0.1"
DEFAULT_PROCESS_COUNT = (ENV["SG_PROCESS_COUNT"]? || 1).to_i

STATIC_FILE_PATH = ENV["PUBLIC_WWW_PATH"]? || "./www"

COOKIE_SESSION_KEY = ENV["COOKIE_SESSION_KEY"]? || "_spider_gazelle_"
COOKIE_SESSION_SECRET = ENV["COOKIE_SESSION_SECRET"]? || "4f74c0b358d5bab4000dd3c75465dc2c"

def self.running_in_production?
ENVIRONMENT == "production"
end
end
30 changes: 30 additions & 0 deletions src/controllers/application.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
require "uuid"

abstract class Application < ActionController::Base
# Configure your log source name
# NOTE:: this is chaining from App::Log
Log = ::App::Log.for("controller")

before_action :set_request_id
before_action :set_date_header

# This makes it simple to match client requests with server side logs.
# When building microservices this ID should be propagated to upstream services.
def set_request_id
request_id = UUID.random.to_s
Log.context.set(
client_ip: client_ip,
request_id: request_id
)
response.headers["X-Request-ID"] = request_id

# If this is an upstream service, the ID should be extracted from a request header.
# request_id = request.headers["X-Request-ID"]? || UUID.random.to_s
# Log.context.set client_ip: client_ip, request_id: request_id
# response.headers["X-Request-ID"] = request_id
end

def set_date_header
response.headers["Date"] = HTTP.format_time(Time.utc)
end
end
Loading

0 comments on commit 21dc707

Please sign in to comment.