diff --git a/.github/workflows/docker-build-push.yaml b/.github/workflows/docker-build-push.yaml index 05dfd552..489376ba 100644 --- a/.github/workflows/docker-build-push.yaml +++ b/.github/workflows/docker-build-push.yaml @@ -16,7 +16,7 @@ jobs: matrix: image: - imageName: examples/spin-rust-hello - context: images/spin + context: images/spin-hello-world - imageName: examples/spin-dotnet-hello context: images/spin_dotnet - imageName: examples/spin-outbound-redis diff --git a/Cargo.lock b/Cargo.lock index 11927d52..c4346993 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3111,7 +3111,7 @@ dependencies = [ "httpdate", "itoa", "pin-project-lite", - "socket2 0.4.10", + "socket2 0.5.8", "tokio", "tower-service", "tracing", @@ -8489,7 +8489,7 @@ version = "1.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97fee6b57c6a41524a810daee9286c02d7752c4253064d0b05472833a438f675" dependencies = [ - "cfg-if 0.1.10", + "cfg-if 1.0.0", "rand 0.8.5", "static_assertions", ] diff --git a/Makefile b/Makefile index 6509d96e..a01ac384 100644 --- a/Makefile +++ b/Makefile @@ -67,6 +67,22 @@ integration-spin-registry-push-tests: tests/collect-debug-logs: ./scripts/collect-debug-logs.sh 2>&1 +# test/k3s + +.PHONY: install-k3s, build-and-push-images +install-k3s: + ./scripts/install-k3s.sh +build-and-push-images: + ./scripts/build-and-push-images.sh --spin + +.PHONY: test/k3s +test/k3s: install-k3s build-and-push-images + kubectl apply -f tests/workloads-common + kubectl apply -f tests/k3s/workloads-pushed-using-spin-registry-push + make pod-terminates-test + SPIN_TEST_PORT=80 cargo test -p containerd-shim-spin-tests -- --nocapture + make teardown-workloads-k3s + # fmt .PHONY: fmt fix @@ -113,10 +129,10 @@ run-%: install load # deploy -./PHONY: up move-bins deploy-workloads-pushed-using-docker-build-push deploy-workloads-pushed-using-spin-registry-push pod-terminates-test prepare-cluster-and-images +.PHONY: up move-bins deploy-workloads-pushed-using-docker-build-push deploy-workloads-pushed-using-spin-registry-push pod-terminates-test prepare-cluster-and-images up: - ./scripts/up.sh + ./scripts/k3d-up.sh move-bins: ./scripts/move-bins.sh $(BIN_DIR) @@ -134,9 +150,12 @@ prepare-cluster-and-images: check-bins move-bins up free-disk pod-status-check # clean -./PHONY: teardown-workloads tests/clean -teardown-workloads: - ./scripts/teardown-workloads.sh +.PHONY: teardown-workloads-k3d teardown-workloads-k3s tests/clean +teardown-workloads-k3d: + ./scripts/teardown-workloads.sh "k3d" +teardown-workloads-k3s: + ./scripts/teardown-workloads.sh "k3s" + docker container stop test-registry && docker container rm test-registry tests/clean: ./scripts/down.sh diff --git a/deployments/k3d/DockerSetup.md b/deployments/k3d/DockerSetup.md index 0a535ed6..c8a5dc71 100644 --- a/deployments/k3d/DockerSetup.md +++ b/deployments/k3d/DockerSetup.md @@ -50,7 +50,7 @@ Otherwise, please append the above content to the file. Now you have Docker 24.0.0-beta.2 installed on your machine, you can build wasm images using the following command. ```shell -docker buildx build --platform=wasi/wasm --load -t wasmtest_spin:latest ./images/spin +docker buildx build --platform=wasi/wasm --load -t wasmtest_spin:latest ./images/spin-hello-world ``` The `wasi/wasm` platform specifies that the image is a wasm image. The major benefit of using this platform is that you don't need to build each image for a different computer architecture. diff --git a/images/spin/Cargo.lock b/images/spin-hello-world/Cargo.lock similarity index 100% rename from images/spin/Cargo.lock rename to images/spin-hello-world/Cargo.lock diff --git a/images/spin/Cargo.toml b/images/spin-hello-world/Cargo.toml similarity index 100% rename from images/spin/Cargo.toml rename to images/spin-hello-world/Cargo.toml diff --git a/images/spin/Dockerfile b/images/spin-hello-world/Dockerfile similarity index 100% rename from images/spin/Dockerfile rename to images/spin-hello-world/Dockerfile diff --git a/images/spin/go-hello/go.mod b/images/spin-hello-world/go-hello/go.mod similarity index 100% rename from images/spin/go-hello/go.mod rename to images/spin-hello-world/go-hello/go.mod diff --git a/images/spin/go-hello/go.sum b/images/spin-hello-world/go-hello/go.sum similarity index 100% rename from images/spin/go-hello/go.sum rename to images/spin-hello-world/go-hello/go.sum diff --git a/images/spin/go-hello/main.go b/images/spin-hello-world/go-hello/main.go similarity index 100% rename from images/spin/go-hello/main.go rename to images/spin-hello-world/go-hello/main.go diff --git a/images/spin/spin.toml b/images/spin-hello-world/spin.toml similarity index 100% rename from images/spin/spin.toml rename to images/spin-hello-world/spin.toml diff --git a/images/spin/src/lib.rs b/images/spin-hello-world/src/lib.rs similarity index 100% rename from images/spin/src/lib.rs rename to images/spin-hello-world/src/lib.rs diff --git a/scripts/build-and-push-images.sh b/scripts/build-and-push-images.sh new file mode 100755 index 00000000..81c4fc9c --- /dev/null +++ b/scripts/build-and-push-images.sh @@ -0,0 +1,69 @@ +#!/bin/bash + +set -euo pipefail + +RUN_SPIN=false +RUN_DOCKER=false + +cluster_name="test-cluster" +OUT_DIRS=("test/out_spin" "test/out_spin_keyvalue" "test/out_spin_outbound_redis" "test/out_spin_multi_trigger_app" "test/out_spin_static_assets" "test/out_spin_mqtt_message_logger") +IMAGES=("spin-hello-world" "spin-keyvalue" "spin-outbound-redis" "spin-multi-trigger-app" "spin-static-assets" "spin-mqtt-message-logger") + + +spin_build_and_push() { + local i=$1 + spin build -f "./images/${IMAGES[$i]}/spin.toml" + if [ "${IMAGES[$i]}" == "spin-static-assets" ]; then + export SPIN_OCI_ARCHIVE_LAYERS=1 + fi + spin registry push "localhost:5000/spin-registry-push/${IMAGES[$i]}:latest" -f "./images/${IMAGES[$i]}/spin.toml" -k +} + +docker_build_and_push() { + local image="$1" + local out_dir="$2" + + docker buildx build -t "${image}:latest" "./images/${image}" --load + mkdir -p "${out_dir}" + docker save -o "${out_dir}/img.tar" "${image}:latest" + k3d image import "${out_dir}/img.tar" -c "$cluster_name" +} + +while [[ "$#" -gt 0 ]]; do + case "$1" in + --spin) RUN_SPIN=true ;; + --docker) RUN_DOCKER=true ;; + --both) RUN_SPIN=true; RUN_DOCKER=true ;; + *) echo "Unknown option: $1"; exit 1 ;; + esac + shift +done + +if ! $RUN_SPIN && ! $RUN_DOCKER; then + echo "Error: At least one of --spin, --docker, or --both must be specified." + exit 1 +fi + + +if $RUN_SPIN; then + echo "Running Spin builds and pushes..." + if ! docker ps | grep -q test-registry; then + docker run -d -p 5000:5000 --name test-registry registry:2 + fi + for i in "${!IMAGES[@]}"; do + spin_build_and_push "$i" & + done +fi + +if $RUN_DOCKER; then + echo "Running Docker builds and pushes..." + for i in "${!IMAGES[@]}"; do + docker_build_and_push "${IMAGES[$i]}" "${OUT_DIRS[$i]}" & + done +fi + +# Wait for all background jobs to finish +wait + +sleep 5 +echo "Images are ready" \ No newline at end of file diff --git a/scripts/install-k3s.sh b/scripts/install-k3s.sh new file mode 100755 index 00000000..3132f9c4 --- /dev/null +++ b/scripts/install-k3s.sh @@ -0,0 +1,14 @@ +#!/bin/bash + +set -euo pipefail + +# Install k3s +# Since the latest k3s already bakes in the Spin shim, we don't need to reconfigure it. +# We will just need to make sure that containerd-shim-spin-v2 binary is in PATH +# and that the k3s service is running. + +curl -sfL https://get.k3s.io | sh -s - server --write-kubeconfig-mode '0644' +sudo systemctl start k3s + +export KUBECONFIG=/etc/rancher/k3s/k3s.yaml +kubectl get nodes \ No newline at end of file diff --git a/scripts/k3d-up.sh b/scripts/k3d-up.sh new file mode 100755 index 00000000..39978555 --- /dev/null +++ b/scripts/k3d-up.sh @@ -0,0 +1,21 @@ +#!/bin/bash + +set -euo pipefail + +cluster_name="test-cluster" +dockerfile_path="deployments/k3d" + +docker build -t k3d-shim-test "$dockerfile_path" + +k3d cluster create "$cluster_name" \ + --image k3d-shim-test --api-port 6551 -p '8082:80@loadbalancer' --agents 2 \ + --registry-create test-registry:0.0.0.0:5000 \ + --k3s-arg '--kubelet-arg=eviction-hard=imagefs.available<1%,nodefs.available<1%@agent:*' \ + --k3s-arg '--kubelet-arg=eviction-minimum-reclaim=imagefs.available=1%,nodefs.available=1%@agent:*' + +kubectl wait --for=condition=ready node --all --timeout=120s + +echo "Running Spin and Docker builds and pushes..." +./scripts/spin-build-and-push-images.sh --both + +echo ">>> Cluster setup and image builds/pushes complete!" \ No newline at end of file diff --git a/scripts/teardown-workloads.sh b/scripts/teardown-workloads.sh index de245830..5d97b4ab 100755 --- a/scripts/teardown-workloads.sh +++ b/scripts/teardown-workloads.sh @@ -2,7 +2,20 @@ set -euo pipefail -kubectl delete -f tests/workloads-common --wait --timeout 60s --ignore-not-found=true -kubectl delete -f tests/workloads-pushed-using-docker-build-push --wait --timeout 60s --ignore-not-found=true -kubectl delete -f tests/workloads-pushed-using-spin-registry-push --wait --timeout 60s --ignore-not-found=true +case "$1" in + k3d) + TESTS_PATH="tests/k3d" + ;; + k3s) + TESTS_PATH="tests/k3s" + ;; + *) + echo "Invalid argument. Use 'k3d' or 'k3s'." + exit 1 + ;; +esac + +kubectl delete -f "tests/workloads-common" --wait --timeout 60s --ignore-not-found=true +kubectl delete -f "tests/workloads-pushed-using-docker-build-push" --wait --timeout 60s --ignore-not-found=true +kubectl delete -f "$TESTS_PATH/workloads-pushed-using-spin-registry-push" --wait --timeout 60s --ignore-not-found=true kubectl wait pod --for=delete -l app=wasm-spin -l app=spin-keyvalue -l app=spin-outbound-redis -l app=spin-multi-trigger-app --timeout 60s \ No newline at end of file diff --git a/scripts/up.sh b/scripts/up.sh deleted file mode 100755 index 3ffd0479..00000000 --- a/scripts/up.sh +++ /dev/null @@ -1,43 +0,0 @@ -#!/bin/bash - -set -euo pipefail - -cluster_name="test-cluster" # name of the k3d cluster -dockerfile_path="deployments/k3d" # path to the Dockerfile - -DOCKER_IMAGES=("spin" "spin-keyvalue" "spin-outbound-redis" "spin-multi-trigger-app" "spin-static-assets" "spin-mqtt-message-logger") -OUT_DIRS=("test/out_spin" "test/out_spin_keyvalue" "test/out_spin_outbound_redis" "test/out_spin_multi_trigger_app" "test/out_spin_static_assets" "test/out_spin_mqtt_message_logger") -IMAGES=("spin-hello-world" "spin-keyvalue" "spin-outbound-redis" "spin-multi-trigger-app" "spin-static-assets" "spin-mqtt-message-logger") - -# build the Docker image for the k3d cluster -docker build -t k3d-shim-test "$dockerfile_path" - -k3d cluster create "$cluster_name" \ - --image k3d-shim-test --api-port 6551 -p '8082:80@loadbalancer' --agents 2 \ - --registry-create test-registry:0.0.0.0:5000 \ - --k3s-arg '--kubelet-arg=eviction-hard=imagefs.available<1%,nodefs.available<1%@agent:*' \ - --k3s-arg '--kubelet-arg=eviction-minimum-reclaim=imagefs.available=1%,nodefs.available=1%@agent:*' - -kubectl wait --for=condition=ready node --all --timeout=120s - -# Iterate through the Docker images and build them -for i in "${!DOCKER_IMAGES[@]}"; do - docker buildx build -t "${IMAGES[$i]}:latest" "./images/${DOCKER_IMAGES[$i]}" --load - mkdir -p "${OUT_DIRS[$i]}" - docker save -o "${OUT_DIRS[$i]}/img.tar" "${IMAGES[$i]}:latest" - k3d image import "${OUT_DIRS[$i]}/img.tar" -c "$cluster_name" - - ## also do spin builds and spin registry push - ## images pushed as localhost:5000//: - ## can be pulled as registry:5000//: from within k3d cluster - spin build -f "./images/${DOCKER_IMAGES[$i]}/spin.toml" - ## For the spin-static-assets app, use archive layers to test this functionality in the shim - if [ "${i}" == "spin-static-assets" ]; then - export SPIN_OCI_ARCHIVE_LAYERS=1 - fi - spin registry push "localhost:5000/spin-registry-push/${IMAGES[$i]}:latest" -f "./images/${DOCKER_IMAGES[$i]}/spin.toml" -k -done - -sleep 5 - -echo ">>> cluster is ready" diff --git a/tests/README.md b/tests/k3d/README.md similarity index 100% rename from tests/README.md rename to tests/k3d/README.md diff --git a/tests/workloads-pushed-using-spin-registry-push/README.md b/tests/k3d/workloads-pushed-using-spin-registry-push/README.md similarity index 100% rename from tests/workloads-pushed-using-spin-registry-push/README.md rename to tests/k3d/workloads-pushed-using-spin-registry-push/README.md diff --git a/tests/workloads-pushed-using-spin-registry-push/workloads.yaml b/tests/k3d/workloads-pushed-using-spin-registry-push/workloads.yaml similarity index 100% rename from tests/workloads-pushed-using-spin-registry-push/workloads.yaml rename to tests/k3d/workloads-pushed-using-spin-registry-push/workloads.yaml diff --git a/tests/k3s/workloads-pushed-using-spin-registry-push/README.md b/tests/k3s/workloads-pushed-using-spin-registry-push/README.md new file mode 100644 index 00000000..299c320a --- /dev/null +++ b/tests/k3s/workloads-pushed-using-spin-registry-push/README.md @@ -0,0 +1 @@ +This folder has yaml file to apply workloads which are built using `spin build` and pushed to a registry, managed with k3d in CI, using `spin registry push` command. \ No newline at end of file diff --git a/tests/k3s/workloads-pushed-using-spin-registry-push/workloads.yaml b/tests/k3s/workloads-pushed-using-spin-registry-push/workloads.yaml new file mode 100644 index 00000000..690cf05f --- /dev/null +++ b/tests/k3s/workloads-pushed-using-spin-registry-push/workloads.yaml @@ -0,0 +1,303 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: wasm-spin +spec: + replicas: 1 + selector: + matchLabels: + app: wasm-spin + template: + metadata: + labels: + app: wasm-spin + spec: + runtimeClassName: wasmtime-spin + containers: + - name: testwasm + image: localhost:5000/spin-registry-push/spin-hello-world:latest + imagePullPolicy: IfNotPresent + command: ["/"] + resources: # limit the resources to 128Mi of memory and 100m of CPU + limits: + cpu: 100m + memory: 128Mi + requests: + cpu: 100m + memory: 128Mi +--- +apiVersion: v1 +kind: Service +metadata: + name: wasm-spin +spec: + ports: + - protocol: TCP + port: 80 + targetPort: 80 + selector: + app: wasm-spin +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: spin-keyvalue +spec: + replicas: 1 + selector: + matchLabels: + app: spin-keyvalue + template: + metadata: + labels: + app: spin-keyvalue + spec: + runtimeClassName: wasmtime-spin + containers: + - name: keyvalue + image: localhost:5000/spin-registry-push/spin-keyvalue:latest + command: ["/"] + imagePullPolicy: IfNotPresent + volumeMounts: + - name: config-volume + mountPath: /runtime-config.toml + subPath: runtime-config.toml + readOnly: true + volumes: + - name: config-volume + configMap: + name: spin-runtime-config +--- +apiVersion: v1 +kind: Service +metadata: + name: spin-keyvalue +spec: + type: LoadBalancer + ports: + - protocol: TCP + port: 80 + targetPort: 80 + selector: + app: spin-keyvalue +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: spin-outbound-redis +spec: + replicas: 1 + selector: + matchLabels: + app: spin-outbound-redis + template: + metadata: + labels: + app: spin-outbound-redis + spec: + runtimeClassName: wasmtime-spin + containers: + - name: outbound-redis + image: localhost:5000/spin-registry-push/spin-outbound-redis:latest + command: ["/"] + imagePullPolicy: IfNotPresent + env: + - name: SPIN_VARIABLE_REDIS_ADDRESS + value: redis://redis-service.default.svc.cluster.local:6379 + - name: SPIN_VARIABLE_REDIS_CHANNEL + value: test +--- +apiVersion: v1 +kind: Service +metadata: + name: spin-outbound-redis +spec: + type: LoadBalancer + ports: + - protocol: TCP + port: 80 + targetPort: 80 + selector: + app: spin-outbound-redis +--- +# Middleware +# Strip prefix /spin +apiVersion: traefik.containo.us/v1alpha1 +kind: Middleware +metadata: + name: strip-prefix +spec: + stripPrefix: + forceSlash: false + prefixes: + - /spin + - /outboundredis + - /keyvalue + - /static-assets +--- +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: wasm-ingress + annotations: + ingress.kubernetes.io/ssl-redirect: "false" + traefik.ingress.kubernetes.io/router.middlewares: default-strip-prefix@kubernetescrd +spec: + ingressClassName: traefik + rules: + - http: + paths: + - path: /spin + pathType: Prefix + backend: + service: + name: wasm-spin + port: + number: 80 + - path: /keyvalue + pathType: Prefix + backend: + service: + name: spin-keyvalue + port: + number: 80 + - path: /outboundredis + pathType: Prefix + backend: + service: + name: spin-outbound-redis + port: + number: 80 + - path: /multi-trigger-app + pathType: Prefix + backend: + service: + name: spin-multi-trigger-app + port: + number: 80 + - path: /static-assets + pathType: Prefix + backend: + service: + name: spin-static-assets + port: + number: 80 +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: spin-multi-trigger-app +spec: + replicas: 1 + selector: + matchLabels: + app: spin-multi-trigger-app + template: + metadata: + labels: + app: spin-multi-trigger-app + spec: + runtimeClassName: wasmtime-spin + containers: + - name: spin-multi-trigger-app + image: localhost:5000/spin-registry-push/spin-multi-trigger-app:latest + imagePullPolicy: IfNotPresent + command: ["/"] + ports: + - containerPort: 80 + env: + - name: SPIN_VARIABLE_REDIS_ADDRESS + value: redis://redis-service.default.svc.cluster.local:6379 + - name: SPIN_VARIABLE_REDIS_CHANNEL + value: testchannel +--- +apiVersion: v1 +kind: Service +metadata: + name: spin-multi-trigger-app +spec: + type: LoadBalancer + ports: + - protocol: TCP + port: 80 + targetPort: 80 + selector: + app: spin-multi-trigger-app +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: spin-static-assets +spec: + replicas: 1 + selector: + matchLabels: + app: spin-static-assets + template: + metadata: + labels: + app: spin-static-assets + spec: + runtimeClassName: wasmtime-spin + containers: + - name: spin-static-assets + image: localhost:5000/spin-registry-push/spin-static-assets:latest + imagePullPolicy: IfNotPresent + command: ["/"] + ports: + - containerPort: 80 +--- +apiVersion: v1 +kind: Service +metadata: + name: spin-static-assets +spec: + type: LoadBalancer + ports: + - protocol: TCP + port: 80 + targetPort: 80 + selector: + app: spin-static-assets +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: spin-mqtt-message-logger +spec: + replicas: 1 + selector: + matchLabels: + app: spin-mqtt-message-logger + template: + metadata: + labels: + app: spin-mqtt-message-logger + spec: + runtimeClassName: wasmtime-spin + containers: + - name: spin-mqtt-message-logger + image: localhost:5000/spin-registry-push/spin-mqtt-message-logger:latest + imagePullPolicy: IfNotPresent + command: ["/"] + ports: + - containerPort: 80 + env: + - name: SPIN_VARIABLE_MQTT_TOPIC + value: containerd-shim-spin/mqtt-test-17h24d + # The MQTT trigger cannot do DNS resolution, so we need to use the IP address of the MQTT broker + - name: SPIN_VARIABLE_MQTT_BROKER_URI + value: "mqtt://test.mosquitto.org" +--- +apiVersion: v1 +kind: Service +metadata: + name: spin-mqtt-message-logger +spec: + type: LoadBalancer + ports: + - protocol: TCP + port: 80 + targetPort: 80 + selector: + app: spin-mqtt-message-logger \ No newline at end of file diff --git a/tests/src/integration_test.rs b/tests/src/integration_test.rs index 111d38fe..d32d0564 100644 --- a/tests/src/integration_test.rs +++ b/tests/src/integration_test.rs @@ -1,6 +1,7 @@ #[cfg(test)] mod test { use std::{ + env, process::Command, thread, time::{self, Duration}, @@ -12,6 +13,7 @@ mod test { const RETRY_TIMES: u32 = 5; const INTERVAL_IN_SECS: u64 = 10; + const DEFAULT_PORT: i32 = 8082; pub async fn retry_get(url: &str, retry_times: u32, interval_in_secs: u64) -> Result> { let mut i = 0; @@ -52,7 +54,9 @@ mod test { #[tokio::test] async fn spin_test() -> Result<()> { - let host_port = 8082; + let host_port: i32 = env::var("SPIN_TEST_PORT") + .and_then(|s| Ok(s.parse::().unwrap())) + .unwrap_or_else(|_| DEFAULT_PORT); // curl for hello println!(" >>> curl http://localhost:{}/spin/hello", host_port); @@ -69,7 +73,9 @@ mod test { #[tokio::test] async fn spin_keyvalue_test() -> Result<()> { - let host_port = 8082; + let host_port: i32 = env::var("SPIN_TEST_PORT") + .and_then(|s| Ok(s.parse::().unwrap())) + .unwrap_or_else(|_| DEFAULT_PORT); // curl for hello println!(" >>> curl http://localhost:{}/keyvalue/keyvalue", host_port); @@ -86,7 +92,9 @@ mod test { #[tokio::test] async fn spin_inbound_redis_outbound_redis_test() -> Result<()> { - let host_port = 8082; + let host_port: i32 = env::var("SPIN_TEST_PORT") + .and_then(|s| Ok(s.parse::().unwrap())) + .unwrap_or_else(|_| DEFAULT_PORT); let redis_port = 6379; // Ensure kubectl is in PATH @@ -123,7 +131,9 @@ mod test { #[tokio::test] async fn spin_multi_trigger_app_test() -> Result<()> { - let host_port = 8082; + let host_port: i32 = env::var("SPIN_TEST_PORT") + .and_then(|s| Ok(s.parse::().unwrap())) + .unwrap_or_else(|_| DEFAULT_PORT); // curl for hello println!(" >>> curl http://localhost:{}/multi-trigger-app", host_port); @@ -222,7 +232,9 @@ mod test { #[tokio::test] async fn spin_static_assets_test() -> Result<()> { - let host_port = 8082; + let host_port: i32 = env::var("SPIN_TEST_PORT") + .and_then(|s| Ok(s.parse::().unwrap())) + .unwrap_or_else(|_| DEFAULT_PORT); // curl for static asset println!(