Skip to content

Commit 75bfac3

Browse files
authored
feat: Add configurable redirects.
Generate redirects
2 parents f51dd04 + aebb7e4 commit 75bfac3

File tree

7 files changed

+204
-0
lines changed

7 files changed

+204
-0
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,4 @@ vendor
77
*.iml
88
*/bootstrap/
99
_config-overrides.yml
10+
.ruby-version

Gemfile.lock

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,12 @@ GEM
2222
eventmachine (>= 0.12.9)
2323
http_parser.rb (~> 0)
2424
eventmachine (1.2.7)
25+
ffi (1.17.2-arm64-darwin)
2526
ffi (1.17.2-x86_64-linux-gnu)
2627
forwardable-extended (2.6.0)
28+
google-protobuf (4.31.1-arm64-darwin)
29+
bigdecimal
30+
rake (>= 13)
2731
google-protobuf (4.31.1-x86_64-linux-gnu)
2832
bigdecimal
2933
rake (>= 13)
@@ -73,6 +77,8 @@ GEM
7377
rb-fsevent (~> 0.10, >= 0.10.3)
7478
rb-inotify (~> 0.9, >= 0.9.10)
7579
mercenary (0.4.0)
80+
nokogiri (1.18.8-arm64-darwin)
81+
racc (~> 1.4)
7682
nokogiri (1.18.8-x86_64-linux-gnu)
7783
racc (~> 1.4)
7884
pathutil (0.16.2)
@@ -86,6 +92,8 @@ GEM
8692
rexml (3.4.1)
8793
rouge (4.5.2)
8894
safe_yaml (1.0.5)
95+
sass-embedded (1.89.2-arm64-darwin)
96+
google-protobuf (~> 4.31)
8997
sass-embedded (1.89.2-x86_64-linux-gnu)
9098
google-protobuf (~> 4.31)
9199
terminal-table (3.0.2)
@@ -94,6 +102,7 @@ GEM
94102
webrick (1.9.1)
95103

96104
PLATFORMS
105+
arm64-darwin-24
97106
x86_64-linux
98107

99108
DEPENDENCIES

README.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,3 +60,16 @@ should go under `/assets/`. To try and preserve a bit of sanity to the repo we s
6060
Some content such as the use-cases include diagrams drawn in Excalidraw. Browsers can't handle the format natively, so instead, in addition to
6161
commiting the `.excalidraw` orginals to the repo, we also export a transparent `.png` for inclusion in the content.
6262

63+
# Redirects
64+
There is nothing more frustrating than reading some documentation or a stack trace with a link to some helpful sounding answer only for that link to be a 404. To try and minimise that pain we have built a Jekyll plugin to generate a redirect from a token to a URL. This plug-in generates a collection of static pages derived from the yaml documents in `_data/redirects`.
65+
Each yaml document defines a namespace for redirections. For example `errors.yaml` will build pages under `/redirects/errors/`. The yaml document takes the format outlined here:
66+
67+
```yaml
68+
baseUrl: https://kroxylicious.io/documentation/ #The base URL to build redirects from
69+
delay: 3 #How long should the re-direction message be shown before loading the target. Defaults to 1.
70+
mappings:
71+
- name: test #the token to be the landing url
72+
fromVersion: 0.10.0 #Optional, if not specified the latest release at time of site build is used.
73+
toVersion: 0.12.0 #Optional, if not specified the latest release at time of site build is used.
74+
path: /html/kroxylicious-proxy/#con-configuring-client-connections-proxy # the path within the baseUrl
75+
```

_config.yml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,3 +83,10 @@ asciidoctor:
8383
toc: preamble
8484
source-highlighter: rouge
8585
sectanchors: ''
86+
87+
defaults:
88+
- scope:
89+
type: pages
90+
path: /redirects
91+
values:
92+
layout: redirect.html

_data/redirects/errors.yaml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
baseUrl: https://kroxylicious.io/documentation/
2+
delay: 3
3+
mappings:
4+
- name: clientTls
5+
fromVersion: 0.13.0
6+
path: /html/kroxylicious-proxy/#con-configuring-client-connections-proxy
7+
8+
9+

_layouts/redirect.html

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<!doctype html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="utf-8">
5+
<meta name="viewport" content="width=device-width, initial-scale=1">
6+
<title>{% if page.title %}{{ page.title }} | {% endif %}{{ site.title }} | {{ site.description }}</title>
7+
<link rel="icon" href="{{ '/favicon.ico' | absolute_url }}" type="image/x-icon"/>
8+
<link rel="stylesheet" href="{{ '/css/style.css' | absolute_url }}"/>
9+
<link rel="stylesheet"
10+
href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-icons/1.11.1/font/bootstrap-icons.min.css"
11+
integrity="sha512-oAvZuuYVzkcTc2dH5z1ZJup5OmSQ000qlfRvuoTTiyTBjwX1faoyearj8KdMq0LgsBTHMrRuMek7s+CxF8yE+w=="
12+
crossorigin="anonymous" referrerpolicy="no-referrer"/>
13+
<script src="https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.2/js/bootstrap.bundle.min.js"
14+
integrity="sha512-X/YkDZyjTf4wyc2Vy16YGCPHwAY8rZJY+POgokZjQB2mhIRFJCckEGc6YyX9eNsPfn0PzThEuNs+uaomE5CO6A=="
15+
crossorigin="anonymous" referrerpolicy="no-referrer"></script>
16+
<meta http-equiv="refresh" content="{{ page.delay }}; url={{ page.target }}">
17+
</head>
18+
<body class="d-flex flex-column min-vh-100" data-bs-theme="light">
19+
{% include nav.html %}
20+
<div class="container-fluid px-0 krx-content" data-bs-theme="light">
21+
<div class="paragraph" role="main">
22+
<div>Redirecting to <a href="{{ page.target }}">{{ page.target }}</a>...</div>
23+
<div class="spinner-border text-success" role="status">
24+
<span class="visually-hidden">waiting...</span>
25+
</div>
26+
</div>
27+
</div>
28+
{% include footer.html %}
29+
</body>
30+
<body>

_plugins/redirector.rb

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
module SamplePlugin
2+
class RedirectPageGenerator < Jekyll::Generator
3+
safe true
4+
5+
def generate(site)
6+
config = site.data['redirects']
7+
latest_release = Version.parse(site.data['kroxylicious']['latestRelease'])
8+
releases = known_releases(site)
9+
config.each { |redirect_config|
10+
Jekyll.logger.info "generating redirects for #{redirect_config[0]}"
11+
redirect_config[1]['mappings'].each do |mapping|
12+
to_version = Version.parse(mapping['toVersion'] ||= latest_release)
13+
from_version = Version.parse(mapping['fromVersion'] ||= latest_release)
14+
versions = releases.select { |rel| rel.between?(from_version, to_version) }
15+
versions.each { |version|
16+
mapping['version'] = version
17+
site.pages << RedirectPage.new(site, redirect_config[0], redirect_config[1], mapping)
18+
}
19+
end
20+
Jekyll.logger.info "Generated redirects #{redirect_config[0]}"
21+
}
22+
end
23+
24+
private
25+
26+
def known_releases(site)
27+
releases = []
28+
site.data['release'].sort.each do |release|
29+
releases << Version.parse(release[0])
30+
end
31+
releases.sort_by { |version| [version.major, version.minor, version.patch] }
32+
end
33+
end
34+
35+
# Subclass of `Jekyll::Page` with custom method definitions.
36+
class RedirectPage < Jekyll::Page
37+
def initialize(site, group, redirectConfig, mapping)
38+
39+
@site = site # the current site instance.
40+
@base = site.source # path to the source directory.
41+
@dir = "/redirect/#{group}/#{mapping['version']}/" # the directory the page will reside in.
42+
43+
# All pages have the same filename, so define attributes straight away.
44+
@basename = mapping['name'] # filename without the extension.
45+
@ext = '.html' # the extension.
46+
@name = basename + ext # basically @basename + @ext.
47+
@layout = 'redirect.html'
48+
delay = redirectConfig['delay'] ||= 1
49+
@data = {
50+
'target' => "#{redirectConfig['baseUrl']}#{mapping['version']}#{mapping['path']}",
51+
'layout' => 'redirect',
52+
'delay' => "#{delay}",
53+
}
54+
55+
Jekyll.logger.info "generated redirect from #{@dir}#{@basename} to #{data['target']}"
56+
end
57+
58+
# Placeholders that are used in constructing page URL.
59+
def url_placeholders
60+
{
61+
:path => @dir,
62+
:category => @dir,
63+
:basename => basename,
64+
:output_ext => output_ext,
65+
}
66+
end
67+
end
68+
69+
class Version
70+
include Comparable
71+
72+
# @return [Integer]
73+
attr_reader :major
74+
75+
# @return [Integer]
76+
attr_reader :minor
77+
78+
# @return [Integer]
79+
attr_reader :patch
80+
81+
def self.parse(version_string)
82+
if version_string.is_a? Version
83+
return version_string
84+
end
85+
if version_string.include? '_'
86+
parts = version_string.split('_').map { |x| x.to_i }
87+
elsif version_string.include? '.'
88+
parts = version_string.split('.').map { |x| x.to_i }
89+
else
90+
raise "Invalid version string: #{version_string}"
91+
end
92+
instance = allocate
93+
instance.send(:initialize, parts[0], parts[1], parts[2])
94+
instance
95+
end
96+
97+
# @param [Integer] major
98+
# @param [Integer] minor
99+
# @param [Integer] patch
100+
def initialize(
101+
major,
102+
minor,
103+
patch
104+
)
105+
@major = major.to_i
106+
@minor = minor.to_i
107+
@patch = patch.to_i
108+
end
109+
110+
# @return [String]
111+
def to_s
112+
"#{major}.#{minor}.#{patch}"
113+
end
114+
115+
def ==(other)
116+
to_s == other.to_s
117+
end
118+
119+
# See section #11 of https://semver.org/spec/v2.0.0.html
120+
# @return [Integer, nil] Returns -1, 0, or 1. or Nil if other is unsortable
121+
def <=>(other)
122+
if other.is_a? Version
123+
[major, minor, patch] <=> [other.major, other.minor, other.patch]
124+
else
125+
nil
126+
end
127+
end
128+
129+
def between?(min, max)
130+
if (min.is_a? Version) && (max.is_a? Version)
131+
self >= min && self <= max
132+
end
133+
end
134+
end
135+
end

0 commit comments

Comments
 (0)