Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions Dockerfile.ecr
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# skysim as it is deployed: the binary plus the world it flies in.
#
# The plain Dockerfile deliberately ships no tiles — worlds are cooked separately so a
# new city does not mean a new image, and locally they are mounted. The deployed ECS
# services have no volume mounts, though, so the world has to travel with the image:
# without it skysim starts flat, every route sweep answers "no building data", and the
# overwatch coordinator refuses to launch anything at all.
#
# The buildspec fetches the cooked world into world-tiles/ before this runs. It creates
# the directory either way, so a build with no world still produces a working simulator
# rather than failing on a missing COPY.
FROM skyhub-skysim:build

COPY world-tiles/ /opt/skysim/tiles/
Comment on lines +12 to +14

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy lift

Run the ECS workload as a non-root user.

This image inherits the base image’s default user and contains no USER instruction, so the simulator runs as root. Create/select a non-root user in the base or runtime image, set USER, and verify that the binary and /opt/skysim/tiles remain readable.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Dockerfile.ecr` around lines 12 - 14, Update the Dockerfile.ecr image setup
to create or select a non-root runtime user, add a USER instruction for that
user, and ensure the simulator binary and /opt/skysim/tiles remain readable by
it.

Source: Linters/SAST tools

45 changes: 45 additions & 0 deletions buildspec.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# Build skysim and deploy it to the ECS service for this environment.
#
# Two things differ from the other services in this account, both deliberate:
#
# * x86_64, not ARM. The skysim task definitions are X86_64 on both dev and prod, and
# an image built for the wrong architecture fails at task start rather than at build.
# * The world is fetched from S3 and baked in. See Dockerfile.ecr.
#
# WORLD_S3_URI and IMAGE_NAME come from the CodeBuild project's environment, so the same
# buildspec serves dev and prod.
version: 0.2

phases:
install:
commands:
- nohup /usr/local/bin/dockerd --host=unix:///var/run/docker.sock --host=tcp://127.0.0.1:2375 &
- timeout 15 sh -c "until docker info; do echo .; sleep 1; done"
- echo Logging in to Amazon ECR...
- export ECR_URI="$AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com"
- aws ecr get-login-password --region $AWS_DEFAULT_REGION | docker login --username AWS --password-stdin $ECR_URI
pre_build:
commands:
# Always present, possibly empty: a missing directory would fail the COPY, and a
# simulator with no buildings is still a working simulator.
- mkdir -p world-tiles
- |
if [ -n "$WORLD_S3_URI" ]; then
echo "Fetching cooked world from $WORLD_S3_URI"
aws s3 cp "$WORLD_S3_URI" /tmp/world.tgz
tar -xzf /tmp/world.tgz -C world-tiles
echo "world: $(ls world-tiles | wc -l) file(s)"
else
echo "WORLD_S3_URI is unset — building with no buildings"
fi
Comment on lines +25 to +34

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== files ==\n'
git ls-files | rg '^(buildspec\.yml|Dockerfile\.ecr|src/main\.cpp|.*TileStreamer.*|.*tile.*stream.*|.*ecs.*|.*task.*json)$' || true

printf '\n== buildspec.yml ==\n'
nl -ba buildspec.yml | sed -n '1,120p'

printf '\n== Dockerfile.ecr ==\n'
nl -ba Dockerfile.ecr | sed -n '1,220p'

printf '\n== search for index.json / tiles references ==\n'
rg -n --hidden --glob '!**/.git/**' 'index\.json|world-tiles|--tiles|TileStreamer|tile_dir|tiles missing|zero-loaded|no-world|WORLD_S3_URI' .

Repository: ID-Robots/skysim

Length of output: 328


🏁 Script executed:

#!/bin/bash
set -euo pipefail
nl -ba buildspec.yml | sed -n '20,60p'

Repository: ID-Robots/skysim

Length of output: 194


🏁 Script executed:

#!/bin/bash
set -euo pipefail
pwd
ls -1

Repository: ID-Robots/skysim

Length of output: 298


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== buildspec.yml ==\n'
sed -n '1,120p' buildspec.yml

printf '\n== Dockerfile.ecr ==\n'
sed -n '1,220p' Dockerfile.ecr

printf '\n== src/main.cpp ==\n'
sed -n '1,240p' src/main.cpp

printf '\n== src/terrain/tile_streamer.h ==\n'
sed -n '1,220p' src/terrain/tile_streamer.h

printf '\n== src/terrain/tile_streamer.cpp ==\n'
sed -n '1,260p' src/terrain/tile_streamer.cpp

Repository: ID-Robots/skysim

Length of output: 19958


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== main tile startup ==\n'
grep -n -A80 -B40 'TileStreamer\|--tiles\|tiles' src/main.cpp

printf '\n== all --tiles references ==\n'
rg -n --hidden --glob '!**/.git/**' -- '--tiles|TileStreamer|index\.json|world-tiles|/opt/skysim/tiles' .

Repository: ID-Robots/skysim

Length of output: 37963


🏁 Script executed:

#!/bin/bash
set -euo pipefail

sed -n '1,120p' docker-entrypoint.sh
printf '\n== relevant docs ==\n'
sed -n '35,70p' docs/SKYHUB_INTEGRATION.md
printf '\n== README tiles section ==\n'
sed -n '115,145p' README.md

Repository: ID-Robots/skysim

Length of output: 5223


Keep the no-world image from exporting --tiles.
world-tiles/ is copied into /opt/skysim/tiles/, and the entrypoint adds --tiles whenever that directory exists. With WORLD_S3_URI unset, TileStreamer aborts on missing index.json, so the no-world build still fails at startup. Either skip --tiles for this variant or generate a valid empty index.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@buildspec.yml` around lines 25 - 34, Update the no-world build flow around
WORLD_S3_URI and the entrypoint’s tile argument handling so an unset
WORLD_S3_URI does not export world-tiles as an enabled tiles directory. Prefer
skipping --tiles for this variant, or ensure the generated directory contains a
valid empty index.json before startup; preserve normal tile extraction and
--tiles behavior when WORLD_S3_URI is set.

build:
commands:
- echo "Building skysim"
- docker build --cache-from $IMAGE_NAME:latest -t skyhub-skysim:build .
- docker build -f Dockerfile.ecr -t $IMAGE_NAME:latest .
- docker tag $IMAGE_NAME:latest $IMAGE_NAME:$CODEBUILD_RESOLVED_SOURCE_VERSION
post_build:
commands:
# Both tags, so a deployment can be traced back to the commit that produced it.
- docker push -a $IMAGE_NAME
- aws ecs update-service --service $ECS_SERVICE --force-new-deployment --cluster $ECS_CLUSTER --region $AWS_DEFAULT_REGION

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail
wc -l buildspec.yml
sed -n '1,120p' buildspec.yml | cat -n

Repository: ID-Robots/skysim

Length of output: 2598


🏁 Script executed:

#!/bin/bash
set -euo pipefail
rg -n "ecs wait|services-stable|update-service" .

Repository: ID-Robots/skysim

Length of output: 302


Wait for the ECS deployment to stabilize. update-service only starts the deployment; add aws ecs wait services-stable --cluster $ECS_CLUSTER --services $ECS_SERVICE so the build fails if replacement tasks never become healthy.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@buildspec.yml` at line 45, Add an `aws ecs wait services-stable` command
immediately after the `aws ecs update-service` command, using the existing
ECS_CLUSTER and ECS_SERVICE variables, so the build waits for healthy
replacement tasks and fails if the deployment does not stabilize.

25 changes: 24 additions & 1 deletion tests/test_api.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -47,10 +47,13 @@ int main() {
std::atomic<bool> saw_launch_process{false};
std::atomic<int> last_instance{-2};
std::atomic<size_t> path_checks{0};
std::atomic<size_t> battery_resets{0};
std::atomic<uint32_t> last_battery_id{0};
std::atomic<size_t> last_waypoint_count{0};
std::atomic<double> last_clearance{-1.0};

// Fake tick thread: drain + answer. Spawn -> fixed result; despawn -> ok iff id == 1.
// Fake tick thread: drain + answer. Spawn -> fixed result; despawn and battery reset
// -> ok iff id == 1.
// Path checks return a hit, a clear result, or unavailable geometry based on the first
// waypoint's north coordinate so the HTTP response cases stay deterministic.
std::thread consumer([&] {
Expand All @@ -62,6 +65,10 @@ int main() {
s->done.set_value(skysim::vehicle::SpawnResult{1, 30, 9302, 6060});
} else if (auto *d = std::get_if<DespawnCommand>(&cmd)) {
d->done.set_value(d->id == 1);
} else if (auto *b = std::get_if<BatteryResetCommand>(&cmd)) {
++battery_resets;
last_battery_id = b->id;
b->done.set_value(b->id == 1);
} else if (auto *p = std::get_if<PathCheckCommand>(&cmd)) {
++path_checks;
last_waypoint_count = p->waypoints_ned.size();
Expand Down Expand Up @@ -165,6 +172,22 @@ int main() {
CHECK(mission_unavailable && mission_unavailable->body.find("\"clear\":false") != std::string::npos);
CHECK(path_checks.load() == 3);

// Fitting a fresh pack. The gateway calls this after every landing, because
// resetting only the autopilot's counter leaves skysim discharging the old pack
// and the vehicle is retired after one sortie.
auto battery = client.Post("/vehicles/1/battery/reset", "", "application/json");
CHECK(battery && battery->status == 200);
CHECK(battery && battery->body.find("\"ok\":true") != std::string::npos);
CHECK(battery_resets.load() == 1);
CHECK(last_battery_id.load() == 1);

// A vehicle that is not there says so, rather than reporting a pack it did not fit.
auto battery404 = client.Post("/vehicles/99/battery/reset", "", "application/json");
CHECK(battery404 && battery404->status == 404);
CHECK(battery404 && battery404->body.find("no such vehicle") != std::string::npos);
CHECK(battery_resets.load() == 2);
CHECK(last_battery_id.load() == 99);

auto del = client.Delete("/vehicles/1");
CHECK(del && del->status == 200 && del->body.find("\"ok\":true") != std::string::npos);
auto del404 = client.Delete("/vehicles/99");
Expand Down