Last active
February 24, 2018 17:30
-
-
Save njbartlett/5e79d4ad69eda7719305171e801ec2a9 to your computer and use it in GitHub Desktop.
Docker Container Reaper in Ponylang (a learning exercise)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
use "collections" | |
use "json" | |
use "net" | |
use "net/http" | |
use "time" | |
actor Main | |
""" | |
This is my learning exercise for the Pony language (ponylang.org). It is a | |
port of a Java application that: | |
* Maintains a Map of ID to Timestamp. | |
* Periodically scans the entire Map and kills the Docker containers | |
corresponding to those IDs with a Timestamp that is too old. | |
* Clients can query the content of the Map via an HTTP GET request. | |
* Clients can add/update an ID in the Map with an HTTP PUT request. The ID | |
should be atomically inserted or updated in the Map with the current time. | |
""" | |
let defaultHost: String = "0.0.0.0" | |
let defaultPort: String = "8080" | |
let defaultLimit: USize = 100 | |
new create(env: Env) => | |
serve(env, env.root) | |
fun serve(env: Env, rootAuth: AmbientAuth) => | |
let manager : ManagedIds = ManagedIds | |
Server( | |
where auth = NetAuth.create(rootAuth) | |
, host = try env.args(1) else defaultHost end | |
, service = try env.args(1) else defaultPort end | |
, limit = try env.args(2).usize() else defaultLimit end | |
, handler = HttpHandler(manager) | |
, notify = InfoListener(env) | |
, logger = CommonLog(env.out) | |
) | |
fun serve(env: Env, rootAuth: None) => | |
env.err.print("No authority to run") | |
actor ManagedIds | |
""" | |
This actor owns the Map and runs the periodic scan-and-kill (not yet | |
implemented). | |
""" | |
let _map: Map[String, Timestamp] = _map.create() | |
.add("foo", Timestamp.now()) | |
.add("bar", Timestamp.now()) | |
be list(fn: {ref(String)} iso) => | |
var rows: List[String] = List[String] | |
for (key, ts) in _map.pairs() do | |
var date = ts.formatISO8601() | |
var row = JsonObject.from_map(Map[String, JsonType] | |
.add("id", key) | |
.add("expiry", date) | |
).string(where pretty_print=true) | |
rows.push(row) | |
end | |
(consume fn)("blah") // TODO: haven't worked out the string join yet | |
class HttpHandler | |
let _manager: ManagedIds | |
new val create(manager: ManagedIds) => | |
_manager = manager | |
fun apply(request: Payload) if request.method == "GET" => | |
""" | |
Handle GET requests | |
""" | |
_manager.list( | |
object iso | |
var _request: Payload = consume request | |
fun ref apply(rows: String) => | |
let response: Payload = Payload.response() | |
response.add_chunk(rows) | |
response.add_chunk("\n") | |
let r: Payload = _request = Payload.request() | |
(consume r).respond(consume response) | |
end) | |
/* | |
fun apply(request: Payload) if request.method == "PUT" => | |
""" | |
Handle PUT requests | |
""" | |
//_managedIDs.insert(request.url.path, Timestamp.now()) | |
//_managedIDs = _managedIDs.add(request.url.path, Timestamp.now()) | |
let response = Payload.response(StatusOK) | |
(consume request).respond(consume response) | |
*/ | |
fun apply(request: Payload) => | |
""" | |
Handle all other HTTP methods and return 405 Not Allowed | |
""" | |
let response: Payload = Payload.response(StatusMethodNotAllowed) | |
(consume request).respond(consume response) | |
class InfoListener | |
let _env: Env | |
new iso create(env: Env) => | |
_env = env | |
fun ref listening(server: Server ref) => | |
try | |
(let host, let service) = server.local_address().name() | |
_env.out.print("Listening on " + host + ":" + service) | |
else | |
_env.out.print("Couldn't get local address.") | |
server.dispose() | |
end | |
fun ref not_listening(server: Server ref) => | |
_env.out.print("Failed to listen.") | |
fun ref closed(server: Server ref) => | |
_env.out.print("Shutdown.") | |
class val Timestamp | |
let _seconds: I64 | |
let _nanos: I64 | |
new val now() => | |
(_seconds, _nanos) = Time.now() | |
fun formatISO8601(): String val => | |
Date(_seconds, _nanos).format("%FT%TZ") |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
@jemc: Thanks, I'm glad it's not just me!