Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 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
18 changes: 11 additions & 7 deletions content/en/_index.html
Original file line number Diff line number Diff line change
Expand Up @@ -52,13 +52,17 @@ <h2>The Absolute Beginner's Guide To Cloud Native</h2>
{{< /blocks/section >}}
{{< blocks/section id="upcoming-events" >}}
<h2>Attend upcoming KubeCon + CloudNativeCon events</h2>
<!-- TODO: change this to a shortcode that auto-updates from a schedule -->
<div>
<a href="https://events.linuxfoundation.org/kubecon-cloudnativecon-europe-2026/" class="desktopKCButton"><strong>Europe</strong> (Amsterdam, Mar 23-26, 2026)</a>
</div>
<div>
<a href="https://events.linuxfoundation.org/kubecon-cloudnativecon-north-america-2026/" class="desktopKCButton"><strong>North America</strong> (Salt Lake City, Nov 9-12, 2026)</a>
</div>
<!--
{{< kubecon-events >}} renders event listings from data/events/kubecon.yaml.
Note for localization:
- Region names support i18n via kubecon_region_* keys.
- City names remain in English.
- Dates are stored in ISO 8601 and formatted by Hugo's time.Format.
- Localized pages may replace this shortcode with a static, fully localized version if needed.
- If a static version is used, keeping event information up to date becomes the responsibility of the localization team.
-->
{{< kubecon-events >}}
Comment thread
lmktfy marked this conversation as resolved.

{{< /blocks/section >}}

{{< blocks/kubernetes-features >}}
Expand Down
23 changes: 23 additions & 0 deletions data/events/kubecon.yaml
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Tiny nit: I would use the path data/events/kubecon.yaml (and revise the other code accordingly).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Okay, thank you

Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Do not manually edit this file
# This file is auto-generated by scripts/fetch_kubecon_events.py
# To update, run:
# python3 scripts/fetch_kubecon_events.py
#
# Last updated: 2026-03-17 15:53:29
# Source: https://events.linuxfoundation.org/about/calendar/?_sf_s=kubecon

events:
- name: KubeCon + CloudNativeCon Europe
region: Europe
start_date: '2026-03-23'
end_date: '2026-03-26'
location: Amsterdam, Netherlands
url: https://events.linuxfoundation.org/kubecon-cloudnativecon-europe/
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

If you fetch that URL, the following Linked Data is available:

{
    "@context": "http://schema.org/",
    "@type": "Event",
    "name": "KubeCon + CloudNativeCon Europe",
    "startDate": "2026-03-23",
    "endDate": "2026-03-26",
    "eventAttendanceMode": "https://schema.org/OfflineEventAttendanceMode",
    "eventStatus": "https://schema.org/EventScheduled",
    "location": [
        {
            "@type": "Place",
            "name": "",
            "address": {
                "@type": "PostalAddress",
                "streetAddress": "",
                "addressLocality": "Amsterdam",
                "postalCode": "",
                "addressRegion": "",
                "addressCountry": "Netherlands"
            }
        }
    ],
    "image": [
        "https://events.linuxfoundation.org/wp-content/uploads/2025/10/Social-Snackable.png"
    ],
    "description": "The Cloud Native Computing Foundation’s flagship conference gathers adopters and technologists from leading open source and cloud native communities in Amsterdam, The Netherlands from 23-26 March, 2026. Join our CNCF Graduated and Incubating Projects as the community gathers for four days to further the education and advancement of cloud native computing."
}

We can also, I think, extract the event colors eg #b0ddff

Might be worth doing … perhaps in a follow up PR?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Yeah, i will check this with a follow up PR

- name: KubeCon + CloudNativeCon India
region: India
start_date: '2026-06-18'
end_date: '2026-06-19'
location: Mumbai, India
url: https://events.linuxfoundation.org/kubecon-cloudnativecon-india/
last_updated: '2026-03-17 15:53:29'
source: https://events.linuxfoundation.org/about/calendar/?_sf_s=kubecon
18 changes: 18 additions & 0 deletions i18n/en/en.toml
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,24 @@ of Katacoda.</p>
2023</b>. You are seeing this notice because this particular page has not yet been updated
following that shutdown.</p>"""

[kubecon_region_china]
other = "China"

[kubecon_region_europe]
other = "Europe"

[kubecon_region_event]
other = "Event"

[kubecon_region_india]
other = "India"

[kubecon_region_japan]
other = "Japan"

[kubecon_region_north_america]
other = "North America"

[kubectl_ref_collapse_all]
other = "Collapse all"

Expand Down
1 change: 0 additions & 1 deletion i18n/pl/pl.toml
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This was great as an example, but please now remove this and squash it out so we can move the pull request forward.

Original file line number Diff line number Diff line change
Expand Up @@ -269,7 +269,6 @@ other = "Wyszukaj polecenia lub opis ..."

[kubectl_ref_view_full]
other = "Zobacz pełną dokumentację"

Copy link
Copy Markdown
Member

@dkarczmarski dkarczmarski May 7, 2026

Choose a reason for hiding this comment

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

the change at line 290 seems unnecessary :)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Agreed, we should probably not change localizations in this PR.

[kubernetes]
other = "Kubernetes"

Expand Down
14 changes: 14 additions & 0 deletions layouts/shortcodes/kubecon-events.html
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This has English text in it, and as such will be difficult to localize. I am not yet sure that merging this PR (as is) would be an improvement.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I agree, the city names and dates are still English because from the conversations, I think it's okay to keep them in English across all locales (or what do you think? everything should be localize?). For the date I can switch to ISO the Hugo time formatter.

Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{{ $data := .Site.Data.events.kubecon }}

{{ range $data.events -}}
{{ $location := split .location "," -}}
{{ $city := index $location 0 -}}
{{ $startDate := time.AsTime .start_date -}}
{{ $endDate := time.AsTime .end_date -}}
{{ $regionKeys := dict "North America" "kubecon_region_north_america" "Europe" "kubecon_region_europe" "India" "kubecon_region_india" "Japan" "kubecon_region_japan" "China" "kubecon_region_china" "Event" "kubecon_region_event" }}
Copy link
Copy Markdown
Member

@lmktfy lmktfy May 17, 2026

Choose a reason for hiding this comment

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

nit: could put this mapping in (eg) data/events/region_name_heuristics.yaml

{{ $regionKey := index $regionKeys .region | default "kubecon_region_event" }}
{{ $regionName := T $regionKey }}
<div>
<a href="{{ .url }}" class="desktopKCButton"><strong>{{ $regionName }}</strong> ({{ $city }}, {{ $startDate | time.Format "Jan 2" }}–{{ $endDate | time.Format "2" }})</a>
</div>
{{- end }}
208 changes: 208 additions & 0 deletions scripts/fetch_kubecon_events.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
#!/usr/bin/env python3
"""
Fetch KubeCon events from Linux Foundation website and generate Hugo data file.
Requirements: pip install requests beautifulsoup4 pyyaml
"""

import requests
from bs4 import BeautifulSoup
import yaml
import re
from datetime import datetime
from typing import Optional

# Configuration
EVENT_URL = "https://events.linuxfoundation.org/about/calendar/?_sf_s=kubecon"
EVENT_LIMIT = 2
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

nit: why 2? If there are 3 events coming up this would skip one.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I guess if we really want to show 3 upcoming events, change this locally and run the script with that setting.

OUTPUT_PATH = "data/events/kubecon.yaml"

def _is_valid_kubecon_title(title: str) -> bool:
"""Check if title is a valid KubeCon event"""
if not title.startswith('KubeCon + CloudNativeCon'):
print(f"Skipped (wrong format): {title}")
return False

if title.count('+') > 1:
print(f"Skipped (joint conference): {title}")
return False

colocated_keywords = [
'ArgoCon', 'BackstageCon', 'CiliumCon', 'FluxCon',
'WasmCon', 'Observability', 'Platform Engineering',
'Cloud Native AI', 'Telco', 'Edge', 'Keycloak',
'Kyverno', 'OpenTofu', 'Security', 'Agentics'
]

if any(keyword in title for keyword in colocated_keywords):
print(f"Skipped (co-located event): {title}")
return False

return True

def _extract_region(title: str) -> str:
"""Extract region from event title"""
regions = {
"North America": "North America",
"Europe": "Europe",
"India": "India",
"Japan": "Japan",
"China": "China",
}
return next((region for key, region in regions.items() if key in title), "Event")

def _parse_date_range(date_text: str) -> Optional[tuple[str, str]]:
print(f" Parsing date: {date_text}")
"""Parse date range text like 'Mar 23–26, 2026' into ISO 8601 dates."""
text = re.sub(r'[–—]', '-', date_text)
# Match: "Mon DD - [Mon] DD, YYYY"
m = re.match(r'(\w+)\s+(\d+)\s*-\s*(?:(\w+)\s+)?(\d+),?\s*(\d{4})', text)
if not m:
print(f" Warning: Could not parse date range: {date_text}")
return None
start_month, start_day, end_month, end_day, year = m.groups()
end_month = end_month or start_month
try:
start = datetime.strptime(f"{start_month} {start_day} {year}", "%b %d %Y")
end = datetime.strptime(f"{end_month} {end_day} {year}", "%b %d %Y")
return start.strftime("%Y-%m-%d"), end.strftime("%Y-%m-%d")
except ValueError:
print(f" Warning: Could not parse date range: {date_text}")
return None


def _extract_event_data(article) -> Optional[dict]:
"""Extract event data from article element"""
title_elem = article.find('h5')
if not title_elem:
return None

link_elem = title_elem.find('a')
if not link_elem:
return None

title = link_elem.get_text(strip=True)

if not _is_valid_kubecon_title(title):
return None

url = link_elem.get('href', '')

# Extract date and parse to ISO 8601
start_date = ""
end_date = ""
date_span = article.find('span', class_='date')
if date_span:
date_text = re.sub(r'\s+', ' ', date_span.get_text(strip=True)).strip()
parsed = _parse_date_range(date_text)
if parsed:
start_date, end_date = parsed

# Extract location
location = "TBA"
country_span = article.find('span', class_='country')
if country_span:
location = re.sub(r'\s+', ' ', country_span.get_text(strip=True)).strip()

return {
'name': title,
'region': _extract_region(title),
'start_date': start_date,
'end_date': end_date,
'location': location,
'url': url,
}

def fetch_kubecon_events(url: str = EVENT_URL) -> list[dict]:
"""Scrape KubeCon events from Linux Foundation calendar"""

headers = {
'User-Agent': 'kubernetes-website-bot/1.0 (+https://github.com/kubernetes/website)'
}

print(f"Fetching from: {url}")
response = requests.get(url, headers=headers)
response.raise_for_status()

soup = BeautifulSoup(response.text, 'html.parser')
events = []

# Find all event articles in the search results
event_articles = soup.find_all('article', class_='callout')
print(f"Found {len(event_articles)} total events on page\n")

for article in event_articles:
try:
event_data = _extract_event_data(article)
if event_data:
events.append(event_data)
print(f"Matched: {event_data['name']}")
except Exception as e:
print(f"Error parsing event: {e}")
continue

return events

def generate_yaml_file(events: list[dict], output_path: str = OUTPUT_PATH, limit: int = EVENT_LIMIT) -> None:
"""Generate Hugo data file"""

# Limit events if specified
if limit and limit > 0:
events = events[:limit]
print(f"\nLimiting to first {limit} events")

data = {
'events': events,
'last_updated': datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
'source': EVENT_URL
}

with open(output_path, 'w') as f:
f.write("# Do not manually edit this file\n")
f.write("# This file is auto-generated by scripts/fetch_kubecon_events.py\n")
f.write("# To update, run:\n")
f.write("# python3 scripts/fetch_kubecon_events.py\n")
f.write("#\n")
f.write(f"# Last updated: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n")
f.write(f"# Source: {EVENT_URL}\n")
f.write("\n")
yaml.dump(data, f, default_flow_style=False, allow_unicode=True, sort_keys=False)

print(f"\nGenerated {output_path}")
print(f" {len(events)} KubeCon events")

if __name__ == '__main__':
print("=" * 60)
print("KubeCon Events Fetcher")
print("=" * 60)

try:
events = fetch_kubecon_events()

if not events:
print("\nNo matching KubeCon events found!")
print(" Looking for: 'KubeCon + CloudNativeCon <region>'")
exit(1)

print(f"\n{'=' * 60}")
print("Generating YAML data file...")
print("=" * 60)

generate_yaml_file(events)

print("\n" + "=" * 60)
print("Success!")
print("=" * 60)
print("\nMatched events:")
for i, event in enumerate(events[:EVENT_LIMIT], 1):
print(f" {i}. {event['name']}")
print(f" {event['location']} - {event['start_date']} to {event['end_date']}")

except requests.exceptions.RequestException as e:
print(f"\nNetwork Error: {e}")
print(" Could not fetch the events page.")
Comment on lines +263 to +264
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

nit: could write this to stderr

exit(1)
except Exception as e:
print(f"\nError: {e}")
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
print(f"\nError: {e}")
print(f"\nError: {e}", file=sys.stderr)

import traceback
traceback.print_exc()
exit(1)
2 changes: 2 additions & 0 deletions scripts/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
requests>=2.28,<3
click>=8.0,<9
jinja2>=3.1,<4
beautifulsoup4>=4.12.0
pyyaml>=6.0