Compare commits

...

13 Commits

Author SHA1 Message Date
21893fe612 Beginnings of making frontend pretty, switched to pico css 2024-07-09 20:37:43 -03:00
665b4f9ab7 Fix proxy config confusion 2024-07-09 16:40:15 -03:00
3b2297e954 Housekeeping 2024-07-09 13:23:21 -03:00
4f4daf5943 Types tweak 2024-07-09 13:23:16 -03:00
2d333c3df1 * More robust stderr/stdout mixing when
running commandsserver side
* More robust streaming responses client side
2024-07-09 13:01:37 -03:00
f015afe7f0 Ensure network exists for start-proxy 2024-07-09 10:57:36 -03:00
81ec077928 Use base58 2024-07-08 22:34:38 -03:00
afb6e8df0c Dockerfile linter 2024-07-08 22:34:27 -03:00
50e8ff7e56 add check to ensure config/funkos is empty 2024-07-08 22:34:07 -03:00
6489ec0dc2 Use base58 for random strings 2024-07-08 22:33:52 -03:00
56ff326098 Lint 2024-07-08 22:02:47 -03:00
80ea5d4fde add check to ensure config/funkos is empty 2024-07-08 21:53:57 -03:00
67c37a49e1 Refactored log setup onto separate project 2024-07-08 21:27:40 -03:00
31 changed files with 256 additions and 222 deletions

3
.hadolint.yml Normal file
View File

@ -0,0 +1,3 @@
ignored:
- DL3018
- DL3059

38
.pre-commit-config.yaml Normal file
View File

@ -0,0 +1,38 @@
# See https://pre-commit.com for more information
# See https://pre-commit.com/hooks.html for more hooks
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.4.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
- id: check-yaml
- id: check-added-large-files
- id: check-merge-conflict
- repo: https://github.com/jumanjihouse/pre-commit-hooks
rev: 3.0.0
hooks:
- id: shellcheck
- id: markdownlint
exclude: '^content'
- repo: https://github.com/mrtazz/checkmake
rev: 0.2.2
hooks:
- id: checkmake
- repo: https://github.com/python-jsonschema/check-jsonschema
rev: 0.23.2
hooks:
- id: check-github-workflows
- repo: local
hooks:
- id: empty-funkos
name: empty-funkos
entry: test ! -s config/funkos
language: system
pass_filenames: false
- repo: https://github.com/hadolint/hadolint
rev: v2.12.0
hooks:
- id: hadolint-docker
exclude: 'j2$'

View File

@ -1,5 +1,13 @@
FROM --platform=${TARGETPLATFORM:-linux/amd64} alpine as build
RUN apk update && apk add crystal shards yaml-dev openssl-dev zlib-dev libxml2-dev make && apk cache clean
FROM --platform=${TARGETPLATFORM:-linux/amd64} alpine:3.20 AS build
RUN apk add --no-cache \
crystal \
shards \
yaml-dev \
openssl-dev \
zlib-dev \
libxml2-dev \
make
RUN rm -rf /var/cache/apk/*
RUN addgroup -S app && adduser app -S -G app
WORKDIR /home/app
COPY shard.yml Makefile ./
@ -9,8 +17,22 @@ COPY runtimes/ runtimes/
RUN make
# RUN strip bin/*
FROM --platform=${TARGETPLATFORM:-linux/amd64} alpine as ship
RUN apk update && apk add caddy nss-tools multirun docker openssl zlib yaml pcre2 gc libevent libgcc libxml2 ttyd && apk cache clean
FROM --platform=${TARGETPLATFORM:-linux/amd64} alpine:3.20 AS ship
RUN apk add --no-cache \
caddy \
nss-tools \
multirun \
docker \
openssl \
zlib \
yaml \
pcre2 \
gc \
libevent \
libgcc \
libxml2 \
ttyd
RUN rm -rf /var/cache/apk/*
# Unprivileged user
RUN addgroup -S app && adduser app -S -G app
@ -21,7 +43,6 @@ COPY public/ public/
COPY --from=build /home/app/bin/faaso-daemon /home/app/bin/faaso /usr/bin/
# Mount points for persistent data
RUN mkdir /secrets
RUN mkdir /config
RUN mkdir /secrets /config
CMD ["/usr/bin/multirun", "-v", "faaso-daemon", "caddy run --config config/Caddyfile"]

View File

@ -2,9 +2,13 @@ build: shard.yml $(wildcard src/**/*) $(runtimes/**/*)
shards build -d --error-trace
cat .rucksack >> bin/faaso
cat .rucksack >> bin/faaso-daemon
proxy: build
proxy:
docker build . -t faaso-proxy
all: build proxy
start-proxy:
docker network create faaso-net || true
docker run --name faaso-proxy-one \
--rm --network=faaso-net \
-e FAASO_SECRET_PATH=${PWD}/secrets \
@ -13,5 +17,10 @@ start-proxy:
-v ${PWD}/config:/home/app/config \
-p 8888:8888 faaso-proxy
test:
crystal spec
.PHONY: build proxy-image start-proxy
clean:
rm bin/*
.PHONY: all build proxy-image start-proxy test clean

View File

@ -50,22 +50,25 @@ This will give you:
You need a server, with docker. In that server, build it as explained above.
You can run the `faaso-proxy` with something like this:
```
docker run --network=faaso-net -v /var/run/docker.sock:/var/run/docker.sock -p 8888:8888 faaso-proxy
```shell
docker run --network=faaso-net \
-v /var/run/docker.sock:/var/run/docker.sock \
-p 8888:8888 faaso-proxy
```
That will give `faaso-proxy` access to your docker, and expose the functionality in
port 8888.
That will give `faaso-proxy` access to your docker, and expose the functionality
in port 8888.
## What it Does
### Funkos
In FaaSO you (the user) can create Funkos. Funkos are the moral equivalent of AWS
In FaaSO you (the user) can create Funkos. Funkos are the moral equivalent of AWS
lambdas and whatever they are called in other systems. In short, they are simple
programs that handle web requests.
For example, here is a `hello world` level funko written using Crystal, a file called `funko.cr`:
For example, here is a `hello world` level funko written using Crystal,
a file called `funko.cr`:
```crystal
get "/" do
@ -82,24 +85,26 @@ runtime: crystal
```
If you have those two files in a folder, that folder is a funko, which is called
`hello` and FaaSO knows it's written in Crystal. In fact, it knows (because the crystal runtime explains that, don't worry about it yet) that it's part of an
`hello` and FaaSO knows it's written in Crystal. In fact, it knows (because the
crystal runtime explains that, don't worry about it yet) that it's part of an
application written in the [Kemal framework](https://kemalcr.com/) and it knows
how to create a whole container which runs the app, and how to check its health,
and so on.
But the funko has *the interesting bits* of the app.
The full details of how to write funkos are still in flux, so not documenting
it for now. Eventually, you will be able to just write the parts you
The full details of how to write funkos are still in flux, so not documenting
it for now. Eventually, you will be able to just write the parts you
need to write to create funkos in different languages. It's easy!
### So what can a funko do?
### So what can a funko do
Once you have a funko, you can *build* it, which will give you a docker image.
```faaso build --local myfunko/```
Or you can export it and get rid of all the mistery of how your funko **really** works:
Or you can export it and get rid of all the mistery of how your funko
**really** works:
```faaso export myfunko/ myfuko-exported```
@ -116,9 +121,9 @@ than one, although currently only one is used by the proxy.
The proxy has a few goals:
1) You can connect to it using `faaso` and have it build/run/etc your funkos.
* This builds the funko in your machine: `faaso build -l myfunko/`
* This builds the funko in the server pointed at by FAASO_SERVER: `faaso build myfunko/`
* This builds the funko in the server pointed at by FAASO_SERVER:
`faaso build myfunko/`
Yes, they are exactly the same thing. In fact, if you don't use the `-l` flag,
faaso just tells the proxy "hey proxy, run *your* copy of faaso over there and
@ -127,10 +132,10 @@ The proxy has a few goals:
2) It automatically reverse-proxies to all funkos.
If you deployed a funko called `hello` and your faaso proxy is at
`http://myserver:8888` then the `/` path in your funko is at
`http://myserver:8888` then the `/` path in your funko is at
`http://myserver:8888/funko/hello/`
This proxying is automatic, you don't need to do anything. As long as you
This proxying is automatic, you don't need to do anything. As long as you
build the image for your funko in the server and then start the funko in the
server? It should work.
@ -145,4 +150,4 @@ beyond bugfixes, since I am redesigning things all the time.
## Contributors
- [Roberto Alsina](https://github.com/ralsina) - creator and maintainer
* [Roberto Alsina](https://github.com/ralsina) - creator and maintainer

22
TODO.md
View File

@ -1,4 +1,6 @@
# Things that need doing before first release
# TODO LIST
## Things that need doing before first release
* User flow for initial proxy setup
* ✅ Setting up password
@ -10,11 +12,12 @@
* ✅ Crystal + Kemal
* ✅ Python + Flask
* ✅ Nodejs + Express
* Document
* How to create a runtime
* How to create a funko
* How to setup the proxy
* APIs
* Create a site
* Document
* FaaSO for app developers
* FaaSO for runtime developers
* FaaSO server setup
* APIs
* Sanitize all inputs
* ✅ Streaming responses in slow operations like scaling down
or building
@ -32,8 +35,9 @@
* ✅ Fix `export examples/hello_crystal` it has a `template/`
* ✅ Implement zero-downtime rollout (`faaso deploy`)
* ✅ Cleanup `tmp/whatever` after use
* `faaso scale` remote is broken
* `faaso scale` remote is broken
* ✅ Setup linters/pre-commit/etc
# Things to do but not before release
## Things to do but not before release
* Propagate errors from `run_faaso` to the remote client
* Propagate errors from `run_faaso` to the remote client

View File

@ -18,5 +18,5 @@ http://*:8888 {
handle_path /admin/* {
reverse_proxy /* http://127.0.0.1:3000
}
import funkos
import funkos
}

View File

@ -38,7 +38,7 @@ up/downscaling, no multiple versions routed by header.
Specifically: no downscaling to zero. It makes everything MUCH
more complicated.
# Function structure
## Function structure
Example using crystal, but it could be anything. Any function has
an associated runtime, for example "crystal" or "python".
@ -69,7 +69,9 @@ Probably some `metadata.yml` that is *not* in the template.
* Files that should be copied along the function
* Whatever
# Implementation Ideas
## Implementation Ideas
* caddy for proxy? It's simple, fast, API-configurable.
* Local docker registry for images? See https://www.docker.com/blog/how-to-use-your-own-registry-2/ (maybe later)
* Local docker registry for images? See
[use own registry](https://www.docker.com/blog/how-to-use-your-own-registry-2/)
(maybe later)

View File

@ -15,34 +15,31 @@ Solution: start the funko on the server. Done. It's implemented.
## Variant 3: Deploy to the server and it's already running
1. If it's already running and it's running the latest image, then nothing to be done.
2. It it's running and is not the latest, we can stop it and start with the latest image.
1. If it's already running and it's running the latest image, then nothing
to be done.
2. It it's running and is not the latest, we can stop it and start with the
latest image.
* Action 2 causes downtime. Usually it will not be significant, but it's there.
* In the future it may be important to have zero downtime.
* We need to figure out what is implied by doing "zero downtime" to see if
not doing it now would make it impossible.
For zero downtime, we want to have two instances running, switch the proxy to the new
one, then stop the old one.
For zero downtime, we want to have two instances running, switch the proxy
to the new one, then stop the old one.
Currently it's impossible to run two instances because the container name is
faaso-funkoname, and we can't have 2 of those.
So: we could instead have faaso-funkoname-1, faaso-funkoname-2, etc. with some sort of suffix
So: we could instead have faaso-funkoname-1, faaso-funkoname-2, etc.
with some sort of suffix
Changes implied in the faaso code:
* If we have two containers for one funko, we need to consider the "state" of
* If we have two containers for one funko, we need to consider the "state" of
the funko differently.
* What does it mean to start/pause/stop a funko with two instances
* Do we want to enable two-instance funkos? With round-robin proxy?
* What happens if we have two instances with different images?
Answers coming up.

View File

@ -49,10 +49,12 @@ faaso-net.
The proxy is the only container exposed to the host network, everything
else needs to be accessed through it.
The faaso-proxy container will automatically proxy all requests so if you access the URL `http://faaso-proxy:8888/funko/hello/foo` that will be
The faaso-proxy container will automatically proxy all requests so if
you access the URL `http://faaso-proxy:8888/funko/hello/foo` that will be
proxied to `/foo` in the `hello` funko.
This is all done via naming conventions. You can create your own `faaso-whatever` container, add it to the `faaso-net` and faaso will
This is all done via naming conventions. You can create your own
`faaso-whatever` container, add it to the `faaso-net` and faaso will
happily consider it a funko.
In the same way all funkos are simply docker containers running in that
@ -78,4 +80,3 @@ faaso-proxy -- GET /bar --> faaso-funko1
The dynamic proxying is achieved by reading the current state of
Docker and just adapt to it using the naming conventions mentioned
above.

View File

@ -31,7 +31,7 @@ So, the proxy can periodically examine its secret store and populate a folder
If on starting a funko we always do a bind mount of `/secrets/foo` to `/secrets`
then it will always have its secrets in place.
## Problem 2: how can the proxy know the secrets without keeping them in the image?
## Problem 2: how can the proxy know the secrets without keeping them in the image
They can't be shipped via the image, so they need to be injected via the admin API.

View File

@ -8,4 +8,4 @@ Write here what it is
## How to use Hello_crystal
And so on.
And so on.

View File

@ -2,8 +2,8 @@
<head>
<meta charset="utf-8" />
<link rel="stylesheet" href="https://matcha.mizu.sh/matcha.css" />
<script src="https://unpkg.com/htmx.org@2.0.0"></script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@picocss/pico@1/css/pico.min.css" />
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@picocss/pico@2/css/pico.colors.min.css" /> <script src="https://unpkg.com/htmx.org@2.0.0"></script>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
@ -11,15 +11,20 @@
<header class="container">
<h1>FaaSO Admin Interface</h1>
</header>
<main class=container>
<h2>Your Funko Collection
<button id="update-funkos" style="float:right; display:inline;" hx-trigger="load, click, every 60s"
<article>
<nav>
<ul>
<li><strong style="font-size: 200%;">Your Funko Collection</strong></li>
</ul>
<ul>
<li><button id="update-funkos" style="float:right; display:inline;" hx-trigger="load, click, every 60s"
hx-get="funkos/?format=html" hx-target="#funko-list">
Refresh
</button>
</h2>
</ul>
</nav>
<span id="message"></span>
<table hx-target="#message">
<table hx-target="#message" class="striped">
<thead>
<tr>
<th>Name</th>
@ -31,23 +36,27 @@
</tbody>
</table>
<div id="terminal" style="resize: vertical; overflow: auto;"></div>
</main>
</article>
<script>
update_funkos = function () {
document.getElementById("update-funkos").click()
}
</script>
<main class=container>
<h2>
Your Secrets
<button id="update-secrets" style="float:right; display:inline;" hx-trigger="load, click, every 60s"
<article>
<nav>
<ul>
<li><strong style="font-size: 200%;">Your Secrets</strong>
</ul>
<ul>
<li><button id="update-secrets" style="float:right; display:inline;" hx-trigger="load, click, every 60s"
hx-get="secrets/?format=html" hx-target="#secret-list">
Refresh
</button>
<button style="float:right; display:inline;" onclick="show_new_secret()">
<li><button style="float:right; display:inline;" onclick="show_new_secret()">
Add
</button>
</h2>
</ul>
</nav>
<span id="message"></span>
<table hx-target="#message">
<thead>
@ -60,14 +69,20 @@
<tbody id="secret-list">
</tbody>
<dialog id="add-secret">
<topic>New Secret</topic>
<article>
<header>
New Secret
</header>
<form hx-post="secrets/">
<input placeholder="funko name" id="new-secret-funko" name="funko">
<input placeholder="secret name" id="new-secret-name" name="name">
<input placeholder="secret value" type="password" id="new-secret-password" name="value">
<button type="submit" hx-on:htmx:after-request="hide_new_secret()">CREATE</button>
<fieldset role="group" style="text-align: right;">
<button style="width:9em; display: inline;" type="submit" hx-on:htmx:after-request="hide_new_secret()">CREATE</button>
<button style="width:9em; display: inline;" onclick="hide_new_secret(); close();" aria-label="Close" rel="prev">CLOSE</button>
</fieldset>
</form>
<button onclick="hide_new_secret(); close();">CLOSE</button>
</article>
</dialog>
<script>
update_secrets = function() {
@ -87,5 +102,5 @@
update_secrets()
}
</script>
</main>
</body>
</article>
</body>

View File

@ -1,4 +1,4 @@
# README
This is the readme for people wanting to change this runtime,
not for people trying to use it
not for people trying to use it

View File

@ -12,4 +12,3 @@ dependencies:
# development_dependencies:
# webmock:
# github: manastech/webmock.cr

View File

@ -17,4 +17,4 @@ USER app
COPY --from=build /home/app/ .
CMD ["node", "funko.js"]
HEALTHCHECK {{ healthcheck_options }} CMD {{ healthcheck_command }}
HEALTHCHECK {{ healthcheck_options }} CMD {{ healthcheck_command }}

View File

@ -12,4 +12,4 @@ app.get('/ping', (req, res) => {
app.listen(port, () => {
console.log(`Example funko listening on port ${port}`)
})
})

View File

@ -19,4 +19,4 @@ USER app
COPY --from=build /home/app/ .
CMD ["venv/bin/uwsgi", "--http", "0.0.0.0:3000", "--master", "-p", "1", "-w", "funko:app"]
HEALTHCHECK {{ healthcheck_options }} CMD {{ healthcheck_command }}
HEALTHCHECK {{ healthcheck_options }} CMD {{ healthcheck_command }}

View File

@ -1 +1 @@
flask
flask

View File

@ -4,6 +4,10 @@ shards:
git: https://github.com/sija/backtracer.cr.git
version: 1.2.2
base58:
git: https://github.com/crystal-china/base58.cr.git
version: 0.1.0+git.commit.d1150d4a6f086013a475640ad00e561a2fe1082a
cr-config:
git: https://github.com/crystal-community/cr-config.git
version: 5.1.0+git.commit.5eae3dfbf97da7dfa7c6e64a2a508069948518d3
@ -40,10 +44,6 @@ shards:
git: https://github.com/mamantoha/http_proxy.git
version: 0.10.3
inotify:
git: https://github.com/petoem/inotify.cr.git
version: 1.0.3
kemal:
git: https://github.com/kemalcr/kemal.git
version: 1.5.0
@ -52,6 +52,10 @@ shards:
git: https://github.com/kemalcr/kemal-basic-auth.git
version: 1.0.0
oplog:
git: https://github.com/ralsina/oplog.git
version: 0.1.0+git.commit.70e3a7bbc2f1f4d75cf4e142244b263ee2844ba1
radix:
git: https://github.com/luislavena/radix.git
version: 0.4.1
@ -59,4 +63,3 @@ shards:
rucksack:
git: https://github.com/busyloop/rucksack.git
version: 2.0.0

View File

@ -15,6 +15,8 @@ crystal: ">= 1.12.2"
license: MIT
dependencies:
base58:
github: crystal-china/base58.cr
crest:
github: mamantoha/crest
crinja:
@ -28,12 +30,12 @@ dependencies:
docr:
github: ralsina/docr
branch: add_exposed_ports
inotify:
github: petoem/inotify.cr
kemal:
github: kemalcr/kemal
kemal-basic-auth:
github: kemalcr/kemal-basic-auth
oplog:
github: ralsina/oplog
rucksack:
github: busyloop/rucksack

View File

@ -1,3 +1,5 @@
require "base58"
module Faaso
module Commands
# Build images for one or more funkos from source
@ -7,7 +9,7 @@ module Faaso
# Create temporary build location
funkos.each do |funko|
tmp_dir = Path.new("tmp", UUID.random.to_s)
tmp_dir = Path.new("tmp", Random.base58(8))
Dir.mkdir_p(tmp_dir) unless File.exists? tmp_dir
funko.runtime = nil if options["--no-runtime"]
@ -56,10 +58,7 @@ module Faaso
{"funko.tgz" => File.open(tmp), "name" => "funko.tgz"},
user: user, password: password
) do |response|
loop do
Log.info { response.body_io.gets }
break if response.body_io.closed?
end
IO.copy(response.body_io, STDOUT)
end
Log.info { "Build finished successfully." }
rescue ex : Crest::InternalServerError

View File

@ -41,10 +41,7 @@ module Faaso
Crest.get(
"#{Config.server}funkos/#{funko_name}/deploy/", \
user: user, password: password) do |response|
loop do
Log.info { response.body_io.gets }
break if response.body_io.closed?
end
IO.copy(response.body_io, STDOUT)
end
0
end

View File

@ -10,14 +10,14 @@ module Faaso
# In both cases stopped instances after the required
# scale is reached are deleted.
struct Scale
def local(options, name : String, scale : Int) : Int32
def local(options, name : String, scale : Int | Nil) : Int32
funko = Funko::Funko.from_names([name])[0]
# Asked about scale
if funko.image_history.empty?
Log.error { "Unknown funko #{funko.name}" }
return 1
end
if !scale
if scale.nil?
Log.info { "Funko #{name} has a scale of #{funko.scale}" }
return 0
end
@ -26,19 +26,21 @@ module Faaso
0
end
def remote(options, name : String, scale : Int) : Int32
def remote(options, name : String, scale : Int | Nil) : Int32
user, password = Config.auth
Faaso.check_version
if !scale
response = Crest.get(
if scale.nil?
Crest.get(
"#{Config.server}funkos/#{name}/scale/", \
user: user, password: password)
Log.info { " => " + response.body }
else
response = Crest.post(
"#{Config.server}funkos/#{name}/scale/",
{"scale" => scale}, user: user, password: password)
Log.info { " => " + response.body }
user: user, password: password) do |response|
IO.copy(response.body_io, STDOUT)
end
return 0
end
Crest.post(
"#{Config.server}funkos/#{name}/scale/",
{"scale" => scale}, user: user, password: password) do |response|
IO.copy(response.body_io, STDOUT)
end
0
rescue ex : Crest::InternalServerError
@ -46,7 +48,8 @@ module Faaso
1
end
def run(options, name : String, scale : Int) : Int32
def run(options, name : String, scale) : Int32
scale = scale.try &.to_s.to_i
if options["--local"]
return local(options, name, scale)
end

View File

@ -31,10 +31,7 @@ module Faaso
Crest.get(
"#{Config.server}funkos/#{name}/status/", \
user: user, password: password) do |response|
loop do
Log.info { response.body_io.gets }
break if response.body_io.closed?
end
IO.copy(response.body_io, STDOUT)
end
0
rescue ex : Crest::InternalServerError

View File

@ -1,3 +1,4 @@
require "base58"
require "docr"
require "kemal"
require "../funko.cr"
@ -34,7 +35,7 @@ module Funko
# mosquito-cr/mosquito to make it a job queue
post "/funkos/build/" do |env|
# Create place to build funko
tmp_dir = Path.new("tmp", UUID.random.to_s)
tmp_dir = Path.new("tmp", Random.base58(8))
Dir.mkdir_p(tmp_dir) unless File.exists? tmp_dir
# Expand tarball in there
@ -132,20 +133,21 @@ module Funko
end
# Helper to run faaso locally and respond via env
def run_faaso(args : Array(String), env) : Bool
Log.info { "Running faaso [#{args.join(", ")}, -l, 2>&1]" }
def run_faaso(args : Array(String), env)
args << "-l" # Always local in the server
Log.info { "Running faaso [#{args}" }
Process.run(
command: "faaso",
args: args + ["-l", "2>&1"], # Always local in the server
shell: true,
args: args,
env: {"FAASO_SERVER_SIDE" => "true"},
) do |process|
loop do
env.response.print process.output.gets(chomp: false)
data = process.output.gets(chomp: false)
env.response.print data
env.response.flush
Fiber.yield
Fiber.yield # Without this the process never ends
break if process.terminated?
end
true
end
# FIXME: find a way to raise an exception on failure
# of the faaso process

View File

@ -1,16 +1,11 @@
require "./funko.cr"
require "docr"
require "inotify"
require "kemal"
module Proxy
CADDY_CONFIG_PATH = "config/funkos"
@@current_config = File.read(CADDY_CONFIG_PATH)
@@watcher = Inotify.watch(CADDY_CONFIG_PATH) do |_|
Log.info { "Reloading caddy config" }
Process.run(command: "caddy", args: ["reload", "--config", CADDY_CONFIG_PATH])
end
CADDY_CONFIG_PATH = "config/Caddyfile"
CADDY_CONFIG_FUNKOS = "config/funkos"
@@current_config = File.read(CADDY_CONFIG_FUNKOS)
# Get current proxy config
get "/proxy/" do
@ -25,7 +20,7 @@ module Proxy
update_proxy_config
end
def self.update_proxy_config
def self.update_proxy_config : Nil
docker_api = Docr::API.new(Docr::Client.new)
containers = docker_api.containers.list(all: true)
@ -50,13 +45,13 @@ module Proxy
if @@current_config != config
Log.info { "Updating proxy config" }
File.open(CADDY_CONFIG_PATH, "w") do |file|
File.open(CADDY_CONFIG_FUNKOS, "w") do |file|
file << config
end
# Reload config
@@current_config = config
Process.run(command: "caddy", args: ["reload", "--config", CADDY_CONFIG_PATH])
end
config
end
end

View File

@ -90,7 +90,11 @@ module Funko
docker_api = Docr::API.new(Docr::Client.new)
current_scale = self.scale
result = [] of String
return result if current_scale == new_scale
if current_scale == new_scale
Log.info { "Funko #{name} already at scale #{new_scale}" }
return result
end
Log.info { "Scaling #{name} from #{current_scale} to #{new_scale}" }
if new_scale > current_scale
@ -309,7 +313,7 @@ module Funko
)
docker_api = Docr::API.new(Docr::Client.new)
response = docker_api.containers.create(name: "faaso-#{name}-#{randstr}", config: conf)
response = docker_api.containers.create(name: "faaso-#{name}-#{Random.base58(6)}", config: conf)
response.@warnings.each { |msg| Log.warn { msg } }
docker_api.containers.start(response.@id) if autostart
response.@id
@ -349,8 +353,3 @@ module Funko
end
end
end
def randstr(length = 6) : String
chars = "abcdefghijklmnopqrstuvwxyz0123456789"
String.new(Bytes.new(chars.to_slice.sample(length).to_unsafe, length))
end

View File

@ -1,50 +0,0 @@
module Logging
extend self
class LogBackend < Log::IOBackend
@stdout = Log::IOBackend.new(io: STDOUT, formatter: LogFormat)
@stderr = Log::IOBackend.new(io: STDERR, formatter: LogFormat)
def write(entry : Log::Entry)
if entry.severity >= Log::Severity::Error
@stderr.write entry
else
@stdout.write entry
end
end
end
struct LogFormat < Log::StaticFormatter
@@colors = {
"FATAL" => :red,
"ERROR" => :red,
"WARN" => :yellow,
"NOTICE" => :yellow,
"INFO" => :green,
"DEBUG" => :blue,
"TRACE" => :light_blue,
}
def run
string "#{@entry.message}".colorize(@@colors[@entry.severity.label])
end
end
def self.setup(verbosity)
Colorize.on_tty_only!
verbosity = [0, verbosity].max
verbosity = [6, verbosity].min
severity = [
Log::Severity::Fatal,
Log::Severity::Error,
Log::Severity::Warn,
Log::Severity::Notice,
Log::Severity::Info,
Log::Severity::Debug,
Log::Severity::Trace,
][verbosity]
Log.setup(
severity,
LogBackend.new)
end
end

View File

@ -1,8 +1,8 @@
require "./config.cr"
require "./faaso.cr"
require "./log.cr"
require "colorize"
require "docopt"
require "oplog"
require "rucksack"
macro version
@ -35,29 +35,28 @@ Options:
DOC
ans = Docopt.docopt(doc, ARGV)
Logging.setup(ans["-v"].to_s.to_i)
Oplog.setup(ans["-v"].to_s.to_i) unless ENV.fetch("FAASO_SERVER_SIDE", nil)
Log.debug { ans }
status : Int32 = 0
case ans
when .fetch("build", false)
status = Faaso::Commands::Build.new.run(ans, ans["FOLDER"].as(Array(String)))
exit Faaso::Commands::Build.new.run(ans, ans["FOLDER"].as(Array(String)))
when .fetch("deploy", false)
status = Faaso::Commands::Deploy.new.run(ans, ans["FUNKO"].as(String))
exit Faaso::Commands::Deploy.new.run(ans, ans["FUNKO"].as(String))
when .fetch("export", false)
status = Faaso::Commands::Export.new.run(ans, ans["SOURCE"].as(String), ans["DESTINATION"].as(String))
exit Faaso::Commands::Export.new.run(ans, ans["SOURCE"].as(String), ans["DESTINATION"].as(String))
when .fetch("login", false)
status = Faaso::Commands::Login.new.run(ans)
exit Faaso::Commands::Login.new.run(ans)
when .fetch("new", false)
status = Faaso::Commands::New.new.run(ans, ans["FOLDER"].as(Array(String))[0])
exit Faaso::Commands::New.new.run(ans, ans["FOLDER"].as(Array(String))[0])
when .fetch("scale", false)
status = Faaso::Commands::Scale.new.run(ans, ans["FUNKO"].as(String), ans["SCALE"].as(String).to_i)
exit Faaso::Commands::Scale.new.run(ans, ans["FUNKO"].as(String), ans["SCALE"])
when .fetch("secret", false)
status = Faaso::Commands::Secret.new.run(ans, ans["FUNKO"].as(String), ans["SECRET"].as(String))
exit Faaso::Commands::Secret.new.run(ans, ans["FUNKO"].as(String), ans["SECRET"].as(String))
when .fetch("status", false)
status = Faaso::Commands::Status.new.run(ans, ans["FUNKO"].as(String))
exit Faaso::Commands::Status.new.run(ans, ans["FUNKO"].as(String))
when .fetch("version", false)
Log.info { "#{version}" }
end
exit(status)
exit 0

View File

@ -1,39 +1,32 @@
<%- result.each do |f| -%>
<tr hx-indicator="#spinner-<%= f["name"] %>">
<td>
<td style="vertical-align: top;">
<%= f["name"] %>
<img id="spinner-<%= f["name"] %>" src="bars.svg" class="htmx-indicator">
</td>
<td>
<table>
<thead>
<th>ID</th>
<th>Current?</th>
<th>Actions</th>
</thead>
<tbody>
<%- f["containers"].as(Array(Docr::Types::ContainerSummary)).each do |c| -%>
<tr>
<td><tt><%= c.@names[0].split("-")[-1] %></tt></td>
<td>
<td style="vertical-align: top;">
<%- f["containers"].as(Array(Docr::Types::ContainerSummary)).each do |c| -%>
<div class="grid">
<div>
<tt><%= c.@names[0].split("-")[-1] %></tt>
</div>
<div>
<%- if c.image_id == f["latest_image"] -%>
<span style="color:green;""> 🟢</span>
<%- else -%>
<span style="color:red;""> 🟢</span>
<%- end -%>
</td>
<td>
</div>
<div role="group">
<button hx-target="#terminal" hx-get="funkos/terminal/logs/<%= c.@names[0].lstrip("/") %>/">Logs</button>
<button hx-target="#terminal" hx-get="funkos/terminal/shell/<%= c.@names[0].lstrip("/") %>/">Shell</button>
</td>
</tr>
<%- end -%>
</tbody>
</p>
</div>
</div>
<%- end -%>
</td>
</table>
<td>
<div role="group">
<%- if f["name"] == "proxy" -%>
<%- else -%>
<%- if f["scale"].as(String).to_i > 0 -%>
@ -47,6 +40,7 @@
<%- end -%>
<button hx-get="funkos/<%= f["name"] %>/restart" hx-on:htmx:after-request="update_funkos()">Restart</button>
<%- end -%>
</div>
</td>
</tr>
<%- end -%>