Source for landscape.dronecode.org, built with landscape2.
This landscape is not only a website. The job board at jobs.dronecode.org reads two files this build publishes, and treats them as the source of truth:
| Endpoint | Consumed for |
|---|---|
/api/members/all.json |
Member names, membership level, logos |
/api/projects/all.json |
Project names, maturity, logos |
The gitjobs syncer deletes any member or project in its database that is absent from these files. Removing an entry here removes it from the job board.
landscape.yml Categories, members, and projects
settings.yml Branding, colors, groups, members_category
hosted_logos/ Logo files (landscape2 requires local files, not URLs)
tools/ Member data refresh from LFX
landscape2 build \
--data-file landscape.yml \
--settings-file settings.yml \
--logos-path hosted_logos \
--output-dir build
landscape2 serve --landscape-dir buildThe build works without credentials. CRUNCHBASE_API_KEY and GITHUB_TOKENS
are optional and only enrich org details and repo stats.
Check the result before publishing it. CI runs exactly this, so a failure here is a failure there:
landscape2 validate data --data-file landscape.yml
landscape2 validate settings --settings-file settings.yml
python3 tools/verify_build.py buildMember entries are generated from LFX active memberships, not hand-written.
tools/lfx_members.json is a snapshot; tools/gen_landscape.py turns it into
the members half of landscape.yml and downloads each logo into
hosted_logos/.
python3 tools/gen_landscape.pyTo pick up membership changes, refresh tools/lfx_members.json from LFX
(project 74801dba-2a15-4bd6-bee7-61e8c6d8b9ed, Active memberships) and re-run.
Project entries are maintained by hand in tools/gen_landscape.py because the
foundation's projects change rarely.
Membership levels come from subcategory names, and new tiers need no code
change. A member's level on the job board is the subcategory it sits under.
Tiers are derived from the LFX data rather than declared anywhere, so a Gold
member appearing in LFX shows up on both the site and the job board with no edit
to this repo beyond refreshing tools/lfx_members.json.
Two things make that safe, and both are load-bearing:
settings.ymldeliberately does not declare acategoriesblock. Declaring one setscategories_overriddenin the build, which turns it into a filter: a subcategory present inlandscape.ymlbut absent from that list is dropped from the website UI while remaining in the API. The result would be a new Gold member who is correctly badged on the job board and invisible on the landscape.TIER_ORDERintools/gen_landscape.pyis a display-order preference, not an allowlist. Tiers not listed there are still emitted, appended after the known ones, with a warning. The generator refuses to writelandscape.ymlat all if the member count going in does not match the count coming out.
To give a new tier a specific position rather than having it appended, add it to
TIER_ORDER. That is a cosmetic change; omitting it never loses data.
Project names are user-facing. The name of each project becomes the string
employers tag jobs with on the job board. They match the LFX project names
(PX4, not PX4 Autopilot).
Members who do not want to be publicised are handled here, because nowhere
else does it. LFX models membership as a billing record: neither the
membership API nor the Insights semantic layer has a consent, publicity, or
opt-out field. So member data flows from a system that has never been asked the
question, to a public website. The NON_PUBLIC set in
tools/gen_landscape.py is the only place that intent is recorded, and it has
to be maintained by hand from what members actually asked for.
A listed member is published as Non-Public Organization N rather than dropped,
which keeps the member count honest without naming them. This matches the CNCF
convention, and gitjobs skips any member whose name contains "non-public"
(gitjobs-syncer/src/syncer.rs:122).
Two details that are easy to get wrong, both guarded in CI:
- The marker must sit outside parentheses. gitjobs strips
" (...)"from every member name before testing for "non-public", so a member markedAcme Corp (non-public)has the marker stripped and is published anyway. CNCF's placement at the start of the name is what makes their convention work. - Scrubbing the name is not enough. A logo file called
modalai.svgsitting in a public repo identifies a member as surely as the name does, even though landscape2 never publishes unreferenced logos. The generator deletes the logo when a member is marked non-public.
Parentheses in member names get stripped downstream. The gitjobs syncer
applies the regex " \(.*\)" to every member name to remove landscape2's
(member) suffix convention. It is unconditional, so a legal name containing
a parenthetical loses it: ZeroOne AeroSpace (Nanjing) Technology Co., LTD
is stored as ZeroOne AeroSpace Technology Co., LTD. This is an upstream
gitjobs behaviour, not something this repo can fix.
Logos must be local files. landscape2 rejects URLs in the logo field. The
refresh script downloads them. Members whose LFX record has no usable logo get a
generated text placeholder so the build stays green rather than dropping a
paying member; replace those files with real artwork when it is available.
.github/workflows/build.yml builds on push to main, daily, and on demand,
then syncs to S3 and invalidates CloudFront. It assumes an AWS role via OIDC and
fails the build if either API endpoint goes missing or empty.
Required repository secrets:
| Secret | Purpose |
|---|---|
AWS_DEPLOY_ROLE_ARN |
Role assumed via OIDC |
LANDSCAPE_S3_BUCKET |
Destination bucket |
LANDSCAPE_CLOUDFRONT_ID |
Distribution to invalidate |
CRUNCHBASE_API_KEY |
Optional |
LANDSCAPE_GITHUB_TOKENS |
Optional |