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
34 changes: 34 additions & 0 deletions .github/workflows/ci-spec.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
name: ci-spec

# Run this repo's own spec suite.
#
# It existed before this workflow did but nothing invoked it: every other file in
# .github/workflows/ here is a `workflow_call` template synced into OTHER repos,
# and ci-lint.yml only lints. So the specs that pin the Cimas templates and the
# data-index config ran solely when someone remembered to `bundle exec rake`.
#
# That became load-bearing when data-deploy.yml started reading
# data-index/configs.yml at build time: this repo's lib/, bin/ and configs.yml are
# now a runtime dependency of every relaton-data-* Pages build, and a broken one
# fails all of them at once. These specs are the only thing standing in front of
# that, so they have to run on the PR that breaks them.

on:
push:
branches: [ main ]
pull_request:

jobs:
spec:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- uses: ruby/setup-ruby@v1
with:
# Matches the version data-deploy.yml resolves branding under, so a
# syntax or stdlib difference cannot pass here and fail in a data repo.
ruby-version: "3.4"
bundler-cache: true

- run: bundle exec rake
Comment on lines +23 to +34
127 changes: 96 additions & 31 deletions .github/workflows/data-deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,23 @@ name: Deploy
# both the "skip a failed crawler run" guard and the Pages concurrency group are
# declared here, so no per-repo copy can drift or forget them.
#
# Optionally, a caller passes the per-flavor branding its `_config.yml` used to
# carry — `title`, `favicon` and `description` (see relaton/relaton#96). Left
# unset, the page carries no favicon and no description, exactly as before.
# Per-flavor branding — `title`, `favicon` and `description` (relaton/relaton#96)
# — is resolved HERE, from relaton/support's `data-index/configs.yml`, and not
# passed by the caller. cimas.yml maps `.github/workflows/deploy.yml` as a
# whole-file copy for 29 repos, so a `with:` block carrying branding is wiped on
# the next `cimas sync` and the page silently loses its favicon and description —
# nothing goes red. Keeping the caller byte-identical fleet-wide is what makes a
# sync safe; see `data-index/README.adoc`.
#
# The three inputs remain declared and still win when a caller sets one
# explicitly (a reusable workflow rejects an undeclared input at parse time, so
# they could not be removed without breaking existing callers anyway). Blank —
# what an omitted input arrives as — falls through to configs.yml.
#
# The trade this makes: relaton/support becomes a runtime dependency of every
# Pages build, not just the source of this file. A bad configs.yml breaks all 29
# at once, which is why the resolve step runs before the expensive build and
# `job.workflow_sha` pins the data to the same commit as this workflow.
#
# Two source modes (input `source`):
# * gem (default) — install the released relaton-cli gem. Ruby only; the gem
Expand All @@ -39,20 +53,26 @@ on:
required: false
default: "gem"
type: string
# The branding trio. Normally unset: `Resolve branding` below reads these
# per flavor from relaton/support's data-index/configs.yml. Setting one
# here overrides that repo's row for this caller only.
title:
description: "Index page title (default: '<FLAVOR> Index' from the repo name)"
description: "Override the index page title (default: from data-index/configs.yml)"
required: false
default: ""
type: string
# Both build steps pass these through unconditionally: relaton-cli treats a
# blank value as absent, so the empty default keeps the output identical
# for callers that set neither.
favicon:
description: "Index page favicon URL or output-relative path (default: none)"
description: "Override the favicon URL or output-relative path (default: from data-index/configs.yml)"
required: false
default: ""
type: string
description:
description: "Index page description (<meta name=\"description\"> and header subtitle)"
description: "Override the description (<meta name=\"description\"> and header subtitle)"
required: false
default: ""
type: string
support-ref:
description: "relaton/support ref to read branding from (default: this workflow's own commit)"
required: false
default: ""
type: string
Expand Down Expand Up @@ -128,10 +148,60 @@ jobs:
steps:
- uses: actions/checkout@v4 # the data repo (has ./data)

# Must come AFTER the checkout above: a checkout into the workspace root
# clears the directory first, which would delete this one.
#
# `job.workflow_repository` and `job.workflow_sha` are the repo and commit
# THIS reusable workflow was loaded from — GitHub documents exactly this
# pair for a reusable workflow checking out its own source. So the resolver
# code and configs.yml always come from one commit, and
# `uses: .../data-deploy.yml@some-branch` reads that branch's branding with
# no caller-side input to set and remember to remove.
#
# Not the `github` context: a called workflow inherits the CALLER's, so
# `github.workflow_ref`/`workflow_sha` name the data repo's own deploy.yml,
# and `github.job_workflow_sha` is not a property at all — it is an OIDC
# token claim. An unknown property evaluates to null rather than failing,
# which would have silently resolved `ref:` to support's default branch.
- name: Checkout relaton/support (branding source of truth)
uses: actions/checkout@v4
with:
repository: ${{ job.workflow_repository }}
ref: ${{ inputs.support-ref || job.workflow_sha }}
path: .relaton-support
# Nothing pushes from here; no reason to leave a credential in the
# workspace's .git/config.
persist-credentials: false
# Cone mode (the default) is correct — these are directories, not
# globs. Do not add sparse-checkout-cone-mode.
sparse-checkout: |
bin
lib
data-index

- uses: ruby/setup-ruby@v1
with:
ruby-version: "3.4"

# Ahead of the source-specific steps on purpose: the git-source build
# exports BUNDLE_GEMFILE to $GITHUB_ENV for every later step, and a
# branding failure should surface before a ~10-minute frontend compile,
# not after it.
- name: Resolve branding
id: branding
# The inputs travel by env for the same reason the build steps below do,
# and so nothing caller-supplied is interpolated into this shell.
env:
TITLE: ${{ inputs.title }}
FAVICON: ${{ inputs.favicon }}
DESCRIPTION: ${{ inputs.description }}
run: |
ruby .relaton-support/bin/index-branding "$GITHUB_REPOSITORY" \
--title "$TITLE" \
--favicon "$FAVICON" \
--description "$DESCRIPTION" \
>> "$GITHUB_OUTPUT"

# ---- gem source (default): install the released gem, Ruby only ----------
- name: Install relaton-cli (released gem)
if: inputs.source == 'gem'
Expand Down Expand Up @@ -176,47 +246,42 @@ jobs:
echo "BUNDLE_GEMFILE=$RUNNER_TEMP/Gemfile.index" >> "$GITHUB_ENV"
BUNDLE_GEMFILE="$RUNNER_TEMP/Gemfile.index" bundle install

- name: Derive title
id: meta
run: |
title="${{ inputs.title }}"
if [ -z "$title" ]; then
flavor="${GITHUB_REPOSITORY##*/}" # relaton-data-bipm
flavor="${flavor#relaton-data-}" # bipm
title="$(echo "$flavor" | tr '[:lower:]' '[:upper:]') Index"
fi
echo "title=$title" >> "$GITHUB_OUTPUT"

- name: Build index (gem source)
if: inputs.source == 'gem'
# The branding values are free-form prose, and `${{ }}` is substituted as
# raw text before bash parses the line — an inlined description reading
# `IEC "TC 1" registry` would word-split into stray argv entries. Passing
# them by env hands the runner's verbatim value to the shell.
# All three branding values are free-form prose, and `${{ }}` is
# substituted as raw text before bash parses the line — an inlined
# description reading `IEC "TC 1" registry` would word-split into stray
# argv entries, and one containing $(...) would execute. Passing them by
# env hands the runner's verbatim value to the shell. `title` belongs
# here too now that it originates in relaton/support rather than in the
# calling repo's own file.
env:
FAVICON: ${{ inputs.favicon }}
DESCRIPTION: ${{ inputs.description }}
TITLE: ${{ steps.branding.outputs.title }}
FAVICON: ${{ steps.branding.outputs.favicon }}
DESCRIPTION: ${{ steps.branding.outputs.description }}
run: |
relaton index "${{ inputs.data-dir }}" \
--output _site \
--mode "${{ inputs.mode }}" \
--title "${{ steps.meta.outputs.title }}" \
--title "$TITLE" \
--favicon "$FAVICON" \
--description "$DESCRIPTION" \
--base-url "https://raw.githubusercontent.com/${GITHUB_REPOSITORY}/${GITHUB_REF_NAME}"

- name: Build index (git source)
if: inputs.source == 'git'
# BUNDLE_GEMFILE is exported to GITHUB_ENV in the build step above.
# FAVICON/DESCRIPTION travel by env for the same reason as the gem step.
# TITLE/FAVICON/DESCRIPTION travel by env for the same reason as the gem
# step.
env:
FAVICON: ${{ inputs.favicon }}
DESCRIPTION: ${{ inputs.description }}
TITLE: ${{ steps.branding.outputs.title }}
FAVICON: ${{ steps.branding.outputs.favicon }}
DESCRIPTION: ${{ steps.branding.outputs.description }}
run: |
bundle exec relaton index "${{ github.workspace }}/${{ inputs.data-dir }}" \
--output "${{ github.workspace }}/_site" \
--mode "${{ inputs.mode }}" \
--title "${{ steps.meta.outputs.title }}" \
--title "$TITLE" \
--favicon "$FAVICON" \
--description "$DESCRIPTION" \
--base-url "https://raw.githubusercontent.com/${GITHUB_REPOSITORY}/${GITHUB_REF_NAME}"
Expand Down
63 changes: 63 additions & 0 deletions bin/index-branding
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
#!/usr/bin/env ruby
# frozen_string_literal: true

# Resolve a relaton-data-* repo's Pages branding from data-index/configs.yml and
# print it in `$GITHUB_OUTPUT` format. Run by the "Resolve branding" step of
# .github/workflows/data-deploy.yml, which appends the result:
#
# ruby .relaton-support/bin/index-branding "$GITHUB_REPOSITORY" \
# --title "$TITLE" --favicon "$FAVICON" --description "$DESCRIPTION" \
# >> "$GITHUB_OUTPUT"
#
# Branding is resolved centrally because cimas.yml maps
# `.github/workflows/deploy.yml` as a whole-file copy for 29 repos: a `with:`
# block carrying it is wiped on the next `cimas sync`, and the site silently
# loses its favicon and description. See data-index/README.adoc.
#
# The three flags are passed unconditionally by the workflow, so a blank value
# means "the caller set no input" and falls through to configs.yml.
#
# Usage:
# bin/index-branding relaton/relaton-data-iana
# bin/index-branding relaton-data-iana --favicon https://example.org/f.ico
# bin/index-branding iana --config path/to/configs.yml

require "optparse"

require_relative "../lib/data_index_config"
require_relative "../lib/github_output"

options = { config: DataIndexConfig::DEFAULT_CONFIG_PATH }
parser = OptionParser.new do |o|
o.banner = "Usage: bin/index-branding OWNER/REPO [options]"
o.on("--title TITLE", "Explicit title, overriding configs.yml") { |v| options[:title] = v }
o.on("--favicon URL", "Explicit favicon, overriding configs.yml") { |v| options[:favicon] = v }
o.on("--description TEXT", "Explicit description, overriding configs.yml") do |v|
options[:description] = v
end
o.on("--config PATH", "Path to configs.yml") { |v| options[:config] = v }
o.on("-h", "--help", "Show this help") do
puts o
exit
end
end
parser.parse!(ARGV)

if ARGV.size != 1
warn parser
exit 1
end

repo = ARGV.fetch(0)
branding = DataIndexConfig.load(options[:config]).branding(
repo,
title: options[:title], favicon: options[:favicon], description: options[:description]
)

# Diagnostics on stderr so they reach the Actions log without landing in
# $GITHUB_OUTPUT, which stdout is redirected to.
warn "#{repo}: #{branding.map { |k, v| "#{k}=#{v.inspect}" }.join(' ')}"

# Rendered whole and written once: a partial write would leave a dangling
# heredoc opener and corrupt every later step's outputs.
print GithubOutput.render(branding)
14 changes: 14 additions & 0 deletions cimas-config/gh-actions/data/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -49,4 +49,18 @@ jobs:
# both centrally — it skips a `workflow_run` whose crawler failed, and
# serializes Pages deployments per repo — so every caller gets them without
# a copy of its own to drift.
#
# And no `with:` block, ever. Cimas copies this file byte-for-byte into 29
# relaton-data-* repos, so an input set here is either wrong for the other 28
# or, once a repo hand-edits it, silently reverted by the next `cimas sync` —
# the page just loses its favicon and description, with nothing red in CI.
# Per-repo branding (title/favicon/description) lives in relaton/support's
# `data-index/configs.yml`, which data-deploy.yml reads at build time.
# Pinned by spec/data_deploy_caller_template_spec.rb and, for every synced
# template, by spec/cimas_data_pages_spec.rb.
#
# The known exception is the temporary `source: git` pin several repos carry
# while relaton-cli ships no `index` command. That one is deliberately NOT
# centralised: losing it fails loudly with `Could not find command "index"`,
# so a sync that wipes it cannot go unnoticed.
uses: relaton/support/.github/workflows/data-deploy.yml@main
Loading
Loading